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