blob: be0ddcc09d9679a0925122f3211b7b5bf02e4a55 [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 <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/unique_ptr.h>
#include <fbl/vector.h>
#include <inttypes.h>
#include <region-alloc/region-alloc.h>
#include <stdio.h>
#include <string.h>
#include "acpi-private.h"
#include "methods.h"
#include "pci.h"
#include "pciroot.h"
#include "resources.h"
// This file contains the implementation for the code supporting the in-progress userland
// pci bus driver.
static fbl::Vector<pci_mcfg_allocation_t*> mcfg_allocations;
const char* kLogTag = "acpi-pci:";
// These allocators container available regions of physical address space in the memory
// map that we should be able to allocate bars from. Different allocators exist for 32
// and 64 bit bars so that we can be sure addresses at < 4GB are preserved for 32 bit
// bars.
RegionAllocator kMmio32Alloc;
RegionAllocator kMmio64Alloc;
RegionAllocator kIoAlloc;
struct report_current_resources_ctx {
zx_handle_t pci_handle;
bool device_is_root_bridge;
bool add_pass;
};
static ACPI_STATUS report_current_resources_resource_cb_ex(ACPI_RESOURCE* res, void* _ctx) {
auto* ctx = static_cast<struct report_current_resources_ctx*>(_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 < 1024 * 1024) {
// 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\n");
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 = &kMmio32Alloc;
} else {
alloc = &kMmio64Alloc;
}
} else {
alloc = &kIoAlloc;
}
zxlogf(TRACE, "ACPI range modification: %sing %s %016lx %016lx\n",
add_range ? "add" : "subtract", is_mmio ? "MMIO" : "PIO", base, len);
if (add_range) {
status = alloc->AddRegion({.base = base, .size = len}, true);
} else {
status = alloc->SubtractRegion({.base = base, .size = len}, true);
}
if (status != ZX_OK) {
if (add_range) {
zxlogf(INFO, "Failed to add range: %d\n", status);
} else {
// If we are subtracting a range and fail, abort. This is bad.
return AE_ERROR;
}
}
return AE_OK;
}
static ACPI_STATUS report_current_resources_device_cb_ex(
ACPI_HANDLE object, uint32_t nesting_level, void* _ctx, void** ret) {
ACPI_DEVICE_INFO* info = NULL;
ACPI_STATUS status = AcpiGetObjectInfo(object, &info);
if (status != AE_OK) {
return status;
}
auto* ctx = static_cast<struct report_current_resources_ctx*>(_ctx);
ctx->device_is_root_bridge = (info->Flags & ACPI_PCI_ROOT_BRIDGE) != 0;
ACPI_FREE(info);
status = AcpiWalkResources(object, (char*)"_CRS", report_current_resources_resource_cb_ex, 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 pci_report_current_resources_ex(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.
struct report_current_resources_ctx ctx = {
.pci_handle = root_resource_handle,
.device_is_root_bridge = false,
.add_pass = true,
};
ACPI_STATUS status = AcpiGetDevices(NULL, report_current_resources_device_cb_ex, &ctx, NULL);
if (status != AE_OK) {
return ZX_ERR_INTERNAL;
}
// Removes resources we believe are in use by other parts of the platform
ctx = (struct report_current_resources_ctx){
.pci_handle = root_resource_handle,
.device_is_root_bridge = false,
.add_pass = false,
};
status = AcpiGetDevices(NULL, report_current_resources_device_cb_ex, &ctx, NULL);
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 pci_read_mcfg_table(void) {
// Systems will have an MCFG table unless they only support legacy PCI.
ACPI_TABLE_HEADER* raw_table = NULL;
ACPI_STATUS status = AcpiGetTable(const_cast<char*>(ACPI_SIG_MCFG), 1, &raw_table);
if (status != AE_OK) {
zxlogf(TRACE, "%s no MCFG table found.\n", kLogTag);
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.
ACPI_TABLE_MCFG* 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(pci_mcfg_allocation_t)) {
zxlogf(ERROR, "%s MCFG table has invalid size %zu\n", kLogTag, 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(pci_mcfg_allocation_t); i++) {
auto entry = &(reinterpret_cast<pci_mcfg_allocation_t*>(table_start))[i];
mcfg_allocations.push_back(entry);
zxlogf(TRACE, "%s MCFG allocation %u (Addr = %#lx, Segment = %u, Start = %u, End = %u)\n",
kLogTag, i, entry->base_address, entry->segment_group, entry->start_bus_num,
entry->end_bus_num);
}
return ZX_OK;
}
bool pci_platform_has_mcfg(void) {
return (mcfg_allocations.size() != 0);
}
// Search the MCFG allocations found earlier for an entry matching a given
// segment a host bridge is a part of. Per the PCI Firmware spec v3 table 4-3
// note 1, a given segment group will contain only a single mcfg allocation
// entry.
zx_status_t pci_get_segment_mcfg_alloc(unsigned segment_group, pci_mcfg_allocation_t* out) {
for (auto& entry : mcfg_allocations) {
if (entry->segment_group == segment_group) {
*out = *entry;
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
static zx_protocol_device_t pciroot_device_ops = {
.version = DEVICE_OPS_VERSION,
.get_protocol = nullptr,
.open = nullptr,
.open_at = nullptr,
.close = nullptr,
.unbind = nullptr,
.release = nullptr,
.read = nullptr,
.write = nullptr,
.get_size = nullptr,
.ioctl = nullptr,
.suspend = nullptr,
.resume = nullptr,
.rxrpc = nullptr,
.message = nullptr,
};
zx_status_t pci_init_bookkeeping(void) {
zx_status_t status;
auto region_pool = RegionAllocator::RegionPool::Create(PAGE_SIZE);
if (region_pool == nullptr) {
return ZX_ERR_NO_MEMORY;
}
status = kMmio32Alloc.SetRegionPool(region_pool);
if (status != ZX_OK) {
return status;
}
status = kMmio64Alloc.SetRegionPool(region_pool);
if (status != ZX_OK) {
return status;
}
status = kIoAlloc.SetRegionPool(region_pool);
if (status != ZX_OK) {
return status;
}
// MCFG table will only exist with PCIe so 'not found' is not a failure case.
status = pci_read_mcfg_table();
if (status != ZX_OK && status != ZX_ERR_NOT_FOUND) {
zxlogf(ERROR, "%s error attempting to read mcfg table %d\n", kLogTag, status);
return status;
}
status = pci_report_current_resources_ex(get_root_resource());
if (status != ZX_OK) {
zxlogf(ERROR, "%s error attempting to populate PCI allocators %d\n", kLogTag, status);
return status;
}
return ZX_OK;
}
zx_status_t pci_init(zx_device_t* parent,
ACPI_HANDLE object,
ACPI_DEVICE_INFO* info,
publish_acpi_device_ctx_t* ctx) {
static bool pci_bookkeeping_initialized = false;
zx_status_t status = ZX_OK;
// If it's the first time we've found a root we need to set up our address
// space allocators and walk the various ACPI tables.
if (!pci_bookkeeping_initialized) {
status = pci_init_bookkeeping();
if (status != ZX_OK) {
return status;
}
pci_bookkeeping_initialized = true;
}
// 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.
fbl::AllocChecker ac;
auto dev_ctx = fbl::unique_ptr<pciroot_ctx_t>(new (&ac) pciroot_ctx_t());
if (!ac.check()) {
zxlogf(ERROR, "failed to allocate pciroot ctx: %d!\n", status);
return ZX_ERR_NO_MEMORY;
}
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 = acpi_bbn_call(object, &dev_ctx->info.start_bus_num);
if (status != ZX_OK && status != ZX_ERR_NOT_FOUND) {
zxlogf(TRACE, "%s Unable to read _BBN for '%s' (%d), assuming base bus of 0\n",
kLogTag, 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 = 255;
}
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(TRACE, "%s Unable to read _SEG for '%s' (%d), assuming segment group 0.\n",
kLogTag, 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;
pci_mcfg_allocation_t mcfg_alloc;
status = pci_get_segment_mcfg_alloc(dev_ctx->info.segment_group, &mcfg_alloc);
if (status == ZX_OK) {
// Do the bus values make sense?
if (found_bbn && mcfg_alloc.start_bus_num != pinfo.start_bus_num) {
zxlogf(ERROR,
"%s: conflicting base bus num for '%s', _BBN reports %u and MCFG reports %u\n",
kLogTag, dev_ctx->name, pinfo.start_bus_num, mcfg_alloc.start_bus_num);
}
// Do the segment values make sense?
if (pinfo.segment_group != 0 && pinfo.segment_group != mcfg_alloc.segment_group) {
zxlogf(ERROR,
"%s: conflicting segment group for '%s', _BBN reports %u and MCFG reports %u\n",
kLogTag, dev_ctx->name, pinfo.segment_group, mcfg_alloc.segment_group);
}
// Since we have an ecam its metadata will replace anything defined in the ACPI tables.
pinfo.segment_group = mcfg_alloc.segment_group;
pinfo.start_bus_num = mcfg_alloc.start_bus_num;
pinfo.end_bus_num = mcfg_alloc.end_bus_num;
// 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.base_address +
(pinfo.start_bus_num * PCIE_ECAM_BYTES_PER_BUS);
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: %d!\n", status);
return status;
}
}
if (zxlog_level_enabled(TRACE)) {
printf("%s %s { acpi_obj(%p), bus range: %u:%u, segment: %u }\n", kLogTag,
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) {
printf("%s ecam base %#" PRIxPTR "\n", kLogTag, mcfg_alloc.base_address);
}
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = dev_ctx->name,
.ctx = dev_ctx.get(),
.ops = &pciroot_device_ops,
.props = nullptr,
.prop_count = 0,
.proto_id = ZX_PROTOCOL_PCIROOT,
.proto_ops = get_pciroot_ops(),
.proxy_args = nullptr,
.flags = 0,
.client_remote = ZX_HANDLE_INVALID,
};
// 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[5];
uint8_t last_pci_bbn = dev_ctx->info.start_bus_num;
memcpy(name, dev_ctx->name, sizeof(name));
status = device_add(parent, &args, &dev_ctx->zxdev);
if (status != ZX_OK) {
zxlogf(ERROR, "%s failed to add pciroot device for '%s': %d\n",
kLogTag, dev_ctx->name, status);
} else {
// devmgr owns the ctx pointer now so release it from the uptr
__UNUSED auto p = dev_ctx.release();
// TODO(cja): these support the legacy-ish ACPI nhlt table handling that will need to be
// updated in the future.
ctx->found_pci = true;
ctx->last_pci = last_pci_bbn;
zxlogf(INFO, "%s published pciroot '%s'\n", kLogTag, name);
}
return status;
}