From: Sebastian Ene <sebastianene@google.com>
To: catalin.marinas@arm.com, fuad.tabba@linux.dev,
joey.gouly@arm.com, mark.rutland@arm.com, maz@kernel.org,
oupton@kernel.org, rananta@google.com, Sascha.Bischoff@arm.com,
suzuki.poulose@arm.com, will@kernel.org
Cc: kvmarm@lists.linux.dev, android-kvm@google.com,
bgrzesik@google.com, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, nathan@kernel.org,
perlarsen@google.com, sebastianene@google.com,
seiden@linux.ibm.com, smostafa@google.com, tglx@kernel.org,
vdonnefort@google.com, vladimir.murzin@arm.com,
yuzenghui@huawei.com, zenghui.yu@linux.dev
Subject: [PATCH v2 01/13] KVM: arm64: Donate MMIO to the hypervisor
Date: Fri, 7 Aug 2026 16:43:11 +0000 [thread overview]
Message-ID: <20260807164322.2970811-3-sebastianene@google.com> (raw)
In-Reply-To: <20260807164322.2970811-2-sebastianene@google.com>
From: Mostafa Saleh <smostafa@google.com>
Extend the pKVM API to allow the donation of MMIO from the host
address space to the hypervisor linear map.
Initialize the host s2 page table with an invalid leaf with the owner ID
of the hypervisor to prevent the host from mapping the page on faults.
Prevent kvm_pgtable_stage2_unmap() from removing owner ID from
stage-2 PTEs, as this can be triggered from recycle logic under memory
pressure.
Signed-off-by: Mostafa Saleh <smostafa@google.com>
Signed-off-by: Sebastian Ene <sebastianene@google.com>
---
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 7 +
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 137 +++++++++++++++++-
arch/arm64/kvm/hyp/pgtable.c | 11 +-
3 files changed, 148 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
index 29935c7da1de..6aa83b129e61 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
@@ -36,6 +36,13 @@ 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_donate_hyp(u64 pfn, u64 nr_pages);
+/*
+ * Donate MMIO range to the hypervisor, it will be mapped in the hypervisor's
+ * linea map and unmapped from the host stage-2.
+ */
+int __pkvm_host_donate_hyp_mmio(phys_addr_t addr, size_t size);
+/* Remaps MMIO range in the host, typically used in error path. */
+int __pkvm_hyp_donate_host_mmio(phys_addr_t addr, size_t size);
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);
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 4e329e39a695..5cf7c4a0ed20 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -378,7 +378,11 @@ static int host_stage2_unmap_dev_all(void)
u64 addr = 0;
int i, ret;
- /* Unmap all non-memory regions to recycle the pages */
+ /*
+ * Unmap all non-memory regions to recycle the pages.
+ * That relies on kvm_pgtable_stage2_unmap() not clearing
+ * counted PTEs which include hypervisor MMIO.
+ */
for (i = 0; i < hyp_memblock_nr; i++, addr = reg->base + reg->size) {
reg = &hyp_memory[i];
ret = kvm_pgtable_stage2_unmap(pgt, addr, reg->base - addr);
@@ -1119,6 +1123,137 @@ int __pkvm_host_donate_hyp(u64 pfn, u64 nr_pages)
return ret;
}
+int __pkvm_host_donate_hyp_mmio(phys_addr_t addr, size_t size)
+{
+ kvm_pte_t pte;
+ u64 offset;
+ void *virt;
+ int ret;
+
+ /* Only before de-privilege. */
+ if (static_branch_unlikely(&kvm_protected_mode_initialized))
+ return -EPERM;
+
+ if (!PAGE_ALIGNED(addr | size) ||
+ !pfn_range_is_valid(hyp_phys_to_pfn(addr), size >> PAGE_SHIFT))
+ return -EINVAL;
+
+ host_lock_component();
+ hyp_lock_component();
+
+ for (offset = 0; offset < size; offset += PAGE_SIZE) {
+ if (addr_is_memory(addr + offset)) {
+ ret = -EINVAL;
+ goto err_with_mapping;
+ }
+
+ ret = kvm_pgtable_get_leaf(&host_mmu.pgt, addr + offset, &pte, NULL);
+ if (ret)
+ goto err_with_mapping;
+
+ if (pte && !kvm_pte_valid(pte)) {
+ ret = -EPERM;
+ goto err_with_mapping;
+ }
+
+ virt = __hyp_va(addr + offset);
+ ret = kvm_pgtable_get_leaf(&pkvm_pgtable, (u64)virt, &pte, NULL);
+ if (ret)
+ goto err_with_mapping;
+ if (pte) {
+ ret = -EBUSY;
+ goto err_with_mapping;
+ }
+
+ ret = pkvm_create_mappings_locked(virt, virt + PAGE_SIZE, PAGE_HYP_DEVICE);
+ if (ret)
+ goto err_with_mapping;
+ }
+
+ /*
+ * We set HYP as the owner of the MMIO pages in the host stage-2, for:
+ * - host aborts: host_stage2_adjust_range() would fail for invalid non zero PTEs.
+ * - recycle under memory pressure: host_stage2_unmap_dev_all() would call
+ * kvm_pgtable_stage2_unmap() which will not clear non zero invalid ptes (counted).
+ * - other MMIO donation: Would fail as we check that the PTE is valid or empty.
+ */
+ ret = host_stage2_try(kvm_pgtable_stage2_annotate, &host_mmu.pgt,
+ addr, size, &host_s2_pool,
+ KVM_HOST_INVALID_PTE_TYPE_DONATION,
+ FIELD_PREP(KVM_HOST_DONATION_PTE_OWNER_MASK, PKVM_ID_HYP));
+ if (ret)
+ goto err_with_mapping;
+unlock:
+ hyp_unlock_component();
+ host_unlock_component();
+ return ret;
+err_with_mapping:
+ if (!offset)
+ goto unlock;
+
+ while (offset) {
+ offset -= PAGE_SIZE;
+ virt = __hyp_va(addr + offset);
+ WARN_ON(kvm_pgtable_hyp_unmap(&pkvm_pgtable, (u64)virt, PAGE_SIZE) != PAGE_SIZE);
+ }
+ goto unlock;
+}
+
+int __pkvm_hyp_donate_host_mmio(phys_addr_t addr, size_t size)
+{
+ kvm_pte_t pte;
+ u64 offset;
+ int ret = 0;
+ void *virt;
+
+ if (static_branch_unlikely(&kvm_protected_mode_initialized))
+ return -EPERM;
+
+ if (!PAGE_ALIGNED(addr | size) ||
+ !pfn_range_is_valid(hyp_phys_to_pfn(addr), size >> PAGE_SHIFT))
+ return -EINVAL;
+
+ host_lock_component();
+ hyp_lock_component();
+
+ for (offset = 0; offset < size; offset += PAGE_SIZE) {
+ if (addr_is_memory(addr + offset)) {
+ ret = -EINVAL;
+ goto err_with_unmap;
+ }
+ ret = kvm_pgtable_get_leaf(&host_mmu.pgt, addr + offset, &pte, NULL);
+ if (ret)
+ goto err_with_unmap;
+ if (!pte || kvm_pte_valid(pte)) {
+ ret = -EINVAL;
+ goto err_with_unmap;
+ }
+ if (FIELD_GET(KVM_HOST_DONATION_PTE_OWNER_MASK, pte) != PKVM_ID_HYP) {
+ ret = -EPERM;
+ goto err_with_unmap;
+ }
+
+ virt = __hyp_va(addr + offset);
+ if (kvm_pgtable_hyp_unmap(&pkvm_pgtable, (u64)virt, PAGE_SIZE) != PAGE_SIZE)
+ goto err_with_unmap;
+ }
+ WARN_ON(host_stage2_idmap_locked(addr, size, PKVM_HOST_MMIO_PROT));
+unlock:
+ hyp_unlock_component();
+ host_unlock_component();
+ return ret;
+err_with_unmap:
+ if (!offset)
+ goto unlock;
+
+ while (offset) {
+ offset -= PAGE_SIZE;
+ virt = __hyp_va(addr + offset);
+ WARN_ON(pkvm_create_mappings_locked(virt, virt + PAGE_SIZE, PAGE_HYP_DEVICE));
+ }
+ goto unlock;
+}
+
int __pkvm_hyp_donate_host(u64 pfn, u64 nr_pages)
{
u64 phys = hyp_pfn_to_phys(pfn);
diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
index b74dd5ce1efd..7638213bd893 100644
--- a/arch/arm64/kvm/hyp/pgtable.c
+++ b/arch/arm64/kvm/hyp/pgtable.c
@@ -1161,13 +1161,12 @@ static int stage2_unmap_walker(const struct kvm_pgtable_visit_ctx *ctx,
kvm_pte_t *childp = NULL;
bool need_flush = false;
- if (!kvm_pte_valid(ctx->old)) {
- if (stage2_pte_is_counted(ctx->old)) {
- kvm_clear_pte(ctx->ptep);
- mm_ops->put_page(ctx->ptep);
- }
+ /*
+ * That also ignores stage2_pte_is_counted() instead of clearing
+ * the PTE as the MMIO can be owned by the hypervisor.
+ */
+ if (!kvm_pte_valid(ctx->old))
return 0;
- }
if (kvm_pte_table(ctx->old, ctx->level)) {
childp = kvm_pte_follow(ctx->old, mm_ops);
--
2.55.0.654.g21b8a5bc05-goog
next prev parent reply other threads:[~2026-08-07 16:43 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-07 16:43 [PATCH v2 00/13] KVM: ITS hardening for pKVM Sebastian Ene
2026-08-07 16:43 ` Sebastian Ene [this message]
2026-08-07 16:43 ` [PATCH v2 02/13] KVM: arm64: Track host-unmapped MMIO regions in a static array Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 03/13] KVM: arm64: Support host MMIO trap handlers for unmapped devices Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 04/13] KVM: Parse the device tree and register the ITS region with pKVM Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 05/13] irqchip/gic-v3-its: Add support for the ITS emulation setup Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 06/13] KVM: arm64: Shadow the ITS command queue and setup emulation Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 07/13] KVM: arm64: Restrict host access to the private ITS tables Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 08/13] KVM: arm64: Trap & emulate the ITS MAPD command Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 09/13] KVM: arm64: Trap & emulate the ITS MAPC command Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 10/13] KVM: arm64: Restrict host updates to GITS_CTLR Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 11/13] KVM: arm64: Prevent the host from specifying a different command queue Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 12/13] KVM: arm64: Prevent the host from programming new GITS_BASER tables Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 13/13] KVM: arm64: Implement HVC interface for ITS emulation setup Sebastian Ene
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260807164322.2970811-3-sebastianene@google.com \
--to=sebastianene@google.com \
--cc=Sascha.Bischoff@arm.com \
--cc=android-kvm@google.com \
--cc=bgrzesik@google.com \
--cc=catalin.marinas@arm.com \
--cc=fuad.tabba@linux.dev \
--cc=joey.gouly@arm.com \
--cc=kvmarm@lists.linux.dev \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=maz@kernel.org \
--cc=nathan@kernel.org \
--cc=oupton@kernel.org \
--cc=perlarsen@google.com \
--cc=rananta@google.com \
--cc=seiden@linux.ibm.com \
--cc=smostafa@google.com \
--cc=suzuki.poulose@arm.com \
--cc=tglx@kernel.org \
--cc=vdonnefort@google.com \
--cc=vladimir.murzin@arm.com \
--cc=will@kernel.org \
--cc=yuzenghui@huawei.com \
--cc=zenghui.yu@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox