diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index 305f8031eb01022e7010ae0b608434143c2236ef..d5f993d2f5f359c223553034ac8084643f67aeaf 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -145,6 +145,8 @@ 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; + int events = atomic_long_read(&work->data) & (PCI_EXP_SLTSTA_PDC | + PCI_EXP_SLTSTA_DLLSC | DISABLE_SLOT); mutex_lock(&p_slot->lock); switch (p_slot->state) { @@ -155,6 +157,12 @@ void pciehp_queue_pushbutton_work(struct work_struct *work) pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); break; default: + if (events) { + atomic_or(events, &ctrl->pending_events); + if (!pciehp_poll_mode) + irq_wake_thread(ctrl->pcie->irq, ctrl); + } else + slot_being_removed_rescanned = 0; break; } mutex_unlock(&p_slot->lock); @@ -180,6 +188,7 @@ void pciehp_handle_button_press(struct slot *p_slot) /* blink green LED and turn off amber */ pciehp_green_led_blink(p_slot); pciehp_set_attention_status(p_slot, 0); + atomic_long_set(&p_slot->work.work.data, 0); schedule_delayed_work(&p_slot->work, 5 * HZ); break; case BLINKINGOFF_STATE: @@ -201,10 +210,12 @@ void pciehp_handle_button_press(struct slot *p_slot) pciehp_set_attention_status(p_slot, 0); ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n", slot_name(p_slot)); + slot_being_removed_rescanned = 0; break; default: ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", slot_name(p_slot), p_slot->state); + slot_being_removed_rescanned = 0; break; } mutex_unlock(&p_slot->lock); @@ -225,6 +236,7 @@ void pciehp_handle_disable_request(struct slot *slot) mutex_unlock(&slot->lock); ctrl->request_result = pciehp_disable_slot(slot, SAFE_REMOVAL); + slot_being_removed_rescanned = 0; } void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) @@ -274,6 +286,7 @@ void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) link_active = pciehp_check_link_active(ctrl); if (!present && !link_active) { mutex_unlock(&slot->lock); + slot_being_removed_rescanned = 0; return; } @@ -296,6 +309,7 @@ void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) mutex_unlock(&slot->lock); break; } + slot_being_removed_rescanned = 0; } static int __pciehp_enable_slot(struct slot *p_slot) diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index f875f73c1f4a3bf5dcb45b9ac03dba0c34b2609a..600961c1e08b7f9f3e29564ef0788fd0154ef7ba 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -659,7 +659,17 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) if (events & PCI_EXP_SLTSTA_ABP) { ctrl_info(ctrl, "Slot(%s): Attention button pressed\n", slot_name(slot)); - pciehp_handle_button_press(slot); + if (!test_and_set_bit(0, &slot_being_removed_rescanned)) + pciehp_handle_button_press(slot); + else { + if (slot->state == BLINKINGOFF_STATE || + slot->state == BLINKINGON_STATE) + pciehp_handle_button_press(slot); + else + ctrl_info(ctrl, "Slot(%s): Slot operation failed because a remove or" + " rescan operation is under processing, please try later!\n", + slot_name(slot)); + } } /* Check Power Fault Detected */ @@ -675,10 +685,46 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) * or Data Link Layer State Changed events. */ down_read(&ctrl->reset_lock); - if (events & DISABLE_SLOT) - pciehp_handle_disable_request(slot); - else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) - pciehp_handle_presence_or_link_change(slot, events); + if (events & DISABLE_SLOT) { + if (!test_and_set_bit(0, &slot_being_removed_rescanned)) + pciehp_handle_disable_request(slot); + else { + if (slot->state == BLINKINGOFF_STATE || + slot->state == BLINKINGON_STATE) + pciehp_handle_disable_request(slot); + else { + /* + * we use the work_struct private data to store + * the event type + */ + atomic_long_set(&slot->work.work.data, + DISABLE_SLOT); + schedule_delayed_work(&slot->work, 3 * HZ); + } + } + } else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) { + if (!test_and_set_bit(0, &slot_being_removed_rescanned)) + pciehp_handle_presence_or_link_change(slot, events); + else { + if (slot->state == BLINKINGOFF_STATE || + slot->state == BLINKINGON_STATE) + pciehp_handle_presence_or_link_change(slot, + events); + else { + /* + * When we are removing or rescanning through + * sysfs, suprise link down/up happens. So we + * will handle this event 3 seconds later. + */ + ctrl_info(ctrl, "Slot(%s): Surprise link down/up in remove or rescan process!\n", + slot_name(slot)); + atomic_long_set(&slot->work.work.data, + events & (PCI_EXP_SLTSTA_PDC | + PCI_EXP_SLTSTA_DLLSC)); + schedule_delayed_work(&slot->work, 3 * HZ); + } + } + } up_read(&ctrl->reset_lock); pci_config_pm_runtime_put(pdev); diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 9ecfe13157c09660ec627076b470e118e794c405..f04d83031759eb934902d3458c9fe9b3c0597e78 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -474,8 +474,16 @@ static ssize_t remove_store(struct device *dev, struct device_attribute *attr, if (kstrtoul(buf, 0, &val) < 0) return -EINVAL; + if (test_and_set_bit(0, &slot_being_removed_rescanned)) { + pr_info("Slot is being removed or rescanned, please try later!\n"); + return -EINVAL; + } + if (val && device_remove_file_self(dev, attr)) pci_stop_and_remove_bus_device_locked(to_pci_dev(dev)); + + slot_being_removed_rescanned = 0; + return count; } static struct device_attribute dev_remove_attr = __ATTR(remove, diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index e9c6b120cf451331dc294f50a3ac1315cd37c2c3..6d15ad0488d21815dc8dec9b4ab5c6aebeac2442 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -3,6 +3,11 @@ #include #include "pci.h" +/* + * When a slot is being removed/rescanned, this flag is set. + */ +unsigned long slot_being_removed_rescanned; + static void pci_free_resources(struct pci_dev *dev) { int i; diff --git a/include/linux/pci.h b/include/linux/pci.h index c7f62371c94a9163e3696ad16f2a8d1e205935f1..3eeff0250dcfc1a9f03fd99756edd9597495700d 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -913,6 +913,9 @@ extern struct bus_type pci_bus_type; /* Do NOT directly access these two variables, unless you are arch-specific PCI * code, or PCI core code. */ extern struct list_head pci_root_buses; /* List of all known PCI buses */ + +extern unsigned long slot_being_removed_rescanned; + /* Some device drivers need know if PCI is initiated */ int no_pci_devices(void);