Allocate IOMMU_DOMAIN_EXTERNAL domains under Hyper-V vIOMMUs and use HVCALL_ATTACH_DEVICE to assign devices to an MSHV partition.
Resolve the userspace-assigned vDEVICE virt_id during attach and use it as the Hyper-V logical device ID. External domains retain their vIOMMU so attach preparation can pin guest memory before atomically replacing the previous device assignment. Keep paging domains backed by normal Hyper-V device domains. Assisted-by: GPT-5.6 Sol Signed-off-by: Jacob Pan <[email protected]> --- drivers/iommu/hyperv/hv-iommu-iommufd.c | 44 ++++++++++++ drivers/iommu/hyperv/hv-iommu-root.c | 89 +++++++++++++++++++++++++ drivers/iommu/hyperv/hv-iommu.h | 5 ++ 3 files changed, 138 insertions(+) diff --git a/drivers/iommu/hyperv/hv-iommu-iommufd.c b/drivers/iommu/hyperv/hv-iommu-iommufd.c index 486a85c0d1e8..e595f53ddc66 100644 --- a/drivers/iommu/hyperv/hv-iommu-iommufd.c +++ b/drivers/iommu/hyperv/hv-iommu-iommufd.c @@ -33,10 +33,54 @@ static void hv_iommu_viommu_destroy(struct iommufd_viommu *viommu) fput(hv_viommu->vm_file); } +static struct iommu_domain * +hv_iommu_alloc_domain_external(struct iommufd_viommu *viommu, u32 flags, + const struct iommu_user_data *user_data) +{ + struct hv_iommu_viommu *hv_viommu = to_hv_iommu_viommu(viommu); + struct iommu_hwpt_external external = {}; + struct hv_domain *hvdom; + int rc; + + if (viommu->type != IOMMU_VIOMMU_TYPE_HYPERVISOR) + return ERR_PTR(-EOPNOTSUPP); + if (flags) + return ERR_PTR(-EOPNOTSUPP); + if (!user_data || user_data->type != IOMMU_HWPT_DATA_EXTERNAL) + return ERR_PTR(-EOPNOTSUPP); + + rc = iommu_copy_struct_from_user(&external, user_data, + IOMMU_HWPT_DATA_EXTERNAL, flags); + if (rc) + return ERR_PTR(rc); + if (external.flags || external.__reserved) + return ERR_PTR(-EOPNOTSUPP); + + hvdom = kzalloc_obj(*hvdom, GFP_KERNEL_ACCOUNT); + if (!hvdom) + return ERR_PTR(-ENOMEM); + + hvdom->iommu_dom.type = IOMMU_DOMAIN_EXTERNAL; + hvdom->iommu_dom.ops = &hv_iommu_external_domain_ops; + hvdom->iommu_dom.pgsize_bitmap = HV_IOMMU_PGSIZES; + hvdom->partid = hv_viommu->partid; + hvdom->viommu = viommu; + + return &hvdom->iommu_dom; +} + static const struct iommufd_viommu_ops hv_iommu_hypervisor_viommu_ops = { .destroy = hv_iommu_viommu_destroy, + .alloc_domain_external = hv_iommu_alloc_domain_external, }; +int hv_iommufd_prepare_attach(struct iommufd_viommu *viommu) +{ + struct hv_iommu_viommu *hv_viommu = to_hv_iommu_viommu(viommu); + + return mshv_partition_file_prepare_attach(hv_viommu->vm_file); +} + size_t hv_iommufd_get_viommu_size(struct device *dev, enum iommu_viommu_type viommu_type) { diff --git a/drivers/iommu/hyperv/hv-iommu-root.c b/drivers/iommu/hyperv/hv-iommu-root.c index d6424b9ea0cd..b87152489630 100644 --- a/drivers/iommu/hyperv/hv-iommu-root.c +++ b/drivers/iommu/hyperv/hv-iommu-root.c @@ -7,6 +7,7 @@ #include <linux/dma-map-ops.h> #include <linux/interval_tree.h> #include <linux/hyperv.h> +#include <linux/iommufd.h> #include "hv-iommu.h" #include <asm/iommu.h> #include <asm/mshyperv.h> @@ -59,6 +60,13 @@ static bool hv_special_domain(struct hv_domain *hvdom) return hvdom == &hv_def_identity_dom || hvdom == &hv_def_blocked_dom; } +static u64 hv_iommu_host_device_id(struct pci_dev *pdev) +{ + u64 devid = hv_pci_vmbus_device_id(pdev); + + return devid ? devid : hv_build_devid_type_pci(pdev); +} + static atomic_t hv_unique_id; /* unique numeric id for a new domain */ static bool hv_iommu_capable(struct device *dev, enum iommu_cap cap) @@ -205,6 +213,11 @@ static void hv_iommu_domain_free(struct iommu_domain *immdom) if (hv_special_domain(hvdom)) return; + if (immdom->type == IOMMU_DOMAIN_EXTERNAL) { + kfree(hvdom); + return; + } + /* Cleanup any remaining. 0 for size results in ULONG_MAX as the last */ hv_iommu_del_tree_mappings(hvdom, 0, 0); @@ -269,6 +282,10 @@ static int hv_iommu_attach_dev(struct iommu_domain *immdom, struct device *dev, pdev = to_pci_dev(dev); + /* + * HVCALL_ATTACH_DEVICE_DOMAIN atomically replaces any existing + * assignment, leaving @old intact if the new attachment fails. + */ rc = hv_iommu_att_dev2dom(hvdom_new, pdev); if (rc) WARN(1, "Failed to attach pdev:%s\n", pci_name(pdev)); @@ -276,6 +293,73 @@ static int hv_iommu_attach_dev(struct iommu_domain *immdom, struct device *dev, return rc; } +static int hv_iommu_external_attach_device(struct pci_dev *pdev, u64 partid, + unsigned long vdev_id) +{ + struct hv_input_attach_device *input; + union hv_device_id host_devid; + unsigned long flags; + u64 status; + int rc; + + if (partid == HV_PARTITION_ID_INVALID) + return -EINVAL; + + host_devid.as_uint64 = hv_iommu_host_device_id(pdev); + + do { + local_irq_save(flags); + input = *this_cpu_ptr(hyperv_pcpu_input_arg); + memset(input, 0, sizeof(*input)); + + input->partition_id = partid; + input->device_id = host_devid; + input->attdev_flags.logical_id = 1; + input->logical_devid = vdev_id; + + status = hv_do_hypercall(HVCALL_ATTACH_DEVICE, input, NULL); + local_irq_restore(flags); + + if (hv_result(status) == HV_STATUS_INSUFFICIENT_MEMORY) { + rc = hv_call_deposit_pages(NUMA_NO_NODE, partid, 1); + if (rc) + return rc; + } + } while (hv_result(status) == HV_STATUS_INSUFFICIENT_MEMORY); + + if (!hv_result_success(status)) + hv_status_err(status, "\n"); + + return hv_result_to_errno(status); +} + +static int hv_iommu_external_attach_dev(struct iommu_domain *immdom, + struct device *dev, + struct iommu_domain *old) +{ + struct hv_domain *hvdom_new = to_hv_domain(immdom); + unsigned long vdev_id; + int rc; + + if (!dev_is_pci(dev)) + return -EINVAL; + + rc = iommufd_viommu_get_vdev_id(hvdom_new->viommu, dev, &vdev_id); + if (rc) + return rc; + + rc = hv_iommufd_prepare_attach(hvdom_new->viommu); + if (rc) + return rc; + + /* + * HVCALL_ATTACH_DEVICE atomically replaces any existing assignment, + * leaving @old intact if the new attachment fails. + */ + return hv_iommu_external_attach_device(to_pci_dev(dev), + hvdom_new->partid, vdev_id); +} + static u64 hv_iommu_unmap_batch(u32 domid_num, ulong iova, u16 count) { ulong flags; @@ -527,6 +611,11 @@ static struct iommu_domain_ops hv_paging_domain_ops = { .free = hv_iommu_domain_free, }; +const struct iommu_domain_ops hv_iommu_external_domain_ops = { + .attach_dev = hv_iommu_external_attach_dev, + .free = hv_iommu_domain_free, +}; + static struct iommu_ops hv_iommu_ops = { .capable = hv_iommu_capable, .domain_alloc_paging = hv_iommu_domain_alloc_paging, diff --git a/drivers/iommu/hyperv/hv-iommu.h b/drivers/iommu/hyperv/hv-iommu.h index 6fdbe56a4b1d..647fead81533 100644 --- a/drivers/iommu/hyperv/hv-iommu.h +++ b/drivers/iommu/hyperv/hv-iommu.h @@ -20,16 +20,21 @@ struct iommu_user_data; struct hv_domain { struct iommu_domain iommu_dom; u32 domid_num; /* as opposed to domain_id.type */ + u64 partid; /* partition id for external attach */ + struct iommufd_viommu *viommu; spinlock_t mappings_lock; /* protects mappings_tree */ struct rb_root_cached mappings_tree; /* iova to pa lookup tree */ }; #define to_hv_domain(d) container_of(d, struct hv_domain, iommu_dom) +extern const struct iommu_domain_ops hv_iommu_external_domain_ops; + size_t hv_iommufd_get_viommu_size(struct device *dev, enum iommu_viommu_type viommu_type); int hv_iommufd_viommu_init(struct iommufd_viommu *viommu, struct iommu_domain *parent_domain, const struct iommu_user_data *user_data); +int hv_iommufd_prepare_attach(struct iommufd_viommu *viommu); #endif /* __HYPERV_IOMMU_H */ -- 2.43.0

