| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 1999,2000,2001,2002,2006,2007,2009,2010 Free Software Foundation, Inc. |
| * |
| * GRUB is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * GRUB is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <grub/symbol.h> |
| #include <grub/machine/boot.h> |
| |
| /* |
| * defines for the code go here |
| */ |
| |
| #define MSG(x) movw $x, %si; call LOCAL(message) |
| |
| .file "diskboot.S" |
| |
| .text |
| |
| /* Tell GAS to generate 16-bit instructions so that this code works |
| in real mode. */ |
| .code16 |
| |
| .globl start, _start |
| start: |
| _start: |
| /* |
| * _start is loaded at 0x2000 and is jumped to with |
| * CS:IP 0:0x2000 in kernel. |
| */ |
| |
| /* |
| * we continue to use the stack for boot.img and assume that |
| * some registers are set to correct values. See boot.S |
| * for more information. |
| */ |
| |
| /* save drive reference first thing! */ |
| pushw %dx |
| |
| /* print a notification message on the screen */ |
| pushw %si |
| MSG(notification_string) |
| popw %si |
| |
| /* this sets up for the first run through "bootloop" */ |
| movw $LOCAL(firstlist), %di |
| |
| /* save the sector number of the second sector in %ebp */ |
| movl (%di), %ebp |
| |
| /* this is the loop for reading the rest of the kernel in */ |
| LOCAL(bootloop): |
| |
| /* check the number of sectors to read */ |
| cmpw $0, 8(%di) |
| |
| /* if zero, go to the start function */ |
| je LOCAL(bootit) |
| |
| LOCAL(setup_sectors): |
| /* check if we use LBA or CHS */ |
| cmpb $0, -1(%si) |
| |
| /* use CHS if zero, LBA otherwise */ |
| je LOCAL(chs_mode) |
| |
| /* load logical sector start */ |
| movl (%di), %ebx |
| movl 4(%di), %ecx |
| |
| /* the maximum is limited to 0x7f because of Phoenix EDD */ |
| xorl %eax, %eax |
| movb $0x7f, %al |
| |
| /* how many do we really want to read? */ |
| cmpw %ax, 8(%di) /* compare against total number of sectors */ |
| |
| /* which is greater? */ |
| jg 1f |
| |
| /* if less than, set to total */ |
| movw 8(%di), %ax |
| |
| 1: |
| /* subtract from total */ |
| subw %ax, 8(%di) |
| |
| /* add into logical sector start */ |
| addl %eax, (%di) |
| adcl $0, 4(%di) |
| |
| /* set up disk address packet */ |
| |
| /* the size and the reserved byte */ |
| movw $0x0010, (%si) |
| |
| /* the number of sectors */ |
| movw %ax, 2(%si) |
| |
| /* the absolute address */ |
| movl %ebx, 8(%si) |
| movl %ecx, 12(%si) |
| |
| /* the segment of buffer address */ |
| movw $GRUB_BOOT_MACHINE_BUFFER_SEG, 6(%si) |
| |
| /* save %ax from destruction! */ |
| pushw %ax |
| |
| /* the offset of buffer address */ |
| movw $0, 4(%si) |
| |
| /* |
| * BIOS call "INT 0x13 Function 0x42" to read sectors from disk into memory |
| * Call with %ah = 0x42 |
| * %dl = drive number |
| * %ds:%si = segment:offset of disk address packet |
| * Return: |
| * %al = 0x0 on success; err code on failure |
| */ |
| |
| movb $0x42, %ah |
| int $0x13 |
| |
| jc LOCAL(read_error) |
| |
| movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx |
| jmp LOCAL(copy_buffer) |
| |
| LOCAL(chs_mode): |
| /* load logical sector start (top half) */ |
| movl 4(%di), %eax |
| orl %eax, %eax |
| jnz LOCAL(geometry_error) |
| |
| /* load logical sector start (bottom half) */ |
| movl (%di), %eax |
| |
| /* zero %edx */ |
| xorl %edx, %edx |
| |
| /* divide by number of sectors */ |
| divl (%si) |
| |
| /* save sector start */ |
| movb %dl, 10(%si) |
| |
| xorl %edx, %edx /* zero %edx */ |
| divl 4(%si) /* divide by number of heads */ |
| |
| /* save head start */ |
| movb %dl, 11(%si) |
| |
| /* save cylinder start */ |
| movw %ax, 12(%si) |
| |
| /* do we need too many cylinders? */ |
| cmpw 8(%si), %ax |
| jge LOCAL(geometry_error) |
| |
| /* determine the maximum sector length of this read */ |
| movw (%si), %ax /* get number of sectors per track/head */ |
| |
| /* subtract sector start */ |
| subb 10(%si), %al |
| |
| /* how many do we really want to read? */ |
| cmpw %ax, 8(%di) /* compare against total number of sectors */ |
| |
| |
| /* which is greater? */ |
| jg 2f |
| |
| /* if less than, set to total */ |
| movw 8(%di), %ax |
| |
| 2: |
| /* subtract from total */ |
| subw %ax, 8(%di) |
| |
| /* add into logical sector start */ |
| addl %eax, (%di) |
| adcl $0, 4(%di) |
| |
| /* |
| * This is the loop for taking care of BIOS geometry translation (ugh!) |
| */ |
| |
| /* get high bits of cylinder */ |
| movb 13(%si), %dl |
| |
| shlb $6, %dl /* shift left by 6 bits */ |
| movb 10(%si), %cl /* get sector */ |
| |
| incb %cl /* normalize sector (sectors go |
| from 1-N, not 0-(N-1) ) */ |
| orb %dl, %cl /* composite together */ |
| movb 12(%si), %ch /* sector+hcyl in cl, cylinder in ch */ |
| |
| /* restore %dx */ |
| popw %dx |
| pushw %dx |
| |
| /* head number */ |
| movb 11(%si), %dh |
| |
| pushw %ax /* save %ax from destruction! */ |
| |
| /* |
| * BIOS call "INT 0x13 Function 0x2" to read sectors from disk into memory |
| * Call with %ah = 0x2 |
| * %al = number of sectors |
| * %ch = cylinder |
| * %cl = sector (bits 6-7 are high bits of "cylinder") |
| * %dh = head |
| * %dl = drive (0x80 for hard disk, 0x0 for floppy disk) |
| * %es:%bx = segment:offset of buffer |
| * Return: |
| * %al = 0x0 on success; err code on failure |
| */ |
| |
| movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx |
| movw %bx, %es /* load %es segment with disk buffer */ |
| |
| xorw %bx, %bx /* %bx = 0, put it at 0 in the segment */ |
| movb $0x2, %ah /* function 2 */ |
| int $0x13 |
| |
| jc LOCAL(read_error) |
| |
| /* save source segment */ |
| movw %es, %bx |
| |
| LOCAL(copy_buffer): |
| |
| /* load addresses for copy from disk buffer to destination */ |
| movw 10(%di), %es /* load destination segment */ |
| |
| /* restore %ax */ |
| popw %ax |
| |
| /* determine the next possible destination address (presuming |
| 512 byte sectors!) */ |
| shlw $5, %ax /* shift %ax five bits to the left */ |
| addw %ax, 10(%di) /* add the corrected value to the destination |
| address for next time */ |
| |
| /* save addressing regs */ |
| pusha |
| pushw %ds |
| |
| /* get the copy length */ |
| shlw $3, %ax |
| movw %ax, %cx |
| |
| xorw %di, %di /* zero offset of destination addresses */ |
| xorw %si, %si /* zero offset of source addresses */ |
| movw %bx, %ds /* restore the source segment */ |
| |
| cld /* sets the copy direction to forward */ |
| |
| /* perform copy */ |
| rep /* sets a repeat */ |
| movsw /* this runs the actual copy */ |
| |
| /* restore addressing regs and print a dot with correct DS |
| (MSG modifies SI, which is saved, and unused AX and BX) */ |
| popw %ds |
| MSG(notification_step) |
| popa |
| |
| /* check if finished with this dataset */ |
| cmpw $0, 8(%di) |
| jne LOCAL(setup_sectors) |
| |
| /* update position to load from */ |
| subw $GRUB_BOOT_MACHINE_LIST_SIZE, %di |
| |
| /* jump to bootloop */ |
| jmp LOCAL(bootloop) |
| |
| /* END OF MAIN LOOP */ |
| |
| LOCAL(bootit): |
| /* print a newline */ |
| MSG(notification_done) |
| popw %dx /* this makes sure %dl is our "boot" drive */ |
| ljmp $0, $(GRUB_BOOT_MACHINE_KERNEL_ADDR + 0x200) |
| |
| |
| /* |
| * BIOS Geometry translation error (past the end of the disk geometry!). |
| */ |
| LOCAL(geometry_error): |
| MSG(geometry_error_string) |
| jmp LOCAL(general_error) |
| |
| /* |
| * Read error on the disk. |
| */ |
| LOCAL(read_error): |
| MSG(read_error_string) |
| |
| LOCAL(general_error): |
| MSG(general_error_string) |
| |
| /* go here when you need to stop the machine hard after an error condition */ |
| LOCAL(stop): jmp LOCAL(stop) |
| |
| notification_string: .asciz "loading" |
| |
| notification_step: .asciz "." |
| notification_done: .asciz "\r\n" |
| |
| geometry_error_string: .asciz "Geom" |
| read_error_string: .asciz "Read" |
| general_error_string: .asciz " Error" |
| |
| /* |
| * message: write the string pointed to by %si |
| * |
| * WARNING: trashes %si, %ax, and %bx |
| */ |
| |
| /* |
| * Use BIOS "int 10H Function 0Eh" to write character in teletype mode |
| * %ah = 0xe %al = character |
| * %bh = page %bl = foreground color (graphics modes) |
| */ |
| 1: |
| movw $0x0001, %bx |
| movb $0xe, %ah |
| int $0x10 /* display a byte */ |
| |
| incw %si |
| LOCAL(message): |
| movb (%si), %al |
| cmpb $0, %al |
| jne 1b /* if not end of string, jmp to display */ |
| ret |
| |
| /* |
| * This area is an empty space between the main body of code below which |
| * grows up (fixed after compilation, but between releases it may change |
| * in size easily), and the lists of sectors to read, which grows down |
| * from a fixed top location. |
| */ |
| |
| .word 0 |
| .word 0 |
| |
| .org 0x200 - GRUB_BOOT_MACHINE_LIST_SIZE |
| LOCAL(firstlist): /* this label has to be before the first list entry!!! */ |
| /* fill the first data listing with the default */ |
| blocklist_default_start: |
| /* this is the sector start parameter, in logical sectors from |
| the start of the disk, sector 0 */ |
| .long 2, 0 |
| blocklist_default_len: |
| /* this is the number of sectors to read. grub-mkimage |
| will fill this up */ |
| .word 0 |
| blocklist_default_seg: |
| /* this is the segment of the starting address to load the data into */ |
| .word (GRUB_BOOT_MACHINE_KERNEL_SEG + 0x20) |