/*
* Copyright 2004-2005, Axel DΓΆrfler, axeld@pinc-software.de. All rights reserved.
* Distributed under the terms of the MIT License.
*/
/** This file contains the boot floppy and BFS boot block entry points for
* the stage 2 boot loader.
* The floppy entry point is at offset 0. It's loaded at 0x07c0:0x000. It
* will load the rest of the loader to 0x1000:0x0200 and execute it.
* The BFS boot block will load the whole stage 2 loader to 0x1000:0x0000
* and will then jump to 0x1000:0x0200 as its entry point.
* This code will then switch to protected mode and will directly call
* the entry function of the embedded ELF part of the loader.
*/
#include "multiboot.h"
#define GLOBAL(x) .globl x
#define OUR_MB_FLAGS (MULTIBOOT_PAGE_ALIGN \
| MULTIBOOT_MEMORY_INFO \
/*| MULTIBOOT_VIDEO_MODE*/ \
| MULTIBOOT_AOUT_KLUDGE)
// load address
#define LOAD_SEGMENT 0x1000
#define LOAD_ADDRESS 0x10000
// MultiBoot load address
#define MB_LOAD_ADDRESS 0x100000
//#define MB_LOAD_ADDRESS LOAD_ADDRESS
#define MB_LOAD_OFFSET (MB_LOAD_ADDRESS - LOAD_ADDRESS)
// this saves us some trouble with relocation (I didn't manage GAS to
// create 32 bit references to labels)
#define FAILURE_STRING 0x1d0
#define DOT_STRING 0x1fc
#define DRIVE_RETRIES 3
// when the drive reading fails for some reason, it will
// retry this many times until it will report a failure
.text
.code16
/** This is the entry point when we were written directly to a floppy disk */
jmp floppy_start
sNumSectors:
// this location will contain the length of the boot loader as
// written by the "makeflop" command in 512 byte blocks
// 0x180 is the allowed maximum, as the zipped TAR with the
// kernel and the boot module might start at offset 192 kB
.word BOOT_ARCHIVE_IMAGE_OFFSET*2
floppy_start:
cli
cld
// set up the stack to 0x0000:0x9000
xor %ax, %ax
mov %ax, %ss
mov $0x9000, %sp
push $0x07c0
pop %ds
push $0x1000
pop %es
// load the rest of the boot loader to 0x1000:0x0200
.code32 // we need to create a 32-bit relocation entry for the linker...
.byte 0x67
movw sNumSectors - 0x10000, %di
// the loader symbols are located at offset 0x10000
.code16
xor %dh, %dh // head 0, don't change BIOS boot device
mov $0x2, %cx // sector 2
mov $0x200, %bx // to 0x1000:0x0200
call load_sectors
// ToDo: this seems to be problematic, at least under Bochs (reboot will fail)
#if 0
or %dl, %dl // if it's a floppy, turn off its motor
jnz start_loader
call disable_floppy_motor
#endif
start_loader:
// indicate that we were booted from CD/floppy/whatever
.code32
.byte 0x67
movb $1, gBootedFromImage - 0x7c00
// %ds is 0x7c0 right now, but the symbol were loaded
// to offset 0x10000
.code16
// set our environment and jump to the standard BFS boot block entry point
xor %dx, %dx // boot device ID and partition offset to 0
xor %eax, %eax
ljmp $0x1000, $0x0200
/** Loads %di sectors from floppy disk, starting at head %dh, sector %cx.
* The data is loaded to %es:%bx. On exit, %es:%bx will point immediately
* behind the loaded data, so that you can continue to read in data.
* %ax, %cx, %dx, %bp, %di and %si will be clobbered.
*/
load_sectors:
// first, get information about the drive as we intend to read whole tracks
push %bx
push %cx
push %dx
push %di
push %es
movb $8, %ah // get drive parameters - changes a lot of registers
int $0x13
pop %es
pop %di
// ToDo: store the number of heads somewhere (it's in %dh)
pop %dx
and $63, %cx // mask out max. sector number (bit 0-5)
mov %cx, %si // and remember it
pop %cx
pop %bx
load_track:
mov %di, %ax // limit the sector count to track boundaries
add %cl, %al
dec %ax
cmp %si, %ax
jbe matches_track_boundary
mov %si, %ax
matches_track_boundary:
inc %ax // take the current sector offset into account
sub %cl, %al
// make sure we don't cross a 64kB address boundary or else the read will fail
// (this small piece of knowledge took me some time to accept :))
shl $9, %ax
mov %ax, %bp
add %bx, %bp
jnc respects_boundary
xor %ax, %ax // only read up to the 64kB boundary
sub %bx, %ax
respects_boundary:
shr $9, %ax
mov DRIVE_RETRIES, %bp
try_to_read:
pusha
movb $2, %ah // load sectors from drive
int $0x13
jnc read_succeeded
xor %ax, %ax
int $0x13 // reset drive
popa
dec %bp
jz load_failed // if already retried often enough, bail out
jmp try_to_read
read_succeeded:
mov $DOT_STRING, %si
call print_string
popa
xor %ah, %ah
add %ax, %cx // next sector start
sub %ax, %di // update sectors left to be read
shl $9, %ax // get byte offset
add %ax, %bx // update target address
jnz check_sector_start
mov %es, %ax // overflow to the next 64kB, %bx is already zero
add $0x1000, %ax
mov %ax, %es
check_sector_start:
mov %si, %ax // compare the sectors, not the cylinders
cmp %al, %cl
jbe continue_reading
sub %si, %cx
inc %dh // next head
cmp $1, %dh
// ToDo: check max. number of heads!
jbe check_sector_start
xor %dh, %dh // next cylinder
inc %ch
jmp check_sector_start
continue_reading:
or %di, %di
jnz load_track
ret
load_failed:
mov $FAILURE_STRING, %si
call print_string
xor %ax, %ax
int $0x16 // wait for key
int $0x19 // and reboot
disable_floppy_motor:
xor %al, %al
mov $0x3f2, %dx
out %al, %dx
ret
print_string:
movb $0x0e, %ah
xor %bx, %bx
print_char:
lodsb
orb %al, %al // are there still characters left?
jz no_more_chars
int $0x10
jmp print_char
no_more_chars:
ret
floppy_end:
.org FAILURE_STRING
.string " Loading failed! Press key to reboot.\r\n"
.org DOT_STRING
.string "."
.org 0x01fe
.word 0xaa55
// this bumps the "start" label to offset 0x0200 as
// expected by the BFS boot loader, and also marks
// this block as valid boot block for the BIOS
//--------------------------------------------------------------
/** This is the entry point of the stage2 bootloader when it has
* been loaded from the stage1 loader from a BFS disk.
*/
bfs_start:
cld // set the data, and extra segment to our code start
pushw $0x1000
pop %ds
push %ds
pop %es
.code32 // save knowledge from the BFS boot block for later use
.byte 0x67
movb %dl, gBootDriveID - 0x10000
.byte 0x67
.byte 0x66
movl %eax, gBootPartitionOffset - 0x10000
.code16
xor %ax, %ax // set up stack at 0x0000:0x9000
mov %ax, %ss
mov $0x9000, %sp
cli // no interrupts please
call enable_a20 // enable a20 gate
.code32 // This forces a 32 bit relocation entry
.byte 0x66 // that allows linking with others
.byte 0x67
lgdt gdt_descriptor - 0x10000
// load global descriptor table
// 0x1000 so we have to manually correct the address
.code16
movl %cr0, %eax // set the PE bit of cr0 to switch to protected mode
orb $0x1, %al
movl %eax, %cr0
.code32
.byte 0x66
ljmp $0x8, $_protected_code_segment
_protected_code_segment:
mov $0x10, %ax // load descriptor 2 in the data and stack segment selectors
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
mov %ax, %ss
movl $0x10000, %esp // setup new stack
pushl $0 // terminate stack frame chain (next frame and
pushl $0 // return address)
mov %esp, %ebp
call _start
//--------------------------------------------------------------
/** MultiBoot entry point
*/
multiboot_start:
//subl $MULTIBOOT_MAGIC2, %eax
//jnz load_failed // rts to grub ?
movl %ebx, gMultiBootInfo + MB_LOAD_OFFSET
// load the GDT
lgdt gdt_descriptor + MB_LOAD_OFFSET
#if MB_LOAD_ADDRESS != LOAD_ADDRESS
// QEMU does not like the real load address...
// copy ourselves to the expected location
cld
mov $(_end - LOAD_ADDRESS), %ecx
add $3, %ecx
shr $2, %ecx
mov $LOAD_ADDRESS, %edi
mov $MB_LOAD_ADDRESS, %esi
rep movsl
// reload the GDT just in case
lgdt gdt_descriptor
#endif
relocated_mb_start:
ljmp $0x8, $_protected_code_segment
//--------------------------------------------------------------
/** Enables the a20 gate. It will first try to enable it through
* the BIOS, and, if that fails, will use the old style AT mechanism
* using the keyboard port.
* ToDo: it no longer does this! Now, it just uses the "fast A20"
* mechanism using port 0x92. This does work on all systems
* I have access to.
*/
enable_a20:
inb $0x92, %al
testb $0x02, %al
jnz _a20_out
orb $0x02, %al
andb $0xfe, %al
outb %al, $0x92
_a20_out:
ret
// ToDo: the code below didn't seem to work properly on all machines
/* movw $0x2402, %ax // first, query the a20 status
int $0x15
jc _a20_old_method // if that fails, use the old AT method
test $0x1, %al
jnz _a20_done // Is a20 gate already enabled?
movw $0x2401, %ax
int $0x15
jnc _a20_done
_a20_old_method:
call _a20_loop1 // empty the keyboard buffer
jnz _a20_done
movb $0xd1, %al
outb %al, $0x64
call _a20_loop1 // empty the keyboard buffer
jnz _a20_done
movb $0xdf, %al
outb %al, $0x60
_a20_loop1:
movl $0x20000, %ecx
_a20_loop2:
inb $0x64, %al
test $0x2, %al
loopne _a20_loop2
_a20_done:
ret
*/
//--------------------------------------------------------------
.org 856
// since we don't need the above space when the boot loader is
// running, it is used as a real mode scratch buffer (as our
// boot loader spans over the whole real mode 0x1000 segment)
.align 4
multiboot_header:
.long MULTIBOOT_MAGIC
.long OUR_MB_FLAGS
.long (0 - MULTIBOOT_MAGIC - OUR_MB_FLAGS) // checksum (8 bytes)
.long multiboot_header + MB_LOAD_OFFSET
.long .text + MB_LOAD_OFFSET
.long .bss + (MB_LOAD_OFFSET - 24)
.long _end + (MB_LOAD_OFFSET - 24)
.long multiboot_start + MB_LOAD_OFFSET
#if (OUR_MB_FLAGS & MULTIBOOT_VIDEO_MODE)
.long 0 // non text mode
.long 1024
.long 786
.long 24
#endif
/* global data table */
gdt:
// null descriptor
.long 0
.long 0
// kernel code segment
.long 0x0000ffff // base: 0, limit: 4 GB
.long 0x00cf9e00 // type: 32 bit, exec-only conforming, privilege 0
// kernel data and stack segment
.long 0x0000ffff // base: 0, limit: 4 GB
.long 0x00cf9200 // type: 32 bit, data read/write, privilege 0
// real mode 16 bit code segment
.long 0x0000ffff // base: 0x10000, limit: 64 kB
.long 0x00009e01
// real mode 16 bit data and stack segment
.long 0x0000ffff // base: 0x10000, limit: 64 kB
.long 0x00009201
// real mode 16 bit stack segment
.long 0x0000ffff // base: 0, limit: 64 kB
.long 0x00009200
gdt_descriptor:
.word 0x2f // 6 entries in the GDT (8 bytes each)
.long gdt
GLOBAL(gBootedFromImage):
.byte 0
GLOBAL(gBootDriveID):
.byte 0
GLOBAL(gBootPartitionOffset):
.long 0
GLOBAL(gMultiBootInfo):
.long 0
.org 1024
.section .bss