2N/A/* -*-Asm-*- */
2N/A/*
2N/A * GRUB -- GRand Unified Bootloader
2N/A * Copyright (C) 1999,2000,2001,2002,2005,2006,2007,2008,2009 Free Software Foundation, Inc.
2N/A *
2N/A * GRUB is free software: you can redistribute it and/or modify
2N/A * it under the terms of the GNU General Public License as published by
2N/A * the Free Software Foundation, either version 3 of the License, or
2N/A * (at your option) any later version.
2N/A *
2N/A * GRUB is distributed in the hope that it will be useful,
2N/A * but WITHOUT ANY WARRANTY; without even the implied warranty of
2N/A * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2N/A * GNU General Public License for more details.
2N/A *
2N/A * You should have received a copy of the GNU General Public License
2N/A * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2N/A */
2N/A
2N/A#include <grub/symbol.h>
2N/A#include <grub/machine/boot.h>
2N/A
2N/A/*
2N/A * defines for the code go here
2N/A */
2N/A
2N/A /* Print message string */
2N/A#define MSG(x) movw $x, %si; call LOCAL(message)
2N/A#define ERR(x) movw $x, %si; jmp LOCAL(error_message)
2N/A
2N/A .file "boot.S"
2N/A
2N/A .text
2N/A
2N/A /* Tell GAS to generate 16-bit instructions so that this code works
2N/A in real mode. */
2N/A .code16
2N/A
2N/A.globl _start, start;
2N/A_start:
2N/Astart:
2N/A /*
2N/A * _start is loaded at 0x7c00 and is jumped to with CS:IP 0:0x7c00
2N/A */
2N/A
2N/A /*
2N/A * Beginning of the sector is compatible with the FAT/HPFS BIOS
2N/A * parameter block.
2N/A */
2N/A
2N/A jmp LOCAL(after_BPB)
2N/A nop /* do I care about this ??? */
2N/A
2N/A /*
2N/A * This space is for the BIOS parameter block!!!! Don't change
2N/A * the first jump, nor start the code anywhere but right after
2N/A * this area.
2N/A */
2N/A
2N/A . = _start + GRUB_BOOT_MACHINE_BPB_START
2N/A . = _start + 4
2N/A
2N/A /* scratch space */
2N/Amode:
2N/A .byte 0
2N/Adisk_address_packet:
2N/Asectors:
2N/A .long 0
2N/Aheads:
2N/A .long 0
2N/Acylinders:
2N/A .word 0
2N/Asector_start:
2N/A .byte 0
2N/Ahead_start:
2N/A .byte 0
2N/Acylinder_start:
2N/A .word 0
2N/A /* more space... */
2N/A
2N/A . = _start + GRUB_BOOT_MACHINE_BPB_END
2N/A
2N/A /*
2N/A * End of BIOS parameter block.
2N/A */
2N/A
2N/Akernel_address:
2N/A .word GRUB_BOOT_MACHINE_KERNEL_ADDR
2N/A
2N/A . = _start + GRUB_BOOT_MACHINE_KERNEL_SECTOR
2N/Akernel_sector:
2N/A .long 1, 0
2N/A
2N/A . = _start + GRUB_BOOT_MACHINE_BOOT_DRIVE
2N/Aboot_drive:
2N/A .byte 0xff /* the disk to load kernel from */
2N/A /* 0xff means use the boot drive */
2N/A
2N/ALOCAL(after_BPB):
2N/A
2N/A/* general setup */
2N/A cli /* we're not safe here! */
2N/A
2N/A /*
2N/A * This is a workaround for buggy BIOSes which don't pass boot
2N/A * drive correctly. If GRUB is installed into a HDD, check if
2N/A * DL is masked correctly. If not, assume that the BIOS passed
2N/A * a bogus value and set DL to 0x80, since this is the only
2N/A * possible boot drive. If GRUB is installed into a floppy,
2N/A * this does nothing (only jump).
2N/A */
2N/A . = _start + GRUB_BOOT_MACHINE_DRIVE_CHECK
2N/Aboot_drive_check:
2N/A jmp 3f /* grub-setup may overwrite this jump */
2N/A testb $0x80, %dl
2N/A jz 2f
2N/A3:
2N/A /* Ignore %dl different from 0-0x0f and 0x80-0x8f. */
2N/A testb $0x70, %dl
2N/A jz 1f
2N/A2:
2N/A movb $0x80, %dl
2N/A1:
2N/A /*
2N/A * ljmp to the next instruction because some bogus BIOSes
2N/A * jump to 07C0:0000 instead of 0000:7C00.
2N/A */
2N/A ljmp $0, $real_start
2N/A
2N/Areal_start:
2N/A
2N/A /* set up %ds and %ss as offset from 0 */
2N/A xorw %ax, %ax
2N/A movw %ax, %ds
2N/A movw %ax, %ss
2N/A
2N/A /* set up the REAL stack */
2N/A movw $GRUB_BOOT_MACHINE_STACK_SEG, %sp
2N/A
2N/A sti /* we're safe again */
2N/A
2N/A /*
2N/A * Check if we have a forced disk reference here
2N/A */
2N/A movb boot_drive, %al
2N/A cmpb $0xff, %al
2N/A je 1f
2N/A movb %al, %dl
2N/A1:
2N/A /* save drive reference first thing! */
2N/A pushw %dx
2N/A
2N/A /* print a notification message on the screen */
2N/A MSG(notification_string)
2N/A
2N/A /* set %si to the disk address packet */
2N/A movw $disk_address_packet, %si
2N/A
2N/A /* check if LBA is supported */
2N/A movb $0x41, %ah
2N/A movw $0x55aa, %bx
2N/A int $0x13
2N/A
2N/A /*
2N/A * %dl may have been clobbered by INT 13, AH=41H.
2N/A * This happens, for example, with AST BIOS 1.04.
2N/A */
2N/A popw %dx
2N/A pushw %dx
2N/A
2N/A /* use CHS if fails */
2N/A jc LOCAL(chs_mode)
2N/A cmpw $0xaa55, %bx
2N/A jne LOCAL(chs_mode)
2N/A
2N/A andw $1, %cx
2N/A jz LOCAL(chs_mode)
2N/A
2N/Alba_mode:
2N/A xorw %ax, %ax
2N/A movw %ax, 4(%si)
2N/A
2N/A incw %ax
2N/A /* set the mode to non-zero */
2N/A movb %al, -1(%si)
2N/A
2N/A /* the blocks */
2N/A movw %ax, 2(%si)
2N/A
2N/A /* the size and the reserved byte */
2N/A movw $0x0010, (%si)
2N/A
2N/A /* the absolute address */
2N/A movl kernel_sector, %ebx
2N/A movl %ebx, 8(%si)
2N/A movl kernel_sector + 4, %ebx
2N/A movl %ebx, 12(%si)
2N/A
2N/A /* the segment of buffer address */
2N/A movw $GRUB_BOOT_MACHINE_BUFFER_SEG, 6(%si)
2N/A
2N/A/*
2N/A * BIOS call "INT 0x13 Function 0x42" to read sectors from disk into memory
2N/A * Call with %ah = 0x42
2N/A * %dl = drive number
2N/A * %ds:%si = segment:offset of disk address packet
2N/A * Return:
2N/A * %al = 0x0 on success; err code on failure
2N/A */
2N/A
2N/A movb $0x42, %ah
2N/A int $0x13
2N/A
2N/A /* LBA read is not supported, so fallback to CHS. */
2N/A jc LOCAL(chs_mode)
2N/A
2N/A movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
2N/A jmp LOCAL(copy_buffer)
2N/A
2N/ALOCAL(chs_mode):
2N/A /*
2N/A * Determine the hard disk geometry from the BIOS!
2N/A * We do this first, so that LS-120 IDE floppies work correctly.
2N/A */
2N/A movb $8, %ah
2N/A int $0x13
2N/A jnc LOCAL(final_init)
2N/A
2N/A /*
2N/A * The call failed, so maybe use the floppy probe instead.
2N/A */
2N/A testb $GRUB_BOOT_MACHINE_BIOS_HD_FLAG, %dl
2N/A jz LOCAL(floppy_probe)
2N/A
2N/A /* Nope, we definitely have a hard disk, and we're screwed. */
2N/A ERR(hd_probe_error_string)
2N/A
2N/ALOCAL(final_init):
2N/A /* set the mode to zero */
2N/A movzbl %dh, %eax
2N/A movb %ah, -1(%si)
2N/A
2N/A /* save number of heads */
2N/A incw %ax
2N/A movl %eax, 4(%si)
2N/A
2N/A movzbw %cl, %dx
2N/A shlw $2, %dx
2N/A movb %ch, %al
2N/A movb %dh, %ah
2N/A
2N/A /* save number of cylinders */
2N/A incw %ax
2N/A movw %ax, 8(%si)
2N/A
2N/A movzbw %dl, %ax
2N/A shrb $2, %al
2N/A
2N/A /* save number of sectors */
2N/A movl %eax, (%si)
2N/A
2N/Asetup_sectors:
2N/A /* load logical sector start (top half) */
2N/A movl kernel_sector + 4, %eax
2N/A
2N/A orl %eax, %eax
2N/A jnz LOCAL(geometry_error)
2N/A
2N/A /* load logical sector start (bottom half) */
2N/A movl kernel_sector, %eax
2N/A
2N/A /* zero %edx */
2N/A xorl %edx, %edx
2N/A
2N/A /* divide by number of sectors */
2N/A divl (%si)
2N/A
2N/A /* save sector start */
2N/A movb %dl, %cl
2N/A
2N/A xorw %dx, %dx /* zero %edx */
2N/A divl 4(%si) /* divide by number of heads */
2N/A
2N/A /* do we need too many cylinders? */
2N/A cmpw 8(%si), %ax
2N/A jge LOCAL(geometry_error)
2N/A
2N/A /* normalize sector start (1-based) */
2N/A incb %cl
2N/A
2N/A /* low bits of cylinder start */
2N/A movb %al, %ch
2N/A
2N/A /* high bits of cylinder start */
2N/A xorb %al, %al
2N/A shrw $2, %ax
2N/A orb %al, %cl
2N/A
2N/A /* save head start */
2N/A movb %dl, %al
2N/A
2N/A /* restore %dl */
2N/A popw %dx
2N/A
2N/A /* head start */
2N/A movb %al, %dh
2N/A
2N/A/*
2N/A * BIOS call "INT 0x13 Function 0x2" to read sectors from disk into memory
2N/A * Call with %ah = 0x2
2N/A * %al = number of sectors
2N/A * %ch = cylinder
2N/A * %cl = sector (bits 6-7 are high bits of "cylinder")
2N/A * %dh = head
2N/A * %dl = drive (0x80 for hard disk, 0x0 for floppy disk)
2N/A * %es:%bx = segment:offset of buffer
2N/A * Return:
2N/A * %al = 0x0 on success; err code on failure
2N/A */
2N/A
2N/A movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
2N/A movw %bx, %es /* load %es segment with disk buffer */
2N/A
2N/A xorw %bx, %bx /* %bx = 0, put it at 0 in the segment */
2N/A movw $0x0201, %ax /* function 2 */
2N/A int $0x13
2N/A
2N/A jc LOCAL(read_error)
2N/A
2N/A movw %es, %bx
2N/A
2N/ALOCAL(copy_buffer):
2N/A /*
2N/A * We need to save %cx and %si because the startup code in
2N/A * kernel uses them without initializing them.
2N/A */
2N/A pusha
2N/A pushw %ds
2N/A
2N/A movw $0x100, %cx
2N/A movw %bx, %ds
2N/A xorw %si, %si
2N/A movw $GRUB_BOOT_MACHINE_KERNEL_ADDR, %di
2N/A movw %si, %es
2N/A
2N/A cld
2N/A
2N/A rep
2N/A movsw
2N/A
2N/A popw %ds
2N/A popa
2N/A
2N/A /* boot kernel */
2N/A jmp *(kernel_address)
2N/A
2N/A/* END OF MAIN LOOP */
2N/A
2N/A/*
2N/A * BIOS Geometry translation error (past the end of the disk geometry!).
2N/A */
2N/ALOCAL(geometry_error):
2N/A ERR(geometry_error_string)
2N/A
2N/A/*
2N/A * Read error on the disk.
2N/A */
2N/ALOCAL(read_error):
2N/A movw $read_error_string, %si
2N/ALOCAL(error_message):
2N/A call LOCAL(message)
2N/ALOCAL(general_error):
2N/A MSG(general_error_string)
2N/A
2N/A/* go here when you need to stop the machine hard after an error condition */
2N/A /* tell the BIOS a boot failure, which may result in no effect */
2N/A int $0x18
2N/ALOCAL(stop):
2N/A jmp LOCAL(stop)
2N/A
2N/Anotification_string: .asciz "GRUB "
2N/Ageometry_error_string: .asciz "Geom"
2N/Ahd_probe_error_string: .asciz "Hard Disk"
2N/Aread_error_string: .asciz "Read"
2N/Ageneral_error_string: .asciz " Error\r\n"
2N/A
2N/A/*
2N/A * message: write the string pointed to by %si
2N/A *
2N/A * WARNING: trashes %si, %ax, and %bx
2N/A */
2N/A
2N/A /*
2N/A * Use BIOS "int 10H Function 0Eh" to write character in teletype mode
2N/A * %ah = 0xe %al = character
2N/A * %bh = page %bl = foreground color (graphics modes)
2N/A */
2N/A1:
2N/A movw $0x0001, %bx
2N/A movb $0xe, %ah
2N/A int $0x10 /* display a byte */
2N/ALOCAL(message):
2N/A lodsb
2N/A cmpb $0, %al
2N/A jne 1b /* if not end of string, jmp to display */
2N/A ret
2N/A
2N/A /*
2N/A * Windows NT breaks compatibility by embedding a magic
2N/A * number here.
2N/A */
2N/A
2N/A . = _start + GRUB_BOOT_MACHINE_WINDOWS_NT_MAGIC
2N/Ant_magic:
2N/A .long 0
2N/A .word 0
2N/A
2N/A /*
2N/A * This is where an MBR would go if on a hard disk. The code
2N/A * here isn't even referenced unless we're on a floppy. Kinda
2N/A * sneaky, huh?
2N/A */
2N/A
2N/A . = _start + GRUB_BOOT_MACHINE_PART_START
2N/Apart_start:
2N/A
2N/Aprobe_values:
2N/A .byte 36, 18, 15, 9, 0
2N/A
2N/ALOCAL(floppy_probe):
2N/A/*
2N/A * Perform floppy probe.
2N/A */
2N/A
2N/A movw $probe_values - 1, %si
2N/A
2N/ALOCAL(probe_loop):
2N/A /* reset floppy controller INT 13h AH=0 */
2N/A xorw %ax, %ax
2N/A int $0x13
2N/A
2N/A incw %si
2N/A movb (%si), %cl
2N/A
2N/A /* if number of sectors is 0, display error and die */
2N/A cmpb $0, %cl
2N/A jne 1f
2N/A
2N/A/*
2N/A * Floppy disk probe failure.
2N/A */
2N/A MSG(fd_probe_error_string)
2N/A jmp LOCAL(general_error)
2N/A
2N/A/* "Floppy" */
2N/Afd_probe_error_string: .asciz "Floppy"
2N/A
2N/A1:
2N/A /* perform read */
2N/A movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
2N/A movw %bx, %es
2N/A xorw %bx, %bx
2N/A movw $0x201, %ax
2N/A movb $0, %ch
2N/A movb $0, %dh
2N/A int $0x13
2N/A
2N/A /* if error, jump to "LOCAL(probe_loop)" */
2N/A jc LOCAL(probe_loop)
2N/A
2N/A /* %cl is already the correct value! */
2N/A movb $1, %dh
2N/A movb $79, %ch
2N/A
2N/A jmp LOCAL(final_init)
2N/A
2N/A . = _start + GRUB_BOOT_MACHINE_PART_END
2N/A
2N/A/* the last 2 bytes in the sector 0 contain the signature */
2N/A .word GRUB_BOOT_MACHINE_SIGNATURE