pciehp: fix interrupt initialization
authorKenji Kaneshige <kaneshige.kenji@jp.fujitsu.com>
Fri, 20 Jun 2008 03:07:08 +0000 (12:07 +0900)
committerJesse Barnes <jbarnes@virtuousgeek.org>
Fri, 27 Jun 2008 20:00:43 +0000 (13:00 -0700)
Current pciehp driver's intialization sequence is as follows:

(1) initialize controller data structure
(2) install interrupt handler
(3) enable software notification
(4) initialize controller specific slot data structure
(5) initialize generic slot data structure and register it to pci hotplug core

The interrupt handler of pciehp assumes that controller specific slot
data structure is already initialized. However, it is installed at (2)
before initializing controller specific slot data structure at
(4). Because of this, pciehp driver cannot handle the following cases
properly.

- If devices that shares IRQ with pciehp raise interrupts between (2) and (4).
- If hotplug events (e.g. MRL open) happen between (3) and (4).

We already have a workaround for this problem ("pciehp: fix NULL
dereference in interrupt handler: dbd79aed1aea2bece0bf43cc2ff3b2f9baf48a08).
But we still need fundamental fix.

This patch fix the problem by changing the initilization sequence as follows:

(1) initialize controller data structure
(2) initialize controller specific slot data structure
(3) install interrupt handler
(4) enable software notification
(5) initialize generic slot data structure and register it to pci hotplug core

Signed-off-by: Kenji Kaneshige <kaneshige.kenji@jp.fujitsu.com>
Acked-by: Alex Chiang <achiang@hp.com>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
drivers/pci/hotplug/pciehp.h
drivers/pci/hotplug/pciehp_core.c
drivers/pci/hotplug/pciehp_hpc.c

index 8492fab..d17233a 100644 (file)
@@ -43,6 +43,7 @@ extern int pciehp_poll_mode;
 extern int pciehp_poll_time;
 extern int pciehp_debug;
 extern int pciehp_force;
+extern int pciehp_slot_with_bus;
 extern struct workqueue_struct *pciehp_wq;
 
 #define dbg(format, arg...)                                            \
@@ -156,10 +157,10 @@ extern u8 pciehp_handle_power_fault(struct slot *p_slot);
 extern int pciehp_configure_device(struct slot *p_slot);
 extern int pciehp_unconfigure_device(struct slot *p_slot);
 extern void pciehp_queue_pushbutton_work(struct work_struct *work);
-int pcie_init(struct controller *ctrl, struct pcie_device *dev);
+struct controller *pcie_init(struct pcie_device *dev);
 int pciehp_enable_slot(struct slot *p_slot);
 int pciehp_disable_slot(struct slot *p_slot);
-int pcie_init_hardware_part2(struct controller *ctrl, struct pcie_device *dev);
+int pcie_enable_notification(struct controller *ctrl);
 
 static inline struct slot *pciehp_find_slot(struct controller *ctrl, u8 device)
 {
index 7b21c86..d0fb569 100644 (file)
@@ -41,7 +41,7 @@ int pciehp_debug;
 int pciehp_poll_mode;
 int pciehp_poll_time;
 int pciehp_force;
-static int pciehp_slot_with_bus;
+int pciehp_slot_with_bus;
 struct workqueue_struct *pciehp_wq;
 
 #define DRIVER_VERSION "0.4"
@@ -183,23 +183,10 @@ static struct hotplug_slot_attribute hotplug_slot_attr_lock = {
  */
 static void release_slot(struct hotplug_slot *hotplug_slot)
 {
-       struct slot *slot = hotplug_slot->private;
-
        dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name);
 
-       kfree(slot->hotplug_slot->info);
-       kfree(slot->hotplug_slot);
-       kfree(slot);
-}
-
-static void make_slot_name(struct slot *slot)
-{
-       if (pciehp_slot_with_bus)
-               snprintf(slot->hotplug_slot->name, SLOT_NAME_SIZE, "%04d_%04d",
-                        slot->bus, slot->number);
-       else
-               snprintf(slot->hotplug_slot->name, SLOT_NAME_SIZE, "%d",
-                        slot->number);
+       kfree(hotplug_slot->info);
+       kfree(hotplug_slot);
 }
 
 static int init_slots(struct controller *ctrl)
@@ -208,44 +195,27 @@ static int init_slots(struct controller *ctrl)
        struct hotplug_slot *hotplug_slot;
        struct hotplug_slot_info *info;
        int retval = -ENOMEM;
-       int i;
-
-       for (i = 0; i < ctrl->num_slots; i++) {
-               slot = kzalloc(sizeof(*slot), GFP_KERNEL);
-               if (!slot)
-                       goto error;
 
+       list_for_each_entry(slot, &ctrl->slot_list, slot_list) {
                hotplug_slot = kzalloc(sizeof(*hotplug_slot), GFP_KERNEL);
                if (!hotplug_slot)
-                       goto error_slot;
-               slot->hotplug_slot = hotplug_slot;
+                       goto error;
 
                info = kzalloc(sizeof(*info), GFP_KERNEL);
                if (!info)
                        goto error_hpslot;
-               hotplug_slot->info = info;
-
-               hotplug_slot->name = slot->name;
-
-               slot->hp_slot = i;
-               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;
-               mutex_init(&slot->lock);
-               INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work);
 
                /* register this slot with the hotplug pci core */
+               hotplug_slot->info = info;
+               hotplug_slot->name = slot->name;
                hotplug_slot->private = slot;
                hotplug_slot->release = &release_slot;
-               make_slot_name(slot);
                hotplug_slot->ops = &pciehp_hotplug_slot_ops;
-
                get_power_status(hotplug_slot, &info->power_status);
                get_attention_status(hotplug_slot, &info->attention_status);
                get_latch_status(hotplug_slot, &info->latch_status);
                get_adapter_status(hotplug_slot, &info->adapter_status);
+               slot->hotplug_slot = hotplug_slot;
 
                dbg("Registering bus=%x dev=%x hp_slot=%x sun=%x "
                    "slot_device_offset=%x\n", slot->bus, slot->device,
@@ -271,8 +241,6 @@ static int init_slots(struct controller *ctrl)
                                goto error_info;
                        }
                }
-
-               list_add(&slot->slot_list, &ctrl->slot_list);
        }
 
        return 0;
