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

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


  • To: Mykyta Poturai <Mykyta_Poturai@xxxxxxxx>
  • From: Jan Beulich <jbeulich@xxxxxxxx>
  • Date: Wed, 28 Oct 2026 14:49:14 +0100
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=google header.d=suse.com header.i="@suse.com" header.h="Content-Transfer-Encoding:Content-Type:In-Reply-To:Autocrypt:From:Content-Language:References:Cc:To:Subject:User-Agent:MIME-Version:Date:Message-ID"
  • Autocrypt: addr=jbeulich@xxxxxxxx; keydata= xsDiBFk3nEQRBADAEaSw6zC/EJkiwGPXbWtPxl2xCdSoeepS07jW8UgcHNurfHvUzogEq5xk hu507c3BarVjyWCJOylMNR98Yd8VqD9UfmX0Hb8/BrA+Hl6/DB/eqGptrf4BSRwcZQM32aZK 7Pj2XbGWIUrZrd70x1eAP9QE3P79Y2oLrsCgbZJfEwCgvz9JjGmQqQkRiTVzlZVCJYcyGGsD /0tbFCzD2h20ahe8rC1gbb3K3qk+LpBtvjBu1RY9drYk0NymiGbJWZgab6t1jM7sk2vuf0Py O9Hf9XBmK0uE9IgMaiCpc32XV9oASz6UJebwkX+zF2jG5I1BfnO9g7KlotcA/v5ClMjgo6Gl MDY4HxoSRu3i1cqqSDtVlt+AOVBJBACrZcnHAUSuCXBPy0jOlBhxPqRWv6ND4c9PH1xjQ3NP nxJuMBS8rnNg22uyfAgmBKNLpLgAGVRMZGaGoJObGf72s6TeIqKJo/LtggAS9qAUiuKVnygo 3wjfkS9A3DRO+SpU7JqWdsveeIQyeyEJ/8PTowmSQLakF+3fote9ybzd880fSmFuIEJldWxp Y2ggPGpiZXVsaWNoQHN1c2UuY29tPsJgBBMRAgAgBQJZN5xEAhsDBgsJCAcDAgQVAggDBBYC AwECHgECF4AACgkQoDSui/t3IH4J+wCfQ5jHdEjCRHj23O/5ttg9r9OIruwAn3103WUITZee e7Sbg12UgcQ5lv7SzsFNBFk3nEQQCACCuTjCjFOUdi5Nm244F+78kLghRcin/awv+IrTcIWF hUpSs1Y91iQQ7KItirz5uwCPlwejSJDQJLIS+QtJHaXDXeV6NI0Uef1hP20+y8qydDiVkv6l IreXjTb7DvksRgJNvCkWtYnlS3mYvQ9NzS9PhyALWbXnH6sIJd2O9lKS1Mrfq+y0IXCP10eS FFGg+Av3IQeFatkJAyju0PPthyTqxSI4lZYuJVPknzgaeuJv/2NccrPvmeDg6Coe7ZIeQ8Yj t0ARxu2xytAkkLCel1Lz1WLmwLstV30g80nkgZf/wr+/BXJW/oIvRlonUkxv+IbBM3dX2OV8 AmRv1ySWPTP7AAMFB/9PQK/VtlNUJvg8GXj9ootzrteGfVZVVT4XBJkfwBcpC/XcPzldjv+3 HYudvpdNK3lLujXeA5fLOH+Z/G9WBc5pFVSMocI71I8bT8lIAzreg0WvkWg5V2WZsUMlnDL9 mpwIGFhlbM3gfDMs7MPMu8YQRFVdUvtSpaAs8OFfGQ0ia3LGZcjA6Ik2+xcqscEJzNH+qh8V m5jjp28yZgaqTaRbg3M/+MTbMpicpZuqF4rnB0AQD12/3BNWDR6bmh+EkYSMcEIpQmBM51qM EKYTQGybRCjpnKHGOxG0rfFY1085mBDZCH5Kx0cl0HVJuQKC+dV2ZY5AqjcKwAxpE75MLFkr wkkEGBECAAkFAlk3nEQCGwwACgkQoDSui/t3IH7nnwCfcJWUDUFKdCsBH/E5d+0ZnMQi+G0A nAuWpQkjM1ASeQwSHEeAWPgskBQL
  • 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>, Julien Grall <julien@xxxxxxx>, Roger Pau Monné <roger@xxxxxxxxxxxxxx>, Stefano Stabellini <sstabellini@xxxxxxxxxx>, "Daniel P. Smith" <dpsmith@xxxxxxxxxxxxxxxxxxxx>, "xen-devel@xxxxxxxxxxxxxxxxxxxx" <xen-devel@xxxxxxxxxxxxxxxxxxxx>
  • Delivery-date: Wed, 28 Oct 2026 13:49:32 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>

