2005-06-25 14:57:56 -07:00
/*
* machine_kexec . c - handle transition of Linux booting another kernel
* Copyright ( C ) 2002 - 2005 Eric Biederman < ebiederm @ xmission . com >
*
* 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>
2006-09-26 10:52:32 +02:00
# include <linux/init.h>
2005-06-25 14:57:56 -07:00
# include <asm/pgtable.h>
# include <asm/pgalloc.h>
# include <asm/tlbflush.h>
# include <asm/mmu_context.h>
# include <asm/io.h>
# include <asm/apic.h>
# include <asm/cpufeature.h>
2005-07-29 13:01:18 -06:00
# include <asm/desc.h>
2005-09-03 15:56:36 -07:00
# include <asm/system.h>
2005-06-25 14:57:56 -07:00
# define PAGE_ALIGNED __attribute__ ((__aligned__(PAGE_SIZE)))
2006-09-26 10:52:38 +02:00
static u32 kexec_pgd [ 1024 ] PAGE_ALIGNED ;
# ifdef CONFIG_X86_PAE
static u32 kexec_pmd0 [ 1024 ] PAGE_ALIGNED ;
static u32 kexec_pmd1 [ 1024 ] PAGE_ALIGNED ;
2005-06-25 14:57:56 -07:00
# endif
2006-09-26 10:52:38 +02:00
static u32 kexec_pte0 [ 1024 ] PAGE_ALIGNED ;
static u32 kexec_pte1 [ 1024 ] PAGE_ALIGNED ;
2005-06-25 14:57:56 -07:00
static void set_idt ( void * newidt , __u16 limit )
{
2005-07-29 13:01:18 -06:00
struct Xgt_desc_struct curidt ;
2005-06-25 14:57:56 -07:00
/* ia32 supports unaliged loads & stores */
2005-07-29 13:01:18 -06:00
curidt . size = limit ;
curidt . address = ( unsigned long ) newidt ;
2005-06-25 14:57:56 -07:00
2005-09-03 15:56:42 -07:00
load_idt ( & curidt ) ;
2005-06-25 14:57:56 -07:00
} ;
static void set_gdt ( void * newgdt , __u16 limit )
{
2005-07-29 13:01:18 -06:00
struct Xgt_desc_struct curgdt ;
2005-06-25 14:57:56 -07:00
/* ia32 supports unaligned loads & stores */
2005-07-29 13:01:18 -06:00
curgdt . size = limit ;
curgdt . address = ( unsigned long ) newgdt ;
2005-06-25 14:57:56 -07:00
2005-09-03 15:56:42 -07:00
load_gdt ( & curgdt ) ;
2005-06-25 14:57:56 -07:00
} ;
static void load_segments ( void )
{
# define __STR(X) #X
# define STR(X) __STR(X)
__asm__ __volatile__ (
" \t ljmp $ " STR ( __KERNEL_CS ) " ,$1f \n "
" \t 1: \n "
2006-03-07 21:55:48 -08:00
" \t movl $ " STR ( __KERNEL_DS ) " ,%%eax \n "
" \t movl %%eax,%%ds \n "
" \t movl %%eax,%%es \n "
" \t movl %%eax,%%fs \n "
" \t movl %%eax,%%gs \n "
" \t movl %%eax,%%ss \n "
: : : " eax " , " memory " ) ;
2005-06-25 14:57:56 -07:00
# undef STR
# undef __STR
}
/*
* A architecture hook called to validate the
* proposed image and prepare the control pages
* as needed . The pages for KEXEC_CONTROL_CODE_SIZE
* have been allocated , but the segments have yet
* been copied into the kernel .
*
* Do what every setup is needed on image and the
* reboot code buffer to allow us to avoid allocations
* later .
*
* Currently nothing .
*/
int machine_kexec_prepare ( struct kimage * image )
{
return 0 ;
}
/*
* Undo anything leftover by machine_kexec_prepare
* when an image is freed .
*/
void machine_kexec_cleanup ( struct kimage * image )
{
}
/*
* Do not allocate memory ( or fail in any way ) in machine_kexec ( ) .
* We are past the point of no return , committed to rebooting now .
*/
NORET_TYPE void machine_kexec ( struct kimage * image )
{
2006-09-26 10:52:38 +02:00
unsigned long page_list [ PAGES_NR ] ;
void * control_page ;
2005-06-25 14:57:56 -07:00
/* Interrupts aren't acceptable while we reboot */
local_irq_disable ( ) ;
2006-09-26 10:52:38 +02:00
control_page = page_address ( image - > control_code_page ) ;
memcpy ( control_page , relocate_kernel , PAGE_SIZE ) ;
page_list [ PA_CONTROL_PAGE ] = __pa ( control_page ) ;
page_list [ VA_CONTROL_PAGE ] = ( unsigned long ) relocate_kernel ;
page_list [ PA_PGD ] = __pa ( kexec_pgd ) ;
page_list [ VA_PGD ] = ( unsigned long ) kexec_pgd ;
# ifdef CONFIG_X86_PAE
page_list [ PA_PMD_0 ] = __pa ( kexec_pmd0 ) ;
page_list [ VA_PMD_0 ] = ( unsigned long ) kexec_pmd0 ;
page_list [ PA_PMD_1 ] = __pa ( kexec_pmd1 ) ;
page_list [ VA_PMD_1 ] = ( unsigned long ) kexec_pmd1 ;
# endif
page_list [ PA_PTE_0 ] = __pa ( kexec_pte0 ) ;
page_list [ VA_PTE_0 ] = ( unsigned long ) kexec_pte0 ;
page_list [ PA_PTE_1 ] = __pa ( kexec_pte1 ) ;
page_list [ VA_PTE_1 ] = ( unsigned long ) kexec_pte1 ;
2005-06-25 14:57:56 -07:00
2006-07-30 03:03:20 -07:00
/* The segment registers are funny things, they have both a
* visible and an invisible part . Whenever the visible part is
* set to a specific selector , the invisible part is loaded
* with from a table in memory . At no other time is the
* descriptor table in memory accessed .
2005-06-25 14:57:56 -07:00
*
* I take advantage of this here by force loading the
* segments , before I zap the gdt with an invalid value .
*/
load_segments ( ) ;
/* The gdt & idt are now invalid.
* If you want to load them you must set up your own idt & gdt .
*/
set_gdt ( phys_to_virt ( 0 ) , 0 ) ;
set_idt ( phys_to_virt ( 0 ) , 0 ) ;
/* now call it */
2006-09-26 10:52:38 +02:00
relocate_kernel ( ( unsigned long ) image - > head , ( unsigned long ) page_list ,
image - > start , cpu_has_pae ) ;
2005-06-25 14:57:56 -07:00
}
2006-09-26 10:52:32 +02:00
/* crashkernel=size@addr specifies the location to reserve for
* a crash kernel . By reserving this memory we guarantee
* that linux never sets it up as a DMA target .
* Useful for holding code to do something appropriate
* after a kernel panic .
*/
static int __init parse_crashkernel ( char * arg )
{
unsigned long size , base ;
size = memparse ( arg , & arg ) ;
if ( * arg = = ' @ ' ) {
base = memparse ( arg + 1 , & arg ) ;
/* FIXME: Do I want a sanity check
* to validate the memory range ?
*/
crashk_res . start = base ;
crashk_res . end = base + size - 1 ;
}
return 0 ;
}
early_param ( " crashkernel " , parse_crashkernel ) ;