Skip to content

Commit

Permalink
PCI: hotplug: Use global PCI rescan-remove locking
Browse files Browse the repository at this point in the history
Multiple race conditions are possible between PCI hotplug and the generic
PCI bus rescan and device removal that can be triggered via sysfs.

To avoid those race conditions make PCI hotplug use global PCI
rescan-remove locking.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
  • Loading branch information
Rafael J. Wysocki authored and Bjorn Helgaas committed Jan 14, 2014
1 parent 5ef68e8 commit c4ec84c
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 19 deletions.
14 changes: 12 additions & 2 deletions drivers/pci/hotplug/cpci_hotplug_pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,12 @@ int __ref cpci_configure_slot(struct slot *slot)
{
struct pci_dev *dev;
struct pci_bus *parent;
int ret = 0;

dbg("%s - enter", __func__);

pci_lock_rescan_remove();

if (slot->dev == NULL) {
dbg("pci_dev null, finding %02x:%02x:%x",
slot->bus->number, PCI_SLOT(slot->devfn), PCI_FUNC(slot->devfn));
Expand All @@ -277,7 +280,8 @@ int __ref cpci_configure_slot(struct slot *slot)
slot->dev = pci_get_slot(slot->bus, slot->devfn);
if (slot->dev == NULL) {
err("Could not find PCI device for slot %02x", slot->number);
return -ENODEV;
ret = -ENODEV;
goto out;
}
}
parent = slot->dev->bus;
Expand All @@ -294,8 +298,10 @@ int __ref cpci_configure_slot(struct slot *slot)

pci_bus_add_devices(parent);

out:
pci_unlock_rescan_remove();
dbg("%s - exit", __func__);
return 0;
return ret;
}

int cpci_unconfigure_slot(struct slot* slot)
Expand All @@ -308,6 +314,8 @@ int cpci_unconfigure_slot(struct slot* slot)
return -ENODEV;
}

pci_lock_rescan_remove();

list_for_each_entry_safe(dev, temp, &slot->bus->devices, bus_list) {
if (PCI_SLOT(dev->devfn) != PCI_SLOT(slot->devfn))
continue;
Expand All @@ -318,6 +326,8 @@ int cpci_unconfigure_slot(struct slot* slot)
pci_dev_put(slot->dev);
slot->dev = NULL;

pci_unlock_rescan_remove();

dbg("%s - exit", __func__);
return 0;
}
8 changes: 7 additions & 1 deletion drivers/pci/hotplug/cpqphp_pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ int cpqhp_configure_device (struct controller* ctrl, struct pci_func* func)
struct pci_bus *child;
int num;

pci_lock_rescan_remove();

if (func->pci_dev == NULL)
func->pci_dev = pci_get_bus_and_slot(func->bus,PCI_DEVFN(func->device, func->function));

Expand All @@ -100,7 +102,7 @@ int cpqhp_configure_device (struct controller* ctrl, struct pci_func* func)
func->pci_dev = pci_get_bus_and_slot(func->bus, PCI_DEVFN(func->device, func->function));
if (func->pci_dev == NULL) {
dbg("ERROR: pci_dev still null\n");
return 0;
goto out;
}
}

Expand All @@ -113,6 +115,8 @@ int cpqhp_configure_device (struct controller* ctrl, struct pci_func* func)

pci_dev_put(func->pci_dev);

out:
pci_unlock_rescan_remove();
return 0;
}

Expand All @@ -123,13 +127,15 @@ int cpqhp_unconfigure_device(struct pci_func* func)

dbg("%s: bus/dev/func = %x/%x/%x\n", __func__, func->bus, func->device, func->function);

pci_lock_rescan_remove();
for (j=0; j<8 ; j++) {
struct pci_dev* temp = pci_get_bus_and_slot(func->bus, PCI_DEVFN(func->device, j));
if (temp) {
pci_dev_put(temp);
pci_stop_and_remove_bus_device(temp);
}
}
pci_unlock_rescan_remove();
return 0;
}

Expand Down
13 changes: 11 additions & 2 deletions drivers/pci/hotplug/ibmphp_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -718,14 +718,19 @@ static void ibm_unconfigure_device(struct pci_func *func)
func->device, func->function);
debug("func->device << 3 | 0x0 = %x\n", func->device << 3 | 0x0);

pci_lock_rescan_remove();

for (j = 0; j < 0x08; j++) {
temp = pci_get_bus_and_slot(func->busno, (func->device << 3) | j);
if (temp) {
pci_stop_and_remove_bus_device(temp);
pci_dev_put(temp);
}
}

pci_dev_put(func->dev);

pci_unlock_rescan_remove();
}

/*
Expand Down Expand Up @@ -780,6 +785,8 @@ static int ibm_configure_device(struct pci_func *func)
int flag = 0; /* this is to make sure we don't double scan the bus,
for bridged devices primarily */

