* [PATCH v13 28/48] KVM: arm64: Handle realm VCPU load
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
When loading a realm VCPU much of the work is handled by the RMM so only
some of the actions are required. Rearrange kvm_arch_vcpu_load()
slightly so we can bail out early for a realm guest.
Signed-off-by: Steven Price <steven.price@arm.com>
---
arch/arm64/kvm/arm.c | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 43d05da7e694..304fb1f2b3ff 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -688,7 +688,7 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
struct kvm_s2_mmu *mmu;
int *last_ran;
- if (is_protected_kvm_enabled())
+ if (is_protected_kvm_enabled() || kvm_is_realm(vcpu->kvm))
goto nommu;
if (vcpu_has_nv(vcpu))
@@ -732,12 +732,6 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
kvm_vgic_load(vcpu);
kvm_vcpu_load_debug(vcpu);
kvm_vcpu_load_fgt(vcpu);
- if (has_vhe())
- kvm_vcpu_load_vhe(vcpu);
- kvm_arch_vcpu_load_fp(vcpu);
- kvm_vcpu_pmu_restore_guest(vcpu);
- if (kvm_arm_is_pvtime_enabled(&vcpu->arch))
- kvm_make_request(KVM_REQ_RECORD_STEAL, vcpu);
if (kvm_vcpu_should_clear_twe(vcpu))
vcpu->arch.hcr_el2 &= ~HCR_TWE;
@@ -759,6 +753,17 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
&vcpu->arch.vgic_cpu.vgic_v3);
}
+ /* No additional state needs to be loaded on Realmed VMs */
+ if (vcpu_is_rec(vcpu))
+ return;
+
+ if (has_vhe())
+ kvm_vcpu_load_vhe(vcpu);
+ kvm_arch_vcpu_load_fp(vcpu);
+ kvm_vcpu_pmu_restore_guest(vcpu);
+ if (kvm_arm_is_pvtime_enabled(&vcpu->arch))
+ kvm_make_request(KVM_REQ_RECORD_STEAL, vcpu);
+
if (!cpumask_test_cpu(cpu, vcpu->kvm->arch.supported_cpus))
vcpu_set_on_unsupported_cpu(vcpu);
}
--
2.43.0
^ permalink raw reply related
* [PATCH v13 27/48] arm64: RMI: Runtime faulting of memory
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
At runtime if the realm guest accesses memory which hasn't yet been
mapped then KVM needs to either populate the region or fault the guest.
For memory in the lower (protected) region of IPA a fresh page is
provided to the RMM which will zero the contents. For memory in the
upper (shared) region of IPA, the memory from the memslot is mapped
into the realm VM non secure.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v12:
* Switch to RMM v2.0 range based APIs.
Changes since v11:
* Adapt to upstream changes.
Changes since v10:
* RME->RMI renaming.
* Adapt to upstream gmem changes.
Changes since v9:
* Fix call to kvm_stage2_unmap_range() in kvm_free_stage2_pgd() to set
may_block to avoid stall warnings.
* Minor coding style fixes.
Changes since v8:
* Propagate the may_block flag.
* Minor comments and coding style changes.
Changes since v7:
* Remove redundant WARN_ONs for realm_create_rtt_levels() - it will
internally WARN when necessary.
Changes since v6:
* Handle PAGE_SIZE being larger than RMM granule size.
* Some minor renaming following review comments.
Changes since v5:
* Reduce use of struct page in preparation for supporting the RMM
having a different page size to the host.
* Handle a race when delegating a page where another CPU has faulted on
a the same page (and already delegated the physical page) but not yet
mapped it. In this case simply return to the guest to either use the
mapping from the other CPU (or refault if the race is lost).
* The changes to populate_par_region() are moved into the previous
patch where they belong.
Changes since v4:
* Code cleanup following review feedback.
* Drop the PTE_SHARED bit when creating unprotected page table entries.
This is now set by the RMM and the host has no control of it and the
spec requires the bit to be set to zero.
Changes since v2:
* Avoid leaking memory if failing to map it in the realm.
* Correctly mask RTT based on LPA2 flag (see rtt_get_phys()).
* Adapt to changes in previous patches.
---
arch/arm64/include/asm/kvm_emulate.h | 8 ++
arch/arm64/include/asm/kvm_rmi.h | 12 ++
arch/arm64/kvm/mmu.c | 139 ++++++++++++++++--
arch/arm64/kvm/rmi.c | 206 +++++++++++++++++++++++++++
4 files changed, 351 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index d194d91fbc2a..0734c4a65174 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -706,6 +706,14 @@ static inline bool kvm_realm_is_created(struct kvm *kvm)
return kvm_is_realm(kvm) && kvm_realm_state(kvm) != REALM_STATE_NONE;
}
+static inline gpa_t kvm_gpa_from_fault(struct kvm *kvm, phys_addr_t ipa)
+{
+ if (!kvm_is_realm(kvm))
+ return ipa;
+
+ return ipa & ~BIT(kvm->arch.realm.ia_bits - 1);
+}
+
static inline bool vcpu_is_rec(struct kvm_vcpu *vcpu)
{
return kvm_is_realm(vcpu->kvm);
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index bf663bb240c4..38208be3c602 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -6,6 +6,7 @@
#ifndef __ASM_KVM_RMI_H
#define __ASM_KVM_RMI_H
+#include <asm/kvm_pgtable.h>
#include <asm/rmi_smc.h>
/**
@@ -105,6 +106,17 @@ void kvm_realm_unmap_range(struct kvm *kvm,
unsigned long size,
bool unmap_private,
bool may_block);
+int realm_map_protected(struct kvm *kvm,
+ unsigned long base_ipa,
+ kvm_pfn_t pfn,
+ unsigned long size,
+ struct kvm_mmu_memory_cache *memcache);
+int realm_map_non_secure(struct realm *realm,
+ unsigned long ipa,
+ kvm_pfn_t pfn,
+ unsigned long size,
+ enum kvm_pgtable_prot prot,
+ struct kvm_mmu_memory_cache *memcache);
static inline bool kvm_realm_is_private_address(struct realm *realm,
unsigned long addr)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index bad93938acdb..73c18c2861a2 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -334,8 +334,15 @@ static void __unmap_stage2_range(struct kvm_s2_mmu *mmu, phys_addr_t start, u64
lockdep_assert_held_write(&kvm->mmu_lock);
WARN_ON(size & ~PAGE_MASK);
- WARN_ON(stage2_apply_range(mmu, start, end, KVM_PGT_FN(kvm_pgtable_stage2_unmap),
- may_block));
+
+ if (kvm_is_realm(kvm)) {
+ kvm_realm_unmap_range(kvm, start, size, !only_shared,
+ may_block);
+ } else {
+ WARN_ON(stage2_apply_range(mmu, start, end,
+ KVM_PGT_FN(kvm_pgtable_stage2_unmap),
+ may_block));
+ }
}
void kvm_stage2_unmap_range(struct kvm_s2_mmu *mmu, phys_addr_t start,
@@ -355,7 +362,10 @@ static void stage2_flush_memslot(struct kvm *kvm,
phys_addr_t addr = memslot->base_gfn << PAGE_SHIFT;
phys_addr_t end = addr + PAGE_SIZE * memslot->npages;
- kvm_stage2_flush_range(&kvm->arch.mmu, addr, end);
+ if (kvm_is_realm(kvm))
+ kvm_realm_unmap_range(kvm, addr, end - addr, false, true);
+ else
+ kvm_stage2_flush_range(&kvm->arch.mmu, addr, end);
}
/**
@@ -1081,6 +1091,10 @@ void stage2_unmap_vm(struct kvm *kvm)
struct kvm_memory_slot *memslot;
int idx, bkt;
+ /* For realms this is handled by the RMM so nothing to do here */
+ if (kvm_is_realm(kvm))
+ return;
+
idx = srcu_read_lock(&kvm->srcu);
mmap_read_lock(current->mm);
write_lock(&kvm->mmu_lock);
@@ -1106,6 +1120,9 @@ void kvm_free_stage2_pgd(struct kvm_s2_mmu *mmu)
if (kvm_is_realm(kvm) &&
(kvm_realm_state(kvm) != REALM_STATE_DEAD &&
kvm_realm_state(kvm) != REALM_STATE_NONE)) {
+ struct realm *realm = &kvm->arch.realm;
+
+ kvm_stage2_unmap_range(mmu, 0, BIT(realm->ia_bits - 1), true);
write_unlock(&kvm->mmu_lock);
kvm_realm_destroy_rtts(kvm);
@@ -1516,6 +1533,29 @@ static bool kvm_vma_mte_allowed(struct vm_area_struct *vma)
return vma->vm_flags & VM_MTE_ALLOWED;
}
+static int realm_map_ipa(struct kvm *kvm, phys_addr_t ipa,
+ kvm_pfn_t pfn, unsigned long map_size,
+ enum kvm_pgtable_prot prot,
+ struct kvm_mmu_memory_cache *memcache)
+{
+ struct realm *realm = &kvm->arch.realm;
+
+ /*
+ * Write permission is required for now even though it's possible to
+ * map unprotected pages (granules) as read-only. It's impossible to
+ * map protected pages (granules) as read-only.
+ */
+ if (WARN_ON(!(prot & KVM_PGTABLE_PROT_W)))
+ return -EFAULT;
+
+ ipa = ALIGN_DOWN(ipa, PAGE_SIZE);
+ if (!kvm_realm_is_private_address(realm, ipa))
+ return realm_map_non_secure(realm, ipa, pfn, map_size, prot,
+ memcache);
+
+ return realm_map_protected(kvm, ipa, pfn, map_size, memcache);
+}
+
static bool kvm_vma_is_cacheable(struct vm_area_struct *vma)
{
switch (FIELD_GET(PTE_ATTRINDX_MASK, pgprot_val(vma->vm_page_prot))) {
@@ -1588,6 +1628,7 @@ static int gmem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_R;
struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
+ gpa_t gpa = kvm_gpa_from_fault(vcpu->kvm, fault_ipa);
unsigned long mmu_seq;
struct page *page;
struct kvm *kvm = vcpu->kvm;
@@ -1596,6 +1637,29 @@ static int gmem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
gfn_t gfn;
int ret;
+ if (kvm_is_realm(vcpu->kvm)) {
+ /* check for memory attribute mismatch */
+ bool is_priv_gfn = kvm_mem_is_private(kvm, gpa >> PAGE_SHIFT);
+ /*
+ * For Realms, the shared address is an alias of the private
+ * PA with the top bit set. Thus is the fault address matches
+ * the GPA then it is the private alias.
+ */
+ bool is_priv_fault = (gpa == fault_ipa);
+
+ if (is_priv_gfn != is_priv_fault) {
+ kvm_prepare_memory_fault_exit(vcpu, gpa, PAGE_SIZE,
+ kvm_is_write_fault(vcpu),
+ false,
+ is_priv_fault);
+ /*
+ * KVM_EXIT_MEMORY_FAULT requires an return code of
+ * -EFAULT, see the API documentation
+ */
+ return -EFAULT;
+ }
+ }
+
ret = prepare_mmu_memcache(vcpu, true, &memcache);
if (ret)
return ret;
@@ -1603,7 +1667,7 @@ static int gmem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
if (nested)
gfn = kvm_s2_trans_output(nested) >> PAGE_SHIFT;
else
- gfn = fault_ipa >> PAGE_SHIFT;
+ gfn = gpa >> PAGE_SHIFT;
write_fault = kvm_is_write_fault(vcpu);
exec_fault = kvm_vcpu_trap_is_exec_fault(vcpu);
@@ -1616,7 +1680,7 @@ static int gmem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
ret = kvm_gmem_get_pfn(kvm, memslot, gfn, &pfn, &page, NULL);
if (ret) {
- kvm_prepare_memory_fault_exit(vcpu, fault_ipa, PAGE_SIZE,
+ kvm_prepare_memory_fault_exit(vcpu, gpa, PAGE_SIZE,
write_fault, exec_fault, false);
return ret;
}
@@ -1638,15 +1702,25 @@ static int gmem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
kvm_fault_lock(kvm);
if (mmu_invalidate_retry(kvm, mmu_seq)) {
ret = -EAGAIN;
- goto out_unlock;
+ goto out_release_page;
+ }
+
+ if (kvm_is_realm(kvm)) {
+ ret = realm_map_ipa(kvm, fault_ipa, pfn,
+ PAGE_SIZE, KVM_PGTABLE_PROT_W, memcache);
+ /* if successful don't release the page */
+ if (!ret)
+ goto out_unlock;
+ goto out_release_page;
}
ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, fault_ipa, PAGE_SIZE,
__pfn_to_phys(pfn), prot,
memcache, flags);
-out_unlock:
+out_release_page:
kvm_release_faultin_page(kvm, page, !!ret, writable);
+out_unlock:
kvm_fault_unlock(kvm);
if (writable && !ret)
@@ -1685,6 +1759,14 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
if (fault_is_perm)
fault_granule = kvm_vcpu_trap_get_perm_fault_granule(vcpu);
write_fault = kvm_is_write_fault(vcpu);
+
+ /*
+ * Realms cannot map protected pages read-only
+ * FIXME: It should be possible to map unprotected pages read-only
+ */
+ if (vcpu_is_rec(vcpu))
+ write_fault = true;
+
exec_fault = kvm_vcpu_trap_is_exec_fault(vcpu);
VM_WARN_ON_ONCE(write_fault && exec_fault);
@@ -1779,7 +1861,7 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
ipa &= ~(vma_pagesize - 1);
}
- gfn = ipa >> PAGE_SHIFT;
+ gfn = kvm_gpa_from_fault(kvm, ipa) >> PAGE_SHIFT;
mte_allowed = kvm_vma_mte_allowed(vma);
vfio_allow_any_uc = vma->vm_flags & VM_ALLOW_ANY_UNCACHED;
@@ -1855,6 +1937,15 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
if (exec_fault && s2_force_noncacheable)
ret = -ENOEXEC;
+ /*
+ * For now we shouldn't be hitting protected addresses because they are
+ * handled in gmem_abort(). In the future this check may be relaxed to
+ * support e.g. protected devices.
+ */
+ if (!ret && vcpu_is_rec(vcpu) &&
+ kvm_gpa_from_fault(kvm, fault_ipa) == fault_ipa)
+ ret = -EINVAL;
+
if (ret) {
kvm_release_page_unused(page);
return ret;
@@ -1939,6 +2030,9 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
*/
prot &= ~KVM_NV_GUEST_MAP_SZ;
ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, fault_ipa, prot, flags);
+ } else if (kvm_is_realm(kvm)) {
+ ret = realm_map_ipa(kvm, fault_ipa, pfn, vma_pagesize,
+ prot, memcache);
} else {
ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, fault_ipa, vma_pagesize,
__pfn_to_phys(pfn), prot,
@@ -2049,6 +2143,13 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)
return 0;
}
+static bool shared_ipa_fault(struct kvm *kvm, phys_addr_t fault_ipa)
+{
+ gpa_t gpa = kvm_gpa_from_fault(kvm, fault_ipa);
+
+ return (gpa != fault_ipa);
+}
+
/**
* kvm_handle_guest_abort - handles all 2nd stage aborts
* @vcpu: the VCPU pointer
@@ -2159,8 +2260,9 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
nested = &nested_trans;
}
- gfn = ipa >> PAGE_SHIFT;
+ gfn = kvm_gpa_from_fault(vcpu->kvm, ipa) >> PAGE_SHIFT;
memslot = gfn_to_memslot(vcpu->kvm, gfn);
+
hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable);
write_fault = kvm_is_write_fault(vcpu);
if (kvm_is_error_hva(hva) || (write_fault && !writable)) {
@@ -2203,7 +2305,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
* of the page size.
*/
ipa |= FAR_TO_FIPA_OFFSET(kvm_vcpu_get_hfar(vcpu));
- ret = io_mem_abort(vcpu, ipa);
+ ret = io_mem_abort(vcpu, kvm_gpa_from_fault(vcpu->kvm, ipa));
goto out_unlock;
}
@@ -2219,7 +2321,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
VM_WARN_ON_ONCE(kvm_vcpu_trap_is_permission_fault(vcpu) &&
!write_fault && !kvm_vcpu_trap_is_exec_fault(vcpu));
- if (kvm_slot_has_gmem(memslot))
+ if (kvm_slot_has_gmem(memslot) && !shared_ipa_fault(vcpu->kvm, fault_ipa))
ret = gmem_abort(vcpu, fault_ipa, nested, memslot,
esr_fsc_is_permission_fault(esr));
else
@@ -2256,6 +2358,10 @@ bool kvm_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
if (!kvm->arch.mmu.pgt)
return false;
+ /* We don't support aging for Realms */
+ if (kvm_is_realm(kvm))
+ return true;
+
return KVM_PGT_FN(kvm_pgtable_stage2_test_clear_young)(kvm->arch.mmu.pgt,
range->start << PAGE_SHIFT,
size, true);
@@ -2272,6 +2378,10 @@ bool kvm_test_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
if (!kvm->arch.mmu.pgt)
return false;
+ /* We don't support aging for Realms */
+ if (kvm_is_realm(kvm))
+ return true;
+
return KVM_PGT_FN(kvm_pgtable_stage2_test_clear_young)(kvm->arch.mmu.pgt,
range->start << PAGE_SHIFT,
size, false);
@@ -2438,10 +2548,11 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
return -EFAULT;
/*
- * Only support guest_memfd backed memslots with mappable memory, since
- * there aren't any CoCo VMs that support only private memory on arm64.
+ * Only support guest_memfd backed memslots with mappable memory,
+ * unless the guest is a CCA realm guest.
*/
- if (kvm_slot_has_gmem(new) && !kvm_memslot_is_gmem_only(new))
+ if (kvm_slot_has_gmem(new) && !kvm_memslot_is_gmem_only(new) &&
+ !kvm_is_realm(kvm))
return -EINVAL;
hva = new->userspace_addr;
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index d5fee203824b..30292814b1ec 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -837,6 +837,212 @@ static int realm_create_protected_data_page(struct kvm *kvm,
return ret;
}
+static int fold_rtt(struct realm *realm, unsigned long addr, int level)
+{
+ phys_addr_t rtt_addr;
+ int ret;
+
+ ret = realm_rtt_fold(realm, addr, level, &rtt_addr);
+ if (ret)
+ return ret;
+
+ free_rtt(rtt_addr);
+
+ return 0;
+}
+
+static unsigned long addr_range_desc(unsigned long phys, unsigned long size)
+{
+ unsigned long out = 0;
+
+ switch (size) {
+ case P4D_SIZE:
+ out = 0 | (1 << 2);
+ break;
+ case PUD_SIZE:
+ out = 1 | (1 << 2);
+ break;
+ case PMD_SIZE:
+ out = 2 | (1 << 2);
+ break;
+ case PAGE_SIZE:
+ out = 3 | (1 << 2);
+ break;
+ default:
+ /*
+ * Only support mapping at the page level granulatity when
+ * it's an unusual length. This should get us back onto a larger
+ * block size for the subsequent mappings.
+ */
+ out = 3 | ((MIN(size >> PAGE_SHIFT, PTRS_PER_PTE - 1)) << 2);
+ break;
+ }
+
+ WARN_ON(phys & ~PAGE_MASK);
+
+ out |= phys & PAGE_MASK;
+
+ return out;
+}
+
+int realm_map_protected(struct kvm *kvm,
+ unsigned long ipa,
+ kvm_pfn_t pfn,
+ unsigned long map_size,
+ struct kvm_mmu_memory_cache *memcache)
+{
+ struct realm *realm = &kvm->arch.realm;
+ phys_addr_t phys = __pfn_to_phys(pfn);
+ phys_addr_t rd = virt_to_phys(realm->rd);
+ unsigned long base_ipa = ipa;
+ unsigned long ipa_top = ipa + map_size;
+ int map_level = IS_ALIGNED(map_size, RMM_L2_BLOCK_SIZE) ?
+ RMM_RTT_BLOCK_LEVEL : RMM_RTT_MAX_LEVEL;
+ int ret = 0;
+
+ if (WARN_ON(!IS_ALIGNED(map_size, PAGE_SIZE) ||
+ !IS_ALIGNED(ipa, map_size)))
+ return -EINVAL;
+
+ if (map_level < RMM_RTT_MAX_LEVEL) {
+ /*
+ * A temporary RTT is needed during the map, precreate it,
+ * however if there is an error (e.g. missing parent tables)
+ * this will be handled below.
+ */
+ realm_create_rtt_levels(realm, ipa, map_level,
+ RMM_RTT_MAX_LEVEL, memcache);
+ }
+
+ if (delegate_range(phys, map_size)) {
+ /*
+ * It's likely we raced with another VCPU on the same
+ * fault. Assume the other VCPU has handled the fault
+ * and return to the guest.
+ */
+ return 0;
+ }
+
+ while (ipa < ipa_top) {
+ unsigned long flags = RMI_ADDR_TYPE_SINGLE;
+ unsigned long range_desc = addr_range_desc(phys, ipa_top - ipa);
+ unsigned long out_top;
+
+ ret = rmi_rtt_data_map(rd, ipa, ipa_top, flags, range_desc,
+ &out_top);
+
+ if (RMI_RETURN_STATUS(ret) == RMI_ERROR_RTT) {
+ /* Create missing RTTs and retry */
+ int level = RMI_RETURN_INDEX(ret);
+
+ WARN_ON(level == RMM_RTT_MAX_LEVEL);
+ ret = realm_create_rtt_levels(realm, ipa, level,
+ RMM_RTT_MAX_LEVEL,
+ memcache);
+ if (ret)
+ goto err_undelegate;
+
+ ret = rmi_rtt_data_map(rd, ipa, ipa_top, flags,
+ range_desc, &out_top);
+ }
+
+ if (WARN_ON(ret))
+ goto err_undelegate;
+
+ phys += out_top - ipa;
+ ipa = out_top;
+ }
+
+ if (map_size == RMM_L2_BLOCK_SIZE) {
+ ret = fold_rtt(realm, base_ipa, map_level + 1);
+ if (WARN_ON(ret))
+ goto err;
+ }
+
+ return 0;
+
+err_undelegate:
+ if (WARN_ON(undelegate_range(phys, map_size))) {
+ /* Page can't be returned to NS world so is lost */
+ get_page(phys_to_page(phys));
+ }
+err:
+ realm_unmap_private_range(kvm, base_ipa, ipa, true);
+ return -ENXIO;
+}
+
+int realm_map_non_secure(struct realm *realm,
+ unsigned long ipa,
+ kvm_pfn_t pfn,
+ unsigned long size,
+ enum kvm_pgtable_prot prot,
+ struct kvm_mmu_memory_cache *memcache)
+{
+ unsigned long attr;
+ phys_addr_t rd = virt_to_phys(realm->rd);
+ phys_addr_t phys = __pfn_to_phys(pfn);
+ unsigned long offset;
+ /* TODO: Support block mappings */
+ int map_level = RMM_RTT_MAX_LEVEL;
+ int map_size = rmi_rtt_level_mapsize(map_level);
+ int ret = 0;
+
+ if (WARN_ON(!IS_ALIGNED(size, PAGE_SIZE) ||
+ !IS_ALIGNED(ipa, size)))
+ return -EINVAL;
+
+ switch (prot & (KVM_PGTABLE_PROT_DEVICE | KVM_PGTABLE_PROT_NORMAL_NC)) {
+ case KVM_PGTABLE_PROT_DEVICE | KVM_PGTABLE_PROT_NORMAL_NC:
+ return -EINVAL;
+ case KVM_PGTABLE_PROT_DEVICE:
+ attr = PTE_S2_MEMATTR(MT_S2_FWB_DEVICE_nGnRE);
+ break;
+ case KVM_PGTABLE_PROT_NORMAL_NC:
+ attr = PTE_S2_MEMATTR(MT_S2_FWB_NORMAL_NC);
+ break;
+ default:
+ attr = PTE_S2_MEMATTR(MT_S2_FWB_NORMAL);
+ }
+
+ for (offset = 0; offset < size; offset += map_size) {
+ /*
+ * realm_map_ipa() enforces that the memory is writable,
+ * so for now we permit both read and write.
+ */
+ unsigned long desc = kvm_phys_to_pte(phys) | attr |
+ KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R |
+ KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W;
+ ret = rmi_rtt_map_unprotected(rd, ipa, map_level, desc);
+
+ if (RMI_RETURN_STATUS(ret) == RMI_ERROR_RTT) {
+ /* Create missing RTTs and retry */
+ int level = RMI_RETURN_INDEX(ret);
+
+ ret = realm_create_rtt_levels(realm, ipa, level,
+ map_level, memcache);
+ if (ret)
+ return -ENXIO;
+
+ ret = rmi_rtt_map_unprotected(rd, ipa, map_level, desc);
+ }
+ /*
+ * RMI_ERROR_RTT can be reported for two reasons: either the
+ * RTT tables are not there, or there is an RTTE already
+ * present for the address. The above call to create RTTs
+ * handles the first case, and in the second case this
+ * indicates that another thread has already populated the RTTE
+ * for us, so we can ignore the error and continue.
+ */
+ if (ret && RMI_RETURN_STATUS(ret) != RMI_ERROR_RTT)
+ return -ENXIO;
+
+ ipa += map_size;
+ phys += map_size;
+ }
+
+ return 0;
+}
+
static int populate_region_cb(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
struct page *src_page, void *opaque)
{
--
2.43.0
^ permalink raw reply related
* [PATCH v13 26/48] arm64: RMI: Create the realm descriptor
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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>
---
Changes since v12:
* Since RMM page size is now equal to the host's page size various
calculations are simplified.
* Switch to using range based APIs to delegate/undelegate.
* VMID handling is now handled entirely by the RMM.
---
arch/arm64/kvm/rmi.c | 94 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 92 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 38349c7b34f4..d5fee203824b 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -649,6 +649,83 @@ static void realm_unmap_shared_range(struct kvm *kvm,
start, end);
}
+static int realm_create_rd(struct kvm *kvm)
+{
+ struct realm *realm = &kvm->arch.realm;
+ struct realm_params *params = realm->params;
+ void *rd = NULL;
+ phys_addr_t rd_phys, params_phys;
+ size_t pgd_size = kvm_pgtable_stage2_pgd_size(kvm->arch.mmu.vtcr);
+ int i, r;
+
+ realm->ia_bits = VTCR_EL2_IPA(kvm->arch.mmu.vtcr);
+
+ if (WARN_ON(realm->rd || !realm->params))
+ return -EEXIST;
+
+ rd = (void *)__get_free_page(GFP_KERNEL);
+ if (!rd)
+ return -ENOMEM;
+
+ rd_phys = virt_to_phys(rd);
+ if (delegate_page(rd_phys)) {
+ r = -ENXIO;
+ goto free_rd;
+ }
+
+ if (delegate_range(kvm->arch.mmu.pgd_phys, pgd_size)) {
+ r = -ENXIO;
+ goto out_undelegate_tables;
+ }
+
+ params->s2sz = VTCR_EL2_IPA(kvm->arch.mmu.vtcr);
+ params->rtt_level_start = get_start_level(realm);
+ params->rtt_num_start = pgd_size / PAGE_SIZE;
+ params->rtt_base = kvm->arch.mmu.pgd_phys;
+
+ if (kvm->arch.arm_pmu) {
+ params->pmu_num_ctrs = kvm->arch.nr_pmu_counters;
+ params->flags |= RMI_REALM_PARAM_FLAG_PMU;
+ }
+
+ if (kvm_lpa2_is_enabled())
+ params->flags |= RMI_REALM_PARAM_FLAG_LPA2;
+
+ 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:
+ if (WARN_ON(undelegate_range(kvm->arch.mmu.pgd_phys, i))) {
+ /* Leak the pages if they cannot be returned */
+ kvm->arch.mmu.pgt = NULL;
+ }
+ if (WARN_ON(undelegate_page(rd_phys))) {
+ /* Leak the page if it isn't returned */
+ return r;
+ }
+free_rd:
+ free_page((unsigned long)rd);
+ return r;
+}
+
static void realm_unmap_private_range(struct kvm *kvm,
unsigned long start,
unsigned long end,
@@ -893,8 +970,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)
--
2.43.0
^ permalink raw reply related
* [PATCH v13 25/48] arm64: RMI: Set RIPAS of initial memslots
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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(+)
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index b48f4e12e4e0..38349c7b34f4 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -883,12 +883,44 @@ static int realm_set_ipa_state(struct kvm_vcpu *vcpu,
return ret;
}
+static int realm_init_ipa_state(struct kvm *kvm,
+ unsigned long gfn,
+ unsigned long pages)
+{
+ return ripas_change(kvm, NULL, gfn_to_gpa(gfn), gfn_to_gpa(gfn + pages),
+ RIPAS_INIT, NULL);
+}
+
static int realm_ensure_created(struct kvm *kvm)
{
/* Provided in later patch */
return -ENXIO;
}
+static int set_ripas_of_protected_regions(struct kvm *kvm)
+{
+ struct kvm_memslots *slots;
+ struct kvm_memory_slot *memslot;
+ int idx, bkt;
+ int ret = 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);
+
+ return ret;
+}
+
int kvm_arm_rmi_populate(struct kvm *kvm,
struct kvm_arm_rmi_populate *args)
{
@@ -1206,6 +1238,10 @@ int kvm_activate_realm(struct kvm *kvm)
return ret;
}
+ ret = set_ripas_of_protected_regions(kvm);
+ if (ret)
+ return ret;
+
ret = rmi_realm_activate(virt_to_phys(realm->rd));
if (ret)
return -ENXIO;
--
2.43.0
^ permalink raw reply related
* [PATCH v13 24/48] arm64: RMI: Allow populating initial contents
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 v12:
* The ioctl now updates the structure with the amount populated rather
than returning this through the ioctl return code.
* Use the new RMM v2.0 range based RMI calls.
* Adapt to upstream changes in kvm_gmem_populate().
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 | 13 ++++
arch/arm64/kvm/rmi.c | 111 +++++++++++++++++++++++++++++++
4 files changed, 129 insertions(+)
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index 46b0cbe6c202..bf663bb240c4 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -96,6 +96,10 @@ int kvm_rec_enter(struct kvm_vcpu *vcpu);
int kvm_rec_pre_enter(struct kvm_vcpu *vcpu);
int handle_rec_exit(struct kvm_vcpu *vcpu, int rec_run_status);
+struct kvm_arm_rmi_populate;
+
+int kvm_arm_rmi_populate(struct kvm *kvm,
+ struct kvm_arm_rmi_populate *arg);
void kvm_realm_unmap_range(struct kvm *kvm,
unsigned long ipa,
unsigned long size,
diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index 1cac6dfc0972..b495dfd3a8b4 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -39,6 +39,7 @@ menuconfig KVM
select GUEST_PERF_EVENTS if PERF_EVENTS
select KVM_GUEST_MEMFD
select KVM_GENERIC_MEMORY_ATTRIBUTES
+ select HAVE_KVM_ARCH_GMEM_POPULATE
help
Support hosting virtualized guest machines.
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index badb94b398bc..43d05da7e694 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2089,6 +2089,19 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
return -EFAULT;
return kvm_vm_ioctl_get_reg_writable_masks(kvm, &range);
}
+ case KVM_ARM_RMI_POPULATE: {
+ struct kvm_arm_rmi_populate req;
+ int ret;
+
+ if (!kvm_is_realm(kvm))
+ return -ENXIO;
+ if (copy_from_user(&req, argp, sizeof(req)))
+ return -EFAULT;
+ ret = kvm_arm_rmi_populate(kvm, &req);
+ if (copy_to_user(argp, &req, sizeof(req)))
+ return -EFAULT;
+ return ret;
+ }
default:
return -EINVAL;
}
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 13eed6f0b9eb..b48f4e12e4e0 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -718,6 +718,80 @@ void kvm_realm_unmap_range(struct kvm *kvm, unsigned long start,
realm_unmap_private_range(kvm, start, end, may_block);
}
+static int realm_create_protected_data_page(struct kvm *kvm,
+ unsigned long ipa,
+ kvm_pfn_t dst_pfn,
+ kvm_pfn_t src_pfn,
+ unsigned long flags)
+{
+ struct realm *realm = &kvm->arch.realm;
+ phys_addr_t rd = virt_to_phys(realm->rd);
+ phys_addr_t dst_phys, src_phys;
+ int ret;
+
+ dst_phys = __pfn_to_phys(dst_pfn);
+ src_phys = __pfn_to_phys(src_pfn);
+
+ if (delegate_page(dst_phys))
+ return -ENXIO;
+
+ ret = rmi_rtt_data_map_init(rd, dst_phys, ipa, src_phys, flags);
+ if (RMI_RETURN_STATUS(ret) == RMI_ERROR_RTT) {
+ /* Create missing RTTs and retry */
+ int level = RMI_RETURN_INDEX(ret);
+
+ KVM_BUG_ON(level == RMM_RTT_MAX_LEVEL, kvm);
+
+ ret = realm_create_rtt_levels(realm, ipa, level,
+ RMM_RTT_MAX_LEVEL, NULL);
+ if (!ret) {
+ ret = rmi_rtt_data_map_init(rd, dst_phys, ipa, src_phys,
+ flags);
+ }
+ }
+
+ if (ret) {
+ if (WARN_ON(undelegate_page(dst_phys))) {
+ /* Undelegate failed, so we leak the page */
+ get_page(pfn_to_page(dst_pfn));
+ }
+ }
+
+ return ret;
+}
+
+static int populate_region_cb(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
+ struct page *src_page, void *opaque)
+{
+ unsigned long data_flags = *(unsigned long *)opaque;
+ phys_addr_t ipa = gfn_to_gpa(gfn);
+
+ if (!src_page)
+ return -EOPNOTSUPP;
+
+ return realm_create_protected_data_page(kvm, ipa, pfn,
+ page_to_pfn(src_page),
+ data_flags);
+}
+
+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,
@@ -815,6 +889,43 @@ 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;
+ long pages_populated;
+ 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) ||
+ !IS_ALIGNED(args->source_uaddr, PAGE_SIZE))
+ return -EINVAL;
+
+ ret = realm_ensure_created(kvm);
+ if (ret)
+ return ret;
+
+ if (args->flags & KVM_ARM_RMI_POPULATE_FLAGS_MEASURE)
+ data_flags |= RMI_MEASURE_CONTENT;
+
+ pages_populated = populate_region(kvm, gpa_to_gfn(ipa_start),
+ args->size >> PAGE_SHIFT,
+ args->source_uaddr, data_flags);
+
+ if (pages_populated < 0)
+ return pages_populated;
+
+ args->size -= pages_populated << PAGE_SHIFT;
+ args->source_uaddr += pages_populated << PAGE_SHIFT;
+ args->base += pages_populated << PAGE_SHIFT;
+
+ return 0;
+}
+
static void kvm_complete_ripas_change(struct kvm_vcpu *vcpu)
{
struct kvm *kvm = vcpu->kvm;
--
2.43.0
^ permalink raw reply related
* [PATCH v13 23/48] KVM: arm64: Expose support for private memory
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
Select KVM_GENERIC_MEMORY_ATTRIBUTES and provide the necessary support
functions.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v12:
* Only define kvm_arch_has_private_mem() when
CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES is set to avoid build issues
when KVM is disabled.
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 | 4 ++++
arch/arm64/kvm/Kconfig | 1 +
arch/arm64/kvm/mmu.c | 24 ++++++++++++++++++++++++
3 files changed, 29 insertions(+)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 64304848aad4..1efea996f474 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -1486,6 +1486,10 @@ struct kvm *kvm_arch_alloc_vm(void);
#define vcpu_is_protected(vcpu) kvm_vm_is_protected((vcpu)->kvm)
+#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+#define kvm_arch_has_private_mem(kvm) ((kvm)->arch.is_realm)
+#endif
+
int kvm_arm_vcpu_finalize(struct kvm_vcpu *vcpu, int feature);
bool kvm_arm_vcpu_is_finalized(struct kvm_vcpu *vcpu);
diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index 4f803fd1c99a..1cac6dfc0972 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -38,6 +38,7 @@ menuconfig KVM
select SCHED_INFO
select GUEST_PERF_EVENTS if PERF_EVENTS
select KVM_GUEST_MEMFD
+ select KVM_GENERIC_MEMORY_ATTRIBUTES
help
Support hosting virtualized guest machines.
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index b705ad6c6c8b..bad93938acdb 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -2494,6 +2494,30 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
return ret;
}
+#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+bool kvm_arch_pre_set_memory_attributes(struct kvm *kvm,
+ struct kvm_gfn_range *range)
+{
+ WARN_ON_ONCE(!kvm_arch_has_private_mem(kvm));
+ return false;
+}
+
+bool kvm_arch_post_set_memory_attributes(struct kvm *kvm,
+ struct kvm_gfn_range *range)
+{
+ if (WARN_ON_ONCE(!kvm_arch_has_private_mem(kvm)))
+ return false;
+
+ if (range->arg.attributes & KVM_MEMORY_ATTRIBUTE_PRIVATE)
+ range->attr_filter = KVM_FILTER_SHARED;
+ else
+ range->attr_filter = KVM_FILTER_PRIVATE;
+ kvm_unmap_gfn_range(kvm, range);
+
+ return false;
+}
+#endif
+
void kvm_arch_free_memslot(struct kvm *kvm, struct kvm_memory_slot *slot)
{
}
--
2.43.0
^ permalink raw reply related
* [PATCH v13 22/48] KVM: arm64: Handle realm MMIO emulation
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 <gshan@redhat.com>
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(-)
diff --git a/arch/arm64/kvm/inject_fault.c b/arch/arm64/kvm/inject_fault.c
index 89982bd3345f..6492397b73d7 100644
--- a/arch/arm64/kvm/inject_fault.c
+++ b/arch/arm64/kvm/inject_fault.c
@@ -228,7 +228,9 @@ static void inject_abt32(struct kvm_vcpu *vcpu, bool is_pabt, u32 addr)
static void __kvm_inject_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr)
{
- if (vcpu_el1_is_32bit(vcpu))
+ if (unlikely(vcpu_is_rec(vcpu)))
+ vcpu->arch.rec.run->enter.flags |= REC_ENTER_FLAG_INJECT_SEA;
+ else if (vcpu_el1_is_32bit(vcpu))
inject_abt32(vcpu, iabt, addr);
else
inject_abt64(vcpu, iabt, addr);
diff --git a/arch/arm64/kvm/mmio.c b/arch/arm64/kvm/mmio.c
index e2285ed8c91d..6a8cb927fcca 100644
--- a/arch/arm64/kvm/mmio.c
+++ b/arch/arm64/kvm/mmio.c
@@ -6,6 +6,7 @@
#include <linux/kvm_host.h>
#include <asm/kvm_emulate.h>
+#include <asm/rmi_smc.h>
#include <trace/events/kvm.h>
#include "trace.h"
@@ -138,14 +139,21 @@ int kvm_handle_mmio_return(struct kvm_vcpu *vcpu)
trace_kvm_mmio(KVM_TRACE_MMIO_READ, len, run->mmio.phys_addr,
&data);
data = vcpu_data_host_to_guest(vcpu, data, len);
- vcpu_set_reg(vcpu, kvm_vcpu_dabt_get_rd(vcpu), data);
+
+ if (vcpu_is_rec(vcpu))
+ vcpu->arch.rec.run->enter.gprs[0] = data;
+ else
+ vcpu_set_reg(vcpu, kvm_vcpu_dabt_get_rd(vcpu), data);
}
/*
* The MMIO instruction is emulated and should not be re-executed
* in the guest.
*/
- kvm_incr_pc(vcpu);
+ if (vcpu_is_rec(vcpu))
+ vcpu->arch.rec.run->enter.flags |= REC_ENTER_FLAG_EMULATED_MMIO;
+ else
+ kvm_incr_pc(vcpu);
return 1;
}
@@ -167,14 +175,14 @@ int io_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa)
* No valid syndrome? Ask userspace for help if it has
* volunteered to do so, and bail out otherwise.
*
- * In the protected VM case, there isn't much userspace can do
+ * In the protected/realm VM case, there isn't much userspace can do
* though, so directly deliver an exception to the guest.
*/
if (!kvm_vcpu_dabt_isvalid(vcpu)) {
trace_kvm_mmio_nisv(*vcpu_pc(vcpu), esr,
kvm_vcpu_get_hfar(vcpu), fault_ipa);
- if (vcpu_is_protected(vcpu))
+ if (vcpu_is_protected(vcpu) || vcpu_is_rec(vcpu))
return kvm_inject_sea_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
if (test_bit(KVM_ARCH_FLAG_RETURN_NISV_IO_ABORT_TO_USER,
diff --git a/arch/arm64/kvm/rmi-exit.c b/arch/arm64/kvm/rmi-exit.c
index f5701153dec0..b4843f094615 100644
--- a/arch/arm64/kvm/rmi-exit.c
+++ b/arch/arm64/kvm/rmi-exit.c
@@ -25,6 +25,20 @@ static int rec_exit_reason_notimpl(struct kvm_vcpu *vcpu)
static int rec_exit_sync_dabt(struct kvm_vcpu *vcpu)
{
+ struct realm_rec *rec = &vcpu->arch.rec;
+
+ /*
+ * In the case of a write, copy over gprs[0] to the target GPR,
+ * preparing to handle MMIO write fault. The content to be written has
+ * been saved to gprs[0] by the RMM (even if another register was used
+ * by the guest). In the case of normal memory access this is redundant
+ * (the guest will replay the instruction), but the overhead is
+ * minimal.
+ */
+ if (kvm_vcpu_dabt_iswrite(vcpu) && kvm_vcpu_dabt_isvalid(vcpu))
+ vcpu_set_reg(vcpu, kvm_vcpu_dabt_get_rd(vcpu),
+ rec->run->exit.gprs[0]);
+
return kvm_handle_guest_abort(vcpu);
}
--
2.43.0
^ permalink raw reply related
* [PATCH v13 21/48] arm64: RMI: Handle RMI_EXIT_RIPAS_CHANGE
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 this 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 implementation isn't spec
compliant in this case, this should be fixed in a later release.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v12:
* Switch to the new RMM v2.0 RMI_RTT_DATA_UNMAP which can unmap an
address range.
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(-)
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index 7bec3a3976e7..46b0cbe6c202 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -96,6 +96,12 @@ int kvm_rec_enter(struct kvm_vcpu *vcpu);
int kvm_rec_pre_enter(struct kvm_vcpu *vcpu);
int handle_rec_exit(struct kvm_vcpu *vcpu, int rec_run_status);
+void kvm_realm_unmap_range(struct kvm *kvm,
+ unsigned long ipa,
+ unsigned long size,
+ bool unmap_private,
+ bool may_block);
+
static inline bool kvm_realm_is_private_address(struct realm *realm,
unsigned long addr)
{
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 41152abf55b2..b705ad6c6c8b 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -319,6 +319,7 @@ static void invalidate_icache_guest_page(void *va, size_t size)
* @start: The intermediate physical base address of the range to unmap
* @size: The size of the area to unmap
* @may_block: Whether or not we are permitted to block
+ * @only_shared: If true then protected mappings should not be unmapped
*
* Clear a range of stage-2 mappings, lowering the various ref-counts. Must
* be called while holding mmu_lock (unless for freeing the stage2 pgd before
@@ -326,7 +327,7 @@ static void invalidate_icache_guest_page(void *va, size_t size)
* with things behind our backs.
*/
static void __unmap_stage2_range(struct kvm_s2_mmu *mmu, phys_addr_t start, u64 size,
- bool may_block)
+ bool may_block, bool only_shared)
{
struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu);
phys_addr_t end = start + size;
@@ -340,7 +341,7 @@ static void __unmap_stage2_range(struct kvm_s2_mmu *mmu, phys_addr_t start, u64
void kvm_stage2_unmap_range(struct kvm_s2_mmu *mmu, phys_addr_t start,
u64 size, bool may_block)
{
- __unmap_stage2_range(mmu, start, size, may_block);
+ __unmap_stage2_range(mmu, start, size, may_block, false);
}
void kvm_stage2_flush_range(struct kvm_s2_mmu *mmu, phys_addr_t addr, phys_addr_t end)
@@ -2241,7 +2242,8 @@ bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range)
__unmap_stage2_range(&kvm->arch.mmu, range->start << PAGE_SHIFT,
(range->end - range->start) << PAGE_SHIFT,
- range->may_block);
+ range->may_block,
+ !(range->attr_filter & KVM_FILTER_PRIVATE));
kvm_nested_s2_unmap(kvm, range->may_block);
return false;
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index ee8aab098117..13eed6f0b9eb 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -251,6 +251,88 @@ static int undelegate_page(phys_addr_t phys)
return undelegate_range(phys, PAGE_SIZE);
}
+static int find_map_level(struct realm *realm,
+ unsigned long start,
+ unsigned long end)
+{
+ int level = RMM_RTT_MAX_LEVEL;
+
+ while (level > get_start_level(realm)) {
+ unsigned long map_size = rmi_rtt_level_mapsize(level - 1);
+
+ if (!IS_ALIGNED(start, map_size) ||
+ (start + map_size) > end)
+ break;
+
+ level--;
+ }
+
+ return level;
+}
+
+static unsigned long level_to_size(int level)
+{
+ switch (level) {
+ case 0:
+ return PAGE_SIZE;
+ case 1:
+ return PMD_SIZE;
+ case 2:
+ return PUD_SIZE;
+ case 3:
+ return P4D_SIZE;
+ }
+ WARN_ON(1);
+ return 0;
+}
+
+static int undelegate_range_desc(unsigned long desc)
+{
+ unsigned long size = level_to_size(RMI_ADDR_RANGE_SIZE(desc));
+ unsigned long count = RMI_ADDR_RANGE_COUNT(desc);
+ unsigned long addr = RMI_ADDR_RANGE_ADDR(desc);
+ unsigned long state = RMI_ADDR_RANGE_STATE(desc);
+
+ if (state == RMI_OP_MEM_UNDELEGATED)
+ return 0;
+
+ return undelegate_range(addr, size * count);
+}
+
+static phys_addr_t alloc_delegated_granule(struct kvm_mmu_memory_cache *mc)
+{
+ phys_addr_t phys;
+ void *virt;
+
+ if (mc) {
+ virt = kvm_mmu_memory_cache_alloc(mc);
+ } else {
+ virt = (void *)__get_free_page(GFP_ATOMIC | __GFP_ZERO |
+ __GFP_ACCOUNT);
+ }
+
+ if (!virt)
+ return PHYS_ADDR_MAX;
+
+ phys = virt_to_phys(virt);
+ if (delegate_page(phys)) {
+ free_page((unsigned long)virt);
+ return PHYS_ADDR_MAX;
+ }
+
+ return phys;
+}
+
+static phys_addr_t alloc_rtt(struct kvm_mmu_memory_cache *mc)
+{
+ phys_addr_t phys = alloc_delegated_granule(mc);
+
+ if (phys != PHYS_ADDR_MAX)
+ kvm_account_pgtable_pages(phys_to_virt(phys), 1);
+
+ return phys;
+}
+
static int free_delegated_page(phys_addr_t phys)
{
if (WARN_ON(undelegate_page(phys))) {
@@ -271,6 +353,32 @@ static void free_rtt(phys_addr_t phys)
kvm_account_pgtable_pages(phys_to_virt(phys), -1);
}
+static int realm_rtt_create(struct realm *realm,
+ unsigned long addr,
+ int level,
+ phys_addr_t phys)
+{
+ addr = ALIGN_DOWN(addr, rmi_rtt_level_mapsize(level - 1));
+ return rmi_rtt_create(virt_to_phys(realm->rd), phys, addr, level);
+}
+
+static int realm_rtt_fold(struct realm *realm,
+ unsigned long addr,
+ int level,
+ phys_addr_t *rtt_granule)
+{
+ unsigned long out_rtt;
+ int ret;
+
+ addr = ALIGN_DOWN(addr, rmi_rtt_level_mapsize(level - 1));
+ ret = rmi_rtt_fold(virt_to_phys(realm->rd), addr, level, &out_rtt);
+
+ if (rtt_granule)
+ *rtt_granule = out_rtt;
+
+ return ret;
+}
+
static int realm_rtt_destroy(struct realm *realm, unsigned long addr,
int level, phys_addr_t *rtt_granule,
unsigned long *next_addr)
@@ -286,6 +394,38 @@ static int realm_rtt_destroy(struct realm *realm, unsigned long addr,
return ret;
}
+static int realm_create_rtt_levels(struct realm *realm,
+ unsigned long ipa,
+ int level,
+ int max_level,
+ struct kvm_mmu_memory_cache *mc)
+{
+ while (level++ < max_level) {
+ phys_addr_t rtt = alloc_rtt(mc);
+ int ret;
+
+ 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;
+ }
+ }
+
+ return 0;
+}
+
static int realm_tear_down_rtt_level(struct realm *realm, int level,
unsigned long start, unsigned long end)
{
@@ -380,6 +520,62 @@ static int realm_tear_down_rtt_range(struct realm *realm,
start, end);
}
+/*
+ * Returns 0 on successful fold, a negative value on error, a positive value if
+ * we were not able to fold all tables at this level.
+ */
+static int realm_fold_rtt_level(struct realm *realm, int level,
+ unsigned long start, unsigned long end)
+{
+ int not_folded = 0;
+ ssize_t map_size;
+ unsigned long addr, next_addr;
+
+ if (WARN_ON(level > RMM_RTT_MAX_LEVEL))
+ return -EINVAL;
+
+ map_size = rmi_rtt_level_mapsize(level - 1);
+
+ for (addr = start; addr < end; addr = next_addr) {
+ phys_addr_t rtt_granule;
+ int ret;
+ unsigned long align_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)) {
+ case RMI_SUCCESS:
+ free_rtt(rtt_granule);
+ break;
+ case RMI_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) {
+ return ret;
+ } else if (ret == 0) {
+ /* Try again at this level */
+ next_addr = addr;
+ }
+ break;
+ default:
+ WARN_ON(1);
+ return -ENXIO;
+ }
+ }
+
+ return not_folded;
+}
+
void kvm_realm_destroy_rtts(struct kvm *kvm)
{
struct realm *realm = &kvm->arch.realm;
@@ -388,12 +584,272 @@ void kvm_realm_destroy_rtts(struct kvm *kvm)
WARN_ON(realm_tear_down_rtt_range(realm, 0, (1UL << ia_bits)));
}
+static void realm_unmap_shared_range(struct kvm *kvm,
+ int level,
+ unsigned long start,
+ unsigned long end,
+ bool may_block)
+{
+ struct realm *realm = &kvm->arch.realm;
+ unsigned long rd = virt_to_phys(realm->rd);
+ ssize_t map_size = rmi_rtt_level_mapsize(level);
+ unsigned long next_addr, addr;
+ unsigned long shared_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) {
+ unsigned long align_addr = ALIGN(addr, map_size);
+ int ret;
+
+ 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)) {
+ case RMI_SUCCESS:
+ break;
+ case RMI_ERROR_RTT:
+ if (next_addr == addr) {
+ /*
+ * There's a mapping here, but it's not a block
+ * mapping, so reset next_addr to the next block
+ * boundary and recurse to clear out the pages
+ * one level deeper.
+ */
+ 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);
+}
+
+static void realm_unmap_private_range(struct kvm *kvm,
+ unsigned long start,
+ unsigned long end,
+ bool may_block)
+{
+ struct realm *realm = &kvm->arch.realm;
+ unsigned long rd = virt_to_phys(realm->rd);
+ unsigned long next_addr, addr;
+ int ret;
+
+ for (addr = start; addr < end; addr = next_addr) {
+ unsigned long out_range;
+ unsigned long flags = RMI_ADDR_TYPE_SINGLE;
+ /* TODO: Optimise using RMI_ADDR_TYPE_LIST */
+
+retry:
+ ret = rmi_rtt_data_unmap(rd, addr, end, flags, 0,
+ &next_addr, &out_range, NULL);
+
+ if (RMI_RETURN_STATUS(ret) == RMI_ERROR_RTT) {
+ phys_addr_t rtt;
+
+ if (next_addr > addr)
+ continue; /* UNASSIGNED */
+
+ rtt = alloc_rtt(NULL);
+ if (WARN_ON(rtt == PHYS_ADDR_MAX))
+ return;
+ ret = realm_rtt_create(realm, addr,
+ RMI_RETURN_INDEX(ret) + 1, rtt);
+ if (WARN_ON(ret)) {
+ free_rtt(rtt);
+ return;
+ }
+ goto retry;
+ } else if (WARN_ON(ret)) {
+ continue;
+ }
+
+ ret = undelegate_range_desc(out_range);
+ if (WARN_ON(ret))
+ break;
+
+ if (may_block)
+ cond_resched_rwlock_write(&kvm->mmu_lock);
+ }
+
+ realm_fold_rtt_level(realm, get_start_level(realm) + 1,
+ start, end);
+}
+
+void kvm_realm_unmap_range(struct kvm *kvm, unsigned long start,
+ unsigned long size, bool unmap_private,
+ bool may_block)
+{
+ unsigned long end = start + size;
+ struct realm *realm = &kvm->arch.realm;
+
+ if (!kvm_realm_is_created(kvm))
+ return;
+
+ end = min(BIT(realm->ia_bits - 1), end);
+
+ 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);
+}
+
+enum ripas_action {
+ RIPAS_INIT,
+ RIPAS_SET,
+};
+
+static int ripas_change(struct kvm *kvm,
+ struct kvm_vcpu *vcpu,
+ unsigned long ipa,
+ unsigned long end,
+ enum ripas_action action,
+ unsigned long *top_ipa)
+{
+ struct realm *realm = &kvm->arch.realm;
+ phys_addr_t rd_phys = virt_to_phys(realm->rd);
+ phys_addr_t rec_phys;
+ struct kvm_mmu_memory_cache *memcache = NULL;
+ int ret = 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) {
+ unsigned long next = ~0;
+
+ switch (action) {
+ case RIPAS_INIT:
+ ret = rmi_rtt_init_ripas(rd_phys, ipa, end, &next);
+ break;
+ case RIPAS_SET:
+ ret = rmi_rtt_set_ripas(rd_phys, rec_phys, ipa, end,
+ &next);
+ break;
+ }
+
+ switch (RMI_RETURN_STATUS(ret)) {
+ case RMI_SUCCESS:
+ ipa = next;
+ break;
+ case RMI_ERROR_RTT: {
+ int err_level = RMI_RETURN_INDEX(ret);
+ int level = find_map_level(realm, ipa, end);
+
+ if (err_level >= level) {
+ /* FIXME: Ugly hack to skip regions which are
+ * already RIPAS_RAM
+ */
+ ipa += PAGE_SIZE;
+ break;
+ return -EINVAL;
+ }
+
+ ret = realm_create_rtt_levels(realm, ipa, err_level,
+ level, memcache);
+ if (ret)
+ return ret;
+ /* Retry with the RTT levels in place */
+ break;
+ }
+ default:
+ WARN_ON(1);
+ return -ENXIO;
+ }
+ }
+
+ if (top_ipa)
+ *top_ipa = ipa;
+
+ return 0;
+}
+
+static int realm_set_ipa_state(struct kvm_vcpu *vcpu,
+ unsigned long start,
+ unsigned long end,
+ unsigned long ripas,
+ unsigned long *top_ipa)
+{
+ struct kvm *kvm = vcpu->kvm;
+ int ret = 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);
+
+ return ret;
+}
+
static int realm_ensure_created(struct kvm *kvm)
{
/* Provided in later patch */
return -ENXIO;
}
+static void kvm_complete_ripas_change(struct kvm_vcpu *vcpu)
+{
+ struct kvm *kvm = vcpu->kvm;
+ struct realm_rec *rec = &vcpu->arch.rec;
+ unsigned long base = rec->run->exit.ripas_base;
+ unsigned long top = rec->run->exit.ripas_top;
+ unsigned long ripas = rec->run->exit.ripas_value;
+ unsigned long top_ipa;
+ int ret;
+
+ 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);
+
+ /*
+ * If this function is called again before the REC_ENTER call then
+ * avoid calling realm_set_ipa_state() again by changing to the value
+ * of ripas_base for the part that has already been covered. The RMM
+ * ignores the contains of the rec_exit structure so this doesn't
+ * affect the RMM.
+ */
+ rec->run->exit.ripas_base = base;
+}
+
/*
* kvm_rec_pre_enter - Complete operations before entering a REC
*
@@ -419,6 +875,9 @@ int kvm_rec_pre_enter(struct kvm_vcpu *vcpu)
for (int i = 0; i < REC_RUN_GPRS; i++)
rec->run->enter.gprs[i] = vcpu_get_reg(vcpu, i);
break;
+ case RMI_EXIT_RIPAS_CHANGE:
+ kvm_complete_ripas_change(vcpu);
+ break;
}
return 1;
--
2.43.0
^ permalink raw reply related
* [PATCH v13 20/48] arm64: RMI: Handle realm enter/exit
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 <gshan@redhat.com>
---
Changes since v12:
* Call guest_state_{enter,exit}_irqoff() around rmi_rec_enter().
* Add handling of the IRQ exception case where IRQs need to be briefly
enabled before exiting guest timing.
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 | 26 ++++-
arch/arm64/kvm/rmi-exit.c | 178 +++++++++++++++++++++++++++++++
arch/arm64/kvm/rmi.c | 43 ++++++++
5 files changed, 247 insertions(+), 6 deletions(-)
create mode 100644 arch/arm64/kvm/rmi-exit.c
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index 4e2c61e71a38..7bec3a3976e7 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -92,6 +92,10 @@ void kvm_destroy_realm(struct kvm *kvm);
void kvm_realm_destroy_rtts(struct kvm *kvm);
void kvm_destroy_rec(struct kvm_vcpu *vcpu);
+int kvm_rec_enter(struct kvm_vcpu *vcpu);
+int kvm_rec_pre_enter(struct kvm_vcpu *vcpu);
+int handle_rec_exit(struct kvm_vcpu *vcpu, int rec_run_status);
+
static inline bool kvm_realm_is_private_address(struct realm *realm,
unsigned long addr)
{
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index e17c4077d8e7..4b103bcbe760 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -16,7 +16,7 @@ CFLAGS_handle_exit.o += -Wno-override-init
kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
inject_fault.o va_layout.o handle_exit.o config.o \
guest.o debug.o reset.o sys_regs.o stacktrace.o \
- vgic-sys-reg-v3.o fpsimd.o pkvm.o rmi.o \
+ vgic-sys-reg-v3.o fpsimd.o pkvm.o rmi.o rmi-exit.o \
arch_timer.o trng.o vmid.o emulate-nested.o nested.o at.o \
vgic/vgic.o vgic/vgic-init.o \
vgic/vgic-irqfd.o vgic/vgic-v2.o \
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 45eff4c41cde..badb94b398bc 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1311,6 +1311,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);
+
/*
* Preparing the interrupts to be injected also
* involves poking the GIC, which must be done in a
@@ -1358,7 +1361,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++;
@@ -1404,7 +1410,9 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
* context synchronization event) is necessary to ensure that
* pending interrupts are taken.
*/
- if (ARM_EXCEPTION_CODE(ret) == ARM_EXCEPTION_IRQ) {
+ if (ARM_EXCEPTION_CODE(ret) == ARM_EXCEPTION_IRQ ||
+ (vcpu_is_rec(vcpu) &&
+ vcpu->arch.rec.run->exit.exit_reason == RMI_EXIT_IRQ)) {
local_irq_enable();
isb();
local_irq_disable();
@@ -1416,8 +1424,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)) {
+ /*
+ * Exit types that need handling before we can be
+ * preempted
+ */
+ handle_exit_early(vcpu, ret);
+ }
kvm_nested_sync_hwstate(vcpu);
@@ -1442,7 +1455,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 */
diff --git a/arch/arm64/kvm/rmi-exit.c b/arch/arm64/kvm/rmi-exit.c
new file mode 100644
index 000000000000..f5701153dec0
--- /dev/null
+++ b/arch/arm64/kvm/rmi-exit.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 ARM Ltd.
+ */
+
+#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>
+
+typedef int (*exit_handler_fn)(struct kvm_vcpu *vcpu);
+
+static int rec_exit_reason_notimpl(struct kvm_vcpu *vcpu)
+{
+ struct realm_rec *rec = &vcpu->arch.rec;
+
+ vcpu_err(vcpu, "Unhandled exit reason from realm (ESR: %#llx)\n",
+ rec->run->exit.esr);
+ return -ENXIO;
+}
+
+static int rec_exit_sync_dabt(struct kvm_vcpu *vcpu)
+{
+ return kvm_handle_guest_abort(vcpu);
+}
+
+static int rec_exit_sync_iabt(struct kvm_vcpu *vcpu)
+{
+ struct realm_rec *rec = &vcpu->arch.rec;
+
+ vcpu_err(vcpu, "Unhandled instruction abort (ESR: %#llx).\n",
+ rec->run->exit.esr);
+ return -ENXIO;
+}
+
+static int rec_exit_sys_reg(struct kvm_vcpu *vcpu)
+{
+ struct realm_rec *rec = &vcpu->arch.rec;
+ unsigned long esr = kvm_vcpu_get_esr(vcpu);
+ int rt = kvm_vcpu_sys_get_rt(vcpu);
+ bool is_write = !(esr & 1);
+ int ret;
+
+ 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);
+
+ return ret;
+}
+
+static exit_handler_fn rec_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
+};
+
+static int rec_exit_psci(struct kvm_vcpu *vcpu)
+{
+ struct realm_rec *rec = &vcpu->arch.rec;
+ int i;
+
+ for (i = 0; i < REC_RUN_GPRS; i++)
+ vcpu_set_reg(vcpu, i, rec->run->exit.gprs[i]);
+
+ return kvm_smccc_call_handler(vcpu);
+}
+
+static int rec_exit_ripas_change(struct kvm_vcpu *vcpu)
+{
+ struct kvm *kvm = vcpu->kvm;
+ struct realm *realm = &kvm->arch.realm;
+ struct realm_rec *rec = &vcpu->arch.rec;
+ unsigned long base = rec->run->exit.ripas_base;
+ unsigned long top = rec->run->exit.ripas_top;
+ unsigned long ripas = 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_FAULT requires an return code of -EFAULT, see the
+ * API documentation
+ */
+ return -EFAULT;
+}
+
+static void update_arch_timer_irq_lines(struct kvm_vcpu *vcpu)
+{
+ struct realm_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 > 0 to return to guest, < 0 on error, 0 (and set exit_reason) on
+ * proper exit to userspace.
+ */
+int handle_rec_exit(struct kvm_vcpu *vcpu, int rec_run_ret)
+{
+ struct realm_rec *rec = &vcpu->arch.rec;
+ u8 esr_ec = ESR_ELx_EC(rec->run->exit.esr);
+ unsigned long status, index;
+
+ status = RMI_RETURN_STATUS(rec_run_ret);
+ index = RMI_RETURN_INDEX(rec_run_ret);
+
+ /*
+ * If a PSCI_SYSTEM_OFF request raced with a vcpu executing, we might
+ * see the following status code and index indicating an attempt to run
+ * a REC when the RD state is SYSTEM_OFF. In this case, we just need to
+ * return to user space which can deal with the system event or will try
+ * to run the KVM VCPU again, at which point we will no longer attempt
+ * to enter the Realm because we will have a sleep request pending on
+ * the VCPU as a result of KVM's PSCI handling.
+ */
+ if (status == RMI_ERROR_REALM && index == 1) {
+ vcpu->run->exit_reason = KVM_EXIT_SHUTDOWN;
+ return 0;
+ }
+
+ 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) {
+ case RMI_EXIT_SYNC:
+ /*
+ * HPFAR_EL2_NS is hijacked to indicate a valid HPFAR value,
+ * see __get_fault_info()
+ */
+ vcpu->arch.fault.hpfar_el2 = rec->run->exit.hpfar | HPFAR_EL2_NS;
+ return rec_exit_handlers[esr_ec](vcpu);
+ case RMI_EXIT_IRQ:
+ case RMI_EXIT_FIQ:
+ return 1;
+ case RMI_EXIT_PSCI:
+ return rec_exit_psci(vcpu);
+ case RMI_EXIT_RIPAS_CHANGE:
+ return rec_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;
+ return 0;
+}
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 6daf14c4b413..ee8aab098117 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -394,6 +394,49 @@ static int realm_ensure_created(struct kvm *kvm)
return -ENXIO;
}
+/*
+ * kvm_rec_pre_enter - Complete operations before entering a REC
+ *
+ * Some operations require work to be completed before entering a realm. That
+ * work may require memory allocation so cannot be done in the kvm_rec_enter()
+ * call.
+ *
+ * Return: 1 if we should enter the guest
+ * 0 if we should exit to userspace
+ * < 0 if we should exit to userspace, where the return value indicates
+ * an error
+ */
+int kvm_rec_pre_enter(struct kvm_vcpu *vcpu)
+{
+ struct realm_rec *rec = &vcpu->arch.rec;
+
+ if (kvm_realm_state(vcpu->kvm) != REALM_STATE_ACTIVE)
+ return -EINVAL;
+
+ switch (rec->run->exit.exit_reason) {
+ case RMI_EXIT_HOST_CALL:
+ case RMI_EXIT_PSCI:
+ for (int i = 0; i < REC_RUN_GPRS; i++)
+ rec->run->enter.gprs[i] = vcpu_get_reg(vcpu, i);
+ break;
+ }
+
+ return 1;
+}
+
+int noinstr kvm_rec_enter(struct kvm_vcpu *vcpu)
+{
+ struct realm_rec *rec = &vcpu->arch.rec;
+ int ret;
+
+ guest_state_enter_irqoff();
+ ret = rmi_rec_enter(virt_to_phys(rec->rec_page),
+ virt_to_phys(rec->run));
+ guest_state_exit_irqoff();
+
+ return ret;
+}
+
static void free_rec_aux(struct page **aux_pages,
unsigned int num_aux)
{
--
2.43.0
^ permalink raw reply related
* [PATCH v13 19/48] KVM: arm64: Support timers in realm RECs
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 v12:
* Adapt to upstream changes.
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 | 28 +++++++++++++++++++++++++---
include/kvm/arm_arch_timer.h | 2 ++
2 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
index 600f250753b4..2f2b4befb448 100644
--- a/arch/arm64/kvm/arch_timer.c
+++ b/arch/arm64/kvm/arch_timer.c
@@ -453,6 +453,21 @@ static void kvm_timer_update_irq(struct kvm_vcpu *vcpu, bool new_level,
timer_ctx);
}
+void kvm_realm_timers_update(struct kvm_vcpu *vcpu)
+{
+ struct arch_timer_cpu *arch_timer = &vcpu->arch.timer_cpu;
+ int i;
+
+ for (i = 0; i < NR_KVM_EL0_TIMERS; i++) {
+ struct arch_timer_context *timer = &arch_timer->timers[i];
+ bool status = timer_get_ctl(timer) & ARCH_TIMER_CTRL_IT_STAT;
+ bool level = 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 */
static void timer_emulate(struct arch_timer_context *ctx)
{
@@ -1056,7 +1071,7 @@ static void timer_context_init(struct kvm_vcpu *vcpu, int timerid)
ctxt->timer_id = timerid;
- if (!kvm_vm_is_protected(vcpu->kvm)) {
+ if (!kvm_vm_is_protected(vcpu->kvm) && !kvm_is_realm(vcpu->kvm)) {
if (timerid == TIMER_VTIMER)
ctxt->offset.vm_offset = &kvm->arch.timer_data.voffset;
else
@@ -1087,7 +1102,7 @@ void kvm_timer_vcpu_init(struct kvm_vcpu *vcpu)
timer_context_init(vcpu, i);
/* Synchronize offsets across timers of a VM if not already provided */
- if (!vcpu_is_protected(vcpu) &&
+ if (!vcpu_is_protected(vcpu) && !kvm_is_realm(vcpu->kvm) &&
!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_ptimer(vcpu), 0);
@@ -1561,6 +1576,13 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
return -EINVAL;
}
+ /*
+ * We don't use mapped IRQs for Realms because the RMI doesn't allow
+ * us setting the LR.HW bit in the VGIC.
+ */
+ if (vcpu_is_rec(vcpu))
+ return 0;
+
get_timer_map(vcpu, &map);
ret = kvm_vgic_map_phys_irq(vcpu,
@@ -1692,7 +1714,7 @@ int kvm_vm_ioctl_set_counter_offset(struct kvm *kvm,
if (offset->reserved)
return -EINVAL;
- if (kvm_vm_is_protected(kvm))
+ if (kvm_vm_is_protected(kvm) || kvm_is_realm(kvm))
return -EINVAL;
mutex_lock(&kvm->lock);
diff --git a/include/kvm/arm_arch_timer.h b/include/kvm/arm_arch_timer.h
index 7310841f4512..bab0daafc6b1 100644
--- a/include/kvm/arm_arch_timer.h
+++ b/include/kvm/arm_arch_timer.h
@@ -111,6 +111,8 @@ int kvm_arm_timer_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
int kvm_arm_timer_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
int kvm_arm_timer_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
+void kvm_realm_timers_update(struct kvm_vcpu *vcpu);
+
u64 kvm_phys_timer_read(void);
void kvm_timer_vcpu_load(struct kvm_vcpu *vcpu);
--
2.43.0
^ permalink raw reply related
* [PATCH v13 18/48] arm64: RMI: Support for the VGIC in realms
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
The RMM provides emulation of a VGIC to the realm guest. With RMM v2.0
the registers are passed in the system registers so this works similar
to a normal guest, but kvm_arch_vcpu_put() need reordering to early out,
and realm guests don't support GICv2 even if the host does.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes from v12:
* GIC registers are now passed in the system registers rather than via
rec_entry/rec_exit which removes most of the changes.
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/kvm/arm.c | 11 ++++++++---
arch/arm64/kvm/vgic/vgic-init.c | 2 +-
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 8c50ebd9fba0..45eff4c41cde 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -770,19 +770,24 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
kvm_call_hyp_nvhe(__pkvm_vcpu_put);
}
+ kvm_timer_vcpu_put(vcpu);
+ kvm_vgic_put(vcpu);
+
+ vcpu->cpu = -1;
+
+ if (vcpu_is_rec(vcpu))
+ return;
+
kvm_vcpu_put_debug(vcpu);
kvm_arch_vcpu_put_fp(vcpu);
if (has_vhe())
kvm_vcpu_put_vhe(vcpu);
- kvm_timer_vcpu_put(vcpu);
- kvm_vgic_put(vcpu);
kvm_vcpu_pmu_restore_host(vcpu);
if (vcpu_has_nv(vcpu))
kvm_vcpu_put_hw_mmu(vcpu);
kvm_arm_vmid_clear_active();
vcpu_clear_on_unsupported_cpu(vcpu);
- vcpu->cpu = -1;
}
static void __kvm_arm_vcpu_power_off(struct kvm_vcpu *vcpu)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 9b3091ad868c..9050e556d11f 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -82,7 +82,7 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
* the proper checks already.
*/
if (type == KVM_DEV_TYPE_ARM_VGIC_V2 &&
- !kvm_vgic_global_state.can_emulate_gicv2)
+ (!kvm_vgic_global_state.can_emulate_gicv2 || kvm_is_realm(kvm)))
return -ENODEV;
/*
--
2.43.0
^ permalink raw reply related
* [PATCH v13 17/48] arm64: RMI: Allocate/free RECs to match vCPUs
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 v12:
* Use the new range-based delegation RMI.
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 | 196 +++++++++++++++++++++++++++
6 files changed, 230 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index 39310d9b4e16..d194d91fbc2a 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -708,7 +708,7 @@ static inline bool kvm_realm_is_created(struct kvm *kvm)
static inline bool vcpu_is_rec(struct kvm_vcpu *vcpu)
{
- return false;
+ return kvm_is_realm(vcpu->kvm);
}
#endif /* __ARM64_KVM_EMULATE_H__ */
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 9267a2f2d65b..64304848aad4 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -924,6 +924,9 @@ struct kvm_vcpu_arch {
/* Per-vcpu TLB for VNCR_EL2 -- NULL when !NV */
struct vncr_tlb *vncr_tlb;
+
+ /* Realm meta data */
+ struct realm_rec rec;
};
/*
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index 6c13847480f7..4e2c61e71a38 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -63,6 +63,26 @@ struct realm {
unsigned int ia_bits;
};
+/**
+ * struct realm_rec - Additional per VCPU data for a Realm
+ *
+ * @mpidr: MPIDR (Multiprocessor Affinity Register) value to identify this VCPU
+ * @rec_page: Kernel VA of the RMM's private page for this REC
+ * @aux_pages: Additional pages private to the RMM for this REC
+ * @run: Kernel VA of the RmiRecRun structure shared with the RMM
+ */
+struct realm_rec {
+ unsigned long mpidr;
+ void *rec_page;
+ /*
+ * REC_PARAMS_AUX_GRANULES is the maximum number of 4K granules that
+ * the RMM can require. The array is sized to be large enough for the
+ * maximum number of host sized pages that could be required.
+ */
+ struct page *aux_pages[(REC_PARAMS_AUX_GRANULES * SZ_4K) >> PAGE_SHIFT];
+ struct rec_run *run;
+};
+
void kvm_init_rmi(void);
u32 kvm_realm_ipa_limit(void);
@@ -70,6 +90,7 @@ int kvm_init_realm_vm(struct kvm *kvm);
int kvm_activate_realm(struct kvm *kvm);
void kvm_destroy_realm(struct kvm *kvm);
void kvm_realm_destroy_rtts(struct kvm *kvm);
+void kvm_destroy_rec(struct kvm_vcpu *vcpu);
static inline bool kvm_realm_is_private_address(struct realm *realm,
unsigned long addr)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index c8e51ed009c0..8c50ebd9fba0 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -575,6 +575,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 */
@@ -1549,7 +1551,7 @@ int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irq_level,
return -EINVAL;
}
-static unsigned long system_supported_vcpu_features(void)
+static unsigned long system_supported_vcpu_features(struct kvm *kvm)
{
unsigned long features = KVM_VCPU_VALID_FEATURES;
@@ -1587,7 +1589,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;
/*
@@ -1609,6 +1611,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;
+
return 0;
}
diff --git a/arch/arm64/kvm/reset.c b/arch/arm64/kvm/reset.c
index 959532422d3a..4bbf58892928 100644
--- a/arch/arm64/kvm/reset.c
+++ b/arch/arm64/kvm/reset.c
@@ -161,6 +161,7 @@ void kvm_arm_vcpu_destroy(struct kvm_vcpu *vcpu)
free_page((unsigned long)vcpu->arch.ctxt.vncr_array);
kfree(vcpu->arch.vncr_tlb);
kfree(vcpu->arch.ccsidr);
+ kvm_destroy_rec(vcpu);
}
static void kvm_vcpu_reset_sve(struct kvm_vcpu *vcpu)
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 937fababf960..6daf14c4b413 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -207,6 +207,28 @@ static int get_start_level(struct realm *realm)
return 4 - stage2_pgtable_levels(realm->ia_bits);
}
+static int delegate_range(phys_addr_t phys, unsigned long size)
+{
+ unsigned long ret;
+ unsigned long top = phys + size;
+ unsigned long out_top;
+
+ while (phys < top) {
+ ret = rmi_granule_range_delegate(phys, top, &out_top);
+ if (ret == RMI_SUCCESS)
+ phys = out_top;
+ else if (ret != RMI_BUSY && ret != RMI_BLOCKED)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int delegate_page(phys_addr_t phys)
+{
+ return delegate_range(phys, PAGE_SIZE);
+}
+
static int undelegate_range(phys_addr_t phys, unsigned long size)
{
unsigned long ret;
@@ -372,9 +394,177 @@ static int realm_ensure_created(struct kvm *kvm)
return -ENXIO;
}
+static void free_rec_aux(struct page **aux_pages,
+ unsigned int num_aux)
+{
+ unsigned int i;
+ unsigned int page_count = 0;
+
+ for (i = 0; i < num_aux; i++) {
+ struct page *aux_page = aux_pages[page_count++];
+ phys_addr_t aux_page_phys = page_to_phys(aux_page);
+
+ if (!WARN_ON(undelegate_page(aux_page_phys)))
+ __free_page(aux_page);
+ aux_page_phys += PAGE_SIZE;
+ }
+}
+
+static int alloc_rec_aux(struct page **aux_pages,
+ u64 *aux_phys_pages,
+ unsigned int num_aux)
+{
+ struct page *aux_page;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < num_aux; i++) {
+ phys_addr_t aux_page_phys;
+
+ aux_page = alloc_page(GFP_KERNEL);
+ if (!aux_page) {
+ ret = -ENOMEM;
+ goto out_err;
+ }
+
+ aux_page_phys = page_to_phys(aux_page);
+ if (delegate_page(aux_page_phys)) {
+ ret = -ENXIO;
+ goto err_undelegate;
+ }
+ aux_phys_pages[i] = aux_page_phys;
+ aux_pages[i] = aux_page;
+ }
+
+ return 0;
+err_undelegate:
+ while (i > 0) {
+ i--;
+ if (WARN_ON(undelegate_page(aux_phys_pages[i]))) {
+ /* Leak the page if the undelegate fails */
+ goto out_err;
+ }
+ }
+ __free_page(aux_page);
+out_err:
+ free_rec_aux(aux_pages, i);
+ return ret;
+}
+
+static int kvm_create_rec(struct kvm_vcpu *vcpu)
+{
+ struct user_pt_regs *vcpu_regs = vcpu_gp_regs(vcpu);
+ unsigned long mpidr = kvm_vcpu_get_mpidr_aff(vcpu);
+ struct realm *realm = &vcpu->kvm->arch.realm;
+ struct realm_rec *rec = &vcpu->arch.rec;
+ unsigned long rec_page_phys;
+ struct rec_params *params;
+ int r, i;
+
+ if (rec->run)
+ return -EBUSY;
+
+ /*
+ * The RMM will report PSCI v1.0 to Realms and the KVM_ARM_VCPU_PSCI_0_2
+ * flag covers v0.2 and onwards.
+ */
+ 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 = (struct rec_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;
+ goto out_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 (delegate_page(rec_page_phys)) {
+ r = -ENXIO;
+ goto out_free_pages;
+ }
+
+ r = alloc_rec_aux(rec->aux_pages, params->aux, realm->num_aux);
+ if (r)
+ goto out_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;
+ goto out_free_rec_aux;
+ }
+
+ rec->mpidr = mpidr;
+
+ free_page((unsigned long)params);
+ return 0;
+
+out_free_rec_aux:
+ free_rec_aux(rec->aux_pages, realm->num_aux);
+out_undelegate_rmm_rec:
+ if (WARN_ON(undelegate_page(rec_page_phys)))
+ rec->rec_page = NULL;
+out_free_pages:
+ free_page((unsigned long)rec->run);
+ free_page((unsigned long)rec->rec_page);
+ free_page((unsigned long)params);
+ rec->run = NULL;
+ return r;
+}
+
+void kvm_destroy_rec(struct kvm_vcpu *vcpu)
+{
+ struct realm *realm = &vcpu->kvm->arch.realm;
+ struct realm_rec *rec = &vcpu->arch.rec;
+ unsigned long rec_page_phys;
+
+ if (!vcpu_is_rec(vcpu))
+ return;
+
+ if (!rec->run) {
+ /* Nothing to do if the VCPU hasn't been finalized */
+ return;
+ }
+
+ free_page((unsigned long)rec->run);
+
+ rec_page_phys = virt_to_phys(rec->rec_page);
+
+ /*
+ * The REC and any AUX pages cannot be reclaimed until the REC is
+ * destroyed. So if the REC destroy fails then the REC page and any AUX
+ * pages will be leaked.
+ */
+ if (WARN_ON(rmi_rec_destroy(rec_page_phys)))
+ return;
+
+ free_rec_aux(rec->aux_pages, realm->num_aux);
+
+ free_delegated_page(rec_page_phys);
+}
+
int kvm_activate_realm(struct kvm *kvm)
{
struct realm *realm = &kvm->arch.realm;
+ struct kvm_vcpu *vcpu;
+ unsigned long i;
int ret;
if (kvm_realm_state(kvm) >= REALM_STATE_ACTIVE)
@@ -397,6 +587,12 @@ int kvm_activate_realm(struct kvm *kvm)
/* Mark state as dead in case we fail */
WRITE_ONCE(realm->state, REALM_STATE_DEAD);
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ ret = kvm_create_rec(vcpu);
+ if (ret)
+ return ret;
+ }
+
ret = rmi_realm_activate(virt_to_phys(realm->rd));
if (ret)
return -ENXIO;
--
2.43.0
^ permalink raw reply related
* [PATCH v13 16/48] arm64: RMI: Activate realm on first VCPU run
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
Use kvm_arch_vcpu_run_pid_change() to check if this is the first time
the realm guest has run. If this is the first run then 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>
---
Changes since v12:
* Fix commit message
* Change realm_state checks to be >= REALM_STATE_ACTIVE to avoid a dead
guest being revived by kvm_activate_realm().
---
arch/arm64/include/asm/kvm_rmi.h | 1 +
arch/arm64/kvm/arm.c | 6 +++++
arch/arm64/kvm/rmi.c | 39 ++++++++++++++++++++++++++++++++
3 files changed, 46 insertions(+)
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index 16a297f3091a..6c13847480f7 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -67,6 +67,7 @@ void kvm_init_rmi(void);
u32 kvm_realm_ipa_limit(void);
int kvm_init_realm_vm(struct kvm *kvm);
+int kvm_activate_realm(struct kvm *kvm);
void kvm_destroy_realm(struct kvm *kvm);
void kvm_realm_destroy_rtts(struct kvm *kvm);
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index b79b58802b33..c8e51ed009c0 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -998,6 +998,12 @@ int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
return ret;
}
+ if (kvm_is_realm(vcpu->kvm)) {
+ ret = kvm_activate_realm(kvm);
+ if (ret)
+ return ret;
+ }
+
mutex_lock(&kvm->arch.config_lock);
set_bit(KVM_ARCH_FLAG_HAS_RAN_ONCE, &kvm->arch.flags);
mutex_unlock(&kvm->arch.config_lock);
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 1fd2c18f7381..937fababf960 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -366,6 +366,45 @@ void kvm_realm_destroy_rtts(struct kvm *kvm)
WARN_ON(realm_tear_down_rtt_range(realm, 0, (1UL << ia_bits)));
}
+static int realm_ensure_created(struct kvm *kvm)
+{
+ /* Provided in later patch */
+ return -ENXIO;
+}
+
+int kvm_activate_realm(struct kvm *kvm)
+{
+ struct realm *realm = &kvm->arch.realm;
+ int ret;
+
+ if (kvm_realm_state(kvm) >= REALM_STATE_ACTIVE)
+ return 0;
+
+ if (!irqchip_in_kernel(kvm)) {
+ /* Userspace irqchip not yet supported with realms */
+ return -EOPNOTSUPP;
+ }
+
+ guard(mutex)(&kvm->arch.config_lock);
+ /* Check again with the lock held */
+ if (kvm_realm_state(kvm) >= REALM_STATE_ACTIVE)
+ return 0;
+
+ ret = realm_ensure_created(kvm);
+ if (ret)
+ return ret;
+
+ /* Mark state as dead in case we fail */
+ WRITE_ONCE(realm->state, REALM_STATE_DEAD);
+
+ ret = rmi_realm_activate(virt_to_phys(realm->rd));
+ if (ret)
+ return -ENXIO;
+
+ WRITE_ONCE(realm->state, REALM_STATE_ACTIVE);
+ return 0;
+}
+
void kvm_destroy_realm(struct kvm *kvm)
{
struct realm *realm = &kvm->arch.realm;
--
2.43.0
^ permalink raw reply related
* [PATCH v13 15/48] arm64: RMI: RTT tear down
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 v12:
* Simplify some functions now we know RMM page size is the same as the
host's.
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 | 145 +++++++++++++++++++++++++++++++
3 files changed, 166 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index 0ada525af18f..16a297f3091a 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -68,5 +68,12 @@ u32 kvm_realm_ipa_limit(void);
int kvm_init_realm_vm(struct kvm *kvm);
void kvm_destroy_realm(struct kvm *kvm);
+void kvm_realm_destroy_rtts(struct kvm *kvm);
+
+static inline bool kvm_realm_is_private_address(struct realm *realm,
+ unsigned long addr)
+{
+ return !(addr & BIT(realm->ia_bits - 1));
+}
#endif /* __ASM_KVM_RMI_H */
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 9dc242c3b9c8..41152abf55b2 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1098,10 +1098,23 @@ void stage2_unmap_vm(struct kvm *kvm)
void kvm_free_stage2_pgd(struct kvm_s2_mmu *mmu)
{
struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu);
- struct kvm_pgtable *pgt = NULL;
+ struct kvm_pgtable *pgt;
write_lock(&kvm->mmu_lock);
pgt = mmu->pgt;
+ if (kvm_is_realm(kvm) &&
+ (kvm_realm_state(kvm) != REALM_STATE_DEAD &&
+ kvm_realm_state(kvm) != REALM_STATE_NONE)) {
+ write_unlock(&kvm->mmu_lock);
+ kvm_realm_destroy_rtts(kvm);
+
+ /*
+ * The PGD pages can be reclaimed only after the realm (RD) is
+ * destroyed. We call this again from kvm_destroy_realm() after
+ * the RD is destroyed.
+ */
+ return;
+ }
if (pgt) {
mmu->pgd_phys = 0;
mmu->pgt = NULL;
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 700b8c935d29..1fd2c18f7381 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -15,6 +15,19 @@
static unsigned long rmm_feat_reg0;
static unsigned long rmm_feat_reg1;
+#define RMM_RTT_BLOCK_LEVEL 2
+#define RMM_RTT_MAX_LEVEL 3
+
+#define RMM_L2_BLOCK_SIZE PMD_SIZE
+
+static inline unsigned long rmi_rtt_level_mapsize(int level)
+{
+ if (WARN_ON(level > RMM_RTT_MAX_LEVEL))
+ return PAGE_SIZE;
+
+ return (1UL << ARM64_HW_PGTABLE_LEVEL_SHIFT(level));
+}
+
static bool rmi_has_feature(unsigned long feature)
{
return !!u64_get_bits(rmm_feat_reg0, feature);
@@ -189,6 +202,11 @@ u32 kvm_realm_ipa_limit(void)
return u64_get_bits(rmm_feat_reg0, RMI_FEATURE_REGISTER_0_S2SZ);
}
+static int get_start_level(struct realm *realm)
+{
+ return 4 - stage2_pgtable_levels(realm->ia_bits);
+}
+
static int undelegate_range(phys_addr_t phys, unsigned long size)
{
unsigned long ret;
@@ -223,6 +241,131 @@ static int free_delegated_page(phys_addr_t phys)
return 0;
}
+static void free_rtt(phys_addr_t phys)
+{
+ if (free_delegated_page(phys))
+ return;
+
+ kvm_account_pgtable_pages(phys_to_virt(phys), -1);
+}
+
+static int realm_rtt_destroy(struct realm *realm, unsigned long addr,
+ int level, phys_addr_t *rtt_granule,
+ unsigned long *next_addr)
+{
+ unsigned long out_rtt;
+ int ret;
+
+ ret = rmi_rtt_destroy(virt_to_phys(realm->rd), addr, level,
+ &out_rtt, next_addr);
+
+ *rtt_granule = out_rtt;
+
+ return ret;
+}
+
+static int realm_tear_down_rtt_level(struct realm *realm, int level,
+ unsigned long start, unsigned long end)
+{
+ ssize_t map_size;
+ unsigned long addr, next_addr;
+
+ if (WARN_ON(level > RMM_RTT_MAX_LEVEL))
+ return -EINVAL;
+
+ map_size = rmi_rtt_level_mapsize(level - 1);
+
+ for (addr = start; addr < end; addr = next_addr) {
+ phys_addr_t rtt_granule;
+ int ret;
+ unsigned long align_addr = ALIGN(addr, map_size);
+
+ next_addr = ALIGN(addr + 1, map_size);
+
+ if (next_addr > end || align_addr != addr) {
+ /*
+ * The target range is smaller than what this level
+ * covers, recurse deeper.
+ */
+ ret = realm_tear_down_rtt_level(realm,
+ level + 1,
+ addr,
+ min(next_addr, end));
+ if (ret)
+ return ret;
+ continue;
+ }
+
+ ret = realm_rtt_destroy(realm, addr, level,
+ &rtt_granule, &next_addr);
+
+ switch (RMI_RETURN_STATUS(ret)) {
+ case RMI_SUCCESS:
+ free_rtt(rtt_granule);
+ break;
+ case RMI_ERROR_RTT:
+ if (next_addr > addr) {
+ /* Missing RTT, skip */
+ break;
+ }
+ /*
+ * We tear down the RTT range for the full IPA
+ * space, after everything is unmapped. Also we
+ * descend down only if we cannot tear down a
+ * top level RTT. Thus RMM must be able to walk
+ * to the requested level. e.g., a block mapping
+ * exists at L1 or L2.
+ */
+ if (WARN_ON(RMI_RETURN_INDEX(ret) != level))
+ return -EBUSY;
+ if (WARN_ON(level == RMM_RTT_MAX_LEVEL))
+ return -EBUSY;
+
+ /*
+ * The table has active entries in it, recurse deeper
+ * and tear down the RTTs.
+ */
+ next_addr = ALIGN(addr + 1, map_size);
+ ret = realm_tear_down_rtt_level(realm,
+ level + 1,
+ addr,
+ next_addr);
+ if (ret)
+ return ret;
+ /*
+ * Now that the child RTTs are destroyed,
+ * retry at this level.
+ */
+ next_addr = addr;
+ break;
+ default:
+ WARN_ON(1);
+ return -ENXIO;
+ }
+ }
+
+ return 0;
+}
+
+static int realm_tear_down_rtt_range(struct realm *realm,
+ unsigned long start, unsigned long end)
+{
+ /*
+ * Root level RTTs can only be destroyed after the RD is destroyed. So
+ * tear down everything below the root level
+ */
+ return realm_tear_down_rtt_level(realm, get_start_level(realm) + 1,
+ start, end);
+}
+
+void kvm_realm_destroy_rtts(struct kvm *kvm)
+{
+ struct realm *realm = &kvm->arch.realm;
+ unsigned int ia_bits = realm->ia_bits;
+
+ WARN_ON(realm_tear_down_rtt_range(realm, 0, (1UL << ia_bits)));
+}
+
void kvm_destroy_realm(struct kvm *kvm)
{
struct realm *realm = &kvm->arch.realm;
@@ -246,6 +389,8 @@ void kvm_destroy_realm(struct kvm *kvm)
if (realm->rd) {
phys_addr_t rd_phys = virt_to_phys(realm->rd);
+ kvm_realm_destroy_rtts(kvm);
+
if (WARN_ON(rmi_realm_destroy(rd_phys)))
return;
free_delegated_page(rd_phys);
--
2.43.0
^ permalink raw reply related
* [PATCH v13 14/48] KVM: arm64: Allow passing machine type in KVM creation
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 <gshan@redhat.com>
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(-)
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 72a2ce96d1ba..bc180c853faf 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -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
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index ddbf080e4f55..b79b58802b33 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -216,6 +216,22 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
mutex_unlock(&kvm->lock);
#endif
+ if (type & ~(KVM_VM_TYPE_ARM_MASK | KVM_VM_TYPE_ARM_IPA_SIZE_MASK))
+ return -EINVAL;
+
+ switch (type & KVM_VM_TYPE_ARM_MASK) {
+ case KVM_VM_TYPE_ARM_NORMAL:
+ break;
+ case KVM_VM_TYPE_ARM_REALM:
+ if (!static_branch_unlikely(&kvm_rmi_is_available))
+ return -EINVAL;
+ WRITE_ONCE(kvm->arch.realm.state, REALM_STATE_NONE);
+ kvm->arch.is_realm = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
kvm_init_nested(kvm);
ret = kvm_share_hyp(kvm, kvm + 1);
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index d6094b60c4ce..9dc242c3b9c8 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -882,9 +882,6 @@ static int kvm_init_ipa_range(struct kvm *kvm,
if (kvm_is_realm(kvm))
kvm_ipa_limit = kvm_realm_ipa_limit();
- if (type & ~KVM_VM_TYPE_ARM_IPA_SIZE_MASK)
- return -EINVAL;
-
phys_shift = KVM_VM_TYPE_ARM_IPA_SIZE(type);
if (is_protected_kvm_enabled()) {
phys_shift = kvm_ipa_limit;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 6ec140ab0ed1..efd054cfbd07 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -691,14 +691,25 @@ struct kvm_enable_cap {
#define KVM_S390_SIE_PAGE_OFFSET 1
/*
- * On arm64, machine type can be used to request the physical
- * address size for the VM. Bits[7-0] are reserved for the guest
- * PA size shift (i.e, log2(PA_Size)). For backward compatibility,
- * value 0 implies the default IPA size, 40bits.
+ * On arm64, machine type can be used to request both the machine type and
+ * the physical address size for the VM.
+ *
+ * Bits[11-8] are reserved for the ARM specific machine type.
+ *
+ * Bits[7-0] are reserved for the guest PA size shift (i.e, log2(PA_Size)).
+ * For backward compatibility, value 0 implies the default IPA size, 40bits.
*/
+#define KVM_VM_TYPE_ARM_SHIFT 8
+#define KVM_VM_TYPE_ARM_MASK (0xfULL << KVM_VM_TYPE_ARM_SHIFT)
+#define KVM_VM_TYPE_ARM(_type) \
+ (((_type) << KVM_VM_TYPE_ARM_SHIFT) & KVM_VM_TYPE_ARM_MASK)
+#define KVM_VM_TYPE_ARM_NORMAL KVM_VM_TYPE_ARM(0)
+#define KVM_VM_TYPE_ARM_REALM KVM_VM_TYPE_ARM(1)
+
#define KVM_VM_TYPE_ARM_IPA_SIZE_MASK 0xffULL
#define KVM_VM_TYPE_ARM_IPA_SIZE(x) \
((x) & KVM_VM_TYPE_ARM_IPA_SIZE_MASK)
+
/*
* ioctls for /dev/kvm fds:
*/
--
2.43.0
^ permalink raw reply related
* [PATCH v13 13/48] kvm: arm64: Don't expose unsupported capabilities for realm guests
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Suzuki K Poulose, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Zenghui Yu, linux-arm-kernel,
linux-kernel, Joey Gouly, Alexandru Elisei, Christoffer Dall,
Fuad Tabba, linux-coco, Ganapatrao Kulkarni, Gavin Shan,
Shanker Donthineni, Alper Gun, Aneesh Kumar K . V, Emi Kisanuki,
Vishal Annapurve, Steven Price
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 9b17bdfaf0c2..ddbf080e4f55 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -357,6 +357,25 @@ static bool kvm_has_full_ptr_auth(void)
(apa + api + apa3) == 1);
}
+static bool kvm_realm_ext_allowed(long ext)
+{
+ switch (ext) {
+ case KVM_CAP_IRQCHIP:
+ case KVM_CAP_ARM_PSCI:
+ case KVM_CAP_ARM_PSCI_0_2:
+ case KVM_CAP_NR_VCPUS:
+ case KVM_CAP_MAX_VCPUS:
+ case KVM_CAP_MAX_VCPU_ID:
+ case KVM_CAP_MSI_DEVID:
+ case KVM_CAP_ARM_VM_IPA_SIZE:
+ case KVM_CAP_ARM_PTRAUTH_ADDRESS:
+ case KVM_CAP_ARM_PTRAUTH_GENERIC:
+ case KVM_CAP_ARM_RMI:
+ return true;
+ }
+ return false;
+}
+
int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
{
int r;
@@ -364,6 +383,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
if (is_protected_kvm_enabled() && !kvm_pkvm_ext_allowed(kvm, ext))
return 0;
+ if (kvm && kvm_is_realm(kvm) && !kvm_realm_ext_allowed(ext))
+ return 0;
+
switch (ext) {
case KVM_CAP_IRQCHIP:
r = vgic_present;
--
2.43.0
^ permalink raw reply related
* [PATCH v13 12/48] arm64: RMI: Basic infrastructure for creating a realm.
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 v12:
* Drop the RMM_PAGE_{SHIFT,SIZE} defines - the RMM is now configured to
be the same as the host's page size.
* Rework delegate/undelegate functions to use the new RMI range based
operations.
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 | 16 +++++
arch/arm64/kvm/arm.c | 12 ++++
arch/arm64/kvm/mmu.c | 11 +++-
arch/arm64/kvm/rmi.c | 88 ++++++++++++++++++++++++++++
5 files changed, 129 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index f38b50151ce8..39310d9b4e16 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -701,6 +701,11 @@ static inline enum realm_state kvm_realm_state(struct kvm *kvm)
return READ_ONCE(kvm->arch.realm.state);
}
+static inline bool kvm_realm_is_created(struct kvm *kvm)
+{
+ return kvm_is_realm(kvm) && kvm_realm_state(kvm) != REALM_STATE_NONE;
+}
+
static inline bool vcpu_is_rec(struct kvm_vcpu *vcpu)
{
return false;
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index 3506f50b05cd..0ada525af18f 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -6,6 +6,8 @@
#ifndef __ASM_KVM_RMI_H
#define __ASM_KVM_RMI_H
+#include <asm/rmi_smc.h>
+
/**
* enum realm_state - State of a Realm
*/
@@ -46,11 +48,25 @@ enum realm_state {
* struct realm - Additional per VM data for a Realm
*
* @state: The lifetime state machine for the realm
+ * @rd: Kernel mapping of the Realm Descriptor (RD)
+ * @params: Parameters for the RMI_REALM_CREATE command
+ * @num_aux: The number of auxiliary pages required by the RMM
+ * @ia_bits: Number of valid Input Address bits in the IPA
*/
struct realm {
enum realm_state state;
+
+ void *rd;
+ struct realm_params *params;
+
+ unsigned long num_aux;
+ unsigned int ia_bits;
};
void kvm_init_rmi(void);
+u32 kvm_realm_ipa_limit(void);
+
+int kvm_init_realm_vm(struct kvm *kvm);
+void kvm_destroy_realm(struct kvm *kvm);
#endif /* __ASM_KVM_RMI_H */
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 274d7866efdc..9b17bdfaf0c2 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -253,6 +253,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)
+ goto err_free_cpumask;
+ }
+
return 0;
err_free_cpumask:
@@ -312,6 +319,8 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
kvm_unshare_hyp(kvm, kvm + 1);
kvm_arm_teardown_hypercalls(kvm);
+ if (kvm_is_realm(kvm))
+ kvm_destroy_realm(kvm);
}
static bool kvm_has_full_ptr_auth(void)
@@ -473,6 +482,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
else
r = kvm_supports_cacheable_pfnmap();
break;
+ case KVM_CAP_ARM_RMI:
+ r = static_key_enabled(&kvm_rmi_is_available);
+ break;
default:
r = 0;
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 070a01e53fcb..d6094b60c4ce 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -872,12 +872,16 @@ static struct kvm_pgtable_mm_ops kvm_s2_mm_ops = {
.icache_inval_pou = invalidate_icache_guest_page,
};
-static int kvm_init_ipa_range(struct kvm_s2_mmu *mmu, unsigned long type)
+static int kvm_init_ipa_range(struct kvm *kvm,
+ struct kvm_s2_mmu *mmu, unsigned long type)
{
u32 kvm_ipa_limit = get_kvm_ipa_limit();
u64 mmfr0, mmfr1;
u32 phys_shift;
+ if (kvm_is_realm(kvm))
+ kvm_ipa_limit = kvm_realm_ipa_limit();
+
if (type & ~KVM_VM_TYPE_ARM_IPA_SIZE_MASK)
return -EINVAL;
@@ -974,7 +978,7 @@ int kvm_init_stage2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu, unsigned long t
return -EINVAL;
}
- err = kvm_init_ipa_range(mmu, type);
+ err = kvm_init_ipa_range(kvm, mmu, type);
if (err)
return err;
@@ -1113,7 +1117,8 @@ void kvm_free_stage2_pgd(struct kvm_s2_mmu *mmu)
write_unlock(&kvm->mmu_lock);
if (pgt) {
- kvm_stage2_destroy(pgt);
+ if (!kvm_is_realm(kvm))
+ kvm_stage2_destroy(pgt);
kfree(pgt);
}
}
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 80aedc85e94a..700b8c935d29 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -6,6 +6,8 @@
#include <linux/kvm_host.h>
#include <linux/memblock.h>
+#include <asm/kvm_emulate.h>
+#include <asm/kvm_mmu.h>
#include <asm/kvm_pgtable.h>
#include <asm/rmi_cmds.h>
#include <asm/virt.h>
@@ -182,6 +184,92 @@ static int rmi_init_metadata(void)
return 0;
}
+u32 kvm_realm_ipa_limit(void)
+{
+ return u64_get_bits(rmm_feat_reg0, RMI_FEATURE_REGISTER_0_S2SZ);
+}
+
+static int undelegate_range(phys_addr_t phys, unsigned long size)
+{
+ unsigned long ret;
+ unsigned long top = phys + size;
+ unsigned long out_top;
+
+ while (phys < top) {
+ ret = rmi_granule_range_undelegate(phys, top, &out_top);
+ if (ret == RMI_SUCCESS)
+ phys = out_top;
+ else if (ret != RMI_BUSY && ret != RMI_BLOCKED)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int undelegate_page(phys_addr_t phys)
+{
+ return undelegate_range(phys, PAGE_SIZE);
+}
+
+static int free_delegated_page(phys_addr_t phys)
+{
+ if (WARN_ON(undelegate_page(phys))) {
+ /* Undelegate failed: leak the page */
+ return -EBUSY;
+ }
+
+ free_page((unsigned long)phys_to_virt(phys));
+
+ return 0;
+}
+
+void kvm_destroy_realm(struct kvm *kvm)
+{
+ struct realm *realm = &kvm->arch.realm;
+ size_t pgd_size = kvm_pgtable_stage2_pgd_size(kvm->arch.mmu.vtcr);
+
+ 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((unsigned long)realm->params);
+ realm->params = NULL;
+ }
+
+ if (!kvm_realm_is_created(kvm))
+ return;
+
+ WRITE_ONCE(realm->state, REALM_STATE_DYING);
+
+ if (realm->rd) {
+ phys_addr_t rd_phys = virt_to_phys(realm->rd);
+
+ if (WARN_ON(rmi_realm_destroy(rd_phys)))
+ return;
+ free_delegated_page(rd_phys);
+ realm->rd = NULL;
+ }
+
+ if (WARN_ON(undelegate_range(kvm->arch.mmu.pgd_phys, pgd_size)))
+ 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);
+}
+
+int kvm_init_realm_vm(struct kvm *kvm)
+{
+ kvm->arch.realm.params = (void *)get_zeroed_page(GFP_KERNEL);
+
+ if (!kvm->arch.realm.params)
+ return -ENOMEM;
+ return 0;
+}
+
static int rmm_check_features(void)
{
if (kvm_lpa2_is_enabled() && !rmi_has_feature(RMI_FEATURE_REGISTER_0_LPA2)) {
--
2.43.0
^ permalink raw reply related
* [PATCH v13 11/48] arm64: RMI: Define the user ABI
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 v12:
* Change KVM_ARM_RMI_POPULATE to update the structure with the amount
that has been progressed rather than return the number of bytes
populated.
* Describe the flag KVM_ARM_RMI_POPULATE_FLAGS_MEASURE.
* CAP number is bumped.
* NOTE: The PSCI ioctl may be removed in a future spec release.
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 | 65 ++++++++++++++++++++++++++++++++++
include/uapi/linux/kvm.h | 22 ++++++++++++
2 files changed, 87 insertions(+)
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index fc5736839edd..72a2ce96d1ba 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -6553,6 +6553,62 @@ KVM_S390_KEYOP_SSKE
Sets the storage key for the guest address ``guest_addr`` to the key
specified in ``key``, returning the previous value in ``key``.
+4.145 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.146 KVM_ARM_RMI_POPULATE
+--------------------------
+
+:Capability: KVM_CAP_ARM_RMI
+:Architectures: arm64
+:Type: vm ioctl
+:Parameters: struct kvm_arm_rmi_populate (in)
+:Returns: 0 on success, < 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
+(non-protected) user space pointer provided into a protected region (backed by
+guestmem_fd). It implicitly sets the destination region to RIPAS RAM. This is
+only valid before any VCPUs have been run. The ioctl might not populate the
+entire region and updates the fields `base`, `size` and `source_uaddr`. User
+space may have to repeatedly call it until `size` is 0 to populate the entire
+region.
+
+`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).
+
.. _kvm_run:
5. The kvm_run structure
@@ -8896,6 +8952,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 65500f5db379..6ec140ab0ed1 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -985,6 +985,7 @@ struct kvm_enable_cap {
#define KVM_CAP_ARM_SEA_TO_USER 245
#define KVM_CAP_S390_USER_OPEREXEC 246
#define KVM_CAP_S390_KEYOP 247
+#define KVM_CAP_ARM_RMI 248
struct kvm_irq_routing_irqchip {
__u32 irqchip;
@@ -1650,4 +1651,25 @@ struct kvm_pre_fault_memory {
__u64 padding[5];
};
+/* Available with KVM_CAP_ARM_RMI, only for VMs with KVM_VM_TYPE_ARM_REALM */
+#define KVM_ARM_VCPU_RMI_PSCI_COMPLETE _IOW(KVMIO, 0xd6, struct kvm_arm_rmi_psci_complete)
+
+struct kvm_arm_rmi_psci_complete {
+ __u64 target_mpidr;
+ __u32 psci_status;
+ __u32 padding[3];
+};
+
+/* Available with KVM_CAP_ARM_RMI, only for VMs with KVM_VM_TYPE_ARM_REALM */
+#define KVM_ARM_RMI_POPULATE _IOWR(KVMIO, 0xd7, struct kvm_arm_rmi_populate)
+#define KVM_ARM_RMI_POPULATE_FLAGS_MEASURE (1 << 0)
+
+struct kvm_arm_rmi_populate {
+ __u64 base;
+ __u64 size;
+ __u64 source_uaddr;
+ __u32 flags;
+ __u32 reserved;
+};
+
#endif /* __LINUX_KVM_H */
--
2.43.0
^ permalink raw reply related
* [PATCH v13 10/48] arm64: RMI: Ensure that the RMM has GPT entries for memory
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
The RMM may not be tracking all the memory of the system at boot. Create
the necessary tracking state and GPTs within the RMM so that all boot
memory can be delegated to the RMM as needed during runtime.
Note: support is currently missing for SROs which means that if the RMM
needs memory donating this will fail (and render CCA unusable in Linux).
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v13
---
arch/arm64/kvm/rmi.c | 89 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 9590dff9a2c1..80aedc85e94a 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -4,6 +4,7 @@
*/
#include <linux/kvm_host.h>
+#include <linux/memblock.h>
#include <asm/kvm_pgtable.h>
#include <asm/rmi_cmds.h>
@@ -56,6 +57,18 @@ static int rmi_check_version(void)
return 0;
}
+/*
+ * These are the 'default' sizes when passing 0 as the tracking_region_size.
+ * TODO: Support other granule sizes
+ */
+#ifdef CONFIG_PAGE_SIZE_4KB
+#define RMM_GRANULE_TRACKING_SIZE SZ_1G
+#elif defined(CONFIG_PAGE_SIZE_16KB)
+#define RMM_GRANULE_TRACKING_SIZE SZ_32M
+#elif defined(CONFIG_PAGE_SIZE_64KB)
+#define RMM_GRANULE_TRACKING_SIZE SZ_512M
+#endif
+
static int rmi_configure(void)
{
struct rmm_config *config __free(free_page) = NULL;
@@ -95,6 +108,80 @@ static int rmi_configure(void)
return 0;
}
+static int rmi_verify_memory_tracking(phys_addr_t start, phys_addr_t end)
+{
+ start = ALIGN_DOWN(start, RMM_GRANULE_TRACKING_SIZE);
+ end = ALIGN(end, RMM_GRANULE_TRACKING_SIZE);
+
+ while (start < end) {
+ unsigned long ret, category, state;
+
+ ret = rmi_granule_tracking_get(start, &category, &state);
+ if (ret != RMI_SUCCESS ||
+ state != RMI_TRACKING_FINE ||
+ category != RMI_MEM_CATEGORY_CONVENTIONAL) {
+ /* TODO: Set granule tracking in this case */
+ kvm_err("Granule tracking for region isn't fine/conventional: %llx",
+ start);
+ return -ENODEV;
+ }
+ start += RMM_GRANULE_TRACKING_SIZE;
+ }
+
+ return 0;
+}
+
+static unsigned long rmi_l0gpt_size(void)
+{
+ return 1UL << (30 + FIELD_GET(RMI_FEATURE_REGISTER_1_L0GPTSZ,
+ rmm_feat_reg1));
+}
+
+static int rmi_create_gpts(phys_addr_t start, phys_addr_t end)
+{
+ unsigned long l0gpt_sz = rmi_l0gpt_size();
+
+ start = ALIGN_DOWN(start, l0gpt_sz);
+ end = ALIGN(end, l0gpt_sz);
+
+ while (start < end) {
+ int ret = rmi_gpt_l1_create(start);
+
+ if (ret && ret != RMI_ERROR_GPT) {
+ /*
+ * FIXME: Handle SRO so that memory can be donated for
+ * the tables.
+ */
+ kvm_err("GPT Level1 table missing for %llx\n", start);
+ return -ENOMEM;
+ }
+ start += l0gpt_sz;
+ }
+
+ return 0;
+}
+
+static int rmi_init_metadata(void)
+{
+ phys_addr_t start, end;
+ const struct memblock_region *r;
+
+ for_each_mem_region(r) {
+ int ret;
+
+ start = memblock_region_memory_base_pfn(r) << PAGE_SHIFT;
+ end = memblock_region_memory_end_pfn(r) << PAGE_SHIFT;
+ ret = rmi_verify_memory_tracking(start, end);
+ if (ret)
+ return ret;
+ ret = rmi_create_gpts(start, end);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static int rmm_check_features(void)
{
if (kvm_lpa2_is_enabled() && !rmi_has_feature(RMI_FEATURE_REGISTER_0_LPA2)) {
@@ -120,6 +207,8 @@ void kvm_init_rmi(void)
return;
if (rmi_configure())
return;
+ if (rmi_init_metadata())
+ return;
/* Future patch will enable static branch kvm_rmi_is_available */
}
--
2.43.0
^ permalink raw reply related
* [PATCH v13 09/48] arm64: RMI: Check for LPA2 support
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
If KVM has enabled LPA2 support then check that the RMM also supports
it. If there is a mismatch then disable support for realm guests as the
VMM may attempt to create a guest which is incompatible with the RMM.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v13
---
arch/arm64/kvm/rmi.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 482dc542451a..9590dff9a2c1 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -5,12 +5,18 @@
#include <linux/kvm_host.h>
+#include <asm/kvm_pgtable.h>
#include <asm/rmi_cmds.h>
#include <asm/virt.h>
static unsigned long rmm_feat_reg0;
static unsigned long rmm_feat_reg1;
+static bool rmi_has_feature(unsigned long feature)
+{
+ return !!u64_get_bits(rmm_feat_reg0, feature);
+}
+
static int rmi_check_version(void)
{
struct arm_smccc_res res;
@@ -89,6 +95,16 @@ static int rmi_configure(void)
return 0;
}
+static int rmm_check_features(void)
+{
+ if (kvm_lpa2_is_enabled() && !rmi_has_feature(RMI_FEATURE_REGISTER_0_LPA2)) {
+ kvm_err("RMM doesn't support LPA2");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
void kvm_init_rmi(void)
{
/* Continue without realm support if we can't agree on a version */
@@ -100,6 +116,8 @@ void kvm_init_rmi(void)
if (WARN_ON(rmi_features(1, &rmm_feat_reg1)))
return;
+ if (rmm_check_features())
+ return;
if (rmi_configure())
return;
--
2.43.0
^ permalink raw reply related
* [PATCH v13 08/48] arm64: RMI: Configure the RMM with the host's page size
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
RMM v2.0 brings the ability to set the RMM's granule size. Read the
feature registers and configure the RMM so that it matches the host's
page size. This means that operations can be done with a granulatity
equal to PAGE_SIZE.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch for v13
---
arch/arm64/kvm/rmi.c | 50 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index fac151580c01..482dc542451a 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -8,6 +8,9 @@
#include <asm/rmi_cmds.h>
#include <asm/virt.h>
+static unsigned long rmm_feat_reg0;
+static unsigned long rmm_feat_reg1;
+
static int rmi_check_version(void)
{
struct arm_smccc_res res;
@@ -47,11 +50,58 @@ static int rmi_check_version(void)
return 0;
}
+static int rmi_configure(void)
+{
+ struct rmm_config *config __free(free_page) = NULL;
+ unsigned long ret;
+
+ config = (struct rmm_config *)get_zeroed_page(GFP_KERNEL);
+ if (!config)
+ return -ENOMEM;
+
+ switch (PAGE_SIZE) {
+ case SZ_4K:
+ config->rmi_granule_size = RMI_GRANULE_SIZE_4KB;
+ break;
+ case SZ_16K:
+ config->rmi_granule_size = RMI_GRANULE_SIZE_16KB;
+ break;
+ case SZ_64K:
+ config->rmi_granule_size = RMI_GRANULE_SIZE_64KB;
+ break;
+ default:
+ kvm_err("Unsupported PAGE_SIZE for RMM\n");
+ return -EINVAL;
+ }
+
+ ret = rmi_rmm_config_set(virt_to_phys(config));
+ if (ret) {
+ kvm_err("RMM config set failed\n");
+ return -EINVAL;
+ }
+
+ ret = rmi_rmm_activate();
+ if (ret) {
+ kvm_err("RMM activate failed\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
void kvm_init_rmi(void)
{
/* Continue without realm support if we can't agree on a version */
if (rmi_check_version())
return;
+ if (WARN_ON(rmi_features(0, &rmm_feat_reg0)))
+ return;
+ if (WARN_ON(rmi_features(1, &rmm_feat_reg1)))
+ return;
+
+ if (rmi_configure())
+ return;
+
/* Future patch will enable static branch kvm_rmi_is_available */
}
--
2.43.0
^ permalink raw reply related
* [PATCH v13 07/48] arm64: RMI: Check for RMI support at KVM init
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 v12:
* Drop check for 4k page size.
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 | 57 ++++++++++++++++++++++++++++
8 files changed, 143 insertions(+), 1 deletion(-)
create mode 100644 arch/arm64/include/asm/kvm_rmi.h
create mode 100644 arch/arm64/kvm/rmi.c
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index 5bf3d7e1d92c..f38b50151ce8 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -688,4 +688,22 @@ static inline void vcpu_set_hcrx(struct kvm_vcpu *vcpu)
vcpu->arch.hcrx_el2 |= HCRX_EL2_EnASR;
}
}
+
+static inline bool kvm_is_realm(struct kvm *kvm)
+{
+ if (static_branch_unlikely(&kvm_rmi_is_available))
+ return kvm->arch.is_realm;
+ return false;
+}
+
+static inline enum realm_state kvm_realm_state(struct kvm *kvm)
+{
+ return READ_ONCE(kvm->arch.realm.state);
+}
+
+static inline bool vcpu_is_rec(struct kvm_vcpu *vcpu)
+{
+ return false;
+}
+
#endif /* __ARM64_KVM_EMULATE_H__ */
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 5d5a3bbdb95e..9267a2f2d65b 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -27,6 +27,7 @@
#include <asm/fpsimd.h>
#include <asm/kvm.h>
#include <asm/kvm_asm.h>
+#include <asm/kvm_rmi.h>
#include <asm/vncr_mapping.h>
#define __KVM_HAVE_ARCH_INTC_INITIALIZED
@@ -405,6 +406,9 @@ struct kvm_arch {
* the associated pKVM instance in the hypervisor.
*/
struct kvm_protected_vm pkvm;
+
+ bool is_realm;
+ struct realm realm;
};
struct kvm_vcpu_fault_info {
diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
new file mode 100644
index 000000000000..3506f50b05cd
--- /dev/null
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023-2025 ARM Ltd.
+ */
+
+#ifndef __ASM_KVM_RMI_H
+#define __ASM_KVM_RMI_H
+
+/**
+ * enum realm_state - State of a Realm
+ */
+enum realm_state {
+ /**
+ * @REALM_STATE_NONE:
+ * Realm has not yet been created. rmi_realm_create() has not
+ * yet been called.
+ */
+ REALM_STATE_NONE,
+ /**
+ * @REALM_STATE_NEW:
+ * Realm is under construction, rmi_realm_create() has been
+ * called, but it is not yet activated. Pages may be populated.
+ */
+ REALM_STATE_NEW,
+ /**
+ * @REALM_STATE_ACTIVE:
+ * Realm has been created and is eligible for execution with
+ * rmi_rec_enter(). Pages may no longer be populated with
+ * rmi_data_create().
+ */
+ REALM_STATE_ACTIVE,
+ /**
+ * @REALM_STATE_DYING:
+ * Realm is in the process of being destroyed or has already been
+ * destroyed.
+ */
+ REALM_STATE_DYING,
+ /**
+ * @REALM_STATE_DEAD:
+ * Realm has been destroyed.
+ */
+ REALM_STATE_DEAD
+};
+
+/**
+ * struct realm - Additional per VM data for a Realm
+ *
+ * @state: The lifetime state machine for the realm
+ */
+struct realm {
+ enum realm_state state;
+};
+
+void kvm_init_rmi(void);
+
+#endif /* __ASM_KVM_RMI_H */
diff --git a/arch/arm64/include/asm/virt.h b/arch/arm64/include/asm/virt.h
index b51ab6840f9c..dc9b2899e0b2 100644
--- a/arch/arm64/include/asm/virt.h
+++ b/arch/arm64/include/asm/virt.h
@@ -87,6 +87,7 @@ void __hyp_reset_vectors(void);
bool is_kvm_arm_initialised(void);
DECLARE_STATIC_KEY_FALSE(kvm_protected_mode_initialized);
+DECLARE_STATIC_KEY_FALSE(kvm_rmi_is_available);
static inline bool is_pkvm_initialized(void)
{
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index c31f8e17732a..ddf7e57f23e8 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -289,6 +289,7 @@ static const struct arm64_ftr_bits ftr_id_aa64isar3[] = {
static const struct arm64_ftr_bits ftr_id_aa64pfr0[] = {
ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_CSV3_SHIFT, 4, 0),
ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_CSV2_SHIFT, 4, 0),
+ ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_RME_SHIFT, 4, 0),
ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_DIT_SHIFT, 4, 0),
ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_AMU_SHIFT, 4, 0),
ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_MPAM_SHIFT, 4, 0),
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 3ebc0570345c..e17c4077d8e7 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -16,7 +16,7 @@ CFLAGS_handle_exit.o += -Wno-override-init
kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
inject_fault.o va_layout.o handle_exit.o config.o \
guest.o debug.o reset.o sys_regs.o stacktrace.o \
- vgic-sys-reg-v3.o fpsimd.o pkvm.o \
+ vgic-sys-reg-v3.o fpsimd.o pkvm.o rmi.o \
arch_timer.o trng.o vmid.o emulate-nested.o nested.o at.o \
vgic/vgic.o vgic/vgic-init.o \
vgic/vgic-irqfd.o vgic/vgic-v2.o \
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 29f0326f7e00..274d7866efdc 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -39,6 +39,7 @@
#include <asm/kvm_nested.h>
#include <asm/kvm_pkvm.h>
#include <asm/kvm_ptrauth.h>
+#include <asm/kvm_rmi.h>
#include <asm/sections.h>
#include <asm/stacktrace/nvhe.h>
@@ -104,6 +105,8 @@ long kvm_get_cap_for_kvm_ioctl(unsigned int ioctl, long *ext)
return -EINVAL;
}
+DEFINE_STATIC_KEY_FALSE(kvm_rmi_is_available);
+
DECLARE_KVM_HYP_PER_CPU(unsigned long, kvm_hyp_vector);
DEFINE_PER_CPU(unsigned long, kvm_arm_hyp_stack_base);
@@ -2921,6 +2924,8 @@ static __init int kvm_arm_init(void)
in_hyp_mode = is_kernel_in_hyp_mode();
+ kvm_init_rmi();
+
if (cpus_have_final_cap(ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) ||
cpus_have_final_cap(ARM64_WORKAROUND_1508412))
kvm_info("Guests without required CPU erratum workarounds can deadlock system!\n" \
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
new file mode 100644
index 000000000000..fac151580c01
--- /dev/null
+++ b/arch/arm64/kvm/rmi.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023-2025 ARM Ltd.
+ */
+
+#include <linux/kvm_host.h>
+
+#include <asm/rmi_cmds.h>
+#include <asm/virt.h>
+
+static int rmi_check_version(void)
+{
+ struct arm_smccc_res res;
+ unsigned short version_major, version_minor;
+ unsigned long host_version = RMI_ABI_VERSION(RMI_ABI_MAJOR_VERSION,
+ RMI_ABI_MINOR_VERSION);
+ unsigned long aa64pfr0 = 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) {
+ unsigned short high_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);
+
+ return 0;
+}
+
+void kvm_init_rmi(void)
+{
+ /* 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 */
+}
--
2.43.0
^ permalink raw reply related
* [PATCH v13 06/48] arm64: RMI: Add wrappers for RMI calls
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
The wrappers make the call sites easier to read and deal with the
boiler plate of handling the error codes from the RMM.
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes from v12:
* Update to RMM v2.0 specification
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 | 699 ++++++++++++++++++++++++++++++
1 file changed, 699 insertions(+)
create mode 100644 arch/arm64/include/asm/rmi_cmds.h
diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
new file mode 100644
index 000000000000..9c4f83644a61
--- /dev/null
+++ b/arch/arm64/include/asm/rmi_cmds.h
@@ -0,0 +1,699 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 ARM Ltd.
+ */
+
+#ifndef __ASM_RMI_CMDS_H
+#define __ASM_RMI_CMDS_H
+
+#include <linux/arm-smccc.h>
+
+#include <asm/rmi_smc.h>
+
+struct rtt_entry {
+ unsigned long walk_level;
+ unsigned long desc;
+ int state;
+ int ripas;
+};
+
+/**
+ * rmi_rmm_config_get() - Get the system configuration
+ * @cfg_ptr: PA of a struct rmm_config
+ *
+ * Gets the system configuration
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rmm_config_get(unsigned long cfg_ptr)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RMM_CONFIG_GET, cfg_ptr, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_rmm_config_set() - Configure the RMM
+ * @cfg_ptr: PA of a struct rmm_config
+ *
+ * Sets configuration options on the RMM.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rmm_config_set(unsigned long cfg_ptr)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RMM_CONFIG_SET, cfg_ptr, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_rmm_activate() - Activate the RMM
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rmm_activate(void)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RMM_ACTIVATE, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_granule_tracking_get() - Get configuration of a Granule tracking region
+ * @addr: PA of the tracking region
+ * @out_category: Memory category
+ * @out_state: Tracking region state
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_granule_tracking_get(unsigned long addr,
+ unsigned long *out_category,
+ unsigned long *out_state)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_GRANULE_TRACKING_GET, addr, &res);
+
+ if (out_category)
+ *out_category = res.a1;
+ if (out_state)
+ *out_state = res.a2;
+
+ return res.a0;
+}
+
+/**
+ * rmi_granule_tracking_set() - Set configuration of a Granule tracking region
+ * @addr: PA of the tracking region
+ * @category: Memory category
+ * @state: Tracking region state
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_granule_tracking_set(unsigned long addr,
+ unsigned long category,
+ unsigned long state)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_GRANULE_TRACKING_SET, addr, category,
+ state, &res);
+
+ if (RMI_RETURN_STATUS(res.a0) == RMI_INCOMPLETE) {
+ /* FIXME */
+ return WARN_ON(res.a0);
+ }
+
+ return res.a0;
+}
+
+/**
+ * rmi_gpt_l1_create() - Create a Level 1 GPT
+ * @addr: Base of physical address region described by the L1GPT
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_gpt_l1_create(unsigned long addr)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_GPT_L1_CREATE, addr, &res);
+
+ if (RMI_RETURN_STATUS(res.a0) == RMI_INCOMPLETE) {
+ /* FIXME */
+ return WARN_ON(res.a0);
+ }
+
+ return res.a0;
+}
+
+/**
+ * rmi_gpt_l1_destroy() - Destroy a Level 1 GPT
+ * @addr: Base of physical address region descripted by the L1GPT
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_gpt_l1_destroy(unsigned long addr)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_GPT_L1_DESTROY, addr, &res);
+
+ if (RMI_RETURN_STATUS(res.a0) == RMI_INCOMPLETE) {
+ /* FIXME */
+ return WARN_ON(res.a0);
+ }
+
+ return res.a0;
+}
+
+/**
+ * rmi_rtt_data_map_init() - Create a protected mapping with data contents
+ * @rd: PA of the RD
+ * @data: PA of the target granule
+ * @ipa: IPA at which the granule will be mapped in the guest
+ * @src: PA of the source granule
+ * @flags: RMI_MEASURE_CONTENT if the contents should be measured
+ *
+ * Create a mapping from Protected IPA space to conventional memory, copying
+ * contents from a Non-secure Granule provided by the caller.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rtt_data_map_init(unsigned long rd, unsigned long data,
+ unsigned long ipa, unsigned long src,
+ unsigned long flags)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RTT_DATA_MAP_INIT, rd, data, ipa, src,
+ flags, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_rtt_data_map() - Create mappings in protected IPA with unknown contents
+ * @rd: PA of the RD
+ * @base: Base of the target IPA range
+ * @top: Top of the target IPA range
+ * @flags: Flags
+ * @oaddr: Output address set descriptor
+ * @out_top: Top address of range which was processed.
+ *
+ * Return RMI return code
+ */
+static inline int rmi_rtt_data_map(unsigned long rd,
+ unsigned long base,
+ unsigned long top,
+ unsigned long flags,
+ unsigned long oaddr,
+ unsigned long *out_top)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RTT_DATA_MAP, rd, base, top, flags, oaddr,
+ &res);
+
+ if (RMI_RETURN_STATUS(res.a0) == RMI_INCOMPLETE) {
+ /* FIXME */
+ return WARN_ON(res.a0);
+ }
+
+ if (out_top)
+ *out_top = res.a1;
+
+ return res.a0;
+}
+
+/**
+ * rmi_rtt_data_unmap() - Remove mappings to conventional memory
+ * @rd: PA of the RD for the target Realm
+ * @base: Base of the target IPA range
+ * @top: Top of the target IPA range
+ * @flags: Flags
+ * @oaddr: Output address set descriptor
+ * @out_top: Returns top IPA of range which has been unmapped
+ * @out_range: Output address range
+ * @out_count: Number of entries in output address list
+ *
+ * Removes mappings to convention memory with a target Protected IPA range.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rtt_data_unmap(unsigned long rd,
+ unsigned long base,
+ unsigned long top,
+ unsigned long flags,
+ unsigned long oaddr,
+ unsigned long *out_top,
+ unsigned long *out_range,
+ unsigned long *out_count)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RTT_DATA_UNMAP, rd, base, top, flags,
+ oaddr, &res);
+
+ /* FIXME: Handle SRO */
+
+ if (out_top)
+ *out_top = res.a1;
+ if (out_range)
+ *out_range = res.a2;
+ if (out_count)
+ *out_count = res.a3;
+
+ return res.a0;
+}
+
+/**
+ * rmi_features() - Read feature register
+ * @index: Feature register index
+ * @out: Feature register value is written to this pointer
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_features(unsigned long index, unsigned long *out)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_FEATURES, index, &res);
+
+ if (out)
+ *out = res.a1;
+ return res.a0;
+}
+
+/**
+ * rmi_granule_range_delegate() - Delegate granules
+ * @base: PA of the first granule of the range
+ * @top: PA of the first granule after the range
+ * @out_top: PA of the first granule not delegated
+ *
+ * Delegate a range of granule for use by the realm world. If the entire range
+ * was delegated then @out_top == @top, otherwise the function should be called
+ * again with @base == @out_top.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_granule_range_delegate(unsigned long base,
+ unsigned long top,
+ unsigned long *out_top)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_GRANULE_RANGE_DELEGATE, base, top, &res);
+
+ if (RMI_RETURN_STATUS(res.a0) == RMI_INCOMPLETE) {
+ /* FIXME - Handle SRO */
+ return WARN_ON(res.a0);
+ }
+
+ if (out_top)
+ *out_top = res.a1;
+
+ return res.a0;
+}
+
+/**
+ * rmi_granule_range_undelegate() - Undelegate a range of granules
+ * @base: Base PA of the target range
+ * @top: Top PA of the target range
+ * @out_top: Returns the top PA of range whose state is undelegated
+ *
+ * Undelegate a range of granules to allow use by the normal world. Will fail if
+ * the granules are in use.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_granule_range_undelegate(unsigned long base,
+ unsigned long top,
+ unsigned long *out_top)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_GRANULE_RANGE_UNDELEGATE, base, top, &res);
+
+ if (RMI_RETURN_STATUS(res.a0) == RMI_INCOMPLETE) {
+ /* FIXME - Handle SRO */
+ return WARN_ON(res.a0);
+ }
+
+ if (out_top)
+ *out_top = res.a1;
+
+ return res.a0;
+}
+
+/**
+ * rmi_psci_complete() - Complete pending PSCI command
+ * @calling_rec: PA of the calling REC
+ * @target_rec: PA of the target REC
+ * @status: Status of the PSCI request
+ *
+ * Completes a pending PSCI command which was called with an MPIDR argument, by
+ * providing the corresponding REC.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_psci_complete(unsigned long calling_rec,
+ unsigned long target_rec,
+ unsigned long status)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_PSCI_COMPLETE, calling_rec, target_rec,
+ status, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_realm_activate() - Active a realm
+ * @rd: PA of the RD
+ *
+ * Mark a realm as Active signalling that creation is complete and allowing
+ * execution of the realm.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_realm_activate(unsigned long rd)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_REALM_ACTIVATE, rd, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_realm_create() - Create a realm
+ * @rd: PA of the RD
+ * @params: PA of realm parameters
+ *
+ * Create a new realm using the given parameters.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_realm_create(unsigned long rd, unsigned long params)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_REALM_CREATE, rd, params, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_realm_destroy() - Destroy a realm
+ * @rd: PA of the RD
+ *
+ * Destroys a realm, all objects belonging to the realm must be destroyed first.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_realm_destroy(unsigned long rd)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_REALM_DESTROY, rd, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_rec_aux_count() - Get number of auxiliary granules required
+ * @rd: PA of the RD
+ * @aux_count: Number of granules written to this pointer
+ *
+ * A REC may require extra auxiliary granules to be delegated for the RMM to
+ * store metadata (not visible to the normal world) in. This function provides
+ * the number of granules that are required.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rec_aux_count(unsigned long rd, unsigned long *aux_count)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_REC_AUX_COUNT, rd, &res);
+
+ if (aux_count)
+ *aux_count = res.a1;
+ return res.a0;
+}
+
+/**
+ * rmi_rec_create() - Create a REC
+ * @rd: PA of the RD
+ * @rec: PA of the target REC
+ * @params: PA of REC parameters
+ *
+ * Create a REC using the parameters specified in the struct rec_params pointed
+ * to by @params.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rec_create(unsigned long rd, unsigned long rec,
+ unsigned long params)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_REC_CREATE, rd, rec, params, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_rec_destroy() - Destroy a REC
+ * @rec: PA of the target REC
+ *
+ * Destroys a REC. The REC must not be running.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rec_destroy(unsigned long rec)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_REC_DESTROY, rec, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_rec_enter() - Enter a REC
+ * @rec: PA of the target REC
+ * @run_ptr: PA of RecRun structure
+ *
+ * Starts (or continues) execution within a REC.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rec_enter(unsigned long rec, unsigned long run_ptr)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_REC_ENTER, rec, run_ptr, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_rtt_create() - Creates an RTT
+ * @rd: PA of the RD
+ * @rtt: PA of the target RTT
+ * @ipa: Base of the IPA range described by the RTT
+ * @level: Depth of the RTT within the tree
+ *
+ * Creates an RTT (Realm Translation Table) at the specified level for the
+ * translation of the specified address within the realm.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rtt_create(unsigned long rd, unsigned long rtt,
+ unsigned long ipa, long level)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RTT_CREATE, rd, rtt, ipa, level, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_rtt_destroy() - Destroy an RTT
+ * @rd: PA of the RD
+ * @ipa: Base of the IPA range described by the RTT
+ * @level: Depth of the RTT within the tree
+ * @out_rtt: Pointer to write the PA of the RTT which was destroyed
+ * @out_top: Pointer to write the top IPA of non-live RTT entries
+ *
+ * Destroys an RTT. The RTT must be non-live, i.e. none of the entries in the
+ * table are in ASSIGNED or TABLE state.
+ *
+ * Return: RMI return code.
+ */
+static inline int rmi_rtt_destroy(unsigned long rd,
+ unsigned long ipa,
+ long level,
+ unsigned long *out_rtt,
+ unsigned long *out_top)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RTT_DESTROY, rd, ipa, level, &res);
+
+ if (out_rtt)
+ *out_rtt = res.a1;
+ if (out_top)
+ *out_top = res.a2;
+
+ return res.a0;
+}
+
+/**
+ * rmi_rtt_fold() - Fold an RTT
+ * @rd: PA of the RD
+ * @ipa: Base of the IPA range described by the RTT
+ * @level: Depth of the RTT within the tree
+ * @out_rtt: Pointer to write the PA of the RTT which was destroyed
+ *
+ * Folds an RTT. If all entries with the RTT are 'homogeneous' the RTT can be
+ * folded into the parent and the RTT destroyed.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rtt_fold(unsigned long rd, unsigned long ipa,
+ long level, unsigned long *out_rtt)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RTT_FOLD, rd, ipa, level, &res);
+
+ if (out_rtt)
+ *out_rtt = res.a1;
+
+ return res.a0;
+}
+
+/**
+ * rmi_rtt_init_ripas() - Set RIPAS for new realm
+ * @rd: PA of the RD
+ * @base: Base of target IPA region
+ * @top: Top of target IPA region
+ * @out_top: Top IPA of range whose RIPAS was modified
+ *
+ * Sets the RIPAS of a target IPA range to RAM, for a realm in the NEW state.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rtt_init_ripas(unsigned long rd, unsigned long base,
+ unsigned long top, unsigned long *out_top)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RTT_INIT_RIPAS, rd, base, top, &res);
+
+ if (out_top)
+ *out_top = res.a1;
+
+ return res.a0;
+}
+
+/**
+ * rmi_rtt_map_unprotected() - Map NS granules into a realm
+ * @rd: PA of the RD
+ * @ipa: Base IPA of the mapping
+ * @level: Depth within the RTT tree
+ * @desc: RTTE descriptor
+ *
+ * Create a mapping from an Unprotected IPA to a Non-secure PA.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rtt_map_unprotected(unsigned long rd,
+ unsigned long ipa,
+ long level,
+ unsigned long desc)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RTT_MAP_UNPROTECTED, rd, ipa, level,
+ desc, &res);
+
+ return res.a0;
+}
+
+/**
+ * rmi_rtt_read_entry() - Read an RTTE
+ * @rd: PA of the RD
+ * @ipa: IPA for which to read the RTTE
+ * @level: RTT level at which to read the RTTE
+ * @rtt: Output structure describing the RTTE
+ *
+ * Reads a RTTE (Realm Translation Table Entry).
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rtt_read_entry(unsigned long rd, unsigned long ipa,
+ long level, struct rtt_entry *rtt)
+{
+ struct arm_smccc_1_2_regs regs = {
+ SMC_RMI_RTT_READ_ENTRY,
+ rd, ipa, level
+ };
+
+ arm_smccc_1_2_invoke(®s, ®s);
+
+ rtt->walk_level = regs.a1;
+ rtt->state = regs.a2 & 0xFF;
+ rtt->desc = regs.a3;
+ rtt->ripas = regs.a4 & 0xFF;
+
+ return regs.a0;
+}
+
+/**
+ * rmi_rtt_set_ripas() - Set RIPAS for an running realm
+ * @rd: PA of the RD
+ * @rec: PA of the REC making the request
+ * @base: Base of target IPA region
+ * @top: Top of target IPA region
+ * @out_top: Pointer to write top IPA of range whose RIPAS was modified
+ *
+ * Completes a request made by the realm to change the RIPAS of a target IPA
+ * range.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rtt_set_ripas(unsigned long rd, unsigned long rec,
+ unsigned long base, unsigned long top,
+ unsigned long *out_top)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RTT_SET_RIPAS, rd, rec, base, top, &res);
+
+ if (out_top)
+ *out_top = res.a1;
+
+ return res.a0;
+}
+
+/**
+ * rmi_rtt_unmap_unprotected() - Remove a NS mapping
+ * @rd: PA of the RD
+ * @ipa: Base IPA of the mapping
+ * @level: Depth within the RTT tree
+ * @out_top: Pointer to write top IPA of non-live RTT entries
+ *
+ * Removes a mapping at an Unprotected IPA.
+ *
+ * Return: RMI return code
+ */
+static inline int rmi_rtt_unmap_unprotected(unsigned long rd,
+ unsigned long ipa,
+ long level,
+ unsigned long *out_top)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_RTT_UNMAP_UNPROTECTED, rd, ipa,
+ level, &res);
+
+ if (out_top)
+ *out_top = res.a1;
+
+ return res.a0;
+}
+
+#endif /* __ASM_RMI_CMDS_H */
--
2.43.0
^ permalink raw reply related
* [PATCH v13 05/48] arm64: RMI: Temporarily add SMCs from RMM v1.0 spec
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
Not all the functionality has been migrated to the v2.0 specification,
so for now we still rely on some v1.0 SMCs. This mixture is not
spec-compliant, but is necessary until an updated RMM is available.
Signed-off-by: Steven Price <steven.price@arm.com>
---
New patch in v13
---
arch/arm64/include/asm/rmi_smc.h | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/rmi_smc.h b/arch/arm64/include/asm/rmi_smc.h
index 8a42b83218f8..049d71470486 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -30,14 +30,15 @@
#define SMC_RMI_REC_ENTER SMC_RMI_CALL(0x015c)
#define SMC_RMI_RTT_CREATE SMC_RMI_CALL(0x015d)
#define SMC_RMI_RTT_DESTROY SMC_RMI_CALL(0x015e)
+#define SMC_RMI_RTT_MAP_UNPROTECTED SMC_RMI_CALL(0x015f) //
#define SMC_RMI_RTT_READ_ENTRY SMC_RMI_CALL(0x0161)
-
+#define SMC_RMI_RTT_UNMAP_UNPROTECTED SMC_RMI_CALL(0x0162) //
#define SMC_RMI_RTT_DEV_VALIDATE SMC_RMI_CALL(0x0163)
#define SMC_RMI_PSCI_COMPLETE SMC_RMI_CALL(0x0164)
#define SMC_RMI_FEATURES SMC_RMI_CALL(0x0165)
#define SMC_RMI_RTT_FOLD SMC_RMI_CALL(0x0166)
-
+#define SMC_RMI_REC_AUX_COUNT SMC_RMI_CALL(0x0167) //
#define SMC_RMI_RTT_INIT_RIPAS SMC_RMI_CALL(0x0168)
#define SMC_RMI_RTT_SET_RIPAS SMC_RMI_CALL(0x0169)
#define SMC_RMI_VSMMU_CREATE SMC_RMI_CALL(0x016a)
--
2.43.0
^ permalink raw reply related
* [PATCH v13 04/48] arm64: RMI: Add SMC definitions for calling the RMM
From: Steven Price @ 2026-03-18 15:53 UTC (permalink / raw)
To: kvm, kvmarm
Cc: Steven Price, Catalin Marinas, Marc Zyngier, Will Deacon,
James Morse, Oliver Upton, Suzuki K Poulose, Zenghui Yu,
linux-arm-kernel, linux-kernel, Joey Gouly, Alexandru Elisei,
Christoffer Dall, Fuad Tabba, linux-coco, Ganapatrao Kulkarni,
Gavin Shan, Shanker Donthineni, Alper Gun, Aneesh Kumar K . V,
Emi Kisanuki, Vishal Annapurve
In-Reply-To: <20260318155413.793430-1-steven.price@arm.com>
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 2.0-bet0
[1] https://developer.arm.com/documentation/den0137/2-0bet0/
Signed-off-by: Steven Price <steven.price@arm.com>
---
Changes since v12:
* Updated to RMM spec v2.0-bet0
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 | 432 +++++++++++++++++++++++++++++++
1 file changed, 432 insertions(+)
create mode 100644 arch/arm64/include/asm/rmi_smc.h
diff --git a/arch/arm64/include/asm/rmi_smc.h b/arch/arm64/include/asm/rmi_smc.h
new file mode 100644
index 000000000000..8a42b83218f8
--- /dev/null
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -0,0 +1,432 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023-2024 ARM Ltd.
+ *
+ * The values and structures in this file are from the Realm Management Monitor
+ * specification (DEN0137) version 1.0-rel0:
+ * https://developer.arm.com/documentation/den0137/1-0rel0/
+ */
+
+#ifndef __ASM_RMI_SMC_H
+#define __ASM_RMI_SMC_H
+
+#include <linux/arm-smccc.h>
+
+#define SMC_RMI_CALL(func) \
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \
+ ARM_SMCCC_SMC_64, \
+ ARM_SMCCC_OWNER_STANDARD, \
+ (func))
+
+#define SMC_RMI_VERSION SMC_RMI_CALL(0x0150)
+
+#define SMC_RMI_RTT_DATA_MAP_INIT SMC_RMI_CALL(0x0153)
+
+#define SMC_RMI_REALM_ACTIVATE SMC_RMI_CALL(0x0157)
+#define SMC_RMI_REALM_CREATE SMC_RMI_CALL(0x0158)
+#define SMC_RMI_REALM_DESTROY SMC_RMI_CALL(0x0159)
+#define SMC_RMI_REC_CREATE SMC_RMI_CALL(0x015a)
+#define SMC_RMI_REC_DESTROY SMC_RMI_CALL(0x015b)
+#define SMC_RMI_REC_ENTER SMC_RMI_CALL(0x015c)
+#define SMC_RMI_RTT_CREATE SMC_RMI_CALL(0x015d)
+#define SMC_RMI_RTT_DESTROY SMC_RMI_CALL(0x015e)
+
+#define SMC_RMI_RTT_READ_ENTRY SMC_RMI_CALL(0x0161)
+
+#define SMC_RMI_RTT_DEV_VALIDATE SMC_RMI_CALL(0x0163)
+#define SMC_RMI_PSCI_COMPLETE SMC_RMI_CALL(0x0164)
+#define SMC_RMI_FEATURES SMC_RMI_CALL(0x0165)
+#define SMC_RMI_RTT_FOLD SMC_RMI_CALL(0x0166)
+
+#define SMC_RMI_RTT_INIT_RIPAS SMC_RMI_CALL(0x0168)
+#define SMC_RMI_RTT_SET_RIPAS SMC_RMI_CALL(0x0169)
+#define SMC_RMI_VSMMU_CREATE SMC_RMI_CALL(0x016a)
+#define SMC_RMI_VSMMU_DESTROY SMC_RMI_CALL(0x016b)
+#define SMC_RMI_RMM_CONFIG_SET SMC_RMI_CALL(0x016e)
+#define SMC_RMI_PSMMU_IRQ_NOTIFY SMC_RMI_CALL(0x016f)
+#define SMC_RMI_ATTEST_PLAT_TOKEN_REFRESH SMC_RMI_CALL(0x0170)
+
+#define SMC_RMI_PDEV_ABORT SMC_RMI_CALL(0x0174)
+#define SMC_RMI_PDEV_COMMUNICATE SMC_RMI_CALL(0x0175)
+#define SMC_RMI_PDEV_CREATE SMC_RMI_CALL(0x0176)
+#define SMC_RMI_PDEV_DESTROY SMC_RMI_CALL(0x0177)
+#define SMC_RMI_PDEV_GET_STATE SMC_RMI_CALL(0x0178)
+
+#define SMC_RMI_PDEV_STREAM_KEY_REFRESH SMC_RMI_CALL(0x017a)
+#define SMC_RMI_PDEV_SET_PUBKEY SMC_RMI_CALL(0x017b)
+#define SMC_RMI_PDEV_STOP SMC_RMI_CALL(0x017c)
+#define SMC_RMI_RTT_AUX_CREATE SMC_RMI_CALL(0x017d)
+#define SMC_RMI_RTT_AUX_DESTROY SMC_RMI_CALL(0x017e)
+#define SMC_RMI_RTT_AUX_FOLD SMC_RMI_CALL(0x017f)
+
+#define SMC_RMI_VDEV_ABORT SMC_RMI_CALL(0x0185)
+#define SMC_RMI_VDEV_COMMUNICATE SMC_RMI_CALL(0x0186)
+#define SMC_RMI_VDEV_CREATE SMC_RMI_CALL(0x0187)
+#define SMC_RMI_VDEV_DESTROY SMC_RMI_CALL(0x0188)
+#define SMC_RMI_VDEV_GET_STATE SMC_RMI_CALL(0x0189)
+#define SMC_RMI_VDEV_UNLOCK SMC_RMI_CALL(0x018a)
+#define SMC_RMI_RTT_SET_S2AP SMC_RMI_CALL(0x018b)
+#define SMC_RMI_VDEV_COMPLETE SMC_RMI_CALL(0x018e)
+
+#define SMC_RMI_VDEV_GET_INTERFACE_REPORT SMC_RMI_CALL(0x01d0)
+#define SMC_RMI_VDEV_GET_MEASUREMENTS SMC_RMI_CALL(0x01d1)
+#define SMC_RMI_VDEV_LOCK SMC_RMI_CALL(0x01d2)
+#define SMC_RMI_VDEV_START SMC_RMI_CALL(0x01d3)
+#define SMC_RMI_VDEV_P2P_BIND SMC_RMI_CALL(0x01d4)
+#define SMC_RMI_VDEV_P2P_UNBIND SMC_RMI_CALL(0x01d5)
+#define SMC_RMI_VSMMU_EVENT_NOTIFY SMC_RMI_CALL(0x01d6)
+#define SMC_RMI_PSMMU_ACTIVATE SMC_RMI_CALL(0x01d7)
+#define SMC_RMI_PSMMU_DEACTIVATE SMC_RMI_CALL(0x01d8)
+
+#define SMC_RMI_PSMMU_ST_L2_CREATE SMC_RMI_CALL(0x01db)
+#define SMC_RMI_PSMMU_ST_L2_DESTROY SMC_RMI_CALL(0x01dc)
+#define SMC_RMI_DPT_L0_CREATE SMC_RMI_CALL(0x01dd)
+#define SMC_RMI_DPT_L0_DESTROY SMC_RMI_CALL(0x01de)
+#define SMC_RMI_DPT_L1_CREATE SMC_RMI_CALL(0x01df)
+#define SMC_RMI_DPT_L1_DESTROY SMC_RMI_CALL(0x01e0)
+#define SMC_RMI_GRANULE_TRACKING_GET SMC_RMI_CALL(0x01e1)
+
+#define SMC_RMI_GRANULE_TRACKING_SET SMC_RMI_CALL(0x01e3)
+#define SMC_RMI_CMEM_ADD_PDEV SMC_RMI_CALL(0x01e4)
+#define SMC_RMI_CMEM_CREATE SMC_RMI_CALL(0x01e5)
+#define SMC_RMI_CMEM_DESTROY SMC_RMI_CALL(0x01e6)
+#define SMC_RMI_CMEM_POPULATE SMC_RMI_CALL(0x01e7)
+#define SMC_RMI_CMEM_REMOTE_PDEV SMC_RMI_CALL(0x01e8)
+#define SMC_RMI_CMEM_START SMC_RMI_CALL(0x01e9)
+#define SMC_RMI_CMEM_STOP SMC_RMI_CALL(0x01ea)
+#define SMC_RMI_CMEM_UNPOPULATE SMC_RMI_CALL(0x01eb)
+#define SMC_RMI_RMM_CONFIG_GET SMC_RMI_CALL(0x01ec)
+#define SMC_RMI_PDEV_MEC_UPDATE SMC_RMI_CALL(0x01ed)
+#define SMC_RMI_VSMMU_EVENT_COMPLETE SMC_RMI_CALL(0x01ee)
+
+#define SMC_RMI_PSMMU_EVENT_DISCARD SMC_RMI_CALL(0x01f0)
+#define SMC_RMI_GRANULE_RANGE_DELEGATE SMC_RMI_CALL(0x01f1)
+#define SMC_RMI_GRANULE_RANGE_UNDELEGATE SMC_RMI_CALL(0x01f2)
+#define SMC_RMI_GPT_L1_CREATE SMC_RMI_CALL(0x01f3)
+#define SMC_RMI_GPT_L1_DESTROY SMC_RMI_CALL(0x01f4)
+#define SMC_RMI_RTT_DATA_MAP SMC_RMI_CALL(0x01f5)
+#define SMC_RMI_RTT_DATA_UNMAP SMC_RMI_CALL(0x01f6)
+#define SMC_RMI_RTT_DEV_MAP SMC_RMI_CALL(0x01f7)
+#define SMC_RMI_RTT_DEV_UNMAP SMC_RMI_CALL(0x01f8)
+#define SMC_RMI_RTT_ARCH_DEV_MAP SMC_RMI_CALL(0x01f9)
+#define SMC_RMI_RTT_ARCH_DEV_UNMAP SMC_RMI_CALL(0x01fa)
+#define SMC_RMI_RTT_UNPROT_MAP SMC_RMI_CALL(0x01fb)
+#define SMC_RMI_RTT_UNPROT_UNMAP SMC_RMI_CALL(0x01fc)
+#define SMC_RMI_RTT_AUX_PROT_MAP SMC_RMI_CALL(0x01fd)
+#define SMC_RMI_RTT_AUX_PROT_UNMAP SMC_RMI_CALL(0x01fe)
+#define SMC_RMI_RTT_AUX_UNPROT_MAP SMC_RMI_CALL(0x01ff)
+#define SMC_RMI_RTT_AUX_UNMAP_UNMAP SMC_RMI_CALL(0x0200)
+#define SMC_RMI_REALM_TERMINATE SMC_RMI_CALL(0x0201)
+#define SMC_RMI_RMM_ACTIVATE SMC_RMI_CALL(0x0202)
+#define SMC_RMI_OP_CONTINUE SMC_RMI_CALL(0x0203)
+#define SMC_RMI_PDEV_STREAM_CONNECT SMC_RMI_CALL(0x0204)
+#define SMC_RMI_PDEV_STREAM_DISCONNECT SMC_RMI_CALL(0x0205)
+#define SMC_RMI_PDEV_STREAM_COMPLETE SMC_RMI_CALL(0x0206)
+#define SMC_RMI_PDEV_STREAM_KEY_PURGE SMC_RMI_CALL(0x0207)
+#define SMC_RMI_OP_MEM_DONATE SMC_RMI_CALL(0x0208)
+#define SMC_RMI_OP_MEM_RECLAIM SMC_RMI_CALL(0x0209)
+#define SMC_RMI_OP_CANCEL SMC_RMI_CALL(0x020a)
+#define SMC_RMI_PDEV_SET_PROT SMC_RMI_CALL(0x020b)
+
+#define RMI_ABI_MAJOR_VERSION 2
+#define RMI_ABI_MINOR_VERSION 0
+
+#define RMI_ABI_VERSION_GET_MAJOR(version) ((version) >> 16)
+#define RMI_ABI_VERSION_GET_MINOR(version) ((version) & 0xFFFF)
+#define RMI_ABI_VERSION(major, minor) (((major) << 16) | (minor))
+
+#define RMI_UNASSIGNED 0
+#define RMI_ASSIGNED 1
+#define RMI_TABLE 2
+
+#define RMI_RETURN_STATUS(ret) ((ret) & 0xFF)
+#define RMI_RETURN_INDEX(ret) (((ret) >> 8) & 0xFF)
+#define RMI_RETURN_MEMREQ(ret) (((ret) >> 8) & 0x3)
+#define RMI_RETURN_CANCANCEL(ret) (((ret) >> 10) & 0x1)
+
+#define RMI_SUCCESS 0
+#define RMI_ERROR_INPUT 1
+#define RMI_ERROR_REALM 2
+#define RMI_ERROR_REC 3
+#define RMI_ERROR_RTT 4
+#define RMI_ERROR_NOT_SUPPORTED 5
+#define RMI_ERROR_DEVICE 6
+#define RMI_ERROR_RTT_AUX 7
+#define RMI_ERROR_PSMMU_ST 8
+#define RMI_ERROR_DPT 9
+#define RMI_BUSY 10
+#define RMI_ERROR_GLOBAL 11
+#define RMI_ERROR_TRACKING 12
+#define RMI_INCOMPLETE 13
+#define RMI_BLOCKED 14
+#define RMI_ERROR_GPT 15
+#define RMI_ERROR_GRANULE 16
+
+#define RMI_OP_MEM_REQ_NONE 0
+#define RMI_OP_MEM_REQ_DONATE 1
+#define RMI_OP_MEM_REQ_RECLAIM 2
+
+#define RMI_DONATE_SIZE(req) ((req) & 0x3)
+#define RMI_DONATE_COUNT_MASK GENMASK(15, 2)
+#define RMI_DONATE_COUNT(req) (((req) & RMI_DONATE_COUNT_MASK) >> 2)
+#define RMI_DONATE_CONTIG(req) (!!((req) & BIT(16)))
+#define RMI_DONATE_STATE(req) (!!((req) & BIT(17)))
+
+#define RMI_OP_MEM_DELEGATED 0
+#define RMI_OP_MEM_UNDELEGATED 1
+
+#define RMI_ADDR_TYPE_NONE 0
+#define RMI_ADDR_TYPE_SINGLE 1
+#define RMI_ADDR_TYPE_LIST 2
+
+#define RMI_ADDR_RANGE_SIZE(ar) (FIELD_GET(GENMASK(1, 0), (ar)))
+#define RMI_ADDR_RANGE_COUNT(ar) (FIELD_GET(GENMASK(PAGE_SHIFT - 1, 2), \
+ (ar)))
+#define RMI_ADDR_RANGE_ADDR(ar) ((ar) & PAGE_MASK & GENMASK(51, 0))
+#define RMI_ADDR_RANGE_STATE(ar) (FIELD_GET(BIT(63), (ar)))
+
+enum rmi_ripas {
+ RMI_EMPTY = 0,
+ RMI_RAM = 1,
+ RMI_DESTROYED = 2,
+};
+
+#define RMI_NO_MEASURE_CONTENT 0
+#define RMI_MEASURE_CONTENT 1
+
+#define RMI_FEATURE_REGISTER_0_S2SZ GENMASK(7, 0)
+#define RMI_FEATURE_REGISTER_0_LPA2 BIT(8)
+#define RMI_FEATURE_REGISTER_0_SVE BIT(9)
+#define RMI_FEATURE_REGISTER_0_SVE_VL GENMASK(13, 10)
+#define RMI_FEATURE_REGISTER_0_NUM_BPS GENMASK(19, 14)
+#define RMI_FEATURE_REGISTER_0_NUM_WPS GENMASK(25, 20)
+#define RMI_FEATURE_REGISTER_0_PMU BIT(26)
+#define RMI_FEATURE_REGISTER_0_PMU_NUM_CTRS GENMASK(31, 27)
+
+#define RMI_FEATURE_REGISTER_1_RMI_GRAN_SZ_4KB BIT(0)
+#define RMI_FEATURE_REGISTER_1_RMI_GRAN_SZ_16KB BIT(1)
+#define RMI_FEATURE_REGISTER_1_RMI_GRAN_SZ_64KB BIT(2)
+#define RMI_FEATURE_REGISTER_1_HASH_SHA_256 BIT(3)
+#define RMI_FEATURE_REGISTER_1_HASH_SHA_384 BIT(4)
+#define RMI_FEATURE_REGISTER_1_HASH_SHA_512 BIT(5)
+#define RMI_FEATURE_REGISTER_1_MAX_RECS_ORDER GENMASK(9, 6)
+#define RMI_FEATURE_REGISTER_1_L0GPTSZ GENMASK(13, 10)
+#define RMI_FEATURE_REGISTER_1_PPS GENMASK(16, 14)
+
+#define RMI_FEATURE_REGISTER_2_DA BIT(0)
+#define RMI_FEATURE_REGISTER_2_DA_COH BIT(1)
+#define RMI_FEATURE_REGISTER_2_VSMMU BIT(2)
+#define RMI_FEATURE_REGISTER_2_ATS BIT(3)
+#define RMI_FEATURE_REGISTER_2_MAX_VDEVS_ORDER GEN_MASK(7, 4)
+#define RMI_FEATURE_REGISTER_2_VDEV_KROU BIT(8)
+#define RMI_FEATURE_REGISTER_2_NON_TEE_STREAM BIT(9)
+
+#define RMI_FEATURE_REGISTER_3_MAX_NUM_AUX_PLANES GENMASK(3, 0)
+#define RMI_FEATURE_REGISTER_3_RTT_PLAN GENMASK(5, 4)
+#define RMI_FEATURE_REGISTER_3_RTT_S2AP_INDIRECT BIT(6)
+
+#define RMI_FEATURE_REGISTER_4_MEC_COUNT GENMASK(63, 0)
+
+#define RMI_MEM_CATEGORY_CONVENTIONAL 0
+#define RMI_MEM_CATEGORY_DEV_NCOH 1
+#define RMI_MEM_CATEGORY_DEV_COH 2
+
+#define RMI_TRACKING_RESERVED 0
+#define RMI_TRACKING_NONE 1
+#define RMI_TRACKING_FINE 2
+#define RMI_TRACKING_COARSE 3
+
+#define RMI_GRANULE_SIZE_4KB 0
+#define RMI_GRANULE_SIZE_16KB 1
+#define RMI_GRANULE_SIZE_64KB 2
+
+/*
+ * Note many of these fields are smaller than u64 but all fields have u64
+ * alignment, so use u64 to ensure correct alignment.
+ */
+struct rmm_config {
+ union { /* 0x0 */
+ struct {
+ u64 tracking_region_size;
+ u64 rmi_granule_size;
+ };
+ u8 sizer[0x1000];
+ };
+};
+
+#define RMI_REALM_PARAM_FLAG_LPA2 BIT(0)
+#define RMI_REALM_PARAM_FLAG_SVE BIT(1)
+#define RMI_REALM_PARAM_FLAG_PMU BIT(2)
+
+struct realm_params {
+ union { /* 0x0 */
+ struct {
+ u64 flags;
+ u64 s2sz;
+ u64 sve_vl;
+ u64 num_bps;
+ u64 num_wps;
+ u64 pmu_num_ctrs;
+ u64 hash_algo;
+ };
+ u8 padding0[0x400];
+ };
+ union { /* 0x400 */
+ u8 rpv[64];
+ u8 padding1[0x400];
+ };
+ union { /* 0x800 */
+ struct {
+ u64 padding;
+ u64 rtt_base;
+ s64 rtt_level_start;
+ u64 rtt_num_start;
+ };
+ u8 padding2[0x800];
+ };
+};
+
+/*
+ * The number of GPRs (starting from X0) that are
+ * configured by the host when a REC is created.
+ */
+#define REC_CREATE_NR_GPRS 8
+
+#define REC_PARAMS_FLAG_RUNNABLE BIT_ULL(0)
+
+#define REC_PARAMS_AUX_GRANULES 16
+
+struct rec_params {
+ union { /* 0x0 */
+ u64 flags;
+ u8 padding0[0x100];
+ };
+ union { /* 0x100 */
+ u64 mpidr;
+ u8 padding1[0x100];
+ };
+ union { /* 0x200 */
+ u64 pc;
+ u8 padding2[0x100];
+ };
+ union { /* 0x300 */
+ u64 gprs[REC_CREATE_NR_GPRS];
+ u8 padding3[0x500];
+ };
+ union { /* 0x800 */
+ struct {
+ u64 num_rec_aux;
+ u64 aux[REC_PARAMS_AUX_GRANULES];
+ };
+ u8 padding4[0x800];
+ };
+};
+
+#define REC_ENTER_FLAG_EMULATED_MMIO BIT(0)
+#define REC_ENTER_FLAG_INJECT_SEA BIT(1)
+#define REC_ENTER_FLAG_TRAP_WFI BIT(2)
+#define REC_ENTER_FLAG_TRAP_WFE BIT(3)
+#define REC_ENTER_FLAG_RIPAS_RESPONSE BIT(4)
+
+#define REC_RUN_GPRS 31
+#define REC_MAX_GIC_NUM_LRS 16
+
+#define RMI_PERMITTED_GICV3_HCR_BITS (ICH_HCR_EL2_UIE | \
+ ICH_HCR_EL2_LRENPIE | \
+ ICH_HCR_EL2_NPIE | \
+ ICH_HCR_EL2_VGrp0EIE | \
+ ICH_HCR_EL2_VGrp0DIE | \
+ ICH_HCR_EL2_VGrp1EIE | \
+ ICH_HCR_EL2_VGrp1DIE | \
+ ICH_HCR_EL2_TDIR)
+
+struct rec_enter {
+ union { /* 0x000 */
+ u64 flags;
+ u8 padding0[0x200];
+ };
+ union { /* 0x200 */
+ u64 gprs[REC_RUN_GPRS];
+ u8 padding1[0x100];
+ };
+ u8 padding3[0x500];
+};
+
+#define RMI_EXIT_SYNC 0x00
+#define RMI_EXIT_IRQ 0x01
+#define RMI_EXIT_FIQ 0x02
+#define RMI_EXIT_PSCI 0x03
+#define RMI_EXIT_RIPAS_CHANGE 0x04
+#define RMI_EXIT_HOST_CALL 0x05
+#define RMI_EXIT_SERROR 0x06
+
+struct rec_exit {
+ union { /* 0x000 */
+ u8 exit_reason;
+ u8 padding0[0x100];
+ };
+ union { /* 0x100 */
+ struct {
+ u64 esr;
+ u64 far;
+ u64 hpfar;
+ u64 rtt_tree;
+ };
+ u8 padding1[0x100];
+ };
+ union { /* 0x200 */
+ u64 gprs[REC_RUN_GPRS];
+ u8 padding2[0x100];
+ };
+ union { /* 0x300 */
+ u8 padding3[0x100];
+ };
+ union { /* 0x400 */
+ struct {
+ u64 cntp_ctl;
+ u64 cntp_cval;
+ u64 cntv_ctl;
+ u64 cntv_cval;
+ };
+ u8 padding4[0x100];
+ };
+ union { /* 0x500 */
+ struct {
+ u64 ripas_base;
+ u64 ripas_top;
+ u8 ripas_value;
+ u8 padding8[15];
+ u64 s2ap_base;
+ u64 s2ap_top;
+ u64 vdev_id_1;
+ u64 vdev_id_2;
+ u64 dev_mem_base;
+ u64 dev_mem_top;
+ u64 dev_mem_pa;
+ };
+ u8 padding5[0x100];
+ };
+ union { /* 0x600 */
+ struct {
+ u16 imm;
+ u16 padding9;
+ u64 plane;
+ };
+ u8 padding6[0x100];
+ };
+ union { /* 0x700 */
+ struct {
+ u8 pmu_ovf_status;
+ u8 padding10[15];
+ u64 vsmmu;
+ };
+ u8 padding7[0x100];
+ };
+};
+
+struct rec_run {
+ struct rec_enter enter;
+ struct rec_exit exit;
+};
+
+#endif /* __ASM_RMI_SMC_H */
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox