diff options
Diffstat (limited to 'drivers/pci')
54 files changed, 1361 insertions, 1181 deletions
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 535201984b8b..1b2cfe51e8d7 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -28,10 +28,10 @@ obj-$(CONFIG_PCI_PF_STUB) += pci-pf-stub.o obj-$(CONFIG_PCI_ECAM) += ecam.o obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o -obj-y += controller/ -obj-y += switch/ - # Endpoint library must be initialized before its users obj-$(CONFIG_PCI_ENDPOINT) += endpoint/ +obj-y += controller/ +obj-y += switch/ + ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index 35b7fc87eac5..5cb40b2518f9 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -330,7 +330,7 @@ void pci_bus_add_device(struct pci_dev *dev) return; } - dev->is_added = 1; + pci_dev_assign_added(dev, true); } EXPORT_SYMBOL_GPL(pci_bus_add_device); @@ -347,14 +347,14 @@ void pci_bus_add_devices(const struct pci_bus *bus) list_for_each_entry(dev, &bus->devices, bus_list) { /* Skip already-added devices */ - if (dev->is_added) + if (pci_dev_is_added(dev)) continue; pci_bus_add_device(dev); } list_for_each_entry(dev, &bus->devices, bus_list) { /* Skip if device attach failed */ - if (!dev->is_added) + if (!pci_dev_is_added(dev)) continue; child = dev->subordinate; if (child) diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index 18fa09b3ac8f..cc9fa02d32a0 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -96,7 +96,6 @@ config PCI_HOST_GENERIC depends on OF select PCI_HOST_COMMON select IRQ_DOMAIN - select PCI_DOMAINS help Say Y here if you want to support a simple generic PCI host controller, such as the one emulated by kvmtool. @@ -138,7 +137,6 @@ config PCI_VERSATILE config PCIE_IPROC tristate - select PCI_DOMAINS help This enables the iProc PCIe core controller support for Broadcom's iProc family of SoCs. An appropriate bus interface driver needs @@ -176,7 +174,6 @@ config PCIE_IPROC_MSI config PCIE_ALTERA bool "Altera PCIe controller" depends on ARM || NIOS2 || COMPILE_TEST - select PCI_DOMAINS help Say Y here if you want to enable PCIe controller support on Altera FPGA. diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index 16f52c626b4b..91b0194240a5 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -58,7 +58,6 @@ config PCIE_DW_PLAT_HOST depends on PCI && PCI_MSI_IRQ_DOMAIN select PCIE_DW_HOST select PCIE_DW_PLAT - default y help Enables support for the PCIe controller in the Designware IP to work in host mode. There are two instances of PCIe controller in diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index 781aa03aeede..29a05759a294 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -363,7 +363,8 @@ int dw_pcie_host_init(struct pcie_port *pp) resource_list_for_each_entry_safe(win, tmp, &bridge->windows) { switch (resource_type(win->res)) { case IORESOURCE_IO: - ret = pci_remap_iospace(win->res, pp->io_base); + ret = devm_pci_remap_iospace(dev, win->res, + pp->io_base); if (ret) { dev_warn(dev, "Error %d: failed to map resource %pR\n", ret, win->res); diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c index d3172d5d3d35..0fae816fba39 100644 --- a/drivers/pci/controller/pci-aardvark.c +++ b/drivers/pci/controller/pci-aardvark.c @@ -849,7 +849,7 @@ static int advk_pcie_parse_request_of_pci_ranges(struct advk_pcie *pcie) 0, 0xF8000000, 0, lower_32_bits(res->start), OB_PCIE_IO); - err = pci_remap_iospace(res, iobase); + err = devm_pci_remap_iospace(dev, res, iobase); if (err) { dev_warn(dev, "error %d: failed to map resource %pR\n", err, res); diff --git a/drivers/pci/controller/pci-ftpci100.c b/drivers/pci/controller/pci-ftpci100.c index a1ebe9ed441f..bf5ece5d9291 100644 --- a/drivers/pci/controller/pci-ftpci100.c +++ b/drivers/pci/controller/pci-ftpci100.c @@ -355,11 +355,13 @@ static int faraday_pci_setup_cascaded_irq(struct faraday_pci *p) irq = of_irq_get(intc, 0); if (irq <= 0) { dev_err(p->dev, "failed to get parent IRQ\n"); + of_node_put(intc); return irq ?: -EINVAL; } p->irqdomain = irq_domain_add_linear(intc, PCI_NUM_INTX, &faraday_pci_irqdomain_ops, p); + of_node_put(intc); if (!p->irqdomain) { dev_err(p->dev, "failed to create Gemini PCI IRQ domain\n"); return -EINVAL; @@ -501,7 +503,7 @@ static int faraday_pci_probe(struct platform_device *pdev) dev_err(dev, "illegal IO mem size\n"); return -EINVAL; } - ret = pci_remap_iospace(io, io_base); + ret = devm_pci_remap_iospace(dev, io, io_base); if (ret) { dev_warn(dev, "error %d: failed to map resource %pR\n", ret, io); diff --git a/drivers/pci/controller/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c index 6cc5036ac83c..f6325f1a89e8 100644 --- a/drivers/pci/controller/pci-hyperv.c +++ b/drivers/pci/controller/pci-hyperv.c @@ -1073,6 +1073,7 @@ static void hv_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) struct pci_bus *pbus; struct pci_dev *pdev; struct cpumask *dest; + unsigned long flags; struct compose_comp_ctxt comp; struct tran_int_desc *int_desc; struct { @@ -1164,14 +1165,15 @@ static void hv_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) * the channel callback directly when channel->target_cpu is * the current CPU. When the higher level interrupt code * calls us with interrupt enabled, let's add the - * local_bh_disable()/enable() to avoid race. + * local_irq_save()/restore() to avoid race: + * hv_pci_onchannelcallback() can also run in tasklet. */ - local_bh_disable(); + local_irq_save(flags); if (hbus->hdev->channel->target_cpu == smp_processor_id()) hv_pci_onchannelcallback(hbus); - local_bh_enable(); + local_irq_restore(flags); if (hpdev->state == hv_pcichild_ejecting) { dev_err_once(&hbus->hdev->device, diff --git a/drivers/pci/controller/pci-v3-semi.c b/drivers/pci/controller/pci-v3-semi.c index 68b8bfbdb867..d219404bad92 100644 --- a/drivers/pci/controller/pci-v3-semi.c +++ b/drivers/pci/controller/pci-v3-semi.c @@ -537,7 +537,7 @@ static int v3_pci_setup_resource(struct v3_pci *v3, v3->io_bus_addr = io->start - win->offset; dev_dbg(dev, "I/O window %pR, bus addr %pap\n", io, &v3->io_bus_addr); - ret = pci_remap_iospace(io, io_base); + ret = devm_pci_remap_iospace(dev, io, io_base); if (ret) { dev_warn(dev, "error %d: failed to map resource %pR\n", diff --git a/drivers/pci/controller/pci-versatile.c b/drivers/pci/controller/pci-versatile.c index 994f32061b32..f59ad2728c0b 100644 --- a/drivers/pci/controller/pci-versatile.c +++ b/drivers/pci/controller/pci-versatile.c @@ -82,7 +82,7 @@ static int versatile_pci_parse_request_of_pci_ranges(struct device *dev, switch (resource_type(res)) { case IORESOURCE_IO: - err = pci_remap_iospace(res, iobase); + err = devm_pci_remap_iospace(dev, res, iobase); if (err) { dev_warn(dev, "error %d: failed to map resource %pR\n", err, res); diff --git a/drivers/pci/controller/pci-xgene.c b/drivers/pci/controller/pci-xgene.c index d854d67e873c..ffda3e8b4742 100644 --- a/drivers/pci/controller/pci-xgene.c +++ b/drivers/pci/controller/pci-xgene.c @@ -423,7 +423,7 @@ static int xgene_pcie_map_ranges(struct xgene_pcie_port *port, case IORESOURCE_IO: xgene_pcie_setup_ob_reg(port, res, OMR3BARL, io_base, res->start - window->offset); - ret = pci_remap_iospace(res, io_base); + ret = devm_pci_remap_iospace(dev, res, io_base); if (ret < 0) return ret; break; diff --git a/drivers/pci/controller/pcie-mediatek.c b/drivers/pci/controller/pcie-mediatek.c index 0baabe30858f..861dda69f366 100644 --- a/drivers/pci/controller/pcie-mediatek.c +++ b/drivers/pci/controller/pcie-mediatek.c @@ -1109,7 +1109,7 @@ static int mtk_pcie_request_resources(struct mtk_pcie *pcie) if (err < 0) return err; - pci_remap_iospace(&pcie->pio, pcie->io.start); + devm_pci_remap_iospace(dev, &pcie->pio, pcie->io.start); return 0; } diff --git a/drivers/pci/controller/pcie-mobiveil.c b/drivers/pci/controller/pcie-mobiveil.c index 4d6c20e47bed..cf0aa7cee5b0 100644 --- a/drivers/pci/controller/pcie-mobiveil.c +++ b/drivers/pci/controller/pcie-mobiveil.c @@ -107,7 +107,7 @@ #define CFG_WINDOW_TYPE 0 #define IO_WINDOW_TYPE 1 #define MEM_WINDOW_TYPE 2 -#define IB_WIN_SIZE (256 * 1024 * 1024 * 1024) +#define IB_WIN_SIZE ((u64)256 * 1024 * 1024 * 1024) #define MAX_PIO_WINDOWS 8 /* Parameters for the waiting for link up routine */ diff --git a/drivers/pci/controller/pcie-rcar.c b/drivers/pci/controller/pcie-rcar.c index 874d75c9ee4a..c8febb009454 100644 --- a/drivers/pci/controller/pcie-rcar.c +++ b/drivers/pci/controller/pcie-rcar.c @@ -680,7 +680,11 @@ static int rcar_pcie_phy_init_gen3(struct rcar_pcie *pcie) if (err) return err; - return phy_power_on(pcie->phy); + err = phy_power_on(pcie->phy); + if (err) + phy_exit(pcie->phy); + + return err; } static int rcar_msi_alloc(struct rcar_msi *chip) @@ -1165,7 +1169,7 @@ static int rcar_pcie_probe(struct platform_device *pdev) if (rcar_pcie_hw_init(pcie)) { dev_info(dev, "PCIe link down\n"); err = -ENODEV; - goto err_clk_disable; + goto err_phy_shutdown; } data = rcar_pci_read_reg(pcie, MACSR); @@ -1177,7 +1181,7 @@ static int rcar_pcie_probe(struct platform_device *pdev) dev_err(dev, "failed to enable MSI support: %d\n", err); - goto err_clk_disable; + goto err_phy_shutdown; } } @@ -1191,6 +1195,12 @@ err_msi_teardown: if (IS_ENABLED(CONFIG_PCI_MSI)) rcar_pcie_teardown_msi(pcie); +err_phy_shutdown: + if (pcie->phy) { + phy_power_off(pcie->phy); + phy_exit(pcie->phy); + } + err_clk_disable: clk_disable_unprepare(pcie->bus_clk); diff --git a/drivers/pci/controller/pcie-xilinx-nwl.c b/drivers/pci/controller/pcie-xilinx-nwl.c index 6a4bbb5b3de0..fb32840ce8e6 100644 --- a/drivers/pci/controller/pcie-xilinx-nwl.c +++ b/drivers/pci/controller/pcie-xilinx-nwl.c @@ -559,7 +559,7 @@ static int nwl_pcie_init_irq_domain(struct nwl_pcie *pcie) PCI_NUM_INTX, &legacy_domain_ops, pcie); - + of_node_put(legacy_intc_node); if (!pcie->legacy_irq_domain) { dev_err(dev, "failed to create IRQ domain\n"); return -ENOMEM; diff --git a/drivers/pci/controller/pcie-xilinx.c b/drivers/pci/controller/pcie-xilinx.c index b110a3a814e3..7b1389d8e2a5 100644 --- a/drivers/pci/controller/pcie-xilinx.c +++ b/drivers/pci/controller/pcie-xilinx.c @@ -509,6 +509,7 @@ static int xilinx_pcie_init_irq_domain(struct xilinx_pcie_port *port) port->leg_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX, &intx_domain_ops, port); + of_node_put(pcie_intc_node); if (!port->leg_domain) { dev_err(dev, "Failed to get a INTx IRQ domain\n"); return -ENODEV; diff --git a/drivers/pci/endpoint/pci-epf-core.c b/drivers/pci/endpoint/pci-epf-core.c index 523a8cab3bfb..825fa24427a3 100644 --- a/drivers/pci/endpoint/pci-epf-core.c +++ b/drivers/pci/endpoint/pci-epf-core.c @@ -137,6 +137,20 @@ void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar) } EXPORT_SYMBOL_GPL(pci_epf_alloc_space); +static void pci_epf_remove_cfs(struct pci_epf_driver *driver) +{ + struct config_group *group, *tmp; + + if (!IS_ENABLED(CONFIG_PCI_ENDPOINT_CONFIGFS)) + return; + + mutex_lock(&pci_epf_mutex); + list_for_each_entry_safe(group, tmp, &driver->epf_group, group_entry) + pci_ep_cfs_remove_epf_group(group); + list_del(&driver->epf_group); + mutex_unlock(&pci_epf_mutex); +} + /** * pci_epf_unregister_driver() - unregister the PCI EPF driver * @driver: the PCI EPF driver that has to be unregistered @@ -145,17 +159,38 @@ EXPORT_SYMBOL_GPL(pci_epf_alloc_space); */ void pci_epf_unregister_driver(struct pci_epf_driver *driver) { - struct config_group *group; - - mutex_lock(&pci_epf_mutex); - list_for_each_entry(group, &driver->epf_group, group_entry) - pci_ep_cfs_remove_epf_group(group); - list_del(&driver->epf_group); - mutex_unlock(&pci_epf_mutex); + pci_epf_remove_cfs(driver); driver_unregister(&driver->driver); } EXPORT_SYMBOL_GPL(pci_epf_unregister_driver); +static int pci_epf_add_cfs(struct pci_epf_driver *driver) +{ + struct config_group *group; + const struct pci_epf_device_id *id; + + if (!IS_ENABLED(CONFIG_PCI_ENDPOINT_CONFIGFS)) + return 0; + + INIT_LIST_HEAD(&driver->epf_group); + + id = driver->id_table; + while (id->name[0]) { + group = pci_ep_cfs_add_epf_group(id->name); + if (IS_ERR(group)) { + pci_epf_remove_cfs(driver); + return PTR_ERR(group); + } + + mutex_lock(&pci_epf_mutex); + list_add_tail(&group->group_entry, &driver->epf_group); + mutex_unlock(&pci_epf_mutex); + id++; + } + + return 0; +} + /** * __pci_epf_register_driver() - register a new PCI EPF driver * @driver: structure representing PCI EPF driver @@ -167,8 +202,6 @@ int __pci_epf_register_driver(struct pci_epf_driver *driver, struct module *owner) { int ret; - struct config_group *group; - const struct pci_epf_device_id *id; if (!driver->ops) return -EINVAL; @@ -183,16 +216,7 @@ int __pci_epf_register_driver(struct pci_epf_driver *driver, if (ret) return ret; - INIT_LIST_HEAD(&driver->epf_group); - - id = driver->id_table; - while (id->name[0]) { - group = pci_ep_cfs_add_epf_group(id->name); - mutex_lock(&pci_epf_mutex); - list_add_tail(&group->group_entry, &driver->epf_group); - mutex_unlock(&pci_epf_mutex); - id++; - } + pci_epf_add_cfs(driver); return 0; } diff --git a/drivers/pci/hotplug/acpi_pcihp.c b/drivers/pci/hotplug/acpi_pcihp.c index 3979f89b250a..6b7c1ed58e7e 100644 --- a/drivers/pci/hotplug/acpi_pcihp.c +++ b/drivers/pci/hotplug/acpi_pcihp.c @@ -7,7 +7,6 @@ * All rights reserved. * * Send feedback to <kristen.c.accardi@intel.com> - * */ #include <linux/module.h> @@ -75,23 +74,34 @@ int acpi_get_hp_hw_control_from_firmware(struct pci_dev *pdev) struct acpi_buffer string = { ACPI_ALLOCATE_BUFFER, NULL }; /* - * Per PCI firmware specification, we should run the ACPI _OSC - * method to get control of hotplug hardware before using it. If - * an _OSC is missing, we look for an OSHP to do the same thing. - * To handle different BIOS behavior, we look for _OSC on a root - * bridge preferentially (according to PCI fw spec). Later for - * OSHP within the scope of the hotplug controller and its parents, - * up to the host bridge under which this controller exists. + * If there's no ACPI host bridge (i.e., ACPI support is compiled + * into the kernel but the hardware platform doesn't support ACPI), + * there's nothing to do here. */ - if (shpchp_is_native(pdev)) - return 0; - - /* If _OSC exists, we should not evaluate OSHP */ host = pci_find_host_bridge(pdev->bus); root = acpi_pci_find_root(ACPI_HANDLE(&host->dev)); - if (root->osc_support_set) - goto no_control; + if (!root) + return 0; + /* + * If _OSC exists, it determines whether we're allowed to manage + * the SHPC. We executed it while enumerating the host bridge. + */ + if (root->osc_support_set) { + if (host->native_shpc_hotplug) + return 0; + return -ENODEV; + } + + /* + * In the absence of _OSC, we're always allowed to manage the SHPC. + * However, if an OSHP method is present, we must execute it so the + * firmware can transfer control to the OS, e.g., direct interrupts + * to the OS instead of to the firmware. + * + * N.B. The PCI Firmware Spec (r3.2, sec 4.8) does not endorse + * searching up the ACPI hierarchy, so the loops below are suspect. + */ handle = ACPI_HANDLE(&pdev->dev); if (!handle) { /* @@ -120,7 +130,7 @@ int acpi_get_hp_hw_control_from_firmware(struct pci_dev *pdev) if (ACPI_FAILURE(status)) break; } -no_control: + pci_info(pdev, "Cannot get control of SHPC hotplug\n"); kfree(string.pointer); return -ENODEV; diff --git a/drivers/pci/hotplug/acpiphp_core.c b/drivers/pci/hotplug/acpiphp_core.c index 12b5655fd390..ad32ffbc4b91 100644 --- a/drivers/pci/hotplug/acpiphp_core.c +++ b/drivers/pci/hotplug/acpiphp_core.c @@ -254,20 +254,6 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) return 0; } -/** - * release_slot - free up the memory used by a slot - * @hotplug_slot: slot to free - */ -static void release_slot(struct hotplug_slot *hotplug_slot) -{ - struct slot *slot = hotplug_slot->private; - - pr_debug("%s - physical_slot = %s\n", __func__, slot_name(slot)); - - kfree(slot->hotplug_slot); - kfree(slot); -} - /* callback routine to initialize 'struct slot' for each slot */ int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot, unsigned int sun) @@ -287,7 +273,6 @@ int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot, slot->hotplug_slot->info = &slot->info; slot->hotplug_slot->private = slot; - slot->hotplug_slot->release = &release_slot; slot->hotplug_slot->ops = &acpi_hotplug_slot_ops; slot->acpi_slot = acpiphp_slot; @@ -324,13 +309,12 @@ error: void acpiphp_unregister_hotplug_slot(struct acpiphp_slot *acpiphp_slot) { struct slot *slot = acpiphp_slot->slot; - int retval = 0; pr_info("Slot [%s] unregistered\n", slot_name(slot)); - retval = pci_hp_deregister(slot->hotplug_slot); - if (retval) - pr_err("pci_hp_deregister failed with error %d\n", retval); + pci_hp_deregister(slot->hotplug_slot); + kfree(slot->hotplug_slot); + kfree(slot); } diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index 3a17b290df5d..ef0b1b6ba86f 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -509,7 +509,7 @@ static void enable_slot(struct acpiphp_slot *slot) list_for_each_entry(dev, &bus->devices, bus_list) { /* Assume that newly added devices are powered on already. */ - if (!dev->is_added) + if (!pci_dev_is_added(dev)) dev->current_state = PCI_D0; } diff --git a/drivers/pci/hotplug/cpci_hotplug_core.c b/drivers/pci/hotplug/cpci_hotplug_core.c index 07b533adc9df..52a339baf06c 100644 --- a/drivers/pci/hotplug/cpci_hotplug_core.c +++ b/drivers/pci/hotplug/cpci_hotplug_core.c @@ -195,10 +195,8 @@ get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) return 0; } -static void release_slot(struct hotplug_slot *hotplug_slot) +static void release_slot(struct slot *slot) { - struct slot *slot = hotplug_slot->private; - kfree(slot->hotplug_slot->info); kfree(slot->hotplug_slot); pci_dev_put(slot->dev); @@ -253,7 +251,6 @@ cpci_hp_register_bus(struct pci_bus *bus, u8 first, u8 last) snprintf(name, SLOT_NAME_SIZE, "%02x:%02x", bus->number, i); hotplug_slot->private = slot; - hotplug_slot->release = &release_slot; hotplug_slot->ops = &cpci_hotplug_slot_ops; /* @@ -308,12 +305,8 @@ cpci_hp_unregister_bus(struct pci_bus *bus) slots--; dbg("deregistering slot %s", slot_name(slot)); - status = pci_hp_deregister(slot->hotplug_slot); - if (status) { - err("pci_hp_deregister failed with error %d", - status); - break; - } + pci_hp_deregister(slot->hotplug_slot); + release_slot(slot); } } up_write(&list_rwsem); @@ -623,6 +616,7 @@ cleanup_slots(void) list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) { list_del(&slot->slot_list); pci_hp_deregister(slot->hotplug_slot); + release_slot(slot); } cleanup_null: up_write(&list_rwsem); diff --git a/drivers/pci/hotplug/cpqphp_core.c b/drivers/pci/hotplug/cpqphp_core.c index 1797e36ec586..5a06636e910a 100644 --- a/drivers/pci/hotplug/cpqphp_core.c +++ b/drivers/pci/hotplug/cpqphp_core.c @@ -266,17 +266,6 @@ static void __iomem *get_SMBIOS_entry(void __iomem *smbios_start, return previous; } -static void release_slot(struct hotplug_slot *hotplug_slot) -{ - struct slot *slot = hotplug_slot->private; - - dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); - - kfree(slot->hotplug_slot->info); - kfree(slot->hotplug_slot); - kfree(slot); -} - static int ctrl_slot_cleanup(struct controller *ctrl) { struct slot *old_slot, *next_slot; @@ -285,9 +274,11 @@ static int ctrl_slot_cleanup(struct controller *ctrl) ctrl->slot = NULL; while (old_slot) { - /* memory will be freed by the release_slot callback */ next_slot = old_slot->next; pci_hp_deregister(old_slot->hotplug_slot); + kfree(old_slot->hotplug_slot->info); + kfree(old_slot->hotplug_slot); + kfree(old_slot); old_slot = next_slot; } @@ -678,7 +669,6 @@ static int ctrl_slot_setup(struct controller *ctrl, ((read_slot_enable(ctrl) << 2) >> ctrl_slot) & 0x04; /* register this slot with the hotplug pci core */ - hotplug_slot->release = &release_slot; hotplug_slot->private = slot; snprintf(name, SLOT_NAME_SIZE, "%u", slot->number); hotplug_slot->ops = &cpqphp_hotplug_slot_ops; diff --git a/drivers/pci/hotplug/ibmphp_core.c b/drivers/pci/hotplug/ibmphp_core.c index 1869b0411ce0..4ea57e9019f1 100644 --- a/drivers/pci/hotplug/ibmphp_core.c +++ b/drivers/pci/hotplug/ibmphp_core.c @@ -673,7 +673,20 @@ static void free_slots(void) list_for_each_entry_safe(slot_cur, next, &ibmphp_slot_head, ibm_slot_list) { - pci_hp_deregister(slot_cur->hotplug_slot); + pci_hp_del(slot_cur->hotplug_slot); + slot_cur->ctrl = NULL; + slot_cur->bus_on = NULL; + + /* + * We don't want to actually remove the resources, + * since ibmphp_free_resources() will do just that. + */ + ibmphp_unconfigure_card(&slot_cur, -1); + + pci_hp_destroy(slot_cur->hotplug_slot); + kfree(slot_cur->hotplug_slot->info); + kfree(slot_cur->hotplug_slot); + kfree(slot_cur); } debug("%s -- exit\n", __func__); } diff --git a/drivers/pci/hotplug/ibmphp_ebda.c b/drivers/pci/hotplug/ibmphp_ebda.c index 64549aa24c0f..6f8e90e3ec08 100644 --- a/drivers/pci/hotplug/ibmphp_ebda.c +++ b/drivers/pci/hotplug/ibmphp_ebda.c @@ -699,25 +699,6 @@ static int fillslotinfo(struct hotplug_slot *hotplug_slot) return rc; } -static void release_slot(struct hotplug_slot *hotplug_slot) -{ - struct slot *slot; - - if (!hotplug_slot || !hotplug_slot->private) - return; - - slot = hotplug_slot->private; - kfree(slot->hotplug_slot->info); - kfree(slot->hotplug_slot); - slot->ctrl = NULL; - slot->bus_on = NULL; - - /* we don't want to actually remove the resources, since free_resources will do just that */ - ibmphp_unconfigure_card(&slot, -1); - - kfree(slot); -} - static struct pci_driver ibmphp_driver; /* @@ -941,7 +922,6 @@ static int __init ebda_rsrc_controller(void) tmp_slot->hotplug_slot = hp_slot_ptr; hp_slot_ptr->private = tmp_slot; - hp_slot_ptr->release = release_slot; rc = fillslotinfo(hp_slot_ptr); if (rc) diff --git a/drivers/pci/hotplug/pci_hotplug_core.c b/drivers/pci/hotplug/pci_hotplug_core.c index af92fed46ab7..90fde5f106d8 100644 --- a/drivers/pci/hotplug/pci_hotplug_core.c +++ b/drivers/pci/hotplug/pci_hotplug_core.c @@ -396,8 +396,9 @@ static struct hotplug_slot *get_slot_from_name(const char *name) * @owner: caller module owner * @mod_name: caller module name * - * Registers a hotplug slot with the pci hotplug subsystem, which will allow - * userspace interaction to the slot. + * Prepares a hotplug slot for in-kernel use and immediately publishes it to + * user space in one go. Drivers may alternatively carry out the two steps + * separately by invoking pci_hp_initialize() and pci_hp_add(). * * Returns 0 if successful, anything else for an error. */ @@ -406,45 +407,91 @@ int __pci_hp_register(struct hotplug_slot *slot, struct pci_bus *bus, struct module *owner, const char *mod_name) { int result; + + result = __pci_hp_initialize(slot, bus, devnr, name, owner, mod_name); + if (result) + return result; + + result = pci_hp_add(slot); + if (result) + pci_hp_destroy(slot); + + return result; +} +EXPORT_SYMBOL_GPL(__pci_hp_register); + +/** + * __pci_hp_initialize - prepare hotplug slot for in-kernel use + * @slot: pointer to the &struct hotplug_slot to initialize + * @bus: bus this slot is on + * @devnr: slot number + * @name: name registered with kobject core + * @owner: caller module owner + * @mod_name: caller module name + * + * Allocate and fill in a PCI slot for use by a hotplug driver. Once this has + * been called, the driver may invoke hotplug_slot_name() to get the slot's + * unique name. The driver must be prepared to handle a ->reset_slot callback + * from this point on. + * + * Returns 0 on success or a negative int on error. + */ +int __pci_hp_initialize(struct hotplug_slot *slot, struct pci_bus *bus, + int devnr, const char *name, struct module *owner, + const char *mod_name) +{ struct pci_slot *pci_slot; if (slot == NULL) return -ENODEV; if ((slot->info == NULL) || (slot->ops == NULL)) return -EINVAL; - if (slot->release == NULL) { - dbg("Why are you trying to register a hotplug slot without a proper release function?\n"); - return -EINVAL; - } slot->ops->owner = owner; slot->ops->mod_name = mod_name; - mutex_lock(&pci_hp_mutex); /* * No problems if we call this interface from both ACPI_PCI_SLOT * driver and call it here again. If we've already created the * pci_slot, the interface will simply bump the refcount. */ pci_slot = pci_create_slot(bus, devnr, name, slot); - if (IS_ERR(pci_slot)) { - result = PTR_ERR(pci_slot); - goto out; - } + if (IS_ERR(pci_slot)) + return PTR_ERR(pci_slot); slot->pci_slot = pci_slot; pci_slot->hotplug = slot; + return 0; +} +EXPORT_SYMBOL_GPL(__pci_hp_initialize); - list_add(&slot->slot_list, &pci_hotplug_slot_list); +/** + * pci_hp_add - publish hotplug slot to user space + * @slot: pointer to the &struct hotplug_slot to publish + * + * Make a hotplug slot's sysfs interface available and inform user space of its + * addition by sending a uevent. The hotplug driver must be prepared to handle + * all &struct hotplug_slot_ops callbacks from this point on. + * + * Returns 0 on success or a negative int on error. + */ +int pci_hp_add(struct hotplug_slot *slot) +{ + struct pci_slot *pci_slot = slot->pci_slot; + int result; result = fs_add_slot(pci_slot); + if (result) + return result; + kobject_uevent(&pci_slot->kobj, KOBJ_ADD); - dbg("Added slot %s to the list\n", name); -out: + mutex_lock(&pci_hp_mutex); + list_add(&slot->slot_list, &pci_hotplug_slot_list); mutex_unlock(&pci_hp_mutex); - return result; + dbg("Added slot %s to the list\n", hotplug_slot_name(slot)); + return 0; } -EXPORT_SYMBOL_GPL(__pci_hp_register); +EXPORT_SYMBOL_GPL(pci_hp_add); /** * pci_hp_deregister - deregister a hotplug_slot with the PCI hotplug subsystem @@ -455,35 +502,62 @@ EXPORT_SYMBOL_GPL(__pci_hp_register); * * Returns 0 if successful, anything else for an error. */ -int pci_hp_deregister(struct hotplug_slot *slot) +void pci_hp_deregister(struct hotplug_slot *slot) +{ + pci_hp_del(slot); + pci_hp_destroy(slot); +} +EXPORT_SYMBOL_GPL(pci_hp_deregister); + +/** + * pci_hp_del - unpublish hotplug slot from user space + * @slot: pointer to the &struct hotplug_slot to unpublish + * + * Remove a hotplug slot's sysfs interface. + * + * Returns 0 on success or a negative int on error. + */ +void pci_hp_del(struct hotplug_slot *slot) { struct hotplug_slot *temp; - struct pci_slot *pci_slot; - if (!slot) - return -ENODEV; + if (WARN_ON(!slot)) + return; mutex_lock(&pci_hp_mutex); temp = get_slot_from_name(hotplug_slot_name(slot)); - if (temp != slot) { + if (WARN_ON(temp != slot)) { mutex_unlock(&pci_hp_mutex); - return -ENODEV; + return; } list_del(&slot->slot_list); - - pci_slot = slot->pci_slot; - fs_remove_slot(pci_slot); + mutex_unlock(&pci_hp_mutex); dbg("Removed slot %s from the list\n", hotplug_slot_name(slot)); + fs_remove_slot(slot->pci_slot); +} +EXPORT_SYMBOL_GPL(pci_hp_del); - slot->release(slot); +/** + * pci_hp_destroy - remove hotplug slot from in-kernel use + * @slot: pointer to the &struct hotplug_slot to destroy + * + * Destroy a PCI slot used by a hotplug driver. Once this has been called, + * the driver may no longer invoke hotplug_slot_name() to get the slot's + * unique name. The driver no longer needs to handle a ->reset_slot callback + * from this point on. + * + * Returns 0 on success or a negative int on error. + */ +void pci_hp_destroy(struct hotplug_slot *slot) +{ + struct pci_slot *pci_slot = slot->pci_slot; + + slot->pci_slot = NULL; pci_slot->hotplug = NULL; pci_destroy_slot(pci_slot); - mutex_unlock(&pci_hp_mutex); - - return 0; } -EXPORT_SYMBOL_GPL(pci_hp_deregister); +EXPORT_SYMBOL_GPL(pci_hp_destroy); /** * pci_hp_change_slot_info - changes the slot's information structure in the core diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h index 5f892065585e..811cf83f956d 100644 --- a/drivers/pci/hotplug/pciehp.h +++ b/drivers/pci/hotplug/pciehp.h @@ -21,6 +21,7 @@ #include <linux/delay.h> #include <linux/sched/signal.h> /* signal_pending() */ #include <linux/mutex.h> +#include <linux/rwsem.h> #include <linux/workqueue.h> #include "../pcie/portdrv.h" @@ -57,49 +58,111 @@ do { \ dev_warn(&ctrl->pcie->device, format, ## arg) #define SLOT_NAME_SIZE 10 + +/** + * struct slot - PCIe hotplug slot + * @state: current state machine position + * @ctrl: pointer to the slot's controller structure + * @hotplug_slot: pointer to the structure registered with the PCI hotplug core + * @work: work item to turn the slot on or off after 5 seconds in response to + * an Attention Button press + * @lock: protects reads and writes of @state; + * protects scheduling, execution and cancellation of @work + */ struct slot { u8 state; struct controller *ctrl; struct hotplug_slot *hotplug_slot; - struct delayed_work work; /* work for button event */ + struct delayed_work work; struct mutex lock; - struct mutex hotplug_lock; - struct workqueue_struct *wq; -}; - -struct event_info { - u32 event_type; - struct slot *p_slot; - struct work_struct work; }; +/** + * struct controller - PCIe hotplug controller + * @ctrl_lock: serializes writes to the Slot Control register + * @pcie: pointer to the controller's PCIe port service device + * @reset_lock: prevents access to the Data Link Layer Link Active bit in the + * Link Status register and to the Presence Detect State bit in the Slot + * Status register during a slot reset which may cause them to flap + * @slot: pointer to the controller's slot structure + * @queue: wait queue to wake up on reception of a Command Completed event, + * used for synchronous writes to the Slot Control register + * @slot_cap: cached copy of the Slot Capabilities register + * @slot_ctrl: cached copy of the Slot Control register + * @poll_thread: thread to poll for slot events if no IRQ is available, + * enabled with pciehp_poll_mode module parameter + * @cmd_started: jiffies when the Slot Control register was last written; + * the next write is allowed 1 second later, absent a Command Completed + * interrupt (PCIe r4.0, sec 6.7.3.2) + * @cmd_busy: flag set on Slot Control register write, cleared by IRQ handler + * on reception of a Command Completed event + * @link_active_reporting: cached copy of Data Link Layer Link Active Reporting + * Capable bit in Link Capabilities register; if this bit is zero, the + * Data Link Layer Link Active bit in the Link Status register will never + * be set and the driver is thus confined to wait 1 second before assuming + * the link to a hotplugged device is up and accessing it + * @notification_enabled: whether the IRQ was requested successfully + * @power_fault_detected: whether a power fault was detected by the hardware + * that has not yet been cleared by the user + * @pending_events: used by the IRQ handler to save events retrieved from the + * Slot Status register for later consumption by the IRQ thread + * @request_result: result of last user request submitted to the IRQ thread + * @requester: wait queue to wake up on completion of user request, + * used for synchronous slot enable/disable request via sysfs + */ struct controller { - struct mutex ctrl_lock; /* controller lock */ - struct pcie_device *pcie; /* PCI Express port service */ + struct mutex ctrl_lock; + struct pcie_device *pcie; + struct rw_semaphore reset_lock; struct slot *slot; - wait_queue_head_t queue; /* sleep & wake process */ + wait_queue_head_t queue; u32 slot_cap; u16 slot_ctrl; - struct timer_list poll_timer; + struct task_struct *poll_thread; unsigned long cmd_started; /* jiffies */ unsigned int cmd_busy:1; unsigned int link_active_reporting:1; unsigned int notification_enabled:1; unsigned int power_fault_detected; + atomic_t pending_events; + int request_result; + wait_queue_head_t requester; }; -#define INT_PRESENCE_ON 1 -#define INT_PRESENCE_OFF 2 -#define INT_POWER_FAULT 3 -#define INT_BUTTON_PRESS 4 -#define INT_LINK_UP 5 -#define INT_LINK_DOWN 6 - -#define STATIC_STATE 0 +/** + * DOC: Slot state + * + * @OFF_STATE: slot is powered off, no subordinate devices are enumerated + * @BLINKINGON_STATE: slot will be powered on after the 5 second delay, + * green led is blinking + * @BLINKINGOFF_STATE: slot will be powered off after the 5 second delay, + * green led is blinking + * @POWERON_STATE: slot is currently powering on + * @POWEROFF_STATE: slot is currently powering off + * @ON_STATE: slot is powered on, subordinate devices have been enumerated + */ +#define OFF_STATE 0 #define BLINKINGON_STATE 1 #define BLINKINGOFF_STATE 2 #define POWERON_STATE 3 #define POWEROFF_STATE 4 +#define ON_STATE 5 + +/** + * DOC: Flags to request an action from the IRQ thread + * + * These are stored together with events read from the Slot Status register, + * hence must be greater than its 16-bit width. + * + * %DISABLE_SLOT: Disable the slot in response to a user request via sysfs or + * an Attention Button press after the 5 second delay + * %RERUN_ISR: Used by the IRQ handler to inform the IRQ thread that the + * hotplug port was inaccessible when the interrupt occurred, requiring + * that the IRQ handler is rerun by the IRQ thread after it has made the + * hotplug port accessible by runtime resuming its parents to D0 + */ +#define DISABLE_SLOT (1 << 16) +#define RERUN_ISR (1 << 17) #define ATTN_BUTTN(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_ABP) #define POWER_CTRL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PCP) @@ -113,15 +176,17 @@ struct controller { int pciehp_sysfs_enable_slot(struct slot *slot); int pciehp_sysfs_disable_slot(struct slot *slot); -void pciehp_queue_interrupt_event(struct slot *slot, u32 event_type); +void pciehp_request(struct controller *ctrl, int action); +void pciehp_handle_button_press(struct slot *slot); +void pciehp_handle_disable_request(struct slot *slot); +void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events); int pciehp_configure_device(struct slot *p_slot); -int pciehp_unconfigure_device(struct slot *p_slot); +void pciehp_unconfigure_device(struct slot *p_slot); void pciehp_queue_pushbutton_work(struct work_struct *work); struct controller *pcie_init(struct pcie_device *dev); int pcie_init_notification(struct controller *ctrl); -int pciehp_enable_slot(struct slot *p_slot); -int pciehp_disable_slot(struct slot *p_slot); -void pcie_reenable_notification(struct controller *ctrl); +void pcie_shutdown_notification(struct controller *ctrl); +void pcie_clear_hotplug_events(struct controller *ctrl); int pciehp_power_on_slot(struct slot *slot); void pciehp_power_off_slot(struct slot *slot); void pciehp_get_power_status(struct slot *slot, u8 *status); diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c index 44a6a63802d5..ec48c9433ae5 100644 --- a/drivers/pci/hotplug/pciehp_core.c +++ b/drivers/pci/hotplug/pciehp_core.c @@ -26,11 +26,12 @@ #include <linux/interrupt.h> #include <linux/time.h> +#include "../pci.h" + /* Global variables */ bool pciehp_debug; bool pciehp_poll_mode; int pciehp_poll_time; -static bool pciehp_force; /* * not really modular, but the easiest way to keep compat with existing @@ -39,11 +40,9 @@ static bool pciehp_force; module_param(pciehp_debug, bool, 0644); module_param(pciehp_poll_mode, bool, 0644); module_param(pciehp_poll_time, int, 0644); -module_param(pciehp_force, bool, 0644); MODULE_PARM_DESC(pciehp_debug, "Debugging mode enabled or not"); MODULE_PARM_DESC(pciehp_poll_mode, "Using polling mechanism for hot-plug events or not"); MODULE_PARM_DESC(pciehp_poll_time, "Polling mechanism frequency, in seconds"); -MODULE_PARM_DESC(pciehp_force, "Force pciehp, even if OSHP is missing"); #define PCIE_MODULE_NAME "pciehp" @@ -56,17 +55,6 @@ static int get_latch_status(struct hotplug_slot *slot, u8 *value); static int get_adapter_status(struct hotplug_slot *slot, u8 *value); static int reset_slot(struct hotplug_slot *slot, int probe); -/** - * release_slot - free up the memory used by a slot - * @hotplug_slot: slot to free - */ -static void release_slot(struct hotplug_slot *hotplug_slot) -{ - kfree(hotplug_slot->ops); - kfree(hotplug_slot->info); - kfree(hotplug_slot); -} - static int init_slot(struct controller *ctrl) { struct slot *slot = ctrl->slot; @@ -107,15 +95,14 @@ static int init_slot(struct controller *ctrl) /* register this slot with the hotplug pci core */ hotplug->info = info; hotplug->private = slot; - hotplug->release = &release_slot; hotplug->ops = ops; slot->hotplug_slot = hotplug; snprintf(name, SLOT_NAME_SIZE, "%u", PSN(ctrl)); - retval = pci_hp_register(hotplug, - ctrl->pcie->port->subordinate, 0, name); + retval = pci_hp_initialize(hotplug, + ctrl->pcie->port->subordinate, 0, name); if (retval) - ctrl_err(ctrl, "pci_hp_register failed: error %d\n", retval); + ctrl_err(ctrl, "pci_hp_initialize failed: error %d\n", retval); out: if (retval) { kfree(ops); @@ -127,7 +114,12 @@ out: static void cleanup_slot(struct controller *ctrl) { - pci_hp_deregister(ctrl->slot->hotplug_slot); + struct hotplug_slot *hotplug_slot = ctrl->slot->hotplug_slot; + + pci_hp_destroy(hotplug_slot); + kfree(hotplug_slot->ops); + kfree(hotplug_slot->info); + kfree(hotplug_slot); } /* @@ -136,8 +128,11 @@ static void cleanup_slot(struct controller *ctrl) static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) { struct slot *slot = hotplug_slot->private; + struct pci_dev *pdev = slot->ctrl->pcie->port; + pci_config_pm_runtime_get(pdev); pciehp_set_attention_status(slot, status); + pci_config_pm_runtime_put(pdev); return 0; } @@ -160,8 +155,11 @@ static int disable_slot(struct hotplug_slot *hotplug_slot) static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) { struct slot *slot = hotplug_slot->private; + struct pci_dev *pdev = slot->ctrl->pcie->port; + pci_config_pm_runtime_get(pdev); pciehp_get_power_status(slot, value); + pci_config_pm_runtime_put(pdev); return 0; } @@ -176,16 +174,22 @@ static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) { struct slot *slot = hotplug_slot->private; + struct pci_dev *pdev = slot->ctrl->pcie->port; + pci_config_pm_runtime_get(pdev); pciehp_get_latch_status(slot, value); + pci_config_pm_runtime_put(pdev); return 0; } static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) { struct slot *slot = hotplug_slot->private; + struct pci_dev *pdev = slot->ctrl->pcie->port; + pci_config_pm_runtime_get(pdev); pciehp_get_adapter_status(slot, value); + pci_config_pm_runtime_put(pdev); return 0; } @@ -196,12 +200,40 @@ static int reset_slot(struct hotplug_slot *hotplug_slot, int probe) return pciehp_reset_slot(slot, probe); } +/** + * pciehp_check_presence() - synthesize event if presence has changed + * + * On probe and resume, an explicit presence check is necessary to bring up an + * occupied slot or bring down an unoccupied slot. This can't be triggered by + * events in the Slot Status register, they may be stale and are therefore + * cleared. Secondly, sending an interrupt for "events that occur while + * interrupt generation is disabled [when] interrupt generation is subsequently + * enabled" is optional per PCIe r4.0, sec 6.7.3.4. + */ +static void pciehp_check_presence(struct controller *ctrl) +{ + struct slot *slot = ctrl->slot; + u8 occupied; + + down_read(&ctrl->reset_lock); + mutex_lock(&slot->lock); + + pciehp_get_adapter_status(slot, &occupied); + if ((occupied && (slot->state == OFF_STATE || + slot->state == BLINKINGON_STATE)) || + (!occupied && (slot->state == ON_STATE || + slot->state == BLINKINGOFF_STATE))) + pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); + + mutex_unlock(&slot->lock); + up_read(&ctrl->reset_lock); +} + static int pciehp_probe(struct pcie_device *dev) { int rc; struct controller *ctrl; struct slot *slot; - u8 occupied, poweron; /* If this is not a "hotplug" service, we have no business here. */ if (dev->service != PCIE_PORT_SERVICE_HP) @@ -238,21 +270,20 @@ static int pciehp_probe(struct pcie_device *dev) goto err_out_free_ctrl_slot; } - /* Check if slot is occupied */ + /* Publish to user space */ slot = ctrl->slot; - pciehp_get_adapter_status(slot, &occupied); - pciehp_get_power_status(slot, &poweron); - if (occupied && pciehp_force) { - mutex_lock(&slot->hotplug_lock); - pciehp_enable_slot(slot); - mutex_unlock(&slot->hotplug_lock); + rc = pci_hp_add(slot->hotplug_slot); + if (rc) { + ctrl_err(ctrl, "Publication to user space failed (%d)\n", rc); + goto err_out_shutdown_notification; } - /* If empty slot's power status is on, turn power off */ - if (!occupied && poweron && POWER_CTRL(ctrl)) - pciehp_power_off_slot(slot); + + pciehp_check_presence(ctrl); return 0; +err_out_shutdown_notification: + pcie_shutdown_notification(ctrl); err_out_free_ctrl_slot: cleanup_slot(ctrl); err_out_release_ctlr: @@ -264,6 +295,8 @@ static void pciehp_remove(struct pcie_device *dev) { struct controller *ctrl = get_service_data(dev); + pci_hp_del(ctrl->slot->hotplug_slot); + pcie_shutdown_notification(ctrl); cleanup_slot(ctrl); pciehp_release_ctrl(ctrl); } @@ -274,27 +307,28 @@ static int pciehp_suspend(struct pcie_device *dev) return 0; } -static int pciehp_resume(struct pcie_device *dev) +static int pciehp_resume_noirq(struct pcie_device *dev) { - struct controller *ctrl; - struct slot *slot; - u8 status; + struct controller *ctrl = get_service_data(dev); + struct slot *slot = ctrl->slot; - ctrl = get_service_data(dev); + /* pci_restore_state() just wrote to the Slot Control register */ + ctrl->cmd_started = jiffies; + ctrl->cmd_busy = true; - /* reinitialize the chipset's event detection logic */ - pcie_reenable_notification(ctrl); + /* clear spurious events from rediscovery of inserted card */ + if (slot->state == ON_STATE || slot->state == BLINKINGOFF_STATE) + pcie_clear_hotplug_events(ctrl); - slot = ctrl->slot; + return 0; +} + +static int pciehp_resume(struct pcie_device *dev) +{ + struct controller *ctrl = get_service_data(dev); + + pciehp_check_presence(ctrl); - /* Check if slot is occupied */ - pciehp_get_adapter_status(slot, &status); - mutex_lock(&slot->hotplug_lock); - if (status) - pciehp_enable_slot(slot); - else - pciehp_disable_slot(slot); - mutex_unlock(&slot->hotplug_lock); return 0; } #endif /* PM */ @@ -309,6 +343,7 @@ static struct pcie_port_service_driver hpdriver_portdrv = { #ifdef CONFIG_PM .suspend = pciehp_suspend, + .resume_noirq = pciehp_resume_noirq, .resume = pciehp_resume, #endif /* PM */ }; diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index cfc46e82ceca..da7c72372ffc 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -17,28 +17,11 @@ #include <linux/kernel.h> #include <linux/types.h> #include <linux/slab.h> +#include <linux/pm_runtime.h> #include <linux/pci.h> #include "../pci.h" #include "pciehp.h" -static void interrupt_event_handler(struct work_struct *work); - -void pciehp_queue_interrupt_event(struct slot *p_slot, u32 event_type) -{ - struct event_info *info; - - info = kmalloc(sizeof(*info), GFP_ATOMIC); - if (!info) { - ctrl_err(p_slot->ctrl, "dropped event %d (ENOMEM)\n", event_type); - return; - } - - INIT_WORK(&info->work, interrupt_event_handler); - info->event_type = event_type; - info->p_slot = p_slot; - queue_work(p_slot->wq, &info->work); -} - /* The following routines constitute the bulk of the hotplug controller logic */ @@ -119,14 +102,11 @@ err_exit: * remove_board - Turns off slot and LEDs * @p_slot: slot where board is being removed */ -static int remove_board(struct slot *p_slot) +static void remove_board(struct slot *p_slot) { - int retval; struct controller *ctrl = p_slot->ctrl; - retval = pciehp_unconfigure_device(p_slot); - if (retval) - return retval; + pciehp_unconfigure_device(p_slot); if (POWER_CTRL(ctrl)) { pciehp_power_off_slot(p_slot); @@ -141,86 +121,30 @@ static int remove_board(struct slot *p_slot) /* turn off Green LED */ pciehp_green_led_off(p_slot); - return 0; } -struct power_work_info { - struct slot *p_slot; - struct work_struct work; - unsigned int req; -#define DISABLE_REQ 0 -#define ENABLE_REQ 1 -}; +static int pciehp_enable_slot(struct slot *slot); +static int pciehp_disable_slot(struct slot *slot); -/** - * pciehp_power_thread - handle pushbutton events - * @work: &struct work_struct describing work to be done - * - * Scheduled procedure to handle blocking stuff for the pushbuttons. - * Handles all pending events and exits. - */ -static void pciehp_power_thread(struct work_struct *work) +void pciehp_request(struct controller *ctrl, int action) { - struct power_work_info *info = - container_of(work, struct power_work_info, work); - struct slot *p_slot = info->p_slot; - int ret; - - switch (info->req) { - case DISABLE_REQ: - mutex_lock(&p_slot->hotplug_lock); - pciehp_disable_slot(p_slot); - mutex_unlock(&p_slot->hotplug_lock); - mutex_lock(&p_slot->lock); - p_slot->state = STATIC_STATE; - mutex_unlock(&p_slot->lock); - break; - case ENABLE_REQ: - mutex_lock(&p_slot->hotplug_lock); - ret = pciehp_enable_slot(p_slot); - mutex_unlock(&p_slot->hotplug_lock); - if (ret) - pciehp_green_led_off(p_slot); - mutex_lock(&p_slot->lock); - p_slot->state = STATIC_STATE; - mutex_unlock(&p_slot->lock); - break; - default: - break; - } - - kfree(info); -} - -static void pciehp_queue_power_work(struct slot *p_slot, int req) -{ - struct power_work_info *info; - - p_slot->state = (req == ENABLE_REQ) ? POWERON_STATE : POWEROFF_STATE; - - info = kmalloc(sizeof(*info), GFP_KERNEL); - if (!info) { - ctrl_err(p_slot->ctrl, "no memory to queue %s request\n", - (req == ENABLE_REQ) ? "poweron" : "poweroff"); - return; - } - info->p_slot = p_slot; - INIT_WORK(&info->work, pciehp_power_thread); - info->req = req; - queue_work(p_slot->wq, &info->work); + atomic_or(action, &ctrl->pending_events); + if (!pciehp_poll_mode) + irq_wake_thread(ctrl->pcie->irq, ctrl); } void pciehp_queue_pushbutton_work(struct work_struct *work) { struct slot *p_slot = container_of(work, struct slot, work.work); + struct controller *ctrl = p_slot->ctrl; mutex_lock(&p_slot->lock); switch (p_slot->state) { case BLINKINGOFF_STATE: - pciehp_queue_power_work(p_slot, DISABLE_REQ); + pciehp_request(ctrl, DISABLE_SLOT); break; case BLINKINGON_STATE: - pciehp_queue_power_work(p_slot, ENABLE_REQ); + pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); break; default: break; @@ -228,18 +152,15 @@ void pciehp_queue_pushbutton_work(struct work_struct *work) mutex_unlock(&p_slot->lock); } -/* - * Note: This function must be called with slot->lock held - */ -static void handle_button_press_event(struct slot *p_slot) +void pciehp_handle_button_press(struct slot *p_slot) { struct controller *ctrl = p_slot->ctrl; - u8 getstatus; + mutex_lock(&p_slot->lock); switch (p_slot->state) { - case STATIC_STATE: - pciehp_get_power_status(p_slot, &getstatus); - if (getstatus) { + case OFF_STATE: + case ON_STATE: + if (p_slot->state == ON_STATE) { p_slot->state = BLINKINGOFF_STATE; ctrl_info(ctrl, "Slot(%s): Powering off due to button press\n", slot_name(p_slot)); @@ -251,7 +172,7 @@ static void handle_button_press_event(struct slot *p_slot) /* blink green LED and turn off amber */ pciehp_green_led_blink(p_slot); pciehp_set_attention_status(p_slot, 0); - queue_delayed_work(p_slot->wq, &p_slot->work, 5*HZ); + schedule_delayed_work(&p_slot->work, 5 * HZ); break; case BLINKINGOFF_STATE: case BLINKINGON_STATE: @@ -262,118 +183,104 @@ static void handle_button_press_event(struct slot *p_slot) */ ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(p_slot)); cancel_delayed_work(&p_slot->work); - if (p_slot->state == BLINKINGOFF_STATE) + if (p_slot->state == BLINKINGOFF_STATE) { + p_slot->state = ON_STATE; pciehp_green_led_on(p_slot); - else + } else { + p_slot->state = OFF_STATE; pciehp_green_led_off(p_slot); + } pciehp_set_attention_status(p_slot, 0); ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n", slot_name(p_slot)); - p_slot->state = STATIC_STATE; - break; - case POWEROFF_STATE: - case POWERON_STATE: - /* - * Ignore if the slot is on power-on or power-off state; - * this means that the previous attention button action - * to hot-add or hot-remove is undergoing - */ - ctrl_info(ctrl, "Slot(%s): Button ignored\n", - slot_name(p_slot)); break; default: ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", slot_name(p_slot), p_slot->state); break; } + mutex_unlock(&p_slot->lock); } -/* - * Note: This function must be called with slot->lock held - */ -static void handle_link_event(struct slot *p_slot, u32 event) +void pciehp_handle_disable_request(struct slot *slot) { - struct controller *ctrl = p_slot->ctrl; + struct controller *ctrl = slot->ctrl; - switch (p_slot->state) { + mutex_lock(&slot->lock); + switch (slot->state) { case BLINKINGON_STATE: case BLINKINGOFF_STATE: - cancel_delayed_work(&p_slot->work); - /* Fall through */ - case STATIC_STATE: - pciehp_queue_power_work(p_slot, event == INT_LINK_UP ? - ENABLE_REQ : DISABLE_REQ); - break; - case POWERON_STATE: - if (event == INT_LINK_UP) { - ctrl_info(ctrl, "Slot(%s): Link Up event ignored; already powering on\n", - slot_name(p_slot)); - } else { - ctrl_info(ctrl, "Slot(%s): Link Down event queued; currently getting powered on\n", - slot_name(p_slot)); - pciehp_queue_power_work(p_slot, DISABLE_REQ); - } - break; - case POWEROFF_STATE: - if (event == INT_LINK_UP) { - ctrl_info(ctrl, "Slot(%s): Link Up event queued; currently getting powered off\n", - slot_name(p_slot)); - pciehp_queue_power_work(p_slot, ENABLE_REQ); - } else { - ctrl_info(ctrl, "Slot(%s): Link Down event ignored; already powering off\n", - slot_name(p_slot)); - } - break; - default: - ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", - slot_name(p_slot), p_slot->state); + cancel_delayed_work(&slot->work); break; } + slot->state = POWEROFF_STATE; + mutex_unlock(&slot->lock); + + ctrl->request_result = pciehp_disable_slot(slot); } -static void interrupt_event_handler(struct work_struct *work) +void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) { - struct event_info *info = container_of(work, struct event_info, work); - struct slot *p_slot = info->p_slot; - struct controller *ctrl = p_slot->ctrl; - - mutex_lock(&p_slot->lock); - switch (info->event_type) { - case INT_BUTTON_PRESS: - handle_button_press_event(p_slot); - break; - case INT_POWER_FAULT: - if (!POWER_CTRL(ctrl)) - break; - pciehp_set_attention_status(p_slot, 1); - pciehp_green_led_off(p_slot); - break; - case INT_PRESENCE_ON: - pciehp_queue_power_work(p_slot, ENABLE_REQ); + struct controller *ctrl = slot->ctrl; + bool link_active; + u8 present; + + /* + * If the slot is on and presence or link has changed, turn it off. + * Even if it's occupied again, we cannot assume the card is the same. + */ + mutex_lock(&slot->lock); + switch (slot->state) { + case BLINKINGOFF_STATE: + cancel_delayed_work(&slot->work); + /* fall through */ + case ON_STATE: + slot->state = POWEROFF_STATE; + mutex_unlock(&slot->lock); + if (events & PCI_EXP_SLTSTA_DLLSC) + ctrl_info(ctrl, "Slot(%s): Link Down\n", + slot_name(slot)); + if (events & PCI_EXP_SLTSTA_PDC) + ctrl_info(ctrl, "Slot(%s): Card not present\n", + slot_name(slot)); + pciehp_disable_slot(slot); break; - case INT_PRESENCE_OFF: - /* - * Regardless of surprise capability, we need to - * definitely remove a card that has been pulled out! - */ - pciehp_queue_power_work(p_slot, DISABLE_REQ); + default: + mutex_unlock(&slot->lock); break; - case INT_LINK_UP: - case INT_LINK_DOWN: - handle_link_event(p_slot, info->event_type); + } + + /* Turn the slot on if it's occupied or link is up */ + mutex_lock(&slot->lock); + pciehp_get_adapter_status(slot, &present); + link_active = pciehp_check_link_active(ctrl); + if (!present && !link_active) { + mutex_unlock(&slot->lock); + return; + } + + switch (slot->state) { + case BLINKINGON_STATE: + cancel_delayed_work(&slot->work); + /* fall through */ + case OFF_STATE: + slot->state = POWERON_STATE; + mutex_unlock(&slot->lock); + if (present) + ctrl_info(ctrl, "Slot(%s): Card present\n", + slot_name(slot)); + if (link_active) + ctrl_info(ctrl, "Slot(%s): Link Up\n", + slot_name(slot)); + ctrl->request_result = pciehp_enable_slot(slot); break; default: + mutex_unlock(&slot->lock); break; } - mutex_unlock(&p_slot->lock); - - kfree(info); } -/* - * Note: This function must be called with slot->hotplug_lock held - */ -int pciehp_enable_slot(struct slot *p_slot) +static int __pciehp_enable_slot(struct slot *p_slot) { u8 getstatus = 0; struct controller *ctrl = p_slot->ctrl; @@ -404,17 +311,29 @@ int pciehp_enable_slot(struct slot *p_slot) return board_added(p_slot); } -/* - * Note: This function must be called with slot->hotplug_lock held - */ -int pciehp_disable_slot(struct slot *p_slot) +static int pciehp_enable_slot(struct slot *slot) +{ + struct controller *ctrl = slot->ctrl; + int ret; + + pm_runtime_get_sync(&ctrl->pcie->port->dev); + ret = __pciehp_enable_slot(slot); + if (ret && ATTN_BUTTN(ctrl)) + pciehp_green_led_off(slot); /* may be blinking */ + pm_runtime_put(&ctrl->pcie->port->dev); + + mutex_lock(&slot->lock); + slot->state = ret ? OFF_STATE : ON_STATE; + mutex_unlock(&slot->lock); + + return ret; +} + +static int __pciehp_disable_slot(struct slot *p_slot) { u8 getstatus = 0; struct controller *ctrl = p_slot->ctrl; - if (!p_slot->ctrl) - return 1; - if (POWER_CTRL(p_slot->ctrl)) { pciehp_get_power_status(p_slot, &getstatus); if (!getstatus) { @@ -424,33 +343,50 @@ int pciehp_disable_slot(struct slot *p_slot) } } - return remove_board(p_slot); + remove_board(p_slot); + return 0; +} + +static int pciehp_disable_slot(struct slot *slot) +{ + struct controller *ctrl = slot->ctrl; + int ret; + + pm_runtime_get_sync(&ctrl->pcie->port->dev); + ret = __pciehp_disable_slot(slot); + pm_runtime_put(&ctrl->pcie->port->dev); + + mutex_lock(&slot->lock); + slot->state = OFF_STATE; + mutex_unlock(&slot->lock); + + return ret; } int pciehp_sysfs_enable_slot(struct slot *p_slot) { - int retval = -ENODEV; struct controller *ctrl = p_slot->ctrl; mutex_lock(&p_slot->lock); switch (p_slot->state) { case BLINKINGON_STATE: - cancel_delayed_work(&p_slot->work); - /* fall through */ - case STATIC_STATE: - p_slot->state = POWERON_STATE; + case OFF_STATE: mutex_unlock(&p_slot->lock); - mutex_lock(&p_slot->hotplug_lock); - retval = pciehp_enable_slot(p_slot); - mutex_unlock(&p_slot->hotplug_lock); - mutex_lock(&p_slot->lock); - p_slot->state = STATIC_STATE; - break; + /* + * The IRQ thread becomes a no-op if the user pulls out the + * card before the thread wakes up, so initialize to -ENODEV. + */ + ctrl->request_result = -ENODEV; + pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); + wait_event(ctrl->requester, + !atomic_read(&ctrl->pending_events)); + return ctrl->request_result; case POWERON_STATE: ctrl_info(ctrl, "Slot(%s): Already in powering on state\n", slot_name(p_slot)); break; case BLINKINGOFF_STATE: + case ON_STATE: case POWEROFF_STATE: ctrl_info(ctrl, "Slot(%s): Already enabled\n", slot_name(p_slot)); @@ -462,33 +398,28 @@ int pciehp_sysfs_enable_slot(struct slot *p_slot) } mutex_unlock(&p_slot->lock); - return retval; + return -ENODEV; } int pciehp_sysfs_disable_slot(struct slot *p_slot) { - int retval = -ENODEV; struct controller *ctrl = p_slot->ctrl; mutex_lock(&p_slot->lock); switch (p_slot->state) { case BLINKINGOFF_STATE: - cancel_delayed_work(&p_slot->work); - /* fall through */ - case STATIC_STATE: - p_slot->state = POWEROFF_STATE; + case ON_STATE: mutex_unlock(&p_slot->lock); - mutex_lock(&p_slot->hotplug_lock); - retval = pciehp_disable_slot(p_slot); - mutex_unlock(&p_slot->hotplug_lock); - mutex_lock(&p_slot->lock); - p_slot->state = STATIC_STATE; - break; + pciehp_request(ctrl, DISABLE_SLOT); + wait_event(ctrl->requester, + !atomic_read(&ctrl->pending_events)); + return ctrl->request_result; case POWEROFF_STATE: ctrl_info(ctrl, "Slot(%s): Already in powering off state\n", slot_name(p_slot)); break; case BLINKINGON_STATE: + case OFF_STATE: case POWERON_STATE: ctrl_info(ctrl, "Slot(%s): Already disabled\n", slot_name(p_slot)); @@ -500,5 +431,5 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot) } mutex_unlock(&p_slot->lock); - return retval; + return -ENODEV; } diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index 718b6073afad..5b15e76f3564 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -17,8 +17,9 @@ #include <linux/types.h> #include <linux/signal.h> #include <linux/jiffies.h> -#include <linux/timer.h> +#include <linux/kthread.h> #include <linux/pci.h> +#include <linux/pm_runtime.h> #include <linux/interrupt.h> #include <linux/time.h> #include <linux/slab.h> @@ -31,47 +32,24 @@ static inline struct pci_dev *ctrl_dev(struct controller *ctrl) return ctrl->pcie->port; } -static irqreturn_t pcie_isr(int irq, void *dev_id); -static void start_int_poll_timer(struct controller *ctrl, int sec); - -/* This is the interrupt polling timeout function. */ -static void int_poll_timeout(struct timer_list *t) -{ - struct controller *ctrl = from_timer(ctrl, t, poll_timer); - - /* Poll for interrupt events. regs == NULL => polling */ - pcie_isr(0, ctrl); - - if (!pciehp_poll_time) - pciehp_poll_time = 2; /* default polling interval is 2 sec */ - - start_int_poll_timer(ctrl, pciehp_poll_time); -} - -/* This function starts the interrupt polling timer. */ -static void start_int_poll_timer(struct controller *ctrl, int sec) -{ - /* Clamp to sane value */ - if ((sec <= 0) || (sec > 60)) - sec = 2; - - ctrl->poll_timer.expires = jiffies + sec * HZ; - add_timer(&ctrl->poll_timer); -} +static irqreturn_t pciehp_isr(int irq, void *dev_id); +static irqreturn_t pciehp_ist(int irq, void *dev_id); +static int pciehp_poll(void *data); static inline int pciehp_request_irq(struct controller *ctrl) { int retval, irq = ctrl->pcie->irq; - /* Install interrupt polling timer. Start with 10 sec delay */ if (pciehp_poll_mode) { - timer_setup(&ctrl->poll_timer, int_poll_timeout, 0); - start_int_poll_timer(ctrl, 10); - return 0; + ctrl->poll_thread = kthread_run(&pciehp_poll, ctrl, + "pciehp_poll-%s", + slot_name(ctrl->slot)); + return PTR_ERR_OR_ZERO(ctrl->poll_thread); } /* Installs the interrupt handler */ - retval = request_irq(irq, pcie_isr, IRQF_SHARED, MY_NAME, ctrl); + retval = request_threaded_irq(irq, pciehp_isr, pciehp_ist, + IRQF_SHARED, MY_NAME, ctrl); if (retval) ctrl_err(ctrl, "Cannot get irq %d for the hotplug controller\n", irq); @@ -81,7 +59,7 @@ static inline int pciehp_request_irq(struct controller *ctrl) static inline void pciehp_free_irq(struct controller *ctrl) { if (pciehp_poll_mode) - del_timer_sync(&ctrl->poll_timer); + kthread_stop(ctrl->poll_thread); else free_irq(ctrl->pcie->irq, ctrl); } @@ -293,6 +271,11 @@ int pciehp_check_link_status(struct controller *ctrl) found = pci_bus_check_dev(ctrl->pcie->port->subordinate, PCI_DEVFN(0, 0)); + /* ignore link or presence changes up to this point */ + if (found) + atomic_and(~(PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC), + &ctrl->pending_events); + pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status); ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status); if ((lnk_status & PCI_EXP_LNKSTA_LT) || @@ -339,7 +322,9 @@ int pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot, struct pci_dev *pdev = ctrl_dev(slot->ctrl); u16 slot_ctrl; + pci_config_pm_runtime_get(pdev); pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + pci_config_pm_runtime_put(pdev); *status = (slot_ctrl & (PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC)) >> 6; return 0; } @@ -350,7 +335,9 @@ void pciehp_get_attention_status(struct slot *slot, u8 *status) struct pci_dev *pdev = ctrl_dev(ctrl); u16 slot_ctrl; + pci_config_pm_runtime_get(pdev); pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + pci_config_pm_runtime_put(pdev); ctrl_dbg(ctrl, "%s: SLOTCTRL %x, value read %x\n", __func__, pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, slot_ctrl); @@ -425,9 +412,12 @@ int pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot, { struct slot *slot = hotplug_slot->private; struct controller *ctrl = slot->ctrl; + struct pci_dev *pdev = ctrl_dev(ctrl); + pci_config_pm_runtime_get(pdev); pcie_write_cmd_nowait(ctrl, status << 6, PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC); + pci_config_pm_runtime_put(pdev); return 0; } @@ -539,20 +529,35 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) { struct controller *ctrl = (struct controller *)dev_id; struct pci_dev *pdev = ctrl_dev(ctrl); - struct pci_bus *subordinate = pdev->subordinate; - struct pci_dev *dev; - struct slot *slot = ctrl->slot; + struct device *parent = pdev->dev.parent; u16 status, events; - u8 present; - bool link; - /* Interrupts cannot originate from a controller that's asleep */ + /* + * Interrupts only occur in D3hot or shallower (PCIe r4.0, sec 6.7.3.4). + */ if (pdev->current_state == PCI_D3cold) return IRQ_NONE; + /* + * Keep the port accessible by holding a runtime PM ref on its parent. + * Defer resume of the parent to the IRQ thread if it's suspended. + * Mask the interrupt until then. + */ + if (parent) { + pm_runtime_get_noresume(parent); + if (!pm_runtime_active(parent)) { + pm_runtime_put(parent); + disable_irq_nosync(irq); + atomic_or(RERUN_ISR, &ctrl->pending_events); + return IRQ_WAKE_THREAD; + } + } + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status); if (status == (u16) ~0) { ctrl_info(ctrl, "%s: no response from device\n", __func__); + if (parent) + pm_runtime_put(parent); return IRQ_NONE; } @@ -571,86 +576,119 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) if (ctrl->power_fault_detected) events &= ~PCI_EXP_SLTSTA_PFD; - if (!events) + if (!events) { + if (parent) + pm_runtime_put(parent); return IRQ_NONE; - - /* Capture link status before clearing interrupts */ - if (events & PCI_EXP_SLTSTA_DLLSC) - link = pciehp_check_link_active(ctrl); + } pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, events); ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events); + if (parent) + pm_runtime_put(parent); - /* Check Command Complete Interrupt Pending */ + /* + * Command Completed notifications are not deferred to the + * IRQ thread because it may be waiting for their arrival. + */ if (events & PCI_EXP_SLTSTA_CC) { ctrl->cmd_busy = 0; smp_mb(); wake_up(&ctrl->queue); + + if (events == PCI_EXP_SLTSTA_CC) + return IRQ_HANDLED; + + events &= ~PCI_EXP_SLTSTA_CC; + } + + if (pdev->ignore_hotplug) { + ctrl_dbg(ctrl, "ignoring hotplug event %#06x\n", events); + return IRQ_HANDLED; } - if (subordinate) { - list_for_each_entry(dev, &subordinate->devices, bus_list) { - if (dev->ignore_hotplug) { - ctrl_dbg(ctrl, "ignoring hotplug event %#06x (%s requested no hotplug)\n", - events, pci_name(dev)); - return IRQ_HANDLED; - } + /* Save pending events for consumption by IRQ thread. */ + atomic_or(events, &ctrl->pending_events); + return IRQ_WAKE_THREAD; +} + +static irqreturn_t pciehp_ist(int irq, void *dev_id) +{ + struct controller *ctrl = (struct controller *)dev_id; + struct pci_dev *pdev = ctrl_dev(ctrl); + struct slot *slot = ctrl->slot; + irqreturn_t ret; + u32 events; + + pci_config_pm_runtime_get(pdev); + + /* rerun pciehp_isr() if the port was inaccessible on interrupt */ + if (atomic_fetch_and(~RERUN_ISR, &ctrl->pending_events) & RERUN_ISR) { + ret = pciehp_isr(irq, dev_id); + enable_irq(irq); + if (ret != IRQ_WAKE_THREAD) { + pci_config_pm_runtime_put(pdev); + return ret; } } + synchronize_hardirq(irq); + events = atomic_xchg(&ctrl->pending_events, 0); + if (!events) { + pci_config_pm_runtime_put(pdev); + return IRQ_NONE; + } + /* Check Attention Button Pressed */ if (events & PCI_EXP_SLTSTA_ABP) { ctrl_info(ctrl, "Slot(%s): Attention button pressed\n", slot_name(slot)); - pciehp_queue_interrupt_event(slot, INT_BUTTON_PRESS); + pciehp_handle_button_press(slot); } /* - * Check Link Status Changed at higher precedence than Presence - * Detect Changed. The PDS value may be set to "card present" from - * out-of-band detection, which may be in conflict with a Link Down - * and cause the wrong event to queue. + * Disable requests have higher priority than Presence Detect Changed + * or Data Link Layer State Changed events. */ - if (events & PCI_EXP_SLTSTA_DLLSC) { - ctrl_info(ctrl, "Slot(%s): Link %s\n", slot_name(slot), - link ? "Up" : "Down"); - pciehp_queue_interrupt_event(slot, link ? INT_LINK_UP : - INT_LINK_DOWN); - } else if (events & PCI_EXP_SLTSTA_PDC) { - present = !!(status & PCI_EXP_SLTSTA_PDS); - ctrl_info(ctrl, "Slot(%s): Card %spresent\n", slot_name(slot), - present ? "" : "not "); - pciehp_queue_interrupt_event(slot, present ? INT_PRESENCE_ON : - INT_PRESENCE_OFF); - } + down_read(&ctrl->reset_lock); + if (events & DISABLE_SLOT) + pciehp_handle_disable_request(slot); + else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) + pciehp_handle_presence_or_link_change(slot, events); + up_read(&ctrl->reset_lock); /* Check Power Fault Detected */ if ((events & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) { ctrl->power_fault_detected = 1; ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(slot)); - pciehp_queue_interrupt_event(slot, INT_POWER_FAULT); + pciehp_set_attention_status(slot, 1); + pciehp_green_led_off(slot); } + pci_config_pm_runtime_put(pdev); + wake_up(&ctrl->requester); return IRQ_HANDLED; } -static irqreturn_t pcie_isr(int irq, void *dev_id) +static int pciehp_poll(void *data) { - irqreturn_t rc, handled = IRQ_NONE; + struct controller *ctrl = data; - /* - * To guarantee that all interrupt events are serviced, we need to - * re-inspect Slot Status register after clearing what is presumed - * to be the last pending interrupt. - */ - do { - rc = pciehp_isr(irq, dev_id); - if (rc == IRQ_HANDLED) - handled = IRQ_HANDLED; - } while (rc == IRQ_HANDLED); + schedule_timeout_idle(10 * HZ); /* start with 10 sec delay */ + + while (!kthread_should_stop()) { + /* poll for interrupt events or user requests */ + while (pciehp_isr(IRQ_NOTCONNECTED, ctrl) == IRQ_WAKE_THREAD || + atomic_read(&ctrl->pending_events)) + pciehp_ist(IRQ_NOTCONNECTED, ctrl); + + if (pciehp_poll_time <= 0 || pciehp_poll_time > 60) + pciehp_poll_time = 2; /* clamp to sane value */ + + schedule_timeout_idle(pciehp_poll_time * HZ); + } - /* Return IRQ_HANDLED if we handled one or more events */ - return handled; + return 0; } static void pcie_enable_notification(struct controller *ctrl) @@ -691,17 +729,6 @@ static void pcie_enable_notification(struct controller *ctrl) pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, cmd); } -void pcie_reenable_notification(struct controller *ctrl) -{ - /* - * Clear both Presence and Data Link Layer Changed to make sure - * those events still fire after we have re-enabled them. - */ - pcie_capability_write_word(ctrl->pcie->port, PCI_EXP_SLTSTA, - PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); - pcie_enable_notification(ctrl); -} - static void pcie_disable_notification(struct controller *ctrl) { u16 mask; @@ -715,6 +742,12 @@ static void pcie_disable_notification(struct controller *ctrl) pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0); } +void pcie_clear_hotplug_events(struct controller *ctrl) +{ + pcie_capability_write_word(ctrl_dev(ctrl), PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); +} + /* * pciehp has a 1:1 bus:slot relationship so we ultimately want a secondary * bus reset of the bridge, but at the same time we want to ensure that it is @@ -732,6 +765,8 @@ int pciehp_reset_slot(struct slot *slot, int probe) if (probe) return 0; + down_write(&ctrl->reset_lock); + if (!ATTN_BUTTN(ctrl)) { ctrl_mask |= PCI_EXP_SLTCTL_PDCE; stat_mask |= PCI_EXP_SLTSTA_PDC; @@ -742,8 +777,6 @@ int pciehp_reset_slot(struct slot *slot, int probe) pcie_write_cmd(ctrl, 0, ctrl_mask); ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0); - if (pciehp_poll_mode) - del_timer_sync(&ctrl->poll_timer); pci_reset_bridge_secondary_bus(ctrl->pcie->port); @@ -751,8 +784,8 @@ int pciehp_reset_slot(struct slot *slot, int probe) pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask); ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask); - if (pciehp_poll_mode) - int_poll_timeout(&ctrl->poll_timer); + + up_write(&ctrl->reset_lock); return 0; } @@ -765,7 +798,7 @@ int pcie_init_notification(struct controller *ctrl) return 0; } -static void pcie_shutdown_notification(struct controller *ctrl) +void pcie_shutdown_notification(struct controller *ctrl) { if (ctrl->notification_enabled) { pcie_disable_notification(ctrl); @@ -776,32 +809,29 @@ static void pcie_shutdown_notification(struct controller *ctrl) static int pcie_init_slot(struct controller *ctrl) { + struct pci_bus *subordinate = ctrl_dev(ctrl)->subordinate; struct slot *slot; slot = kzalloc(sizeof(*slot), GFP_KERNEL); if (!slot) return -ENOMEM; - slot->wq = alloc_ordered_workqueue("pciehp-%u", 0, PSN(ctrl)); - if (!slot->wq) - goto abort; + down_read(&pci_bus_sem); + slot->state = list_empty(&subordinate->devices) ? OFF_STATE : ON_STATE; + up_read(&pci_bus_sem); slot->ctrl = ctrl; mutex_init(&slot->lock); - mutex_init(&slot->hotplug_lock); INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work); ctrl->slot = slot; return 0; -abort: - kfree(slot); - return -ENOMEM; } static void pcie_cleanup_slot(struct controller *ctrl) { struct slot *slot = ctrl->slot; - cancel_delayed_work(&slot->work); - destroy_workqueue(slot->wq); + + cancel_delayed_work_sync(&slot->work); kfree(slot); } @@ -826,6 +856,7 @@ struct controller *pcie_init(struct pcie_device *dev) { struct controller *ctrl; u32 slot_cap, link_cap; + u8 occupied, poweron; struct pci_dev *pdev = dev->port; ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); @@ -847,6 +878,8 @@ struct controller *pcie_init(struct pcie_device *dev) ctrl->slot_cap = slot_cap; mutex_init(&ctrl->ctrl_lock); + init_rwsem(&ctrl->reset_lock); + init_waitqueue_head(&ctrl->requester); init_waitqueue_head(&ctrl->queue); dbg_ctrl(ctrl); @@ -855,16 +888,11 @@ struct controller *pcie_init(struct pcie_device *dev) if (link_cap & PCI_EXP_LNKCAP_DLLLARC) ctrl->link_active_reporting = 1; - /* - * Clear all remaining event bits in Slot Status register except - * Presence Detect Changed. We want to make sure possible - * hotplug event is triggered when the interrupt is unmasked so - * that we don't lose that event. - */ + /* Clear all remaining event bits in Slot Status register. */ pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | PCI_EXP_SLTSTA_MRLSC | PCI_EXP_SLTSTA_CC | - PCI_EXP_SLTSTA_DLLSC); + PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC); ctrl_info(ctrl, "Slot #%d AttnBtn%c PwrCtrl%c MRL%c AttnInd%c PwrInd%c HotPlug%c Surprise%c Interlock%c NoCompl%c LLActRep%c%s\n", (slot_cap & PCI_EXP_SLTCAP_PSN) >> 19, @@ -883,6 +911,19 @@ struct controller *pcie_init(struct pcie_device *dev) if (pcie_init_slot(ctrl)) goto abort_ctrl; + /* + * If empty slot's power status is on, turn power off. The IRQ isn't + * requested yet, so avoid triggering a notification with this command. + */ + if (POWER_CTRL(ctrl)) { + pciehp_get_adapter_status(ctrl->slot, &occupied); + pciehp_get_power_status(ctrl->slot, &poweron); + if (!occupied && poweron) { + pcie_disable_notification(ctrl); + pciehp_power_off_slot(ctrl->slot); + } + } + return ctrl; abort_ctrl: @@ -893,7 +934,6 @@ abort: void pciehp_release_ctrl(struct controller *ctrl) { - pcie_shutdown_notification(ctrl); pcie_cleanup_slot(ctrl); kfree(ctrl); } diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c index 3f518dea856d..5c58c22e0c08 100644 --- a/drivers/pci/hotplug/pciehp_pci.c +++ b/drivers/pci/hotplug/pciehp_pci.c @@ -62,9 +62,8 @@ int pciehp_configure_device(struct slot *p_slot) return ret; } -int pciehp_unconfigure_device(struct slot *p_slot) +void pciehp_unconfigure_device(struct slot *p_slot) { - int rc = 0; u8 presence = 0; struct pci_dev *dev, *temp; struct pci_bus *parent = p_slot->ctrl->pcie->port->subordinate; @@ -107,5 +106,4 @@ int pciehp_unconfigure_device(struct slot *p_slot) } pci_unlock_rescan_remove(); - return rc; } diff --git a/drivers/pci/hotplug/pcihp_skeleton.c b/drivers/pci/hotplug/pcihp_skeleton.c deleted file mode 100644 index c19694a04d2c..000000000000 --- a/drivers/pci/hotplug/pcihp_skeleton.c +++ /dev/null @@ -1,348 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * PCI Hot Plug Controller Skeleton Driver - 0.3 - * - * Copyright (C) 2001,2003 Greg Kroah-Hartman (greg@kroah.com) - * Copyright (C) 2001,2003 IBM Corp. - * - * All rights reserved. - * - * This driver is to be used as a skeleton driver to show how to interface - * with the pci hotplug core easily. - * - * Send feedback to <greg@kroah.com> - * - */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/kernel.h> -#include <linux/slab.h> -#include <linux/pci.h> -#include <linux/pci_hotplug.h> -#include <linux/init.h> - -#define SLOT_NAME_SIZE 10 -struct slot { - u8 number; - struct hotplug_slot *hotplug_slot; - struct list_head slot_list; - char name[SLOT_NAME_SIZE]; -}; - -static LIST_HEAD(slot_list); - -#define MY_NAME "pcihp_skeleton" - -#define dbg(format, arg...) \ - do { \ - if (debug) \ - printk(KERN_DEBUG "%s: " format "\n", \ - MY_NAME, ## arg); \ - } while (0) -#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME, ## arg) -#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME, ## arg) -#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME, ## arg) - -/* local variables */ -static bool debug; -static int num_slots; - -#define DRIVER_VERSION "0.3" -#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>" -#define DRIVER_DESC "Hot Plug PCI Controller Skeleton Driver" - -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); -module_param(debug, bool, 0644); -MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); - -static int enable_slot(struct hotplug_slot *slot); -static int disable_slot(struct hotplug_slot *slot); -static int set_attention_status(struct hotplug_slot *slot, u8 value); -static int hardware_test(struct hotplug_slot *slot, u32 value); -static int get_power_status(struct hotplug_slot *slot, u8 *value); -static int get_attention_status(struct hotplug_slot *slot, u8 *value); -static int get_latch_status(struct hotplug_slot *slot, u8 *value); -static int get_adapter_status(struct hotplug_slot *slot, u8 *value); - -static struct hotplug_slot_ops skel_hotplug_slot_ops = { - .enable_slot = enable_slot, - .disable_slot = disable_slot, - .set_attention_status = set_attention_status, - .hardware_test = hardware_test, - .get_power_status = get_power_status, - .get_attention_status = get_attention_status, - .get_latch_status = get_latch_status, - .get_adapter_status = get_adapter_status, -}; - -static int enable_slot(struct hotplug_slot *hotplug_slot) -{ - struct slot *slot = hotplug_slot->private; - int retval = 0; - - dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); - - /* - * Fill in code here to enable the specified slot - */ - - return retval; -} - -static int disable_slot(struct hotplug_slot *hotplug_slot) -{ - struct slot *slot = hotplug_slot->private; - int retval = 0; - - dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); - - /* - * Fill in code here to disable the specified slot - */ - - return retval; -} - -static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) -{ - struct slot *slot = hotplug_slot->private; - int retval = 0; - - dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); - - switch (status) { - case 0: - /* - * Fill in code here to turn light off - */ - break; - - case 1: - default: - /* - * Fill in code here to turn light on - */ - break; - } - - return retval; -} - -static int hardware_test(struct hotplug_slot *hotplug_slot, u32 value) -{ - struct slot *slot = hotplug_slot->private; - int retval = 0; - - dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); - - switch (value) { - case 0: - /* Specify a test here */ - break; - case 1: - /* Specify another test here */ - break; - } - - return retval; -} - -static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) -{ - struct slot *slot = hotplug_slot->private; - int retval = 0; - - dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); - - /* - * Fill in logic to get the current power status of the specific - * slot and store it in the *value location. - */ - - return retval; -} - -static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) -{ - struct slot *slot = hotplug_slot->private; - int retval = 0; - - dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); - - /* - * Fill in logic to get the current attention status of the specific - * slot and store it in the *value location. - */ - - return retval; -} - -static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) -{ - struct slot *slot = hotplug_slot->private; - int retval = 0; - - dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); - - /* - * Fill in logic to get the current latch status of the specific - * slot and store it in the *value location. - */ - - return retval; -} - -static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) -{ - struct slot *slot = hotplug_slot->private; - int retval = 0; - - dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); - - /* - * Fill in logic to get the current adapter status of the specific - * slot and store it in the *value location. - */ - - return retval; -} - -static void release_slot(struct hotplug_slot *hotplug_slot) -{ - struct slot *slot = hotplug_slot->private; - - dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); - kfree(slot->hotplug_slot->info); - kfree(slot->hotplug_slot); - kfree(slot); -} - -static void make_slot_name(struct slot *slot) -{ - /* - * Stupid way to make a filename out of the slot name. - * replace this if your hardware provides a better way to name slots. - */ - snprintf(slot->hotplug_slot->name, SLOT_NAME_SIZE, "%d", slot->number); -} - -/** - * init_slots - initialize 'struct slot' structures for each slot - * - */ -static int __init init_slots(void) -{ - struct slot *slot; - struct hotplug_slot *hotplug_slot; - struct hotplug_slot_info *info; - int retval; - int i; - - /* - * Create a structure for each slot, and register that slot - * with the pci_hotplug subsystem. - */ - for (i = 0; i < num_slots; ++i) { - slot = kzalloc(sizeof(*slot), GFP_KERNEL); - if (!slot) { - retval = -ENOMEM; - goto error; - } - - hotplug_slot = kzalloc(sizeof(*hotplug_slot), GFP_KERNEL); - if (!hotplug_slot) { - retval = -ENOMEM; - goto error_slot; - } - slot->hotplug_slot = hotplug_slot; - - info = kzalloc(sizeof(*info), GFP_KERNEL); - if (!info) { - retval = -ENOMEM; - goto error_hpslot; - } - hotplug_slot->info = info; - - slot->number = i; - - hotplug_slot->name = slot->name; - hotplug_slot->private = slot; - hotplug_slot->release = &release_slot; - make_slot_name(slot); - hotplug_slot->ops = &skel_hotplug_slot_ops; - - /* - * Initialize the slot info structure with some known - * good values. - */ - get_power_status(hotplug_slot, &info->power_status); - get_attention_status(hotplug_slot, &info->attention_status); - get_latch_status(hotplug_slot, &info->latch_status); - get_adapter_status(hotplug_slot, &info->adapter_status); - - dbg("registering slot %d\n", i); - retval = pci_hp_register(slot->hotplug_slot); - if (retval) { - err("pci_hp_register failed with error %d\n", retval); - goto error_info; - } - - /* add slot to our internal list */ - list_add(&slot->slot_list, &slot_list); - } - - return 0; -error_info: - kfree(info); -error_hpslot: - kfree(hotplug_slot); -error_slot: - kfree(slot); -error: - return retval; -} - -static void __exit cleanup_slots(void) -{ - struct slot *slot, *next; - - /* - * Unregister all of our slots with the pci_hotplug subsystem. - * Memory will be freed in release_slot() callback after slot's - * lifespan is finished. - */ - list_for_each_entry_safe(slot, next, &slot_list, slot_list) { - list_del(&slot->slot_list); - pci_hp_deregister(slot->hotplug_slot); - } -} - -static int __init pcihp_skel_init(void) -{ - int retval; - - info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); - /* - * Do specific initialization stuff for your driver here - * like initializing your controller hardware (if any) and - * determining the number of slots you have in the system - * right now. - */ - num_slots = 5; - - return init_slots(); -} - -static void __exit pcihp_skel_exit(void) -{ - /* - * Clean everything up. - */ - cleanup_slots(); -} - -module_init(pcihp_skel_init); -module_exit(pcihp_skel_exit); diff --git a/drivers/pci/hotplug/pnv_php.c b/drivers/pci/hotplug/pnv_php.c index 6c2e8d7307c6..3276a5e4c430 100644 --- a/drivers/pci/hotplug/pnv_php.c +++ b/drivers/pci/hotplug/pnv_php.c @@ -538,9 +538,8 @@ static struct hotplug_slot_ops php_slot_ops = { .disable_slot = pnv_php_disable_slot, }; -static void pnv_php_release(struct hotplug_slot *slot) +static void pnv_php_release(struct pnv_php_slot *php_slot) { - struct pnv_php_slot *php_slot = slot->private; unsigned long flags; /* Remove from global or child list */ @@ -596,7 +595,6 @@ static struct pnv_php_slot *pnv_php_alloc_slot(struct device_node *dn) php_slot->power_state_check = false; php_slot->slot.ops = &php_slot_ops; php_slot->slot.info = &php_slot->slot_info; - php_slot->slot.release = pnv_php_release; php_slot->slot.private = php_slot; INIT_LIST_HEAD(&php_slot->children); @@ -924,6 +922,7 @@ static void pnv_php_unregister_one(struct device_node *dn) php_slot->state = PNV_PHP_STATE_OFFLINE; pci_hp_deregister(&php_slot->slot); + pnv_php_release(php_slot); pnv_php_put_slot(php_slot); } diff --git a/drivers/pci/hotplug/rpaphp_core.c b/drivers/pci/hotplug/rpaphp_core.c index fb5e0845429d..857c358b727b 100644 --- a/drivers/pci/hotplug/rpaphp_core.c +++ b/drivers/pci/hotplug/rpaphp_core.c @@ -404,13 +404,13 @@ static void __exit cleanup_slots(void) /* * Unregister all of our slots with the pci_hotplug subsystem, * and free up all memory that we had allocated. - * memory will be freed in release_slot callback. */ list_for_each_entry_safe(slot, next, &rpaphp_slot_head, rpaphp_slot_list) { list_del(&slot->rpaphp_slot_list); pci_hp_deregister(slot->hotplug_slot); + dealloc_slot_struct(slot); } return; } diff --git a/drivers/pci/hotplug/rpaphp_slot.c b/drivers/pci/hotplug/rpaphp_slot.c index 3840a2075e6a..b916c8e4372d 100644 --- a/drivers/pci/hotplug/rpaphp_slot.c +++ b/drivers/pci/hotplug/rpaphp_slot.c @@ -19,12 +19,6 @@ #include "rpaphp.h" /* free up the memory used by a slot */ -static void rpaphp_release_slot(struct hotplug_slot *hotplug_slot) -{ - struct slot *slot = (struct slot *) hotplug_slot->private; - dealloc_slot_struct(slot); -} - void dealloc_slot_struct(struct slot *slot) { kfree(slot->hotplug_slot->info); @@ -56,7 +50,6 @@ struct slot *alloc_slot_struct(struct device_node *dn, slot->power_domain = power_domain; slot->hotplug_slot->private = slot; slot->hotplug_slot->ops = &rpaphp_hotplug_slot_ops; - slot->hotplug_slot->release = &rpaphp_release_slot; return (slot); @@ -90,10 +83,8 @@ int rpaphp_deregister_slot(struct slot *slot) __func__, slot->name); list_del(&slot->rpaphp_slot_list); - - retval = pci_hp_deregister(php_slot); - if (retval) - err("Problem unregistering a slot %s\n", slot->name); + pci_hp_deregister(php_slot); + dealloc_slot_struct(slot); dbg("%s - Exit: rc[%d]\n", __func__, retval); return retval; diff --git a/drivers/pci/hotplug/s390_pci_hpc.c b/drivers/pci/hotplug/s390_pci_hpc.c index ffdc2977395d..93b5341d282c 100644 --- a/drivers/pci/hotplug/s390_pci_hpc.c +++ b/drivers/pci/hotplug/s390_pci_hpc.c @@ -130,15 +130,6 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) return 0; } -static void release_slot(struct hotplug_slot *hotplug_slot) -{ - struct slot *slot = hotplug_slot->private; - - kfree(slot->hotplug_slot->info); - kfree(slot->hotplug_slot); - kfree(slot); -} - static struct hotplug_slot_ops s390_hotplug_slot_ops = { .enable_slot = enable_slot, .disable_slot = disable_slot, @@ -175,7 +166,6 @@ int zpci_init_slot(struct zpci_dev *zdev) hotplug_slot->info = info; hotplug_slot->ops = &s390_hotplug_slot_ops; - hotplug_slot->release = &release_slot; get_power_status(hotplug_slot, &info->power_status); get_adapter_status(hotplug_slot, &info->adapter_status); @@ -209,5 +199,8 @@ void zpci_exit_slot(struct zpci_dev *zdev) continue; list_del(&slot->slot_list); pci_hp_deregister(slot->hotplug_slot); + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot); + kfree(slot); } } diff --git a/drivers/pci/hotplug/sgi_hotplug.c b/drivers/pci/hotplug/sgi_hotplug.c index 78b6bdbb3a39..babd23409f61 100644 --- a/drivers/pci/hotplug/sgi_hotplug.c +++ b/drivers/pci/hotplug/sgi_hotplug.c @@ -628,7 +628,6 @@ static int sn_hotplug_slot_register(struct pci_bus *pci_bus) goto alloc_err; } bss_hotplug_slot->ops = &sn_hotplug_slot_ops; - bss_hotplug_slot->release = &sn_release_slot; rc = pci_hp_register(bss_hotplug_slot, pci_bus, device, name); if (rc) @@ -656,8 +655,10 @@ alloc_err: sn_release_slot(bss_hotplug_slot); /* destroy anything else on the list */ - while ((bss_hotplug_slot = sn_hp_destroy())) + while ((bss_hotplug_slot = sn_hp_destroy())) { pci_hp_deregister(bss_hotplug_slot); + sn_release_slot(bss_hotplug_slot); + } return rc; } @@ -703,8 +704,10 @@ static void __exit sn_pci_hotplug_exit(void) { struct hotplug_slot *bss_hotplug_slot; - while ((bss_hotplug_slot = sn_hp_destroy())) + while ((bss_hotplug_slot = sn_hp_destroy())) { pci_hp_deregister(bss_hotplug_slot); + sn_release_slot(bss_hotplug_slot); + } if (!list_empty(&sn_hp_list)) printk(KERN_ERR "%s: internal list is not empty\n", __FILE__); diff --git a/drivers/pci/hotplug/shpchp_core.c b/drivers/pci/hotplug/shpchp_core.c index e91be287f292..97cee23f3d51 100644 --- a/drivers/pci/hotplug/shpchp_core.c +++ b/drivers/pci/hotplug/shpchp_core.c @@ -61,22 +61,6 @@ static struct hotplug_slot_ops shpchp_hotplug_slot_ops = { .get_adapter_status = get_adapter_status, }; -/** - * release_slot - free up the memory used by a slot - * @hotplug_slot: slot to free - */ -static void release_slot(struct hotplug_slot *hotplug_slot) -{ - struct slot *slot = hotplug_slot->private; - - ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", - __func__, slot_name(slot)); - - kfree(slot->hotplug_slot->info); - kfree(slot->hotplug_slot); - kfree(slot); -} - static int init_slots(struct controller *ctrl) { struct slot *slot; @@ -125,7 +109,6 @@ static int init_slots(struct controller *ctrl) /* register this slot with the hotplug pci core */ hotplug_slot->private = slot; - hotplug_slot->release = &release_slot; snprintf(name, SLOT_NAME_SIZE, "%d", slot->number); hotplug_slot->ops = &shpchp_hotplug_slot_ops; @@ -171,6 +154,9 @@ void cleanup_slots(struct controller *ctrl) cancel_delayed_work(&slot->work); destroy_workqueue(slot->wq); pci_hp_deregister(slot->hotplug_slot); + kfree(slot->hotplug_slot->info); + kfree(slot->hotplug_slot); + kfree(slot); } } @@ -270,11 +256,30 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) return 0; } +static bool shpc_capable(struct pci_dev *bridge) +{ + /* + * It is assumed that AMD GOLAM chips support SHPC but they do not + * have SHPC capability. + */ + if (bridge->vendor == PCI_VENDOR_ID_AMD && + bridge->device == PCI_DEVICE_ID_AMD_GOLAM_7450) + return true; + + if (pci_find_capability(bridge, PCI_CAP_ID_SHPC)) + return true; + + return false; +} + static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { int rc; struct controller *ctrl; + if (!shpc_capable(pdev)) + return -ENODEV; + if (acpi_get_hp_hw_control_from_firmware(pdev)) return -ENODEV; @@ -303,6 +308,7 @@ static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (rc) goto err_cleanup_slots; + pdev->shpc_managed = 1; return 0; err_cleanup_slots: @@ -319,6 +325,7 @@ static void shpc_remove(struct pci_dev *dev) { struct controller *ctrl = pci_get_drvdata(dev); + dev->shpc_managed = 0; shpchp_remove_ctrl_files(ctrl); ctrl->hpc_ops->release_ctlr(ctrl); kfree(ctrl); diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index d0d73dbbd5ca..0f04ae648cf1 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c @@ -575,6 +575,22 @@ void pci_iov_release(struct pci_dev *dev) } /** + * pci_iov_remove - clean up SR-IOV state after PF driver is detached + * @dev: the PCI device + */ +void pci_iov_remove(struct pci_dev *dev) +{ + struct pci_sriov *iov = dev->sriov; + + if (!dev->is_physfn) + return; + + iov->driver_max_VFs = iov->total_VFs; + if (iov->num_VFs) + pci_warn(dev, "driver left SR-IOV enabled after remove\n"); +} + +/** * pci_iov_update_resource - update a VF BAR * @dev: the PCI device * @resno: the resource number diff --git a/drivers/pci/of.c b/drivers/pci/of.c index d088c9147f10..69a60d6ebd73 100644 --- a/drivers/pci/of.c +++ b/drivers/pci/of.c @@ -612,7 +612,7 @@ int pci_parse_request_of_pci_ranges(struct device *dev, switch (resource_type(res)) { case IORESOURCE_IO: - err = pci_remap_iospace(res, iobase); + err = devm_pci_remap_iospace(dev, res, iobase); if (err) { dev_warn(dev, "error %d: failed to map resource %pR\n", err, res); diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 65113b6eed14..5100fd2d5a75 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -403,24 +403,7 @@ bool pciehp_is_native(struct pci_dev *bridge) */ bool shpchp_is_native(struct pci_dev *bridge) { - const struct pci_host_bridge *host; - - if (!IS_ENABLED(CONFIG_HOTPLUG_PCI_SHPC)) - return false; - - /* - * It is assumed that AMD GOLAM chips support SHPC but they do not - * have SHPC capability. - */ - if (bridge->vendor == PCI_VENDOR_ID_AMD && - bridge->device == PCI_DEVICE_ID_AMD_GOLAM_7450) - return true; - - if (!pci_find_capability(bridge, PCI_CAP_ID_SHPC)) - return false; - - host = pci_find_host_bridge(bridge->bus); - return host->native_shpc_hotplug; + return bridge->shpc_managed; } /** diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 7a02f94fd231..bef17c3fca67 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -445,6 +445,7 @@ static int pci_device_remove(struct device *dev) } pcibios_free_irq(pci_dev); pci_dev->driver = NULL; + pci_iov_remove(pci_dev); } /* Undo the runtime PM settings in local_pci_probe() */ diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 0c4653c1d2ce..9ecfe13157c0 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -23,7 +23,6 @@ #include <linux/fs.h> #include <linux/capability.h> #include <linux/security.h> -#include <linux/pci-aspm.h> #include <linux/slab.h> #include <linux/vgaarb.h> #include <linux/pm_runtime.h> @@ -1449,7 +1448,9 @@ static ssize_t reset_store(struct device *dev, struct device_attribute *attr, if (val != 1) return -EINVAL; + pm_runtime_get_sync(dev); result = pci_reset_function(pdev); + pm_runtime_put(dev); if (result < 0) return result; @@ -1746,6 +1747,9 @@ static const struct attribute_group *pci_dev_attr_groups[] = { #endif &pci_bridge_attr_group, &pcie_dev_attr_group, +#ifdef CONFIG_PCIEAER + &aer_stats_attr_group, +#endif NULL, }; diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index f5c6ab14fb31..0306fa44653a 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -23,7 +23,6 @@ #include <linux/string.h> #include <linux/log2.h> #include <linux/logic_pio.h> -#include <linux/pci-aspm.h> #include <linux/pm_wakeup.h> #include <linux/interrupt.h> #include <linux/device.h> @@ -2291,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) { @@ -2306,18 +2305,27 @@ 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; + /* 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 + * 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. @@ -3580,6 +3588,44 @@ void pci_unmap_iospace(struct resource *res) } EXPORT_SYMBOL(pci_unmap_iospace); +static void devm_pci_unmap_iospace(struct device *dev, void *ptr) +{ + struct resource **res = ptr; + + pci_unmap_iospace(*res); +} + +/** + * devm_pci_remap_iospace - Managed pci_remap_iospace() + * @dev: Generic device to remap IO address for + * @res: Resource describing the I/O space + * @phys_addr: physical address of range to be mapped + * + * Managed pci_remap_iospace(). Map is automatically unmapped on driver + * detach. + */ +int devm_pci_remap_iospace(struct device *dev, const struct resource *res, + phys_addr_t phys_addr) +{ + const struct resource **ptr; + int error; + + ptr = devres_alloc(devm_pci_unmap_iospace, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + error = pci_remap_iospace(res, phys_addr); + if (error) { + devres_free(ptr); + } else { + *ptr = res; + devres_add(dev, ptr); + } + + return error; +} +EXPORT_SYMBOL(devm_pci_remap_iospace); + /** * devm_pci_remap_cfgspace - Managed pci_remap_cfgspace() * @dev: Generic device to remap IO address for @@ -5265,14 +5311,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; @@ -5282,11 +5330,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, @@ -5294,6 +5342,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 c358e7a07f3f..ccb0537d5d89 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -225,6 +225,10 @@ enum pci_bar_type { int pci_configure_extended_tags(struct pci_dev *dev, void *ign); bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *pl, int crs_timeout); +bool pci_bus_generic_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *pl, + int crs_timeout); +int pci_idt_bus_quirk(struct pci_bus *bus, int devfn, u32 *pl, int crs_timeout); + int pci_setup_device(struct pci_dev *dev); int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, struct resource *res, unsigned int reg); @@ -259,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 { @@ -288,6 +293,7 @@ struct pci_sriov { /* pci_dev priv_flags */ #define PCI_DEV_DISCONNECTED 0 +#define PCI_DEV_ADDED 1 static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused) { @@ -300,6 +306,44 @@ static inline bool pci_dev_is_disconnected(const struct pci_dev *dev) return test_bit(PCI_DEV_DISCONNECTED, &dev->priv_flags); } +static inline void pci_dev_assign_added(struct pci_dev *dev, bool added) +{ + assign_bit(PCI_DEV_ADDED, &dev->priv_flags, added); +} + +static inline bool pci_dev_is_added(const struct pci_dev *dev) +{ + return test_bit(PCI_DEV_ADDED, &dev->priv_flags); +} + +#ifdef CONFIG_PCIEAER +#include <linux/aer.h> + +#define AER_MAX_MULTI_ERR_DEVICES 5 /* Not likely to have more */ + +struct aer_err_info { + struct pci_dev *dev[AER_MAX_MULTI_ERR_DEVICES]; + int error_dev_num; + + unsigned int id:16; + + unsigned int severity:2; /* 0:NONFATAL | 1:FATAL | 2:COR */ + unsigned int __pad1:5; + unsigned int multi_error_valid:1; + + unsigned int first_error:5; + unsigned int __pad2:2; + unsigned int tlp_header_valid:1; + + unsigned int status; /* COR/UNCOR Error Status */ + unsigned int mask; /* COR/UNCOR Error Mask */ + struct aer_header_log_regs tlp; /* TLP Header */ +}; + +int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info); +void aer_print_error(struct pci_dev *dev, struct aer_err_info *info); +#endif /* CONFIG_PCIEAER */ + #ifdef CONFIG_PCI_ATS void pci_restore_ats_state(struct pci_dev *dev); #else @@ -311,6 +355,7 @@ static inline void pci_restore_ats_state(struct pci_dev *dev) #ifdef CONFIG_PCI_IOV int pci_iov_init(struct pci_dev *dev); void pci_iov_release(struct pci_dev *dev); +void pci_iov_remove(struct pci_dev *dev); void pci_iov_update_resource(struct pci_dev *dev, int resno); resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev, int resno); void pci_restore_iov_state(struct pci_dev *dev); @@ -325,6 +370,9 @@ static inline void pci_iov_release(struct pci_dev *dev) { } +static inline void pci_iov_remove(struct pci_dev *dev) +{ +} static inline void pci_restore_iov_state(struct pci_dev *dev) { } @@ -452,4 +500,19 @@ static inline int devm_of_pci_get_host_bridge_resources(struct device *dev, } #endif +#ifdef CONFIG_PCIEAER +void pci_no_aer(void); +void pci_aer_init(struct pci_dev *dev); +void pci_aer_exit(struct pci_dev *dev); +extern const struct attribute_group aer_stats_attr_group; +void pci_aer_clear_fatal_status(struct pci_dev *dev); +void pci_aer_clear_device_status(struct pci_dev *dev); +#else +static inline void pci_no_aer(void) { } +static inline int pci_aer_init(struct pci_dev *d) { return -ENODEV; } +static inline void pci_aer_exit(struct pci_dev *d) { } +static inline void pci_aer_clear_fatal_status(struct pci_dev *dev) { } +static inline void pci_aer_clear_device_status(struct pci_dev *dev) { } +#endif + #endif /* DRIVERS_PCI_H */ diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index a2e88386af28..4e823ae051a7 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -31,26 +31,9 @@ #include "portdrv.h" #define AER_ERROR_SOURCES_MAX 100 -#define AER_MAX_MULTI_ERR_DEVICES 5 /* Not likely to have more */ -struct aer_err_info { - struct pci_dev *dev[AER_MAX_MULTI_ERR_DEVICES]; - int error_dev_num; - - unsigned int id:16; - - unsigned int severity:2; /* 0:NONFATAL | 1:FATAL | 2:COR */ - unsigned int __pad1:5; - unsigned int multi_error_valid:1; - - unsigned int first_error:5; - unsigned int __pad2:2; - unsigned int tlp_header_valid:1; - - unsigned int status; /* COR/UNCOR Error Status */ - unsigned int mask; /* COR/UNCOR Error Mask */ - struct aer_header_log_regs tlp; /* TLP Header */ -}; +#define AER_MAX_TYPEOF_COR_ERRS 16 /* as per PCI_ERR_COR_STATUS */ +#define AER_MAX_TYPEOF_UNCOR_ERRS 26 /* as per PCI_ERR_UNCOR_STATUS*/ struct aer_err_source { unsigned int status; @@ -76,6 +59,42 @@ struct aer_rpc { */ }; +/* AER stats for the device */ +struct aer_stats { + + /* + * Fields for all AER capable devices. They indicate the errors + * "as seen by this device". Note that this may mean that if an + * end point is causing problems, the AER counters may increment + * at its link partner (e.g. root port) because the errors will be + * "seen" by the link partner and not the the problematic end point + * itself (which may report all counters as 0 as it never saw any + * problems). + */ + /* Counters for different type of correctable errors */ + u64 dev_cor_errs[AER_MAX_TYPEOF_COR_ERRS]; + /* Counters for different type of fatal uncorrectable errors */ + u64 dev_fatal_errs[AER_MAX_TYPEOF_UNCOR_ERRS]; + /* Counters for different type of nonfatal uncorrectable errors */ + u64 dev_nonfatal_errs[AER_MAX_TYPEOF_UNCOR_ERRS]; + /* Total number of ERR_COR sent by this device */ + u64 dev_total_cor_errs; + /* Total number of ERR_FATAL sent by this device */ + u64 dev_total_fatal_errs; + /* Total number of ERR_NONFATAL sent by this device */ + u64 dev_total_nonfatal_errs; + + /* + * Fields for Root ports & root complex event collectors only, these + * indicate the total number of ERR_COR, ERR_FATAL, and ERR_NONFATAL + * messages received by the root port / event collector, INCLUDING the + * ones that are generated internally (by the rootport itself) + */ + u64 rootport_total_cor_errs; + u64 rootport_total_fatal_errs; + u64 rootport_total_nonfatal_errs; +}; + #define AER_LOG_TLP_MASKS (PCI_ERR_UNC_POISON_TLP| \ PCI_ERR_UNC_ECRC| \ PCI_ERR_UNC_UNSUP| \ @@ -303,12 +322,13 @@ int pcie_aer_get_firmware_first(struct pci_dev *dev) if (!pci_is_pcie(dev)) return 0; + if (pcie_ports_native) + return 0; + if (!dev->__aer_firmware_first_valid) aer_set_firmware_first(dev); return dev->__aer_firmware_first; } -#define PCI_EXP_AER_FLAGS (PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | \ - PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE) static bool aer_firmware_first; @@ -323,6 +343,9 @@ bool aer_acpi_firmware_first(void) .firmware_first = 0, }; + if (pcie_ports_native) + return false; + if (!parsed) { apei_hest_parse(aer_hest_parse, &info); aer_firmware_first = info.firmware_first; @@ -357,16 +380,30 @@ int pci_disable_pcie_error_reporting(struct pci_dev *dev) } EXPORT_SYMBOL_GPL(pci_disable_pcie_error_reporting); +void pci_aer_clear_device_status(struct pci_dev *dev) +{ + u16 sta; + + pcie_capability_read_word(dev, PCI_EXP_DEVSTA, &sta); + pcie_capability_write_word(dev, PCI_EXP_DEVSTA, sta); +} + int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev) { int pos; - u32 status; + u32 status, sev; pos = dev->aer_cap; if (!pos) return -EIO; + if (pcie_aer_get_firmware_first(dev)) + return -EIO; + + /* Clear status bits for ERR_NONFATAL errors only */ pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &sev); + status &= ~sev; if (status) pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); @@ -374,6 +411,26 @@ int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev) } EXPORT_SYMBOL_GPL(pci_cleanup_aer_uncorrect_error_status); +void pci_aer_clear_fatal_status(struct pci_dev *dev) +{ + int pos; + u32 status, sev; + + pos = dev->aer_cap; + if (!pos) + return; + + if (pcie_aer_get_firmware_first(dev)) + return; + + /* Clear status bits for ERR_FATAL errors only */ + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &sev); + status &= sev; + if (status) + pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); +} + int pci_cleanup_aer_error_status_regs(struct pci_dev *dev) { int pos; @@ -387,6 +444,9 @@ int pci_cleanup_aer_error_status_regs(struct pci_dev *dev) if (!pos) return -EIO; + if (pcie_aer_get_firmware_first(dev)) + return -EIO; + port_type = pci_pcie_type(dev); if (port_type == PCI_EXP_TYPE_ROOT_PORT) { pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, &status); @@ -402,10 +462,20 @@ int pci_cleanup_aer_error_status_regs(struct pci_dev *dev) return 0; } -int pci_aer_init(struct pci_dev *dev) +void pci_aer_init(struct pci_dev *dev) { dev->aer_cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); - return pci_cleanup_aer_error_status_regs(dev); + + if (dev->aer_cap) + dev->aer_stats = kzalloc(sizeof(struct aer_stats), GFP_KERNEL); + + pci_cleanup_aer_error_status_regs(dev); +} + +void pci_aer_exit(struct pci_dev *dev) +{ + kfree(dev->aer_stats); + dev->aer_stats = NULL; } #define AER_AGENT_RECEIVER 0 @@ -458,52 +528,52 @@ static const char *aer_error_layer[] = { "Transaction Layer" }; -static const char *aer_correctable_error_string[] = { - "Receiver Error", /* Bit Position 0 */ +static const char *aer_correctable_error_string[AER_MAX_TYPEOF_COR_ERRS] = { + "RxErr", /* Bit Position 0 */ NULL, NULL, NULL, NULL, NULL, - "Bad TLP", /* Bit Position 6 */ - "Bad DLLP", /* Bit Position 7 */ - "RELAY_NUM Rollover", /* Bit Position 8 */ + "BadTLP", /* Bit Position 6 */ + "BadDLLP", /* Bit Position 7 */ + "Rollover", /* Bit Position 8 */ NULL, NULL, NULL, - "Replay Timer Timeout", /* Bit Position 12 */ - "Advisory Non-Fatal", /* Bit Position 13 */ - "Corrected Internal Error", /* Bit Position 14 */ - "Header Log Overflow", /* Bit Position 15 */ + "Timeout", /* Bit Position 12 */ + "NonFatalErr", /* Bit Position 13 */ + "CorrIntErr", /* Bit Position 14 */ + "HeaderOF", /* Bit Position 15 */ }; -static const char *aer_uncorrectable_error_string[] = { +static const char *aer_uncorrectable_error_string[AER_MAX_TYPEOF_UNCOR_ERRS] = { "Undefined", /* Bit Position 0 */ NULL, NULL, NULL, - "Data Link Protocol", /* Bit Position 4 */ - "Surprise Down Error", /* Bit Position 5 */ + "DLP", /* Bit Position 4 */ + "SDES", /* Bit Position 5 */ NULL, NULL, NULL, NULL, NULL, NULL, - "Poisoned TLP", /* Bit Position 12 */ - "Flow Control Protocol", /* Bit Position 13 */ - "Completion Timeout", /* Bit Position 14 */ - "Completer Abort", /* Bit Position 15 */ - "Unexpected Completion", /* Bit Position 16 */ - "Receiver Overflow", /* Bit Position 17 */ - "Malformed TLP", /* Bit Position 18 */ + "TLP", /* Bit Position 12 */ + "FCP", /* Bit Position 13 */ + "CmpltTO", /* Bit Position 14 */ + "CmpltAbrt", /* Bit Position 15 */ + "UnxCmplt", /* Bit Position 16 */ + "RxOF", /* Bit Position 17 */ + "MalfTLP", /* Bit Position 18 */ "ECRC", /* Bit Position 19 */ - "Unsupported Request", /* Bit Position 20 */ - "ACS Violation", /* Bit Position 21 */ - "Uncorrectable Internal Error", /* Bit Position 22 */ - "MC Blocked TLP", /* Bit Position 23 */ - "AtomicOp Egress Blocked", /* Bit Position 24 */ - "TLP Prefix Blocked Error", /* Bit Position 25 */ + "UnsupReq", /* Bit Position 20 */ + "ACSViol", /* Bit Position 21 */ + "UncorrIntErr", /* Bit Position 22 */ + "BlockedTLP", /* Bit Position 23 */ + "AtomicOpBlocked", /* Bit Position 24 */ + "TLPBlockedErr", /* Bit Position 25 */ }; static const char *aer_agent_string[] = { @@ -513,6 +583,144 @@ static const char *aer_agent_string[] = { "Transmitter ID" }; +#define aer_stats_dev_attr(name, stats_array, strings_array, \ + total_string, total_field) \ + static ssize_t \ + name##_show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + unsigned int i; \ + char *str = buf; \ + struct pci_dev *pdev = to_pci_dev(dev); \ + u64 *stats = pdev->aer_stats->stats_array; \ + \ + for (i = 0; i < ARRAY_SIZE(strings_array); i++) { \ + if (strings_array[i]) \ + str += sprintf(str, "%s %llu\n", \ + strings_array[i], stats[i]); \ + else if (stats[i]) \ + str += sprintf(str, #stats_array "_bit[%d] %llu\n",\ + i, stats[i]); \ + } \ + str += sprintf(str, "TOTAL_%s %llu\n", total_string, \ + pdev->aer_stats->total_field); \ + return str-buf; \ +} \ +static DEVICE_ATTR_RO(name) + +aer_stats_dev_attr(aer_dev_correctable, dev_cor_errs, + aer_correctable_error_string, "ERR_COR", + dev_total_cor_errs); +aer_stats_dev_attr(aer_dev_fatal, dev_fatal_errs, + aer_uncorrectable_error_string, "ERR_FATAL", + dev_total_fatal_errs); +aer_stats_dev_attr(aer_dev_nonfatal, dev_nonfatal_errs, + aer_uncorrectable_error_string, "ERR_NONFATAL", + dev_total_nonfatal_errs); + +#define aer_stats_rootport_attr(name, field) \ + static ssize_t \ + name##_show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct pci_dev *pdev = to_pci_dev(dev); \ + return sprintf(buf, "%llu\n", pdev->aer_stats->field); \ +} \ +static DEVICE_ATTR_RO(name) + +aer_stats_rootport_attr(aer_rootport_total_err_cor, + rootport_total_cor_errs); +aer_stats_rootport_attr(aer_rootport_total_err_fatal, + rootport_total_fatal_errs); +aer_stats_rootport_attr(aer_rootport_total_err_nonfatal, + rootport_total_nonfatal_errs); + +static struct attribute *aer_stats_attrs[] __ro_after_init = { + &dev_attr_aer_dev_correctable.attr, + &dev_attr_aer_dev_fatal.attr, + &dev_attr_aer_dev_nonfatal.attr, + &dev_attr_aer_rootport_total_err_cor.attr, + &dev_attr_aer_rootport_total_err_fatal.attr, + &dev_attr_aer_rootport_total_err_nonfatal.attr, + NULL +}; + +static umode_t aer_stats_attrs_are_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct pci_dev *pdev = to_pci_dev(dev); + + if (!pdev->aer_stats) + return 0; + + if ((a == &dev_attr_aer_rootport_total_err_cor.attr || + a == &dev_attr_aer_rootport_total_err_fatal.attr || + a == &dev_attr_aer_rootport_total_err_nonfatal.attr) && + pci_pcie_type(pdev) != PCI_EXP_TYPE_ROOT_PORT) + return 0; + + return a->mode; +} + +const struct attribute_group aer_stats_attr_group = { + .attrs = aer_stats_attrs, + .is_visible = aer_stats_attrs_are_visible, +}; + +static void pci_dev_aer_stats_incr(struct pci_dev *pdev, + struct aer_err_info *info) +{ + int status, i, max = -1; + u64 *counter = NULL; + struct aer_stats *aer_stats = pdev->aer_stats; + + if (!aer_stats) + return; + + switch (info->severity) { + case AER_CORRECTABLE: + aer_stats->dev_total_cor_errs++; + counter = &aer_stats->dev_cor_errs[0]; + max = AER_MAX_TYPEOF_COR_ERRS; + break; + case AER_NONFATAL: + aer_stats->dev_total_nonfatal_errs++; + counter = &aer_stats->dev_nonfatal_errs[0]; + max = AER_MAX_TYPEOF_UNCOR_ERRS; + break; + case AER_FATAL: + aer_stats->dev_total_fatal_errs++; + counter = &aer_stats->dev_fatal_errs[0]; + max = AER_MAX_TYPEOF_UNCOR_ERRS; + break; + } + + status = (info->status & ~info->mask); + for (i = 0; i < max; i++) + if (status & (1 << i)) + counter[i]++; +} + +static void pci_rootport_aer_stats_incr(struct pci_dev *pdev, + struct aer_err_source *e_src) +{ + struct aer_stats *aer_stats = pdev->aer_stats; + + if (!aer_stats) + return; + + if (e_src->status & PCI_ERR_ROOT_COR_RCV) + aer_stats->rootport_total_cor_errs++; + + if (e_src->status & PCI_ERR_ROOT_UNCOR_RCV) { + if (e_src->status & PCI_ERR_ROOT_FATAL_RCV) + aer_stats->rootport_total_fatal_errs++; + else + aer_stats->rootport_total_nonfatal_errs++; + } +} + static void __print_tlp_header(struct pci_dev *dev, struct aer_header_log_regs *t) { @@ -545,9 +753,10 @@ static void __aer_print_error(struct pci_dev *dev, pci_err(dev, " [%2d] Unknown Error Bit%s\n", i, info->first_error == i ? " (First)" : ""); } + pci_dev_aer_stats_incr(dev, info); } -static void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) +void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) { int layer, agent; int id = ((dev->bus->number << 8) | dev->devfn); @@ -799,6 +1008,7 @@ static void handle_error_source(struct pci_dev *dev, struct aer_err_info *info) if (pos) pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS, info->status); + pci_aer_clear_device_status(dev); } else if (info->severity == AER_NONFATAL) pcie_do_nonfatal_recovery(dev); else if (info->severity == AER_FATAL) @@ -876,7 +1086,7 @@ EXPORT_SYMBOL_GPL(aer_recover_queue); #endif /** - * get_device_error_info - read error status from dev and store it to info + * aer_get_device_error_info - read error status from dev and store it to info * @dev: pointer to the device expected to have a error record * @info: pointer to structure to store the error record * @@ -884,7 +1094,7 @@ EXPORT_SYMBOL_GPL(aer_recover_queue); * * Note that @info is reused among all error devices. Clear fields properly. */ -static int get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) +int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) { int pos, temp; @@ -942,11 +1152,11 @@ static inline void aer_process_err_devices(struct aer_err_info *e_info) /* Report all before handle them, not to lost records by reset etc. */ for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { - if (get_device_error_info(e_info->dev[i], e_info)) + if (aer_get_device_error_info(e_info->dev[i], e_info)) aer_print_error(e_info->dev[i], e_info); } for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { - if (get_device_error_info(e_info->dev[i], e_info)) + if (aer_get_device_error_info(e_info->dev[i], e_info)) handle_error_source(e_info->dev[i], e_info); } } @@ -962,6 +1172,8 @@ static void aer_isr_one_error(struct aer_rpc *rpc, struct pci_dev *pdev = rpc->rpd; struct aer_err_info *e_info = &rpc->e_info; + pci_rootport_aer_stats_incr(pdev, e_src); + /* * There is a possibility that both correctable error and * uncorrectable error being logged. Report correctable error first. @@ -1336,20 +1548,8 @@ static pci_ers_result_t aer_root_reset(struct pci_dev *dev) */ static void aer_error_resume(struct pci_dev *dev) { - int pos; - u32 status, mask; - u16 reg16; - - /* Clean up Root device status */ - pcie_capability_read_word(dev, PCI_EXP_DEVSTA, ®16); - pcie_capability_write_word(dev, PCI_EXP_DEVSTA, reg16); - - /* Clean AER Root Error Status */ - pos = dev->aer_cap; - pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); - pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &mask); - status &= ~mask; /* Clear corresponding nonfatal bits */ - pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); + pci_aer_clear_device_status(dev); + pci_cleanup_aer_uncorrect_error_status(dev); } static struct pcie_port_service_driver aerdriver = { diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index c687c817b47d..5326916715d2 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -1127,11 +1127,9 @@ static int pcie_aspm_set_policy(const char *val, if (aspm_disabled) return -EPERM; - 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; + i = sysfs_match_string(policy_str, val); + if (i < 0) + return i; if (i == aspm_policy) return 0; diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c index 921ed979109d..f03279fc87cd 100644 --- a/drivers/pci/pcie/dpc.c +++ b/drivers/pci/pcie/dpc.c @@ -6,6 +6,7 @@ * Copyright (C) 2016 Intel Corp. */ +#include <linux/aer.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/init.h> @@ -16,10 +17,8 @@ struct dpc_dev { struct pcie_device *dev; - struct work_struct work; u16 cap_pos; bool rp_extensions; - u32 rp_pio_status; u8 rp_log_size; }; @@ -65,19 +64,13 @@ static int dpc_wait_rp_inactive(struct dpc_dev *dpc) return 0; } -static void dpc_wait_link_inactive(struct dpc_dev *dpc) -{ - struct pci_dev *pdev = dpc->dev->port; - - pcie_wait_for_link(pdev, false); -} - static pci_ers_result_t dpc_reset_link(struct pci_dev *pdev) { struct dpc_dev *dpc; struct pcie_device *pciedev; struct device *devdpc; - u16 cap, ctl; + + u16 cap; /* * DPC disables the Link automatically in hardware, so it has @@ -92,34 +85,17 @@ static pci_ers_result_t dpc_reset_link(struct pci_dev *pdev) * Wait until the Link is inactive, then clear DPC Trigger Status * to allow the Port to leave DPC. */ - dpc_wait_link_inactive(dpc); + pcie_wait_for_link(pdev, false); if (dpc->rp_extensions && dpc_wait_rp_inactive(dpc)) return PCI_ERS_RESULT_DISCONNECT; - if (dpc->rp_extensions && dpc->rp_pio_status) { - pci_write_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_STATUS, - dpc->rp_pio_status); - dpc->rp_pio_status = 0; - } pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS, PCI_EXP_DPC_STATUS_TRIGGER); - pci_read_config_word(pdev, cap + PCI_EXP_DPC_CTL, &ctl); - pci_write_config_word(pdev, cap + PCI_EXP_DPC_CTL, - ctl | PCI_EXP_DPC_CTL_INT_EN); - return PCI_ERS_RESULT_RECOVERED; } -static void dpc_work(struct work_struct *work) -{ - struct dpc_dev *dpc = container_of(work, struct dpc_dev, work); - struct pci_dev *pdev = dpc->dev->port; - - /* We configure DPC so it only triggers on ERR_FATAL */ - pcie_do_fatal_recovery(pdev, PCIE_PORT_SERVICE_DPC); -} static void dpc_process_rp_pio_error(struct dpc_dev *dpc) { @@ -134,8 +110,6 @@ static void dpc_process_rp_pio_error(struct dpc_dev *dpc) dev_err(dev, "rp_pio_status: %#010x, rp_pio_mask: %#010x\n", status, mask); - dpc->rp_pio_status = status; - pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_SEVERITY, &sev); pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_SYSERROR, &syserr); pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_EXCEPTION, &exc); @@ -146,15 +120,14 @@ static void dpc_process_rp_pio_error(struct dpc_dev *dpc) pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &dpc_status); first_error = (dpc_status & 0x1f00) >> 8; - status &= ~mask; for (i = 0; i < ARRAY_SIZE(rp_pio_error_string); i++) { - if (status & (1 << i)) + if ((status & ~mask) & (1 << i)) dev_err(dev, "[%2d] %s%s\n", i, rp_pio_error_string[i], first_error == i ? " (First)" : ""); } if (dpc->rp_log_size < 4) - return; + goto clear_status; pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG, &dw0); pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG + 4, @@ -167,7 +140,7 @@ static void dpc_process_rp_pio_error(struct dpc_dev *dpc) dw0, dw1, dw2, dw3); if (dpc->rp_log_size < 5) - return; + goto clear_status; pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_IMPSPEC_LOG, &log); dev_err(dev, "RP PIO ImpSpec Log %#010x\n", log); @@ -176,43 +149,26 @@ static void dpc_process_rp_pio_error(struct dpc_dev *dpc) cap + PCI_EXP_DPC_RP_PIO_TLPPREFIX_LOG, &prefix); dev_err(dev, "TLP Prefix Header: dw%d, %#010x\n", i, prefix); } + clear_status: + pci_write_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_STATUS, status); } -static irqreturn_t dpc_irq(int irq, void *context) +static irqreturn_t dpc_handler(int irq, void *context) { - struct dpc_dev *dpc = (struct dpc_dev *)context; + struct aer_err_info info; + struct dpc_dev *dpc = context; struct pci_dev *pdev = dpc->dev->port; struct device *dev = &dpc->dev->device; - u16 cap = dpc->cap_pos, ctl, status, source, reason, ext_reason; - - pci_read_config_word(pdev, cap + PCI_EXP_DPC_CTL, &ctl); - - if (!(ctl & PCI_EXP_DPC_CTL_INT_EN) || ctl == (u16)(~0)) - return IRQ_NONE; + u16 cap = dpc->cap_pos, status, source, reason, ext_reason; pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status); - - if (!(status & PCI_EXP_DPC_STATUS_INTERRUPT)) - return IRQ_NONE; - - if (!(status & PCI_EXP_DPC_STATUS_TRIGGER)) { - pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS, - PCI_EXP_DPC_STATUS_INTERRUPT); - return IRQ_HANDLED; - } - - pci_write_config_word(pdev, cap + PCI_EXP_DPC_CTL, - ctl & ~PCI_EXP_DPC_CTL_INT_EN); - - pci_read_config_word(pdev, cap + PCI_EXP_DPC_SOURCE_ID, - &source); + pci_read_config_word(pdev, cap + PCI_EXP_DPC_SOURCE_ID, &source); dev_info(dev, "DPC containment event, status:%#06x source:%#06x\n", - status, source); + status, source); reason = (status & PCI_EXP_DPC_STATUS_TRIGGER_RSN) >> 1; ext_reason = (status & PCI_EXP_DPC_STATUS_TRIGGER_RSN_EXT) >> 5; - dev_warn(dev, "DPC %s detected, remove downstream devices\n", (reason == 0) ? "unmasked uncorrectable error" : (reason == 1) ? "ERR_NONFATAL" : @@ -220,15 +176,36 @@ static irqreturn_t dpc_irq(int irq, void *context) (ext_reason == 0) ? "RP PIO error" : (ext_reason == 1) ? "software trigger" : "reserved error"); + /* show RP PIO error detail information */ if (dpc->rp_extensions && reason == 3 && ext_reason == 0) dpc_process_rp_pio_error(dpc); + else if (reason == 0 && aer_get_device_error_info(pdev, &info)) { + aer_print_error(pdev, &info); + pci_cleanup_aer_uncorrect_error_status(pdev); + } - pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS, - PCI_EXP_DPC_STATUS_INTERRUPT); + /* We configure DPC so it only triggers on ERR_FATAL */ + pcie_do_fatal_recovery(pdev, PCIE_PORT_SERVICE_DPC); + + return IRQ_HANDLED; +} + +static irqreturn_t dpc_irq(int irq, void *context) +{ + struct dpc_dev *dpc = (struct dpc_dev *)context; + struct pci_dev *pdev = dpc->dev->port; + u16 cap = dpc->cap_pos, status; + + pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status); - schedule_work(&dpc->work); + if (!(status & PCI_EXP_DPC_STATUS_INTERRUPT) || status == (u16)(~0)) + return IRQ_NONE; + pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS, + PCI_EXP_DPC_STATUS_INTERRUPT); + if (status & PCI_EXP_DPC_STATUS_TRIGGER) + return IRQ_WAKE_THREAD; return IRQ_HANDLED; } @@ -250,11 +227,11 @@ static int dpc_probe(struct pcie_device *dev) dpc->cap_pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_DPC); dpc->dev = dev; - INIT_WORK(&dpc->work, dpc_work); set_service_data(dev, dpc); - status = devm_request_irq(device, dev->irq, dpc_irq, IRQF_SHARED, - "pcie-dpc", dpc); + status = devm_request_threaded_irq(device, dev->irq, dpc_irq, + dpc_handler, IRQF_SHARED, + "pcie-dpc", dpc); if (status) { dev_warn(device, "request IRQ%d failed: %d\n", dev->irq, status); diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c index f7ce0cb0b0b7..674984a9277a 100644 --- a/drivers/pci/pcie/err.c +++ b/drivers/pci/pcie/err.c @@ -252,6 +252,7 @@ static pci_ers_result_t broadcast_error_message(struct pci_dev *dev, dev->error_state = state; pci_walk_bus(dev->subordinate, cb, &result_data); if (cb == report_resume) { + pci_aer_clear_device_status(dev); pci_cleanup_aer_uncorrect_error_status(dev); dev->error_state = pci_channel_io_normal; } @@ -259,15 +260,10 @@ static pci_ers_result_t broadcast_error_message(struct pci_dev *dev, /* * If the error is reported by an end point, we think this * error is related to the upstream link of the end point. + * The error is non fatal so the bus is ok; just invoke + * the callback for the function that logged the error. */ - if (state == pci_channel_io_normal) - /* - * the error is non fatal so the bus is ok, just invoke - * the callback for the function that logged the error. - */ - cb(dev, &result_data); - else - pci_walk_bus(dev->bus, cb, &result_data); + cb(dev, &result_data); } return result_data.result; @@ -295,6 +291,7 @@ void pcie_do_fatal_recovery(struct pci_dev *dev, u32 service) parent = udev->subordinate; pci_lock_rescan_remove(); + pci_dev_get(dev); list_for_each_entry_safe_reverse(pdev, temp, &parent->devices, bus_list) { pci_dev_get(pdev); @@ -316,7 +313,8 @@ void pcie_do_fatal_recovery(struct pci_dev *dev, u32 service) * do error recovery on all subordinates of the bridge instead * of the bridge and clear the error status of the bridge. */ - pci_cleanup_aer_uncorrect_error_status(dev); + pci_aer_clear_fatal_status(dev); + pci_aer_clear_device_status(dev); } if (result == PCI_ERS_RESULT_RECOVERED) { @@ -328,6 +326,7 @@ void pcie_do_fatal_recovery(struct pci_dev *dev, u32 service) pci_info(dev, "Device recovery from fatal error failed\n"); } + pci_dev_put(dev); pci_unlock_rescan_remove(); } diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h index 6ffc797a0dc1..d59afa42fc14 100644 --- a/drivers/pci/pcie/portdrv.h +++ b/drivers/pci/pcie/portdrv.h @@ -50,6 +50,7 @@ struct pcie_port_service_driver { int (*probe) (struct pcie_device *dev); void (*remove) (struct pcie_device *dev); int (*suspend) (struct pcie_device *dev); + int (*resume_noirq) (struct pcie_device *dev); int (*resume) (struct pcie_device *dev); /* Device driver may resume normal operations */ @@ -82,6 +83,7 @@ extern struct bus_type pcie_port_bus_type; int pcie_port_device_register(struct pci_dev *dev); #ifdef CONFIG_PM int pcie_port_device_suspend(struct device *dev); +int pcie_port_device_resume_noirq(struct device *dev); int pcie_port_device_resume(struct device *dev); #endif void pcie_port_device_remove(struct pci_dev *dev); diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index e0261ad4bcdd..7c37d815229e 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -353,14 +353,19 @@ error_disable: } #ifdef CONFIG_PM -static int suspend_iter(struct device *dev, void *data) +typedef int (*pcie_pm_callback_t)(struct pcie_device *); + +static int pm_iter(struct device *dev, void *data) { struct pcie_port_service_driver *service_driver; + size_t offset = *(size_t *)data; + pcie_pm_callback_t cb; if ((dev->bus == &pcie_port_bus_type) && dev->driver) { service_driver = to_service_driver(dev->driver); - if (service_driver->suspend) - service_driver->suspend(to_pcie_device(dev)); + cb = *(pcie_pm_callback_t *)((void *)service_driver + offset); + if (cb) + return cb(to_pcie_device(dev)); } return 0; } @@ -371,20 +376,14 @@ static int suspend_iter(struct device *dev, void *data) */ int pcie_port_device_suspend(struct device *dev) { - return device_for_each_child(dev, NULL, suspend_iter); + size_t off = offsetof(struct pcie_port_service_driver, suspend); + return device_for_each_child(dev, &off, pm_iter); } -static int resume_iter(struct device *dev, void *data) +int pcie_port_device_resume_noirq(struct device *dev) { - struct pcie_port_service_driver *service_driver; - - if ((dev->bus == &pcie_port_bus_type) && - (dev->driver)) { - service_driver = to_service_driver(dev->driver); - if (service_driver->resume) - service_driver->resume(to_pcie_device(dev)); - } - return 0; + size_t off = offsetof(struct pcie_port_service_driver, resume_noirq); + return device_for_each_child(dev, &off, pm_iter); } /** @@ -393,7 +392,8 @@ static int resume_iter(struct device *dev, void *data) */ int pcie_port_device_resume(struct device *dev) { - return device_for_each_child(dev, NULL, resume_iter); + size_t off = offsetof(struct pcie_port_service_driver, resume); + return device_for_each_child(dev, &off, pm_iter); } #endif /* PM */ diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 973f1b80a038..eef22dc29140 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -42,17 +42,6 @@ __setup("pcie_ports=", pcie_port_setup); /* global data */ -static int pcie_portdrv_restore_config(struct pci_dev *dev) -{ - int retval; - - retval = pci_enable_device(dev); - if (retval) - return retval; - pci_set_master(dev); - return 0; -} - #ifdef CONFIG_PM static int pcie_port_runtime_suspend(struct device *dev) { @@ -76,10 +65,12 @@ static int pcie_port_runtime_idle(struct device *dev) static const struct dev_pm_ops pcie_portdrv_pm_ops = { .suspend = pcie_port_device_suspend, + .resume_noirq = pcie_port_device_resume_noirq, .resume = pcie_port_device_resume, .freeze = pcie_port_device_suspend, .thaw = pcie_port_device_resume, .poweroff = pcie_port_device_suspend, + .restore_noirq = pcie_port_device_resume_noirq, .restore = pcie_port_device_resume, .runtime_suspend = pcie_port_runtime_suspend, .runtime_resume = pcie_port_runtime_resume, @@ -160,19 +151,6 @@ static pci_ers_result_t pcie_portdrv_mmio_enabled(struct pci_dev *dev) return PCI_ERS_RESULT_RECOVERED; } -static pci_ers_result_t pcie_portdrv_slot_reset(struct pci_dev *dev) -{ - /* If fatal, restore cfg space for possible link reset at upstream */ - if (dev->error_state == pci_channel_io_frozen) { - dev->state_saved = true; - pci_restore_state(dev); - pcie_portdrv_restore_config(dev); - pci_enable_pcie_error_reporting(dev); - } - - return PCI_ERS_RESULT_RECOVERED; -} - static int resume_iter(struct device *device, void *data) { struct pcie_device *pcie_device; @@ -208,7 +186,6 @@ static const struct pci_device_id port_pci_ids[] = { { static const struct pci_error_handlers pcie_portdrv_err_handler = { .error_detected = pcie_portdrv_error_detected, .mmio_enabled = pcie_portdrv_mmio_enabled, - .slot_reset = pcie_portdrv_slot_reset, .resume = pcie_portdrv_err_resume, }; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index ac876e32de4b..f3d2fcc05cda 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -13,7 +13,6 @@ #include <linux/slab.h> #include <linux/module.h> #include <linux/cpumask.h> -#include <linux/pci-aspm.h> #include <linux/aer.h> #include <linux/acpi.h> #include <linux/hypervisor.h> @@ -1725,11 +1724,15 @@ int pci_setup_device(struct pci_dev *dev) static void pci_configure_mps(struct pci_dev *dev) { struct pci_dev *bridge = pci_upstream_bridge(dev); - int mps, p_mps, rc; + int mps, mpss, p_mps, rc; if (!pci_is_pcie(dev) || !bridge || !pci_is_pcie(bridge)) return; + /* MPS and MRRS fields are of type 'RsvdP' for VFs, short-circuit out */ + if (dev->is_virtfn) + return; + mps = pcie_get_mps(dev); p_mps = pcie_get_mps(bridge); @@ -1749,6 +1752,14 @@ static void pci_configure_mps(struct pci_dev *dev) if (pcie_bus_config != PCIE_BUS_DEFAULT) return; + mpss = 128 << dev->pcie_mpss; + if (mpss < p_mps && pci_pcie_type(bridge) == PCI_EXP_TYPE_ROOT_PORT) { + pcie_set_mps(bridge, mpss); + pci_info(dev, "Upstream bridge's Max Payload Size set to %d (was %d, max %d)\n", + mpss, p_mps, 128 << bridge->pcie_mpss); + p_mps = pcie_get_mps(bridge); + } + rc = pcie_set_mps(dev, p_mps); if (rc) { pci_warn(dev, "can't set Max Payload Size to %d; if necessary, use \"pci=pcie_bus_safe\" and report a bug\n", @@ -1757,7 +1768,7 @@ static void pci_configure_mps(struct pci_dev *dev) } pci_info(dev, "Max Payload Size set to %d (was %d, max %d)\n", - p_mps, mps, 128 << dev->pcie_mpss); + p_mps, mps, mpss); } static struct hpp_type0 pci_default_type0 = { @@ -2064,6 +2075,7 @@ static void pci_configure_device(struct pci_dev *dev) static void pci_release_capabilities(struct pci_dev *dev) { + pci_aer_exit(dev); pci_vpd_release(dev); pci_iov_release(dev); pci_free_cap_save_buffers(dev); @@ -2156,8 +2168,8 @@ static bool pci_bus_wait_crs(struct pci_bus *bus, int devfn, u32 *l, return true; } -bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l, - int timeout) +bool pci_bus_generic_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l, + int timeout) { if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, l)) return false; @@ -2172,6 +2184,24 @@ bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l, return true; } + +bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l, + int timeout) +{ +#ifdef CONFIG_PCI_QUIRKS + struct pci_dev *bridge = bus->self; + + /* + * Certain IDT switches have an issue where they improperly trigger + * ACS Source Validation errors on completions for config reads. + */ + if (bridge && bridge->vendor == PCI_VENDOR_ID_IDT && + bridge->device == 0x80b5) + return pci_idt_bus_quirk(bus, devfn, l, timeout); +#endif + + return pci_bus_generic_read_dev_vendor_id(bus, devfn, l, timeout); +} EXPORT_SYMBOL(pci_bus_read_dev_vendor_id); /* @@ -2205,6 +2235,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 */ @@ -2240,6 +2289,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; } @@ -2433,13 +2484,13 @@ int pci_scan_slot(struct pci_bus *bus, int devfn) dev = pci_scan_single_device(bus, devfn); if (!dev) return 0; - if (!dev->is_added) + if (!pci_dev_is_added(dev)) nr++; for (fn = next_fn(bus, dev, 0); fn > 0; fn = next_fn(bus, dev, fn)) { dev = pci_scan_single_device(bus, devfn + fn); if (dev) { - if (!dev->is_added) + if (!pci_dev_is_added(dev)) nr++; dev->multifunction = 1; } diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 7743cd56b89a..2c6d987c271f 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -4755,3 +4755,58 @@ DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_VENDOR_ID_AMD, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_HD_AUDIO, 8, quirk_gpu_hda); DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID, PCI_CLASS_MULTIMEDIA_HD_AUDIO, 8, quirk_gpu_hda); + +/* + * Some IDT switches incorrectly flag an ACS Source Validation error on + * completions for config read requests even though PCIe r4.0, sec + * 6.12.1.1, says that completions are never affected by ACS Source + * Validation. Here's the text of IDT 89H32H8G3-YC, erratum #36: + * + * Item #36 - Downstream port applies ACS Source Validation to Completions + * Section 6.12.1.1 of the PCI Express Base Specification 3.1 states that + * completions are never affected by ACS Source Validation. However, + * completions received by a downstream port of the PCIe switch from a + * device that has not yet captured a PCIe bus number are incorrectly + * dropped by ACS Source Validation by the switch downstream port. + * + * The workaround suggested by IDT is to issue a config write to the + * downstream device before issuing the first config read. This allows the + * downstream device to capture its bus and device numbers (see PCIe r4.0, + * sec 2.2.9), thus avoiding the ACS error on the completion. + * + * However, we don't know when the device is ready to accept the config + * write, so we do config reads until we receive a non-Config Request Retry + * Status, then do the config write. + * + * To avoid hitting the erratum when doing the config reads, we disable ACS + * SV around this process. + */ +int pci_idt_bus_quirk(struct pci_bus *bus, int devfn, u32 *l, int timeout) +{ + int pos; + u16 ctrl = 0; + bool found; + struct pci_dev *bridge = bus->self; + + pos = pci_find_ext_capability(bridge, PCI_EXT_CAP_ID_ACS); + + /* Disable ACS SV before initial config reads */ + if (pos) { + pci_read_config_word(bridge, pos + PCI_ACS_CTRL, &ctrl); + if (ctrl & PCI_ACS_SV) + pci_write_config_word(bridge, pos + PCI_ACS_CTRL, + ctrl & ~PCI_ACS_SV); + } + + found = pci_bus_generic_read_dev_vendor_id(bus, devfn, l, timeout); + + /* Write Vendor ID (read-only) so the endpoint latches its bus/dev */ + if (found) + pci_bus_write_config_word(bus, devfn, PCI_VENDOR_ID, 0); + + /* Re-enable ACS_SV if it was previously enabled */ + if (ctrl & PCI_ACS_SV) + pci_write_config_word(bridge, pos + PCI_ACS_CTRL, ctrl); + + return found; +} diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 6f072eae4f7a..461e7fd2756f 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/pci.h> #include <linux/module.h> -#include <linux/pci-aspm.h> #include "pci.h" static void pci_free_resources(struct pci_dev *dev) @@ -19,11 +18,12 @@ static void pci_stop_dev(struct pci_dev *dev) { pci_pme_active(dev, false); - if (dev->is_added) { + if (pci_dev_is_added(dev)) { device_release_driver(&dev->dev); pci_proc_detach_device(dev); pci_remove_sysfs_dev_files(dev); - dev->is_added = 0; + + pci_dev_assign_added(dev, false); } if (dev->bus->self) |