PCI: override BIOS/firmware resource allocation through command line parameters git commit 977d17bb1749517b353874ccdc9b85abc7a58c2a released and reallocated all resources when allocation of any resource of any type, ioport and memory, failed. This caused failure to reallocate fragile io port resources, as reported in https://bugzilla.kernel.org/show_bug.cgi?id=15960 The problem was solved by reverting the commit, through git commit 769d9968e42c995eaaf61ac5583d998f32e0769a. However reverting the original patch fails MMIO resource allocation for SRIOV PCI-Bars on some platforms. Especially on platforms where the BIOS is unaware of SRIOV resource BARs. The following code, an idea proposed by Linus, allows the user to tailor the resource allocation for devices through kernel command line parameter. Details of the idea can be found at http://marc.info/?l=linux-kernel&m=127846075028242&w=2 Signed-off-by: Ram Pai <linuxram@xxxxxxxxxx> diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index f084af0..eec068f 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1961,6 +1961,21 @@ and is between 256 and 4096 characters. It is defined in the file PAGE_SIZE is used as alignment. PCI-PCI bridge can be specified, if resource windows need to be expanded. + override=[off, conflict, always, <device>] + off : Do not override BIOS/firmware allocations. This is the + default + conflict : override BIOS/firmware allocations if conflicting + or not allocated. + always : override all BIOS/firmware allocations + <device>: Format [<domain>:]<bus>:<slot>.<func>[; ...] + override BIOS/firmware allocations of specified + devices + + clear=<device> + <device>: Format [<domain>:]<bus>:<slot>.<func>[; ...] + release BIOS/firmware allocations of specified + devices. By default no allocations are cleared. + ecrc= Enable/disable PCIe ECRC (transaction layer end-to-end CRC checking). bios: Use BIOS/firmware settings. This is the diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 7fa3cbd..5676416 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2984,6 +2984,85 @@ void __weak pci_fixup_cardbus(struct pci_bus *bus) } EXPORT_SYMBOL(pci_fixup_cardbus); +#define RESOURCE_RELEASE_PARAM_SIZE COMMAND_LINE_SIZE +static char __initdata resource_release_param[RESOURCE_RELEASE_PARAM_SIZE]; +int pci_override=PCI_OVERRIDE_OFF; + +/* + * Return success if 'dev' is listed as one of the devices + * through the kernel command line + * pci=[override|clear]=device[;device]* + */ +int __init is_pci_reset_device(struct pci_dev *dev) +{ + int seg, bus, slot, func, count; + char *p; + + p = resource_release_param; + while (*p) { + count = 0; + if (sscanf(p, "%x:%x:%x.%x%n", + &seg, &bus, &slot, &func, &count) != 4) { + seg = 0; + if (sscanf(p, "%x:%x.%x%n", + &bus, &slot, &func, &count) != 3) { + /* Invalid format */ + printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: %s\n", + p); + return 0; + } + } + p += count; + if (seg == pci_domain_nr(dev->bus) && + bus == dev->bus->number && + slot == PCI_SLOT(dev->devfn) && + func == PCI_FUNC(dev->devfn)) { + return 1; + } + if (*p != ';') { + /* End of param or invalid format */ + return 0; + } + p++; + } + return 0; +} + + +static void __init pci_override_setup(const char *str, int override) +{ + if (override && !strncmp(str, "off", 3)) { + + pci_override = PCI_OVERRIDE_OFF; + printk(KERN_INFO "pci: do not override " + "BIOS/uEFI allocations\n"); + + } else if (override && !strncmp(str, "conflict", 8)) { + + pci_override = PCI_OVERRIDE_CONFLICT; + printk(KERN_INFO "pci: reallocate BIOS/uEFI " + "allocated resources on conflicts\n"); + + } else if (override && !strncmp(str, "always", 6)) { + + pci_override = PCI_OVERRIDE_ALWAYS; + printk(KERN_INFO "pci: override all BIOS/uEFI allocations\n"); + + } else { + int count=strlen(str); + + pci_override = override ? PCI_OVERRIDE_DEVICE : + PCI_CLEAR_DEVICE; + if (count > RESOURCE_RELEASE_PARAM_SIZE - 1) + count = RESOURCE_RELEASE_PARAM_SIZE - 1; + strncpy(resource_release_param, str, count); + resource_release_param[count] = '\0'; + printk(KERN_INFO "pci: %s BIOS/uEFI allocations for %s\n", + override ? "override" : "clear", resource_release_param); + } + return; +} + static int __init pci_setup(char *str) { while (str) { @@ -3010,6 +3089,10 @@ static int __init pci_setup(char *str) pci_hotplug_io_size = memparse(str + 9, &str); } else if (!strncmp(str, "hpmemsize=", 10)) { pci_hotplug_mem_size = memparse(str + 10, &str); + } else if (!strncmp(str, "override=", 9)) { + pci_override_setup(str + 9, 1); + } else if (!strncmp(str, "clear=", 6)) { + pci_override_setup(str + 6, 0); } else { printk(KERN_ERR "PCI: Unknown option `%s'\n", str); diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 66cb8f4..e215ee9 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -838,21 +838,195 @@ static void pci_bus_dump_resources(struct pci_bus *bus) } } +static void __init +__pci_dev_reset_resource(struct pci_dev *dev, + struct resource_list_x *fail_head) +{ + int idx, start, end; + struct resource *r; + u16 class = dev->class >> 8; + + if (class == PCI_CLASS_BRIDGE_PCI) { + start=PCI_BRIDGE_RESOURCES; + end=PCI_BRIDGE_RESOURCE_END+1; + } else { + start=0; + end=PCI_NUM_RESOURCES; + } + + for (idx = start; idx < end; idx++) { + r = &dev->resource[idx]; + + if (r->flags & IORESOURCE_PCI_FIXED) + continue; + + if ( idx == PCI_ROM_RESOURCE || + r->flags & IORESOURCE_ROM_ENABLE) + continue; + + if (fail_head) + add_to_failed_list(fail_head, dev, r); + + /* keep the old size */ + r->end = 0; + r->start = 0; + r->flags = 0; + } +} + +/* + * reset the resource requirement of the device tree under 'dev'. + * Capture and add each resource's original requirement to the list 'head' + */ +static void __init +pci_dev_reset_resource(struct pci_dev *dev, + struct resource_list_x *head) +{ + u16 class = dev->class >> 8; + + /* Don't touch classless devices or host bridges or ioapics. */ + if (class == PCI_CLASS_NOT_DEFINED || class == PCI_CLASS_BRIDGE_HOST) + return; + + /* Don't touch ioapic devices already enabled by firmware */ + if (class == PCI_CLASS_SYSTEM_PIC) { + u16 command; + pci_read_config_word(dev, PCI_COMMAND, &command); + if (command & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) + return; + } + + if (class == PCI_CLASS_BRIDGE_PCI) { + struct pci_bus *bus = dev->subordinate; + if (bus) { + struct pci_dev *dev1; + list_for_each_entry(dev1, &bus->devices, bus_list) + pci_dev_reset_resource(dev1, head); + } + } + __pci_dev_reset_resource(dev, head); + return; +} + + +/* + * Reset the resource requirement of all devices under 'bus' that + * specified by the kernel parameter 'pci=[override|clear]=<device>[;<device>]*' + * Capture and add each resource's original requirement to the + * list 'head' + */ +static void __init +pci_bus_reset_resource(struct pci_bus *bus, + struct resource_list_x *head) +{ + struct pci_bus *b; + struct pci_dev *dev; + + if (bus->self && (pci_override == PCI_OVERRIDE_ALWAYS || + is_pci_reset_device(bus->self))) { + pci_dev_reset_resource(bus->self, head); + return; + } + + list_for_each_entry(dev, &bus->devices, bus_list) { + if (is_pci_reset_device(dev)) { + pci_dev_reset_resource(dev, head); + continue; + } + if ((b = dev->subordinate)) + pci_bus_reset_resource(b, head); + } + return; +} + +/* + * Release all the resources in the list 'head'. + */ +static void __init +pci_release_resources(struct resource_list_x *head) +{ + struct resource_list_x *list; + unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | + IORESOURCE_PREFETCH; + enum release_type rel_type = whole_subtree; + /* + * Try to release leaf bridge's resources that doesn't fit resource of + * child device under that bridge + */ + for (list = head->next; list;) { + struct pci_bus *bus = list->dev->bus; + pci_bus_release_bridge_resources(bus, list->flags & type_mask, + rel_type); + list = list->next; + } + /* restore size and flags */ + for (list = head->next; list;) { + struct resource *res = list->res; + + res->start = list->start; + res->end = list->end; + res->flags = list->flags; + if (list->dev->subordinate) + res->flags = 0; + + list = list->next; + } + free_failed_list(head); +} + + void __init pci_assign_unassigned_resources(void) { struct pci_bus *bus; + int tried_times = 0; + struct resource_list_x head; + + head.next = NULL; - /* Depth first, calculate sizes and alignments of all - subordinate buses. */ list_for_each_entry(bus, &pci_root_buses, node) { - pci_bus_size_bridges(bus); + if (pci_override == PCI_OVERRIDE_DEVICE || + pci_override == PCI_OVERRIDE_ALWAYS) { + pci_bus_reset_resource(bus, &head); + pci_release_resources(&head); + } else if (pci_override == PCI_CLEAR_DEVICE) { + pci_bus_reset_resource(bus, NULL); + } } - /* Depth last, allocate resources and update the hardware. */ - list_for_each_entry(bus, &pci_root_buses, node) { - pci_bus_assign_resources(bus); + + do { /* loop at most 2 times */ + /* Depth first, calculate sizes and alignments of all + subordinate buses. */ + list_for_each_entry(bus, &pci_root_buses, node) { + pci_bus_size_bridges(bus); + } + + /* Depth last, allocate resources and update the hardware. */ + list_for_each_entry(bus, &pci_root_buses, node) { + __pci_bus_assign_resources(bus, &head); + } + + /* happily exit the loop if all devices are happy */ + if (!head.next) + goto enable_and_dump; + + /* dont' care if any device complained, unless asked to care */ + if (pci_override != PCI_OVERRIDE_CONFLICT) { + free_failed_list(&head); + goto enable_and_dump; + } + + printk(KERN_INFO "PCI: No. %d try to assign unassigned res\n", + tried_times); + + pci_release_resources(&head); + + } while (!tried_times++); + +enable_and_dump: + /* Depth last, update the hardware. */ + list_for_each_entry(bus, &pci_root_buses, node) pci_enable_bridges(bus); - } /* dump the resource on buses */ list_for_each_entry(bus, &pci_root_buses, node) { diff --git a/include/linux/pci.h b/include/linux/pci.h index b1d1795..c001005 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1357,10 +1357,18 @@ extern u8 pci_cache_line_size; extern unsigned long pci_hotplug_io_size; extern unsigned long pci_hotplug_mem_size; +extern int pci_override; +#define PCI_OVERRIDE_OFF 1 +#define PCI_OVERRIDE_CONFLICT 2 +#define PCI_OVERRIDE_ALWAYS 3 +#define PCI_OVERRIDE_DEVICE 4 +#define PCI_CLEAR_DEVICE 5 + int pcibios_add_platform_entries(struct pci_dev *dev); void pcibios_disable_device(struct pci_dev *dev); int pcibios_set_pcie_reset_state(struct pci_dev *dev, enum pcie_reset_state state); +int is_pci_reset_device(struct pci_dev *dev); #ifdef CONFIG_PCI_MMCONFIG extern void __init pci_mmcfg_early_init(void); -- To unsubscribe from this list: send the line "unsubscribe linux-pci" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html