Aoik

Grub4DOS WEE source code study - A20 gate

a20.inc

grubutils/wee/a20.inc:

/* real-mode A20 gate control code for grub4dos.
 *
 *  Copyright (C) 2010  Tinybit(tinybit@tom.com)
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* The code is based on bcopy32.c from syslinux-3.63. Here is the original
 * copyright notice:
 *
 * -----------------------------------------------------------------------
 *
 *   Copyright 1994-2008 H. Peter Anvin - All Rights Reserved
 *
 *   This program 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, Inc., 53 Temple Place Ste 330,
 *   Boston MA 02111-1307, USA; either version 2 of the License, or
 *   (at your option) any later version; incorporated herein by reference.
 *
 * -----------------------------------------------------------------------
 */

#
# Routines to enable and disable (yuck) A20.  These routines are gathered
# from tips from a couple of sources, including the Linux kernel and
# http://www.x86.org/.  The need for the delay to be as large as given here
# is indicated by Donnie Barnes of RedHat, the problematic system being an
# IBM ThinkPad 760EL.
#
# We typically toggle A20 twice for every 64K transferred.
#

#define DISABLE_CPU_CACHE   0

#define IO_DELAY_PORT   0x80        /* Invalid port (we hope!) */

# Note the skip of 2 here
# The 2-byte stepping is because the value is used as index into `A20List` and
# `A20List`'s item is 2 bytes each.
#define A20_DUNNO   0       /* A20 type unknown */
#define A20_NONE    2       /* A20 always on? */
#define A20_BIOS    4       /* A20 BIOS enable */
#define A20_KBC     6       /* A20 through KBC */
#define A20_FAST    8       /* A20 through port 92h */

    /* Align Dword here so that other alignments below could work
     * as expected.
     */

    .align  4

enable_disable_a20:

    ###################################################################
    # input:    DL=0        disable a20
    #       DL=non-zero enable a20
    #       DH=0        a20 debug off
    #       DH=non-zero a20 debug on
    #       CX=loops to try when failure
    #
    # output:   ZF=0        failed
    #       ZF=1        completed ok. If ZF=CF=1, then
    #               the A20 status needn't change and
    #               was not touched.
    #       EAX modified
    #       CX modified
    ###################################################################

    # First, see if the A20 status is already what we desired.
// Push ECX.
    pushl   %ecx
// Put 2 to ECX, to be used as try times for `a20_test_match` below.
    movl    $0x2, %ecx
// Test whether A20 is in desired state.
    call    a20_test_match
// Pop old ECX.
    popl    %ecx
    /* ZF=1(means equal) for desired and we needn't do anything. */
// If ZF is 0 which means A20 is not in desired state, jump.
    jnz 1f
// If ZF is 1 which means A20 is in desired state, put 1 to CF to indicate no
// change was made.
    stc
// Return.
    ret

assign_base_pointer:
// The `call` instruction will push the runtime address of the following
// instruction, i.e. the runtime address of `base_addr`, to stack.
    call    base_addr
base_addr:
// Pop the runtime address of `base_addr` to BP.
    popw    %bp
// Return.
    ret

1:
    /********************************************/
    /**  Now we have to enable or disable A20  **/
    /********************************************/

// Push EBP.
    pushl   %ebp
// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
    /* save original EBP */
// Pop old EBP to `A20_old_ebp`.
    popl    %cs:(A20_old_ebp - base_addr)(%bp)

    /* save original return address */
// Pop return address to `A20ReturnAddress`.
    popw    %cs:(A20ReturnAddress - base_addr)(%bp)

// Store registers.
    movl    %eax, %cs:(A20_old_eax - base_addr)(%bp)
    movl    %ebx, %cs:(A20_old_ebx - base_addr)(%bp)
    movl    %ecx, %cs:(A20_old_ecx - base_addr)(%bp)
    movl    %edx, %cs:(A20_old_edx - base_addr)(%bp)
    movl    %esi, %cs:(A20_old_esi - base_addr)(%bp)
    movl    %edi, %cs:(A20_old_edi - base_addr)(%bp)

//  pushal          # save all

// Put 200 to CX, to be used as try times.
    movw    $200, %cx
// Test whether DL is 0.
    testb   %dl, %dl
// If DL is not 0 which means to enable A20, jump.
    jnz 1f
// If DL is 0 which means to disable A20, put 20 to CX, to be used as try
// times.
    movw    $20, %cx
1:

// Put the try times in CX to `A20Tries`.
    # Times to try to make this work
    movw    %cx, %cs:(A20Tries - base_addr)(%bp)

    /* save original IF, DF */
// Push EFLAGS.
    pushfw
// Pop EFLAGS to `A20Flags`.
    popw    %cs:(A20Flags - base_addr)(%bp)

a20_try_again:

    ######################################################################
    ## If the A20 type is known, jump straight to type
    ######################################################################

// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
// Put `A20Type` to SI.
// The initial value is `A20_DUNNO`.
    movw    %cs:(A20Type - base_addr)(%bp), %si
// Put BP to BX.
    movw    %bp, %bx
// Put routine address `A20List[SI]` to BX.
    addw    %cs:(A20List - base_addr)(%bp, %si), %bx
// Jump to the routine.
    jmp *%bx

    ######################################################################
    ## First, see if we are on a system with no A20 gate
    ######################################################################
    /*
     * If the system has no A20 gate, then we needn't enable it and could
     * return SUCCESS right now without calling A20_TEST.
     */
a20_none:
// Test whether DL is 0.
    testb   %dl, %dl
// If DL is 0 which means to disable A20, jump.
    jz  a20_done_fail
// If DL is not 0 which means to enable A20, next.
//  cmpb    %dl, %dl    # set ZF=1 for success
//  jmp a20_done

a20_dunno:
    //movb  $A20_DUNNO, %cs:(A20Type - base_addr)(%bp)
// Print.
    call    a20_debug_print

// Push ECX.
    pushl   %ecx
// Put 2 to ECX, to be used as try times for `a20_test_match` below.
    movl    $0x2, %ecx
// Test whether A20 is in desired state.
    call    a20_test_match
// Pop old ECX.
    popl    %ecx
    /* ZF=1(means equal) for desired and we needn't do anything. */
// If ZF is 1 which means A20 is in desired state, jump.
    jz  a20_done
// If ZF is 0 which means A20 is not in desired state, next.

    #######################################################
    ## Next, try the BIOS (INT 15h AX=240xh)
    #######################################################
a20_bios:
#if 0
    /* dell hangs on the A20 BIOS call, so we avoid calling it. */

// Test whether DL is 0.
    testb   %dl, %dl
// If DL is 0 which means to disable A20, jump.
    jz  1f

// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
// Put `$A20_BIOS` to `A20Type`.
    movb    $A20_BIOS, %cs:(A20Type - base_addr)(%bp)
// Print.
    call    a20_debug_print
1:
// Push BP.
    pushw   %bp     /* in case it is destroyed by int 15 */
// Push DX.
    pushw   %dx     /* in case it is destroyed by int 15 */
// Push EFLAGS.
    pushfw          # Some BIOSes muck with IF

// Test whether DL is 0.
    testb   %dl, %dl
// Put ~ZF to AL.
    setnz   %al
// Put 0x24 to AH to specify interrupt 0x15 AH=0x24 service.
    movb    $0x24, %ah

.ifdef int13_handler
.ifdef ROM_int15
    /* we are inside asm.S */
// Push EFLAGS.
    pushfw
// Call `ROM_int15`.
    lcall   %cs:*(ROM_int15 - int13_handler)
.else
// Invoke interrupt 0x15 AH=0x24 service.
    int $0x15
.endif
.else
// Invoke interrupt 0x15 AH=0x24 service.
    int $0x15
.endif

// Pop old EFLAGS.
    popfw
// Pop old DX.
    popw    %dx
// Pop old BP.
    popw    %bp

// Push ECX.
    pushl   %ecx
// Put 2 to ECX, to be used as try times for `a20_test_match` below.
    movl    $0x2, %ecx
// Test whether A20 is in desired state.
    call    a20_test_match
// Pop old ECX.
    popl    %ecx
    /* ZF=1(means equal) for desired and we needn't do anything. */
// If ZF is 1 which means A20 is in desired state, jump.
    jz  a20_done
// If ZF is 0 which means A20 is not in desired state, next.
#endif
    #######################################################
    ## Enable the keyboard controller A20 gate
    #######################################################
a20_kbc:
// Empty 8042 controller's input and output buffers.
    call    empty_8042

// Push EFLAGS.
    pushfw          # ZF=0 indicates there is no 8042
// Push ECX.
    pushl   %ecx
// Put 2 to ECX, to be used as try times for `a20_test_match` below.
    movl    $0x2, %ecx
// Test whether A20 is in desired state.
    call    a20_test_match
// Pop old ECX.
    popl    %ecx
// Pop old EFLAGS to AX.
    popw    %ax     # flags
    /* ZF=1(means equal) for desired and we needn't do anything. */
// If ZF is 1 which means A20 is in desired state, jump.
    jz  a20_done    # A20 live, no need to use KBC
// If ZF is 0 which means A20 is not in desired state, next.

// Push old EFLAGS in AX.
    pushw   %ax     # flags
// Pop old EFLAGS.
    popfw
// If ZF is 0 which means no 8042 controller, jump.
    jnz a20_fast    # failure, no 8042, try next
// If ZF is 1 which means have 8042 controller, next.

// Test whether DL is 0.
    testb   %dl, %dl
// If DL is 0 which means to disable A20, jump.
    jz  1f
// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
// Put `$A20_KBC` to `A20Type`.
    movb    $A20_KBC, %cs:(A20Type - base_addr)(%bp)
// Print.
    call    a20_debug_print
1:
// Put 0xD1 to AL.
    movb    $0xD1, %al  # 8042 command byte to write output port
// Put 0xD1 to port 0x64.
    outb    %al, $0x64  # write command to port 64h
// Empty 8042 controller's input and output buffers.
    call    empty_8042

// Put 0xDD to AL.
    movb    $0xDD, %al  # 0xDD is for disable, 0xDF is for enable
// Test whether DL is 0.
    testb   %dl, %dl
// Put ~ZF to AH.
    setne   %ah
// Left-shift AH by 1.
    shlb    $1, %ah
// Merge AH to AL.
    orb %ah, %al
// Put AL to port 0x60.
    outb    %al, $0x60
// Empty 8042 controller's input and output buffers.
    call    empty_8042

// Push ECX.
    pushl   %ecx
// Put 2 to ECX, to be used as try times for `a20_test_match` below.
    movl    $0x2, %ecx
// Test whether A20 is in desired state.
    call    a20_test_match
// Pop old ECX.
    popl    %ecx
    /* ZF=1(means equal) for desired and we needn't do anything. */
// Push EFLAGS.
    pushfw          # ZF=1 for "A20 is OK"

    /* output a dummy command (USB keyboard hack) */
// Put 0xFF to AL.
    movb    $0xFF, %al
// Put 0xFF to port 0x64.
    outb    %al, $0x64
// Empty 8042 controller's input and output buffers.
    call    empty_8042

// Pop old EFLAGS.
    popfw           # ZF=1 for "A20 is OK"
// If ZF is 1 which means A20 is in desired state, jump.
    jz  a20_done    # A20 live, no need to use KBC
// If ZF is 0 which means A20 is not in desired state, next.

// Push ECX.
    pushl   %ecx
// Put 2 to ECX, to be used as try times for `a20_test_match` below.
    movl    $0x2, %ecx  # 0x200000 is too big
// Test whether A20 is in desired state.
    call    a20_test_match
// Pop ECX.
    popl    %ecx
    /* ZF=1(means equal) for desired and we needn't do anything. */
// If ZF is 1 which means A20 is in desired state, jump.
    jz  a20_done
// If ZF is 0 which means A20 is not in desired state, next.

    ######################################################################
    ## Fast A20 Gate: System Control Port A
    ######################################################################

a20_fast:
// Put port 0x92 to AL.
    inb $0x92, %al
// Test whether DL is 0.
    testb   %dl, %dl
// If DL is 0 which means to disable A20, jump.
    jz  2f
    /* enable a20 */
// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
// Put `$A20_FAST` to `A20Type`.
    movb    $A20_FAST, %cs:(A20Type - base_addr)(%bp)
// Print.
    call    a20_debug_print
// Test whether bit 1 is on in AL.
    testb   $0x02, %al
// If bit 1 is on in AL, jump.
    jnz 1f      # chipset bug: do nothing if already set
// Turn on bit 1 in AL.
    orb $0x02, %al  # "fast A20" version
// Turn off bit 0 in AL.
    andb    $0xFE, %al  # don't accidentally reset the cpu
// Jump.
    jmp 3f
2:
    /* disable a20 */
// Test whether bit 1 is on in AL.
    testb   $0x02, %al
// If bit 1 is on in AL, jump.
    jz  1f      # chipset bug: do nothing if already cleared
// Turn off bits 0 and 1 in AL.
    andb    $0xFC, %al  # don't accidentally reset the cpu
3:
// Put AL to port 0x92.
    outb    %al, $0x92
1:

// Push ECX.
    pushl   %ecx
// Put 8 to ECX, to be used as try times for `a20_test_match` below.
    movl    $0x8, %ecx  # 0x200000 is too big
// Test whether A20 is in desired state.
    call    a20_test_match
// Pop old ECX.
    popl    %ecx
    /* ZF=1(means equal) for desired and we needn't do anything. */
// If ZF is 1 which means A20 is in desired state, jump.
    jz  a20_done
// If ZF is 0 which means A20 is not in desired state, next.

    #==================================================================
    #   A20 is not responding.  Try again.
    #==================================================================

    /* A20Type is now A20_FAST, so it must be reset!! */
// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
// Put `$A20_DUNNO` to `A20Type`.
    movb    $A20_DUNNO, %cs:(A20Type - base_addr)(%bp)
// Print.
    call    a20_debug_print
// Decrement `A20Tries`.
    decw    %cs:(A20Tries - base_addr)(%bp)
// If A20Tries is not 0, jump to re-try.
    jnz a20_try_again
// If A20Tries is 0, next.

    #==================================================================
    #   Finally failed.
    #==================================================================

// Test whether DL is 0.
    testb   %dl, %dl
// If DL is not 0 which means to enable A20, jump.
    jnz a20_done_fail
// If DL is 0 which means to disable A20, next.
    /* We cannot disable it, so consider there is no A20 gate. */
// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
// Put `$A20_NONE` to `A20Type`.
    movb    $A20_NONE, %cs:(A20Type - base_addr)(%bp)

a20_done_fail:
// Increment DX. ZF becomes 0.
    incw    %dx     # set ZF=0 for failure

a20_done:
// Push EFLAGS.
    pushfw
    /* print "done!" to show that a return was executed */
// Test whether DH is 0.
    testb   %dh, %dh
// If DH is 0 which means debug is off, jump.
    jz  1f
// If DH is 1 which means debug is on, next.
// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
// Put `A20DbgMsgEnd`'s runtime address to SI.
    leaw    (A20DbgMsgEnd - base_addr)(%bp), %si    /* CS:SI is string */
// Print message at `A20DbgMsgEnd`.
    call    a20_print_string
1:
// Pop old EFLAGS.
    popfw
//  popal
// Restore registers.
    movl    %cs:(A20_old_eax - base_addr)(%bp), %eax
    movl    %cs:(A20_old_ebx - base_addr)(%bp), %ebx
    movl    %cs:(A20_old_ecx - base_addr)(%bp), %ecx
    movl    %cs:(A20_old_edx - base_addr)(%bp), %edx
    movl    %cs:(A20_old_esi - base_addr)(%bp), %esi
    movl    %cs:(A20_old_edi - base_addr)(%bp), %edi
// Push return address.
    pushw   %cs:(A20ReturnAddress - base_addr)(%bp)   /* return address */
// Restore EBP.
    movl    %cs:(A20_old_ebp - base_addr)(%bp), %ebp  /* restore EBP */
// Return.
    ret


//////////////////////////////////////////////////////////////////////////////
//
//  /////////////////////////////////////////////////////////
//  //
//  //  Subroutines begin here
//  //
//  /////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////


    ######################################################################
    ## This routine tests if A20 status matches the desired.
    ######################################################################

// Test whether A20 is in desired state.
// Input DL: 0 for A20 off, not 0 for A20 on.
// Output ZF: 0 for not in desired state, 1 for in desired state.
a20_test_match:
1:
// Test whether A20 is on.
    call    a20_test
// Push AX.
    pushw   %ax
// Put ZF to AL.
    sete    %al     /* save ZF to AL */
// Test whether DL is 0.
    testb   %dl, %dl
