virtfn_add_bus() is find-then-create and therefore assumes external
serialization. Create every virtfn bus before adding any VF, taking the
bus-create path out of the per-VF loop so a later commit can run the
per-VF adds concurrently against a stable bus set. Bus numbers are
already fixed before the loop, since pci_iov_set_numvfs() has latched
offset and stride.

The cleanup pass must remove only buses this enable created, because
virtfn_remove_bus() checks that a bus is empty rather than who created
it, so virtfn_add_bus() now reports whether it created the bus and
sriov_add_vfs() records that per VF id in a bitmap. Return early for
num_vfs == 0: kvcalloc(0, ...) returns ZERO_SIZE_PTR and a later commit
dereferences buses[0] unconditionally.

Several ids can share one bus number; only the creating id's bit is set.

Assisted-by: LLM
Signed-off-by: Pavol Sakac <[email protected]>
---
 drivers/pci/iov.c | 60 +++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 56 insertions(+), 4 deletions(-)

diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c
index 1826d32a2364..dda9303516f5 100644
--- a/drivers/pci/iov.c
+++ b/drivers/pci/iov.c
@@ -8,6 +8,7 @@
  */
 
 #include <linux/bitfield.h>
+#include <linux/bitmap.h>
 #include <linux/bits.h>
 #include <linux/log2.h>
 #include <linux/pci.h>
@@ -124,10 +125,14 @@ static int compute_max_vf_buses(struct pci_dev *dev)
        return rc;
 }
 
-static struct pci_bus *virtfn_add_bus(struct pci_bus *bus, int busnr)
+static struct pci_bus *virtfn_add_bus(struct pci_bus *bus, int busnr,
+                                     bool *created)
 {
        struct pci_bus *child;
 
+       if (created)
+               *created = false;
+
        if (bus->number == busnr)
                return bus;
 
@@ -140,6 +145,8 @@ static struct pci_bus *virtfn_add_bus(struct pci_bus *bus, 
int busnr)
                return NULL;
 
        pci_bus_insert_busn_res(child, busnr, busnr);
+       if (created)
+               *created = true;
 
        return child;
 }
@@ -394,7 +401,7 @@ int pci_iov_add_virtfn(struct pci_dev *dev, int id)
        struct pci_bus *bus;
        int rc;
 
-       bus = virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, id));
+       bus = virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, id), NULL);
        if (!bus)
                return -ENOMEM;
 
@@ -632,22 +639,67 @@ int __weak pcibios_sriov_disable(struct pci_dev *pdev)
 
 static int sriov_add_vfs(struct pci_dev *dev, u16 num_vfs)
 {
+       unsigned long *created_buses;
+       struct pci_bus **buses;
+       struct pci_bus *bus;
        unsigned int i;
        int rc;
 
-       if (dev->no_vf_scan)
+       if (!num_vfs || dev->no_vf_scan)
                return 0;
 
+       buses = kvcalloc(num_vfs, sizeof(*buses), GFP_KERNEL);
+       if (!buses)
+               return -ENOMEM;
+
+       created_buses = bitmap_zalloc(num_vfs, GFP_KERNEL);
+       if (!created_buses) {
+               kvfree(buses);
+               return -ENOMEM;
+       }
+
        for (i = 0; i < num_vfs; i++) {
-               rc = pci_iov_add_virtfn(dev, i);
+               bool created;
+
+               buses[i] = virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, i),
+                                         &created);
+               if (!buses[i]) {
+                       rc = -ENOMEM;
+                       goto remove_buses;
+               }
+               if (created)
+                       __set_bit(i, created_buses);
+       }
+
+       for (i = 0; i < num_vfs; i++) {
+               rc = __pci_iov_add_virtfn(dev, buses[i], i);
                if (rc)
                        goto failed;
        }
+
+       bitmap_free(created_buses);
+       kvfree(buses);
        return 0;
 failed:
        while (i--)
                pci_iov_remove_virtfn(dev, i);
 
+remove_buses:
+       /*
+        * Remove only buses this enable created: several ids can share one
+        * bus and only the creating id is recorded; virtfn_remove_bus()
+        * checks emptiness, not ownership.  Re-look each up by number --
+        * the VF unwind above may already have freed it.
+        */
+       for_each_set_bit(i, created_buses, num_vfs) {
+               bus = pci_find_bus(pci_domain_nr(dev->bus),
+                                  pci_iov_virtfn_bus(dev, i));
+               if (bus)
+                       virtfn_remove_bus(dev->bus, bus);
+       }
+
+       bitmap_free(created_buses);
+       kvfree(buses);
        return rc;
 }
 
-- 
2.47.3


Reply via email to