diff --git a/tools/libxl/libxl_pci.c b/tools/libxl/libxl_pci.c index 6f8f49c7c0..370f60b9c1 100644 --- a/tools/libxl/libxl_pci.c +++ b/tools/libxl/libxl_pci.c @@ -761,6 +761,7 @@ static int libxl__device_pci_assignable_add(libxl__gc *gc, libxl_device_pci *pcidev, int rebind) { + libxl_ctx *ctx = libxl__gc_owner(gc); unsigned dom, bus, dev, func; char *spath, *driver_path = NULL; int rc; @@ -786,7 +787,7 @@ static int libxl__device_pci_assignable_add(libxl__gc *gc, } if ( rc ) { LOG(WARN, PCI_BDF" already assigned to pciback", dom, bus, dev, func); - return 0; + goto quarantine; } /* Check to see if there's already a driver that we need to unbind from */ @@ -817,6 +818,19 @@ static int libxl__device_pci_assignable_add(libxl__gc *gc, return ERROR_FAIL; } +quarantine: + /* + * DOMID_IO is just a sentinel domain, without any actual mappings, + * so always pass XEN_DOMCTL_DEV_RDM_RELAXED to avoid assignment being + * unnecessarily denied. + */ + rc = xc_assign_device(ctx->xch, DOMID_IO, pcidev_encode_bdf(pcidev), + XEN_DOMCTL_DEV_RDM_RELAXED); + if ( rc < 0 ) { + LOG(ERROR, "failed to quarantine "PCI_BDF, dom, bus, dev, func); + return ERROR_FAIL; + } + return 0; } @@ -824,9 +838,18 @@ static int libxl__device_pci_assignable_remove(libxl__gc *gc, libxl_device_pci *pcidev, int rebind) { + libxl_ctx *ctx = libxl__gc_owner(gc); int rc; char *driver_path; + /* De-quarantine */ + rc = xc_deassign_device(ctx->xch, DOMID_IO, pcidev_encode_bdf(pcidev)); + if ( rc < 0 ) { + LOG(ERROR, "failed to de-quarantine "PCI_BDF, pcidev->domain, pcidev->bus, + pcidev->dev, pcidev->func); + return ERROR_FAIL; + } + /* Unbind from pciback */ if ( (rc=pciback_dev_is_assigned(gc, pcidev)) < 0 ) { return ERROR_FAIL; diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c index b5ca5031a8..ed1e49bf6f 100644 --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -311,9 +311,11 @@ void __init arch_init_memory(void) * Initialise our DOMID_IO domain. * This domain owns I/O pages that are within the range of the page_info * array. Mappings occur at the priv of the caller. + * Quarantined PCI devices will be associated with this domain. */ dom_io = domain_create(DOMID_IO, DOMCRF_dummy, 0, NULL); BUG_ON(IS_ERR(dom_io)); + INIT_LIST_HEAD(&dom_io->arch.pdev_list); /* * Initialise our COW domain. diff --git a/xen/common/domctl.c b/xen/common/domctl.c index 858f377281..bb2a85c7c3 100644 --- a/xen/common/domctl.c +++ b/xen/common/domctl.c @@ -400,6 +400,16 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl) case XEN_DOMCTL_gdbsx_guestmemio: d = NULL; break; + case XEN_DOMCTL_assign_device: + case XEN_DOMCTL_deassign_device: + if ( op->domain == DOMID_IO ) + { + d = dom_io; + break; + } + else if ( op->domain == DOMID_INVALID ) + return -ESRCH; + /* fall through */ default: d = rcu_lock_domain_by_id(op->domain); if ( !d && op->cmd != XEN_DOMCTL_getdomaininfo ) @@ -412,7 +422,7 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl) if ( !domctl_lock_acquire() ) { - if ( d ) + if ( d && d != dom_io ) rcu_unlock_domain(d); return hypercall_create_continuation( __HYPERVISOR_domctl, "h", u_domctl); @@ -1165,7 +1175,7 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl) domctl_lock_release(); domctl_out_unlock_domonly: - if ( d ) + if ( d && d != dom_io ) rcu_unlock_domain(d); if ( copyback && __copy_to_guest(u_domctl, op, 1) ) diff --git a/xen/drivers/passthrough/amd/pci_amd_iommu.c b/xen/drivers/passthrough/amd/pci_amd_iommu.c index 94a25a4d8d..f9aee7907f 100644 --- a/xen/drivers/passthrough/amd/pci_amd_iommu.c +++ b/xen/drivers/passthrough/amd/pci_amd_iommu.c @@ -118,6 +118,10 @@ static void amd_iommu_setup_domain_device( u8 bus = pdev->bus; const struct domain_iommu *hd = dom_iommu(domain); + /* dom_io is used as a sentinel for quarantined devices */ + if ( domain == dom_io ) + return; + BUG_ON( !hd->arch.root_table || !hd->arch.paging_mode || !iommu->dev_table.buffer ); @@ -329,6 +333,10 @@ void amd_iommu_disable_domain_device(struct domain *domain, int req_id; u8 bus = pdev->bus; + /* dom_io is used as a sentinel for quarantined devices */ + if ( domain == dom_io ) + return; + BUG_ON ( iommu->dev_table.buffer == NULL ); req_id = get_dma_requestor_id(iommu->seg, PCI_BDF2(bus, devfn)); dte = iommu->dev_table.buffer + (req_id * IOMMU_DEV_TABLE_ENTRY_SIZE); @@ -416,7 +424,7 @@ static int amd_iommu_assign_device(struct domain *d, u8 devfn, ivrs_mappings[req_id].read_permission); } - return reassign_device(hardware_domain, d, devfn, pdev); + return reassign_device(pdev->domain, d, devfn, pdev); } static void deallocate_next_page_table(struct page_info *pg, int level) diff --git a/xen/drivers/passthrough/device_tree.c b/xen/drivers/passthrough/device_tree.c index 79bf8bf5ed..9f00129c99 100644 --- a/xen/drivers/passthrough/device_tree.c +++ b/xen/drivers/passthrough/device_tree.c @@ -163,6 +163,9 @@ int iommu_do_dt_domctl(struct xen_domctl *domctl, struct domain *d, if ( ret ) break; + if ( d == dom_io ) + return -EINVAL; + ret = iommu_assign_dt_device(d, dev); if ( ret ) @@ -184,6 +187,9 @@ int iommu_do_dt_domctl(struct xen_domctl *domctl, struct domain *d, ret = xsm_deassign_dtdevice(XSM_HOOK, d, dt_node_full_name(dev)); + if ( d == dom_io ) + return -EINVAL; + ret = iommu_deassign_dt_device(d, dev); if ( ret ) diff --git a/xen/drivers/passthrough/iommu.c b/xen/drivers/passthrough/iommu.c index 5e81813942..9ebb8c6bc4 100644 --- a/xen/drivers/passthrough/iommu.c +++ b/xen/drivers/passthrough/iommu.c @@ -208,6 +208,9 @@ void iommu_teardown(struct domain *d) { const struct domain_iommu *hd = dom_iommu(d); + if ( d == dom_io ) + return; + d->need_iommu = 0; hd->platform_ops->teardown(d); tasklet_schedule(&iommu_pt_cleanup_tasklet); @@ -218,6 +221,9 @@ int iommu_construct(struct domain *d) if ( need_iommu(d) > 0 ) return 0; + if ( d == dom_io ) + return 0; + if ( !iommu_use_hap_pt(d) ) { int rc; @@ -393,6 +399,9 @@ int __init iommu_setup(void) printk("I/O virtualisation %sabled\n", iommu_enabled ? "en" : "dis"); if ( iommu_enabled ) { + if ( iommu_domain_init(dom_io) ) + panic("Could not set up quarantine\n"); + printk(" - Dom0 mode: %s\n", iommu_passthrough ? "Passthrough" : iommu_dom0_strict ? "Strict" : "Relaxed"); diff --git a/xen/drivers/passthrough/pci.c b/xen/drivers/passthrough/pci.c index 1c28e3800e..ef3d39c65b 100644 --- a/xen/drivers/passthrough/pci.c +++ b/xen/drivers/passthrough/pci.c @@ -1344,19 +1344,29 @@ int iommu_remove_device(struct pci_dev *pdev) return hd->platform_ops->remove_device(pdev->devfn, pci_to_dev(pdev)); } -/* - * If the device isn't owned by the hardware domain, it means it already - * has been assigned to other domain, or it doesn't exist. - */ static int device_assigned(u16 seg, u8 bus, u8 devfn) { struct pci_dev *pdev; + int rc = 0; pcidevs_lock(); - pdev = pci_get_pdev_by_domain(hardware_domain, seg, bus, devfn); + + pdev = pci_get_pdev(seg, bus, devfn); + + if ( !pdev ) + rc = -ENODEV; + /* + * If the device exists and it is not owned by either the hardware + * domain or dom_io then it must be assigned to a guest, or be + * hidden (owned by dom_xen). + */ + else if ( pdev->domain != hardware_domain && + pdev->domain != dom_io ) + rc = -EBUSY; + pcidevs_unlock(); - return pdev ? 0 : -EBUSY; + return rc; } static int assign_device(struct domain *d, u16 seg, u8 bus, u8 devfn, u32 flag) @@ -1370,7 +1380,7 @@ static int assign_device(struct domain *d, u16 seg, u8 bus, u8 devfn, u32 flag) /* Prevent device assign if mem paging or mem sharing have been * enabled for this domain */ - if ( unlikely(!need_iommu(d) && + if ( unlikely(!need_iommu(d) && d != dom_io && (d->arch.hvm_domain.mem_sharing_enabled || d->vm_event->paging.ring_page || p2m_get_hostp2m(d)->global_logdirty)) ) @@ -1386,12 +1396,20 @@ static int assign_device(struct domain *d, u16 seg, u8 bus, u8 devfn, u32 flag) return rc; } - pdev = pci_get_pdev_by_domain(hardware_domain, seg, bus, devfn); + pdev = pci_get_pdev(seg, bus, devfn); + + rc = -ENODEV; if ( !pdev ) - { - rc = pci_get_pdev(seg, bus, devfn) ? -EBUSY : -ENODEV; goto done; - } + + rc = 0; + if ( d == pdev->domain ) + goto done; + + rc = -EBUSY; + if ( pdev->domain != hardware_domain && + pdev->domain != dom_io ) + goto done; if ( pdev->msix ) msixtbl_init(d); @@ -1414,6 +1432,10 @@ static int assign_device(struct domain *d, u16 seg, u8 bus, u8 devfn, u32 flag) } done: + /* The device is assigned to dom_io so mark it as quarantined */ + if ( !rc && d == dom_io ) + pdev->quarantine = true; + if ( !has_arch_pdevs(d) && need_iommu(d) ) iommu_teardown(d); pcidevs_unlock(); @@ -1426,6 +1448,7 @@ int deassign_device(struct domain *d, u16 seg, u8 bus, u8 devfn) { const struct domain_iommu *hd = dom_iommu(d); struct pci_dev *pdev = NULL; + struct domain *target; int ret = 0; if ( !iommu_enabled || !hd->platform_ops ) @@ -1436,12 +1459,16 @@ int deassign_device(struct domain *d, u16 seg, u8 bus, u8 devfn) if ( !pdev ) return -ENODEV; + /* De-assignment from dom_io should de-quarantine the device */ + target = (pdev->quarantine && pdev->domain != dom_io) ? + dom_io : hardware_domain; + while ( pdev->phantom_stride ) { devfn += pdev->phantom_stride; if ( PCI_SLOT(devfn) != PCI_SLOT(pdev->devfn) ) break; - ret = hd->platform_ops->reassign_device(d, hardware_domain, devfn, + ret = hd->platform_ops->reassign_device(d, target, devfn, pci_to_dev(pdev)); if ( !ret ) continue; @@ -1452,7 +1479,7 @@ int deassign_device(struct domain *d, u16 seg, u8 bus, u8 devfn) } devfn = pdev->devfn; - ret = hd->platform_ops->reassign_device(d, hardware_domain, devfn, + ret = hd->platform_ops->reassign_device(d, target, devfn, pci_to_dev(pdev)); if ( ret ) { @@ -1462,6 +1489,9 @@ int deassign_device(struct domain *d, u16 seg, u8 bus, u8 devfn) return ret; } + if ( pdev->domain == hardware_domain ) + pdev->quarantine = false; + pdev->fault.count = 0; if ( !has_arch_pdevs(d) && need_iommu(d) ) @@ -1646,7 +1676,7 @@ int iommu_do_pci_domctl( ret = hypercall_create_continuation(__HYPERVISOR_domctl, "h", u_domctl); else if ( ret ) - printk(XENLOG_G_ERR "XEN_DOMCTL_assign_device: " + printk(XENLOG_G_ERR "assign %04x:%02x:%02x.%u to dom%d failed (%d)\n", seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn), d->domain_id, ret); diff --git a/xen/drivers/passthrough/vtd/iommu.c b/xen/drivers/passthrough/vtd/iommu.c index 78ad7a71e9..54cb798c2e 100644 --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -1332,6 +1332,10 @@ int domain_context_mapping_one( int agaw, rc, ret; bool_t flush_dev_iotlb; + /* dom_io is used as a sentinel for quarantined devices */ + if ( domain == dom_io ) + return 0; + ASSERT(pcidevs_locked()); spin_lock(&iommu->lock); maddr = bus_to_context_maddr(iommu, bus); @@ -1567,6 +1571,10 @@ int domain_context_unmap_one( int iommu_domid, rc, ret; bool_t flush_dev_iotlb; + /* dom_io is used as a sentinel for quarantined devices */ + if ( domain == dom_io ) + return 0; + ASSERT(pcidevs_locked()); spin_lock(&iommu->lock); @@ -1699,6 +1707,10 @@ static int domain_context_unmap(struct domain *domain, u8 devfn, goto out; } + /* dom_io is used as a sentinel for quarantined devices */ + if ( domain == dom_io ) + goto out; + /* * if no other devices under the same iommu owned by this domain, * clear iommu in iommu_bitmap and clear domain_id in domid_bitmp @@ -2381,6 +2393,15 @@ static int reassign_device_ownership( if ( ret ) return ret; + if ( devfn == pdev->devfn && pdev->domain != dom_io ) + { + list_move(&pdev->domain_list, &dom_io->arch.pdev_list); + pdev->domain = dom_io; + } + + if ( !has_arch_pdevs(source) ) + vmx_pi_hooks_deassign(source); + if ( !has_arch_pdevs(target) ) vmx_pi_hooks_assign(target); @@ -2393,21 +2414,19 @@ static int reassign_device_ownership( return ret; } - if ( devfn == pdev->devfn ) + if ( devfn == pdev->devfn && pdev->domain != target ) { list_move(&pdev->domain_list, &target->arch.pdev_list); pdev->domain = target; } - if ( !has_arch_pdevs(source) ) - vmx_pi_hooks_deassign(source); - return ret; } static int intel_iommu_assign_device( struct domain *d, u8 devfn, struct pci_dev *pdev, u32 flag) { + struct domain *s = pdev->domain; struct acpi_rmrr_unit *rmrr; int ret = 0, i; u16 bdf, seg; @@ -2450,8 +2469,8 @@ static int intel_iommu_assign_device( } } - ret = reassign_device_ownership(hardware_domain, d, devfn, pdev); - if ( ret ) + ret = reassign_device_ownership(s, d, devfn, pdev); + if ( ret || d == dom_io ) return ret; /* Setup rmrr identity mapping */ @@ -2464,11 +2483,20 @@ static int intel_iommu_assign_device( ret = rmrr_identity_mapping(d, 1, rmrr, flag); if ( ret ) { - reassign_device_ownership(d, hardware_domain, devfn, pdev); + int rc; + + rc = reassign_device_ownership(d, s, devfn, pdev); printk(XENLOG_G_ERR VTDPREFIX " cannot map reserved region (%"PRIx64",%"PRIx64"] for Dom%d (%d)\n", rmrr->base_address, rmrr->end_address, d->domain_id, ret); + if ( rc ) + { + printk(XENLOG_ERR VTDPREFIX + " failed to reclaim %04x:%02x:%02x.%u from %pd (%d)\n", + seg, bus, PCI_SLOT(devfn), PCI_FUNC(devfn), d, rc); + domain_crash(d); + } break; } } diff --git a/xen/include/xen/pci.h b/xen/include/xen/pci.h index c97d3b30ae..21b8b3ab64 100644 --- a/xen/include/xen/pci.h +++ b/xen/include/xen/pci.h @@ -68,6 +68,9 @@ struct pci_dev { nodeid_t node; /* NUMA node */ + /* Device to be quarantined, don't automatically re-assign to dom0 */ + bool quarantine; + enum pdev_type { DEV_TYPE_PCI_UNKNOWN, DEV_TYPE_PCIe_ENDPOINT,