| // 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 <machina/virtio_pci.h> |
| |
| #include <stdio.h> |
| |
| #include <fbl/auto_lock.h> |
| #include <hypervisor/bits.h> |
| #include <machina/virtio.h> |
| #include <virtio/virtio_ids.h> |
| |
| static uint8_t kPciCapTypeVendorSpecific = 0x9; |
| |
| static uint16_t kPciVendorIdVirtio = 0x1af4; |
| |
| /* Virtio PCI Bar Layout. |
| * |
| * Expose all read/write fields on BAR0 using a strongly ordered mapping. |
| * Map the Queue notify region to BAR1 with a BELL type that does not require |
| * the guest to decode any instruction fields. The queue to notify can be |
| * inferred based on the address accessed alone. |
| * |
| * BAR0 BAR1 |
| * ------------ 00h ------------ 00h |
| * | Virtio PCI | | Queue 0 | |
| * | Common | | Notify | |
| * | Config | |------------| 04h |
| * |------------| 38h | Queue 1 | |
| * | ISR Config | | Notify | |
| * |------------| 3ch |------------| |
| * | Device- | | ... | |
| * | Specific | |------------| 04 * N |
| * | Config | | Queue N | |
| * | | | Notify | |
| * ------------ ------------ |
| * These structures are defined in Virtio 1.0 Section 4.1.4. |
| */ |
| static const uint8_t kVirtioPciBar = 0; |
| static const uint8_t kVirtioPciNotifyBar = 1; |
| |
| // Common configuration. |
| static const size_t kVirtioPciCommonCfgBase = 0; |
| static const size_t kVirtioPciCommonCfgSize = 0x38; |
| static const size_t kVirtioPciCommonCfgTop = kVirtioPciCommonCfgBase + kVirtioPciCommonCfgSize - 1; |
| static_assert(kVirtioPciCommonCfgSize == sizeof(virtio_pci_common_cfg_t), |
| "virtio_pci_common_cfg_t has unexpected size"); |
| // Virtio 1.0 Section 4.1.4.3.1: offset MUST be 4-byte aligned. |
| static_assert(is_aligned(kVirtioPciCommonCfgBase, 4), |
| "Virtio PCI common config has illegal alignment."); |
| |
| // Notification configuration. |
| // |
| // Virtio 1.0 Section 4.1.4.4: notify_off_multiplier is combined with the |
| // queue_notify_off to derive the Queue Notify address within a BAR for a |
| // virtqueue: |
| // |
| // cap.offset + queue_notify_off * notify_off_multiplier |
| // |
| // Virtio 1.0 Section 4.1.4.4.1: The device MUST either present |
| // notify_off_multiplier as an even power of 2, or present |
| // notify_off_multiplier as 0. |
| // |
| // By using a multiplier of 4, we use sequential 4b words to notify, ex: |
| // |
| // cap.offset + 0 -> Notify Queue 0 |
| // cap.offset + 4 -> Notify Queue 1 |
| // ... |
| // cap.offset + 4n -> Notify Queuen 'n' |
| static const size_t kVirtioPciNotifyCfgMultiplier = 4; |
| static const size_t kVirtioPciNotifyCfgBase = 0; |
| // Virtio 1.0 Section 4.1.4.4.1: offset MUST be 2-byte aligned. |
| static_assert(is_aligned(kVirtioPciNotifyCfgBase, 2), |
| "Virtio PCI notify config has illegal alignment."); |
| |
| // Interrupt status configuration. |
| static const size_t kVirtioPciIsrCfgBase = 0x38; |
| static const size_t kVirtioPciIsrCfgSize = 1; |
| static const size_t kVirtioPciIsrCfgTop = kVirtioPciIsrCfgBase + kVirtioPciIsrCfgSize - 1; |
| // Virtio 1.0 Section 4.1.4.5: The offset for the ISR status has no alignment |
| // requirements. |
| |
| // Device-specific configuration. |
| static const size_t kVirtioPciDeviceCfgBase = 0x3c; |
| // Virtio 1.0 Section 4.1.4.6.1: The offset for the device-specific |
| // configuration MUST be 4-byte aligned. |
| static_assert(is_aligned(kVirtioPciDeviceCfgBase, 4), |
| "Virtio PCI notify config has illegal alignment."); |
| |
| /* Handle reads to the common configuration structure as defined in |
| * Virtio 1.0 Section 4.1.4.3. |
| */ |
| zx_status_t VirtioPci::CommonCfgRead(uint64_t addr, IoValue* value) const { |
| switch (addr) { |
| case VIRTIO_PCI_COMMON_CFG_DRIVER_FEATURES_SEL: { |
| fbl::AutoLock lock(&device_->mutex_); |
| value->u32 = device_->driver_features_sel_; |
| value->access_size = 4; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_DEVICE_FEATURES_SEL: { |
| fbl::AutoLock lock(&device_->mutex_); |
| value->u32 = device_->features_sel_; |
| value->access_size = 4; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_DRIVER_FEATURES: { |
| // We currently only support a single feature word. |
| fbl::AutoLock lock(&device_->mutex_); |
| value->u32 = device_->driver_features_sel_ > 0 ? 0 : device_->driver_features_; |
| value->access_size = 4; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_DEVICE_FEATURES: { |
| // Virtio 1.0 Section 6: |
| // |
| // A device MUST offer VIRTIO_F_VERSION_1. |
| // |
| // VIRTIO_F_VERSION_1(32) This indicates compliance with this |
| // specification, giving a simple way to detect legacy devices or |
| // drivers. |
| // |
| // This is the only feature supported beyond the first feature word so |
| // we just specaial case it here. |
| fbl::AutoLock lock(&device_->mutex_); |
| value->access_size = 4; |
| if (device_->features_sel_ == 1) { |
| value->u32 = 1; |
| return ZX_OK; |
| } |
| |
| value->u32 = device_->features_sel_ > 0 ? 0 : device_->features_; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_NUM_QUEUES: { |
| fbl::AutoLock lock(&device_->mutex_); |
| value->u16 = device_->num_queues_; |
| value->access_size = 2; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_DEVICE_STATUS: { |
| fbl::AutoLock lock(&device_->mutex_); |
| value->u8 = device_->status_; |
| value->access_size = 1; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_SEL: { |
| fbl::AutoLock lock(&device_->mutex_); |
| value->u16 = device_->queue_sel_; |
| value->access_size = 2; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_SIZE: { |
| virtio_queue_t* queue = selected_queue(); |
| if (queue == nullptr) |
| return ZX_ERR_BAD_STATE; |
| |
| fbl::AutoLock lock(&queue->mutex); |
| value->u16 = queue->size; |
| value->access_size = 2; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_ENABLE: |
| // Virtio 1.0 Section 4.1.4.3: The device MUST present a 0 in |
| // queue_enable on reset. |
| // |
| // Note the implementation currently does not respect this value. |
| value->access_size = 2; |
| value->u16 = 0; |
| return ZX_OK; |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_DESC_LOW... VIRTIO_PCI_COMMON_CFG_QUEUE_USED_HIGH: { |
| virtio_queue_t* queue = selected_queue(); |
| if (queue == nullptr) |
| return ZX_ERR_BAD_STATE; |
| |
| size_t word = (addr - VIRTIO_PCI_COMMON_CFG_QUEUE_DESC_LOW) / sizeof(uint32_t); |
| fbl::AutoLock lock(&queue->mutex); |
| value->u32 = queue->addr.words[word]; |
| value->access_size = 4; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_NOTIFY_OFF: { |
| fbl::AutoLock lock(&device_->mutex_); |
| if (device_->queue_sel_ >= device_->num_queues_) |
| return ZX_ERR_BAD_STATE; |
| |
| value->u32 = device_->queue_sel_; |
| value->access_size = 4; |
| return ZX_OK; |
| } |
| |
| // Currently not implmeneted. |
| case VIRTIO_PCI_COMMON_CFG_CONFIG_GEN: |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_MSIX_VECTOR: |
| case VIRTIO_PCI_COMMON_CFG_MSIX_CONFIG: |
| value->u32 = 0; |
| return ZX_OK; |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t VirtioPci::ConfigBarRead(uint64_t addr, IoValue* value) const { |
| switch (addr) { |
| case kVirtioPciCommonCfgBase... kVirtioPciCommonCfgTop: |
| return CommonCfgRead(addr - kVirtioPciCommonCfgBase, value); |
| case kVirtioPciIsrCfgBase... kVirtioPciIsrCfgTop: |
| fbl::AutoLock lock(&device_->mutex_); |
| value->u8 = device_->isr_status_; |
| value->access_size = 1; |
| |
| // 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; |
| return ZX_OK; |
| } |
| |
| size_t device_config_top = kVirtioPciDeviceCfgBase + device_->device_config_size_; |
| if (addr >= kVirtioPciDeviceCfgBase && addr < device_config_top) { |
| uint64_t device_offset = addr - kVirtioPciDeviceCfgBase; |
| return device_->ReadConfig(device_offset, value); |
| } |
| fprintf(stderr, "Unhandled read %#lx\n", addr); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static void virtio_queue_update_addr(virtio_queue_t* queue) { |
| virtio_queue_set_desc_addr(queue, queue->addr.desc); |
| virtio_queue_set_avail_addr(queue, queue->addr.avail); |
| virtio_queue_set_used_addr(queue, queue->addr.used); |
| } |
| |
| /* Handle writes to the common configuration structure as defined in |
| * Virtio 1.0 Section 4.1.4.3. |
| */ |
| zx_status_t VirtioPci::CommonCfgWrite(uint64_t addr, const IoValue& value) { |
| switch (addr) { |
| case VIRTIO_PCI_COMMON_CFG_DEVICE_FEATURES_SEL: { |
| if (value.access_size != 4) |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| |
| fbl::AutoLock lock(&device_->mutex_); |
| device_->features_sel_ = value.u32; |
| return ZX_OK; |
| } |
| |
| case VIRTIO_PCI_COMMON_CFG_DRIVER_FEATURES_SEL: { |
| if (value.access_size != 4) |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| |
| fbl::AutoLock lock(&device_->mutex_); |
| device_->driver_features_sel_ = value.u32; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_DRIVER_FEATURES: { |
| if (value.access_size != 4) |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| |
| fbl::AutoLock lock(&device_->mutex_); |
| if (device_->driver_features_sel_ == 0) |
| device_->driver_features_ = value.u32; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_DEVICE_STATUS: { |
| if (value.access_size != 1) |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| |
| fbl::AutoLock lock(&device_->mutex_); |
| device_->status_ = value.u8; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_SEL: { |
| if (value.access_size != 2) |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| if (value.u16 >= device_->num_queues_) |
| return ZX_ERR_NOT_SUPPORTED; |
| |
| fbl::AutoLock lock(&device_->mutex_); |
| device_->queue_sel_ = value.u16; |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_SIZE: { |
| if (value.access_size != 2) |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| virtio_queue_t* queue = selected_queue(); |
| if (queue == nullptr) |
| return ZX_ERR_BAD_STATE; |
| |
| fbl::AutoLock lock(&queue->mutex); |
| queue->size = value.u16; |
| virtio_queue_update_addr(queue); |
| return ZX_OK; |
| } |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_DESC_LOW... VIRTIO_PCI_COMMON_CFG_QUEUE_USED_HIGH: { |
| if (value.access_size != 4) |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| virtio_queue_t* queue = selected_queue(); |
| if (queue == nullptr) |
| return ZX_ERR_BAD_STATE; |
| |
| size_t word = (addr - VIRTIO_PCI_COMMON_CFG_QUEUE_DESC_LOW) / sizeof(uint32_t); |
| fbl::AutoLock lock(&queue->mutex); |
| queue->addr.words[word] = value.u32; |
| virtio_queue_update_addr(queue); |
| return ZX_OK; |
| } |
| // Not implemented registers. |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_MSIX_VECTOR: |
| case VIRTIO_PCI_COMMON_CFG_MSIX_CONFIG: |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_ENABLE: |
| return ZX_OK; |
| // Read-only registers. |
| case VIRTIO_PCI_COMMON_CFG_QUEUE_NOTIFY_OFF: |
| case VIRTIO_PCI_COMMON_CFG_NUM_QUEUES: |
| case VIRTIO_PCI_COMMON_CFG_CONFIG_GEN: |
| case VIRTIO_PCI_COMMON_CFG_DEVICE_FEATURES: |
| fprintf(stderr, "Unsupported write to %#lx\n", addr); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t VirtioPci::ConfigBarWrite(uint64_t addr, const IoValue& value) { |
| switch (addr) { |
| case kVirtioPciCommonCfgBase... kVirtioPciCommonCfgTop: { |
| uint64_t offset = addr - kVirtioPciCommonCfgBase; |
| return CommonCfgWrite(offset, value); |
| } |
| } |
| |
| size_t device_config_top = kVirtioPciDeviceCfgBase + device_->device_config_size_; |
| if (addr >= kVirtioPciDeviceCfgBase && addr < device_config_top) { |
| uint64_t device_offset = addr - kVirtioPciDeviceCfgBase; |
| return device_->WriteConfig(device_offset, value); |
| } |
| fprintf(stderr, "Unhandled write %#lx\n", addr); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| void VirtioPci::SetupCap(pci_cap_t* cap, virtio_pci_cap_t* virtio_cap, uint8_t cfg_type, |
| size_t cap_len, size_t data_length, uint8_t bar, size_t bar_offset) { |
| virtio_cap->cfg_type = cfg_type; |
| virtio_cap->bar = bar; |
| virtio_cap->offset = static_cast<uint32_t>(bar_offset); |
| virtio_cap->length = static_cast<uint32_t>(data_length); |
| |
| cap->id = kPciCapTypeVendorSpecific; |
| cap->data = reinterpret_cast<uint8_t*>(virtio_cap); |
| cap->len = virtio_cap->cap_len = static_cast<uint8_t>(cap_len); |
| } |
| |
| void VirtioPci::SetupCaps() { |
| // Common configuration. |
| SetupCap(&capabilities_[0], &common_cfg_cap_, |
| VIRTIO_PCI_CAP_COMMON_CFG, sizeof(common_cfg_cap_), |
| kVirtioPciCommonCfgSize, kVirtioPciBar, kVirtioPciCommonCfgBase); |
| |
| // Notify configuration. |
| notify_cfg_cap_.notify_off_multiplier = kVirtioPciNotifyCfgMultiplier; |
| size_t notify_size = device_->num_queues() * kVirtioPciNotifyCfgMultiplier; |
| SetupCap(&capabilities_[1], ¬ify_cfg_cap_.cap, |
| VIRTIO_PCI_CAP_NOTIFY_CFG, sizeof(notify_cfg_cap_), |
| notify_size, kVirtioPciNotifyBar, kVirtioPciNotifyCfgBase); |
| bar_[kVirtioPciNotifyBar].size = static_cast<uint32_t>(notify_size); |
| bar_[kVirtioPciNotifyBar].trap_type = TrapType::MMIO_BELL; |
| |
| // ISR configuration. |
| SetupCap(&capabilities_[2], &isr_cfg_cap_, |
| VIRTIO_PCI_CAP_ISR_CFG, sizeof(isr_cfg_cap_), |
| kVirtioPciIsrCfgSize, kVirtioPciBar, kVirtioPciIsrCfgBase); |
| |
| // Device-specific configuration. |
| SetupCap(&capabilities_[3], &device_cfg_cap_, |
| VIRTIO_PCI_CAP_DEVICE_CFG, sizeof(device_cfg_cap_), |
| device_->device_config_size_, kVirtioPciBar, kVirtioPciDeviceCfgBase); |
| |
| // Note VIRTIO_PCI_CAP_PCI_CFG is not implmeneted. |
| // This one is more complex since it is writable and doesn't seem to be |
| // used by Linux or Zircon. |
| |
| static_assert(kVirtioPciNumCapabilities == 4, "Incorrect number of capabilities."); |
| set_capabilities(capabilities_, kVirtioPciNumCapabilities); |
| |
| static_assert(kVirtioPciBar < PCI_MAX_BARS, "Not enough BAR registers available."); |
| bar_[kVirtioPciBar].size = static_cast<uint32_t>( |
| kVirtioPciDeviceCfgBase + device_->device_config_size_); |
| bar_[kVirtioPciBar].trap_type = TrapType::MMIO_SYNC; |
| } |
| |
| static constexpr uint16_t virtio_pci_id(uint16_t virtio_id) { |
| return static_cast<uint16_t>(virtio_id + 0x1040u); |
| } |
| |
| static constexpr uint32_t virtio_pci_class_code(uint16_t virtio_id) { |
| // See PCI LOCAL BUS SPECIFICATION, REV. 3.0 Section D. |
| switch (virtio_id) { |
| case VIRTIO_ID_BLOCK: |
| return 0x01800000; |
| case VIRTIO_ID_GPU: |
| return 0x03808000; |
| case VIRTIO_ID_INPUT: |
| return 0x09800000; |
| } |
| return 0; |
| } |
| |
| // Virtio 1.0 Section 4.1.2.1: Non-transitional devices SHOULD have a PCI |
| // Revision ID of 1 or higher. |
| static constexpr uint32_t kVirtioPciRevisionId = 1; |
| |
| static constexpr uint32_t virtio_pci_device_class(uint16_t virtio_id) { |
| return virtio_pci_class_code(virtio_id) | kVirtioPciRevisionId; |
| } |
| |
| virtio_queue_t* VirtioPci::selected_queue() const { |
| fbl::AutoLock lock(&device_->mutex_); |
| if (device_->queue_sel_ >= device_->num_queues_) |
| return nullptr; |
| return &device_->queues_[device_->queue_sel_]; |
| } |
| |
| VirtioPci::VirtioPci(VirtioDevice* device) |
| : PciDevice({ |
| .device_id = virtio_pci_id(device->device_id_), |
| .vendor_id = kPciVendorIdVirtio, |
| .subsystem_id = device->device_id_, |
| .subsystem_vendor_id = 0, |
| .device_class = virtio_pci_device_class(device->device_id_), |
| }), |
| device_(device) { |
| |
| SetupCaps(); |
| } |
| |
| zx_status_t VirtioPci::ReadBar(uint8_t bar, uint64_t offset, IoValue* value) const { |
| switch (bar) { |
| case kVirtioPciBar: |
| return ConfigBarRead(offset, value); |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t VirtioPci::WriteBar(uint8_t bar, uint64_t offset, const IoValue& value) { |
| switch (bar) { |
| case kVirtioPciBar: |
| return ConfigBarWrite(offset, value); |
| case kVirtioPciNotifyBar: |
| return NotifyBarWrite(offset, value); |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t VirtioPci::NotifyBarWrite(uint64_t offset, const IoValue& value) { |
| if (!is_aligned(offset, kVirtioPciNotifyCfgMultiplier)) |
| return ZX_ERR_INVALID_ARGS; |
| |
| uint64_t notify_queue = offset / kVirtioPciNotifyCfgMultiplier; |
| if (notify_queue >= device_->num_queues()) |
| return ZX_ERR_INVALID_ARGS; |
| |
| return device_->Kick(static_cast<uint16_t>(notify_queue)); |
| } |