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