diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c index 1397f83e41..98edf5b89c 100644 --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -783,6 +783,29 @@ bool is_iomem_page(mfn_t mfn) return (page_get_owner(page) == dom_io); } +/* Input ranges are inclusive. */ +bool is_memory_hole(mfn_t start, mfn_t end) +{ + unsigned long s = mfn_x(start); + unsigned long e = mfn_x(end); + unsigned int i; + + for ( i = 0; i < e820.nr_map; i++ ) + { + const struct e820entry *entry = &e820.map[i]; + + if ( !entry->size ) + continue; + + /* Do not allow overlaps with any memory range. */ + if ( s <= PFN_DOWN(entry->addr + entry->size - 1) && + PFN_DOWN(entry->addr) <= e ) + return false; + } + + return true; +} + static int update_xen_mappings(unsigned long mfn, unsigned int cacheattr) { int err = 0; diff --git a/xen/drivers/passthrough/pci.c b/xen/drivers/passthrough/pci.c index 1fad80362f..e8b09d77d8 100644 --- a/xen/drivers/passthrough/pci.c +++ b/xen/drivers/passthrough/pci.c @@ -233,6 +233,9 @@ static void check_pdev(const struct pci_dev *pdev) PCI_STATUS_REC_TARGET_ABORT | PCI_STATUS_REC_MASTER_ABORT | \ PCI_STATUS_SIG_SYSTEM_ERROR | PCI_STATUS_DETECTED_PARITY) u16 val; + unsigned int nbars = 0, rom_pos = 0, i; + static const char warn[] = XENLOG_WARNING + "%pp disabled: %sBAR [%#lx, %#lx] overlaps with memory map\n"; if ( command_mask ) { @@ -251,6 +254,8 @@ static void check_pdev(const struct pci_dev *pdev) switch ( pci_conf_read8(pdev->sbdf, PCI_HEADER_TYPE) & 0x7f ) { case PCI_HEADER_TYPE_BRIDGE: + nbars = PCI_HEADER_BRIDGE_NR_BARS; + rom_pos = PCI_ROM_ADDRESS1; if ( !bridge_ctl_mask ) break; val = pci_conf_read16(pdev->sbdf, PCI_BRIDGE_CONTROL); @@ -267,11 +272,75 @@ static void check_pdev(const struct pci_dev *pdev) } break; + case PCI_HEADER_TYPE_NORMAL: + nbars = PCI_HEADER_NORMAL_NR_BARS; + rom_pos = PCI_ROM_ADDRESS; + break; + case PCI_HEADER_TYPE_CARDBUS: /* TODO */ break; } #undef PCI_STATUS_CHECK + + /* Check if BARs overlap with other memory regions. */ + val = pci_conf_read16(pdev->sbdf, PCI_COMMAND); + if ( !(val & PCI_COMMAND_MEMORY) || pdev->ignore_bars ) + return; + + pci_conf_write16(pdev->sbdf, PCI_COMMAND, val & ~PCI_COMMAND_MEMORY); + for ( i = 0; i < nbars; ) + { + uint64_t addr, size; + unsigned int reg = PCI_BASE_ADDRESS_0 + i * 4; + int rc = 1; + + if ( (pci_conf_read32(pdev->sbdf, reg) & PCI_BASE_ADDRESS_SPACE) != + PCI_BASE_ADDRESS_SPACE_MEMORY ) + goto next; + + rc = pci_size_mem_bar(pdev->sbdf, reg, &addr, &size, + (i == nbars - 1) ? PCI_BAR_LAST : 0); + if ( rc < 0 ) + /* Unable to size, better leave memory decoding disabled. */ + return; + if ( size && !is_memory_hole(maddr_to_mfn(addr), + maddr_to_mfn(addr + size - 1)) ) + { + /* + * Return without enabling memory decoding if BAR position is not + * in IO suitable memory. Let the hardware domain re-position the + * BAR. + */ + printk(warn, + &pdev->sbdf, "", PFN_DOWN(addr), PFN_DOWN(addr + size - 1)); + return; + } + + next: + ASSERT(rc > 0); + i += rc; + } + + if ( rom_pos && + (pci_conf_read32(pdev->sbdf, rom_pos) & PCI_ROM_ADDRESS_ENABLE) ) + { + uint64_t addr, size; + int rc = pci_size_mem_bar(pdev->sbdf, rom_pos, &addr, &size, + PCI_BAR_ROM); + + if ( rc < 0 ) + return; + if ( size && !is_memory_hole(maddr_to_mfn(addr), + maddr_to_mfn(addr + size - 1)) ) + { + printk(warn, &pdev->sbdf, "ROM ", PFN_DOWN(addr), + PFN_DOWN(addr + size - 1)); + return; + } + } + + pci_conf_write16(pdev->sbdf, PCI_COMMAND, val); } static void apply_quirks(struct pci_dev *pdev) @@ -399,8 +468,8 @@ static struct pci_dev *alloc_pdev(struct pci_seg *pseg, u8 bus, u8 devfn) break; } - check_pdev(pdev); apply_quirks(pdev); + check_pdev(pdev); return pdev; } diff --git a/xen/include/xen/mm.h b/xen/include/xen/mm.h index 5db26ed477..3be754da92 100644 --- a/xen/include/xen/mm.h +++ b/xen/include/xen/mm.h @@ -554,6 +554,8 @@ int __must_check steal_page(struct domain *d, struct page_info *page, int page_is_ram_type(unsigned long mfn, unsigned long mem_type); /* Returns the page type(s). */ unsigned int page_get_ram_type(mfn_t mfn); +/* Check if a range falls into a hole in the memory map. */ +bool is_memory_hole(mfn_t start, mfn_t end); /* Prepare/destroy a ring for a dom0 helper. Helper with talk * with Xen on behalf of this domain. */ diff --git a/xen/include/xen/pci_regs.h b/xen/include/xen/pci_regs.h index cc4ee3b83e..ee8e82be36 100644 --- a/xen/include/xen/pci_regs.h +++ b/xen/include/xen/pci_regs.h @@ -88,6 +88,8 @@ * 0xffffffff to the register, and reading it back. Only * 1 bits are decoded. */ +#define PCI_HEADER_NORMAL_NR_BARS 6 +#define PCI_HEADER_BRIDGE_NR_BARS 2 #define PCI_BASE_ADDRESS_0 0x10 /* 32 bits */ #define PCI_BASE_ADDRESS_1 0x14 /* 32 bits [htype 0,1 only] */ #define PCI_BASE_ADDRESS_2 0x18 /* 32 bits [htype 0 only] */ diff --git a/xen/include/xen/vpci.h b/xen/include/xen/vpci.h index 3f32de9d7e..e8ac1eb395 100644 --- a/xen/include/xen/vpci.h +++ b/xen/include/xen/vpci.h @@ -80,8 +80,6 @@ struct vpci { bool prefetchable : 1; /* Store whether the BAR is mapped into guest p2m. */ bool enabled : 1; -#define PCI_HEADER_NORMAL_NR_BARS 6 -#define PCI_HEADER_BRIDGE_NR_BARS 2 } bars[PCI_HEADER_NORMAL_NR_BARS + 1]; /* At most 6 BARS + 1 expansion ROM BAR. */