PNP: work around Dell 1546 BIOS MMCONFIG bug that breaks USB From: Bjorn Helgaas The Dell 1546 BIOS (A04 11/08/2010) programs PCI MMCONFIG space for [mem 0xd0000000-0xdfffffff], but in the E820 table and the PNP0C02 device, it only reports the smaller [mem 0xd0000000-0xd3ffffff] region as being consumed: BIOS-e820: 00000000cfec5400 - 00000000d4000000 (reserved) Fam 10h mmconf [d0000000, dfffffff] PCI: MMCONFIG for domain 0000 [bus 00-3f] at [mem 0xd0000000-0xd3ffffff] (base 0xd0000000) pnp 00:0c: [mem 0xd0000000-0xd3ffffff] The BIOS also leaves USB devices outside the PCI host bridge windows reported by _CRS: pci_root PNP0A03:00: host bridge window [mem 0xfee10000-0xff9fffff] pci_root PNP0A03:00: host bridge window [mem 0xffc00000-0xffdffbff] pci 0000:00:12.0: reg 10: [mem 0xffb00000-0xffb00fff] pci 0000:00:12.0: no compatible bridge window for [mem 0xffb00000-0xffb00fff] Starting with 2.6.34, Linux pays attention to _CRS windows, so it moves the USB devices into the windows. The [mem 0xd4000000-0xdfffffff] area appears available, so we move them there, but that conflicts with the MMCONFIG area, so the devices don't work. This patch adds a quirk that expands the PNP0C02 region to cover the entire area claimed by the chipset. References: https://bugzilla.kernel.org/show_bug.cgi?id=31602 Reported-by: Cc: stable@kernel.org # 2.6.34+ Signed-off-by: Bjorn Helgaas --- arch/x86/include/asm/amd_nb.h | 1 + arch/x86/kernel/amd_nb.c | 32 ++++++++++++++++++++++++++++++++ arch/x86/pci/amd_bus.c | 32 +++----------------------------- drivers/pnp/quirks.c | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 29 deletions(-) diff --git a/arch/x86/include/asm/amd_nb.h b/arch/x86/include/asm/amd_nb.h index 67f87f2..fcad8d4 100644 --- a/arch/x86/include/asm/amd_nb.h +++ b/arch/x86/include/asm/amd_nb.h @@ -13,6 +13,7 @@ extern const struct pci_device_id amd_nb_misc_ids[]; extern const struct amd_nb_bus_dev_range amd_nb_bus_dev_ranges[]; extern bool early_is_amd_nb(u32 value); +extern void amd_mmconfig_range(u64 *start, u64 *end); extern int amd_cache_northbridges(void); extern void amd_flush_garts(void); extern int amd_numa_init(void); diff --git a/arch/x86/kernel/amd_nb.c b/arch/x86/kernel/amd_nb.c index 4c39baa..824dd0c 100644 --- a/arch/x86/kernel/amd_nb.c +++ b/arch/x86/kernel/amd_nb.c @@ -119,6 +119,38 @@ bool __init early_is_amd_nb(u32 device) return false; } +void amd_mmconfig_range(u64 *start, u64 *end) +{ + u32 address; + u64 base, msr; + unsigned segn_busn_bits; + + *start = 0; + *end = 0; + + if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD) + return; + + /* assume all cpus from fam10h have mmconf */ + if (boot_cpu_data.x86 < 0x10) + return; + + address = MSR_FAM10H_MMIO_CONF_BASE; + rdmsrl(address, msr); + + /* mmconfig is not enable */ + if (!(msr & FAM10H_MMIO_CONF_ENABLE)) + return; + + base = msr & (FAM10H_MMIO_CONF_BASE_MASK<> FAM10H_MMIO_CONF_BUSRANGE_SHIFT) & + FAM10H_MMIO_CONF_BUSRANGE_MASK; + + *start = base; + *end = base + (1ULL<<(segn_busn_bits + 20)) - 1; +} + int amd_get_subcaches(int cpu) { struct pci_dev *link = node_to_amd_nb(amd_get_nb_id(cpu))->link; diff --git a/arch/x86/pci/amd_bus.c b/arch/x86/pci/amd_bus.c index 026e493..26436c1 100644 --- a/arch/x86/pci/amd_bus.c +++ b/arch/x86/pci/amd_bus.c @@ -30,34 +30,6 @@ static struct pci_hostbridge_probe pci_probes[] __initdata = { { 0, 0x18, PCI_VENDOR_ID_AMD, 0x1300 }, }; -static u64 __initdata fam10h_mmconf_start; -static u64 __initdata fam10h_mmconf_end; -static void __init get_pci_mmcfg_amd_fam10h_range(void) -{ - u32 address; - u64 base, msr; - unsigned segn_busn_bits; - - /* assume all cpus from fam10h have mmconf */ - if (boot_cpu_data.x86 < 0x10) - return; - - address = MSR_FAM10H_MMIO_CONF_BASE; - rdmsrl(address, msr); - - /* mmconfig is not enable */ - if (!(msr & FAM10H_MMIO_CONF_ENABLE)) - return; - - base = msr & (FAM10H_MMIO_CONF_BASE_MASK<> FAM10H_MMIO_CONF_BUSRANGE_SHIFT) & - FAM10H_MMIO_CONF_BUSRANGE_MASK; - - fam10h_mmconf_start = base; - fam10h_mmconf_end = base + (1ULL<<(segn_busn_bits + 20)) - 1; -} - #define RANGE_NUM 16 /** @@ -85,6 +57,8 @@ static int __init early_fill_mp_bus_info(void) u64 val; u32 address; bool found; + u64 fam10h_mmconf_start; + u64 fam10h_mmconf_end; if (!early_pci_allowed()) return -1; @@ -211,7 +185,7 @@ static int __init early_fill_mp_bus_info(void) subtract_range(range, RANGE_NUM, 0, end); /* get mmconfig */ - get_pci_mmcfg_amd_fam10h_range(); + amd_mmconfig_range(&fam10h_mmconf_start, &fam10h_mmconf_end); /* need to take out mmconf range */ if (fam10h_mmconf_end) { printk(KERN_DEBUG "Fam 10h mmconf [%llx, %llx]\n", fam10h_mmconf_start, fam10h_mmconf_end); diff --git a/drivers/pnp/quirks.c b/drivers/pnp/quirks.c index dfbd5a6..b31d6f3 100644 --- a/drivers/pnp/quirks.c +++ b/drivers/pnp/quirks.c @@ -295,6 +295,35 @@ static void quirk_system_pci_resources(struct pnp_dev *dev) } } +#ifdef CONFIG_AMD_NB + +#include + +static void quirk_amd_mmconfig_area(struct pnp_dev *dev) +{ + u64 mmconfig_start, mmconfig_end; + struct pnp_resource *pnp_res; + struct resource *res; + + amd_mmconfig_range(&mmconfig_start, &mmconfig_end); + if (!mmconfig_end) + return; + + list_for_each_entry(pnp_res, &dev->resources, list) { + res = &pnp_res->res; + if (res->end < mmconfig_start || res->start > mmconfig_end) + continue; + + if (mmconfig_start < res->start) + res->start = mmconfig_start; + if (mmconfig_end > res->end) + res->end = mmconfig_end; + dev_warn(&dev->dev, FW_BUG + "enlarged for AMD MMCONFIG area, now %pR\n", res); + } +} +#endif + /* * PnP Quirks * Cards or devices that need some tweaking due to incomplete resource info @@ -322,6 +351,9 @@ static struct pnp_fixup pnp_fixups[] = { /* PnP resources that might overlap PCI BARs */ {"PNP0c01", quirk_system_pci_resources}, {"PNP0c02", quirk_system_pci_resources}, +#ifdef CONFIG_AMD_NB + {"PNP0c01", quirk_amd_mmconfig_area}, +#endif {""} };