diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c index feccd070dd..47648c3951 100644 --- a/xen/drivers/vpci/header.c +++ b/xen/drivers/vpci/header.c @@ -34,6 +34,7 @@ struct map_data { struct domain *d; + const struct vpci_bar *bar; bool map; }; @@ -41,26 +42,37 @@ static int cf_check map_range( unsigned long s, unsigned long e, void *data, unsigned long *c) { const struct map_data *map = data; + /* Start address of the BAR as seen by the guest. */ + unsigned long start_gfn = PFN_DOWN(map->bar->guest_addr); + /* Physical start address of the BAR. */ + unsigned long start_mfn = PFN_DOWN(map->bar->addr); int rc; for ( ; ; ) { unsigned long size = e - s + 1; + /* + * Ranges to be mapped don't always start at the BAR start address, as + * there can be holes or partially consumed ranges. Account for the + * offset of the current address from the BAR start. + */ + unsigned long map_mfn = start_mfn + s - start_gfn; + unsigned long m_end = map_mfn + size - 1; - if ( !iomem_access_permitted(map->d, s, e) ) + if ( !iomem_access_permitted(map->d, map_mfn, m_end) ) { printk(XENLOG_G_WARNING "%pd denied access to MMIO range [%#lx, %#lx]\n", - map->d, s, e); + map->d, map_mfn, m_end); return -EPERM; } - rc = xsm_iomem_mapping(XSM_HOOK, map->d, s, e, map->map); + rc = xsm_iomem_mapping(XSM_HOOK, map->d, map_mfn, m_end, map->map); if ( rc ) { printk(XENLOG_G_WARNING "%pd XSM denied access to MMIO range [%#lx, %#lx]: %d\n", - map->d, s, e, rc); + map->d, map_mfn, m_end, rc); return rc; } @@ -73,8 +85,8 @@ static int cf_check map_range( * - {un}map_mmio_regions doesn't support preemption. */ - rc = map->map ? map_mmio_regions(map->d, _gfn(s), size, _mfn(s)) - : unmap_mmio_regions(map->d, _gfn(s), size, _mfn(s)); + rc = map->map ? map_mmio_regions(map->d, _gfn(s), size, _mfn(map_mfn)) + : unmap_mmio_regions(map->d, _gfn(s), size, _mfn(map_mfn)); if ( rc == 0 ) { *c += size; @@ -83,8 +95,9 @@ static int cf_check map_range( if ( rc < 0 ) { printk(XENLOG_G_WARNING - "Failed to identity %smap [%lx, %lx] for d%d: %d\n", - map->map ? "" : "un", s, e, map->d->domain_id, rc); + "Failed to %smap [%lx %lx] -> [%lx %lx] for %pd: %d\n", + map->map ? "" : "un", s, e, map_mfn, + map_mfn + size, map->d, rc); break; } ASSERT(rc < size); @@ -163,10 +176,6 @@ 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; - struct map_data data = { - .d = v->domain, - .map = v->vpci.cmd & PCI_COMMAND_MEMORY, - }; struct vpci_header *header = NULL; unsigned int i; @@ -186,6 +195,11 @@ bool vpci_process_pending(struct vcpu *v) 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) ) @@ -236,7 +250,6 @@ bool vpci_process_pending(struct vcpu *v) static int __init apply_map(struct domain *d, const struct pci_dev *pdev, uint16_t cmd) { - struct map_data data = { .d = d, .map = true }; struct vpci_header *header = &pdev->vpci->header; int rc = 0; unsigned int i; @@ -246,6 +259,7 @@ 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 map_data data = { .d = d, .map = true, .bar = bar }; if ( rangeset_is_empty(bar->mem) ) continue; @@ -311,12 +325,16 @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) * First fill the rangesets with the BAR of this device or with the ROM * BAR only, depending on whether the guest is toggling the memory decode * bit of the command register, or the enable bit of the ROM BAR register. + * + * For non-hardware domain we use guest physical addresses. */ for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) { struct vpci_bar *bar = &header->bars[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; @@ -336,11 +354,26 @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) continue; } - rc = rangeset_add_range(bar->mem, start, end); + ASSERT(rangeset_is_empty(bar->mem)); + + /* + * Make sure that the guest set address has the same page offset + * as the physical address on the host or otherwise things won't work as + * expected. + */ + if ( PAGE_OFFSET(bar->guest_addr) != PAGE_OFFSET(bar->addr) ) + { + 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 = rangeset_add_range(bar->mem, start_guest, end_guest); if ( rc ) { printk(XENLOG_G_WARNING "Failed to add [%lx, %lx]: %d\n", - start, end, rc); + start_guest, end_guest, rc); return rc; } @@ -352,12 +385,12 @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) if ( rangeset_is_empty(prev_bar->mem) ) continue; - rc = rangeset_remove_range(prev_bar->mem, start, end); + rc = rangeset_remove_range(prev_bar->mem, start_guest, end_guest); if ( rc ) { gprintk(XENLOG_WARNING, "%pp: failed to remove overlapping range [%lx, %lx]: %d\n", - &pdev->sbdf, start, end, rc); + &pdev->sbdf, start_guest, end_guest, rc); return rc; } } @@ -425,8 +458,8 @@ static int modify_bars(const struct pci_dev *pdev, uint16_t cmd, bool rom_only) for ( i = 0; i < ARRAY_SIZE(tmp->vpci->header.bars); i++ ) { const struct vpci_bar *remote_bar = &tmp->vpci->header.bars[i]; - unsigned long start = PFN_DOWN(remote_bar->addr); - unsigned long end = PFN_DOWN(remote_bar->addr + + unsigned long start = PFN_DOWN(remote_bar->guest_addr); + unsigned long end = PFN_DOWN(remote_bar->guest_addr + remote_bar->size - 1); if ( !remote_bar->enabled ) @@ -513,6 +546,8 @@ static void cf_check bar_write( struct vpci_bar *bar = data; bool hi = false; + ASSERT(is_hardware_domain(pdev->domain)); + if ( bar->type == VPCI_BAR_MEM64_HI ) { ASSERT(reg > PCI_BASE_ADDRESS_0); @@ -543,6 +578,8 @@ static void cf_check bar_write( */ bar->addr &= ~(0xffffffffULL << (hi ? 32 : 0)); bar->addr |= (uint64_t)val << (hi ? 32 : 0); + /* Update guest address, so hardware domain BAR is identity mapped. */ + bar->guest_addr = bar->addr; /* Make sure Xen writes back the same value for the BAR RO bits. */ if ( !hi ) @@ -639,11 +676,14 @@ static void cf_check rom_write( } if ( !rom->enabled ) + { /* * If the ROM BAR is not mapped update the address field so the * correct address is mapped into the p2m. */ rom->addr = val & PCI_ROM_ADDRESS_MASK; + rom->guest_addr = rom->addr; + } if ( !header->bars_mapped || rom->enabled == new_enabled ) { @@ -667,7 +707,10 @@ static void cf_check rom_write( return; if ( !new_enabled ) + { rom->addr = val & PCI_ROM_ADDRESS_MASK; + rom->guest_addr = rom->addr; + } } static int bar_add_rangeset(const struct pci_dev *pdev, struct vpci_bar *bar, @@ -862,6 +905,7 @@ static int cf_check init_header(struct pci_dev *pdev) } bars[i].addr = addr; + bars[i].guest_addr = addr; bars[i].size = size; bars[i].prefetchable = val & PCI_BASE_ADDRESS_MEM_PREFETCH; @@ -884,6 +928,7 @@ static int cf_check init_header(struct pci_dev *pdev) rom->type = VPCI_BAR_ROM; rom->size = size; rom->addr = addr; + rom->guest_addr = addr; header->rom_enabled = pci_conf_read32(pdev->sbdf, rom_reg) & PCI_ROM_ADDRESS_ENABLE; diff --git a/xen/include/xen/vpci.h b/xen/include/xen/vpci.h index 817ee9ee73..e89c571890 100644 --- a/xen/include/xen/vpci.h +++ b/xen/include/xen/vpci.h @@ -216,7 +216,8 @@ int vpci_msix_arch_print(const struct vpci_msix *msix); */ static inline paddr_t vmsix_table_base(const struct vpci *vpci, unsigned int nr) { - return vpci->header.bars[vpci->msix->tables[nr] & PCI_MSIX_BIRMASK].addr; + return vpci->header.bars[vpci->msix->tables[nr] & + PCI_MSIX_BIRMASK].guest_addr; } static inline paddr_t vmsix_table_addr(const struct vpci *vpci, unsigned int nr)