;; ----------------------------------------------------------------------- ;; ;; 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