[PATCH v8 06/25] KVM: arm64: iommu: Introduce IOMMU driver infrastructure
From: Mostafa Saleh <smostafa@google.com>
Date: 2026-09-22 13:13:11
Also in:
kvmarm, linux-iommu, lkml
Subsystem:
arm64 port (aarch64 architecture), kernel virtual machine for arm64 (kvm/arm64), the rest · Maintainers:
Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton, Linus Torvalds
To establish DMA isolation, KVM needs an IOMMU driver which provides ops implemented at EL2. Only one driver can be used and is registered with pkvm_iommu_register_driver() by passing pointer to the ops. This must be called before module_init() which is the point KVM initializes. Signed-off-by: Jean-Philippe Brucker <redacted> Signed-off-by: Mostafa Saleh <smostafa@google.com> --- arch/arm64/include/asm/kvm_host.h | 5 +++++ arch/arm64/kvm/Makefile | 2 +- arch/arm64/kvm/hyp/include/nvhe/iommu.h | 11 +++++++++++ arch/arm64/kvm/hyp/nvhe/Makefile | 3 ++- arch/arm64/kvm/hyp/nvhe/iommu.c | 19 +++++++++++++++++++ arch/arm64/kvm/hyp/nvhe/setup.c | 10 ++++++++++ arch/arm64/kvm/iommu.c | 25 +++++++++++++++++++++++++ 7 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 arch/arm64/kvm/hyp/include/nvhe/iommu.h create mode 100644 arch/arm64/kvm/hyp/nvhe/iommu.c create mode 100644 arch/arm64/kvm/iommu.c
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 27fe0cd5b2d7..7ba7d384889e 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h@@ -1717,4 +1717,9 @@ static __always_inline enum fgt_group_id __fgt_reg_to_group_id(enum vcpu_sysreg long kvm_get_cap_for_kvm_ioctl(unsigned int ioctl, long *ext); +#ifndef __KVM_NVHE_HYPERVISOR__ +struct pkvm_iommu_ops; +int pkvm_iommu_register_driver(struct pkvm_iommu_ops *hyp_ops); +#endif + #endif /* __ARM64_KVM_HOST_H__ */
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 59612d2f277c..0ddef54f7434 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile@@ -24,7 +24,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \ vgic/vgic-mmio.o vgic/vgic-mmio-v2.o \ vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \ vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o \ - vgic/vgic-v5.o + vgic/vgic-v5.o iommu.o kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o
diff --git a/arch/arm64/kvm/hyp/include/nvhe/iommu.h b/arch/arm64/kvm/hyp/include/nvhe/iommu.h
new file mode 100644
index 000000000000..83abe1a443c6
--- /dev/null
+++ b/arch/arm64/kvm/hyp/include/nvhe/iommu.h@@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ARM64_KVM_NVHE_IOMMU_H__ +#define __ARM64_KVM_NVHE_IOMMU_H__ + +struct pkvm_iommu_ops { + int (*init)(void); +}; + +int pkvm_iommu_init(void); + +#endif /* __ARM64_KVM_NVHE_IOMMU_H__ */
diff --git a/arch/arm64/kvm/hyp/nvhe/Makefile b/arch/arm64/kvm/hyp/nvhe/Makefile
index f57450ebcb49..7cb79db9bc47 100644
--- a/arch/arm64/kvm/hyp/nvhe/Makefile
+++ b/arch/arm64/kvm/hyp/nvhe/Makefile@@ -24,7 +24,8 @@ CFLAGS_switch.nvhe.o += -Wno-override-init hyp-obj-y := timer-sr.o sysreg-sr.o debug-sr.o switch.o tlb.o hyp-init.o host.o \ hyp-main.o hyp-smp.o psci-relay.o early_alloc.o page_alloc.o \ - cache.o setup.o mm.o mem_protect.o sys_regs.o pkvm.o stacktrace.o ffa.o + cache.o setup.o mm.o mem_protect.o sys_regs.o pkvm.o stacktrace.o ffa.o \ + iommu.o hyp-obj-y += ../vgic-v3-sr.o ../aarch32.o ../vgic-v2-cpuif-proxy.o ../entry.o \ ../hyp-entry.o ../exception.o ../pgtable.o ../vgic-v5-sr.o hyp-obj-y += ../../../kernel/smccc-call.o
diff --git a/arch/arm64/kvm/hyp/nvhe/iommu.c b/arch/arm64/kvm/hyp/nvhe/iommu.c
new file mode 100644
index 000000000000..ef456eff42d2
--- /dev/null
+++ b/arch/arm64/kvm/hyp/nvhe/iommu.c@@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * IOMMU operations for pKVM + * + * Copyright (C) 2022 Linaro Ltd. + */ +#include <nvhe/iommu.h> + +/* Only one set of ops supported */ +struct pkvm_iommu_ops *pkvm_iommu_ops; + +int pkvm_iommu_init(void) +{ + /* Keep DMA isolation optional. */ + if (!pkvm_iommu_ops || !pkvm_iommu_ops->init) + return 0; + + return pkvm_iommu_ops->init(); +}
diff --git a/arch/arm64/kvm/hyp/nvhe/setup.c b/arch/arm64/kvm/hyp/nvhe/setup.c
index 75b00c323310..9607d1b18a88 100644
--- a/arch/arm64/kvm/hyp/nvhe/setup.c
+++ b/arch/arm64/kvm/hyp/nvhe/setup.c@@ -13,6 +13,7 @@ #include <nvhe/early_alloc.h> #include <nvhe/ffa.h> #include <nvhe/gfp.h> +#include <nvhe/iommu.h> #include <nvhe/memory.h> #include <nvhe/mem_protect.h> #include <nvhe/mm.h>
@@ -328,6 +329,15 @@ void __noreturn __pkvm_init_finalise(void) if (ret) goto out; + /* + * IOMMU init is done at the end, as it sets up and initializes many + * resources that would be leaked if the hypervisor fails after as there + * is no remove_iommu_driver() at the moment. + */ + ret = pkvm_iommu_init(); + if (ret) + goto out; + pkvm_hyp_vm_table_init(vm_table_base); pkvm_ownership_selftest(selftest_base);
diff --git a/arch/arm64/kvm/iommu.c b/arch/arm64/kvm/iommu.c
new file mode 100644
index 000000000000..30a3862e93d7
--- /dev/null
+++ b/arch/arm64/kvm/iommu.c@@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Google LLC + * Author: Mostafa Saleh <smostafa@google.com> + */ + +#include <linux/kvm_host.h> + +extern struct pkvm_iommu_ops *kvm_nvhe_sym(pkvm_iommu_ops); + +static DEFINE_MUTEX(pkvm_iommu_reg_lock); + +int pkvm_iommu_register_driver(struct pkvm_iommu_ops *hyp_ops) +{ + guard(mutex)(&pkvm_iommu_reg_lock); + + if (!is_protected_kvm_enabled() || is_kvm_arm_initialised()) + return -EPERM; + + if (kvm_nvhe_sym(pkvm_iommu_ops)) + return -EBUSY; + + kvm_nvhe_sym(pkvm_iommu_ops) = hyp_ops; + return 0; +}
--
2.55.0.1082.g2b9226bbc0-goog