blob: 7b98944fdfc5f499668d47eca22ced3bc0c8a994 [file] [log] [blame]
;; -----------------------------------------------------------------------
;;
;; Copyright 1994-2009 H. Peter Anvin - All Rights Reserved
;; Copyright 2009 Intel Corporation; author: H. Peter Anvin
;;
;; 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.
;;
;; -----------------------------------------------------------------------
;;
;; pm.inc
;;
;; Functions to enter and exit 32-bit protected mode, handle interrupts
;; and cross-mode calls.
;;
;; PM refers to 32-bit flat protected mode; RM to 16-bit real mode.
;;
bits 16
section .text16
;
; _pm_call: call PM routine in low memory from RM
;
; on stack = PM routine to call (a 32-bit address)
;
; ECX, ESI, EDI passed to the called function;
; EAX = EBP in the called function points to the stack frame
; which includes all registers (which can be changed if desired.)
;
; All registers and the flags saved/restored
;
; This routine is invoked by the pm_call macro.
;
_pm_call:
pushfd
pushad
push ds
push es
push fs
push gs
mov bp,sp
mov ax,cs
mov ebx,.pm
mov ds,ax
jmp enter_pm
bits 32
section .textnr
.pm:
; EAX points to the top of the RM stack, which is EFLAGS
test RM_FLAGSH,02h ; RM EFLAGS.IF
jz .no_sti
sti
.no_sti:
call [ebp+4*2+9*4+2] ; Entrypoint on RM stack
mov bx,.rm
jmp enter_rm
bits 16
section .text16
.rm:
pop gs
pop fs
pop es
pop ds
popad
popfd
ret 4 ; Drop entrypoint
;
; enter_pm: Go to PM with interrupt service configured
; EBX = PM entry point
; EAX = EBP = on exit, points to the RM stack as a 32-bit value
; ECX, EDX, ESI, EDI preserved across this routine
;
; Assumes CS == DS
;
; This routine doesn't enable interrupts, but the target routine
; can enable interrupts by executing STI.
;
bits 16
section .text16
enter_pm:
cli
xor eax,eax
mov ds,ax
mov ax,ss
mov [RealModeSSSP],sp
mov [RealModeSSSP+2],ax
movzx ebp,sp
shl eax,4
add ebp,eax ; EBP -> top of real-mode stack
cld
call enable_a20
.a20ok:
mov byte [bcopy_gdt.TSS+5],89h ; Mark TSS unbusy
lgdt [bcopy_gdt] ; We can use the same GDT just fine
lidt [PM_IDT_ptr] ; Set up the IDT
mov eax,cr0
or al,1
mov cr0,eax ; Enter protected mode
jmp PM_CS32:.in_pm
bits 32
section .textnr
.in_pm:
xor eax,eax ; Available for future use...
mov fs,eax
mov gs,eax
lldt ax
mov al,PM_DS32 ; Set up data segments
mov es,eax
mov ds,eax
mov ss,eax
mov al,PM_TSS ; Be nice to Intel's VT by
ltr ax ; giving it a valid TR
mov esp,[PMESP] ; Load protmode %esp
mov eax,ebp ; EAX -> top of real-mode stack
jmp ebx ; Go to where we need to go
;
; enter_rm: Return to RM from PM
;
; BX = RM entry point (CS = 0)
; ECX, EDX, ESI, EDI preserved across this routine
; EAX clobbered
; EBP reserved
;
; This routine doesn't enable interrupts, but the target routine
; can enable interrupts by executing STI.
;
bits 32
section .textnr
enter_rm:
cli
cld
mov [PMESP],esp ; Save exit %esp
jmp PM_CS16:.in_pm16 ; Return to 16-bit mode first
bits 16
section .text16
.in_pm16:
mov ax,PM_DS16 ; Real-mode-like segment
mov es,ax
mov ds,ax
mov ss,ax
mov fs,ax
mov gs,ax
lidt [RM_IDT_ptr] ; Real-mode IDT (rm needs no GDT)
xor dx,dx
mov eax,cr0
and al,~1
mov cr0,eax
jmp 0:.in_rm
.in_rm: ; Back in real mode
lss sp,[cs:RealModeSSSP] ; Restore stack
movzx esp,sp ; Make sure the high bits are zero
mov ds,dx ; Set up sane segments
mov es,dx
mov fs,dx
mov gs,dx
jmp bx ; Go to whereever we need to go...
section .data16
alignz 4
extern __stack_end
PMESP dd __stack_end ; Protected-mode ESP
PM_IDT_ptr: dw 8*256-1 ; Length
dd IDT ; Offset
;
; This is invoked on getting an interrupt in protected mode. At
; this point, we need to context-switch to real mode and invoke
; the interrupt routine.
;
; When this gets invoked, the registers are saved on the stack and
; AL contains the register number.
;
bits 32
section .textnr
pm_irq:
pushad
movzx esi,byte [esp+8*4] ; Interrupt number
inc dword [CallbackCtr]
mov ebx,.rm
jmp enter_rm ; Go to real mode
bits 16
section .text16
.rm:
pushf ; Flags on stack
call far [cs:esi*4] ; Call IVT entry
mov ebx,.pm
jmp enter_pm ; Go back to PM
bits 32
section .textnr
.pm:
dec dword [CallbackCtr]
jnz .skip
call [core_pm_hook]
.skip:
popad
add esp,4 ; Drop interrupt number
iretd
;
; Initially, the core_pm_hook does nothing; it is available for the
; threaded derivatives to run the scheduler, or examine the result from
; interrupt routines.
;
global core_pm_null_hook
core_pm_null_hook:
ret
section .data16
alignz 4
global core_pm_hook
core_pm_hook: dd core_pm_null_hook
bits 16
section .text16
;
; 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.
;
section .data16
alignz 2
A20Ptr dw a20_dunno
section .bss16
alignb 4
A20Test resd 1 ; Counter for testing A20 status
A20Tries resb 1 ; Times until giving up on A20
section .text16
enable_a20:
pushad
mov byte [cs:A20Tries],255 ; Times to try to make this work
try_enable_a20:
;
; First, see if we are on a system with no A20 gate, or the A20 gate
; is already enabled for us...
;
a20_none:
call a20_test
jnz a20_done
; Otherwise, see if we had something memorized...
jmp word [cs:A20Ptr]
;
; Next, try the BIOS (INT 15h AX=2401h)
;
a20_dunno:
a20_bios:
mov word [cs:A20Ptr], 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 word [cs:A20Ptr], a20_kbc ; Starting KBC command sequence
mov al,0D1h ; Write output port
out 064h, al
call empty_8042_uncond
mov al,0DFh ; A20 on
out 060h, al
call empty_8042_uncond
; Apparently the UHCI spec assumes that A20 toggle
; ends with a null command (assumed to be for sychronization?)
; Put it here to see if it helps anything...
mov al,0FFh ; Null command
out 064h, 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 word [cs:A20Ptr], a20_fast
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 a20_dunno ; Did we get the wrong type?
mov si, err_a20
pm_call pm_writestr
jmp kaboom
section .data16
err_a20 db CR, LF, 'A20 gate not responding!', CR, LF, 0
section .text16
;
; 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.
;
; The no-write early out avoids the io_delay in the (presumably common)
; case of A20 already enabled (e.g. from a previous call.)
;
a20_test:
push es
push cx
push eax
mov cx,0FFFFh ; HMA = segment 0FFFFh
mov es,cx
mov eax,[cs:A20Test]
mov cx,32 ; Loop count
jmp .test ; First iteration = early out
.wait: add eax,0x430aea41 ; A large prime number
mov [cs:A20Test],eax
io_delay ; Serialize, and fix delay
.test: cmp eax,[es:A20Test+10h]
loopz .wait
.done: pop eax
pop cx
pop es
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
;
; This initializes the protected-mode interrupt thunk set
;
section .text16
pm_init:
xor edi,edi
mov bx,IDT
mov di,IRQStubs
mov eax,7aeb006ah ; push byte .. jmp short ..
mov cx,8 ; 8 groups of 32 IRQs
.gloop:
push cx
mov cx,32 ; 32 entries per group
.eloop:
mov [bx],di ; IDT offset [15:0]
mov word [bx+2],PM_CS32 ; IDT segment
mov dword [bx+4],08e00h ; IDT offset [31:16], 32-bit interrupt
; gate, CPL 0 (we don't have a TSS
; set up...)
add bx,8
stosd
; Increment IRQ, decrement jmp short offset
add eax,(-4 << 24)+(1 << 8)
loop .eloop
; At the end of each group, replace the EBxx with
; the final E9xxxxxxxx
add di,3
mov byte [di-5],0E9h ; JMP NEAR
mov edx,pm_irq
sub edx,edi
mov [di-4],edx
add eax,(0x80 << 24) ; Proper offset for the next one
pop cx
loop .gloop
ret
; pm_init is called before bss clearing, so put these
; in .earlybss!
section .earlybss
alignb 8
IDT: resq 256
global RealModeSSSP
RealModeSSSP resd 1 ; Real-mode SS:SP
section .gentextnr ; Autogenerated 32-bit code
IRQStubs: resb 4*256+3*8
section .text16
%include "callback.inc" ; Real-mode callbacks