blob: 10fb2ba0a92bfbbc3e8a0ad49dce6c7705723478 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#include "pci.h"
#include <fuchsia/hardware/pciroot/c/banjo.h>
#include <inttypes.h>
#include <lib/pci/pciroot.h>
#include <lib/pci/pio.h>
#include <lib/pci/root_host.h>
#include <lib/zx/resource.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <zircon/status.h>
#include <zircon/syscalls/resource.h>
#include <zircon/types.h>
#include <memory>
#include <acpica/acpi.h>
#include <acpica/actypes.h>
#include <acpica/acuuid.h>
#include <bits/limits.h>
#include <ddk/debug.h>
#include <fbl/alloc_checker.h>
#include <fbl/array.h>
#include <fbl/string_buffer.h>
#include <fbl/vector.h>
#include <region-alloc/region-alloc.h>
#include "acpi-private.h"
#include "methods.h"
#include "resources.h"
// This file contains the implementation for the code supporting the in-progress userland
// pci bus driver.
std::unique_ptr<PciRootHost> RootHost = {};
struct ResourceContext {
zx_handle_t pci_handle;
bool device_is_root_bridge;
bool add_pass;
};
// ACPICA will call this function for each resource found while walking a device object's resource
// list.
static ACPI_STATUS resource_report_callback(ACPI_RESOURCE* res, void* _ctx) {
auto* ctx = static_cast<ResourceContext*>(_ctx);
zx_status_t status;
bool is_mmio = false;
uint64_t base = 0;
uint64_t len = 0;
bool add_range = false;
if (resource_is_memory(res)) {
resource_memory_t mem;
status = resource_parse_memory(res, &mem);
if (status != ZX_OK || mem.minimum != mem.maximum) {
return AE_ERROR;
}
is_mmio = true;
base = mem.minimum;
len = mem.address_length;
} else if (resource_is_address(res)) {
resource_address_t addr;
status = resource_parse_address(res, &addr);
if (status != ZX_OK) {
return AE_ERROR;
}
if (addr.resource_type == RESOURCE_ADDRESS_MEMORY) {
is_mmio = true;
} else if (addr.resource_type == RESOURCE_ADDRESS_IO) {
is_mmio = false;
} else {
return AE_OK;
}
if (!addr.min_address_fixed || !addr.max_address_fixed || addr.maximum < addr.minimum) {
printf("WARNING: ACPI found bad _CRS address entry\n");
return AE_OK;
}
// We compute len from maximum rather than address_length, since some
// implementations don't set address_length...
base = addr.minimum;
len = addr.maximum - base + 1;
// PCI root bridges report downstream resources via _CRS. Since we're
// gathering data on acceptable ranges for PCI to use for MMIO, consider
// non-consume-only address resources to be valid for PCI MMIO.
if (ctx->device_is_root_bridge && !addr.consumed_only) {
add_range = true;
}
} else if (resource_is_io(res)) {
resource_io_t io;
status = resource_parse_io(res, &io);
if (status != ZX_OK) {
return AE_ERROR;
}
if (io.minimum != io.maximum) {
printf("WARNING: ACPI found bad _CRS IO entry\n");
return AE_OK;
}
is_mmio = false;
base = io.minimum;
len = io.address_length;
} else {
return AE_OK;
}
// Ignore empty regions that are reported, and skip any resources that
// aren't for the pass we're doing.
if (len == 0 || add_range != ctx->add_pass) {
return AE_OK;
}
if (add_range && is_mmio && base < MB(1)) {
// The PC platform defines many legacy regions below 1MB that we do not
// want PCIe to try to map onto.
zxlogf(INFO, "Skipping adding MMIO range due to being below 1MB");
return AE_OK;
}
// Add/Subtract the [base, len] region we found through ACPI to the allocators
// that PCI can use to allocate BARs.
RegionAllocator* alloc;
if (is_mmio) {
if (base + len < UINT32_MAX) {
alloc = &RootHost->Mmio32();
} else {
alloc = &RootHost->Mmio64();
}
} else {
alloc = &RootHost->Io();
}
zxlogf(DEBUG, "ACPI range modification: %sing %s %016lx %016lx", add_range ? "add" : "subtract",
is_mmio ? "MMIO" : "PIO", base, len);
// Not all resources ACPI informs us are in use are provided to us as
// resources in the first search, so we allow Incomplete ranges in both add
// and subtract passes.
if (add_range) {
status = alloc->AddRegion({.base = base, .size = len}, RegionAllocator::AllowOverlap::Yes);
} else {
status =
alloc->SubtractRegion({.base = base, .size = len}, RegionAllocator::AllowIncomplete::Yes);
}
if (status != ZX_OK) {
if (add_range) {
zxlogf(INFO, "Failed to add range: [%#lx - %#lx] (%#lx): %d", base, base + len, len, status);
} else {
// If we are subtracting a range and fail, abort. This is bad.
zxlogf(INFO, "Failed to subtract range [%#lx - %#lx] (%#lx): %d", base, base + len, len,
status);
return AE_ERROR;
}
}
return AE_OK;
}
// ACPICA will call this function once per device object found while walking the device
// tree off of the PCI root.
static ACPI_STATUS walk_devices_callback(ACPI_HANDLE object, uint32_t /*nesting_level*/, void* _ctx,
void** /*ret*/) {
acpi::UniquePtr<ACPI_DEVICE_INFO> info;
auto res = acpi::GetObjectInfo(object);
if (res.is_error()) {
zxlogf(DEBUG, "acpi::GetObjectInfo failed %d", res.error_value());
return res.error_value();
}
info = std::move(res.value());
auto* ctx = static_cast<ResourceContext*>(_ctx);
ctx->device_is_root_bridge = (info->Flags & ACPI_PCI_ROOT_BRIDGE) != 0;
ACPI_STATUS status =
AcpiWalkResources(object, const_cast<char*>("_CRS"), resource_report_callback, ctx);
if (status == AE_NOT_FOUND || status == AE_OK) {
return AE_OK;
}
return status;
}
/* @brief Report current resources to the kernel PCI driver
*
* Walks the ACPI namespace and use the reported current resources to inform
the kernel PCI interface about what memory it shouldn't use.
*
* @param root_resource_handle The handle to pass to the kernel when talking
* to the PCI driver.
*
* @return ZX_OK on success
*/
zx_status_t scan_acpi_tree_for_resources(zx_handle_t root_resource_handle) {
// First we search for resources to add, then we subtract out things that
// are being consumed elsewhere. This forces an ordering on the
// operations so that it should be consistent, and should protect against
// inconistencies in the _CRS methods.
// Walk the device tree and add to the PCIe IO ranges any resources
// "produced" by the PCI root in the ACPI namespace.
ResourceContext ctx = {
.pci_handle = root_resource_handle,
.device_is_root_bridge = false,
.add_pass = true,
};
ACPI_STATUS status = AcpiGetDevices(nullptr, walk_devices_callback, &ctx, nullptr);
if (status != AE_OK) {
return ZX_ERR_INTERNAL;
}
// Removes resources we believe are in use by other parts of the platform
ctx.add_pass = false;
status = AcpiGetDevices(nullptr, walk_devices_callback, &ctx, nullptr);
if (status != AE_OK) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
// Reads the MCFG table from ACPI and caches it for later calls
// to pci_ger_segment_mcfg_alloc()
static zx_status_t read_mcfg_table(std::vector<McfgAllocation>* mcfg_table) {
// Systems will have an MCFG table unless they only support legacy PCI.
ACPI_TABLE_HEADER* raw_table = nullptr;
ACPI_STATUS status = AcpiGetTable(const_cast<char*>(ACPI_SIG_MCFG), 1, &raw_table);
if (status != AE_OK) {
zxlogf(DEBUG, "no MCFG table found.");
return ZX_ERR_NOT_FOUND;
}
// The MCFG table contains a variable number of Extended Config tables
// hanging off of the end. Typically there will be one, but more
// complicated systems may have multiple per PCI Host Bridge. The length in
// the header is the overall size, so that is used to calculate how many
// ECAMs are included.
auto* mcfg = reinterpret_cast<ACPI_TABLE_MCFG*>(raw_table);
uintptr_t table_start = reinterpret_cast<uintptr_t>(mcfg) + sizeof(ACPI_TABLE_MCFG);
uintptr_t table_end = reinterpret_cast<uintptr_t>(mcfg) + mcfg->Header.Length;
size_t table_bytes = table_end - table_start;
if (table_bytes % sizeof(McfgAllocation)) {
zxlogf(ERROR, "MCFG table has invalid size %zu", table_bytes);
return ZX_ERR_INTERNAL;
}
// Each allocation corresponds to a particular PCI Segment Group. We'll
// store them so that the protocol can return them for bus driver instances
// later.
for (unsigned i = 0; i < table_bytes / sizeof(acpi_mcfg_allocation); i++) {
auto entry = &(reinterpret_cast<acpi_mcfg_allocation*>(table_start))[i];
zxlogf(DEBUG, "MCFG allocation %u (Addr = %#llx, Segment = %u, Start = %u, End = %u)", i,
entry->Address, entry->PciSegment, entry->StartBusNumber, entry->EndBusNumber);
mcfg_table->push_back(
{entry->Address, entry->PciSegment, entry->StartBusNumber, entry->EndBusNumber});
}
return ZX_OK;
}
zx_status_t pci_init_interrupts(ACPI_HANDLE object, x64Pciroot::Context* dev_ctx) {
zx::vmo routing_vmo{};
if (acpi::GetPciRootIrqRouting(object, dev_ctx) != AE_OK) {
zxlogf(ERROR, "Failed to obtain PCI IRQ routing information, legacy IRQs will not function");
}
fbl::Array<pci_legacy_irq> irq_list(new pci_legacy_irq[dev_ctx->irqs.size()]{},
dev_ctx->irqs.size());
size_t irq_cnt = 0;
fbl::StringBuffer<ZX_MAX_NAME_LEN> name = {};
name.Append(dev_ctx->name, sizeof(dev_ctx->name));
name.Append(" legacy");
for (const auto& e : dev_ctx->irqs) {
const uint32_t& vector = e.first;
const acpi_legacy_irq& irq_cfg = e.second;
zx::resource resource;
zx_status_t status = zx::resource::create(*zx::unowned_resource(get_root_resource()),
ZX_RSRC_KIND_IRQ | ZX_RSRC_FLAG_EXCLUSIVE, vector, 1,
name.data(), name.size(), &resource);
if (status != ZX_OK) {
zxlogf(ERROR, "Couldn't create resource for legacy vector %#x: %s, skipping it", vector,
zx_status_get_string(status));
continue;
}
status =
zx_interrupt_create(resource.get(), vector, irq_cfg.options, &irq_list[irq_cnt].interrupt);
if (status != ZX_OK) {
zxlogf(ERROR, "Couldn't create irq for legacy vector %#x: %s, skipping it", vector,
zx_status_get_string(status));
continue;
}
dev_ctx->irq_resources.push_back(std::move(resource));
irq_list[irq_cnt].vector = vector;
irq_cnt++;
}
dev_ctx->info.legacy_irqs_list = irq_list.release();
dev_ctx->info.legacy_irqs_count = irq_cnt;
return ZX_OK;
}
zx_status_t pci_init_segment_and_ecam(ACPI_HANDLE object, x64Pciroot::Context* dev_ctx) {
zx_status_t status = acpi_bbn_call(object, &dev_ctx->info.start_bus_num);
if (status != ZX_OK && status != ZX_ERR_NOT_FOUND) {
zxlogf(DEBUG, "Unable to read _BBN for '%s' (%d), assuming base bus of 0", dev_ctx->name,
status);
// Until we find an ecam we assume this potential legacy pci bus spans
// bus 0 to bus 255 in its segment group.
dev_ctx->info.end_bus_num = PCI_BUS_MAX;
}
bool found_bbn = (status == ZX_OK);
status = acpi_seg_call(object, &dev_ctx->info.segment_group);
if (status != ZX_OK) {
dev_ctx->info.segment_group = 0;
zxlogf(DEBUG, "Unable to read _SEG for '%s' (%d), assuming segment group 0.", dev_ctx->name,
status);
}
// If an MCFG is found for the given segment group this root has then we'll
// cache it for later pciroot operations and use its information to populate
// any fields missing via _BBN / _SEG.
auto& pinfo = dev_ctx->info;
memcpy(pinfo.name, dev_ctx->name, sizeof(pinfo.name));
McfgAllocation mcfg_alloc;
status = RootHost->GetSegmentMcfgAllocation(dev_ctx->info.segment_group, &mcfg_alloc);
if (status == ZX_OK) {
// Print a warning if _BBN and MCFG bus numbers don't match. We'll use the
// MCFG first if we have one, but a mismatch likely represents an error in
// an ACPI table.
if (found_bbn && mcfg_alloc.start_bus_number != pinfo.start_bus_num) {
zxlogf(WARNING, "conflicting base bus num for '%s', _BBN reports %u and MCFG reports %u",
dev_ctx->name, pinfo.start_bus_num, mcfg_alloc.start_bus_number);
}
// Same situation with Segment Group as with bus number above.
if (pinfo.segment_group != 0 && pinfo.segment_group != mcfg_alloc.pci_segment) {
zxlogf(WARNING, "conflicting segment group for '%s', _BBN reports %u and MCFG reports %u",
dev_ctx->name, pinfo.segment_group, mcfg_alloc.pci_segment);
}
// Since we have an ecam its metadata will replace anything defined in the ACPI tables.
pinfo.segment_group = mcfg_alloc.pci_segment;
pinfo.start_bus_num = mcfg_alloc.start_bus_number;
pinfo.end_bus_num = mcfg_alloc.end_bus_number;
// The bus driver needs a VMO representing the entire ecam region so it can map it in.
// The range from start_bus_num to end_bus_num is inclusive.
size_t ecam_size = (pinfo.end_bus_num - pinfo.start_bus_num + 1) * PCIE_ECAM_BYTES_PER_BUS;
zx_paddr_t vmo_base = mcfg_alloc.address + (pinfo.start_bus_num * PCIE_ECAM_BYTES_PER_BUS);
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
status = zx_vmo_create_physical(get_root_resource(), vmo_base, ecam_size, &pinfo.ecam_vmo);
if (status != ZX_OK) {
zxlogf(ERROR, "couldn't create VMO for ecam, mmio cfg will not work: %s!",
zx_status_get_string(status));
return status;
}
}
if (zxlog_level_enabled(DEBUG)) {
fbl::StringBuffer<128> log;
log.AppendPrintf("%s { acpi_obj(%p), bus range: %u:%u, segment: %u", dev_ctx->name,
dev_ctx->acpi_object, pinfo.start_bus_num, pinfo.end_bus_num,
pinfo.segment_group);
if (pinfo.ecam_vmo != ZX_HANDLE_INVALID) {
log.AppendPrintf(", ecam base: %#" PRIxPTR, mcfg_alloc.address);
}
log.AppendPrintf(" }");
zxlogf(DEBUG, "%s", log.c_str());
}
return ZX_OK;
}
// Parse the MCFG table and initialize the window allocators for the RootHost if this is the first
// root found.
zx_status_t pci_root_host_init() {
static bool initialized = false;
if (initialized) {
return ZX_OK;
}
if (!RootHost) {
RootHost = std::make_unique<PciRootHost>(zx::unowned_resource(get_root_resource()),
/*io_type=*/PCI_ADDRESS_SPACE_IO);
}
zx_status_t st = read_mcfg_table(&RootHost->mcfgs());
if (st != ZX_OK) {
return st;
}
st = scan_acpi_tree_for_resources(get_root_resource());
if (st != ZX_OK) {
zxlogf(ERROR, "Scanning acpi resources failed: %s", zx_status_get_string(st));
return st;
}
initialized = true;
return ZX_OK;
}
zx_status_t pci_init(zx_device_t* sys_root, zx_device_t* parent, ACPI_HANDLE object,
ACPI_DEVICE_INFO* info) {
zx_status_t status = pci_root_host_init();
if (status != ZX_OK) {
zxlogf(ERROR, "Error initializing PCI root host, attempting to boot regardless: %d", status);
}
// Build up a context structure for the PCI Root / Host Bridge we've found.
// If we find _BBN / _SEG we will use those, but if we don't we can fall
// back on having an ecam from mcfg allocations.
x64Pciroot::Context dev_ctx = {};
dev_ctx.platform_bus = parent;
dev_ctx.acpi_object = object;
dev_ctx.acpi_device_info = *info;
// ACPI names are stored as 4 bytes in a u32
memcpy(dev_ctx.name, &info->Name, 4);
status = pci_init_segment_and_ecam(object, &dev_ctx);
if (status != ZX_OK) {
zxlogf(ERROR, "Initializing %.*s ecam and bus information failed: %s",
static_cast<int>(sizeof(dev_ctx.name)), dev_ctx.name, zx_status_get_string(status));
return status;
}
status = pci_init_interrupts(object, &dev_ctx);
if (status != ZX_OK) {
zxlogf(ERROR, "Initializing %.*s interrupt information failed: %s",
static_cast<int>(sizeof(dev_ctx.name)), dev_ctx.name, zx_status_get_string(status));
return status;
}
// These are cached here to work around dev_ctx potentially going out of scope
// after device_add in the event that unbind/release are called from the DDK. See
// the below TODO for more information.
char name[ZX_DEVICE_NAME_MAX] = {0};
memcpy(name, dev_ctx.name, ACPI_NAMESEG_SIZE);
status = x64Pciroot::Create(&*RootHost, std::move(dev_ctx), parent, name);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to add pciroot device for '%s': %d", name, status);
} else {
zxlogf(INFO, "published pciroot '%s'", name);
}
return status;
}