From: Mukesh R <mrathor@linux.microsoft.com>
To: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
Cc: linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org,
iommu@lists.linux.dev, linux-arch@vger.kernel.org,
jgg@nvidia.com, jacob.pan@linux.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 V4 5/5] x86/hyperv: Implement root partition IOMMU kernel only driver
Date: Fri, 6 Nov 2026 15:39:10 -0800 [thread overview]
Message-ID: <9d010202-39e4-c424-3a40-0d37b62b17fd@linux.microsoft.com> (raw)
In-Reply-To: <f2579646-7f77-4dd2-b7ce-ccf702828610@linux.microsoft.com>
On 11/6/26 13:54, Easwar Hariharan wrote:
> On 11/4/2026 18:01, Mukesh R wrote:
>> Add a new file to implement a kernel only virtual IOMMU that works
>> on Microsoft Hyper-V hypervisor (aka MSHV) in 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/hyperv/Kconfig | 13 +
>> drivers/iommu/hyperv/Makefile | 1 +
>> drivers/iommu/hyperv/hv-iommu-root.c | 613 +++++++++++++++++++++++++++
>> include/asm-generic/mshyperv.h | 3 +
>> include/linux/hyperv.h | 6 +
>> 6 files changed, 638 insertions(+)
>> create mode 100644 drivers/iommu/hyperv/hv-iommu-root.c
>>
>
> <snip>
>
>> obj-$(CONFIG_HYPERV_PVIOMMU) += hv-iommu-guest.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..e0e3e9d4385e
>> --- /dev/null
>> +++ b/drivers/iommu/hyperv/hv-iommu-root.c
>> @@ -0,0 +1,613 @@
>> +// 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.
>> + * (Linux renamed to : HV_DEVICE_DOMAIN_ID_DEFAULT)
>> + * 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;
>> + }
>> +}
>
> Seems like this could match the guest driver and just be: return cap == IOMMU_CAP_CACHE_COHERENCY.
>
> <snip>
>
>> +
>> +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_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_NULL; /* INTMAX */
>> +}
>> +
>
> A net negative 9 lines to initialize these with .field notation, as suggested in v0: https://lore.kernel.org/all/4f7cadc5-817c-4163-bac8-1c02a7c6a862@linux.microsoft.com/
Huh! which are the 9 lines you are claiming to have removed? are you saying
the definition part is not a "line"?
> Wasn't super hard to do the 1 forward declaration needed...
Not a question of hard or not. Having that upfront in the module provides
good introduction to a reader, specially since hyperv is all new around
here and internals are not readily available. Moreover, imo, generally
just embedding definitions in code abrupts the flow often, and historically
hasn't been a common practice.
Thanks,
-Mukesh
> diff --git a/drivers/iommu/hyperv/hv-iommu-root.c b/drivers/iommu/hyperv/hv-iommu-root.c
> index e0e3e9d4385e2..4e6bf94413458 100644
> --- a/drivers/iommu/hyperv/hv-iommu-root.c
> +++ b/drivers/iommu/hyperv/hv-iommu-root.c
> @@ -12,6 +12,7 @@
> #include "../dma-iommu.h"
>
> static dma_addr_t hv_iommu_max_iova;
> +static struct iommu_ops hv_iommu_ops;
> static struct iommu_domain_ops hv_paging_domain_ops;
>
> /* IOMMU device that we export to the world. MSHV supports max of one */
> @@ -32,23 +33,6 @@ struct hv_iommu_mapping {
> 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.
> - * (Linux renamed to : HV_DEVICE_DOMAIN_ID_DEFAULT)
> - * 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
> @@ -488,6 +472,35 @@ static void hv_iommu_get_resv_regions(struct device *dev,
> iommu_add_resv_x86_msi_region(head);
> }
>
> +static const struct iommu_domain_ops hv_special_domain_ops = {
> + .attach_dev = hv_iommu_attach_dev,
> +};
> +
> +/*
> + * 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.
> + * (Linux renamed to : HV_DEVICE_DOMAIN_ID_DEFAULT)
> + * 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 = {
> + .iommu_dom.type = IOMMU_DOMAIN_IDENTITY,
> + .iommu_dom.ops = &hv_special_domain_ops,
> + .iommu_dom.owner = &hv_iommu_ops,
> + .domid_num = HV_DEVICE_DOMAIN_ID_DEFAULT, /* 0 */
> +};
> +
> +static struct hv_iommu_domain hv_def_blocked_dom = {
> + .iommu_dom.type = IOMMU_DOMAIN_BLOCKED,
> + .iommu_dom.ops = &hv_special_domain_ops,
> + .iommu_dom.owner = &hv_iommu_ops,
> + .domid_num = HV_DEVICE_DOMAIN_ID_NULL, /* INTMAX */
> +};
> +
> static struct iommu_domain_ops hv_paging_domain_ops = {
> .attach_dev = hv_iommu_attach_dev,
> .map_pages = hv_iommu_map_pages,
> @@ -508,23 +521,6 @@ static struct iommu_ops hv_iommu_ops = {
> .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_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_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,
> @@ -571,11 +567,6 @@ static int __init hv_root_iommu_init(void)
>
> 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);
>
>
> <snip>
>
> Thanks,
> Easwar (he/him)
prev parent reply other threads:[~2026-11-06 23:39 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 2:01 [PATCH V4 0/5] Hyper-V: root VM iommu " Mukesh R
2026-11-05 2:01 ` [PATCH V4 1/5] mshv: Add gfp flags to deposit memory logic Mukesh R
2026-11-05 4:51 ` Easwar Hariharan
2026-11-05 2:01 ` [PATCH V4 2/5] PCI: hv: Export hv_build_devid_type_pci() and change return type Mukesh R
2026-11-06 19:39 ` Easwar Hariharan
2026-11-05 2:01 ` [PATCH V4 3/5] mshv: Import data structs around device domains from hyperv headers Mukesh R
2026-11-05 2:01 ` [PATCH V4 4/5] iommu/hyperv: Only call pviommu init on non-root VMs Mukesh R
2026-11-05 4:34 ` Easwar Hariharan
2026-11-05 2:01 ` [PATCH V4 5/5] x86/hyperv: Implement root partition IOMMU kernel only driver Mukesh R
2026-11-06 21:54 ` Easwar Hariharan
2026-11-06 23:39 ` Mukesh R [this message]
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=9d010202-39e4-c424-3a40-0d37b62b17fd@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=easwar.hariharan@linux.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=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®