Skip to contentSkip to conversation

Hyper-V: Add para-virtualized IOMMU support for Linux guests

PATCH Applied Yu Zhang 4 participants 13 messages Sep 30 – Oct 9

Subsystems (9)
lore
Share & tools
lore

Yu Zhang

author
#0 lore raw

v6 cover Hyper-V: Add para-virtualized IOMMU support for Linux guests

This patch series introduces a para-virtualized IOMMU driver for Linux guests running on Microsoft Hyper-V. The driver enables two primary use cases:
1) In-kernel DMA protection for devices assigned to the guest.
2) Device assignment to guest user space (e.g., via VFIO).

The driver implements the following core functionality:
* Hypercall-based Enumeration
Unlike traditional ACPI-based discovery (e.g., DMAR/IVRS), this driver enumerates the Hyper-V IOMMU capabilities directly via hypercalls. This approach allows the guest to discover IOMMU presence and features without requiring specific virtual firmware extensions or modifications.

* Domain Management
The driver manages IOMMU domains through a new set of Hyper-V hypercall interfaces, handling domain allocation and attachment for endpoint devices.

* Nested Translation Support
This implementation leverages guest-managed stage-1 I/O page tables nested with host stage-2 translations. It is built upon the consolidated IOMMU page table framework (IOMMU_PT). This design eliminates the need for emulating map operations. Both Intel VT-d and AMD IOMMU platforms are supported.

* IOTLB Invalidation
IOTLB invalidation requests are marshaled and issued to the hypervisor through the same hypercall mechanism. Both domain- selective and page-selective flushes are supported.

Implementation Notes:
* Platform Support
The current implementation targets x86-64 platforms with Intel VT-d and AMD IOMMU hardware.

* MSI Region Handling
The standard x86 MSI interrupt range
(0xfee00000 - 0xfeefffff) is reserved from normal IOVA mappings. Future updates may allow this configuration to be queried via hypercalls if new hardware platforms are to be supported.

* Reserved Regions (RMRR)
There is currently no requirement to support assigned devices with ACPI RMRR limitations. Consequently, this patch series does not specify or query reserved memory regions.

* Guest Hibernation
Guest hibernation and resume are not supported by this initial implementation. The current pvIOMMU interface does not define how Hyper-V device-domain objects and device attachments are restored.

Testing:
Runtime validation from earlier revisions:
- NVMe devices assigned to the guest on AMD platforms, tested with fio.
- Intel DSA devices assigned to the guest, tested with dmatest.
- dma_map_benchmark for DMA mapping performance evaluation.

Changelog:

v5[1] -> v6:
- Added Jason Gunthorpe's Reviewed-by tags for patches 3/4/5 and Michael Kelley's Reviewed-by tag for patch 5.
- iommu/x86: architectural MSI reserved region:
- Renamed the helper to iommu_add_resv_x86_msi_region() and moved list insertion into it.
- Updated the Intel and AMD callers, preserving their behavior on allocation failure.
- iommu/hyperv: para-virtualized IOMMU:
- Selected IOMMU_DMA instead of the redundant IOMMU_API and IOMMU_IOVA selections
- Simplified the reserved-region callback to use the updated helper.
- Wrapped long lines to fit in 80 cols.
- Added comments when usging existing per-CPU input page for both input and output.

v4[2] -> v5:
- Added Michael Kelley's Reviewed-by tags for patch 1/2/3/4.

- hv: logical device ID registry:
- Used four-digit hexadecimal values to print PCI domain numbers.
- Included the PCI domain number in the unregister failure warning.

- iommu/hyperv: para-virtualized IOMMU:
- Removed the unused per-device endpoint structure, allocation, private pointer, and release_device() callback.
- Clarified the logical device ID lookup failure message.
- Read the ATS capability from the IOMMU associated with the domain instead of the global IOMMU pointer.

- iommu/hyperv: page-selective IOTLB flush:
- Unified normal and full-range descriptor construction around the aligned range start.
- Used max() to express the minimum Hyper-V page granularity.
- Reported Hyper-V status values in warnings when the flush fails.

v3[3] -> v4:
- Added Jacob Pan's Reviewed-by tag for patch 1/4/5.

- hyperv: hypercall interfaces:
- Added the authoritative Hyper-V type names to imported definitions.
- Added HV_STATUS_DEVICE_ALREADY_IN_DOMAIN and mapped it to -EEXIST.

