123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720 |
- /*
- * librm: a library for interfacing to real-mode code
- *
- * Michael Brown <mbrown@fensystems.co.uk>
- *
- */
-
- /* Drag in local definitions */
- #include "librm.h"
-
- /* Drag in FREE_BASEMEM_HEADER_SIZE */
- #include "basemem.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
-
- /****************************************************************************
- * Note that the first sizeof(struct free_base_memory_header) bytes of
- * librm will get vapourised by free_base_memory(). Since we need
- * librm to continue working even when this happens, we put some
- * padding here.
- *
- * We must also ensure that the total size of librm is <1kB, otherwise
- * free_base_memory() will stomp somewhere in the middle of us as
- * well...
- ****************************************************************************
- */
- .fill FREE_BASEMEM_HEADER_SIZE, 1, 0
-
- /****************************************************************************
- * 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 */
- data32 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
|