Implement SR-IOV support for PVH
v4 cover Implement SR-IOV support for PVH
This series enables support for PCI SR-IOV capability for PVH domains. It allows Dom0 to enable and use SR-IOV virtual functions and for these functions to be passed to guests.
To achieve this, add handlers for SRIOV_CONTROL registers and simplified handlers for VF headers. Dom0 enables SR-IOV; Xen then discovers the VFs itself, terminating config space accesses to them for 100ms as the spec permits, and adding each VF on the first access past that window. No hypercall from dom0 is involved.
Core functionality is based on previous work[1].
Tested on R-Car Spider board with Samsung NVMe SSD Controller 980 and Intel X550T ethernet card.
[1]: https://lore.kernel.org/xen-devel/[email protected]/
v3->v4:
* rework the series for VF discovery in Xen
* squash export patches
* drop "vpci: Use pervcpu ranges for BAR mapping"; rangesets are now allocated per mapping task
* split the non-functional churn into "vpci: use a local variable for the BAR rangeset"
* fold the documentation patch into "add SR-IOV support for PVH Dom0"
* see individual patches
v2->v3:
* rework the series for VF discovery by Dom0
* drop pci/iommu: Check that IOMMU supports removing devices, see [2]
* drop vpci: add a wait operation to the vpci vcpu pending actions
* add vpci: allow queueing of mapping operations
* minor changes in individual patches
[2]: https://patchew.org/Xen/a59c2da0d4c72deb42950e9a8e3982fbdee60668.1775555766.git.mykyta._5Fpoturai@epam.com/v1->v2:
* rework the series for VF discovery in Xen
* separate doc changes into the last patch
Mykyta Poturai (1):
vpci: use a local variable for the BAR rangeset
Stewart Hildebrand (4):
vpci: Export functions required for SR-IOV support
vpci: allow queueing of mapping operations
vpci: add SR-IOV support for PVH Dom0
vpci: add SR-IOV support for DomUs
CHANGELOG.md | 1 + SUPPORT.md | 2 - tools/tests/vpci/emul.h | 4 + xen/common/domain.c | 2 + xen/drivers/passthrough/pci.c | 44 +++ xen/drivers/pci/physdev.c | 16 +- xen/drivers/vpci/Makefile | 1 + xen/drivers/vpci/header.c | 309 +++++++++++------- xen/drivers/vpci/private.h | 24 ++ xen/drivers/vpci/sriov.c | 568 ++++++++++++++++++++++++++++++++++ xen/drivers/vpci/vpci.c | 24 +- xen/include/xen/pci.h | 3 + xen/include/xen/vpci.h | 45 ++- 13 files changed, 909 insertions(+), 134 deletions(-) create mode 100644 xen/drivers/vpci/sriov.c
Signature · 1 line
2.55.0
PATCH v4 1/5 vpci: Export functions required for SR-IOV support
From: Stewart Hildebrand <[email protected]>
Rename and export vpci_modify_bars, vpci_guest_mem_bar_{read,write}
---v3->v4:
* squash "vpci: rename and export vpci_guest_mem_bar_{read,write}" with "vpci: rename and export vpci_modify_bars"v2->v3:
* move declaration to private.hv1->v2
* Collect RBs
--- xen/drivers/vpci/header.c | 36 ++++++++++++++++++++---------------- xen/drivers/vpci/private.h | 9 +++++++++ 2 files changed, 29 insertions(+), 16 deletions(-)
xen/drivers/vpci/header.c +20 -16
@@ -305,7 +305,7 @@ static void defer_map(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)raise_softirq (SCHEDULE_SOFTIRQ ); } -static int modify_bars (const struct pci_dev *pdev,uint16_t cmd,bool rom_only) +int vpci_modify_bars (const struct pci_dev *pdev,uint16_t cmd,bool rom_only) {struct vpci_header *header = &pdev->vpci->header;struct pci_dev *tmp;
@@ -546,7 +546,7 @@ static void cf_check cmd_write(* memory decoding bit has not been changed, so leave everything as-is, * hoping the guest will realize and try again. */ -modify_bars (pdev, cmd, false); +vpci_modify_bars (pdev, cmd, false); else pci_conf_write16(pdev->sbdf, reg, cmd); }
@@ -611,9 +611,9 @@ static void cf_check bar_write(pci_conf_write32 (pdev->sbdf, reg, val); } -static void cf_check guest_mem_bar_write(const struct pci_dev *pdev, -unsigned int reg,uint32_t val, -void *data) +void cf_check vpci_guest_mem_bar_write(const struct pci_dev *pdev, +unsigned int reg,uint32_t val, +void *data) {struct vpci_bar *bar = data;bool hi = false;
@@ -653,8 +653,8 @@ static void cf_check guest_mem_bar_write(const struct pci_dev *pdev, bar->guest_addr = guest_addr; } -static uint32_t cf_check guest_mem_bar_read(const struct pci_dev *pdev, -unsigned int reg,void *data) +uint32_t cf_check vpci_guest_mem_bar_read(const struct pci_dev *pdev, +unsigned int reg,void *data) {const struct vpci_bar *bar = data;uint32_t reg_val;
@@ -714,13 +714,15 @@ static void cf_check rom_write(* Pass PCI_COMMAND_MEMORY or 0 to signal a map/unmap request, note that * this fabricated command is never going to be written to the register. */ - else if ( modify_bars(pdev, new_enabled ? PCI_COMMAND_MEMORY : 0, true) ) + else if ( vpci_modify_bars(pdev, + new_enabled ? PCI_COMMAND_MEMORY : 0, + true) ) /* * No memory has been added or removed from the p2m (because the actual * p2m changes are deferred in defer_map) and the ROM enable bit has * not been changed, so leave everything as-is, hoping the guest will * realize and try again. It's important to not update rom->addr in the - * unmap case if modify_bars has failed, or future attempts would + * unmap case if vpci_modify_bars has failed, or future attempts would * attempt to unmap the wrong address. */ return;
@@ -800,8 +802,8 @@ int vpci_init_header(struct pci_dev *pdev)/* * For DomUs, clear PCI_COMMAND_{MASTER,MEMORY,IO} and other * DomU-controllable bits in PCI_COMMAND. Devices assigned to DomUs will -* start with memory decoding disabled, and modify_bars() will not be called -* at the end of this function. +* start with memory decoding disabled, and vpci_modify_bars() will not be +* called at the end of this function. */ if ( !is_hwdom ) cmd &= ~(PCI_COMMAND_VGA_PALETTE | PCI_COMMAND_INVALIDATE |
@@ -824,8 +826,9 @@ int vpci_init_header(struct pci_dev *pdev) bars[i].type =VPCI_BAR_MEM64_HI ; rc =vpci_add_register (pdev->vpci, is_hwdom ? vpci_hw_read32 - : guest_mem_bar_read, - is_hwdom ? bar_write : guest_mem_bar_write, + : vpci_guest_mem_bar_read, + is_hwdom ? bar_write + : vpci_guest_mem_bar_write, reg,4 , &bars[i]);if ( rc ) goto fail;
@@ -883,8 +886,9 @@ int vpci_init_header(struct pci_dev *pdev) bars[i].prefetchable = val &PCI_BASE_ADDRESS_MEM_PREFETCH ; rc =vpci_add_register (pdev->vpci, - is_hwdom ? vpci_hw_read32 : guest_mem_bar_read, - is_hwdom ? bar_write : guest_mem_bar_write, + is_hwdom ? vpci_hw_read32 + : vpci_guest_mem_bar_read, + is_hwdom ? bar_write : vpci_guest_mem_bar_write, reg,4 , &bars[i]);if ( rc ) goto fail;
@@ -926,7 +930,7 @@ int vpci_init_header(struct pci_dev *pdev) goto fail; } -return (cmd &PCI_COMMAND_MEMORY ) ?modify_bars (pdev, cmd, false) :0 ; +return (cmd &PCI_COMMAND_MEMORY ) ?vpci_modify_bars (pdev, cmd, false) :0 ; fail:pci_conf_write16 (pdev->sbdf,PCI_COMMAND , cmd);
xen/drivers/vpci/private.h +9 -0
@@ -138,6 +138,15 @@ static inline size_t vmsix_table_size(const struct vpci *vpci, unsigned int nr) 8), 8); } +/* Map/unmap the BARs of a vPCI device. */ +int vpci_modify_bars (const struct pci_dev *pdev,uint16_t cmd,bool rom_only); + +void cf_check vpci_guest_mem_bar_write(const struct pci_dev *pdev, +unsigned int reg,uint32_t val, +void *data); + +uint32_t cf_check vpci_guest_mem_bar_read(const struct pci_dev *pdev, +unsigned int reg,void *data);#endif /* __XEN__ */ #endif /* VPCI_PRIVATE_H */
Signature · 1 line
2.55.0
Re: [PATCH v4 1/5] vpci: Export functions required for SR-IOV support
Quoted 7 lines · Mykyta Poturai
From: Stewart Hildebrand <[email protected]>
Rename and export
vpci_modify_bars, vpci_guest_mem_bar_{read,write}Signed-off-by Stewart Hildebrand <[email protected]>Signed-off-by Mykyta Poturai <[email protected]>Reviewed-by Teddy Astie <[email protected]>
PATCH v4 2/5 vpci: use a local variable for the BAR rangeset
In reply to Mykyta PoturaiFrom: Mykyta Poturai <[email protected]>
In the BAR (un)mapping paths latch the rangeset of the BAR being processed into a local variable, instead of dereferencing struct vpci_bar on every use. Preparatory work for moving the rangesets out of struct vpci_bar.
No functional change.
---v3->v4:
* new patch, split out of "vpci: Use pervcpu ranges for BAR mapping" so that the non-functional churn is separate from the functional change
--- xen/drivers/vpci/header.c | 41 +++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 19 deletions(-)
xen/drivers/vpci/header.c +22 -19
@@ -196,6 +196,7 @@ bool vpci_process_pending(struct vcpu *v)for ( i =0 ; i <ARRAY_SIZE (header->bars); i++ ) {struct vpci_bar *bar = &header->bars[i]; +struct rangeset *mem = bar->mem;struct map_data data = { .d = v->domain, .map = v->vpci.cmd &PCI_COMMAND_MEMORY ,
@@ -203,10 +204,10 @@ bool vpci_process_pending(struct vcpu *v) };int rc; -if (rangeset_is_empty (bar->mem) ) +if (rangeset_is_empty (mem) )continue ; - rc =rangeset_consume_ranges (bar->mem, map_range, &data); + rc =rangeset_consume_ranges (mem, map_range, &data);if ( rc == -ERESTART ) {
@@ -218,8 +219,7 @@ bool vpci_process_pending(struct vcpu *v) {spin_lock (&pdev->vpci->lock);/* Disable memory decoding unconditionally on failure. */ -modify_decoding (pdev, v->vpci.cmd & ~PCI_COMMAND_MEMORY , - false); +modify_decoding (pdev, v->vpci.cmd & ~PCI_COMMAND_MEMORY , false);spin_unlock (&pdev->vpci->lock);/* Clean all the rangesets */
@@ -260,13 +260,14 @@ static int __init apply_map(struct domain *d, const struct pci_dev *pdev,for ( i =0 ; i <ARRAY_SIZE (header->bars); i++ ) {struct vpci_bar *bar = &header->bars[i]; +struct rangeset *mem = bar->mem;struct map_data data = { .d = d, .map = true, .bar = bar }; -if (rangeset_is_empty (bar->mem) ) +if (rangeset_is_empty (mem) )continue ; -while ( (rc =rangeset_consume_ranges (bar->mem, map_range, - &data)) == -ERESTART ) +while ( (rc =rangeset_consume_ranges (mem, map_range, &data)) == + -ERESTART ) {/* * It's safe to drop and reacquire the lock in this context
@@ -331,12 +332,13 @@ int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)for ( i =0 ; i <ARRAY_SIZE (header->bars); i++ ) {struct vpci_bar *bar = &header->bars[i]; +struct rangeset *mem = bar->mem;unsigned long start =PFN_DOWN (bar->addr);unsigned long end =PFN_DOWN (bar->addr + bar->size -1 );unsigned long start_guest =PFN_DOWN (bar->guest_addr);unsigned long end_guest =PFN_DOWN (bar->guest_addr + bar->size -1 ); -if ( !bar->mem ) +if ( !mem )continue ; if ( !MAPPABLE_BAR(bar) ||
@@ -354,7 +356,7 @@ int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)continue ; } -ASSERT (rangeset_is_empty (bar->mem)); +ASSERT (rangeset_is_empty (mem));/* * Make sure that the guest set address has the same page offset
@@ -369,7 +371,7 @@ int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)return -EINVAL ; } - rc =rangeset_add_range (bar->mem, start_guest, end_guest); + rc =rangeset_add_range (mem, start_guest, end_guest);if ( rc ) { printk(XENLOG_G_WARNING "Failed to add [%lx, %lx]: %d\n",
@@ -380,12 +382,12 @@ int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)/* Check for overlap with the already setup BAR ranges. */ for ( j =0 ; j < i; j++ ) { -struct vpci_bar *prev_bar = &header->bars[j]; +struct rangeset *prev_mem = header->bars[j].mem; -if (rangeset_is_empty (prev_bar->mem) ) +if (rangeset_is_empty (prev_mem) )continue ; - rc =rangeset_remove_range (prev_bar->mem, start_guest, end_guest); + rc =rangeset_remove_range (prev_mem, start_guest, end_guest);if ( rc ) { gprintk(XENLOG_WARNING,
@@ -395,7 +397,7 @@ int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) } } - rc =pci_sanitize_bar_memory (bar->mem); + rc =pci_sanitize_bar_memory (mem);if ( rc ) { gprintk(XENLOG_WARNING,
@@ -414,12 +416,12 @@ int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)for ( j =0 ; j <ARRAY_SIZE (header->bars); j++ ) { -const struct vpci_bar *bar = &header->bars[j]; +struct rangeset *mem = header->bars[j].mem; -if (rangeset_is_empty (bar->mem) ) +if (rangeset_is_empty (mem) )continue ; - rc =rangeset_remove_range (bar->mem, start, end); + rc =rangeset_remove_range (mem, start, end);if ( rc ) { gprintk(XENLOG_WARNING,
@@ -469,8 +471,9 @@ int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)for ( j =0 ; j <ARRAY_SIZE (header->bars); j++) {const struct vpci_bar *bar = &header->bars[j]; +struct rangeset *mem = bar->mem; - if ( !rangeset_overlaps_range(bar->mem, start, end) || + if ( !rangeset_overlaps_range(mem, start, end) ||/* * If only the ROM enable bit is toggled check against * other BARs in the same device for overlaps, but not
@@ -481,7 +484,7 @@ int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) bar->type == VPCI_BAR_ROM) )continue ; - rc =rangeset_remove_range (bar->mem, start, end); + rc =rangeset_remove_range (mem, start, end);if ( rc ) { gprintk(XENLOG_WARNING,
Signature · 1 line
2.55.0
Re: [PATCH v4 2/5] vpci: use a local variable for the BAR rangeset
Quoted 11 lines · Mykyta Poturai
From: Mykyta Poturai <[email protected]>
In the BAR (un)mapping paths latch the rangeset of the BAR being processed into a local variable, instead of dereferencing struct
vpci_baron every use. Preparatory work for moving the rangesets out of structvpci_bar.No functional change.
Signed-off-by Mykyta Poturai <[email protected]>Signed-off-by Stewart Hildebrand <[email protected]>
I guess you need a second S-o-B after Stewart's since you are submitting? Unless...
Quoted 4 lines
---
v3->v4:
* new patch, split out of "vpci: Use pervcpu ranges for BAR mapping" so that the non-functional churn is separate from the functional change
... it is something totally created by you. Then you can drop Stewart - I think.
xen/drivers/vpci/header.c bool vpci_process_pending(struct vcpu *v) · line 222
Quoted 7 lines
xen/drivers/vpci/header.c +1
-2@@ -218,8 +219,7 @@ bool vpci_process_pending(struct vcpu *v) {spin_lock (&pdev->vpci->lock);/* Disable memory decoding unconditionally on failure. */ -modify_decoding (pdev, v->vpci.cmd & ~PCI_COMMAND_MEMORY , - false); +modify_decoding (pdev, v->vpci.cmd & ~PCI_COMMAND_MEMORY , false);
This seems like an unrelated change. I think you should drop it and incorporate into the next patch touching this line.
With that:
PATCH v4 3/5 vpci: allow queueing of mapping operations
In reply to Mykyta PoturaiFrom: Stewart Hildebrand <[email protected]>
Introduce vPCI BAR mapping task queue. Store information needed to map/unmap BARs in struct vpci_map_task. Allow queueing of BAR map/unmap operations in a list, thus making it possible to perform multiple p2m operations associated with single PCI config space register access.
The rangesets describing the ranges to be (un)mapped move from struct vpci_bar into the task. They are only live for the duration of the operation, and a single per-device set cannot describe more than one queued operation. Allocate them along with the task and free them once the task has been processed.
This is preparatory work for further changes that need to perform multiple unmap/map operations before returning to guest.
At the moment, only a single operation will be queued. However, when multiple operations are queued, there is a check in modify_bars() to skip BARs already in the requested state that will no longer be accurate. Remove this check in preparation of upcoming changes.
Amends: 622bdd962822 ("vpci/header: handle p2m range sets per BAR")
Message-ID: <[email protected]>
---v3->v4:
* drop "vpci: Use pervcpu ranges for BAR mapping"; the rangesets are now allocated per task here rather than per vcpu, so that patch only added code this one removed again
* non-functional churn split out into "vpci: use a local variable for the BAR rangeset"
v2->v3:
* new patch in this series, borrowed from [1]
[1]: https://patchew.org/Xen/[email protected]/[email protected]/
--- xen/common/domain.c | 2 + xen/drivers/vpci/header.c | 232 ++++++++++++++++++++++++-------------- xen/drivers/vpci/vpci.c | 10 +- xen/include/xen/vpci.h | 21 +++- 4 files changed, 173 insertions(+), 92 deletions(-)
xen/common/domain.c +2 -0
@@ -508,6 +508,8 @@ struct vcpu *vcpu_create(struct domain *d, unsigned int vcpu_id)if (arch_vcpu_create (v) !=0 ) goto fail_sched; +vpci_vcpu_init (v); + d->vcpu[vcpu_id] = v;if ( vcpu_id !=0 ) {
xen/drivers/vpci/header.c +149 -83
413 lines not on this page · raw message
xen/drivers/vpci/vpci.c +5 -5
@@ -24,6 +24,11 @@ #ifdef __XEN__ +void vpci_vcpu_init(struct vcpu *v) +{ + INIT_LIST_HEAD(&v->vpci.task_queue); +} + #ifdef CONFIG_HAS_VPCI_GUEST_SUPPORT static int assign_virtual_sbdf(struct pci_dev *pdev) {
@@ -89,8 +94,6 @@ struct vpci_register *vpci_get_register(const struct vpci *vpci,void vpci_deassign_device (struct pci_dev *pdev) { -unsigned int i; -ASSERT (rw_is_write_locked (&pdev->domain->pci_lock));if ( !has_vpci (pdev->domain) || !pdev->vpci )
@@ -116,9 +119,6 @@ void vpci_deassign_device(struct pci_dev *pdev) }spin_unlock (&pdev->vpci->lock); -for ( i =0 ; i <ARRAY_SIZE (pdev->vpci->header.bars); i++ ) -rangeset_destroy (pdev->vpci->header.bars[i].mem); -xfree (pdev->vpci); pdev->vpci =NULL ; }
xen/include/xen/vpci.h +17 -4
@@ -19,6 +19,8 @@*/ #define VPCI_MAX_VIRT_DEV (PCI_SLOT(~0) + 1) +void vpci_vcpu_init (struct vcpu *v); +/* Assign vPCI to device by adding handlers. */ int __must_check vpci_assign_device (struct pci_dev *pdev);
@@ -54,7 +56,6 @@ struct vpci {uint64_t guest_addr;uint64_t size;uint64_t resizable_sizes; -struct rangeset *mem;enum {VPCI_BAR_EMPTY ,VPCI_BAR_IO ,
@@ -152,14 +153,24 @@ struct vpci {#endif }; -struct vpci_vcpu { +#ifdef __XEN__ +struct vpci_map_task {/* Per-vcpu structure to store state while {un}mapping of PCI BARs. */ -const struct pci_dev *pdev; +struct list_head next; +struct vpci_bar_map { +uint64_t addr; +uint64_t guest_addr; +struct rangeset *mem; + } bars[ARRAY_SIZE (((struct vpci_header *)NULL )->bars)];uint16_t cmd;bool rom_only :1 ; +const struct pci_dev *pdev; +}; + +struct vpci_vcpu { +struct list_head task_queue; }; -#ifdef __XEN__void vpci_dump_msi (void );/* Arch-specific vPCI MSI helpers. */
@@ -204,6 +215,8 @@ bool vpci_ecam_read(pci_sbdf_t sbdf, unsigned int reg, unsigned int len,#else /* !CONFIG_HAS_VPCI */ struct vpci_vcpu {}; +static inline void vpci_vcpu_init (struct vcpu *v) { } +static inline int vpci_reinit_ext_capabilities (struct pci_dev *pdev) {return 0 ;
Signature · 1 line
2.55.0
Re: [PATCH v4 3/5] vpci: allow queueing of mapping operations
Quoted 10 lines · Mykyta Poturai
From: Stewart Hildebrand <[email protected]>
Introduce vPCI BAR mapping task queue. Store information needed to map/unmap BARs in struct
vpci_map_task. Allow queueing of BAR map/unmap operations in a list, thus making it possible to perform multiple p2m operations associated with single PCI config space register access.The rangesets describing the ranges to be (un)mapped move from struct
vpci_barinto the task. They are only live for the duration of the operation, and a single per-device set cannot describe more than one
"per-device rangeset"?
Quoted 17 lines
queued operation. Allocate them along with the task and free them once the task has been processed.
This is preparatory work for further changes that need to perform multiple unmap/map operations before returning to guest.
At the moment, only a single operation will be queued. However, when multiple operations are queued, there is a check in
modify_bars()to skip BARs already in the requested state that will no longer be accurate. Remove this check in preparation of upcoming changes.Amends:
622bdd962822("vpci/header: handle p2m range sets per BAR")
Message-ID: <[email protected]>Signed-off-by Stewart Hildebrand <[email protected]>Signed-off-by Mykyta Poturai <[email protected]>
xen/drivers/vpci/header.c static int __init apply_map(struct domain *d, const struct pci_dev *pdev, · line 329
Quoted 10 lines
xen/drivers/vpci/header.c +8
-1@@ -280,12 +321,48 @@ static int __init apply_map(struct domain *d, const struct pci_dev *pdev, -static void defer_map (const struct pci_dev *pdev,uint16_t cmd,bool rom_only) +static struct vpci_map_task *alloc_map_task (const struct pci_dev *pdev, +uint16_t cmd,bool rom_only) +{ +struct vpci_map_task *task; +unsigned int i; + + task = xvzalloc(struct vpci_map_task); +
Nit: No newline needed here.
xen/drivers/vpci/header.c static int __init apply_map(struct domain *d, const struct pci_dev *pdev, · line 347
Quoted 11 lines
xen/drivers/vpci/header.c +11
-0+if ( !task ) +return NULL ; + +for ( i =0 ; i <ARRAY_SIZE (task->bars); i++ ) + { +if ( !MAPPABLE_BAR (&pdev->vpci->header.bars[i]) ) +continue ; + + task->bars[i].mem =rangeset_new (pdev->domain,NULL , + RANGESETF_no_print); +
Nit: Extra newline.
Quoted 24 lines
xen/drivers/vpci/header.c +18
-0+if ( !task->bars[i].mem ) + { +destroy_map_task (task); +return NULL ; + } + + task->bars[i].addr = pdev->vpci->header.bars[i].addr; + task->bars[i].guest_addr = pdev->vpci->header.bars[i].guest_addr; + } + + task->cmd = cmd; + task->rom_only = rom_only; + task->pdev = pdev; + +return task; +} + +static void defer_map (struct vpci_map_task *task) {struct vcpu *curr = current;@@ -312,11 +388,17 @@ int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only)struct pci_dev *tmp;
xen/drivers/vpci/header.c int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) · line 398
Quoted 10 lines
xen/drivers/vpci/header.c +3
-0const struct domain *d;const struct vpci_msix *msix = pdev->vpci->msix; +struct vpci_map_task *task;unsigned int i, j;int rc;ASSERT (rw_is_write_locked (&pdev->domain->pci_lock)); + task =alloc_map_task (pdev, cmd, rom_only); +
Nit: Extra newline
Quoted 8 lines
xen/drivers/vpci/header.c +3
-0+if ( !task ) +return -ENOMEM ; +/* * Create a rangeset per BAR that represents the current device memory * region and compare it against all the currently active BAR memory
xen/drivers/vpci/header.c int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) · line 348
Quoted 6 lines
xen/drivers/vpci/header.c +0
-3@@ -343,9 +425,7 @@ int vpci_modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) if ( !MAPPABLE_BAR(bar) || (rom_only ? bar->type != VPCI_BAR_ROM - : (bar->type ==VPCI_BAR_ROM && !header->rom_enabled)) || -/* Skip BARs already in the requested state. */ - bar->enabled == !!(cmd &PCI_COMMAND_MEMORY ) )
This is the removed check mentioned in the commit message? And the reason is that a queue of operation 1 unmap, operation 2 map needs to be processed sequentially, but cmd won't be changing?
Quoted 13 lines
xen/drivers/vpci/header.c +3
-1+ : (bar->type == VPCI_BAR_ROM && !header->rom_enabled)) )continue ;if ( !pci_check_bar (pdev,_mfn (start),_mfn (end)) )@@ -152,14 +153,24 @@ struct vpci {#endif }; -struct vpci_vcpu { +#ifdef __XEN__ +struct vpci_map_task {/* Per-vcpu structure to store state while {un}mapping of PCI BARs. */
xen/drivers/vpci/header.c struct vpci { · line 167
Quoted 10 lines
xen/drivers/vpci/header.c +7
-1-const struct pci_dev *pdev; +struct list_head next; +struct vpci_bar_map { +uint64_t addr; +uint64_t guest_addr; +struct rangeset *mem; + } bars[ARRAY_SIZE (((struct vpci_header *)NULL )->bars)];uint16_t cmd; bool rom_only : 1; +const struct pci_dev *pdev;
Is there a reason to move this pointer? You introduce a hole in the structure before it.
Thanks,
Jason
Re: [PATCH v4 3/5] vpci: allow queueing of mapping operations
In reply to Mykyta Poturaixen/drivers/vpci/vpci.c line 30
Quoted 10 lines · Mykyta Poturai
xen/drivers/vpci/vpci.c +4
-0@@ -24,6 +24,11 @@#ifdef __XEN__ +void vpci_vcpu_init (struct vcpu *v) +{ +INIT_LIST_HEAD (&v->vpci.task_queue); +}
If there's more that's going to be added to this function, having it out-of- line is fine. If it stays like that, it can be inline (and then there won't be a need for a separate stub).
Quoted 28 lines
xen/include/xen/vpci.h +4
-3@@ -19,6 +19,8 @@*/ #define VPCI_MAX_VIRT_DEV (PCI_SLOT(~0) + 1) +void vpci_vcpu_init (struct vcpu *v); +/* Assign vPCI to device by adding handlers. */ int __must_check vpci_assign_device (struct pci_dev *pdev);@@ -54,7 +56,6 @@ struct vpci {uint64_t guest_addr;uint64_t size;uint64_t resizable_sizes; -struct rangeset *mem;enum {VPCI_BAR_EMPTY ,VPCI_BAR_IO ,@@ -152,14 +153,24 @@ struct vpci {#endif }; -struct vpci_vcpu { +#ifdef __XEN__ +struct vpci_map_task {/* Per-vcpu structure to store state while {un}mapping of PCI BARs. */ -const struct pci_dev *pdev;
xen/include/xen/vpci.h struct vpci { · line 168
Quoted 10 lines
xen/include/xen/vpci.h +8
-0+struct list_head next; +struct vpci_bar_map { +uint64_t addr; +uint64_t guest_addr; +struct rangeset *mem; + } bars[ARRAY_SIZE (((struct vpci_header *)NULL )->bars)];uint16_t cmd; bool rom_only : 1; +const struct pci_dev *pdev; +};
As this struct isn't used anywhere in the header itself, it can likely move to vPCI's private.h.
Jan
Quoted 17 lines
xen/include/xen/vpci.h +4
-1+struct vpci_vcpu { +struct list_head task_queue; }; -#ifdef __XEN__void vpci_dump_msi (void );/* Arch-specific vPCI MSI helpers. */ @@ -204,6 +215,8 @@ bool vpci_ecam_read(pci_sbdf_t sbdf, unsigned int reg, unsigned int len,#else /* !CONFIG_HAS_VPCI */ struct vpci_vcpu {}; +static inline void vpci_vcpu_init (struct vcpu *v) { } +static inline int vpci_reinit_ext_capabilities (struct pci_dev *pdev) {return 0 ;
PATCH v4 4/5 vpci: add SR-IOV support for PVH Dom0
In reply to Mykyta PoturaiFrom: Stewart Hildebrand <[email protected]>
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.
---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
CHANGELOG.md +1 -0
@@ -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:
SUPPORT.md +0 -2
@@ -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.
tools/tests/vpci/emul.h +4 -0
@@ -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. */
xen/drivers/passthrough/pci.c +44 -0
62 lines not on this page · raw message
xen/drivers/pci/physdev.c +8 -8
@@ -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 )
xen/drivers/vpci/Makefile +1 -0
@@ -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
xen/drivers/vpci/header.c +9 -1
@@ -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 );
xen/drivers/vpci/private.h +15 -0
@@ -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);
xen/drivers/vpci/sriov.c new file +507 -0
507 lines not on this page · raw message
xen/drivers/vpci/vpci.c +14 -0
@@ -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. */
xen/include/xen/pci.h +3 -0
@@ -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);
xen/include/xen/vpci.h +23 -1
@@ -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 );
Signature · 1 line
2.55.0
Re: [PATCH v4 4/5] vpci: add SR-IOV support for PVH Dom0
Quoted 16 lines · Mykyta Poturai
From: Stewart Hildebrand <[email protected]>
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.
Quoted 8 lines
Signed-off-by Stewart Hildebrand <[email protected]>Signed-off-by Roger Pau Monné <[email protected]>Signed-off-by Mykyta Poturai <[email protected]>+/*
+ * Marking is serialised by the hardware domain'spci_lock, which the SR-IOV
+ * control register write handler holds; unmarking only clears a bit.
Maybe add an ASSERT()?
Quoted 27 lines
+ */
xen/drivers/passthrough/pci.c +20
-0+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 ; +}xen/drivers/pci/physdev.c +0
-0
xen/drivers/pci/physdev.c ret_t pci_physdev_op(int cmd, XEN_GUEST_HANDLE_PARAM(void) arg) · line 31
Quoted 7 lines
xen/drivers/pci/physdev.c +3
-1@@ -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.
Does a PV dom0 still need this path? vPCI won't be used in that case.
Quoted 47 lines
xen/drivers/pci/physdev.c +5
-7+*/ 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 )xen/drivers/vpci/sriov.c new file +23
-0@@ -0,0 +1,507 @@ +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; + } +
xen/drivers/vpci/sriov.c line 50
Quoted 10 lines
xen/drivers/vpci/sriov.c new file +10
-0+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];
Maybe just remove physfn_vf_bars since it is only used once?
Quoted 7 lines
xen/drivers/vpci/sriov.c new file +7
-0+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 )
xen/drivers/vpci/sriov.c line 67
Quoted 10 lines
xen/drivers/vpci/sriov.c new file +10
-0+ { + 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;
AIUI, the PF has the SR-IOV BARs, which are sized like normal. But I thought the pf_bar->size needs to be reduced by some factor to get the per-VF size. I can't find where that is done. Or am I wrong about this?
Quoted 35 lines
xen/drivers/vpci/sriov.c new file +34
-0+ 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 ; +} +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 ; +} +
xen/drivers/vpci/sriov.c line 141
Quoted 10 lines
xen/drivers/vpci/sriov.c new file +10
-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);
AIUI, because sriov was already dereferenced above, this can be dropped by the compiler. At least if (!pf_pdev->vpci->sriov) can be dropped.
Quoted 54 lines
xen/drivers/vpci/sriov.c new file +52
-0+ +/* 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 ; + } + } +} +static int cf_check init_sriov(struct pci_dev *pdev) +{
xen/drivers/vpci/sriov.c
Quoted 10 lines
xen/drivers/vpci/sriov.c new file +10
-0+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) )
This is a little misleading. During boot, current is Xen - d[IDLE]. With my XSM_HW_PRIV patches, I failed this check since Xen is not the hardware domain.
It seems to me the point of this check is to only expose the SR-IOV capability to a suitable domain. If the PF is assigned to a domU - then don't show the SR-IOV capability.
SR-IOV and its BARs is more of a hardware domain responsibility, so I think this should just be an is_hardware_domain() check. And I think it should be based on pdev->domain and not current. On the cleanup side, this is already checking is_hardware_domain(pdev->domain).
Quoted 31 lines
xen/drivers/vpci/sriov.c new file +31
-0+ { +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 ;
xen/drivers/vpci/sriov.c line 452
Quoted 10 lines
xen/drivers/vpci/sriov.c new file +10
-0+ +ASSERT (!pdev->info.is_virtfn); + +unmark_vfs (pdev); + +if ( !list_empty (&pdev->vf_list) ) + { +struct pci_dev *vf_pdev, *temp; + + printk(XENLOG_WARNING
Thunderbird stripped it from my reply, but there is trailing space above.
Quoted 23 lines
xen/drivers/vpci/sriov.c new file +23
-0+"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);
xen/drivers/vpci/sriov.c line 485
Quoted 10 lines
xen/drivers/vpci/sriov.c new file +10
-0+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) )
This is the counterpart to the earlier XSM check.
Quoted 28 lines
xen/drivers/vpci/sriov.c new file +22
-0+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: +*/ xen/drivers/vpci/vpci.c +0
-0
xen/drivers/vpci/vpci.c uint32_t vpci_read(pci_sbdf_t sbdf, unsigned int reg, unsigned int size) · line 491
Quoted 5 lines
xen/drivers/vpci/vpci.c +2
-0@@ -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)) &&
unlikely() here breaks the tests build. Add
#define unlikely
to tools/tests/vpci/emul.h
Quoted 19 lines
xen/drivers/vpci/vpci.c +8
-0+ !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.
xen/drivers/vpci/vpci.c void vpci_write(pci_sbdf_t sbdf, unsigned int reg, unsigned int size, · line 696
Quoted 6 lines
xen/drivers/vpci/vpci.c +3
-0@@ -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 ();
Hooking every vpci_write() for this seems a little unfortunate. Could it be hooked into the vpci softirq?
Quoted 17 lines
xen/drivers/vpci/vpci.c +0
-0}/* Helper function to check an access size and alignment on vpci space. */ xen/include/xen/vpci.h +4
-0@@ -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 ]; +/*
xen/include/xen/vpci.h struct vpci { · line 161
Quoted 10 lines
xen/include/xen/vpci.h +10
-0+* 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;
unsigned int before uint16_t packs the structure a little better.
Thanks,
Jason
Re: [PATCH v4 4/5] vpci: add SR-IOV support for PVH Dom0
Quoted 14 lines · Jason Andryuk
Quoted 13 lines · Mykyta Poturai
From: Stewart Hildebrand <[email protected]>
+ 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;
Quoted 8 lines
xen/drivers/vpci/sriov.c line 67
Quoted 7 lines
+ }
xen/drivers/vpci/sriov.c new file +6
-0+ + 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;AIUI, the PF has the SR-IOV BARs, which are sized like normal. But I thought the
pf_bar->sizeneeds to be reduced by some factor to get the per-VF size. I can't find where that is done. Or am I wrong about this?
Ok, I looked around and I think I was wrong. The SR-IOV bar returns the single VF size, and it is up to the OS to multiply by the number of VFs.
Thanks,
Jason
Re: [PATCH v4 4/5] vpci: add SR-IOV support for PVH Dom0
In reply to Jason AndryukQuoted 8 lines · Jason Andryuk
Quoted 7 lines · Mykyta Poturai
xen/drivers/vpci/vpci.c +0
-0@@ -486,6 +487,11 @@ uint32_t vpci_read(pci_sbdf_t sbdf, unsigned int reg, unsigned int size)return data; }
Quoted 6 lines
xen/drivers/vpci/vpci.c uint32_t vpci_read(pci_sbdf_t sbdf, unsigned int reg, unsigned int size) · line 491
Quoted 2 lines
xen/drivers/vpci/vpci.c +2
-0+/* Ahead of any lock being taken, as adding a device acquires them. */ + if ( unlikely(is_hardware_domain(d) && pci_vf_setup_pending(sbdf)) &&
unlikely()here breaks the tests build. Add#define unlikelyto
tools/tests/vpci/emul.h
Or better omit unlikely() here. It's not used properly anyway: Unless it's known that the compiler will translate an && expression to a single branch, each part of && (or || ) wants wrapping, not the entire expression.
Quoted 7 lines
Quoted 7 lines
xen/include/xen/vpci.h +0
-0@@ -145,6 +145,22 @@ struct vpci { unsigned int nbars:3; } rebar;
Quoted 16 lines
Quoted 4 lines
xen/include/xen/vpci.h +4
-0+struct vpci_sriov { +/* PF only */ +struct vpci_bar vf_bars[PCI_SRIOV_NUM_BARS ]; +/* xen/include/xen/vpci.h struct vpci { · line 161
Quoted 10 lines
xen/include/xen/vpci.h +10
-0+* 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;unsigned int before
uint16_tpacks the structure a little better.
As per ./CODING_STYLE, the use of uint16_t may not be justified here anyway.
Jan
Re: [PATCH v4 4/5] vpci: add SR-IOV support for PVH Dom0
In reply to Mykyta PoturaiCHANGELOG.md The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · line 19
Quoted 7 lines · Mykyta Poturai
CHANGELOG.md +1
-0@@ -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?
Quoted 20 lines
xen/drivers/passthrough/pci.c +3
-0@@ -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
xen/drivers/passthrough/pci.c const unsigned long *pci_get_ro_map(u16 seg) · line 148
Quoted 7 lines
xen/drivers/passthrough/pci.c +4
-0@@ -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?
xen/drivers/vpci/Makefile line 4
Quoted 7 lines
xen/drivers/vpci/Makefile +1
-0@@ -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?
xen/drivers/vpci/Makefile
Quoted 13 lines
xen/drivers/vpci/Makefile +13
-0+/* + * 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?
xen/drivers/vpci/Makefile
Quoted 10 lines
xen/drivers/vpci/Makefile +10
-0+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, ...
xen/drivers/vpci/Makefile
Quoted 10 lines
xen/drivers/vpci/Makefile +10
-0+ 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 ...
xen/drivers/vpci/Makefile
Quoted 13 lines
xen/drivers/vpci/Makefile +13
-0+ } +} + +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?
Quoted 1 line
+
sriov->vfs_ready= 0;
Might this better be done ahead of the loop?
xen/drivers/vpci/Makefile
Quoted 6 lines
xen/drivers/vpci/Makefile +6
-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;
You return pointer-to-const, and nothing in the function alters *pdev.
Quoted 4 lines
xen/drivers/vpci/Makefile +4
-0+ ASSERT(rw_is_locked(&d->pci_lock)); + + for_each_pdev ( d, pdev ) + {
xen/drivers/vpci/Makefile
Quoted 10 lines
xen/drivers/vpci/Makefile +10
-0+ 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".
xen/drivers/vpci/Makefile
Quoted 10 lines
xen/drivers/vpci/Makefile +10
-0+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.
xen/drivers/vpci/Makefile
Quoted 6 lines
xen/drivers/vpci/Makefile +6
-0+ /* + * 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.
xen/drivers/vpci/Makefile
Quoted 2 lines
xen/drivers/vpci/Makefile +2
-0+ pcidevs_lock(); + pf = pci_get_pdev(NULL, pf_sbdf);
Why is this a global lookup?
Quoted 22 lines
xen/drivers/vpci/Makefile +22
-0+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(). */
xen/drivers/vpci/Makefile
Quoted 10 lines
xen/drivers/vpci/Makefile +10
-0+ 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.
xen/drivers/vpci/Makefile
Quoted 6 lines
xen/drivers/vpci/Makefile +6
-0+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 ...
xen/drivers/vpci/Makefile
Quoted 3 lines
xen/drivers/vpci/Makefile +3
-0+ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_SRIOV); + + ASSERT(pos);
... this one?
xen/drivers/vpci/Makefile
Quoted 7 lines
xen/drivers/vpci/Makefile +7
-0+ 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.
Quoted 6 lines
xen/drivers/vpci/Makefile +6
-0+ pdev->vpci->sriov = xvzalloc(struct vpci_sriov); + if ( !pdev->vpci->sriov ) + return -ENOMEM; + + pdev->vpci->sriov->pos = pos; +
xen/drivers/vpci/Makefile
Quoted 10 lines
xen/drivers/vpci/Makefile +10
-0+ /* + * 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.
Quoted 9 lines
xen/drivers/vpci/Makefile +9
-0+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);
xen/drivers/vpci/Makefile
Quoted 10 lines
xen/drivers/vpci/Makefile +10
-0+ + 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?
xen/drivers/vpci/Makefile
Quoted 6 lines
xen/drivers/vpci/Makefile +6
-0+ 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.
Quoted 21 lines
xen/drivers/vpci/Makefile +21
-0+ } + + 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
xen/drivers/vpci/Makefile
Quoted 10 lines
xen/drivers/vpci/Makefile +10
-0+ * 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.
xen/drivers/vpci/Makefile uint32_t vpci_read(pci_sbdf_t sbdf, unsigned int reg, unsigned int size) · line 493
Quoted 8 lines
xen/drivers/vpci/Makefile +4
-0@@ -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?
Quoted 13 lines
xen/drivers/vpci/Makefile +9
-0@@ -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;
xen/drivers/vpci/Makefile struct vpci { · line 166
Quoted 10 lines
xen/drivers/vpci/Makefile +7
-0+ /* 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, ...
xen/drivers/vpci/Makefile struct vpci_map_task { · line 193
Quoted 10 lines
xen/drivers/vpci/Makefile +6
-0@@ -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
Re: [PATCH v4 4/5] vpci: add SR-IOV support for PVH Dom0
Quoted 34 lines · Jan Beulich
CHANGELOG.md The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · line 19
Quoted 7 lines · Mykyta Poturai
CHANGELOG.md +1
-0@@ -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?
Quoted 23 lines
xen/drivers/passthrough/pci.c +3
-0@@ -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 ; }
Quoted 8 lines
xen/drivers/passthrough/pci.c const unsigned long *pci_get_ro_map(u16 seg) · line 148
Quoted 4 lines
xen/drivers/passthrough/pci.c +4
-0+/* +* 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?
It isn't. The test runs ahead of any lock because adding the VF takes them.
We could put it under the hardware domain's pci_lock, which is what control_write() holds for marking and unmarking, but it would have to be dropped before vpci_sriov_add_vf() anyway: pci_add_device() takes pcidevs_lock, and the order is pcidevs_lock then pci_lock. So the test and the add can't be made atomic with the locks we have. Is the lock worth taking just for the bit test?
The only thing I can see actually misfiring is vf_setup_map: xvzalloc_array() zeroes it and the pointer store publishes it with nothing in between, so a reader can theoretically see the pointer ahead of the zeroing and test an uninitialised bit. Should we reallocate it in alloc_pseg(), or just add a memory barrier?
Re: [PATCH v4 4/5] vpci: add SR-IOV support for PVH Dom0
Quoted 14 lines · Mykyta Poturai
Quoted 5 lines · Jan Beulich
Quoted 4 lines · Mykyta Poturai
xen/drivers/passthrough/pci.c +0
-0@@ -139,6 +142,47 @@ const unsigned long *pci_get_ro_map(u16 seg)return pseg ? pseg->ro_map :NULL ; }Quoted 8 lines
xen/drivers/passthrough/pci.c const unsigned long *pci_get_ro_map(u16 seg) · line 148
Quoted 4 lines
xen/drivers/passthrough/pci.c +4
-0+/* +* 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?
Quoted 8 lines
It isn't. The test runs ahead of any lock because adding the VF takes them.
We could put it under the hardware domain's
pci_lock, which is whatcontrol_write()holds for marking and unmarking, but it would have to be dropped beforevpci_sriov_add_vf()anyway:pci_add_device()takespcidevs_lock, and the order ispcidevs_lockthenpci_lock. So the test and the add can't be made atomic with the locks we have. Is the lock worth taking just for the bit test?
No, such locking would make sense only when held from before the test until after the result was actually acted upon (in the case here: the bit was cleared by pci_vf_setup_end()).
pcidevs_lock vs d->pci_lock wouldn't really be an issue here, though: The former is recursive, so you could acquire ahead of acquiring d->pci_lock. The "inner" acquire in pci_add_device() would then be little more than a no-op. (Of course proper commentary would need adding if such an approach was taken.)
Quoted 4 lines
The only thing I can see actually misfiring is
vf_setup_map:xvzalloc_array()zeroes it and the pointer store publishes it with nothing in between, so a reader can theoretically see the pointer ahead of the zeroing and test an uninitialised bit. Should we reallocate it inalloc_pseg(), or just add a memory barrier?
Hmm, is this really the only issue? What if two racing pci_vf_setup_pending() both yield true? Then we'd end up with two competing vpci_sriov_add_vf().
Jan
Re: [PATCH v4 4/5] vpci: add SR-IOV support for PVH Dom0
Quoted 28 lines · Jan Beulich
Quoted 14 lines · Mykyta Poturai
Quoted 5 lines · Jan Beulich
Quoted 4 lines · Mykyta Poturai
xen/drivers/passthrough/pci.c +0
-0@@ -139,6 +142,47 @@ const unsigned long *pci_get_ro_map(u16 seg)return pseg ? pseg->ro_map :NULL ; }Quoted 8 lines
xen/drivers/passthrough/pci.c const unsigned long *pci_get_ro_map(u16 seg) · line 148
Quoted 4 lines
xen/drivers/passthrough/pci.c +4
-0+/* +* 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?Quoted 8 lines
It isn't. The test runs ahead of any lock because adding the VF takes them.
We could put it under the hardware domain's
pci_lock, which is whatcontrol_write()holds for marking and unmarking, but it would have to be dropped beforevpci_sriov_add_vf()anyway:pci_add_device()takespcidevs_lock, and the order ispcidevs_lockthenpci_lock. So the test and the add can't be made atomic with the locks we have. Is the lock worth taking just for the bit test?No, such locking would make sense only when held from before the test until after the result was actually acted upon (in the case here: the bit was cleared by
pci_vf_setup_end()).
Quoted 6 lines
pcidevs_lockvsd->pci_lockwouldn't really be an issue here, though: The former is recursive, so you could acquire ahead of acquiringd->pci_lock. The "inner" acquire inpci_add_device()would then be little more than a no-op. (Of course proper commentary would need adding if such an approach was taken.)
Missed that it was recursive somehow, so yes, checking under the lock is clearly better. Will do this in the next version.
Quoted 9 lines
Quoted 4 lines
The only thing I can see actually misfiring is
vf_setup_map:xvzalloc_array()zeroes it and the pointer store publishes it with nothing in between, so a reader can theoretically see the pointer ahead of the zeroing and test an uninitialised bit. Should we reallocate it inalloc_pseg(), or just add a memory barrier?Hmm, is this really the only issue? What if two racing
pci_vf_setup_pending()both yield true? Then we'd end up with two competingvpci_sriov_add_vf().Jan
Re: [PATCH v4 4/5] vpci: add SR-IOV support for PVH Dom0
In reply to Jan BeulichQuoted 14 lines · Jan Beulich
xen/drivers/vpci/Makefile line 4
Quoted 7 lines · Mykyta Poturai
xen/drivers/vpci/Makefile +1
-0@@ -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.oWhen 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_SRIOVcontrol?
I will fix the sorting, but I think guarding every PCI cap under a config option seems like an overkill to me. MSI/X being an exception because it depends heavily on platform support (having a working ITS on Arm at least).
Quoted 10 lines
Quoted 10 lines
xen/drivers/vpci/Makefile +10
-0+ 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; +
Quoted 9 lines
xen/drivers/vpci/Makefile
Quoted 4 lines
xen/drivers/vpci/Makefile +4
-0+ 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".
It can take a decent number of iterations, but it only runs when there is a VF still waiting to be added, so it is once per VF Enable and never on a normal vPCI access. And even a few hundred iterations of a loop like this is nothing next to the pci_add_device() that same access performs. I don't see a reason to introduce the complexity of another list for that.
Quoted 9 lines
xen/drivers/vpci/Makefile
Quoted 6 lines
xen/drivers/vpci/Makefile +6
-0+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 ...
Quoted 6 lines
xen/drivers/vpci/Makefile
Quoted 3 lines
xen/drivers/vpci/Makefile +3
-0+ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_SRIOV); + + ASSERT(pos);... this one?
I think it is better to leave it to have some protection for malicious/faulty devices that have VFs report SR-IOV caps themselves. Not sure if such devices exist but still the check costs nothing.
PATCH v4 5/5 vpci: add SR-IOV support for DomUs
In reply to Mykyta PoturaiFrom: Stewart Hildebrand <[email protected]>
Emulate guest BAR register values based on PF BAR values for VFs. This allows creating a guest view of the normal BAR registers and emulates the size and properties as it is done during PCI device enumeration by the guest.
Expose VID/DID and class/revision to the guest.
Note, that VFs ROM BAR is read-only and is all zeros, but VF may provide access to the PFs ROM via emulation and is not implemented.
Note this only removes one of the gaps on the way to vPCI DomU support; VFs are not usable in DomUs until the rest of it is upstream. The DomU testing behind this patch used out-of-tree vPCI DomU patches.
---v3->v4:
* emulate VID/DID again
* add a note to commit message
v2->v3:
* don't emulate VID/DID registers
* replace ifdef with IS_ENABLED
v1->v2:
* remove VF register handlers covered by init_header
* set guest addr unconditionally
--- xen/drivers/vpci/sriov.c | 65 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-)
xen/drivers/vpci/sriov.c +63 -2
@@ -79,19 +79,80 @@ int vpci_vf_init_header(struct pci_dev *vf_pdev)const struct pci_dev *pf_pdev;int rc =0 ;uint16_t ctrl; +unsigned int sriov_pos;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); + sriov_pos = pf_pdev->vpci->sriov->pos; 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 ); + ctrl =pci_conf_read16 (pf_pdev->sbdf, sriov_pos +PCI_SRIOV_CTRL ); + +if (IS_ENABLED (CONFIG_HAS_VPCI_GUEST_SUPPORT ) && + pf_pdev->domain != vf_pdev->domain ) + { +struct vpci_bar *bars = vf_pdev->vpci->header.bars; +/* +* A VF's Vendor and Device ID registers read as 0xffff. The guest has +* no PF to synthesise them from, so take the vendor from the PF and +* the device from its SR-IOV capability. +*/ +uint16_t vid =pci_conf_read16 (pf_pdev->sbdf,PCI_VENDOR_ID ); +uint16_t did =pci_conf_read16 (pf_pdev->sbdf, + sriov_pos +PCI_SRIOV_VF_DID ); + + rc =vpci_add_register (vf_pdev->vpci, vpci_read_val,NULL , +PCI_VENDOR_ID ,2 , (void *)(uintptr_t )vid); +if ( rc ) +return rc; + + rc =vpci_add_register (vf_pdev->vpci, vpci_read_val,NULL , +PCI_DEVICE_ID ,2 , (void *)(uintptr_t )did); +if ( rc ) +return rc; + +/* Hardcode multi-function device bit to 0 */ + rc =vpci_add_register (vf_pdev->vpci, vpci_read_val,NULL , +PCI_HEADER_TYPE ,1 , + (void *)PCI_HEADER_TYPE_NORMAL ); +if ( rc ) +return rc; + + rc =vpci_add_register (vf_pdev->vpci, vpci_hw_read32,NULL , +PCI_CLASS_REVISION ,4 ,NULL ); +if ( rc ) +return rc; + +for (unsigned int i =0 ; i <PCI_SRIOV_NUM_BARS ; i++ ) + { +switch ( pf_pdev->vpci->sriov->vf_bars[i].type ) + { +case VPCI_BAR_MEM32 : +case VPCI_BAR_MEM64_LO : +case VPCI_BAR_MEM64_HI : + rc =vpci_add_register (vf_pdev->vpci, vpci_guest_mem_bar_read, + vpci_guest_mem_bar_write, +PCI_BASE_ADDRESS_0 + i *4 ,4 , &bars[i]); +if ( rc ) +return rc; +break ; + +default : + rc =vpci_add_register (vf_pdev->vpci, vpci_read_val,NULL , +PCI_BASE_ADDRESS_0 + i *4 ,4 , + (void *)0 ); +if ( rc ) +return rc; +break ; + } + } + }if ( (pf_pdev->domain == vf_pdev->domain) && (ctrl &PCI_SRIOV_CTRL_MSE ) ) {
Signature · 1 line
2.55.0
Re: [PATCH v4 5/5] vpci: add SR-IOV support for DomUs
Quoted 12 lines · Mykyta Poturai
From: Stewart Hildebrand <[email protected]>
Emulate guest BAR register values based on PF BAR values for VFs. This allows creating a guest view of the normal BAR registers and emulates the size and properties as it is done during PCI device enumeration by the guest.
Expose VID/DID and class/revision to the guest.
Note, that VFs ROM BAR is read-only and is all zeros, but VF may provide access to the PFs ROM via emulation and is not implemented.
Quoted 6 lines
Note this only removes one of the gaps on the way to vPCI DomU support; VFs are not usable in DomUs until the rest of it is upstream. The DomU testing behind this patch used out-of-tree vPCI DomU patches.
Signed-off-by Stewart Hildebrand <[email protected]>Signed-off-by Mykyta Poturai <[email protected]>
Thanks,
Jason
Re: [PATCH v4 5/5] vpci: add SR-IOV support for DomUs
In reply to Mykyta PoturaiQuoted 4 lines · Mykyta Poturai
xen/drivers/vpci/sriov.c +0
-0@@ -79,19 +79,80 @@ int vpci_vf_init_header(struct pci_dev *vf_pdev)const struct pci_dev *pf_pdev;
xen/drivers/vpci/sriov.c int vpci_vf_init_header(struct pci_dev *vf_pdev) · line 89
Quoted 10 lines
xen/drivers/vpci/sriov.c +2
-0int rc =0 ;uint16_t ctrl; +unsigned int sriov_pos;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); + sriov_pos = pf_pdev->vpci->sriov->pos;
Can't this move ...
xen/drivers/vpci/sriov.c int vpci_vf_init_header(struct pci_dev *vf_pdev) · line 93
Quoted 3 lines
xen/drivers/vpci/sriov.c +0
-0rc =vf_init_bars (vf_pdev);if ( rc )return rc;
... here?
Quoted 9 lines
xen/drivers/vpci/sriov.c +7
-2- ctrl =pci_conf_read16 (pf_pdev->sbdf, - pf_pdev->vpci->sriov->pos +PCI_SRIOV_CTRL ); + ctrl =pci_conf_read16 (pf_pdev->sbdf, sriov_pos +PCI_SRIOV_CTRL ); + +if (IS_ENABLED (CONFIG_HAS_VPCI_GUEST_SUPPORT ) && + pf_pdev->domain != vf_pdev->domain ) + { +struct vpci_bar *bars = vf_pdev->vpci->header.bars; +/*
xen/drivers/vpci/sriov.c int vpci_vf_init_header(struct pci_dev *vf_pdev) · line 111
Quoted 10 lines
xen/drivers/vpci/sriov.c +10
-0+* A VF's Vendor and Device ID registers read as 0xffff. The guest has +* no PF to synthesise them from, so take the vendor from the PF and +* the device from its SR-IOV capability. +*/ +uint16_t vid =pci_conf_read16 (pf_pdev->sbdf,PCI_VENDOR_ID ); +uint16_t did =pci_conf_read16 (pf_pdev->sbdf, + sriov_pos +PCI_SRIOV_VF_DID ); + + rc =vpci_add_register (vf_pdev->vpci, vpci_read_val,NULL , +PCI_VENDOR_ID ,2 , (void *)(uintptr_t )vid);
This effectively open-codes _p().
xen/drivers/vpci/sriov.c int vpci_vf_init_header(struct pci_dev *vf_pdev) · line 116
Quoted 5 lines
xen/drivers/vpci/sriov.c +5
-0+if ( rc ) +return rc; + + rc =vpci_add_register (vf_pdev->vpci, vpci_read_val,NULL , +PCI_DEVICE_ID ,2 , (void *)(uintptr_t )did);
Same here.
xen/drivers/vpci/sriov.c int vpci_vf_init_header(struct pci_dev *vf_pdev) · line 123
Quoted 7 lines
xen/drivers/vpci/sriov.c +7
-0+if ( rc ) +return rc; + +/* Hardcode multi-function device bit to 0 */ + rc =vpci_add_register (vf_pdev->vpci, vpci_read_val,NULL , +PCI_HEADER_TYPE ,1 , + (void *)PCI_HEADER_TYPE_NORMAL );
And this works only because PCI_HEADER_TYPE_NORMAL happens to be 0. Imo _p() wants using here as well.
xen/drivers/vpci/sriov.c int vpci_vf_init_header(struct pci_dev *vf_pdev) · line 130
Quoted 7 lines
xen/drivers/vpci/sriov.c +7
-0+if ( rc ) +return rc; + + rc =vpci_add_register (vf_pdev->vpci, vpci_hw_read32,NULL , +PCI_CLASS_REVISION ,4 ,NULL ); +if ( rc ) +return rc;
How is this SR-IOV-related?
Quoted 8 lines
xen/drivers/vpci/sriov.c +8
-0+for (unsigned int i =0 ; i <PCI_SRIOV_NUM_BARS ; i++ ) + { +switch ( pf_pdev->vpci->sriov->vf_bars[i].type ) + { +case VPCI_BAR_MEM32 : +case VPCI_BAR_MEM64_LO : +case VPCI_BAR_MEM64_HI : + rc = vpci_add_register(vf_pdev->vpci, vpci_guest_mem_bar_read,
xen/drivers/vpci/sriov.c int vpci_vf_init_header(struct pci_dev *vf_pdev) · line 149
Quoted 10 lines
xen/drivers/vpci/sriov.c +10
-0+ vpci_guest_mem_bar_write, + PCI_BASE_ADDRESS_0 + i * 4, 4, &bars[i]); +if ( rc ) +return rc; +break ; + +default : + rc =vpci_add_register (vf_pdev->vpci, vpci_read_val,NULL , +PCI_BASE_ADDRESS_0 + i *4 ,4 , + (void *)0 );
Unlike above here I'm less certain - we deliberately use this pattern elsewhere already. Yet maybe those would best be switched to _p() as well.
Jan