summaryrefslogtreecommitdiffstats
path: root/contrib/syslinux-4.02/core/pm.inc
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/syslinux-4.02/core/pm.inc')
-rw-r--r--contrib/syslinux-4.02/core/pm.inc450
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