- hv: logical device ID registry:
- Made the registry independent of CONFIG_HYPERV_PVIOMMU.
- Renamed the APIs to hv_pci_xxx_dev_id(),
- Reworked initialization and error cleanup to propagate failures.
- Return -EEXIST with a warning for duplicate registration.

- iommu/x86: architectural MSI reserved region:
- Replaced the exported range constants with

    iommu_alloc_resv_x86_msi_region().

- iommu/hyperv: para-virtualized IOMMU:
- Accepted HV_STATUS_DEVICE_ALREADY_IN_DOMAIN as a successful attach, and removed the blocking-domain-specific duplicate check.
- Removed the redundant ATS disable from release_device(); ATS is disabled when attaching the release domain.

- iommu/hyperv: page-selective IOTLB flush:
- Removed the redundant include of linux/hyperv.h.
- Documented that the list flush also invalidates non-leaf page-walk caches before detached page-table pages are freed.

v2[4] -> v3:
- Added a separate patch defining the common x86 architectural MSI reserved range and converted the Intel, AMD, and Hyper-V IOMMU drivers to use it.

- hyperv: hypercall interfaces:
- Used BIT_ULL() for 64-bit capability flags.
- Matched the create-device-domain input layout to the 32-byte Hyper-V ABI.
- Corrected the page-selective flush range layout to 6/6/52 bits.

- hv: logical device ID registry:
- Replaced the linear list with an rhashtable for the sparse PCI domain number mapping.
- Used RCU-protected lookup and deferred freeing on removal.
- Simplified the registration and cleanup paths.

- iommu/hyperv: para-virtualized IOMMU:
- Renamed the guest driver source to hv-iommu-guest.c.
- Enabled ATS only after a device-domain attachment succeeds, and disabled ATS before attaching the blocking domain.
- Removed the obsolete deferred-flush capability, unused geometry, and redundant device-private-data clearing.
- Made the Hyper-V domain settings explicit at each caller instead of deriving them from the Linux domain type.
- Added transactional create/configure domain handling with rollback.
- Required only the base page size and masked the domain page-size bitmap with the Hyper-V-supported bitmap.
- Silenced detection for unsupported hosts while retaining errors for actual hypercall and capability failures.
- Clarified Kconfig support for assigned PCI devices and documented the lack of guest hibernation/resume support.

- iommu/hyperv: page-selective IOTLB flush:
- Removed the inclusive-end overflow and kept the range calculation in byte addresses until the final page conversion.
- Cleared both the fixed hypercall header and the range entry.
- Warned on selective-flush failure before falling back to a full-domain flush.

v1[5] -> v2:
- Dropped the "move to subdirectory" patch; the directory now exists upstream.

- hv: logical device ID registry:
- Moved the registry to hv_common.c so it can be shared, and derived the prefix via a shared helper instead of caching it in pci-hyperv's private struct.
- Moved the lookup out of the irq-disabled region (PREEMPT_RT).

- iommu/hyperv: para-virtualized IOMMU:
- Removed the unused detach_dev op.
- Rejected a hypervisor not advertising x86 page sizes instead of masking and warning.
- Statically initialized the identity and blocking domains.
- Gave the blocking domain its own attach op, which returns the hypercall status and WARNs on failure.

- iommu/hyperv: page-selective IOTLB flush:
- Used a single descriptor covering a slightly larger power-of-two range, instead of splitting the range into multiple descriptors.
- Fixed the inclusive-end corner case in the flush range calculation.

RFC v1[6] -> v1[5]:
- Scoped platform support to x86 only (Intel VT-d and AMD IOMMU); initialization now uses x86_init.iommu.iommu_init
- Added page-selective IOTLB flush support
- Disable device ATS in hv_iommu_release_device()
- Addressed review comments from Michael Kelley:
- Reversed dependency: pvIOMMU exports registration API for pci-hyperv to call, instead of pci-hyperv exporting hv_build_logical_dev_id()
- Dropped separate output page allocation patch; hypercall input and output now share the same per-CPU page
- Cleaned up Kconfig (removed PCI_HYPERV dependency, unnecessary selects)
- Removed dev_list, per-domain spinlock, and syscore_ops
- Removed forward declarations by reordering functions
- Fixed typos, cleaned up Kconfig selects, improved pr_info messages, etc.

