From 2baad5f96b498812626eadb6f6af3eb41d8656a3 Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Wed, 13 Feb 2008 23:30:12 +0200 Subject: PCI: #if 0 pci_assign_resource_fixed() An unused function that bloated the kernel only when CONFIG_EMBEDDED was enabled... Signed-off-by: Adrian Bunk Signed-off-by: Greg Kroah-Hartman --- include/linux/pci.h | 1 - 1 file changed, 1 deletion(-) (limited to 'include') diff --git a/include/linux/pci.h b/include/linux/pci.h index ea760e519c4..3a2b9fbdb37 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -601,7 +601,6 @@ int pcie_get_readrq(struct pci_dev *dev); int pcie_set_readrq(struct pci_dev *dev, int rq); void pci_update_resource(struct pci_dev *dev, struct resource *res, int resno); int __must_check pci_assign_resource(struct pci_dev *dev, int i); -int __must_check pci_assign_resource_fixed(struct pci_dev *dev, int i); int pci_select_bars(struct pci_dev *dev, unsigned long flags); /* ROM control related routines */ -- cgit v1.2.3-70-g09d2 From 448432c4b8e2e3189177d6dbd16b8a8d83c5c11c Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 12 Feb 2008 13:36:20 -0800 Subject: PCI: remove pci_find_present No one is using this function anymore for quite some time, so remove it. Everyone calls pci_dev_present() instead anyway... Signed-off-by: Greg Kroah-Hartman --- drivers/pci/search.c | 35 +++++++++++++++-------------------- include/linux/pci.h | 2 -- 2 files changed, 15 insertions(+), 22 deletions(-) (limited to 'include') diff --git a/drivers/pci/search.c b/drivers/pci/search.c index 8541034021f..1aabe3dbc7c 100644 --- a/drivers/pci/search.c +++ b/drivers/pci/search.c @@ -436,7 +436,18 @@ exit: return dev; } -const struct pci_device_id *pci_find_present(const struct pci_device_id *ids) +/** + * pci_dev_present - Returns 1 if device matching the device list is present, 0 if not. + * @ids: A pointer to a null terminated list of struct pci_device_id structures + * that describe the type of PCI device the caller is trying to find. + * + * Obvious fact: You do not have a reference to any device that might be found + * by this function, so if that device is removed from the system right after + * this function is finished, the value will be stale. Use this function to + * find devices that are usually built into a system, or for a general hint as + * to if another device happens to be present at this specific moment in time. + */ +int pci_dev_present(const struct pci_device_id *ids) { struct pci_dev *dev; const struct pci_device_id *found = NULL; @@ -452,27 +463,11 @@ const struct pci_device_id *pci_find_present(const struct pci_device_id *ids) } exit: up_read(&pci_bus_sem); - return found; -} - -/** - * pci_dev_present - Returns 1 if device matching the device list is present, 0 if not. - * @ids: A pointer to a null terminated list of struct pci_device_id structures - * that describe the type of PCI device the caller is trying to find. - * - * Obvious fact: You do not have a reference to any device that might be found - * by this function, so if that device is removed from the system right after - * this function is finished, the value will be stale. Use this function to - * find devices that are usually built into a system, or for a general hint as - * to if another device happens to be present at this specific moment in time. - */ -int pci_dev_present(const struct pci_device_id *ids) -{ - return pci_find_present(ids) == NULL ? 0 : 1; + if (found) + return 1; + return 0; } - EXPORT_SYMBOL(pci_dev_present); -EXPORT_SYMBOL(pci_find_present); #ifdef CONFIG_PCI_LEGACY EXPORT_SYMBOL(pci_find_device); diff --git a/include/linux/pci.h b/include/linux/pci.h index 3a2b9fbdb37..b39f2abbea1 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -527,7 +527,6 @@ struct pci_dev *pci_get_slot(struct pci_bus *bus, unsigned int devfn); struct pci_dev *pci_get_bus_and_slot(unsigned int bus, unsigned int devfn); struct pci_dev *pci_get_class(unsigned int class, struct pci_dev *from); int pci_dev_present(const struct pci_device_id *ids); -const struct pci_device_id *pci_find_present(const struct pci_device_id *ids); int pci_bus_read_config_byte(struct pci_bus *bus, unsigned int devfn, int where, u8 *val); @@ -816,7 +815,6 @@ static inline struct pci_dev *pci_get_class(unsigned int class, #define pci_dev_present(ids) (0) #define no_pci_devices() (1) -#define pci_find_present(ids) (NULL) #define pci_dev_put(dev) do { } while (0) static inline void pci_set_master(struct pci_dev *dev) -- cgit v1.2.3-70-g09d2 From 34220909a26b7f7cfc71e88ce01856c2563fe1d4 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 13 Feb 2008 09:32:03 -0800 Subject: PCI: remove pci_get_device_reverse This removes the pci_get_device_reverse function as there should not be any need to walk pci devices backwards anymore. All users of this call are now gone from the tree, so it is safe to remove it. Signed-off-by: Greg Kroah-Hartman --- drivers/pci/search.c | 41 ----------------------------------------- include/linux/pci.h | 10 ---------- 2 files changed, 51 deletions(-) (limited to 'include') diff --git a/drivers/pci/search.c b/drivers/pci/search.c index 1aabe3dbc7c..a04c43ffce4 100644 --- a/drivers/pci/search.c +++ b/drivers/pci/search.c @@ -359,46 +359,6 @@ pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev *from) return pci_get_subsys(vendor, device, PCI_ANY_ID, PCI_ANY_ID, from); } -/** - * pci_get_device_reverse - begin or continue searching for a PCI device by vendor/device id - * @vendor: PCI vendor id to match, or %PCI_ANY_ID to match all vendor ids - * @device: PCI device id to match, or %PCI_ANY_ID to match all device ids - * @from: Previous PCI device found in search, or %NULL for new search. - * - * Iterates through the list of known PCI devices in the reverse order of - * pci_get_device. - * If a PCI device is found with a matching @vendor and @device, the reference - * count to the device is incremented and a pointer to its device structure - * is returned Otherwise, %NULL is returned. A new search is initiated by - * passing %NULL as the @from argument. Otherwise if @from is not %NULL, - * searches continue from next device on the global list. The reference - * count for @from is always decremented if it is not %NULL. - */ -struct pci_dev * -pci_get_device_reverse(unsigned int vendor, unsigned int device, struct pci_dev *from) -{ - struct list_head *n; - struct pci_dev *dev; - - WARN_ON(in_interrupt()); - down_read(&pci_bus_sem); - n = from ? from->global_list.prev : pci_devices.prev; - - while (n && (n != &pci_devices)) { - dev = pci_dev_g(n); - if ((vendor == PCI_ANY_ID || dev->vendor == vendor) && - (device == PCI_ANY_ID || dev->device == device)) - goto exit; - n = n->prev; - } - dev = NULL; -exit: - dev = pci_dev_get(dev); - up_read(&pci_bus_sem); - pci_dev_put(from); - return dev; -} - /** * pci_get_class - begin or continue searching for a PCI device by class * @class: search for a PCI device with this class designation @@ -479,7 +439,6 @@ EXPORT_SYMBOL(pci_find_bus); EXPORT_SYMBOL(pci_find_next_bus); /* For everyone */ EXPORT_SYMBOL(pci_get_device); -EXPORT_SYMBOL(pci_get_device_reverse); EXPORT_SYMBOL(pci_get_subsys); EXPORT_SYMBOL(pci_get_slot); EXPORT_SYMBOL(pci_get_bus_and_slot); diff --git a/include/linux/pci.h b/include/linux/pci.h index b39f2abbea1..39ecf48ffa3 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -517,9 +517,6 @@ struct pci_bus *pci_find_next_bus(const struct pci_bus *from); struct pci_dev *pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev *from); -struct pci_dev *pci_get_device_reverse(unsigned int vendor, unsigned int device, - struct pci_dev *from); - struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device, unsigned int ss_vendor, unsigned int ss_device, struct pci_dev *from); @@ -791,13 +788,6 @@ static inline struct pci_dev *pci_get_device(unsigned int vendor, return NULL; } -static inline struct pci_dev *pci_get_device_reverse(unsigned int vendor, - unsigned int device, - struct pci_dev *from) -{ - return NULL; -} - static inline struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device, unsigned int ss_vendor, -- cgit v1.2.3-70-g09d2 From 95247b57ed844511a212265b45cf9a919753aea1 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 13 Feb 2008 11:03:58 -0800 Subject: PCI: clean up search.c a lot This cleans up the search.c file, now using the pci list of devices that are created for the driver core, instead of relying on our separate list of devices. It's better to use the functions already created for this kind of thing, instead of rolling our own all the time. This work is done in anticipation of getting rid of that second list of pci devices all together. And it ends up saving code, always a nice benefit. This also removes one compiler warning for when CONFIG_PCI_LEGACY is enabled as we no longer internally use the deprecated functions anymore. Signed-off-by: Greg Kroah-Hartman --- drivers/pci/search.c | 249 +++++++++++++++++++++++---------------------------- include/linux/pci.h | 4 +- 2 files changed, 114 insertions(+), 139 deletions(-) (limited to 'include') diff --git a/drivers/pci/search.c b/drivers/pci/search.c index a04c43ffce4..217814fef4e 100644 --- a/drivers/pci/search.c +++ b/drivers/pci/search.c @@ -114,31 +114,63 @@ pci_find_next_bus(const struct pci_bus *from) } #ifdef CONFIG_PCI_LEGACY - /** * pci_find_slot - locate PCI device from a given PCI slot * @bus: number of PCI bus on which desired PCI device resides - * @devfn: encodes number of PCI slot in which the desired PCI - * device resides and the logical device number within that slot + * @devfn: encodes number of PCI slot in which the desired PCI + * device resides and the logical device number within that slot * in case of multi-function devices. * - * Given a PCI bus and slot/function number, the desired PCI device + * Given a PCI bus and slot/function number, the desired PCI device * is located in system global list of PCI devices. If the device - * is found, a pointer to its data structure is returned. If no + * is found, a pointer to its data structure is returned. If no * device is found, %NULL is returned. + * + * NOTE: Do not use this function any more; use pci_get_slot() instead, as + * the PCI device returned by this function can disappear at any moment in + * time. */ -struct pci_dev * -pci_find_slot(unsigned int bus, unsigned int devfn) +struct pci_dev *pci_find_slot(unsigned int bus, unsigned int devfn) { struct pci_dev *dev = NULL; - while ((dev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { - if (dev->bus->number == bus && dev->devfn == devfn) + while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { + if (dev->bus->number == bus && dev->devfn == devfn) { + pci_dev_put(dev); return dev; + } } return NULL; } +EXPORT_SYMBOL(pci_find_slot); +/** + * pci_find_device - begin or continue searching for a PCI device by vendor/device id + * @vendor: PCI vendor id to match, or %PCI_ANY_ID to match all vendor ids + * @device: PCI device id to match, or %PCI_ANY_ID to match all device ids + * @from: Previous PCI device found in search, or %NULL for new search. + * + * Iterates through the list of known PCI devices. If a PCI device is found + * with a matching @vendor and @device, a pointer to its device structure is + * returned. Otherwise, %NULL is returned. + * A new search is initiated by passing %NULL as the @from argument. + * Otherwise if @from is not %NULL, searches continue from next device + * on the global list. + * + * NOTE: Do not use this function any more; use pci_get_device() instead, as + * the PCI device returned by this function can disappear at any moment in + * time. + */ +struct pci_dev *pci_find_device(unsigned int vendor, unsigned int device, + const struct pci_dev *from) +{ + struct pci_dev *pdev; + + pdev = pci_get_subsys(vendor, device, PCI_ANY_ID, PCI_ANY_ID, from); + pci_dev_put(pdev); + return pdev; +} +EXPORT_SYMBOL(pci_find_device); #endif /* CONFIG_PCI_LEGACY */ /** @@ -204,86 +236,52 @@ struct pci_dev * pci_get_bus_and_slot(unsigned int bus, unsigned int devfn) return NULL; } -#ifdef CONFIG_PCI_LEGACY -/** - * pci_find_subsys - begin or continue searching for a PCI device by vendor/subvendor/device/subdevice id - * @vendor: PCI vendor id to match, or %PCI_ANY_ID to match all vendor ids - * @device: PCI device id to match, or %PCI_ANY_ID to match all device ids - * @ss_vendor: PCI subsystem vendor id to match, or %PCI_ANY_ID to match all vendor ids - * @ss_device: PCI subsystem device id to match, or %PCI_ANY_ID to match all device ids - * @from: Previous PCI device found in search, or %NULL for new search. - * - * Iterates through the list of known PCI devices. If a PCI device is - * found with a matching @vendor, @device, @ss_vendor and @ss_device, a - * pointer to its device structure is returned. Otherwise, %NULL is returned. - * A new search is initiated by passing %NULL as the @from argument. - * Otherwise if @from is not %NULL, searches continue from next device - * on the global list. - * - * NOTE: Do not use this function any more; use pci_get_subsys() instead, as - * the PCI device returned by this function can disappear at any moment in - * time. - */ -static struct pci_dev * pci_find_subsys(unsigned int vendor, - unsigned int device, - unsigned int ss_vendor, - unsigned int ss_device, - const struct pci_dev *from) +static int match_pci_dev_by_id(struct device *dev, void *data) { - struct list_head *n; - struct pci_dev *dev; + struct pci_dev *pdev = to_pci_dev(dev); + struct pci_device_id *id = data; - WARN_ON(in_interrupt()); - - /* - * pci_find_subsys() can be called on the ide_setup() path, super-early - * in boot. But the down_read() will enable local interrupts, which - * can cause some machines to crash. So here we detect and flag that - * situation and bail out early. - */ - if (unlikely(no_pci_devices())) - return NULL; - down_read(&pci_bus_sem); - n = from ? from->global_list.next : pci_devices.next; - - while (n && (n != &pci_devices)) { - dev = pci_dev_g(n); - if ((vendor == PCI_ANY_ID || dev->vendor == vendor) && - (device == PCI_ANY_ID || dev->device == device) && - (ss_vendor == PCI_ANY_ID || dev->subsystem_vendor == ss_vendor) && - (ss_device == PCI_ANY_ID || dev->subsystem_device == ss_device)) - goto exit; - n = n->next; - } - dev = NULL; -exit: - up_read(&pci_bus_sem); - return dev; + if (pci_match_one_device(id, pdev)) + return 1; + return 0; } -/** - * pci_find_device - begin or continue searching for a PCI device by vendor/device id - * @vendor: PCI vendor id to match, or %PCI_ANY_ID to match all vendor ids - * @device: PCI device id to match, or %PCI_ANY_ID to match all device ids +/* + * pci_get_dev_by_id - begin or continue searching for a PCI device by id + * @id: pointer to struct pci_device_id to match for the device * @from: Previous PCI device found in search, or %NULL for new search. * * Iterates through the list of known PCI devices. If a PCI device is found - * with a matching @vendor and @device, a pointer to its device structure is - * returned. Otherwise, %NULL is returned. - * A new search is initiated by passing %NULL as the @from argument. - * Otherwise if @from is not %NULL, searches continue from next device - * on the global list. - * - * NOTE: Do not use this function any more; use pci_get_device() instead, as - * the PCI device returned by this function can disappear at any moment in - * time. + * with a matching id a pointer to its device structure is returned, and the + * reference count to the device is incremented. Otherwise, %NULL is returned. + * A new search is initiated by passing %NULL as the @from argument. Otherwise + * if @from is not %NULL, searches continue from next device on the global + * list. The reference count for @from is always decremented if it is not + * %NULL. + * + * This is an internal function for use by the other search functions in + * this file. */ -struct pci_dev * -pci_find_device(unsigned int vendor, unsigned int device, const struct pci_dev *from) +static struct pci_dev *pci_get_dev_by_id(const struct pci_device_id *id, + const struct pci_dev *from) { - return pci_find_subsys(vendor, device, PCI_ANY_ID, PCI_ANY_ID, from); + struct device *dev; + struct device *dev_start = NULL; + struct pci_dev *pdev = NULL; + + WARN_ON(in_interrupt()); + if (from) { + /* FIXME + * take the cast off, when bus_find_device is made const. + */ + dev_start = (struct device *)&from->dev; + } + dev = bus_find_device(&pci_bus_type, dev_start, (void *)id, + match_pci_dev_by_id); + if (dev) + pdev = to_pci_dev(dev); + return pdev; } -#endif /* CONFIG_PCI_LEGACY */ /** * pci_get_subsys - begin or continue searching for a PCI device by vendor/subvendor/device/subdevice id @@ -301,42 +299,34 @@ pci_find_device(unsigned int vendor, unsigned int device, const struct pci_dev * * searches continue from next device on the global list. * The reference count for @from is always decremented if it is not %NULL. */ -struct pci_dev * -pci_get_subsys(unsigned int vendor, unsigned int device, - unsigned int ss_vendor, unsigned int ss_device, - struct pci_dev *from) +struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device, + unsigned int ss_vendor, unsigned int ss_device, + const struct pci_dev *from) { - struct list_head *n; - struct pci_dev *dev; - - WARN_ON(in_interrupt()); + struct pci_dev *pdev; + struct pci_device_id *id; /* - * pci_get_subsys() can potentially be called by drivers super-early - * in boot. But the down_read() will enable local interrupts, which - * can cause some machines to crash. So here we detect and flag that - * situation and bail out early. + * pci_find_subsys() can be called on the ide_setup() path, + * super-early in boot. But the down_read() will enable local + * interrupts, which can cause some machines to crash. So here we + * detect and flag that situation and bail out early. */ if (unlikely(no_pci_devices())) return NULL; - down_read(&pci_bus_sem); - n = from ? from->global_list.next : pci_devices.next; - - while (n && (n != &pci_devices)) { - dev = pci_dev_g(n); - if ((vendor == PCI_ANY_ID || dev->vendor == vendor) && - (device == PCI_ANY_ID || dev->device == device) && - (ss_vendor == PCI_ANY_ID || dev->subsystem_vendor == ss_vendor) && - (ss_device == PCI_ANY_ID || dev->subsystem_device == ss_device)) - goto exit; - n = n->next; - } - dev = NULL; -exit: - dev = pci_dev_get(dev); - up_read(&pci_bus_sem); - pci_dev_put(from); - return dev; + + id = kzalloc(sizeof(*id), GFP_KERNEL); + if (!id) + return NULL; + id->vendor = vendor; + id->device = device; + id->subvendor = ss_vendor; + id->subdevice = ss_device; + + pdev = pci_get_dev_by_id(id, from); + kfree(id); + + return pdev; } /** @@ -375,24 +365,18 @@ pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev *from) */ struct pci_dev *pci_get_class(unsigned int class, struct pci_dev *from) { - struct list_head *n; struct pci_dev *dev; + struct pci_device_id *id; - WARN_ON(in_interrupt()); - down_read(&pci_bus_sem); - n = from ? from->global_list.next : pci_devices.next; + id = kzalloc(sizeof(*id), GFP_KERNEL); + if (!id) + return NULL; + id->vendor = id->device = id->subvendor = id->subdevice = PCI_ANY_ID; + id->class_mask = PCI_ANY_ID; + id->class = class; - while (n && (n != &pci_devices)) { - dev = pci_dev_g(n); - if (dev->class == class) - goto exit; - n = n->next; - } - dev = NULL; -exit: - dev = pci_dev_get(dev); - up_read(&pci_bus_sem); - pci_dev_put(from); + dev = pci_get_dev_by_id(id, from); + kfree(id); return dev; } @@ -409,31 +393,22 @@ exit: */ int pci_dev_present(const struct pci_device_id *ids) { - struct pci_dev *dev; - const struct pci_device_id *found = NULL; + struct pci_dev *found = NULL; WARN_ON(in_interrupt()); - down_read(&pci_bus_sem); while (ids->vendor || ids->subvendor || ids->class_mask) { - list_for_each_entry(dev, &pci_devices, global_list) { - if ((found = pci_match_one_device(ids, dev)) != NULL) - goto exit; - } + found = pci_get_dev_by_id(ids, NULL); + if (found) + goto exit; ids++; } exit: - up_read(&pci_bus_sem); if (found) return 1; return 0; } EXPORT_SYMBOL(pci_dev_present); -#ifdef CONFIG_PCI_LEGACY -EXPORT_SYMBOL(pci_find_device); -EXPORT_SYMBOL(pci_find_slot); -#endif /* CONFIG_PCI_LEGACY */ - /* For boot time work */ EXPORT_SYMBOL(pci_find_bus); EXPORT_SYMBOL(pci_find_next_bus); diff --git a/include/linux/pci.h b/include/linux/pci.h index 39ecf48ffa3..5f79c72bae6 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -519,7 +519,7 @@ struct pci_dev *pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev *from); struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device, unsigned int ss_vendor, unsigned int ss_device, - struct pci_dev *from); + const struct pci_dev *from); struct pci_dev *pci_get_slot(struct pci_bus *bus, unsigned int devfn); struct pci_dev *pci_get_bus_and_slot(unsigned int bus, unsigned int devfn); struct pci_dev *pci_get_class(unsigned int class, struct pci_dev *from); @@ -792,7 +792,7 @@ static inline struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device, unsigned int ss_vendor, unsigned int ss_device, - struct pci_dev *from) + const struct pci_dev *from) { return NULL; } -- cgit v1.2.3-70-g09d2 From 1ba6ab11d8fbd8d29afec4e39236e1255ae0339a Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 13 Feb 2008 15:06:38 -0800 Subject: PCI: remove initial bios sort of PCI devices on x86 We currently keep 2 lists of PCI devices in the system, one in the driver core, and one all on its own. This second list is sorted at boot time, in "BIOS" order, to try to remain compatible with older kernels (2.2 and earlier days). There was also a "nosort" option to turn this sorting off, to remain compatible with even older kernel versions, but that just ends up being what we have been doing from 2.5 days... Unfortunately, the second list of devices is not really ever used to determine the probing order of PCI devices or drivers[1]. That is done using the driver core list instead. This change happened back in the early 2.5 days. Relying on BIOS ording for the binding of drivers to specific device names is problematic for many reasons, and userspace tools like udev exist to properly name devices in a persistant manner if that is needed, no reliance on the BIOS is needed. Matt Domsch and others at Dell noticed this back in 2006, and added a boot option to sort the PCI device lists (both of them) in a breadth-first manner to help remain compatible with the 2.4 order, if needed for any reason. This option is not going away, as some systems rely on them. This patch removes the sorting of the internal PCI device list in "BIOS" mode, as it's not needed at all anymore, and hasn't for many years. I've also removed the PCI flags for this from some other arches that for some reason defined them, but never used them. This should not change the ordering of any drivers or device probing. [1] The old-style pci_get_device and pci_find_device() still used this sorting order, but there are very few drivers that use these functions, as they are deprecated for use in this manner. If for some reason, a driver rely on the order and uses these functions, the breadth-first boot option will resolve any problem. Cc: Matt Domsch Signed-off-by: Greg Kroah-Hartman --- Documentation/kernel-parameters.txt | 4 -- arch/frv/mb93090-mb00/pci-frv.h | 2 - arch/mn10300/unit-asb2305/pci-asb2305.h | 2 - arch/sh/drivers/pci/pci-sh4.h | 2 - arch/x86/pci/common.c | 7 ---- arch/x86/pci/pcbios.c | 72 --------------------------------- arch/x86/pci/pci.h | 3 -- include/asm-sh/mpc1211/pci.h | 2 - 8 files changed, 94 deletions(-) (limited to 'include') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 4b0f1ae31a4..e30d8fe4e4b 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1461,10 +1461,6 @@ and is between 256 and 4096 characters. It is defined in the file nomsi [MSI] If the PCI_MSI kernel config parameter is enabled, this kernel boot option can be used to disable the use of MSI interrupts system-wide. - nosort [X86-32] Don't sort PCI devices according to - order given by the PCI BIOS. This sorting is - done to get a device order compatible with - older kernels. biosirq [X86-32] Use PCI BIOS calls to get the interrupt routing table. These calls are known to be buggy on several machines and they hang the machine diff --git a/arch/frv/mb93090-mb00/pci-frv.h b/arch/frv/mb93090-mb00/pci-frv.h index 7481797ab38..0c7bf39dc72 100644 --- a/arch/frv/mb93090-mb00/pci-frv.h +++ b/arch/frv/mb93090-mb00/pci-frv.h @@ -17,8 +17,6 @@ #define PCI_PROBE_BIOS 0x0001 #define PCI_PROBE_CONF1 0x0002 #define PCI_PROBE_CONF2 0x0004 -#define PCI_NO_SORT 0x0100 -#define PCI_BIOS_SORT 0x0200 #define PCI_NO_CHECKS 0x0400 #define PCI_ASSIGN_ROMS 0x1000 #define PCI_BIOS_IRQ_SCAN 0x2000 diff --git a/arch/mn10300/unit-asb2305/pci-asb2305.h b/arch/mn10300/unit-asb2305/pci-asb2305.h index 84634fa3bce..9763d1ce343 100644 --- a/arch/mn10300/unit-asb2305/pci-asb2305.h +++ b/arch/mn10300/unit-asb2305/pci-asb2305.h @@ -23,8 +23,6 @@ #define PCI_PROBE_BIOS 1 #define PCI_PROBE_CONF1 2 #define PCI_PROBE_CONF2 4 -#define PCI_NO_SORT 0x100 -#define PCI_BIOS_SORT 0x200 #define PCI_NO_CHECKS 0x400 #define PCI_ASSIGN_ROMS 0x1000 #define PCI_BIOS_IRQ_SCAN 0x2000 diff --git a/arch/sh/drivers/pci/pci-sh4.h b/arch/sh/drivers/pci/pci-sh4.h index 07e29506080..a83dcf70c13 100644 --- a/arch/sh/drivers/pci/pci-sh4.h +++ b/arch/sh/drivers/pci/pci-sh4.h @@ -15,8 +15,6 @@ #define PCI_PROBE_BIOS 1 #define PCI_PROBE_CONF1 2 #define PCI_PROBE_CONF2 4 -#define PCI_NO_SORT 0x100 -#define PCI_BIOS_SORT 0x200 #define PCI_NO_CHECKS 0x400 #define PCI_ASSIGN_ROMS 0x1000 #define PCI_BIOS_IRQ_SCAN 0x2000 diff --git a/arch/x86/pci/common.c b/arch/x86/pci/common.c index 7b6e3bb9b28..c9ff4ff6673 100644 --- a/arch/x86/pci/common.c +++ b/arch/x86/pci/common.c @@ -427,10 +427,6 @@ static int __init pcibios_init(void) if (pci_bf_sort >= pci_force_bf) pci_sort_breadthfirst(); -#ifdef CONFIG_PCI_BIOS - if ((pci_probe & PCI_BIOS_SORT) && !(pci_probe & PCI_NO_SORT)) - pcibios_sort(); -#endif return 0; } @@ -455,9 +451,6 @@ char * __devinit pcibios_setup(char *str) } else if (!strcmp(str, "nobios")) { pci_probe &= ~PCI_PROBE_BIOS; return NULL; - } else if (!strcmp(str, "nosort")) { - pci_probe |= PCI_NO_SORT; - return NULL; } else if (!strcmp(str, "biosirq")) { pci_probe |= PCI_BIOS_IRQ_SCAN; return NULL; diff --git a/arch/x86/pci/pcbios.c b/arch/x86/pci/pcbios.c index 2f7109ac4c1..37472fc6f72 100644 --- a/arch/x86/pci/pcbios.c +++ b/arch/x86/pci/pcbios.c @@ -152,28 +152,6 @@ static int __devinit check_pcibios(void) return 0; } -static int __devinit pci_bios_find_device (unsigned short vendor, unsigned short device_id, - unsigned short index, unsigned char *bus, unsigned char *device_fn) -{ - unsigned short bx; - unsigned short ret; - - __asm__("lcall *(%%edi); cld\n\t" - "jc 1f\n\t" - "xor %%ah, %%ah\n" - "1:" - : "=b" (bx), - "=a" (ret) - : "1" (PCIBIOS_FIND_PCI_DEVICE), - "c" (device_id), - "d" (vendor), - "S" ((int) index), - "D" (&pci_indirect)); - *bus = (bx >> 8) & 0xff; - *device_fn = bx & 0xff; - return (int) (ret & 0xff00) >> 8; -} - static int pci_bios_read(unsigned int seg, unsigned int bus, unsigned int devfn, int reg, int len, u32 *value) { @@ -363,55 +341,6 @@ static struct pci_raw_ops * __devinit pci_find_bios(void) return NULL; } -/* - * Sort the device list according to PCI BIOS. Nasty hack, but since some - * fool forgot to define the `correct' device order in the PCI BIOS specs - * and we want to be (possibly bug-to-bug ;-]) compatible with older kernels - * which used BIOS ordering, we are bound to do this... - */ - -void __devinit pcibios_sort(void) -{ - LIST_HEAD(sorted_devices); - struct list_head *ln; - struct pci_dev *dev, *d; - int idx, found; - unsigned char bus, devfn; - - DBG("PCI: Sorting device list...\n"); - while (!list_empty(&pci_devices)) { - ln = pci_devices.next; - dev = pci_dev_g(ln); - idx = found = 0; - while (pci_bios_find_device(dev->vendor, dev->device, idx, &bus, &devfn) == PCIBIOS_SUCCESSFUL) { - idx++; - list_for_each(ln, &pci_devices) { - d = pci_dev_g(ln); - if (d->bus->number == bus && d->devfn == devfn) { - list_move_tail(&d->global_list, &sorted_devices); - if (d == dev) - found = 1; - break; - } - } - if (ln == &pci_devices) { - printk(KERN_WARNING "PCI: BIOS reporting unknown device %02x:%02x\n", bus, devfn); - /* - * We must not continue scanning as several buggy BIOSes - * return garbage after the last device. Grr. - */ - break; - } - } - if (!found) { - printk(KERN_WARNING "PCI: Device %s not found by BIOS\n", - pci_name(dev)); - list_move_tail(&dev->global_list, &sorted_devices); - } - } - list_splice(&sorted_devices, &pci_devices); -} - /* * BIOS Functions for IRQ Routing */ @@ -495,7 +424,6 @@ void __init pci_pcbios_init(void) { if ((pci_probe & PCI_PROBE_BIOS) && ((raw_pci_ops = pci_find_bios()))) { - pci_probe |= PCI_BIOS_SORT; pci_bios_present = 1; } } diff --git a/arch/x86/pci/pci.h b/arch/x86/pci/pci.h index 3431518d921..02b016a9842 100644 --- a/arch/x86/pci/pci.h +++ b/arch/x86/pci/pci.h @@ -19,8 +19,6 @@ #define PCI_PROBE_MASK 0x000f #define PCI_PROBE_NOEARLY 0x0010 -#define PCI_NO_SORT 0x0100 -#define PCI_BIOS_SORT 0x0200 #define PCI_NO_CHECKS 0x0400 #define PCI_USE_PIRQ_MASK 0x0800 #define PCI_ASSIGN_ROMS 0x1000 @@ -101,7 +99,6 @@ extern int pci_direct_probe(void); extern void pci_direct_init(int type); extern void pci_pcbios_init(void); extern void pci_mmcfg_init(int type); -extern void pcibios_sort(void); /* pci-mmconfig.c */ diff --git a/include/asm-sh/mpc1211/pci.h b/include/asm-sh/mpc1211/pci.h index 5d3712c3a70..d9162c5ed76 100644 --- a/include/asm-sh/mpc1211/pci.h +++ b/include/asm-sh/mpc1211/pci.h @@ -24,8 +24,6 @@ #define PCI_PROBE_BIOS 1 #define PCI_PROBE_CONF1 2 #define PCI_PROBE_CONF2 4 -#define PCI_NO_SORT 0x100 -#define PCI_BIOS_SORT 0x200 #define PCI_NO_CHECKS 0x400 #define PCI_ASSIGN_ROMS 0x1000 #define PCI_BIOS_IRQ_SCAN 0x2000 -- cgit v1.2.3-70-g09d2 From 8a1bc9013a03d41a0e36ee413bb6f97281b30bd1 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 14 Feb 2008 14:56:56 -0800 Subject: PCI: add is_added flag to struct pci_dev This lets us check if the device is really added to the driver core or not, which is what we need when walking some of the bus lists. The flag is there in anticipation of getting rid of the other PCI device list, which is what we used to check in this situation. Signed-off-by: Greg Kroah-Hartman --- arch/powerpc/platforms/pseries/pci_dlpar.c | 7 ++----- drivers/pci/bus.c | 11 ++++------- drivers/pci/probe.c | 2 +- drivers/pci/remove.c | 6 ++---- include/linux/pci.h | 1 + 5 files changed, 10 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/arch/powerpc/platforms/pseries/pci_dlpar.c b/arch/powerpc/platforms/pseries/pci_dlpar.c index 5a5a19e40bb..d26a7bcad6b 100644 --- a/arch/powerpc/platforms/pseries/pci_dlpar.c +++ b/arch/powerpc/platforms/pseries/pci_dlpar.c @@ -88,11 +88,8 @@ pcibios_fixup_new_pci_devices(struct pci_bus *bus) struct pci_dev *dev; list_for_each_entry(dev, &bus->devices, bus_list) { - /* - * Skip already-present devices (which are on the - * global device list.) - */ - if (list_empty(&dev->global_list)) { + /* Skip already-added devices */ + if (!dev->is_added) { int i; /* Fill device archdata and setup iommu table */ diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index d708358326e..e1c079aa0e8 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -84,6 +84,7 @@ int pci_bus_add_device(struct pci_dev *dev) if (retval) return retval; + dev->is_added = 1; down_write(&pci_bus_sem); list_add_tail(&dev->global_list, &pci_devices); up_write(&pci_bus_sem); @@ -112,11 +113,8 @@ void pci_bus_add_devices(struct pci_bus *bus) int retval; list_for_each_entry(dev, &bus->devices, bus_list) { - /* - * Skip already-present devices (which are on the - * global device list.) - */ - if (!list_empty(&dev->global_list)) + /* Skip already-added devices */ + if (dev->is_added) continue; retval = pci_bus_add_device(dev); if (retval) @@ -124,8 +122,7 @@ void pci_bus_add_devices(struct pci_bus *bus) } list_for_each_entry(dev, &bus->devices, bus_list) { - - BUG_ON(list_empty(&dev->global_list)); + BUG_ON(!dev->is_added); /* * If there is an unattached subordinate bus, attach diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 387fbbb9743..7217f4283ce 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -984,7 +984,7 @@ EXPORT_SYMBOL(pci_scan_single_device); * * Scan a PCI slot on the specified PCI bus for devices, adding * discovered devices to the @bus->devices list. New devices - * will have an empty dev->global_list head. + * will not have is_added set. */ int pci_scan_slot(struct pci_bus *bus, int devfn) { diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 9684e1bde27..d3c77cbe327 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -18,13 +18,11 @@ static void pci_free_resources(struct pci_dev *dev) static void pci_stop_dev(struct pci_dev *dev) { - if (!dev->global_list.next) - return; - - if (!list_empty(&dev->global_list)) { + if (dev->is_added) { pci_proc_detach_device(dev); pci_remove_sysfs_dev_files(dev); device_unregister(&dev->dev); + dev->is_added = 0; down_write(&pci_bus_sem); list_del(&dev->global_list); dev->global_list.next = dev->global_list.prev = NULL; diff --git a/include/linux/pci.h b/include/linux/pci.h index 5f79c72bae6..5e6d0f413fb 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -181,6 +181,7 @@ struct pci_dev { unsigned int transparent:1; /* Transparent PCI bridge */ unsigned int multifunction:1;/* Part of multi-function device */ /* keep track of device state */ + unsigned int is_added:1; unsigned int is_busmaster:1; /* device is busmaster */ unsigned int no_msi:1; /* device may not use msi */ unsigned int no_d1d2:1; /* only allow d0 or d3 */ -- cgit v1.2.3-70-g09d2 From 5ff580c10ec06fd296bd23d4570c1a95194094a0 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 14 Feb 2008 14:56:56 -0800 Subject: PCI: remove global list of PCI devices This patch finally removes the global list of PCI devices. We are relying entirely on the list held in the driver core now, and do not need a separate "shadow" list as no one uses it. Signed-off-by: Greg Kroah-Hartman --- drivers/pci/bus.c | 4 ---- drivers/pci/probe.c | 39 +-------------------------------------- drivers/pci/remove.c | 4 ---- include/linux/pci.h | 3 --- 4 files changed, 1 insertion(+), 49 deletions(-) (limited to 'include') diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index e1c079aa0e8..529d9d7727b 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -85,10 +85,6 @@ int pci_bus_add_device(struct pci_dev *dev) return retval; dev->is_added = 1; - down_write(&pci_bus_sem); - list_add_tail(&dev->global_list, &pci_devices); - up_write(&pci_bus_sem); - pci_proc_attach_device(dev); pci_create_sysfs_dev_files(dev); return 0; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 7217f4283ce..504f19b2af4 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -20,8 +20,6 @@ LIST_HEAD(pci_root_buses); EXPORT_SYMBOL(pci_root_buses); -LIST_HEAD(pci_devices); - static int find_anything(struct device *dev, void *data) { @@ -860,7 +858,6 @@ struct pci_dev *alloc_pci_dev(void) if (!dev) return NULL; - INIT_LIST_HEAD(&dev->global_list); INIT_LIST_HEAD(&dev->bus_list); pci_msi_init_pci_dev(dev); @@ -957,7 +954,6 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus) * Add the device to our list of discovered devices * and the bus list for fixup functions, etc. */ - INIT_LIST_HEAD(&dev->global_list); down_write(&pci_bus_sem); list_add_tail(&dev->bus_list, &bus->devices); up_write(&pci_bus_sem); @@ -1186,7 +1182,7 @@ static void __init pci_insertion_sort_klist(struct pci_dev *a, struct list_head list_move_tail(&a->dev.knode_bus.n_node, list); } -static void __init pci_sort_breadthfirst_klist(void) +void __init pci_sort_breadthfirst(void) { LIST_HEAD(sorted_devices); struct list_head *pos, *tmp; @@ -1207,36 +1203,3 @@ static void __init pci_sort_breadthfirst_klist(void) list_splice(&sorted_devices, &device_klist->k_list); spin_unlock(&device_klist->k_lock); } - -static void __init pci_insertion_sort_devices(struct pci_dev *a, struct list_head *list) -{ - struct pci_dev *b; - - list_for_each_entry(b, list, global_list) { - if (pci_sort_bf_cmp(a, b) <= 0) { - list_move_tail(&a->global_list, &b->global_list); - return; - } - } - list_move_tail(&a->global_list, list); -} - -static void __init pci_sort_breadthfirst_devices(void) -{ - LIST_HEAD(sorted_devices); - struct pci_dev *dev, *tmp; - - down_write(&pci_bus_sem); - list_for_each_entry_safe(dev, tmp, &pci_devices, global_list) { - pci_insertion_sort_devices(dev, &sorted_devices); - } - list_splice(&sorted_devices, &pci_devices); - up_write(&pci_bus_sem); -} - -void __init pci_sort_breadthfirst(void) -{ - pci_sort_breadthfirst_devices(); - pci_sort_breadthfirst_klist(); -} - diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index d3c77cbe327..b6824833343 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -23,10 +23,6 @@ static void pci_stop_dev(struct pci_dev *dev) pci_remove_sysfs_dev_files(dev); device_unregister(&dev->dev); dev->is_added = 0; - down_write(&pci_bus_sem); - list_del(&dev->global_list); - dev->global_list.next = dev->global_list.prev = NULL; - up_write(&pci_bus_sem); } } diff --git a/include/linux/pci.h b/include/linux/pci.h index 5e6d0f413fb..3b8a4e17052 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -132,7 +132,6 @@ struct pci_cap_saved_state { * The pci_dev structure is used to describe PCI devices. */ struct pci_dev { - struct list_head global_list; /* node in list of all PCI devices */ struct list_head bus_list; /* node in per-bus list */ struct pci_bus *bus; /* bus this device is on */ struct pci_bus *subordinate; /* bus this device bridges to */ @@ -206,7 +205,6 @@ struct pci_dev { extern struct pci_dev *alloc_pci_dev(void); -#define pci_dev_g(n) list_entry(n, struct pci_dev, global_list) #define pci_dev_b(n) list_entry(n, struct pci_dev, bus_list) #define to_pci_dev(n) container_of(n, struct pci_dev, dev) #define for_each_pci_dev(d) while ((d = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, d)) != NULL) @@ -450,7 +448,6 @@ extern struct bus_type pci_bus_type; /* Do NOT directly access these two variables, unless you are arch specific pci * code, or pci core code. */ extern struct list_head pci_root_buses; /* list of all known PCI buses */ -extern struct list_head pci_devices; /* list of all devices */ /* Some device drivers need know if pci is initiated */ extern int no_pci_devices(void); -- cgit v1.2.3-70-g09d2 From 21c6847406784fde73ad5ea47c2c3434714d58d1 Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Mon, 4 Feb 2008 23:50:11 -0800 Subject: PCI: #if 0 pci_cleanup_aer_correct_error_status() #if 0 the no longer used pci_cleanup_aer_correct_error_status(). Signed-off-by: Adrian Bunk Cc: Stephen Hemminger Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- drivers/pci/pcie/aer/aerdrv_core.c | 3 ++- include/linux/aer.h | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/pci/pcie/aer/aerdrv_core.c b/drivers/pci/pcie/aer/aerdrv_core.c index 3c0d8d138f5..e84dfc8be0e 100644 --- a/drivers/pci/pcie/aer/aerdrv_core.c +++ b/drivers/pci/pcie/aer/aerdrv_core.c @@ -117,6 +117,7 @@ int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev) return 0; } +#if 0 int pci_cleanup_aer_correct_error_status(struct pci_dev *dev) { int pos; @@ -131,6 +132,7 @@ int pci_cleanup_aer_correct_error_status(struct pci_dev *dev) return 0; } +#endif /* 0 */ static int find_device_iter(struct device *device, void *data) { @@ -757,5 +759,4 @@ EXPORT_SYMBOL_GPL(pci_find_aer_capability); EXPORT_SYMBOL_GPL(pci_enable_pcie_error_reporting); EXPORT_SYMBOL_GPL(pci_disable_pcie_error_reporting); EXPORT_SYMBOL_GPL(pci_cleanup_aer_uncorrect_error_status); -EXPORT_SYMBOL_GPL(pci_cleanup_aer_correct_error_status); diff --git a/include/linux/aer.h b/include/linux/aer.h index bcf236d825e..f2518141de8 100644 --- a/include/linux/aer.h +++ b/include/linux/aer.h @@ -13,7 +13,6 @@ extern int pci_enable_pcie_error_reporting(struct pci_dev *dev); extern int pci_find_aer_capability(struct pci_dev *dev); extern int pci_disable_pcie_error_reporting(struct pci_dev *dev); extern int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev); -extern int pci_cleanup_aer_correct_error_status(struct pci_dev *dev); #else static inline int pci_enable_pcie_error_reporting(struct pci_dev *dev) { @@ -31,10 +30,6 @@ static inline int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev) { return -EINVAL; } -static inline int pci_cleanup_aer_correct_error_status(struct pci_dev *dev) -{ - return -EINVAL; -} #endif #endif //_AER_H_ -- cgit v1.2.3-70-g09d2 From 7d715a6c1ae5785d00fb9a876b5abdfc43abc44b Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Mon, 25 Feb 2008 09:46:41 +0800 Subject: PCI: add PCI Express ASPM support PCI Express ASPM defines a protocol for PCI Express components in the D0 state to reduce Link power by placing their Links into a low power state and instructing the other end of the Link to do likewise. This capability allows hardware-autonomous, dynamic Link power reduction beyond what is achievable by software-only controlled power management. However, The device should be configured by software appropriately. Enabling ASPM will save power, but will introduce device latency. This patch adds ASPM support in Linux. It introduces a global policy for ASPM, a sysfs file /sys/module/pcie_aspm/parameters/policy can control it. The interface can be used as a boot option too. Currently we have below setting: -default, BIOS default setting -powersave, highest power saving mode, enable all available ASPM state and clock power management -performance, highest performance, disable ASPM and clock power management By default, the 'default' policy is used currently. In my test, power difference between powersave mode and performance mode is about 1.3w in a system with 3 PCIE links. Note: some devices might not work well with aspm, either because chipset issue or device issue. The patch provide API (pci_disable_link_state), driver can disable ASPM for specific device. Signed-off-by: Shaohua Li Signed-off-by: Greg Kroah-Hartman --- drivers/pci/pci-sysfs.c | 5 + drivers/pci/pci.c | 4 + drivers/pci/pcie/Kconfig | 20 ++ drivers/pci/pcie/Makefile | 3 + drivers/pci/pcie/aspm.c | 811 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/pci/probe.c | 5 + drivers/pci/remove.c | 4 + include/linux/pci-aspm.h | 56 ++++ include/linux/pci.h | 5 + include/linux/pci_regs.h | 8 + 10 files changed, 921 insertions(+) create mode 100644 drivers/pci/pcie/aspm.c create mode 100644 include/linux/pci-aspm.h (limited to 'include') diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 8dcf1458aa2..f5b0b622c18 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "pci.h" static int sysfs_initialized; /* = 0 */ @@ -650,6 +651,8 @@ int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) if (pcibios_add_platform_entries(pdev)) goto err_rom_file; + pcie_aspm_create_sysfs_dev_files(pdev); + return 0; err_rom_file: @@ -679,6 +682,8 @@ void pci_remove_sysfs_dev_files(struct pci_dev *pdev) if (!sysfs_initialized) return; + pcie_aspm_remove_sysfs_dev_files(pdev); + if (pdev->cfg_size < 4096) sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr); else diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index a4445b7210b..f331feb4eb8 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -18,6 +18,7 @@ #include #include #include +#include #include /* isa_dma_bridge_buggy */ #include "pci.h" @@ -501,6 +502,9 @@ pci_set_power_state(struct pci_dev *dev, pci_power_t state) if (need_restore) pci_restore_bars(dev); + if (dev->bus->self) + pcie_aspm_pm_state_change(dev->bus->self); + return 0; } diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig index 287a9311716..25b04fb2517 100644 --- a/drivers/pci/pcie/Kconfig +++ b/drivers/pci/pcie/Kconfig @@ -26,3 +26,23 @@ config HOTPLUG_PCI_PCIE When in doubt, say N. source "drivers/pci/pcie/aer/Kconfig" + +# +# PCI Express ASPM +# +config PCIEASPM + bool "PCI Express ASPM support(Experimental)" + depends on PCI && EXPERIMENTAL && PCIEPORTBUS + default y + help + This enables PCI Express ASPM (Active State Power Management) and + Clock Power Management. ASPM supports state L0/L0s/L1. + + When in doubt, say N. +config PCIEASPM_DEBUG + bool "Debug PCI Express ASPM" + depends on PCIEASPM + default n + help + This enables PCI Express ASPM debug support. It will add per-device + interface to control ASPM. diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile index e00fb99acf4..11f6bb1eae2 100644 --- a/drivers/pci/pcie/Makefile +++ b/drivers/pci/pcie/Makefile @@ -2,6 +2,9 @@ # Makefile for PCI-Express PORT Driver # +# Build PCI Express ASPM if needed +obj-$(CONFIG_PCIEASPM) += aspm.o + pcieportdrv-y := portdrv_core.o portdrv_pci.o portdrv_bus.o obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c new file mode 100644 index 00000000000..61fedb2448b --- /dev/null +++ b/drivers/pci/pcie/aspm.c @@ -0,0 +1,811 @@ +/* + * File: drivers/pci/pcie/aspm.c + * Enabling PCIE link L0s/L1 state and Clock Power Management + * + * Copyright (C) 2007 Intel + * Copyright (C) Zhang Yanmin (yanmin.zhang@intel.com) + * Copyright (C) Shaohua Li (shaohua.li@intel.com) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../pci.h" + +#ifdef MODULE_PARAM_PREFIX +#undef MODULE_PARAM_PREFIX +#endif +#define MODULE_PARAM_PREFIX "pcie_aspm." + +struct endpoint_state { + unsigned int l0s_acceptable_latency; + unsigned int l1_acceptable_latency; +}; + +struct pcie_link_state { + struct list_head sibiling; + struct pci_dev *pdev; + + /* ASPM state */ + unsigned int support_state; + unsigned int enabled_state; + unsigned int bios_aspm_state; + /* upstream component */ + unsigned int l0s_upper_latency; + unsigned int l1_upper_latency; + /* downstream component */ + unsigned int l0s_down_latency; + unsigned int l1_down_latency; + /* Clock PM state*/ + unsigned int clk_pm_capable; + unsigned int clk_pm_enabled; + unsigned int bios_clk_state; + + /* + * A pcie downstream port only has one slot under it, so at most there + * are 8 functions + */ + struct endpoint_state endpoints[8]; +}; + +static int aspm_disabled; +static DEFINE_MUTEX(aspm_lock); +static LIST_HEAD(link_list); + +#define POLICY_DEFAULT 0 /* BIOS default setting */ +#define POLICY_PERFORMANCE 1 /* high performance */ +#define POLICY_POWERSAVE 2 /* high power saving */ +static int aspm_policy; +static const char *policy_str[] = { + [POLICY_DEFAULT] = "default", + [POLICY_PERFORMANCE] = "performance", + [POLICY_POWERSAVE] = "powersave" +}; + +static int policy_to_aspm_state(struct pci_dev *pdev) +{ + struct pcie_link_state *link_state = pdev->link_state; + + switch (aspm_policy) { + case POLICY_PERFORMANCE: + /* Disable ASPM and Clock PM */ + return 0; + case POLICY_POWERSAVE: + /* Enable ASPM L0s/L1 */ + return PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; + case POLICY_DEFAULT: + return link_state->bios_aspm_state; + } + return 0; +} + +static int policy_to_clkpm_state(struct pci_dev *pdev) +{ + struct pcie_link_state *link_state = pdev->link_state; + + switch (aspm_policy) { + case POLICY_PERFORMANCE: + /* Disable ASPM and Clock PM */ + return 0; + case POLICY_POWERSAVE: + /* Disable Clock PM */ + return 1; + case POLICY_DEFAULT: + return link_state->bios_clk_state; + } + return 0; +} + +static void pcie_set_clock_pm(struct pci_dev *pdev, int enable) +{ + struct pci_dev *child_dev; + int pos; + u16 reg16; + struct pcie_link_state *link_state = pdev->link_state; + + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP); + if (!pos) + return; + pci_read_config_word(child_dev, pos + PCI_EXP_LNKCTL, ®16); + if (enable) + reg16 |= PCI_EXP_LNKCTL_CLKREQ_EN; + else + reg16 &= ~PCI_EXP_LNKCTL_CLKREQ_EN; + pci_write_config_word(child_dev, pos + PCI_EXP_LNKCTL, reg16); + } + link_state->clk_pm_enabled = !!enable; +} + +static void pcie_check_clock_pm(struct pci_dev *pdev) +{ + int pos; + u32 reg32; + u16 reg16; + int capable = 1, enabled = 1; + struct pci_dev *child_dev; + struct pcie_link_state *link_state = pdev->link_state; + + /* All functions should have the same cap and state, take the worst */ + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP); + if (!pos) + return; + pci_read_config_dword(child_dev, pos + PCI_EXP_LNKCAP, ®32); + if (!(reg32 & PCI_EXP_LNKCAP_CLKPM)) { + capable = 0; + enabled = 0; + break; + } + pci_read_config_word(child_dev, pos + PCI_EXP_LNKCTL, ®16); + if (!(reg16 & PCI_EXP_LNKCTL_CLKREQ_EN)) + enabled = 0; + } + link_state->clk_pm_capable = capable; + link_state->clk_pm_enabled = enabled; + link_state->bios_clk_state = enabled; + pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev)); +} + +/* + * pcie_aspm_configure_common_clock: check if the 2 ends of a link + * could use common clock. If they are, configure them to use the + * common clock. That will reduce the ASPM state exit latency. + */ +static void pcie_aspm_configure_common_clock(struct pci_dev *pdev) +{ + int pos, child_pos; + u16 reg16 = 0; + struct pci_dev *child_dev; + int same_clock = 1; + + /* + * all functions of a slot should have the same Slot Clock + * Configuration, so just check one function + * */ + child_dev = list_entry(pdev->subordinate->devices.next, struct pci_dev, + bus_list); + BUG_ON(!child_dev->is_pcie); + + /* Check downstream component if bit Slot Clock Configuration is 1 */ + child_pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP); + pci_read_config_word(child_dev, child_pos + PCI_EXP_LNKSTA, ®16); + if (!(reg16 & PCI_EXP_LNKSTA_SLC)) + same_clock = 0; + + /* Check upstream component if bit Slot Clock Configuration is 1 */ + pos = pci_find_capability(pdev, PCI_CAP_ID_EXP); + pci_read_config_word(pdev, pos + PCI_EXP_LNKSTA, ®16); + if (!(reg16 & PCI_EXP_LNKSTA_SLC)) + same_clock = 0; + + /* Configure downstream component, all functions */ + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + child_pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP); + pci_read_config_word(child_dev, child_pos + PCI_EXP_LNKCTL, + ®16); + if (same_clock) + reg16 |= PCI_EXP_LNKCTL_CCC; + else + reg16 &= ~PCI_EXP_LNKCTL_CCC; + pci_write_config_word(child_dev, child_pos + PCI_EXP_LNKCTL, + reg16); + } + + /* Configure upstream component */ + pci_read_config_word(pdev, pos + PCI_EXP_LNKCTL, ®16); + if (same_clock) + reg16 |= PCI_EXP_LNKCTL_CCC; + else + reg16 &= ~PCI_EXP_LNKCTL_CCC; + pci_write_config_word(pdev, pos + PCI_EXP_LNKCTL, reg16); + + /* retrain link */ + reg16 |= PCI_EXP_LNKCTL_RL; + pci_write_config_word(pdev, pos + PCI_EXP_LNKCTL, reg16); + + /* Wait for link training end */ + while (1) { + pci_read_config_word(pdev, pos + PCI_EXP_LNKSTA, ®16); + if (!(reg16 & PCI_EXP_LNKSTA_LT)) + break; + cpu_relax(); + } +} + +/* + * calc_L0S_latency: Convert L0s latency encoding to ns + */ +static unsigned int calc_L0S_latency(unsigned int latency_encoding, int ac) +{ + unsigned int ns = 64; + + if (latency_encoding == 0x7) { + if (ac) + ns = -1U; + else + ns = 5*1000; /* > 4us */ + } else + ns *= (1 << latency_encoding); + return ns; +} + +/* + * calc_L1_latency: Convert L1 latency encoding to ns + */ +static unsigned int calc_L1_latency(unsigned int latency_encoding, int ac) +{ + unsigned int ns = 1000; + + if (latency_encoding == 0x7) { + if (ac) + ns = -1U; + else + ns = 65*1000; /* > 64us */ + } else + ns *= (1 << latency_encoding); + return ns; +} + +static void pcie_aspm_get_cap_device(struct pci_dev *pdev, u32 *state, + unsigned int *l0s, unsigned int *l1, unsigned int *enabled) +{ + int pos; + u16 reg16; + u32 reg32; + unsigned int latency; + + pos = pci_find_capability(pdev, PCI_CAP_ID_EXP); + pci_read_config_dword(pdev, pos + PCI_EXP_LNKCAP, ®32); + *state = (reg32 & PCI_EXP_LNKCAP_ASPMS) >> 10; + if (*state != PCIE_LINK_STATE_L0S && + *state != (PCIE_LINK_STATE_L1|PCIE_LINK_STATE_L0S)) + *state = 0; + if (*state == 0) + return; + + latency = (reg32 & PCI_EXP_LNKCAP_L0SEL) >> 12; + *l0s = calc_L0S_latency(latency, 0); + if (*state & PCIE_LINK_STATE_L1) { + latency = (reg32 & PCI_EXP_LNKCAP_L1EL) >> 15; + *l1 = calc_L1_latency(latency, 0); + } + pci_read_config_word(pdev, pos + PCI_EXP_LNKCTL, ®16); + *enabled = reg16 & (PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1); +} + +static void pcie_aspm_cap_init(struct pci_dev *pdev) +{ + struct pci_dev *child_dev; + u32 state, tmp; + struct pcie_link_state *link_state = pdev->link_state; + + /* upstream component states */ + pcie_aspm_get_cap_device(pdev, &link_state->support_state, + &link_state->l0s_upper_latency, + &link_state->l1_upper_latency, + &link_state->enabled_state); + /* downstream component states, all functions have the same setting */ + child_dev = list_entry(pdev->subordinate->devices.next, struct pci_dev, + bus_list); + pcie_aspm_get_cap_device(child_dev, &state, + &link_state->l0s_down_latency, + &link_state->l1_down_latency, + &tmp); + link_state->support_state &= state; + if (!link_state->support_state) + return; + link_state->enabled_state &= link_state->support_state; + link_state->bios_aspm_state = link_state->enabled_state; + + /* ENDPOINT states*/ + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + int pos; + u32 reg32; + unsigned int latency; + struct endpoint_state *ep_state = + &link_state->endpoints[PCI_FUNC(child_dev->devfn)]; + + if (child_dev->pcie_type != PCI_EXP_TYPE_ENDPOINT && + child_dev->pcie_type != PCI_EXP_TYPE_LEG_END) + continue; + + pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP); + pci_read_config_dword(child_dev, pos + PCI_EXP_DEVCAP, ®32); + latency = (reg32 & PCI_EXP_DEVCAP_L0S) >> 6; + latency = calc_L0S_latency(latency, 1); + ep_state->l0s_acceptable_latency = latency; + if (link_state->support_state & PCIE_LINK_STATE_L1) { + latency = (reg32 & PCI_EXP_DEVCAP_L1) >> 9; + latency = calc_L1_latency(latency, 1); + ep_state->l1_acceptable_latency = latency; + } + } +} + +static unsigned int __pcie_aspm_check_state_one(struct pci_dev *pdev, + unsigned int state) +{ + struct pci_dev *parent_dev, *tmp_dev; + unsigned int latency, l1_latency = 0; + struct pcie_link_state *link_state; + struct endpoint_state *ep_state; + + parent_dev = pdev->bus->self; + link_state = parent_dev->link_state; + state &= link_state->support_state; + if (state == 0) + return 0; + ep_state = &link_state->endpoints[PCI_FUNC(pdev->devfn)]; + + /* + * Check latency for endpoint device. + * TBD: The latency from the endpoint to root complex vary per + * switch's upstream link state above the device. Here we just do a + * simple check which assumes all links above the device can be in L1 + * state, that is we just consider the worst case. If switch's upstream + * link can't be put into L0S/L1, then our check is too strictly. + */ + tmp_dev = pdev; + while (state & (PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1)) { + parent_dev = tmp_dev->bus->self; + link_state = parent_dev->link_state; + if (state & PCIE_LINK_STATE_L0S) { + latency = max_t(unsigned int, + link_state->l0s_upper_latency, + link_state->l0s_down_latency); + if (latency > ep_state->l0s_acceptable_latency) + state &= ~PCIE_LINK_STATE_L0S; + } + if (state & PCIE_LINK_STATE_L1) { + latency = max_t(unsigned int, + link_state->l1_upper_latency, + link_state->l1_down_latency); + if (latency + l1_latency > + ep_state->l1_acceptable_latency) + state &= ~PCIE_LINK_STATE_L1; + } + if (!parent_dev->bus->self) /* parent_dev is a root port */ + break; + else { + /* + * parent_dev is the downstream port of a switch, make + * tmp_dev the upstream port of the switch + */ + tmp_dev = parent_dev->bus->self; + /* + * every switch on the path to root complex need 1 more + * microsecond for L1. Spec doesn't mention L0S. + */ + if (state & PCIE_LINK_STATE_L1) + l1_latency += 1000; + } + } + return state; +} + +static unsigned int pcie_aspm_check_state(struct pci_dev *pdev, + unsigned int state) +{ + struct pci_dev *child_dev; + + /* If no child, disable the link */ + if (list_empty(&pdev->subordinate->devices)) + return 0; + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + if (child_dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) { + /* + * If downstream component of a link is pci bridge, we + * disable ASPM for now for the link + * */ + state = 0; + break; + } + if ((child_dev->pcie_type != PCI_EXP_TYPE_ENDPOINT && + child_dev->pcie_type != PCI_EXP_TYPE_LEG_END)) + continue; + /* Device not in D0 doesn't need check latency */ + if (child_dev->current_state == PCI_D1 || + child_dev->current_state == PCI_D2 || + child_dev->current_state == PCI_D3hot || + child_dev->current_state == PCI_D3cold) + continue; + state = __pcie_aspm_check_state_one(child_dev, state); + } + return state; +} + +static void __pcie_aspm_config_one_dev(struct pci_dev *pdev, unsigned int state) +{ + u16 reg16; + int pos = pci_find_capability(pdev, PCI_CAP_ID_EXP); + + pci_read_config_word(pdev, pos + PCI_EXP_LNKCTL, ®16); + reg16 &= ~0x3; + reg16 |= state; + pci_write_config_word(pdev, pos + PCI_EXP_LNKCTL, reg16); +} + +static void __pcie_aspm_config_link(struct pci_dev *pdev, unsigned int state) +{ + struct pci_dev *child_dev; + int valid = 1; + struct pcie_link_state *link_state = pdev->link_state; + + /* + * if the downstream component has pci bridge function, don't do ASPM + * now + */ + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { + if (child_dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) { + valid = 0; + break; + } + } + if (!valid) + return; + + /* + * spec 2.0 suggests all functions should be configured the same + * setting for ASPM. Enabling ASPM L1 should be done in upstream + * component first and then downstream, and vice versa for disabling + * ASPM L1. Spec doesn't mention L0S. + */ + if (state & PCIE_LINK_STATE_L1) + __pcie_aspm_config_one_dev(pdev, state); + + list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) + __pcie_aspm_config_one_dev(child_dev, state); + + if (!(state & PCIE_LINK_STATE_L1)) + __pcie_aspm_config_one_dev(pdev, state); + + link_state->enabled_state = state; +} + +static void __pcie_aspm_configure_link_state(struct pci_dev *pdev, + unsigned int state) +{ + struct pcie_link_state *link_state = pdev->link_state; + + if (link_state->support_state == 0) + return; + state &= PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; + + /* state 0 means disabling aspm */ + state = pcie_aspm_check_state(pdev, state); + if (link_state->enabled_state == state) + return; + __pcie_aspm_config_link(pdev, state); +} + +/* + * pcie_aspm_configure_link_state: enable/disable PCI express link state + * @pdev: the root port or switch downstream port + */ +static void pcie_aspm_configure_link_state(struct pci_dev *pdev, + unsigned int state) +{ + down_read(&pci_bus_sem); + mutex_lock(&aspm_lock); + __pcie_aspm_configure_link_state(pdev, state); + mutex_unlock(&aspm_lock); + up_read(&pci_bus_sem); +} + +static void free_link_state(struct pci_dev *pdev) +{ + kfree(pdev->link_state); + pdev->link_state = NULL; +} + +/* + * pcie_aspm_init_link_state: Initiate PCI express link state. + * It is called after the pcie and its children devices are scaned. + * @pdev: the root port or switch downstream port + */ +void pcie_aspm_init_link_state(struct pci_dev *pdev) +{ + unsigned int state; + struct pcie_link_state *link_state; + int error = 0; + + if (aspm_disabled || !pdev->is_pcie || pdev->link_state) + return; + if (pdev->pcie_type != PCI_EXP_TYPE_ROOT_PORT && + pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM) + return; + down_read(&pci_bus_sem); + if (list_empty(&pdev->subordinate->devices)) + goto out; + + mutex_lock(&aspm_lock); + + link_state = kzalloc(sizeof(*link_state), GFP_KERNEL); + if (!link_state) + goto unlock_out; + pdev->link_state = link_state; + + pcie_aspm_configure_common_clock(pdev); + + pcie_aspm_cap_init(pdev); + + /* config link state to avoid BIOS error */ + state = pcie_aspm_check_state(pdev, policy_to_aspm_state(pdev)); + __pcie_aspm_config_link(pdev, state); + + pcie_check_clock_pm(pdev); + + link_state->pdev = pdev; + list_add(&link_state->sibiling, &link_list); + +unlock_out: + if (error) + free_link_state(pdev); + mutex_unlock(&aspm_lock); +out: + up_read(&pci_bus_sem); +} + +/* @pdev: the endpoint device */ +void pcie_aspm_exit_link_state(struct pci_dev *pdev) +{ + struct pci_dev *parent = pdev->bus->self; + struct pcie_link_state *link_state = parent->link_state; + + if (aspm_disabled || !pdev->is_pcie || !parent || !link_state) + return; + if (parent->pcie_type != PCI_EXP_TYPE_ROOT_PORT && + parent->pcie_type != PCI_EXP_TYPE_DOWNSTREAM) + return; + down_read(&pci_bus_sem); + mutex_lock(&aspm_lock); + + /* + * All PCIe functions are in one slot, remove one function will remove + * the the whole slot, so just wait + */ + if (!list_empty(&parent->subordinate->devices)) + goto out; + + /* All functions are removed, so just disable ASPM for the link */ + __pcie_aspm_config_one_dev(parent, 0); + list_del(&link_state->sibiling); + /* Clock PM is for endpoint device */ + + free_link_state(parent); +out: + mutex_unlock(&aspm_lock); + up_read(&pci_bus_sem); +} + +/* @pdev: the root port or switch downstream port */ +void pcie_aspm_pm_state_change(struct pci_dev *pdev) +{ + struct pcie_link_state *link_state = pdev->link_state; + + if (aspm_disabled || !pdev->is_pcie || !pdev->link_state) + return; + if (pdev->pcie_type != PCI_EXP_TYPE_ROOT_PORT && + pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM) + return; + /* + * devices changed PM state, we should recheck if latency meets all + * functions' requirement + */ + pcie_aspm_configure_link_state(pdev, link_state->enabled_state); +} + +/* + * pci_disable_link_state - disable pci device's link state, so the link will + * never enter specific states + */ +void pci_disable_link_state(struct pci_dev *pdev, int state) +{ + struct pci_dev *parent = pdev->bus->self; + struct pcie_link_state *link_state; + + if (aspm_disabled || !pdev->is_pcie) + return; + if (pdev->pcie_type == PCI_EXP_TYPE_ROOT_PORT || + pdev->pcie_type == PCI_EXP_TYPE_DOWNSTREAM) + parent = pdev; + if (!parent || !parent->link_state) + return; + + down_read(&pci_bus_sem); + mutex_lock(&aspm_lock); + link_state = parent->link_state; + link_state->support_state &= + ~(state & (PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1)); + if (state & PCIE_LINK_STATE_CLKPM) + link_state->clk_pm_capable = 0; + + __pcie_aspm_configure_link_state(parent, link_state->enabled_state); + if (!link_state->clk_pm_capable && link_state->clk_pm_enabled) + pcie_set_clock_pm(parent, 0); + mutex_unlock(&aspm_lock); + up_read(&pci_bus_sem); +} +EXPORT_SYMBOL(pci_disable_link_state); + +static int pcie_aspm_set_policy(const char *val, struct kernel_param *kp) +{ + int i; + struct pci_dev *pdev; + struct pcie_link_state *link_state; + + for (i = 0; i < ARRAY_SIZE(policy_str); i++) + if (!strncmp(val, policy_str[i], strlen(policy_str[i]))) + break; + if (i >= ARRAY_SIZE(policy_str)) + return -EINVAL; + if (i == aspm_policy) + return 0; + + down_read(&pci_bus_sem); + mutex_lock(&aspm_lock); + aspm_policy = i; + list_for_each_entry(link_state, &link_list, sibiling) { + pdev = link_state->pdev; + __pcie_aspm_configure_link_state(pdev, + policy_to_aspm_state(pdev)); + if (link_state->clk_pm_capable && + link_state->clk_pm_enabled != policy_to_clkpm_state(pdev)) + pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev)); + + } + mutex_unlock(&aspm_lock); + up_read(&pci_bus_sem); + return 0; +} + +static int pcie_aspm_get_policy(char *buffer, struct kernel_param *kp) +{ + int i, cnt = 0; + for (i = 0; i < ARRAY_SIZE(policy_str); i++) + if (i == aspm_policy) + cnt += sprintf(buffer + cnt, "[%s] ", policy_str[i]); + else + cnt += sprintf(buffer + cnt, "%s ", policy_str[i]); + return cnt; +} + +module_param_call(policy, pcie_aspm_set_policy, pcie_aspm_get_policy, + NULL, 0644); + +#ifdef CONFIG_PCIEASPM_DEBUG +static ssize_t link_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pci_dev *pci_device = to_pci_dev(dev); + struct pcie_link_state *link_state = pci_device->link_state; + + return sprintf(buf, "%d\n", link_state->enabled_state); +} + +static ssize_t link_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t n) +{ + struct pci_dev *pci_device = to_pci_dev(dev); + int state; + + if (n < 1) + return -EINVAL; + state = buf[0]-'0'; + if (state >= 0 && state <= 3) { + /* setup link aspm state */ + pcie_aspm_configure_link_state(pci_device, state); + return n; + } + + return -EINVAL; +} + +static ssize_t clk_ctl_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pci_dev *pci_device = to_pci_dev(dev); + struct pcie_link_state *link_state = pci_device->link_state; + + return sprintf(buf, "%d\n", link_state->clk_pm_enabled); +} + +static ssize_t clk_ctl_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t n) +{ + struct pci_dev *pci_device = to_pci_dev(dev); + int state; + + if (n < 1) + return -EINVAL; + state = buf[0]-'0'; + + down_read(&pci_bus_sem); + mutex_lock(&aspm_lock); + pcie_set_clock_pm(pci_device, !!state); + mutex_unlock(&aspm_lock); + up_read(&pci_bus_sem); + + return n; +} + +static DEVICE_ATTR(link_state, 0644, link_state_show, link_state_store); +static DEVICE_ATTR(clk_ctl, 0644, clk_ctl_show, clk_ctl_store); + +static char power_group[] = "power"; +void pcie_aspm_create_sysfs_dev_files(struct pci_dev *pdev) +{ + struct pcie_link_state *link_state = pdev->link_state; + + if (!pdev->is_pcie || (pdev->pcie_type != PCI_EXP_TYPE_ROOT_PORT && + pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM) || !link_state) + return; + + if (link_state->support_state) + sysfs_add_file_to_group(&pdev->dev.kobj, + &dev_attr_link_state.attr, power_group); + if (link_state->clk_pm_capable) + sysfs_add_file_to_group(&pdev->dev.kobj, + &dev_attr_clk_ctl.attr, power_group); +} + +void pcie_aspm_remove_sysfs_dev_files(struct pci_dev *pdev) +{ + struct pcie_link_state *link_state = pdev->link_state; + + if (!pdev->is_pcie || (pdev->pcie_type != PCI_EXP_TYPE_ROOT_PORT && + pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM) || !link_state) + return; + + if (link_state->support_state) + sysfs_remove_file_from_group(&pdev->dev.kobj, + &dev_attr_link_state.attr, power_group); + if (link_state->clk_pm_capable) + sysfs_remove_file_from_group(&pdev->dev.kobj, + &dev_attr_clk_ctl.attr, power_group); +} +#endif + +static int __init pcie_aspm_disable(char *str) +{ + aspm_disabled = 1; + return 1; +} + +__setup("pcie_noaspm", pcie_aspm_disable); + +#ifdef CONFIG_ACPI +#include +#include +static void pcie_aspm_platform_init(void) +{ + pcie_osc_support_set(OSC_ACTIVE_STATE_PWR_SUPPORT| + OSC_CLOCK_PWR_CAPABILITY_SUPPORT); +} +#else +static inline void pcie_aspm_platform_init(void) { } +#endif + +static int __init pcie_aspm_init(void) +{ + if (aspm_disabled) + return 0; + pcie_aspm_platform_init(); + return 0; +} + +fs_initcall(pcie_aspm_init); diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 07d5c7424b0..284ef392c3e 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "pci.h" #define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */ @@ -1014,6 +1015,10 @@ int pci_scan_slot(struct pci_bus *bus, int devfn) break; } } + + if (bus->self) + pcie_aspm_init_link_state(bus->self); + return nr; } diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index b6824833343..bdc2a44d68e 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -1,5 +1,6 @@ #include #include +#include #include "pci.h" static void pci_free_resources(struct pci_dev *dev) @@ -24,6 +25,9 @@ static void pci_stop_dev(struct pci_dev *dev) device_unregister(&dev->dev); dev->is_added = 0; } + + if (dev->bus->self) + pcie_aspm_exit_link_state(dev); } static void pci_destroy_dev(struct pci_dev *dev) diff --git a/include/linux/pci-aspm.h b/include/linux/pci-aspm.h new file mode 100644 index 00000000000..a1a1e618e99 --- /dev/null +++ b/include/linux/pci-aspm.h @@ -0,0 +1,56 @@ +/* + * aspm.h + * + * PCI Express ASPM defines and function prototypes + * + * Copyright (C) 2007 Intel Corp. + * Zhang Yanmin (yanmin.zhang@intel.com) + * Shaohua Li (shaohua.li@intel.com) + * + * For more information, please consult the following manuals (look at + * http://www.pcisig.com/ for how to get them): + * + * PCI Express Specification + */ + +#ifndef LINUX_ASPM_H +#define LINUX_ASPM_H + +#include + +#define PCIE_LINK_STATE_L0S 1 +#define PCIE_LINK_STATE_L1 2 +#define PCIE_LINK_STATE_CLKPM 4 + +#ifdef CONFIG_PCIEASPM +extern void pcie_aspm_init_link_state(struct pci_dev *pdev); +extern void pcie_aspm_exit_link_state(struct pci_dev *pdev); +extern void pcie_aspm_pm_state_change(struct pci_dev *pdev); +extern void pci_disable_link_state(struct pci_dev *pdev, int state); +#else +static inline void pcie_aspm_init_link_state(struct pci_dev *pdev) +{ +} +static inline void pcie_aspm_exit_link_state(struct pci_dev *pdev) +{ +} +static inline void pcie_aspm_pm_state_change(struct pci_dev *pdev) +{ +} +static inline void pci_disable_link_state(struct pci_dev *pdev, int state) +{ +} +#endif + +#ifdef CONFIG_PCIEASPM_DEBUG /* this depends on CONFIG_PCIEASPM */ +extern void pcie_aspm_create_sysfs_dev_files(struct pci_dev *pdev); +extern void pcie_aspm_remove_sysfs_dev_files(struct pci_dev *pdev); +#else +static inline void pcie_aspm_create_sysfs_dev_files(struct pci_dev *pdev) +{ +} +static inline void pcie_aspm_remove_sysfs_dev_files(struct pci_dev *pdev) +{ +} +#endif +#endif /* LINUX_ASPM_H */ diff --git a/include/linux/pci.h b/include/linux/pci.h index 3b8a4e17052..14bf3d236d1 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -128,6 +128,7 @@ struct pci_cap_saved_state { u32 data[0]; }; +struct pcie_link_state; /* * The pci_dev structure is used to describe PCI devices. */ @@ -164,6 +165,10 @@ struct pci_dev { this is D0-D3, D0 being fully functional, and D3 being off. */ +#ifdef CONFIG_PCIEASPM + struct pcie_link_state *link_state; /* ASPM link state. */ +#endif + pci_channel_state_t error_state; /* current connectivity state */ struct device dev; /* Generic device interface */ diff --git a/include/linux/pci_regs.h b/include/linux/pci_regs.h index c1914a8b94a..c0c1223c919 100644 --- a/include/linux/pci_regs.h +++ b/include/linux/pci_regs.h @@ -395,9 +395,17 @@ #define PCI_EXP_DEVSTA_AUXPD 0x10 /* AUX Power Detected */ #define PCI_EXP_DEVSTA_TRPND 0x20 /* Transactions Pending */ #define PCI_EXP_LNKCAP 12 /* Link Capabilities */ +#define PCI_EXP_LNKCAP_ASPMS 0xc00 /* ASPM Support */ +#define PCI_EXP_LNKCAP_L0SEL 0x7000 /* L0s Exit Latency */ +#define PCI_EXP_LNKCAP_L1EL 0x38000 /* L1 Exit Latency */ +#define PCI_EXP_LNKCAP_CLKPM 0x40000 /* L1 Clock Power Management */ #define PCI_EXP_LNKCTL 16 /* Link Control */ +#define PCI_EXP_LNKCTL_RL 0x20 /* Retrain Link */ +#define PCI_EXP_LNKCTL_CCC 0x40 /* Common Clock COnfiguration */ #define PCI_EXP_LNKCTL_CLKREQ_EN 0x100 /* Enable clkreq */ #define PCI_EXP_LNKSTA 18 /* Link Status */ +#define PCI_EXP_LNKSTA_LT 0x800 /* Link Training */ +#define PCI_EXP_LNKSTA_SLC 0x1000 /* Slot Clock Configuration */ #define PCI_EXP_SLTCAP 20 /* Slot Capabilities */ #define PCI_EXP_SLTCTL 24 /* Slot Control */ #define PCI_EXP_SLTSTA 26 /* Slot Status */ -- cgit v1.2.3-70-g09d2 From 842de40d93e00a5c40a1a7f520a6fbe422994e99 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 4 Mar 2008 11:56:47 -0700 Subject: PCI: add generic pci_enable_resources() Each architecture has its own pcibios_enable_resources() implementation. These differ in many minor ways that have nothing to do with actual architectural differences. Follow-on patches will make most arches use this generic version instead. This version is based on powerpc, which seemed most up-to-date. The only functional difference from the x86 version is that this uses "!r->parent" to check for resource collisions instead of "!r->start && r->end". Signed-off-by: Bjorn Helgaas Acked-by: Benjamin Herrenschmidt Acked-by: David Howells Signed-off-by: Greg Kroah-Hartman --- drivers/pci/setup-res.c | 43 +++++++++++++++++++++++++++++++++++++++++++ include/linux/pci.h | 1 + 2 files changed, 44 insertions(+) (limited to 'include') diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index 9e4d485ba9c..bad509e40fb 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -263,3 +263,46 @@ void pdev_sort_resources(struct pci_dev *dev, struct resource_list *head) } } } + +int pci_enable_resources(struct pci_dev *dev, int mask) +{ + u16 cmd, old_cmd; + int i; + struct resource *r; + + pci_read_config_word(dev, PCI_COMMAND, &cmd); + old_cmd = cmd; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + if (!(mask & (1 << i))) + continue; + + r = &dev->resource[i]; + + if (!(r->flags & (IORESOURCE_IO | IORESOURCE_MEM))) + continue; + if ((i == PCI_ROM_RESOURCE) && + (!(r->flags & IORESOURCE_ROM_ENABLE))) + continue; + + if (!r->parent) { + dev_err(&dev->dev, "device not available because of " + "BAR %d [%llx:%llx] collisions\n", i, + (unsigned long long) r->start, + (unsigned long long) r->end); + return -EINVAL; + } + + if (r->flags & IORESOURCE_IO) + cmd |= PCI_COMMAND_IO; + if (r->flags & IORESOURCE_MEM) + cmd |= PCI_COMMAND_MEMORY; + } + + if (cmd != old_cmd) { + dev_info(&dev->dev, "enabling device (%04x -> %04x)\n", + old_cmd, cmd); + pci_write_config_word(dev, PCI_COMMAND, cmd); + } + return 0; +} diff --git a/include/linux/pci.h b/include/linux/pci.h index 14bf3d236d1..e2f46b05cf8 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -624,6 +624,7 @@ int pci_claim_resource(struct pci_dev *, int); void pci_assign_unassigned_resources(void); void pdev_enable_device(struct pci_dev *); void pdev_sort_resources(struct pci_dev *, struct resource_list *); +int pci_enable_resources(struct pci_dev *, int mask); void pci_fixup_irqs(u8 (*)(struct pci_dev *, u8 *), int (*)(struct pci_dev *, u8, u8)); #define HAVE_PCI_REQ_REGIONS 2 -- cgit v1.2.3-70-g09d2 From 94e6108803469a37ee1e3c92dafdd1d59298602f Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Wed, 5 Mar 2008 16:52:39 +0000 Subject: PCI: Expose PCI VPD through sysfs Vital Product Data (VPD) may be exposed by PCI devices in several ways. It is generally unsafe to read this information through the existing interfaces to user-land because of stateful interfaces. This adds: - abstract operations for VPD access (struct pci_vpd_ops) - VPD state information in struct pci_dev (struct pci_vpd) - an implementation of the VPD access method specified in PCI 2.2 (in access.c) - a 'vpd' binary file in sysfs directories for PCI devices with VPD operations defined It adds a probe for PCI 2.2 VPD in pci_scan_device() and release of VPD state in pci_release_dev(). Signed-off-by: Ben Hutchings Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-pci | 11 +++ drivers/pci/access.c | 166 ++++++++++++++++++++++++++++++++ drivers/pci/pci-sysfs.c | 109 ++++++++++++++++++--- drivers/pci/pci.h | 19 ++++ drivers/pci/probe.c | 3 + include/linux/pci.h | 3 + 6 files changed, 297 insertions(+), 14 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-bus-pci (limited to 'include') diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci new file mode 100644 index 00000000000..ceddcff4082 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-pci @@ -0,0 +1,11 @@ +What: /sys/bus/pci/devices/.../vpd +Date: February 2008 +Contact: Ben Hutchings +Description: + A file named vpd in a device directory will be a + binary file containing the Vital Product Data for the + device. It should follow the VPD format defined in + PCI Specification 2.1 or 2.2, but users should consider + that some devices may have malformatted data. If the + underlying VPD has a writable section then the + corresponding section of this file will be writable. diff --git a/drivers/pci/access.c b/drivers/pci/access.c index fc405f0165d..ec8f7002b09 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -126,6 +127,171 @@ PCI_USER_WRITE_CONFIG(byte, u8) PCI_USER_WRITE_CONFIG(word, u16) PCI_USER_WRITE_CONFIG(dword, u32) +/* VPD access through PCI 2.2+ VPD capability */ + +#define PCI_VPD_PCI22_SIZE (PCI_VPD_ADDR_MASK + 1) + +struct pci_vpd_pci22 { + struct pci_vpd base; + spinlock_t lock; /* controls access to hardware and the flags */ + u8 cap; + bool busy; + bool flag; /* value of F bit to wait for */ +}; + +/* Wait for last operation to complete */ +static int pci_vpd_pci22_wait(struct pci_dev *dev) +{ + struct pci_vpd_pci22 *vpd = + container_of(dev->vpd, struct pci_vpd_pci22, base); + u16 flag, status; + int wait; + int ret; + + if (!vpd->busy) + return 0; + + flag = vpd->flag ? PCI_VPD_ADDR_F : 0; + wait = vpd->flag ? 10 : 1000; /* read: 100 us; write: 10 ms */ + for (;;) { + ret = pci_user_read_config_word(dev, + vpd->cap + PCI_VPD_ADDR, + &status); + if (ret < 0) + return ret; + if ((status & PCI_VPD_ADDR_F) == flag) { + vpd->busy = false; + return 0; + } + if (wait-- == 0) + return -ETIMEDOUT; + udelay(10); + } +} + +static int pci_vpd_pci22_read(struct pci_dev *dev, int pos, int size, + char *buf) +{ + struct pci_vpd_pci22 *vpd = + container_of(dev->vpd, struct pci_vpd_pci22, base); + u32 val; + int ret; + int begin, end, i; + + if (pos < 0 || pos > PCI_VPD_PCI22_SIZE || + size > PCI_VPD_PCI22_SIZE - pos) + return -EINVAL; + if (size == 0) + return 0; + + spin_lock_irq(&vpd->lock); + ret = pci_vpd_pci22_wait(dev); + if (ret < 0) + goto out; + ret = pci_user_write_config_word(dev, vpd->cap + PCI_VPD_ADDR, + pos & ~3); + if (ret < 0) + goto out; + vpd->busy = true; + vpd->flag = 1; + ret = pci_vpd_pci22_wait(dev); + if (ret < 0) + goto out; + ret = pci_user_read_config_dword(dev, vpd->cap + PCI_VPD_DATA, + &val); +out: + spin_unlock_irq(&vpd->lock); + if (ret < 0) + return ret; + + /* Convert to bytes */ + begin = pos & 3; + end = min(4, begin + size); + for (i = 0; i < end; ++i) { + if (i >= begin) + *buf++ = val; + val >>= 8; + } + return end - begin; +} + +static int pci_vpd_pci22_write(struct pci_dev *dev, int pos, int size, + const char *buf) +{ + struct pci_vpd_pci22 *vpd = + container_of(dev->vpd, struct pci_vpd_pci22, base); + u32 val; + int ret; + + if (pos < 0 || pos > PCI_VPD_PCI22_SIZE || pos & 3 || + size > PCI_VPD_PCI22_SIZE - pos || size < 4) + return -EINVAL; + + val = (u8) *buf++; + val |= ((u8) *buf++) << 8; + val |= ((u8) *buf++) << 16; + val |= ((u32)(u8) *buf++) << 24; + + spin_lock_irq(&vpd->lock); + ret = pci_vpd_pci22_wait(dev); + if (ret < 0) + goto out; + ret = pci_user_write_config_dword(dev, vpd->cap + PCI_VPD_DATA, + val); + if (ret < 0) + goto out; + ret = pci_user_write_config_word(dev, vpd->cap + PCI_VPD_ADDR, + pos | PCI_VPD_ADDR_F); + if (ret < 0) + goto out; + vpd->busy = true; + vpd->flag = 0; + ret = pci_vpd_pci22_wait(dev); +out: + spin_unlock_irq(&vpd->lock); + if (ret < 0) + return ret; + + return 4; +} + +static int pci_vpd_pci22_get_size(struct pci_dev *dev) +{ + return PCI_VPD_PCI22_SIZE; +} + +static void pci_vpd_pci22_release(struct pci_dev *dev) +{ + kfree(container_of(dev->vpd, struct pci_vpd_pci22, base)); +} + +static struct pci_vpd_ops pci_vpd_pci22_ops = { + .read = pci_vpd_pci22_read, + .write = pci_vpd_pci22_write, + .get_size = pci_vpd_pci22_get_size, + .release = pci_vpd_pci22_release, +}; + +int pci_vpd_pci22_init(struct pci_dev *dev) +{ + struct pci_vpd_pci22 *vpd; + u8 cap; + + cap = pci_find_capability(dev, PCI_CAP_ID_VPD); + if (!cap) + return -ENODEV; + vpd = kzalloc(sizeof(*vpd), GFP_ATOMIC); + if (!vpd) + return -ENOMEM; + + vpd->base.ops = &pci_vpd_pci22_ops; + spin_lock_init(&vpd->lock); + vpd->cap = cap; + vpd->busy = false; + dev->vpd = &vpd->base; + return 0; +} + /** * pci_block_user_cfg_access - Block userspace PCI config reads/writes * @dev: pci device struct diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index f5b0b622c18..ae9a7695be9 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -343,6 +343,58 @@ pci_write_config(struct kobject *kobj, struct bin_attribute *bin_attr, return count; } +static ssize_t +pci_read_vpd(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct pci_dev *dev = + to_pci_dev(container_of(kobj, struct device, kobj)); + int end; + int ret; + + if (off > bin_attr->size) + count = 0; + else if (count > bin_attr->size - off) + count = bin_attr->size - off; + end = off + count; + + while (off < end) { + ret = dev->vpd->ops->read(dev, off, end - off, buf); + if (ret < 0) + return ret; + buf += ret; + off += ret; + } + + return count; +} + +static ssize_t +pci_write_vpd(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct pci_dev *dev = + to_pci_dev(container_of(kobj, struct device, kobj)); + int end; + int ret; + + if (off > bin_attr->size) + count = 0; + else if (count > bin_attr->size - off) + count = bin_attr->size - off; + end = off + count; + + while (off < end) { + ret = dev->vpd->ops->write(dev, off, end - off, buf); + if (ret < 0) + return ret; + buf += ret; + off += ret; + } + + return count; +} + #ifdef HAVE_PCI_LEGACY /** * pci_read_legacy_io - read byte(s) from legacy I/O port space @@ -611,7 +663,7 @@ int __attribute__ ((weak)) pcibios_add_platform_entries(struct pci_dev *dev) int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) { - struct bin_attribute *rom_attr = NULL; + struct bin_attribute *attr = NULL; int retval; if (!sysfs_initialized) @@ -624,22 +676,41 @@ int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) if (retval) goto err; + /* If the device has VPD, try to expose it in sysfs. */ + if (pdev->vpd) { + attr = kzalloc(sizeof(*attr), GFP_ATOMIC); + if (attr) { + pdev->vpd->attr = attr; + attr->size = pdev->vpd->ops->get_size(pdev); + attr->attr.name = "vpd"; + attr->attr.mode = S_IRUGO | S_IWUSR; + attr->read = pci_read_vpd; + attr->write = pci_write_vpd; + retval = sysfs_create_bin_file(&pdev->dev.kobj, attr); + if (retval) + goto err_vpd; + } else { + retval = -ENOMEM; + goto err_config_file; + } + } + retval = pci_create_resource_files(pdev); if (retval) - goto err_bin_file; + goto err_vpd_file; /* If the device has a ROM, try to expose it in sysfs. */ if (pci_resource_len(pdev, PCI_ROM_RESOURCE) || (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW)) { - rom_attr = kzalloc(sizeof(*rom_attr), GFP_ATOMIC); - if (rom_attr) { - pdev->rom_attr = rom_attr; - rom_attr->size = pci_resource_len(pdev, PCI_ROM_RESOURCE); - rom_attr->attr.name = "rom"; - rom_attr->attr.mode = S_IRUSR; - rom_attr->read = pci_read_rom; - rom_attr->write = pci_write_rom; - retval = sysfs_create_bin_file(&pdev->dev.kobj, rom_attr); + attr = kzalloc(sizeof(*attr), GFP_ATOMIC); + if (attr) { + pdev->rom_attr = attr; + attr->size = pci_resource_len(pdev, PCI_ROM_RESOURCE); + attr->attr.name = "rom"; + attr->attr.mode = S_IRUSR; + attr->read = pci_read_rom; + attr->write = pci_write_rom; + retval = sysfs_create_bin_file(&pdev->dev.kobj, attr); if (retval) goto err_rom; } else { @@ -657,12 +728,18 @@ int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) err_rom_file: if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) - sysfs_remove_bin_file(&pdev->dev.kobj, rom_attr); + sysfs_remove_bin_file(&pdev->dev.kobj, pdev->rom_attr); err_rom: - kfree(rom_attr); + kfree(pdev->rom_attr); err_resource_files: pci_remove_resource_files(pdev); -err_bin_file: +err_vpd_file: + if (pdev->vpd) { + sysfs_remove_bin_file(&pdev->dev.kobj, pdev->vpd->attr); +err_vpd: + kfree(pdev->vpd->attr); + } +err_config_file: if (pdev->cfg_size < 4096) sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr); else @@ -684,6 +761,10 @@ void pci_remove_sysfs_dev_files(struct pci_dev *pdev) pcie_aspm_remove_sysfs_dev_files(pdev); + if (pdev->vpd) { + sysfs_remove_bin_file(&pdev->dev.kobj, pdev->vpd->attr); + kfree(pdev->vpd->attr); + } if (pdev->cfg_size < 4096) sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr); else diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index eabeb1f2ec9..0a497c1b422 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -18,6 +18,25 @@ extern int pci_user_write_config_byte(struct pci_dev *dev, int where, u8 val); extern int pci_user_write_config_word(struct pci_dev *dev, int where, u16 val); extern int pci_user_write_config_dword(struct pci_dev *dev, int where, u32 val); +struct pci_vpd_ops { + int (*read)(struct pci_dev *dev, int pos, int size, char *buf); + int (*write)(struct pci_dev *dev, int pos, int size, const char *buf); + int (*get_size)(struct pci_dev *dev); + void (*release)(struct pci_dev *dev); +}; + +struct pci_vpd { + struct pci_vpd_ops *ops; + struct bin_attribute *attr; /* descriptor for sysfs VPD entry */ +}; + +extern int pci_vpd_pci22_init(struct pci_dev *dev); +static inline void pci_vpd_release(struct pci_dev *dev) +{ + if (dev->vpd) + dev->vpd->ops->release(dev); +} + /* PCI /proc functions */ #ifdef CONFIG_PROC_FS extern int pci_proc_attach_device(struct pci_dev *dev); diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 284ef392c3e..c2e99fd87fa 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -794,6 +794,7 @@ static void pci_release_dev(struct device *dev) struct pci_dev *pci_dev; pci_dev = to_pci_dev(dev); + pci_vpd_release(pci_dev); kfree(pci_dev); } @@ -933,6 +934,8 @@ pci_scan_device(struct pci_bus *bus, int devfn) return NULL; } + pci_vpd_pci22_init(dev); + return dev; } diff --git a/include/linux/pci.h b/include/linux/pci.h index e2f46b05cf8..292491324b0 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -20,6 +20,8 @@ /* Include the pci register defines */ #include +struct pci_vpd; + /* * The PCI interface treats multi-function devices as independent * devices. The slot/function address of each device is encoded @@ -206,6 +208,7 @@ struct pci_dev { #ifdef CONFIG_PCI_MSI struct list_head msi_list; #endif + struct pci_vpd *vpd; }; extern struct pci_dev *alloc_pci_dev(void); -- cgit v1.2.3-70-g09d2 From 884525655d07fdee9245716b998ecdc45cdd8007 Mon Sep 17 00:00:00 2001 From: Ivan Kokshaysky Date: Sun, 30 Mar 2008 19:50:14 +0400 Subject: PCI: clean up resource alignment management Done per Linus' request and suggestions. Linus has explained that better than I'll be able to explain: On Thu, Mar 27, 2008 at 10:12:10AM -0700, Linus Torvalds wrote: > Actually, before we go any further, there might be a less intrusive > alternative: add just a couple of flags to the resource flags field (we > still have something like 8 unused bits on 32-bit), and use those to > implement a generic "resource_alignment()" routine. > > Two flags would do it: > > - IORESOURCE_SIZEALIGN: size indicates alignment (regular PCI device > resources) > > - IORESOURCE_STARTALIGN: start field is alignment (PCI bus resources > during probing) > > and then the case of both flags zero (or both bits set) would actually be > "invalid", and we would also clear the IORESOURCE_STARTALIGN flag when we > actually allocate the resource (so that we don't use the "start" field as > alignment incorrectly when it no longer indicates alignment). > > That wouldn't be totally generic, but it would have the nice property of > automatically at least add sanity checking for that whole "res->start has > the odd meaning of 'alignment' during probing" and remove the need for a > new field, and it would allow us to have a generic "resource_alignment()" > routine that just gets a resource pointer. Besides, I removed IORESOURCE_BUS_HAS_VGA flag which was unused for ages. Signed-off-by: Ivan Kokshaysky Cc: Linus Torvalds Cc: Gary Hade Signed-off-by: Greg Kroah-Hartman --- drivers/pci/probe.c | 5 +++-- drivers/pci/setup-bus.c | 3 +++ drivers/pci/setup-res.c | 42 +++++++++++++++++++++++------------------- include/linux/ioport.h | 5 ++++- kernel/resource.c | 18 ++++++++++++++++++ 5 files changed, 51 insertions(+), 22 deletions(-) (limited to 'include') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index c2e99fd87fa..33d9b8bea6e 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -235,7 +235,7 @@ static void pci_read_bases(struct pci_dev *dev, unsigned int howmany, int rom) res->flags |= l & ~PCI_BASE_ADDRESS_IO_MASK; } res->end = res->start + (unsigned long) sz; - res->flags |= pci_calc_resource_flags(l); + res->flags |= pci_calc_resource_flags(l) | IORESOURCE_SIZEALIGN; if (is_64bit_memory(l)) { u32 szhi, lhi; @@ -288,7 +288,8 @@ static void pci_read_bases(struct pci_dev *dev, unsigned int howmany, int rom) if (sz) { res->flags = (l & IORESOURCE_ROM_ENABLE) | IORESOURCE_MEM | IORESOURCE_PREFETCH | - IORESOURCE_READONLY | IORESOURCE_CACHEABLE; + IORESOURCE_READONLY | IORESOURCE_CACHEABLE | + IORESOURCE_SIZEALIGN; res->start = l & PCI_ROM_ADDRESS_MASK; res->end = res->start + (unsigned long) sz; } diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index f7cb8e0758b..5cf84568c9e 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -65,6 +65,7 @@ static void pbus_assign_resources_sorted(struct pci_bus *bus) res = list->res; idx = res - &list->dev->resource[0]; if (pci_assign_resource(list->dev, idx)) { + /* FIXME: get rid of this */ res->start = 0; res->end = 0; res->flags = 0; @@ -327,6 +328,7 @@ static void pbus_size_io(struct pci_bus *bus) /* Alignment of the IO window is always 4K */ b_res->start = 4096; b_res->end = b_res->start + size - 1; + b_res->flags |= IORESOURCE_STARTALIGN; } /* Calculate the size of the bus and minimal alignment which @@ -401,6 +403,7 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, unsigned long } b_res->start = min_align; b_res->end = size + min_align - 1; + b_res->flags |= IORESOURCE_STARTALIGN; return 1; } diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index bad509e40fb..7d35cdf4579 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -137,10 +137,16 @@ int pci_assign_resource(struct pci_dev *dev, int resno) size = res->end - res->start + 1; min = (res->flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM; - /* The bridge resources are special, as their - size != alignment. Sizing routines return - required alignment in the "start" field. */ - align = (resno < PCI_BRIDGE_RESOURCES) ? size : res->start; + + align = resource_alignment(res); + if (!align) { + printk(KERN_ERR "PCI: Cannot allocate resource (bogus " + "alignment) %d [%llx:%llx] (flags %lx) of %s\n", + resno, (unsigned long long)res->start, + (unsigned long long)res->end, res->flags, + pci_name(dev)); + return -EINVAL; + } /* First, try exact prefetching match.. */ ret = pci_bus_alloc_resource(bus, res, size, align, min, @@ -164,8 +170,10 @@ int pci_assign_resource(struct pci_dev *dev, int resno) res->flags & IORESOURCE_IO ? "I/O" : "mem", resno, (unsigned long long)size, (unsigned long long)res->start, pci_name(dev)); - } else if (resno < PCI_BRIDGE_RESOURCES) { - pci_update_resource(dev, res, resno); + } else { + res->flags &= ~IORESOURCE_STARTALIGN; + if (resno < PCI_BRIDGE_RESOURCES) + pci_update_resource(dev, res, resno); } return ret; @@ -226,29 +234,25 @@ void pdev_sort_resources(struct pci_dev *dev, struct resource_list *head) if (r->flags & IORESOURCE_PCI_FIXED) continue; - r_align = r->end - r->start; - if (!(r->flags) || r->parent) continue; + + r_align = resource_alignment(r); if (!r_align) { - printk(KERN_WARNING "PCI: Ignore bogus resource %d " - "[%llx:%llx] of %s\n", + printk(KERN_WARNING "PCI: bogus alignment of resource " + "%d [%llx:%llx] (flags %lx) of %s\n", i, (unsigned long long)r->start, - (unsigned long long)r->end, pci_name(dev)); + (unsigned long long)r->end, r->flags, + pci_name(dev)); continue; } - r_align = (i < PCI_BRIDGE_RESOURCES) ? r_align + 1 : r->start; for (list = head; ; list = list->next) { resource_size_t align = 0; struct resource_list *ln = list->next; - int idx; - if (ln) { - idx = ln->res - &ln->dev->resource[0]; - align = (idx < PCI_BRIDGE_RESOURCES) ? - ln->res->end - ln->res->start + 1 : - ln->res->start; - } + if (ln) + align = resource_alignment(ln->res); + if (r_align > align) { tmp = kmalloc(sizeof(*tmp), GFP_KERNEL); if (!tmp) diff --git a/include/linux/ioport.h b/include/linux/ioport.h index 605d237364d..d5d40a9f792 100644 --- a/include/linux/ioport.h +++ b/include/linux/ioport.h @@ -44,7 +44,9 @@ struct resource_list { #define IORESOURCE_CACHEABLE 0x00004000 #define IORESOURCE_RANGELENGTH 0x00008000 #define IORESOURCE_SHADOWABLE 0x00010000 -#define IORESOURCE_BUS_HAS_VGA 0x00080000 + +#define IORESOURCE_SIZEALIGN 0x00020000 /* size indicates alignment */ +#define IORESOURCE_STARTALIGN 0x00040000 /* start field is alignment */ #define IORESOURCE_DISABLED 0x10000000 #define IORESOURCE_UNSET 0x20000000 @@ -110,6 +112,7 @@ extern int allocate_resource(struct resource *root, struct resource *new, void *alignf_data); int adjust_resource(struct resource *res, resource_size_t start, resource_size_t size); +resource_size_t resource_alignment(struct resource *res); /* Convenience shorthand with allocation */ #define request_region(start,n,name) __request_region(&ioport_resource, (start), (n), (name)) diff --git a/kernel/resource.c b/kernel/resource.c index 82aea814d40..cee12cc47ca 100644 --- a/kernel/resource.c +++ b/kernel/resource.c @@ -486,6 +486,24 @@ int adjust_resource(struct resource *res, resource_size_t start, resource_size_t EXPORT_SYMBOL(adjust_resource); +/** + * resource_alignment - calculate resource's alignment + * @res: resource pointer + * + * Returns alignment on success, 0 (invalid alignment) on failure. + */ +resource_size_t resource_alignment(struct resource *res) +{ + switch (res->flags & (IORESOURCE_SIZEALIGN | IORESOURCE_STARTALIGN)) { + case IORESOURCE_SIZEALIGN: + return res->end - res->start + 1; + case IORESOURCE_STARTALIGN: + return res->start; + default: + return 0; + } +} + /* * This is compatibility stuff for IO resources. * -- cgit v1.2.3-70-g09d2