arch/x86/mm/init_64.c | 1 + 1 file changed, 1 insertion(+)
When phys_pud_init() splits an existing 1 GB huge page (pud_leaf(*pud))
into PMD table entries, it extracts the page protection attributes but
does not clear the existing PUD entry (*pud).
Subsequently, pud_populate_init() -> set_pud_safe() is invoked to link
the new PMD table. Because *pud is still populated with the 1 GB huge
page entry, set_pud_safe() detects a present entry that differs from the
new PMD table pointer, triggering a WARN_ON_ONCE call trace in dmesg:
WARNING: CPU: 0 PID: 0 at arch/x86/mm/init_64.c:89 phys_pud_init+0x2d6/0x390
Call pud_clear(pud) right after extracting protection attributes when
splitting the 1 GB page so that set_pud_safe() can populate the PMD
table cleanly without issuing a warning.
Signed-off-by: Phineas Su <pohaosu@google.com>
---
arch/x86/mm/init_64.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c
index d57f29ca23a5..fdfcf4a7b090 100644
--- a/arch/x86/mm/init_64.c
+++ b/arch/x86/mm/init_64.c
@@ -659,6 +659,7 @@ phys_pud_init(pud_t *pud_page, unsigned long paddr, unsigned long paddr_end,
continue;
}
prot = pte_pgprot(pte_clrhuge(*(pte_t *)pud));
+ pud_clear(pud);
}
if (page_size_mask & (1<<PG_LEVEL_1G)) {
--
2.55.0.229.g6434b31f56-goog
On 7/19/26 11:26, Phineas Su wrote: > When phys_pud_init() splits an existing 1 GB huge page (pud_leaf(*pud)) > into PMD table entries, it extracts the page protection attributes but > does not clear the existing PUD entry (*pud). > > Subsequently, pud_populate_init() -> set_pud_safe() is invoked to link > the new PMD table. Because *pud is still populated with the 1 GB huge > page entry, set_pud_safe() detects a present entry that differs from the > new PMD table pointer, triggering a WARN_ON_ONCE call trace in dmesg: > > WARNING: CPU: 0 PID: 0 at arch/x86/mm/init_64.c:89 phys_pud_init+0x2d6/0x390 > > Call pud_clear(pud) right after extracting protection attributes when > splitting the 1 GB page so that set_pud_safe() can populate the PMD > table cleanly without issuing a warning. Why aren't we running in to this left and right? I wouldn't think splitting a PUD would be _that_ rare.
On Mon, Jul 20, 2026 at 9:08 PM Dave Hansen <dave.hansen@intel.com> wrote: > > On 7/19/26 11:26, Phineas Su wrote: > > When phys_pud_init() splits an existing 1 GB huge page (pud_leaf(*pud)) > > into PMD table entries, it extracts the page protection attributes but > > does not clear the existing PUD entry (*pud). > > > > Subsequently, pud_populate_init() -> set_pud_safe() is invoked to link > > the new PMD table. Because *pud is still populated with the 1 GB huge > > page entry, set_pud_safe() detects a present entry that differs from the > > new PMD table pointer, triggering a WARN_ON_ONCE call trace in dmesg: > > > > WARNING: CPU: 0 PID: 0 at arch/x86/mm/init_64.c:89 phys_pud_init+0x2d6/0x390 > > > > Call pud_clear(pud) right after extracting protection attributes when > > splitting the 1 GB page so that set_pud_safe() can populate the PMD > > table cleanly without issuing a warning. > > Why aren't we running in to this left and right? I wouldn't think > splitting a PUD would be _that_ rare. I'm not sure how common or rare PUD splitting is overall, but here is how we hit it in our setup: 1. A 1 GB physical memory block is mapped as a 1 GB leaf page (pud_large(*pud)) during an initial top-down mapping step in memory_map_top_down(). 2. A non-1GB-aligned physical memory reservation (such as the AMD SEV-SNP RMP table or CXL reserved regions) splits physical memory near that 1 GB boundary. 3. A subsequent pass in init_range_memory_mapping() maps a small usable RAM fragment adjacent to the reservation within that same 1 GB block. split_mem_range() strips PG_LEVEL_1G due to sub-1G alignment, forcing phys_pud_init() to split the 1 GB PUD entry into PMD table entries. Without pud_clear(pud), *pud remains populated with the old 1 GB leaf entry when pud_populate_init() -> set_pud_safe() is invoked, causing set_pud_safe() to detect a present entry (pud_present(*pud) && !pud_same(*pud, new_pmd)) and trigger WARN_ON_ONCE. Also, after auditing the other phys_xxx_init() functions, phys_pmd_init() has the exact same issue when splitting 2 MB pages into 4 KB PTEs. I will send v2 including both fixes.
When phys_pud_init() or phys_pmd_init() splits an existing huge page
(pud_leaf(*pud) or pmd_leaf(*pmd)) into lower-level table entries, it
extracts the page protection attributes but does not clear the existing
PUD/PMD entry (*pud or *pmd).
Subsequently, set_pud_safe() / set_pmd_safe() is invoked to link the new
PMD/PTE table. Because *pud / *pmd is still populated with the huge page
entry, set_pud_safe() / set_pmd_safe() detects a present entry that differs
from the new table pointer, triggering a WARN_ON_ONCE call trace:
WARNING: CPU: 0 PID: 0 at arch/x86/mm/init_64.c:89 phys_pud_init+0x2d6/0x390
Call pud_clear(pud) and pmd_clear(pmd) right after extracting protection
attributes when splitting 1 GB / 2 MB huge pages so that set_pud_safe() and
set_pmd_safe() can populate lower-level tables cleanly without issuing a
warning.
Signed-off-by: Phineas Su <pohaosu@google.com>
---
v2:
- Also add pmd_clear(pmd) in phys_pmd_init() to handle 2 MB huge page splitting.
- Update commit message to cover both PUD and PMD splitting.
arch/x86/mm/init_64.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c
index d57f29ca23a5..55845c40c1ca 100644
--- a/arch/x86/mm/init_64.c
+++ b/arch/x86/mm/init_64.c
@@ -573,6 +573,7 @@ phys_pmd_init(pmd_t *pmd_page, unsigned long paddr, unsigned long paddr_end,
continue;
}
new_prot = pte_pgprot(pte_clrhuge(*(pte_t *)pmd));
+ pmd_clear(pmd);
}
if (page_size_mask & (1<<PG_LEVEL_2M)) {
@@ -659,6 +660,7 @@ phys_pud_init(pud_t *pud_page, unsigned long paddr, unsigned long paddr_end,
continue;
}
prot = pte_pgprot(pte_clrhuge(*(pte_t *)pud));
+ pud_clear(pud);
}
if (page_size_mask & (1<<PG_LEVEL_1G)) {
--
2.55.0.229.g6434b31f56-goog
© 2016 - 2026 Red Hat, Inc.