[librm] Reduce real-mode stack consumption in virt_call()

Some PXE NBPs are known to make PXE API calls with very little space
available on the real-mode stack.  For example, the Rembo-ia32 NBP
from some versions of IBM's Tivoli Provisioning Manager for Operating
System Deployment (TPMfOSD) will issue calls with the real-mode stack
placed at 0000:03d2; this is at the end of the interrupt vector table
and leaves only 498 bytes of stack space available before overwriting
the hardware IRQ vectors.  This limits the amount of state that we can
preserve before transitioning to protected mode.

Work around these challenging conditions by preserving everything
other than the initial register dump in a temporary static buffer
within our real-mode data segment, and copying the contents of this
buffer to the protected-mode stack.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
pull/53/head
Michael Brown 2016-04-29 11:33:13 +01:00
parent b696a5063e
commit 2d42d3cff6
1 changed files with 103 additions and 59 deletions

View File

@ -180,17 +180,49 @@ gdt_end:
* to us. * to us.
**************************************************************************** ****************************************************************************
*/ */
.section ".bss.rm_sp", "aw", @nobits .section ".bss.rm_ss_sp", "aw", @nobits
.globl rm_sp .globl rm_sp
rm_sp: .word 0 rm_sp: .word 0
.section ".bss.rm_ss", "aw", @nobits
.globl rm_ss .globl rm_ss
rm_ss: .word 0 rm_ss: .word 0
.section ".data.pm_esp", "aw", @progbits .section ".data.pm_esp", "aw", @progbits
pm_esp: .long VIRTUAL(_estack) pm_esp: .long VIRTUAL(_estack)
/****************************************************************************
* Temporary static data buffer
*
* This is used to reduce the amount of real-mode stack space consumed
* during mode transitions, since we are sometimes called with very
* little real-mode stack space available.
****************************************************************************
*/
/* Temporary static buffer usage by virt_call */
.struct 0
VC_TMP_GDT: .space 6
VC_TMP_IDT: .space 6
VC_TMP_PAD: .space 4 /* for alignment */
.if64
VC_TMP_CR3: .space 4
VC_TMP_CR4: .space 4
VC_TMP_EMER: .space 8
.endif
VC_TMP_END:
.previous
/* Temporary static buffer usage by real_call */
.struct 0
RC_TMP_FUNCTION: .space 4
RC_TMP_END:
.previous
/* Shared temporary static buffer */
.section ".bss16.rm_tmpbuf", "aw", @nobits
.align 16
rm_tmpbuf:
.space VC_TMP_END
.size rm_tmpbuf, . - rm_tmpbuf
/**************************************************************************** /****************************************************************************
* Virtual address offsets * Virtual address offsets
* *
@ -341,6 +373,7 @@ set_seg_base:
* *
* Parameters: * Parameters:
* %ecx : number of bytes to move from RM stack to PM stack * %ecx : number of bytes to move from RM stack to PM stack
* %edx : number of bytes to copy from RM temporary buffer to PM stack
* *
**************************************************************************** ****************************************************************************
*/ */
@ -361,14 +394,19 @@ real_to_prot:
/* Add protected-mode return address to length of data to be copied */ /* Add protected-mode return address to length of data to be copied */
addw $4, %cx /* %ecx must be less than 64kB anyway */ addw $4, %cx /* %ecx must be less than 64kB anyway */
/* Real-mode %ss:%sp => %ebp:%edx and virtual address => %esi */ /* Real-mode %ss:%sp => %ebp and virtual address => %esi */
xorl %ebp, %ebp xorl %eax, %eax
movw %ss, %bp movw %ss, %ax
movzwl %sp, %edx
movl %ebp, %eax
shll $4, %eax shll $4, %eax
addr32 leal (%eax,%edx), %esi movzwl %sp, %ebp
addr32 leal (%eax,%ebp), %esi
subl rm_virt_offset, %esi subl rm_virt_offset, %esi
shll $12, %eax
orl %eax, %ebp
/* Real-mode data segment virtual address => %ebx */
movl rm_data16, %ebx
.if64 ; subl rm_virt_offset, %ebx ; .endif
/* Load protected-mode global descriptor table */ /* Load protected-mode global descriptor table */
data32 lgdt gdtr data32 lgdt gdtr
@ -407,15 +445,20 @@ r2p_pmode:
lidt VIRTUAL(idtr32) lidt VIRTUAL(idtr32)
/* Record real-mode %ss:sp (after removal of data) */ /* Record real-mode %ss:sp (after removal of data) */
movw %bp, VIRTUAL(rm_ss) addl %ecx, %ebp
addl %ecx, %edx movl %ebp, VIRTUAL(rm_sp)
movw %dx, VIRTUAL(rm_sp)
/* Move data from RM stack to PM stack */ /* Move data from RM stack to PM stack */
subl %edx, %esp
subl %ecx, %esp subl %ecx, %esp
movl %esp, %edi movl %esp, %edi
rep movsb rep movsb
/* Copy data from RM temporary buffer to PM stack */
leal rm_tmpbuf(%ebx), %esi
movl %edx, %ecx
rep movsb
/* Return to virtual address */ /* Return to virtual address */
ret ret
@ -435,6 +478,7 @@ r2p_pmode:
* *
* Parameters: * Parameters:
* %ecx : number of bytes to move from PM stack to RM stack * %ecx : number of bytes to move from PM stack to RM stack
* %edx : number of bytes to move from PM stack to RM temporary buffer
* %esi : real-mode global and interrupt descriptor table registers * %esi : real-mode global and interrupt descriptor table registers
* *
**************************************************************************** ****************************************************************************
@ -455,19 +499,26 @@ prot_to_real:
/* Add return address to data to be moved to RM stack */ /* Add return address to data to be moved to RM stack */
addl $4, %ecx addl $4, %ecx
/* Real-mode %ss:sp => %ebp:edx and virtual address => %edi */ /* Real-mode %ss:sp => %ebp and virtual address => %edi */
movzwl VIRTUAL(rm_ss), %ebp movl VIRTUAL(rm_sp), %ebp
movzwl VIRTUAL(rm_sp), %edx subl %ecx, %ebp
subl %ecx, %edx movzwl VIRTUAL(rm_ss), %eax
movl %ebp, %eax
shll $4, %eax shll $4, %eax
leal (%eax,%edx), %edi movzwl %bp, %edi
addl %eax, %edi
subl VIRTUAL(virt_offset), %edi subl VIRTUAL(virt_offset), %edi
/* Move data from PM stack to RM stack */ /* Move data from PM stack to RM stack */
movl %esp, %esi movl %esp, %esi
rep movsb rep movsb
/* Move data from PM stack to RM temporary buffer */
movl VIRTUAL(data16), %edi
.if64 ; subl VIRTUAL(virt_offset), %edi ; .endif
addl $rm_tmpbuf, %edi
movl %edx, %ecx
rep movsb
/* Record protected-mode %esp (after removal of data) */ /* Record protected-mode %esp (after removal of data) */
movl %esi, VIRTUAL(pm_esp) movl %esi, VIRTUAL(pm_esp)
@ -497,8 +548,10 @@ p2r_ljmp_rm_cs:
movw %ax, %es movw %ax, %es
movw %ax, %fs movw %ax, %fs
movw %ax, %gs movw %ax, %gs
movw %bp, %ss movl %ebp, %eax
movl %edx, %esp shrl $16, %eax
movw %ax, %ss
movzwl %bp, %esp
/* Return to real-mode address */ /* Return to real-mode address */
data32 ret data32 ret
@ -921,14 +974,6 @@ long_restore_regs:
**************************************************************************** ****************************************************************************
*/ */
.struct 0 .struct 0
VC_OFFSET_GDT: .space 6
VC_OFFSET_IDT: .space 6
.if64
VC_OFFSET_PADDING64: .space 4 /* for alignment */
VC_OFFSET_CR3: .space 4
VC_OFFSET_CR4: .space 4
VC_OFFSET_EMER: .space 8
.endif
VC_OFFSET_IX86: .space SIZEOF_I386_ALL_REGS VC_OFFSET_IX86: .space SIZEOF_I386_ALL_REGS
VC_OFFSET_PADDING: .space 2 /* for alignment */ VC_OFFSET_PADDING: .space 2 /* for alignment */
VC_OFFSET_RETADDR: .space 2 VC_OFFSET_RETADDR: .space 2
@ -941,7 +986,7 @@ VC_OFFSET_END:
.code16 .code16
.globl virt_call .globl virt_call
virt_call: virt_call:
/* Preserve registers, flags and GDT on external RM stack */ /* Preserve registers and flags on external RM stack */
pushw %ss /* padding */ pushw %ss /* padding */
pushfl pushfl
pushal pushal
@ -951,34 +996,38 @@ virt_call:
pushw %ds pushw %ds
pushw %ss pushw %ss
pushw %cs pushw %cs
subw $VC_OFFSET_IX86, %sp
movw %sp, %bp /* Claim ownership of temporary static buffer */
sidt VC_OFFSET_IDT(%bp) cli
sgdt VC_OFFSET_GDT(%bp)
/* Preserve GDT and IDT in temporary static buffer */
movw %cs:rm_ds, %ds
sidt ( rm_tmpbuf + VC_TMP_IDT )
sgdt ( rm_tmpbuf + VC_TMP_GDT )
.if64 ; /* Preserve control registers, if applicable */ .if64 ; /* Preserve control registers, if applicable */
movl $MSR_EFER, %ecx movl $MSR_EFER, %ecx
rdmsr rdmsr
movl %eax, (VC_OFFSET_EMER+0)(%bp) movl %eax, ( rm_tmpbuf + VC_TMP_EMER + 0 )
movl %edx, (VC_OFFSET_EMER+4)(%bp) movl %edx, ( rm_tmpbuf + VC_TMP_EMER + 4 )
movl %cr4, %eax movl %cr4, %eax
movl %eax, VC_OFFSET_CR4(%bp) movl %eax, ( rm_tmpbuf + VC_TMP_CR4 )
movl %cr3, %eax movl %cr3, %eax
movl %eax, VC_OFFSET_CR3(%bp) movl %eax, ( rm_tmpbuf + VC_TMP_CR3 )
.endif .endif
/* For sanity's sake, clear the direction flag as soon as possible */ /* For sanity's sake, clear the direction flag as soon as possible */
cld cld
/* Switch to protected mode and move register dump to PM stack */ /* Switch to protected mode and move register dump to PM stack */
movl $VC_OFFSET_END, %ecx movl $VC_OFFSET_END, %ecx
movl $VC_TMP_END, %edx
pushl $VIRTUAL(vc_pmode) pushl $VIRTUAL(vc_pmode)
vc_jmp: jmp real_to_prot vc_jmp: jmp real_to_prot
.section ".text.virt_call", "ax", @progbits .section ".text.virt_call", "ax", @progbits
.code32 .code32
vc_pmode: vc_pmode:
/* Call function (in protected mode) */ /* Call function (in protected mode) */
leal VC_OFFSET_IX86(%esp), %eax pushl %esp
pushl %eax
call *(VC_OFFSET_FUNCTION+4)(%esp) call *(VC_OFFSET_FUNCTION+4)(%esp)
popl %eax /* discard */ popl %eax /* discard */
@ -989,11 +1038,9 @@ vc_lmode:
.code64 .code64
/* Call function (in long mode) */ /* Call function (in long mode) */
leaq VC_OFFSET_IX86(%rsp), %rdi movq %rsp, %rdi
pushq %rdi movslq VC_OFFSET_FUNCTION(%rsp), %rax
movslq (VC_OFFSET_FUNCTION+8)(%rsp), %rax
callq *%rax callq *%rax
popq %rdi /* discard */
/* Switch to protected mode */ /* Switch to protected mode */
call long_to_prot call long_to_prot
@ -1001,7 +1048,8 @@ vc_lmode:
.endif .endif
/* Switch to real mode and move register dump back to RM stack */ /* Switch to real mode and move register dump back to RM stack */
movl $VC_OFFSET_END, %ecx movl $VC_OFFSET_END, %ecx
movl %esp, %esi movl $VC_TMP_END, %edx
leal VC_TMP_GDT(%esp, %ecx), %esi
pushl $vc_rmode pushl $vc_rmode
jmp prot_to_real jmp prot_to_real
.section ".text16.virt_call", "ax", @progbits .section ".text16.virt_call", "ax", @progbits
@ -1009,17 +1057,17 @@ vc_lmode:
vc_rmode: vc_rmode:
.if64 ; /* Restore control registers, if applicable */ .if64 ; /* Restore control registers, if applicable */
movw %sp, %bp movw %sp, %bp
movl VC_OFFSET_CR3(%bp), %eax movl ( rm_tmpbuf + VC_TMP_CR3 ), %eax
movl %eax, %cr3 movl %eax, %cr3
movl VC_OFFSET_CR4(%bp), %eax movl ( rm_tmpbuf + VC_TMP_CR4 ), %eax
movl %eax, %cr4 movl %eax, %cr4
movl (VC_OFFSET_EMER+0)(%bp), %eax movl ( rm_tmpbuf + VC_TMP_EMER + 0 ), %eax
movl (VC_OFFSET_EMER+4)(%bp), %edx movl ( rm_tmpbuf + VC_TMP_EMER + 4 ), %edx
movl $MSR_EFER, %ecx movl $MSR_EFER, %ecx
wrmsr wrmsr
.endif .endif
/* Restore registers and flags and return */ /* Restore registers and flags and return */
addw $( VC_OFFSET_IX86 + 4 /* also skip %cs and %ss */ ), %sp popl %eax /* skip %cs and %ss */
popw %ds popw %ds
popw %es popw %es
popw %fs popw %fs
@ -1067,6 +1115,7 @@ vc_rmode:
.struct 0 .struct 0
RC_OFFSET_REGS: .space SIZEOF_I386_REGS RC_OFFSET_REGS: .space SIZEOF_I386_REGS
RC_OFFSET_REGS_END: RC_OFFSET_REGS_END:
RC_OFFSET_FUNCTION_COPY:.space 4
.if64 .if64
RC_OFFSET_LREGS: .space SIZEOF_X86_64_REGS RC_OFFSET_LREGS: .space SIZEOF_X86_64_REGS
RC_OFFSET_LREG_RETADDR: .space SIZEOF_ADDR RC_OFFSET_LREG_RETADDR: .space SIZEOF_ADDR
@ -1087,11 +1136,12 @@ real_call:
.code32 .code32
.endif .endif
/* Create register dump and function pointer copy on PM stack */ /* Create register dump and function pointer copy on PM stack */
pushl ( RC_OFFSET_FUNCTION - RC_OFFSET_FUNCTION_COPY - 4 )(%esp)
pushal pushal
pushl RC_OFFSET_FUNCTION(%esp)
/* Switch to real mode and move register dump to RM stack */ /* Switch to real mode and move register dump to RM stack */
movl $( RC_OFFSET_REGS_END + 4 /* function pointer copy */ ), %ecx movl $RC_OFFSET_REGS_END, %ecx
movl $RC_TMP_END, %edx
pushl $rc_rmode pushl $rc_rmode
movl $VIRTUAL(rm_default_gdtr_idtr), %esi movl $VIRTUAL(rm_default_gdtr_idtr), %esi
jmp prot_to_real jmp prot_to_real
@ -1099,9 +1149,8 @@ real_call:
.code16 .code16
rc_rmode: rc_rmode:
/* Call real-mode function */ /* Call real-mode function */
popl rc_function
popal popal
call *rc_function call *( rm_tmpbuf + RC_TMP_FUNCTION )
pushal pushal
/* For sanity's sake, clear the direction flag as soon as possible */ /* For sanity's sake, clear the direction flag as soon as possible */
@ -1109,6 +1158,7 @@ rc_rmode:
/* Switch to protected mode and move register dump back to PM stack */ /* Switch to protected mode and move register dump back to PM stack */
movl $RC_OFFSET_REGS_END, %ecx movl $RC_OFFSET_REGS_END, %ecx
xorl %edx, %edx
pushl $VIRTUAL(rc_pmode) pushl $VIRTUAL(rc_pmode)
jmp real_to_prot jmp real_to_prot
.section ".text.real_call", "ax", @progbits .section ".text.real_call", "ax", @progbits
@ -1126,12 +1176,6 @@ rc_pmode:
ret $( RC_OFFSET_END - RC_OFFSET_PARAMS ) ret $( RC_OFFSET_END - RC_OFFSET_PARAMS )
/* Function vector, used because "call xx(%sp)" is not a valid
* 16-bit expression.
*/
.section ".bss16.rc_function", "aw", @nobits
rc_function: .word 0, 0
/* Default real-mode global and interrupt descriptor table registers */ /* Default real-mode global and interrupt descriptor table registers */
.section ".data.rm_default_gdtr_idtr", "aw", @progbits .section ".data.rm_default_gdtr_idtr", "aw", @progbits
rm_default_gdtr_idtr: rm_default_gdtr_idtr: