diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/pci/Kconfig | 2 | ||||
-rw-r--r-- | drivers/pci/Makefile | 3 | ||||
-rw-r--r-- | drivers/pci/hotplug/acpiphp_glue.c | 1 | ||||
-rw-r--r-- | drivers/pci/pci-label.c | 252 | ||||
-rw-r--r-- | drivers/pci/pci-sysfs.c | 2 | ||||
-rw-r--r-- | drivers/pci/pci.h | 2 | ||||
-rw-r--r-- | drivers/pci/pcie/aer/aer_inject.c | 31 | ||||
-rw-r--r-- | drivers/pci/probe.c | 4 | ||||
-rw-r--r-- | drivers/pci/quirks.c | 113 | ||||
-rw-r--r-- | drivers/pci/setup-bus.c | 321 |
10 files changed, 576 insertions, 155 deletions
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index a9523fdc691..c8ff646c0b0 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -87,3 +87,5 @@ config PCI_IOAPIC depends on ACPI depends on HOTPLUG default y + +select NLS if (DMI || ACPI) diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 77cf813ba26..98d61c8e984 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -54,8 +54,9 @@ obj-$(CONFIG_TILE) += setup-bus.o setup-irq.o # # ACPI Related PCI FW Functions +# ACPI _DSM provided firmware instance and string name # -obj-$(CONFIG_ACPI) += pci-acpi.o +obj-$(CONFIG_ACPI) += pci-acpi.o pci-label.o # SMBIOS provided firmware instance and labels obj-$(CONFIG_DMI) += pci-label.o diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index cb23aa2ebf9..e610cfe4f07 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -212,6 +212,7 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv) pdev = pci_get_slot(pbus, PCI_DEVFN(device, function)); if (pdev) { + pdev->current_state = PCI_D0; slot->flags |= (SLOT_ENABLED | SLOT_POWEREDON); pci_dev_put(pdev); } diff --git a/drivers/pci/pci-label.c b/drivers/pci/pci-label.c index 90c0a729cd3..77cb2a14c89 100644 --- a/drivers/pci/pci-label.c +++ b/drivers/pci/pci-label.c @@ -5,6 +5,13 @@ * by Narendra K <Narendra_K@dell.com>, * Jordan Hargrave <Jordan_Hargrave@dell.com> * + * PCI Firmware Specification Revision 3.1 section 4.6.7 (DSM for Naming a + * PCI or PCI Express Device Under Operating Systems) defines an instance + * number and string name. This code retrieves them and exports them to sysfs. + * If the system firmware does not provide the ACPI _DSM (Device Specific + * Method), then the SMBIOS type 41 instance number and string is exported to + * sysfs. + * * SMBIOS defines type 41 for onboard pci devices. This code retrieves * the instance number and string from the type 41 record and exports * it to sysfs. @@ -19,8 +26,29 @@ #include <linux/pci_ids.h> #include <linux/module.h> #include <linux/device.h> +#include <linux/nls.h> +#include <linux/acpi.h> +#include <linux/pci-acpi.h> +#include <acpi/acpi_bus.h> #include "pci.h" +#define DEVICE_LABEL_DSM 0x07 + +#ifndef CONFIG_DMI + +static inline int +pci_create_smbiosname_file(struct pci_dev *pdev) +{ + return -1; +} + +static inline void +pci_remove_smbiosname_file(struct pci_dev *pdev) +{ +} + +#else + enum smbios_attr_enum { SMBIOS_ATTR_NONE = 0, SMBIOS_ATTR_LABEL_SHOW, @@ -120,9 +148,7 @@ static struct attribute_group smbios_attr_group = { static int pci_create_smbiosname_file(struct pci_dev *pdev) { - if (!sysfs_create_group(&pdev->dev.kobj, &smbios_attr_group)) - return 0; - return -ENODEV; + return sysfs_create_group(&pdev->dev.kobj, &smbios_attr_group); } static void @@ -131,13 +157,227 @@ pci_remove_smbiosname_file(struct pci_dev *pdev) sysfs_remove_group(&pdev->dev.kobj, &smbios_attr_group); } +#endif + +#ifndef CONFIG_ACPI + +static inline int +pci_create_acpi_index_label_files(struct pci_dev *pdev) +{ + return -1; +} + +static inline int +pci_remove_acpi_index_label_files(struct pci_dev *pdev) +{ + return -1; +} + +static inline bool +device_has_dsm(struct device *dev) +{ + return false; +} + +#else + +static const char device_label_dsm_uuid[] = { + 0xD0, 0x37, 0xC9, 0xE5, 0x53, 0x35, 0x7A, 0x4D, + 0x91, 0x17, 0xEA, 0x4D, 0x19, 0xC3, 0x43, 0x4D +}; + +enum acpi_attr_enum { + ACPI_ATTR_NONE = 0, + ACPI_ATTR_LABEL_SHOW, + ACPI_ATTR_INDEX_SHOW, +}; + +static void dsm_label_utf16s_to_utf8s(union acpi_object *obj, char *buf) +{ + int len; + len = utf16s_to_utf8s((const wchar_t *)obj-> + package.elements[1].string.pointer, + obj->package.elements[1].string.length, + UTF16_LITTLE_ENDIAN, + buf, PAGE_SIZE); + buf[len] = '\n'; +} + +static int +dsm_get_label(acpi_handle handle, int func, + struct acpi_buffer *output, + char *buf, enum acpi_attr_enum attribute) +{ + struct acpi_object_list input; + union acpi_object params[4]; + union acpi_object *obj; + int len = 0; + + int err; + + input.count = 4; + input.pointer = params; + params[0].type = ACPI_TYPE_BUFFER; + params[0].buffer.length = sizeof(device_label_dsm_uuid); + params[0].buffer.pointer = (char *)device_label_dsm_uuid; + params[1].type = ACPI_TYPE_INTEGER; + params[1].integer.value = 0x02; + params[2].type = ACPI_TYPE_INTEGER; + params[2].integer.value = func; + params[3].type = ACPI_TYPE_PACKAGE; + params[3].package.count = 0; + params[3].package.elements = NULL; + + err = acpi_evaluate_object(handle, "_DSM", &input, output); + if (err) + return -1; + + obj = (union acpi_object *)output->pointer; + + switch (obj->type) { + case ACPI_TYPE_PACKAGE: + if (obj->package.count != 2) + break; + len = obj->package.elements[0].integer.value; + if (buf) { + if (attribute == ACPI_ATTR_INDEX_SHOW) + scnprintf(buf, PAGE_SIZE, "%llu\n", + obj->package.elements[0].integer.value); + else if (attribute == ACPI_ATTR_LABEL_SHOW) + dsm_label_utf16s_to_utf8s(obj, buf); + kfree(output->pointer); + return strlen(buf); + } + kfree(output->pointer); + return len; + break; + default: + kfree(output->pointer); + } + return -1; +} + +static bool +device_has_dsm(struct device *dev) +{ + acpi_handle handle; + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + + handle = DEVICE_ACPI_HANDLE(dev); + + if (!handle) + return FALSE; + + if (dsm_get_label(handle, DEVICE_LABEL_DSM, &output, NULL, + ACPI_ATTR_NONE) > 0) + return TRUE; + + return FALSE; +} + +static mode_t +acpi_index_string_exist(struct kobject *kobj, struct attribute *attr, int n) +{ + struct device *dev; + + dev = container_of(kobj, struct device, kobj); + + if (device_has_dsm(dev)) + return S_IRUGO; + + return 0; +} + +static ssize_t +acpilabel_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + acpi_handle handle; + int length; + + handle = DEVICE_ACPI_HANDLE(dev); + + if (!handle) + return -1; + + length = dsm_get_label(handle, DEVICE_LABEL_DSM, + &output, buf, ACPI_ATTR_LABEL_SHOW); + + if (length < 1) + return -1; + + return length; +} + +static ssize_t +acpiindex_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + acpi_handle handle; + int length; + + handle = DEVICE_ACPI_HANDLE(dev); + + if (!handle) + return -1; + + length = dsm_get_label(handle, DEVICE_LABEL_DSM, + &output, buf, ACPI_ATTR_INDEX_SHOW); + + if (length < 0) + return -1; + + return length; + +} + +static struct device_attribute acpi_attr_label = { + .attr = {.name = "label", .mode = 0444}, + .show = acpilabel_show, +}; + +static struct device_attribute acpi_attr_index = { + .attr = {.name = "acpi_index", .mode = 0444}, + .show = acpiindex_show, +}; + +static struct attribute *acpi_attributes[] = { + &acpi_attr_label.attr, + &acpi_attr_index.attr, + NULL, +}; + +static struct attribute_group acpi_attr_group = { + .attrs = acpi_attributes, + .is_visible = acpi_index_string_exist, +}; + +static int +pci_create_acpi_index_label_files(struct pci_dev *pdev) +{ + return sysfs_create_group(&pdev->dev.kobj, &acpi_attr_group); +} + +static int +pci_remove_acpi_index_label_files(struct pci_dev *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, &acpi_attr_group); + return 0; +} +#endif + void pci_create_firmware_label_files(struct pci_dev *pdev) { - if (!pci_create_smbiosname_file(pdev)) - ; + if (device_has_dsm(&pdev->dev)) + pci_create_acpi_index_label_files(pdev); + else + pci_create_smbiosname_file(pdev); } void pci_remove_firmware_label_files(struct pci_dev *pdev) { - pci_remove_smbiosname_file(pdev); + if (device_has_dsm(&pdev->dev)) + pci_remove_acpi_index_label_files(pdev); + else + pci_remove_smbiosname_file(pdev); } diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index ea25e5bfcf2..c85438a367d 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1088,7 +1088,7 @@ static int pci_create_capabilities_sysfs(struct pci_dev *dev) attr->write = write_vpd_attr; retval = sysfs_create_bin_file(&dev->dev.kobj, attr); if (retval) { - kfree(dev->vpd->attr); + kfree(attr); return retval; } dev->vpd->attr = attr; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index f69d6e0fda7..a6ec200fe5e 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -11,7 +11,7 @@ extern int pci_uevent(struct device *dev, struct kobj_uevent_env *env); extern int pci_create_sysfs_dev_files(struct pci_dev *pdev); extern void pci_remove_sysfs_dev_files(struct pci_dev *pdev); -#ifndef CONFIG_DMI +#if !defined(CONFIG_DMI) && !defined(CONFIG_ACPI) static inline void pci_create_firmware_label_files(struct pci_dev *pdev) { return; } static inline void pci_remove_firmware_label_files(struct pci_dev *pdev) diff --git a/drivers/pci/pcie/aer/aer_inject.c b/drivers/pci/pcie/aer/aer_inject.c index b3cf6223f63..f62079ff06d 100644 --- a/drivers/pci/pcie/aer/aer_inject.c +++ b/drivers/pci/pcie/aer/aer_inject.c @@ -27,6 +27,10 @@ #include <linux/stddef.h> #include "aerdrv.h" +/* Override the existing corrected and uncorrected error masks */ +static int aer_mask_override; +module_param(aer_mask_override, bool, 0); + struct aer_error_inj { u8 bus; u8 dev; @@ -322,7 +326,7 @@ static int aer_inject(struct aer_error_inj *einj) unsigned long flags; unsigned int devfn = PCI_DEVFN(einj->dev, einj->fn); int pos_cap_err, rp_pos_cap_err; - u32 sever, cor_mask, uncor_mask; + u32 sever, cor_mask, uncor_mask, cor_mask_orig, uncor_mask_orig; int ret = 0; dev = pci_get_domain_bus_and_slot((int)einj->domain, einj->bus, devfn); @@ -361,6 +365,18 @@ static int aer_inject(struct aer_error_inj *einj) goto out_put; } + if (aer_mask_override) { + cor_mask_orig = cor_mask; + cor_mask &= !(einj->cor_status); + pci_write_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK, + cor_mask); + + uncor_mask_orig = uncor_mask; + uncor_mask &= !(einj->uncor_status); + pci_write_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK, + uncor_mask); + } + spin_lock_irqsave(&inject_lock, flags); err = __find_aer_error_by_dev(dev); @@ -378,14 +394,16 @@ static int aer_inject(struct aer_error_inj *einj) err->header_log2 = einj->header_log2; err->header_log3 = einj->header_log3; - if (einj->cor_status && !(einj->cor_status & ~cor_mask)) { + if (!aer_mask_override && einj->cor_status && + !(einj->cor_status & ~cor_mask)) { ret = -EINVAL; printk(KERN_WARNING "The correctable error(s) is masked " "by device\n"); spin_unlock_irqrestore(&inject_lock, flags); goto out_put; } - if (einj->uncor_status && !(einj->uncor_status & ~uncor_mask)) { + if (!aer_mask_override && einj->uncor_status && + !(einj->uncor_status & ~uncor_mask)) { ret = -EINVAL; printk(KERN_WARNING "The uncorrectable error(s) is masked " "by device\n"); @@ -425,6 +443,13 @@ static int aer_inject(struct aer_error_inj *einj) } spin_unlock_irqrestore(&inject_lock, flags); + if (aer_mask_override) { + pci_write_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK, + cor_mask_orig); + pci_write_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK, + uncor_mask_orig); + } + ret = pci_bus_set_aer_ops(dev->bus); if (ret) goto out_put; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index c84900da3c5..44cbbbaa499 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -764,6 +764,8 @@ int __devinit pci_scan_bridge(struct pci_bus *bus, struct pci_dev *dev, int max, if (pci_find_bus(pci_domain_nr(bus), max+1)) goto out; child = pci_add_new_bus(bus, dev, ++max); + if (!child) + goto out; buses = (buses & 0xff000000) | ((unsigned int)(child->primary) << 0) | ((unsigned int)(child->secondary) << 8) @@ -777,7 +779,7 @@ int __devinit pci_scan_bridge(struct pci_bus *bus, struct pci_dev *dev, int max, buses &= ~0xff000000; buses |= CARDBUS_LATENCY_TIMER << 24; } - + /* * We need to blast all three values with a single write. */ diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 53a786fd0d4..bd80f637846 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -533,6 +533,17 @@ static void __devinit quirk_piix4_acpi(struct pci_dev *dev) DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3, quirk_piix4_acpi); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443MX_3, quirk_piix4_acpi); +#define ICH_PMBASE 0x40 +#define ICH_ACPI_CNTL 0x44 +#define ICH4_ACPI_EN 0x10 +#define ICH6_ACPI_EN 0x80 +#define ICH4_GPIOBASE 0x58 +#define ICH4_GPIO_CNTL 0x5c +#define ICH4_GPIO_EN 0x10 +#define ICH6_GPIOBASE 0x48 +#define ICH6_GPIO_CNTL 0x4c +#define ICH6_GPIO_EN 0x10 + /* * ICH4, ICH4-M, ICH5, ICH5-M ACPI: Three IO regions pointed to by longwords at * 0x40 (128 bytes of ACPI, GPIO & TCO registers) @@ -541,12 +552,33 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443MX_3, qui static void __devinit quirk_ich4_lpc_acpi(struct pci_dev *dev) { u32 region; + u8 enable; - pci_read_config_dword(dev, 0x40, ®ion); - quirk_io_region(dev, region, 128, PCI_BRIDGE_RESOURCES, "ICH4 ACPI/GPIO/TCO"); + /* + * The check for PCIBIOS_MIN_IO is to ensure we won't create a conflict + * with low legacy (and fixed) ports. We don't know the decoding + * priority and can't tell whether the legacy device or the one created + * here is really at that address. This happens on boards with broken + * BIOSes. + */ + + pci_read_config_byte(dev, ICH_ACPI_CNTL, &enable); + if (enable & ICH4_ACPI_EN) { + pci_read_config_dword(dev, ICH_PMBASE, ®ion); + region &= PCI_BASE_ADDRESS_IO_MASK; + if (region >= PCIBIOS_MIN_IO) + quirk_io_region(dev, region, 128, PCI_BRIDGE_RESOURCES, + "ICH4 ACPI/GPIO/TCO"); + } - pci_read_config_dword(dev, 0x58, ®ion); - quirk_io_region(dev, region, 64, PCI_BRIDGE_RESOURCES+1, "ICH4 GPIO"); + pci_read_config_byte(dev, ICH4_GPIO_CNTL, &enable); + if (enable & ICH4_GPIO_EN) { + pci_read_config_dword(dev, ICH4_GPIOBASE, ®ion); + region &= PCI_BASE_ADDRESS_IO_MASK; + if (region >= PCIBIOS_MIN_IO) + quirk_io_region(dev, region, 64, + PCI_BRIDGE_RESOURCES + 1, "ICH4 GPIO"); + } } DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0, quirk_ich4_lpc_acpi); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0, quirk_ich4_lpc_acpi); @@ -562,12 +594,25 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1, qui static void __devinit ich6_lpc_acpi_gpio(struct pci_dev *dev) { u32 region; + u8 enable; - pci_read_config_dword(dev, 0x40, ®ion); - quirk_io_region(dev, region, 128, PCI_BRIDGE_RESOURCES, "ICH6 ACPI/GPIO/TCO"); + pci_read_config_byte(dev, ICH_ACPI_CNTL, &enable); + if (enable & ICH6_ACPI_EN) { + pci_read_config_dword(dev, ICH_PMBASE, ®ion); + region &= PCI_BASE_ADDRESS_IO_MASK; + if (region >= PCIBIOS_MIN_IO) + quirk_io_region(dev, region, 128, PCI_BRIDGE_RESOURCES, + "ICH6 ACPI/GPIO/TCO"); + } - pci_read_config_dword(dev, 0x48, ®ion); - quirk_io_region(dev, region, 64, PCI_BRIDGE_RESOURCES+1, "ICH6 GPIO"); + pci_read_config_byte(dev, ICH6_GPIO_CNTL, &enable); + if (enable & ICH4_GPIO_EN) { + pci_read_config_dword(dev, ICH6_GPIOBASE, ®ion); + region &= PCI_BASE_ADDRESS_IO_MASK; + if (region >= PCIBIOS_MIN_IO) + quirk_io_region(dev, region, 64, + PCI_BRIDGE_RESOURCES + 1, "ICH6 GPIO"); + } } static void __devinit ich6_lpc_generic_decode(struct pci_dev *dev, unsigned reg, const char *name, int dynsize) @@ -2618,58 +2663,6 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4375, #endif /* CONFIG_PCI_MSI */ -#ifdef CONFIG_PCI_IOV - -/* - * For Intel 82576 SR-IOV NIC, if BIOS doesn't allocate resources for the - * SR-IOV BARs, zero the Flash BAR and program the SR-IOV BARs to use the - * old Flash Memory Space. - */ -static void __devinit quirk_i82576_sriov(struct pci_dev *dev) -{ - int pos, flags; - u32 bar, start, size; - - if (PAGE_SIZE > 0x10000) - return; - - flags = pci_resource_flags(dev, 0); - if ((flags & PCI_BASE_ADDRESS_SPACE) != - PCI_BASE_ADDRESS_SPACE_MEMORY || - (flags & PCI_BASE_ADDRESS_MEM_TYPE_MASK) != - PCI_BASE_ADDRESS_MEM_TYPE_32) - return; - - pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_SRIOV); - if (!pos) - return; - - pci_read_config_dword(dev, pos + PCI_SRIOV_BAR, &bar); - if (bar & PCI_BASE_ADDRESS_MEM_MASK) - return; - - start = pci_resource_start(dev, 1); - size = pci_resource_len(dev, 1); - if (!start || size != 0x400000 || start & (size - 1)) - return; - - pci_resource_flags(dev, 1) = 0; - pci_write_config_dword(dev, PCI_BASE_ADDRESS_1, 0); - pci_write_config_dword(dev, pos + PCI_SRIOV_BAR, start); - pci_write_config_dword(dev, pos + PCI_SRIOV_BAR + 12, start + size / 2); - - dev_info(&dev->dev, "use Flash Memory Space for SR-IOV BARs\n"); -} -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x10c9, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x10e6, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x10e7, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x10e8, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x150a, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x150d, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1518, quirk_i82576_sriov); - -#endif /* CONFIG_PCI_IOV */ - /* Allow manual resource allocation for PCI hotplug bridges * via pci=hpmemsize=nnM and pci=hpiosize=nnM parameters. For * some PCI-PCI hotplug bridges, like PLX 6254 (former HINT HB6), diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 66cb8f4cc5f..89d0a6a88df 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -33,11 +33,32 @@ struct resource_list_x { struct pci_dev *dev; resource_size_t start; resource_size_t end; + resource_size_t add_size; unsigned long flags; }; -static void add_to_failed_list(struct resource_list_x *head, - struct pci_dev *dev, struct resource *res) +#define free_list(type, head) do { \ + struct type *list, *tmp; \ + for (list = (head)->next; list;) { \ + tmp = list; \ + list = list->next; \ + kfree(tmp); \ + } \ + (head)->next = NULL; \ +} while (0) + +/** + * add_to_list() - add a new resource tracker to the list + * @head: Head of the list + * @dev: device corresponding to which the resource + * belongs + * @res: The resource to be tracked + * @add_size: additional size to be optionally added + * to the resource + */ +static void add_to_list(struct resource_list_x *head, + struct pci_dev *dev, struct resource *res, + resource_size_t add_size) { struct resource_list_x *list = head; struct resource_list_x *ln = list->next; @@ -45,7 +66,7 @@ static void add_to_failed_list(struct resource_list_x *head, tmp = kmalloc(sizeof(*tmp), GFP_KERNEL); if (!tmp) { - pr_warning("add_to_failed_list: kmalloc() failed!\n"); + pr_warning("add_to_list: kmalloc() failed!\n"); return; } @@ -55,20 +76,14 @@ static void add_to_failed_list(struct resource_list_x *head, tmp->start = res->start; tmp->end = res->end; tmp->flags = res->flags; + tmp->add_size = add_size; list->next = tmp; } -static void free_failed_list(struct resource_list_x *head) +static void add_to_failed_list(struct resource_list_x *head, + struct pci_dev *dev, struct resource *res) { - struct resource_list_x *list, *tmp; - - for (list = head->next; list;) { - tmp = list; - list = list->next; - kfree(tmp); - } - - head->next = NULL; + add_to_list(head, dev, res, 0); } static void __dev_sort_resources(struct pci_dev *dev, @@ -91,18 +106,88 @@ static void __dev_sort_resources(struct pci_dev *dev, pdev_sort_resources(dev, head); } -static void __assign_resources_sorted(struct resource_list *head, - struct resource_list_x *fail_head) +static inline void reset_resource(struct resource *res) +{ + res->start = 0; + res->end = 0; + res->flags = 0; +} + +/** + * adjust_resources_sorted() - satisfy any additional resource requests + * + * @add_head : head of the list tracking requests requiring additional + * resources + * @head : head of the list tracking requests with allocated + * resources + * + * Walk through each element of the add_head and try to procure + * additional resources for the element, provided the element + * is in the head list. + */ +static void adjust_resources_sorted(struct resource_list_x *add_head, + struct resource_list *head) { struct resource *res; - struct resource_list *list, *tmp; + struct resource_list_x *list, *tmp, *prev; + struct resource_list *hlist; + resource_size_t add_size; int idx; - for (list = head->next; list;) { + prev = add_head; + for (list = add_head->next; list;) { res = list->res; + /* skip resource that has been reset */ + if (!res->flags) + goto out; + + /* skip this resource if not found in head list */ + for (hlist = head->next; hlist && hlist->res != res; + hlist = hlist->next); + if (!hlist) { /* just skip */ + prev = list; + list = list->next; + continue; + } + idx = res - &list->dev->resource[0]; + add_size=list->add_size; + if (!resource_size(res) && add_size) { + res->end = res->start + add_size - 1; + if(pci_assign_resource(list->dev, idx)) + reset_resource(res); + } else if (add_size) { + adjust_resource(res, res->start, + resource_size(res) + add_size); + } +out: + tmp = list; + prev->next = list = list->next; + kfree(tmp); + } +} + +/** + * assign_requested_resources_sorted() - satisfy resource requests + * + * @head : head of the list tracking requests for resources + * @failed_list : head of the list tracking requests that could + * not be allocated + * + * Satisfy resource requests of each element in the list. Add + * requests that could not satisfied to the failed_list. + */ +static void assign_requested_resources_sorted(struct resource_list *head, + struct resource_list_x *fail_head) +{ + struct resource *res; + struct resource_list *list; + int idx; - if (pci_assign_resource(list->dev, idx)) { + for (list = head->next; list; list = list->next) { + res = list->res; + idx = res - &list->dev->resource[0]; + if (resource_size(res) && pci_assign_resource(list->dev, idx)) { if (fail_head && !pci_is_root_bus(list->dev->bus)) { /* * if the failed res is for ROM BAR, and it will @@ -112,16 +197,25 @@ static void __assign_resources_sorted(struct resource_list *head, (!(res->flags & IORESOURCE_ROM_ENABLE)))) add_to_failed_list(fail_head, list->dev, res); } - res->start = 0; - res->end = 0; - res->flags = 0; + reset_resource(res); } - tmp = list; - list = list->next; - kfree(tmp); } } +static void __assign_resources_sorted(struct resource_list *head, + struct resource_list_x *add_head, + struct resource_list_x *fail_head) +{ + /* Satisfy the must-have resource requests */ + assign_requested_resources_sorted(head, fail_head); + + /* Try to satisfy any additional nice-to-have resource + requests */ + if (add_head) + adjust_resources_sorted(add_head, head); + free_list(resource_list, head); +} + static void pdev_assign_resources_sorted(struct pci_dev *dev, struct resource_list_x *fail_head) { @@ -129,11 +223,12 @@ static void pdev_assign_resources_sorted(struct pci_dev *dev, head.next = NULL; __dev_sort_resources(dev, &head); - __assign_resources_sorted(&head, fail_head); + __assign_resources_sorted(&head, NULL, fail_head); } static void pbus_assign_resources_sorted(const struct pci_bus *bus, + struct resource_list_x *add_head, struct resource_list_x *fail_head) { struct pci_dev *dev; @@ -143,7 +238,7 @@ static void pbus_assign_resources_sorted(const struct pci_bus *bus, list_for_each_entry(dev, &bus->devices, bus_list) __dev_sort_resources(dev, &head); - __assign_resources_sorted(&head, fail_head); + __assign_resources_sorted(&head, add_head, fail_head); } void pci_setup_cardbus(struct pci_bus *bus) @@ -404,15 +499,62 @@ static struct resource *find_free_bus_resource(struct pci_bus *bus, unsigned lon return NULL; } -/* Sizing the IO windows of the PCI-PCI bridge is trivial, - since these windows have 4K granularity and the IO ranges - of non-bridge PCI devices are limited to 256 bytes. - We must be careful with the ISA aliasing though. */ -static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size) +static resource_size_t calculate_iosize(resource_size_t size, + resource_size_t min_size, + resource_size_t size1, + resource_size_t old_size, + resource_size_t align) +{ + if (size < min_size) + size = min_size; + if (old_size == 1 ) + old_size = 0; + /* To be fixed in 2.5: we should have sort of HAVE_ISA + flag in the struct pci_bus. */ +#if defined(CONFIG_ISA) || defined(CONFIG_EISA) + size = (size & 0xff) + ((size & ~0xffUL) << 2); +#endif + size = ALIGN(size + size1, align); + if (size < old_size) + size = old_size; + return size; +} + +static resource_size_t calculate_memsize(resource_size_t size, + resource_size_t min_size, + resource_size_t size1, + resource_size_t old_size, + resource_size_t align) +{ + if (size < min_size) + size = min_size; + if (old_size == 1 ) + old_size = 0; + if (size < old_size) + size = old_size; + size = ALIGN(size + size1, align); + return size; +} + +/** + * pbus_size_io() - size the io window of a given bus + * + * @bus : the bus + * @min_size : the minimum io window that must to be allocated + * @add_size : additional optional io window + * @add_head : track the additional io window on this list + * + * Sizing the IO windows of the PCI-PCI bridge is trivial, + * since these windows have 4K granularity and the IO ranges + * of non-bridge PCI devices are limited to 256 bytes. + * We must be careful with the ISA aliasing though. + */ +static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size, + resource_size_t add_size, struct resource_list_x *add_head) { struct pci_dev *dev; struct resource *b_res = find_free_bus_resource(bus, IORESOURCE_IO); - unsigned long size = 0, size1 = 0, old_size; + unsigned long size = 0, size0 = 0, size1 = 0; if (!b_res) return; @@ -435,20 +577,12 @@ static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size) size1 += r_size; } } - if (size < min_size) - size = min_size; - old_size = resource_size(b_res); - if (old_size == 1) - old_size = 0; -/* To be fixed in 2.5: we should have sort of HAVE_ISA - flag in the struct pci_bus. */ -#if defined(CONFIG_ISA) || defined(CONFIG_EISA) - size = (size & 0xff) + ((size & ~0xffUL) << 2); -#endif - size = ALIGN(size + size1, 4096); - if (size < old_size) - size = old_size; - if (!size) { + size0 = calculate_iosize(size, min_size, size1, + resource_size(b_res), 4096); + size1 = !add_size? size0: + calculate_iosize(size, min_size+add_size, size1, + resource_size(b_res), 4096); + if (!size0 && !size1) { if (b_res->start || b_res->end) dev_info(&bus->self->dev, "disabling bridge window " "%pR to [bus %02x-%02x] (unused)\n", b_res, @@ -458,17 +592,30 @@ static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size) } /* Alignment of the IO window is always 4K */ b_res->start = 4096; - b_res->end = b_res->start + size - 1; + b_res->end = b_res->start + size0 - 1; b_res->flags |= IORESOURCE_STARTALIGN; + if (size1 > size0 && add_head) + add_to_list(add_head, bus->self, b_res, size1-size0); } -/* Calculate the size of the bus and minimal alignment which - guarantees that all child resources fit in this size. */ +/** + * pbus_size_mem() - size the memory window of a given bus + * + * @bus : the bus + * @min_size : the minimum memory window that must to be allocated + * @add_size : additional optional memory window + * @add_head : track the additional memory window on this list + * + * Calculate the size of the bus and minimal alignment which + * guarantees that all child resources fit in this size. + */ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, - unsigned long type, resource_size_t min_size) + unsigned long type, resource_size_t min_size, + resource_size_t add_size, + struct resource_list_x *add_head) { struct pci_dev *dev; - resource_size_t min_align, align, size, old_size; + resource_size_t min_align, align, size, size0, size1; resource_size_t aligns[12]; /* Alignments from 1Mb to 2Gb */ int order, max_order; struct resource *b_res = find_free_bus_resource(bus, type); @@ -516,14 +663,6 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, mem64_mask &= r->flags & IORESOURCE_MEM_64; } } - if (size < min_size) - size = min_size; - old_size = resource_size(b_res); - if (old_size == 1) - old_size = 0; - if (size < old_size) - size = old_size; - align = 0; min_align = 0; for (order = 0; order <= max_order; order++) { @@ -537,8 +676,11 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, min_align = align1 >> 1; align += aligns[order]; } - size = ALIGN(size, min_align); - if (!size) { + size0 = calculate_memsize(size, min_size, 0, resource_size(b_res), align); + size1 = !add_size ? size : + calculate_memsize(size, min_size+add_size, 0, + resource_size(b_res), align); + if (!size0 && !size1) { if (b_res->start || b_res->end) dev_info(&bus->self->dev, "disabling bridge window " "%pR to [bus %02x-%02x] (unused)\n", b_res, @@ -547,9 +689,10 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, return 1; } b_res->start = min_align; - b_res->end = size + min_align - 1; - b_res->flags |= IORESOURCE_STARTALIGN; - b_res->flags |= mem64_mask; + b_res->end = size0 + min_align - 1; + b_res->flags |= IORESOURCE_STARTALIGN | mem64_mask; + if (size1 > size0 && add_head) + add_to_list(add_head, bus->self, b_res, size1-size0); return 1; } @@ -602,11 +745,12 @@ static void pci_bus_size_cardbus(struct pci_bus *bus) } } -void __ref pci_bus_size_bridges(struct pci_bus *bus) +void __ref __pci_bus_size_bridges(struct pci_bus *bus, + struct resource_list_x *add_head) { struct pci_dev *dev; unsigned long mask, prefmask; - resource_size_t min_mem_size = 0, min_io_size = 0; + resource_size_t additional_mem_size = 0, additional_io_size = 0; list_for_each_entry(dev, &bus->devices, bus_list) { struct pci_bus *b = dev->subordinate; @@ -620,7 +764,7 @@ void __ref pci_bus_size_bridges(struct pci_bus *bus) case PCI_CLASS_BRIDGE_PCI: default: - pci_bus_size_bridges(b); + __pci_bus_size_bridges(b, add_head); break; } } @@ -637,11 +781,14 @@ void __ref pci_bus_size_bridges(struct pci_bus *bus) case PCI_CLASS_BRIDGE_PCI: pci_bridge_check_ranges(bus); if (bus->self->is_hotplug_bridge) { - min_io_size = pci_hotplug_io_size; - min_mem_size = pci_hotplug_mem_size; + additional_io_size = pci_hotplug_io_size; + additional_mem_size = pci_hotplug_mem_size; } + /* + * Follow thru + */ default: - pbus_size_io(bus, min_io_size); + pbus_size_io(bus, 0, additional_io_size, add_head); /* If the bridge supports prefetchable range, size it separately. If it doesn't, or its prefetchable window has already been allocated by arch code, try @@ -649,30 +796,36 @@ void __ref pci_bus_size_bridges(struct pci_bus *bus) resources. */ mask = IORESOURCE_MEM; prefmask = IORESOURCE_MEM | IORESOURCE_PREFETCH; - if (pbus_size_mem(bus, prefmask, prefmask, min_mem_size)) + if (pbus_size_mem(bus, prefmask, prefmask, 0, additional_mem_size, add_head)) mask = prefmask; /* Success, size non-prefetch only. */ else - min_mem_size += min_mem_size; - pbus_size_mem(bus, mask, IORESOURCE_MEM, min_mem_size); + additional_mem_size += additional_mem_size; + pbus_size_mem(bus, mask, IORESOURCE_MEM, 0, additional_mem_size, add_head); break; } } + +void __ref pci_bus_size_bridges(struct pci_bus *bus) +{ + __pci_bus_size_bridges(bus, NULL); +} EXPORT_SYMBOL(pci_bus_size_bridges); static void __ref __pci_bus_assign_resources(const struct pci_bus *bus, + struct resource_list_x *add_head, struct resource_list_x *fail_head) { struct pci_bus *b; struct pci_dev *dev; - pbus_assign_resources_sorted(bus, fail_head); + pbus_assign_resources_sorted(bus, add_head, fail_head); list_for_each_entry(dev, &bus->devices, bus_list) { b = dev->subordinate; if (!b) continue; - __pci_bus_assign_resources(b, fail_head); + __pci_bus_assign_resources(b, add_head, fail_head); switch (dev->class >> 8) { case PCI_CLASS_BRIDGE_PCI: @@ -694,7 +847,7 @@ static void __ref __pci_bus_assign_resources(const struct pci_bus *bus, void __ref pci_bus_assign_resources(const struct pci_bus *bus) { - __pci_bus_assign_resources(bus, NULL); + __pci_bus_assign_resources(bus, NULL, NULL); } EXPORT_SYMBOL(pci_bus_assign_resources); @@ -709,7 +862,7 @@ static void __ref __pci_bridge_assign_resources(const struct pci_dev *bridge, if (!b) return; - __pci_bus_assign_resources(b, fail_head); + __pci_bus_assign_resources(b, NULL, fail_head); switch (bridge->class >> 8) { case PCI_CLASS_BRIDGE_PCI: @@ -842,17 +995,21 @@ void __init pci_assign_unassigned_resources(void) { struct pci_bus *bus; - + struct resource_list_x add_list; /* list of resources that + want additional resources */ + add_list.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); + __pci_bus_size_bridges(bus, &add_list); } + /* Depth last, allocate resources and update the hardware. */ list_for_each_entry(bus, &pci_root_buses, node) { - pci_bus_assign_resources(bus); + __pci_bus_assign_resources(bus, &add_list, NULL); pci_enable_bridges(bus); } + BUG_ON(add_list.next); /* dump the resource on buses */ list_for_each_entry(bus, &pci_root_buses, node) { @@ -882,7 +1039,7 @@ again: if (tried_times >= 2) { /* still fail, don't need to try more */ - free_failed_list(&head); + free_list(resource_list_x, &head); goto enable_all; } @@ -913,7 +1070,7 @@ again: list = list->next; } - free_failed_list(&head); + free_list(resource_list_x, &head); goto again; |