Re: [PATCH v4 3/6] KVM: arm64: Add auto DBM support for hardware dirty tracking
From: Tian Zheng
Date: Tue Jul 28 2026 - 04:58:24 EST
On 7/17/2026 11:21 PM, Leonardo Bras wrote:
On Fri, Jul 17, 2026 at 11:58:06AM +0800, Tian Zheng wrote:
On 7/16/2026 3:39 PM, Oliver Upton wrote:
Hi Tian,
On Thu, Jul 09, 2026 at 06:40:23PM +0800, Tian Zheng wrote:
- if (prot & KVM_PGTABLE_PROT_W)This block makes it pretty evident that the DBM bit really *is* the
+ if (prot & KVM_PGTABLE_PROT_W) {
set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W;
+ /*
+ * No DEVICE filter needed here: relax_perms is only called
+ * on FSC_PERM faults. Device pages always get full RW from
+ * initial mapping and are never write-protected during
+ * migration, so they never trigger a permission fault.
+ */
+ if (pgt->flags & KVM_PGTABLE_S2_DBM)
+ set |= KVM_PTE_LEAF_ATTR_HI_S2_DBM;
+ } else {
+ /*
+ * Clear DBM on W→RO downgrade to prevent hardware from
+ * silently upgrading RO+DBM back to W+dirty, which would
+ * bypass KVM's write tracking and cause data corruption.
+ */
+ clr |= KVM_PTE_LEAF_ATTR_HI_S2_DBM;
+ }
+
write permission bit. I'd much rather we introduce the concept of dirty
state to the page table library and migrate the abstract write
permission to the DBM field, even if we don't have FEAT_HAFDBS.
Ohh, that's an amazing idea!
That way everything 'just works' from outside the page-table library:
write-protecting hugepages would have the effect of clearing DBM and we
can separately reap dirty state from page descriptors.
If/when the architecture forces FEAT_S2PIE upon us we will need to make
this change anyway since dirty state management is unconditional and
handled separately from the actual permissions.
Thanks,
Oliver
Hi Oliver,
Thanks again for your insightful review. Following your suggestion, I've
reworked the design around a unified three-state model that works regardless
of whether FEAT_HAFDBS is implemented:
**State table**
State | DBM | S2AP[1] | Without HTTU | With HTTU (HAFDBS)
Non-writable (N) | 0 | 0 | write -> fault, inject | write -> fault, inject
Writable-clean (C) | 1 | 0 | write -> fault, sw C->D | write -> hw C->D, no fault, HDBSS logs
Writable-dirty (D) | 1 | 1 | writable, no fault | writable, no fault
Yeah, that's how the table works with HAFDBS/HDBSS/HACDBS.
**Proposed changes**
1. Remove KVM_PGTABLE_S2_DBM from enum kvm_pgtable_stage2_flags
— VTCR_EL2.{HD,HDBSS,HA} enablement in kvm_arm_enable_hdbss_global()
already keys off kvm->arch.enable_hdbss / system_supports_hdbss().
We may need a system_support_hdbss() for the actual hdbss routines, though.
Yes, and sorry for the unclear phrasing. What I meant is:
Setting DBM unconditionally is safe because hardware only interprets it when VTCR_EL2.HD is set. That only happens in kvm_arm_enable_hdbss_global() after checking both system_supports_hdbss() and migration state.
2. stage2_set_prot_attr() — set DBM unconditionally on writable pages:
```
if (prot & KVM_PGTABLE_PROT_W) {
attr |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W;
/* Writable-dirty: DBM=1 conveys write intent, S2AP[1]=1 marks dirty */
attr |= KVM_PTE_LEAF_ATTR_HI_S2_DBM;
}
```
3. kvm_pgtable_stage2_relax_perms() — drop the else branch entirely:
```
if (prot & KVM_PGTABLE_PROT_W) {
set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W;
/* Non-writable -> Writable-dirty: restore both write intent and dirty state */
set |= KVM_PTE_LEAF_ATTR_HI_S2_DBM;
In the future, depending on the setup of HDBSS/splitting, we may want to
change this behavior. But for software only, it looks nice.
}Thanks!
/* no else: callers passing !W (e.g. exec faults) must not touch DBM */
```
4. kvm_pgtable_stage2_wrprotect() — unchanged: it only clears S2AP1 (D->C).
DBM is preserved so HDBSS re-arms next round.
```
int kvm_pgtable_stage2_wrprotect(struct kvm_pgtable *pgt, u64 addr, u64
size)
{
/* Writable-dirty -> Writable-clean: clear dirty state (S2AP_W),
* preserve write intent (DBM) so HDBSS re-arms for next write.
*/
return stage2_update_leaf_attrs(pgt, addr, size, 0,
KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W,
NULL, NULL,
KVM_PGTABLE_WALK_IGNORE_EAGAIN);
}
```
**One clarification**
In the three-state model above, wrprotect() clears S2AP[1] but preserves DBM
(D->C).
This allows HDBSS to re-arm on the next write. If we instead cleared DBM as
well (->N),
HDBSS would be permanently disabled on that page and we'd lose the benefit
of hardware
dirty tracking.
So my understanding is:
wrprotect() (dirty tracking): D->C — clears S2AP[1], preserves DBM
mkreadonly() (true RO, future): ->N — clears both S2AP[1] and DBM
Does this match what you had in mind?
Looking forward to your thoughts.
Thanks,
Tian
Leo