Merge branch 'pci/hotplug'

- Avoid returning prematurely from sysfs requests to enable or disable a
    PCIe hotplug slot (Lukas Wunner)

  - Don't disable interrupts twice when suspending hotplug ports (Mika
    Westerberg)

  - Fix deadlocks when PCIe ports are hot-removed while suspended (Mika
    Westerberg)

  - Fix boot-time Embedded Controller GPE storm caused by incorrect
    resource assignment after ACPI Bus Check Notification (Mika Westerberg)

* pci/hotplug:
  ACPI / hotplug / PCI: Allocate resources directly under the non-hotplug bridge
  PCI: pciehp: Prevent deadlock on disconnect
  PCI: pciehp: Do not disable interrupt twice on suspend
  PCI: pciehp: Refactor infinite loop in pcie_poll_cmd()
  PCI: pciehp: Avoid returning prematurely from sysfs requests
This commit is contained in:
Bjorn Helgaas 2019-11-28 08:54:31 -06:00
commit 2df08822a6
5 changed files with 103 additions and 30 deletions

View File

@ -449,8 +449,15 @@ static void acpiphp_native_scan_bridge(struct pci_dev *bridge)
/* Scan non-hotplug bridges that need to be reconfigured */
for_each_pci_bridge(dev, bus) {
if (!hotplug_is_native(dev))
max = pci_scan_bridge(bus, dev, max, 1);
if (hotplug_is_native(dev))
continue;
max = pci_scan_bridge(bus, dev, max, 1);
if (dev->subordinate) {
pcibios_resource_survey_bus(dev->subordinate);
pci_bus_size_bridges(dev->subordinate);
pci_bus_assign_resources(dev->subordinate);
}
}
}
@ -480,7 +487,6 @@ static void enable_slot(struct acpiphp_slot *slot, bool bridge)
if (PCI_SLOT(dev->devfn) == slot->device)
acpiphp_native_scan_bridge(dev);
}
pci_assign_unassigned_bridge_resources(bus->self);
} else {
LIST_HEAD(add_list);
int max, pass;

View File

@ -72,6 +72,7 @@ extern int pciehp_poll_time;
* @reset_lock: prevents access to the Data Link Layer Link Active bit in the
* Link Status register and to the Presence Detect State bit in the Slot
* Status register during a slot reset which may cause them to flap
* @ist_running: flag to keep user request waiting while IRQ thread is running
* @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
@ -101,6 +102,7 @@ struct controller {
struct hotplug_slot hotplug_slot; /* hotplug core interface */
struct rw_semaphore reset_lock;
unsigned int ist_running;
int request_result;
wait_queue_head_t requester;
};
@ -172,10 +174,10 @@ void pciehp_set_indicators(struct controller *ctrl, int pwr, int attn);
void pciehp_get_latch_status(struct controller *ctrl, u8 *status);
int pciehp_query_power_fault(struct controller *ctrl);
bool pciehp_card_present(struct controller *ctrl);
bool pciehp_card_present_or_link_active(struct controller *ctrl);
int pciehp_card_present(struct controller *ctrl);
int pciehp_card_present_or_link_active(struct controller *ctrl);
int pciehp_check_link_status(struct controller *ctrl);
bool pciehp_check_link_active(struct controller *ctrl);
int pciehp_check_link_active(struct controller *ctrl);
void pciehp_release_ctrl(struct controller *ctrl);
int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot);

View File

