2007-10-16 01:24:13 -07:00
/*
* Virtual Memory Map support
*
2008-07-04 09:59:22 -07:00
* ( C ) 2007 sgi . Christoph Lameter .
2007-10-16 01:24:13 -07:00
*
* Virtual memory maps allow VM primitives pfn_to_page , page_to_pfn ,
* virt_to_page , page_address ( ) to be implemented as a base offset
* calculation without memory access .
*
* However , virtual mappings need a page table and TLBs . Many Linux
* architectures already map their physical space using 1 - 1 mappings
* via TLBs . For those arches the virtual memmory map is essentially
* for free if we use the same page size as the 1 - 1 mappings . In that
* case the overhead consists of a few additional pages that are
* allocated to create a view of memory for vmemmap .
*
2007-10-16 01:24:14 -07:00
* The architecture is expected to provide a vmemmap_populate ( ) function
* to instantiate the mapping .
2007-10-16 01:24:13 -07:00
*/
# include <linux/mm.h>
# include <linux/mmzone.h>
# include <linux/bootmem.h>
# include <linux/highmem.h>
# include <linux/module.h>
# include <linux/spinlock.h>
# include <linux/vmalloc.h>
2007-10-29 14:37:19 -07:00
# include <linux/sched.h>
2007-10-16 01:24:13 -07:00
# include <asm/dma.h>
# include <asm/pgalloc.h>
# include <asm/pgtable.h>
/*
* Allocate a block of memory to be used to back the virtual memory map
* or to back the page tables that are used to create the mapping .
* Uses the main allocators if they are available , else bootmem .
*/
2007-11-28 16:21:57 -08:00
static void * __init_refok __earlyonly_bootmem_alloc ( int node ,
unsigned long size ,
unsigned long align ,
unsigned long goal )
{
2010-02-10 01:20:20 -08:00
return __alloc_bootmem_node_high ( NODE_DATA ( node ) , size , align , goal ) ;
2007-11-28 16:21:57 -08:00
}
2010-02-10 01:20:22 -08:00
static void * vmemmap_buf ;
static void * vmemmap_buf_end ;
2007-11-28 16:21:57 -08:00
2007-10-16 01:24:13 -07:00
void * __meminit vmemmap_alloc_block ( unsigned long size , int node )
{
/* If the main allocator is up use that, fallback to bootmem. */
if ( slab_is_available ( ) ) {
2009-09-21 17:01:19 -07:00
struct page * page ;
if ( node_state ( node , N_HIGH_MEMORY ) )
page = alloc_pages_node ( node ,
2007-10-16 01:24:13 -07:00
GFP_KERNEL | __GFP_ZERO , get_order ( size ) ) ;
2009-09-21 17:01:19 -07:00
else
page = alloc_pages ( GFP_KERNEL | __GFP_ZERO ,
get_order ( size ) ) ;
2007-10-16 01:24:13 -07:00
if ( page )
return page_address ( page ) ;
return NULL ;
} else
2007-11-28 16:21:57 -08:00
return __earlyonly_bootmem_alloc ( node , size , size ,
2007-10-16 01:24:13 -07:00
__pa ( MAX_DMA_ADDRESS ) ) ;
}
2010-02-10 01:20:22 -08:00
/* need to make sure size is all the same during early stage */
void * __meminit vmemmap_alloc_block_buf ( unsigned long size , int node )
{
void * ptr ;
if ( ! vmemmap_buf )
return vmemmap_alloc_block ( size , node ) ;
/* take the from buf */
ptr = ( void * ) ALIGN ( ( unsigned long ) vmemmap_buf , size ) ;
if ( ptr + size > vmemmap_buf_end )
return vmemmap_alloc_block ( size , node ) ;
vmemmap_buf = ptr + size ;
return ptr ;
}
2007-10-16 01:24:13 -07:00
void __meminit vmemmap_verify ( pte_t * pte , int node ,
unsigned long start , unsigned long end )
{
unsigned long pfn = pte_pfn ( * pte ) ;
int actual_node = early_pfn_to_nid ( pfn ) ;
2008-11-06 12:53:31 -08:00
if ( node_distance ( actual_node , node ) > LOCAL_DISTANCE )
2007-10-16 01:24:13 -07:00
printk ( KERN_WARNING " [%lx-%lx] potential offnode "
" page_structs \n " , start , end - 1 ) ;
}
2007-10-16 01:24:14 -07:00
pte_t * __meminit vmemmap_pte_populate ( pmd_t * pmd , unsigned long addr , int node )
2007-10-16 01:24:13 -07:00
{
2007-10-16 01:24:14 -07:00
pte_t * pte = pte_offset_kernel ( pmd , addr ) ;
if ( pte_none ( * pte ) ) {
pte_t entry ;
2010-02-10 01:20:22 -08:00
void * p = vmemmap_alloc_block_buf ( PAGE_SIZE , node ) ;
2007-10-16 01:24:14 -07:00
if ( ! p )
2008-03-29 03:07:28 +00:00
return NULL ;
2007-10-16 01:24:14 -07:00
entry = pfn_pte ( __pa ( p ) > > PAGE_SHIFT , PAGE_KERNEL ) ;
set_pte_at ( & init_mm , addr , pte , entry ) ;
}
return pte ;
2007-10-16 01:24:13 -07:00
}
2007-10-16 01:24:14 -07:00
pmd_t * __meminit vmemmap_pmd_populate ( pud_t * pud , unsigned long addr , int node )
2007-10-16 01:24:13 -07:00
{
2007-10-16 01:24:14 -07:00
pmd_t * pmd = pmd_offset ( pud , addr ) ;
if ( pmd_none ( * pmd ) ) {
void * p = vmemmap_alloc_block ( PAGE_SIZE , node ) ;
if ( ! p )
2008-03-29 03:07:28 +00:00
return NULL ;
2007-10-16 01:24:14 -07:00
pmd_populate_kernel ( & init_mm , pmd , p ) ;
2007-10-16 01:24:13 -07:00
}
2007-10-16 01:24:14 -07:00
return pmd ;
2007-10-16 01:24:13 -07:00
}
2007-10-16 01:24:14 -07:00
pud_t * __meminit vmemmap_pud_populate ( pgd_t * pgd , unsigned long addr , int node )
2007-10-16 01:24:13 -07:00
{
2007-10-16 01:24:14 -07:00
pud_t * pud = pud_offset ( pgd , addr ) ;
if ( pud_none ( * pud ) ) {
void * p = vmemmap_alloc_block ( PAGE_SIZE , node ) ;
if ( ! p )
2008-03-29 03:07:28 +00:00
return NULL ;
2007-10-16 01:24:14 -07:00
pud_populate ( & init_mm , pud , p ) ;
}
return pud ;
}
2007-10-16 01:24:13 -07:00
2007-10-16 01:24:14 -07:00
pgd_t * __meminit vmemmap_pgd_populate ( unsigned long addr , int node )
{
pgd_t * pgd = pgd_offset_k ( addr ) ;
if ( pgd_none ( * pgd ) ) {
void * p = vmemmap_alloc_block ( PAGE_SIZE , node ) ;
if ( ! p )
2008-03-29 03:07:28 +00:00
return NULL ;
2007-10-16 01:24:14 -07:00
pgd_populate ( & init_mm , pgd , p ) ;
2007-10-16 01:24:13 -07:00
}
2007-10-16 01:24:14 -07:00
return pgd ;
2007-10-16 01:24:13 -07:00
}
2007-10-16 01:24:14 -07:00
int __meminit vmemmap_populate_basepages ( struct page * start_page ,
unsigned long size , int node )
2007-10-16 01:24:13 -07:00
{
unsigned long addr = ( unsigned long ) start_page ;
2007-10-16 01:24:14 -07:00
unsigned long end = ( unsigned long ) ( start_page + size ) ;
pgd_t * pgd ;
pud_t * pud ;
pmd_t * pmd ;
pte_t * pte ;
2007-10-16 01:24:13 -07:00
2007-10-16 01:24:14 -07:00
for ( ; addr < end ; addr + = PAGE_SIZE ) {
pgd = vmemmap_pgd_populate ( addr , node ) ;
if ( ! pgd )
return - ENOMEM ;
pud = vmemmap_pud_populate ( pgd , addr , node ) ;
if ( ! pud )
return - ENOMEM ;
pmd = vmemmap_pmd_populate ( pud , addr , node ) ;
if ( ! pmd )
return - ENOMEM ;
pte = vmemmap_pte_populate ( pmd , addr , node ) ;
if ( ! pte )
return - ENOMEM ;
vmemmap_verify ( pte , node , addr , addr + PAGE_SIZE ) ;
2007-10-16 01:24:13 -07:00
}
2007-10-16 01:24:14 -07:00
return 0 ;
2007-10-16 01:24:13 -07:00
}
2007-10-16 01:26:14 -07:00
struct page * __meminit sparse_mem_map_populate ( unsigned long pnum , int nid )
2007-10-16 01:24:13 -07:00
{
struct page * map = pfn_to_page ( pnum * PAGES_PER_SECTION ) ;
int error = vmemmap_populate ( map , PAGES_PER_SECTION , nid ) ;
if ( error )
return NULL ;
return map ;
}
2010-02-10 01:20:22 -08:00
void __init sparse_mem_maps_populate_node ( struct page * * map_map ,
unsigned long pnum_begin ,
unsigned long pnum_end ,
unsigned long map_count , int nodeid )
{
unsigned long pnum ;
unsigned long size = sizeof ( struct page ) * PAGES_PER_SECTION ;
void * vmemmap_buf_start ;
size = ALIGN ( size , PMD_SIZE ) ;
vmemmap_buf_start = __earlyonly_bootmem_alloc ( nodeid , size * map_count ,
PMD_SIZE , __pa ( MAX_DMA_ADDRESS ) ) ;
if ( vmemmap_buf_start ) {
vmemmap_buf = vmemmap_buf_start ;
vmemmap_buf_end = vmemmap_buf_start + size * map_count ;
}
for ( pnum = pnum_begin ; pnum < pnum_end ; pnum + + ) {
struct mem_section * ms ;
if ( ! present_section_nr ( pnum ) )
continue ;
map_map [ pnum ] = sparse_mem_map_populate ( pnum , nodeid ) ;
if ( map_map [ pnum ] )
continue ;
ms = __nr_to_section ( pnum ) ;
printk ( KERN_ERR " %s: sparsemem memory map backing failed "
" some memory will not be available. \n " , __func__ ) ;
ms - > section_mem_map = 0 ;
}
if ( vmemmap_buf_start ) {
/* need to free left buf */
# ifdef CONFIG_NO_BOOTMEM
free_early ( __pa ( vmemmap_buf_start ) , __pa ( vmemmap_buf_end ) ) ;
if ( vmemmap_buf_start < vmemmap_buf ) {
char name [ 15 ] ;
snprintf ( name , sizeof ( name ) , " MEMMAP %d " , nodeid ) ;
reserve_early_without_check ( __pa ( vmemmap_buf_start ) ,
__pa ( vmemmap_buf ) , name ) ;
}
# else
free_bootmem ( __pa ( vmemmap_buf ) , vmemmap_buf_end - vmemmap_buf ) ;
# endif
vmemmap_buf = NULL ;
vmemmap_buf_end = NULL ;
}
}