summaryrefslogblamecommitdiffstats
path: root/src/arch/i386/transitions/librm.S
blob: 5299841c9fee9f6d0cebd2af3b2808c6e0f52487 (plain) (tree)
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
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498

















































































































































































































































































































































































































































































































                                                                              
                    
           

                                                                            























                                                                        











                                                                     





































































































































































                                                                              
/*
 * librm: a library for interfacing to real-mode code
 *
 * Michael Brown <mbrown@fensystems.co.uk>
 *
 */

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

/****************************************************************************
 * This file defines librm: a block of code that is designed to reside
 * permanently in base memory and provide the interface between
 * real-mode code running in base memory and protected-mode code
 * running in high memory.  It provides the following functions:
 *
 *   real_to_prot &	switch between real and protected mode 
 *   prot_to_real	while running in base memory, preserving 
 *			all non-segment registers
 *
 *   real_call		issue a call to a real-mode routine from
 *			protected-mode code running in high memory
 *
 *   prot_call		issue a call to a protected-mode routine from
 *			real-mode code running in base memory
 *
 * librm requires the following functions to be present in the
 * protected-mode code:
 *
 *   _phys_to_virt	Switch from physical to virtual addressing.  This
 *			routine must be position-independent and must
 *			*not* assume that it is genuinely running with
 *			flat physical addresses
 *
 *   _virt_to_phys	Switch from virtual to physical addresses.
 *
 *   gateA20_set	Enable the A20 line to permit access to the odd
 *			megabytes of RAM.  (This function will be called
 *			with virtual addresses set up).
 *
 * librm needs to be linked against the protected-mode binary so that
 * it can import the symbols for these functions.
 *
 * librm requires that the protected-mode code set up the following
 * segments:
 *
 *   PHYSICAL_CS	32-bit pmode code and data segments with flat
 *   PHYSICAL_DS	physical addresses.
 *
 *   VIRTUAL_CS		32-bit pmode code segment with virtual
 *			addressing, such that a protected-mode routine
 *			can always be found at $VIRTUAL_CS:routine.
 *
 * These segments must be set as #define constants when compiling
 * librm.  Edit librm.h to change the values.
 *
 * librm does not know the location of the code executing in high
 * memory.  It relies on the code running in high memory setting up a
 * GDT such that the high-memory code is accessible at virtual
 * addresses fixed at compile-time.
 *
 * librm symbols are exported as absolute values and represent offsets
 * into librm.  This is the most useful form of the symbols, since
 * librm is basically a binary blob that you place somewhere in base
 * memory.
 *
 * librm.h provides convenient ways to use these symbols: you simply
 * set the pointer ( char * ) installed_librm to point to wherever
 * librm is installed, and can then use e.g. inst_rm_stack just like
 * any other variable and have it automatically refer to the value of
 * rm_stack in the installed librm.  Macro trickery makes this
 * completely transparent, and the resulting assembler code is
 * amazingly efficient.
 *
 * Note that librm must be called in genuine real mode, not 16:16 or
 * 16:32 protected mode.  It makes the assumption that
 * physical_address = 16*segment+offset, and also that it can use
 * OFFSET(%bp) to access stack variables.  The former assumption will
 * break in either protected mode, the latter may break in 16:32
 * protected mode.
 ****************************************************************************
 */

/*
 * Default values for pmode segments if not defined
 */
#ifndef PHYSICAL_CS
#warning "Assuming PHYSICAL_CS = 0x08"
#define PHYSICAL_CS 0x08
#endif
#ifndef PHYSICAL_DS
#warning "Assuming PHYSICAL_DS = 0x10"
#define PHYSICAL_DS 0x10
#endif
#ifndef VIRTUAL_CS
#warning "Assuming VIRTUAL_CS = 0x18"
#define VIRTUAL_CS 0x18
#endif

