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 29PcieUpstreamNode::~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 38void 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 53void 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 61void 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 69void 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 121fbl::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