root/src/system/boot/platform/bios_ia32/shell.S
/*
 * 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 ; 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; we're still in real mode segment
                // 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