/* 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_I386_ALL_REGS	( SIZEOF_I386_SEG_REGS + SIZEOF_I386_REGS )
#define SIZEOF_I386_FLAGS	4
#define SIZEOF_REAL_MODE_REGS   ( SIZEOF_I386_ALL_REGS + SIZEOF_I386_FLAGS )
#define SIZEOF_SEGOFF_T		4
#define SIZEOF_REAL_CALL_PARAMS ( SIZEOF_I386_ALL_REGS + 2 * SIZEOF_SEGOFF_T )
	
	.text
	.arch i386
	.section ".librm", "awx", @progbits
	.align 16

	.globl	librm
librm:
	
_librm_start:

#undef OFFSET
#define OFFSET(sym) ( sym - _librm_start )

#undef EXPORT
#define EXPORT(sym) \
	.globl sym ; \
	.globl _ ## sym ;  \
	.equ _ ## sym, OFFSET(sym) ; \
	sym

/****************************************************************************
 * GDT for initial transition to protected mode
 *
 * PHYSICAL_CS and PHYSICAL_DS are defined in an external header file.
 * We use only those selectors, and construct our GDT to match the
 * selector values we're asked to use.  Use PHYSICAL_CS=0x08 and
 * PHYSICAL_DS=0x10 to minimise the space occupied by this GDT.
 *
 * Note: pm_gdt is also used to store the location of the
 * protected-mode GDT as recorded on entry to prot_to_real.
 ****************************************************************************
 */
	.align 16
pm_gdt:
pm_gdt_limit:		.word pm_gdt_length - 1
pm_gdt_addr:		.long 0
			.word 0 /* padding */
	
	.org	pm_gdt + PHYSICAL_CS
pm_gdt_pm_cs:
	/* 32 bit protected mode code segment, physical addresses */
	.word	0xffff, 0
	.byte	0, 0x9f, 0xcf, 0
	
	.org	pm_gdt + PHYSICAL_DS
pm_gdt_pm_ds:
	/* 32 bit protected mode data segment, physical addresses */
	.word	0xffff,0
	.byte	0,0x93,0xcf,0
	
pm_gdt_end:		
	.equ	pm_gdt_length, pm_gdt_end - pm_gdt

/****************************************************************************
 * GDT for transition to real mode
 *
 * This is used primarily to set 64kB segment limits.  Define
 * FLATTEN_REAL_MODE if you want to use so-called "flat real mode"
 * with 4GB limits instead.  The base address of each of the segments
 * will be adjusted at run-time.
 *
 * 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 RM_CS when the ljmp is encountered.
 *
 * Note also that putting ".word rm_gdt_end - rm_gdt - 1" directly
 * into rm_gdt_limit, rather than going via rm_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
	.align 16
rm_gdt:
rm_gdt_limit:		.word rm_gdt_length - 1
rm_gdt_base:		.long 0
			.word 0 /* padding */
	
rm_gdt_rm_cs: 	/* 16 bit real mode code segment */
	.equ	RM_CS, rm_gdt_rm_cs - rm_gdt
	.word	0xffff,(0&0xffff)
	.byte	(0>>16),0x9b,RM_LIMIT_16_19__AVL__SIZE__GRANULARITY,(0>>24)
	
rm_gdt_rm_ds:	/* 16 bit real mode data segment */
	.equ	RM_DS, rm_gdt_rm_ds - rm_gdt
	.word	0xffff,(0&0xffff)
	.byte	(0>>16),0x93,RM_LIMIT_16_19__AVL__SIZE__GRANULARITY,(0>>24)
	
rm_gdt_end:
	.equ	rm_gdt_length, rm_gdt_end - rm_gdt

/****************************************************************************
 * real_to_prot (real-mode far call)
 *
 * Switch from 16-bit real-mode to 32-bit protected mode with flat
 * physical addresses.  %esp is restored from the saved pm_esp.  All
 * segment registers are set to flat physical-mode values.  All other
 * registers are preserved.  Interrupts are disabled.
 *
 * Note that this routine can be called *without* having first set up
 * a stored pm_esp or stored GDT.  If you do this, real_to_prot will
 * return with a temporary stack that is only *FOUR BYTES* in size.
 * This is just enough to enable you to do a "call 1f; popl %ebp"
 * sequence in order to find out your physical address and then load a
 * proper 32-bit protected-mode stack pointer.  Do *NOT* use more than
 * four bytes since this will overwrite code in librm!
 *
 * Parameters: none
 ****************************************************************************
 */

	.code16		
EXPORT(real_to_prot):
	/* Disable interrupts */
	cli

	/* Set %ds = %cs, for easier access to variables */
	pushw	%cs
	popw	%ds
	
	/* Preserve registers */
	movl	%eax, %ds:OFFSET(save_eax)
	movl	%ebx, %ds:OFFSET(save_ebx)

	/* Extract real-mode far return address from stack */
	popl	%ds:OFFSET(save_retaddr)

	/* Record real-mode stack pointer */
	movw	%sp, %ds:OFFSET(rm_sp)
	pushw	%ss
	popw	%ds:OFFSET(rm_ss)

	/* Physical base address of librm to %ebx */
	xorl	%ebx, %ebx
	movw	%cs, %bx
	shll	$4, %ebx
		
	/* Check base address of stored protected-mode GDT.  If it's
	 * zero, set it up to use our internal GDT (with physical
	 * segments only).
	 */
	movl	%ds:OFFSET(pm_gdt_addr), %eax
	testl	%eax, %eax
	jnz	1f
	/* Use internal GDT */
	movl	%ebx, %eax
	addl	$OFFSET(pm_gdt), %eax
	movl	%eax, %ds:OFFSET(pm_gdt_addr)
1:	
	
	/* Set up protected-mode continuation address on real-mode stack */
	pushl	$PHYSICAL_CS
	movl	%ebx, %eax
	addl	$OFFSET(1f), %eax
	pushl	%eax
	
	/* Restore protected-mode GDT */
	lgdt	%ds:OFFSET(pm_gdt)

	/* Switch to protected mode */
	movl	%cr0, %eax
	orb	$CR0_PE, %al
	movl	%eax, %cr0

	/* Flush prefetch queue and reload %cs:eip */
	data32 lret
1:	.code32

	/* Set up protected-mode stack and data segments */
	movw	$PHYSICAL_DS, %ax
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %fs
	movw	%ax, %gs
	movw	%ax, %ss

	/* Switch to saved protected-mode stack.  Note that there may
	 * not actually *be* a saved protected-mode stack.
	 */
	movl	OFFSET(pm_esp)(%ebx), %esp
	testl	%esp, %esp
	jnz	1f
	/* No stack - use save_retaddr as a 4-byte temporary stack */
	leal	OFFSET(save_retaddr+4)(%ebx), %esp
1:	
	
	/* Convert real-mode far return address to physical address
	 * and place on stack
	 */
	pushl	OFFSET(save_retaddr)(%ebx)
	xorl	%eax, %eax
	xchgw	2(%esp), %ax
	shll	$4, %eax
	addl	%eax, 0(%esp)

	/* Restore registers and return */
	movl	OFFSET(save_eax)(%ebx), %eax
	movl	OFFSET(save_ebx)(%ebx), %ebx
	ret

/****************************************************************************
 * prot_to_real (protected-mode near call, physical addresses)
 *
 * Switch from 32-bit protected mode with flat physical addresses to
 * 16-bit real mode.  %ss:sp is restored from the saved rm_ss and
 * rm_sp.  %cs is set such that %cs:0000 is the start of librm.  All
 * other segment registers are set to %ss.  All other registers are
 * preserved.  Interrupts are *not* enabled, since we want to be able
 * to use this routine inside an ISR.
 *
 * Note that since %cs:0000 points to the start of librm on exit, it
 * follows that the code calling prot_to_real must be located within
 * 64kB of the start of librm.
 *
 * Parameters: none
 ****************************************************************************
 */

	.code32
EXPORT(prot_to_real):
	/* Calculate physical base address of librm in %ebx, preserve
	 * original %eax and %ebx in save_eax and save_ebx
	 */
	pushl	%ebx
	call	1f
1:	popl	%ebx
	subl	$OFFSET(1b), %ebx
	popl	OFFSET(save_ebx)(%ebx)
	movl	%eax, OFFSET(save_eax)(%ebx)

	/* Extract return address from the stack, convert to offset
	 * within librm and save in save_retaddr
	 */
	popl	%eax
	subl	%ebx, %eax
	movl	%eax, OFFSET(save_retaddr)(%ebx)

	/* Record protected-mode stack pointer */
	movl	%esp, OFFSET(pm_esp)(%ebx)

	/* Record protected-mode GDT */
	sgdt	OFFSET(pm_gdt)(%ebx)

	/* Set up real-mode GDT */
	leal	OFFSET(rm_gdt)(%ebx), %eax
	movl	%eax, OFFSET(rm_gdt_base)(%ebx)
	movl	%ebx, %eax
	rorl	$16, %eax
	movw	%bx, OFFSET(rm_gdt_rm_cs+2)(%ebx)
	movb	%al, OFFSET(rm_gdt_rm_cs+4)(%ebx)
	movw	%bx, OFFSET(rm_gdt_rm_ds+2)(%ebx)
	movb	%al, OFFSET(rm_gdt_rm_ds+4)(%ebx)
	
	/* Switch to real-mode GDT and reload segment registers to get
	 * 64kB limits.  Stack is invalidated by this process.
	 */
	lgdt	OFFSET(rm_gdt)(%ebx)
	ljmp	$RM_CS, $1f
