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 <trace.h>
8
9 #include <ktl/move.h>
10 #include <fbl/auto_lock.h>
11
12 #include <dev/address_provider/address_provider.h>
13 #include <dev/pci_common.h>
14 #include <kernel/range_check.h>
15
16 #define LOCAL_TRACE 0
17
~MmioPcieAddressProvider()18 MmioPcieAddressProvider::~MmioPcieAddressProvider() {
19 // Unmap and free all of our mapped ECAM regions
20 ecam_regions_.clear();
21 }
22
Translate(uint8_t bus_id,uint8_t device_id,uint8_t function_id,vaddr_t * virt,paddr_t * phys)23 zx_status_t MmioPcieAddressProvider::Translate(uint8_t bus_id, uint8_t device_id,
24 uint8_t function_id,
25 vaddr_t* virt,
26 paddr_t* phys) {
27 // Find the region which would contain this bus_id, if any.
28 // add does not overlap with any already defined regions.
29 fbl::AutoLock ecam_region_lock(&ecam_region_lock_);
30 auto iter = ecam_regions_.upper_bound(static_cast<uint8_t>(bus_id));
31 --iter;
32
33 if (!iter.IsValid()) {
34 return ZX_ERR_NOT_FOUND;
35 }
36
37 if ((bus_id < iter->ecam().bus_start) ||
38 (bus_id > iter->ecam().bus_end)) {
39 return ZX_ERR_NOT_FOUND;
40 }
41
42 bus_id = static_cast<uint8_t>(bus_id - iter->ecam().bus_start);
43 size_t offset = (static_cast<size_t>(bus_id) << 20) |
44 (static_cast<size_t>(device_id) << 15) |
45 (static_cast<size_t>(function_id) << 12);
46
47 if (phys) {
48 *phys = iter->ecam().phys_base + offset;
49 }
50
51 // TODO(cja): Move to a BDF based associative container for better lookup time
52 // and insert or find behavior.
53 *virt = reinterpret_cast<uintptr_t>(static_cast<uint8_t*>(iter->vaddr()) + offset);
54 return ZX_OK;
55 }
56
AddEcamRegion(const PciEcamRegion & ecam)57 zx_status_t MmioPcieAddressProvider::AddEcamRegion(const PciEcamRegion& ecam) {
58 // Sanity check the region first.
59 if (ecam.bus_start > ecam.bus_end) {
60 return ZX_ERR_INVALID_ARGS;
61 }
62
63 size_t bus_count = static_cast<size_t>(ecam.bus_end) - ecam.bus_start + 1u;
64 if (ecam.size != (PCIE_ECAM_BYTE_PER_BUS * bus_count)){
65 return ZX_ERR_INVALID_ARGS;
66 }
67
68 // Grab the ECAM lock and make certain that the region we have been asked to
69 // add does not overlap with any already defined regions.
70 fbl::AutoLock ecam_region_lock(&ecam_region_lock_);
71 auto iter = ecam_regions_.upper_bound(ecam.bus_start);
72 --iter;
73
74 // If iter is valid, it now points to the region with the largest bus_start
75 // which is <= ecam.bus_start. If any region overlaps with the region we
76 // are attempting to add, it will be this one.
77 if (iter.IsValid()) {
78 const uint8_t iter_start = iter->ecam().bus_start;
79 const size_t iter_len = iter->ecam().bus_end - iter->ecam().bus_start + 1;
80
81 const uint8_t bus_start = ecam.bus_start;
82 const size_t bus_len = ecam.bus_end - ecam.bus_start + 1;
83
84 if (Intersects(iter_start, iter_len, bus_start, bus_len)) {
85 return ZX_ERR_BAD_STATE;
86 }
87 }
88
89 // Looks good. Attempt to allocate and map this ECAM region.
90 fbl::AllocChecker ac;
91 ktl::unique_ptr<MappedEcamRegion> region(new (&ac) MappedEcamRegion(ecam));
92 if (!ac.check()) {
93 TRACEF("Failed to allocate ECAM region for bus range [0x%02x, 0x%02x]\n",
94 ecam.bus_start, ecam.bus_end);
95 return ZX_ERR_NO_MEMORY;
96 }
97
98 zx_status_t res = region->MapEcam();
99 if (res != ZX_OK) {
100 TRACEF("Failed to map ECAM region for bus range [0x%02x, 0x%02x]\n",
101 ecam.bus_start, ecam.bus_end);
102 return res;
103 }
104
105 // Everything checks out. Add the new region to our set of regions and we are done.
106 ecam_regions_.insert(ktl::move(region));
107 return ZX_OK;
108 }
109
CreateConfig(const uintptr_t addr)110 fbl::RefPtr<PciConfig> MmioPcieAddressProvider::CreateConfig(const uintptr_t addr) {
111 return PciConfig::Create(addr, PciAddrSpace::MMIO);
112 }
113