aboutsummaryrefslogtreecommitdiffstats
path: root/core/pm.inc
blob: 7b98944fdfc5f499668d47eca22ced3bc0c8a994 (plain)
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