|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [PATCH v3 10/11] vpci: Add initial support for virtual PCI bus topology
Hi, Roger
On 26.10.21 14:33, Roger Pau Monné wrote:
> On Thu, Sep 30, 2021 at 10:52:22AM +0300, Oleksandr Andrushchenko wrote:
>> From: Oleksandr Andrushchenko <oleksandr_andrushchenko@xxxxxxxx>
>>
>> Assign SBDF to the PCI devices being passed through with bus 0.
>> The resulting topology is where PCIe devices reside on the bus 0 of the
>> root complex itself (embedded endpoints).
>> This implementation is limited to 32 devices which are allowed on
>> a single PCI bus.
>>
>> Signed-off-by: Oleksandr Andrushchenko <oleksandr_andrushchenko@xxxxxxxx>
>>
>> ---
>> Since v2:
>> - remove casts that are (a) malformed and (b) unnecessary
>> - add new line for better readability
>> - remove CONFIG_HAS_VPCI_GUEST_SUPPORT ifdef's as the relevant vPCI
>> functions are now completely gated with this config
>> - gate common code with CONFIG_HAS_VPCI_GUEST_SUPPORT
>> New in v2
>> ---
>> xen/common/domain.c | 3 ++
>> xen/drivers/passthrough/pci.c | 60 +++++++++++++++++++++++++++++++++++
>> xen/drivers/vpci/vpci.c | 14 +++++++-
>> xen/include/xen/pci.h | 22 +++++++++++++
>> xen/include/xen/sched.h | 8 +++++
>> 5 files changed, 106 insertions(+), 1 deletion(-)
>>
>> diff --git a/xen/common/domain.c b/xen/common/domain.c
>> index 40d67ec34232..e0170087612d 100644
>> --- a/xen/common/domain.c
>> +++ b/xen/common/domain.c
>> @@ -601,6 +601,9 @@ struct domain *domain_create(domid_t domid,
>>
>> #ifdef CONFIG_HAS_PCI
>> INIT_LIST_HEAD(&d->pdev_list);
>> +#ifdef CONFIG_HAS_VPCI_GUEST_SUPPORT
>> + INIT_LIST_HEAD(&d->vdev_list);
>> +#endif
>> #endif
>>
>> /* All error paths can depend on the above setup. */
>> diff --git a/xen/drivers/passthrough/pci.c b/xen/drivers/passthrough/pci.c
>> index 805ab86ed555..5b963d75d1ba 100644
>> --- a/xen/drivers/passthrough/pci.c
>> +++ b/xen/drivers/passthrough/pci.c
>> @@ -831,6 +831,66 @@ int pci_remove_device(u16 seg, u8 bus, u8 devfn)
>> return ret;
>> }
>>
>> +#ifdef CONFIG_HAS_VPCI_GUEST_SUPPORT
>> +static struct vpci_dev *pci_find_virtual_device(const struct domain *d,
>> + const struct pci_dev *pdev)
>> +{
>> + struct vpci_dev *vdev;
>> +
>> + list_for_each_entry ( vdev, &d->vdev_list, list )
>> + if ( vdev->pdev == pdev )
>> + return vdev;
>> + return NULL;
>> +}
>> +
>> +int pci_add_virtual_device(struct domain *d, const struct pci_dev *pdev)
>> +{
>> + struct vpci_dev *vdev;
>> +
>> + ASSERT(!pci_find_virtual_device(d, pdev));
>> +
>> + /* Each PCI bus supports 32 devices/slots at max. */
>> + if ( d->vpci_dev_next > 31 )
>> + return -ENOSPC;
>> +
>> + vdev = xzalloc(struct vpci_dev);
>> + if ( !vdev )
>> + return -ENOMEM;
>> +
>> + /* We emulate a single host bridge for the guest, so segment is always
>> 0. */
>> + vdev->seg = 0;
>> +
>> + /*
>> + * The bus number is set to 0, so virtual devices are seen
>> + * as embedded endpoints behind the root complex.
>> + */
>> + vdev->bus = 0;
>> + vdev->devfn = PCI_DEVFN(d->vpci_dev_next++, 0);
> This would likely be better as a bitmap where you set the bits of
> in-use slots. Then you can use find_first_bit or similar to get a free
> slot.
>
> Long term you might want to allow the caller to provide a pre-selected
> slot, as it's possible for users to request the device to appear at a
> specific slot on the emulated bus.
>
>> +
>> + vdev->pdev = pdev;
>> + vdev->domain = d;
>> +
>> + pcidevs_lock();
>> + list_add_tail(&vdev->list, &d->vdev_list);
>> + pcidevs_unlock();
>> +
>> + return 0;
>> +}
>> +
>> +int pci_remove_virtual_device(struct domain *d, const struct pci_dev *pdev)
>> +{
>> + struct vpci_dev *vdev;
>> +
>> + pcidevs_lock();
>> + vdev = pci_find_virtual_device(d, pdev);
>> + if ( vdev )
>> + list_del(&vdev->list);
>> + pcidevs_unlock();
>> + xfree(vdev);
>> + return 0;
>> +}
>> +#endif /* CONFIG_HAS_VPCI_GUEST_SUPPORT */
>> +
>> /* Caller should hold the pcidevs_lock */
>> static int deassign_device(struct domain *d, uint16_t seg, uint8_t bus,
>> uint8_t devfn)
>> diff --git a/xen/drivers/vpci/vpci.c b/xen/drivers/vpci/vpci.c
>> index 702f7b5d5dda..d787f13e679e 100644
>> --- a/xen/drivers/vpci/vpci.c
>> +++ b/xen/drivers/vpci/vpci.c
>> @@ -91,20 +91,32 @@ int __hwdom_init vpci_add_handlers(struct pci_dev *pdev)
>> /* Notify vPCI that device is assigned to guest. */
>> int vpci_assign_device(struct domain *d, const struct pci_dev *dev)
>> {
>> + int rc;
>> +
>> /* It only makes sense to assign for hwdom or guest domain. */
>> if ( is_system_domain(d) || !has_vpci(d) )
>> return 0;
>>
>> - return vpci_bar_add_handlers(d, dev);
>> + rc = vpci_bar_add_handlers(d, dev);
>> + if ( rc )
>> + return rc;
>> +
>> + return pci_add_virtual_device(d, dev);
>> }
>>
>> /* Notify vPCI that device is de-assigned from guest. */
>> int vpci_deassign_device(struct domain *d, const struct pci_dev *dev)
>> {
>> + int rc;
>> +
>> /* It only makes sense to de-assign from hwdom or guest domain. */
>> if ( is_system_domain(d) || !has_vpci(d) )
>> return 0;
>>
>> + rc = pci_remove_virtual_device(d, dev);
>> + if ( rc )
>> + return rc;
>> +
>> return vpci_bar_remove_handlers(d, dev);
>> }
>> #endif /* CONFIG_HAS_VPCI_GUEST_SUPPORT */
>> diff --git a/xen/include/xen/pci.h b/xen/include/xen/pci.h
>> index 43b8a0817076..33033a3a8f8d 100644
>> --- a/xen/include/xen/pci.h
>> +++ b/xen/include/xen/pci.h
>> @@ -137,6 +137,24 @@ struct pci_dev {
>> struct vpci *vpci;
>> };
>>
>> +#ifdef CONFIG_HAS_VPCI_GUEST_SUPPORT
>> +struct vpci_dev {
>> + struct list_head list;
>> + /* Physical PCI device this virtual device is connected to. */
>> + const struct pci_dev *pdev;
>> + /* Virtual SBDF of the device. */
>> + union {
>> + struct {
>> + uint8_t devfn;
>> + uint8_t bus;
>> + uint16_t seg;
>> + };
>> + pci_sbdf_t sbdf;
>> + };
>> + struct domain *domain;
>> +};
>> +#endif
> I wonder whether this is strictly needed. Won't it be enough to store
> the virtual (ie: guest) sbdf inside the existing vpci struct?
>
> It would avoid the overhead of the translation you do from pdev ->
> vdev, and there doesn't seem to be anything relevant stored in
> vpci_dev apart from the virtual sbdf.
TL;DR It seems it might be needed from performance POV. If not implemented
for every MMIO trap we use a global PCI lock, e.g. pcidevs_{lock|unlock}.
Note: pcidevs' lock is a recursive lock
There are 2 sources of access to virtual devices:
1. During initialization when we add, assign or de-assign a PCI device
2. At run-time when we trap configuration space access and need to
translate virtual SBDF into physical SBDF
3. At least de-assign can run concurrently with MMIO handlers
Now let's see which locks are in use while doing that.
1. No struct vpci_dev is used.
1.1. We remove the structure and just add pdev->vpci->guest_sbdf as you suggest
1.2. To protect virtual devices we use pcidevs_{lock|unlock}
1.3. Locking happens on system level
2. struct vpci_dev is used
2.1. We have a per-domain lock vdev_lock
2.2. Locking happens on per domain level
To compare the two:
1. Without vpci_dev
pros: much simpler code
pros/cons: global lock is used during MMIO handling, but it is a recursive lock
2. With vpc_dev
pros: per-domain locking
cons: more code
I have implemented the two methods and we need to decide
which route we go.
> Thanks, Roger.
Thank you,
Oleksandr
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |