You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
471 lines
9.9 KiB
471 lines
9.9 KiB
;; ----------------------------------------------------------------------- |
|
;; |
|
;; 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
|
|
|