@@ -280,27 +248,18 @@ error_info:
        kfree(info);
 error_hpslot:
        kfree(hotplug_slot);
-error_slot:
-       kfree(slot);
 error:
        return retval;
 }
 
 static void cleanup_slots(struct controller *ctrl)
 {
-       struct list_head *tmp;
-       struct list_head *next;
        struct slot *slot;
 
-       list_for_each_safe(tmp, next, &ctrl->slot_list) {
-               slot = list_entry(tmp, struct slot, slot_list);
-               list_del(&slot->slot_list);
+       list_for_each_entry(slot, &ctrl->slot_list, slot_list) {
                if (EMI(ctrl))
                        sysfs_remove_file(&slot->hotplug_slot->pci_slot->kobj,
                                &hotplug_slot_attr_lock.attr);
-               cancel_delayed_work(&slot->work);
-               flush_scheduled_work();
-               flush_workqueue(pciehp_wq);
                pci_hp_deregister(slot->hotplug_slot);
        }
 }
@@ -441,25 +400,13 @@ static int pciehp_probe(struct pcie_device *dev, const struct pcie_port_service_
        else if (pciehp_get_hp_hw_control_from_firmware(pdev))
                goto err_out_none;
 
-       ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
+       ctrl = pcie_init(dev);
        if (!ctrl) {
-               err("%s : out of memory\n", __func__);
-               goto err_out_none;
-       }
-       INIT_LIST_HEAD(&ctrl->slot_list);
-
-       rc = pcie_init(ctrl, dev);
-       if (rc) {
                dbg("%s: controller initialization failed\n", PCIE_MODULE_NAME);
-               goto err_out_free_ctrl;
+               goto err_out_none;
        }
-
        pci_set_drvdata(pdev, ctrl);
 
-       dbg("%s: ctrl bus=0x%x, device=%x, function=%x, irq=%x\n",
-           __func__, pdev->bus->number, PCI_SLOT(pdev->devfn),
-           PCI_FUNC(pdev->devfn), pdev->irq);
-
        /* Setup the slot information structures */
        rc = init_slots(ctrl);
        if (rc) {
@@ -492,8 +439,6 @@ err_out_free_ctrl_slot:
        cleanup_slots(ctrl);
 err_out_release_ctlr:
        ctrl->hpc_ops->release_ctlr(ctrl);
-err_out_free_ctrl:
-       kfree(ctrl);
 err_out_none:
        return -ENODEV;
 }
@@ -505,7 +450,6 @@ static void pciehp_remove (struct pcie_device *dev)
 
        cleanup_slots(ctrl);
        ctrl->hpc_ops->release_ctlr(ctrl);
-       kfree(ctrl);
 }
 
 #ifdef CONFIG_PM
@@ -525,7 +469,7 @@ static int pciehp_resume (struct pcie_device *dev)
                u8 status;
 
                /* reinitialize the chipset's event detection logic */
-               pcie_init_hardware_part2(ctrl, dev);
+               pcie_enable_notification(ctrl);
 
                t_slot = pciehp_find_slot(ctrl, ctrl->slot_device_offset);
 
index 59c2809..1ce5243 100644 (file)
@@ -609,23 +609,6 @@ static void hpc_set_green_led_blink(struct slot *slot)
            __func__, ctrl->cap_base + SLOTCTRL, slot_cmd);
 }
 
