/*! @file i386/boot1pxe/boot1pxe.s @copyright 2007,2009 David F. Elliott @abstract PXE NBP (remote.0 style) @discussion This file must be the first in the binary and must be located at 0:7C00 because it will be called (in real mode no less) by the PXE NBP loader. This program is an NBP and as such we simply follow the NBP guidelines. The entire thing (including BSS) really ought not to be larger than 32k so as to avoid eating into the next real-mode segment. It's actually 33k from 0x7c00 to the start of the next segment. */ #include #include "memory.h" .file "boot1pxe.s" .data # Provide SEGOFF16 structure to hold "PXENV+" from ES:BX # NOTE: Must be within 34k (0x7c00 to 0x10000) EXPORT(_pxenv_segoff) Lpxenv_off: .word 0x0000 Lpxenv_seg: .word 0x0000 # Provide SEGOFF16 structure to hold "!PXE" from stack EXPORT(_pxe_segoff) Lpxe_off: .word 0x0000 Lpxe_seg: .word 0x0000 .text # Booter Entry point. Called by PXE # Absolutely MUST be first in TEXT segment at 0x7c00 # Or alternatively second in the TEXT segment at 0x7e00 # Either way, the code enters as-if it were just called by the PXE NBP # loader. If the prebooter is used then it will have stowed the # PXENV+ and !PXE pointers for us which is really handy # PXE makes essentially a real-mode far call so SS:[SP+4] is a far-pointer to the "!PXE" # That is, unless we're running on pre-2.1 PXE in which case no guarantee is made. # # For pre-2.1 PXE (and this remains in the new spec) ES:BX points to the old "PXENV+" # # Other state: # # CS:IP ought to be (must be according to doc) 0:7C00 # NOTE: it will be 0:7E00 if the prebooter is used # EDX is meaningless # SS:SP is valid and is the true PXE stack.. it must be the active stack when # boot2 is finally called because it will then use it when calling us # and we will in turn use it when calling PXE # We are supposedly guaranteed at least 1.5k of stack space, which should be fine # .text LABEL(boot1pxe) .code16 pushw %bp movw %sp, %bp pushw %cx # Save general purpose registers pushw %bx pushw %si pushw %di push %ds # Save DS, ES push %es # NOTE: ES must be last thing pushed movl %cs, %ax # Update segment registers. mov %ax, %ds # Set DS and ES to match CS mov %ax, %es // Save EBP (which is entrypoint ESP - 2) so that calls to PXE // will appear to have come from a giant real-mode far procedure movl %ebp, _gPXE_real_entry_ebp #if 0 # This never ends.. until we manually set $eax = 0xffff from GDB Lreal_loop1: cmpw $0xffff, %ax jne Lreal_loop1 #endif //===================================================================== // Save "PXENV+" far pointer // NOTE: We haven't clobbered BX so we can just mov it directly mov %bx, _pxenv_segoff // NOTE: We've clobbered ES so pop it into ax then push it back pop %ax push %ax #if 0 mov %ax, _pxenv_segoff + 2 #else // Linker hack: Cannot relocate word symbol w/ offset although symbol alone works // unsupported r_length=1 for scattered vanilla reloc file '.../obj/i386/boot1pxe/boot1pxe.o' for architecture i386 .byte 0xa3 // mov %ax, .long _pxenv_segoff + 2 + 0x90900000 #endif //===================================================================== // Save "!PXE" far pointer // This is actually the first (and only) argument to this function as // if it were 16-bit C mov %ss:6(%bp), %ax mov %ax, _pxe_segoff mov %ss:8(%bp), %ax #if 0 mov %ax, _pxe_segoff + 2 #else // Linker hack: Cannot relocate word symbol w/ offset although symbol alone works // unsupported r_length=1 for scattered vanilla reloc file '.../obj/i386/boot1pxe/boot1pxe.o' for architecture i386 .byte 0xa3 // mov %ax, .long _pxe_segoff + 2 + 0x90900000 #endif //===================================================================== // Start booter #if 0 calll __switch_stack # Switch to new stack #endif calll __real_to_prot # Enter protected mode. .code32 call __adjust_ebp_real_to_prot # Adjust ebp fninit # FPU init # We are now in 32-bit protected mode. # Transfer execution to C by calling boot(). pushl %edx # bootdev (basically useless) call _boot # NOTE: technically we're done with this stack anyway so cleaning # it up isn't really needed, but do it anyway addl $4, %esp # clean up stack movl %eax, %ebx # Stow EAX return in EBX call __prot_to_real # Back to real mode. .code16 #if 0 calll __switch_stack # Restore original stack #endif movw %bx, %ax # Get 16-bit return value back from BX pop %es # Restore original ES and DS pop %ds popw %di # Restore all general purpose registers popw %si # except EAX. popw %bx popw %cx popw %bp testw %ax, %ax jz do_chain lret # far return do_chain: jmp $BOOT2_SEG, $BOOT2_OFS .code32 // We're a PXE booter, so CS = 0 #define BASE1PXE_SEG 0 // Segment zero (which NBPs must live in) begins at memory address 0 CODE32_BASE = ADDR32(BASE1PXE_SEG, 0) //CODE32_BASE = 0 CODE16_SEG = BASE1PXE_SEG #define BASE1PXE_ADDR ADDR32(BASE1PXE_SEG, 0) #define CR0_PE_ON 0x1 #define CR0_PE_OFF 0x7ffffff0 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Pointer to 6-bytes in memory that contains the base address and the limit // (size of GDT table in bytes) of the GDT. The LGDT is the only instruction // that directly loads a linear address (not a segment relative address) and // a limit in protected mode. .globl _Gdtr .data .align 2, 0x90 _Gdtr: .word GDTLIMIT .long vtop(_Gdt) // DFE: Data area for the saved real SS // It's a long because we fill it in from a 32-bit GP register so it's // just easier for it to also be 32-bit saved_real_ss: .long 0 .text // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // real_to_prot() // // Transfer from real mode to protected mode. // Preserves all registers except EAX. // // DFE: This is slightly enhanced over Apple's original version because it // does not assume that SS=0x5000 and thus ESP must have 0x50000 added to it. // This is required because we aren't technically allowed to use any area // of real-memory not given to us by the NBP loader until we figure out // which areas are and are not available. // Since it guarantees us at least 1.5k of stack and since that should // be enough for us, we prefer to use it's stack as is! // // DFE: Another change is that it doesn't screw with ebp since the bios.s // code really doesn't expect this (this is a bug in the original code here) // That means that callers other than bios may want to adjust ebp accordingly LABEL(__real_to_prot) .code16 // Interrupts are disabled in protected mode. cli // Load the Global Descriptor Table Register (GDTR). addr32 \ lgdtl OFFSET16(_Gdtr) // Enter protected mode by setting the PE bit in CR0. mov %cr0, %eax orl $CR0_PE_ON, %eax mov %eax, %cr0 // Make intrasegment jump to flush the processor pipeline and // reload CS register. ljmpl $0x08, $xprot xprot: .code32 // we are in USE32 mode now // set up the protected mode segment registers : DS, SS, ES, FS, GS mov $0x10, %eax movw %ax, %ds // movw %ax, %ss // Don't clobber ss movw %ax, %es movw %ax, %fs movw %ax, %gs // SS is still loaded with old data // Make sure we're only using the low 16-bits of esp since // real mode would have been using only the low 16-bits movzwl %sp, %eax movl %eax, %esp // NOTE: standard movl moves and zeros 16-bit ss (still 16-bit even in PM) // to 32-bit eax movl %ss, %eax // stash the ss so we can restore it when switching back movl %eax, saved_real_ss // shift the 16-bit segment by 4 bits sall $4, %eax // add it to esp addl %eax, %esp // Finally set ss appropriately movl $0x10, %eax nop nop movw %ax, %ss // Modify the caller's return address on the stack from // segment offset to linear address. popl %eax addl $CODE32_BASE, %eax pushl %eax ret // Call from 32-bit protected mode to adjust bp after real_to_prot call // clobbers nothing LABEL(__adjust_ebp_real_to_prot) pushl %eax // Make sure we're only using the low 16-bits of ebp since // real mode would have been using only the low 16-bits movzwl %bp, %eax movl %eax, %ebp movl saved_real_ss, %eax // shift the 16-bit segment by 4 bits sall $4, %eax // add it to ebp addl %eax, %ebp popl %eax ret // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // prot_to_real() // // Transfer from protected mode to real mode. // Preserves all registers except EAX. // LABEL(__prot_to_real) // Set up segment registers appropriate for real mode. movw $0x30, %ax movw %ax, %ds movw %ax, %es movw %ax, %fs movw %ax, %gs // movw %ax, %ss ljmp $0x18, $x16 // change to USE16 mode x16: .code16 mov %cr0, %eax // clear the PE bit of CR0 andl $CR0_PE_OFF, %eax mov %eax, %cr0 // make intersegment jmp to flush the processor pipeline // and reload CS register ljmpl $CODE16_SEG, $xreal - CODE32_BASE xreal: // we are in real mode now // set up the real mode segment registers : DS, DS, ES, FS, GS movl %cs, %eax movw %ax, %ds movw %ax, %es movw %ax, %fs movw %ax, %gs .code16 // Make our life easier // GAS will generate code that when run in 16-bit mode (which we're now in) // will still do 32-bit arithmetic.. yay movl saved_real_ss, %eax mov %eax, %ss // shift the 16-bit segment left by 4 bits sall $4, %eax // Subtract it from esp which gives us our new 16-bit sp subl %eax, %esp // Modify caller's return address on the stack // from linear address to segment offset. popl %eax movzwl %ax, %eax pushl %eax // Reenable maskable interrupts. sti retl .code32