blob: 372433050ad606bdf73d632c4a74eb437f45533d [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fbl/unique_ptr.h>
#include <hypervisor/bits.h>
#include <hypervisor/vcpu.h>
#include <hypervisor/virtio.h>
#include <zircon/syscalls/port.h>
#include <virtio/virtio.h>
#include <virtio/virtio_ring.h>
#include "virtio_priv.h"
/* Accesses to this range are device specific. */
#define VIRTIO_PCI_DEVICE_CFG_BASE VIRTIO_PCI_CONFIG_OFFSET_NOMSI
#define VIRTIO_PCI_DEVICE_CFG_TOP(size) (VIRTIO_PCI_DEVICE_CFG_BASE + size - 1)
static virtio_device_t* pci_device_to_virtio(const pci_device_t* device) {
return static_cast<virtio_device_t*>(device->impl);
}
static virtio_queue_t* selected_queue(const virtio_device_t* device) {
return device->queue_sel < device->num_queues ? &device->queues[device->queue_sel] : nullptr;
}
// Virtio 1.0 Section 4.1.5.1.3:
//
// When using the legacy interface, the queue layout follows 2.4.2 Legacy
// Interfaces: A Note on Virtqueue Layout with an alignment of 4096. Driver
// writes the physical address, divided by 4096 to the Queue Address field 2.
static zx_status_t virtio_queue_set_pfn(virtio_queue_t* queue, uint32_t pfn) {
uintptr_t desc_paddr = pfn * PAGE_SIZE;
uintptr_t desc_size = queue->size * sizeof(queue->desc[0]);
virtio_queue_set_desc_addr(queue, desc_paddr);
uintptr_t avail_paddr = desc_paddr + desc_size;
uintptr_t avail_size = sizeof(*queue->avail)
+ (queue->size * sizeof(queue->avail->ring[0]))
+ sizeof(*queue->used_event);
virtio_queue_set_avail_addr(queue, avail_paddr);
uintptr_t used_paddr = align(avail_paddr + avail_size, 4096);
virtio_queue_set_used_addr(queue, used_paddr);
return ZX_OK;
}
zx_status_t virtio_pci_legacy_read(const pci_device_t* pci_device, uint8_t bar, uint16_t port,
uint8_t access_size, zx_vcpu_io_t* vcpu_io) {
if (bar != 0)
return ZX_ERR_NOT_SUPPORTED;
virtio_device_t* device = pci_device_to_virtio(pci_device);
const virtio_queue_t* queue = selected_queue(device);
switch (port) {
case VIRTIO_PCI_DEVICE_FEATURES:
vcpu_io->access_size = 4;
vcpu_io->u32 = device->features;
return ZX_OK;
case VIRTIO_PCI_QUEUE_PFN:
if (!queue)
return ZX_ERR_NOT_SUPPORTED;
vcpu_io->access_size = 4;
vcpu_io->u32 = static_cast<uint32_t>(queue->addr.desc / PAGE_SIZE);
return ZX_OK;
case VIRTIO_PCI_QUEUE_SIZE:
if (!queue)
return ZX_ERR_NOT_SUPPORTED;
vcpu_io->access_size = 2;
vcpu_io->u16 = queue->size;
return ZX_OK;
case VIRTIO_PCI_DEVICE_STATUS:
vcpu_io->access_size = 1;
vcpu_io->u8 = device->status;
return ZX_OK;
case VIRTIO_PCI_ISR_STATUS:
vcpu_io->access_size = 1;
mtx_lock(&device->mutex);
vcpu_io->u8 = device->isr_status;
// From VIRTIO 1.0 Section 4.1.4.5:
//
// To avoid an extra access, simply reading this register resets it to
// 0 and causes the device to de-assert the interrupt.
device->isr_status = 0;
mtx_unlock(&device->mutex);
return ZX_OK;
}
// Handle device-specific accesses.
if (port >= VIRTIO_PCI_DEVICE_CFG_BASE) {
uint16_t device_offset = static_cast<uint16_t>(port - VIRTIO_PCI_DEVICE_CFG_BASE);
return device->ops->read(device, device_offset, access_size, vcpu_io);
}
fprintf(stderr, "Unhandled virtio device read %#x\n", port);
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t virtio_pci_legacy_write(pci_device_t* pci_device, uint8_t bar, uint16_t port,
const zx_vcpu_io_t* io) {
if (bar != 0)
return ZX_ERR_NOT_SUPPORTED;
virtio_device_t* device = pci_device_to_virtio(pci_device);
virtio_queue_t* queue = selected_queue(device);
switch (port) {
case VIRTIO_PCI_DRIVER_FEATURES:
if (io->access_size != 4)
return ZX_ERR_IO_DATA_INTEGRITY;
// Currently we expect the driver to accept all our features.
if (io->u32 != device->features)
return ZX_ERR_INVALID_ARGS;
return ZX_OK;
case VIRTIO_PCI_DEVICE_STATUS:
if (io->access_size != 1)
return ZX_ERR_IO_DATA_INTEGRITY;
device->status = io->u8;
return ZX_OK;
case VIRTIO_PCI_QUEUE_PFN: {
if (io->access_size != 4)
return ZX_ERR_IO_DATA_INTEGRITY;
if (!queue)
return ZX_ERR_NOT_SUPPORTED;
return virtio_queue_set_pfn(queue, io->u32);
}
case VIRTIO_PCI_QUEUE_SIZE:
if (io->access_size != 2)
return ZX_ERR_IO_DATA_INTEGRITY;
queue->size = io->u16;
return ZX_OK;
case VIRTIO_PCI_QUEUE_SELECT:
if (io->access_size != 2)
return ZX_ERR_IO_DATA_INTEGRITY;
if (io->u16 >= device->num_queues) {
fprintf(stderr, "Selected queue does not exist.\n");
return ZX_ERR_NOT_SUPPORTED;
}
device->queue_sel = io->u16;
return ZX_OK;
case VIRTIO_PCI_QUEUE_NOTIFY: {
if (io->access_size != 2)
return ZX_ERR_IO_DATA_INTEGRITY;
return virtio_device_kick(device, io->u16);
}
}
// Handle device-specific accesses.
if (port >= VIRTIO_PCI_DEVICE_CFG_BASE) {
uint16_t device_offset = static_cast<uint16_t>(port - VIRTIO_PCI_DEVICE_CFG_BASE);
return device->ops->write(device, device_offset, io);
}
fprintf(stderr, "Unhandled virtio device write %#x\n", port);
return ZX_ERR_NOT_SUPPORTED;
}