From: Dimitri Daskalakis <[email protected]>

Modify pci_init_capabilities() to discover the SIOV extended capability
(cap ID 0x38). When present, allocate struct pci_siov that records the
capability position, total SDI count, routing ID offset and stride, and
the maximum bus range the SDIs can span.

The init path mirrors sriov_init(): read the capability registers,
compute the worst-case bus consumption from total_SDIs, and stash the
result in the PF's pci_dev. Release frees the structure on teardown.

If is_physfn was already set (by sriov_init), it will not be cleared if
siov_init() fails. This prevents clobbering the flag for devices that
enable both virtualization types.

The SR-IOV code does not unset the is_physfn bit of a pci device
when disabled, and the SIOV code follows that pattern.

Assisted-by: Claude:claude-opus-4.7
Signed-off-by: Dimitri Daskalakis <[email protected]>
---
 drivers/pci/Makefile |   1 +
 drivers/pci/pci.h    |  16 ++++++
 drivers/pci/probe.c  |   2 +
 drivers/pci/siov.c   | 113 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 132 insertions(+)
 create mode 100644 drivers/pci/siov.c

diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 41ebc3b9a518..a584cd1bf08a 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_PCI_QUIRKS)      += quirks.o
 obj-$(CONFIG_HOTPLUG_PCI)      += hotplug/
 obj-$(CONFIG_PCI_ATS)          += ats.o
 obj-$(CONFIG_PCI_IOV)          += iov.o
+obj-$(CONFIG_PCI_SIOV)         += siov.o
 obj-$(CONFIG_PCI_BRIDGE_EMUL)  += pci-bridge-emul.o
 obj-$(CONFIG_PCI_LABEL)                += pci-label.o
 obj-$(CONFIG_X86_INTEL_MID)    += pci-mid.o
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index fd7c04e26c16..a516db996aab 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -1042,6 +1042,22 @@ static inline u16 pci_virtfn_routing_id(struct pci_dev 
*pf, u16 offset,
 }
 #endif
 
+#ifdef CONFIG_PCI_SIOV
+int pci_siov_init(struct pci_dev *dev);
+void pci_siov_release(struct pci_dev *dev);
+int pci_siov_bus_range(struct pci_bus *bus);
+#else
+static inline int pci_siov_init(struct pci_dev *dev)
+{
+       return -ENODEV;
+}
+static inline void pci_siov_release(struct pci_dev *dev) { }
+static inline int pci_siov_bus_range(struct pci_bus *bus)
+{
+       return 0;
+}
+#endif
+
 #ifdef CONFIG_PCIE_TPH
 void pci_restore_tph_state(struct pci_dev *dev);
 void pci_save_tph_state(struct pci_dev *dev);
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b63cd0c310bc..bebc32c8d374 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2473,6 +2473,7 @@ static void pci_release_capabilities(struct pci_dev *dev)
        pci_aer_exit(dev);
        pci_rcec_exit(dev);
        pci_iov_release(dev);
+       pci_siov_release(dev);
        pci_free_cap_save_buffers(dev);
 }
 
@@ -2666,6 +2667,7 @@ static void pci_init_capabilities(struct pci_dev *dev)
        pci_vpd_init(dev);              /* Vital Product Data */
        pci_configure_ari(dev);         /* Alternative Routing-ID Forwarding */
        pci_iov_init(dev);              /* Single Root I/O Virtualization */
+       pci_siov_init(dev);             /* Scalable I/O Virtualization */
        pci_ats_init(dev);              /* Address Translation Services */
        pci_pri_init(dev);              /* Page Request Interface */
        pci_pasid_init(dev);            /* Process Address Space ID */
diff --git a/drivers/pci/siov.c b/drivers/pci/siov.c
new file mode 100644
index 000000000000..7372ce95714b
--- /dev/null
+++ b/drivers/pci/siov.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PCI Express Scalable I/O Virtualization (SIOV) support
+ */
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+#include "pci.h"
+
+static int pci_siov_sdi_bus(struct pci_dev *dev, int sdi_id)
+{
+       if (!dev->siov)
+               return -EINVAL;
+       return pci_virtfn_routing_id(dev, dev->siov->offset,
+                                 dev->siov->stride, sdi_id) >> 8;
+}
+
+static int compute_max_sdi_buses(struct pci_dev *dev)
+{
+       struct pci_siov *siov = dev->siov;
+
+       if (!siov->offset || (siov->total_SDIs > 1 && !siov->stride))
+               return -EIO;
+
+       siov->max_SDI_buses = pci_siov_sdi_bus(dev, siov->total_SDIs - 1);
+       return 0;
+}
+
+static int siov_init(struct pci_dev *dev, int pos)
+{
+       struct pci_siov *siov;
+       bool was_physfn;
+       u16 total;
+       u8 status;
+       int rc;
+
+       pci_read_config_byte(dev, pos + PCI_SIOV_STATUS, &status);
+       if (status & PCI_SIOV_STATUS_ENABLED)
+               pci_warn(dev, "SIOV: SDIs active at init, FLR may be 
required\n");
+
+       pci_read_config_word(dev, pos + PCI_SIOV_TOTAL_SDI, &total);
+       if (!total)
+               return 0;
+
+       siov = kzalloc_obj(*siov);
+       if (!siov)
+               return -ENOMEM;
+
+       siov->pos = pos;
+       siov->total_SDIs = total;
+       siov->driver_max_SDIs = total;
+       siov->self = dev;
+       pci_read_config_dword(dev, pos + PCI_SIOV_CAP, &siov->cap);
+       pci_read_config_word(dev, pos + PCI_SIOV_SDI_OFFSET, &siov->offset);
+       pci_read_config_word(dev, pos + PCI_SIOV_SDI_STRIDE, &siov->stride);
+
+       was_physfn = dev->is_physfn;
+
+       dev->siov = siov;
+       dev->is_physfn = 1;
+       dev->is_siov = 1;
+       rc = compute_max_sdi_buses(dev);
+       if (rc) {
+               dev->siov = NULL;
+               dev->is_siov = 0;
+               if (!was_physfn)
+                       dev->is_physfn = 0;
+               kfree(siov);
+               return rc;
+       }
+
+       return 0;
+}
+
+static void siov_release(struct pci_dev *dev)
+{
+       WARN_ON_ONCE(dev->siov->num_SDIs);
+
+       kfree(dev->siov);
+       dev->siov = NULL;
+       dev->is_siov = 0;
+}
+
+/**
+ * pci_siov_init - initialize the Scalable IOV capability
+ * @dev: the PCI device
+ *
+ * Returns 0 on success, or negative on failure.
+ */
+int pci_siov_init(struct pci_dev *dev)
+{
+       int pos;
+
+       if (!pci_is_pcie(dev))
+               return -ENODEV;
+
+       pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_SIOV);
+       if (pos)
+               return siov_init(dev, pos);
+
+       return -ENODEV;
+}
+
+/**
+ * pci_siov_release - release resources used by the SIOV capability
+ * @dev: the PCI device
+ */
+void pci_siov_release(struct pci_dev *dev)
+{
+       if (dev->siov)
+               siov_release(dev);
+}
-- 
2.52.0


Reply via email to