i386: PCI improve extended config space verification From: rajesh.shah@intel.com Extend the verification for PCI-X/PCI-Express extended config space pointer. This patch checks whether the MCFG address range is listed as a motherboard resource, per the PCI firmware spec. The old check only looked in int 15 e820 memory map, causing several systems to fail the verification and lose extended config space. [AK: Originally from Rajesh but somewhat hacked up by me. It now has a new heuristic to not do this check if Type1 access doesn't work. This will hopefully fix the x86 iMacs. Also assorted cleanups.] Cc: gregkh@suse.d Signed-off-by: Rajesh Shah arch/i386/pci/acpi.c | 101 +++++++++++++++++++++++++++++++++++++++++++++ arch/i386/pci/mmconfig.c | 11 +++- drivers/acpi/motherboard.c | 3 - include/acpi/aclocal.h | 4 + 4 files changed, 113 insertions(+), 6 deletions(-) Index: linux/arch/i386/pci/acpi.c =================================================================== --- linux.orig/arch/i386/pci/acpi.c +++ linux/arch/i386/pci/acpi.c @@ -5,6 +5,107 @@ #include #include "pci.h" +static int __init is_motherboard_resource(acpi_handle handle) +{ + acpi_status status; + struct acpi_device_info *info; + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + int i; + + status = acpi_get_object_info(handle, &buffer); + if (ACPI_FAILURE(status)) + return 0; + info = buffer.pointer; + if ((info->valid & ACPI_VALID_HID) && + (!strcmp(ACPI_MB_HID1, info->hardware_id.value) || + !strcmp(ACPI_MB_HID2, info->hardware_id.value))) { + kfree(buffer.pointer); + return 1; + } + if (info->valid & ACPI_VALID_CID) { + for (i=0; i < info->compatibility_id.count; i++) { + if (!strcmp(ACPI_MB_HID1, + info->compatibility_id.id[i].value) || + !strcmp(ACPI_MB_HID2, + info->compatibility_id.id[i].value)) { + kfree(buffer.pointer); + return 1; + } + } + } + kfree(buffer.pointer); + return 0; +} + +static acpi_status __init check_mcfg_resource(struct acpi_resource *res, + void *data) +{ + struct resource *mcfg_res = data; + struct acpi_resource_address64 address; + acpi_status status; + + if (res->type == ACPI_RESOURCE_TYPE_FIXED_MEMORY32) { + struct acpi_resource_fixed_memory32 *fixmem32; + + fixmem32 = &res->data.fixed_memory32; + if (!fixmem32) + return AE_OK; + if ((mcfg_res->start >= fixmem32->address) && + (mcfg_res->end <= (fixmem32->address + + fixmem32->address_length))) { + mcfg_res->flags = 1; + return AE_CTRL_TERMINATE; + } + } + if ((res->type != ACPI_RESOURCE_TYPE_ADDRESS32) && + (res->type != ACPI_RESOURCE_TYPE_ADDRESS64)) + return AE_OK; + + status = acpi_resource_to_address64(res, &address); + if (ACPI_FAILURE(status) || (address.address_length <= 0) || + (address.resource_type != ACPI_MEMORY_RANGE)) + return AE_OK; + + if ((mcfg_res->start >= address.minimum) && + (mcfg_res->end <= + (address.minimum +address.address_length))) { + mcfg_res->flags = 1; + return AE_CTRL_TERMINATE; + } + return AE_OK; +} + +static acpi_status __init find_mboard_resource(acpi_handle handle, u32 lvl, + void *context, void **rv) +{ + struct resource *mcfg_res = context; + acpi_status status = AE_OK; + + /* Stop namespace scanning if we've already verified MMCONFIG */ + if (mcfg_res->flags) + return AE_CTRL_TERMINATE; + + if (is_motherboard_resource(handle)) { + status = acpi_walk_resources(handle, METHOD_NAME__CRS, + check_mcfg_resource, context); + } + return status; +} + +int __init is_acpi_reserved(unsigned long start, unsigned long end) +{ + struct resource mcfg_res; + + mcfg_res.start = start; + mcfg_res.end = end; + mcfg_res.flags = 0; + + acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, find_mboard_resource, + (void *)&mcfg_res, NULL); + return mcfg_res.flags; +} + struct pci_bus * __devinit pci_acpi_scan_root(struct acpi_device *device, int domain, int busnum) { struct pci_bus *bus; Index: linux/arch/i386/pci/mmconfig.c =================================================================== --- linux.orig/arch/i386/pci/mmconfig.c +++ linux/arch/i386/pci/mmconfig.c @@ -221,6 +221,17 @@ void __init pci_mmcfg_init(int type) (pci_mmcfg_config[0].base_address == 0)) return; + /* When Type1 access is not available don't check because + we really need MCFG then and it's hopefully ok*/ + if (type == 1 && !is_acpi_reserved(pci_mmcfg_config[0].base_address, + pci_mmcfg_config[0].base_address + + MMCONFIG_APER_MIN)) { + printk(KERN_ERR + "PCI: BIOS Bug: MCFG area at %x not reserved in ACPI\n", + pci_mmcfg_config[0].base_address); + return; + } + printk(KERN_INFO "PCI: Using MMCONFIG\n"); raw_pci_ops = &pci_mmcfg; pci_probe = (pci_probe & ~PCI_PROBE_MASK) | PCI_PROBE_MMCONF; Index: linux/drivers/acpi/motherboard.c =================================================================== --- linux.orig/drivers/acpi/motherboard.c +++ linux/drivers/acpi/motherboard.c @@ -32,9 +32,6 @@ #define _COMPONENT ACPI_SYSTEM_COMPONENT ACPI_MODULE_NAME("acpi_motherboard") -/* Dell use PNP0C01 instead of PNP0C02 */ -#define ACPI_MB_HID1 "PNP0C01" -#define ACPI_MB_HID2 "PNP0C02" /** * Doesn't care about legacy IO ports, only IO ports beyond 0x1000 are reserved * Doesn't care about the failure of 'request_region', since other may reserve Index: linux/include/acpi/aclocal.h =================================================================== --- linux.orig/include/acpi/aclocal.h +++ linux/include/acpi/aclocal.h @@ -696,6 +696,10 @@ struct acpi_parse_state { #define PCI_ROOT_HID_STRING "PNP0A03" #define PCI_EXPRESS_ROOT_HID_STRING "PNP0A08" +/* Dell use PNP0C01 instead of PNP0C02 */ +#define ACPI_MB_HID1 "PNP0C01" +#define ACPI_MB_HID2 "PNP0C02" + struct acpi_bit_register_info { u8 parent_register; Index: linux/arch/i386/pci/pci.h =================================================================== --- linux.orig/arch/i386/pci/pci.h +++ linux/arch/i386/pci/pci.h @@ -88,5 +88,5 @@ extern void pci_pcbios_init(void); extern void pci_mmcfg_init(int type); extern void pcibios_sort(void); -extern int is_acpi_reserved(unsigned long start, unsigned long end) +extern int is_acpi_reserved(unsigned long start, unsigned long end);