:p
atchew
Login
Pipeline: https://gitlab.com/xen-project/people/stewarthildebrand/xen/-/pipelines/1845628953 RFC->v1: * rework BAR mapping machinery to support unmap-then-map operation RFC: https://lore.kernel.org/xen-devel/20250312195019.382926-1-stewart.hildebrand@amd.com/T/#t Stewart Hildebrand (5): vpci: const-ify some pdev instances vpci: rework error path in vpci_process_pending() vpci: introduce map_bars() vpci: use separate rangeset for BAR unmapping vpci: allow 32-bit BAR writes with memory decoding enabled xen/drivers/vpci/header.c | 220 ++++++++++++++++++++++++++------------ xen/drivers/vpci/vpci.c | 5 +- xen/include/xen/vpci.h | 10 +- 3 files changed, 162 insertions(+), 73 deletions(-) base-commit: 96a587a057363e519ca74498882fac42d72670b6 -- 2.49.0
Since 622bdd962822 ("vpci/header: handle p2m range sets per BAR"), a non-const pdev is no longer needed for error handling in vpci_process_pending(). Const-ify pdev in vpci_process_pending(), defer_map(), and struct vpci_vcpu. Get rid of const-removal workaround in modify_bars(). Take the opportunity to remove an unused parameter in defer_map(). Signed-off-by: Stewart Hildebrand <stewart.hildebrand@amd.com> --- This is prerequisite for ("vpci: use separate rangeset for BAR unmapping") in order to call defer_map() with a const pdev. --- xen/drivers/vpci/header.c | 16 ++++------------ xen/include/xen/vpci.h | 2 +- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/header.c +++ b/xen/drivers/vpci/header.c @@ -XXX,XX +XXX,XX @@ static void modify_decoding(const struct pci_dev *pdev, uint16_t cmd, bool vpci_process_pending(struct vcpu *v) { - struct pci_dev *pdev = v->vpci.pdev; + const struct pci_dev *pdev = v->vpci.pdev; struct vpci_header *header = NULL; unsigned int i; @@ -XXX,XX +XXX,XX @@ static int __init apply_map(struct domain *d, const struct pci_dev *pdev, return rc; } -static void defer_map(struct domain *d, struct pci_dev *pdev, - uint16_t cmd, bool rom_only) +static void defer_map(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) { struct vcpu *curr = current; @@ -XXX,XX +XXX,XX @@ static void defer_map(struct domain *d, struct pci_dev *pdev, static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) { struct vpci_header *header = &pdev->vpci->header; - struct pci_dev *tmp, *dev = NULL; + struct pci_dev *tmp; const struct domain *d; const struct vpci_msix *msix = pdev->vpci->msix; unsigned int i, j; @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) if ( tmp == pdev ) { - /* - * Need to store the device so it's not constified and defer_map - * can modify it in case of error. - */ - dev = tmp; if ( !rom_only ) /* * If memory decoding is toggled avoid checking against the @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) d = dom_xen; } - ASSERT(dev); - if ( system_state < SYS_STATE_active ) { /* @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) return apply_map(pdev->domain, pdev, cmd); } - defer_map(dev->domain, dev, cmd, rom_only); + defer_map(pdev, cmd, rom_only); return 0; } diff --git a/xen/include/xen/vpci.h b/xen/include/xen/vpci.h index XXXXXXX..XXXXXXX 100644 --- a/xen/include/xen/vpci.h +++ b/xen/include/xen/vpci.h @@ -XXX,XX +XXX,XX @@ struct vpci { struct vpci_vcpu { /* Per-vcpu structure to store state while {un}mapping of PCI BARs. */ - struct pci_dev *pdev; + const struct pci_dev *pdev; uint16_t cmd; bool rom_only : 1; }; -- 2.49.0
This will make further refactoring simpler. Signed-off-by: Stewart Hildebrand <stewart.hildebrand@amd.com> --- xen/drivers/vpci/header.c | 42 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/header.c +++ b/xen/drivers/vpci/header.c @@ -XXX,XX +XXX,XX @@ bool vpci_process_pending(struct vcpu *v) } if ( rc ) - { - spin_lock(&pdev->vpci->lock); - /* Disable memory decoding unconditionally on failure. */ - modify_decoding(pdev, v->vpci.cmd & ~PCI_COMMAND_MEMORY, - false); - spin_unlock(&pdev->vpci->lock); - - /* Clean all the rangesets */ - for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) - if ( !rangeset_is_empty(header->bars[i].mem) ) - rangeset_purge(header->bars[i].mem); - - v->vpci.pdev = NULL; - - read_unlock(&v->domain->pci_lock); - - if ( !is_hardware_domain(v->domain) ) - domain_crash(v->domain); - - return false; - } + goto fail; } v->vpci.pdev = NULL; @@ -XXX,XX +XXX,XX @@ bool vpci_process_pending(struct vcpu *v) read_unlock(&v->domain->pci_lock); return false; + + fail: + spin_lock(&pdev->vpci->lock); + /* Disable memory decoding unconditionally on failure. */ + modify_decoding(pdev, v->vpci.cmd & ~PCI_COMMAND_MEMORY, false); + spin_unlock(&pdev->vpci->lock); + + /* Clean all the rangesets */ + for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) + if ( !rangeset_is_empty(header->bars[i].mem) ) + rangeset_purge(header->bars[i].mem); + + v->vpci.pdev = NULL; + + read_unlock(&v->domain->pci_lock); + + if ( !is_hardware_domain(v->domain) ) + domain_crash(v->domain); + + return false; } static int __init apply_map(struct domain *d, const struct pci_dev *pdev, -- 2.49.0
Move some logic to a new function to enable code reuse. Signed-off-by: Stewart Hildebrand <stewart.hildebrand@amd.com> --- xen/drivers/vpci/header.c | 56 ++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/header.c +++ b/xen/drivers/vpci/header.c @@ -XXX,XX +XXX,XX @@ static void modify_decoding(const struct pci_dev *pdev, uint16_t cmd, ASSERT_UNREACHABLE(); } +static int map_bars(struct vpci_header *header, struct domain *d, bool map) +{ + unsigned int i; + + for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) + { + struct vpci_bar *bar = &header->bars[i]; + struct map_data data = { + .d = d, + .map = map, + .bar = bar, + }; + int rc; + + if ( rangeset_is_empty(bar->mem) ) + continue; + + rc = rangeset_consume_ranges(bar->mem, map_range, &data); + + if ( rc ) + return rc; + } + + return 0; +} + bool vpci_process_pending(struct vcpu *v) { const struct pci_dev *pdev = v->vpci.pdev; struct vpci_header *header = NULL; unsigned int i; + int rc; if ( !pdev ) return false; @@ -XXX,XX +XXX,XX @@ bool vpci_process_pending(struct vcpu *v) } header = &pdev->vpci->header; - for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) - { - struct vpci_bar *bar = &header->bars[i]; - struct map_data data = { - .d = v->domain, - .map = v->vpci.cmd & PCI_COMMAND_MEMORY, - .bar = bar, - }; - int rc; - - if ( rangeset_is_empty(bar->mem) ) - continue; + rc = map_bars(header, v->domain, v->vpci.cmd & PCI_COMMAND_MEMORY); - rc = rangeset_consume_ranges(bar->mem, map_range, &data); + if ( rc == -ERESTART ) + { + read_unlock(&v->domain->pci_lock); + return true; + } - if ( rc == -ERESTART ) - { - read_unlock(&v->domain->pci_lock); - return true; - } + if ( rc ) + goto fail; - if ( rc ) - goto fail; - } v->vpci.pdev = NULL; spin_lock(&pdev->vpci->lock); -- 2.49.0
Introduce a new per-BAR rangeset, unmap_mem, for p2m unmapping. Rename existing mem rangeset to map_mem, which is now only used for mapping. Populate unmap_mem by moving just-mapped ranges from map_mem to unmap_mem. In modify_bars(), skip recalculating the ranges when unmapping as they are already stored in unmap_mem. Signed-off-by: Stewart Hildebrand <stewart.hildebrand@amd.com> --- xen/drivers/vpci/header.c | 74 +++++++++++++++++++++++++++++---------- xen/drivers/vpci/vpci.c | 5 ++- xen/include/xen/vpci.h | 3 +- 3 files changed, 62 insertions(+), 20 deletions(-) diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/header.c +++ b/xen/drivers/vpci/header.c @@ -XXX,XX +XXX,XX @@ static int cf_check map_range( if ( rc == 0 ) { *c += size; + if ( map->map ) + rc = rangeset_add_range(map->bar->unmap_mem, s, e); break; } if ( rc < 0 ) @@ -XXX,XX +XXX,XX @@ static int cf_check map_range( } ASSERT(rc < size); *c += rc; + if ( map->map ) + { + int rc2 = rangeset_add_range(map->bar->unmap_mem, s, s + rc); + + if ( rc2 ) + return rc2; + } s += rc; if ( general_preempt_check() ) return -ERESTART; @@ -XXX,XX +XXX,XX @@ static int map_bars(struct vpci_header *header, struct domain *d, bool map) .map = map, .bar = bar, }; + struct rangeset *r = map ? bar->map_mem : bar->unmap_mem; int rc; - if ( rangeset_is_empty(bar->mem) ) + if ( rangeset_is_empty(r) ) continue; - rc = rangeset_consume_ranges(bar->mem, map_range, &data); + rc = rangeset_consume_ranges(r, map_range, &data); if ( rc ) return rc; @@ -XXX,XX +XXX,XX @@ bool vpci_process_pending(struct vcpu *v) /* Clean all the rangesets */ for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) - if ( !rangeset_is_empty(header->bars[i].mem) ) - rangeset_purge(header->bars[i].mem); + { + if ( !rangeset_is_empty(header->bars[i].map_mem) ) + rangeset_purge(header->bars[i].map_mem); + + if ( !rangeset_is_empty(header->bars[i].unmap_mem) ) + rangeset_purge(header->bars[i].unmap_mem); + } v->vpci.pdev = NULL; @@ -XXX,XX +XXX,XX @@ static int __init apply_map(struct domain *d, const struct pci_dev *pdev, struct vpci_bar *bar = &header->bars[i]; struct map_data data = { .d = d, .map = true, .bar = bar }; - if ( rangeset_is_empty(bar->mem) ) + if ( rangeset_is_empty(bar->map_mem) ) continue; - while ( (rc = rangeset_consume_ranges(bar->mem, map_range, + while ( (rc = rangeset_consume_ranges(bar->map_mem, map_range, &data)) == -ERESTART ) { /* @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) ASSERT(rw_is_write_locked(&pdev->domain->pci_lock)); + if ( !(cmd & PCI_COMMAND_MEMORY) ) + { + defer_map(pdev, cmd, rom_only); + + return 0; + } + /* * Create a rangeset per BAR that represents the current device memory * region and compare it against all the currently active BAR memory @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) 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 ( !bar->map_mem || !bar->unmap_mem ) continue; if ( !MAPPABLE_BAR(bar) || @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) continue; } - ASSERT(rangeset_is_empty(bar->mem)); + ASSERT(rangeset_is_empty(bar->map_mem)); /* * Make sure that the guest set address has the same page offset @@ -XXX,XX +XXX,XX @@ static int 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(bar->map_mem, start_guest, end_guest); if ( rc ) { printk(XENLOG_G_WARNING "Failed to add [%lx, %lx]: %d\n", @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) { struct vpci_bar *prev_bar = &header->bars[j]; - if ( rangeset_is_empty(prev_bar->mem) ) + if ( rangeset_is_empty(prev_bar->map_mem) ) continue; - rc = rangeset_remove_range(prev_bar->mem, start_guest, end_guest); + rc = rangeset_remove_range(prev_bar->map_mem, start_guest, end_guest); if ( rc ) { gprintk(XENLOG_WARNING, @@ -XXX,XX +XXX,XX @@ static int 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(bar->map_mem); if ( rc ) { gprintk(XENLOG_WARNING, @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) { const struct vpci_bar *bar = &header->bars[j]; - if ( rangeset_is_empty(bar->mem) ) + if ( rangeset_is_empty(bar->map_mem) ) continue; - rc = rangeset_remove_range(bar->mem, start, end); + rc = rangeset_remove_range(bar->map_mem, start, end); if ( rc ) { gprintk(XENLOG_WARNING, @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) { const struct vpci_bar *bar = &header->bars[j]; - if ( !rangeset_overlaps_range(bar->mem, start, end) || + if ( !rangeset_overlaps_range(bar->map_mem, start, end) || /* * If only the ROM enable bit is toggled check against * other BARs in the same device for overlaps, but not @@ -XXX,XX +XXX,XX @@ static int 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(bar->map_mem, start, end); if ( rc ) { gprintk(XENLOG_WARNING, @@ -XXX,XX +XXX,XX @@ static int bar_add_rangeset(const struct pci_dev *pdev, struct vpci_bar *bar, unsigned int i) { char str[32]; + int rc = 0; snprintf(str, sizeof(str), "%pp:BAR%u", &pdev->sbdf, i); - bar->mem = rangeset_new(pdev->domain, str, RANGESETF_no_print); + bar->map_mem = rangeset_new(pdev->domain, str, RANGESETF_no_print); + bar->unmap_mem = rangeset_new(pdev->domain, str, RANGESETF_no_print); + + if ( !bar->map_mem ) + rc = -ENOMEM; + + if ( !bar->unmap_mem ) + rc = -ENOMEM; - return !bar->mem ? -ENOMEM : 0; + if ( rc == -ENOMEM ) + { + rangeset_destroy(bar->map_mem); + rangeset_destroy(bar->unmap_mem); + bar->map_mem = NULL; + bar->unmap_mem = NULL; + } + + return rc; } static int cf_check init_header(struct pci_dev *pdev) diff --git a/xen/drivers/vpci/vpci.c b/xen/drivers/vpci/vpci.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/vpci.c +++ b/xen/drivers/vpci/vpci.c @@ -XXX,XX +XXX,XX @@ void vpci_deassign_device(struct pci_dev *pdev) } for ( i = 0; i < ARRAY_SIZE(pdev->vpci->header.bars); i++ ) - rangeset_destroy(pdev->vpci->header.bars[i].mem); + { + rangeset_destroy(pdev->vpci->header.bars[i].map_mem); + rangeset_destroy(pdev->vpci->header.bars[i].unmap_mem); + } xfree(pdev->vpci->msix); xfree(pdev->vpci->msi); diff --git a/xen/include/xen/vpci.h b/xen/include/xen/vpci.h index XXXXXXX..XXXXXXX 100644 --- a/xen/include/xen/vpci.h +++ b/xen/include/xen/vpci.h @@ -XXX,XX +XXX,XX @@ struct vpci { uint64_t guest_addr; uint64_t size; uint64_t resizable_sizes; - struct rangeset *mem; + struct rangeset *map_mem; + struct rangeset *unmap_mem; enum { VPCI_BAR_EMPTY, VPCI_BAR_IO, -- 2.49.0
Currently, Xen vPCI refuses BAR writes if the BAR is mapped in p2m. If firmware initializes a 32-bit BAR to a bad address, Linux may try to write a new address to the BAR without disabling memory decoding. Since Xen refuses such writes, the BAR (and thus PCI device) will be non-functional. Currently the deferred mapping machinery supports only map or unmap operations. Rework the deferred mapping machinery to support unmap-then-map (VPCI_MOVE) operations. Allow the hardware domain to issue 32-bit BAR writes with memory decoding enabled, using the VPCI_MOVE operation to remap the BAR in p2m. Take the opportunity to remove a stray newline in bar_write(). Resolves: https://gitlab.com/xen-project/xen/-/issues/197 Signed-off-by: Stewart Hildebrand <stewart.hildebrand@amd.com> --- RFC->v1: * keep memory decoding enabled in hardware * allow write while memory decoding is enabled for 32-bit BARs only * rework BAR mapping machinery to support unmap-then-map operation --- xen/drivers/vpci/header.c | 86 +++++++++++++++++++++++++++------------ xen/include/xen/vpci.h | 5 +++ 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/header.c +++ b/xen/drivers/vpci/header.c @@ -XXX,XX +XXX,XX @@ bool vpci_process_pending(struct vcpu *v) const struct pci_dev *pdev = v->vpci.pdev; struct vpci_header *header = NULL; unsigned int i; - int rc; if ( !pdev ) return false; @@ -XXX,XX +XXX,XX @@ bool vpci_process_pending(struct vcpu *v) } header = &pdev->vpci->header; - rc = map_bars(header, v->domain, v->vpci.cmd & PCI_COMMAND_MEMORY); - if ( rc == -ERESTART ) + if ( v->vpci.map_op == VPCI_UNMAP || v->vpci.map_op == VPCI_MOVE ) { - read_unlock(&v->domain->pci_lock); - return true; + int rc = map_bars(header, v->domain, false); + + if ( rc == -ERESTART ) + { + read_unlock(&v->domain->pci_lock); + return true; + } + + if ( rc ) + goto fail; } - if ( rc ) - goto fail; + if ( v->vpci.map_op == VPCI_MAP || v->vpci.map_op == VPCI_MOVE ) + { + int rc = map_bars(header, v->domain, true); + + if ( rc == -ERESTART ) + { + read_unlock(&v->domain->pci_lock); + return true; + } + + if ( rc ) + goto fail; + } v->vpci.pdev = NULL; @@ -XXX,XX +XXX,XX @@ static int __init apply_map(struct domain *d, const struct pci_dev *pdev, return rc; } -static void defer_map(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) +static void defer_map(const struct pci_dev *pdev, uint16_t cmd, + enum vpci_map_op map_op, bool rom_only) { struct vcpu *curr = current; @@ -XXX,XX +XXX,XX @@ static void defer_map(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) */ curr->vpci.pdev = pdev; curr->vpci.cmd = cmd; + curr->vpci.map_op = map_op; curr->vpci.rom_only = rom_only; /* * Raise a scheduler softirq in order to prevent the guest from resuming @@ -XXX,XX +XXX,XX @@ 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) +static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, + enum vpci_map_op map_op, bool rom_only) { struct vpci_header *header = &pdev->vpci->header; struct pci_dev *tmp; @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) ASSERT(rw_is_write_locked(&pdev->domain->pci_lock)); - if ( !(cmd & PCI_COMMAND_MEMORY) ) + if ( map_op == VPCI_UNMAP ) { - defer_map(pdev, cmd, rom_only); + defer_map(pdev, cmd, map_op, rom_only); return 0; } @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) (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) ) + (bar->enabled == !!(cmd & PCI_COMMAND_MEMORY) && + map_op != VPCI_MOVE) ) continue; if ( !pci_check_bar(pdev, _mfn(start), _mfn(end)) ) @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) return apply_map(pdev->domain, pdev, cmd); } - defer_map(pdev, cmd, rom_only); + defer_map(pdev, cmd, map_op, rom_only); return 0; } @@ -XXX,XX +XXX,XX @@ 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); + modify_bars(pdev, cmd, cmd & PCI_COMMAND_MEMORY ? VPCI_MAP : VPCI_UNMAP, + false); else pci_conf_write16(pdev->sbdf, reg, cmd); } @@ -XXX,XX +XXX,XX @@ static void cf_check bar_write( val &= PCI_BASE_ADDRESS_MEM_MASK; /* - * Xen only cares whether the BAR is mapped into the p2m, so allow BAR - * writes as long as the BAR is not mapped into the p2m. + * Allow 64-bit BAR writes only when the BAR is not mapped in p2m. Always + * allow 32-bit BAR writes, but skip unnecessary p2m operations when mapped. */ if ( bar->enabled ) { - /* If the value written is the current one avoid printing a warning. */ - if ( val != (uint32_t)(bar->addr >> (hi ? 32 : 0)) ) - gprintk(XENLOG_WARNING, - "%pp: ignored BAR %zu write while mapped\n", - &pdev->sbdf, bar - pdev->vpci->header.bars + hi); - return; + if ( bar->type == VPCI_BAR_MEM32 ) + { + if ( val == bar->addr ) + return; + } + else + { + /* If the value written is the same avoid printing a warning. */ + if ( val != (uint32_t)(bar->addr >> (hi ? 32 : 0)) ) + gprintk(XENLOG_WARNING, + "%pp: ignored BAR %zu write while mapped\n", + &pdev->sbdf, bar - pdev->vpci->header.bars + hi); + return; + } } - /* * Update the cached address, so that when memory decoding is enabled * Xen can map the BAR into the guest p2m. @@ -XXX,XX +XXX,XX @@ static void cf_check bar_write( } pci_conf_write32(pdev->sbdf, reg, val); + + if ( bar->enabled ) + modify_bars(pdev, pci_conf_read16(pdev->sbdf, PCI_COMMAND), VPCI_MOVE, + false); } static void cf_check guest_mem_bar_write(const struct pci_dev *pdev, @@ -XXX,XX +XXX,XX @@ 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 ( modify_bars(pdev, new_enabled ? PCI_COMMAND_MEMORY : 0, + new_enabled ? VPCI_MAP : VPCI_UNMAP, 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 @@ -XXX,XX +XXX,XX @@ static int cf_check init_header(struct pci_dev *pdev) goto fail; } - return (cmd & PCI_COMMAND_MEMORY) ? modify_bars(pdev, cmd, false) : 0; + return (cmd & PCI_COMMAND_MEMORY) + ? modify_bars(pdev, cmd, VPCI_MAP, false) + : 0; fail: pci_conf_write16(pdev->sbdf, PCI_COMMAND, cmd); diff --git a/xen/include/xen/vpci.h b/xen/include/xen/vpci.h index XXXXXXX..XXXXXXX 100644 --- a/xen/include/xen/vpci.h +++ b/xen/include/xen/vpci.h @@ -XXX,XX +XXX,XX @@ struct vpci_vcpu { /* Per-vcpu structure to store state while {un}mapping of PCI BARs. */ const struct pci_dev *pdev; uint16_t cmd; + enum vpci_map_op { + VPCI_MAP, + VPCI_UNMAP, + VPCI_MOVE, + } map_op; bool rom_only : 1; }; -- 2.49.0
These 2 patches ("vpci: Use pervcpu ranges for BAR mapping") ("vpci: allow queueing of mapping operations") are also pre-requisites for SR-IOV. Pipeline: https://gitlab.com/xen-project/people/stewarthildebrand/xen/-/pipelines/2432615038 v3->v4: * switch back to dynamically allocated queue elements v2->v3: * add ("vpci: Use pervcpu ranges for BAR mapping") * rework with fixed array of map/unmap slots v1->v2: * new approach with queued p2m operations RFC->v1: * rework BAR mapping machinery to support unmap-then-map operation v3: https://lore.kernel.org/xen-devel/20260324030513.700217-1-stewart.hildebrand@amd.com/T/#t v2: https://lore.kernel.org/xen-devel/20250723163744.13095-1-stewart.hildebrand@amd.com/T/#t v1: https://lore.kernel.org/xen-devel/20250531125405.268984-1-stewart.hildebrand@amd.com/T/#t RFC: https://lore.kernel.org/xen-devel/20250312195019.382926-1-stewart.hildebrand@amd.com/T/#t Mykyta Poturai (1): vpci: Use pervcpu ranges for BAR mapping Stewart Hildebrand (3): vpci: allow queueing of mapping operations vpci: allow BAR map/unmap without affecting memory decoding bit vpci: allow 32-bit BAR writes with memory decoding enabled xen/common/domain.c | 2 + xen/drivers/vpci/header.c | 333 ++++++++++++++++++++++++-------------- xen/drivers/vpci/vpci.c | 10 +- xen/include/xen/vpci.h | 22 ++- 4 files changed, 239 insertions(+), 128 deletions(-) base-commit: 33ceaa28275ca4e298616689ef96f19efaa87c35 -- 2.53.0
From: Mykyta Poturai <Mykyta_Poturai@epam.com> There is no need to store ranges for each PCI device, as they are only used during the mapping/unmapping process and can be reused for each device. This also allows to avoid the need to allocate and destroy rangesets for each device. Move the rangesets from struct vpci_bar to struct vpci_vcpu and perform (de-)allocation with vcpu (de-)allocation. Introduce RANGESET_DESTROY() macro to free a rangeset and set the pointer to NULL. Amends: 622bdd962822 ("vpci/header: handle p2m range sets per BAR") Signed-off-by: Mykyta Poturai <mykyta_poturai@epam.com> Signed-off-by: Stewart Hildebrand <stewart.hildebrand@amd.com> --- It seems a bit awkward to introduce various vpci vcpu alloc/dealloc functions here only to undo most of it in the next patch. Thoughts on folding the next patch into this one? v3->v4: * no change v2->v3: * new patch in this series, borrowed from [1] * add Amends tag * remove unused variable i due to rebasing over 998060dd9101 ("vPCI: move vpci_init_capabilities() to a separate file") * enclose entire struct vpci_vcpu inside #ifdef __XEN__ * s/bar_mem/mem/ * use ARRAY_SIZE * put init/destroy in functions * only allocate for domains with vPCI and idle domain * replace 'if ( !mem ) continue;' with ASSERT v1->v2 (in SR-IOV series [1]): * new patch [1] https://lore.kernel.org/xen-devel/cover.1772806036.git.mykyta_poturai@epam.com/T/#t --- xen/common/domain.c | 5 +++ xen/drivers/vpci/header.c | 67 ++++++++++++++------------------------ xen/drivers/vpci/vpci.c | 36 +++++++++++++++++--- xen/include/xen/rangeset.h | 7 ++++ xen/include/xen/vpci.h | 10 ++++-- 5 files changed, 75 insertions(+), 50 deletions(-) diff --git a/xen/common/domain.c b/xen/common/domain.c index XXXXXXX..XXXXXXX 100644 --- a/xen/common/domain.c +++ b/xen/common/domain.c @@ -XXX,XX +XXX,XX @@ static int vcpu_teardown(struct vcpu *v) */ static void vcpu_destroy(struct vcpu *v) { + vpci_vcpu_destroy(v); + free_vcpu_struct(v); } @@ -XXX,XX +XXX,XX @@ struct vcpu *vcpu_create(struct domain *d, unsigned int vcpu_id) if ( arch_vcpu_create(v) != 0 ) goto fail_sched; + if ( vpci_vcpu_init(v) ) + goto fail_sched; + d->vcpu[vcpu_id] = v; if ( vcpu_id != 0 ) { diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/header.c +++ b/xen/drivers/vpci/header.c @@ -XXX,XX +XXX,XX @@ 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 = v->vpci.mem[i]; struct map_data data = { .d = v->domain, .map = v->vpci.cmd & PCI_COMMAND_MEMORY, @@ -XXX,XX +XXX,XX @@ 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 ) { @@ -XXX,XX +XXX,XX @@ bool vpci_process_pending(struct vcpu *v) /* Clean all the rangesets */ for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) - if ( !rangeset_is_empty(header->bars[i].mem) ) - rangeset_purge(header->bars[i].mem); + if ( !rangeset_is_empty(v->vpci.mem[i]) ) + rangeset_purge(v->vpci.mem[i]); v->vpci.pdev = NULL; @@ -XXX,XX +XXX,XX @@ 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 = current->vpci.mem[i]; 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 @@ -XXX,XX +XXX,XX @@ static int 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 = current->vpci.mem[i]; 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 ) - continue; + ASSERT(mem); if ( !MAPPABLE_BAR(bar) || (rom_only ? bar->type != VPCI_BAR_ROM @@ -XXX,XX +XXX,XX @@ static int 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 @@ -XXX,XX +XXX,XX @@ static int 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", @@ -XXX,XX +XXX,XX @@ static int 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 = current->vpci.mem[j]; - 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, @@ -XXX,XX +XXX,XX @@ static int 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, @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) unsigned long end = PFN_DOWN(vmsix_table_addr(pdev->vpci, i) + vmsix_table_size(pdev->vpci, i) - 1); - for ( j = 0; j < ARRAY_SIZE(header->bars); j++ ) + for ( j = 0; j < ARRAY_SIZE(current->vpci.mem); j++ ) { - const struct vpci_bar *bar = &header->bars[j]; + struct rangeset *mem = current->vpci.mem[j]; - 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, @@ -XXX,XX +XXX,XX @@ static int 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 = current->vpci.mem[j]; - 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 @@ -XXX,XX +XXX,XX @@ static int 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, @@ -XXX,XX +XXX,XX @@ static void cf_check rom_write( } } -static int bar_add_rangeset(const struct pci_dev *pdev, struct vpci_bar *bar, - unsigned int i) -{ - char str[32]; - - snprintf(str, sizeof(str), "%pp:BAR%u", &pdev->sbdf, i); - - bar->mem = rangeset_new(pdev->domain, str, RANGESETF_no_print); - - return !bar->mem ? -ENOMEM : 0; -} - int vpci_init_header(struct pci_dev *pdev) { uint16_t cmd; @@ -XXX,XX +XXX,XX @@ int vpci_init_header(struct pci_dev *pdev) else bars[i].type = VPCI_BAR_MEM32; - rc = bar_add_rangeset(pdev, &bars[i], i); - if ( rc ) - goto fail; - rc = pci_size_mem_bar(pdev->sbdf, reg, &addr, &size, (i == num_bars - 1) ? PCI_BAR_LAST : 0); if ( rc < 0 ) @@ -XXX,XX +XXX,XX @@ int vpci_init_header(struct pci_dev *pdev) 4, rom); if ( rc ) rom->type = VPCI_BAR_EMPTY; - else - { - rc = bar_add_rangeset(pdev, rom, num_bars); - if ( rc ) - goto fail; - } } else if ( !is_hwdom ) { diff --git a/xen/drivers/vpci/vpci.c b/xen/drivers/vpci/vpci.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/vpci.c +++ b/xen/drivers/vpci/vpci.c @@ -XXX,XX +XXX,XX @@ #ifdef __XEN__ +void vpci_vcpu_destroy(struct vcpu *v) +{ + unsigned int i; + + if ( !has_vpci(v->domain) && !is_idle_domain(v->domain) ) + return; + + for ( i = 0; i < ARRAY_SIZE(v->vpci.mem); i++ ) + RANGESET_DESTROY(v->vpci.mem[i]); +} + +int vpci_vcpu_init(struct vcpu *v) +{ + unsigned int i; + + if ( !has_vpci(v->domain) && !is_idle_domain(v->domain) ) + return 0; + + for ( i = 0; i < ARRAY_SIZE(v->vpci.mem); i++ ) + { + char str[32]; + + snprintf(str, sizeof(str), "%pv:BAR%u", v, i); + v->vpci.mem[i] = rangeset_new(v->domain, str, RANGESETF_no_print); + if ( !v->vpci.mem[i] ) + return -ENOMEM; + } + + return 0; +} + #ifdef CONFIG_HAS_VPCI_GUEST_SUPPORT static int assign_virtual_sbdf(struct pci_dev *pdev) { @@ -XXX,XX +XXX,XX @@ 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 ) @@ -XXX,XX +XXX,XX @@ 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; } diff --git a/xen/include/xen/rangeset.h b/xen/include/xen/rangeset.h index XXXXXXX..XXXXXXX 100644 --- a/xen/include/xen/rangeset.h +++ b/xen/include/xen/rangeset.h @@ -XXX,XX +XXX,XX @@ struct rangeset *rangeset_new( void rangeset_destroy( struct rangeset *r); +/* Destroy a rangeset, and zero the pointer to it. */ +#define RANGESET_DESTROY(r) \ + ({ \ + rangeset_destroy(r); \ + (r) = NULL; \ + }) + /* * Set a limit on the number of ranges that may exist in set @r. * NOTE: This must be called while @r is empty. diff --git a/xen/include/xen/vpci.h b/xen/include/xen/vpci.h index XXXXXXX..XXXXXXX 100644 --- a/xen/include/xen/vpci.h +++ b/xen/include/xen/vpci.h @@ -XXX,XX +XXX,XX @@ */ #define VPCI_MAX_VIRT_DEV (PCI_SLOT(~0) + 1) +void vpci_vcpu_destroy(struct vcpu *v); +int vpci_vcpu_init(struct vcpu *v); + /* Assign vPCI to device by adding handlers. */ int __must_check vpci_assign_device(struct pci_dev *pdev); @@ -XXX,XX +XXX,XX @@ struct vpci { uint64_t guest_addr; uint64_t size; uint64_t resizable_sizes; - struct rangeset *mem; enum { VPCI_BAR_EMPTY, VPCI_BAR_IO, @@ -XXX,XX +XXX,XX @@ struct vpci { #endif }; +#ifdef __XEN__ struct vpci_vcpu { /* Per-vcpu structure to store state while {un}mapping of PCI BARs. */ const struct pci_dev *pdev; + struct rangeset *mem[ARRAY_SIZE(((struct vpci_header *)NULL)->bars)]; uint16_t cmd; bool rom_only : 1; }; -#ifdef __XEN__ void vpci_dump_msi(void); /* Arch-specific vPCI MSI helpers. */ @@ -XXX,XX +XXX,XX @@ 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_destroy(struct vcpu *v) { } +static inline int vpci_vcpu_init(struct vcpu *v) { return 0; } + static inline int vpci_reinit_ext_capabilities(struct pci_dev *pdev) { return 0; -- 2.53.0
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 device. 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. Signed-off-by: Stewart Hildebrand <stewart.hildebrand@amd.com> --- apply_map() and vpci_process_map_task() are very similar. Should we try to combine them into a single function? I concede that the dynamic allocation/deallocation of struct vpci_map_task is not ideal. However, to support SR-IOV, there will be a need to queue many mapping operations (one per VF), and statically pre-allocating that much would seem wasteful. Only the hardware and/or control domain would need to queue many operations, and only when configuring SR-IOV. v3->v4: * switch back to dynamically allocated queue elements v2->v3: * base on ("vpci: Use pervcpu ranges for BAR mapping") from [1] * rework with fixed array of map/unmap slots [1] https://lore.kernel.org/xen-devel/cover.1772806036.git.mykyta_poturai@epam.com/T/#t v1->v2: * new patch --- xen/common/domain.c | 5 +- xen/drivers/vpci/header.c | 227 ++++++++++++++++++++++++++----------- xen/drivers/vpci/vpci.c | 30 +---- xen/include/xen/rangeset.h | 7 -- xen/include/xen/vpci.h | 21 ++-- 5 files changed, 179 insertions(+), 111 deletions(-) diff --git a/xen/common/domain.c b/xen/common/domain.c index XXXXXXX..XXXXXXX 100644 --- a/xen/common/domain.c +++ b/xen/common/domain.c @@ -XXX,XX +XXX,XX @@ static int vcpu_teardown(struct vcpu *v) */ static void vcpu_destroy(struct vcpu *v) { - vpci_vcpu_destroy(v); - free_vcpu_struct(v); } @@ -XXX,XX +XXX,XX @@ struct vcpu *vcpu_create(struct domain *d, unsigned int vcpu_id) if ( arch_vcpu_create(v) != 0 ) goto fail_sched; - if ( vpci_vcpu_init(v) ) - goto fail_sched; + vpci_vcpu_init(v); d->vcpu[vcpu_id] = v; if ( vcpu_id != 0 ) diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/header.c +++ b/xen/drivers/vpci/header.c @@ -XXX,XX +XXX,XX @@ #include <xen/lib.h> #include <xen/sched.h> #include <xen/softirq.h> +#include <xen/xvmalloc.h> #include <xsm/xsm.h> @@ -XXX,XX +XXX,XX @@ struct map_data { struct domain *d; - const struct vpci_bar *bar; + const struct vpci_bar_map *bar; bool map; }; @@ -XXX,XX +XXX,XX @@ static void modify_decoding(const struct pci_dev *pdev, uint16_t cmd, ASSERT_UNREACHABLE(); } -bool vpci_process_pending(struct vcpu *v) +static int vpci_process_map_task(const struct pci_dev *pdev, + struct vpci_map_task *task) { - const struct pci_dev *pdev = v->vpci.pdev; - struct vpci_header *header = NULL; unsigned int i; - if ( !pdev ) - return false; - - read_lock(&v->domain->pci_lock); - - if ( !pdev->vpci || (v->domain != pdev->domain) ) - { - v->vpci.pdev = NULL; - read_unlock(&v->domain->pci_lock); - return false; - } + ASSERT(rw_is_locked(&pdev->domain->pci_lock)); - header = &pdev->vpci->header; - for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) + for ( i = 0; i < ARRAY_SIZE(task->bars); i++ ) { - struct vpci_bar *bar = &header->bars[i]; - struct rangeset *mem = v->vpci.mem[i]; + struct vpci_bar_map *bar = &task->bars[i]; + struct rangeset *mem = bar->mem; struct map_data data = { - .d = v->domain, - .map = v->vpci.cmd & PCI_COMMAND_MEMORY, + .d = pdev->domain, + .map = task->cmd & PCI_COMMAND_MEMORY, .bar = bar, }; int rc; @@ -XXX,XX +XXX,XX @@ bool vpci_process_pending(struct vcpu *v) rc = rangeset_consume_ranges(mem, map_range, &data); if ( rc == -ERESTART ) - { - read_unlock(&v->domain->pci_lock); - return true; - } + return rc; if ( rc ) { spin_lock(&pdev->vpci->lock); /* Disable memory decoding unconditionally on failure. */ - modify_decoding(pdev, v->vpci.cmd & ~PCI_COMMAND_MEMORY, - false); + modify_decoding(pdev, task->cmd & ~PCI_COMMAND_MEMORY, false); spin_unlock(&pdev->vpci->lock); - /* Clean all the rangesets */ - for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) - if ( !rangeset_is_empty(v->vpci.mem[i]) ) - rangeset_purge(v->vpci.mem[i]); + if ( !is_hardware_domain(pdev->domain) ) + domain_crash(pdev->domain); + + return rc; + } + } + + spin_lock(&pdev->vpci->lock); + modify_decoding(pdev, task->cmd, task->rom_only); + spin_unlock(&pdev->vpci->lock); + + return 0; +} + +static void destroy_map_task(struct vpci_map_task *task) +{ + unsigned int i; + + if ( !task ) + { + ASSERT_UNREACHABLE(); + return; + } + + for ( i = 0; i < ARRAY_SIZE(task->bars); i++ ) + rangeset_destroy(task->bars[i].mem); + + xvfree(task); +} + +static void clear_map_queue(struct vcpu *v) +{ + struct vpci_map_task *task; + + while ( (task = list_first_entry_or_null(&v->vpci.task_queue, + struct vpci_map_task, + next)) != NULL ) + { + list_del(&task->next); + destroy_map_task(task); + } +} + +bool vpci_process_pending(struct vcpu *v) +{ + const struct pci_dev *pdev = v->vpci.pdev; + struct vpci_map_task *task; - v->vpci.pdev = NULL; + if ( !pdev ) + return false; + read_lock(&v->domain->pci_lock); + + if ( !pdev->vpci || (v->domain != pdev->domain) ) + { + clear_map_queue(v); + v->vpci.pdev = NULL; + read_unlock(&v->domain->pci_lock); + return false; + } + + while ( (task = list_first_entry_or_null(&v->vpci.task_queue, + struct vpci_map_task, + next)) != NULL ) + { + int rc = vpci_process_map_task(pdev, task); + + if ( rc == -ERESTART ) + { read_unlock(&v->domain->pci_lock); + return true; + } - if ( !is_hardware_domain(v->domain) ) - domain_crash(v->domain); + list_del(&task->next); + destroy_map_task(task); - return false; + if ( rc ) + { + clear_map_queue(v); + break; } } v->vpci.pdev = NULL; - spin_lock(&pdev->vpci->lock); - modify_decoding(pdev, v->vpci.cmd, v->vpci.rom_only); - spin_unlock(&pdev->vpci->lock); - read_unlock(&v->domain->pci_lock); return false; } static int __init apply_map(struct domain *d, const struct pci_dev *pdev, - uint16_t cmd) + struct vpci_map_task *task) { - struct vpci_header *header = &pdev->vpci->header; int rc = 0; unsigned int i; ASSERT(rw_is_write_locked(&d->pci_lock)); - for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) + for ( i = 0; i < ARRAY_SIZE(task->bars); i++ ) { - struct vpci_bar *bar = &header->bars[i]; - struct rangeset *mem = current->vpci.mem[i]; + struct vpci_bar_map *bar = &task->bars[i]; + struct rangeset *mem = bar->mem; struct map_data data = { .d = d, .map = true, .bar = bar }; if ( rangeset_is_empty(mem) ) @@ -XXX,XX +XXX,XX @@ static int __init apply_map(struct domain *d, const struct pci_dev *pdev, } } if ( !rc ) - modify_decoding(pdev, cmd, false); + modify_decoding(pdev, task->cmd, false); return rc; } -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); + + 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); + + 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; + + return task; +} + +static void defer_map(const struct pci_dev *pdev, struct vpci_map_task *task) { struct vcpu *curr = current; + ASSERT(!curr->vpci.pdev || curr->vpci.pdev == pdev); + /* * FIXME: when deferring the {un}map the state of the device should not * be trusted. For example the enable bit is toggled after the device @@ -XXX,XX +XXX,XX @@ static void defer_map(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) * started for the same device if the domain is not well-behaved. */ curr->vpci.pdev = pdev; - curr->vpci.cmd = cmd; - curr->vpci.rom_only = rom_only; + list_add_tail(&task->next, &curr->vpci.task_queue); + /* * Raise a scheduler softirq in order to prevent the guest from resuming * execution with pending mapping operations, to trigger the invocation @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) struct pci_dev *tmp; const 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); + + 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 @@ -XXX,XX +XXX,XX @@ static int 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 = current->vpci.mem[i]; + struct rangeset *mem = task->bars[i].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); - ASSERT(mem); + if ( !mem ) + continue; 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) ) + : (bar->type == VPCI_BAR_ROM && !header->rom_enabled)) ) continue; if ( !pci_check_bar(pdev, _mfn(start), _mfn(end)) ) @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) gprintk(XENLOG_G_WARNING, "%pp: can't map BAR%u - offset mismatch: %#lx vs %#lx\n", &pdev->sbdf, i, bar->guest_addr, bar->addr); - return -EINVAL; + rc = -EINVAL; + goto fail; } rc = rangeset_add_range(mem, start_guest, end_guest); @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) { printk(XENLOG_G_WARNING "Failed to add [%lx, %lx]: %d\n", start_guest, end_guest, rc); - return rc; + goto fail; } /* Check for overlap with the already setup BAR ranges. */ for ( j = 0; j < i; j++ ) { - struct rangeset *prev_mem = current->vpci.mem[j]; + struct rangeset *prev_mem = task->bars[j].mem; if ( rangeset_is_empty(prev_mem) ) continue; @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) gprintk(XENLOG_WARNING, "%pp: failed to remove overlapping range [%lx, %lx]: %d\n", &pdev->sbdf, start_guest, end_guest, rc); - return rc; + goto fail; } } @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) gprintk(XENLOG_WARNING, "%pp: failed to sanitize BAR#%u memory: %d\n", &pdev->sbdf, i, rc); - return rc; + goto fail; } } @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) unsigned long end = PFN_DOWN(vmsix_table_addr(pdev->vpci, i) + vmsix_table_size(pdev->vpci, i) - 1); - for ( j = 0; j < ARRAY_SIZE(current->vpci.mem); j++ ) + for ( j = 0; j < ARRAY_SIZE(task->bars); j++ ) { - struct rangeset *mem = current->vpci.mem[j]; + struct rangeset *mem = task->bars[j].mem; if ( rangeset_is_empty(mem) ) continue; @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) gprintk(XENLOG_WARNING, "%pp: failed to remove MSIX table [%lx, %lx]: %d\n", &pdev->sbdf, start, end, rc); - return rc; + goto fail; } } } @@ -XXX,XX +XXX,XX @@ static int 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 = current->vpci.mem[j]; + struct rangeset *mem = task->bars[j].mem; if ( !rangeset_overlaps_range(mem, start, end) || /* @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) gprintk(XENLOG_WARNING, "%pp: failed to remove [%lx, %lx]: %d\n", &pdev->sbdf, start, end, rc); - return rc; + goto fail; } } } @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) * will always be to establish mappings and process all the BARs. */ ASSERT((cmd & PCI_COMMAND_MEMORY) && !rom_only); - return apply_map(pdev->domain, pdev, cmd); + rc = apply_map(pdev->domain, pdev, task); + destroy_map_task(task); + return rc; } - defer_map(pdev, cmd, rom_only); + defer_map(pdev, task); return 0; + + fail: + destroy_map_task(task); + + return rc; } static void cf_check cmd_write( diff --git a/xen/drivers/vpci/vpci.c b/xen/drivers/vpci/vpci.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/vpci.c +++ b/xen/drivers/vpci/vpci.c @@ -XXX,XX +XXX,XX @@ #ifdef __XEN__ -void vpci_vcpu_destroy(struct vcpu *v) +void vpci_vcpu_init(struct vcpu *v) { - unsigned int i; - - if ( !has_vpci(v->domain) && !is_idle_domain(v->domain) ) - return; - - for ( i = 0; i < ARRAY_SIZE(v->vpci.mem); i++ ) - RANGESET_DESTROY(v->vpci.mem[i]); -} - -int vpci_vcpu_init(struct vcpu *v) -{ - unsigned int i; - - if ( !has_vpci(v->domain) && !is_idle_domain(v->domain) ) - return 0; - - for ( i = 0; i < ARRAY_SIZE(v->vpci.mem); i++ ) - { - char str[32]; - - snprintf(str, sizeof(str), "%pv:BAR%u", v, i); - v->vpci.mem[i] = rangeset_new(v->domain, str, RANGESETF_no_print); - if ( !v->vpci.mem[i] ) - return -ENOMEM; - } - - return 0; + INIT_LIST_HEAD(&v->vpci.task_queue); } #ifdef CONFIG_HAS_VPCI_GUEST_SUPPORT diff --git a/xen/include/xen/rangeset.h b/xen/include/xen/rangeset.h index XXXXXXX..XXXXXXX 100644 --- a/xen/include/xen/rangeset.h +++ b/xen/include/xen/rangeset.h @@ -XXX,XX +XXX,XX @@ struct rangeset *rangeset_new( void rangeset_destroy( struct rangeset *r); -/* Destroy a rangeset, and zero the pointer to it. */ -#define RANGESET_DESTROY(r) \ - ({ \ - rangeset_destroy(r); \ - (r) = NULL; \ - }) - /* * Set a limit on the number of ranges that may exist in set @r. * NOTE: This must be called while @r is empty. diff --git a/xen/include/xen/vpci.h b/xen/include/xen/vpci.h index XXXXXXX..XXXXXXX 100644 --- a/xen/include/xen/vpci.h +++ b/xen/include/xen/vpci.h @@ -XXX,XX +XXX,XX @@ */ #define VPCI_MAX_VIRT_DEV (PCI_SLOT(~0) + 1) -void vpci_vcpu_destroy(struct vcpu *v); -int vpci_vcpu_init(struct vcpu *v); +void vpci_vcpu_init(struct vcpu *v); /* Assign vPCI to device by adding handlers. */ int __must_check vpci_assign_device(struct pci_dev *pdev); @@ -XXX,XX +XXX,XX @@ struct vpci { }; #ifdef __XEN__ -struct vpci_vcpu { +struct vpci_map_task { /* Per-vcpu structure to store state while {un}mapping of PCI BARs. */ - const struct pci_dev *pdev; - struct rangeset *mem[ARRAY_SIZE(((struct vpci_header *)NULL)->bars)]; + 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; }; +struct vpci_vcpu { + const struct pci_dev *pdev; + struct list_head task_queue; +}; + void vpci_dump_msi(void); /* Arch-specific vPCI MSI helpers. */ @@ -XXX,XX +XXX,XX @@ 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_destroy(struct vcpu *v) { } -static inline int vpci_vcpu_init(struct vcpu *v) { return 0; } +static inline void vpci_vcpu_init(struct vcpu *v) { } static inline int vpci_reinit_ext_capabilities(struct pci_dev *pdev) { -- 2.53.0
Introduce 'bool map' and allow invoking modify_bars() without changing the memory decoding bit. This will allow hardware domain to reposition BARs without affecting the memory decoding bit. Signed-off-by: Stewart Hildebrand <stewart.hildebrand@amd.com> --- This also lays some groundwork to allow domUs to toggle the guest view without affecting hardware, a step toward addressing the FIXME in [1]. [1] https://lore.kernel.org/xen-devel/20250814160358.95543-4-roger.pau@citrix.com/ v3->v4: * rebase on dynamically allocated map queue v2->v3: * use bool * switch to task->map in more places v1->v2: * new patch --- xen/drivers/vpci/header.c | 43 +++++++++++++++++++++------------------ xen/include/xen/vpci.h | 1 + 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/header.c +++ b/xen/drivers/vpci/header.c @@ -XXX,XX +XXX,XX @@ static int cf_check map_range( * BAR's enable bit has changed with the memory decoding bit already enabled. * If rom_only is not set then it's the memory decoding bit that changed. */ -static void modify_decoding(const struct pci_dev *pdev, uint16_t cmd, - bool rom_only) +static void modify_decoding(const struct pci_dev *pdev, + struct vpci_map_task *task) { struct vpci_header *header = &pdev->vpci->header; - bool map = cmd & PCI_COMMAND_MEMORY; + bool rom_only = task->rom_only; + bool map = task->map; unsigned int i; for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) @@ -XXX,XX +XXX,XX @@ static void modify_decoding(const struct pci_dev *pdev, uint16_t cmd, if ( !rom_only ) { - pci_conf_write16(pdev->sbdf, PCI_COMMAND, cmd); + pci_conf_write16(pdev->sbdf, PCI_COMMAND, task->cmd); header->bars_mapped = map; } else @@ -XXX,XX +XXX,XX @@ static int vpci_process_map_task(const struct pci_dev *pdev, struct rangeset *mem = bar->mem; struct map_data data = { .d = pdev->domain, - .map = task->cmd & PCI_COMMAND_MEMORY, + .map = task->map, .bar = bar, }; int rc; @@ -XXX,XX +XXX,XX @@ static int vpci_process_map_task(const struct pci_dev *pdev, if ( rc ) { - spin_lock(&pdev->vpci->lock); /* Disable memory decoding unconditionally on failure. */ - modify_decoding(pdev, task->cmd & ~PCI_COMMAND_MEMORY, false); + task->cmd &= ~PCI_COMMAND_MEMORY; + task->map = false; + spin_lock(&pdev->vpci->lock); + modify_decoding(pdev, task); spin_unlock(&pdev->vpci->lock); if ( !is_hardware_domain(pdev->domain) ) @@ -XXX,XX +XXX,XX @@ static int vpci_process_map_task(const struct pci_dev *pdev, } spin_lock(&pdev->vpci->lock); - modify_decoding(pdev, task->cmd, task->rom_only); + modify_decoding(pdev, task); spin_unlock(&pdev->vpci->lock); return 0; @@ -XXX,XX +XXX,XX @@ static int __init apply_map(struct domain *d, const struct pci_dev *pdev, } } if ( !rc ) - modify_decoding(pdev, task->cmd, false); + modify_decoding(pdev, task); return rc; } static struct vpci_map_task *alloc_map_task(const struct pci_dev *pdev, - uint16_t cmd, bool rom_only) + uint16_t cmd, bool rom_only, + bool map) { struct vpci_map_task *task; unsigned int i; @@ -XXX,XX +XXX,XX @@ static struct vpci_map_task *alloc_map_task(const struct pci_dev *pdev, task->cmd = cmd; task->rom_only = rom_only; + task->map = map; return task; } @@ -XXX,XX +XXX,XX @@ static void defer_map(const struct pci_dev *pdev, struct vpci_map_task *task) raise_softirq(SCHEDULE_SOFTIRQ); } -static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) +static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only, + bool map) { struct vpci_header *header = &pdev->vpci->header; struct pci_dev *tmp; @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) ASSERT(rw_is_write_locked(&pdev->domain->pci_lock)); - task = alloc_map_task(pdev, cmd, rom_only); + task = alloc_map_task(pdev, cmd, rom_only, map); if ( !task ) return -ENOMEM; @@ -XXX,XX +XXX,XX @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) * be called iff the memory decoding bit is enabled, thus the operation * will always be to establish mappings and process all the BARs. */ - ASSERT((cmd & PCI_COMMAND_MEMORY) && !rom_only); + ASSERT(map && !rom_only); rc = apply_map(pdev->domain, pdev, task); destroy_map_task(task); return rc; @@ -XXX,XX +XXX,XX @@ 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); + modify_bars(pdev, cmd, false, cmd & PCI_COMMAND_MEMORY); else pci_conf_write16(pdev->sbdf, reg, cmd); } @@ -XXX,XX +XXX,XX @@ static void cf_check rom_write( header->rom_enabled = new_enabled; pci_conf_write32(pdev->sbdf, reg, val); } - /* - * 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) ) + /* Note that the command value 0 will never be written to the register */ + else if ( modify_bars(pdev, 0, true, new_enabled) ) /* * 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 @@ -XXX,XX +XXX,XX @@ 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) ? modify_bars(pdev, cmd, false, true) : 0; fail: pci_conf_write16(pdev->sbdf, PCI_COMMAND, cmd); diff --git a/xen/include/xen/vpci.h b/xen/include/xen/vpci.h index XXXXXXX..XXXXXXX 100644 --- a/xen/include/xen/vpci.h +++ b/xen/include/xen/vpci.h @@ -XXX,XX +XXX,XX @@ struct vpci_map_task { } bars[ARRAY_SIZE(((struct vpci_header *)NULL)->bars)]; uint16_t cmd; bool rom_only : 1; + bool map : 1; }; struct vpci_vcpu { -- 2.53.0
Currently, Xen vPCI refuses BAR writes if the BAR is mapped in p2m. If firmware initializes a 32-bit BAR to a bad address, Linux may try to write a new address to the 32-bit BAR without disabling memory decoding. Since Xen refuses such writes, the BAR (and thus PCI device) will be non-functional. Allow the hardware domain to issue 32-bit BAR writes with memory decoding enabled. This increases the compatibility of PVH dom0 with more hardware. Note that Linux aims at disabling memory decoding before writing 64-bit BARs. Continue to refuse 64-bit BAR writes in Xen while those BARs are mapped for now to avoid mapping half-updated BARs in p2m. Resolves: https://gitlab.com/xen-project/xen/-/issues/197 Signed-off-by: Stewart Hildebrand <stewart.hildebrand@amd.com> --- v3->v4: * rebase on dynamically allocated map queue v2->v3: * minor tweaks for fixed number of map/unmap slots v1->v2: * rework on top of queued BAR map/unmap operation machinery RFC->v1: * keep memory decoding enabled in hardware * allow write while memory decoding is enabled for 32-bit BARs only * rework BAR mapping machinery to support unmap-then-map operation --- xen/drivers/vpci/header.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c index XXXXXXX..XXXXXXX 100644 --- a/xen/drivers/vpci/header.c +++ b/xen/drivers/vpci/header.c @@ -XXX,XX +XXX,XX @@ static void cf_check bar_write( { struct vpci_bar *bar = data; bool hi = false; + uint16_t cmd = 0; ASSERT(is_hardware_domain(pdev->domain)); @@ -XXX,XX +XXX,XX @@ static void cf_check bar_write( val &= PCI_BASE_ADDRESS_MEM_MASK; /* - * Xen only cares whether the BAR is mapped into the p2m, so allow BAR - * writes as long as the BAR is not mapped into the p2m. + * Allow 64-bit BAR writes only when the BAR is not mapped in p2m. Always + * allow 32-bit BAR writes. */ if ( bar->enabled ) { - /* If the value written is the current one avoid printing a warning. */ - if ( val != (uint32_t)(bar->addr >> (hi ? 32 : 0)) ) - gprintk(XENLOG_WARNING, - "%pp: ignored BAR %zu write while mapped\n", - &pdev->sbdf, bar - pdev->vpci->header.bars + hi); - return; - } + if ( bar->type == VPCI_BAR_MEM32 ) + { + if ( val == bar->addr ) + return; + cmd = pci_conf_read16(pdev->sbdf, PCI_COMMAND); + modify_bars(pdev, cmd, false, false); + } + else + { + /* If the value written is the same avoid printing a warning. */ + if ( val != (uint32_t)(bar->addr >> (hi ? 32 : 0)) ) + gprintk(XENLOG_WARNING, + "%pp: ignored BAR %zu write while mapped\n", + &pdev->sbdf, bar - pdev->vpci->header.bars + hi); + return; + } + } /* * Update the cached address, so that when memory decoding is enabled @@ -XXX,XX +XXX,XX @@ static void cf_check bar_write( } pci_conf_write32(pdev->sbdf, reg, val); + + if ( bar->enabled ) + modify_bars(pdev, cmd, false, true); } static void cf_check guest_mem_bar_write(const struct pci_dev *pdev, -- 2.53.0