blob: 82c11a6ba5ae4dcc85fdca9834576947ba404489 [file] [log] [blame] [raw]
/* real-mode A20 gate control code for grub4dos.
*
* Copyright (C) 2008 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
#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.
pushl %ecx
movl $0x2, %ecx
call a20_test_match
popl %ecx
/* ZF=1(means equal) for desired and we needn't do anything. */
jnz 1f
stc
ret
assign_base_pointer:
call base_addr
base_addr:
popw %bp
ret
1:
/********************************************/
/** Now we have to enable or disable A20 **/
/********************************************/
pushl %ebp
call assign_base_pointer /* BP points to base_addr */
/* save original EBP */
popl %cs:(A20_old_ebp - base_addr)(%bp)
/* save original return address */
popw %cs:(A20ReturnAddress - base_addr)(%bp)
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
movw $200, %cx
testb %dl, %dl
jnz 1f
movw $20, %cx
1:
# Times to try to make this work
movw %cx, %cs:(A20Tries - base_addr)(%bp)
/* save original IF, DF */
pushfw
popw %cs:(A20Flags - base_addr)(%bp)
a20_try_again:
######################################################################
## If the A20 type is known, jump straight to type
######################################################################
call assign_base_pointer /* BP points to base_addr */
movw %cs:(A20Type - base_addr)(%bp), %si
movw %bp, %bx
addw %cs:(A20List - base_addr)(%bp, %si), %bx
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:
testb %dl, %dl
jz a20_done_fail
// cmpb %dl, %dl # set ZF=1 for success
// jmp a20_done
a20_dunno:
//movb $A20_DUNNO, %cs:(A20Type - base_addr)(%bp)
call a20_debug_print
pushl %ecx
movl $0x2, %ecx
call a20_test_match
popl %ecx
/* ZF=1(means equal) for desired and we needn't do anything. */
jz a20_done
#######################################################
## Next, try the BIOS (INT 15h AX=240xh)
#######################################################
a20_bios:
#if 0
/* dell hangs on the A20 BIOS call, so we avoid calling it. */
testb %dl, %dl
jz 1f
call assign_base_pointer /* BP points to base_addr */
movb $A20_BIOS, %cs:(A20Type - base_addr)(%bp)
call a20_debug_print
1:
pushw %bp /* in case it is destroyed by int 15 */
pushw %dx /* in case it is destroyed by int 15 */
pushfw # Some BIOSes muck with IF
testb %dl, %dl
setnz %al
movb $0x24, %ah
.ifdef int13_handler
.ifdef ROM_int15
/* we are inside asm.S */
pushfw
lcall %cs:*(ROM_int15 - int13_handler)
.else
int $0x15
.endif
.else
int $0x15
.endif
popfw
popw %dx
popw %bp
pushl %ecx
movl $0x2, %ecx
call a20_test_match
popl %ecx
/* ZF=1(means equal) for desired and we needn't do anything. */
jz a20_done
#endif
#######################################################
## Enable the keyboard controller A20 gate
#######################################################
a20_kbc:
call empty_8042
pushfw # ZF=0 indicates there is no 8042
pushl %ecx
movl $0x2, %ecx
call a20_test_match
popl %ecx
popw %ax # flags
/* ZF=1(means equal) for desired and we needn't do anything. */
jz a20_done # A20 live, no need to use KBC
pushw %ax # flags
popfw
jnz a20_fast # failure, no 8042, try next
testb %dl, %dl
jz 1f
call assign_base_pointer /* BP points to base_addr */
movb $A20_KBC, %cs:(A20Type - base_addr)(%bp)
call a20_debug_print
1:
movb $0xD1, %al # 8042 command byte to write output port
outb %al, $0x64 # write command to port 64h
call empty_8042
movb $0xDD, %al # 0xDD is for disable, 0xDF is for enable
testb %dl, %dl
setne %ah
shlb $1, %ah
orb %ah, %al
outb %al, $0x60
call empty_8042
pushl %ecx
movl $0x2, %ecx
call a20_test_match
popl %ecx
/* ZF=1(means equal) for desired and we needn't do anything. */
pushfw # ZF=1 for "A20 is OK"
/* output a dummy command (USB keyboard hack) */
movb $0xFF, %al
outb %al, $0x64
call empty_8042
popfw # ZF=1 for "A20 is OK"
jz a20_done # A20 live, no need to use KBC
pushl %ecx
movl $0x2, %ecx # 0x200000 is too big
call a20_test_match
popl %ecx
/* ZF=1(means equal) for desired and we needn't do anything. */
jz a20_done
######################################################################
## Fast A20 Gate: System Control Port A
######################################################################
a20_fast:
inb $0x92, %al
testb %dl, %dl
jz 2f
/* enable a20 */
call assign_base_pointer /* BP points to base_addr */
movb $A20_FAST, %cs:(A20Type - base_addr)(%bp)
call a20_debug_print
testb $0x02, %al
jnz 1f # chipset bug: do nothing if already set
orb $0x02, %al # "fast A20" version
andb $0xFE, %al # don't accidentally reset the cpu
jmp 3f
2:
/* disable a20 */
testb $0x02, %al
jz 1f # chipset bug: do nothing if already cleared
andb $0xFC, %al # don't accidentally reset the cpu
3:
outb %al, $0x92
1:
pushl %ecx
movl $0x8, %ecx # 0x200000 is too big
call a20_test_match
popl %ecx
/* ZF=1(means equal) for desired and we needn't do anything. */
jz a20_done
#==================================================================
# A20 is not responding. Try again.
#==================================================================
/* A20Type is now A20_FAST, so it must be reset!! */
call assign_base_pointer /* BP points to base_addr */
movb $A20_DUNNO, %cs:(A20Type - base_addr)(%bp)
call a20_debug_print
decw %cs:(A20Tries - base_addr)(%bp)
jnz a20_try_again
#==================================================================
# Finally failed.
#==================================================================
testb %dl, %dl
jnz a20_done_fail
/* We cannot disable it, so consider there is no A20 gate. */
call assign_base_pointer /* BP points to base_addr */
movb $A20_NONE, %cs:(A20Type - base_addr)(%bp)
a20_done_fail:
incw %dx # set ZF=0 for failure
a20_done:
pushfw
/* print "done!" to show that a return was executed */
testb %dh, %dh
jz 1f
call assign_base_pointer /* BP points to base_addr */
leaw (A20DbgMsgEnd - base_addr)(%bp), %si /* CS:SI is string */
call a20_print_string
1:
popfw
// popal
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
pushw %cs:(A20ReturnAddress - base_addr)(%bp) /* return address */
movl %cs:(A20_old_ebp - base_addr)(%bp), %ebp /* restore EBP */
ret
//////////////////////////////////////////////////////////////////////////////
//
// /////////////////////////////////////////////////////////
// //
// // Subroutines begin here
// //
// /////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////
######################################################################
## This routine tests if A20 status matches the desired.
######################################################################
a20_test_match:
1:
call a20_test
pushw %ax
sete %al /* save ZF to AL */
testb %dl, %dl
sete %ah /* save ZF to AH */
cmpb %al, %ah
popw %ax
ADDR32 loopnz 1b /* dec ECX */
/* ZF=1(means equal) for match */
ret
######################################################################
## This routine tests if A20 is enabled (ZF = 0). This routine
## must not destroy any register contents.
######################################################################
a20_test:
/******************************************************************/
/* Don't call a20_debug_print! The A20_tmp_ variables are shared! */
/******************************************************************/
pushl %ebp
call assign_base_pointer /* BP points to base_addr */
/* save original EBP */
popl %cs:(A20_tmp_ebp - base_addr)(%bp)
/* save a20_test return address */
popw %cs:(A20_tmp_ReturnAddress - base_addr)(%bp)
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
pushfw /* save old IF, DF */
#if DISABLE_CPU_CACHE
/* disable CPU cache for the test to work reliably. */
cli
movl %cr0, %eax
pushl %eax /* save old cr0 */
orl $0x60000000, %eax /* set CD and NW */
movl %eax, %cr0
jmp 1f
1:
movl %cr0, %eax
testl $0x60000000, %eax /* check if we can use wbinvd. */
jz 1f /* CPU has no wbinvd instruction. */
wbinvd
andl $0xDFFFFFFF, %eax /* clear NW */
movl %eax, %cr0
jmp 1f
1:
#endif
sti
xorw %ax, %ax
movw %ax, %ds /* DS=0 */
decw %ax
movw %ax, %es /* ES=0xFFFF */
movw $(0xFFF0 / 4), %cx
xorw %si, %si
movw $0x0010, %di
cld
repz cmpsl
jne 1f /* A20 is known to be enabled */
/* A20 status unknown */
movl 0x200, %eax
pushl %eax /* save old int 0x80 vector */
movw $32, %cx # Loop count
//cli /* safe to touch int 0x80 vector */
2:
pause
incl %eax
movl %eax, 0x200
call delay # Serialize, and fix delay
pause
cmpl %es:0x210, %eax
loopz 2b
popl %eax /* restore int 0x80 vector */
movl %eax, 0x200
1:
//sti
/* ZF=0(means not equal) for A20 on, ZF=1(means equal) for A20 off. */
#if DISABLE_CPU_CACHE
cli
popl %eax /* restore cr0 */
movl %eax, %cr0
jmp 1f
1:
#endif
lahf /* Load Flags into AH Register. */
/* AH = SF:ZF:xx:AF:xx:PF:xx:CF */
popfw /* restore IF, DF */
sahf /* update ZF */
//popw %di
//popw %si
//popw %es
//popw %ds
//popw %cx
//popl %eax
call assign_base_pointer /* BP points to base_addr */
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 */
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
pushw %cx
movw $8, %cx
1:
pause
loop 1b
popw %cx
ret
######################################################################
##
## Print A20Tries, A20Type
##
######################################################################
a20_debug_print:
testb %dh, %dh /* debug mode? */
jnz 1f /* yes, continue */
ret
1:
//pushal
call assign_base_pointer /* BP points to base_addr */
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)
movb %cs:(A20Tries - base_addr)(%bp), %al /* A20Tries */
call a20_hex
movw %ax, %cs:(A20DbgMsgTryHex - base_addr)(%bp) /* A20Tries */
movb %cs:(A20Type - base_addr)(%bp), %al /* A20Type */
call a20_hex
movw %ax, %cs:(A20DbgMsgTryHex - base_addr + 2)(%bp) /* A20Type */
leaw (A20DbgMsgTry - base_addr)(%bp), %si /* CS:SI is string */
call a20_print_string
call assign_base_pointer /* BP points to base_addr */
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
ret
/************************************************/
/* print ASCIZ string CS:SI (modifies AX BX SI) */
/************************************************/
3:
xorw %bx, %bx /* video page 0 */
movb $0x0e, %ah /* print char in AL */
int $0x10 /* via TTY mode */
a20_print_string:
lodsb %cs:(%si), %al /* get token */
cmpb $0, %al /* end of string? */
jne 3b
ret
/****************************************/
/* convert AL to hex ascii number in AX */
/****************************************/
a20_hex:
movb %al, %ah
shrb $4, %al
andb $0x0F, %ah
orw $0x3030, %ax
/* least significant digit in AH */
cmpb $0x39, %ah
jbe 1f
addb $7, %ah
1:
/* most significant digit in AL */
cmpb $0x39, %al
jbe 1f
addb $7, %al
1:
ret
######################################################################
##
## Routine to empty the 8042 KBC controller. Return ZF=0 on failure.
##
######################################################################
empty_8042:
pushl %ecx
movl $10000, %ecx # 100000 is too big
4:
call delay
inb $0x64, %al # read 8042 status from port 64h
testb $1, %al # is output buffer(data FROM keyboard) full?
jnz 1f # yes, read it and discard
testb $2, %al # is input buffer(data TO keyboard) empty?
jnz 2f # no, wait until time out
jmp 3f # both input buffer and output buffer are empty
# success and return with ZF=1
1:
call delay # ZF=0, DELAY should not touch flags!!
inb $0x60, %al # read output buffer and discard input
# data/status from 8042
2:
ADDR32 loop 4b # ZF=0
# timed out and failure, return with ZF=0
3:
popl %ecx
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
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