From c89ac4433030fa95a453b14857358cc6ec2e30db Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 31 Mar 2015 19:38:38 -0700 Subject: PCI: Fix typo in Thunderbolt kernel message Fix a typo in one of the kernel messages. Signed-off-by: Darrick J. Wong Signed-off-by: Bjorn Helgaas --- drivers/pci/quirks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 85f247e28a80..c619fce20957 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3182,7 +3182,7 @@ static void quirk_apple_wait_for_thunderbolt(struct pci_dev *dev) || nhi->subsystem_vendor != 0x2222 || nhi->subsystem_device != 0x1111) goto out; - dev_info(&dev->dev, "quirk: wating for thunderbolt to reestablish pci tunnels...\n"); + dev_info(&dev->dev, "quirk: waiting for thunderbolt to reestablish PCI tunnels...\n"); device_pm_wait_for_dev(&dev->dev, &nhi->dev); out: pci_dev_put(nhi); -- cgit v1.2.3 From 18e94a3384368340d438cf67fcaebd72084f34f8 Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Wed, 25 Mar 2015 14:31:41 +0800 Subject: PCI: Make a shareable UUID for PCI firmware ACPI _DSM The PCI Firmware Specification, r3.0, sec 4.6.4.1.3, defines a single UUID for an ACPI _DSM method to provide device-specific control functions. This _DSM method support several functions, including PCI Express Slot Information, PCI Express Slot Number, PCI Bus Capabilities, etc. Move the UUID definition from pci/pci-label.c, where it could be used only for one function, to pci/pci-acpi.c where it can be shared for all these functions. [bhelgaas: changelog] Signed-off-by: Aaron Lu Signed-off-by: Bjorn Helgaas Acked-by: Rafael J. Wysocki --- drivers/pci/pci-acpi.c | 9 +++++++++ drivers/pci/pci-label.c | 11 ++--------- include/linux/pci-acpi.h | 3 +++ 3 files changed, 14 insertions(+), 9 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 489063987325..bea6be4992c3 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -18,6 +18,15 @@ #include #include "pci.h" +/* + * The UUID is defined in the PCI Firmware Specification available here: + * https://www.pcisig.com/members/downloads/pcifw_r3_1_13Dec10.pdf + */ +const u8 pci_acpi_dsm_uuid[] = { + 0xd0, 0x37, 0xc9, 0xe5, 0x53, 0x35, 0x7a, 0x4d, + 0x91, 0x17, 0xea, 0x4d, 0x19, 0xc3, 0x43, 0x4d +}; + phys_addr_t acpi_pci_root_get_mcfg_addr(acpi_handle handle) { acpi_status status = AE_NOT_EXIST; diff --git a/drivers/pci/pci-label.c b/drivers/pci/pci-label.c index 2ab1b47c7651..024b5c179348 100644 --- a/drivers/pci/pci-label.c +++ b/drivers/pci/pci-label.c @@ -31,8 +31,6 @@ #include #include "pci.h" -#define DEVICE_LABEL_DSM 0x07 - #ifdef CONFIG_DMI enum smbios_attr_enum { SMBIOS_ATTR_NONE = 0, @@ -148,11 +146,6 @@ static inline void pci_remove_smbiosname_file(struct pci_dev *pdev) #endif #ifdef CONFIG_ACPI -static const char device_label_dsm_uuid[] = { - 0xD0, 0x37, 0xC9, 0xE5, 0x53, 0x35, 0x7A, 0x4D, - 0x91, 0x17, 0xEA, 0x4D, 0x19, 0xC3, 0x43, 0x4D -}; - enum acpi_attr_enum { ACPI_ATTR_LABEL_SHOW, ACPI_ATTR_INDEX_SHOW, @@ -179,7 +172,7 @@ static int dsm_get_label(struct device *dev, char *buf, if (!handle) return -1; - obj = acpi_evaluate_dsm(handle, device_label_dsm_uuid, 0x2, + obj = acpi_evaluate_dsm(handle, pci_acpi_dsm_uuid, 0x2, DEVICE_LABEL_DSM, NULL); if (!obj) return -1; @@ -219,7 +212,7 @@ static bool device_has_dsm(struct device *dev) if (!handle) return false; - return !!acpi_check_dsm(handle, device_label_dsm_uuid, 0x2, + return !!acpi_check_dsm(handle, pci_acpi_dsm_uuid, 0x2, 1 << DEVICE_LABEL_DSM); } diff --git a/include/linux/pci-acpi.h b/include/linux/pci-acpi.h index 24c7728ca681..3801c704a945 100644 --- a/include/linux/pci-acpi.h +++ b/include/linux/pci-acpi.h @@ -77,6 +77,9 @@ static inline void acpiphp_remove_slots(struct pci_bus *bus) { } static inline void acpiphp_check_host_bridge(struct acpi_device *adev) { } #endif +extern const u8 pci_acpi_dsm_uuid[]; +#define DEVICE_LABEL_DSM 0x07 + #else /* CONFIG_ACPI */ static inline void acpi_pci_add_bus(struct pci_bus *bus) { } static inline void acpi_pci_remove_bus(struct pci_bus *bus) { } -- cgit v1.2.3 From 3390e0850b711d163b35508464cfbe0a4477dfe2 Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Wed, 25 Mar 2015 14:32:25 +0800 Subject: PCI: Export pci_find_host_bridge() for use inside PCI core The find_pci_host_bridge() function can be useful to other PCI code so export it. Change its name to pci_find_host_bridge(). Signed-off-by: Aaron Lu Signed-off-by: Bjorn Helgaas --- drivers/pci/host-bridge.c | 6 +++--- drivers/pci/pci.h | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/host-bridge.c b/drivers/pci/host-bridge.c index 39b2dbe585aa..30211c617400 100644 --- a/drivers/pci/host-bridge.c +++ b/drivers/pci/host-bridge.c @@ -16,7 +16,7 @@ static struct pci_bus *find_pci_root_bus(struct pci_bus *bus) return bus; } -static struct pci_host_bridge *find_pci_host_bridge(struct pci_bus *bus) +struct pci_host_bridge *pci_find_host_bridge(struct pci_bus *bus) { struct pci_bus *root_bus = find_pci_root_bus(bus); @@ -34,7 +34,7 @@ void pci_set_host_bridge_release(struct pci_host_bridge *bridge, void pcibios_resource_to_bus(struct pci_bus *bus, struct pci_bus_region *region, struct resource *res) { - struct pci_host_bridge *bridge = find_pci_host_bridge(bus); + struct pci_host_bridge *bridge = pci_find_host_bridge(bus); struct resource_entry *window; resource_size_t offset = 0; @@ -59,7 +59,7 @@ static bool region_contains(struct pci_bus_region *region1, void pcibios_bus_to_resource(struct pci_bus *bus, struct resource *res, struct pci_bus_region *region) { - struct pci_host_bridge *bridge = find_pci_host_bridge(bus); + struct pci_host_bridge *bridge = pci_find_host_bridge(bus); struct resource_entry *window; resource_size_t offset = 0; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 4091f82239cd..d72f849174a4 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -321,4 +321,6 @@ static inline int pci_dev_specific_reset(struct pci_dev *dev, int probe) } #endif +struct pci_host_bridge *pci_find_host_bridge(struct pci_bus *bus); + #endif /* DRIVERS_PCI_H */ -- cgit v1.2.3 From e33caa82e221b0bbeba373b507e3f134dc2efa1a Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Wed, 25 Mar 2015 14:37:06 +0800 Subject: PCI/ACPI: Optimize device state transition delays The PCI "ACPI additions for FW latency optimizations" ECN (link below) defines two functions in the PCI _DSM: Function 8, "Reset Delay," applies to the entire hierarchy below a PCI host bridge. If it returns one, the OS may assume that all devices in the hierarchy have already completed power-on reset delays. Function 9, "Device Readiness Durations," applies only to the object where it is located. It returns delay durations required after various events if the device requires less time than the spec requires. Delays from this function take precedence over the Reset Delay function. Add support for Reset Delay and part of Device Readiness Durations. [bhelgaas: changelog, comments] Link: https://www.pcisig.com/specifications/conventional/pci_firmware/ECN_fw_latency_optimization_final.pdf Signed-off-by: Aaron Lu Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-acpi.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/pci-acpi.h | 2 ++ include/linux/pci.h | 1 + 3 files changed, 77 insertions(+) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index bea6be4992c3..5eba74716900 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -537,11 +537,32 @@ static struct pci_platform_pm_ops acpi_pci_platform_pm = { void acpi_pci_add_bus(struct pci_bus *bus) { + union acpi_object *obj; + struct pci_host_bridge *bridge; + if (acpi_pci_disabled || !bus->bridge) return; acpi_pci_slot_enumerate(bus); acpiphp_enumerate_slots(bus); + + /* + * For a host bridge, check its _DSM for function 8 and if + * that is available, mark it in pci_host_bridge. + */ + if (!pci_is_root_bus(bus)) + return; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(bus->bridge), pci_acpi_dsm_uuid, 3, + RESET_DELAY_DSM, NULL); + if (!obj) + return; + + if (obj->type == ACPI_TYPE_INTEGER && obj->integer.value == 1) { + bridge = pci_find_host_bridge(bus); + bridge->ignore_reset_delay = 1; + } + ACPI_FREE(obj); } void acpi_pci_remove_bus(struct pci_bus *bus) @@ -567,6 +588,57 @@ static struct acpi_device *acpi_pci_find_companion(struct device *dev) check_children); } +/** + * pci_acpi_optimize_delay - optimize PCI D3 and D3cold delay from ACPI + * @pdev: the PCI device whose delay is to be updated + * @adev: the companion ACPI device of this PCI device + * + * Update the d3_delay and d3cold_delay of a PCI device from the ACPI _DSM + * control method of either the device itself or the PCI host bridge. + * + * Function 8, "Reset Delay," applies to the entire hierarchy below a PCI + * host bridge. If it returns one, the OS may assume that all devices in + * the hierarchy have already completed power-on reset delays. + * + * Function 9, "Device Readiness Durations," applies only to the object + * where it is located. It returns delay durations required after various + * events if the device requires less time than the spec requires. Delays + * from this function take precedence over the Reset Delay function. + * + * These _DSM functions are defined by the draft ECN of January 28, 2014, + * titled "ACPI additions for FW latency optimizations." + */ +static void pci_acpi_optimize_delay(struct pci_dev *pdev, + acpi_handle handle) +{ + struct pci_host_bridge *bridge = pci_find_host_bridge(pdev->bus); + int value; + union acpi_object *obj, *elements; + + if (bridge->ignore_reset_delay) + pdev->d3cold_delay = 0; + + obj = acpi_evaluate_dsm(handle, pci_acpi_dsm_uuid, 3, + FUNCTION_DELAY_DSM, NULL); + if (!obj) + return; + + if (obj->type == ACPI_TYPE_PACKAGE && obj->package.count == 5) { + elements = obj->package.elements; + if (elements[0].type == ACPI_TYPE_INTEGER) { + value = (int)elements[0].integer.value / 1000; + if (value < PCI_PM_D3COLD_WAIT) + pdev->d3cold_delay = value; + } + if (elements[3].type == ACPI_TYPE_INTEGER) { + value = (int)elements[3].integer.value / 1000; + if (value < PCI_PM_D3_WAIT) + pdev->d3_delay = value; + } + } + ACPI_FREE(obj); +} + static void pci_acpi_setup(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); @@ -575,6 +647,8 @@ static void pci_acpi_setup(struct device *dev) if (!adev) return; + pci_acpi_optimize_delay(pci_dev, adev->handle); + pci_acpi_add_pm_notifier(adev, pci_dev); if (!adev->wakeup.flags.valid) return; diff --git a/include/linux/pci-acpi.h b/include/linux/pci-acpi.h index 3801c704a945..a965efa52152 100644 --- a/include/linux/pci-acpi.h +++ b/include/linux/pci-acpi.h @@ -79,6 +79,8 @@ static inline void acpiphp_check_host_bridge(struct acpi_device *adev) { } extern const u8 pci_acpi_dsm_uuid[]; #define DEVICE_LABEL_DSM 0x07 +#define RESET_DELAY_DSM 0x08 +#define FUNCTION_DELAY_DSM 0x09 #else /* CONFIG_ACPI */ static inline void acpi_pci_add_bus(struct pci_bus *bus) { } diff --git a/include/linux/pci.h b/include/linux/pci.h index 211e9da8a7d7..c5aa7b8a40c1 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -406,6 +406,7 @@ struct pci_host_bridge { struct list_head windows; /* resource_entry */ void (*release_fn)(struct pci_host_bridge *); void *release_data; + unsigned int ignore_reset_delay:1; /* for entire hierarchy */ }; #define to_pci_host_bridge(n) container_of(n, struct pci_host_bridge, dev) -- cgit v1.2.3 From 387d37577fdd05e9472c20885464c2a53b3c945f Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Tue, 7 Apr 2015 11:07:00 -0700 Subject: PCI: Don't clear ASPM bits when the FADT declares it's unsupported Communications with a hardware vendor confirm that the expected behaviour on systems that set the FADT ASPM disable bit but which still grant full PCIe control is for the OS to leave any BIOS configuration intact and refuse to touch the ASPM bits. This mimics the behaviour of Windows. Signed-off-by: Matthew Garrett Signed-off-by: Bjorn Helgaas --- drivers/acpi/pci_root.c | 19 ++++++++----------- drivers/pci/pcie/aspm.c | 18 ------------------ include/linux/pci-aspm.h | 4 ---- 3 files changed, 8 insertions(+), 33 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 68a5f712cd19..1b5569c092c6 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -423,8 +423,7 @@ out: } EXPORT_SYMBOL(acpi_pci_osc_control_set); -static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm, - int *clear_aspm) +static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm) { u32 support, control, requested; acpi_status status; @@ -495,10 +494,12 @@ static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm, decode_osc_control(root, "OS now controls", control); if (acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_ASPM) { /* - * We have ASPM control, but the FADT indicates - * that it's unsupported. Clear it. + * We have ASPM control, but the FADT indicates that + * it's unsupported. Leave existing configuration + * intact and prevent the OS from touching it. */ - *clear_aspm = 1; + dev_info(&device->dev, "FADT indicates ASPM is unsupported, using BIOS configuration\n"); + *no_aspm = 1; } } else { decode_osc_control(root, "OS requested", requested); @@ -525,7 +526,7 @@ static int acpi_pci_root_add(struct acpi_device *device, int result; struct acpi_pci_root *root; acpi_handle handle = device->handle; - int no_aspm = 0, clear_aspm = 0; + int no_aspm = 0; bool hotadd = system_state != SYSTEM_BOOTING; root = kzalloc(sizeof(struct acpi_pci_root), GFP_KERNEL); @@ -584,7 +585,7 @@ static int acpi_pci_root_add(struct acpi_device *device, root->mcfg_addr = acpi_pci_root_get_mcfg_addr(handle); - negotiate_os_control(root, &no_aspm, &clear_aspm); + negotiate_os_control(root, &no_aspm); /* * TBD: Need PCI interface for enumeration/configuration of roots. @@ -607,10 +608,6 @@ static int acpi_pci_root_add(struct acpi_device *device, goto remove_dmar; } - if (clear_aspm) { - dev_info(&device->dev, "Disabling ASPM (FADT indicates it is unsupported)\n"); - pcie_clear_aspm(root->bus); - } if (no_aspm) pcie_no_aspm(); diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 820740a22e94..7d4fcdc512aa 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -782,24 +782,6 @@ void pci_disable_link_state(struct pci_dev *pdev, int state) } EXPORT_SYMBOL(pci_disable_link_state); -void pcie_clear_aspm(struct pci_bus *bus) -{ - struct pci_dev *child; - - if (aspm_force) - return; - - /* - * Clear any ASPM setup that the firmware has carried out on this bus - */ - list_for_each_entry(child, &bus->devices, bus_list) { - __pci_disable_link_state(child, PCIE_LINK_STATE_L0S | - PCIE_LINK_STATE_L1 | - PCIE_LINK_STATE_CLKPM, - false, true); - } -} - static int pcie_aspm_set_policy(const char *val, struct kernel_param *kp) { int i; diff --git a/include/linux/pci-aspm.h b/include/linux/pci-aspm.h index 8af4610c2e41..207c561fb40e 100644 --- a/include/linux/pci-aspm.h +++ b/include/linux/pci-aspm.h @@ -29,7 +29,6 @@ void pcie_aspm_pm_state_change(struct pci_dev *pdev); void pcie_aspm_powersave_config_link(struct pci_dev *pdev); void pci_disable_link_state(struct pci_dev *pdev, int state); void pci_disable_link_state_locked(struct pci_dev *pdev, int state); -void pcie_clear_aspm(struct pci_bus *bus); void pcie_no_aspm(void); #else static inline void pcie_aspm_init_link_state(struct pci_dev *pdev) @@ -47,9 +46,6 @@ static inline void pcie_aspm_powersave_config_link(struct pci_dev *pdev) static inline void pci_disable_link_state(struct pci_dev *pdev, int state) { } -static inline void pcie_clear_aspm(struct pci_bus *bus) -{ -} static inline void pcie_no_aspm(void) { } -- cgit v1.2.3 From 55db3208f238ee776f1f747734051723d8a3c13e Mon Sep 17 00:00:00 2001 From: "Sean O. Stalley" Date: Thu, 2 Apr 2015 14:10:19 -0700 Subject: PCI: Read capability list as dwords, not bytes Reading both the capability ID and "next" pointer at the same time lets us parse the list with half the number of config reads. Signed-off-by: Sean O. Stalley Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 81f06e8dcc04..3c84cc6bc75a 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -145,19 +145,22 @@ static int __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn, u8 pos, int cap, int *ttl) { u8 id; + u16 ent; + + pci_bus_read_config_byte(bus, devfn, pos, &pos); while ((*ttl)--) { - pci_bus_read_config_byte(bus, devfn, pos, &pos); if (pos < 0x40) break; pos &= ~3; - pci_bus_read_config_byte(bus, devfn, pos + PCI_CAP_LIST_ID, - &id); + pci_bus_read_config_word(bus, devfn, pos, &ent); + + id = ent & 0xff; if (id == 0xff) break; if (id == cap) return pos; - pos += PCI_CAP_LIST_NEXT; + pos = (ent >> 8); } return 0; } -- cgit v1.2.3