|  | // Copyright 2016 The Fuchsia Authors | 
|  | // Copyright (c) 2016, Google, Inc. All rights reserved | 
|  | // | 
|  | // Use of this source code is governed by a MIT-style | 
|  | // license that can be found in the LICENSE file or at | 
|  | // https://opensource.org/licenses/MIT | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <debug.h> | 
|  | #include <inttypes.h> | 
|  | #include <platform.h> | 
|  | #include <string.h> | 
|  | #include <trace.h> | 
|  | #include <zircon/compiler.h> | 
|  | #include <zircon/errors.h> | 
|  | #include <zircon/types.h> | 
|  |  | 
|  | #include <dev/interrupt.h> | 
|  | #include <dev/pcie_bridge.h> | 
|  | #include <dev/pcie_root.h> | 
|  | #include <fbl/algorithm.h> | 
|  | #include <kernel/mutex.h> | 
|  | #include <kernel/spinlock.h> | 
|  | #include <ktl/iterator.h> | 
|  | #include <vm/vm.h> | 
|  |  | 
|  | #define LOCAL_TRACE 0 | 
|  |  | 
|  | PcieUpstreamNode::~PcieUpstreamNode() { | 
|  | if constexpr (DEBUG_ASSERT_IMPLEMENTED) { | 
|  | // Sanity check to make sure that all child devices have been released as | 
|  | // well. | 
|  | for (size_t i = 0; i < ktl::size(downstream_); ++i) | 
|  | DEBUG_ASSERT(!downstream_[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PcieUpstreamNode::AllocateDownstreamBars() { | 
|  | /* Finally, allocate all of the BARs for our downstream devices.  Make sure | 
|  | * to not access our downstream devices directly.  Instead, hold references | 
|  | * to downstream devices we obtain while holding bus driver's topology lock. | 
|  | * */ | 
|  | for (uint i = 0; i < ktl::size(downstream_); ++i) { | 
|  | auto device = GetDownstream(i); | 
|  | if (device != nullptr) { | 
|  | zx_status_t res = device->AllocateBars(); | 
|  | if (res != ZX_OK) | 
|  | device->Disable(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void PcieUpstreamNode::DisableDownstream() { | 
|  | for (uint i = 0; i < ktl::size(downstream_); ++i) { | 
|  | auto downstream_device = GetDownstream(i); | 
|  | if (downstream_device) | 
|  | downstream_device->Disable(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PcieUpstreamNode::UnplugDownstream() { | 
|  | for (uint i = 0; i < ktl::size(downstream_); ++i) { | 
|  | auto downstream_device = GetDownstream(i); | 
|  | if (downstream_device) | 
|  | downstream_device->Unplug(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PcieUpstreamNode::ScanDownstream() { | 
|  | DEBUG_ASSERT(driver().RescanLockIsHeld()); | 
|  |  | 
|  | for (uint dev_id = 0; dev_id < PCIE_MAX_DEVICES_PER_BUS; ++dev_id) { | 
|  | for (uint func_id = 0; func_id < PCIE_MAX_FUNCTIONS_PER_DEVICE; ++func_id) { | 
|  | /* If we can find the config, and it has a valid vendor ID, go ahead | 
|  | * and scan it looking for a valid function. */ | 
|  | auto cfg = driver().GetConfig(managed_bus_id_, dev_id, func_id); | 
|  | if (cfg == nullptr) { | 
|  | TRACEF("Warning: bus being scanned is outside ecam region!\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | uint16_t vendor_id = cfg->Read(PciConfig::kVendorId); | 
|  | bool good_device = cfg && (vendor_id != PCIE_INVALID_VENDOR_ID); | 
|  | if (good_device) { | 
|  | uint16_t device_id = cfg->Read(PciConfig::kDeviceId); | 
|  | LTRACEF("found valid device %04x:%04x at %02x:%02x.%01x\n", vendor_id, device_id, | 
|  | managed_bus_id_, dev_id, func_id); | 
|  | /* Don't scan the function again if we have already discovered | 
|  | * it.  If this function happens to be a bridge, go ahead and | 
|  | * look under it for new devices. */ | 
|  | uint ndx = (dev_id * PCIE_MAX_FUNCTIONS_PER_DEVICE) + func_id; | 
|  | DEBUG_ASSERT(ndx < ktl::size(downstream_)); | 
|  |  | 
|  | auto downstream_device = GetDownstream(ndx); | 
|  | if (!downstream_device) { | 
|  | auto new_dev = ScanDevice(cfg, dev_id, func_id); | 
|  | if (new_dev == nullptr) { | 
|  | TRACEF( | 
|  | "Failed to initialize device %02x:%02x.%01x; This is Very Bad.  " | 
|  | "Device (and any of its children) will be inaccessible!\n", | 
|  | managed_bus_id_, dev_id, func_id); | 
|  | good_device = false; | 
|  | } | 
|  | } else if (downstream_device->is_bridge()) { | 
|  | // TODO(johngro) : Instead of going up and down the class graph with static | 
|  | // casts, would it be better to do this with vtable tricks? | 
|  | static_cast<PcieUpstreamNode*>(static_cast<PcieBridge*>(downstream_device.get())) | 
|  | ->ScanDownstream(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* If this was function zero, and there is either no device, or the | 
|  | * config's header type indicates that this is not a multi-function | 
|  | * device, then just move on to the next device. */ | 
|  | if (!func_id && | 
|  | (!good_device || !(cfg->Read(PciConfig::kHeaderType) & PCI_HEADER_TYPE_MULTI_FN))) | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | fbl::RefPtr<PcieDevice> PcieUpstreamNode::ScanDevice(const PciConfig* cfg, uint dev_id, | 
|  | uint func_id) { | 
|  | DEBUG_ASSERT(cfg); | 
|  | DEBUG_ASSERT(dev_id < PCIE_MAX_DEVICES_PER_BUS); | 
|  | DEBUG_ASSERT(func_id < PCIE_MAX_FUNCTIONS_PER_DEVICE); | 
|  | DEBUG_ASSERT(driver().RescanLockIsHeld()); | 
|  |  | 
|  | __UNUSED uint ndx = (dev_id * PCIE_MAX_FUNCTIONS_PER_DEVICE) + func_id; | 
|  | DEBUG_ASSERT(ndx < ktl::size(downstream_)); | 
|  | DEBUG_ASSERT(downstream_[ndx] == nullptr); | 
|  |  | 
|  | LTRACEF("Scanning new function at %02x:%02x.%01x\n", managed_bus_id_, dev_id, func_id); | 
|  |  | 
|  | /* Is there an actual device here? */ | 
|  | uint16_t vendor_id = cfg->Read(PciConfig::kVendorId); | 
|  | if (vendor_id == PCIE_INVALID_VENDOR_ID) { | 
|  | LTRACEF("Bad vendor ID (0x%04hx) when looking for PCIe device at %02x:%02x.%01x\n", vendor_id, | 
|  | managed_bus_id_, dev_id, func_id); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // Create the either a PcieBridge or a PcieDevice based on the configuration | 
|  | // header type. | 
|  | uint8_t header_type = cfg->Read(PciConfig::kHeaderType) & PCI_HEADER_TYPE_MASK; | 
|  | if (header_type == PCI_HEADER_TYPE_PCI_BRIDGE) { | 
|  | uint secondary_id = cfg->Read(PciConfig::kSecondaryBusId); | 
|  | return PcieBridge::Create(*this, dev_id, func_id, secondary_id); | 
|  | } | 
|  |  | 
|  | return PcieDevice::Create(*this, dev_id, func_id); | 
|  | } |