[1] v5:
https://lore.kernel.org/linux-hyperv/[email protected]/
[2] v4:
https://lore.kernel.org/linux-hyperv/[email protected]/
[3] v3:
https://lore.kernel.org/linux-hyperv/[email protected]/
[4] v2:
https://lore.kernel.org/linux-hyperv/[email protected]/
[5] v1:
https://lore.kernel.org/linux-hyperv/[email protected]/
[6] RFC v1:
https://lore.kernel.org/linux-hyperv/[email protected]/

Wei Liu (1):
hyperv: Introduce new hypercall interfaces used by Hyper-V guest IOMMU

Yu Zhang (4):
Drivers: hv: Add logical device ID registry for vPCI devices iommu/x86: Add architectural MSI reserved region helper iommu/hyperv: Add para-virtualized IOMMU support for Hyper-V guest iommu/hyperv: Add page-selective IOTLB flush support

 arch/x86/hyperv/hv_init.c             |   4 +
 arch/x86/include/asm/iommu.h          |  20 +
 arch/x86/include/asm/mshyperv.h       |   4 +
 drivers/hv/hv_common.c                | 115 +++++
 drivers/iommu/Kconfig                 |   1 +
 drivers/iommu/amd/iommu.c             |   8 +-
 drivers/iommu/hyperv/Kconfig          |  15 +
 drivers/iommu/hyperv/Makefile         |   1 +
 drivers/iommu/hyperv/hv-iommu-guest.c | 682 ++++++++++++++++++++++++++
 drivers/iommu/hyperv/iommu.h          |  44 ++
 drivers/iommu/intel/iommu.c           |  11 +-
 drivers/pci/controller/pci-hyperv.c   |  21 +-
 include/asm-generic/mshyperv.h        |   4 +
 include/hyperv/hvgdk_mini.h           |  10 +
 include/hyperv/hvhdk_mini.h           | 155 ++++++
 include/linux/hyperv.h                |   8 +
 16 files changed, 1082 insertions(+), 21 deletions(-)
 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

base-commit: a243ede718463c7b481878656f1ff32a0ce0fd54

Signature · 1 line
2.52.0
author
#1 lore raw

PATCH v6 1/5 hyperv: Introduce new hypercall interfaces used by Hyper-V guest IOMMU

From: Wei Liu <[email protected]>

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).

---
 drivers/hv/hv_common.c      |   1 +
 include/hyperv/hvgdk_mini.h |   9 +++
 include/hyperv/hvhdk_mini.h | 137 ++++++++++++++++++++++++++++++++++++
 3 files changed, 147 insertions(+)
