[PATCH 01/15] KVM: arm64: pgtables: Change write bit from S2AP_W to DBM
From: Leonardo Bras <leo.bras@arm.com> As a first step of changing the encoding for the Stage2 PTE descriptor, introduce the DBM bit, and adapt every usage of writable to use the DBM bit (51) instead of S2AP[1]/Dirty bit (7). For this step, we convert usages of RW(Dirty) -> WD(DBM|Dirty). [zhengtian: keep reading writability from S2AP[1] only in walk_nested_s2_pgd(). KVM hides HAFDBS.DBM from L1 (its visible ID_AA64MMFR1_EL1.HAFDBS is limited to AF-only), so bit 51 is RES0 from L1's perspective, and an L1 using it as software metadata would have its read-only pages misread as writable by L0. Restore the union reading once nested HAFDBS emulation exists.] Link: https://lore.kernel.org/all/20260901171558.2674031-2-leo.bras@arm.com/ Signed-off-by: Leonardo Bras <leo.bras@arm.com> Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/include/asm/kvm_pgtable.h | 3 +++ arch/arm64/kvm/hyp/pgtable.c | 8 +++++--- arch/arm64/kvm/ptdump.c | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h index 41a8687938eb..37baa86d6fd8 100644 --- a/arch/arm64/include/asm/kvm_pgtable.h +++ b/arch/arm64/include/asm/kvm_pgtable.h @@ -93,10 +93,13 @@ typedef u64 kvm_pte_t; #define KVM_PTE_LEAF_ATTR_HI_S2_XN GENMASK(54, 53) +#define KVM_PTE_LEAF_ATTR_HI_S2_DBM BIT(51) + #define KVM_PTE_LEAF_ATTR_HI_S1_GP BIT(50) #define KVM_PTE_LEAF_ATTR_S2_PERMS (KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R | \ KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W | \ + KVM_PTE_LEAF_ATTR_HI_S2_DBM | \ KVM_PTE_LEAF_ATTR_HI_S2_XN) /* pKVM invalid pte encodings */ diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c index b74dd5ce1efd..ca49f1bd7c34 100644 --- a/arch/arm64/kvm/hyp/pgtable.c +++ b/arch/arm64/kvm/hyp/pgtable.c @@ -732,7 +732,8 @@ static int stage2_set_prot_attr(struct kvm_pgtable *pgt, enum kvm_pgtable_prot p attr |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R; if (prot & KVM_PGTABLE_PROT_W) - attr |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W; + attr |= KVM_PTE_LEAF_ATTR_HI_S2_DBM | KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W; + if (!kvm_lpa2_is_enabled()) attr |= FIELD_PREP(KVM_PTE_LEAF_ATTR_LO_S2_SH, sh); @@ -753,7 +754,7 @@ enum kvm_pgtable_prot kvm_pgtable_stage2_pte_prot(kvm_pte_t pte) if (pte & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R) prot |= KVM_PGTABLE_PROT_R; - if (pte & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W) + if (pte & KVM_PTE_LEAF_ATTR_HI_S2_DBM) prot |= KVM_PGTABLE_PROT_W; switch (FIELD_GET(KVM_PTE_LEAF_ATTR_HI_S2_XN, pte)) { @@ -1288,6 +1289,7 @@ static int stage2_update_leaf_attrs(struct kvm_pgtable *pgt, u64 addr, int kvm_pgtable_stage2_wrprotect(struct kvm_pgtable *pgt, u64 addr, u64 size) { return stage2_update_leaf_attrs(pgt, addr, size, 0, + KVM_PTE_LEAF_ATTR_HI_S2_DBM | KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, NULL, NULL, KVM_PGTABLE_WALK_IGNORE_EAGAIN); @@ -1368,7 +1370,7 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr, set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R; if (prot & KVM_PGTABLE_PROT_W) - set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W; + set |= KVM_PTE_LEAF_ATTR_HI_S2_DBM | KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W; if (prot & KVM_PGTABLE_PROT_X) { ret = stage2_set_xn_attr(prot, &xn); diff --git a/arch/arm64/kvm/ptdump.c b/arch/arm64/kvm/ptdump.c index 69899797dbad..b0cb8d84a9e9 100644 --- a/arch/arm64/kvm/ptdump.c +++ b/arch/arm64/kvm/ptdump.c @@ -40,8 +40,8 @@ static const struct ptdump_prot_bits stage2_pte_bits[] = { .clear = " ", }, { - .mask = KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, - .val = KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, + .mask = KVM_PTE_LEAF_ATTR_HI_S2_DBM, + .val = KVM_PTE_LEAF_ATTR_HI_S2_DBM, .set = "W", .clear = " ", }, -- 2.33.0
From: Leonardo Bras <leo.bras@arm.com> Second step of changing the encoding for the Stage2 PTE descriptor, introduce the concept of dirty page, so we can have a writable but not dirty (WC) page, and a writable and dirty (WD) page. In order to do so, evaluate uses in a per-case basis, and figure what concept was important in each case (being dirty, or writable). [zhengtian: split the folio dirty account from the dirty-bitmap account in kvm_s2_fault_map() and gmem_abort(). The host folio account (SetPageDirty, reclaim/writeback correctness) must be speculative: a writable-clean mapping can be hardware-promoted to writable-dirty without any VM exit, and the arm64 KVM unmap path does not harvest the stage-2 dirty bit, so releasing the folio clean on a read fault would leave written guest data invisible to reclaim. Gate the folio release on PROT_W while keeping the dirty bitmap on PROT_DIRTY, per the contract documented in kvm_release_faultin_page().] Link: https://lore.kernel.org/all/20260901171558.2674031-3-leo.bras@arm.com/ Signed-off-by: Leonardo Bras <leo.bras@arm.com> Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/include/asm/kvm_pgtable.h | 9 +++++--- arch/arm64/kvm/hyp/pgtable.c | 23 ++++++++++++++----- arch/arm64/kvm/mmu.c | 33 +++++++++++++++++++--------- arch/arm64/kvm/ptdump.c | 6 +++++ 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h index 37baa86d6fd8..379031c74cbc 100644 --- a/arch/arm64/include/asm/kvm_pgtable.h +++ b/arch/arm64/include/asm/kvm_pgtable.h @@ -265,6 +265,7 @@ enum kvm_pgtable_stage2_flags { * @KVM_PGTABLE_PROT_X: Privileged and unprivileged execute permission. * @KVM_PGTABLE_PROT_W: Write permission. * @KVM_PGTABLE_PROT_R: Read permission. + * @KVM_PGTABLE_PROT_DIRTY: Dirty attribute. * @KVM_PGTABLE_PROT_DEVICE: Device attributes. * @KVM_PGTABLE_PROT_NORMAL_NC: Normal noncacheable attributes. * @KVM_PGTABLE_PROT_SW0: Software bit 0. @@ -279,9 +280,10 @@ enum kvm_pgtable_prot { KVM_PGTABLE_PROT_UX, KVM_PGTABLE_PROT_W = BIT(2), KVM_PGTABLE_PROT_R = BIT(3), + KVM_PGTABLE_PROT_DIRTY = BIT(4), - KVM_PGTABLE_PROT_DEVICE = BIT(4), - KVM_PGTABLE_PROT_NORMAL_NC = BIT(5), + KVM_PGTABLE_PROT_DEVICE = BIT(5), + KVM_PGTABLE_PROT_NORMAL_NC = BIT(6), KVM_PGTABLE_PROT_SW0 = BIT(55), KVM_PGTABLE_PROT_SW1 = BIT(56), @@ -289,7 +291,8 @@ enum kvm_pgtable_prot { KVM_PGTABLE_PROT_SW3 = BIT(58), }; -#define KVM_PGTABLE_PROT_RW (KVM_PGTABLE_PROT_R | KVM_PGTABLE_PROT_W) +#define KVM_PGTABLE_PROT_RW (KVM_PGTABLE_PROT_R | KVM_PGTABLE_PROT_W | \ + KVM_PGTABLE_PROT_DIRTY) #define KVM_PGTABLE_PROT_RWX (KVM_PGTABLE_PROT_RW | KVM_PGTABLE_PROT_X) #define PKVM_HOST_MEM_PROT KVM_PGTABLE_PROT_RWX diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c index ca49f1bd7c34..2ff33d3e371e 100644 --- a/arch/arm64/kvm/hyp/pgtable.c +++ b/arch/arm64/kvm/hyp/pgtable.c @@ -731,8 +731,12 @@ static int stage2_set_prot_attr(struct kvm_pgtable *pgt, enum kvm_pgtable_prot p if (prot & KVM_PGTABLE_PROT_R) attr |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R; - if (prot & KVM_PGTABLE_PROT_W) - attr |= KVM_PTE_LEAF_ATTR_HI_S2_DBM | KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W; + if (prot & KVM_PGTABLE_PROT_W) { + attr |= KVM_PTE_LEAF_ATTR_HI_S2_DBM; + + if (prot & KVM_PGTABLE_PROT_DIRTY) + attr |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W; + } if (!kvm_lpa2_is_enabled()) @@ -754,9 +758,13 @@ enum kvm_pgtable_prot kvm_pgtable_stage2_pte_prot(kvm_pte_t pte) if (pte & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R) prot |= KVM_PGTABLE_PROT_R; - if (pte & KVM_PTE_LEAF_ATTR_HI_S2_DBM) + if (pte & KVM_PTE_LEAF_ATTR_HI_S2_DBM) { prot |= KVM_PGTABLE_PROT_W; + if (pte & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W) + prot |= KVM_PGTABLE_PROT_DIRTY; + } + switch (FIELD_GET(KVM_PTE_LEAF_ATTR_HI_S2_XN, pte)) { case 0b00: prot |= KVM_PGTABLE_PROT_PX | KVM_PGTABLE_PROT_UX; @@ -1289,7 +1297,6 @@ static int stage2_update_leaf_attrs(struct kvm_pgtable *pgt, u64 addr, int kvm_pgtable_stage2_wrprotect(struct kvm_pgtable *pgt, u64 addr, u64 size) { return stage2_update_leaf_attrs(pgt, addr, size, 0, - KVM_PTE_LEAF_ATTR_HI_S2_DBM | KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, NULL, NULL, KVM_PGTABLE_WALK_IGNORE_EAGAIN); @@ -1369,8 +1376,12 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr, if (prot & KVM_PGTABLE_PROT_R) set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R; - if (prot & KVM_PGTABLE_PROT_W) - set |= KVM_PTE_LEAF_ATTR_HI_S2_DBM | KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W; + if (prot & KVM_PGTABLE_PROT_W) { + set |= KVM_PTE_LEAF_ATTR_HI_S2_DBM; + + if (prot & KVM_PGTABLE_PROT_DIRTY) + set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W; + } if (prot & KVM_PGTABLE_PROT_X) { ret = stage2_set_xn_attr(prot, &xn); diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 9ba86450fe4a..e01db87daa97 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -1212,7 +1212,9 @@ int kvm_phys_addr_ioremap(struct kvm *kvm, phys_addr_t guest_ipa, struct kvm_pgtable *pgt = mmu->pgt; enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_DEVICE | KVM_PGTABLE_PROT_R | - (writable ? KVM_PGTABLE_PROT_W : 0); + (writable ? + (KVM_PGTABLE_PROT_W | KVM_PGTABLE_PROT_DIRTY) : + 0); if (is_protected_kvm_enabled()) return -EPERM; @@ -1578,7 +1580,7 @@ static enum kvm_pgtable_prot adjust_nested_fault_perms(struct kvm_s2_trans *nest enum kvm_pgtable_prot prot) { if (!kvm_s2_trans_writable(nested)) - prot &= ~KVM_PGTABLE_PROT_W; + prot &= ~(KVM_PGTABLE_PROT_W | KVM_PGTABLE_PROT_DIRTY); if (!kvm_s2_trans_readable(nested)) prot &= ~KVM_PGTABLE_PROT_R; @@ -1649,7 +1651,7 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd) } if (!(s2fd->memslot->flags & KVM_MEM_READONLY)) - prot |= KVM_PGTABLE_PROT_W; + prot |= KVM_PGTABLE_PROT_W | KVM_PGTABLE_PROT_DIRTY; if (s2fd->nested) prot = adjust_nested_fault_perms(s2fd->nested, prot); @@ -1681,10 +1683,17 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd) } out_unlock: + /* + * Two distinct dirty ledgers with different precision requirements: + * the host folio account (SetPageDirty) must be speculative, as a + * writable-clean mapping can be hardware-promoted to writable-dirty + * without any VM exit once VTCR_EL2.HD is set; the dirty bitmap must + * remain exact, or pre-copy will ship clean pages. + */ kvm_release_faultin_page(kvm, page, !!ret, prot & KVM_PGTABLE_PROT_W); kvm_fault_unlock(kvm); - if ((prot & KVM_PGTABLE_PROT_W) && !ret) + if ((prot & KVM_PGTABLE_PROT_DIRTY) && !ret) mark_page_dirty_in_slot(kvm, s2fd->memslot, gfn); return ret != -EAGAIN ? ret : 0; @@ -1984,11 +1993,14 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd, *prot = KVM_PGTABLE_PROT_R; - if (s2vi->map_writable && (s2vi->device || - !memslot_is_logging(s2fd->memslot) || - kvm_is_write_fault(s2fd->vcpu))) + if (s2vi->map_writable) { *prot |= KVM_PGTABLE_PROT_W; + if (s2vi->device || !memslot_is_logging(s2fd->memslot) || + kvm_is_write_fault(s2fd->vcpu)) + *prot |= KVM_PGTABLE_PROT_DIRTY; + } + if (s2fd->nested) *prot = adjust_nested_fault_perms(s2fd->nested, *prot); @@ -2019,7 +2031,7 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd, void *memcache) { enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED; - bool writable = prot & KVM_PGTABLE_PROT_W; + bool dirty = prot & KVM_PGTABLE_PROT_DIRTY; struct kvm *kvm = s2fd->vcpu->kvm; struct kvm_pgtable *pgt; long perm_fault_granule; @@ -2082,7 +2094,8 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd, } out_unlock: - kvm_release_faultin_page(kvm, s2vi->page, !!ret, writable); + /* Speculative folio dirtying, see gmem_abort(): use W, not DIRTY. */ + kvm_release_faultin_page(kvm, s2vi->page, !!ret, prot & KVM_PGTABLE_PROT_W); kvm_fault_unlock(kvm); /* @@ -2090,7 +2103,7 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd, * making sure we adjust the canonical IPA if the mapping size has * been updated (via a THP upgrade, for example). */ - if (writable && !ret) { + if (dirty && !ret) { phys_addr_t ipa = gfn_to_gpa(get_canonical_gfn(s2fd, s2vi)); ipa &= ~(mapping_size - 1); mark_page_dirty_in_slot(kvm, s2fd->memslot, gpa_to_gfn(ipa)); diff --git a/arch/arm64/kvm/ptdump.c b/arch/arm64/kvm/ptdump.c index b0cb8d84a9e9..a1251e252b4f 100644 --- a/arch/arm64/kvm/ptdump.c +++ b/arch/arm64/kvm/ptdump.c @@ -45,6 +45,12 @@ static const struct ptdump_prot_bits stage2_pte_bits[] = { .set = "W", .clear = " ", }, + { + .mask = KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, + .val = KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, + .set = "D", + .clear = "C", + }, { .mask = KVM_PTE_LEAF_ATTR_HI_S2_XN, .val = 0b00UL << __bf_shf(KVM_PTE_LEAF_ATTR_HI_S2_XN), -- 2.33.0
From: Leonardo Bras <leo.bras@arm.com> The new walker cleans the dirty bit on leaf entries, as well as clean the DBM bit in blocks so it still faults for lazy hugepage splitting when we enable FEAT_HDBSS in future patches. With disabled HDBSS, there should be no change in faulting behavior. Link: https://lore.kernel.org/all/20260901171558.2674031-4-leo.bras@arm.com/ Signed-off-by: Leonardo Bras <leo.bras@arm.com> Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/kvm/hyp/pgtable.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c index 2ff33d3e371e..2849c136f366 100644 --- a/arch/arm64/kvm/hyp/pgtable.c +++ b/arch/arm64/kvm/hyp/pgtable.c @@ -1294,12 +1294,30 @@ static int stage2_update_leaf_attrs(struct kvm_pgtable *pgt, u64 addr, return 0; } +static int stage2_wrprotect_walker(const struct kvm_pgtable_visit_ctx *ctx, + enum kvm_pgtable_walk_flags visit) +{ + kvm_pte_t new = ctx->old & ~KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W; + + /* We remove DBM on blocks so they can fault and get split */ + if (ctx->level < KVM_PGTABLE_LAST_LEVEL) + new &= ~KVM_PTE_LEAF_ATTR_HI_S2_DBM; + + if (kvm_pte_valid(ctx->old) && ctx->old != new) + WRITE_ONCE(*ctx->ptep, new); + + return 0; +} + + int kvm_pgtable_stage2_wrprotect(struct kvm_pgtable *pgt, u64 addr, u64 size) { - return stage2_update_leaf_attrs(pgt, addr, size, 0, - KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, - NULL, NULL, - KVM_PGTABLE_WALK_IGNORE_EAGAIN); + struct kvm_pgtable_walker walker = { + .cb = stage2_wrprotect_walker, + .flags = KVM_PGTABLE_WALK_LEAF, + }; + + return kvm_pgtable_walk(pgt, addr, size, &walker); } void kvm_pgtable_stage2_mkyoung(struct kvm_pgtable *pgt, u64 addr, -- 2.33.0
From: Leonardo Bras <leo.bras@arm.com> Add a vcpu request to exit guest, reload Stage2, and then come back to guest. This will be used on future patches that enable S2 HAFDBS and HDBSS, as they may need to change VTCR bits for enabling/disabling the feature when the vcpus are still running. Link: https://lore.kernel.org/all/20260901171558.2674031-5-leo.bras@arm.com/ Signed-off-by: Leonardo Bras <leo.bras@arm.com> Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/include/asm/kvm_host.h | 2 ++ arch/arm64/kvm/arm.c | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index 27fe0cd5b2d7..00fe169f239f 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -55,6 +55,8 @@ #define KVM_REQ_GUEST_HYP_IRQ_PENDING KVM_ARCH_REQ(9) #define KVM_REQ_MAP_L1_VNCR_EL2 KVM_ARCH_REQ(10) #define KVM_REQ_VGIC_PROCESS_UPDATE KVM_ARCH_REQ(11) +#define KVM_REQ_RELOAD_STAGE2 \ + KVM_ARCH_REQ_FLAGS(12, KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP) #define KVM_DIRTY_LOG_MANUAL_CAPS (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE | \ KVM_DIRTY_LOG_INITIALLY_SET) diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 8b080804bc90..1e528d53d093 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -1171,6 +1171,14 @@ static int check_vcpu_requests(struct kvm_vcpu *vcpu) if (kvm_dirty_ring_check_request(vcpu)) return 0; + if (kvm_check_request(KVM_REQ_RELOAD_STAGE2, vcpu)) { + unsigned long flags; + + local_irq_save(flags); + __load_stage2(vcpu->arch.hw_mmu); + local_irq_restore(flags); + } + check_nested_vcpu_requests(vcpu); } -- 2.33.0
With VTCR_EL2.HD set, hardware promotes writable-clean descriptors to writable-dirty without any VM exit, and nothing in the arm64 KVM unmap paths harvests the stage-2 dirty bit - stage2_unmap_walker() only puts the page-table page. A folio whose only dirty state lives in a hardware-promoted stage-2 PTE is therefore invisible to the host MM, and reclaim can discard written guest data (silent corruption). The fault paths mark the folio speculatively at fault-in time, but the mapping lifecycle still needs exact harvesting, mirroring zap_present_folio_ptes() in the generic mm: - stage2_unmap_walker(): before tearing down a valid leaf with S2AP[1] set, harvest its output address into the folio account. Exact accounting: the walk runs under the mmu write lock, so the PTE being removed is the one being harvested. - stage2_wrprotect_walker(): a writable-dirty page losing its dirty state (WD -> WC during CLEAR) is harvested before the clear, so the information survives even if the mapping is torn down before the dirty state is observed anywhere else. Both hooks go through a new kvm_pgtable_mm_ops::mark_page_dirty callback: the walkers live in the dual-compiled hyp/pgtable.c, while SetPageDirty() is host-only and must not be reached for pKVM's host stage-2 page tables, whose mm_ops leave the callback NULL. Block mappings always map a folio-aligned head page, so dirtying the head covers the whole block. MMIO/PFNMAP ranges and reserved pages are excluded, mirroring kvm_is_ad_tracked_page() in virt/kvm/kvm_main.c. Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/include/asm/kvm_pgtable.h | 10 ++++++++++ arch/arm64/kvm/hyp/pgtable.c | 30 ++++++++++++++++++++++++++-- arch/arm64/kvm/mmu.c | 26 ++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h index 379031c74cbc..6535f56db127 100644 --- a/arch/arm64/include/asm/kvm_pgtable.h +++ b/arch/arm64/include/asm/kvm_pgtable.h @@ -246,6 +246,16 @@ struct kvm_pgtable_mm_ops { phys_addr_t (*virt_to_phys)(void *addr); void (*dcache_clean_inval_poc)(void *addr, size_t size); void (*icache_inval_pou)(void *addr, size_t size); + /* + * Harvest the dirty state of a leaf mapping into the host folio + * account before the mapping (or its write permission) goes away. + * Called for valid leaf entries with S2AP[1] set, whose dirty state + * may have been set by hardware (HAFDBS/HDBSS) without KVM ever + * observing a write fault. Takes the output address of the mapping + * (physical). Host-only; implementations that do not track folios + * leave it NULL. + */ + void (*mark_page_dirty)(u64 pa); }; /** diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c index 2849c136f366..1e7583c5abf9 100644 --- a/arch/arm64/kvm/hyp/pgtable.c +++ b/arch/arm64/kvm/hyp/pgtable.c @@ -1183,8 +1183,22 @@ static int stage2_unmap_walker(const struct kvm_pgtable_visit_ctx *ctx, if (mm_ops->page_count(childp) != 1) return 0; - } else if (stage2_pte_cacheable(pgt, ctx->old)) { - need_flush = !cpus_have_final_cap(ARM64_HAS_STAGE2_FWB); + } else { + if (stage2_pte_cacheable(pgt, ctx->old)) + need_flush = !cpus_have_final_cap(ARM64_HAS_STAGE2_FWB); + + /* + * Harvest the dirty state into the host folio account + * before the mapping goes away, mirroring + * zap_present_folio_ptes() in the generic mm: S2AP[1] may + * have been set by hardware (FEAT_HAFDBS) without KVM ever + * observing a write fault, so this is the last chance to + * account it. Block mappings always map a folio-aligned + * head page, so dirtying the head covers the whole block. + */ + if ((ctx->old & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W) && + mm_ops->mark_page_dirty) + mm_ops->mark_page_dirty(kvm_pte_to_phys(ctx->old)); } /* @@ -1303,6 +1317,18 @@ static int stage2_wrprotect_walker(const struct kvm_pgtable_visit_ctx *ctx, if (ctx->level < KVM_PGTABLE_LAST_LEVEL) new &= ~KVM_PTE_LEAF_ATTR_HI_S2_DBM; + /* + * A writable-dirty page is about to lose its dirty state. If + * hardware promoted it, S2AP[1] is the only record of the write + * outside the PTE: harvest the folio account before the clear so + * the dirty information survives the transition regardless of + * what happens to the mapping afterwards. + */ + if (kvm_pte_valid(ctx->old) && ctx->old != new && + (ctx->old & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W) && + ctx->mm_ops->mark_page_dirty) + ctx->mm_ops->mark_page_dirty(kvm_pte_to_phys(ctx->old)); + if (kvm_pte_valid(ctx->old) && ctx->old != new) WRITE_ONCE(*ctx->ptep, new); diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index e01db87daa97..b1a6f743be7b 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -888,6 +888,31 @@ static int get_user_mapping_size(struct kvm *kvm, u64 addr) return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(level)); } +/* + * Harvest the stage-2 dirty state of a mapping into the host folio + * account, wired into kvm_s2_mm_ops::mark_page_dirty for the unmap and + * write-protect walkers. The dirty bit may have been set by hardware + * (FEAT_HAFDBS) without KVM ever observing a write fault, so this is + * the exact accounting, unlike the speculative folio dirtying at + * fault-in time. MMIO/PFNMAP ranges and reserved pages are outside the + * page-dirty machinery (mirrors kvm_is_ad_tracked_page() in + * virt/kvm/kvm_main.c). + */ +static void kvm_s2_mark_page_dirty(u64 pa) +{ + unsigned long pfn = pa >> PAGE_SHIFT; + struct page *page; + + if (!pfn_valid(pfn)) + return; + + page = pfn_to_page(pfn); + if (PageReserved(page)) + return; + + SetPageDirty(page); +} + static struct kvm_pgtable_mm_ops kvm_s2_mm_ops = { .zalloc_page = stage2_memcache_zalloc_page, .zalloc_pages_exact = kvm_s2_zalloc_pages_exact, @@ -900,6 +925,7 @@ static struct kvm_pgtable_mm_ops kvm_s2_mm_ops = { .virt_to_phys = kvm_host_pa, .dcache_clean_inval_poc = clean_dcache_guest_page, .icache_inval_pou = invalidate_icache_guest_page, + .mark_page_dirty = kvm_s2_mark_page_dirty, }; static int kvm_init_ipa_range(struct kvm_s2_mmu *mmu, unsigned long type) -- 2.33.0
Armv9.5 introduces the hardware dirty state tracking structure (HDBSS), with which hardware updates stage-2 descriptors from writable-clean to writable-dirty without a permission fault, and records the dirtied IPAs into an in-memory buffer. The rest of this series uses HDBSS for hardware-assisted dirty page tracking during live migration: instead of write-protecting guest pages and taking a fault on every first write, KVM leaves the pages writable-clean and drains the buffer on VM exit. Add CPU capability detection for the feature, advertised through ID_AA64MMFR1_EL1.HAFDBS (0b0100). Because the buffer registers (HDBSSBR_EL2, HDBSSPROD_EL2) are EL2-only and are programmed directly from host context, restrict the capability to VHE systems with a custom matches() callback that also checks is_kernel_in_hyp_mode(). Also add a system_supports_hdbss() helper, which the rest of the series uses to check hardware support. Suggested-by: Zhou Wang <wangzhou1@hisilicon.com> Co-developed-by: Eillon <yezhenyu2@huawei.com> Signed-off-by: Eillon <yezhenyu2@huawei.com> Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/include/asm/cpufeature.h | 5 +++++ arch/arm64/kernel/cpufeature.c | 17 +++++++++++++++++ arch/arm64/tools/cpucaps | 1 + 3 files changed, 23 insertions(+) diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h index 4f04ad82ea34..0592e6f6b4d7 100644 --- a/arch/arm64/include/asm/cpufeature.h +++ b/arch/arm64/include/asm/cpufeature.h @@ -856,6 +856,11 @@ static inline bool system_supports_haft(void) return cpus_have_final_cap(ARM64_HAFT); } +static inline bool system_supports_hdbss(void) +{ + return cpus_have_final_cap(ARM64_HAS_HDBSS); +} + static __always_inline bool system_supports_mpam(void) { return alternative_has_cap_unlikely(ARM64_MPAM); diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 32102c3912fa..df401b678860 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -2158,6 +2158,16 @@ static bool hvhe_possible(const struct arm64_cpu_capabilities *entry, return arm64_test_sw_feature_override(ARM64_SW_FEATURE_OVERRIDE_HVHE); } +/* + * HDBSS is only usable with VHE: the buffer registers (HDBSSBR_EL2, + * HDBSSPROD_EL2) are EL2-only and are programmed directly from host + * context, which requires the kernel to run at EL2. + */ +static bool has_vhe_hdbss(const struct arm64_cpu_capabilities *entry, int scope) +{ + return is_kernel_in_hyp_mode() && has_cpuid_feature(entry, scope); +} + bool cpu_supports_bbml3(void) { /* CPUs that support BBML3 but dont advertise through ID_AA64MMFR2_EL1 */ @@ -2816,6 +2826,13 @@ static const struct arm64_cpu_capabilities arm64_features[] = { ARM64_CPUID_FIELDS(ID_AA64MMFR1_EL1, HAFDBS, HAFT) }, #endif + { + .desc = "Hardware Dirty state tracking structure (HDBSS)", + .type = ARM64_CPUCAP_SYSTEM_FEATURE, + .capability = ARM64_HAS_HDBSS, + .matches = has_vhe_hdbss, + ARM64_CPUID_FIELDS(ID_AA64MMFR1_EL1, HAFDBS, HDBSS) + }, { .desc = "CRC32 instructions", .capability = ARM64_HAS_CRC32, diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps index 2775ba3359cf..8acb3db980c9 100644 --- a/arch/arm64/tools/cpucaps +++ b/arch/arm64/tools/cpucaps @@ -71,6 +71,7 @@ HAS_VA52 HAS_VIRT_HOST_EXTN HAS_WFXT HAS_XNX +HAS_HDBSS HAFT HW_DBM KVM_HVHE -- 2.33.0
From: Eillon <yezhenyu2@huawei.com> Each vCPU needs its own HDBSS buffer, described to hardware through HDBSSBR_EL2 (base address and encoded size) and a producer index, HDBSSPROD_EL2, which hardware advances as entries are appended. Tie the buffer lifetime to the vCPU: allocate it at vCPU creation and free it at vCPU destruction, independent of dirty logging. A later patch enables and disables HDBSS purely by toggling VTCR_EL2 bits, so the buffer must already exist by the time HDBSS turns on, and must never be freed while a vCPU is in guest mode with the feature active. Save HDBSSPROD_EL2 on vCPU put and restore both registers on load: the index is per-CPU register state that is reprogrammed by the next vCPU load, so buffering it in the vCPU is the only way to carry it across context switches and migrations. Keep the buddy allocation order separate from the HDBSSBR_EL2.SZ encoding: the two only coincide on 4KB pages, and using the SZ encoding as the free order would release more pages than were allocated on 16KB/64KB granules. A vcpu_hdbss_enabled() helper checks whether HDBSS is active for the current vCPU by testing hw_mmu->vtcr, correctly handling nested virtualization, where the shadow stage-2 MMU does not have the HDBSS bits set. Rework the kvm_arch_vcpu_create() error paths to unwind kvm_share_hyp() when the HDBSS allocation fails. Signed-off-by: Eillon <yezhenyu2@huawei.com> Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/include/asm/kvm_dirty_bit.h | 40 +++++++++++++++++ arch/arm64/include/asm/kvm_host.h | 13 ++++++ arch/arm64/include/asm/sysreg.h | 9 ++++ arch/arm64/kvm/Makefile | 1 + arch/arm64/kvm/arm.c | 14 +++++- arch/arm64/kvm/dirty_bit.c | 60 ++++++++++++++++++++++++++ arch/arm64/kvm/hyp/vhe/switch.c | 17 ++++++++ arch/arm64/kvm/reset.c | 3 ++ 8 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 arch/arm64/include/asm/kvm_dirty_bit.h create mode 100644 arch/arm64/kvm/dirty_bit.c diff --git a/arch/arm64/include/asm/kvm_dirty_bit.h b/arch/arm64/include/asm/kvm_dirty_bit.h new file mode 100644 index 000000000000..ee4de4ba8f5c --- /dev/null +++ b/arch/arm64/include/asm/kvm_dirty_bit.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Per-vCPU buffer management for HDBSS-based dirty page tracking. + * + * Copyright (C) 2026 Huawei Technologies Co., Ltd + * Author: Tian Zheng <zhengtian10@huawei.com> + */ + +#ifndef __ARM64_KVM_DIRTY_BIT_H__ +#define __ARM64_KVM_DIRTY_BIT_H__ + +#include <asm/kvm_pgtable.h> +#include <asm/sysreg.h> +#include <linux/sizes.h> + +#define KVM_ARM_HDBSS_DEFAULT_SIZE PAGE_SIZE +#define KVM_ARM_HDBSS_MAX_SIZE SZ_2M + +/* 0 means unconfigured, fall back to one page per vCPU. */ +static inline u32 kvm_hdbss_buffer_size(struct kvm *kvm) +{ + return kvm->arch.hdbss_buffer_size ?: KVM_ARM_HDBSS_DEFAULT_SIZE; +} + +/* + * Tests hw_mmu->vtcr, the stage-2 MMU in use for the vCPU's current + * execution context: when running L1, hw_mmu points to kvm->arch.mmu + * (HDBSS bits present); when running L2, it points to a shadow + * stage-2 MMU (HDBSS bits absent). + */ +static inline bool vcpu_hdbss_enabled(struct kvm_vcpu *vcpu) +{ + return vcpu->arch.hw_mmu && + (vcpu->arch.hw_mmu->vtcr & VTCR_EL2_HDBSS); +} + +int kvm_arm_vcpu_alloc_hdbss(struct kvm_vcpu *vcpu); +void kvm_arm_vcpu_free_hdbss(struct kvm_vcpu *vcpu); + +#endif /* __ARM64_KVM_DIRTY_BIT_H__ */ diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index 00fe169f239f..e25f1d40c114 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -425,6 +425,9 @@ struct kvm_arch { */ struct kvm_protected_vm pkvm; + /* HDBSS: per-VM buffer size in bytes (0 = not configured, use default) */ + u32 hdbss_buffer_size; + #ifdef CONFIG_PTDUMP_STAGE2_DEBUGFS /* Nested virtualization info */ struct dentry *debugfs_nv_dentry; @@ -844,6 +847,13 @@ struct vcpu_reset_state { bool reset; }; +struct vcpu_hdbss_state { + struct page *hdbss_pg; /* HDBSS buffer */ + u64 hdbssbr_el2; /* programmed into the CPU on load */ + u64 hdbssprod_el2; /* producer index, saved on put */ + unsigned int buddy_order; /* allocation order for __free_pages() */ +}; + struct vncr_tlb; struct kvm_vcpu_arch { @@ -951,6 +961,9 @@ struct kvm_vcpu_arch { /* Hyp-readable copy of kvm_vcpu::pid */ pid_t pid; + + /* HDBSS buffer state */ + struct vcpu_hdbss_state hdbss; }; /* diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h index 7aa08d59d494..7c71560b57e4 100644 --- a/arch/arm64/include/asm/sysreg.h +++ b/arch/arm64/include/asm/sysreg.h @@ -1039,6 +1039,15 @@ #define GCS_CAP(x) ((((unsigned long)x) & GCS_CAP_ADDR_MASK) | \ GCS_CAP_VALID_TOKEN) + +/* + * Definitions for the HDBSS feature + */ +#define HDBSSBR_EL2(baddr, sz) (((baddr) & HDBSSBR_EL2_BADDR_MASK) | \ + FIELD_PREP(HDBSSBR_EL2_SZ_MASK, sz)) + +#define HDBSSPROD_IDX(prod) FIELD_GET(HDBSSPROD_EL2_INDEX_MASK, prod) + /* * Definitions for GICv5 instructions */ diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile index 59612d2f277c..ec2749af64fa 100644 --- a/arch/arm64/kvm/Makefile +++ b/arch/arm64/kvm/Makefile @@ -18,6 +18,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \ guest.o debug.o reset.o sys_regs.o stacktrace.o \ vgic-sys-reg-v3.o fpsimd.o pkvm.o \ arch_timer.o trng.o vmid.o emulate-nested.o nested.o at.o \ + dirty_bit.o \ vgic/vgic.o vgic/vgic-init.o \ vgic/vgic-irqfd.o vgic/vgic-v2.o \ vgic/vgic-v3.o vgic/vgic-v4.o \ diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 1e528d53d093..483b64e0b832 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -36,6 +36,7 @@ #include <asm/virt.h> #include <asm/kvm_arm.h> #include <asm/kvm_asm.h> +#include <asm/kvm_dirty_bit.h> #include <asm/kvm_emulate.h> #include <asm/kvm_hyp.h> #include <asm/kvm_mmu.h> @@ -576,10 +577,19 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) } err = kvm_share_hyp(vcpu, vcpu + 1); - if (err) + if (err) { kvm_vgic_vcpu_destroy(vcpu); + return err; + } - return err; + err = kvm_arm_vcpu_alloc_hdbss(vcpu); + if (err) { + kvm_unshare_hyp(vcpu, vcpu + 1); + kvm_vgic_vcpu_destroy(vcpu); + return err; + } + + return 0; } void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu) diff --git a/arch/arm64/kvm/dirty_bit.c b/arch/arm64/kvm/dirty_bit.c new file mode 100644 index 000000000000..52ece46ddd0c --- /dev/null +++ b/arch/arm64/kvm/dirty_bit.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Per-vCPU HDBSS buffer management. + * + * Copyright (C) 2026 Huawei Technologies Co., Ltd + * Author: Tian Zheng <zhengtian10@huawei.com> + */ + +#include <asm/kvm_dirty_bit.h> +#include <asm/kvm_mmu.h> +#include <asm/sysreg.h> +#include <linux/gfp.h> +#include <linux/kconfig.h> +#include <linux/log2.h> +#include <linux/mm.h> + +int kvm_arm_vcpu_alloc_hdbss(struct kvm_vcpu *vcpu) +{ + struct page *hdbss_pg; + u32 size; + unsigned int buddy_order; + u32 sz_encoded; + + if (vcpu->arch.hdbss.hdbss_pg || !system_supports_hdbss()) + return 0; + + size = kvm_hdbss_buffer_size(vcpu->kvm); + + /* + * HDBSSBR_EL2.SZ is relative to 4KB, the buddy order to + * PAGE_SIZE: they differ on 16KB/64KB granules, and freeing + * with the SZ encoding over-releases. + */ + buddy_order = get_order(size); + sz_encoded = ilog2(size) - 12; + + hdbss_pg = alloc_pages(GFP_KERNEL_ACCOUNT, buddy_order); + if (!hdbss_pg) + return -ENOMEM; + + vcpu->arch.hdbss = (struct vcpu_hdbss_state) { + .hdbss_pg = hdbss_pg, + .hdbssbr_el2 = HDBSSBR_EL2(page_to_phys(hdbss_pg), sz_encoded), + .hdbssprod_el2 = 0, + .buddy_order = buddy_order, + }; + + return 0; +} + +void kvm_arm_vcpu_free_hdbss(struct kvm_vcpu *vcpu) +{ + if (!vcpu->arch.hdbss.hdbss_pg) + return; + + __free_pages(vcpu->arch.hdbss.hdbss_pg, vcpu->arch.hdbss.buddy_order); + + vcpu->arch.hdbss.hdbss_pg = NULL; + vcpu->arch.hdbss.hdbssbr_el2 = 0; +} diff --git a/arch/arm64/kvm/hyp/vhe/switch.c b/arch/arm64/kvm/hyp/vhe/switch.c index 7875911c0506..c575ee1e0c8b 100644 --- a/arch/arm64/kvm/hyp/vhe/switch.c +++ b/arch/arm64/kvm/hyp/vhe/switch.c @@ -19,6 +19,7 @@ #include <asm/cpufeature.h> #include <asm/kprobes.h> #include <asm/kvm_asm.h> +#include <asm/kvm_dirty_bit.h> #include <asm/kvm_emulate.h> #include <asm/kvm_hyp.h> #include <asm/kvm_mmu.h> @@ -219,6 +220,17 @@ static void __vcpu_put_deactivate_traps(struct kvm_vcpu *vcpu) local_irq_restore(flags); } +static void __load_hdbss(struct kvm_vcpu *vcpu) +{ + if (!vcpu_hdbss_enabled(vcpu)) + return; + + write_sysreg_s(vcpu->arch.hdbss.hdbssbr_el2, SYS_HDBSSBR_EL2); + write_sysreg_s(vcpu->arch.hdbss.hdbssprod_el2, SYS_HDBSSPROD_EL2); + + isb(); +} + void kvm_vcpu_load_vhe(struct kvm_vcpu *vcpu) { host_data_ptr(host_ctxt)->__hyp_running_vcpu = vcpu; @@ -226,10 +238,15 @@ void kvm_vcpu_load_vhe(struct kvm_vcpu *vcpu) __vcpu_load_switch_sysregs(vcpu); __vcpu_load_activate_traps(vcpu); __load_stage2(vcpu->arch.hw_mmu); + __load_hdbss(vcpu); } void kvm_vcpu_put_vhe(struct kvm_vcpu *vcpu) { + /* The producer index is per-CPU state, reprogrammed on next load. */ + if (vcpu_hdbss_enabled(vcpu)) + vcpu->arch.hdbss.hdbssprod_el2 = read_sysreg_s(SYS_HDBSSPROD_EL2); + __vcpu_put_deactivate_traps(vcpu); __vcpu_put_switch_sysregs(vcpu); diff --git a/arch/arm64/kvm/reset.c b/arch/arm64/kvm/reset.c index 10eb7249aa9e..05ebe304830e 100644 --- a/arch/arm64/kvm/reset.c +++ b/arch/arm64/kvm/reset.c @@ -25,6 +25,7 @@ #include <asm/ptrace.h> #include <asm/kvm_arm.h> #include <asm/kvm_asm.h> +#include <asm/kvm_dirty_bit.h> #include <asm/kvm_emulate.h> #include <asm/kvm_mmu.h> #include <asm/kvm_nested.h> @@ -149,6 +150,8 @@ void kvm_arm_vcpu_destroy(struct kvm_vcpu *vcpu) free_page((unsigned long)vcpu->arch.ctxt.vncr_array); kfree(vcpu->arch.vncr_tlb); kfree(vcpu->arch.ccsidr); + + kvm_arm_vcpu_free_hdbss(vcpu); } static void kvm_vcpu_reset_sve(struct kvm_vcpu *vcpu) -- 2.33.0
From: Eillon <yezhenyu2@huawei.com> HDBSS entries accumulate in the per-vCPU buffer while the guest runs, and must be drained into the dirty bitmap or dirty ring before userspace can observe them. Once the buffer is full, the architecture no longer promotes writable-clean descriptors, and the write access instead generates a stage-2 Permission fault with ESR_EL2.ISS2.HDBSSF. Drain the buffer at a single point: kvm_arch_vcpu_ioctl_run() flushes it on every VM exit, before the exit reason is handled. The placement inside the run loop is what closes the dirty ring feedback in time: kvm_dirty_ring_push() raises KVM_REQ_DIRTY_RING_SOFT_FULL when the flush pushes the ring past its soft limit, and check_vcpu_requests() observes that at the top of the next loop iteration, so the vCPU exits to userspace for the harvest before re-entering the guest. A flush from kvm_arch_vcpu_put() instead would raise the request outside the run loop, where nothing observes it until the next KVM_RUN entry, delaying the userspace drain by a full round trip. kvm_flush_hdbss_buffer() marks the recorded pages dirty under kvm->srcu, as the memslots it resolves GFNs through are SRCU-protected. It only resets HDBSSPROD_EL2.INDEX, leaving the FSC field alone: a non-zero FSC means an HDBSS access failed, and the error state must survive until the fault handler observes it. kvm_handle_hdbss_fault(), reached from kvm_handle_guest_abort() through the new esr_iss2_is_hdbssf() helper, distinguishes a full buffer (FSC == OK, resume the guest) from external aborts and granule protection check faults: the latter clear FSC to resume hardware dirty state updates and are reported to userspace with -EFAULT. kvm_arch_sync_dirty_log() kicks all vCPUs out of guest mode: the exit path flushes on the way out, and the dirty-log GET/CLEAR protocol tolerates bits set concurrently with the snapshot. Signed-off-by: Eillon <yezhenyu2@huawei.com> Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/include/asm/esr.h | 5 ++ arch/arm64/include/asm/kvm_dirty_bit.h | 16 ++++++ arch/arm64/kvm/arm.c | 20 +++++++ arch/arm64/kvm/dirty_bit.c | 79 ++++++++++++++++++++++++++ arch/arm64/kvm/mmu.c | 4 ++ 5 files changed, 124 insertions(+) diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h index f816f5d77f1a..4b3ccd407faa 100644 --- a/arch/arm64/include/asm/esr.h +++ b/arch/arm64/include/asm/esr.h @@ -437,6 +437,11 @@ #ifndef __ASSEMBLER__ #include <asm/types.h> +static inline bool esr_iss2_is_hdbssf(unsigned long esr) +{ + return !!(ESR_ELx_ISS2(esr) & ESR_ELx_HDBSSF); +} + static inline unsigned long esr_brk_comment(unsigned long esr) { return esr & ESR_ELx_BRK64_ISS_COMMENT_MASK; diff --git a/arch/arm64/include/asm/kvm_dirty_bit.h b/arch/arm64/include/asm/kvm_dirty_bit.h index ee4de4ba8f5c..7319bae2d14e 100644 --- a/arch/arm64/include/asm/kvm_dirty_bit.h +++ b/arch/arm64/include/asm/kvm_dirty_bit.h @@ -13,6 +13,9 @@ #include <asm/sysreg.h> #include <linux/sizes.h> +#define HDBSS_ENTRY_VALID BIT(0) +#define HDBSS_ENTRY_IPA GENMASK_ULL(55, 12) + #define KVM_ARM_HDBSS_DEFAULT_SIZE PAGE_SIZE #define KVM_ARM_HDBSS_MAX_SIZE SZ_2M @@ -22,6 +25,17 @@ static inline u32 kvm_hdbss_buffer_size(struct kvm *kvm) return kvm->arch.hdbss_buffer_size ?: KVM_ARM_HDBSS_DEFAULT_SIZE; } +/* + * VM-level HDBSS check, L1 only: kvm->arch.mmu.vtcr does not follow + * the vCPU into a nested shadow stage-2 MMU, unlike vcpu_hdbss_enabled(). + * Use in VM-level control paths that never touch the HDBSS registers + * directly. + */ +static inline bool kvm_hdbss_enabled(struct kvm *kvm) +{ + return kvm->arch.mmu.vtcr & VTCR_EL2_HDBSS; +} + /* * Tests hw_mmu->vtcr, the stage-2 MMU in use for the vCPU's current * execution context: when running L1, hw_mmu points to kvm->arch.mmu @@ -36,5 +50,7 @@ static inline bool vcpu_hdbss_enabled(struct kvm_vcpu *vcpu) int kvm_arm_vcpu_alloc_hdbss(struct kvm_vcpu *vcpu); void kvm_arm_vcpu_free_hdbss(struct kvm_vcpu *vcpu); +void kvm_flush_hdbss_buffer(struct kvm_vcpu *vcpu); +int kvm_handle_hdbss_fault(struct kvm_vcpu *vcpu); #endif /* __ARM64_KVM_DIRTY_BIT_H__ */ diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 483b64e0b832..8e5af2e3958c 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -1426,6 +1426,14 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu) trace_kvm_exit(ret, kvm_vcpu_trap_get_class(vcpu), *vcpu_pc(vcpu)); + /* + * Drain the HDBSS buffer before the exit is handled, so + * entries pushed to the dirty ring are accounted for by + * dirty_ring_check_request() on the next iteration. + */ + if (vcpu_hdbss_enabled(vcpu)) + kvm_flush_hdbss_buffer(vcpu); + /* Exit types that need handling before we can be preempted */ handle_exit_early(vcpu, ret); @@ -2013,7 +2021,19 @@ long kvm_arch_vcpu_unlocked_ioctl(struct file *filp, unsigned int ioctl, void kvm_arch_sync_dirty_log(struct kvm *kvm, struct kvm_memory_slot *memslot) { + unsigned long i; + struct kvm_vcpu *vcpu; + if (!kvm_hdbss_enabled(kvm)) + return; + + /* + * The buffer is drained on every VM exit, so kicking running + * vCPUs is enough to flush them; the dirty-log GET/CLEAR + * protocol tolerates bits set concurrently with the snapshot. + */ + kvm_for_each_vcpu(i, vcpu, kvm) + kvm_vcpu_kick(vcpu); } static int kvm_vm_ioctl_set_device_addr(struct kvm *kvm, diff --git a/arch/arm64/kvm/dirty_bit.c b/arch/arm64/kvm/dirty_bit.c index 52ece46ddd0c..74b8b4113bda 100644 --- a/arch/arm64/kvm/dirty_bit.c +++ b/arch/arm64/kvm/dirty_bit.c @@ -13,6 +13,7 @@ #include <linux/kconfig.h> #include <linux/log2.h> #include <linux/mm.h> +#include <linux/srcu.h> int kvm_arm_vcpu_alloc_hdbss(struct kvm_vcpu *vcpu) { @@ -58,3 +59,81 @@ void kvm_arm_vcpu_free_hdbss(struct kvm_vcpu *vcpu) vcpu->arch.hdbss.hdbss_pg = NULL; vcpu->arch.hdbss.hdbssbr_el2 = 0; } + +void kvm_flush_hdbss_buffer(struct kvm_vcpu *vcpu) +{ + int idx, curr_idx; + u64 prod; + u32 entries; + u64 *hdbss_buf; + struct kvm *kvm = vcpu->kvm; + int srcu_idx; + + if (!vcpu_hdbss_enabled(vcpu)) + return; + + prod = read_sysreg_s(SYS_HDBSSPROD_EL2); + curr_idx = HDBSSPROD_IDX(prod); + + if (curr_idx == 0 || !vcpu->arch.hdbss.hdbss_pg) + return; + + hdbss_buf = page_address(vcpu->arch.hdbss.hdbss_pg); + if (!hdbss_buf) + return; + + entries = kvm_hdbss_buffer_size(kvm) / sizeof(u64); + + /* kvm_vcpu_mark_page_dirty() resolves the memslot under SRCU. */ + srcu_idx = srcu_read_lock(&kvm->srcu); + for (idx = 0; idx < min_t(u32, curr_idx, entries); idx++) { + u64 gpa; + + gpa = hdbss_buf[idx]; + if (!(gpa & HDBSS_ENTRY_VALID)) + continue; + + gpa &= HDBSS_ENTRY_IPA; + kvm_vcpu_mark_page_dirty(vcpu, gpa >> PAGE_SHIFT); + } + srcu_read_unlock(&kvm->srcu, srcu_idx); + + /* + * Reset the index, keeping FSC: a non-zero FSC flags a failed + * HDBSS access and must survive until the fault handler has + * observed it. + */ + prod &= ~HDBSSPROD_EL2_INDEX_MASK; + write_sysreg_s(prod, SYS_HDBSSPROD_EL2); + vcpu->arch.hdbss.hdbssprod_el2 = prod; + isb(); +} + +int kvm_handle_hdbss_fault(struct kvm_vcpu *vcpu) +{ + u64 prod; + u64 fsc; + + if (WARN_ON_ONCE(!system_supports_hdbss())) + return -EFAULT; + + if (WARN_ON_ONCE(!vcpu_hdbss_enabled(vcpu))) + return -EFAULT; + + prod = read_sysreg_s(SYS_HDBSSPROD_EL2); + fsc = FIELD_GET(HDBSSPROD_EL2_FSC_MASK, prod); + + if (fsc == HDBSSPROD_EL2_FSC_OK) + /* Buffer full: the exit path drained it before handle_exit. */ + return 1; + + if (fsc != HDBSSPROD_EL2_FSC_ExternalAbort && + fsc != HDBSSPROD_EL2_FSC_GPF) + WARN_ONCE(1, + "Unexpected HDBSS fault type, FSC: 0x%llx (prod=0x%llx, vcpu=%d)\n", + fsc, prod, vcpu->vcpu_id); + + /* Clear FSC so hardware dirty state updates can resume. */ + write_sysreg_s(prod & ~HDBSSPROD_EL2_FSC_MASK, SYS_HDBSSPROD_EL2); + return -EFAULT; +} diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index b1a6f743be7b..a9b35adf962f 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -15,6 +15,7 @@ #include <asm/pgalloc.h> #include <asm/cacheflush.h> #include <asm/kvm_arm.h> +#include <asm/kvm_dirty_bit.h> #include <asm/kvm_mmu.h> #include <asm/kvm_pgtable.h> #include <asm/kvm_pkvm.h> @@ -2313,6 +2314,9 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu) is_iabt = kvm_vcpu_trap_is_iabt(vcpu); + if (esr_iss2_is_hdbssf(esr)) + return kvm_handle_hdbss_fault(vcpu); + if (esr_fsc_is_translation_fault(esr)) { /* Beyond sanitised PARange (which is the IPA limit) */ if (fault_ipa >= BIT_ULL(get_kvm_ipa_limit())) { -- 2.33.0
An architecture that keeps a CPU-side dirty log buffer drained into the dirty ring must size that buffer with the ring in mind, as the ring reserves room for a full buffer flush. Give architectures a chance to react when kvm->dirty_ring_size is set: call a new kvm_arch_dirty_ring_size_updated() hook right after the size is recorded, with a __weak no-op default so this changes nothing for existing architectures. arm64 will use this to pin its HDBSS buffer size in dirty-ring mode. Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- include/linux/kvm_dirty_ring.h | 1 + virt/kvm/dirty_ring.c | 4 ++++ virt/kvm/kvm_main.c | 1 + 3 files changed, 6 insertions(+) diff --git a/include/linux/kvm_dirty_ring.h b/include/linux/kvm_dirty_ring.h index eb10d87adf7d..c2b922791530 100644 --- a/include/linux/kvm_dirty_ring.h +++ b/include/linux/kvm_dirty_ring.h @@ -73,6 +73,7 @@ static inline void kvm_dirty_ring_free(struct kvm_dirty_ring *ring) #else /* CONFIG_HAVE_KVM_DIRTY_RING */ int kvm_cpu_dirty_log_size(struct kvm *kvm); +void kvm_arch_dirty_ring_size_updated(struct kvm *kvm); bool kvm_use_dirty_bitmap(struct kvm *kvm); bool kvm_arch_allow_write_without_running_vcpu(struct kvm *kvm); u32 kvm_dirty_ring_get_rsvd_entries(struct kvm *kvm); diff --git a/virt/kvm/dirty_ring.c b/virt/kvm/dirty_ring.c index 572b854edf74..784f53c95b54 100644 --- a/virt/kvm/dirty_ring.c +++ b/virt/kvm/dirty_ring.c @@ -16,6 +16,10 @@ int __weak kvm_cpu_dirty_log_size(struct kvm *kvm) return 0; } +void __weak kvm_arch_dirty_ring_size_updated(struct kvm *kvm) +{ +} + u32 kvm_dirty_ring_get_rsvd_entries(struct kvm *kvm) { return KVM_DIRTY_RING_RSVD_ENTRIES + kvm_cpu_dirty_log_size(kvm); diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 65eb26a0520d..7d574fb29a8f 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -4988,6 +4988,7 @@ static int kvm_vm_ioctl_enable_dirty_log_ring(struct kvm *kvm, u32 size) r = -EINVAL; } else { kvm->dirty_ring_size = size; + kvm_arch_dirty_ring_size_updated(kvm); r = 0; } -- 2.33.0
The dirty ring reserves room for CPU-side dirty buffers: rsvd = KVM_DIRTY_RING_RSVD_ENTRIES + kvm_cpu_dirty_log_size(), which the framework subtracts from the ring size to obtain the soft_limit - the fill level at which the vCPU exits to userspace to have the ring harvested. Wire HDBSS into this scheme by reporting the buffer entry count through a new kvm_cpu_dirty_log_size() implementation, so a full buffer flush always finds room in the ring. Pin the HDBSS buffer size to PAGE_SIZE in dirty-ring mode, using the kvm_arch_dirty_ring_size_updated() hook: the ring-side reservation means a larger buffer would only shrink the soft_limit and force more frequent userspace drains, so the smallest buffer gives the best drain interval. The pin is unconditional - a buffer size configured before the ring was enabled is reset to the default, and the buffer-size capability is rejected once the ring is enabled. Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/kvm/arm.c | 13 +++++++++++++ arch/arm64/kvm/mmu.c | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 8e5af2e3958c..facc53991998 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -215,6 +215,19 @@ static int kvm_arm_default_max_vcpus(void) return vgic_present ? kvm_vgic_get_max_vcpus() : KVM_MAX_VCPUS; } +/* + * A larger buffer takes more reserved room in the ring, so the + * ring reports full and forces a userspace drain sooner: the + * smallest buffer drains the least often. + */ +void kvm_arch_dirty_ring_size_updated(struct kvm *kvm) +{ + if (!system_supports_hdbss()) + return; + + kvm->arch.hdbss_buffer_size = KVM_ARM_HDBSS_DEFAULT_SIZE; +} + /** * kvm_arch_init_vm - initializes a VM data structure * @kvm: pointer to the KVM struct diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index a9b35adf962f..2b915468521c 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -2839,3 +2839,11 @@ void kvm_toggle_cache(struct kvm_vcpu *vcpu, bool was_enabled) trace_kvm_toggle_cache(*vcpu_pc(vcpu), was_enabled, now_enabled); } + +int kvm_cpu_dirty_log_size(struct kvm *kvm) +{ + if (!system_supports_hdbss()) + return 0; + + return kvm_hdbss_buffer_size(kvm) / sizeof(u64); +} -- 2.33.0
Hardware dirty management of the stage-2 descriptors is currently enabled or disabled by two independent paths that pull VTCR_EL2 in opposite directions. Leonardo Bras' "Enable HAFDBS for guests not on migration" turns HD on outside of migration and off once any memslot starts logging, so the write-protect permission fault remains the dirty tracking signal. The HDBSS enablement turns HD|HA|HDBSS on when logging starts, so hardware promotes writable-clean pages and records the dirtied IPAs into the per-vCPU buffers instead. Both hooks run from kvm_arch_commit_memory_region(), so whichever runs second wins, and the two independent read-modify-write cycles also race against the vCPU setup path, which could leave HDBSS set with HD clear - an illegal VTCR_EL2 combination. Replace both with kvm_arch_update_hw_dirty_mode(), a single derived mode that is a pure function of the static capabilities and the number of logging memslots: - logging && HDBSS-capable -> HD|HA|HDBSS (hardware tracking) - logging, no HDBSS -> off (classic write-protect faults) - !logging && HAFDBS-cap. -> HD|HA (only written pages go dirty) Because the mode is recomputed rather than toggled, there is no lost-update window to lock against, and a vCPU created while the VM is migrating inherits the current mode instead of clobbering it. The HDBSS leg is gated on !kvm_vcpu_has_nv(), matching the HAFDBS leg: nested shadow stage-2 MMUs do not carry the HDBSS bits. Always set HA together with HD: FEAT_HDBSS requires VTCR_EL2.{HDBSS,HA,HD} to be all set, and neither the kernel's own stage-1 enablement (cpu_enable_hw_dbm()) nor the SMMUv3 driver ever enables dirty updates without access-flag updates. Hardware-managed AF also removes the access-flag exits on mmu-notifier aged pages. The mode switch issues a single KVM_REQ_RELOAD_STAGE2 followed by a VMID-wide TLB invalidation: the request only reloads VTCR_EL2, while cached translations created under the old mode survive until invalidated, mirroring cpu_enable_hw_dbm()'s pairing of the TCR_EL1.HD flip with local_flush_tlb_all(). In the logging-stop direction (HD back on) stale read-only translations of writable-clean pages would otherwise keep faulting until each page takes its next fault. kvm_s2_fault_compute_prot() decides whether a read-faulted page may stay writable-clean based on the live VTCR_EL2.HD state rather than the static capability: QEMU keeps KVM_MEM_LOG_DIRTY_PAGES on the VGA VRAM slot for the entire runtime of a desktop VM, so with HD off for the whole VM but the static check still true, every read fault on the non-logged RAM slots would install writable-clean - read-only under HD=0 - and pay one extra permission fault per page. kvm_arch_destroy_vm() forces every bit off via kvm_arm_disable_hdbss_global(), as userspace may destroy the VM without ever stopping dirty logging. This is a rework of Leonardo Bras' "Enable HAFDBS for guests not on migration", which introduced the VHE-only HAFDBS enablement, the vcpu-setup hook and kvm_set_hafdbs(). Link: https://lore.kernel.org/all/20260901171558.2674031-6-leo.bras@arm.com/ Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/include/asm/kvm_dirty_bit.h | 1 + arch/arm64/include/asm/kvm_mmu.h | 31 +++++++++ arch/arm64/include/asm/kvm_nested.h | 9 ++- arch/arm64/kvm/arm.c | 17 +++++ arch/arm64/kvm/dirty_bit.c | 30 +++++++- arch/arm64/kvm/hyp/pgtable.c | 14 ++-- arch/arm64/kvm/mmu.c | 95 ++++++++++++++++++++++++-- 7 files changed, 181 insertions(+), 16 deletions(-) diff --git a/arch/arm64/include/asm/kvm_dirty_bit.h b/arch/arm64/include/asm/kvm_dirty_bit.h index 7319bae2d14e..e334b2c3657c 100644 --- a/arch/arm64/include/asm/kvm_dirty_bit.h +++ b/arch/arm64/include/asm/kvm_dirty_bit.h @@ -52,5 +52,6 @@ int kvm_arm_vcpu_alloc_hdbss(struct kvm_vcpu *vcpu); void kvm_arm_vcpu_free_hdbss(struct kvm_vcpu *vcpu); void kvm_flush_hdbss_buffer(struct kvm_vcpu *vcpu); int kvm_handle_hdbss_fault(struct kvm_vcpu *vcpu); +void kvm_arm_disable_hdbss_global(struct kvm *kvm); #endif /* __ARM64_KVM_DIRTY_BIT_H__ */ diff --git a/arch/arm64/include/asm/kvm_mmu.h b/arch/arm64/include/asm/kvm_mmu.h index 6eae7e7e2a68..c9bc38478f0a 100644 --- a/arch/arm64/include/asm/kvm_mmu.h +++ b/arch/arm64/include/asm/kvm_mmu.h @@ -390,6 +390,37 @@ static inline bool kvm_supports_cacheable_pfnmap(void) cpus_have_final_cap(ARM64_HAS_CACHE_DIC); } +static inline bool kvm_supports_hafdbs(struct kvm *kvm) +{ + return IS_ENABLED(CONFIG_ARM64_HW_AFDBM) && has_vhe() && + !kvm_vcpu_has_nv(kvm) && cpus_have_final_cap(ARM64_HW_DBM); +} + +/* + * Whether this VM can use FEAT_HDBSS for hardware dirty tracking: + * system support (VHE-only, the buffer registers are EL2-only and are + * programmed from host context) and no nested virtualization, whose + * shadow stage-2 MMUs do not carry the HDBSS bits. + */ +static inline bool kvm_supports_hdbss(struct kvm *kvm) +{ + return system_supports_hdbss() && !kvm_vcpu_has_nv(kvm); +} + +void kvm_arch_update_hw_dirty_mode(struct kvm *kvm); + +/* + * Live counterpart of kvm_supports_hafdbs(): reports whether hardware + * dirty management is actually enabled for this VM right now. The HD + * bit is flipped on/off with dirty logging (and HDBSS), so decisions + * that depend on it must observe the dynamic state, not the static + * capability. + */ +static inline bool kvm_hw_dirty_enabled(struct kvm *kvm) +{ + return kvm->arch.mmu.vtcr & VTCR_EL2_HD; +} + #ifdef CONFIG_PTDUMP_STAGE2_DEBUGFS void kvm_s2_ptdump_create_debugfs(struct kvm *kvm); void kvm_nested_s2_ptdump_create_debugfs(struct kvm_s2_mmu *mmu); diff --git a/arch/arm64/include/asm/kvm_nested.h b/arch/arm64/include/asm/kvm_nested.h index 1ed708335809..9242b5d665af 100644 --- a/arch/arm64/include/asm/kvm_nested.h +++ b/arch/arm64/include/asm/kvm_nested.h @@ -7,11 +7,16 @@ #include <asm/kvm_emulate.h> #include <asm/kvm_pgtable.h> -static inline bool vcpu_has_nv(const struct kvm_vcpu *vcpu) +static inline bool kvm_vcpu_has_nv(const struct kvm *kvm) { return (!__is_defined(__KVM_NVHE_HYPERVISOR__) && cpus_have_final_cap(ARM64_HAS_NESTED_VIRT) && - vcpu_has_feature(vcpu, KVM_ARM_VCPU_HAS_EL2)); + kvm_vcpu_has_feature(kvm, KVM_ARM_VCPU_HAS_EL2)); +} + +static inline bool vcpu_has_nv(const struct kvm_vcpu *vcpu) +{ + return kvm_vcpu_has_nv(vcpu->kvm); } /* Translation helpers from non-VHE EL2 to EL1 */ diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index facc53991998..dadb1e3990f4 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -343,6 +343,15 @@ void kvm_arch_destroy_vm(struct kvm *kvm) if (is_protected_kvm_enabled()) pkvm_destroy_hyp_vm(kvm); + /* + * Userspace may destroy the VM without disabling dirty + * logging, so the auto-disable path is never reached. Force + * disable HDBSS before the stage-2 MMU and the vCPU buffers + * go away. + */ + if (kvm_hdbss_enabled(kvm)) + kvm_arm_disable_hdbss_global(kvm); + kvm_uninit_stage2_mmu(kvm); kvm_destroy_mpidr_data(kvm); @@ -1714,6 +1723,14 @@ static int kvm_setup_vcpu(struct kvm_vcpu *vcpu) if (!ret && vcpu_has_nv(vcpu)) ret = kvm_vcpu_init_nested(vcpu); + /* + * Derive the hardware dirty mode (HAFDBS while not logging, HDBSS + * or off during migration). Being a pure function of the logging + * state and the static capabilities, a vCPU created while the VM + * is migrating keeps the current mode instead of clobbering it. + */ + kvm_arch_update_hw_dirty_mode(kvm); + return ret; } diff --git a/arch/arm64/kvm/dirty_bit.c b/arch/arm64/kvm/dirty_bit.c index 74b8b4113bda..923f9301ca99 100644 --- a/arch/arm64/kvm/dirty_bit.c +++ b/arch/arm64/kvm/dirty_bit.c @@ -84,7 +84,15 @@ void kvm_flush_hdbss_buffer(struct kvm_vcpu *vcpu) entries = kvm_hdbss_buffer_size(kvm) / sizeof(u64); - /* kvm_vcpu_mark_page_dirty() resolves the memslot under SRCU. */ + /* + * kvm_vcpu_mark_page_dirty() resolves the memslot under SRCU. The + * host folio account is deliberately not touched here: with the + * speculative folio dirtying at fault-in time and the unmap / + * write-protect walkers harvesting the stage-2 dirty bit, every + * exit from the writable-dirty state is already covered, so a + * folio harvest in the drain would be redundant - and keeping + * this path free of mmu_lock preserves its SRCU-only design. + */ srcu_idx = srcu_read_lock(&kvm->srcu); for (idx = 0; idx < min_t(u32, curr_idx, entries); idx++) { u64 gpa; @@ -137,3 +145,23 @@ int kvm_handle_hdbss_fault(struct kvm_vcpu *vcpu) write_sysreg_s(prod & ~HDBSSPROD_EL2_FSC_MASK, SYS_HDBSSPROD_EL2); return -EFAULT; } + +/* + * VM teardown path: userspace may destroy the VM without ever + * stopping dirty logging, in which case kvm_arch_update_hw_dirty_mode() + * would keep deriving the logging-based mode. Force every hardware + * dirty bit off before the stage-2 MMU and the vCPU buffers go away. + */ +void kvm_arm_disable_hdbss_global(struct kvm *kvm) +{ + unsigned long dirty_bits = + VTCR_EL2_HD | VTCR_EL2_HDBSS | VTCR_EL2_HA; + + if (!(kvm->arch.mmu.vtcr & dirty_bits)) + return; + + kvm->arch.mmu.vtcr &= ~dirty_bits; + + kvm_make_all_cpus_request(kvm, KVM_REQ_RELOAD_STAGE2); + kvm_flush_remote_tlbs(kvm); +} diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c index 1e7583c5abf9..08cfce90396c 100644 --- a/arch/arm64/kvm/hyp/pgtable.c +++ b/arch/arm64/kvm/hyp/pgtable.c @@ -1191,9 +1191,9 @@ static int stage2_unmap_walker(const struct kvm_pgtable_visit_ctx *ctx, * Harvest the dirty state into the host folio account * before the mapping goes away, mirroring * zap_present_folio_ptes() in the generic mm: S2AP[1] may - * have been set by hardware (FEAT_HAFDBS) without KVM ever - * observing a write fault, so this is the last chance to - * account it. Block mappings always map a folio-aligned + * have been set by hardware (HAFDBS/HDBSS) without KVM + * ever observing a write fault, so this is the last chance + * to account it. Block mappings always map a folio-aligned * head page, so dirtying the head covers the whole block. */ if ((ctx->old & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W) && @@ -1319,10 +1319,10 @@ static int stage2_wrprotect_walker(const struct kvm_pgtable_visit_ctx *ctx, /* * A writable-dirty page is about to lose its dirty state. If - * hardware promoted it, S2AP[1] is the only record of the write - * outside the PTE: harvest the folio account before the clear so - * the dirty information survives the transition regardless of - * what happens to the mapping afterwards. + * hardware promoted it (HAFDBS/HDBSS), the pending buffer entry + * has not been drained yet and the CLEAR ioctl can race with an + * unmap that follows; harvest the folio account now so the dirty + * information survives the transition regardless. */ if (kvm_pte_valid(ctx->old) && ctx->old != new && (ctx->old & KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W) && diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 2b915468521c..42cc35dde736 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -893,7 +893,7 @@ static int get_user_mapping_size(struct kvm *kvm, u64 addr) * Harvest the stage-2 dirty state of a mapping into the host folio * account, wired into kvm_s2_mm_ops::mark_page_dirty for the unmap and * write-protect walkers. The dirty bit may have been set by hardware - * (FEAT_HAFDBS) without KVM ever observing a write fault, so this is + * (HAFDBS/HDBSS) without KVM ever observing a write fault, so this is * the exact accounting, unlike the speculative folio dirtying at * fault-in time. MMIO/PFNMAP ranges and reserved pages are outside the * page-dirty machinery (mirrors kvm_is_ad_tracked_page() in @@ -1712,10 +1712,17 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd) out_unlock: /* * Two distinct dirty ledgers with different precision requirements: - * the host folio account (SetPageDirty) must be speculative, as a - * writable-clean mapping can be hardware-promoted to writable-dirty - * without any VM exit once VTCR_EL2.HD is set; the dirty bitmap must - * remain exact, or pre-copy will ship clean pages. + * + * - the host folio account (SetPageDirty) must be *speculative*: + * a writable-clean mapping can be hardware-promoted to writable + * dirty without any VM exit once VTCR_EL2.HD is set, so any + * mapping that grants write permission must dirty the folio at + * fault-in time. Over-marking is safe, it only causes a spurious + * writeback. + * + * - the KVM dirty bitmap must remain *exact*: a read-faulted + * writable-clean page must not enter the bitmap or pre-copy will + * ship clean pages. */ kvm_release_faultin_page(kvm, page, !!ret, prot & KVM_PGTABLE_PROT_W); kvm_fault_unlock(kvm); @@ -2023,7 +2030,20 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd, if (s2vi->map_writable) { *prot |= KVM_PGTABLE_PROT_W; - if (s2vi->device || !memslot_is_logging(s2fd->memslot) || + /* + * Check the *live* HD state, not the static capability: HD + * is turned off for the whole VM as soon as any single + * memslot starts logging, and QEMU keeps + * KVM_MEM_LOG_DIRTY_PAGES on the VGA VRAM slot for the + * entire runtime of a desktop VM. With HD off but the static + * check still true, every read fault on the non-logged RAM + * slots would install writable-clean - which is read-only + * under HD=0 - and pay an extra permission fault per page. + * A racing flip is benign: at worst one page is mapped + * writable-dirty one last time or takes one extra fault. + */ + if (s2vi->device || + !(memslot_is_logging(s2fd->memslot) || kvm_hw_dirty_enabled(kvm)) || kvm_is_write_fault(s2fd->vcpu)) *prot |= KVM_PGTABLE_PROT_DIRTY; } @@ -2615,6 +2635,66 @@ int __init kvm_mmu_init(u32 hyp_va_bits) return err; } +/* + * The VM's hardware dirty-management mode is a *derived* value, never + * an independently managed one: it depends only on the static + * capabilities and the number of logging memslots, so recomputing it + * on every event (vCPU setup, logging start/stop) cannot lose an + * update and needs no extra locking against racing writers: + * + * logging && HDBSS-capable -> HDBSS (HD|HA|HDBSS): hardware + * promotes writable-clean pages and + * records the dirtied IPAs into the + * per-vCPU buffers, which replace + * the write-fault as the tracking + * signal. + * logging, no HDBSS -> off: the write-protect fault is the + * tracking signal, so HD must be off + * or hardware would promote + * writable-clean pages silently. + * !logging, HAFDBS-capable -> HAFDBS (HD|HA): read faults install + * writable-clean pages, and only + * pages actually written to become + * dirty. + */ +void kvm_arch_update_hw_dirty_mode(struct kvm *kvm) +{ + unsigned long cur, target; + bool logging = atomic_read(&kvm->nr_memslots_dirty_logging) != 0; + + if (logging && kvm_supports_hdbss(kvm)) + target = VTCR_EL2_HD | VTCR_EL2_HA | VTCR_EL2_HDBSS; + else if (logging || !kvm_supports_hafdbs(kvm)) + target = 0; + else + target = VTCR_EL2_HD | VTCR_EL2_HA; + + cur = kvm->arch.mmu.vtcr & (VTCR_EL2_HD | VTCR_EL2_HA | VTCR_EL2_HDBSS); + if (cur == target) + return; + + kvm->arch.mmu.vtcr = (kvm->arch.mmu.vtcr & + ~(VTCR_EL2_HD | VTCR_EL2_HA | VTCR_EL2_HDBSS)) | + target; + + kvm_make_all_cpus_request(kvm, KVM_REQ_RELOAD_STAGE2); + + /* + * The request above only reloads VTCR_EL2; cached translations + * created under the old mode survive until invalidated. In the + * logging-start direction the subsequent write-protect + + * kvm_flush_remote_tlbs_memslot() happens to cover the logging + * memslot, but in the logging-stop direction (HD back on) stale + * read-only translations of writable-clean pages would keep + * faulting until each page takes its next fault. Mirror the + * kernel's own stage-1 enablement (cpu_enable_hw_dbm() pairs the + * TCR_EL1.HD flip with local_flush_tlb_all()) and flush the VMID + * once every vCPU has reloaded the new VTCR. __kvm_tlb_flush_vmid + * broadcasts inner-shareable, so one CPU executing it suffices. + */ + kvm_flush_remote_tlbs(kvm); +} + void kvm_arch_commit_memory_region(struct kvm *kvm, struct kvm_memory_slot *old, const struct kvm_memory_slot *new, @@ -2622,6 +2702,9 @@ void kvm_arch_commit_memory_region(struct kvm *kvm, { bool log_dirty_pages = new && new->flags & KVM_MEM_LOG_DIRTY_PAGES; + /* Derive the hardware dirty mode from the new logging state. */ + kvm_arch_update_hw_dirty_mode(kvm); + /* * At this point memslot has been committed and there is an * allocated dirty_bitmap[], dirty pages will be tracked while the -- 2.33.0
In dirty-bitmap mode, a larger HDBSS buffer lets the guest dirty more pages between VM exits, trading memory for fewer buffer drains during live migration. The default of one page per vCPU is a reasonable starting point, so allow userspace to opt into a larger buffer with KVM_CAP_ARM_HDBSS_BUFFER_SIZE. The size is specified in bytes via KVM_ENABLE_CAP and must be a power of two in the range [PAGE_SIZE, SZ_2M]: the upper bound is the largest HDBSSBR_EL2.SZ encoding, and the power-of-two requirement matches the buddy allocator. The setting must be applied before any vCPU is created, as the buffers are allocated per vCPU; attempts to change it after vCPUs exist or after it has already been set return -EINVAL and -EBUSY respectively. The capability is rejected in dirty-ring mode, where the buffer size is pinned to the default by kvm_arch_dirty_ring_size_updated(). KVM_CHECK_EXTENSION returns the configured size for a specific VM, or the maximum supported size (SZ_2M) when queried globally with a NULL kvm argument. Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/kvm/arm.c | 34 ++++++++++++++++++++++++++++++++++ include/uapi/linux/kvm.h | 1 + 2 files changed, 35 insertions(+) diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index dadb1e3990f4..1737dbe13419 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -19,6 +19,7 @@ #include <linux/kvm.h> #include <linux/kvm_irqfd.h> #include <linux/irqbypass.h> +#include <linux/log2.h> #include <linux/sched/stat.h> #include <linux/psci.h> #include <trace/events/kvm.h> @@ -203,6 +204,31 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm, r = 0; set_bit(KVM_ARCH_FLAG_EXIT_SEA, &kvm->arch.flags); break; + case KVM_CAP_ARM_HDBSS_BUFFER_SIZE: { + u64 size = cap->args[0]; + + if (!system_supports_hdbss()) + break; + if (kvm->dirty_ring_size) + break; + if (size < KVM_ARM_HDBSS_DEFAULT_SIZE || + size > KVM_ARM_HDBSS_MAX_SIZE) + break; + if (!is_power_of_2(size)) + break; + + mutex_lock(&kvm->lock); + if (kvm->created_vcpus) { + r = -EINVAL; + } else if (kvm->arch.hdbss_buffer_size) { + r = -EBUSY; + } else { + kvm->arch.hdbss_buffer_size = size; + r = 0; + } + mutex_unlock(&kvm->lock); + break; + } default: break; } @@ -510,6 +536,14 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) else r = KVM_ARM_EAGER_SPLIT_CHUNK_SIZE_DEFAULT; break; + case KVM_CAP_ARM_HDBSS_BUFFER_SIZE: + if (!system_supports_hdbss()) + r = 0; + else if (kvm) + r = kvm->arch.hdbss_buffer_size ?: KVM_ARM_HDBSS_DEFAULT_SIZE; + else + r = KVM_ARM_HDBSS_MAX_SIZE; + break; case KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES: r = kvm_supported_block_sizes(); break; diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index ac2d77d14963..59eb211494e4 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -999,6 +999,7 @@ struct kvm_enable_cap { #define KVM_CAP_S390_HPAGE_2G 249 #define KVM_CAP_PPC_COMPAT_CAPS 250 #define KVM_CAP_ARM_PMU_V3_STRICT 251 +#define KVM_CAP_ARM_HDBSS_BUFFER_SIZE 252 struct kvm_irq_routing_irqchip { __u32 irqchip; -- 2.33.0
Document the KVM_CAP_ARM_HDBSS_BUFFER_SIZE capability, which allows userspace to configure the per-vCPU hardware dirty state tracking structure (HDBSS) buffer size used for hardware-assisted dirty page tracking during live migration. The capability applies to dirty-bitmap mode only: it is rejected once the dirty ring is enabled, and enabling the dirty ring after a size was configured resets it to the default, which is what the ring mode uses in any case. Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- Documentation/virt/kvm/api.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index e0430cc750c9..14588c5a480b 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -9056,6 +9056,36 @@ enabled, cmma can't be enabled anymore and pfmfi and the storage key interpretation are disabled. If cmma has already been enabled or the hpage_2g module parameter is not set to 1, -EINVAL is returned. +7.48 KVM_CAP_ARM_HDBSS_BUFFER_SIZE +----------------------------------- + +:Architectures: arm64 +:Target: VM +:Parameters: args[0] is the per-vCPU HDBSS buffer size in bytes +:Returns: 0 on success; -EINVAL if the size is invalid or vCPUs have already + been created; -EBUSY if the buffer size was already configured. + +This capability configures the hardware dirty state tracking structure +(HDBSS) buffer size used for hardware-assisted dirty page tracking during +live migration. + +When live migration is initiated, userspace can configure the per-vCPU HDBSS +buffer size through this capability; KVM will allocate per-vCPU HDBSS buffers +of the requested size. + +The size must be a power of two between the default (``PAGE_SIZE``) and the +maximum (2MB). This capability applies to dirty-bitmap mode only: it is +rejected with -EINVAL once the dirty ring (``KVM_CAP_DIRTY_LOG_RING``) is +enabled, and enabling the dirty ring after a size was configured resets it +to the default, which is what the ring mode uses in any case. + +This capability may only be set before any vCPUs are created. Setting the +size a second time is rejected with -EBUSY. + +KVM_CHECK_EXTENSION for this capability returns the maximum supported buffer +size when queried without a VM, or the currently configured per-VM buffer size +(defaulting to ``PAGE_SIZE``) when queried with a VM. + 8. Other capabilities. ====================== -- 2.33.0
HDBSS is enabled by flipping VTCR_EL2.HDBSS (kvm_arch_update_hw_dirty_mode()) and issuing KVM_REQ_RELOAD_STAGE2, whose handler reloads VTCR/VTTBR through __load_stage2(). HDBSSBR_EL2/HDBSSPROD_EL2 are per-CPU registers binding the hardware dirty-state buffer to the running vCPU, but __load_hdbss() skipped programming them while HDBSS was disabled -- so a vCPU that was mid-KVM_RUN when dirty logging started re-entered the guest with HDBSS active but a stale buffer address: the reset value after a cold boot, or the freed (and possibly reused) buffer page of a vCPU from a previous VM on this host. Hardware dirty-state updates are silent writable-clean to writable-dirty promotions: the MMU writes the dirtied IPA to HDBSSBR_EL2.BADDR + HDBSSPROD_EL2.INDEX * 8 without any fault, so the entries landed in unrelated physical memory while kvm_flush_hdbss_buffer() read the vCPU's own software page -- the real entries were lost forever, and with them any chance of the pages being write-protected again. Everything written after the migration bulk snapshot was silently lost and the destination VM ran on stale memory. Fix it in the loader itself rather than at each VTCR-flipping site: __load_hdbss() now programs the registers whenever the vCPU owns a buffer (hdbss_pg), regardless of HDBSS enablement. Since kvm_arch_vcpu_load() runs on every KVM_RUN entry and every sched-in, the registers always hold the running vCPU's buffer by the time the guest can execute -- including the moment KVM_REQ_RELOAD_STAGE2 turns HDBSS on mid-KVM_RUN, which therefore needs no special handling. The registers are plain storage while the feature is off, and this is off the hot path. Save the producer index in kvm_vcpu_put_vhe() under the same ownership condition, so the software copy stays live across VTCR_EL2.HDBSS epochs instead of freezing with a stale index from a previous one. Also zero the HDBSS buffer pages at allocation, so a stale HDBSSPROD_EL2.INDEX can only ever observe invalid entries instead of pushing leftover page contents into the dirty ring as GFNs. This manifests with 4K stage-2 mappings plus the dirty ring (eager write-protect leaves level-3 PTEs writable-clean, and the dirty-ring QEMU path does not enable KVM_DIRTY_LOG_INITIALLY_SET); the dirty-bitmap path skips the eager write-protect and huge pages fault in software before any writable-clean page exists. The equivalent fix on the code_v5_2_tested branch (d52cdc8ea269: program the registers in the RELOAD handler) was validated empirically: the crash no longer reproduces. Signed-off-by: Tian Zheng <zhengtian10@huawei.com> --- arch/arm64/kvm/dirty_bit.c | 8 +++++++- arch/arm64/kvm/hyp/vhe/switch.c | 22 +++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/arch/arm64/kvm/dirty_bit.c b/arch/arm64/kvm/dirty_bit.c index 923f9301ca99..205b5f7a2805 100644 --- a/arch/arm64/kvm/dirty_bit.c +++ b/arch/arm64/kvm/dirty_bit.c @@ -35,7 +35,13 @@ int kvm_arm_vcpu_alloc_hdbss(struct kvm_vcpu *vcpu) buddy_order = get_order(size); sz_encoded = ilog2(size) - 12; - hdbss_pg = alloc_pages(GFP_KERNEL_ACCOUNT, buddy_order); + /* + * The hardware fills this buffer based on HDBSSPROD_EL2.INDEX and + * the flush reads as many entries: zero it so a stale index can + * only ever observe invalid entries (bit 0 clear) instead of + * pushing leftover page contents into the dirty ring as GFNs. + */ + hdbss_pg = alloc_pages(GFP_KERNEL_ACCOUNT | __GFP_ZERO, buddy_order); if (!hdbss_pg) return -ENOMEM; diff --git a/arch/arm64/kvm/hyp/vhe/switch.c b/arch/arm64/kvm/hyp/vhe/switch.c index c575ee1e0c8b..e4dfe439740a 100644 --- a/arch/arm64/kvm/hyp/vhe/switch.c +++ b/arch/arm64/kvm/hyp/vhe/switch.c @@ -220,9 +220,19 @@ static void __vcpu_put_deactivate_traps(struct kvm_vcpu *vcpu) local_irq_restore(flags); } +/* + * Program the buffer registers whenever the vCPU owns a buffer, + * regardless of whether HDBSS is currently enabled: the registers are + * plain storage while the feature is off, and keeping them bound to + * the running vCPU across VTCR_EL2.HDBSS transitions means any path + * that flips the VTCR bits (KVM_REQ_RELOAD_STAGE2) finds them already + * pointing at the right buffer -- stale cross-VM addresses, or the + * reset value after a cold boot, can never be observed with HDBSS + * active. + */ static void __load_hdbss(struct kvm_vcpu *vcpu) { - if (!vcpu_hdbss_enabled(vcpu)) + if (!vcpu->arch.hdbss.hdbss_pg) return; write_sysreg_s(vcpu->arch.hdbss.hdbssbr_el2, SYS_HDBSSBR_EL2); @@ -243,8 +253,14 @@ void kvm_vcpu_load_vhe(struct kvm_vcpu *vcpu) void kvm_vcpu_put_vhe(struct kvm_vcpu *vcpu) { - /* The producer index is per-CPU state, reprogrammed on next load. */ - if (vcpu_hdbss_enabled(vcpu)) + /* + * The producer index is per-CPU state, reprogrammed on next load. + * Save it symmetrically with __load_hdbss(): both test buffer + * ownership, not HDBSS enablement, so the software copy stays + * live across VTCR_EL2.HDBSS transitions instead of freezing + * with a stale index from a previous epoch. + */ + if (vcpu->arch.hdbss.hdbss_pg) vcpu->arch.hdbss.hdbssprod_el2 = read_sysreg_s(SYS_HDBSSPROD_EL2); __vcpu_put_deactivate_traps(vcpu); -- 2.33.0
--- arch/arm64/kvm/arm.c | 5 +++++ arch/arm64/kvm/dirty_bit.c | 15 ++++++++++++++- arch/arm64/kvm/mmu.c | 5 ++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 1737dbe13419..e053edf8387d 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -224,6 +224,8 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm, r = -EBUSY; } else { kvm->arch.hdbss_buffer_size = size; + kvm_info("HDBSS buffer size set by userspace to %llu bytes\n", + size); r = 0; } mutex_unlock(&kvm->lock); @@ -252,6 +254,9 @@ void kvm_arch_dirty_ring_size_updated(struct kvm *kvm) return; kvm->arch.hdbss_buffer_size = KVM_ARM_HDBSS_DEFAULT_SIZE; + + kvm_info("HDBSS buffer size auto-configured to %u bytes for dirty-ring mode\n", + KVM_ARM_HDBSS_DEFAULT_SIZE); } /** diff --git a/arch/arm64/kvm/dirty_bit.c b/arch/arm64/kvm/dirty_bit.c index 205b5f7a2805..ae5acfc4fd5b 100644 --- a/arch/arm64/kvm/dirty_bit.c +++ b/arch/arm64/kvm/dirty_bit.c @@ -42,8 +42,11 @@ int kvm_arm_vcpu_alloc_hdbss(struct kvm_vcpu *vcpu) * pushing leftover page contents into the dirty ring as GFNs. */ hdbss_pg = alloc_pages(GFP_KERNEL_ACCOUNT | __GFP_ZERO, buddy_order); - if (!hdbss_pg) + if (!hdbss_pg) { + kvm_err("VCPU%u HDBSS buffer allocation failed (order=%u)\n", + vcpu->vcpu_id, buddy_order); return -ENOMEM; + } vcpu->arch.hdbss = (struct vcpu_hdbss_state) { .hdbss_pg = hdbss_pg, @@ -52,6 +55,9 @@ int kvm_arm_vcpu_alloc_hdbss(struct kvm_vcpu *vcpu) .buddy_order = buddy_order, }; + kvm_info("VCPU%u HDBSS buffer allocated (size=%u bytes, order=%u)\n", + vcpu->vcpu_id, size, buddy_order); + return 0; } @@ -60,6 +66,11 @@ void kvm_arm_vcpu_free_hdbss(struct kvm_vcpu *vcpu) if (!vcpu->arch.hdbss.hdbss_pg) return; + kvm_info("VCPU%u HDBSS buffer freed (size=%u bytes, order=%u)\n", + vcpu->vcpu_id, + PAGE_SIZE << vcpu->arch.hdbss.buddy_order, + vcpu->arch.hdbss.buddy_order); + __free_pages(vcpu->arch.hdbss.hdbss_pg, vcpu->arch.hdbss.buddy_order); vcpu->arch.hdbss.hdbss_pg = NULL; @@ -168,6 +179,8 @@ void kvm_arm_disable_hdbss_global(struct kvm *kvm) kvm->arch.mmu.vtcr &= ~dirty_bits; + kvm_info("HDBSS disabled\n"); + kvm_make_all_cpus_request(kvm, KVM_REQ_RELOAD_STAGE2); kvm_flush_remote_tlbs(kvm); } diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 42cc35dde736..473de0f0b01b 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -2662,8 +2662,11 @@ void kvm_arch_update_hw_dirty_mode(struct kvm *kvm) unsigned long cur, target; bool logging = atomic_read(&kvm->nr_memslots_dirty_logging) != 0; - if (logging && kvm_supports_hdbss(kvm)) + if (logging && kvm_supports_hdbss(kvm)) { target = VTCR_EL2_HD | VTCR_EL2_HA | VTCR_EL2_HDBSS; + kvm_info("HDBSS enabled (buffer_size=%u bytes)\n", + kvm_hdbss_buffer_size(kvm)); + } else if (logging || !kvm_supports_hafdbs(kvm)) target = 0; else -- 2.33.0
participants (1)
-
Tian Zheng