* [PATCH v2 01/12] KVM: arm64: CCA: Reserve the RMM mode
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
@ 2026-11-11 14:57 ` Steven Price
2026-11-11 14:57 ` [PATCH v2 02/12] KVM: arm64: guest_memfd: Check mmap support when preparing memslot Steven Price
` (10 subsequent siblings)
11 siblings, 0 replies; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi,
Kohei Enju
Reserve KVM_MODE_RMM for CCA realm support. The kernel parameter that
selects this mode will be added when the Linux CCA implementation is
ready.
Signed-off-by: Steven Price <steven.price@arm.com>
---
arch/arm64/include/asm/kvm_host.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 97332ffcb130..ccd2dc8f73c3 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -120,6 +120,7 @@ enum kvm_mode {
KVM_MODE_DEFAULT,
KVM_MODE_PROTECTED,
KVM_MODE_NV,
+ KVM_MODE_RMM,
KVM_MODE_NONE,
};
#ifdef CONFIG_KVM
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v2 02/12] KVM: arm64: guest_memfd: Check mmap support when preparing memslot
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
2026-11-11 14:57 ` [PATCH v2 01/12] KVM: arm64: CCA: Reserve the RMM mode Steven Price
@ 2026-11-11 14:57 ` Steven Price
2026-11-11 15:21 ` Suzuki K Poulose
2026-11-11 14:57 ` [PATCH v2 03/12] KVM: arm64: CCA: Introduce Realms Steven Price
` (9 subsequent siblings)
11 siblings, 1 reply; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi,
Kohei Enju
Check the guest_memfd backing file when deciding whether an arm64
memslot has mappable memory rather than checking whether the slot is
gmem only. A public helper function is added to be able to reuse the
static kvm_gmem_supports_mmap() function.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v1:
- New patch in v2. Check the guest_memfd backing file rather than the
memslot flag when deciding whether arm64 can map the backing memory.
---
arch/arm64/kvm/mmu.c | 7 ++++---
include/linux/kvm_host.h | 1 +
virt/kvm/guest_memfd.c | 6 ++++++
3 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 1db10d8d6db7..a146f6cde3d0 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -2839,10 +2839,11 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
return -EFAULT;
/*
- * Only support guest_memfd backed memslots with mappable memory, since
- * there aren't any CoCo VMs that support only private memory on arm64.
+ * Protected VMs currently require guest_memfd backed memslots with
+ * mappable memory to support shared mappings.
*/
- if (kvm_slot_has_gmem(new) && !kvm_memslot_is_gmem_only(new))
+ if (kvm_arch_has_private_mem(kvm) && kvm_slot_has_gmem(new) &&
+ !kvm_gmem_slot_supports_mmap(new))
return -EINVAL;
/* guest_memfd is incompatible with MTE. */
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index e7fc94e2910a..f65728a713ec 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2614,6 +2614,7 @@ static inline bool kvm_is_private_gfn(struct kvm *kvm, gfn_t gfn)
#ifdef CONFIG_KVM_GUEST_MEMFD
bool kvm_gmem_is_private_gfn(struct kvm *kvm, gfn_t gfn);
+bool kvm_gmem_slot_supports_mmap(const struct kvm_memory_slot *slot);
bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm);
static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 8c97a69b16dc..3dd8d227bebb 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -442,6 +442,12 @@ static bool kvm_gmem_supports_mmap(struct inode *inode)
return GMEM_I(inode)->flags & GUEST_MEMFD_FLAG_MMAP;
}
+bool kvm_gmem_slot_supports_mmap(const struct kvm_memory_slot *slot)
+{
+ /* The guest_memfd file is pinned while preparing a new memslot. */
+ return kvm_gmem_supports_mmap(file_inode(slot->gmem.file));
+}
+
static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
{
struct inode *inode = file_inode(vmf->vma->vm_file);
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v2 02/12] KVM: arm64: guest_memfd: Check mmap support when preparing memslot
2026-11-11 14:57 ` [PATCH v2 02/12] KVM: arm64: guest_memfd: Check mmap support when preparing memslot Steven Price
@ 2026-11-11 15:21 ` Suzuki K Poulose
0 siblings, 0 replies; 14+ messages in thread
From: Suzuki K Poulose @ 2026-11-11 15:21 UTC (permalink / raw)
To: Steven Price, kvm, kvmarm
Cc: Catalin Marinas, Marc Zyngier, Will Deacon, James Morse,
Oliver Upton, Zenghui Yu, linux-arm-kernel, linux-kernel,
Joey Gouly, Alexandru Elisei, Christoffer Dall, Fuad Tabba,
linux-coco, Ganapatrao Kulkarni, Gavin Shan, Shanker Donthineni,
Alper Gun, Aneesh Kumar K . V, Emi Kisanuki, Vishal Annapurve,
WeiLin.Chang, Lorenzo Pieralisi, Kohei Enju
On 11/11/2026 14:57, Steven Price wrote:
> Check the guest_memfd backing file when deciding whether an arm64
> memslot has mappable memory rather than checking whether the slot is
> gmem only. A public helper function is added to be able to reuse the
> static kvm_gmem_supports_mmap() function.
>
Please could you explain why we need this ? Why not rely on
kvm_memslot_is_gmem_only(), which already tells kvm_gmem_supports_mmap()
? With inplace conversion support gmem supports mmap.
> Signed-off-by: Steven Price <steven.price@arm.com>
>
> ---
> Changes since v1:
> - New patch in v2. Check the guest_memfd backing file rather than the
> memslot flag when deciding whether arm64 can map the backing memory.
> ---
> arch/arm64/kvm/mmu.c | 7 ++++---
> include/linux/kvm_host.h | 1 +
> virt/kvm/guest_memfd.c | 6 ++++++
> 3 files changed, 11 insertions(+), 3 deletions(-)
>
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index 1db10d8d6db7..a146f6cde3d0 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
> @@ -2839,10 +2839,11 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
> return -EFAULT;
>
> /*
> - * Only support guest_memfd backed memslots with mappable memory, since
> - * there aren't any CoCo VMs that support only private memory on arm64.
> + * Protected VMs currently require guest_memfd backed memslots with
> + * mappable memory to support shared mappings.
> */
> - if (kvm_slot_has_gmem(new) && !kvm_memslot_is_gmem_only(new))
> + if (kvm_arch_has_private_mem(kvm) && kvm_slot_has_gmem(new) &&
> + !kvm_gmem_slot_supports_mmap(new))
> return -EINVAL;
>
> /* guest_memfd is incompatible with MTE. */
> diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
> index e7fc94e2910a..f65728a713ec 100644
> --- a/include/linux/kvm_host.h
> +++ b/include/linux/kvm_host.h
> @@ -2614,6 +2614,7 @@ static inline bool kvm_is_private_gfn(struct kvm *kvm, gfn_t gfn)
>
> #ifdef CONFIG_KVM_GUEST_MEMFD
> bool kvm_gmem_is_private_gfn(struct kvm *kvm, gfn_t gfn);
> +bool kvm_gmem_slot_supports_mmap(const struct kvm_memory_slot *slot);
> bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm);
>
> static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)
> diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
> index 8c97a69b16dc..3dd8d227bebb 100644
> --- a/virt/kvm/guest_memfd.c
> +++ b/virt/kvm/guest_memfd.c
> @@ -442,6 +442,12 @@ static bool kvm_gmem_supports_mmap(struct inode *inode)
> return GMEM_I(inode)->flags & GUEST_MEMFD_FLAG_MMAP;
> }
>
> +bool kvm_gmem_slot_supports_mmap(const struct kvm_memory_slot *slot)
> +{
> + /* The guest_memfd file is pinned while preparing a new memslot. */
We should gate this on kvm_slot_has_gmem() and how do we ensure that the
call comes after it is prepared ?
> + return kvm_gmem_supports_mmap(file_inode(slot->gmem.file));
> +}
> +
> static vm_fault_t kvm_gmem_fault_user_mapping(struct vm_fault *vmf)
> {
> struct inode *inode = file_inode(vmf->vma->vm_file);
Cheers
Suzuki
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v2 03/12] KVM: arm64: CCA: Introduce Realms
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
2026-11-11 14:57 ` [PATCH v2 01/12] KVM: arm64: CCA: Reserve the RMM mode Steven Price
2026-11-11 14:57 ` [PATCH v2 02/12] KVM: arm64: guest_memfd: Check mmap support when preparing memslot Steven Price
@ 2026-11-11 14:57 ` Steven Price
2026-11-11 14:57 ` [PATCH v2 04/12] KVM: arm64: CCA: Check RMI support and features at init Steven Price
` (8 subsequent siblings)
11 siblings, 0 replies; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Suzuki K Poulose, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Zenghui Yu, linux-arm-kernel,
linux-kernel, Joey Gouly, Alexandru Elisei, Christoffer Dall,
Fuad Tabba, linux-coco, Ganapatrao Kulkarni, Gavin Shan,
Shanker Donthineni, Alper Gun, Aneesh Kumar K . V, Emi Kisanuki,
Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi, Kohei Enju,
Steven Price
From: Suzuki K Poulose <suzuki.poulose@arm.com>
Introduce the Realm VM flavor and per-VM state tracking. Realm and
protected pKVM states are mutually exclusive, so share their storage
in kvm_arch. Adjust the protected VM helpers for the new flavor.
Realm VMs cannot be created yet; RMI initialization and the creation
UABI follow in later patches.
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v1:
- Move RMI initialization to the RMI checks patch and Realm vCPU
feature restrictions to the follow-on integration series. Keep
this patch focused on the Realm VM flavor and per-VM state.
- Limit pKVM power state handling to pKVM vCPUs.
---
arch/arm64/include/asm/kvm_emulate.h | 16 +++++++
arch/arm64/include/asm/kvm_host.h | 47 ++++++++++++--------
arch/arm64/include/asm/kvm_rmi.h | 59 ++++++++++++++++++++++++++
arch/arm64/kvm/arm.c | 7 ++-
arch/arm64/kvm/hyp/include/nvhe/pkvm.h | 2 +-
arch/arm64/kvm/hyp/nvhe/switch.c | 4 +-
arch/arm64/kvm/hyp/nvhe/timer-sr.c | 2 +-
arch/arm64/kvm/mmu.c | 1 +
arch/arm64/kvm/pkvm.c | 2 +-
arch/arm64/kvm/psci.c | 2 +-
10 files changed, 117 insertions(+), 25 deletions(-)
create mode 100644 arch/arm64/include/asm/kvm_rmi.h
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index 334211b8b2ee..32021bdf2610 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -831,4 +831,20 @@ static inline void kvm_reset_vcpu_psci(struct kvm_vcpu *vcpu,
vcpu_set_reg(vcpu, 0, reset_state->r0);
}
+static inline enum realm_state kvm_realm_state(struct kvm *kvm)
+{
+ return READ_ONCE(kvm->arch.realm.state);
+}
+
+static inline void kvm_set_realm_state(struct kvm *kvm,
+ enum realm_state new_state)
+{
+ WRITE_ONCE(kvm->arch.realm.state, new_state);
+}
+
+static inline bool kvm_realm_is_created(struct kvm *kvm)
+{
+ return kvm_vm_is_realm(kvm) && kvm_realm_state(kvm) != REALM_STATE_NONE;
+}
+
#endif /* __ARM64_KVM_EMULATE_H__ */
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index ccd2dc8f73c3..7948b177bf13 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -29,6 +29,7 @@
#include <asm/kvm.h>
#include <asm/kvm_asm.h>
#include <asm/kvm_hcall.h>
+#include <asm/kvm_rmi.h>
#include <asm/vncr_mapping.h>
#define __KVM_HAVE_ARCH_INTC_INITIALIZED
@@ -409,6 +410,7 @@ enum kvm_arm_vm_flavor {
VM_PKVM, /* Normal guests on pKVM */
MARKER(__VM_PROTECTED),
VM_PROTECTED_PKVM, /* Protected VM */
+ VM_REALM, /* CCA */
VM_FLAVOR_MAX
};
@@ -502,11 +504,14 @@ struct kvm_arch {
/* Count the number of VNCR_EL2 TLBs */
atomic_t vncr_tlb_count;
- /*
- * For an untrusted host VM, 'pkvm.handle' is used to lookup
- * the associated pKVM instance in the hypervisor.
- */
- struct kvm_protected_vm pkvm;
+ union {
+ /*
+ * For an untrusted host VM, 'pkvm.handle' is used to lookup
+ * the associated pKVM instance in the hypervisor.
+ */
+ struct kvm_protected_vm pkvm;
+ struct realm realm;
+ };
#ifdef CONFIG_PTDUMP_STAGE2_DEBUGFS
/* Nested virtualization info */
@@ -1572,31 +1577,39 @@ struct kvm *kvm_arch_alloc_vm(void);
#define __KVM_HAVE_ARCH_FLUSH_REMOTE_TLBS_RANGE
-#ifdef __KVM_NVHE_HYPERVISOR__
+#define kvm_vm_is_protected(kvm) ((kvm)->arch.vm_flavor >= __VM_PROTECTED)
-#define kvm_vm_is_protected(kvm) \
- (is_protected_kvm_enabled() && ((kvm)->arch.vm_flavor == VM_PROTECTED_PKVM))
+#ifdef __KVM_NVHE_HYPERVISOR__
/*
* Accessing vcpu->kvm from nVHE hyp stub is tricky, as we need to convert the
* pointer to the hyp VA. With pKVM, the nVHE code runs with the hyp_vcpu,
- * which is populated correctly and is gated on is_protected_kvm_enabled().
+ * which is populated correctly. Always vcpu_is_protected_pkvm(), which is
+ * gated on is_protected_kvm_enabled().
*/
-#define vcpu_is_protected(vcpu) \
+#define vcpu_is_protected(vcpu) BUILD_BUG_ON(1)
+#else
+#define vcpu_is_protected(vcpu) kvm_vm_is_protected((vcpu)->kvm)
+#endif
+
+#define kvm_vm_is_protected_pkvm(kvm) \
+ (is_protected_kvm_enabled() && ((kvm)->arch.vm_flavor == VM_PROTECTED_PKVM))
+
+/*
+ * Rely on is_protected_kvm_enabled() check in kvm_vm_is_protected_pkvm() to
+ * make sure the vcpu->kvm is always valid VA in the context
+ */
+#define vcpu_is_protected_pkvm(vcpu) \
({ \
struct kvm *__kvm = READ_ONCE((vcpu)->kvm); \
\
- (__kvm && kvm_vm_is_protected(__kvm)); \
+ (__kvm && kvm_vm_is_protected_pkvm(__kvm)); \
})
-#else
-
-#define kvm_vm_is_protected(kvm) ((kvm)->arch.vm_flavor >= __VM_PROTECTED)
-#define vcpu_is_protected(vcpu) kvm_vm_is_protected((vcpu)->kvm)
+#define kvm_vm_is_realm(kvm) ((kvm)->arch.vm_flavor == VM_REALM)
+#define vcpu_is_rec(vcpu) kvm_vm_is_realm((vcpu)->kvm)
#define kvm_vm_hyp_is_distrusting(kvm) ((kvm)->arch.vm_flavor >= __VM_DISTRUSTING_HYP)
-#endif /* __KVM_NVHE_HYPERVISOR__ */
-
#define kvm_has_mte(kvm) \
(system_supports_mte() && \
test_bit(KVM_ARCH_FLAG_MTE_ENABLED, &(kvm)->arch.flags))
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
new file mode 100644
index 000000000000..67f16793b4d0
--- /dev/null
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023-2026 ARM Ltd.
+ */
+
+#ifndef __ASM_KVM_RMI_H
+#define __ASM_KVM_RMI_H
+
+/**
+ * enum realm_state - State of a Realm
+ *
+ * Mirrors the RMM's Realm lifecycle states where they are meaningful to KVM,
+ * with REALM_STATE_DYING being a KVM-internal state used to prevent further
+ * requests while teardown is in progress. KVM does not track REALM_SYSTEM_OFF
+ * or REALM_ZOMBIE separately as they naturally lead to teardown.
+ */
+enum realm_state {
+ /**
+ * @REALM_STATE_NONE:
+ * Realm has not yet been created. rmi_realm_create() has not
+ * yet been called.
+ */
+ REALM_STATE_NONE,
+ /**
+ * @REALM_STATE_NEW:
+ * Realm is under construction, Realm Descriptor has been created,
+ * but it is not yet activated. Memory may be populated.
+ */
+ REALM_STATE_NEW,
+ /**
+ * @REALM_STATE_ACTIVE:
+ * Realm has been created and is eligible for execution with
+ * rmi_rec_enter(). Memory may no longer be populated with known
+ * contents.
+ */
+ REALM_STATE_ACTIVE,
+ /**
+ * @REALM_STATE_DYING:
+ * Realm is in the process of being destroyed or has already been
+ * destroyed.
+ */
+ REALM_STATE_DYING,
+ /**
+ * @REALM_STATE_DEAD:
+ * Realm has been destroyed.
+ */
+ REALM_STATE_DEAD
+};
+
+/**
+ * struct realm - Additional per VM data for a Realm
+ *
+ * @state: The lifetime state machine for the realm
+ */
+struct realm {
+ enum realm_state state;
+};
+
+#endif /* __ASM_KVM_RMI_H */
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 157bf4b81202..e06cc390a3d6 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -877,7 +877,8 @@ static void __kvm_arm_vcpu_power_off(struct kvm_vcpu *vcpu, u32 mp_state)
/* The guest's own CPU_OFF: EL2 has already powered the vCPU off. */
void kvm_arm_vcpu_power_off(struct kvm_vcpu *vcpu)
{
- u32 mp_state = vcpu_is_protected(vcpu) ? KVM_MP_STATE_UNINITIALIZED : KVM_MP_STATE_STOPPED;
+ u32 mp_state = vcpu_is_protected_pkvm(vcpu) ?
+ KVM_MP_STATE_UNINITIALIZED : KVM_MP_STATE_STOPPED;
spin_lock(&vcpu->arch.mp_state_lock);
__kvm_arm_vcpu_power_off(vcpu, mp_state);
@@ -906,7 +907,8 @@ static bool kvm_arm_vcpu_suspended(struct kvm_vcpu *vcpu)
/* Only the guest's CPU_ON may start a vCPU EL2 holds powered off. */
bool kvm_pkvm_vcpu_is_powered_off(struct kvm_vcpu *vcpu)
{
- return READ_ONCE(vcpu->arch.mp_state.mp_state) == KVM_MP_STATE_UNINITIALIZED;
+ return vcpu_is_protected_pkvm(vcpu) &&
+ READ_ONCE(vcpu->arch.mp_state.mp_state) == KVM_MP_STATE_UNINITIALIZED;
}
int kvm_arch_vcpu_ioctl_get_mpstate(struct kvm_vcpu *vcpu,
@@ -2312,6 +2314,7 @@ static const struct kvm_vcpu_ops *arm64_vcpu_ops[] = {
KVM_VCPU_OPS(VM_VHE, vhe_vcpu_ops),
KVM_VCPU_OPS(VM_PKVM, pkvm_vcpu_ops),
KVM_VCPU_OPS(VM_PROTECTED_PKVM, pkvm_vcpu_ops),
+ [VM_REALM] = NULL,
};
static void kvm_init_vcpu_ops(struct kvm_vcpu *vcpu)
diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
index 0261b2bc681f..ae6ced6d7b9e 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
@@ -84,7 +84,7 @@ pkvm_hyp_vcpu_to_hyp_vm(struct pkvm_hyp_vcpu *hyp_vcpu)
static inline bool pkvm_hyp_vcpu_is_protected(struct pkvm_hyp_vcpu *hyp_vcpu)
{
- return vcpu_is_protected(&hyp_vcpu->vcpu);
+ return vcpu_is_protected_pkvm(&hyp_vcpu->vcpu);
}
static inline bool pkvm_hyp_vm_is_protected(struct pkvm_hyp_vm *hyp_vm)
diff --git a/arch/arm64/kvm/hyp/nvhe/switch.c b/arch/arm64/kvm/hyp/nvhe/switch.c
index 89fb9dd351ad..6c03f48e6be3 100644
--- a/arch/arm64/kvm/hyp/nvhe/switch.c
+++ b/arch/arm64/kvm/hyp/nvhe/switch.c
@@ -238,7 +238,7 @@ static const exit_handler_fn pvm_exit_handlers[] = {
static const exit_handler_fn *kvm_get_exit_handler_array(struct kvm_vcpu *vcpu)
{
- if (unlikely(vcpu_is_protected(vcpu)))
+ if (unlikely(vcpu_is_protected_pkvm(vcpu)))
return pvm_exit_handlers;
return hyp_exit_handlers;
@@ -259,7 +259,7 @@ static inline bool fixup_guest_exit(struct kvm_vcpu *vcpu, u64 *exit_code)
* it. The check below is based on the one in
* kvm_arch_vcpu_ioctl_run().
*/
- if (unlikely(vcpu_is_protected(vcpu) && vcpu_mode_is_32bit(vcpu))) {
+ if (unlikely(vcpu_is_protected_pkvm(vcpu) && vcpu_mode_is_32bit(vcpu))) {
/*
* As we have caught the guest red-handed, decide that it isn't
* fit for purpose anymore by making the vcpu invalid. The VMM
diff --git a/arch/arm64/kvm/hyp/nvhe/timer-sr.c b/arch/arm64/kvm/hyp/nvhe/timer-sr.c
index 993065716913..17bd1ff621ab 100644
--- a/arch/arm64/kvm/hyp/nvhe/timer-sr.c
+++ b/arch/arm64/kvm/hyp/nvhe/timer-sr.c
@@ -48,7 +48,7 @@ void __timer_enable_traps(struct kvm_vcpu *vcpu)
* or running a protected VM (we don't offset anything in this case).
*/
clr = CNTHCTL_EL1PCEN;
- if (vcpu_is_protected(vcpu) ||
+ if (vcpu_is_protected_pkvm(vcpu) ||
!timer_get_offset(vcpu_ptimer(vcpu)))
set |= CNTHCTL_EL1PCTEN;
else
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index a146f6cde3d0..434502e8c4f9 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -3166,6 +3166,7 @@ static const struct kvm_vm_s2_ops *arm64_vm_s2_ops[] = {
KVM_VM_S2_OPS(VM_NVHE, kvm_default_vm_s2_ops),
KVM_VM_S2_OPS(VM_PKVM, pkvm_vm_s2_ops),
KVM_VM_S2_OPS(VM_PROTECTED_PKVM, protected_pkvm_vm_s2_ops),
+ [VM_REALM] = NULL,
};
static int kvm_vm_init_vm_s2_ops(struct kvm *kvm)
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index 016997288fd2..a5145bd75d0c 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -192,7 +192,7 @@ static int __pkvm_create_hyp_vcpu(struct kvm_vcpu *vcpu)
* Mirror EL2's seeding of power_state from mp_state. The hyp vCPU is
* published, so take mp_state_lock against kvm_psci_vcpu_on().
*/
- if (vcpu_is_protected(vcpu)) {
+ if (vcpu_is_protected_pkvm(vcpu)) {
spin_lock(&vcpu->arch.mp_state_lock);
if (kvm_arm_vcpu_stopped(vcpu))
WRITE_ONCE(vcpu->arch.mp_state.mp_state, KVM_MP_STATE_UNINITIALIZED);
diff --git a/arch/arm64/kvm/psci.c b/arch/arm64/kvm/psci.c
index b01f8d44033f..6674e8d6ff4c 100644
--- a/arch/arm64/kvm/psci.c
+++ b/arch/arm64/kvm/psci.c
@@ -68,7 +68,7 @@ static unsigned long kvm_psci_vcpu_on(struct kvm_vcpu *source_vcpu)
* STOPPED before that is the VMM's pause.
*/
if (!kvm_arm_vcpu_stopped(vcpu) ||
- (vcpu_is_protected(vcpu) && !kvm_pkvm_vcpu_is_powered_off(vcpu))) {
+ (vcpu_is_protected_pkvm(vcpu) && !kvm_pkvm_vcpu_is_powered_off(vcpu))) {
if (kvm_psci_version(source_vcpu) != KVM_ARM_PSCI_0_1)
ret = PSCI_RET_ALREADY_ON;
else
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v2 04/12] KVM: arm64: CCA: Check RMI support and features at init
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
` (2 preceding siblings ...)
2026-11-11 14:57 ` [PATCH v2 03/12] KVM: arm64: CCA: Introduce Realms Steven Price
@ 2026-11-11 14:57 ` Steven Price
2026-11-11 14:57 ` [PATCH v2 05/12] KVM: arm64: Handle attributes in stage2_unmap_range Steven Price
` (7 subsequent siblings)
11 siblings, 0 replies; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi,
Kohei Enju
Wire RMI initialization into KVM startup and select ARM_RMM_RMI.
Proceed only when RMM mode is selected and RMI is available. If
KVM uses LPA2, require RMM LPA2 support. Also require SHA-256
measurement support.
Define a disabled kvm_rmi_is_available static key for later Realm
capability checks. The key is enabled after the remaining Realm
support is in place.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v1:
- Combine the separate v1 RMI availability and LPA2/SHA-256 feature
check patches into one initialization patch.
---
arch/arm64/include/asm/kvm_rmi.h | 2 ++
arch/arm64/include/asm/virt.h | 1 +
arch/arm64/kvm/Kconfig | 1 +
arch/arm64/kvm/Makefile | 2 +-
arch/arm64/kvm/arm.c | 5 ++++
arch/arm64/kvm/rmi.c | 42 ++++++++++++++++++++++++++++++++
include/linux/arm-rmi-cmds.h | 5 ++++
7 files changed, 57 insertions(+), 1 deletion(-)
create mode 100644 arch/arm64/kvm/rmi.c
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index 67f16793b4d0..3194daaacf2b 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -56,4 +56,6 @@ struct realm {
enum realm_state state;
};
+void kvm_init_rmi(void);
+
#endif /* __ASM_KVM_RMI_H */
diff --git a/arch/arm64/include/asm/virt.h b/arch/arm64/include/asm/virt.h
index b546703c3ab9..92cec42952f4 100644
--- a/arch/arm64/include/asm/virt.h
+++ b/arch/arm64/include/asm/virt.h
@@ -87,6 +87,7 @@ void __hyp_reset_vectors(void);
bool is_kvm_arm_initialised(void);
DECLARE_STATIC_KEY_FALSE(kvm_protected_mode_initialized);
+DECLARE_STATIC_KEY_FALSE(kvm_rmi_is_available);
static inline bool is_pkvm_initialized(void)
{
diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index cecea0820e52..d067063a3686 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -34,6 +34,7 @@ menuconfig KVM
select GUEST_PERF_EVENTS if PERF_EVENTS
select KVM_GUEST_MEMFD
select KVM_GENERIC_PRE_FAULT_MEMORY
+ select ARM_RMM_RMI
help
Support hosting virtualized guest machines.
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 7aaeeb84e788..66a719ab5b6b 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -16,7 +16,7 @@ CFLAGS_handle_exit.o += -Wno-override-init
kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
inject_fault.o va_layout.o handle_exit.o config.o \
guest.o debug.o reset.o sys_regs.o stacktrace.o \
- vgic-sys-reg-v3.o fpsimd.o pkvm.o \
+ vgic-sys-reg-v3.o fpsimd.o pkvm.o rmi.o \
arch_timer.o trng.o vmid.o emulate-nested.o nested.o at.o \
vgic/vgic.o vgic/vgic-init.o \
vgic/vgic-irqfd.o vgic/vgic-v2.o \
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index e06cc390a3d6..318e8a4b19f8 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -42,6 +42,7 @@
#include <asm/kvm_nested.h>
#include <asm/kvm_pkvm.h>
#include <asm/kvm_ptrauth.h>
+#include <asm/kvm_rmi.h>
#include <asm/sections.h>
#include <asm/stacktrace/nvhe.h>
@@ -112,6 +113,8 @@ long kvm_get_cap_for_kvm_ioctl(unsigned int ioctl, long *ext)
return -EINVAL;
}
+DEFINE_STATIC_KEY_FALSE(kvm_rmi_is_available);
+
DECLARE_KVM_HYP_PER_CPU(unsigned long, kvm_hyp_vector);
DEFINE_PER_CPU(unsigned long, kvm_arm_hyp_stack_base);
@@ -3272,6 +3275,8 @@ static __init int kvm_arm_init(void)
in_hyp_mode = is_kernel_in_hyp_mode();
+ kvm_init_rmi();
+
if (cpus_have_final_cap(ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) ||
cpus_have_final_cap(ARM64_WORKAROUND_1508412))
kvm_info("Guests without required CPU erratum workarounds can deadlock system!\n" \
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
new file mode 100644
index 000000000000..f4c45021c6f7
--- /dev/null
+++ b/arch/arm64/kvm/rmi.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023-2026 ARM Ltd.
+ */
+
+#include <linux/arm-rmi-cmds.h>
+#include <linux/kvm_host.h>
+
+#include <asm/kvm_pgtable.h>
+#include <asm/virt.h>
+
+static int rmm_check_features(void)
+{
+ if (kvm_lpa2_is_enabled() &&
+ !rmi_has_feature(0, RMI_FEATURE_REGISTER_0_LPA2)) {
+ kvm_err("RMM doesn't support LPA2\n");
+ return -ENXIO;
+ }
+
+ if (!rmi_has_feature(1, RMI_FEATURE_REGISTER_1_HASH_SHA_256)) {
+ kvm_err("RMM doesn't support SHA-256 measurements\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+void kvm_init_rmi(void)
+{
+ if (kvm_get_mode() != KVM_MODE_RMM)
+ return;
+
+ if (!is_rmi_available()) {
+ kvm_info("Realm VM support is disabled because RMI is not available\n");
+ return;
+ }
+
+ if (rmm_check_features())
+ return;
+
+ /* Future patch will enable static branch kvm_rmi_is_available */
+}
diff --git a/include/linux/arm-rmi-cmds.h b/include/linux/arm-rmi-cmds.h
index a84e50ac851c..6f43eac7c7be 100644
--- a/include/linux/arm-rmi-cmds.h
+++ b/include/linux/arm-rmi-cmds.h
@@ -67,6 +67,11 @@ static __always_inline void rmi_smccc_invoke_once(struct arm_smccc_1_2_regs *reg
unsigned long rmi_feat_reg(unsigned int index);
+static inline bool rmi_has_feature(unsigned int index, unsigned long feature)
+{
+ return !!(rmi_feat_reg(index) & feature);
+}
+
int rmi_delegate_range(phys_addr_t phys, unsigned long size,
phys_addr_t *out_phys);
int rmi_undelegate_range(phys_addr_t phys, unsigned long size);
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v2 05/12] KVM: arm64: Handle attributes in stage2_unmap_range
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
` (3 preceding siblings ...)
2026-11-11 14:57 ` [PATCH v2 04/12] KVM: arm64: CCA: Check RMI support and features at init Steven Price
@ 2026-11-11 14:57 ` Steven Price
2026-11-11 14:57 ` [PATCH v2 06/12] KVM: arm64: CCA: Add basic infrastructure for creating a realm Steven Price
` (6 subsequent siblings)
11 siblings, 0 replies; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Suzuki K Poulose, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Zenghui Yu, linux-arm-kernel,
linux-kernel, Joey Gouly, Alexandru Elisei, Christoffer Dall,
Fuad Tabba, linux-coco, Ganapatrao Kulkarni, Gavin Shan,
Shanker Donthineni, Alper Gun, Aneesh Kumar K . V, Emi Kisanuki,
Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi, Kohei Enju,
Steven Price
From: Suzuki K Poulose <suzuki.poulose@arm.com>
With the introduction of private memory support, we need to
handle invalidation requests with filters for a given type.
Add support for passing the attributes down.
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Steven Price <steven.price@arm.com>
---
arch/arm64/include/asm/kvm_host.h | 4 +++-
arch/arm64/kvm/mmu.c | 31 +++++++++++++++++++++++--------
2 files changed, 26 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 7948b177bf13..d672d87ffe62 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -234,6 +234,7 @@ struct kvm_vcpu_ops {
};
struct kvm_gfn_range;
+enum kvm_gfn_range_filter;
struct kvm_s2_fault_desc;
struct kvm_vm_s2_ops {
@@ -244,7 +245,8 @@ struct kvm_vm_s2_ops {
u64 nr_pages);
void (*vm_stage2_unmap_range)(struct kvm_s2_mmu *mmu,
phys_addr_t start, u64 size,
- bool may_block);
+ bool may_block,
+ enum kvm_gfn_range_filter filter);
int (*vm_mem_abort)(const struct kvm_s2_fault_desc *s2fd);
};
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 434502e8c4f9..c5aeb3a58417 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -324,7 +324,8 @@ static int kvm_pgtable_stage2_unmap_tracked(struct kvm_pgtable *pgt,
static void kvm_vm_stage2_unmap_range(struct kvm_s2_mmu *mmu,
phys_addr_t start,
- u64 size, bool may_block)
+ u64 size, bool may_block,
+ enum kvm_gfn_range_filter filter)
{
struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu);
int (*fn)(struct kvm_pgtable *, u64, u64);
@@ -339,14 +340,16 @@ static void kvm_vm_stage2_unmap_range(struct kvm_s2_mmu *mmu,
static void pkvm_stage2_unmap_range(struct kvm_s2_mmu *mmu,
phys_addr_t start,
- u64 size, bool may_block)
+ u64 size, bool may_block,
+ enum kvm_gfn_range_filter filter)
{
WARN_ON(stage2_apply_range(mmu, start, start + size,
pkvm_pgtable_stage2_unmap, may_block));
}
static void no_stage2_unmap_range(struct kvm_s2_mmu *mmu,
- phys_addr_t start, u64 size, bool may_block)
+ phys_addr_t start, u64 size, bool may_block,
+ enum kvm_gfn_range_filter filter)
{
}
@@ -388,25 +391,36 @@ static int kvm_pgtable_stage2_unmap_tracked(struct kvm_pgtable *pgt, u64 addr, u
}
/**
- * kvm_stage2_unmap_range -- Clear stage2 page table entries to unmap a range
+ * kvm_stage2_unmap_range_filter -- Clear stage2 page table entries to unmap a
+ * range
* @mmu: The KVM stage-2 MMU pointer
* @start: The intermediate physical base address of the range to unmap
* @size: The size of the area to unmap
* @may_block: Whether or not we are permitted to block
+ * @filter: Filter for the ipa range (private and/or shared)
*
* Clear a range of stage-2 mappings, lowering the various ref-counts. Must
* be called while holding mmu_lock otherwise another faulting VCPU may
* come in and mess with things behind our backs.
*/
-void kvm_stage2_unmap_range(struct kvm_s2_mmu *mmu, phys_addr_t start,
- u64 size, bool may_block)
+static void kvm_stage2_unmap_range_filter(struct kvm_s2_mmu *mmu, phys_addr_t start,
+ u64 size, bool may_block,
+ enum kvm_gfn_range_filter filter)
{
struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu);
lockdep_assert_held_write(&kvm->mmu_lock);
WARN_ON(size & ~PAGE_MASK);
- kvm->arch.vm_s2_ops->vm_stage2_unmap_range(mmu, start, size, may_block);
+ kvm->arch.vm_s2_ops->vm_stage2_unmap_range(mmu, start, size,
+ may_block, filter);
+}
+
+void kvm_stage2_unmap_range(struct kvm_s2_mmu *mmu, phys_addr_t start,
+ u64 size, bool may_block)
+{
+ kvm_stage2_unmap_range_filter(mmu, start, size, may_block,
+ KVM_FILTER_PRIVATE | KVM_FILTER_SHARED);
}
void kvm_stage2_flush_range(struct kvm_s2_mmu *mmu, phys_addr_t addr, phys_addr_t end)
@@ -2597,7 +2611,8 @@ bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range)
if (!kvm->arch.mmu.pgt)
return false;
- kvm_stage2_unmap_range(&kvm->arch.mmu, gpa, size, may_block);
+ kvm_stage2_unmap_range_filter(&kvm->arch.mmu, gpa, size, may_block,
+ range->attr_filter);
kvm_nested_unmap_cipa_range(kvm, gpa, size, may_block);
return false;
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v2 06/12] KVM: arm64: CCA: Add basic infrastructure for creating a realm
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
` (4 preceding siblings ...)
2026-11-11 14:57 ` [PATCH v2 05/12] KVM: arm64: Handle attributes in stage2_unmap_range Steven Price
@ 2026-11-11 14:57 ` Steven Price
2026-11-11 14:57 ` [PATCH v2 07/12] KVM: arm64: CCA: Tear down RTTs Steven Price
` (5 subsequent siblings)
11 siblings, 0 replies; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi,
Kohei Enju
Introduce the skeleton functions for creating and destroying a realm.
The requested IPA size is checked against both the KVM and RMM limits.
The actual work of constructing the realm will be added in future
patches.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v1:
- Cap the Realm IPA size at the smaller KVM and RMM limit, and record
the selected IPA size when initializing Realm state.
---
arch/arm64/include/asm/kvm_rmi.h | 20 +++++++++++
arch/arm64/kvm/arm.c | 9 +++++
arch/arm64/kvm/mmu.c | 18 ++++++++--
arch/arm64/kvm/rmi.c | 57 ++++++++++++++++++++++++++++++++
4 files changed, 102 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index 3194daaacf2b..f1a1ed6b91f5 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -6,6 +6,9 @@
#ifndef __ASM_KVM_RMI_H
#define __ASM_KVM_RMI_H
+#include <linux/arm-smccc-rmi.h>
+#include <linux/kvm.h>
+
/**
* enum realm_state - State of a Realm
*
@@ -50,12 +53,29 @@ enum realm_state {
/**
* struct realm - Additional per VM data for a Realm
*
+ * @rd: Kernel mapping of the RMM-managed Realm Descriptor (RD) granule
+ * @sro: Preallocated SRO state context for Realm MMU operations
* @state: The lifetime state machine for the realm
+ * @ia_bits: Number of valid Input Address bits in the IPA
*/
struct realm {
+ void *rd;
+ /*
+ * Reused by RTT map/unmap SRO commands. Those commands are only
+ * issued from Realm stage-2 map/unmap paths while kvm->mmu_lock is
+ * held for write, including Realm fault handling where
+ * kvm_fault_lock() takes the write side, so concurrent use is
+ * serialized.
+ */
+ struct rmi_sro_state *sro;
enum realm_state state;
+ unsigned int ia_bits;
};
void kvm_init_rmi(void);
+u32 kvm_rmm_ipa_limit(void);
+
+int kvm_init_realm(struct kvm *kvm);
+void kvm_destroy_realm(struct kvm *kvm);
#endif /* __ASM_KVM_RMI_H */
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 318e8a4b19f8..b65a83ab31d8 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -303,6 +303,13 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
bitmap_zero(kvm->arch.vcpu_features, KVM_VCPU_MAX_FEATURES);
+ /* Initialise the realm bits after the generic bits are enabled */
+ if (kvm_vm_is_realm(kvm)) {
+ ret = kvm_init_realm(kvm);
+ if (ret)
+ goto err_uninit_mmu;
+ }
+
return 0;
err_uninit_mmu:
@@ -367,6 +374,8 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
kvm_destroy_nested(kvm);
kvm_arm_teardown_hypercalls(kvm);
+ if (kvm_vm_is_realm(kvm))
+ kvm_destroy_realm(kvm);
}
static bool kvm_has_full_ptr_auth(void)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index c5aeb3a58417..9df3f8d5b4ef 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -981,10 +981,14 @@ static struct kvm_pgtable_mm_ops kvm_s2_mm_ops = {
static int kvm_init_ipa_range(struct kvm_s2_mmu *mmu, unsigned long type)
{
+ struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu);
u32 kvm_ipa_limit = get_kvm_ipa_limit();
u64 mmfr0, mmfr1;
u32 phys_shift;
+ if (kvm_vm_is_realm(kvm))
+ kvm_ipa_limit = min(kvm_ipa_limit, kvm_rmm_ipa_limit());
+
phys_shift = KVM_VM_TYPE_ARM_IPA_SIZE(type);
if (is_protected_kvm_enabled()) {
phys_shift = kvm_ipa_limit;
@@ -1034,9 +1038,18 @@ static void stage2_destroy_range(struct kvm_pgtable *pgt, phys_addr_t addr,
static void kvm_stage2_destroy(struct kvm_pgtable *pgt)
{
+ struct kvm *kvm = kvm_s2_mmu_to_kvm(pgt->mmu);
unsigned int ia_bits = VTCR_EL2_IPA(pgt->mmu->vtcr);
- stage2_destroy_range(pgt, 0, BIT(ia_bits));
+ /*
+ * Realm RTTs are inaccessible to the host and may contain stale data
+ * after the RMM has released them. The non-root RTTs are explicitly
+ * destroyed through RMI before the RD is destroyed; only the root PGD
+ * pages remain to be freed here.
+ */
+ if (!kvm_vm_is_realm(kvm))
+ stage2_destroy_range(pgt, 0, BIT(ia_bits));
+
KVM_PGT_FN(kvm_pgtable_stage2_destroy_pgd)(pgt);
}
@@ -1084,6 +1097,8 @@ int kvm_init_stage2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu, unsigned long t
return -EINVAL;
}
+ mmu->arch = &kvm->arch;
+
err = kvm_init_ipa_range(mmu, type);
if (err)
return err;
@@ -1092,7 +1107,6 @@ int kvm_init_stage2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu, unsigned long t
if (!pgt)
return -ENOMEM;
- mmu->arch = &kvm->arch;
err = KVM_PGT_FN(kvm_pgtable_stage2_init)(pgt, mmu, &kvm_s2_mm_ops);
if (err)
goto out_free_pgtable;
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index f4c45021c6f7..69c628cfb6c7 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -6,9 +6,66 @@
#include <linux/arm-rmi-cmds.h>
#include <linux/kvm_host.h>
+#include <asm/kvm_emulate.h>
+#include <asm/kvm_mmu.h>
#include <asm/kvm_pgtable.h>
#include <asm/virt.h>
+u32 kvm_rmm_ipa_limit(void)
+{
+ return u64_get_bits(rmi_feat_reg(0), RMI_FEATURE_REGISTER_0_S2SZ);
+}
+
+void kvm_destroy_realm(struct kvm *kvm)
+{
+ struct realm *realm = &kvm->arch.realm;
+ size_t pgd_size = kvm_pgtable_stage2_pgd_size(kvm->arch.mmu.vtcr);
+
+ if (!kvm_realm_is_created(kvm)) {
+ kfree(realm->sro);
+ realm->sro = NULL;
+ return;
+ }
+
+ kvm_set_realm_state(kvm, REALM_STATE_DYING);
+
+ if (realm->rd) {
+ phys_addr_t rd_phys = virt_to_phys(realm->rd);
+
+ if (WARN_ON(rmi_realm_terminate(rd_phys, realm->sro)))
+ return;
+
+ if (WARN_ON(rmi_realm_destroy(rd_phys, realm->sro)))
+ return;
+ rmi_free_delegated_page(rd_phys);
+ realm->rd = NULL;
+ }
+
+ if (WARN_ON(rmi_undelegate_range(kvm->arch.mmu.pgd_phys,
+ pgd_size)))
+ return;
+
+ kvm_set_realm_state(kvm, REALM_STATE_DEAD);
+
+ /* Now that the realm is destroyed, free the entry-level RTTs. */
+ kvm_free_stage2_pgd(&kvm->arch.mmu);
+
+ kfree(realm->sro);
+ realm->sro = NULL;
+}
+
+int kvm_init_realm(struct kvm *kvm)
+{
+ struct realm *realm = &kvm->arch.realm;
+
+ realm->ia_bits = VTCR_EL2_IPA(kvm->arch.mmu.vtcr);
+ realm->sro = kmalloc_obj(*realm->sro);
+ if (!realm->sro)
+ return -ENOMEM;
+
+ return 0;
+}
+
static int rmm_check_features(void)
{
if (kvm_lpa2_is_enabled() &&
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v2 07/12] KVM: arm64: CCA: Tear down RTTs
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
` (5 preceding siblings ...)
2026-11-11 14:57 ` [PATCH v2 06/12] KVM: arm64: CCA: Add basic infrastructure for creating a realm Steven Price
@ 2026-11-11 14:57 ` Steven Price
2026-11-11 14:57 ` [PATCH v2 08/12] KVM: arm64: CCA: Add Realm RTT mapping and invalidation helpers Steven Price
` (4 subsequent siblings)
11 siblings, 0 replies; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi,
Kohei Enju
The RMM owns the stage 2 page tables for a realm, and KVM must request
that the RMM creates/destroys entries as necessary. The physical pages
to store the page tables are delegated to the realm as required, and can
be undelegated when no longer used.
Creating new RTTs is the easy part, tearing down is a little more
tricky. The result of realm_rtt_destroy() can be used to effectively
walk the tree and destroy the entries (undelegating pages that were
given to the realm).
Tear down a created Realm whether it is still new or has been activated.
Unmap the Realm IPA space once during stage-2 teardown. Defer
destruction of non-root RTTs until final Realm destruction, after
REALM_TERMINATE and before destroying the RD; root RTTs remain until the
RD has gone away.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v1:
- Tear down created Realms in both NEW and ACTIVE states. Fix the
RTT start level, page accounting on failed undelegation, and
handling of RTT granules returned by RMI.
---
arch/arm64/include/asm/kvm_rmi.h | 9 ++
arch/arm64/kvm/mmu.c | 18 ++-
arch/arm64/kvm/rmi.c | 189 +++++++++++++++++++++++++++++++
3 files changed, 215 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index f1a1ed6b91f5..58d825e40bdf 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -57,6 +57,7 @@ enum realm_state {
* @sro: Preallocated SRO state context for Realm MMU operations
* @state: The lifetime state machine for the realm
* @ia_bits: Number of valid Input Address bits in the IPA
+ * @stage2_unmapped: The Realm stage-2 mappings have been removed
*/
struct realm {
void *rd;
@@ -70,6 +71,7 @@ struct realm {
struct rmi_sro_state *sro;
enum realm_state state;
unsigned int ia_bits;
+ bool stage2_unmapped;
};
void kvm_init_rmi(void);
@@ -77,5 +79,12 @@ u32 kvm_rmm_ipa_limit(void);
int kvm_init_realm(struct kvm *kvm);
void kvm_destroy_realm(struct kvm *kvm);
+int kvm_realm_teardown_stage2(struct kvm *kvm);
+
+static inline bool kvm_realm_is_private_address(struct realm *realm,
+ unsigned long addr)
+{
+ return !(addr & BIT(realm->ia_bits - 1));
+}
#endif /* __ASM_KVM_RMI_H */
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 9df3f8d5b4ef..be014f6c00e7 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1145,9 +1145,25 @@ int kvm_init_stage2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu, unsigned long t
return err;
}
+static void kvm_realm_uninit_stage2(struct kvm_s2_mmu *mmu)
+{
+ struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu);
+
+ mutex_lock(&kvm->arch.config_lock);
+ if (kvm_realm_state(kvm) == REALM_STATE_NEW ||
+ kvm_realm_state(kvm) == REALM_STATE_ACTIVE) {
+ kvm_set_realm_state(kvm, REALM_STATE_DYING);
+ WARN_ON(kvm_realm_teardown_stage2(kvm));
+ }
+ mutex_unlock(&kvm->arch.config_lock);
+}
+
void kvm_uninit_stage2_mmu(struct kvm *kvm)
{
- kvm_free_stage2_pgd(&kvm->arch.mmu);
+ if (kvm_realm_is_created(kvm))
+ kvm_realm_uninit_stage2(&kvm->arch.mmu);
+ else
+ kvm_free_stage2_pgd(&kvm->arch.mmu);
kvm_mmu_free_memory_cache(&kvm->arch.mmu.split_page_cache);
}
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 69c628cfb6c7..b904e6541fb0 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -11,16 +11,193 @@
#include <asm/kvm_pgtable.h>
#include <asm/virt.h>
+static inline unsigned long rmi_rtt_level_mapsize(int level)
+{
+ if (WARN_ON(level > KVM_PGTABLE_LAST_LEVEL))
+ return PAGE_SIZE;
+
+ return (1UL << ARM64_HW_PGTABLE_LEVEL_SHIFT(level));
+}
+
u32 kvm_rmm_ipa_limit(void)
{
return u64_get_bits(rmi_feat_reg(0), RMI_FEATURE_REGISTER_0_S2SZ);
}
+static int get_start_level(struct realm *realm)
+{
+ return 4 - max(2, stage2_pgtable_levels(realm->ia_bits));
+}
+
+static void free_rtt(phys_addr_t phys)
+{
+ kvm_account_pgtable_pages(phys_to_virt(phys), -1);
+ if (rmi_free_delegated_page(phys))
+ kvm_account_pgtable_pages(phys_to_virt(phys), 1);
+}
+
+/*
+ * realm_rtt_destroy - Destroy an RTT at @level for @addr.
+ *
+ * Returns - Result of the RMI_RTT_DESTROY call, and:
+ * @rtt_granule: RTT granule, if the RTT was destroyed.
+ * @next_addr: IPA corresponding to the next possible valid entry we
+ * can target
+ */
+static long realm_rtt_destroy(struct realm *realm, unsigned long addr,
+ int level, phys_addr_t *rtt_granule,
+ unsigned long *next_addr)
+{
+ unsigned long out_rtt;
+ long ret;
+
+ ret = rmi_rtt_destroy(virt_to_phys(realm->rd), addr, level,
+ &out_rtt, next_addr);
+
+ if (ret == RMI_SUCCESS)
+ *rtt_granule = out_rtt;
+
+ return ret;
+}
+
+static int realm_tear_down_rtt_level(struct realm *realm, int level,
+ unsigned long start, unsigned long end)
+{
+ ssize_t map_size;
+ unsigned long addr, next_addr;
+
+ if (WARN_ON(level > KVM_PGTABLE_LAST_LEVEL))
+ return -EINVAL;
+
+ map_size = rmi_rtt_level_mapsize(level - 1);
+
+ for (addr = start; addr < end; addr = next_addr) {
+ phys_addr_t rtt_granule;
+ long ret;
+ unsigned long align_addr = ALIGN(addr, map_size);
+
+ next_addr = ALIGN(addr + 1, map_size);
+
+ if (next_addr > end || align_addr != addr) {
+ /*
+ * The target range is smaller than what this level
+ * covers, recurse deeper.
+ */
+ ret = realm_tear_down_rtt_level(realm,
+ level + 1,
+ addr,
+ min(next_addr, end));
+ if (ret)
+ return ret;
+ continue;
+ }
+
+ ret = realm_rtt_destroy(realm, addr, level,
+ &rtt_granule, &next_addr);
+ if (ret < 0)
+ return ret;
+
+ switch (RMI_RESULT_STATUS(ret)) {
+ case RMI_SUCCESS:
+ free_rtt(rtt_granule);
+ break;
+ case RMI_ERROR_RTT:
+ if (next_addr > addr) {
+ /* Missing RTT, skip */
+ break;
+ }
+ /*
+ * We tear down the RTT range for the full IPA
+ * space, after everything is unmapped. Also we
+ * descend down only if we cannot tear down a
+ * top level RTT. Thus RMM must be able to walk
+ * to the requested level. e.g., a block mapping
+ * exists at L1 or L2.
+ */
+ if (WARN_ON(RMI_RESULT_DATA_LEVEL(ret) != level))
+ return -EBUSY;
+ if (WARN_ON(level == KVM_PGTABLE_LAST_LEVEL))
+ return -EBUSY;
+
+ /*
+ * The table has active entries in it, recurse deeper
+ * and tear down the RTTs.
+ */
+ next_addr = ALIGN(addr + 1, map_size);
+ ret = realm_tear_down_rtt_level(realm,
+ level + 1,
+ addr,
+ next_addr);
+ if (ret)
+ return ret;
+ /*
+ * Now that the child RTTs are destroyed,
+ * retry at this level.
+ */
+ next_addr = addr;
+ break;
+ default:
+ WARN_ON(1);
+ return -ENXIO;
+ }
+ }
+
+ return 0;
+}
+
+static int realm_tear_down_rtt_range(struct realm *realm,
+ unsigned long start, unsigned long end)
+{
+ /*
+ * Root level RTTs can only be destroyed after the RD is destroyed. So
+ * tear down everything below the root level
+ */
+ return realm_tear_down_rtt_level(realm, get_start_level(realm) + 1,
+ start, end);
+}
+
+static int realm_destroy_rtts(struct kvm *kvm)
+{
+ struct realm *realm = &kvm->arch.realm;
+ unsigned int ia_bits = realm->ia_bits;
+
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ return realm_tear_down_rtt_range(realm, 0, (1UL << ia_bits));
+}
+
+static void realm_unmap_stage2(struct kvm *kvm)
+{
+ struct realm *realm = &kvm->arch.realm;
+
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ if (realm->stage2_unmapped)
+ return;
+
+ write_lock(&kvm->mmu_lock);
+ kvm_stage2_unmap_range(&kvm->arch.mmu, 0,
+ BIT(realm->ia_bits - 1), true);
+ write_unlock(&kvm->mmu_lock);
+
+ realm->stage2_unmapped = true;
+}
+
+int kvm_realm_teardown_stage2(struct kvm *kvm)
+{
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ realm_unmap_stage2(kvm);
+ return 0;
+}
+
void kvm_destroy_realm(struct kvm *kvm)
{
struct realm *realm = &kvm->arch.realm;
size_t pgd_size = kvm_pgtable_stage2_pgd_size(kvm->arch.mmu.vtcr);
+ guard(mutex)(&kvm->arch.config_lock);
+
if (!kvm_realm_is_created(kvm)) {
kfree(realm->sro);
realm->sro = NULL;
@@ -29,12 +206,24 @@ void kvm_destroy_realm(struct kvm *kvm)
kvm_set_realm_state(kvm, REALM_STATE_DYING);
+ /*
+ * REALM_DESTROY requires the realm to be non-live: all RECs must have
+ * been destroyed and the root RTTs must be empty. Unmap the IPA space
+ * and destroy any non-root RTTs before tearing down the RD. The root
+ * RTT pages are still owned by the RMM at this point, so keep the KVM
+ * pgtable alive until after REALM_DESTROY and undelegation.
+ */
+ realm_unmap_stage2(kvm);
+
if (realm->rd) {
phys_addr_t rd_phys = virt_to_phys(realm->rd);
if (WARN_ON(rmi_realm_terminate(rd_phys, realm->sro)))
return;
+ if (WARN_ON(realm_destroy_rtts(kvm)))
+ return;
+
if (WARN_ON(rmi_realm_destroy(rd_phys, realm->sro)))
return;
rmi_free_delegated_page(rd_phys);
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v2 08/12] KVM: arm64: CCA: Add Realm RTT mapping and invalidation helpers
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
` (6 preceding siblings ...)
2026-11-11 14:57 ` [PATCH v2 07/12] KVM: arm64: CCA: Tear down RTTs Steven Price
@ 2026-11-11 14:57 ` Steven Price
2026-11-11 14:57 ` [PATCH v2 09/12] KVM: Allow architectures to reject memslot changes before invalidation Steven Price
` (3 subsequent siblings)
11 siblings, 0 replies; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi,
Kohei Enju
Add the RMI RTT creation and unmap helpers needed by CCA Realm stage-2
memory operations. REC exit handling and guest requested RIPAS changes
follow in the vCPU series.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v1:
- Rework RTT creation and folding around missing or existing RTTs.
Use a shared RMI block-size helper and handle alignment, partial
progress and errors in range operations.
---
arch/arm64/include/asm/kvm_rmi.h | 8 +
arch/arm64/kvm/rmi.c | 378 ++++++++++++++++++++++++++++++-
drivers/firmware/arm_rmm/rmi.c | 10 -
include/linux/arm-rmi-cmds.h | 8 +
4 files changed, 385 insertions(+), 19 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index 58d825e40bdf..e64c3bcce160 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -81,6 +81,14 @@ int kvm_init_realm(struct kvm *kvm);
void kvm_destroy_realm(struct kvm *kvm);
int kvm_realm_teardown_stage2(struct kvm *kvm);
+enum kvm_gfn_range_filter;
+
+void kvm_realm_unmap_range_filter(struct kvm *kvm,
+ unsigned long ipa,
+ unsigned long size,
+ bool may_block,
+ enum kvm_gfn_range_filter attr_filter);
+
static inline bool kvm_realm_is_private_address(struct realm *realm,
unsigned long addr)
{
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index b904e6541fb0..5aa34d68de59 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -11,14 +11,6 @@
#include <asm/kvm_pgtable.h>
#include <asm/virt.h>
-static inline unsigned long rmi_rtt_level_mapsize(int level)
-{
- if (WARN_ON(level > KVM_PGTABLE_LAST_LEVEL))
- return PAGE_SIZE;
-
- return (1UL << ARM64_HW_PGTABLE_LEVEL_SHIFT(level));
-}
-
u32 kvm_rmm_ipa_limit(void)
{
return u64_get_bits(rmi_feat_reg(0), RMI_FEATURE_REGISTER_0_S2SZ);
@@ -29,6 +21,75 @@ static int get_start_level(struct realm *realm)
return 4 - max(2, stage2_pgtable_levels(realm->ia_bits));
}
+static int find_map_level(struct realm *realm,
+ unsigned long start,
+ unsigned long end)
+{
+ int level = KVM_PGTABLE_LAST_LEVEL;
+
+ while (level > get_start_level(realm)) {
+ unsigned long map_size = kvm_granule_size(level - 1);
+
+ if (!IS_ALIGNED(start, map_size) ||
+ (start + map_size) > end)
+ break;
+
+ level--;
+ }
+
+ return level;
+}
+
+static int undelegate_range_desc(unsigned long desc)
+{
+ unsigned long size = rmi_addr_block_size_to_bytes(RMI_ADDR_RANGE_BLOCK_SIZE(desc));
+ unsigned long count = RMI_ADDR_RANGE_COUNT(desc);
+ unsigned long addr = RMI_ADDR_RANGE_ADDR(desc);
+ unsigned long state = RMI_ADDR_RANGE_STATE(desc);
+
+ if (state == RMI_OP_MEM_UNDELEGATED)
+ return 0;
+
+ if (size * count == 0)
+ return 0;
+
+ return rmi_undelegate_range(addr, size * count);
+}
+
+static phys_addr_t alloc_delegated_granule(struct kvm_mmu_memory_cache *mc)
+{
+ phys_addr_t phys;
+ void *virt;
+
+ if (mc) {
+ virt = kvm_mmu_memory_cache_alloc(mc);
+ } else {
+ virt = (void *)__get_free_page(GFP_ATOMIC | __GFP_ZERO |
+ __GFP_ACCOUNT);
+ }
+
+ if (!virt)
+ return PHYS_ADDR_MAX;
+
+ phys = virt_to_phys(virt);
+ if (rmi_delegate_page(phys)) {
+ free_page((unsigned long)virt);
+ return PHYS_ADDR_MAX;
+ }
+
+ return phys;
+}
+
+static phys_addr_t alloc_rtt(struct kvm_mmu_memory_cache *mc)
+{
+ phys_addr_t phys = alloc_delegated_granule(mc);
+
+ if (phys != PHYS_ADDR_MAX)
+ kvm_account_pgtable_pages(phys_to_virt(phys), 1);
+
+ return phys;
+}
+
static void free_rtt(phys_addr_t phys)
{
kvm_account_pgtable_pages(phys_to_virt(phys), -1);
@@ -36,6 +97,32 @@ static void free_rtt(phys_addr_t phys)
kvm_account_pgtable_pages(phys_to_virt(phys), 1);
}
+static long realm_rtt_create(struct realm *realm,
+ unsigned long addr,
+ int level,
+ phys_addr_t phys)
+{
+ addr = ALIGN_DOWN(addr, kvm_granule_size(level - 1));
+ return rmi_rtt_create(virt_to_phys(realm->rd), phys, addr, level);
+}
+
+static long realm_rtt_fold(struct realm *realm,
+ unsigned long addr,
+ int level,
+ phys_addr_t *rtt_granule)
+{
+ unsigned long out_rtt;
+ long ret;
+
+ addr = ALIGN_DOWN(addr, kvm_granule_size(level - 1));
+ ret = rmi_rtt_fold(virt_to_phys(realm->rd), addr, level, &out_rtt);
+
+ if (ret == RMI_SUCCESS && rtt_granule)
+ *rtt_granule = out_rtt;
+
+ return ret;
+}
+
/*
* realm_rtt_destroy - Destroy an RTT at @level for @addr.
*
@@ -60,6 +147,47 @@ static long realm_rtt_destroy(struct realm *realm, unsigned long addr,
return ret;
}
+static int realm_create_rtt_levels(struct realm *realm,
+ unsigned long ipa,
+ int level,
+ int max_level,
+ struct kvm_mmu_memory_cache *mc)
+{
+ long ret = 0;
+ phys_addr_t rtt = PHYS_ADDR_MAX;
+
+ while (level++ < max_level) {
+ if (rtt == PHYS_ADDR_MAX)
+ rtt = alloc_rtt(mc);
+ if (rtt == PHYS_ADDR_MAX)
+ return -ENOMEM;
+
+ ret = realm_rtt_create(realm, ipa, level, rtt);
+ if (ret < 0)
+ break;
+
+ if (RMI_RESULT_STATUS(ret) == RMI_ERROR_RTT &&
+ RMI_RESULT_DATA_LEVEL(ret) == level - 1) {
+ /* The RTT already exists, reuse the granule at the next level. */
+ ret = 0;
+ continue;
+ }
+
+ if (ret) {
+ WARN(1, "Failed to create RTT at level %d: %ld\n",
+ level, ret);
+ ret = -ENXIO;
+ break;
+ }
+ rtt = PHYS_ADDR_MAX;
+ }
+
+ if (rtt != PHYS_ADDR_MAX)
+ free_rtt(rtt);
+
+ return ret;
+}
+
static int realm_tear_down_rtt_level(struct realm *realm, int level,
unsigned long start, unsigned long end)
{
@@ -69,7 +197,9 @@ static int realm_tear_down_rtt_level(struct realm *realm, int level,
if (WARN_ON(level > KVM_PGTABLE_LAST_LEVEL))
return -EINVAL;
- map_size = rmi_rtt_level_mapsize(level - 1);
+ map_size = kvm_granule_size(level - 1);
+ if (WARN_ON(!IS_ALIGNED(start, map_size)))
+ return -EINVAL;
for (addr = start; addr < end; addr = next_addr) {
phys_addr_t rtt_granule;
@@ -156,12 +286,94 @@ static int realm_tear_down_rtt_range(struct realm *realm,
start, end);
}
+/*
+ * Returns 0 on successful fold, a negative value on error, a positive value if
+ * we were not able to fold all tables at this level.
+ */
+static int realm_fold_rtt_level(struct realm *realm, int level,
+ unsigned long start, unsigned long end)
+{
+ int not_folded = 0;
+ ssize_t map_size;
+ unsigned long addr, next_addr;
+
+ if (WARN_ON(level > KVM_PGTABLE_LAST_LEVEL))
+ return -EINVAL;
+
+ map_size = kvm_granule_size(level - 1);
+
+ for (addr = start; addr < end; addr = next_addr) {
+ phys_addr_t rtt_granule;
+ long ret;
+
+ next_addr = ALIGN(addr + 1, map_size);
+
+ ret = realm_rtt_fold(realm, addr, level, &rtt_granule);
+ if (ret < 0)
+ return ret;
+
+ switch (RMI_RESULT_STATUS(ret)) {
+ case RMI_SUCCESS:
+ free_rtt(rtt_granule);
+ break;
+ case RMI_ERROR_RTT: {
+ int err_level = RMI_RESULT_DATA_LEVEL(ret);
+
+ if (err_level < level) {
+ /* No RTT at this level; skip the missing ancestor. */
+ if (WARN_ON(err_level < get_start_level(realm)))
+ return -EINVAL;
+ next_addr = ALIGN(addr + 1,
+ kvm_granule_size(err_level - 1));
+ break;
+ }
+ if (level == KVM_PGTABLE_LAST_LEVEL) {
+ not_folded++;
+ break;
+ }
+ /* Recurse a level deeper */
+ ret = realm_fold_rtt_level(realm,
+ level + 1,
+ addr,
+ min(next_addr, end));
+ if (ret < 0)
+ return ret;
+ if (ret) {
+ not_folded++;
+ break;
+ }
+ /* Try once more after folding the child RTTs. */
+ ret = realm_rtt_fold(realm, addr, level, &rtt_granule);
+ if (ret < 0)
+ return ret;
+ if (RMI_RESULT_STATUS(ret) == RMI_SUCCESS) {
+ free_rtt(rtt_granule);
+ break;
+ }
+ if (RMI_RESULT_STATUS(ret) != RMI_ERROR_RTT) {
+ WARN_ON(1);
+ return -ENXIO;
+ }
+ not_folded++;
+ break;
+ }
+ default:
+ WARN_ON(1);
+ return -ENXIO;
+ }
+ }
+
+ return not_folded;
+}
+
static int realm_destroy_rtts(struct kvm *kvm)
{
struct realm *realm = &kvm->arch.realm;
unsigned int ia_bits = realm->ia_bits;
lockdep_assert_held(&kvm->arch.config_lock);
+ if (WARN_ON_ONCE(!realm->stage2_unmapped))
+ return -EINVAL;
return realm_tear_down_rtt_range(realm, 0, (1UL << ia_bits));
}
@@ -191,6 +403,154 @@ int kvm_realm_teardown_stage2(struct kvm *kvm)
return 0;
}
+static void realm_unmap_shared_range(struct kvm *kvm,
+ unsigned long start,
+ unsigned long end,
+ bool may_block)
+{
+ struct realm *realm = &kvm->arch.realm;
+ unsigned long rd = virt_to_phys(realm->rd);
+ unsigned long next_addr, addr;
+ unsigned long shared_bit = BIT(realm->ia_bits - 1);
+
+ if (start >= end)
+ return;
+
+ /*
+ * Callers may pass either an IPA range or its shared alias. Add the
+ * shared bit only for the former. Use addition so an exclusive end at
+ * shared_bit advances to the end of the IPA space.
+ */
+ if (start < shared_bit) {
+ start += shared_bit;
+ end += shared_bit;
+ }
+
+ for (addr = start; addr < end; addr = next_addr) {
+ long ret;
+
+ ret = rmi_rtt_unprot_unmap(rd, addr, end, RMI_ADDR_TYPE_NONE,
+ 0, &next_addr, NULL, NULL);
+ if (WARN_ON(ret < 0))
+ return;
+
+ switch (RMI_RESULT_STATUS(ret)) {
+ case RMI_SUCCESS:
+ break;
+ case RMI_ERROR_RTT: {
+ int err_level = RMI_RESULT_DATA_LEVEL(ret);
+ int level = find_map_level(realm, addr, end);
+
+ if (WARN_ON(err_level > KVM_PGTABLE_LAST_LEVEL))
+ return;
+
+ if (err_level >= level) {
+ /* Nothing present, so skip */
+ next_addr = ALIGN(addr + 1,
+ kvm_granule_size(err_level));
+ break;
+ }
+
+ ret = realm_create_rtt_levels(realm, addr, err_level,
+ level, NULL);
+ if (WARN_ON(ret))
+ return;
+ /* Retry with the RTT levels in place */
+ next_addr = addr;
+ break;
+ }
+ default:
+ WARN_ON(1);
+ return;
+ }
+
+ if (may_block)
+ cond_resched_rwlock_write(&kvm->mmu_lock);
+ }
+
+ realm_fold_rtt_level(realm, get_start_level(realm) + 1,
+ start, end);
+}
+
+static void realm_unmap_private_range(struct kvm *kvm,
+ unsigned long start,
+ unsigned long end,
+ bool may_block)
+{
+ struct realm *realm = &kvm->arch.realm;
+ unsigned long rd = virt_to_phys(realm->rd);
+ unsigned long next_addr, addr;
+ long ret;
+
+ for (addr = start; addr < end; addr = next_addr) {
+ unsigned long out_range;
+ unsigned long flags = RMI_ADDR_TYPE_SINGLE;
+ /* TODO: Optimise using RMI_ADDR_TYPE_LIST */
+
+ ret = rmi_rtt_data_unmap(rd, addr, end, flags, 0,
+ &next_addr, &out_range, NULL);
+ if (WARN_ON(ret < 0))
+ return;
+
+ switch (RMI_RESULT_STATUS(ret)) {
+ case RMI_SUCCESS:
+ ret = undelegate_range_desc(out_range);
+ if (WARN_ON(ret))
+ return;
+ break;
+ case RMI_ERROR_RTT: {
+ int err_level = RMI_RESULT_DATA_LEVEL(ret);
+ int level = find_map_level(realm, addr, end);
+
+ if (WARN_ON(err_level > KVM_PGTABLE_LAST_LEVEL))
+ return;
+
+ if (err_level >= level) {
+ /* Nothing present, so skip this RTT entry. */
+ next_addr = ALIGN(addr + 1,
+ kvm_granule_size(err_level));
+ break;
+ }
+
+ ret = realm_create_rtt_levels(realm, addr, err_level,
+ level, NULL);
+ if (WARN_ON(ret))
+ return;
+ /* Retry with the RTT levels in place. */
+ next_addr = addr;
+ break;
+ }
+ default:
+ WARN_ON(1);
+ return;
+ }
+
+ if (may_block)
+ cond_resched_rwlock_write(&kvm->mmu_lock);
+ }
+
+ realm_fold_rtt_level(realm, get_start_level(realm) + 1,
+ start, end);
+}
+
+void kvm_realm_unmap_range_filter(struct kvm *kvm, unsigned long start,
+ unsigned long size, bool may_block,
+ enum kvm_gfn_range_filter attr_filter)
+{
+ unsigned long end = start + size;
+ struct realm *realm = &kvm->arch.realm;
+
+ if (!kvm_realm_is_created(kvm))
+ return;
+
+ end = min(BIT(realm->ia_bits - 1), end);
+
+ if (attr_filter & KVM_FILTER_SHARED)
+ realm_unmap_shared_range(kvm, start, end, may_block);
+ if (attr_filter & KVM_FILTER_PRIVATE)
+ realm_unmap_private_range(kvm, start, end, may_block);
+}
+
void kvm_destroy_realm(struct kvm *kvm)
{
struct realm *realm = &kvm->arch.realm;
diff --git a/drivers/firmware/arm_rmm/rmi.c b/drivers/firmware/arm_rmm/rmi.c
index 77255a90e0a6..a9f42c9a4f79 100644
--- a/drivers/firmware/arm_rmm/rmi.c
+++ b/drivers/firmware/arm_rmm/rmi.c
@@ -11,7 +11,6 @@
#include <linux/slab.h>
#include <asm/memory.h>
-#include <asm/pgtable-hwdef.h>
/* RMM v2.0 defines RmiFeatureRegister0 to RmiFeatureRegister4. */
static unsigned long rmi_feat_reg_cache[5] __ro_after_init;
@@ -183,15 +182,6 @@ int rmi_free_delegated_page(phys_addr_t phys)
}
EXPORT_SYMBOL_GPL(rmi_free_delegated_page);
-/*
- * Convert the RmiAddrBlockSize to actual size. This is used in RmiDonateReq
- * and RmiAddrRangeDesc*.
- */
-static unsigned long rmi_addr_block_size_to_bytes(unsigned long block_size_fld)
-{
- return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(3 - block_size_fld));
-}
-
/*
* free_addr_range: Free memory described by the address range entry, which may
* be partially consumed by RMM.
diff --git a/include/linux/arm-rmi-cmds.h b/include/linux/arm-rmi-cmds.h
index 6f43eac7c7be..71ba94eb72c0 100644
--- a/include/linux/arm-rmi-cmds.h
+++ b/include/linux/arm-rmi-cmds.h
@@ -13,8 +13,16 @@
#include <linux/string.h>
#include <linux/types.h>
+#include <asm/pgtable-hwdef.h>
+
#define RMI_MAX_ADDR_LIST 256
+/* Convert the block size used by RmiDonateReq and RmiAddrRangeDesc. */
+static inline unsigned long rmi_addr_block_size_to_bytes(unsigned long block_size)
+{
+ return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(3 - block_size));
+}
+
struct rmi_sro_state {
struct arm_smccc_1_2_regs regs;
int addr_count;
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v2 09/12] KVM: Allow architectures to reject memslot changes before invalidation
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
` (7 preceding siblings ...)
2026-11-11 14:57 ` [PATCH v2 08/12] KVM: arm64: CCA: Add Realm RTT mapping and invalidation helpers Steven Price
@ 2026-11-11 14:57 ` Steven Price
2026-11-11 14:57 ` [PATCH v2 10/12] KVM: arm64: CCA: Prevent memslot modifications after Realm creation Steven Price
` (2 subsequent siblings)
11 siblings, 0 replies; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Suzuki K Poulose, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Zenghui Yu, linux-arm-kernel,
linux-kernel, Joey Gouly, Alexandru Elisei, Christoffer Dall,
Fuad Tabba, linux-coco, Ganapatrao Kulkarni, Gavin Shan,
Shanker Donthineni, Alper Gun, Aneesh Kumar K . V, Emi Kisanuki,
Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi, Kohei Enju,
Steven Price
From: Suzuki K Poulose <suzuki.poulose@arm.com>
Architectures may need to reject deletion of a memory slot without
changing the guest's memory mappings. Currently, KVM invalidates the slot
and calls kvm_arch_flush_shadow_memslot() before
kvm_arch_prepare_memory_region() can reject the request. On arm64, this
unmaps the slot from stage-2. Restoring the old memslot on failure does not
restore those mappings, so even a rejected deletion has side effects.
Add kvm_arch_validate_memory_region() and invoke it before invalidating
the slot, with slots_lock and slots_arch_lock held. The hook performs
validation only, allowing architectures to reject a request without
changing the memslot or its mappings. Provide a weak no-op implementation
for architectures that do not need early validation.
Move arm64's protected VM checks into the new hook so that unsupported
deletions and moves are rejected before stage-2 unmapping. Keep resource
preparation after invalidation, since invalidation drops slots_arch_lock
and allows concurrent updates to architecture-specific memslot metadata.
Assisted-by: LLM
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Steven Price <steven.price@arm.com>
---
arch/arm64/kvm/mmu.c | 22 ++++++++++++++++------
arch/arm64/kvm/pkvm.c | 2 +-
include/linux/kvm_host.h | 4 ++++
virt/kvm/kvm_main.c | 33 ++++++++++++++++++++++++++-------
4 files changed, 47 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index be014f6c00e7..8c29ed204d61 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -2851,13 +2851,12 @@ void kvm_arch_commit_memory_region(struct kvm *kvm,
}
}
-int kvm_arch_prepare_memory_region(struct kvm *kvm,
- const struct kvm_memory_slot *old,
- struct kvm_memory_slot *new,
- enum kvm_mr_change change)
+int kvm_arch_validate_memory_region(struct kvm *kvm,
+ const struct kvm_memory_slot *old,
+ const struct kvm_memory_slot *new,
+ enum kvm_mr_change change)
{
- hva_t hva, reg_end;
- int ret = 0;
+ lockdep_assert_held(&kvm->slots_lock);
if (kvm_vm_is_protected(kvm)) {
/* Cannot modify memslots once a pVM has run. */
@@ -2872,6 +2871,17 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
}
}
+ return 0;
+}
+
+int kvm_arch_prepare_memory_region(struct kvm *kvm,
+ const struct kvm_memory_slot *old,
+ struct kvm_memory_slot *new,
+ enum kvm_mr_change change)
+{
+ hva_t hva, reg_end;
+ int ret = 0;
+
if (change != KVM_MR_CREATE && change != KVM_MR_MOVE &&
change != KVM_MR_FLAGS_ONLY)
return 0;
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index a5145bd75d0c..f4db0e051d6a 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -262,7 +262,7 @@ int pkvm_create_hyp_vm(struct kvm *kvm)
int ret = 0;
/*
- * Synchronise with kvm_arch_prepare_memory_region(), as we
+ * Synchronise with kvm_arch_validate_memory_region(), as we
* prevent memslot modifications on a pVM that has been run.
*/
mutex_lock(&kvm->slots_lock);
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index f65728a713ec..15848221860f 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1255,6 +1255,10 @@ int kvm_set_internal_memslot(struct kvm *kvm,
const struct kvm_userspace_memory_region2 *mem);
void kvm_arch_free_memslot(struct kvm *kvm, struct kvm_memory_slot *slot);
void kvm_arch_memslots_updated(struct kvm *kvm, u64 gen);
+int kvm_arch_validate_memory_region(struct kvm *kvm,
+ const struct kvm_memory_slot *old,
+ const struct kvm_memory_slot *new,
+ enum kvm_mr_change change);
int kvm_arch_prepare_memory_region(struct kvm *kvm,
const struct kvm_memory_slot *old,
struct kvm_memory_slot *new,
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index cf4cb586f140..3ef370d71818 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -1682,6 +1682,20 @@ static void kvm_swap_active_memslots(struct kvm *kvm, int as_id)
slots->generation = gen;
}
+/*
+ * Validate a memslot change before invalidation, which may irreversibly reclaim
+ * private guest memory. Called with slots_lock and slots_arch_lock held.
+ * Implementations must not modify memslot state or allocate resources; leave
+ * those operations to kvm_arch_prepare_memory_region().
+ */
+__weak int kvm_arch_validate_memory_region(struct kvm *kvm,
+ const struct kvm_memory_slot *old,
+ const struct kvm_memory_slot *new,
+ enum kvm_mr_change change)
+{
+ return 0;
+}
+
static int kvm_prepare_memory_region(struct kvm *kvm,
const struct kvm_memory_slot *old,
struct kvm_memory_slot *new,
@@ -1934,6 +1948,10 @@ static int kvm_set_memslot(struct kvm *kvm,
*/
mutex_lock(&kvm->slots_arch_lock);
+ r = kvm_arch_validate_memory_region(kvm, old, new, change);
+ if (r)
+ goto unlock_err;
+
/*
* Invalidate the old slot if it's being deleted or moved. This is
* done prior to actually deleting/moving the memslot to allow vCPUs to
@@ -1950,22 +1968,22 @@ static int kvm_set_memslot(struct kvm *kvm,
if (change == KVM_MR_DELETE || change == KVM_MR_MOVE) {
invalid_slot = kzalloc_obj(*invalid_slot, GFP_KERNEL_ACCOUNT);
if (!invalid_slot) {
- mutex_unlock(&kvm->slots_arch_lock);
- return -ENOMEM;
+ r = -ENOMEM;
+ goto unlock_err;
}
kvm_invalidate_memslot(kvm, old, invalid_slot);
}
r = kvm_prepare_memory_region(kvm, old, new, change);
if (r)
- goto err;
+ goto revert_slot;
if (change == KVM_MR_CREATE && (new->flags & KVM_MEM_GUEST_MEMFD)) {
r = kvm_gmem_commit_memory_region(kvm, new);
if (r) {
kvm_arch_free_memslot(kvm, new);
kvm_destroy_dirty_bitmap(new);
- goto err;
+ goto unlock_err;
}
}
@@ -2000,7 +2018,7 @@ static int kvm_set_memslot(struct kvm *kvm,
return 0;
-err:
+revert_slot:
/*
* For DELETE/MOVE, revert the above INVALID change. No modifications
* required since the original slot was preserved in the inactive slots.
@@ -2009,9 +2027,10 @@ static int kvm_set_memslot(struct kvm *kvm,
if (change == KVM_MR_DELETE || change == KVM_MR_MOVE) {
kvm_activate_memslot(kvm, invalid_slot, old);
kfree(invalid_slot);
- } else {
- mutex_unlock(&kvm->slots_arch_lock);
+ return r;
}
+unlock_err:
+ mutex_unlock(&kvm->slots_arch_lock);
return r;
}
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v2 10/12] KVM: arm64: CCA: Prevent memslot modifications after Realm creation
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
` (8 preceding siblings ...)
2026-11-11 14:57 ` [PATCH v2 09/12] KVM: Allow architectures to reject memslot changes before invalidation Steven Price
@ 2026-11-11 14:57 ` Steven Price
2026-11-11 14:57 ` [PATCH v2 11/12] KVM: arm64: CCA: Support runtime faulting of memory Steven Price
2026-11-11 14:57 ` [PATCH v2 12/12] KVM: arm64: CCA: Add Realm stage-2 MMU operations Steven Price
11 siblings, 0 replies; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Suzuki K Poulose, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Zenghui Yu, linux-arm-kernel,
linux-kernel, Joey Gouly, Alexandru Elisei, Christoffer Dall,
Fuad Tabba, linux-coco, Ganapatrao Kulkarni, Gavin Shan,
Shanker Donthineni, Alper Gun, Aneesh Kumar K . V, Emi Kisanuki,
Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi, Kohei Enju,
Steven Price
From: Suzuki K Poulose <suzuki.poulose@arm.com>
Once the Realm is created, prevent any changes to guest_memfd backed
memslots, as the private memory is provided by slots backed by gmem.
This will also be validated when we handle a fault for Realm Stage2 (in
a later commit).
Require Realm memslots to lie in the lower half of the IPA space, as the
upper, shared half aliases the lower, protected half.
A rejected deletion can already have invalidated the memslot before
the prepare hook rejects it. Keep private mappings intact in this
case.
Additionally, similar to pKVM, we do not support dirty logging or
read-only memslots.
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v1:
- Preserve private mappings if KVM invalidates a Realm guest_memfd
slot before the prepare hook rejects its deletion.
---
arch/arm64/include/asm/kvm_host.h | 2 ++
arch/arm64/kvm/mmu.c | 37 +++++++++++++++++++++++--------
2 files changed, 30 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index d672d87ffe62..a82b18a3e4e5 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -1612,6 +1612,8 @@ struct kvm *kvm_arch_alloc_vm(void);
#define kvm_vm_hyp_is_distrusting(kvm) ((kvm)->arch.vm_flavor >= __VM_DISTRUSTING_HYP)
+#define kvm_arch_has_private_mem(kvm) kvm_vm_is_realm(kvm)
+
#define kvm_has_mte(kvm) \
(system_supports_mte() && \
test_bit(KVM_ARCH_FLAG_MTE_ENABLED, &(kvm)->arch.flags))
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 8c29ed204d61..e550360fc94d 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -2858,17 +2858,32 @@ int kvm_arch_validate_memory_region(struct kvm *kvm,
{
lockdep_assert_held(&kvm->slots_lock);
- if (kvm_vm_is_protected(kvm)) {
+ if (kvm_vm_is_protected(kvm) && new &&
+ (new->flags & (KVM_MEM_LOG_DIRTY_PAGES | KVM_MEM_READONLY)))
+ return -EPERM;
+
+ switch (kvm->arch.vm_flavor) {
+ case VM_PROTECTED_PKVM:
/* Cannot modify memslots once a pVM has run. */
if (pkvm_hyp_vm_is_created(kvm) &&
- (change == KVM_MR_DELETE || change == KVM_MR_MOVE)) {
+ (change == KVM_MR_DELETE || change == KVM_MR_MOVE))
return -EPERM;
- }
+ break;
+ case VM_REALM:
+ /*
+ * Once the Realm is created, we cannot modify any slots that
+ * could be providing private memory. i.e., guest_memfd backed
+ * slots.
+ * TODO: Handle trusted device private memory slots
+ */
+ if (!kvm_realm_is_created(kvm))
+ break;
- if (new &&
- new->flags & (KVM_MEM_LOG_DIRTY_PAGES | KVM_MEM_READONLY)) {
+ if (kvm_slot_has_gmem(old) || kvm_slot_has_gmem(new))
return -EPERM;
- }
+ break;
+ default:
+ break;
}
return 0;
@@ -2880,6 +2895,7 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
enum kvm_mr_change change)
{
hva_t hva, reg_end;
+ gfn_t ipa_limit;
int ret = 0;
if (change != KVM_MR_CREATE && change != KVM_MR_MOVE &&
@@ -2887,10 +2903,13 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
return 0;
/*
- * Prevent userspace from creating a memory region outside of the IPA
- * space addressable by the KVM guest IPA space.
+ * Realm shared IPAs alias the corresponding private IPAs, so memslots
+ * must reside entirely in the canonical lower half of the IPA space.
*/
- if ((new->base_gfn + new->npages) > (kvm_phys_size(&kvm->arch.mmu) >> PAGE_SHIFT))
+ ipa_limit = kvm_phys_size(&kvm->arch.mmu) >> PAGE_SHIFT;
+ if (kvm_vm_is_realm(kvm))
+ ipa_limit >>= 1;
+ if (new->base_gfn + new->npages > ipa_limit)
return -EFAULT;
/*
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v2 11/12] KVM: arm64: CCA: Support runtime faulting of memory
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
` (9 preceding siblings ...)
2026-11-11 14:57 ` [PATCH v2 10/12] KVM: arm64: CCA: Prevent memslot modifications after Realm creation Steven Price
@ 2026-11-11 14:57 ` Steven Price
2026-11-11 14:57 ` [PATCH v2 12/12] KVM: arm64: CCA: Add Realm stage-2 MMU operations Steven Price
11 siblings, 0 replies; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi,
Kohei Enju
At runtime, if the realm guest accesses an IPA without a stage-2
mapping, then KVM fixes up the stage-2 mapping for the backing memory.
For memory in the lower (protected) region of IPA, verify that the
corresponding guest_memfd page is private before delegating to the RMM.
The RMM then maps the page into the realm and initializes its contents
to zero.
For memory in the upper (shared) IPA region, map the backing page as
non-secure memory. Check that a guest_memfd page is shared when the
fault uses guest_memfd; ordinary userspace-backed memslots can only be
mapped at unprotected IPAs and follow the normal user memory fault path.
If the fault IPA and guest_memfd attributes disagree then exit to
userspace with KVM_EXIT_MEMORY_FAULT so the VMM can perform the required
conversion. If a shared IPA has no memslot, KVM follows the existing
MMIO or abort-injection path. The latter can result in injecting a SEA
to the guest.
Realm faults may need to create RTTs, including when updating a
mapping after a permission fault. Take the write side of mmu_lock
and top up the MMU cache for these faults. Do not pre-fault Realm
memory.
Co-developed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v1:
- Use the write side of mmu_lock and top up the MMU cache for Realm
faults that may create RTTs. Skip unnecessary cache flushing,
disable Realm pre-faulting, and tighten memory-fault and mapping
retry handling.
---
arch/arm64/include/asm/kvm_emulate.h | 13 ++
arch/arm64/include/asm/kvm_mmu.h | 4 +-
arch/arm64/include/asm/kvm_rmi.h | 6 +
arch/arm64/kvm/mmu.c | 115 +++++++++++--
arch/arm64/kvm/rmi.c | 235 +++++++++++++++++++++++++++
5 files changed, 353 insertions(+), 20 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index 32021bdf2610..8a71fddfdb85 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -847,4 +847,17 @@ static inline bool kvm_realm_is_created(struct kvm *kvm)
return kvm_vm_is_realm(kvm) && kvm_realm_state(kvm) != REALM_STATE_NONE;
}
+static inline gpa_t kvm_gpa_from_fault(struct kvm *kvm, phys_addr_t ipa)
+{
+ if (!kvm_vm_is_realm(kvm))
+ return ipa;
+
+ return ipa & ~BIT(kvm->arch.realm.ia_bits - 1);
+}
+
+static inline gfn_t kvm_gfn_from_fault(struct kvm *kvm, phys_addr_t ipa)
+{
+ return gpa_to_gfn(kvm_gpa_from_fault(kvm, ipa));
+}
+
#endif /* __ARM64_KVM_EMULATE_H__ */
diff --git a/arch/arm64/include/asm/kvm_mmu.h b/arch/arm64/include/asm/kvm_mmu.h
index 32392abf2e21..a70f8bd4b2b9 100644
--- a/arch/arm64/include/asm/kvm_mmu.h
+++ b/arch/arm64/include/asm/kvm_mmu.h
@@ -371,7 +371,7 @@ static inline bool kvm_is_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu)
static inline void kvm_fault_lock(struct kvm *kvm)
{
- if (is_protected_kvm_enabled())
+ if (kvm_vm_hyp_is_distrusting(kvm))
write_lock(&kvm->mmu_lock);
else
read_lock(&kvm->mmu_lock);
@@ -379,7 +379,7 @@ static inline void kvm_fault_lock(struct kvm *kvm)
static inline void kvm_fault_unlock(struct kvm *kvm)
{
- if (is_protected_kvm_enabled())
+ if (kvm_vm_hyp_is_distrusting(kvm))
write_unlock(&kvm->mmu_lock);
else
read_unlock(&kvm->mmu_lock);
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index e64c3bcce160..d1ba0c325bb0 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -6,6 +6,7 @@
#ifndef __ASM_KVM_RMI_H
#define __ASM_KVM_RMI_H
+#include <asm/kvm_pgtable.h>
#include <linux/arm-smccc-rmi.h>
#include <linux/kvm.h>
@@ -89,6 +90,11 @@ void kvm_realm_unmap_range_filter(struct kvm *kvm,
bool may_block,
enum kvm_gfn_range_filter attr_filter);
+int realm_map_ipa(struct kvm *kvm, phys_addr_t ipa,
+ kvm_pfn_t pfn, unsigned long map_size,
+ enum kvm_pgtable_prot prot,
+ struct kvm_mmu_memory_cache *memcache);
+
static inline bool kvm_realm_is_private_address(struct realm *realm,
unsigned long addr)
{
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index e550360fc94d..b6c61d7890c8 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -450,6 +450,10 @@ static void stage2_flush_vm(struct kvm *kvm)
struct kvm_memory_slot *memslot;
int idx, bkt;
+ /* The RMM requires FWB, so Realm stage-2 mappings need no cache flush. */
+ if (kvm_vm_is_realm(kvm))
+ return;
+
idx = srcu_read_lock(&kvm->srcu);
write_lock(&kvm->mmu_lock);
@@ -1222,6 +1226,10 @@ void stage2_unmap_vm(struct kvm *kvm)
struct kvm_memory_slot *memslot;
int idx, bkt;
+ /* For realms this is handled by the RMM so nothing to do here */
+ if (kvm_vm_is_realm(kvm))
+ return;
+
idx = srcu_read_lock(&kvm->srcu);
mmap_read_lock(current->mm);
write_lock(&kvm->mmu_lock);
@@ -1791,12 +1799,18 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd,
void *memcache = NULL;
gfn_t canonical_gfn;
int ret;
+ bool is_private = false;
- if (!perm_fault) {
+ if (KVM_BUG_ON(!kvm_slot_has_gmem(s2fd->memslot), kvm))
+ return -EINVAL;
+
+ if (!perm_fault || kvm_arch_has_private_mem(kvm)) {
memcache = get_mmu_memcache(s2fd->vcpu);
ret = topup_mmu_memcache(s2fd->mmu, memcache);
if (ret)
return ret;
+ }
+ if (!perm_fault) {
if (kvm_is_nested_s2_mmu(kvm, pgt->mmu)) {
mapping = kmalloc_obj(struct kvm_guest_s2_mapping, GFP_KERNEL_ACCOUNT);
if (!mapping)
@@ -1805,7 +1819,7 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd,
}
s2vi.vma_pagesize = PAGE_SIZE;
- s2vi.gfn = ALIGN_DOWN(s2fd->fault_ipa, s2vi.vma_pagesize) >> PAGE_SHIFT;
+ s2vi.gfn = gpa_to_gfn(kvm_gpa_from_fault(kvm, s2fd->fault_ipa));
canonical_gfn = get_canonical_gfn(s2fd, &s2vi);
write_fault = kvm_s2_fault_is_write(s2fd);
@@ -1817,13 +1831,35 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd,
/* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
smp_rmb();
+ if (kvm_arch_has_private_mem(kvm)) {
+ switch (kvm->arch.vm_flavor) {
+ case VM_REALM:
+ is_private = kvm_realm_is_private_address(&kvm->arch.realm,
+ s2fd->fault_ipa);
+ break;
+ default:
+ break;
+ }
+
+ if (is_private != kvm_gmem_is_private_gfn(kvm, canonical_gfn)) {
+ if (!result)
+ kvm_prepare_memory_fault_exit(s2fd->vcpu,
+ gfn_to_gpa(canonical_gfn),
+ PAGE_SIZE,
+ write_fault, exec_fault,
+ is_private);
+ kfree(mapping);
+ return -EFAULT;
+ }
+ }
+
ret = kvm_gmem_get_pfn(kvm, s2fd->memslot, canonical_gfn, &s2vi.pfn, NULL);
if (ret) {
/* If result is non-NULL this is a synthetic fault. */
if (!result)
kvm_prepare_memory_fault_exit(s2fd->vcpu, gfn_to_gpa(canonical_gfn),
s2vi.vma_pagesize, write_fault, exec_fault,
- false);
+ is_private);
kfree(mapping);
return ret;
}
@@ -1846,6 +1882,16 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd,
goto out_unlock;
}
+ switch (kvm->arch.vm_flavor) {
+ case VM_REALM:
+ prot &= ~KVM_PGTABLE_PROT_X;
+ ret = realm_map_ipa(kvm, s2fd->fault_ipa, s2vi.pfn,
+ PAGE_SIZE, prot, memcache);
+ goto out_unlock;
+ default:
+ break;
+ }
+
if (perm_fault) {
/*
* Drop the SW bits in favour of those stored in the
@@ -2047,7 +2093,8 @@ static int kvm_s2_fault_get_vma_info(const struct kvm_s2_fault_desc *s2fd,
* mapping size to ensure we find the right PFN and lay down the
* mapping in the right place.
*/
- s2vi->gfn = ALIGN_DOWN(s2fd->fault_ipa, s2vi->vma_pagesize) >> PAGE_SHIFT;
+ s2vi->gfn = kvm_gfn_from_fault(kvm, ALIGN_DOWN(s2fd->fault_ipa,
+ s2vi->vma_pagesize));
s2vi->mte_allowed = kvm_vma_mte_allowed(vma);
@@ -2227,7 +2274,7 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
* possible.
*/
if (mapping_size == PAGE_SIZE && s2vi->max_map_size != PAGE_SIZE) {
- if (perm_fault_granule > PAGE_SIZE) {
+ if (perm_fault_granule > PAGE_SIZE && !kvm_vm_is_realm(kvm)) {
mapping_size = perm_fault_granule;
} else {
mapping_size = transparent_hugepage_adjust(s2fd, &pfn, &gfn);
@@ -2242,12 +2289,15 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
if (!perm_fault_granule && !s2vi->map_non_cacheable && kvm_has_mte(kvm))
sanitise_mte_tags(kvm, pfn, mapping_size);
- /*
- * Under the premise of getting a FSC_PERM fault, we just need to relax
- * permissions only if mapping_size equals perm_fault_granule. Otherwise,
- * kvm_pgtable_stage2_map() should be called to change block size.
- */
- if (mapping_size == perm_fault_granule) {
+ if (kvm_vm_is_realm(kvm)) {
+ ret = realm_map_ipa(kvm, s2fd->fault_ipa, pfn, mapping_size,
+ prot, memcache);
+ } else if (mapping_size == perm_fault_granule) {
+ /*
+ * Under the premise of getting a FSC_PERM fault, we just need to relax
+ * permissions only if mapping_size equals perm_fault_granule. Otherwise,
+ * kvm_pgtable_stage2_map() should be called to change block size.
+ */
/*
* Drop the SW bits in favour of those stored in the
* PTE, which will be preserved.
@@ -2306,15 +2356,16 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd,
/*
* Permission faults just need to update the existing leaf entry,
* and so normally don't require allocations from the memcache. The
- * only exception to this is when dirty logging is enabled at runtime
+ * exceptions are when dirty logging is enabled at runtime
* and a fault needs to collapse a block entry into a table.
+ * Realms can also require new RTT levels when replacing a mapping.
* Under pKVM a permission fault can also collapse pages into a block,
* which needs a fresh mapping object, and the hypervisor requires the
* min-pages memcache even when the install allocates nothing.
*/
memcache = get_mmu_memcache(s2fd->vcpu);
if (!perm_fault || memslot_is_logging(s2fd->memslot) ||
- is_protected_kvm_enabled()) {
+ is_protected_kvm_enabled() || kvm_vm_is_realm(s2fd->vcpu->kvm)) {
ret = topup_mmu_memcache(s2fd->mmu, memcache);
if (ret)
return ret;
@@ -2340,13 +2391,40 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd,
return kvm_s2_fault_map(s2fd, &s2vi, prot, memcache, result);
}
+static bool private_ipa_fault(struct kvm *kvm, phys_addr_t fault_ipa)
+{
+ return kvm_vm_is_realm(kvm) &&
+ kvm_realm_is_private_address(&kvm->arch.realm, fault_ipa);
+}
+
+/* Decide if an abort should be handled by the gmem */
+static bool kvm_fault_is_gmem_abort(struct kvm *kvm,
+ const struct kvm_s2_fault_desc *s2fd)
+{
+ if (!kvm_slot_has_gmem(s2fd->memslot))
+ return false;
+ if (kvm_memslot_is_gmem_only(s2fd->memslot))
+ return true;
+ /*
+ * For Realms, all private faults must be backed by GMEM.
+ * TODO: Handle Trusted device private memory mappings.
+ */
+ if (private_ipa_fault(kvm, s2fd->fault_ipa))
+ return true;
+ return false;
+}
+
static int kvm_vm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
{
VM_WARN_ON_ONCE(kvm_s2_fault_is_perm(s2fd) &&
!esr_abt_is_write_fault(s2fd->esr) &&
!kvm_s2_fault_is_exec(s2fd));
- if (kvm_slot_has_gmem(s2fd->memslot))
+ if (private_ipa_fault(s2fd->vcpu->kvm, s2fd->fault_ipa) &&
+ !kvm_slot_has_gmem(s2fd->memslot))
+ return -EINVAL;
+
+ if (kvm_fault_is_gmem_abort(s2fd->vcpu->kvm, s2fd))
return gmem_abort(s2fd, NULL);
else
return user_mem_abort(s2fd, NULL);
@@ -2458,10 +2536,10 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)
*/
int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
{
+ struct kvm *kvm = vcpu->kvm;
struct kvm_s2_trans nested_trans, *nested = NULL;
unsigned long esr = kvm_vcpu_get_esr(vcpu);
struct kvm_s2_mmu *mmu = vcpu->arch.hw_mmu;
- struct kvm *kvm = vcpu->kvm;
phys_addr_t fault_ipa; /* The address we faulted on */
phys_addr_t ipa; /* Always the IPA in the L1 guest phys space */
struct kvm_memory_slot *memslot;
@@ -2554,8 +2632,9 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
nested = &nested_trans;
}
- gfn = ipa >> PAGE_SHIFT;
+ gfn = kvm_gfn_from_fault(kvm, ipa);
memslot = gfn_to_memslot(kvm, gfn);
+
hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable);
write_fault = esr_abt_is_write_fault(esr);
if (kvm_is_error_hva(hva) || (write_fault && !writable)) {
@@ -2598,7 +2677,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
* of the page size.
*/
ipa |= FAR_TO_FIPA_OFFSET(kvm_vcpu_get_hfar(vcpu));
- ret = io_mem_abort(vcpu, ipa);
+ ret = io_mem_abort(vcpu, kvm_gpa_from_fault(kvm, ipa));
goto out_unlock;
}
@@ -3156,7 +3235,7 @@ pre_fault_bytes_consumed(gpa_t gpa, unsigned long granule_size,
int kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
{
- if (is_protected_kvm_enabled())
+ if (is_protected_kvm_enabled() || kvm_vm_is_realm(vcpu->kvm))
return -EOPNOTSUPP;
if (!kvm_vcpu_initialized(vcpu))
return -ENOEXEC;
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 5aa34d68de59..682ed7005d57 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -551,6 +551,241 @@ void kvm_realm_unmap_range_filter(struct kvm *kvm, unsigned long start,
realm_unmap_private_range(kvm, start, end, may_block);
}
+static unsigned long addr_range_desc(unsigned long ipa, unsigned long phys,
+ unsigned long size, unsigned long state)
+{
+ unsigned long count = 1;
+ unsigned long out;
+ int rmi_size;
+
+ for (rmi_size = KVM_PGTABLE_LAST_LEVEL; rmi_size >= 0; rmi_size--) {
+ unsigned long entry_size = rmi_addr_block_size_to_bytes(rmi_size);
+
+ if (size >= entry_size && IS_ALIGNED(ipa, entry_size) &&
+ IS_ALIGNED(phys, entry_size))
+ break;
+ }
+
+ if (rmi_size == 0) {
+ /*
+ * Stop at the next block boundary so that a later iteration can
+ * use a larger mapping when both addresses are aligned.
+ */
+ count = MIN(size >> PAGE_SHIFT, PTRS_PER_PTE - 1);
+ count = min(count, (PMD_SIZE - (ipa & (PMD_SIZE - 1))) >> PAGE_SHIFT);
+ count = min(count, (PMD_SIZE - (phys & (PMD_SIZE - 1))) >> PAGE_SHIFT);
+ }
+
+ WARN_ON(phys & ~PAGE_MASK);
+
+ out = FIELD_PREP(RMI_ADDR_RANGE_BLOCK_SIZE_MASK, rmi_size) |
+ FIELD_PREP(RMI_ADDR_RANGE_COUNT_MASK, count) |
+ FIELD_PREP(RMI_ADDR_RANGE_STATE_MASK, state);
+ out |= phys & PAGE_MASK;
+
+ return out;
+}
+
+static int realm_map_protected(struct kvm *kvm,
+ unsigned long ipa,
+ kvm_pfn_t pfn,
+ unsigned long map_size,
+ struct kvm_mmu_memory_cache *memcache)
+{
+ struct realm *realm = &kvm->arch.realm;
+ phys_addr_t phys = __pfn_to_phys(pfn);
+ phys_addr_t base_phys = phys;
+ phys_addr_t rd = virt_to_phys(realm->rd);
+ phys_addr_t delegated_phys;
+ unsigned long base_ipa = ipa;
+ unsigned long ipa_top;
+ long ret = 0;
+
+ if (WARN_ON(!IS_ALIGNED(map_size, PAGE_SIZE) ||
+ !IS_ALIGNED(ipa, map_size)))
+ return -EINVAL;
+
+ if (rmi_delegate_range(phys, map_size, &delegated_phys)) {
+ if (delegated_phys == phys) {
+ /*
+ * It's likely we raced with another VCPU on the same
+ * fault. Assume the other VCPU has handled the fault
+ * and return to the guest.
+ */
+ return 0;
+ }
+ /* Partial delegation - map as much as we can */
+ map_size = delegated_phys - phys;
+ }
+
+ ipa_top = ipa + map_size;
+
+ while (ipa < ipa_top) {
+ unsigned long flags = RMI_ADDR_TYPE_SINGLE;
+ unsigned long range_desc = addr_range_desc(ipa, phys, ipa_top - ipa,
+ RMI_OP_MEM_DELEGATED);
+ unsigned long out_top;
+
+ ret = rmi_rtt_data_map(rd, ipa, ipa_top, flags, range_desc,
+ &out_top);
+ if (ret < 0)
+ goto err_undelegate;
+
+ if (RMI_RESULT_STATUS(ret) == RMI_ERROR_RTT) {
+ /* Create missing RTTs and retry */
+ int level = RMI_RESULT_DATA_LEVEL(ret);
+
+ if (WARN_ON(level >= KVM_PGTABLE_LAST_LEVEL))
+ goto err_undelegate;
+ ret = realm_create_rtt_levels(realm, ipa, level,
+ level + 1,
+ memcache);
+ if (ret)
+ goto err_undelegate;
+
+ continue;
+ }
+
+ if (WARN_ON(ret))
+ goto err_undelegate;
+
+ /* An IRQ can stop the RMM before it maps the first entry. */
+ if (out_top == ipa) {
+ ret = -EAGAIN;
+ goto err_undelegate;
+ }
+ if (WARN_ON_ONCE(out_top < ipa || out_top > ipa_top)) {
+ ret = -ENXIO;
+ goto err_undelegate;
+ }
+
+ phys += out_top - ipa;
+ ipa = out_top;
+ }
+
+ return 0;
+
+err_undelegate:
+ realm_unmap_private_range(kvm, base_ipa, ipa, false);
+ /* A range which cannot be undelegated remains owned by the RMM. */
+ WARN_ON(rmi_undelegate_range(base_phys, map_size));
+ return ret < 0 ? ret : -ENXIO;
+}
+
+static int realm_map_unprotected(struct kvm *kvm,
+ unsigned long ipa,
+ kvm_pfn_t pfn,
+ unsigned long size,
+ enum kvm_pgtable_prot prot,
+ struct kvm_mmu_memory_cache *memcache)
+{
+ struct realm *realm = &kvm->arch.realm;
+ unsigned long attr, flags = 0;
+ phys_addr_t rd = virt_to_phys(realm->rd);
+ phys_addr_t phys = __pfn_to_phys(pfn);
+ unsigned long ipa_top = ipa + size;
+ unsigned int retries = 0;
+ long ret;
+
+ if (WARN_ON(!IS_ALIGNED(size, PAGE_SIZE)))
+ return -EINVAL;
+
+ switch (prot & (KVM_PGTABLE_PROT_DEVICE | KVM_PGTABLE_PROT_NORMAL_NC)) {
+ case KVM_PGTABLE_PROT_DEVICE | KVM_PGTABLE_PROT_NORMAL_NC:
+ return -EINVAL;
+ case KVM_PGTABLE_PROT_DEVICE:
+ attr = MT_S2_FWB_DEVICE_nGnRE;
+ break;
+ case KVM_PGTABLE_PROT_NORMAL_NC:
+ attr = MT_S2_FWB_NORMAL_NC;
+ break;
+ default:
+ attr = MT_S2_FWB_NORMAL;
+ }
+
+ flags |= FIELD_PREP(RMI_RTT_UNPROT_MAP_FLAGS_MEMATTR_MASK, attr);
+
+ if (prot & KVM_PGTABLE_PROT_R)
+ flags |= FIELD_PREP(RMI_RTT_UNPROT_MAP_FLAGS_S2AP_MASK, RMI_S2AP_DIRECT_READ);
+ if (prot & KVM_PGTABLE_PROT_W)
+ flags |= FIELD_PREP(RMI_RTT_UNPROT_MAP_FLAGS_S2AP_MASK, RMI_S2AP_DIRECT_WRITE);
+
+ /* TODO: Batch mappings with RMI_ADDR_TYPE_LIST. */
+ flags |= RMI_ADDR_TYPE_SINGLE;
+
+ while (ipa < ipa_top) {
+ unsigned long range_desc = addr_range_desc(ipa, phys, ipa_top - ipa,
+ RMI_OP_MEM_UNDELEGATED);
+ unsigned long call_top = ipa + RMI_ADDR_RANGE_COUNT(range_desc) *
+ rmi_addr_block_size_to_bytes(RMI_ADDR_RANGE_BLOCK_SIZE(range_desc));
+ unsigned long out_top;
+
+ ret = rmi_rtt_unprot_map(rd, ipa, call_top, flags, range_desc,
+ &out_top);
+ if (ret < 0)
+ return ret;
+
+ if (ret) {
+ /* Create missing RTTs and retry */
+ int level = RMI_RESULT_DATA_LEVEL(ret);
+ int req_level = KVM_PGTABLE_LAST_LEVEL -
+ RMI_ADDR_RANGE_BLOCK_SIZE(range_desc);
+
+ if (RMI_RESULT_STATUS(ret) != RMI_ERROR_RTT ||
+ ++retries > KVM_PGTABLE_LAST_LEVEL + 2)
+ return -ENXIO;
+
+ /* Replace an existing mapping to update its permissions. */
+ if (level >= req_level) {
+ realm_unmap_shared_range(kvm, ipa, ipa_top, false);
+ continue;
+ }
+
+ ret = realm_create_rtt_levels(realm, ipa, level,
+ req_level,
+ memcache);
+ if (ret)
+ return ret;
+
+ continue;
+ }
+
+ /* Retry the fault after releasing mmu_lock to service the IRQ. */
+ if (out_top == ipa)
+ return -EAGAIN;
+ if (WARN_ON_ONCE(out_top < ipa || out_top > call_top))
+ return -ENXIO;
+
+ phys += out_top - ipa;
+ ipa = out_top;
+ retries = 0;
+ }
+
+ return 0;
+}
+
+int realm_map_ipa(struct kvm *kvm, phys_addr_t ipa,
+ kvm_pfn_t pfn, unsigned long map_size,
+ enum kvm_pgtable_prot prot,
+ struct kvm_mmu_memory_cache *memcache)
+{
+ struct realm *realm = &kvm->arch.realm;
+
+ if (kvm_realm_state(kvm) != REALM_STATE_ACTIVE)
+ return -EBUSY;
+
+ ipa = ALIGN_DOWN(ipa, map_size);
+ if (!kvm_realm_is_private_address(realm, ipa)) {
+ return realm_map_unprotected(kvm, ipa, pfn, map_size, prot,
+ memcache);
+ }
+
+ /* It's impossible to map protected pages read-only. */
+ if (WARN_ON(!(prot & KVM_PGTABLE_PROT_W)))
+ return -EFAULT;
+ return realm_map_protected(kvm, ipa, pfn, map_size, memcache);
+}
+
void kvm_destroy_realm(struct kvm *kvm)
{
struct realm *realm = &kvm->arch.realm;
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v2 12/12] KVM: arm64: CCA: Add Realm stage-2 MMU operations
2026-11-11 14:57 [PATCH v2 00/12] KVM: arm64: CCA: Prepare Realm memory management Steven Price
` (10 preceding siblings ...)
2026-11-11 14:57 ` [PATCH v2 11/12] KVM: arm64: CCA: Support runtime faulting of memory Steven Price
@ 2026-11-11 14:57 ` Steven Price
11 siblings, 0 replies; 14+ messages in thread
From: Steven Price @ 2026-11-11 14:57 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Suzuki K Poulose, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Zenghui Yu, linux-arm-kernel,
linux-kernel, Joey Gouly, Alexandru Elisei, Christoffer Dall,
Fuad Tabba, linux-coco, Ganapatrao Kulkarni, Gavin Shan,
Shanker Donthineni, Alper Gun, Aneesh Kumar K . V, Emi Kisanuki,
Vishal Annapurve, WeiLin.Chang, Lorenzo Pieralisi, Kohei Enju,
Steven Price
From: Suzuki K Poulose <suzuki.poulose@arm.com>
Register the Realm stage-2 MMU operations now that the RMM mapping
and unmapping helpers are available. Route unmapping through the
RMM, and use the existing fault handler for Realm memory faults.
The RMM handles TLB invalidation when stage-2 mappings change, so
the host has no separate TLB flush to perform. Realm stage-2 does
not support access flags, including for shared IPAs.
Dispatch stage-2 destruction according to the VM flavor. Realm
teardown destroys non-root RTTs through RMI, so the host frees
only the root RTT pages.
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v1:
- Dispatch stage-2 destruction by VM flavor. Free only the Realm
root RTT pages after RMI teardown while preserving the native
and pKVM destruction paths.
---
arch/arm64/include/asm/kvm_host.h | 1 +
arch/arm64/kvm/mmu.c | 72 ++++++++++++++++++++++++++-----
2 files changed, 62 insertions(+), 11 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index a82b18a3e4e5..33b1bd2740d3 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -243,6 +243,7 @@ struct kvm_vm_s2_ops {
int (*vm_flush_remote_tlbs)(struct kvm *kvm);
int (*vm_flush_remote_tlbs_range)(struct kvm *kvm, gfn_t gfn,
u64 nr_pages);
+ void (*vm_stage2_destroy)(struct kvm_pgtable *pgt);
void (*vm_stage2_unmap_range)(struct kvm_s2_mmu *mmu,
phys_addr_t start, u64 size,
bool may_block,
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index b6c61d7890c8..b416bc2ff69c 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -191,6 +191,12 @@ static int kvm_vm_flush_remote_tlbs(struct kvm *kvm)
return 0;
}
+static int realm_flush_remote_tlbs(struct kvm *kvm)
+{
+ /* Nothing to do here, RMM does the job */
+ return 0;
+}
+
/**
* kvm_arch_flush_remote_tlbs() - flush all VM TLB entries for v7/8
* @kvm: pointer to kvm structure.
@@ -218,6 +224,13 @@ static int kvm_vm_flush_remote_tlbs_range(struct kvm *kvm,
return 0;
}
+static int realm_flush_remote_tlbs_range(struct kvm *kvm,
+ gfn_t gfn, u64 nr_pages)
+{
+ /* Nothing to do here, RMM does the job */
+ return 0;
+}
+
int kvm_arch_flush_remote_tlbs_range(struct kvm *kvm,
gfn_t gfn, u64 nr_pages)
{
@@ -353,6 +366,15 @@ static void no_stage2_unmap_range(struct kvm_s2_mmu *mmu,
{
}
+static void realm_stage2_unmap_range(struct kvm_s2_mmu *mmu,
+ phys_addr_t start, u64 size, bool may_block,
+ enum kvm_gfn_range_filter filter)
+{
+ struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu);
+
+ kvm_realm_unmap_range_filter(kvm, start, size, may_block, filter);
+}
+
/*
* Unmapping vs dcache management:
*
@@ -1026,35 +1048,50 @@ static int kvm_init_ipa_range(struct kvm_s2_mmu *mmu, unsigned long type)
* cond_resched() periodically to prevent hogging the CPU for a long time
* and schedule something else, if required.
*/
-static void stage2_destroy_range(struct kvm_pgtable *pgt, phys_addr_t addr,
- phys_addr_t end)
+static void stage2_destroy_range(struct kvm_pgtable *pgt,
+ void (*destroy_range)(struct kvm_pgtable *pgt,
+ u64 addr, u64 size))
{
+ u64 addr = 0;
+ u64 end = BIT(VTCR_EL2_IPA(pgt->mmu->vtcr));
u64 next;
do {
next = stage2_range_addr_end(addr, end);
- KVM_PGT_FN(kvm_pgtable_stage2_destroy_range)(pgt, addr,
- next - addr);
+ destroy_range(pgt, addr, next - addr);
if (next != end)
cond_resched();
} while (addr = next, addr != end);
}
-static void kvm_stage2_destroy(struct kvm_pgtable *pgt)
+static void kvm_vm_stage2_destroy(struct kvm_pgtable *pgt)
{
- struct kvm *kvm = kvm_s2_mmu_to_kvm(pgt->mmu);
- unsigned int ia_bits = VTCR_EL2_IPA(pgt->mmu->vtcr);
+ stage2_destroy_range(pgt, kvm_pgtable_stage2_destroy_range);
+ kvm_pgtable_stage2_destroy_pgd(pgt);
+}
+
+static void pkvm_stage2_destroy(struct kvm_pgtable *pgt)
+{
+ stage2_destroy_range(pgt, pkvm_pgtable_stage2_destroy_range);
+ pkvm_pgtable_stage2_destroy_pgd(pgt);
+}
+static void realm_stage2_destroy(struct kvm_pgtable *pgt)
+{
/*
* Realm RTTs are inaccessible to the host and may contain stale data
* after the RMM has released them. The non-root RTTs are explicitly
* destroyed through RMI before the RD is destroyed; only the root PGD
* pages remain to be freed here.
*/
- if (!kvm_vm_is_realm(kvm))
- stage2_destroy_range(pgt, 0, BIT(ia_bits));
+ kvm_pgtable_stage2_destroy_pgd(pgt);
+}
- KVM_PGT_FN(kvm_pgtable_stage2_destroy_pgd)(pgt);
+static void kvm_stage2_destroy(struct kvm_pgtable *pgt)
+{
+ struct kvm *kvm = kvm_s2_mmu_to_kvm(pgt->mmu);
+
+ kvm->arch.vm_s2_ops->vm_stage2_destroy(pgt);
}
/**
@@ -3285,6 +3322,7 @@ long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
}
static const struct kvm_vm_s2_ops protected_pkvm_vm_s2_ops = {
+ .vm_stage2_destroy = pkvm_stage2_destroy,
.vm_flush_remote_tlbs = pkvm_flush_remote_tlbs,
.vm_flush_remote_tlbs_range = pkvm_flush_remote_tlbs_range,
.vm_age_gfn = no_age_gfn,
@@ -3294,6 +3332,7 @@ static const struct kvm_vm_s2_ops protected_pkvm_vm_s2_ops = {
};
static const struct kvm_vm_s2_ops pkvm_vm_s2_ops = {
+ .vm_stage2_destroy = pkvm_stage2_destroy,
.vm_flush_remote_tlbs = pkvm_flush_remote_tlbs,
.vm_flush_remote_tlbs_range = pkvm_flush_remote_tlbs_range,
.vm_age_gfn = pkvm_age_gfn,
@@ -3303,6 +3342,7 @@ static const struct kvm_vm_s2_ops pkvm_vm_s2_ops = {
};
static const struct kvm_vm_s2_ops kvm_default_vm_s2_ops = {
+ .vm_stage2_destroy = kvm_vm_stage2_destroy,
.vm_flush_remote_tlbs = kvm_vm_flush_remote_tlbs,
.vm_flush_remote_tlbs_range = kvm_vm_flush_remote_tlbs_range,
.vm_age_gfn = kvm_vm_age_gfn,
@@ -3311,6 +3351,16 @@ static const struct kvm_vm_s2_ops kvm_default_vm_s2_ops = {
.vm_mem_abort = kvm_vm_mem_abort,
};
+static const struct kvm_vm_s2_ops realm_vm_s2_ops = {
+ .vm_stage2_destroy = realm_stage2_destroy,
+ .vm_flush_remote_tlbs = realm_flush_remote_tlbs,
+ .vm_flush_remote_tlbs_range = realm_flush_remote_tlbs_range,
+ .vm_age_gfn = no_age_gfn,
+ .vm_test_age_gfn = no_age_gfn,
+ .vm_stage2_unmap_range = realm_stage2_unmap_range,
+ .vm_mem_abort = kvm_vm_mem_abort,
+};
+
#define KVM_VM_S2_OPS(flavor, ops) \
[(flavor)] = &(ops)
@@ -3319,7 +3369,7 @@ static const struct kvm_vm_s2_ops *arm64_vm_s2_ops[] = {
KVM_VM_S2_OPS(VM_NVHE, kvm_default_vm_s2_ops),
KVM_VM_S2_OPS(VM_PKVM, pkvm_vm_s2_ops),
KVM_VM_S2_OPS(VM_PROTECTED_PKVM, protected_pkvm_vm_s2_ops),
- [VM_REALM] = NULL,
+ KVM_VM_S2_OPS(VM_REALM, realm_vm_s2_ops),
};
static int kvm_vm_init_vm_s2_ops(struct kvm *kvm)
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread