blob: 9c52896e0c1c8f6d27a7ca4e16849e6e19cadea4 [file] [log] [blame]
/*
* exec_kernel/user_space/head.S
*
* Copyright (C) 2000, 2002, 2003 Eric Biederman
*
* Parts of this code were take from the linux startup
* code of linux-2.4.0-test9
*
* Other parts were taken from etherboot-5.0.5
*/
#define ASSEMBLY 1
#define RELOC 0x10000
#define PROT_CODE_SEG 0x10
#define PROT_DATA_SEG 0x18
#define REAL_CODE_SEG 0x08
#define REAL_DATA_SEG 0x20
.equ CR0_PE,1
.text
.code32
#include "convert.h"
.globl startup_32
startup_32:
cld
cli
# Save the arguments safely out of the way
movl %eax, boot_type
movl %ebx, boot_data
cmp $0,%esp
jz 1f
movl 4(%esp), %eax
movl %eax, boot_param
1:
movl stack_start, %esp
# Clear eflags
pushl $0
popfl
# Clear BSS
xorl %eax,%eax
movl $ _edata,%edi
movl $ _end,%ecx
subl %edi,%ecx
cld
rep
stosb
# Move the gdt where Linux will not smash it during decompression
movl $gdt, %esi
movl $GDTLOC, %edi
movl $(gdt_end - gdt), %ecx
rep movsb
# Linux makes stupid assumptions about the segments
# that are already setup, so setup a new gdt & ldt
# and then reload the segment registers.
lgdt gdt_48
lidt idt_48
# Load the data segment registers
movl $ PROT_DATA_SEG, %eax
movl %eax, %ds
movl %eax, %es
movl %eax, %fs
movl %eax, %gs
movl %eax, %ss
pushl $image_params # image build time parameters as forth arg
pushl boot_param # boot_param pointer as third arg
pushl boot_data # boot data pointer as second arg
pushl boot_type # boot data type as first argument
call convert_params
movl %eax, %esi # put the real mode pointer in a safe place
addl $16, %esp # pop the arguments
# Setup the registers before jumping to linux
# clear eflags
pushl $0
popfl
# Flag to indicate we are the bootstrap processor
xorl %ebx, %ebx
movl switch_64, %eax
cmp $1, %eax
jz switch_to_64
# Clear the unspecified registers for good measure
xorl %eax, %eax
xorl %ecx, %ecx
xorl %edx, %edx
xorl %edi, %edi
xorl %ebp, %ebp
# do not clear esp, we still need to use lret later
pushl $PROT_CODE_SEG
movl entry, %eax
pushl %eax
lret
switch_to_64:
/* We need to switch to 64bit before use startup_64 entry go to kernel */
/*
* Prepare for entering 64 bit mode
*/
# Move the gdt64 where Linux will not smash it during decompression
movl %esi, %eax # save the real mode pointer
movl $gdt64, %esi
movl $GDT64LOC, %edi
movl $(gdt64_end - gdt64), %ecx
rep movsb
movl %eax, %esi
/* Load new GDT with the 64bit segments using 32bit descriptor */
lgdt gdt64
/* Enable PAE mode */
xorl %eax, %eax
btsl $5, %eax
movl %eax, %cr4
/*
* Build early 4G boot pagetable
*/
/* Initialize Page tables to 0*/
movl $PGTLOC, %edi
xorl %eax, %eax
movl $((4096*6)/4), %ecx
rep stosl
/* Build Level 4 */
movl $(PGTLOC + 0), %edi
leal 0x1007 (%edi), %eax
movl %eax, 0(%edi)
/* Build Level 3 */
movl $(PGTLOC + 0x1000), %edi
leal 0x1007(%edi), %eax
movl $4, %ecx
1: movl %eax, 0x00(%edi)
addl $0x00001000, %eax
addl $8, %edi
decl %ecx
jnz 1b
/* Build Level 2 */
movl $(PGTLOC + 0x2000), %edi
movl $0x00000183, %eax
movl $2048, %ecx
1: movl %eax, 0(%edi)
addl $0x00200000, %eax
addl $8, %edi
decl %ecx
jnz 1b
/* Enable the boot page tables */
movl $PGTLOC, %eax
movl %eax, %cr3
/* Enable Long mode in EFER (Extended Feature Enable Register) */
movl $0xc0000080, %ecx
rdmsr
btsl $8, %eax
wrmsr
/* Preparing for 64bit jmp */
pushl $PROT_CODE_SEG
movl entry, %eax
pushl %eax
/* Enter paged protected Mode, activating Long Mode */
xorl %eax, %eax
btsl $31, %eax
btsl $0, %eax
movl %eax, %cr0
/*
* At this point we're in long mode but in 32bit compatibility mode
* with EFER.LME = 1, CS.L = 0, CS.D = 1 (and in turn
* EFER.LMA = 1). Now we want to jump in 64bit mode, to do that we use
* the new gdt/idt that has __KERNEL_CS with CS.L = 1.
*/
lret
/* Routines to query the BIOS... */
/**************************************************************************
E820_MEMSIZE - Get a listing of memory regions
**************************************************************************/
#define SMAP 0x534d4150
.globl meme820
meme820:
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %esi
pushl %edi
movl 8(%ebp), %edi /* Address to return e820 structures at */
subl $RELOC, %edi
movl 12(%ebp), %esi /* Maximum number of e820 structurs to return */
pushl %esi
call _prot_to_real
.code16
xorl %ebx, %ebx
jmpe820:
movl $0xe820, %eax
movl $SMAP, %edx
movl $20, %ecx
/* %di was setup earlier */
int $0x15
jc bail820
cmpl $SMAP, %eax
jne bail820
good820:
/* If this is useable memory, we save it by simply advancing %di by
* sizeof(e820rec)
*/
decl %esi
testl %esi,%esi
jz bail820
addw $20, %di
again820:
cmpl $0, %ebx /* check to see if %ebx is set to EOF */
jne jmpe820
bail820:
data32 call _real_to_prot
.code32
popl %eax
subl %esi, %eax /* Compute how many structure we read */
/* Restore everything else */
popl %edi
popl %esi
popl %ebx
movl %ebp, %esp
popl %ebp
ret
/**************************************************************************
MEME801 - Determine size of extended memory
**************************************************************************/
.globl meme801
meme801:
pushl %ebx
pushl %esi
pushl %edi
call _prot_to_real
.code16
stc # fix to work around buggy
xorw %cx,%cx # BIOSes which dont clear/set
xorw %dx,%dx # carry on pass/error of
# e801h memory size call
# or merely pass cx,dx though
# without changing them.
movw $0xe801,%ax
int $0x15
jc e801absent
cmpw $0x0, %cx # Kludge to handle BIOSes
jne e801usecxdx # which report their extended
cmpw $0x0, %dx # memory in AX/BX rather than
jne e801usecxdx # CX/DX. The spec I have read
movw %ax, %cx # seems to indicate AX/BX
movw %bx, %dx # are more reasonable anyway...
e801usecxdx:
andl $0xffff, %edx # clear sign extend
shll $6, %edx # and go from 64k to 1k chunks
movl %edx, %eax # store extended memory size
andl $0xffff, %ecx # clear sign extend
addl %ecx, %eax # and add lower memory into
jmp e801out
e801absent:
xorl %eax,%eax
e801out:
data32 call _real_to_prot
.code32
/* Restore Everything */
popl %edi
popl %esi
popl %ebx
ret
/**************************************************************************
MEM88 - Determine size of extended memory
**************************************************************************/
.globl mem88
mem88:
pushl %ebx
pushl %esi
pushl %edi
call _prot_to_real
.code16
movb $0x88, %ah
int $0x15
andl $0xffff, %eax
data32 call _real_to_prot
.code32
/* Restore Everything */
popl %edi
popl %esi
popl %ebx
ret
/**************************************************************************
BASEMEMSIZE - Get size of the conventional (base) memory
**************************************************************************/
.globl basememsize
basememsize:
call _prot_to_real
.code16
int $0x12
movw %ax,%cx
DATA32 call _real_to_prot
.code32
movw %cx,%ax
ret
/**************************************************************************
_REAL_TO_PROT - Go from REAL mode to Protected Mode
**************************************************************************/
.globl _real_to_prot
_real_to_prot:
.code16
cli
cs
addr32 lgdt gdt_48 - RELOC
movl %cr0,%eax
orl $CR0_PE,%eax
movl %eax,%cr0 /* turn on protected mode */
/* flush prefetch queue, and reload %cs:%eip */
data32 ljmp $PROT_CODE_SEG,$1f
1:
.code32
/* reload other segment registers */
movl $PROT_DATA_SEG,%eax
movl %eax,%ds
movl %eax,%es
movl %eax,%ss
addl $RELOC,%esp /* Fix up stack pointer */
xorl %eax,%eax
movl %eax,%fs
movl %eax,%gs
popl %eax /* Fix up return address */
addl $RELOC,%eax
pushl %eax
/* switch to protected mode idt */
cs
lidt idt_48
ret
/**************************************************************************
_PROT_TO_REAL - Go from Protected Mode to REAL Mode
**************************************************************************/
.globl _prot_to_real
_prot_to_real:
.code32
popl %eax
subl $RELOC,%eax /* Adjust return address */
pushl %eax
subl $RELOC,%esp /* Adjust stack pointer */
ljmp $REAL_CODE_SEG,$1f- RELOC /* jump to a 16 bit segment */
1:
.code16
/* clear the PE bit of CR0 */
movl %cr0,%eax
andl $0!CR0_PE,%eax
movl %eax,%cr0
/* make intersegment jmp to flush the processor pipeline
* and reload %cs:%eip (to clear upper 16 bits of %eip).
*/
data32 ljmp $(RELOC)>>4,$2f- RELOC
2:
/* we are in real mode now
* set up the real mode segment registers : %ds, $ss, %es
*/
movw %cs,%ax
movw %ax,%ds
movw %ax,%es
movw %ax,%ss
movw %ax,%fs
movw %ax,%gs
/* Switch to the real mode idt */
cs
addr32 lidt idt_real - RELOC
sti
data32 ret /* There is a 32 bit return address on the stack */
.code32
boot_type: .long 0
boot_data: .long 0
boot_param: .long 0
idt_real:
.word 0x400 # idt limit = 256
.word 0, 0
idt_48:
.word 0 # idt limit = 0
.word 0, 0 # idt base = 0L
gdt_48:
.word gdt_end - gdt - 1 # gdt limit=40,
# (5 GDT entries)
.long GDTLOC # gdt base
# Descriptor tables
# These need to be in a seperate section so I can be
# certain later activities dont stomp them
gdt:
/* 0x00 */
.word 0, 0, 0, 0 # dummy
/* 0x08 */
/* 16 bit real mode code segment */
.word 0xffff,(RELOC&0xffff)
.byte (RELOC>>16),0x9b,0x00,(RELOC>>24)
/* 0x10 */
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
.word 0 # base address = 0
.word 0x9A00 # code read/exec
.word 0x00CF # granularity = 4096, 386
# (+5th nibble of limit)
/* 0x18 */
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
.word 0 # base address = 0
.word 0x9200 # data read/write
.word 0x00CF # granularity = 4096, 386
# (+5th nibble of limit)
/* 0x20 */
/* 16 bit real mode data segment */
.word 0xffff,(RELOC&0xffff)
.byte (RELOC>>16),0x93,0x00,(RELOC>>24)
/* For 2.5.x the kernel segments have moved */
/* 0x28 dummy */
.quad 0
/* 0x30 dummy */
.quad 0
/* 0x38 dummy */
.quad 0
/* 0x40 dummy */
.quad 0
/* 0x48 dummy */
.quad 0
/* 0x50 dummy */
.quad 0
/* 0x58 dummy */
.quad 0
/* 0x60 */
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
.word 0 # base address = 0
.word 0x9A00 # code read/exec
.word 0x00CF # granularity = 4096, 386
# (+5th nibble of limit)
/* 0x68 */
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
.word 0 # base address = 0
.word 0x9200 # data read/write
.word 0x00CF # granularity = 4096, 386
# (+5th nibble of limit)
/*
* The layout of the per-CPU GDT under Linux:
*
* 0 - null
* 1 - reserved
* 2 - reserved
* 3 - reserved
*
* 4 - default user CS <==== new cacheline
* 5 - default user DS
*
* ------- start of TLS (Thread-Local Storage) segments:
*
* 6 - TLS segment #1 [ glibc's TLS segment ]
* 7 - TLS segment #2 [ Wine's %fs Win32 segment ]
* 8 - TLS segment #3
* 9 - reserved
* 10 - reserved
* 11 - reserved
*
* ------- start of kernel segments:
*
* 12 - kernel code segment <==== new cacheline
* 13 - kernel data segment
* 14 - TSS
* 15 - LDT
* 16 - PNPBIOS support (16->32 gate)
* 17 - PNPBIOS support
* 18 - PNPBIOS support
* 19 - PNPBIOS support
* 20 - PNPBIOS support
* 21 - APM BIOS support
* 22 - APM BIOS support
* 23 - APM BIOS support
*/
gdt_end:
gdt64:
.word gdt64_end - gdt64
.long GDT64LOC
.word 0
.quad 0x0000000000000000 /* NULL descriptor */
.quad 0x00af9a000000ffff /* __KERNEL_CS */
.quad 0x00cf92000000ffff /* __KERNEL_DS */
gdt64_end:
.section ".trailer", "a"
/* Constants set at build time, these are at the very end of my image */
.balign 16
.global image_params
image_params:
convert_magic:
.long CONVERT_MAGIC
gdt_size:
.long gdt_end - gdt
gdt64_size:
.long gdt64_end - gdt64
pgt_size:
.long 4096*6
bss_size:
.long bss_sizex
ramdisk_flags:
.word 0
root_dev:
.word DEFAULT_ROOT_DEV
entry:
.long 0
switch_64:
.long 0
initrd_start:
.long 0
initrd_size:
.long 0
cmdline:
.asciz "BOOT_IMAGE=head.S console=ttyS0 ip=dhcp root=/dev/nfs"
.org cmdline + CMDLINE_MAX, 0
cmdline_end: