[PATCH 14/14] KVM: arm64: Implement HVC interface for ITS emulation setup

Sebastian Ene
2026-03-10T12:50:24Z

Introduce a new HVC to allow the host to trigger the ITS emulation
setup.

This interface notifies the ITS driver that hypervisor initialization is
complete. Upon invocation, the hypervisor replaces the initial
"trap-and-forward" MMIO handler with a full-featured emulation handler.
This transition enables mediated access to the ITS hardware, enforcing
the verifications required for a protected hypervisor environment.

Signed-off-by: Sebastian Ene <sebastianene@google.com>
---
arch/arm64/include/asm/kvm_asm.h | 1 +
arch/arm64/include/asm/kvm_pkvm.h | 3 ++-
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 14 ++++++++++++++
arch/arm64/kvm/pkvm.c | 24 +++++++++++++++++++++++-
4 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index a1ad12c72ebf..550dafee88ef 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -89,6 +89,7 @@ enum __kvm_host_smccc_func {
__KVM_HOST_SMCCC_FUNC___pkvm_vcpu_load,
__KVM_HOST_SMCCC_FUNC___pkvm_vcpu_put,
__KVM_HOST_SMCCC_FUNC___pkvm_tlb_flush_vmid,
+ __KVM_HOST_SMCCC_FUNC___pkvm_init_its_emulation,
};

#define DECLARE_KVM_VHE_SYM(sym) extern char sym[]
diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h
index dc5ef2f9ac49..20fb2678a9b9 100644
--- a/arch/arm64/include/asm/kvm_pkvm.h
+++ b/arch/arm64/include/asm/kvm_pkvm.h
@@ -35,7 +35,8 @@ extern struct pkvm_protected_reg kvm_nvhe_sym(pkvm_protected_regs)[];
extern unsigned int kvm_nvhe_sym(num_protected_reg);
extern void kvm_nvhe_sym(pkvm_handle_forward_req)(struct pkvm_protected_reg *region, u64 offset,
bool write, u64 *reg, u8 reg_size);
-
+extern void kvm_nvhe_sym(pkvm_handle_gic_emulation)(struct pkvm_protected_reg *region, u64 offset,
+ bool write, u64 *reg, u8 reg_size);
int pkvm_init_host_vm(struct kvm *kvm);
int pkvm_create_hyp_vm(struct kvm *kvm);
bool pkvm_hyp_vm_is_created(struct kvm *kvm);
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index e7790097db93..4e58e24a1eed 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -14,6 +14,7 @@
#include <asm/kvm_hyp.h>
#include <asm/kvm_mmu.h>

+#include <nvhe/its_emulate.h>
#include <nvhe/ffa.h>
#include <nvhe/mem_protect.h>
#include <nvhe/mm.h>
@@ -421,6 +422,18 @@ static void handle___kvm_tlb_flush_vmid(struct kvm_cpu_context *host_ctxt)
__kvm_tlb_flush_vmid(kern_hyp_va(mmu));
}

+static void handle___pkvm_init_its_emulation(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(phys_addr_t, dev_addr, host_ctxt, 1);
+ DECLARE_REG(void *, its_state, host_ctxt, 2);
+ DECLARE_REG(struct its_shadow_tables *, shadow, host_ctxt, 3);
+
+ if (!is_protected_kvm_enabled())
+ return;
+
+ cpu_reg(host_ctxt, 1) = pkvm_init_gic_its_emulation(dev_addr, its_state, shadow);
+}
+
static void handle___pkvm_tlb_flush_vmid(struct kvm_cpu_context *host_ctxt)
{
DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
@@ -630,6 +643,7 @@ static const hcall_t host_hcall[] = {
HANDLE_FUNC(__pkvm_vcpu_load),
HANDLE_FUNC(__pkvm_vcpu_put),
HANDLE_FUNC(__pkvm_tlb_flush_vmid),
+ HANDLE_FUNC(__pkvm_init_its_emulation),
};

static void handle_host_hcall(struct kvm_cpu_context *host_ctxt)
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index a766be6de735..5399998d5235 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -6,6 +6,7 @@

#include <linux/init.h>
#include <linux/interval_tree_generic.h>
+#include <linux/irqchip/arm-gic-v3.h>
#include <linux/kmemleak.h>
#include <linux/kvm_host.h>
#include <asm/kvm_mmu.h>
@@ -62,7 +63,7 @@ static int __init register_protected_regions(void)

pkvm_protected_regs[i].start_pfn = res.start >> PAGE_SHIFT;
pkvm_protected_regs[i].num_pages = resource_size(&res) >> PAGE_SHIFT;
- pkvm_protected_regs[i].cb = lm_alias(&kvm_nvhe_sym(pkvm_handle_forward_req));
+ pkvm_protected_regs[i].cb = lm_alias(&kvm_nvhe_sym(pkvm_handle_gic_emulation));
i++;
}

@@ -286,16 +287,37 @@ static void __init _kvm_host_prot_finalize(void *arg)
WRITE_ONCE(*err, -EINVAL);
}

+static int pkvm_init_its_emulation(phys_addr_t dev_addr, struct its_shadow_tables *shadow)
+{
+ void *its_state;
+ int ret;
+
+ its_state = (void *)__get_free_page(GFP_KERNEL_ACCOUNT);
+ if (!its_state)
+ return -ENOMEM;
+
+ ret = kvm_call_hyp_nvhe(__pkvm_init_its_emulation, dev_addr, its_state, shadow);
+ if (ret)
+ free_page((unsigned long)its_state);
+
+ return ret;
+}
+
static int __init pkvm_drop_host_privileges(void)
{
int ret = 0;
+ void *flags;

/*
* Flip the static key upfront as that may no longer be possible
* once the host stage 2 is installed.
*/
static_branch_enable(&kvm_protected_mode_initialized);
+
+ flags = its_start_depriviledge();
on_each_cpu(_kvm_host_prot_finalize, &ret, 1);
+ its_end_depriviledge(ret, flags, &pkvm_init_its_emulation);
+
return ret;
}

--
2.53.0.473.g4a7958ca14-goog