arm64: drop pfn_valid_within() and simplify pfn_valid()
Subsystems (7)
Mike Rapoport
maintainerv4 cover arm64: drop pfn_valid_within() and simplify pfn_valid()
From: Mike Rapoport <[email protected]>
Hi,
These patches aim to remove CONFIG_HOLES_IN_ZONE and essentially hardwire pfn_valid_within() to 1.
The idea is to mark NOMAP pages as reserved in the memory map and restore the intended semantics of pfn_valid() to designate availability of struct page for a pfn.
With this the core mm will be able to cope with the fact that it cannot use NOMAP pages and the holes created by NOMAP ranges within MAX_ORDER blocks will be treated correctly even without the need for pfn_valid_within.
The patches are boot tested on qemu-system-aarch64.
I beleive it would be best to route these via mmotm tree.
v4:
* rebase on v5.13-rc1
v3: Link: https://lore.kernel.org/lkml/[email protected]
* Fix minor issues found by Anshuman
* Freshen up the declaration of pfn_valid() to make it consistent with pfn_is_map_memory()
* Add more Acked-by and Reviewed-by tags, thanks Anshuman and David
v2: Link: https://lore.kernel.org/lkml/[email protected]
* Add check for PFN overflow in pfn_is_map_memory()
* Add Acked-by and Reviewed-by tags, thanks David.
v1: Link: https://lore.kernel.org/lkml/[email protected]
* Add comment about the semantics of pfn_valid() as Anshuman suggested
* Extend comments about MEMBLOCK_NOMAP, per Anshuman
* Use pfn_is_map_memory() name for the exported wrapper for memblock_is_map_memory(). It is still local to arch/arm64 in the end because of header dependency issues.
rfc: Link: https://lore.kernel.org/lkml/[email protected]
Mike Rapoport (4):
include/linux/mmzone.h: add documentation for pfn_valid()
memblock: update initialization of reserved pages
arm64: decouple check whether pfn is in linear map from pfn_valid()
arm64: drop pfn_valid_within() and simplify pfn_valid()
arch/arm64/Kconfig | 3 --- arch/arm64/include/asm/memory.h | 2 +- arch/arm64/include/asm/page.h | 3 ++- arch/arm64/kvm/mmu.c | 2 +- arch/arm64/mm/init.c | 14 +++++++++++++- arch/arm64/mm/ioremap.c | 4 ++-- arch/arm64/mm/mmu.c | 2 +- include/linux/memblock.h | 4 +++- include/linux/mmzone.h | 11 +++++++++++ mm/memblock.c | 28 ++++++++++++++++++++++++++-- 10 files changed, 60 insertions(+), 13 deletions(-)
base-commit: 6efb943b8616ec53a5e444193dccf1af9ad627b5
Signature · 1 line
2.28.0
Mike Rapoport
maintainerPATCH v4 1/4 include/linux/mmzone.h: add documentation for pfn_valid()
From: Mike Rapoport <[email protected]>
Add comment describing the semantics of pfn_valid() that clarifies that pfn_valid() only checks for availability of a memory map entry (i.e. struct page) for a PFN rather than availability of usable memory backing that PFN.
The most "generic" version of pfn_valid() used by the configurations with SPARSEMEM enabled resides in include/linux/mmzone.h so this is the most suitable place for documentation about semantics of pfn_valid().
--- include/linux/mmzone.h | 11 +++++++++++ 1 file changed, 11 insertions(+)
include/linux/mmzone.h +11 -0
@@ -1427,6 +1427,17 @@ static inline int pfn_section_valid(struct mem_section *ms, unsigned long pfn)#endif #ifndef CONFIG_HAVE_ARCH_PFN_VALID +/** +* pfn_valid - check if there is a valid memory map entry for a PFN +* @pfn: the page frame number to check +* +* Check if there is a valid memory map entry aka struct page for the @pfn. +* Note, that availability of the memory map entry does not imply that +* there is actual usable memory at that @pfn. The struct page may +* represent a hole or an unusable page frame. +* +* Return: 1 for PFNs that have memory map entries and 0 otherwise +*/ static inline int pfn_valid (unsigned long pfn) {struct mem_section *ms;
Signature · 1 line
2.28.0
Re: [PATCH v4 1/4] include/linux/mmzone.h: add documentation for pfn_valid()
Quoted 7 lines · Mike Rapoport
From: Mike Rapoport <[email protected]>
Add comment describing the semantics of
pfn_valid()that clarifies thatpfn_valid()only checks for availability of a memory map entry (i.e. struct page) for a PFN rather than availability of usable memory backing that PFN.
Quoted 7 lines
The most "generic" version of
pfn_valid()used by the configurations with SPARSEMEM enabled resides ininclude/linux/mmzone.hso this is the most suitable place for documentation about semantics ofpfn_valid().Suggested-by Anshuman Khandual <[email protected]>Signed-off-by Mike Rapoport <[email protected]>Reviewed-by Anshuman Khandual <[email protected]>
Quoted 29 lines
--- include/linux/mmzone.h | 11 +++++++++++ 1 file changed, 11 insertions(+)include/linux/mmzone.h +11
-1@@ -1427,6 +1427,17 @@ static inline int pfn_section_valid(struct mem_section *ms, unsigned long pfn)#endif #ifndef CONFIG_HAVE_ARCH_PFN_VALID +/** +* pfn_valid - check if there is a valid memory map entry for a PFN +* @pfn: the page frame number to check +* +* Check if there is a valid memory map entry aka struct page for the @pfn. +* Note, that availability of the memory map entry does not imply that +* there is actual usable memory at that @pfn. The struct page may +* represent a hole or an unusable page frame. +* +* Return: 1 for PFNs that have memory map entries and 0 otherwise +*/ static inline int pfn_valid (unsigned long pfn) {struct mem_section *ms; --2.28.0
Mike Rapoport
maintainerPATCH v4 2/4 memblock: update initialization of reserved pages
In reply to Mike RapoportFrom: Mike Rapoport <[email protected]>
The struct pages representing a reserved memory region are initialized using reserve_bootmem_range() function. This function is called for each reserved region just before the memory is freed from memblock to the buddy page allocator.
The struct pages for MEMBLOCK_NOMAP regions are kept with the default values set by the memory map initialization which makes it necessary to have a special treatment for such pages in pfn_valid() and pfn_valid_within().
Split out initialization of the reserved pages to a function with a meaningful name and treat the MEMBLOCK_NOMAP regions the same way as the reserved regions and mark struct pages for the NOMAP regions as PageReserved.
--- include/linux/memblock.h | 4 +++- mm/memblock.c | 28 ++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-)
include/linux/memblock.h +3 -1
@@ -30,7 +30,9 @@ extern unsigned long long max_possible_pfn;* @MEMBLOCK_NONE: no special request * @MEMBLOCK_HOTPLUG: hotpluggable region * @MEMBLOCK_MIRROR: mirrored region -* @MEMBLOCK_NOMAP: don't add to kernel direct mapping +* @MEMBLOCK_NOMAP: don't add to kernel direct mapping and treat as +* reserved in the memory map; refer to memblock_mark_nomap() description +* for further details */ enum memblock_flags {MEMBLOCK_NONE =0x0 ,/* No special request */
mm/memblock.c +26 -2
@@ -906,6 +906,11 @@ int __init_memblock memblock_mark_mirror(phys_addr_t base, phys_addr_t size)* @base: the base phys addr of the region * @size: the size of the region * +* The memory regions marked with %MEMBLOCK_NOMAP will not be added to the +* direct mapping of the physical memory. These regions will still be +* covered by the memory map. The struct page representing NOMAP memory +* frames in the memory map will be PageReserved() +* * Return: 0 on success, -errno on failure. */ int __init_memblock memblock_mark_nomap(phys_addr_t base, phys_addr_t size)
@@ -2002,6 +2007,26 @@ static unsigned long __init __free_memory_core(phys_addr_t start,return end_pfn - start_pfn; } +static void __init memmap_init_reserved_pages (void ) +{ +struct memblock_region *region; +phys_addr_t start, end; +u64 i; + +/* initialize struct pages for the reserved regions */ +for_each_reserved_mem_range (i, &start, &end) +reserve_bootmem_region (start, end); + +/* and also treat struct pages for the NOMAP regions as PageReserved */ +for_each_mem_region (region) { +if (memblock_is_nomap (region)) { + start = region->base; + end = start + region->size; +reserve_bootmem_region (start, end); + } + } +} +static unsigned long __init free_low_memory_core_early (void ) {unsigned long count =0 ;
@@ -2010,8 +2035,7 @@ static unsigned long __init free_low_memory_core_early(void)memblock_clear_hotplug (0 ,-1 ); -for_each_reserved_mem_range (i, &start, &end) -reserve_bootmem_region (start, end); +memmap_init_reserved_pages ();/* * We need to use NUMA_NO_NODE instead of NODE_DATA(0)->node_id
Signature · 1 line
2.28.0
Re: [PATCH v4 2/4] memblock: update initialization of reserved pages
Quoted 13 lines · Mike Rapoport
From: Mike Rapoport <[email protected]>
The struct pages representing a reserved memory region are initialized using
reserve_bootmem_range()function. This function is called for each reserved region just before the memory is freed from memblock to the buddy page allocator.The struct pages for
MEMBLOCK_NOMAPregions are kept with the default values set by the memory map initialization which makes it necessary to have a special treatment for such pages inpfn_valid()andpfn_valid_within().
Quoted 8 lines
Split out initialization of the reserved pages to a function with a meaningful name and treat the
MEMBLOCK_NOMAPregions the same way as the reserved regions and mark struct pages for the NOMAP regions as PageReserved.Signed-off-by Mike Rapoport <[email protected]>Reviewed-by David Hildenbrand <[email protected]>Reviewed-by Anshuman Khandual <[email protected]>
Quoted 76 lines
--- include/linux/memblock.h | 4 +++- mm/memblock.c | 28 ++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-)include/linux/memblock.h +3
-1@@ -30,7 +30,9 @@ extern unsigned long long max_possible_pfn;* @MEMBLOCK_NONE: no special request * @MEMBLOCK_HOTPLUG: hotpluggable region * @MEMBLOCK_MIRROR: mirrored region -* @MEMBLOCK_NOMAP: don't add to kernel direct mapping +* @MEMBLOCK_NOMAP: don't add to kernel direct mapping and treat as +* reserved in the memory map; refer to memblock_mark_nomap() description +* for further details */ enum memblock_flags {MEMBLOCK_NONE =0x0 ,/* No special request */ mm/memblock.c +26
-3@@ -906,6 +906,11 @@ int __init_memblock memblock_mark_mirror(phys_addr_t base, phys_addr_t size)* @base: the base phys addr of the region * @size: the size of the region * +* The memory regions marked with %MEMBLOCK_NOMAP will not be added to the +* direct mapping of the physical memory. These regions will still be +* covered by the memory map. The struct page representing NOMAP memory +* frames in the memory map will be PageReserved() +* * Return: 0 on success, -errno on failure. */ int __init_memblock memblock_mark_nomap(phys_addr_t base, phys_addr_t size)@@ -2002,6 +2007,26 @@ static unsigned long __init __free_memory_core(phys_addr_t start,return end_pfn - start_pfn; } +static void __init memmap_init_reserved_pages (void ) +{ +struct memblock_region *region; +phys_addr_t start, end; +u64 i; + +/* initialize struct pages for the reserved regions */ +for_each_reserved_mem_range (i, &start, &end) +reserve_bootmem_region (start, end); + +/* and also treat struct pages for the NOMAP regions as PageReserved */ +for_each_mem_region (region) { +if (memblock_is_nomap (region)) { + start = region->base; + end = start + region->size; +reserve_bootmem_region (start, end); + } + } +} +static unsigned long __init free_low_memory_core_early (void ) {unsigned long count =0 ;@@ -2010,8 +2035,7 @@ static unsigned long __init free_low_memory_core_early(void)memblock_clear_hotplug (0 ,-1 ); -for_each_reserved_mem_range (i, &start, &end) -reserve_bootmem_region (start, end); +memmap_init_reserved_pages ();/* * We need to use NUMA_NO_NODE instead of NODE_DATA(0)->node_id -- 2.28.0
Re: [PATCH v4 2/4] memblock: update initialization of reserved pages
In reply to Mike RapoportQuoted 70 lines · Mike Rapoport
From: Mike Rapoport <[email protected]>
The struct pages representing a reserved memory region are initialized using
reserve_bootmem_range()function. This function is called for each reserved region just before the memory is freed from memblock to the buddy page allocator.The struct pages for
MEMBLOCK_NOMAPregions are kept with the default values set by the memory map initialization which makes it necessary to have a special treatment for such pages inpfn_valid()andpfn_valid_within().Split out initialization of the reserved pages to a function with a meaningful name and treat the
MEMBLOCK_NOMAPregions the same way as the reserved regions and mark struct pages for the NOMAP regions as PageReserved.Signed-off-by Mike Rapoport <[email protected]>Reviewed-by David Hildenbrand <[email protected]>Reviewed-by Anshuman Khandual <[email protected]>---
include/linux/memblock.h | 4 +++-
mm/memblock.c | 28 ++++++++++++++++++++++++++--2 files changed, 29 insertions(+), 3 deletions(-)include/linux/memblock.h +3
-1@@ -30,7 +30,9 @@ extern unsigned long long max_possible_pfn;* @MEMBLOCK_NONE: no special request * @MEMBLOCK_HOTPLUG: hotpluggable region * @MEMBLOCK_MIRROR: mirrored region -* @MEMBLOCK_NOMAP: don't add to kernel direct mapping +* @MEMBLOCK_NOMAP: don't add to kernel direct mapping and treat as +* reserved in the memory map; refer to memblock_mark_nomap() description +* for further details */ enum memblock_flags {
MEMBLOCK_NONE=0x0, /* No special request */mm/memblock.c +5
-0@@ -906,6 +906,11 @@ int __init_memblock memblock_mark_mirror(phys_addr_t base, phys_addr_t size)* @base: the base phys addr of the region * @size: the size of the region * +* The memory regions marked with %MEMBLOCK_NOMAP will not be added to the +* direct mapping of the physical memory. These regions will still be +* covered by the memory map. The struct page representing NOMAP memory +* frames in the memory map will be PageReserved() +* * Return: 0 on success, -errno on failure. */ int __init_memblock memblock_mark_nomap(phys_addr_t base, phys_addr_t size)@@ -2002,6 +2007,26 @@ static unsigned long __init __free_memory_core(phys_addr_t start, return end_pfn - start_pfn; }mm/memblock.c +10
-0+static void __init memmap_init_reserved_pages (void ) +{ +struct memblock_region *region; +phys_addr_t start, end; +u64 i; + +/* initialize struct pages for the reserved regions */ +for_each_reserved_mem_range (i, &start, &end) +reserve_bootmem_region (start, end); +
mm/memblock.c static unsigned long __init __free_memory_core(phys_addr_t start, · line 2029
Quoted 10 lines
mm/memblock.c +10
-0+/* and also treat struct pages for the NOMAP regions as PageReserved */ +for_each_mem_region (region) { +if (memblock_is_nomap (region)) { + start = region->base; + end = start + region->size; +reserve_bootmem_region (start, end); + } + } +} +
In some cases, that whole call to reserve_bootmem_region() may be a no- op because pfn_valid() is not true for *any* address in that range.
But reserve_bootmem_region() spends a long time iterating of them all, and eventually doing nothing:
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);
for (; start_pfn < end_pfn; start_pfn++) {
if (pfn_valid(start_pfn)) {
struct page *page = pfn_to_page(start_pfn);
init_reserved_page(start_pfn, nid);
/*
* no need for atomic set_bit because the struct
* page is not visible yet so nobody should
* access it yet.
*/
__SetPageReserved(page);
}
}
}On platforms with large NOMAP regions (e.g. which are actually reserved for guest memory to keep it out of the Linux address map and allow for kexec-based live update of the hypervisor), this pointless loop ends up taking a significant amount of time which is visible as guest steal time during the live update.
Can reserve_bootmem_region() skip the loop *completely* if no PFN in the range from start to end is valid? Or tweak the loop itself to have an 'else' case which skips to the next valid PFN? Something like
for(...) {
if (pfn_valid(start_pfn)) {
...
} else {
start_pfn = next_valid_pfn(start_pfn);
}}
Mike Rapoport
maintainerRe: [PATCH v4 2/4] memblock: update initialization of reserved pages
Quoted 61 lines · David Woodhouse
Quoted 11 lines · Mike Rapoport
mm/memblock.c +10
-0+static void __init memmap_init_reserved_pages (void ) +{ +struct memblock_region *region; +phys_addr_t start, end; +u64 i; + +/* initialize struct pages for the reserved regions */ +for_each_reserved_mem_range (i, &start, &end) +reserve_bootmem_region (start, end); +mm/memblock.c static unsigned long __init __free_memory_core(phys_addr_t start, · line 2029
Quoted 10 lines
mm/memblock.c +10
-0+/* and also treat struct pages for the NOMAP regions as PageReserved */ +for_each_mem_region (region) { +if (memblock_is_nomap (region)) { + start = region->base; + end = start + region->size; +reserve_bootmem_region (start, end); + } + } +} +In some cases, that whole call to
reserve_bootmem_region()may be a no- op becausepfn_valid()is not true for *any* address in that range.But
reserve_bootmem_region()spends a long time iterating of them all, and eventually doing nothing:void
__meminitreserve_bootmem_region(phys_addr_t start,
phys_addr_tend, int nid){ unsigned long start_pfn = PFN_DOWN(start); unsigned long end_pfn = PFN_UP(end); for (; start_pfn < end_pfn; start_pfn++) { if (pfn_valid(start_pfn)) { struct page *page = pfn_to_page(start_pfn); init_reserved_page(start_pfn, nid); /* * no need for atomic set_bit because the struct * page is not visible yet so nobody should * access it yet. */ __SetPageReserved(page); } } }On platforms with large NOMAP regions (e.g. which are actually reserved for guest memory to keep it out of the Linux address map and allow for kexec-based live update of the hypervisor), this pointless loop ends up taking a significant amount of time which is visible as guest steal time during the live update.
Can
reserve_bootmem_region()skip the loop *completely* if no PFN in the range from start to end is valid? Or tweak the loop itself to have an 'else' case which skips to the next valid PFN? Something like
Quoted 7 lines
for(...) { if (pfn_valid(start_pfn)) { ... } else { start_pfn = next_valid_pfn(start_pfn); }}
My understanding is that you have large reserved NOMAP ranges that don't appear as memory at all, so no memory map for them is created and so pfn_valid() is false for pfns in those ranges.
If this is the case one way indeed would be to make reserve_bootmem_region() skip ranges with no valid pfns.
Another way could be to memblock_reserved_mark_noinit() such ranges and then reserve_bootmem_region() won't even get called, but that would require firmware to pass that information somehow.
Signature · 2 lines
Sincerely yours, Mike.
Re: [PATCH v4 2/4] memblock: update initialization of reserved pages
Quoted 74 lines · Mike Rapoport
Quoted 61 lines · David Woodhouse
Quoted 11 lines · Mike Rapoport
(quoted diff) +10
-0+static void __init memmap_init_reserved_pages(void) +{ + struct memblock_region *region; + phys_addr_t start, end; + u64 i; + + /* initialize struct pages for the reserved regions */ + for_each_reserved_mem_range(i, &start, &end) + reserve_bootmem_region(start, end); +Review
Quoted 10 lines
(quoted diff) +10
-0+ /* and also treat struct pages for the NOMAP regions as PageReserved */ + for_each_mem_region(region) { + if (memblock_is_nomap(region)) { + start = region->base; + end = start + region->size; + reserve_bootmem_region(start, end); + } + } +} +In some cases, that whole call to
reserve_bootmem_region()may be a no- op becausepfn_valid()is not true for *any* address in that range.But
reserve_bootmem_region()spends a long time iterating of them all, and eventually doing nothing:void
__meminitreserve_bootmem_region(phys_addr_t start, phys_addr_t end, int nid){
unsigned longstart_pfn=PFN_DOWN(start); unsigned longend_pfn=PFN_UP(end);for (;
start_pfn<end_pfn; start_pfn++) {
if (pfn_valid(start_pfn)) {
struct page *page =pfn_to_page(start_pfn);
init_reserved_page(start_pfn, nid);/*
* no need for atomicset_bitbecause the struct
* page is not visible yet so nobody should
* access it yet.
*/
__SetPageReserved(page);
}
}
}On platforms with large NOMAP regions (e.g. which are actually reserved for guest memory to keep it out of the Linux address map and allow for kexec-based live update of the hypervisor), this pointless loop ends up taking a significant amount of time which is visible as guest steal time during the live update.
Can
reserve_bootmem_region()skip the loop *completely* if no PFN in the range from start to end is valid? Or tweak the loop itself to have an 'else' case which skips to the next valid PFN? Something likeQuoted 7 lines
for(...) {
if (pfn_valid(start_pfn)) {
...
} else {
start_pfn=next_valid_pfn(start_pfn);
}
}My understanding is that you have large reserved NOMAP ranges that don't appear as memory at all, so no memory map for them is created and so
pfn_valid()is false for pfns in those ranges.
Quoted 6 lines
If this is the case one way indeed would be to make
reserve_bootmem_region()skip ranges with no valid pfns.Another way could be to
memblock_reserved_mark_noinit()such ranges and thenreserve_bootmem_region()won't even get called, but that would require firmware to pass that information somehow.
I was thinking along these lines (not even build tested)...
I don't much like the (unsigned long)-1 part. I might make the helper 'static inline bool first_valid_pfn (unsigned long *pfn)' and return success or failure. But that's an implementation detail.
index 6d1fb6162ac1..edd27ba3e908 100644
include/asm-generic/memory_model.h +35 -0
@@ -29,8 +29,43 @@ static inline int pfn_valid(unsigned long pfn)return pfn >= pfn_offset && (pfn - pfn_offset) < max_mapnr; }#define pfn_valid pfn_valid + +static inline unsigned long 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) +return pfn_offset; + +if ((pfn - pfn_offset) < max_mapnr) +return pfn; + +return (unsigned long )(-1 ); +} + +#ifndef for_each_valid_pfn +#define for_each_valid_pfn (pfn, start_pfn, end_pfn) \ +/* Sanity check on the end condition */ \ + BUG_ON(end_pfn == (unsigned long)-1); \ + for (pfn = first_valid_pfn(pfn); pfn < end_pfn; \ + pfn = first_valid_pfn(pfn + 1)) +#endif +#endif + +/* +* If the architecture provides its own pfn_valid(), it can either +* provide a matching for_each_valid_pfn() or use the fallback which +* just iterates over them *all*, calling pfn_valid() for each. +*/ +#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 +#elif defined(CONFIG_SPARSEMEM_VMEMMAP)/* memmap is virtually contiguous. */
Mike Rapoport
maintainerRe: [PATCH v4 2/4] memblock: update initialization of reserved pages
Quoted 51 lines · David Woodhouse
Quoted 25 lines · Mike Rapoport
Quoted 12 lines · David Woodhouse
On Tue, 2021-05-11 at 13:05 +0300, Mike Rapoport wrote:
On platforms with large NOMAP regions (e.g. which are actually reserved for guest memory to keep it out of the Linux address map and allow for kexec-based live update of the hypervisor), this pointless loop ends up taking a significant amount of time which is visible as guest steal time during the live update.
Can
reserve_bootmem_region()skip the loop *completely* if no PFN in the range from start to end is valid? Or tweak the loop itself to have an 'else' case which skips to the next valid PFN? Something likeQuoted 7 lines
for(...) {
if (pfn_valid(start_pfn)) {
...
} else {
start_pfn=next_valid_pfn(start_pfn);
}
}My understanding is that you have large reserved NOMAP ranges that don't appear as memory at all, so no memory map for them is created and so
pfn_valid()is false for pfns in those ranges.Quoted 6 lines
If this is the case one way indeed would be to make
reserve_bootmem_region()skip ranges with no valid pfns.Another way could be to
memblock_reserved_mark_noinit()such ranges and thenreserve_bootmem_region()won't even get called, but that would require firmware to pass that information somehow.I was thinking along these lines (not even build tested)...
I don't much like the (unsigned long)-1 part. I might make the helper 'static inline bool
first_valid_pfn(unsigned long *pfn)' and return success or failure. But that's an implementation detail.index
6d1fb6162ac1..edd27ba3e908100644include/asm-generic/memory_model.h +5
-0@@ -29,8 +29,43 @@ static inline int pfn_valid(unsigned long pfn)return pfn >= pfn_offset && (pfn - pfn_offset) < max_mapnr; }#define pfn_valid pfn_valid + +static inline unsigned long first_valid_pfn (unsigned long pfn) +{ +/* avoid <linux/mm.h> include hell */ +extern unsigned long max_mapnr;
include/asm-generic/memory_model.h static inline int pfn_valid(unsigned long pfn) · line 46
Quoted 10 lines
include/asm-generic/memory_model.h +10
-0+unsigned long pfn_offset =ARCH_PFN_OFFSET ; + +if (pfn < pfn_offset) +return pfn_offset; + +if ((pfn - pfn_offset) < max_mapnr) +return pfn; + +return (unsigned long )(-1 ); +}
This seems about right for FLATMEM. For SPARSEMEM it would be something along these lines (I kept dubious -1):
static inline unsigned long first_valid_pfn(unsigned long pfn)
{
unsigned long nr = pfn_to_section_nr(pfn);
do {
if (pfn_valid(pfn))
return pfn;
pfn = section_nr_to_pfn(nr++);
} while (nr < NR_MEM_SECTIONS);
return (unsigned long)-1;
}Signature · 2 lines
Sincerely yours, Mike.
Re: [PATCH v4 2/4] memblock: update initialization of reserved pages
Quoted 57 lines · Mike Rapoport
Quoted 56 lines · David Woodhouse
Quoted 25 lines · Mike Rapoport
Quoted 12 lines · David Woodhouse
On Tue, 2021-05-11 at 13:05 +0300, Mike Rapoport wrote:
On platforms with large NOMAP regions (e.g. which are actually reserved for guest memory to keep it out of the Linux address map and allow for kexec-based live update of the hypervisor), this pointless loop ends up taking a significant amount of time which is visible as guest steal time during the live update.
Can
reserve_bootmem_region()skip the loop *completely* if no PFN in the range from start to end is valid? Or tweak the loop itself to have an 'else' case which skips to the next valid PFN? Something likeQuoted 7 lines
for(...) {
if (pfn_valid(start_pfn)) {
...
} else {
start_pfn=next_valid_pfn(start_pfn);
}
}My understanding is that you have large reserved NOMAP ranges that don't appear as memory at all, so no memory map for them is created and so
pfn_valid()is false for pfns in those ranges.Quoted 6 lines
If this is the case one way indeed would be to make
reserve_bootmem_region()skip ranges with no valid pfns.Another way could be to
memblock_reserved_mark_noinit()such ranges and thenreserve_bootmem_region()won't even get called, but that would require firmware to pass that information somehow.I was thinking along these lines (not even build tested)...
I don't much like the (unsigned long)-1 part. I might make the helper 'static inline bool
first_valid_pfn(unsigned long *pfn)' and return success or failure. But that's an implementation detail.index
6d1fb6162ac1..edd27ba3e908100644include/asm-generic/memory_model.h +10
-0@@ -29,8 +29,43 @@ static inline int pfn_valid(unsigned long pfn) return pfn >= pfn_offset && (pfn - pfn_offset) < max_mapnr; } #define pfn_valid pfn_valid + +static inline unsigned long 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) + return pfn_offset; +
Quoted 8 lines
include/asm-generic/memory_model.h static inline int pfn_valid(unsigned long pfn) · line 46
Quoted 5 lines
include/asm-generic/memory_model.h +5
-0+ if ((pfn - pfn_offset) < max_mapnr) + return pfn; + + return (unsigned long)(-1); +}This seems about right for FLATMEM. For SPARSEMEM it would be something along these lines (I kept dubious -1):
Thanks. Is that right even with CONFIG_SPARSEMEM_VMEMMAP? It seems that it's possible for pfn_valid() to be false for a given *page*, but there may still be valid pages in the remainder of the same section in that case?
I think it should only skip to the next section if the current section doesn't exist at all, not just when pfn_section_valid() return false?
I also wasn't sure how to cope with the rcu_read_lock_sched() that happens in pfn_valid(). What's that protecting against? Does it mean that by the time pfn_valid() returns true, that might not be the correct answer any more?
Quoted 13 lines
static inline unsigned long
first_valid_pfn(unsigned long pfn){ unsigned long nr = pfn_to_section_nr(pfn); do { if (pfn_valid(pfn)) return pfn; pfn = section_nr_to_pfn(nr++); } while (nr < NR_MEM_SECTIONS); return (unsigned long)-1; }
Mike Rapoport
maintainerRe: [PATCH v4 2/4] memblock: update initialization of reserved pages
Quoted 67 lines · David Woodhouse
Quoted 57 lines · Mike Rapoport
Quoted 56 lines · David Woodhouse
Quoted 25 lines · Mike Rapoport
Quoted 12 lines · David Woodhouse
On Tue, 2021-05-11 at 13:05 +0300, Mike Rapoport wrote:
On platforms with large NOMAP regions (e.g. which are actually reserved for guest memory to keep it out of the Linux address map and allow for kexec-based live update of the hypervisor), this pointless loop ends up taking a significant amount of time which is visible as guest steal time during the live update.
Can
reserve_bootmem_region()skip the loop *completely* if no PFN in the range from start to end is valid? Or tweak the loop itself to have an 'else' case which skips to the next valid PFN? Something likeQuoted 7 lines
for(...) {
if (pfn_valid(start_pfn)) {
...
} else {
start_pfn=next_valid_pfn(start_pfn);
}
}My understanding is that you have large reserved NOMAP ranges that don't appear as memory at all, so no memory map for them is created and so
pfn_valid()is false for pfns in those ranges.Quoted 6 lines
If this is the case one way indeed would be to make
reserve_bootmem_region()skip ranges with no valid pfns.Another way could be to
memblock_reserved_mark_noinit()such ranges and thenreserve_bootmem_region()won't even get called, but that would require firmware to pass that information somehow.I was thinking along these lines (not even build tested)...
I don't much like the (unsigned long)-1 part. I might make the helper 'static inline bool
first_valid_pfn(unsigned long *pfn)' and return success or failure. But that's an implementation detail.index
6d1fb6162ac1..edd27ba3e908100644include/asm-generic/memory_model.h +10
-0@@ -29,8 +29,43 @@ static inline int pfn_valid(unsigned long pfn) return pfn >= pfn_offset && (pfn - pfn_offset) < max_mapnr; } #define pfn_valid pfn_valid + +static inline unsigned long 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) + return pfn_offset; +Quoted 8 lines
include/asm-generic/memory_model.h static inline int pfn_valid(unsigned long pfn) · line 46
Quoted 5 lines
include/asm-generic/memory_model.h +5
-0+ if ((pfn - pfn_offset) < max_mapnr) + return pfn; + + return (unsigned long)(-1); +}This seems about right for FLATMEM. For SPARSEMEM it would be something along these lines (I kept dubious -1):
Quoted 4 lines
Thanks. Is that right even with
CONFIG_SPARSEMEM_VMEMMAP? It seems that it's possible forpfn_valid()to be false for a given *page*, but there may still be valid pages in the remainder of the same section in that case?
Right, it might after memory hot-remove. At boot the entire section either valid or not.
Quoted 2 lines
I think it should only skip to the next section if the current section doesn't exist at all, not just when
pfn_section_valid()return false?
Yeah, when pfn_section_valid() returns false it should itereate pfns until the end of the section and check if they are valid.
Quoted 4 lines
I also wasn't sure how to cope with the
rcu_read_lock_sched()that happens inpfn_valid(). What's that protecting against? Does it mean that by the timepfn_valid()returns true, that might not be the correct answer any more?
That's protecting against kfree_rcu() in section_deactivate() so even if the answer is still correct, later access to apparently valid struct page may blow up :/
Quoted 12 lines
Quoted 12 lines
static inline unsigned long
first_valid_pfn(unsigned long pfn){ unsigned long nr = pfn_to_section_nr(pfn); do { if (pfn_valid(pfn)) return pfn; pfn = section_nr_to_pfn(nr++); } while (nr < NR_MEM_SECTIONS); return (unsigned long)-1; }
Signature · 2 lines
Sincerely yours, Mike.
RFC PATCH 1/3 mm: Introduce for_each_valid_pfn() and use it from reserve_bootmem_region()
From: David Woodhouse <[email protected]>
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().
--- include/linux/mmzone.h | 10 ++++++++++ mm/mm_init.c | 23 ++++++++++------------- 2 files changed, 20 insertions(+), 13 deletions(-)
include/linux/mmzone.h +10 -0
@@ -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 */
mm/mm_init.c +10 -13
@@ -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); } }
Signature · 1 line
2.49.0
RFC PATCH 2/3 mm: Implement for_each_valid_pfn() for CONFIG_FLATMEM
From: David Woodhouse <[email protected]>
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.
--- include/asm-generic/memory_model.h | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-)
include/asm-generic/memory_model.h +25 -1
@@ -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)
Signature · 1 line
2.49.0
Re: [RFC PATCH 2/3] mm: Implement for_each_valid_pfn() for CONFIG_FLATMEM
Quoted 9 lines · David Woodhouse
From: David Woodhouse <[email protected]>
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 simplefor()loop using those as min/max respectively.Signed-off-by David Woodhouse <[email protected]>
Quoted 44 lines
--- include/asm-generic/memory_model.h | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-)include/asm-generic/memory_model.h +25
-1@@ -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)Signature · 1 line
2.49.0
Signature · 2 lines
Sincerely yours, Mike.
RFC PATCH 3/3 mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM
In reply to David WoodhouseFrom: David Woodhouse <[email protected]>
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.
--- include/linux/mmzone.h | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+)
include/linux/mmzone.h +65 -0
@@ -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,45 @@ 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); +struct mem_section *ms; +bool ret = false; + + ms =__pfn_to_section (pfn); + +rcu_read_lock_sched (); + +while (!ret && nr <= __highest_present_section_nr) { +if (valid_section (ms) && + (early_section (ms) ||pfn_section_first_valid (ms, &pfn))) { + ret = true; +break ; + } + + nr++; +if (nr > __highest_present_section_nr) +break ; + + pfn =section_nr_to_pfn (nr); + ms =__pfn_to_section (pfn); + } + +rcu_read_unlock_sched (); + + *p_pfn = pfn; + +return ret; +} + +#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)
Signature · 1 line
2.49.0
Re: [RFC PATCH 3/3] mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM
Quoted 11 lines · David Woodhouse
From: David Woodhouse <[email protected]>
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 apfn_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 <[email protected]>
with a small nit below
include/linux/mmzone.h static inline int pfn_valid(unsigned long pfn) · line 2168
Quoted 12 lines
include/linux/mmzone.h +12
-0+static inline bool first_valid_pfn (unsigned long *p_pfn) +{ +unsigned long pfn = *p_pfn; +unsigned long nr =pfn_to_section_nr (pfn); +struct mem_section *ms; +bool ret = false; + + ms =__pfn_to_section (pfn); + +rcu_read_lock_sched (); + +while (!ret && nr <= __highest_present_section_nr) {
This could be just for(;;), we anyway break when ret becomes true or we get past last present section.
Quoted 32 lines
include/linux/mmzone.h +26
-0+if (valid_section (ms) && + (early_section (ms) ||pfn_section_first_valid (ms, &pfn))) { + ret = true; +break ; + } + + nr++; +if (nr > __highest_present_section_nr) +break ; + + pfn =section_nr_to_pfn (nr); + ms = __pfn_to_section(pfn); + } + +rcu_read_unlock_sched (); + + *p_pfn = pfn; + +return ret; +} + +#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)Signature · 1 line
2.49.0
Signature · 2 lines
Sincerely yours, Mike.
Re: [RFC PATCH 3/3] mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM
Quoted 15 lines · Mike Rapoport
Quoted 14 lines · David Woodhouse
From: David Woodhouse <[email protected]>
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 apfn_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.
Quoted 3 lines
Quoted 1 line
Signed-off-by David Woodhouse <[email protected]>Reviewed-by Mike Rapoport (Microsoft) <[email protected]>
Thanks.
Quoted 17 lines
with a small nit below
include/linux/mmzone.h static inline int pfn_valid(unsigned long pfn) · line 2168
Quoted 12 lines
include/linux/mmzone.h +12
-0+static inline bool first_valid_pfn (unsigned long *p_pfn) +{ +unsigned long pfn = *p_pfn; +unsigned long nr =pfn_to_section_nr (pfn); +struct mem_section *ms; +bool ret = false; + + ms =__pfn_to_section (pfn); + +rcu_read_lock_sched (); + +while (!ret && nr <= __highest_present_section_nr) {This could be just for(;;), we anyway break when ret becomes true or we get past last present section.
True for the 'ret' part but not *nicely* for the last present section. If the original pfn is higher than the last present section it could trigger that check before entering the loop.
Yes, in that case 'ms' will be NULL, valid_section(NULL) is false and you're right that it'll make it through to the check in the loop without crashing. So it would currently be harmless, but I didn't like it. It's relying on the loop not to do the wrong thing with an input which is arguably invalid.
I'll see if I can make it neater. I may drop the 'ret' variable completely and just turn the match clause into unlock-and-return-true. I *like* having a single unlock site. But I think I like simpler loop code more than that.
FWIW I think the check for (PHYS_PFN(PFN_PHYS(pfn)) != pfn) at the start of pfn_valid() a few lines above is similarly redundant. Because if the high bits are set in the PFN then pfn_to_section_nr(pfn) is surely going to be higher than NR_MEM_SECTIONS and it'll get thrown out at the very next check, won't it?
I care because I didn't bother to duplicate that 'redundant' check in my first_valid_pfn(), so if there's a reason for it that I'm missing, I should take a closer look.
I'm also missing the reason why the FLATMEM code in memory_model.h does 'unsigned long pfn_offset = ARCH_PFN_OFFSET' and then uses its local pfn_offset variable, instead of just using ARCH_PFN_OFFSET directly as I do in the FLATMEM for_each_valid_pfn() macro.
Quoted 33 lines
Quoted 32 lines
include/linux/mmzone.h +26
-0+if (valid_section (ms) && + (early_section(ms) || pfn_section_first_valid(ms, &pfn))) { + ret = true; +break ; + } + + nr++; +if (nr > __highest_present_section_nr) +break ; + + pfn =section_nr_to_pfn (nr); + ms =__pfn_to_section (pfn); + } + +rcu_read_unlock_sched (); + + *p_pfn = pfn; + +return ret; +} + +#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 intpfn_in_present_section(unsigned long pfn)Signature · 1 line
2.49.0
Re: [RFC PATCH 3/3] mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM
Quoted 5 lines · David Woodhouse
I'll see if I can make it neater. I may drop the 'ret' variable completely and just turn the match clause into unlock-and-return-true. I *like* having a single unlock site. But I think I like simpler loop code more than that.
That's better (IMO).
And I note that pfn_valid() already doesn't follow the modern fetish for having only one unlock site even when it makes the surrounding code more complex to do so.
static inline bool first_valid_pfn(unsigned long *p_pfn)
{
unsigned long pfn = *p_pfn;
unsigned long nr = pfn_to_section_nr(pfn);
struct mem_section *ms;
rcu_read_lock_sched();
while (nr <= __highest_present_section_nr) {
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;
}Mike Rapoport
maintainerRe: [RFC PATCH 3/3] mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM
Quoted 14 lines · David Woodhouse
Quoted 5 lines · David Woodhouse
I'll see if I can make it neater. I may drop the 'ret' variable completely and just turn the match clause into unlock-and-return-true. I *like* having a single unlock site. But I think I like simpler loop code more than that.
That's better (IMO).
And I note that
pfn_valid()already doesn't follow the modern fetish for having only one unlock site even when it makes the surrounding code more complex to do so.static inline bool
first_valid_pfn(unsigned long *p_pfn)
Quoted 9 lines
{
unsigned long pfn = *p_pfn;
unsigned long nr = pfn_to_section_nr(pfn);
struct mem_section *ms;
rcu_read_lock_sched();
while (nr <= __highest_present_section_nr) {
ms = __pfn_to_section(pfn);Maybe move the declaration here:
struct mem_section *ms = __pfn_to_section(pfn);
Quoted 17 lines
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;
}Signature · 2 lines
Sincerely yours, Mike.
Re: [RFC PATCH 3/3] mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM
Quoted 7 lines · Mike Rapoport
Quoted 6 lines
static inline bool
first_valid_pfn(unsigned long *p_pfn){
unsigned long pfn =*p_pfn;
unsigned long nr =pfn_to_section_nr(pfn); structmem_section*ms;
Quoted 8 lines
Quoted 4 lines
rcu_read_lock_sched();while (nr <=
__highest_present_section_nr) { ms =__pfn_to_section(pfn);Maybe move the declaration here:
struct
mem_section*ms =__pfn_to_section(pfn);
Ack.
https://git.infradead.org/users/dwmw2/linux.git/shortlog/refs/heads/for_each_valid_pfn
Mike Rapoport
maintainerRe: [RFC PATCH 3/3] mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM
Quoted 13 lines · David Woodhouse
Quoted 12 lines · Mike Rapoport
Quoted 10 lines
static inline bool
first_valid_pfn(unsigned long *p_pfn){
unsigned long pfn =*p_pfn;
unsigned long nr =pfn_to_section_nr(pfn); structmem_section*ms;
rcu_read_lock_sched();while (nr <=
__highest_present_section_nr) { ms =__pfn_to_section(pfn);
Quoted 7 lines
Quoted 3 lines
Maybe move the declaration here:
struct
mem_section*ms =__pfn_to_section(pfn);Ack.
https://git.infradead.org/users/dwmw2/linux.git/shortlog/refs/heads/for_each_valid_pfn
Fine with me, keep the RB tag :)
Signature · 2 lines
Sincerely yours, Mike.
Mike Rapoport
maintainerRe: [RFC PATCH 3/3] mm: Implement for_each_valid_pfn() for CONFIG_SPARSEMEM
In reply to David WoodhouseQuoted 34 lines · David Woodhouse
Quoted 17 lines · Mike Rapoport
with a small nit below
include/linux/mmzone.h static inline int pfn_valid(unsigned long pfn) · line 2168
Quoted 12 lines
include/linux/mmzone.h +12
-0+static inline bool first_valid_pfn (unsigned long *p_pfn) +{ +unsigned long pfn = *p_pfn; +unsigned long nr =pfn_to_section_nr (pfn); +struct mem_section *ms; +bool ret = false; + + ms =__pfn_to_section (pfn); + +rcu_read_lock_sched (); + +while (!ret && nr <= __highest_present_section_nr) {This could be just for(;;), we anyway break when ret becomes true or we get past last present section.
True for the 'ret' part but not *nicely* for the last present section. If the original pfn is higher than the last present section it could trigger that check before entering the loop.
Yes, in that case 'ms' will be NULL,
valid_section(NULL)is false and you're right that it'll make it through to the check in the loop without crashing. So it would currently be harmless, but I didn't like it. It's relying on the loop not to do the wrong thing with an input which is arguably invalid.I'll see if I can make it neater. I may drop the 'ret' variable completely and just turn the match clause into unlock-and-return-true. I *like* having a single unlock site. But I think I like simpler loop code more than that.
Quoted 5 lines
FWIW I think the check for (PHYS_PFN(PFN_PHYS(pfn)) != pfn) at the start of
pfn_valid()a few lines above is similarly redundant. Because if the high bits are set in the PFN thenpfn_to_section_nr(pfn)is surely going to be higher thanNR_MEM_SECTIONSand it'll get thrown out at the very next check, won't it?
I believe the check for (PHYS_PFN(PFN_PHYS(pfn)) != pfn) got to the generic version from arm64::pfn_valid() that historically supported both FLATMEM and SPARSEMEM.
I can't think of a configuration in which (PHYS_PFN(PFN_PHYS(pfn)) != pfn) and pfn_to_section_nr(pfn) won't be higher than NR_MEM_SECTIONS, but with all variants that arm64 has for PAGE_SHIFT and ARM64_PA_BITS I could miss something.
Quoted 8 lines
I care because I didn't bother to duplicate that 'redundant' check in my
first_valid_pfn(), so if there's a reason for it that I'm missing, I should take a closer look.I'm also missing the reason why the FLATMEM code in
memory_model.hdoes 'unsigned longpfn_offset=ARCH_PFN_OFFSET' and then uses its localpfn_offsetvariable, instead of just usingARCH_PFN_OFFSETdirectly as I do in the FLATMEMfor_each_valid_pfn()macro.
Don't remember now, but I surely had some $REASON for that :)
Signature · 2 lines
Sincerely yours, Mike.
Re: [RFC PATCH 1/3] mm: Introduce for_each_valid_pfn() and use it from reserve_bootmem_region()
In reply to David WoodhouseQuoted 11 lines · David Woodhouse
From: David Woodhouse <[email protected]>
Especially since commit
9092d4f7a1f8("memblock: update initialization of reserved pages"), thereserve_bootmem_region()function can spend a significant amount of time iterating over every 4KiB PFN in a range, callingpfn_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.
Quoted 8 lines
Introduce
for_each_valid_pfn()and use it fromreserve_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 ownpfn_valid()implementation, until/unless they also provide a matchingfor_each_valid_pfn().Signed-off-by David Woodhouse <[email protected]>
Quoted 66 lines
--- include/linux/mmzone.h | 10 ++++++++++ mm/mm_init.c | 23 ++++++++++------------- 2 files changed, 20 insertions(+), 13 deletions(-)include/linux/mmzone.h +10
-0@@ -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 */ mm/mm_init.c +10
-13@@ -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); } }Signature · 1 line
2.49.0
Signature · 2 lines
Sincerely yours, Mike.
Mike Rapoport
maintainerPATCH v4 3/4 arm64: decouple check whether pfn is in linear map from pfn_valid()
In reply to Mike RapoportFrom: Mike Rapoport <[email protected]>
The intended semantics of pfn_valid() is to verify whether there is a struct page for the pfn in question and nothing else.
Yet, on arm64 it is used to distinguish memory areas that are mapped in the linear map vs those that require ioremap() to access them.
Introduce a dedicated pfn_is_map_memory() wrapper for memblock_is_map_memory() to perform such check and use it where appropriate.
Using a wrapper allows to avoid cyclic include dependencies.
While here also update style of pfn_valid() so that both pfn_valid() and pfn_is_map_memory() declarations will be consistent.
--- arch/arm64/include/asm/memory.h | 2 +- arch/arm64/include/asm/page.h | 3 ++- arch/arm64/kvm/mmu.c | 2 +- arch/arm64/mm/init.c | 12 ++++++++++++ arch/arm64/mm/ioremap.c | 4 ++-- arch/arm64/mm/mmu.c | 2 +- 6 files changed, 19 insertions(+), 6 deletions(-)
arch/arm64/include/asm/memory.h +1 -1
@@ -369,7 +369,7 @@ static inline void *phys_to_virt(phys_addr_t x)#define virt_addr_valid (addr) ({ \ __typeof__(addr) __addr = __tag_reset(addr); \ - __is_lm_address(__addr) && pfn_valid(virt_to_pfn(__addr)); \ + __is_lm_address(__addr) && pfn_is_map_memory(virt_to_pfn(__addr)); \ })void dump_mem_limit (void );
arch/arm64/include/asm/page.h +2 -1
@@ -37,7 +37,8 @@ void copy_highpage(struct page *to, struct page *from);typedef struct page *pgtable_t ; -extern int pfn_valid (unsigned long ); +int pfn_valid (unsigned long pfn); +int pfn_is_map_memory (unsigned long pfn);#include <asm/memory.h>
arch/arm64/kvm/mmu.c +1 -1
@@ -85,7 +85,7 @@ void kvm_flush_remote_tlbs(struct kvm *kvm)static bool kvm_is_device_pfn (unsigned long pfn) { -return !pfn_valid (pfn); +return !pfn_is_map_memory (pfn); }static void *stage2_memcache_zalloc_page (void *arg)
arch/arm64/mm/init.c +12 -0
@@ -255,6 +255,18 @@ int pfn_valid(unsigned long pfn) }EXPORT_SYMBOL (pfn_valid); +int pfn_is_map_memory (unsigned long pfn) +{ +phys_addr_t addr =PFN_PHYS (pfn); + +/* avoid false positives for bogus PFNs, see comment in pfn_valid() */ +if (PHYS_PFN (addr) != pfn) +return 0 ; + +return memblock_is_map_memory (addr); +} +EXPORT_SYMBOL (pfn_is_map_memory); +static phys_addr_t memory_limit =PHYS_ADDR_MAX ;/*
arch/arm64/mm/ioremap.c +2 -2
@@ -43,7 +43,7 @@ static void __iomem *__ioremap_caller(phys_addr_t phys_addr, size_t size,/* * Don't allow RAM to be mapped. */ -if (WARN_ON (pfn_valid (__phys_to_pfn (phys_addr)))) +if (WARN_ON (pfn_is_map_memory (__phys_to_pfn (phys_addr))))return NULL ; area =get_vm_area_caller (size,VM_IOREMAP , caller);
@@ -84,7 +84,7 @@ EXPORT_SYMBOL(iounmap);void __iomem *ioremap_cache (phys_addr_t phys_addr,size_t size) {/* For normal memory we already have a cacheable mapping. */ -if (pfn_valid (__phys_to_pfn (phys_addr))) +if (pfn_is_map_memory (__phys_to_pfn (phys_addr)))return (void __iomem *)__phys_to_virt (phys_addr); return __ioremap_caller(phys_addr, size, __pgprot(PROT_NORMAL),
arch/arm64/mm/mmu.c +1 -1
@@ -82,7 +82,7 @@ void set_swapper_pgd(pgd_t *pgdp, pgd_t pgd)pgprot_t phys_mem_access_prot (struct file *file,unsigned long pfn,unsigned long size,pgprot_t vma_prot) { -if (!pfn_valid (pfn)) +if (!pfn_is_map_memory (pfn))return pgprot_noncached (vma_prot);else if (file->f_flags &O_SYNC )return pgprot_writecombine (vma_prot);
Signature · 1 line
2.28.0
Re: [PATCH v4 3/4] arm64: decouple check whether pfn is in linear map from pfn_valid()
Quoted 13 lines · Mike Rapoport
From: Mike Rapoport <[email protected]>
The intended semantics of
pfn_valid()is to verify whether there is a struct page for the pfn in question and nothing else.Yet, on arm64 it is used to distinguish memory areas that are mapped in the linear map vs those that require
ioremap()to access them.Introduce a dedicated
pfn_is_map_memory()wrapper formemblock_is_map_memory()to perform such check and use it where appropriate.
Quoted 7 lines
Using a wrapper allows to avoid cyclic include dependencies.
While here also update style of
pfn_valid()so that bothpfn_valid()andpfn_is_map_memory()declarations will be consistent.Signed-off-by Mike Rapoport <[email protected]>
Quoted 110 lines
--- arch/arm64/include/asm/memory.h | 2 +- arch/arm64/include/asm/page.h | 3 ++- arch/arm64/kvm/mmu.c | 2 +- arch/arm64/mm/init.c | 12 ++++++++++++ arch/arm64/mm/ioremap.c | 4 ++-- arch/arm64/mm/mmu.c | 2 +- 6 files changed, 19 insertions(+), 6 deletions(-)arch/arm64/include/asm/memory.h +1
-1@@ -369,7 +369,7 @@ static inline void *phys_to_virt(phys_addr_t x)#define virt_addr_valid (addr) ({ \ __typeof__(addr) __addr = __tag_reset(addr); \ - __is_lm_address(__addr) && pfn_valid(virt_to_pfn(__addr)); \ + __is_lm_address(__addr) && pfn_is_map_memory(virt_to_pfn(__addr)); \ })void dump_mem_limit (void );arch/arm64/include/asm/page.h +2
-1@@ -37,7 +37,8 @@ void copy_highpage(struct page *to, struct page *from);typedef struct page *pgtable_t ; -extern int pfn_valid (unsigned long ); +int pfn_valid (unsigned long pfn); +int pfn_is_map_memory (unsigned long pfn);#include <asm/memory.h> arch/arm64/kvm/mmu.c +1
-1@@ -85,7 +85,7 @@ void kvm_flush_remote_tlbs(struct kvm *kvm)static bool kvm_is_device_pfn (unsigned long pfn) { -return !pfn_valid (pfn); +return !pfn_is_map_memory (pfn); }static void *stage2_memcache_zalloc_page (void *arg)arch/arm64/mm/init.c +12
-0@@ -255,6 +255,18 @@ int pfn_valid(unsigned long pfn) }EXPORT_SYMBOL (pfn_valid); +int pfn_is_map_memory (unsigned long pfn) +{ +phys_addr_t addr =PFN_PHYS (pfn); + +/* avoid false positives for bogus PFNs, see comment in pfn_valid() */ +if (PHYS_PFN (addr) != pfn) +return 0 ; + +return memblock_is_map_memory (addr); +} +EXPORT_SYMBOL (pfn_is_map_memory); +static phys_addr_t memory_limit =PHYS_ADDR_MAX ;/* arch/arm64/mm/ioremap.c +2
-2@@ -43,7 +43,7 @@ static void __iomem *__ioremap_caller(phys_addr_t phys_addr, size_t size,/* * Don't allow RAM to be mapped. */ -if (WARN_ON (pfn_valid (__phys_to_pfn (phys_addr)))) +if (WARN_ON (pfn_is_map_memory (__phys_to_pfn (phys_addr))))return NULL ; area =get_vm_area_caller (size,VM_IOREMAP , caller);@@ -84,7 +84,7 @@ EXPORT_SYMBOL(iounmap);void __iomem *ioremap_cache (phys_addr_t phys_addr,size_t size) {/* For normal memory we already have a cacheable mapping. */ -if (pfn_valid (__phys_to_pfn (phys_addr))) +if (pfn_is_map_memory (__phys_to_pfn (phys_addr)))return (void __iomem *)__phys_to_virt (phys_addr); return __ioremap_caller(phys_addr, size, __pgprot(PROT_NORMAL),arch/arm64/mm/mmu.c +1
-2@@ -82,7 +82,7 @@ void set_swapper_pgd(pgd_t *pgdp, pgd_t pgd)pgprot_t phys_mem_access_prot (struct file *file,unsigned long pfn,unsigned long size,pgprot_t vma_prot) { -if (!pfn_valid (pfn)) +if (!pfn_is_map_memory (pfn))return pgprot_noncached (vma_prot);else if (file->f_flags &O_SYNC )return pgprot_writecombine (vma_prot); --2.28.0
Mike Rapoport
maintainerPATCH v4 4/4 arm64: drop pfn_valid_within() and simplify pfn_valid()
In reply to Mike RapoportFrom: Mike Rapoport <[email protected]>
The arm64's version of pfn_valid() differs from the generic because of two reasons:
* Parts of the memory map are freed during boot. This makes it necessary to verify that there is actual physical memory that corresponds to a pfn which is done by querying memblock.
* There are NOMAP memory regions. These regions are not mapped in the linear map and until the previous commit the struct pages representing these areas had default values.
As the consequence of absence of the special treatment of NOMAP regions in the memory map it was necessary to use memblock_is_map_memory() in pfn_valid() and to have pfn_valid_within() aliased to pfn_valid() so that generic mm functionality would not treat a NOMAP page as a normal page.
Since the NOMAP regions are now marked as PageReserved(), pfn walkers and the rest of core mm will treat them as unusable memory and thus pfn_valid_within() is no longer required at all and can be disabled by removing CONFIG_HOLES_IN_ZONE on arm64.
pfn_valid() can be slightly simplified by replacing memblock_is_map_memory() with memblock_is_memory().
--- arch/arm64/Kconfig | 3 --- arch/arm64/mm/init.c | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-)
arch/arm64/Kconfig +0 -3
@@ -1052,9 +1052,6 @@ config NEED_PER_CPU_EMBED_FIRST_CHUNK def_bool y depends on NUMA -config HOLES_IN_ZONE - def_bool y - source "kernel/Kconfig.hz" config ARCH_SPARSEMEM_ENABLE
arch/arm64/mm/init.c +1 -1
@@ -251,7 +251,7 @@ int pfn_valid(unsigned long pfn)if (!early_section (ms))return pfn_section_valid (ms, pfn); -return memblock_is_map_memory (addr); +return memblock_is_memory (addr); }EXPORT_SYMBOL (pfn_valid);
Signature · 1 line
2.28.0
Quoted 24 lines · Mike Rapoport
From: Mike Rapoport <[email protected]>
The arm64's version of
pfn_valid()differs from the generic because of two reasons:* Parts of the memory map are freed during boot. This makes it necessary to verify that there is actual physical memory that corresponds to a pfn which is done by querying memblock.
* There are NOMAP memory regions. These regions are not mapped in the linear map and until the previous commit the struct pages representing these areas had default values.
As the consequence of absence of the special treatment of NOMAP regions in the memory map it was necessary to use
memblock_is_map_memory()inpfn_valid()and to havepfn_valid_within()aliased topfn_valid()so that generic mm functionality would not treat a NOMAP page as a normal page.Since the NOMAP regions are now marked as
PageReserved(), pfn walkers and the rest of core mm will treat them as unusable memory and thuspfn_valid_within()is no longer required at all and can be disabled by removingCONFIG_HOLES_IN_ZONEon arm64.
Quoted 5 lines
pfn_valid()can be slightly simplified by replacingmemblock_is_map_memory()withmemblock_is_memory().Signed-off-by Mike Rapoport <[email protected]>
... and many thanks for cleaning this up.
Quoted 35 lines
--- arch/arm64/Kconfig | 3 --- arch/arm64/mm/init.c | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-)arch/arm64/Kconfig +0
-3@@ -1052,9 +1052,6 @@ config NEED_PER_CPU_EMBED_FIRST_CHUNK def_bool y depends on NUMA -config HOLES_IN_ZONE - def_bool y - source "kernel/Kconfig.hz" config ARCH_SPARSEMEM_ENABLEarch/arm64/mm/init.c +1
-2@@ -251,7 +251,7 @@ int pfn_valid(unsigned long pfn)if (!early_section (ms))return pfn_section_valid (ms, pfn); -return memblock_is_map_memory (addr); +return memblock_is_memory (addr); }EXPORT_SYMBOL (pfn_valid); --2.28.0
Andrew Morton
maintainerQuoted 31 lines · On Tue, 11 May 2021 13:05:50 +0300 Mike Rapoport <[email protected]> wrote:
From: Mike Rapoport <[email protected]>
The arm64's version of
pfn_valid()differs from the generic because of two reasons:* Parts of the memory map are freed during boot. This makes it necessary to verify that there is actual physical memory that corresponds to a pfn which is done by querying memblock.
* There are NOMAP memory regions. These regions are not mapped in the linear map and until the previous commit the struct pages representing these areas had default values.
As the consequence of absence of the special treatment of NOMAP regions in the memory map it was necessary to use
memblock_is_map_memory()inpfn_valid()and to havepfn_valid_within()aliased topfn_valid()so that generic mm functionality would not treat a NOMAP page as a normal page.Since the NOMAP regions are now marked as
PageReserved(), pfn walkers and the rest of core mm will treat them as unusable memory and thuspfn_valid_within()is no longer required at all and can be disabled by removingCONFIG_HOLES_IN_ZONEon arm64.
pfn_valid()can be slightly simplified by replacingmemblock_is_map_memory()withmemblock_is_memory()....
arch/arm64/Kconfig config NEED_PER_CPU_EMBED_FIRST_CHUNK · line 1057
Quoted 9 lines
arch/arm64/Kconfig +0
-3@@ -1052,9 +1052,6 @@ config NEED_PER_CPU_EMBED_FIRST_CHUNK def_bool y depends on NUMA -config HOLES_IN_ZONE - def_bool y - source "kernel/Kconfig.hz" config ARCH_SPARSEMEM_ENABLE
https://lkml.kernel.org/r/[email protected]
already did this, so I simply dropped that hunk? And I don't think the changelog needs updating for this?
Quoted 12 lines
arch/arm64/mm/init.c +1
-1@@ -251,7 +251,7 @@ int pfn_valid(unsigned long pfn)if (!early_section (ms))return pfn_section_valid (ms, pfn); -return memblock_is_map_memory (addr); +return memblock_is_memory (addr); }EXPORT_SYMBOL (pfn_valid);
Mike Rapoport
maintainerQuoted 41 lines · Andrew Morton
Quoted 39 lines · On Tue, 11 May 2021 13:05:50 +0300 Mike Rapoport <[email protected]> wrote:
From: Mike Rapoport <[email protected]>
The arm64's version of
pfn_valid()differs from the generic because of two reasons:* Parts of the memory map are freed during boot. This makes it necessary to verify that there is actual physical memory that corresponds to a pfn which is done by querying memblock.
* There are NOMAP memory regions. These regions are not mapped in the linear map and until the previous commit the struct pages representing these areas had default values.
As the consequence of absence of the special treatment of NOMAP regions in the memory map it was necessary to use
memblock_is_map_memory()inpfn_valid()and to havepfn_valid_within()aliased topfn_valid()so that generic mm functionality would not treat a NOMAP page as a normal page.Since the NOMAP regions are now marked as
PageReserved(), pfn walkers and the rest of core mm will treat them as unusable memory and thuspfn_valid_within()is no longer required at all and can be disabled by removingCONFIG_HOLES_IN_ZONEon arm64.
pfn_valid()can be slightly simplified by replacingmemblock_is_map_memory()withmemblock_is_memory()....
arch/arm64/Kconfig +0
-3@@ -1052,9 +1052,6 @@ config NEED_PER_CPU_EMBED_FIRST_CHUNK def_bool y depends on NUMA -config HOLES_IN_ZONE - def_bool y - source "kernel/Kconfig.hz"
Quoted 5 lines
arch/arm64/Kconfig config NEED_PER_CPU_EMBED_FIRST_CHUNK · line 1057
Quoted 1 line
arch/arm64/Kconfig +0
-0config ARCH_SPARSEMEM_ENABLEhttps://lkml.kernel.org/r/[email protected]
already did this, so I simply dropped that hunk? And I don't think the changelog needs updating for this?
We need another hunk instead (below)
Quoted 1 line
And I don't think the changelog needs updating for this?
maybe "s/disabled by removing CONFIG_HOLES_IN_ZONE/disabled/", but does not seem that important to me.
arch/arm64/Kconfig +0 -1
@@ -201,7 +201,6 @@ config ARM64 select HAVE_KPROBES select HAVE_KRETPROBES select HAVE_GENERIC_VDSO - select HOLES_IN_ZONE select IOMMU_DMA if IOMMU_SUPPORT select IRQ_DOMAIN select IRQ_FORCED_THREADING
Quoted 11 lines · Mike Rapoport
From: Mike Rapoport <[email protected]>
Hi,
These patches aim to remove
CONFIG_HOLES_IN_ZONEand essentially hardwirepfn_valid_within()to 1.The idea is to mark NOMAP pages as reserved in the memory map and restore the intended semantics of
pfn_valid()to designate availability of struct page for a pfn.
Quoted 7 lines
With this the core mm will be able to cope with the fact that it cannot use NOMAP pages and the holes created by NOMAP ranges within
MAX_ORDERblocks will be treated correctly even without the need forpfn_valid_within.The patches are boot tested on qemu-system-aarch64.
I beleive it would be best to route these via mmotm tree.
Quoted 12 lines · Mike Rapoport
From: Mike Rapoport <[email protected]>
Hi,
These patches aim to remove
CONFIG_HOLES_IN_ZONEand essentially hardwirepfn_valid_within()to 1.The idea is to mark NOMAP pages as reserved in the memory map and restore the intended semantics of
pfn_valid()to designate availability of struct page for a pfn.
Quoted 6 lines
With this the core mm will be able to cope with the fact that it cannot use NOMAP pages and the holes created by NOMAP ranges within
MAX_ORDERblocks will be treated correctly even without the need forpfn_valid_within.The patches are boot tested on qemu-system-aarch64.
Did you use EFI boot when testing this? The memory map is much more fragmented in that case, so this would be a good data point.
Quoted 47 lines
I beleive it would be best to route these via mmotm tree.
v4:
* rebase on v5.13-rc1v3: Link: https://lore.kernel.org/lkml/[email protected]
* Fix minor issues found by Anshuman
* Freshen up the declaration ofpfn_valid()to make it consistent withpfn_is_map_memory()
* Add more Acked-by and Reviewed-by tags, thanks Anshuman and Davidv2: Link: https://lore.kernel.org/lkml/[email protected]
* Add check for PFN overflow inpfn_is_map_memory()
* Add Acked-by and Reviewed-by tags, thanks David.v1: Link: https://lore.kernel.org/lkml/[email protected]
* Add comment about the semantics ofpfn_valid()as Anshuman suggested
* Extend comments aboutMEMBLOCK_NOMAP, per Anshuman
* Usepfn_is_map_memory()name for the exported wrapper formemblock_is_map_memory(). It is still local toarch/arm64in the end because of header dependency issues.rfc: Link: https://lore.kernel.org/lkml/[email protected]
Mike Rapoport (4):
include/linux/mmzone.h: add documentation forpfn_valid()
memblock: update initialization of reserved pages
arm64: decouple check whether pfn is in linear map frompfn_valid()
arm64: droppfn_valid_within()and simplifypfn_valid()arch/arm64/Kconfig | 3 --- arch/arm64/include/asm/memory.h | 2 +- arch/arm64/include/asm/page.h | 3 ++- arch/arm64/kvm/mmu.c | 2 +- arch/arm64/mm/init.c | 14 +++++++++++++- arch/arm64/mm/ioremap.c | 4 ++-- arch/arm64/mm/mmu.c | 2 +- include/linux/memblock.h | 4 +++- include/linux/mmzone.h | 11 +++++++++++ mm/memblock.c | 28 ++++++++++++++++++++++++++-- 10 files changed, 60 insertions(+), 13 deletions(-)base-commit:
6efb943b8616ec53a5e444193dccf1af9ad627b5--
2.28.0
Quoted 17 lines · Ard Biesheuvel
Quoted 16 lines · Mike Rapoport
From: Mike Rapoport <[email protected]>
Hi,
These patches aim to remove
CONFIG_HOLES_IN_ZONEand essentially hardwirepfn_valid_within()to 1.The idea is to mark NOMAP pages as reserved in the memory map and restore the intended semantics of
pfn_valid()to designate availability of struct page for a pfn.With this the core mm will be able to cope with the fact that it cannot use NOMAP pages and the holes created by NOMAP ranges within
MAX_ORDERblocks will be treated correctly even without the need forpfn_valid_within.
Quoted 5 lines
Quoted 2 lines
The patches are boot tested on qemu-system-aarch64.
Did you use EFI boot when testing this? The memory map is much more fragmented in that case, so this would be a good data point.
Right, something like this:
[ 0.000000] Early memory node ranges [ 0.000000] node 0: [mem 0x0000000040000000-0x00000000ffffbfff] [ 0.000000] node 0: [mem 0x00000000ffffc000-0x00000000ffffffff] [ 0.000000] node 0: [mem 0x0000000100000000-0x00000004386fffff] [ 0.000000] node 0: [mem 0x0000000438700000-0x000000043899ffff] [ 0.000000] node 0: [mem 0x00000004389a0000-0x00000004389bffff] [ 0.000000] node 0: [mem 0x00000004389c0000-0x0000000438b5ffff] [ 0.000000] node 0: [mem 0x0000000438b60000-0x000000043be3ffff] [ 0.000000] node 0: [mem 0x000000043be40000-0x000000043becffff] [ 0.000000] node 0: [mem 0x000000043bed0000-0x000000043bedffff] [ 0.000000] node 0: [mem 0x000000043bee0000-0x000000043bffffff] [ 0.000000] node 0: [mem 0x000000043c000000-0x000000043fffffff]
This is a pity really, because I don't see a fundamental reason for those tiny holes all over the place.
I know that EFI/ACPI mandates "IO style" memory access for those regions, but I fail to get why...
Quoted 47 lines
Quoted 47 lines
I beleive it would be best to route these via mmotm tree.
v4:
* rebase on v5.13-rc1v3: Link: https://lore.kernel.org/lkml/[email protected]
* Fix minor issues found by Anshuman
* Freshen up the declaration ofpfn_valid()to make it consistent withpfn_is_map_memory()
* Add more Acked-by and Reviewed-by tags, thanks Anshuman and Davidv2: Link: https://lore.kernel.org/lkml/[email protected]
* Add check for PFN overflow inpfn_is_map_memory()
* Add Acked-by and Reviewed-by tags, thanks David.v1: Link: https://lore.kernel.org/lkml/[email protected]
* Add comment about the semantics ofpfn_valid()as Anshuman suggested
* Extend comments aboutMEMBLOCK_NOMAP, per Anshuman
* Usepfn_is_map_memory()name for the exported wrapper formemblock_is_map_memory(). It is still local toarch/arm64in the end because of header dependency issues.rfc: Link: https://lore.kernel.org/lkml/[email protected]
Mike Rapoport (4):
include/linux/mmzone.h: add documentation forpfn_valid()
memblock: update initialization of reserved pages
arm64: decouple check whether pfn is in linear map frompfn_valid()
arm64: droppfn_valid_within()and simplifypfn_valid()arch/arm64/Kconfig | 3 --- arch/arm64/include/asm/memory.h | 2 +- arch/arm64/include/asm/page.h | 3 ++- arch/arm64/kvm/mmu.c | 2 +- arch/arm64/mm/init.c | 14 +++++++++++++- arch/arm64/mm/ioremap.c | 4 ++-- arch/arm64/mm/mmu.c | 2 +- include/linux/memblock.h | 4 +++- include/linux/mmzone.h | 11 +++++++++++ mm/memblock.c | 28 ++++++++++++++++++++++++++-- 10 files changed, 60 insertions(+), 13 deletions(-)base-commit:
6efb943b8616ec53a5e444193dccf1af9ad627b5--
2.28.0
Signature · 2 lines
Sincerely yours, Mike.
Quoted 19 lines · Mike Rapoport
Quoted 17 lines · Ard Biesheuvel
Quoted 16 lines · Mike Rapoport
From: Mike Rapoport <[email protected]>
Hi,
These patches aim to remove
CONFIG_HOLES_IN_ZONEand essentially hardwirepfn_valid_within()to 1.The idea is to mark NOMAP pages as reserved in the memory map and restore the intended semantics of
pfn_valid()to designate availability of struct page for a pfn.With this the core mm will be able to cope with the fact that it cannot use NOMAP pages and the holes created by NOMAP ranges within
MAX_ORDERblocks will be treated correctly even without the need forpfn_valid_within.
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
The patches are boot tested on qemu-system-aarch64.
Did you use EFI boot when testing this? The memory map is much more fragmented in that case, so this would be a good data point.
Right, something like this:
Yes, although it is not always that bad.
Quoted 3 lines
[ 0.000000] Early memory node ranges [ 0.000000] node 0: [mem 0x0000000040000000-0x00000000ffffbfff] [ 0.000000] node 0: [mem 0x00000000ffffc000-0x00000000ffffffff]
This is allocated below 4 GB by the firmware, for reasons that are only valid on x86 (where some of the early boot chain is IA32 only)
Quoted 10 lines
[ 0.000000] node 0: [mem 0x0000000100000000-0x00000004386fffff] [ 0.000000] node 0: [mem 0x0000000438700000-0x000000043899ffff] [ 0.000000] node 0: [mem 0x00000004389a0000-0x00000004389bffff] [ 0.000000] node 0: [mem 0x00000004389c0000-0x0000000438b5ffff] [ 0.000000] node 0: [mem 0x0000000438b60000-0x000000043be3ffff] [ 0.000000] node 0: [mem 0x000000043be40000-0x000000043becffff] [ 0.000000] node 0: [mem 0x000000043bed0000-0x000000043bedffff] [ 0.000000] node 0: [mem 0x000000043bee0000-0x000000043bffffff] [ 0.000000] node 0: [mem 0x000000043c000000-0x000000043fffffff]
Quoted 3 lines
This is a pity really, because I don't see a fundamental reason for those tiny holes all over the place.
There is a config option in the firmware build that allows these regions to be preallocated using larger windows, which greatly reduces the fragmentation.
Quoted 3 lines
I know that EFI/ACPI mandates "IO style" memory access for those regions, but I fail to get why...
Not sure what you mean by 'IO style memory access'.
Quoted 51 lines
Quoted 43 lines
Quoted 43 lines
I beleive it would be best to route these via mmotm tree.
v4:
* rebase on v5.13-rc1v3: Link: https://lore.kernel.org/lkml/[email protected]
* Fix minor issues found by Anshuman
* Freshen up the declaration ofpfn_valid()to make it consistent withpfn_is_map_memory()
* Add more Acked-by and Reviewed-by tags, thanks Anshuman and Davidv2: Link: https://lore.kernel.org/lkml/[email protected]
* Add check for PFN overflow inpfn_is_map_memory()
* Add Acked-by and Reviewed-by tags, thanks David.v1: Link: https://lore.kernel.org/lkml/[email protected]
* Add comment about the semantics ofpfn_valid()as Anshuman suggested
* Extend comments aboutMEMBLOCK_NOMAP, per Anshuman
* Usepfn_is_map_memory()name for the exported wrapper formemblock_is_map_memory(). It is still local toarch/arm64in the end because of header dependency issues.rfc: Link: https://lore.kernel.org/lkml/[email protected]
Mike Rapoport (4):
include/linux/mmzone.h: add documentation forpfn_valid()
memblock: update initialization of reserved pages
arm64: decouple check whether pfn is in linear map frompfn_valid()
arm64: droppfn_valid_within()and simplifypfn_valid()arch/arm64/Kconfig | 3 --- arch/arm64/include/asm/memory.h | 2 +- arch/arm64/include/asm/page.h | 3 ++- arch/arm64/kvm/mmu.c | 2 +- arch/arm64/mm/init.c | 14 +++++++++++++- arch/arm64/mm/ioremap.c | 4 ++-- arch/arm64/mm/mmu.c | 2 +- include/linux/memblock.h | 4 +++- include/linux/mmzone.h | 11 +++++++++++ mm/memblock.c | 28 ++++++++++++++++++++++++++-- 10 files changed, 60 insertions(+), 13 deletions(-)Quoted 4 lines
Quoted 4 lines
base-commit:
6efb943b8616ec53a5e444193dccf1af9ad627b5--
2.28.0--
Sincerely yours,
Mike.
Mike Rapoport
maintainerQuoted 52 lines · Ard Biesheuvel
Quoted 19 lines · Mike Rapoport
Quoted 17 lines · Ard Biesheuvel
Quoted 16 lines · Mike Rapoport
From: Mike Rapoport <[email protected]>
Hi,
These patches aim to remove
CONFIG_HOLES_IN_ZONEand essentially hardwirepfn_valid_within()to 1.The idea is to mark NOMAP pages as reserved in the memory map and restore the intended semantics of
pfn_valid()to designate availability of struct page for a pfn.With this the core mm will be able to cope with the fact that it cannot use NOMAP pages and the holes created by NOMAP ranges within
MAX_ORDERblocks will be treated correctly even without the need forpfn_valid_within.Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
The patches are boot tested on qemu-system-aarch64.
Did you use EFI boot when testing this? The memory map is much more fragmented in that case, so this would be a good data point.
Right, something like this:
Yes, although it is not always that bad.
Quoted 3 lines
[ 0.000000] Early memory node ranges [ 0.000000] node 0: [mem 0x0000000040000000-0x00000000ffffbfff] [ 0.000000] node 0: [mem 0x00000000ffffc000-0x00000000ffffffff]This is allocated below 4 GB by the firmware, for reasons that are only valid on x86 (where some of the early boot chain is IA32 only)
Quoted 10 lines
[ 0.000000] node 0: [mem 0x0000000100000000-0x00000004386fffff] [ 0.000000] node 0: [mem 0x0000000438700000-0x000000043899ffff] [ 0.000000] node 0: [mem 0x00000004389a0000-0x00000004389bffff] [ 0.000000] node 0: [mem 0x00000004389c0000-0x0000000438b5ffff] [ 0.000000] node 0: [mem 0x0000000438b60000-0x000000043be3ffff] [ 0.000000] node 0: [mem 0x000000043be40000-0x000000043becffff] [ 0.000000] node 0: [mem 0x000000043bed0000-0x000000043bedffff] [ 0.000000] node 0: [mem 0x000000043bee0000-0x000000043bffffff] [ 0.000000] node 0: [mem 0x000000043c000000-0x000000043fffffff]Quoted 3 lines
This is a pity really, because I don't see a fundamental reason for those tiny holes all over the place.
Quoted 8 lines
There is a config option in the firmware build that allows these regions to be preallocated using larger windows, which greatly reduces the fragmentation.
Quoted 3 lines
I know that EFI/ACPI mandates "IO style" memory access for those regions, but I fail to get why...
Not sure what you mean by 'IO style memory access'.
Well, my understanding is that the memory reserved by the firmware cannot be mapped in the linear map because it might require different caching modes (e.g like IO) and arm64 cannot tolerate aliased mappings with different caching.
But what evades me is *why* these areas cannot be accessed as normal RAM.
Quoted 51 lines
Quoted 51 lines
Quoted 43 lines
Quoted 43 lines
I beleive it would be best to route these via mmotm tree.
v4:
* rebase on v5.13-rc1v3: Link: https://lore.kernel.org/lkml/[email protected]
* Fix minor issues found by Anshuman
* Freshen up the declaration ofpfn_valid()to make it consistent withpfn_is_map_memory()
* Add more Acked-by and Reviewed-by tags, thanks Anshuman and Davidv2: Link: https://lore.kernel.org/lkml/[email protected]
* Add check for PFN overflow inpfn_is_map_memory()
* Add Acked-by and Reviewed-by tags, thanks David.v1: Link: https://lore.kernel.org/lkml/[email protected]
* Add comment about the semantics ofpfn_valid()as Anshuman suggested
* Extend comments aboutMEMBLOCK_NOMAP, per Anshuman
* Usepfn_is_map_memory()name for the exported wrapper formemblock_is_map_memory(). It is still local toarch/arm64in the end because of header dependency issues.rfc: Link: https://lore.kernel.org/lkml/[email protected]
Mike Rapoport (4):
include/linux/mmzone.h: add documentation forpfn_valid()
memblock: update initialization of reserved pages
arm64: decouple check whether pfn is in linear map frompfn_valid()
arm64: droppfn_valid_within()and simplifypfn_valid()arch/arm64/Kconfig | 3 --- arch/arm64/include/asm/memory.h | 2 +- arch/arm64/include/asm/page.h | 3 ++- arch/arm64/kvm/mmu.c | 2 +- arch/arm64/mm/init.c | 14 +++++++++++++- arch/arm64/mm/ioremap.c | 4 ++-- arch/arm64/mm/mmu.c | 2 +- include/linux/memblock.h | 4 +++- include/linux/mmzone.h | 11 +++++++++++ mm/memblock.c | 28 ++++++++++++++++++++++++++-- 10 files changed, 60 insertions(+), 13 deletions(-)Quoted 4 lines
Quoted 4 lines
base-commit:
6efb943b8616ec53a5e444193dccf1af9ad627b5--
2.28.0--
Sincerely yours,
Mike.
Signature · 2 lines
Sincerely yours, Mike.