summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/pci/hotplug/pciehp.h18
-rw-r--r--drivers/pci/hotplug/pciehp_core.c22
-rw-r--r--drivers/pci/hotplug/pciehp_ctrl.c99
-rw-r--r--drivers/pci/hotplug/pciehp_hpc.c14
4 files changed, 93 insertions, 60 deletions
diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h
index 1ba335d6563a..ed42dde5f9ac 100644
--- a/drivers/pci/hotplug/pciehp.h
+++ b/drivers/pci/hotplug/pciehp.h
@@ -105,6 +105,9 @@ struct slot {
* that has not yet been cleared by the user
* @pending_events: used by the IRQ handler to save events retrieved from the
* Slot Status register for later consumption by the IRQ thread
+ * @request_result: result of last user request submitted to the IRQ thread
+ * @requester: wait queue to wake up on completion of user request,
+ * used for synchronous slot enable/disable request via sysfs
*/
struct controller {
struct mutex ctrl_lock;
@@ -120,6 +123,8 @@ struct controller {
unsigned int notification_enabled:1;
unsigned int power_fault_detected;
atomic_t pending_events;
+ int request_result;
+ wait_queue_head_t requester;
};
/**
@@ -141,6 +146,17 @@ struct controller {
#define POWEROFF_STATE 4
#define ON_STATE 5
+/**
+ * DOC: Flags to request an action from the IRQ thread
+ *
+ * These are stored together with events read from the Slot Status register,
+ * hence must be greater than its 16-bit width.
+ *
+ * %DISABLE_SLOT: Disable the slot in response to a user request via sysfs or
+ * an Attention Button press after the 5 second delay
+ */
+#define DISABLE_SLOT (1 << 16)
+
#define ATTN_BUTTN(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_ABP)
#define POWER_CTRL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PCP)
#define MRL_SENS(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_MRLSP)
@@ -153,7 +169,9 @@ struct controller {
int pciehp_sysfs_enable_slot(struct slot *slot);
int pciehp_sysfs_disable_slot(struct slot *slot);
+void pciehp_request(struct controller *ctrl, int action);
void pciehp_handle_button_press(struct slot *slot);
+void pciehp_handle_disable_request(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);
diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c
index cde32e137f6c..b11f0db0695f 100644
--- a/drivers/pci/hotplug/pciehp_core.c
+++ b/drivers/pci/hotplug/pciehp_core.c
@@ -240,13 +240,19 @@ static int pciehp_probe(struct pcie_device *dev)
}
/* Check if slot is occupied */
+ mutex_lock(&slot->lock);
pciehp_get_adapter_status(slot, &occupied);
pciehp_get_power_status(slot, &poweron);
- if (occupied && pciehp_force)
- pciehp_enable_slot(slot);
+ if (pciehp_force &&
+ ((occupied && (slot->state == OFF_STATE ||
+ slot->state == BLINKINGON_STATE)) ||
+ (!occupied && (slot->state == ON_STATE ||
+ slot->state == BLINKINGOFF_STATE))))
+ pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
/* If empty slot's power status is on, turn power off */
if (!occupied && poweron && POWER_CTRL(ctrl))
pciehp_power_off_slot(slot);
+ mutex_unlock(&slot->lock);
return 0;
@@ -290,10 +296,14 @@ static int pciehp_resume(struct pcie_device *dev)
/* Check if slot is occupied */
pciehp_get_adapter_status(slot, &status);
- if (status)
- pciehp_enable_slot(slot);
- else
- pciehp_disable_slot(slot);
+ mutex_lock(&slot->lock);
+ if ((status && (slot->state == OFF_STATE ||
+ slot->state == BLINKINGON_STATE)) ||
+ (!status && (slot->state == ON_STATE ||
+ slot->state == BLINKINGOFF_STATE)))
+ pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
+ mutex_unlock(&slot->lock);
+
return 0;
}
#endif /* PM */
diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c
index 627e846df802..70bad847a450 100644
--- a/drivers/pci/hotplug/pciehp_ctrl.c
+++ b/drivers/pci/hotplug/pciehp_ctrl.c
@@ -122,22 +122,26 @@ static void remove_board(struct slot *p_slot)
pciehp_green_led_off(p_slot);
}
+void pciehp_request(struct controller *ctrl, int action)
+{
+ atomic_or(action, &ctrl->pending_events);
+ if (!pciehp_poll_mode)
+ irq_wake_thread(ctrl->pcie->irq, ctrl);
+}
+
void pciehp_queue_pushbutton_work(struct work_struct *work)
{
struct slot *p_slot = container_of(work, struct slot, work.work);
+ struct controller *ctrl = p_slot->ctrl;
mutex_lock(&p_slot->lock);
switch (p_slot->state) {
case BLINKINGOFF_STATE:
- p_slot->state = POWEROFF_STATE;
- mutex_unlock(&p_slot->lock);
- pciehp_disable_slot(p_slot);
- return;
+ pciehp_request(ctrl, DISABLE_SLOT);
+ break;
case BLINKINGON_STATE:
- p_slot->state = POWERON_STATE;
- mutex_unlock(&p_slot->lock);
- pciehp_enable_slot(p_slot);
- return;
+ pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
+ break;
default:
break;
}
@@ -186,16 +190,6 @@ void pciehp_handle_button_press(struct slot *p_slot)
ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n",
slot_name(p_slot));
break;
- case POWEROFF_STATE:
- case POWERON_STATE:
- /*
- * Ignore if the slot is on power-on or power-off state;
- * this means that the previous attention button action
- * to hot-add or hot-remove is undergoing
- */
- ctrl_info(ctrl, "Slot(%s): Button ignored\n",
- slot_name(p_slot));
- break;
default:
ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n",
slot_name(p_slot), p_slot->state);
@@ -204,6 +198,22 @@ void pciehp_handle_button_press(struct slot *p_slot)
mutex_unlock(&p_slot->lock);
}
+void pciehp_handle_disable_request(struct slot *slot)
+{
+ struct controller *ctrl = slot->ctrl;
+
+ mutex_lock(&slot->lock);
+ switch (slot->state) {
+ case BLINKINGON_STATE:
+ case BLINKINGOFF_STATE:
+ cancel_delayed_work(&slot->work);
+ }
+ slot->state = POWEROFF_STATE;
+ mutex_unlock(&slot->lock);
+
+ ctrl->request_result = pciehp_disable_slot(slot);
+}
+
void pciehp_handle_link_change(struct slot *p_slot)
{
struct controller *ctrl = p_slot->ctrl;
@@ -232,32 +242,6 @@ void pciehp_handle_link_change(struct slot *p_slot)
}
return;
break;
- case POWERON_STATE:
- 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_disable_slot(p_slot);
- return;
- }
- break;
- case POWEROFF_STATE:
- 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_enable_slot(p_slot);
- return;
- } else {
- ctrl_info(ctrl, "Slot(%s): Link Down event ignored; already powering off\n",
- slot_name(p_slot));
- }
- break;
default:
ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n",
slot_name(p_slot), p_slot->state);
@@ -272,6 +256,12 @@ void pciehp_handle_presence_change(struct slot *slot)
u8 present;
mutex_lock(&slot->lock);
+ switch (slot->state) {
+ case BLINKINGON_STATE:
+ case BLINKINGOFF_STATE:
+ cancel_delayed_work(&slot->work);
+ }
+
pciehp_get_adapter_status(slot, &present);
ctrl_info(ctrl, "Slot(%s): Card %spresent\n", slot_name(slot),
present ? "" : "not ");
@@ -279,7 +269,7 @@ void pciehp_handle_presence_change(struct slot *slot)
if (present) {
slot->state = POWERON_STATE;
mutex_unlock(&slot->lock);
- pciehp_enable_slot(slot);
+ ctrl->request_result = pciehp_enable_slot(slot);
} else {
slot->state = POWEROFF_STATE;
mutex_unlock(&slot->lock);
@@ -383,11 +373,17 @@ int pciehp_sysfs_enable_slot(struct slot *p_slot)
mutex_lock(&p_slot->lock);
switch (p_slot->state) {
case BLINKINGON_STATE:
- cancel_delayed_work(&p_slot->work);
case OFF_STATE:
- p_slot->state = POWERON_STATE;
mutex_unlock(&p_slot->lock);
- return pciehp_enable_slot(p_slot);
+ /*
+ * The IRQ thread becomes a no-op if the user pulls out the
+ * card before the thread wakes up, so initialize to -ENODEV.
+ */
+ ctrl->request_result = -ENODEV;
+ pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
+ wait_event(ctrl->requester,
+ !atomic_read(&ctrl->pending_events));
+ return ctrl->request_result;
case POWERON_STATE:
ctrl_info(ctrl, "Slot(%s): Already in powering on state\n",
slot_name(p_slot));
@@ -415,11 +411,12 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot)
mutex_lock(&p_slot->lock);
switch (p_slot->state) {
case BLINKINGOFF_STATE:
- cancel_delayed_work(&p_slot->work);
case ON_STATE:
- p_slot->state = POWEROFF_STATE;
mutex_unlock(&p_slot->lock);
- return pciehp_disable_slot(p_slot);
+ pciehp_request(ctrl, DISABLE_SLOT);
+ wait_event(ctrl->requester,
+ !atomic_read(&ctrl->pending_events));
+ return ctrl->request_result;
case POWEROFF_STATE:
ctrl_info(ctrl, "Slot(%s): Already in powering off state\n",
slot_name(p_slot));
diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
index 9c6a18da1af5..6951a0123e39 100644
--- a/drivers/pci/hotplug/pciehp_hpc.c
+++ b/drivers/pci/hotplug/pciehp_hpc.c
@@ -595,11 +595,16 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
}
/*
+ * Disable requests have higher priority than Presence Detect Changed
+ * or Data Link Layer State Changed events.
+ *
* 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.
*/
- if (events & PCI_EXP_SLTSTA_DLLSC)
+ if (events & DISABLE_SLOT)
+ pciehp_handle_disable_request(slot);
+ else if (events & PCI_EXP_SLTSTA_DLLSC)
pciehp_handle_link_change(slot);
else if (events & PCI_EXP_SLTSTA_PDC)
pciehp_handle_presence_change(slot);
@@ -612,6 +617,7 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
pciehp_green_led_off(slot);
}
+ wake_up(&ctrl->requester);
return IRQ_HANDLED;
}
@@ -625,8 +631,9 @@ static int pciehp_poll(void *data)
if (kthread_should_park())
kthread_parkme();
- /* poll for interrupt events */
- while (pciehp_isr(IRQ_NOTCONNECTED, ctrl) == IRQ_WAKE_THREAD)
+ /* poll for interrupt events or user requests */
+ while (pciehp_isr(IRQ_NOTCONNECTED, ctrl) == IRQ_WAKE_THREAD ||
+ atomic_read(&ctrl->pending_events))
pciehp_ist(IRQ_NOTCONNECTED, ctrl);
if (pciehp_poll_time <= 0 || pciehp_poll_time > 60)
@@ -830,6 +837,7 @@ struct controller *pcie_init(struct pcie_device *dev)
ctrl->slot_cap = slot_cap;
mutex_init(&ctrl->ctrl_lock);
+ init_waitqueue_head(&ctrl->requester);
init_waitqueue_head(&ctrl->queue);
dbg_ctrl(ctrl);