1:	.code16
	movw	$RM_DS, %ax
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %fs
	movw	%ax, %gs
	movw	%ax, %ss

	/* Calculate real-mode code segment in %ax and store in ljmp
	 * instruction
	 */
	movl	%ebx, %eax
	shrl	$4, %eax
	movw	%ax, OFFSET(p2r_ljmp) + 3

	/* Switch to real mode */
	movl	%cr0, %ebx
	andb	$0!CR0_PE, %bl
	movl	%ebx, %cr0

	/* Intersegment jump to flush prefetch queue and reload
	 * %cs:eip.  The segment gets filled in by the above code.  We
	 * can't just use lret to achieve this, because we have no
	 * stack at the moment.
	 */
p2r_ljmp:
	ljmp	$0, $OFFSET(1f)
1:	

	/* Set %ds to point to code segment for easier data access */
	movw	%ax, %ds
			
	/* Restore registers */
	movl	OFFSET(save_eax), %eax
	movl	OFFSET(save_ebx), %ebx

	/* Set up real-mode data segments and stack */
	movw	OFFSET(rm_ss), %ss
	movw	OFFSET(rm_sp), %sp
	pushw	%ss
	pushw	%ss
	pushw	%ss
	pushw	%ss
	popw	%ds
	popw	%es
	popw	%fs
	popw	%gs

	/* Set up return address on stack and return */
	pushw	%cs:OFFSET(save_retaddr)
	ret

/****************************************************************************
 * prot_call (real-mode far call)
 *
 * Call a specific C function in the protected-mode code.  The
 * prototype of the C function must be
 *   void function ( struct real_mode_regs *rm_regs,
 *		     void (*retaddr) (void) ); 
 * rm_regs will point to a struct containing the real-mode registers
 * at entry to prot_call.  retaddr will point to the (virtual) return
 * address from "function".  This return address will point into
 * librm.  It is included so that "function" may, if desired, relocate
 * librm and return via the new copy.  It must not be directly called
 * as a function, i.e. you may not do "*retaddr()"; you must instead
 * do something like:
 *	*retaddr += ( new_librm_location - old_librm_location );
 *	return;
 *
 * All registers will be preserved across prot_call(), unless the C
 * function explicitly overwrites values in rm_regs.  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
 *	lcall	$LIBRM_SEGMENT, $prot_call
 *	addw	$4, %sp
 * to call in to the C function
 *      void pxe_api_call ( struct real_mode_regs *rm_regs );
 ****************************************************************************
 */

#define PC_OFFSET_RM_REGS ( 0 )
#define PC_OFFSET_RETADDR ( PC_OFFSET_RM_REGS + SIZEOF_REAL_MODE_REGS )
#define PC_OFFSET_FUNCTION ( PC_OFFSET_RETADDR + 4 )
	
	.code16
EXPORT(prot_call):
	/* Preserve registers and flags on RM stack */
	pushfl
	pushal
	pushw	%gs
	pushw	%fs
	pushw	%es
	pushw	%ds
	pushw	%ss
	pushw	%cs	
	
	/* Record RM stack pointer */
	xorl	%ebp, %ebp
	movw	%sp, %bp
	
	/* Physical address of RM stack pointer to %esi */
	xorl	%esi, %esi
	pushw	%ss
	popw	%si
	shll	$4, %esi
	addl	%ebp, %esi

	/* Address of pmode function to %ebx */
	movl	%ss:(PC_OFFSET_FUNCTION)(%bp), %ebx
	
	/* Switch to protected mode */
	pushw	%cs
	call	real_to_prot
	.code32

	/* Copy rm_regs from RM stack to PM stack */
	movl	$SIZEOF_REAL_MODE_REGS, %ecx
	subl	%ecx, %esp
	movl	%esp, %edi
	pushl	%esi
	cld
	rep movsb
	popl	%edi		/* %edi = phys addr of RM copy of rm_regs */
	
	/* Switch to virtual addresses. */
	call	1f
	jmp	2f
