* [PATCH v3 1/5] hyperv: Introduce new hypercall interfaces used by Hyper-V guest IOMMU
2026-08-11 15:50 [PATCH v3 0/5] Hyper-V: Add para-virtualized IOMMU support for Linux guests Yu Zhang
@ 2026-08-11 15:50 ` Yu Zhang
2026-08-11 15:50 ` [PATCH v3 2/5] Drivers: hv: Add logical device ID registry for vPCI devices Yu Zhang
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Yu Zhang @ 2026-08-11 15:50 UTC (permalink / raw)
To: linux-kernel, linux-hyperv, iommu, linux-pci, linux-arch, x86
Cc: wei.liu, kys, haiyangz, decui, longli, joro, will, robin.murphy,
bhelgaas, kwilczynski, lpieralisi, mani, robh, arnd, jgg,
mhklinux, jacob.pan, tgopinath, easwar.hariharan, mrathor,
baolu.lu, suravee.suthikulpanit, vasant.hegde
From: Wei Liu <wei.liu@kernel.org>
Hyper-V guest IOMMU is a para-virtualized IOMMU based on hypercalls.
Introduce the hypercalls used by the child partition to interact with
this facility.
These hypercalls fall into below categories:
- Detection and capability: HVCALL_GET_IOMMU_CAPABILITIES is used to
detect the existence and capabilities of the guest IOMMU.
- Device management: HVCALL_GET_LOGICAL_DEVICE_PROPERTY is used to
check whether an endpoint device is managed by the guest IOMMU.
- Domain management: A set of hypercalls is provided to handle the
creation, configuration, and deletion of guest domains, as well as
the attachment/detachment of endpoint devices to/from those domains.
- IOTLB flushing: HVCALL_FLUSH_DEVICE_DOMAIN is used to ask Hyper-V
for a domain-selective IOTLB flush (which in its handler may flush
the device TLB as well).
Signed-off-by: Wei Liu <wei.liu@kernel.org>
Co-developed-by: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
Signed-off-by: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
Co-developed-by: Yu Zhang <zhangyu1@linux.microsoft.com>
Signed-off-by: Yu Zhang <zhangyu1@linux.microsoft.com>
---
include/hyperv/hvgdk_mini.h | 8 +++
include/hyperv/hvhdk_mini.h | 126 ++++++++++++++++++++++++++++++++++++
2 files changed, 134 insertions(+)
diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h
index 6a4e8b9d570f..5bdbb44da112 100644
--- a/include/hyperv/hvgdk_mini.h
+++ b/include/hyperv/hvgdk_mini.h
@@ -486,10 +486,16 @@ 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_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_CONFIGURE_DEVICE_DOMAIN 0x00ce
+#define HVCALL_FLUSH_DEVICE_DOMAIN 0x00d0
#define HVCALL_ACQUIRE_SPARSE_SPA_PAGE_HOST_ACCESS 0x00d7
#define HVCALL_RELEASE_SPARSE_SPA_PAGE_HOST_ACCESS 0x00d8
#define HVCALL_MODIFY_SPARSE_GPA_PAGE_HOST_VISIBILITY 0x00db
@@ -502,6 +508,8 @@ union hv_vp_assist_msr_contents { /* HV_REGISTER_VP_ASSIST_PAGE */
#define HVCALL_MMIO_READ 0x0106
#define HVCALL_MMIO_WRITE 0x0107
#define HVCALL_DISABLE_HYP_EX 0x010f
+#define HVCALL_GET_IOMMU_CAPABILITIES 0x0125
+#define HVCALL_GET_LOGICAL_DEVICE_PROPERTY 0x0127
#define HVCALL_MAP_STATS_PAGE2 0x0131
/* HV_HYPERCALL_INPUT */
diff --git a/include/hyperv/hvhdk_mini.h b/include/hyperv/hvhdk_mini.h
index 035ba20870f7..1e3eac99886a 100644
--- a/include/hyperv/hvhdk_mini.h
+++ b/include/hyperv/hvhdk_mini.h
@@ -548,4 +548,130 @@ union hv_device_id { /* HV_DEVICE_ID */
} acpi;
} __packed;
+/* Device domain types */
+#define HV_DEVICE_DOMAIN_TYPE_S1 1 /* Stage 1 domain */
+
+/* ID for default domain and NULL domain */
+#define HV_DEVICE_DOMAIN_ID_DEFAULT 0
+#define HV_DEVICE_DOMAIN_ID_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 {
+ 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 {
+ u32 as_uint32;
+ struct {
+ u32 forward_progress_required: 1;
+ u32 inherit_owning_vtl: 1;
+ u32 reserved: 30;
+ } __packed;
+};
+
+struct hv_input_create_device_domain {
+ struct hv_input_device_domain device_domain;
+ union hv_create_device_domain_flags create_device_domain_flags;
+ u32 padding;
+} __packed;
+static_assert(sizeof(struct hv_input_create_device_domain) == 32);
+
+struct hv_input_delete_device_domain {
+ struct hv_input_device_domain device_domain;
+} __packed;
+
+struct 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 {
+ u64 partition_id;
+ union hv_device_id device_id;
+} __packed;
+
+struct hv_device_domain_settings {
+ struct {
+ /*
+ * Enable translations. If not enabled, all transaction bypass
+ * S1 translations.
+ */
+ u64 translation_enabled: 1;
+ u64 blocked: 1;
+ /*
+ * First stage address translation paging mode:
+ * 0: 4-level paging (default)
+ * 1: 5-level paging
+ */
+ u64 first_stage_paging_mode: 1;
+ u64 reserved: 61;
+ } flags;
+
+ /* Address of translation table */
+ u64 page_table_root;
+} __packed;
+
+struct hv_input_configure_device_domain {
+ struct hv_input_device_domain device_domain;
+ struct hv_device_domain_settings settings;
+} __packed;
+
+struct hv_input_get_iommu_capabilities {
+ u64 partition_id;
+ u64 reserved;
+} __packed;
+
+struct hv_output_get_iommu_capabilities {
+ u32 size;
+ u16 reserved;
+ u8 max_iova_width;
+ u8 max_pasid_width;
+
+#define HV_IOMMU_CAP_PRESENT BIT_ULL(0)
+#define HV_IOMMU_CAP_S2 BIT_ULL(1)
+#define HV_IOMMU_CAP_S1 BIT_ULL(2)
+#define HV_IOMMU_CAP_S1_5LVL BIT_ULL(3)
+#define HV_IOMMU_CAP_PASID BIT_ULL(4)
+#define HV_IOMMU_CAP_ATS BIT_ULL(5)
+#define HV_IOMMU_CAP_PRI BIT_ULL(6)
+
+ u64 iommu_cap;
+ u64 pgsize_bitmap;
+} __packed;
+
+enum hv_logical_device_property_code {
+ HV_LOGICAL_DEVICE_PROPERTY_PVIOMMU = 10,
+};
+
+struct hv_input_get_logical_device_property {
+ u64 partition_id;
+ u64 logical_device_id;
+ /* Takes values from enum hv_logical_device_property_code. */
+ u32 code;
+ u32 reserved;
+} __packed;
+
+struct hv_output_get_logical_device_property {
+#define HV_DEVICE_IOMMU_ENABLED BIT_ULL(0)
+ u64 device_iommu;
+ u64 reserved;
+} __packed;
+
+struct hv_input_flush_device_domain {
+ struct hv_input_device_domain device_domain;
+ u32 flags;
+ u32 reserved;
+} __packed;
+
#endif /* _HV_HVHDK_MINI_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v3 2/5] Drivers: hv: Add logical device ID registry for vPCI devices
2026-08-11 15:50 [PATCH v3 0/5] Hyper-V: Add para-virtualized IOMMU support for Linux guests Yu Zhang
2026-08-11 15:50 ` [PATCH v3 1/5] hyperv: Introduce new hypercall interfaces used by Hyper-V guest IOMMU Yu Zhang
@ 2026-08-11 15:50 ` Yu Zhang
2026-08-11 15:50 ` [PATCH v3 3/5] iommu/x86: Share the architectural MSI reserved range Yu Zhang
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Yu Zhang @ 2026-08-11 15:50 UTC (permalink / raw)
To: linux-kernel, linux-hyperv, iommu, linux-pci, linux-arch, x86
Cc: wei.liu, kys, haiyangz, decui, longli, joro, will, robin.murphy,
bhelgaas, kwilczynski, lpieralisi, mani, robh, arnd, jgg,
mhklinux, jacob.pan, tgopinath, easwar.hariharan, mrathor,
baolu.lu, suravee.suthikulpanit, vasant.hegde
Hyper-V identifies each PCI pass-thru device by a logical device ID in
its hypercall interface. This ID consists of a per-bus prefix, derived
from the VMBus device instance GUID, combined with the PCI function
number of the endpoint device.
Add a registry in hv_common.c that maps a PCI domain number to its
logical device ID prefix. The vPCI bus driver (pci-hyperv) registers the
prefix when a bus is probed and unregisters it when the bus is removed.
Consumers such as the para-virtualized IOMMU driver look up the prefix
by PCI domain number and combine it with the function number to form the
complete logical device ID for hypercalls.
Use rhashtable for the sparse exact-match mapping. Lookups copy the
prefix while holding the RCU read lock, and removal defers freeing the
entry until existing readers have completed.
The prefix construction is shared via hv_build_logical_dev_id_prefix() so
that pci-hyperv's interrupt retargeting path and the registry use exactly
the same byte layout. It is derived on demand from the constant hv_device
instance GUID rather than cached in struct hv_pcibus_device, which is
private to the pci-hyperv module; this keeps the interface narrow and
avoids depending on pci-hyperv internals.
Co-developed-by: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
Signed-off-by: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
Signed-off-by: Yu Zhang <zhangyu1@linux.microsoft.com>
---
drivers/hv/hv_common.c | 123 ++++++++++++++++++++++++++++
drivers/pci/controller/pci-hyperv.c | 21 +++--
include/asm-generic/mshyperv.h | 14 ++++
include/linux/hyperv.h | 8 ++
4 files changed, 161 insertions(+), 5 deletions(-)
diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
index 6b67ac616789..b30495b48a37 100644
--- a/drivers/hv/hv_common.c
+++ b/drivers/hv/hv_common.c
@@ -21,6 +21,7 @@
#include <linux/panic_notifier.h>
#include <linux/ptrace.h>
#include <linux/random.h>
+#include <linux/rhashtable.h>
#include <linux/efi.h>
#include <linux/kdebug.h>
#include <linux/kmsg_dump.h>
@@ -78,6 +79,27 @@ static struct ctl_table_header *hv_ctl_table_hdr;
u8 * __percpu *hv_synic_eventring_tail;
EXPORT_SYMBOL_GPL(hv_synic_eventring_tail);
+#ifdef CONFIG_HYPERV_PVIOMMU
+struct hv_pci_busdata {
+ int pci_domain_nr;
+ u32 logical_dev_id_prefix;
+ struct rhash_head node;
+ struct rcu_head rcu;
+};
+
+static struct rhashtable hv_pci_bus_ht;
+static bool hv_pci_bus_ht_initialized;
+
+static const struct rhashtable_params hv_pci_bus_ht_params = {
+ .key_len = sizeof_field(struct hv_pci_busdata,
+ pci_domain_nr),
+ .key_offset = offsetof(struct hv_pci_busdata,
+ pci_domain_nr),
+ .head_offset = offsetof(struct hv_pci_busdata, node),
+};
+
+#endif
+
/*
* Hyper-V specific initialization and shutdown code that is
* common across all architectures. Called from architecture
@@ -86,6 +108,13 @@ EXPORT_SYMBOL_GPL(hv_synic_eventring_tail);
void __init hv_common_free(void)
{
+#ifdef CONFIG_HYPERV_PVIOMMU
+ if (hv_pci_bus_ht_initialized) {
+ rhashtable_destroy(&hv_pci_bus_ht);
+ hv_pci_bus_ht_initialized = false;
+ }
+#endif
+
unregister_sysctl_table(hv_ctl_table_hdr);
hv_ctl_table_hdr = NULL;
@@ -315,6 +344,9 @@ u8 __init get_vtl(void)
int __init hv_common_init(void)
{
int i;
+#ifdef CONFIG_HYPERV_PVIOMMU
+ int ret;
+#endif
union hv_hypervisor_version_info version;
/* Get information about the Microsoft Hypervisor version */
@@ -394,6 +426,15 @@ int __init hv_common_init(void)
for (i = 0; i < nr_cpu_ids; i++)
hv_vp_index[i] = VP_INVAL;
+#ifdef CONFIG_HYPERV_PVIOMMU
+ ret = rhashtable_init(&hv_pci_bus_ht, &hv_pci_bus_ht_params);
+ if (ret) {
+ hv_common_free();
+ return ret;
+ }
+ hv_pci_bus_ht_initialized = true;
+#endif
+
return 0;
}
@@ -863,3 +904,85 @@ const char *hv_result_to_string(u64 status)
return "Unknown";
}
EXPORT_SYMBOL_GPL(hv_result_to_string);
+
+#ifdef CONFIG_HYPERV_PVIOMMU
+/*
+ * Logical device ID registry shared between the vPCI bus driver
+ * (pci-hyperv) and the para-virtualized IOMMU driver. The vPCI driver
+ * registers the per-bus logical device ID prefix at bus probe time, and
+ * the pvIOMMU driver looks it up to build the full logical device ID used
+ * in IOMMU hypercalls.
+ */
+int hv_iommu_register_pci_bus(int pci_domain_nr, u32 logical_dev_id_prefix)
+{
+ struct hv_pci_busdata *bus, *new;
+ int ret = 0;
+
+ new = kzalloc_obj(*new, GFP_KERNEL);
+ if (!new)
+ return -ENOMEM;
+
+ new->pci_domain_nr = pci_domain_nr;
+ new->logical_dev_id_prefix = logical_dev_id_prefix;
+
+ bus = rhashtable_lookup_get_insert_fast(&hv_pci_bus_ht, &new->node,
+ hv_pci_bus_ht_params);
+ if (IS_ERR(bus)) {
+ ret = PTR_ERR(bus);
+ } else if (bus) {
+ if (bus->logical_dev_id_prefix != logical_dev_id_prefix) {
+ pr_err("stale registration for PCI domain %d (old prefix 0x%08x, new 0x%08x)\n",
+ pci_domain_nr, bus->logical_dev_id_prefix,
+ logical_dev_id_prefix);
+ ret = -EEXIST;
+ }
+ } else {
+ goto out;
+ }
+
+ kfree(new);
+out:
+ return ret;
+}
+EXPORT_SYMBOL_FOR_MODULES(hv_iommu_register_pci_bus, "pci-hyperv");
+
+void hv_iommu_unregister_pci_bus(int pci_domain_nr)
+{
+ struct hv_pci_busdata *bus;
+ bool removed = false;
+
+ rcu_read_lock();
+ bus = rhashtable_lookup(&hv_pci_bus_ht, &pci_domain_nr,
+ hv_pci_bus_ht_params);
+ if (bus)
+ removed = !rhashtable_remove_fast(&hv_pci_bus_ht, &bus->node,
+ hv_pci_bus_ht_params);
+ rcu_read_unlock();
+
+ if (removed)
+ kfree_rcu(bus, rcu);
+}
+EXPORT_SYMBOL_FOR_MODULES(hv_iommu_unregister_pci_bus, "pci-hyperv");
+
+/*
+ * Look up the logical device ID prefix registered for @pci_domain_nr.
+ * Returns 0 on success with *prefix filled in; -ENODEV if no entry is
+ * registered for that PCI domain.
+ */
+int hv_iommu_lookup_logical_dev_id(int pci_domain_nr, u32 *prefix)
+{
+ struct hv_pci_busdata *bus;
+ int ret = -ENODEV;
+
+ rcu_read_lock();
+ bus = rhashtable_lookup(&hv_pci_bus_ht, &pci_domain_nr,
+ hv_pci_bus_ht_params);
+ if (bus) {
+ *prefix = bus->logical_dev_id_prefix;
+ ret = 0;
+ }
+ rcu_read_unlock();
+
+ return ret;
+}
+#endif /* CONFIG_HYPERV_PVIOMMU */
diff --git a/drivers/pci/controller/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c
index cfc8fa403dad..0b12b18fe0f1 100644
--- a/drivers/pci/controller/pci-hyperv.c
+++ b/drivers/pci/controller/pci-hyperv.c
@@ -641,10 +641,7 @@ static void hv_irq_retarget_interrupt(struct irq_data *data)
params->int_entry.source = HV_INTERRUPT_SOURCE_MSI;
params->int_entry.msi_entry.address.as_uint32 = int_desc->address & 0xffffffff;
params->int_entry.msi_entry.data.as_uint32 = int_desc->data;
- params->device_id = (hbus->hdev->dev_instance.b[5] << 24) |
- (hbus->hdev->dev_instance.b[4] << 16) |
- (hbus->hdev->dev_instance.b[7] << 8) |
- (hbus->hdev->dev_instance.b[6] & 0xf8) |
+ params->device_id = hv_build_logical_dev_id_prefix(hbus->hdev) |
PCI_FUNC(pdev->devfn);
params->int_target.vector = hv_msi_get_int_vector(data);
@@ -3715,6 +3712,7 @@ static int hv_pci_probe(struct hv_device *hdev,
struct hv_pcibus_device *hbus;
int ret, dom;
u16 dom_req;
+ u32 prefix;
char *name;
bridge = devm_pci_alloc_host_bridge(&hdev->device, 0);
@@ -3857,13 +3855,22 @@ static int hv_pci_probe(struct hv_device *hdev,
hbus->state = hv_pcibus_probed;
- ret = create_root_hv_pci_bus(hbus);
+ /* Register the bus before scanning any devices on it. */
+ prefix = hv_build_logical_dev_id_prefix(hdev);
+
+ ret = hv_iommu_register_pci_bus(dom, prefix);
if (ret)
goto free_windows;
+ ret = create_root_hv_pci_bus(hbus);
+ if (ret)
+ goto unregister_pci_bus;
+
mutex_unlock(&hbus->state_lock);
return 0;
+unregister_pci_bus:
+ hv_iommu_unregister_pci_bus(dom);
free_windows:
hv_pci_free_bridge_windows(hbus);
exit_d0:
@@ -3977,6 +3984,8 @@ static void hv_pci_remove(struct hv_device *hdev)
hbus = hv_get_drvdata(hdev);
if (hbus->state == hv_pcibus_installed) {
+ int dom = hbus->bridge->domain_nr;
+
tasklet_disable(&hdev->channel->callback_event);
hbus->state = hv_pcibus_removing;
tasklet_enable(&hdev->channel->callback_event);
@@ -3994,6 +4003,8 @@ static void hv_pci_remove(struct hv_device *hdev)
hv_pci_remove_slots(hbus);
pci_remove_root_bus(hbus->bridge->bus);
pci_unlock_rescan_remove();
+
+ hv_iommu_unregister_pci_bus(dom);
}
hv_pci_bus_exit(hdev, false);
diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
index bf601d67cecb..4b3c9ba69cdb 100644
--- a/include/asm-generic/mshyperv.h
+++ b/include/asm-generic/mshyperv.h
@@ -73,6 +73,20 @@ extern enum hv_partition_type hv_curr_partition_type;
extern void * __percpu *hyperv_pcpu_input_arg;
extern void * __percpu *hyperv_pcpu_output_arg;
+#ifdef CONFIG_HYPERV_PVIOMMU
+int hv_iommu_register_pci_bus(int pci_domain_nr, u32 logical_dev_id_prefix);
+void hv_iommu_unregister_pci_bus(int pci_domain_nr);
+int hv_iommu_lookup_logical_dev_id(int pci_domain_nr, u32 *prefix);
+#else
+static inline int hv_iommu_register_pci_bus(int pci_domain_nr,
+ u32 logical_dev_id_prefix)
+{
+ return 0;
+}
+
+static inline void hv_iommu_unregister_pci_bus(int pci_domain_nr) { }
+#endif
+
u64 hv_do_hypercall(u64 control, void *inputaddr, void *outputaddr);
u64 hv_do_fast_hypercall8(u16 control, u64 input8);
u64 hv_do_fast_hypercall16(u16 control, u64 input1, u64 input2);
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index a2b484679eb4..7bc7b9b60002 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -1287,6 +1287,14 @@ struct hv_device {
#define device_to_hv_device(d) container_of_const(d, struct hv_device, device)
#define drv_to_hv_drv(d) container_of_const(d, struct hv_driver, driver)
+static inline u32 hv_build_logical_dev_id_prefix(struct hv_device *hdev)
+{
+ return ((u32)hdev->dev_instance.b[5] << 24) |
+ ((u32)hdev->dev_instance.b[4] << 16) |
+ ((u32)hdev->dev_instance.b[7] << 8) |
+ (hdev->dev_instance.b[6] & 0xf8u);
+}
+
static inline void hv_set_drvdata(struct hv_device *dev, void *data)
{
dev_set_drvdata(&dev->device, data);
--
2.52.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v3 3/5] iommu/x86: Share the architectural MSI reserved range
2026-08-11 15:50 [PATCH v3 0/5] Hyper-V: Add para-virtualized IOMMU support for Linux guests Yu Zhang
2026-08-11 15:50 ` [PATCH v3 1/5] hyperv: Introduce new hypercall interfaces used by Hyper-V guest IOMMU Yu Zhang
2026-08-11 15:50 ` [PATCH v3 2/5] Drivers: hv: Add logical device ID registry for vPCI devices Yu Zhang
@ 2026-08-11 15:50 ` Yu Zhang
2026-08-11 16:21 ` Jason Gunthorpe
2026-08-11 15:50 ` [PATCH v3 4/5] iommu/hyperv: Add para-virtualized IOMMU support for Hyper-V guest Yu Zhang
2026-08-11 15:50 ` [PATCH v3 5/5] iommu/hyperv: Add page-selective IOTLB flush support Yu Zhang
4 siblings, 1 reply; 7+ messages in thread
From: Yu Zhang @ 2026-08-11 15:50 UTC (permalink / raw)
To: linux-kernel, linux-hyperv, iommu, linux-pci, linux-arch, x86
Cc: wei.liu, kys, haiyangz, decui, longli, joro, will, robin.murphy,
bhelgaas, kwilczynski, lpieralisi, mani, robh, arnd, jgg,
mhklinux, jacob.pan, tgopinath, easwar.hariharan, mrathor,
baolu.lu, suravee.suthikulpanit, vasant.hegde
Intel and AMD IOMMU drivers both reserve the x86 architectural MSI
address window from normal IOVA mappings. Define the range in the
common x86 IOMMU header and use it in both drivers.
Signed-off-by: Yu Zhang <zhangyu1@linux.microsoft.com>
---
arch/x86/include/asm/iommu.h | 3 +++
drivers/iommu/amd/iommu.c | 7 +++----
drivers/iommu/intel/iommu.c | 7 +++----
3 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/arch/x86/include/asm/iommu.h b/arch/x86/include/asm/iommu.h
index 3be2451e7bc8..386db10ea909 100644
--- a/arch/x86/include/asm/iommu.h
+++ b/arch/x86/include/asm/iommu.h
@@ -6,6 +6,9 @@
#include <asm/e820/api.h>
+#define X86_IOMMU_MSI_RANGE_START 0xfee00000
+#define X86_IOMMU_MSI_RANGE_END 0xfeefffff
+
extern int force_iommu, no_iommu;
extern int iommu_detected;
extern int iommu_merge;
diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
index 29dc18d3d22e..ead96acdb766 100644
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -50,8 +50,6 @@
#define CMD_SET_TYPE(cmd, t) ((cmd)->data[1] |= ((t) << 28))
/* Reserved IOVA ranges */
-#define MSI_RANGE_START (0xfee00000)
-#define MSI_RANGE_END (0xfeefffff)
#define HT_RANGE_START (0xfd00000000ULL)
#define HT_RANGE_END (0xffffffffffULL)
@@ -3097,8 +3095,9 @@ static void amd_iommu_get_resv_regions(struct device *dev,
list_add_tail(®ion->list, head);
}
- region = iommu_alloc_resv_region(MSI_RANGE_START,
- MSI_RANGE_END - MSI_RANGE_START + 1,
+ region = iommu_alloc_resv_region(X86_IOMMU_MSI_RANGE_START,
+ X86_IOMMU_MSI_RANGE_END -
+ X86_IOMMU_MSI_RANGE_START + 1,
0, IOMMU_RESV_MSI, GFP_KERNEL);
if (!region)
return;
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 849d06dfe1ae..500f6fa5bf72 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -39,8 +39,6 @@
#define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA)
#define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e)
-#define IOAPIC_RANGE_START (0xfee00000)
-#define IOAPIC_RANGE_END (0xfeefffff)
#define IOVA_START_ADDR (0x1000)
#define DEFAULT_DOMAIN_ADDRESS_WIDTH 57
@@ -3432,8 +3430,9 @@ static void intel_iommu_get_resv_regions(struct device *device,
}
#endif /* CONFIG_INTEL_IOMMU_FLOPPY_WA */
- reg = iommu_alloc_resv_region(IOAPIC_RANGE_START,
- IOAPIC_RANGE_END - IOAPIC_RANGE_START + 1,
+ reg = iommu_alloc_resv_region(X86_IOMMU_MSI_RANGE_START,
+ X86_IOMMU_MSI_RANGE_END -
+ X86_IOMMU_MSI_RANGE_START + 1,
0, IOMMU_RESV_MSI, GFP_KERNEL);
if (!reg)
return;
--
2.52.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v3 3/5] iommu/x86: Share the architectural MSI reserved range
2026-08-11 15:50 ` [PATCH v3 3/5] iommu/x86: Share the architectural MSI reserved range Yu Zhang
@ 2026-08-11 16:21 ` Jason Gunthorpe
0 siblings, 0 replies; 7+ messages in thread
From: Jason Gunthorpe @ 2026-08-11 16:21 UTC (permalink / raw)
To: Yu Zhang
Cc: linux-kernel, linux-hyperv, iommu, linux-pci, linux-arch, x86,
wei.liu, kys, haiyangz, decui, longli, joro, will, robin.murphy,
bhelgaas, kwilczynski, lpieralisi, mani, robh, arnd, mhklinux,
jacob.pan, tgopinath, easwar.hariharan, mrathor, baolu.lu,
suravee.suthikulpanit, vasant.hegde
On Tue, Aug 11, 2026 at 11:50:19PM +0800, Yu Zhang wrote:
> @@ -50,8 +50,6 @@
> #define CMD_SET_TYPE(cmd, t) ((cmd)->data[1] |= ((t) << 28))
>
> /* Reserved IOVA ranges */
> -#define MSI_RANGE_START (0xfee00000)
> -#define MSI_RANGE_END (0xfeefffff)
> #define HT_RANGE_START (0xfd00000000ULL)
> #define HT_RANGE_END (0xffffffffffULL)
>
> @@ -3097,8 +3095,9 @@ static void amd_iommu_get_resv_regions(struct device *dev,
> list_add_tail(®ion->list, head);
> }
>
> - region = iommu_alloc_resv_region(MSI_RANGE_START,
> - MSI_RANGE_END - MSI_RANGE_START + 1,
> + region = iommu_alloc_resv_region(X86_IOMMU_MSI_RANGE_START,
> + X86_IOMMU_MSI_RANGE_END -
> + X86_IOMMU_MSI_RANGE_START + 1,
> 0, IOMMU_RESV_MSI, GFP_KERNEL);
Maybe instead of adding constants you should add a
iommu_alloc_resv_x86_msi_region()
That is just an inline doing the above?
Jason
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v3 4/5] iommu/hyperv: Add para-virtualized IOMMU support for Hyper-V guest
2026-08-11 15:50 [PATCH v3 0/5] Hyper-V: Add para-virtualized IOMMU support for Linux guests Yu Zhang
` (2 preceding siblings ...)
2026-08-11 15:50 ` [PATCH v3 3/5] iommu/x86: Share the architectural MSI reserved range Yu Zhang
@ 2026-08-11 15:50 ` Yu Zhang
2026-08-11 15:50 ` [PATCH v3 5/5] iommu/hyperv: Add page-selective IOTLB flush support Yu Zhang
4 siblings, 0 replies; 7+ messages in thread
From: Yu Zhang @ 2026-08-11 15:50 UTC (permalink / raw)
To: linux-kernel, linux-hyperv, iommu, linux-pci, linux-arch, x86
Cc: wei.liu, kys, haiyangz, decui, longli, joro, will, robin.murphy,
bhelgaas, kwilczynski, lpieralisi, mani, robh, arnd, jgg,
mhklinux, jacob.pan, tgopinath, easwar.hariharan, mrathor,
baolu.lu, suravee.suthikulpanit, vasant.hegde
Add a para-virtualized IOMMU driver for Linux guests running on Hyper-V.
This driver implements stage-1 IO translation within the guest OS.
It integrates with the Linux IOMMU core, utilizing Hyper-V hypercalls
for:
- Capability discovery
- Domain allocation, configuration, and deallocation
- Device attachment and detachment
- IOTLB invalidation
The driver constructs x86-compatible stage-1 IO page tables in the
guest memory using consolidated IO page table helpers. This allows
the guest to manage stage-1 translations independently of vendor-
specific drivers (like Intel VT-d or AMD IOMMU).
Hyper-V consumes this stage-1 IO page table when a device domain is
created and configured, and nests it with the host's stage-2 IO page
tables, therefore eliminating the VM exits for guest IOMMU mapping
operations. For unmapping operations, VM exits to perform the IOTLB
flush are still unavoidable.
Guest hibernation and resume are not supported by this initial
implementation. The guest-owned stage-1 page tables reside in guest
memory and are preserved in the hibernation image. However, the image
does not by itself preserve the Hyper-V device-domain objects or their
device attachments, and the current pvIOMMU interface does not define
whether or how Hyper-V restores that state.
To identify a device in its hypercall interface, the driver looks up the
logical device ID prefix registered for the device's PCI domain (see the
logical device ID registry in hv_common.c) and combines it with the PCI
function number of the endpoint device.
Co-developed-by: Wei Liu <wei.liu@kernel.org>
Signed-off-by: Wei Liu <wei.liu@kernel.org>
Co-developed-by: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
Signed-off-by: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
Signed-off-by: Yu Zhang <zhangyu1@linux.microsoft.com>
---
arch/x86/hyperv/hv_init.c | 4 +
arch/x86/include/asm/mshyperv.h | 4 +
drivers/iommu/Kconfig | 1 +
drivers/iommu/hyperv/Kconfig | 16 +
drivers/iommu/hyperv/Makefile | 1 +
drivers/iommu/hyperv/hv-iommu-guest.c | 632 ++++++++++++++++++++++++++
drivers/iommu/hyperv/iommu.h | 50 ++
7 files changed, 708 insertions(+)
create mode 100644 drivers/iommu/hyperv/Kconfig
create mode 100644 drivers/iommu/hyperv/hv-iommu-guest.c
create mode 100644 drivers/iommu/hyperv/iommu.h
diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c
index 55a8b6de2865..094f9f7ddb72 100644
--- a/arch/x86/hyperv/hv_init.c
+++ b/arch/x86/hyperv/hv_init.c
@@ -578,6 +578,10 @@ void __init hyperv_init(void)
old_setup_percpu_clockev = x86_init.timers.setup_percpu_clockev;
x86_init.timers.setup_percpu_clockev = hv_stimer_setup_percpu_clockev;
+#ifdef CONFIG_HYPERV_PVIOMMU
+ x86_init.iommu.iommu_init = hv_iommu_init;
+#endif
+
hv_apic_init();
x86_init.pci.arch_init = hv_pci_init;
diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
index f64393e853ee..20d947c2c758 100644
--- a/arch/x86/include/asm/mshyperv.h
+++ b/arch/x86/include/asm/mshyperv.h
@@ -313,6 +313,10 @@ static inline void mshv_vtl_return_hypercall(void) {}
static inline void __mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) {}
#endif
+#ifdef CONFIG_HYPERV_PVIOMMU
+int __init hv_iommu_init(void);
+#endif
+
#include <asm-generic/mshyperv.h>
#endif
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 6e07bd69467a..0d128f377929 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -195,6 +195,7 @@ config MSM_IOMMU
source "drivers/iommu/amd/Kconfig"
source "drivers/iommu/arm/Kconfig"
source "drivers/iommu/intel/Kconfig"
+source "drivers/iommu/hyperv/Kconfig"
source "drivers/iommu/iommufd/Kconfig"
source "drivers/iommu/riscv/Kconfig"
diff --git a/drivers/iommu/hyperv/Kconfig b/drivers/iommu/hyperv/Kconfig
new file mode 100644
index 000000000000..37114239b832
--- /dev/null
+++ b/drivers/iommu/hyperv/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# HyperV paravirtualized IOMMU support
+config HYPERV_PVIOMMU
+ bool "Microsoft Hypervisor para-virtualized IOMMU support"
+ depends on X86_64 && HYPERV
+ select IOMMU_API
+ select GENERIC_PT
+ select IOMMU_PT
+ select IOMMU_PT_X86_64
+ select IOMMU_IOVA
+ default HYPERV
+ help
+ Para-virtualized IOMMU driver for Linux guests running on
+ Microsoft Hyper-V. Provides DMA remapping and IOTLB
+ flush support to enable DMA isolation for PCI devices
+ assigned to the guest.
diff --git a/drivers/iommu/hyperv/Makefile b/drivers/iommu/hyperv/Makefile
index 6ef0ef97f3dd..d3b2a259439c 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_PVIOMMU) += hv-iommu-guest.o
diff --git a/drivers/iommu/hyperv/hv-iommu-guest.c b/drivers/iommu/hyperv/hv-iommu-guest.c
new file mode 100644
index 000000000000..2a00353ce733
--- /dev/null
+++ b/drivers/iommu/hyperv/hv-iommu-guest.c
@@ -0,0 +1,632 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Hyper-V IOMMU driver.
+ *
+ * Copyright (C) 2019, 2024-2026 Microsoft, Inc.
+ */
+
+#define pr_fmt(fmt) "Hyper-V pvIOMMU: " fmt
+#define dev_fmt(fmt) pr_fmt(fmt)
+
+#include <linux/iommu.h>
+#include <linux/pci.h>
+#include <linux/dma-map-ops.h>
+#include <linux/generic_pt/iommu.h>
+#include <linux/pci-ats.h>
+
+#include <asm/iommu.h>
+#include <asm/hypervisor.h>
+#include <asm/mshyperv.h>
+
+#include "iommu.h"
+#include "../iommu-pages.h"
+
+struct hv_iommu_dev *hv_iommu_device;
+
+/*
+ * Identity and blocking domains are static singletons: identity is a 1:1
+ * passthrough with no page table, blocking rejects all DMA. Neither holds
+ * per-IOMMU state, so one instance suffices even with multiple vIOMMUs.
+ */
+static const struct iommu_domain_ops hv_iommu_identity_domain_ops;
+static const struct iommu_domain_ops hv_iommu_blocking_domain_ops;
+static struct iommu_ops hv_iommu_ops;
+
+static struct hv_iommu_domain hv_identity_domain = {
+ .domain = {
+ .type = IOMMU_DOMAIN_IDENTITY,
+ .ops = &hv_iommu_identity_domain_ops,
+ .owner = &hv_iommu_ops,
+ },
+};
+
+static struct hv_iommu_domain hv_blocking_domain = {
+ .domain = {
+ .type = IOMMU_DOMAIN_BLOCKED,
+ .ops = &hv_iommu_blocking_domain_ops,
+ .owner = &hv_iommu_ops,
+ },
+};
+
+static inline bool hv_iommu_present(u64 cap)
+{
+ return cap & HV_IOMMU_CAP_PRESENT;
+}
+
+static inline bool hv_iommu_s1_domain_supported(u64 cap)
+{
+ return cap & HV_IOMMU_CAP_S1;
+}
+
+static inline bool hv_iommu_5lvl_supported(u64 cap)
+{
+ return cap & HV_IOMMU_CAP_S1_5LVL;
+}
+
+static inline bool hv_iommu_ats_supported(u64 cap)
+{
+ return cap & HV_IOMMU_CAP_ATS;
+}
+
+static int hv_create_device_domain(struct hv_iommu_domain *hv_domain)
+{
+ int ret;
+ u64 status;
+ unsigned long flags;
+ struct hv_input_create_device_domain *input;
+
+ ret = ida_alloc_range(&hv_iommu_device->domain_ids,
+ hv_iommu_device->first_domain,
+ hv_iommu_device->last_domain, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ hv_domain->device_domain.partition_id = HV_PARTITION_ID_SELF;
+ hv_domain->device_domain.domain_id.type = HV_DEVICE_DOMAIN_TYPE_S1;
+ hv_domain->device_domain.domain_id.id = ret;
+ hv_domain->hv_iommu = hv_iommu_device;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->device_domain = hv_domain->device_domain;
+ 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, "HVCALL_CREATE_DEVICE_DOMAIN failed\n");
+ ida_free(&hv_iommu_device->domain_ids,
+ hv_domain->device_domain.domain_id.id);
+ }
+
+ return hv_result_to_errno(status);
+}
+
+static void hv_delete_device_domain(struct hv_iommu_domain *hv_domain)
+{
+ u64 status;
+ unsigned long flags;
+ struct hv_input_delete_device_domain *input;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->device_domain = hv_domain->device_domain;
+ status = hv_do_hypercall(HVCALL_DELETE_DEVICE_DOMAIN, input, NULL);
+
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status, "HVCALL_DELETE_DEVICE_DOMAIN failed\n");
+
+ ida_free(&hv_domain->hv_iommu->domain_ids,
+ hv_domain->device_domain.domain_id.id);
+}
+
+static int
+hv_configure_device_domain(struct hv_iommu_domain *hv_domain,
+ const struct hv_device_domain_settings *settings)
+{
+ struct hv_input_configure_device_domain *input;
+ unsigned long flags;
+ u64 status;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->device_domain = hv_domain->device_domain;
+ input->settings = *settings;
+ status = hv_do_hypercall(HVCALL_CONFIGURE_DEVICE_DOMAIN, input, NULL);
+
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status,
+ "HVCALL_CONFIGURE_DEVICE_DOMAIN failed\n");
+
+ return hv_result_to_errno(status);
+}
+
+static int
+hv_create_configure_device_domain(struct hv_iommu_domain *hv_domain,
+ const struct hv_device_domain_settings *settings)
+{
+ int ret;
+
+ ret = hv_create_device_domain(hv_domain);
+ if (ret)
+ return ret;
+
+ ret = hv_configure_device_domain(hv_domain, settings);
+ if (ret)
+ hv_delete_device_domain(hv_domain);
+
+ return ret;
+}
+
+static bool hv_iommu_capable(struct device *dev, enum iommu_cap cap)
+{
+ return cap == IOMMU_CAP_CACHE_COHERENCY;
+}
+
+static void hv_flush_device_domain(struct hv_iommu_domain *hv_domain)
+{
+ u64 status;
+ unsigned long flags;
+ struct hv_input_flush_device_domain *input;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->device_domain = hv_domain->device_domain;
+ status = hv_do_hypercall(HVCALL_FLUSH_DEVICE_DOMAIN, input, NULL);
+
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status, "HVCALL_FLUSH_DEVICE_DOMAIN failed\n");
+}
+
+static int hv_iommu_attach_dev(struct iommu_domain *domain, struct device *dev,
+ struct iommu_domain *old)
+{
+ u64 status;
+ u32 prefix;
+ unsigned long flags;
+ struct pci_dev *pdev;
+ struct hv_input_attach_device_domain *input;
+ struct hv_iommu_endpoint *vdev = dev_iommu_priv_get(dev);
+ struct hv_iommu_domain *hv_domain = to_hv_iommu_domain(domain);
+ int ret;
+
+ pdev = to_pci_dev(dev);
+ dev_dbg(dev, "attaching to domain %d\n",
+ hv_domain->device_domain.domain_id.id);
+
+ ret = hv_iommu_lookup_logical_dev_id(pci_domain_nr(pdev->bus), &prefix);
+ if (ret) {
+ dev_err(&pdev->dev, "no IOMMU registration for vPCI bus\n");
+ return ret;
+ }
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ memset(input, 0, sizeof(*input));
+ input->device_domain = hv_domain->device_domain;
+ input->device_id.as_uint64 = (u64)prefix | PCI_FUNC(pdev->devfn);
+ status = hv_do_hypercall(HVCALL_ATTACH_DEVICE_DOMAIN, input, NULL);
+
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status)) {
+ hv_status_err(status, "HVCALL_ATTACH_DEVICE_DOMAIN failed\n");
+ } else {
+ vdev->hv_domain = hv_domain;
+
+ if (domain != &hv_blocking_domain.domain &&
+ !pdev->ats_enabled &&
+ hv_iommu_ats_supported(hv_iommu_device->cap) &&
+ pci_ats_supported(pdev))
+ pci_enable_ats(pdev, PAGE_SHIFT);
+ }
+
+ return hv_result_to_errno(status);
+}
+
+static int hv_iommu_blocking_attach_dev(struct iommu_domain *domain,
+ struct device *dev,
+ struct iommu_domain *old)
+{
+ struct hv_iommu_endpoint *vdev = dev_iommu_priv_get(dev);
+ struct hv_iommu_domain *hv_domain = to_hv_iommu_domain(domain);
+ struct pci_dev *pdev = to_pci_dev(dev);
+ int ret;
+
+ if (pdev->ats_enabled)
+ pci_disable_ats(pdev);
+
+ if (vdev->hv_domain == hv_domain)
+ return 0;
+
+ ret = hv_iommu_attach_dev(domain, dev, old);
+
+ /*
+ * Attaching to the blocking domain only asks the hypervisor to
+ * disable translation and IOPF for the device, so it cannot fail
+ * unless there is a driver or hypervisor bug. Return the hypercall
+ * status rather than 0 so that a failure on the DMA ownership claim
+ * path (VFIO/iommufd) fails the claim instead of leaving the device
+ * unblocked. WARN since such a failure indicates a bug.
+ */
+ WARN_ON(ret);
+ return ret;
+}
+
+static int
+hv_iommu_get_logical_device_property(struct device *dev, u32 code,
+ struct hv_output_get_logical_device_property *property)
+{
+ u64 status;
+ u32 prefix;
+ unsigned long flags;
+ int ret;
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct hv_input_get_logical_device_property *input;
+ struct hv_output_get_logical_device_property *output;
+
+ ret = hv_iommu_lookup_logical_dev_id(pci_domain_nr(pdev->bus), &prefix);
+ if (ret)
+ return ret;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ output = (struct hv_output_get_logical_device_property *)(input + 1);
+ memset(input, 0, sizeof(*input));
+ input->partition_id = HV_PARTITION_ID_SELF;
+ input->logical_device_id = (u64)prefix | PCI_FUNC(pdev->devfn);
+ input->code = code;
+ status = hv_do_hypercall(HVCALL_GET_LOGICAL_DEVICE_PROPERTY, input, output);
+ *property = *output;
+
+ local_irq_restore(flags);
+
+ if (!hv_result_success(status))
+ hv_status_err(status,
+ "HVCALL_GET_LOGICAL_DEVICE_PROPERTY failed\n");
+
+ return hv_result_to_errno(status);
+}
+
+static struct iommu_device *hv_iommu_probe_device(struct device *dev)
+{
+ struct hv_iommu_endpoint *vdev;
+ struct hv_output_get_logical_device_property device_iommu_property = {0};
+
+ if (!dev_is_pci(dev))
+ return ERR_PTR(-ENODEV);
+
+ if (hv_iommu_get_logical_device_property(dev,
+ HV_LOGICAL_DEVICE_PROPERTY_PVIOMMU,
+ &device_iommu_property) ||
+ !(device_iommu_property.device_iommu & HV_DEVICE_IOMMU_ENABLED))
+ return ERR_PTR(-ENODEV);
+
+ vdev = kzalloc_obj(*vdev, GFP_KERNEL);
+ if (!vdev)
+ return ERR_PTR(-ENOMEM);
+
+ vdev->dev = dev;
+ vdev->hv_iommu = hv_iommu_device;
+ dev_iommu_priv_set(dev, vdev);
+
+ return &vdev->hv_iommu->iommu;
+}
+
+static void hv_iommu_release_device(struct device *dev)
+{
+ struct hv_iommu_endpoint *vdev = dev_iommu_priv_get(dev);
+ struct pci_dev *pdev = to_pci_dev(dev);
+
+ if (pdev->ats_enabled)
+ pci_disable_ats(pdev);
+
+ kfree(vdev);
+}
+
+static struct iommu_group *hv_iommu_device_group(struct device *dev)
+{
+ if (!dev_is_pci(dev))
+ return ERR_PTR(-ENODEV);
+
+ return pci_device_group(dev);
+}
+
+static int __init hv_initialize_static_domains(void)
+{
+ /*
+ * Clearing translation_enabled bypasses stage-1 translation, so DMA
+ * addresses are used directly as GPAs. Hyper-V requires paging and
+ * blocked domains to keep translation_enabled set.
+ */
+ const struct hv_device_domain_settings identity_settings = {
+ .flags.translation_enabled = 0,
+ };
+ const struct hv_device_domain_settings blocked_settings = {
+ .flags = {
+ .translation_enabled = 1,
+ .blocked = 1,
+ },
+ };
+ int ret;
+
+ /* Default stage-1 identity domain */
+ ret = hv_create_configure_device_domain(&hv_identity_domain,
+ &identity_settings);
+ if (ret)
+ return ret;
+
+ /* Default stage-1 blocked domain */
+ ret = hv_create_configure_device_domain(&hv_blocking_domain,
+ &blocked_settings);
+ if (ret)
+ goto delete_identity_domain;
+
+ return 0;
+
+delete_identity_domain:
+ hv_delete_device_domain(&hv_identity_domain);
+ return ret;
+}
+
+static void hv_iommu_get_resv_regions(struct device *dev,
+ struct list_head *head)
+{
+ struct iommu_resv_region *region;
+
+ region = iommu_alloc_resv_region(X86_IOMMU_MSI_RANGE_START,
+ X86_IOMMU_MSI_RANGE_END -
+ X86_IOMMU_MSI_RANGE_START + 1,
+ 0, IOMMU_RESV_MSI, GFP_KERNEL);
+ if (!region)
+ return;
+
+ list_add_tail(®ion->list, head);
+}
+
+static void hv_iommu_flush_iotlb_all(struct iommu_domain *domain)
+{
+ hv_flush_device_domain(to_hv_iommu_domain(domain));
+}
+
+static void hv_iommu_iotlb_sync(struct iommu_domain *domain,
+ struct iommu_iotlb_gather *iotlb_gather)
+{
+ hv_flush_device_domain(to_hv_iommu_domain(domain));
+
+ iommu_put_pages_list(&iotlb_gather->freelist);
+}
+
+static void hv_iommu_paging_domain_free(struct iommu_domain *domain)
+{
+ struct hv_iommu_domain *hv_domain = to_hv_iommu_domain(domain);
+
+ /* Free all remaining mappings */
+ pt_iommu_deinit(&hv_domain->pt_iommu);
+
+ hv_delete_device_domain(hv_domain);
+
+ kfree(hv_domain);
+}
+
+static const struct iommu_domain_ops hv_iommu_identity_domain_ops = {
+ .attach_dev = hv_iommu_attach_dev,
+};
+
+static const struct iommu_domain_ops hv_iommu_blocking_domain_ops = {
+ .attach_dev = hv_iommu_blocking_attach_dev,
+};
+
+static const struct iommu_domain_ops hv_iommu_paging_domain_ops = {
+ .attach_dev = hv_iommu_attach_dev,
+ IOMMU_PT_DOMAIN_OPS(x86_64),
+ .flush_iotlb_all = hv_iommu_flush_iotlb_all,
+ .iotlb_sync = hv_iommu_iotlb_sync,
+ .free = hv_iommu_paging_domain_free,
+};
+
+static struct iommu_domain *hv_iommu_domain_alloc_paging(struct device *dev)
+{
+ int ret;
+ struct hv_iommu_domain *hv_domain;
+ struct pt_iommu_x86_64_hw_info pt_info;
+ struct pt_iommu_x86_64_cfg cfg = {};
+ struct hv_device_domain_settings settings = {
+ .flags = {
+ .translation_enabled = 1,
+ },
+ };
+
+ hv_domain = kzalloc_obj(*hv_domain, GFP_KERNEL);
+ if (!hv_domain)
+ return ERR_PTR(-ENOMEM);
+
+ hv_domain->pt_iommu.nid = dev_to_node(dev);
+
+ cfg.common.hw_max_vasz_lg2 = hv_iommu_device->max_iova_width;
+ cfg.common.hw_max_oasz_lg2 = 52;
+ /*
+ * Hyper-V S1 domains use a 4-level root for IOVA widths up to
+ * 48 bits. A 5-level root is used only for wider apertures when
+ * HV_IOMMU_CAP_S1_5LVL is advertised.
+ */
+ cfg.top_level = (hv_iommu_device->max_iova_width > 48) ? 4 : 3;
+
+ ret = pt_iommu_x86_64_init(&hv_domain->pt_iommu_x86_64, &cfg, GFP_KERNEL);
+ if (ret)
+ goto err_free;
+
+ /* Constrain to page sizes the hypervisor supports */
+ hv_domain->domain.pgsize_bitmap &= hv_iommu_device->pgsize_bitmap;
+
+ hv_domain->domain.ops = &hv_iommu_paging_domain_ops;
+
+ pt_iommu_x86_64_hw_info(&hv_domain->pt_iommu_x86_64, &pt_info);
+ settings.page_table_root = pt_info.gcr3_pt;
+ settings.flags.first_stage_paging_mode = pt_info.levels == 5;
+
+ ret = hv_create_configure_device_domain(hv_domain, &settings);
+ if (ret)
+ goto err_pt_deinit;
+
+ return &hv_domain->domain;
+
+err_pt_deinit:
+ pt_iommu_deinit(&hv_domain->pt_iommu);
+err_free:
+ kfree(hv_domain);
+ return ERR_PTR(ret);
+}
+
+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,
+ .release_device = hv_iommu_release_device,
+ .device_group = hv_iommu_device_group,
+ .get_resv_regions = hv_iommu_get_resv_regions,
+ .owner = THIS_MODULE,
+ .identity_domain = &hv_identity_domain.domain,
+ .blocked_domain = &hv_blocking_domain.domain,
+ .release_domain = &hv_blocking_domain.domain,
+};
+
+static u64 hv_iommu_detect(struct hv_output_get_iommu_capabilities *cap)
+{
+ u64 status;
+ unsigned long flags;
+ struct hv_input_get_iommu_capabilities *input;
+ struct hv_output_get_iommu_capabilities *output;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ output = (struct hv_output_get_iommu_capabilities *)(input + 1);
+ memset(input, 0, sizeof(*input));
+ input->partition_id = HV_PARTITION_ID_SELF;
+ status = hv_do_hypercall(HVCALL_GET_IOMMU_CAPABILITIES, input, output);
+ *cap = *output;
+
+ local_irq_restore(flags);
+
+ return status;
+}
+
+static void __init
+hv_init_iommu_device(struct hv_iommu_dev *hv_iommu,
+ struct hv_output_get_iommu_capabilities *hv_iommu_cap)
+{
+ ida_init(&hv_iommu->domain_ids);
+
+ hv_iommu->cap = hv_iommu_cap->iommu_cap;
+ hv_iommu->max_iova_width = hv_iommu_cap->max_iova_width;
+ if (!hv_iommu_5lvl_supported(hv_iommu->cap) &&
+ hv_iommu->max_iova_width > 48) {
+ pr_info("5-level paging not supported, limiting iova width to 48.\n");
+ hv_iommu->max_iova_width = 48;
+ }
+
+ hv_iommu->first_domain = HV_DEVICE_DOMAIN_ID_DEFAULT + 1;
+ hv_iommu->last_domain = HV_DEVICE_DOMAIN_ID_NULL - 1;
+ hv_iommu->pgsize_bitmap = hv_iommu_cap->pgsize_bitmap;
+ hv_iommu_device = hv_iommu;
+}
+
+int __init hv_iommu_init(void)
+{
+ u64 status;
+ int ret = 0;
+ struct hv_iommu_dev *hv_iommu = NULL;
+ struct hv_output_get_iommu_capabilities hv_iommu_cap = {0};
+
+ if (no_iommu || iommu_detected)
+ return -ENODEV;
+
+ if (!hv_is_hyperv_initialized())
+ return -ENODEV;
+
+ status = hv_iommu_detect(&hv_iommu_cap);
+ if (!hv_result_success(status)) {
+ if (hv_result(status) == HV_STATUS_INVALID_HYPERCALL_CODE)
+ return -ENODEV;
+
+ hv_status_err(status, "HVCALL_GET_IOMMU_CAPABILITIES failed\n");
+ return hv_result_to_errno(status);
+ }
+
+ if (!hv_iommu_present(hv_iommu_cap.iommu_cap))
+ return -ENODEV;
+
+ if (!hv_iommu_s1_domain_supported(hv_iommu_cap.iommu_cap)) {
+ pr_err("stage-1 translation not supported: cap=%#llx\n",
+ hv_iommu_cap.iommu_cap);
+ return -ENODEV;
+ }
+
+ /*
+ * Require the base page size. The domain page-size bitmap is later
+ * restricted to the sizes supported by both iommupt and Hyper-V.
+ */
+ if (!(hv_iommu_cap.pgsize_bitmap & PAGE_SIZE)) {
+ pr_err("unsupported page sizes: pgsize_bitmap=0x%llx\n",
+ hv_iommu_cap.pgsize_bitmap);
+ return -ENODEV;
+ }
+
+ iommu_detected = 1;
+ pci_request_acs();
+
+ hv_iommu = kzalloc_obj(*hv_iommu, GFP_KERNEL);
+ if (!hv_iommu)
+ return -ENOMEM;
+
+ hv_init_iommu_device(hv_iommu, &hv_iommu_cap);
+
+ ret = hv_initialize_static_domains();
+ if (ret)
+ goto err_free;
+
+ ret = iommu_device_sysfs_add(&hv_iommu->iommu, NULL, NULL, "%s", "hv-iommu");
+ if (ret) {
+ pr_err("iommu_device_sysfs_add failed: %d\n", ret);
+ goto err_delete_static_domains;
+ }
+
+ ret = iommu_device_register(&hv_iommu->iommu, &hv_iommu_ops, NULL);
+ if (ret) {
+ pr_err("iommu_device_register failed: %d\n", ret);
+ goto err_sysfs_remove;
+ }
+
+ pr_info("initialized: %u-bit IOVA aperture, page-size bitmap %#llx\n",
+ hv_iommu->max_iova_width, hv_iommu->pgsize_bitmap);
+ return 0;
+
+err_sysfs_remove:
+ iommu_device_sysfs_remove(&hv_iommu->iommu);
+err_delete_static_domains:
+ hv_delete_device_domain(&hv_blocking_domain);
+ hv_delete_device_domain(&hv_identity_domain);
+err_free:
+ kfree(hv_iommu);
+ return ret;
+}
diff --git a/drivers/iommu/hyperv/iommu.h b/drivers/iommu/hyperv/iommu.h
new file mode 100644
index 000000000000..a8566d511102
--- /dev/null
+++ b/drivers/iommu/hyperv/iommu.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Hyper-V IOMMU driver.
+ *
+ * Copyright (C) 2024-2025, Microsoft, Inc.
+ *
+ */
+
+#ifndef _HYPERV_IOMMU_H
+#define _HYPERV_IOMMU_H
+
+struct hv_iommu_dev {
+ struct iommu_device iommu;
+ struct ida domain_ids;
+
+ /* Device configuration */
+ u8 max_iova_width;
+ u8 max_pasid_width;
+ u64 cap;
+ u64 pgsize_bitmap;
+
+ u64 first_domain;
+ u64 last_domain;
+};
+
+struct hv_iommu_domain {
+ union {
+ struct iommu_domain domain;
+ struct pt_iommu pt_iommu;
+ struct pt_iommu_x86_64 pt_iommu_x86_64;
+ };
+ struct hv_iommu_dev *hv_iommu;
+ struct hv_input_device_domain device_domain;
+ u64 pgsize_bitmap;
+};
+
+PT_IOMMU_CHECK_DOMAIN(struct hv_iommu_domain, pt_iommu, domain);
+PT_IOMMU_CHECK_DOMAIN(struct hv_iommu_domain, pt_iommu_x86_64.iommu, domain);
+
+struct hv_iommu_endpoint {
+ struct device *dev;
+ struct hv_iommu_dev *hv_iommu;
+ struct hv_iommu_domain *hv_domain;
+};
+
+#define to_hv_iommu_domain(d) \
+ container_of(d, struct hv_iommu_domain, domain)
+
+#endif /* _HYPERV_IOMMU_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v3 5/5] iommu/hyperv: Add page-selective IOTLB flush support
2026-08-11 15:50 [PATCH v3 0/5] Hyper-V: Add para-virtualized IOMMU support for Linux guests Yu Zhang
` (3 preceding siblings ...)
2026-08-11 15:50 ` [PATCH v3 4/5] iommu/hyperv: Add para-virtualized IOMMU support for Hyper-V guest Yu Zhang
@ 2026-08-11 15:50 ` Yu Zhang
4 siblings, 0 replies; 7+ messages in thread
From: Yu Zhang @ 2026-08-11 15:50 UTC (permalink / raw)
To: linux-kernel, linux-hyperv, iommu, linux-pci, linux-arch, x86
Cc: wei.liu, kys, haiyangz, decui, longli, joro, will, robin.murphy,
bhelgaas, kwilczynski, lpieralisi, mani, robh, arnd, jgg,
mhklinux, jacob.pan, tgopinath, easwar.hariharan, mrathor,
baolu.lu, suravee.suthikulpanit, vasant.hegde
Add page-selective IOTLB flush using HVCALL_FLUSH_DEVICE_DOMAIN_LIST.
This hypercall accepts a list of (page_number, page_mask_shift) entries,
enabling finer-grained IOTLB invalidation compared to the domain-wide
HVCALL_FLUSH_DEVICE_DOMAIN used by hv_iommu_flush_iotlb_all().
hv_iommu_calc_flush_range() computes the smallest power-of-two aligned
range that covers the target IOVA region, producing a single flush
descriptor. This may over-flush when the range is not naturally aligned,
matching the approach used by Intel VT-d PSI. If the page-selective
flush fails, the code falls back to a full domain flush.
Signed-off-by: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
Signed-off-by: Yu Zhang <zhangyu1@linux.microsoft.com>
---
drivers/iommu/hyperv/hv-iommu-guest.c | 73 ++++++++++++++++++++++++++-
include/hyperv/hvgdk_mini.h | 1 +
include/hyperv/hvhdk_mini.h | 17 +++++++
3 files changed, 90 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/hyperv/hv-iommu-guest.c b/drivers/iommu/hyperv/hv-iommu-guest.c
index 2a00353ce733..060c2efd6bf0 100644
--- a/drivers/iommu/hyperv/hv-iommu-guest.c
+++ b/drivers/iommu/hyperv/hv-iommu-guest.c
@@ -9,6 +9,7 @@
#define pr_fmt(fmt) "Hyper-V pvIOMMU: " fmt
#define dev_fmt(fmt) pr_fmt(fmt)
+#include <linux/hyperv.h>
#include <linux/iommu.h>
#include <linux/pci.h>
#include <linux/dma-map-ops.h>
@@ -408,10 +409,79 @@ static void hv_iommu_flush_iotlb_all(struct iommu_domain *domain)
hv_flush_device_domain(to_hv_iommu_domain(domain));
}
+/*
+ * Calculate the minimal power-of-two aligned range that covers [start, end]
+ * (end is inclusive). Returns a single (page_number, page_mask_shift)
+ * descriptor that may over-flush when the range is not naturally aligned.
+ */
+static void
+hv_iommu_calc_flush_range(unsigned long start, unsigned long end,
+ union hv_iommu_flush_va *va)
+{
+ unsigned int sz_lg2;
+
+ sz_lg2 = fls_long(start ^ end);
+ if (sz_lg2 < HV_HYP_PAGE_SHIFT)
+ sz_lg2 = HV_HYP_PAGE_SHIFT;
+
+ /*
+ * A valid IOVA range shall not span bit 63. Use the maximum mask
+ * so the host can safely perform a full flush.
+ */
+ if (WARN_ON_ONCE(sz_lg2 >= BITS_PER_LONG)) {
+ va->as_uint64 = 0;
+ va->page_mask_shift =
+ BITS_PER_LONG - HV_HYP_PAGE_SHIFT;
+ return;
+ }
+
+ va->page_number =
+ (start & GENMASK(BITS_PER_LONG - 1, sz_lg2)) >>
+ HV_HYP_PAGE_SHIFT;
+ va->page_mask_shift = sz_lg2 - HV_HYP_PAGE_SHIFT;
+}
+
+static void hv_flush_device_domain_list(struct hv_iommu_domain *hv_domain,
+ struct iommu_iotlb_gather *iotlb_gather)
+{
+ u64 status;
+ unsigned long flags;
+ struct hv_input_flush_device_domain_list *input;
+
+ local_irq_save(flags);
+
+ input = *this_cpu_ptr(hyperv_pcpu_input_arg);
+ /* Clear the fixed header and the single range entry. */
+ memset(input, 0, struct_size(input, iova_list, 1));
+
+ input->device_domain = hv_domain->device_domain;
+ input->flags |= HV_FLUSH_DEVICE_DOMAIN_LIST_IOMMU_FORMAT;
+ hv_iommu_calc_flush_range(iotlb_gather->start, iotlb_gather->end,
+ &input->iova_list[0]);
+
+ status = hv_do_rep_hypercall(HVCALL_FLUSH_DEVICE_DOMAIN_LIST,
+ 1, 0, input, NULL);
+
+ if (WARN_ON_ONCE(!hv_result_success(status))) {
+ /* Page-selective flush failed, fall back to full flush. */
+ struct hv_input_flush_device_domain *flush_all = (void *)input;
+
+ memset(flush_all, 0, sizeof(*flush_all));
+ flush_all->device_domain = hv_domain->device_domain;
+ status = hv_do_hypercall(HVCALL_FLUSH_DEVICE_DOMAIN,
+ flush_all, NULL);
+ WARN(!hv_result_success(status),
+ "HVCALL_FLUSH_DEVICE_DOMAIN fallback also failed: %lld\n",
+ status);
+ }
+
+ local_irq_restore(flags);
+}
+
static void hv_iommu_iotlb_sync(struct iommu_domain *domain,
struct iommu_iotlb_gather *iotlb_gather)
{
- hv_flush_device_domain(to_hv_iommu_domain(domain));
+ hv_flush_device_domain_list(to_hv_iommu_domain(domain), iotlb_gather);
iommu_put_pages_list(&iotlb_gather->freelist);
}
@@ -464,6 +534,7 @@ static struct iommu_domain *hv_iommu_domain_alloc_paging(struct device *dev)
cfg.common.hw_max_vasz_lg2 = hv_iommu_device->max_iova_width;
cfg.common.hw_max_oasz_lg2 = 52;
+ cfg.common.features |= BIT(PT_FEAT_FLUSH_RANGE);
/*
* Hyper-V S1 domains use a 4-level root for IOVA widths up to
* 48 bits. A 5-level root is used only for wider apertures when
diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h
index 5bdbb44da112..eaaf87171478 100644
--- a/include/hyperv/hvgdk_mini.h
+++ b/include/hyperv/hvgdk_mini.h
@@ -496,6 +496,7 @@ union hv_vp_assist_msr_contents { /* HV_REGISTER_VP_ASSIST_PAGE */
#define HVCALL_GET_GPA_PAGES_ACCESS_STATES 0x00c9
#define HVCALL_CONFIGURE_DEVICE_DOMAIN 0x00ce
#define HVCALL_FLUSH_DEVICE_DOMAIN 0x00d0
+#define HVCALL_FLUSH_DEVICE_DOMAIN_LIST 0x00d1
#define HVCALL_ACQUIRE_SPARSE_SPA_PAGE_HOST_ACCESS 0x00d7
#define HVCALL_RELEASE_SPARSE_SPA_PAGE_HOST_ACCESS 0x00d8
#define HVCALL_MODIFY_SPARSE_GPA_PAGE_HOST_VISIBILITY 0x00db
diff --git a/include/hyperv/hvhdk_mini.h b/include/hyperv/hvhdk_mini.h
index 1e3eac99886a..25671ee7056d 100644
--- a/include/hyperv/hvhdk_mini.h
+++ b/include/hyperv/hvhdk_mini.h
@@ -674,4 +674,21 @@ struct hv_input_flush_device_domain {
u32 reserved;
} __packed;
+union hv_iommu_flush_va {
+ u64 as_uint64;
+ struct {
+ u64 page_mask_shift : 6;
+ u64 reserved : 6;
+ u64 page_number : 52;
+ };
+} __packed;
+
+struct hv_input_flush_device_domain_list {
+ struct hv_input_device_domain device_domain;
+#define HV_FLUSH_DEVICE_DOMAIN_LIST_IOMMU_FORMAT BIT(0)
+ u32 flags;
+ u32 reserved;
+ union hv_iommu_flush_va iova_list[];
+} __packed;
+
#endif /* _HV_HVHDK_MINI_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 7+ messages in thread