Re: [PATCH] x86/mm: Clear PUD entry before populating PMD table when splitting 1GB page in phys_pud_init
From: Phineas Su
Date: Tue Jul 21 2026 - 13:48:38 EST
On Mon, Jul 20, 2026 at 9:08 PM Dave Hansen <dave.hansen@xxxxxxxxx> 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.