mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH 0/9] iommu/iommufd: Add hypervisor external attach
@ 2026-09-25 19:07 Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 1/9] iommu: Introduce external attach domain type Jacob Pan
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Jacob Pan @ 2026-09-25 19:07 UTC (permalink / raw)
  To: iommu
  Cc: Jason Gunthorpe, Nicolin Chen, Kevin Tian, Will Deacon,
	Robin Murphy, Wei Liu, K . Y . Srinivasan, Haiyang Zhang,
	Dexuan Cui, Long Li, linux-hyperv, Joerg Roedel,
	Suravee Suthikulpanit, Vasant Hegde, Arnd Bergmann, linux-arch,
	linux-kernel, Aneesh Kumar K . V, Mukesh Rathor, John Starks,
	Souradeep Chakrabarti, Yu Zhang, Easwar Hariharan,
	Alex Williamson

Hi,

This RFC supports guest VM device assignment from a Linux host using the
Microsoft Hypervisor (MSHV). The goal is that a common set of flows
and infrastructure can be applied for Xen and other type 1 hypervisors.

Background:

The IOMMUFD/VFIO stack used for device assignment has largely evolved around
the KVM/QEMU model, at least as the first enabling target. In that model, the
Linux host owns the host-side physical IOMMU programming as well as the
physical PCI devices.

Architecturally, MSHV and Xen differ from this KVM/QEMU ownership model.
At a high level:

  +------------------+
  | Guest VM         |
  | virtual IOMMU    |
  +--------+---------+
           | vIOMMU ops
           v
  +------------------+    external attach     +------------------+
  | MSHV             |<-----------------------| Linux host       |
  +--------+---------+                        +--------+---------+
           | programs                                  | owns
           v                                           v
  +------------------+                        +------------------+
  | HW IOMMU         |<---------------------->| PCI dev          |
  +------------------+          DMA           +------------------+

For MSHV, the specific ownership model is as follows:
  1. The Linux host OS owns the physical device and exposes it through
     VFIO/IOMMUFD, but it does not directly own any of the IOMMU hardware.
  2. The device node (/dev/mshv) owns the VM partition file used by userspace.
  3. MSHV owns most of the physical IOMMU programming, including stage-2 I/O
     page tables.
  4. The Linux MSHV root pvIOMMU driver bridges IOMMUFD/VFIO UAPIs to hardware
     IOMMU programming through MSHV hypercalls.
  5. Guest virtual IOMMU emulation is done in MSHV rather than the userspace
     VMM.
  6. Stage-2 IOPT may be shared between CPU and IOMMU. It is managed by
     MSHV and abstracted from the Linux host, including TLB synchronization.

These structural differences from the KVM/QEMU model create several needs for
IOMMUFD support:
  1. Move a device to a guest whose I/O page table is externally managed. This
     patchset refers to that operation as external attach. Unlike conventional
     IOMMU vendor drivers, where translation is managed within the driver
     through paging domains, external attach means that translation is managed
     outside the driver, such as by the hypervisor.
  2. Identify the guest-visible device across VMM, host pvIOMMU driver, and
     hypervisor namespaces.
  3. Identify the guest partition in the host pvIOMMU driver for device attach
     hypercalls.
  4. Tie the device-attachment lifetime to the hypervisor partition.
  5. Coordinate guest-memory pinning and mapping for DMA between MSHV and the
     external IOMMU domain attachment.

External attach has a few caveats compared with KVM/QEMU, where nesting is
typically handled by the host IOMMU driver in one transaction after the
userspace VMM uses IOMMUFD to form nested domains:

  1. MSHV creates a temporal split between host and guest device-domain
     attach. The host first attaches S2 through a hypercall; later, if virtual
     IOMMU is present, the guest
     allocates S1 and establishes nesting with MSHV directly through a pvIOMMU
     or emulated vIOMMU.
  2. Guest-memory pinning before mapping is covered by the MSHV/userspace VMM
     contract, not by the VFIO/IOMMUFD pin-and-map DMA flow.
  3. Device capabilities such as ATS and PASID need a contract among MSHV, the
     userspace VMM, and the host pvIOMMU driver. At the time of host external
     attach, only S2 is attached, so the guest's later ATS/PASID usage is not
     yet known. For example, whether to expose and enable PASID in the device
     could depend on whether the userspace VMM creates a guest virtual IOMMU.

This RFC proposes an IOMMUFD external attach model for those cases. The normal
Linux-managed paging or nested domain is not the object that ultimately owns
the translation context. Instead, userspace identifies a VM/domain object,
binds a physical device to a VM-visible device identity, and asks the host
pvIOMMU driver to attach the device to an externally managed address space.

The series makes that relationship explicit in IOMMUFD. The model is anchored
around a hypervisor vIOMMU object that represents the per-VM partition
abstraction. It introduces:

  1. IOMMU_DOMAIN_EXTERNAL for externally managed translation domains.
  2. IOMMU_VIOMMU_TYPE_HYPERVISOR for the file-backed VM partition.
  3. IOMMU_HWPT_EXTERNAL for user-visible external type IOMMU domains.

It reuses the IOMMUFD vDEVICE object to carry the guest-VM-scoped logical
device ID visible to the hypervisor.

Roughly, the VMM flow is as follows:
  1. iommufd = open("/dev/iommu")
  2. vfio_fd = open("/dev/vfio/devices/vfioN")
  3. dev_id = bind_vfio_device(vfio_fd, iommufd)
  4. create_mshv_partition(&mshv_fd, &vm_fd)
     // Creates the guest vIOMMU and configures guest-memory pinning policy.
  5. alloc_hypervisor_viommu(iommufd, &viommu_id, dev_id, vm_fd)
     // Uses the hypervisor vIOMMU type.
  6. alloc_vdevice(iommufd, viommu_id, dev_id, vm_device_id, &vdevice_id)
  7. alloc_external_hwpt(iommufd, dev_id, viommu_id, &external_hwpt_id)
  8. attach_external_hwpt(vfio_fd, external_hwpt_id)

Step 4 may be completed before steps 1-3, but it must precede step 5 because
the partition file is required to allocate the hypervisor vIOMMU.

The attach_external_hwpt() operation enters the MSHV root pvIOMMU driver's
external-HWPT device_attach path:
  -> resolve partition ID from hypervisor vIOMMU
  -> resolve logical device ID from vDEVICE
  -> invoke the partition prepare_attach callback to force pinning if policy
     requires
  -> MSHV attach-device hypercall

Object lifetime:
  - The hypervisor vIOMMU takes a reference on the MSHV partition file and
    holds it until the vIOMMU is destroyed. Closing the userspace copy of the
    partition fd therefore does not destroy the partition while the vIOMMU
    still exists.
  - Each vDEVICE and external HWPT holds a reference on its parent vIOMMU.
    The vIOMMU cannot be destroyed until these child objects are removed.
  - An attached device holds a reference on the external HWPT, so userspace
    cannot destroy the HWPT while the device is attached.
  - Closing the IOMMUFD fd destroys the object graph from the leaves upward.
    Once the child objects are gone, destroying the vIOMMU drops its partition
    file reference and allows the MSHV partition to be released.

Thus, the external-attach lifetime is anchored directly by IOMMUFD object
references and the partition file. It does not require a separate
VFIO-hypervisor bridge, such as kvm-vfio: the MSHV root IOMMU driver's detach
operation directly tears down the hypervisor-visible external-attach state.

The root pvIOMMU driver changes in this RFC address the following concerns
received from the older version[1]:
  1. The target partition comes from the fd-backed hypervisor vIOMMU created
     by userspace, not from the thread issuing the attach.
  2. External attach is represented explicitly, avoiding paging-domain hacks.
  3. Guest memory is pinned exactly once. In the older model, the same memory
     could be pinned once by /dev/mshv and again by the VFIO DMA map ioctl.

This RFC builds on the MSHV PCI passthrough/root pvIOMMU base work [2]. The
mergeable MSHV driver changes should be added on top of a future version of
that base root pvIOMMU driver.

Feedback would be especially useful on:

  1. Whether IOMMU_DOMAIN_EXTERNAL is the right IOMMU core representation
     for an externally managed translation (Xen, perhaps TSM [4]).
  2. Whether IOMMU_VIOMMU_TYPE_HYPERVISOR/VDEVICE can be common to MSHV,
     Xen, and similar type-1 hypervisor flows, and whether it can cover the
     semantics in CoCo TDISP that would otherwise require tsm_bind() [3].
  3. Whether a file-backed VM/domain reference is the right way to anchor the
     external attach lifetime.
  4. Whether vDEVICE virt_id is the right generic source for the
     hypervisor-assigned logical device ID.

At LPC 2026, I will be presenting this work and have open discussions.
https://lpc.events/event/20/contributions/2520/

Future work:
1. ATS/PASID policy integration with the root pvIOMMU driver.
ATS/PASID policy is intentionally left as a separate contract issue. I do not
think the root pvIOMMU driver should blindly enable or disable PASID/ATS on a
device without knowing the guest configuration or receiving acknowledgment
from MSHV, which owns the device-TLB flush for S2 changes.

2. Live update support
The current LUO proposal preserves DMA mappings through IOMMUFD-owned state.
External attach requires a different preservation model because the S2 IOPT
is managed by MSHV rather than IOMMUFD.

3. vDEVICE lifetime during external attachment
An attached external HWPT does not currently retain the vDEVICE used by MSHV
to resolve the guest logical device ID. We can either let the core retain
the vDEVICE identity for external attach, or provide an opt-in mechanism
to the vendor IOMMU drivers as needed. I am not sure if all type 1 hypervisors
require a vDEVICE identity.

4. Revokable dma-buf support for P2PDMA
Now that MSHV, instead of IOMMUFD, manages the S2 IOPT, we need to reconsider
how revokable dma-bufs are handled. Perhaps MSHV can be the importer of
revokable dma-bufs for P2PDMA and unmap them when the dma-buf is revoked.

[1] https://lore.kernel.org/linux-iommu/20260731223427.2554388-1-mrathor@linux.microsoft.com/
[2] https://lore.kernel.org/all/20260902235609.GG2890729@ziepe.ca/
[3] https://lore.kernel.org/all/20260921225028.4007330-1-mrathor@linux.microsoft.com/T/#t
[4] https://lore.kernel.org/linux-iommu/yq5a33uxka2g.fsf@kernel.org/

Patch groups:

IOMMUFD core and selftest:

  1. iommu: Introduce external attach domain type
  2. iommufd: Introduce hypervisor vIOMMU type
  3. iommufd: Add external HWPT support
  4. iommufd/selftest: Add hypervisor external attach backend

MSHV:

  5. mshv: Add partition file identity helper
  6. mshv: Add prepare callback for external device attach

MSHV root pIOMMU driver:

  7. iommu/hyperv: Split root IOMMU declarations
  8. iommu/hyperv: Add fd-backed vIOMMU support
  9. iommu/hyperv: Add IOMMUFD external domains


Thanks,
Jacob

