| // Copyright 2018 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <dev/address_provider/address_provider.h> |
| #include <zircon/hw/pci.h> |
| #include <trace.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| namespace { |
| |
| inline bool isRootBridge(const pci_bdf_t& bdf) { |
| // The Root Bridge _must_ be BDF 0:0:0, there are no other devices on Bus 0 |
| // and we short circuit and return false. |
| return bdf.bus_id == 0 && |
| bdf.device_id == 0 && |
| bdf.function_id == 0; |
| } |
| |
| inline bool isDownstream(const pci_bdf_t& bdf) { |
| // This is hacky but it's reasonable. The controller appears to (?) support |
| // more than a single downstream device but we've never seen this in |
| // practice. If we wanted to _actually_ support multiple downstream devices |
| // we'd have to perform additional iATU acrobatics (which we will eventually |
| // do, when this driver lives in userland). |
| // For now, we pin this device to BDF 1:0:0. Also note that the choice of |
| // bus_id and device_id are arbitrary. |
| return bdf.bus_id == 1 && |
| bdf.device_id == 0 && |
| bdf.function_id == 0; |
| } |
| |
| } // namespace |
| |
| zx_status_t DesignWarePcieAddressProvider::Init(const PciEcamRegion& root_bridge, |
| const PciEcamRegion& downstream_device) { |
| fbl::AllocChecker ac; |
| |
| if (root_bridge.bus_start != 0 || root_bridge.bus_end != 0) { |
| TRACEF("Root bridge must be responsible for only bus 0\n"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (downstream_device.bus_start != 1 || downstream_device.bus_end != 1) { |
| TRACEF("Downstream device must responsible for only bus 1\n"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| root_bridge_region_ = ktl::make_unique<MappedEcamRegion>(&ac, root_bridge); |
| if (!ac.check()) { |
| TRACEF("Failed to allocate root_bridge ECAM region\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| downstream_region_ = ktl::make_unique<MappedEcamRegion>(&ac, downstream_device); |
| if (!ac.check()) { |
| TRACEF("Failed to allocate downstream ECAM region\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_status_t st; |
| if ((st = root_bridge_region_->MapEcam()) != ZX_OK) { |
| TRACEF("Failed to map root bridge ECAM region\n"); |
| return st; |
| } |
| |
| if ((st = downstream_region_->MapEcam()) != ZX_OK) { |
| TRACEF("Failed to map downstream ECAM region\n"); |
| return st; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t DesignWarePcieAddressProvider::Translate(const uint8_t bus_id, |
| const uint8_t device_id, |
| const uint8_t function_id, |
| vaddr_t* virt, |
| paddr_t* phys) { |
| if (!root_bridge_region_ || !downstream_region_) { |
| TRACEF("DesignWarePcieAddressProvider::Translate called before DesignWarePcieAddressProvider::Init\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| const pci_bdf_t bdf = { |
| .bus_id = bus_id, |
| .device_id = device_id, |
| .function_id = function_id, |
| }; |
| |
| // Two comments here: |
| // (1) Firstly, the Root Bridge and Downstream devices live in different |
| // apertures of memory so we need to decide if the BDF translates to the |
| // root bridge aperture or the downstream device aperture. |
| // (2) Secondly, the controller appears to support multiple downstream |
| // devices however we've only ever seen configurations with exactly one |
| // root bridge attached to exactly one downstream device in the wild. |
| // There are two strategies for supporting downstream devices and they |
| // each have their advantages and drawbacks: |
| // (i) If the SoC vendor has granted us a generous* aperture into PCI |
| // memory, we should map all devices contiguously thus producing an |
| // ECAM that is entirely standards compliant! |
| // (ii) Otherwise (the situation that we see most often), we should |
| // program the iATU each time we perform a config access and stack |
| // ECAMs for all devices as shadow registers on top of one another. |
| // |
| // * Enough to accommodate all PF/MMIO/IO BARs for all downstream devices |
| // with enough aperture left over for a full ECAM. |
| if (isRootBridge(bdf)) { |
| *virt = reinterpret_cast<vaddr_t>(root_bridge_region_->vaddr()); |
| if (phys) { |
| *phys = root_bridge_region_->ecam().phys_base; |
| } |
| return ZX_OK; |
| } else if (isDownstream(bdf)) { |
| *virt = reinterpret_cast<vaddr_t>(downstream_region_->vaddr()); |
| if (phys) { |
| *phys = downstream_region_->ecam().phys_base; |
| } |
| return ZX_OK; |
| } |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| fbl::RefPtr<PciConfig> DesignWarePcieAddressProvider::CreateConfig(const uintptr_t addr) { |
| // DesignWare has a strange translation mechanism from BDF->Memory Address |
| // but at the end of the day it's still a memory mapped device which means |
| // we can create an MMIO address space. |
| return PciConfig::Create(addr, PciAddrSpace::MMIO); |
| } |