linux-mm.kvack.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH v2 1/7] mm: Introduce for_each_valid_pfn() and use it from reserve_bootmem_region()
@ 2025-04-04 15:59 David Woodhouse
  2025-04-04 15:59 ` [RFC PATCH v2 2/7] mm: Implement for_each_valid_pfn() for CONFIG_FLATMEM David Woodhouse
                   ` (5 more replies)
  0 siblings, 6 replies; 11+ messages in thread
From: David Woodhouse @ 2025-04-04 15:59 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Andrew Morton, Sauerwein, David, Anshuman Khandual,
	Ard Biesheuvel, Catalin Marinas, David Hildenbrand, Marc Zyngier,
	Mark Rutland, Mike Rapoport, Will Deacon, kvmarm,
	linux-arm-kernel, linux-kernel, linux-mm

From: David Woodhouse <dwmw@amazon.co.uk>

Especially since commit 9092d4f7a1f8 ("memblock: update initialization
of reserved pages"), the reserve_bootmem_region() function can spend a
significant amount of time iterating over every 4KiB PFN in a range,
calling pfn_valid() on each one, and ultimately doing absolutely nothing.

On a platform used for virtualization, with large NOMAP regions that
eventually get used for guest RAM, this leads to a significant increase
in steal time experienced during kexec for a live update.

Introduce for_each_valid_pfn() and use it from reserve_bootmem_region().
This implementation is precisely the same naïve loop that the function
used to have, but subsequent commits will provide optimised versions
for FLATMEM and SPARSEMEM, and this version will remain for those
architectures which provide their own pfn_valid() implementation,
until/unless they also provide a matching for_each_valid_pfn().

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
 include/linux/mmzone.h | 10 ++++++++++
 mm/mm_init.c           | 23 ++++++++++-------------
 2 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 25e80b2ca7f4..32ecb5cadbaf 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2176,6 +2176,16 @@ void sparse_init(void);
 #define subsection_map_init(_pfn, _nr_pages) do {} while (0)
 #endif /* CONFIG_SPARSEMEM */
 
+/*
+ * Fallback case for when the architecture provides its own pfn_valid() but
+ * not a corresponding for_each_valid_pfn().
+ */
+#ifndef for_each_valid_pfn
+#define for_each_valid_pfn(_pfn, _start_pfn, _end_pfn)			\
+	for ((_pfn) = (_start_pfn); (_pfn) < (_end_pfn); (_pfn)++)	\
+		if (pfn_valid(_pfn))
+#endif
+
 #endif /* !__GENERATING_BOUNDS.H */
 #endif /* !__ASSEMBLY__ */
 #endif /* _LINUX_MMZONE_H */
diff --git a/mm/mm_init.c b/mm/mm_init.c
index a38a1909b407..7c699bad42ad 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -777,22 +777,19 @@ static inline void init_deferred_page(unsigned long pfn, int nid)
 void __meminit reserve_bootmem_region(phys_addr_t start,
 				      phys_addr_t end, int nid)
 {
-	unsigned long start_pfn = PFN_DOWN(start);
-	unsigned long end_pfn = PFN_UP(end);
+	unsigned long pfn;
 
-	for (; start_pfn < end_pfn; start_pfn++) {
-		if (pfn_valid(start_pfn)) {
-			struct page *page = pfn_to_page(start_pfn);
+	for_each_valid_pfn (pfn, PFN_DOWN(start), PFN_UP(end)) {
+		struct page *page = pfn_to_page(pfn);
 
-			init_deferred_page(start_pfn, nid);
+		init_deferred_page(pfn, nid);
 
-			/*
-			 * no need for atomic set_bit because the struct
-			 * page is not visible yet so nobody should
-			 * access it yet.
-			 */
-			__SetPageReserved(page);
-		}
+		/*
+		 * no need for atomic set_bit because the struct
+		 * page is not visible yet so nobody should
+		 * access it yet.
+		 */
+		__SetPageReserved(page);
 	}
 }
 
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [RFC PATCH v2 2/7] mm: Implement for_each_valid_pfn() for CONFIG_FLATMEM
  2025-04-04 15:59 [RFC PATCH v2 1/7] mm: Introduce for_each_valid_pfn() and use it from reserve_bootmem_region() David Woodhouse
@ 2025-04-04 15:59 ` David Woodhouse
  2025-04-07  6:54   ` Mike Rapoport
  2025-04-04 15:59 ` [RFC PATCH v2 3/7] mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM David Woodhouse
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 11+ messages in thread
From: David Woodhouse @ 2025-04-04 15:59 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Andrew Morton, Sauerwein, David, Anshuman Khandual,
	Ard Biesheuvel, Catalin Marinas, David Hildenbrand, Marc Zyngier,
	Mark Rutland, Mike Rapoport, Will Deacon, kvmarm,
	linux-arm-kernel, linux-kernel, linux-mm

From: David Woodhouse <dwmw@amazon.co.uk>

In the FLATMEM case, the default pfn_valid() just checks that the PFN is
within the range [ ARCH_PFN_OFFSET .. ARCH_PFN_OFFSET + max_mapnr ).

The for_each_valid_pfn() function can therefore be a simple for() loop
using those as min/max respectively.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
 include/asm-generic/memory_model.h | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/include/asm-generic/memory_model.h b/include/asm-generic/memory_model.h
index a3b5029aebbd..044536da3390 100644
--- a/include/asm-generic/memory_model.h
+++ b/include/asm-generic/memory_model.h
@@ -30,7 +30,31 @@ static inline int pfn_valid(unsigned long pfn)
 	return pfn >= pfn_offset && (pfn - pfn_offset) < max_mapnr;
 }
 #define pfn_valid pfn_valid
-#endif
+
+static inline bool first_valid_pfn(unsigned long *pfn)
+{
+	/* avoid <linux/mm.h> include hell */
+	extern unsigned long max_mapnr;
+	unsigned long pfn_offset = ARCH_PFN_OFFSET;
+
+	if (*pfn < pfn_offset) {
+		*pfn = pfn_offset;
+		return true;
+	}
+
+	if ((*pfn - pfn_offset) < max_mapnr)
+		return true;
+
+	return false;
+}
+
+#ifndef for_each_valid_pfn
+#define for_each_valid_pfn(pfn, start_pfn, end_pfn)			       \
+	for (pfn = max_t(unsigned long, start_pfn, ARCH_PFN_OFFSET);	\
+	     pfn < min_t(unsigned long, end_pfn, ARCH_PFN_OFFSET + max_mapnr); \
+			 pfn++)
+#endif /* for_each_valid_pfn */
+#endif /* valid_pfn */
 
 #elif defined(CONFIG_SPARSEMEM_VMEMMAP)
 
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [RFC PATCH v2 3/7] mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM
  2025-04-04 15:59 [RFC PATCH v2 1/7] mm: Introduce for_each_valid_pfn() and use it from reserve_bootmem_region() David Woodhouse
  2025-04-04 15:59 ` [RFC PATCH v2 2/7] mm: Implement for_each_valid_pfn() for CONFIG_FLATMEM David Woodhouse
@ 2025-04-04 15:59 ` David Woodhouse
  2025-04-04 15:59 ` [RFC PATCH v2 4/7] mm: Optimise SPARSEMEM implementation of for_each_valid_pfn() David Woodhouse
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 11+ messages in thread
From: David Woodhouse @ 2025-04-04 15:59 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Andrew Morton, Sauerwein, David, Anshuman Khandual,
	Ard Biesheuvel, Catalin Marinas, David Hildenbrand, Marc Zyngier,
	Mark Rutland, Mike Rapoport, Will Deacon, kvmarm,
	linux-arm-kernel, linux-kernel, linux-mm

From: David Woodhouse <dwmw@amazon.co.uk>

Introduce a pfn_first_valid() helper which takes a pointer to the PFN and
updates it to point to the first valid PFN starting from that point, and
returns true if a valid PFN was found.

This largely mirrors pfn_valid(), calling into a pfn_section_first_valid()
helper which is trivial for the !CONFIG_SPARSEMEM_VMEMMAP case, and in
the VMEMMAP case will skip to the next subsection as needed.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
 include/linux/mmzone.h | 59 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 59 insertions(+)

diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 32ecb5cadbaf..67cdf675a4b9 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2074,11 +2074,37 @@ static inline int pfn_section_valid(struct mem_section *ms, unsigned long pfn)
 
 	return usage ? test_bit(idx, usage->subsection_map) : 0;
 }
+
+static inline bool pfn_section_first_valid(struct mem_section *ms, unsigned long *pfn)
+{
+	struct mem_section_usage *usage = READ_ONCE(ms->usage);
+	int idx = subsection_map_index(*pfn);
+	unsigned long bit;
+
+	if (!usage)
+		return false;
+
+	if (test_bit(idx, usage->subsection_map))
+		return true;
+
+	/* Find the next subsection that exists */
+	bit = find_next_bit(usage->subsection_map, SUBSECTIONS_PER_SECTION, idx);
+	if (bit == SUBSECTIONS_PER_SECTION)
+		return false;
+
+	*pfn = (*pfn & PAGE_SECTION_MASK) + (bit * PAGES_PER_SUBSECTION);
+	return true;
+}
 #else
 static inline int pfn_section_valid(struct mem_section *ms, unsigned long pfn)
 {
 	return 1;
 }
+
+static inline bool pfn_section_first_valid(struct mem_section *ms, unsigned long *pfn)
+{
+	return true;
+}
 #endif
 
 void sparse_init_early_section(int nid, struct page *map, unsigned long pnum,
@@ -2127,6 +2153,39 @@ static inline int pfn_valid(unsigned long pfn)
 
 	return ret;
 }
+
+static inline bool first_valid_pfn(unsigned long *p_pfn)
+{
+	unsigned long pfn = *p_pfn;
+	unsigned long nr = pfn_to_section_nr(pfn);
+
+	rcu_read_lock_sched();
+
+	while (nr <= __highest_present_section_nr) {
+		struct mem_section *ms = __pfn_to_section(pfn);
+
+		if (valid_section(ms) &&
+		    (early_section(ms) || pfn_section_first_valid(ms, &pfn))) {
+			*p_pfn = pfn;
+			rcu_read_unlock_sched();
+			return true;
+		}
+
+		/* Nothing left in this section? Skip to next section */
+		nr++;
+		pfn = section_nr_to_pfn(nr);
+	}
+
+	rcu_read_unlock_sched();
+
+	return false;
+}
+
+#define for_each_valid_pfn(_pfn, _start_pfn, _end_pfn)	       \
+	for ((_pfn) = (_start_pfn);			       \
+	     first_valid_pfn(&(_pfn)) && (_pfn) < (_end_pfn);  \
+	     (_pfn)++)
+
 #endif
 
 static inline int pfn_in_present_section(unsigned long pfn)
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [RFC PATCH v2 4/7] mm: Optimise SPARSEMEM implementation of for_each_valid_pfn()
  2025-04-04 15:59 [RFC PATCH v2 1/7] mm: Introduce for_each_valid_pfn() and use it from reserve_bootmem_region() David Woodhouse
  2025-04-04 15:59 ` [RFC PATCH v2 2/7] mm: Implement for_each_valid_pfn() for CONFIG_FLATMEM David Woodhouse
  2025-04-04 15:59 ` [RFC PATCH v2 3/7] mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM David Woodhouse
@ 2025-04-04 15:59 ` David Woodhouse
  2025-04-07  7:07   ` Mike Rapoport
  2025-04-04 15:59 ` [RFC PATCH v2 5/7] mm, PM: Use for_each_valid_pfn() in kernel/power/snapshot.c David Woodhouse
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 11+ messages in thread
From: David Woodhouse @ 2025-04-04 15:59 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Andrew Morton, Sauerwein, David, Anshuman Khandual,
	Ard Biesheuvel, Catalin Marinas, David Hildenbrand, Marc Zyngier,
	Mark Rutland, Mike Rapoport, Will Deacon, kvmarm,
	linux-arm-kernel, linux-kernel, linux-mm

From: David Woodhouse <dwmw@amazon.co.uk>

There's no point in checking the section and subsection bitmap for *every*
PFN in the same section; they're either all valid or they aren't.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 include/linux/mmzone.h | 39 +++++++++++++++++++++++++++++----------
 1 file changed, 29 insertions(+), 10 deletions(-)

diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 67cdf675a4b9..0da1b0ba5d9f 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2154,21 +2154,20 @@ static inline int pfn_valid(unsigned long pfn)
 	return ret;
 }
 
-static inline bool first_valid_pfn(unsigned long *p_pfn)
+/* Returns -1 (an invalid PFN) if no valid PFN remaining */
+static inline unsigned long first_valid_pfn(unsigned long pfn, unsigned long end_pfn)
 {
-	unsigned long pfn = *p_pfn;
 	unsigned long nr = pfn_to_section_nr(pfn);
 
 	rcu_read_lock_sched();
 
-	while (nr <= __highest_present_section_nr) {
+	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))) {
-			*p_pfn = pfn;
 			rcu_read_unlock_sched();
-			return true;
+			return pfn;
 		}
 
 		/* Nothing left in this section? Skip to next section */
@@ -2177,14 +2176,34 @@ static inline bool first_valid_pfn(unsigned long *p_pfn)
 	}
 
 	rcu_read_unlock_sched();
+	return (unsigned long)-1;
+}
 
-	return false;
+static inline unsigned long next_valid_pfn(unsigned long pfn, unsigned long end_pfn)
+{
+	pfn++;
+
+	if (pfn >= end_pfn)
+		return (unsigned long)-1;
+
+	/*
+	 * Either every PFN within the section (or subsection for VMEMMAP) is
+	 * valid, or none of them are. So there's no point repeating the check
+	 * for every PFN; only call first_valid_pfn() the first time, and when
+	 * crossing a (sub)section boundary (i.e. !(pfn & ~PFN_VALID_MASK)).
+	 */
+	if (pfn & (IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP) ?
+		   PAGE_SUBSECTION_MASK : PAGE_SECTION_MASK))
+		return pfn;
+
+	return first_valid_pfn(pfn, end_pfn);
 }
 
-#define for_each_valid_pfn(_pfn, _start_pfn, _end_pfn)	       \
-	for ((_pfn) = (_start_pfn);			       \
-	     first_valid_pfn(&(_pfn)) && (_pfn) < (_end_pfn);  \
-	     (_pfn)++)
+
+#define for_each_valid_pfn(_pfn, _start_pfn, _end_pfn)			\
+	for ((_pfn) = first_valid_pfn((_start_pfn), (_end_pfn));	\
+	     (_pfn) != (unsigned long)-1;				\
+	     (_pfn) = next_valid_pfn((_pfn), (_end_pfn)))
 
 #endif
 
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [RFC PATCH v2 5/7] mm, PM: Use for_each_valid_pfn() in kernel/power/snapshot.c
  2025-04-04 15:59 [RFC PATCH v2 1/7] mm: Introduce for_each_valid_pfn() and use it from reserve_bootmem_region() David Woodhouse
                   ` (2 preceding siblings ...)
  2025-04-04 15:59 ` [RFC PATCH v2 4/7] mm: Optimise SPARSEMEM implementation of for_each_valid_pfn() David Woodhouse
@ 2025-04-04 15:59 ` David Woodhouse
  2025-04-04 15:59 ` [RFC PATCH v2 6/7] mm, x86: Use for_each_valid_pfn() from __ioremap_check_ram() David Woodhouse
  2025-04-04 15:59 ` [RFC PATCH v2 7/7] mm: use for_each_valid_pfn() in memory_hotplug David Woodhouse
  5 siblings, 0 replies; 11+ messages in thread
From: David Woodhouse @ 2025-04-04 15:59 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Andrew Morton, Sauerwein, David, Anshuman Khandual,
	Ard Biesheuvel, Catalin Marinas, David Hildenbrand, Marc Zyngier,
	Mark Rutland, Mike Rapoport, Will Deacon, kvmarm,
	linux-arm-kernel, linux-kernel, linux-mm

From: David Woodhouse <dwmw@amazon.co.uk>

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 kernel/power/snapshot.c | 42 ++++++++++++++++++++---------------------
 1 file changed, 20 insertions(+), 22 deletions(-)

diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index 4e6e24e8b854..f151c7a45584 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -1094,16 +1094,15 @@ static void mark_nosave_pages(struct memory_bitmap *bm)
 			 ((unsigned long long) region->end_pfn << PAGE_SHIFT)
 				- 1);
 