Jacob Pan (9):
  iommu: Introduce external attach domain type
  iommufd: Introduce hypervisor vIOMMU type
  iommufd: Add external HWPT support
  iommufd/selftest: Add hypervisor external attach backend
  mshv: Add partition file identity helper
  mshv: Add prepare callback for external device attach
  iommu/hyperv: Split root IOMMU declarations
  iommu/hyperv: Add fd-backed vIOMMU support
  iommu/hyperv: Add IOMMUFD external domains

 drivers/hv/hv_common.c                  |  91 ++++++++++++++++
 drivers/hv/mshv_regions.c               |   6 ++
 drivers/hv/mshv_root.h                  |   1 +
 drivers/hv/mshv_root_main.c             |  71 ++++++++++++-
 drivers/iommu/amd/iommufd.c             |  10 +-
 drivers/iommu/hyperv/Makefile           |   2 +-
 drivers/iommu/hyperv/hv-iommu-iommufd.c | 135 ++++++++++++++++++++++++
 drivers/iommu/hyperv/hv-iommu-root.c    | 102 ++++++++++++++++--
 drivers/iommu/hyperv/hv-iommu.h         |  40 +++++++
 drivers/iommu/iommu.c                   |   2 +
 drivers/iommu/iommufd/device.c          |   9 +-
 drivers/iommu/iommufd/hw_pagetable.c    |  88 +++++++++++++--
 drivers/iommu/iommufd/iommufd_private.h |   7 ++
 drivers/iommu/iommufd/main.c            |   4 +
 drivers/iommu/iommufd/selftest.c        | 120 +++++++++++++++++++--
 drivers/iommu/iommufd/viommu.c          |  37 ++++---
 include/asm-generic/mshyperv.h          |  42 ++++++++
 include/linux/iommu.h                   |  17 ++-
 include/linux/iommufd.h                 |  11 ++
 include/uapi/linux/iommufd.h            |  42 +++++++-
 20 files changed, 790 insertions(+), 47 deletions(-)
 create mode 100644 drivers/iommu/hyperv/hv-iommu-iommufd.c
 create mode 100644 drivers/iommu/hyperv/hv-iommu.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH RFC 1/9] iommu: Introduce external attach domain type
  2026-09-25 19:07 [RFC PATCH 0/9] iommu/iommufd: Add hypervisor external attach Jacob Pan
@ 2026-09-25 19:07 ` Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 2/9] iommufd: Introduce hypervisor vIOMMU type Jacob Pan
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jacob Pan @ 2026-09-25 19:07 UTC (permalink / raw)
  To: iommu
  Cc: Jason Gunthorpe, Nicolin Chen, Kevin Tian, Will Deacon,
	Robin Murphy, Wei Liu, K . Y . Srinivasan, Haiyang Zhang,
	Dexuan Cui, Long Li, linux-hyperv, Joerg Roedel,
	Suravee Suthikulpanit, Vasant Hegde, Arnd Bergmann, linux-arch,
	linux-kernel, Aneesh Kumar K . V, Mukesh Rathor, John Starks,
	Souradeep Chakrabarti, Yu Zhang, Easwar Hariharan,
	Alex Williamson

Add IOMMU_DOMAIN_EXTERNAL for externally managed domains that move or
attach devices to another VM address space provisioned by an external
owner, such as a type 1 hypervisor.

This is not a paging domain and is not sysfs-selectable as a default
domain type. It requires external owner context, such as a vIOMMU.

PV IOMMU drivers are expected to use it when moving a device to a guest
VM with an already-created S2 IOPT.

Assisted-by: GPT-5.6 Sol
Signed-off-by: Jacob Pan <jacob.pan@linux.microsoft.com>
---
 drivers/iommu/iommu.c | 2 ++
 include/linux/iommu.h | 9 +++++++++
 2 files changed, 11 insertions(+)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index cd1bca7ede9a..d0bd3ef00379 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -220,6 +220,8 @@ static const char *iommu_domain_type_str(unsigned int t)
 		return "Translated";
 	case IOMMU_DOMAIN_PLATFORM:
 		return "Platform";
+	case IOMMU_DOMAIN_EXTERNAL:
+		return "External";
 	default:
 		return "Unknown";
 	}
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index ac43b8b93f14..ede77b90f0fd 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -188,6 +188,7 @@ enum iommu_domain_cookie_type {
 
 #define __IOMMU_DOMAIN_NESTED	(1U << 6)  /* User-managed address space nested
 					      on a stage-2 translation        */
+#define __IOMMU_DOMAIN_EXTERNAL	(1U << 7)  /* Externally managed translation */
 
 #define IOMMU_DOMAIN_ALLOC_FLAGS ~__IOMMU_DOMAIN_DMA_FQ
 /*
@@ -207,6 +208,13 @@ enum iommu_domain_cookie_type {
  *				  represented by mm_struct's.
  *	IOMMU_DOMAIN_PLATFORM	- Legacy domain for drivers that do their own
  *				  dma_api stuff. Do not use in new drivers.
+ *	IOMMU_DOMAIN_EXTERNAL	- Externally managed domain for moving/attaching
+ *				  devices to another VM address space
+ *				  provisioned by the external owner, such as
+ *				  a type 1 hypervisor. This is not a
+ *				  sysfs-selectable default domain type; it
+ *				  requires external owner context such as a
+ *				  vIOMMU.
  */
 #define IOMMU_DOMAIN_BLOCKED	(0U)
 #define IOMMU_DOMAIN_IDENTITY	(__IOMMU_DOMAIN_PT)
@@ -219,6 +227,7 @@ enum iommu_domain_cookie_type {
 #define IOMMU_DOMAIN_SVA	(__IOMMU_DOMAIN_SVA)
 #define IOMMU_DOMAIN_PLATFORM	(__IOMMU_DOMAIN_PLATFORM)
 #define IOMMU_DOMAIN_NESTED	(__IOMMU_DOMAIN_NESTED)
+#define IOMMU_DOMAIN_EXTERNAL	(__IOMMU_DOMAIN_EXTERNAL)
 
 struct iommu_domain {
 	unsigned type;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH RFC 2/9] iommufd: Introduce hypervisor vIOMMU type
  2026-09-25 19:07 [RFC PATCH 0/9] iommu/iommufd: Add hypervisor external attach Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 1/9] iommu: Introduce external attach domain type Jacob Pan
@ 2026-09-25 19:07 ` Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 3/9] iommufd: Add external HWPT support Jacob Pan
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jacob Pan @ 2026-09-25 19:07 UTC (permalink / raw)
  To: iommu
  Cc: Jason Gunthorpe, Nicolin Chen, Kevin Tian, Will Deacon,
	Robin Murphy, Wei Liu, K . Y . Srinivasan, Haiyang Zhang,
	Dexuan Cui, Long Li, linux-hyperv, Joerg Roedel,
	Suravee Suthikulpanit, Vasant Hegde, Arnd Bergmann, linux-arch,
	linux-kernel, Aneesh Kumar K . V, Mukesh Rathor, John Starks,
	Souradeep Chakrabarti, Yu Zhang, Easwar Hariharan,
	Alex Williamson

Add IOMMU_VIOMMU_TYPE_HYPERVISOR with a file-based VM partition
reference. This represents a hypervisor-backed per-VM IOMMU object used
to bind and organize vDEVICEs for a guest VM partition, with or without
a guest vIOMMU.

This type has no nesting-parent HWPT. Teach the vIOMMU core to pass a
NULL parent domain to the driver callback.

Signed-off-by: Jacob Pan <jacob.pan@linux.microsoft.com>
Assisted-by: GPT-5.6 Sol
---
 drivers/iommu/amd/iommufd.c    | 10 ++++++++-
 drivers/iommu/iommufd/viommu.c | 37 +++++++++++++++++++++++-----------
 include/linux/iommu.h          |  8 +++++---
 include/uapi/linux/iommufd.h   | 25 ++++++++++++++++++++++-
 4 files changed, 63 insertions(+), 17 deletions(-)

diff --git a/drivers/iommu/amd/iommufd.c b/drivers/iommu/amd/iommufd.c
index 52300b867c1f..8fcd5228c547 100644
--- a/drivers/iommu/amd/iommufd.c
+++ b/drivers/iommu/amd/iommufd.c
@@ -34,6 +34,10 @@ void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_ty
 
 size_t amd_iommufd_get_viommu_size(struct device *dev, enum iommu_viommu_type viommu_type)
 {
+	/* AMD vIOMMUs require a nesting parent; hypervisor vIOMMUs are parentless. */
+	if (viommu_type == IOMMU_VIOMMU_TYPE_HYPERVISOR)
+		return 0;
+
 	return VIOMMU_STRUCT_SIZE(struct amd_iommu_viommu, core);
 }
 
@@ -41,9 +45,13 @@ int amd_iommufd_viommu_init(struct iommufd_viommu *viommu, struct iommu_domain *
 			    const struct iommu_user_data *user_data)
 {
 	unsigned long flags;
-	struct protection_domain *pdom = to_pdomain(parent);
+	struct protection_domain *pdom;
 	struct amd_iommu_viommu *aviommu = container_of(viommu, struct amd_iommu_viommu, core);
 
+	if (!parent)
+		return -EOPNOTSUPP;
+
+	pdom = to_pdomain(parent);
 	xa_init_flags(&aviommu->gdomid_array, XA_FLAGS_ALLOC1);
 	aviommu->parent = pdom;
 
diff --git a/drivers/iommu/iommufd/viommu.c b/drivers/iommu/iommufd/viommu.c
index f7951057a1e5..73a06c1a2ada 100644
--- a/drivers/iommu/iommufd/viommu.c
+++ b/drivers/iommu/iommufd/viommu.c
@@ -10,7 +10,8 @@ void iommufd_viommu_destroy(struct iommufd_object *obj)
 
 	if (viommu->ops && viommu->ops->destroy)
 		viommu->ops->destroy(viommu);
-	refcount_dec(&viommu->hwpt->common.obj.users);
+	if (viommu->hwpt)
+		refcount_dec(&viommu->hwpt->common.obj.users);
 	xa_destroy(&viommu->vdevs);
 }
 
@@ -63,15 +64,19 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
 		goto out_put_idev;
 	}
 
