Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7d8c4a2c authored by Bjorn Helgaas's avatar Bjorn Helgaas
Browse files

Merge branch 'pci/aw-reset-v5' into next

* pci/aw-reset-v5:
  PCI: Add pci_probe_reset_slot() and pci_probe_reset_bus()
  PCI: Remove aer_do_secondary_bus_reset()
  PCI: Tune secondary bus reset timing
  PCI: Wake-up devices before saving config space for reset
  PCI: Add pci_reset_slot() and pci_reset_bus()
  PCI: Split out pci_dev lock/unlock and save/restore
  PCI: Add slot reset option to pci_dev_reset()
  PCI: pciehp: Add reset_slot() method
  PCI: Add hotplug_slot_ops.reset_slot()
  PCI: Add pci_reset_bridge_secondary_bus()
parents 63ef4181 9a3d2b9b
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -155,6 +155,7 @@ void pciehp_green_led_off(struct slot *slot);
void pciehp_green_led_blink(struct slot *slot);
void pciehp_green_led_blink(struct slot *slot);
int pciehp_check_link_status(struct controller *ctrl);
int pciehp_check_link_status(struct controller *ctrl);
void pciehp_release_ctrl(struct controller *ctrl);
void pciehp_release_ctrl(struct controller *ctrl);
int pciehp_reset_slot(struct slot *slot, int probe);


