[PATCH v5 3/5] mm: remove the special early-section handling from pfn_valid() and for_each_valid_pfn()

From: Yuan Liu

Date: Wed May 20 2026 - 05:36:11 EST


Make pfn_valid() return 0 for PFNs that fall into invalid subsections
in early sections. Make for_each_valid_pfn() skip PFNs that fall into
invalid subsections in early sections.

This change is in preparation for optimizing zone contiguity checks
based on pages_with_online_memmap.

Reviewed-by: Wei Yang <richard.weiyang@xxxxxxxxx>
Reviewed-by: Jason Zeng <jason.zeng@xxxxxxxxx>
Signed-off-by: Yuan Liu <yuan1.liu@xxxxxxxxx>
---
include/linux/mmzone.h | 13 ++++++-------
mm/sparse-vmemmap.c | 4 ++--
2 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 9adb2ad21da5..783084f8bbfe 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2259,6 +2259,10 @@ void sparse_init_early_section(int nid, struct page *map, unsigned long pnum,
* there is actual usable memory at that @pfn. The struct page may
* represent a hole or an unusable page frame.
*
+ * Note that this function returns 0 for PFNs that fall into
+ * invalid subsections as part of early sections, even though there would
+ * currently be a memmap allocated (that should not be touched).
+ *
* Return: 1 for PFNs that have memory map entries and 0 otherwise
*/
static inline int pfn_valid(unsigned long pfn)
@@ -2283,11 +2287,7 @@ static inline int pfn_valid(unsigned long pfn)
rcu_read_unlock_sched();
return 0;
}
- /*
- * Traditionally early sections always returned pfn_valid() for
- * the entire section-sized span.
- */
- ret = early_section(ms) || pfn_section_valid(ms, pfn);
+ ret = pfn_section_valid(ms, pfn);
rcu_read_unlock_sched();

return ret;
@@ -2303,8 +2303,7 @@ static inline unsigned long first_valid_pfn(unsigned long pfn, unsigned long end
while (nr <= __highest_present_section_nr && pfn < end_pfn) {
struct mem_section *ms = __pfn_to_section(pfn);

- if (valid_section(ms) &&
- (early_section(ms) || pfn_section_first_valid(ms, &pfn))) {
+ if (valid_section(ms) && pfn_section_first_valid(ms, &pfn)) {
rcu_read_unlock_sched();
return pfn;
}
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 6eadb9d116e4..c6eefbb6013f 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -771,8 +771,8 @@ static void section_deactivate(unsigned long pfn, unsigned long nr_pages,
}

/*
- * The memmap of early sections is always fully populated. See
- * section_activate() and pfn_valid() .
+ * The memmap of early sections is currently always fully populated. See
+ * section_activate().
*/
if (!section_is_early) {
memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
--
2.47.3