[PATCH] LoongArch: Fix missing dirty page tracking with HW PTW for both pte and pmd
From: Hongchen Zhang
Date: Wed Jun 24 2026 - 22:44:33 EST
When hardware page table walk (PTW) is enabled on LoongArch, the CPU
may set _PAGE_DIRTY directly in the page table entry during a write
TLB miss, without going through the software TLB store handler. The
software TLB store handler (tlbex.S:254) sets both _PAGE_DIRTY and
_PAGE_MODIFIED together:
ori t0, t0, (_PAGE_VALID | _PAGE_DIRTY | _PAGE_MODIFIED)
But hardware PTW only sets _PAGE_DIRTY, the software-only bit
_PAGE_MODIFIED is left unchanged. This creates a window where a PTE
has _PAGE_DIRTY set (hardware knows the page is dirty) but
_PAGE_MODIFIED clear (software is unaware).
When fork() triggers copy-on-write, __copy_present_ptes() calls
pte_wrprotect(), which unconditionally clears both _PAGE_WRITE and
_PAGE_DIRTY:
pte_val(pte) &= ~(_PAGE_WRITE | _PAGE_DIRTY);
Since _PAGE_MODIFIED was never set, the dirtiness information is
completely lost. Subsequently, when memory pressure triggers page
reclaim, page_mkclean() / try_to_unmap() sees the page as clean
(pte_dirty() returns false) and the page may be freed without
writeback, causing data corruption.
Fix this by propagating _PAGE_DIRTY to _PAGE_MODIFIED in both
pte_wrprotect() and pmd_wrprotect() before clearing writability bits:
if (pte_val(pte) & _PAGE_DIRTY)
pte_val(pte) |= _PAGE_MODIFIED;
The pmd_wrprotect() fix handles the CONFIG_TRANSPARENT_HUGEPAGE case,
where pmd entries need the same treatment.
This ensures the software dirty tracking bit (checked by pte_dirty(),
which reads _PAGE_DIRTY | _PAGE_MODIFIED) is preserved across fork
COW write-protection.
The issue was found by LTP madvise09 test case, which exercises
page reclaim after madvise(MADV_FREE)/write/fork on private anonymous
mappings.
Fixes: 09cfefb7fa70 ("LoongArch: Add memory management")
Cc: stable@xxxxxxxxxxxxxxx
Co-developed-by: Huacai Chen <chenhuacai@xxxxxxxxxxx>
Signed-off-by: Huacai Chen <chenhuacai@xxxxxxxxxxx>
Co-developed-by: Tianyang Zhang <zhangtianyang@xxxxxxxxxxx>
Signed-off-by: Tianyang Zhang <zhangtianyang@xxxxxxxxxxx>
Signed-off-by: Hongchen Zhang <zhanghongchen@xxxxxxxxxxx>
---
arch/loongarch/include/asm/pgtable.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/loongarch/include/asm/pgtable.h b/arch/loongarch/include/asm/pgtable.h
index 2a0b63ae421f..223528c04d73 100644
--- a/arch/loongarch/include/asm/pgtable.h
+++ b/arch/loongarch/include/asm/pgtable.h
@@ -429,6 +429,8 @@ static inline pte_t pte_mkwrite_novma(pte_t pte)
static inline pte_t pte_wrprotect(pte_t pte)
{
+ if (pte_val(pte) & _PAGE_DIRTY)
+ pte_val(pte) |= _PAGE_MODIFIED;
pte_val(pte) &= ~(_PAGE_WRITE | _PAGE_DIRTY);
return pte;
}
@@ -535,6 +537,8 @@ static inline pmd_t pmd_mkwrite_novma(pmd_t pmd)
static inline pmd_t pmd_wrprotect(pmd_t pmd)
{
+ if (pmd_val(pmd) & _PAGE_DIRTY)
+ pmd_val(pmd) |= _PAGE_MODIFIED;
pmd_val(pmd) &= ~(_PAGE_WRITE | _PAGE_DIRTY);
return pmd;
}
--
2.33.0