[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v4 4/5] vpci: add SR-IOV support for PVH Dom0


  • To: "xen-devel@xxxxxxxxxxxxxxxxxxxx" <xen-devel@xxxxxxxxxxxxxxxxxxxx>
  • From: Mykyta Poturai <Mykyta_Poturai@xxxxxxxx>
  • Date: Thu, 15 Oct 2026 14:08:38 +0000
  • Accept-language: en-US
  • Arc-authentication-results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=epam.com; dmarc=pass action=none header.from=epam.com; dkim=pass header.d=epam.com; arc=none
  • Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=lutSN8JcJw/rmCOxT94t6wZYtd+HubLGqaLUO9x16O4=; b=RSG2vdRrOeJfwdqtFrLUteMSyUwE2OzTM4QPNyKirNyDzYnWsfpzXJTKRLht3LVj1r8phbA/1XBsrYfH6uSPfGIAWaaTatwwWhouMyOXGwWNL6yqTTct6aUU4kkkOBJJzWS6AuXRP1KPfYVAlnp9Y0T4CCcgjE8LJx8lE7ppRVaTKsDvULGdvNxNrrjLi6ONxfnahWgCZi2JXEHewkRFHiBHd4NoFVsFW4E6pUaFawvmkOWcVxMCNjOW/h9zxCCkZYV6Iy7IeCCps8Yl9/4yOxUityWbnBpRbiO7CKYHl9hk7II+R5kqXt/W+5fjX3ZoLc/ddTcnvyzhWJWh5c0Tkg==
  • Arc-seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=SWWOqAeBmcsp4odSEvsq55338X/th9tXZyNY8JKePhEpjpiGnOD96sd/cyk1O4GnRdsQ+MoAGChyjLJUccZFdX9Gx2AAGOeW1nXkbYfNmHnlMUNyddtWVWz+sIuiMUvZ4rTr1f5tbfipz7z0/e8tkCg+MYFlEx3zNwae7XXU+RjAw5qM+/zUTcZxxyLTAx1OxbmkvRGIudH7j1SZrjR2vaVJJ2yGlZmSBooMFxygvmLXpL/3eYpZhvwIxrG8O4Imf91ipaBbZGG4HI4WMNw0OTaG80ojJE880n0VyG41dgV76qeO+NbfKfWpt2XOv6uKjjjhtTypeDBKBxadnktlng==
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=selector1 header.d=epam.com header.i="@epam.com" header.h="From:Date:Subject:Message-ID:Content-Type:MIME-Version:x-ms-exchange-senderadcheck"
  • Authentication-results: mx.microsoft.com 1; dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=epam.com;
  • Cc: Stewart Hildebrand <stewart.hildebrand@xxxxxxx>, Oleksii Kurochko <oleksii.kurochko@xxxxxxxxx>, Community Manager <community.manager@xxxxxxxxxxxxxx>, Andrew Cooper <andrew.cooper3@xxxxxxxxxx>, Anthony PERARD <anthony.perard@xxxxxxxxxx>, Michal Orzel <michal.orzel@xxxxxxx>, Jan Beulich <jbeulich@xxxxxxxx>, Julien Grall <julien@xxxxxxx>, Roger Pau Monné <roger@xxxxxxxxxxxxxx>, Stefano Stabellini <sstabellini@xxxxxxxxxx>, Roger Pau Monné <roger@xxxxxxxxxxxxxx>, "Daniel P. Smith" <dpsmith@xxxxxxxxxxxxxxxxxxxx>, Roger Pau Monné <roger.pau@xxxxxxxxxx>, Mykyta Poturai <Mykyta_Poturai@xxxxxxxx>
  • Delivery-date: Thu, 15 Oct 2026 14:08:53 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>
  • Thread-index: AQHdXK6sWnTwG0FjdEmPa5OC537fEg==
  • Thread-topic: [PATCH v4 4/5] vpci: add SR-IOV support for PVH Dom0

From: Stewart Hildebrand <stewart.hildebrand@xxxxxxx>

This code is expected to only be used by privileged domains,
unprivileged domains should not get access to the SR-IOV capability.

Implement a write handler for the SR-IOV control register, so Xen can
discover the VFs a PF enables and map their BARs. VF BARs live in the
PF's capability and are re-sized on every VF Enable, as the System Page
Size register can change them. They decode only while both VF Enable
and VF MSE are set.

Xen adds the VFs itself: on VF Enable their routing IDs are recorded
and config space accesses to them terminated for 100ms, as the spec
permits; the first access past that adds the VF. Clearing VF Enable
removes them again.

Signed-off-by: Stewart Hildebrand <stewart.hildebrand@xxxxxxx>
Signed-off-by: Roger Pau Monné <roger.pau@xxxxxxxxxx>
Signed-off-by: Mykyta Poturai <mykyta_poturai@xxxxxxxx>
---
v3->v4:
* add VFs in Xen instead of requiring the hardware domain to report
  them
