| // 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/pci_config.h> |
| #include <dev/pcie_bridge.h> |
| #include <fbl/alloc_checker.h> |
| #include <kernel/spinlock.h> |
| #include <vm/vm.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| PcieBridge::PcieBridge(PcieBusDriver& bus_drv, uint bus_id, uint dev_id, uint func_id, uint mbus_id) |
| : PcieDevice(bus_drv, bus_id, dev_id, func_id, true), |
| PcieUpstreamNode(bus_drv, PcieUpstreamNode::Type::BRIDGE, mbus_id) { |
| /* Assign the driver-wide region pool to this bridge's allocators. */ |
| DEBUG_ASSERT(driver().region_bookkeeping() != nullptr); |
| pf_mmio_regions_.SetRegionPool(driver().region_bookkeeping()); |
| mmio_lo_regions_.SetRegionPool(driver().region_bookkeeping()); |
| mmio_hi_regions_.SetRegionPool(driver().region_bookkeeping()); |
| pio_regions_.SetRegionPool(driver().region_bookkeeping()); |
| } |
| |
| fbl::RefPtr<PcieDevice> PcieBridge::Create(PcieUpstreamNode& upstream, uint dev_id, uint func_id, |
| uint managed_bus_id) { |
| fbl::AllocChecker ac; |
| auto raw_bridge = new (&ac) |
| PcieBridge(upstream.driver(), upstream.managed_bus_id(), dev_id, func_id, managed_bus_id); |
| if (!ac.check()) { |
| DEBUG_ASSERT(raw_bridge == nullptr); |
| TRACEF("Out of memory attemping to create PCIe bridge %02x:%02x.%01x.\n", |
| upstream.managed_bus_id(), dev_id, func_id); |
| return nullptr; |
| } |
| |
| auto bridge = fbl::AdoptRef(static_cast<PcieDevice*>(raw_bridge)); |
| zx_status_t res = raw_bridge->Init(upstream); |
| if (res != ZX_OK) { |
| TRACEF("Failed to initialize PCIe bridge %02x:%02x.%01x. (res %d)\n", upstream.managed_bus_id(), |
| dev_id, func_id, res); |
| return nullptr; |
| } |
| |
| return bridge; |
| } |
| |
| zx_status_t PcieBridge::Init(PcieUpstreamNode& upstream) { |
| Guard<Mutex> guard{&bridge_lock_}; |
| zx_status_t res; |
| // Initialize the device portion of ourselves first. |
| { |
| Guard<Mutex> _{dev_lock()}; |
| res = PcieDevice::InitLocked(upstream); |
| if (res != ZX_OK) { |
| return res; |
| } |
| } |
| |
| // Sanity checks of bus allocation. |
| // |
| // TODO(johngro) : Strengthen sanity checks around bridge topology and |
| // handle the need to reconfigure bridge topology if a bridge happens to be |
| // misconfigured. Right now, we just assume that the BIOS/Bootloader has |
| // taken care of bridge configuration. In the short term, it would be good |
| // to add some protection against cycles in the bridge configuration which |
| // could lead to infinite recursion. |
| uint primary_id = cfg_->Read(PciConfig::kPrimaryBusId); |
| uint secondary_id = cfg_->Read(PciConfig::kSecondaryBusId); |
| |
| if (primary_id == secondary_id) { |
| TRACEF( |
| "PCI-to-PCI bridge detected at %02x:%02x.%01x claims to be bridged to itsef " |
| "(primary %02x == secondary %02x)... skipping scan.\n", |
| bus_id_, dev_id_, func_id_, primary_id, secondary_id); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (primary_id != bus_id_) { |
| TRACEF( |
| "PCI-to-PCI bridge detected at %02x:%02x.%01x has invalid primary bus id " |
| "(%02x)... skipping scan.\n", |
| bus_id_, dev_id_, func_id_, primary_id); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (secondary_id != managed_bus_id()) { |
| TRACEF( |
| "PCI-to-PCI bridge detected at %02x:%02x.%01x has invalid secondary bus id " |
| "(%02x)... skipping scan.\n", |
| bus_id_, dev_id_, func_id_, secondary_id); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // Parse the state of its I/O and Memory windows. |
| res = ParseBusWindowsLocked(); |
| if (res != ZX_OK) { |
| return res; |
| } |
| |
| // Things went well, flag the device as plugged in and link ourselves up to |
| // the graph. |
| plugged_in_ = true; |
| driver().LinkDeviceToUpstream(*this, upstream); |
| // Release the device lock, then recurse and scan for downstream devices. |
| ScanDownstream(); |
| return ZX_OK; |
| } |
| |
| zx_status_t PcieBridge::EnableBusMasterUpstream(bool enabled) { |
| // If being asked to disable Bus Mastering then we should ensure that no other |
| // devices downstream of this bridge still have it enabled. If any do then we |
| // leave BusMastering enabled. |
| Guard<Mutex> guard{&bridge_lock_}; |
| if (enabled) { |
| downstream_bus_mastering_cnt_++; |
| } else { |
| if (downstream_bus_mastering_cnt_ == 0) { |
| return ZX_ERR_BAD_STATE; |
| } |
| downstream_bus_mastering_cnt_--; |
| } |
| |
| LTRACEF("UpstreamNode bm cnt: %zu\n", downstream_bus_mastering_cnt_); |
| // Only make a change to the bridge's bus mastering configuration in a case |
| // where the state of the children has changed meaningfully. |
| if (downstream_bus_mastering_cnt_ == 0) { |
| LTRACEF("Disabling BusMastering\n"); |
| return PcieDevice::EnableBusMaster(false); |
| } |
| |
| if (downstream_bus_mastering_cnt_ == 1 && enabled) { |
| LTRACEF("Enabling BusMastering\n"); |
| return PcieDevice::EnableBusMaster(true); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t PcieBridge::ParseBusWindowsLocked() { |
| // Parse the currently configured windows used to determine MMIO/PIO |
| // forwarding policy for this bridge. |
| // |
| // See The PCI-to-PCI Bridge Architecture Specification Revision 1.2, |
| // section 3.2.5 and chapter 4 for detail. |
| uint32_t base, limit; |
| |
| // I/O window |
| base = cfg_->Read(PciConfig::kIoBase); |
| limit = cfg_->Read(PciConfig::kIoLimit); |
| |
| supports_32bit_pio_ = ((base & 0xF) == 0x1) && ((base & 0xF) == (limit & 0xF)); |
| io_base_ = (base & ~0xF) << 8; |
| io_limit_ = (limit << 8) | 0xFFF; |
| if (supports_32bit_pio_) { |
| io_base_ |= static_cast<uint32_t>(cfg_->Read(PciConfig::kIoBaseUpper)) << 16; |
| io_limit_ |= static_cast<uint32_t>(cfg_->Read(PciConfig::kIoLimitUpper)) << 16; |
| } |
| |
| // Non-prefetchable memory window |
| mem_base_ = (static_cast<uint32_t>(cfg_->Read(PciConfig::kMemoryBase)) << 16) & ~0xFFFFF; |
| mem_limit_ = (static_cast<uint32_t>(cfg_->Read(PciConfig::kMemoryLimit)) << 16) | 0xFFFFF; |
| |
| // Prefetchable memory window |
| base = cfg_->Read(PciConfig::kPrefetchableMemoryBase); |
| limit = cfg_->Read(PciConfig::kPrefetchableMemoryLimit); |
| |
| bool supports_64bit_pf_mem = ((base & 0xF) == 0x1) && ((base & 0xF) == (limit & 0xF)); |
| pf_mem_base_ = (base & ~0xF) << 16; |
| pf_mem_limit_ = (limit << 16) | 0xFFFFF; |
| if (supports_64bit_pf_mem) { |
| pf_mem_base_ |= static_cast<uint64_t>(cfg_->Read(PciConfig::kPrefetchableMemoryBaseUpper)) |
| << 32; |
| pf_mem_limit_ |= static_cast<uint64_t>(cfg_->Read(PciConfig::kPrefetchableMemoryLimitUpper)) |
| << 32; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void PcieBridge::Dump() const { |
| PcieDevice::Dump(); |
| |
| printf("\tbridge managed bus id %#02x\n", managed_bus_id()); |
| printf("\tio base %#x limit %#x\n", io_base(), io_limit()); |
| printf("\tmem base %#x limit %#x\n", mem_base(), mem_limit()); |
| printf("\tprefectable base %#" PRIx64 " limit %#" PRIx64 "\n", pf_mem_base(), pf_mem_limit()); |
| } |
| |
| void PcieBridge::Unplug() { |
| PcieDevice::Unplug(); |
| PcieUpstreamNode::UnplugDownstream(); |
| } |
| |
| zx_status_t PcieBridge::AllocateBars() { |
| { |
| Guard<Mutex> guard{&bridge_lock_}; |
| |
| // Start by making sure we can allocate our bridge windows. |
| zx_status_t res = AllocateBridgeWindowsLocked(); |
| if (res != ZX_OK) { |
| return res; |
| } |
| |
| // Now, attempt to allocate our device BARs. |
| { |
| Guard<Mutex> _{dev_lock()}; |
| res = PcieDevice::AllocateBarsLocked(); |
| if (res != ZX_OK) { |
| return res; |
| } |
| } |
| // Great, we are good to go. Leave our device lock and attempt to allocate |
| // our downstream devices' resources. |
| } |
| PcieUpstreamNode::AllocateDownstreamBars(); |
| return ZX_OK; |
| } |
| |
| zx_status_t PcieBridge::AllocateBridgeWindowsLocked() { |
| zx_status_t ret; |
| |
| // Hold a reference to our upstream node while we do this. If we cannot |
| // obtain a reference, then our upstream node has become unplugged and we |
| // should just fail out now. |
| auto upstream = GetUpstream(); |
| if (upstream == nullptr) { |
| return ZX_ERR_UNAVAILABLE; |
| } |
| |
| // We are configuring a bridge. We need to be able to allocate the MMIO and |
| // PIO regions this bridge is configured to manage. Currently, we don't |
| // support re-allocating a bridge's MMIO/PIO windows. |
| // |
| // TODO(johngro) : support dynamic configuration of bridge windows. Its |
| // going to be important when we need to support hot-plugging. See fxbug.dev/30282 |
| // |
| if (io_base_ <= io_limit_) { |
| uint64_t size = static_cast<uint64_t>(io_limit_) - io_base_ + 1; |
| ret = upstream->pio_regions().GetRegion({.base = io_base_, .size = size}, pio_window_); |
| |
| if (ret != ZX_OK) { |
| TRACEF("Failed to allocate bridge PIO window [0x%08x, 0x%08x]\n", io_base_, io_limit_); |
| return ret; |
| } |
| |
| DEBUG_ASSERT(pio_window_ != nullptr); |
| pio_regions().AddRegion(*pio_window_); |
| } |
| |
| if (mem_base_ <= mem_limit_) { |
| uint64_t size = mem_limit_ - mem_base_ + 1; |
| ret = upstream->mmio_lo_regions().GetRegion({.base = mem_base_, .size = size}, mmio_window_); |
| |
| if (ret != ZX_OK) { |
| TRACEF("Failed to allocate bridge MMIO window [0x%08x, 0x%08x]\n", mem_base_, mem_limit_); |
| return ret; |
| } |
| |
| DEBUG_ASSERT(mmio_window_ != nullptr); |
| mmio_lo_regions().AddRegion(*mmio_window_); |
| } |
| |
| if (pf_mem_base_ <= pf_mem_limit_) { |
| uint64_t size = pf_mem_limit_ - pf_mem_base_ + 1; |
| |
| // Attempt to allocate out of the upstream's prefetchable region. |
| ret = upstream->pf_mmio_regions().GetRegion({.base = pf_mem_base_, .size = size}, |
| pf_mmio_window_); |
| if (ret != ZX_OK) { |
| // We failed. If it's the root bridge try to allocate from its MMIO regions. |
| if (upstream->type() == PcieUpstreamNode::Type::ROOT) { |
| ret = upstream->mmio_lo_regions().GetRegion({.base = pf_mem_base_, .size = size}, |
| pf_mmio_window_); |
| if (ret != ZX_OK) { |
| ret = upstream->mmio_hi_regions().GetRegion({.base = pf_mem_base_, .size = size}, |
| pf_mmio_window_); |
| } |
| } |
| } |
| |
| if (ret != ZX_OK) { |
| TRACEF( |
| "Failed to allocate bridge prefetcable MMIO window " |
| "[%#" PRIx64 ", %#" PRIx64 "]\n", |
| pf_mem_base_, pf_mem_limit_); |
| return ret; |
| } |
| |
| DEBUG_ASSERT(pf_mmio_window_ != nullptr); |
| pf_mmio_regions().AddRegion(*pf_mmio_window_); |
| } |
| |
| return ZX_OK; |
| } |
| |
| void PcieBridge::Disable() { |
| // Immediately enter the device lock and enter the disabled state. We want |
| // to be outside of the device lock as we disable our downstream devices, |
| // but we don't want any new devices to be able to plug into us as we do so. |
| { |
| Guard<Mutex> guard{&dev_lock_}; |
| disabled_ = true; |
| |
| // Start by disabling all of our downstream devices. This should prevent |
| // the from bothering us moving forward. Do not hold the device lock while |
| // we do this. |
| guard.CallUnlocked([this]() { PcieUpstreamNode::DisableDownstream(); }); |
| |
| // Enter the device lock again and finish shooting ourselves in the head. |
| |
| // Disable the device portion of ourselves. |
| PcieDevice::DisableLocked(); |
| |
| // Close all of our IO windows at the HW level and update the internal |
| // bookkeeping to indicate that they are closed. |
| cfg_->Write(PciConfig::kIoBase, 0xF0); |
| cfg_->Write(PciConfig::kIoLimit, 0); |
| cfg_->Write(PciConfig::kIoBaseUpper, 0); |
| cfg_->Write(PciConfig::kIoLimitUpper, 0); |
| |
| cfg_->Write(PciConfig::kMemoryBase, 0xFFF0); |
| cfg_->Write(PciConfig::kMemoryLimit, 0); |
| |
| cfg_->Write(PciConfig::kPrefetchableMemoryBase, 0xFFF0); |
| cfg_->Write(PciConfig::kPrefetchableMemoryLimit, 0); |
| cfg_->Write(PciConfig::kPrefetchableMemoryBaseUpper, 0); |
| cfg_->Write(PciConfig::kPrefetchableMemoryLimitUpper, 0); |
| } |
| |
| Guard<Mutex> _{&bridge_lock_}; |
| pf_mem_limit_ = mem_limit_ = io_limit_ = 0u; |
| pf_mem_base_ = mem_base_ = io_base_ = 1u; |
| |
| // Release our internal bookkeeping |
| mmio_lo_regions().Reset(); |
| mmio_hi_regions().Reset(); |
| pio_regions().Reset(); |
| |
| mmio_window_.reset(); |
| pio_window_.reset(); |
| } |