root/src/system/boot/platform/bios_ia32/bios_asm.S
/*
 * Copyright 2004-2010, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


/*!     This file contains code to call BIOS functions out of a protected
        mode environment. It doesn't use the virtual86 mode - it switches
        to real mode, make the BIOS call, and switch back to protected
        mode again. It's meant to be used in a single-threaded boot loader,
        not in a multi-tasking operating system.
        It relies on the real mode segment descriptors found in shell.S.
*/


#define FUNCTION(x) .globl x ; x ## :

#define REAL_MODE_STACK 0x9000
        // the location of the stack in real mode

#define SAVED_ESP               0x10000
#define SAVED_CR3               0x10004
#define SAVED_EAX               0x10008
#define SAVED_ES                0x1000c
#define SAVED_FLAGS             0x10010
#define SAVED_EBP               0x10014
        // we're overwriting the start of our boot loader to hold some
        // temporary values - the first 1024 bytes of it are used at
        // startup only, and we avoid some linking issues this way


.text
.code32


/*!     This function brings you back to protected mode after you've
        switched to it using switch_to_real_mode().
        Should restore the whole environment to what it looked like
        before. Clobbers %eax.
*/
FUNCTION(switch_to_protected_mode)
        cli                                             // turn off interrupts

        .code16
        movl    %cr0, %eax              // set the PE bit (0) to switch to protected mode
        orb             $0x1, %al
        movl    %eax, %cr0

        .code32
        .byte   0x66                    // jump to the protected mode segment
        ljmp    $0x8, $_protected_code_segment
_protected_code_segment:
        movw    $0x10, %ax              // setup data and stack selectors
        movw    %ax, %ds
        movw    %ax, %es
        movw    %ax, %fs
        movw    %ax, %gs
        movw    %ax, %ss

        // turn on paging again
        movl    SAVED_CR3, %eax // restore the saved page directory
        orl             %eax, %eax              // is there a paging directory at all?
        jz              _no_paging;

        movl    %eax, %cr3

        movl    %cr0, %eax              // set the PG bit (31) to enable paging
        orl             $0x80000000, %eax
        movl    %eax, %cr0

_no_paging:
        // save the return address so that we can pick it up again later
        movl    (%esp), %eax
        movl    %eax, REAL_MODE_STACK

        // setup protected stack frame again
        movl    SAVED_ESP, %eax
        movl    %eax, %esp
        movl    SAVED_EBP, %ebp

        // copy the return address to the current stack
        movl    REAL_MODE_STACK, %eax
        movl    %eax, (%esp)

        ret

//--------------------------------------------------------------


/*!     Switches from protected mode back to real mode.
        It will disable paging and set the real mode segment selectors to 0x1000,
        except for the stack selector, which will be 0x0 (the stack is at 0x9000
        which is where the BFS boot loader puts it as well).
        Clobbers %eax.
*/
FUNCTION(switch_to_real_mode)
        // save the %esp register
        movl    %esp, %eax
        movl    %eax, SAVED_ESP

        movl    %ebp, SAVED_EBP

        // put the return address on the real mode stack
        movl    (%esp), %eax
        movl    %eax, REAL_MODE_STACK

        // disable paging
        movl    %cr3, %eax                      // save the page directory address
        movl    %eax, SAVED_CR3

        movl    %cr0, %eax
        andl    $0x7fffffff, %eax       // clear PG bit (31)
        movl    %eax, %cr0

        xor             %eax, %eax                      // clear page directory to flush TLBs
        movl    %eax, %cr3

        // setup real mode stack
        movl    $REAL_MODE_STACK, %eax
        movl    %eax, %esp
        movl    %eax, %ebp

        // setup selectors to point to our 16 bit segments
        movw    $0x20, %ax
        movw    %ax, %ds
        movw    %ax, %es
        movw    %ax, %fs
        movw    %ax, %gs
        movw    $0x28, %ax
        movw    %ax, %ss

        ljmp    $0x18, $(_almost_real_code_segment - 0x10000)

_almost_real_code_segment:
        movl    %cr0, %eax                      // switch to real mode
        andb    $0xfe, %al                      // clear PE bit (0)
        movl    %eax, %cr0

        .byte   0x66
        ljmp    $0x1000, $(_real_code_segment - 0x10000)

