blob: 7d049b7335299f3f6cff9c7be848e47b3b89a822 [file] [log] [blame]
;; $Id$
;; -----------------------------------------------------------------------
;;
;; Copyright 1994-2004 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.
;;
;; -----------------------------------------------------------------------
;;
;; bcopy32.inc
;;
;; 32-bit bcopy routine for real mode
;;
;
; 32-bit bcopy routine for real mode
;
; We enter protected mode, set up a flat 32-bit environment, run rep movsd
; and then exit. IMPORTANT: This code assumes cs == 0.
;
; This code is probably excessively anal-retentive in its handling of
; segments, but this stuff is painful enough as it is without having to rely
; on everything happening "as it ought to."
;
; NOTE: this code is relocated into low memory, just after the .earlybss
; segment, in order to support to "bcopy over self" operation.
;
section .bcopy32
align 8
__bcopy_start:
; This is in the .text segment since it needs to be
; contiguous with the rest of the bcopy stuff
bcopy_gdt: dw bcopy_gdt_size-1 ; Null descriptor - contains GDT
dd bcopy_gdt ; pointer for LGDT instruction
dw 0
dd 0000ffffh ; Code segment, use16, readable,
dd 00009b00h ; present, dpl 0, cover 64K
dd 0000ffffh ; Data segment, use16, read/write,
dd 008f9300h ; present, dpl 0, cover all 4G
dd 0000ffffh ; Data segment, use16, read/write,
dd 00009300h ; present, dpl 0, cover 64K
; The rest are used for COM32 only
dd 0000ffffh ; Code segment, use32, readable,
dd 00cf9b00h ; present, dpl 0, cover all 4G
dd 0000ffffh ; Data segment, use32, read/write,
dd 00cf9300h ; present, dpl 0, cover all 4G
bcopy_gdt_size: equ $-bcopy_gdt
;
; bcopy:
; 32-bit copy
;
; Inputs:
; ESI - source pointer
; EDI - target pointer
; ECX - byte count
; DF - zero
;
; Outputs:
; ESI - first byte after source
; EDI - first byte after target
; ECX - zero
;
bcopy: push eax
pushf ; Saves, among others, the IF flag
push gs
push fs
push ds
push es
mov [cs:SavedSSSP],sp
mov [cs:SavedSSSP+2],ss
cli
call enable_a20
o32 lgdt [cs:bcopy_gdt]
mov eax,cr0
or al,1
mov cr0,eax ; Enter protected mode
jmp 08h:.in_pm
.in_pm: mov ax,10h ; Data segment selector
mov es,ax
mov ds,ax
mov al,18h ; "Real-mode-like" data segment
mov ss,ax
mov fs,ax
mov gs,ax
mov al,cl ; Save low bits
shr ecx,2 ; Convert to dwords
a32 rep movsd ; Do our business
; At this point ecx == 0
mov cl,al ; Copy any fractional dword
and cl,3
a32 rep movsb
mov al,18h ; "Real-mode-like" data segment
mov es,ax
mov ds,ax
mov eax,cr0
and al,~1
mov cr0,eax ; Disable protected mode
jmp 0:.in_rm
.in_rm: ; Back in real mode
lss sp,[cs:SavedSSSP]
pop es
pop ds
pop fs
pop gs
call disable_a20
popf ; Re-enables interrupts
pop eax
ret
;
; 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 io_delay call _io_delay
%define IO_DELAY_PORT 80h ; Invalid port (we hope!)
%define disable_wait 32 ; How long to wait for a disable
; 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
slow_out: out dx, al ; Fall through
_io_delay: out IO_DELAY_PORT,al
out IO_DELAY_PORT,al
ret
enable_a20:
pushad
mov byte [cs:A20Tries],255 ; Times to try to make this work
try_enable_a20:
;
; Flush the caches
;
%if DO_WBINVD
call try_wbinvd
%endif
;
; If the A20 type is known, jump straight to type
;
mov bp,[cs:A20Type]
jmp word [cs:bp+A20List]
;
; First, see if we are on a system with no A20 gate
;
a20_dunno:
a20_none:
mov byte [cs:A20Type], A20_NONE
call a20_test
jnz a20_done
;
; Next, try the BIOS (INT 15h AX=2401h)
;
a20_bios:
mov byte [cs:A20Type], A20_BIOS
mov ax,2401h
pushf ; Some BIOSes muck with IF
int 15h
popf
call a20_test
jnz a20_done
;
; Enable the keyboard controller A20 gate
;
a20_kbc:
mov dl, 1 ; Allow early exit
call empty_8042
jnz a20_done ; A20 live, no need to use KBC
mov byte [cs:A20Type], A20_KBC ; Starting KBC command sequence
mov al,0D1h ; Command write
out 064h, al
call empty_8042_uncond
mov al,0DFh ; A20 on
out 060h, al
call empty_8042_uncond
; Verify that A20 actually is enabled. Do that by
; observing a word in low memory and the same word in
; the HMA until they are no longer coherent. Note that
; we don't do the same check in the disable case, because
; we don't want to *require* A20 masking (SYSLINUX should
; work fine without it, if the BIOS does.)
.kbc_wait: push cx
xor cx,cx
.kbc_wait_loop:
call a20_test
jnz a20_done_pop
loop .kbc_wait_loop
pop cx
;
; Running out of options here. Final attempt: enable the "fast A20 gate"
;
a20_fast:
mov byte [cs:A20Type], A20_FAST ; Haven't used the KBC yet
in al, 092h
or al,02h
and al,~01h ; Don't accidentally reset the machine!
out 092h, al
.fast_wait: push cx
xor cx,cx
.fast_wait_loop:
call a20_test
jnz a20_done_pop
loop .fast_wait_loop
pop cx
;
; Oh bugger. A20 is not responding. Try frobbing it again; eventually give up
; and report failure to the user.
;
dec byte [cs:A20Tries]
jnz try_enable_a20
mov si, err_a20
jmp abort_load
;
; A20 unmasked, proceed...
;
a20_done_pop: pop cx
a20_done: popad
ret
;
; This routine tests if A20 is enabled (ZF = 0). This routine
; must not destroy any register contents.
;
a20_test:
push es
push cx
push ax
mov cx,0FFFFh ; HMA = segment 0FFFFh
mov es,cx
mov cx,32 ; Loop count
mov ax,[cs:A20Test]
.a20_wait: inc ax
mov [cs:A20Test],ax
io_delay ; Serialize, and fix delay
cmp ax,[es:A20Test+10h]
loopz .a20_wait
.a20_done: pop ax
pop cx
pop es
ret
disable_a20:
pushad
;
; Flush the caches
;
%if DO_WBINVD
call try_wbinvd
%endif
mov bp,[cs:A20Type]
jmp word [cs:bp+A20DList]
a20d_bios:
mov ax,2400h
pushf ; Some BIOSes muck with IF
int 15h
popf
jmp short a20d_snooze
;
; Disable the "fast A20 gate"
;
a20d_fast:
in al, 092h
and al,~03h
out 092h, al
jmp short a20d_snooze
;
; Disable the keyboard controller A20 gate
;
a20d_kbc:
call empty_8042_uncond
mov al,0D1h
out 064h, al ; Command write
call empty_8042_uncond
mov al,0DDh ; A20 off
out 060h, al
call empty_8042_uncond
; Wait a bit for it to take effect
a20d_snooze:
push cx
mov cx, disable_wait
.delayloop: call a20_test
jz .disabled
loop .delayloop
.disabled: pop cx
a20d_dunno:
a20d_none:
popad
ret
;
; Routine to empty the 8042 KBC controller. If dl != 0
; then we will test A20 in the loop and exit if A20 is
; suddenly enabled.
;
empty_8042_uncond:
xor dl,dl
empty_8042:
call a20_test
jz .a20_on
and dl,dl
jnz .done
.a20_on: io_delay
in al, 064h ; Status port
test al,1
jz .no_output
io_delay
in al, 060h ; Read input
jmp short empty_8042
.no_output:
test al,2
jnz empty_8042
io_delay
.done: ret
;
; Execute a WBINVD instruction if possible on this CPU
;
%if DO_WBINVD
try_wbinvd:
wbinvd
ret
%endif
;
; bcopy_over_self:
;
; This routine is used to copy large blocks of code on top of
; conventional memory (to 0:7c00). We therefore have to move
; necessary code into the trackbuf area before doing the copy,
; and do adjustments to anything except BSS area references.
;
; NOTE: Since PXELINUX relocates itself, put all these
; references in the ".earlybss" segment.
;
; After performing the copy, this routine resets the stack and
; jumps to 0:7c00.
;
; IMPORTANT: This routine does not canonicalize the stack or the
; SS register. That is the responsibility of the caller.
;
; Inputs:
; ESI, EDI, ECX - same as bcopy
; On stack - initial state (fd, ad, ds, es, fs, gs)
;
bcopy_over_self:
call bcopy
pop gs
pop fs
pop es
pop ds
popad
popfd
jmp 0:7c00h
align 2
A20List dw a20_dunno, a20_none, a20_bios, a20_kbc, a20_fast
A20DList dw a20d_dunno, a20d_none, a20d_bios, a20d_kbc, a20d_fast
a20_adjust_cnt equ ($-A20List)/2
A20Type dw A20_NONE ; A20 type
; Total size of .bcopy32 section
alignb 4, db 0 ; Even number of dwords
__bcopy_size equ $-__bcopy_start
section .earlybss
alignb 2
SavedSSSP resd 1 ; Saved real mode SS:SP
A20Test resw 1 ; Counter for testing status of A20
A20Tries resb 1 ; Times until giving up on A20