blob: 69b4daccdc695957e4883d364156f317bac4a68f [file] [log] [blame]
// Copyright 2016 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 <assert.h>
#include <lib/pci/pciroot.h>
#include <stdio.h>
#include <array>
#include <acpica/acpi.h>
#include <ddk/debug.h>
#include "acpi-private.h"
#include "methods.h"
#include "pci.h"
#include "resources.h"
#define PCI_HID ((char*)"PNP0A03")
#define PCIE_HID ((char*)"PNP0A08")
#define xprintf(fmt...) zxlogf(TRACE, fmt)
#define PANIC_UNIMPLEMENTED __builtin_trap()
/* Helper routine for translating IRQ routing tables into usable form
*
* @param port_dev_id The device ID on the root bus of this root port or
* UINT8_MAX if this call is for the root bus, not a root port
* @param port_func_id The function ID on the root bus of this root port or
* UINT8_MAX if this call is for the root bus, not a root port
*/
static ACPI_STATUS handle_prt(ACPI_HANDLE object, zx_pci_init_arg_t* arg, uint8_t port_dev_id,
uint8_t port_func_id) {
assert((port_dev_id == UINT8_MAX && port_func_id == UINT8_MAX) ||
(port_dev_id != UINT8_MAX && port_func_id != UINT8_MAX));
ACPI_BUFFER buffer = {
// Request that the ACPI subsystem allocate the buffer
.Length = ACPI_ALLOCATE_BUFFER,
.Pointer = NULL,
};
ACPI_BUFFER crs_buffer = {
.Length = ACPI_ALLOCATE_BUFFER,
.Pointer = NULL,
};
ACPI_STATUS status = AcpiGetIrqRoutingTable(object, &buffer);
// IRQ routing tables are *required* to exist on the root hub
if (status != AE_OK) {
goto cleanup;
}
uintptr_t entry_addr;
entry_addr = reinterpret_cast<uintptr_t>(buffer.Pointer);
ACPI_PCI_ROUTING_TABLE* entry;
for (entry = (ACPI_PCI_ROUTING_TABLE*)entry_addr; entry->Length != 0;
entry_addr += entry->Length, entry = (ACPI_PCI_ROUTING_TABLE*)entry_addr) {
if (entry_addr > (uintptr_t)buffer.Pointer + buffer.Length) {
return AE_ERROR;
}
if (entry->Pin >= PCI_MAX_LEGACY_IRQ_PINS) {
return AE_ERROR;
}
uint8_t dev_id = (entry->Address >> 16) & (PCI_MAX_DEVICES_PER_BUS - 1);
// Either we're handling the root complex (port_dev_id == UINT8_MAX), or
// we're handling a root port, and if it's a root port, dev_id should
// be 0.
if (port_dev_id != UINT8_MAX && dev_id != 0) {
// this is a weird entry, skip it
continue;
}
uint32_t global_irq = ZX_PCI_NO_IRQ_MAPPING;
bool level_triggered = true;
bool active_high = false;
if (entry->Source[0]) {
// If the Source is not just a NULL byte, then it refers to a
// PCI Interrupt Link Device
ACPI_HANDLE ild;
status = AcpiGetHandle(object, entry->Source, &ild);
if (status != AE_OK) {
goto cleanup;
}
status = AcpiGetCurrentResources(ild, &crs_buffer);
if (status != AE_OK) {
goto cleanup;
}
uintptr_t crs_entry_addr = (uintptr_t)crs_buffer.Pointer;
ACPI_RESOURCE* res = (ACPI_RESOURCE*)crs_entry_addr;
while (res->Type != ACPI_RESOURCE_TYPE_END_TAG) {
if (res->Type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) {
ACPI_RESOURCE_EXTENDED_IRQ* irq = &res->Data.ExtendedIrq;
if (global_irq != ZX_PCI_NO_IRQ_MAPPING) {
// TODO: Handle finding two allocated IRQs. Shouldn't
// happen?
PANIC_UNIMPLEMENTED;
}
if (irq->InterruptCount != 1) {
// TODO: Handle finding two allocated IRQs. Shouldn't
// happen?
PANIC_UNIMPLEMENTED;
}
if (irq->Interrupts[0] != 0) {
active_high = (irq->Polarity == ACPI_ACTIVE_HIGH);
level_triggered = (irq->Triggering == ACPI_LEVEL_SENSITIVE);
global_irq = irq->Interrupts[0];
}
} else {
// TODO: Handle non extended IRQs
PANIC_UNIMPLEMENTED;
}
crs_entry_addr += res->Length;
res = (ACPI_RESOURCE*)crs_entry_addr;
}
if (global_irq == ZX_PCI_NO_IRQ_MAPPING) {
// TODO: Invoke PRS to find what is allocatable and allocate it with SRS
PANIC_UNIMPLEMENTED;
}
AcpiOsFree(crs_buffer.Pointer);
crs_buffer.Length = ACPI_ALLOCATE_BUFFER;
crs_buffer.Pointer = NULL;
} else {
// Otherwise, SourceIndex refers to a global IRQ number that the pin
// is connected to
global_irq = entry->SourceIndex;
}
// Check if we've seen this IRQ already, and if so, confirm the
// IRQ signaling is the same.
bool found_irq = false;
for (unsigned int i = 0; i < arg->num_irqs; ++i) {
if (global_irq != arg->irqs[i].global_irq) {
continue;
}
if (active_high != arg->irqs[i].active_high ||
level_triggered != arg->irqs[i].level_triggered) {
// TODO: Handle mismatch here
PANIC_UNIMPLEMENTED;
}
found_irq = true;
break;
}
if (!found_irq) {
assert(arg->num_irqs < countof(arg->irqs));
arg->irqs[arg->num_irqs].global_irq = global_irq;
arg->irqs[arg->num_irqs].active_high = active_high;
arg->irqs[arg->num_irqs].level_triggered = level_triggered;
arg->num_irqs++;
}
if (port_dev_id == UINT8_MAX) {
for (unsigned int i = 0; i < PCI_MAX_FUNCTIONS_PER_DEVICE; ++i) {
arg->dev_pin_to_global_irq[dev_id][i][entry->Pin] = global_irq;
}
} else {
arg->dev_pin_to_global_irq[port_dev_id][port_func_id][entry->Pin] = global_irq;
}
}
cleanup:
if (crs_buffer.Pointer) {
AcpiOsFree(crs_buffer.Pointer);
}
if (buffer.Pointer) {
AcpiOsFree(buffer.Pointer);
}
return status;
}
/* @brief Find the PCI config (returns the first one found)
*
* @param arg The structure to populate
*
* @return ZX_OK on success.
*/
static zx_status_t find_pcie_config(zx_pci_init_arg_t* arg) {
ACPI_TABLE_HEADER* raw_table = NULL;
ACPI_STATUS status = AcpiGetTable((char*)ACPI_SIG_MCFG, 1, &raw_table);
if (status != AE_OK) {
xprintf("could not find MCFG");
return ZX_ERR_NOT_FOUND;
}
ACPI_TABLE_MCFG* mcfg = (ACPI_TABLE_MCFG*)raw_table;
ACPI_MCFG_ALLOCATION* table_start =
reinterpret_cast<ACPI_MCFG_ALLOCATION*>(reinterpret_cast<uintptr_t>(mcfg) + sizeof(*mcfg));
ACPI_MCFG_ALLOCATION* table_end = reinterpret_cast<ACPI_MCFG_ALLOCATION*>(
reinterpret_cast<uintptr_t>(mcfg) + mcfg->Header.Length);
uintptr_t table_bytes = (uintptr_t)table_end - (uintptr_t)table_start;
if (table_bytes % sizeof(*table_start) != 0) {
xprintf("MCFG has unexpected size");
return ZX_ERR_INTERNAL;
}
size_t num_entries = table_end - table_start;
if (num_entries == 0) {
xprintf("MCFG has no entries");
return ZX_ERR_NOT_FOUND;
}
if (num_entries > 1) {
xprintf("MCFG has more than one entry, just taking the first");
}
size_t size_per_bus =
PCIE_EXTENDED_CONFIG_SIZE * PCI_MAX_DEVICES_PER_BUS * PCI_MAX_FUNCTIONS_PER_DEVICE;
int num_buses = table_start->EndBusNumber - table_start->StartBusNumber + 1;
if (table_start->PciSegment != 0) {
xprintf("Non-zero segment found");
return ZX_ERR_NOT_SUPPORTED;
}
arg->addr_windows[0].cfg_space_type = PCI_CFG_SPACE_TYPE_MMIO;
arg->addr_windows[0].has_ecam = true;
arg->addr_windows[0].bus_start = table_start->StartBusNumber;
arg->addr_windows[0].bus_end = table_start->EndBusNumber;
// We need to adjust the physical address we received to align to the proper
// bus number.
//
// Citation from PCI Firmware Spec 3.0:
// For PCI-X and PCI Express platforms utilizing the enhanced
// configuration access method, the base address of the memory mapped
// configuration space always corresponds to bus number 0 (regardless
// of the start bus number decoded by the host bridge).
arg->addr_windows[0].base = table_start->Address + size_per_bus * arg->addr_windows[0].bus_start;
// The size of this mapping is defined in the PCI Firmware v3 spec to be
// big enough for all of the buses in this config.
arg->addr_windows[0].size = size_per_bus * num_buses;
arg->addr_window_count = 1;
return ZX_OK;
}
/* @brief Device enumerator for platform_configure_pcie_legacy_irqs */
static ACPI_STATUS get_pcie_devices_irq(ACPI_HANDLE object, UINT32 nesting_level, void* context,
void** ret) {
zx_pci_init_arg_t* arg = static_cast<zx_pci_init_arg_t*>(context);
ACPI_STATUS status = handle_prt(object, arg, UINT8_MAX, UINT8_MAX);
if (status != AE_OK) {
return status;
}
// Enumerate root ports
ACPI_HANDLE child = NULL;
while (1) {
status = AcpiGetNextObject(ACPI_TYPE_DEVICE, object, child, &child);
if (status == AE_NOT_FOUND) {
break;
} else if (status != AE_OK) {
return status;
}
ACPI_OBJECT object = {0};
ACPI_BUFFER buffer = {
.Length = sizeof(object),
.Pointer = &object,
};
status = AcpiEvaluateObject(child, (char*)"_ADR", NULL, &buffer);
if (status != AE_OK || buffer.Length < sizeof(object) || object.Type != ACPI_TYPE_INTEGER) {
continue;
}
UINT64 data = object.Integer.Value;
uint8_t port_dev_id = (data >> 16) & (PCI_MAX_DEVICES_PER_BUS - 1);
uint8_t port_func_id = data & (PCI_MAX_FUNCTIONS_PER_DEVICE - 1);
// Ignore the return value of this, since if child is not a
// root port, it will fail and we don't care.
handle_prt(child, arg, port_dev_id, port_func_id);
}
return AE_OK;
}
/* @brief Find the legacy IRQ swizzling for the PCIe root bus
*
* @param arg The structure to populate
*
* @return ZX_OK on success
*/
static zx_status_t find_pci_legacy_irq_mapping(zx_pci_init_arg_t* arg) {
unsigned int map_len =
sizeof(arg->dev_pin_to_global_irq) / (sizeof(**(arg->dev_pin_to_global_irq)));
for (unsigned int i = 0; i < map_len; ++i) {
uint32_t* flat_map = (uint32_t*)&arg->dev_pin_to_global_irq;
flat_map[i] = ZX_PCI_NO_IRQ_MAPPING;
}
arg->num_irqs = 0;
ACPI_STATUS status = AcpiGetDevices((arg->addr_windows[0].has_ecam) ? PCIE_HID : PCI_HID,
get_pcie_devices_irq, arg, NULL);
if (status != AE_OK) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
static ACPI_STATUS find_pci_configs_cb(ACPI_HANDLE object, uint32_t nesting_level, void* _ctx,
void** ret) {
size_t size_per_bus =
PCI_BASE_CONFIG_SIZE * PCI_MAX_DEVICES_PER_BUS * PCI_MAX_FUNCTIONS_PER_DEVICE;
zx_pci_init_arg_t* arg = (zx_pci_init_arg_t*)_ctx;
// TODO(cja): This is essentially a hacky solution to deal with
// legacy PCI on Virtualbox and GCE. When the ACPI bus driver
// is enabled we'll be using proper binding and not need this
// anymore.
if (auto res = acpi::GetObjectInfo(object); res.is_ok()) {
arg->addr_windows[0].cfg_space_type = PCI_CFG_SPACE_TYPE_PIO;
arg->addr_windows[0].has_ecam = false;
arg->addr_windows[0].base = 0;
arg->addr_windows[0].bus_start = 0;
arg->addr_windows[0].bus_end = 0;
arg->addr_windows[0].size = size_per_bus;
arg->addr_window_count = 1;
return AE_OK;
}
return AE_ERROR;
}
/* @brief Find the PCI config (returns the first one found)
*
* @param arg The structure to populate
*
* @return ZX_OK on success.
*/
static zx_status_t find_pci_config(zx_pci_init_arg_t* arg) {
// TODO: Although this will find every PCI legacy root, we're presently
// hardcoding to just use the first at bus 0 dev 0 func 0 segment 0.
return AcpiGetDevices(PCI_HID, find_pci_configs_cb, arg, NULL);
}
/* @brief Compute PCIe initialization information
*
* The computed initialization information can be released with free()
*
* @param arg Pointer to store the initialization information into
*
* @return ZX_OK on success
*/
zx_status_t get_pci_init_arg(zx_pci_init_arg_t** arg, uint32_t* size) {
zx_pci_init_arg_t* res = NULL;
// TODO(teisenbe): We assume only one ECAM window right now...
size_t obj_size = sizeof(*res) + sizeof(res->addr_windows[0]) * 1;
res = static_cast<zx_pci_init_arg_t*>(calloc(1, obj_size));
if (!res) {
return ZX_ERR_NO_MEMORY;
}
// First look for a PCIe root complex. If none is found, try legacy PCI.
// This presently only cares about the first root found, multiple roots
// will be handled when the PCI bus driver binds to roots via ACPI.
zx_status_t status = find_pcie_config(res);
if (status != ZX_OK) {
status = find_pci_config(res);
if (status != ZX_OK) {
goto fail;
}
}
status = find_pci_legacy_irq_mapping(res);
if (status != ZX_OK) {
goto fail;
}
*arg = res;
*size =
static_cast<uint32_t>(sizeof(*res) + sizeof(res->addr_windows[0]) * res->addr_window_count);
return ZX_OK;
fail:
free(res);
return status;
}
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(ACPI_RESOURCE* res, void* _ctx) {
auto* ctx = static_cast<report_current_resources_ctx*>(_ctx);
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;
zx_status_t 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;
zx_status_t 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;
zx_status_t 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.
xprintf("Skipping adding MMIO range, due to being below 1MB");
return AE_OK;
}
xprintf("ACPI range modification: %sing %s %016lx %016lx", add_range ? "add" : "subtract",
is_mmio ? "MMIO" : "PIO", base, len);
zx_status_t status = zx_pci_add_subtract_io_range(ctx->pci_handle, is_mmio, base, len, add_range);
if (status != ZX_OK) {
if (add_range) {
xprintf("Failed to add range: %d", status);
} else {
// If we are subtracting a range and fail, abort. This is bad.
return AE_ERROR;
}
}
return AE_OK;
}
static ACPI_STATUS pci_report_current_resources_device_cb(ACPI_HANDLE object,
uint32_t nesting_level, void* _ctx,
void** ret) {
acpi::UniquePtr<ACPI_DEVICE_INFO> info;
if (auto res = acpi::GetObjectInfo(object); res.is_error()) {
return res.error_value();
} else {
info = std::move(res.value());
}
auto* ctx = static_cast<report_current_resources_ctx*>(_ctx);
ctx->device_is_root_bridge = (info->Flags & ACPI_PCI_ROOT_BRIDGE) != 0;
ACPI_STATUS status =
AcpiWalkResources(object, (char*)"_CRS", report_current_resources_resource_cb, 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(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, pci_report_current_resources_device_cb, &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, pci_report_current_resources_device_cb, &ctx, NULL);
if (status != AE_OK) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
zx_protocol_device_t acpi_device_proto = [] {
zx_protocol_device_t ops = {};
ops.version = DEVICE_OPS_VERSION;
ops.release = free;
return ops;
}();
// This pci_init initializes the kernel pci driver and is not compiled in at the same time as the
// userspace pci driver under development.
zx_status_t pci_init(zx_device_t* sysdev, zx_device_t* platform_bus, ACPI_HANDLE object,
ACPI_DEVICE_INFO* info) {
// Report current resources to kernel PCI driver
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
zx_status_t status = pci_report_current_resources(get_root_resource());
if (status != ZX_OK) {
zxlogf(ERROR, "acpi: WARNING: ACPI failed to report all current resources!");
}
// Initialize kernel PCI driver
zx_pci_init_arg_t* arg;
uint32_t arg_size;
status = get_pci_init_arg(&arg, &arg_size);
if (status != ZX_OK) {
zxlogf(ERROR, "acpi: erorr %d in get_pci_init_arg", status);
return AE_ERROR;
}
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
status = zx_pci_init(get_root_resource(), arg, arg_size);
if (status != ZX_OK) {
zxlogf(ERROR, "acpi: error %d in zx_pci_init", status);
return AE_ERROR;
}
free(arg);
// Publish PCI root as /dev/sys/ level.
// Only publish one PCI root device for all PCI roots
// TODO: store context for PCI root protocol
std::array<zx_device_prop_t, 4> props;
auto args = get_device_add_args("pci", info, &props);
auto device = std::make_unique<acpi::Device>(sysdev, object, platform_bus);
args.version = DEVICE_ADD_ARGS_VERSION;
args.ctx = device.get();
args.ops = &acpi_device_proto;
// The acpi::Device implements both the Acpi protocol and the Pciroot protocol. We may find a
// better way to do this in the future, but kernel PCI will eventually be removed anyway, at
// which point we'll need to refactor it all anyway.
args.proto_id = ZX_PROTOCOL_PCIROOT;
args.proto_ops = get_pciroot_ops();
if (zx_status_t status = device_add(sysdev, &args, device->mutable_zxdev()); status != ZX_OK) {
zxlogf(ERROR, "acpi: error %d in device_add, parent=%s(%p)", status, device_get_name(sysdev),
sysdev);
return status;
} else {
zxlogf(INFO, "acpi: published device %s(%p), parent=%s(%p), handle=%p", args.name, device.get(),
device_get_name(sysdev), sysdev, device->acpi_handle());
// device_add takes ownership of args.ctx, but only on success.
device.release();
}
return ZX_OK;
}