grub/grub-core/kern/i386/pc/startup.S
2010-11-12 08:45:16 +01:00

1013 lines
20 KiB
ArmAsm

/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 1999,2000,2001,2002,2003,2005,2006,2007,2008,2009 Free Software Foundation, Inc.
*
* GRUB 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 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Note: These functions defined in this file may be called from C.
* Be careful of that you must not modify some registers. Quote
* from gcc-2.95.2/gcc/config/i386/i386.h:
1 for registers not available across function calls.
These must include the FIXED_REGISTERS and also any
registers that can be used without being saved.
The latter must include the registers where values are returned
and the register where structure-value addresses are passed.
Aside from that, you can include as many other registers as you like.
ax,dx,cx,bx,si,di,bp,sp,st,st1,st2,st3,st4,st5,st6,st7,arg
{ 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
*/
/*
* Note: GRUB is compiled with the options -mrtd and -mregparm=3.
* So the first three arguments are passed in %eax, %edx, and %ecx,
* respectively, and if a function has a fixed number of arguments
* and the number is greater than three, the function must return
* with "ret $N" where N is ((the number of arguments) - 3) * 4.
*/
#include <config.h>
#include <grub/symbol.h>
#include <grub/boot.h>
#include <grub/machine/boot.h>
#include <grub/machine/memory.h>
#include <grub/machine/console.h>
#include <grub/cpu/linux.h>
#include <grub/machine/kernel.h>
#include <grub/term.h>
#include <multiboot.h>
#include <multiboot2.h>
#define ABS(x) ((x) - LOCAL (base) + GRUB_BOOT_MACHINE_KERNEL_ADDR + 0x200)
.file "startup.S"
.text
/* Tell GAS to generate 16-bit instructions so that this code works
in real mode. */
.code16
.globl start, _start
start:
_start:
LOCAL (base):
/*
* Guarantee that "main" is loaded at 0x0:0x8200.
*/
#ifdef __APPLE__
ljmp $0, $(ABS(LOCAL (codestart)) - 0x10000)
#else
ljmp $0, $ABS(LOCAL (codestart))
#endif
/*
* Compatibility version number
*
* These MUST be at byte offset 6 and 7 of the executable
* DO NOT MOVE !!!
*/
. = _start + 0x6
.byte GRUB_BOOT_VERSION_MAJOR, GRUB_BOOT_VERSION_MINOR
/*
* This is a special data area 8 bytes from the beginning.
*/
. = _start + 0x8
VARIABLE(grub_total_module_size)
.long 0
VARIABLE(grub_kernel_image_size)
.long 0
VARIABLE(grub_compressed_size)
.long 0
VARIABLE(grub_install_dos_part)
.long 0xFFFFFFFF
VARIABLE(grub_install_bsd_part)
.long 0xFFFFFFFF
reed_solomon_redundancy:
.long 0
#ifdef APPLE_CC
bss_start:
.long 0
bss_end:
.long 0
#endif
/*
* This is the area for all of the special variables.
*/
VARIABLE(grub_boot_drive)
.byte 0
/* the real mode code continues... */
LOCAL (codestart):
cli /* we're not safe here! */
/* set up %ds, %ss, and %es */
xorw %ax, %ax
movw %ax, %ds
movw %ax, %ss
movw %ax, %es
/* set up the real mode/BIOS stack */
movl $GRUB_MEMORY_MACHINE_REAL_STACK, %ebp
movl %ebp, %esp
sti /* we're safe again */
/* save the boot drive */
ADDR32 movb %dl, EXT_C(grub_boot_drive)
/* reset disk system (%ah = 0) */
int $0x13
/* transition to protected mode */
DATA32 call real_to_prot
/* The ".code32" directive takes GAS out of 16-bit mode. */
.code32
incl %eax
call grub_gate_a20
movl EXT_C(grub_compressed_size), %edx
addl $(GRUB_KERNEL_MACHINE_RAW_SIZE - GRUB_KERNEL_I386_PC_NO_REED_SOLOMON_PART), %edx
movl reed_solomon_redundancy, %ecx
leal _start + GRUB_KERNEL_I386_PC_NO_REED_SOLOMON_PART, %eax
testl %edx, %edx
jz post_reed_solomon
call EXT_C (grub_reed_solomon_recover)
jmp post_reed_solomon
#include <rs_decoder.S>
.text
. = _start + GRUB_KERNEL_I386_PC_NO_REED_SOLOMON_PART
/*
* Support for booting GRUB from a Multiboot boot loader (e.g. GRUB itself).
* This uses the a.out kludge to load raw binary to the area starting at 1MB,
* and relocates itself after loaded.
*/
.p2align 2 /* force 4-byte alignment */
multiboot_header:
/* magic */
.long 0x1BADB002
/* flags */
.long (1 << 16)
/* checksum */
.long -0x1BADB002 - (1 << 16)
/* header addr */
.long multiboot_header - _start + 0x100000 + 0x200
/* load addr */
.long 0x100000
/* load end addr */
.long 0
/* bss end addr */
.long 0
/* entry addr */
.long multiboot_entry - _start + 0x100000 + 0x200
multiboot_entry:
.code32
/* obtain the boot device */
movl 12(%ebx), %edx
movl $GRUB_MEMORY_MACHINE_PROT_STACK, %ebp
movl %ebp, %esp
/* relocate the code */
movl $(GRUB_KERNEL_MACHINE_RAW_SIZE + 0x200), %ecx
addl EXT_C(grub_compressed_size) - _start + 0x100000 + 0x200, %ecx
movl $0x100000, %esi
movl $GRUB_BOOT_MACHINE_KERNEL_ADDR, %edi
cld
rep
movsb
/* jump to the real address */
movl $multiboot_trampoline, %eax
jmp *%eax
multiboot_trampoline:
/* fill the boot information */
movl %edx, %eax
shrl $8, %eax
xorl %ebx, %ebx
cmpb $0xFF, %ah
je 1f
movb %ah, %bl
movl %ebx, EXT_C(grub_install_dos_part)
1:
cmpb $0xFF, %al
je 2f
movb %al, %bl
movl %ebx, EXT_C(grub_install_bsd_part)
2:
shrl $24, %edx
movb $0xFF, %dh
/* enter the usual booting */
call prot_to_real
jmp LOCAL (codestart)
post_reed_solomon:
#ifdef ENABLE_LZMA
movl $GRUB_MEMORY_MACHINE_DECOMPRESSION_ADDR, %edi
movl $(_start + GRUB_KERNEL_MACHINE_RAW_SIZE), %esi
pushl %edi
pushl %esi
movl EXT_C(grub_kernel_image_size), %ecx
addl EXT_C(grub_total_module_size), %ecx
subl $GRUB_KERNEL_MACHINE_RAW_SIZE, %ecx
pushl %ecx
leal (%edi, %ecx), %ebx
call _LzmaDecodeA
/* _LzmaDecodeA clears DF, so no need to run cld */
popl %ecx
popl %edi
popl %esi
#endif
/* copy back the decompressed part (except the modules) */
subl EXT_C(grub_total_module_size), %ecx
rep
movsb
#if 0
/* copy modules before cleaning out the bss */
movl EXT_C(grub_total_module_size), %ecx
movl EXT_C(grub_kernel_image_size), %esi
addl %ecx, %esi
addl $_start, %esi
decl %esi
movl $END_SYMBOL, %edi
addl %ecx, %edi
decl %edi
std
rep
movsb
#endif
#ifdef APPLE_CC
/* clean out the bss */
bss_start_abs = ABS (bss_start)
bss_end_abs = ABS (bss_end)
movl bss_start_abs, %edi
/* compute the bss length */
movl bss_end_abs, %ecx
subl %edi, %ecx
#else
/* clean out the bss */
movl $BSS_START_SYMBOL, %edi
/* compute the bss length */
movl $END_SYMBOL, %ecx
subl %edi, %ecx
#endif
/* clean out */
xorl %eax, %eax
cld
rep
stosb
/*
* Call the start of main body of C code.
*/
call EXT_C(grub_main)
#include "../realmode.S"
/*
* grub_gate_a20(int on)
*
* Gate address-line 20 for high memory.
*
* This routine is probably overconservative in what it does, but so what?
*
* It also eats any keystrokes in the keyboard buffer. :-(
*/
grub_gate_a20:
movl %eax, %edx
gate_a20_test_current_state:
/* first of all, test if already in a good state */
call gate_a20_check_state
cmpb %al, %dl
jnz gate_a20_try_bios
ret
gate_a20_try_bios:
/* second, try a BIOS call */
pushl %ebp
call prot_to_real
.code16
movw $0x2400, %ax
testb %dl, %dl
jz 1f
incw %ax
1: int $0x15
DATA32 call real_to_prot
.code32
popl %ebp
call gate_a20_check_state
cmpb %al, %dl
jnz gate_a20_try_system_control_port_a
ret
gate_a20_try_system_control_port_a:
/*
* In macbook, the keyboard test would hang the machine, so we move
* this forward.
*/
/* fourth, try the system control port A */
inb $0x92
andb $(~0x03), %al
testb %dl, %dl
jz 6f
orb $0x02, %al
6: outb $0x92
/* When turning off Gate A20, do not check the state strictly,
because a failure is not fatal usually, and Gate A20 is always
on some modern machines. */
testb %dl, %dl
jz 7f
call gate_a20_check_state
cmpb %al, %dl
jnz gate_a20_try_keyboard_controller
7: ret
gate_a20_flush_keyboard_buffer:
inb $0x64
andb $0x02, %al
jnz gate_a20_flush_keyboard_buffer
2:
inb $0x64
andb $0x01, %al
jz 3f
inb $0x60
jmp 2b
3:
ret
gate_a20_try_keyboard_controller:
/* third, try the keyboard controller */
call gate_a20_flush_keyboard_buffer
movb $0xd1, %al
outb $0x64
4:
inb $0x64
andb $0x02, %al
jnz 4b
movb $0xdd, %al
testb %dl, %dl
jz 5f
orb $0x02, %al
5: outb $0x60
call gate_a20_flush_keyboard_buffer
/* output a dummy command (USB keyboard hack) */
movb $0xff, %al
outb $0x64
call gate_a20_flush_keyboard_buffer
call gate_a20_check_state
cmpb %al, %dl
/* everything failed, so restart from the beginning */
jnz gate_a20_try_bios
ret
gate_a20_check_state:
/* iterate the checking for a while */
movl $100, %ecx
1:
call 3f
cmpb %al, %dl
jz 2f
loop 1b
2:
ret
3:
pushl %ebx
pushl %ecx
xorl %eax, %eax
/* compare the byte at 0x8000 with that at 0x108000 */
movl $GRUB_BOOT_MACHINE_KERNEL_ADDR, %ebx
pushl %ebx
/* save the original byte in CL */
movb (%ebx), %cl
/* store the value at 0x108000 in AL */
addl $0x100000, %ebx
movb (%ebx), %al
/* try to set one less value at 0x8000 */
popl %ebx
movb %al, %ch
decb %ch
movb %ch, (%ebx)
/* serialize */
outb %al, $0x80
outb %al, $0x80
/* obtain the value at 0x108000 in CH */
pushl %ebx
addl $0x100000, %ebx
movb (%ebx), %ch
/* this result is 1 if A20 is on or 0 if it is off */
subb %ch, %al
xorb $1, %al
/* restore the original */
popl %ebx
movb %cl, (%ebx)
popl %ecx
popl %ebx
ret
#ifdef ENABLE_LZMA
#include "lzma_decode.S"
#endif
/*
* The code beyond this point is compressed. Assert that the uncompressed
* code fits GRUB_KERNEL_MACHINE_RAW_SIZE.
*/
. = _start + GRUB_KERNEL_MACHINE_RAW_SIZE
. = _start + GRUB_KERNEL_MACHINE_PREFIX
VARIABLE(grub_prefix)
/* to be filled by grub-mkimage */
/*
* Leave some breathing room for the prefix.
*/
. = _start + GRUB_KERNEL_MACHINE_PREFIX_END
/*
* grub_exit()
*
* Exit the system.
*/
FUNCTION(grub_exit)
call prot_to_real
.code16
/* Tell the BIOS a boot failure. If this does not work, reboot. */
int $0x18
jmp cold_reboot
.code32
/*
* void grub_chainloader_real_boot (int drive, void *part_addr)
*
* This starts another boot loader.
*/
FUNCTION(grub_chainloader_real_boot)
pushl %edx
pushl %eax
/* Turn off Gate A20 */
xorl %eax, %eax
call grub_gate_a20
/* set up to pass boot drive */
popl %edx
/* ESI must point to a partition table entry */
popl %esi
call prot_to_real
.code16
ljmp $0, $GRUB_MEMORY_MACHINE_BOOT_LOADER_ADDR
.code32
/*
* void grub_console_putchar (int c)
*
* Put the character C on the console. Because GRUB wants to write a
* character with an attribute, this implementation is a bit tricky.
* If C is a control character (CR, LF, BEL, BS), use INT 10, AH = 0Eh
* (TELETYPE OUTPUT). Otherwise, save the original position, put a space,
* save the current position, restore the original position, write the
* character and the attribute, and restore the current position.
*
* The reason why this is so complicated is that there is no easy way to
* get the height of the screen, and the TELETYPE OUTPUT BIOS call doesn't
* support setting a background attribute.
*/
FUNCTION(grub_console_putchar)
/* Retrieve the base character. */
movl 0(%edx), %edx
pusha
movb EXT_C(grub_console_cur_color), %bl
call prot_to_real
.code16
movb %dl, %al
xorb %bh, %bh
/* use teletype output if control character */
cmpb $0x7, %al
je 1f
cmpb $0x8, %al
je 1f
cmpb $0xa, %al
je 1f
cmpb $0xd, %al
je 1f
/* save the character and the attribute on the stack */
pushw %ax
pushw %bx
/* get the current position */
movb $0x3, %ah
int $0x10
/* check the column with the width */
cmpb $79, %dl
jl 2f
/* print CR and LF, if next write will exceed the width */
movw $0x0e0d, %ax
int $0x10
movb $0x0a, %al
int $0x10
/* get the current position */
movb $0x3, %ah
int $0x10
2:
/* restore the character and the attribute */
popw %bx
popw %ax
/* write the character with the attribute */
movb $0x9, %ah
movw $1, %cx
int $0x10
/* move the cursor forward */
incb %dl
movb $0x2, %ah
int $0x10
jmp 3f
1: movw $1, %bx
movb $0xe, %ah
int $0x10
3: DATA32 call real_to_prot
.code32
popa
ret
LOCAL(bypass_table):
.word 0x011b, 0x0f00 | '\t', 0x0e00 | '\b', 0x1c00 | '\r'
.word 0x1c00 | '\n'
LOCAL(bypass_table_end):
/*
* int grub_console_getkey (void)
* if there is a character pending, return it; otherwise return -1
* BIOS call "INT 16H Function 01H" to check whether a character is pending
* Call with %ah = 0x1
* Return:
* If key waiting to be input:
* %ah = keyboard scan code
* %al = ASCII character
* Zero flag = clear
* else
* Zero flag = set
* BIOS call "INT 16H Function 00H" to read character from keyboard
* Call with %ah = 0x0
* Return: %ah = keyboard scan code
* %al = ASCII character
*/
FUNCTION(grub_console_getkey)
pushl %ebp
pushl %edi
call prot_to_real
.code16
/*
* Due to a bug in apple's bootcamp implementation, INT 16/AH = 0 would
* cause the machine to hang at the second keystroke. However, we can
* work around this problem by ensuring the presence of keystroke with
* INT 16/AH = 1 before calling INT 16/AH = 0.
*/
movb $1, %ah
int $0x16
jz notpending
movb $0, %ah
int $0x16
xorl %edx, %edx
movw %ax, %dx /* real_to_prot uses %eax */
DATA32 call real_to_prot
.code32
movl $0xff, %eax
testl %eax, %edx
jz 1f
andl %edx, %eax
cmpl $0x20, %eax
jae 2f
movl %edx, %eax
leal LOCAL(bypass_table), %edi
movl $((LOCAL(bypass_table_end) - LOCAL(bypass_table)) / 2), %ecx
repne scasw
jz 3f
andl $0xff, %eax
addl $(('a' - 1) | GRUB_TERM_CTRL), %eax
jmp 2f
3:
andl $0xff, %eax
jmp 2f
1: movl %edx, %eax
shrl $8, %eax
orl $GRUB_TERM_EXTENDED, %eax
2:
popl %edi
popl %ebp
ret
notpending:
.code16
DATA32 call real_to_prot
.code32
#if GRUB_TERM_NO_KEY != 0
#error Fix this asm code
#endif
jmp 2b
/*
* grub_uint16_t grub_console_getxy (void)
* BIOS call "INT 10H Function 03h" to get cursor position
* Call with %ah = 0x03
* %bh = page
* Returns %ch = starting scan line
* %cl = ending scan line
* %dh = row (0 is top)
* %dl = column (0 is left)
*/
FUNCTION(grub_console_getxy)
pushl %ebp
pushl %ebx /* save EBX */
call prot_to_real
.code16
xorb %bh, %bh /* set page to 0 */
movb $0x3, %ah
int $0x10 /* get cursor position */
DATA32 call real_to_prot
.code32
movb %dl, %ah
movb %dh, %al
popl %ebx
popl %ebp
ret
/*
* void grub_console_gotoxy(grub_uint8_t x, grub_uint8_t y)
* BIOS call "INT 10H Function 02h" to set cursor position
* Call with %ah = 0x02
* %bh = page
* %dh = row (0 is top)
* %dl = column (0 is left)
*/
FUNCTION(grub_console_gotoxy)
pushl %ebp
pushl %ebx /* save EBX */
movb %cl, %dh /* %dh = y */
/* %dl = x */
call prot_to_real
.code16
xorb %bh, %bh /* set page to 0 */
movb $0x2, %ah
int $0x10 /* set cursor position */
DATA32 call real_to_prot
.code32
popl %ebx
popl %ebp
ret
/*
* void grub_console_cls (void)
* BIOS call "INT 10H Function 09h" to write character and attribute
* Call with %ah = 0x09
* %al = (character)
* %bh = (page number)
* %bl = (attribute)
* %cx = (number of times)
*/
FUNCTION(grub_console_cls)
pushl %ebp
pushl %ebx /* save EBX */
call prot_to_real
.code16
/* move the cursor to the beginning */
movb $0x02, %ah
xorb %bh, %bh
xorw %dx, %dx
int $0x10
/* write spaces to the entire screen */
movw $0x0920, %ax
movw $0x07, %bx
movw $(80 * 25), %cx
int $0x10
/* move back the cursor */
movb $0x02, %ah
int $0x10
DATA32 call real_to_prot
.code32
popl %ebx
popl %ebp
ret
/*
* void grub_console_setcursor (int on)
* BIOS call "INT 10H Function 01h" to set cursor type
* Call with %ah = 0x01
* %ch = cursor starting scanline
* %cl = cursor ending scanline
*/
console_cursor_state:
.byte 1
console_cursor_shape:
.word 0
FUNCTION(grub_console_setcursor)
pushl %ebp
pushl %ebx
/* push ON */
pushl %edx
/* check if the standard cursor shape has already been saved */
movw console_cursor_shape, %ax
testw %ax, %ax
jne 1f
call prot_to_real
.code16
movb $0x03, %ah
xorb %bh, %bh
int $0x10
DATA32 call real_to_prot
.code32
cmp %cl, %ch
jb 3f
movw $0x0d0e, %cx
3:
movw %cx, console_cursor_shape
1:
/* set %cx to the designated cursor shape */
movw $0x2000, %cx
popl %eax
testl %eax, %eax
jz 2f
movw console_cursor_shape, %cx
2:
call prot_to_real
.code16
movb $0x1, %ah
int $0x10
DATA32 call real_to_prot
.code32
popl %ebx
popl %ebp
ret
/*
* grub_get_rtc()
* return the real time in ticks, of which there are about
* 18-20 per second
*/
FUNCTION(grub_get_rtc)
pushl %ebp
call prot_to_real /* enter real mode */
.code16
/* %ax is already zero */
int $0x1a
DATA32 call real_to_prot
.code32
movl %ecx, %eax
shll $16, %eax
movw %dx, %ax
popl %ebp
ret
/*
* int grub_pxe_call (int func, void* data, grub_uint32_t pxe_rm_entry);
*/
FUNCTION(grub_pxe_call)
pushl %ebp
movl %esp, %ebp
pushl %esi
pushl %edi
pushl %ebx
movl %ecx, %ebx
movl %eax, %ecx
movl %edx, %eax
andl $0xF, %eax
shrl $4, %edx
shll $16, %edx
addl %eax, %edx
call prot_to_real
.code16
pushl %ebx
pushl %edx
pushw %cx
movw %sp, %bx
lcall *%ss:6(%bx)
cld
addw $10, %sp
movw %ax, %cx
DATA32 call real_to_prot
.code32
movzwl %cx, %eax
popl %ebx
popl %edi
popl %esi
popl %ebp
ret
FUNCTION(grub_bios_interrupt)
pushl %ebp
pushl %ecx
pushl %eax
pushl %ebx
pushl %esi
pushl %edi
pushl %edx
movb %al, intno
movl (%edx), %eax
movl %eax, LOCAL(bios_register_eax)
movw 4(%edx), %ax
movw %ax, LOCAL(bios_register_es)
movw 6(%edx), %ax
movw %ax, LOCAL(bios_register_ds)
movw 8(%edx), %ax
movw %ax, LOCAL(bios_register_flags)
movl 12(%edx), %ebx
movl 16(%edx), %ecx
movl 20(%edx), %edi
movl 24(%edx), %esi
movl 28(%edx), %edx
call prot_to_real
.code16
mov %ds, %ax
push %ax
/* movw imm16, %ax*/
.byte 0xb8
LOCAL(bios_register_es):
.short 0
movw %ax, %es
/* movw imm16, %ax*/
.byte 0xb8
LOCAL(bios_register_ds):
.short 0
movw %ax, %ds
/* movw imm16, %ax*/
.byte 0xb8
LOCAL(bios_register_flags):
.short 0
push %ax
popf
/* movl imm32, %eax*/
.byte 0x66, 0xb8
LOCAL(bios_register_eax):
.long 0
/* int imm8. */
.byte 0xcd
intno:
.byte 0
movl %eax, %cs:LOCAL(bios_register_eax)
movw %ds, %ax
movw %ax, %cs:LOCAL(bios_register_ds)
pop %ax
mov %ax, %ds
pushf
pop %ax
movw %ax, LOCAL(bios_register_flags)
mov %es, %ax
movw %ax, LOCAL(bios_register_es)
DATA32 call real_to_prot
.code32
popl %eax
movl %ebx, 12(%eax)
movl %ecx, 16(%eax)
movl %edi, 20(%eax)
movl %esi, 24(%eax)
movl %edx, 28(%eax)
movl %eax, %edx
movl LOCAL(bios_register_eax), %eax
movl %eax, (%edx)
movw LOCAL(bios_register_es), %ax
movw %ax, 4(%edx)
movw LOCAL(bios_register_ds), %ax
movw %ax, 6(%edx)
movw LOCAL(bios_register_flags), %ax
movw %ax, 8(%edx)
popl %edi
popl %esi
popl %ebx
popl %eax
popl %ecx
popl %ebp
ret