-	hwpt_paging = iommufd_get_hwpt_paging(ucmd, cmd->hwpt_id);
-	if (IS_ERR(hwpt_paging)) {
-		rc = PTR_ERR(hwpt_paging);
-		goto out_put_idev;
-	}
+	if (cmd->type == IOMMU_VIOMMU_TYPE_HYPERVISOR) {
+		hwpt_paging = NULL;
+	} else {
+		hwpt_paging = iommufd_get_hwpt_paging(ucmd, cmd->hwpt_id);
+		if (IS_ERR(hwpt_paging)) {
+			rc = PTR_ERR(hwpt_paging);
+			goto out_put_idev;
+		}
 
-	if (!hwpt_paging->nest_parent) {
-		rc = -EINVAL;
-		goto out_put_hwpt;
+		if (!hwpt_paging->nest_parent) {
+			rc = -EINVAL;
+			goto out_put_hwpt;
+		}
 	}
 
 	viommu = (struct iommufd_viommu *)_iommufd_object_alloc_ucmd(
@@ -85,7 +90,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
 	viommu->type = cmd->type;
 	viommu->ictx = ucmd->ictx;
 	viommu->hwpt = hwpt_paging;
-	refcount_inc(&viommu->hwpt->common.obj.users);
+	if (viommu->hwpt)
+		refcount_inc(&viommu->hwpt->common.obj.users);
 	INIT_LIST_HEAD(&viommu->veventqs);
 	init_rwsem(&viommu->veventqs_rwsem);
 	/*
@@ -95,7 +101,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
 	 */
 	viommu->iommu_dev = iommu_dev;
 
-	rc = ops->viommu_init(viommu, hwpt_paging->common.domain,
+	rc = ops->viommu_init(viommu,
+			      hwpt_paging ? hwpt_paging->common.domain : NULL,
 			      user_data.len ? &user_data : NULL);
 	if (rc)
 		goto out_put_hwpt;
@@ -110,7 +117,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
 	rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
 
 out_put_hwpt:
-	iommufd_put_object(ucmd->ictx, &hwpt_paging->common.obj);
+	if (hwpt_paging)
+		iommufd_put_object(ucmd->ictx, &hwpt_paging->common.obj);
 out_put_idev:
 	iommufd_put_object(ucmd->ictx, &idev->obj);
 	return rc;
@@ -394,6 +402,11 @@ int iommufd_hw_queue_alloc_ioctl(struct iommufd_ucmd *ucmd)
 	if (IS_ERR(viommu))
 		return PTR_ERR(viommu);
 
+	if (!viommu->hwpt) {
+		rc = -EOPNOTSUPP;
+		goto out_put_viommu;
+	}
+
 	if (!viommu->ops || !viommu->ops->get_hw_queue_size ||
 	    !viommu->ops->hw_queue_init_phys) {
 		rc = -EOPNOTSUPP;
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index ede77b90f0fd..95211e95ea5d 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -685,9 +685,11 @@ __iommu_copy_struct_to_user(const struct iommu_user_data *dst_data,
  *                   driver-level vIOMMU structure related to the core one
  * @viommu_init: Init the driver-level struct of an iommufd_viommu on a physical
  *               IOMMU instance @viommu->iommu_dev, as the set of virtualization
- *               resources shared/passed to user space IOMMU instance. Associate
- *               it with a nesting @parent_domain. It is required for driver to
- *               set @viommu->ops pointing to its own viommu_ops
+ *               resources shared/passed to user space IOMMU instance.
+ *               @parent_domain may be NULL for a parentless vIOMMU type; a
+ *               driver advertising such a type through @get_viommu_size must
+ *               accept a NULL parent. It is required for driver to set
+ *               @viommu->ops pointing to its own viommu_ops
  * @owner: Driver module providing these ops
  * @identity_domain: An always available, always attachable identity
  *                   translation.
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 206fa667c782..bffa25c5267e 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -1095,6 +1095,11 @@ struct iommu_fault_alloc {
  * @IOMMU_VIOMMU_TYPE_ARM_SMMUV3: ARM SMMUv3 driver specific type
  * @IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV: NVIDIA Tegra241 CMDQV (extension for ARM
  *                                    SMMUv3) enabled ARM SMMUv3 type
+ * @IOMMU_VIOMMU_TYPE_HYPERVISOR: Hypervisor-backed per-VM IOMMU object
+ *                                tracks a file-based VM partition
+ *                                reference; also used to bind and organize
+ *                                vDEVICEs for a guest VM partition, with or
+ *                                without a guest vIOMMU.
  */
 enum iommu_viommu_type {
 	IOMMU_VIOMMU_TYPE_DEFAULT = 0,
@@ -1105,6 +1110,7 @@ enum iommu_viommu_type {
 	 *   VMM must wire the HYP_OWN bit to 0 in guest VINTF_CONFIG register
 	 */
 	IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV = 2,
+	IOMMU_VIOMMU_TYPE_HYPERVISOR = 3,
 };
 
 /**
@@ -1123,13 +1129,29 @@ struct iommu_viommu_tegra241_cmdqv {
 	__aligned_u64 out_vintf_mmap_length;
 };
 
+/**
+ * struct iommu_viommu_hypervisor - Hypervisor-backed virtual IOMMU
+ *                            (IOMMU_VIOMMU_TYPE_HYPERVISOR)
+ * @vm_fd: Hypervisor VM/partition file descriptor
+ * @flags: Must be 0 for now. Future flags may indicate the presence of a guest
+ *         virtual IOMMU, allowing hypervisor pvIOMMU drivers to enable
+ *         PASID/ATS and prepare for nested translation as appropriate.
+ * @__reserved: Must be 0
+ */
+struct iommu_viommu_hypervisor {
+	__s32 vm_fd;
+	__u32 flags;
+	__aligned_u64 __reserved;
+};
+
 /**
  * struct iommu_viommu_alloc - ioctl(IOMMU_VIOMMU_ALLOC)
  * @size: sizeof(struct iommu_viommu_alloc)
  * @flags: Must be 0
  * @type: Type of the virtual IOMMU. Must be defined in enum iommu_viommu_type
  * @dev_id: The device's physical IOMMU will be used to back the virtual IOMMU
- * @hwpt_id: ID of a nesting parent HWPT to associate to
+ * @hwpt_id: ID of a nesting parent HWPT to associate to. This field is
+ *           ignored if the vIOMMU type does not use a nesting parent
  * @out_viommu_id: Output virtual IOMMU ID for the allocated object
  * @data_len: Length of the type specific data
  * @__reserved: Must be 0
@@ -1146,6 +1168,7 @@ struct iommu_viommu_tegra241_cmdqv {
  * - Delivery of paravirtualized invalidation
  * - Direct assigned invalidation queues
  * - Direct assigned interrupts
+ * - Hypervisor controlled translation, e.g. for Type-1 Bare-metal hypervisors
  */
 struct iommu_viommu_alloc {
 	__u32 size;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH RFC 3/9] iommufd: Add external HWPT support
  2026-09-25 19:07 [RFC PATCH 0/9] iommu/iommufd: Add hypervisor external attach Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 1/9] iommu: Introduce external attach domain type Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 2/9] iommufd: Introduce hypervisor vIOMMU type Jacob Pan
@ 2026-09-25 19:07 ` Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 4/9] iommufd/selftest: Add hypervisor external attach backend Jacob Pan
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jacob Pan @ 2026-09-25 19:07 UTC (permalink / raw)
  To: iommu
  Cc: Jason Gunthorpe, Nicolin Chen, Kevin Tian, Will Deacon,
	Robin Murphy, Wei Liu, K . Y . Srinivasan, Haiyang Zhang,
	Dexuan Cui, Long Li, linux-hyperv, Joerg Roedel,
	Suravee Suthikulpanit, Vasant Hegde, Arnd Bergmann, linux-arch,
	linux-kernel, Aneesh Kumar K . V, Mukesh Rathor, John Starks,
	Souradeep Chakrabarti, Yu Zhang, Easwar Hariharan,
	Alex Williamson

Add IOMMU_HWPT_DATA_EXTERNAL and an IOMMUFD_OBJ_HWPT_EXTERNAL object for
externally managed attachable domains created under a vIOMMU.

Route vIOMMU child HWPT allocation by data type, add the driver
alloc_domain_external callback, and allow device attach/replace to
consume external HWPTs.

Assisted-by: GPT-5.6 Sol
Signed-off-by: Jacob Pan <jacob.pan@linux.microsoft.com>
---
 drivers/iommu/iommufd/device.c          |  9 +--
 drivers/iommu/iommufd/hw_pagetable.c    | 88 +++++++++++++++++++++++--
 drivers/iommu/iommufd/iommufd_private.h |  7 ++
 drivers/iommu/iommufd/main.c            |  4 ++
 include/linux/iommufd.h                 | 11 ++++
 include/uapi/linux/iommufd.h            | 17 +++++
 6 files changed, 126 insertions(+), 10 deletions(-)

diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c
index 5c4b06eda546..f99b4730994c 100644
--- a/drivers/iommu/iommufd/device.c
+++ b/drivers/iommu/iommufd/device.c
@@ -1026,6 +1026,7 @@ static int iommufd_device_change_pt(struct iommufd_device *idev,
 		return PTR_ERR(pt_obj);
 
 	switch (pt_obj->type) {
+	case IOMMUFD_OBJ_HWPT_EXTERNAL:
 	case IOMMUFD_OBJ_HWPT_NESTED:
 	case IOMMUFD_OBJ_HWPT_PAGING: {
 		struct iommufd_hw_pagetable *hwpt =
@@ -1066,8 +1067,8 @@ static int iommufd_device_change_pt(struct iommufd_device *idev,
  * iommufd_device_attach - Connect a device/pasid to an iommu_domain
  * @idev: device to attach
  * @pasid: pasid to attach
- * @pt_id: Input an IOMMUFD_OBJ_IOAS, or IOMMUFD_OBJ_HWPT_PAGING
- *         Output the IOMMUFD_OBJ_HWPT_PAGING ID
+ * @pt_id: Input an IOMMUFD_OBJ_IOAS, IOMMUFD_OBJ_HWPT_PAGING, or
+ *         IOMMUFD_OBJ_HWPT_EXTERNAL. Output the HWPT ID
  *
  * This connects the device/pasid to an iommu_domain, either automatically
  * or manually selected. Once this completes the device could do DMA with
@@ -1099,8 +1100,8 @@ EXPORT_SYMBOL_NS_GPL(iommufd_device_attach, "IOMMUFD");
  * iommufd_device_replace - Change the device/pasid's iommu_domain
  * @idev: device to change
  * @pasid: pasid to change
- * @pt_id: Input an IOMMUFD_OBJ_IOAS, or IOMMUFD_OBJ_HWPT_PAGING
- *         Output the IOMMUFD_OBJ_HWPT_PAGING ID
+ * @pt_id: Input an IOMMUFD_OBJ_IOAS, IOMMUFD_OBJ_HWPT_PAGING, or
+ *         IOMMUFD_OBJ_HWPT_EXTERNAL. Output the HWPT ID
  *
  * This is the same as::
  *
diff --git a/drivers/iommu/iommufd/hw_pagetable.c b/drivers/iommu/iommufd/hw_pagetable.c
index ef6e119c2a75..324a81d9bae4 100644
--- a/drivers/iommu/iommufd/hw_pagetable.c
+++ b/drivers/iommu/iommufd/hw_pagetable.c
@@ -77,6 +77,20 @@ void iommufd_hwpt_nested_abort(struct iommufd_object *obj)
 	iommufd_hwpt_nested_destroy(obj);
 }
 
+void iommufd_hwpt_external_destroy(struct iommufd_object *obj)
+{
+	struct iommufd_hwpt_external *hwpt_external =
+		container_of(obj, struct iommufd_hwpt_external, common.obj);
+
+	__iommufd_hwpt_destroy(&hwpt_external->common);
+	refcount_dec(&hwpt_external->viommu->obj.users);
+}
+
+void iommufd_hwpt_external_abort(struct iommufd_object *obj)
+{
+	iommufd_hwpt_external_destroy(obj);
+}
+
 static int
 iommufd_hwpt_paging_enforce_cc(struct iommufd_hwpt_paging *hwpt_paging)
 {
@@ -306,6 +320,8 @@ iommufd_viommu_alloc_hwpt_nested(struct iommufd_viommu *viommu, u32 flags,
 		return ERR_PTR(-EOPNOTSUPP);
 	if (!user_data->len)
 		return ERR_PTR(-EOPNOTSUPP);
+	if (!viommu->hwpt)
+		return ERR_PTR(-EINVAL);
 	if (!viommu->ops || !viommu->ops->alloc_domain_nested)
 		return ERR_PTR(-EOPNOTSUPP);
 
@@ -342,6 +358,53 @@ iommufd_viommu_alloc_hwpt_nested(struct iommufd_viommu *viommu, u32 flags,
 	return ERR_PTR(rc);
 }
 
+static struct iommufd_hwpt_external *
+iommufd_viommu_alloc_hwpt_external(struct iommufd_viommu *viommu, u32 flags,
+				   const struct iommu_user_data *user_data)
+{
+	struct iommufd_hwpt_external *hwpt_external;
+	struct iommufd_hw_pagetable *hwpt;
+	int rc;
+
+	if (flags)
+		return ERR_PTR(-EOPNOTSUPP);
+	if (!user_data->len)
+		return ERR_PTR(-EOPNOTSUPP);
+	if (!viommu->ops || !viommu->ops->alloc_domain_external)
+		return ERR_PTR(-EOPNOTSUPP);
+
+	hwpt_external = __iommufd_object_alloc(viommu->ictx, hwpt_external,
+					       IOMMUFD_OBJ_HWPT_EXTERNAL,
+					       common.obj);
+	if (IS_ERR(hwpt_external))
+		return ERR_CAST(hwpt_external);
+	hwpt = &hwpt_external->common;
+
+	hwpt_external->viommu = viommu;
+	refcount_inc(&viommu->obj.users);
+
+	hwpt->domain = viommu->ops->alloc_domain_external(viommu, flags,
+							user_data);
+	if (IS_ERR(hwpt->domain)) {
+		rc = PTR_ERR(hwpt->domain);
+		hwpt->domain = NULL;
+		goto out_abort;
+	}
+	hwpt->domain->iommufd_hwpt = hwpt;
+	hwpt->domain->owner = viommu->iommu_dev->ops;
+	hwpt->domain->cookie_type = IOMMU_COOKIE_IOMMUFD;
+
+	if (WARN_ON_ONCE(hwpt->domain->type != IOMMU_DOMAIN_EXTERNAL)) {
+		rc = -EOPNOTSUPP;
+		goto out_abort;
+	}
+	return hwpt_external;
+
+out_abort:
+	iommufd_object_abort_and_destroy(viommu->ictx, &hwpt->obj);
+	return ERR_PTR(rc);
+}
+
 int iommufd_hwpt_alloc(struct iommufd_ucmd *ucmd)
 {
 	struct iommu_hwpt_alloc *cmd = ucmd->cmd;
@@ -399,6 +462,7 @@ int iommufd_hwpt_alloc(struct iommufd_ucmd *ucmd)
 		}
 		hwpt = &hwpt_nested->common;
 	} else if (pt_obj->type == IOMMUFD_OBJ_VIOMMU) {
+		struct iommufd_hwpt_external *hwpt_external;
 		struct iommufd_hwpt_nested *hwpt_nested;
 		struct iommu_device *iommu_dev;
 		struct iommufd_viommu *viommu;
@@ -409,13 +473,25 @@ int iommufd_hwpt_alloc(struct iommufd_ucmd *ucmd)
 			rc = -EINVAL;
 			goto out_unlock;
 		}
-		hwpt_nested = iommufd_viommu_alloc_hwpt_nested(
-			viommu, cmd->flags, &user_data);
-		if (IS_ERR(hwpt_nested)) {
-			rc = PTR_ERR(hwpt_nested);
-			goto out_unlock;
+		if (cmd->data_type == IOMMU_HWPT_DATA_EXTERNAL) {
+			hwpt_external =
+				iommufd_viommu_alloc_hwpt_external(viommu,
+								   cmd->flags,
+								   &user_data);
+			if (IS_ERR(hwpt_external)) {
+				rc = PTR_ERR(hwpt_external);
+				goto out_unlock;
+			}
+			hwpt = &hwpt_external->common;
+		} else {
+			hwpt_nested = iommufd_viommu_alloc_hwpt_nested(
+				viommu, cmd->flags, &user_data);
+			if (IS_ERR(hwpt_nested)) {
+				rc = PTR_ERR(hwpt_nested);
+				goto out_unlock;
+			}
+			hwpt = &hwpt_nested->common;
 		}
-		hwpt = &hwpt_nested->common;
 	} else {
 		rc = -EINVAL;
 		goto out_put_pt;
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index eb2e85b27e42..8b7aaaaba194 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -403,6 +403,11 @@ struct iommufd_hwpt_nested {
 	struct iommufd_viommu *viommu;
 };
 
+struct iommufd_hwpt_external {
+	struct iommufd_hw_pagetable common;
+	struct iommufd_viommu *viommu;
+};
+
 static inline bool hwpt_is_paging(struct iommufd_hw_pagetable *hwpt)
 {
 	return hwpt->obj.type == IOMMUFD_OBJ_HWPT_PAGING;
@@ -465,6 +470,8 @@ void iommufd_hwpt_paging_destroy(struct iommufd_object *obj);
 void iommufd_hwpt_paging_abort(struct iommufd_object *obj);
 void iommufd_hwpt_nested_destroy(struct iommufd_object *obj);
 void iommufd_hwpt_nested_abort(struct iommufd_object *obj);
+void iommufd_hwpt_external_destroy(struct iommufd_object *obj);
+void iommufd_hwpt_external_abort(struct iommufd_object *obj);
 int iommufd_hwpt_alloc(struct iommufd_ucmd *ucmd);
 int iommufd_hwpt_invalidate(struct iommufd_ucmd *ucmd);
 
diff --git a/drivers/iommu/iommufd/main.c b/drivers/iommu/iommufd/main.c
index 9a921b153162..6835bb87916a 100644
--- a/drivers/iommu/iommufd/main.c
+++ b/drivers/iommu/iommufd/main.c
@@ -736,6 +736,10 @@ static const struct iommufd_object_ops iommufd_object_ops[] = {
 		.destroy = iommufd_hwpt_nested_destroy,
 		.abort = iommufd_hwpt_nested_abort,
 	},
+	[IOMMUFD_OBJ_HWPT_EXTERNAL] = {
+		.destroy = iommufd_hwpt_external_destroy,
+		.abort = iommufd_hwpt_external_abort,
+	},
 	[IOMMUFD_OBJ_IOAS] = {
 		.destroy = iommufd_ioas_destroy,
 	},
diff --git a/include/linux/iommufd.h b/include/linux/iommufd.h
index 6e7efe83bc5d..ce740a2d1588 100644
--- a/include/linux/iommufd.h
+++ b/include/linux/iommufd.h
@@ -31,6 +31,7 @@ enum iommufd_object_type {
 	IOMMUFD_OBJ_DEVICE,
 	IOMMUFD_OBJ_HWPT_PAGING,
 	IOMMUFD_OBJ_HWPT_NESTED,
+	IOMMUFD_OBJ_HWPT_EXTERNAL,
 	IOMMUFD_OBJ_IOAS,
 	IOMMUFD_OBJ_ACCESS,
 	IOMMUFD_OBJ_FAULT,
@@ -149,6 +150,12 @@ struct iommufd_hw_queue {
  *                       must be defined in include/uapi/linux/iommufd.h.
  *                       It must fully initialize the new iommu_domain before
  *                       returning. Upon failure, ERR_PTR must be returned.
+ * @alloc_domain_external: Allocate a IOMMU_DOMAIN_EXTERNAL on a vIOMMU that holds
+ *                       the VM identity for an external attach domain. @user_data must
+ *                       be defined in
+ *                       include/uapi/linux/iommufd.h. It must fully initialize
+ *                       the new iommu_domain before returning. Upon failure,
+ *                       ERR_PTR must be returned.
  * @cache_invalidate: Flush hardware cache used by a vIOMMU. It can be used for
  *                    any IOMMU hardware specific cache: TLB and device cache.
  *                    The @array passes in the cache invalidation requests, in
@@ -186,6 +193,10 @@ struct iommufd_viommu_ops {
 	struct iommu_domain *(*alloc_domain_nested)(
 		struct iommufd_viommu *viommu, u32 flags,
 		const struct iommu_user_data *user_data);
+	struct iommu_domain *
+		(*alloc_domain_external)(struct iommufd_viommu *viommu,
+					 u32 flags,
+					 const struct iommu_user_data *user_data);
 	int (*cache_invalidate)(struct iommufd_viommu *viommu,
 				struct iommu_user_data_array *array);
 	const size_t vdevice_size;
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index bffa25c5267e..fa6185f04c1c 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -506,18 +506,31 @@ struct iommu_hwpt_amd_guest {
 	__aligned_u64 dte[4];
 };
 
+/**
+ * struct iommu_hwpt_external - external attach domain
+ *			      (IOMMU_HWPT_DATA_EXTERNAL)
+ * @flags: Must be 0
+ * @__reserved: Must be 0
+ */
+struct iommu_hwpt_external {
+	__u32 flags;
+	__u32 __reserved;
+};
+
 /**
  * enum iommu_hwpt_data_type - IOMMU HWPT Data Type
  * @IOMMU_HWPT_DATA_NONE: no data
  * @IOMMU_HWPT_DATA_VTD_S1: Intel VT-d stage-1 page table
  * @IOMMU_HWPT_DATA_ARM_SMMUV3: ARM SMMUv3 Context Descriptor Table
  * @IOMMU_HWPT_DATA_AMD_GUEST: AMD IOMMU guest page table
+ * @IOMMU_HWPT_DATA_EXTERNAL: external attach domain
  */
 enum iommu_hwpt_data_type {
 	IOMMU_HWPT_DATA_NONE = 0,
 	IOMMU_HWPT_DATA_VTD_S1 = 1,
 	IOMMU_HWPT_DATA_ARM_SMMUV3 = 2,
 	IOMMU_HWPT_DATA_AMD_GUEST = 3,
+	IOMMU_HWPT_DATA_EXTERNAL = 4,
 };
 
 /**
@@ -552,6 +565,10 @@ enum iommu_hwpt_data_type {
  * via @dev_id and the vIOMMU via @pt_id must be associated to the same IOMMU
  * instance.
  *
+ * An external attach HWPT can be created from a given vIOMMU via @pt_id. In this
+ * case, @data_type selects the external attach domain type, such as
+ * IOMMU_HWPT_DATA_EXTERNAL, and the created HWPT is not IOAS-backed.
+ *
  * If the @data_type is set to IOMMU_HWPT_DATA_NONE, @data_len and
  * @data_uptr should be zero. Otherwise, both @data_len and @data_uptr
  * must be given.
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH RFC 4/9] iommufd/selftest: Add hypervisor external attach backend
  2026-09-25 19:07 [RFC PATCH 0/9] iommu/iommufd: Add hypervisor external attach Jacob Pan
                   ` (2 preceding siblings ...)
  2026-09-25 19:07 ` [PATCH RFC 3/9] iommufd: Add external HWPT support Jacob Pan
@ 2026-09-25 19:07 ` Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 5/9] mshv: Add partition file identity helper Jacob Pan
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jacob Pan @ 2026-09-25 19:07 UTC (permalink / raw)
  To: iommu
  Cc: Jason Gunthorpe, Nicolin Chen, Kevin Tian, Will Deacon,
	Robin Murphy, Wei Liu, K . Y . Srinivasan, Haiyang Zhang,
	Dexuan Cui, Long Li, linux-hyperv, Joerg Roedel,
	Suravee Suthikulpanit, Vasant Hegde, Arnd Bergmann, linux-arch,
	linux-kernel, Aneesh Kumar K . V, Mukesh Rathor, John Starks,
	Souradeep Chakrabarti, Yu Zhang, Easwar Hariharan,
	Alex Williamson

Teach the mock IOMMU driver to allocate an hypervisor vIOMMU from a
partition fd and to allocate an external domain for external attach HWPT
testing.

Require a matching vDEVICE during external-domain attach so the selftest
backend exercises the vIOMMU/vDEVICE/HWPT relationship used by the
external attach flow.

Assisted-by: GPT-5.6 Sol
Signed-off-by: Jacob Pan <jacob.pan@linux.microsoft.com>
---
 drivers/iommu/iommufd/selftest.c | 120 ++++++++++++++++++++++++++++---
 1 file changed, 112 insertions(+), 8 deletions(-)

diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c
index ee706f18f7e9..b264114b6552 100644
--- a/drivers/iommu/iommufd/selftest.c
+++ b/drivers/iommu/iommufd/selftest.c
@@ -28,6 +28,7 @@ static struct dentry *dbgfs_root;
 static struct platform_device *selftest_iommu_dev;
 static const struct iommu_ops mock_ops;
 static struct iommu_domain_ops domain_nested_ops;
+static struct iommu_domain_ops domain_direct_ops;
 
 size_t iommufd_test_memory_limit = 65536;
 
@@ -142,9 +143,21 @@ to_mock_nested(struct iommu_domain *domain)
 	return container_of(domain, struct mock_iommu_domain_nested, domain);
 }
 
+struct mock_iommu_domain_direct {
+	struct iommu_domain domain;
+	struct mock_viommu *mock_viommu;
+};
+
+static inline struct mock_iommu_domain_direct *
+to_mock_direct(struct iommu_domain *domain)
+{
+	return container_of(domain, struct mock_iommu_domain_direct, domain);
+}
+
 struct mock_viommu {
 	struct iommufd_viommu core;
 	struct mock_iommu_domain *s2_parent;
+	struct file *vm_file;
 	struct mock_hw_queue *hw_queue[IOMMU_TEST_HW_QUEUE_MAX];
 	struct mutex queue_mutex;
 
@@ -231,6 +244,12 @@ static int mock_domain_nop_attach(struct iommu_domain *domain,
 			if (rc)
 				return rc;
 		}
+	} else if (domain->type == IOMMU_DOMAIN_EXTERNAL) {
+		new_viommu = to_mock_direct(domain)->mock_viommu;
+		rc = iommufd_viommu_get_vdev_id(&new_viommu->core, dev,
+						&vdev_id);
+		if (rc)
+			return rc;
 	}
 	if (new_viommu != mdev->viommu) {
 		down_write(&mdev->viommu_rwsem);
@@ -607,7 +626,15 @@ static void mock_viommu_destroy(struct iommufd_viommu *viommu)
 	if (mock_viommu->mmap_offset)
 		iommufd_viommu_destroy_mmap(&mock_viommu->core,
 					    mock_viommu->mmap_offset);
-	free_pages((unsigned long)mock_viommu->page, 1);
+	if (mock_viommu->page)
+		free_pages((unsigned long)mock_viommu->page, 1);
+	if (mock_viommu->vm_file) {
+		pr_info("iommufd selftest: MSHV viommu destroy drops pinned vm_file=%p\n",
+			mock_viommu->vm_file);
+		fput(mock_viommu->vm_file);
+		pr_info("iommufd selftest: MSHV viommu dropped pinned vm_file=%p\n",
+			mock_viommu->vm_file);
+	}
 	mutex_destroy(&mock_viommu->queue_mutex);
 
 	/* iommufd core frees mock_viommu and viommu */
@@ -630,6 +657,40 @@ mock_viommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,
 	return &mock_nested->domain;
 }
 
+static struct iommu_domain *
+mock_viommu_alloc_domain_external(struct iommufd_viommu *viommu, u32 flags,
+				  const struct iommu_user_data *user_data)
+{
+	struct mock_iommu_domain_direct *mock_direct;
+	struct iommu_hwpt_external data = {};
+	int rc;
+
+	if (viommu->type != IOMMU_VIOMMU_TYPE_HYPERVISOR)
+		return ERR_PTR(-EOPNOTSUPP);
+	if (flags)
+		return ERR_PTR(-EOPNOTSUPP);
+	if (user_data->type != IOMMU_HWPT_DATA_EXTERNAL)
+		return ERR_PTR(-EOPNOTSUPP);
+
+	rc = iommu_copy_struct_from_user(&data, user_data,
+					 IOMMU_HWPT_DATA_EXTERNAL, flags);
+	if (rc)
+		return ERR_PTR(rc);
+	if (data.flags || data.__reserved)
+		return ERR_PTR(-EOPNOTSUPP);
+
+	mock_direct = kzalloc_obj(*mock_direct);
+	if (!mock_direct)
+		return ERR_PTR(-ENOMEM);
+
+	mock_direct->domain.ops = &domain_direct_ops;
+	mock_direct->domain.type = IOMMU_DOMAIN_EXTERNAL;
+	mock_direct->mock_viommu = to_mock_viommu(viommu);
+	pr_info("iommufd selftest: external attach domain allocated viommu_id=%u\n",
+		viommu->obj.id);
+	return &mock_direct->domain;
+}
+
 static int mock_viommu_cache_invalidate(struct iommufd_viommu *viommu,
 					struct iommu_user_data_array *array)
 {
@@ -777,6 +838,7 @@ static int mock_hw_queue_init_phys(struct iommufd_hw_queue *hw_queue, u32 index,
 static struct iommufd_viommu_ops mock_viommu_ops = {
 	.destroy = mock_viommu_destroy,
 	.alloc_domain_nested = mock_viommu_alloc_domain_nested,
+	.alloc_domain_external = mock_viommu_alloc_domain_external,
 	.cache_invalidate = mock_viommu_cache_invalidate,
 	.get_hw_queue_size = mock_viommu_get_hw_queue_size,
 	.hw_queue_init_phys = mock_hw_queue_init_phys,
@@ -785,7 +847,8 @@ static struct iommufd_viommu_ops mock_viommu_ops = {
 static size_t mock_get_viommu_size(struct device *dev,
 				   enum iommu_viommu_type viommu_type)
 {
-	if (viommu_type != IOMMU_VIOMMU_TYPE_SELFTEST)
+	if (viommu_type != IOMMU_VIOMMU_TYPE_SELFTEST &&
+	    viommu_type != IOMMU_VIOMMU_TYPE_HYPERVISOR)
 		return 0;
 	return VIOMMU_STRUCT_SIZE(struct mock_viommu, core);
 }
@@ -797,10 +860,32 @@ static int mock_viommu_init(struct iommufd_viommu *viommu,
 	struct mock_iommu_device *mock_iommu = container_of(
 		viommu->iommu_dev, struct mock_iommu_device, iommu_dev);
 	struct mock_viommu *mock_viommu = to_mock_viommu(viommu);
-	struct iommu_viommu_selftest data;
+	struct iommu_viommu_selftest data = {};
 	int rc;
 
-	if (user_data) {
+	if (viommu->type == IOMMU_VIOMMU_TYPE_HYPERVISOR) {
+		struct iommu_viommu_hypervisor hypervisor = {};
+
+		if (!user_data)
+			return -EINVAL;
+
+		rc = iommu_copy_struct_from_user(&hypervisor, user_data,
+						 IOMMU_VIOMMU_TYPE_HYPERVISOR,
+						 vm_fd);
+		if (rc)
+			return rc;
+		if (hypervisor.flags || hypervisor.__reserved)
+			return -EOPNOTSUPP;
+
+		mock_viommu->vm_file = fget(hypervisor.vm_fd);
+		if (!mock_viommu->vm_file)
+			return -EBADF;
+		pr_info("iommufd selftest: MSHV viommu init type=%u vm_fd=%d vm_file=%p has_hwpt_paging=%u\n",
+			viommu->type, hypervisor.vm_fd, mock_viommu->vm_file,
+			!!parent_domain);
+		pr_info("iommufd selftest: MSHV viommu pins vm_file=%p, userspace close waits for viommu destroy\n",
+			mock_viommu->vm_file);
+	} else if (user_data) {
 		rc = iommu_copy_struct_from_user(
 			&data, user_data, IOMMU_VIOMMU_TYPE_SELFTEST, out_data);
 		if (rc)
@@ -809,8 +894,10 @@ static int mock_viommu_init(struct iommufd_viommu *viommu,
 		/* Allocate two pages */
 		mock_viommu->page =
 			(u32 *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 1);
-		if (!mock_viommu->page)
-			return -ENOMEM;
+		if (!mock_viommu->page) {
+			rc = -ENOMEM;
+			goto err_put_vm_file;
+		}
 
 		rc = iommufd_viommu_alloc_mmap(&mock_viommu->core,
 					       __pa(mock_viommu->page),
@@ -832,7 +919,8 @@ static int mock_viommu_init(struct iommufd_viommu *viommu,
 
 	refcount_inc(&mock_iommu->users);
 	mutex_init(&mock_viommu->queue_mutex);
-	mock_viommu->s2_parent = to_mock_domain(parent_domain);
+	if (parent_domain)
+		mock_viommu->s2_parent = to_mock_domain(parent_domain);
 
 	viommu->ops = &mock_viommu_ops;
 	return 0;
@@ -842,6 +930,11 @@ static int mock_viommu_init(struct iommufd_viommu *viommu,
 				    mock_viommu->mmap_offset);
 err_free_page:
 	free_pages((unsigned long)mock_viommu->page, 1);
+err_put_vm_file:
+	if (mock_viommu->vm_file) {
+		fput(mock_viommu->vm_file);
+		mock_viommu->vm_file = NULL;
+	}
 	return rc;
 }
 
@@ -870,6 +963,11 @@ static void mock_domain_free_nested(struct iommu_domain *domain)
 	kfree(to_mock_nested(domain));
 }
 
+static void mock_domain_free_direct(struct iommu_domain *domain)
+{
+	kfree(to_mock_direct(domain));
+}
+
 static int
 mock_domain_cache_invalidate_user(struct iommu_domain *domain,
 				  struct iommu_user_data_array *array)
@@ -925,6 +1023,11 @@ static struct iommu_domain_ops domain_nested_ops = {
 	.set_dev_pasid = mock_domain_set_dev_pasid_nop,
 };
 
+static struct iommu_domain_ops domain_direct_ops = {
+	.free = mock_domain_free_direct,
+	.attach_dev = mock_domain_nop_attach,
+};
+
 static inline struct iommufd_hw_pagetable *
 __get_md_pagetable(struct iommufd_ucmd *ucmd, u32 mockpt_id, u32 hwpt_type)
 {
@@ -1817,7 +1920,8 @@ iommufd_get_hwpt(struct iommufd_ucmd *ucmd, u32 id)
 	if (IS_ERR(pt_obj))
 		return ERR_CAST(pt_obj);
 
-	if (pt_obj->type != IOMMUFD_OBJ_HWPT_NESTED &&
+	if (pt_obj->type != IOMMUFD_OBJ_HWPT_EXTERNAL &&
+	    pt_obj->type != IOMMUFD_OBJ_HWPT_NESTED &&
 	    pt_obj->type != IOMMUFD_OBJ_HWPT_PAGING) {
 		iommufd_put_object(ucmd->ictx, pt_obj);
 		return ERR_PTR(-EINVAL);
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH RFC 5/9] mshv: Add partition file identity helper
  2026-09-25 19:07 [RFC PATCH 0/9] iommu/iommufd: Add hypervisor external attach Jacob Pan
                   ` (3 preceding siblings ...)
  2026-09-25 19:07 ` [PATCH RFC 4/9] iommufd/selftest: Add hypervisor external attach backend Jacob Pan
@ 2026-09-25 19:07 ` Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 6/9] mshv: Add prepare callback for external device attach Jacob Pan
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jacob Pan @ 2026-09-25 19:07 UTC (permalink / raw)
  To: iommu
  Cc: Jason Gunthorpe, Nicolin Chen, Kevin Tian, Will Deacon,
	Robin Murphy, Wei Liu, K . Y . Srinivasan, Haiyang Zhang,
	Dexuan Cui, Long Li, linux-hyperv, Joerg Roedel,
	Suravee Suthikulpanit, Vasant Hegde, Arnd Bergmann, linux-arch,
	linux-kernel, Aneesh Kumar K . V, Mukesh Rathor, John Starks,
	Souradeep Chakrabarti, Yu Zhang, Easwar Hariharan,
	Alex Williamson

Provide a helper to derive the immutable MSHV partition ID from a
partition file. IOMMU drivers can use this to bind a vIOMMU to the
partition object represented by a userspace fd instead of relying on
current-task lookup.

Keep the public helper in built-in Hyper-V common code and let the MSHV
root driver register the partition-file callbacks. This lets built-in
IOMMU callers avoid symbol_get() when MSHV_ROOT is built as a module.

Assisted-by: GPT-5.6 Sol
Signed-off-by: Jacob Pan <jacob.pan@linux.microsoft.com>
---
 drivers/hv/hv_common.c         | 69 ++++++++++++++++++++++++++++++++++
 drivers/hv/mshv_root_main.c    | 36 +++++++++++++++++-
 include/asm-generic/mshyperv.h | 35 +++++++++++++++++
 3 files changed, 139 insertions(+), 1 deletion(-)

diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
index 31256cb22b39..d259cb833376 100644
--- a/drivers/hv/hv_common.c
+++ b/drivers/hv/hv_common.c
@@ -17,6 +17,7 @@
 #include <linux/export.h>
 #include <linux/bitfield.h>
 #include <linux/cpumask.h>
+#include <linux/fs.h>
 #include <linux/sched/task_stack.h>
 #include <linux/panic_notifier.h>
 #include <linux/ptrace.h>
@@ -24,6 +25,7 @@
 #include <linux/efi.h>
 #include <linux/kdebug.h>
 #include <linux/kmsg_dump.h>
+#include <linux/mutex.h>
 #include <linux/sizes.h>
 #include <linux/slab.h>
 #include <linux/dma-map-ops.h>
@@ -37,6 +39,73 @@ EXPORT_SYMBOL_GPL(hv_current_partition_id);
 enum hv_partition_type hv_curr_partition_type;
 EXPORT_SYMBOL_GPL(hv_curr_partition_type);
 
+static DEFINE_MUTEX(mshv_partition_file_ops_lock);
+static const struct mshv_partition_file_ops *mshv_partition_file_ops;
+
+int mshv_partition_file_ops_register(const struct mshv_partition_file_ops *ops)
+{
+	int ret = 0;
+
+	if (!ops || !ops->file_is_partition || !ops->get_partid)
+		return -EINVAL;
+
+	mutex_lock(&mshv_partition_file_ops_lock);
+	if (mshv_partition_file_ops)
+		ret = -EBUSY;
+	else
+		mshv_partition_file_ops = ops;
+	mutex_unlock(&mshv_partition_file_ops_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(mshv_partition_file_ops_register);
+
+void
+mshv_partition_file_ops_unregister(const struct mshv_partition_file_ops *ops)
+{
+	mutex_lock(&mshv_partition_file_ops_lock);
+	if (mshv_partition_file_ops == ops)
+		mshv_partition_file_ops = NULL;
+	mutex_unlock(&mshv_partition_file_ops_lock);
+}
+EXPORT_SYMBOL_GPL(mshv_partition_file_ops_unregister);
+
+bool file_is_mshv_partition(struct file *file)
+{
+	const struct mshv_partition_file_ops *ops;
+	bool ret = false;
+
+	if (!file)
+		return false;
+
+	mutex_lock(&mshv_partition_file_ops_lock);
+	ops = mshv_partition_file_ops;
+	if (ops)
+		ret = ops->file_is_partition(file);
+	mutex_unlock(&mshv_partition_file_ops_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(file_is_mshv_partition);
+
+u64 mshv_partition_file_get_partid(struct file *file)
+{
+	const struct mshv_partition_file_ops *ops;
+	u64 ret = HV_PARTITION_ID_INVALID;
+
+	if (!file)
+		return HV_PARTITION_ID_INVALID;
+
+	mutex_lock(&mshv_partition_file_ops_lock);
+	ops = mshv_partition_file_ops;
+	if (ops && ops->file_is_partition(file))
+		ret = ops->get_partid(file);
+	mutex_unlock(&mshv_partition_file_ops_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(mshv_partition_file_get_partid);
+
 /*
  * ms_hyperv and hv_nested are defined here with other
  * Hyper-V specific globals so they are shared across all architectures and are
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 8b31e3948c09..838ea6397c9f 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2160,6 +2160,33 @@ u64 mshv_current_partid(void)
 }
 EXPORT_SYMBOL_GPL(mshv_current_partid);
 
+static bool mshv_partition_file_is_valid(struct file *file)
+{
+	if (!file)
+		return false;
+
+	if (file->f_op == &mshv_partition_fops)
+		return true;
+
+	return false;
+}
+
+static u64 mshv_partition_file_get_partid_impl(struct file *file)
+{
+	if (file->f_op == &mshv_partition_fops) {
+		struct mshv_partition *partition = file->private_data;
+
+		return partition ? partition->pt_id : HV_PARTITION_ID_INVALID;
+	}
+
+	return HV_PARTITION_ID_INVALID;
+}
+
+static const struct mshv_partition_file_ops mshv_partition_file_ops = {
+	.file_is_partition = mshv_partition_file_is_valid,
+	.get_partid = mshv_partition_file_get_partid_impl,
+};
+
 static int
 add_partition(struct mshv_partition *partition)
 {
@@ -2599,10 +2626,14 @@ static int __init mshv_parent_partition_init(void)
 	if (hv_get_hypervisor_version(&version_info))
 		return -ENODEV;
 
-	ret = misc_register(&mshv_dev);
+	ret = mshv_partition_file_ops_register(&mshv_partition_file_ops);
 	if (ret)
 		return ret;
 
+	ret = misc_register(&mshv_dev);
+	if (ret)
+		goto unregister_file_ops;
+
 	dev = mshv_dev.this_device;
 
 	if (version_info.build_number < MSHV_HV_MIN_VERSION ||
@@ -2652,6 +2683,8 @@ static int __init mshv_parent_partition_init(void)
 	mshv_synic_exit();
 device_deregister:
 	misc_deregister(&mshv_dev);
+unregister_file_ops:
+	mshv_partition_file_ops_unregister(&mshv_partition_file_ops);
 	return ret;
 }
 
@@ -2661,6 +2694,7 @@ static void __exit mshv_parent_partition_exit(void)
 	mshv_port_table_fini();
 	mshv_debugfs_exit();
 	misc_deregister(&mshv_dev);
+	mshv_partition_file_ops_unregister(&mshv_partition_file_ops);
 	mshv_irqfd_wq_cleanup();
 	root_scheduler_deinit();
 	mshv_synic_exit();
diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
index c7258cd72a89..ed7ab21d68a2 100644
--- a/include/asm-generic/mshyperv.h
+++ b/include/asm-generic/mshyperv.h
@@ -401,6 +401,41 @@ static inline u64 mshv_current_partid(void)
 }
 #endif /* CONFIG_MSHV_ROOT */
 
+struct file;
+struct mshv_partition_file_ops {
+	bool (*file_is_partition)(struct file *file);
+	u64 (*get_partid)(struct file *file);
+};
+
+#if IS_ENABLED(CONFIG_HYPERV)
+int mshv_partition_file_ops_register(const struct mshv_partition_file_ops *ops);
+void
+mshv_partition_file_ops_unregister(const struct mshv_partition_file_ops *ops);
+bool file_is_mshv_partition(struct file *file);
+u64 mshv_partition_file_get_partid(struct file *file);
+#else
+static inline int
+mshv_partition_file_ops_register(const struct mshv_partition_file_ops *ops)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void
+mshv_partition_file_ops_unregister(const struct mshv_partition_file_ops *ops)
+{
+}
+
+static inline bool file_is_mshv_partition(struct file *file)
+{
+	return false;
+}
+
+static inline u64 mshv_partition_file_get_partid(struct file *file)
+{
+	return HV_PARTITION_ID_INVALID;
+}
+#endif
+
 static inline int hv_deposit_memory(u64 partition_id, u64 status)
 {
 	return hv_deposit_memory_node(NUMA_NO_NODE, partition_id, status);
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH RFC 6/9] mshv: Add prepare callback for external device attach
  2026-09-25 19:07 [RFC PATCH 0/9] iommu/iommufd: Add hypervisor external attach Jacob Pan
                   ` (4 preceding siblings ...)
  2026-09-25 19:07 ` [PATCH RFC 5/9] mshv: Add partition file identity helper Jacob Pan
@ 2026-09-25 19:07 ` Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 7/9] iommu/hyperv: Split root IOMMU declarations Jacob Pan
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jacob Pan @ 2026-09-25 19:07 UTC (permalink / raw)
  To: iommu
  Cc: Jason Gunthorpe, Nicolin Chen, Kevin Tian, Will Deacon,
	Robin Murphy, Wei Liu, K . Y . Srinivasan, Haiyang Zhang,
	Dexuan Cui, Long Li, linux-hyperv, Joerg Roedel,
	Suravee Suthikulpanit, Vasant Hegde, Arnd Bergmann, linux-arch,
	linux-kernel, Aneesh Kumar K . V, Mukesh Rathor, John Starks,
	Souradeep Chakrabarti, Yu Zhang, Easwar Hariharan,
	Alex Williamson

Unlike VFIO/IOMMUFD paging domains, external attach bypasses the DMA
map flow that pins mapped memory. The ownership model is also different
for MSHV external attach: MSHV, as the L0 hypervisor, owns address
translation and DMA fault handling.

Guest memory must therefore be pinned through the MSHV/userspace VMM
contract before it is mapped for DMA, rather than by the VFIO/IOMMUFD
pin-and-map flow.

Add a partition-file prepare_attach callback that lets MSHV apply its
guest-memory pinning policy and perform any other preparation required
before an external device is attached. A later external-domain patch
invokes the callback through the vIOMMU-held VM file.

The per-region conversion helper is intentionally left as a stub for
this RFC to show where MSHV will pin guest memory.

Assisted-by: GPT-5.6 Sol
Signed-off-by: Jacob Pan <jacob.pan@linux.microsoft.com>
---
 drivers/hv/hv_common.c         | 24 ++++++++++++++++++++++-
 drivers/hv/mshv_regions.c      |  6 ++++++
 drivers/hv/mshv_root.h         |  1 +
 drivers/hv/mshv_root_main.c    | 35 ++++++++++++++++++++++++++++++++++
 include/asm-generic/mshyperv.h |  7 +++++++
 5 files changed, 72 insertions(+), 1 deletion(-)

diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
index d259cb833376..7acea1bceae1 100644
--- a/drivers/hv/hv_common.c
+++ b/drivers/hv/hv_common.c
@@ -46,7 +46,8 @@ int mshv_partition_file_ops_register(const struct mshv_partition_file_ops *ops)
 {
 	int ret = 0;
 
-	if (!ops || !ops->file_is_partition || !ops->get_partid)
+	if (!ops || !ops->file_is_partition || !ops->get_partid ||
+	    !ops->prepare_attach)
 		return -EINVAL;
 
 	mutex_lock(&mshv_partition_file_ops_lock);
@@ -106,6 +107,27 @@ u64 mshv_partition_file_get_partid(struct file *file)
 }
 EXPORT_SYMBOL_GPL(mshv_partition_file_get_partid);
 
+int mshv_partition_file_prepare_attach(struct file *file)
+{
+	const struct mshv_partition_file_ops *ops;
+	int ret = -EOPNOTSUPP;
+
+	if (!file)
+		return -EINVAL;
+
+	mutex_lock(&mshv_partition_file_ops_lock);
+	ops = mshv_partition_file_ops;
+	if (ops && ops->file_is_partition(file))
+		ret = 0;
+	mutex_unlock(&mshv_partition_file_ops_lock);
+
+	if (!ret)
+		ret = ops->prepare_attach(file);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(mshv_partition_file_prepare_attach);
+
 /*
  * ms_hyperv and hv_nested are defined here with other
  * Hyper-V specific globals so they are shared across all architectures and are
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index dddaade31b5d..b98e5c3e7819 100644
--- a/drivers/hv/mshv_regions.c
+++ b/drivers/hv/mshv_regions.c
@@ -324,6 +324,12 @@ int mshv_region_pin(struct mshv_mem_region *region)
 	return ret < 0 ? ret : -ENOMEM;
 }
 
+int mshv_region_make_pinned(struct mshv_mem_region *region)
+{
+	/* Guest-memory pin conversion is not implemented in this RFC. */
+	return 0;
+}
+
 static int mshv_region_chunk_unmap(struct mshv_mem_region *region,
 				   u32 flags,
 				   u64 page_offset, u64 page_count,
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index d57c26950203..d0f3a28abb3b 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -393,6 +393,7 @@ int mshv_region_unshare(struct mshv_mem_region *region);
 int mshv_region_map(struct mshv_mem_region *region);
 void mshv_region_invalidate(struct mshv_mem_region *region);
 int mshv_region_pin(struct mshv_mem_region *region);
+int mshv_region_make_pinned(struct mshv_mem_region *region);
 void mshv_region_put(struct mshv_mem_region *region);
 int mshv_region_get(struct mshv_mem_region *region);
 bool mshv_region_handle_gfn_fault(struct mshv_mem_region *region, u64 gfn);
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 838ea6397c9f..fb422ed23662 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -2182,9 +2182,44 @@ static u64 mshv_partition_file_get_partid_impl(struct file *file)
 	return HV_PARTITION_ID_INVALID;
 }
 
+static int mshv_partition_file_prepare_attach_impl(struct file *file)
+{
+	struct mshv_mem_region *region;
+	struct mshv_partition *partition;
+	int ret = 0;
+
+#if IS_ENABLED(CONFIG_IOMMUFD_TEST)
+	if (file->f_op == &mshv_fake_partition_fops)
+		return 0;
+#endif
+	if (file->f_op != &mshv_partition_fops)
+		return -EINVAL;
+
+	partition = file->private_data;
+	if (!partition)
+		return -EINVAL;
+
+	mutex_lock(&partition->pt_mutex);
+	if (partition->pt_regions_pinned)
+		goto out_unlock;
+
+	hlist_for_each_entry(region, &partition->pt_mem_regions, hnode) {
+		ret = mshv_region_make_pinned(region);
+		if (ret)
+			goto out_unlock;
+	}
+
+	partition->pt_regions_pinned = true;
+
+out_unlock:
+	mutex_unlock(&partition->pt_mutex);
+	return ret;
+}
+
 static const struct mshv_partition_file_ops mshv_partition_file_ops = {
 	.file_is_partition = mshv_partition_file_is_valid,
 	.get_partid = mshv_partition_file_get_partid_impl,
+	.prepare_attach = mshv_partition_file_prepare_attach_impl,
 };
 
 static int
diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
index ed7ab21d68a2..6af793444a4b 100644
--- a/include/asm-generic/mshyperv.h
+++ b/include/asm-generic/mshyperv.h
@@ -405,6 +405,7 @@ struct file;
 struct mshv_partition_file_ops {
 	bool (*file_is_partition)(struct file *file);
 	u64 (*get_partid)(struct file *file);
+	int (*prepare_attach)(struct file *file);
 };
 
 #if IS_ENABLED(CONFIG_HYPERV)
@@ -413,6 +414,7 @@ void
 mshv_partition_file_ops_unregister(const struct mshv_partition_file_ops *ops);
 bool file_is_mshv_partition(struct file *file);
 u64 mshv_partition_file_get_partid(struct file *file);
+int mshv_partition_file_prepare_attach(struct file *file);
 #else
 static inline int
 mshv_partition_file_ops_register(const struct mshv_partition_file_ops *ops)
@@ -434,6 +436,11 @@ static inline u64 mshv_partition_file_get_partid(struct file *file)
 {
 	return HV_PARTITION_ID_INVALID;
 }
+
+static inline int mshv_partition_file_prepare_attach(struct file *file)
+{
+	return -EOPNOTSUPP;
+}
 #endif
 
 static inline int hv_deposit_memory(u64 partition_id, u64 status)
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH RFC 7/9] iommu/hyperv: Split root IOMMU declarations
  2026-09-25 19:07 [RFC PATCH 0/9] iommu/iommufd: Add hypervisor external attach Jacob Pan
                   ` (5 preceding siblings ...)
  2026-09-25 19:07 ` [PATCH RFC 6/9] mshv: Add prepare callback for external device attach Jacob Pan
@ 2026-09-25 19:07 ` Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 8/9] iommu/hyperv: Add fd-backed vIOMMU support Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 9/9] iommu/hyperv: Add IOMMUFD external domains Jacob Pan
  8 siblings, 0 replies; 10+ messages in thread
From: Jacob Pan @ 2026-09-25 19:07 UTC (permalink / raw)
  To: iommu
  Cc: Jason Gunthorpe, Nicolin Chen, Kevin Tian, Will Deacon,
	Robin Murphy, Wei Liu, K . Y . Srinivasan, Haiyang Zhang,
	Dexuan Cui, Long Li, linux-hyperv, Joerg Roedel,
	Suravee Suthikulpanit, Vasant Hegde, Arnd Bergmann, linux-arch,
	linux-kernel, Aneesh Kumar K . V, Mukesh Rathor, John Starks,
	Souradeep Chakrabarti, Yu Zhang, Easwar Hariharan,
	Alex Williamson

Move the root IOMMU shared domain type, page-size mask, and
geometry into a private Hyper-V IOMMU header so IOMMUFD support
can live outside the main root driver file.

Assisted-by: GPT-5.6 Sol
Signed-off-by: Jacob Pan <jacob.pan@linux.microsoft.com>
---
 drivers/iommu/hyperv/hv-iommu-root.c | 11 +----------
 drivers/iommu/hyperv/hv-iommu.h      | 26 ++++++++++++++++++++++++++
 2 files changed, 27 insertions(+), 10 deletions(-)
 create mode 100644 drivers/iommu/hyperv/hv-iommu.h

diff --git a/drivers/iommu/hyperv/hv-iommu-root.c b/drivers/iommu/hyperv/hv-iommu-root.c
index a5268e0e52cc..3b82891c9cab 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 "hv-iommu.h"
 #include <asm/iommu.h>
 #include <asm/mshyperv.h>
 #include "../dma-iommu.h"
@@ -31,15 +32,6 @@ static struct iommu_domain_ops hv_paging_domain_ops;
 /* IOMMU device that we export to the world. HyperV supports max of one */
 static struct iommu_device hv_virt_iommu;
 
-struct hv_domain {
-	struct iommu_domain iommu_dom;
-	u32 domid_num;			      /* as opposed to domain_id.type */
-	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)
-
 struct hv_iommu_mapping {
 	phys_addr_t paddr;
 	struct interval_tree_node iova;
@@ -67,7 +59,6 @@ static bool hv_special_domain(struct hv_domain *hvdom)
 	return hvdom == &hv_def_identity_dom || hvdom == &hv_def_blocked_dom;
 }
 
-#define HV_IOMMU_PGSIZES SZ_4K		/* for now, to be enhanced */
 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)
diff --git a/drivers/iommu/hyperv/hv-iommu.h b/drivers/iommu/hyperv/hv-iommu.h
new file mode 100644
index 000000000000..6895b4e7eb6d
--- /dev/null
+++ b/drivers/iommu/hyperv/hv-iommu.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Hyper-V root vIOMMU driver.
+ * Copyright (C) 2026, Microsoft, Inc.
+ */
+
+#ifndef __HYPERV_IOMMU_H
+#define __HYPERV_IOMMU_H
+
+#include <linux/interval_tree.h>
+#include <linux/iommu.h>
+#include <linux/sizes.h>
+#include <linux/spinlock.h>
+
+#define HV_IOMMU_PGSIZES SZ_4K  /* for now, to be enhanced */
+
+struct hv_domain {
+	struct iommu_domain iommu_dom;
+	u32 domid_num;			      /* as opposed to domain_id.type */
+	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)
+
+#endif /* __HYPERV_IOMMU_H */
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH RFC 8/9] iommu/hyperv: Add fd-backed vIOMMU support
  2026-09-25 19:07 [RFC PATCH 0/9] iommu/iommufd: Add hypervisor external attach Jacob Pan
                   ` (6 preceding siblings ...)
  2026-09-25 19:07 ` [PATCH RFC 7/9] iommu/hyperv: Split root IOMMU declarations Jacob Pan
@ 2026-09-25 19:07 ` Jacob Pan
  2026-09-25 19:07 ` [PATCH RFC 9/9] iommu/hyperv: Add IOMMUFD external domains Jacob Pan
  8 siblings, 0 replies; 10+ messages in thread
From: Jacob Pan @ 2026-09-25 19:07 UTC (permalink / raw)
  To: iommu
  Cc: Jason Gunthorpe, Nicolin Chen, Kevin Tian, Will Deacon,
	Robin Murphy, Wei Liu, K . Y . Srinivasan, Haiyang Zhang,
	Dexuan Cui, Long Li, linux-hyperv, Joerg Roedel,
	Suravee Suthikulpanit, Vasant Hegde, Arnd Bergmann, linux-arch,
	linux-kernel, Aneesh Kumar K . V, Mukesh Rathor, John Starks,
	Souradeep Chakrabarti, Yu Zhang, Easwar Hariharan,
	Alex Williamson

Wire Hyper-V root IOMMU ops to initialize hypervisor vIOMMUs from
MSHV partition file descriptors. Keep the VM file pinned for the
vIOMMU lifetime and derive the partition ID from the fd instead
of the VMM thread identity.

Assisted-by: GPT-5.6 Sol
Signed-off-by: Jacob Pan <jacob.pan@linux.microsoft.com>
---
 drivers/iommu/hyperv/Makefile           |  2 +-
 drivers/iommu/hyperv/hv-iommu-iommufd.c | 91 +++++++++++++++++++++++++
 drivers/iommu/hyperv/hv-iommu-root.c    |  2 +
 drivers/iommu/hyperv/hv-iommu.h         |  9 +++
 4 files changed, 103 insertions(+), 1 deletion(-)
 create mode 100644 drivers/iommu/hyperv/hv-iommu-iommufd.c

diff --git a/drivers/iommu/hyperv/Makefile b/drivers/iommu/hyperv/Makefile
index c7e7d0dac2a4..2b61a407ee58 100644
--- a/drivers/iommu/hyperv/Makefile
+++ b/drivers/iommu/hyperv/Makefile
@@ -1,3 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_IRQ_REMAP) += hv-irq-remap-x86.o
-obj-$(CONFIG_HYPERV_ROOT_IOMMU) += hv-iommu-root.o
+obj-$(CONFIG_HYPERV_ROOT_IOMMU) += hv-iommu-root.o hv-iommu-iommufd.o
diff --git a/drivers/iommu/hyperv/hv-iommu-iommufd.c b/drivers/iommu/hyperv/hv-iommu-iommufd.c
new file mode 100644
index 000000000000..486a85c0d1e8
--- /dev/null
+++ b/drivers/iommu/hyperv/hv-iommu-iommufd.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hyper-V root vIOMMU IOMMUFD support.
+ * Copyright (C) 2026, Microsoft, Inc.
+ */
+
+#include <linux/file.h>
+#include <linux/iommufd.h>
+#include <linux/slab.h>
+
+#include <asm/mshyperv.h>
+
+#include "hv-iommu.h"
+
+struct hv_iommu_viommu {
+	struct iommufd_viommu core;
+	struct file *vm_file;
+	u64 partid;
+};
+
+static const struct iommufd_viommu_ops hv_iommu_hypervisor_viommu_ops;
+
+static struct hv_iommu_viommu *
+to_hv_iommu_viommu(struct iommufd_viommu *viommu)
+{
+	return container_of(viommu, struct hv_iommu_viommu, core);
+}
+
+static void hv_iommu_viommu_destroy(struct iommufd_viommu *viommu)
+{
+	struct hv_iommu_viommu *hv_viommu = to_hv_iommu_viommu(viommu);
+
+	fput(hv_viommu->vm_file);
+}
+
+static const struct iommufd_viommu_ops hv_iommu_hypervisor_viommu_ops = {
+	.destroy = hv_iommu_viommu_destroy,
+};
+
+size_t hv_iommufd_get_viommu_size(struct device *dev,
+				  enum iommu_viommu_type viommu_type)
+{
+	if (viommu_type != IOMMU_VIOMMU_TYPE_HYPERVISOR)
+		return 0;
+	return VIOMMU_STRUCT_SIZE(struct hv_iommu_viommu, core);
+}
+
+int hv_iommufd_viommu_init(struct iommufd_viommu *viommu,
+			   struct iommu_domain *parent_domain,
+			   const struct iommu_user_data *user_data)
+{
+	struct hv_iommu_viommu *hv_viommu = to_hv_iommu_viommu(viommu);
+	struct iommu_viommu_hypervisor hypervisor = {};
+	int rc;
+
+	if (viommu->type != IOMMU_VIOMMU_TYPE_HYPERVISOR)
+		return -EOPNOTSUPP;
+	if (parent_domain || !user_data)
+		return -EINVAL;
+
+	rc = iommu_copy_struct_from_user(&hypervisor, user_data,
+					 IOMMU_VIOMMU_TYPE_HYPERVISOR, vm_fd);
+	if (rc)
+		return rc;
+	if (hypervisor.flags || hypervisor.__reserved)
+		return -EOPNOTSUPP;
+
+	hv_viommu->vm_file = fget(hypervisor.vm_fd);
+	if (!hv_viommu->vm_file)
+		return -EBADF;
+
+	if (!file_is_mshv_partition(hv_viommu->vm_file)) {
+		rc = -EINVAL;
+		goto out_put_file;
+	}
+
+	hv_viommu->partid =
+		mshv_partition_file_get_partid(hv_viommu->vm_file);
+	if (hv_viommu->partid == HV_PARTITION_ID_INVALID) {
+		rc = -EINVAL;
+		goto out_put_file;
+	}
+
+	viommu->ops = &hv_iommu_hypervisor_viommu_ops;
+	return 0;
+
+out_put_file:
+	fput(hv_viommu->vm_file);
+	hv_viommu->vm_file = NULL;
+	return rc;
+}
diff --git a/drivers/iommu/hyperv/hv-iommu-root.c b/drivers/iommu/hyperv/hv-iommu-root.c
index 3b82891c9cab..d6424b9ea0cd 100644
--- a/drivers/iommu/hyperv/hv-iommu-root.c
+++ b/drivers/iommu/hyperv/hv-iommu-root.c
@@ -530,6 +530,8 @@ static struct iommu_domain_ops hv_paging_domain_ops = {
 static struct iommu_ops hv_iommu_ops = {
 	.capable	    = hv_iommu_capable,
 	.domain_alloc_paging	= hv_iommu_domain_alloc_paging,
+	.get_viommu_size    = hv_iommufd_get_viommu_size,
+	.viommu_init	    = hv_iommufd_viommu_init,
 	.probe_device	    = hv_iommu_probe_device,
 	.device_group	    = hv_iommu_device_group,
 	.get_resv_regions   = hv_iommu_get_resv_regions,
diff --git a/drivers/iommu/hyperv/hv-iommu.h b/drivers/iommu/hyperv/hv-iommu.h
index 6895b4e7eb6d..6fdbe56a4b1d 100644
--- a/drivers/iommu/hyperv/hv-iommu.h
+++ b/drivers/iommu/hyperv/hv-iommu.h
@@ -14,6 +14,9 @@
 
 #define HV_IOMMU_PGSIZES SZ_4K  /* for now, to be enhanced */
 
+struct iommufd_viommu;
+struct iommu_user_data;
+
 struct hv_domain {
 	struct iommu_domain iommu_dom;
 	u32 domid_num;			      /* as opposed to domain_id.type */
@@ -23,4 +26,10 @@ struct hv_domain {
 
 #define to_hv_domain(d) container_of(d, struct hv_domain, iommu_dom)
 
+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);
+
 #endif /* __HYPERV_IOMMU_H */
-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH RFC 9/9] iommu/hyperv: Add IOMMUFD external domains
  2026-09-25 19:07 [RFC PATCH 0/9] iommu/iommufd: Add hypervisor external attach Jacob Pan
                   ` (7 preceding siblings ...)
  2026-09-25 19:07 ` [PATCH RFC 8/9] iommu/hyperv: Add fd-backed vIOMMU support Jacob Pan
@ 2026-09-25 19:07 ` Jacob Pan
  8 siblings, 0 replies; 10+ messages in thread
From: Jacob Pan @ 2026-09-25 19:07 UTC (permalink / raw)
  To: iommu
  Cc: Jason Gunthorpe, Nicolin Chen, Kevin Tian, Will Deacon,
	Robin Murphy, Wei Liu, K . Y . Srinivasan, Haiyang Zhang,
	Dexuan Cui, Long Li, linux-hyperv, Joerg Roedel,
	Suravee Suthikulpanit, Vasant Hegde, Arnd Bergmann, linux-arch,
	linux-kernel, Aneesh Kumar K . V, Mukesh Rathor, John Starks,
	Souradeep Chakrabarti, Yu Zhang, Easwar Hariharan,
	Alex Williamson

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 <jacob.pan@linux.microsoft.com>
---
 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


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-09-25 19:08 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-25 19:07 [RFC PATCH 0/9] iommu/iommufd: Add hypervisor external attach Jacob Pan
2026-09-25 19:07 ` [PATCH RFC 1/9] iommu: Introduce external attach domain type Jacob Pan
2026-09-25 19:07 ` [PATCH RFC 2/9] iommufd: Introduce hypervisor vIOMMU type Jacob Pan
2026-09-25 19:07 ` [PATCH RFC 3/9] iommufd: Add external HWPT support Jacob Pan
2026-09-25 19:07 ` [PATCH RFC 4/9] iommufd/selftest: Add hypervisor external attach backend Jacob Pan
2026-09-25 19:07 ` [PATCH RFC 5/9] mshv: Add partition file identity helper Jacob Pan
2026-09-25 19:07 ` [PATCH RFC 6/9] mshv: Add prepare callback for external device attach Jacob Pan
2026-09-25 19:07 ` [PATCH RFC 7/9] iommu/hyperv: Split root IOMMU declarations Jacob Pan
2026-09-25 19:07 ` [PATCH RFC 8/9] iommu/hyperv: Add fd-backed vIOMMU support Jacob Pan
2026-09-25 19:07 ` [PATCH RFC 9/9] iommu/hyperv: Add IOMMUFD external domains Jacob Pan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®