* [PATCH V4 0/9] PCI passthru on Hyper-V
@ 2026-07-18 2:19 Mukesh R
2026-07-18 2:19 ` [PATCH V4 1/9] mshv: Provide a way to get partition ID if running in a VMM process Mukesh R
` (8 more replies)
0 siblings, 9 replies; 19+ messages in thread
From: Mukesh R @ 2026-07-18 2:19 UTC (permalink / raw)
To: hpa, robin.murphy, robh, wei.liu, mrathor, mhklinux, muislam,
namjain, magnuskulke, anbelski, linux-kernel, linux-hyperv, iommu,
linux-pci, linux-arch
Cc: kys, haiyangz, decui, longli, tglx, mingo, bp, dave.hansen, x86,
joro, will, lpieralisi, kwilczynski, bhelgaas, arnd, jacob.pan
Implement passthru of PCI devices to unprivileged virtual machines
(VMs) when Linux is running as a privileged VM on Microsoft Hyper-V
hypervisor. This support is made to fit within the workings of VFIO
framework. This supports both full device passthru and SR-IOV based VFs.
At a high level, the hypervisor supports traditional paging iommu domains
that use explicit map and unmap hypercalls for mapping and unmapping
guest RAM into the iommu subsystem. Hyper-V also has a concept of direct
attach devices (DDA) whereby the iommu subsystem simply uses the guest
HW page table (ept/npt/..). This series adds support for the former.
This series rebased to: a4ffc59238be (origin/hyperv-next)
Testing:
o Most testing done on hyperv-next:a4ffc59238be using Cloud Hypervisor (51).
o All tests involved passthru of devices using MSIx.
o Tested on Baremetal dom0/root:
test 1: Mellanox ConnectX-6 Lx passthru
test 2: NVIDIA Tesla Tesla T4 GPU.
test 3: Both of above simultaneous passthru
o VF: Mellanox ConnectX-6 Lx passthru
This series strives to establish a base line. Some pending work items:
o arm64 : some delta to make this work on arm64 (in progress).
o Qemu and OpenVMM support (in progress).
o device sleep/wakeup.
o More stress testing with high end GPUs
Changes in V4:
o Completely remove direct device attach (DDA) support
o Remove first two patches that were submitted separately and are
already merged.
o Change hv_iommu_unmap_pages() to unmap in batches of HV_REP_COUNT_MAX,
otherwise if a physical range happens to be larger than that, there would
be errors from the hypercall.
o Use large page (2M) flag for mmio mappings as internal testing found
that to speed things up greatly.
o rename hv_no_movbl_pgs to hv_memreg in last patch.
Changes in V3:
o patch #8: fix compiler issues incase of !CONFIG_HYPERV. Also, do forward
declaration of struct pci_dev instead of including pci.h.
o patch #9: minor changes to comments. Pass hv_domain instead of
iommu_domain to hv_iommu_detach_dev() since that's what it needs. Set
device private to null if attach fails. Clam down number of PFNs passed
to hv_iommu_map_pgs().
Changes in V2:
o rebase to 5170a82e8921
o minor fixes for arm64 build
o drop patch 03: "x86/hyperv: add insufficient memory support in irqdomain.c"
as it that path is no longer used
o drop patch 08: "PCI: hv: rename hv_compose_msi_msg .. " and do it separately
outside this series.
o minor updates to commit messages
Changes in V1:
o patch 1: Don't tie hyperv-irq.c to CONFIG_HYPERV_IOMMU.
o patch 4: Redesigned to address security vulnerability found by copilot
with passing tgid as a parameter. Also, do tgid setting right
after setting pt_id.
o patch 5: Remove unused type parameter from mshv_device_ops.device_create
o patch 7: mshv_partition_ioctl_create_device cleanup on copy_to_user.
o patch 10: Add export of hv_build_devid_type_pci here to get rid of
patch 11.
o patch 12: Move functions to build device ids from patch 11 here for
the benefit of arm64. Rename file to: hyperv-iommu-root.c.
o patch 13: removed to be made part of interrupt part II of this support.
o patch 14: get rid of fast path to reduce review noise.
o New (last) patch to pin ram regions if device passthru to a VM.
Thanks,
-Mukesh
Mukesh R (9):
mshv: Provide a way to get partition ID if running in a VMM process
mshv: Add declarations and definitions for VFIO-MSHV bridge device
mshv: Introduce basic mshv bridge device for VFIO to build upon
mshv: Add ioctl support for MSHV-VFIO bridge device
mshv: Import data structs around device passthru from hyperv headers
PCI: hv: Export hv_build_devid_type_pci() and change return type
x86/hyperv: Implement Hyper-V virtual IOMMU
mshv: Populate mmio mappings for PCI passthru
mshv: Disable movable regions upfront if device passthru
arch/x86/hyperv/irqdomain.c | 9 +-
arch/x86/include/asm/mshyperv.h | 6 +
arch/x86/kernel/pci-dma.c | 2 +
drivers/hv/Makefile | 3 +-
drivers/hv/mshv_root.h | 24 +-
drivers/hv/mshv_root_hv_call.c | 101 +++-
drivers/hv/mshv_root_main.c | 259 +++++++++-
drivers/hv/mshv_vfio.c | 211 ++++++++
drivers/iommu/Kconfig | 1 +
drivers/iommu/hyperv/Kconfig | 15 +
drivers/iommu/hyperv/Makefile | 1 +
drivers/iommu/hyperv/hv-iommu-root.c | 704 +++++++++++++++++++++++++++
include/asm-generic/mshyperv.h | 8 +
include/hyperv/hvgdk_mini.h | 8 +
include/hyperv/hvhdk_mini.h | 67 +++
include/linux/hyperv.h | 6 +
include/uapi/linux/mshv.h | 31 ++
17 files changed, 1408 insertions(+), 48 deletions(-)
create mode 100644 drivers/hv/mshv_vfio.c
create mode 100644 drivers/iommu/hyperv/Kconfig
create mode 100644 drivers/iommu/hyperv/hv-iommu-root.c
--
2.51.2.vfs.0.1
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH V4 1/9] mshv: Provide a way to get partition ID if running in a VMM process
2026-07-18 2:19 [PATCH V4 0/9] PCI passthru on Hyper-V Mukesh R
@ 2026-07-18 2:19 ` Mukesh R
2026-07-18 2:32 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 2/9] mshv: Add declarations and definitions for VFIO-MSHV bridge device Mukesh R
` (7 subsequent siblings)
8 siblings, 1 reply; 19+ messages in thread
From: Mukesh R @ 2026-07-18 2:19 UTC (permalink / raw)
To: hpa, robin.murphy, robh, wei.liu, mrathor, mhklinux, muislam,
namjain, magnuskulke, anbelski, linux-kernel, linux-hyperv, iommu,
linux-pci, linux-arch
Cc: kys, haiyangz, decui, longli, tglx, mingo, bp, dave.hansen, x86,
joro, will, lpieralisi, kwilczynski, bhelgaas, arnd, jacob.pan
Many PCI passthru related hypercalls require partition ID of the target
guest. Guests are actually managed by MSHV driver and the partition ID
is only maintained there. Add a field in the partition struct in MSHV
driver to save the tgid of the VMM process creating the partition, and
add a function there to retrieve partition ID if the current process is
a VMM process.
Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
Reviewed-by: Anirudh Rayabharam (Microsoft) <anirudh@anirudhrb.com>
---
drivers/hv/mshv_root.h | 1 +
drivers/hv/mshv_root_main.c | 22 ++++++++++++++++++++++
include/asm-generic/mshyperv.h | 5 +++++
3 files changed, 28 insertions(+)
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 1f086dcb7aa1..a85c24dcc701 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -138,6 +138,7 @@ struct mshv_partition {
struct mshv_girq_routing_table __rcu *pt_girq_tbl;
u64 isolation_type;
+ pid_t pt_vmm_tgid;
bool import_completed;
bool pt_initialized;
#if IS_ENABLED(CONFIG_DEBUG_FS)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index bd1359eb58dd..02c107458be9 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -1908,6 +1908,27 @@ mshv_partition_release(struct inode *inode, struct file *filp)
return 0;
}
+/* Given a process tgid, return partition id if it is a VMM process */
+u64 mshv_current_partid(void)
+{
+ struct mshv_partition *pt;
+ int i;
+ u64 ret_ptid = HV_PARTITION_ID_INVALID;
+
+ rcu_read_lock();
+
+ hash_for_each_rcu(mshv_root.pt_htable, i, pt, pt_hnode) {
+ if (pt->pt_vmm_tgid == current->tgid) {
+ ret_ptid = pt->pt_id;
+ break;
+ }
+ }
+
+ rcu_read_unlock();
+ return ret_ptid;
+}
+EXPORT_SYMBOL_GPL(mshv_current_partid);
+
static int
add_partition(struct mshv_partition *partition)
{
@@ -2073,6 +2094,7 @@ mshv_ioctl_create_partition(void __user *user_arg, struct device *module_dev)
goto cleanup_irq_srcu;
partition->pt_id = pt_id;
+ partition->pt_vmm_tgid = current->tgid;
ret = add_partition(partition);
if (ret)
diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
index bf601d67cecb..e8cbc4e3f7ad 100644
--- a/include/asm-generic/mshyperv.h
+++ b/include/asm-generic/mshyperv.h
@@ -350,6 +350,7 @@ int hv_call_add_logical_proc(int node, u32 lp_index, u32 acpi_id);
int hv_call_notify_all_processors_started(void);
bool hv_lp_exists(u32 lp_index);
int hv_call_create_vp(int node, u64 partition_id, u32 vp_index, u32 flags);
+u64 mshv_current_partid(void);
#else /* CONFIG_MSHV_ROOT */
static inline bool hv_root_partition(void) { return false; }
@@ -380,6 +381,10 @@ static inline int hv_call_create_vp(int node, u64 partition_id, u32 vp_index, u3
{
return -EOPNOTSUPP;
}
+static inline u64 mshv_current_partid(void)
+{
+ return HV_PARTITION_ID_INVALID;
+}
#endif /* CONFIG_MSHV_ROOT */
static inline int hv_deposit_memory(u64 partition_id, u64 status)
--
2.51.2.vfs.0.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH V4 2/9] mshv: Add declarations and definitions for VFIO-MSHV bridge device
2026-07-18 2:19 [PATCH V4 0/9] PCI passthru on Hyper-V Mukesh R
2026-07-18 2:19 ` [PATCH V4 1/9] mshv: Provide a way to get partition ID if running in a VMM process Mukesh R
@ 2026-07-18 2:19 ` Mukesh R
2026-07-18 2:31 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 3/9] mshv: Introduce basic mshv bridge device for VFIO to build upon Mukesh R
` (6 subsequent siblings)
8 siblings, 1 reply; 19+ messages in thread
From: Mukesh R @ 2026-07-18 2:19 UTC (permalink / raw)
To: hpa, robin.murphy, robh, wei.liu, mrathor, mhklinux, muislam,
namjain, magnuskulke, anbelski, linux-kernel, linux-hyperv, iommu,
linux-pci, linux-arch
Cc: kys, haiyangz, decui, longli, tglx, mingo, bp, dave.hansen, x86,
joro, will, lpieralisi, kwilczynski, bhelgaas, arnd, jacob.pan
Add data structs needed by the subsequent patch that introduces a new
module to implement VFIO-MSHV pseudo device.
Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
Reviewed-by: Souradeep Chakrabarti <schakrabarti@linux.microsoft.com>
---
drivers/hv/mshv_root.h | 19 +++++++++++++++++++
include/uapi/linux/mshv.h | 30 ++++++++++++++++++++++++++++++
2 files changed, 49 insertions(+)
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index a85c24dcc701..b9880d0bdc4d 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -227,6 +227,25 @@ struct port_table_info {
};
};
+struct mshv_device {
+ const struct mshv_device_ops *device_ops;
+ struct mshv_partition *device_pt;
+ void *device_private;
+ struct hlist_node device_ptnode;
+};
+
+struct mshv_device_ops {
+ const char *device_name;
+ long (*device_create)(struct mshv_device *dev);
+ void (*device_release)(struct mshv_device *dev);
+ long (*device_set_attr)(struct mshv_device *dev,
+ struct mshv_device_attr *attr);
+ long (*device_has_attr)(struct mshv_device *dev,
+ struct mshv_device_attr *attr);
+};
+
+extern struct mshv_device_ops mshv_vfio_device_ops;
+
int mshv_update_routing_table(struct mshv_partition *partition,
const struct mshv_user_irq_entry *entries,
unsigned int numents);
diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h
index 32ff92b6342b..be6fe3ee8707 100644
--- a/include/uapi/linux/mshv.h
+++ b/include/uapi/linux/mshv.h
@@ -404,4 +404,34 @@ struct mshv_sint_mask {
/* hv_hvcall device */
#define MSHV_HVCALL_SETUP _IOW(MSHV_IOCTL, 0x1E, struct mshv_vtl_hvcall_setup)
#define MSHV_HVCALL _IOWR(MSHV_IOCTL, 0x1F, struct mshv_vtl_hvcall)
+
+/* Device passhthru */
+#define MSHV_CREATE_DEVICE_TEST 1
+
+enum {
+ MSHV_DEV_TYPE_VFIO,
+ MSHV_DEV_TYPE_MAX,
+};
+
+struct mshv_create_device {
+ __u32 type; /* in: MSHV_DEV_TYPE_xxx */
+ __u32 fd; /* out: device handle */
+ __u32 flags; /* in: MSHV_CREATE_DEVICE_xxx */
+};
+
+#define MSHV_DEV_VFIO_FILE 1
+#define MSHV_DEV_VFIO_FILE_ADD 1
+#define MSHV_DEV_VFIO_FILE_DEL 2
+
+struct mshv_device_attr {
+ __u32 flags; /* no flags currently defined */
+ __u32 group; /* device-defined */
+ __u64 attr; /* group-defined */
+ __u64 addr; /* userspace address of attr data */
+};
+
+/* Device fds created with MSHV_CREATE_DEVICE */
+#define MSHV_SET_DEVICE_ATTR _IOW(MSHV_IOCTL, 0x00, struct mshv_device_attr)
+#define MSHV_HAS_DEVICE_ATTR _IOW(MSHV_IOCTL, 0x01, struct mshv_device_attr)
+
#endif
--
2.51.2.vfs.0.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH V4 3/9] mshv: Introduce basic mshv bridge device for VFIO to build upon
2026-07-18 2:19 [PATCH V4 0/9] PCI passthru on Hyper-V Mukesh R
2026-07-18 2:19 ` [PATCH V4 1/9] mshv: Provide a way to get partition ID if running in a VMM process Mukesh R
2026-07-18 2:19 ` [PATCH V4 2/9] mshv: Add declarations and definitions for VFIO-MSHV bridge device Mukesh R
@ 2026-07-18 2:19 ` Mukesh R
2026-07-18 2:36 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 4/9] mshv: Add ioctl support for MSHV-VFIO bridge device Mukesh R
` (5 subsequent siblings)
8 siblings, 1 reply; 19+ messages in thread
From: Mukesh R @ 2026-07-18 2:19 UTC (permalink / raw)
To: hpa, robin.murphy, robh, wei.liu, mrathor, mhklinux, muislam,
namjain, magnuskulke, anbelski, linux-kernel, linux-hyperv, iommu,
linux-pci, linux-arch
Cc: kys, haiyangz, decui, longli, tglx, mingo, bp, dave.hansen, x86,
joro, will, lpieralisi, kwilczynski, bhelgaas, arnd, jacob.pan
Add a new file to implement basic VFIO-MSHV bridge pseudo device. These
functions are called in the VFIO framework, and credits to kvm/vfio.c
as this file was adapted from it. This is a basic version to build upon.
Co-developed-by: Wei Liu <wei.liu@kernel.org>
Signed-off-by: Wei Liu <wei.liu@kernel.org>
Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
---
drivers/hv/Makefile | 3 +-
drivers/hv/mshv_vfio.c | 211 ++++++++++++++++++++++++++++++++++++++
include/uapi/linux/mshv.h | 1 +
3 files changed, 214 insertions(+), 1 deletion(-)
create mode 100644 drivers/hv/mshv_vfio.c
diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index 888a748cc7cb..9ab6fc254c38 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -14,7 +14,8 @@ hv_vmbus-y := vmbus_drv.o \
hv_vmbus-$(CONFIG_HYPERV_TESTING) += hv_debugfs.o
hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_utils_transport.o
mshv_root-y := mshv_root_main.o mshv_synic.o mshv_eventfd.o mshv_irq.o \
- mshv_root_hv_call.o mshv_portid_table.o mshv_regions.o
+ mshv_root_hv_call.o mshv_portid_table.o mshv_regions.o \
+ mshv_vfio.o
mshv_root-$(CONFIG_DEBUG_FS) += mshv_debugfs.o
mshv_root-$(CONFIG_TRACEPOINTS) += mshv_trace.o
mshv_vtl-y := mshv_vtl_main.o
diff --git a/drivers/hv/mshv_vfio.c b/drivers/hv/mshv_vfio.c
new file mode 100644
index 000000000000..a770b508f080
--- /dev/null
+++ b/drivers/hv/mshv_vfio.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VFIO-MSHV bridge pseudo device
+ *
+ * Heavily inspired by the VFIO-KVM bridge pseudo device.
+ */
+#include <linux/errno.h>
+#include <linux/file.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/vfio.h>
+#include <asm/mshyperv.h>
+
+#include "mshv.h"
+#include "mshv_root.h"
+
+struct mshv_vfio_file {
+ struct list_head node;
+ struct file *file; /* list of struct mshv_vfio_file */
+};
+
+struct mshv_vfio {
+ struct list_head file_list;
+ struct mutex lock;
+};
+
+static bool mshv_vfio_file_is_valid(struct file *file)
+{
+ bool (*fn)(struct file *file);
+ bool ret;
+
+ fn = symbol_get(vfio_file_is_valid);
+ if (!fn)
+ return false;
+
+ ret = fn(file);
+
+ symbol_put(vfio_file_is_valid);
+
+ return ret;
+}
+
+static long mshv_vfio_file_add(struct mshv_device *mshvdev, unsigned int fd)
+{
+ struct mshv_vfio *mshv_vfio = mshvdev->device_private;
+ struct mshv_vfio_file *mvf;
+ struct file *filp;
+ long ret = 0;
+
+ filp = fget(fd);
+ if (!filp)
+ return -EBADF;
+
+ /* Ensure the FD is a vfio FD. */
+ if (!mshv_vfio_file_is_valid(filp)) {
+ ret = -EINVAL;
+ goto out_fput;
+ }
+
+ mutex_lock(&mshv_vfio->lock);
+
+ list_for_each_entry(mvf, &mshv_vfio->file_list, node) {
+ if (mvf->file == filp) {
+ ret = -EEXIST;
+ goto out_unlock;
+ }
+ }
+
+ mvf = kzalloc(sizeof(*mvf), GFP_KERNEL_ACCOUNT);
+ if (!mvf) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+
+ mvf->file = get_file(filp);
+ list_add_tail(&mvf->node, &mshv_vfio->file_list);
+
+out_unlock:
+ mutex_unlock(&mshv_vfio->lock);
+out_fput:
+ fput(filp);
+ return ret;
+}
+
+static long mshv_vfio_file_del(struct mshv_device *mshvdev, unsigned int fd)
+{
+ struct mshv_vfio *mshv_vfio = mshvdev->device_private;
+ struct mshv_vfio_file *mvf;
+ long ret;
+
+ CLASS(fd, f)(fd);
+
+ if (fd_empty(f))
+ return -EBADF;
+
+ ret = -ENOENT;
+ mutex_lock(&mshv_vfio->lock);
+
+ list_for_each_entry(mvf, &mshv_vfio->file_list, node) {
+ if (mvf->file != fd_file(f))
+ continue;
+
+ list_del(&mvf->node);
+ fput(mvf->file);
+ kfree(mvf);
+ ret = 0;
+ break;
+ }
+
+ mutex_unlock(&mshv_vfio->lock);
+ return ret;
+}
+
+static long mshv_vfio_set_file(struct mshv_device *mshvdev, long attr,
+ void __user *arg)
+{
+ int32_t __user *argp = arg;
+ int32_t fd;
+
+ switch (attr) {
+ case MSHV_DEV_VFIO_FILE_ADD:
+ if (get_user(fd, argp))
+ return -EFAULT;
+ return mshv_vfio_file_add(mshvdev, fd);
+
+ case MSHV_DEV_VFIO_FILE_DEL:
+ if (get_user(fd, argp))
+ return -EFAULT;
+ return mshv_vfio_file_del(mshvdev, fd);
+ }
+
+ return -ENXIO;
+}
+
+static long mshv_vfio_set_attr(struct mshv_device *mshvdev,
+ struct mshv_device_attr *attr)
+{
+ switch (attr->group) {
+ case MSHV_DEV_VFIO_FILE:
+ return mshv_vfio_set_file(mshvdev, attr->attr,
+ u64_to_user_ptr(attr->addr));
+ }
+
+ return -ENXIO;
+}
+
+static long mshv_vfio_has_attr(struct mshv_device *mshvdev,
+ struct mshv_device_attr *attr)
+{
+ switch (attr->group) {
+ case MSHV_DEV_VFIO_FILE:
+ switch (attr->attr) {
+ case MSHV_DEV_VFIO_FILE_ADD:
+ case MSHV_DEV_VFIO_FILE_DEL:
+ return 0;
+ }
+
+ break;
+ }
+
+ return -ENXIO;
+}
+
+static long mshv_vfio_create_device(struct mshv_device *mshvdev)
+{
+ struct mshv_device *tmp;
+ struct mshv_vfio *mshv_vfio;
+
+ /* Only one VFIO "device" per VM */
+ hlist_for_each_entry(tmp, &mshvdev->device_pt->pt_devices,
+ device_ptnode)
+ if (tmp->device_ops == &mshv_vfio_device_ops)
+ return -EBUSY;
+
+ mshv_vfio = kzalloc_obj(*mshv_vfio);
+ if (mshv_vfio == NULL)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&mshv_vfio->file_list);
+ mutex_init(&mshv_vfio->lock);
+
+ mshvdev->device_private = mshv_vfio;
+
+ return 0;
+}
+
+/* This is called from mshv_device_fop_release() */
+static void mshv_vfio_release_device(struct mshv_device *mshvdev)
+{
+ struct mshv_vfio *mv = mshvdev->device_private;
+ struct mshv_vfio_file *mvf, *tmp;
+
+ list_for_each_entry_safe(mvf, tmp, &mv->file_list, node) {
+ fput(mvf->file);
+ list_del(&mvf->node);
+ kfree(mvf);
+ }
+
+ kfree(mv);
+ kfree(mshvdev);
+}
+
+struct mshv_device_ops mshv_vfio_device_ops = {
+ .device_name = "mshv-vfio",
+ .device_create = mshv_vfio_create_device,
+ .device_release = mshv_vfio_release_device,
+ .device_set_attr = mshv_vfio_set_attr,
+ .device_has_attr = mshv_vfio_has_attr,
+};
diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h
index be6fe3ee8707..b038a79786d2 100644
--- a/include/uapi/linux/mshv.h
+++ b/include/uapi/linux/mshv.h
@@ -254,6 +254,7 @@ struct mshv_root_hvcall {
#define MSHV_GET_GPAP_ACCESS_BITMAP _IOWR(MSHV_IOCTL, 0x06, struct mshv_gpap_access_bitmap)
/* Generic hypercall */
#define MSHV_ROOT_HVCALL _IOWR(MSHV_IOCTL, 0x07, struct mshv_root_hvcall)
+#define MSHV_CREATE_DEVICE _IOWR(MSHV_IOCTL, 0x08, struct mshv_create_device)
/*
********************************
--
2.51.2.vfs.0.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH V4 4/9] mshv: Add ioctl support for MSHV-VFIO bridge device
2026-07-18 2:19 [PATCH V4 0/9] PCI passthru on Hyper-V Mukesh R
` (2 preceding siblings ...)
2026-07-18 2:19 ` [PATCH V4 3/9] mshv: Introduce basic mshv bridge device for VFIO to build upon Mukesh R
@ 2026-07-18 2:19 ` Mukesh R
2026-07-18 2:34 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 5/9] mshv: Import data structs around device passthru from hyperv headers Mukesh R
` (4 subsequent siblings)
8 siblings, 1 reply; 19+ messages in thread
From: Mukesh R @ 2026-07-18 2:19 UTC (permalink / raw)
To: hpa, robin.murphy, robh, wei.liu, mrathor, mhklinux, muislam,
namjain, magnuskulke, anbelski, linux-kernel, linux-hyperv, iommu,
linux-pci, linux-arch
Cc: kys, haiyangz, decui, longli, tglx, mingo, bp, dave.hansen, x86,
joro, will, lpieralisi, kwilczynski, bhelgaas, arnd, jacob.pan
Add ioctl support for MSHV devices for a partition. At present only VFIO
device types are supported, but more could be added. At a high level, a
partition ioctl to create device verifies it is of type VFIO and does some
setup for bridge code in mshv_vfio.c. Adapted from KVM device ioctls.
Co-developed-by: Wei Liu <wei.liu@kernel.org>
Signed-off-by: Wei Liu <wei.liu@kernel.org>
Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
---
drivers/hv/mshv_root_main.c | 119 ++++++++++++++++++++++++++++++++++++
1 file changed, 119 insertions(+)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 02c107458be9..a36e54bfa064 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -1625,6 +1625,122 @@ mshv_partition_ioctl_initialize(struct mshv_partition *partition)
return ret;
}
+static long mshv_device_attr_ioctl(struct mshv_device *mshv_dev, int cmd,
+ ulong uarg)
+{
+ struct mshv_device_attr attr;
+ const struct mshv_device_ops *devops = mshv_dev->device_ops;
+
+ if (copy_from_user(&attr, (void __user *)uarg, sizeof(attr)))
+ return -EFAULT;
+
+ switch (cmd) {
+ case MSHV_SET_DEVICE_ATTR:
+ if (devops->device_set_attr)
+ return devops->device_set_attr(mshv_dev, &attr);
+ break;
+ case MSHV_HAS_DEVICE_ATTR:
+ if (devops->device_has_attr)
+ return devops->device_has_attr(mshv_dev, &attr);
+ break;
+ }
+
+ return -EPERM;
+}
+
+static long mshv_device_fop_ioctl(struct file *filp, unsigned int cmd,
+ ulong uarg)
+{
+ struct mshv_device *mshv_dev = filp->private_data;
+
+ switch (cmd) {
+ case MSHV_SET_DEVICE_ATTR:
+ case MSHV_HAS_DEVICE_ATTR:
+ return mshv_device_attr_ioctl(mshv_dev, cmd, uarg);
+ }
+
+ return -ENOTTY;
+}
+
+static int mshv_device_fop_release(struct inode *inode, struct file *filp)
+{
+ struct mshv_device *mshv_dev = filp->private_data;
+ struct mshv_partition *partition = mshv_dev->device_pt;
+
+ if (mshv_dev->device_ops->device_release) {
+ mutex_lock(&partition->pt_mutex);
+ hlist_del(&mshv_dev->device_ptnode);
+ mshv_dev->device_ops->device_release(mshv_dev);
+ mutex_unlock(&partition->pt_mutex);
+ }
+
+ mshv_partition_put(partition);
+ return 0;
+}
+
+static const struct file_operations mshv_device_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = mshv_device_fop_ioctl,
+ .release = mshv_device_fop_release,
+};
+
+static long mshv_partition_ioctl_create_device(struct mshv_partition *partition,
+ void __user *uarg)
+{
+ long rc;
+ struct mshv_create_device devargk;
+ struct mshv_device *mshv_dev;
+ const struct mshv_device_ops *vfio_ops;
+
+ if (copy_from_user(&devargk, uarg, sizeof(devargk)))
+ return -EFAULT;
+
+ /* At present, only VFIO is supported */
+ if (devargk.type != MSHV_DEV_TYPE_VFIO)
+ return -ENODEV;
+
+ if (devargk.flags & MSHV_CREATE_DEVICE_TEST)
+ return 0;
+
+ if (devargk.flags) /* currently, only MSHV_CREATE_DEVICE_TEST */
+ return -EINVAL;
+
+ /* This is freed later by mshv_vfio_release_device() */
+ mshv_dev = kzalloc_obj(*mshv_dev);
+ if (mshv_dev == NULL)
+ return -ENOMEM;
+
+ vfio_ops = &mshv_vfio_device_ops;
+ mshv_dev->device_ops = vfio_ops;
+ mshv_dev->device_pt = partition;
+
+ rc = vfio_ops->device_create(mshv_dev);
+ if (rc < 0) {
+ kfree(mshv_dev);
+ return rc;
+ }
+
+ hlist_add_head(&mshv_dev->device_ptnode, &partition->pt_devices);
+
+ mshv_partition_get(partition);
+ rc = anon_inode_getfd(vfio_ops->device_name, &mshv_device_fops,
+ mshv_dev, O_RDWR | O_CLOEXEC);
+ if (rc < 0)
+ goto undo_out;
+
+ devargk.fd = rc;
+ if (copy_to_user(uarg, &devargk, sizeof(devargk)))
+ return -EFAULT; /* cleanup in mshv_device_fop_release() */
+
+ return 0;
+
+undo_out:
+ hlist_del(&mshv_dev->device_ptnode);
+ vfio_ops->device_release(mshv_dev); /* will kfree(mshv_dev) */
+ mshv_partition_put(partition);
+ return rc;
+}
+
static long
mshv_partition_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
@@ -1661,6 +1777,9 @@ mshv_partition_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
case MSHV_ROOT_HVCALL:
ret = mshv_ioctl_passthru_hvcall(partition, true, uarg);
break;
+ case MSHV_CREATE_DEVICE:
+ ret = mshv_partition_ioctl_create_device(partition, uarg);
+ break;
default:
ret = -ENOTTY;
}
--
2.51.2.vfs.0.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH V4 5/9] mshv: Import data structs around device passthru from hyperv headers
2026-07-18 2:19 [PATCH V4 0/9] PCI passthru on Hyper-V Mukesh R
` (3 preceding siblings ...)
2026-07-18 2:19 ` [PATCH V4 4/9] mshv: Add ioctl support for MSHV-VFIO bridge device Mukesh R
@ 2026-07-18 2:19 ` Mukesh R
2026-07-18 2:30 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 6/9] PCI: hv: Export hv_build_devid_type_pci() and change return type Mukesh R
` (3 subsequent siblings)
8 siblings, 1 reply; 19+ messages in thread
From: Mukesh R @ 2026-07-18 2:19 UTC (permalink / raw)
To: hpa, robin.murphy, robh, wei.liu, mrathor, mhklinux, muislam,
namjain, magnuskulke, anbelski, linux-kernel, linux-hyperv, iommu,
linux-pci, linux-arch
Cc: kys, haiyangz, decui, longli, tglx, mingo, bp, dave.hansen, x86,
joro, will, lpieralisi, kwilczynski, bhelgaas, arnd, jacob.pan
Copy/import from Hyper-V public headers, definitions and declarations that
are related to creating domains in the hypervisor, attaching devices to
them, etc.
Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
---
include/hyperv/hvgdk_mini.h | 8 +++++
include/hyperv/hvhdk_mini.h | 67 +++++++++++++++++++++++++++++++++++++
2 files changed, 75 insertions(+)
diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h
index 6a4e8b9d570f..e62f8d708e8e 100644
--- a/include/hyperv/hvgdk_mini.h
+++ b/include/hyperv/hvgdk_mini.h
@@ -326,6 +326,8 @@ union hv_hypervisor_version_info {
/* stimer Direct Mode is available */
#define HV_STIMER_DIRECT_MODE_AVAILABLE BIT(19)
+#define HV_DEVICE_DOMAIN_AVAILABLE BIT(24)
+
/*
* Implementation recommendations. Indicates which behaviors the hypervisor
* recommends the OS implement for optimal performance.
@@ -486,9 +488,15 @@ union hv_vp_assist_msr_contents { /* HV_REGISTER_VP_ASSIST_PAGE */
#define HVCALL_GET_VP_INDEX_FROM_APIC_ID 0x009a
#define HVCALL_FLUSH_GUEST_PHYSICAL_ADDRESS_SPACE 0x00af
#define HVCALL_FLUSH_GUEST_PHYSICAL_ADDRESS_LIST 0x00b0
+#define HVCALL_CREATE_DEVICE_DOMAIN 0x00b1
+#define HVCALL_ATTACH_DEVICE_DOMAIN 0x00b2
+#define HVCALL_MAP_DEVICE_GPA_PAGES 0x00b3
+#define HVCALL_UNMAP_DEVICE_GPA_PAGES 0x00b4
#define HVCALL_SIGNAL_EVENT_DIRECT 0x00c0
#define HVCALL_POST_MESSAGE_DIRECT 0x00c1
#define HVCALL_DISPATCH_VP 0x00c2
+#define HVCALL_DETACH_DEVICE_DOMAIN 0x00c4
+#define HVCALL_DELETE_DEVICE_DOMAIN 0x00c5
#define HVCALL_GET_GPA_PAGES_ACCESS_STATES 0x00c9
#define HVCALL_ACQUIRE_SPARSE_SPA_PAGE_HOST_ACCESS 0x00d7
#define HVCALL_RELEASE_SPARSE_SPA_PAGE_HOST_ACCESS 0x00d8
diff --git a/include/hyperv/hvhdk_mini.h b/include/hyperv/hvhdk_mini.h
index b4cb2fa26e9b..90497635c3cb 100644
--- a/include/hyperv/hvhdk_mini.h
+++ b/include/hyperv/hvhdk_mini.h
@@ -547,4 +547,71 @@ union hv_device_id { /* HV_DEVICE_ID */
} acpi;
} __packed;
+/* 3 domain types: stage 1, stage 2, and SOC */
+#define HV_DEVICE_DOMAIN_TYPE_S2 0 /* HV_DEVICE_DOMAIN_ID_TYPE_S2 */
+#define HV_DEVICE_DOMAIN_TYPE_S1 1 /* HV_DEVICE_DOMAIN_ID_TYPE_S1 */
+#define HV_DEVICE_DOMAIN_TYPE_SOC 2 /* HV_DEVICE_DOMAIN_ID_TYPE_SOC */
+
+/* ID for stage 2 default domain and NULL domain */
+#define HV_DEVICE_DOMAIN_ID_S2_DEFAULT 0
+#define HV_DEVICE_DOMAIN_ID_S2_NULL 0xFFFFFFFFULL
+
+union hv_device_domain_id {
+ u64 as_uint64;
+ struct {
+ u32 type : 4;
+ u32 reserved : 28;
+ u32 id;
+ };
+} __packed;
+
+struct hv_input_device_domain { /* HV_INPUT_DEVICE_DOMAIN */
+ u64 partition_id;
+ union hv_input_vtl owner_vtl;
+ u8 padding[7];
+ union hv_device_domain_id domain_id;
+} __packed;
+
+union hv_create_device_domain_flags { /* HV_CREATE_DEVICE_DOMAIN_FLAGS */
+ u32 as_uint32;
+ struct {
+ u32 forward_progress_required : 1;
+ u32 inherit_owning_vtl : 1;
+ u32 reserved : 30;
+ } __packed;
+} __packed;
+
+struct hv_input_create_device_domain { /* HV_INPUT_CREATE_DEVICE_DOMAIN */
+ struct hv_input_device_domain device_domain;
+ union hv_create_device_domain_flags create_device_domain_flags;
+} __packed;
+
+struct hv_input_delete_device_domain { /* HV_INPUT_DELETE_DEVICE_DOMAIN */
+ struct hv_input_device_domain device_domain;
+} __packed;
+
+struct hv_input_attach_device_domain { /* HV_INPUT_ATTACH_DEVICE_DOMAIN */
+ struct hv_input_device_domain device_domain;
+ union hv_device_id device_id;
+} __packed;
+
+struct hv_input_detach_device_domain { /* HV_INPUT_DETACH_DEVICE_DOMAIN */
+ u64 partition_id;
+ union hv_device_id device_id;
+} __packed;
+
+struct hv_input_map_device_gpa_pages { /* HV_INPUT_MAP_DEVICE_GPA_PAGES */
+ struct hv_input_device_domain device_domain;
+ union hv_input_vtl target_vtl;
+ u8 padding[3];
+ u32 map_flags;
+ u64 target_device_va_base;
+ u64 gpa_page_list[];
+} __packed;
+
+struct hv_input_unmap_device_gpa_pages { /* HV_INPUT_UNMAP_DEVICE_GPA_PAGES */
+ struct hv_input_device_domain device_domain;
+ u64 target_device_va_base;
+} __packed;
+
#endif /* _HV_HVHDK_MINI_H */
--
2.51.2.vfs.0.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH V4 6/9] PCI: hv: Export hv_build_devid_type_pci() and change return type
2026-07-18 2:19 [PATCH V4 0/9] PCI passthru on Hyper-V Mukesh R
` (4 preceding siblings ...)
2026-07-18 2:19 ` [PATCH V4 5/9] mshv: Import data structs around device passthru from hyperv headers Mukesh R
@ 2026-07-18 2:19 ` Mukesh R
2026-07-18 2:32 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 7/9] x86/hyperv: Implement Hyper-V virtual IOMMU Mukesh R
` (2 subsequent siblings)
8 siblings, 1 reply; 19+ messages in thread
From: Mukesh R @ 2026-07-18 2:19 UTC (permalink / raw)
To: hpa, robin.murphy, robh, wei.liu, mrathor, mhklinux, muislam,
namjain, magnuskulke, anbelski, linux-kernel, linux-hyperv, iommu,
linux-pci, linux-arch
Cc: kys, haiyangz, decui, longli, tglx, mingo, bp, dave.hansen, x86,
joro, will, lpieralisi, kwilczynski, bhelgaas, arnd, jacob.pan
On Hyper-V, most hypercalls related to PCI passthru to map/unmap regions,
interrupts, etc need a device ID as a parameter. This device ID refers
to that specific device during the lifetime of passthru. So,
make hv_build_devid_type_pci() public and change return type to u64 to
enforce it's size.
Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
Reviewed-by: Souradeep Chakrabarti <schakrabarti@linux.microsoft.com>
---
arch/x86/hyperv/irqdomain.c | 9 +++++----
arch/x86/include/asm/mshyperv.h | 6 ++++++
2 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/arch/x86/hyperv/irqdomain.c b/arch/x86/hyperv/irqdomain.c
index b3ad50a874dc..8780573a4332 100644
--- a/arch/x86/hyperv/irqdomain.c
+++ b/arch/x86/hyperv/irqdomain.c
@@ -112,7 +112,7 @@ static int get_rid_cb(struct pci_dev *pdev, u16 alias, void *data)
return 0;
}
-static union hv_device_id hv_build_devid_type_pci(struct pci_dev *pdev)
+u64 hv_build_devid_type_pci(struct pci_dev *pdev)
{
int pos;
union hv_device_id hv_devid;
@@ -172,8 +172,9 @@ static union hv_device_id hv_build_devid_type_pci(struct pci_dev *pdev)
}
out:
- return hv_devid;
+ return hv_devid.as_uint64;
}
+EXPORT_SYMBOL_GPL(hv_build_devid_type_pci);
/*
* hv_map_msi_interrupt() - Map the MSI IRQ in the hypervisor.
@@ -196,7 +197,7 @@ int hv_map_msi_interrupt(struct irq_data *data,
msidesc = irq_data_get_msi_desc(data);
pdev = msi_desc_to_pci_dev(msidesc);
- hv_devid = hv_build_devid_type_pci(pdev);
+ hv_devid.as_uint64 = hv_build_devid_type_pci(pdev);
cpu = cpumask_first(irq_data_get_effective_affinity_mask(data));
return hv_map_interrupt(hv_devid, false, cpu, cfg->vector,
@@ -271,7 +272,7 @@ static int hv_unmap_msi_interrupt(struct pci_dev *pdev,
{
union hv_device_id hv_devid;
- hv_devid = hv_build_devid_type_pci(pdev);
+ hv_devid.as_uint64 = hv_build_devid_type_pci(pdev);
return hv_unmap_interrupt(hv_devid.as_uint64, irq_entry);
}
diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
index f64393e853ee..8ebbd1cb7c8c 100644
--- a/arch/x86/include/asm/mshyperv.h
+++ b/arch/x86/include/asm/mshyperv.h
@@ -248,6 +248,12 @@ void hv_crash_asm_end(void);
static inline void hv_root_crash_init(void) {}
#endif /* CONFIG_MSHV_ROOT && CONFIG_CRASH_DUMP */
+#ifdef CONFIG_PCI_MSI
+u64 hv_build_devid_type_pci(struct pci_dev *pdev);
+#else
+static inline u64 hv_build_devid_type_pci(struct pci_dev *pdev) { return 0; }
+#endif
+
#else /* CONFIG_HYPERV */
static inline void hyperv_init(void) {}
static inline void hyperv_setup_mmu_ops(void) {}
--
2.51.2.vfs.0.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH V4 7/9] x86/hyperv: Implement Hyper-V virtual IOMMU
2026-07-18 2:19 [PATCH V4 0/9] PCI passthru on Hyper-V Mukesh R
` (5 preceding siblings ...)
2026-07-18 2:19 ` [PATCH V4 6/9] PCI: hv: Export hv_build_devid_type_pci() and change return type Mukesh R
@ 2026-07-18 2:19 ` Mukesh R
2026-07-18 2:34 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 8/9] mshv: Populate mmio mappings for PCI passthru Mukesh R
2026-07-18 2:19 ` [PATCH V4 9/9] mshv: Disable movable regions upfront if device passthru Mukesh R
8 siblings, 1 reply; 19+ messages in thread
From: Mukesh R @ 2026-07-18 2:19 UTC (permalink / raw)
To: hpa, robin.murphy, robh, wei.liu, mrathor, mhklinux, muislam,
namjain, magnuskulke, anbelski, linux-kernel, linux-hyperv, iommu,
linux-pci, linux-arch
Cc: kys, haiyangz, decui, longli, tglx, mingo, bp, dave.hansen, x86,
joro, will, lpieralisi, kwilczynski, bhelgaas, arnd, jacob.pan
Add a new file to implement a virtual IOMMU that works with Microsoft
Hyper-V hypervisor. The hypervisor claims the IOMMU upon boot, and this
driver communicates with it for creating and deleting paging domains,
mapping and unmapping of pages, etc. The default domain type is IDENTITY.
Lastly, this is only supported on baremetal or nested root/dom0, and not
L1VH root/dom0.
Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
---
arch/x86/kernel/pci-dma.c | 2 +
drivers/iommu/Kconfig | 1 +
drivers/iommu/hyperv/Kconfig | 15 +
drivers/iommu/hyperv/Makefile | 1 +
drivers/iommu/hyperv/hv-iommu-root.c | 704 +++++++++++++++++++++++++++
include/asm-generic/mshyperv.h | 3 +
include/linux/hyperv.h | 6 +
7 files changed, 732 insertions(+)
create mode 100644 drivers/iommu/hyperv/Kconfig
create mode 100644 drivers/iommu/hyperv/hv-iommu-root.c
diff --git a/arch/x86/kernel/pci-dma.c b/arch/x86/kernel/pci-dma.c
index 6267363e0189..37fed8c7a8c2 100644
--- a/arch/x86/kernel/pci-dma.c
+++ b/arch/x86/kernel/pci-dma.c
@@ -8,6 +8,7 @@
#include <linux/gfp.h>
#include <linux/pci.h>
#include <linux/amd-iommu.h>
+#include <linux/hyperv.h>
#include <asm/proto.h>
#include <asm/dma.h>
@@ -103,6 +104,7 @@ void __init pci_iommu_alloc(void)
}
pci_swiotlb_detect();
gart_iommu_hole_init();
+ hv_iommu_detect();
amd_iommu_detect();
detect_intel_iommu();
swiotlb_init(x86_swiotlb_enable, x86_swiotlb_flags);
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 1becc0f20222..e95a56a6e075 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -197,6 +197,7 @@ source "drivers/iommu/arm/Kconfig"
source "drivers/iommu/intel/Kconfig"
source "drivers/iommu/iommufd/Kconfig"
source "drivers/iommu/riscv/Kconfig"
+source "drivers/iommu/hyperv/Kconfig"
config IRQ_REMAP
bool "Support for Interrupt Remapping"
diff --git a/drivers/iommu/hyperv/Kconfig b/drivers/iommu/hyperv/Kconfig
new file mode 100644
index 000000000000..512b154b2f59
--- /dev/null
+++ b/drivers/iommu/hyperv/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# Hyper-V IOMMU support
+
+config HYPERV_IOMMU
+ bool "Hyper-V IOMMU Device"
+ depends on HYPERV && X86
+ select IOMMU_API
+ default HYPERV
+ help
+ This enables Hyper-V pseudo IOMMU device. When running as privileged
+ VM aka root on Microsoft Hyper-V hypervisor, this must be enabled
+ for doing any PCI passthru of devices to guest VMs. This applies to
+ both PFs and VFs. When enabling this, it is best to disable amd/intel
+ iommus via: intel_iommu=off amd_iommu=off as the hypervisor really
+ owns the iommu.
diff --git a/drivers/iommu/hyperv/Makefile b/drivers/iommu/hyperv/Makefile
index 6ef0ef97f3dd..aaced370a94f 100644
--- a/drivers/iommu/hyperv/Makefile
+++ b/drivers/iommu/hyperv/Makefile
@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_IRQ_REMAP) += hv-irq-remap-x86.o
+obj-$(CONFIG_HYPERV_IOMMU) += hv-iommu-root.o
diff --git a/drivers/iommu/hyperv/hv-iommu-root.c b/drivers/iommu/hyperv/hv-iommu-root.c
new file mode 100644
index 000000000000..374fa0c177c6
--- /dev/null
+++ b/drivers/iommu/hyperv/hv-iommu-root.c
@@ -0,0 +1,704 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hyper-V root vIOMMU driver.
+ * Copyright (C) 2026, Microsoft, Inc.
+ */
+#include <linux/pci.h>
+#include <linux/dma-map-ops.h>
+#include <linux/interval_tree.h>
+#include <linux/hyperv.h>
+#include <asm/iommu.h>
+#include <asm/mshyperv.h>
+#include "../dma-iommu.h"
+
+/* We will not claim these PCI devices, eg hypervisor needs it for debugger */
+static char *pci_devs_to_skip;
+static int __init hv_iommu_setup_skip(char *str)
+{
+ pci_devs_to_skip = str;
+
+ return 0;
+}
+/* hv_iommu_skip=(SSSS:BB:DD.F)(SSSS:BB:DD.F) */
+__setup("hv_iommu_skip=", hv_iommu_setup_skip);
+
+/* 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 */
+ u64 partid; /* partition id */
+ 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;
+ u32 flags;
+};
+
+/*
+ * By default, during boot the hypervisor creates one Stage 2 (S2) default
+ * domain. Stage 2 means that the page table is controlled by the hypervisor.
+ * It has two types:
+ * S2 default: access to entire root partition memory. This for us easily
+ * maps to IOMMU_DOMAIN_IDENTITY in the iommu subsystem, and
+ * is called HV_DEVICE_DOMAIN_ID_S2_DEFAULT in the hypervisor.
+ * S2 NULL: Blocks everything except RMRR
+ */
+
+/*
+ * Create dummy domains to correspond to hypervisor prebuilt default identity
+ * and null domains (dummy because we do not make hypercalls to create them).
+ */
+static struct hv_domain hv_def_identity_dom;
+static struct hv_domain hv_null_dom;
+
+static bool hv_special_domain(struct hv_domain *hvdom)
+{
+ return hvdom == &hv_def_identity_dom || hvdom == &hv_null_dom;
+}
+
+struct iommu_domain_geometry default_geometry = (struct iommu_domain_geometry) {
+ .aperture_start = 0,
+ .aperture_end = -1UL,
+ .force_aperture = true,
+};
+
+#define HV_IOMMU_PGSIZES SZ_4K /* for now, to be enhanced */
+
+static atomic_t hv_unique_id; /* unique numeric id for a new domain */
+
+/*
+ * If the current thread is a VMM thread, return the partition id of the VM it
+ * is managing, else return HV_PARTITION_ID_INVALID.
+ */
+static u64 hv_get_current_partid(void)
+{
+ u64 (*fn)(void);
+ u64 ptid;
+
+ fn = symbol_get(mshv_current_partid);
+ if (!fn)
+ return HV_PARTITION_ID_INVALID;
+
+ ptid = fn();
+ symbol_put(mshv_current_partid);
+
+ return ptid;
+}
+
+static bool hv_iommu_capable(struct device *dev, enum iommu_cap cap)
+{
+ switch (cap) {
+ case IOMMU_CAP_CACHE_COHERENCY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* Create a new device domain in the hypervisor */
+static int hv_iommu_create_hyp_devdom(struct hv_domain *hvdom)
+{
+ u64 status;
+ struct hv_input_device_domain *ddp;
+ struct hv_input_create_device_domain *input;
+ unsigned long flags;
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+
+ ddp = &input->device_domain;
+ ddp->partition_id = HV_PARTITION_ID_SELF;
+ ddp->domain_id.type = HV_DEVICE_DOMAIN_TYPE_S2;
+ ddp->domain_id.id = hvdom->domid_num;
+
+ input->create_device_domain_flags.forward_progress_required = 1;
+ input->create_device_domain_flags.inherit_owning_vtl = 0;
+
+ status = hv_do_hypercall(HVCALL_CREATE_DEVICE_DOMAIN, input, NULL);
+
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status, "\n");
+
+ return hv_result_to_errno(status);
+}
+
+static struct iommu_domain *hv_iommu_domain_alloc_paging(struct device *dev)
+{
+ struct hv_domain *hvdom;
+ int rc;
+ u32 unique_id;
+ u64 ptid = hv_get_current_partid();
+
+ if (ptid == HV_PARTITION_ID_INVALID)
+ return NULL;
+
+ hvdom = kzalloc_obj(struct hv_domain);
+ if (hvdom == NULL)
+ return NULL;
+
+ spin_lock_init(&hvdom->mappings_lock);
+ hvdom->mappings_tree = RB_ROOT_CACHED;
+
+ unique_id = (u32)atomic_inc_return(&hv_unique_id);
+ if (unique_id == HV_DEVICE_DOMAIN_ID_S2_NULL) /* ie, UINTMAX */
+ goto out_err;
+
+ hvdom->domid_num = unique_id;
+ hvdom->partid = ptid;
+ hvdom->iommu_dom.geometry = default_geometry;
+ hvdom->iommu_dom.pgsize_bitmap = HV_IOMMU_PGSIZES;
+
+ rc = hv_iommu_create_hyp_devdom(hvdom);
+ if (rc)
+ goto out_err;
+
+ return &hvdom->iommu_dom;
+
+out_err:
+ kfree(hvdom);
+ return NULL;
+}
+
+static void hv_iommu_domain_free(struct iommu_domain *immdom)
+{
+ unsigned long flags;
+ u64 status;
+ struct hv_input_delete_device_domain *input;
+ struct hv_input_device_domain *ddp;
+ struct hv_domain *hvdom = to_hv_domain(immdom);
+
+ if (hv_special_domain(hvdom))
+ return;
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ ddp = &input->device_domain;
+ memset(input, 0, sizeof(*input));
+
+ ddp->partition_id = HV_PARTITION_ID_SELF;
+ ddp->domain_id.type = HV_DEVICE_DOMAIN_TYPE_S2;
+ ddp->domain_id.id = hvdom->domid_num;
+
+ status = hv_do_hypercall(HVCALL_DELETE_DEVICE_DOMAIN, input,
+ NULL);
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status, "\n");
+
+ kfree(hvdom);
+}
+
+static void hv_iommu_det_dev_from_dom(struct pci_dev *pdev)
+{
+ u64 status;
+ unsigned long flags;
+ struct hv_input_detach_device_domain *input;
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+
+ input->partition_id = HV_PARTITION_ID_SELF;
+ input->device_id.as_uint64 = hv_build_devid_type_pci(pdev);
+ status = hv_do_hypercall(HVCALL_DETACH_DEVICE_DOMAIN, input, NULL);
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status, "\n");
+}
+
+static void hv_iommu_detach_dev(struct hv_domain *hvdom, struct device *dev)
+{
+ struct pci_dev *pdev;
+
+ /* See the attach function, only PCI devices for now */
+ if (!dev_is_pci(dev))
+ return;
+
+ pdev = to_pci_dev(dev);
+ hv_iommu_det_dev_from_dom(pdev);
+}
+
+/*
+ * Attach a device to the default domain, or the null domain, or to a domain
+ * previously created in the hypervisor.
+ */
+static int hv_iommu_att_dev2dom(struct hv_domain *hvdom, struct pci_dev *pdev)
+{
+ unsigned long flags;
+ u64 status;
+ struct hv_input_attach_device_domain *input;
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+
+ /* For null domain, hvdom->domid_num == HV_DEVICE_DOMAIN_ID_S2_NULL */
+ input->device_domain.partition_id = HV_PARTITION_ID_SELF;
+ input->device_domain.domain_id.type = HV_DEVICE_DOMAIN_TYPE_S2;
+ input->device_domain.domain_id.id = hvdom->domid_num;
+
+ /* NB: Upon guest shutdown, device is re-attached to the default domain
+ * without explicit detach.
+ */
+ input->device_id.as_uint64 = hv_build_devid_type_pci(pdev);
+
+ status = hv_do_hypercall(HVCALL_ATTACH_DEVICE_DOMAIN, input, NULL);
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status, "\n");
+
+ return hv_result_to_errno(status);
+}
+
+/* Attach a device for passthru to guest VMs, host apps like SPDK, etc */
+static int hv_iommu_attach_dev(struct iommu_domain *immdom, struct device *dev,
+ struct iommu_domain *old)
+{
+ struct pci_dev *pdev;
+ int rc;
+ struct hv_domain *hvdom_new = to_hv_domain(immdom);
+ struct hv_domain *hvdom_prev = to_hv_domain(old);
+
+ /* Only allow PCI devices for now */
+ if (!dev_is_pci(dev))
+ return -EINVAL;
+
+ pdev = to_pci_dev(dev);
+
+ /* There are no explicit detach calls, hence check first if we need
+ * to detach first. Also, in case of guest shutdown, it's the VMM
+ * thread that attaches it back to the hv_def_identity_dom, and
+ * hvdom_prev will not be null then. It is null during boot.
+ */
+ if (hvdom_prev && !hv_special_domain(hvdom_prev))
+ hv_iommu_detach_dev(hvdom_prev, dev);
+
+ rc = hv_iommu_att_dev2dom(hvdom_new, pdev);
+
+ if (rc == 0)
+ dev_iommu_priv_set(dev, hvdom_new); /* sets "private" field */
+ else
+ dev_iommu_priv_set(dev, NULL);
+
+ return rc;
+}
+
+static int hv_iommu_add_tree_mapping(struct hv_domain *hvdom,
+ unsigned long iova, phys_addr_t paddr,
+ size_t size, u32 flags)
+{
+ unsigned long irqflags;
+ struct hv_iommu_mapping *mapping;
+
+ mapping = kzalloc_obj(struct hv_iommu_mapping);
+ if (!mapping)
+ return -ENOMEM;
+
+ mapping->paddr = paddr;
+ mapping->iova.start = iova;
+ mapping->iova.last = iova + size - 1;
+ mapping->flags = flags;
+
+ spin_lock_irqsave(&hvdom->mappings_lock, irqflags);
+ interval_tree_insert(&mapping->iova, &hvdom->mappings_tree);
+ spin_unlock_irqrestore(&hvdom->mappings_lock, irqflags);
+
+ return 0;
+}
+
+static size_t hv_iommu_del_tree_mappings(struct hv_domain *hvdom,
+ unsigned long iova, size_t size)
+{
+ unsigned long flags;
+ size_t unmapped = 0;
+ unsigned long last = iova + size - 1;
+ struct hv_iommu_mapping *mapping = NULL;
+ struct interval_tree_node *node, *next;
+
+ spin_lock_irqsave(&hvdom->mappings_lock, flags);
+ next = interval_tree_iter_first(&hvdom->mappings_tree, iova, last);
+ while (next) {
+ node = next;
+ mapping = container_of(node, struct hv_iommu_mapping, iova);
+ next = interval_tree_iter_next(node, iova, last);
+
+ /* Trying to split a mapping? Not supported for now. */
+ if (mapping->iova.start < iova)
+ break;
+
+ unmapped += mapping->iova.last - mapping->iova.start + 1;
+
+ interval_tree_remove(node, &hvdom->mappings_tree);
+ kfree(mapping);
+ }
+ spin_unlock_irqrestore(&hvdom->mappings_lock, flags);
+
+ return unmapped;
+}
+
+static u64 hv_iommu_unmap_batch(u32 domid_num, ulong iova, u16 count)
+{
+ ulong flags;
+ struct hv_input_unmap_device_gpa_pages *input;
+ u64 status;
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+
+ input->device_domain.partition_id = HV_PARTITION_ID_SELF;
+ input->device_domain.domain_id.type = HV_DEVICE_DOMAIN_TYPE_S2;
+ input->device_domain.domain_id.id = domid_num;
+ input->target_device_va_base = iova;
+
+ status = hv_do_rep_hypercall(HVCALL_UNMAP_DEVICE_GPA_PAGES, count,
+ 0, input, NULL);
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status, "iova:0x%lx count:0x%x\n", iova, count);
+
+ return status;
+}
+
+static size_t hv_iommu_unmap_pages(struct iommu_domain *immdom, ulong iova,
+ size_t pgsize, size_t pgcount,
+ struct iommu_iotlb_gather *gather)
+{
+ unsigned long npages;
+ u64 status;
+ struct hv_domain *hvdom = to_hv_domain(immdom);
+ size_t unmapped, tot_done = 0, size = pgsize * pgcount;
+
+ unmapped = hv_iommu_del_tree_mappings(hvdom, iova, size);
+ if (unmapped < size) {
+ pr_err("%s: could not delete all mappings (%lx:%lx/%lx)\n",
+ __func__, iova, unmapped, size);
+ size = unmapped;
+ }
+
+ npages = size >> HV_HYP_PAGE_SHIFT;
+
+ while (npages) {
+ int done, count = min(npages, HV_REP_COUNT_MAX);
+
+ status = hv_iommu_unmap_batch(hvdom->domid_num, iova, count);
+
+ done = hv_repcomp(status);
+ tot_done += done;
+ npages -= done;
+ iova += done << HV_HYP_PAGE_SHIFT;
+
+ if (!hv_result_success(status))
+ break;
+ }
+
+ return tot_done << HV_HYP_PAGE_SHIFT;
+}
+
+/* Return: must return exact status from the hypercall without changes */
+static u64 hv_iommu_map_pgs(struct hv_domain *hvdom,
+ unsigned long iova, phys_addr_t paddr,
+ unsigned long npages, u32 map_flags)
+{
+ u64 status;
+ int i;
+ struct hv_input_map_device_gpa_pages *input;
+ unsigned long flags, pfn;
+
+ local_irq_save(flags);
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+
+ input->device_domain.partition_id = HV_PARTITION_ID_SELF;
+ input->device_domain.domain_id.type = HV_DEVICE_DOMAIN_TYPE_S2;
+ input->device_domain.domain_id.id = hvdom->domid_num;
+ input->map_flags = map_flags;
+ input->target_device_va_base = iova;
+
+ pfn = paddr >> HV_HYP_PAGE_SHIFT;
+ for (i = 0; i < npages; i++, pfn++)
+ input->gpa_page_list[i] = pfn;
+
+ status = hv_do_rep_hypercall(HVCALL_MAP_DEVICE_GPA_PAGES, npages, 0,
+ input, NULL);
+ local_irq_restore(flags);
+
+ return status;
+}
+
+#define HV_MAP_DEVICE_GPA_BATCH_SIZE \
+ ((HV_HYP_PAGE_SIZE - sizeof(struct hv_input_map_device_gpa_pages)) \
+ / sizeof(u64))
+
+/*
+ * The core VFIO code loops over memory ranges calling this function with the
+ * largest pgsize from HV_IOMMU_PGSIZES. cond_resched() is in vfio_iommu_map.
+ */
+static int hv_iommu_map_pages(struct iommu_domain *immdom, ulong iova,
+ phys_addr_t paddr, size_t pgsize, size_t pgcount,
+ int prot, gfp_t gfp, size_t *mapped)
+{
+ u32 map_flags;
+ int ret;
+ u64 status;
+ unsigned long npages, done = 0;
+ struct hv_domain *hvdom = to_hv_domain(immdom);
+ size_t size = pgsize * pgcount;
+
+ map_flags = HV_MAP_GPA_READABLE; /* required */
+ map_flags |= prot & IOMMU_WRITE ? HV_MAP_GPA_WRITABLE : 0;
+
+ ret = hv_iommu_add_tree_mapping(hvdom, iova, paddr, size, map_flags);
+ if (ret)
+ return ret;
+
+ npages = size >> HV_HYP_PAGE_SHIFT;
+ while (done < npages) {
+ ulong completed, remain = npages - done;
+
+ remain = min(remain, HV_MAP_DEVICE_GPA_BATCH_SIZE);
+
+ status = hv_iommu_map_pgs(hvdom, iova, paddr, remain,
+ map_flags);
+
+ completed = hv_repcomp(status);
+ done = done + completed;
+ iova = iova + (completed << HV_HYP_PAGE_SHIFT);
+ paddr = paddr + (completed << HV_HYP_PAGE_SHIFT);
+
+ if (hv_result(status) == HV_STATUS_INSUFFICIENT_MEMORY) {
+ ret = hv_call_deposit_pages(NUMA_NO_NODE,
+ hv_current_partition_id,
+ 256);
+ if (ret)
+ break;
+ continue;
+ }
+ if (!hv_result_success(status))
+ break;
+ }
+
+ if (!hv_result_success(status)) {
+ size_t done_size = done << HV_HYP_PAGE_SHIFT;
+
+ hv_status_err(status, "pgs:%lx/%lx iova:%lx\n",
+ done, npages, iova);
+ /*
+ * lookup tree has all mappings [0 - size-1]. Below unmap will
+ * only remove from [0 - done], we need to remove second chunk
+ * [done+1 - size-1].
+ */
+ hv_iommu_del_tree_mappings(hvdom, iova, size - done_size);
+ hv_iommu_unmap_pages(immdom, iova - done_size, HV_HYP_PAGE_SIZE,
+ done, NULL);
+ if (mapped)
+ *mapped = 0;
+ } else
+ if (mapped)
+ *mapped = size;
+
+ return hv_result_to_errno(status);
+}
+
+static phys_addr_t hv_iommu_iova_to_phys(struct iommu_domain *immdom,
+ dma_addr_t iova)
+{
+ unsigned long flags;
+ struct hv_iommu_mapping *mapping;
+ struct interval_tree_node *node;
+ u64 paddr = 0;
+ struct hv_domain *hvdom = to_hv_domain(immdom);
+
+ spin_lock_irqsave(&hvdom->mappings_lock, flags);
+ node = interval_tree_iter_first(&hvdom->mappings_tree, iova, iova);
+ if (node) {
+ mapping = container_of(node, struct hv_iommu_mapping, iova);
+ paddr = mapping->paddr + (iova - mapping->iova.start);
+ }
+ spin_unlock_irqrestore(&hvdom->mappings_lock, flags);
+
+ return paddr;
+}
+
+/*
+ * Currently, hypervisor does not provide list of devices it is using
+ * dynamically. So use this to allow users to manually specify devices that
+ * should be skipped. (eg. hypervisor debugger using some network device).
+ */
+static struct iommu_device *hv_iommu_probe_device(struct device *dev)
+{
+ if (!dev_is_pci(dev))
+ return ERR_PTR(-ENODEV);
+
+ if (pci_devs_to_skip && *pci_devs_to_skip) {
+ int rc, pos = 0;
+ int parsed;
+ int segment, bus, slot, func;
+ struct pci_dev *pdev = to_pci_dev(dev);
+
+ do {
+ parsed = 0;
+
+ rc = sscanf(pci_devs_to_skip + pos, " (%x:%x:%x.%x) %n",
+ &segment, &bus, &slot, &func, &parsed);
+ if (rc)
+ break;
+ if (parsed <= 0)
+ break;
+
+ if (pci_domain_nr(pdev->bus) == segment &&
+ pdev->bus->number == bus &&
+ PCI_SLOT(pdev->devfn) == slot &&
+ PCI_FUNC(pdev->devfn) == func) {
+
+ dev_info(dev, "skipped by Hyper-V IOMMU\n");
+ return ERR_PTR(-ENODEV);
+ }
+ pos += parsed;
+
+ } while (pci_devs_to_skip[pos]);
+ }
+
+ /* Device will be explicitly attached to the default domain, so no need
+ * to do dev_iommu_priv_set() here.
+ */
+
+ return &hv_virt_iommu;
+}
+
+static void hv_iommu_probe_finalize(struct device *dev)
+{
+ struct iommu_domain *immdom = iommu_get_domain_for_dev(dev);
+
+ if (immdom && immdom->type == IOMMU_DOMAIN_DMA)
+ iommu_setup_dma_ops(dev, immdom);
+ else
+ set_dma_ops(dev, NULL);
+}
+
+static void hv_iommu_release_device(struct device *dev)
+{
+ struct hv_domain *hvdom = dev_iommu_priv_get(dev);
+
+ /* Need to detach device from device domain if necessary. */
+ if (hvdom)
+ hv_iommu_detach_dev(hvdom, dev);
+
+ dev_iommu_priv_set(dev, NULL);
+ set_dma_ops(dev, NULL);
+}
+
+static struct iommu_group *hv_iommu_device_group(struct device *dev)
+{
+ if (dev_is_pci(dev))
+ return pci_device_group(dev);
+ else
+ return generic_device_group(dev);
+}
+
+static int hv_iommu_def_domain_type(struct device *dev)
+{
+ /* The hypervisor always creates this by default during boot */
+ return IOMMU_DOMAIN_IDENTITY;
+}
+
+static struct iommu_ops hv_iommu_ops = {
+ .capable = hv_iommu_capable,
+ .domain_alloc_paging = hv_iommu_domain_alloc_paging,
+ .probe_device = hv_iommu_probe_device,
+ .probe_finalize = hv_iommu_probe_finalize,
+ .release_device = hv_iommu_release_device,
+ .def_domain_type = hv_iommu_def_domain_type,
+ .device_group = hv_iommu_device_group,
+ .default_domain_ops = &(const struct iommu_domain_ops) {
+ .attach_dev = hv_iommu_attach_dev,
+ .map_pages = hv_iommu_map_pages,
+ .unmap_pages = hv_iommu_unmap_pages,
+ .iova_to_phys = hv_iommu_iova_to_phys,
+ .free = hv_iommu_domain_free,
+ },
+ .owner = THIS_MODULE,
+ .identity_domain = &hv_def_identity_dom.iommu_dom,
+ .blocked_domain = &hv_null_dom.iommu_dom,
+};
+
+static const struct iommu_domain_ops hv_special_domain_ops = {
+ .attach_dev = hv_iommu_attach_dev,
+};
+
+static void __init hv_initialize_special_domains(void)
+{
+ hv_def_identity_dom.iommu_dom.type = IOMMU_DOMAIN_IDENTITY;
+ hv_def_identity_dom.iommu_dom.ops = &hv_special_domain_ops;
+ hv_def_identity_dom.iommu_dom.owner = &hv_iommu_ops;
+ hv_def_identity_dom.iommu_dom.geometry = default_geometry;
+ hv_def_identity_dom.domid_num = HV_DEVICE_DOMAIN_ID_S2_DEFAULT; /* 0 */
+
+ hv_null_dom.iommu_dom.type = IOMMU_DOMAIN_BLOCKED;
+ hv_null_dom.iommu_dom.ops = &hv_special_domain_ops;
+ hv_null_dom.iommu_dom.owner = &hv_iommu_ops;
+ hv_null_dom.iommu_dom.geometry = default_geometry;
+ hv_null_dom.domid_num = HV_DEVICE_DOMAIN_ID_S2_NULL; /* INTMAX */
+}
+
+static int __init hv_iommu_init(void)
+{
+ int ret;
+ struct iommu_device *iommup = &hv_virt_iommu;
+
+ if (!hv_is_hyperv_initialized())
+ return -ENODEV;
+
+ ret = iommu_device_sysfs_add(iommup, NULL, NULL, "%s", "hyperv-iommu");
+ if (ret) {
+ pr_err("Hyper-V: iommu_device_sysfs_add failed: %d\n", ret);
+ return ret;
+ }
+
+ /* This must come before iommu_device_register() because the latter
+ * calls into the hooks.
+ */
+ hv_initialize_special_domains();
+
+ ret = iommu_device_register(iommup, &hv_iommu_ops, NULL);
+ if (ret) {
+ pr_err("Hyper-V: iommu_device_register failed: %d\n", ret);
+ goto err_sysfs_remove;
+ }
+
+ pr_info("Hyper-V IOMMU initialized\n");
+
+ return 0;
+
+err_sysfs_remove:
+ iommu_device_sysfs_remove(iommup);
+ return ret;
+}
+
+void __init hv_iommu_detect(void)
+{
+ if (no_iommu || iommu_detected || hv_l1vh_partition())
+ return;
+
+ if (!(ms_hyperv.misc_features & HV_DEVICE_DOMAIN_AVAILABLE))
+ return;
+
+ iommu_detected = 1;
+ x86_init.iommu.iommu_init = hv_iommu_init;
+
+ pci_request_acs();
+}
diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
index e8cbc4e3f7ad..2c5d0812f19d 100644
--- a/include/asm-generic/mshyperv.h
+++ b/include/asm-generic/mshyperv.h
@@ -28,6 +28,9 @@
#define VTPM_BASE_ADDRESS 0xfed40000
+#define HV_REP_COUNT_MAX \
+ (HV_HYPERCALL_REP_COMP_MASK >> HV_HYPERCALL_REP_COMP_OFFSET)
+
enum hv_partition_type {
HV_PARTITION_TYPE_GUEST,
HV_PARTITION_TYPE_ROOT,
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index 964f1be8150c..f78645e8d776 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -1769,4 +1769,10 @@ static inline unsigned long virt_to_hvpfn(void *addr)
#define HVPFN_DOWN(x) ((x) >> HV_HYP_PAGE_SHIFT)
#define page_to_hvpfn(page) (page_to_pfn(page) * NR_HV_HYP_PAGES_IN_PAGE)
+#ifdef CONFIG_HYPERV_IOMMU
+void __init hv_iommu_detect(void);
+#else
+static inline void hv_iommu_detect(void) { }
+#endif /* CONFIG_HYPERV_IOMMU */
+
#endif /* _HYPERV_H */
--
2.51.2.vfs.0.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH V4 8/9] mshv: Populate mmio mappings for PCI passthru
2026-07-18 2:19 [PATCH V4 0/9] PCI passthru on Hyper-V Mukesh R
` (6 preceding siblings ...)
2026-07-18 2:19 ` [PATCH V4 7/9] x86/hyperv: Implement Hyper-V virtual IOMMU Mukesh R
@ 2026-07-18 2:19 ` Mukesh R
2026-07-18 2:33 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 9/9] mshv: Disable movable regions upfront if device passthru Mukesh R
8 siblings, 1 reply; 19+ messages in thread
From: Mukesh R @ 2026-07-18 2:19 UTC (permalink / raw)
To: hpa, robin.murphy, robh, wei.liu, mrathor, mhklinux, muislam,
namjain, magnuskulke, anbelski, linux-kernel, linux-hyperv, iommu,
linux-pci, linux-arch
Cc: kys, haiyangz, decui, longli, tglx, mingo, bp, dave.hansen, x86,
joro, will, lpieralisi, kwilczynski, bhelgaas, arnd, jacob.pan
Upon guest accesses, in case of missing mmio mappings, the hypervisor
generates unmapped gpa intercepts. In this path, lookup the PCI
resource pfn for the guest gpa, and ask the hypervisor to map it
via hypercall. The PCI resource pfn is maintained by the VFIO driver,
and obtained via fixup_user_fault() call (similar to KVM). Try to use
2M page size as much as possible for significant performance gains.
Also, remove existing code that is using vma->vm_pgoff to map mmio
space, it is broken and will cause panics.
Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
---
drivers/hv/mshv_root.h | 3 +-
drivers/hv/mshv_root_hv_call.c | 101 ++++++++++++++++++++++++--------
drivers/hv/mshv_root_main.c | 103 +++++++++++++++++++++++++++------
3 files changed, 165 insertions(+), 42 deletions(-)
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index b9880d0bdc4d..c97a1b5da9ad 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -301,7 +301,8 @@ int hv_call_create_partition(u64 flags,
int hv_call_initialize_partition(u64 partition_id);
int hv_call_finalize_partition(u64 partition_id);
int hv_call_delete_partition(u64 partition_id);
-int hv_call_map_mmio_pages(u64 partition_id, u64 gfn, u64 mmio_spa, u64 numpgs);
+int hv_map_mmio_pages(u64 partition_id, struct mshv_mem_region *reg, u64 gfn,
+ u64 mmio_mfn);
int hv_call_map_gpa_pages(u64 partition_id, u64 gpa_target, u64 page_count,
u32 flags, struct page **pages);
int hv_call_unmap_gpa_pages(u64 partition_id, u64 gpa_target, u64 page_count,
diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
index cb55d4d4be2e..937225c60473 100644
--- a/drivers/hv/mshv_root_hv_call.c
+++ b/drivers/hv/mshv_root_hv_call.c
@@ -189,31 +189,33 @@ int hv_call_delete_partition(u64 partition_id)
}
/* Ask the hypervisor to map guest ram pages or the guest mmio space */
-static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_struct_count,
- u32 flags, struct page **pages, u64 mmio_spa)
+static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_count,
+ u32 flags, struct page **pages, u64 mmio_mfn)
{
struct hv_input_map_gpa_pages *input_page;
u64 status, *pfnlist;
unsigned long irq_flags, large_shift = 0;
- int ret = 0, done = 0;
- u64 page_count = page_struct_count;
-
- if (page_count == 0 || (pages && mmio_spa))
- return -EINVAL;
+ int i, ret = 0, done = 0;
+ u64 adj_page_count = page_count;
- if (flags & HV_MAP_GPA_LARGE_PAGE) {
- if (mmio_spa)
+ if (mmio_mfn) {
+ if (pages)
return -EINVAL;
+ for (i = 0; i < page_count; i++)
+ if (page_is_ram(mmio_mfn + i))
+ return -EINVAL;
+ }
+ if (flags & HV_MAP_GPA_LARGE_PAGE) {
if (!HV_PAGE_COUNT_2M_ALIGNED(page_count))
return -EINVAL;
large_shift = HV_HYP_LARGE_PAGE_SHIFT - HV_HYP_PAGE_SHIFT;
- page_count >>= large_shift;
+ adj_page_count >>= large_shift;
}
- while (done < page_count) {
- ulong i, completed, remain = page_count - done;
+ while (done < adj_page_count) {
+ ulong i, completed, remain = adj_page_count - done;
int rep_count = min(remain, HV_MAP_GPA_BATCH_SIZE);
local_irq_save(irq_flags);
@@ -230,13 +232,14 @@ static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_struct_count,
} else if (pages) {
u64 index = (done + i) << large_shift;
- if (index >= page_struct_count) {
+ if (index >= page_count) {
ret = -EINVAL;
break;
}
pfnlist[i] = page_to_pfn(pages[index]);
} else {
- pfnlist[i] = mmio_spa + done + i;
+ pfnlist[i] = mmio_mfn +
+ ((done + i) << large_shift);
}
if (ret)
break;
@@ -254,6 +257,9 @@ static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_struct_count,
break;
} else if (!hv_result_success(status)) {
+ pr_err("%s: failed to map pages at gfn %#llx: completed %u/%llu, flags=%#x, status=%#llx (%s)\n",
+ __func__, gfn, done, page_count, flags, status,
+ hv_result_to_string(hv_result(status)));
ret = hv_result_to_errno(status);
break;
}
@@ -280,19 +286,66 @@ int hv_call_map_gpa_pages(u64 partition_id, u64 gpa_target, u64 page_count,
flags, pages, 0);
}
-/* Ask the hypervisor to map guest mmio space */
-int hv_call_map_mmio_pages(u64 partition_id, u64 gfn, u64 mmio_spa, u64 numpgs)
+/*
+ * Ask the hypervisor to map guest mmio space. Don't set HV_MAP_GPA_NOT_CACHED
+ * in hcall flags for slightly better performance, and in that case the guest
+ * stage 1 page table will control caching.
+ */
+int hv_map_mmio_pages(u64 partition_id, struct mshv_mem_region *reg, u64 gfn,
+ u64 mmio_mfn)
{
- int i;
- u32 flags = HV_MAP_GPA_READABLE | HV_MAP_GPA_WRITABLE |
- HV_MAP_GPA_NOT_CACHED;
+ int rc;
+ u32 flags = HV_MAP_GPA_READABLE;
+ u64 hpages, numpgs, numpgs_in_hpage = HPAGE_SIZE / PAGE_SIZE;
+
+ if (reg->hv_map_flags & HV_MAP_GPA_WRITABLE)
+ flags |= HV_MAP_GPA_WRITABLE;
+ if (reg->hv_map_flags & HV_MAP_GPA_EXECUTABLE)
+ flags |= HV_MAP_GPA_EXECUTABLE;
+
+ mmio_mfn = mmio_mfn - (gfn - reg->start_gfn); /* start of the range */
+
+ numpgs = 0;
+ gfn = reg->start_gfn;
+ while (!HV_PAGE_COUNT_2M_ALIGNED(gfn) &&
+ !HV_PAGE_COUNT_2M_ALIGNED(mmio_mfn) &&
+ numpgs < reg->nr_pages) {
+ numpgs++;
+ gfn++;
+ }
- for (i = 0; i < numpgs; i++)
- if (page_is_ram(mmio_spa + i))
- return -EINVAL;
+ if (numpgs) {
+ rc = hv_do_map_gpa_hcall(partition_id, reg->start_gfn, numpgs,
+ flags, NULL, mmio_mfn);
+ if (rc || numpgs == reg->nr_pages)
+ return rc;
+ }
+
+ mmio_mfn = mmio_mfn + numpgs;
+ numpgs = reg->nr_pages - numpgs;
+
+ if (numpgs < numpgs_in_hpage)
+ return hv_do_map_gpa_hcall(partition_id, gfn, numpgs, flags,
+ NULL, mmio_mfn);
+
+ for (hpages = 0; numpgs >= numpgs_in_hpage;) {
+ hpages++;
+ numpgs = numpgs - numpgs_in_hpage;
+ }
+ rc = hv_do_map_gpa_hcall(partition_id, gfn, hpages * numpgs_in_hpage,
+ flags | HV_MAP_GPA_LARGE_PAGE, NULL, mmio_mfn);
+ if (rc)
+ return rc;
+
+ if (numpgs) {
+ gfn = gfn + hpages * numpgs_in_hpage;
+ mmio_mfn = mmio_mfn + hpages * numpgs_in_hpage;
+
+ rc = hv_do_map_gpa_hcall(partition_id, gfn, numpgs, flags, NULL,
+ mmio_mfn);
+ }
- return hv_do_map_gpa_hcall(partition_id, gfn, numpgs, flags, NULL,
- mmio_spa);
+ return rc;
}
int hv_call_unmap_gpa_pages(u64 partition_id, u64 gfn, u64 page_count_4k,
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index a36e54bfa064..6bce4123c5ff 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -641,6 +641,87 @@ mshv_partition_region_by_gfn_get(struct mshv_partition *p, u64 gfn)
return region;
}
+/*
+ * Check if uaddr is for mmio range. If yes, return 0 with mmio_pfn filled in
+ * else just return -errno.
+ */
+static int mshv_chk_get_mmio_start_pfn(u64 uaddr, u64 *mmio_pfnp)
+{
+ struct vm_area_struct *vma;
+ bool is_mmio;
+ struct follow_pfnmap_args pfnmap_args;
+ int rc = -EINVAL;
+
+ mmap_read_lock(current->mm);
+ vma = vma_lookup(current->mm, uaddr);
+ is_mmio = vma ? !!(vma->vm_flags & (VM_IO | VM_PFNMAP)) : 0;
+ if (!is_mmio)
+ goto unlock_mmap_out;
+
+ pfnmap_args.vma = vma;
+ pfnmap_args.address = uaddr;
+
+ rc = follow_pfnmap_start(&pfnmap_args);
+ if (rc) {
+ rc = fixup_user_fault(current->mm, uaddr, FAULT_FLAG_WRITE,
+ NULL);
+ if (rc)
+ goto unlock_mmap_out;
+
+ rc = follow_pfnmap_start(&pfnmap_args);
+ if (rc)
+ goto unlock_mmap_out;
+ }
+
+ *mmio_pfnp = pfnmap_args.pfn;
+ follow_pfnmap_end(&pfnmap_args);
+
+unlock_mmap_out:
+ mmap_read_unlock(current->mm);
+ return rc;
+}
+
+/*
+ * Check if the unmapped gpa belongs to mmio space. If yes, resolve it.
+ *
+ * Returns: True if valid mmio intercept and handled, else false.
+ */
+static bool mshv_handle_unmapped_gpa(struct mshv_vp *vp)
+{
+ struct hv_message *hvmsg = vp->vp_intercept_msg_page;
+ u64 gfn, uaddr, mmio_mfn;
+ struct mshv_mem_region *rg;
+ int rc = -EINVAL;
+ struct mshv_partition *pt = vp->vp_partition;
+#if defined(CONFIG_X86_64)
+ struct hv_x64_memory_intercept_message *msg =
+ (struct hv_x64_memory_intercept_message *)hvmsg->u.payload;
+#elif defined(CONFIG_ARM64)
+ struct hv_arm64_memory_intercept_message *msg =
+ (struct hv_arm64_memory_intercept_message *)hvmsg->u.payload;
+#endif
+
+ gfn = msg->guest_physical_address >> HV_HYP_PAGE_SHIFT;
+
+ rg = mshv_partition_region_by_gfn_get(pt, gfn);
+ if (rg == NULL)
+ return false;
+ if (rg->mreg_type != MSHV_REGION_TYPE_MMIO)
+ goto put_rg_out;
+
+ uaddr = rg->start_uaddr + ((gfn - rg->start_gfn) << HV_HYP_PAGE_SHIFT);
+
+ rc = mshv_chk_get_mmio_start_pfn(uaddr, &mmio_mfn);
+ if (rc)
+ goto put_rg_out;
+
+ rc = hv_map_mmio_pages(pt->pt_id, rg, gfn, mmio_mfn);
+
+put_rg_out:
+ mshv_region_put(rg);
+ return rc == 0;
+}
+
/**
* mshv_handle_gpa_intercept - Handle GPA (Guest Physical Address) intercepts.
* @vp: Pointer to the virtual processor structure.
@@ -699,6 +780,8 @@ static bool mshv_handle_gpa_intercept(struct mshv_vp *vp)
static bool mshv_vp_handle_intercept(struct mshv_vp *vp)
{
switch (vp->vp_intercept_msg_page->header.message_type) {
+ case HVMSG_UNMAPPED_GPA:
+ return mshv_handle_unmapped_gpa(vp);
case HVMSG_GPA_INTERCEPT:
return mshv_handle_gpa_intercept(vp);
}
@@ -1322,16 +1405,8 @@ static int mshv_prepare_pinned_region(struct mshv_mem_region *region)
}
/*
- * This maps two things: guest RAM and for pci passthru mmio space.
- *
- * mmio:
- * - vfio overloads vm_pgoff to store the mmio start pfn/spa.
- * - Two things need to happen for mapping mmio range:
- * 1. mapped in the uaddr so VMM can access it.
- * 2. mapped in the hwpt (gfn <-> mmio phys addr) so guest can access it.
- *
- * This function takes care of the second. The first one is managed by vfio,
- * and hence is taken care of via vfio_pci_mmap_fault().
+ * This is called for both user ram and mmio space. The mmio space is not
+ * mapped here, but later during intercept on demand.
*/
static long
mshv_map_user_memory(struct mshv_partition *partition,
@@ -1340,7 +1415,6 @@ mshv_map_user_memory(struct mshv_partition *partition,
struct mshv_mem_region *region;
struct vm_area_struct *vma;
bool is_mmio;
- ulong mmio_pfn;
long ret;
if (mem->flags & BIT(MSHV_SET_MEM_BIT_UNMAP) ||
@@ -1350,7 +1424,6 @@ mshv_map_user_memory(struct mshv_partition *partition,
mmap_read_lock(current->mm);
vma = vma_lookup(current->mm, mem->userspace_addr);
is_mmio = vma ? !!(vma->vm_flags & (VM_IO | VM_PFNMAP)) : 0;
- mmio_pfn = is_mmio ? vma->vm_pgoff : 0;
mmap_read_unlock(current->mm);
if (!vma)
@@ -1376,11 +1449,7 @@ mshv_map_user_memory(struct mshv_partition *partition,
region->nr_pages,
HV_MAP_GPA_NO_ACCESS, NULL);
break;
- case MSHV_REGION_TYPE_MMIO:
- ret = hv_call_map_mmio_pages(partition->pt_id,
- region->start_gfn,
- mmio_pfn,
- region->nr_pages);
+ default:
break;
}
--
2.51.2.vfs.0.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH V4 9/9] mshv: Disable movable regions upfront if device passthru
2026-07-18 2:19 [PATCH V4 0/9] PCI passthru on Hyper-V Mukesh R
` (7 preceding siblings ...)
2026-07-18 2:19 ` [PATCH V4 8/9] mshv: Populate mmio mappings for PCI passthru Mukesh R
@ 2026-07-18 2:19 ` Mukesh R
2026-07-18 2:40 ` sashiko-bot
8 siblings, 1 reply; 19+ messages in thread
From: Mukesh R @ 2026-07-18 2:19 UTC (permalink / raw)
To: hpa, robin.murphy, robh, wei.liu, mrathor, mhklinux, muislam,
namjain, magnuskulke, anbelski, linux-kernel, linux-hyperv, iommu,
linux-pci, linux-arch
Cc: kys, haiyangz, decui, longli, tglx, mingo, bp, dave.hansen, x86,
joro, will, lpieralisi, kwilczynski, bhelgaas, arnd, jacob.pan
If a VM is started with device attached, the mem regions must be marked
non-movable as the device attach hypercall right away allows the use of
SLAT for IOMMU. Marking them non-movable forces mapping of the entire
guest RAM in the SLAT at the time of region creation along with the region
pinned. Also, because a device could be dynamically attached much later
in a VM, create a boot parameter to allow a user to pin them upfront
if they anticipate such an action. It is understood that VFIO pins the
regions also, so there will be double pinning. This will be enhanced in
the near future with a third region type that is not movable nor pinned.
Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
---
drivers/hv/mshv_root.h | 1 +
drivers/hv/mshv_root_main.c | 15 ++++++++++++++-
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index c97a1b5da9ad..0282f489ad90 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -141,6 +141,7 @@ struct mshv_partition {
pid_t pt_vmm_tgid;
bool import_completed;
bool pt_initialized;
+ bool pt_regions_pinned;
#if IS_ENABLED(CONFIG_DEBUG_FS)
struct dentry *pt_stats_dentry;
struct dentry *pt_vp_dentry;
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 6bce4123c5ff..d7cc131652af 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -46,6 +46,10 @@ MODULE_DESCRIPTION("Microsoft Hyper-V root partition VMM interface /dev/mshv");
#define HV_VP_COUNTER_ROOT_DISPATCH_THREAD_BLOCKED 95
#endif
+static uint hv_memreg; /* mshv_mem_region allocations type */
+module_param(hv_memreg, uint, 0644);
+MODULE_PARM_DESC(hv_memreg, "0: movable (default), 1: pinned");
+
struct mshv_root mshv_root;
enum hv_scheduler_type hv_scheduler_type;
@@ -1293,6 +1297,12 @@ static void mshv_async_hvcall_handler(void *data, u64 *status)
*status = partition->async_hypercall_status;
}
+static bool mshv_do_pt_regions_pinned(struct mshv_partition *pt)
+{
+ return pt->pt_regions_pinned || mshv_partition_encrypted(pt) ||
+ hv_memreg == 1;
+}
+
/*
* NB: caller checks and makes sure mem->size is page aligned
* Returns: 0 with regionpp updated on success, or -errno
@@ -1323,7 +1333,7 @@ static int mshv_partition_create_region(struct mshv_partition *partition,
if (is_mmio)
rg->mreg_type = MSHV_REGION_TYPE_MMIO;
- else if (mshv_partition_encrypted(partition) ||
+ else if (mshv_do_pt_regions_pinned(partition) ||
!mshv_region_movable_init(rg))
rg->mreg_type = MSHV_REGION_TYPE_MEM_PINNED;
else
@@ -1801,6 +1811,9 @@ static long mshv_partition_ioctl_create_device(struct mshv_partition *partition,
if (copy_to_user(uarg, &devargk, sizeof(devargk)))
return -EFAULT; /* cleanup in mshv_device_fop_release() */
+ /* For now, just mark regions pinned if there is device passthru. */
+ partition->pt_regions_pinned = true;
+
return 0;
undo_out:
--
2.51.2.vfs.0.1
^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH V4 5/9] mshv: Import data structs around device passthru from hyperv headers
2026-07-18 2:19 ` [PATCH V4 5/9] mshv: Import data structs around device passthru from hyperv headers Mukesh R
@ 2026-07-18 2:30 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-18 2:30 UTC (permalink / raw)
To: Mukesh R; +Cc: linux-hyperv, linux-pci
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The memory layout of `union hv_device_domain_id` is inverted compared to the expected Hyper-V ABI, resulting in corrupted device domain IDs being passed to the hypervisor.
--
--- Patch [5]: [PATCH V4 5/9] mshv: Import data structs around device passthru from hyperv headers ---
commit 02ed2cd292e85f0b6e754b6a29df473f95394315
Author: Mukesh R <mrathor@linux.microsoft.com>
mshv: Import data structs around device passthru from hyperv headers
This commit copies and imports definitions and declarations from Hyper-V
public headers. These additions are related to creating domains in the
hypervisor and attaching devices to them.
> diff --git a/include/hyperv/hvhdk_mini.h b/include/hyperv/hvhdk_mini.h
> index b4cb2fa26e9bc..90497635c3cbd 100644
> --- a/include/hyperv/hvhdk_mini.h
> +++ b/include/hyperv/hvhdk_mini.h
[ ... ]
> +/* 3 domain types: stage 1, stage 2, and SOC */
> +#define HV_DEVICE_DOMAIN_TYPE_S2 0 /* HV_DEVICE_DOMAIN_ID_TYPE_S2 */
> +#define HV_DEVICE_DOMAIN_TYPE_S1 1 /* HV_DEVICE_DOMAIN_ID_TYPE_S1 */
> +#define HV_DEVICE_DOMAIN_TYPE_SOC 2 /* HV_DEVICE_DOMAIN_ID_TYPE_SOC */
> +
> +/* ID for stage 2 default domain and NULL domain */
> +#define HV_DEVICE_DOMAIN_ID_S2_DEFAULT 0
> +#define HV_DEVICE_DOMAIN_ID_S2_NULL 0xFFFFFFFFULL
> +
> +union hv_device_domain_id {
> + u64 as_uint64;
> + struct {
> + u32 type : 4;
> + u32 reserved : 28;
> + u32 id;
> + };
> +} __packed;
[Severity: High]
Is the memory layout of union hv_device_domain_id inverted compared to the
expected Hyper-V ABI?
The macro HV_DEVICE_DOMAIN_ID_S2_NULL is defined as 0xFFFFFFFFULL, which
places 0xFFFFFFFF in the lower 32 bits and 0 in the upper 32 bits. Given
that Stage 2 (S2) domains have a type of 0, this macro implies that the id
should be in the lower 32 bits and the type should be in the upper 32 bits.
However, union hv_device_domain_id defines type as the first bitfield
(occupying the lower 32 bits) and id as the second (occupying the upper 32
bits).
Under this layout, casting 0xFFFFFFFFULL evaluates to type = 15 and id = 0,
instead of the expected type = 0 and id = 0xFFFFFFFF. Could this cause
malformed domain IDs to be sent to the hypervisor?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718021949.926306-1-mrathor@linux.microsoft.com?part=5
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH V4 2/9] mshv: Add declarations and definitions for VFIO-MSHV bridge device
2026-07-18 2:19 ` [PATCH V4 2/9] mshv: Add declarations and definitions for VFIO-MSHV bridge device Mukesh R
@ 2026-07-18 2:31 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-18 2:31 UTC (permalink / raw)
To: Mukesh R; +Cc: linux-pci, linux-hyperv
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The UAPI ioctl command macro for MSHV_CREATE_DEVICE is missing.
- [Medium] The ops structure mshv_vfio_device_ops is not declared as const.
--
--- Patch [2]: [PATCH V4 2/9] mshv: Add declarations and definitions for VFIO-MSHV bridge device ---
commit dbad27877cef927b38632a1b77ab492ceeebae34
Author: Mukesh R <mrathor@linux.microsoft.com>
mshv: Add declarations and definitions for VFIO-MSHV bridge device
Add data structs needed by the subsequent patch that introduces a new
module to implement VFIO-MSHV pseudo device.
> diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
> index a85c24dcc701a..b9880d0bdc4df 100644
> --- a/drivers/hv/mshv_root.h
> +++ b/drivers/hv/mshv_root.h
[ ... ]
> +struct mshv_device_ops {
> + const char *device_name;
> + long (*device_create)(struct mshv_device *dev);
> + void (*device_release)(struct mshv_device *dev);
> + long (*device_set_attr)(struct mshv_device *dev,
> + struct mshv_device_attr *attr);
> + long (*device_has_attr)(struct mshv_device *dev,
> + struct mshv_device_attr *attr);
> +};
> +
> +extern struct mshv_device_ops mshv_vfio_device_ops;
[Severity: Medium]
Should mshv_vfio_device_ops be declared as const?
Operations structures containing function pointers are typically placed in
read-only memory as a security hardening measure. Missing the const keyword on
the extern declaration prevents the future definition from being const without
triggering compilation errors.
> diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h
> index 32ff92b6342b2..be6fe3ee87078 100644
> --- a/include/uapi/linux/mshv.h
> +++ b/include/uapi/linux/mshv.h
[ ... ]
> +/* Device passhthru */
> +#define MSHV_CREATE_DEVICE_TEST 1
> +
> +enum {
> + MSHV_DEV_TYPE_VFIO,
> + MSHV_DEV_TYPE_MAX,
> +};
> +
> +struct mshv_create_device {
> + __u32 type; /* in: MSHV_DEV_TYPE_xxx */
> + __u32 fd; /* out: device handle */
> + __u32 flags; /* in: MSHV_CREATE_DEVICE_xxx */
> +};
> +
> +#define MSHV_DEV_VFIO_FILE 1
> +#define MSHV_DEV_VFIO_FILE_ADD 1
> +#define MSHV_DEV_VFIO_FILE_DEL 2
> +
> +struct mshv_device_attr {
> + __u32 flags; /* no flags currently defined */
> + __u32 group; /* device-defined */
> + __u64 attr; /* group-defined */
> + __u64 addr; /* userspace address of attr data */
> +};
> +
> +/* Device fds created with MSHV_CREATE_DEVICE */
> +#define MSHV_SET_DEVICE_ATTR _IOW(MSHV_IOCTL, 0x00, struct mshv_device_attr)
> +#define MSHV_HAS_DEVICE_ATTR _IOW(MSHV_IOCTL, 0x01, struct mshv_device_attr)
[Severity: High]
Is the UAPI ioctl command macro for MSHV_CREATE_DEVICE missing from this
header?
The struct mshv_create_device is defined for the ioctl payload, and the
comments reference MSHV_CREATE_DEVICE, but the actual ioctl command macro
appears to be absent. Without this definition, userspace cannot compile or
invoke the ioctl to create a device.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718021949.926306-1-mrathor@linux.microsoft.com?part=2
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH V4 1/9] mshv: Provide a way to get partition ID if running in a VMM process
2026-07-18 2:19 ` [PATCH V4 1/9] mshv: Provide a way to get partition ID if running in a VMM process Mukesh R
@ 2026-07-18 2:32 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-18 2:32 UTC (permalink / raw)
To: Mukesh R; +Cc: linux-pci, linux-hyperv
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] Using global `current->tgid` to track and authorize partition ownership introduces a PID reuse vulnerability and breaks support for VMMs managing multiple partitions.
- [High] `mshv_current_partid()` returns a raw partition ID without acquiring a reference, leading to a TOCTOU race where the partition can be freed and its ID reused.
Pre-existing issues:
- [High] VPs are freed before their parent partition is unlinked from the RCU hash table, causing a Use-After-Free for concurrent RCU readers.
--
--- Patch [1]: [PATCH V4 1/9] mshv: Provide a way to get partition ID if running in a VMM process ---
commit ce04f426ae72519f5c20b1bad8ffd34a07810200
Author: Mukesh R <mrathor@linux.microsoft.com>
mshv: Provide a way to get partition ID if running in a VMM process
Many PCI passthru related hypercalls require partition ID of the target
guest. Guests are actually managed by MSHV driver and the partition ID
is only maintained there. Add a field in the partition struct in MSHV
driver to save the tgid of the VMM process creating the partition, and
add a function there to retrieve partition ID if the current process is
a VMM process.
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index 146726cc4e9ba..f1ff45a77b42c 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
> @@ -1908,6 +1908,27 @@ mshv_partition_release(struct inode *inode, struct file *filp)
> return 0;
> }
[Severity: High]
This is a pre-existing issue, but there appears to be a use-after-free
condition when a partition is being destroyed.
In destroy_partition(), child mshv_vp objects are freed while the parent
partition remains linked in the RCU-protected mshv_root.pt_htable:
destroy_partition() {
...
kfree(vp);
partition->pt_vp_array[i] = NULL;
...
remove_partition(partition);
}
If a concurrent asynchronous interrupt (like handle_bitset_message) looks up
the partition via mshv_partition_find() and accesses the VP before
remove_partition() unlinks the partition and waits for a grace period,
could the ISR call kick_vp(vp) on freed memory?
> +/* Given a process tgid, return partition id if it is a VMM process */
> +u64 mshv_current_partid(void)
> +{
> + struct mshv_partition *pt;
> + int i;
> + u64 ret_ptid = HV_PARTITION_ID_INVALID;
> +
> + rcu_read_lock();
> +
> + hash_for_each_rcu(mshv_root.pt_htable, i, pt, pt_hnode) {
> + if (pt->pt_vmm_tgid == current->tgid) {
> + ret_ptid = pt->pt_id;
> + break;
> + }
> + }
[Severity: High]
Is it safe to track partition ownership using a global transient identifier
like current->tgid instead of an explicit file descriptor or reference-counted
token?
If a single VMM process creates multiple partitions, wouldn't
mshv_current_partid() arbitrarily return the first one found in the hash
table, breaking PCI passthru operations for all subsequent partitions?
Furthermore, could PID recycling lead to a situation where a new VMM process
accidentally matches an old partition whose fd was passed to a long-running
process that outlived the original creator?
> +
> + rcu_read_unlock();
> + return ret_ptid;
[Severity: High]
Does returning the raw pt_id here without acquiring a reference to the
partition object introduce a race condition?
Once the RCU read lock is dropped, a concurrent thread could close the
partition fd, causing destroy_partition() to delete it in the hypervisor.
If the hypervisor reuses this ID for a newly created partition belonging to a
different VMM, could the original caller's subsequent PCI passthru hypercall
mistakenly target the unrelated partition?
> +}
> +EXPORT_SYMBOL_GPL(mshv_current_partid);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718021949.926306-1-mrathor@linux.microsoft.com?part=1
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH V4 6/9] PCI: hv: Export hv_build_devid_type_pci() and change return type
2026-07-18 2:19 ` [PATCH V4 6/9] PCI: hv: Export hv_build_devid_type_pci() and change return type Mukesh R
@ 2026-07-18 2:32 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-18 2:32 UTC (permalink / raw)
To: Mukesh R; +Cc: linux-pci, linux-hyperv
> On Hyper-V, most hypercalls related to PCI passthru to map/unmap regions,
> interrupts, etc need a device ID as a parameter. This device ID refers
> to that specific device during the lifetime of passthru. So,
> make hv_build_devid_type_pci() public and change return type to u64 to
> enforce it's size.
>
> Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718021949.926306-1-mrathor@linux.microsoft.com?part=6
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH V4 8/9] mshv: Populate mmio mappings for PCI passthru
2026-07-18 2:19 ` [PATCH V4 8/9] mshv: Populate mmio mappings for PCI passthru Mukesh R
@ 2026-07-18 2:33 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-18 2:33 UTC (permalink / raw)
To: Mukesh R; +Cc: linux-pci, linux-hyperv
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
New issues:
- [Critical] hv_map_mmio_pages extrapolates the physical base address for an entire MMIO region from a single page fault, allowing unauthorized arbitrary physical memory access.
- [High] Flawed alignment logic allows mapping unaligned addresses as 2M large pages and fails to iterate the physical frame variable.
- [High] Unconditional use of FAULT_FLAG_WRITE in mshv_chk_get_mmio_start_pfn breaks read-only MMIO mappings.
- [High] Missing cleanup of partial mappings in hv_map_mmio_pages on failure.
- [High] Resolving user space PFNs without synchronizing with MMU notifiers introduces a TOCTOU race.
Pre-existing issues:
- [Critical] Guest MMIO mappings are never torn down when host userspace unmaps the VMA or detaches the device.
- [High] Synchronous, unbounded loop over page_is_ram for the entire region size can cause soft lockups.
- [High] Error handling rollback in hv_do_map_gpa_hcall fails to unmap pages due to a mismatched page count unit.
--
--- Patch [8]: [PATCH V4 8/9] mshv: Populate mmio mappings for PCI passthru ---
commit 65da49ecfde6e092de4c941dffc7d00deae59b78
Author: Mukesh R <mrathor@linux.microsoft.com>
mshv: Populate mmio mappings for PCI passthru
This commit handles unmapped GPA intercepts by resolving the PCI resource
pfn for a guest gpa and dynamically asking the hypervisor to map it via
hypercalls. It attempts to use 2M page sizes where possible and removes
the old vma->vm_pgoff mapping approach.
> diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
> --- a/drivers/hv/mshv_root_hv_call.c
> +++ b/drivers/hv/mshv_root_hv_call.c
[ ... ]
> @@ -189,31 +189,33 @@ int hv_call_delete_partition(u64 partition_id)
> }
>
> /* Ask the hypervisor to map guest ram pages or the guest mmio space */
> -static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_struct_count,
> - u32 flags, struct page **pages, u64 mmio_spa)
> +static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_count,
> + u32 flags, struct page **pages, u64 mmio_mfn)
> {
> struct hv_input_map_gpa_pages *input_page;
> u64 status, *pfnlist;
> unsigned long irq_flags, large_shift = 0;
> - int ret = 0, done = 0;
> - u64 page_count = page_struct_count;
> -
> - if (page_count == 0 || (pages && mmio_spa))
> - return -EINVAL;
> + int i, ret = 0, done = 0;
> + u64 adj_page_count = page_count;
>
> - if (flags & HV_MAP_GPA_LARGE_PAGE) {
> - if (mmio_spa)
> + if (mmio_mfn) {
> + if (pages)
> return -EINVAL;
> + for (i = 0; i < page_count; i++)
> + if (page_is_ram(mmio_mfn + i))
> + return -EINVAL;
[Severity: High]
This is a pre-existing issue, but could this synchronous loop cause soft
lockups?
If a user maps a multi-gigabyte MMIO region (like a large GPU BAR), this
loops page_count times calling page_is_ram() synchronously for every 4KB
page without preemption, which could trigger kernel soft lockup watchdogs.
[ ... ]
> } else if (!hv_result_success(status)) {
> + pr_err("%s: failed to map pages at gfn %#llx: completed %u/%llu, flags=%#x, status=%#llx (%s)\n",
> + __func__, gfn, done, page_count, flags, status,
> + hv_result_to_string(hv_result(status)));
> ret = hv_result_to_errno(status);
> break;
> }
[Severity: High]
This isn't a bug introduced by this patch, but does the error rollback at
the end of hv_do_map_gpa_hcall() pass the wrong unit to
hv_call_unmap_gpa_pages()?
If an error occurs during mapping and HV_MAP_GPA_LARGE_PAGE is set, "done"
contains the number of mapped 2M pages. This is passed to
hv_call_unmap_gpa_pages(), which expects a 4K page count:
hv_do_map_gpa_hcall() {
...
if (ret && done) {
u32 unmap_flags = 0;
if (flags & HV_MAP_GPA_LARGE_PAGE)
unmap_flags |= HV_UNMAP_GPA_LARGE_PAGE;
hv_call_unmap_gpa_pages(partition_id, gfn, done, unmap_flags);
}
}
Since hv_call_unmap_gpa_pages() expects 4K pages, it evaluates
!HV_PAGE_COUNT_2M_ALIGNED(done), which fails for small integers and aborts
with -EINVAL, leaking the memory mappings in the hypervisor's page tables.
[ ... ]
> -/* Ask the hypervisor to map guest mmio space */
> -int hv_call_map_mmio_pages(u64 partition_id, u64 gfn, u64 mmio_spa, u64 numpgs)
> +/*
> + * Ask the hypervisor to map guest mmio space. Don't set HV_MAP_GPA_NOT_CACHED
> + * in hcall flags for slightly better performance, and in that case the guest
> + * stage 1 page table will control caching.
> + */
> +int hv_map_mmio_pages(u64 partition_id, struct mshv_mem_region *reg, u64 gfn,
> + u64 mmio_mfn)
> {
> - int i;
> - u32 flags = HV_MAP_GPA_READABLE | HV_MAP_GPA_WRITABLE |
> - HV_MAP_GPA_NOT_CACHED;
> + int rc;
> + u32 flags = HV_MAP_GPA_READABLE;
> + u64 hpages, numpgs, numpgs_in_hpage = HPAGE_SIZE / PAGE_SIZE;
> +
> + if (reg->hv_map_flags & HV_MAP_GPA_WRITABLE)
> + flags |= HV_MAP_GPA_WRITABLE;
> + if (reg->hv_map_flags & HV_MAP_GPA_EXECUTABLE)
> + flags |= HV_MAP_GPA_EXECUTABLE;
> +
> + mmio_mfn = mmio_mfn - (gfn - reg->start_gfn); /* start of the range */
[Severity: Critical]
Does extrapolating the physical base address from a single page fault allow
mapping arbitrary physical memory?
If a user creates a large MSHV region backed by a sparse or smaller VM_PFNMAP
VMA, mshv_handle_unmapped_gpa() resolves a single PFN. This extrapolation then
calculates the base PFN for the entire region and maps reg->nr_pages of
contiguous physical memory into the guest. This would map the holes or
unrelated physical memory (like other devices' MSI-X tables or kernel RAM),
bypassing VFIO's memory protections.
> +
> + numpgs = 0;
> + gfn = reg->start_gfn;
> + while (!HV_PAGE_COUNT_2M_ALIGNED(gfn) &&
> + !HV_PAGE_COUNT_2M_ALIGNED(mmio_mfn) &&
> + numpgs < reg->nr_pages) {
> + numpgs++;
> + gfn++;
> + }
[Severity: High]
Does this loop condition prematurely exit, and are we failing to iterate
mmio_mfn?
If gfn and mmio_mfn have different 2M offsets, the logical AND causes the loop
to exit as soon as one of them is aligned. Furthermore, mmio_mfn is never
incremented inside this loop, which leaves it misaligned when it is later
passed to the large page mapping request.
> +
> + if (numpgs) {
> + rc = hv_do_map_gpa_hcall(partition_id, reg->start_gfn, numpgs,
> + flags, NULL, mmio_mfn);
> + if (rc || numpgs == reg->nr_pages)
> + return rc;
> + }
> +
> + mmio_mfn = mmio_mfn + numpgs;
> + numpgs = reg->nr_pages - numpgs;
> +
> + if (numpgs < numpgs_in_hpage)
> + return hv_do_map_gpa_hcall(partition_id, gfn, numpgs, flags,
> + NULL, mmio_mfn);
> +
> + for (hpages = 0; numpgs >= numpgs_in_hpage;) {
> + hpages++;
> + numpgs = numpgs - numpgs_in_hpage;
> + }
> + rc = hv_do_map_gpa_hcall(partition_id, gfn, hpages * numpgs_in_hpage,
> + flags | HV_MAP_GPA_LARGE_PAGE, NULL, mmio_mfn);
> + if (rc)
> + return rc;
[Severity: High]
Are we leaking partial mappings here if the hypercall batch fails?
If the initial batch of 4K pages succeeds but this second batch for 2M pages
fails, the function returns the error code directly. This skips cleaning up
the pages mapped during the first hv_do_map_gpa_hcall(), leaking those
mappings in the hypervisor.
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
[ ... ]
> +static int mshv_chk_get_mmio_start_pfn(u64 uaddr, u64 *mmio_pfnp)
> +{
> + struct vm_area_struct *vma;
> + bool is_mmio;
> + struct follow_pfnmap_args pfnmap_args;
> + int rc = -EINVAL;
> +
> + mmap_read_lock(current->mm);
> + vma = vma_lookup(current->mm, uaddr);
> + is_mmio = vma ? !!(vma->vm_flags & (VM_IO | VM_PFNMAP)) : 0;
> + if (!is_mmio)
> + goto unlock_mmap_out;
> +
> + pfnmap_args.vma = vma;
> + pfnmap_args.address = uaddr;
> +
> + rc = follow_pfnmap_start(&pfnmap_args);
> + if (rc) {
> + rc = fixup_user_fault(current->mm, uaddr, FAULT_FLAG_WRITE,
> + NULL);
[Severity: High]
Does this unconditional use of FAULT_FLAG_WRITE break read-only MMIO
mappings?
When resolving an unmapped GPA intercept for a read-only VMA, the
fixup_user_fault() call will fail with -EFAULT because the VMA lacks write
permissions, completely denying the guest access to read-only MMIO regions.
> + if (rc)
> + goto unlock_mmap_out;
> +
> + rc = follow_pfnmap_start(&pfnmap_args);
> + if (rc)
> + goto unlock_mmap_out;
> + }
> +
> + *mmio_pfnp = pfnmap_args.pfn;
> + follow_pfnmap_end(&pfnmap_args);
> +
> +unlock_mmap_out:
> + mmap_read_unlock(current->mm);
> + return rc;
> +}
> +
> +/*
> + * Check if the unmapped gpa belongs to mmio space. If yes, resolve it.
> + *
> + * Returns: True if valid mmio intercept and handled, else false.
> + */
> +static bool mshv_handle_unmapped_gpa(struct mshv_vp *vp)
> +{
[ ... ]
> + rc = mshv_chk_get_mmio_start_pfn(uaddr, &mmio_mfn);
> + if (rc)
> + goto put_rg_out;
> +
> + rc = hv_map_mmio_pages(pt->pt_id, rg, gfn, mmio_mfn);
[Severity: High]
Is there a TOCTOU race here when mapping the resolved PFN?
mshv_chk_get_mmio_start_pfn() resolves the PFN and drops mmap_read_lock
before returning. There is no synchronization with the MMU interval notifier
sequence lock prior to calling hv_map_mmio_pages(). If the VMA is unmapped
concurrently after the lock is dropped, could the hypervisor map a stale or
freed PFN into the guest?
[ ... ]
> @@ -1376,11 +1449,7 @@ mshv_map_user_memory(struct mshv_partition *partition,
> region->nr_pages,
> HV_MAP_GPA_NO_ACCESS, NULL);
> break;
> - case MSHV_REGION_TYPE_MMIO:
> - ret = hv_call_map_mmio_pages(partition->pt_id,
> - region->start_gfn,
> - mmio_pfn,
> - region->nr_pages);
> + default:
> break;
> }
[Severity: Critical]
This is a pre-existing issue, but are guest MMIO mappings ever torn down
when host userspace unmaps the VMA or detaches the device?
Because MMIO regions fall into the default case, they skip
mshv_prepare_pinned_region() and do not register MMU interval notifiers.
When the host userspace calls munmap() on the VFIO region or the device is
detached, the guest's stage-2 page tables are never invalidated, leaving the
guest with arbitrary access to reassigned physical memory.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718021949.926306-1-mrathor@linux.microsoft.com?part=8
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH V4 4/9] mshv: Add ioctl support for MSHV-VFIO bridge device
2026-07-18 2:19 ` [PATCH V4 4/9] mshv: Add ioctl support for MSHV-VFIO bridge device Mukesh R
@ 2026-07-18 2:34 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-18 2:34 UTC (permalink / raw)
To: Mukesh R; +Cc: linux-pci, linux-hyperv
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] File descriptor and associated kernel resources leak on `copy_to_user` error path.
- [Medium] The MSHV_CREATE_DEVICE_TEST flag check completely ignores other unknown flags, breaking forward compatibility.
--
--- Patch [4]: [PATCH V4 4/9] mshv: Add ioctl support for MSHV-VFIO bridge device ---
commit 4db495dcb20f06e9a8bed00e54250347faa9f23f
Author: Mukesh R <mrathor@linux.microsoft.com>
mshv: Add ioctl support for MSHV-VFIO bridge device
This commit adds ioctl support for creating MSHV devices for a partition. It
currently supports VFIO device types and performs initialization for the
bridge code. The implementation includes file operations and partition ioctl
handlers.
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index f1ff45a77b42c..56422f801ee2f 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
[ ... ]
> @@ -1625,6 +1625,122 @@ mshv_partition_ioctl_initialize(struct mshv_partition *partition)
[ ... ]
> +static long mshv_partition_ioctl_create_device(struct mshv_partition *partition,
> + void __user *uarg)
> +{
> + long rc;
> + struct mshv_create_device devargk;
> + struct mshv_device *mshv_dev;
> + const struct mshv_device_ops *vfio_ops;
> +
> + if (copy_from_user(&devargk, uarg, sizeof(devargk)))
> + return -EFAULT;
> +
> + /* At present, only VFIO is supported */
> + if (devargk.type != MSHV_DEV_TYPE_VFIO)
> + return -ENODEV;
> +
> + if (devargk.flags & MSHV_CREATE_DEVICE_TEST)
> + return 0;
> +
> + if (devargk.flags) /* currently, only MSHV_CREATE_DEVICE_TEST */
> + return -EINVAL;
[Severity: Medium]
Does this logic break forward compatibility by ignoring unknown flags when the
test bit is set?
If userspace queries support for a future flag by passing:
devargk.flags = MSHV_CREATE_DEVICE_TEST | FUTURE_FLAG
The kernel will immediately return 0. This could mislead userspace into
believing the flag is supported. However, when userspace attempts to use the
flag without the test bit, the kernel will reject it.
Would it be better to check for unknown flags before checking the test flag?
[ ... ]
> + mshv_partition_get(partition);
> + rc = anon_inode_getfd(vfio_ops->device_name, &mshv_device_fops,
> + mshv_dev, O_RDWR | O_CLOEXEC);
> + if (rc < 0)
> + goto undo_out;
> +
> + devargk.fd = rc;
> + if (copy_to_user(uarg, &devargk, sizeof(devargk)))
> + return -EFAULT; /* cleanup in mshv_device_fop_release() */
[Severity: High]
Can this leak the file descriptor and associated resources if copy_to_user
fails?
The file descriptor is installed into the process fd table via
anon_inode_getfd(). If the subsequent copy_to_user() fails, the kernel returns
-EFAULT, but userspace does not receive the allocated fd number and cannot
close it.
Should this approach use get_unused_fd_flags() and anon_inode_getfile(),
delaying fd_install() until after copy_to_user() succeeds?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718021949.926306-1-mrathor@linux.microsoft.com?part=4
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH V4 7/9] x86/hyperv: Implement Hyper-V virtual IOMMU
2026-07-18 2:19 ` [PATCH V4 7/9] x86/hyperv: Implement Hyper-V virtual IOMMU Mukesh R
@ 2026-07-18 2:34 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-18 2:34 UTC (permalink / raw)
To: Mukesh R; +Cc: linux-pci, linux-hyperv
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] Partial IOMMU unmaps corrupt the interval tree state and silently leak DMA mappings in the hypervisor, allowing potential DMA use-after-free.
- [High] `hv_iommu_domain_alloc_paging` unconditionally rejects domain allocation if the calling process is not an active VMM, breaking VFIO for host applications.
- [High] The `hv_iommu_map_pages` callback ignores the caller-provided `gfp` flags and implicitly uses `GFP_KERNEL`, risking sleep-in-atomic panics.
- [High] Unsafe interval tree iteration during node removal breaks RB-tree invariants and causes memory corruption or skipped nodes.
- [Medium] The `pci_devs_to_skip` string parsing logic incorrectly aborts on a successful parse, completely breaking the `hv_iommu_skip=` boot parameter.
- [Medium] `hv_iommu_domain_free` does not free remaining `hv_iommu_mapping` nodes in the `mappings_tree`, leading to a memory leak.
- [Medium] Failure in `hv_iommu_attach_dev` leaves the hardware detached while failing to restore the device to its previous domain.
--
--- Patch [7]: [PATCH V4 7/9] x86/hyperv: Implement Hyper-V virtual IOMMU ---
commit eedf8d68fd442834b8bd7d597c6c152d43c81395
Author: Mukesh R <mrathor@linux.microsoft.com>
Subject: x86/hyperv: Implement Hyper-V virtual IOMMU
This commit adds a new virtual IOMMU driver for Microsoft Hyper-V. The
hypervisor claims the IOMMU upon boot, and this driver communicates with
it to create and delete paging domains, map and unmap pages, and handle
default identity domains.
> diff --git a/drivers/iommu/hyperv/hv-iommu-root.c b/drivers/iommu/hyperv/hv-iommu-root.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/iommu/hyperv/hv-iommu-root.c
[ ... ]
> +static struct iommu_domain *hv_iommu_domain_alloc_paging(struct device *dev)
> +{
> + struct hv_domain *hvdom;
> + int rc;
> + u32 unique_id;
> + u64 ptid = hv_get_current_partid();
> +
> + if (ptid == HV_PARTITION_ID_INVALID)
> + return NULL;
[Severity: High]
The commit message explicitly mentions support for host apps like SPDK, but
could this check unintentionally reject domain allocation for them?
Since host applications like SPDK are standard userspace processes rather
than mshv VMM threads, hv_get_current_partid() will return
HV_PARTITION_ID_INVALID, which blocks the allocation entirely.
[ ... ]
> +static void hv_iommu_domain_free(struct iommu_domain *immdom)
> +{
> + unsigned long flags;
> + u64 status;
> + struct hv_input_delete_device_domain *input;
> + struct hv_input_device_domain *ddp;
> + struct hv_domain *hvdom = to_hv_domain(immdom);
> +
> + if (hv_special_domain(hvdom))
> + return;
> +
> + local_irq_save(flags);
> + input = *this_cpu_ptr(hyperv_pcpu_input_arg);
> + ddp = &input->device_domain;
> + memset(input, 0, sizeof(*input));
> +
> + ddp->partition_id = HV_PARTITION_ID_SELF;
> + ddp->domain_id.type = HV_DEVICE_DOMAIN_TYPE_S2;
> + ddp->domain_id.id = hvdom->domid_num;
> +
> + status = hv_do_hypercall(HVCALL_DELETE_DEVICE_DOMAIN, input,
> + NULL);
> + local_irq_restore(flags);
> +
> + if (!hv_result_success(status))
> + hv_status_err(status, "\n");
> +
> + kfree(hvdom);
> +}
[Severity: Medium]
When an IOMMU domain is destroyed, does this leave the dynamically allocated
nodes inside hvdom->mappings_tree leaked?
The function frees the hvdom structure but there does not appear to be a
cleanup loop iterating over and freeing the struct hv_iommu_mapping objects
left in the tree.
[ ... ]
> +static int hv_iommu_attach_dev(struct iommu_domain *immdom, struct device *dev,
> + struct iommu_domain *old)
> +{
> + struct pci_dev *pdev;
> + int rc;
> + struct hv_domain *hvdom_new = to_hv_domain(immdom);
> + struct hv_domain *hvdom_prev = to_hv_domain(old);
> +
> + /* Only allow PCI devices for now */
> + if (!dev_is_pci(dev))
> + return -EINVAL;
> +
> + pdev = to_pci_dev(dev);
> +
> + /* There are no explicit detach calls, hence check first if we need
> + * to detach first. Also, in case of guest shutdown, it's the VMM
> + * thread that attaches it back to the hv_def_identity_dom, and
> + * hvdom_prev will not be null then. It is null during boot.
> + */
> + if (hvdom_prev && !hv_special_domain(hvdom_prev))
> + hv_iommu_detach_dev(hvdom_prev, dev);
> +
> + rc = hv_iommu_att_dev2dom(hvdom_new, pdev);
> +
> + if (rc == 0)
> + dev_iommu_priv_set(dev, hvdom_new); /* sets "private" field */
> + else
> + dev_iommu_priv_set(dev, NULL);
> +
> + return rc;
> +}
[Severity: Medium]
If the device successfully detaches from the old domain but the new attach
hypercall fails, is the hardware left in limbo?
Since there is no rollback to re-attach the device to the previous domain,
the subsystem logic might believe the device is safely detached and handle
the error, while the hardware is left without an assigned domain.
[ ... ]
> +static int hv_iommu_add_tree_mapping(struct hv_domain *hvdom,
> + unsigned long iova, phys_addr_t paddr,
> + size_t size, u32 flags)
> +{
> + unsigned long irqflags;
> + struct hv_iommu_mapping *mapping;
> +
> + mapping = kzalloc_obj(struct hv_iommu_mapping);
> + if (!mapping)
> + return -ENOMEM;
[Severity: High]
Can this allocation trigger a panic by sleeping in atomic context?
The kzalloc_obj macro implicitly uses a GFP_KERNEL allocation by default.
The caller, hv_iommu_map_pages(), receives a gfp_t parameter specifically
because DMA mapping operations can be called with GFP_ATOMIC inside
interrupt handlers, but that parameter is discarded before calling this
function.
[ ... ]
> +static size_t hv_iommu_del_tree_mappings(struct hv_domain *hvdom,
> + unsigned long iova, size_t size)
> +{
> + unsigned long flags;
> + size_t unmapped = 0;
> + unsigned long last = iova + size - 1;
> + struct hv_iommu_mapping *mapping = NULL;
> + struct interval_tree_node *node, *next;
> +
> + spin_lock_irqsave(&hvdom->mappings_lock, flags);
> + next = interval_tree_iter_first(&hvdom->mappings_tree, iova, last);
> + while (next) {
> + node = next;
> + mapping = container_of(node, struct hv_iommu_mapping, iova);
> + next = interval_tree_iter_next(node, iova, last);
[Severity: High]
Is it safe to use a cached next pointer for interval tree iteration after
modifying the tree?
Calling interval_tree_remove() later in this loop restructures the
underlying RB-tree and changes parent and child relationships. Continuing
the iteration with a pre-cached pointer on the mutated tree might break the
iterator's traversal logic, leading to skipped nodes or memory corruption.
> +
> + /* Trying to split a mapping? Not supported for now. */
> + if (mapping->iova.start < iova)
> + break;
> +
> + unmapped += mapping->iova.last - mapping->iova.start + 1;
> +
> + interval_tree_remove(node, &hvdom->mappings_tree);
> + kfree(mapping);
> + }
> + spin_lock_irqrestore(&hvdom->mappings_lock, flags);
> +
> + return unmapped;
> +}
[Severity: High]
What happens if a caller unmaps a subset of a mapping?
If the unmap starts exactly at the IOVA, hv_iommu_del_tree_mappings()
removes the entire node from the tree and returns the node's full size.
Then, hv_iommu_unmap_pages() only unmaps the originally requested size
from the hypervisor.
This appears to permanently lose the remainder of the mapping from the
tree while leaving it mapped in the hypervisor, causing a desync and
potential DMA memory leak.
[ ... ]
> +static struct iommu_device *hv_iommu_probe_device(struct device *dev)
> +{
> + if (!dev_is_pci(dev))
> + return ERR_PTR(-ENODEV);
> +
> + if (pci_devs_to_skip && *pci_devs_to_skip) {
> + int rc, pos = 0;
> + int parsed;
> + int segment, bus, slot, func;
> + struct pci_dev *pdev = to_pci_dev(dev);
> +
> + do {
> + parsed = 0;
> +
> + rc = sscanf(pci_devs_to_skip + pos, " (%x:%x:%x.%x) %n",
> + &segment, &bus, &slot, &func, &parsed);
> + if (rc)
> + break;
[Severity: Medium]
Does this condition inadvertently terminate the loop on a successful parse?
The sscanf function returns the number of successfully matched items. If
the configuration string is parsed correctly, rc will be greater than zero
and the loop will immediately break before checking the parsed fields or
matching the devices to skip.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718021949.926306-1-mrathor@linux.microsoft.com?part=7
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH V4 3/9] mshv: Introduce basic mshv bridge device for VFIO to build upon
2026-07-18 2:19 ` [PATCH V4 3/9] mshv: Introduce basic mshv bridge device for VFIO to build upon Mukesh R
@ 2026-07-18 2:36 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-18 2:36 UTC (permalink / raw)
To: Mukesh R; +Cc: linux-pci, linux-hyperv
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The MSHV_CREATE_DEVICE ioctl handler is missing in the core MSHV framework, rendering the VFIO-MSHV bridge device completely unreachable.
--
--- Patch [3]: [PATCH V4 3/9] mshv: Introduce basic mshv bridge device for VFIO to build upon ---
commit 58c1a16396f01cf3f0ff1eb2d7b11c08f90ca678
Author: Mukesh R <mrathor@linux.microsoft.com>
mshv: Introduce basic mshv bridge device for VFIO to build upon
This commit introduces a basic VFIO-MSHV bridge pseudo device based on the
VFIO-KVM bridge. It adds the mshv_vfio.c implementation and a new UAPI ioctl
MSHV_CREATE_DEVICE.
> diff --git a/drivers/hv/mshv_vfio.c b/drivers/hv/mshv_vfio.c
> new file mode 100644
> index 0000000000000..a770b508f0800
> --- /dev/null
> +++ b/drivers/hv/mshv_vfio.c
[ ... ]
> +struct mshv_device_ops mshv_vfio_device_ops = {
> + .device_name = "mshv-vfio",
> + .device_create = mshv_vfio_create_device,
> + .device_release = mshv_vfio_release_device,
> + .device_set_attr = mshv_vfio_set_attr,
> + .device_has_attr = mshv_vfio_has_attr,
> +};
[Severity: Medium]
Since the ioctl to create this device is missing from the core ioctl
handlers, does this device operations struct and the rest of the VFIO-MSHV
bridge implementation become unreachable dead code?
> diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h
> index be6fe3ee87078..b038a79786d2d 100644
> --- a/include/uapi/linux/mshv.h
> +++ b/include/uapi/linux/mshv.h
> @@ -254,6 +254,7 @@ struct mshv_root_hvcall {
> #define MSHV_GET_GPAP_ACCESS_BITMAP _IOWR(MSHV_IOCTL, 0x06, struct mshv_gpap_access_bitmap)
> /* Generic hypercall */
> #define MSHV_ROOT_HVCALL _IOWR(MSHV_IOCTL, 0x07, struct mshv_root_hvcall)
> +#define MSHV_CREATE_DEVICE _IOWR(MSHV_IOCTL, 0x08, struct mshv_create_device)
[Severity: Medium]
Is this ioctl actually wired up anywhere in the core MSHV framework?
I can't find an implementation for MSHV_CREATE_DEVICE in any ioctl
dispatcher (like mshv_partition_ioctl()). Will userspace simply receive
-ENOTTY when attempting to use this command?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718021949.926306-1-mrathor@linux.microsoft.com?part=3
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH V4 9/9] mshv: Disable movable regions upfront if device passthru
2026-07-18 2:19 ` [PATCH V4 9/9] mshv: Disable movable regions upfront if device passthru Mukesh R
@ 2026-07-18 2:40 ` sashiko-bot
0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-07-18 2:40 UTC (permalink / raw)
To: Mukesh R; +Cc: linux-hyperv, linux-pci
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Malicious userspace can bypass memory pinning enforcement by forcing copy_to_user() to fail.
- [Low] Module parameter `hv_memreg` is exposed as runtime-writable despite being designed as a boot parameter.
--
--- Patch [9]: [PATCH V4 9/9] mshv: Disable movable regions upfront if device passthru ---
commit c768ee85b3efb1c981444ab02e4e4d365693795e
Author: Mukesh R <mrathor@linux.microsoft.com>
mshv: Disable movable regions upfront if device passthru
If a VM is started with a device attached, the memory regions must be marked
non-movable since the device attach hypercall allows the use of SLAT for IOMMU.
This patch marks them non-movable upfront upon device passthru and adds a boot
parameter to allow a user to pin them manually if anticipated.
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index 58cf61cb227ba..b085fa13574f0 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
> @@ -46,6 +46,10 @@ MODULE_DESCRIPTION("Microsoft Hyper-V root partition VMM interface /dev/mshv");
> #define HV_VP_COUNTER_ROOT_DISPATCH_THREAD_BLOCKED 95
> #endif
>
> +static uint hv_memreg; /* mshv_mem_region allocations type */
> +module_param(hv_memreg, uint, 0644);
[Severity: Low]
The commit message states this is a "boot parameter". Is it intentional
that this module parameter is created with 0644 permissions? This allows
root to change the allocation policy at runtime, whereas 0444 would
correctly enforce its boot-only nature.
> +MODULE_PARM_DESC(hv_memreg, "0: movable (default), 1: pinned");
> +
> struct mshv_root mshv_root;
[ ... ]
> @@ -1801,6 +1811,9 @@ static long mshv_partition_ioctl_create_device(struct mshv_partition *partition,
> if (copy_to_user(uarg, &devargk, sizeof(devargk)))
> return -EFAULT; /* cleanup in mshv_device_fop_release() */
[Severity: High]
If userspace passes a read-only memory buffer to the MSHV_CREATE_DEVICE ioctl,
copy_from_user() can succeed but copy_to_user() will fault and return -EFAULT.
Because the file descriptor has already been installed via anon_inode_getfd()
prior to the copy_to_user() call, the user can recover the functional device
FD via /proc/self/fd/ even after the ioctl returns an error.
Does this early return allow malicious userspace to bypass the memory pinning
enforcement by forcing copy_to_user() to fail, intentionally skipping the
partition->pt_regions_pinned = true assignment?
>
> + /* For now, just mark regions pinned if there is device passthru. */
> + partition->pt_regions_pinned = true;
> +
> return 0;
>
> undo_out:
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260718021949.926306-1-mrathor@linux.microsoft.com?part=9
^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2026-07-18 2:40 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-18 2:19 [PATCH V4 0/9] PCI passthru on Hyper-V Mukesh R
2026-07-18 2:19 ` [PATCH V4 1/9] mshv: Provide a way to get partition ID if running in a VMM process Mukesh R
2026-07-18 2:32 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 2/9] mshv: Add declarations and definitions for VFIO-MSHV bridge device Mukesh R
2026-07-18 2:31 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 3/9] mshv: Introduce basic mshv bridge device for VFIO to build upon Mukesh R
2026-07-18 2:36 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 4/9] mshv: Add ioctl support for MSHV-VFIO bridge device Mukesh R
2026-07-18 2:34 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 5/9] mshv: Import data structs around device passthru from hyperv headers Mukesh R
2026-07-18 2:30 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 6/9] PCI: hv: Export hv_build_devid_type_pci() and change return type Mukesh R
2026-07-18 2:32 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 7/9] x86/hyperv: Implement Hyper-V virtual IOMMU Mukesh R
2026-07-18 2:34 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 8/9] mshv: Populate mmio mappings for PCI passthru Mukesh R
2026-07-18 2:33 ` sashiko-bot
2026-07-18 2:19 ` [PATCH V4 9/9] mshv: Disable movable regions upfront if device passthru Mukesh R
2026-07-18 2:40 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.