blob: 171b24bcb63777ae9b71cccbfbf0a9cddbcb8944 [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 "pci.h"
#include <assert.h>
#include <fdio/debug.h>
#include <stdio.h>
#include <acpica/acpi.h>
#include "resources.h"
#define MXDEBUG 0
#define PCIE_MAX_LEGACY_IRQ_PINS 4
#define PCIE_MAX_DEVICES_PER_BUS 32
#define PCIE_MAX_FUNCTIONS_PER_DEVICE 8
#define PCI_CONFIG_SIZE 256
#define PCIE_EXTENDED_CONFIG_SIZE 4096
#define PCI_HID ((char*)"PNP0A03")
#define PCIE_HID ((char*)"PNP0A08")
#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 = (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 >= PCIE_MAX_LEGACY_IRQ_PINS) {
return AE_ERROR;
}
uint8_t dev_id = (entry->Address >> 16) & (PCIE_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 < PCIE_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\n");
return ZX_ERR_NOT_FOUND;
}
ACPI_TABLE_MCFG* mcfg = (ACPI_TABLE_MCFG*)raw_table;
ACPI_MCFG_ALLOCATION* table_start = ((void*)mcfg) + sizeof(*mcfg);
ACPI_MCFG_ALLOCATION* table_end = ((void*)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\n");
return ZX_ERR_INTERNAL;
}
int num_entries = table_end - table_start;
if (num_entries == 0) {
xprintf("MCFG has no entries\n");
return ZX_ERR_NOT_FOUND;
}
if (num_entries > 1) {
xprintf("MCFG has more than one entry, just taking the first\n");
}
size_t size_per_bus = PCIE_EXTENDED_CONFIG_SIZE *
PCIE_MAX_DEVICES_PER_BUS * PCIE_MAX_FUNCTIONS_PER_DEVICE;
int num_buses = table_start->EndBusNumber - table_start->StartBusNumber + 1;
if (table_start->PciSegment != 0) {
xprintf("Non-zero segment found\n");
return ZX_ERR_NOT_SUPPORTED;
}
arg->addr_windows[0].is_mmio = true;
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 = 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) & (PCIE_MAX_DEVICES_PER_BUS - 1);
uint8_t port_func_id = data & (PCIE_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(uint32_t);
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) {
ACPI_DEVICE_INFO* info;
size_t size_per_bus = PCI_CONFIG_SIZE *
PCIE_MAX_DEVICES_PER_BUS * PCIE_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 (AcpiGetObjectInfo(object, &info) == AE_OK) {
arg->addr_windows[0].is_mmio = false;
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 = 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 = 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) {
struct report_current_resources_ctx* 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\n");
return AE_OK;
}
xprintf("ACPI range modification: %sing %s %016lx %016lx\n",
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\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(
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;
}
struct report_current_resources_ctx* 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, 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, 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, report_current_resources_device_cb, &ctx, NULL);
if (status != AE_OK) {
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}