// Put ZF to AH.
    sete    %ah     /* save ZF to AH */
// Compare AL with AH.
// If ZF becomes 1, it means A20 is in desired state.
    cmpb    %al, %ah
// Pop old AX.
    popw    %ax
// Loop if ECX is not 0 and ZF is 0.
    /*ADDR32*/ loopnz   1b  /* dec ECX */
    /* ZF=1(means equal) for match */
// Return.
    ret

    ######################################################################
    ## This routine tests if A20 is enabled (ZF = 0).  This routine
    ## must not destroy any register contents.
    ######################################################################

// Test whether A20 is on.
// Output ZF: 0 for A20 off, 1 for A20 on.
a20_test:

    /******************************************************************/
    /* Don't call a20_debug_print! The A20_tmp_ variables are shared! */
    /******************************************************************/

// Push EBP.
    pushl   %ebp
// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
// Pop old EBP to `A20_tmp_ebp`.
    /* save original EBP */
    popl    %cs:(A20_tmp_ebp - base_addr)(%bp)

// Pop return addres to `A20_tmp_ReturnAddress`.
    /* save a20_test return address */
    popw    %cs:(A20_tmp_ReturnAddress - base_addr)(%bp)

// Store registers.
    movl    %eax, %cs:(A20_tmp_eax - base_addr)(%bp)
    movl    %ebx, %cs:(A20_tmp_ebx - base_addr)(%bp)
    movl    %ecx, %cs:(A20_tmp_ecx - base_addr)(%bp)
    movl    %edx, %cs:(A20_tmp_edx - base_addr)(%bp)
    movl    %esi, %cs:(A20_tmp_esi - base_addr)(%bp)
    movl    %edi, %cs:(A20_tmp_edi - base_addr)(%bp)
    movw    %ds, %cs:(A20_tmp_ds - base_addr)(%bp)
    movw    %es, %cs:(A20_tmp_es - base_addr)(%bp)

    //pushl %eax
    //pushw %cx
    //pushw %ds
    //pushw %es
    //pushw %si
    //pushw %di

// Push EFLAGS
    pushfw              /* save old IF, DF */

#if DISABLE_CPU_CACHE

    /* disable CPU cache for the test to work reliably. */

// Put 0 to IF to disable maskable interrupts.
    cli
// Put CR0 to EAX.
    movl    %cr0, %eax

// Push CR0 in EAX.
    pushl   %eax            /* save old cr0 */

// Turn on bit NW (not write through) and CD (cache disable) in EAX.
    orl $0x60000000, %eax   /* set CD and NW */
// Put EAX to CR0 to disable CPU cache.
    movl    %eax, %cr0
// Jump.
    jmp 1f
1:
// Put CR0 to EAX.
    movl    %cr0, %eax
// Test whether bits NW and CD are on.
    testl   $0x60000000, %eax   /* check if we can use wbinvd. */
// If both bits are off which means the CPU not supports wbinvd instruction,
// jump.
    jz  1f          /* CPU has no wbinvd instruction. */
// If the CPU supports wbinvd instruction, run wbinvd instruction to flush
// cache.
    wbinvd
// Turn off bit NW in EAX.
    andl    $0xDFFFFFFF, %eax   /* clear NW */
// Put EAX to CR0.
    movl    %eax, %cr0
// Jump.
    jmp 1f
1:
#endif
// Put 1 to IF to enable maskable interrupts.
    sti
// Put 0 to AX.
    xorw    %ax, %ax
// Put 0 to DS.
    movw    %ax, %ds    /* DS=0 */
// Put 0xFFFF to AX.
    decw    %ax
// Put 0xFFFF to ES.
    movw    %ax, %es    /* ES=0xFFFF */

// Put `0xFFF0 / 4` to CX, to be used as count for `cmpsl` below.
// `0xFFF0` is the number of bytes to compare.
// Because DI is set to 0x0010 below, comparing 0xFFF0 times will compare the
// range [0x0010, 0xFFFF].
// Dividing by 4 is because the `cmpsl` below compares 4 bytes each time.
    movw    $(0xFFF0 / 4), %cx
// Put 0 to SI.
    xorw    %si, %si
// Put 0x0010 to DI.
// FFFF:000F is the last address before overflow when A20 is disabled.
// FFFF:0010 is the first address after overflow when A20 is disabled.
    movw    $0x0010, %di
