From d3252ace0bc652a1a244455556b6a549f969bf99 Mon Sep 17 00:00:00 2001 From: Christian König Date: Fri, 29 Jun 2018 19:54:55 -0500 Subject: PCI: Restore resized BAR state on resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resize BARs after resume to the expected size again. BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=199959 Fixes: d6895ad39f3b ("drm/amdgpu: resize VRAM BAR for CPU access v6") Fixes: 276b738deb5b ("PCI: Add resizable BAR infrastructure") Signed-off-by: Christian König Signed-off-by: Bjorn Helgaas CC: stable@vger.kernel.org # v4.15+ --- drivers/pci/pci.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 97acba712e4e..44ccfb31363e 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1171,6 +1171,33 @@ static void pci_restore_config_space(struct pci_dev *pdev) } } +static void pci_restore_rebar_state(struct pci_dev *pdev) +{ + unsigned int pos, nbars, i; + u32 ctrl; + + pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); + if (!pos) + return; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); + nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >> + PCI_REBAR_CTRL_NBAR_SHIFT; + + for (i = 0; i < nbars; i++, pos += 8) { + struct resource *res; + int bar_idx, size; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); + bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; + res = pdev->resource + bar_idx; + size = order_base_2((resource_size(res) >> 20) | 1) - 1; + ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; + ctrl |= size << 8; + pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); + } +} + /** * pci_restore_state - Restore the saved state of a PCI device * @dev: - PCI device that we're dealing with @@ -1186,6 +1213,7 @@ void pci_restore_state(struct pci_dev *dev) pci_restore_pri_state(dev); pci_restore_ats_state(dev); pci_restore_vc_state(dev); + pci_restore_rebar_state(dev); pci_cleanup_aer_error_status_regs(dev); -- cgit v1.2.3 From b1277a226d8c519b8c33e23fe68b4e1658f15963 Mon Sep 17 00:00:00 2001 From: Christian König Date: Fri, 29 Jun 2018 19:55:03 -0500 Subject: PCI: Cleanup PCI_REBAR_CTRL_BAR_SHIFT handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleanup PCI_REBAR_CTRL_BAR_SHIFT handling. That was hard coded instead of properly defined in the header for some reason. Signed-off-by: Christian König Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 6 +++--- include/uapi/linux/pci_regs.h | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 44ccfb31363e..1b20c4392f09 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1193,7 +1193,7 @@ static void pci_restore_rebar_state(struct pci_dev *pdev) res = pdev->resource + bar_idx; size = order_base_2((resource_size(res) >> 20) | 1) - 1; ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; - ctrl |= size << 8; + ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT; pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); } } @@ -3098,7 +3098,7 @@ int pci_rebar_get_current_size(struct pci_dev *pdev, int bar) return pos; pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); - return (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> 8; + return (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> PCI_REBAR_CTRL_BAR_SHIFT; } /** @@ -3121,7 +3121,7 @@ int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size) pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; - ctrl |= size << 8; + ctrl |= size << PCI_REBAR_CTRL_BAR_SHIFT; pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); return 0; } diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 4da87e2ef8a8..82e6b361204e 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -960,8 +960,9 @@ #define PCI_REBAR_CTRL 8 /* control register */ #define PCI_REBAR_CTRL_BAR_IDX 0x00000007 /* BAR index */ #define PCI_REBAR_CTRL_NBAR_MASK 0x000000E0 /* # of resizable BARs */ -#define PCI_REBAR_CTRL_NBAR_SHIFT 5 /* shift for # of BARs */ +#define PCI_REBAR_CTRL_NBAR_SHIFT 5 /* shift for # of BARs */ #define PCI_REBAR_CTRL_BAR_SIZE 0x00001F00 /* BAR size */ +#define PCI_REBAR_CTRL_BAR_SHIFT 8 /* shift for BAR size */ /* Dynamic Power Allocation */ #define PCI_DPA_CAP 4 /* capability register */ -- cgit v1.2.3 From 11eb0e0e8dea8b97cff972b09cf6fb033b729dff Mon Sep 17 00:00:00 2001 From: Sinan Kaya Date: Mon, 4 Jun 2018 22:16:09 -0400 Subject: PCI: Make early dump functionality generic Move early dump functionality into common code so that it is available for all architectures. No need to carry arch-specific reads around as the read hooks are already initialized by the time pci_setup_device() is getting called during scan. Tested-by: Andy Shevchenko Signed-off-by: Sinan Kaya Signed-off-by: Bjorn Helgaas Reviewed-by: Andy Shevchenko --- Documentation/admin-guide/kernel-parameters.txt | 2 +- arch/x86/include/asm/pci-direct.h | 4 --- arch/x86/kernel/setup.c | 5 --- arch/x86/pci/common.c | 4 --- arch/x86/pci/early.c | 44 ------------------------- drivers/pci/pci.c | 5 +++ drivers/pci/pci.h | 1 + drivers/pci/probe.c | 17 ++++++++++ 8 files changed, 24 insertions(+), 58 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index efc7aa7a0670..f11b9485ed7f 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -2995,7 +2995,7 @@ See also Documentation/blockdev/paride.txt. pci=option[,option...] [PCI] various PCI subsystem options: - earlydump [X86] dump PCI config space before the kernel + earlydump dump PCI config space before the kernel changes anything off [X86] don't probe for the PCI bus bios [X86-32] force use of PCI BIOS, don't access diff --git a/arch/x86/include/asm/pci-direct.h b/arch/x86/include/asm/pci-direct.h index e1084f71a295..94597a3cf3d0 100644 --- a/arch/x86/include/asm/pci-direct.h +++ b/arch/x86/include/asm/pci-direct.h @@ -15,8 +15,4 @@ extern void write_pci_config_byte(u8 bus, u8 slot, u8 func, u8 offset, u8 val); extern void write_pci_config_16(u8 bus, u8 slot, u8 func, u8 offset, u16 val); extern int early_pci_allowed(void); - -extern unsigned int pci_early_dump_regs; -extern void early_dump_pci_device(u8 bus, u8 slot, u8 func); -extern void early_dump_pci_devices(void); #endif /* _ASM_X86_PCI_DIRECT_H */ diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c index 2f86d883dd95..480f250b3c4f 100644 --- a/arch/x86/kernel/setup.c +++ b/arch/x86/kernel/setup.c @@ -991,11 +991,6 @@ void __init setup_arch(char **cmdline_p) setup_clear_cpu_cap(X86_FEATURE_APIC); } -#ifdef CONFIG_PCI - if (pci_early_dump_regs) - early_dump_pci_devices(); -#endif - e820__reserve_setup_data(); e820__finish_early_params(); diff --git a/arch/x86/pci/common.c b/arch/x86/pci/common.c index 563049c483a1..d4ec117c1142 100644 --- a/arch/x86/pci/common.c +++ b/arch/x86/pci/common.c @@ -22,7 +22,6 @@ unsigned int pci_probe = PCI_PROBE_BIOS | PCI_PROBE_CONF1 | PCI_PROBE_CONF2 | PCI_PROBE_MMCONF; -unsigned int pci_early_dump_regs; static int pci_bf_sort; int pci_routeirq; int noioapicquirk; @@ -599,9 +598,6 @@ char *__init pcibios_setup(char *str) pci_probe |= PCI_BIG_ROOT_WINDOW; return NULL; #endif - } else if (!strcmp(str, "earlydump")) { - pci_early_dump_regs = 1; - return NULL; } else if (!strcmp(str, "routeirq")) { pci_routeirq = 1; return NULL; diff --git a/arch/x86/pci/early.c b/arch/x86/pci/early.c index e5f753cbb1c3..f5fc953e5848 100644 --- a/arch/x86/pci/early.c +++ b/arch/x86/pci/early.c @@ -57,47 +57,3 @@ int early_pci_allowed(void) PCI_PROBE_CONF1; } -void early_dump_pci_device(u8 bus, u8 slot, u8 func) -{ - u32 value[256 / 4]; - int i; - - pr_info("pci 0000:%02x:%02x.%d config space:\n", bus, slot, func); - - for (i = 0; i < 256; i += 4) - value[i / 4] = read_pci_config(bus, slot, func, i); - - print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, value, 256, false); -} - -void early_dump_pci_devices(void) -{ - unsigned bus, slot, func; - - if (!early_pci_allowed()) - return; - - for (bus = 0; bus < 256; bus++) { - for (slot = 0; slot < 32; slot++) { - for (func = 0; func < 8; func++) { - u32 class; - u8 type; - - class = read_pci_config(bus, slot, func, - PCI_CLASS_REVISION); - if (class == 0xffffffff) - continue; - - early_dump_pci_device(bus, slot, func); - - if (func == 0) { - type = read_pci_config_byte(bus, slot, - func, - PCI_HEADER_TYPE); - if (!(type & 0x80)) - break; - } - } - } - } -} diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 1b20c4392f09..e1b0bbd05fa3 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -115,6 +115,9 @@ static bool pcie_ari_disabled; /* If set, the PCIe ATS capability will not be used. */ static bool pcie_ats_disabled; +/* If set, the PCI config space of each device is printed during boot. */ +bool pci_early_dump; + bool pci_ats_disabled(void) { return pcie_ats_disabled; @@ -5833,6 +5836,8 @@ static int __init pci_setup(char *str) pcie_ats_disabled = true; } else if (!strcmp(str, "noaer")) { pci_no_aer(); + } else if (!strcmp(str, "earlydump")) { + pci_early_dump = true; } else if (!strncmp(str, "realloc=", 8)) { pci_realloc_get_opt(str + 8); } else if (!strncmp(str, "realloc", 7)) { diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index c358e7a07f3f..c33265e02c3a 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -7,6 +7,7 @@ #define PCI_VSEC_ID_INTEL_TBT 0x1234 /* Thunderbolt */ extern const unsigned char pcie_link_speed[]; +extern bool pci_early_dump; bool pcie_cap_has_lnkctl(const struct pci_dev *dev); diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index ac876e32de4b..84034a685f83 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1549,6 +1549,20 @@ static int pci_intx_mask_broken(struct pci_dev *dev) return 0; } +static void early_dump_pci_device(struct pci_dev *pdev) +{ + u32 value[256 / 4]; + int i; + + pci_info(pdev, "config space:\n"); + + for (i = 0; i < 256; i += 4) + pci_read_config_dword(pdev, i, &value[i / 4]); + + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1, + value, 256, false); +} + /** * pci_setup_device - Fill in class and map information of a device * @dev: the device structure to fill @@ -1598,6 +1612,9 @@ int pci_setup_device(struct pci_dev *dev) pci_printk(KERN_DEBUG, dev, "[%04x:%04x] type %02x class %#08x\n", dev->vendor, dev->device, dev->hdr_type, dev->class); + if (pci_early_dump) + early_dump_pci_device(dev); + /* Need to have dev->class ready */ dev->cfg_size = pci_cfg_space_size(dev); -- cgit v1.2.3 From f778a0d26fe3912424401e7b997155094de36487 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Wed, 30 May 2018 14:13:11 -0600 Subject: PCI: Expand documentation for pci_add_dma_alias() Seeing there's been some confusion about the use of pci_add_dma_alias(), expand the comment to describe why it must be called early and how early it must be called. Also, expand on the purpose of this function and common reasons it would be used. [The comment was reworded to some extent by Alex Williamson] Signed-off-by: Logan Gunthorpe Signed-off-by: Bjorn Helgaas Cc: Alex Williamson Cc: Doug Meyer --- drivers/pci/pci.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 97acba712e4e..ea4a9d3be9f9 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -5387,8 +5387,19 @@ int pci_set_vga_state(struct pci_dev *dev, bool decode, * @dev: the PCI device for which alias is added * @devfn: alias slot and function * - * This helper encodes 8-bit devfn as bit number in dma_alias_mask. - * It should be called early, preferably as PCI fixup header quirk. + * This helper encodes an 8-bit devfn as a bit number in dma_alias_mask + * which is used to program permissible bus-devfn source addresses for DMA + * requests in an IOMMU. These aliases factor into IOMMU group creation + * and are useful for devices generating DMA requests beyond or different + * from their logical bus-devfn. Examples include device quirks where the + * device simply uses the wrong devfn, as well as non-transparent bridges + * where the alias may be a proxy for devices in another domain. + * + * IOMMU group creation is performed during device discovery or addition, + * prior to any potential DMA mapping and therefore prior to driver probing + * (especially for userspace assigned devices where IOMMU group definition + * cannot be left as a userspace activity). DMA aliases should therefore + * be configured via quirks, such as the PCI fixup header quirk. */ void pci_add_dma_alias(struct pci_dev *dev, u8 devfn) { -- cgit v1.2.3 From d6488ac19aabcc6c85a74b69eaf1b7301124c323 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Thu, 5 Jul 2018 09:56:00 -0500 Subject: PCI: Mark fall-through switch cases before enabling -Wimplicit-fallthrough In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Warning level 2 was used: -Wimplicit-fallthrough=2 Signed-off-by: Gustavo A. R. Silva Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/pciehp_ctrl.c | 2 ++ drivers/pci/hotplug/shpchp_ctrl.c | 2 ++ drivers/pci/pci.c | 1 + drivers/pci/quirks.c | 1 + 4 files changed, 6 insertions(+) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index c684faa43387..cfc46e82ceca 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -436,6 +436,7 @@ int pciehp_sysfs_enable_slot(struct slot *p_slot) switch (p_slot->state) { case BLINKINGON_STATE: cancel_delayed_work(&p_slot->work); + /* fall through */ case STATIC_STATE: p_slot->state = POWERON_STATE; mutex_unlock(&p_slot->lock); @@ -473,6 +474,7 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot) switch (p_slot->state) { case BLINKINGOFF_STATE: cancel_delayed_work(&p_slot->work); + /* fall through */ case STATIC_STATE: p_slot->state = POWEROFF_STATE; mutex_unlock(&p_slot->lock); diff --git a/drivers/pci/hotplug/shpchp_ctrl.c b/drivers/pci/hotplug/shpchp_ctrl.c index 1047b56e5730..1267dcc5a531 100644 --- a/drivers/pci/hotplug/shpchp_ctrl.c +++ b/drivers/pci/hotplug/shpchp_ctrl.c @@ -654,6 +654,7 @@ int shpchp_sysfs_enable_slot(struct slot *p_slot) switch (p_slot->state) { case BLINKINGON_STATE: cancel_delayed_work(&p_slot->work); + /* fall through */ case STATIC_STATE: p_slot->state = POWERON_STATE; mutex_unlock(&p_slot->lock); @@ -689,6 +690,7 @@ int shpchp_sysfs_disable_slot(struct slot *p_slot) switch (p_slot->state) { case BLINKINGOFF_STATE: cancel_delayed_work(&p_slot->work); + /* fall through */ case STATIC_STATE: p_slot->state = POWEROFF_STATE; mutex_unlock(&p_slot->lock); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 97acba712e4e..f5c6ab14fb31 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2045,6 +2045,7 @@ static pci_power_t pci_target_state(struct pci_dev *dev, bool wakeup) case PCI_D2: if (pci_no_d1d2(dev)) break; + /* else: fall through */ default: target_state = state; } diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index f439de848658..502275c092ae 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -2105,6 +2105,7 @@ static void quirk_netmos(struct pci_dev *dev) if (dev->subsystem_vendor == PCI_VENDOR_ID_IBM && dev->subsystem_device == 0x0299) return; + /* else: fall through */ case PCI_DEVICE_ID_NETMOS_9735: case PCI_DEVICE_ID_NETMOS_9745: case PCI_DEVICE_ID_NETMOS_9845: -- cgit v1.2.3 From 1842623850d09b0b1147d4974573aa305658d97f Mon Sep 17 00:00:00 2001 From: Sinan Kaya Date: Thu, 19 Jul 2018 18:04:09 -0500 Subject: PCI: Handle error return from pci_reset_bridge_secondary_bus() Commit 01fd61c0b9bd ("PCI: Add a return type for pci_reset_bridge_secondary_bus()") added a return value to the function to return if a device is accessible following a reset. Callers are not checking the value. Pass error code up high in the stack if device is not accessible. Fixes: 01fd61c0b9bd ("PCI: Add a return type for pci_reset_bridge_secondary_bus()") Signed-off-by: Sinan Kaya Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/pciehp_hpc.c | 5 +++-- drivers/pci/pci.c | 12 ++++++------ drivers/pci/pcie/aer.c | 5 +++-- drivers/pci/pcie/err.c | 6 ++++-- 4 files changed, 16 insertions(+), 12 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index 718b6073afad..bbaa2114f953 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -728,6 +728,7 @@ int pciehp_reset_slot(struct slot *slot, int probe) struct controller *ctrl = slot->ctrl; struct pci_dev *pdev = ctrl_dev(ctrl); u16 stat_mask = 0, ctrl_mask = 0; + int rc; if (probe) return 0; @@ -745,7 +746,7 @@ int pciehp_reset_slot(struct slot *slot, int probe) if (pciehp_poll_mode) del_timer_sync(&ctrl->poll_timer); - pci_reset_bridge_secondary_bus(ctrl->pcie->port); + rc = pci_reset_bridge_secondary_bus(ctrl->pcie->port); pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, stat_mask); pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask); @@ -753,7 +754,7 @@ int pciehp_reset_slot(struct slot *slot, int probe) pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask); if (pciehp_poll_mode) int_poll_timeout(&ctrl->poll_timer); - return 0; + return rc; } int pcie_init_notification(struct controller *ctrl) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 97acba712e4e..98d149070205 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4253,9 +4253,7 @@ static int pci_parent_bus_reset(struct pci_dev *dev, int probe) if (probe) return 0; - pci_reset_bridge_secondary_bus(dev->bus->self); - - return 0; + return pci_reset_bridge_secondary_bus(dev->bus->self); } static int pci_reset_hotplug_slot(struct hotplug_slot *hotplug, int probe) @@ -4850,6 +4848,8 @@ EXPORT_SYMBOL_GPL(pci_try_reset_slot); static int pci_bus_reset(struct pci_bus *bus, int probe) { + int ret; + if (!bus->self || !pci_bus_resetable(bus)) return -ENOTTY; @@ -4860,11 +4860,11 @@ static int pci_bus_reset(struct pci_bus *bus, int probe) might_sleep(); - pci_reset_bridge_secondary_bus(bus->self); + ret = pci_reset_bridge_secondary_bus(bus->self); pci_bus_unlock(bus); - return 0; + return ret; } /** @@ -4924,7 +4924,7 @@ int pci_try_reset_bus(struct pci_bus *bus) if (pci_bus_trylock(bus)) { might_sleep(); - pci_reset_bridge_secondary_bus(bus->self); + rc = pci_reset_bridge_secondary_bus(bus->self); pci_bus_unlock(bus); } else rc = -EAGAIN; diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index a2e88386af28..bde723db3d65 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1305,6 +1305,7 @@ static pci_ers_result_t aer_root_reset(struct pci_dev *dev) { u32 reg32; int pos; + int rc; pos = dev->aer_cap; @@ -1313,7 +1314,7 @@ static pci_ers_result_t aer_root_reset(struct pci_dev *dev) reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK; pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, reg32); - pci_reset_bridge_secondary_bus(dev); + rc = pci_reset_bridge_secondary_bus(dev); pci_printk(KERN_DEBUG, dev, "Root Port link has been reset\n"); /* Clear Root Error Status */ @@ -1325,7 +1326,7 @@ static pci_ers_result_t aer_root_reset(struct pci_dev *dev) reg32 |= ROOT_PORT_INTR_ON_MESG_MASK; pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, reg32); - return PCI_ERS_RESULT_RECOVERED; + return rc ? PCI_ERS_RESULT_DISCONNECT : PCI_ERS_RESULT_RECOVERED; } /** diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c index f7ce0cb0b0b7..03075cff86f4 100644 --- a/drivers/pci/pcie/err.c +++ b/drivers/pci/pcie/err.c @@ -175,9 +175,11 @@ out: */ static pci_ers_result_t default_reset_link(struct pci_dev *dev) { - pci_reset_bridge_secondary_bus(dev); + int rc; + + rc = pci_reset_bridge_secondary_bus(dev); pci_printk(KERN_DEBUG, dev, "downstream link has been reset\n"); - return PCI_ERS_RESULT_RECOVERED; + return rc ? PCI_ERS_RESULT_DISCONNECT : PCI_ERS_RESULT_RECOVERED; } static pci_ers_result_t reset_link(struct pci_dev *dev, u32 service) -- cgit v1.2.3 From 381634cad15b711e033a2638d558232b60f753f6 Mon Sep 17 00:00:00 2001 From: Sinan Kaya Date: Thu, 19 Jul 2018 18:04:11 -0500 Subject: PCI: Hide pci_reset_bridge_secondary_bus() from drivers Rename pci_reset_bridge_secondary_bus() to pci_bridge_secondary_bus_reset() and move the declaration from linux/pci.h to drivers/pci.h to be used internally in PCI directory only. Signed-off-by: Sinan Kaya Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/pciehp_hpc.c | 2 +- drivers/pci/pci.c | 11 +++++------ drivers/pci/pci.h | 1 + drivers/pci/pcie/aer.c | 2 +- drivers/pci/pcie/err.c | 2 +- include/linux/pci.h | 1 - 6 files changed, 9 insertions(+), 10 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index bbaa2114f953..8dae23221344 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -746,7 +746,7 @@ int pciehp_reset_slot(struct slot *slot, int probe) if (pciehp_poll_mode) del_timer_sync(&ctrl->poll_timer); - rc = pci_reset_bridge_secondary_bus(ctrl->pcie->port); + rc = pci_bridge_secondary_bus_reset(ctrl->pcie->port); pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, stat_mask); pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 98d149070205..236220cb0f77 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4224,19 +4224,18 @@ void __weak pcibios_reset_secondary_bus(struct pci_dev *dev) } /** - * pci_reset_bridge_secondary_bus - Reset the secondary bus on a PCI bridge. + * pci_bridge_secondary_bus_reset - Reset the secondary bus on a PCI bridge. * @dev: Bridge device * * Use the bridge control register to assert reset on the secondary bus. * Devices on the secondary bus are left in power-on state. */ -int pci_reset_bridge_secondary_bus(struct pci_dev *dev) +int pci_bridge_secondary_bus_reset(struct pci_dev *dev) { pcibios_reset_secondary_bus(dev); return pci_dev_wait(dev, "bus reset", PCIE_RESET_READY_POLL_MS); } -EXPORT_SYMBOL_GPL(pci_reset_bridge_secondary_bus); static int pci_parent_bus_reset(struct pci_dev *dev, int probe) { @@ -4253,7 +4252,7 @@ static int pci_parent_bus_reset(struct pci_dev *dev, int probe) if (probe) return 0; - return pci_reset_bridge_secondary_bus(dev->bus->self); + return pci_bridge_secondary_bus_reset(dev->bus->self); } static int pci_reset_hotplug_slot(struct hotplug_slot *hotplug, int probe) @@ -4860,7 +4859,7 @@ static int pci_bus_reset(struct pci_bus *bus, int probe) might_sleep(); - ret = pci_reset_bridge_secondary_bus(bus->self); + ret = pci_bridge_secondary_bus_reset(bus->self); pci_bus_unlock(bus); @@ -4924,7 +4923,7 @@ int pci_try_reset_bus(struct pci_bus *bus) if (pci_bus_trylock(bus)) { might_sleep(); - rc = pci_reset_bridge_secondary_bus(bus->self); + rc = pci_bridge_secondary_bus_reset(bus->self); pci_bus_unlock(bus); } else rc = -EAGAIN; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index c358e7a07f3f..f784263ad587 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -33,6 +33,7 @@ int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vmai, enum pci_mmap_api mmap_api); int pci_probe_reset_function(struct pci_dev *dev); +int pci_bridge_secondary_bus_reset(struct pci_dev *dev); /** * struct pci_platform_pm_ops - Firmware PM callbacks diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index bde723db3d65..8c12efca9259 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1314,7 +1314,7 @@ static pci_ers_result_t aer_root_reset(struct pci_dev *dev) reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK; pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, reg32); - rc = pci_reset_bridge_secondary_bus(dev); + rc = pci_bridge_secondary_bus_reset(dev); pci_printk(KERN_DEBUG, dev, "Root Port link has been reset\n"); /* Clear Root Error Status */ diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c index 03075cff86f4..ae72f88d3ca2 100644 --- a/drivers/pci/pcie/err.c +++ b/drivers/pci/pcie/err.c @@ -177,7 +177,7 @@ static pci_ers_result_t default_reset_link(struct pci_dev *dev) { int rc; - rc = pci_reset_bridge_secondary_bus(dev); + rc = pci_bridge_secondary_bus_reset(dev); pci_printk(KERN_DEBUG, dev, "downstream link has been reset\n"); return rc ? PCI_ERS_RESULT_DISCONNECT : PCI_ERS_RESULT_RECOVERED; } diff --git a/include/linux/pci.h b/include/linux/pci.h index 6ba818449095..03520ff23d73 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1103,7 +1103,6 @@ int pci_reset_bus(struct pci_bus *bus); int pci_try_reset_bus(struct pci_bus *bus); void pci_reset_secondary_bus(struct pci_dev *dev); void pcibios_reset_secondary_bus(struct pci_dev *dev); -int pci_reset_bridge_secondary_bus(struct pci_dev *dev); void pci_update_resource(struct pci_dev *dev, int resno); int __must_check pci_assign_resource(struct pci_dev *dev, int i); int __must_check pci_reassign_resource(struct pci_dev *dev, int i, resource_size_t add_size, resource_size_t align); -- cgit v1.2.3 From 811c5cb37df46b0cd714dbd053d19cdb97d08cff Mon Sep 17 00:00:00 2001 From: Sinan Kaya Date: Thu, 19 Jul 2018 18:04:12 -0500 Subject: PCI: Unify try slot and bus reset API Drivers are expected to call pci_try_reset_slot() or pci_try_reset_bus() by querying if a system supports hotplug or not. A survey showed that most drivers don't do this and we are leaking hotplug capability to the user. Hide pci_try_slot_reset() from drivers and embed into pci_try_bus_reset(). Change pci_try_reset_bus() parameter from struct pci_bus to struct pci_dev. Signed-off-by: Sinan Kaya Signed-off-by: Bjorn Helgaas --- drivers/infiniband/hw/hfi1/pcie.c | 2 +- drivers/pci/pci.c | 21 ++++++++++++++++----- drivers/vfio/pci/vfio_pci.c | 6 ++---- include/linux/pci.h | 3 +-- 4 files changed, 20 insertions(+), 12 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/infiniband/hw/hfi1/pcie.c b/drivers/infiniband/hw/hfi1/pcie.c index 4570c4dc93d9..df4f2d390be8 100644 --- a/drivers/infiniband/hw/hfi1/pcie.c +++ b/drivers/infiniband/hw/hfi1/pcie.c @@ -905,7 +905,7 @@ static int trigger_sbr(struct hfi1_devdata *dd) * delay after a reset is required. Per spec requirements, * the link is either working or not after that point. */ - return pci_try_reset_bus(dev->bus); + return pci_try_reset_bus(dev); } /* diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 236220cb0f77..a31e6dbf21c3 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4817,12 +4817,12 @@ int pci_reset_slot(struct pci_slot *slot) EXPORT_SYMBOL_GPL(pci_reset_slot); /** - * pci_try_reset_slot - Try to reset a PCI slot + * __pci_try_reset_slot - Try to reset a PCI slot * @slot: PCI slot to reset * * Same as above except return -EAGAIN if the slot cannot be locked */ -int pci_try_reset_slot(struct pci_slot *slot) +static int __pci_try_reset_slot(struct pci_slot *slot) { int rc; @@ -4843,7 +4843,6 @@ int pci_try_reset_slot(struct pci_slot *slot) return rc; } -EXPORT_SYMBOL_GPL(pci_try_reset_slot); static int pci_bus_reset(struct pci_bus *bus, int probe) { @@ -4906,12 +4905,12 @@ int pci_reset_bus(struct pci_bus *bus) EXPORT_SYMBOL_GPL(pci_reset_bus); /** - * pci_try_reset_bus - Try to reset a PCI bus + * __pci_try_reset_bus - Try to reset a PCI bus * @bus: top level PCI bus to reset * * Same as above except return -EAGAIN if the bus cannot be locked */ -int pci_try_reset_bus(struct pci_bus *bus) +static int __pci_try_reset_bus(struct pci_bus *bus) { int rc; @@ -4932,6 +4931,18 @@ int pci_try_reset_bus(struct pci_bus *bus) return rc; } + +/** + * pci_try_reset_bus - Try to reset a PCI bus + * @pdev: top level PCI device to reset via slot/bus + * + * Same as above except return -EAGAIN if the bus cannot be locked + */ +int pci_try_reset_bus(struct pci_dev *pdev) +{ + return pci_probe_reset_slot(pdev->slot) ? + __pci_try_reset_slot(pdev->slot) : __pci_try_reset_bus(pdev->bus); +} EXPORT_SYMBOL_GPL(pci_try_reset_bus); /** diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c index b423a309a6e0..71018ec9065b 100644 --- a/drivers/vfio/pci/vfio_pci.c +++ b/drivers/vfio/pci/vfio_pci.c @@ -1010,8 +1010,7 @@ reset_info_exit: &info, slot); if (!ret) /* User has access, do the reset */ - ret = slot ? pci_try_reset_slot(vdev->pdev->slot) : - pci_try_reset_bus(vdev->pdev->bus); + ret = pci_try_reset_bus(vdev->pdev); hot_reset_release: for (i--; i >= 0; i--) @@ -1373,8 +1372,7 @@ static void vfio_pci_try_bus_reset(struct vfio_pci_device *vdev) } if (needs_reset) - ret = slot ? pci_try_reset_slot(vdev->pdev->slot) : - pci_try_reset_bus(vdev->pdev->bus); + ret = pci_try_reset_bus(vdev->pdev); put_devs: for (i = 0; i < devs.cur_index; i++) { diff --git a/include/linux/pci.h b/include/linux/pci.h index 03520ff23d73..19fb82559145 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1097,10 +1097,9 @@ int pci_reset_function_locked(struct pci_dev *dev); int pci_try_reset_function(struct pci_dev *dev); int pci_probe_reset_slot(struct pci_slot *slot); int pci_reset_slot(struct pci_slot *slot); -int pci_try_reset_slot(struct pci_slot *slot); int pci_probe_reset_bus(struct pci_bus *bus); int pci_reset_bus(struct pci_bus *bus); -int pci_try_reset_bus(struct pci_bus *bus); +int pci_try_reset_bus(struct pci_dev *dev); void pci_reset_secondary_bus(struct pci_dev *dev); void pcibios_reset_secondary_bus(struct pci_dev *dev); void pci_update_resource(struct pci_dev *dev, int resno); -- cgit v1.2.3 From fe32e2fa656c29d5d25f959f8e6168ac405d9ab4 Mon Sep 17 00:00:00 2001 From: Sinan Kaya Date: Thu, 19 Jul 2018 18:04:14 -0500 Subject: PCI: Deprecate pci_reset_bus() and pci_reset_slot() functions pci_reset_bus() and pci_reset_slot() functions are not being used by any code. Remove them from the kernel in favor of pci_try_reset_bus() and pci_try_reset_slot() functions. Signed-off-by: Sinan Kaya Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 53 +---------------------------------------------------- include/linux/pci.h | 2 -- 2 files changed, 1 insertion(+), 54 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index a31e6dbf21c3..8ee9f386c1ee 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4784,7 +4784,7 @@ int pci_probe_reset_slot(struct pci_slot *slot) EXPORT_SYMBOL_GPL(pci_probe_reset_slot); /** - * pci_reset_slot - reset a PCI slot + * __pci_try_reset_slot - Try to reset a PCI slot * @slot: PCI slot to reset * * A PCI bus may host multiple slots, each slot may support a reset mechanism @@ -4796,30 +4796,6 @@ EXPORT_SYMBOL_GPL(pci_probe_reset_slot); * through this function. PCI config space of all devices in the slot and * behind the slot is saved before and restored after reset. * - * Return 0 on success, non-zero on error. - */ -int pci_reset_slot(struct pci_slot *slot) -{ - int rc; - - rc = pci_slot_reset(slot, 1); - if (rc) - return rc; - - pci_slot_save_and_disable(slot); - - rc = pci_slot_reset(slot, 0); - - pci_slot_restore(slot); - - return rc; -} -EXPORT_SYMBOL_GPL(pci_reset_slot); - -/** - * __pci_try_reset_slot - Try to reset a PCI slot - * @slot: PCI slot to reset - * * Same as above except return -EAGAIN if the slot cannot be locked */ static int __pci_try_reset_slot(struct pci_slot *slot) @@ -4877,33 +4853,6 @@ int pci_probe_reset_bus(struct pci_bus *bus) } EXPORT_SYMBOL_GPL(pci_probe_reset_bus); -/** - * pci_reset_bus - reset a PCI bus - * @bus: top level PCI bus to reset - * - * Do a bus reset on the given bus and any subordinate buses, saving - * and restoring state of all devices. - * - * Return 0 on success, non-zero on error. - */ -int pci_reset_bus(struct pci_bus *bus) -{ - int rc; - - rc = pci_bus_reset(bus, 1); - if (rc) - return rc; - - pci_bus_save_and_disable(bus); - - rc = pci_bus_reset(bus, 0); - - pci_bus_restore(bus); - - return rc; -} -EXPORT_SYMBOL_GPL(pci_reset_bus); - /** * __pci_try_reset_bus - Try to reset a PCI bus * @bus: top level PCI bus to reset diff --git a/include/linux/pci.h b/include/linux/pci.h index 19fb82559145..cf53ecb50789 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1096,9 +1096,7 @@ int pci_reset_function(struct pci_dev *dev); int pci_reset_function_locked(struct pci_dev *dev); int pci_try_reset_function(struct pci_dev *dev); int pci_probe_reset_slot(struct pci_slot *slot); -int pci_reset_slot(struct pci_slot *slot); int pci_probe_reset_bus(struct pci_bus *bus); -int pci_reset_bus(struct pci_bus *bus); int pci_try_reset_bus(struct pci_dev *dev); void pci_reset_secondary_bus(struct pci_dev *dev); void pcibios_reset_secondary_bus(struct pci_dev *dev); -- cgit v1.2.3 From c6a44ba950d147e15fe6dab6455a52f91d8fe625 Mon Sep 17 00:00:00 2001 From: Sinan Kaya Date: Thu, 19 Jul 2018 18:04:15 -0500 Subject: PCI: Rename pci_try_reset_bus() to pci_reset_bus() Now that the old implementation of pci_reset_bus() is gone, replace pci_try_reset_bus() with pci_reset_bus(). Compared to the old implementation, new code will fail immmediately with -EAGAIN if object lock cannot be obtained. Signed-off-by: Sinan Kaya Signed-off-by: Bjorn Helgaas --- drivers/infiniband/hw/hfi1/pcie.c | 2 +- drivers/pci/pci.c | 16 ++++++++-------- drivers/vfio/pci/vfio_pci.c | 4 ++-- include/linux/pci.h | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/infiniband/hw/hfi1/pcie.c b/drivers/infiniband/hw/hfi1/pcie.c index df4f2d390be8..baf7c324f7b8 100644 --- a/drivers/infiniband/hw/hfi1/pcie.c +++ b/drivers/infiniband/hw/hfi1/pcie.c @@ -905,7 +905,7 @@ static int trigger_sbr(struct hfi1_devdata *dd) * delay after a reset is required. Per spec requirements, * the link is either working or not after that point. */ - return pci_try_reset_bus(dev); + return pci_reset_bus(dev); } /* diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 8ee9f386c1ee..d123c2b173da 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4784,7 +4784,7 @@ int pci_probe_reset_slot(struct pci_slot *slot) EXPORT_SYMBOL_GPL(pci_probe_reset_slot); /** - * __pci_try_reset_slot - Try to reset a PCI slot + * __pci_reset_slot - Try to reset a PCI slot * @slot: PCI slot to reset * * A PCI bus may host multiple slots, each slot may support a reset mechanism @@ -4798,7 +4798,7 @@ EXPORT_SYMBOL_GPL(pci_probe_reset_slot); * * Same as above except return -EAGAIN if the slot cannot be locked */ -static int __pci_try_reset_slot(struct pci_slot *slot) +static int __pci_reset_slot(struct pci_slot *slot) { int rc; @@ -4854,12 +4854,12 @@ int pci_probe_reset_bus(struct pci_bus *bus) EXPORT_SYMBOL_GPL(pci_probe_reset_bus); /** - * __pci_try_reset_bus - Try to reset a PCI bus + * __pci_reset_bus - Try to reset a PCI bus * @bus: top level PCI bus to reset * * Same as above except return -EAGAIN if the bus cannot be locked */ -static int __pci_try_reset_bus(struct pci_bus *bus) +static int __pci_reset_bus(struct pci_bus *bus) { int rc; @@ -4882,17 +4882,17 @@ static int __pci_try_reset_bus(struct pci_bus *bus) } /** - * pci_try_reset_bus - Try to reset a PCI bus + * pci_reset_bus - Try to reset a PCI bus * @pdev: top level PCI device to reset via slot/bus * * Same as above except return -EAGAIN if the bus cannot be locked */ -int pci_try_reset_bus(struct pci_dev *pdev) +int pci_reset_bus(struct pci_dev *pdev) { return pci_probe_reset_slot(pdev->slot) ? - __pci_try_reset_slot(pdev->slot) : __pci_try_reset_bus(pdev->bus); + __pci_reset_slot(pdev->slot) : __pci_reset_bus(pdev->bus); } -EXPORT_SYMBOL_GPL(pci_try_reset_bus); +EXPORT_SYMBOL_GPL(pci_reset_bus); /** * pcix_get_max_mmrbc - get PCI-X maximum designed memory read byte count diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c index 71018ec9065b..345c0dc8a6dc 100644 --- a/drivers/vfio/pci/vfio_pci.c +++ b/drivers/vfio/pci/vfio_pci.c @@ -1010,7 +1010,7 @@ reset_info_exit: &info, slot); if (!ret) /* User has access, do the reset */ - ret = pci_try_reset_bus(vdev->pdev); + ret = pci_reset_bus(vdev->pdev); hot_reset_release: for (i--; i >= 0; i--) @@ -1372,7 +1372,7 @@ static void vfio_pci_try_bus_reset(struct vfio_pci_device *vdev) } if (needs_reset) - ret = pci_try_reset_bus(vdev->pdev); + ret = pci_reset_bus(vdev->pdev); put_devs: for (i = 0; i < devs.cur_index; i++) { diff --git a/include/linux/pci.h b/include/linux/pci.h index cf53ecb50789..307b336496f6 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1097,7 +1097,7 @@ int pci_reset_function_locked(struct pci_dev *dev); int pci_try_reset_function(struct pci_dev *dev); int pci_probe_reset_slot(struct pci_slot *slot); int pci_probe_reset_bus(struct pci_bus *bus); -int pci_try_reset_bus(struct pci_dev *dev); +int pci_reset_bus(struct pci_dev *dev); void pci_reset_secondary_bus(struct pci_dev *dev); void pcibios_reset_secondary_bus(struct pci_dev *dev); void pci_update_resource(struct pci_dev *dev, int resno); -- cgit v1.2.3 From eb3b5bf1a88d0cc1cd7acc7464eb211d69779808 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Thu, 19 Jul 2018 17:27:59 -0500 Subject: PCI: Whitelist native hotplug ports for runtime D3 Previously we blacklisted PCIe hotplug ports for runtime D3 because: (a) Ports handled by the firmware must not be transitioned to D3 by the OS behind the firmware's back: https://bugzilla.kernel.org/show_bug.cgi?id=53811 (b) Ports handled natively by the OS lacked runtime D3 support in the pciehp driver. We've just rectified the latter, so allow users to manually enable and test it by passing pcie_port_pm=force on the command line. Vendors are thus put in a position to validate hotplug ports for runtime D3 and perhaps we can someday enable it by default, but with a BIOS cutoff date. Ashok Raj tested runtime D3 on hotplug ports of a SkyLake Xeon-SP in 2017 and encountered Hardware Error NMIs, so this feature clearly cannot be enabled for everyone yet: https://lkml.kernel.org/r/20170503180426.GA4058@otc-nc-03 While at it, remove an erroneous code comment I added with 97a90aee5dab ("PCI: Consolidate conditions to allow runtime PM on PCIe ports") which claims that parents of a hotplug port must stay awake lest interrupts cannot be delivered. That has turned out to be wrong at least for Thunderbolt hotplug ports. Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Cc: Rafael J. Wysocki Cc: Mika Westerberg Cc: Ashok Raj Cc: Keith Busch Cc: Yinghai Lu --- drivers/pci/pci.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 97acba712e4e..4099a6c14b6d 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2305,18 +2305,23 @@ bool pci_bridge_d3_possible(struct pci_dev *bridge) return false; /* - * Hotplug interrupts cannot be delivered if the link is down, - * so parents of a hotplug port must stay awake. In addition, - * hotplug ports handled by firmware in System Management Mode + * Hotplug ports handled by firmware in System Management Mode * may not be put into D3 by the OS (Thunderbolt on non-Macs). - * For simplicity, disallow in general for now. */ - if (bridge->is_hotplug_bridge) + if (bridge->is_hotplug_bridge && !pciehp_is_native(bridge)) return false; if (pci_bridge_d3_force) return true; + /* + * Hotplug ports handled natively by the OS were not validated + * by vendors for runtime D3 at least until 2018 because there + * was no OS support. + */ + if (bridge->is_hotplug_bridge) + return false; + /* * It should be safe to put PCIe ports from 2015 or newer * to D3. -- cgit v1.2.3 From 47a8e237ed443c174f8f73402755c458c56eb611 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Thu, 19 Jul 2018 17:28:00 -0500 Subject: PCI: Whitelist Thunderbolt ports for runtime D3 Thunderbolt controllers can be runtime suspended to D3cold to save ~1.5W. This requires that runtime D3 is allowed on its PCIe ports, so whitelist them. The 2015 BIOS cutoff that we've instituted for runtime D3 on PCIe ports is unnecessary on Thunderbolt because we know that even the oldest controller, Light Ridge (2010), is able to suspend its ports to D3 just fine -- specifically including its hotplug ports. And the power saving should be afforded to machines even if their BIOS predates 2015. Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg Cc: Rafael J. Wysocki Cc: Andreas Noever --- drivers/pci/pci.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 4099a6c14b6d..c4d10726c59a 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2290,7 +2290,7 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev) * @bridge: Bridge to check * * This function checks if it is possible to move the bridge to D3. - * Currently we only allow D3 for recent enough PCIe ports. + * Currently we only allow D3 for recent enough PCIe ports and Thunderbolt. */ bool pci_bridge_d3_possible(struct pci_dev *bridge) { @@ -2314,6 +2314,10 @@ bool pci_bridge_d3_possible(struct pci_dev *bridge) if (pci_bridge_d3_force) return true; + /* Even the oldest 2010 Thunderbolt controller supports D3. */ + if (bridge->is_thunderbolt) + return true; + /* * Hotplug ports handled natively by the OS were not validated * by vendors for runtime D3 at least until 2018 because there -- cgit v1.2.3 From ce29af2a505627cc8f1deddb84bcbaf186b0bfd5 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Wed, 25 Jul 2018 14:53:42 -0500 Subject: PCI: Remove unnecessary include of Several PCI core files include pci-aspm.h even though they don't need anything provided by that file. Remove the unnecessary includes of it. Signed-off-by: Bjorn Helgaas Reviewed-by: Sinan Kaya --- drivers/pci/pci-sysfs.c | 1 - drivers/pci/pci.c | 1 - drivers/pci/probe.c | 1 - drivers/pci/remove.c | 1 - 4 files changed, 4 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 0c4653c1d2ce..91337faae60d 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 97acba712e4e..d92d216e4f64 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index ac876e32de4b..1ed2852dee21 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 6f072eae4f7a..01ec7fcb5634 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include #include -#include #include "pci.h" static void pci_free_resources(struct pci_dev *dev) -- cgit v1.2.3 From 2d2917f7747805a1f4188672f308d82a8ba01700 Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Thu, 9 Aug 2018 14:04:14 -0600 Subject: PCI: Export pcie_has_flr() pcie_flr() suggests pcie_has_flr() to ensure that PCIe FLR support is present prior to calling. pcie_flr() is exported while pcie_has_flr() is not. Resolve this. Signed-off-by: Alex Williamson Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 3 ++- include/linux/pci.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index d123c2b173da..85e5b80a69a7 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4039,7 +4039,7 @@ static int pci_dev_wait(struct pci_dev *dev, char *reset_type, int timeout) * Returns true if the device advertises support for PCIe function level * resets. */ -static bool pcie_has_flr(struct pci_dev *dev) +bool pcie_has_flr(struct pci_dev *dev) { u32 cap; @@ -4049,6 +4049,7 @@ static bool pcie_has_flr(struct pci_dev *dev) pcie_capability_read_dword(dev, PCI_EXP_DEVCAP, &cap); return cap & PCI_EXP_DEVCAP_FLR; } +EXPORT_SYMBOL_GPL(pcie_has_flr); /** * pcie_flr - initiate a PCIe function level reset diff --git a/include/linux/pci.h b/include/linux/pci.h index 307b336496f6..bace761deff2 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1090,6 +1090,7 @@ u32 pcie_bandwidth_available(struct pci_dev *dev, struct pci_dev **limiting_dev, enum pci_bus_speed *speed, enum pcie_link_width *width); void pcie_print_link_status(struct pci_dev *dev); +bool pcie_has_flr(struct pci_dev *dev); int pcie_flr(struct pci_dev *dev); int __pci_reset_function_locked(struct pci_dev *dev); int pci_reset_function(struct pci_dev *dev); -- cgit v1.2.3 From 07d8d7e57c28ca9a07dab4efd75dad3a654aeb85 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Mon, 30 Jul 2018 10:18:37 -0600 Subject: PCI: Make specifying PCI devices in kernel parameters reusable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separate out the code to match a PCI device with a string (typically originating from a kernel parameter) from the pci_specified_resource_alignment() function into its own helper function. While we are at it, this change fixes the kernel style of the function (fixing a number of long lines and extra parentheses). Additionally, make the analogous change to the kernel parameter documentation: Separate the description of how to specify a PCI device into its own section at the head of the "pci=" parameter. This patch should have no functional alterations. Signed-off-by: Logan Gunthorpe [bhelgaas: use "device" instead of "slot" in documentation since that's the usual language in the PCI specs] Signed-off-by: Bjorn Helgaas Reviewed-by: Stephen Bates Reviewed-by: Alex Williamson Acked-by: Christian König --- Documentation/admin-guide/kernel-parameters.txt | 28 ++++- drivers/pci/pci.c | 157 ++++++++++++++++-------- 2 files changed, 126 insertions(+), 59 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index efc7aa7a0670..ab36fb34ed01 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -2994,7 +2994,26 @@ See header of drivers/block/paride/pcd.c. See also Documentation/blockdev/paride.txt. - pci=option[,option...] [PCI] various PCI subsystem options: + pci=option[,option...] [PCI] various PCI subsystem options. + + Some options herein operate on a specific device + or a set of devices (). These are + specified in one of the following formats: + + [:]:. + pci::[::] + + Note: the first format specifies a PCI + bus/device/function address which may change + if new hardware is inserted, if motherboard + firmware changes, or due to changes caused + by other kernel parameters. If the + domain is left unspecified, it is + taken to be zero. The second format + selects devices using IDs from the + configuration space which may match multiple + devices in the system. + earlydump [X86] dump PCI config space before the kernel changes anything off [X86] don't probe for the PCI bus @@ -3123,11 +3142,10 @@ window. The default value is 64 megabytes. resource_alignment= Format: - [@][:]:.[; ...] - [@]pci::\ - [::][; ...] + [@][; ...] Specifies alignment and device to reassign - aligned memory resources. + aligned memory resources. How to + specify the device is described above. If is not specified, PAGE_SIZE is used as alignment. PCI-PCI bridge can be specified, if resource diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 97acba712e4e..1574b2da25e7 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -191,6 +191,92 @@ void __iomem *pci_ioremap_wc_bar(struct pci_dev *pdev, int bar) EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar); #endif +/** + * pci_dev_str_match - test if a string matches a device + * @dev: the PCI device to test + * @p: string to match the device against + * @endptr: pointer to the string after the match + * + * Test if a string (typically from a kernel parameter) matches a specified + * PCI device. The string may be of one of the following formats: + * + * [:]:. + * pci::[::] + * + * The first format specifies a PCI bus/device/function address which + * may change if new hardware is inserted, if motherboard firmware changes, + * or due to changes caused in kernel parameters. If the domain is + * left unspecified, it is taken to be 0. + * + * The second format matches devices using IDs in the configuration + * space which may match multiple devices in the system. A value of 0 + * for any field will match all devices. (Note: this differs from + * in-kernel code that uses PCI_ANY_ID which is ~0; this is for + * legacy reasons and convenience so users don't have to specify + * FFFFFFFFs on the command line.) + * + * Returns 1 if the string matches the device, 0 if it does not and + * a negative error code if the string cannot be parsed. + */ +static int pci_dev_str_match(struct pci_dev *dev, const char *p, + const char **endptr) +{ + int ret; + int seg, bus, slot, func, count; + unsigned short vendor, device, subsystem_vendor, subsystem_device; + + if (strncmp(p, "pci:", 4) == 0) { + /* PCI vendor/device (subvendor/subdevice) IDs are specified */ + p += 4; + ret = sscanf(p, "%hx:%hx:%hx:%hx%n", &vendor, &device, + &subsystem_vendor, &subsystem_device, &count); + if (ret != 4) { + ret = sscanf(p, "%hx:%hx%n", &vendor, &device, &count); + if (ret != 2) + return -EINVAL; + + subsystem_vendor = 0; + subsystem_device = 0; + } + + p += count; + + if ((!vendor || vendor == dev->vendor) && + (!device || device == dev->device) && + (!subsystem_vendor || + subsystem_vendor == dev->subsystem_vendor) && + (!subsystem_device || + subsystem_device == dev->subsystem_device)) + goto found; + + } else { + /* PCI Bus, Device, Function IDs are specified */ + ret = sscanf(p, "%x:%x:%x.%x%n", &seg, &bus, &slot, + &func, &count); + if (ret != 4) { + seg = 0; + ret = sscanf(p, "%x:%x.%x%n", &bus, &slot, + &func, &count); + if (ret != 3) + return -EINVAL; + } + + p += count; + + if (seg == pci_domain_nr(dev->bus) && + bus == dev->bus->number && + slot == PCI_SLOT(dev->devfn) && + func == PCI_FUNC(dev->devfn)) + goto found; + } + + *endptr = p; + return 0; + +found: + *endptr = p; + return 1; +} static int __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn, u8 pos, int cap, int *ttl) @@ -5454,10 +5540,10 @@ static DEFINE_SPINLOCK(resource_alignment_lock); static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev, bool *resize) { - int seg, bus, slot, func, align_order, count; - unsigned short vendor, device, subsystem_vendor, subsystem_device; + int align_order, count; resource_size_t align = pcibios_default_alignment(); - char *p; + const char *p; + int ret; spin_lock(&resource_alignment_lock); p = resource_alignment_param; @@ -5477,58 +5563,21 @@ static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev, } else { align_order = -1; } - if (strncmp(p, "pci:", 4) == 0) { - /* PCI vendor/device (subvendor/subdevice) ids are specified */ - p += 4; - if (sscanf(p, "%hx:%hx:%hx:%hx%n", - &vendor, &device, &subsystem_vendor, &subsystem_device, &count) != 4) { - if (sscanf(p, "%hx:%hx%n", &vendor, &device, &count) != 2) { - printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: pci:%s\n", - p); - break; - } - subsystem_vendor = subsystem_device = 0; - } - p += count; - if ((!vendor || (vendor == dev->vendor)) && - (!device || (device == dev->device)) && - (!subsystem_vendor || (subsystem_vendor == dev->subsystem_vendor)) && - (!subsystem_device || (subsystem_device == dev->subsystem_device))) { - *resize = true; - if (align_order == -1) - align = PAGE_SIZE; - else - align = 1 << align_order; - /* Found */ - break; - } - } - else { - if (sscanf(p, "%x:%x:%x.%x%n", - &seg, &bus, &slot, &func, &count) != 4) { - seg = 0; - if (sscanf(p, "%x:%x.%x%n", - &bus, &slot, &func, &count) != 3) { - /* Invalid format */ - printk(KERN_ERR "PCI: Can't parse resource_alignment parameter: %s\n", - p); - break; - } - } - p += count; - if (seg == pci_domain_nr(dev->bus) && - bus == dev->bus->number && - slot == PCI_SLOT(dev->devfn) && - func == PCI_FUNC(dev->devfn)) { - *resize = true; - if (align_order == -1) - align = PAGE_SIZE; - else - align = 1 << align_order; - /* Found */ - break; - } + + ret = pci_dev_str_match(dev, p, &p); + if (ret == 1) { + *resize = true; + if (align_order == -1) + align = PAGE_SIZE; + else + align = 1 << align_order; + break; + } else if (ret < 0) { + pr_err("PCI: Can't parse resource_alignment parameter: %s\n", + p); + break; } + if (*p != ';' && *p != ',') { /* End of param or invalid format */ break; -- cgit v1.2.3 From 45db33709ccc7330c55fc6751c96468de407f2ac Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Mon, 30 Jul 2018 10:18:38 -0600 Subject: PCI: Allow specifying devices using a base bus and path of devfns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When specifying PCI devices on the kernel command line using a bus/device/function address, bus numbers can change when adding or replacing a device, changing motherboard firmware, or applying kernel parameters like "pci=assign-buses". When bus numbers change, it's likely the command line tweak will be applied to the wrong device. Therefore, it is useful to be able to specify devices with a base bus number and the path of devfns needed to get to it, similar to the "device scope" structure in the Intel VT-d spec, Section 8.3.1. Thus, we add an option to specify devices in the following format: [:]:.[/.]* The path can be any segment within the PCI hierarchy of any length and determined through the use of 'lspci -t'. When specified this way, it is less likely that a renumbered bus will result in a valid device specification and the tweak won't be applied to the wrong device. Signed-off-by: Logan Gunthorpe [bhelgaas: use "device" instead of "slot" in documentation since that's the usual language in the PCI specs] Signed-off-by: Bjorn Helgaas Reviewed-by: Stephen Bates Reviewed-by: Alex Williamson Acked-by: Christian König --- Documentation/admin-guide/kernel-parameters.txt | 8 +- drivers/pci/pci.c | 118 +++++++++++++++++++----- 2 files changed, 103 insertions(+), 23 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index ab36fb34ed01..4fa4c9ff04ae 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -3000,7 +3000,7 @@ or a set of devices (). These are specified in one of the following formats: - [:]:. + [:]:.[/.]* pci::[::] Note: the first format specifies a PCI @@ -3009,7 +3009,11 @@ firmware changes, or due to changes caused by other kernel parameters. If the domain is left unspecified, it is - taken to be zero. The second format + taken to be zero. Optionally, a path + to a device through multiple device/function + addresses can be specified after the base + address (this is more robust against + renumbering issues). The second format selects devices using IDs from the configuration space which may match multiple devices in the system. diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 1574b2da25e7..a6c38b15ac33 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -191,6 +191,89 @@ void __iomem *pci_ioremap_wc_bar(struct pci_dev *pdev, int bar) EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar); #endif +/** + * pci_dev_str_match_path - test if a path string matches a device + * @dev: the PCI device to test + * @p: string to match the device against + * @endptr: pointer to the string after the match + * + * Test if a string (typically from a kernel parameter) formatted as a + * path of device/function addresses matches a PCI device. The string must + * be of the form: + * + * [:]:.[/.]* + * + * A path for a device can be obtained using 'lspci -t'. Using a path + * is more robust against bus renumbering than using only a single bus, + * device and function address. + * + * Returns 1 if the string matches the device, 0 if it does not and + * a negative error code if it fails to parse the string. + */ +static int pci_dev_str_match_path(struct pci_dev *dev, const char *path, + const char **endptr) +{ + int ret; + int seg, bus, slot, func; + char *wpath, *p; + char end; + + *endptr = strchrnul(path, ';'); + + wpath = kmemdup_nul(path, *endptr - path, GFP_KERNEL); + if (!wpath) + return -ENOMEM; + + while (1) { + p = strrchr(wpath, '/'); + if (!p) + break; + ret = sscanf(p, "/%x.%x%c", &slot, &func, &end); + if (ret != 2) { + ret = -EINVAL; + goto free_and_exit; + } + + if (dev->devfn != PCI_DEVFN(slot, func)) { + ret = 0; + goto free_and_exit; + } + + /* + * Note: we don't need to get a reference to the upstream + * bridge because we hold a reference to the top level + * device which should hold a reference to the bridge, + * and so on. + */ + dev = pci_upstream_bridge(dev); + if (!dev) { + ret = 0; + goto free_and_exit; + } + + *p = 0; + } + + ret = sscanf(wpath, "%x:%x:%x.%x%c", &seg, &bus, &slot, + &func, &end); + if (ret != 4) { + seg = 0; + ret = sscanf(wpath, "%x:%x.%x%c", &bus, &slot, &func, &end); + if (ret != 3) { + ret = -EINVAL; + goto free_and_exit; + } + } + + ret = (seg == pci_domain_nr(dev->bus) && + bus == dev->bus->number && + dev->devfn == PCI_DEVFN(slot, func)); + +free_and_exit: + kfree(wpath); + return ret; +} + /** * pci_dev_str_match - test if a string matches a device * @dev: the PCI device to test @@ -200,13 +283,16 @@ EXPORT_SYMBOL_GPL(pci_ioremap_wc_bar); * Test if a string (typically from a kernel parameter) matches a specified * PCI device. The string may be of one of the following formats: * - * [:]:. + * [:]:.[/.]* * pci::[::] * * The first format specifies a PCI bus/device/function address which * may change if new hardware is inserted, if motherboard firmware changes, * or due to changes caused in kernel parameters. If the domain is - * left unspecified, it is taken to be 0. + * left unspecified, it is taken to be 0. In order to be robust against + * bus renumbering issues, a path of PCI device/function numbers may be used + * to address the specific device. The path for a device can be determined + * through the use of 'lspci -t'. * * The second format matches devices using IDs in the configuration * space which may match multiple devices in the system. A value of 0 @@ -222,7 +308,7 @@ static int pci_dev_str_match(struct pci_dev *dev, const char *p, const char **endptr) { int ret; - int seg, bus, slot, func, count; + int count; unsigned short vendor, device, subsystem_vendor, subsystem_device; if (strncmp(p, "pci:", 4) == 0) { @@ -248,25 +334,15 @@ static int pci_dev_str_match(struct pci_dev *dev, const char *p, (!subsystem_device || subsystem_device == dev->subsystem_device)) goto found; - } else { - /* PCI Bus, Device, Function IDs are specified */ - ret = sscanf(p, "%x:%x:%x.%x%n", &seg, &bus, &slot, - &func, &count); - if (ret != 4) { - seg = 0; - ret = sscanf(p, "%x:%x.%x%n", &bus, &slot, - &func, &count); - if (ret != 3) - return -EINVAL; - } - - p += count; - - if (seg == pci_domain_nr(dev->bus) && - bus == dev->bus->number && - slot == PCI_SLOT(dev->devfn) && - func == PCI_FUNC(dev->devfn)) + /* + * PCI Bus, Device, Function IDs are specified + * (optionally, may include a path of devfns following it) + */ + ret = pci_dev_str_match_path(dev, p, &p); + if (ret < 0) + return ret; + else if (ret) goto found; } -- cgit v1.2.3 From aaca43fda742223e4f62bd73e13055f5364e9a9b Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Mon, 30 Jul 2018 10:18:40 -0600 Subject: PCI: Add "pci=disable_acs_redir=" parameter for peer-to-peer support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To support peer-to-peer traffic on a segment of the PCI hierarchy, we must disable the ACS redirect bits for select PCI bridges. The bridges must be selected before the devices are discovered by the kernel and the IOMMU groups created. Therefore, add a kernel command line parameter to specify devices which must have their ACS bits disabled. The new parameter takes a list of devices separated by a semicolon. Each device specified will have its ACS redirect bits disabled. This is similar to the existing 'resource_alignment' parameter. The ACS Request P2P Request Redirect, P2P Completion Redirect and P2P Egress Control bits are disabled, which is sufficient to always allow passing P2P traffic uninterrupted. The bits are set after the kernel (optionally) enables the ACS bits itself. It is also done regardless of whether the kernel or platform firmware sets the bits. If the user tries to disable the ACS redirect for a device without the ACS capability, print a warning to dmesg. Signed-off-by: Logan Gunthorpe [bhelgaas: reorder to add the generic code first and move the device-specific quirk to subsequent patches] Signed-off-by: Bjorn Helgaas Reviewed-by: Stephen Bates Reviewed-by: Alex Williamson Acked-by: Christian König --- Documentation/admin-guide/kernel-parameters.txt | 9 +++ drivers/pci/pci.c | 73 ++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 2 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 4fa4c9ff04ae..d5c27d947c2e 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -3192,6 +3192,15 @@ Adding the window is slightly risky (it may conflict with unreported devices), so this taints the kernel. + disable_acs_redir=[; ...] + Specify one or more PCI devices (in the format + specified above) separated by semicolons. + Each device specified will have the PCI ACS + redirect capabilities forced off which will + allow P2P traffic between devices through + bridges without forcing it upstream. Note: + this removes isolation between devices and + may put more devices in an IOMMU group. pcie_aspm= [PCIE] Forcibly enable or disable PCIe Active State Power Management. diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index a6c38b15ac33..822577d9b39e 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2982,6 +2982,63 @@ void pci_request_acs(void) pci_acs_enable = 1; } +static const char *disable_acs_redir_param; + +/** + * pci_disable_acs_redir - disable ACS redirect capabilities + * @dev: the PCI device + * + * For only devices specified in the disable_acs_redir parameter. + */ +static void pci_disable_acs_redir(struct pci_dev *dev) +{ + int ret = 0; + const char *p; + int pos; + u16 ctrl; + + if (!disable_acs_redir_param) + return; + + p = disable_acs_redir_param; + while (*p) { + ret = pci_dev_str_match(dev, p, &p); + if (ret < 0) { + pr_info_once("PCI: Can't parse disable_acs_redir parameter: %s\n", + disable_acs_redir_param); + + break; + } else if (ret == 1) { + /* Found a match */ + break; + } + + if (*p != ';' && *p != ',') { + /* End of param or invalid format */ + break; + } + p++; + } + + if (ret != 1) + return; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS); + if (!pos) { + pci_warn(dev, "cannot disable ACS redirect for this hardware as it does not have ACS capabilities\n"); + return; + } + + pci_read_config_word(dev, pos + PCI_ACS_CTRL, &ctrl); + + /* P2P Request & Completion Redirect */ + ctrl &= ~(PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_EC); + + pci_write_config_word(dev, pos + PCI_ACS_CTRL, ctrl); + + pci_info(dev, "disabled ACS redirect\n"); +} + /** * pci_std_enable_acs - enable ACS on devices using standard ACS capabilites * @dev: the PCI device @@ -3021,12 +3078,22 @@ static void pci_std_enable_acs(struct pci_dev *dev) void pci_enable_acs(struct pci_dev *dev) { if (!pci_acs_enable) - return; + goto disable_acs_redir; if (!pci_dev_specific_enable_acs(dev)) - return; + goto disable_acs_redir; pci_std_enable_acs(dev); + +disable_acs_redir: + /* + * Note: pci_disable_acs_redir() must be called even if ACS was not + * enabled by the kernel because it may have been enabled by + * platform firmware. So if we are told to disable it, we should + * always disable it after setting the kernel's default + * preferences. + */ + pci_disable_acs_redir(dev); } static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags) @@ -5966,6 +6033,8 @@ static int __init pci_setup(char *str) pcie_bus_config = PCIE_BUS_PEER2PEER; } else if (!strncmp(str, "pcie_scan_all", 13)) { pci_add_flags(PCI_SCAN_ALL_PCIE_DEVS); + } else if (!strncmp(str, "disable_acs_redir=", 18)) { + disable_acs_redir_param = str + 18; } else { printk(KERN_ERR "PCI: Unknown option `%s'\n", str); -- cgit v1.2.3 From 73c47ddef29b5869a2221e93cefdb56fd8263718 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Thu, 9 Aug 2018 16:51:43 -0500 Subject: PCI: Add device-specific ACS Redirect disable infrastructure Intel Sunrise Point (SPT) PCH hardware has an implementation of the ACS bits that does not comply with the PCIe standard. To deal with this we need device-specific quirks to disable ACS redirection. Add a new pci_dev_specific_disable_acs_redir() quirk and a new .disable_acs_redir() function pointer for use by non-compliant devices. No functional change intended. Signed-off-by: Logan Gunthorpe [bhelgaas: split to separate patch, move pci_dev_specific_disable_acs_redir() declarations to drivers/pci/pci.h] Signed-off-by: Bjorn Helgaas Reviewed-by: Alex Williamson --- drivers/pci/pci.c | 3 +++ drivers/pci/pci.h | 5 +++++ drivers/pci/quirks.c | 43 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 43 insertions(+), 8 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 822577d9b39e..17d8de109556 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3023,6 +3023,9 @@ static void pci_disable_acs_redir(struct pci_dev *dev) if (ret != 1) return; + if (!pci_dev_specific_disable_acs_redir(dev)) + return; + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS); if (!pos) { pci_warn(dev, "cannot disable ACS redirect for this hardware as it does not have ACS capabilities\n"); diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index a1224fef3409..04f5035d12c6 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -355,6 +355,7 @@ void pci_enable_acs(struct pci_dev *dev); #ifdef CONFIG_PCI_QUIRKS int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags); int pci_dev_specific_enable_acs(struct pci_dev *dev); +int pci_dev_specific_disable_acs_redir(struct pci_dev *dev); #else static inline int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags) @@ -365,6 +366,10 @@ static inline int pci_dev_specific_enable_acs(struct pci_dev *dev) { return -ENOTTY; } +static inline int pci_dev_specific_disable_acs_redir(struct pci_dev *dev) +{ + return -ENOTTY; +} #endif /* PCI error reporting and recovery */ diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 823d8e4b4b37..4f79631159eb 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -4553,26 +4553,32 @@ static int pci_quirk_enable_intel_spt_pch_acs(struct pci_dev *dev) return 0; } -static const struct pci_dev_enable_acs { +static const struct pci_dev_acs_ops { u16 vendor; u16 device; int (*enable_acs)(struct pci_dev *dev); -} pci_dev_enable_acs[] = { - { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_quirk_enable_intel_pch_acs }, - { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_quirk_enable_intel_spt_pch_acs }, + int (*disable_acs_redir)(struct pci_dev *dev); +} pci_dev_acs_ops[] = { + { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, + .enable_acs = pci_quirk_enable_intel_pch_acs, + }, + { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, + .enable_acs = pci_quirk_enable_intel_spt_pch_acs, + }, }; int pci_dev_specific_enable_acs(struct pci_dev *dev) { - const struct pci_dev_enable_acs *p; + const struct pci_dev_acs_ops *p; int i, ret; - for (i = 0; i < ARRAY_SIZE(pci_dev_enable_acs); i++) { - p = &pci_dev_enable_acs[i]; + for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) { + p = &pci_dev_acs_ops[i]; if ((p->vendor == dev->vendor || p->vendor == (u16)PCI_ANY_ID) && (p->device == dev->device || - p->device == (u16)PCI_ANY_ID)) { + p->device == (u16)PCI_ANY_ID) && + p->enable_acs) { ret = p->enable_acs(dev); if (ret >= 0) return ret; @@ -4582,6 +4588,27 @@ int pci_dev_specific_enable_acs(struct pci_dev *dev) return -ENOTTY; } +int pci_dev_specific_disable_acs_redir(struct pci_dev *dev) +{ + const struct pci_dev_acs_ops *p; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(pci_dev_acs_ops); i++) { + p = &pci_dev_acs_ops[i]; + if ((p->vendor == dev->vendor || + p->vendor == (u16)PCI_ANY_ID) && + (p->device == dev->device || + p->device == (u16)PCI_ANY_ID) && + p->disable_acs_redir) { + ret = p->disable_acs_redir(dev); + if (ret >= 0) + return ret; + } + } + + return -ENOTTY; +} + /* * The PCI capabilities list for Intel DH895xCC VFs (device ID 0x0443) with * QuickAssist Technology (QAT) is prematurely terminated in hardware. The -- cgit v1.2.3 From 2d1ce5ec2117d16047334a1aa4b62e0cfb5a0605 Mon Sep 17 00:00:00 2001 From: Alexandru Gagniuc Date: Mon, 6 Aug 2018 18:25:35 -0500 Subject: PCI: Check for PCIe Link downtraining When both ends of a PCIe Link are capable of a higher bandwidth than is currently in use, the Link is said to be "downtrained". A downtrained Link may indicate hardware or configuration problems in the system, but it's hard to identify such Links from userspace. Refactor pcie_print_link_status() so it continues to always print PCIe bandwidth information, as several NIC drivers desire. Add a new internal __pcie_print_link_status() to emit a message only when a device's bandwidth is constrained by the fabric and call it from the PCI core for all devices, which identifies all downtrained Links. It also emits messages for a few cases that are technically not downtrained, such as a x4 device in an open-ended x1 slot. Signed-off-by: Alexandru Gagniuc [bhelgaas: changelog, move __pcie_print_link_status() declaration to drivers/pci/, rename pcie_check_upstream_link() to pcie_report_downtraining()] Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 27 ++++++++++++++++++++------- drivers/pci/pci.h | 1 + drivers/pci/probe.c | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) (limited to 'drivers/pci/pci.c') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 97acba712e4e..a84d341504a5 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -5264,14 +5264,16 @@ u32 pcie_bandwidth_capable(struct pci_dev *dev, enum pci_bus_speed *speed, } /** - * pcie_print_link_status - Report the PCI device's link speed and width + * __pcie_print_link_status - Report the PCI device's link speed and width * @dev: PCI device to query + * @verbose: Print info even when enough bandwidth is available * - * Report the available bandwidth at the device. If this is less than the - * device is capable of, report the device's maximum possible bandwidth and - * the upstream link that limits its performance to less than that. + * If the available bandwidth at the device is less than the device is + * capable of, report the device's maximum possible bandwidth and the + * upstream link that limits its performance. If @verbose, always print + * the available bandwidth, even if the device isn't constrained. */ -void pcie_print_link_status(struct pci_dev *dev) +void __pcie_print_link_status(struct pci_dev *dev, bool verbose) { enum pcie_link_width width, width_cap; enum pci_bus_speed speed, speed_cap; @@ -5281,11 +5283,11 @@ void pcie_print_link_status(struct pci_dev *dev) bw_cap = pcie_bandwidth_capable(dev, &speed_cap, &width_cap); bw_avail = pcie_bandwidth_available(dev, &limiting_dev, &speed, &width); - if (bw_avail >= bw_cap) + if (bw_avail >= bw_cap && verbose) pci_info(dev, "%u.%03u Gb/s available PCIe bandwidth (%s x%d link)\n", bw_cap / 1000, bw_cap % 1000, PCIE_SPEED2STR(speed_cap), width_cap); - else + else if (bw_avail < bw_cap) pci_info(dev, "%u.%03u Gb/s available PCIe bandwidth, limited by %s x%d link at %s (capable of %u.%03u Gb/s with %s x%d link)\n", bw_avail / 1000, bw_avail % 1000, PCIE_SPEED2STR(speed), width, @@ -5293,6 +5295,17 @@ void pcie_print_link_status(struct pci_dev *dev) bw_cap / 1000, bw_cap % 1000, PCIE_SPEED2STR(speed_cap), width_cap); } + +/** + * pcie_print_link_status - Report the PCI device's link speed and width + * @dev: PCI device to query + * + * Report the available bandwidth at the device. + */ +void pcie_print_link_status(struct pci_dev *dev) +{ + __pcie_print_link_status(dev, true); +} EXPORT_SYMBOL(pcie_print_link_status); /** diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 70808c168fb9..ce880dab5bc8 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -263,6 +263,7 @@ enum pci_bus_speed pcie_get_speed_cap(struct pci_dev *dev); enum pcie_link_width pcie_get_width_cap(struct pci_dev *dev); u32 pcie_bandwidth_capable(struct pci_dev *dev, enum pci_bus_speed *speed, enum pcie_link_width *width); +void __pcie_print_link_status(struct pci_dev *dev, bool verbose); /* Single Root I/O Virtualization */ struct pci_sriov { diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 7c0c8ab94bcf..71412db3cbeb 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2223,6 +2223,25 @@ static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn) return dev; } +static void pcie_report_downtraining(struct pci_dev *dev) +{ + if (!pci_is_pcie(dev)) + return; + + /* Look from the device up to avoid downstream ports with no devices */ + if ((pci_pcie_type(dev) != PCI_EXP_TYPE_ENDPOINT) && + (pci_pcie_type(dev) != PCI_EXP_TYPE_LEG_END) && + (pci_pcie_type(dev) != PCI_EXP_TYPE_UPSTREAM)) + return; + + /* Multi-function PCIe devices share the same link/status */ + if (PCI_FUNC(dev->devfn) != 0 || dev->is_virtfn) + return; + + /* Print link status only if the device is constrained by the fabric */ + __pcie_print_link_status(dev, false); +} + static void pci_init_capabilities(struct pci_dev *dev) { /* Enhanced Allocation */ @@ -2258,6 +2277,8 @@ static void pci_init_capabilities(struct pci_dev *dev) /* Advanced Error Reporting */ pci_aer_init(dev); + pcie_report_downtraining(dev); + if (pci_probe_reset_function(dev) == 0) dev->reset_fn = 1; } -- cgit v1.2.3