2005-10-30 04:16:54 +03:00
/*
* linux / mm / memory_hotplug . c
*
* Copyright ( C )
*/
# include <linux/config.h>
# include <linux/stddef.h>
# include <linux/mm.h>
# include <linux/swap.h>
# include <linux/interrupt.h>
# include <linux/pagemap.h>
# include <linux/bootmem.h>
# include <linux/compiler.h>
# include <linux/module.h>
# include <linux/pagevec.h>
# include <linux/slab.h>
# include <linux/sysctl.h>
# include <linux/cpu.h>
# include <linux/memory.h>
# include <linux/memory_hotplug.h>
# include <linux/highmem.h>
# include <linux/vmalloc.h>
# include <asm/tlbflush.h>
extern void zonetable_add ( struct zone * zone , int nid , int zid , unsigned long pfn ,
unsigned long size ) ;
2006-06-23 13:03:10 +04:00
static int __add_zone ( struct zone * zone , unsigned long phys_start_pfn )
2005-10-30 04:16:54 +03:00
{
struct pglist_data * pgdat = zone - > zone_pgdat ;
int nr_pages = PAGES_PER_SECTION ;
int nid = pgdat - > node_id ;
int zone_type ;
zone_type = zone - pgdat - > node_zones ;
2006-06-23 13:03:10 +04:00
if ( ! populated_zone ( zone ) ) {
int ret = 0 ;
ret = init_currently_empty_zone ( zone , phys_start_pfn , nr_pages ) ;
if ( ret < 0 )
return ret ;
}
2005-10-30 04:16:54 +03:00
memmap_init_zone ( nr_pages , nid , zone_type , phys_start_pfn ) ;
zonetable_add ( zone , nid , zone_type , phys_start_pfn , nr_pages ) ;
2006-06-23 13:03:10 +04:00
return 0 ;
2005-10-30 04:16:54 +03:00
}
2005-10-30 04:16:55 +03:00
extern int sparse_add_one_section ( struct zone * zone , unsigned long start_pfn ,
int nr_pages ) ;
2005-10-30 04:16:54 +03:00
static int __add_section ( struct zone * zone , unsigned long phys_start_pfn )
{
int nr_pages = PAGES_PER_SECTION ;
int ret ;
2005-10-30 04:16:55 +03:00
ret = sparse_add_one_section ( zone , phys_start_pfn , nr_pages ) ;
2005-10-30 04:16:54 +03:00
if ( ret < 0 )
return ret ;
2006-06-23 13:03:10 +04:00
ret = __add_zone ( zone , phys_start_pfn ) ;
if ( ret < 0 )
return ret ;
2005-10-30 04:16:54 +03:00
return register_new_memory ( __pfn_to_section ( phys_start_pfn ) ) ;
}
/*
* Reasonably generic function for adding memory . It is
* expected that archs that support memory hotplug will
* call this function after deciding the zone to which to
* add the new pages .
*/
int __add_pages ( struct zone * zone , unsigned long phys_start_pfn ,
unsigned long nr_pages )
{
unsigned long i ;
int err = 0 ;
for ( i = 0 ; i < nr_pages ; i + = PAGES_PER_SECTION ) {
err = __add_section ( zone , phys_start_pfn + i ) ;
2006-05-01 23:16:11 +04:00
/* We want to keep adding the rest of the
* sections if the first ones already exist
*/
if ( err & & ( err ! = - EEXIST ) )
2005-10-30 04:16:54 +03:00
break ;
}
return err ;
}
2006-05-01 23:16:11 +04:00
EXPORT_SYMBOL_GPL ( __add_pages ) ;
2005-10-30 04:16:54 +03:00
static void grow_zone_span ( struct zone * zone ,
unsigned long start_pfn , unsigned long end_pfn )
{
unsigned long old_zone_end_pfn ;
zone_span_writelock ( zone ) ;
old_zone_end_pfn = zone - > zone_start_pfn + zone - > spanned_pages ;
if ( start_pfn < zone - > zone_start_pfn )
zone - > zone_start_pfn = start_pfn ;
2006-05-31 08:25:42 +04:00
zone - > spanned_pages = max ( old_zone_end_pfn , end_pfn ) -
zone - > zone_start_pfn ;
2005-10-30 04:16:54 +03:00
zone_span_writeunlock ( zone ) ;
}
static void grow_pgdat_span ( struct pglist_data * pgdat ,
unsigned long start_pfn , unsigned long end_pfn )
{
unsigned long old_pgdat_end_pfn =
pgdat - > node_start_pfn + pgdat - > node_spanned_pages ;
if ( start_pfn < pgdat - > node_start_pfn )
pgdat - > node_start_pfn = start_pfn ;
2006-05-31 08:25:42 +04:00
pgdat - > node_spanned_pages = max ( old_pgdat_end_pfn , end_pfn ) -
pgdat - > node_start_pfn ;
2005-10-30 04:16:54 +03:00
}
int online_pages ( unsigned long pfn , unsigned long nr_pages )
{
unsigned long i ;
unsigned long flags ;
unsigned long onlined_pages = 0 ;
struct zone * zone ;
2006-06-23 13:03:11 +04:00
int need_zonelists_rebuild = 0 ;
2005-10-30 04:16:54 +03:00
/*
* This doesn ' t need a lock to do pfn_to_page ( ) .
* The section can ' t be removed here because of the
* memory_block - > state_sem .
*/
zone = page_zone ( pfn_to_page ( pfn ) ) ;
pgdat_resize_lock ( zone - > zone_pgdat , & flags ) ;
grow_zone_span ( zone , pfn , pfn + nr_pages ) ;
grow_pgdat_span ( zone - > zone_pgdat , pfn , pfn + nr_pages ) ;
pgdat_resize_unlock ( zone - > zone_pgdat , & flags ) ;
2006-06-23 13:03:11 +04:00
/*
* If this zone is not populated , then it is not in zonelist .
* This means the page allocator ignores this zone .
* So , zonelist must be updated after online .
*/
if ( ! populated_zone ( zone ) )
need_zonelists_rebuild = 1 ;
2005-10-30 04:16:54 +03:00
for ( i = 0 ; i < nr_pages ; i + + ) {
struct page * page = pfn_to_page ( pfn + i ) ;
online_page ( page ) ;
onlined_pages + + ;
}
zone - > present_pages + = onlined_pages ;
2006-03-10 04:33:51 +03:00
zone - > zone_pgdat - > node_present_pages + = onlined_pages ;
2005-10-30 04:16:54 +03:00
2005-10-30 04:16:56 +03:00
setup_per_zone_pages_min ( ) ;
2006-06-23 13:03:11 +04:00
if ( need_zonelists_rebuild )
build_all_zonelists ( ) ;
2006-06-23 13:03:47 +04:00
vm_total_pages = nr_free_pagecache_pages ( ) ;
2005-10-30 04:16:54 +03:00
return 0 ;
}