static inline const char *slot_name(struct slot *slot)
static inline const char *slot_name(struct slot *slot)
{
{
+12 −0
Original line number Original line Diff line number Diff line
@@ -69,6 +69,7 @@ static int get_power_status (struct hotplug_slot *slot, u8 *value);
static int get_attention_status	(struct hotplug_slot *slot, u8 *value);
static int get_attention_status	(struct hotplug_slot *slot, u8 *value);
static int get_latch_status	(struct hotplug_slot *slot, u8 *value);
static int get_latch_status	(struct hotplug_slot *slot, u8 *value);
static int get_adapter_status	(struct hotplug_slot *slot, u8 *value);
static int get_adapter_status	(struct hotplug_slot *slot, u8 *value);
static int reset_slot		(struct hotplug_slot *slot, int probe);


/**
/**
 * release_slot - free up the memory used by a slot
 * release_slot - free up the memory used by a slot
@@ -111,6 +112,7 @@ static int init_slot(struct controller *ctrl)
	ops->disable_slot = disable_slot;
	ops->disable_slot = disable_slot;
	ops->get_power_status = get_power_status;
	ops->get_power_status = get_power_status;
	ops->get_adapter_status = get_adapter_status;
	ops->get_adapter_status = get_adapter_status;
	ops->reset_slot = reset_slot;
	if (MRL_SENS(ctrl))
	if (MRL_SENS(ctrl))
		ops->get_latch_status = get_latch_status;
		ops->get_latch_status = get_latch_status;
	if (ATTN_LED(ctrl)) {
	if (ATTN_LED(ctrl)) {
@@ -223,6 +225,16 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
	return pciehp_get_adapter_status(slot, value);
	return pciehp_get_adapter_status(slot, value);
}
}


static int reset_slot(struct hotplug_slot *hotplug_slot, int probe)
{
	struct slot *slot = hotplug_slot->private;

	ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n",
		 __func__, slot_name(slot));

	return pciehp_reset_slot(slot, probe);
}

static int pciehp_probe(struct pcie_device *dev)
static int pciehp_probe(struct pcie_device *dev)
{
{
	int rc;
	int rc;
+31 −0
Original line number Original line Diff line number Diff line
@@ -749,6 +749,37 @@ static void pcie_disable_notification(struct controller *ctrl)
		ctrl_warn(ctrl, "Cannot disable software notification\n");
		ctrl_warn(ctrl, "Cannot disable software notification\n");
}
}


/*
 * pciehp has a 1:1 bus:slot relationship so we ultimately want a secondary
 * bus reset of the bridge, but if the slot supports surprise removal we need
 * to disable presence detection around the bus reset and clear any spurious
 * events after.
 */
int pciehp_reset_slot(struct slot *slot, int probe)
{
	struct controller *ctrl = slot->ctrl;

	if (probe)
		return 0;

	if (HP_SUPR_RM(ctrl)) {
		pcie_write_cmd(ctrl, 0, PCI_EXP_SLTCTL_PDCE);
		if (pciehp_poll_mode)
			del_timer_sync(&ctrl->poll_timer);
	}

	pci_reset_bridge_secondary_bus(ctrl->pcie->port);

	if (HP_SUPR_RM(ctrl)) {
		pciehp_writew(ctrl, PCI_EXP_SLTSTA, PCI_EXP_SLTSTA_PDC);
		pcie_write_cmd(ctrl, PCI_EXP_SLTCTL_PDCE, PCI_EXP_SLTCTL_PDCE);
		if (pciehp_poll_mode)
			int_poll_timeout(ctrl->poll_timer.data);
	}

	return 0;
}

int pcie_init_notification(struct controller *ctrl)
int pcie_init_notification(struct controller *ctrl)
{
{
	if (pciehp_request_irq(ctrl))
	if (pciehp_request_irq(ctrl))
+347 −26
Original line number Original line Diff line number Diff line
@@ -22,6 +22,7 @@
#include <linux/interrupt.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/device.h>
#include <linux/pm_runtime.h>
#include <linux/pm_runtime.h>
#include <linux/pci_hotplug.h>
#include <asm-generic/pci-bridge.h>
#include <asm-generic/pci-bridge.h>
#include <asm/setup.h>
#include <asm/setup.h>
#include "pci.h"
#include "pci.h"
@@ -3288,9 +3289,42 @@ static int pci_pm_reset(struct pci_dev *dev, int probe)
	return 0;
	return 0;
}
}


static int pci_parent_bus_reset(struct pci_dev *dev, int probe)
/**
 * pci_reset_bridge_secondary_bus - Reset the secondary bus on a PCI bridge.
 * @dev: Bridge device
 *
 * Use the bridge control register to assert reset on the secondary bus.
 * Devices on the secondary bus are left in power-on state.
 */
void pci_reset_bridge_secondary_bus(struct pci_dev *dev)
{
{
	u16 ctrl;
	u16 ctrl;

	pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &ctrl);
	ctrl |= PCI_BRIDGE_CTL_BUS_RESET;
	pci_write_config_word(dev, PCI_BRIDGE_CONTROL, ctrl);
	/*
	 * PCI spec v3.0 7.6.4.2 requires minimum Trst of 1ms.  Double
	 * this to 2ms to ensure that we meet the minium requirement.
	 */
	msleep(2);

	ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET;
	pci_write_config_word(dev, PCI_BRIDGE_CONTROL, ctrl);

	/*
	 * Trhfa for conventional PCI is 2^25 clock cycles.
	 * Assuming a minimum 33MHz clock this results in a 1s
	 * delay before we can consider subordinate devices to
	 * be re-initialized.  PCIe has some ways to shorten this,
	 * but we don't make use of them yet.
	 */
	ssleep(1);
}
EXPORT_SYMBOL_GPL(pci_reset_bridge_secondary_bus);

static int pci_parent_bus_reset(struct pci_dev *dev, int probe)
{
	struct pci_dev *pdev;
	struct pci_dev *pdev;


	if (pci_is_root_bus(dev->bus) || dev->subordinate || !dev->bus->self)
	if (pci_is_root_bus(dev->bus) || dev->subordinate || !dev->bus->self)
@@ -3303,18 +3337,40 @@ static int pci_parent_bus_reset(struct pci_dev *dev, int probe)
	if (probe)
	if (probe)
		return 0;
		return 0;


	pci_read_config_word(dev->bus->self, PCI_BRIDGE_CONTROL, &ctrl);
	pci_reset_bridge_secondary_bus(dev->bus->self);
	ctrl |= PCI_BRIDGE_CTL_BUS_RESET;
	pci_write_config_word(dev->bus->self, PCI_BRIDGE_CONTROL, ctrl);
	msleep(100);

	ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET;
	pci_write_config_word(dev->bus->self, PCI_BRIDGE_CONTROL, ctrl);
	msleep(100);


	return 0;
	return 0;
}
}


static int pci_reset_hotplug_slot(struct hotplug_slot *hotplug, int probe)
{
	int rc = -ENOTTY;

	if (!hotplug || !try_module_get(hotplug->ops->owner))
		return rc;

	if (hotplug->ops->reset_slot)
		rc = hotplug->ops->reset_slot(hotplug, probe);

	module_put(hotplug->ops->owner);

	return rc;
}

static int pci_dev_reset_slot_function(struct pci_dev *dev, int probe)
{
	struct pci_dev *pdev;

	if (dev->subordinate || !dev->slot)
		return -ENOTTY;

	list_for_each_entry(pdev, &dev->bus->devices, bus_list)
		if (pdev != dev && pdev->slot == dev->slot)
			return -ENOTTY;

	return pci_reset_hotplug_slot(dev->slot->hotplug, probe);
}

static int __pci_dev_reset(struct pci_dev *dev, int probe)
static int __pci_dev_reset(struct pci_dev *dev, int probe)
{
{
	int rc;
	int rc;
@@ -3337,27 +3393,65 @@ static int __pci_dev_reset(struct pci_dev *dev, int probe)
	if (rc != -ENOTTY)
	if (rc != -ENOTTY)
		goto done;
		goto done;


	rc = pci_dev_reset_slot_function(dev, probe);
	if (rc != -ENOTTY)
		goto done;

	rc = pci_parent_bus_reset(dev, probe);
	rc = pci_parent_bus_reset(dev, probe);
done:
done:
	return rc;
	return rc;
}
}


static int pci_dev_reset(struct pci_dev *dev, int probe)
static void pci_dev_lock(struct pci_dev *dev)
{
{
	int rc;

	if (!probe) {
	pci_cfg_access_lock(dev);
	pci_cfg_access_lock(dev);
	/* block PM suspend, driver probe, etc. */
	/* block PM suspend, driver probe, etc. */
	device_lock(&dev->dev);
	device_lock(&dev->dev);
}
}


	rc = __pci_dev_reset(dev, probe);
static void pci_dev_unlock(struct pci_dev *dev)

{
	if (!probe) {
	device_unlock(&dev->dev);
	device_unlock(&dev->dev);
	pci_cfg_access_unlock(dev);
	pci_cfg_access_unlock(dev);
}
}

static void pci_dev_save_and_disable(struct pci_dev *dev)
{
	/*
	 * Wake-up device prior to save.  PM registers default to D0 after
	 * reset and a simple register restore doesn't reliably return
	 * to a non-D0 state anyway.
	 */
	pci_set_power_state(dev, PCI_D0);

	pci_save_state(dev);
	/*
	 * Disable the device by clearing the Command register, except for
	 * INTx-disable which is set.  This not only disables MMIO and I/O port
	 * BARs, but also prevents the device from being Bus Master, preventing
	 * DMA from the device including MSI/MSI-X interrupts.  For PCI 2.3
	 * compliant devices, INTx-disable prevents legacy interrupts.
	 */
	pci_write_config_word(dev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE);
}

static void pci_dev_restore(struct pci_dev *dev)
{
	pci_restore_state(dev);
}

static int pci_dev_reset(struct pci_dev *dev, int probe)
{
	int rc;

	if (!probe)
		pci_dev_lock(dev);

	rc = __pci_dev_reset(dev, probe);

	if (!probe)
		pci_dev_unlock(dev);

	return rc;
	return rc;
}
}
/**
/**
@@ -3448,21 +3542,248 @@ int pci_reset_function(struct pci_dev *dev)
	if (rc)
	if (rc)
		return rc;
		return rc;


	pci_save_state(dev);
	pci_dev_save_and_disable(dev);

	rc = pci_dev_reset(dev, 0);

	pci_dev_restore(dev);

	return rc;
}
EXPORT_SYMBOL_GPL(pci_reset_function);

/* Lock devices from the top of the tree down */
static void pci_bus_lock(struct pci_bus *bus)
{
	struct pci_dev *dev;

	list_for_each_entry(dev, &bus->devices, bus_list) {
		pci_dev_lock(dev);
		if (dev->subordinate)
			pci_bus_lock(dev->subordinate);
	}
}

/* Unlock devices from the bottom of the tree up */
static void pci_bus_unlock(struct pci_bus *bus)
{
	struct pci_dev *dev;

	list_for_each_entry(dev, &bus->devices, bus_list) {
		if (dev->subordinate)
			pci_bus_unlock(dev->subordinate);
		pci_dev_unlock(dev);
	}
}

/* Lock devices from the top of the tree down */
static void pci_slot_lock(struct pci_slot *slot)
{
	struct pci_dev *dev;

	list_for_each_entry(dev, &slot->bus->devices, bus_list) {
		if (!dev->slot || dev->slot != slot)
			continue;
		pci_dev_lock(dev);
		if (dev->subordinate)
			pci_bus_lock(dev->subordinate);
	}
}

/* Unlock devices from the bottom of the tree up */
static void pci_slot_unlock(struct pci_slot *slot)
{
	struct pci_dev *dev;

	list_for_each_entry(dev, &slot->bus->devices, bus_list) {
		if (!dev->slot || dev->slot != slot)
			continue;
		if (dev->subordinate)
			pci_bus_unlock(dev->subordinate);
		pci_dev_unlock(dev);
	}
}

/* Save and disable devices from the top of the tree down */
static void pci_bus_save_and_disable(struct pci_bus *bus)
{
	struct pci_dev *dev;

	list_for_each_entry(dev, &bus->devices, bus_list) {
		pci_dev_save_and_disable(dev);
		if (dev->subordinate)
			pci_bus_save_and_disable(dev->subordinate);
	}
}


/*
/*
	 * both INTx and MSI are disabled after the Interrupt Disable bit
 * Restore devices from top of the tree down - parent bridges need to be
	 * is set and the Bus Master bit is cleared.
 * restored before we can get to subordinate devices.
 */
 */
	pci_write_config_word(dev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE);
static void pci_bus_restore(struct pci_bus *bus)
{
	struct pci_dev *dev;


	rc = pci_dev_reset(dev, 0);
	list_for_each_entry(dev, &bus->devices, bus_list) {
		pci_dev_restore(dev);
		if (dev->subordinate)
			pci_bus_restore(dev->subordinate);
	}
}


	pci_restore_state(dev);
/* Save and disable devices from the top of the tree down */
static void pci_slot_save_and_disable(struct pci_slot *slot)
{
	struct pci_dev *dev;

	list_for_each_entry(dev, &slot->bus->devices, bus_list) {
		if (!dev->slot || dev->slot != slot)
			continue;
		pci_dev_save_and_disable(dev);
		if (dev->subordinate)
			pci_bus_save_and_disable(dev->subordinate);
	}
}

/*
 * Restore devices from top of the tree down - parent bridges need to be
 * restored before we can get to subordinate devices.
 */
static void pci_slot_restore(struct pci_slot *slot)
{
	struct pci_dev *dev;

	list_for_each_entry(dev, &slot->bus->devices, bus_list) {
		if (!dev->slot || dev->slot != slot)
			continue;
		pci_dev_restore(dev);
		if (dev->subordinate)
			pci_bus_restore(dev->subordinate);
	}
}

static int pci_slot_reset(struct pci_slot *slot, int probe)
{
	int rc;

	if (!slot)
		return -ENOTTY;

	if (!probe)
		pci_slot_lock(slot);

	might_sleep();

	rc = pci_reset_hotplug_slot(slot->hotplug, probe);

	if (!probe)
		pci_slot_unlock(slot);


	return rc;
	return rc;
}
}
EXPORT_SYMBOL_GPL(pci_reset_function);