-		for (pfn = region->start_pfn; pfn < region->end_pfn; pfn++)
-			if (pfn_valid(pfn)) {
-				/*
-				 * It is safe to ignore the result of
-				 * mem_bm_set_bit_check() here, since we won't
-				 * touch the PFNs for which the error is
-				 * returned anyway.
-				 */
-				mem_bm_set_bit_check(bm, pfn);
-			}
+		for_each_valid_pfn (pfn, region->start_pfn, region->end_pfn) {
+			/*
+			 * It is safe to ignore the result of
+			 * mem_bm_set_bit_check() here, since we won't
+			 * touch the PFNs for which the error is
+			 * returned anyway.
+			 */
+			mem_bm_set_bit_check(bm, pfn);
+		}
 	}
 }
 
@@ -1255,21 +1254,20 @@ static void mark_free_pages(struct zone *zone)
 	spin_lock_irqsave(&zone->lock, flags);
 
 	max_zone_pfn = zone_end_pfn(zone);
-	for (pfn = zone->zone_start_pfn; pfn < max_zone_pfn; pfn++)
-		if (pfn_valid(pfn)) {
-			page = pfn_to_page(pfn);
+	for_each_valid_pfn(pfn, zone->zone_start_pfn, max_zone_pfn) {
+		page = pfn_to_page(pfn);
 
-			if (!--page_count) {
-				touch_nmi_watchdog();
-				page_count = WD_PAGE_COUNT;
-			}
+		if (!--page_count) {
+			touch_nmi_watchdog();
+			page_count = WD_PAGE_COUNT;
+		}
 
-			if (page_zone(page) != zone)
-				continue;
+		if (page_zone(page) != zone)
+			continue;
 
-			if (!swsusp_page_is_forbidden(page))
-				swsusp_unset_page_free(page);
-		}
+		if (!swsusp_page_is_forbidden(page))
+			swsusp_unset_page_free(page);
+	}
 
 	for_each_migratetype_order(order, t) {
 		list_for_each_entry(page,
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [RFC PATCH v2 6/7] mm, x86: Use for_each_valid_pfn() from __ioremap_check_ram()
  2025-04-04 15:59 [RFC PATCH v2 1/7] mm: Introduce for_each_valid_pfn() and use it from reserve_bootmem_region() David Woodhouse
                   ` (3 preceding siblings ...)
  2025-04-04 15:59 ` [RFC PATCH v2 5/7] mm, PM: Use for_each_valid_pfn() in kernel/power/snapshot.c David Woodhouse
@ 2025-04-04 15:59 ` David Woodhouse
  2025-04-04 15:59 ` [RFC PATCH v2 7/7] mm: use for_each_valid_pfn() in memory_hotplug David Woodhouse
  5 siblings, 0 replies; 11+ messages in thread
From: David Woodhouse @ 2025-04-04 15:59 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Andrew Morton, Sauerwein, David, Anshuman Khandual,
	Ard Biesheuvel, Catalin Marinas, David Hildenbrand, Marc Zyngier,
	Mark Rutland, Mike Rapoport, Will Deacon, kvmarm,
	linux-arm-kernel, linux-kernel, linux-mm

From: David Woodhouse <dwmw@amazon.co.uk>

Instead of calling pfn_valid() separately for every single PFN in the
range, use for_each_valid_pfn() and only look at the ones which are.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/mm/ioremap.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/arch/x86/mm/ioremap.c b/arch/x86/mm/ioremap.c
index 331e101bf801..12c8180ca1ba 100644
--- a/arch/x86/mm/ioremap.c
+++ b/arch/x86/mm/ioremap.c
@@ -71,7 +71,7 @@ int ioremap_change_attr(unsigned long vaddr, unsigned long size,
 static unsigned int __ioremap_check_ram(struct resource *res)
 {
 	unsigned long start_pfn, stop_pfn;
-	unsigned long i;
+	unsigned long pfn;
 
 	if ((res->flags & IORESOURCE_SYSTEM_RAM) != IORESOURCE_SYSTEM_RAM)
 		return 0;
@@ -79,9 +79,8 @@ static unsigned int __ioremap_check_ram(struct resource *res)
 	start_pfn = (res->start + PAGE_SIZE - 1) >> PAGE_SHIFT;
 	stop_pfn = (res->end + 1) >> PAGE_SHIFT;
 	if (stop_pfn > start_pfn) {
-		for (i = 0; i < (stop_pfn - start_pfn); ++i)
-			if (pfn_valid(start_pfn + i) &&
-			    !PageReserved(pfn_to_page(start_pfn + i)))
+		for_each_valid_pfn(pfn, start_pfn, stop_pfn)
+			if (!PageReserved(pfn_to_page(pfn)))
 				return IORES_MAP_SYSTEM_RAM;
 	}
 
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [RFC PATCH v2 7/7] mm: use for_each_valid_pfn() in memory_hotplug
  2025-04-04 15:59 [RFC PATCH v2 1/7] mm: Introduce for_each_valid_pfn() and use it from reserve_bootmem_region() David Woodhouse
                   ` (4 preceding siblings ...)
  2025-04-04 15:59 ` [RFC PATCH v2 6/7] mm, x86: Use for_each_valid_pfn() from __ioremap_check_ram() David Woodhouse
@ 2025-04-04 15:59 ` David Woodhouse
  5 siblings, 0 replies; 11+ messages in thread
From: David Woodhouse @ 2025-04-04 15:59 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Andrew Morton, Sauerwein, David, Anshuman Khandual,
	Ard Biesheuvel, Catalin Marinas, David Hildenbrand, Marc Zyngier,
	Mark Rutland, Mike Rapoport, Will Deacon, kvmarm,
	linux-arm-kernel, linux-kernel, linux-mm

From: David Woodhouse <dwmw@amazon.co.uk>

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 mm/memory_hotplug.c | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 75401866fb76..20c39ae45016 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -1756,12 +1756,10 @@ static int scan_movable_pages(unsigned long start, unsigned long end,
 {
 	unsigned long pfn;
 
-	for (pfn = start; pfn < end; pfn++) {
+	for_each_valid_pfn (pfn, start, end) {
 		struct page *page;
 		struct folio *folio;
 
-		if (!pfn_valid(pfn))
-			continue;
 		page = pfn_to_page(pfn);
 		if (PageLRU(page))
 			goto found;
@@ -1805,11 +1803,9 @@ static void do_migrate_range(unsigned long start_pfn, unsigned long end_pfn)
 	static DEFINE_RATELIMIT_STATE(migrate_rs, DEFAULT_RATELIMIT_INTERVAL,
 				      DEFAULT_RATELIMIT_BURST);
 
-	for (pfn = start_pfn; pfn < end_pfn; pfn++) {
+	for_each_valid_pfn (pfn, start_pfn, end_pfn) {
 		struct page *page;
 
-		if (!pfn_valid(pfn))
-			continue;
 		page = pfn_to_page(pfn);
 		folio = page_folio(page);
 
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [RFC PATCH v2 2/7] mm: Implement for_each_valid_pfn() for CONFIG_FLATMEM
  2025-04-04 15:59 ` [RFC PATCH v2 2/7] mm: Implement for_each_valid_pfn() for CONFIG_FLATMEM David Woodhouse
@ 2025-04-07  6:54   ` Mike Rapoport
  2025-04-07  7:56     ` David Woodhouse
  0 siblings, 1 reply; 11+ messages in thread
From: Mike Rapoport @ 2025-04-07  6:54 UTC (permalink / raw)
  To: David Woodhouse
  Cc: Andrew Morton, Sauerwein, David, Anshuman Khandual,
	Ard Biesheuvel, Catalin Marinas, David Hildenbrand, Marc Zyngier,
	Mark Rutland, Mike Rapoport, Will Deacon, kvmarm,
	linux-arm-kernel, linux-kernel, linux-mm

On Fri, Apr 04, 2025 at 04:59:54PM +0100, David Woodhouse wrote:
> From: David Woodhouse <dwmw@amazon.co.uk>
> 
> In the FLATMEM case, the default pfn_valid() just checks that the PFN is
> within the range [ ARCH_PFN_OFFSET .. ARCH_PFN_OFFSET + max_mapnr ).
> 
> The for_each_valid_pfn() function can therefore be a simple for() loop
> using those as min/max respectively.
> 
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
>  include/asm-generic/memory_model.h | 26 +++++++++++++++++++++++++-
>  1 file changed, 25 insertions(+), 1 deletion(-)
> 
> diff --git a/include/asm-generic/memory_model.h b/include/asm-generic/memory_model.h
> index a3b5029aebbd..044536da3390 100644
> --- a/include/asm-generic/memory_model.h
> +++ b/include/asm-generic/memory_model.h
> @@ -30,7 +30,31 @@ static inline int pfn_valid(unsigned long pfn)
>  	return pfn >= pfn_offset && (pfn - pfn_offset) < max_mapnr;
>  }
>  #define pfn_valid pfn_valid
> -#endif
> +
> +static inline bool first_valid_pfn(unsigned long *pfn)

This is now different from SPARSEMEM version. Do we need it at all?

> +{
> +	/* avoid <linux/mm.h> include hell */
>
> +	extern unsigned long max_mapnr;
> +	unsigned long pfn_offset = ARCH_PFN_OFFSET;
> +
> +	if (*pfn < pfn_offset) {
> +		*pfn = pfn_offset;
> +		return true;
> +	}
> +
> +	if ((*pfn - pfn_offset) < max_mapnr)
> +		return true;
> +
> +	return false;
> +}
> +
> +#ifndef for_each_valid_pfn
> +#define for_each_valid_pfn(pfn, start_pfn, end_pfn)			       \
> +	for (pfn = max_t(unsigned long, start_pfn, ARCH_PFN_OFFSET);	\
> +	     pfn < min_t(unsigned long, end_pfn, ARCH_PFN_OFFSET + max_mapnr); \
> +			 pfn++)
> +#endif /* for_each_valid_pfn */
> +#endif /* valid_pfn */
>  
>  #elif defined(CONFIG_SPARSEMEM_VMEMMAP)
>  
> -- 
> 2.49.0
> 

-- 
Sincerely yours,
Mike.


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC PATCH v2 4/7] mm: Optimise SPARSEMEM implementation of for_each_valid_pfn()
  2025-04-04 15:59 ` [RFC PATCH v2 4/7] mm: Optimise SPARSEMEM implementation of for_each_valid_pfn() David Woodhouse
@ 2025-04-07  7:07   ` Mike Rapoport
  2025-04-07  8:01     ` David Woodhouse
  0 siblings, 1 reply; 11+ messages in thread
From: Mike Rapoport @ 2025-04-07  7:07 UTC (permalink / raw)
  To: David Woodhouse
  Cc: Andrew Morton, Sauerwein, David, Anshuman Khandual,
	Ard Biesheuvel, Catalin Marinas, David Hildenbrand, Marc Zyngier,
	Mark Rutland, Mike Rapoport, Will Deacon, kvmarm,
	linux-arm-kernel, linux-kernel, linux-mm

On Fri, Apr 04, 2025 at 04:59:56PM +0100, David Woodhouse wrote:
> From: David Woodhouse <dwmw@amazon.co.uk>
> 
> There's no point in checking the section and subsection bitmap for *every*
> PFN in the same section; they're either all valid or they aren't.

Don't you want to merge this with the previous commit?
 
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> ---
>  include/linux/mmzone.h | 39 +++++++++++++++++++++++++++++----------
>  1 file changed, 29 insertions(+), 10 deletions(-)
> 
> diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
> index 67cdf675a4b9..0da1b0ba5d9f 100644
> --- a/include/linux/mmzone.h
> +++ b/include/linux/mmzone.h
> @@ -2154,21 +2154,20 @@ static inline int pfn_valid(unsigned long pfn)
>  	return ret;
>  }
>  
> -static inline bool first_valid_pfn(unsigned long *p_pfn)
> +/* Returns -1 (an invalid PFN) if no valid PFN remaining */
> +static inline unsigned long first_valid_pfn(unsigned long pfn, unsigned long end_pfn)
>  {
> -	unsigned long pfn = *p_pfn;
>  	unsigned long nr = pfn_to_section_nr(pfn);
>  
>  	rcu_read_lock_sched();
>  
> -	while (nr <= __highest_present_section_nr) {
> +	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))) {
> -			*p_pfn = pfn;
>  			rcu_read_unlock_sched();
> -			return true;
> +			return pfn;
>  		}
>  
>  		/* Nothing left in this section? Skip to next section */
> @@ -2177,14 +2176,34 @@ static inline bool first_valid_pfn(unsigned long *p_pfn)
>  	}
>  
>  	rcu_read_unlock_sched();
> +	return (unsigned long)-1;
> +}
>  
> -	return false;
> +static inline unsigned long next_valid_pfn(unsigned long pfn, unsigned long end_pfn)
> +{
> +	pfn++;
> +
> +	if (pfn >= end_pfn)
> +		return (unsigned long)-1;
> +
> +	/*
> +	 * Either every PFN within the section (or subsection for VMEMMAP) is
> +	 * valid, or none of them are. So there's no point repeating the check
> +	 * for every PFN; only call first_valid_pfn() the first time, and when
> +	 * crossing a (sub)section boundary (i.e. !(pfn & ~PFN_VALID_MASK)).
> +	 */
> +	if (pfn & (IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP) ?
> +		   PAGE_SUBSECTION_MASK : PAGE_SECTION_MASK))
> +		return pfn;
> +
> +	return first_valid_pfn(pfn, end_pfn);
>  }
>  
> -#define for_each_valid_pfn(_pfn, _start_pfn, _end_pfn)	       \
> -	for ((_pfn) = (_start_pfn);			       \
> -	     first_valid_pfn(&(_pfn)) && (_pfn) < (_end_pfn);  \
> -	     (_pfn)++)
> +
> +#define for_each_valid_pfn(_pfn, _start_pfn, _end_pfn)			\
> +	for ((_pfn) = first_valid_pfn((_start_pfn), (_end_pfn));	\
> +	     (_pfn) != (unsigned long)-1;				\
> +	     (_pfn) = next_valid_pfn((_pfn), (_end_pfn)))
>  
>  #endif
>  
> -- 
> 2.49.0
> 

-- 
Sincerely yours,
Mike.


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC PATCH v2 2/7] mm: Implement for_each_valid_pfn() for CONFIG_FLATMEM
  2025-04-07  6:54   ` Mike Rapoport
@ 2025-04-07  7:56     ` David Woodhouse
  0 siblings, 0 replies; 11+ messages in thread
From: David Woodhouse @ 2025-04-07  7:56 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Andrew Morton, Sauerwein, David, Anshuman Khandual,
	Ard Biesheuvel, Catalin Marinas, David Hildenbrand, Marc Zyngier,
	Mark Rutland, Mike Rapoport, Will Deacon, kvmarm,
	linux-arm-kernel, linux-kernel, linux-mm

[-- Attachment #1: Type: text/plain, Size: 754 bytes --]

On Mon, 2025-04-07 at 09:54 +0300, Mike Rapoport wrote:
> el.h b/include/asm-generic/memory_model.h
> > index a3b5029aebbd..044536da3390 100644
> > --- a/include/asm-generic/memory_model.h
> > +++ b/include/asm-generic/memory_model.h
> > @@ -30,7 +30,31 @@ static inline int pfn_valid(unsigned long pfn)
> >   	return pfn >= pfn_offset && (pfn - pfn_offset) <
> > max_mapnr;
> >   }
> >   #define pfn_valid pfn_valid
> > -#endif
> > +
> > +static inline bool first_valid_pfn(unsigned long *pfn)
> 
> This is now different from SPARSEMEM version. Do we need it at all?

Er, no. I think it's left over from the first implementation, before I
realised I could put it all into the loop and didn't need a helper at
all. I'll remove it.

[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [RFC PATCH v2 4/7] mm: Optimise SPARSEMEM implementation of for_each_valid_pfn()
  2025-04-07  7:07   ` Mike Rapoport
@ 2025-04-07  8:01     ` David Woodhouse
  0 siblings, 0 replies; 11+ messages in thread
From: David Woodhouse @ 2025-04-07  8:01 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Andrew Morton, Sauerwein, David, Anshuman Khandual,
	Ard Biesheuvel, Catalin Marinas, David Hildenbrand, Marc Zyngier,
	Mark Rutland, Mike Rapoport, Will Deacon, kvmarm,
	linux-arm-kernel, linux-kernel, linux-mm

[-- Attachment #1: Type: text/plain, Size: 911 bytes --]

On Mon, 2025-04-07 at 10:07 +0300, Mike Rapoport wrote:
> On Fri, Apr 04, 2025 at 04:59:56PM +0100, David Woodhouse wrote:
> > From: David Woodhouse <dwmw@amazon.co.uk>
> > 
> > There's no point in checking the section and subsection bitmap for *every*
> > PFN in the same section; they're either all valid or they aren't.
> 
> Don't you want to merge this with the previous commit?

Maybe. Or at least the previous commit should be using the 'return -1'
model to minimise the differences.

To start with though, I wanted it to be reviewable as an incremental
patch to what we'd already been discussing. (And I figured there was at
least a non-zero chance of you not liking it just because it's too
complex, so the whole thing is easy to drop this way).

Even after review, keeping it as a separate patch means it's easily
revertible if we find we want to go back to the simpler version.



[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2025-04-07  8:01 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-04 15:59 [RFC PATCH v2 1/7] mm: Introduce for_each_valid_pfn() and use it from reserve_bootmem_region() David Woodhouse
2025-04-04 15:59 ` [RFC PATCH v2 2/7] mm: Implement for_each_valid_pfn() for CONFIG_FLATMEM David Woodhouse
2025-04-07  6:54   ` Mike Rapoport
2025-04-07  7:56     ` David Woodhouse
2025-04-04 15:59 ` [RFC PATCH v2 3/7] mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM David Woodhouse
2025-04-04 15:59 ` [RFC PATCH v2 4/7] mm: Optimise SPARSEMEM implementation of for_each_valid_pfn() David Woodhouse
2025-04-07  7:07   ` Mike Rapoport
2025-04-07  8:01     ` David Woodhouse
2025-04-04 15:59 ` [RFC PATCH v2 5/7] mm, PM: Use for_each_valid_pfn() in kernel/power/snapshot.c David Woodhouse
2025-04-04 15:59 ` [RFC PATCH v2 6/7] mm, x86: Use for_each_valid_pfn() from __ioremap_check_ram() David Woodhouse
2025-04-04 15:59 ` [RFC PATCH v2 7/7] mm: use for_each_valid_pfn() in memory_hotplug David Woodhouse

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).