2008-04-18 13:33:50 -07:00
/*
* pseries Memory Hotplug infrastructure .
*
* Copyright ( C ) 2008 Badari Pulavarty , IBM Corporation
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*/
# include <linux/of.h>
2010-07-12 14:36:09 +10:00
# include <linux/memblock.h>
2010-04-06 15:03:40 +00:00
# include <linux/vmalloc.h>
2008-04-18 13:33:50 -07:00
# include <asm/firmware.h>
# include <asm/machdep.h>
# include <asm/pSeries_reconfig.h>
2009-02-08 14:49:39 +00:00
# include <asm/sparsemem.h>
2008-04-18 13:33:50 -07:00
2010-07-12 14:36:09 +10:00
static int pseries_remove_memblock ( unsigned long base , unsigned int memblock_size )
2008-04-18 13:33:50 -07:00
{
2008-07-03 13:22:39 +10:00
unsigned long start , start_pfn ;
2008-04-18 13:33:50 -07:00
struct zone * zone ;
2008-07-03 13:22:39 +10:00
int ret ;
2008-07-03 13:20:58 +10:00
2008-10-01 09:44:02 +00:00
start_pfn = base > > PAGE_SHIFT ;
2008-10-13 08:42:00 +00:00
if ( ! pfn_valid ( start_pfn ) ) {
2010-07-12 14:36:09 +10:00
memblock_remove ( base , memblock_size ) ;
2008-10-13 08:42:00 +00:00
return 0 ;
}
2008-04-18 13:33:50 -07:00
zone = page_zone ( pfn_to_page ( start_pfn ) ) ;
/*
* Remove section mappings and sysfs entries for the
* section of the memory we are removing .
*
* NOTE : Ideally , this should be done in generic code like
* remove_memory ( ) . But remove_memory ( ) gets called by writing
* to sysfs " state " file and we can ' t remove sysfs entries
* while writing to it . So we have to defer it to here .
*/
2010-07-12 14:36:09 +10:00
ret = __remove_pages ( zone , start_pfn , memblock_size > > PAGE_SHIFT ) ;
2008-04-18 13:33:50 -07:00
if ( ret )
return ret ;
2008-04-18 13:33:52 -07:00
/*
* Update memory regions for memory remove
*/
2010-07-12 14:36:09 +10:00
memblock_remove ( base , memblock_size ) ;
2008-04-18 13:33:52 -07:00
2008-04-18 13:33:50 -07:00
/*
* Remove htab bolted mappings for this section of memory
*/
2008-07-03 13:20:58 +10:00
start = ( unsigned long ) __va ( base ) ;
2010-07-12 14:36:09 +10:00
ret = remove_section_mapping ( start , start + memblock_size ) ;
2010-04-06 15:03:40 +00:00
/* Ensure all vmalloc mappings are flushed in case they also
* hit that section of memory
*/
vm_unmap_aliases ( ) ;
2008-04-18 13:33:50 -07:00
return ret ;
}
2008-07-03 13:22:39 +10:00
static int pseries_remove_memory ( struct device_node * np )
{
const char * type ;
const unsigned int * regs ;
unsigned long base ;
2010-07-12 14:36:09 +10:00
unsigned int memblock_size ;
2008-07-03 13:22:39 +10:00
int ret = - EINVAL ;
/*
* Check to see if we are actually removing memory
*/
type = of_get_property ( np , " device_type " , NULL ) ;
if ( type = = NULL | | strcmp ( type , " memory " ) ! = 0 )
return 0 ;
/*
2010-07-12 14:36:09 +10:00
* Find the bae address and size of the memblock
2008-07-03 13:22:39 +10:00
*/
regs = of_get_property ( np , " reg " , NULL ) ;
if ( ! regs )
return ret ;
base = * ( unsigned long * ) regs ;
2010-07-12 14:36:09 +10:00
memblock_size = regs [ 3 ] ;
2008-07-03 13:22:39 +10:00
2010-07-12 14:36:09 +10:00
ret = pseries_remove_memblock ( base , memblock_size ) ;
2008-07-03 13:22:39 +10:00
return ret ;
}
2008-04-18 13:33:52 -07:00
static int pseries_add_memory ( struct device_node * np )
{
const char * type ;
const unsigned int * regs ;
2008-07-03 13:20:58 +10:00
unsigned long base ;
2010-07-12 14:36:09 +10:00
unsigned int memblock_size ;
2008-04-18 13:33:52 -07:00
int ret = - EINVAL ;
/*
* Check to see if we are actually adding memory
*/
type = of_get_property ( np , " device_type " , NULL ) ;
if ( type = = NULL | | strcmp ( type , " memory " ) ! = 0 )
return 0 ;
/*
2010-07-12 14:36:09 +10:00
* Find the base and size of the memblock
2008-04-18 13:33:52 -07:00
*/
regs = of_get_property ( np , " reg " , NULL ) ;
if ( ! regs )
return ret ;
2008-07-03 13:20:58 +10:00
base = * ( unsigned long * ) regs ;
2010-07-12 14:36:09 +10:00
memblock_size = regs [ 3 ] ;
2008-04-18 13:33:52 -07:00
/*
* Update memory region to represent the memory add
*/
2010-07-12 14:36:09 +10:00
ret = memblock_add ( base , memblock_size ) ;
2008-07-03 13:22:39 +10:00
return ( ret < 0 ) ? - EINVAL : 0 ;
}
static int pseries_drconf_memory ( unsigned long * base , unsigned int action )
{
struct device_node * np ;
2010-07-12 14:36:09 +10:00
const unsigned long * memblock_size ;
2008-07-03 13:22:39 +10:00
int rc ;
np = of_find_node_by_path ( " /ibm,dynamic-reconfiguration-memory " ) ;
if ( ! np )
return - EINVAL ;
2010-07-12 14:36:09 +10:00
memblock_size = of_get_property ( np , " ibm,memblock-size " , NULL ) ;
if ( ! memblock_size ) {
2008-07-03 13:22:39 +10:00
of_node_put ( np ) ;
return - EINVAL ;
}
if ( action = = PSERIES_DRCONF_MEM_ADD ) {
2010-07-12 14:36:09 +10:00
rc = memblock_add ( * base , * memblock_size ) ;
2008-07-03 13:22:39 +10:00
rc = ( rc < 0 ) ? - EINVAL : 0 ;
} else if ( action = = PSERIES_DRCONF_MEM_REMOVE ) {
2010-07-12 14:36:09 +10:00
rc = pseries_remove_memblock ( * base , * memblock_size ) ;
2008-07-03 13:22:39 +10:00
} else {
rc = - EINVAL ;
}
of_node_put ( np ) ;
return rc ;
2008-04-18 13:33:52 -07:00
}
2008-04-18 13:33:50 -07:00
static int pseries_memory_notifier ( struct notifier_block * nb ,
unsigned long action , void * node )
{
int err = NOTIFY_OK ;
switch ( action ) {
case PSERIES_RECONFIG_ADD :
2008-04-18 13:33:52 -07:00
if ( pseries_add_memory ( node ) )
err = NOTIFY_BAD ;
2008-04-18 13:33:50 -07:00
break ;
case PSERIES_RECONFIG_REMOVE :
if ( pseries_remove_memory ( node ) )
err = NOTIFY_BAD ;
break ;
2008-07-03 13:22:39 +10:00
case PSERIES_DRCONF_MEM_ADD :
case PSERIES_DRCONF_MEM_REMOVE :
if ( pseries_drconf_memory ( node , action ) )
err = NOTIFY_BAD ;
break ;
2008-04-18 13:33:50 -07:00
default :
err = NOTIFY_DONE ;
break ;
}
return err ;
}
static struct notifier_block pseries_mem_nb = {
. notifier_call = pseries_memory_notifier ,
} ;
static int __init pseries_memory_hotplug_init ( void )
{
if ( firmware_has_feature ( FW_FEATURE_LPAR ) )
pSeries_reconfig_notifier_register ( & pseries_mem_nb ) ;
return 0 ;
}
machine_device_initcall ( pseries , pseries_memory_hotplug_init ) ;