From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:11:46
This series adds support for running protected VMs using KVM under the
Arm Confidential Compute Architecture (CCA). I've changed the uAPI
following feedback from Marc.
The main change is that rather than providing a multiplex CAP and
expecting the VMM to drive the different stages of realm construction,
there's now just a minimal interface and KVM performs the necessary
operations when needed.
This series is lightly tested and is meant as a demonstration of the new
uAPI. There are a number of (known) rough corners in the implementation
that I haven't dealt with properly.
In particular please note that this series is still targetting RMM v1.0.
There is an alpha quality version of RMM v2.0 available[1]. Feedback was
that there are a number of blockers for merging with RMM v1.0 and so I
expect to rework this series to support RMM v2.0 before it is merged.
That will necessarily involve reworking the implementation.
Specifically I'm expecting improvements in:
* GIC handling - passing state in registers, and allowing the host to
fully emulate the GIC by allowing trap bits to be set.
* PMU handling - again providing flexibility to the host's emulation.
* Page size/granule size mismatch. RMM v1.0 defines the granule as 4k,
RMM v2.0 provide the option for the host to change the granule size.
The intention is that Linux would simply set the granule size equal
to its page size which will significantly simplify the management of
granules.
* Some performance improvement from the use of range-based map/unmap
RMI calls.
This series is based on v6.19-rc1. It is also available as a git
repository:
https://gitlab.arm.com/linux-arm/linux-cca cca-host/v12
Work in progress changes for kvmtool are available from the git
repository below:
https://gitlab.arm.com/linux-arm/kvmtool-cca cca/v10
[1] https://developer.arm.com/documentation/den0137/latest/
Jean-Philippe Brucker (7):
arm64: RMI: Propagate number of breakpoints and watchpoints to
userspace
arm64: RMI: Set breakpoint parameters through SET_ONE_REG
arm64: RMI: Initialize PMCR.N with number counter supported by RMM
arm64: RMI: Propagate max SVE vector length from RMM
arm64: RMI: Configure max SVE vector length for a Realm
arm64: RMI: Provide register list for unfinalized RMI RECs
arm64: RMI: Provide accurate register list
Joey Gouly (2):
arm64: RMI: allow userspace to inject aborts
arm64: RMI: support RSI_HOST_CALL
Steven Price (34):
arm64: RME: Handle Granule Protection Faults (GPFs)
arm64: RMI: Add SMC definitions for calling the RMM
arm64: RMI: Add wrappers for RMI calls
arm64: RMI: Check for RMI support at KVM init
arm64: RMI: Define the user ABI
arm64: RMI: Basic infrastructure for creating a realm.
KVM: arm64: Allow passing machine type in KVM creation
arm64: RMI: RTT tear down
arm64: RMI: Activate realm on first VCPU run
arm64: RMI: Allocate/free RECs to match vCPUs
KVM: arm64: vgic: Provide helper for number of list registers
arm64: RMI: Support for the VGIC in realms
KVM: arm64: Support timers in realm RECs
arm64: RMI: Handle realm enter/exit
arm64: RMI: Handle RMI_EXIT_RIPAS_CHANGE
KVM: arm64: Handle realm MMIO emulation
KVM: arm64: Expose support for private memory
arm64: RMI: Allow populating initial contents
arm64: RMI: Set RIPAS of initial memslots
arm64: RMI: Create the realm descriptor
arm64: RMI: Add a VMID allocator for realms
arm64: RMI: Runtime faulting of memory
KVM: arm64: Handle realm VCPU load
KVM: arm64: Validate register access for a Realm VM
KVM: arm64: Handle Realm PSCI requests
KVM: arm64: WARN on injected undef exceptions
arm64: Don't expose stolen time for realm guests
arm64: RMI: Always use 4k pages for realms
arm64: RMI: Prevent Device mappings for Realms
HACK: Restore per-CPU cpu_armpmu pointer
arm_pmu: Provide a mechanism for disabling the physical IRQ
arm64: RMI: Enable PMU support with a realm guest
KVM: arm64: Expose KVM_ARM_VCPU_REC to user space
arm64: RMI: Enable realms to be created
Suzuki K Poulose (3):
kvm: arm64: Include kvm_emulate.h in kvm/arm_psci.h
kvm: arm64: Don't expose unsupported capabilities for realm guests
arm64: RMI: Allow checking SVE on VM instance
Documentation/virt/kvm/api.rst | 78 +-
arch/arm64/include/asm/kvm_emulate.h | 31 +
arch/arm64/include/asm/kvm_host.h | 13 +-
arch/arm64/include/asm/kvm_rmi.h | 137 +++
arch/arm64/include/asm/rmi_cmds.h | 508 ++++++++
arch/arm64/include/asm/rmi_smc.h | 269 +++++
arch/arm64/include/asm/virt.h | 1 +
arch/arm64/kernel/cpufeature.c | 1 +
arch/arm64/kvm/Kconfig | 2 +
arch/arm64/kvm/Makefile | 2 +-
arch/arm64/kvm/arch_timer.c | 37 +-
arch/arm64/kvm/arm.c | 179 ++-
arch/arm64/kvm/guest.c | 95 +-
arch/arm64/kvm/hypercalls.c | 4 +-
arch/arm64/kvm/inject_fault.c | 5 +-
arch/arm64/kvm/mmio.c | 16 +-
arch/arm64/kvm/mmu.c | 214 +++-
arch/arm64/kvm/pmu-emul.c | 6 +
arch/arm64/kvm/psci.c | 30 +
arch/arm64/kvm/reset.c | 13 +-
arch/arm64/kvm/rmi-exit.c | 207 ++++
arch/arm64/kvm/rmi.c | 1663 ++++++++++++++++++++++++++
arch/arm64/kvm/sys_regs.c | 53 +-
arch/arm64/kvm/vgic/vgic-init.c | 2 +-
arch/arm64/kvm/vgic/vgic-v2.c | 6 +-
arch/arm64/kvm/vgic/vgic-v3.c | 14 +-
arch/arm64/kvm/vgic/vgic.c | 55 +-
arch/arm64/kvm/vgic/vgic.h | 20 +-
arch/arm64/mm/fault.c | 28 +-
drivers/perf/arm_pmu.c | 20 +
include/kvm/arm_arch_timer.h | 2 +
include/kvm/arm_pmu.h | 4 +
include/kvm/arm_psci.h | 2 +
include/linux/perf/arm_pmu.h | 7 +
include/uapi/linux/kvm.h | 42 +-
35 files changed, 3650 insertions(+), 116 deletions(-)
create mode 100644 arch/arm64/include/asm/kvm_rmi.h
create mode 100644 arch/arm64/include/asm/rmi_cmds.h
create mode 100644 arch/arm64/include/asm/rmi_smc.h
create mode 100644 arch/arm64/kvm/rmi-exit.c
create mode 100644 arch/arm64/kvm/rmi.c
--
2.43.0
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:11:51
From: Suzuki K Poulose <suzuki.poulose@arm.com>
Fix a potential build error (like below, when asm/kvm_emulate.h gets
included after the kvm/arm_psci.h) by including the missing header file
in kvm/arm_psci.h:
./include/kvm/arm_psci.h: In function ‘kvm_psci_version’:
./include/kvm/arm_psci.h:29:13: error: implicit declaration of function
‘vcpu_has_feature’; did you mean ‘cpu_have_feature’? [-Werror=implicit-function-declaration]
29 | if (vcpu_has_feature(vcpu, KVM_ARM_VCPU_PSCI_0_2)) {
| ^~~~~~~~~~~~~~~~
| cpu_have_feature
Reviewed-by: Gavin Shan <redacted>
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Steven Price <steven.price@arm.com>
---
include/kvm/arm_psci.h | 2 ++
1 file changed, 2 insertions(+)
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:11:56
If the host attempts to access granules that have been delegated for use
in a realm these accesses will be caught and will trigger a Granule
Protection Fault (GPF).
A fault during a page walk signals a bug in the kernel and is handled by
oopsing the kernel. A non-page walk fault could be caused by user space
having access to a page which has been delegated to the kernel and will
trigger a SIGBUS to allow debugging why user space is trying to access a
delegated page.
Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Reviewed-by: Gavin Shan <redacted>
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v10:
* Don't call arm64_notify_die() in do_gpf() but simply return 1.
Changes since v2:
* Include missing "Granule Protection Fault at level -1"
---
arch/arm64/mm/fault.c | 28 ++++++++++++++++++++++------
1 file changed, 22 insertions(+), 6 deletions(-)
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:02
The RMM (Realm Management Monitor) provides functionality that can be
accessed by SMC calls from the host.
The SMC definitions are based on DEN0137[1] version 1.0-rel0
[1] https://developer.arm.com/documentation/den0137/1-0rel0/
Reviewed-by: Gavin Shan <redacted>
Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v9:
* Corrected size of 'ripas_value' in struct rec_exit. The spec states
this is an 8-bit type with padding afterwards (rather than a u64).
Changes since v8:
* Added RMI_PERMITTED_GICV3_HCR_BITS to define which bits the RMM
permits to be modified.
Changes since v6:
* Renamed REC_ENTER_xxx defines to include 'FLAG' to make it obvious
these are flag values.
Changes since v5:
* Sorted the SMC #defines by value.
* Renamed SMI_RxI_CALL to SMI_RMI_CALL since the macro is only used for
RMI calls.
* Renamed REC_GIC_NUM_LRS to REC_MAX_GIC_NUM_LRS since the actual
number of available list registers could be lower.
* Provided a define for the reserved fields of FeatureRegister0.
* Fix inconsistent names for padding fields.
Changes since v4:
* Update to point to final released RMM spec.
* Minor rearrangements.
Changes since v3:
* Update to match RMM spec v1.0-rel0-rc1.
Changes since v2:
* Fix specification link.
* Rename rec_entry->rec_enter to match spec.
* Fix size of pmu_ovf_status to match spec.
---
arch/arm64/include/asm/rmi_smc.h | 269 +++++++++++++++++++++++++++++++
1 file changed, 269 insertions(+)
create mode 100644 arch/arm64/include/asm/rmi_smc.h
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:06
The wrappers make the call sites easier to read and deal with the
boiler plate of handling the error codes from the RMM.
Reviewed-by: Gavin Shan <redacted>
Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes from v8:
* Switch from arm_smccc_1_2_smc() to arm_smccc_1_2_invoke() in
rmi_rtt_read_entry() for consistency.
Changes from v7:
* Minor renaming of parameters and updated comments
Changes from v5:
* Further improve comments
Changes from v4:
* Improve comments
Changes from v2:
* Make output arguments optional.
* Mask RIPAS value rmi_rtt_read_entry()
* Drop unused rmi_rtt_get_phys()
---
arch/arm64/include/asm/rmi_cmds.h | 508 ++++++++++++++++++++++++++++++
1 file changed, 508 insertions(+)
create mode 100644 arch/arm64/include/asm/rmi_cmds.h
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:10
Query the RMI version number and check if it is a compatible version. A
static key is also provided to signal that a supported RMM is available.
Functions are provided to query if a VM or VCPU is a realm (or rec)
which currently will always return false.
Later patches make use of struct realm and the states as the ioctls
interfaces are added to support realm and REC creation and destruction.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Reword slightly the comments on the realm states.
Changes since v10:
* kvm_is_realm() no longer has a NULL check.
* Rename from "rme" to "rmi" when referring to the RMM interface.
* Check for RME (hardware) support before probing for RMI support.
Changes since v8:
* No need to guard kvm_init_rme() behind 'in_hyp_mode'.
Changes since v6:
* Improved message for an unsupported RMI ABI version.
Changes since v5:
* Reword "unsupported" message from "host supports" to "we want" to
clarify that 'we' are the 'host'.
Changes since v2:
* Drop return value from kvm_init_rme(), it was always 0.
* Rely on the RMM return value to identify whether the RSI ABI is
compatible.
---
arch/arm64/include/asm/kvm_emulate.h | 18 ++++++++
arch/arm64/include/asm/kvm_host.h | 4 ++
arch/arm64/include/asm/kvm_rmi.h | 56 +++++++++++++++++++++++++
arch/arm64/include/asm/virt.h | 1 +
arch/arm64/kernel/cpufeature.c | 1 +
arch/arm64/kvm/Makefile | 2 +-
arch/arm64/kvm/arm.c | 5 +++
arch/arm64/kvm/rmi.c | 61 ++++++++++++++++++++++++++++
8 files changed, 147 insertions(+), 1 deletion(-)
create mode 100644 arch/arm64/include/asm/kvm_rmi.h
create mode 100644 arch/arm64/kvm/rmi.c
@@ -2865,6 +2868,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"\
@@ -0,0 +1,61 @@+// SPDX-License-Identifier: GPL-2.0+/*+*Copyright(C)2023-2025ARMLtd.+*/++#include<linux/kvm_host.h>++#include<asm/rmi_cmds.h>+#include<asm/virt.h>++staticintrmi_check_version(void)+{+structarm_smccc_resres;+unsignedshortversion_major,version_minor;+unsignedlonghost_version=RMI_ABI_VERSION(RMI_ABI_MAJOR_VERSION,+RMI_ABI_MINOR_VERSION);+unsignedlongaa64pfr0=read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1);++/* If RME isn't supported, then RMI can't be */+if(cpuid_feature_extract_unsigned_field(aa64pfr0,ID_AA64PFR0_EL1_RME_SHIFT)==0)+return-ENXIO;++arm_smccc_1_1_invoke(SMC_RMI_VERSION,host_version,&res);++if(res.a0==SMCCC_RET_NOT_SUPPORTED)+return-ENXIO;++version_major=RMI_ABI_VERSION_GET_MAJOR(res.a1);+version_minor=RMI_ABI_VERSION_GET_MINOR(res.a1);++if(res.a0!=RMI_SUCCESS){+unsignedshorthigh_version_major,high_version_minor;++high_version_major=RMI_ABI_VERSION_GET_MAJOR(res.a2);+high_version_minor=RMI_ABI_VERSION_GET_MINOR(res.a2);++kvm_err("Unsupported RMI ABI (v%d.%d - v%d.%d) we want v%d.%d\n",+version_major,version_minor,+high_version_major,high_version_minor,+RMI_ABI_MAJOR_VERSION,+RMI_ABI_MINOR_VERSION);+return-ENXIO;+}++kvm_info("RMI ABI version %d.%d\n",version_major,version_minor);++return0;+}++voidkvm_init_rmi(void)+{+/* Only 4k page size on the host is supported */+if(PAGE_SIZE!=SZ_4K)+return;++/* Continue without realm support if we can't agree on a version */+if(rmi_check_version())+return;++/* Future patch will enable static branch kvm_rmi_is_available */+}
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:16
There is one CAP which identified the presence of CCA, and two ioctls.
One ioctl is used to populate memory and the other is used when user
space is providing the PSCI implementation to identify the target of the
operation.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Completely reworked to be more implicit. Rather than having explicit
CAP operations to progress the realm construction these operations
are done when needed (on populating and on first vCPU run).
* Populate and PSCI complete are promoted to proper ioctls.
Changes since v10:
* Rename symbols from RME to RMI.
Changes since v9:
* Improvements to documentation.
* Bump the magic number for KVM_CAP_ARM_RME to avoid conflicts.
Changes since v8:
* Minor improvements to documentation following review.
* Bump the magic numbers to avoid conflicts.
Changes since v7:
* Add documentation of new ioctls
* Bump the magic numbers to avoid conflicts
Changes since v6:
* Rename some of the symbols to make their usage clearer and avoid
repetition.
Changes from v5:
* Actually expose the new VCPU capability (KVM_ARM_VCPU_REC) by bumping
KVM_VCPU_MAX_FEATURES - note this also exposes KVM_ARM_VCPU_HAS_EL2!
---
Documentation/virt/kvm/api.rst | 57 ++++++++++++++++++++++++++++++++++
include/uapi/linux/kvm.h | 23 ++++++++++++++
2 files changed, 80 insertions(+)
@@ -6517,6 +6517,54 @@ the capability to be present.`flags` must currently be zero.+4.144 KVM_ARM_VCPU_RMI_PSCI_COMPLETE+------------------------------------++:Capability: KVM_CAP_ARM_RMI+:Architectures: arm64+:Type: vcpu ioctl+:Parameters: struct kvm_arm_rmi_psci_complete (in)+:Returns: 0 if successful, < 0 on error++::++ struct kvm_arm_rmi_psci_complete {+ __u64 target_mpidr;+ __u32 psci_status;+ __u32 padding[3];+ };++Where PSCI functions are handled by user space, the RMM needs to be informed of+the target of the operation using `target_mpidr`, along with the status+(`psci_status`). The RMM v1.0 specification defines two functions that require+this call: PSCI_CPU_ON and PSCI_AFFINITY_INFO.++If the kernel is handling PSCI then this is done automatically and the VMM+doesn't need to call this ioctl.++4.145 KVM_ARM_RMI_POPULATE+--------------------------++:Capability: KVM_CAP_ARM_RMI+:Architectures: arm64+:Type: vm ioctl+:Parameters: struct kvm_arm_rmi_populate (in)+:Returns: number of bytes populated, < 0 on error++::++ struct kvm_arm_rmi_populate {+ __u64 base;+ __u64 size;+ __u64 source_uaddr;+ __u32 flags;+ __u32 reserved;+ };++Populate a region of protected address space by copying the data from the user+space pointer provided. This is only valid before any VCPUs have been run.+The ioctl might not populate the entire region and user space may have to+repeatedly call it (with updated pointers) to populate the entire region..._kvm_run:
@@ -8765,6 +8813,15 @@ helpful if user space wants to emulate instructions which are not This capability can be enabled dynamically even if VCPUs were already created and are running.+7.47 KVM_CAP_ARM_RMI+--------------------++:Architectures: arm64+:Target: VM+:Parameters: None++This capability indicates that support for CCA realms is available.+8. Other capabilities. ======================
@@ -1628,4 +1629,26 @@ struct kvm_pre_fault_memory {__u64padding[5];};+/* Available with KVM_CAP_ARM_RMI, only for VMs with KVM_VM_TYPE_ARM_REALM */+structkvm_arm_rmi_psci_complete{+__u64target_mpidr;+__u32psci_status;+__u32padding[3];+};++#define KVM_ARM_VCPU_RMI_PSCI_COMPLETE _IOW(KVMIO, 0xd6, struct kvm_arm_rmi_psci_complete)++/* Available with KVM_CAP_ARM_RMI, only for VMs with KVM_VM_TYPE_ARM_REALM */+structkvm_arm_rmi_populate{+__u64base;+__u64size;+__u64source_uaddr;+__u32flags;+__u32reserved;+};++#define KVM_ARM_RMI_POPULATE_FLAGS_MEASURE (1 << 0)++#define KVM_ARM_RMI_POPULATE _IOW(KVMIO, 0xd7, struct kvm_arm_rmi_populate)+#endif /* __LINUX_KVM_H */
From: Suzuki K Poulose <suzuki.poulose@arm.com> Date: 2026-01-23 16:47:17
Hi Steven
On 17/12/2025 10:10, Steven Price wrote:
There is one CAP which identified the presence of CCA, and two ioctls.
One ioctl is used to populate memory and the other is used when user
space is providing the PSCI implementation to identify the target of the
operation.
Could we split these changes to the corresponding implementation patches
? That might give us a full picture of how these UAPIs fit in the bigger
picture.
quoted hunk
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Completely reworked to be more implicit. Rather than having explicit
CAP operations to progress the realm construction these operations
are done when needed (on populating and on first vCPU run).
* Populate and PSCI complete are promoted to proper ioctls.
Changes since v10:
* Rename symbols from RME to RMI.
Changes since v9:
* Improvements to documentation.
* Bump the magic number for KVM_CAP_ARM_RME to avoid conflicts.
Changes since v8:
* Minor improvements to documentation following review.
* Bump the magic numbers to avoid conflicts.
Changes since v7:
* Add documentation of new ioctls
* Bump the magic numbers to avoid conflicts
Changes since v6:
* Rename some of the symbols to make their usage clearer and avoid
repetition.
Changes from v5:
* Actually expose the new VCPU capability (KVM_ARM_VCPU_REC) by bumping
KVM_VCPU_MAX_FEATURES - note this also exposes KVM_ARM_VCPU_HAS_EL2!
---
Documentation/virt/kvm/api.rst | 57 ++++++++++++++++++++++++++++++++++
include/uapi/linux/kvm.h | 23 ++++++++++++++
2 files changed, 80 insertions(+)
@@ -6517,6 +6517,54 @@ the capability to be present.`flags` must currently be zero.+4.144 KVM_ARM_VCPU_RMI_PSCI_COMPLETE+------------------------------------++:Capability: KVM_CAP_ARM_RMI+:Architectures: arm64+:Type: vcpu ioctl+:Parameters: struct kvm_arm_rmi_psci_complete (in)+:Returns: 0 if successful, < 0 on error++::++ struct kvm_arm_rmi_psci_complete {+ __u64 target_mpidr;+ __u32 psci_status;+ __u32 padding[3];+ };++Where PSCI functions are handled by user space, the RMM needs to be informed of+the target of the operation using `target_mpidr`, along with the status+(`psci_status`). The RMM v1.0 specification defines two functions that require+this call: PSCI_CPU_ON and PSCI_AFFINITY_INFO.++If the kernel is handling PSCI then this is done automatically and the VMM+doesn't need to call this ioctl.++4.145 KVM_ARM_RMI_POPULATE+--------------------------++:Capability: KVM_CAP_ARM_RMI+:Architectures: arm64+:Type: vm ioctl+:Parameters: struct kvm_arm_rmi_populate (in)+:Returns: number of bytes populated, < 0 on error++::++ struct kvm_arm_rmi_populate {+ __u64 base;+ __u64 size;+ __u64 source_uaddr;+ __u32 flags;+ __u32 reserved;+ };++Populate a region of protected address space by copying the data from the user+space pointer provided. This is only valid before any VCPUs have been run.+The ioctl might not populate the entire region and user space may have to+repeatedly call it (with updated pointers) to populate the entire region.
It may be a good idea to spill out the restrictions on calling this,
right ? We expect that the source_uaddr is a separate memory area
from the "DRAM" (in shared mode) ?
Can this be called before/after converting the entire memory of the
Guest to Private ? I believe, it must be done after the "initial
change all of DRAM to private" ?
Suzuki
quoted hunk
.. _kvm_run:
@@ -8765,6 +8813,15 @@ helpful if user space wants to emulate instructions which are not This capability can be enabled dynamically even if VCPUs were already created and are running.+7.47 KVM_CAP_ARM_RMI+--------------------++:Architectures: arm64+:Target: VM+:Parameters: None++This capability indicates that support for CCA realms is available.+ 8. Other capabilities. ======================
@@ -1628,4 +1629,26 @@ struct kvm_pre_fault_memory {__u64padding[5];};+/* Available with KVM_CAP_ARM_RMI, only for VMs with KVM_VM_TYPE_ARM_REALM */+structkvm_arm_rmi_psci_complete{+__u64target_mpidr;+__u32psci_status;+__u32padding[3];+};++#define KVM_ARM_VCPU_RMI_PSCI_COMPLETE _IOW(KVMIO, 0xd6, struct kvm_arm_rmi_psci_complete)++/* Available with KVM_CAP_ARM_RMI, only for VMs with KVM_VM_TYPE_ARM_REALM */+structkvm_arm_rmi_populate{+__u64base;+__u64size;+__u64source_uaddr;+__u32flags;+__u32reserved;+};++#define KVM_ARM_RMI_POPULATE_FLAGS_MEASURE (1 << 0)++#define KVM_ARM_RMI_POPULATE _IOW(KVMIO, 0xd7, struct kvm_arm_rmi_populate)+#endif /* __LINUX_KVM_H */
From: Steven Price <steven.price@arm.com> Date: 2026-01-26 09:38:06
On 23/01/2026 16:47, Suzuki K Poulose wrote:
Hi Steven
On 17/12/2025 10:10, Steven Price wrote:
quoted
There is one CAP which identified the presence of CCA, and two ioctls.
One ioctl is used to populate memory and the other is used when user
space is providing the PSCI implementation to identify the target of the
operation.
Could we split these changes to the corresponding implementation patches
? That might give us a full picture of how these UAPIs fit in the bigger
picture.
I could do that, but I was deliberately keeping this as a patch which
contains the uABI interface so that the whole uABI is visible in one
place. I was trying to reduce the amount to review for those who aren't
so interested in the implementation details (especially for this series
where we know it's going to change somewhat with the move to RMM v2.0).
quoted
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Completely reworked to be more implicit. Rather than having explicit
CAP operations to progress the realm construction these operations
are done when needed (on populating and on first vCPU run).
* Populate and PSCI complete are promoted to proper ioctls.
Changes since v10:
* Rename symbols from RME to RMI.
Changes since v9:
* Improvements to documentation.
* Bump the magic number for KVM_CAP_ARM_RME to avoid conflicts.
Changes since v8:
* Minor improvements to documentation following review.
* Bump the magic numbers to avoid conflicts.
Changes since v7:
* Add documentation of new ioctls
* Bump the magic numbers to avoid conflicts
Changes since v6:
* Rename some of the symbols to make their usage clearer and avoid
repetition.
Changes from v5:
* Actually expose the new VCPU capability (KVM_ARM_VCPU_REC) by bumping
KVM_VCPU_MAX_FEATURES - note this also exposes KVM_ARM_VCPU_HAS_EL2!
---
Documentation/virt/kvm/api.rst | 57 ++++++++++++++++++++++++++++++++++
include/uapi/linux/kvm.h | 23 ++++++++++++++
2 files changed, 80 insertions(+)
@@ -6517,6 +6517,54 @@ the capability to be present.
`flags` must currently be zero.
+4.144 KVM_ARM_VCPU_RMI_PSCI_COMPLETE
+------------------------------------
+
+:Capability: KVM_CAP_ARM_RMI
+:Architectures: arm64
+:Type: vcpu ioctl
+:Parameters: struct kvm_arm_rmi_psci_complete (in)
+:Returns: 0 if successful, < 0 on error
+
+::
+
+ struct kvm_arm_rmi_psci_complete {
+ __u64 target_mpidr;
+ __u32 psci_status;
+ __u32 padding[3];
+ };
+
+Where PSCI functions are handled by user space, the RMM needs to be
informed of
+the target of the operation using `target_mpidr`, along with the status
+(`psci_status`). The RMM v1.0 specification defines two functions
that require
+this call: PSCI_CPU_ON and PSCI_AFFINITY_INFO.
+
+If the kernel is handling PSCI then this is done automatically and
the VMM
+doesn't need to call this ioctl.
+
+4.145 KVM_ARM_RMI_POPULATE
+--------------------------
+
+:Capability: KVM_CAP_ARM_RMI
+:Architectures: arm64
+:Type: vm ioctl
+:Parameters: struct kvm_arm_rmi_populate (in)
+:Returns: number of bytes populated, < 0 on error
+
+::
+
+ struct kvm_arm_rmi_populate {
+ __u64 base;
+ __u64 size;
+ __u64 source_uaddr;
+ __u32 flags;
+ __u32 reserved;
+ };
+
+Populate a region of protected address space by copying the data from
the user
+space pointer provided. This is only valid before any VCPUs have been
run.
+The ioctl might not populate the entire region and user space may
have to
+repeatedly call it (with updated pointers) to populate the entire
region.
It may be a good idea to spill out the restrictions on calling this,
right ? We expect that the source_uaddr is a separate memory area
from the "DRAM" (in shared mode) ?
Yes - I'd tried to say that by mentioning it's a "user space pointer",
but I could make that more explicit.
Can this be called before/after converting the entire memory of the
Guest to Private ? I believe, it must be done after the "initial
change all of DRAM to private" ?
It can be called before or after setting RIPAS and it implicitly sets
RIPAS to RAM (I'll add something to make that explicit).
With RMM v1.0 there's an ugly problem that INIT_RIPAS fails on a region
which is already RIPAS RAM. This should be fixed with RMM v2.0. There's
a workaround in patch 17 currently:
/* FIXME: Ugly hack to skip regions which are
* already RIPAS_RAM
*/
But that should go away.
Note that with this proposed uAPI there is no mechanism for user space
to directly set the RIPAS - it's assumed that all guestmem_fd regions
should be set to RIPAS RAM at the time of the first VCPU run - which
means the VMM doesn't need to do anything special.
In the future we could consider a guestmem_fd flag or extra ioctl to
allow the VMM to control RIPAS at a finer granularity. But so far we
don't seem to have any compelling reason to expose that.
Thanks,
Steve
Suzuki
quoted
.. _kvm_run:
@@ -8765,6 +8813,15 @@ helpful if user space wants to emulate
instructions which are not
This capability can be enabled dynamically even if VCPUs were already
created and are running.
+7.47 KVM_CAP_ARM_RMI
+--------------------
+
+:Architectures: arm64
+:Target: VM
+:Parameters: None
+
+This capability indicates that support for CCA realms is available.
+
8. Other capabilities.
======================
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index dddb781b0507..8e66ba9c81db 100644
From: Marc Zyngier <maz@kernel.org> Date: 2026-03-02 14:25:40
On Wed, 17 Dec 2025 10:10:43 +0000,
Steven Price [off-list ref] wrote:
quoted hunk
There is one CAP which identified the presence of CCA, and two ioctls.
One ioctl is used to populate memory and the other is used when user
space is providing the PSCI implementation to identify the target of the
operation.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Completely reworked to be more implicit. Rather than having explicit
CAP operations to progress the realm construction these operations
are done when needed (on populating and on first vCPU run).
* Populate and PSCI complete are promoted to proper ioctls.
Changes since v10:
* Rename symbols from RME to RMI.
Changes since v9:
* Improvements to documentation.
* Bump the magic number for KVM_CAP_ARM_RME to avoid conflicts.
Changes since v8:
* Minor improvements to documentation following review.
* Bump the magic numbers to avoid conflicts.
Changes since v7:
* Add documentation of new ioctls
* Bump the magic numbers to avoid conflicts
Changes since v6:
* Rename some of the symbols to make their usage clearer and avoid
repetition.
Changes from v5:
* Actually expose the new VCPU capability (KVM_ARM_VCPU_REC) by bumping
KVM_VCPU_MAX_FEATURES - note this also exposes KVM_ARM_VCPU_HAS_EL2!
---
Documentation/virt/kvm/api.rst | 57 ++++++++++++++++++++++++++++++++++
include/uapi/linux/kvm.h | 23 ++++++++++++++
2 files changed, 80 insertions(+)
@@ -6517,6 +6517,54 @@ the capability to be present.`flags` must currently be zero.+4.144 KVM_ARM_VCPU_RMI_PSCI_COMPLETE+------------------------------------++:Capability: KVM_CAP_ARM_RMI+:Architectures: arm64+:Type: vcpu ioctl+:Parameters: struct kvm_arm_rmi_psci_complete (in)+:Returns: 0 if successful, < 0 on error++::++ struct kvm_arm_rmi_psci_complete {+ __u64 target_mpidr;+ __u32 psci_status;+ __u32 padding[3];+ };++Where PSCI functions are handled by user space, the RMM needs to be informed of+the target of the operation using `target_mpidr`, along with the status+(`psci_status`). The RMM v1.0 specification defines two functions that require+this call: PSCI_CPU_ON and PSCI_AFFINITY_INFO.++If the kernel is handling PSCI then this is done automatically and the VMM+doesn't need to call this ioctl.
Shouldn't we make handling of PSCI mandatory for VMMs that deal with
CCA? I suspect it would simplify the implementation significantly.
What vcpu fd does this apply to? The vcpu calling the PSCI function?
Or the target? This is pretty important for PSCI_ON. My guess is that
this is setting the return value for the caller?
Assuming this is indeed for the caller, why do we have a different
flow from anything else that returns a result from a hypercall?
+
+4.145 KVM_ARM_RMI_POPULATE
+--------------------------
+
+:Capability: KVM_CAP_ARM_RMI
+:Architectures: arm64
+:Type: vm ioctl
+:Parameters: struct kvm_arm_rmi_populate (in)
+:Returns: number of bytes populated, < 0 on error
+
+::
+
+ struct kvm_arm_rmi_populate {
+ __u64 base;
+ __u64 size;
+ __u64 source_uaddr;
+ __u32 flags;
+ __u32 reserved;
+ };
+
+Populate a region of protected address space by copying the data from the user
+space pointer provided. This is only valid before any VCPUs have been run.
+The ioctl might not populate the entire region and user space may have to
+repeatedly call it (with updated pointers) to populate the entire region.
size as a __u64 is odd, as the return value from the ioctl is a signed
int. This implies that you can't really report how many bytes you have
copied. Some form of consistency wouldn't hurt.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
From: Steven Price <steven.price@arm.com> Date: 2026-03-02 15:23:52
Hi Marc,
On 02/03/2026 14:25, Marc Zyngier wrote:
On Wed, 17 Dec 2025 10:10:43 +0000,
Steven Price [off-list ref] wrote:
quoted
There is one CAP which identified the presence of CCA, and two ioctls.
One ioctl is used to populate memory and the other is used when user
space is providing the PSCI implementation to identify the target of the
operation.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Completely reworked to be more implicit. Rather than having explicit
CAP operations to progress the realm construction these operations
are done when needed (on populating and on first vCPU run).
* Populate and PSCI complete are promoted to proper ioctls.
Changes since v10:
* Rename symbols from RME to RMI.
Changes since v9:
* Improvements to documentation.
* Bump the magic number for KVM_CAP_ARM_RME to avoid conflicts.
Changes since v8:
* Minor improvements to documentation following review.
* Bump the magic numbers to avoid conflicts.
Changes since v7:
* Add documentation of new ioctls
* Bump the magic numbers to avoid conflicts
Changes since v6:
* Rename some of the symbols to make their usage clearer and avoid
repetition.
Changes from v5:
* Actually expose the new VCPU capability (KVM_ARM_VCPU_REC) by bumping
KVM_VCPU_MAX_FEATURES - note this also exposes KVM_ARM_VCPU_HAS_EL2!
---
Documentation/virt/kvm/api.rst | 57 ++++++++++++++++++++++++++++++++++
include/uapi/linux/kvm.h | 23 ++++++++++++++
2 files changed, 80 insertions(+)
@@ -6517,6 +6517,54 @@ the capability to be present.`flags` must currently be zero.+4.144 KVM_ARM_VCPU_RMI_PSCI_COMPLETE+------------------------------------++:Capability: KVM_CAP_ARM_RMI+:Architectures: arm64+:Type: vcpu ioctl+:Parameters: struct kvm_arm_rmi_psci_complete (in)+:Returns: 0 if successful, < 0 on error++::++ struct kvm_arm_rmi_psci_complete {+ __u64 target_mpidr;+ __u32 psci_status;+ __u32 padding[3];+ };++Where PSCI functions are handled by user space, the RMM needs to be informed of+the target of the operation using `target_mpidr`, along with the status+(`psci_status`). The RMM v1.0 specification defines two functions that require+this call: PSCI_CPU_ON and PSCI_AFFINITY_INFO.++If the kernel is handling PSCI then this is done automatically and the VMM+doesn't need to call this ioctl.
Shouldn't we make handling of PSCI mandatory for VMMs that deal with
CCA? I suspect it would simplify the implementation significantly.
What do you mean by making it "mandatory for VMMs"? If you mean PSCI is
always forwarded to user space then I don't think it's going to make
much difference. Patch 27 handles the PSCI changes (72 lines added), and
some of that is adding this uAPI for the VMM to handle it.
Removing the functionality to allow the VMM to handle it would obviously
simplify things a bit (we can drop this uAPI), but I think the desire is
to push this onto user space.
What vcpu fd does this apply to? The vcpu calling the PSCI function?
Or the target? This is pretty important for PSCI_ON. My guess is that
this is setting the return value for the caller?
Yes the fd is the vcpu calling PSCI. As you say, this is for the return
value to be set correctly.
Assuming this is indeed for the caller, why do we have a different
flow from anything else that returns a result from a hypercall?
I'm not entirely sure what you are suggesting. Do you mean why are we
not just writing to the GPRS that would contain the result? The issue
here is that the RMM needs to know the PA of the target REC structure -
this isn't a return to the guest, but information for the RMM itself to
complete the PSCI call.
Ultimately even in the case where the VMM is handling PSCI, it's
actually a combination of the VMM and the RMM - with the RMM validating
the responses.
quoted
+
+4.145 KVM_ARM_RMI_POPULATE
+--------------------------
+
+:Capability: KVM_CAP_ARM_RMI
+:Architectures: arm64
+:Type: vm ioctl
+:Parameters: struct kvm_arm_rmi_populate (in)
+:Returns: number of bytes populated, < 0 on error
+
+::
+
+ struct kvm_arm_rmi_populate {
+ __u64 base;
+ __u64 size;
+ __u64 source_uaddr;
+ __u32 flags;
+ __u32 reserved;
+ };
+
+Populate a region of protected address space by copying the data from the user
+space pointer provided. This is only valid before any VCPUs have been run.
+The ioctl might not populate the entire region and user space may have to
+repeatedly call it (with updated pointers) to populate the entire region.
size as a __u64 is odd, as the return value from the ioctl is a signed
int. This implies that you can't really report how many bytes you have
copied. Some form of consistency wouldn't hurt.
Good spot. In practice this works because >2GB in one operation is
highly unlikely to be processed in one go. But I guess I'll change this
to have an output size argument. I guess I could make the kernel update
all of base,size,source_uaddr which would simplify user space.
Thanks,
Steve
From: Suzuki K Poulose <suzuki.poulose@arm.com> Date: 2026-03-02 17:13:48
On 02/03/2026 15:23, Steven Price wrote:
Hi Marc,
On 02/03/2026 14:25, Marc Zyngier wrote:
quoted
On Wed, 17 Dec 2025 10:10:43 +0000,
Steven Price [off-list ref] wrote:
quoted
There is one CAP which identified the presence of CCA, and two ioctls.
One ioctl is used to populate memory and the other is used when user
space is providing the PSCI implementation to identify the target of the
operation.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Completely reworked to be more implicit. Rather than having explicit
CAP operations to progress the realm construction these operations
are done when needed (on populating and on first vCPU run).
* Populate and PSCI complete are promoted to proper ioctls.
Changes since v10:
* Rename symbols from RME to RMI.
Changes since v9:
* Improvements to documentation.
* Bump the magic number for KVM_CAP_ARM_RME to avoid conflicts.
Changes since v8:
* Minor improvements to documentation following review.
* Bump the magic numbers to avoid conflicts.
Changes since v7:
* Add documentation of new ioctls
* Bump the magic numbers to avoid conflicts
Changes since v6:
* Rename some of the symbols to make their usage clearer and avoid
repetition.
Changes from v5:
* Actually expose the new VCPU capability (KVM_ARM_VCPU_REC) by bumping
KVM_VCPU_MAX_FEATURES - note this also exposes KVM_ARM_VCPU_HAS_EL2!
---
Documentation/virt/kvm/api.rst | 57 ++++++++++++++++++++++++++++++++++
include/uapi/linux/kvm.h | 23 ++++++++++++++
2 files changed, 80 insertions(+)
@@ -6517,6 +6517,54 @@ the capability to be present.`flags` must currently be zero.+4.144 KVM_ARM_VCPU_RMI_PSCI_COMPLETE+------------------------------------++:Capability: KVM_CAP_ARM_RMI+:Architectures: arm64+:Type: vcpu ioctl+:Parameters: struct kvm_arm_rmi_psci_complete (in)+:Returns: 0 if successful, < 0 on error++::++ struct kvm_arm_rmi_psci_complete {+ __u64 target_mpidr;+ __u32 psci_status;+ __u32 padding[3];+ };++Where PSCI functions are handled by user space, the RMM needs to be informed of+the target of the operation using `target_mpidr`, along with the status+(`psci_status`). The RMM v1.0 specification defines two functions that require+this call: PSCI_CPU_ON and PSCI_AFFINITY_INFO.++If the kernel is handling PSCI then this is done automatically and the VMM+doesn't need to call this ioctl.
Shouldn't we make handling of PSCI mandatory for VMMs that deal with
CCA? I suspect it would simplify the implementation significantly.
What do you mean by making it "mandatory for VMMs"? If you mean PSCI is
always forwarded to user space then I don't think it's going to make
much difference. Patch 27 handles the PSCI changes (72 lines added), and
some of that is adding this uAPI for the VMM to handle it.
Removing the functionality to allow the VMM to handle it would obviously
simplify things a bit (we can drop this uAPI), but I think the desire is
to push this onto user space.
quoted
What vcpu fd does this apply to? The vcpu calling the PSCI function?
Or the target? This is pretty important for PSCI_ON. My guess is that
this is setting the return value for the caller?
Yes the fd is the vcpu calling PSCI. As you say, this is for the return
value to be set correctly.
quoted
Assuming this is indeed for the caller, why do we have a different
flow from anything else that returns a result from a hypercall?
I'm not entirely sure what you are suggesting. Do you mean why are we
not just writing to the GPRS that would contain the result? The issue
here is that the RMM needs to know the PA of the target REC structure -
this isn't a return to the guest, but information for the RMM itself to
complete the PSCI call.
Ultimately even in the case where the VMM is handling PSCI, it's
actually a combination of the VMM and the RMM - with the RMM validating
the responses.
More importantly, we have to make sure that the "RMI_PSCI_COMPLETE" is
invoked before both of the following:
1. The "source" vCPU is run again
2. More importantly the "target" vCPU is run.
The latter part makes it difficult to issue this RMI_PSCI_COMPLETE on
the way back from servicing the SMCCC call.
Suzuki
quoted
quoted
+
+4.145 KVM_ARM_RMI_POPULATE
+--------------------------
+
+:Capability: KVM_CAP_ARM_RMI
+:Architectures: arm64
+:Type: vm ioctl
+:Parameters: struct kvm_arm_rmi_populate (in)
+:Returns: number of bytes populated, < 0 on error
+
+::
+
+ struct kvm_arm_rmi_populate {
+ __u64 base;
+ __u64 size;
+ __u64 source_uaddr;
+ __u32 flags;
+ __u32 reserved;
+ };
+
+Populate a region of protected address space by copying the data from the user
+space pointer provided. This is only valid before any VCPUs have been run.
+The ioctl might not populate the entire region and user space may have to
+repeatedly call it (with updated pointers) to populate the entire region.
size as a __u64 is odd, as the return value from the ioctl is a signed
int. This implies that you can't really report how many bytes you have
copied. Some form of consistency wouldn't hurt.
Good spot. In practice this works because >2GB in one operation is
highly unlikely to be processed in one go. But I guess I'll change this
to have an output size argument. I guess I could make the kernel update
all of base,size,source_uaddr which would simplify user space.
Thanks,
Steve
From: Marc Zyngier <maz@kernel.org> Date: 2026-03-03 13:13:58
On Mon, 02 Mar 2026 17:13:41 +0000,
Suzuki K Poulose [off-list ref] wrote:
On 02/03/2026 15:23, Steven Price wrote:
quoted
Hi Marc,
On 02/03/2026 14:25, Marc Zyngier wrote:
quoted
On Wed, 17 Dec 2025 10:10:43 +0000,
Steven Price [off-list ref] wrote:
quoted
There is one CAP which identified the presence of CCA, and two ioctls.
One ioctl is used to populate memory and the other is used when user
space is providing the PSCI implementation to identify the target of the
operation.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Completely reworked to be more implicit. Rather than having explicit
CAP operations to progress the realm construction these operations
are done when needed (on populating and on first vCPU run).
* Populate and PSCI complete are promoted to proper ioctls.
Changes since v10:
* Rename symbols from RME to RMI.
Changes since v9:
* Improvements to documentation.
* Bump the magic number for KVM_CAP_ARM_RME to avoid conflicts.
Changes since v8:
* Minor improvements to documentation following review.
* Bump the magic numbers to avoid conflicts.
Changes since v7:
* Add documentation of new ioctls
* Bump the magic numbers to avoid conflicts
Changes since v6:
* Rename some of the symbols to make their usage clearer and avoid
repetition.
Changes from v5:
* Actually expose the new VCPU capability (KVM_ARM_VCPU_REC) by bumping
KVM_VCPU_MAX_FEATURES - note this also exposes KVM_ARM_VCPU_HAS_EL2!
---
Documentation/virt/kvm/api.rst | 57 ++++++++++++++++++++++++++++++++++
include/uapi/linux/kvm.h | 23 ++++++++++++++
2 files changed, 80 insertions(+)
@@ -6517,6 +6517,54 @@ the capability to be present.`flags` must currently be zero. +4.144 KVM_ARM_VCPU_RMI_PSCI_COMPLETE+------------------------------------++:Capability: KVM_CAP_ARM_RMI+:Architectures: arm64+:Type: vcpu ioctl+:Parameters: struct kvm_arm_rmi_psci_complete (in)+:Returns: 0 if successful, < 0 on error++::++ struct kvm_arm_rmi_psci_complete {+ __u64 target_mpidr;+ __u32 psci_status;+ __u32 padding[3];+ };++Where PSCI functions are handled by user space, the RMM needs to be informed of+the target of the operation using `target_mpidr`, along with the status+(`psci_status`). The RMM v1.0 specification defines two functions that require+this call: PSCI_CPU_ON and PSCI_AFFINITY_INFO.++If the kernel is handling PSCI then this is done automatically and the VMM+doesn't need to call this ioctl.
Shouldn't we make handling of PSCI mandatory for VMMs that deal with
CCA? I suspect it would simplify the implementation significantly.
What do you mean by making it "mandatory for VMMs"? If you mean PSCI is
always forwarded to user space then I don't think it's going to make
much difference. Patch 27 handles the PSCI changes (72 lines added), and
some of that is adding this uAPI for the VMM to handle it.
Removing the functionality to allow the VMM to handle it would obviously
simplify things a bit (we can drop this uAPI), but I think the desire is
to push this onto user space.
quoted
What vcpu fd does this apply to? The vcpu calling the PSCI function?
Or the target? This is pretty important for PSCI_ON. My guess is that
this is setting the return value for the caller?
Yes the fd is the vcpu calling PSCI. As you say, this is for the return
value to be set correctly.
quoted
Assuming this is indeed for the caller, why do we have a different
flow from anything else that returns a result from a hypercall?
I'm not entirely sure what you are suggesting. Do you mean why are we
not just writing to the GPRS that would contain the result? The issue
here is that the RMM needs to know the PA of the target REC structure -
this isn't a return to the guest, but information for the RMM itself to
complete the PSCI call.
Ultimately even in the case where the VMM is handling PSCI, it's
actually a combination of the VMM and the RMM - with the RMM validating
the responses.
More importantly, we have to make sure that the "RMI_PSCI_COMPLETE" is
invoked before both of the following:
1. The "source" vCPU is run again
2. More importantly the "target" vCPU is run.
I don't understand why (1) is required. Once the VMM gets the request,
the target vcpu can run, and can itself do the completion, without any
additional userspace involvement.
M.
--
Without deviation from the norm, progress is not possible.
From: Suzuki K Poulose <suzuki.poulose@arm.com> Date: 2026-03-03 14:24:24
On 03/03/2026 13:13, Marc Zyngier wrote:
On Mon, 02 Mar 2026 17:13:41 +0000,
Suzuki K Poulose [off-list ref] wrote:
quoted
On 02/03/2026 15:23, Steven Price wrote:
quoted
Hi Marc,
On 02/03/2026 14:25, Marc Zyngier wrote:
quoted
On Wed, 17 Dec 2025 10:10:43 +0000,
Steven Price [off-list ref] wrote:
quoted
There is one CAP which identified the presence of CCA, and two ioctls.
One ioctl is used to populate memory and the other is used when user
space is providing the PSCI implementation to identify the target of the
operation.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Completely reworked to be more implicit. Rather than having explicit
CAP operations to progress the realm construction these operations
are done when needed (on populating and on first vCPU run).
* Populate and PSCI complete are promoted to proper ioctls.
Changes since v10:
* Rename symbols from RME to RMI.
Changes since v9:
* Improvements to documentation.
* Bump the magic number for KVM_CAP_ARM_RME to avoid conflicts.
Changes since v8:
* Minor improvements to documentation following review.
* Bump the magic numbers to avoid conflicts.
Changes since v7:
* Add documentation of new ioctls
* Bump the magic numbers to avoid conflicts
Changes since v6:
* Rename some of the symbols to make their usage clearer and avoid
repetition.
Changes from v5:
* Actually expose the new VCPU capability (KVM_ARM_VCPU_REC) by bumping
KVM_VCPU_MAX_FEATURES - note this also exposes KVM_ARM_VCPU_HAS_EL2!
---
Documentation/virt/kvm/api.rst | 57 ++++++++++++++++++++++++++++++++++
include/uapi/linux/kvm.h | 23 ++++++++++++++
2 files changed, 80 insertions(+)
@@ -6517,6 +6517,54 @@ the capability to be present.`flags` must currently be zero. +4.144 KVM_ARM_VCPU_RMI_PSCI_COMPLETE+------------------------------------++:Capability: KVM_CAP_ARM_RMI+:Architectures: arm64+:Type: vcpu ioctl+:Parameters: struct kvm_arm_rmi_psci_complete (in)+:Returns: 0 if successful, < 0 on error++::++ struct kvm_arm_rmi_psci_complete {+ __u64 target_mpidr;+ __u32 psci_status;+ __u32 padding[3];+ };++Where PSCI functions are handled by user space, the RMM needs to be informed of+the target of the operation using `target_mpidr`, along with the status+(`psci_status`). The RMM v1.0 specification defines two functions that require+this call: PSCI_CPU_ON and PSCI_AFFINITY_INFO.++If the kernel is handling PSCI then this is done automatically and the VMM+doesn't need to call this ioctl.
Shouldn't we make handling of PSCI mandatory for VMMs that deal with
CCA? I suspect it would simplify the implementation significantly.
What do you mean by making it "mandatory for VMMs"? If you mean PSCI is
always forwarded to user space then I don't think it's going to make
much difference. Patch 27 handles the PSCI changes (72 lines added), and
some of that is adding this uAPI for the VMM to handle it.
Removing the functionality to allow the VMM to handle it would obviously
simplify things a bit (we can drop this uAPI), but I think the desire is
to push this onto user space.
quoted
What vcpu fd does this apply to? The vcpu calling the PSCI function?
Or the target? This is pretty important for PSCI_ON. My guess is that
this is setting the return value for the caller?
Yes the fd is the vcpu calling PSCI. As you say, this is for the return
value to be set correctly.
quoted
Assuming this is indeed for the caller, why do we have a different
flow from anything else that returns a result from a hypercall?
I'm not entirely sure what you are suggesting. Do you mean why are we
not just writing to the GPRS that would contain the result? The issue
here is that the RMM needs to know the PA of the target REC structure -
this isn't a return to the guest, but information for the RMM itself to
complete the PSCI call.
Ultimately even in the case where the VMM is handling PSCI, it's
actually a combination of the VMM and the RMM - with the RMM validating
the responses.
More importantly, we have to make sure that the "RMI_PSCI_COMPLETE" is
invoked before both of the following:
1. The "source" vCPU is run again
2. More importantly the "target" vCPU is run.
I don't understand why (1) is required. Once the VMM gets the request,
The underlying issue is, the RMM doesn't have the VCPU object for the
"target" VCPU, to make the book keeping. Also, please note that for a
Realm, PSCI is emulated by the "RMM". Host is obviously notified of the
"PSCI" changes via EXIT_PSCI (note, it is not SMCCC exit)
so that it can be in sync with the real state. And does have a say in
CPU_ON. So, before we return to running the "source" CPU,
Host must provide the target VCPU object and its consent (via
psci_status) to the RMM. This allows the RMM to emulate the PSCI
request correctly and also at the same time keep its book keeping
in tact (i.e., marking the Target VCPU as runnable or not).
When a "source" VCPU exits to the host with a PSCI_EXIT, the RMM
marks the source VCPU has a pending PSCI operation, and
RMI_PSCI_COMPLETE request ticks that off, making it runnable again.
Suzuki
the target vcpu can run, and can itself do the completion, without any
additional userspace involvement.
M.
From: Marc Zyngier <maz@kernel.org> Date: 2026-03-03 14:37:03
On Tue, 03 Mar 2026 14:23:08 +0000,
Suzuki K Poulose [off-list ref] wrote:
On 03/03/2026 13:13, Marc Zyngier wrote:
quoted
On Mon, 02 Mar 2026 17:13:41 +0000,
Suzuki K Poulose [off-list ref] wrote:
quoted
More importantly, we have to make sure that the "RMI_PSCI_COMPLETE" is
invoked before both of the following:
1. The "source" vCPU is run again
2. More importantly the "target" vCPU is run.
I don't understand why (1) is required. Once the VMM gets the request,
The underlying issue is, the RMM doesn't have the VCPU object for the
"target" VCPU, to make the book keeping. Also, please note that for a
Realm, PSCI is emulated by the "RMM". Host is obviously notified of the
"PSCI" changes via EXIT_PSCI (note, it is not SMCCC exit)
so that it can be in sync with the real state. And does have a say in
CPU_ON. So, before we return to running the "source" CPU,
Host must provide the target VCPU object and its consent (via
psci_status) to the RMM. This allows the RMM to emulate the PSCI
request correctly and also at the same time keep its book keeping
in tact (i.e., marking the Target VCPU as runnable or not).
When a "source" VCPU exits to the host with a PSCI_EXIT, the RMM
marks the source VCPU has a pending PSCI operation, and
RMI_PSCI_COMPLETE request ticks that off, making it runnable again.
Sure. What I don't get is what this has to happen on the source vcpu
thread. The RMM has absolutely no clue about that, and there should be
no impediment to letting the target vcpu do it as it starts.
Even better, you should be able to do that on the first thread that
reenters the guest, completely removing any RMM knowledge from the
PSCI handling in userspace.
If you can't do that, then please consider fixing the RMM to allow it.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
From: Suzuki K Poulose <suzuki.poulose@arm.com> Date: 2026-03-03 16:02:59
On 03/03/2026 14:37, Marc Zyngier wrote:
On Tue, 03 Mar 2026 14:23:08 +0000,
Suzuki K Poulose [off-list ref] wrote:
quoted
On 03/03/2026 13:13, Marc Zyngier wrote:
quoted
On Mon, 02 Mar 2026 17:13:41 +0000,
Suzuki K Poulose [off-list ref] wrote:
quoted
More importantly, we have to make sure that the "RMI_PSCI_COMPLETE" is
invoked before both of the following:
1. The "source" vCPU is run again
2. More importantly the "target" vCPU is run.
I don't understand why (1) is required. Once the VMM gets the request,
The underlying issue is, the RMM doesn't have the VCPU object for the
"target" VCPU, to make the book keeping. Also, please note that for a
Realm, PSCI is emulated by the "RMM". Host is obviously notified of the
"PSCI" changes via EXIT_PSCI (note, it is not SMCCC exit)
so that it can be in sync with the real state. And does have a say in
CPU_ON. So, before we return to running the "source" CPU,
Host must provide the target VCPU object and its consent (via
psci_status) to the RMM. This allows the RMM to emulate the PSCI
request correctly and also at the same time keep its book keeping
in tact (i.e., marking the Target VCPU as runnable or not).
When a "source" VCPU exits to the host with a PSCI_EXIT, the RMM
marks the source VCPU has a pending PSCI operation, and
RMI_PSCI_COMPLETE request ticks that off, making it runnable again.
Sure. What I don't get is what this has to happen on the source vcpu
thread. The RMM has absolutely no clue about that, and there should be
no impediment to letting the target vcpu do it as it starts.
Because, the RMM wants to make that the state is consistent. i.e,
Host cannot lie to the "source" VCPU and RMM (e.g., CPU_ON denied) and
then run the "target" VCPU.
In other words, the response to the CPU_ON must be recorded by the RMM
in the target VCPU state, to make sure the target VCPU state is
consistent with the response.
The only way to fix this would be RMM keeping track of "mpidr" to REC
object mapping (VCPU object), which impacts the scalability. With that
in place, RMM can update the target VCPU state from the return of the
REC_ENTER after a PSCI_EXIT.
That said, will explore the options to address this
Thanks
Suzuki
Even better, you should be able to do that on the first thread that
reenters the guest, completely removing any RMM knowledge from the
PSCI handling in userspace.
If you can't do that, then please consider fixing the RMM to allow it.
Thanks,
M.
From: Marc Zyngier <maz@kernel.org> Date: 2026-03-03 13:11:11
On Mon, 02 Mar 2026 15:23:44 +0000,
Steven Price [off-list ref] wrote:
Hi Marc,
On 02/03/2026 14:25, Marc Zyngier wrote:
quoted
On Wed, 17 Dec 2025 10:10:43 +0000,
Steven Price [off-list ref] wrote:
quoted
There is one CAP which identified the presence of CCA, and two ioctls.
One ioctl is used to populate memory and the other is used when user
space is providing the PSCI implementation to identify the target of the
operation.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Completely reworked to be more implicit. Rather than having explicit
CAP operations to progress the realm construction these operations
are done when needed (on populating and on first vCPU run).
* Populate and PSCI complete are promoted to proper ioctls.
Changes since v10:
* Rename symbols from RME to RMI.
Changes since v9:
* Improvements to documentation.
* Bump the magic number for KVM_CAP_ARM_RME to avoid conflicts.
Changes since v8:
* Minor improvements to documentation following review.
* Bump the magic numbers to avoid conflicts.
Changes since v7:
* Add documentation of new ioctls
* Bump the magic numbers to avoid conflicts
Changes since v6:
* Rename some of the symbols to make their usage clearer and avoid
repetition.
Changes from v5:
* Actually expose the new VCPU capability (KVM_ARM_VCPU_REC) by bumping
KVM_VCPU_MAX_FEATURES - note this also exposes KVM_ARM_VCPU_HAS_EL2!
---
Documentation/virt/kvm/api.rst | 57 ++++++++++++++++++++++++++++++++++
include/uapi/linux/kvm.h | 23 ++++++++++++++
2 files changed, 80 insertions(+)
@@ -6517,6 +6517,54 @@ the capability to be present.`flags` must currently be zero.+4.144 KVM_ARM_VCPU_RMI_PSCI_COMPLETE+------------------------------------++:Capability: KVM_CAP_ARM_RMI+:Architectures: arm64+:Type: vcpu ioctl+:Parameters: struct kvm_arm_rmi_psci_complete (in)+:Returns: 0 if successful, < 0 on error++::++ struct kvm_arm_rmi_psci_complete {+ __u64 target_mpidr;+ __u32 psci_status;+ __u32 padding[3];+ };++Where PSCI functions are handled by user space, the RMM needs to be informed of+the target of the operation using `target_mpidr`, along with the status+(`psci_status`). The RMM v1.0 specification defines two functions that require+this call: PSCI_CPU_ON and PSCI_AFFINITY_INFO.++If the kernel is handling PSCI then this is done automatically and the VMM+doesn't need to call this ioctl.
Shouldn't we make handling of PSCI mandatory for VMMs that deal with
CCA? I suspect it would simplify the implementation significantly.
What do you mean by making it "mandatory for VMMs"? If you mean PSCI is
always forwarded to user space then I don't think it's going to make
much difference. Patch 27 handles the PSCI changes (72 lines added), and
some of that is adding this uAPI for the VMM to handle it.
Removing the functionality to allow the VMM to handle it would obviously
simplify things a bit (we can drop this uAPI), but I think the desire is
to push this onto user space.
And that's what I'm asking for. I do not want this to be optional. CCA
should implies PSCI in userspace, and that's it.
quoted
What vcpu fd does this apply to? The vcpu calling the PSCI function?
Or the target? This is pretty important for PSCI_ON. My guess is that
this is setting the return value for the caller?
Yes the fd is the vcpu calling PSCI. As you say, this is for the return
value to be set correctly.
Another question is why do we need the ioctl at all? Why can't it be
done on the first run of the target vcpu? If no PSCI call was issued
to run it, then the run fails.
quoted
Assuming this is indeed for the caller, why do we have a different
flow from anything else that returns a result from a hypercall?
I'm not entirely sure what you are suggesting. Do you mean why are we
not just writing to the GPRS that would contain the result? The issue
here is that the RMM needs to know the PA of the target REC structure -
this isn't a return to the guest, but information for the RMM itself to
complete the PSCI call.
PSCI is a SMC call. SMC calls are routed to userspace as such. For odd
reasons, the RMM treats PSCI differently from any other SMC call.
That seems a very bizarre behaviour to me.
Ultimately even in the case where the VMM is handling PSCI, it's
actually a combination of the VMM and the RMM - with the RMM validating
the responses.
I don't see why PSCI is singled out here, irrespective of the tracking
that the RMM wants to do.
M.
--
Without deviation from the norm, progress is not possible.
From: Steven Price <steven.price@arm.com> Date: 2026-03-04 12:08:37
On 03/03/2026 13:11, Marc Zyngier wrote:
On Mon, 02 Mar 2026 15:23:44 +0000,
Steven Price [off-list ref] wrote:
quoted
Hi Marc,
On 02/03/2026 14:25, Marc Zyngier wrote:
quoted
On Wed, 17 Dec 2025 10:10:43 +0000,
Steven Price [off-list ref] wrote:
quoted
There is one CAP which identified the presence of CCA, and two ioctls.
One ioctl is used to populate memory and the other is used when user
space is providing the PSCI implementation to identify the target of the
operation.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Completely reworked to be more implicit. Rather than having explicit
CAP operations to progress the realm construction these operations
are done when needed (on populating and on first vCPU run).
* Populate and PSCI complete are promoted to proper ioctls.
Changes since v10:
* Rename symbols from RME to RMI.
Changes since v9:
* Improvements to documentation.
* Bump the magic number for KVM_CAP_ARM_RME to avoid conflicts.
Changes since v8:
* Minor improvements to documentation following review.
* Bump the magic numbers to avoid conflicts.
Changes since v7:
* Add documentation of new ioctls
* Bump the magic numbers to avoid conflicts
Changes since v6:
* Rename some of the symbols to make their usage clearer and avoid
repetition.
Changes from v5:
* Actually expose the new VCPU capability (KVM_ARM_VCPU_REC) by bumping
KVM_VCPU_MAX_FEATURES - note this also exposes KVM_ARM_VCPU_HAS_EL2!
---
Documentation/virt/kvm/api.rst | 57 ++++++++++++++++++++++++++++++++++
include/uapi/linux/kvm.h | 23 ++++++++++++++
2 files changed, 80 insertions(+)
@@ -6517,6 +6517,54 @@ the capability to be present.`flags` must currently be zero.+4.144 KVM_ARM_VCPU_RMI_PSCI_COMPLETE+------------------------------------++:Capability: KVM_CAP_ARM_RMI+:Architectures: arm64+:Type: vcpu ioctl+:Parameters: struct kvm_arm_rmi_psci_complete (in)+:Returns: 0 if successful, < 0 on error++::++ struct kvm_arm_rmi_psci_complete {+ __u64 target_mpidr;+ __u32 psci_status;+ __u32 padding[3];+ };++Where PSCI functions are handled by user space, the RMM needs to be informed of+the target of the operation using `target_mpidr`, along with the status+(`psci_status`). The RMM v1.0 specification defines two functions that require+this call: PSCI_CPU_ON and PSCI_AFFINITY_INFO.++If the kernel is handling PSCI then this is done automatically and the VMM+doesn't need to call this ioctl.
Shouldn't we make handling of PSCI mandatory for VMMs that deal with
CCA? I suspect it would simplify the implementation significantly.
What do you mean by making it "mandatory for VMMs"? If you mean PSCI is
always forwarded to user space then I don't think it's going to make
much difference. Patch 27 handles the PSCI changes (72 lines added), and
some of that is adding this uAPI for the VMM to handle it.
Removing the functionality to allow the VMM to handle it would obviously
simplify things a bit (we can drop this uAPI), but I think the desire is
to push this onto user space.
And that's what I'm asking for. I do not want this to be optional. CCA
should implies PSCI in userspace, and that's it.
quoted
quoted
What vcpu fd does this apply to? The vcpu calling the PSCI function?
Or the target? This is pretty important for PSCI_ON. My guess is that
this is setting the return value for the caller?
Yes the fd is the vcpu calling PSCI. As you say, this is for the return
value to be set correctly.
Another question is why do we need the ioctl at all? Why can't it be
done on the first run of the target vcpu? If no PSCI call was issued
to run it, then the run fails.
So my concern is the ordering of operations for PSCI_CPU_ON. As things
stand the RMM needs to know the MPIDR mapping to look up the REC object
before either VCPU runs again.
If we do this on the first run of the target VCPU, then how is the VMM
to tell that the target VCPU has executed "long enough" that it is safe
to do the return on the initial VCPU? Since the VCPUs are different
threads this becomes tricky. Options I can see are:
a) The VMM has to wait for the target VCPU to exit - we'd probably want
to trigger an artificial early exit in this case to unblock things.
b) The kernel blocks the initial VCPU from running until the target VCPU
has completed this "first run" logic. I think waiting in the kernel is
probably problematic, so this implies return some sort of "retry later"
response to the VMM.
c) The kernel handles the "PSCI_COMPLETE" dance on whichever VCPU runs
first, blocking the other until the dance is complete. A disadvantage
here is that behaviour can differ (in error conditions) depending on
which VCPU thread wins the race.
All these options also involve the kernel keeping track of the PSCI
sequence, in particular:
1. Tracking that the exit was due to a PSCI_CPU_ON.
2. Treating attempting to run the target VCPU as an implicit success
return from the PSCI call.
3. Recognising the next run on the initial VCPU as containing the PSCI
result - if 2, above, has happened then the kernel will need to handle
this (by killing the guest).
TLDR; Yes this is possible but I don't think it's pretty, and I'm not
convinced it's an improved uAPI.
Of course the above all assumes that the RMM can't just track things
internally. My preference is to kill RMI_PSCI_COMPLETE altogether, but
I'm not sure how possible that is within the context of the RMM.
quoted
quoted
Assuming this is indeed for the caller, why do we have a different
flow from anything else that returns a result from a hypercall?
I'm not entirely sure what you are suggesting. Do you mean why are we
not just writing to the GPRS that would contain the result? The issue
here is that the RMM needs to know the PA of the target REC structure -
this isn't a return to the guest, but information for the RMM itself to
complete the PSCI call.
PSCI is a SMC call. SMC calls are routed to userspace as such. For odd
reasons, the RMM treats PSCI differently from any other SMC call.
That seems a very bizarre behaviour to me.
The RMM generally treats SMC specially. We have the RSI_HOST_CALL as a
proxy for "general SMC-like" behaviour which is forwarded to the VMM. I
believe the intention here is to ensure that SMCs (from the realm guest)
are handled by a trusted agent (i.e. the RMM). PSCI is a corner case
because it requires some coordination and buy-in from the VMM.
I'm not sure I fully understand the security pros and cons of the design
here and what impact it would have if PSCI was well trusted.
Thanks,
Steve
quoted
Ultimately even in the case where the VMM is handling PSCI, it's
actually a combination of the VMM and the RMM - with the RMM validating
the responses.
I don't see why PSCI is singled out here, irrespective of the tracking
that the RMM wants to do.
M.
From: Marc Zyngier <maz@kernel.org> Date: 2026-03-11 19:10:09
On Mon, 02 Mar 2026 15:23:44 +0000,
Steven Price [off-list ref] wrote:
quoted
quoted
+ struct kvm_arm_rmi_populate {
+ __u64 base;
+ __u64 size;
+ __u64 source_uaddr;
+ __u32 flags;
+ __u32 reserved;
+ };
+
+Populate a region of protected address space by copying the data from the user
+space pointer provided. This is only valid before any VCPUs have been run.
+The ioctl might not populate the entire region and user space may have to
+repeatedly call it (with updated pointers) to populate the entire region.
size as a __u64 is odd, as the return value from the ioctl is a signed
int. This implies that you can't really report how many bytes you have
copied. Some form of consistency wouldn't hurt.
Good spot. In practice this works because >2GB in one operation is
highly unlikely to be processed in one go. But I guess I'll change this
to have an output size argument. I guess I could make the kernel update
all of base,size,source_uaddr which would simplify user space.
In a conversation with Suzuki, I suggested that splice(2) could be a
nicer way to express this, and allow asynchronous use with io-uring.
After all, having a guestmem backend for CCA is not exactly
outlandish, and having a splice implementation realistic enough.
Thoughts?
M.
--
Jazz isn't dead. It just smells funny.
From: Suzuki K Poulose <suzuki.poulose@arm.com> Date: 2026-03-12 09:28:26
On 11/03/2026 19:10, Marc Zyngier wrote:
On Mon, 02 Mar 2026 15:23:44 +0000,
Steven Price [off-list ref] wrote:
quoted
quoted
quoted
+ struct kvm_arm_rmi_populate {
+ __u64 base;
+ __u64 size;
+ __u64 source_uaddr;
+ __u32 flags;
+ __u32 reserved;
+ };
+
+Populate a region of protected address space by copying the data from the user
+space pointer provided. This is only valid before any VCPUs have been run.
+The ioctl might not populate the entire region and user space may have to
+repeatedly call it (with updated pointers) to populate the entire region.
size as a __u64 is odd, as the return value from the ioctl is a signed
int. This implies that you can't really report how many bytes you have
copied. Some form of consistency wouldn't hurt.
Good spot. In practice this works because >2GB in one operation is
highly unlikely to be processed in one go. But I guess I'll change this
to have an output size argument. I guess I could make the kernel update
all of base,size,source_uaddr which would simplify user space.
In a conversation with Suzuki, I suggested that splice(2) could be a
nicer way to express this, and allow asynchronous use with io-uring.
After all, having a guestmem backend for CCA is not exactly
outlandish, and having a splice implementation realistic enough.
Thoughts?
One issue that I realised, is about the "flags" for the populate.
Data can be loaded as measured vs unmeasured in CCA (and in TDX
and SNP). I don't see a way to convey this with splice.
Suzuki
From: Marc Zyngier <maz@kernel.org> Date: 2026-03-12 09:39:40
On Thu, 12 Mar 2026 09:28:16 +0000,
Suzuki K Poulose [off-list ref] wrote:
On 11/03/2026 19:10, Marc Zyngier wrote:
quoted
On Mon, 02 Mar 2026 15:23:44 +0000,
Steven Price [off-list ref] wrote:
quoted
quoted
quoted
+ struct kvm_arm_rmi_populate {
+ __u64 base;
+ __u64 size;
+ __u64 source_uaddr;
+ __u32 flags;
+ __u32 reserved;
+ };
+
+Populate a region of protected address space by copying the data from the user
+space pointer provided. This is only valid before any VCPUs have been run.
+The ioctl might not populate the entire region and user space may have to
+repeatedly call it (with updated pointers) to populate the entire region.
size as a __u64 is odd, as the return value from the ioctl is a signed
int. This implies that you can't really report how many bytes you have
copied. Some form of consistency wouldn't hurt.
Good spot. In practice this works because >2GB in one operation is
highly unlikely to be processed in one go. But I guess I'll change this
to have an output size argument. I guess I could make the kernel update
all of base,size,source_uaddr which would simplify user space.
In a conversation with Suzuki, I suggested that splice(2) could be a
nicer way to express this, and allow asynchronous use with io-uring.
After all, having a guestmem backend for CCA is not exactly
outlandish, and having a splice implementation realistic enough.
Thoughts?
One issue that I realised, is about the "flags" for the populate.
Data can be loaded as measured vs unmeasured in CCA (and in TDX
and SNP). I don't see a way to convey this with splice.
Surely that should me a property of the memory region, and not the way
it is copied, right?
M.
--
Without deviation from the norm, progress is not possible.
From: Steven Price <steven.price@arm.com> Date: 2026-03-12 10:45:25
On 12/03/2026 09:39, Marc Zyngier wrote:
On Thu, 12 Mar 2026 09:28:16 +0000,
Suzuki K Poulose [off-list ref] wrote:
quoted
On 11/03/2026 19:10, Marc Zyngier wrote:
quoted
On Mon, 02 Mar 2026 15:23:44 +0000,
Steven Price [off-list ref] wrote:
quoted
quoted
quoted
+ struct kvm_arm_rmi_populate {
+ __u64 base;
+ __u64 size;
+ __u64 source_uaddr;
+ __u32 flags;
+ __u32 reserved;
+ };
+
+Populate a region of protected address space by copying the data from the user
+space pointer provided. This is only valid before any VCPUs have been run.
+The ioctl might not populate the entire region and user space may have to
+repeatedly call it (with updated pointers) to populate the entire region.
size as a __u64 is odd, as the return value from the ioctl is a signed
int. This implies that you can't really report how many bytes you have
copied. Some form of consistency wouldn't hurt.
Good spot. In practice this works because >2GB in one operation is
highly unlikely to be processed in one go. But I guess I'll change this
to have an output size argument. I guess I could make the kernel update
all of base,size,source_uaddr which would simplify user space.
In a conversation with Suzuki, I suggested that splice(2) could be a
nicer way to express this, and allow asynchronous use with io-uring.
After all, having a guestmem backend for CCA is not exactly
outlandish, and having a splice implementation realistic enough.
Thoughts?
One issue that I realised, is about the "flags" for the populate.
Data can be loaded as measured vs unmeasured in CCA (and in TDX
and SNP). I don't see a way to convey this with splice.
Surely that should me a property of the memory region, and not the way
it is copied, right?
I'm not sure what the current thoughts on the matter are, but there was
an intention in the past that some data could be measured and some not
within a region which is logically the same. E.g. the DT might be
deliberately unmeasured so that the same measurement would cover a
selection of different configurations (with the guest obviously
validating that it was sane before continuing). Forcing the memory to be
split into multiple memslots just so a measurement flag can be set
correctly seems like a poor uAPI.
While splice(2) certainly looks similar to what we want, it does
(according to the man page at least) have the requirement that "one of
the file descriptors must refer to a pipe" which seems less than ideal
for the DT case where it's likely to be generated in memory by the VMM.
Or is my man page out of date and the pipe restriction isn't there in
the kernel?
Thanks,
Steve
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:21
Introduce the skeleton functions for creating and destroying a realm.
The IPA size requested is checked against what the RMM supports.
The actual work of constructing the realm will be added in future
patches.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Major rework to drop the realm configuration and make the
construction of realms implicit rather than driven by the VMM
directly.
* The code to create RDs, handle VMIDs etc is moved to later patches.
Changes since v10:
* Rename from RME to RMI.
* Move the stage2 cleanup to a later patch.
Changes since v9:
* Avoid walking the stage 2 page tables when destroying the realm -
the real ones are not accessible to the non-secure world, and the RMM
may leave junk in the physical pages when returning them.
* Fix an error path in realm_create_rd() to actually return an error value.
Changes since v8:
* Fix free_delegated_granule() to not call kvm_account_pgtable_pages();
a separate wrapper will be introduced in a later patch to deal with
RTTs.
* Minor code cleanups following review.
Changes since v7:
* Minor code cleanup following Gavin's review.
Changes since v6:
* Separate RMM RTT calculations from host PAGE_SIZE. This allows the
host page size to be larger than 4k while still communicating with an
RMM which uses 4k granules.
Changes since v5:
* Introduce free_delegated_granule() to replace many
undelegate/free_page() instances and centralise the comment on
leaking when the undelegate fails.
* Several other minor improvements suggested by reviews - thanks for
the feedback!
Changes since v2:
* Improved commit description.
* Improved return failures for rmi_check_version().
* Clear contents of PGD after it has been undelegated in case the RMM
left stale data.
* Minor changes to reflect changes in previous patches.
---
arch/arm64/include/asm/kvm_emulate.h | 5 ++
arch/arm64/include/asm/kvm_rmi.h | 18 +++++++
arch/arm64/kvm/arm.c | 11 ++++
arch/arm64/kvm/mmu.c | 11 ++--
arch/arm64/kvm/rmi.c | 81 ++++++++++++++++++++++++++++
5 files changed, 123 insertions(+), 3 deletions(-)
@@ -207,6 +207,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_is_realm(kvm)){+ret=kvm_init_realm_vm(kvm);+if(ret)+gotoerr_free_cpumask;+}+return0;err_free_cpumask:
@@ -427,6 +435,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)elser=kvm_supports_cacheable_pfnmap();break;+caseKVM_CAP_ARM_RMI:+r=static_key_enabled(&kvm_rmi_is_available);+break;default:r=0;
@@ -47,6 +56,75 @@ static int rmi_check_version(void)return0;}+u32kvm_realm_ipa_limit(void)+{+returnu64_get_bits(rmm_feat_reg0,RMI_FEATURE_REGISTER_0_S2SZ);+}++staticintfree_delegated_granule(phys_addr_tphys)+{+if(WARN_ON(rmi_granule_undelegate(phys))){+/* Undelegate failed: leak the page */+return-EBUSY;+}++free_page((unsignedlong)phys_to_virt(phys));++return0;+}++voidkvm_destroy_realm(structkvm*kvm)+{+structrealm*realm=&kvm->arch.realm;+size_tpgd_size=kvm_pgtable_stage2_pgd_size(kvm->arch.mmu.vtcr);+inti;++write_lock(&kvm->mmu_lock);+kvm_stage2_unmap_range(&kvm->arch.mmu,0,+BIT(realm->ia_bits-1),true);+write_unlock(&kvm->mmu_lock);++if(realm->params){+free_page((unsignedlong)realm->params);+realm->params=NULL;+}++if(!kvm_realm_is_created(kvm))+return;++WRITE_ONCE(realm->state,REALM_STATE_DYING);++if(realm->rd){+phys_addr_trd_phys=virt_to_phys(realm->rd);++if(WARN_ON(rmi_realm_destroy(rd_phys)))+return;+free_delegated_granule(rd_phys);+realm->rd=NULL;+}++for(i=0;i<pgd_size;i+=RMM_PAGE_SIZE){+phys_addr_tpgd_phys=kvm->arch.mmu.pgd_phys+i;++if(WARN_ON(rmi_granule_undelegate(pgd_phys)))+return;+}++WRITE_ONCE(realm->state,REALM_STATE_DEAD);++/* Now that the Realm is destroyed, free the entry level RTTs */+kvm_free_stage2_pgd(&kvm->arch.mmu);+}++intkvm_init_realm_vm(structkvm*kvm)+{+kvm->arch.realm.params=(void*)get_zeroed_page(GFP_KERNEL);++if(!kvm->arch.realm.params)+return-ENOMEM;+return0;+}+voidkvm_init_rmi(void){/* Only 4k page size on the host is supported */
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:26
From: Suzuki K Poulose <suzuki.poulose@arm.com>
RMM v1.0 provides no mechanism for the host to perform debug operations
on the guest. So limit the extensions that are visible to an allowlist
so that only those capabilities we can support are advertised.
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v10:
* Add a kvm_realm_ext_allowed() function which limits which extensions
are exposed to an allowlist. This removes the need for special casing
various extensions.
Changes since v7:
* Remove the helper functions and inline the kvm_is_realm() check with
a ternary operator.
* Rewrite the commit message to explain this patch.
---
arch/arm64/kvm/arm.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
@@ -317,6 +337,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)if(kvm&&kvm_vm_is_protected(kvm)&&!kvm_pvm_ext_allowed(ext))return0;+if(kvm&&kvm_is_realm(kvm)&&!kvm_realm_ext_allowed(ext))+return0;+switch(ext){caseKVM_CAP_IRQCHIP:r=vgic_present;
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:31
Previously machine type was used purely for specifying the physical
address size of the guest. Reserve the higher bits to specify an ARM
specific machine type and declare a new type 'KVM_VM_TYPE_ARM_REALM'
used to create a realm guest.
Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Reviewed-by: Gavin Shan <redacted>
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v9:
* Explictly set realm.state to REALM_STATE_NONE rather than rely on the
zeroing of the structure.
Changes since v7:
* Add some documentation explaining the new machine type.
Changes since v6:
* Make the check for kvm_rme_is_available more visible and report an
error code of -EPERM (instead of -EINVAL) to make it explicit that
the kernel supports RME, but the platform doesn't.
---
Documentation/virt/kvm/api.rst | 16 ++++++++++++++--
arch/arm64/kvm/arm.c | 16 ++++++++++++++++
arch/arm64/kvm/mmu.c | 3 ---
include/uapi/linux/kvm.h | 19 +++++++++++++++----
4 files changed, 45 insertions(+), 9 deletions(-)
@@ -181,8 +181,20 @@ flag KVM_VM_MIPS_VZ. ARM64: ^^^^^^-On arm64, the physical address size for a VM (IPA Size limit) is limited-to 40bits by default. The limit can be configured if the host supports the+On arm64, the machine type identifier is used to encode a type and the+physical address size for the VM. The lower byte (bits[7-0]) encode the+address size and the upper bits[11-8] encode a machine type. The machine+types that might be available are:++ ====================== ============================================+ KVM_VM_TYPE_ARM_NORMAL A standard VM+ KVM_VM_TYPE_ARM_REALM A "Realm" VM using the Arm Confidential+ Compute extensions, the VM's memory is+ protected from the host.+ ====================== ============================================++The physical address size for a VM (IPA Size limit) is limited to 40bits+by default. The limit can be configured if the host supports the extension KVM_CAP_ARM_VM_IPA_SIZE. When supported, use KVM_VM_TYPE_ARM_IPA_SIZE(IPA_Bits) to set the size in the machine type identifier, where IPA_Bits is the maximum width of any physical
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:35
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).
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Moved some code from earlier in the series to this one so that it's
added when it's first used.
Changes since v10:
* RME->RMI rename.
* Some code to handle freeing stage 2 PGD moved into this patch where
it belongs.
Changes since v9:
* Add a comment clarifying that root level RTTs are not destroyed until
after the RD is destroyed.
Changes since v8:
* Introduce free_rtt() wrapper which calls free_delegated_granule()
followed by kvm_account_pgtable_pages(). This makes it clear where an
RTT is being freed rather than just a delegated granule.
Changes since v6:
* Move rme_rtt_level_mapsize() and supporting defines from kvm_rme.h
into rme.c as they are only used in that file.
Changes since v5:
* Rename some RME_xxx defines to do with page sizes as RMM_xxx - they are
a property of the RMM specification not the RME architecture.
Changes since v2:
* Moved {alloc,free}_delegated_page() and ensure_spare_page() to a
later patch when they are actually used.
* Some simplifications now rmi_xxx() functions allow NULL as an output
parameter.
* Improved comments and code layout.
---
arch/arm64/include/asm/kvm_rmi.h | 7 ++
arch/arm64/kvm/mmu.c | 15 ++-
arch/arm64/kvm/rmi.c | 151 +++++++++++++++++++++++++++++++
3 files changed, 172 insertions(+), 1 deletion(-)
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:40
When a VCPU migrates to another physical CPU check if this is the first
time the guest has run, and if so activate the realm.
Before the realm can be activated it must first be created, this is a
stub in this patch and will be filled in by a later patch.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v12
---
arch/arm64/include/asm/kvm_rmi.h | 1 +
arch/arm64/kvm/arm.c | 6 +++++
arch/arm64/kvm/rmi.c | 42 ++++++++++++++++++++++++++++++++
3 files changed, 49 insertions(+)
@@ -223,6 +223,48 @@ void kvm_realm_destroy_rtts(struct kvm *kvm)WARN_ON(realm_tear_down_rtt_range(realm,0,(1UL<<ia_bits)));}+staticintrealm_ensure_created(structkvm*kvm)+{+/* Provided in later patch */+return-ENXIO;+}++intkvm_activate_realm(structkvm*kvm)+{+structrealm*realm=&kvm->arch.realm;+intret;++if(!kvm_is_realm(kvm))+return-ENXIO;++if(kvm_realm_state(kvm)==REALM_STATE_ACTIVE)+return0;++guard(mutex)(&kvm->arch.config_lock);+/* Check again with the lock held */+if(kvm_realm_state(kvm)==REALM_STATE_ACTIVE)+return0;++ret=realm_ensure_created(kvm);+if(ret)+returnret;++/* Mark state as dead in case we fail */+WRITE_ONCE(realm->state,REALM_STATE_DEAD);++if(!irqchip_in_kernel(kvm)){+/* Userspace irqchip not yet supported with realms */+return-EOPNOTSUPP;+}++ret=rmi_realm_activate(virt_to_phys(realm->rd));+if(ret)+return-ENXIO;++WRITE_ONCE(realm->state,REALM_STATE_ACTIVE);+return0;+}+voidkvm_destroy_realm(structkvm*kvm){structrealm*realm=&kvm->arch.realm;
From: Suzuki K Poulose <suzuki.poulose@arm.com> Date: 2025-12-17 14:29:52
On 17/12/2025 10:10, Steven Price wrote:
quoted hunk
When a VCPU migrates to another physical CPU check if this is the first
time the guest has run, and if so activate the realm.
Before the realm can be activated it must first be created, this is a
stub in this patch and will be filled in by a later patch.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v12
---
arch/arm64/include/asm/kvm_rmi.h | 1 +
arch/arm64/kvm/arm.c | 6 +++++
arch/arm64/kvm/rmi.c | 42 ++++++++++++++++++++++++++++++++
3 files changed, 49 insertions(+)
@@ -223,6 +223,48 @@ void kvm_realm_destroy_rtts(struct kvm *kvm)WARN_ON(realm_tear_down_rtt_range(realm,0,(1UL<<ia_bits)));}+staticintrealm_ensure_created(structkvm*kvm)+{+/* Provided in later patch */+return-ENXIO;+}++intkvm_activate_realm(structkvm*kvm)+{+structrealm*realm=&kvm->arch.realm;+intret;++if(!kvm_is_realm(kvm))+return-ENXIO;++if(kvm_realm_state(kvm)==REALM_STATE_ACTIVE)+return0;++guard(mutex)(&kvm->arch.config_lock);+/* Check again with the lock held */+if(kvm_realm_state(kvm)==REALM_STATE_ACTIVE)+return0;++ret=realm_ensure_created(kvm);+if(ret)+returnret;++/* Mark state as dead in case we fail */+WRITE_ONCE(realm->state,REALM_STATE_DEAD);++if(!irqchip_in_kernel(kvm)){+/* Userspace irqchip not yet supported with realms */+return-EOPNOTSUPP;+}
super minor nit: We could do this check before create the realm, within
the config_lock'ed region.
Suzuki
From: Marc Zyngier <maz@kernel.org> Date: 2026-03-02 14:40:57
On Wed, 17 Dec 2025 10:10:48 +0000,
Steven Price [off-list ref] wrote:
When a VCPU migrates to another physical CPU check
To another physical CPU?
That's not what kvm_arch_vcpu_run_pid_change() tracks. It really is
limited to a new PID being associated to the vpcu thread. Which is
indeed the case when the vpcu runs for the first time, but that's
about it.
If you need to track the physical CPU, we have some tracking for it in
vcpu_load(), but that'd need some rework.
quoted hunk
if this is the first
time the guest has run, and if so activate the realm.
Before the realm can be activated it must first be created, this is a
stub in this patch and will be filled in by a later patch.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v12
---
arch/arm64/include/asm/kvm_rmi.h | 1 +
arch/arm64/kvm/arm.c | 6 +++++
arch/arm64/kvm/rmi.c | 42 ++++++++++++++++++++++++++++++++
3 files changed, 49 insertions(+)
From: Steven Price <steven.price@arm.com> Date: 2026-03-02 16:31:08
On 02/03/2026 14:40, Marc Zyngier wrote:
On Wed, 17 Dec 2025 10:10:48 +0000,
Steven Price [off-list ref] wrote:
quoted
When a VCPU migrates to another physical CPU check
To another physical CPU?
That's not what kvm_arch_vcpu_run_pid_change() tracks. It really is
limited to a new PID being associated to the vpcu thread. Which is
indeed the case when the vpcu runs for the first time, but that's
about it.
If you need to track the physical CPU, we have some tracking for it in
vcpu_load(), but that'd need some rework.
Sorry that was just a complete brainfart when I wrote that commit
message. We don't care about physical CPUs - this is just catching the
first VCPU run. Thanks for catching that.
quoted
if this is the first
time the guest has run, and if so activate the realm.
Before the realm can be activated it must first be created, this is a
stub in this patch and will be filled in by a later patch.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v12
---
arch/arm64/include/asm/kvm_rmi.h | 1 +
arch/arm64/kvm/arm.c | 6 +++++
arch/arm64/kvm/rmi.c | 42 ++++++++++++++++++++++++++++++++
3 files changed, 49 insertions(+)
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:45
The RMM maintains a data structure known as the Realm Execution Context
(or REC). It is similar to struct kvm_vcpu and tracks the state of the
virtual CPUs. KVM must delegate memory and request the structures are
created when vCPUs are created, and suitably tear down on destruction.
RECs must also be supplied with addition pages - auxiliary (or AUX)
granules - for storing the larger registers state (e.g. for SVE). The
number of AUX granules for a REC depends on the parameters with which
the Realm was created - the RMM makes this information available via the
RMI_REC_AUX_COUNT call performed after creating the Realm Descriptor (RD).
Note that only some of register state for the REC can be set by KVM, the
rest is defined by the RMM (zeroed). The register state then cannot be
changed by KVM after the REC is created (except when the guest
explicitly requests this e.g. by performing a PSCI call).
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Remove the KVM_ARM_VCPU_REC feature. User space no longer needs to
configure each VCPU separately, RECs are created on the first VCPU
run of the guest.
Changes since v9:
* Size the aux_pages array according to the PAGE_SIZE of the host.
Changes since v7:
* Add comment explaining the aux_pages array.
* Rename "undeleted_failed" variable to "should_free" to avoid a
confusing double negative.
Changes since v6:
* Avoid reporting the KVM_ARM_VCPU_REC feature if the guest isn't a
realm guest.
* Support host page size being larger than RMM's granule size when
allocating/freeing aux granules.
Changes since v5:
* Separate the concept of vcpu_is_rec() and
kvm_arm_vcpu_rec_finalized() by using the KVM_ARM_VCPU_REC feature as
the indication that the VCPU is a REC.
Changes since v2:
* Free rec->run earlier in kvm_destroy_realm() and adapt to previous patches.
---
arch/arm64/include/asm/kvm_emulate.h | 2 +-
arch/arm64/include/asm/kvm_host.h | 3 +
arch/arm64/include/asm/kvm_rmi.h | 21 +++
arch/arm64/kvm/arm.c | 10 +-
arch/arm64/kvm/reset.c | 1 +
arch/arm64/kvm/rmi.c | 185 +++++++++++++++++++++++++++
6 files changed, 219 insertions(+), 3 deletions(-)
@@ -900,6 +900,9 @@ struct kvm_vcpu_arch {/* Per-vcpu TLB for VNCR_EL2 -- NULL when !NV */structvncr_tlb*vncr_tlb;++/* Realm meta data */+structrealm_recrec;};/*
@@ -529,6 +529,8 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)/* Force users to call KVM_ARM_VCPU_INIT */vcpu_clear_flag(vcpu,VCPU_INITIALIZED);+vcpu->arch.rec.mpidr=INVALID_HWID;+vcpu->arch.mmu_page_cache.gfp_zero=__GFP_ZERO;/* Set up the timer */
@@ -1540,7 +1542,7 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu,return-ENOENT;}-if(features&~system_supported_vcpu_features())+if(features&~system_supported_vcpu_features(vcpu->kvm))return-EINVAL;/*
@@ -1562,6 +1564,10 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu,if(test_bit(KVM_ARM_VCPU_HAS_EL2,&features))return-EINVAL;+/* Realms are incompatible with AArch32 */+if(vcpu_is_rec(vcpu))+return-EINVAL;+return0;}
@@ -229,9 +229,188 @@ static int realm_ensure_created(struct kvm *kvm)return-ENXIO;}+staticvoidfree_rec_aux(structpage**aux_pages,+unsignedintnum_aux)+{+unsignedinti,j;+unsignedintpage_count=0;++for(i=0;i<num_aux;){+structpage*aux_page=aux_pages[page_count++];+phys_addr_taux_page_phys=page_to_phys(aux_page);+boolshould_free=true;++for(j=0;j<PAGE_SIZE&&i<num_aux;j+=RMM_PAGE_SIZE){+if(WARN_ON(rmi_granule_undelegate(aux_page_phys)))+should_free=false;+aux_page_phys+=RMM_PAGE_SIZE;+i++;+}+/* Only free if all the undelegate calls were successful */+if(should_free)+__free_page(aux_page);+}+}++staticintalloc_rec_aux(structpage**aux_pages,+u64*aux_phys_pages,+unsignedintnum_aux)+{+structpage*aux_page;+intpage_count=0;+unsignedinti,j;+intret;++for(i=0;i<num_aux;){+phys_addr_taux_page_phys;++aux_page=alloc_page(GFP_KERNEL);+if(!aux_page){+ret=-ENOMEM;+gotoout_err;+}++aux_page_phys=page_to_phys(aux_page);+for(j=0;j<PAGE_SIZE&&i<num_aux;j+=RMM_PAGE_SIZE){+if(rmi_granule_delegate(aux_page_phys)){+ret=-ENXIO;+gotoerr_undelegate;+}+aux_phys_pages[i++]=aux_page_phys;+aux_page_phys+=RMM_PAGE_SIZE;+}+aux_pages[page_count++]=aux_page;+}++return0;+err_undelegate:+while(j>0){+j-=RMM_PAGE_SIZE;+i--;+if(WARN_ON(rmi_granule_undelegate(aux_phys_pages[i]))){+/* Leak the page if the undelegate fails */+gotoout_err;+}+}+__free_page(aux_page);+out_err:+free_rec_aux(aux_pages,i);+returnret;+}++staticintkvm_create_rec(structkvm_vcpu*vcpu)+{+structuser_pt_regs*vcpu_regs=vcpu_gp_regs(vcpu);+unsignedlongmpidr=kvm_vcpu_get_mpidr_aff(vcpu);+structrealm*realm=&vcpu->kvm->arch.realm;+structrealm_rec*rec=&vcpu->arch.rec;+unsignedlongrec_page_phys;+structrec_params*params;+intr,i;++if(rec->run)+return-EBUSY;++/*+*TheRMMwillreportPSCIv1.0toRealmsandtheKVM_ARM_VCPU_PSCI_0_2+*flagcoversv0.2andonwards.+*/+if(!vcpu_has_feature(vcpu,KVM_ARM_VCPU_PSCI_0_2))+return-EINVAL;++BUILD_BUG_ON(sizeof(*params)>PAGE_SIZE);+BUILD_BUG_ON(sizeof(*rec->run)>PAGE_SIZE);++params=(structrec_params*)get_zeroed_page(GFP_KERNEL);+rec->rec_page=(void*)__get_free_page(GFP_KERNEL);+rec->run=(void*)get_zeroed_page(GFP_KERNEL);+if(!params||!rec->rec_page||!rec->run){+r=-ENOMEM;+gotoout_free_pages;+}++for(i=0;i<ARRAY_SIZE(params->gprs);i++)+params->gprs[i]=vcpu_regs->regs[i];++params->pc=vcpu_regs->pc;++if(vcpu->vcpu_id==0)+params->flags|=REC_PARAMS_FLAG_RUNNABLE;++rec_page_phys=virt_to_phys(rec->rec_page);++if(rmi_granule_delegate(rec_page_phys)){+r=-ENXIO;+gotoout_free_pages;+}++r=alloc_rec_aux(rec->aux_pages,params->aux,realm->num_aux);+if(r)+gotoout_undelegate_rmm_rec;++params->num_rec_aux=realm->num_aux;+params->mpidr=mpidr;++if(rmi_rec_create(virt_to_phys(realm->rd),+rec_page_phys,+virt_to_phys(params))){+r=-ENXIO;+gotoout_free_rec_aux;+}++rec->mpidr=mpidr;++free_page((unsignedlong)params);+return0;++out_free_rec_aux:+free_rec_aux(rec->aux_pages,realm->num_aux);+out_undelegate_rmm_rec:+if(WARN_ON(rmi_granule_undelegate(rec_page_phys)))+rec->rec_page=NULL;+out_free_pages:+free_page((unsignedlong)rec->run);+free_page((unsignedlong)rec->rec_page);+free_page((unsignedlong)params);+returnr;+}++voidkvm_destroy_rec(structkvm_vcpu*vcpu)+{+structrealm*realm=&vcpu->kvm->arch.realm;+structrealm_rec*rec=&vcpu->arch.rec;+unsignedlongrec_page_phys;++if(!vcpu_is_rec(vcpu))+return;++if(!rec->run){+/* Nothing to do if the VCPU hasn't been finalized */+return;+}++free_page((unsignedlong)rec->run);++rec_page_phys=virt_to_phys(rec->rec_page);++/*+*TheRECandanyAUXpagescannotbereclaimeduntiltheRECis+*destroyed.SoiftheRECdestroyfailsthentheRECpageandanyAUX+*pageswillbeleaked.+*/+if(WARN_ON(rmi_rec_destroy(rec_page_phys)))+return;++free_rec_aux(rec->aux_pages,realm->num_aux);++free_delegated_granule(rec_page_phys);+}+intkvm_activate_realm(structkvm*kvm){structrealm*realm=&kvm->arch.realm;+structkvm_vcpu*vcpu;+unsignedlongi;intret;if(!kvm_is_realm(kvm))
@@ -257,6 +436,12 @@ int kvm_activate_realm(struct kvm *kvm)return-EOPNOTSUPP;}+kvm_for_each_vcpu(i,vcpu,kvm){+ret=kvm_create_rec(vcpu);+if(ret)+returnret;+}+ret=rmi_realm_activate(virt_to_phys(realm->rd));if(ret)return-ENXIO;
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:50
Currently the number of list registers available is stored in a global
(kvm_vgic_global_state.nr_lr). With Arm CCA the RMM is permitted to
reserve list registers for its own use and so the number of available
list registers can be fewer for a realm VM. Provide wrapper functions
to fetch the global in preparation for restricting nr_lr when dealing
with a realm VM.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes in v12:
* Upstream changes mean that vcpu isn't available everywhere we need
it, so update helpers to take vcpu.
* Note that the VGIC handling will be reworked for the RMM 2.0 spec.
New patch for v6
---
arch/arm64/kvm/vgic/vgic-v2.c | 6 +++---
arch/arm64/kvm/vgic/vgic-v3.c | 8 ++++----
arch/arm64/kvm/vgic/vgic.c | 6 +++---
arch/arm64/kvm/vgic/vgic.h | 18 ++++++++++++------
4 files changed, 22 insertions(+), 16 deletions(-)
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:12:55
The RMM provides emulation of a VGIC to the realm guest but delegates
much of the handling to the host. Implement support in KVM for
saving/restoring state to/from the REC structure.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes from v11:
* Minor changes to align with the previous patches. Note that the VGIC
handling will change with RMM v2.0.
Changes from v10:
* Make sure we sync the VGIC v4 state, and only populate valid lrs from
the list.
Changes from v9:
* Copy gicv3_vmcr from the RMM at the same time as gicv3_hcr rather
than having to handle that as a special case.
Changes from v8:
* Propagate gicv3_hcr to from the RMM.
Changes from v5:
* Handle RMM providing fewer GIC LRs than the hardware supports.
---
arch/arm64/include/asm/kvm_rmi.h | 1 +
arch/arm64/kvm/arm.c | 11 +++++--
arch/arm64/kvm/rmi.c | 5 ++++
arch/arm64/kvm/vgic/vgic-init.c | 2 +-
arch/arm64/kvm/vgic/vgic-v3.c | 6 ++--
arch/arm64/kvm/vgic/vgic.c | 49 +++++++++++++++++++++++++++++---
arch/arm64/kvm/vgic/vgic.h | 2 ++
7 files changed, 65 insertions(+), 11 deletions(-)
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:13:00
The RMM keeps track of the timer while the realm REC is running, but on
exit to the normal world KVM is responsible for handling the timers.
A later patch adds the support for propagating the timer values from the
exit data structure and calling kvm_realm_timers_update().
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Drop the kvm_is_realm() check from timer_set_offset(). We already
ensure that the offset is 0 when calling the function.
Changes since v10:
* KVM_CAP_COUNTER_OFFSET is now already hidden by a previous patch.
Changes since v9:
* No need to move the call to kvm_timer_unblocking() in
kvm_timer_vcpu_load().
Changes since v7:
* Hide KVM_CAP_COUNTER_OFFSET for realm guests.
---
arch/arm64/kvm/arch_timer.c | 37 ++++++++++++++++++++++++++++++++++--
include/kvm/arm_arch_timer.h | 2 ++
2 files changed, 37 insertions(+), 2 deletions(-)
@@ -453,6 +453,21 @@ static void kvm_timer_update_irq(struct kvm_vcpu *vcpu, bool new_level,timer_ctx);}+voidkvm_realm_timers_update(structkvm_vcpu*vcpu)+{+structarch_timer_cpu*arch_timer=&vcpu->arch.timer_cpu;+inti;++for(i=0;i<NR_KVM_EL0_TIMERS;i++){+structarch_timer_context*timer=&arch_timer->timers[i];+boolstatus=timer_get_ctl(timer)&ARCH_TIMER_CTRL_IT_STAT;+boollevel=kvm_timer_irq_can_fire(timer)&&status;++if(level!=timer->irq.level)+kvm_timer_update_irq(vcpu,level,timer);+}+}+/* Only called for a fully emulated timer */staticvoidtimer_emulate(structarch_timer_context*ctx){
@@ -1078,13 +1095,19 @@ static void timer_context_init(struct kvm_vcpu *vcpu, int timerid)voidkvm_timer_vcpu_init(structkvm_vcpu*vcpu){structarch_timer_cpu*timer=vcpu_timer(vcpu);+u64cntvoff;for(inti=0;i<NR_KVM_TIMERS;i++)timer_context_init(vcpu,i);+if(kvm_is_realm(vcpu->kvm))+cntvoff=0;+else+cntvoff=kvm_phys_timer_read();+/* Synchronize offsets across timers of a VM if not already provided */if(!test_bit(KVM_ARCH_FLAG_VM_COUNTER_OFFSET,&vcpu->kvm->arch.flags)){-timer_set_offset(vcpu_vtimer(vcpu),kvm_phys_timer_read());+timer_set_offset(vcpu_vtimer(vcpu),cntvoff);timer_set_offset(vcpu_ptimer(vcpu),0);}
@@ -1556,6 +1579,13 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)return-EINVAL;}+/*+*Wedon'tusemappedIRQsforRealmsbecausetheRMIdoesn'tallow+*ussettingtheLR.HWbitintheVGIC.+*/+if(vcpu_is_rec(vcpu))+return0;+get_timer_map(vcpu,&map);ret=kvm_vgic_map_phys_irq(vcpu,
@@ -1687,6 +1717,9 @@ int kvm_vm_ioctl_set_counter_offset(struct kvm *kvm,if(offset->reserved)return-EINVAL;+if(kvm_is_realm(kvm))+return-EINVAL;+mutex_lock(&kvm->lock);if(!kvm_trylock_all_vcpus(kvm)){
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:13:05
Entering a realm is done using a SMC call to the RMM. On exit the
exit-codes need to be handled slightly differently to the normal KVM
path so define our own functions for realm enter/exit and hook them
in if the guest is a realm guest.
Signed-off-by: Steven Price <steven.price@arm.com>
Reviewed-by: Gavin Shan <redacted>
---
Changes since v8:
* Introduce kvm_rec_pre_enter() called before entering an atomic
section to handle operations that might require memory allocation
(specifically completing a RIPAS change introduced in a later patch).
* Updates to align with upstream changes to hpfar_el2 which now (ab)uses
HPFAR_EL2_NS as a valid flag.
* Fix exit reason when racing with PSCI shutdown to return
KVM_EXIT_SHUTDOWN rather than KVM_EXIT_UNKNOWN.
Changes since v7:
* A return of 0 from kvm_handle_sys_reg() doesn't mean the register has
been read (although that can never happen in the current code). Tidy
up the condition to handle any future refactoring.
Changes since v6:
* Use vcpu_err() rather than pr_err/kvm_err when there is an associated
vcpu to the error.
* Return -EFAULT for KVM_EXIT_MEMORY_FAULT as per the documentation for
this exit type.
* Split code handling a RIPAS change triggered by the guest to the
following patch.
Changes since v5:
* For a RIPAS_CHANGE request from the guest perform the actual RIPAS
change on next entry rather than immediately on the exit. This allows
the VMM to 'reject' a RIPAS change by refusing to continue
scheduling.
Changes since v4:
* Rename handle_rme_exit() to handle_rec_exit()
* Move the loop to copy registers into the REC enter structure from the
to rec_exit_handlers callbacks to kvm_rec_enter(). This fixes a bug
where the handler exits to user space and user space wants to modify
the GPRS.
* Some code rearrangement in rec_exit_ripas_change().
Changes since v2:
* realm_set_ipa_state() now provides an output parameter for the
top_iap that was changed. Use this to signal the VMM with the correct
range that has been transitioned.
* Adapt to previous patch changes.
---
arch/arm64/include/asm/kvm_rmi.h | 4 +
arch/arm64/kvm/Makefile | 2 +-
arch/arm64/kvm/arm.c | 22 +++-
arch/arm64/kvm/rmi-exit.c | 178 +++++++++++++++++++++++++++++++
arch/arm64/kvm/rmi.c | 38 +++++++
5 files changed, 239 insertions(+), 5 deletions(-)
create mode 100644 arch/arm64/kvm/rmi-exit.c
@@ -1264,6 +1264,9 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)if(ret>0)ret=check_vcpu_requests(vcpu);+if(ret>0&&vcpu_is_rec(vcpu))+ret=kvm_rec_pre_enter(vcpu);+/**Preparingtheinterruptstobeinjectedalso*involvespokingtheGIC,whichmustbedoneina
@@ -1311,7 +1314,10 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)trace_kvm_entry(*vcpu_pc(vcpu));guest_timing_enter_irqoff();-ret=kvm_arm_vcpu_enter_exit(vcpu);+if(vcpu_is_rec(vcpu))+ret=kvm_rec_enter(vcpu);+else+ret=kvm_arm_vcpu_enter_exit(vcpu);vcpu->mode=OUTSIDE_GUEST_MODE;vcpu->stat.exits++;
@@ -1369,8 +1375,13 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)trace_kvm_exit(ret,kvm_vcpu_trap_get_class(vcpu),*vcpu_pc(vcpu));-/* Exit types that need handling before we can be preempted */-handle_exit_early(vcpu,ret);+if(!vcpu_is_rec(vcpu)){+/*+*Exittypesthatneedhandlingbeforewecanbe+*preempted+*/+handle_exit_early(vcpu,ret);+}kvm_nested_sync_hwstate(vcpu);
@@ -1395,7 +1406,10 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)ret=ARM_EXCEPTION_IL;}-ret=handle_exit(vcpu,ret);+if(vcpu_is_rec(vcpu))+ret=handle_rec_exit(vcpu,ret);+else+ret=handle_exit(vcpu,ret);}/* Tell userspace about in-kernel device output levels */
@@ -0,0 +1,178 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*Copyright(C)2023ARMLtd.+*/++#include<linux/kvm_host.h>+#include<kvm/arm_hypercalls.h>+#include<kvm/arm_psci.h>++#include<asm/rmi_smc.h>+#include<asm/kvm_emulate.h>+#include<asm/kvm_rmi.h>+#include<asm/kvm_mmu.h>++typedefint(*exit_handler_fn)(structkvm_vcpu*vcpu);++staticintrec_exit_reason_notimpl(structkvm_vcpu*vcpu)+{+structrealm_rec*rec=&vcpu->arch.rec;++vcpu_err(vcpu,"Unhandled exit reason from realm (ESR: %#llx)\n",+rec->run->exit.esr);+return-ENXIO;+}++staticintrec_exit_sync_dabt(structkvm_vcpu*vcpu)+{+returnkvm_handle_guest_abort(vcpu);+}++staticintrec_exit_sync_iabt(structkvm_vcpu*vcpu)+{+structrealm_rec*rec=&vcpu->arch.rec;++vcpu_err(vcpu,"Unhandled instruction abort (ESR: %#llx).\n",+rec->run->exit.esr);+return-ENXIO;+}++staticintrec_exit_sys_reg(structkvm_vcpu*vcpu)+{+structrealm_rec*rec=&vcpu->arch.rec;+unsignedlongesr=kvm_vcpu_get_esr(vcpu);+intrt=kvm_vcpu_sys_get_rt(vcpu);+boolis_write=!(esr&1);+intret;++if(is_write)+vcpu_set_reg(vcpu,rt,rec->run->exit.gprs[0]);++ret=kvm_handle_sys_reg(vcpu);+if(!is_write)+rec->run->enter.gprs[0]=vcpu_get_reg(vcpu,rt);++returnret;+}++staticexit_handler_fnrec_exit_handlers[]={+[0...ESR_ELx_EC_MAX]=rec_exit_reason_notimpl,+[ESR_ELx_EC_SYS64]=rec_exit_sys_reg,+[ESR_ELx_EC_DABT_LOW]=rec_exit_sync_dabt,+[ESR_ELx_EC_IABT_LOW]=rec_exit_sync_iabt+};++staticintrec_exit_psci(structkvm_vcpu*vcpu)+{+structrealm_rec*rec=&vcpu->arch.rec;+inti;++for(i=0;i<REC_RUN_GPRS;i++)+vcpu_set_reg(vcpu,i,rec->run->exit.gprs[i]);++returnkvm_smccc_call_handler(vcpu);+}++staticintrec_exit_ripas_change(structkvm_vcpu*vcpu)+{+structkvm*kvm=vcpu->kvm;+structrealm*realm=&kvm->arch.realm;+structrealm_rec*rec=&vcpu->arch.rec;+unsignedlongbase=rec->run->exit.ripas_base;+unsignedlongtop=rec->run->exit.ripas_top;+unsignedlongripas=rec->run->exit.ripas_value;++if(!kvm_realm_is_private_address(realm,base)||+!kvm_realm_is_private_address(realm,top-1)){+vcpu_err(vcpu,"Invalid RIPAS_CHANGE for %#lx - %#lx, ripas: %#lx\n",+base,top,ripas);+/* Set RMI_REJECT bit */+rec->run->enter.flags=REC_ENTER_FLAG_RIPAS_RESPONSE;+return-EINVAL;+}++/* Exit to VMM, the actual RIPAS change is done on next entry */+kvm_prepare_memory_fault_exit(vcpu,base,top-base,false,false,+ripas==RMI_RAM);++/*+*KVM_EXIT_MEMORY_FAULTrequiresanreturncodeof-EFAULT,seethe+*APIdocumentation+*/+return-EFAULT;+}++staticvoidupdate_arch_timer_irq_lines(structkvm_vcpu*vcpu)+{+structrealm_rec*rec=&vcpu->arch.rec;++__vcpu_assign_sys_reg(vcpu,CNTV_CTL_EL0,rec->run->exit.cntv_ctl);+__vcpu_assign_sys_reg(vcpu,CNTV_CVAL_EL0,rec->run->exit.cntv_cval);+__vcpu_assign_sys_reg(vcpu,CNTP_CTL_EL0,rec->run->exit.cntp_ctl);+__vcpu_assign_sys_reg(vcpu,CNTP_CVAL_EL0,rec->run->exit.cntp_cval);++kvm_realm_timers_update(vcpu);+}++/*+*Return>0toreturntoguest,<0onerror,0(andsetexit_reason)on+*properexittouserspace.+*/+inthandle_rec_exit(structkvm_vcpu*vcpu,intrec_run_ret)+{+structrealm_rec*rec=&vcpu->arch.rec;+u8esr_ec=ESR_ELx_EC(rec->run->exit.esr);+unsignedlongstatus,index;++status=RMI_RETURN_STATUS(rec_run_ret);+index=RMI_RETURN_INDEX(rec_run_ret);++/*+*IfaPSCI_SYSTEM_OFFrequestracedwithavcpuexecuting,wemight+*seethefollowingstatuscodeandindexindicatinganattempttorun+*aRECwhentheRDstateisSYSTEM_OFF.Inthiscase,wejustneedto+*returntouserspacewhichcandealwiththesystemeventorwilltry+*toruntheKVMVCPUagain,atwhichpointwewillnolongerattempt+*toentertheRealmbecausewewillhaveasleeprequestpendingon+*theVCPUasaresultofKVM'sPSCIhandling.+*/+if(status==RMI_ERROR_REALM&&index==1){+vcpu->run->exit_reason=KVM_EXIT_SHUTDOWN;+return0;+}++if(rec_run_ret)+return-ENXIO;++vcpu->arch.fault.esr_el2=rec->run->exit.esr;+vcpu->arch.fault.far_el2=rec->run->exit.far;+/* HPFAR_EL2 is only valid for RMI_EXIT_SYNC */+vcpu->arch.fault.hpfar_el2=0;++update_arch_timer_irq_lines(vcpu);++/* Reset the emulation flags for the next run of the REC */+rec->run->enter.flags=0;++switch(rec->run->exit.exit_reason){+caseRMI_EXIT_SYNC:+/*+*HPFAR_EL2_NSishijackedtoindicateavalidHPFARvalue,+*see__get_fault_info()+*/+vcpu->arch.fault.hpfar_el2=rec->run->exit.hpfar|HPFAR_EL2_NS;+returnrec_exit_handlers[esr_ec](vcpu);+caseRMI_EXIT_IRQ:+caseRMI_EXIT_FIQ:+return1;+caseRMI_EXIT_PSCI:+returnrec_exit_psci(vcpu);+caseRMI_EXIT_RIPAS_CHANGE:+returnrec_exit_ripas_change(vcpu);+}++kvm_pr_unimpl("Unsupported exit reason: %u\n",+rec->run->exit.exit_reason);+vcpu->run->exit_reason=KVM_EXIT_INTERNAL_ERROR;+return0;+}
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:13:09
The guest can request that a region of it's protected address space is
switched between RIPAS_RAM and RIPAS_EMPTY (and back) using
RSI_IPA_STATE_SET. This causes a guest exit with the
RMI_EXIT_RIPAS_CHANGE code. We treat this as a request to convert a
protected region to unprotected (or back), exiting to the VMM to make
the necessary changes to the guest_memfd and memslot mappings. On the
next entry the RIPAS changes are committed by making RMI_RTT_SET_RIPAS
calls.
The VMM may wish to reject the RIPAS change requested by the guest. For
now it can only do with by no longer scheduling the VCPU as we don't
currently have a usecase for returning that rejection to the guest, but
by postponing the RMI_RTT_SET_RIPAS changes to entry we leave the door
open for adding a new ioctl in the future for this purpose.
There's a FIXME for the case where the RMM rejects a RIPAS change when
(a portion of) the region. The current RMM API makes this difficult to
handle efficiently, but it should be fixed in a later version of the
spec.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* Combine the "Allow VMM to set RIPAS" patch into this one to avoid
adding functions before they are used.
* Drop the CAP for setting RIPAS and adapt to changes from previous
patches.
Changes since v10:
* Add comment explaining the assignment of rec->run->exit.ripas_base in
kvm_complete_ripas_change().
Changes since v8:
* Make use of ripas_change() from a previous patch to implement
realm_set_ipa_state().
* Update exit.ripas_base after a RIPAS change so that, if instead of
entering the guest we exit to user space, we don't attempt to repeat
the RIPAS change (triggering an error from the RMM).
Changes since v7:
* Rework the loop in realm_set_ipa_state() to make it clear when the
'next' output value of rmi_rtt_set_ripas() is used.
New patch for v7: The code was previously split awkwardly between two
other patches.
---
arch/arm64/include/asm/kvm_rmi.h | 6 +
arch/arm64/kvm/mmu.c | 8 +-
arch/arm64/kvm/rmi.c | 459 +++++++++++++++++++++++++++++++
3 files changed, 470 insertions(+), 3 deletions(-)
@@ -126,6 +205,38 @@ static int realm_rtt_destroy(struct realm *realm, unsigned long addr,returnret;}+staticintrealm_create_rtt_levels(structrealm*realm,+unsignedlongipa,+intlevel,+intmax_level,+structkvm_mmu_memory_cache*mc)+{+while(level++<max_level){+phys_addr_trtt=alloc_rtt(mc);+intret;++if(rtt==PHYS_ADDR_MAX)+return-ENOMEM;++ret=realm_rtt_create(realm,ipa,level,rtt);+if(RMI_RETURN_STATUS(ret)==RMI_ERROR_RTT&&+RMI_RETURN_INDEX(ret)==level-1){+/* The RTT already exists, continue */+free_rtt(rtt);+continue;+}++if(ret){+WARN(1,"Failed to create RTT at level %d: %d\n",+level,ret);+free_rtt(rtt);+return-ENXIO;+}+}++return0;+}+staticintrealm_tear_down_rtt_level(structrealm*realm,intlevel,unsignedlongstart,unsignedlongend){
@@ -220,6 +331,62 @@ static int realm_tear_down_rtt_range(struct realm *realm,start,end);}+/*+*Returns0onsuccessfulfold,anegativevalueonerror,apositivevalueif+*wewerenotabletofoldalltablesatthislevel.+*/+staticintrealm_fold_rtt_level(structrealm*realm,intlevel,+unsignedlongstart,unsignedlongend)+{+intnot_folded=0;+ssize_tmap_size;+unsignedlongaddr,next_addr;++if(WARN_ON(level>RMM_RTT_MAX_LEVEL))+return-EINVAL;++map_size=rmi_rtt_level_mapsize(level-1);++for(addr=start;addr<end;addr=next_addr){+phys_addr_trtt_granule;+intret;+unsignedlongalign_addr=ALIGN(addr,map_size);++next_addr=ALIGN(addr+1,map_size);++ret=realm_rtt_fold(realm,align_addr,level,&rtt_granule);++switch(RMI_RETURN_STATUS(ret)){+caseRMI_SUCCESS:+free_rtt(rtt_granule);+break;+caseRMI_ERROR_RTT:+if(level==RMM_RTT_MAX_LEVEL||+RMI_RETURN_INDEX(ret)<level){+not_folded++;+break;+}+/* Recurse a level deeper */+ret=realm_fold_rtt_level(realm,+level+1,+addr,+next_addr);+if(ret<0){+returnret;+}elseif(ret==0){+/* Try again at this level */+next_addr=addr;+}+break;+default:+WARN_ON(1);+return-ENXIO;+}+}++returnnot_folded;+}+voidkvm_realm_destroy_rtts(structkvm*kvm){structrealm*realm=&kvm->arch.realm;
@@ -228,12 +395,301 @@ void kvm_realm_destroy_rtts(struct kvm *kvm)WARN_ON(realm_tear_down_rtt_range(realm,0,(1UL<<ia_bits)));}+staticintrealm_destroy_private_granule(structrealm*realm,+unsignedlongipa,+unsignedlong*next_addr)+{+unsignedlongrd=virt_to_phys(realm->rd);+unsignedlongphy_addr;+phys_addr_trtt;+intret;++retry:+ret=rmi_data_destroy(rd,ipa,&phy_addr,next_addr);+if(RMI_RETURN_STATUS(ret)==RMI_ERROR_RTT){+if(*next_addr>ipa)+return0;/* UNASSIGNED */+rtt=alloc_rtt(NULL);+if(WARN_ON(rtt==PHYS_ADDR_MAX))+return-ENOMEM;+/*+*ASSIGNED-ipaismappedasablock,sosplit.Theindex+*fromthereturncodeshouldbe2otherwiseitappears+*there'sahugepagebiggerthanKVMcurrentlysupports+*/+WARN_ON(RMI_RETURN_INDEX(ret)!=2);+ret=realm_rtt_create(realm,ipa,3,rtt);+if(WARN_ON(ret)){+free_rtt(rtt);+return-ENXIO;+}+gotoretry;+}elseif(WARN_ON(ret)){+return-ENXIO;+}++ret=rmi_granule_undelegate(phy_addr);+if(WARN_ON(ret))+return-ENXIO;++return0;+}++staticvoidrealm_unmap_shared_range(structkvm*kvm,+intlevel,+unsignedlongstart,+unsignedlongend,+boolmay_block)+{+structrealm*realm=&kvm->arch.realm;+unsignedlongrd=virt_to_phys(realm->rd);+ssize_tmap_size=rmi_rtt_level_mapsize(level);+unsignedlongnext_addr,addr;+unsignedlongshared_bit=BIT(realm->ia_bits-1);++if(WARN_ON(level>RMM_RTT_MAX_LEVEL))+return;++start|=shared_bit;+end|=shared_bit;++for(addr=start;addr<end;addr=next_addr){+unsignedlongalign_addr=ALIGN(addr,map_size);+intret;++next_addr=ALIGN(addr+1,map_size);++if(align_addr!=addr||next_addr>end){+/* Need to recurse deeper */+if(addr<align_addr)+next_addr=align_addr;+realm_unmap_shared_range(kvm,level+1,addr,+min(next_addr,end),+may_block);+continue;+}++ret=rmi_rtt_unmap_unprotected(rd,addr,level,&next_addr);+switch(RMI_RETURN_STATUS(ret)){+caseRMI_SUCCESS:+break;+caseRMI_ERROR_RTT:+if(next_addr==addr){+/*+*There'samappinghere,butit'snotablock+*mapping,soresetnext_addrtothenextblock+*boundaryandrecursetoclearoutthepages+*oneleveldeeper.+*/+next_addr=ALIGN(addr+1,map_size);+realm_unmap_shared_range(kvm,level+1,addr,+next_addr,+may_block);+}+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);+}++staticintrealm_unmap_private_page(structrealm*realm,+unsignedlongipa,+unsignedlong*next_addr)+{+unsignedlongend=ALIGN(ipa+1,PAGE_SIZE);+unsignedlongaddr;+intret;++for(addr=ipa;addr<end;addr=*next_addr){+ret=realm_destroy_private_granule(realm,addr,next_addr);+if(ret)+returnret;+}++return0;+}++staticvoidrealm_unmap_private_range(structkvm*kvm,+unsignedlongstart,+unsignedlongend,+boolmay_block)+{+structrealm*realm=&kvm->arch.realm;+unsignedlongnext_addr,addr;+intret;++for(addr=start;addr<end;addr=next_addr){+ret=realm_unmap_private_page(realm,addr,&next_addr);++if(ret)+break;++if(may_block)+cond_resched_rwlock_write(&kvm->mmu_lock);+}++realm_fold_rtt_level(realm,get_start_level(realm)+1,+start,end);+}++voidkvm_realm_unmap_range(structkvm*kvm,unsignedlongstart,+unsignedlongsize,boolunmap_private,+boolmay_block)+{+unsignedlongend=start+size;+structrealm*realm=&kvm->arch.realm;++end=min(BIT(realm->ia_bits-1),end);++if(!kvm_realm_is_created(kvm))+return;++realm_unmap_shared_range(kvm,find_map_level(realm,start,end),+start,end,may_block);+if(unmap_private)+realm_unmap_private_range(kvm,start,end,may_block);+}++enumripas_action{+RIPAS_INIT,+RIPAS_SET,+};++staticintripas_change(structkvm*kvm,+structkvm_vcpu*vcpu,+unsignedlongipa,+unsignedlongend,+enumripas_actionaction,+unsignedlong*top_ipa)+{+structrealm*realm=&kvm->arch.realm;+phys_addr_trd_phys=virt_to_phys(realm->rd);+phys_addr_trec_phys;+structkvm_mmu_memory_cache*memcache=NULL;+intret=0;++if(vcpu){+rec_phys=virt_to_phys(vcpu->arch.rec.rec_page);+memcache=&vcpu->arch.mmu_page_cache;++WARN_ON(action!=RIPAS_SET);+}else{+WARN_ON(action!=RIPAS_INIT);+}++while(ipa<end){+unsignedlongnext=~0;++switch(action){+caseRIPAS_INIT:+ret=rmi_rtt_init_ripas(rd_phys,ipa,end,&next);+break;+caseRIPAS_SET:+ret=rmi_rtt_set_ripas(rd_phys,rec_phys,ipa,end,+&next);+break;+}++switch(RMI_RETURN_STATUS(ret)){+caseRMI_SUCCESS:+ipa=next;+break;+caseRMI_ERROR_RTT:{+interr_level=RMI_RETURN_INDEX(ret);+intlevel=find_map_level(realm,ipa,end);++if(err_level>=level){+/* FIXME: Ugly hack to skip regions which are+*alreadyRIPAS_RAM+*/+ipa+=PAGE_SIZE;+break;+return-EINVAL;+}++ret=realm_create_rtt_levels(realm,ipa,err_level,+level,memcache);+if(ret)+returnret;+/* Retry with the RTT levels in place */+break;+}+default:+WARN_ON(1);+return-ENXIO;+}+}++if(top_ipa)+*top_ipa=ipa;++return0;+}++staticintrealm_set_ipa_state(structkvm_vcpu*vcpu,+unsignedlongstart,+unsignedlongend,+unsignedlongripas,+unsignedlong*top_ipa)+{+structkvm*kvm=vcpu->kvm;+intret=ripas_change(kvm,vcpu,start,end,RIPAS_SET,top_ipa);++if(ripas==RMI_EMPTY&&*top_ipa!=start)+realm_unmap_private_range(kvm,start,*top_ipa,false);++returnret;+}+staticintrealm_ensure_created(structkvm*kvm){/* Provided in later patch */return-ENXIO;}+staticvoidkvm_complete_ripas_change(structkvm_vcpu*vcpu)+{+structkvm*kvm=vcpu->kvm;+structrealm_rec*rec=&vcpu->arch.rec;+unsignedlongbase=rec->run->exit.ripas_base;+unsignedlongtop=rec->run->exit.ripas_top;+unsignedlongripas=rec->run->exit.ripas_value;+unsignedlongtop_ipa;+intret;++do{+kvm_mmu_topup_memory_cache(&vcpu->arch.mmu_page_cache,+kvm_mmu_cache_min_pages(vcpu->arch.hw_mmu));+write_lock(&kvm->mmu_lock);+ret=realm_set_ipa_state(vcpu,base,top,ripas,&top_ipa);+write_unlock(&kvm->mmu_lock);++if(WARN_RATELIMIT(ret&&ret!=-ENOMEM,+"Unable to satisfy RIPAS_CHANGE for %#lx - %#lx, ripas: %#lx\n",+base,top,ripas))+break;++base=top_ipa;+}while(base<top);++/*+*IfthisfunctioniscalledagainbeforetheREC_ENTERcallthen+*avoidcallingrealm_set_ipa_state()againbychangingtothevalue+*ofripas_baseforthepartthathasalreadybeencovered.TheRMM+*ignoresthecontainsoftherec_exitstructuresothisdoesn't+*affecttheRMM.+*/+rec->run->exit.ripas_base=base;+}+/**kvm_rec_pre_enter-CompleteoperationsbeforeenteringaREC*
@@ -259,6 +715,9 @@ int kvm_rec_pre_enter(struct kvm_vcpu *vcpu)for(inti=0;i<REC_RUN_GPRS;i++)rec->run->enter.gprs[i]=vcpu_get_reg(vcpu,i);break;+caseRMI_EXIT_RIPAS_CHANGE:+kvm_complete_ripas_change(vcpu);+break;}return1;
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:13:14
MMIO emulation for a realm cannot be done directly with the VM's
registers as they are protected from the host. However, for emulatable
data aborts, the RMM uses GPRS[0] to provide the read/written value.
We can transfer this from/to the equivalent VCPU's register entry and
then depend on the generic MMIO handling code in KVM.
For a MMIO read, the value is placed in the shared RecExit structure
during kvm_handle_mmio_return() rather than in the VCPU's register
entry.
Signed-off-by: Steven Price <steven.price@arm.com>
Reviewed-by: Gavin Shan <redacted>
Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
---
Changes since v7:
* New comment for rec_exit_sync_dabt() explaining the call to
vcpu_set_reg().
Changes since v5:
* Inject SEA to the guest is an emulatable MMIO access triggers a data
abort.
* kvm_handle_mmio_return() - disable kvm_incr_pc() for a REC (as the PC
isn't under the host's control) and move the REC_ENTER_EMULATED_MMIO
flag setting to this location (as that tells the RMM to skip the
instruction).
---
arch/arm64/kvm/inject_fault.c | 4 +++-
arch/arm64/kvm/mmio.c | 16 ++++++++++++----
arch/arm64/kvm/rmi-exit.c | 14 ++++++++++++++
3 files changed, 29 insertions(+), 5 deletions(-)
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:13:19
Select KVM_GENERIC_MEMORY_ATTRIBUTES and provide the necessary support
functions.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v10:
* KVM_GENERIC_PRIVATE_MEM replacd with KVM_GENERIC_MEMORY_ATTRIBUTES.
Changes since v9:
* Drop the #ifdef CONFIG_KVM_PRIVATE_MEM guard from the definition of
kvm_arch_has_private_mem()
Changes since v2:
* Switch kvm_arch_has_private_mem() to a macro to avoid overhead of a
function call.
* Guard definitions of kvm_arch_{pre,post}_set_memory_attributes() with
#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES.
* Early out in kvm_arch_post_set_memory_attributes() if the WARN_ON
should trigger.
---
arch/arm64/include/asm/kvm_host.h | 2 ++
arch/arm64/kvm/Kconfig | 1 +
arch/arm64/kvm/mmu.c | 24 ++++++++++++++++++++++++
3 files changed, 27 insertions(+)
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:13:24
The VMM needs to populate the realm with some data before starting (e.g.
a kernel and initrd). This is measured by the RMM and used as part of
the attestation later on.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* The multiplex CAP is gone and there's a new ioctl which makes use of
the generic kvm_gmem_populate() functionality.
Changes since v7:
* Improve the error codes.
* Other minor changes from review.
Changes since v6:
* Handle host potentially having a larger page size than the RMM
granule.
* Drop historic "par" (protected address range) from
populate_par_region() - it doesn't exist within the current
architecture.
* Add a cond_resched() call in kvm_populate_realm().
Changes since v5:
* Refactor to use PFNs rather than tracking struct page in
realm_create_protected_data_page().
* Pull changes from a later patch (in the v5 series) for accessing
pages from a guest memfd.
* Do the populate in chunks to avoid holding locks for too long and
triggering RCU stall warnings.
---
arch/arm64/include/asm/kvm_rmi.h | 4 +
arch/arm64/kvm/Kconfig | 1 +
arch/arm64/kvm/arm.c | 9 ++
arch/arm64/kvm/rmi.c | 175 +++++++++++++++++++++++++++++++
4 files changed, 189 insertions(+)
@@ -2037,6 +2037,15 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)return-EFAULT;returnkvm_vm_ioctl_get_reg_writable_masks(kvm,&range);}+caseKVM_ARM_RMI_POPULATE:{+structkvm_arm_rmi_populatereq;++if(!kvm_is_realm(kvm))+return-EPERM;+if(copy_from_user(&req,argp,sizeof(req)))+return-EFAULT;+returnkvm_arm_rmi_populate(kvm,&req);+}default:return-EINVAL;}
From: Marc Zyngier <maz@kernel.org> Date: 2026-03-02 14:56:27
On Wed, 17 Dec 2025 10:10:57 +0000,
Steven Price [off-list ref] wrote:
quoted hunk
The VMM needs to populate the realm with some data before starting (e.g.
a kernel and initrd). This is measured by the RMM and used as part of
the attestation later on.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* The multiplex CAP is gone and there's a new ioctl which makes use of
the generic kvm_gmem_populate() functionality.
Changes since v7:
* Improve the error codes.
* Other minor changes from review.
Changes since v6:
* Handle host potentially having a larger page size than the RMM
granule.
* Drop historic "par" (protected address range) from
populate_par_region() - it doesn't exist within the current
architecture.
* Add a cond_resched() call in kvm_populate_realm().
Changes since v5:
* Refactor to use PFNs rather than tracking struct page in
realm_create_protected_data_page().
* Pull changes from a later patch (in the v5 series) for accessing
pages from a guest memfd.
* Do the populate in chunks to avoid holding locks for too long and
triggering RCU stall warnings.
---
arch/arm64/include/asm/kvm_rmi.h | 4 +
arch/arm64/kvm/Kconfig | 1 +
arch/arm64/kvm/arm.c | 9 ++
arch/arm64/kvm/rmi.c | 175 +++++++++++++++++++++++++++++++
4 files changed, 189 insertions(+)
@@ -2037,6 +2037,15 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)return-EFAULT;returnkvm_vm_ioctl_get_reg_writable_masks(kvm,&range);}+caseKVM_ARM_RMI_POPULATE:{+structkvm_arm_rmi_populatereq;++if(!kvm_is_realm(kvm))+return-EPERM;
EPERM is odd. It isn't that the VMM doesn't have the right to do it,
it is that it shouldn't have called that, because the ioctl doesn't
exist for a normal VM. -ENOSYS?
Bits of the code works on PAGE_SIZE, other bits on RMM_PAGE_SIZE. It
is pretty confusing. Are you in the middle of reworking this?
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
From: Steven Price <steven.price@arm.com> Date: 2026-03-02 16:47:01
On 02/03/2026 14:56, Marc Zyngier wrote:
On Wed, 17 Dec 2025 10:10:57 +0000,
Steven Price [off-list ref] wrote:
quoted
The VMM needs to populate the realm with some data before starting (e.g.
a kernel and initrd). This is measured by the RMM and used as part of
the attestation later on.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v11:
* The multiplex CAP is gone and there's a new ioctl which makes use of
the generic kvm_gmem_populate() functionality.
Changes since v7:
* Improve the error codes.
* Other minor changes from review.
Changes since v6:
* Handle host potentially having a larger page size than the RMM
granule.
* Drop historic "par" (protected address range) from
populate_par_region() - it doesn't exist within the current
architecture.
* Add a cond_resched() call in kvm_populate_realm().
Changes since v5:
* Refactor to use PFNs rather than tracking struct page in
realm_create_protected_data_page().
* Pull changes from a later patch (in the v5 series) for accessing
pages from a guest memfd.
* Do the populate in chunks to avoid holding locks for too long and
triggering RCU stall warnings.
---
arch/arm64/include/asm/kvm_rmi.h | 4 +
arch/arm64/kvm/Kconfig | 1 +
arch/arm64/kvm/arm.c | 9 ++
arch/arm64/kvm/rmi.c | 175 +++++++++++++++++++++++++++++++
4 files changed, 189 insertions(+)
@@ -2037,6 +2037,15 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)return-EFAULT;returnkvm_vm_ioctl_get_reg_writable_masks(kvm,&range);}+caseKVM_ARM_RMI_POPULATE:{+structkvm_arm_rmi_populatereq;++if(!kvm_is_realm(kvm))+return-EPERM;
EPERM is odd. It isn't that the VMM doesn't have the right to do it,
it is that it shouldn't have called that, because the ioctl doesn't
exist for a normal VM. -ENOSYS?
@@ -558,6 +558,150 @@ void kvm_realm_unmap_range(struct kvm *kvm, unsigned long start,realm_unmap_private_range(kvm,start,end,may_block);}+staticintrealm_create_protected_data_granule(structrealm*realm,+unsignedlongipa,+phys_addr_tdst_phys,+phys_addr_tsrc_phys,+unsignedlongflags)+{+phys_addr_trd=virt_to_phys(realm->rd);+intret;++if(rmi_granule_delegate(dst_phys))+return-ENXIO;++ret=rmi_data_create(rd,dst_phys,ipa,src_phys,flags);+if(RMI_RETURN_STATUS(ret)==RMI_ERROR_RTT){+/* Create missing RTTs and retry */+intlevel=RMI_RETURN_INDEX(ret);++WARN_ON(level==RMM_RTT_MAX_LEVEL);
If this is unexpected, why do we still try to handle it? We should
abort hard on anything that doesn't seem 100% correct, and mark the
realm dead.
Well this is a "should never happen - the RMM (or Linux kerne) is buggy"
situation - so it's not specifically the realm's fault. The "do nothing"
error handling deals with things quite reasonably - the following
realm_create_rtt_levels() call is a no-op, so we'll retry the
rmi_data_create() call and bubble the error up.
I'll change this to KVM_BUG_ON so that the guest is killed just in case
it turns out the guest can somehow trigger this maliciously.
quoted
+
+ ret = realm_create_rtt_levels(realm, ipa, level,
+ RMM_RTT_MAX_LEVEL, NULL);
+ if (ret)
+ return -EIO;
+
+ ret = rmi_data_create(rd, dst_phys, ipa, src_phys, flags);
+ }
+ if (ret)
+ return -EIO;
+
+ return 0;
+}
+
+static int realm_create_protected_data_page(struct realm *realm,
+ unsigned long ipa,
+ kvm_pfn_t dst_pfn,
+ kvm_pfn_t src_pfn,
+ unsigned long flags)
+{
+ unsigned long rd = virt_to_phys(realm->rd);
+ phys_addr_t dst_phys, src_phys;
+ bool undelegate_failed = false;
+ int ret, offset;
+
+ dst_phys = __pfn_to_phys(dst_pfn);
+ src_phys = __pfn_to_phys(src_pfn);
+
+ for (offset = 0; offset < PAGE_SIZE; offset += RMM_PAGE_SIZE) {
+ ret = realm_create_protected_data_granule(realm,
+ ipa,
+ dst_phys,
+ src_phys,
+ flags);
+ if (ret)
+ goto err;
+
+ ipa += RMM_PAGE_SIZE;
+ dst_phys += RMM_PAGE_SIZE;
+ src_phys += RMM_PAGE_SIZE;
+ }
+
+ return 0;
+
+err:
+ if (ret == -EIO) {
+ /* current offset needs undelegating */
+ if (WARN_ON(rmi_granule_undelegate(dst_phys)))
+ undelegate_failed = true;
+ }
+ while (offset > 0) {
+ ipa -= RMM_PAGE_SIZE;
+ offset -= RMM_PAGE_SIZE;
+ dst_phys -= RMM_PAGE_SIZE;
+
+ rmi_data_destroy(rd, ipa, NULL, NULL);
+
+ if (WARN_ON(rmi_granule_undelegate(dst_phys)))
+ undelegate_failed = true;
+ }
+
+ if (undelegate_failed) {
+ /*
+ * A granule could not be undelegated,
+ * so the page has to be leaked
+ */
+ get_page(pfn_to_page(dst_pfn));
+ }
+
+ return -ENXIO;
+}
+
+static int populate_region_cb(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
+ void __user *src, int order, void *opaque)
+{
+ struct realm *realm = &kvm->arch.realm;
+ unsigned long data_flags = *(unsigned long *)opaque;
+ phys_addr_t ipa = gfn_to_gpa(gfn);
+ int npages = (1 << order);
+ int i;
+
+ for (i = 0; i < npages; i++) {
+ struct page *src_page;
+ int ret;
+
+ ret = get_user_pages((unsigned long)src, 1, 0, &src_page);
+ if (ret < 0)
+ return ret;
+ if (ret != 1)
+ return -ENOMEM;
+
+ ret = realm_create_protected_data_page(realm, ipa, pfn,
+ page_to_pfn(src_page),
+ data_flags);
+
+ put_page(src_page);
+
+ if (ret)
+ return ret;
+
+ ipa += PAGE_SIZE;
+ pfn++;
+ src += PAGE_SIZE;
+ }
+
+ return 0;
+}
+
+static long populate_region(struct kvm *kvm,
+ gfn_t base_gfn,
+ unsigned long pages,
+ u64 uaddr,
+ unsigned long data_flags)
+{
+ long ret = 0;
+
+ mutex_lock(&kvm->slots_lock);
+ mmap_read_lock(current->mm);
+ ret = kvm_gmem_populate(kvm, base_gfn, u64_to_user_ptr(uaddr), pages,
+ populate_region_cb, &data_flags);
+ mmap_read_unlock(current->mm);
+ mutex_unlock(&kvm->slots_lock);
+
+ return ret;
+}
+
enum ripas_action {
RIPAS_INIT,
RIPAS_SET,
@@ -655,6 +799,37 @@ static int realm_ensure_created(struct kvm *kvm) return -ENXIO; }+int kvm_arm_rmi_populate(struct kvm *kvm,+ struct kvm_arm_rmi_populate *args)+{+ unsigned long data_flags = 0;+ unsigned long ipa_start = args->base;+ unsigned long ipa_end = ipa_start + args->size;+ int ret;++ if (args->reserved ||+ (args->flags & ~KVM_ARM_RMI_POPULATE_FLAGS_MEASURE) ||+ !IS_ALIGNED(ipa_start, PAGE_SIZE) ||+ !IS_ALIGNED(ipa_end, PAGE_SIZE))+ return -EINVAL;++ ret = realm_ensure_created(kvm);+ if (ret)+ return ret;++ if (args->flags & KVM_ARM_RMI_POPULATE_FLAGS_MEASURE)
This flag isn't documented.
Indeed - that's an oversight! I'll add the following to the docs:
`flags` can be set to `KVM_ARM_RMI_POPULATE_FLAGS_MEASURE` to request that the
populated data is hashed and added to the guest's Realm Initial Measurement
(RIM).
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:13:29
The memory which the realm guest accesses must be set to RIPAS_RAM.
Iterate over the memslots and set all gmem memslots to RIPAS_RAM.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v12.
---
arch/arm64/kvm/rmi.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
@@ -793,12 +793,44 @@ static int realm_set_ipa_state(struct kvm_vcpu *vcpu,returnret;}+staticintrealm_init_ipa_state(structkvm*kvm,+unsignedlonggfn,+unsignedlongpages)+{+returnripas_change(kvm,NULL,gfn_to_gpa(gfn),gfn_to_gpa(gfn+pages),+RIPAS_INIT,NULL);+}+staticintrealm_ensure_created(structkvm*kvm){/* Provided in later patch */return-ENXIO;}+staticintset_ripas_of_protected_regions(structkvm*kvm)+{+structkvm_memslots*slots;+structkvm_memory_slot*memslot;+intidx,bkt;+intret=0;++idx=srcu_read_lock(&kvm->srcu);++slots=kvm_memslots(kvm);+kvm_for_each_memslot(memslot,bkt,slots){+if(!kvm_slot_has_gmem(memslot))+continue;++ret=realm_init_ipa_state(kvm,memslot->base_gfn,+memslot->npages);+if(ret)+break;+}+srcu_read_unlock(&kvm->srcu,idx);++returnret;+}+intkvm_arm_rmi_populate(structkvm*kvm,structkvm_arm_rmi_populate*args){
@@ -1119,6 +1151,10 @@ int kvm_activate_realm(struct kvm *kvm)returnret;}+ret=set_ripas_of_protected_regions(kvm);+if(ret)+returnret;+ret=rmi_realm_activate(virt_to_phys(realm->rd));if(ret)return-ENXIO;
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:13:33
Creating a realm involves first creating a realm descriptor (RD). This
involves passing the configuration information to the RMM. Do this as
part of realm_ensure_created() so that the realm is created when it is
first needed.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v12
---
arch/arm64/kvm/rmi.c | 117 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 115 insertions(+), 2 deletions(-)
@@ -500,6 +500,106 @@ static void realm_unmap_shared_range(struct kvm *kvm,start,end);}+/* Calculate the number of s2 root rtts needed */+staticintrealm_num_root_rtts(structrealm*realm)+{+unsignedintipa_bits=realm->ia_bits;+unsignedintlevels=4-get_start_level(realm);+unsignedintsl_ipa_bits=levels*(RMM_PAGE_SHIFT-3)++RMM_PAGE_SHIFT;++if(sl_ipa_bits>=ipa_bits)+return1;++return1<<(ipa_bits-sl_ipa_bits);+}++staticintrealm_create_rd(structkvm*kvm)+{+structrealm*realm=&kvm->arch.realm;+structrealm_params*params=realm->params;+void*rd=NULL;+phys_addr_trd_phys,params_phys;+size_tpgd_size=kvm_pgtable_stage2_pgd_size(kvm->arch.mmu.vtcr);+inti,r;+intrtt_num_start;++realm->ia_bits=VTCR_EL2_IPA(kvm->arch.mmu.vtcr);+rtt_num_start=realm_num_root_rtts(realm);++if(WARN_ON(realm->rd||!realm->params))+return-EEXIST;++if(pgd_size/RMM_PAGE_SIZE<rtt_num_start)+return-EINVAL;++rd=(void*)__get_free_page(GFP_KERNEL);+if(!rd)+return-ENOMEM;++rd_phys=virt_to_phys(rd);+if(rmi_granule_delegate(rd_phys)){+r=-ENXIO;+gotofree_rd;+}++for(i=0;i<pgd_size;i+=RMM_PAGE_SIZE){+phys_addr_tpgd_phys=kvm->arch.mmu.pgd_phys+i;++if(rmi_granule_delegate(pgd_phys)){+r=-ENXIO;+gotoout_undelegate_tables;+}+}++params->s2sz=VTCR_EL2_IPA(kvm->arch.mmu.vtcr);+params->rtt_level_start=get_start_level(realm);+params->rtt_num_start=rtt_num_start;+params->rtt_base=kvm->arch.mmu.pgd_phys;+params->vmid=realm->vmid;++params_phys=virt_to_phys(params);++if(rmi_realm_create(rd_phys,params_phys)){+r=-ENXIO;+gotoout_undelegate_tables;+}++if(WARN_ON(rmi_rec_aux_count(rd_phys,&realm->num_aux))){+WARN_ON(rmi_realm_destroy(rd_phys));+r=-ENXIO;+gotoout_undelegate_tables;+}++realm->rd=rd;+WRITE_ONCE(realm->state,REALM_STATE_NEW);+/* The realm is up, free the parameters. */+free_page((unsignedlong)realm->params);+realm->params=NULL;++return0;++out_undelegate_tables:+while(i>0){+i-=RMM_PAGE_SIZE;++phys_addr_tpgd_phys=kvm->arch.mmu.pgd_phys+i;++if(WARN_ON(rmi_granule_undelegate(pgd_phys))){+/* Leak the pages if they cannot be returned */+kvm->arch.mmu.pgt=NULL;+break;+}+}+if(WARN_ON(rmi_granule_undelegate(rd_phys))){+/* Leak the page if it isn't returned */+returnr;+}+free_rd:+free_page((unsignedlong)rd);+returnr;+}+staticintrealm_unmap_private_page(structrealm*realm,unsignedlongipa,unsignedlong*next_addr)
@@ -803,8 +903,21 @@ static int realm_init_ipa_state(struct kvm *kvm,staticintrealm_ensure_created(structkvm*kvm){-/* Provided in later patch */-return-ENXIO;+intret;++switch(kvm_realm_state(kvm)){+caseREALM_STATE_NONE:+break;+caseREALM_STATE_NEW:+return0;+caseREALM_STATE_DEAD:+return-ENXIO;+default:+return-EBUSY;+}++ret=realm_create_rd(kvm);+returnret;}staticintset_ripas_of_protected_regions(structkvm*kvm)
On Wed, Dec 17, 2025 at 2:13 AM Steven Price [off-list ref] wrote:
quoted hunk
Creating a realm involves first creating a realm descriptor (RD). This
involves passing the configuration information to the RMM. Do this as
part of realm_ensure_created() so that the realm is created when it is
first needed.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v12
---
arch/arm64/kvm/rmi.c | 117 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 115 insertions(+), 2 deletions(-)
@@ -500,6 +500,106 @@ static void realm_unmap_shared_range(struct kvm *kvm,start,end);}+/* Calculate the number of s2 root rtts needed */+staticintrealm_num_root_rtts(structrealm*realm)+{+unsignedintipa_bits=realm->ia_bits;+unsignedintlevels=4-get_start_level(realm);+unsignedintsl_ipa_bits=levels*(RMM_PAGE_SHIFT-3)++RMM_PAGE_SHIFT;++if(sl_ipa_bits>=ipa_bits)+return1;++return1<<(ipa_bits-sl_ipa_bits);+}++staticintrealm_create_rd(structkvm*kvm)+{+structrealm*realm=&kvm->arch.realm;+structrealm_params*params=realm->params;+void*rd=NULL;+phys_addr_trd_phys,params_phys;+size_tpgd_size=kvm_pgtable_stage2_pgd_size(kvm->arch.mmu.vtcr);+inti,r;+intrtt_num_start;++realm->ia_bits=VTCR_EL2_IPA(kvm->arch.mmu.vtcr);+rtt_num_start=realm_num_root_rtts(realm);++if(WARN_ON(realm->rd||!realm->params))+return-EEXIST;++if(pgd_size/RMM_PAGE_SIZE<rtt_num_start)+return-EINVAL;++rd=(void*)__get_free_page(GFP_KERNEL);+if(!rd)+return-ENOMEM;++rd_phys=virt_to_phys(rd);+if(rmi_granule_delegate(rd_phys)){+r=-ENXIO;+gotofree_rd;+}++for(i=0;i<pgd_size;i+=RMM_PAGE_SIZE){+phys_addr_tpgd_phys=kvm->arch.mmu.pgd_phys+i;++if(rmi_granule_delegate(pgd_phys)){+r=-ENXIO;+gotoout_undelegate_tables;+}+}++params->s2sz=VTCR_EL2_IPA(kvm->arch.mmu.vtcr);+params->rtt_level_start=get_start_level(realm);+params->rtt_num_start=rtt_num_start;+params->rtt_base=kvm->arch.mmu.pgd_phys;+params->vmid=realm->vmid;
I don't see a way to configure rpv and hash_algo anymore. I assume they
are gone for a minimal userspace interface. Will there be a way to set
them going forward?
quoted hunk
+
+ params_phys = virt_to_phys(params);
+
+ if (rmi_realm_create(rd_phys, params_phys)) {
+ r = -ENXIO;
+ goto out_undelegate_tables;
+ }
+
+ if (WARN_ON(rmi_rec_aux_count(rd_phys, &realm->num_aux))) {
+ WARN_ON(rmi_realm_destroy(rd_phys));
+ r = -ENXIO;
+ goto out_undelegate_tables;
+ }
+
+ realm->rd = rd;
+ WRITE_ONCE(realm->state, REALM_STATE_NEW);
+ /* The realm is up, free the parameters. */
+ free_page((unsigned long)realm->params);
+ realm->params = NULL;
+
+ return 0;
+
+out_undelegate_tables:
+ while (i > 0) {
+ i -= RMM_PAGE_SIZE;
+
+ phys_addr_t pgd_phys = kvm->arch.mmu.pgd_phys + i;
+
+ if (WARN_ON(rmi_granule_undelegate(pgd_phys))) {
+ /* Leak the pages if they cannot be returned */
+ kvm->arch.mmu.pgt = NULL;
+ break;
+ }
+ }
+ if (WARN_ON(rmi_granule_undelegate(rd_phys))) {
+ /* Leak the page if it isn't returned */
+ return r;
+ }
+free_rd:
+ free_page((unsigned long)rd);
+ return r;
+}
+
static int realm_unmap_private_page(struct realm *realm,
unsigned long ipa,
unsigned long *next_addr)
@@ -803,8 +903,21 @@ static int realm_init_ipa_state(struct kvm *kvm, static int realm_ensure_created(struct kvm *kvm) {- /* Provided in later patch */- return -ENXIO;+ int ret;++ switch (kvm_realm_state(kvm)) {+ case REALM_STATE_NONE:+ break;+ case REALM_STATE_NEW:+ return 0;+ case REALM_STATE_DEAD:+ return -ENXIO;+ default:+ return -EBUSY;+ }++ ret = realm_create_rd(kvm);+ return ret; } static int set_ripas_of_protected_regions(struct kvm *kvm)--
From: Steven Price <steven.price@arm.com> Date: 2026-01-26 09:50:57
On 23/01/2026 18:57, Alper Gun wrote:
On Wed, Dec 17, 2025 at 2:13 AM Steven Price [off-list ref] wrote:
quoted
Creating a realm involves first creating a realm descriptor (RD). This
involves passing the configuration information to the RMM. Do this as
part of realm_ensure_created() so that the realm is created when it is
first needed.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v12
---
arch/arm64/kvm/rmi.c | 117 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 115 insertions(+), 2 deletions(-)
@@ -500,6 +500,106 @@ static void realm_unmap_shared_range(struct kvm *kvm,start,end);}+/* Calculate the number of s2 root rtts needed */+staticintrealm_num_root_rtts(structrealm*realm)+{+unsignedintipa_bits=realm->ia_bits;+unsignedintlevels=4-get_start_level(realm);+unsignedintsl_ipa_bits=levels*(RMM_PAGE_SHIFT-3)++RMM_PAGE_SHIFT;++if(sl_ipa_bits>=ipa_bits)+return1;++return1<<(ipa_bits-sl_ipa_bits);+}++staticintrealm_create_rd(structkvm*kvm)+{+structrealm*realm=&kvm->arch.realm;+structrealm_params*params=realm->params;+void*rd=NULL;+phys_addr_trd_phys,params_phys;+size_tpgd_size=kvm_pgtable_stage2_pgd_size(kvm->arch.mmu.vtcr);+inti,r;+intrtt_num_start;++realm->ia_bits=VTCR_EL2_IPA(kvm->arch.mmu.vtcr);+rtt_num_start=realm_num_root_rtts(realm);++if(WARN_ON(realm->rd||!realm->params))+return-EEXIST;++if(pgd_size/RMM_PAGE_SIZE<rtt_num_start)+return-EINVAL;++rd=(void*)__get_free_page(GFP_KERNEL);+if(!rd)+return-ENOMEM;++rd_phys=virt_to_phys(rd);+if(rmi_granule_delegate(rd_phys)){+r=-ENXIO;+gotofree_rd;+}++for(i=0;i<pgd_size;i+=RMM_PAGE_SIZE){+phys_addr_tpgd_phys=kvm->arch.mmu.pgd_phys+i;++if(rmi_granule_delegate(pgd_phys)){+r=-ENXIO;+gotoout_undelegate_tables;+}+}++params->s2sz=VTCR_EL2_IPA(kvm->arch.mmu.vtcr);+params->rtt_level_start=get_start_level(realm);+params->rtt_num_start=rtt_num_start;+params->rtt_base=kvm->arch.mmu.pgd_phys;+params->vmid=realm->vmid;
I don't see a way to configure rpv and hash_algo anymore. I assume they
are gone for a minimal userspace interface. Will there be a way to set
them going forward?
Yes the intention is that the uAPI will be extended in the future to
allow these to be configured. This would be by exposing new capability
flags and allowing the VMM to set the capability. This ensures that a
basic VMM doesn't need to worry about them, but a more featured VMM can
provide support for configuration.
This series is already rather long so I've attempted to drop optional
parts to keep the complexity down while still providing something
"useful" (i.e. can launch a realm guest and there's some meaningful
extra security/attestation over a normal VM). There's loads of extra
features in the spec which will come later.
Thanks,
Steve
quoted
+
+ params_phys = virt_to_phys(params);
+
+ if (rmi_realm_create(rd_phys, params_phys)) {
+ r = -ENXIO;
+ goto out_undelegate_tables;
+ }
+
+ if (WARN_ON(rmi_rec_aux_count(rd_phys, &realm->num_aux))) {
+ WARN_ON(rmi_realm_destroy(rd_phys));
+ r = -ENXIO;
+ goto out_undelegate_tables;
+ }
+
+ realm->rd = rd;
+ WRITE_ONCE(realm->state, REALM_STATE_NEW);
+ /* The realm is up, free the parameters. */
+ free_page((unsigned long)realm->params);
+ realm->params = NULL;
+
+ return 0;
+
+out_undelegate_tables:
+ while (i > 0) {
+ i -= RMM_PAGE_SIZE;
+
+ phys_addr_t pgd_phys = kvm->arch.mmu.pgd_phys + i;
+
+ if (WARN_ON(rmi_granule_undelegate(pgd_phys))) {
+ /* Leak the pages if they cannot be returned */
+ kvm->arch.mmu.pgt = NULL;
+ break;
+ }
+ }
+ if (WARN_ON(rmi_granule_undelegate(rd_phys))) {
+ /* Leak the page if it isn't returned */
+ return r;
+ }
+free_rd:
+ free_page((unsigned long)rd);
+ return r;
+}
+
static int realm_unmap_private_page(struct realm *realm,
unsigned long ipa,
unsigned long *next_addr)
@@ -803,8 +903,21 @@ static int realm_init_ipa_state(struct kvm *kvm, static int realm_ensure_created(struct kvm *kvm) {- /* Provided in later patch */- return -ENXIO;+ int ret;++ switch (kvm_realm_state(kvm)) {+ case REALM_STATE_NONE:+ break;+ case REALM_STATE_NEW:+ return 0;+ case REALM_STATE_DEAD:+ return -ENXIO;+ default:+ return -EBUSY;+ }++ ret = realm_create_rd(kvm);+ return ret; } static int set_ripas_of_protected_regions(struct kvm *kvm)--
From: Steven Price <steven.price@arm.com> Date: 2025-12-17 10:13:38
The RMM v1.0 spec requires that the host manage the VMIDs of realm
guests. Add a basic allocator and assign a unique VMID to the guest when
creating the realm descriptor.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v12.
NOTE: RMM v2.0 will remove the requirement for the host to manage VMIDs
---
arch/arm64/kvm/rmi.c | 48 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)