summaryrefslogtreecommitdiffstats
path: root/drivers/pci/hotplug/pciehp_hpc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/hotplug/pciehp_hpc.c')
-rw-r--r--drivers/pci/hotplug/pciehp_hpc.c276
1 files changed, 158 insertions, 118 deletions
diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
index 718b6073afad..5b15e76f3564 100644
--- a/drivers/pci/hotplug/pciehp_hpc.c
+++ b/drivers/pci/hotplug/pciehp_hpc.c
@@ -17,8 +17,9 @@
#include <linux/types.h>
#include <linux/signal.h>
#include <linux/jiffies.h>
-#include <linux/timer.h>
+#include <linux/kthread.h>
#include <linux/pci.h>
+#include <linux/pm_runtime.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <linux/slab.h>
@@ -31,47 +32,24 @@ static inline struct pci_dev *ctrl_dev(struct controller *ctrl)
return ctrl->pcie->port;
}
-static irqreturn_t pcie_isr(int irq, void *dev_id);
-static void start_int_poll_timer(struct controller *ctrl, int sec);
-
-/* This is the interrupt polling timeout function. */
-static void int_poll_timeout(struct timer_list *t)
-{
- struct controller *ctrl = from_timer(ctrl, t, poll_timer);
-
- /* Poll for interrupt events. regs == NULL => polling */
- pcie_isr(0, ctrl);
-
- if (!pciehp_poll_time)
- pciehp_poll_time = 2; /* default polling interval is 2 sec */
-
- start_int_poll_timer(ctrl, pciehp_poll_time);
-}
-
-/* This function starts the interrupt polling timer. */
-static void start_int_poll_timer(struct controller *ctrl, int sec)
-{
- /* Clamp to sane value */
- if ((sec <= 0) || (sec > 60))
- sec = 2;
-
- ctrl->poll_timer.expires = jiffies + sec * HZ;
- add_timer(&ctrl->poll_timer);
-}
+static irqreturn_t pciehp_isr(int irq, void *dev_id);
+static irqreturn_t pciehp_ist(int irq, void *dev_id);
+static int pciehp_poll(void *data);
static inline int pciehp_request_irq(struct controller *ctrl)
{
int retval, irq = ctrl->pcie->irq;
- /* Install interrupt polling timer. Start with 10 sec delay */
if (pciehp_poll_mode) {
- timer_setup(&ctrl->poll_timer, int_poll_timeout, 0);
- start_int_poll_timer(ctrl, 10);
- return 0;
+ ctrl->poll_thread = kthread_run(&pciehp_poll, ctrl,
+ "pciehp_poll-%s",
+ slot_name(ctrl->slot));
+ return PTR_ERR_OR_ZERO(ctrl->poll_thread);
}
/* Installs the interrupt handler */
- retval = request_irq(irq, pcie_isr, IRQF_SHARED, MY_NAME, ctrl);
+ retval = request_threaded_irq(irq, pciehp_isr, pciehp_ist,
+ IRQF_SHARED, MY_NAME, ctrl);
if (retval)
ctrl_err(ctrl, "Cannot get irq %d for the hotplug controller\n",
irq);
@@ -81,7 +59,7 @@ static inline int pciehp_request_irq(struct controller *ctrl)
static inline void pciehp_free_irq(struct controller *ctrl)
{
if (pciehp_poll_mode)
- del_timer_sync(&ctrl->poll_timer);
+ kthread_stop(ctrl->poll_thread);
else
free_irq(ctrl->pcie->irq, ctrl);
}
@@ -293,6 +271,11 @@ int pciehp_check_link_status(struct controller *ctrl)
found = pci_bus_check_dev(ctrl->pcie->port->subordinate,
PCI_DEVFN(0, 0));
+ /* ignore link or presence changes up to this point */
+ if (found)
+ atomic_and(~(PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC),
+ &ctrl->pending_events);
+
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status);
if ((lnk_status & PCI_EXP_LNKSTA_LT) ||
@@ -339,7 +322,9 @@ int pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot,
struct pci_dev *pdev = ctrl_dev(slot->ctrl);
u16 slot_ctrl;
+ pci_config_pm_runtime_get(pdev);
pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl);
+ pci_config_pm_runtime_put(pdev);
*status = (slot_ctrl & (PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC)) >> 6;
return 0;
}
@@ -350,7 +335,9 @@ void pciehp_get_attention_status(struct slot *slot, u8 *status)
struct pci_dev *pdev = ctrl_dev(ctrl);
u16 slot_ctrl;
+ pci_config_pm_runtime_get(pdev);
pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl);
+ pci_config_pm_runtime_put(pdev);
ctrl_dbg(ctrl, "%s: SLOTCTRL %x, value read %x\n", __func__,
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, slot_ctrl);
@@ -425,9 +412,12 @@ int pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot,
{
struct slot *slot = hotplug_slot->private;
struct controller *ctrl = slot->ctrl;
+ struct pci_dev *pdev = ctrl_dev(ctrl);
+ pci_config_pm_runtime_get(pdev);
pcie_write_cmd_nowait(ctrl, status << 6,
PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC);
+ pci_config_pm_runtime_put(pdev);
return 0;
}
@@ -539,20 +529,35 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id)
{
struct controller *ctrl = (struct controller *)dev_id;
struct pci_dev *pdev = ctrl_dev(ctrl);
- struct pci_bus *subordinate = pdev->subordinate;
- struct pci_dev *dev;
- struct slot *slot = ctrl->slot;
+ struct device *parent = pdev->dev.parent;
u16 status, events;
- u8 present;
- bool link;
- /* Interrupts cannot originate from a controller that's asleep */
+ /*
+ * Interrupts only occur in D3hot or shallower (PCIe r4.0, sec 6.7.3.4).
+ */
if (pdev->current_state == PCI_D3cold)
return IRQ_NONE;
+ /*
+ * Keep the port accessible by holding a runtime PM ref on its parent.
+ * Defer resume of the parent to the IRQ thread if it's suspended.
+ * Mask the interrupt until then.
+ */
+ if (parent) {
+ pm_runtime_get_noresume(parent);
+ if (!pm_runtime_active(parent)) {
+ pm_runtime_put(parent);
+ disable_irq_nosync(irq);
+ atomic_or(RERUN_ISR, &ctrl->pending_events);
+ return IRQ_WAKE_THREAD;
+ }
+ }
+
pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status);
if (status == (u16) ~0) {
ctrl_info(ctrl, "%s: no response from device\n", __func__);
+ if (parent)
+ pm_runtime_put(parent);
return IRQ_NONE;
}
@@ -571,86 +576,119 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id)
if (ctrl->power_fault_detected)
events &= ~PCI_EXP_SLTSTA_PFD;
- if (!events)
+ if (!events) {
+ if (parent)
+ pm_runtime_put(parent);
return IRQ_NONE;
-
- /* Capture link status before clearing interrupts */
- if (events & PCI_EXP_SLTSTA_DLLSC)
- link = pciehp_check_link_active(ctrl);
+ }
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, events);
ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events);
+ if (parent)
+ pm_runtime_put(parent);
- /* Check Command Complete Interrupt Pending */
+ /*
+ * Command Completed notifications are not deferred to the
+ * IRQ thread because it may be waiting for their arrival.
+ */
if (events & PCI_EXP_SLTSTA_CC) {
ctrl->cmd_busy = 0;
smp_mb();
wake_up(&ctrl->queue);
+
+ if (events == PCI_EXP_SLTSTA_CC)
+ return IRQ_HANDLED;
+
+ events &= ~PCI_EXP_SLTSTA_CC;
+ }
+
+ if (pdev->ignore_hotplug) {
+ ctrl_dbg(ctrl, "ignoring hotplug event %#06x\n", events);
+ return IRQ_HANDLED;
}
- if (subordinate) {
- list_for_each_entry(dev, &subordinate->devices, bus_list) {
- if (dev->ignore_hotplug) {
- ctrl_dbg(ctrl, "ignoring hotplug event %#06x (%s requested no hotplug)\n",
- events, pci_name(dev));
- return IRQ_HANDLED;
- }
+ /* Save pending events for consumption by IRQ thread. */
+ atomic_or(events, &ctrl->pending_events);
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t pciehp_ist(int irq, void *dev_id)
+{
+ struct controller *ctrl = (struct controller *)dev_id;
+ struct pci_dev *pdev = ctrl_dev(ctrl);
+ struct slot *slot = ctrl->slot;
+ irqreturn_t ret;
+ u32 events;
+
+ pci_config_pm_runtime_get(pdev);
+
+ /* rerun pciehp_isr() if the port was inaccessible on interrupt */
+ if (atomic_fetch_and(~RERUN_ISR, &ctrl->pending_events) & RERUN_ISR) {
+ ret = pciehp_isr(irq, dev_id);
+ enable_irq(irq);
+ if (ret != IRQ_WAKE_THREAD) {
+ pci_config_pm_runtime_put(pdev);
+ return ret;
}
}
+ synchronize_hardirq(irq);
+ events = atomic_xchg(&ctrl->pending_events, 0);
+ if (!events) {
+ pci_config_pm_runtime_put(pdev);
+ return IRQ_NONE;
+ }
+
/* Check Attention Button Pressed */
if (events & PCI_EXP_SLTSTA_ABP) {
ctrl_info(ctrl, "Slot(%s): Attention button pressed\n",
slot_name(slot));
- pciehp_queue_interrupt_event(slot, INT_BUTTON_PRESS);
+ pciehp_handle_button_press(slot);
}
/*
- * Check Link Status Changed at higher precedence than Presence
- * Detect Changed. The PDS value may be set to "card present" from
- * out-of-band detection, which may be in conflict with a Link Down
- * and cause the wrong event to queue.
+ * Disable requests have higher priority than Presence Detect Changed
+ * or Data Link Layer State Changed events.
*/
- if (events & PCI_EXP_SLTSTA_DLLSC) {
- ctrl_info(ctrl, "Slot(%s): Link %s\n", slot_name(slot),
- link ? "Up" : "Down");
- pciehp_queue_interrupt_event(slot, link ? INT_LINK_UP :
- INT_LINK_DOWN);
- } else if (events & PCI_EXP_SLTSTA_PDC) {
- present = !!(status & PCI_EXP_SLTSTA_PDS);
- ctrl_info(ctrl, "Slot(%s): Card %spresent\n", slot_name(slot),
- present ? "" : "not ");
- pciehp_queue_interrupt_event(slot, present ? INT_PRESENCE_ON :
- INT_PRESENCE_OFF);
- }
+ down_read(&ctrl->reset_lock);
+ if (events & DISABLE_SLOT)
+ pciehp_handle_disable_request(slot);
+ else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC))
+ pciehp_handle_presence_or_link_change(slot, events);
+ up_read(&ctrl->reset_lock);
/* Check Power Fault Detected */
if ((events & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) {
ctrl->power_fault_detected = 1;
ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(slot));
- pciehp_queue_interrupt_event(slot, INT_POWER_FAULT);
+ pciehp_set_attention_status(slot, 1);
+ pciehp_green_led_off(slot);
}
+ pci_config_pm_runtime_put(pdev);
+ wake_up(&ctrl->requester);
return IRQ_HANDLED;
}
-static irqreturn_t pcie_isr(int irq, void *dev_id)
+static int pciehp_poll(void *data)
{
- irqreturn_t rc, handled = IRQ_NONE;
+ struct controller *ctrl = data;
- /*
- * To guarantee that all interrupt events are serviced, we need to
- * re-inspect Slot Status register after clearing what is presumed
- * to be the last pending interrupt.
- */
- do {
- rc = pciehp_isr(irq, dev_id);
- if (rc == IRQ_HANDLED)
- handled = IRQ_HANDLED;
- } while (rc == IRQ_HANDLED);
+ schedule_timeout_idle(10 * HZ); /* start with 10 sec delay */
+
+ while (!kthread_should_stop()) {
+ /* poll for interrupt events or user requests */
+ while (pciehp_isr(IRQ_NOTCONNECTED, ctrl) == IRQ_WAKE_THREAD ||
+ atomic_read(&ctrl->pending_events))
+ pciehp_ist(IRQ_NOTCONNECTED, ctrl);
+
+ if (pciehp_poll_time <= 0 || pciehp_poll_time > 60)
+ pciehp_poll_time = 2; /* clamp to sane value */
+
+ schedule_timeout_idle(pciehp_poll_time * HZ);
+ }
- /* Return IRQ_HANDLED if we handled one or more events */
- return handled;
+ return 0;
}
static void pcie_enable_notification(struct controller *ctrl)
@@ -691,17 +729,6 @@ static void pcie_enable_notification(struct controller *ctrl)
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, cmd);
}
-void pcie_reenable_notification(struct controller *ctrl)
-{
- /*
- * Clear both Presence and Data Link Layer Changed to make sure
- * those events still fire after we have re-enabled them.
- */
- pcie_capability_write_word(ctrl->pcie->port, PCI_EXP_SLTSTA,
- PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC);
- pcie_enable_notification(ctrl);
-}
-
static void pcie_disable_notification(struct controller *ctrl)
{
u16 mask;
@@ -715,6 +742,12 @@ static void pcie_disable_notification(struct controller *ctrl)
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0);
}
+void pcie_clear_hotplug_events(struct controller *ctrl)
+{
+ pcie_capability_write_word(ctrl_dev(ctrl), PCI_EXP_SLTSTA,
+ PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC);
+}
+
/*
* pciehp has a 1:1 bus:slot relationship so we ultimately want a secondary
* bus reset of the bridge, but at the same time we want to ensure that it is
@@ -732,6 +765,8 @@ int pciehp_reset_slot(struct slot *slot, int probe)
if (probe)
return 0;
+ down_write(&ctrl->reset_lock);
+
if (!ATTN_BUTTN(ctrl)) {
ctrl_mask |= PCI_EXP_SLTCTL_PDCE;
stat_mask |= PCI_EXP_SLTSTA_PDC;
@@ -742,8 +777,6 @@ int pciehp_reset_slot(struct slot *slot, int probe)
pcie_write_cmd(ctrl, 0, ctrl_mask);
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0);
- if (pciehp_poll_mode)
- del_timer_sync(&ctrl->poll_timer);
pci_reset_bridge_secondary_bus(ctrl->pcie->port);
@@ -751,8 +784,8 @@ int pciehp_reset_slot(struct slot *slot, int probe)
pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask);
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask);
- if (pciehp_poll_mode)
- int_poll_timeout(&ctrl->poll_timer);
+
+ up_write(&ctrl->reset_lock);
return 0;
}
@@ -765,7 +798,7 @@ int pcie_init_notification(struct controller *ctrl)
return 0;
}
-static void pcie_shutdown_notification(struct controller *ctrl)
+void pcie_shutdown_notification(struct controller *ctrl)
{
if (ctrl->notification_enabled) {
pcie_disable_notification(ctrl);
@@ -776,32 +809,29 @@ static void pcie_shutdown_notification(struct controller *ctrl)
static int pcie_init_slot(struct controller *ctrl)
{
+ struct pci_bus *subordinate = ctrl_dev(ctrl)->subordinate;
struct slot *slot;
slot = kzalloc(sizeof(*slot), GFP_KERNEL);
if (!slot)
return -ENOMEM;
- slot->wq = alloc_ordered_workqueue("pciehp-%u", 0, PSN(ctrl));
- if (!slot->wq)
- goto abort;
+ down_read(&pci_bus_sem);
+ slot->state = list_empty(&subordinate->devices) ? OFF_STATE : ON_STATE;
+ up_read(&pci_bus_sem);
slot->ctrl = ctrl;
mutex_init(&slot->lock);
- mutex_init(&slot->hotplug_lock);
INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work);
ctrl->slot = slot;
return 0;
-abort:
- kfree(slot);
- return -ENOMEM;
}
static void pcie_cleanup_slot(struct controller *ctrl)
{
struct slot *slot = ctrl->slot;
- cancel_delayed_work(&slot->work);
- destroy_workqueue(slot->wq);
+
+ cancel_delayed_work_sync(&slot->work);
kfree(slot);
}
@@ -826,6 +856,7 @@ struct controller *pcie_init(struct pcie_device *dev)
{
struct controller *ctrl;
u32 slot_cap, link_cap;
+ u8 occupied, poweron;
struct pci_dev *pdev = dev->port;
ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
@@ -847,6 +878,8 @@ struct controller *pcie_init(struct pcie_device *dev)
ctrl->slot_cap = slot_cap;
mutex_init(&ctrl->ctrl_lock);
+ init_rwsem(&ctrl->reset_lock);
+ init_waitqueue_head(&ctrl->requester);
init_waitqueue_head(&ctrl->queue);
dbg_ctrl(ctrl);
@@ -855,16 +888,11 @@ struct controller *pcie_init(struct pcie_device *dev)
if (link_cap & PCI_EXP_LNKCAP_DLLLARC)
ctrl->link_active_reporting = 1;
- /*
- * Clear all remaining event bits in Slot Status register except
- * Presence Detect Changed. We want to make sure possible
- * hotplug event is triggered when the interrupt is unmasked so
- * that we don't lose that event.
- */
+ /* Clear all remaining event bits in Slot Status register. */
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD |
PCI_EXP_SLTSTA_MRLSC | PCI_EXP_SLTSTA_CC |
- PCI_EXP_SLTSTA_DLLSC);
+ PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC);
ctrl_info(ctrl, "Slot #%d AttnBtn%c PwrCtrl%c MRL%c AttnInd%c PwrInd%c HotPlug%c Surprise%c Interlock%c NoCompl%c LLActRep%c%s\n",
(slot_cap & PCI_EXP_SLTCAP_PSN) >> 19,
@@ -883,6 +911,19 @@ struct controller *pcie_init(struct pcie_device *dev)
if (pcie_init_slot(ctrl))
goto abort_ctrl;
+ /*
+ * If empty slot's power status is on, turn power off. The IRQ isn't
+ * requested yet, so avoid triggering a notification with this command.
+ */
+ if (POWER_CTRL(ctrl)) {
+ pciehp_get_adapter_status(ctrl->slot, &occupied);
+ pciehp_get_power_status(ctrl->slot, &poweron);
+ if (!occupied && poweron) {
+ pcie_disable_notification(ctrl);
+ pciehp_power_off_slot(ctrl->slot);
+ }
+ }
+
return ctrl;
abort_ctrl:
@@ -893,7 +934,6 @@ abort:
void pciehp_release_ctrl(struct controller *ctrl)
{
- pcie_shutdown_notification(ctrl);
pcie_cleanup_slot(ctrl);
kfree(ctrl);
}