// Put 0 to DF. When DF is 0, string operations increment SI and DI.
    cld
// Compare range [0000:0000, 0000:FFEF] with range [FFFF:0010, FFFF:FFFF].
    repz cmpsl
// If not equal which means A20 is enabled, jump.
    jne 1f      /* A20 is known to be enabled */
// If equal which means A20 is probably disabled.

    /* A20 status unknown */

// Put [0000:0200] to EAX.
    movl    0x200, %eax
// Push [0000:0200] in EAX.
    pushl   %eax        /* save old int 0x80 vector */

// Put 32 to CX, to be used as loop count below.
    movw    $32, %cx    # Loop count
    //cli           /* safe to touch int 0x80 vector */
2:
// Declare a spin-wait loop.
    pause
// Increment EAX.
    incl    %eax
// Put EAX to [0000:0200].
    movl    %eax, 0x200
// Delay.
    call    delay       # Serialize, and fix delay
// Declare a spin-wait loop.
    pause
// Compare [FFFF:0210] with [0000:0200] in EAX.
// [FFFF:0210] overflows to [0000:0200] when A20 is disabled.
// If equal, ZF becomes 1.
    cmpl    %es:0x210, %eax
// Loop if CX is not 0 and ZF is 1.
    loopz   2b
// If ZF is 0, it means A20 is on.
// If ZF is 1, it means A20 is off.


// Pop old [0000:0200] to EAX.
    popl    %eax        /* restore int 0x80 vector */
// Put EAX to [0000:0200].
    movl    %eax, 0x200
1:
    //sti
    /* ZF=0(means not equal) for A20 on, ZF=1(means equal) for A20 off. */

#if DISABLE_CPU_CACHE
// Put 0 to IF to disable maskable interrupts.
    cli
// Pop old CR0 to EAX.
    popl    %eax        /* restore cr0 */
// Put old CR0 to CR0.
    movl    %eax, %cr0
// Jump.
    jmp 1f
1:
#endif

// Put (SF:ZF:0:AF:0:PF:1:CF) to AH.
    lahf            /* Load Flags into AH Register. */
                /* AH = SF:ZF:xx:AF:xx:PF:xx:CF */

// Pop old EFLAGS to EFLAGS.
    popfw           /* restore IF, DF */
// Put (SF:ZF:0:AF:0:PF:1:CF) in AH to EFLAGS.
    sahf            /* update ZF */

    //popw  %di
    //popw  %si
    //popw  %es
    //popw  %ds
    //popw  %cx
    //popl  %eax
// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
// Restore registers.
    movl    %cs:(A20_tmp_eax - base_addr)(%bp), %eax
    movl    %cs:(A20_tmp_ebx - base_addr)(%bp), %ebx
    movl    %cs:(A20_tmp_ecx - base_addr)(%bp), %ecx
    movl    %cs:(A20_tmp_edx - base_addr)(%bp), %edx
    movl    %cs:(A20_tmp_esi - base_addr)(%bp), %esi
    movl    %cs:(A20_tmp_edi - base_addr)(%bp), %edi
    movw    %cs:(A20_tmp_ds - base_addr)(%bp), %ds
    movw    %cs:(A20_tmp_es - base_addr)(%bp), %es
    pushw   %cs:(A20_tmp_ReturnAddress - base_addr)(%bp)
    movl    %cs:(A20_tmp_ebp - base_addr)(%bp), %ebp  /* restore EBP */
// Return.
    ret

//slow_out:
//  outb    %al, %dx    # Fall through

delay:
    //pushw %ax
    //movb  $0x80, %al  /* try to write only a known value to port */
    //aam   //outb  %al, $IO_DELAY_PORT
    //aam   //outb  %al, $IO_DELAY_PORT
    //popw  %ax
// Push CX.
    pushw   %cx
// Put 8 to CX, to be used as loop count below.
    movw    $8, %cx
1:
// Declare a spin-wait loop.
    pause
// Loop if CX is not 0.
    loop    1b
// Pop old CX.
    popw    %cx
// Return.
    ret

    ######################################################################
    ##
    ## Print A20Tries, A20Type
    ##
    ######################################################################

a20_debug_print:
// Test whether DH is 0.
    testb   %dh, %dh    /* debug mode? */
