Commit 5f5d6e40 authored by Aneesh Kumar K.V's avatar Aneesh Kumar K.V Committed by Michael Ellerman

powerpc/nvdimm: Update vmemmap_populated to check sub-section range

With commit: 7cc7867f ("mm/devm_memremap_pages: enable sub-section remap")
pmem namespaces are remapped in 2M chunks. On architectures like ppc64 we
can map the memmap area using 16MB hugepage size and that can cover
a memory range of 16G.

While enabling new pmem namespaces, since memory is added in sub-section chunks,
before creating a new memmap mapping, kernel should check whether there is an
existing memmap mapping covering the new pmem namespace. Currently, this is
validated by checking whether the section covering the range is already
initialized or not. Considering there can be multiple namespaces in the same
section this can result in wrong validation. Update this to check for
sub-sections in the range. This is done by checking for all pfns in the range we
are mapping.

We could optimize this by checking only just one pfn in each sub-section. But
since this is not fast-path we keep this simple.
Signed-off-by: default avatarAneesh Kumar K.V <aneesh.kumar@linux.ibm.com>
Signed-off-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20190917123851.22553-1-aneesh.kumar@linux.ibm.com
parent 69393cb0
...@@ -70,31 +70,46 @@ EXPORT_SYMBOL_GPL(kernstart_addr); ...@@ -70,31 +70,46 @@ EXPORT_SYMBOL_GPL(kernstart_addr);
#ifdef CONFIG_SPARSEMEM_VMEMMAP #ifdef CONFIG_SPARSEMEM_VMEMMAP
/* /*
* Given an address within the vmemmap, determine the pfn of the page that * Given an address within the vmemmap, determine the page that
* represents the start of the section it is within. Note that we have to * represents the start of the subsection it is within. Note that we have to
* do this by hand as the proffered address may not be correctly aligned. * do this by hand as the proffered address may not be correctly aligned.
* Subtraction of non-aligned pointers produces undefined results. * Subtraction of non-aligned pointers produces undefined results.
*/ */
static unsigned long __meminit vmemmap_section_start(unsigned long page) static struct page * __meminit vmemmap_subsection_start(unsigned long vmemmap_addr)
{ {
unsigned long offset = page - ((unsigned long)(vmemmap)); unsigned long start_pfn;
unsigned long offset = vmemmap_addr - ((unsigned long)(vmemmap));
/* Return the pfn of the start of the section. */ /* Return the pfn of the start of the section. */
return (offset / sizeof(struct page)) & PAGE_SECTION_MASK; start_pfn = (offset / sizeof(struct page)) & PAGE_SUBSECTION_MASK;
return pfn_to_page(start_pfn);
} }
/* /*
* Check if this vmemmap page is already initialised. If any section * Since memory is added in sub-section chunks, before creating a new vmemmap
* which overlaps this vmemmap page is initialised then this page is * mapping, the kernel should check whether there is an existing memmap mapping
* initialised already. * covering the new subsection added. This is needed because kernel can map
* vmemmap area using 16MB pages which will cover a memory range of 16G. Such
* a range covers multiple subsections (2M)
*
* If any subsection in the 16G range mapped by vmemmap is valid we consider the
* vmemmap populated (There is a page table entry already present). We can't do
* a page table lookup here because with the hash translation we don't keep
* vmemmap details in linux page table.
*/ */
static int __meminit vmemmap_populated(unsigned long start, int page_size) static int __meminit vmemmap_populated(unsigned long vmemmap_addr, int vmemmap_map_size)
{ {
unsigned long end = start + page_size; struct page *start;
start = (unsigned long)(pfn_to_page(vmemmap_section_start(start))); unsigned long vmemmap_end = vmemmap_addr + vmemmap_map_size;
start = vmemmap_subsection_start(vmemmap_addr);
for (; start < end; start += (PAGES_PER_SECTION * sizeof(struct page))) for (; (unsigned long)start < vmemmap_end; start += PAGES_PER_SUBSECTION)
if (pfn_valid(page_to_pfn((struct page *)start))) /*
* pfn valid check here is intended to really check
* whether we have any subsection already initialized
* in this range.
*/
if (pfn_valid(page_to_pfn(start)))
return 1; return 1;
return 0; return 0;
...@@ -201,6 +216,12 @@ int __meminit vmemmap_populate(unsigned long start, unsigned long end, int node, ...@@ -201,6 +216,12 @@ int __meminit vmemmap_populate(unsigned long start, unsigned long end, int node,
void *p = NULL; void *p = NULL;
int rc; int rc;
/*
* This vmemmap range is backing different subsections. If any
* of that subsection is marked valid, that means we already
* have initialized a page table covering this range and hence
* the vmemmap range is populated.
*/
if (vmemmap_populated(start, page_size)) if (vmemmap_populated(start, page_size))
continue; continue;
...@@ -290,9 +311,10 @@ void __ref vmemmap_free(unsigned long start, unsigned long end, ...@@ -290,9 +311,10 @@ void __ref vmemmap_free(unsigned long start, unsigned long end,
struct page *page; struct page *page;
/* /*
* the section has already be marked as invalid, so * We have already marked the subsection we are trying to remove
* vmemmap_populated() true means some other sections still * invalid. So if we want to remove the vmemmap range, we
* in this page, so skip it. * need to make sure there is no subsection marked valid
* in this range.
*/ */
if (vmemmap_populated(start, page_size)) if (vmemmap_populated(start, page_size))
continue; continue;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment