mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Mukesh R <mrathor@linux.microsoft.com>
To: linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org,
	iommu@lists.linux.dev, linux-arch@vger.kernel.org
Cc: jgg@nvidia.com, jacob.pan@linux.microsoft.com, kys@microsoft.com,
	haiyangz@microsoft.com, wei.liu@kernel.org, decui@microsoft.com,
	tglx@kernel.org, mingo@redhat.com, bp@alien8.de,
	dave.hansen@linux.intel.com, x86@kernel.org, hpa@zytor.com,
	joro@8bytes.org, will@kernel.org, robin.murphy@arm.com,
	arnd@arndb.de
Subject: Re: [PATCH V2 4/4] x86/hyperv: Implement root partition IOMMU kernel only driver
Date: Wed, 21 Oct 2026 14:36:53 -0700	[thread overview]
Message-ID: <c01d422c-5277-010b-ff14-dfe5fec6a7bb@linux.microsoft.com> (raw)
In-Reply-To: <20260929223622.778617-5-mrathor@linux.microsoft.com>

Hi Jason and Joerg,

I am going to rebase due to all the patches that went it and crank
out a new version. Do you have any more comments/concerns, I think I have
addressed all your concerns. Pl lmk.

Thanks,
-Mukesh


On 9/29/26 15:36, Mukesh R wrote:
> Add a new file to implement a kernel only virtual IOMMU that works
> with Microsoft Hyper-V hypervisor (aka MSHV) on privileged VMs aka
> root VMs. The hypervisor claims the IOMMU upon boot, and this driver
> communicates with it for creating and deleting paging domains, attaching
> of devices, mapping and unmapping of pages, etc. During boot, hypervisor
> automatically creates identity and blocked domains, so there is no need
> to do hypercalls to create them.  This is a kernel only driver and only
> supported on baremetal root (and not L1VH root) without any guest passthru
> support. Support for guest device passthru will be added incrementally.
> 
> Signed-off-by: Mukesh R <mrathor@linux.microsoft.com>
> ---
>   arch/x86/kernel/pci-dma.c            |   2 +
>   drivers/iommu/Kconfig                |   1 +
>   drivers/iommu/hyperv/Kconfig         |  15 +
>   drivers/iommu/hyperv/Makefile        |   1 +
>   drivers/iommu/hyperv/hv-iommu-root.c | 608 +++++++++++++++++++++++++++
>   include/asm-generic/mshyperv.h       |   3 +
>   include/linux/hyperv.h               |   6 +
>   7 files changed, 636 insertions(+)
>   create mode 100644 drivers/iommu/hyperv/Kconfig
>   create mode 100644 drivers/iommu/hyperv/hv-iommu-root.c
> 
> diff --git a/arch/x86/kernel/pci-dma.c b/arch/x86/kernel/pci-dma.c
> index 6267363e0189..37fed8c7a8c2 100644
> --- a/arch/x86/kernel/pci-dma.c
> +++ b/arch/x86/kernel/pci-dma.c
> @@ -8,6 +8,7 @@
>   #include <linux/gfp.h>
>   #include <linux/pci.h>
>   #include <linux/amd-iommu.h>
> +#include <linux/hyperv.h>
>   
>   #include <asm/proto.h>
>   #include <asm/dma.h>
> @@ -103,6 +104,7 @@ void __init pci_iommu_alloc(void)
>   	}
>   	pci_swiotlb_detect();
>   	gart_iommu_hole_init();
> +	hv_iommu_detect();
>   	amd_iommu_detect();
>   	detect_intel_iommu();
>   	swiotlb_init(x86_swiotlb_enable, x86_swiotlb_flags);
> diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
> index 6e07bd69467a..3e410e0f3e1d 100644
> --- a/drivers/iommu/Kconfig
> +++ b/drivers/iommu/Kconfig
> @@ -197,6 +197,7 @@ source "drivers/iommu/arm/Kconfig"
>   source "drivers/iommu/intel/Kconfig"
>   source "drivers/iommu/iommufd/Kconfig"
>   source "drivers/iommu/riscv/Kconfig"
> +source "drivers/iommu/hyperv/Kconfig"
>   
>   config IRQ_REMAP
>   	bool "Support for Interrupt Remapping"
> diff --git a/drivers/iommu/hyperv/Kconfig b/drivers/iommu/hyperv/Kconfig
> new file mode 100644
> index 000000000000..b9be8de4d901
> --- /dev/null
> +++ b/drivers/iommu/hyperv/Kconfig
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +# Hyper-V IOMMU support
> +
> +config HYPERV_ROOT_IOMMU
> +	bool "Hyper-V IOMMU Device in root partition"
> +	depends on HYPERV && X86 && PCI_MSI && MSHV_ROOT
> +	select IOMMU_API
> +	default HYPERV
> +	help
> +	  This enables Hyper-V pseudo IOMMU device. When running as privileged
> +          VM aka root on Microsoft Hyper-V hypervisor, this must be enabled
> +          for doing any PCI passthru of devices to guest VMs. This applies to
> +          both PFs and VFs. When enabling this, it is best to disable amd/intel
> +          iommus via: intel_iommu=off amd_iommu=off as the hypervisor really
> +          owns the iommu.
> diff --git a/drivers/iommu/hyperv/Makefile b/drivers/iommu/hyperv/Makefile
> index 6ef0ef97f3dd..c7e7d0dac2a4 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_ROOT_IOMMU) += hv-iommu-root.o
> diff --git a/drivers/iommu/hyperv/hv-iommu-root.c b/drivers/iommu/hyperv/hv-iommu-root.c
> new file mode 100644
> index 000000000000..2502db5624de
> --- /dev/null
> +++ b/drivers/iommu/hyperv/hv-iommu-root.c
> @@ -0,0 +1,608 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Hyper-V root vIOMMU driver.
> + * Copyright (C) 2026, Microsoft, Inc.
> + */
> +#include <linux/pci.h>
> +#include <linux/dma-map-ops.h>
> +#include <linux/interval_tree.h>
> +#include <linux/hyperv.h>
> +#include <asm/iommu.h>
> +#include <asm/mshyperv.h>
> +#include "../dma-iommu.h"
> +
> +static dma_addr_t hv_iommu_max_iova;
> +static struct iommu_domain_ops hv_paging_domain_ops;
> +
> +/* IOMMU device that we export to the world. MSHV supports max of one */
> +static struct iommu_device hv_virt_iommu;
> +
> +struct hv_iommu_domain {
> +	struct iommu_domain iommu_dom;
> +	u32 domid_num;			      /* as opposed to domain_id.type */
> +	spinlock_t mappings_lock;	      /* protects mappings_tree */
> +	struct rb_root_cached mappings_tree;  /* iova to pa lookup tree */
> +};
> +
> +#define to_hv_domain(d) container_of(d, struct hv_iommu_domain, iommu_dom)
> +
> +struct hv_iommu_mapping {
> +	phys_addr_t paddr;
> +	struct interval_tree_node iova;
> +	u32 flags;
> +};
> +
> +/*
> + * By default, during boot the hypervisor creates one Stage 2 (S2) default
> + * domain. It has two types:
> + *   S2 default: access to entire root partition memory. This for the host
> + *		 maps to IOMMU_DOMAIN_IDENTITY in the iommu subsystem, and
> + *		 is called HV_DEVICE_DOMAIN_ID_S2_DEFAULT in the hypervisor.
> + *   S2 NULL: Blocks everything, ie, IOMMU_DOMAIN_BLOCKED in linux.
> + */
> +
> +/*
> + * Create dummy domains to correspond to hypervisor prebuilt default identity
> + * and null domains (dummy because we do not make hypercalls to create them).
> + */
> +static struct hv_iommu_domain hv_def_identity_dom;
> +static struct hv_iommu_domain hv_def_blocked_dom;
> +
> +/*
> + * The hypervisor owns the IOMMU. As such it will choose the best page size
> + * and whenever possible use larger page sizes automatically. The map gpa
> + * hypercall to map the iova however currently uses only 4K pfns as input.
> + * So we specify 4K as the IOMMU PAGE_SIZE.
> + */
> +#define HV_IOMMU_PGSIZES SZ_4K
> +
> +static DEFINE_IDA(hv_iommu_domids);   /* unique numeric ids for new domains */
> +
> +static bool hv_iommu_capable(struct device *dev, enum iommu_cap cap)
> +{
> +	switch (cap) {
> +	case IOMMU_CAP_CACHE_COHERENCY:
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +static int hv_iommu_add_tree_mapping(struct hv_iommu_domain *hvdom, ulong iova,
> +				     phys_addr_t paddr, size_t size, u32 flags)
> +{
> +	ulong irqflags;
> +	struct hv_iommu_mapping *mapping;
> +
> +	mapping = kzalloc_obj(struct hv_iommu_mapping, GFP_ATOMIC);
> +	if (!mapping)
> +		return -ENOMEM;
> +
> +	mapping->paddr = paddr;
> +	mapping->iova.start = iova;
> +	mapping->iova.last = iova + size - 1;
> +	mapping->flags = flags;
> +
> +	spin_lock_irqsave(&hvdom->mappings_lock, irqflags);
> +	interval_tree_insert(&mapping->iova, &hvdom->mappings_tree);
> +	spin_unlock_irqrestore(&hvdom->mappings_lock, irqflags);
> +
> +	return 0;
> +}
> +
> +/* If size == 0, then last = ULONG_MAX. With iova 0, will remove everything */
> +static size_t hv_iommu_del_tree_mappings(struct hv_iommu_domain *hvdom,
> +					 ulong iova, size_t size)
> +{
> +	ulong flags;
> +	size_t unmapped = 0;
> +	ulong last = iova + size - 1;
> +	struct hv_iommu_mapping *mapping = NULL;
> +	struct interval_tree_node *node, *next;
> +
> +	spin_lock_irqsave(&hvdom->mappings_lock, flags);
> +	next = interval_tree_iter_first(&hvdom->mappings_tree, iova, last);
> +	while (next) {
> +		node = next;
> +		mapping = container_of(node, struct hv_iommu_mapping, iova);
> +		next = interval_tree_iter_next(node, iova, last);
> +
> +		/* Splitting of a mapping is not supported at present */
> +		if (mapping->iova.start < iova)
> +			break;
> +
> +		unmapped += mapping->iova.last - mapping->iova.start + 1;
> +
> +		interval_tree_remove(node, &hvdom->mappings_tree);
> +		kfree(mapping);
> +	}
> +	spin_unlock_irqrestore(&hvdom->mappings_lock, flags);
> +
> +	return unmapped;
> +}
> +
> +/* Create a new device domain in the hypervisor */
> +static int hv_iommu_create_hyp_devdom(struct hv_iommu_domain *hvdom)
> +{
> +	u64 status;
> +	struct hv_input_device_domain *ddp;
> +	struct hv_input_create_device_domain *input;
> +	ulong flags;
> +
> +	local_irq_save(flags);
> +	input = *this_cpu_ptr(hyperv_pcpu_input_arg);
> +	memset(input, 0, sizeof(*input));
> +
> +	ddp = &input->device_domain;
> +	ddp->partition_id = HV_PARTITION_ID_SELF;
> +	ddp->domain_id.type = HV_DEVICE_DOMAIN_TYPE_S2;
> +	ddp->domain_id.id = hvdom->domid_num;
> +
> +	input->create_device_domain_flags.forward_progress_required = 1;
> +	input->create_device_domain_flags.inherit_owning_vtl = 0;
> +
> +	status = hv_do_hypercall(HVCALL_CREATE_DEVICE_DOMAIN, input, NULL);
> +
> +	local_irq_restore(flags);
> +
> +	if (!hv_result_success(status))
> +		hv_status_err(status, "\n");
> +
> +	return hv_result_to_errno(status);
> +}
> +
> +static struct iommu_domain *hv_iommu_domain_alloc_paging(struct device *dev)
> +{
> +	struct hv_iommu_domain *hvdom;
> +	int rc;
> +
> +	if (!dev_is_pci(dev))
> +		return NULL;
> +
> +	hvdom = kzalloc_obj(struct hv_iommu_domain);
> +	if (hvdom == NULL)
> +		return ERR_PTR(-ENOMEM);
> +
> +	spin_lock_init(&hvdom->mappings_lock);
> +	hvdom->mappings_tree = RB_ROOT_CACHED;
> +
> +	rc = ida_alloc_range(&hv_iommu_domids,
> +			     HV_DEVICE_DOMAIN_ID_S2_DEFAULT + 1,
> +			     HV_DEVICE_DOMAIN_ID_S2_NULL - 1, GFP_KERNEL);
> +	if (rc < 0)
> +		goto out_err;
> +
> +	hvdom->domid_num = rc;
> +	hvdom->iommu_dom.pgsize_bitmap = HV_IOMMU_PGSIZES;
> +	hvdom->iommu_dom.geometry.aperture_start = 0;
> +	hvdom->iommu_dom.geometry.aperture_end = hv_iommu_max_iova;
> +	hvdom->iommu_dom.geometry.force_aperture = true;
> +	hvdom->iommu_dom.ops = &hv_paging_domain_ops;
> +
> +	rc = hv_iommu_create_hyp_devdom(hvdom);
> +	if (rc)
> +		goto out_err1;
> +
> +	return &hvdom->iommu_dom;
> +
> +out_err1:
> +	ida_free(&hv_iommu_domids, hvdom->domid_num);
> +out_err:
> +	kfree(hvdom);
> +	return ERR_PTR(rc);
> +}
> +
> +static void hv_iommu_domain_free(struct iommu_domain *immdom)
> +{
> +	ulong flags;
> +	u64 status;
> +	struct hv_input_delete_device_domain *input;
> +	struct hv_input_device_domain *ddp;
> +	struct hv_iommu_domain *hvdom = to_hv_domain(immdom);
> +
> +	/* Cleanup any remaining. 0 for size results in ULONG_MAX as the last */
> +	hv_iommu_del_tree_mappings(hvdom, 0, 0);
> +
> +	local_irq_save(flags);
> +	input = *this_cpu_ptr(hyperv_pcpu_input_arg);
> +	ddp = &input->device_domain;
> +	memset(input, 0, sizeof(*input));
> +
> +	ddp->partition_id = HV_PARTITION_ID_SELF;
> +	ddp->domain_id.type = HV_DEVICE_DOMAIN_TYPE_S2;
> +	ddp->domain_id.id = hvdom->domid_num;
> +
> +	status = hv_do_hypercall(HVCALL_DELETE_DEVICE_DOMAIN, input,
> +				 NULL);
> +	local_irq_restore(flags);
> +
> +	if (!hv_result_success(status))
> +		hv_status_err(status, "\n");
> +
> +	ida_free(&hv_iommu_domids, hvdom->domid_num);
> +	kfree(hvdom);
> +}
> +
> +/*
> + * Attach a device to the default domain, or the null domain, or to a domain
> + * previously created in the hypervisor.
> + */
> +static int hv_iommu_att_dev2dom(struct hv_iommu_domain *hvdom,
> +				struct pci_dev *pdev)
> +{
> +	ulong flags;
> +	u64 status;
> +	struct hv_input_attach_device_domain *input;
> +
> +	local_irq_save(flags);
> +	input = *this_cpu_ptr(hyperv_pcpu_input_arg);
> +	memset(input, 0, sizeof(*input));
> +
> +	/* For null domain, hvdom->domid_num == HV_DEVICE_DOMAIN_ID_S2_NULL */
> +	input->device_domain.partition_id = HV_PARTITION_ID_SELF;
> +	input->device_domain.domain_id.type = HV_DEVICE_DOMAIN_TYPE_S2;
> +	input->device_domain.domain_id.id = hvdom->domid_num;
> +
> +	input->device_id.as_uint64 = hv_build_devid_type_pci(pdev);
> +
> +	status = hv_do_hypercall(HVCALL_ATTACH_DEVICE_DOMAIN, input, NULL);
> +	local_irq_restore(flags);
> +
> +	if (!hv_result_success(status))
> +		hv_status_err(status, "\n");
> +
> +	return hv_result_to_errno(status);
> +}
> +
> +static int hv_iommu_attach_dev(struct iommu_domain *immdom, struct device *dev,
> +			       struct iommu_domain *old)
> +{
> +	struct pci_dev *pdev;
> +	int rc;
> +	struct hv_iommu_domain *hvdom = to_hv_domain(immdom);
> +
> +	pdev = to_pci_dev(dev);
> +
> +	rc = hv_iommu_att_dev2dom(hvdom, pdev);
> +	if (rc)
> +		WARN(1, "Failed to attach pdev:%s\n", pci_name(pdev));
> +
> +	return rc;
> +}
> +
> +static u64 hv_iommu_unmap_batch(u32 domid_num, ulong iova, u16 count)
> +{
> +	ulong flags;
> +	struct hv_input_unmap_device_gpa_pages *input;
> +	u64 status;
> +
> +	local_irq_save(flags);
> +	input = *this_cpu_ptr(hyperv_pcpu_input_arg);
> +	memset(input, 0, sizeof(*input));
> +
> +	input->device_domain.partition_id = HV_PARTITION_ID_SELF;
> +	input->device_domain.domain_id.type = HV_DEVICE_DOMAIN_TYPE_S2;
> +	input->device_domain.domain_id.id = domid_num;
> +	input->target_device_va_base = iova;
> +
> +	status = hv_do_rep_hypercall(HVCALL_UNMAP_DEVICE_GPA_PAGES, count,
> +				     0, input, NULL);
> +	local_irq_restore(flags);
> +
> +	if (!hv_result_success(status))
> +		hv_status_err(status, "iova:0x%lx count:0x%x\n", iova, count);
> +
> +	return status;
> +}
> +
> +static size_t hv_iommu_hyp_unmap_pages(struct iommu_domain *immdom, ulong iova,
> +				       size_t size)
> +{
> +	u64 status;
> +	struct hv_iommu_domain *hvdom = to_hv_domain(immdom);
> +	size_t tot_done = 0;
> +	ulong npages = size >> HV_HYP_PAGE_SHIFT;
> +
> +	while (npages) {
> +		int done, count = min(npages, HV_REP_COUNT_MAX);
> +
> +		status = hv_iommu_unmap_batch(hvdom->domid_num, iova, count);
> +
> +		done = hv_repcomp(status);
> +		tot_done += done;
> +		npages -= done;
> +		iova += done << HV_HYP_PAGE_SHIFT;
> +
> +		if (!hv_result_success(status))
> +			break;
> +	}
> +
> +	return tot_done << HV_HYP_PAGE_SHIFT;
> +}
> +
> +static size_t hv_iommu_unmap_pages(struct iommu_domain *immdom, ulong iova,
> +				   size_t pgsize, size_t pgcount,
> +				   struct iommu_iotlb_gather *gather)
> +{
> +	struct hv_iommu_domain *hvdom = to_hv_domain(immdom);
> +	size_t unmap_sz, size = pgsize * pgcount;
> +
> +	/* For valid input, the hypervisor guarantees unmap will succeed */
> +	unmap_sz = hv_iommu_hyp_unmap_pages(immdom, iova, size);
> +	if (unmap_sz != size)
> +		WARN(1, "Failed to unmap device gpa(%lx/%lx)\n", unmap_sz,
> +		     size);
> +
> +	size = hv_iommu_del_tree_mappings(hvdom, iova, unmap_sz);
> +	if (size != unmap_sz)
> +		pr_err("%s: could not delete tree mappings (%lx:%lx/%lx)\n",
> +		       __func__, iova, unmap_sz, size);
> +
> +	return size;
> +}
> +
> +/* Return: must return exact status from the hypercall without changes */
> +static u64 hv_iommu_map_pgs(struct hv_iommu_domain *hvdom, ulong iova,
> +			    phys_addr_t paddr, ulong npages, u32 map_flags)
> +{
> +	u64 status;
> +	int i;
> +	struct hv_input_map_device_gpa_pages *input;
> +	unsigned long flags, pfn;
> +
> +	local_irq_save(flags);
> +	input = *this_cpu_ptr(hyperv_pcpu_input_arg);
> +	memset(input, 0, sizeof(*input));
> +
> +	input->device_domain.partition_id = HV_PARTITION_ID_SELF;
> +	input->device_domain.domain_id.type = HV_DEVICE_DOMAIN_TYPE_S2;
> +	input->device_domain.domain_id.id = hvdom->domid_num;
> +	input->map_flags = map_flags;
> +	input->target_device_va_base = iova;
> +
> +	pfn = paddr >> HV_HYP_PAGE_SHIFT;
> +	for (i = 0; i < npages; i++, pfn++)
> +		input->gpa_page_list[i] = pfn;
> +
> +	status = hv_do_rep_hypercall(HVCALL_MAP_DEVICE_GPA_PAGES, npages, 0,
> +				     input, NULL);
> +	local_irq_restore(flags);
> +
> +	if (!hv_result_success(status))
> +		hv_status_err(status, "npgs:%lx paddr:%llx iova:%lx mf:0x%x\n",
> +			      npages, paddr, iova, map_flags);
> +	return status;
> +}
> +
> +#define HV_MAP_DEVICE_GPA_BATCH_SIZE   \
> +	((HV_HYP_PAGE_SIZE - sizeof(struct hv_input_map_device_gpa_pages)) \
> +			/ sizeof(u64))
> +
> +/* NB: cond_resched() is in vfio_iommu_map() */
> +static int hv_iommu_map_pages(struct iommu_domain *immdom, ulong iova,
> +			      phys_addr_t paddr, size_t pgsize, size_t pgcount,
> +			      int prot, gfp_t gfp, size_t *mapped)
> +{
> +	u32 map_flags;
> +	int ret;
> +	u64 status = 0;
> +	ulong npages, sav_iova = iova, done = 0;
> +	struct hv_iommu_domain *hvdom = to_hv_domain(immdom);
> +	size_t size = pgsize * pgcount;
> +
> +	map_flags = HV_MAP_GPA_READABLE;	/* required */
> +	map_flags |= prot & IOMMU_WRITE ? HV_MAP_GPA_WRITABLE : 0;
> +
> +	ret = hv_iommu_add_tree_mapping(hvdom, iova, paddr, size, map_flags);
> +	if (ret)
> +		return ret;
> +
> +	npages = size >> HV_HYP_PAGE_SHIFT;
> +	while (done < npages) {
> +		ulong completed, remain = npages - done;
> +
> +		remain = min(remain, HV_MAP_DEVICE_GPA_BATCH_SIZE);
> +
> +		status = hv_iommu_map_pgs(hvdom, iova, paddr, remain,
> +					  map_flags);
> +
> +		completed = hv_repcomp(status);
> +		done = done + completed;
> +		iova = iova + (completed << HV_HYP_PAGE_SHIFT);
> +		paddr = paddr + (completed << HV_HYP_PAGE_SHIFT);
> +
> +		if (hv_result_needs_memory(status)) {
> +			ret = hv_call_deposit_pages(NUMA_NO_NODE,
> +						    hv_current_partition_id,
> +						    4, gfp);
> +			if (ret)
> +				break;
> +			continue;
> +		}
> +		if (!hv_result_success(status))
> +			break;
> +	}
> +
> +	if (hv_result_success(status)) {
> +		if (mapped)
> +			*mapped = size;
> +	} else {
> +		size_t done_size = done << HV_HYP_PAGE_SHIFT;
> +
> +		hv_status_err(status, "pgs:%lx/%lx iova:%lx\n", done, npages,
> +			      sav_iova);
> +
> +		hv_iommu_del_tree_mappings(hvdom, sav_iova, size);
> +
> +		/* Hypervisor guarantees unmap will succeed here */
> +		hv_iommu_hyp_unmap_pages(immdom, sav_iova, done_size);
> +
> +		if (mapped)
> +			*mapped = 0;
> +	}
> +
> +	return hv_result_to_errno(status);
> +}
> +
> +static phys_addr_t hv_iommu_iova_to_phys(struct iommu_domain *immdom,
> +					 dma_addr_t iova)
> +{
> +	unsigned long flags;
> +	struct hv_iommu_mapping *mapping;
> +	struct interval_tree_node *node;
> +	u64 paddr = 0;
> +	struct hv_iommu_domain *hvdom = to_hv_domain(immdom);
> +
> +	spin_lock_irqsave(&hvdom->mappings_lock, flags);
> +	node = interval_tree_iter_first(&hvdom->mappings_tree, iova, iova);
> +	if (node) {
> +		mapping = container_of(node, struct hv_iommu_mapping, iova);
> +		paddr = mapping->paddr + (iova - mapping->iova.start);
> +	}
> +	spin_unlock_irqrestore(&hvdom->mappings_lock, flags);
> +
> +	return paddr;
> +}
> +
> +static struct iommu_device *hv_iommu_probe_device(struct device *dev)
> +{
> +	if (!dev_is_pci(dev))
> +		return ERR_PTR(-ENODEV);
> +
> +	return &hv_virt_iommu;
> +}
> +
> +static void hv_iommu_get_resv_regions(struct device *dev,
> +				      struct list_head *head)
> +{
> +	struct iommu_resv_region *reg;
> +
> +	/* reserve the entire LAPIC region */
> +	reg = iommu_alloc_resv_region(0xfee00000, SZ_1M, 0, IOMMU_RESV_MSI,
> +				      GFP_KERNEL);
> +	if (reg)
> +		list_add_tail(&reg->list, head);
> +}
> +
> +static struct iommu_domain_ops hv_paging_domain_ops = {
> +	.attach_dev = hv_iommu_attach_dev,
> +	.map_pages = hv_iommu_map_pages,
> +	.unmap_pages = hv_iommu_unmap_pages,
> +	.iova_to_phys = hv_iommu_iova_to_phys,
> +	.free = hv_iommu_domain_free,
> +};
> +
> +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,
> +	.device_group	    = pci_device_group,
> +	.get_resv_regions   = hv_iommu_get_resv_regions,
> +	.owner		    = THIS_MODULE,
> +	.identity_domain    = &hv_def_identity_dom.iommu_dom,
> +	.blocked_domain     = &hv_def_blocked_dom.iommu_dom,
> +	.release_domain     = &hv_def_blocked_dom.iommu_dom,
> +};
> +
> +static const struct iommu_domain_ops hv_special_domain_ops = {
> +	.attach_dev = hv_iommu_attach_dev,
> +};
> +
> +static void __init hv_initialize_special_domains(void)
> +{
> +	hv_def_identity_dom.iommu_dom.type = IOMMU_DOMAIN_IDENTITY;
> +	hv_def_identity_dom.iommu_dom.ops = &hv_special_domain_ops;
> +	hv_def_identity_dom.iommu_dom.owner = &hv_iommu_ops;
> +	hv_def_identity_dom.domid_num = HV_DEVICE_DOMAIN_ID_S2_DEFAULT; /* 0 */
> +
> +	hv_def_blocked_dom.iommu_dom.type = IOMMU_DOMAIN_BLOCKED;
> +	hv_def_blocked_dom.iommu_dom.ops = &hv_special_domain_ops;
> +	hv_def_blocked_dom.iommu_dom.owner = &hv_iommu_ops;
> +	hv_def_blocked_dom.domid_num = HV_DEVICE_DOMAIN_ID_S2_NULL; /* INTMAX */
> +}
> +
> +/*
> + * In general, the hypervisor does not support the capabilities hypercall for
> + * root partitions, an exception was made for max_iova_width. For IOMMU cap,
> + * hv_iommu_detect() below checks for HV_DEVICE_DOMAIN_AVAILABLE which
> + * supersedes HV_IOMMU_CAP_PRESENT. As for the pgsize_bitmap cap, see comment
> + * above HV_IOMMU_PGSIZES.
> + */
> +static int hv_iommu_get_caps(struct hv_output_get_iommu_capabilities *caps)
> +{
> +	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 = *this_cpu_ptr(hyperv_pcpu_output_arg);
> +	memset(input, 0, sizeof(*input));
> +	input->partition_id = HV_PARTITION_ID_SELF;
> +	status = hv_do_hypercall(HVCALL_GET_IOMMU_CAPABILITIES, input, output);
> +	*caps = *output;
> +
> +	local_irq_restore(flags);
> +
> +	if (!hv_result_success(status))
> +		hv_status_err(status, "\n");
> +
> +	return hv_result_to_errno(status);
> +}
> +
> +static int __init hv_iommu_init(void)
> +{
> +	int rc;
> +	struct iommu_device *iommup = &hv_virt_iommu;
> +	struct hv_output_get_iommu_capabilities caps;
> +
> +	if (!hv_is_hyperv_initialized())
> +		return -ENODEV;
> +
> +	rc = hv_iommu_get_caps(&caps);
> +	if (rc)
> +		return rc;
> +
> +	hv_iommu_max_iova = DMA_BIT_MASK(caps.max_iova_width);
> +
> +	/* This must come before iommu_device_register() because the latter
> +	 * calls into the hooks.
> +	 */
> +	hv_initialize_special_domains();
> +
> +	rc = iommu_device_sysfs_add(iommup, NULL, NULL, "%s", "hyperv-iommu");
> +	if (rc) {
> +		pr_err("Hyper-V: iommu_device_sysfs_add failed: %d\n", rc);
> +		return rc;
> +	}
> +
> +	rc = iommu_device_register(iommup, &hv_iommu_ops, NULL);
> +	if (rc) {
> +		pr_err("Hyper-V: iommu_device_register failed: %d\n", rc);
> +		goto err_sysfs_remove;
> +	}
> +
> +	pr_info("Hyper-V IOMMU initialized\n");
> +
> +	return 0;
> +
> +err_sysfs_remove:
> +	iommu_device_sysfs_remove(iommup);
> +	return rc;
> +}
> +
> +void __init hv_iommu_detect(void)
> +{
> +	if (no_iommu || iommu_detected || hv_l1vh_partition())
> +		return;
> +
> +	if (!(ms_hyperv.misc_features & HV_DEVICE_DOMAIN_AVAILABLE))
> +		return;
> +
> +	iommu_detected = 1;
> +	x86_init.iommu.iommu_init = hv_iommu_init;
> +
> +	pci_request_acs();
> +}
> diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
> index af65dd3e3725..a35e30b87815 100644
> --- a/include/asm-generic/mshyperv.h
> +++ b/include/asm-generic/mshyperv.h
> @@ -28,6 +28,9 @@
>   
>   #define VTPM_BASE_ADDRESS 0xfed40000
>   
> +#define HV_REP_COUNT_MAX        \
> +	(HV_HYPERCALL_REP_COMP_MASK >> HV_HYPERCALL_REP_COMP_OFFSET)
> +
>   enum hv_partition_type {
>   	HV_PARTITION_TYPE_GUEST,
>   	HV_PARTITION_TYPE_ROOT,
> diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
> index 9e109d91aa14..01a69f88cfa7 100644
> --- a/include/linux/hyperv.h
> +++ b/include/linux/hyperv.h
> @@ -1783,4 +1783,10 @@ static inline unsigned long virt_to_hvpfn(void *addr)
>   #define HVPFN_DOWN(x)	((x) >> HV_HYP_PAGE_SHIFT)
>   #define page_to_hvpfn(page)	(page_to_pfn(page) * NR_HV_HYP_PAGES_IN_PAGE)
>   
> +#ifdef CONFIG_HYPERV_ROOT_IOMMU
> +void __init hv_iommu_detect(void);
> +#else
> +static inline void hv_iommu_detect(void) { }
> +#endif /* CONFIG_HYPERV_ROOT_IOMMU */
> +
>   #endif /* _HYPERV_H */


  reply	other threads:[~2026-10-21 21:37 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-29 22:36 [PATCH V2 0/4] Hyper-V: root VM iommu " Mukesh R
2026-09-29 22:36 ` [PATCH V2 1/4] mshv: Add gfp_flags parameter to hv_call_deposit_pages() Mukesh R
2026-09-29 22:36 ` [PATCH V2 2/4] PCI: hv: Export hv_build_devid_type_pci() and change return type Mukesh R
2026-09-29 22:36 ` [PATCH V2 3/4] mshv: Import data structs around device domains from hyperv headers Mukesh R
2026-09-29 22:36 ` [PATCH V2 4/4] x86/hyperv: Implement root partition IOMMU kernel only driver Mukesh R
2026-10-21 21:36   ` Mukesh R [this message]
2026-10-21 23:20     ` Jason Gunthorpe

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=c01d422c-5277-010b-ff14-dfe5fec6a7bb@linux.microsoft.com \
    --to=mrathor@linux.microsoft.com \
    --cc=arnd@arndb.de \
    --cc=bp@alien8.de \
    --cc=dave.hansen@linux.intel.com \
    --cc=decui@microsoft.com \
    --cc=haiyangz@microsoft.com \
    --cc=hpa@zytor.com \
    --cc=iommu@lists.linux.dev \
    --cc=jacob.pan@linux.microsoft.com \
    --cc=jgg@nvidia.com \
    --cc=joro@8bytes.org \
    --cc=kys@microsoft.com \
    --cc=linux-arch@vger.kernel.org \
    --cc=linux-hyperv@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mingo@redhat.com \
    --cc=robin.murphy@arm.com \
    --cc=tglx@kernel.org \
    --cc=wei.liu@kernel.org \
    --cc=will@kernel.org \
    --cc=x86@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®