1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
|
;; -----------------------------------------------------------------------
;;
;; 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
|