/* * 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 . */ /* * 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 #include #include #include #include #include #include #include #include #include #include #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 #ifdef APPLE_CC bss_start: .long 0 bss_end: .long 0 #endif /* * 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 .code16 /* 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 #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) /* * This is the area for all of the special variables. */ VARIABLE(grub_boot_drive) .byte 0 .p2align 2 /* force 4-byte alignment */ #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 /* * int grub_console_getkey (void) * BIOS call "INT 16H Function 00H" to read character from keyboard * Call with %ah = 0x0 * Return: %ah = keyboard scan code * %al = ASCII character */ /* this table is used in translate_keycode below */ LOCAL (translation_table): .word GRUB_CONSOLE_KEY_LEFT, GRUB_TERM_LEFT .word GRUB_CONSOLE_KEY_RIGHT, GRUB_TERM_RIGHT .word GRUB_CONSOLE_KEY_UP, GRUB_TERM_UP .word GRUB_CONSOLE_KEY_DOWN, GRUB_TERM_DOWN .word GRUB_CONSOLE_KEY_HOME, GRUB_TERM_HOME .word GRUB_CONSOLE_KEY_END, GRUB_TERM_END .word GRUB_CONSOLE_KEY_DC, GRUB_TERM_DC .word GRUB_CONSOLE_KEY_BACKSPACE, GRUB_TERM_BACKSPACE .word GRUB_CONSOLE_KEY_PPAGE, GRUB_TERM_PPAGE .word GRUB_CONSOLE_KEY_NPAGE, GRUB_TERM_NPAGE .word 0 /* * translate_keycode translates the key code %dx to an ascii code. */ .code16 translate_keycode: pushw %bx pushw %si #ifdef __APPLE__ movw $(ABS(LOCAL (translation_table)) - 0x10000), %si #else movw $ABS(LOCAL (translation_table)), %si #endif 1: lodsw /* check if this is the end */ testw %ax, %ax jz 2f /* load the ascii code into %ax */ movw %ax, %bx lodsw /* check if this matches the key code */ cmpw %bx, %dx jne 1b /* translate %dx, if successful */ movw %ax, %dx 2: popw %si popw %bx ret .code32 FUNCTION(grub_console_getkey) pushl %ebp 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. */ 1: movb $1, %ah int $0x16 jnz 2f hlt jmp 1b 2: movb $0, %ah int $0x16 movw %ax, %dx /* real_to_prot uses %eax */ call translate_keycode DATA32 call real_to_prot .code32 movw %dx, %ax popl %ebp ret /* * int grub_console_checkkey (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 */ FUNCTION(grub_console_checkkey) pushl %ebp xorl %edx, %edx call prot_to_real /* enter real mode */ .code16 movb $0x1, %ah int $0x16 jz notpending movw %ax, %dx DATA32 jmp pending notpending: decl %edx pending: DATA32 call real_to_prot .code32 movl %edx, %eax popl %ebp ret /* * 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 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