Blob Blame History Raw
From 0e94916e6091f48391b65110e71c87c583021640 Mon Sep 17 00:00:00 2001
From: Lukas Wunner <lukas@wunner.de>
Date: Thu, 19 Jul 2018 17:27:41 -0500
Subject: [PATCH] PCI: pciehp: Handle events synchronously
Git-commit: 0e94916e6091f48391b65110e71c87c583021640
Patch-mainline: v4.19
References: FATE#326303

Up until now, pciehp's IRQ handler schedules a work item for each event,
which in turn schedules a work item to enable or disable the slot.  This
double indirection was necessary because sleeping wasn't allowed in the
IRQ handler.

However it is now that pciehp has been converted to threaded IRQ handling
and polling, so handle events synchronously in pciehp_ist() and remove
the work item infrastructure (with the exception of work items to handle
a button press after the 5 second delay).

For link or presence change events, move the register read to determine
the current link or presence state behind acquisition of the slot lock
to prevent it from becoming stale while the lock is contended.

Signed-off-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
---
 drivers/pci/hotplug/pciehp.h      |   17 ---
 drivers/pci/hotplug/pciehp_ctrl.c |  189 ++++++++++++--------------------------
 drivers/pci/hotplug/pciehp_hpc.c  |   27 +----
 3 files changed, 72 insertions(+), 161 deletions(-)

--- a/drivers/pci/hotplug/pciehp.h
+++ b/drivers/pci/hotplug/pciehp.h
@@ -96,12 +96,6 @@ struct slot {
 	struct workqueue_struct *wq;
 };
 
-struct event_info {
-	u32 event_type;
-	struct slot *p_slot;
-	struct work_struct work;
-};
-
 /**
  * struct controller - PCIe hotplug controller
  * @ctrl_lock: serializes writes to the Slot Control register
@@ -143,13 +137,6 @@ struct controller {
 	atomic_t pending_events;
 };
 
-#define INT_PRESENCE_ON			1
-#define INT_PRESENCE_OFF		2
-#define INT_POWER_FAULT			3
-#define INT_BUTTON_PRESS		4
-#define INT_LINK_UP			5
-#define INT_LINK_DOWN			6
-
 #define STATIC_STATE			0
 #define BLINKINGON_STATE		1
 #define BLINKINGOFF_STATE		2
@@ -168,7 +155,9 @@ struct controller {
 
 int pciehp_sysfs_enable_slot(struct slot *slot);
 int pciehp_sysfs_disable_slot(struct slot *slot);
-void pciehp_queue_interrupt_event(struct slot *slot, u32 event_type);
+void pciehp_handle_button_press(struct slot *slot);
+void pciehp_handle_link_change(struct slot *slot);
+void pciehp_handle_presence_change(struct slot *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);
--- a/drivers/pci/hotplug/pciehp_ctrl.c
+++ b/drivers/pci/hotplug/pciehp_ctrl.c
@@ -35,24 +35,6 @@
 #include "../pci.h"
 #include "pciehp.h"
 
-static void interrupt_event_handler(struct work_struct *work);
-
-void pciehp_queue_interrupt_event(struct slot *p_slot, u32 event_type)
-{
-	struct event_info *info;
-
-	info = kmalloc(sizeof(*info), GFP_ATOMIC);
-	if (!info) {
-		ctrl_err(p_slot->ctrl, "dropped event %d (ENOMEM)\n", event_type);
-		return;
-	}
-
-	INIT_WORK(&info->work, interrupt_event_handler);
-	info->event_type = event_type;
-	info->p_slot = p_slot;
-	queue_work(p_slot->wq, &info->work);
-}
-
 /* The following routines constitute the bulk of the
    hotplug controller logic
  */
@@ -158,59 +140,6 @@ static int remove_board(struct slot *p_s
 	return 0;
 }
 
