-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpm.inc
471 lines (413 loc) · 9.88 KB
/
pm.inc
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