From nobody Sat Jul 25 05:22:16 2026 Received: from out-170.mta1.migadu.com (out-170.mta1.migadu.com [95.215.58.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 959D73FD123 for ; Fri, 17 Jul 2026 13:04:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293444; cv=none; b=L4n6LqvhY3ZUWTKclbDcHvUiwHzcc8SZDuXer31TnnYyF0KYQNRorRnYTkyagAK8ZcKvMn7HQWDdTyCHacvqMZF3Vo9KuxDxu6u/V8YzIhBFwRe/PVo0DhwiJjU/8YR5FloCWbLGGRqsvr7hCxu+oVATW5fVrdNu+8e7AFzEefg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293444; c=relaxed/simple; bh=6NkZvmG5WlRjKaf9vo/rU0FXUSyATvE4PlCrYHNBIoo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=GxGkQb1ABVCzuRcZ7ijffJ4FrSE4SkQYvJa+PIJnEsBXAqhMi3S5l6Hf6TXZYwZhlo/q4kyRSpyu67643eds3FsSvHl4r335AEVHOgff3QSK47uaziVsNWblIgkA+tD7lZi/8ZR6UchITZxtwGtQy24kzTaSefCc1rKJWRc/oZk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=bTt1bC3t; arc=none smtp.client-ip=95.215.58.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="bTt1bC3t" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1784293440; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=nGVeKTTWAc9E9IUshZYvJfmhVmD43mhNRriYPRWPSws=; b=bTt1bC3tHPWxJo2efKJyKDuIt3XSi86C7GZICKn3qXE5I7cB/GAaA8VjpnZFgwl6xjOAZV uRDQ2pj768cQ0u8loDjVpiWbLRxhY9pu8D90pYSXaPs34u49hU7AY2tb5I4gW/zMuCATcB IvUxUdpzsAJoVi65gSAKgaNOobkCcfg= From: Fuad Tabba To: Marc Zyngier , Oliver Upton Cc: Joey Gouly , Steffen Eiden , Suzuki K Poulose , Zenghui Yu , Catalin Marinas , Will Deacon , Shuah Khan , Quentin Perret , Vincent Donnefort , Alexandru Elisei , Gavin Shan , Dev Jain , Bradley Morgan , Fuad Tabba , linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev, linux-kernel@vger.kernel.org Subject: [PATCH v5 1/7] KVM: arm64: Skip cache maintenance for non-cacheable pKVM mappings Date: Fri, 17 Jul 2026 14:03:11 +0100 Message-Id: <20260717130317.1953574-2-fuad.tabba@linux.dev> In-Reply-To: <20260717130317.1953574-1-fuad.tabba@linux.dev> References: <20260717130317.1953574-1-fuad.tabba@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" From: Bradley Morgan The pKVM flush path walks its own pkvm_mappings list and cleans the data cache for every mapping, unlike the generic stage-2 walker it shadows, which skips non-cacheable leaves. Cleaning the cacheable alias of a non-cacheable mapping is pointless and can corrupt a device endpoint. Record whether a mapping is non-cacheable in spare bits of nr_pages and skip cache maintenance for it. Fixes: e912efed485a ("KVM: arm64: Introduce the EL1 pKVM MMU") Suggested-by: Marc Zyngier Signed-off-by: Bradley Morgan [tabba: use Marc's anonymous bitfield in place of the open-coded mask and h= elpers] Signed-off-by: Fuad Tabba Tested-by: Bradley Morgan # On pixel 7, Android 17 CP2A= .260705.006 --- arch/arm64/include/asm/kvm_pkvm.h | 5 ++++- arch/arm64/kvm/pkvm.c | 15 +++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm= _pkvm.h index 74fedd9c5ff0..57afb07d6b13 100644 --- a/arch/arm64/include/asm/kvm_pkvm.h +++ b/arch/arm64/include/asm/kvm_pkvm.h @@ -195,7 +195,10 @@ struct pkvm_mapping { struct rb_node node; u64 gfn; u64 pfn; - u64 nr_pages; + struct { + u64 nr_pages:48; + u64 nc:1; + }; u64 __subtree_last; /* Internal member for interval tree */ }; =20 diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c index 053e4f733e4b..f70c601dcf4c 100644 --- a/arch/arm64/kvm/pkvm.c +++ b/arch/arm64/kvm/pkvm.c @@ -369,7 +369,7 @@ static int __pkvm_pgtable_stage2_unshare(struct kvm_pgt= able *pgt, u64 start, u64 =20 for_each_mapping_in_range_safe(pgt, start, end, mapping) { ret =3D kvm_call_hyp_nvhe(__pkvm_host_unshare_guest, handle, mapping->gf= n, - mapping->nr_pages); + (u64)mapping->nr_pages); if (WARN_ON(ret)) return ret; pkvm_mapping_remove(mapping, &pgt->pkvm_mappings); @@ -473,6 +473,7 @@ int pkvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u6= 4 addr, u64 size, mapping->gfn =3D gfn; mapping->pfn =3D pfn; mapping->nr_pages =3D size / PAGE_SIZE; + mapping->nc =3D !!(prot & (KVM_PGTABLE_PROT_DEVICE | KVM_PGTABLE_PROT_NOR= MAL_NC)); pkvm_mapping_insert(mapping, &pgt->pkvm_mappings); =20 return ret; @@ -503,7 +504,7 @@ int pkvm_pgtable_stage2_wrprotect(struct kvm_pgtable *p= gt, u64 addr, u64 size) lockdep_assert_held(&kvm->mmu_lock); for_each_mapping_in_range_safe(pgt, addr, addr + size, mapping) { ret =3D kvm_call_hyp_nvhe(__pkvm_host_wrprotect_guest, handle, mapping->= gfn, - mapping->nr_pages); + (u64)mapping->nr_pages); if (WARN_ON(ret)) break; } @@ -517,9 +518,11 @@ int pkvm_pgtable_stage2_flush(struct kvm_pgtable *pgt,= u64 addr, u64 size) struct pkvm_mapping *mapping; =20 lockdep_assert_held(&kvm->mmu_lock); - for_each_mapping_in_range_safe(pgt, addr, addr + size, mapping) - __clean_dcache_guest_page(pfn_to_kaddr(mapping->pfn), - PAGE_SIZE * mapping->nr_pages); + for_each_mapping_in_range_safe(pgt, addr, addr + size, mapping) { + if (!mapping->nc) + __clean_dcache_guest_page(pfn_to_kaddr(mapping->pfn), + PAGE_SIZE * mapping->nr_pages); + } =20 return 0; } @@ -537,7 +540,7 @@ bool pkvm_pgtable_stage2_test_clear_young(struct kvm_pg= table *pgt, u64 addr, u64 lockdep_assert_held(&kvm->mmu_lock); for_each_mapping_in_range_safe(pgt, addr, addr + size, mapping) young |=3D kvm_call_hyp_nvhe(__pkvm_host_test_clear_young_guest, handle,= mapping->gfn, - mapping->nr_pages, mkold); + (u64)mapping->nr_pages, mkold); =20 return young; } --=20 2.39.5 From nobody Sat Jul 25 05:22:16 2026 Received: from out-178.mta1.migadu.com (out-178.mta1.migadu.com [95.215.58.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AB7B83FD96B for ; Fri, 17 Jul 2026 13:04:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293447; cv=none; b=LhG7m2+Da2y9hYFjlLbNk9e22q8UyHI9sn+yFxVEzeKeysyzJ/2BqoF5Jync1+wMQNp0HPiYypabuF3OetglSzgqQhqlq30d9o6ouiVfz4V7B5W3MGaGZuzfrBbVBmh84HRINipURRvxRMhfUWefFfR4rgArZnbcF8HTRaIXupY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293447; c=relaxed/simple; bh=ixcN7TojDaxCxxawPFqmnn7W+Nnfy5nReo+h5Jaceho=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=QkCb7OAo4vdH3n44rEujsA6veWjsu4deags0XH7Fk1VzAAug6PllRmNKoGaMFgNxArEk14ez8ptZVAlMuZZpuM5Ch3J+E5CO5DINCSf/UBXOI5s7lv1Kte7sxlhZn1SntDz4y/CDUcJP35RzBIfgCbNIZOTwpGXwseropt1F4Rg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=qIpcuxTa; arc=none smtp.client-ip=95.215.58.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="qIpcuxTa" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1784293443; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=vQfbr1Pd7lVsaw2RzzlCdtntUUL+zBQG5yeNfbLMXec=; b=qIpcuxTaDqo6sQz8/i5CXU/dpCr2ttdwiMxdMha9Lg5rEajvtQvQ6WJQLiBnoEKtAH4r7x dAuyhQeNzDPdcY5tBqHqiO1JocEGtwmQAE8aRj4BFUrDpLm16+GHKtAp9kneAPxFrz2ZrV IVPaeeXa8W4oLN0N2Fkg11vbF6KdKss= From: Fuad Tabba To: Marc Zyngier , Oliver Upton Cc: Joey Gouly , Steffen Eiden , Suzuki K Poulose , Zenghui Yu , Catalin Marinas , Will Deacon , Shuah Khan , Quentin Perret , Vincent Donnefort , Alexandru Elisei , Gavin Shan , Dev Jain , Bradley Morgan , Fuad Tabba , linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev, linux-kernel@vger.kernel.org Subject: [PATCH v5 2/7] KVM: arm64: Top up the memcache for pKVM permission faults Date: Fri, 17 Jul 2026 14:03:12 +0100 Message-Id: <20260717130317.1953574-3-fuad.tabba@linux.dev> In-Reply-To: <20260717130317.1953574-1-fuad.tabba@linux.dev> References: <20260717130317.1953574-1-fuad.tabba@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" A permission fault normally only relaxes a leaf, so user_mem_abort() skips the memcache top-up. Under pKVM such a fault can instead collapse pages into a block. That needs a fresh pkvm_mapping object, and without it cache->mapping is NULL, so pkvm_pgtable_stage2_map() dereferences NULL and faults the host under mmu_lock. Staging only the object is not enough: the hypervisor requires kvm_mmu_cache_min_pages in the memcache even for the allocation-free install, so under memcache pressure the collapse returns -ENOMEM and trips the WARN_ON(ret) in pkvm_pgtable_stage2_map() where a non-pKVM guest succeeds. Top up the full memcache for pKVM permission faults so both the mapping object and the min-pages are staged before mmu_lock. Fixes: db14091d8f75 ("KVM: arm64: Stage-2 huge mappings for np-guests") Reported-by: Bradley Morgan Link: https://lore.kernel.org/all/20260623161545.EA08E1F000E9@smtp.kernel.o= rg/ Signed-off-by: Fuad Tabba Tested-by: Bradley Morgan # On pixel 7, Android 17 CP2A= .260705.006 --- arch/arm64/kvm/mmu.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 6c941aaa10c6..4d7c9bdcef00 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -2114,10 +2114,14 @@ static int user_mem_abort(const struct kvm_s2_fault= _desc *s2fd) * and so normally don't require allocations from the memcache. The * only exception to this is when dirty logging is enabled at runtime * and a write fault needs to collapse a block entry into a table. + * Under pKVM a permission fault can also collapse pages into a block, + * which needs a fresh mapping object, and the hypervisor requires the + * min-pages memcache even when the install allocates nothing. */ memcache =3D get_mmu_memcache(s2fd->vcpu); if (!perm_fault || (memslot_is_logging(s2fd->memslot) && - kvm_is_write_fault(s2fd->vcpu))) { + kvm_is_write_fault(s2fd->vcpu)) || + is_protected_kvm_enabled()) { ret =3D topup_mmu_memcache(s2fd->vcpu, memcache); if (ret) return ret; --=20 2.39.5 From nobody Sat Jul 25 05:22:16 2026 Received: from out-172.mta1.migadu.com (out-172.mta1.migadu.com [95.215.58.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1A6563FDBE5 for ; Fri, 17 Jul 2026 13:04:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293449; cv=none; b=CFCTN1KMXhchQahdoZMUGYoYtRV+1MtJXp+kQVdbgeEdQ8sW9wKeH1Y4yT1PDlMCjN5Hj1GoTRMJmF7YuEZldGJ78v4mnzb4BjTpXm+gpTeK/yxp7PAWoRo7UFFRzwOStRaRsjCOiSF5y2FIZ2GTUXMK1db0E2OI3Fqjdw6oIeE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293449; c=relaxed/simple; bh=cHaqF91J8k5fNTX15TBR8HD4DACMPbXOC77gjnpK65Q=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=rCVfbfzWcFmVFyJxr+XyH6ZtF2q/k3B1UGbje0m/9ba9dp2TNpmLH8UMtRnhHYpBpF8BX2Pwihykjd8gwodILU1EGr1FWPdmjEj2wvppkVSjCHk/JP6wQVOXwTjl6AXiMeAcsVbwjDqS7XxyMo59uqqy0oCLbEPSfFHG8iWslX4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=EIdNAbav; arc=none smtp.client-ip=95.215.58.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="EIdNAbav" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1784293445; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Tm/R+8+SeI3EIiHJjFKMzh728Ajegh5x45TsNe4aXWo=; b=EIdNAbavmrb8hsL8oklMYZ0FDv2qjouLDJOhckDFY72MVYlnR9MO5TvlRGezc+9Twi9Pt9 YC/3unfpE2iBbjMSUznTLLfeaxOrdchIdRAwZ/jEPCT2sJglIgA1PD+1iZm79bOGCVWJP6 FAckyQGF/bhUZmvs7toO5zz0XFrCCNI= From: Fuad Tabba To: Marc Zyngier , Oliver Upton Cc: Joey Gouly , Steffen Eiden , Suzuki K Poulose , Zenghui Yu , Catalin Marinas , Will Deacon , Shuah Khan , Quentin Perret , Vincent Donnefort , Alexandru Elisei , Gavin Shan , Dev Jain , Bradley Morgan , Fuad Tabba , linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev, linux-kernel@vger.kernel.org Subject: [PATCH v5 3/7] KVM: arm64: Top up stage-2 memcache for dirty logging faults Date: Fri, 17 Jul 2026 14:03:13 +0100 Message-Id: <20260717130317.1953574-4-fuad.tabba@linux.dev> In-Reply-To: <20260717130317.1953574-1-fuad.tabba@linux.dev> References: <20260717130317.1953574-1-fuad.tabba@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" From: Bradley Morgan Dirty logging forces new stage-2 mappings to page size but does not always split an existing block first (eager splitting is best effort and off by default). A non-write permission fault on such a block, an instruction fetch, still needs a page-table page to split it, but the top-up is gated on write faults. With the cache empty, kvm_mmu_memory_cache_alloc() hits its guest-triggerable WARN_ON(!nobjs) and falls back to a GFP_ATOMIC allocation under mmu_lock, with a BUG_ON() if that fails. Top up the memcache for any permission fault while dirty logging is active. Fixes: 6f745f1bb5bf ("KVM: arm64: Convert user_mem_abort() to generic page-= table API") Link: https://lore.kernel.org/all/20260623165634.699011F000E9@smtp.kernel.o= rg/ Signed-off-by: Bradley Morgan [tabba: reword the commit message for the generic, non-pKVM failure mode] Signed-off-by: Fuad Tabba Tested-by: Bradley Morgan # On pixel 7, Android 17 CP2A= .260705.006 --- arch/arm64/kvm/mmu.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 4d7c9bdcef00..74e7e7f7564c 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -2113,14 +2113,13 @@ static int user_mem_abort(const struct kvm_s2_fault= _desc *s2fd) * Permission faults just need to update the existing leaf entry, * and so normally don't require allocations from the memcache. The * only exception to this is when dirty logging is enabled at runtime - * and a write fault needs to collapse a block entry into a table. + * and a fault needs to collapse a block entry into a table. * Under pKVM a permission fault can also collapse pages into a block, * which needs a fresh mapping object, and the hypervisor requires the * min-pages memcache even when the install allocates nothing. */ memcache =3D get_mmu_memcache(s2fd->vcpu); - if (!perm_fault || (memslot_is_logging(s2fd->memslot) && - kvm_is_write_fault(s2fd->vcpu)) || + if (!perm_fault || memslot_is_logging(s2fd->memslot) || is_protected_kvm_enabled()) { ret =3D topup_mmu_memcache(s2fd->vcpu, memcache); if (ret) --=20 2.39.5 From nobody Sat Jul 25 05:22:16 2026 Received: from out-173.mta1.migadu.com (out-173.mta1.migadu.com [95.215.58.173]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 274BB3FD13F for ; Fri, 17 Jul 2026 13:04:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.173 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293451; cv=none; b=XYaFeKZjY648PVXbYblM404j07wi2jWpPVzjw5t1wfcwHRDhv0hA/YBtdzs4QrGbU0uxf9FuZL0yrtVN6QQF4mefuXq1JfbNEFxilzri4p3CzguQHF8GR/sZAFhE8sPZH5kQsHWHPfw9w8mFeNT3pYx7QTLo7wMvcit+KOb8yNU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293451; c=relaxed/simple; bh=jmsewc0sWd3StwFaO0x1Igk4O5irp82T8pqNrTcmEQo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=olQZ8QhbhFKmmZA5jK793m9Y2/sqzHgWSt1GB0EbMZ9GoetIhJ9qBm4McQa/+VnBganQlmozFx0oU3DOyiDvSjkO+pOgZoo9X9gpylA91z5+bAWP6aCV2AnlfDHCMPoLp7gL/Z7vv5uhnqyTfbwggqtwc/sczcTvP0ydq5vCmCw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=vqChw9zc; arc=none smtp.client-ip=95.215.58.173 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="vqChw9zc" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1784293447; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=CI1v8eeCBLFTdJ/DMxS17nabZVLaO47wNFZpGWZf8b4=; b=vqChw9zcOPNplCIkodaAthFKhF9/qHUGIO8HHLx9F8lVMbcLi5gYPYL/7We5a26SbrUd9h qAD/apqCdmgb68x8+zjhhYHf2IA+T/cKcQv/4eXY6msQu4tBdxb8n0OLSvgtVZUBeLurIJ PMoBYcZ8Qpet0gmuNcGgEz7kh/BL75Q= From: Fuad Tabba To: Marc Zyngier , Oliver Upton Cc: Joey Gouly , Steffen Eiden , Suzuki K Poulose , Zenghui Yu , Catalin Marinas , Will Deacon , Shuah Khan , Quentin Perret , Vincent Donnefort , Alexandru Elisei , Gavin Shan , Dev Jain , Bradley Morgan , Fuad Tabba , linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev, linux-kernel@vger.kernel.org Subject: [PATCH v5 4/7] KVM: arm64: Skip pKVM stage-2 flush when FWB is enabled Date: Fri, 17 Jul 2026 14:03:14 +0100 Message-Id: <20260717130317.1953574-5-fuad.tabba@linux.dev> In-Reply-To: <20260717130317.1953574-1-fuad.tabba@linux.dev> References: <20260717130317.1953574-1-fuad.tabba@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" pkvm_pgtable_stage2_flush() cleans the D-cache for every mapping in the range even on hardware with stage-2 Force Write-Back, where FWB keeps guest memory coherent to the PoC and the maintenance is unnecessary. The generic kvm_pgtable_stage2_flush() returns early in that case, but the pKVM MMU does not, so it needlessly cleans the whole range on, e.g., every set/way trap. Return early when FWB is enabled, matching the generic walker. Fixes: e912efed485a ("KVM: arm64: Introduce the EL1 pKVM MMU") Signed-off-by: Fuad Tabba Reviewed-by: Bradley Morgan Tested-by: Bradley Morgan # On pixel 7, Android 17 CP2A= .260705.006 --- arch/arm64/kvm/pkvm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c index f70c601dcf4c..ba6570e37545 100644 --- a/arch/arm64/kvm/pkvm.c +++ b/arch/arm64/kvm/pkvm.c @@ -518,6 +518,10 @@ int pkvm_pgtable_stage2_flush(struct kvm_pgtable *pgt,= u64 addr, u64 size) struct pkvm_mapping *mapping; =20 lockdep_assert_held(&kvm->mmu_lock); + + if (cpus_have_final_cap(ARM64_HAS_STAGE2_FWB)) + return 0; + for_each_mapping_in_range_safe(pgt, addr, addr + size, mapping) { if (!mapping->nc) __clean_dcache_guest_page(pfn_to_kaddr(mapping->pfn), --=20 2.39.5 From nobody Sat Jul 25 05:22:16 2026 Received: from out-188.mta1.migadu.com (out-188.mta1.migadu.com [95.215.58.188]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 024743FE37A for ; Fri, 17 Jul 2026 13:04:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.188 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293453; cv=none; b=tqwINKvk9Ux5rWWO8/HY4RMLySFbgtg3zdFgbS+kqEfNKxR2QNzX3ZxIOlK/WnpFCf8QPCN2j/kohmyBqj72zRtV3W94IjAGJJV4LHkkgJXivRylFijlrH3qBJ8wtFfckPjnxyos24YFhx9cX2fpt6QmF9PgCWJkersME8j0Hw0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293453; c=relaxed/simple; bh=5KMMgT2ETpoNaISeUolCEGWZMQWe/wTHmfcqBE87/tE=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=g4dEfRhGC+0+dplyQmi4KfDm8U+govfwncp3hMZHyZKrA7z3MDxtn3ASf5fLN28hteuKjJTy7wq2/b7RprPyge0rP8dLZcV26JBFDch+urDt32ljqfEahsOTm/Fc/iFT7a7fbl3zlOkFBsL6r/chNJrWalGVwH44b1FZ8iYZmdU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=OdKlhYzO; arc=none smtp.client-ip=95.215.58.188 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="OdKlhYzO" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1784293449; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=t62BgL8MjPLp/vMP9DZbVtXwp29HVKeCqzhIx8Pz0Y8=; b=OdKlhYzO5z90QIvAtZFzdTuaTThgKal3zbR5ENagrtMDYVyYo2JEhmsSEYG61sVy6mDvmo Z0okoOfILrcrBo0mAl6Q3WfUGSPkrvRhx9xRaDkeTjOQoJJt86SMhyH2k0Szb1qdZIWXTy 64WfrqN30uGScUnE1fiVvhfe/YokMHY= From: Fuad Tabba To: Marc Zyngier , Oliver Upton Cc: Joey Gouly , Steffen Eiden , Suzuki K Poulose , Zenghui Yu , Catalin Marinas , Will Deacon , Shuah Khan , Quentin Perret , Vincent Donnefort , Alexandru Elisei , Gavin Shan , Dev Jain , Bradley Morgan , Fuad Tabba , linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev, linux-kernel@vger.kernel.org Subject: [PATCH v5 5/7] KVM: arm64: Don't WARN on pKVM stage-2 map failures Date: Fri, 17 Jul 2026 14:03:15 +0100 Message-Id: <20260717130317.1953574-6-fuad.tabba@linux.dev> In-Reply-To: <20260717130317.1953574-1-fuad.tabba@linux.dev> References: <20260717130317.1953574-1-fuad.tabba@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" pkvm_pgtable_stage2_map() wraps the __pkvm_host_share_guest() and __pkvm_host_donate_guest() return in WARN_ON(), but those hypercalls fail for reasons that are not EL1 invariant violations: -EINVAL for a pfn that is not memblock RAM (check_range_allowed_memory() rejects a device page mapped into a non-protected guest) and -ENOMEM under memcache pressure. Both are reachable from a guest fault, so the WARN splats on host input. Return the error without warning. The unshare and write-protect WARNs stay, since a failure there does signal a broken EL1 invariant. Fixes: 3669ddd8fa8b5 ("KVM: arm64: Add a range to pkvm_mappings") Signed-off-by: Fuad Tabba Reviewed-by: Bradley Morgan Tested-by: Bradley Morgan # On pixel 7, Android 17 CP2A= .260705.006 --- arch/arm64/kvm/pkvm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c index ba6570e37545..1212a2c5613d 100644 --- a/arch/arm64/kvm/pkvm.c +++ b/arch/arm64/kvm/pkvm.c @@ -466,7 +466,7 @@ int pkvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u6= 4 addr, u64 size, size / PAGE_SIZE, prot); } =20 - if (WARN_ON(ret)) + if (ret) return ret; =20 swap(mapping, cache->mapping); --=20 2.39.5 From nobody Sat Jul 25 05:22:16 2026 Received: from out-177.mta1.migadu.com (out-177.mta1.migadu.com [95.215.58.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 18B203FF1B9 for ; Fri, 17 Jul 2026 13:04:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293456; cv=none; b=GAGiU8hON+LYcBTLBjrZC8BpAXmoktQrNFBJVowNpjW65NVkVOEq8fy/OkU728orAXYIzIqmMaQOXOxv+3fbwLdpEr35ryR2ahOqlmgEdpdHz4qvUYtcDXQvv4MsLaOMyWTA1dZWe9dW22eeIqTY1H0ho8AFAoN0GwtK3m4SNvQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293456; c=relaxed/simple; bh=/dJq1ElgkuWD4WVTYnRbakmDuhEmWZkPbbAcOrtDOFg=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=dVWfXmwXqnF1p1C1Tqkb96RQPvhTSTVpfGnjIXWGs6rcDckVLLlseytd6mbSGS8L6iWXuQ5O0kn0129pV2CjbQp/Sq/Pe6d3bMRugcwr1jg59RQu/Y3Z9hgaxMdrhc6CoYzunLF01RtNZeC+muEwnMwV9QRq1HVz2AyiDq94YQQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=vTqZQVcv; arc=none smtp.client-ip=95.215.58.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="vTqZQVcv" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1784293451; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=w6Awn3Cip6WinOaXp4OqAmFuhAYE4A4acDGbfEewbog=; b=vTqZQVcvnbqTera6vR3ctDXJn/somZz5PfLLM0khrASOMvrxrDjGa+jgm1rPclTNFJ9VHZ W4pJeIuM2LskrdT4zeBAcXZHQUPnlN5eiS2bHHkWZJaBd/S03Nu+a9kOjG2hURa0tnI5vo 3fnSaxpDtapXRTv/t+dkvxH3HOBBF4I= From: Fuad Tabba To: Marc Zyngier , Oliver Upton Cc: Joey Gouly , Steffen Eiden , Suzuki K Poulose , Zenghui Yu , Catalin Marinas , Will Deacon , Shuah Khan , Quentin Perret , Vincent Donnefort , Alexandru Elisei , Gavin Shan , Dev Jain , Bradley Morgan , Fuad Tabba , linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev, linux-kernel@vger.kernel.org Subject: [PATCH v5 6/7] KVM: arm64: Don't advertise eager page splitting under pKVM Date: Fri, 17 Jul 2026 14:03:16 +0100 Message-Id: <20260717130317.1953574-7-fuad.tabba@linux.dev> In-Reply-To: <20260717130317.1953574-1-fuad.tabba@linux.dev> References: <20260717130317.1953574-1-fuad.tabba@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" Under pKVM the stage-2 walker resolves to pkvm_pgtable_stage2_split(), a WARN_ON_ONCE(1) stub, yet KVM_CAP_ARM_EAGER_SPLIT_CHUNK_SIZE is still enabled and reported for non-protected guests: the capability check keys on the per-VM protected state while the walker dispatch keys on the host-global mode. Enabling the cap and then dirty-logging the guest reaches the stub, splatting a userspace-reachable WARN. Reject the capability, and stop reporting a chunk size and the supported block sizes, for every VM once pKVM is enabled, keyed on the host-global mode like the split dispatch. Gating only protected VMs would leave the non-protected guests that reach the stub still able to enable it. Userspace decides whether eager splitting is available from the block-size bitmap (QEMU falls back to no eager splitting when it reads 0), so leaving it advertised steers an explicit request into the enable failure instead of the fallback. Fixes: e912efed485a ("KVM: arm64: Introduce the EL1 pKVM MMU") Signed-off-by: Fuad Tabba Reviewed-by: Bradley Morgan Tested-by: Bradley Morgan # On pixel 7, Android 17 CP2A= .260705.006 --- arch/arm64/include/asm/kvm_pkvm.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm= _pkvm.h index 57afb07d6b13..beea00e693a0 100644 --- a/arch/arm64/include/asm/kvm_pkvm.h +++ b/arch/arm64/include/asm/kvm_pkvm.h @@ -45,6 +45,9 @@ static inline bool kvm_pkvm_ext_allowed(struct kvm *kvm, = long ext) return true; case KVM_CAP_ARM_MTE: return false; + case KVM_CAP_ARM_EAGER_SPLIT_CHUNK_SIZE: + case KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES: + return false; default: return !kvm || !kvm_vm_is_protected(kvm); } --=20 2.39.5 From nobody Sat Jul 25 05:22:16 2026 Received: from out-171.mta1.migadu.com (out-171.mta1.migadu.com [95.215.58.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 25FE93FE37A for ; Fri, 17 Jul 2026 13:04:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293457; cv=none; b=ZFg6gUbNtuwEvZg8QPKvxNgKDxBD80+uIwkEybCet2x0DufiNw/7Z5Tsq86zfbdGrRVQqNwu4qSbHCGRMUOfrMoSanhr/uzHF4ByuJtEq+85M0bk/O4l2p3XUejLcMtMAIRmO5MKOheMgW9Szyms5JiftkxKQQLx6yfvV1PmnRc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784293457; c=relaxed/simple; bh=7z5FGVOAfSolooaQgQMq2u00zHDHxGUneImlxsVYphQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=eV4CPnTtvWkCLi3eaYFF48XEY/QW4AKVE/Kz8IbibnO0T/tVTSlVhiIEgLpipmItobS4w6NesQ9blSjq9v7Czb6a844+Mjigd9j7L3DS4ZUvz8101YDr0eRbBD2P/xkOIIPFVl/CD/Th+P9KMRDXgDevqYv9ROpE+uz7PGGhW24= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=IKq3ZohP; arc=none smtp.client-ip=95.215.58.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="IKq3ZohP" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1784293453; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ZSov26SoNzFoDbQjfVuL51TE+LmJwc4x9zfxixe6C8U=; b=IKq3ZohPaJOeF+j9hraLAzAOKhLZYJH53ntijnNYC+nqxylG9cyara1oELMIlARuR++bqL 6czgAMP6YsoasbgSfLMqTB+73C3857pFILaD7ODjjxlUaNvwFlxTlKo0hVqLtj/4T3HvSu CkdEgv6AqtFLpmCCV2ZsvJc1IK5d8ko= From: Fuad Tabba To: Marc Zyngier , Oliver Upton Cc: Joey Gouly , Steffen Eiden , Suzuki K Poulose , Zenghui Yu , Catalin Marinas , Will Deacon , Shuah Khan , Quentin Perret , Vincent Donnefort , Alexandru Elisei , Gavin Shan , Dev Jain , Bradley Morgan , Fuad Tabba , linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev, linux-kernel@vger.kernel.org Subject: [PATCH v5 7/7] KVM: arm64: selftests: Add stage-2 block transition test Date: Fri, 17 Jul 2026 14:03:17 +0100 Message-Id: <20260717130317.1953574-8-fuad.tabba@linux.dev> In-Reply-To: <20260717130317.1953574-1-fuad.tabba@linux.dev> References: <20260717130317.1953574-1-fuad.tabba@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" Add a test for the two stage-2 granularity changes dirty logging forces at fault time, asserting the guest completes with no KVM_RUN error. The first scenario collapses a page into a hugetlb-backed block: it writes under logging, re-write-protects the page via GET_DIRTY_LOG, then writes again with logging off. The second splits blocks: it faults in several non-executable 2M blocks, enables logging, then executes in each block so an execute permission fault splits it. It is skipped when CTR_EL0.DIC is set, since mappings are then executable and no execute fault occurs. Both paths make the fault handler allocate under mmu_lock, so a backend that fails to stage that memory returns a KVM_RUN error or crashes the host. The property holds on any host. On a pKVM host, where a non-protected guest uses the pkvm_pgtable_*() backend, it also guards that backend's fault-time staging. Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Fuad Tabba Tested-by: Bradley Morgan # On QEMU arm64 host Tested-by: Bradley Morgan # On pixel 7, Android 17 CP2A= .260705.006 --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../kvm/arm64/stage2_block_transitions.c | 226 ++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 tools/testing/selftests/kvm/arm64/stage2_block_transiti= ons.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selft= ests/kvm/Makefile.kvm index d28a057fa6c2..ed2877825eee 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -179,6 +179,7 @@ TEST_GEN_PROGS_arm64 +=3D arm64/psci_test TEST_GEN_PROGS_arm64 +=3D arm64/sea_to_user TEST_GEN_PROGS_arm64 +=3D arm64/set_id_regs TEST_GEN_PROGS_arm64 +=3D arm64/smccc_filter +TEST_GEN_PROGS_arm64 +=3D arm64/stage2_block_transitions TEST_GEN_PROGS_arm64 +=3D arm64/vcpu_width_config TEST_GEN_PROGS_arm64 +=3D arm64/vgic_init TEST_GEN_PROGS_arm64 +=3D arm64/vgic_irq diff --git a/tools/testing/selftests/kvm/arm64/stage2_block_transitions.c b= /tools/testing/selftests/kvm/arm64/stage2_block_transitions.c new file mode 100644 index 000000000000..5fd47f4ada1f --- /dev/null +++ b/tools/testing/selftests/kvm/arm64/stage2_block_transitions.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026 Google LLC + * Author: Fuad Tabba + * + * stage2_block_transitions - Exercise stage-2 block/page granularity chan= ges + * that dirty logging forces at fault time, and assert the guest completes. + * + * Both scenarios need the fault handler to allocate at fault time (a fresh + * mapping and/or page-table pages while holding mmu_lock), so a fault path + * that fails to stage that memory manifests as a KVM_RUN error or, worse,= a + * host crash. The asserted property is host-agnostic: the guest runs the + * sequence to completion and every KVM_RUN succeeds. On a pKVM host, wher= e a + * non-protected guest's stage-2 faults are serviced by the pkvm_pgtable_*= () + * backend, the same sequences also guard that backend's fault-time stagin= g. + * + * Scenario 1 - block collapse on dirty-logging disable: + * A write under dirty logging installs a 4K page; GET_DIRTY_LOG + * re-write-protects it; logging is disabled; a second write takes a + * permission fault that collapses the page into a hugetlb-backed block, + * which requires a fresh mapping object under mmu_lock. + * + * Scenario 2 - block split under dirty logging: + * Several hugetlb-backed blocks are faulted in as non-executable blocks, + * dirty logging is enabled (write-protect only), then the guest executes + * into each block. Each instruction fetch takes an execute permission + * fault that must split the block into pages during logging, draining + * page-table pages. Skipped on CTR_EL0.DIC hardware, where mappings are + * made executable eagerly and the execute fault never occurs. + */ +#include +#include +#include +#include +#include + +#include + +#include "kvm_util.h" +#include "processor.h" +#include "test_util.h" +#include "ucall.h" + +#define DATA_SLOT 1 +#define TEST_GVA 0xc0000000UL +#define BLOCK_SIZE SZ_2M + +/* AArch64 "ret" (ret x30): a self-contained, returnable executable payloa= d. */ +#define RET_INSN 0xd65f03c0U + +/* + * A non-protected guest's per-VM stage-2 pool is seeded only with the PGD + * donation, which stage-2 init immediately consumes, so the page-table bu= dget + * for a fault that does not top up is just the handful (~2x the stage-2 m= in + * pages) of memcache leftovers. Executing into this many distinct blocks + * demands far more than that budget: a fault path that tops up on every f= ault + * completes all of them, one that skips non-write faults runs out mid-seq= uence. + */ +#define NR_BLOCKS 16 + +/* Scenario 2 guest -> host sync stages. */ +#define STAGE_SKIP_DIC 1 +#define STAGE_BLOCKS_READY 2 + +static void collapse_guest_code(u64 gva) +{ + u64 *data =3D (u64 *)gva; + + /* Under dirty logging: install a 4K writable page. */ + WRITE_ONCE(*data, 0x1); + GUEST_SYNC(1); + + /* Logging disabled: a permission fault collapses the page into a block. = */ + WRITE_ONCE(*data, 0x2); + GUEST_SYNC(2); + + GUEST_DONE(); +} + +static void test_block_collapse(void) +{ + struct kvm_vcpu *vcpu; + unsigned long *bmap; + struct kvm_vm *vm; + struct ucall uc; + size_t npages; + u64 gpa; + + vm =3D vm_create_with_one_vcpu(&vcpu, collapse_guest_code); + npages =3D BLOCK_SIZE / vm->page_size; + + gpa =3D (vm_compute_max_gfn(vm) * vm->page_size) - BLOCK_SIZE; + gpa =3D align_down(gpa, BLOCK_SIZE); + + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS_HUGETLB_2MB, gpa, + DATA_SLOT, npages, KVM_MEM_LOG_DIRTY_PAGES); + virt_map(vm, TEST_GVA, gpa, npages); + vcpu_args_set(vcpu, 1, TEST_GVA); + + bmap =3D bitmap_zalloc(BLOCK_SIZE / getpagesize()); + + vcpu_run(vcpu); + TEST_ASSERT(get_ucall(vcpu, &uc) =3D=3D UCALL_SYNC && uc.args[1] =3D=3D 1, + "Expected first sync, got cmd %lu arg %lu", uc.cmd, uc.args[1]); + + /* GET_DIRTY_LOG re-write-protects the dirtied page; then stop logging. */ + kvm_vm_get_dirty_log(vm, DATA_SLOT, bmap); + vm_mem_region_set_flags(vm, DATA_SLOT, 0); + + /* The collapsing permission fault: a broken fault path faults here. */ + vcpu_run(vcpu); + TEST_ASSERT(get_ucall(vcpu, &uc) =3D=3D UCALL_SYNC && uc.args[1] =3D=3D 2, + "Expected second sync, got cmd %lu arg %lu", uc.cmd, uc.args[1]); + + vcpu_run(vcpu); + TEST_ASSERT(get_ucall(vcpu, &uc) =3D=3D UCALL_DONE, + "Expected done, got cmd %lu", uc.cmd); + + free(bmap); + kvm_vm_free(vm); +} + +static void guest_sync_insn(u64 va) +{ + /* Make the just-written instruction coherent for execution (!DIC). */ + asm volatile("dc cvau, %0\n" + "dsb ish\n" + "ic ivau, %0\n" + "dsb ish\n" + "isb\n" + :: "r" (va) : "memory"); +} + +static void split_guest_code(u64 base_gva, u64 nblocks) +{ + u64 i, va; + + if (FIELD_GET(CTR_EL0_DIC_MASK, read_sysreg(ctr_el0))) { + GUEST_SYNC(STAGE_SKIP_DIC); + GUEST_DONE(); + return; + } + + /* Fault in each block (non-executable) and stage an executable payload. = */ + for (i =3D 0; i < nblocks; i++) { + va =3D base_gva + i * BLOCK_SIZE; + WRITE_ONCE(*(u32 *)va, RET_INSN); + guest_sync_insn(va); + } + GUEST_SYNC(STAGE_BLOCKS_READY); + + /* Logging is now on: executing into each block splits it into pages. */ + for (i =3D 0; i < nblocks; i++) { + va =3D base_gva + i * BLOCK_SIZE; + ((void (*)(void))va)(); + } + + GUEST_DONE(); +} + +static void test_exec_split_drain(void) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + struct ucall uc; + size_t npages; + u64 gpa; + + vm =3D vm_create_with_one_vcpu(&vcpu, split_guest_code); + npages =3D NR_BLOCKS * (BLOCK_SIZE / vm->page_size); + + gpa =3D (vm_compute_max_gfn(vm) * vm->page_size) - NR_BLOCKS * BLOCK_SIZE; + gpa =3D align_down(gpa, BLOCK_SIZE); + + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS_HUGETLB_2MB, gpa, + DATA_SLOT, npages, 0); + virt_map(vm, TEST_GVA, gpa, npages); + vcpu_args_set(vcpu, 2, TEST_GVA, (u64)NR_BLOCKS); + + vcpu_run(vcpu); + TEST_ASSERT(get_ucall(vcpu, &uc) =3D=3D UCALL_SYNC, + "Expected sync, got cmd %lu", uc.cmd); + if (uc.args[1] =3D=3D STAGE_SKIP_DIC) { + ksft_print_msg("SKIP block split: CTR_EL0.DIC =3D=3D 1\n"); + kvm_vm_free(vm); + return; + } + TEST_ASSERT(uc.args[1] =3D=3D STAGE_BLOCKS_READY, + "Expected blocks-ready sync, got arg %lu", uc.args[1]); + + /* Write-protect the blocks; the guest then splits them by executing. */ + vm_mem_region_set_flags(vm, DATA_SLOT, KVM_MEM_LOG_DIRTY_PAGES); + + vcpu_run(vcpu); + TEST_ASSERT(get_ucall(vcpu, &uc) =3D=3D UCALL_DONE, + "Expected done, got cmd %lu", uc.cmd); + + kvm_vm_free(vm); +} + +/* + * The explicit-size hugetlb backing hard-fails region creation if the pag= es + * are not already reserved, so probe here and skip rather than abort. The + * peak reservation is scenario 2's; the two scenarios run and free in tur= n. + */ +static void require_hugepages(size_t bytes) +{ + void *mem =3D mmap(NULL, bytes, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_HUGE_2MB, + -1, 0); + + if (mem =3D=3D MAP_FAILED) + ksft_exit_skip("Need %zu bytes of reserved 2M hugepages\n", bytes); + munmap(mem, bytes); +} + +int main(void) +{ + require_hugepages(NR_BLOCKS * BLOCK_SIZE); + + test_block_collapse(); + test_exec_split_drain(); + + ksft_print_msg("All ok!\n"); + return 0; +} --=20 2.39.5