@ -139,10 +139,15 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
{
struct controller *ctrl = to_ctrl(hotplug_slot);
struct pci_dev *pdev = ctrl->pcie->port;
int ret;
pci_config_pm_runtime_get(pdev);
*value = pciehp_card_present_or_link_active(ctrl);
ret = pciehp_card_present_or_link_active(ctrl);
pci_config_pm_runtime_put(pdev);
if (ret < 0)
return ret;
*value = ret;
return 0;
}
@ -158,13 +163,13 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
*/
static void pciehp_check_presence(struct controller *ctrl)
{
bool occupied;
int occupied;
down_read(&ctrl->reset_lock);
mutex_lock(&ctrl->state_lock);
occupied = pciehp_card_present_or_link_active(ctrl);
if ((occupied && (ctrl->state == OFF_STATE ||
if ((occupied > 0 && (ctrl->state == OFF_STATE ||
ctrl->state == BLINKINGON_STATE)) ||
(!occupied && (ctrl->state == ON_STATE ||
ctrl->state == BLINKINGOFF_STATE)))
@ -253,7 +258,7 @@ static bool pme_is_native(struct pcie_device *dev)
return pcie_ports_native || host->native_pme;
}
static int pciehp_suspend(struct pcie_device *dev)
static void pciehp_disable_interrupt(struct pcie_device *dev)
{
/*
* Disable hotplug interrupt so that it does not trigger
@ -261,7 +266,19 @@ static int pciehp_suspend(struct pcie_device *dev)
*/
if (pme_is_native(dev))
pcie_disable_interrupt(get_service_data(dev));
}
#ifdef CONFIG_PM_SLEEP
static int pciehp_suspend(struct pcie_device *dev)
{
/*
* If the port is already runtime suspended we can keep it that
* way.
*/
if (dev_pm_smart_suspend_and_suspended(&dev->port->dev))
return 0;
pciehp_disable_interrupt(dev);
return 0;
}
@ -279,6 +296,7 @@ static int pciehp_resume_noirq(struct pcie_device *dev)
return 0;
}
#endif
static int pciehp_resume(struct pcie_device *dev)
{
@ -292,6 +310,12 @@ static int pciehp_resume(struct pcie_device *dev)
return 0;
}
static int pciehp_runtime_suspend(struct pcie_device *dev)
{
pciehp_disable_interrupt(dev);
return 0;
}
static int pciehp_runtime_resume(struct pcie_device *dev)
{
struct controller *ctrl = get_service_data(dev);
@ -318,10 +342,12 @@ static struct pcie_port_service_driver hpdriver_portdrv = {
.remove = pciehp_remove,
#ifdef CONFIG_PM
#ifdef CONFIG_PM_SLEEP
.suspend = pciehp_suspend,
.resume_noirq = pciehp_resume_noirq,
.resume = pciehp_resume,
.runtime_suspend = pciehp_suspend,
#endif
.runtime_suspend = pciehp_runtime_suspend,
.runtime_resume = pciehp_runtime_resume,
#endif /* PM */
};

View File

@ -226,7 +226,7 @@ void pciehp_handle_disable_request(struct controller *ctrl)
void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events)
{
bool present, link_active;
int present, link_active;
/*
* If the slot is on and presence or link has changed, turn it off.
@ -257,7 +257,7 @@ void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events)
mutex_lock(&ctrl->state_lock);
present = pciehp_card_present(ctrl);
link_active = pciehp_check_link_active(ctrl);
if (!present && !link_active) {
if (present <= 0 && link_active <= 0) {
mutex_unlock(&ctrl->state_lock);
return;
}
@ -375,7 +375,8 @@ int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot)
ctrl->request_result = -ENODEV;
pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
wait_event(ctrl->requester,
!atomic_read(&ctrl->pending_events));
!atomic_read(&ctrl->pending_events) &&
!ctrl->ist_running);
return ctrl->request_result;
case POWERON_STATE:
ctrl_info(ctrl, "Slot(%s): Already in powering on state\n",
@ -408,7 +409,8 @@ int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot)
mutex_unlock(&ctrl->state_lock);
pciehp_request(ctrl, DISABLE_SLOT);
wait_event(ctrl->requester,
!atomic_read(&ctrl->pending_events));
!atomic_read(&ctrl->pending_events) &&
!ctrl->ist_running);
return ctrl->request_result;
case POWEROFF_STATE:
ctrl_info(ctrl, "Slot(%s): Already in powering off state\n",

View File

@ -68,7 +68,7 @@ static int pcie_poll_cmd(struct controller *ctrl, int timeout)
struct pci_dev *pdev = ctrl_dev(ctrl);
u16 slot_status;
while (true) {
do {
pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status);
if (slot_status == (u16) ~0) {
ctrl_info(ctrl, "%s: no response from device\n",
@ -81,11 +81,9 @@ static int pcie_poll_cmd(struct controller *ctrl, int timeout)
PCI_EXP_SLTSTA_CC);
return 1;
}
if (timeout < 0)
break;
msleep(10);
timeout -= 10;
}
} while (timeout >= 0);
return 0; /* timeout */
}
@ -201,17 +199,29 @@ static void pcie_write_cmd_nowait(struct controller *ctrl, u16 cmd, u16 mask)
pcie_do_write_cmd(ctrl, cmd, mask, false);
}
bool pciehp_check_link_active(struct controller *ctrl)
/**
* pciehp_check_link_active() - Is the link active
* @ctrl: PCIe hotplug controller
*
* Check whether the downstream link is currently active. Note it is
* possible that the card is removed immediately after this so the
* caller may need to take it into account.
*
* If the hotplug controller itself is not available anymore returns
* %-ENODEV.
*/
int pciehp_check_link_active(struct controller *ctrl)
{
struct pci_dev *pdev = ctrl_dev(ctrl);
u16 lnk_status;
bool ret;
int ret;
ret = pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
if (ret == PCIBIOS_DEVICE_NOT_FOUND || lnk_status == (u16)~0)
return -ENODEV;
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
ret = !!(lnk_status & PCI_EXP_LNKSTA_DLLLA);
if (ret)
ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status);
ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status);
return ret;
}
@ -373,13 +383,29 @@ void pciehp_get_latch_status(struct controller *ctrl, u8 *status)
*status = !!(slot_status & PCI_EXP_SLTSTA_MRLSS);
}
bool pciehp_card_present(struct controller *ctrl)
/**
* pciehp_card_present() - Is the card present
* @ctrl: PCIe hotplug controller
*
* Function checks whether the card is currently present in the slot and
* in that case returns true. Note it is possible that the card is
* removed immediately after the check so the caller may need to take
* this into account.
*
* It the hotplug controller itself is not available anymore returns
* %-ENODEV.
*/
int pciehp_card_present(struct controller *ctrl)
{
struct pci_dev *pdev = ctrl_dev(ctrl);
u16 slot_status;
int ret;
pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status);
return slot_status & PCI_EXP_SLTSTA_PDS;
ret = pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status);
if (ret == PCIBIOS_DEVICE_NOT_FOUND || slot_status == (u16)~0)
return -ENODEV;
return !!(slot_status & PCI_EXP_SLTSTA_PDS);
}
/**
@ -390,10 +416,19 @@ bool pciehp_card_present(struct controller *ctrl)
* Presence Detect State bit, this helper also returns true if the Link Active
* bit is set. This is a concession to broken hotplug ports which hardwire
* Presence Detect State to zero, such as Wilocity's [1ae9:0200].
*
* Returns: %1 if the slot is occupied and %0 if it is not. If the hotplug
* port is not present anymore returns %-ENODEV.
*/
bool pciehp_card_present_or_link_active(struct controller *ctrl)
int pciehp_card_present_or_link_active(struct controller *ctrl)
{
return pciehp_card_present(ctrl) || pciehp_check_link_active(ctrl);
int ret;
ret = pciehp_card_present(ctrl);
if (ret)
return ret;
return pciehp_check_link_active(ctrl);
}
int pciehp_query_power_fault(struct controller *ctrl)
@ -583,6 +618,7 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
irqreturn_t ret;
u32 events;
ctrl->ist_running = true;
pci_config_pm_runtime_get(pdev);
/* rerun pciehp_isr() if the port was inaccessible on interrupt */
@ -629,6 +665,7 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
up_read(&ctrl->reset_lock);
pci_config_pm_runtime_put(pdev);
ctrl->ist_running = false;
wake_up(&ctrl->requester);
return IRQ_HANDLED;
}