1 // Copyright 2018 The Fuchsia Authors
2 //
3 // Use of this source code is governed by a MIT-style
4 // license that can be found in the LICENSE file or at
5 // https://opensource.org/licenses/MIT
6 
7 #include <dev/address_provider/address_provider.h>
8 #include <zircon/hw/pci.h>
9 #include <trace.h>
10 
11 #define LOCAL_TRACE 0
12 
13 namespace {
14 
isRootBridge(const pci_bdf_t & bdf)15 inline bool isRootBridge(const pci_bdf_t& bdf) {
16     // The Root Bridge _must_ be BDF 0:0:0, there are no other devices on Bus 0
17     // and we short circuit and return false.
18     return bdf.bus_id == 0 &&
19            bdf.device_id == 0 &&
20            bdf.function_id == 0;
21 }
22 
isDownstream(const pci_bdf_t & bdf)23 inline bool isDownstream(const pci_bdf_t& bdf) {
24     // This is hacky but it's reasonable. The controller appears to (?) support
25     // more than a single downstream device but we've never seen this in
26     // practice. If we wanted to _actually_ support multiple downstream devices
27     // we'd have to perform additional iATU acrobatics (which we will eventually
28     // do, when this driver lives in userland).
29     // For now, we pin this device to BDF 1:0:0. Also note that the choice of
30     // bus_id and device_id are arbitrary.
31     return bdf.bus_id == 1 &&
32            bdf.device_id == 0 &&
33            bdf.function_id == 0;
34 }
35 
36 } // namespace
37 
Init(const PciEcamRegion & root_bridge,const PciEcamRegion & downstream_device)38 zx_status_t DesignWarePcieAddressProvider::Init(const PciEcamRegion& root_bridge,
39                                                 const PciEcamRegion& downstream_device) {
40     fbl::AllocChecker ac;
41 
42     if (root_bridge.bus_start != 0 || root_bridge.bus_end != 0) {
43         TRACEF("Root bridge must be responsible for only bus 0\n");
44         return ZX_ERR_INVALID_ARGS;
45     }
46 
47     if (downstream_device.bus_start != 1 || downstream_device.bus_end != 1) {
48         TRACEF("Downstream device must responsible for only bus 1\n");
49         return ZX_ERR_INVALID_ARGS;
50     }
51 
52     root_bridge_region_ = ktl::make_unique<MappedEcamRegion>(&ac, root_bridge);
53     if (!ac.check()) {
54         TRACEF("Failed to allocate root_bridge ECAM region\n");
55         return ZX_ERR_NO_MEMORY;
56     }
57 
58     downstream_region_ = ktl::make_unique<MappedEcamRegion>(&ac, downstream_device);
59     if (!ac.check()) {
60         TRACEF("Failed to allocate downstream ECAM region\n");
61         return ZX_ERR_NO_MEMORY;
62     }
63 
64     zx_status_t st;
65     if ((st = root_bridge_region_->MapEcam()) != ZX_OK) {
66         TRACEF("Failed to map root bridge ECAM region\n");
67         return st;
68     }
69 
70     if ((st = downstream_region_->MapEcam()) != ZX_OK) {
71         TRACEF("Failed to map downstream ECAM region\n");
72         return st;
73     }
74 
75     return ZX_OK;
76 }
77 
Translate(const uint8_t bus_id,const uint8_t device_id,const uint8_t function_id,vaddr_t * virt,paddr_t * phys)78 zx_status_t DesignWarePcieAddressProvider::Translate(const uint8_t bus_id,
79                                                      const uint8_t device_id,
80                                                      const uint8_t function_id,
81                                                      vaddr_t* virt,
82                                                      paddr_t* phys) {
83     if (!root_bridge_region_ || !downstream_region_) {
84         TRACEF("DesignWarePcieAddressProvider::Translate called before DesignWarePcieAddressProvider::Init\n");
85         return ZX_ERR_BAD_STATE;
86     }
87 
88     const pci_bdf_t bdf = {
89         .bus_id = bus_id,
90         .device_id = device_id,
91         .function_id = function_id,
92     };
93 
94     // Two comments here:
95     // (1) Firstly, the Root Bridge and Downstream devices live in different
96     //     apertures of memory so we need to decide if the BDF translates to the
97     //     root bridge aperture or the downstream device aperture.
98     // (2) Secondly, the controller appears to support multiple downstream
99     //     devices however we've only ever seen configurations with exactly one
100     //     root bridge attached to exactly one downstream device in the wild.
101     //     There are two strategies for supporting downstream devices and they
102     //     each have their advantages and drawbacks:
103     //     (i)  If the SoC vendor has granted us a generous* aperture into PCI
104     //          memory, we should map all devices contiguously thus producing an
105     //          ECAM that is entirely standards compliant!
106     //     (ii) Otherwise (the situation that we see most often), we should
107     //          program the iATU each time we perform a config access and stack
108     //          ECAMs for all devices as shadow registers on top of one another.
109     //
110     // * Enough to accommodate all PF/MMIO/IO BARs for all downstream devices
111     //   with enough aperture left over for a full ECAM.
112     if (isRootBridge(bdf)) {
113         *virt = reinterpret_cast<vaddr_t>(root_bridge_region_->vaddr());
114         if (phys) {
115             *phys = root_bridge_region_->ecam().phys_base;
116         }
117         return ZX_OK;
118     } else if (isDownstream(bdf)) {
119         *virt = reinterpret_cast<vaddr_t>(downstream_region_->vaddr());
120         if (phys) {
121             *phys = downstream_region_->ecam().phys_base;
122         }
123         return ZX_OK;
124     }
125     return ZX_ERR_NOT_FOUND;
126 }
127 
CreateConfig(const uintptr_t addr)128 fbl::RefPtr<PciConfig> DesignWarePcieAddressProvider::CreateConfig(const uintptr_t addr) {
129     // DesignWare has a strange translation mechanism from BDF->Memory Address
130     // but at the end of the day it's still a memory mapped device which means
131     // we can create an MMIO address space.
132     return PciConfig::Create(addr, PciAddrSpace::MMIO);
133 }
134