drivers/hv/hv_common.c +1 -0
index 31256cb22b39..8896fb4809b5 100644
@@ -808,6 +808,7 @@ static const struct hv_status_info hv_status_infos[] = {
 	_STATUS_INFO(HV_STATUS_PROCESSOR_FEATURE_NOT_SUPPORTED,	-EOPNOTSUPP),
 	_STATUS_INFO(HV_STATUS_INVALID_LP_INDEX,		-EINVAL),
 	_STATUS_INFO(HV_STATUS_INVALID_REGISTER_VALUE,		-EINVAL),
+	_STATUS_INFO(HV_STATUS_DEVICE_ALREADY_IN_DOMAIN,	-EEXIST),
 	_STATUS_INFO(HV_STATUS_OPERATION_FAILED,		-EIO),
 	_STATUS_INFO(HV_STATUS_TIME_OUT,			-EIO),
 	_STATUS_INFO(HV_STATUS_CALL_PENDING,			-EIO),
include/hyperv/hvgdk_mini.h +9 -0
index 6a4e8b9d570f..5d1338197eee 100644
@@ -37,6 +37,7 @@ struct hv_u128 {
 #define HV_STATUS_PROCESSOR_FEATURE_NOT_SUPPORTED	0x20
 #define HV_STATUS_INVALID_LP_INDEX			0x41
 #define HV_STATUS_INVALID_REGISTER_VALUE		0x50
+#define HV_STATUS_DEVICE_ALREADY_IN_DOMAIN		0x66
 #define HV_STATUS_OPERATION_FAILED			0x71
 #define HV_STATUS_INSUFFICIENT_ROOT_MEMORY		0x73
 #define HV_STATUS_INSUFFICIENT_CONTIGUOUS_MEMORY	0x75
@@ -486,10 +487,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 +509,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 */
include/hyperv/hvhdk_mini.h +137 -0

141 lines not on this page · raw message

Signature · 1 line
2.52.0
#2 lore raw

Re: [PATCH v6 1/5] hyperv: Introduce new hypercall interfaces used by Hyper-V guest IOMMU

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 21 lines

From: Wei Liu <[email protected]>

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).

Quoted 5 lines

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
author
#3 lore raw

PATCH v6 2/5 Drivers: hv: Add logical device ID registry for vPCI devices

In reply to Yu Zhang

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.

---
 drivers/hv/hv_common.c              | 114 ++++++++++++++++++++++++++++
 drivers/pci/controller/pci-hyperv.c |  21 +++--
 include/asm-generic/mshyperv.h      |   4 +
 include/linux/hyperv.h              |   8 ++
 4 files changed, 142 insertions(+), 5 deletions(-)
drivers/hv/hv_common.c +114 -0
index 8896fb4809b5..4f3bb9b4e87c 100644
@@ -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,22 @@ static struct ctl_table_header *hv_ctl_table_hdr;
 u8 * __percpu *hv_synic_eventring_tail;
 EXPORT_SYMBOL_GPL(hv_synic_eventring_tail);
 
+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),
+};
+
 /*
  * Hyper-V specific initialization and shutdown code that is
  * common across all architectures.  Called from architecture
@@ -86,6 +103,11 @@ EXPORT_SYMBOL_GPL(hv_synic_eventring_tail);
 
 void __init hv_common_free(void)
 {
+	if (hv_pci_bus_ht_initialized) {
+		rhashtable_destroy(&hv_pci_bus_ht);
+		hv_pci_bus_ht_initialized = false;
+	}
+
 	unregister_sysctl_table(hv_ctl_table_hdr);
 	hv_ctl_table_hdr = NULL;
 
@@ -315,6 +337,7 @@ u8 __init get_vtl(void)
 int __init hv_common_init(void)
 {
 	int i;
+	int ret;
 	union hv_hypervisor_version_info version;
 
 	/* Get information about the Microsoft Hypervisor version */
@@ -394,6 +417,13 @@ int __init hv_common_init(void)
 	for (i = 0; i < nr_cpu_ids; i++)
 		hv_vp_index[i] = VP_INVAL;
 
+	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;
+
 	return 0;
 }
 
@@ -862,3 +892,87 @@ const char *hv_result_to_string(u64 status)
 	return "Unknown";
 }
 EXPORT_SYMBOL_GPL(hv_result_to_string);
+
+/*
+ * Logical device ID registry for Hyper-V PCI buses. The pci-hyperv
+ * driver registers each bus's logical device ID prefix before scanning
+ * its devices. Consumers look up the prefix by PCI domain number when
+ * building logical device IDs for Hyper-V interfaces.
+ */
+int hv_pci_register_dev_id(int pci_domain_nr, u32 logical_dev_id_prefix)
+{
+	struct hv_pci_busdata *bus, *new;
+	int ret;
+
+	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);
+		goto free_new;
+	}
+
+	if (WARN_ONCE(bus != NULL,
+		      "Hyper-V PCI domain %04x is already registered\n",
+		      pci_domain_nr)) {
+		ret = -EEXIST;
+		goto free_new;
+	}
+
+	return 0;
+
+free_new:
+	kfree(new);
+	return ret;
+}
+EXPORT_SYMBOL_FOR_MODULES(hv_pci_register_dev_id, "pci-hyperv");
+
+void hv_pci_unregister_dev_id(int pci_domain_nr)
+{
+	struct hv_pci_busdata *bus;
+	int ret = -ENOENT;
+
+	rcu_read_lock();
+	bus = rhashtable_lookup(&hv_pci_bus_ht, &pci_domain_nr,
+				hv_pci_bus_ht_params);
+	if (bus)
+		ret = rhashtable_remove_fast(&hv_pci_bus_ht, &bus->node,
+					     hv_pci_bus_ht_params);
+	rcu_read_unlock();
+
+	if (WARN_ONCE(ret,
+		      "Failed to unregister Hyper-V PCI domain %04x\n",
+		      pci_domain_nr))
+		return;
+
+	kfree_rcu(bus, rcu);
+}
+EXPORT_SYMBOL_FOR_MODULES(hv_pci_unregister_dev_id, "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_pci_lookup_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;
+}
drivers/pci/controller/pci-hyperv.c +16 -5
index 89816a2bd7cd..db70be47af6e 100644
@@ -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);
 