On 15.10.2026 16:08, Mykyta Poturai wrote:
> --- 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.

Isn't this x86-only right now?

> --- 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.
> + */

What about pci_vf_setup_pending()? How's that (to be) serialized again marking
and unmarking?

> --- 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

When I added cap.o on a separate line, the intention was that new files would
follow suite, including the respecting of sorting. Imo sriov.o wants to go
after msi{,x}.o. Seeing how the latter are conditionally included - wouldn't
SR-IOV also want to have a HAS_SRIOV control?

> +/*
> + * 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;
> +}

The comment ahead of the function doesn't appear to be related to it. Does
it belong elsewhere?

> +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);

What if we end up reading 0 here, yet ->num_vfs is greater than 1? Or put
differently, ...

> +    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);

... wouldn't pci_vf_setup_begin() better indicate to its caller when the
BDF was already marked prior to the call? Also, if you didn't end up
setting the bit here, shouldn't ...

> +    }
> +}
> +
> +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));

... its clearing better also be avoided?

> +    sriov->vfs_ready = 0;

Might this better be done ahead of the loop?

> +}
> +
> +/* 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;

You return pointer-to-const, and nothing in the function alters *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;
> +    }

This loop can easily have hundreds of iterations, when the number of SR-IOV
PFs in a system is likely relatively small. I wonder whether there shouldn't
be a separate list of "PDEVs with VFs under construction".

> +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 call site has a comment that the call here happens after all locks
were dropped. To prevent two calls here racing, the check-and-update
here needs to be atomic imo.

> +    /*
> +     * 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();

This isn't very nice, yet I also don't have any better suggestion.

> +    pcidevs_lock();
> +    pf = pci_get_pdev(NULL, pf_sbdf);

Why is this a global lookup?

> +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;

Minimally an assertion may be wanted here, to check that drop_vfs_pf isn't
already in use.

> +static int cf_check init_sriov(struct pci_dev *pdev)
> +{
> +    unsigned int pos;
> +
> +    if ( pdev->info.is_virtfn )
> +        return -EINVAL;

Can this happen? I.e. doesn't this want to be an assertion, much like ...

> +    pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_SRIOV);
> +
> +    ASSERT(pos);

... this one?

> +    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;
> +    }

In addition to Jason's remark (which may require Daniel's input): Why "unpriv"
in the log message? That's fine when dummy or SILO are in use, but that may
be entirely off under Flask.

> +    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);
> +}

This kind of comment is at risk of going stale without anyone noticing. It's
anyway written more like something belonging in a commit message.

> +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);

What would be lost if "with VFs still present" was dropped from the log message?

> +        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);
> +        }

Nit: No need for figure braces here.

> +    }
> +
> +    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);

I'm inclined to suggest "re-add", so it's clear from just seeing the message in
a log that this isn't about init of the device.

> @@ -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));

Is there a reason you can't simply "return data" here, like is done in the
first conditional of the function?

> @@ -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 })

While for guests we have control over the range of valid SBDF values, ...

> @@ -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;

... I don't see what would guarantee an all-ones pci_sbdf_t to not represent
a real device.

Jan



 


Rackspace

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