aboutsummaryrefslogtreecommitdiff
path: root/src/arch/i386/transitions/librm.S
blob: 2fbffb9e3df49ce3e25476c84d4f6179abe898b6 (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
472
473
474
475
476
477
478
479
480
481
482
/*
 * librm: a library for interfacing to real-mode code
 *
 * Michael Brown <mbrown@fensystems.co.uk>
 *
 */

/* Drag in local definitions */
#include "librm.h"

/* For switches to/from protected mode */
#define CR0_PE 1

/* Size of various C data structures */
#define SIZEOF_I386_SEG_REGS	12
#define SIZEOF_I386_REGS	32
#define SIZEOF_REAL_MODE_REGS	( SIZEOF_I386_SEG_REGS + SIZEOF_I386_REGS )
#define SIZEOF_I386_FLAGS	4
#define SIZEOF_I386_ALL_REGS	( SIZEOF_REAL_MODE_REGS + SIZEOF_I386_FLAGS )
	
	.arch i386
	.section ".text16", "awx", @progbits

/****************************************************************************
 * Global descriptor table
 *
 * Call init_gdt to set up the GDT before attempting to use any
 * protected-mode code.
 *
 * Define FLATTEN_REAL_MODE if you want to use so-called "flat real
 * mode" with 4GB limits instead.
 *
 * NOTE: This must be located before prot_to_real, otherwise gas
 * throws a "can't handle non absolute segment in `ljmp'" error due to
 * not knowing the value of REAL_CS when the ljmp is encountered.
 *
 * Note also that putting ".word gdt_end - gdt - 1" directly into
 * gdt_limit, rather than going via gdt_length, will also produce the
 * "non absolute segment" error.  This is most probably a bug in gas.
 ****************************************************************************
 */
	
#ifdef FLATTEN_REAL_MODE
#define RM_LIMIT_16_19__AVL__SIZE__GRANULARITY 0x8f
#else
#define RM_LIMIT_16_19__AVL__SIZE__GRANULARITY 0x00
#endif
	.section ".text16"
	.align 16
gdt:
gdt_limit:		.word gdt_length - 1
gdt_base:		.long 0
			.word 0 /* padding */

	.org	gdt + VIRTUAL_CS, 0
virtual_cs:	/* 32 bit protected mode code segment, virtual addresses */
	.word	0xffff, 0
	.byte	0, 0x9f, 0xcf, 0

	.org	gdt + VIRTUAL_DS, 0
virtual_ds:	/* 32 bit protected mode data segment, virtual addresses */
	.word	0xffff, 0
	.byte	0, 0x93, 0xcf, 0
	
	.org	gdt + PHYSICAL_CS, 0
physical_cs:	/* 32 bit protected mode code segment, physical addresses */
	.word	0xffff, 0
	.byte	0, 0x9f, 0xcf, 0

	.org	gdt + PHYSICAL_DS, 0
physical_ds:	/* 32 bit protected mode data segment, physical addresses */
	.word	0xffff, 0
	.byte	0, 0x93, 0xcf, 0	

	.org	gdt + REAL_CS, 0
real_cs: 	/* 16 bit real mode code segment */
	.word	0xffff, 0
	.byte	0, 0x9b, RM_LIMIT_16_19__AVL__SIZE__GRANULARITY, 0

	.org	gdt + REAL_DS	
real_ds:	/* 16 bit real mode data segment */
	.word	0xffff, 0
	.byte	0, 0x93, RM_LIMIT_16_19__AVL__SIZE__GRANULARITY, 0
	
gdt_end:
	.equ	gdt_length, gdt_end - gdt

/****************************************************************************
 * init_gdt (real-mode near call, 16-bit real-mode return address)
 *
 * Initialise the GDT ready for transitions to protected mode.
 *
 * Parameters: 
 *   %edi : Physical base of protected-mode code
 ****************************************************************************
 */
	.section ".text16"
	.code16
	.globl init_gdt
init_gdt:
	/* Preserve registers */
	pushl	%eax
	pushw	%bx

	/* Record virt_offset */
	movl	%edi, %cs:virt_offset_rm_copy

	/* Set virtual_cs and virtual_ds base */
	movl	%edi, %eax
	movw	$virtual_cs, %bx
	call	set_seg_base

	/* Set real_cs and real_ds base, and GDT base */
	movw	$real_cs, %bx
	xorl	%eax, %eax
	movw	%cs, %ax
	shll	$4, %eax
	call	set_seg_base
	addl	$gdt, %eax
	movl	%eax, %cs:gdt_base

	/* Restore registers */
	popw	%bx
	popl	%eax
	ret

	.section ".text16"
	.code16
set_seg_base:
	pushl	%eax
	movw	%ax, %cs:(0+2)(%bx)
	movw	%ax, %cs:(8+2)(%bx)
	shrl	$16, %eax
	movb	%al, %cs:(0+4)(%bx)
	movb	%al, %cs:(8+4)(%bx)
	movb	%ah, %cs:(0+7)(%bx)
	movb	%ah, %cs:(8+7)(%bx)
	popl	%eax
	ret
	
/****************************************************************************
 * real_to_prot (real-mode near call, 32-bit virtual return address)
 *
 * Switch from 16-bit real-mode to 32-bit protected mode with virtual
 * addresses.  The real-mode %ss:sp is stored in rm_ss and rm_sp, and
 * the protected-mode %esp is restored from the saved pm_esp.
 * Interrupts are disabled.  All other registers may be destroyed.
 *
 * The return address for this function should be a 32-bit virtual
 * address.
 *
 * Parameters: 
 *   %ecx : number of bytes to move from RM stack to PM stack
 *
 ****************************************************************************
 */
	.section ".text16"
	.code16
real_to_prot:
	/* Protected-mode return address => %ebx */
	popl	%ebx

	/* Real-mode %cs => %dx, %ss => %bp */
	movw	%cs, %dx
	movw	%ss, %bp

	/* virt_offset => %edi */
	movl	%cs:virt_offset_rm_copy, %edi

	/* Switch to protected mode */
	cli
	data32 lgdt	%cs:gdt
	movl	%cr0, %eax
	orb	$CR0_PE, %al
	movl	%eax, %cr0
	data32 ljmp	$VIRTUAL_CS, $1f
	.section ".text"
	.code32
1:
	/* Set up protected-mode data segments */
	movw	$VIRTUAL_DS, %ax
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %fs
	movw	%ax, %gs

	/* Record virt_offset */
	movl	%edi, virt_offset

	/* Move data from RM stack to PM stack and set up PM stack */
	movzwl	%sp, %esi
	movl	pm_esp, %esp
	subl	%ecx, %esp
	movl	%esp, %edi
	rep ss movsb
	movw	%ax, %ss

	/* Record real-mode %cs and %ss:sp */
	movw	%dx, rm_cs
	movw	%bp, rm_ss
	movw	%si, rm_sp

	/* Return to virtual address */
	jmp	*%ebx

/****************************************************************************
 * prot_to_real (protected-mode near call, 32-bit real-mode return address)
 *
 * Switch from 32-bit protected mode with virtual addresses to 16-bit
 * real mode.  The protected-mode %esp is stored in pm_esp and the
 * real-mode %ss:sp is restored from the saved rm_ss and rm_sp.  All
 * real-mode data segment registers are set equal to %ss.  Interrupts
 * are *not* enabled, since we want to be able to use prot_to_real in
 * an ISR.  All other registers may be destroyed.
 *
 * The return address for this function should be a 32-bit (sic)
 * real-mode offset within .code16.
 *
 * Parameters: 
 *   %ecx : number of bytes to move from PM stack to RM stack
 *
 ****************************************************************************
 */
	.section ".text"
	.code32
prot_to_real:
	/* Real-mode return address => %ebx */
	popl	%ebx
	
	/* Real-mode %ss:sp => %ebp:edx */
	movzwl	rm_ss, %ebp
	movzwl	rm_sp, %edx
	subl	%ecx, %edx
	
	/* Copy data from PM stack to RM stack */
	movl	%ebp, %eax
	shll	$4, %eax
	leal	(%eax,%edx), %edi
	subl	virt_offset, %edi
	movl	%esp, %esi
	rep movsb
	
	/* Record protected-mode %esp */
	movl	%esi, pm_esp

	/* Real-mode %cs => %di */
	movw	rm_cs, %di

	/* Load real-mode segment limits */
	movw	$REAL_DS, %ax
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %fs
	movw	%ax, %gs
	movw	%ax, %ss
	ljmp	$REAL_CS, $1f
	.section ".text16"
	.code16
1:
	/* Set up real-mode ljmp instruction */
	movw	%di, %ds:(p2r_ljmp + 3)
	
	/* Switch to real mode */
	movl	%cr0, %eax
	andb	$0!CR0_PE, %al
	movl	%eax, %cr0

p2r_ljmp:
	ljmp	$0, $1f /* Segment is filled in by above code */
1:
	/* Set up real-mode stack and data segments, and stack pointer */
	movw	%bp, %ds
	movw	%bp, %es
	movw	%bp, %fs
	movw	%bp, %gs
	movw	%bp, %ss
	movw	%dx, %sp

	/* Return to real-mode address */
	jmp	*%bx
	
/****************************************************************************
 * prot_call (real-mode near call, 32-bit real-mode return address)
 *
 * Call a specific C function in the protected-mode code.  The
 * prototype of the C function must be
 *   void function ( struct i386_all_regs *ix86 ); 
 * ix86 will point to a struct containing the real-mode registers
 * at entry to prot_call.  
 *
 * All registers will be preserved across prot_call(), unless the C
 * function explicitly overwrites values in ix86.  Interrupt status
 * will also be preserved.  Gate A20 will be enabled.
 *
 * Parameters:
 *   function : virtual address of protected-mode function to call
 *
 * Example usage:
 *	pushl	$pxe_api_call
 *	call	prot_call
 *	addw	$4, %sp
 * to call in to the C function
 *      void pxe_api_call ( struct i386_all_regs *ix86 );
 ****************************************************************************
 */

#define PC_OFFSET_IX86 ( 0 )
#define PC_OFFSET_RETADDR ( PC_OFFSET_IX86 + SIZEOF_I386_ALL_REGS )
#define PC_OFFSET_FUNCTION ( PC_OFFSET_RETADDR + 4 )
#define PC_OFFSET_END ( PC_OFFSET_FUNCTION + 4 )

	.section ".text16"
	.code16
	.globl prot_call
prot_call:
	/* Preserve registers and flags on external RM stack */
	pushfl
	pushal
	pushw	%gs
	pushw	%fs
	pushw	%es
	pushw	%ds
	pushw	%ss
	pushw	%cs

	/* For sanity's sake, clear the direction flag as soon as possible */
	cld

	/* Switch to protected mode and move register dump to PM stack */
	movl	$PC_OFFSET_END, %ecx
	pushl	$1f
	jmp	real_to_prot
	.section ".text"
	.code32
1:
	/* Set up environment expected by C code */
	call	gateA20_set

	/* Call function */
	pushl	%esp
	call	*(PC_OFFSET_FUNCTION+4)(%esp)
	popl	%eax /* discard */

	/* Switch to real mode and move register dump back to RM stack */
	movl	$PC_OFFSET_END, %ecx
	pushl	$1f
	jmp	prot_to_real
	.section ".text16"
	.code16
1:	
	/* Restore registers and flags and return */
	popw	%ax	/* skip %cs - it is already set */
	popw	%ax	/* skip %ss - it is already set */
	popw	%ds
	popw	%es
	popw	%fs
	popw	%gs
	popal
	popfl
	ret

/****************************************************************************
 * real_call (protected-mode near call, 32-bit virtual return address)
 *
 * Call a real-mode function from protected-mode code.
 *
 * The non-segment register values will be passed directly to the
 * real-mode code.  The segment registers will be set as per
 * prot_to_real.  The non-segment register values set by the real-mode
 * function will be passed back to the protected-mode caller.  A
 * result of this is that this routine cannot be called directly from
 * C code, since it clobbers registers that the C ABI expects the
 * callee to preserve.  Gate A20 will be re-enabled in case the
 * real-mode routine disabled it.
 *
 * librm.h defines two convenient macros for using real_call:
 * REAL_CALL and REAL_EXEC.  See librm.h and realmode.h for details
 * and examples.
 *
 * Parameters:
 *   (32-bit) near pointer to real-mode function to call
 *
 * Returns: none
 ****************************************************************************
 */

#define RC_OFFSET_PRESERVE_REGS ( 0 )
#define RC_OFFSET_RETADDR ( RC_OFFSET_PRESERVE_REGS + SIZEOF_I386_REGS )
#define RC_OFFSET_FUNCTION ( RC_OFFSET_RETADDR + 4 )
#define RC_OFFSET_END ( RC_OFFSET_FUNCTION + 4 )

	.section ".text"
	.code32
	.globl real_call
real_call:
	/* Create register dump on PM stack */
	pushal

	/* Switch to real mode and move register dump to RM stack */
	movl	$RC_OFFSET_END, %ecx
	pushl	$1f
	jmp	prot_to_real
	.section ".text16"
	.code16
1:
	/* Construct call to real-mode function */
	movw	%sp, %bp
	movw	RC_OFFSET_FUNCTION(%bp), %ax
	movw	%ax, %cs:rc_function

	/* Call real-mode function */
	popal
	call	*%cs:rc_function
	pushal

	/* Switch to protected mode and move register dump back to PM stack */
	movl	$RC_OFFSET_END, %ecx
	pushl	$1f
	jmp	real_to_prot
	.section ".text"
	.code32
1:
	/* Set up environment expected by C code */
	call	gateA20_set

	/* Restore registers and return */
	popal
	ret

	.section ".text16"
rc_function:	.word 0
	
/****************************************************************************
 * Stored real-mode and protected-mode stack pointers
 *
 * The real-mode stack pointer is stored here whenever real_to_prot
 * is called and restored whenever prot_to_real is called.  The
 * converse happens for the protected-mode stack pointer.
 *
 * Despite initial appearances this scheme is, in fact re-entrant,
 * because program flow dictates that we always return via the point
 * we left by.  For example:
 *    PXE API call entry
 *  1   real => prot
 *        ...
 *        Print a text string
 *	    ...
 *  2       prot => real
 *            INT 10
 *  3       real => prot
 *	    ...
 *        ...
 *  4   prot => real
 *    PXE API call exit
 *
 * At point 1, the RM mode stack value, say RPXE, is stored in
 * rm_ss,sp.  We want this value to still be present in rm_ss,sp when
 * we reach point 4.
 *
 * At point 2, the RM stack value is restored from RPXE.  At point 3,
 * the RM stack value is again stored in rm_ss,sp.  This *does*
 * overwrite the RPXE that we have stored there, but it's the same
 * value, since the code between points 2 and 3 has managed to return
 * to us.
 ****************************************************************************
 */

	.section ".data"
	.globl rm_sp
rm_sp:	.word 0
	.globl rm_ss
rm_ss:	.word 0
	.globl rm_cs
rm_cs:	.word 0
	.globl pm_esp
pm_esp:	.long _estack

	.section ".text16"
virt_offset_rm_copy:	.long 0
	.section ".data"
	.globl virt_offset
virt_offset:		.long 0