| // 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 <stdio.h> |
| |
| #include <acpica/acpi.h> |
| #include <ddk/debug.h> |
| |
| #include "methods.h" |
| #include "pciroot.h" |
| #include "resources.h" |
| #include "acpi-private.h" |
| |
| #define PCI_HID ((char*)"PNP0A03") |
| #define PCIE_HID ((char*)"PNP0A08") |
| #define xprintf(fmt...) zxlogf(SPEW, 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 = (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\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 * |
| 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\n"); |
| 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 = 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(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_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 (AcpiGetObjectInfo(object, &info) == AE_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 = 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 pci_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, 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; |
| } |
| |
| // 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* parent, ACPI_HANDLE object, ACPI_DEVICE_INFO* info, publish_acpi_device_ctx_t* ctx) { |
| if (!ctx->found_pci) { |
| // Report current resources to kernel PCI driver |
| 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!\n"); |
| } |
| |
| // 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\n", status); |
| return AE_ERROR; |
| } |
| |
| status = zx_pci_init(get_root_resource(), arg, arg_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "acpi: error %d in zx_pci_init\n", status); |
| return AE_ERROR; |
| } |
| |
| free(arg); |
| |
| // Publish PCI root as top level |
| // Only publish one PCI root device for all PCI roots |
| // TODO: store context for PCI root protocol |
| parent = device_get_parent(parent); |
| zx_device_t* pcidev = publish_device(parent, object, info, "pci", |
| ZX_PROTOCOL_PCIROOT, get_pciroot_ops()); |
| ctx->found_pci = (pcidev != NULL); |
| } |
| // Get the PCI base bus number |
| zx_status_t status = acpi_bbn_call(object, &ctx->last_pci); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "acpi: failed to get PCI base bus number for device '%s' " |
| "(status %u)\n", (const char*)&info->Name, status); |
| } |
| |
| zxlogf(TRACE, "acpi: found pci root #%u\n", ctx->last_pci); |
| return ZX_OK; |
| } |