Blob Blame History Raw
From 11e87702be65780be92fb1f0a5b7b293954185f7 Mon Sep 17 00:00:00 2001
From: Lukas Wunner <lukas@wunner.de>
Date: Tue, 31 Jul 2018 07:50:37 +0200
Subject: [PATCH] PCI: pciehp: Differentiate between surprise and safe removal
Git-commit: 11e87702be65780be92fb1f0a5b7b293954185f7
Patch-mainline: v4.20-rc1
References: FATE#326303

When removing PCI devices below a hotplug bridge, pciehp marks them as
disconnected if the card is no longer present in the slot or it quiesces
them if the card is still present (by disabling INTx interrupts, bus
mastering and SERR# reporting).

To detect whether the card is still present, pciehp checks the Presence
Detect State bit in the Slot Status register.  The problem with this
approach is that even if the card is present, the link to it may be
down, and it that case it would be better to mark the devices as
disconnected instead of trying to quiesce them.  Moreover, if the card
in the slot was quickly replaced by another one, the Presence Detect
State bit would be set, yet trying to quiesce the new card's devices
would be wrong and the correct thing to do is to mark the previous
card's devices as disconnected.

Instead of looking at the Presence Detect State bit, it is better to
differentiate whether the card was surprise removed versus safely
removed (via sysfs or an Attention Button press).  On surprise removal,
the devices should be marked as disconnected, whereas on safe removal it
is correct to quiesce the devices.

The knowledge whether a surprise removal or a safe removal is at hand
does exist further up in the call stack:  A surprise removal is
initiated by pciehp_handle_presence_or_link_change(), a safe removal by
pciehp_handle_disable_request().

Pass that information down to pciehp_unconfigure_device() and use it in
lieu of the Presence Detect State bit.  While there, add kernel-doc to
pciehp_unconfigure_device() and pciehp_configure_device().

Tested-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Cc: Keith Busch <keith.busch@intel.com>
Signed-off-by: Oliver Neukum <oneukum@suse.com>
---
 drivers/pci/hotplug/pciehp.h      |    4 ++--
 drivers/pci/hotplug/pciehp_ctrl.c |   24 ++++++++++++++----------
 drivers/pci/hotplug/pciehp_pci.c  |   23 ++++++++++++++++++++---
 3 files changed, 36 insertions(+), 15 deletions(-)

--- a/drivers/pci/hotplug/pciehp.h
+++ b/drivers/pci/hotplug/pciehp.h
@@ -191,13 +191,13 @@ void pciehp_handle_button_press(struct s
 void pciehp_handle_disable_request(struct slot *slot);
 void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events);
 int pciehp_configure_device(struct slot *p_slot);
-int pciehp_unconfigure_device(struct slot *p_slot);
+int pciehp_unconfigure_device(struct slot *p_sloti, bool presence);
 void pciehp_queue_pushbutton_work(struct work_struct *work);
 struct controller *pcie_init(struct pcie_device *dev);
 int pcie_init_notification(struct controller *ctrl);
 void pcie_shutdown_notification(struct controller *ctrl);
 int pciehp_enable_slot(struct slot *p_slot);
-int pciehp_disable_slot(struct slot *p_slot);
+int pciehp_disable_slot(struct slot *p_slot, bool safe_removal);
 void pcie_clear_hotplug_events(struct controller *ctrl);
 int pciehp_power_on_slot(struct slot *slot);
 void pciehp_power_off_slot(struct slot *slot);
--- a/drivers/pci/hotplug/pciehp_ctrl.c
+++ b/drivers/pci/hotplug/pciehp_ctrl.c
@@ -39,6 +39,9 @@
    hotplug controller logic
  */
 
+#define SAFE_REMOVAL	 true
+#define SURPRISE_REMOVAL false
+
 static void set_slot_off(struct controller *ctrl, struct slot *pslot)
 {
 	/* turn off slot, turn on Amber LED, turn off Green LED if supported*/
@@ -114,13 +117,14 @@ err_exit:
 /**
  * remove_board - Turns off slot and LEDs
  * @p_slot: slot where board is being removed
+ * @safe_removal: whether the board is safely removed (versus surprise removed)
  */
-static int remove_board(struct slot *p_slot)
+static int remove_board(struct slot *p_slot, bool safe_removal)
 {
 	int retval;
 	struct controller *ctrl = p_slot->ctrl;
 
-	retval = pciehp_unconfigure_device(p_slot);
+	retval = pciehp_unconfigure_device(p_slot, safe_removal);
 	if (retval)
 		return retval;
 
@@ -231,7 +235,7 @@ void pciehp_handle_disable_request(struc
 	slot->state = POWEROFF_STATE;
 	mutex_unlock(&slot->lock);
 
-	ctrl->request_result = pciehp_disable_slot(slot);
+	ctrl->request_result = pciehp_disable_slot(slot, SAFE_REMOVAL);
 }
 
 void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events)
@@ -257,7 +261,7 @@ void pciehp_handle_presence_or_link_chan
 		if (events & PCI_EXP_SLTSTA_PDC)
 			ctrl_info(ctrl, "Slot(%s): Card not present\n",
 				  slot_name(slot));
-		pciehp_disable_slot(slot);
+		pciehp_disable_slot(slot, SURPRISE_REMOVAL);
 		break;
 	default:
 		mutex_unlock(&slot->lock);
@@ -338,7 +342,7 @@ int pciehp_enable_slot(struct slot *slot
 		pciehp_green_led_off(slot); /* may be blinking */
 
 	mutex_lock(&slot->lock);
-	slot->state = STATIC_STATE;
+	slot->state = ON_STATE;
 	mutex_unlock(&slot->lock);
 
 	return ret;
@@ -347,7 +351,7 @@ int pciehp_enable_slot(struct slot *slot
 /*
  * Note: This function must be called with slot->hotplug_lock held
  */
-static int __pciehp_disable_slot(struct slot *p_slot)
+static int __pciehp_disable_slot(struct slot *p_slot, bool safe_removal)
 {
 	u8 getstatus = 0;
 	struct controller *ctrl = p_slot->ctrl;
@@ -361,19 +365,19 @@ static int __pciehp_disable_slot(struct
 		}
 	}
 
-	return remove_board(p_slot);
+	return remove_board(p_slot, safe_removal);
 }
 
-int pciehp_disable_slot(struct slot *slot)
+int pciehp_disable_slot(struct slot *slot, bool safe_removal)
 {
 	int ret;
 
 	mutex_lock(&slot->hotplug_lock);
-	ret = __pciehp_disable_slot(slot);
+	ret = __pciehp_disable_slot(slot, safe_removal);
 	mutex_unlock(&slot->hotplug_lock);
 
 	mutex_lock(&slot->lock);
-	slot->state = STATIC_STATE;
+	slot->state = OFF_STATE;
 	mutex_unlock(&slot->lock);
 
 	return ret;
--- a/drivers/pci/hotplug/pciehp_pci.c
+++ b/drivers/pci/hotplug/pciehp_pci.c
@@ -34,6 +34,14 @@
 #include "../pci.h"
 #include "pciehp.h"
 
+/**
+ * pciehp_configure_device() - enumerate PCI devices below a hotplug bridge
+ * @p_slot: PCIe hotplug slot
+ *
+ * Enumerate PCI devices below a hotplug bridge and add them to the system.
+ * Return 0 on success, %-EEXIST if the devices are already enumerated or
+ * %-ENODEV if enumeration failed.
+ */
 int pciehp_configure_device(struct slot *p_slot)
 {
 	struct pci_dev *dev;
@@ -76,10 +84,20 @@ int pciehp_configure_device(struct slot
 	return ret;
 }
 
-int pciehp_unconfigure_device(struct slot *p_slot)
+/**
+ * pciehp_unconfigure_device() - remove PCI devices below a hotplug bridge
+ * @p_slot: PCIe hotplug slot
+ * @presence: whether the card is still present in the slot;
+ *     true for safe removal via sysfs or an Attention Button press,
+ *     false for surprise removal
+ *
+ * Unbind PCI devices below a hotplug bridge from their drivers and remove
+ * them from the system.  Safely removed devices are quiesced.  Surprise
+ * removed devices are marked as such to prevent further accesses.
+ */
+int pciehp_unconfigure_device(struct slot *p_slot, bool presence)
 {
 	int rc = 0;
-	u8 presence = 0;
 	struct pci_dev *dev, *temp;
 	struct pci_bus *parent = p_slot->ctrl->pcie->port->subordinate;
 	u16 command;
@@ -87,7 +105,6 @@ int pciehp_unconfigure_device(struct slo
 
 	ctrl_dbg(ctrl, "%s: domain:bus:dev = %04x:%02x:00\n",
 		 __func__, pci_domain_nr(parent), parent->number);
-	pciehp_get_adapter_status(p_slot, &presence);
 
 	if (!presence)
 		pci_walk_bus(parent, pci_dev_set_disconnected, NULL);