; -*- fundamental -*- (asm-mode sucks) ; **************************************************************************** ; ; pxelinux.asm ; ; A program to boot Linux kernels off a TFTP server using the Intel PXE ; network booting API. It is based on the SYSLINUX boot loader for ; MS-DOS floppies. ; ; Copyright 1994-2009 H. Peter Anvin - All Rights Reserved ; Copyright 2009 Intel Corporation; author: H. Peter Anvin ; ; This program is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation, Inc., 53 Temple Place Ste 330, ; Boston MA 02111-1307, USA; either version 2 of the License, or ; (at your option) any later version; incorporated herein by reference. ; ; **************************************************************************** %define IS_PXELINUX 1 %include "head.inc" %include "pxe.inc" ; gPXE extensions support %define GPXE 1 ; ; Some semi-configurable constants... change on your own risk. ; my_id equ pxelinux_id NULLFILE equ 0 ; Zero byte == null file name NULLOFFSET equ 0 ; Position in which to look REBOOT_TIME equ 5*60 ; If failure, time until full reset %assign HIGHMEM_SLOP 128*1024 ; Avoid this much memory near the top TFTP_BLOCKSIZE_LG2 equ 9 ; log2(bytes/block) TFTP_BLOCKSIZE equ (1 << TFTP_BLOCKSIZE_LG2) SECTOR_SHIFT equ TFTP_BLOCKSIZE_LG2 SECTOR_SIZE equ TFTP_BLOCKSIZE ; --------------------------------------------------------------------------- ; BEGIN CODE ; --------------------------------------------------------------------------- ; ; Memory below this point is reserved for the BIOS and the MBR ; section .earlybss global trackbuf trackbufsize equ 8192 trackbuf resb trackbufsize ; Track buffer goes here ; ends at 2800h ; These fields save information from before the time ; .bss is zeroed... must be in .earlybss global InitStack InitStack resd 1 section .bss16 alignb FILENAME_MAX PXEStack resd 1 ; Saved stack during PXE call alignb 4 global DHCPMagic, RebootTime, BIOSName RebootTime resd 1 ; Reboot timeout, if set by option LocalBootType resw 1 ; Local boot return code DHCPMagic resb 1 ; PXELINUX magic flags BIOSName resw 1 ; Dummy variable - always 0 section .text16 global StackBuf StackBuf equ STACK_TOP-44 ; Base of stack if we use our own StackHome equ StackBuf ; PXE loads the whole file, but assume it can't be more ; than (384-31)K in size. MaxLMA equ 384*1024 ; ; Primary entry point. ; bootsec equ $ _start: jmp 0:_start1 ; Canonicalize the address and skip ; the patch header ; ; Patch area for adding hardwired DHCP options ; align 4 hcdhcp_magic dd 0x2983c8ac ; Magic number hcdhcp_len dd 7*4 ; Size of this structure hcdhcp_flags dd 0 ; Reserved for the future global bdhcp_len, adhcp_len ; Parameters to be parsed before the ones from PXE bdhcp_offset dd 0 ; Offset (entered by patcher) bdhcp_len dd 0 ; Length (entered by patcher) ; Parameters to be parsed *after* the ones from PXE adhcp_offset dd 0 ; Offset (entered by patcher) adhcp_len dd 0 ; Length (entered by patcher) _start1: pushfd ; Paranoia... in case of return to PXE pushad ; ... save as much state as possible push ds push es push fs push gs cld ; Copy upwards xor ax,ax mov ds,ax mov es,ax %if 0 ; debugging code only... not intended for production use ; Clobber the stack segment, to test for specific pathologies mov di,STACK_BASE mov cx,STACK_LEN >> 1 mov ax,0xf4f4 rep stosw ; Clobber the tail of the 64K segment, too extern __bss1_end mov di,__bss1_end sub cx,di ; CX = 0 previously shr cx,1 rep stosw %endif ; That is all pushed onto the PXE stack. Save the pointer ; to it and switch to an internal stack. mov [InitStack],sp mov [InitStack+2],ss lss esp,[BaseStack] sti ; Stack set up and ready ; ; Initialize screen (if we're using one) ; %include "init.inc" ; ; Tell the user we got this far ; mov si,syslinux_banner call writestr_early mov si,copyright_str call writestr_early ; ; do fs initialize ; mov eax,ROOT_FS_OPS xor ebp,ebp pm_call pm_fs_init section .rodata alignz 4 ROOT_FS_OPS: extern pxe_fs_ops dd pxe_fs_ops dd 0 section .text16 ; ; Initialize the idle mechanism ; extern reset_idle pm_call reset_idle ; ; Now we're all set to start with our *real* business. ; ; In previous versions I avoided using 32-bit registers because of a ; rumour some BIOSes clobbered the upper half of 32-bit registers at ; random. I figure, though, that if there are any of those still left ; they probably won't be trying to install Linux on them... ; ; The code is still ripe with 16-bitisms, though. Not worth the hassle ; to take'm out. In fact, we may want to put them back if we're going ; to boot ELKS at some point. ; ; ; Linux kernel loading code is common. However, we need to define ; a couple of helper macros... ; ; Unload PXE stack %define HAVE_UNLOAD_PREP %macro UNLOAD_PREP 0 pm_call unload_pxe %endmacro ; ; Jump to 32-bit ELF space ; pm_call load_env32 jmp kaboom ; load_env32() shouldn't return. If it does, then kaboom! print_hello: enter_command: auto_boot: pm_call hello ; ; Save hardwired DHCP options. This is done before the C environment ; is initialized, so it has to be done in assembly. ; %define MAX_DHCP_OPTS 4096 bits 32 section .savedata global bdhcp_data, adhcp_data bdhcp_data: resb MAX_DHCP_OPTS adhcp_data: resb MAX_DHCP_OPTS section .textnr pm_save_data: mov eax,MAX_DHCP_OPTS movzx ecx,word [bdhcp_len] cmp ecx,eax jna .oksize mov ecx,eax mov [bdhcp_len],ax .oksize: mov esi,[bdhcp_offset] add esi,_start mov edi,bdhcp_data add ecx,3 shr ecx,2 rep movsd adhcp_copy: movzx ecx,word [adhcp_len] cmp ecx,eax jna .oksize mov ecx,eax mov [adhcp_len],ax .oksize: mov esi,[adhcp_offset] add esi,_start mov edi,adhcp_data add ecx,3 shr ecx,2 rep movsd ret bits 16 ; As core/ui.inc used to be included here in core/pxelinux.asm, and it's no ; longer used, its global variables that were previously used by ; core/pxelinux.asm are now declared here. section .bss16 alignb 4 Kernel_EAX resd 1 Kernel_SI resw 1 section .bss16 alignb 4 ThisKbdTo resd 1 ; Temporary holder for KbdTimeout ThisTotalTo resd 1 ; Temporary holder for TotalTimeout KernelExtPtr resw 1 ; During search, final null pointer FuncFlag resb 1 ; Escape sequences received from keyboard KernelType resb 1 ; Kernel type, from vkernel, if known global KernelName KernelName resb FILENAME_MAX ; Mangled name for kernel section .text16 ; ; COM32 vestigial data structure ; %include "com32.inc" section .text16 global local_boot16:function hidden local_boot16: mov [LocalBootType],ax lss sp,[InitStack] pop gs pop fs pop es pop ds popad mov ax,[cs:LocalBootType] cmp ax,-1 ; localboot -1 == INT 18h je .int18 popfd retf ; Return to PXE .int18: popfd int 18h jmp 0F000h:0FFF0h hlt ; ; kaboom: write a message and bail out. Wait for quite a while, ; or a user keypress, then do a hard reboot. ; ; Note: use BIOS_timer here; we may not have jiffies set up. ; global kaboom kaboom: RESET_STACK_AND_SEGS AX .patch: mov si,bailmsg call writestr_early ; Returns with AL = 0 .drain: call pollchar jz .drained call getchar jmp short .drain .drained: mov edi,[RebootTime] mov al,[DHCPMagic] and al,09h ; Magic+Timeout cmp al,09h je .time_set mov edi,REBOOT_TIME .time_set: mov cx,18 .wait1: push cx mov ecx,edi .wait2: mov dx,[BIOS_timer] .wait3: call pollchar jnz .keypress pm_call __idle cmp dx,[BIOS_timer] je .wait3 loop .wait2,ecx mov al,'.' pm_call pm_writechr pop cx loop .wait1 .keypress: pm_call crlf mov word [BIOS_magic],0 ; Cold reboot jmp 0F000h:0FFF0h ; Reset vector address ; ; pxenv ; ; This is the main PXENV+/!PXE entry point, using the PXENV+ ; calling convention. This is a separate local routine so ; we can hook special things from it if necessary. In particular, ; some PXE stacks seem to not like being invoked from anything but ; the initial stack, so humour it. ; ; While we're at it, save and restore all registers. ; global pxenv pxenv: pushfd pushad ; We may be removing ourselves from memory cmp bx,PXENV_RESTART_TFTP jz .disable_timer cmp bx,PXENV_FILE_EXEC jnz .store_stack .disable_timer: call bios_timer_cleanup .store_stack: pushf cli inc word [cs:PXEStackLock] jnz .skip1 pop bp mov [cs:PXEStack],sp mov [cs:PXEStack+2],ss lss sp,[cs:InitStack] push bp .skip1: popf ; Pre-clear the Status field mov word [es:di],cs ; This works either for the PXENV+ or the !PXE calling ; convention, as long as we ignore CF (which is redundant ; with AX anyway.) push es push di push bx .jump: call 0:0 add sp,6 mov [cs:PXEStatus],ax pushf cli dec word [cs:PXEStackLock] jns .skip2 pop bp lss sp,[cs:PXEStack] push bp .skip2: popf mov bp,sp and ax,ax setnz [bp+32] ; If AX != 0 set CF on return ; This clobbers the AX return, but we already saved it into ; the PXEStatus variable. popad ; If the call failed, it could return. cmp bx,PXENV_RESTART_TFTP jz .enable_timer cmp bx,PXENV_FILE_EXEC jnz .pop_flags .enable_timer: call timer_init .pop_flags: popfd ; Restore flags (incl. IF, DF) ret ; Must be after function def due to NASM bug global PXEEntry PXEEntry equ pxenv.jump+1 ; ; The PXEStackLock keeps us from switching stacks if we take an interrupt ; (which ends up calling pxenv) while we are already on the PXE stack. ; It will be -1 normally, 0 inside a PXE call, and a positive value ; inside a *nested* PXE call. ; section .data16 alignb 2 PXEStackLock dw -1 section .bss16 alignb 2 PXEStatus resb 2 section .text16 ; ; Invoke INT 1Ah on the PXE stack. This is used by the "Plan C" method ; for finding the PXE entry point. ; global pxe_int1a pxe_int1a: mov [cs:PXEStack],sp mov [cs:PXEStack+2],ss lss sp,[cs:InitStack] int 1Ah ; May trash registers lss sp,[cs:PXEStack] ret ; ; Special unload for gPXE: this switches the InitStack from ; gPXE to the ROM PXE stack. ; %if GPXE global gpxe_unload gpxe_unload: mov bx,PXENV_FILE_EXIT_HOOK mov di,pxe_file_exit_hook call pxenv jc .plain ; Now we actually need to exit back to gPXE, which will ; give control back to us on the *new* "original stack"... pushfd push ds push es mov [PXEStack],sp mov [PXEStack+2],ss lss sp,[InitStack] pop gs pop fs pop es pop ds popad popfd xor ax,ax retf .resume: cli ; gPXE will have a stack frame looking much like our ; InitStack, except it has a magic cookie at the top, ; and the segment registers are in reverse order. pop eax pop ax pop bx pop cx pop dx push ax push bx push cx push dx mov [cs:InitStack],sp mov [cs:InitStack+2],ss lss sp,[cs:PXEStack] pop es pop ds popfd .plain: ret writestr_early: pm_call pm_writestr ret pollchar: pm_call pm_pollchar ret getchar: pm_call pm_getchar ret section .data16 alignz 4 pxe_file_exit_hook: .status: dw 0 .offset: dw gpxe_unload.resume .seg: dw 0 %endif section .text16 ; ----------------------------------------------------------------------------- ; PXE modules ; ----------------------------------------------------------------------------- %if IS_LPXELINUX %include "pxeisr.inc" %endif ; ----------------------------------------------------------------------------- ; Common modules ; ----------------------------------------------------------------------------- %include "common.inc" ; Universal modules ; ----------------------------------------------------------------------------- ; Begin data section ; ----------------------------------------------------------------------------- section .data16 global copyright_str, syslinux_banner copyright_str db 'Copyright (C) 1994-' asciidec YEAR db ' H. Peter Anvin et al', CR, LF, 0 err_bootfailed db CR, LF, 'Boot failed: press a key to retry, or wait for reset...', CR, LF, 0 bailmsg equ err_bootfailed localboot_msg db 'Booting from local disk...', CR, LF, 0 syslinux_banner db CR, LF, MY_NAME, ' ', VERSION_STR, ' ', MY_TYPE, ' ' db DATE_STR, ' ', 0 ; ; Misc initialized (data) variables ; section .data16 global KeepPXE KeepPXE db 0 ; Should PXE be kept around? section .bss16 global OrigFDCTabPtr OrigFDCTabPtr resd 1 ; Keep bios_cleanup_hardware() honest