Home | History | Annotate | Download | only in core
      1 ;; -----------------------------------------------------------------------
      2 ;;
      3 ;;   Copyright 1994-2009 H. Peter Anvin - All Rights Reserved
      4 ;;   Copyright 2009 Intel Corporation; author: H. Peter Anvin
      5 ;;
      6 ;;   This program is free software; you can redistribute it and/or modify
      7 ;;   it under the terms of the GNU General Public License as published by
      8 ;;   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
      9 ;;   Boston MA 02111-1307, USA; either version 2 of the License, or
     10 ;;   (at your option) any later version; incorporated herein by reference.
     11 ;;
     12 ;; -----------------------------------------------------------------------
     13 
     14 ;;
     15 ;; pm.inc
     16 ;;
     17 ;; Functions to enter and exit 32-bit protected mode, handle interrupts
     18 ;; and cross-mode calls.
     19 ;;
     20 ;; PM refers to 32-bit flat protected mode; RM to 16-bit real mode.
     21 ;;
     22 
     23 		bits 16
     24 		section .text16
     25 ;
     26 ; _pm_call: call PM routine in low memory from RM
     27 ;
     28 ;	on stack	= PM routine to call (a 32-bit address)
     29 ;
     30 ;	ECX, ESI, EDI passed to the called function;
     31 ;	EAX = EBP in the called function points to the stack frame
     32 ;	which includes all registers (which can be changed if desired.)
     33 ;
     34 ;	All registers and the flags saved/restored
     35 ;
     36 ;	This routine is invoked by the pm_call macro.
     37 ;
     38 _pm_call:
     39 		pushfd
     40 		pushad
     41 		push ds
     42 		push es
     43 		push fs
     44 		push gs
     45 		mov bp,sp
     46 		mov ax,cs
     47 		mov ebx,.pm
     48 		mov ds,ax
     49 		jmp enter_pm
     50 
     51 		bits 32
     52 		section .textnr
     53 .pm:
     54 		; EAX points to the top of the RM stack, which is EFLAGS
     55 		test RM_FLAGSH,02h		; RM EFLAGS.IF
     56 		jz .no_sti
     57 		sti
     58 .no_sti:
     59 		call [ebp+4*2+9*4+2]		; Entrypoint on RM stack
     60 		mov bx,.rm
     61 		jmp enter_rm
     62 
     63 		bits 16
     64 		section .text16
     65 .rm:
     66 		pop gs
     67 		pop fs
     68 		pop es
     69 		pop ds
     70 		popad
     71 		popfd
     72 		ret 4		; Drop entrypoint
     73 
     74 ;
     75 ; enter_pm: Go to PM with interrupt service configured
     76 ;	EBX	  = PM entry point
     77 ;	EAX = EBP = on exit, points to the RM stack as a 32-bit value
     78 ;	ECX, EDX, ESI, EDI preserved across this routine
     79 ;
     80 ;	Assumes CS == DS
     81 ;
     82 ; This routine doesn't enable interrupts, but the target routine
     83 ; can enable interrupts by executing STI.
     84 ;
     85 		bits 16
     86 		section .text16
     87 enter_pm:
     88 		cli
     89 		xor eax,eax
     90 		mov ds,ax
     91 		mov ax,ss
     92 		mov [RealModeSSSP],sp
     93 		mov [RealModeSSSP+2],ax
     94 		movzx ebp,sp
     95 		shl eax,4
     96 		add ebp,eax		; EBP -> top of real-mode stack
     97 		cld
     98 		call enable_a20
     99 
    100 .a20ok:
    101 		mov byte [bcopy_gdt.TSS+5],89h	; Mark TSS unbusy
    102 
    103 		lgdt [bcopy_gdt]	; We can use the same GDT just fine
    104 		lidt [PM_IDT_ptr]	; Set up the IDT
    105 		mov eax,cr0
    106 		or al,1
    107 		mov cr0,eax		; Enter protected mode
    108 		jmp PM_CS32:.in_pm
    109 
    110 		bits 32
    111 		section .textnr
    112 .in_pm:
    113 		xor eax,eax		; Available for future use...
    114 		mov fs,eax
    115 		mov gs,eax
    116 		lldt ax
    117 
    118 		mov al,PM_DS32		; Set up data segments
    119 		mov es,eax
    120 		mov ds,eax
    121 		mov ss,eax
    122 
    123 		mov al,PM_TSS		; Be nice to Intel's VT by
    124 		ltr ax			; giving it a valid TR
    125 
    126 		mov esp,[PMESP]		; Load protmode %esp
    127 		mov eax,ebp		; EAX -> top of real-mode stack
    128 		jmp ebx			; Go to where we need to go
    129 
    130 ;
    131 ; enter_rm: Return to RM from PM
    132 ;
    133 ;	BX	= RM entry point (CS = 0)
    134 ;	ECX, EDX, ESI, EDI preserved across this routine
    135 ;	EAX	clobbered
    136 ;	EBP	reserved
    137 ;
    138 ; This routine doesn't enable interrupts, but the target routine
    139 ; can enable interrupts by executing STI.
    140 ;
    141 		bits 32
    142 		section .textnr
    143 enter_rm:
    144 		cli
    145 		cld
    146 		mov [PMESP],esp		; Save exit %esp
    147 		jmp PM_CS16:.in_pm16	; Return to 16-bit mode first
    148 
    149 		bits 16
    150 		section .text16
    151 .in_pm16:
    152 		mov ax,PM_DS16		; Real-mode-like segment
    153 		mov es,ax
    154 		mov ds,ax
    155 		mov ss,ax
    156 		mov fs,ax
    157 		mov gs,ax
    158 
    159 		lidt [RM_IDT_ptr]	; Real-mode IDT (rm needs no GDT)
    160 		xor dx,dx
    161 		mov eax,cr0
    162 		and al,~1
    163 		mov cr0,eax
    164 		jmp 0:.in_rm
    165 
    166 .in_rm:					; Back in real mode
    167 		lss sp,[cs:RealModeSSSP]	; Restore stack
    168 		movzx esp,sp		; Make sure the high bits are zero
    169 		mov ds,dx		; Set up sane segments
    170 		mov es,dx
    171 		mov fs,dx
    172 		mov gs,dx
    173 		jmp bx			; Go to whereever we need to go...
    174 
    175 		section .data16
    176 		alignz 4
    177 
    178 		extern __stack_end
    179 PMESP		dd __stack_end		; Protected-mode ESP
    180 
    181 PM_IDT_ptr:	dw 8*256-1		; Length
    182 		dd IDT			; Offset
    183 
    184 ;
    185 ; This is invoked on getting an interrupt in protected mode.  At
    186 ; this point, we need to context-switch to real mode and invoke
    187 ; the interrupt routine.
    188 ;
    189 ; When this gets invoked, the registers are saved on the stack and
    190 ; AL contains the register number.
    191 ;
    192 		bits 32
    193 		section .textnr
    194 pm_irq:
    195 		pushad
    196 		movzx esi,byte [esp+8*4] ; Interrupt number
    197 		inc dword [CallbackCtr]
    198 		mov ebx,.rm
    199 		jmp enter_rm		; Go to real mode
    200 
    201 		bits 16
    202 		section .text16
    203 .rm:
    204 		pushf			; Flags on stack
    205 		call far [cs:esi*4]	; Call IVT entry
    206 		mov ebx,.pm
    207 		jmp enter_pm		; Go back to PM
    208 
    209 		bits 32
    210 		section .textnr
    211 .pm:
    212 		dec dword [CallbackCtr]
    213 		jnz .skip
    214 		call [core_pm_hook]
    215 .skip:
    216 		popad
    217 		add esp,4		; Drop interrupt number
    218 		iretd
    219 
    220 ;
    221 ; Initially, the core_pm_hook does nothing; it is available for the
    222 ; threaded derivatives to run the scheduler, or examine the result from
    223 ; interrupt routines.
    224 ;
    225 		global core_pm_null_hook
    226 core_pm_null_hook:
    227 		ret
    228 
    229 		section .data16
    230 		alignz 4
    231 		global core_pm_hook
    232 core_pm_hook:	dd core_pm_null_hook
    233 
    234 		bits 16
    235 		section .text16
    236 ;
    237 ; Routines to enable and disable (yuck) A20.  These routines are gathered
    238 ; from tips from a couple of sources, including the Linux kernel and
    239 ; http://www.x86.org/.  The need for the delay to be as large as given here
    240 ; is indicated by Donnie Barnes of RedHat, the problematic system being an
    241 ; IBM ThinkPad 760EL.
    242 ;
    243 
    244 		section .data16
    245 		alignz 2
    246 A20Ptr		dw a20_dunno
    247 
    248 		section .bss16
    249 		alignb 4
    250 A20Test		resd 1			; Counter for testing A20 status
    251 A20Tries	resb 1			; Times until giving up on A20
    252 
    253 		section .text16
    254 enable_a20:
    255 		pushad
    256 		mov byte [cs:A20Tries],255 ; Times to try to make this work
    257 
    258 try_enable_a20:
    259 
    260 ;
    261 ; First, see if we are on a system with no A20 gate, or the A20 gate
    262 ; is already enabled for us...
    263 ;
    264 a20_none:
    265 		call a20_test
    266 		jnz a20_done
    267 		; Otherwise, see if we had something memorized...
    268 		jmp word [cs:A20Ptr]
    269 
    270 ;
    271 ; Next, try the BIOS (INT 15h AX=2401h)
    272 ;
    273 a20_dunno:
    274 a20_bios:
    275 		mov word [cs:A20Ptr], a20_bios
    276 		mov ax,2401h
    277 		pushf				; Some BIOSes muck with IF
    278 		int 15h
    279 		popf
    280 
    281 		call a20_test
    282 		jnz a20_done
    283 
    284 ;
    285 ; Enable the keyboard controller A20 gate
    286 ;
    287 a20_kbc:
    288 		mov dl, 1			; Allow early exit
    289 		call empty_8042
    290 		jnz a20_done			; A20 live, no need to use KBC
    291 
    292 		mov word [cs:A20Ptr], a20_kbc	; Starting KBC command sequence
    293 
    294 		mov al,0D1h			; Write output port
    295 		out 064h, al
    296 		call empty_8042_uncond
    297 
    298 		mov al,0DFh			; A20 on
    299 		out 060h, al
    300 		call empty_8042_uncond
    301 
    302 		; Apparently the UHCI spec assumes that A20 toggle
    303 		; ends with a null command (assumed to be for sychronization?)
    304 		; Put it here to see if it helps anything...
    305 		mov al,0FFh			; Null command
    306 		out 064h, al
    307 		call empty_8042_uncond
    308 
    309 		; Verify that A20 actually is enabled.  Do that by
    310 		; observing a word in low memory and the same word in
    311 		; the HMA until they are no longer coherent.  Note that
    312 		; we don't do the same check in the disable case, because
    313 		; we don't want to *require* A20 masking (SYSLINUX should
    314 		; work fine without it, if the BIOS does.)
    315 .kbc_wait:	push cx
    316 		xor cx,cx
    317 .kbc_wait_loop:
    318 		call a20_test
    319 		jnz a20_done_pop
    320 		loop .kbc_wait_loop
    321 
    322 		pop cx
    323 ;
    324 ; Running out of options here.  Final attempt: enable the "fast A20 gate"
    325 ;
    326 a20_fast:
    327 		mov word [cs:A20Ptr], a20_fast
    328 		in al, 092h
    329 		or al,02h
    330 		and al,~01h			; Don't accidentally reset the machine!
    331 		out 092h, al
    332 
    333 .fast_wait:	push cx
    334 		xor cx,cx
    335 .fast_wait_loop:
    336 		call a20_test
    337 		jnz a20_done_pop
    338 		loop .fast_wait_loop
    339 
    340 		pop cx
    341 
    342 ;
    343 ; Oh bugger.  A20 is not responding.  Try frobbing it again; eventually give up
    344 ; and report failure to the user.
    345 ;
    346 		dec byte [cs:A20Tries]
    347 		jnz a20_dunno		; Did we get the wrong type?
    348 
    349 		mov si, err_a20
    350 		pm_call pm_writestr
    351 		jmp kaboom
    352 
    353 		section .data16
    354 err_a20		db CR, LF, 'A20 gate not responding!', CR, LF, 0
    355 		section .text16
    356 
    357 ;
    358 ; A20 unmasked, proceed...
    359 ;
    360 a20_done_pop:	pop cx
    361 a20_done:	popad
    362 		ret
    363 
    364 ;
    365 ; This routine tests if A20 is enabled (ZF = 0).  This routine
    366 ; must not destroy any register contents.
    367 ;
    368 ; The no-write early out avoids the io_delay in the (presumably common)
    369 ; case of A20 already enabled (e.g. from a previous call.)
    370 ;
    371 a20_test:
    372 		push es
    373 		push cx
    374 		push eax
    375 		mov cx,0FFFFh			; HMA = segment 0FFFFh
    376 		mov es,cx
    377 		mov eax,[cs:A20Test]
    378 		mov cx,32			; Loop count
    379 		jmp .test			; First iteration = early out
    380 .wait:		add eax,0x430aea41		; A large prime number
    381 		mov [cs:A20Test],eax
    382 		io_delay			; Serialize, and fix delay
    383 .test:		cmp eax,[es:A20Test+10h]
    384 		loopz .wait
    385 .done:		pop eax
    386 		pop cx
    387 		pop es
    388 		ret
    389 
    390 ;
    391 ; Routine to empty the 8042 KBC controller.  If dl != 0
    392 ; then we will test A20 in the loop and exit if A20 is
    393 ; suddenly enabled.
    394 ;
    395 empty_8042_uncond:
    396 		xor dl,dl
    397 empty_8042:
    398 		call a20_test
    399 		jz .a20_on
    400 		and dl,dl
    401 		jnz .done
    402 .a20_on:	io_delay
    403 		in al, 064h		; Status port
    404 		test al,1
    405 		jz .no_output
    406 		io_delay
    407 		in al, 060h		; Read input
    408 		jmp short empty_8042
    409 .no_output:
    410 		test al,2
    411 		jnz empty_8042
    412 		io_delay
    413 .done:		ret
    414 
    415 ;
    416 ; This initializes the protected-mode interrupt thunk set
    417 ;
    418 		section .text16
    419 pm_init:
    420 		xor edi,edi
    421 		mov bx,IDT
    422 		mov di,IRQStubs
    423 
    424 		mov eax,7aeb006ah	; push byte .. jmp short ..
    425 
    426 		mov cx,8		; 8 groups of 32 IRQs
    427 .gloop:
    428 		push cx
    429 		mov cx,32		; 32 entries per group
    430 .eloop:
    431 		mov [bx],di		; IDT offset [15:0]
    432 		mov word [bx+2],PM_CS32	; IDT segment
    433 		mov dword [bx+4],08e00h	; IDT offset [31:16], 32-bit interrupt
    434 					; gate, CPL 0 (we don't have a TSS
    435 					; set up...)
    436 		add bx,8
    437 
    438 		stosd
    439 		; Increment IRQ, decrement jmp short offset
    440 		add eax,(-4 << 24)+(1 << 8)
    441 
    442 		loop .eloop
    443 
    444 		; At the end of each group, replace the EBxx with
    445 		; the final E9xxxxxxxx
    446 		add di,3
    447 		mov byte [di-5],0E9h	; JMP NEAR
    448 		mov edx,pm_irq
    449 		sub edx,edi
    450 		mov [di-4],edx
    451 
    452 		add eax,(0x80 << 24)	; Proper offset for the next one
    453 		pop cx
    454 		loop .gloop
    455 
    456 		ret
    457 
    458 		; pm_init is called before bss clearing, so put these
    459 		; in .earlybss!
    460 		section .earlybss
    461 		alignb 8
    462 IDT:		resq 256
    463 		global RealModeSSSP
    464 RealModeSSSP	resd 1			; Real-mode SS:SP
    465 
    466 		section .gentextnr	; Autogenerated 32-bit code
    467 IRQStubs:	resb 4*256+3*8
    468 
    469 		section .text16
    470 
    471 %include "callback.inc"			; Real-mode callbacks
    472