@@ -3716,6 +3713,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);
@@ -3858,13 +3856,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_pci_register_dev_id(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_pci_unregister_dev_id(dom);
 free_windows:
 	hv_pci_free_bridge_windows(hbus);
 exit_d0:
@@ -3978,6 +3985,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);
@@ -3995,6 +4004,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_pci_unregister_dev_id(dom);
 	}
 
 	hv_pci_bus_exit(hdev, false);
include/asm-generic/mshyperv.h +4 -0
index bf601d67cecb..34d4c332449c 100644
@@ -73,6 +73,10 @@ extern enum hv_partition_type hv_curr_partition_type;
 extern void * __percpu *hyperv_pcpu_input_arg;
 extern void * __percpu *hyperv_pcpu_output_arg;
 
+int hv_pci_register_dev_id(int pci_domain_nr, u32 logical_dev_id_prefix);
+void hv_pci_unregister_dev_id(int pci_domain_nr);
+int hv_pci_lookup_dev_id(int pci_domain_nr, u32 *prefix);
+
 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);
include/linux/hyperv.h +8 -0
index 9e109d91aa14..e61f9a4cb7c3 100644
@@ -1299,6 +1299,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);
Signature · 1 line
2.52.0
#4 lore raw

Re: [PATCH v6 2/5] Drivers: hv: Add logical device ID registry for vPCI devices

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] pci-hyperv: logical device ID leaked on remove after failed resume --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] pci-hyperv: logical device ID leaked on remove after failed resume --

commit 60c40edaf8c7fd1e4ba684f1b2ec029a89674f03
Author: Yu Zhang <[email protected]>
Drivers: hv: Add logical device ID registry for vPCI devices

This commit adds a registry in hv_common.c that maps a PCI domain number to its logical device ID prefix. The vPCI bus driver registers the prefix when a bus is probed and unregisters it when the bus is removed.

Quoted 4 lines
drivers/pci/controller/pci-hyperv.c +0 -0
index 89816a2bd7cd3..db70be47af6e1 100644

[ ... ]

Quoted 10 lines
drivers/pci/controller/pci-hyperv.c +2 -0
@@ -3978,6 +3985,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);

drivers/pci/controller/pci-hyperv.c static void hv_pci_remove(struct hv_device *hdev) · line 4008

Quoted 5 lines
drivers/pci/controller/pci-hyperv.c +2 -0
@@ -3995,6 +4004,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_pci_unregister_dev_id(dom);
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: Medium]
Does this code leak the logical device ID if hv_pci_resume() fails?

If the device suspends and hv_pci_resume() fails, it leaves hbus->state as hv_pcibus_init (or if hv_pci_suspend() fails, it leaves it as hv_pcibus_removing). Later, when the device is unbound and hv_pci_remove() is called, it skips this if (hbus->state == hv_pcibus_installed) block, omitting the required hv_pci_unregister_dev_id() call.

This will cause a memory leak of struct hv_pci_busdata and its hash table entry. Because the domain remains registered, any subsequent probe (e.g., driver re-bind) will fail with -EEXIST in hv_pci_register_dev_id(), permanently disabling the device until reboot.

Although the underlying hbus->state mishandling is pre-existing, the leaked registry and the resulting -EEXIST probe failure are introduced in this patch.

Quoted 3 lines
drivers/pci/controller/pci-hyperv.c +0 -0
 	}
 
 	hv_pci_bus_exit(hdev, false);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
author
#5 lore raw

PATCH v6 3/5 iommu/x86: Add architectural MSI reserved region helper

In reply to Yu Zhang

Intel and AMD IOMMU drivers both reserve the x86 architectural MSI address window from normal IOVA mappings, but each open-codes the range, allocation attributes, and insertion into the reserved-region list.

Add iommu_add_resv_x86_msi_region() to encapsulate those operations, and convert the Intel and AMD drivers to use it. Return an error on allocation failure so callers can preserve their existing error paths.

---
 arch/x86/include/asm/iommu.h | 20 ++++++++++++++++++++
 drivers/iommu/amd/iommu.c    |  8 +-------
 drivers/iommu/intel/iommu.c  | 11 ++---------
 3 files changed, 23 insertions(+), 16 deletions(-)
