diff options
Diffstat (limited to 'drivers/pci/hotplug')
32 files changed, 1338 insertions, 484 deletions
diff --git a/drivers/pci/hotplug/Kconfig b/drivers/pci/hotplug/Kconfig index 48113b210cf9..3207860b52e4 100644 --- a/drivers/pci/hotplug/Kconfig +++ b/drivers/pci/hotplug/Kconfig @@ -61,6 +61,18 @@ config HOTPLUG_PCI_ACPI When in doubt, say N. +config HOTPLUG_PCI_ACPI_AMPERE_ALTRA + tristate "ACPI PCI Hotplug driver Ampere Altra extensions" + depends on HOTPLUG_PCI_ACPI + depends on HAVE_ARM_SMCCC_DISCOVERY + help + Say Y here if you have an Ampere Altra system. + + To compile this driver as a module, choose M here: the + module will be called acpiphp_ampere_altra. + + When in doubt, say N. + config HOTPLUG_PCI_ACPI_IBM tristate "ACPI PCI Hotplug driver IBM extensions" depends on HOTPLUG_PCI_ACPI @@ -85,7 +97,7 @@ config HOTPLUG_PCI_CPCI_ZT5550 tristate "Ziatech ZT5550 CompactPCI Hotplug driver" depends on HOTPLUG_PCI_CPCI && X86 help - Say Y here if you have an Performance Technologies (formerly Intel, + Say Y here if you have a Performance Technologies (formerly Intel, formerly just Ziatech) Ziatech ZT5550 CompactPCI system card. To compile this driver as a module, choose M here: the @@ -106,6 +118,16 @@ config HOTPLUG_PCI_CPCI_GENERIC When in doubt, say N. +config HOTPLUG_PCI_OCTEONEP + bool "Marvell OCTEON PCI Hotplug driver" + depends on HOTPLUG_PCI + help + Say Y here if you have an OCTEON PCIe device with a hotplug + controller. This driver enables the non-controller functions of the + device to be registered as hotplug slots. + + When in doubt, say N. + config HOTPLUG_PCI_SHPC bool "SHPC PCI Hotplug driver" help diff --git a/drivers/pci/hotplug/Makefile b/drivers/pci/hotplug/Makefile index 5196983220df..40aaf31fe338 100644 --- a/drivers/pci/hotplug/Makefile +++ b/drivers/pci/hotplug/Makefile @@ -20,9 +20,11 @@ obj-$(CONFIG_HOTPLUG_PCI_RPA) += rpaphp.o obj-$(CONFIG_HOTPLUG_PCI_RPA_DLPAR) += rpadlpar_io.o obj-$(CONFIG_HOTPLUG_PCI_ACPI) += acpiphp.o obj-$(CONFIG_HOTPLUG_PCI_S390) += s390_pci_hpc.o +obj-$(CONFIG_HOTPLUG_PCI_OCTEONEP) += octep_hp.o # acpiphp_ibm extends acpiphp, so should be linked afterwards. +obj-$(CONFIG_HOTPLUG_PCI_ACPI_AMPERE_ALTRA) += acpiphp_ampere_altra.o obj-$(CONFIG_HOTPLUG_PCI_ACPI_IBM) += acpiphp_ibm.o pci_hotplug-objs := pci_hotplug_core.o diff --git a/drivers/pci/hotplug/TODO b/drivers/pci/hotplug/TODO index fdb8dd6ea24d..7397374af171 100644 --- a/drivers/pci/hotplug/TODO +++ b/drivers/pci/hotplug/TODO @@ -2,9 +2,7 @@ Contributions are solicited in particular to remedy the following issues: cpcihp: -* There are no implementations of the ->hardware_test, ->get_power and - ->set_power callbacks in struct cpci_hp_controller_ops. Why were they - introduced? Can they be removed from the struct? +* Returned code from pci_hp_add_bridge() is not checked. cpqphp: @@ -16,6 +14,8 @@ cpqphp: * A large portion of cpqphp_ctrl.c and cpqphp_pci.c concerns resource management. Doesn't this duplicate functionality in the core? +* Returned code from pci_hp_add_bridge() is not checked. + ibmphp: * Implementations of hotplug_slot_ops callbacks such as get_adapter_present() @@ -43,21 +43,10 @@ ibmphp: * A large portion of ibmphp_res.c and ibmphp_pci.c concerns resource management. Doesn't this duplicate functionality in the core? -sgi_hotplug: - -* Several functions access the pci_slot member in struct hotplug_slot even - though pci_hotplug.h declares it private. See sn_hp_destroy() for an - example. Either the pci_slot member should no longer be declared private - or sgi_hotplug should store a pointer to it in struct slot. Probably the - former. +* Returned code from pci_hp_add_bridge() is not checked. shpchp: -* There is only a single implementation of struct hpc_ops. Can the struct be - removed and its functions invoked directly? This has already been done in - pciehp with commit 82a9e79ef132 ("PCI: pciehp: remove hpc_ops"). Clarify - if there was a specific reason not to apply the same change to shpchp. - * The hardirq handler shpc_isr() queues events on a workqueue. It can be simplified by converting it to threaded IRQ handling. Use pciehp as a template. diff --git a/drivers/pci/hotplug/acpiphp.h b/drivers/pci/hotplug/acpiphp.h index 1f8ab4377ad8..5745be6018e1 100644 --- a/drivers/pci/hotplug/acpiphp.h +++ b/drivers/pci/hotplug/acpiphp.h @@ -178,7 +178,6 @@ void acpiphp_unregister_hotplug_slot(struct acpiphp_slot *slot); int acpiphp_enable_slot(struct acpiphp_slot *slot); int acpiphp_disable_slot(struct acpiphp_slot *slot); u8 acpiphp_get_power_status(struct acpiphp_slot *slot); -u8 acpiphp_get_attention_status(struct acpiphp_slot *slot); u8 acpiphp_get_latch_status(struct acpiphp_slot *slot); u8 acpiphp_get_adapter_status(struct acpiphp_slot *slot); diff --git a/drivers/pci/hotplug/acpiphp_ampere_altra.c b/drivers/pci/hotplug/acpiphp_ampere_altra.c new file mode 100644 index 000000000000..70dbc0431fc6 --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_ampere_altra.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ACPI PCI Hot Plug Extension for Ampere Altra. Allows control of + * attention LEDs via requests to system firmware. + * + * Copyright (C) 2023 Ampere Computing LLC + */ + +#define pr_fmt(fmt) "acpiphp_ampere_altra: " fmt + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/platform_device.h> + +#include "acpiphp.h" + +#define HANDLE_OPEN 0xb0200000 +#define HANDLE_CLOSE 0xb0300000 +#define REQUEST 0xf0700000 +#define LED_CMD 0x00000004 +#define LED_ATTENTION 0x00000002 +#define LED_SET_ON 0x00000001 +#define LED_SET_OFF 0x00000002 +#define LED_SET_BLINK 0x00000003 + +static u32 led_service_id[4]; + +static int led_status(u8 status) +{ + switch (status) { + case 1: return LED_SET_ON; + case 2: return LED_SET_BLINK; + default: return LED_SET_OFF; + } +} + +static int set_attention_status(struct hotplug_slot *slot, u8 status) +{ + struct arm_smccc_res res; + struct pci_bus *bus; + struct pci_dev *root_port; + unsigned long flags; + u32 handle; + int ret = 0; + + bus = slot->pci_slot->bus; + root_port = pcie_find_root_port(bus->self); + if (!root_port) + return -ENODEV; + + local_irq_save(flags); + arm_smccc_smc(HANDLE_OPEN, led_service_id[0], led_service_id[1], + led_service_id[2], led_service_id[3], 0, 0, 0, &res); + if (res.a0) { + ret = -ENODEV; + goto out; + } + handle = res.a1 & 0xffff0000; + + arm_smccc_smc(REQUEST, LED_CMD, led_status(status), LED_ATTENTION, + (PCI_SLOT(root_port->devfn) << 4) | (pci_domain_nr(bus) & 0xf), + 0, 0, handle, &res); + if (res.a0) + ret = -ENODEV; + + arm_smccc_smc(HANDLE_CLOSE, handle, 0, 0, 0, 0, 0, 0, &res); + + out: + local_irq_restore(flags); + return ret; +} + +static int get_attention_status(struct hotplug_slot *slot, u8 *status) +{ + return -EINVAL; +} + +static struct acpiphp_attention_info ampere_altra_attn = { + .set_attn = set_attention_status, + .get_attn = get_attention_status, + .owner = THIS_MODULE, +}; + +static int altra_led_probe(struct platform_device *pdev) +{ + struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev); + int ret; + + ret = fwnode_property_read_u32_array(fwnode, "uuid", led_service_id, 4); + if (ret) { + dev_err(&pdev->dev, "can't find uuid\n"); + return ret; + } + + ret = acpiphp_register_attention(&ere_altra_attn); + if (ret) { + dev_err(&pdev->dev, "can't register driver\n"); + return ret; + } + return 0; +} + +static void altra_led_remove(struct platform_device *pdev) +{ + acpiphp_unregister_attention(&ere_altra_attn); +} + +static const struct acpi_device_id altra_led_ids[] = { + { "AMPC0008", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, altra_led_ids); + +static struct platform_driver altra_led_driver = { + .driver = { + .name = "ampere-altra-leds", + .acpi_match_table = altra_led_ids, + }, + .probe = altra_led_probe, + .remove = altra_led_remove, +}; +module_platform_driver(altra_led_driver); + +MODULE_AUTHOR("D Scott Phillips <scott@os.amperecomputing.com>"); +MODULE_DESCRIPTION("ACPI PCI Hot Plug Extension for Ampere Altra"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pci/hotplug/acpiphp_core.c b/drivers/pci/hotplug/acpiphp_core.c index 853e04ad272c..9dad14e80bcf 100644 --- a/drivers/pci/hotplug/acpiphp_core.c +++ b/drivers/pci/hotplug/acpiphp_core.c @@ -45,7 +45,6 @@ static struct acpiphp_attention_info *attention_info; MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); MODULE_PARM_DESC(disable, "disable acpiphp driver"); module_param_named(disable, acpiphp_disabled, bool, 0444); @@ -79,8 +78,7 @@ int acpiphp_register_attention(struct acpiphp_attention_info *info) { int retval = -EINVAL; - if (info && info->owner && info->set_attn && - info->get_attn && !attention_info) { + if (info && info->set_attn && info->get_attn && !attention_info) { retval = 0; attention_info = info; } diff --git a/drivers/pci/hotplug/acpiphp_ibm.c b/drivers/pci/hotplug/acpiphp_ibm.c index 8f3a0a33f362..18e01cd55a8e 100644 --- a/drivers/pci/hotplug/acpiphp_ibm.c +++ b/drivers/pci/hotplug/acpiphp_ibm.c @@ -84,7 +84,7 @@ static int ibm_get_attention_status(struct hotplug_slot *slot, u8 *status); static void ibm_handle_events(acpi_handle handle, u32 event, void *context); static int ibm_get_table_from_acpi(char **bufp); static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t size); static acpi_status __init ibm_find_acpi_device(acpi_handle handle, u32 lvl, void *context, void **rv); @@ -353,7 +353,7 @@ read_table_done: * our solution is to only allow reading the table in all at once. */ static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, + const struct bin_attribute *bin_attr, char *buffer, loff_t pos, size_t size) { int bytes_read = -EINVAL; diff --git a/drivers/pci/hotplug/cpci_hotplug.h b/drivers/pci/hotplug/cpci_hotplug.h index 3fdd1b9bd8c3..a31d6b62f523 100644 --- a/drivers/pci/hotplug/cpci_hotplug.h +++ b/drivers/pci/hotplug/cpci_hotplug.h @@ -44,9 +44,6 @@ struct cpci_hp_controller_ops { int (*enable_irq)(void); int (*disable_irq)(void); int (*check_irq)(void *dev_id); - int (*hardware_test)(struct slot *slot, u32 value); - u8 (*get_power)(struct slot *slot); - int (*set_power)(struct slot *slot, int value); }; struct cpci_hp_controller { @@ -83,8 +80,6 @@ extern int cpci_debug; * board/chassis drivers. */ u8 cpci_get_attention_status(struct slot *slot); -u8 cpci_get_latch_status(struct slot *slot); -u8 cpci_get_adapter_status(struct slot *slot); u16 cpci_get_hs_csr(struct slot *slot); int cpci_set_attention_status(struct slot *slot, int status); int cpci_check_and_clear_ins(struct slot *slot); diff --git a/drivers/pci/hotplug/cpci_hotplug_core.c b/drivers/pci/hotplug/cpci_hotplug_core.c index d0559d2faf50..dd93e53ea7c2 100644 --- a/drivers/pci/hotplug/cpci_hotplug_core.c +++ b/drivers/pci/hotplug/cpci_hotplug_core.c @@ -71,13 +71,10 @@ static int enable_slot(struct hotplug_slot *hotplug_slot) { struct slot *slot = to_slot(hotplug_slot); - int retval = 0; dbg("%s - physical_slot = %s", __func__, slot_name(slot)); - if (controller->ops->set_power) - retval = controller->ops->set_power(slot, 1); - return retval; + return 0; } static int @@ -109,12 +106,6 @@ disable_slot(struct hotplug_slot *hotplug_slot) } cpci_led_on(slot); - if (controller->ops->set_power) { - retval = controller->ops->set_power(slot, 0); - if (retval) - goto disable_error; - } - slot->adapter_status = 0; if (slot->extracting) { @@ -129,11 +120,7 @@ disable_error: static u8 cpci_get_power_status(struct slot *slot) { - u8 power = 1; - - if (controller->ops->get_power) - power = controller->ops->get_power(slot); - return power; + return 1; } static int diff --git a/drivers/pci/hotplug/cpqphp_core.c b/drivers/pci/hotplug/cpqphp_core.c index c94b40e64baf..47a3ed16159a 100644 --- a/drivers/pci/hotplug/cpqphp_core.c +++ b/drivers/pci/hotplug/cpqphp_core.c @@ -328,7 +328,7 @@ get_slot_mapping(struct pci_bus *bus, u8 bus_num, u8 dev_num, u8 *slot) } else { /* Did not get a match on the target PCI device. Check * if the current IRQ table entry is a PCI-to-PCI - * bridge device. If so, and it's secondary bus + * bridge device. If so, and its secondary bus * matches the bus number for the target device, I need * to save the bridge's slot number. If I can not find * an entry for the target device, I will have to diff --git a/drivers/pci/hotplug/cpqphp_ctrl.c b/drivers/pci/hotplug/cpqphp_ctrl.c index e429ecddc8fe..760a5dec0431 100644 --- a/drivers/pci/hotplug/cpqphp_ctrl.c +++ b/drivers/pci/hotplug/cpqphp_ctrl.c @@ -1794,7 +1794,7 @@ static void interrupt_event_handler(struct controller *ctrl) } else if (ctrl->event_queue[loop].event_type == INT_BUTTON_CANCEL) { dbg("button cancel\n"); - del_timer(&p_slot->task_event); + timer_delete(&p_slot->task_event); mutex_lock(&ctrl->crit_sect); @@ -1883,7 +1883,7 @@ void cpqhp_pushbutton_thread(struct timer_list *t) { u8 hp_slot; struct pci_func *func; - struct slot *p_slot = from_timer(p_slot, t, task_event); + struct slot *p_slot = timer_container_of(p_slot, t, task_event); struct controller *ctrl = (struct controller *) p_slot->ctrl; pushbutton_pending = NULL; @@ -2059,7 +2059,7 @@ int cpqhp_process_SS(struct controller *ctrl, struct pci_func *func) return rc; /* If it's a bridge, check the VGA Enable bit */ - if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { + if ((header_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_BRIDGE) { rc = pci_bus_read_config_byte(pci_bus, devfn, PCI_BRIDGE_CONTROL, &BCR); if (rc) return rc; @@ -2342,7 +2342,7 @@ static int configure_new_function(struct controller *ctrl, struct pci_func *func if (rc) return rc; - if ((temp_byte & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { + if ((temp_byte & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_BRIDGE) { /* set Primary bus */ dbg("set Primary bus = %d\n", func->bus); rc = pci_bus_write_config_byte(pci_bus, devfn, PCI_PRIMARY_BUS, func->bus); @@ -2739,7 +2739,7 @@ static int configure_new_function(struct controller *ctrl, struct pci_func *func * PCI_BRIDGE_CTL_SERR | * PCI_BRIDGE_CTL_NO_ISA */ rc = pci_bus_write_config_word(pci_bus, devfn, PCI_BRIDGE_CONTROL, command); - } else if ((temp_byte & 0x7F) == PCI_HEADER_TYPE_NORMAL) { + } else if ((temp_byte & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_NORMAL) { /* Standard device */ rc = pci_bus_read_config_byte(pci_bus, devfn, 0x0B, &class_code); diff --git a/drivers/pci/hotplug/cpqphp_pci.c b/drivers/pci/hotplug/cpqphp_pci.c index 3b248426a9f4..88929360fe77 100644 --- a/drivers/pci/hotplug/cpqphp_pci.c +++ b/drivers/pci/hotplug/cpqphp_pci.c @@ -12,8 +12,11 @@ * */ +#define pr_fmt(fmt) "cpqphp: " fmt + #include <linux/module.h> #include <linux/kernel.h> +#include <linux/printk.h> #include <linux/types.h> #include <linux/slab.h> #include <linux/workqueue.h> @@ -132,18 +135,6 @@ int cpqhp_unconfigure_device(struct pci_func *func) return 0; } -static int PCI_RefinedAccessConfig(struct pci_bus *bus, unsigned int devfn, u8 offset, u32 *value) -{ - u32 vendID = 0; - - if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &vendID) == -1) - return -1; - if (vendID == 0xffffffff) - return -1; - return pci_bus_read_config_dword(bus, devfn, offset, value); -} - - /* * cpqhp_set_irq * @@ -202,13 +193,16 @@ static int PCI_ScanBusForNonBridge(struct controller *ctrl, u8 bus_num, u8 *dev_ { u16 tdevice; u32 work; - u8 tbus; + int ret = -1; ctrl->pci_bus->number = bus_num; for (tdevice = 0; tdevice < 0xFF; tdevice++) { /* Scan for access first */ - if (PCI_RefinedAccessConfig(ctrl->pci_bus, tdevice, 0x08, &work) == -1) + if (!pci_bus_read_dev_vendor_id(ctrl->pci_bus, tdevice, &work, 0)) + continue; + ret = pci_bus_read_config_dword(ctrl->pci_bus, tdevice, PCI_CLASS_REVISION, &work); + if (ret) continue; dbg("Looking for nonbridge bus_num %d dev_num %d\n", bus_num, tdevice); /* Yep we got one. Not a bridge ? */ @@ -216,23 +210,20 @@ static int PCI_ScanBusForNonBridge(struct controller *ctrl, u8 bus_num, u8 *dev_ *dev_num = tdevice; dbg("found it !\n"); return 0; - } - } - for (tdevice = 0; tdevice < 0xFF; tdevice++) { - /* Scan for access first */ - if (PCI_RefinedAccessConfig(ctrl->pci_bus, tdevice, 0x08, &work) == -1) - continue; - dbg("Looking for bridge bus_num %d dev_num %d\n", bus_num, tdevice); - /* Yep we got one. bridge ? */ - if ((work >> 8) == PCI_TO_PCI_BRIDGE_CLASS) { - pci_bus_read_config_byte(ctrl->pci_bus, PCI_DEVFN(tdevice, 0), PCI_SECONDARY_BUS, &tbus); - /* XXX: no recursion, wtf? */ - dbg("Recurse on bus_num %d tdevice %d\n", tbus, tdevice); - return 0; + } else { + /* + * XXX: Code whose debug printout indicated + * recursion to buses underneath bridges might be + * necessary was removed because it never did + * any recursion. + */ + ret = 0; + pr_warn("missing feature: bridge scan recursion not implemented\n"); } } - return -1; + + return ret; } @@ -253,7 +244,7 @@ static int PCI_GetBusDevHelper(struct controller *ctrl, u8 *bus_num, u8 *dev_num *dev_num = tdevice; ctrl->pci_bus->number = tbus; pci_bus_read_config_dword(ctrl->pci_bus, *dev_num, PCI_VENDOR_ID, &work); - if (!nobridge || (work == 0xffffffff)) + if (!nobridge || PCI_POSSIBLE_ERROR(work)) return 0; dbg("bus_num %d devfn %d\n", *bus_num, *dev_num); @@ -363,7 +354,7 @@ int cpqhp_save_config(struct controller *ctrl, int busnumber, int is_hot_plug) return rc; /* If multi-function device, set max_functions to 8 */ - if (header_type & 0x80) + if (header_type & PCI_HEADER_TYPE_MFD) max_functions = 8; else max_functions = 1; @@ -372,7 +363,7 @@ int cpqhp_save_config(struct controller *ctrl, int busnumber, int is_hot_plug) do { DevError = 0; - if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { + if ((header_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_BRIDGE) { /* Recurse the subordinate bus * get the subordinate bus number */ @@ -487,13 +478,13 @@ int cpqhp_save_slot_config(struct controller *ctrl, struct pci_func *new_slot) pci_bus_read_config_byte(ctrl->pci_bus, PCI_DEVFN(new_slot->device, 0), 0x0B, &class_code); pci_bus_read_config_byte(ctrl->pci_bus, PCI_DEVFN(new_slot->device, 0), PCI_HEADER_TYPE, &header_type); - if (header_type & 0x80) /* Multi-function device */ + if (header_type & PCI_HEADER_TYPE_MFD) max_functions = 8; else max_functions = 1; while (function < max_functions) { - if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { + if ((header_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_BRIDGE) { /* Recurse the subordinate bus */ pci_bus_read_config_byte(ctrl->pci_bus, PCI_DEVFN(new_slot->device, function), PCI_SECONDARY_BUS, &secondary_bus); @@ -571,7 +562,7 @@ int cpqhp_save_base_addr_length(struct controller *ctrl, struct pci_func *func) /* Check for Bridge */ pci_bus_read_config_byte(pci_bus, devfn, PCI_HEADER_TYPE, &header_type); - if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { + if ((header_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_BRIDGE) { pci_bus_read_config_byte(pci_bus, devfn, PCI_SECONDARY_BUS, &secondary_bus); sub_bus = (int) secondary_bus; @@ -625,7 +616,7 @@ int cpqhp_save_base_addr_length(struct controller *ctrl, struct pci_func *func) } /* End of base register loop */ - } else if ((header_type & 0x7F) == 0x00) { + } else if ((header_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_NORMAL) { /* Figure out IO and memory base lengths */ for (cloop = 0x10; cloop <= 0x24; cloop += 4) { temp_register = 0xFFFFFFFF; @@ -723,7 +714,7 @@ int cpqhp_save_used_resources(struct controller *ctrl, struct pci_func *func) /* Check for Bridge */ pci_bus_read_config_byte(pci_bus, devfn, PCI_HEADER_TYPE, &header_type); - if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { + if ((header_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_BRIDGE) { /* Clear Bridge Control Register */ command = 0x00; pci_bus_write_config_word(pci_bus, devfn, PCI_BRIDGE_CONTROL, command); @@ -858,7 +849,7 @@ int cpqhp_save_used_resources(struct controller *ctrl, struct pci_func *func) } } /* End of base register loop */ /* Standard header */ - } else if ((header_type & 0x7F) == 0x00) { + } else if ((header_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_NORMAL) { /* Figure out IO and memory base lengths */ for (cloop = 0x10; cloop <= 0x24; cloop += 4) { pci_bus_read_config_dword(pci_bus, devfn, cloop, &save_base); @@ -975,7 +966,7 @@ int cpqhp_configure_board(struct controller *ctrl, struct pci_func *func) pci_bus_read_config_byte(pci_bus, devfn, PCI_HEADER_TYPE, &header_type); /* If this is a bridge device, restore subordinate devices */ - if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { + if ((header_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_BRIDGE) { pci_bus_read_config_byte(pci_bus, devfn, PCI_SECONDARY_BUS, &secondary_bus); sub_bus = (int) secondary_bus; @@ -1067,7 +1058,7 @@ int cpqhp_valid_replace(struct controller *ctrl, struct pci_func *func) /* Check for Bridge */ pci_bus_read_config_byte(pci_bus, devfn, PCI_HEADER_TYPE, &header_type); - if ((header_type & 0x7F) == PCI_HEADER_TYPE_BRIDGE) { + if ((header_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_BRIDGE) { /* In order to continue checking, we must program the * bus registers in the bridge to respond to accesses * for its subordinate bus(es) @@ -1090,7 +1081,7 @@ int cpqhp_valid_replace(struct controller *ctrl, struct pci_func *func) } /* Check to see if it is a standard config header */ - else if ((header_type & 0x7F) == PCI_HEADER_TYPE_NORMAL) { + else if ((header_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_NORMAL) { /* Check subsystem vendor and ID */ pci_bus_read_config_dword(pci_bus, devfn, PCI_SUBSYSTEM_VENDOR_ID, &temp_register); @@ -1311,7 +1302,7 @@ int cpqhp_find_available_resources(struct controller *ctrl, void __iomem *rom_st dbg("found io_node(base, length) = %x, %x\n", io_node->base, io_node->length); - dbg("populated slot =%d \n", populated_slot); + dbg("populated slot = %d\n", populated_slot); if (!populated_slot) { io_node->next = ctrl->io_head; ctrl->io_head = io_node; @@ -1334,7 +1325,7 @@ int cpqhp_find_available_resources(struct controller *ctrl, void __iomem *rom_st dbg("found mem_node(base, length) = %x, %x\n", mem_node->base, mem_node->length); - dbg("populated slot =%d \n", populated_slot); + dbg("populated slot = %d\n", populated_slot); if (!populated_slot) { mem_node->next = ctrl->mem_head; ctrl->mem_head = mem_node; @@ -1358,7 +1349,7 @@ int cpqhp_find_available_resources(struct controller *ctrl, void __iomem *rom_st p_mem_node->length = pre_mem_length << 16; dbg("found p_mem_node(base, length) = %x, %x\n", p_mem_node->base, p_mem_node->length); - dbg("populated slot =%d \n", populated_slot); + dbg("populated slot = %d\n", populated_slot); if (!populated_slot) { p_mem_node->next = ctrl->p_mem_head; @@ -1382,7 +1373,7 @@ int cpqhp_find_available_resources(struct controller *ctrl, void __iomem *rom_st bus_node->length = max_bus - secondary_bus + 1; dbg("found bus_node(base, length) = %x, %x\n", bus_node->base, bus_node->length); - dbg("populated slot =%d \n", populated_slot); + dbg("populated slot = %d\n", populated_slot); if (!populated_slot) { bus_node->next = ctrl->bus_head; ctrl->bus_head = bus_node; diff --git a/drivers/pci/hotplug/cpqphp_sysfs.c b/drivers/pci/hotplug/cpqphp_sysfs.c index fed1360ee9b1..6143ebf71f21 100644 --- a/drivers/pci/hotplug/cpqphp_sysfs.c +++ b/drivers/pci/hotplug/cpqphp_sysfs.c @@ -123,7 +123,6 @@ static int spew_debug_info(struct controller *ctrl, char *data, int size) struct ctrl_dbg { int size; char *data; - struct controller *ctrl; }; #define MAX_OUTPUT (4*PAGE_SIZE) diff --git a/drivers/pci/hotplug/ibmphp.h b/drivers/pci/hotplug/ibmphp.h index 0399c60d2ec1..c248a09be7b5 100644 --- a/drivers/pci/hotplug/ibmphp.h +++ b/drivers/pci/hotplug/ibmphp.h @@ -17,6 +17,7 @@ */ #include <linux/pci_hotplug.h> +#include <linux/pci_regs.h> extern int ibmphp_debug; @@ -264,8 +265,6 @@ extern struct list_head ibmphp_slot_head; void ibmphp_free_ebda_hpc_queue(void); int ibmphp_access_ebda(void); struct slot *ibmphp_get_slot_from_physical_num(u8); -int ibmphp_get_total_hp_slots(void); -void ibmphp_free_ibm_slot(struct slot *); void ibmphp_free_bus_info_queue(void); void ibmphp_free_ebda_pci_rsrc_queue(void); struct bus_info *ibmphp_find_same_bus_num(u32); @@ -288,8 +287,8 @@ int ibmphp_register_pci(void); /* pci specific defines */ #define PCI_VENDOR_ID_NOTVALID 0xFFFF -#define PCI_HEADER_TYPE_MULTIDEVICE 0x80 -#define PCI_HEADER_TYPE_MULTIBRIDGE 0x81 +#define PCI_HEADER_TYPE_MULTIDEVICE (PCI_HEADER_TYPE_MFD|PCI_HEADER_TYPE_NORMAL) +#define PCI_HEADER_TYPE_MULTIBRIDGE (PCI_HEADER_TYPE_MFD|PCI_HEADER_TYPE_BRIDGE) #define LATENCY 0x64 #define CACHE 64 diff --git a/drivers/pci/hotplug/ibmphp_hpc.c b/drivers/pci/hotplug/ibmphp_hpc.c index a5720d12e573..2324167656a6 100644 --- a/drivers/pci/hotplug/ibmphp_hpc.c +++ b/drivers/pci/hotplug/ibmphp_hpc.c @@ -124,7 +124,7 @@ static u8 i2c_ctrl_read(struct controller *ctlr_ptr, void __iomem *WPGBbar, u8 i unsigned long ultemp; unsigned long data; // actual data HILO format - debug_polling("%s - Entry WPGBbar[%p] index[%x] \n", __func__, WPGBbar, index); + debug_polling("%s - Entry WPGBbar[%p] index[%x]\n", __func__, WPGBbar, index); //-------------------------------------------------------------------- // READ - step 1 @@ -147,7 +147,7 @@ static u8 i2c_ctrl_read(struct controller *ctlr_ptr, void __iomem *WPGBbar, u8 i ultemp = ultemp << 8; data |= ultemp; } else { - err("this controller type is not supported \n"); + err("this controller type is not supported\n"); return HPC_ERROR; } @@ -258,7 +258,7 @@ static u8 i2c_ctrl_write(struct controller *ctlr_ptr, void __iomem *WPGBbar, u8 ultemp = ultemp << 8; data |= ultemp; } else { - err("this controller type is not supported \n"); + err("this controller type is not supported\n"); return HPC_ERROR; } diff --git a/drivers/pci/hotplug/ibmphp_pci.c b/drivers/pci/hotplug/ibmphp_pci.c index 754c3f23282e..eeb412cbd9fe 100644 --- a/drivers/pci/hotplug/ibmphp_pci.c +++ b/drivers/pci/hotplug/ibmphp_pci.c @@ -329,7 +329,7 @@ error: static int configure_device(struct pci_func *func) { u32 bar[6]; - u32 address[] = { + static const u32 address[] = { PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_2, @@ -564,7 +564,7 @@ static int configure_bridge(struct pci_func **func_passed, u8 slotno) struct resource_node *pfmem = NULL; struct resource_node *bus_pfmem[2] = {NULL, NULL}; struct bus_node *bus; - u32 address[] = { + static const u32 address[] = { PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_1, 0 @@ -1053,7 +1053,7 @@ static struct res_needed *scan_behind_bridge(struct pci_func *func, u8 busno) int howmany = 0; /*this is to see if there are any devices behind the bridge */ u32 bar[6], class; - u32 address[] = { + static const u32 address[] = { PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_2, @@ -1087,7 +1087,7 @@ static struct res_needed *scan_behind_bridge(struct pci_func *func, u8 busno) pci_bus_read_config_dword(ibmphp_pci_bus, devfn, PCI_CLASS_REVISION, &class); debug("hdr_type behind the bridge is %x\n", hdr_type); - if ((hdr_type & 0x7f) == PCI_HEADER_TYPE_BRIDGE) { + if ((hdr_type & PCI_HEADER_TYPE_MASK) == PCI_HEADER_TYPE_BRIDGE) { err("embedded bridges not supported for hot-plugging.\n"); amount->not_correct = 1; return amount; @@ -1182,7 +1182,7 @@ static struct res_needed *scan_behind_bridge(struct pci_func *func, u8 busno) static int unconfigure_boot_device(u8 busno, u8 device, u8 function) { u32 start_address; - u32 address[] = { + static const u32 address[] = { PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_1, PCI_BASE_ADDRESS_2, @@ -1310,7 +1310,7 @@ static int unconfigure_boot_bridge(u8 busno, u8 device, u8 function) struct resource_node *mem = NULL; struct resource_node *pfmem = NULL; struct bus_node *bus; - u32 address[] = { + static const u32 address[] = { PCI_BASE_ADDRESS_0, PCI_BASE_ADDRESS_1, 0 diff --git a/drivers/pci/hotplug/octep_hp.c b/drivers/pci/hotplug/octep_hp.c new file mode 100644 index 000000000000..2bce7296c050 --- /dev/null +++ b/drivers/pci/hotplug/octep_hp.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (C) 2024 Marvell. */ + +#include <linux/cleanup.h> +#include <linux/container_of.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/pci_hotplug.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> + +#define OCTEP_HP_INTR_OFFSET(x) (0x20400 + ((x) << 4)) +#define OCTEP_HP_INTR_VECTOR(x) (16 + (x)) +#define OCTEP_HP_DRV_NAME "octep_hp" + +/* + * Type of MSI-X interrupts. OCTEP_HP_INTR_VECTOR() and + * OCTEP_HP_INTR_OFFSET() generate the vector and offset for an interrupt + * type. + */ +enum octep_hp_intr_type { + OCTEP_HP_INTR_INVALID = -1, + OCTEP_HP_INTR_ENA = 0, + OCTEP_HP_INTR_DIS = 1, + OCTEP_HP_INTR_MAX = 2, +}; + +struct octep_hp_cmd { + struct list_head list; + enum octep_hp_intr_type intr_type; + u64 intr_val; +}; + +struct octep_hp_slot { + struct list_head list; + struct hotplug_slot slot; + u16 slot_number; + struct pci_dev *hp_pdev; + unsigned int hp_devfn; + struct octep_hp_controller *ctrl; +}; + +struct octep_hp_intr_info { + enum octep_hp_intr_type type; + int number; + char name[16]; +}; + +struct octep_hp_controller { + void __iomem *base; + struct pci_dev *pdev; + struct octep_hp_intr_info intr[OCTEP_HP_INTR_MAX]; + struct work_struct work; + struct list_head slot_list; + struct mutex slot_lock; /* Protects slot_list */ + struct list_head hp_cmd_list; + spinlock_t hp_cmd_lock; /* Protects hp_cmd_list */ +}; + +static void octep_hp_enable_pdev(struct octep_hp_controller *hp_ctrl, + struct octep_hp_slot *hp_slot) +{ + guard(mutex)(&hp_ctrl->slot_lock); + if (hp_slot->hp_pdev) { + pci_dbg(hp_slot->hp_pdev, "Slot %s is already enabled\n", + hotplug_slot_name(&hp_slot->slot)); + return; + } + + /* Scan the device and add it to the bus */ + hp_slot->hp_pdev = pci_scan_single_device(hp_ctrl->pdev->bus, + hp_slot->hp_devfn); + pci_bus_assign_resources(hp_ctrl->pdev->bus); + pci_bus_add_device(hp_slot->hp_pdev); + + dev_dbg(&hp_slot->hp_pdev->dev, "Enabled slot %s\n", + hotplug_slot_name(&hp_slot->slot)); +} + +static void octep_hp_disable_pdev(struct octep_hp_controller *hp_ctrl, + struct octep_hp_slot *hp_slot) +{ + guard(mutex)(&hp_ctrl->slot_lock); + if (!hp_slot->hp_pdev) { + pci_dbg(hp_ctrl->pdev, "Slot %s is already disabled\n", + hotplug_slot_name(&hp_slot->slot)); + return; + } + + pci_dbg(hp_slot->hp_pdev, "Disabling slot %s\n", + hotplug_slot_name(&hp_slot->slot)); + + /* Remove the device from the bus */ + pci_stop_and_remove_bus_device_locked(hp_slot->hp_pdev); + hp_slot->hp_pdev = NULL; +} + +static int octep_hp_enable_slot(struct hotplug_slot *slot) +{ + struct octep_hp_slot *hp_slot = + container_of(slot, struct octep_hp_slot, slot); + + octep_hp_enable_pdev(hp_slot->ctrl, hp_slot); + return 0; +} + +static int octep_hp_disable_slot(struct hotplug_slot *slot) +{ + struct octep_hp_slot *hp_slot = + container_of(slot, struct octep_hp_slot, slot); + + octep_hp_disable_pdev(hp_slot->ctrl, hp_slot); + return 0; +} + +static struct hotplug_slot_ops octep_hp_slot_ops = { + .enable_slot = octep_hp_enable_slot, + .disable_slot = octep_hp_disable_slot, +}; + +#define SLOT_NAME_SIZE 16 +static struct octep_hp_slot * +octep_hp_register_slot(struct octep_hp_controller *hp_ctrl, + struct pci_dev *pdev, u16 slot_number) +{ + char slot_name[SLOT_NAME_SIZE]; + struct octep_hp_slot *hp_slot; + int ret; + + hp_slot = kzalloc(sizeof(*hp_slot), GFP_KERNEL); + if (!hp_slot) + return ERR_PTR(-ENOMEM); + + hp_slot->ctrl = hp_ctrl; + hp_slot->hp_pdev = pdev; + hp_slot->hp_devfn = pdev->devfn; + hp_slot->slot_number = slot_number; + hp_slot->slot.ops = &octep_hp_slot_ops; + + snprintf(slot_name, sizeof(slot_name), "octep_hp_%u", slot_number); + ret = pci_hp_register(&hp_slot->slot, hp_ctrl->pdev->bus, + PCI_SLOT(pdev->devfn), slot_name); + if (ret) { + kfree(hp_slot); + return ERR_PTR(ret); + } + + pci_info(pdev, "Registered slot %s for device %s\n", + slot_name, pci_name(pdev)); + + list_add_tail(&hp_slot->list, &hp_ctrl->slot_list); + octep_hp_disable_pdev(hp_ctrl, hp_slot); + + return hp_slot; +} + +static void octep_hp_deregister_slot(void *data) +{ + struct octep_hp_slot *hp_slot = data; + struct octep_hp_controller *hp_ctrl = hp_slot->ctrl; + + pci_hp_deregister(&hp_slot->slot); + octep_hp_enable_pdev(hp_ctrl, hp_slot); + list_del(&hp_slot->list); + kfree(hp_slot); +} + +static const char *octep_hp_cmd_name(enum octep_hp_intr_type type) +{ + switch (type) { + case OCTEP_HP_INTR_ENA: + return "hotplug enable"; + case OCTEP_HP_INTR_DIS: + return "hotplug disable"; + default: + return "invalid"; + } +} + +static void octep_hp_cmd_handler(struct octep_hp_controller *hp_ctrl, + struct octep_hp_cmd *hp_cmd) +{ + struct octep_hp_slot *hp_slot; + + /* + * Enable or disable the slots based on the slot mask. + * intr_val is a bit mask where each bit represents a slot. + */ + list_for_each_entry(hp_slot, &hp_ctrl->slot_list, list) { + if (!(hp_cmd->intr_val & BIT(hp_slot->slot_number))) + continue; + + pci_info(hp_ctrl->pdev, "Received %s command for slot %s\n", + octep_hp_cmd_name(hp_cmd->intr_type), + hotplug_slot_name(&hp_slot->slot)); + + switch (hp_cmd->intr_type) { + case OCTEP_HP_INTR_ENA: + octep_hp_enable_pdev(hp_ctrl, hp_slot); + break; + case OCTEP_HP_INTR_DIS: + octep_hp_disable_pdev(hp_ctrl, hp_slot); + break; + default: + break; + } + } +} + +static void octep_hp_work_handler(struct work_struct *work) +{ + struct octep_hp_controller *hp_ctrl; + struct octep_hp_cmd *hp_cmd; + unsigned long flags; + + hp_ctrl = container_of(work, struct octep_hp_controller, work); + + /* Process all the hotplug commands */ + spin_lock_irqsave(&hp_ctrl->hp_cmd_lock, flags); + while (!list_empty(&hp_ctrl->hp_cmd_list)) { + hp_cmd = list_first_entry(&hp_ctrl->hp_cmd_list, + struct octep_hp_cmd, list); + list_del(&hp_cmd->list); + spin_unlock_irqrestore(&hp_ctrl->hp_cmd_lock, flags); + + octep_hp_cmd_handler(hp_ctrl, hp_cmd); + kfree(hp_cmd); + + spin_lock_irqsave(&hp_ctrl->hp_cmd_lock, flags); + } + spin_unlock_irqrestore(&hp_ctrl->hp_cmd_lock, flags); +} + +static enum octep_hp_intr_type octep_hp_intr_type(struct octep_hp_intr_info *intr, + int irq) +{ + enum octep_hp_intr_type type; + + for (type = OCTEP_HP_INTR_ENA; type < OCTEP_HP_INTR_MAX; type++) { + if (intr[type].number == irq) + return type; + } + + return OCTEP_HP_INTR_INVALID; +} + +static irqreturn_t octep_hp_intr_handler(int irq, void *data) +{ + struct octep_hp_controller *hp_ctrl = data; + struct pci_dev *pdev = hp_ctrl->pdev; + enum octep_hp_intr_type type; + struct octep_hp_cmd *hp_cmd; + u64 intr_val; + + type = octep_hp_intr_type(hp_ctrl->intr, irq); + if (type == OCTEP_HP_INTR_INVALID) { + pci_err(pdev, "Invalid interrupt %d\n", irq); + return IRQ_HANDLED; + } + + /* Read and clear the interrupt */ + intr_val = readq(hp_ctrl->base + OCTEP_HP_INTR_OFFSET(type)); + writeq(intr_val, hp_ctrl->base + OCTEP_HP_INTR_OFFSET(type)); + + hp_cmd = kzalloc(sizeof(*hp_cmd), GFP_ATOMIC); + if (!hp_cmd) + return IRQ_HANDLED; + + hp_cmd->intr_val = intr_val; + hp_cmd->intr_type = type; + + /* Add the command to the list and schedule the work */ + spin_lock(&hp_ctrl->hp_cmd_lock); + list_add_tail(&hp_cmd->list, &hp_ctrl->hp_cmd_list); + spin_unlock(&hp_ctrl->hp_cmd_lock); + schedule_work(&hp_ctrl->work); + + return IRQ_HANDLED; +} + +static void octep_hp_irq_cleanup(void *data) +{ + struct octep_hp_controller *hp_ctrl = data; + + pci_free_irq_vectors(hp_ctrl->pdev); + flush_work(&hp_ctrl->work); +} + +static int octep_hp_request_irq(struct octep_hp_controller *hp_ctrl, + enum octep_hp_intr_type type) +{ + struct pci_dev *pdev = hp_ctrl->pdev; + struct octep_hp_intr_info *intr; + int irq; + + irq = pci_irq_vector(pdev, OCTEP_HP_INTR_VECTOR(type)); + if (irq < 0) + return irq; + + intr = &hp_ctrl->intr[type]; + intr->number = irq; + intr->type = type; + snprintf(intr->name, sizeof(intr->name), "octep_hp_%d", type); + + return devm_request_irq(&pdev->dev, irq, octep_hp_intr_handler, + IRQF_SHARED, intr->name, hp_ctrl); +} + +static int octep_hp_controller_setup(struct pci_dev *pdev, + struct octep_hp_controller *hp_ctrl) +{ + struct device *dev = &pdev->dev; + enum octep_hp_intr_type type; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable PCI device\n"); + + hp_ctrl->base = pcim_iomap_region(pdev, 0, OCTEP_HP_DRV_NAME); + if (IS_ERR(hp_ctrl->base)) + return dev_err_probe(dev, PTR_ERR(hp_ctrl->base), + "Failed to map PCI device region\n"); + + pci_set_master(pdev); + pci_set_drvdata(pdev, hp_ctrl); + + INIT_LIST_HEAD(&hp_ctrl->slot_list); + INIT_LIST_HEAD(&hp_ctrl->hp_cmd_list); + mutex_init(&hp_ctrl->slot_lock); + spin_lock_init(&hp_ctrl->hp_cmd_lock); + INIT_WORK(&hp_ctrl->work, octep_hp_work_handler); + hp_ctrl->pdev = pdev; + + ret = pci_alloc_irq_vectors(pdev, 1, + OCTEP_HP_INTR_VECTOR(OCTEP_HP_INTR_MAX), + PCI_IRQ_MSIX); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to alloc MSI-X vectors\n"); + + ret = devm_add_action(&pdev->dev, octep_hp_irq_cleanup, hp_ctrl); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to add IRQ cleanup action\n"); + + for (type = OCTEP_HP_INTR_ENA; type < OCTEP_HP_INTR_MAX; type++) { + ret = octep_hp_request_irq(hp_ctrl, type); + if (ret) + return dev_err_probe(dev, ret, + "Failed to request IRQ for vector %d\n", + OCTEP_HP_INTR_VECTOR(type)); + } + + return 0; +} + +static int octep_hp_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct octep_hp_controller *hp_ctrl; + struct pci_dev *tmp_pdev, *next; + struct octep_hp_slot *hp_slot; + u16 slot_number = 0; + int ret; + + hp_ctrl = devm_kzalloc(&pdev->dev, sizeof(*hp_ctrl), GFP_KERNEL); + if (!hp_ctrl) + return -ENOMEM; + + ret = octep_hp_controller_setup(pdev, hp_ctrl); + if (ret) + return ret; + + /* + * Register all hotplug slots. Hotplug controller is the first function + * of the PCI device. The hotplug slots are the remaining functions of + * the PCI device. The hotplug slot functions are logically removed from + * the bus during probing and are re-enabled by the driver when a + * hotplug event is received. + */ + list_for_each_entry_safe(tmp_pdev, next, &pdev->bus->devices, bus_list) { + if (tmp_pdev == pdev) + continue; + + hp_slot = octep_hp_register_slot(hp_ctrl, tmp_pdev, slot_number); + if (IS_ERR(hp_slot)) + return dev_err_probe(&pdev->dev, PTR_ERR(hp_slot), + "Failed to register hotplug slot %u\n", + slot_number); + + ret = devm_add_action(&pdev->dev, octep_hp_deregister_slot, + hp_slot); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to add action for deregistering slot %u\n", + slot_number); + slot_number++; + } + + return 0; +} + +#define PCI_DEVICE_ID_CAVIUM_OCTEP_HP_CTLR 0xa0e3 +static struct pci_device_id octep_hp_pci_map[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_CAVIUM_OCTEP_HP_CTLR) }, + { }, +}; + +static struct pci_driver octep_hp = { + .name = OCTEP_HP_DRV_NAME, + .id_table = octep_hp_pci_map, + .probe = octep_hp_pci_probe, +}; + +module_pci_driver(octep_hp); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marvell"); +MODULE_DESCRIPTION("Marvell OCTEON PCI Hotplug driver"); diff --git a/drivers/pci/hotplug/pci_hotplug_core.c b/drivers/pci/hotplug/pci_hotplug_core.c index 058d5937d8a9..fadcf98a8a66 100644 --- a/drivers/pci/hotplug/pci_hotplug_core.c +++ b/drivers/pci/hotplug/pci_hotplug_core.c @@ -14,21 +14,15 @@ * Scott Murray <scottm@somanetworks.com> */ -#include <linux/module.h> /* try_module_get & module_put */ +#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/kernel.h> #include <linux/types.h> -#include <linux/list.h> #include <linux/kobject.h> #include <linux/sysfs.h> -#include <linux/pagemap.h> #include <linux/init.h> -#include <linux/mount.h> -#include <linux/namei.h> -#include <linux/mutex.h> #include <linux/pci.h> #include <linux/pci_hotplug.h> -#include <linux/uaccess.h> #include "../pci.h" #include "cpci_hotplug.h" @@ -42,20 +36,14 @@ /* local variables */ static bool debug; -static LIST_HEAD(pci_hotplug_slot_list); -static DEFINE_MUTEX(pci_hp_mutex); - /* Weee, fun with macros... */ #define GET_STATUS(name, type) \ static int get_##name(struct hotplug_slot *slot, type *value) \ { \ const struct hotplug_slot_ops *ops = slot->ops; \ int retval = 0; \ - if (!try_module_get(slot->owner)) \ - return -ENODEV; \ if (ops->get_##name) \ retval = ops->get_##name(slot, value); \ - module_put(slot->owner); \ return retval; \ } @@ -88,10 +76,6 @@ static ssize_t power_write_file(struct pci_slot *pci_slot, const char *buf, power = (u8)(lpower & 0xff); dbg("power = %d\n", power); - if (!try_module_get(slot->owner)) { - retval = -ENODEV; - goto exit; - } switch (power) { case 0: if (slot->ops->disable_slot) @@ -107,9 +91,7 @@ static ssize_t power_write_file(struct pci_slot *pci_slot, const char *buf, err("Illegal value specified for power\n"); retval = -EINVAL; } - module_put(slot->owner); -exit: if (retval) return retval; return count; @@ -146,15 +128,9 @@ static ssize_t attention_write_file(struct pci_slot *pci_slot, const char *buf, attention = (u8)(lattention & 0xff); dbg(" - attention = %d\n", attention); - if (!try_module_get(slot->owner)) { - retval = -ENODEV; - goto exit; - } if (ops->set_attention_status) retval = ops->set_attention_status(slot, attention); - module_put(slot->owner); -exit: if (retval) return retval; return count; @@ -212,15 +188,9 @@ static ssize_t test_write_file(struct pci_slot *pci_slot, const char *buf, test = (u32)(ltest & 0xffffffff); dbg("test = %d\n", test); - if (!try_module_get(slot->owner)) { - retval = -ENODEV; - goto exit; - } if (slot->ops->hardware_test) retval = slot->ops->hardware_test(slot, test); - module_put(slot->owner); -exit: if (retval) return retval; return count; @@ -231,12 +201,8 @@ static struct pci_slot_attribute hotplug_slot_attr_test = { .store = test_write_file }; -static bool has_power_file(struct pci_slot *pci_slot) +static bool has_power_file(struct hotplug_slot *slot) { - struct hotplug_slot *slot = pci_slot->hotplug; - - if ((!slot) || (!slot->ops)) - return false; if ((slot->ops->enable_slot) || (slot->ops->disable_slot) || (slot->ops->get_power_status)) @@ -244,87 +210,79 @@ static bool has_power_file(struct pci_slot *pci_slot) return false; } -static bool has_attention_file(struct pci_slot *pci_slot) +static bool has_attention_file(struct hotplug_slot *slot) { - struct hotplug_slot *slot = pci_slot->hotplug; - - if ((!slot) || (!slot->ops)) - return false; if ((slot->ops->set_attention_status) || (slot->ops->get_attention_status)) return true; return false; } -static bool has_latch_file(struct pci_slot *pci_slot) +static bool has_latch_file(struct hotplug_slot *slot) { - struct hotplug_slot *slot = pci_slot->hotplug; - - if ((!slot) || (!slot->ops)) - return false; if (slot->ops->get_latch_status) return true; return false; } -static bool has_adapter_file(struct pci_slot *pci_slot) +static bool has_adapter_file(struct hotplug_slot *slot) { - struct hotplug_slot *slot = pci_slot->hotplug; - - if ((!slot) || (!slot->ops)) - return false; if (slot->ops->get_adapter_status) return true; return false; } -static bool has_test_file(struct pci_slot *pci_slot) +static bool has_test_file(struct hotplug_slot *slot) { - struct hotplug_slot *slot = pci_slot->hotplug; - - if ((!slot) || (!slot->ops)) - return false; if (slot->ops->hardware_test) return true; return false; } -static int fs_add_slot(struct pci_slot *pci_slot) +static int fs_add_slot(struct hotplug_slot *slot, struct pci_slot *pci_slot) { + struct kobject *kobj; int retval = 0; /* Create symbolic link to the hotplug driver module */ - pci_hp_create_module_link(pci_slot); + kobj = kset_find_obj(module_kset, slot->mod_name); + if (kobj) { + retval = sysfs_create_link(&pci_slot->kobj, kobj, "module"); + if (retval) + dev_err(&pci_slot->bus->dev, + "Error creating sysfs link (%d)\n", retval); + kobject_put(kobj); + } - if (has_power_file(pci_slot)) { + if (has_power_file(slot)) { retval = sysfs_create_file(&pci_slot->kobj, &hotplug_slot_attr_power.attr); if (retval) goto exit_power; } - if (has_attention_file(pci_slot)) { + if (has_attention_file(slot)) { retval = sysfs_create_file(&pci_slot->kobj, &hotplug_slot_attr_attention.attr); if (retval) goto exit_attention; } - if (has_latch_file(pci_slot)) { + if (has_latch_file(slot)) { retval = sysfs_create_file(&pci_slot->kobj, &hotplug_slot_attr_latch.attr); if (retval) goto exit_latch; } - if (has_adapter_file(pci_slot)) { + if (has_adapter_file(slot)) { retval = sysfs_create_file(&pci_slot->kobj, &hotplug_slot_attr_presence.attr); if (retval) goto exit_adapter; } - if (has_test_file(pci_slot)) { + if (has_test_file(slot)) { retval = sysfs_create_file(&pci_slot->kobj, &hotplug_slot_attr_test.attr); if (retval) @@ -334,62 +292,51 @@ static int fs_add_slot(struct pci_slot *pci_slot) goto exit; exit_test: - if (has_adapter_file(pci_slot)) + if (has_adapter_file(slot)) sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_presence.attr); exit_adapter: - if (has_latch_file(pci_slot)) + if (has_latch_file(slot)) sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_latch.attr); exit_latch: - if (has_attention_file(pci_slot)) + if (has_attention_file(slot)) sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_attention.attr); exit_attention: - if (has_power_file(pci_slot)) + if (has_power_file(slot)) sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_power.attr); exit_power: - pci_hp_remove_module_link(pci_slot); + sysfs_remove_link(&pci_slot->kobj, "module"); exit: return retval; } -static void fs_remove_slot(struct pci_slot *pci_slot) +static void fs_remove_slot(struct hotplug_slot *slot, struct pci_slot *pci_slot) { - if (has_power_file(pci_slot)) + if (has_power_file(slot)) sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_power.attr); - if (has_attention_file(pci_slot)) + if (has_attention_file(slot)) sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_attention.attr); - if (has_latch_file(pci_slot)) + if (has_latch_file(slot)) sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_latch.attr); - if (has_adapter_file(pci_slot)) + if (has_adapter_file(slot)) sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_presence.attr); - if (has_test_file(pci_slot)) + if (has_test_file(slot)) sysfs_remove_file(&pci_slot->kobj, &hotplug_slot_attr_test.attr); - pci_hp_remove_module_link(pci_slot); -} - -static struct hotplug_slot *get_slot_from_name(const char *name) -{ - struct hotplug_slot *slot; - - list_for_each_entry(slot, &pci_hotplug_slot_list, slot_list) { - if (strcmp(hotplug_slot_name(slot), name) == 0) - return slot; - } - return NULL; + sysfs_remove_link(&pci_slot->kobj, "module"); } /** * __pci_hp_register - register a hotplug_slot with the PCI hotplug subsystem - * @bus: bus this slot is on * @slot: pointer to the &struct hotplug_slot to register + * @bus: bus this slot is on * @devnr: device number * @name: name registered with kobject core * @owner: caller module owner @@ -476,18 +423,19 @@ EXPORT_SYMBOL_GPL(__pci_hp_initialize); */ int pci_hp_add(struct hotplug_slot *slot) { - struct pci_slot *pci_slot = slot->pci_slot; + struct pci_slot *pci_slot; int result; - result = fs_add_slot(pci_slot); + if (WARN_ON(!slot)) + return -EINVAL; + + pci_slot = slot->pci_slot; + + result = fs_add_slot(slot, pci_slot); if (result) return result; kobject_uevent(&pci_slot->kobj, KOBJ_ADD); - mutex_lock(&pci_hp_mutex); - list_add(&slot->slot_list, &pci_hotplug_slot_list); - mutex_unlock(&pci_hp_mutex); - dbg("Added slot %s to the list\n", hotplug_slot_name(slot)); return 0; } EXPORT_SYMBOL_GPL(pci_hp_add); @@ -498,8 +446,6 @@ EXPORT_SYMBOL_GPL(pci_hp_add); * * The @slot must have been registered with the pci hotplug subsystem * previously with a call to pci_hp_register(). - * - * Returns 0 if successful, anything else for an error. */ void pci_hp_deregister(struct hotplug_slot *slot) { @@ -513,27 +459,13 @@ EXPORT_SYMBOL_GPL(pci_hp_deregister); * @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; - if (WARN_ON(!slot)) return; - mutex_lock(&pci_hp_mutex); - temp = get_slot_from_name(hotplug_slot_name(slot)); - if (WARN_ON(temp != slot)) { - mutex_unlock(&pci_hp_mutex); - return; - } - - list_del(&slot->slot_list); - mutex_unlock(&pci_hp_mutex); - dbg("Removed slot %s from the list\n", hotplug_slot_name(slot)); - fs_remove_slot(slot->pci_slot); + fs_remove_slot(slot, slot->pci_slot); } EXPORT_SYMBOL_GPL(pci_hp_del); @@ -545,8 +477,6 @@ EXPORT_SYMBOL_GPL(pci_hp_del); * 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) { @@ -558,6 +488,75 @@ void pci_hp_destroy(struct hotplug_slot *slot) } EXPORT_SYMBOL_GPL(pci_hp_destroy); +static DECLARE_WAIT_QUEUE_HEAD(pci_hp_link_change_wq); + +/** + * pci_hp_ignore_link_change - begin code section causing spurious link changes + * @pdev: PCI hotplug bridge + * + * Mark the beginning of a code section causing spurious link changes on the + * Secondary Bus of @pdev, e.g. as a side effect of a Secondary Bus Reset, + * D3cold transition, firmware update or FPGA reconfiguration. + * + * Hotplug drivers can thus check whether such a code section is executing + * concurrently, await it with pci_hp_spurious_link_change() and ignore the + * resulting link change events. + * + * Must be paired with pci_hp_unignore_link_change(). May be called both + * from the PCI core and from Endpoint drivers. May be called for bridges + * which are not hotplug-capable, in which case it has no effect because + * no hotplug driver is bound to the bridge. + */ +void pci_hp_ignore_link_change(struct pci_dev *pdev) +{ + set_bit(PCI_LINK_CHANGING, &pdev->priv_flags); + smp_mb__after_atomic(); /* pairs with implied barrier of wait_event() */ +} + +/** + * pci_hp_unignore_link_change - end code section causing spurious link changes + * @pdev: PCI hotplug bridge + * + * Mark the end of a code section causing spurious link changes on the + * Secondary Bus of @pdev. Must be paired with pci_hp_ignore_link_change(). + */ +void pci_hp_unignore_link_change(struct pci_dev *pdev) +{ + set_bit(PCI_LINK_CHANGED, &pdev->priv_flags); + mb(); /* ensure pci_hp_spurious_link_change() sees either bit set */ + clear_bit(PCI_LINK_CHANGING, &pdev->priv_flags); + wake_up_all(&pci_hp_link_change_wq); +} + +/** + * pci_hp_spurious_link_change - check for spurious link changes + * @pdev: PCI hotplug bridge + * + * Check whether a code section is executing concurrently which is causing + * spurious link changes on the Secondary Bus of @pdev. Await the end of the + * code section if so. + * + * May be called by hotplug drivers to check whether a link change is spurious + * and can be ignored. + * + * Because a genuine link change may have occurred in-between a spurious link + * change and the invocation of this function, hotplug drivers should perform + * sanity checks such as retrieving the current link state and bringing down + * the slot if the link is down. + * + * Return: %true if such a code section has been executing concurrently, + * otherwise %false. Also return %true if such a code section has not been + * executing concurrently, but at least once since the last invocation of this + * function. + */ +bool pci_hp_spurious_link_change(struct pci_dev *pdev) +{ + wait_event(pci_hp_link_change_wq, + !test_bit(PCI_LINK_CHANGING, &pdev->priv_flags)); + + return test_and_clear_bit(PCI_LINK_CHANGED, &pdev->priv_flags); +} + static int __init pci_hotplug_init(void) { int result; diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h index e0a614acee05..debc79b0adfb 100644 --- a/drivers/pci/hotplug/pciehp.h +++ b/drivers/pci/hotplug/pciehp.h @@ -46,6 +46,9 @@ extern int pciehp_poll_time; /** * struct controller - PCIe hotplug controller * @pcie: pointer to the controller's PCIe port service device + * @dsn: cached copy of Device Serial Number of Function 0 in the hotplug slot + * (PCIe r6.2 sec 7.9.3); used to determine whether a hotplugged device + * was replaced with a different one during system sleep * @slot_cap: cached copy of the Slot Capabilities register * @inband_presence_disabled: In-Band Presence Detect Disable supported by * controller and disabled per spec recommendation (PCIe r5.0, appendix I @@ -87,6 +90,7 @@ extern int pciehp_poll_time; */ struct controller { struct pcie_device *pcie; + u64 dsn; u32 slot_cap; /* capabilities and quirks */ unsigned int inband_presence_disabled:1; @@ -183,6 +187,7 @@ int pciehp_card_present(struct controller *ctrl); int pciehp_card_present_or_link_active(struct controller *ctrl); int pciehp_check_link_status(struct controller *ctrl); int pciehp_check_link_active(struct controller *ctrl); +bool pciehp_device_replaced(struct controller *ctrl); void pciehp_release_ctrl(struct controller *ctrl); int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot); diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c index 4042d87d539d..f59baa912970 100644 --- a/drivers/pci/hotplug/pciehp_core.c +++ b/drivers/pci/hotplug/pciehp_core.c @@ -20,6 +20,7 @@ #define pr_fmt(fmt) "pciehp: " fmt #define dev_fmt pr_fmt +#include <linux/bitfield.h> #include <linux/moduleparam.h> #include <linux/kernel.h> #include <linux/slab.h> @@ -103,7 +104,7 @@ static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) struct pci_dev *pdev = ctrl->pcie->port; if (status) - status <<= PCI_EXP_SLTCTL_ATTN_IND_SHIFT; + status = FIELD_PREP(PCI_EXP_SLTCTL_AIC, status); else status = PCI_EXP_SLTCTL_ATTN_IND_OFF; @@ -292,9 +293,23 @@ static int pciehp_resume_noirq(struct pcie_device *dev) ctrl->cmd_busy = true; /* clear spurious events from rediscovery of inserted card */ - if (ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE) + if (ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE) { pcie_clear_hotplug_events(ctrl); + /* + * If hotplugged device was replaced with a different one + * during system sleep, mark the old device disconnected + * (to prevent its driver from accessing the new device) + * and synthesize a Presence Detect Changed event. + */ + if (pciehp_device_replaced(ctrl)) { + ctrl_dbg(ctrl, "device replaced during system sleep\n"); + pci_walk_bus(ctrl->pcie->port->subordinate, + pci_dev_set_disconnected, NULL); + pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); + } + } + return 0; } #endif diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index 529c34808440..bcc938d4420f 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -19,6 +19,8 @@ #include <linux/types.h> #include <linux/pm_runtime.h> #include <linux/pci.h> + +#include "../pci.h" #include "pciehp.h" /* The following routines constitute the bulk of the @@ -127,6 +129,9 @@ static void remove_board(struct controller *ctrl, bool safe_removal) pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, INDICATOR_NOOP); + + /* Don't carry LBMS indications across */ + pcie_reset_lbms(ctrl->pcie->port); } static int pciehp_enable_slot(struct controller *ctrl); @@ -166,11 +171,11 @@ void pciehp_handle_button_press(struct controller *ctrl) case ON_STATE: if (ctrl->state == ON_STATE) { ctrl->state = BLINKINGOFF_STATE; - ctrl_info(ctrl, "Slot(%s): Powering off due to button press\n", + ctrl_info(ctrl, "Slot(%s): Button press: will power off in 5 sec\n", slot_name(ctrl)); } else { ctrl->state = BLINKINGON_STATE; - ctrl_info(ctrl, "Slot(%s) Powering on due to button press\n", + ctrl_info(ctrl, "Slot(%s): Button press: will power on in 5 sec\n", slot_name(ctrl)); } /* blink power indicator and turn off attention */ @@ -185,22 +190,23 @@ void pciehp_handle_button_press(struct controller *ctrl) * press the attention again before the 5 sec. limit * expires to cancel hot-add or hot-remove */ - ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(ctrl)); cancel_delayed_work(&ctrl->button_work); if (ctrl->state == BLINKINGOFF_STATE) { ctrl->state = ON_STATE; pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_ON, PCI_EXP_SLTCTL_ATTN_IND_OFF); + ctrl_info(ctrl, "Slot(%s): Button press: canceling request to power off\n", + slot_name(ctrl)); } else { ctrl->state = OFF_STATE; pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, PCI_EXP_SLTCTL_ATTN_IND_OFF); + ctrl_info(ctrl, "Slot(%s): Button press: canceling request to power on\n", + slot_name(ctrl)); } - ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n", - slot_name(ctrl)); break; default: - ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", + ctrl_err(ctrl, "Slot(%s): Button press: ignoring invalid state %#x\n", slot_name(ctrl), ctrl->state); break; } @@ -256,6 +262,14 @@ void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events) present = pciehp_card_present(ctrl); link_active = pciehp_check_link_active(ctrl); if (present <= 0 && link_active <= 0) { + if (ctrl->state == BLINKINGON_STATE) { + ctrl->state = OFF_STATE; + cancel_delayed_work(&ctrl->button_work); + pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + INDICATOR_NOOP); + ctrl_info(ctrl, "Slot(%s): Card not present\n", + slot_name(ctrl)); + } mutex_unlock(&ctrl->state_lock); return; } diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index 10e9670eea0b..bcc51b26d03d 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -14,6 +14,7 @@ #define dev_fmt(fmt) "pciehp: " fmt +#include <linux/bitfield.h> #include <linux/dmi.h> #include <linux/kernel.h> #include <linux/types.h> @@ -291,7 +292,7 @@ int pciehp_check_link_status(struct controller *ctrl) { struct pci_dev *pdev = ctrl_dev(ctrl); bool found; - u16 lnk_status; + u16 lnk_status, linksta2; if (!pcie_wait_for_link(pdev, true)) { ctrl_info(ctrl, "Slot(%s): No link\n", slot_name(ctrl)); @@ -318,7 +319,8 @@ int pciehp_check_link_status(struct controller *ctrl) return -1; } - pcie_update_link_speed(ctrl->pcie->port->subordinate, lnk_status); + pcie_capability_read_word(pdev, PCI_EXP_LNKSTA2, &linksta2); + __pcie_update_link_speed(ctrl->pcie->port->subordinate, lnk_status, linksta2); if (!found) { ctrl_info(ctrl, "Slot(%s): No device found\n", @@ -332,17 +334,11 @@ int pciehp_check_link_status(struct controller *ctrl) static int __pciehp_link_set(struct controller *ctrl, bool enable) { struct pci_dev *pdev = ctrl_dev(ctrl); - u16 lnk_ctrl; - - pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &lnk_ctrl); - if (enable) - lnk_ctrl &= ~PCI_EXP_LNKCTL_LD; - else - lnk_ctrl |= PCI_EXP_LNKCTL_LD; + pcie_capability_clear_and_set_word(pdev, PCI_EXP_LNKCTL, + PCI_EXP_LNKCTL_LD, + enable ? 0 : PCI_EXP_LNKCTL_LD); - pcie_capability_write_word(pdev, PCI_EXP_LNKCTL, lnk_ctrl); - ctrl_dbg(ctrl, "%s: lnk_ctrl = %x\n", __func__, lnk_ctrl); return 0; } @@ -435,7 +431,7 @@ void pciehp_get_latch_status(struct controller *ctrl, u8 *status) * removed immediately after the check so the caller may need to take * this into account. * - * It the hotplug controller itself is not available anymore returns + * If the hotplug controller itself is not available anymore returns * %-ENODEV. */ int pciehp_card_present(struct controller *ctrl) @@ -490,7 +486,9 @@ int pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot, struct pci_dev *pdev = ctrl_dev(ctrl); pci_config_pm_runtime_get(pdev); - pcie_write_cmd_nowait(ctrl, status << 6, + + /* Attention and Power Indicator Control bits are supported */ + pcie_write_cmd_nowait(ctrl, FIELD_PREP(PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC, status), PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC); pci_config_pm_runtime_put(pdev); return 0; @@ -565,20 +563,50 @@ void pciehp_power_off_slot(struct controller *ctrl) PCI_EXP_SLTCTL_PWR_OFF); } -static void pciehp_ignore_dpc_link_change(struct controller *ctrl, - struct pci_dev *pdev, int irq) +bool pciehp_device_replaced(struct controller *ctrl) +{ + struct pci_dev *pdev __free(pci_dev_put) = NULL; + u32 reg; + + if (pci_dev_is_disconnected(ctrl->pcie->port)) + return false; + + pdev = pci_get_slot(ctrl->pcie->port->subordinate, PCI_DEVFN(0, 0)); + if (!pdev) + return true; + + if (pci_read_config_dword(pdev, PCI_VENDOR_ID, ®) || + reg != (pdev->vendor | (pdev->device << 16)) || + pci_read_config_dword(pdev, PCI_CLASS_REVISION, ®) || + reg != (pdev->revision | (pdev->class << 8))) + return true; + + if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL && + (pci_read_config_dword(pdev, PCI_SUBSYSTEM_VENDOR_ID, ®) || + reg != (pdev->subsystem_vendor | (pdev->subsystem_device << 16)))) + return true; + + if (pci_get_dsn(pdev) != ctrl->dsn) + return true; + + return false; +} + +static void pciehp_ignore_link_change(struct controller *ctrl, + struct pci_dev *pdev, int irq, + u16 ignored_events) { /* * Ignore link changes which occurred while waiting for DPC recovery. * Could be several if DPC triggered multiple times consecutively. + * Also ignore link changes caused by Secondary Bus Reset, etc. */ synchronize_hardirq(irq); - atomic_and(~PCI_EXP_SLTSTA_DLLSC, &ctrl->pending_events); + atomic_and(~ignored_events, &ctrl->pending_events); if (pciehp_poll_mode) pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, - PCI_EXP_SLTSTA_DLLSC); - ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored (recovered by DPC)\n", - slot_name(ctrl)); + ignored_events); + ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored\n", slot_name(ctrl)); /* * If the link is unexpectedly down after successful recovery, @@ -586,8 +614,8 @@ static void pciehp_ignore_dpc_link_change(struct controller *ctrl, * Synthesize it to ensure that it is acted on. */ down_read_nested(&ctrl->reset_lock, ctrl->depth); - if (!pciehp_check_link_active(ctrl)) - pciehp_request(ctrl, PCI_EXP_SLTSTA_DLLSC); + if (!pciehp_check_link_active(ctrl) || pciehp_device_replaced(ctrl)) + pciehp_request(ctrl, ignored_events); up_read(&ctrl->reset_lock); } @@ -722,11 +750,8 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) } /* Check Attention Button Pressed */ - if (events & PCI_EXP_SLTSTA_ABP) { - ctrl_info(ctrl, "Slot(%s): Attention button pressed\n", - slot_name(ctrl)); + if (events & PCI_EXP_SLTSTA_ABP) pciehp_handle_button_press(ctrl); - } /* Check Power Fault Detected */ if (events & PCI_EXP_SLTSTA_PFD) { @@ -737,12 +762,19 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) /* * Ignore Link Down/Up events caused by Downstream Port Containment - * if recovery from the error succeeded. + * if recovery succeeded, or caused by Secondary Bus Reset, + * suspend to D3cold, firmware update, FPGA reconfiguration, etc. */ - if ((events & PCI_EXP_SLTSTA_DLLSC) && pci_dpc_recovered(pdev) && + if ((events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) && + (pci_dpc_recovered(pdev) || pci_hp_spurious_link_change(pdev)) && ctrl->state == ON_STATE) { - events &= ~PCI_EXP_SLTSTA_DLLSC; - pciehp_ignore_dpc_link_change(ctrl, pdev, irq); + u16 ignored_events = PCI_EXP_SLTSTA_DLLSC; + + if (!ctrl->inband_presence_disabled) + ignored_events |= PCI_EXP_SLTSTA_PDC; + + events &= ~ignored_events; + pciehp_ignore_link_change(ctrl, pdev, irq, ignored_events); } /* @@ -848,7 +880,9 @@ void pcie_enable_interrupt(struct controller *ctrl) { u16 mask; - mask = PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_DLLSCE; + mask = PCI_EXP_SLTCTL_DLLSCE; + if (!pciehp_poll_mode) + mask |= PCI_EXP_SLTCTL_HPIE; pcie_write_cmd(ctrl, mask, mask); } @@ -905,7 +939,6 @@ int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, bool probe) { struct controller *ctrl = to_ctrl(hotplug_slot); struct pci_dev *pdev = ctrl_dev(ctrl); - u16 stat_mask = 0, ctrl_mask = 0; int rc; if (probe) @@ -913,23 +946,11 @@ int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, bool probe) down_write_nested(&ctrl->reset_lock, ctrl->depth); - if (!ATTN_BUTTN(ctrl)) { - ctrl_mask |= PCI_EXP_SLTCTL_PDCE; - stat_mask |= PCI_EXP_SLTSTA_PDC; - } - ctrl_mask |= PCI_EXP_SLTCTL_DLLSCE; - stat_mask |= PCI_EXP_SLTSTA_DLLSC; - - 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); + pci_hp_ignore_link_change(pdev); rc = pci_bridge_secondary_bus_reset(ctrl->pcie->port); - pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, stat_mask); - pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask); - ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, - pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask); + pci_hp_unignore_link_change(pdev); up_write(&ctrl->reset_lock); return rc; @@ -974,7 +995,7 @@ static inline int pcie_hotplug_depth(struct pci_dev *dev) while (bus->parent) { bus = bus->parent; - if (bus->self && bus->self->is_hotplug_bridge) + if (bus->self && bus->self->is_pciehp) depth++; } @@ -984,7 +1005,7 @@ static inline int pcie_hotplug_depth(struct pci_dev *dev) struct controller *pcie_init(struct pcie_device *dev) { struct controller *ctrl; - u32 slot_cap, slot_cap2, link_cap; + u32 slot_cap, slot_cap2; u8 poweron; struct pci_dev *pdev = dev->port; struct pci_bus *subordinate = pdev->subordinate; @@ -1030,9 +1051,6 @@ struct controller *pcie_init(struct pcie_device *dev) if (dmi_first_match(inband_presence_disabled_dmi_table)) ctrl->inband_presence_disabled = 1; - /* Check if Data Link Layer Link Active Reporting is implemented */ - pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &link_cap); - /* 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 | @@ -1040,7 +1058,7 @@ struct controller *pcie_init(struct pcie_device *dev) 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 IbPresDis%c LLActRep%c%s\n", - (slot_cap & PCI_EXP_SLTCAP_PSN) >> 19, + FIELD_GET(PCI_EXP_SLTCAP_PSN, slot_cap), FLAG(slot_cap, PCI_EXP_SLTCAP_ABP), FLAG(slot_cap, PCI_EXP_SLTCAP_PCP), FLAG(slot_cap, PCI_EXP_SLTCAP_MRLSP), @@ -1051,7 +1069,7 @@ struct controller *pcie_init(struct pcie_device *dev) FLAG(slot_cap, PCI_EXP_SLTCAP_EIP), FLAG(slot_cap, PCI_EXP_SLTCAP_NCCS), FLAG(slot_cap2, PCI_EXP_SLTCAP2_IBPD), - FLAG(link_cap, PCI_EXP_LNKCAP_DLLLARC), + FLAG(pdev->link_active_reporting, true), pdev->broken_cmd_compl ? " (with Cmd Compl erratum)" : ""); /* @@ -1066,6 +1084,11 @@ struct controller *pcie_init(struct pcie_device *dev) } } + pdev = pci_get_slot(subordinate, PCI_DEVFN(0, 0)); + if (pdev) + ctrl->dsn = pci_get_dsn(pdev); + pci_dev_put(pdev); + return ctrl; } @@ -1088,6 +1111,8 @@ static void quirk_cmd_compl(struct pci_dev *pdev) } DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_QCOM, 0x010e, + PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_QCOM, 0x0110, PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_QCOM, 0x0400, diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c index d17f3bf36f70..65e50bee1a8c 100644 --- a/drivers/pci/hotplug/pciehp_pci.c +++ b/drivers/pci/hotplug/pciehp_pci.c @@ -63,7 +63,18 @@ int pciehp_configure_device(struct controller *ctrl) pci_assign_unassigned_bridge_resources(bridge); pcie_bus_configure_settings(parent); + + /* + * Release reset_lock during driver binding + * to avoid AB-BA deadlock with device_lock. + */ + up_read(&ctrl->reset_lock); pci_bus_add_devices(parent); + down_read_nested(&ctrl->reset_lock, ctrl->depth); + + dev = pci_get_slot(parent, PCI_DEVFN(0, 0)); + ctrl->dsn = pci_get_dsn(dev); + pci_dev_put(dev); out: pci_unlock_rescan_remove(); @@ -104,7 +115,15 @@ void pciehp_unconfigure_device(struct controller *ctrl, bool presence) list_for_each_entry_safe_reverse(dev, temp, &parent->devices, bus_list) { pci_dev_get(dev); + + /* + * Release reset_lock during driver unbinding + * to avoid AB-BA deadlock with device_lock. + */ + up_read(&ctrl->reset_lock); pci_stop_and_remove_bus_device(dev); + down_read_nested(&ctrl->reset_lock, ctrl->depth); + /* * Ensure that no new Requests will be generated from * the device. diff --git a/drivers/pci/hotplug/pnv_php.c b/drivers/pci/hotplug/pnv_php.c index 881d420637bf..c5345bff9a55 100644 --- a/drivers/pci/hotplug/pnv_php.c +++ b/drivers/pci/hotplug/pnv_php.c @@ -3,11 +3,15 @@ * PCI Hotplug Driver for PowerPC PowerNV platform. * * Copyright Gavin Shan, IBM Corporation 2016. + * Copyright (C) 2025 Raptor Engineering, LLC + * Copyright (C) 2025 Raptor Computing Systems, LLC */ +#include <linux/bitfield.h> #include <linux/libfdt.h> #include <linux/module.h> #include <linux/pci.h> +#include <linux/delay.h> #include <linux/pci_hotplug.h> #include <linux/of_fdt.h> @@ -35,11 +39,12 @@ static void pnv_php_register(struct device_node *dn); static void pnv_php_unregister_one(struct device_node *dn); static void pnv_php_unregister(struct device_node *dn); +static void pnv_php_enable_irq(struct pnv_php_slot *php_slot); + static void pnv_php_disable_irq(struct pnv_php_slot *php_slot, - bool disable_device) + bool disable_device, bool disable_msi) { struct pci_dev *pdev = php_slot->pdev; - int irq = php_slot->irq; u16 ctrl; if (php_slot->irq > 0) { @@ -53,19 +58,15 @@ static void pnv_php_disable_irq(struct pnv_php_slot *php_slot, php_slot->irq = 0; } - if (php_slot->wq) { - destroy_workqueue(php_slot->wq); - php_slot->wq = NULL; - } - - if (disable_device || irq > 0) { + if (disable_device || disable_msi) { if (pdev->msix_enabled) pci_disable_msix(pdev); else if (pdev->msi_enabled) pci_disable_msi(pdev); + } + if (disable_device) pci_disable_device(pdev); - } } static void pnv_php_free_slot(struct kref *kref) @@ -74,7 +75,8 @@ static void pnv_php_free_slot(struct kref *kref) struct pnv_php_slot, kref); WARN_ON(!list_empty(&php_slot->children)); - pnv_php_disable_irq(php_slot, false); + pnv_php_disable_irq(php_slot, false, false); + destroy_workqueue(php_slot->wq); kfree(php_slot->name); kfree(php_slot); } @@ -391,6 +393,20 @@ static int pnv_php_get_power_state(struct hotplug_slot *slot, u8 *state) return 0; } +static int pcie_check_link_active(struct pci_dev *pdev) +{ + u16 lnk_status; + int ret; + + ret = pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status); + if (ret == PCIBIOS_DEVICE_NOT_FOUND || PCI_POSSIBLE_ERROR(lnk_status)) + return -ENODEV; + + ret = !!(lnk_status & PCI_EXP_LNKSTA_DLLLA); + + return ret; +} + static int pnv_php_get_adapter_state(struct hotplug_slot *slot, u8 *state) { struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); @@ -403,6 +419,19 @@ static int pnv_php_get_adapter_state(struct hotplug_slot *slot, u8 *state) */ ret = pnv_pci_get_presence_state(php_slot->id, &presence); if (ret >= 0) { + if (pci_pcie_type(php_slot->pdev) == PCI_EXP_TYPE_DOWNSTREAM && + presence == OPAL_PCI_SLOT_EMPTY) { + /* + * Similar to pciehp_hpc, check whether the Link Active + * bit is set to account for broken downstream bridges + * that don't properly assert Presence Detect State, as + * was observed on the Microsemi Switchtec PM8533 PFX + * [11f8:8533]. + */ + if (pcie_check_link_active(php_slot->pdev) > 0) + presence = OPAL_PCI_SLOT_PRESENT; + } + *state = presence; ret = 0; } else { @@ -412,10 +441,23 @@ static int pnv_php_get_adapter_state(struct hotplug_slot *slot, u8 *state) return ret; } +static int pnv_php_get_raw_indicator_status(struct hotplug_slot *slot, u8 *state) +{ + struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + struct pci_dev *bridge = php_slot->pdev; + u16 status; + + pcie_capability_read_word(bridge, PCI_EXP_SLTCTL, &status); + *state = (status & (PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC)) >> 6; + return 0; +} + + static int pnv_php_get_attention_state(struct hotplug_slot *slot, u8 *state) { struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + pnv_php_get_raw_indicator_status(slot, &php_slot->attention_state); *state = php_slot->attention_state; return 0; } @@ -433,7 +475,7 @@ static int pnv_php_set_attention_state(struct hotplug_slot *slot, u8 state) mask = PCI_EXP_SLTCTL_AIC; if (state) - new = PCI_EXP_SLTCTL_ATTN_IND_ON; + new = FIELD_PREP(PCI_EXP_SLTCTL_AIC, state); else new = PCI_EXP_SLTCTL_ATTN_IND_OFF; @@ -442,6 +484,61 @@ static int pnv_php_set_attention_state(struct hotplug_slot *slot, u8 state) return 0; } +static int pnv_php_activate_slot(struct pnv_php_slot *php_slot, + struct hotplug_slot *slot) +{ + int ret, i; + + /* + * Issue initial slot activation command to firmware + * + * Firmware will power slot on, attempt to train the link, and + * discover any downstream devices. If this process fails, firmware + * will return an error code and an invalid device tree. Failure + * can be caused for multiple reasons, including a faulty + * downstream device, poor connection to the downstream device, or + * a previously latched PHB fence. On failure, issue fundamental + * reset up to three times before aborting. + */ + ret = pnv_php_set_slot_power_state(slot, OPAL_PCI_SLOT_POWER_ON); + if (ret) { + SLOT_WARN( + php_slot, + "PCI slot activation failed with error code %d, possible frozen PHB", + ret); + SLOT_WARN( + php_slot, + "Attempting complete PHB reset before retrying slot activation\n"); + for (i = 0; i < 3; i++) { + /* + * Slot activation failed, PHB may be fenced from a + * prior device failure. + * + * Use the OPAL fundamental reset call to both try a + * device reset and clear any potentially active PHB + * fence / freeze. + */ + SLOT_WARN(php_slot, "Try %d...\n", i + 1); + pci_set_pcie_reset_state(php_slot->pdev, + pcie_warm_reset); + msleep(250); + pci_set_pcie_reset_state(php_slot->pdev, + pcie_deassert_reset); + + ret = pnv_php_set_slot_power_state( + slot, OPAL_PCI_SLOT_POWER_ON); + if (!ret) + break; + } + + if (i >= 3) + SLOT_WARN(php_slot, + "Failed to bring slot online, aborting!\n"); + } + + return ret; +} + static int pnv_php_enable(struct pnv_php_slot *php_slot, bool rescan) { struct hotplug_slot *slot = &php_slot->slot; @@ -504,7 +601,7 @@ static int pnv_php_enable(struct pnv_php_slot *php_slot, bool rescan) goto scan; /* Power is off, turn it on and then scan the slot */ - ret = pnv_php_set_slot_power_state(slot, OPAL_PCI_SLOT_POWER_ON); + ret = pnv_php_activate_slot(php_slot, slot); if (ret) return ret; @@ -561,8 +658,58 @@ static int pnv_php_reset_slot(struct hotplug_slot *slot, bool probe) static int pnv_php_enable_slot(struct hotplug_slot *slot) { struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); + u32 prop32; + int ret; + + ret = pnv_php_enable(php_slot, true); + if (ret) + return ret; + + /* (Re-)enable interrupt if the slot supports surprise hotplug */ + ret = of_property_read_u32(php_slot->dn, "ibm,slot-surprise-pluggable", + &prop32); + if (!ret && prop32) + pnv_php_enable_irq(php_slot); + + return 0; +} + +/* + * Disable any hotplug interrupts for all slots on the provided bus, as well as + * all downstream slots in preparation for a hot unplug. + */ +static int pnv_php_disable_all_irqs(struct pci_bus *bus) +{ + struct pci_bus *child_bus; + struct pci_slot *slot; + + /* First go down child buses */ + list_for_each_entry(child_bus, &bus->children, node) + pnv_php_disable_all_irqs(child_bus); + + /* Disable IRQs for all pnv_php slots on this bus */ + list_for_each_entry(slot, &bus->slots, list) { + struct pnv_php_slot *php_slot = to_pnv_php_slot(slot->hotplug); - return pnv_php_enable(php_slot, true); + pnv_php_disable_irq(php_slot, false, true); + } + + return 0; +} + +/* + * Disable any hotplug interrupts for all downstream slots on the provided + * bus in preparation for a hot unplug. + */ +static int pnv_php_disable_all_downstream_irqs(struct pci_bus *bus) +{ + struct pci_bus *child_bus; + + /* Go down child buses, recursively deactivating their IRQs */ + list_for_each_entry(child_bus, &bus->children, node) + pnv_php_disable_all_irqs(child_bus); + + return 0; } static int pnv_php_disable_slot(struct hotplug_slot *slot) @@ -579,6 +726,13 @@ static int pnv_php_disable_slot(struct hotplug_slot *slot) php_slot->state != PNV_PHP_STATE_REGISTERED) return 0; + /* + * Free all IRQ resources from all child slots before remove. + * Note that we do not disable the root slot IRQ here as that + * would also deactivate the slot hot (re)plug interrupt! + */ + pnv_php_disable_all_downstream_irqs(php_slot->bus); + /* Remove all devices behind the slot */ pci_lock_rescan_remove(); pci_hp_remove_devices(php_slot->bus); @@ -647,6 +801,15 @@ static struct pnv_php_slot *pnv_php_alloc_slot(struct device_node *dn) return NULL; } + /* Allocate workqueue for this slot's interrupt handling */ + php_slot->wq = alloc_workqueue("pciehp-%s", 0, 0, php_slot->name); + if (!php_slot->wq) { + SLOT_WARN(php_slot, "Cannot alloc workqueue\n"); + kfree(php_slot->name); + kfree(php_slot); + return NULL; + } + if (dn->child && PCI_DN(dn->child)) php_slot->slot_no = PCI_SLOT(PCI_DN(dn->child)->devfn); else @@ -731,7 +894,7 @@ static int pnv_php_enable_msix(struct pnv_php_slot *php_slot) /* Check hotplug MSIx entry is in range */ pcie_capability_read_word(pdev, PCI_EXP_FLAGS, &pcie_flag); - entry.entry = (pcie_flag & PCI_EXP_FLAGS_IRQ) >> 9; + entry.entry = FIELD_GET(PCI_EXP_FLAGS_IRQ, pcie_flag); if (entry.entry >= nr_entries) return -ERANGE; @@ -745,16 +908,63 @@ static int pnv_php_enable_msix(struct pnv_php_slot *php_slot) return entry.vector; } +static void +pnv_php_detect_clear_suprise_removal_freeze(struct pnv_php_slot *php_slot) +{ + struct pci_dev *pdev = php_slot->pdev; + struct eeh_dev *edev; + struct eeh_pe *pe; + int i, rc; + + /* + * When a device is surprise removed from a downstream bridge slot, + * the upstream bridge port can still end up frozen due to related EEH + * events, which will in turn block the MSI interrupts for slot hotplug + * detection. + * + * Detect and thaw any frozen upstream PE after slot deactivation. + */ + edev = pci_dev_to_eeh_dev(pdev); + pe = edev ? edev->pe : NULL; + rc = eeh_pe_get_state(pe); + if ((rc == -ENODEV) || (rc == -ENOENT)) { + SLOT_WARN( + php_slot, + "Upstream bridge PE state unknown, hotplug detect may fail\n"); + } else { + if (pe->state & EEH_PE_ISOLATED) { + SLOT_WARN( + php_slot, + "Upstream bridge PE %02x frozen, thawing...\n", + pe->addr); + for (i = 0; i < 3; i++) + if (!eeh_unfreeze_pe(pe)) + break; + if (i >= 3) + SLOT_WARN( + php_slot, + "Unable to thaw PE %02x, hotplug detect will fail!\n", + pe->addr); + else + SLOT_WARN(php_slot, + "PE %02x thawed successfully\n", + pe->addr); + } + } +} + static void pnv_php_event_handler(struct work_struct *work) { struct pnv_php_event *event = container_of(work, struct pnv_php_event, work); struct pnv_php_slot *php_slot = event->php_slot; - if (event->added) + if (event->added) { pnv_php_enable_slot(&php_slot->slot); - else + } else { pnv_php_disable_slot(&php_slot->slot); + pnv_php_detect_clear_suprise_removal_freeze(php_slot); + } kfree(event); } @@ -843,14 +1053,6 @@ static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq) u16 sts, ctrl; int ret; - /* Allocate workqueue */ - php_slot->wq = alloc_workqueue("pciehp-%s", 0, 0, php_slot->name); - if (!php_slot->wq) { - SLOT_WARN(php_slot, "Cannot alloc workqueue\n"); - pnv_php_disable_irq(php_slot, true); - return; - } - /* Check PDC (Presence Detection Change) is broken or not */ ret = of_property_read_u32(php_slot->dn, "ibm,slot-broken-pdc", &broken_pdc); @@ -869,7 +1071,7 @@ static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq) ret = request_irq(irq, pnv_php_interrupt, IRQF_SHARED, php_slot->name, php_slot); if (ret) { - pnv_php_disable_irq(php_slot, true); + pnv_php_disable_irq(php_slot, true, true); SLOT_WARN(php_slot, "Error %d enabling IRQ %d\n", ret, irq); return; } diff --git a/drivers/pci/hotplug/rpaphp_core.c b/drivers/pci/hotplug/rpaphp_core.c index 491986197c47..2316de0fd198 100644 --- a/drivers/pci/hotplug/rpaphp_core.c +++ b/drivers/pci/hotplug/rpaphp_core.c @@ -278,7 +278,7 @@ int rpaphp_check_drc_props(struct device_node *dn, char *drc_name, return -EINVAL; } - if (of_find_property(dn->parent, "ibm,drc-info", NULL)) + if (of_property_present(dn->parent, "ibm,drc-info")) return rpaphp_check_drc_props_v2(dn, drc_name, drc_type, be32_to_cpu(*my_index)); else @@ -440,7 +440,7 @@ int rpaphp_add_slot(struct device_node *dn) if (!of_node_name_eq(dn, "pci")) return 0; - if (of_find_property(dn, "ibm,drc-info", NULL)) + if (of_property_present(dn, "ibm,drc-info")) return rpaphp_drc_info_add_slot(dn); else return rpaphp_drc_add_slot(dn); diff --git a/drivers/pci/hotplug/rpaphp_pci.c b/drivers/pci/hotplug/rpaphp_pci.c index 630f77057c23..bcfd26ec6d30 100644 --- a/drivers/pci/hotplug/rpaphp_pci.c +++ b/drivers/pci/hotplug/rpaphp_pci.c @@ -19,12 +19,92 @@ #include "../pci.h" /* for pci_add_new_bus */ #include "rpaphp.h" +/* + * RTAS call get-sensor-state(DR_ENTITY_SENSE) return values as per PAPR: + * -- generic return codes --- + * -1: Hardware Error + * -2: RTAS_BUSY + * -3: Invalid sensor. RTAS Parameter Error. + * -- rtas_get_sensor function specific return codes --- + * -9000: Need DR entity to be powered up and unisolated before RTAS call + * -9001: Need DR entity to be powered up, but not unisolated, before RTAS call + * -9002: DR entity unusable + * 990x: Extended delay - where x is a number in the range of 0-5 + */ +#define RTAS_SLOT_UNISOLATED -9000 +#define RTAS_SLOT_NOT_UNISOLATED -9001 +#define RTAS_SLOT_NOT_USABLE -9002 + +static int rtas_get_sensor_errno(int rtas_rc) +{ + switch (rtas_rc) { + case 0: + /* Success case */ + return 0; + case RTAS_SLOT_UNISOLATED: + case RTAS_SLOT_NOT_UNISOLATED: + return -EFAULT; + case RTAS_SLOT_NOT_USABLE: + return -ENODEV; + case RTAS_BUSY: + case RTAS_EXTENDED_DELAY_MIN...RTAS_EXTENDED_DELAY_MAX: + return -EBUSY; + default: + return rtas_error_rc(rtas_rc); + } +} + +/* + * get_adapter_status() can be called by the EEH handler during EEH recovery. + * On certain PHB failures, the RTAS call rtas_call(get-sensor-state) returns + * extended busy error (9902) until PHB is recovered by pHyp. The RTAS call + * interface rtas_get_sensor() loops over the RTAS call on extended delay + * return code (9902) until the return value is either success (0) or error + * (-1). This causes the EEH handler to get stuck for ~6 seconds before it + * could notify that the PCI error has been detected and stop any active + * operations. This sometimes causes EEH recovery to fail. To avoid this issue, + * invoke rtas_call(get-sensor-state) directly if the respective PE is in EEH + * recovery state and return -EBUSY error based on RTAS return status. This + * will help the EEH handler to notify the driver about the PCI error + * immediately and successfully proceed with EEH recovery steps. + */ + +static int __rpaphp_get_sensor_state(struct slot *slot, int *state) +{ + int rc; + int token = rtas_token("get-sensor-state"); + struct pci_dn *pdn; + struct eeh_pe *pe; + struct pci_controller *phb = PCI_DN(slot->dn)->phb; + + if (token == RTAS_UNKNOWN_SERVICE) + return -ENOENT; + + /* + * Fallback to existing method for empty slot or PE isn't in EEH + * recovery. + */ + pdn = list_first_entry_or_null(&PCI_DN(phb->dn)->child_list, + struct pci_dn, list); + if (!pdn) + goto fallback; + + pe = eeh_dev_to_pe(pdn->edev); + if (pe && (pe->state & EEH_PE_RECOVERING)) { + rc = rtas_call(token, 2, 2, state, DR_ENTITY_SENSE, + slot->index); + return rtas_get_sensor_errno(rc); + } +fallback: + return rtas_get_sensor(DR_ENTITY_SENSE, slot->index, state); +} + int rpaphp_get_sensor_state(struct slot *slot, int *state) { int rc; int setlevel; - rc = rtas_get_sensor(DR_ENTITY_SENSE, slot->index, state); + rc = __rpaphp_get_sensor_state(slot, state); if (rc < 0) { if (rc == -EFAULT || rc == -EEXIST) { @@ -40,8 +120,7 @@ int rpaphp_get_sensor_state(struct slot *slot, int *state) dbg("%s: power on slot[%s] failed rc=%d.\n", __func__, slot->name, rc); } else { - rc = rtas_get_sensor(DR_ENTITY_SENSE, - slot->index, state); + rc = __rpaphp_get_sensor_state(slot, state); } } else if (rc == -ENODEV) info("%s: slot is unusable\n", __func__); diff --git a/drivers/pci/hotplug/s390_pci_hpc.c b/drivers/pci/hotplug/s390_pci_hpc.c index a89b7de72dcf..a55967082ef6 100644 --- a/drivers/pci/hotplug/s390_pci_hpc.c +++ b/drivers/pci/hotplug/s390_pci_hpc.c @@ -8,8 +8,7 @@ * Jan Glauber <jang@linux.vnet.ibm.com> */ -#define KMSG_COMPONENT "zpci" -#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt +#define pr_fmt(fmt) "zpci: " fmt #include <linux/kernel.h> #include <linux/slab.h> @@ -26,58 +25,78 @@ static int enable_slot(struct hotplug_slot *hotplug_slot) hotplug_slot); int rc; - if (zdev->state != ZPCI_FN_STATE_STANDBY) - return -EIO; + mutex_lock(&zdev->state_lock); + if (zdev->state != ZPCI_FN_STATE_STANDBY) { + rc = -EIO; + goto out; + } rc = sclp_pci_configure(zdev->fid); zpci_dbg(3, "conf fid:%x, rc:%d\n", zdev->fid, rc); if (rc) - return rc; + goto out; zdev->state = ZPCI_FN_STATE_CONFIGURED; - return zpci_scan_configured_device(zdev, zdev->fh); + rc = zpci_scan_configured_device(zdev, zdev->fh); +out: + mutex_unlock(&zdev->state_lock); + return rc; } static int disable_slot(struct hotplug_slot *hotplug_slot) { struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev, hotplug_slot); - struct pci_dev *pdev; + struct pci_dev *pdev = NULL; + int rc; - if (zdev->state != ZPCI_FN_STATE_CONFIGURED) - return -EIO; + mutex_lock(&zdev->state_lock); + if (zdev->state != ZPCI_FN_STATE_CONFIGURED) { + rc = -EIO; + goto out; + } pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn); if (pdev && pci_num_vf(pdev)) { - pci_dev_put(pdev); - return -EBUSY; + rc = -EBUSY; + goto out; } - pci_dev_put(pdev); - return zpci_deconfigure_device(zdev); + rc = zpci_deconfigure_device(zdev); +out: + if (pdev) + pci_dev_put(pdev); + mutex_unlock(&zdev->state_lock); + return rc; } static int reset_slot(struct hotplug_slot *hotplug_slot, bool probe) { struct zpci_dev *zdev = container_of(hotplug_slot, struct zpci_dev, hotplug_slot); + int rc = -EIO; - if (zdev->state != ZPCI_FN_STATE_CONFIGURED) - return -EIO; /* - * We can't take the zdev->lock as reset_slot may be called during - * probing and/or device removal which already happens under the - * zdev->lock. Instead the user should use the higher level - * pci_reset_function() or pci_bus_reset() which hold the PCI device - * lock preventing concurrent removal. If not using these functions - * holding the PCI device lock is required. + * If we can't get the zdev->state_lock the device state is + * currently undergoing a transition and we bail out - just + * the same as if the device's state is not configured at all. */ + if (!mutex_trylock(&zdev->state_lock)) + return rc; - /* As long as the function is configured we can reset */ - if (probe) - return 0; + /* We can reset only if the function is configured */ + if (zdev->state != ZPCI_FN_STATE_CONFIGURED) + goto out; + + if (probe) { + rc = 0; + goto out; + } - return zpci_hot_reset_device(zdev); + rc = zpci_hot_reset_device(zdev); +out: + mutex_unlock(&zdev->state_lock); + return rc; } static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) @@ -91,7 +110,7 @@ static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) { - /* if the slot exits it always contains a function */ + /* if the slot exists it always contains a function */ *value = 1; return 0; } diff --git a/drivers/pci/hotplug/shpchp.h b/drivers/pci/hotplug/shpchp.h index 3a97f455336e..a425530e0939 100644 --- a/drivers/pci/hotplug/shpchp.h +++ b/drivers/pci/hotplug/shpchp.h @@ -33,24 +33,8 @@ extern bool shpchp_poll_mode; extern int shpchp_poll_time; extern bool shpchp_debug; -#define dbg(format, arg...) \ -do { \ - if (shpchp_debug) \ - printk(KERN_DEBUG "%s: " format, MY_NAME, ## arg); \ -} while (0) -#define err(format, arg...) \ - printk(KERN_ERR "%s: " format, MY_NAME, ## arg) -#define info(format, arg...) \ - printk(KERN_INFO "%s: " format, MY_NAME, ## arg) -#define warn(format, arg...) \ - printk(KERN_WARNING "%s: " format, MY_NAME, ## arg) - #define ctrl_dbg(ctrl, format, arg...) \ - do { \ - if (shpchp_debug) \ - pci_printk(KERN_DEBUG, ctrl->pci_dev, \ - format, ## arg); \ - } while (0) + pci_dbg(ctrl->pci_dev, format, ## arg) #define ctrl_err(ctrl, format, arg...) \ pci_err(ctrl->pci_dev, format, ## arg) #define ctrl_info(ctrl, format, arg...) \ @@ -72,7 +56,6 @@ struct slot { u8 latch_save; u8 pwr_save; struct controller *ctrl; - const struct hpc_ops *hpc_ops; struct hotplug_slot hotplug_slot; struct list_head slot_list; struct delayed_work work; /* work for button event */ @@ -94,7 +77,6 @@ struct controller { int slot_num_inc; /* 1 or -1 */ struct pci_dev *pci_dev; struct list_head slot_list; - const struct hpc_ops *hpc_ops; wait_queue_head_t queue; /* sleep & wake process */ u8 slot_device_offset; u32 pcix_misc2_reg; /* for amd pogo errata */ @@ -300,24 +282,22 @@ static inline void amd_pogo_errata_restore_misc_reg(struct slot *p_slot) pci_write_config_dword(p_slot->ctrl->pci_dev, PCIX_MISCII_OFFSET, pcix_misc2_temp); } -struct hpc_ops { - int (*power_on_slot)(struct slot *slot); - int (*slot_enable)(struct slot *slot); - int (*slot_disable)(struct slot *slot); - int (*set_bus_speed_mode)(struct slot *slot, enum pci_bus_speed speed); - int (*get_power_status)(struct slot *slot, u8 *status); - int (*get_attention_status)(struct slot *slot, u8 *status); - int (*set_attention_status)(struct slot *slot, u8 status); - int (*get_latch_status)(struct slot *slot, u8 *status); - int (*get_adapter_status)(struct slot *slot, u8 *status); - int (*get_adapter_speed)(struct slot *slot, enum pci_bus_speed *speed); - int (*get_prog_int)(struct slot *slot, u8 *prog_int); - int (*query_power_fault)(struct slot *slot); - void (*green_led_on)(struct slot *slot); - void (*green_led_off)(struct slot *slot); - void (*green_led_blink)(struct slot *slot); - void (*release_ctlr)(struct controller *ctrl); - int (*check_cmd_status)(struct controller *ctrl); -}; +int shpchp_power_on_slot(struct slot *slot); +int shpchp_slot_enable(struct slot *slot); +int shpchp_slot_disable(struct slot *slot); +int shpchp_set_bus_speed_mode(struct slot *slot, enum pci_bus_speed speed); +int shpchp_get_power_status(struct slot *slot, u8 *status); +int shpchp_get_attention_status(struct slot *slot, u8 *status); +int shpchp_set_attention_status(struct slot *slot, u8 status); +int shpchp_get_latch_status(struct slot *slot, u8 *status); +int shpchp_get_adapter_status(struct slot *slot, u8 *status); +int shpchp_get_adapter_speed(struct slot *slot, enum pci_bus_speed *speed); +int shpchp_get_prog_int(struct slot *slot, u8 *prog_int); +int shpchp_query_power_fault(struct slot *slot); +void shpchp_green_led_on(struct slot *slot); +void shpchp_green_led_off(struct slot *slot); +void shpchp_green_led_blink(struct slot *slot); +void shpchp_release_ctlr(struct controller *ctrl); +int shpchp_check_cmd_status(struct controller *ctrl); #endif /* _SHPCHP_H */ diff --git a/drivers/pci/hotplug/shpchp_core.c b/drivers/pci/hotplug/shpchp_core.c index 53692b048301..0c341453afc6 100644 --- a/drivers/pci/hotplug/shpchp_core.c +++ b/drivers/pci/hotplug/shpchp_core.c @@ -22,7 +22,6 @@ #include "shpchp.h" /* Global variables */ -bool shpchp_debug; bool shpchp_poll_mode; int shpchp_poll_time; @@ -32,12 +31,9 @@ int shpchp_poll_time; MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); -module_param(shpchp_debug, bool, 0644); module_param(shpchp_poll_mode, bool, 0644); module_param(shpchp_poll_time, int, 0644); -MODULE_PARM_DESC(shpchp_debug, "Debugging mode enabled or not"); MODULE_PARM_DESC(shpchp_poll_mode, "Using polling mechanism for hot-plug events or not"); MODULE_PARM_DESC(shpchp_poll_time, "Polling mechanism frequency, in seconds"); @@ -82,7 +78,6 @@ static int init_slots(struct controller *ctrl) slot->ctrl = ctrl; slot->bus = ctrl->pci_dev->subordinate->number; slot->device = ctrl->slot_device_offset + i; - slot->hpc_ops = ctrl->hpc_ops; slot->number = ctrl->first_slot + (ctrl->slot_num_inc * i); slot->wq = alloc_workqueue("shpchp-%d", 0, 0, slot->number); @@ -151,7 +146,7 @@ static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) __func__, slot_name(slot)); slot->attention_save = status; - slot->hpc_ops->set_attention_status(slot, status); + shpchp_set_attention_status(slot, status); return 0; } @@ -184,7 +179,7 @@ static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", __func__, slot_name(slot)); - retval = slot->hpc_ops->get_power_status(slot, value); + retval = shpchp_get_power_status(slot, value); if (retval < 0) *value = slot->pwr_save; @@ -199,7 +194,7 @@ static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", __func__, slot_name(slot)); - retval = slot->hpc_ops->get_attention_status(slot, value); + retval = shpchp_get_attention_status(slot, value); if (retval < 0) *value = slot->attention_save; @@ -214,7 +209,7 @@ static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", __func__, slot_name(slot)); - retval = slot->hpc_ops->get_latch_status(slot, value); + retval = shpchp_get_latch_status(slot, value); if (retval < 0) *value = slot->latch_save; @@ -229,7 +224,7 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", __func__, slot_name(slot)); - retval = slot->hpc_ops->get_adapter_status(slot, value); + retval = shpchp_get_adapter_status(slot, value); if (retval < 0) *value = slot->presence_save; @@ -294,7 +289,7 @@ static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) err_cleanup_slots: cleanup_slots(ctrl); err_out_release_ctlr: - ctrl->hpc_ops->release_ctlr(ctrl); + shpchp_release_ctlr(ctrl); err_out_free_ctrl: kfree(ctrl); err_out_none: @@ -307,7 +302,7 @@ static void shpc_remove(struct pci_dev *dev) dev->shpc_managed = 0; shpchp_remove_ctrl_files(ctrl); - ctrl->hpc_ops->release_ctlr(ctrl); + shpchp_release_ctlr(ctrl); kfree(ctrl); } @@ -326,20 +321,12 @@ static struct pci_driver shpc_driver = { static int __init shpcd_init(void) { - int retval; - - retval = pci_register_driver(&shpc_driver); - dbg("%s: pci_register_driver = %d\n", __func__, retval); - info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); - - return retval; + return pci_register_driver(&shpc_driver); } static void __exit shpcd_cleanup(void) { - dbg("unload_shpchpd()\n"); pci_unregister_driver(&shpc_driver); - info(DRIVER_DESC " version: " DRIVER_VERSION " unloaded\n"); } module_init(shpcd_init); diff --git a/drivers/pci/hotplug/shpchp_ctrl.c b/drivers/pci/hotplug/shpchp_ctrl.c index 6a6705e0cf17..e6c6f23bae27 100644 --- a/drivers/pci/hotplug/shpchp_ctrl.c +++ b/drivers/pci/hotplug/shpchp_ctrl.c @@ -51,7 +51,7 @@ u8 shpchp_handle_attention_button(u8 hp_slot, struct controller *ctrl) ctrl_dbg(ctrl, "Attention button interrupt received\n"); p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); - p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); + shpchp_get_adapter_status(p_slot, &p_slot->presence_save); /* * Button pressed - See if need to TAKE ACTION!!! @@ -75,8 +75,8 @@ u8 shpchp_handle_switch_change(u8 hp_slot, struct controller *ctrl) ctrl_dbg(ctrl, "Switch interrupt received\n"); p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); - p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); - p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + shpchp_get_adapter_status(p_slot, &p_slot->presence_save); + shpchp_get_latch_status(p_slot, &getstatus); ctrl_dbg(ctrl, "Card present %x Power status %x\n", p_slot->presence_save, p_slot->pwr_save); @@ -116,7 +116,7 @@ u8 shpchp_handle_presence_change(u8 hp_slot, struct controller *ctrl) /* * Save the presence state */ - p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); + shpchp_get_adapter_status(p_slot, &p_slot->presence_save); if (p_slot->presence_save) { /* * Card Present @@ -148,7 +148,7 @@ u8 shpchp_handle_power_fault(u8 hp_slot, struct controller *ctrl) p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); - if (!(p_slot->hpc_ops->query_power_fault(p_slot))) { + if (!(shpchp_query_power_fault(p_slot))) { /* * Power fault Cleared */ @@ -181,7 +181,7 @@ static int change_bus_speed(struct controller *ctrl, struct slot *p_slot, int rc = 0; ctrl_dbg(ctrl, "Change speed to %d\n", speed); - rc = p_slot->hpc_ops->set_bus_speed_mode(p_slot, speed); + rc = shpchp_set_bus_speed_mode(p_slot, speed); if (rc) { ctrl_err(ctrl, "%s: Issue of set bus speed mode command failed\n", __func__); @@ -241,14 +241,14 @@ static int board_added(struct slot *p_slot) __func__, p_slot->device, ctrl->slot_device_offset, hp_slot); /* Power on slot without connecting to bus */ - rc = p_slot->hpc_ops->power_on_slot(p_slot); + rc = shpchp_power_on_slot(p_slot); if (rc) { ctrl_err(ctrl, "Failed to power on slot\n"); return -1; } if ((ctrl->pci_dev->vendor == 0x8086) && (ctrl->pci_dev->device == 0x0332)) { - rc = p_slot->hpc_ops->set_bus_speed_mode(p_slot, PCI_SPEED_33MHz); + rc = shpchp_set_bus_speed_mode(p_slot, PCI_SPEED_33MHz); if (rc) { ctrl_err(ctrl, "%s: Issue of set bus speed mode command failed\n", __func__); @@ -256,14 +256,14 @@ static int board_added(struct slot *p_slot) } /* turn on board, blink green LED, turn off Amber LED */ - rc = p_slot->hpc_ops->slot_enable(p_slot); + rc = shpchp_slot_enable(p_slot); if (rc) { ctrl_err(ctrl, "Issue of Slot Enable command failed\n"); return rc; } } - rc = p_slot->hpc_ops->get_adapter_speed(p_slot, &asp); + rc = shpchp_get_adapter_speed(p_slot, &asp); if (rc) { ctrl_err(ctrl, "Can't get adapter speed or bus mode mismatch\n"); return WRONG_BUS_FREQUENCY; @@ -285,7 +285,7 @@ static int board_added(struct slot *p_slot) return rc; /* turn on board, blink green LED, turn off Amber LED */ - rc = p_slot->hpc_ops->slot_enable(p_slot); + rc = shpchp_slot_enable(p_slot); if (rc) { ctrl_err(ctrl, "Issue of Slot Enable command failed\n"); return rc; @@ -313,13 +313,13 @@ static int board_added(struct slot *p_slot) p_slot->is_a_board = 0x01; p_slot->pwr_save = 1; - p_slot->hpc_ops->green_led_on(p_slot); + shpchp_green_led_on(p_slot); return 0; err_exit: /* turn off slot, turn on Amber LED, turn off Green LED */ - rc = p_slot->hpc_ops->slot_disable(p_slot); + rc = shpchp_slot_disable(p_slot); if (rc) { ctrl_err(ctrl, "%s: Issue of Slot Disable command failed\n", __func__); @@ -352,14 +352,14 @@ static int remove_board(struct slot *p_slot) p_slot->status = 0x01; /* turn off slot, turn on Amber LED, turn off Green LED */ - rc = p_slot->hpc_ops->slot_disable(p_slot); + rc = shpchp_slot_disable(p_slot); if (rc) { ctrl_err(ctrl, "%s: Issue of Slot Disable command failed\n", __func__); return rc; } - rc = p_slot->hpc_ops->set_attention_status(p_slot, 0); + rc = shpchp_set_attention_status(p_slot, 0); if (rc) { ctrl_err(ctrl, "Issue of Set Attention command failed\n"); return rc; @@ -401,7 +401,7 @@ static void shpchp_pushbutton_thread(struct work_struct *work) case POWERON_STATE: mutex_unlock(&p_slot->lock); if (shpchp_enable_slot(p_slot)) - p_slot->hpc_ops->green_led_off(p_slot); + shpchp_green_led_off(p_slot); mutex_lock(&p_slot->lock); p_slot->state = STATIC_STATE; break; @@ -446,10 +446,10 @@ void shpchp_queue_pushbutton_work(struct work_struct *work) static void update_slot_info(struct slot *slot) { - slot->hpc_ops->get_power_status(slot, &slot->pwr_save); - slot->hpc_ops->get_attention_status(slot, &slot->attention_save); - slot->hpc_ops->get_latch_status(slot, &slot->latch_save); - slot->hpc_ops->get_adapter_status(slot, &slot->presence_save); + shpchp_get_power_status(slot, &slot->pwr_save); + shpchp_get_attention_status(slot, &slot->attention_save); + shpchp_get_latch_status(slot, &slot->latch_save); + shpchp_get_adapter_status(slot, &slot->presence_save); } /* @@ -462,7 +462,7 @@ static void handle_button_press_event(struct slot *p_slot) switch (p_slot->state) { case STATIC_STATE: - p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + shpchp_get_power_status(p_slot, &getstatus); if (getstatus) { p_slot->state = BLINKINGOFF_STATE; ctrl_info(ctrl, "PCI slot #%s - powering off due to button press\n", @@ -473,8 +473,8 @@ static void handle_button_press_event(struct slot *p_slot) slot_name(p_slot)); } /* blink green LED and turn off amber */ - p_slot->hpc_ops->green_led_blink(p_slot); - p_slot->hpc_ops->set_attention_status(p_slot, 0); + shpchp_green_led_blink(p_slot); + shpchp_set_attention_status(p_slot, 0); queue_delayed_work(p_slot->wq, &p_slot->work, 5*HZ); break; @@ -489,10 +489,10 @@ static void handle_button_press_event(struct slot *p_slot) slot_name(p_slot)); cancel_delayed_work(&p_slot->work); if (p_slot->state == BLINKINGOFF_STATE) - p_slot->hpc_ops->green_led_on(p_slot); + shpchp_green_led_on(p_slot); else - p_slot->hpc_ops->green_led_off(p_slot); - p_slot->hpc_ops->set_attention_status(p_slot, 0); + shpchp_green_led_off(p_slot); + shpchp_set_attention_status(p_slot, 0); ctrl_info(ctrl, "PCI slot #%s - action canceled due to button press\n", slot_name(p_slot)); p_slot->state = STATIC_STATE; @@ -526,8 +526,8 @@ static void interrupt_event_handler(struct work_struct *work) break; case INT_POWER_FAULT: ctrl_dbg(p_slot->ctrl, "%s: Power fault\n", __func__); - p_slot->hpc_ops->set_attention_status(p_slot, 1); - p_slot->hpc_ops->green_led_off(p_slot); + shpchp_set_attention_status(p_slot, 1); + shpchp_green_led_off(p_slot); break; default: update_slot_info(p_slot); @@ -547,17 +547,17 @@ static int shpchp_enable_slot (struct slot *p_slot) /* Check to see if (latch closed, card present, power off) */ mutex_lock(&p_slot->ctrl->crit_sect); - rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + rc = shpchp_get_adapter_status(p_slot, &getstatus); if (rc || !getstatus) { ctrl_info(ctrl, "No adapter on slot(%s)\n", slot_name(p_slot)); goto out; } - rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + rc = shpchp_get_latch_status(p_slot, &getstatus); if (rc || getstatus) { ctrl_info(ctrl, "Latch open on slot(%s)\n", slot_name(p_slot)); goto out; } - rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + rc = shpchp_get_power_status(p_slot, &getstatus); if (rc || getstatus) { ctrl_info(ctrl, "Already enabled on slot(%s)\n", slot_name(p_slot)); @@ -567,10 +567,10 @@ static int shpchp_enable_slot (struct slot *p_slot) p_slot->is_a_board = 1; /* We have to save the presence info for these slots */ - p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); - p_slot->hpc_ops->get_power_status(p_slot, &(p_slot->pwr_save)); + shpchp_get_adapter_status(p_slot, &p_slot->presence_save); + shpchp_get_power_status(p_slot, &p_slot->pwr_save); ctrl_dbg(ctrl, "%s: p_slot->pwr_save %x\n", __func__, p_slot->pwr_save); - p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + shpchp_get_latch_status(p_slot, &getstatus); if ((p_slot->ctrl->pci_dev->vendor == PCI_VENDOR_ID_AMD && p_slot->ctrl->pci_dev->device == PCI_DEVICE_ID_AMD_POGO_7458) @@ -584,9 +584,8 @@ static int shpchp_enable_slot (struct slot *p_slot) retval = board_added(p_slot); if (retval) { - p_slot->hpc_ops->get_adapter_status(p_slot, - &(p_slot->presence_save)); - p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + shpchp_get_adapter_status(p_slot, &p_slot->presence_save); + shpchp_get_latch_status(p_slot, &getstatus); } update_slot_info(p_slot); @@ -608,17 +607,17 @@ static int shpchp_disable_slot (struct slot *p_slot) /* Check to see if (latch closed, card present, power on) */ mutex_lock(&p_slot->ctrl->crit_sect); - rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + rc = shpchp_get_adapter_status(p_slot, &getstatus); if (rc || !getstatus) { ctrl_info(ctrl, "No adapter on slot(%s)\n", slot_name(p_slot)); goto out; } - rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); + rc = shpchp_get_latch_status(p_slot, &getstatus); if (rc || getstatus) { ctrl_info(ctrl, "Latch open on slot(%s)\n", slot_name(p_slot)); goto out; } - rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + rc = shpchp_get_power_status(p_slot, &getstatus); if (rc || !getstatus) { ctrl_info(ctrl, "Already disabled on slot(%s)\n", slot_name(p_slot)); diff --git a/drivers/pci/hotplug/shpchp_hpc.c b/drivers/pci/hotplug/shpchp_hpc.c index 48e4daefc44a..183bf43510a1 100644 --- a/drivers/pci/hotplug/shpchp_hpc.c +++ b/drivers/pci/hotplug/shpchp_hpc.c @@ -167,7 +167,6 @@ static irqreturn_t shpc_isr(int irq, void *dev_id); static void start_int_poll_timer(struct controller *ctrl, int sec); -static int hpc_check_cmd_status(struct controller *ctrl); static inline u8 shpc_readb(struct controller *ctrl, int reg) { @@ -212,7 +211,7 @@ static inline int shpc_indirect_read(struct controller *ctrl, int index, */ static void int_poll_timeout(struct timer_list *t) { - struct controller *ctrl = from_timer(ctrl, t, poll_timer); + struct controller *ctrl = timer_container_of(ctrl, t, poll_timer); /* Poll for interrupt events. regs == NULL => polling */ shpc_isr(0, ctrl); @@ -317,7 +316,7 @@ static int shpc_write_cmd(struct slot *slot, u8 t_slot, u8 cmd) if (retval) goto out; - cmd_status = hpc_check_cmd_status(slot->ctrl); + cmd_status = shpchp_check_cmd_status(slot->ctrl); if (cmd_status) { ctrl_err(ctrl, "Failed to issued command 0x%x (error code = %d)\n", cmd, cmd_status); @@ -328,7 +327,7 @@ static int shpc_write_cmd(struct slot *slot, u8 t_slot, u8 cmd) return retval; } -static int hpc_check_cmd_status(struct controller *ctrl) +int shpchp_check_cmd_status(struct controller *ctrl) { int retval = 0; u16 cmd_status = shpc_readw(ctrl, CMD_STATUS) & 0x000F; @@ -357,7 +356,7 @@ static int hpc_check_cmd_status(struct controller *ctrl) } -static int hpc_get_attention_status(struct slot *slot, u8 *status) +int shpchp_get_attention_status(struct slot *slot, u8 *status) { struct controller *ctrl = slot->ctrl; u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); @@ -381,7 +380,7 @@ static int hpc_get_attention_status(struct slot *slot, u8 *status) return 0; } -static int hpc_get_power_status(struct slot *slot, u8 *status) +int shpchp_get_power_status(struct slot *slot, u8 *status) { struct controller *ctrl = slot->ctrl; u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); @@ -406,7 +405,7 @@ static int hpc_get_power_status(struct slot *slot, u8 *status) } -static int hpc_get_latch_status(struct slot *slot, u8 *status) +int shpchp_get_latch_status(struct slot *slot, u8 *status) { struct controller *ctrl = slot->ctrl; u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); @@ -416,7 +415,7 @@ static int hpc_get_latch_status(struct slot *slot, u8 *status) return 0; } -static int hpc_get_adapter_status(struct slot *slot, u8 *status) +int shpchp_get_adapter_status(struct slot *slot, u8 *status) { struct controller *ctrl = slot->ctrl; u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); @@ -427,7 +426,7 @@ static int hpc_get_adapter_status(struct slot *slot, u8 *status) return 0; } -static int hpc_get_prog_int(struct slot *slot, u8 *prog_int) +int shpchp_get_prog_int(struct slot *slot, u8 *prog_int) { struct controller *ctrl = slot->ctrl; @@ -436,7 +435,7 @@ static int hpc_get_prog_int(struct slot *slot, u8 *prog_int) return 0; } -static int hpc_get_adapter_speed(struct slot *slot, enum pci_bus_speed *value) +int shpchp_get_adapter_speed(struct slot *slot, enum pci_bus_speed *value) { int retval = 0; struct controller *ctrl = slot->ctrl; @@ -444,7 +443,7 @@ static int hpc_get_adapter_speed(struct slot *slot, enum pci_bus_speed *value) u8 m66_cap = !!(slot_reg & MHZ66_CAP); u8 pi, pcix_cap; - retval = hpc_get_prog_int(slot, &pi); + retval = shpchp_get_prog_int(slot, &pi); if (retval) return retval; @@ -489,7 +488,7 @@ static int hpc_get_adapter_speed(struct slot *slot, enum pci_bus_speed *value) return retval; } -static int hpc_query_power_fault(struct slot *slot) +int shpchp_query_power_fault(struct slot *slot) { struct controller *ctrl = slot->ctrl; u32 slot_reg = shpc_readl(ctrl, SLOT_REG(slot->hp_slot)); @@ -498,7 +497,7 @@ static int hpc_query_power_fault(struct slot *slot) return !(slot_reg & POWER_FAULT); } -static int hpc_set_attention_status(struct slot *slot, u8 value) +int shpchp_set_attention_status(struct slot *slot, u8 value) { u8 slot_cmd = 0; @@ -520,22 +519,22 @@ static int hpc_set_attention_status(struct slot *slot, u8 value) } -static void hpc_set_green_led_on(struct slot *slot) +void shpchp_green_led_on(struct slot *slot) { shpc_write_cmd(slot, slot->hp_slot, SET_PWR_ON); } -static void hpc_set_green_led_off(struct slot *slot) +void shpchp_green_led_off(struct slot *slot) { shpc_write_cmd(slot, slot->hp_slot, SET_PWR_OFF); } -static void hpc_set_green_led_blink(struct slot *slot) +void shpchp_green_led_blink(struct slot *slot) { shpc_write_cmd(slot, slot->hp_slot, SET_PWR_BLINK); } -static void hpc_release_ctlr(struct controller *ctrl) +void shpchp_release_ctlr(struct controller *ctrl) { int i; u32 slot_reg, serr_int; @@ -565,7 +564,7 @@ static void hpc_release_ctlr(struct controller *ctrl) shpc_writel(ctrl, SERR_INTR_ENABLE, serr_int); if (shpchp_poll_mode) - del_timer(&ctrl->poll_timer); + timer_delete(&ctrl->poll_timer); else { free_irq(ctrl->pci_dev->irq, ctrl); pci_disable_msi(ctrl->pci_dev); @@ -575,7 +574,7 @@ static void hpc_release_ctlr(struct controller *ctrl) release_mem_region(ctrl->mmio_base, ctrl->mmio_size); } -static int hpc_power_on_slot(struct slot *slot) +int shpchp_power_on_slot(struct slot *slot) { int retval; @@ -586,7 +585,7 @@ static int hpc_power_on_slot(struct slot *slot) return retval; } -static int hpc_slot_enable(struct slot *slot) +int shpchp_slot_enable(struct slot *slot) { int retval; @@ -599,7 +598,7 @@ static int hpc_slot_enable(struct slot *slot) return retval; } -static int hpc_slot_disable(struct slot *slot) +int shpchp_slot_disable(struct slot *slot) { int retval; @@ -676,12 +675,12 @@ static int shpc_get_cur_bus_speed(struct controller *ctrl) out: bus->cur_bus_speed = bus_speed; - dbg("Current bus speed = %d\n", bus_speed); + ctrl_dbg(ctrl, "Current bus speed = %d\n", bus_speed); return retval; } -static int hpc_set_bus_speed_mode(struct slot *slot, enum pci_bus_speed value) +int shpchp_set_bus_speed_mode(struct slot *slot, enum pci_bus_speed value) { int retval; struct controller *ctrl = slot->ctrl; @@ -871,28 +870,6 @@ static int shpc_get_max_bus_speed(struct controller *ctrl) return retval; } -static const struct hpc_ops shpchp_hpc_ops = { - .power_on_slot = hpc_power_on_slot, - .slot_enable = hpc_slot_enable, - .slot_disable = hpc_slot_disable, - .set_bus_speed_mode = hpc_set_bus_speed_mode, - .set_attention_status = hpc_set_attention_status, - .get_power_status = hpc_get_power_status, - .get_attention_status = hpc_get_attention_status, - .get_latch_status = hpc_get_latch_status, - .get_adapter_status = hpc_get_adapter_status, - - .get_adapter_speed = hpc_get_adapter_speed, - .get_prog_int = hpc_get_prog_int, - - .query_power_fault = hpc_query_power_fault, - .green_led_on = hpc_set_green_led_on, - .green_led_off = hpc_set_green_led_off, - .green_led_blink = hpc_set_green_led_blink, - - .release_ctlr = hpc_release_ctlr, -}; - int shpc_init(struct controller *ctrl, struct pci_dev *pdev) { int rc = -1, num_slots = 0; @@ -978,8 +955,6 @@ int shpc_init(struct controller *ctrl, struct pci_dev *pdev) /* Setup wait queue */ init_waitqueue_head(&ctrl->queue); - ctrl->hpc_ops = &shpchp_hpc_ops; - /* Return PCI Controller Info */ slot_config = shpc_readl(ctrl, SLOT_CONFIG); ctrl->slot_device_offset = (slot_config & FIRST_DEV_NUM) >> 8; diff --git a/drivers/pci/hotplug/shpchp_sysfs.c b/drivers/pci/hotplug/shpchp_sysfs.c index 64beed7a26be..01d47a42da04 100644 --- a/drivers/pci/hotplug/shpchp_sysfs.c +++ b/drivers/pci/hotplug/shpchp_sysfs.c @@ -24,16 +24,16 @@ static ssize_t show_ctrl(struct device *dev, struct device_attribute *attr, char *buf) { struct pci_dev *pdev; - int index, busnr; struct resource *res; struct pci_bus *bus; size_t len = 0; + int busnr; pdev = to_pci_dev(dev); bus = pdev->subordinate; len += sysfs_emit_at(buf, len, "Free resources: memory\n"); - pci_bus_for_each_resource(bus, res, index) { + pci_bus_for_each_resource(bus, res) { if (res && (res->flags & IORESOURCE_MEM) && !(res->flags & IORESOURCE_PREFETCH)) { len += sysfs_emit_at(buf, len, @@ -43,7 +43,7 @@ static ssize_t show_ctrl(struct device *dev, struct device_attribute *attr, char } } len += sysfs_emit_at(buf, len, "Free resources: prefetchable memory\n"); - pci_bus_for_each_resource(bus, res, index) { + pci_bus_for_each_resource(bus, res) { if (res && (res->flags & IORESOURCE_MEM) && (res->flags & IORESOURCE_PREFETCH)) { len += sysfs_emit_at(buf, len, @@ -53,7 +53,7 @@ static ssize_t show_ctrl(struct device *dev, struct device_attribute *attr, char } } len += sysfs_emit_at(buf, len, "Free resources: IO\n"); - pci_bus_for_each_resource(bus, res, index) { + pci_bus_for_each_resource(bus, res) { if (res && (res->flags & IORESOURCE_IO)) { len += sysfs_emit_at(buf, len, "start = %8.8llx, length = %8.8llx\n", |
