diff options
Diffstat (limited to 'contrib/syslinux-4.02/core/pm.inc')
-rw-r--r-- | contrib/syslinux-4.02/core/pm.inc | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/contrib/syslinux-4.02/core/pm.inc b/contrib/syslinux-4.02/core/pm.inc new file mode 100644 index 0000000..9584cda --- /dev/null +++ b/contrib/syslinux-4.02/core/pm.inc @@ -0,0 +1,450 @@ +;; ----------------------------------------------------------------------- +;; +;; 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 + 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: + popad + add esp,4 ; Drop interrupt number + iretd + + 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 + jmp abort_load + + 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 +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 |