diff --git a/xen/drivers/vpci/vpci.c b/xen/drivers/vpci/vpci.c index da67226e4f..fd02718b47 100644 --- a/xen/drivers/vpci/vpci.c +++ b/xen/drivers/vpci/vpci.c @@ -165,6 +165,92 @@ static int vpci_capability_hide(const struct pci_dev *pdev, unsigned int cap) return 0; } +static struct vpci_register *vpci_get_previous_ext_cap_register( + const struct vpci *vpci, unsigned int offset) +{ + unsigned int pos = PCI_CFG_SPACE_SIZE; + struct vpci_register *r; + + if ( offset <= PCI_CFG_SPACE_SIZE ) + { + ASSERT_UNREACHABLE(); + return NULL; + } + + for ( r = vpci_get_register(vpci, pos, 4); r; + r = pos > PCI_CFG_SPACE_SIZE ? vpci_get_register(vpci, pos, 4) + : NULL ) + { + uint32_t header = (uint32_t)(uintptr_t)r->private; + + ASSERT(header == (uintptr_t)r->private); + + pos = PCI_EXT_CAP_NEXT(header); + if ( pos == offset ) + break; + } + + return r; +} + +static int vpci_ext_capability_hide( + const struct pci_dev *pdev, unsigned int cap) +{ + const unsigned int offset = pci_find_ext_capability(pdev->sbdf, cap); + struct vpci_register *r, *prev_r; + struct vpci *vpci = pdev->vpci; + uint32_t header, pre_header; + + if ( offset < PCI_CFG_SPACE_SIZE ) + { + ASSERT_UNREACHABLE(); + return 0; + } + + spin_lock(&vpci->lock); + r = vpci_get_register(vpci, offset, 4); + if ( !r ) + { + spin_unlock(&vpci->lock); + return -ENODEV; + } + + header = (uint32_t)(uintptr_t)r->private; + if ( offset == PCI_CFG_SPACE_SIZE ) + { + if ( PCI_EXT_CAP_NEXT(header) <= PCI_CFG_SPACE_SIZE ) + r->private = (void *)0; + else + /* + * The first extended capability (0x100) can not be removed from + * the linked list, so instead mask its capability ID to return 0 + * hopefully forcing OSes to skip it. + */ + r->private = (void *)(uintptr_t)(header & ~PCI_EXT_CAP_ID(header)); + + spin_unlock(&vpci->lock); + return 0; + } + + prev_r = vpci_get_previous_ext_cap_register(vpci, offset); + if ( !prev_r ) + { + spin_unlock(&vpci->lock); + return -ENODEV; + } + + pre_header = (uint32_t)(uintptr_t)prev_r->private; + pre_header &= ~PCI_EXT_CAP_NEXT_MASK; + pre_header |= header & PCI_EXT_CAP_NEXT_MASK; + prev_r->private = (void *)(uintptr_t)pre_header; + + list_del(&r->node); + spin_unlock(&vpci->lock); + xfree(r); + + return 0; +} + static int vpci_init_capabilities(struct pci_dev *pdev) { for ( unsigned int i = 0; i < NUM_VPCI_INIT; i++ ) @@ -206,6 +292,8 @@ static int vpci_init_capabilities(struct pci_dev *pdev) if ( !is_ext ) rc = vpci_capability_hide(pdev, cap); + else + rc = vpci_ext_capability_hide(pdev, cap); if ( rc ) { printk(XENLOG_ERR "%pd %pp: hide %s cap %u fail rc=%d\n", diff --git a/xen/include/xen/pci_regs.h b/xen/include/xen/pci_regs.h index 27b4f44eed..3b6963133d 100644 --- a/xen/include/xen/pci_regs.h +++ b/xen/include/xen/pci_regs.h @@ -448,7 +448,10 @@ /* Extended Capabilities (PCI-X 2.0 and Express) */ #define PCI_EXT_CAP_ID(header) ((header) & 0x0000ffff) #define PCI_EXT_CAP_VER(header) (((header) >> 16) & 0xf) -#define PCI_EXT_CAP_NEXT(header) (((header) >> 20) & 0xffc) +#define PCI_EXT_CAP_NEXT_MASK 0xfff00000U +/* Bottom two bits of next capability position are reserved. */ +#define PCI_EXT_CAP_NEXT(header) \ + (MASK_EXTR(header, PCI_EXT_CAP_NEXT_MASK) & 0xffcU) #define PCI_EXT_CAP_ID_ERR 1 #define PCI_EXT_CAP_ID_VC 2