From: Mostafa Saleh <smostafa@google.com>
To: linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, kvmarm@lists.linux.dev,
iommu@lists.linux.dev
Cc: catalin.marinas@arm.com, will@kernel.org, maz@kernel.org,
oliver.upton@linux.dev, joey.gouly@arm.com,
suzuki.poulose@arm.com, yuzenghui@huawei.com, joro@8bytes.org,
jgg@ziepe.ca, mark.rutland@arm.com, qperret@google.com,
tabba@google.com, vdonnefort@google.com, nicolinc@nvidia.com,
Mostafa Saleh <smostafa@google.com>
Subject: [PATCH v9 07/25] KVM: arm64: iommu: Shadow host stage-2 page table
Date: Thu, 15 Oct 2026 20:08:38 +0000 [thread overview]
Message-ID: <20261015200903.2735565-8-smostafa@google.com> (raw)
In-Reply-To: <20261015200903.2735565-1-smostafa@google.com>
Create a page table for the IOMMU that shadows the host CPU stage-2 to
establish DMA isolation.
An initial snapshot is taken after the driver init, then on every
ownership change a callback is called for the IOMMU driver to update
its page table. The callback runs under host_mmu.lock, prot 0 unmaps,
and a range may already be mapped with the same prot.
There are 3 different ways to add the callback:
1) In the high level memory transitions (__pkvm_host_donate_hyp(),
__pkvm_host_donate_guest(), ...).
2) In lower level functions covering all transitions:
- host_stage2_set_owner_metadata_locked(), which covers
__pkvm_host_donate_hyp(), __pkvm_host_donate_guest() and
__pkvm_guest_unshare_host().
- host_stage2_set_owner_locked() for PKVM_ID_HOST, which covers
__pkvm_hyp_donate_host(), __pkvm_host_force_reclaim_page_guest()
and __pkvm_host_reclaim_page_guest().
- __pkvm_guest_share_host().
3) In the lowest level function __host_update_page_state(), which
requires only one callback. However, the page state is not enough
there as the old state is needed as well.
Option 2 is implemented here.
IOMMUs can't handle faults, so the snapshot maps everything the host
owns, including what it has not faulted in yet. Failures to update the
shadow are fatal, as WARN_ON() at EL2 panics, so a driver must
guarantee the update cannot fail. The SMMUv3 driver does so by mapping
memory at page granularity, so ownership changes never split a block,
and by sizing its page table pool accordingly.
Signed-off-by: Mostafa Saleh <smostafa@google.com>
---
arch/arm64/kvm/hyp/include/nvhe/iommu.h | 9 ++
arch/arm64/kvm/hyp/nvhe/iommu.c | 123 +++++++++++++++++++++++-
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 27 +++---
3 files changed, 143 insertions(+), 16 deletions(-)
diff --git a/arch/arm64/kvm/hyp/include/nvhe/iommu.h b/arch/arm64/kvm/hyp/include/nvhe/iommu.h
index df3d0cc5d4db..6cbcc9d65b4b 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/iommu.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/iommu.h
@@ -3,11 +3,20 @@
#define __ARM64_KVM_NVHE_IOMMU_H__
#include <asm/kvm_host.h>
+#include <asm/kvm_pgtable.h>
struct pkvm_iommu_ops {
int (*init)(void);
+ /*
+ * Mirror [start, end) of the host stage-2 with IOMMU_* @prot, where 0
+ * unmaps. Called with host_mmu.lock held. A range may already be mapped
+ * with the same @prot, which must succeed.
+ */
+ int (*host_stage2_idmap)(phys_addr_t start, phys_addr_t end, int prot);
};
int pkvm_iommu_init(void);
+int pkvm_iommu_host_stage2_idmap(phys_addr_t start, phys_addr_t end,
+ enum kvm_pgtable_prot prot);
#endif /* __ARM64_KVM_NVHE_IOMMU_H__ */
diff --git a/arch/arm64/kvm/hyp/nvhe/iommu.c b/arch/arm64/kvm/hyp/nvhe/iommu.c
index f4572b2dba76..662b1ed2330f 100644
--- a/arch/arm64/kvm/hyp/nvhe/iommu.c
+++ b/arch/arm64/kvm/hyp/nvhe/iommu.c
@@ -4,16 +4,131 @@
*
* Copyright (C) 2022 Linaro Ltd.
*/
+#include <linux/iommu.h>
+#include <asm/kvm_pkvm.h>
+
#include <nvhe/iommu.h>
+#include <nvhe/mem_protect.h>
+#include <nvhe/spinlock.h>
/* Only one set of ops supported */
struct pkvm_iommu_ops *pkvm_iommu_ops __ro_after_init;
-int pkvm_iommu_init(void)
+/* Protected by host_mmu.lock */
+static bool pkvm_idmap_initialized;
+
+static inline int pkvm_to_iommu_prot(enum kvm_pgtable_prot prot)
{
- /* Keep DMA isolation optional. */
- if (!pkvm_iommu_ops || !pkvm_iommu_ops->init)
+ int iommu_prot = 0;
+
+ if (prot & KVM_PGTABLE_PROT_R)
+ iommu_prot |= IOMMU_READ;
+ if (prot & KVM_PGTABLE_PROT_W)
+ iommu_prot |= IOMMU_WRITE;
+
+ /* We don't understand that, might be dangerous. */
+ WARN_ON(prot & ~PKVM_HOST_MEM_PROT);
+ return iommu_prot;
+}
+
+static int pkvm_iommu_snapshot_walker(const struct kvm_pgtable_visit_ctx *ctx,
+ enum kvm_pgtable_walk_flags visit)
+{
+ bool is_memory = *(bool *)ctx->arg;
+ kvm_pte_t pte = ctx->old;
+ u64 start = ctx->addr;
+ u64 block_end, end;
+ int prot;
+
+ block_end = ALIGN_DOWN(ctx->addr, kvm_granule_size(ctx->level)) +
+ kvm_granule_size(ctx->level);
+ end = min(ctx->end, block_end);
+ /*
+ * Annotated PTEs stay unmapped. Everything else is mapped, including
+ * PTEs the host has not faulted in yet (0) to avoid page faults.
+ */
+ if (pte && !kvm_pte_valid(pte))
return 0;
- return pkvm_iommu_ops->init();
+ if (kvm_pte_valid(pte))
+ prot = pkvm_to_iommu_prot(kvm_pgtable_stage2_pte_prot(pte));
+ else
+ prot = IOMMU_READ | IOMMU_WRITE;
+
+ if (!is_memory)
+ prot |= IOMMU_MMIO;
+
+ return pkvm_iommu_ops->host_stage2_idmap(start, end, prot);
+}
+
+static int pkvm_iommu_snapshot_host_stage2(void)
+{
+ struct kvm_pgtable *pgt = &host_mmu.pgt;
+ bool is_memory;
+ struct kvm_pgtable_walker walker = {
+ .cb = pkvm_iommu_snapshot_walker,
+ .flags = KVM_PGTABLE_WALK_LEAF,
+ .arg = &is_memory,
+ };
+ int ret = 0, i;
+ u64 start = 0;
+
+ hyp_spin_lock(&host_mmu.lock);
+ for (i = 0; i < hyp_memblock_nr; i++) {
+ struct memblock_region *reg = &hyp_memory[i];
+
+ if (start < reg->base) {
+ is_memory = false;
+ ret = kvm_pgtable_walk(pgt, start, reg->base - start, &walker);
+ if (ret)
+ goto out_unlock;
+ }
+
+ is_memory = true;
+ ret = kvm_pgtable_walk(pgt, reg->base, reg->size, &walker);
+ if (ret)
+ goto out_unlock;
+
+ start = reg->base + reg->size;
+ }
+
+ if (start < BIT(pgt->ia_bits)) {
+ is_memory = false;
+ ret = kvm_pgtable_walk(pgt, start, BIT(pgt->ia_bits) - start, &walker);
+ if (ret)
+ goto out_unlock;
+ }
+
+ pkvm_idmap_initialized = true;
+
+out_unlock:
+ hyp_spin_unlock(&host_mmu.lock);
+ return ret;
+}
+
+int pkvm_iommu_init(void)
+{
+ int ret;
+
+ /* Keep DMA isolation optional. */
+ if (!pkvm_iommu_ops || !pkvm_iommu_ops->init ||
+ !pkvm_iommu_ops->host_stage2_idmap)
+ return 0;
+
+ ret = pkvm_iommu_ops->init();
+ if (ret)
+ return ret;
+
+ return WARN_ON(pkvm_iommu_snapshot_host_stage2());
+}
+
+int pkvm_iommu_host_stage2_idmap(phys_addr_t start, phys_addr_t end,
+ enum kvm_pgtable_prot prot)
+{
+ hyp_assert_lock_held(&host_mmu.lock);
+
+ if (!pkvm_idmap_initialized)
+ return 0;
+
+ return pkvm_iommu_ops->host_stage2_idmap(start, end, pkvm_to_iommu_prot(prot));
}
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 75d250ca809c..c14af6a4877c 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -17,6 +17,7 @@
#include <nvhe/arm-smccc.h>
#include <nvhe/gfp.h>
+#include <nvhe/iommu.h>
#include <nvhe/memory.h>
#include <nvhe/mem_protect.h>
#include <nvhe/mm.h>
@@ -611,16 +612,15 @@ static int host_stage2_set_owner_metadata_locked(phys_addr_t addr, u64 size,
ret = host_stage2_try(kvm_pgtable_stage2_annotate, &host_mmu.pgt,
addr, size, &host_s2_pool,
KVM_HOST_INVALID_PTE_TYPE_DONATION, annotation);
- if (!ret) {
- /*
- * After stage2 maintenance has happened, but before the page
- * owner has changed.
- */
- pkvm_sme_dvmsync_fw_call();
- __host_update_page_state(addr, size, PKVM_NOPAGE);
- }
-
- return ret;
+ if (ret)
+ return ret;
+ /*
+ * After stage2 maintenance has happened, but before the page
+ * owner has changed.
+ */
+ pkvm_sme_dvmsync_fw_call();
+ __host_update_page_state(addr, size, PKVM_NOPAGE);
+ return WARN_ON(pkvm_iommu_host_stage2_idmap(addr, addr + size, 0));
}
int host_stage2_set_owner_locked(phys_addr_t addr, u64 size, u8 owner_id)
@@ -633,8 +633,10 @@ int host_stage2_set_owner_locked(phys_addr_t addr, u64 size, u8 owner_id)
return -EPERM;
ret = host_stage2_idmap_locked(addr, size, PKVM_HOST_MEM_PROT);
- if (!ret)
- __host_update_page_state(addr, size, PKVM_PAGE_OWNED);
+ if (ret)
+ break;
+ __host_update_page_state(addr, size, PKVM_PAGE_OWNED);
+ ret = WARN_ON(pkvm_iommu_host_stage2_idmap(addr, addr + size, PKVM_HOST_MEM_PROT));
break;
case PKVM_ID_HYP:
ret = host_stage2_set_owner_metadata_locked(addr, size,
@@ -1049,6 +1051,7 @@ int __pkvm_guest_share_host(struct pkvm_hyp_vcpu *vcpu, u64 gfn)
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(pkvm_iommu_host_stage2_idmap(phys, phys + PAGE_SIZE, PKVM_HOST_MEM_PROT));
unlock:
guest_unlock_component(vm);
host_unlock_component();
--
2.56.0.176.g3575e65682-goog
next prev parent reply other threads:[~2026-10-15 20:09 UTC|newest]
Thread overview: 52+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-15 20:08 [PATCH v9 00/25] KVM: arm64: SMMUv3 driver for pKVM (trap and emulate) Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 01/25] KVM: arm64: Donate MMIO to the hypervisor Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 02/25] iommu/arm-smmu-v3: Move Queue and STE functions to header Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 03/25] iommu/arm-smmu-v3: Move IDR parsing to common functions Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 04/25] iommu/arm-smmu-v3: Move hitless machinery to common code Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 05/25] iommu/arm-smmu-v3: Move RangeInval " Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 06/25] KVM: arm64: iommu: Introduce IOMMU driver infrastructure Mostafa Saleh
2026-10-15 20:08 ` Mostafa Saleh [this message]
2026-10-15 20:08 ` [PATCH v9 08/25] KVM: arm64: iommu: Add memory pool Mostafa Saleh
2026-10-15 20:18 ` sashiko-bot
2026-10-15 20:40 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 09/25] KVM: arm64: iommu: Support DABT for IOMMU Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 10/25] iommu/arm-smmu-v3-kvm: Add SMMUv3 driver Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 11/25] iommu/arm-smmu-v3-kvm: Add the kernel driver Mostafa Saleh
2026-10-15 20:35 ` sashiko-bot
2026-10-15 20:52 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 12/25] iommu/arm-smmu-v3-kvm: Probe SMMU HW Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 13/25] iommu/arm-smmu-v3-kvm: Add MMIO emulation Mostafa Saleh
2026-10-15 20:30 ` sashiko-bot
2026-10-15 21:00 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 14/25] iommu/arm-smmu-v3-kvm: Shadow the command queue Mostafa Saleh
2026-10-15 20:28 ` sashiko-bot
2026-10-15 21:01 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 15/25] iommu/arm-smmu-v3-kvm: Add CMDQ functions Mostafa Saleh
2026-10-15 20:30 ` sashiko-bot
2026-10-15 21:11 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 16/25] iommu/arm-smmu-v3-kvm: Emulate CMDQ for host Mostafa Saleh
2026-10-15 20:30 ` sashiko-bot
2026-10-15 21:17 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 17/25] iommu/arm-smmu-v3-kvm: Shadow stream table Mostafa Saleh
2026-10-15 20:30 ` sashiko-bot
2026-10-15 21:20 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 18/25] iommu/arm-smmu-v3-kvm: Shadow STEs Mostafa Saleh
2026-10-15 20:30 ` sashiko-bot
2026-10-15 21:22 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 19/25] iommu/arm-smmu-v3-kvm: Share other queues Mostafa Saleh
2026-10-15 20:29 ` sashiko-bot
2026-10-15 21:23 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 20/25] iommu/arm-smmu-v3-kvm: Emulate GBPA Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 21/25] iommu/io-pgtable-arm: Support io-pgtable-arm in the hypervisor Mostafa Saleh
2026-10-15 20:26 ` sashiko-bot
2026-10-15 21:24 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 22/25] iommu/arm-smmu-v3-kvm: Shadow the CPU stage-2 page table Mostafa Saleh
2026-10-15 20:32 ` sashiko-bot
2026-10-15 21:32 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 23/25] iommu/arm-smmu-v3-kvm: Invalidate the SMMU TLBs Mostafa Saleh
2026-10-15 20:28 ` sashiko-bot
2026-10-15 21:35 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 24/25] iommu/arm-smmu-v3-kvm: Enable nesting Mostafa Saleh
2026-10-15 20:34 ` sashiko-bot
2026-10-15 21:37 ` Mostafa Saleh
2026-10-15 20:08 ` [PATCH v9 25/25] KVM: arm64: Add documentation for pKVM DMA isolation Mostafa Saleh
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=20261015200903.2735565-8-smostafa@google.com \
--to=smostafa@google.com \
--cc=catalin.marinas@arm.com \
--cc=iommu@lists.linux.dev \
--cc=jgg@ziepe.ca \
--cc=joey.gouly@arm.com \
--cc=joro@8bytes.org \
--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=nicolinc@nvidia.com \
--cc=oliver.upton@linux.dev \
--cc=qperret@google.com \
--cc=suzuki.poulose@arm.com \
--cc=tabba@google.com \
--cc=vdonnefort@google.com \
--cc=will@kernel.org \
--cc=yuzenghui@huawei.com \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.