PCI: pciehp: Use link change notifications for hot-plug and removal
authorRajat Jain <rajatxjain@gmail.com>
Wed, 5 Feb 2014 02:29:10 +0000 (18:29 -0800)
committerBjorn Helgaas <bhelgaas@google.com>
Tue, 11 Feb 2014 01:12:44 +0000 (18:12 -0700)
A lot of systems do not have the fancy buttons and LEDs, and instead
want to rely only on the Link state change events to drive the hotplug
and removal state machinery.
(http://www.spinics.net/lists/hotplug/msg05802.html)

This patch adds support for that functionality. Here are the details
about the patch itself:

* Define and use interrupt events for linkup / linkdown.

* Make the pcie_isr() also look at link events, and direct control to
  corresponding (new) link state change handler function.

* Introduce the functions to handle link-up and link-down events and
  queue the add / removal work in the slot->wq to be processed by
  pciehp_power_thread()

As a side note, this patch also fixes the bug
https://bugzilla.kernel.org/show_bug.cgi?id=65521 "pciehp ignores Data Link
Layer State Changed bit."

Signed-off-by: Rajat Jain <rajatxjain@gmail.com>
Signed-off-by: Rajat Jain <rajatjain@juniper.net>
Signed-off-by: Guenter Roeck <groeck@juniper.net>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
drivers/pci/hotplug/pciehp.h
drivers/pci/hotplug/pciehp_ctrl.c
drivers/pci/hotplug/pciehp_hpc.c

index f9524592da0f09ad549aa8ae1c3cc3cad6df166b..d8d033619a77a9286d27c470327bfa32d239634c 100644 (file)
@@ -109,6 +109,8 @@ struct controller {
 #define INT_BUTTON_PRESS               7
 #define INT_BUTTON_RELEASE             8
 #define INT_BUTTON_CANCEL              9
+#define INT_LINK_UP                    10
+#define INT_LINK_DOWN                  11
 
 #define STATIC_STATE                   0
 #define BLINKINGON_STATE               1
@@ -132,6 +134,7 @@ u8 pciehp_handle_attention_button(struct slot *p_slot);
 u8 pciehp_handle_switch_change(struct slot *p_slot);
 u8 pciehp_handle_presence_change(struct slot *p_slot);
 u8 pciehp_handle_power_fault(struct slot *p_slot);
+void pciehp_handle_linkstate_change(struct slot *p_slot);
 int pciehp_configure_device(struct slot *p_slot);
 int pciehp_unconfigure_device(struct slot *p_slot);
 void pciehp_queue_pushbutton_work(struct work_struct *work);
index 50628487597deb2fe9de2b7499bc7dc253c7c388..56082842b2654c08940c97650379db9a7bfa02f2 100644 (file)
@@ -150,6 +150,27 @@ u8 pciehp_handle_power_fault(struct slot *p_slot)
        return 1;
 }
 
+void pciehp_handle_linkstate_change(struct slot *p_slot)
+{
+       u32 event_type;
+       struct controller *ctrl = p_slot->ctrl;
+
+       /* Link Status Change */
+       ctrl_dbg(ctrl, "Data Link Layer State change\n");
+
+       if (pciehp_check_link_active(ctrl)) {
+               ctrl_info(ctrl, "slot(%s): Link Up event\n",
+                         slot_name(p_slot));
+               event_type = INT_LINK_UP;
+       } else {
+               ctrl_info(ctrl, "slot(%s): Link Down event\n",
+                         slot_name(p_slot));
+               event_type = INT_LINK_DOWN;
+       }
+
+       queue_interrupt_event(p_slot, event_type);
+}
+
 /* The following routines constitute the bulk of the
    hotplug controller logic
  */
@@ -415,6 +436,69 @@ static void handle_surprise_event(struct slot *p_slot)
        queue_work(p_slot->wq, &info->work);
 }
 
+/*
+ * Note: This function must be called with slot->lock held
+ */
+static void handle_link_event(struct slot *p_slot, u32 event)
+{
+       struct controller *ctrl = p_slot->ctrl;
+       struct power_work_info *info;
+
+       info = kmalloc(sizeof(*info), GFP_KERNEL);
+       if (!info) {
+               ctrl_err(p_slot->ctrl, "%s: Cannot allocate memory\n",
+                        __func__);
+               return;
+       }
+       info->p_slot = p_slot;
+       INIT_WORK(&info->work, pciehp_power_thread);
+
+       switch (p_slot->state) {
+       case BLINKINGON_STATE:
+       case BLINKINGOFF_STATE:
+               cancel_delayed_work(&p_slot->work);
+               /* Fall through */
+       case STATIC_STATE:
+               p_slot->state = event == INT_LINK_UP ?
+                   POWERON_STATE : POWEROFF_STATE;
+               queue_work(p_slot->wq, &info->work);
+               break;
+       case POWERON_STATE:
+               if (event == INT_LINK_UP) {
+                       ctrl_info(ctrl,
+                                 "Link Up event ignored on slot(%s): already powering on\n",
+                                 slot_name(p_slot));
+                       kfree(info);
+               } else {
+                       ctrl_info(ctrl,
+                                 "Link Down event queued on slot(%s): currently getting powered on\n",
+                                 slot_name(p_slot));
+                       p_slot->state = POWEROFF_STATE;
+                       queue_work(p_slot->wq, &info->work);
+               }
+               break;
+       case POWEROFF_STATE:
+               if (event == INT_LINK_UP) {
+                       ctrl_info(ctrl,
+                                 "Link Up event queued on slot(%s): currently getting powered off\n",
+                                 slot_name(p_slot));
+                       p_slot->state = POWERON_STATE;
+                       queue_work(p_slot->wq, &info->work);
+               } else {
+                       ctrl_info(ctrl,
+                                 "Link Down event ignored on slot(%s): already powering off\n",
+                                 slot_name(p_slot));
+                       kfree(info);
+               }
+               break;
+       default:
+               ctrl_err(ctrl, "Not a valid state on slot(%s)\n",
+                        slot_name(p_slot));
+               kfree(info);
+               break;
+       }
+}
+
 static void interrupt_event_handler(struct work_struct *work)
 {
        struct event_info *info = container_of(work, struct event_info, work);
@@ -439,6 +523,10 @@ static void interrupt_event_handler(struct work_struct *work)
                ctrl_dbg(ctrl, "Surprise Removal\n");
                handle_surprise_event(p_slot);
                break;
+       case INT_LINK_UP:
+       case INT_LINK_DOWN:
+               handle_link_event(p_slot, info->event_type);
+               break;
        default:
                break;
        }
index aed6ab43cc13227091b710ff00882db53c6ec5ca..b413dce8f19436aed0963e45aecf1916f875bd89 100644 (file)
@@ -540,7 +540,7 @@ static irqreturn_t pcie_isr(int irq, void *dev_id)
 
                detected &= (PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD |
                             PCI_EXP_SLTSTA_MRLSC | PCI_EXP_SLTSTA_PDC |
-                            PCI_EXP_SLTSTA_CC);
+                            PCI_EXP_SLTSTA_CC | PCI_EXP_SLTSTA_DLLSC);
                detected &= ~intr_loc;
                intr_loc |= detected;
                if (!intr_loc)
@@ -579,6 +579,10 @@ static irqreturn_t pcie_isr(int irq, void *dev_id)
                ctrl->power_fault_detected = 1;
                pciehp_handle_power_fault(slot);
        }
+
+       if (intr_loc & PCI_EXP_SLTSTA_DLLSC)
+               pciehp_handle_linkstate_change(slot);
+
        return IRQ_HANDLED;
 }