pci_lock_rescan_remove();

if (!(bus_structure_fixup(func->busno)))
flag = 1;
if (func->dev == NULL)
Expand All @@ -789,7 +796,7 @@ static int ibm_configure_device(struct pci_func *func)
if (func->dev == NULL) {
struct pci_bus *bus = pci_find_bus(0, func->busno);
if (!bus)
return 0;
goto out;

num = pci_scan_slot(bus,
PCI_DEVFN(func->device, func->function));
Expand All @@ -800,7 +807,7 @@ static int ibm_configure_device(struct pci_func *func)
PCI_DEVFN(func->device, func->function));
if (func->dev == NULL) {
err("ERROR... : pci_dev still NULL\n");
return 0;
goto out;
}
}
if (!(flag) && (func->dev->hdr_type == PCI_HEADER_TYPE_BRIDGE)) {
Expand All @@ -810,6 +817,8 @@ static int ibm_configure_device(struct pci_func *func)
pci_bus_add_devices(child);
}

out:
pci_unlock_rescan_remove();
return 0;
}

Expand Down
17 changes: 13 additions & 4 deletions drivers/pci/hotplug/pciehp_pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,26 @@ int pciehp_configure_device(struct slot *p_slot)
struct pci_dev *dev;
struct pci_dev *bridge = p_slot->ctrl->pcie->port;
struct pci_bus *parent = bridge->subordinate;
int num;
int num, ret = 0;
struct controller *ctrl = p_slot->ctrl;

pci_lock_rescan_remove();

dev = pci_get_slot(parent, PCI_DEVFN(0, 0));
if (dev) {
ctrl_err(ctrl, "Device %s already exists "
"at %04x:%02x:00, cannot hot-add\n", pci_name(dev),
pci_domain_nr(parent), parent->number);
pci_dev_put(dev);
return -EINVAL;
ret = -EINVAL;
goto out;
}

num = pci_scan_slot(parent, PCI_DEVFN(0, 0));
if (num == 0) {
ctrl_err(ctrl, "No new device found\n");
return -ENODEV;
ret = -ENODEV;
goto out;
}

list_for_each_entry(dev, &parent->devices, bus_list)
Expand All @@ -73,7 +77,9 @@ int pciehp_configure_device(struct slot *p_slot)

pci_bus_add_devices(parent);

return 0;
out:
pci_unlock_rescan_remove();
return ret;
}

int pciehp_unconfigure_device(struct slot *p_slot)
Expand All @@ -90,6 +96,8 @@ int pciehp_unconfigure_device(struct slot *p_slot)
__func__, pci_domain_nr(parent), parent->number);
pciehp_get_adapter_status(p_slot, &presence);

pci_lock_rescan_remove();

/*
* Stopping an SR-IOV PF device removes all the associated VFs,
* which will update the bus->devices list and confuse the
Expand Down Expand Up @@ -124,5 +132,6 @@ int pciehp_unconfigure_device(struct slot *p_slot)
pci_dev_put(dev);
}

pci_unlock_rescan_remove();
return rc;
}
19 changes: 14 additions & 5 deletions drivers/pci/hotplug/rpadlpar_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,15 @@ int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn)
{
struct pci_bus *bus;
struct slot *slot;
int ret = 0;

pci_lock_rescan_remove();

bus = pcibios_find_pci_bus(dn);
if (!bus)
return -EINVAL;
if (!bus) {
ret = -EINVAL;
goto out;
}

pr_debug("PCI: Removing PCI slot below EADS bridge %s\n",
bus->self ? pci_name(bus->self) : "<!PHB!>");
Expand All @@ -371,7 +376,8 @@ int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn)
printk(KERN_ERR
"%s: unable to remove hotplug slot %s\n",
__func__, drc_name);
return -EIO;
ret = -EIO;
goto out;
}
}

Expand All @@ -382,15 +388,18 @@ int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn)
if (pcibios_unmap_io_space(bus)) {
printk(KERN_ERR "%s: failed to unmap bus range\n",
__func__);
return -ERANGE;
ret = -ERANGE;
goto out;
}

/* Remove the EADS bridge device itself */
BUG_ON(!bus->self);
pr_debug("PCI: Now removing bridge device %s\n", pci_name(bus->self));
pci_stop_and_remove_bus_device(bus->self);

return 0;
out:
pci_unlock_rescan_remove();
return ret;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions drivers/pci/hotplug/rpaphp_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,9 @@ static int enable_slot(struct hotplug_slot *hotplug_slot)
return retval;

