[RFC PATCH 2/8] PCI/IOV: Create virtfn buses up front in sriov_add_vfs()

From: Pavol Sakac

Date: Fri Sep 11 2026 - 08:44:41 EST


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 <sakacpav@xxxxxxxxx>
---
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