| // 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; |
| } |