// If DH is not 0 which means debug is on, jump.
    jnz 1f      /* yes, continue */
// If DH is 0 which means debug is off, next.
// Return.
    ret
1:
    //pushal
// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
// Store registers.
    movl    %eax, %cs:(A20_tmp_eax - base_addr)(%bp)
    movl    %ebx, %cs:(A20_tmp_ebx - base_addr)(%bp)
    movl    %ecx, %cs:(A20_tmp_ecx - base_addr)(%bp)
    movl    %edx, %cs:(A20_tmp_edx - base_addr)(%bp)
    movl    %esi, %cs:(A20_tmp_esi - base_addr)(%bp)
    movl    %edi, %cs:(A20_tmp_edi - base_addr)(%bp)
    movw    %ds, %cs:(A20_tmp_ds - base_addr)(%bp)
    movw    %es, %cs:(A20_tmp_es - base_addr)(%bp)
    movw    %fs, %cs:(A20_tmp_fs - base_addr)(%bp)
    movw    %gs, %cs:(A20_tmp_gs - base_addr)(%bp)

// Put `A20Tries` to AL.
    movb    %cs:(A20Tries - base_addr)(%bp), %al        /* A20Tries */
// Convert `A20Tries` in AL to hex in AX.
    call    a20_hex
// Put `A20Tries` hex in AX to `A20DbgMsgTryHex`.
    movw    %ax, %cs:(A20DbgMsgTryHex - base_addr)(%bp) /* A20Tries */

// Put `A20Type` to AL.
    movb    %cs:(A20Type - base_addr)(%bp), %al     /* A20Type */
// Convert `A20Type` in AL to hex in AX.
    call    a20_hex
// Put `A20Type` hex in AX to `A20DbgMsgTryHex + 2`.
    movw    %ax, %cs:(A20DbgMsgTryHex - base_addr + 2)(%bp) /* A20Type */

// Put `A20DbgMsgTry`'s address to SI.
    leaw    (A20DbgMsgTry - base_addr)(%bp), %si    /* CS:SI is string */
// Print message at `A20DbgMsgTry`.
    call    a20_print_string
// Put `base_addr`'s runtime address to BP.
    call    assign_base_pointer /* BP points to base_addr */
// Restore registers.
    movl    %cs:(A20_tmp_eax - base_addr)(%bp), %eax
    movl    %cs:(A20_tmp_ebx - base_addr)(%bp), %ebx
    movl    %cs:(A20_tmp_ecx - base_addr)(%bp), %ecx
    movl    %cs:(A20_tmp_edx - base_addr)(%bp), %edx
    movl    %cs:(A20_tmp_esi - base_addr)(%bp), %esi
    movl    %cs:(A20_tmp_edi - base_addr)(%bp), %edi
    movw    %cs:(A20_tmp_ds - base_addr)(%bp), %ds
    movw    %cs:(A20_tmp_es - base_addr)(%bp), %es
    movw    %cs:(A20_tmp_fs - base_addr)(%bp), %fs
    movw    %cs:(A20_tmp_gs - base_addr)(%bp), %gs
    //popal
// Return.
    ret

    /************************************************/
    /* print ASCIZ string CS:SI (modifies AX BX SI) */
    /************************************************/
3:
// Put 0 to BX, which means page 0 for interrupt 0x10 video service.
    xorw    %bx, %bx    /* video page 0 */
// Put 0x0E to AH, which means TTY mode for interrupt 0x10 video service.
    movb    $0x0e, %ah  /* print char in AL */
// Invoke interrupt 0x10 video service to print the character in AL.
    int $0x10       /* via TTY mode */

a20_print_string:

// Put the character in [DS:SI] to AL.
// SI is incremented.
    lodsb   %cs:(%si), %al  /* get token */
// Test whether AL is 0 which means end of string.
    cmpb    $0, %al     /* end of string? */
// If AL is not 0, jump to print the next character.
    jne 3b
// If AL is 0, next.
// Return.
    ret

    /****************************************/
    /* convert AL to hex ascii number in AX */
    /****************************************/

a20_hex:
// Put AL to AH.
    movb    %al, %ah
// Right-shift AL by 4.
// Now AL stores the higher 4 bits.
    shrb    $4, %al
// Turn off higher 4 bits in AH.
// Now AH stores the lower 4 bits.
    andb    $0x0F, %ah
// Add 0x30 to AH and AL.
// 0x30 is '0'.
    orw $0x3030, %ax

    /* least significant digit in AH */
// Compare AH with '9'
    cmpb    $0x39, %ah
// If AH <= '9', jump.
    jbe 1f
// If AH > '9', add 7 to AH.
// There are 7 other characters between '9' and 'A'.
    addb    $7, %ah
1:
    /* most significant digit in AL */
// Compare AL with '9'
    cmpb    $0x39, %al
// If AL <= '9', jump.
    jbe 1f
// If AL > '9', add 7 to AL.
// There are 7 other characters between '9' and 'A'.
    addb    $7, %al
1:
// Return.
    ret

    ######################################################################
    ##
    ## Routine to empty the 8042 KBC controller. Return ZF=0 on failure.
    ##
    ######################################################################

empty_8042:
// Push ECX.
    pushl   %ecx
// Move 10000 to ECX, to be used as try times below.
    movl    $10000, %ecx    # 100000 is too big
4:
// Delay.
    call    delay

// Put port 0x64 to AL.
    inb $0x64, %al  # read 8042 status from port 64h
// Test whether bit 0 is on in AL.
    testb   $1, %al     # is output buffer(data FROM keyboard) full?
// If bit 0 is on which means keyboard output buffer is not empty, jump.
    jnz 1f      # yes, read it and discard
// If bit 0 is off which means keyboard output buffer is empty, next.
// Test whether bit 1 is on in AL.
    testb   $2, %al     # is input buffer(data TO keyboard) empty?
// If bit 1 is on which means keyboard input buffer is not empty, jump.
    jnz 2f      # no, wait until time out
// If bit 1 is off which means keyboard input buffer is empty, jump.
    jmp 3f      # both input buffer and output buffer are empty
                # success and return with ZF=1
1:
// Delay.
    call    delay       # ZF=0, DELAY should not touch flags!!
// Put port 0x60 (reading keyboard output buffer) to AL.
    inb $0x60, %al  # read output buffer and discard input
                # data/status from 8042
2:
// Loop if ECX is not 0.
    /*ADDR32*/ loop 4b  # ZF=0
                # timed out and failure, return with ZF=0
3:
// Pop old ECX.
    popl    %ecx
// Return.
    ret

    /* a20 debug message. 25 backspaces to wipe out the previous
     * "A20 Debug: XXXX trying..." message.
     */
A20DbgMsgTry:
    .ascii  "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bA20 Debug: "
A20DbgMsgTryHex:
    .string "XXXX trying..."        // null terminated

    /* a20 done message. 9 backspaces to wipe out the previous
     * "trying..." message.
     */
A20DbgMsgEnd:
    .string "\b\b\b\b\b\b\b\b\bdone! "  // null terminated

    .align  2

// A list of routine addresses. `A20Type` is used as the index.
A20List:
    .word   a20_dunno - base_addr
    .word   a20_none - base_addr
    .word   a20_bios - base_addr
    .word   a20_kbc - base_addr
    .word   a20_fast - base_addr
A20Type:
    .word   A20_DUNNO   // default = unknown
A20Tries:
    .word   0       // Times until giving up on A20

    /* Just in case INT 15 might have destroyed the stack... */
A20Flags:
    .word   0       // save original Flags here
A20ReturnAddress:
    .word   0       // save original return address here
A20_tmp_ReturnAddress:
    .word   0       // save a20_test return address here

    .align  4

A20_old_ebp:    .long   0
A20_old_eax:    .long   0
A20_old_ebx:    .long   0
A20_old_ecx:    .long   0
A20_old_edx:    .long   0
A20_old_esi:    .long   0
A20_old_edi:    .long   0
A20_tmp_ebp:    .long   0
A20_tmp_eax:    .long   0
A20_tmp_ebx:    .long   0
A20_tmp_ecx:    .long   0
A20_tmp_edx:    .long   0
A20_tmp_esi:    .long   0
A20_tmp_edi:    .long   0
A20_tmp_ds: .word   0
A20_tmp_es: .word   0
A20_tmp_fs: .word   0
A20_tmp_gs: .word   0
Prev Post:
Next Post:

Comments:

Reply to: