Commit 2df08822 authored by Bjorn Helgaas's avatar Bjorn Helgaas
Browse files

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
parents 093b9062 77adf935
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -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))
		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;
+5 −3
Original line number Diff line number Diff line
@@ -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);
+31 −5
Original line number Diff line number Diff line
@@ -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 */
};
+6 −4
Original line number Diff line number Diff line
@@ -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",
+52 −15
Original line number Diff line number Diff line
@@ -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,16 +199,28 @@ 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;

	pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
	ret = !!(lnk_status & PCI_EXP_LNKSTA_DLLLA);
	ret = pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
	if (ret == PCIBIOS_DEVICE_NOT_FOUND || lnk_status == (u16)~0)
		return -ENODEV;

	if (ret)
	ret = !!(lnk_status & PCI_EXP_LNKSTA_DLLLA);
	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;
}