arch/x86/include/asm/iommu.h +20 -0
index 3be2451e7bc8..4e1e55c3c09a 100644
@@ -4,6 +4,11 @@
 
 #include <linux/acpi.h>
 
+#ifdef CONFIG_IOMMU_API
+#include <linux/iommu.h>
+#include <linux/sizes.h>
+#endif
+
 #include <asm/e820/api.h>
 
 extern int force_iommu, no_iommu;
@@ -21,6 +26,21 @@ extern bool x86_swiotlb_enable;
 /* 10 seconds */
 #define DMAR_OPERATION_TIMEOUT ((cycles_t) tsc_khz*10*1000)
 
+#ifdef CONFIG_IOMMU_API
+static inline int iommu_add_resv_x86_msi_region(struct list_head *head)
+{
+	struct iommu_resv_region *region;
+
+	region = iommu_alloc_resv_region(0xfee00000, SZ_1M, 0,
+					 IOMMU_RESV_MSI, GFP_KERNEL);
+	if (!region)
+		return -ENOMEM;
+
+	list_add_tail(&region->list, head);
+	return 0;
+}
+#endif
+
 static inline int __init
 arch_rmrr_sanity_check(struct acpi_dmar_reserved_memory *rmrr)
 {
drivers/iommu/amd/iommu.c +1 -7
index 56262f6b1f70..391cec4d86c0 100644
@@ -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)
 
@@ -3137,12 +3135,8 @@ static void amd_iommu_get_resv_regions(struct device *dev,
 		list_add_tail(&region->list, head);
 	}
 
-	region = iommu_alloc_resv_region(MSI_RANGE_START,
-					 MSI_RANGE_END - MSI_RANGE_START + 1,
-					 0, IOMMU_RESV_MSI, GFP_KERNEL);
-	if (!region)
+	if (iommu_add_resv_x86_msi_region(head))
 		return;
-	list_add_tail(&region->list, head);
 
 	if (amd_iommu_ht_range_ignore())
 		return;