1:	ljmp	$VIRTUAL_CS, $_phys_to_virt
2:	

	/* Enable A20 line */
	pushal
	lcall	$VIRTUAL_CS, $gateA20_set
	popl	%eax	/* discard */
	popal

	/* Push &rm_regs and &retaddr on the stack, and call function */
	movl	%esp, %ebp
	pushl	%esp
	subl	$12, 0(%esp)
	pushl	%ebp
	call	*%ebx
	popl	%eax /* discard */
	popl	%eax /* discard */

	/* Switch to physical addresses, discard PM register store */
	lcall	$VIRTUAL_CS, $_virt_to_phys
	popl	%eax /* discard */

	/* Copy rm_regs from PM stack to RM stack, and remove rm_regs
	 * from PM stack.  (%edi still contains physical address of
	 * rm_regs on RM stack from earlier, since C code preserves
	 * %edi).
	 */
	movl	%esp, %esi
	movl	$SIZEOF_REAL_MODE_REGS, %ecx
	cld
	rep movsb
	movl	%esi, %esp	/* remove rm_regs from PM stack */

	/* Switch to real mode */
	call	prot_to_real
	.code16

	/* Restore registers and flags, and return */
	popw	%ax	/* skip %cs */
	popw	%ax	/* skip %ss */
	popw	%ds
	popw	%es
	popw	%fs
	popw	%gs
	popal
	popfl
	lret

/****************************************************************************
 * real_call (protected-mode near call, virtual addresses)
 *
 * 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:
 *   far pointer to real-mode function to call
 *
 * Returns: none
 ****************************************************************************
 */

#define RC_OFFSET_PRESERVE_REGS ( 0 )
#define RC_OFFSET_RETADDR ( RC_OFFSET_PRESERVE_REGS + 8 )
#define RC_OFFSET_RM_FUNCTION ( RC_OFFSET_RETADDR + 4 )
	
	.code32
EXPORT(real_call):
	/* Preserve registers */
	pushl	%ebp
	pushl	%eax
	
	/* Switch to physical addresses */
	lcall	$VIRTUAL_CS, $_virt_to_phys
	addl	$4, %esp

	/* Extract real-mode function address and store in ljmp instruction */
	call	1f
1:	popl	%ebp
	movl	RC_OFFSET_RM_FUNCTION(%esp), %eax
	movl	%eax, (rc_ljmp + 1 - 1b)(%ebp)

	/* Restore registers */
	popl	%eax
	popl	%ebp

	/* Switch to real mode, preserving non-segment registers */
	call	prot_to_real
	.code16

	/* Far call to real-mode routine */
	pushw	%cs
	call	rc_ljmp
	jmp	2f
rc_ljmp:	
	ljmp	$0, $0	/* address filled in by above code */
2:	
	
	/* Switch to protected mode */
	pushw	%cs
	call	real_to_prot
	.code32

	/* Switch to virtual addresses */
	call	1f
	jmp	2f
1:	ljmp	$VIRTUAL_CS, $_phys_to_virt
2:	

	/* Enable A20 line */
	pushal
	lcall	$VIRTUAL_CS, $gateA20_set
	popl	%eax	/* discard */
	popal

	/* Return */
	ret
	
/****************************************************************************
 * Relocation lock counter
 *
 * librm may be moved in base memory only when this counter is zero.
 * The counter gets incremented whenever a reference to librm is
 * generated (e.g. a real_call is made, resulting in a return address
 * pointing to librm being placed on the stack), and decremented when
 * the reference goes out of scope (e.g. the real_call returns).
 ****************************************************************************
 */
EXPORT(librm_ref_count):	.byte 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.
 ****************************************************************************
 */

EXPORT(rm_stack):	/* comprises rm_ss and rm_sp */
rm_sp:		.word 0
rm_ss:		.word 0

EXPORT(pm_stack):
pm_esp:		.long 0

/****************************************************************************
 * Temporary variables
 ****************************************************************************
 */
save_eax:	.long 0
save_ebx:	.long 0
save_retaddr:	.long 0
	
/****************************************************************************
 * End of librm
 ****************************************************************************
 */
_librm_end:
	.globl _librm_size
	.equ _librm_size, _librm_end - _librm_start