/* * GRUB -- GRand Unified Bootloader * Copyright (C) 1999,2000,2001,2002,2004 Free Software Foundation, Inc. * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * 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 } */ #define ASM_FILE #include "shared.h" #ifdef STAGE1_5 # define ABS(x) ((x) - EXT_C(main) + 0x2200) #else # define ABS(x) ((x) - EXT_C(main) + 0x8200) #endif .file "asm.S" .text /* Tell GAS to generate 16-bit instructions so that this code works in real mode. */ .code16 #ifndef STAGE1_5 /* * In stage2, do not link start.S with the rest of the source * files directly, so define the start symbols here just to * force ld quiet. These are not referred anyway. */ .globl start, _start start: _start: #endif /* ! STAGE1_5 */ ENTRY(main) /* * Guarantee that "main" is loaded at 0x0:0x8200 in stage2 and * at 0x0:0x2200 in stage1.5. */ ljmp $0, $ABS(codestart) /* * Compatibility version number * * These MUST be at byte offset 6 and 7 of the executable * DO NOT MOVE !!! */ . = EXT_C(main) + 0x6 .byte COMPAT_VERSION_MAJOR, COMPAT_VERSION_MINOR /* * This is a special data area 8 bytes from the beginning. */ . = EXT_C(main) + 0x8 VARIABLE(install_partition) .long 0xFFFFFF /* This variable is here only because of a historical reason. */ VARIABLE(saved_entryno) .long 0 VARIABLE(stage2_id) .byte STAGE2_ID VARIABLE(force_lba) .byte 0 VARIABLE(version_string) .string VERSION VARIABLE(config_file) #ifndef STAGE1_5 .string "/boot/grub/menu.lst" #else /* STAGE1_5 */ .long 0xffffffff .string "/boot/grub/stage2" #endif /* STAGE1_5 */ /* * Leave some breathing room for the config file name. */ . = EXT_C(main) + 0x70 /* the real mode code continues... */ 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 #ifndef SUPPORT_DISKLESS /* * Save the sector number of the second sector (i.e. this sector) * in INSTALL_SECOND_SECTOR. See also "stage2/start.S". */ ADDR32 movl %ebp, EXT_C(install_second_sector) #endif /* set up the real mode/BIOS stack */ movl $STACKOFF, %ebp movl %ebp, %esp sti /* we're safe again */ #ifndef SUPPORT_DISKLESS /* save boot drive reference */ ADDR32 movb %dl, EXT_C(boot_drive) /* reset disk system (%ah = 0) */ int $0x13 #endif /* transition to protected mode */ DATA32 call EXT_C(real_to_prot) /* The ".code32" directive takes GAS out of 16-bit mode. */ .code32 /* clean out the bss */ /* set %edi to the bss starting address */ #if defined(HAVE_USCORE_USCORE_BSS_START_SYMBOL) movl $__bss_start, %edi #elif defined(HAVE_USCORE_EDATA_SYMBOL) movl $_edata, %edi #elif defined(HAVE_EDATA_SYMBOL) movl $edata, %edi #endif /* set %ecx to the bss end */ #if defined(HAVE_END_SYMBOL) movl $end, %ecx #elif defined(HAVE_USCORE_END_SYMBOL) movl $_end, %ecx #endif /* compute the bss length */ subl %edi, %ecx /* zero %al */ xorb %al, %al /* set the direction */ cld /* clean out */ rep stosb /* * Call the start of main body of C code, which does some * of it's own initialization before transferring to "cmain". */ call EXT_C(init_bios_info) /* * This call is special... it never returns... in fact it should simply * hang at this point! */ ENTRY(stop) call EXT_C(prot_to_real) /* * This next part is sort of evil. It takes advantage of the * byte ordering on the x86 to work in either 16-bit or 32-bit * mode, so think about it before changing it. */ ENTRY(hard_stop) hlt jmp EXT_C(hard_stop) #ifndef STAGE1_5 /* * stop_floppy() * * Stops the floppy drive from spinning, so that other software is * jumped to with a known state. */ ENTRY(stop_floppy) pusha call EXT_C(prot_to_real) .code16 xorb %dl, %dl int $0x13 DATA32 call EXT_C(real_to_prot) .code32 popa ret /* * grub_reboot() * * Reboot the system. At the moment, rely on BIOS. */ ENTRY(grub_reboot) call EXT_C(prot_to_real) .code16 /* cold boot */ movw $0x0472, %di movw %ax, (%di) ljmp $0xFFFF, $0x0000 .code32 /* * grub_halt(int no_apm) * * Halt the system, using APM if possible. If NO_APM is true, don't use * APM even if it is available. */ ENTRY(grub_halt) /* get the argument */ movl 4(%esp), %eax /* see if zero */ testl %eax, %eax jnz EXT_C(stop) call EXT_C(prot_to_real) .code16 /* detect APM */ movw $0x5300, %ax xorw %bx, %bx int $0x15 jc EXT_C(hard_stop) /* don't check %bx for buggy BIOSes... */ /* disconnect APM first */ movw $0x5304, %ax xorw %bx, %bx int $0x15 /* connect APM */ movw $0x5301, %ax xorw %bx, %bx int $0x15 jc EXT_C(hard_stop) /* set APM protocol level - 1.1 or bust. (this covers APM 1.2 also) */ movw $0x530E, %ax xorw %bx, %bx movw $0x0101, %cx int $0x15 jc EXT_C(hard_stop) /* set the power state to off */ movw $0x5307, %ax movw $1, %bx movw $3, %cx int $0x15 /* shouldn't reach here */ jmp EXT_C(hard_stop) .code32 /* * track_int13(int drive) * * Track the int13 handler to probe I/O address space. */ ENTRY(track_int13) pushl %ebp movl %esp, %ebp pushl %ebx pushl %edi /* copy the original int13 handler segment:offset */ movl $0x4c, %edi movl (%edi), %eax movl %eax, track_int13_addr /* replace the int1 handler */ movl $0x4, %edi pushl (%edi) movl $ABS(int1_handler), %eax movl %eax, (%edi) /* read the MBR to call int13 successfully */ movb 8(%ebp), %dl call EXT_C(prot_to_real) .code16 movw $SCRATCHSEG, %ax movw %ax, %es xorw %bx, %bx movw $1, %cx xorb %dh, %dh /* save FLAGS on the stack to emulate int13 */ pushfw /* set the TF flag */ /* FIXME: this can be simplified not to use AX */ pushfw popw %ax orw $0x100, %ax pushw %ax popfw movw $0x0201, %ax .byte 0x9a /* lcall */ track_int13_addr: .word 0 /* offset */ .word 0 /* segment */ /* TF is cleared here automatically */ DATA32 call EXT_C(real_to_prot) .code32 /* restore the int1 handler */ movl $0x4, %edi popl (%edi) popl %edi popl %ebx popl %ebp ret /* * Check if the next instruction is I/O, and if this is true, add the * port into the io map. * * Note: Probably this will make the execution of int13 very slow. * * Note2: In this implementation, all we can know is I/O-mapped I/O. It * is impossible to detect memory-mapped I/O. */ int1_handler: .code16 pushw %bp movw %sp, %bp pushw %ds pushw %ax pushw %si pushw %dx /* IP */ movw 2(%bp), %si /* CS */ movw 4(%bp), %ax movw %ax, %ds /* examine the next instruction */ 1: lodsb (%si), %al /* skip this code if it is a prefix */ cmpb $0x2E, %al je 1b cmpb $0x36, %al je 1b cmpb $0x3E, %al je 1b cmpb $0x26, %al je 1b cmpb $0x64, %al jl 2f cmpb $0x67, %al jle 1b 2: cmpb $0xF0, %al jl 3f cmpb $0xF3, %al jle 1b 3: /* check if this code is out* or in* */ /* ins? or outs? */ cmpb $0x6C, %al jl 4f cmpb $0x6F, %al jle 5f 4: /* in? or out? (register operand version) */ cmpb $0xEC, %al jl 6f cmpb $0xEF, %al jle 5f 6: /* in? or out? (immediate operand version) */ cmpb $0xE4, %al jl 8f cmpb $0xE7, %al jg 8f 7: /* immediate has a port */ lodsb (%si), %al movzbw %al, %dx 5: /* %dx has a port */ /* set %ds to zero */ xorw %ax, %ax movw %ax, %ds /* set %si to the io map */ movw $ABS(EXT_C(io_map)), %si 9: /* check if the io map already has the port */ lodsw (%si), %ax /* check if this is the end */ testw %ax, %ax jz 1f /* check if this matches the port */ cmpw %ax, %dx jne 9b /* if so, leave from this handler */ jmp 8f 1: /* check for the buffer overrun */ cmpw $(ABS(EXT_C(io_map)) + (IO_MAP_SIZE + 1) * 2), %si je 8f /* add the port into the io map */ movw %dx, -2(%si) 8: /* restore registers */ popw %dx popw %si popw %ax popw %ds popw %bp iret .code32 ENTRY(io_map) .space (IO_MAP_SIZE + 1) * 2 /* * set_int15_handler(void) * * Set up int15_handler. */ ENTRY(set_int15_handler) pushl %edi /* save the original int15 handler */ movl $0x54, %edi movw (%edi), %ax movw %ax, ABS(int15_offset) movw 2(%edi), %ax movw %ax, ABS(int15_segment) /* save the new int15 handler */ movw $ABS(int15_handler), %ax movw %ax, (%edi) xorw %ax, %ax movw %ax, 2(%edi) popl %edi ret /* * unset_int15_handler(void) * * Restore the original int15 handler */ ENTRY(unset_int15_handler) pushl %edi /* check if int15_handler is set */ movl $0x54, %edi movw $ABS(int15_handler), %ax cmpw %ax, (%edi) jne 1f xorw %ax, %ax cmpw %ax, 2(%edi) jne 1f /* restore the original */ movw ABS(int15_offset), %ax movw %ax, (%edi) movw ABS(int15_segment), %ax movw %ax, 2(%edi) 1: popl %edi ret /* * Translate a key code to another. * * Note: This implementation cannot handle more than one length * scancodes (such as Right Ctrl). */ .code16 int15_handler: /* if non-carrier, ignore it */ jnc 1f /* check if AH=4F */ cmpb $0x4F, %ah jne 1f /* E0 and E1 are special */ cmpb $0xE1, %al je 4f cmpb $0xE0, %al /* this flag is actually the machine code (je or jmp) */ int15_skip_flag: je 4f pushw %bp movw %sp, %bp pushw %bx pushw %dx pushw %ds pushw %si /* save bits 0-6 of %al in %dl */ movw %ax, %dx andb $0x7f, %dl /* save the highest bit in %bl */ movb %al, %bl xorb %dl, %bl /* set %ds to 0 */ xorw %ax, %ax movw %ax, %ds /* set %si to the key map */ movw $ABS(EXT_C(bios_key_map)), %si /* find the key code from the key map */ 2: lodsw /* check if this is the end */ testw %ax, %ax jz 3f /* check if this matches the key code */ cmpb %al, %dl jne 2b /* if so, perform the mapping */ movb %ah, %dl 3: /* restore %ax */ movw %dx, %ax orb %bl, %al /* make sure that CF is set */ orw $1, 6(%bp) /* restore other registers */ popw %si popw %ds popw %dx popw %bx popw %bp iret 4: /* tricky: jmp (0x74) <-> je (0xeb) */ xorb $(0x74 ^ 0xeb), ABS(int15_skip_flag) 1: /* just cascade to the original */ /* ljmp */ .byte 0xea int15_offset: .word 0 int15_segment: .word 0 .code32 .align 4 ENTRY(bios_key_map) .space (KEY_MAP_SIZE + 1) * 2 /* * set_int13_handler(map) * * Copy MAP to the drive map and set up int13_handler. */ ENTRY(set_int13_handler) pushl %ebp movl %esp, %ebp pushl %edi pushl %esi /* copy MAP to the drive map */ movl $(DRIVE_MAP_SIZE * 2), %ecx movl $ABS(drive_map), %edi movl 8(%ebp), %esi cld rep movsb /* save the original int13 handler */ movl $0x4c, %edi movw (%edi), %ax movw %ax, ABS(int13_offset) movw 2(%edi), %ax movw %ax, ABS(int13_segment) /* decrease the lower memory size and set it to the BIOS memory */ movl $0x413, %edi decw (%edi) xorl %eax, %eax movw (%edi), %ax /* compute the segment */ shll $6, %eax /* save the new int13 handler */ movl $0x4c, %edi movw %ax, 2(%edi) xorw %cx, %cx movw %cx, (%edi) /* copy int13_handler to the reserved area */ shll $4, %eax movl %eax, %edi movl $ABS(int13_handler), %esi movl $(int13_handler_end - int13_handler), %ecx rep movsb popl %esi popl %edi popl %ebp ret /* * Map a drive to another drive. */ .code16 int13_handler: pushw %ax pushw %bp movw %sp, %bp pushw %si /* set %si to the drive map */ movw $(drive_map - int13_handler), %si /* find the drive number from the drive map */ cld 1: lodsw %cs:(%si), %ax /* check if this is the end */ testw %ax, %ax jz 2f /* check if this matches the drive number */ cmpb %al, %dl jne 1b /* if so, perform the mapping */ movb %ah, %dl 2: /* restore %si */ popw %si /* save %ax in the stack */ pushw %ax /* simulate the interrupt call */ pushw 8(%bp) /* set %ax and %bp to the original values */ movw 2(%bp), %ax movw (%bp), %bp /* lcall */ .byte 0x9a int13_offset: .word 0 int13_segment: .word 0 /* save flags */ pushf /* restore %bp */ movw %sp, %bp /* save %ax */ pushw %ax /* set the flags in the stack to the value returned by int13 */ movw (%bp), %ax movw %ax, 0xc(%bp) /* check if should map the drive number */ movw 6(%bp), %ax cmpw $0x8, %ax jne 3f cmpw $0x15, %ax jne 3f /* check if the mapping was performed */ movw 2(%bp), %ax testw %ax, %ax jz 3f /* perform the mapping */ movb %al, %dl 3: popw %ax movw 4(%bp), %bp addw $8, %sp iret .align 4 drive_map: .space (DRIVE_MAP_SIZE + 1) * 2 int13_handler_end: .code32 /* * chain_stage1(segment, offset, part_table_addr) * * This starts another stage1 loader, at segment:offset. */ ENTRY(chain_stage1) /* no need to save anything, just use %esp */ /* store %ESI, presuming %ES is 0 */ movl 0xc(%esp), %esi /* store new offset */ movl 0x8(%esp), %eax movl %eax, offset /* store new segment */ movw 0x4(%esp), %ax movw %ax, segment /* set up to pass boot drive */ movb EXT_C(boot_drive), %dl call EXT_C(prot_to_real) .code16 #ifdef ABSOLUTE_WITHOUT_ASTERISK DATA32 ADDR32 ljmp (offset) #else DATA32 ADDR32 ljmp *(offset) #endif .code32 #endif /* STAGE1_5 */ #ifdef STAGE1_5 /* * chain_stage2(segment, offset, second_sector) * * This starts another stage2 loader, at segment:offset. It presumes * that the other one starts with this same "asm.S" file, and passes * parameters by writing the embedded install variables. */ ENTRY(chain_stage2) /* no need to save anything, just use %esp */ /* store new offset */ movl 0x8(%esp), %eax movl %eax, offset movl %eax, %ebx /* store new segment */ movw 0x4(%esp), %ax movw %ax, segment shll $4, %eax /* generate linear address */ addl %eax, %ebx /* set up to pass the partition where stage2 is located in */ movl EXT_C(current_partition), %eax movl %eax, (EXT_C(install_partition)-EXT_C(main))(%ebx) /* set up to pass the drive where stage2 is located in */ movb EXT_C(current_drive), %dl /* set up to pass the second sector of stage2 */ movl 0xc(%esp), %ecx call EXT_C(prot_to_real) .code16 movl %ecx, %ebp #ifdef ABSOLUTE_WITHOUT_ASTERISK DATA32 ADDR32 ljmp (offset) #else DATA32 ADDR32 ljmp *(offset) #endif .code32 #endif /* STAGE1_5 */ /* * These next two routines, "real_to_prot" and "prot_to_real" are structured * in a very specific way. Be very careful when changing them. * * NOTE: Use of either one messes up %eax and %ebp. */ ENTRY(real_to_prot) .code16 cli /* load the GDT register */ DATA32 ADDR32 lgdt gdtdesc /* turn on protected mode */ movl %cr0, %eax orl $CR0_PE_ON, %eax movl %eax, %cr0 /* jump to relocation, flush prefetch queue, and reload %cs */ DATA32 ljmp $PROT_MODE_CSEG, $protcseg /* * The ".code32" directive only works in GAS, the GNU assembler! * This gets out of "16-bit" mode. */ .code32 protcseg: /* reload other segment registers */ movw $PROT_MODE_DSEG, %ax movw %ax, %ds movw %ax, %es movw %ax, %fs movw %ax, %gs movw %ax, %ss /* put the return address in a known safe location */ movl (%esp), %eax movl %eax, STACKOFF /* get protected mode stack */ movl protstack, %eax movl %eax, %esp movl %eax, %ebp /* get return address onto the right stack */ movl STACKOFF, %eax movl %eax, (%esp) /* zero %eax */ xorl %eax, %eax /* return on the old (or initialized) stack! */ ret ENTRY(prot_to_real) /* just in case, set GDT */ lgdt gdtdesc /* save the protected mode stack */ movl %esp, %eax movl %eax, protstack /* get the return address */ movl (%esp), %eax movl %eax, STACKOFF /* set up new stack */ movl $STACKOFF, %eax movl %eax, %esp movl %eax, %ebp /* set up segment limits */ movw $PSEUDO_RM_DSEG, %ax movw %ax, %ds movw %ax, %es movw %ax, %fs movw %ax, %gs movw %ax, %ss /* this might be an extra step */ ljmp $PSEUDO_RM_CSEG, $tmpcseg /* jump to a 16 bit segment */ tmpcseg: .code16 /* clear the PE bit of CR0 */ movl %cr0, %eax andl $CR0_PE_OFF, %eax movl %eax, %cr0 /* flush prefetch queue, reload %cs */ DATA32 ljmp $0, $realcseg realcseg: /* we are in real mode now * set up the real mode segment registers : DS, SS, ES */ /* zero %eax */ xorl %eax, %eax movw %ax, %ds movw %ax, %es movw %ax, %fs movw %ax, %gs movw %ax, %ss /* restore interrupts */ sti /* return on new stack! */ DATA32 ret .code32 /* * int biosdisk_int13_extensions (int ax, int drive, void *dap) * * Call IBM/MS INT13 Extensions (int 13 %ax=AX) for DRIVE. DAP * is passed for disk address packet. If an error occurs, return * non-zero, otherwise zero. */ ENTRY(biosdisk_int13_extensions) pushl %ebp movl %esp, %ebp pushl %esi pushl %ebx /* compute the address of disk_address_packet */ movl 0x10(%ebp), %eax movw %ax, %si xorw %ax, %ax shrl $4, %eax movw %ax, %cx /* save the segment to cx */ /* drive */ movb 0xc(%ebp), %dl /* ax */ movw 0x8(%ebp), %bx /* enter real mode */ call EXT_C(prot_to_real) .code16 movw %bx, %ax movw %cx, %ds int $0x13 /* do the operation */ movb %ah, %dl /* save return value */ /* clear the data segment */ xorw %ax, %ax movw %ax, %ds /* back to protected mode */ DATA32 call EXT_C(real_to_prot) .code32 movb %dl, %al /* return value in %eax */ popl %ebx popl %esi popl %ebp ret /* * int biosdisk_standard (int ah, int drive, int coff, int hoff, int soff, * int nsec, int segment) * * Call standard and old INT13 (int 13 %ah=AH) for DRIVE. Read/write * NSEC sectors from COFF/HOFF/SOFF into SEGMENT. If an error occurs, * return non-zero, otherwise zero. */ ENTRY(biosdisk_standard) pushl %ebp movl %esp, %ebp pushl %ebx pushl %edi pushl %esi /* set up CHS information */ movl 0x10(%ebp), %eax movb %al, %ch movb 0x18(%ebp), %al shlb $2, %al shrw $2, %ax movb %al, %cl movb 0x14(%ebp), %dh /* drive */ movb 0xc(%ebp), %dl /* segment */ movw 0x20(%ebp), %bx /* save nsec and ah to %di */ movb 0x8(%ebp), %ah movb 0x1c(%ebp), %al movw %ax, %di /* enter real mode */ call EXT_C(prot_to_real) .code16 movw %bx, %es xorw %bx, %bx movw $3, %si /* attempt at least three times */ 1: movw %di, %ax int $0x13 /* do the operation */ jnc 2f /* check if successful */ movb %ah, %bl /* save return value */ /* if fail, reset the disk system */ xorw %ax, %ax int $0x13 decw %si cmpw $0, %si je 2f xorb %bl, %bl jmp 1b /* retry */ 2: /* back to protected mode */ DATA32 call EXT_C(real_to_prot) .code32 movb %bl, %al /* return value in %eax */ popl %esi popl %edi popl %ebx popl %ebp ret /* * int check_int13_extensions (int drive) * * Check if LBA is supported for DRIVE. If it is supported, then return * the major version of extensions, otherwise zero. */ ENTRY(check_int13_extensions) pushl %ebp movl %esp, %ebp pushl %ebx /* drive */ movb 0x8(%ebp), %dl /* enter real mode */ call EXT_C(prot_to_real) .code16 movb $0x41, %ah movw $0x55aa, %bx int $0x13 /* do the operation */ /* check the result */ jc 1f cmpw $0xaa55, %bx jne 1f movb %ah, %bl /* save the major version into %bl */ /* check if AH=0x42 is supported if FORCE_LBA is zero */ movb EXT_C(force_lba), %al testb %al, %al jnz 2f andw $1, %cx jnz 2f 1: xorb %bl, %bl 2: /* back to protected mode */ DATA32 call EXT_C(real_to_prot) .code32 movb %bl, %al /* return value in %eax */ popl %ebx popl %ebp ret /* * int get_diskinfo_standard (int drive, unsigned long *cylinders, * unsigned long *heads, unsigned long *sectors) * * Return the geometry of DRIVE in CYLINDERS, HEADS and SECTORS. If an * error occurs, then return non-zero, otherwise zero. */ ENTRY(get_diskinfo_standard) pushl %ebp movl %esp, %ebp pushl %ebx pushl %edi /* drive */ movb 0x8(%ebp), %dl /* enter real mode */ call EXT_C(prot_to_real) .code16 movb $0x8, %ah int $0x13 /* do the operation */ /* check if successful */ testb %ah, %ah jnz 1f /* bogus BIOSes may not return an error number */ testb $0x3f, %cl /* 0 sectors means no disk */ jnz 1f /* if non-zero, then succeed */ /* XXX 0x60 is one of the unused error numbers */ movb $0x60, %ah 1: movb %ah, %bl /* save return value in %bl */ /* back to protected mode */ DATA32 call EXT_C(real_to_prot) .code32 /* restore %ebp */ leal 0x8(%esp), %ebp /* heads */ movb %dh, %al incl %eax /* the number of heads is counted from zero */ movl 0x10(%ebp), %edi movl %eax, (%edi) /* sectors */ xorl %eax, %eax movb %cl, %al andb $0x3f, %al movl 0x14(%ebp), %edi movl %eax, (%edi) /* cylinders */ shrb $6, %cl movb %cl, %ah movb %ch, %al incl %eax /* the number of cylinders is counted from zero */ movl 0xc(%ebp), %edi movl %eax, (%edi) xorl %eax, %eax movb %bl, %al /* return value in %eax */ popl %edi popl %ebx popl %ebp ret #if 0 /* * int get_diskinfo_floppy (int drive, unsigned long *cylinders, * unsigned long *heads, unsigned long *sectors) * * Return the geometry of DRIVE in CYLINDERS, HEADS and SECTORS. If an * error occurs, then return non-zero, otherwise zero. */ ENTRY(get_diskinfo_floppy) pushl %ebp movl %esp, %ebp pushl %ebx pushl %esi /* drive */ movb 0x8(%ebp), %dl /* enter real mode */ call EXT_C(prot_to_real) .code16 /* init probe value */ movl $probe_values-1, %esi 1: xorw %ax, %ax int $0x13 /* reset floppy controller */ incw %si movb (%si), %cl cmpb $0, %cl /* probe failed if zero */ je 2f /* perform read */ movw $SCRATCHSEG, %ax movw %ax, %es xorw %bx, %bx movw $0x0201, %ax movb $0, %ch movb $0, %dh int $0x13 /* FIXME: Read from floppy may fail even if the geometry is correct. So should retry at least three times. */ jc 1b /* next value */ /* succeed */ jmp 2f probe_values: .byte 36, 18, 15, 9, 0 2: /* back to protected mode */ DATA32 call EXT_C(real_to_prot) .code32 /* restore %ebp */ leal 0x8(%esp), %ebp /* cylinders */ movl 0xc(%ebp), %eax movl $80, %ebx movl %ebx, (%eax) /* heads */ movl 0x10(%ebp), %eax movl $2, %ebx movl %ebx, (%eax) /* sectors */ movl 0x14(%ebp), %eax movzbl %cl, %ebx movl %ebx, (%eax) /* return value in %eax */ xorl %eax, %eax cmpb $0, %cl jne 3f incl %eax /* %eax = 1 (non-zero) */ 3: popl %esi popl %ebx popl %ebp ret #endif /* Source files are splitted, as they have different copyrights. */ #ifndef STAGE1_5 # include "setjmp.S" # include "apm.S" #endif /* ! STAGE1_5 */ #ifndef STAGE1_5 /* get_code_end() : return the address of the end of the code * This is here so that it can be replaced by asmstub.c. */ ENTRY(get_code_end) /* will be the end of the bss */ # if defined(HAVE_END_SYMBOL) movl $end, %eax # elif defined(HAVE_USCORE_END_SYMBOL) movl $_end, %eax # endif shrl $2, %eax /* Round up to the next word. */ incl %eax shll $2, %eax ret #endif /* ! STAGE1_5 */ /* * * get_memsize(i) : return the memory size in KB. i == 0 for conventional * memory, i == 1 for extended memory * BIOS call "INT 12H" to get conventional memory size * BIOS call "INT 15H, AH=88H" to get extended memory size * Both have the return value in AX. * */ ENTRY(get_memsize) push %ebp push %ebx mov 0xc(%esp), %ebx call EXT_C(prot_to_real) /* enter real mode */ .code16 cmpb $0x1, %bl DATA32 je xext int $0x12 DATA32 jmp xdone xext: movb $0x88, %ah int $0x15 xdone: movw %ax, %bx DATA32 call EXT_C(real_to_prot) .code32 movw %bx, %ax pop %ebx pop %ebp ret #ifndef STAGE1_5 /* * * get_eisamemsize() : return packed EISA memory map, lower 16 bits is * memory between 1M and 16M in 1K parts, upper 16 bits is * memory above 16M in 64K parts. If error, return -1. * BIOS call "INT 15H, AH=E801H" to get EISA memory map, * AX = memory between 1M and 16M in 1K parts. * BX = memory above 16M in 64K parts. * */ ENTRY(get_eisamemsize) push %ebp push %ebx call EXT_C(prot_to_real) /* enter real mode */ .code16 movw $0xe801, %ax int $0x15 shll $16, %ebx movw %ax, %bx DATA32 call EXT_C(real_to_prot) .code32 movl $0xFFFFFFFF, %eax cmpb $0x86, %bh je xnoteisa movl %ebx, %eax xnoteisa: pop %ebx pop %ebp ret /* * * get_mmap_entry(addr, cont) : address and old continuation value (zero to * start), for the Query System Address Map BIOS call. * * Sets the first 4-byte int value of "addr" to the size returned by * the call. If the call fails, sets it to zero. * * Returns: new (non-zero) continuation value, 0 if done. * * NOTE: Currently hard-coded for a maximum buffer length of 1024. */ ENTRY(get_mmap_entry) push %ebp push %ebx push %edi push %esi /* place address (+4) in ES:DI */ movl 0x14(%esp), %eax addl $4, %eax movl %eax, %edi andl $0xf, %edi shrl $4, %eax movl %eax, %esi /* set continuation value */ movl 0x18(%esp), %ebx /* set default maximum buffer size */ movl $0x14, %ecx /* set EDX to 'SMAP' */ movl $0x534d4150, %edx call EXT_C(prot_to_real) /* enter real mode */ .code16 movw %si, %es movl $0xe820, %eax int $0x15 DATA32 jc xnosmap cmpl $0x534d4150, %eax DATA32 jne xnosmap cmpl $0x14, %ecx DATA32 jl xnosmap cmpl $0x400, %ecx DATA32 jg xnosmap DATA32 jmp xsmap xnosmap: movl $0, %ecx xsmap: DATA32 call EXT_C(real_to_prot) .code32 /* write length of buffer (zero if error) into "addr" */ movl 0x14(%esp), %eax movl %ecx, (%eax) /* set return value to continuation */ movl %ebx, %eax pop %esi pop %edi pop %ebx pop %ebp ret /* * get_rom_config_table() * * Get the linear address of a ROM configuration table. Return zero, * if fails. */ ENTRY(get_rom_config_table) pushl %ebp pushl %ebx /* zero %ebx for simplicity */ xorl %ebx, %ebx call EXT_C(prot_to_real) .code16 movw $0xc0, %ax int $0x15 jc no_rom_table testb %ah, %ah jnz no_rom_table movw %es, %dx jmp found_rom_table no_rom_table: xorw %dx, %dx xorw %bx, %bx found_rom_table: DATA32 call EXT_C(real_to_prot) .code32 /* compute the linear address */ movw %dx, %ax shll $4, %eax addl %ebx, %eax popl %ebx popl %ebp ret /* * int get_vbe_controller_info (struct vbe_controller *controller_ptr) * * Get VBE controller information. */ ENTRY(get_vbe_controller_info) pushl %ebp movl %esp, %ebp pushl %edi pushl %ebx /* Convert the linear address to segment:offset */ movl 8(%ebp), %eax movl %eax, %edi andl $0x0000000f, %edi shrl $4, %eax movl %eax, %ebx call EXT_C(prot_to_real) .code16 movw %bx, %es movw $0x4F00, %ax int $0x10 movw %ax, %bx DATA32 call EXT_C(real_to_prot) .code32 movzwl %bx, %eax popl %ebx popl %edi popl %ebp ret /* * int get_vbe_mode_info (int mode_number, struct vbe_mode *mode_ptr) * * Get VBE mode information. */ ENTRY(get_vbe_mode_info) pushl %ebp movl %esp, %ebp pushl %edi pushl %ebx /* Convert the linear address to segment:offset */ movl 0xc(%ebp), %eax movl %eax, %edi andl $0x0000000f, %edi shrl $4, %eax movl %eax, %ebx /* Save the mode number in %cx */ movl 0x8(%ebp), %ecx call EXT_C(prot_to_real) .code16 movw %bx, %es movw $0x4F01, %ax int $0x10 movw %ax, %bx DATA32 call EXT_C(real_to_prot) .code32 movzwl %bx, %eax popl %ebx popl %edi popl %ebp ret /* * int set_vbe_mode (int mode_number) * * Set VBE mode. Don't support user-specified CRTC information. */ ENTRY(set_vbe_mode) pushl %ebp movl %esp, %ebp pushl %ebx /* Save the mode number in %bx */ movl 0x8(%ebp), %ebx /* Clear bit D11 */ andl $0xF7FF, %ebx call EXT_C(prot_to_real) .code16 movw $0x4F02, %ax int $0x10 movw %ax, %bx DATA32 call EXT_C(real_to_prot) .code32 movzwl %bx, %eax popl %ebx popl %ebp ret /* * gateA20(int linear) * * 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. :-( */ ENTRY(gateA20) /* first, try a BIOS call */ pushl %ebp movl 8(%esp), %edx call EXT_C(prot_to_real) .code16 movw $0x2400, %ax testw %dx, %dx jz 1f incw %ax 1: stc int $0x15 jnc 2f /* set non-zero if failed */ movb $1, %ah /* save the status */ 2: movb %ah, %dl DATA32 call EXT_C(real_to_prot) .code32 popl %ebp testb %dl, %dl jnz 3f ret 3: /* use keyboard controller */ pushl %eax call gloop1 movb $KC_CMD_WOUT, %al outb $K_CMD gloopint1: inb $K_STATUS andb $K_IBUF_FUL, %al jnz gloopint1 movb $KB_OUTPUT_MASK, %al cmpb $0, 0x8(%esp) jz gdoit orb $KB_A20_ENABLE, %al gdoit: outb $K_RDWR call gloop1 /* output a dummy command (USB keyboard hack) */ movb $0xff, %al outb $K_CMD call gloop1 popl %eax ret gloop1: inb $K_STATUS andb $K_IBUF_FUL, %al jnz gloop1 gloop2: inb $K_STATUS andb $K_OBUF_FUL, %al jz gloop2ret inb $K_RDWR jmp gloop2 gloop2ret: ret ENTRY(patch_code) /* labels start with "pc_" */ .code16 mov %cs, %ax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs ADDR32 movl $0, 0 pc_stop: hlt DATA32 jmp pc_stop ENTRY(patch_code_end) .code32 /* * linux_boot() * * Does some funky things (including on the stack!), then jumps to the * entry point of the Linux setup code. */ VARIABLE(linux_text_len) .long 0 VARIABLE(linux_data_tmp_addr) .long 0 VARIABLE(linux_data_real_addr) .long 0 ENTRY(linux_boot) /* don't worry about saving anything, we're committed at this point */ cld /* forward copying */ /* copy kernel */ movl EXT_C(linux_text_len), %ecx addl $3, %ecx shrl $2, %ecx movl $LINUX_BZIMAGE_ADDR, %esi movl $LINUX_ZIMAGE_ADDR, %edi rep movsl ENTRY(big_linux_boot) movl EXT_C(linux_data_real_addr), %ebx /* copy the real mode part */ movl EXT_C(linux_data_tmp_addr), %esi movl %ebx, %edi movl $LINUX_SETUP_MOVE_SIZE, %ecx cld rep movsb /* change %ebx to the segment address */ shrl $4, %ebx movl %ebx, %eax addl $0x20, %eax movl %eax, linux_setup_seg /* XXX new stack pointer in safe area for calling functions */ movl $0x4000, %esp call EXT_C(stop_floppy) /* final setup for linux boot */ call EXT_C(prot_to_real) .code16 /* final setup for linux boot */ cli movw %bx, %ss movw $LINUX_SETUP_STACK, %sp movw %bx, %ds movw %bx, %es movw %bx, %fs movw %bx, %gs /* jump to start */ /* ljmp */ .byte 0xea .word 0 linux_setup_seg: .word 0 .code32 /* * multi_boot(int start, int mb_info) * * This starts a kernel in the manner expected of the multiboot standard. */ ENTRY(multi_boot) /* no need to save anything */ call EXT_C(stop_floppy) movl $0x2BADB002, %eax movl 0x8(%esp), %ebx /* boot kernel here (absolute address call) */ call *0x4(%esp) /* error */ call EXT_C(stop) #endif /* ! STAGE1_5 */ /* * void 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 OUPUT BIOS call doesn't * support setting a background attribute. */ ENTRY(console_putchar) movl 0x4(%esp), %edx pusha #ifdef STAGE1_5 movb $0x07, %bl #else movl EXT_C(console_current_color), %ebx #endif call EXT_C(prot_to_real) .code16 movb %dl, %al xorb %bh, %bh #ifndef STAGE1_5 /* 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 #endif /* ! STAGE1_5 */ 1: movb $0xe, %ah int $0x10 3: DATA32 call EXT_C(real_to_prot) .code32 popa ret #ifndef STAGE1_5 /* this table is used in translate_keycode below */ translation_table: .word KEY_LEFT, 2 .word KEY_RIGHT, 6 .word KEY_UP, 16 .word KEY_DOWN, 14 .word KEY_HOME, 1 .word KEY_END, 5 .word KEY_DC, 4 .word KEY_BACKSPACE, 8 .word KEY_PPAGE, 7 .word KEY_NPAGE, 3 .word 0 /* * translate_keycode translates the key code %dx to an ascii code. */ .code16 translate_keycode: pushw %bx pushw %si movw $ABS(translation_table), %si 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 /* * remap_ascii_char remaps the ascii code %dl to another if the code is * contained in ASCII_KEY_MAP. */ .code16 remap_ascii_char: pushw %si movw $ABS(EXT_C(ascii_key_map)), %si 1: lodsw /* check if this is the end */ testw %ax, %ax jz 2f /* check if this matches the ascii code */ cmpb %al, %dl jne 1b /* if so, perform the mapping */ movb %ah, %dl 2: /* restore %si */ popw %si ret .code32 .align 4 ENTRY(ascii_key_map) .space (KEY_MAP_SIZE + 1) * 2 /* * int 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 */ ENTRY(console_getkey) push %ebp call EXT_C(prot_to_real) .code16 int $0x16 movw %ax, %dx /* real_to_prot uses %eax */ call translate_keycode call remap_ascii_char DATA32 call EXT_C(real_to_prot) .code32 movw %dx, %ax pop %ebp ret /* * int 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 */ ENTRY(console_checkkey) push %ebp xorl %edx, %edx call EXT_C(prot_to_real) /* enter real mode */ .code16 movb $0x1, %ah int $0x16 DATA32 jz notpending movw %ax, %dx call translate_keycode call remap_ascii_char DATA32 jmp pending notpending: movl $0xFFFFFFFF, %edx pending: DATA32 call EXT_C(real_to_prot) .code32 mov %edx, %eax pop %ebp ret /* * int 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) */ ENTRY(console_getxy) push %ebp push %ebx /* save EBX */ call EXT_C(prot_to_real) .code16 xorb %bh, %bh /* set page to 0 */ movb $0x3, %ah int $0x10 /* get cursor position */ DATA32 call EXT_C(real_to_prot) .code32 movb %dl, %ah movb %dh, %al pop %ebx pop %ebp ret /* * void console_gotoxy(int x, int 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) */ ENTRY(console_gotoxy) push %ebp push %ebx /* save EBX */ movb 0xc(%esp), %dl /* %dl = x */ movb 0x10(%esp), %dh /* %dh = y */ call EXT_C(prot_to_real) .code16 xorb %bh, %bh /* set page to 0 */ movb $0x2, %ah int $0x10 /* set cursor position */ DATA32 call EXT_C(real_to_prot) .code32 pop %ebx pop %ebp ret /* * void 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) */ ENTRY(console_cls) push %ebp push %ebx /* save EBX */ call EXT_C(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 EXT_C(real_to_prot) .code32 pop %ebx pop %ebp ret /* * int 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 ENTRY(console_setcursor) push %ebp push %ebx /* check if the standard cursor shape has already been saved */ movw console_cursor_shape, %ax testw %ax, %ax jne 1f call EXT_C(prot_to_real) .code16 movb $0x03, %ah xorb %bh, %bh int $0x10 DATA32 call EXT_C(real_to_prot) .code32 movw %cx, console_cursor_shape 1: /* set %cx to the designated cursor shape */ movw $0x2000, %cx movl 0xc(%esp), %ebx testl %ebx, %ebx jz 2f movw console_cursor_shape, %cx 2: call EXT_C(prot_to_real) .code16 movb $0x1, %ah int $0x10 DATA32 call EXT_C(real_to_prot) .code32 movzbl console_cursor_state, %eax movb %bl, console_cursor_state pop %ebx pop %ebp ret /* * getrtsecs() * if a seconds value can be read, read it and return it (BCD), * otherwise return 0xFF * BIOS call "INT 1AH Function 02H" to check whether a character is pending * Call with %ah = 0x2 * Return: * If RT Clock can give correct values * %ch = hour (BCD) * %cl = minutes (BCD) * %dh = seconds (BCD) * %dl = daylight savings time (00h std, 01h daylight) * Carry flag = clear * else * Carry flag = set * (this indicates that the clock is updating, or * that it isn't running) */ ENTRY(getrtsecs) push %ebp call EXT_C(prot_to_real) /* enter real mode */ .code16 movb $0x2, %ah int $0x1a DATA32 jnc gottime movb $0xff, %dh gottime: DATA32 call EXT_C(real_to_prot) .code32 movb %dh, %al pop %ebp ret /* * currticks() * return the real time in ticks, of which there are about * 18-20 per second */ ENTRY(currticks) pushl %ebp call EXT_C(prot_to_real) /* enter real mode */ .code16 /* %ax is already zero */ int $0x1a DATA32 call EXT_C(real_to_prot) .code32 movl %ecx, %eax shll $16, %eax movw %dx, %ax popl %ebp ret #endif /* STAGE1_5 */ /* * This is the area for all of the special variables. */ .p2align 2 /* force 4-byte alignment */ protstack: .long PROTSTACKINIT VARIABLE(boot_drive) #ifdef SUPPORT_DISKLESS .long NETWORK_DRIVE #else .long 0 #endif VARIABLE(install_second_sector) .long 0 /* an address can only be long-jumped to if it is in memory, this is used by multiple routines */ offset: .long 0x8000 segment: .word 0 VARIABLE(apm_bios_info) .word 0 /* version */ .word 0 /* cseg */ .long 0 /* offset */ .word 0 /* cseg_16 */ .word 0 /* dseg_16 */ .word 0 /* cseg_len */ .word 0 /* cseg_16_len */ .word 0 /* dseg_16_len */ /* * This is the Global Descriptor Table * * An entry, a "Segment Descriptor", looks like this: * * 31 24 19 16 7 0 * ------------------------------------------------------------ * | | |B| |A| | | |1|0|E|W|A| | * | BASE 31..24 |G|/|0|V| LIMIT |P|DPL| TYPE | BASE 23:16 | * | | |D| |L| 19..16| | |1|1|C|R|A| | * ------------------------------------------------------------ * | | | * | BASE 15..0 | LIMIT 15..0 | * | | | * ------------------------------------------------------------ * * Note the ordering of the data items is reversed from the above * description. */ .p2align 2 /* force 4-byte alignment */ gdt: .word 0, 0 .byte 0, 0, 0, 0 /* code segment */ .word 0xFFFF, 0 .byte 0, 0x9A, 0xCF, 0 /* data segment */ .word 0xFFFF, 0 .byte 0, 0x92, 0xCF, 0 /* 16 bit real mode CS */ .word 0xFFFF, 0 .byte 0, 0x9E, 0, 0 /* 16 bit real mode DS */ .word 0xFFFF, 0 .byte 0, 0x92, 0, 0 /* this is the GDT descriptor */ gdtdesc: .word 0x27 /* limit */ .long gdt /* addr */