1 // Copyright 2016 The Fuchsia Authors
2 // Copyright (c) 2016, Google, Inc. All rights reserved
3 //
4 // Use of this source code is governed by a MIT-style
5 // license that can be found in the LICENSE file or at
6 // https://opensource.org/licenses/MIT
7 
8 #include <assert.h>
9 #include <zircon/compiler.h>
10 #include <debug.h>
11 #include <err.h>
12 #include <inttypes.h>
13 #include <kernel/mutex.h>
14 #include <kernel/spinlock.h>
15 #include <vm/vm.h>
16 #include <lk/init.h>
17 #include <fbl/algorithm.h>
18 #include <fbl/limits.h>
19 #include <dev/interrupt.h>
20 #include <string.h>
21 #include <trace.h>
22 #include <platform.h>
23 
24 #include <dev/pcie_bridge.h>
25 #include <dev/pcie_root.h>
26 
27 #define LOCAL_TRACE 0
28 
~PcieUpstreamNode()29 PcieUpstreamNode::~PcieUpstreamNode() {
30 #if LK_DEBUGLEVEL > 0
31      // Sanity check to make sure that all child devices have been released as
32      // well.
33     for (size_t i = 0; i < fbl::count_of(downstream_); ++i)
34         DEBUG_ASSERT(!downstream_[i]);
35 #endif
36 }
37 
AllocateDownstreamBars()38 void PcieUpstreamNode::AllocateDownstreamBars() {
39     /* Finally, allocate all of the BARs for our downstream devices.  Make sure
40      * to not access our downstream devices directly.  Instead, hold references
41      * to downstream devices we obtain while holding bus driver's topology lock.
42      * */
43     for (uint i = 0; i < fbl::count_of(downstream_); ++i) {
44         auto device = GetDownstream(i);
45         if (device != nullptr) {
46             zx_status_t res = device->AllocateBars();
47             if (res != ZX_OK)
48                 device->Disable();
49         }
50     }
51 }
52 
DisableDownstream()53 void PcieUpstreamNode::DisableDownstream() {
54     for (uint i = 0; i < fbl::count_of(downstream_); ++i) {
55         auto downstream_device = GetDownstream(i);
56         if (downstream_device)
57             downstream_device->Disable();
58     }
59 }
60 
UnplugDownstream()61 void PcieUpstreamNode::UnplugDownstream() {
62     for (uint i = 0; i < fbl::count_of(downstream_); ++i) {
63         auto downstream_device = GetDownstream(i);
64         if (downstream_device)
65             downstream_device->Unplug();
66     }
67 }
68 
ScanDownstream()69 void PcieUpstreamNode::ScanDownstream() {
70     DEBUG_ASSERT(driver().RescanLockIsHeld());
71 
72     for (uint dev_id = 0; dev_id < PCIE_MAX_DEVICES_PER_BUS; ++dev_id) {
73         for (uint func_id = 0; func_id < PCIE_MAX_FUNCTIONS_PER_DEVICE; ++func_id) {
74             /* If we can find the config, and it has a valid vendor ID, go ahead
75              * and scan it looking for a valid function. */
76             auto cfg = driver().GetConfig(managed_bus_id_, dev_id, func_id);
77             if (cfg == nullptr) {
78                 TRACEF("Warning: bus being scanned is outside ecam region!\n");
79                 return;
80             }
81 
82             uint16_t vendor_id = cfg->Read(PciConfig::kVendorId);
83             bool good_device = cfg && (vendor_id != PCIE_INVALID_VENDOR_ID);
84             if (good_device) {
85                 uint16_t device_id = cfg->Read(PciConfig::kDeviceId);
86                 LTRACEF("found valid device %04x:%04x at %02x:%02x.%01x\n",
87                         vendor_id, device_id, managed_bus_id_, dev_id, func_id);
88                 /* Don't scan the function again if we have already discovered
89                  * it.  If this function happens to be a bridge, go ahead and
90                  * look under it for new devices. */
91                 uint ndx    = (dev_id * PCIE_MAX_FUNCTIONS_PER_DEVICE) + func_id;
92                 DEBUG_ASSERT(ndx < fbl::count_of(downstream_));
93 
94                 auto downstream_device = GetDownstream(ndx);
95                 if (!downstream_device) {
96                     auto new_dev = ScanDevice(cfg, dev_id, func_id);
97                     if (new_dev == nullptr) {
98                         TRACEF("Failed to initialize device %02x:%02x.%01x; This is Very Bad.  "
99                                "Device (and any of its children) will be inaccessible!\n",
100                                managed_bus_id_, dev_id, func_id);
101                         good_device = false;
102                     }
103                 } else if (downstream_device->is_bridge()) {
104                     // TODO(johngro) : Instead of going up and down the class graph with static
105                     // casts, would it be better to do this with vtable tricks?
106                     static_cast<PcieUpstreamNode*>(
107                     static_cast<PcieBridge*>(downstream_device.get()))->ScanDownstream();
108                 }
109             }
110 
111             /* If this was function zero, and there is either no device, or the
112              * config's header type indicates that this is not a multi-function
113              * device, then just move on to the next device. */
114             if (!func_id &&
115                (!good_device || !(cfg->Read(PciConfig::kHeaderType) & PCI_HEADER_TYPE_MULTI_FN)))
116                 break;
117         }
118     }
119 }
120 
ScanDevice(const PciConfig * cfg,uint dev_id,uint func_id)121 fbl::RefPtr<PcieDevice> PcieUpstreamNode::ScanDevice(const PciConfig* cfg,
122                                                       uint dev_id,
123                                                       uint func_id) {
124     DEBUG_ASSERT(cfg);
125     DEBUG_ASSERT(dev_id  < PCIE_MAX_DEVICES_PER_BUS);
126     DEBUG_ASSERT(func_id < PCIE_MAX_FUNCTIONS_PER_DEVICE);
127     DEBUG_ASSERT(driver().RescanLockIsHeld());
128 
129     __UNUSED uint ndx = (dev_id * PCIE_MAX_FUNCTIONS_PER_DEVICE) + func_id;
130     DEBUG_ASSERT(ndx < fbl::count_of(downstream_));
131     DEBUG_ASSERT(downstream_[ndx] == nullptr);
132 
133     LTRACEF("Scanning new function at %02x:%02x.%01x\n", managed_bus_id_, dev_id, func_id);
134 
135     /* Is there an actual device here? */
136     uint16_t vendor_id = cfg->Read(PciConfig::kVendorId);
137     if (vendor_id == PCIE_INVALID_VENDOR_ID) {
138         LTRACEF("Bad vendor ID (0x%04hx) when looking for PCIe device at %02x:%02x.%01x\n",
139                 vendor_id, managed_bus_id_, dev_id, func_id);
140         return nullptr;
141     }
142 
143     // Create the either a PcieBridge or a PcieDevice based on the configuration
144     // header type.
145     uint8_t header_type = cfg->Read(PciConfig::kHeaderType) & PCI_HEADER_TYPE_MASK;
146     if (header_type == PCI_HEADER_TYPE_PCI_BRIDGE) {
147         uint secondary_id = cfg->Read(PciConfig::kSecondaryBusId);
148         return PcieBridge::Create(*this, dev_id, func_id, secondary_id);
149     }
150 
151     return PcieDevice::Create(*this, dev_id, func_id);
152 }
153