#define PXENV_UNDI_SHUTDOWN 0x0005 #define PXENV_UNDI_GET_NIC_TYPE 0x0012 #define PXENV_STOP_UNDI 0x0015 #define PXENV_UNLOAD_STACK 0x0070 #define PXE_STACK_MAGIC 0x57ac /* 'STac' */ .text .arch i386 .org 0 .section ".prefix", "ax", @progbits .section ".prefix.data", "aw", @progbits .code16 /***************************************************************************** * Entry point: set operating context, print welcome message ***************************************************************************** */ .section ".prefix" jmp $0x7c0, $1f 1: /* Preserve registers for return to PXE stack */ pushfl pushal pushw %gs pushw %fs pushw %es pushw %ds pushw %ss pushw %cs pushw $PXE_STACK_MAGIC /* PXE stack magic marker */ /* Set up stack just below 0x7c00 */ pushw %ss popw %gs movw %sp, %bp /* %gs:%bp points to old PXE stack */ xorw %ax, %ax movw %ax, %ss movw $0x7c00, %sp pushw %gs /* Save old PXE stack pointer */ pushw %bp /* Set up our other segment registers */ pushw %cs popw %ds movw $0x40, %ax /* BIOS data segment access */ movw %ax, %fs /* Clear direction flag, for the sake of sanity */ cld /* Print welcome message */ movw $10f, %si call print_message .section ".prefix.data" 10: .asciz "PXE->EB:" .previous /***************************************************************************** * Verify PXENV+ structure and record parameters of interest ***************************************************************************** */ detect_pxenv: /* Signature check */ cmpl $0x4e455850, %es:(%bx) /* 'PXEN' signature */ jne 99f cmpw $0x2b56, %es:4(%bx) /* 'V+' signature */ jne 99f /* Record structure address, entry point, and UNDI segments */ pushw %es popw pxenv_segment movw %bx, pxenv_offset pushl %es:0x0a(%bx) /* Entry point */ popl entry_segoff pushw %es:0x24(%bx) /* UNDI code segment */ pushw %es:0x26(%bx) /* UNDI code size */ popl undi_code_segoff pushw %es:0x20(%bx) /* UNDI data segment */ pushw %es:0x22(%bx) /* UNDI data size */ popl undi_data_segoff /* Print "PXENV+ at
" */ movw $10f, %si call print_message movw %bx, %di call print_segoff movb $',', %al call print_character .section ".prefix.data" 10: .asciz " PXENV+ at " .previous 99: /***************************************************************************** * Verify !PXE structure and record parameters of interest ***************************************************************************** */ detect_ppxe: /* Signature check */ les %gs:54(%bp), %di /* !PXE structure */ cmpl $0x45585021, %es:(%di) /* '!PXE' signature */ jne 99f /* Record structure address, entry point, and UNDI segments */ pushw %es popw ppxe_segment movw %di, ppxe_offset pushl %es:0x10(%di) /* Entry point */ popl entry_segoff pushw %es:0x30(%di) /* UNDI code segment */ pushw %es:0x36(%di) /* UNDI code size */ popl undi_code_segoff pushw %es:0x28(%di) /* UNDI data segment */ pushw %es:0x2e(%di) /* UNDI data size */ popl undi_data_segoff /* Print "!PXE at
" */ movw $10f, %si call print_message call print_segoff movb $',', %al call print_character .section ".prefix.data" 10: .asciz " !PXE at " .previous 99: /***************************************************************************** * Sanity check: we must have an entry point ***************************************************************************** */ check_have_stack: /* Check for entry point */ movl entry_segoff, %eax testl %eax, %eax jnz 99f /* No entry point: print message and skip everything else */ movw $10f, %si call print_message jmp finished .section ".prefix.data" 10: .asciz " No PXE stack found!\n" .previous 99: /***************************************************************************** * Calculate base memory usage by UNDI ***************************************************************************** */ find_undi_basemem_usage: movw undi_code_segment, %ax movw undi_code_size, %bx movw undi_data_segment, %cx movw undi_data_size, %dx cmpw %ax, %cx ja 1f xchgw %ax, %cx xchgw %bx, %dx 1: /* %ax:%bx now describes the lower region, %cx:%dx the higher */ shrw $6, %ax /* Round down to nearest kB */ movw %ax, undi_fbms_start addw $0x0f, %dx /* Round up to next segment */ shrw $4, %dx addw %dx, %cx addw $((1024 / 16) - 1), %cx /* Round up to next kB */ shrw $6, %cx movw %cx, undi_fbms_end /***************************************************************************** * Print information about detected PXE stack ***************************************************************************** */ print_structure_information: /* Print entry point */ movw $10f, %si call print_message les entry_segoff, %di call print_segoff .section ".prefix.data" 10: .asciz " entry point at " .previous /* Print UNDI code segment */ movw $10f, %si call print_message les undi_code_segoff, %di call print_segoff .section ".prefix.data" 10: .asciz "\n UNDI code segment " .previous /* Print UNDI data segment */ movw $10f, %si call print_message les undi_data_segoff, %di call print_segoff .section ".prefix.data" 10: .asciz ", data segment " .previous /* Print UNDI memory usage */ movw $10f, %si call print_message movw undi_fbms_start, %ax call print_word movb $'-', %al call print_character movw undi_fbms_end, %ax call print_word movw $20f, %si call print_message .section ".prefix.data" 10: .asciz " (" 20: .asciz "kB)\n" .previous /***************************************************************************** * Determine physical device ***************************************************************************** */ get_physical_device: /* Issue PXENV_UNDI_GET_NIC_TYPE */ movw $PXENV_UNDI_GET_NIC_TYPE, %bx call pxe_call jnc 1f call print_pxe_error jmp no_physical_device 1: /* Determine physical device type */ movb ( pxe_parameter_structure + 0x02 ), %al cmpb $2, %al je pci_physical_device jmp no_physical_device pci_physical_device: /* Record PCI bus:dev.fn */ movw ( pxe_parameter_structure + 0x0b ), %ax movw %ax, pci_busdevfn movw $10f, %si call print_message call print_pci_busdevfn movb $0x0a, %al call print_character jmp 99f .section ".prefix.data" 10: .asciz " UNDI device is PCI " .previous no_physical_device: /* No device found, or device type not understood */ movw $10f, %si call print_message .section ".prefix.data" 10: .asciz " Unable to determine UNDI physical device\n" .previous 99: /***************************************************************************** * Leave NIC in a safe state ***************************************************************************** */ shutdown_nic: /* Issue PXENV_UNDI_SHUTDOWN */ movw $PXENV_UNDI_SHUTDOWN, %bx call pxe_call jnc 1f call print_pxe_error 1: /***************************************************************************** * Unload PXE base code ***************************************************************************** */ unload_base_code: /* Issue PXENV_UNLOAD_STACK */ movw $PXENV_UNLOAD_STACK, %bx call pxe_call jnc 1f call print_pxe_error jmp 99f 1: /* Free base memory used by PXE base code */ movw %fs:(0x13), %si movw undi_fbms_start, %di call free_basemem 99: /***************************************************************************** * Unload UNDI driver ***************************************************************************** */ unload_undi: /* Issue PXENV_STOP_UNDI */ movw $PXENV_STOP_UNDI, %bx call pxe_call jnc 1f call print_pxe_error jmp 99f 1: /* Free base memory used by UNDI */ #ifndef PXELOADER_KEEP_UNDI movw undi_fbms_start, %si movw undi_fbms_end, %di call free_basemem #endif /* PXELOADER_KEEP_UNDI */ 99: /***************************************************************************** * Print remaining free base memory ***************************************************************************** */ print_free_basemem: movw $10f, %si call print_message movw %fs:(0x13), %ax call print_word movw $20f, %si call print_message .section ".prefix.data" 10: .asciz " " 20: .asciz "kB free base memory after PXE unload\n" .previous /***************************************************************************** * Exit point ***************************************************************************** */ finished: jmp run_etherboot /***************************************************************************** * Subroutine: print character (with LF -> LF,CR translation) * * Parameters: * %al : character to print * Returns: * Nothing ***************************************************************************** */ print_character: /* Preserve registers */ pushw %ax pushw %bx pushw %bp /* Print character */ movw $0x0007, %bx /* page 0, attribute 7 (normal) */ movb $0x0e, %ah /* write char, tty mode */ cmpb $0x0a, %al /* '\n'? */ jne 1f int $0x10 movb $0x0d, %al 1: int $0x10 /* Restore registers and return */ popw %bp popw %bx popw %ax ret /***************************************************************************** * Subroutine: print a NUL-terminated string * * Parameters: * %ds:%si : string to print * Returns: * Nothing ***************************************************************************** */ print_message: /* Preserve registers */ pushw %ax pushw %si /* Print string */ 1: lodsb testb %al, %al je 2f call print_character jmp 1b 2: /* Restore registers and return */ popw %si popw %ax ret /***************************************************************************** * Subroutine: print hex digit * * Parameters: * %al (low nibble) : digit to print * Returns: * Nothing ***************************************************************************** */ print_hex_nibble: /* Preserve registers */ pushw %ax /* Print digit (technique by Norbert Juffa */ andb $0x0f, %al cmpb $10, %al sbbb $0x69, %al das call print_character /* Restore registers and return */ popw %ax ret /***************************************************************************** * Subroutine: print hex byte * * Parameters: * %al : byte to print * Returns: * Nothing ***************************************************************************** */ print_hex_byte: rorb $4, %al call print_hex_nibble rorb $4, %al call print_hex_nibble ret /***************************************************************************** * Subroutine: print hex word * * Parameters: * %ax : word to print * Returns: * Nothing ***************************************************************************** */ print_hex_word: xchgb %al, %ah call print_hex_byte xchgb %al, %ah call print_hex_byte ret /***************************************************************************** * Subroutine: print segment:offset address * * Parameters: * %es:%di : segment:offset address to print * Returns: * Nothing ***************************************************************************** */ print_segoff: /* Preserve registers */ pushw %ax /* Print ":offset" */ movw %es, %ax call print_hex_word movb $':', %al call print_character movw %di, %ax call print_hex_word /* Restore registers and return */ popw %ax ret /***************************************************************************** * Subroutine: print decimal word * * Parameters: * %ax : word to print * Returns: * Nothing ***************************************************************************** */ print_word: /* Preserve registers */ pushw %ax pushw %bx pushw %cx pushw %dx /* Build up digit sequence on stack */ movw $10, %bx xorw %cx, %cx 1: xorw %dx, %dx divw %bx, %ax pushw %dx incw %cx testw %ax, %ax jnz 1b /* Print digit sequence */ 1: popw %ax call print_hex_nibble loop 1b /* Restore registers and return */ popw %dx popw %cx popw %bx popw %ax ret /***************************************************************************** * Subroutine: print PCI bus:dev.fn * * Parameters: * %ax : PCI bus:dev.fn to print * Returns: * Nothing ***************************************************************************** */ print_pci_busdevfn: /* Preserve registers */ pushw %ax /* Print bus */ xchgb %al, %ah call print_hex_byte /* Print ":" */ movb $':', %al call print_character /* Print device */ movb %ah, %al shrb $3, %al call print_hex_byte /* Print "." */ movb $'.', %al call print_character /* Print function */ movb %ah, %al andb $0x07, %al call print_hex_nibble /* Restore registers and return */ popw %ax ret /***************************************************************************** * Subroutine: zero 1kB block of base memory * * Parameters: * %si : block to zero (in kB) * Returns: * Nothing ***************************************************************************** */ zero_kb: /* Preserve registers */ pushw %ax pushw %cx pushw %di pushw %es /* Zero block */ movw %si, %ax shlw $6, %ax movw %ax, %es movw $0x400, %cx xorw %di, %di xorw %ax, %ax rep stosb /* Restore registers and return */ popw %es popw %di popw %cx popw %ax ret /***************************************************************************** * Subroutine: free and zero base memory * * Parameters: * %si : Expected current free base memory counter (in kB) * %di : Desired new free base memory counter (in kB) * %fs : BIOS data segment (0x40) * Returns: * %ax : Actual new free base memory counter (in kB) * * The base memory from %si kB to %di kB is unconditionally zeroed. * It will be freed if and only if the expected current free base * memory counter (%si) matches the actual current free base memory * counter in 0x40:0x13; if this does not match then the memory will * be leaked. ***************************************************************************** */ free_basemem: /* Zero base memory */ pushw %si 1: cmpw %si, %di je 2f call zero_kb incw %si jmp 1b 2: popw %si /* Free base memory */ movw %fs:(0x13), %ax /* Current FBMS to %ax */ cmpw %ax, %si /* Update FBMS only if "old" value */ jne 1f /* is correct */ movw %di, %ax 1: movw %ax, %fs:(0x13) ret /***************************************************************************** * Subroutine: make a PXE API call. Works with either !PXE or PXENV+ API. * * Parameters: * %bx : PXE API call number * %ds:pxe_parameter_structure : Parameters for PXE API call * Returns: * %ax : PXE status code (not exit code) * CF set if %ax is non-zero ***************************************************************************** */ pxe_call: /* Preserve registers */ pushw %di pushw %es /* Set up registers for PXENV+ API. %bx already set up */ pushw %ds popw %es movw $pxe_parameter_structure, %di /* Set up stack for !PXE API */ pushw %es pushw %di pushw %bx /* Make the API call */ lcall *entry_segoff /* Reset the stack */ addw $6, %sp movw pxe_parameter_structure, %ax clc testw %ax, %ax jz 1f stc 1: /* Restore registers and return */ popw %es popw %di ret /***************************************************************************** * Subroutine: print PXE API call error message * * Parameters: * %ax : PXE status code * %bx : PXE API call number * Returns: * Nothing ***************************************************************************** */ print_pxe_error: pushw %si movw $10f, %si call print_message xchgw %ax, %bx call print_hex_word movw $20f, %si call print_message xchgw %ax, %bx call print_hex_word movw $30f, %si call print_message popw %si ret .section ".prefix.data" 10: .asciz " UNDI API call " 20: .asciz " failed: status code " 30: .asciz "\n" .previous /***************************************************************************** * PXE data structures ***************************************************************************** */ pxe_parameter_structure: .fill 20 undi_code_segoff: undi_code_size: .word 0 undi_code_segment: .word 0 undi_data_segoff: undi_data_size: .word 0 undi_data_segment: .word 0 /* The following fields are part of a struct undi_device */ undi_device: pxenv_segoff: pxenv_offset: .word 0 pxenv_segment: .word 0 ppxe_segoff: ppxe_offset: .word 0 ppxe_segment: .word 0 entry_segoff: entry_offset: .word 0 entry_segment: .word 0 undi_fbms_start: .word 0 undi_fbms_end: .word 0 pci_busdevfn: .word 0xffff isapnp_csn: .word 0xffff isapnp_read_port: .word 0xffff .equ undi_device_size, ( . - undi_device ) /***************************************************************************** * Run Etherboot main code ***************************************************************************** */ run_etherboot: /* Install Etherboot */ call install #ifdef PXELOADER_KEEP_UNDI /* Copy our undi_device structure to the preloaded_undi variable */ movw %bx, %es movw $preloaded_undi, %di movw $undi_device, %si movw $undi_device_size, %cx rep movsb #endif /* Jump to .text16 segment with %ds pointing to .data16*/ movw %bx, %ds pushw %ax pushw $1f lret .section ".text16", "ax", @progbits 1: /* Original PXE stack pointer to es:di. We must hold it in * registers, because our current stack may be vapourised by * the time main() returns. (main() will still be able to * return, because prot_call() transfers the return address to * the internal stack and back again). */ popw %di popw %es /* Run main program */ pushl $main pushw %cs call prot_call popl %eax /* discard */ /* If original PXE stack is intact, return via PXE, else via INT 18 */ cmpw $PXE_STACK_MAGIC, %es:0(%di) jne exit_via_int18 exit_via_pxe: /* Stack OK, return to PXE */ movw $exit_via_pxe_message, %si call print_exit_message pushw %es /* Restore original PXE stack */ popw %ss movw %di, %sp popw %ax /* discard PXE_STACK_MAGIC */ popw %ax /* discard %cs */ popw %ax /* discard %ss */ popw %ds popw %es popw %fs popw %gs popal popfl xorw %ax, %ax /* Return PXENV_STATUS_SUCCESS */ lret exit_via_int18: /* Stack damaged, do int 18 */ movw $exit_via_int18_message, %si call print_exit_message int $0x18 print_exit_message: movw $0x0007, %bx /* page 0, attribute 7 (normal) */ movb $0x0e, %ah /* write char, tty mode */ 1: lodsb testb %al, %al je 2f int $0x10 jmp 1b 2: ret .section ".data16", "aw", @progbits exit_via_pxe_message: .asciz "EB->PXE\r\n" exit_via_int18_message: .asciz "EB->BIOS\r\n"