x86, mm: Create slow_virt_to_phys()
authorDave Hansen <dave@linux.vnet.ibm.com>
Tue, 22 Jan 2013 21:24:33 +0000 (13:24 -0800)
committerH. Peter Anvin <hpa@linux.intel.com>
Sat, 26 Jan 2013 00:33:23 +0000 (16:33 -0800)
This is necessary because __pa() does not work on some kinds of
memory, like vmalloc() or the alloc_remap() areas on 32-bit
NUMA systems.  We have some functions to do conversions _like_
this in the vmalloc() code (like vmalloc_to_page()), but they
do not work on sizes other than 4k pages.  We would potentially
need to be able to handle all the page sizes that we use for
the kernel linear mapping (4k, 2M, 1G).

In practice, on 32-bit NUMA systems, the percpu areas get stuck
in the alloc_remap() area.  Any __pa() call on them will break
and basically return garbage.

This patch introduces a new function slow_virt_to_phys(), which
walks the kernel page tables on x86 and should do precisely
the same logical thing as __pa(), but actually work on a wider
range of memory.  It should work on the normal linear mapping,
vmalloc(), kmap(), etc...

Signed-off-by: Dave Hansen <dave@linux.vnet.ibm.com>
Link: http://lkml.kernel.org/r/20130122212433.4D1FCA62@kernel.stglabs.ibm.com
Acked-by: Rik van Riel <riel@redhat.com>
Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
arch/x86/include/asm/pgtable_types.h
arch/x86/mm/pageattr.c

index 6c297e7998cc07398431ada2460410a7f24ff83f..9f82690f81ed71a8fbc5b7c25c1cc4107206dfa8 100644 (file)
@@ -352,6 +352,7 @@ static inline void update_page_count(int level, unsigned long pages) { }
  * as a pte too.
  */
 extern pte_t *lookup_address(unsigned long address, unsigned int *level);
+extern phys_addr_t slow_virt_to_phys(void *__address);
 
 #endif /* !__ASSEMBLY__ */
 
index 2a5c9ab710b9fa9a2a4fbf10d466c542c1f1d640..6d13d2a3f825306a11195a258fb7f78ed073c887 100644 (file)
@@ -363,6 +363,37 @@ pte_t *lookup_address(unsigned long address, unsigned int *level)
 }
 EXPORT_SYMBOL_GPL(lookup_address);
 
+/*
+ * This is necessary because __pa() does not work on some
+ * kinds of memory, like vmalloc() or the alloc_remap()
+ * areas on 32-bit NUMA systems.  The percpu areas can
+ * end up in this kind of memory, for instance.
+ *
+ * This could be optimized, but it is only intended to be
+ * used at inititalization time, and keeping it
+ * unoptimized should increase the testing coverage for
+ * the more obscure platforms.
+ */
+phys_addr_t slow_virt_to_phys(void *__virt_addr)
+{
+       unsigned long virt_addr = (unsigned long)__virt_addr;
+       phys_addr_t phys_addr;
+       unsigned long offset;
+       enum pg_level level;
+       unsigned long psize;
+       unsigned long pmask;
+       pte_t *pte;
+
+       pte = lookup_address(virt_addr, &level);
+       BUG_ON(!pte);
+       psize = page_level_size(level);
+       pmask = page_level_mask(level);
+       offset = virt_addr & ~pmask;
+       phys_addr = pte_pfn(*pte) << PAGE_SHIFT;
+       return (phys_addr | offset);
+}
+EXPORT_SYMBOL_GPL(slow_virt_to_phys);
+
 /*
  * Set the new pmd in all the pgds we know about:
  */