| // 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); |
| } |