PCI: hotplug: Use global PCI rescan-remove locking
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>
Tue, 14 Jan 2014 19:03:14 +0000 (12:03 -0700)
committerBjorn Helgaas <bhelgaas@google.com>
Tue, 14 Jan 2014 19:14:25 +0000 (12:14 -0700)
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>
drivers/pci/hotplug/cpci_hotplug_pci.c
drivers/pci/hotplug/cpqphp_pci.c
drivers/pci/hotplug/ibmphp_core.c
drivers/pci/hotplug/pciehp_pci.c
drivers/pci/hotplug/rpadlpar_core.c
drivers/pci/hotplug/rpaphp_core.c
drivers/pci/hotplug/s390_pci_hpc.c
drivers/pci/hotplug/sgi_hotplug.c
drivers/pci/hotplug/shpchp_pci.c

index d3add9819f633d5531c02958f05a1cd219044731..8c1464851768140f0c31c27f60f04f93df0ff8c6 100644 (file)
@@ -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));
@@ -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;
@@ -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)
@@ -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;
@@ -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;
 }
index 6e4a12c91adbaaf066093cef758b764485ddd8b4..a3e3c2002b58e97b9260525947db723d7c6b00e7 100644 (file)
@@ -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));
 
@@ -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;
                }
        }
 
@@ -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;
 }
 
@@ -123,6 +127,7 @@ 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) {
@@ -130,6 +135,7 @@ int cpqhp_unconfigure_device(struct pci_func* func)
                        pci_stop_and_remove_bus_device(temp);
                }
        }
+       pci_unlock_rescan_remove();
        return 0;
 }
 
index efdc13adbe41fb26ea8ea466c082b17f7b64f940..cf3ac1e4b099b381862131fddb91ac2e7df10ac0 100644 (file)
@@ -718,6 +718,8 @@ 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) {
@@ -725,7 +727,10 @@ static void ibm_unconfigure_device(struct pci_func *func)
                        pci_dev_put(temp);
                }
        }
+
        pci_dev_put(func->dev);
+
+       pci_unlock_rescan_remove();
 }
 
 /*
@@ -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)
@@ -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));
@@ -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)) {
@@ -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;
 }
 
index 198355112ee7f36101cf36b1482f87fc0e964487..b07d7cc2d697e8c9ce9152eaca90980e90906015 100644 (file)
@@ -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)
@@ -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)
@@ -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
@@ -124,5 +132,6 @@ int pciehp_unconfigure_device(struct slot *p_slot)
                pci_dev_put(dev);
        }
 
+       pci_unlock_rescan_remove();
        return rc;
 }
index e9c044d15add0e9c06bfbe74f736312e0cf1128d..4fcdeedda31b8eedc44101fb2ecc05d45bcb7f1c 100644 (file)
@@ -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!>");
@@ -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;
                }
        }
 
@@ -382,7 +388,8 @@ 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 */
@@ -390,7 +397,9 @@ int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn)
        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;
 }
 
 /**
index b7fc5c9255a5b6fb76f25f54dd4fac5ec06bc331..4796c15fba941df07c4d3bbeed94413d2df48473 100644 (file)
@@ -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;
@@ -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;
index 3c7eb5dd91c636c17e7dadd374022096455d24b1..8d2ce22151eb62001e0a2184e1f68da1a606dec5 100644 (file)
@@ -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;
 
@@ -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)
index 5b05a68cca6c73aaf50c7f66ccec5923e4f3d431..613043f7576f302c75bb5727d4dff910dd0aaaf2 100644 (file)
@@ -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)
@@ -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)
@@ -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) {
index b0e83132542e4ddb52482f899e0d0b1c29a776b2..2bf69fe1926c6b503283c43b954779ceaf2ea1d7 100644 (file)
@@ -40,7 +40,9 @@ 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) {
@@ -48,13 +50,15 @@ int __ref shpchp_configure_device(struct slot *p_slot)
                         "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) {
@@ -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)
@@ -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;
@@ -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;
 }