_real_code_segment:
        .code16
        movw    $0x1000, %ax            // setup data & stack segments
        movw    %ax, %ds                        // data in segment 0x1000,
        movw    %ax, %es
        movw    %ax, %fs
        movw    %ax, %gs

        xor             %ax, %ax                        // stack in segment 0x0
        movw    %ax, %ss

        sti                                                     // turn on interrupts again
        ret

        .code32

//--------------------------------------------------------------


/*!     void call_bios_internal(uint8 num, struct bios_regs *regs)
        Does a BIOS call by triggering a software interrupt in real
        mode.
*/
FUNCTION(call_bios_internal)
        pushal
        pushfl

        // make sure the correct IDT is in place
        lidt    idt_descriptor

        // get the interrupt vector and patch the instruction at the target address
        movl    40(%esp), %eax
        mov             %al, int_number

        // Fills registers from the passed in structure
        // Since switch_to_real_mode() clobbers %eax, we have to handle
        // it specially here (by temporarily storing it to an arbitrary
        // memory location, SAVED_EAX)
        movl    44(%esp), %ebp

        movl    (%ebp), %eax
        movl    %eax, SAVED_EAX
        movl    4(%ebp), %ebx
        movl    8(%ebp), %ecx
        movl    12(%ebp), %edx
        movl    16(%ebp), %esi
        movl    20(%ebp), %edi
        movw    24(%ebp), %ax
        movw    %ax, SAVED_ES

        call    switch_to_real_mode

        .code16

        // restore %eax and %es from saved location
        movl    (SAVED_EAX - 0x10000), %eax
        movw    (SAVED_ES - 0x10000), %es

        // call the interrupt (will be dynamically changed above)
        .byte   0xcd
int_number:
        .byte   0

        // we're interested in the flags state as well
        pushf

        // save %eax from the call
        movl    %eax, (SAVED_EAX - 0x10000)

        // save flags from the call
        pop             %ax
        movw    %ax, (SAVED_FLAGS - 0x10000)

        // back to protected mode
        call    switch_to_protected_mode
        .code32

        // store the register state into the structure that has been passed in
        movl    44(%esp), %eax

        movl    %ebx, 4(%eax)
        movl    %ecx, 8(%eax)
        movl    %edx, 12(%eax)
        movl    %esi, 16(%eax)
        movl    %edi, 20(%eax)
        movl    SAVED_EAX, %ecx         // special handling for %eax and flags
        movl    %ecx, (%eax)
        movw    SAVED_FLAGS, %cx
        movw    %cx, 26(%eax)

        popfl
        popal

        ret

//--------------------------------------------------------------


/*!     uint32  boot_key_in_keyboard_buffer()
        Search keyboard buffer for the keycodes for space in the first run, and,
        if not found - for the escape key at the last two positions of this buffer
*/
FUNCTION(boot_key_in_keyboard_buffer)
        pushal
        pushfl

        // make sure the correct IDT is in place
        lidt    idt_descriptor

        call    switch_to_real_mode
        .code16

        cld
        push    %ds
        xorl    %eax, %eax
        mov             %ax, %ds
        mov             $0x41E, %si             // BIOS kbd buffer
search_cycle1:
        lodsw
        cmp             $0x3920, %ax    // test space key
        jz              to_ret
        cmp             $0x440, %si
        jnz             search_cycle1

        addw    0x41C, %si
        movw    -0x42(%si), %ax
        cmp             $0x011B, %ax    // test ESC key
        jz              to_ret
        movw    -0x44(%si), %ax
        cmp             $0x011B, %ax    // test ESC key
to_ret:
        pop             %ds

        // save %eax
        movl    %eax, (SAVED_EAX - 0x10000)

        call    switch_to_protected_mode
        .code32

        popfl
        popal

        // restore %eax
        movl    SAVED_EAX, %eax

        ret

//--------------------------------------------------------------


.globl idt_descriptor
idt_descriptor:
        .short  0x7ff                           // IDT at 0x0, default real mode location
        .long   0x0