drivers/iommu/intel/iommu.c +2 -9
index 2e3b3ab216f8..74fb5c894fe5 100644
@@ -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
@@ -3412,7 +3410,6 @@ static void intel_iommu_get_resv_regions(struct device *device,
 					 struct list_head *head)
 {
 	int prot = DMA_PTE_READ | DMA_PTE_WRITE;
-	struct iommu_resv_region *reg;
 	struct dmar_rmrr_unit *rmrr;
 	struct device *i_dev;
 	int i;
@@ -3448,6 +3445,7 @@ static void intel_iommu_get_resv_regions(struct device *device,
 #ifdef CONFIG_INTEL_IOMMU_FLOPPY_WA
 	if (dev_is_pci(device)) {
 		struct pci_dev *pdev = to_pci_dev(device);
+		struct iommu_resv_region *reg;
 
 		if ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) {
 			reg = iommu_alloc_resv_region(0, 1UL << 24, prot,
@@ -3459,12 +3457,7 @@ 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,
-				      0, IOMMU_RESV_MSI, GFP_KERNEL);
-	if (!reg)
-		return;
-	list_add_tail(&reg->list, head);
+	iommu_add_resv_x86_msi_region(head);
 }
 
 static struct iommu_group *intel_iommu_device_group(struct device *dev)
Signature · 1 line
2.52.0
#6 lore raw

Re: [PATCH v6 3/5] iommu/x86: Add architectural MSI reserved region helper

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines

Intel and AMD IOMMU drivers both reserve the x86 architectural MSI address window from normal IOVA mappings, but each open-codes the range, allocation attributes, and insertion into the reserved-region list.

Add iommu_add_resv_x86_msi_region() to encapsulate those operations, and convert the Intel and AMD drivers to use it. Return an error on allocation failure so callers can preserve their existing error paths.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
maintainer Reviewed-by on v6 3/5
#7 lore raw

Re: [PATCH v6 3/5] iommu/x86: Add architectural MSI reserved region helper

In reply to Yu Zhang
Quoted 11 lines · Yu Zhang

Intel and AMD IOMMU drivers both reserve the x86 architectural MSI address window from normal IOVA mappings, but each open-codes the range, allocation attributes, and insertion into the reserved-region list.

Add iommu_add_resv_x86_msi_region() to encapsulate those operations, and convert the Intel and AMD drivers to use it. Return an error on allocation failure so callers can preserve their existing error paths.

-Vasant

author
#8 lore raw

PATCH v6 4/5 iommu/hyperv: Add para-virtualized IOMMU support for Hyper-V guest

In reply to Yu Zhang

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.

---
 arch/x86/hyperv/hv_init.c             |   4 +
 arch/x86/include/asm/mshyperv.h       |   4 +
 drivers/iommu/Kconfig                 |   1 +
 drivers/iommu/hyperv/Kconfig          |  15 +
 drivers/iommu/hyperv/Makefile         |   1 +
 drivers/iommu/hyperv/hv-iommu-guest.c | 610 ++++++++++++++++++++++++++
 drivers/iommu/hyperv/iommu.h          |  44 ++
 7 files changed, 679 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
arch/x86/hyperv/hv_init.c +4 -0
index 0b4a1c0b0b16..d5edc8530964 100644
@@ -579,6 +579,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;
arch/x86/include/asm/mshyperv.h +4 -0
index f64393e853ee..20d947c2c758 100644
@@ -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
drivers/iommu/Kconfig +1 -0
index 6e07bd69467a..0d128f377929 100644
@@ -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"
 
drivers/iommu/hyperv/Kconfig new file +15 -0
new file mode 100644
index 000000000000..fe24ed65b6a7
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# Hyper-V para-virtualized IOMMU driver for Linux guests
+config HYPERV_PVIOMMU
+	bool "Microsoft Hyper-V para-virtualized IOMMU driver for Linux guests"
+	depends on X86_64 && HYPERV
+	select IOMMU_DMA
+	select GENERIC_PT
+	select IOMMU_PT
+	select IOMMU_PT_X86_64
+	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.
drivers/iommu/hyperv/Makefile +1 -0
index 6ef0ef97f3dd..d3b2a259439c 100644
@@ -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
drivers/iommu/hyperv/hv-iommu-guest.c new file +610 -0

610 lines not on this page · raw message

drivers/iommu/hyperv/iommu.h new file +44 -0

44 lines not on this page · raw message

Signature · 1 line
2.52.0
#9 lore raw

Re: [PATCH v6 4/5] iommu/hyperv: Add para-virtualized IOMMU support for Hyper-V guest

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] iommu/hyperv: missing detach causes silent attach failures - [Medium] iommu/hyperv: page table UAF…

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] iommu/hyperv: missing detach causes silent attach failures
- [Medium] iommu/hyperv: page table UAF due to wrong domain teardown order
- [Low] iommu/hyperv: missing MODULE_IMPORT_NS causes modpost warning
--

commit e9c9e7afc1e982c251a775443c1fa2af0f333b80
Author: Yu Zhang <[email protected]>

iommu/hyperv: Add para-virtualized IOMMU support for Hyper-V guest

Add a para-virtualized IOMMU driver for Linux guests running on Hyper-V. This driver implements stage-1 IO translation within the guest OS and integrates with the Linux IOMMU core utilizing Hyper-V hypercalls.

drivers/iommu/hyperv/hv-iommu-guest.c new file +0 -0
new file mode 100644
index 0000000000000..341d70eed5924

[ ... ]

Quoted 3 lines
(quoted diff) +3 -0
+static int hv_iommu_attach_dev(struct iommu_domain *domain, struct device *dev,
+			       struct iommu_domain *old)
+{

[ ... ]

Review

Quoted 6 lines
(quoted diff) +6 -0
+	status = hv_do_hypercall(HVCALL_ATTACH_DEVICE_DOMAIN, input, NULL);
+
+	local_irq_restore(flags);
+
+	if (!hv_result_success(status) &&
+	    hv_result(status) != HV_STATUS_DEVICE_ALREADY_IN_DOMAIN) {

[Severity: High]
If a device is already attached to a domain (e.g., blocking or identity), HVCALL_ATTACH_DEVICE_DOMAIN fails with HV_STATUS_DEVICE_ALREADY_IN_DOMAIN.

By ignoring this error code and returning success without detaching from the old domain first, does the IOMMU core assume the device is successfully isolated in the new domain while it actually remains in the old domain? Could this silently break DMA isolation?

Quoted 6 lines
(quoted diff) +6 -0
+		hv_status_err(status, "HVCALL_ATTACH_DEVICE_DOMAIN failed\n");
+		return hv_result_to_errno(status);
+	}
+
+	if (domain != &hv_blocking_domain.domain &&
+	    !pdev->ats_enabled &&

[ ... ]

Review

Quoted 8 lines
(quoted diff) +8 -0
+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);

[Severity: Medium]
In hv_iommu_paging_domain_free(), the driver frees the guest memory backing the page tables via pt_iommu_deinit() before instructing the hypervisor to destroy the domain via hv_delete_device_domain().

Since the hypervisor's device domain is still active and holds the page table root pointer, could the hypervisor access freed memory if it performs background invalidations or delayed faults before the domain is destroyed? Should the domain be deleted before the page tables are freed?

Quoted 3 lines
(quoted diff) +3 -0
+
+	kfree(hv_domain);
+}