-static void hpc_release_ctlr(struct controller *ctrl)
-{
-       /* Mask Hot-plug Interrupt Enable */
-       if (pcie_write_cmd(ctrl, 0, HP_INTR_ENABLE | CMD_CMPL_INTR_ENABLE))
-               err("%s: Cannot mask hotplug interrupt enable\n", __func__);
-
-       /* Free interrupt handler or interrupt polling timer */
-       pciehp_free_irq(ctrl);
-
-       /*
-        * If this is the last controller to be released, destroy the
-        * pciehp work queue
-        */
-       if (atomic_dec_and_test(&pciehp_num_controllers))
-               destroy_workqueue(pciehp_wq);
-}
-
 static int hpc_power_on_slot(struct slot * slot)
 {
        struct controller *ctrl = slot->ctrl;
@@ -798,19 +781,7 @@ static irqreturn_t pcie_isr(int irq, void *dev_id)
        if (!(intr_loc & ~CMD_COMPLETED))
                return IRQ_HANDLED;
 
-       /*
-        * Return without handling events if this handler routine is
-        * called before controller initialization is done. This may
-        * happen if hotplug event or another interrupt that shares
-        * the IRQ with pciehp arrives before slot initialization is
-        * done after interrupt handler is registered.
-        *
-        * FIXME - Need more structural fixes. We need to be ready to
-        * handle the event before installing interrupt handler.
-        */
        p_slot = pciehp_find_slot(ctrl, ctrl->slot_device_offset);
-       if (!p_slot || !p_slot->hpc_ops)
-               return IRQ_HANDLED;
 
        /* Check MRL Sensor Changed */
        if (intr_loc & MRL_SENS_CHANGED)
@@ -987,6 +958,7 @@ static int hpc_get_cur_lnk_width(struct slot *slot,
        return retval;
 }
 
+static void pcie_release_ctrl(struct controller *ctrl);
 static struct hpc_ops pciehp_hpc_ops = {
        .power_on_slot                  = hpc_power_on_slot,
        .power_off_slot                 = hpc_power_off_slot,
@@ -1008,28 +980,11 @@ static struct hpc_ops pciehp_hpc_ops = {
        .green_led_off                  = hpc_set_green_led_off,
        .green_led_blink                = hpc_set_green_led_blink,
 
-       .release_ctlr                   = hpc_release_ctlr,
+       .release_ctlr                   = pcie_release_ctrl,
        .check_lnk_status               = hpc_check_lnk_status,
 };
 
-static int pcie_init_hardware_part1(struct controller *ctrl,
-                                   struct pcie_device *dev)
-{
-       /* Clear all remaining event bits in Slot Status register */
-       if (pciehp_writew(ctrl, SLOTSTATUS, 0x1f)) {
-               err("%s: Cannot write to SLOTSTATUS register\n", __func__);
-               return -1;
-       }
-
-       /* Mask Hot-plug Interrupt Enable */
-       if (pcie_write_cmd(ctrl, 0, HP_INTR_ENABLE | CMD_CMPL_INTR_ENABLE)) {
-               err("%s: Cannot mask hotplug interrupt enable\n", __func__);
-               return -1;
-       }
-       return 0;
-}
-
-int pcie_init_hardware_part2(struct controller *ctrl, struct pcie_device *dev)
+int pcie_enable_notification(struct controller *ctrl)
 {
        u16 cmd, mask;
 
@@ -1050,10 +1005,76 @@ int pcie_init_hardware_part2(struct controller *ctrl, struct pcie_device *dev)
                err("%s: Cannot enable software notification\n", __func__);
                return -1;
        }
+       return 0;
+}
+
+static void pcie_disable_notification(struct controller *ctrl)
+{
+       u16 mask;
+       mask = PRSN_DETECT_ENABLE | ATTN_BUTTN_ENABLE | MRL_DETECT_ENABLE |
+              PWR_FAULT_DETECT_ENABLE | HP_INTR_ENABLE | CMD_CMPL_INTR_ENABLE;
+       if (pcie_write_cmd(ctrl, 0, mask))
+               warn("%s: Cannot disable software notification\n", __func__);
+}
+
+static int pcie_init_notification(struct controller *ctrl)
+{
+       if (pciehp_request_irq(ctrl))
+               return -1;
+       if (pcie_enable_notification(ctrl)) {
+               pciehp_free_irq(ctrl);
+               return -1;
+       }
+       return 0;
+}
+
+static void pcie_shutdown_notification(struct controller *ctrl)
+{
+       pcie_disable_notification(ctrl);
+       pciehp_free_irq(ctrl);
+}
+
+static void make_slot_name(struct slot *slot)
+{
+       if (pciehp_slot_with_bus)
+               snprintf(slot->name, SLOT_NAME_SIZE, "%04d_%04d",
+                        slot->bus, slot->number);
+       else
+               snprintf(slot->name, SLOT_NAME_SIZE, "%d", slot->number);
+}
 
+static int pcie_init_slot(struct controller *ctrl)
+{
+       struct slot *slot;
+
+       slot = kzalloc(sizeof(*slot), GFP_KERNEL);
+       if (!slot)
+               return -ENOMEM;
+
+       slot->hp_slot = 0;
+       slot->ctrl = ctrl;
+       slot->bus = ctrl->pci_dev->subordinate->number;
+       slot->device = ctrl->slot_device_offset + slot->hp_slot;
+       slot->hpc_ops = ctrl->hpc_ops;
+       slot->number = ctrl->first_slot;
+       make_slot_name(slot);
+       mutex_init(&slot->lock);
+       INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work);
+       list_add(&slot->slot_list, &ctrl->slot_list);
        return 0;
 }
 
+static void pcie_cleanup_slot(struct controller *ctrl)
+{
+       struct slot *slot;
+       slot = list_first_entry(&ctrl->slot_list, struct slot, slot_list);
+       list_del(&slot->slot_list);
+       cancel_delayed_work(&slot->work);
+       flush_scheduled_work();
+       flush_workqueue(pciehp_wq);
+       kfree(slot);
+}
+
 static inline void dbg_ctrl(struct controller *ctrl)
 {
        int i;
@@ -1093,11 +1114,19 @@ static inline void dbg_ctrl(struct controller *ctrl)
        dbg("Slot Control           : 0x%04x\n", reg16);
 }
 
-int pcie_init(struct controller *ctrl, struct pcie_device *dev)
+struct controller *pcie_init(struct pcie_device *dev)
 {
+       struct controller *ctrl;
        u32 slot_cap;
        struct pci_dev *pdev = dev->port;
 
+       ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
+       if (!ctrl) {
+               err("%s : out of memory\n", __func__);
+               goto abort;
+       }
+       INIT_LIST_HEAD(&ctrl->slot_list);
+
        ctrl->pci_dev = pdev;
        ctrl->cap_base = pci_find_capability(pdev, PCI_CAP_ID_EXP);
        if (!ctrl->cap_base) {
@@ -1128,15 +1157,12 @@ int pcie_init(struct controller *ctrl, struct pcie_device *dev)
            !(POWER_CTRL(ctrl) | ATTN_LED(ctrl) | PWR_LED(ctrl) | EMI(ctrl)))
            ctrl->no_cmd_complete = 1;
 
-       info("HPC vendor_id %x device_id %x ss_vid %x ss_did %x\n",
-            pdev->vendor, pdev->device,
-            pdev->subsystem_vendor, pdev->subsystem_device);
+       /* Clear all remaining event bits in Slot Status register */
+       if (pciehp_writew(ctrl, SLOTSTATUS, 0x1f))
+               goto abort_ctrl;
 
-       if (pcie_init_hardware_part1(ctrl, dev))
-               goto abort;
-
-       if (pciehp_request_irq(ctrl))
-               goto abort;
+       /* Disable sotfware notification */
+       pcie_disable_notification(ctrl);
 
        /*
         * If this is the first controller to be initialized,
@@ -1144,18 +1170,39 @@ int pcie_init(struct controller *ctrl, struct pcie_device *dev)
         */
        if (atomic_add_return(1, &pciehp_num_controllers) == 1) {
                pciehp_wq = create_singlethread_workqueue("pciehpd");
-               if (!pciehp_wq) {
-                       goto abort_free_irq;
-               }
+               if (!pciehp_wq)
+                       goto abort_ctrl;
        }
 
-       if (pcie_init_hardware_part2(ctrl, dev))
-               goto abort_free_irq;
+       info("HPC vendor_id %x device_id %x ss_vid %x ss_did %x\n",
+            pdev->vendor, pdev->device,
+            pdev->subsystem_vendor, pdev->subsystem_device);
+
+       if (pcie_init_slot(ctrl))
+               goto abort_ctrl;
 
-       return 0;
+       if (pcie_init_notification(ctrl))
+               goto abort_slot;
 
-abort_free_irq:
-       pciehp_free_irq(ctrl);
+       return ctrl;
+
+abort_slot:
+       pcie_cleanup_slot(ctrl);
+abort_ctrl:
+       kfree(ctrl);
 abort:
-       return -1;
+       return NULL;
+}
+
+void pcie_release_ctrl(struct controller *ctrl)
+{
+       pcie_shutdown_notification(ctrl);
+       pcie_cleanup_slot(ctrl);
+       /*
+        * If this is the last controller to be released, destroy the
+        * pciehp work queue
+        */
+       if (atomic_dec_and_test(&pciehp_num_controllers))
+               destroy_workqueue(pciehp_wq);
+       kfree(ctrl);
 }