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