blob: ec46b250b4a52bfc6e3e3b5caf17ecee8aa87cc3 [file] [log] [blame] [raw]
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 1996 Erich Boleyn <erich@uruk.org>
* Copyright (C) 1999 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.
*/
#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 0x020000
VARIABLE(stage2_id)
.byte STAGE2_ID
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
/* set up the real mode/BIOS stack */
movl $STACKOFF, %ebp
movl %ebp, %esp
sti /* we're safe again */
/* save boot drive reference */
ADDR32 movb %dl, EXT_C(boot_drive)
/* reset disk system (%ah = 0) */
int $0x13
/* 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)
movw $0x3F2, %dx
xorb %al, %al
outb %al, %dx
ret
/*
* track_int13(int drive)
*
* Track the int13 handler to probe I/O address space.
*/
ENTRY(track_int13)
pushl %ebp
movl %esp, %ebp
pushl %edx
pushl %ebx
pushl %edi
/* save the original int13 handler */
movl $0x4c, %edi
movw (%edi), %ax
movw %ax, ABS(set_tf_int13_offset)
movw 2(%edi), %ax
movw %ax, ABS(set_tf_int13_segment)
/* save the new int13 handler */
movl $ABS(set_tf_int13_handler), %eax
movl %eax, (%edi)
/* 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
movw $0x201, %ax
int $0x13
DATA32 call EXT_C(real_to_prot)
.code32
/* restore the int1 handler */
movl $0x4, %edi
popl (%edi)
/* restore the int13 handler */
movl $0x4c, %edi
movw ABS(set_tf_int13_offset), %ax
movw %ax, (%edi)
movw ABS(set_tf_int13_segment), %ax
movw %ax, 2(%edi)
popl %edi
popl %ebx
popl %edx
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 */
xorb %ah, %ah
lodsb (%si), %al
movw %ax, %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
/*
* Just set the TF flag. This handler is necessary because any interrupt
* call clears the flag automatically.
*
* Note: we need not to clear this flag after the tracking explicitly,
* because iret restores the original FLAGS.
*/
set_tf_int13_handler:
/* save %ax int the stack */
pushw %ax
/* set the TF flag */
pushf
popw %ax
orw $0x100, %ax
pushw %ax
popf
/* restore %ax */
popw %ax
/* simulate the interrupt call */
pushf
/* lcall */
.byte 0x9a
set_tf_int13_offset: .word 0
set_tf_int13_segment: .word 0
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 %ecx
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)
/* get the lower memory size */
movl $EXT_C(mbi), %edi
movl 4(%edi), %eax
/* decrease the lower memory size and set it to the BIOS memory */
decl %eax
movl $0x413, %edi
movw %ax, (%edi)
/* 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 %ecx
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
DATA32 ADDR32 ljmp (offset)
.code32
#endif /* STAGE1_5 */
#ifdef STAGE1_5
/*
* chain_stage2(segment, offset)
*
* 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
call EXT_C(prot_to_real)
.code16
DATA32 ADDR32 ljmp (offset)
.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 ah, int drive, void *dap)
*
* Call IBM/MS INT13 Extensions (int 13 %ah=AH) 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 %ecx
pushl %edx
pushl %esi
/* 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
/* ah */
movb 0x8(%ebp), %dh
/* enter real mode */
call EXT_C(prot_to_real)
.code16
movb %dh, %ah
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 %esi
popl %edx
popl %ecx
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 %ecx
pushl %edx
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 %edx
popl %ecx
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
pushl %ecx
pushl %edx
/* 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
andw $1, %cx
jz 1f
movb %ah, %bl /* save the major version into %bl */
jmp 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 %edx
popl %ecx
popl %ebx
popl %ebp
ret
/*
* int get_diskinfo_int13_extensions (int drive, void *drp)
*
* Return the geometry of DRIVE in a drive parameters, DRP. If an error
* occurs, then return non-zero, otherwise zero.
*/
ENTRY(get_diskinfo_int13_extensions)
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %edx
pushl %esi
/* compute the address of drive parameters */
movl 0xc(%ebp), %eax
movw %ax, %si
xorw %ax, %ax
shrl $4, %eax
movw %ax, %bx /* save the segment into %bx */
/* drive */
movb 0x8(%ebp), %dl
/* enter real mode */
call EXT_C(prot_to_real)
.code16
movb $0x48, %ah
movw %bx, %ds
int $0x13 /* do the operation */
movb %ah, %bl /* save return value in %bl */
/* clear the data segment */
xorw %ax, %ax
movw %ax, %ds
/* back to protected mode */
DATA32 call EXT_C(real_to_prot)
.code32
movb %bl, %al /* return value in %eax */
popl %esi
popl %edx
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 %ecx
pushl %edx
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 0x10(%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 %edx
popl %ecx
popl %ebx
popl %ebp
ret
/*
* 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 %ecx
pushl %edx
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 0x10(%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 %edx
popl %ecx
popl %ebx
popl %ebp
ret
/*
* putchar(c) : Puts character on the screen, interpreting '\n' as in the
* UNIX fashion.
*
* BIOS call "INT 10H Function 0Eh" to write character to console
* Call with %ah = 0x0e
* %al = character
* %bh = page
* %bl = foreground color ( graphics modes)
*/
ENTRY(grub_putchar)
push %ebp
push %eax
push %ebx
movb 0x10(%esp), %bl
/* if not '\n', just print the character */
cmpb $0xa, %bl
jne pc_notnewline
/* if newline, print CR as well */
pushl $0xd
call EXT_C(grub_putchar)
popl %eax
pc_notnewline:
call EXT_C(prot_to_real)
.code16
movb %bl, %al
movb $0xe, %ah
movw $1, %bx
int $0x10
DATA32 call EXT_C(real_to_prot)
.code32
pop %ebx
pop %eax
pop %ebp
ret
#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
push %ecx
push %edx
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 %edx
pop %ecx
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 %ecx
push %edx
push %edi
push %esi
/* place address (+4) in ES:DI */
movl 0x1c(%esp), %eax
addl $4, %eax
movl %eax, %edi
andl $0xf, %edi
shrl $4, %eax
movl %eax, %esi
/* set continuation value */
movl 0x20(%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 0x1c(%esp), %eax
movl %ecx, (%eax)
/* set return value to continuation */
movl %ebx, %eax
pop %esi
pop %edi
pop %edx
pop %ecx
pop %ebx
pop %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)
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
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.
*/
ENTRY(linux_boot)
/* don't worry about saving anything, we're committed at this point */
cld /* forward copying */
/* copy kernel */
movl $LINUX_SETUP, %eax
movl LINUX_KERNEL_LEN_OFFSET(%eax), %ecx
shll $2, %ecx
movl $LINUX_STAGING_AREA, %esi
movl $LINUX_KERNEL, %edi
rep
movsl
ENTRY(big_linux_boot)
/* XXX new stack pointer in safe area for calling functions */
movl $0x4000, %esp
call EXT_C(stop_floppy)
/* final setup for linux boot */
movw $LINUX_SETUP_SEG, %ax
movw %ax, segment
xorl %eax, %eax
movl %eax, offset
call EXT_C(prot_to_real)
.code16
/* final setup for linux boot */
movw $LINUX_SETUP_STACK, %sp
movw $LINUX_INIT_SEG, %ax
movw %ax, %ss
/* jump to start */
DATA32 ADDR32 ljmp (offset)
.code32
/*
* multi_boot(int start, int mbi)
*
* 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)
/*
* cls()
* BIOS call "INT 10H Function 0Fh" to get current video mode
* Call with %ah = 0x0f
* Returns %al = (video mode)
* %bh = (page number)
* BIOS call "INT 10H Function 00h" to set the video mode (clears screen)
* Call with %ah = 0x00
* %al = (video mode)
*/
ENTRY(cls)
push %ebp
push %eax
push %ebx /* save EBX */
call EXT_C(prot_to_real)
.code16
movb $0xf, %ah
int $0x10 /* Get Current Video mode */
xorb %ah, %ah
int $0x10 /* Set Video mode (clears screen) */
DATA32 call EXT_C(real_to_prot)
.code32
pop %ebx
pop %eax
pop %ebp
ret
/*
* nocursor()
* BIOS call "INT 10H Function 01h" to set cursor type
* Call with %ah = 0x01
* %ch = cursor starting scanline
* %cl = cursor ending scanline
*/
ENTRY(nocursor)
push %ebp
push %eax
push %ebx /* save EBX */
push %edx
call EXT_C(prot_to_real)
.code16
movw $0x2000, %cx
movb $0x1, %ah
int $0x10
DATA32 call EXT_C(real_to_prot)
.code32
pop %edx
pop %ebx
pop %eax
pop %ebp
ret
/*
* getxy()
* 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(getxy)
push %ebp
push %ebx /* save EBX */
push %ecx /* save ECX */
push %edx
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 %edx
pop %ecx
pop %ebx
pop %ebp
ret
/*
* gotoxy(x,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(gotoxy)
push %ebp
push %eax
push %ebx /* save EBX */
push %edx
movb 0x14(%esp), %dl /* %dl = x */
movb 0x18(%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 %edx
pop %ebx
pop %eax
pop %ebp
ret
/*
* set_attrib(attr) : Sets the character attributes for character at
* current cursor position.
*
* Bitfields for character's display attribute:
* Bit(s) Description
* 7 foreground blink
* 6-4 background color
* 3 foreground bright
* 2-0 foreground color
*
* Values for character color:
* Normal Bright
* 000b black dark gray
* 001b blue light blue
* 010b green light green
* 011b cyan light cyan
* 100b red light red
* 101b magenta light magenta
* 110b brown yellow
* 111b light gray white
*
* BIOS call "INT 10H Function 08h" to read character and attribute data
* Call with %ah = 0x08
* %bh = page
* Returns %ah = character attribute
* %al = character value
* BIOS call "INT 10H Function 09h" to write character and attribute data
* Call with %ah = 0x09
* %al = character value
* %bh = page
* %bl = character attribute
* %cx = count to display (???, possible side-effects!!)
*/
ENTRY(set_attrib)
push %ebp
push %eax
push %ebx
push %ecx
movl 0x14(%esp), %ecx
xorl %ebx, %ebx
call EXT_C(prot_to_real)
.code16
movb $0x8, %ah
int $0x10
movb $0x9, %ah
movb %cl, %bl
movw $1, %cx
int $0x10
DATA32 call EXT_C(real_to_prot)
.code32
pop %ecx
pop %ebx
pop %eax
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
push %ecx
push %edx
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 %edx
pop %ecx
pop %ebp
ret
/*
* currticks()
* return the real time in ticks, of which there are about
* 18-20 per second
*/
ENTRY(currticks)
pushl %ebp
pushl %ecx
pushl %edx
call EXT_C(prot_to_real) /* enter real mode */
.code16
xorl %eax, %eax
int $0x1a
DATA32 call EXT_C(real_to_prot)
.code32
movl %ecx, %eax
shll $16, %eax
movw %dx, %ax
popl %edx
popl %ecx
popl %ebp
ret
/*
* remap_ascii_char remaps the ascii code %bl 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, %bl
jne 1b
/* if so, perform the mapping */
movb %ah, %bl
2:
/* restore %si */
popw %si
ret
.code32
.align 4
ENTRY(ascii_key_map)
.space (KEY_MAP_SIZE + 1) * 2
/*
* getkey()
* BIOS call "INT 16H Function 00H" to read character from keyboard
* Call with %ah = 0x0
* Return: %ah = keyboard scan code
* %al = ASCII character
*/
ENTRY(getkey)
push %ebp
push %ebx /* save %ebx */
call EXT_C(prot_to_real)
.code16
int $0x16
movw %ax, %bx /* real_to_prot uses %eax */
call remap_ascii_char
DATA32 call EXT_C(real_to_prot)
.code32
movw %bx, %ax
pop %ebx
pop %ebp
ret
/*
* checkkey()
* 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(checkkey)
push %ebp
push %ebx
xorl %ebx, %ebx
call EXT_C(prot_to_real) /* enter real mode */
.code16
movb $0x1, %ah
int $0x16
DATA32 jz notpending
movw %ax, %bx
call remap_ascii_char
DATA32 jmp pending
notpending:
movl $0xFFFFFFFF, %ebx
pending:
DATA32 call EXT_C(real_to_prot)
.code32
mov %ebx, %eax
pop %ebx
pop %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)
.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
/*
* 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 */