* drop the emulated command register read for VFs; PCI_COMMAND_MEMORY
  is read-only 0 on a VF and must not control mapping
* require VF Enable in addition to MSE before mapping
* cache the SR-IOV capability position at init
* address review comments on vf_init_bars(), init_sriov(),
  cleanup_sriov() and size_vf_bars()
* change SOB order

v2->v3:
* rework adding VFs with multiple mapping operations support
* switch back to no longer adding VFs withing Xen
* use private.h
* style fixes
* fixup cleanup_sriov
* emulate command register read for VFs

v1->v2:
* switch to VF discovery by Xen
* fix sequential vpci_modify_bars calls
* move documentation changes to a separate commit
---
 CHANGELOG.md                  |   1 +
 SUPPORT.md                    |   2 -
 tools/tests/vpci/emul.h       |   4 +
 xen/drivers/passthrough/pci.c |  44 +++
 xen/drivers/pci/physdev.c     |  16 +-
 xen/drivers/vpci/Makefile     |   1 +
 xen/drivers/vpci/header.c     |  10 +-
 xen/drivers/vpci/private.h    |  15 +
 xen/drivers/vpci/sriov.c      | 507 ++++++++++++++++++++++++++++++++++
 xen/drivers/vpci/vpci.c       |  14 +
 xen/include/xen/pci.h         |   3 +
 xen/include/xen/vpci.h        |  24 +-
 12 files changed, 629 insertions(+), 12 deletions(-)
 create mode 100644 xen/drivers/vpci/sriov.c

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2dd01037b..69f3175014 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ The format is based on [Keep a 
Changelog](https://keepachangelog.com/en/1.0.0/)
      afterwards.
 
 ### Added
+ - PCI SR-IOV is supported for PVH dom0.
 
 ### Removed
  - On x86:
diff --git a/SUPPORT.md b/SUPPORT.md
index eb07332462..62c9fd54e8 100644
--- a/SUPPORT.md
+++ b/SUPPORT.md
@@ -170,8 +170,6 @@ unexpected behavior or issues on some hardware.
 
 At least the following features are missing on a PVH dom0:
 
-  * PCI SR-IOV.
-
   * Native NMI forwarding (nmi=dom0 command line option).
 
   * MCE handling.
diff --git a/tools/tests/vpci/emul.h b/tools/tests/vpci/emul.h
index 38211afbb8..01770dc851 100644
--- a/tools/tests/vpci/emul.h
+++ b/tools/tests/vpci/emul.h
@@ -101,6 +101,10 @@ typedef union {
 #define pci_get_pdev(...) (&test_pdev)
 #define pci_get_ro_map(...) NULL
 
+#define pci_vf_setup_pending(...) false
+#define vpci_sriov_add_vf(...) false
+#define vpci_sriov_drop_vfs(...)
+
 #define test_bit(...) false
 
 /* Dummy native helpers. Writes are ignored, reads return 1's. */
diff --git a/xen/drivers/passthrough/pci.c b/xen/drivers/passthrough/pci.c
index 578472d068..63d207b5d6 100644
--- a/xen/drivers/passthrough/pci.c
+++ b/xen/drivers/passthrough/pci.c
@@ -32,6 +32,7 @@
 #include <xen/softirq.h>
 #include <xen/tasklet.h>
 #include <xen/vpci.h>
+#include <xen/xvmalloc.h>
 #include <xen/msi.h>
 #include <xsm/xsm.h>
 #include "ats.h"
@@ -40,6 +41,8 @@ struct pci_seg {
     struct list_head alldevs_list;
     u16 nr;
     unsigned long *ro_map;
+    /* BDFs of VFs which have been enabled but which Xen has not added yet. */
+    unsigned long *vf_setup_map;
     /* bus2bridge_lock protects bus2bridge array */
     spinlock_t bus2bridge_lock;
 #define MAX_BUSES 256
@@ -139,6 +142,47 @@ const unsigned long *pci_get_ro_map(u16 seg)
     return pseg ? pseg->ro_map : NULL;
 }
 
+/*
+ * Marking is serialised by the hardware domain's pci_lock, which the SR-IOV
+ * control register write handler holds; unmarking only clears a bit.
+ */
+int pci_vf_setup_begin(pci_sbdf_t sbdf)
+{
+    struct pci_seg *pseg = get_pseg(sbdf.seg);
+
+    if ( !pseg )
+        return -ENODEV;
+
+    if ( !pseg->vf_setup_map )
+    {
+        unsigned int nr = PCI_BDF(-1, -1, -1) + 1;
+
+        pseg->vf_setup_map = xvzalloc_array(unsigned long, BITS_TO_LONGS(nr));
+        if ( !pseg->vf_setup_map )
+            return -ENOMEM;
+    }
+
+    set_bit(sbdf.bdf, pseg->vf_setup_map);
+
+    return 0;
+}
+
+void pci_vf_setup_end(pci_sbdf_t sbdf)
+{
+    const struct pci_seg *pseg = get_pseg(sbdf.seg);
+
+    if ( pseg && pseg->vf_setup_map )
+        clear_bit(sbdf.bdf, pseg->vf_setup_map);
+}
+
+bool pci_vf_setup_pending(pci_sbdf_t sbdf)
+{
+    const struct pci_seg *pseg = get_pseg(sbdf.seg);
+
+    return pseg && pseg->vf_setup_map &&
+           test_bit(sbdf.bdf, pseg->vf_setup_map);
+}
+
 static struct phantom_dev {
     u16 seg;
     u8 bus, slot, stride;
diff --git a/xen/drivers/pci/physdev.c b/xen/drivers/pci/physdev.c
index 5695c300f7..7c0277a7e4 100644
--- a/xen/drivers/pci/physdev.c
+++ b/xen/drivers/pci/physdev.c
@@ -26,15 +26,15 @@ ret_t pci_physdev_op(int cmd, XEN_GUEST_HANDLE_PARAM(void) 
arg)
         if ( copy_from_guest(&add, arg, 1) != 0 )
             break;
 
-        pdev_info.is_extfn = (add.flags & XEN_PCI_DEV_EXTFN);
+        /*
+         * Virtual functions are always discovered by the hypervisor, so
+         * they don't need to be added here.
+         */
         if ( add.flags & XEN_PCI_DEV_VIRTFN )
-        {
-            pdev_info.is_virtfn = true;
-            pdev_info.physfn.bus = add.physfn.bus;
-            pdev_info.physfn.devfn = add.physfn.devfn;
-        }
-        else
-            pdev_info.is_virtfn = false;
+            return 0;
+
+        pdev_info.is_extfn = (add.flags & XEN_PCI_DEV_EXTFN);
+        pdev_info.is_virtfn = false;
 
 #ifdef CONFIG_NUMA
         if ( add.flags & XEN_PCI_DEV_PXM )
diff --git a/xen/drivers/vpci/Makefile b/xen/drivers/vpci/Makefile
index 9793a4f9b0..3ad9a31f2d 100644
--- a/xen/drivers/vpci/Makefile
+++ b/xen/drivers/vpci/Makefile
@@ -1,3 +1,4 @@
 obj-y += cap.o
+obj-y += sriov.o
 obj-y += vpci.o header.o rebar.o
 obj-$(CONFIG_HAS_PCI_MSI) += msi.o msix.o
diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c
index 01881e5eb2..a07e547c64 100644
--- a/xen/drivers/vpci/header.c
+++ b/xen/drivers/vpci/header.c
@@ -629,8 +629,13 @@ static void cf_check cmd_write(
      * Let Dom0 play with all the bits directly except for the memory
      * decoding one. Bits that are not allowed for DomU are already
      * handled above and by the rsvdp_mask.
+     *
+     * PCI_COMMAND_MEMORY is hardwired to 0 for VFs, so for the hardware domain
+     * it must not control the mapping: VF memory space is enabled by MSE in 
the
+     * PF's SR-IOV control register.
      */
-    if ( header->bars_mapped != !!(cmd & PCI_COMMAND_MEMORY) )
+    if ( !(pdev->info.is_virtfn && is_hardware_domain(pdev->domain)) &&
+         header->bars_mapped != !!(cmd & PCI_COMMAND_MEMORY) )
         /*
          * Ignore the error. No memory has been added or removed from the p2m
          * (because the actual p2m changes are deferred in defer_map) and the
@@ -891,6 +896,9 @@ int vpci_init_header(struct pci_dev *pdev)
 
     header->guest_cmd = cmd;
 
+    if ( pdev->info.is_virtfn )
+        return vpci_vf_init_header(pdev);
+
     /* Disable memory decoding before sizing. */
     if ( !is_hwdom || (cmd & PCI_COMMAND_MEMORY) )
         pci_conf_write16(pdev->sbdf, PCI_COMMAND, cmd & ~PCI_COMMAND_MEMORY);
diff --git a/xen/drivers/vpci/private.h b/xen/drivers/vpci/private.h
index f012fd160d..1faf87a628 100644
--- a/xen/drivers/vpci/private.h
+++ b/xen/drivers/vpci/private.h
@@ -45,6 +45,7 @@ typedef struct {
     REGISTER_VPCI_CAPABILITY(PCI_EXT_CAP_ID_##name, name, finit, fclean, true)
 
 int __must_check vpci_init_header(struct pci_dev *pdev);
+int __must_check vpci_vf_init_header(struct pci_dev *pdev);
 
 int vpci_init_capabilities(struct pci_dev *pdev, bool ext_only);
 void vpci_cleanup_capabilities(struct pci_dev *pdev, bool ext_only);
@@ -141,6 +142,20 @@ static inline size_t vmsix_table_size(const struct vpci 
*vpci, unsigned int nr)
 /* Map/unmap the BARs of a vPCI device. */
 int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only);
 
+/*
+ * Handle a config space access to a VF which is enabled in hardware but not
+ * known to Xen yet.  Returns false while the VF need not respond, in which
+ * case the access has to be terminated.  Must be called with no PCI lock
+ * held, as adding a device acquires them.
+ */
+bool vpci_sriov_add_vf(pci_sbdf_t sbdf);
+
+/*
+ * Remove the VFs of the PF whose VF Enable the config space write in progress
+ * has cleared.  Same locking requirement as above.
+ */
+void vpci_sriov_drop_vfs(void);
+
 void cf_check vpci_guest_mem_bar_write(const struct pci_dev *pdev,
                                        unsigned int reg, uint32_t val,
                                        void *data);
diff --git a/xen/drivers/vpci/sriov.c b/xen/drivers/vpci/sriov.c
new file mode 100644
index 0000000000..4e09bdfbfd
--- /dev/null
+++ b/xen/drivers/vpci/sriov.c
@@ -0,0 +1,507 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Handlers for accesses to the SR-IOV capability structure.
+ *
+ * Copyright (C) 2026 Citrix Systems R&D
+ */
+
+#include "private.h"
+
+#include <xen/sched.h>
+#include <xen/softirq.h>
+#include <xen/time.h>
+#include <xen/vpci.h>
+#include <xen/xvmalloc.h>
+
+#include <xsm/xsm.h>
+
+static int vf_init_bars(struct pci_dev *vf_pdev)
+{
+    int vf_idx;
+    unsigned int i;
+    const struct pci_dev *pf_pdev = vf_pdev->pf_pdev;
+    struct vpci_bar *bars = vf_pdev->vpci->header.bars;
+    struct vpci_bar *physfn_vf_bars = pf_pdev->vpci->sriov->vf_bars;
+    struct vpci_sriov *sriov = pf_pdev->vpci->sriov;
+
+    vf_idx = vf_pdev->sbdf.sbdf - (pf_pdev->sbdf.sbdf + sriov->offset);
+    if ( vf_idx < 0 )
+        return -EINVAL;
+
+    if ( sriov->num_vfs > 1 && !sriov->stride )
+        return -EINVAL;
+
+    if ( sriov->stride )
+    {
+        if ( vf_idx % sriov->stride )
+            return -EINVAL;
+        vf_idx /= sriov->stride;
+    }
+
+    if ( vf_idx >= sriov->num_vfs )
+        return -EINVAL;
+
+    /*
+     * Set up BARs for this VF out of PF's VF BARs taking into account
+     * the index of the VF.
+     */
+    for ( i = 0; i < PCI_SRIOV_NUM_BARS; i++ )
+    {
+        struct vpci_bar *pf_bar = &physfn_vf_bars[i];
+        struct vpci_bar *bar = &bars[i];
+
+        /*
+         * The upper half of a 64-bit BAR holds no address or size of its own;
+         * the handlers reach the lower half through its type alone.
+         */
+        if ( pf_bar->type == VPCI_BAR_MEM64_HI )
+        {
+            bar->type = VPCI_BAR_MEM64_HI;
+            continue;
+        }
+
+        if ( pf_bar->type != VPCI_BAR_MEM32 &&
+             pf_bar->type != VPCI_BAR_MEM64_LO )
+            continue;
+
+        bar->addr         = pf_bar->addr + vf_idx * pf_bar->size;
+        bar->guest_addr   = is_hardware_domain(vf_pdev->domain) ? bar->addr : 
0;
+        bar->size         = pf_bar->size;
+        bar->type         = pf_bar->type;
+        bar->prefetchable = pf_bar->prefetchable;
+    }
+
+    return 0;
+}
+
+int vpci_vf_init_header(struct pci_dev *vf_pdev)
+{
+    const struct pci_dev *pf_pdev;
+    int rc = 0;
+    uint16_t ctrl;
+
+    ASSERT(rw_is_write_locked(&vf_pdev->domain->pci_lock));
+    ASSERT(vf_pdev->info.is_virtfn);
+
+    pf_pdev = vf_pdev->pf_pdev;
+    ASSERT(pf_pdev);
+
+    rc = vf_init_bars(vf_pdev);
+    if ( rc )
+        return rc;
+
+    ctrl = pci_conf_read16(pf_pdev->sbdf,
+                           pf_pdev->vpci->sriov->pos + PCI_SRIOV_CTRL);
+
+    if ( (pf_pdev->domain == vf_pdev->domain) && (ctrl & PCI_SRIOV_CTRL_MSE) )
+    {
+        rc = vpci_modify_bars(vf_pdev, PCI_COMMAND_MEMORY, false);
+        if ( rc )
+            return rc;
+    }
+
+    return rc;
+}
+
+static int map_vfs(const struct pci_dev *pf_pdev, uint16_t cmd)
+{
+    struct pci_dev *vf_pdev;
+    int rc;
+
+    ASSERT(rw_is_write_locked(&pf_pdev->domain->pci_lock));
+
+    list_for_each_entry(vf_pdev, &pf_pdev->vf_list, vf_list)
+    {
+        /* Don't try to change mapping if it is already in the correct state. 
*/
+        if ( vf_pdev->vpci->header.bars_mapped == !!cmd )
+            continue;
+
+        rc = vpci_modify_bars(vf_pdev, cmd, false);
+        if ( rc )
+        {
+            gprintk(XENLOG_ERR, "failed to %s VF %pp: %d\n",
+                    (cmd & PCI_COMMAND_MEMORY) ? "map" : "unmap",
+                    &vf_pdev->sbdf, rc);
+            return rc;
+        }
+    }
+
+    return 0;
+}
+
+static void size_vf_bars(const struct pci_dev *pf_pdev, unsigned int sriov_pos,
+                         uint64_t *vf_rlen)
+{
+    struct vpci_bar *bars = pf_pdev->vpci->sriov->vf_bars;
+    unsigned int i;
+    int rc = 0;
+
+    ASSERT(rw_is_write_locked(&pf_pdev->domain->pci_lock));
+    ASSERT(!pf_pdev->info.is_virtfn);
+    ASSERT(pf_pdev->vpci->sriov);
+
+    /* Set the BARs addresses and size. */
+    for ( i = 0; i < PCI_SRIOV_NUM_BARS; i += rc )
+    {
+        unsigned int idx = sriov_pos + PCI_SRIOV_BAR + i * 4;
+        uint32_t bar;
+        uint64_t addr, size;
+
+        bar = pci_conf_read32(pf_pdev->sbdf, idx);
+
+        rc = pci_size_mem_bar(pf_pdev->sbdf, idx, &addr, &size,
+                              PCI_BAR_VF |
+                              ((i == PCI_SRIOV_NUM_BARS - 1) ? PCI_BAR_LAST
+                                                             : 0));
+
+        /*
+         * Update vf_rlen on the PF. According to the spec the size of
+         * the BARs can change if the system page size register is
+         * modified, so always update rlen when enabling VFs.
+         */
+        vf_rlen[i] = size;
+
+        if ( !size )
+        {
+            bars[i].type = VPCI_BAR_EMPTY;
+            continue;
+        }
+
+        bars[i].addr = addr;
+        bars[i].guest_addr = addr;
+        bars[i].size = size;
+        bars[i].prefetchable = bar & PCI_BASE_ADDRESS_MEM_PREFETCH;
+
+        switch ( rc )
+        {
+        case 1:
+            bars[i].type = VPCI_BAR_MEM32;
+            break;
+
+        case 2:
+            bars[i].type = VPCI_BAR_MEM64_LO;
+            bars[i + 1].type = VPCI_BAR_MEM64_HI;
+            break;
+
+        default:
+            ASSERT_UNREACHABLE();
+            rc = 1;
+        }
+    }
+}
+
+/*
+ * Accesses to a marked BDF are terminated until 100ms after VF Enable; the
+ * first one past that adds the VF, which control_write() cannot do itself.
+ */
+static pci_sbdf_t vf_sbdf(const struct pci_dev *pf_pdev, unsigned int idx)
+{
+    const struct vpci_sriov *sriov = pf_pdev->vpci->sriov;
+    pci_sbdf_t sbdf = pf_pdev->sbdf;
+
+    sbdf.bdf += sriov->offset + sriov->stride * idx;
+
+    return sbdf;
+}
+
+static void mark_vfs(const struct pci_dev *pf_pdev)
+{
+    struct vpci_sriov *sriov = pf_pdev->vpci->sriov;
+    unsigned int i;
+
+    /* VF Offset and VF Stride are only valid while VF Enable is set. */
+    sriov->offset = pci_conf_read16(pf_pdev->sbdf,
+                                    sriov->pos + PCI_SRIOV_VF_OFFSET);
+    sriov->stride = pci_conf_read16(pf_pdev->sbdf,
+                                    sriov->pos + PCI_SRIOV_VF_STRIDE);
+    sriov->vfs_ready = NOW() + MILLISECS(100);
+
+    for ( i = 0; i < sriov->num_vfs; i++ )
+    {
+        pci_sbdf_t sbdf = vf_sbdf(pf_pdev, i);
+        int rc = pci_vf_setup_begin(sbdf);
+
+        if ( rc )
+            gprintk(XENLOG_WARNING, "%pp: cannot set up VF %pp: %d\n",
+                    &pf_pdev->sbdf, &sbdf, rc);
+    }
+}
+
+static void unmark_vfs(const struct pci_dev *pf_pdev)
+{
+    struct vpci_sriov *sriov = pf_pdev->vpci->sriov;
+    unsigned int i;
+
+    if ( !sriov->vfs_ready )
+        return;
+
+    for ( i = 0; i < sriov->num_vfs; i++ )
+        pci_vf_setup_end(vf_sbdf(pf_pdev, i));
+
+    sriov->vfs_ready = 0;
+}
+
+/* Find the PF which has enabled @sbdf, if that VF is ready to be added. */
+static const struct pci_dev *ready_pf(const struct domain *d, pci_sbdf_t sbdf)
+{
+    struct pci_dev *pdev;
+
+    ASSERT(rw_is_locked(&d->pci_lock));
+
+    for_each_pdev ( d, pdev )
+    {
+        const struct vpci_sriov *sriov = pdev->vpci ? pdev->vpci->sriov : NULL;
+        unsigned int i;
+
+        if ( !sriov || !sriov->vfs_ready || NOW() < sriov->vfs_ready )
+            continue;
+
+        for ( i = 0; i < sriov->num_vfs; i++ )
+            if ( vf_sbdf(pdev, i).sbdf == sbdf.sbdf )
+                return pdev;
+    }
+
+    return NULL;
+}
+
+bool vpci_sriov_add_vf(pci_sbdf_t sbdf)
+{
+    struct domain *d = current->domain;
+    struct pci_dev_info info = { .is_virtfn = true };
+    const struct pci_dev *pf;
+    pci_sbdf_t pf_sbdf;
+    nodeid_t node;
+    int rc;
+
+    read_lock(&d->pci_lock);
+    pf = ready_pf(d, sbdf);
+    if ( !pf )
+    {
+        read_unlock(&d->pci_lock);
+        gprintk(XENLOG_DEBUG, "%pp: config space access during VF setup\n",
+                &sbdf);
+        return false;
+    }
+
+    pf_sbdf = pf->sbdf;
+    info.physfn.bus = pf_sbdf.bus;
+    info.physfn.devfn = pf_sbdf.devfn;
+    node = pf->node;
+    read_unlock(&d->pci_lock);
+
+    rc = pci_add_device(sbdf.seg, sbdf.bus, sbdf.devfn, &info, node);
+    if ( rc )
+        gprintk(XENLOG_ERR, "%pp: cannot add VF %pp: %d\n", &pf_sbdf, &sbdf,
+                rc);
+
+    /*
+     * Stop terminating accesses either way: a VF which Xen failed to add is
+     * left for the hardware domain to register.
+     */
+    pci_vf_setup_end(sbdf);
+
+    return true;
+}
+
+void vpci_sriov_drop_vfs(void)
+{
+    struct vcpu *curr = current;
+    pci_sbdf_t pf_sbdf = curr->vpci.drop_vfs_pf;
+    struct pci_dev *pf, *vf, *tmp;
+
+    if ( likely(pf_sbdf.sbdf == ~0U) )
+        return;
+
+    curr->vpci.drop_vfs_pf.sbdf = ~0U;
+
+    /*
+     * The unmapping queued by control_write() still references the VF pdevs,
+     * so it has to complete before they are freed.
+     */
+    while ( vpci_process_pending(curr) )
+        process_pending_softirqs();
+
+    pcidevs_lock();
+    pf = pci_get_pdev(NULL, pf_sbdf);
+    if ( pf )
+    {
+        list_for_each_entry_safe(vf, tmp, &pf->vf_list, vf_list)
+        {
+            pci_sbdf_t sbdf = vf->sbdf;
+            int rc = pci_remove_device(sbdf.seg, sbdf.bus, sbdf.devfn);
+
+            if ( rc )
+                gprintk(XENLOG_ERR, "%pp: cannot remove VF %pp: %d\n",
+                        &pf_sbdf, &sbdf, rc);
+        }
+    }
+    pcidevs_unlock();
+}
+
+static void cf_check control_write(const struct pci_dev *pdev, unsigned int 
reg,
+                                   uint32_t val, void *data)
+{
+    unsigned int sriov_pos = reg - PCI_SRIOV_CTRL;
+    struct vpci_sriov *sriov = pdev->vpci->sriov;
+    uint16_t control = pci_conf_read16(pdev->sbdf, reg);
+    bool enabled = control & PCI_SRIOV_CTRL_VFE;
+    bool new_enabled = val & PCI_SRIOV_CTRL_VFE;
+    /* VF BARs decode only while both VF Enable and VF MSE are set. */
+    bool decoding = enabled && (control & PCI_SRIOV_CTRL_MSE);
+    bool new_decoding = new_enabled && (val & PCI_SRIOV_CTRL_MSE);
+    int rc;
+
+    ASSERT(!pdev->info.is_virtfn);
+
+    if ( new_enabled == enabled && new_decoding == decoding )
+    {
+        pci_conf_write16(pdev->sbdf, reg, val);
+        return;
+    }
+
+    /* Unmap while the VF pdevs are still around for vpci_modify_bars(). */
+    if ( decoding && !new_decoding )
+        map_vfs(pdev, 0);
+
+    if ( enabled && !new_enabled )
+    {
+        unmark_vfs(pdev);
+
+        /* The VFs cease to exist below, so Xen has to remove them. */
+        if ( !list_empty(&pdev->vf_list) )
+            current->vpci.drop_vfs_pf = pdev->sbdf;
+    }
+
+    if ( !enabled && new_enabled )
+    {
+        size_vf_bars(pdev, sriov_pos, data);
+
+        /*
+         * Only update the number of active VFs when enabling, when
+         * disabling use the cached value in order to always remove the same
+         * number of VFs that were active.
+         */
+        sriov->num_vfs = pci_conf_read16(pdev->sbdf,
+                                         sriov_pos + PCI_SRIOV_NUM_VF);
+        mark_vfs(pdev);
+    }
+
+    if ( !decoding && new_decoding )
+    {
+        rc = map_vfs(pdev, PCI_COMMAND_MEMORY);
+
+        if ( rc )
+            map_vfs(pdev, 0);
+    }
+
+    pci_conf_write16(pdev->sbdf, reg, val);
+}
+
+static int cf_check init_sriov(struct pci_dev *pdev)
+{
+    unsigned int pos;
+
+    if ( pdev->info.is_virtfn )
+        return -EINVAL;
+
+    pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_SRIOV);
+
+    ASSERT(pos);
+
+    if ( xsm_resource_setup_pci(XSM_PRIV, pdev->sbdf.bdf) )
+    {
+        printk(XENLOG_ERR
+               "%pp: SR-IOV configuration unsupported for unpriv %pd\n",
+               &pdev->sbdf, pdev->domain);
+        return -EACCES;
+    }
+
+    pdev->vpci->sriov = xvzalloc(struct vpci_sriov);
+    if ( !pdev->vpci->sriov )
+        return -ENOMEM;
+
+    pdev->vpci->sriov->pos = pos;
+
+    /*
+     * We need to modify vf_rlen in control_write but we can't do it cleanly
+     * from pdev because write callback only accepts const pdev. Moving vf_rlen
+     * inside of struct vpci_sriov is also not possible because it is used
+     * before vpci init. So pass it here as additional data to not require
+     * dropping const in control_write.
+     */
+    return vpci_add_register(pdev->vpci, vpci_hw_read16, control_write,
+                             pos + PCI_SRIOV_CTRL, 2, &pdev->physfn.vf_rlen);
+}
+
+static int cf_check cleanup_sriov(const struct pci_dev *pdev, bool hide)
+{
+    unsigned int pos;
+    int rc;
+
+    if ( !pdev->vpci->sriov )
+        return 0;
+
+    ASSERT(!pdev->info.is_virtfn);
+
+    unmark_vfs(pdev);
+
+    if ( !list_empty(&pdev->vf_list) )
+    {
+        struct pci_dev *vf_pdev, *temp;
+
+        printk(XENLOG_WARNING 
+               "Attempting to remove SR-IOV PF %pp with VFs still present, VFs 
will be forcefully removed first\n",
+               &pdev->sbdf);
+
+        map_vfs(pdev, 0);
+        list_for_each_entry_safe_reverse(vf_pdev, temp, &pdev->vf_list, 
vf_list)
+        {
+            pci_remove_device(vf_pdev->sbdf.seg, vf_pdev->sbdf.bus,
+                              vf_pdev->sbdf.devfn);
+        }
+    }
+
+    pos = pdev->vpci->sriov->pos;
+    if ( !hide )
+    {
+        XVFREE(pdev->vpci->sriov);
+        return 0;
+    }
+
+    rc = vpci_remove_registers(pdev->vpci, pos + PCI_SRIOV_CTRL, 2);
+    if ( rc )
+    {
+        printk(XENLOG_ERR "%pd %pp: fail to remove SRIOV handlers rc=%d\n",
+                pdev->domain, &pdev->sbdf, rc);
+        ASSERT_UNREACHABLE();
+        return rc;
+    }
+    XVFREE(pdev->vpci->sriov);
+
+    /*
+     * Unprivileged domains have a deny by default register access policy, no
+     * need to add any further handlers for them.
+     */
+    if ( !is_hardware_domain(pdev->domain) )
+        return 0;
+
+    rc = vpci_add_register(pdev->vpci, vpci_hw_read16, NULL,
+                           pos + PCI_SRIOV_CTRL, 2, NULL);
+    if ( rc )
+        printk(XENLOG_ERR "%pd %pp: fail to add SRIOV ctrl handler rc=%d\n",
+               pdev->domain, &pdev->sbdf, rc);
+
+    return rc;
+}
+
+REGISTER_VPCI_EXTCAP(SRIOV, init_sriov, cleanup_sriov);
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/drivers/vpci/vpci.c b/xen/drivers/vpci/vpci.c
index 9a422f9ea0..767fe5c6c9 100644
--- a/xen/drivers/vpci/vpci.c
+++ b/xen/drivers/vpci/vpci.c
@@ -27,6 +27,7 @@
 void vpci_vcpu_init(struct vcpu *v)
 {
     INIT_LIST_HEAD(&v->vpci.task_queue);
+    v->vpci.drop_vfs_pf.sbdf = ~0U;
 }
 
 #ifdef CONFIG_HAS_VPCI_GUEST_SUPPORT
@@ -486,6 +487,11 @@ uint32_t vpci_read(pci_sbdf_t sbdf, unsigned int reg, 
unsigned int size)
         return data;
     }
 
+    /* Ahead of any lock being taken, as adding a device acquires them. */
+    if ( unlikely(is_hardware_domain(d) && pci_vf_setup_pending(sbdf)) &&
+         !vpci_sriov_add_vf(sbdf) )
+        return data & (0xffffffffU >> (32 - 8 * size));
+
     /*
      * Find the PCI dev matching the address, which for hwdom also requires
      * consulting DomXEN.  Passthrough everything that's not trapped.
@@ -607,6 +613,11 @@ void vpci_write(pci_sbdf_t sbdf, unsigned int reg, 
unsigned int size,
         return;
     }
 
+    /* Ahead of any lock being taken, as adding a device acquires them. */
+    if ( unlikely(is_hardware_domain(d) && pci_vf_setup_pending(sbdf)) &&
+         !vpci_sriov_add_vf(sbdf) )
+        return;
+
     /*
      * Find the PCI dev matching the address, which for hwdom also requires
      * consulting DomXEN.  Passthrough everything that's not trapped.
@@ -680,6 +691,9 @@ void vpci_write(pci_sbdf_t sbdf, unsigned int reg, unsigned 
int size,
         /* Tailing gap, write the remaining. */
         vpci_write_hw(sbdf, reg + data_offset, size - data_offset,
                       data >> (data_offset * 8));
+
+    /* Only once every lock has been released, as removal acquires them. */
+    vpci_sriov_drop_vfs();
 }
 
 /* Helper function to check an access size and alignment on vpci space. */
diff --git a/xen/include/xen/pci.h b/xen/include/xen/pci.h
index b7a35371a7..b902c5e888 100644
--- a/xen/include/xen/pci.h
+++ b/xen/include/xen/pci.h
@@ -229,6 +229,9 @@ void setup_hwdom_pci_devices(struct domain *d,
 int pci_release_devices(struct domain *d);
 int pci_add_segment(u16 seg);
 const unsigned long *pci_get_ro_map(u16 seg);
+int pci_vf_setup_begin(pci_sbdf_t sbdf);
+void pci_vf_setup_end(pci_sbdf_t sbdf);
+bool pci_vf_setup_pending(pci_sbdf_t sbdf);
 int pci_add_device(u16 seg, u8 bus, u8 devfn,
                    const struct pci_dev_info *info, nodeid_t node);
 int pci_remove_device(u16 seg, u8 bus, u8 devfn);
diff --git a/xen/include/xen/vpci.h b/xen/include/xen/vpci.h
index 26aa120583..eead9487d0 100644
--- a/xen/include/xen/vpci.h
+++ b/xen/include/xen/vpci.h
@@ -4,6 +4,7 @@
 #ifdef CONFIG_HAS_VPCI
 
 #include <xen/pci.h>
+#include <xen/time.h>
 #include <xen/types.h>
 #include <xen/list.h>
 
@@ -84,7 +85,6 @@ struct vpci {
          * upon to know whether BARs are mapped into the guest p2m.
          */
         bool bars_mapped      : 1;
-        /* FIXME: currently there's no support for SR-IOV. */
     } header;
 
     /* MSI data. */
@@ -145,6 +145,22 @@ struct vpci {
         unsigned int nbars:3;
     } rebar;
 
+    struct vpci_sriov {
+        /* PF only */
+        struct vpci_bar vf_bars[PCI_SRIOV_NUM_BARS];
+        /*
+         * When the VFs which are enabled but not added to Xen yet are
+         * guaranteed to respond to config space accesses.  Zero if there are
+         * none.
+         */
+        s_time_t vfs_ready;
+        /* Routing ID offset of the first VF, and stride between VFs. */
+        uint16_t offset;
+        uint16_t stride;
+        uint16_t num_vfs;
+        unsigned int pos;
+    } *sriov;
+
 #ifdef CONFIG_HAS_VPCI_GUEST_SUPPORT
     /* Guest SBDF of the device. */
 #define INVALID_GUEST_SBDF ((pci_sbdf_t){ .sbdf = ~0U })
@@ -169,6 +185,12 @@ struct vpci_map_task {
 
 struct vpci_vcpu {
     struct list_head task_queue;
+    /*
+     * PF whose VFs have been disabled by the config space write in progress,
+     * and which Xen has to remove once the PCI locks have been dropped.  All
+     * ones when there is none.
+     */
+    pci_sbdf_t drop_vfs_pf;
 };
 
 void vpci_dump_msi(void);
-- 
2.55.0

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.