/**
 * pci_probe_reset_slot - probe whether a PCI slot can be reset
 * @slot: PCI slot to probe
 *
 * Return 0 if slot can be reset, negative if a slot reset is not supported.
 */
int pci_probe_reset_slot(struct pci_slot *slot)
{
	return pci_slot_reset(slot, 1);
}
EXPORT_SYMBOL_GPL(pci_probe_reset_slot);

/**
 * pci_reset_slot - reset a PCI slot
 * @slot: PCI slot to reset
 *
 * A PCI bus may host multiple slots, each slot may support a reset mechanism
 * independent of other slots.  For instance, some slots may support slot power
 * control.  In the case of a 1:1 bus to slot architecture, this function may
 * wrap the bus reset to avoid spurious slot related events such as hotplug.
 * Generally a slot reset should be attempted before a bus reset.  All of the
 * function of the slot and any subordinate buses behind the slot are reset
 * through this function.  PCI config space of all devices in the slot and
 * behind the slot is saved before and restored after reset.
 *
 * Return 0 on success, non-zero on error.
 */
int pci_reset_slot(struct pci_slot *slot)
{
	int rc;

	rc = pci_slot_reset(slot, 1);
	if (rc)
		return rc;

	pci_slot_save_and_disable(slot);

	rc = pci_slot_reset(slot, 0);

	pci_slot_restore(slot);

	return rc;
}
EXPORT_SYMBOL_GPL(pci_reset_slot);