-struct power_work_info {
-	struct slot *p_slot;
-	struct work_struct work;
-	unsigned int req;
-#define DISABLE_REQ 0
-#define ENABLE_REQ  1
-};
-
-/**
- * pciehp_power_thread - handle pushbutton events
- * @work: &struct work_struct describing work to be done
- *
- * Scheduled procedure to handle blocking stuff for the pushbuttons.
- * Handles all pending events and exits.
- */
-static void pciehp_power_thread(struct work_struct *work)
-{
-	struct power_work_info *info =
-		container_of(work, struct power_work_info, work);
-	struct slot *p_slot = info->p_slot;
-
-	switch (info->req) {
-	case DISABLE_REQ:
-		pciehp_disable_slot(p_slot);
-		break;
-	case ENABLE_REQ:
-		pciehp_enable_slot(p_slot);
-		break;
-	default:
-		break;
-	}
-
-	kfree(info);
-}
-
-static void pciehp_queue_power_work(struct slot *p_slot, int req)
-{
-	struct power_work_info *info;
-
-	p_slot->state = (req == ENABLE_REQ) ? POWERON_STATE : POWEROFF_STATE;
-
-	info = kmalloc(sizeof(*info), GFP_KERNEL);
-	if (!info) {
-		ctrl_err(p_slot->ctrl, "no memory to queue %s request\n",
-			 (req == ENABLE_REQ) ? "poweron" : "poweroff");
-		return;
-	}
-	info->p_slot = p_slot;
-	INIT_WORK(&info->work, pciehp_power_thread);
-	info->req = req;
-	queue_work(p_slot->wq, &info->work);
-}
-
 void pciehp_queue_pushbutton_work(struct work_struct *work)
 {
 	struct slot *p_slot = container_of(work, struct slot, work.work);
@@ -218,27 +147,30 @@ void pciehp_queue_pushbutton_work(struct
 	mutex_lock(&p_slot->lock);
 	switch (p_slot->state) {
 	case BLINKINGOFF_STATE:
-		pciehp_queue_power_work(p_slot, DISABLE_REQ);
-		break;
+		p_slot->state = POWEROFF_STATE;
+		mutex_unlock(&p_slot->lock);
+		pciehp_disable_slot(p_slot);
+		return;
 	case BLINKINGON_STATE:
-		pciehp_queue_power_work(p_slot, ENABLE_REQ);
-		break;
+		p_slot->state = POWERON_STATE;
+		mutex_unlock(&p_slot->lock);
+		pciehp_enable_slot(p_slot);
+		return;
 	default:
 		break;
 	}
 	mutex_unlock(&p_slot->lock);
 }
 
-/*
- * Note: This function must be called with slot->lock held
- */
-static void handle_button_press_event(struct slot *p_slot)
+void pciehp_handle_button_press(struct slot *p_slot)
 {
 	struct controller *ctrl = p_slot->ctrl;
 	u8 getstatus;
 
+	mutex_lock(&p_slot->lock);
 	switch (p_slot->state) {
-	case STATIC_STATE:
+	case ON_STATE:
+	case OFF_STATE:
 		pciehp_get_power_status(p_slot, &getstatus);
 		if (getstatus) {
 			p_slot->state = BLINKINGOFF_STATE;
@@ -270,7 +202,6 @@ static void handle_button_press_event(st
 		pciehp_set_attention_status(p_slot, 0);
 		ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n",
 			  slot_name(p_slot));
-		p_slot->state = STATIC_STATE;
 		break;
 	case POWEROFF_STATE:
 	case POWERON_STATE:
@@ -287,39 +218,58 @@ static void handle_button_press_event(st
 			 slot_name(p_slot), p_slot->state);
 		break;
 	}
+	mutex_unlock(&p_slot->lock);
 }
 
-/*
- * Note: This function must be called with slot->lock held
- */
-static void handle_link_event(struct slot *p_slot, u32 event)
+void pciehp_handle_link_change(struct slot *p_slot)
 {
 	struct controller *ctrl = p_slot->ctrl;
+	bool link_active;
+
+	mutex_lock(&p_slot->lock);
+	link_active = pciehp_check_link_active(ctrl);
 
 	switch (p_slot->state) {
 	case BLINKINGON_STATE:
 	case BLINKINGOFF_STATE:
 		cancel_delayed_work(&p_slot->work);
 		/* Fall through */
-	case STATIC_STATE:
-		pciehp_queue_power_work(p_slot, event == INT_LINK_UP ?
-					ENABLE_REQ : DISABLE_REQ);
+	case ON_STATE:
+	case OFF_STATE:
+		if (link_active) {
+			p_slot->state = POWERON_STATE;
+			mutex_unlock(&p_slot->lock);
+			ctrl_info(ctrl, "Slot(%s): Link Up\n", slot_name(p_slot));
+			pciehp_enable_slot(p_slot);
+		} else {
+			p_slot->state = POWEROFF_STATE;
+			mutex_unlock(&p_slot->lock);
+			ctrl_info(ctrl, "Slot(%s): Link Down\n", slot_name(p_slot));
+			pciehp_disable_slot(p_slot);
+		}
+		return;
 		break;
 	case POWERON_STATE:
-		if (event == INT_LINK_UP) {
+		if (link_active) {
 			ctrl_info(ctrl, "Slot(%s): Link Up event ignored; already powering on\n",
 				  slot_name(p_slot));
 		} else {
+			p_slot->state = POWEROFF_STATE;
+			mutex_unlock(&p_slot->lock);
 			ctrl_info(ctrl, "Slot(%s): Link Down event queued; currently getting powered on\n",
 				  slot_name(p_slot));
-			pciehp_queue_power_work(p_slot, DISABLE_REQ);
+			pciehp_disable_slot(p_slot);
+			return;
 		}
 		break;
 	case POWEROFF_STATE:
-		if (event == INT_LINK_UP) {
+		if (link_active) {
+			p_slot->state = POWERON_STATE;
+			mutex_unlock(&p_slot->lock);
 			ctrl_info(ctrl, "Slot(%s): Link Up event queued; currently getting powered off\n",
 				  slot_name(p_slot));
-			pciehp_queue_power_work(p_slot, ENABLE_REQ);
+			pciehp_enable_slot(p_slot);
+			return;
 		} else {
 			ctrl_info(ctrl, "Slot(%s): Link Down event ignored; already powering off\n",
 				  slot_name(p_slot));
@@ -330,45 +280,28 @@ static void handle_link_event(struct slo
 			 slot_name(p_slot), p_slot->state);
 		break;
 	}
+	mutex_unlock(&p_slot->lock);
 }
 
-static void interrupt_event_handler(struct work_struct *work)
+void pciehp_handle_presence_change(struct slot *slot)
 {
-	struct event_info *info = container_of(work, struct event_info, work);
-	struct slot *p_slot = info->p_slot;
-	struct controller *ctrl = p_slot->ctrl;
+	struct controller *ctrl = slot->ctrl;
+	u8 present;
 
-	mutex_lock(&p_slot->lock);
-	switch (info->event_type) {
-	case INT_BUTTON_PRESS:
-		handle_button_press_event(p_slot);
-		break;
-	case INT_POWER_FAULT:
-		if (!POWER_CTRL(ctrl))
-			break;
-		pciehp_set_attention_status(p_slot, 1);
-		pciehp_green_led_off(p_slot);
-		break;
-	case INT_PRESENCE_ON:
-		pciehp_queue_power_work(p_slot, ENABLE_REQ);
-		break;
-	case INT_PRESENCE_OFF:
-		/*
-		 * Regardless of surprise capability, we need to
-		 * definitely remove a card that has been pulled out!
-		 */
-		pciehp_queue_power_work(p_slot, DISABLE_REQ);
-		break;
-	case INT_LINK_UP:
-	case INT_LINK_DOWN:
-		handle_link_event(p_slot, info->event_type);
-		break;
-	default:
-		break;
+	mutex_lock(&slot->lock);
+	pciehp_get_adapter_status(slot, &present);
+	ctrl_info(ctrl, "Slot(%s): Card %spresent\n", slot_name(slot),
+		  present ? "" : "not ");
+
+	if (present) {
+		slot->state = POWERON_STATE;
+		mutex_unlock(&slot->lock);
+		pciehp_enable_slot(slot);
+	} else {
+		slot->state = POWEROFF_STATE;
+		mutex_unlock(&slot->lock);
+		pciehp_disable_slot(slot);
 	}
-	mutex_unlock(&p_slot->lock);
-
-	kfree(info);
 }
 
 /*
@@ -470,7 +403,7 @@ int pciehp_sysfs_enable_slot(struct slot
 	switch (p_slot->state) {
 	case BLINKINGON_STATE:
 		cancel_delayed_work(&p_slot->work);
-	case STATIC_STATE:
+	case OFF_STATE:
 		p_slot->state = POWERON_STATE;
 		mutex_unlock(&p_slot->lock);
 		return pciehp_enable_slot(p_slot);
@@ -501,7 +434,7 @@ int pciehp_sysfs_disable_slot(struct slo
 	switch (p_slot->state) {
 	case BLINKINGOFF_STATE:
 		cancel_delayed_work(&p_slot->work);
-	case STATIC_STATE:
+	case ON_STATE:
 		p_slot->state = POWEROFF_STATE;
 		mutex_unlock(&p_slot->lock);
 		return pciehp_disable_slot(p_slot);
--- a/drivers/pci/hotplug/pciehp_hpc.c
+++ b/drivers/pci/hotplug/pciehp_hpc.c
@@ -596,8 +596,6 @@ static irqreturn_t pciehp_ist(int irq, v
 	struct controller *ctrl = (struct controller *)dev_id;
 	struct slot *slot = ctrl->slot;
 	u32 events;
-	u8 present;
-	bool link;
 
 	synchronize_hardirq(irq);
 	events = atomic_xchg(&ctrl->pending_events, 0);
@@ -608,34 +606,25 @@ static irqreturn_t pciehp_ist(int irq, v
 	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.
+	 * out-of-band detection, which may be in conflict with a Link Down.
 	 */
-	if (events & PCI_EXP_SLTSTA_DLLSC) {
-		link = pciehp_check_link_active(ctrl);
-		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) {
-		pciehp_get_adapter_status(slot, &present);
-		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);
-	}
+	if (events & PCI_EXP_SLTSTA_DLLSC)
+		pciehp_handle_link_change(slot);
+	else if (events & PCI_EXP_SLTSTA_PDC)
+		pciehp_handle_presence_change(slot);
 
 	/* 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);
 	}
 
 	return IRQ_HANDLED;