if (state == PRESENT) {
pci_lock_rescan_remove();
pcibios_add_pci_devices(slot->bus);
pci_unlock_rescan_remove();
slot->state = CONFIGURED;
} else if (state == EMPTY) {
slot->state = EMPTY;
Expand All @@ -418,7 +420,9 @@ static int disable_slot(struct hotplug_slot *hotplug_slot)
if (slot->state == NOT_CONFIGURED)
return -EINVAL;

pci_lock_rescan_remove();
pcibios_remove_pci_devices(slot->bus);
pci_unlock_rescan_remove();
vm_unmap_aliases();

slot->state = NOT_CONFIGURED;
Expand Down
4 changes: 3 additions & 1 deletion drivers/pci/hotplug/s390_pci_hpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ static int enable_slot(struct hotplug_slot *hotplug_slot)
goto out_deconfigure;

pci_scan_slot(slot->zdev->bus, ZPCI_DEVFN);
pci_lock_rescan_remove();
pci_bus_add_devices(slot->zdev->bus);
pci_unlock_rescan_remove();

return rc;

Expand All @@ -98,7 +100,7 @@ static int disable_slot(struct hotplug_slot *hotplug_slot)
return -EIO;

if (slot->zdev->pdev)
pci_stop_and_remove_bus_device(slot->zdev->pdev);
pci_stop_and_remove_bus_device_locked(slot->zdev->pdev);

rc = zpci_disable_device(slot->zdev);
if (rc)
Expand Down
5 changes: 5 additions & 0 deletions drivers/pci/hotplug/sgi_hotplug.c
Original file line number Diff line number Diff line change
Expand Up @@ -459,12 +459,15 @@ static int enable_slot(struct hotplug_slot *bss_hotplug_slot)
acpi_scan_lock_release();
}

pci_lock_rescan_remove();

/* Call the driver for the new device */
pci_bus_add_devices(slot->pci_bus);
/* Call the drivers for the new devices subordinate to PPB */
if (new_ppb)
pci_bus_add_devices(new_bus);

pci_unlock_rescan_remove();
mutex_unlock(&sn_hotplug_mutex);

if (rc == 0)
Expand Down Expand Up @@ -540,6 +543,7 @@ static int disable_slot(struct hotplug_slot *bss_hotplug_slot)
acpi_scan_lock_release();
}

pci_lock_rescan_remove();
/* Free the SN resources assigned to the Linux device.*/
list_for_each_entry_safe(dev, temp, &slot->pci_bus->devices, bus_list) {
if (PCI_SLOT(dev->devfn) != slot->device_num + 1)
Expand All @@ -550,6 +554,7 @@ static int disable_slot(struct hotplug_slot *bss_hotplug_slot)
pci_stop_and_remove_bus_device(dev);
pci_dev_put(dev);
}
pci_unlock_rescan_remove();

/* Remove the SSDT for the slot from the ACPI namespace */
if (SN_ACPI_BASE_SUPPORT() && ssdt_id) {
Expand Down
18 changes: 14 additions & 4 deletions drivers/pci/hotplug/shpchp_pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,25 @@ int __ref shpchp_configure_device(struct slot *p_slot)
struct controller *ctrl = p_slot->ctrl;
struct pci_dev *bridge = ctrl->pci_dev;
struct pci_bus *parent = bridge->subordinate;
int num;
int num, ret = 0;

pci_lock_rescan_remove();

dev = pci_get_slot(parent, PCI_DEVFN(p_slot->device, 0));
if (dev) {
ctrl_err(ctrl, "Device %s already exists "
"at %04x:%02x:%02x, cannot hot-add\n", pci_name(dev),
pci_domain_nr(parent), p_slot->bus, p_slot->device);
pci_dev_put(dev);
return -EINVAL;
ret = -EINVAL;
goto out;
}

num = pci_scan_slot(parent, PCI_DEVFN(p_slot->device, 0));
if (num == 0) {
ctrl_err(ctrl, "No new device found\n");
return -ENODEV;
ret = -ENODEV;
goto out;
}

list_for_each_entry(dev, &parent->devices, bus_list) {
Expand All @@ -75,7 +79,9 @@ int __ref shpchp_configure_device(struct slot *p_slot)

pci_bus_add_devices(parent);

return 0;
out:
pci_unlock_rescan_remove();
return ret;
}

int shpchp_unconfigure_device(struct slot *p_slot)
Expand All @@ -89,6 +95,8 @@ int shpchp_unconfigure_device(struct slot *p_slot)
ctrl_dbg(ctrl, "%s: domain:bus:dev = %04x:%02x:%02x\n",
__func__, pci_domain_nr(parent), p_slot->bus, p_slot->device);

pci_lock_rescan_remove();

list_for_each_entry_safe(dev, temp, &parent->devices, bus_list) {
if (PCI_SLOT(dev->devfn) != p_slot->device)
continue;
Expand All @@ -108,6 +116,8 @@ int shpchp_unconfigure_device(struct slot *p_slot)
pci_stop_and_remove_bus_device(dev);
pci_dev_put(dev);
}

pci_unlock_rescan_remove();
return rc;
}

0 comments on commit c4ec84c

Please sign in to comment.