// SPDX-License-Identifier: GPL-2.0+ /* * PCI Express Hot Plug Controller Driver * * Copyright (C) 1995,2001 Compaq Computer Corporation * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) * Copyright (C) 2001 IBM Corp. * Copyright (C) 2003-2004 Intel Corporation * * All rights reserved. * * Send feedback to , * * Authors: * Dan Zink * Greg Kroah-Hartman * Dely Sy " */ #define pr_fmt(fmt) "pciehp: " fmt #define dev_fmt pr_fmt #include #include #include #include #include #include #include "pciehp.h" #include "../pci.h" /* Global variables */ bool pciehp_poll_mode; int pciehp_poll_time; /* * not really modular, but the easiest way to keep compat with existing * bootargs behaviour is to continue using module_param here. */ module_param(pciehp_poll_mode, bool, 0644); module_param(pciehp_poll_time, int, 0644); MODULE_PARM_DESC(pciehp_poll_mode, "Using polling mechanism for hot-plug events or not"); MODULE_PARM_DESC(pciehp_poll_time, "Polling mechanism frequency, in seconds"); static int set_attention_status(struct hotplug_slot *slot, u8 value); static int get_power_status(struct hotplug_slot *slot, u8 *value); static int get_latch_status(struct hotplug_slot *slot, u8 *value); static int get_adapter_status(struct hotplug_slot *slot, u8 *value); static int init_slot(struct controller *ctrl) { struct hotplug_slot_ops *ops; char name[SLOT_NAME_SIZE]; int retval; /* Setup hotplug slot ops */ ops = kzalloc(sizeof(*ops), GFP_KERNEL); if (!ops) return -ENOMEM; ops->enable_slot = pciehp_sysfs_enable_slot; ops->disable_slot = pciehp_sysfs_disable_slot; ops->get_power_status = get_power_status; ops->get_adapter_status = get_adapter_status; ops->reset_slot = pciehp_reset_slot; if (MRL_SENS(ctrl)) ops->get_latch_status = get_latch_status; if (ATTN_LED(ctrl)) { ops->get_attention_status = pciehp_get_attention_status; ops->set_attention_status = set_attention_status; } else if (ctrl->pcie->port->hotplug_user_indicators) { ops->get_attention_status = pciehp_get_raw_indicator_status; ops->set_attention_status = pciehp_set_raw_indicator_status; } /* register this slot with the hotplug pci core */ ctrl->hotplug_slot.ops = ops; snprintf(name, SLOT_NAME_SIZE, "%u", PSN(ctrl)); retval = pci_hp_initialize(&ctrl->hotplug_slot, ctrl->pcie->port->subordinate, 0, name); if (retval) { ctrl_err(ctrl, "pci_hp_initialize failed: error %d\n", retval); kfree(ops); } return retval; } static void cleanup_slot(struct controller *ctrl) { struct hotplug_slot *hotplug_slot = &ctrl->hotplug_slot; pci_hp_destroy(hotplug_slot); kfree(hotplug_slot->ops); } /* * set_attention_status - Turns the Attention Indicator on, off or blinking */ static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) { struct controller *ctrl = to_ctrl(hotplug_slot); struct pci_dev *pdev = ctrl->pcie->port; if (status) status = FIELD_PREP(PCI_EXP_SLTCTL_AIC, status); else status = PCI_EXP_SLTCTL_ATTN_IND_OFF; pci_config_pm_runtime_get(pdev); pciehp_set_indicators(ctrl, INDICATOR_NOOP, status); pci_config_pm_runtime_put(pdev); return 0; } static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) { struct controller *ctrl = to_ctrl(hotplug_slot); struct pci_dev *pdev = ctrl->pcie->port; pci_config_pm_runtime_get(pdev); pciehp_get_power_status(ctrl, value); pci_config_pm_runtime_put(pdev); return 0; } static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) { struct controller *ctrl = to_ctrl(hotplug_slot); struct pci_dev *pdev = ctrl->pcie->port; pci_config_pm_runtime_get(pdev); pciehp_get_latch_status(ctrl, value); pci_config_pm_runtime_put(pdev); return 0; } static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) { struct controller *ctrl = to_ctrl(hotplug_slot); struct pci_dev *pdev = ctrl->pcie->port; int ret; pci_config_pm_runtime_get(pdev); ret = pciehp_card_present_or_link_active(ctrl); pci_config_pm_runtime_put(pdev); if (ret < 0) return ret; *value = ret; return 0; } /** * pciehp_check_presence() - synthesize event if presence has changed * @ctrl: controller to check * * On probe and resume, an explicit presence check is necessary to bring up an * occupied slot or bring down an unoccupied slot. This can't be triggered by * events in the Slot Status register, they may be stale and are therefore * cleared. Secondly, sending an interrupt for "events that occur while * interrupt generation is disabled [when] interrupt generation is subsequently * enabled" is optional per PCIe r4.0, sec 6.7.3.4. */ static void pciehp_check_presence(struct controller *ctrl) { int occupied; down_read_nested(&ctrl->reset_lock, ctrl->depth); mutex_lock(&ctrl->state_lock); occupied = pciehp_card_present_or_link_active(ctrl); if ((occupied > 0 && (ctrl->state == OFF_STATE || ctrl->state == BLINKINGON_STATE)) || (!occupied && (ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE))) pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); mutex_unlock(&ctrl->state_lock); up_read(&ctrl->reset_lock); } static int pciehp_probe(struct pcie_device *dev) { int rc; struct controller *ctrl; /* If this is not a "hotplug" service, we have no business here. */ if (dev->service != PCIE_PORT_SERVICE_HP) return -ENODEV; if (!dev->port->subordinate) { /* Can happen if we run out of bus numbers during probe */ pci_err(dev->port, "Hotplug bridge without secondary bus, ignoring\n"); return -ENODEV; } ctrl = pcie_init(dev); if (!ctrl) { pci_err(dev->port, "Controller initialization failed\n"); return -ENODEV; } set_service_data(dev, ctrl); /* Setup the slot information structures */ rc = init_slot(ctrl); if (rc) { if (rc == -EBUSY) ctrl_warn(ctrl, "Slot already registered by another hotplug driver\n"); else ctrl_err(ctrl, "Slot initialization failed (%d)\n", rc); goto err_out_release_ctlr; } /* Enable events after we have setup the data structures */ rc = pcie_init_notification(ctrl); if (rc) { ctrl_err(ctrl, "Notification initialization failed (%d)\n", rc); goto err_out_free_ctrl_slot; } /* Publish to user space */ rc = pci_hp_add(&ctrl->hotplug_slot); if (rc) { ctrl_err(ctrl, "Publication to user space failed (%d)\n", rc); goto err_out_shutdown_notification; } pciehp_check_presence(ctrl); return 0; err_out_shutdown_notification: pcie_shutdown_notification(ctrl); err_out_free_ctrl_slot: cleanup_slot(ctrl); err_out_release_ctlr: pciehp_release_ctrl(ctrl); return -ENODEV; } static void pciehp_remove(struct pcie_device *dev) { struct controller *ctrl = get_service_data(dev); pci_hp_del(&ctrl->hotplug_slot); pcie_shutdown_notification(ctrl); cleanup_slot(ctrl); pciehp_release_ctrl(ctrl); } #ifdef CONFIG_PM static bool pme_is_native(struct pcie_device *dev) { const struct pci_host_bridge *host; host = pci_find_host_bridge(dev->port->bus); return pcie_ports_native || host->native_pme; } static void pciehp_disable_interrupt(struct pcie_device *dev) { /* * Disable hotplug interrupt so that it does not trigger * immediately when the downstream link goes down. */ if (pme_is_native(dev)) pcie_disable_interrupt(get_service_data(dev)); } #ifdef CONFIG_PM_SLEEP static int pciehp_suspend(struct pcie_device *dev) { /* * If the port is already runtime suspended we can keep it that * way. */ if (dev_pm_skip_suspend(&dev->port->dev)) return 0; pciehp_disable_interrupt(dev); return 0; } static bool pciehp_device_replaced(struct controller *ctrl) { struct pci_dev *pdev __free(pci_dev_put); u32 reg; 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 int pciehp_resume_noirq(struct pcie_device *dev) { struct controller *ctrl = get_service_data(dev); /* pci_restore_state() just wrote to the Slot Control register */ ctrl->cmd_started = jiffies; ctrl->cmd_busy = true; /* clear spurious events from rediscovery of inserted card */ 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 static int pciehp_resume(struct pcie_device *dev) { struct controller *ctrl = get_service_data(dev); if (pme_is_native(dev)) pcie_enable_interrupt(ctrl); pciehp_check_presence(ctrl); return 0; } static int pciehp_runtime_suspend(struct pcie_device *dev) { pciehp_disable_interrupt(dev); return 0; } static int pciehp_runtime_resume(struct pcie_device *dev) { struct controller *ctrl = get_service_data(dev); /* pci_restore_state() just wrote to the Slot Control register */ ctrl->cmd_started = jiffies; ctrl->cmd_busy = true; /* clear spurious events from rediscovery of inserted card */ if ((ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE) && pme_is_native(dev)) pcie_clear_hotplug_events(ctrl); return pciehp_resume(dev); } #endif /* PM */ static struct pcie_port_service_driver hpdriver_portdrv = { .name = "pciehp", .port_type = PCIE_ANY_PORT, .service = PCIE_PORT_SERVICE_HP, .probe = pciehp_probe, .remove = pciehp_remove, #ifdef CONFIG_PM #ifdef CONFIG_PM_SLEEP .suspend = pciehp_suspend, .resume_noirq = pciehp_resume_noirq, .resume = pciehp_resume, #endif .runtime_suspend = pciehp_runtime_suspend, .runtime_resume = pciehp_runtime_resume, #endif /* PM */ .slot_reset = pciehp_slot_reset, }; int __init pcie_hp_init(void) { int retval = 0; retval = pcie_port_service_register(&hpdriver_portdrv); pr_debug("pcie_port_service_register = %d\n", retval); if (retval) pr_debug("Failure to register service\n"); return retval; }