* [PATCH v2 01/22] KVM: arm64: Prefault host stage-2 entries on block split
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 02/22] KVM: arm64: Propagate host stage-2 annotated " Vincent Donnefort
` (20 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang, Keir Fraser,
Vincent Donnefort
From: Keir Fraser <keirf@google.com>
For the host stage-2 that is idmap, we can avoid lazy-mapping on block
splitting by propagating existing valid mappings to neighbouring entries
of the new table.
Signed-off-by: Keir Fraser <keirf@google.com>
Co-developed-by: Quentin Perret <qperret@google.com>
Signed-off-by: Quentin Perret <qperret@google.com>
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/pgtable.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
index b74dd5ce1efd..34f78a58cded 100644
--- a/arch/arm64/kvm/hyp/pgtable.c
+++ b/arch/arm64/kvm/hyp/pgtable.c
@@ -1030,10 +1030,39 @@ static int stage2_map_walk_table_pre(const struct kvm_pgtable_visit_ctx *ctx,
return 0;
}
+static void stage2_map_prefault_idmap(const struct kvm_pgtable_visit_ctx *ctx, kvm_pte_t *ptep)
+{
+ kvm_pte_t block_pte = ctx->old;
+ u64 pa;
+ int i;
+
+ if (!kvm_pte_valid(block_pte))
+ return;
+
+ pa = ALIGN_DOWN(ctx->addr, kvm_granule_size(ctx->level));
+ for (i = 0; i < PTRS_PER_PTE; ++i, ++ptep, pa += kvm_granule_size(ctx->level + 1)) {
+ kvm_pte_t pte = kvm_init_valid_leaf_pte(pa, block_pte, ctx->level + 1);
+
+ /*
+ * Skip ptes in the range being modified by the caller if we're
+ * installing last level entries. Otherwise, we need to
+ * temporarily put in a valid mapping to make sure the
+ * prefaulting logic is triggered on the next
+ * stage2_map_walk_leaf(). This adds an unnecessary TLBI as
+ * we'll presumably re-break the freshly installed block, but
+ * that should happen very infrequently.
+ */
+ if ((ctx->level < (KVM_PGTABLE_LAST_LEVEL - 1)) ||
+ (pa < ctx->addr) || (pa >= ctx->end))
+ *ptep = pte;
+ }
+}
+
static int stage2_map_walk_leaf(const struct kvm_pgtable_visit_ctx *ctx,
struct stage2_map_data *data)
{
struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
+ struct kvm_pgtable *pgt = data->mmu->pgt;
kvm_pte_t *childp, new;
int ret;
@@ -1051,6 +1080,9 @@ static int stage2_map_walk_leaf(const struct kvm_pgtable_visit_ctx *ctx,
if (!childp)
return -ENOMEM;
+ if (pgt->flags & KVM_PGTABLE_S2_IDMAP)
+ stage2_map_prefault_idmap(ctx, childp);
+
if (!stage2_try_break_pte(ctx, data->mmu)) {
mm_ops->put_page(childp);
return -EAGAIN;
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 02/22] KVM: arm64: Propagate host stage-2 annotated entries on block split
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 01/22] KVM: arm64: Prefault host stage-2 entries on block split Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 03/22] KVM: arm64: Allow block-level stage-2 annotation Vincent Donnefort
` (19 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
Propagate ownership refcounts for invalid stage-2 entries mapped on
block split. This intends to allow splitting host stage-2 blocks while
keeping existing page ownership and metadata annotations.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/pgtable.c | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
index 34f78a58cded..0ca157ecee64 100644
--- a/arch/arm64/kvm/hyp/pgtable.c
+++ b/arch/arm64/kvm/hyp/pgtable.c
@@ -1033,15 +1033,28 @@ static int stage2_map_walk_table_pre(const struct kvm_pgtable_visit_ctx *ctx,
static void stage2_map_prefault_idmap(const struct kvm_pgtable_visit_ctx *ctx, kvm_pte_t *ptep)
{
kvm_pte_t block_pte = ctx->old;
+ bool counted, valid;
u64 pa;
int i;
- if (!kvm_pte_valid(block_pte))
+ counted = stage2_pte_is_counted(block_pte);
+ valid = kvm_pte_valid(block_pte);
+
+ if (!valid && !counted)
+ return;
+
+ /*
+ * Shared walks not supported: cannot rollback refcounts on break
+ * failure.
+ */
+ if (counted && WARN_ON_ONCE(kvm_pgtable_walk_shared(ctx)))
return;
pa = ALIGN_DOWN(ctx->addr, kvm_granule_size(ctx->level));
for (i = 0; i < PTRS_PER_PTE; ++i, ++ptep, pa += kvm_granule_size(ctx->level + 1)) {
- kvm_pte_t pte = kvm_init_valid_leaf_pte(pa, block_pte, ctx->level + 1);
+ kvm_pte_t pte = valid ?
+ kvm_init_valid_leaf_pte(pa, block_pte, ctx->level + 1) :
+ block_pte;
/*
* Skip ptes in the range being modified by the caller if we're
@@ -1053,8 +1066,11 @@ static void stage2_map_prefault_idmap(const struct kvm_pgtable_visit_ctx *ctx, k
* that should happen very infrequently.
*/
if ((ctx->level < (KVM_PGTABLE_LAST_LEVEL - 1)) ||
- (pa < ctx->addr) || (pa >= ctx->end))
+ (pa < ctx->addr) || (pa >= ctx->end)) {
*ptep = pte;
+ if (counted)
+ ctx->mm_ops->get_page(ptep);
+ }
}
}
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 03/22] KVM: arm64: Allow block-level stage-2 annotation
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 01/22] KVM: arm64: Prefault host stage-2 entries on block split Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 02/22] KVM: arm64: Propagate host stage-2 annotated " Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 04/22] KVM: arm64: Use block-level annotations when setting up the host stage-2 Vincent Donnefort
` (18 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
On host to guest donation, pKVM annotates the host stage-2 invalid
entries with the GFN. Now that the host stage-2 preserves annotations on
block split, we can allow these annotations at block-level. This enables
safe support for guest stage-2 huge mappings.
Allowing block-level annotations also reduces stage-2 fragmentation when
unmapping a huge region, and reduces host stage-2 memory usage by
avoiding the allocation of intermediate page tables.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/pgtable.c | 4 ----
1 file changed, 4 deletions(-)
diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
index 0ca157ecee64..c4ebae0544d4 100644
--- a/arch/arm64/kvm/hyp/pgtable.c
+++ b/arch/arm64/kvm/hyp/pgtable.c
@@ -947,9 +947,6 @@ static bool stage2_leaf_mapping_allowed(const struct kvm_pgtable_visit_ctx *ctx,
if (data->force_pte && ctx->level < KVM_PGTABLE_LAST_LEVEL)
return false;
- if (data->annotation)
- return true;
-
return kvm_block_mapping_supported(ctx, phys);
}
@@ -1178,7 +1175,6 @@ int kvm_pgtable_stage2_annotate(struct kvm_pgtable *pgt, u64 addr, u64 size,
struct stage2_map_data map_data = {
.mmu = pgt->mmu,
.memcache = mc,
- .force_pte = true,
.annotation = true,
.pte_annot = pte_annot |
FIELD_PREP(KVM_INVALID_PTE_TYPE_MASK, type),
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 04/22] KVM: arm64: Use block-level annotations when setting up the host stage-2
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (2 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 03/22] KVM: arm64: Allow block-level stage-2 annotation Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 05/22] KVM: arm64: Make pKVM ownership selftest an HVC Vincent Donnefort
` (17 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
During pKVM initialisation, fix_host_ownership() configures the host
stage-2 mappings to match the hypervisor stage-1. Previously this was
done page-by-page. Now that the host stage-2 supports block-level
annotations, refactor the walker to apply the ownership state to an
entire range at once.
This reduces host stage-2 page-table fragmentation and memory usage by
avoiding allocation of intermediate page-table pages.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/include/nvhe/memory.h | 5 +
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 5 -
arch/arm64/kvm/hyp/nvhe/setup.c | 132 ++++++++++++++---------
3 files changed, 89 insertions(+), 53 deletions(-)
diff --git a/arch/arm64/kvm/hyp/include/nvhe/memory.h b/arch/arm64/kvm/hyp/include/nvhe/memory.h
index b50712d47f6d..86a51cf1d590 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/memory.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/memory.h
@@ -122,6 +122,11 @@ static inline void set_hyp_state(struct hyp_page *p, enum pkvm_page_state state)
p->__hyp_state_comp = state ^ PKVM_PAGE_STATE_VMEMMAP_MASK;
}
+#define for_each_hyp_page(__p, __st, __sz) \
+ for (struct hyp_page *__p = hyp_phys_to_page(__st), \
+ *__e = __p + ((__sz) >> PAGE_SHIFT); \
+ __p < __e; __p++)
+
/*
* Refcounting for 'struct hyp_page'.
* hyp_pool::lock must be held if atomic access to the refcount is required.
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 39aa8911f62c..c3676243fddc 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -76,11 +76,6 @@ static void hyp_unlock_component(void)
hyp_spin_unlock(&pkvm_pgd_lock);
}
-#define for_each_hyp_page(__p, __st, __sz) \
- for (struct hyp_page *__p = hyp_phys_to_page(__st), \
- *__e = __p + ((__sz) >> PAGE_SHIFT); \
- __p < __e; __p++)
-
static void *host_s2_zalloc_pages_exact(size_t size)
{
void *addr = hyp_alloc_pages(&host_s2_pool, get_order(size));
diff --git a/arch/arm64/kvm/hyp/nvhe/setup.c b/arch/arm64/kvm/hyp/nvhe/setup.c
index 75b00c323310..22200bb50990 100644
--- a/arch/arm64/kvm/hyp/nvhe/setup.c
+++ b/arch/arm64/kvm/hyp/nvhe/setup.c
@@ -186,50 +186,46 @@ static void hpool_put_page(void *addr)
hyp_put_page(&hpool, addr);
}
-static int fix_host_ownership_walker(const struct kvm_pgtable_visit_ctx *ctx,
- enum kvm_pgtable_walk_flags visit)
+struct fix_host_ownership_data {
+ phys_addr_t phys;
+ size_t size;
+ enum kvm_pgtable_prot prot;
+};
+
+static int __fix_host_ownership(const struct fix_host_ownership_data *data)
{
- enum pkvm_page_state state;
- struct hyp_page *page;
- phys_addr_t phys;
- enum kvm_pgtable_prot prot;
+ enum pkvm_page_state state = pkvm_getstate(data->prot);
- if (!kvm_pte_valid(ctx->old))
+ if (!data->prot)
return 0;
- if (ctx->level != KVM_PGTABLE_LAST_LEVEL)
- return -EINVAL;
-
- phys = kvm_pte_to_phys(ctx->old);
- if (!addr_is_memory(phys))
- return -EINVAL;
-
- page = hyp_phys_to_page(phys);
-
/*
* Adjust the host stage-2 mappings to match the ownership attributes
* configured in the hypervisor stage-1, and make sure to propagate them
* to the hyp_vmemmap state.
*/
- prot = kvm_pgtable_hyp_pte_prot(ctx->old);
- state = pkvm_getstate(prot);
switch (state) {
case PKVM_PAGE_OWNED:
- set_hyp_state(page, PKVM_PAGE_OWNED);
- /* hyp text is RO in the host stage-2 to be inspected on panic. */
- if (prot == PAGE_HYP_EXEC) {
+ for_each_hyp_page(page, data->phys, data->size) {
+ set_hyp_state(page, PKVM_PAGE_OWNED);
set_host_state(page, PKVM_NOPAGE);
- return host_stage2_idmap_locked(phys, PAGE_SIZE, KVM_PGTABLE_PROT_R);
- } else {
- return host_stage2_set_owner_locked(phys, PAGE_SIZE, PKVM_ID_HYP);
}
+ /* hyp text is RO in the host stage-2 to be inspected on panic. */
+ if (data->prot == PAGE_HYP_EXEC)
+ return host_stage2_idmap_locked(data->phys, data->size, KVM_PGTABLE_PROT_R);
+ else
+ return host_stage2_set_owner_locked(data->phys, data->size, PKVM_ID_HYP);
case PKVM_PAGE_SHARED_OWNED:
- set_hyp_state(page, PKVM_PAGE_SHARED_OWNED);
- set_host_state(page, PKVM_PAGE_SHARED_BORROWED);
+ for_each_hyp_page(page, data->phys, data->size) {
+ set_hyp_state(page, PKVM_PAGE_SHARED_OWNED);
+ set_host_state(page, PKVM_PAGE_SHARED_BORROWED);
+ }
break;
case PKVM_PAGE_SHARED_BORROWED:
- set_hyp_state(page, PKVM_PAGE_SHARED_BORROWED);
- set_host_state(page, PKVM_PAGE_SHARED_OWNED);
+ for_each_hyp_page(page, data->phys, data->size) {
+ set_hyp_state(page, PKVM_PAGE_SHARED_BORROWED);
+ set_host_state(page, PKVM_PAGE_SHARED_OWNED);
+ }
break;
default:
return -EINVAL;
@@ -238,6 +234,66 @@ static int fix_host_ownership_walker(const struct kvm_pgtable_visit_ctx *ctx,
return 0;
}
+static int fix_host_ownership_walker(const struct kvm_pgtable_visit_ctx *ctx,
+ enum kvm_pgtable_walk_flags visit)
+{
+ struct fix_host_ownership_data *data = ctx->arg;
+ enum kvm_pgtable_prot prot = 0;
+ phys_addr_t phys = 0;
+ int ret;
+
+ if (kvm_pte_valid(ctx->old)) {
+ prot = kvm_pgtable_hyp_pte_prot(ctx->old);
+ phys = kvm_pte_to_phys(ctx->old);
+ if (!addr_is_memory(phys))
+ return -EINVAL;
+ }
+
+ if (prot == data->prot &&
+ phys == data->phys + data->size) {
+ data->size += kvm_granule_size(ctx->level);
+
+ return 0;
+ }
+
+ ret = __fix_host_ownership(data);
+ if (ret)
+ return ret;
+
+ data->phys = phys;
+ data->size = kvm_granule_size(ctx->level);
+ data->prot = prot;
+
+ return 0;
+}
+
+static int fix_host_ownership(void)
+{
+ struct fix_host_ownership_data data;
+ struct kvm_pgtable_walker walker = {
+ .cb = fix_host_ownership_walker,
+ .flags = KVM_PGTABLE_WALK_LEAF,
+ .arg = &data,
+ };
+ int i, ret;
+
+ for (i = 0; i < hyp_memblock_nr; i++) {
+ struct memblock_region *reg = &hyp_memory[i];
+ u64 start = (u64)hyp_phys_to_virt(reg->base);
+
+ data = (struct fix_host_ownership_data){ 0 };
+ ret = kvm_pgtable_walk(&pkvm_pgtable, start, reg->size, &walker);
+ if (ret)
+ return ret;
+
+ ret = __fix_host_ownership(&data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static int fix_hyp_pgtable_refcnt_walker(const struct kvm_pgtable_visit_ctx *ctx,
enum kvm_pgtable_walk_flags visit)
{
@@ -252,26 +308,6 @@ static int fix_hyp_pgtable_refcnt_walker(const struct kvm_pgtable_visit_ctx *ctx
return 0;
}
-static int fix_host_ownership(void)
-{
- struct kvm_pgtable_walker walker = {
- .cb = fix_host_ownership_walker,
- .flags = KVM_PGTABLE_WALK_LEAF,
- };
- int i, ret;
-
- for (i = 0; i < hyp_memblock_nr; i++) {
- struct memblock_region *reg = &hyp_memory[i];
- u64 start = (u64)hyp_phys_to_virt(reg->base);
-
- ret = kvm_pgtable_walk(&pkvm_pgtable, start, reg->size, &walker);
- if (ret)
- return ret;
- }
-
- return 0;
-}
-
static int fix_hyp_pgtable_refcnt(void)
{
struct kvm_pgtable_walker walker = {
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 05/22] KVM: arm64: Make pKVM ownership selftest an HVC
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (3 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 04/22] KVM: arm64: Use block-level annotations when setting up the host stage-2 Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 06/22] KVM: arm64: Add a range to __pkvm_host_share/unshare_hyp() Vincent Donnefort
` (16 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
In preparation for testing the pKVM ownership with huge mappings,
refactor the selftest to be triggered via an HVC using dynamically
allocated memory from the host. This avoids wasting memory which would
be even worse for testing ownership transitions for huge stage-2
mappings.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/include/asm/kvm_asm.h | 1 +
arch/arm64/include/asm/kvm_pkvm.h | 12 +++---
arch/arm64/kvm/arm.c | 2 +
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 7 ++--
arch/arm64/kvm/hyp/include/nvhe/pkvm.h | 4 ++
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 10 +++++
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 39 +++++++++++++------
arch/arm64/kvm/hyp/nvhe/pkvm.c | 12 +++++-
arch/arm64/kvm/hyp/nvhe/setup.c | 8 ----
arch/arm64/kvm/pkvm.c | 30 +++++++++++++-
10 files changed, 92 insertions(+), 33 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index e5b92ac09e69..7fdec5d98201 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -62,6 +62,7 @@ enum __kvm_host_smccc_func {
__KVM_HOST_SMCCC_FUNC___kvm_enable_ssbs,
__KVM_HOST_SMCCC_FUNC___vgic_v3_init_lrs,
__KVM_HOST_SMCCC_FUNC___vgic_v3_get_gic_config,
+ __KVM_HOST_SMCCC_FUNC___pkvm_ownership_selftest,
MARKER(__KVM_HOST_SMCCC_FUNC_MIN_PKVM),
diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h
index beea00e693a0..ff8e93efcd7a 100644
--- a/arch/arm64/include/asm/kvm_pkvm.h
+++ b/arch/arm64/include/asm/kvm_pkvm.h
@@ -23,6 +23,12 @@ bool pkvm_hyp_vm_is_created(struct kvm *kvm);
void pkvm_destroy_hyp_vm(struct kvm *kvm);
int pkvm_create_hyp_vcpu(struct kvm_vcpu *vcpu);
+#ifdef CONFIG_NVHE_EL2_DEBUG
+void pkvm_ownership_selftest(void);
+#else
+static inline void pkvm_ownership_selftest(void) { }
+#endif
+
/*
* Check whether the specific capability is allowed in pKVM.
*
@@ -161,12 +167,6 @@ static inline unsigned long host_s2_pgtable_pages(void)
return res;
}
-#ifdef CONFIG_NVHE_EL2_DEBUG
-static inline unsigned long pkvm_selftest_pages(void) { return 32; }
-#else
-static inline unsigned long pkvm_selftest_pages(void) { return 0; }
-#endif
-
#define KVM_FFA_MBOX_NR_PAGES 1
static inline unsigned long hyp_ffa_proxy_pages(void)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 8b080804bc90..fb36e73a8ffb 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2914,6 +2914,8 @@ static int __init init_hyp_mode(void)
kvm_err("Failed to init hyp memory protection\n");
goto out_err;
}
+
+ pkvm_ownership_selftest();
}
return 0;
diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
index 29935c7da1de..e2a5d7ffec7d 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
@@ -74,10 +74,9 @@ static __always_inline void __load_host_stage2(void)
}
#ifdef CONFIG_NVHE_EL2_DEBUG
-void pkvm_ownership_selftest(void *base);
-struct pkvm_hyp_vcpu *init_selftest_vm(void *virt);
-void teardown_selftest_vm(void);
+int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr_pages);
#else
-static inline void pkvm_ownership_selftest(void *base) { }
+static inline int
+__pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr_pages) { return 0; }
#endif
#endif /* __KVM_NVHE_MEM_PROTECT__ */
diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
index c904647d2f76..f04cea5ff389 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
@@ -93,5 +93,9 @@ bool kvm_handle_pvm_sysreg(struct kvm_vcpu *vcpu, u64 *exit_code);
bool kvm_handle_pvm_restricted(struct kvm_vcpu *vcpu, u64 *exit_code);
void kvm_init_pvm_id_regs(struct kvm_vcpu *vcpu);
int kvm_check_pvm_sysreg_table(void);
+#ifdef CONFIG_NVHE_EL2_DEBUG
+struct pkvm_hyp_vcpu *init_selftest_vm(void *virt, u64 nr_pages);
+void teardown_selftest_vm(void);
+#endif
#endif /* __ARM64_KVM_NVHE_PKVM_H__ */
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 9a3b92e626ad..9fbe703fc595 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -751,6 +751,15 @@ static void handle___pkvm_create_private_mapping(struct kvm_cpu_context *host_ct
cpu_reg(host_ctxt, 1) = haddr;
}
+static void handle___pkvm_ownership_selftest(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(u64, pfn, host_ctxt, 1);
+ DECLARE_REG(u64, pgtable_pfn, host_ctxt, 2);
+ DECLARE_REG(unsigned long, pgtable_nr_pages, host_ctxt, 3);
+
+ cpu_reg(host_ctxt, 1) = __pkvm_ownership_selftest(pfn, pgtable_pfn, pgtable_nr_pages);
+}
+
static void handle___pkvm_prot_finalize(struct kvm_cpu_context *host_ctxt)
{
cpu_reg(host_ctxt, 1) = __pkvm_prot_finalize();
@@ -911,6 +920,7 @@ static const hcall_t host_hcall[] = {
HANDLE_FUNC(__kvm_enable_ssbs),
HANDLE_FUNC(__vgic_v3_init_lrs),
HANDLE_FUNC(__vgic_v3_get_gic_config),
+ HANDLE_FUNC(__pkvm_ownership_selftest),
HANDLE_FUNC(__pkvm_prot_finalize),
HANDLE_FUNC(__kvm_adjust_pc),
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index c3676243fddc..183ecd7058cf 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -1748,24 +1748,36 @@ static void assert_page_state(void)
assert_page_state(); \
} while (0)
-void pkvm_ownership_selftest(void *base)
+int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr_pages)
{
enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_RWX;
- void *virt = hyp_alloc_pages(&host_s2_pool, 0);
struct pkvm_hyp_vcpu *vcpu;
- u64 phys, size, pfn, gfn;
struct pkvm_hyp_vm *vm;
+ u64 phys, size, gfn;
+ void *virt;
+ int ret;
- WARN_ON(!virt);
- selftest_page = hyp_virt_to_page(virt);
- selftest_page->refcount = 0;
- selftest_vcpu = vcpu = init_selftest_vm(base);
+ ret = __pkvm_host_donate_hyp(pgtable_pfn, pgtable_nr_pages);
+ if (ret)
+ return ret;
+
+ ret = __pkvm_host_donate_hyp(pfn, 1);
+ if (ret) {
+ WARN_ON(__pkvm_hyp_donate_host(pgtable_pfn, pgtable_nr_pages));
+ return ret;
+ }
+
+ selftest_vcpu = vcpu = init_selftest_vm(hyp_phys_to_virt(hyp_pfn_to_phys(pgtable_pfn)),
+ pgtable_nr_pages);
vm = pkvm_hyp_vcpu_to_hyp_vm(vcpu);
- size = PAGE_SIZE << selftest_page->order;
- phys = hyp_virt_to_phys(virt);
- pfn = hyp_phys_to_pfn(phys);
+ phys = hyp_pfn_to_phys(pfn);
+ virt = hyp_phys_to_virt(phys);
gfn = hyp_phys_to_pfn(selftest_ipa());
+ size = PAGE_SIZE;
+
+ selftest_page = hyp_virt_to_page(virt);
+ selftest_page->refcount = 0;
selftest_state.host = PKVM_NOPAGE;
selftest_state.hyp = PKVM_PAGE_OWNED;
@@ -1926,8 +1938,11 @@ void pkvm_ownership_selftest(void *base)
selftest_state.hyp = PKVM_PAGE_OWNED;
assert_transition_res(0, __pkvm_host_donate_hyp, pfn, 1);
+ /* This also gives back the ownership to the host */
teardown_selftest_vm();
- selftest_page->refcount = 1;
- hyp_put_page(&host_s2_pool, virt);
+
+ WARN_ON(__pkvm_hyp_donate_host(pfn, 1));
+
+ return 0;
}
#endif
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index 459bd9eb7e4b..ad9afe2cd1b9 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -762,7 +762,7 @@ static struct pkvm_hyp_vcpu selftest_vcpu = {
},
};
-struct pkvm_hyp_vcpu *init_selftest_vm(void *virt)
+struct pkvm_hyp_vcpu *init_selftest_vm(void *virt, u64 nr_pages)
{
struct hyp_page *p = hyp_virt_to_page(virt);
unsigned long min_pages, seeded = 0;
@@ -778,7 +778,7 @@ struct pkvm_hyp_vcpu *init_selftest_vm(void *virt)
*/
min_pages = kvm_mmu_cache_min_pages(&selftest_vm.kvm.arch.mmu);
- for (i = 0; i < pkvm_selftest_pages(); i++) {
+ for (i = 0; i < nr_pages; i++) {
if (p[i].refcount)
continue;
p[i].refcount = 1;
@@ -798,9 +798,17 @@ struct pkvm_hyp_vcpu *init_selftest_vm(void *virt)
void teardown_selftest_vm(void)
{
+ struct kvm_hyp_memcache *vcpu_mc = &selftest_vcpu.vcpu.arch.pkvm_memcache;
+ struct kvm_hyp_memcache mc = { 0 };
+
+ while (vcpu_mc->nr_pages)
+ hyp_put_page(&selftest_vm.pool, pop_hyp_memcache(vcpu_mc, hyp_phys_to_virt));
+
hyp_spin_lock(&vm_table_lock);
remove_vm_table_entry(selftest_vm.kvm.arch.pkvm.handle);
hyp_spin_unlock(&vm_table_lock);
+
+ reclaim_pgtable_pages(&selftest_vm, &mc);
}
#endif /* CONFIG_NVHE_EL2_DEBUG */
diff --git a/arch/arm64/kvm/hyp/nvhe/setup.c b/arch/arm64/kvm/hyp/nvhe/setup.c
index 22200bb50990..38d3212f8678 100644
--- a/arch/arm64/kvm/hyp/nvhe/setup.c
+++ b/arch/arm64/kvm/hyp/nvhe/setup.c
@@ -28,7 +28,6 @@ static void *vmemmap_base;
static void *vm_table_base;
static void *hyp_pgt_base;
static void *host_s2_pgt_base;
-static void *selftest_base;
static void *ffa_proxy_pages;
static struct kvm_pgtable_mm_ops pkvm_pgtable_mm_ops;
static struct hyp_pool hpool;
@@ -39,11 +38,6 @@ static int divide_memory_pool(void *virt, unsigned long size)
hyp_early_alloc_init(virt, size);
- nr_pages = pkvm_selftest_pages();
- selftest_base = hyp_early_alloc_contig(nr_pages);
- if (nr_pages && !selftest_base)
- return -ENOMEM;
-
nr_pages = hyp_vmemmap_pages(sizeof(struct hyp_page));
vmemmap_base = hyp_early_alloc_contig(nr_pages);
if (!vmemmap_base)
@@ -365,8 +359,6 @@ void __noreturn __pkvm_init_finalise(void)
goto out;
pkvm_hyp_vm_table_init(vm_table_base);
-
- pkvm_ownership_selftest(selftest_base);
out:
/*
* We tail-called to here from handle___pkvm_init() and will not return,
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index 8e4c6e4bec12..b88c1fcaadbd 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -61,7 +61,6 @@ void __init kvm_hyp_reserve(void)
hyp_mem_pages += host_s2_pgtable_pages();
hyp_mem_pages += hyp_vm_table_pages();
hyp_mem_pages += hyp_vmemmap_pages(STRUCT_HYP_PAGE_SIZE);
- hyp_mem_pages += pkvm_selftest_pages();
hyp_mem_pages += hyp_ffa_proxy_pages();
/*
@@ -257,6 +256,35 @@ static void __init _kvm_host_prot_finalize(void *arg)
WRITE_ONCE(*err, -EINVAL);
}
+#ifdef CONFIG_NVHE_EL2_DEBUG
+void __init pkvm_ownership_selftest(void)
+{
+ unsigned long pgtable_order = 5;
+ unsigned long pgtable = 0;
+ unsigned long page = 0;
+ long ret = -ENOMEM;
+
+ /* Memory for the VM page-table */
+ pgtable = __get_free_pages(GFP_KERNEL, pgtable_order);
+ if (!pgtable)
+ goto out;
+
+ /* Page whose ownership will be tested */
+ page = __get_free_page(GFP_KERNEL);
+ if (!page)
+ goto out;
+
+ ret = kvm_call_hyp_nvhe(__pkvm_ownership_selftest, virt_to_pfn((void *)page),
+ virt_to_pfn((void *)pgtable), 1 << pgtable_order);
+out:
+ if (ret)
+ kvm_err("Failed to run %s (%ld)\n", __func__, ret);
+
+ free_pages(pgtable, pgtable_order);
+ free_page(page);
+}
+#endif
+
static int __init pkvm_drop_host_privileges(void)
{
int ret = 0;
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 06/22] KVM: arm64: Add a range to __pkvm_host_share/unshare_hyp()
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (4 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 05/22] KVM: arm64: Make pKVM ownership selftest an HVC Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 07/22] KVM: arm64: Add a range to __pkvm_host_donate_guest() Vincent Donnefort
` (15 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
Nothing prevents the host/hyp sharing transition from working on a
range. In preparation for testing huge mappings with the pKVM ownership
selftest, add a range to these hypercalls to allow them to be tested
just like the others without any special case.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 4 +-
arch/arm64/kvm/hyp/nvhe/ffa.c | 12 ++---
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 4 +-
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 50 +++++++++++--------
4 files changed, 38 insertions(+), 32 deletions(-)
diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
index e2a5d7ffec7d..1f70162fa625 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
@@ -31,10 +31,10 @@ enum pkvm_component_id {
};
int __pkvm_prot_finalize(void);
-int __pkvm_host_share_hyp(u64 pfn);
+int __pkvm_host_share_hyp(u64 pfn, u64 nr_pages);
int __pkvm_guest_share_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn);
int __pkvm_guest_unshare_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn);
-int __pkvm_host_unshare_hyp(u64 pfn);
+int __pkvm_host_unshare_hyp(u64 pfn, u64 nr_pages);
int __pkvm_host_donate_hyp(u64 pfn, u64 nr_pages);
int __pkvm_hyp_donate_host(u64 pfn, u64 nr_pages);
int __pkvm_host_share_ffa(u64 pfn, u64 nr_pages);
diff --git a/arch/arm64/kvm/hyp/nvhe/ffa.c b/arch/arm64/kvm/hyp/nvhe/ffa.c
index a327c2bbb6b6..a6e12f240c50 100644
--- a/arch/arm64/kvm/hyp/nvhe/ffa.c
+++ b/arch/arm64/kvm/hyp/nvhe/ffa.c
@@ -263,13 +263,13 @@ static void do_ffa_rxtx_map(struct arm_smccc_1_2_regs *res,
if (ret)
goto out_unlock;
- ret = __pkvm_host_share_hyp(hyp_phys_to_pfn(tx));
+ ret = __pkvm_host_share_hyp(hyp_phys_to_pfn(tx), 1);
if (ret) {
ret = FFA_RET_INVALID_PARAMETERS;
goto err_unmap;
}
- ret = __pkvm_host_share_hyp(hyp_phys_to_pfn(rx));
+ ret = __pkvm_host_share_hyp(hyp_phys_to_pfn(rx), 1);
if (ret) {
ret = FFA_RET_INVALID_PARAMETERS;
goto err_unshare_tx;
@@ -301,9 +301,9 @@ static void do_ffa_rxtx_map(struct arm_smccc_1_2_regs *res,
err_unpin_tx:
hyp_unpin_shared_mem(tx_virt, tx_virt + 1);
err_unshare_rx:
- __pkvm_host_unshare_hyp(hyp_phys_to_pfn(rx));
+ __pkvm_host_unshare_hyp(hyp_phys_to_pfn(rx), 1);
err_unshare_tx:
- __pkvm_host_unshare_hyp(hyp_phys_to_pfn(tx));
+ __pkvm_host_unshare_hyp(hyp_phys_to_pfn(tx), 1);
err_unmap:
ffa_unmap_hyp_buffers();
goto out_unlock;
@@ -327,11 +327,11 @@ static void do_ffa_rxtx_unmap(struct arm_smccc_1_2_regs *res,
}
hyp_unpin_shared_mem(host_buffers.tx, host_buffers.tx + 1);
- WARN_ON(__pkvm_host_unshare_hyp(hyp_virt_to_pfn(host_buffers.tx)));
+ WARN_ON(__pkvm_host_unshare_hyp(hyp_virt_to_pfn(host_buffers.tx), 1));
host_buffers.tx = NULL;
hyp_unpin_shared_mem(host_buffers.rx, host_buffers.rx + 1);
- WARN_ON(__pkvm_host_unshare_hyp(hyp_virt_to_pfn(host_buffers.rx)));
+ WARN_ON(__pkvm_host_unshare_hyp(hyp_virt_to_pfn(host_buffers.rx), 1));
host_buffers.rx = NULL;
ffa_unmap_hyp_buffers();
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 9fbe703fc595..bb23006e60c4 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -716,14 +716,14 @@ static void handle___pkvm_host_share_hyp(struct kvm_cpu_context *host_ctxt)
{
DECLARE_REG(u64, pfn, host_ctxt, 1);
- cpu_reg(host_ctxt, 1) = __pkvm_host_share_hyp(pfn);
+ cpu_reg(host_ctxt, 1) = __pkvm_host_share_hyp(pfn, 1);
}
static void handle___pkvm_host_unshare_hyp(struct kvm_cpu_context *host_ctxt)
{
DECLARE_REG(u64, pfn, host_ctxt, 1);
- cpu_reg(host_ctxt, 1) = __pkvm_host_unshare_hyp(pfn);
+ cpu_reg(host_ctxt, 1) = __pkvm_host_unshare_hyp(pfn, 1);
}
static void handle___pkvm_create_private_mapping(struct kvm_cpu_context *host_ctxt)
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 183ecd7058cf..fb01f6129217 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -969,12 +969,15 @@ int __pkvm_vcpu_in_poison_fault(struct pkvm_hyp_vcpu *hyp_vcpu)
return ret;
}
-int __pkvm_host_share_hyp(u64 pfn)
+int __pkvm_host_share_hyp(u64 pfn, u64 nr_pages)
{
u64 phys = hyp_pfn_to_phys(pfn);
- u64 size = PAGE_SIZE;
+ u64 size = PAGE_SIZE * nr_pages;
int ret;
+ if (!pfn_range_is_valid(pfn, nr_pages))
+ return -EINVAL;
+
host_lock_component();
hyp_lock_component();
@@ -1061,12 +1064,15 @@ int __pkvm_guest_unshare_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn)
return ret;
}
-int __pkvm_host_unshare_hyp(u64 pfn)
+int __pkvm_host_unshare_hyp(u64 pfn, u64 nr_pages)
{
u64 phys = hyp_pfn_to_phys(pfn);
- u64 size = PAGE_SIZE;
+ u64 size = PAGE_SIZE * nr_pages;
int ret;
+ if (!pfn_range_is_valid(pfn, nr_pages))
+ return -EINVAL;
+
host_lock_component();
hyp_lock_component();
@@ -1784,8 +1790,8 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.guest[0] = selftest_state.guest[1] = PKVM_NOPAGE;
assert_page_state();
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_unshare_ffa, pfn, 1);
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
@@ -1797,15 +1803,15 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.hyp = PKVM_NOPAGE;
assert_transition_res(0, __pkvm_hyp_donate_host, pfn, 1);
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_unshare_ffa, pfn, 1);
assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, 1, vm);
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
selftest_state.host = PKVM_PAGE_SHARED_OWNED;
selftest_state.hyp = PKVM_PAGE_SHARED_BORROWED;
- assert_transition_res(0, __pkvm_host_share_hyp, pfn);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
+ assert_transition_res(0, __pkvm_host_share_hyp, pfn, 1);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
@@ -1817,8 +1823,8 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(0, hyp_pin_shared_mem, virt, virt + size);
hyp_unpin_shared_mem(virt, virt + size);
WARN_ON(hyp_page_count(virt) != 1);
- assert_transition_res(-EBUSY, __pkvm_host_unshare_hyp, pfn);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
+ assert_transition_res(-EBUSY, __pkvm_host_unshare_hyp, pfn, 1);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
@@ -1832,15 +1838,15 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.host = PKVM_PAGE_OWNED;
selftest_state.hyp = PKVM_NOPAGE;
- assert_transition_res(0, __pkvm_host_unshare_hyp, pfn);
+ assert_transition_res(0, __pkvm_host_unshare_hyp, pfn, 1);
selftest_state.host = PKVM_PAGE_SHARED_OWNED;
selftest_state.hyp = PKVM_NOPAGE;
assert_transition_res(0, __pkvm_host_share_ffa, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, 1, vm);
@@ -1858,8 +1864,8 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, vcpu);
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
@@ -1884,8 +1890,8 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + 1, 1, vcpu, prot);
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
selftest_state.host = PKVM_PAGE_SHARED_BORROWED;
@@ -1898,8 +1904,8 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + 1, 1, vcpu, prot);
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
selftest_state.host = PKVM_NOPAGE;
@@ -1912,8 +1918,8 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + 1, 1, vcpu, prot);
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
selftest_state.host = PKVM_PAGE_OWNED;
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 07/22] KVM: arm64: Add a range to __pkvm_host_donate_guest()
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (5 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 06/22] KVM: arm64: Add a range to __pkvm_host_share/unshare_hyp() Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 08/22] KVM: arm64: Add a range to hyp_poison_page() Vincent Donnefort
` (14 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
In preparation for supporting stage-2 huge mappings for protected VMs,
add a nr_pages argument to the __pkvm_host_donate_guest() hypercall.
This range supports only two values: 1 or PMD_SIZE / PAGE_SIZE (that is
512 on a 4K-pages system).
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 2 +-
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 3 +-
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 48 +++++++++++--------
arch/arm64/kvm/pkvm.c | 2 +-
4 files changed, 32 insertions(+), 23 deletions(-)
diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
index 1f70162fa625..c13f258fffb7 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
@@ -39,7 +39,7 @@ int __pkvm_host_donate_hyp(u64 pfn, u64 nr_pages);
int __pkvm_hyp_donate_host(u64 pfn, u64 nr_pages);
int __pkvm_host_share_ffa(u64 pfn, u64 nr_pages);
int __pkvm_host_unshare_ffa(u64 pfn, u64 nr_pages);
-int __pkvm_host_donate_guest(u64 pfn, u64 gfn, struct pkvm_hyp_vcpu *vcpu);
+int __pkvm_host_donate_guest(u64 pfn, u64 gfn, u64 nr_pages, struct pkvm_hyp_vcpu *vcpu);
int __pkvm_vcpu_in_poison_fault(struct pkvm_hyp_vcpu *hyp_vcpu);
int __pkvm_host_force_reclaim_page_guest(phys_addr_t phys);
int __pkvm_host_reclaim_page_guest(u64 gfn, struct pkvm_hyp_vm *vm);
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index bb23006e60c4..84ff87d4e18d 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -415,6 +415,7 @@ static void handle___pkvm_host_donate_guest(struct kvm_cpu_context *host_ctxt)
{
DECLARE_REG(u64, pfn, host_ctxt, 1);
DECLARE_REG(u64, gfn, host_ctxt, 2);
+ DECLARE_REG(u64, nr_pages, host_ctxt, 3);
struct pkvm_hyp_vcpu *hyp_vcpu;
int ret = -EINVAL;
@@ -426,7 +427,7 @@ static void handle___pkvm_host_donate_guest(struct kvm_cpu_context *host_ctxt)
if (ret)
goto out;
- ret = __pkvm_host_donate_guest(pfn, gfn, hyp_vcpu);
+ ret = __pkvm_host_donate_guest(pfn, gfn, nr_pages, hyp_vcpu);
out:
cpu_reg(host_ctxt, 1) = ret;
}
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index fb01f6129217..58ea0fa62226 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -1429,22 +1429,30 @@ static int __guest_check_pgtable_memcache(struct pkvm_hyp_vcpu *vcpu)
return 0;
}
-int __pkvm_host_donate_guest(u64 pfn, u64 gfn, struct pkvm_hyp_vcpu *vcpu)
+int __pkvm_host_donate_guest(u64 pfn, u64 gfn, u64 nr_pages, struct pkvm_hyp_vcpu *vcpu)
{
struct pkvm_hyp_vm *vm = pkvm_hyp_vcpu_to_hyp_vm(vcpu);
u64 phys = hyp_pfn_to_phys(pfn);
u64 ipa = hyp_pfn_to_phys(gfn);
+ u64 size;
u64 meta;
int ret;
+ if (!pfn_range_is_valid(pfn, nr_pages))
+ return -EINVAL;
+
+ ret = __guest_check_transition_size(phys, ipa, nr_pages, &size);
+ if (ret)
+ return ret;
+
host_lock_component();
guest_lock_component(vm);
- ret = __host_check_page_state_range(phys, PAGE_SIZE, PKVM_PAGE_OWNED);
+ ret = __host_check_page_state_range(phys, size, PKVM_PAGE_OWNED);
if (ret)
goto unlock;
- ret = __guest_check_page_state_range(vm, ipa, PAGE_SIZE, PKVM_NOPAGE);
+ ret = __guest_check_page_state_range(vm, ipa, size, PKVM_NOPAGE);
if (ret)
goto unlock;
@@ -1453,9 +1461,9 @@ int __pkvm_host_donate_guest(u64 pfn, u64 gfn, struct pkvm_hyp_vcpu *vcpu)
goto unlock;
meta = host_stage2_encode_gfn_meta(vm, gfn);
- WARN_ON(host_stage2_set_owner_metadata_locked(phys, PAGE_SIZE,
+ WARN_ON(host_stage2_set_owner_metadata_locked(phys, size,
PKVM_ID_GUEST, meta));
- WARN_ON(kvm_pgtable_stage2_map(&vm->pgt, ipa, PAGE_SIZE, phys,
+ WARN_ON(kvm_pgtable_stage2_map(&vm->pgt, ipa, size, phys,
pkvm_mkstate(KVM_PGTABLE_PROT_RWX, PKVM_PAGE_OWNED),
&vcpu->vcpu.arch.pkvm_memcache, 0));
@@ -1797,7 +1805,7 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, 1, vm);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
selftest_state.host = PKVM_PAGE_OWNED;
selftest_state.hyp = PKVM_NOPAGE;
@@ -1817,7 +1825,7 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, 1, vm);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
assert_transition_res(0, hyp_pin_shared_mem, virt, virt + size);
assert_transition_res(0, hyp_pin_shared_mem, virt, virt + size);
@@ -1830,7 +1838,7 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, 1, vm);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
hyp_unpin_shared_mem(virt, virt + size);
assert_page_state();
@@ -1850,7 +1858,7 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, 1, vm);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
selftest_state.host = PKVM_PAGE_OWNED;
@@ -1867,7 +1875,7 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
selftest_state.guest[1] = PKVM_PAGE_SHARED_BORROWED;
@@ -1883,9 +1891,9 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.host = PKVM_NOPAGE;
selftest_state.guest[0] = PKVM_PAGE_OWNED;
- assert_transition_res(0, __pkvm_host_donate_guest, pfn, gfn, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + 1, vcpu);
+ assert_transition_res(0, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + 1, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + 1, 1, vcpu, prot);
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
@@ -1898,8 +1906,8 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.guest[0] = PKVM_PAGE_SHARED_OWNED;
assert_transition_res(0, __pkvm_guest_share_host, vcpu, gfn);
assert_transition_res(-EPERM, __pkvm_guest_share_host, vcpu, gfn);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + 1, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + 1, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + 1, 1, vcpu, prot);
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
@@ -1912,8 +1920,8 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.guest[0] = PKVM_PAGE_OWNED;
assert_transition_res(0, __pkvm_guest_unshare_host, vcpu, gfn);
assert_transition_res(-EPERM, __pkvm_guest_unshare_host, vcpu, gfn);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + 1, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + 1, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + 1, 1, vcpu, prot);
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
@@ -1925,19 +1933,19 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.host = PKVM_PAGE_OWNED;
selftest_state.guest[0] = PKVM_POISON;
assert_transition_res(0, __pkvm_host_force_reclaim_page_guest, phys);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
assert_transition_res(-EHWPOISON, __pkvm_guest_share_host, vcpu, gfn);
assert_transition_res(-EHWPOISON, __pkvm_guest_unshare_host, vcpu, gfn);
selftest_state.host = PKVM_NOPAGE;
selftest_state.guest[1] = PKVM_PAGE_OWNED;
- assert_transition_res(0, __pkvm_host_donate_guest, pfn, gfn + 1, vcpu);
+ assert_transition_res(0, __pkvm_host_donate_guest, pfn, gfn + 1, 1, vcpu);
selftest_state.host = PKVM_PAGE_OWNED;
selftest_state.guest[1] = PKVM_NOPAGE;
assert_transition_res(0, __pkvm_host_reclaim_page_guest, gfn + 1, vm);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
selftest_state.host = PKVM_NOPAGE;
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index b88c1fcaadbd..99100204b093 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -462,7 +462,7 @@ int pkvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size,
return ret ? -EFAULT : -EAGAIN;
}
- ret = kvm_call_hyp_nvhe(__pkvm_host_donate_guest, pfn, gfn);
+ ret = kvm_call_hyp_nvhe(__pkvm_host_donate_guest, pfn, gfn, 1);
} else {
if (WARN_ON_ONCE(size != PAGE_SIZE && size != PMD_SIZE))
return -EINVAL;
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 08/22] KVM: arm64: Add a range to hyp_poison_page()
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (6 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 07/22] KVM: arm64: Add a range to __pkvm_host_donate_guest() Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 09/22] KVM: arm64: Add a range to __pkvm_host_reclaim_guest() Vincent Donnefort
` (13 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
In preparation for supporting stage-2 huge mappings for protected VMs,
add a nr_pages argument to the memory poisoning mechanism. Use the
existing __apply_guest_page() helper which speeds up the process by
using a PMD_SIZE fixmap when possible.
Also, rename the function to hyp_poison_range() to reflect the new
capability.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 58ea0fa62226..642096b2e711 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -1276,11 +1276,9 @@ static int __guest_check_transition_size(u64 phys, u64 ipa, u64 nr_pages, u64 *s
return 0;
}
-static void hyp_poison_page(phys_addr_t phys)
+static void __hyp_poison_page(void *addr, size_t size)
{
- void *addr = hyp_fixmap_map(phys);
-
- memset(addr, 0, PAGE_SIZE);
+ memset(addr, 0, size);
/*
* Prefer kvm_flush_dcache_to_poc() over __clean_dcache_guest_page()
* here as the latter may elide the CMO under the assumption that FWB
@@ -1288,8 +1286,12 @@ static void hyp_poison_page(phys_addr_t phys)
* host stage-2 and would otherwise lead to a malicious host potentially
* being able to read the contents of newly reclaimed guest pages.
*/
- kvm_flush_dcache_to_poc(addr, PAGE_SIZE);
- hyp_fixmap_unmap();
+ kvm_flush_dcache_to_poc(addr, size);
+}
+
+static void hyp_poison_range(phys_addr_t phys, u64 size)
+{
+ __apply_guest_page(__hyp_va(phys), size, __hyp_poison_page);
}
static int host_stage2_get_guest_info(phys_addr_t phys, struct pkvm_hyp_vm **vm,
@@ -1361,7 +1363,7 @@ int __pkvm_host_force_reclaim_page_guest(phys_addr_t phys)
if (ret)
goto unlock_guest;
- hyp_poison_page(phys);
+ hyp_poison_range(phys, PAGE_SIZE);
WARN_ON(host_stage2_set_owner_locked(phys, PAGE_SIZE, PKVM_ID_HOST));
unlock_guest:
guest_unlock_component(vm);
@@ -1389,7 +1391,7 @@ int __pkvm_host_reclaim_page_guest(u64 gfn, struct pkvm_hyp_vm *vm)
switch (guest_get_page_state(pte, ipa)) {
case PKVM_PAGE_OWNED:
WARN_ON(__host_check_page_state_range(phys, PAGE_SIZE, PKVM_NOPAGE));
- hyp_poison_page(phys);
+ hyp_poison_range(phys, PAGE_SIZE);
break;
case PKVM_PAGE_SHARED_OWNED:
WARN_ON(__host_check_page_state_range(phys, PAGE_SIZE, PKVM_PAGE_SHARED_BORROWED));
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 09/22] KVM: arm64: Add a range to __pkvm_host_reclaim_guest()
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (7 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 08/22] KVM: arm64: Add a range to hyp_poison_page() Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 10/22] KVM: arm64: Add a range to __pkvm_guest_share_host() Vincent Donnefort
` (12 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
In preparation for supporting stage-2 huge mappings for protected VMs,
add a nr_pages argument to the __pkvm_host_reclaim_guest() hypercall.
This range supports only two values: 1 or PMD_SIZE / PAGE_SIZE (that is
512 on a 4K-pages system).
Also, rename both the mem_protect function and the HVC to
__pkvm_host_reclaim_guest() as it can now operate on a range.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/include/asm/kvm_asm.h | 2 +-
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 2 +-
arch/arm64/kvm/hyp/include/nvhe/pkvm.h | 2 +-
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 7 ++-
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 58 +++++++++++--------
arch/arm64/kvm/hyp/nvhe/pkvm.c | 4 +-
arch/arm64/kvm/pkvm.c | 5 +-
7 files changed, 47 insertions(+), 33 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index 7fdec5d98201..95dd62e5601b 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -109,7 +109,7 @@ enum __kvm_host_smccc_func {
__KVM_HOST_SMCCC_FUNC___pkvm_init_vcpu,
__KVM_HOST_SMCCC_FUNC___pkvm_vcpu_in_poison_fault,
__KVM_HOST_SMCCC_FUNC___pkvm_force_reclaim_guest_page,
- __KVM_HOST_SMCCC_FUNC___pkvm_reclaim_dying_guest_page,
+ __KVM_HOST_SMCCC_FUNC___pkvm_reclaim_dying_guest,
__KVM_HOST_SMCCC_FUNC___pkvm_start_teardown_vm,
__KVM_HOST_SMCCC_FUNC___pkvm_finalize_teardown_vm,
__KVM_HOST_SMCCC_FUNC___pkvm_vcpu_load,
diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
index c13f258fffb7..646d2335e5c1 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
@@ -42,7 +42,7 @@ int __pkvm_host_unshare_ffa(u64 pfn, u64 nr_pages);
int __pkvm_host_donate_guest(u64 pfn, u64 gfn, u64 nr_pages, struct pkvm_hyp_vcpu *vcpu);
int __pkvm_vcpu_in_poison_fault(struct pkvm_hyp_vcpu *hyp_vcpu);
int __pkvm_host_force_reclaim_page_guest(phys_addr_t phys);
-int __pkvm_host_reclaim_page_guest(u64 gfn, struct pkvm_hyp_vm *vm);
+int __pkvm_host_reclaim_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vm *vm);
int __pkvm_host_share_guest(u64 pfn, u64 gfn, u64 nr_pages, struct pkvm_hyp_vcpu *vcpu,
enum kvm_pgtable_prot prot);
int __pkvm_host_unshare_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vm *hyp_vm);
diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
index f04cea5ff389..4183614055bc 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h
@@ -74,7 +74,7 @@ int __pkvm_init_vm(struct kvm *host_kvm, unsigned long vm_hva,
int __pkvm_init_vcpu(pkvm_handle_t handle, struct kvm_vcpu *host_vcpu,
unsigned long vcpu_hva);
-int __pkvm_reclaim_dying_guest_page(pkvm_handle_t handle, u64 gfn);
+int __pkvm_reclaim_dying_guest(pkvm_handle_t handle, u64 gfn, u64 nr_pages);
int __pkvm_start_teardown_vm(pkvm_handle_t handle);
int __pkvm_finalize_teardown_vm(pkvm_handle_t handle);
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 84ff87d4e18d..ce816f01ab4f 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -814,12 +814,13 @@ static void handle___pkvm_force_reclaim_guest_page(struct kvm_cpu_context *host_
cpu_reg(host_ctxt, 1) = __pkvm_host_force_reclaim_page_guest(phys);
}
-static void handle___pkvm_reclaim_dying_guest_page(struct kvm_cpu_context *host_ctxt)
+static void handle___pkvm_reclaim_dying_guest(struct kvm_cpu_context *host_ctxt)
{
DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
DECLARE_REG(u64, gfn, host_ctxt, 2);
+ DECLARE_REG(u64, nr_pages, host_ctxt, 3);
- cpu_reg(host_ctxt, 1) = __pkvm_reclaim_dying_guest_page(handle, gfn);
+ cpu_reg(host_ctxt, 1) = __pkvm_reclaim_dying_guest(handle, gfn, nr_pages);
}
static void handle___pkvm_start_teardown_vm(struct kvm_cpu_context *host_ctxt)
@@ -961,7 +962,7 @@ static const hcall_t host_hcall[] = {
HANDLE_FUNC(__pkvm_init_vcpu),
HANDLE_FUNC(__pkvm_vcpu_in_poison_fault),
HANDLE_FUNC(__pkvm_force_reclaim_guest_page),
- HANDLE_FUNC(__pkvm_reclaim_dying_guest_page),
+ HANDLE_FUNC(__pkvm_reclaim_dying_guest),
HANDLE_FUNC(__pkvm_start_teardown_vm),
HANDLE_FUNC(__pkvm_finalize_teardown_vm),
HANDLE_FUNC(__pkvm_vcpu_load),
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 642096b2e711..25a86b318dfc 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -898,7 +898,8 @@ static int __guest_check_page_state_range(struct pkvm_hyp_vm *vm, u64 addr,
return check_page_state_range(&vm->pgt, addr, size, &d);
}
-static int get_valid_guest_pte(struct pkvm_hyp_vm *vm, u64 ipa, kvm_pte_t *ptep, u64 *physp)
+static int __get_valid_guest_pte(struct pkvm_hyp_vm *vm, u64 ipa,
+ kvm_pte_t *ptep, u64 *physp, s8 *levelp)
{
kvm_pte_t pte;
u64 phys;
@@ -912,20 +913,32 @@ static int get_valid_guest_pte(struct pkvm_hyp_vm *vm, u64 ipa, kvm_pte_t *ptep,
return -EHWPOISON;
if (!kvm_pte_valid(pte))
return -ENOENT;
- if (level != KVM_PGTABLE_LAST_LEVEL)
- return -E2BIG;
phys = kvm_pte_to_phys(pte);
- ret = check_range_allowed_memory(phys, phys + PAGE_SIZE);
+ ret = check_range_allowed_memory(phys, phys + kvm_granule_size(level));
if (WARN_ON(ret))
return ret;
*ptep = pte;
*physp = phys;
+ *levelp = level;
return 0;
}
+static int get_valid_guest_pte(struct pkvm_hyp_vm *vm, u64 ipa, u64 size,
+ kvm_pte_t *ptep, u64 *physp)
+{
+ s8 level;
+ int ret;
+
+ ret = __get_valid_guest_pte(vm, ipa, ptep, physp, &level);
+ if (ret)
+ return ret;
+
+ return kvm_granule_size(level) == size ? 0 : -E2BIG;
+}
+
int __pkvm_vcpu_in_poison_fault(struct pkvm_hyp_vcpu *hyp_vcpu)
{
struct pkvm_hyp_vm *vm = pkvm_hyp_vcpu_to_hyp_vm(hyp_vcpu);
@@ -1008,7 +1021,7 @@ int __pkvm_guest_share_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn)
host_lock_component();
guest_lock_component(vm);
- ret = get_valid_guest_pte(vm, ipa, &pte, &phys);
+ ret = get_valid_guest_pte(vm, ipa, PAGE_SIZE, &pte, &phys);
if (ret)
goto unlock;
@@ -1040,7 +1053,7 @@ int __pkvm_guest_unshare_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn)
host_lock_component();
guest_lock_component(vm);
- ret = get_valid_guest_pte(vm, ipa, &pte, &phys);
+ ret = get_valid_guest_pte(vm, ipa, PAGE_SIZE, &pte, &phys);
if (ret)
goto unlock;
@@ -1346,7 +1359,7 @@ int __pkvm_host_force_reclaim_page_guest(phys_addr_t phys)
ipa = hyp_pfn_to_phys(gfn);
guest_lock_component(vm);
- ret = get_valid_guest_pte(vm, ipa, &pte, &pa);
+ ret = get_valid_guest_pte(vm, ipa, PAGE_SIZE, &pte, &pa);
if (ret)
goto unlock_guest;
@@ -1374,35 +1387,40 @@ int __pkvm_host_force_reclaim_page_guest(phys_addr_t phys)
return ret;
}
-int __pkvm_host_reclaim_page_guest(u64 gfn, struct pkvm_hyp_vm *vm)
+int __pkvm_host_reclaim_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vm *vm)
{
u64 ipa = hyp_pfn_to_phys(gfn);
kvm_pte_t pte;
u64 phys;
+ u64 size;
int ret;
+ ret = __guest_check_transition_size(0, ipa, nr_pages, &size);
+ if (ret)
+ return ret;
+
host_lock_component();
guest_lock_component(vm);
- ret = get_valid_guest_pte(vm, ipa, &pte, &phys);
+ ret = get_valid_guest_pte(vm, ipa, size, &pte, &phys);
if (ret)
goto unlock;
switch (guest_get_page_state(pte, ipa)) {
case PKVM_PAGE_OWNED:
- WARN_ON(__host_check_page_state_range(phys, PAGE_SIZE, PKVM_NOPAGE));
- hyp_poison_range(phys, PAGE_SIZE);
+ WARN_ON(__host_check_page_state_range(phys, size, PKVM_NOPAGE));
+ hyp_poison_range(phys, size);
break;
case PKVM_PAGE_SHARED_OWNED:
- WARN_ON(__host_check_page_state_range(phys, PAGE_SIZE, PKVM_PAGE_SHARED_BORROWED));
+ WARN_ON(__host_check_page_state_range(phys, size, PKVM_PAGE_SHARED_BORROWED));
break;
default:
ret = -EPERM;
goto unlock;
}
- WARN_ON(kvm_pgtable_stage2_unmap(&vm->pgt, ipa, PAGE_SIZE));
- WARN_ON(host_stage2_set_owner_locked(phys, PAGE_SIZE, PKVM_ID_HOST));
+ WARN_ON(kvm_pgtable_stage2_unmap(&vm->pgt, ipa, size));
+ WARN_ON(host_stage2_set_owner_locked(phys, size, PKVM_ID_HOST));
unlock:
guest_unlock_component(vm);
@@ -1555,11 +1573,10 @@ static int __check_host_shared_guest(struct pkvm_hyp_vm *vm, u64 *__phys, u64 ip
s8 level;
int ret;
- ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level);
+ ret = __get_valid_guest_pte(vm, ipa, &pte, &phys, &level);
if (ret)
return ret;
- if (!kvm_pte_valid(pte))
- return -ENOENT;
+
if (size && kvm_granule_size(level) != size)
return -E2BIG;
@@ -1570,11 +1587,6 @@ static int __check_host_shared_guest(struct pkvm_hyp_vm *vm, u64 *__phys, u64 ip
if (state != PKVM_PAGE_SHARED_BORROWED)
return -EPERM;
- phys = kvm_pte_to_phys(pte);
- ret = check_range_allowed_memory(phys, phys + size);
- if (WARN_ON(ret))
- return ret;
-
for_each_hyp_page(page, phys, size) {
if (get_host_state(page) != PKVM_PAGE_SHARED_OWNED)
return -EPERM;
@@ -1946,7 +1958,7 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.host = PKVM_PAGE_OWNED;
selftest_state.guest[1] = PKVM_NOPAGE;
- assert_transition_res(0, __pkvm_host_reclaim_page_guest, gfn + 1, vm);
+ assert_transition_res(0, __pkvm_host_reclaim_guest, gfn + 1, 1, vm);
assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index ad9afe2cd1b9..6590c4de7fd2 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -965,7 +965,7 @@ teardown_donated_memory(struct kvm_hyp_memcache *mc, void *addr, size_t size)
unmap_donated_memory_noclear(addr, size);
}
-int __pkvm_reclaim_dying_guest_page(pkvm_handle_t handle, u64 gfn)
+int __pkvm_reclaim_dying_guest(pkvm_handle_t handle, u64 gfn, u64 nr_pages)
{
struct pkvm_hyp_vm *hyp_vm = get_pkvm_hyp_vm(handle);
int ret = -EINVAL;
@@ -974,7 +974,7 @@ int __pkvm_reclaim_dying_guest_page(pkvm_handle_t handle, u64 gfn)
return ret;
if (hyp_vm->kvm.arch.pkvm.is_dying)
- ret = __pkvm_host_reclaim_page_guest(gfn, hyp_vm);
+ ret = __pkvm_host_reclaim_guest(gfn, nr_pages, hyp_vm);
put_pkvm_hyp_vm(hyp_vm);
return ret;
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index 99100204b093..5b4256b19507 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -369,8 +369,9 @@ static int __pkvm_pgtable_stage2_reclaim(struct kvm_pgtable *pgt, u64 start, u64
for_each_mapping_in_range_safe(pgt, start, end, mapping) {
struct page *page;
- ret = kvm_call_hyp_nvhe(__pkvm_reclaim_dying_guest_page,
- handle, mapping->gfn);
+ ret = kvm_call_hyp_nvhe(__pkvm_reclaim_dying_guest,
+ handle, mapping->gfn,
+ (u64)mapping->nr_pages);
if (WARN_ON(ret))
continue;
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 10/22] KVM: arm64: Add a range to __pkvm_guest_share_host()
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (8 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 09/22] KVM: arm64: Add a range to __pkvm_host_reclaim_guest() Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 11/22] KVM: arm64: Add a range to __pkvm_guest_unshare_host() Vincent Donnefort
` (11 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
In preparation for supporting stage-2 huge mappings for protected VMs,
add a nr_pages argument to the __pkvm_guest_share_host() hypercall. This
range supports only two values: 1 or PMD_SIZE / PAGE_SIZE (that is 512
on a 4K-pages system).
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 2 +-
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 24 ++++++++++++-------
arch/arm64/kvm/hyp/nvhe/pkvm.c | 2 +-
3 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
index 646d2335e5c1..b00a14c5ed9d 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
@@ -32,7 +32,7 @@ enum pkvm_component_id {
int __pkvm_prot_finalize(void);
int __pkvm_host_share_hyp(u64 pfn, u64 nr_pages);
-int __pkvm_guest_share_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn);
+int __pkvm_guest_share_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn, u64 nr_pages);
int __pkvm_guest_unshare_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn);
int __pkvm_host_unshare_hyp(u64 pfn, u64 nr_pages);
int __pkvm_host_donate_hyp(u64 pfn, u64 nr_pages);
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 25a86b318dfc..619dde4f576b 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -1011,31 +1011,37 @@ int __pkvm_host_share_hyp(u64 pfn, u64 nr_pages)
return ret;
}
-int __pkvm_guest_share_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn)
+static int __guest_check_transition_size(u64 phys, u64 ipa, u64 nr_pages, u64 *size);
+
+int __pkvm_guest_share_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn, u64 nr_pages)
{
struct pkvm_hyp_vm *vm = pkvm_hyp_vcpu_to_hyp_vm(vcpu);
- u64 phys, ipa = hyp_pfn_to_phys(gfn);
+ u64 size, phys, ipa = hyp_pfn_to_phys(gfn);
kvm_pte_t pte;
int ret;
+ ret = __guest_check_transition_size(0, ipa, nr_pages, &size);
+ if (ret)
+ return ret;
+
host_lock_component();
guest_lock_component(vm);
- ret = get_valid_guest_pte(vm, ipa, PAGE_SIZE, &pte, &phys);
+ ret = get_valid_guest_pte(vm, ipa, size, &pte, &phys);
if (ret)
goto unlock;
ret = -EPERM;
if (pkvm_getstate(kvm_pgtable_stage2_pte_prot(pte)) != PKVM_PAGE_OWNED)
goto unlock;
- if (__host_check_page_state_range(phys, PAGE_SIZE, PKVM_NOPAGE))
+ if (__host_check_page_state_range(phys, size, PKVM_NOPAGE))
goto unlock;
ret = 0;
- WARN_ON(kvm_pgtable_stage2_map(&vm->pgt, ipa, PAGE_SIZE, phys,
+ WARN_ON(kvm_pgtable_stage2_map(&vm->pgt, ipa, size, phys,
pkvm_mkstate(KVM_PGTABLE_PROT_RWX, PKVM_PAGE_SHARED_OWNED),
&vcpu->vcpu.arch.pkvm_memcache, 0));
- WARN_ON(__host_set_page_state_range(phys, PAGE_SIZE, PKVM_PAGE_SHARED_BORROWED));
+ WARN_ON(__host_set_page_state_range(phys, size, PKVM_PAGE_SHARED_BORROWED));
unlock:
guest_unlock_component(vm);
host_unlock_component();
@@ -1918,8 +1924,8 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.host = PKVM_PAGE_SHARED_BORROWED;
selftest_state.guest[0] = PKVM_PAGE_SHARED_OWNED;
- assert_transition_res(0, __pkvm_guest_share_host, vcpu, gfn);
- assert_transition_res(-EPERM, __pkvm_guest_share_host, vcpu, gfn);
+ assert_transition_res(0, __pkvm_guest_share_host, vcpu, gfn, 1);
+ assert_transition_res(-EPERM, __pkvm_guest_share_host, vcpu, gfn, 1);
assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + 1, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
@@ -1949,7 +1955,7 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(0, __pkvm_host_force_reclaim_page_guest, phys);
assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
- assert_transition_res(-EHWPOISON, __pkvm_guest_share_host, vcpu, gfn);
+ assert_transition_res(-EHWPOISON, __pkvm_guest_share_host, vcpu, gfn, 1);
assert_transition_res(-EHWPOISON, __pkvm_guest_unshare_host, vcpu, gfn);
selftest_state.host = PKVM_NOPAGE;
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index 6590c4de7fd2..8f5c87ece728 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -1104,7 +1104,7 @@ static bool pkvm_memshare_call(u64 *ret, struct kvm_vcpu *vcpu, u64 *exit_code)
goto out_guest;
hyp_vcpu = container_of(vcpu, struct pkvm_hyp_vcpu, vcpu);
- switch (__pkvm_guest_share_host(hyp_vcpu, hyp_phys_to_pfn(ipa))) {
+ switch (__pkvm_guest_share_host(hyp_vcpu, hyp_phys_to_pfn(ipa), 1)) {
case 0:
ret[0] = SMCCC_RET_SUCCESS;
goto out_guest;
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 11/22] KVM: arm64: Add a range to __pkvm_guest_unshare_host()
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (9 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 10/22] KVM: arm64: Add a range to __pkvm_guest_share_host() Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 12/22] KVM: arm64: Handle huge mappings in __pkvm_host_force_reclaim_page_guest() Vincent Donnefort
` (10 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
In preparation for supporting stage-2 huge mappings for protected VMs,
add a nr_pages argument to the __pkvm_guest_unshare_host() hypercall.
This range supports only two values: 1 or PMD_SIZE / PAGE_SIZE (that is
512 on a 4K-pages system).
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 2 +-
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 22 +++++++++++--------
arch/arm64/kvm/hyp/nvhe/pkvm.c | 2 +-
3 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
index b00a14c5ed9d..33f2d1824614 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
@@ -33,7 +33,7 @@ enum pkvm_component_id {
int __pkvm_prot_finalize(void);
int __pkvm_host_share_hyp(u64 pfn, u64 nr_pages);
int __pkvm_guest_share_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn, u64 nr_pages);
-int __pkvm_guest_unshare_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn);
+int __pkvm_guest_unshare_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn, u64 nr_pages);
int __pkvm_host_unshare_hyp(u64 pfn, u64 nr_pages);
int __pkvm_host_donate_hyp(u64 pfn, u64 nr_pages);
int __pkvm_hyp_donate_host(u64 pfn, u64 nr_pages);
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 619dde4f576b..7e6882ce51f6 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -1049,31 +1049,35 @@ int __pkvm_guest_share_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn, u64 nr_pages)
return ret;
}
-int __pkvm_guest_unshare_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn)
+int __pkvm_guest_unshare_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn, u64 nr_pages)
{
struct pkvm_hyp_vm *vm = pkvm_hyp_vcpu_to_hyp_vm(vcpu);
- u64 meta, phys, ipa = hyp_pfn_to_phys(gfn);
+ u64 size, meta, phys, ipa = hyp_pfn_to_phys(gfn);
kvm_pte_t pte;
int ret;
+ ret = __guest_check_transition_size(0, ipa, nr_pages, &size);
+ if (ret)
+ return ret;
+
host_lock_component();
guest_lock_component(vm);
- ret = get_valid_guest_pte(vm, ipa, PAGE_SIZE, &pte, &phys);
+ ret = get_valid_guest_pte(vm, ipa, size, &pte, &phys);
if (ret)
goto unlock;
ret = -EPERM;
if (pkvm_getstate(kvm_pgtable_stage2_pte_prot(pte)) != PKVM_PAGE_SHARED_OWNED)
goto unlock;
- if (__host_check_page_state_range(phys, PAGE_SIZE, PKVM_PAGE_SHARED_BORROWED))
+ if (__host_check_page_state_range(phys, size, PKVM_PAGE_SHARED_BORROWED))
goto unlock;
ret = 0;
meta = host_stage2_encode_gfn_meta(vm, gfn);
- WARN_ON(host_stage2_set_owner_metadata_locked(phys, PAGE_SIZE,
+ WARN_ON(host_stage2_set_owner_metadata_locked(phys, size,
PKVM_ID_GUEST, meta));
- WARN_ON(kvm_pgtable_stage2_map(&vm->pgt, ipa, PAGE_SIZE, phys,
+ WARN_ON(kvm_pgtable_stage2_map(&vm->pgt, ipa, size, phys,
pkvm_mkstate(KVM_PGTABLE_PROT_RWX, PKVM_PAGE_OWNED),
&vcpu->vcpu.arch.pkvm_memcache, 0));
unlock:
@@ -1938,8 +1942,8 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.host = PKVM_NOPAGE;
selftest_state.guest[0] = PKVM_PAGE_OWNED;
- assert_transition_res(0, __pkvm_guest_unshare_host, vcpu, gfn);
- assert_transition_res(-EPERM, __pkvm_guest_unshare_host, vcpu, gfn);
+ assert_transition_res(0, __pkvm_guest_unshare_host, vcpu, gfn, 1);
+ assert_transition_res(-EPERM, __pkvm_guest_unshare_host, vcpu, gfn, 1);
assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + 1, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
@@ -1956,7 +1960,7 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
assert_transition_res(-EHWPOISON, __pkvm_guest_share_host, vcpu, gfn, 1);
- assert_transition_res(-EHWPOISON, __pkvm_guest_unshare_host, vcpu, gfn);
+ assert_transition_res(-EHWPOISON, __pkvm_guest_unshare_host, vcpu, gfn, 1);
selftest_state.host = PKVM_NOPAGE;
selftest_state.guest[1] = PKVM_PAGE_OWNED;
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index 8f5c87ece728..2b0b0bc3064c 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -1132,7 +1132,7 @@ static void pkvm_memunshare_call(u64 *ret, struct kvm_vcpu *vcpu)
return;
hyp_vcpu = container_of(vcpu, struct pkvm_hyp_vcpu, vcpu);
- if (!__pkvm_guest_unshare_host(hyp_vcpu, hyp_phys_to_pfn(ipa)))
+ if (!__pkvm_guest_unshare_host(hyp_vcpu, hyp_phys_to_pfn(ipa), 1))
ret[0] = SMCCC_RET_SUCCESS;
}
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 12/22] KVM: arm64: Handle huge mappings in __pkvm_host_force_reclaim_page_guest()
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (10 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 11/22] KVM: arm64: Add a range to __pkvm_guest_unshare_host() Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 13/22] KVM: arm64: Handle huge mappings in __pkvm_vcpu_in_poison_fault() Vincent Donnefort
` (9 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
In preparation for supporting stage-2 huge mappings for protected VMs,
allow __pkvm_host_force_reclaim_page_guest() to work with PMD_SIZE
mappings. As this HVC is called from a non-preemptible context, it is
not possible to rely on the host pkvm_mappings tree to get the mapping
size. Instead, use the actual host stage-2 mapping size and compare it
with the guest stage-2.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 33 +++++++++++++++++----------
1 file changed, 21 insertions(+), 12 deletions(-)
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 7e6882ce51f6..674159736495 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -1317,7 +1317,7 @@ static void hyp_poison_range(phys_addr_t phys, u64 size)
__apply_guest_page(__hyp_va(phys), size, __hyp_poison_page);
}
-static int host_stage2_get_guest_info(phys_addr_t phys, struct pkvm_hyp_vm **vm,
+static int host_stage2_get_guest_info(phys_addr_t phys, u64 *size, struct pkvm_hyp_vm **vm,
u64 *gfn)
{
enum pkvm_page_state state;
@@ -1345,34 +1345,43 @@ static int host_stage2_get_guest_info(phys_addr_t phys, struct pkvm_hyp_vm **vm,
if (ret)
return ret;
- if (WARN_ON(level != KVM_PGTABLE_LAST_LEVEL))
- return -EINVAL;
+ /* We only support either PAGE_SIZE or PMD_SIZE */
+ if (level < KVM_PGTABLE_LAST_LEVEL - 1)
+ return -E2BIG;
- return host_stage2_decode_gfn_meta(pte, vm, gfn);
+ ret = host_stage2_decode_gfn_meta(pte, vm, gfn);
+ if (ret)
+ return ret;
+
+ *size = kvm_granule_size(level);
+
+ return 0;
}
int __pkvm_host_force_reclaim_page_guest(phys_addr_t phys)
{
+ u64 gfn, ipa, pa, size;
struct pkvm_hyp_vm *vm;
- u64 gfn, ipa, pa;
kvm_pte_t pte;
int ret;
- phys &= PAGE_MASK;
-
hyp_spin_lock(&vm_table_lock);
host_lock_component();
- ret = host_stage2_get_guest_info(phys, &vm, &gfn);
+ ret = host_stage2_get_guest_info(phys, &size, &vm, &gfn);
if (ret)
goto unlock_host;
ipa = hyp_pfn_to_phys(gfn);
+
guest_lock_component(vm);
- ret = get_valid_guest_pte(vm, ipa, PAGE_SIZE, &pte, &pa);
+ ret = get_valid_guest_pte(vm, ipa, size, &pte, &pa);
if (ret)
goto unlock_guest;
+ phys = ALIGN_DOWN(phys, size);
+ pa = ALIGN_DOWN(pa, size);
+
WARN_ON(pa != phys);
if (guest_get_page_state(pte, ipa) != PKVM_PAGE_OWNED) {
ret = -EPERM;
@@ -1380,14 +1389,14 @@ int __pkvm_host_force_reclaim_page_guest(phys_addr_t phys)
}
/* We really shouldn't be allocating, so don't pass a memcache */
- ret = kvm_pgtable_stage2_annotate(&vm->pgt, ipa, PAGE_SIZE, NULL,
+ ret = kvm_pgtable_stage2_annotate(&vm->pgt, ipa, size, NULL,
KVM_GUEST_INVALID_PTE_TYPE_POISONED,
0);
if (ret)
goto unlock_guest;
- hyp_poison_range(phys, PAGE_SIZE);
- WARN_ON(host_stage2_set_owner_locked(phys, PAGE_SIZE, PKVM_ID_HOST));
+ hyp_poison_range(phys, size);
+ WARN_ON(host_stage2_set_owner_locked(phys, size, PKVM_ID_HOST));
unlock_guest:
guest_unlock_component(vm);
unlock_host:
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 13/22] KVM: arm64: Handle huge mappings in __pkvm_vcpu_in_poison_fault()
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (11 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 12/22] KVM: arm64: Handle huge mappings in __pkvm_host_force_reclaim_page_guest() Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 14/22] KVM: arm64: Add a range to pKVM ownership selftest Vincent Donnefort
` (8 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
In preparation for supporting stage-2 huge mappings for protected VMs,
allow __pkvm_vcpu_in_poison_fault() to work with PMD_SIZE mappings.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 674159736495..cfcdf187cbc3 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -971,7 +971,7 @@ int __pkvm_vcpu_in_poison_fault(struct pkvm_hyp_vcpu *hyp_vcpu)
if (ret)
goto unlock;
- if (level != KVM_PGTABLE_LAST_LEVEL) {
+ if (level < KVM_PGTABLE_LAST_LEVEL - 1) {
ret = -EINVAL;
goto unlock;
}
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 14/22] KVM: arm64: Add a range to pKVM ownership selftest
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (12 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 13/22] KVM: arm64: Handle huge mappings in __pkvm_vcpu_in_poison_fault() Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 15/22] KVM: arm64: Warn on pKVM guest stage-2 block collapse Vincent Donnefort
` (7 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
Extend pkvm_ownership_selftest() with a nr_pages argument so that state
transitions can be tested with both page-granular (1) and block-granular
(PMD_SIZE / PAGE_SIZE) ranges.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 9 +-
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 8 +-
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 214 ++++++++++--------
arch/arm64/kvm/pkvm.c | 37 +--
4 files changed, 150 insertions(+), 118 deletions(-)
diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
index 33f2d1824614..7904a95ab8ac 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
@@ -74,9 +74,14 @@ static __always_inline void __load_host_stage2(void)
}
#ifdef CONFIG_NVHE_EL2_DEBUG
-int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr_pages);
+int __pkvm_ownership_selftest(u64 pfn, u64 nr_pages, u64 pgtable_pfn,
+ unsigned long pgtable_nr_pages);
#else
static inline int
-__pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr_pages) { return 0; }
+__pkvm_ownership_selftest(u64 pfn, u64 nr_pages, u64 pgtable_pfn,
+ unsigned long pgtable_nr_pages)
+{
+ return 0;
+}
#endif
#endif /* __KVM_NVHE_MEM_PROTECT__ */
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index ce816f01ab4f..7ef7da69f48b 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -755,10 +755,12 @@ static void handle___pkvm_create_private_mapping(struct kvm_cpu_context *host_ct
static void handle___pkvm_ownership_selftest(struct kvm_cpu_context *host_ctxt)
{
DECLARE_REG(u64, pfn, host_ctxt, 1);
- DECLARE_REG(u64, pgtable_pfn, host_ctxt, 2);
- DECLARE_REG(unsigned long, pgtable_nr_pages, host_ctxt, 3);
+ DECLARE_REG(u64, nr_pages, host_ctxt, 2);
+ DECLARE_REG(u64, pgtable_pfn, host_ctxt, 3);
+ DECLARE_REG(unsigned long, pgtable_nr_pages, host_ctxt, 4);
- cpu_reg(host_ctxt, 1) = __pkvm_ownership_selftest(pfn, pgtable_pfn, pgtable_nr_pages);
+ cpu_reg(host_ctxt, 1) = __pkvm_ownership_selftest(pfn, nr_pages, pgtable_pfn,
+ pgtable_nr_pages);
}
static void handle___pkvm_prot_finalize(struct kvm_cpu_context *host_ctxt)
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index cfcdf187cbc3..2893183c2056 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -1764,14 +1764,19 @@ static u64 selftest_ipa(void)
return BIT(selftest_vcpu->vcpu.arch.hw_mmu->pgt->ia_bits - 1);
}
+static unsigned long selftest_nr_pages;
+
static void assert_page_state(void)
{
void *virt = hyp_page_to_virt(selftest_page);
- u64 size = PAGE_SIZE << selftest_page->order;
struct pkvm_hyp_vcpu *vcpu = selftest_vcpu;
+ u64 size = selftest_nr_pages * PAGE_SIZE;
u64 phys = hyp_virt_to_phys(virt);
- u64 ipa[2] = { selftest_ipa(), selftest_ipa() + PAGE_SIZE };
struct pkvm_hyp_vm *vm;
+ u64 ipa[2] = {
+ selftest_ipa(),
+ selftest_ipa() + size
+ };
vm = pkvm_hyp_vcpu_to_hyp_vm(vcpu);
@@ -1795,7 +1800,8 @@ static void assert_page_state(void)
assert_page_state(); \
} while (0)
-int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr_pages)
+int __pkvm_ownership_selftest(u64 pfn, u64 nr_pages, u64 pgtable_pfn,
+ unsigned long pgtable_nr_pages)
{
enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_RWX;
struct pkvm_hyp_vcpu *vcpu;
@@ -1808,7 +1814,7 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
if (ret)
return ret;
- ret = __pkvm_host_donate_hyp(pfn, 1);
+ ret = __pkvm_host_donate_hyp(pfn, nr_pages);
if (ret) {
WARN_ON(__pkvm_hyp_donate_host(pgtable_pfn, pgtable_nr_pages));
return ret;
@@ -1818,10 +1824,11 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
pgtable_nr_pages);
vm = pkvm_hyp_vcpu_to_hyp_vm(vcpu);
+ selftest_nr_pages = nr_pages;
phys = hyp_pfn_to_phys(pfn);
virt = hyp_phys_to_virt(phys);
gfn = hyp_phys_to_pfn(selftest_ipa());
- size = PAGE_SIZE;
+ size = nr_pages * PAGE_SIZE;
selftest_page = hyp_virt_to_page(virt);
selftest_page->refcount = 0;
@@ -1830,48 +1837,48 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.hyp = PKVM_PAGE_OWNED;
selftest_state.guest[0] = selftest_state.guest[1] = PKVM_NOPAGE;
assert_page_state();
- assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_unshare_ffa, pfn, 1);
+ assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_ffa, pfn, nr_pages);
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
- assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, 1, vm);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
+ assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, nr_pages, vm);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
selftest_state.host = PKVM_PAGE_OWNED;
selftest_state.hyp = PKVM_NOPAGE;
- assert_transition_res(0, __pkvm_hyp_donate_host, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_unshare_ffa, pfn, 1);
- assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, 1, vm);
+ assert_transition_res(0, __pkvm_hyp_donate_host, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_ffa, pfn, nr_pages);
+ assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, nr_pages, vm);
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
selftest_state.host = PKVM_PAGE_SHARED_OWNED;
selftest_state.hyp = PKVM_PAGE_SHARED_BORROWED;
- assert_transition_res(0, __pkvm_host_share_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
- assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, 1, vm);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
+ assert_transition_res(0, __pkvm_host_share_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
+ assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, nr_pages, vm);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
assert_transition_res(0, hyp_pin_shared_mem, virt, virt + size);
assert_transition_res(0, hyp_pin_shared_mem, virt, virt + size);
hyp_unpin_shared_mem(virt, virt + size);
WARN_ON(hyp_page_count(virt) != 1);
- assert_transition_res(-EBUSY, __pkvm_host_unshare_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
- assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, 1, vm);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
+ assert_transition_res(-EBUSY, __pkvm_host_unshare_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
+ assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, nr_pages, vm);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
hyp_unpin_shared_mem(virt, virt + size);
assert_page_state();
@@ -1879,116 +1886,125 @@ int __pkvm_ownership_selftest(u64 pfn, u64 pgtable_pfn, unsigned long pgtable_nr
selftest_state.host = PKVM_PAGE_OWNED;
selftest_state.hyp = PKVM_NOPAGE;
- assert_transition_res(0, __pkvm_host_unshare_hyp, pfn, 1);
+ assert_transition_res(0, __pkvm_host_unshare_hyp, pfn, nr_pages);
selftest_state.host = PKVM_PAGE_SHARED_OWNED;
selftest_state.hyp = PKVM_NOPAGE;
- assert_transition_res(0, __pkvm_host_share_ffa, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
- assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, 1, vm);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
+ assert_transition_res(0, __pkvm_host_share_ffa, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
+ assert_transition_res(-ENOENT, __pkvm_host_unshare_guest, gfn, nr_pages, vm);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
selftest_state.host = PKVM_PAGE_OWNED;
selftest_state.hyp = PKVM_NOPAGE;
- assert_transition_res(0, __pkvm_host_unshare_ffa, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_unshare_ffa, pfn, 1);
+ assert_transition_res(0, __pkvm_host_unshare_ffa, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_ffa, pfn, nr_pages);
selftest_state.host = PKVM_PAGE_SHARED_OWNED;
selftest_state.guest[0] = PKVM_PAGE_SHARED_BORROWED;
- assert_transition_res(0, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
- assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
+ assert_transition_res(0, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
+ assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
selftest_state.guest[1] = PKVM_PAGE_SHARED_BORROWED;
- assert_transition_res(0, __pkvm_host_share_guest, pfn, gfn + 1, 1, vcpu, prot);
+ assert_transition_res(0, __pkvm_host_share_guest, pfn, gfn + nr_pages, nr_pages,
+ vcpu, prot);
WARN_ON(hyp_virt_to_page(virt)->host_share_guest_count != 2);
selftest_state.guest[0] = PKVM_NOPAGE;
- assert_transition_res(0, __pkvm_host_unshare_guest, gfn, 1, vm);
+ assert_transition_res(0, __pkvm_host_unshare_guest, gfn, nr_pages, vm);
selftest_state.guest[1] = PKVM_NOPAGE;
selftest_state.host = PKVM_PAGE_OWNED;
- assert_transition_res(0, __pkvm_host_unshare_guest, gfn + 1, 1, vm);
+ assert_transition_res(0, __pkvm_host_unshare_guest, gfn + nr_pages, nr_pages, vm);
selftest_state.host = PKVM_NOPAGE;
selftest_state.guest[0] = PKVM_PAGE_OWNED;
- assert_transition_res(0, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + 1, 1, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + 1, 1, vcpu, prot);
- assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
+ assert_transition_res(0, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + nr_pages, nr_pages,
+ vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + nr_pages, nr_pages,
+ vcpu, prot);
+ assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, nr_pages);
selftest_state.host = PKVM_PAGE_SHARED_BORROWED;
selftest_state.guest[0] = PKVM_PAGE_SHARED_OWNED;
- assert_transition_res(0, __pkvm_guest_share_host, vcpu, gfn, 1);
- assert_transition_res(-EPERM, __pkvm_guest_share_host, vcpu, gfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + 1, 1, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + 1, 1, vcpu, prot);
- assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
+ assert_transition_res(0, __pkvm_guest_share_host, vcpu, gfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_guest_share_host, vcpu, gfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + nr_pages, nr_pages,
+ vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + nr_pages, nr_pages,
+ vcpu, prot);
+ assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, nr_pages);
selftest_state.host = PKVM_NOPAGE;
selftest_state.guest[0] = PKVM_PAGE_OWNED;
- assert_transition_res(0, __pkvm_guest_unshare_host, vcpu, gfn, 1);
- assert_transition_res(-EPERM, __pkvm_guest_unshare_host, vcpu, gfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + 1, 1, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + 1, 1, vcpu, prot);
- assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, 1);
- assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
+ assert_transition_res(0, __pkvm_guest_unshare_host, vcpu, gfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_guest_unshare_host, vcpu, gfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn + nr_pages, nr_pages,
+ vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn + nr_pages, nr_pages,
+ vcpu, prot);
+ assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn, nr_pages);
+ assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, nr_pages);
selftest_state.host = PKVM_PAGE_OWNED;
selftest_state.guest[0] = PKVM_POISON;
assert_transition_res(0, __pkvm_host_force_reclaim_page_guest, phys);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
- assert_transition_res(-EHWPOISON, __pkvm_guest_share_host, vcpu, gfn, 1);
- assert_transition_res(-EHWPOISON, __pkvm_guest_unshare_host, vcpu, gfn, 1);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
+ assert_transition_res(-EHWPOISON, __pkvm_guest_share_host, vcpu, gfn, nr_pages);
+ assert_transition_res(-EHWPOISON, __pkvm_guest_unshare_host, vcpu, gfn, nr_pages);
selftest_state.host = PKVM_NOPAGE;
selftest_state.guest[1] = PKVM_PAGE_OWNED;
- assert_transition_res(0, __pkvm_host_donate_guest, pfn, gfn + 1, 1, vcpu);
+ assert_transition_res(0, __pkvm_host_donate_guest, pfn, gfn + nr_pages, nr_pages,
+ vcpu);
selftest_state.host = PKVM_PAGE_OWNED;
selftest_state.guest[1] = PKVM_NOPAGE;
- assert_transition_res(0, __pkvm_host_reclaim_guest, gfn + 1, 1, vm);
- assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, 1, vcpu);
- assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, 1, vcpu, prot);
+ assert_transition_res(0, __pkvm_host_reclaim_guest, gfn + nr_pages, nr_pages,
+ vm);
+ assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
+ assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
selftest_state.host = PKVM_NOPAGE;
selftest_state.hyp = PKVM_PAGE_OWNED;
- assert_transition_res(0, __pkvm_host_donate_hyp, pfn, 1);
+ assert_transition_res(0, __pkvm_host_donate_hyp, pfn, nr_pages);
/* This also gives back the ownership to the host */
teardown_selftest_vm();
- WARN_ON(__pkvm_hyp_donate_host(pfn, 1));
+ WARN_ON(__pkvm_hyp_donate_host(pfn, nr_pages));
return 0;
}
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index 5b4256b19507..ae434f806455 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -257,31 +257,40 @@ static void __init _kvm_host_prot_finalize(void *arg)
}
#ifdef CONFIG_NVHE_EL2_DEBUG
+static void __init __pkvm_ownership_selftest(unsigned long page_order,
+ unsigned long pgtable,
+ unsigned long pgtable_order)
+{
+ unsigned long nr_pages = 1UL << page_order;
+ unsigned long page;
+ int ret = -ENOMEM;
+
+ /* Page whose ownership will be tested */
+ page = __get_free_pages(GFP_KERNEL, page_order);
+ if (page)
+ ret = kvm_call_hyp_nvhe(__pkvm_ownership_selftest,
+ virt_to_pfn((void *)page), nr_pages,
+ virt_to_pfn((void *)pgtable), 1 << pgtable_order);
+ if (ret)
+ kvm_err("%s(order=%ld) failed (%d)\n", __func__, page_order, ret);
+
+ free_pages(page, page_order);
+}
+
void __init pkvm_ownership_selftest(void)
{
unsigned long pgtable_order = 5;
unsigned long pgtable = 0;
- unsigned long page = 0;
- long ret = -ENOMEM;
/* Memory for the VM page-table */
pgtable = __get_free_pages(GFP_KERNEL, pgtable_order);
if (!pgtable)
- goto out;
+ return;
- /* Page whose ownership will be tested */
- page = __get_free_page(GFP_KERNEL);
- if (!page)
- goto out;
-
- ret = kvm_call_hyp_nvhe(__pkvm_ownership_selftest, virt_to_pfn((void *)page),
- virt_to_pfn((void *)pgtable), 1 << pgtable_order);
-out:
- if (ret)
- kvm_err("Failed to run %s (%ld)\n", __func__, ret);
+ __pkvm_ownership_selftest(0, pgtable, pgtable_order);
+ __pkvm_ownership_selftest(PMD_ORDER, pgtable, pgtable_order);
free_pages(pgtable, pgtable_order);
- free_page(page);
}
#endif
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 15/22] KVM: arm64: Warn on pKVM guest stage-2 block collapse
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (13 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 14/22] KVM: arm64: Add a range to pKVM ownership selftest Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 16/22] KVM: arm64: Add pkvm_hyp_req infrastructure Vincent Donnefort
` (6 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
When pKVM is enabled, the host relies on a pkvm_mapping tree that
mirrors the actual hypervisor-owned stage-2. The hypervisor is not
allowed to silently split blocks or collapse page-tables. This should be
treated as a bug.
Therefore, WARN on guest stage-2 page-table collapse which would
otherwise be fatal anyway.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 2893183c2056..2e8c241f76c9 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -226,6 +226,12 @@ static void guest_s2_put_page(void *addr)
hyp_put_page(¤t_vm->pool, addr);
}
+static void guest_s2_free_unlinked_table(void *addr, s8 level)
+{
+ /* We should never collapse guest stage-2 tables into blocks */
+ WARN_ON(1);
+}
+
static void __apply_guest_page(void *va, size_t size,
void (*func)(void *addr, size_t size))
{
@@ -294,6 +300,7 @@ int kvm_guest_prepare_stage2(struct pkvm_hyp_vm *vm, void *pgd)
.put_page = guest_s2_put_page,
.dcache_clean_inval_poc = clean_dcache_guest_page,
.icache_inval_pou = invalidate_icache_guest_page,
+ .free_unlinked_table = guest_s2_free_unlinked_table,
};
guest_lock_component(vm);
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 16/22] KVM: arm64: Add pkvm_hyp_req infrastructure
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (14 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 15/22] KVM: arm64: Warn on pKVM guest stage-2 block collapse Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 17/22] KVM: arm64: Introduce kvm_pgtable_stage2_table_install() Vincent Donnefort
` (5 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
The pKVM hypervisor depends on the host for a lot of operations like
memory allocation. Introduce a struct pkvm_hyp_req to enable the pKVM
hypervisor to request resources from the host.
Additionally, introduce a trace event to track the handling of these
requests.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/include/asm/kvm_asm.h | 10 ++++---
arch/arm64/include/asm/kvm_host.h | 12 +++++++++
arch/arm64/include/asm/kvm_pkvm.h | 1 +
arch/arm64/kvm/handle_exit.c | 3 +++
arch/arm64/kvm/pkvm.c | 20 ++++++++++++++
arch/arm64/kvm/trace_pkvm.h | 43 +++++++++++++++++++++++++++++++
6 files changed, 85 insertions(+), 4 deletions(-)
create mode 100644 arch/arm64/kvm/trace_pkvm.h
diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index 95dd62e5601b..ec59f0fe9374 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -17,10 +17,11 @@
#define ARM_EXCEPTION_IS_TRAP(x) (ARM_EXCEPTION_CODE((x)) == ARM_EXCEPTION_TRAP)
#define ARM_SERROR_PENDING(x) !!((x) & (1U << ARM_EXIT_WITH_SERROR_BIT))
-#define ARM_EXCEPTION_IRQ 0
-#define ARM_EXCEPTION_EL1_SERROR 1
-#define ARM_EXCEPTION_TRAP 2
-#define ARM_EXCEPTION_IL 3
+#define ARM_EXCEPTION_IRQ 0
+#define ARM_EXCEPTION_EL1_SERROR 1
+#define ARM_EXCEPTION_TRAP 2
+#define ARM_EXCEPTION_IL 3
+#define ARM_EXCEPTION_PKVM_HYP_REQ 4
/* The hyp-stub will return this for any kvm_call_hyp() call */
#define ARM_EXCEPTION_HYP_GONE HVC_STUB_ERR
@@ -28,6 +29,7 @@
{ARM_EXCEPTION_IRQ, "IRQ" }, \
{ARM_EXCEPTION_EL1_SERROR, "SERROR" }, \
{ARM_EXCEPTION_TRAP, "TRAP" }, \
+ {ARM_EXCEPTION_PKVM_HYP_REQ, "PKVM_HYP_REQ" }, \
{ARM_EXCEPTION_HYP_GONE, "HYP_GONE" }
/*
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 27fe0cd5b2d7..ea76c0f3120e 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -85,6 +85,15 @@ u32 __attribute_const__ kvm_target_cpu(void);
void kvm_reset_vcpu(struct kvm_vcpu *vcpu);
void kvm_arm_vcpu_destroy(struct kvm_vcpu *vcpu);
+enum pkvm_hyp_req_type {
+ PKVM_HYP_NO_REQ = 0,
+ __PKVM_HYP_REQ_TYPE_MAX,
+};
+
+struct pkvm_hyp_req {
+ u8 type;
+};
+
struct kvm_hyp_memcache {
phys_addr_t head;
unsigned long nr_pages;
@@ -929,6 +938,9 @@ struct kvm_vcpu_arch {
/* Pages to top-up the pKVM/EL2 guest pool */
struct kvm_hyp_memcache pkvm_memcache;
+ /* To be read on ARM_EXCEPTION_PKVM_HYP_REQ */
+ struct pkvm_hyp_req pkvm_hyp_req;
+
/* Virtual SError ESR to restore when HCR_EL2.VSE is set */
u64 vsesr_el2;
diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h
index ff8e93efcd7a..7240dcc4f395 100644
--- a/arch/arm64/include/asm/kvm_pkvm.h
+++ b/arch/arm64/include/asm/kvm_pkvm.h
@@ -22,6 +22,7 @@ int pkvm_create_hyp_vm(struct kvm *kvm);
bool pkvm_hyp_vm_is_created(struct kvm *kvm);
void pkvm_destroy_hyp_vm(struct kvm *kvm);
int pkvm_create_hyp_vcpu(struct kvm_vcpu *vcpu);
+int __pkvm_handle_vcpu_req(struct kvm_vcpu *vcpu);
#ifdef CONFIG_NVHE_EL2_DEBUG
void pkvm_ownership_selftest(void);
diff --git a/arch/arm64/kvm/handle_exit.c b/arch/arm64/kvm/handle_exit.c
index db37678dcb05..90500b204e16 100644
--- a/arch/arm64/kvm/handle_exit.c
+++ b/arch/arm64/kvm/handle_exit.c
@@ -18,6 +18,7 @@
#include <asm/kvm_emulate.h>
#include <asm/kvm_mmu.h>
#include <asm/kvm_nested.h>
+#include <asm/kvm_pkvm.h>
#include <asm/debug-monitors.h>
#include <asm/stacktrace/nvhe.h>
#include <asm/traps.h>
@@ -464,6 +465,8 @@ int handle_exit(struct kvm_vcpu *vcpu, int exception_index)
return 1;
case ARM_EXCEPTION_TRAP:
return handle_trap_exceptions(vcpu);
+ case ARM_EXCEPTION_PKVM_HYP_REQ:
+ return __pkvm_handle_vcpu_req(vcpu);
case ARM_EXCEPTION_HYP_GONE:
/*
* EL2 has been reset to the hyp-stub. This happens when a guest
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index ae434f806455..02ad686d7661 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -16,6 +16,9 @@
#include "hyp_constants.h"
+#define CREATE_TRACE_POINTS
+#include "trace_pkvm.h"
+
DEFINE_STATIC_KEY_FALSE(kvm_protected_mode_initialized);
static struct memblock_region *hyp_memory = kvm_nvhe_sym(hyp_memory);
@@ -632,3 +635,20 @@ bool pkvm_force_reclaim_guest_page(phys_addr_t phys)
return !ret || ret == -EAGAIN;
}
+
+static int pkvm_hyp_req_handle(struct pkvm_hyp_req *req, struct kvm_vcpu *vcpu)
+{
+ int ret = -EINVAL;
+
+ switch (req->type) {
+ }
+
+ trace_kvm_handle_pkvm_hyp_req(req, ret);
+
+ return ret;
+}
+
+int __pkvm_handle_vcpu_req(struct kvm_vcpu *vcpu)
+{
+ return pkvm_hyp_req_handle(&vcpu->arch.pkvm_hyp_req, vcpu) ?: 1;
+}
diff --git a/arch/arm64/kvm/trace_pkvm.h b/arch/arm64/kvm/trace_pkvm.h
new file mode 100644
index 000000000000..3966c111e3ad
--- /dev/null
+++ b/arch/arm64/kvm/trace_pkvm.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#if !defined(_TRACE_PKVM_ARM64_KVM_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_PKVM_ARM64_KVM_H
+
+#include <linux/tracepoint.h>
+#include <asm/kvm_pkvm.h>
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM kvm
+
+TRACE_DEFINE_ENUM(PKVM_HYP_NO_REQ);
+
+#define PKVM_HYP_REQ_TYPES \
+ { PKVM_HYP_NO_REQ, "NO_REQ" }
+
+TRACE_EVENT(kvm_handle_pkvm_hyp_req,
+ TP_PROTO(struct pkvm_hyp_req *req, int ret),
+ TP_ARGS(req, ret),
+
+ TP_STRUCT__entry(
+ __field(u8, type)
+ __field(int, ret)
+ ),
+
+ TP_fast_assign(
+ __entry->type = req->type;
+ __entry->ret = ret;
+ ),
+
+ TP_printk("type: %s ret: %d",
+ __print_symbolic(__entry->type, PKVM_HYP_REQ_TYPES),
+ __entry->ret)
+);
+
+#endif /* _TRACE_PKVM_ARM64_KVM_H */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace_pkvm
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 17/22] KVM: arm64: Introduce kvm_pgtable_stage2_table_install()
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (15 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 16/22] KVM: arm64: Add pkvm_hyp_req infrastructure Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 18/22] KVM: arm64: Add __pkvm_host_split_guest HVC Vincent Donnefort
` (4 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
The existing kvm_pgtable_stage2_split() is unfit for pKVM: it relies on
kvm_mmu_memory_cache which pKVM doesn't use and has no easy way to
handle pKVM-specific annotations.
Add the pgtable helper kvm_pgtable_stage2_table_install() so pKVM can
handle block splitting in mem_protect.c. That helper can take a table
either populated with kvm_pgtable_stage2_create_unlinked() or one filled
with annotations.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/include/asm/kvm_pgtable.h | 12 ++++
arch/arm64/kvm/hyp/pgtable.c | 85 ++++++++++++++++++++++++----
2 files changed, 86 insertions(+), 11 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h
index 41a8687938eb..24d6e05d8050 100644
--- a/arch/arm64/include/asm/kvm_pgtable.h
+++ b/arch/arm64/include/asm/kvm_pgtable.h
@@ -827,6 +827,18 @@ int kvm_pgtable_stage2_flush(struct kvm_pgtable *pgt, u64 addr, u64 size);
int kvm_pgtable_stage2_split(struct kvm_pgtable *pgt, u64 addr, u64 size,
struct kvm_mmu_memory_cache *mc);
+/**
+ * kvm_pgtable_stage2_table_install() - Replace a stage-2 leaf entry with a table.
+ * @pgt: Page-table structure initialised by kvm_pgtable_stage2_create_unlinked().
+ * @addr: Address identifying the leaf entry to replace.
+ * @size: Size of the block to replace.
+ * @tab: Pointer to the pre-allocated table to install.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int kvm_pgtable_stage2_table_install(struct kvm_pgtable *pgt, u64 addr, u64 size,
+ kvm_pte_t *table);
+
/**
* kvm_pgtable_walk() - Walk a page-table.
* @pgt: Page-table structure initialised by kvm_pgtable_*_init().
diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
index c4ebae0544d4..6c85af51002d 100644
--- a/arch/arm64/kvm/hyp/pgtable.c
+++ b/arch/arm64/kvm/hyp/pgtable.c
@@ -1533,18 +1533,38 @@ static int stage2_block_get_nr_page_tables(s8 level)
};
}
+static int stage2_table_install(const struct kvm_pgtable_visit_ctx *ctx,
+ struct kvm_s2_mmu *mmu,
+ kvm_pte_t *table)
+{
+ kvm_pte_t new;
+
+ if (!stage2_try_break_pte(ctx, mmu))
+ return -EAGAIN;
+
+ /*
+ * Note, the contents of the page table are guaranteed to be made
+ * visible before the new PTE is assigned because stage2_make_pte()
+ * writes the PTE using smp_store_release().
+ */
+ new = kvm_init_table_pte(table, ctx->mm_ops);
+ stage2_make_pte(ctx, new);
+ return 0;
+}
+
static int stage2_split_walker(const struct kvm_pgtable_visit_ctx *ctx,
enum kvm_pgtable_walk_flags visit)
{
struct kvm_pgtable_mm_ops *mm_ops = ctx->mm_ops;
struct kvm_mmu_memory_cache *mc = ctx->arg;
- struct kvm_s2_mmu *mmu;
- kvm_pte_t pte = ctx->old, new, *childp;
+ kvm_pte_t pte = ctx->old, *childp;
enum kvm_pgtable_prot prot;
+ struct kvm_s2_mmu *mmu;
s8 level = ctx->level;
bool force_pte;
int nr_pages;
u64 phys;
+ int ret;
/* No huge-pages exist at the last level */
if (level == KVM_PGTABLE_LAST_LEVEL)
@@ -1586,18 +1606,12 @@ static int stage2_split_walker(const struct kvm_pgtable_visit_ctx *ctx,
if (IS_ERR(childp))
return PTR_ERR(childp);
- if (!stage2_try_break_pte(ctx, mmu)) {
+ ret = stage2_table_install(ctx, mmu, childp);
+ if (ret) {
kvm_pgtable_stage2_free_unlinked(mm_ops, childp, level);
- return -EAGAIN;
+ return ret;
}
- /*
- * Note, the contents of the page table are guaranteed to be made
- * visible before the new PTE is assigned because stage2_make_pte()
- * writes the PTE using smp_store_release().
- */
- new = kvm_init_table_pte(childp, mm_ops);
- stage2_make_pte(ctx, new);
return 0;
}
@@ -1616,6 +1630,55 @@ int kvm_pgtable_stage2_split(struct kvm_pgtable *pgt, u64 addr, u64 size,
return ret;
}
+struct stage2_table_install_data {
+ struct kvm_s2_mmu *mmu;
+ kvm_pte_t *table;
+};
+
+static int stage2_table_install_walker(const struct kvm_pgtable_visit_ctx *ctx,
+ enum kvm_pgtable_walk_flags visit)
+{
+ struct stage2_table_install_data *data = ctx->arg;
+ struct kvm_s2_mmu *mmu = data->mmu;
+ kvm_pte_t *childp = data->table;
+
+ if (ctx->level == KVM_PGTABLE_LAST_LEVEL)
+ return -EINVAL;
+
+ if (ctx->addr != ctx->start ||
+ ctx->end != ctx->addr + kvm_granule_size(ctx->level))
+ return -EINVAL;
+
+ /*
+ * On success, return a positive value to prevent descending into the
+ * new table.
+ */
+ return stage2_table_install(ctx, mmu, childp) ?: 1;
+}
+
+int kvm_pgtable_stage2_table_install(struct kvm_pgtable *pgt, u64 addr, u64 size, kvm_pte_t *table)
+{
+ struct stage2_table_install_data data = {
+ .mmu = pgt->mmu,
+ .table = table,
+ };
+ struct kvm_pgtable_walker walker = {
+ .cb = stage2_table_install_walker,
+ .flags = KVM_PGTABLE_WALK_LEAF,
+ .arg = &data,
+ };
+ int ret;
+
+ ret = kvm_pgtable_walk(pgt, addr, size, &walker);
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ return -EINVAL;
+
+ dsb(ishst);
+ return 0;
+}
+
int __kvm_pgtable_stage2_init(struct kvm_pgtable *pgt, struct kvm_s2_mmu *mmu,
struct kvm_pgtable_mm_ops *mm_ops,
enum kvm_pgtable_stage2_flags flags,
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 18/22] KVM: arm64: Add __pkvm_host_split_guest HVC
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (16 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 17/22] KVM: arm64: Introduce kvm_pgtable_stage2_table_install() Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 19/22] KVM: arm64: Extend pKVM page ownership selftests to cover guest block split Vincent Donnefort
` (3 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
This HVC allows the host to break an existing block in a protected VM. This
will later allow the guest to share a sub-region of an existing
huge-mapping with the host.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/include/asm/kvm_asm.h | 1 +
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 1 +
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 22 +++
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 127 ++++++++++++++++--
4 files changed, 138 insertions(+), 13 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index ec59f0fe9374..a67d171c86d0 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -105,6 +105,7 @@ enum __kvm_host_smccc_func {
__KVM_HOST_SMCCC_FUNC___pkvm_host_wrprotect_guest,
__KVM_HOST_SMCCC_FUNC___pkvm_host_test_clear_young_guest,
__KVM_HOST_SMCCC_FUNC___pkvm_host_mkyoung_guest,
+ __KVM_HOST_SMCCC_FUNC___pkvm_host_split_guest,
__KVM_HOST_SMCCC_FUNC___pkvm_reserve_vm,
__KVM_HOST_SMCCC_FUNC___pkvm_unreserve_vm,
__KVM_HOST_SMCCC_FUNC___pkvm_init_vm,
diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
index 7904a95ab8ac..f9604f9a92dc 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
@@ -50,6 +50,7 @@ int __pkvm_host_relax_perms_guest(u64 gfn, struct pkvm_hyp_vcpu *vcpu, enum kvm_
int __pkvm_host_wrprotect_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vm *hyp_vm);
int __pkvm_host_test_clear_young_guest(u64 gfn, u64 nr_pages, bool mkold, struct pkvm_hyp_vm *vm);
int __pkvm_host_mkyoung_guest(u64 gfn, struct pkvm_hyp_vcpu *vcpu);
+int __pkvm_host_split_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vcpu *vcpu);
bool addr_is_memory(phys_addr_t phys);
int host_stage2_idmap_locked(phys_addr_t addr, u64 size, enum kvm_pgtable_prot prot);
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 7ef7da69f48b..cc0e1061d584 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -540,6 +540,27 @@ static void handle___pkvm_host_mkyoung_guest(struct kvm_cpu_context *host_ctxt)
cpu_reg(host_ctxt, 1) = ret;
}
+static void handle___pkvm_host_split_guest(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(u64, gfn, host_ctxt, 1);
+ DECLARE_REG(u64, nr_pages, host_ctxt, 2);
+ struct pkvm_hyp_vcpu *hyp_vcpu;
+ int ret = -EINVAL;
+
+ hyp_vcpu = pkvm_get_loaded_hyp_vcpu();
+ if (!hyp_vcpu || !pkvm_hyp_vcpu_is_protected(hyp_vcpu))
+ goto out;
+
+ ret = pkvm_refill_memcache(hyp_vcpu);
+ if (ret)
+ goto out;
+
+ ret = __pkvm_host_split_guest(gfn, nr_pages, hyp_vcpu);
+
+out:
+ cpu_reg(host_ctxt, 1) = ret;
+}
+
static void handle___kvm_adjust_pc(struct kvm_cpu_context *host_ctxt)
{
DECLARE_REG(struct kvm_vcpu *, vcpu, host_ctxt, 1);
@@ -958,6 +979,7 @@ static const hcall_t host_hcall[] = {
HANDLE_FUNC(__pkvm_host_wrprotect_guest),
HANDLE_FUNC(__pkvm_host_test_clear_young_guest),
HANDLE_FUNC(__pkvm_host_mkyoung_guest),
+ HANDLE_FUNC(__pkvm_host_split_guest),
HANDLE_FUNC(__pkvm_reserve_vm),
HANDLE_FUNC(__pkvm_unreserve_vm),
HANDLE_FUNC(__pkvm_init_vm),
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 2e8c241f76c9..f4943c2a237f 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -578,6 +578,12 @@ static void __host_update_page_state(phys_addr_t addr, u64 size, enum pkvm_page_
#define KVM_HOST_DONATION_PTE_OWNER_MASK GENMASK(3, 1)
#define KVM_HOST_DONATION_PTE_EXTRA_MASK GENMASK(59, 4)
+static kvm_pte_t host_stage2_encode_annotation(u8 owner_id, u64 meta)
+{
+ return FIELD_PREP(KVM_HOST_DONATION_PTE_OWNER_MASK, owner_id) |
+ FIELD_PREP(KVM_HOST_DONATION_PTE_EXTRA_MASK, meta);
+}
+
static int host_stage2_set_owner_metadata_locked(phys_addr_t addr, u64 size,
u8 owner_id, u64 meta)
{
@@ -596,8 +602,7 @@ static int host_stage2_set_owner_metadata_locked(phys_addr_t addr, u64 size,
if (!FIELD_FIT(KVM_HOST_DONATION_PTE_EXTRA_MASK, meta))
return -EINVAL;
- annotation = FIELD_PREP(KVM_HOST_DONATION_PTE_OWNER_MASK, owner_id) |
- FIELD_PREP(KVM_HOST_DONATION_PTE_EXTRA_MASK, meta);
+ annotation = host_stage2_encode_annotation(owner_id, meta);
ret = host_stage2_try(kvm_pgtable_stage2_annotate, &host_mmu.pgt,
addr, size, &host_s2_pool,
KVM_HOST_INVALID_PTE_TYPE_DONATION, annotation);
@@ -649,10 +654,8 @@ static u64 host_stage2_encode_gfn_meta(struct pkvm_hyp_vm *vm, u64 gfn)
FIELD_PREP(KVM_HOST_PTE_OWNER_GUEST_GFN_MASK, gfn);
}
-static int host_stage2_decode_gfn_meta(kvm_pte_t pte, struct pkvm_hyp_vm **vm,
- u64 *gfn)
+static int host_stage2_decode_gfn_meta(kvm_pte_t pte, pkvm_handle_t *handle, u64 *gfn)
{
- pkvm_handle_t handle;
u64 meta;
if (WARN_ON(kvm_pte_valid(pte)))
@@ -667,17 +670,59 @@ static int host_stage2_decode_gfn_meta(kvm_pte_t pte, struct pkvm_hyp_vm **vm,
return -EPERM;
meta = FIELD_GET(KVM_HOST_DONATION_PTE_EXTRA_MASK, pte);
- handle = FIELD_GET(KVM_HOST_PTE_OWNER_GUEST_HANDLE_MASK, meta);
- *vm = get_vm_by_handle(handle);
- if (!*vm) {
- /* We probably raced with teardown; try again */
- return -EAGAIN;
- }
-
+ *handle = FIELD_GET(KVM_HOST_PTE_OWNER_GUEST_HANDLE_MASK, meta);
*gfn = FIELD_GET(KVM_HOST_PTE_OWNER_GUEST_GFN_MASK, meta);
return 0;
}
+static int host_stage2_split_gfn_meta(phys_addr_t phys, u64 ipa, u64 size, struct pkvm_hyp_vm *vm)
+{
+ struct kvm_pgtable_mm_ops *mm_ops = &host_mmu.mm_ops;
+ struct kvm_pgtable *pgt = &host_mmu.pgt;
+ kvm_pte_t pte, *table, *ptep;
+ pkvm_handle_t handle;
+ s8 level;
+ u64 gfn;
+ int ret;
+
+ ret = kvm_pgtable_get_leaf(pgt, phys, &pte, &level);
+ if (ret)
+ return ret;
+
+ if (kvm_granule_size(level) != size)
+ return -EINVAL;
+
+ ret = host_stage2_decode_gfn_meta(pte, &handle, &gfn);
+ if (ret)
+ return ret;
+
+ if (handle != vm->kvm.arch.pkvm.handle || gfn != (ipa >> PAGE_SHIFT))
+ return -EINVAL;
+
+ table = mm_ops->zalloc_page(&host_s2_pool);
+ if (!table)
+ return -ENOMEM;
+
+ ptep = table;
+ while (ptep < table + PTRS_PER_PTE) {
+ u64 meta = host_stage2_encode_gfn_meta(vm, gfn);
+ kvm_pte_t annotation = host_stage2_encode_annotation(PKVM_ID_GUEST, meta);
+
+ *ptep = FIELD_PREP(KVM_INVALID_PTE_TYPE_MASK, KVM_HOST_INVALID_PTE_TYPE_DONATION) |
+ annotation;
+
+ mm_ops->get_page(ptep);
+ ptep++;
+ gfn++;
+ }
+
+ ret = kvm_pgtable_stage2_table_install(pgt, phys, size, table);
+ if (ret)
+ kvm_pgtable_stage2_free_unlinked(mm_ops, table, level);
+
+ return ret;
+}
+
static bool host_stage2_force_pte_cb(u64 addr, u64 end, enum kvm_pgtable_prot prot)
{
/*
@@ -1328,6 +1373,7 @@ static int host_stage2_get_guest_info(phys_addr_t phys, u64 *size, struct pkvm_h
u64 *gfn)
{
enum pkvm_page_state state;
+ pkvm_handle_t handle;
kvm_pte_t pte;
s8 level;
int ret;
@@ -1356,10 +1402,16 @@ static int host_stage2_get_guest_info(phys_addr_t phys, u64 *size, struct pkvm_h
if (level < KVM_PGTABLE_LAST_LEVEL - 1)
return -E2BIG;
- ret = host_stage2_decode_gfn_meta(pte, vm, gfn);
+ ret = host_stage2_decode_gfn_meta(pte, &handle, gfn);
if (ret)
return ret;
+ *vm = get_vm_by_handle(handle);
+ if (!*vm) {
+ /* We probably raced with teardown; try again */
+ return -EAGAIN;
+ }
+
*size = kvm_granule_size(level);
return 0;
@@ -1755,6 +1807,55 @@ int __pkvm_host_mkyoung_guest(u64 gfn, struct pkvm_hyp_vcpu *vcpu)
return 0;
}
+int __pkvm_host_split_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vcpu *vcpu)
+{
+ struct kvm_hyp_memcache *mc = &vcpu->vcpu.arch.pkvm_memcache;
+ struct pkvm_hyp_vm *vm = pkvm_hyp_vcpu_to_hyp_vm(vcpu);
+ u64 size = nr_pages << PAGE_SHIFT;
+ u64 ipa = hyp_pfn_to_phys(gfn);
+ kvm_pte_t pte, *table;
+ phys_addr_t phys;
+ s8 level;
+ int ret;
+
+ if (!vcpu->vcpu.arch.pkvm_memcache.nr_pages)
+ return -ENOMEM;
+
+ if (size != PMD_SIZE || !IS_ALIGNED(ipa, PMD_SIZE))
+ return -EINVAL;
+
+ level = KVM_PGTABLE_LAST_LEVEL - 1;
+
+ host_lock_component();
+ guest_lock_component(vm);
+
+ ret = get_valid_guest_pte(vm, ipa, size, &pte, &phys);
+ if (ret)
+ goto unlock;
+
+ table = kvm_pgtable_stage2_create_unlinked(&vm->pgt, kvm_pte_to_phys(pte), level,
+ kvm_pgtable_stage2_pte_prot(pte),
+ mc, true);
+ if (IS_ERR(table)) {
+ ret = PTR_ERR(table);
+ goto unlock;
+ }
+
+ ret = host_stage2_split_gfn_meta(phys, ipa, size, vm);
+ if (ret) {
+ kvm_pgtable_stage2_free_unlinked(&vm->mm_ops, table, level);
+ goto unlock;
+ }
+
+ WARN_ON(kvm_pgtable_stage2_table_install(&vm->pgt, ipa, size, table));
+
+unlock:
+ guest_unlock_component(vm);
+ host_unlock_component();
+
+ return ret;
+}
+
#ifdef CONFIG_NVHE_EL2_DEBUG
struct pkvm_expected_state {
enum pkvm_page_state host;
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 19/22] KVM: arm64: Extend pKVM page ownership selftests to cover guest block split
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (17 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 18/22] KVM: arm64: Add __pkvm_host_split_guest HVC Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 20/22] KVM: arm64: Add PKVM_HYP_REQ_SPLIT Vincent Donnefort
` (2 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
Extend the pKVM page ownership selftests to test the split of a guest
stage-2 block and the per-page reclaim of that same block.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 41 +++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index f4943c2a237f..e318b5a3f600 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -2105,6 +2105,47 @@ int __pkvm_ownership_selftest(u64 pfn, u64 nr_pages, u64 pgtable_pfn,
assert_transition_res(-EPERM, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
assert_transition_res(-EPERM, __pkvm_host_share_guest, pfn, gfn, nr_pages, vcpu, prot);
+ if (size == PMD_SIZE) {
+ /* [gfn, gfn + nr_pages) was poisoned. Skip it */
+ gfn += nr_pages;
+
+ selftest_state.host = PKVM_NOPAGE;
+ selftest_state.guest[1] = PKVM_PAGE_OWNED;
+ assert_transition_res(0, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
+ assert_transition_res(-E2BIG, __pkvm_host_reclaim_guest, gfn, 1, vm);
+ assert_transition_res(-E2BIG, __pkvm_guest_share_host, vcpu, gfn, 1);
+ assert_transition_res(0, __pkvm_host_split_guest, gfn, nr_pages, vcpu);
+
+ assert_transition_res(-E2BIG, __pkvm_host_split_guest, gfn, nr_pages, vcpu);
+ assert_transition_res(-E2BIG, __pkvm_host_reclaim_guest, gfn, nr_pages, vm);
+ assert_transition_res(-E2BIG, __pkvm_guest_share_host, vcpu, gfn, nr_pages);
+
+ for (int i = 0; i < nr_pages; i++) {
+ WARN_ON(__pkvm_guest_share_host(vcpu, gfn + i, 1));
+ WARN_ON(__pkvm_guest_unshare_host(vcpu, gfn + i, 1));
+ }
+ assert_page_state();
+
+ for (int i = 0; i < nr_pages; i++)
+ WARN_ON(__pkvm_host_reclaim_guest(gfn + i, 1, vm));
+
+ selftest_state.host = PKVM_PAGE_OWNED;
+ selftest_state.guest[1] = PKVM_NOPAGE;
+ assert_page_state();
+
+ selftest_state.host = PKVM_NOPAGE;
+ selftest_state.guest[1] = PKVM_PAGE_OWNED;
+ assert_transition_res(0, __pkvm_host_donate_guest, pfn, gfn, nr_pages, vcpu);
+ assert_transition_res(0, __pkvm_host_split_guest, gfn, nr_pages, vcpu);
+
+ for (int i = 0; i < nr_pages; i++)
+ WARN_ON(__pkvm_host_force_reclaim_page_guest(phys + i * PAGE_SIZE));
+
+ selftest_state.host = PKVM_PAGE_OWNED;
+ selftest_state.guest[1] = PKVM_POISON;
+ assert_page_state();
+ }
+
selftest_state.host = PKVM_NOPAGE;
selftest_state.hyp = PKVM_PAGE_OWNED;
assert_transition_res(0, __pkvm_host_donate_hyp, pfn, nr_pages);
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 20/22] KVM: arm64: Add PKVM_HYP_REQ_SPLIT
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (18 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 19/22] KVM: arm64: Extend pKVM page ownership selftests to cover guest block split Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 21/22] KVM: arm64: Raise PKVM_HYP_REQ_SPLIT on guest to host sharing Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 22/22] KVM: arm64: Stage-2 huge mappings for protected VMs Vincent Donnefort
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
With the upcoming support for stage-2 huge mappings for protected VMs,
we need a way to split blocks. Since the host has its own "copy" of the
guest stage-2 in the pkvm_mappings rb-tree, the split must be done
simultaneously for both that tree and the guest stage-2. Therefore the
hypervisor can't do it on its own and must rely on the host for this
operation.
Create a pKVM hypervisor request to ask the host to split a specified
region of the guest. On this request, the host can synchronise the split
of both guest stage-2 (HVC __pkvm_host_split_guest) and the
pkvm_mappings tree. It ensures a concurrent VM teardown can't observe a
PMD_SIZE pkvm_mapping while the guest stage-2 is PAGE_SIZE.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/include/asm/kvm_host.h | 7 ++
arch/arm64/include/asm/kvm_pkvm.h | 5 +-
arch/arm64/kvm/pkvm.c | 145 ++++++++++++++++++++++++++++++
arch/arm64/kvm/trace_pkvm.h | 5 +-
4 files changed, 159 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index ea76c0f3120e..c4cb2232d4c8 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -87,11 +87,18 @@ void kvm_arm_vcpu_destroy(struct kvm_vcpu *vcpu);
enum pkvm_hyp_req_type {
PKVM_HYP_NO_REQ = 0,
+ PKVM_HYP_REQ_SPLIT,
__PKVM_HYP_REQ_TYPE_MAX,
};
struct pkvm_hyp_req {
u8 type;
+ union {
+ struct {
+ u32 nr_pages;
+ u64 gfn;
+ } split;
+ };
};
struct kvm_hyp_memcache {
diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h
index 7240dcc4f395..33c94f353eda 100644
--- a/arch/arm64/include/asm/kvm_pkvm.h
+++ b/arch/arm64/include/asm/kvm_pkvm.h
@@ -196,7 +196,10 @@ static inline size_t pkvm_host_sve_state_size(void)
}
struct pkvm_mapping {
- struct rb_node node;
+ union {
+ struct rb_node node;
+ struct list_head list;
+ };
u64 gfn;
u64 pfn;
struct {
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index 02ad686d7661..2840053ef2f4 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -362,6 +362,64 @@ INTERVAL_TREE_DEFINE(struct pkvm_mapping, node, u64, __subtree_last,
}); \
)
+static void pkvm_mapping_free_spares(struct list_head *spares)
+{
+ struct pkvm_mapping *m, *tmp;
+
+ list_for_each_entry_safe(m, tmp, spares, list) {
+ list_del(&m->list);
+ kfree(m);
+ }
+}
+
+static int pkvm_mapping_alloc_spares(struct list_head *head, u64 nr_spares)
+{
+ struct pkvm_mapping *m;
+
+ while (nr_spares--) {
+ m = kzalloc_obj(*m, GFP_KERNEL_ACCOUNT);
+ if (!m) {
+ pkvm_mapping_free_spares(head);
+ return -ENOMEM;
+ }
+
+ list_add(&m->list, head);
+ }
+
+ return 0;
+}
+
+static void pkvm_mapping_split(struct pkvm_mapping *mapping, struct kvm_pgtable *pgt,
+ struct list_head *spares)
+{
+ struct kvm *kvm = kvm_s2_mmu_to_kvm(pgt->mmu);
+ u64 nr_pages = mapping->nr_pages - 1;
+ gfn_t gfn = mapping->gfn + 1;
+ u64 pfn = mapping->pfn + 1;
+
+ lockdep_assert_held_write(&kvm->mmu_lock);
+
+ pkvm_mapping_remove(mapping, &pgt->pkvm_mappings);
+ mapping->nr_pages = 1;
+ pkvm_mapping_insert(mapping, &pgt->pkvm_mappings);
+
+ while (nr_pages--) {
+ struct pkvm_mapping *m;
+
+ if (WARN_ON(list_empty(spares)))
+ break;
+
+ m = list_first_entry(spares, struct pkvm_mapping, list);
+ list_del(&m->list);
+
+ m->nr_pages = 1;
+ m->gfn = gfn++;
+ m->pfn = pfn++;
+
+ pkvm_mapping_insert(m, &pgt->pkvm_mappings);
+ }
+}
+
int pkvm_pgtable_stage2_init(struct kvm_pgtable *pgt, struct kvm_s2_mmu *mmu,
struct kvm_pgtable_mm_ops *mm_ops)
{
@@ -624,6 +682,73 @@ int pkvm_pgtable_stage2_split(struct kvm_pgtable *pgt, u64 addr, u64 size,
return -EINVAL;
}
+/*
+ * Splitting is only expected on the back of a guest HVC, while
+ * pkvm_pgtable_stage2_split() can be called with dirty logging.
+ */
+static int __pkvm_pgtable_stage2_split(struct kvm_vcpu *vcpu, phys_addr_t ipa, u64 size)
+{
+ struct kvm_hyp_memcache *mc = &vcpu->arch.pkvm_memcache;
+ struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
+ struct pkvm_mapping *mapping;
+ struct kvm *kvm = vcpu->kvm;
+ struct list_head spares;
+ u64 nr_pages;
+ int ret;
+
+ if (WARN_ON(!kvm_vm_is_protected(kvm)))
+ return -EINVAL;
+
+ if (!IS_ALIGNED(ipa, PMD_SIZE) || size != PMD_SIZE)
+ return -EINVAL;
+
+ ret = topup_hyp_memcache(mc, 1);
+ if (ret)
+ return ret;
+
+ /* We already have 1 pin on the huge-page */
+ nr_pages = (size / PAGE_SIZE) - 1;
+
+ INIT_LIST_HEAD(&spares);
+ ret = pkvm_mapping_alloc_spares(&spares, nr_pages);
+ if (ret)
+ return ret;
+
+ write_lock(&kvm->mmu_lock);
+
+ mapping = pkvm_mapping_iter_first(&pgt->pkvm_mappings, ipa, ipa + size - 1);
+ if (!mapping) {
+ ret = -EPERM;
+ goto unlock_mmu;
+ } else if (mapping->nr_pages == 1) {
+ /* We've raced with another vCPU */
+ ret = 0;
+ goto unlock_mmu;
+ } else if (mapping->nr_pages * PAGE_SIZE != PMD_SIZE) {
+ ret = -EINVAL;
+ goto unlock_mmu;
+ }
+
+ ret = folio_add_pins(page_folio(pfn_to_page(mapping->pfn)), nr_pages);
+ if (ret)
+ goto unlock_mmu;
+
+ ret = kvm_call_hyp_nvhe(__pkvm_host_split_guest, gpa_to_gfn(ipa), size / PAGE_SIZE);
+ if (ret) {
+ for (int i = 0; i < nr_pages; i++)
+ unpin_user_page(pfn_to_page(mapping->pfn + 1 + i));
+ goto unlock_mmu;
+ }
+
+ pkvm_mapping_split(mapping, pgt, &spares);
+
+unlock_mmu:
+ write_unlock(&kvm->mmu_lock);
+ pkvm_mapping_free_spares(&spares);
+
+ return ret;
+}
+
/*
* Forcefully reclaim a page from the guest, zeroing its contents and
* poisoning the stage-2 pte so that pages can no longer be mapped at
@@ -636,11 +761,31 @@ bool pkvm_force_reclaim_guest_page(phys_addr_t phys)
return !ret || ret == -EAGAIN;
}
+static int pkvm_hyp_req_handle_split(struct kvm_vcpu *vcpu, u64 gfn, u64 nr_pages)
+{
+ phys_addr_t addr = ALIGN_DOWN(gfn << PAGE_SHIFT, PMD_SIZE);
+ phys_addr_t end = ALIGN((gfn + nr_pages) << PAGE_SHIFT, PMD_SIZE);
+
+ while (addr < end) {
+ int ret = __pkvm_pgtable_stage2_split(vcpu, addr, PMD_SIZE);
+
+ if (ret)
+ return ret;
+
+ addr += PMD_SIZE;
+ }
+
+ return 0;
+}
+
static int pkvm_hyp_req_handle(struct pkvm_hyp_req *req, struct kvm_vcpu *vcpu)
{
int ret = -EINVAL;
switch (req->type) {
+ case PKVM_HYP_REQ_SPLIT:
+ ret = pkvm_hyp_req_handle_split(vcpu, req->split.gfn, req->split.nr_pages);
+ break;
}
trace_kvm_handle_pkvm_hyp_req(req, ret);
diff --git a/arch/arm64/kvm/trace_pkvm.h b/arch/arm64/kvm/trace_pkvm.h
index 3966c111e3ad..801c6e9aaa4c 100644
--- a/arch/arm64/kvm/trace_pkvm.h
+++ b/arch/arm64/kvm/trace_pkvm.h
@@ -10,8 +10,9 @@
TRACE_DEFINE_ENUM(PKVM_HYP_NO_REQ);
-#define PKVM_HYP_REQ_TYPES \
- { PKVM_HYP_NO_REQ, "NO_REQ" }
+#define PKVM_HYP_REQ_TYPES \
+ { PKVM_HYP_NO_REQ, "NO_REQ" }, \
+ { PKVM_HYP_REQ_SPLIT, "SPLIT" },
TRACE_EVENT(kvm_handle_pkvm_hyp_req,
TP_PROTO(struct pkvm_hyp_req *req, int ret),
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 21/22] KVM: arm64: Raise PKVM_HYP_REQ_SPLIT on guest to host sharing
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (19 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 20/22] KVM: arm64: Add PKVM_HYP_REQ_SPLIT Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
2026-09-11 13:50 ` [PATCH v2 22/22] KVM: arm64: Stage-2 huge mappings for protected VMs Vincent Donnefort
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
Pages shared by the guest with the host are annotated into the guest
stage-2. If the shared page is backed by a huge mapping, we need to
split it first.
Raise a PKVM_HYP_REQ_SPLIT for the host on the guest HVC MEM_SHARE, if a
block exists (-E2BIG) and replay the HVC (by rewinding the ELR).
At the moment, the guest HVC MEM_SHARE is single-page only. This means
unsharing is ensured to find a PTE-level mapping. However, as this will
most likely be extended later, raise the same PKVM_HYP_REQ_SPLIT on the
guest HVC MEM_UNSHARE.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/hyp/nvhe/pkvm.c | 43 ++++++++++++++++++++++++++++++----
1 file changed, 38 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c
index 2b0b0bc3064c..9b24c2233490 100644
--- a/arch/arm64/kvm/hyp/nvhe/pkvm.c
+++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c
@@ -1095,16 +1095,34 @@ static u64 __pkvm_memshare_page_req(struct kvm_vcpu *vcpu, u64 ipa)
return ARM_EXCEPTION_TRAP;
}
+static u64 pkvm_request_split(struct pkvm_hyp_vcpu *hyp_vcpu, u64 gfn, u64 nr_pages)
+{
+ struct pkvm_hyp_req *req = &hyp_vcpu->host_vcpu->arch.pkvm_hyp_req;
+ u64 elr;
+
+ req->type = PKVM_HYP_REQ_SPLIT;
+ req->split.gfn = gfn;
+ req->split.nr_pages = nr_pages;
+
+ /* Rewind the ELR so we return to the HVC once the block is split */
+ elr = read_sysreg(elr_el2);
+ elr -= 4;
+ write_sysreg(elr, elr_el2);
+
+ return ARM_EXCEPTION_PKVM_HYP_REQ;
+}
+
static bool pkvm_memshare_call(u64 *ret, struct kvm_vcpu *vcpu, u64 *exit_code)
{
struct pkvm_hyp_vcpu *hyp_vcpu;
u64 ipa = smccc_get_arg1(vcpu);
+ u64 gfn = hyp_phys_to_pfn(ipa);
if (!PAGE_ALIGNED(ipa))
goto out_guest;
hyp_vcpu = container_of(vcpu, struct pkvm_hyp_vcpu, vcpu);
- switch (__pkvm_guest_share_host(hyp_vcpu, hyp_phys_to_pfn(ipa), 1)) {
+ switch (__pkvm_guest_share_host(hyp_vcpu, gfn, 1)) {
case 0:
ret[0] = SMCCC_RET_SUCCESS;
goto out_guest;
@@ -1115,6 +1133,9 @@ static bool pkvm_memshare_call(u64 *ret, struct kvm_vcpu *vcpu, u64 *exit_code)
*/
*exit_code = __pkvm_memshare_page_req(vcpu, ipa);
goto out_host;
+ case -E2BIG:
+ *exit_code = pkvm_request_split(hyp_vcpu, gfn, 1);
+ goto out_host;
}
out_guest:
@@ -1123,17 +1144,29 @@ static bool pkvm_memshare_call(u64 *ret, struct kvm_vcpu *vcpu, u64 *exit_code)
return false;
}
-static void pkvm_memunshare_call(u64 *ret, struct kvm_vcpu *vcpu)
+static bool pkvm_memunshare_call(u64 *ret, struct kvm_vcpu *vcpu, u64 *exit_code)
{
struct pkvm_hyp_vcpu *hyp_vcpu;
u64 ipa = smccc_get_arg1(vcpu);
+ u64 gfn = hyp_phys_to_pfn(ipa);
if (!PAGE_ALIGNED(ipa))
- return;
+ goto out_guest;
hyp_vcpu = container_of(vcpu, struct pkvm_hyp_vcpu, vcpu);
- if (!__pkvm_guest_unshare_host(hyp_vcpu, hyp_phys_to_pfn(ipa), 1))
+ switch (__pkvm_guest_unshare_host(hyp_vcpu, gfn, 1)) {
+ case 0:
ret[0] = SMCCC_RET_SUCCESS;
+ goto out_guest;
+ case -E2BIG:
+ *exit_code = pkvm_request_split(hyp_vcpu, gfn, 1);
+ goto out_host;
+ }
+
+out_guest:
+ return true;
+out_host:
+ return false;
}
/*
@@ -1178,7 +1211,7 @@ bool kvm_handle_pvm_hvc64(struct kvm_vcpu *vcpu, u64 *exit_code)
break;
}
- pkvm_memunshare_call(val, vcpu);
+ handled = pkvm_memunshare_call(val, vcpu, exit_code);
break;
default:
/* Punt everything else back to the host, for now. */
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v2 22/22] KVM: arm64: Stage-2 huge mappings for protected VMs
2026-09-11 13:50 [PATCH v2 00/22] Huge mapping support for protected VMs Vincent Donnefort
` (20 preceding siblings ...)
2026-09-11 13:50 ` [PATCH v2 21/22] KVM: arm64: Raise PKVM_HYP_REQ_SPLIT on guest to host sharing Vincent Donnefort
@ 2026-09-11 13:50 ` Vincent Donnefort
21 siblings, 0 replies; 23+ messages in thread
From: Vincent Donnefort @ 2026-09-11 13:50 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, weilin.chang,
Vincent Donnefort
Enable PMD-sized stage-2 block mappings for protected VMs. This is
possible whenever the stage-1 mapping allows it, that is, if it is
itself backed by THPs.
When a THP is found, an entire PMD_SIZE mapping is donated to the guest.
This mapping can only be broken down via the HVC
__pkvm_host_split_guest() which the hypervisor can request with
PKVM_HYP_REQ_SPLIT.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/mmu.c | 119 +++++++++++++++++++++++++++---------------
arch/arm64/kvm/pkvm.c | 21 ++++----
2 files changed, 89 insertions(+), 51 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 9ba86450fe4a..218df096c72e 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1690,52 +1690,26 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
return ret != -EAGAIN ? ret : 0;
}
-struct kvm_s2_fault_vma_info {
- unsigned long mmu_seq;
- long vma_pagesize;
- vm_flags_t vm_flags;
- unsigned long max_map_size;
- struct page *page;
- kvm_pfn_t pfn;
- gfn_t gfn;
- bool device;
- bool mte_allowed;
- bool is_vma_cacheable;
- bool map_writable;
- bool map_non_cacheable;
-};
-
-static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
+static int pkvm_pin_user_pages(const struct kvm_s2_fault_desc *s2fd, struct page **__page,
+ unsigned long *__size, kvm_pfn_t *__pfn, gfn_t *__gfn)
{
unsigned int flags = FOLL_HWPOISON | FOLL_LONGTERM | FOLL_WRITE;
struct kvm_vcpu *vcpu = s2fd->vcpu;
- struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
struct mm_struct *mm = current->mm;
struct kvm *kvm = vcpu->kvm;
- void *hyp_memcache;
struct page *page;
- int ret;
+ kvm_pfn_t pfn;
+ gfn_t gfn;
+ long ret;
- hyp_memcache = get_mmu_memcache(vcpu);
- ret = topup_mmu_memcache(vcpu, hyp_memcache);
- if (ret)
- return -ENOMEM;
+ guard(mmap_read_lock)(mm);
- ret = account_locked_vm(mm, 1, true);
- if (ret)
- return ret;
-
- mmap_read_lock(mm);
ret = pin_user_pages(s2fd->hva, 1, flags, &page);
- mmap_read_unlock(mm);
-
if (ret == -EHWPOISON) {
kvm_send_hwpoison_signal(s2fd->hva, PAGE_SHIFT);
- ret = 0;
- goto dec_account;
+ return 0;
} else if (ret != 1) {
- ret = -EFAULT;
- goto dec_account;
+ return -EFAULT;
} else if (!folio_test_swapbacked(page_folio(page))) {
/*
* We really can't deal with page-cache pages returned by GUP
@@ -1751,29 +1725,90 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
* pages backed by swap in the knowledge that the GUP pin will
* prevent try_to_unmap() from succeeding.
*/
- ret = -EIO;
- goto unpin;
+ unpin_user_page(page);
+ return -EIO;
}
+ pfn = page_to_pfn(page);
+ gfn = gpa_to_gfn(s2fd->fault_ipa);
+
+ ret = transparent_hugepage_adjust(kvm, s2fd->memslot, s2fd->hva, &pfn, &gfn);
+ if (ret < 0) {
+ unpin_user_page(page);
+ return ret;
+ } else if (ret == PMD_SIZE && WARN_ON_ONCE(folio_size(page_folio(page)) < PMD_SIZE)) {
+ unpin_user_page(page);
+ return -EINVAL;
+ }
+
+ *__page = page;
+ *__size = ret;
+ *__pfn = pfn;
+ *__gfn = gfn;
+
+ return 0;
+}
+
+static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
+{
+ struct kvm_vcpu *vcpu = s2fd->vcpu;
+ struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
+ struct mm_struct *mm = current->mm;
+ struct kvm *kvm = vcpu->kvm;
+ unsigned long size;
+ void *hyp_memcache;
+ struct page *page;
+ kvm_pfn_t pfn;
+ gfn_t gfn;
+ int ret;
+
+ hyp_memcache = get_mmu_memcache(vcpu);
+ ret = topup_mmu_memcache(vcpu, hyp_memcache);
+ if (ret)
+ return -ENOMEM;
+
+ ret = pkvm_pin_user_pages(s2fd, &page, &size, &pfn, &gfn);
+ if (ret)
+ return ret;
+
+ ret = account_locked_vm(mm, size / PAGE_SIZE, true);
+ if (ret)
+ goto unpin;
+
write_lock(&kvm->mmu_lock);
- ret = pkvm_pgtable_stage2_map(pgt, s2fd->fault_ipa, PAGE_SIZE,
- page_to_phys(page), KVM_PGTABLE_PROT_RWX,
- hyp_memcache, 0);
+ ret = pkvm_pgtable_stage2_map(pgt, gfn_to_gpa(gfn), size, __pfn_to_phys(pfn),
+ KVM_PGTABLE_PROT_RWX, hyp_memcache, 0);
write_unlock(&kvm->mmu_lock);
if (ret) {
if (ret == -EAGAIN)
ret = 0;
+
+ account_locked_vm(mm, size / PAGE_SIZE, false);
goto unpin;
}
return 0;
+
unpin:
- unpin_user_pages(&page, 1);
-dec_account:
- account_locked_vm(mm, 1, false);
+ unpin_user_page(page);
return ret;
}
+struct kvm_s2_fault_vma_info {
+ unsigned long mmu_seq;
+ long vma_pagesize;
+ vm_flags_t vm_flags;
+ unsigned long max_map_size;
+ struct page *page;
+ kvm_pfn_t pfn;
+ gfn_t gfn;
+ bool device;
+ bool mte_allowed;
+ bool is_vma_cacheable;
+ bool map_writable;
+ bool map_non_cacheable;
+};
+
static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd,
struct kvm_s2_fault_vma_info *s2vi,
struct vm_area_struct *vma)
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index 2840053ef2f4..6a4f35067642 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -446,9 +446,8 @@ static int __pkvm_pgtable_stage2_reclaim(struct kvm_pgtable *pgt, u64 start, u64
continue;
page = pfn_to_page(mapping->pfn);
- WARN_ON_ONCE(mapping->nr_pages != 1);
unpin_user_pages_dirty_lock(&page, 1, true);
- account_locked_vm(kvm->mm, 1, false);
+ account_locked_vm(kvm->mm, mapping->nr_pages, false);
pkvm_mapping_remove(mapping, &pgt->pkvm_mappings);
kfree(mapping);
}
@@ -513,17 +512,23 @@ int pkvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size,
u64 end = addr + size;
int ret;
+ if (WARN_ON_ONCE(size != PAGE_SIZE && size != PMD_SIZE))
+ return -EINVAL;
+
lockdep_assert_held_write(&kvm->mmu_lock);
mapping = pkvm_mapping_iter_first(&pgt->pkvm_mappings, addr, end - 1);
if (kvm_vm_is_protected(kvm)) {
- /* Protected VMs are mapped using RWX page-granular mappings */
- if (WARN_ON_ONCE(size != PAGE_SIZE))
- return -EINVAL;
-
if (WARN_ON_ONCE(prot != KVM_PGTABLE_PROT_RWX))
return -EINVAL;
+ /*
+ * If a huge mapping overlaps an existing PAGE_SIZE one,
+ * then the VMM has played games with the stage-1. Abort.
+ */
+ if (WARN_ON_ONCE(mapping && mapping->nr_pages == 1 && size > PAGE_SIZE))
+ return -EFAULT;
+
/*
* We either raced with another vCPU or the guest PTE
* has been poisoned by an erroneous host access.
@@ -533,10 +538,8 @@ int pkvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size,
return ret ? -EFAULT : -EAGAIN;
}
- ret = kvm_call_hyp_nvhe(__pkvm_host_donate_guest, pfn, gfn, 1);
+ ret = kvm_call_hyp_nvhe(__pkvm_host_donate_guest, pfn, gfn, size / PAGE_SIZE);
} else {
- if (WARN_ON_ONCE(size != PAGE_SIZE && size != PMD_SIZE))
- return -EINVAL;
/*
* We either raced with another vCPU or we're changing between
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply related [flat|nested] 23+ messages in thread