pciehp: fix interrupt initialization

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>
diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h
index 8492fab..d17233a 100644
--- a/drivers/pci/hotplug/pciehp.h
+++ b/drivers/pci/hotplug/pciehp.h
@@ -43,6 +43,7 @@
 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 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)
 {
diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c
index 7b21c86..d0fb569 100644
--- a/drivers/pci/hotplug/pciehp_core.c
+++ b/drivers/pci/hotplug/pciehp_core.c
@@ -41,7 +41,7 @@
 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 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 @@
 	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 @@
 				goto error_info;
 			}
 		}
-
-		list_add(&slot->slot_list, &ctrl->slot_list);
 	}
 
 	return 0;
@@ -280,27 +248,18 @@
 	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 @@
 	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__);
+		dbg("%s: controller initialization failed\n", PCIE_MODULE_NAME);
 		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;
-	}
-
 	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 @@
 	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 @@
 
 	cleanup_slots(ctrl);
 	ctrl->hpc_ops->release_ctlr(ctrl);
-	kfree(ctrl);
 }
 
 #ifdef CONFIG_PM
@@ -525,7 +469,7 @@
 		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);
 
diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
index 59c2809..1ce5243 100644
--- a/drivers/pci/hotplug/pciehp_hpc.c
+++ b/drivers/pci/hotplug/pciehp_hpc.c
@@ -609,23 +609,6 @@
 	    __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 @@
 	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 @@
 	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 @@
 	.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 @@
 		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 @@
 	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 @@
 	    !(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 @@
 	 */
 	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);
 
-	return 0;
+	if (pcie_init_slot(ctrl))
+		goto abort_ctrl;
 
-abort_free_irq:
-	pciehp_free_irq(ctrl);
+	if (pcie_init_notification(ctrl))
+		goto abort_slot;
+
+	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);
 }