Introduce a set of handlers that trap accesses to the PCI BARs and the command register, in order to emulate BAR sizing and BAR relocation.
The command handler is used to detect changes to bit 2 (response to memory space accesses), and maps/unmaps the BARs of the device into the guest p2m. The BAR register handlers are used to detect attempts by the guest to size or relocate the BARs. Signed-off-by: Roger Pau Monné <roger....@citrix.com> --- Cc: Andrew Cooper <andrew.coop...@citrix.com> Cc: George Dunlap <george.dun...@eu.citrix.com> Cc: Ian Jackson <ian.jack...@eu.citrix.com> Cc: Jan Beulich <jbeul...@suse.com> Cc: Konrad Rzeszutek Wilk <konrad.w...@oracle.com> Cc: Stefano Stabellini <sstabell...@kernel.org> Cc: Tim Deegan <t...@xen.org> Cc: Wei Liu <wei.l...@citrix.com> --- Changes since v3: - Propagate previous changes: drop xen_ prefix and use u8/u16/u32 instead of the previous half_word/word/double_word. - Constify some of the paramerters. - s/VPCI_BAR_MEM/VPCI_BAR_MEM32/. - Simplify the number of fields stored for each BAR, a single address field is stored and contains the address of the BAR both on Xen and in the guest. - Allow the guest to move the BARs around in the physical memory map. - Add support for expansion ROM BARs. - Do not cache the value of the command register. - Remove a label used in vpci_cmd_write. - Fix the calculation of the sizing mask in vpci_bar_write. - Check the memory decode bit in order to decide if a BAR is positioned or not. - Disable memory decoding before sizing the BARs in Xen. - When mapping/unmapping BARs check if there's overlap between BARs, in order to avoid unmapping memory required by another BAR. - Introduce a macro to check whether a BAR is mappable or not. - Add a comment regarding the lack of support for SR-IOV. - Remove the usage of the GENMASK macro. Changes since v2: - Detect unset BARs and allow the hardware domain to position them. --- xen/drivers/vpci/Makefile | 2 +- xen/drivers/vpci/header.c | 473 ++++++++++++++++++++++++++++++++++++++++++++++ xen/include/xen/vpci.h | 23 +++ 3 files changed, 497 insertions(+), 1 deletion(-) create mode 100644 xen/drivers/vpci/header.c diff --git a/xen/drivers/vpci/Makefile b/xen/drivers/vpci/Makefile index 840a906470..241467212f 100644 --- a/xen/drivers/vpci/Makefile +++ b/xen/drivers/vpci/Makefile @@ -1 +1 @@ -obj-y += vpci.o +obj-y += vpci.o header.o diff --git a/xen/drivers/vpci/header.c b/xen/drivers/vpci/header.c new file mode 100644 index 0000000000..3c800c4cf7 --- /dev/null +++ b/xen/drivers/vpci/header.c @@ -0,0 +1,473 @@ +/* + * Generic functionality for handling accesses to the PCI header from the + * configuration space. + * + * Copyright (C) 2017 Citrix Systems R&D + * + * This program is free software; you can redistribute it and/or + * modify it under the terms and conditions of the GNU General Public + * License, version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; If not, see <http://www.gnu.org/licenses/>. + */ + +#include <xen/sched.h> +#include <xen/vpci.h> +#include <xen/p2m-common.h> + +#define MAPPABLE_BAR(x) \ + (((x)->type == VPCI_BAR_MEM32 || (x)->type == VPCI_BAR_MEM64_LO || \ + ((x)->type == VPCI_BAR_ROM && (x)->enabled)) && \ + (x)->addr != INVALID_PADDR) + +static struct rangeset *vpci_get_bar_memory(const struct domain *d, + const struct vpci_bar *map) +{ + const struct pci_dev *pdev; + struct rangeset *mem = rangeset_new(NULL, NULL, 0); + int rc; + + if ( !mem ) + return ERR_PTR(-ENOMEM); + + /* + * Create a rangeset that represents the current BAR memory region + * and compare it against all the currently active BAR memory regions. + * If an overlap is found, subtract it from the region to be + * mapped/unmapped. + * + * NB: the rangeset uses frames, and if start and end addresses are + * equal it means only one frame is used, that's why PFN_DOWN is used + * to calculate the end of the rangeset. + */ + rc = rangeset_add_range(mem, PFN_DOWN(map->addr), + PFN_DOWN(map->addr + map->size)); + if ( rc ) + { + rangeset_destroy(mem); + return ERR_PTR(rc); + } + + list_for_each_entry(pdev, &d->arch.pdev_list, domain_list) + { + uint16_t cmd = pci_conf_read16(pdev->seg, pdev->bus, + PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), + PCI_COMMAND); + unsigned int i; + + /* Check if memory decoding is enabled. */ + if ( !(cmd & PCI_COMMAND_MEMORY) ) + continue; + + for ( i = 0; i < ARRAY_SIZE(pdev->vpci->header.bars); i++ ) + { + const struct vpci_bar *bar = &pdev->vpci->header.bars[i]; + + if ( bar == map || !MAPPABLE_BAR(bar) || + !rangeset_overlaps_range(mem, PFN_DOWN(bar->addr), + PFN_DOWN(bar->addr + bar->size)) ) + continue; + + rc = rangeset_remove_range(mem, PFN_DOWN(bar->addr), + PFN_DOWN(bar->addr + bar->size)); + if ( rc ) + { + rangeset_destroy(mem); + return ERR_PTR(rc); + } + } + } + + return mem; +} + +struct map_data { + struct domain *d; + bool map; +}; + +static int vpci_map_range(unsigned long s, unsigned long e, void *data) +{ + const struct map_data *map = data; + + return modify_mmio(map->d, _gfn(s), _mfn(s), e - s + 1, map->map); +} + +static int vpci_modify_bar(struct domain *d, const struct vpci_bar *bar, + const bool map) +{ + struct rangeset *mem; + struct map_data data = { .d = d, .map = map }; + int rc; + + ASSERT(MAPPABLE_BAR(bar)); + + mem = vpci_get_bar_memory(d, bar); + if ( IS_ERR(mem) ) + return -PTR_ERR(mem); + + rc = rangeset_report_ranges(mem, 0, ~0ul, vpci_map_range, &data); + rangeset_destroy(mem); + if ( rc ) + return rc; + + return 0; +} + +static int vpci_modify_bars(const struct pci_dev *pdev, const bool map) +{ + const struct vpci_header *header = &pdev->vpci->header; + unsigned int i; + + for ( i = 0; i < ARRAY_SIZE(header->bars); i++ ) + { + const struct vpci_bar *bar = &header->bars[i]; + int rc; + + if ( !MAPPABLE_BAR(bar) ) + continue; + + rc = vpci_modify_bar(pdev->domain, bar, map); + if ( rc ) + return rc; + } + + return 0; +} + +static void vpci_cmd_read(struct pci_dev *pdev, unsigned int reg, + union vpci_val *val, void *data) +{ + uint8_t seg = pdev->seg, bus = pdev->bus; + uint8_t slot = PCI_SLOT(pdev->devfn), func = PCI_FUNC(pdev->devfn); + + val->u16 = pci_conf_read16(seg, bus, slot, func, reg); +} + +static void vpci_cmd_write(struct pci_dev *pdev, unsigned int reg, + union vpci_val val, void *data) +{ + uint16_t cmd = val.u16, current_cmd; + uint8_t seg = pdev->seg, bus = pdev->bus; + uint8_t slot = PCI_SLOT(pdev->devfn), func = PCI_FUNC(pdev->devfn); + int rc; + + current_cmd = pci_conf_read16(seg, bus, slot, func, reg); + + if ( !((cmd ^ current_cmd) & PCI_COMMAND_MEMORY) ) + { + /* + * Let the guest play with all the bits directly except for the + * memory decoding one. + */ + pci_conf_write16(seg, bus, slot, func, reg, cmd); + return; + } + + /* Memory space access change. */ + rc = vpci_modify_bars(pdev, cmd & PCI_COMMAND_MEMORY); + if ( rc ) + { + dprintk(XENLOG_ERR, + "%04x:%02x:%02x.%u:unable to %smap BARs: %d\n", + seg, bus, slot, func, + cmd & PCI_COMMAND_MEMORY ? "" : "un", rc); + return; + } + + pci_conf_write16(seg, bus, slot, func, reg, cmd); +} + +static void vpci_bar_read(struct pci_dev *pdev, unsigned int reg, + union vpci_val *val, void *data) +{ + const struct vpci_bar *bar = data; + bool hi = false; + + ASSERT(bar->type == VPCI_BAR_MEM32 || bar->type == VPCI_BAR_MEM64_LO || + bar->type == VPCI_BAR_MEM64_HI); + + if ( bar->type == VPCI_BAR_MEM64_HI ) + { + ASSERT(reg > PCI_BASE_ADDRESS_0); + bar--; + hi = true; + } + + if ( bar->sizing ) + val->u32 = ~(bar->size - 1) >> (hi ? 32 : 0); + else + val->u32 = bar->addr >> (hi ? 32 : 0); + + if ( !hi ) + { + val->u32 |= bar->type == VPCI_BAR_MEM32 ? PCI_BASE_ADDRESS_MEM_TYPE_32 + : PCI_BASE_ADDRESS_MEM_TYPE_64; + val->u32 |= bar->prefetchable ? PCI_BASE_ADDRESS_MEM_PREFETCH : 0; + } +} + +static void vpci_bar_write(struct pci_dev *pdev, unsigned int reg, + union vpci_val val, void *data) +{ + struct vpci_bar *bar = data; + uint8_t seg = pdev->seg, bus = pdev->bus; + uint8_t slot = PCI_SLOT(pdev->devfn), func = PCI_FUNC(pdev->devfn); + uint32_t wdata = val.u32, size_mask; + bool hi = false; + + switch ( bar->type ) + { + case VPCI_BAR_MEM32: + case VPCI_BAR_MEM64_LO: + size_mask = (uint32_t)PCI_BASE_ADDRESS_MEM_MASK; + break; + case VPCI_BAR_MEM64_HI: + size_mask = ~0u; + break; + default: + ASSERT_UNREACHABLE(); + return; + } + + if ( (wdata & size_mask) == size_mask ) + { + /* Next reads from this register are going to return the BAR size. */ + bar->sizing = true; + return; + } + + /* End previous sizing cycle if any. */ + bar->sizing = false; + + /* + * Ignore attempts to change the position of the BAR if memory decoding is + * active. + */ + if ( pci_conf_read16(seg, bus, slot, func, PCI_COMMAND) & + PCI_COMMAND_MEMORY ) + return; + + if ( bar->type == VPCI_BAR_MEM64_HI ) + { + ASSERT(reg > PCI_BASE_ADDRESS_0); + bar--; + hi = true; + } + + if ( !hi ) + wdata &= PCI_BASE_ADDRESS_MEM_MASK; + + /* Update the relevant part of the BAR address. */ + bar->addr &= ~((uint64_t)0xffffffff << (hi ? 32 : 0)); + bar->addr |= (uint64_t)wdata << (hi ? 32 : 0); + + /* Make sure Xen writes back the same value for the BAR RO bits. */ + if ( !hi ) + wdata |= pci_conf_read32(pdev->seg, pdev->bus, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), reg) & + ~PCI_BASE_ADDRESS_MEM_MASK; + pci_conf_write32(pdev->seg, pdev->bus, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), reg, wdata); +} + +static void vpci_rom_read(struct pci_dev *pdev, unsigned int reg, + union vpci_val *val, void *data) +{ + const struct vpci_bar *rom = data; + + if ( rom->sizing ) + val->u32 = ~(rom->size - 1); + else + val->u32 = rom->addr; + + val->u32 |= rom->enabled ? PCI_ROM_ADDRESS_ENABLE : 0; +} + +static void vpci_rom_write(struct pci_dev *pdev, unsigned int reg, + union vpci_val val, void *data) +{ + struct vpci_bar *rom = data; + uint8_t seg = pdev->seg, bus = pdev->bus; + uint8_t slot = PCI_SLOT(pdev->devfn), func = PCI_FUNC(pdev->devfn); + const uint32_t wdata = val.u32; + + if ( (wdata & PCI_ROM_ADDRESS_MASK) == PCI_ROM_ADDRESS_MASK ) + { + /* Next reads from this register are going to return the BAR size. */ + rom->sizing = true; + return; + } + + /* End previous sizing cycle if any. */ + rom->sizing = false; + + rom->addr = wdata & PCI_ROM_ADDRESS_MASK; + + /* Check if memory decoding is enabled. */ + if ( pci_conf_read16(seg, bus, slot, func, PCI_COMMAND) & + PCI_COMMAND_MEMORY && + (rom->enabled ^ (wdata & PCI_ROM_ADDRESS_ENABLE)) ) + { + if ( vpci_modify_bar(pdev->domain, rom, + wdata & PCI_ROM_ADDRESS_ENABLE) ) + return; + + rom->enabled = wdata & PCI_ROM_ADDRESS_ENABLE; + } + + pci_conf_write32(pdev->seg, pdev->bus, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), reg, wdata); +} + +static int vpci_init_bars(struct pci_dev *pdev) +{ + uint8_t seg = pdev->seg, bus = pdev->bus; + uint8_t slot = PCI_SLOT(pdev->devfn), func = PCI_FUNC(pdev->devfn); + uint8_t header_type; + uint16_t cmd; + uint32_t rom_val; + uint64_t addr, size; + unsigned int i, num_bars, rom_reg; + struct vpci_header *header = &pdev->vpci->header; + struct vpci_bar *bars = header->bars; + int rc; + + header_type = pci_conf_read8(seg, bus, slot, func, PCI_HEADER_TYPE) & 0x7f; + switch ( header_type ) + { + case PCI_HEADER_TYPE_NORMAL: + num_bars = 6; + rom_reg = PCI_ROM_ADDRESS; + break; + case PCI_HEADER_TYPE_BRIDGE: + num_bars = 2; + rom_reg = PCI_ROM_ADDRESS1; + break; + default: + return -EOPNOTSUPP; + } + + /* Setup a handler for the command register. */ + cmd = pci_conf_read16(seg, bus, slot, func, PCI_COMMAND); + rc = vpci_add_register(pdev, vpci_cmd_read, vpci_cmd_write, PCI_COMMAND, + 2, header); + if ( rc ) + return rc; + + /* Disable memory decoding before sizing. */ + if ( cmd & PCI_COMMAND_MEMORY ) + pci_conf_write16(seg, bus, slot, func, PCI_COMMAND, + cmd & ~PCI_COMMAND_MEMORY); + + for ( i = 0; i < num_bars; i++ ) + { + uint8_t reg = PCI_BASE_ADDRESS_0 + i * 4; + uint32_t val = pci_conf_read32(seg, bus, slot, func, reg); + + if ( i && bars[i - 1].type == VPCI_BAR_MEM64_LO ) + { + bars[i].type = VPCI_BAR_MEM64_HI; + rc = vpci_add_register(pdev, vpci_bar_read, vpci_bar_write, reg, 4, + &bars[i]); + if ( rc ) + return rc; + + continue; + } + if ( (val & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO ) + { + bars[i].type = VPCI_BAR_IO; + continue; + } + if ( (val & PCI_BASE_ADDRESS_MEM_TYPE_MASK) == + PCI_BASE_ADDRESS_MEM_TYPE_64 ) + bars[i].type = VPCI_BAR_MEM64_LO; + else + bars[i].type = VPCI_BAR_MEM32; + + /* Size the BAR and map it. */ + rc = pci_size_mem_bar(seg, bus, slot, func, reg, i == num_bars - 1, + &addr, &size); + if ( rc < 0 ) + return rc; + + if ( size == 0 ) + { + bars[i].type = VPCI_BAR_EMPTY; + continue; + } + + bars[i].addr = (cmd & PCI_COMMAND_MEMORY) ? addr : INVALID_PADDR; + bars[i].size = size; + bars[i].prefetchable = val & PCI_BASE_ADDRESS_MEM_PREFETCH; + + rc = vpci_add_register(pdev, vpci_bar_read, vpci_bar_write, reg, 4, + &bars[i]); + if ( rc ) + return rc; + } + + /* Check expansion ROM. */ + rom_val = pci_conf_read32(seg, bus, slot, func, rom_reg); + if ( rom_val & PCI_ROM_ADDRESS_ENABLE ) + pci_conf_write32(seg, bus, slot, func, rom_reg, + rom_val & ~PCI_ROM_ADDRESS_ENABLE); + + rc = pci_size_mem_bar(seg, bus, slot, func, rom_reg, true, &addr, &size); + if ( rc < 0 ) + return rc; + + if ( size ) + { + struct vpci_bar *rom = &header->bars[num_bars]; + + rom->type = VPCI_BAR_ROM; + rom->size = size; + rom->enabled = rom_val & PCI_ROM_ADDRESS_ENABLE; + if ( rom->enabled ) + rom->addr = addr; + else + rom->addr = INVALID_PADDR; + + rc = vpci_add_register(pdev, vpci_rom_read, vpci_rom_write, rom_reg, 4, + rom); + if ( rc ) + return rc; + + if ( rom->enabled ) + pci_conf_write32(seg, bus, slot, func, rom_reg, rom_val); + } + + if ( cmd & PCI_COMMAND_MEMORY ) + { + rc = vpci_modify_bars(pdev, true); + if ( rc ) + return rc; + + /* Enable memory decoding. */ + pci_conf_write16(seg, bus, slot, func, PCI_COMMAND, cmd); + } + + return 0; +} + +REGISTER_VPCI_INIT(vpci_init_bars); + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ + diff --git a/xen/include/xen/vpci.h b/xen/include/xen/vpci.h index 5e1b0bb3da..452ee482e8 100644 --- a/xen/include/xen/vpci.h +++ b/xen/include/xen/vpci.h @@ -63,6 +63,29 @@ void vpci_write(unsigned int seg, unsigned int bus, unsigned int slot, struct vpci { /* Root pointer for the tree of vPCI handlers. */ struct list_head handlers; + +#ifdef __XEN__ + /* Hide the rest of the vpci struct from the user-space test harness. */ + struct vpci_header { + /* Information about the PCI BARs of this device. */ + struct vpci_bar { + enum { + VPCI_BAR_EMPTY, + VPCI_BAR_IO, + VPCI_BAR_MEM32, + VPCI_BAR_MEM64_LO, + VPCI_BAR_MEM64_HI, + VPCI_BAR_ROM, + } type; + paddr_t addr; + uint64_t size; + bool prefetchable; + bool sizing; + bool enabled; + } bars[7]; /* At most 6 BARS + 1 expansion ROM BAR. */ + /* FIXME: currently there's no support for SR-IOV. */ + } header; +#endif }; #endif -- 2.11.0 (Apple Git-81) _______________________________________________ Xen-devel mailing list Xen-devel@lists.xen.org https://lists.xen.org/xen-devel