2007-07-11 23:18:53 +04:00
/* -*- linux-c -*- ------------------------------------------------------- *
*
* Copyright ( C ) 1991 , 1992 Linus Torvalds
* Copyright 2007 rPath , Inc . - All Rights Reserved
*
* This file is part of the Linux kernel , and is made available under
* the terms of the GNU General Public License version 2.
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
* arch / i386 / boot / pm . c
*
* Prepare the machine for transition to protected mode .
*/
# include "boot.h"
# include <asm/segment.h>
/*
* Invoke the realmode switch hook if present ; otherwise
* disable all interrupts .
*/
static void realmode_switch_hook ( void )
{
if ( boot_params . hdr . realmode_swtch ) {
asm volatile ( " lcallw *%0 "
: : " m " ( boot_params . hdr . realmode_swtch )
: " eax " , " ebx " , " ecx " , " edx " ) ;
} else {
asm volatile ( " cli " ) ;
outb ( 0x80 , 0x70 ) ; /* Disable NMI */
io_delay ( ) ;
}
}
/*
* A zImage kernel is loaded at 0x10000 but wants to run at 0x1000 .
* A bzImage kernel is loaded and runs at 0x100000 .
*/
static void move_kernel_around ( void )
{
/* Note: rely on the compile-time option here rather than
the LOADED_HIGH flag . The Qemu kernel loader unconditionally
sets the loadflags to zero . */
# ifndef __BIG_KERNEL__
u16 dst_seg , src_seg ;
u32 syssize ;
dst_seg = 0x1000 > > 4 ;
src_seg = 0x10000 > > 4 ;
syssize = boot_params . hdr . syssize ; /* Size in 16-byte paragraphs */
while ( syssize ) {
int paras = ( syssize > = 0x1000 ) ? 0x1000 : syssize ;
int dwords = paras < < 2 ;
asm volatile ( " pushw %%es ; "
" pushw %%ds ; "
" movw %1,%%es ; "
" movw %2,%%ds ; "
" xorw %%di,%%di ; "
" xorw %%si,%%si ; "
" rep;movsl ; "
" popw %%ds ; "
" popw %%es "
: " +c " ( dwords )
2007-07-15 03:47:13 +04:00
: " r " ( dst_seg ) , " r " ( src_seg )
2007-07-11 23:18:53 +04:00
: " esi " , " edi " ) ;
syssize - = paras ;
dst_seg + = paras ;
src_seg + = paras ;
}
# endif
}
/*
* Disable all interrupts at the legacy PIC .
*/
static void mask_all_interrupts ( void )
{
2007-07-26 21:10:35 +04:00
outb ( 0xff , 0xa1 ) ; /* Mask all interrupts on the secondary PIC */
2007-07-11 23:18:53 +04:00
io_delay ( ) ;
outb ( 0xfb , 0x21 ) ; /* Mask all but cascade on the primary PIC */
io_delay ( ) ;
}
/*
* Reset IGNNE # if asserted in the FPU .
*/
static void reset_coprocessor ( void )
{
outb ( 0 , 0xf0 ) ;
io_delay ( ) ;
outb ( 0 , 0xf1 ) ;
io_delay ( ) ;
}
/*
* Set up the GDT
*/
# define GDT_ENTRY(flags,base,limit) \
( ( ( u64 ) ( base & 0xff000000 ) < < 32 ) | \
( ( u64 ) flags < < 40 ) | \
( ( u64 ) ( limit & 0x00ff0000 ) < < 32 ) | \
( ( u64 ) ( base & 0x00ffff00 ) < < 16 ) | \
( ( u64 ) ( limit & 0x0000ffff ) ) )
struct gdt_ptr {
u16 len ;
u32 ptr ;
} __attribute__ ( ( packed ) ) ;
static void setup_gdt ( void )
{
/* There are machines which are known to not boot with the GDT
being 8 - byte unaligned . Intel recommends 16 byte alignment . */
static const u64 boot_gdt [ ] __attribute__ ( ( aligned ( 16 ) ) ) = {
/* CS: code, read/execute, 4 GB, base 0 */
[ GDT_ENTRY_BOOT_CS ] = GDT_ENTRY ( 0xc09b , 0 , 0xfffff ) ,
/* DS: data, read/write, 4 GB, base 0 */
[ GDT_ENTRY_BOOT_DS ] = GDT_ENTRY ( 0xc093 , 0 , 0xfffff ) ,
} ;
2007-09-03 22:32:38 +04:00
/* Xen HVM incorrectly stores a pointer to the gdt_ptr, instead
of the gdt_ptr contents . Thus , make it static so it will
stay in memory , at least long enough that we switch to the
proper kernel GDT . */
static struct gdt_ptr gdt ;
2007-07-11 23:18:53 +04:00
gdt . len = sizeof ( boot_gdt ) - 1 ;
gdt . ptr = ( u32 ) & boot_gdt + ( ds ( ) < < 4 ) ;
asm volatile ( " lgdtl %0 " : : " m " ( gdt ) ) ;
}
/*
* Set up the IDT
*/
static void setup_idt ( void )
{
static const struct gdt_ptr null_idt = { 0 , 0 } ;
asm volatile ( " lidtl %0 " : : " m " ( null_idt ) ) ;
}
/*
* Actual invocation sequence
*/
void go_to_protected_mode ( void )
{
/* Hook before leaving real mode, also disables interrupts */
realmode_switch_hook ( ) ;
/* Move the kernel/setup to their final resting places */
move_kernel_around ( ) ;
/* Enable the A20 gate */
if ( enable_a20 ( ) ) {
puts ( " A20 gate not responding, unable to boot... \n " ) ;
die ( ) ;
}
/* Reset coprocessor (IGNNE#) */
reset_coprocessor ( ) ;
/* Mask all interrupts in the PIC */
mask_all_interrupts ( ) ;
/* Actual transition to protected mode... */
setup_idt ( ) ;
setup_gdt ( ) ;
protected_mode_jump ( boot_params . hdr . code32_start ,
( u32 ) & boot_params + ( ds ( ) < < 4 ) ) ;
}