diff -rupN a/drivers/pci/hotplug/pci_hotplug_core.c b/drivers/pci/hotplug/pci_hotplug_core.c --- a/drivers/pci/hotplug/pci_hotplug_core.c 2013-06-22 19:47:31.000000000 +0000 +++ b/drivers/pci/hotplug/pci_hotplug_core.c 2013-06-27 16:04:16.082714806 +0000 @@ -528,6 +528,29 @@ int pci_hp_change_slot_info(struct hotpl return 0; } +/** + * pci_hp_reset_slot - reset slot + * + * @hotplug: pointer to hotplug slot to reset + * @probe: reset slot (0) or just probe + * + * Returns 0 if successful, anything else for an error. + */ +int pci_hp_reset_slot(struct hotplug_slot *hotplug, int probe) +{ + int result = -ENOTTY; + + if (!hotplug || !try_module_get(hotplug->ops->owner)) + return result; + + if (hotplug->ops->reset_slot) + result = hotplug->ops->reset_slot(hotplug, probe); + + module_put(hotplug->ops->owner); + + return result; +} + static int __init pci_hotplug_init (void) { int result; @@ -561,3 +584,4 @@ MODULE_PARM_DESC(debug, "Debugging mode EXPORT_SYMBOL_GPL(__pci_hp_register); EXPORT_SYMBOL_GPL(pci_hp_deregister); EXPORT_SYMBOL_GPL(pci_hp_change_slot_info); +EXPORT_SYMBOL_GPL(pci_hp_reset_slot); diff -rupN a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h --- a/drivers/pci/hotplug/pciehp.h 2013-06-22 19:47:31.000000000 +0000 +++ b/drivers/pci/hotplug/pciehp.h 2013-06-27 16:04:16.084714807 +0000 @@ -155,6 +155,7 @@ void pciehp_green_led_off(struct slot *s void pciehp_green_led_blink(struct slot *slot); int pciehp_check_link_status(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) { diff -rupN a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c --- a/drivers/pci/hotplug/pciehp_core.c 2013-06-22 19:47:31.000000000 +0000 +++ b/drivers/pci/hotplug/pciehp_core.c 2013-06-27 16:04:16.084714807 +0000 @@ -69,6 +69,7 @@ static int get_power_status (struct hotp 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_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 @@ -111,6 +112,7 @@ static int init_slot(struct controller * ops->disable_slot = disable_slot; ops->get_power_status = get_power_status; ops->get_adapter_status = get_adapter_status; + ops->reset_slot = reset_slot; if (MRL_SENS(ctrl)) ops->get_latch_status = get_latch_status; if (ATTN_LED(ctrl)) { @@ -223,6 +225,16 @@ static int get_adapter_status(struct hot 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) { int rc; diff -rupN a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c --- a/drivers/pci/hotplug/pciehp_hpc.c 2013-06-22 19:47:31.000000000 +0000 +++ b/drivers/pci/hotplug/pciehp_hpc.c 2013-06-27 16:04:16.085714807 +0000 @@ -749,6 +749,37 @@ static void pcie_disable_notification(st 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) { if (pciehp_request_irq(ctrl)) diff -rupN a/drivers/pci/pci.c b/drivers/pci/pci.c --- a/drivers/pci/pci.c 2013-06-22 19:47:31.000000000 +0000 +++ b/drivers/pci/pci.c 2013-06-27 16:04:16.124714810 +0000 @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include "pci.h" @@ -3205,9 +3206,30 @@ static int pci_pm_reset(struct pci_dev * 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; + + pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &ctrl); + ctrl |= PCI_BRIDGE_CTL_BUS_RESET; + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, ctrl); + msleep(100); + + ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET; + pci_write_config_word(dev, PCI_BRIDGE_CONTROL, ctrl); + msleep(100); +} +EXPORT_SYMBOL_GPL(pci_reset_bridge_secondary_bus); + +static int pci_parent_bus_reset(struct pci_dev *dev, int probe) +{ struct pci_dev *pdev; if (pci_is_root_bus(dev->bus) || dev->subordinate || !dev->bus->self) @@ -3220,18 +3242,25 @@ static int pci_parent_bus_reset(struct p if (probe) return 0; - pci_read_config_word(dev->bus->self, PCI_BRIDGE_CONTROL, &ctrl); - 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); + pci_reset_bridge_secondary_bus(dev->bus->self); return 0; } +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_hp_reset_slot(dev->slot->hotplug, probe); +} + static int __pci_dev_reset(struct pci_dev *dev, int probe) { int rc; @@ -3254,27 +3283,62 @@ static int __pci_dev_reset(struct pci_de if (rc != -ENOTTY) goto done; + rc = pci_dev_reset_slot_function(dev, probe); + if (rc != -ENOTTY) + goto done; + rc = pci_parent_bus_reset(dev, probe); done: return rc; } +static void pci_dev_lock(struct pci_dev *dev) +{ + pci_cfg_access_lock(dev); + /* block PM suspend, driver probe, etc. */ + device_lock(&dev->dev); +} + +static void pci_dev_unlock(struct pci_dev *dev) +{ + device_unlock(&dev->dev); + pci_cfg_access_unlock(dev); +} + +static void pci_dev_save(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); + /* + * both INTx and MSI are disabled after the Interrupt Disable bit + * is set and the Bus Master bit is cleared. + */ + 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_cfg_access_lock(dev); - /* block PM suspend, driver probe, etc. */ - device_lock(&dev->dev); - } + if (!probe) + pci_dev_lock(dev); rc = __pci_dev_reset(dev, probe); - if (!probe) { - device_unlock(&dev->dev); - pci_cfg_access_unlock(dev); - } + if (!probe) + pci_dev_unlock(dev); + return rc; } /** @@ -3365,22 +3429,215 @@ int pci_reset_function(struct pci_dev *d if (rc) return rc; - pci_save_state(dev); - - /* - * both INTx and MSI are disabled after the Interrupt Disable bit - * is set and the Bus Master bit is cleared. - */ - pci_write_config_word(dev, PCI_COMMAND, PCI_COMMAND_INTX_DISABLE); + pci_dev_save(dev); rc = pci_dev_reset(dev, 0); - pci_restore_state(dev); + pci_dev_restore(dev); return rc; } EXPORT_SYMBOL_GPL(pci_reset_function); +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); + } +} + +static void pci_bus_unlock(struct pci_bus *bus) +{ + struct pci_dev *dev; + + list_for_each_entry(dev, &bus->devices, bus_list) { + pci_dev_unlock(dev); + if (dev->subordinate) + pci_bus_unlock(dev->subordinate); + } +} + +static void pci_slot_lock(struct pci_slot *slot) +{ + struct pci_dev *dev; + + BUG_ON(!slot); + + list_for_each_entry(dev, &slot->bus->devices, bus_list) { + if (dev->slot && dev->slot == slot) + pci_dev_lock(dev); + if (dev->subordinate) + pci_bus_lock(dev->subordinate); + } +} + +static void pci_slot_unlock(struct pci_slot *slot) +{ + struct pci_dev *dev; + + BUG_ON(!slot); + + list_for_each_entry(dev, &slot->bus->devices, bus_list) { + if (dev->slot && dev->slot == slot) + pci_dev_unlock(dev); + if (dev->subordinate) + pci_bus_unlock(dev->subordinate); + } +} + +static void pci_bus_save(struct pci_bus *bus) +{ + struct pci_dev *dev; + + list_for_each_entry(dev, &bus->devices, bus_list) { + pci_dev_save(dev); + if (dev->subordinate) + pci_bus_save(dev->subordinate); + } +} + +static void pci_bus_restore(struct pci_bus *bus) +{ + struct pci_dev *dev; + + list_for_each_entry(dev, &bus->devices, bus_list) { + pci_dev_restore(dev); + if (dev->subordinate) + pci_bus_restore(dev->subordinate); + } +} + +static void pci_slot_save(struct pci_slot *slot) +{ + struct pci_dev *dev; + + BUG_ON(!slot); + + list_for_each_entry(dev, &slot->bus->devices, bus_list) { + if (dev->slot && dev->slot == slot) + pci_dev_save(dev); + if (dev->subordinate) + pci_bus_save(dev->subordinate); + } +} + +static void pci_slot_restore(struct pci_slot *slot) +{ + struct pci_dev *dev; + + BUG_ON(!slot); + + list_for_each_entry(dev, &slot->bus->devices, bus_list) { + if (dev->slot && dev->slot == slot) + 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_hp_reset_slot(slot->hotplug, probe); + + if (!probe) + pci_slot_unlock(slot); + + return rc; +} + +/** + * 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(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_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(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 * @dev: PCI device to query diff -rupN a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c --- a/drivers/vfio/pci/vfio_pci.c 2013-06-22 19:47:31.000000000 +0000 +++ b/drivers/vfio/pci/vfio_pci.c 2013-06-27 16:04:16.125714811 +0000 @@ -208,6 +208,62 @@ static int vfio_pci_get_irq_count(struct return 0; } +static bool vfio_pci_bus_contained(struct vfio_container *container, + struct pci_bus *bus) +{ + struct pci_dev *pdev; + + list_for_each_entry(pdev, &bus->devices, bus_list) + if (!vfio_container_includes_locked(container, &pdev->dev) || + (pdev->subordinate && + !vfio_pci_bus_contained(container, pdev->subordinate))) + return false; + + return true; +} + +static bool vfio_pci_slot_contained(struct vfio_container *container, + struct pci_slot *slot) +{ + struct pci_dev *pdev; + + list_for_each_entry(pdev, &slot->bus->devices, bus_list) { + if (!pdev->slot || pdev->slot != slot) + continue; + + if (!vfio_container_includes_locked(container, &pdev->dev) || + (pdev->subordinate && + !vfio_pci_bus_contained(container, pdev->subordinate))) + return false; + } + + return true; +} + +static long vfio_pci_reset_bus(struct vfio_pci_device *vdev) +{ + struct vfio_container *container; + long ret = -EBUSY; + + container = vfio_container_lock_from_dev(&vdev->pdev->dev); + if (IS_ERR(container)) + return PTR_ERR(container); + + if (vdev->pdev->slot && + vfio_pci_slot_contained(container, vdev->pdev->slot)) { + ret = pci_reset_slot(vdev->pdev->slot); + if (!ret) + goto out; + } + + if (vfio_pci_bus_contained(container, vdev->pdev->bus)) + ret = pci_reset_bus(vdev->pdev->bus); +out: + vfio_container_unlock(container); + + return ret; +} + static long vfio_pci_ioctl(void *device_data, unsigned int cmd, unsigned long arg) { @@ -391,6 +447,8 @@ static long vfio_pci_ioctl(void *device_ } else if (cmd == VFIO_DEVICE_RESET) return vdev->reset_works ? pci_reset_function(vdev->pdev) : -EINVAL; + else if (cmd == VFIO_DEVICE_PCI_BUS_RESET) + return vfio_pci_reset_bus(vdev); return -ENOTTY; } diff -rupN a/drivers/vfio/vfio.c b/drivers/vfio/vfio.c --- a/drivers/vfio/vfio.c 2013-06-22 19:47:31.000000000 +0000 +++ b/drivers/vfio/vfio.c 2013-06-27 16:04:16.125714811 +0000 @@ -713,7 +713,11 @@ static long vfio_ioctl_check_extension(s driver = container->iommu_driver; switch (arg) { - /* No base extensions yet */ +#ifdef CONFIG_VFIO_PCI + case VFIO_CAP_DEVICE_PCI_BUS_RESET: + ret = 1; + break; +#endif default: /* * If no driver is set, poll all registered drivers for @@ -1355,6 +1359,48 @@ static const struct file_operations vfio .mmap = vfio_device_fops_mmap, }; +struct vfio_container *vfio_container_lock_from_dev(struct device *dev) +{ + struct vfio_device *device = vfio_device_get_from_dev(dev); + struct vfio_group *group = device->group; + struct vfio_container *container = group->container; + + down_read(&container->group_lock); + + return container; +} +EXPORT_SYMBOL_GPL(vfio_container_lock_from_dev); + +bool vfio_container_includes_locked(struct vfio_container *container, + struct device *dev) +{ + struct iommu_group *iommu_group; + struct vfio_group *group; + bool ret = false; + + iommu_group = iommu_group_get(dev); + if (!iommu_group) + return ret; + + list_for_each_entry(group, &container->group_list, container_next) { + if (group->iommu_group == iommu_group) { + ret = true; + break; + } + } + + iommu_group_put(iommu_group); + + return ret; +} +EXPORT_SYMBOL_GPL(vfio_container_includes_locked); + +void vfio_container_unlock(struct vfio_container *container) +{ + up_read(&container->group_lock); +} +EXPORT_SYMBOL_GPL(vfio_container_unlock); + /** * Module/class support */ diff -rupN a/include/linux/pci.h b/include/linux/pci.h --- a/include/linux/pci.h 2013-06-22 19:47:31.000000000 +0000 +++ b/include/linux/pci.h 2013-06-27 16:04:16.124714810 +0000 @@ -923,6 +923,9 @@ int pcie_set_mps(struct pci_dev *dev, in int __pci_reset_function(struct pci_dev *dev); int __pci_reset_function_locked(struct pci_dev *dev); int pci_reset_function(struct pci_dev *dev); +int pci_reset_slot(struct pci_slot *slot); +int pci_reset_bus(struct pci_bus *bus); +void pci_reset_bridge_secondary_bus(struct pci_dev *dev); void pci_update_resource(struct pci_dev *dev, int resno); int __must_check pci_assign_resource(struct pci_dev *dev, int i); int __must_check pci_reassign_resource(struct pci_dev *dev, int i, resource_size_t add_size, resource_size_t align); diff -rupN a/include/linux/pci_hotplug.h b/include/linux/pci_hotplug.h --- a/include/linux/pci_hotplug.h 2013-06-22 19:47:31.000000000 +0000 +++ b/include/linux/pci_hotplug.h 2013-06-27 16:05:47.609723605 +0000 @@ -63,6 +63,9 @@ enum pcie_link_width { * @get_adapter_status: Called to get see if an adapter is present in the slot or not. * If this field is NULL, the value passed in the struct hotplug_slot_info * will be used when this value is requested by a user. + * @reset_slot: Optional interface to allow override of a bus reset for the + * slot for cases where a secondary bus reset can result in spurious + * hotplug events or where a slot can be reset independent of the bus. * * The table of function pointers that is passed to the hotplug pci core by a * hotplug pci driver. These functions are called by the hotplug pci core when @@ -80,6 +83,7 @@ struct hotplug_slot_ops { int (*get_attention_status) (struct hotplug_slot *slot, u8 *value); int (*get_latch_status) (struct hotplug_slot *slot, u8 *value); int (*get_adapter_status) (struct hotplug_slot *slot, u8 *value); + int (*reset_slot) (struct hotplug_slot *slot, int probe); }; /** @@ -132,6 +136,15 @@ int pci_hp_deregister(struct hotplug_slo int __must_check pci_hp_change_slot_info(struct hotplug_slot *slot, struct hotplug_slot_info *info); +#ifdef CONFIG_HOTPLUG_PCI +int pci_hp_reset_slot(struct hotplug_slot *slot, int probe); +#else +static inline int pci_hp_reset_slot(struct hotplug_slot *slot, int probe) +{ + return -ENOTTY; +} +#endif + /* use a define to avoid include chaining to get THIS_MODULE & friends */ #define pci_hp_register(slot, pbus, devnr, name) \ __pci_hp_register(slot, pbus, devnr, name, THIS_MODULE, KBUILD_MODNAME) diff -rupN a/include/linux/vfio.h b/include/linux/vfio.h --- a/include/linux/vfio.h 2013-06-22 19:47:31.000000000 +0000 +++ b/include/linux/vfio.h 2013-06-27 16:04:16.125714811 +0000 @@ -49,6 +49,11 @@ extern struct vfio_device *vfio_device_g extern void vfio_device_put(struct vfio_device *device); extern void *vfio_device_data(struct vfio_device *device); +extern struct vfio_container *vfio_container_lock_from_dev(struct device *dev); +extern bool vfio_container_includes_locked(struct vfio_container *container, + struct device *dev); +extern void vfio_container_unlock(struct vfio_container *container); + /** * struct vfio_iommu_driver_ops - VFIO IOMMU driver callbacks */ diff -rupN a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h --- a/include/uapi/linux/vfio.h 2013-06-22 19:47:31.000000000 +0000 +++ b/include/uapi/linux/vfio.h 2013-06-27 16:04:16.125714811 +0000 @@ -59,6 +59,9 @@ */ #define VFIO_CHECK_EXTENSION _IO(VFIO_TYPE, VFIO_BASE + 1) +/* Support for VFIO_DEVICE_PCI_BUS_RESET */ +#define VFIO_CAP_DEVICE_PCI_BUS_RESET 1 + /** * VFIO_SET_IOMMU - _IOW(VFIO_TYPE, VFIO_BASE + 2, __s32) * @@ -323,6 +326,17 @@ enum { VFIO_PCI_NUM_IRQS }; +/* VFIO-PCI defines the following VFIO-PCI specific device ioctl(s) */ + +/** + * VFIO_DEVICE_PCI_BUS_RESET - _IO(VFIO_TYPE, VFIO_BASE + 12) + * + * Reset the PCI slot/bus of the device. If available, a slot reset + * will be used instead of a bus reset. All of the devices in or below + * the slot/bus will be reset and MUST be attached to the same container. + */ +#define VFIO_DEVICE_PCI_BUS_RESET _IO(VFIO_TYPE, VFIO_BASE + 12) + /* -------- API for Type1 VFIO IOMMU -------- */ /**