| // 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 "garnet/bin/guest/vmm/pci.h" |
| |
| #include <endian.h> |
| #include <stdio.h> |
| |
| #include <hw/pci.h> |
| #include <zircon/assert.h> |
| |
| __BEGIN_CDECLS; |
| #include <libfdt.h> |
| __END_CDECLS; |
| |
| // PCI ECAM address manipulation. |
| constexpr uint8_t pci_ecam_bus(uint64_t addr) { |
| return bits_shift(addr, 27, 20); |
| } |
| |
| constexpr uint8_t pci_ecam_device(uint64_t addr) { |
| return bits_shift(addr, 19, 15); |
| } |
| |
| constexpr uint8_t pci_ecam_function(uint64_t addr) { |
| return bits_shift(addr, 14, 12); |
| } |
| |
| constexpr uint16_t pci_ecam_register_etc(uint64_t addr) { |
| return bits_shift(addr, 11, 0); |
| } |
| |
| // The size of an ECAM region depends on values in the MCFG ACPI table. For |
| // each ECAM region there is a defined physical base address as well as a bus |
| // start/end value for that region. |
| // |
| // When creating an ECAM address for a PCI configuration register, the bus |
| // value must be relative to the starting bus number for that ECAM region. |
| static inline constexpr uint64_t pci_ecam_size(uint64_t start_bus, |
| uint64_t end_bus) { |
| return (end_bus - start_bus) << 20; |
| } |
| |
| // PCI command register bits. |
| static constexpr uint16_t kPciCommandIoEnable = 1 << 0; |
| static constexpr uint16_t kPciCommandMemEnable = 1 << 1; |
| static constexpr uint16_t kPciCommandIntEnable = 1 << 10; |
| |
| constexpr bool pci_irq_enabled(uint16_t command_register) { |
| return (command_register & kPciCommandIntEnable) == 0; |
| } |
| |
| // PCI config relative IO port addresses (typically at 0xcf8). |
| static constexpr uint16_t kPciConfigAddrPortBase = 0; |
| static constexpr uint16_t kPciConfigAddrPortTop = 3; |
| static constexpr uint16_t kPciConfigDataPortBase = 4; |
| static constexpr uint16_t kPciConfigDataPortTop = 7; |
| |
| // PCI base address registers. |
| static constexpr uint8_t kPciRegisterBar0 = 0x10; |
| static constexpr uint8_t kPciRegisterBar1 = 0x14; |
| static constexpr uint8_t kPciRegisterBar2 = 0x18; |
| static constexpr uint8_t kPciRegisterBar3 = 0x1c; |
| static constexpr uint8_t kPciRegisterBar4 = 0x20; |
| static constexpr uint8_t kPciRegisterBar5 = 0x24; |
| |
| // PCI capabilities registers. |
| static constexpr uint8_t kPciRegisterCapBase = 0xa4; |
| static constexpr uint8_t kPciRegisterCapTop = UINT8_MAX; |
| |
| // PCI capabilities register layout. |
| constexpr uint8_t kPciCapTypeOffset = 0; |
| constexpr uint8_t kPciCapNextOffset = 1; |
| |
| // clang-format off |
| |
| // PCI memory ranges. |
| #if __aarch64__ |
| |
| static constexpr uint64_t kPciEcamPhysBase = 0x808100000; |
| static constexpr uint64_t kPciMmioBarPhysBase = 0x808200000; |
| |
| #elif __x86_64__ |
| |
| static constexpr uint64_t kPciEcamPhysBase = 0xf8100000; |
| static constexpr uint64_t kPciMmioBarPhysBase = 0xf8200000; |
| static constexpr uint64_t kPciConfigPortBase = 0xcf8; |
| static constexpr uint64_t kPciConfigPortSize = 0x8; |
| |
| #endif |
| |
| static constexpr uint64_t kPciEcamSize = pci_ecam_size(0, 1); |
| static constexpr uint64_t kPciMmioBarSize = 0x100000; |
| |
| // clang-format on |
| |
| // Per-device IRQ assignments. |
| // |
| // These are provided to the guest via the /pci@10000000 node within the device |
| // tree, and via the _SB section in the DSDT ACPI table. |
| // |
| // The device tree and DSDT define interrupts for 12 devices (IRQ 32-47). |
| // Adding additional devices beyond that will require updates to both. |
| static constexpr uint32_t kPciGlobalIrqAssigments[kPciMaxDevices] = { |
| 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; |
| |
| uint64_t PciBar::aspace() const { |
| switch (trap_type) { |
| case TrapType::MMIO_SYNC: |
| case TrapType::MMIO_BELL: |
| return kPciBarMmioType64Bit | kPciBarMmioAccessSpace; |
| default: |
| return 0; |
| } |
| } |
| |
| uint64_t PciBar::base() const { |
| switch (trap_type) { |
| case TrapType::MMIO_SYNC: |
| case TrapType::MMIO_BELL: |
| return addr & kPciBarMmioAddrMask; |
| default: |
| return 0; |
| } |
| } |
| |
| zx_status_t PciBar::Read(uint64_t addr, IoValue* value) const { |
| return device->ReadBar(n, addr, value); |
| } |
| |
| zx_status_t PciBar::Write(uint64_t addr, const IoValue& value) { |
| return device->WriteBar(n, addr, value); |
| } |
| |
| 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); |
| } |
| |
| static constexpr 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), |
| }; |
| |
| PciBus::PciBus(Guest* guest, InterruptController* interrupt_controller) |
| : guest_(guest), |
| ecam_handler_(this), |
| port_handler_(this), |
| interrupt_controller_(interrupt_controller), |
| root_complex_(kRootComplexAttributes), |
| mmio_base_(kPciMmioBarPhysBase) {} |
| |
| 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_); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Setup ECAM trap for a single bus. |
| status = guest_->CreateMapping(TrapType::MMIO_SYNC, kPciEcamPhysBase, |
| kPciEcamSize, 0, &ecam_handler_); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| #if __x86_64__ |
| // Setup PIO trap. |
| status = guest_->CreateMapping(TrapType::PIO_SYNC, kPciConfigPortBase, |
| kPciConfigPortSize, 0, &port_handler_); |
| if (status != ZX_OK) { |
| return status; |
| } |
| #endif |
| |
| return ZX_OK; |
| } |
| |
| uint32_t PciBus::config_addr() { |
| std::lock_guard<std::mutex> lock(mutex_); |
| return config_addr_; |
| } |
| |
| void PciBus::set_config_addr(uint32_t addr) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| config_addr_ = addr; |
| } |
| |
| zx_status_t PciBus::Connect(PciDevice* device, bool skip_bell) { |
| if (next_open_slot_ >= kPciMaxDevices) { |
| FXL_LOG(ERROR) << "No PCI device slots available"; |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| ZX_DEBUG_ASSERT(device_[next_open_slot_] == nullptr); |
| size_t slot = next_open_slot_++; |
| |
| // Initialize BAR registers. |
| for (uint8_t bar_num = 0; bar_num < kPciMaxBars; ++bar_num) { |
| // Skip unimplemented bars. |
| if (!device->is_bar_implemented(bar_num)) { |
| break; |
| } |
| |
| device->bus_ = this; |
| PciBar* bar = &device->bar_[bar_num]; |
| bar->size = align(bar->size, PAGE_SIZE); |
| bar->addr = mmio_base_; |
| mmio_base_ += bar->size; |
| } |
| if (mmio_base_ >= kPciMmioBarPhysBase + kPciMmioBarSize) { |
| FXL_LOG(ERROR) << "No PCI MMIO address space available"; |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| device->command_ = kPciCommandIoEnable | kPciCommandMemEnable; |
| device->global_irq_ = kPciGlobalIrqAssigments[slot]; |
| device_[slot] = device; |
| return device->SetupBarTraps(guest_, skip_bell); |
| } |
| |
| // 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_etc(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_etc(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 kPciConfigAddrPortBase ... kPciConfigAddrPortTop: { |
| uint64_t bit_offset = (port - kPciConfigAddrPortBase) * 8; |
| uint32_t mask = bit_mask<uint32_t>(value->access_size * 8); |
| |
| std::lock_guard<std::mutex> 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; |
| { |
| std::lock_guard<std::mutex> 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 kPciConfigAddrPortBase ... kPciConfigAddrPortTop: { |
| // 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 - kPciConfigAddrPortBase) * 8; |
| uint32_t bit_size = value.access_size * 8; |
| uint32_t mask = bit_mask<uint32_t>(bit_size); |
| |
| std::lock_guard<std::mutex> 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; |
| { |
| std::lock_guard<std::mutex> 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(PciDevice& device) { |
| { |
| std::lock_guard<std::mutex> lock(device.mutex_); |
| if (!pci_irq_enabled(device.command_)) { |
| device.pending_irq_ = true; |
| return ZX_OK; |
| } |
| device.pending_irq_ = false; |
| } |
| return interrupt_controller_->Interrupt(device.global_irq_); |
| } |
| |
| zx_status_t PciBus::ConfigureDtb(void* dtb) const { |
| uint64_t reg_val[2] = {htobe64(kPciEcamPhysBase), htobe64(kPciEcamSize)}; |
| int node_off = |
| fdt_node_offset_by_prop_value(dtb, -1, "reg", reg_val, sizeof(reg_val)); |
| if (node_off < 0) { |
| FXL_LOG(ERROR) << "Failed to find PCI in DTB"; |
| return ZX_ERR_INTERNAL; |
| } |
| int ret = fdt_node_check_compatible(dtb, node_off, "pci-host-ecam-generic"); |
| if (ret != 0) { |
| FXL_LOG(ERROR) << "Device with PCI registers is not PCI compatible"; |
| return ZX_ERR_INTERNAL; |
| } |
| return ZX_OK; |
| } |
| |
| // 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); |
| } |
| |
| PciDevice::PciDevice(const Attributes attrs) : attrs_(attrs) {} |
| |
| const pci_cap_t* PciDevice::FindCapability(uint8_t addr, uint8_t* cap_index, |
| uint32_t* cap_base) const { |
| uint32_t base = kPciRegisterCapBase; |
| 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: { |
| std::lock_guard<std::mutex> 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 kPciRegisterBar0: |
| case kPciRegisterBar1: |
| case kPciRegisterBar2: |
| case kPciRegisterBar3: |
| case kPciRegisterBar4: |
| case kPciRegisterBar5: { |
| const uint64_t pci_reg = (reg - kPciRegisterBar0) / 4; |
| const uint64_t bar_num = pci_reg / 2; |
| const bool high_word = pci_reg % 2; |
| if (bar_num >= kPciMaxBars) { |
| return pci_read_unimplemented_register(value); |
| } |
| |
| std::lock_guard<std::mutex> lock(mutex_); |
| const PciBar* bar = &bar_[bar_num]; |
| if (!high_word) { |
| *value = bar->addr | bar->aspace(); |
| } else { |
| *value = bar->addr >> 32; |
| } |
| 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 |= kPciRegisterCapBase; |
| } |
| return ZX_OK; |
| case kPciRegisterCapBase ... kPciRegisterCapTop: |
| 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; |
| } |
| |
| bool fire_pending_irq = false; |
| { |
| std::lock_guard<std::mutex> lock(mutex_); |
| command_ = value.u16; |
| // If we have a pending IRQ and this write will enable interrupts for |
| // this device, we'll inject that pending IRQ now. |
| fire_pending_irq = pending_irq_ && pci_irq_enabled(command_); |
| } |
| if (fire_pending_irq) { |
| return Interrupt(); |
| } |
| return ZX_OK; |
| } |
| case kPciRegisterBar0: |
| case kPciRegisterBar1: |
| case kPciRegisterBar2: |
| case kPciRegisterBar3: |
| case kPciRegisterBar4: |
| case kPciRegisterBar5: { |
| if (value.access_size != 4) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| const uint64_t pci_reg = (reg - kPciRegisterBar0) / 4; |
| const uint64_t bar_num = pci_reg / 2; |
| const bool high_word = pci_reg % 2; |
| if (bar_num >= kPciMaxBars) { |
| return pci_write_unimplemented_register(); |
| } |
| |
| std::lock_guard<std::mutex> lock(mutex_); |
| PciBar* bar = &bar_[bar_num]; |
| auto addr = reinterpret_cast<uint32_t*>(&bar->addr); |
| // We zero bits in the BAR in order to set the size. |
| if (!high_word) { |
| addr[0] = value.u32; |
| addr[0] &= ~(bar->size - 1); |
| } else { |
| addr[1] = value.u32; |
| addr[1] &= ~((bar->size - 1) >> 32); |
| } |
| return ZX_OK; |
| } |
| default: |
| return pci_write_unimplemented_register(); |
| } |
| } |
| |
| zx_status_t PciDevice::SetupBarTraps(Guest* guest, bool skip_bell) { |
| for (uint8_t i = 0; i < kPciMaxBars; ++i) { |
| PciBar* bar = &bar_[i]; |
| if (!is_bar_implemented(i)) { |
| break; |
| } else if (skip_bell && bar->trap_type == TrapType::MMIO_BELL) { |
| continue; |
| } |
| |
| bar->n = i; |
| bar->device = this; |
| zx_status_t status = |
| guest->CreateMapping(bar->trap_type, bar->base(), bar->size, 0, bar); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t PciDevice::Interrupt() { |
| if (!bus_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| return bus_->Interrupt(*this); |
| } |