[ ... ]

Quoted 2 lines
(quoted diff) +2 -0
+static struct iommu_domain *hv_iommu_domain_alloc_paging(struct device *dev)
+{

[ ... ]

Review

Quoted 3 lines
(quoted diff) +3 -0
+	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);

[Severity: Low]
The driver selects GENERIC_PT and calls pt_iommu_x86_64_init(), which is exported into the GENERIC_PT_IOMMU namespace.

Without a MODULE_IMPORT_NS("GENERIC_PT_IOMMU") declaration, will this cause modpost warnings during the kernel build?

Quoted 4 lines
(quoted diff) +4 -0
+	if (ret)
+		goto err_free;
+
+	/* Constrain to page sizes the hypervisor supports */

[ ... ]

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
author
#10 lore raw

PATCH v6 5/5 iommu/hyperv: Add page-selective IOTLB flush support

In reply to Yu Zhang

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.

---
 drivers/iommu/hyperv/hv-iommu-guest.c | 74 ++++++++++++++++++++++++++-
 include/hyperv/hvgdk_mini.h           |  1 +
 include/hyperv/hvhdk_mini.h           | 18 +++++++
 3 files changed, 92 insertions(+), 1 deletion(-)
drivers/iommu/hyperv/hv-iommu-guest.c +73 -1
index 341d70eed592..fe06bd970621 100644
@@ -383,11 +383,82 @@ 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 long aligned_start;
+	unsigned int sz_lg2;
+
+	sz_lg2 = max(fls_long(start ^ end), 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))
+		aligned_start = 0;
+	else
+		aligned_start =
+			start & GENMASK(BITS_PER_LONG - 1, sz_lg2);
+
+	va->page_number = aligned_start >> 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_ONCE(!hv_result_success(status),
+		      "HVCALL_FLUSH_DEVICE_DOMAIN_LIST failed: %#llx (%s)\n",
+		      status, hv_result_to_string(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: %#llx (%s)\n",
+		     status, hv_result_to_string(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);
 
+	/*
+	 * The hypercall also invalidates non-leaf page-walk caches, so
+	 * detached page-table pages can now be freed.
+	 */
 	iommu_put_pages_list(&iotlb_gather->freelist);
 }
 
@@ -439,6 +510,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
include/hyperv/hvgdk_mini.h +1 -0
index 5d1338197eee..966945f0504f 100644
@@ -497,6 +497,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
include/hyperv/hvhdk_mini.h +18 -0
index 7b9f43b320cd..57b70efe60d4 100644
@@ -685,4 +685,22 @@ struct hv_input_flush_device_domain {
 	u32 reserved;
 } __packed;
 
+union hv_iommu_flush_va {	/* HV_IOMMU_GVA_RANGE */
+	u64 as_uint64;
+	struct {
+		u64 page_mask_shift : 6;
+		u64 reserved : 6;
+		u64 page_number : 52;
+	};
+} __packed;
+
+/* HV_INPUT_FLUSH_DEVICE_DOMAIN_LIST */
+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 */
Signature · 1 line
2.52.0
#11 lore raw

Re: [PATCH v6 5/5] iommu/hyperv: Add page-selective IOTLB flush support

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 5 lines

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().

Quoted 8 lines

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.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
maintainer Applied
#12 lore raw
In reply to Yu Zhang
Quoted 8 lines · Yu Zhang

Wei Liu (1):
hyperv: Introduce new hypercall interfaces used by Hyper-V guest IOMMU

Yu Zhang (4):
Drivers: hv: Add logical device ID registry for vPCI devices iommu/x86: Add architectural MSI reserved region helper iommu/hyperv: Add para-virtualized IOMMU support for Hyper-V guest iommu/hyperv: Add page-selective IOTLB flush support

Applied, thanks.