| // 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/pci.h> |
| |
| #include <stdio.h> |
| |
| #include <fbl/auto_lock.h> |
| #include <hw/pci.h> |
| #include <hypervisor/address.h> |
| #include <hypervisor/bits.h> |
| #include <machina/interrupt_controller.h> |
| |
| // PCI BAR register addresses. |
| #define PCI_REGISTER_BAR_0 0x10 |
| #define PCI_REGISTER_BAR_1 0x14 |
| #define PCI_REGISTER_BAR_2 0x18 |
| #define PCI_REGISTER_BAR_3 0x1c |
| #define PCI_REGISTER_BAR_4 0x20 |
| #define PCI_REGISTER_BAR_5 0x24 |
| |
| // PCI Capabilities registers. |
| #define PCI_REGISTER_CAP_BASE 0xa4 |
| #define PCI_REGISTER_CAP_TOP UINT8_MAX |
| |
| /* PCI config relative IO port addresses (typically at 0xcf8). */ |
| constexpr uint16_t kPciConfigAddressPortBase = 0; |
| constexpr uint16_t kPciConfigAddressPortTop = 3; |
| constexpr uint16_t kPciConfigDataPortBase = 4; |
| constexpr uint16_t kPciConfigDataPortTop = 7; |
| |
| constexpr uint32_t kPioAddressMask = ~bit_mask<uint32_t>(2); |
| constexpr uint32_t kMmioAddressMask = ~bit_mask<uint32_t>(4); |
| |
| // PCI capabilities register layout. |
| constexpr uint8_t kPciCapTypeOffset = 0; |
| constexpr uint8_t kPciCapNextOffset = 1; |
| |
| /* Per-device IRQ assignments. |
| * |
| * These are provided to the guest via the _SB section in the DSDT ACPI table. |
| * |
| * The DSDT defines interrupts for 5 devices (IRQ 32-36). Adding |
| * additional devices beyond that will require updates to the DSDT. |
| */ |
| constexpr uint32_t kPciGlobalIrqAssigments[PCI_MAX_DEVICES] = {32, 33, 34, 35, 36}; |
| |
| uint32_t PciBar::aspace() const { |
| switch (trap_type) { |
| case TrapType::PIO_SYNC: |
| case TrapType::PIO_ASYNC: |
| return PCI_BAR_ASPACE_PIO; |
| case TrapType::MMIO_SYNC: |
| case TrapType::MMIO_BELL: |
| return PCI_BAR_ASPACE_MMIO; |
| default: |
| return 0; |
| } |
| } |
| |
| uint32_t PciBar::base() const { |
| switch (aspace()) { |
| case PCI_BAR_ASPACE_PIO: |
| return addr & kPioAddressMask; |
| case PCI_BAR_ASPACE_MMIO: |
| return addr & kMmioAddressMask; |
| default: |
| return 0; |
| } |
| } |
| |
| zx_status_t PciBar::Read(uint64_t addr, IoValue* value) const { |
| if (device == nullptr) |
| return ZX_ERR_BAD_STATE; |
| return device->ReadBar(n, addr, value); |
| } |
| |
| zx_status_t PciBar::Write(uint64_t addr, const IoValue& value) { |
| if (device == nullptr) |
| return ZX_ERR_BAD_STATE; |
| return device->WriteBar(n, addr, value); |
| } |
| |
| static const PciDevice::Attributes kRootComplexAttributes = { |
| .device_id = PCI_DEVICE_ID_INTEL_Q35, |
| .vendor_id = PCI_VENDOR_ID_INTEL, |
| .subsystem_id = 0, |
| .subsystem_vendor_id = 0, |
| .device_class = (PCI_CLASS_BRIDGE_HOST << 16), |
| }; |
| |
| PciDevice::PciDevice(const Attributes attrs) |
| : attrs_(attrs) {} |
| |
| PciPortHandler::PciPortHandler(PciBus* bus) |
| : bus_(bus) {} |
| |
| zx_status_t PciPortHandler::Read(uint64_t addr, IoValue* value) const { |
| return bus_->ReadIoPort(addr, value); |
| } |
| |
| zx_status_t PciPortHandler::Write(uint64_t addr, const IoValue& value) { |
| return bus_->WriteIoPort(addr, value); |
| } |
| |
| PciEcamHandler::PciEcamHandler(PciBus* bus) |
| : bus_(bus) {} |
| |
| zx_status_t PciEcamHandler::Read(uint64_t addr, IoValue* value) const { |
| return bus_->ReadEcam(addr, value); |
| } |
| |
| zx_status_t PciEcamHandler::Write(uint64_t addr, const IoValue& value) { |
| return bus_->WriteEcam(addr, value); |
| } |
| |
| PciBus::PciBus(Guest* guest, const InterruptController* interrupt_controller) |
| : guest_(guest), ecam_handler_(this), port_handler_(this), |
| interrupt_controller_(interrupt_controller), root_complex_(kRootComplexAttributes) {} |
| |
| zx_status_t PciBus::Init() { |
| root_complex_.bar_[0].size = 0x10; |
| root_complex_.bar_[0].trap_type = TrapType::MMIO_SYNC; |
| zx_status_t status = Connect(&root_complex_, PCI_DEVICE_ROOT_COMPLEX); |
| if (status != ZX_OK) |
| return status; |
| |
| // Setup ECAM trap for a single bus. |
| status = guest_->CreateMapping(TrapType::MMIO_SYNC, PCI_ECAM_PHYS_BASE, PCI_ECAM_SIZE(0, 1), |
| 0, &ecam_handler_); |
| if (status != ZX_OK) |
| return status; |
| |
| #if __x86_64__ |
| // Setup PIO trap. |
| status = guest_->CreateMapping(TrapType::PIO_SYNC, PCI_CONFIG_PORT_BASE, PCI_CONFIG_PORT_SIZE, |
| 0, &port_handler_); |
| if (status != ZX_OK) |
| return status; |
| #endif |
| |
| return ZX_OK; |
| } |
| |
| uint32_t PciBus::config_addr() { |
| fbl::AutoLock lock(&mutex_); |
| return config_addr_; |
| } |
| |
| void PciBus::set_config_addr(uint32_t addr) { |
| fbl::AutoLock lock(&mutex_); |
| config_addr_ = addr; |
| } |
| |
| zx_status_t PciBus::Connect(PciDevice* device, uint8_t slot) { |
| if (slot >= PCI_MAX_DEVICES) |
| return ZX_ERR_OUT_OF_RANGE; |
| if (device_[slot]) |
| return ZX_ERR_ALREADY_EXISTS; |
| |
| // Initialize BAR registers. |
| for (uint8_t bar_num = 0; bar_num < PCI_MAX_BARS; ++bar_num) { |
| PciBar* bar = &device->bar_[bar_num]; |
| |
| // Skip unimplemented bars. |
| if (!device->is_bar_implemented(bar_num)) |
| break; |
| |
| device->bus_ = this; |
| if (device->bar_[bar_num].aspace() == PCI_BAR_ASPACE_PIO) { |
| // PCI LOCAL BUS SPECIFICATION, REV. 3.0 Section 6.2.5.1 |
| // |
| // This design implies that all address spaces used are a power of two in |
| // size and are naturally aligned. |
| bar->size = round_up_pow2(bar->size); |
| bar->addr = align(pio_base_, bar->size); |
| pio_base_ = bar->addr + bar->size; |
| } else { |
| bar->size = static_cast<uint16_t>(align(bar->size, PAGE_SIZE)); |
| bar->addr = mmio_base_; |
| mmio_base_ += bar->size; |
| } |
| } |
| |
| device->command_ = PCI_COMMAND_IO_EN | PCI_COMMAND_MEM_EN; |
| device->global_irq_ = kPciGlobalIrqAssigments[slot]; |
| device_[slot] = device; |
| |
| return device->SetupBarTraps(guest_); |
| } |
| |
| // PCI LOCAL BUS SPECIFICATION, REV. 3.0 Section 6.1: All PCI devices must |
| // treat Configuration Space write operations to reserved registers as no-ops; |
| // that is, the access must be completed normally on the bus and the data |
| // discarded. |
| static inline zx_status_t pci_write_unimplemented_register() { |
| return ZX_OK; |
| } |
| |
| static inline zx_status_t pci_write_unimplemented_device() { |
| return ZX_OK; |
| } |
| |
| // PCI LOCAL BUS SPECIFICATION, REV. 3.0 Section 6.1: Read accesses to reserved |
| // or unimplemented registers must be completed normally and a data value of 0 |
| // returned. |
| static inline zx_status_t pci_read_unimplemented_register(uint32_t* value) { |
| *value = 0; |
| return ZX_OK; |
| } |
| |
| // PCI LOCAL BUS SPECIFICATION, REV. 3.0 Section 6.1: The host bus to PCI bridge |
| // must unambiguously report attempts to read the Vendor ID of non-existent |
| // devices. Since 0 FFFFh is an invalid Vendor ID, it is adequate for the host |
| // bus to PCI bridge to return a value of all 1's on read accesses to |
| // Configuration Space registers of non-existent devices. |
| static inline zx_status_t pci_read_unimplemented_device(IoValue* value) { |
| value->u32 = bit_mask<uint32_t>(value->access_size * 8); |
| return ZX_OK; |
| } |
| |
| zx_status_t PciBus::ReadEcam(uint64_t addr, IoValue* value) const { |
| const uint8_t device = PCI_ECAM_DEVICE(addr); |
| const uint16_t reg = PCI_ECAM_REGISTER(addr); |
| const bool valid = is_addr_valid(PCI_ECAM_BUS(addr), device, PCI_ECAM_FUNCTION(addr)); |
| if (!valid) { |
| return pci_read_unimplemented_device(value); |
| } |
| |
| return device_[device]->ReadConfig(reg, value); |
| } |
| |
| zx_status_t PciBus::WriteEcam(uint64_t addr, const IoValue& value) { |
| const uint8_t device = PCI_ECAM_DEVICE(addr); |
| const uint16_t reg = PCI_ECAM_REGISTER(addr); |
| const bool valid = is_addr_valid(PCI_ECAM_BUS(addr), device, PCI_ECAM_FUNCTION(addr)); |
| if (!valid) { |
| return pci_write_unimplemented_device(); |
| } |
| |
| return device_[device]->WriteConfig(reg, value); |
| } |
| |
| zx_status_t PciBus::ReadIoPort(uint64_t port, IoValue* value) const { |
| switch (port) { |
| case kPciConfigAddressPortBase... kPciConfigAddressPortTop: { |
| uint64_t bit_offset = (port - kPciConfigAddressPortBase) * 8; |
| uint32_t mask = bit_mask<uint32_t>(value->access_size * 8); |
| |
| fbl::AutoLock lock(&mutex_); |
| uint32_t addr = config_addr_ >> bit_offset; |
| value->u32 = addr & mask; |
| return ZX_OK; |
| } |
| case kPciConfigDataPortBase... kPciConfigDataPortTop: { |
| uint32_t addr; |
| uint64_t reg; |
| { |
| fbl::AutoLock lock(&mutex_); |
| addr = config_addr_; |
| if (!is_addr_valid(PCI_TYPE1_BUS(addr), PCI_TYPE1_DEVICE(addr), |
| PCI_TYPE1_FUNCTION(addr))) { |
| return pci_read_unimplemented_device(value); |
| } |
| } |
| |
| PciDevice* device = device_[PCI_TYPE1_DEVICE(addr)]; |
| reg = PCI_TYPE1_REGISTER(addr) + port - kPciConfigDataPortBase; |
| return device->ReadConfig(reg, value); |
| } |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t PciBus::WriteIoPort(uint64_t port, const IoValue& value) { |
| switch (port) { |
| case kPciConfigAddressPortBase... kPciConfigAddressPortTop: { |
| // Software can (and Linux does) perform partial word accesses to the |
| // PCI address register. This means we need to take care to read/write |
| // portions of the 32bit register without trampling the other bits. |
| uint64_t bit_offset = (port - kPciConfigAddressPortBase) * 8; |
| uint32_t bit_size = value.access_size * 8; |
| uint32_t mask = bit_mask<uint32_t>(bit_size); |
| |
| fbl::AutoLock lock(&mutex_); |
| // Clear out the bits we'll be modifying. |
| config_addr_ = clear_bits(config_addr_, bit_size, bit_offset); |
| // Set the bits of the address. |
| config_addr_ |= (value.u32 & mask) << bit_offset; |
| return ZX_OK; |
| } |
| case kPciConfigDataPortBase... kPciConfigDataPortTop: { |
| uint32_t addr; |
| uint64_t reg; |
| { |
| fbl::AutoLock lock(&mutex_); |
| addr = config_addr_; |
| |
| if (!is_addr_valid(PCI_TYPE1_BUS(addr), |
| PCI_TYPE1_DEVICE(addr), |
| PCI_TYPE1_FUNCTION(addr))) { |
| return pci_write_unimplemented_device(); |
| } |
| |
| reg = PCI_TYPE1_REGISTER(addr) + port - kPciConfigDataPortBase; |
| } |
| PciDevice* device = device_[PCI_TYPE1_DEVICE(addr)]; |
| return device->WriteConfig(reg, value); |
| } |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t PciBus::Interrupt(const PciDevice& device) const { |
| return interrupt_controller_->Interrupt(device.global_irq_); |
| } |
| |
| // PCI Local Bus Spec v3.0 Section 6.7: Each capability must be DWORD aligned. |
| static inline uint8_t pci_cap_len(const pci_cap_t* cap) { |
| return align(cap->len, 4); |
| } |
| |
| const pci_cap_t* PciDevice::FindCapability(uint8_t addr, uint8_t* cap_index, |
| uint32_t* cap_base) const { |
| uint32_t base = PCI_REGISTER_CAP_BASE; |
| for (uint8_t i = 0; i < num_capabilities_; ++i) { |
| const pci_cap_t* cap = &capabilities_[i]; |
| uint8_t cap_len = pci_cap_len(cap); |
| if (addr >= base + cap_len) { |
| base += cap_len; |
| continue; |
| } |
| *cap_index = i; |
| *cap_base = base; |
| return cap; |
| } |
| |
| // Given address doesn't lie within the range of addresses occupied by |
| // capabilities. |
| return nullptr; |
| } |
| |
| zx_status_t PciDevice::ReadCapability(uint8_t addr, uint32_t* out) const { |
| uint8_t cap_index; |
| uint32_t cap_base; |
| const pci_cap_t* cap = FindCapability(addr, &cap_index, &cap_base); |
| if (cap == nullptr) |
| return ZX_ERR_NOT_FOUND; |
| |
| uint32_t word = 0; |
| uint32_t cap_offset = addr - cap_base; |
| for (uint8_t byte = 0; byte < sizeof(word); ++byte, ++cap_offset) { |
| |
| // In the case of padding bytes, return 0. |
| if (cap_offset >= cap->len) |
| break; |
| |
| // PCI Local Bus Spec v3.0 Section 6.7: |
| // Each capability in the list consists of an 8-bit ID field assigned |
| // by the PCI SIG, an 8 bit pointer in configuration space to the next |
| // capability, and some number of additional registers immediately |
| // following the pointer to implement that capability. |
| uint32_t val = 0; |
| switch (cap_offset) { |
| case kPciCapTypeOffset: |
| val = cap->id; |
| break; |
| case kPciCapNextOffset: |
| // PCI Local Bus Spec v3.0 Section 6.7: A pointer value of 00h is |
| // used to indicate the last capability in the list. |
| if (cap_index + 1u < num_capabilities_) |
| val = cap_base + pci_cap_len(cap); |
| break; |
| default: |
| val = cap->data[cap_offset]; |
| break; |
| } |
| word |= val << (byte * 8); |
| } |
| |
| *out = word; |
| return ZX_OK; |
| } |
| |
| /* Read a 4 byte aligned value from PCI config space. */ |
| zx_status_t PciDevice::ReadConfigWord(uint8_t reg, uint32_t* value) const { |
| switch (reg) { |
| // --------------------------------- |
| // | (31..16) | (15..0) | |
| // | device_id | vendor_id | |
| // --------------------------------- |
| case PCI_CONFIG_VENDOR_ID: |
| *value = attrs_.vendor_id; |
| *value |= attrs_.device_id << 16; |
| return ZX_OK; |
| // ---------------------------- |
| // | (31..16) | (15..0) | |
| // | status | command | |
| // ---------------------------- |
| case PCI_CONFIG_COMMAND: { |
| fbl::AutoLock lock(&mutex_); |
| *value = command_; |
| |
| uint16_t status = PCI_STATUS_INTERRUPT; |
| if (capabilities_ != nullptr) |
| status |= PCI_STATUS_NEW_CAPS; |
| *value |= status << 16; |
| return ZX_OK; |
| } |
| // ------------------------------------------------- |
| // | (31..16) | (15..8) | (7..0) | |
| // | class_code | prog_if | revision_id | |
| // ------------------------------------------------- |
| case PCI_CONFIG_REVISION_ID: |
| *value = attrs_.device_class; |
| return ZX_OK; |
| // --------------------------------------------------------------- |
| // | (31..24) | (23..16) | (15..8) | (7..0) | |
| // | BIST | header_type | latency_timer | cache_line_size | |
| // --------------------------------------------------------------- |
| case PCI_CONFIG_CACHE_LINE_SIZE: |
| *value = PCI_HEADER_TYPE_STANDARD << 16; |
| return ZX_OK; |
| case PCI_REGISTER_BAR_0: |
| case PCI_REGISTER_BAR_1: |
| case PCI_REGISTER_BAR_2: |
| case PCI_REGISTER_BAR_3: |
| case PCI_REGISTER_BAR_4: |
| case PCI_REGISTER_BAR_5: { |
| uint32_t bar_num = (reg - PCI_REGISTER_BAR_0) / 4; |
| if (bar_num >= PCI_MAX_BARS) |
| return pci_read_unimplemented_register(value); |
| |
| fbl::AutoLock lock(&mutex_); |
| const PciBar* bar = &bar_[bar_num]; |
| *value = bar->addr | bar->aspace(); |
| return ZX_OK; |
| } |
| // ------------------------------------------------------------- |
| // | (31..24) | (23..16) | (15..8) | (7..0) | |
| // | max_latency | min_grant | interrupt_pin | interrupt_line | |
| // ------------------------------------------------------------- |
| case PCI_CONFIG_INTERRUPT_LINE: { |
| const uint8_t interrupt_pin = 1; |
| *value = interrupt_pin << 8; |
| return ZX_OK; |
| } |
| // ------------------------------------------- |
| // | (31..16) | (15..0) | |
| // | subsystem_id | subsystem_vendor_id | |
| // ------------------------------------------- |
| case PCI_CONFIG_SUBSYS_VENDOR_ID: |
| *value = attrs_.subsystem_vendor_id; |
| *value |= attrs_.subsystem_id << 16; |
| return ZX_OK; |
| // ------------------------------------------ |
| // | (31..8) | (7..0) | |
| // | Reserved | capabilities_pointer | |
| // ------------------------------------------ |
| case PCI_CONFIG_CAPABILITIES: |
| *value = 0; |
| if (capabilities_ != nullptr) |
| *value |= PCI_REGISTER_CAP_BASE; |
| return ZX_OK; |
| case PCI_REGISTER_CAP_BASE... PCI_REGISTER_CAP_TOP: |
| if (ReadCapability(reg, value) != ZX_ERR_NOT_FOUND) |
| return ZX_OK; |
| // Fall-through if the capability is not-implemented. |
| default: |
| return pci_read_unimplemented_register(value); |
| } |
| } |
| |
| zx_status_t PciDevice::ReadConfig(uint64_t reg, IoValue* value) const { |
| // Perform 4-byte aligned read and then shift + mask the result to get the |
| // expected value. |
| uint32_t word = 0; |
| const uint8_t reg_mask = bit_mask<uint8_t>(2); |
| uint8_t word_aligend_reg = static_cast<uint8_t>(reg & ~reg_mask); |
| uint8_t bit_offset = static_cast<uint8_t>((reg & reg_mask) * 8); |
| zx_status_t status = ReadConfigWord(word_aligend_reg, &word); |
| if (status != ZX_OK) |
| return status; |
| |
| word >>= bit_offset; |
| word &= bit_mask<uint32_t>(value->access_size * 8); |
| value->u32 = word; |
| return ZX_OK; |
| } |
| |
| zx_status_t PciDevice::WriteConfig(uint64_t reg, const IoValue& value) { |
| switch (reg) { |
| case PCI_CONFIG_COMMAND: { |
| if (value.access_size != 2) |
| return ZX_ERR_NOT_SUPPORTED; |
| fbl::AutoLock lock(&mutex_); |
| command_ = value.u16; |
| return ZX_OK; |
| } |
| case PCI_REGISTER_BAR_0: |
| case PCI_REGISTER_BAR_1: |
| case PCI_REGISTER_BAR_2: |
| case PCI_REGISTER_BAR_3: |
| case PCI_REGISTER_BAR_4: |
| case PCI_REGISTER_BAR_5: { |
| if (value.access_size != 4) |
| return ZX_ERR_NOT_SUPPORTED; |
| |
| uint64_t bar_num = (reg - PCI_REGISTER_BAR_0) / 4; |
| if (bar_num >= PCI_MAX_BARS) |
| return pci_write_unimplemented_register(); |
| |
| fbl::AutoLock lock(&mutex_); |
| PciBar* bar = &bar_[bar_num]; |
| bar->addr = value.u32; |
| // We zero bits in the BAR in order to set the size. |
| bar->addr &= ~(bar->size - 1); |
| return ZX_OK; |
| } |
| default: |
| return pci_write_unimplemented_register(); |
| } |
| } |
| |
| zx_status_t PciDevice::SetupBarTraps(Guest* guest) { |
| for (uint8_t i = 0; i < PCI_MAX_BARS; ++i) { |
| PciBar* bar = &bar_[i]; |
| if (!is_bar_implemented(i)) |
| break; |
| |
| bar->n = i; |
| bar->device = this; |
| |
| uint64_t addr = bar->base(); |
| uint64_t size = bar->size; |
| zx_status_t status = guest->CreateMapping(bar->trap_type, addr, size, 0, bar); |
| if (status != ZX_OK) |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t PciDevice::Interrupt() const { |
| if (!bus_) |
| return ZX_ERR_BAD_STATE; |
| return bus_->Interrupt(*this); |
| } |