2006-01-16 22:14:10 -08:00
/*
* machine_kexec . c - handle transition of Linux booting another kernel
* Copyright ( C ) 2002 - 2003 Eric Biederman < ebiederm @ xmission . com >
*
* GameCube / ppc32 port Copyright ( C ) 2004 Albert Herranz
* LANDISK / sh4 supported by kogiidena
*
* This source code is licensed under the GNU General Public License ,
* Version 2. See the file COPYING for more details .
*/
# include <linux/mm.h>
# include <linux/kexec.h>
# include <linux/delay.h>
# include <linux/reboot.h>
2008-08-04 13:42:49 +09:00
# include <linux/numa.h>
2009-03-18 19:07:16 +09:00
# include <linux/ftrace.h>
2009-03-18 08:51:29 +00:00
# include <linux/suspend.h>
2010-07-12 14:36:09 +10:00
# include <linux/memblock.h>
2006-01-16 22:14:10 -08:00
# include <asm/pgtable.h>
# include <asm/pgalloc.h>
# include <asm/mmu_context.h>
# include <asm/io.h>
# include <asm/cacheflush.h>
2010-01-12 14:50:43 +09:00
# include <asm/sh_bios.h>
2010-01-20 16:42:52 +09:00
# include <asm/reboot.h>
2006-01-16 22:14:10 -08:00
2009-03-18 08:51:29 +00:00
typedef void ( * relocate_new_kernel_t ) ( unsigned long indirection_page ,
unsigned long reboot_code_buffer ,
unsigned long start_address ) ;
2006-01-16 22:14:10 -08:00
2006-06-26 18:57:34 +02:00
extern const unsigned char relocate_new_kernel [ ] ;
extern const unsigned int relocate_new_kernel_size ;
2009-03-18 08:51:29 +00:00
extern void * vbr_base ;
2006-01-16 22:14:10 -08:00
2010-01-20 16:42:52 +09:00
void native_machine_crash_shutdown ( struct pt_regs * regs )
2006-01-16 22:14:10 -08:00
{
2010-01-20 16:42:52 +09:00
/* Nothing to do for UP, but definitely broken for SMP.. */
2006-01-16 22:14:10 -08:00
}
/*
* Do what every setup is needed on image and the
* reboot code buffer to allow us to avoid allocations
* later .
*/
int machine_kexec_prepare ( struct kimage * image )
{
return 0 ;
}
void machine_kexec_cleanup ( struct kimage * image )
{
}
static void kexec_info ( struct kimage * image )
{
int i ;
printk ( " kexec information \n " ) ;
for ( i = 0 ; i < image - > nr_segments ; i + + ) {
printk ( " segment[%d]: 0x%08x - 0x%08x (0x%08x) \n " ,
i ,
( unsigned int ) image - > segment [ i ] . mem ,
2007-04-27 11:25:57 +09:00
( unsigned int ) image - > segment [ i ] . mem +
image - > segment [ i ] . memsz ,
2006-01-16 22:14:10 -08:00
( unsigned int ) image - > segment [ i ] . memsz ) ;
2007-04-27 11:25:57 +09:00
}
2006-01-16 22:14:10 -08:00
printk ( " start : 0x%08x \n \n " , ( unsigned int ) image - > start ) ;
}
/*
* Do not allocate memory ( or fail in any way ) in machine_kexec ( ) .
* We are past the point of no return , committed to rebooting now .
*/
2008-07-25 19:45:07 -07:00
void machine_kexec ( struct kimage * image )
2006-01-16 22:14:10 -08:00
{
unsigned long page_list ;
unsigned long reboot_code_buffer ;
relocate_new_kernel_t rnk ;
2009-03-18 08:49:45 +00:00
unsigned long entry ;
unsigned long * ptr ;
2009-03-18 19:07:16 +09:00
int save_ftrace_enabled ;
2009-03-18 08:49:45 +00:00
/*
* Nicked from the mips version of machine_kexec ( ) :
* The generic kexec code builds a page list with physical
* addresses . Use phys_to_virt ( ) to convert them to virtual .
*/
for ( ptr = & image - > head ; ( entry = * ptr ) & & ! ( entry & IND_DONE ) ;
ptr = ( entry & IND_INDIRECTION ) ?
phys_to_virt ( entry & PAGE_MASK ) : ptr + 1 ) {
if ( * ptr & IND_SOURCE | | * ptr & IND_INDIRECTION | |
* ptr & IND_DESTINATION )
* ptr = ( unsigned long ) phys_to_virt ( * ptr ) ;
}
2006-01-16 22:14:10 -08:00
2009-03-18 08:51:29 +00:00
# ifdef CONFIG_KEXEC_JUMP
if ( image - > preserve_context )
save_processor_state ( ) ;
# endif
2009-03-18 19:07:16 +09:00
save_ftrace_enabled = __ftrace_enabled_save ( ) ;
2006-01-16 22:14:10 -08:00
/* Interrupts aren't acceptable while we reboot */
local_irq_disable ( ) ;
page_list = image - > head ;
/* we need both effective and real address here */
reboot_code_buffer =
( unsigned long ) page_address ( image - > control_code_page ) ;
/* copy our kernel relocation code to the control code page */
memcpy ( ( void * ) reboot_code_buffer , relocate_new_kernel ,
relocate_new_kernel_size ) ;
2009-03-18 19:06:15 +09:00
kexec_info ( image ) ;
2006-01-16 22:14:10 -08:00
flush_cache_all ( ) ;
2010-01-12 14:50:43 +09:00
sh_bios_vbr_reload ( ) ;
2009-03-18 19:06:15 +09:00
2006-01-16 22:14:10 -08:00
/* now call it */
rnk = ( relocate_new_kernel_t ) reboot_code_buffer ;
2009-03-19 10:04:29 +00:00
( * rnk ) ( page_list , reboot_code_buffer ,
( unsigned long ) phys_to_virt ( image - > start ) ) ;
2009-03-18 08:51:29 +00:00
# ifdef CONFIG_KEXEC_JUMP
asm volatile ( " ldc %0, vbr " : : " r " ( & vbr_base ) : " memory " ) ;
2009-03-18 19:06:15 +09:00
2009-03-18 08:51:29 +00:00
if ( image - > preserve_context )
restore_processor_state ( ) ;
/* Convert page list back to physical addresses, what a mess. */
for ( ptr = & image - > head ; ( entry = * ptr ) & & ! ( entry & IND_DONE ) ;
ptr = ( * ptr & IND_INDIRECTION ) ?
phys_to_virt ( * ptr & PAGE_MASK ) : ptr + 1 ) {
if ( * ptr & IND_SOURCE | | * ptr & IND_INDIRECTION | |
* ptr & IND_DESTINATION )
* ptr = virt_to_phys ( * ptr ) ;
}
# endif
2009-03-18 19:07:16 +09:00
__ftrace_enabled_restore ( save_ftrace_enabled ) ;
2006-01-16 22:14:10 -08:00
}
2008-08-04 13:42:49 +09:00
void arch_crash_save_vmcoreinfo ( void )
{
# ifdef CONFIG_NUMA
VMCOREINFO_SYMBOL ( node_data ) ;
VMCOREINFO_LENGTH ( node_data , MAX_NUMNODES ) ;
# endif
2010-05-07 17:14:00 +09:00
# ifdef CONFIG_X2TLB
VMCOREINFO_CONFIG ( X2TLB ) ;
# endif
2008-08-04 13:42:49 +09:00
}
2010-05-07 14:54:55 +09:00
void __init reserve_crashkernel ( void )
{
unsigned long long crash_size , crash_base ;
int ret ;
2010-07-12 14:36:09 +10:00
/* this is necessary because of memblock_phys_mem_size() */
memblock_analyze ( ) ;
2010-05-07 14:54:55 +09:00
2010-07-12 14:36:09 +10:00
ret = parse_crashkernel ( boot_command_line , memblock_phys_mem_size ( ) ,
2010-05-07 14:54:55 +09:00
& crash_size , & crash_base ) ;
if ( ret = = 0 & & crash_size > 0 ) {
crashk_res . start = crash_base ;
crashk_res . end = crash_base + crash_size - 1 ;
}
if ( crashk_res . end = = crashk_res . start )
goto disable ;
crash_size = PAGE_ALIGN ( crashk_res . end - crashk_res . start + 1 ) ;
if ( ! crashk_res . start ) {
2010-07-12 14:36:09 +10:00
unsigned long max = memblock_end_of_DRAM ( ) - memory_limit ;
crashk_res . start = __memblock_alloc_base ( crash_size , PAGE_SIZE , max ) ;
2010-05-07 14:54:55 +09:00
if ( ! crashk_res . start ) {
pr_err ( " crashkernel allocation failed \n " ) ;
goto disable ;
}
} else {
2010-07-12 14:36:09 +10:00
ret = memblock_reserve ( crashk_res . start , crash_size ) ;
2010-05-07 14:54:55 +09:00
if ( unlikely ( ret < 0 ) ) {
pr_err ( " crashkernel reservation failed - "
" memory is in use \n " ) ;
goto disable ;
}
}
2010-05-10 20:17:25 +09:00
crashk_res . end = crashk_res . start + crash_size - 1 ;
/*
* Crash kernel trumps memory limit
*/
2010-07-12 14:36:09 +10:00
if ( ( memblock_end_of_DRAM ( ) - memory_limit ) < = crashk_res . end ) {
2010-05-10 20:17:25 +09:00
memory_limit = 0 ;
pr_info ( " Disabled memory limit for crashkernel \n " ) ;
}
pr_info ( " Reserving %ldMB of memory at 0x%08lx "
2010-05-07 14:54:55 +09:00
" for crashkernel (System RAM: %ldMB) \n " ,
( unsigned long ) ( crash_size > > 20 ) ,
2010-05-10 20:17:25 +09:00
( unsigned long ) ( crashk_res . start ) ,
2010-07-12 14:36:09 +10:00
( unsigned long ) ( memblock_phys_mem_size ( ) > > 20 ) ) ;
2010-05-07 14:54:55 +09:00
return ;
disable :
crashk_res . start = crashk_res . end = 0 ;
}