blob: 79acc344ad8de80af6be41f90cb6bac14ce0f3b2 [file] [log] [blame]
// 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
#pragma once
#include <dev/address_provider/address_provider.h>
#include <dev/pci_config.h>
#include <dev/pcie_platform.h>
#include <kernel/auto_lock.h>
#include <kernel/mutex.h>
#include <fbl/intrusive_single_list.h>
#include <fbl/intrusive_wavl_tree.h>
#include <fbl/macros.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <region-alloc/region-alloc.h>
class SharedLegacyIrqHandler;
class PcieBridge;
class PcieDebugConsole;
class PcieDevice;
class PcieRoot;
class PcieUpstreamNode;
class PciConfig;
class PcieBusDriver : public fbl::RefCounted<PcieBusDriver> {
public:
// QuirkHandler
//
// Definition of a quirk handler hook. Quirks are behaviors which can be
// registered by platforms to deal with the sometimes odd (dare I say,
// quirky?) behavior of hardware detected on the PCI bus. All registered
// quirks handlers are executed whenever new hardware is discovered and
// probed, but before resource assignment has taken place.
//
// Once the system has been initialized and is ready to begin resource
// allocation, all quirks will be executed one final time will nullptr
// passed as the device argument. It is recommended that all quirks
// implementations use this final call as one last chance to make certain
// that the quirk has successfully done its job, and to log a warning/error
// if it has not.
//
// For example, if a platform has a quirk to deal with a particular oddness
// of a specific chipset, the quirk should use the final call as a chance to
// check to make sure that it saw a chipset device recognized and took
// appropriate action. If it didn't, it should log a warning informing the
// maintainers to come back and update the quirk to take the appropriate
// actions (if any) for the new chipset.
using QuirkHandler = void (*)(const fbl::RefPtr<PcieDevice>& device);
~PcieBusDriver();
PciePlatformInterface& platform() const { return platform_; }
const PciConfig* GetConfig(uint bus_id,
uint dev_id,
uint func_id,
paddr_t* out_cfg_phys = nullptr);
// Address space (PIO and MMIO) allocation management
//
// Note: Internally, regions held for MMIO address space allocation are
// tracked in two different allocators; one for <4GB allocations usable by
// 32-bit or 64-bit BARs, and one for >4GB allocations usable only by 64-bit
// BARs.
//
// Users of Add/SubtractBusRegion are permitted to supply regions which span
// the 4GB mark in the MMIO address space, but their operation will be
// internally split into two different operations executed against the two
// different allocators. The low memory portion of the operation will be
// executed first. In the case that the first of the split operations
// succeeds but the second fails, the first operation will not be rolled
// back. If this behavior is unacceptable, users should be sure to submit
// only MMIO address space operations which target regions either entirely
// above or entirely below the 4GB mark.
zx_status_t AddBusRegion(uint64_t base, uint64_t size, PciAddrSpace aspace) {
return AddSubtractBusRegion(base, size, aspace, true);
}
zx_status_t SubtractBusRegion(uint64_t base, uint64_t size, PciAddrSpace aspace) {
return AddSubtractBusRegion(base, size, aspace, false);
}
// Add a root bus to the driver and attempt to scan it for devices.
zx_status_t AddRoot(fbl::RefPtr<PcieRoot>&& root);
// A PcieAddressProvider translates a BDF address to an address that the
// system can use to access ECAMs.
zx_status_t SetAddressTranslationProvider(ktl::unique_ptr<PcieAddressProvider> provider);
// Start the driver
//
// Notes about startup:
// Before starting the bus driver, platforms must add all of the resources
// to be used by the driver during operation. Once started, the set of
// resources used by the driver may not be modified. Resources which must
// be supplied include...
//
// ++ ECAM regions for memory mapped config sections. See AddEcamRegion
// ++ Bus regions for both MMIO and PIO bus access. See (Add|Subtract)BusRegion
// ++ Roots. See AddRoot
//
// Resources may be added in any order.
//
// Once all of the resources have been added, StartBusDriver will scan for
// devices under each of the added roots, run all registered quirks and
// attempt to allocated bus/IRQ resources for discovered devices.
//
zx_status_t StartBusDriver();
// Rescan looking for new devices
zx_status_t RescanDevices();
// TODO(johngro) : Remove this someday. Getting the "Nth" device is not a
// concept which is going to carry over well to the world of hot-pluggable
// devices.
fbl::RefPtr<PcieDevice> GetNthDevice(uint32_t index);
// Topology related stuff
void LinkDeviceToUpstream(PcieDevice& dev, PcieUpstreamNode& upstream);
void UnlinkDeviceFromUpstream(PcieDevice& dev);
fbl::RefPtr<PcieUpstreamNode> GetUpstream(PcieDevice& dev);
fbl::RefPtr<PcieDevice> GetDownstream(PcieUpstreamNode& upstream, uint ndx);
fbl::RefPtr<PcieDevice> GetRefedDevice(uint bus_id, uint dev_id, uint func_id);
// Bus region allocation
const RegionAllocator::RegionPool::RefPtr& region_bookkeeping() const {
return region_bookkeeping_;
}
RegionAllocator& pf_mmio_regions() { return pf_mmio_regions_; }
RegionAllocator& mmio_lo_regions() { return mmio_lo_regions_; }
RegionAllocator& mmio_hi_regions() { return mmio_hi_regions_; }
RegionAllocator& pio_regions() { return pio_regions_; }
// TODO(johngro) : Make this private when we can.
fbl::RefPtr<SharedLegacyIrqHandler> FindLegacyIrqHandler(uint irq_id);
// TODO(johngro) : end TODO section
// Disallow copying, assigning and moving.
DISALLOW_COPY_ASSIGN_AND_MOVE(PcieBusDriver);
static fbl::RefPtr<PcieBusDriver> GetDriver() {
fbl::AutoLock lock(&driver_lock_);
return driver_;
}
void DisableBus();
static zx_status_t InitializeDriver(PciePlatformInterface& platform);
static void ShutdownDriver();
// Debug/ASSERT routine, used by devices and bridges to assert that the
// rescan lock is currently being held.
bool RescanLockIsHeld() const { return bus_rescan_lock_.IsHeld(); }
private:
friend class PcieDebugConsole;
static constexpr size_t REGION_BOOKKEEPING_SLAB_SIZE = 16 << 10;
static constexpr size_t REGION_BOOKKEEPING_MAX_MEM = 128 << 10;
using RootCollection = fbl::WAVLTree<uint, fbl::RefPtr<PcieRoot>>;
using ForeachRootCallback = bool (*)(const fbl::RefPtr<PcieRoot>& root, void* ctx);
using ForeachDeviceCallback = bool (*)(const fbl::RefPtr<PcieDevice>& dev,
void* ctx, uint level);
enum class State {
NOT_STARTED = 0,
STARTING_SCANNING = 1,
STARTING_RUNNING_QUIRKS = 2,
STARTING_RESOURCE_ALLOCATION = 3,
OPERATIONAL = 4,
};
explicit PcieBusDriver(PciePlatformInterface& platform);
bool AdvanceState(State expected, State next);
bool IsNotStarted(bool allow_quirks_phase = false) const;
bool IsOperational() const { smp_mb(); return state_ == State::OPERATIONAL; }
zx_status_t AllocBookkeeping();
void ForeachRoot(ForeachRootCallback cbk, void* ctx);
void ForeachDevice(ForeachDeviceCallback cbk, void* ctx);
bool ForeachDownstreamDevice(const fbl::RefPtr<PcieUpstreamNode>& upstream,
uint level,
ForeachDeviceCallback cbk,
void* ctx);
zx_status_t AddSubtractBusRegion(uint64_t base, uint64_t size,
PciAddrSpace aspace, bool add_op);
// IRQ support. Implementation in pcie_irqs.cpp
void ShutdownIrqs();
static void RunQuirks(const fbl::RefPtr<PcieDevice>& device);
State state_ = State::NOT_STARTED;
fbl::Mutex bus_topology_lock_;
fbl::Mutex bus_rescan_lock_;
mutable fbl::Mutex start_lock_;
RootCollection roots_;
fbl::SinglyLinkedList<fbl::RefPtr<PciConfig>> configs_;
RegionAllocator::RegionPool::RefPtr region_bookkeeping_;
RegionAllocator pf_mmio_regions_;
RegionAllocator mmio_lo_regions_;
RegionAllocator mmio_hi_regions_;
RegionAllocator pio_regions_;
ktl::unique_ptr<PcieAddressProvider> addr_provider_;
fbl::Mutex legacy_irq_list_lock_;
fbl::SinglyLinkedList<fbl::RefPtr<SharedLegacyIrqHandler>> legacy_irq_list_;
PciePlatformInterface& platform_;
static fbl::RefPtr<PcieBusDriver> driver_;
static fbl::Mutex driver_lock_;
};