static int pci_bus_reset(struct pci_bus *bus, int probe)
{
	if (!bus->self)
		return -ENOTTY;

	if (probe)
		return 0;

	pci_bus_lock(bus);

	might_sleep();

	pci_reset_bridge_secondary_bus(bus->self);

	pci_bus_unlock(bus);

	return 0;
}

/**
 * pci_probe_reset_bus - probe whether a PCI bus can be reset
 * @bus: PCI bus to probe
 *
 * Return 0 if bus can be reset, negative if a bus reset is not supported.
 */
int pci_probe_reset_bus(struct pci_bus *bus)
{
	return pci_bus_reset(bus, 1);
}
EXPORT_SYMBOL_GPL(pci_probe_reset_bus);

/**
 * pci_reset_bus - reset a PCI bus
 * @bus: top level PCI bus to reset
 *
 * Do a bus reset on the given bus and any subordinate buses, saving
 * and restoring state of all devices.
 *
 * Return 0 on success, non-zero on error.
 */
int pci_reset_bus(struct pci_bus *bus)
{
	int rc;

	rc = pci_bus_reset(bus, 1);
	if (rc)
		return rc;

	pci_bus_save_and_disable(bus);

	rc = pci_bus_reset(bus, 0);

	pci_bus_restore(bus);

	return rc;
}
EXPORT_SYMBOL_GPL(pci_reset_bus);


/**
/**
 * pcix_get_max_mmrbc - get PCI-X maximum designed memory read byte count
 * pcix_get_max_mmrbc - get PCI-X maximum designed memory read byte count
+1 −1
Original line number Original line Diff line number Diff line
@@ -352,7 +352,7 @@ static pci_ers_result_t aer_root_reset(struct pci_dev *dev)
	reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK;
	reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK;
	pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, reg32);
	pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, reg32);


	aer_do_secondary_bus_reset(dev);
	pci_reset_bridge_secondary_bus(dev);
	dev_printk(KERN_DEBUG, &dev->dev, "Root Port link has been reset\n");
	dev_printk(KERN_DEBUG, &dev->dev, "Root Port link has been reset\n");


	/* Clear Root Error Status */
	/* Clear Root Error Status */
Loading