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.
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>
Reviewed-by: Jacob Pan <jacob.pan@linux.microsoft.com>
Reviewed-by: Michael Kelley <mhklinux@outlook.com>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
This commit is contained in:
Yu Zhang 2026-09-30 22:14:08 +08:00 committed by Joerg Roedel
parent 07f8807ca9
commit 2690d4156b
7 changed files with 679 additions and 0 deletions

View File

@ -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;

View File

@ -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

View File

@ -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"

View File

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

View File

@ -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

View File

@ -0,0 +1,610 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Hyper-V para-virtualized IOMMU driver for Linux guests.
*
* Copyright (C) 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)
{
u64 status;
unsigned long flags;
struct hv_input_configure_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;
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);
WARN_ONCE(!hv_result_success(status),
"HVCALL_FLUSH_DEVICE_DOMAIN failed: %#llx (%s)\n",
status, hv_result_to_string(status));
}
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_domain *hv_domain = to_hv_iommu_domain(domain);
int pci_domain;
int ret;
pdev = to_pci_dev(dev);
dev_dbg(dev, "attaching to domain %d\n",
hv_domain->device_domain.domain_id.id);
pci_domain = pci_domain_nr(pdev->bus);
ret = hv_pci_lookup_dev_id(pci_domain, &prefix);
if (ret) {
dev_err(dev,
"no logical device ID registered for PCI domain %04x\n",
pci_domain);
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_result(status) != HV_STATUS_DEVICE_ALREADY_IN_DOMAIN) {
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 &&
hv_iommu_ats_supported(hv_domain->hv_iommu->cap) &&
pci_ats_supported(pdev))
pci_enable_ats(pdev, PAGE_SHIFT);
return 0;
}
static int hv_iommu_blocking_attach_dev(struct iommu_domain *domain,
struct device *dev,
struct iommu_domain *old)
{
struct pci_dev *pdev = to_pci_dev(dev);
int ret;
if (pdev->ats_enabled)
pci_disable_ats(pdev);
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_dev_property(struct device *dev, u32 code,
struct hv_output_get_logical_device_property *prop)
{
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_pci_lookup_dev_id(pci_domain_nr(pdev->bus), &prefix);
if (ret)
return ret;
local_irq_save(flags);
/*
* The combined input and output fit in the per-CPU input page,
* so no separate output page is needed.
*/
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);
*prop = *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_output_get_logical_device_property property = {0};
int ret;
if (!dev_is_pci(dev))
return ERR_PTR(-ENODEV);
ret = hv_iommu_get_dev_property(dev, HV_LOGICAL_DEVICE_PROPERTY_PVIOMMU,
&property);
if (ret || !(property.device_iommu & HV_DEVICE_IOMMU_ENABLED))
return ERR_PTR(-ENODEV);
return &hv_iommu_device->iommu;
}
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)
{
iommu_add_resv_x86_msi_region(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,
.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);
/*
* The combined input and output fit in the per-CPU input page,
* so no separate output page is needed.
*/
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;
}

View File

@ -0,0 +1,44 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Hyper-V IOMMU driver.
*
* Copyright (C) 2024-2026, 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);
#define to_hv_iommu_domain(d) \
container_of(d, struct hv_iommu_domain, domain)
#endif /* _HYPERV_IOMMU_H */