; -*- 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
;
; The following structure is used for "virtual kernels"; i.e. LILO-style
; option labels. The options we permit here are `kernel' and `append
; Since there is no room in the bottom 64K for all of these, we
; stick them in high memory and copy them down before we need them.
;
struc vkernel
vk_vname: resb FILENAME_MAX ; Virtual name **MUST BE FIRST!**
vk_rname: resb FILENAME_MAX ; Real name
vk_ipappend: resb 1 ; "IPAPPEND" flag
vk_type: resb 1 ; Type of file
vk_appendlen: resw 1
alignb 4
vk_append: resb max_cmd_len+1 ; Command line
alignb 4
vk_end: equ $ ; Should be <= vk_size
endstruc
; ---------------------------------------------------------------------------
; 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, APIVer
RebootTime resd 1 ; Reboot timeout, if set by option
StrucPtr resw 2 ; Pointer to PXENV+ or !PXE structure
APIVer resw 1 ; PXE API version found
LocalBootType resw 1 ; Local boot return code
DHCPMagic resb 1 ; PXELINUX magic flags
section .text16
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
; 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
;
; Move the hardwired DHCP options (if present) to a safe place...
;
bdhcp_copy:
mov cx,[bdhcp_len]
mov ax,trackbufsize/2
jcxz .none
cmp cx,ax
jbe .oksize
mov cx,ax
mov [bdhcp_len],ax
.oksize:
mov eax,[bdhcp_offset]
add eax,_start
mov si,ax
and si,000Fh
shr eax,4
push ds
mov ds,ax
mov di,trackbuf
add cx,3
shr cx,2
rep movsd
pop ds
.none:
adhcp_copy:
mov cx,[adhcp_len]
mov ax,trackbufsize/2
jcxz .none
cmp cx,ax
jbe .oksize
mov cx,ax
mov [adhcp_len],ax
.oksize:
mov eax,[adhcp_offset]
add eax,_start
mov si,ax
and si,000Fh
shr eax,4
push ds
mov ds,ax
mov di,trackbuf+trackbufsize/2
add cx,3
shr cx,2
rep movsd
pop ds
.none:
;
; 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 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
;
call reset_idle
;
; Now we're all set to start with our *real* business. First load the
; configuration file (if any) and parse it.
;
; 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.
;
;
; Load configuration file
;
pm_call load_config
;
; 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
;
; Now we have the config file open. Parse the config file and
; run the user interface.
;
%include "ui.inc"
;
; Boot to the local disk by returning the appropriate PXE magic.
; AX contains the appropriate return code.
;
local_boot:
push cs
pop ds
mov [LocalBootType],ax
call vgaclearmode
mov si,localboot_msg
call writestr_early
; Restore the environment we were called with
pm_call reset_pxe
call cleanup_hardware
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
call do_idle
cmp dx,[BIOS_timer]
je .wait3
loop .wait2,ecx
mov al,'.'
call writechr
pop cx
loop .wait1
.keypress:
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
mov [cs:PXEStack],sp
mov [cs:PXEStack+2],ss
lss sp,[cs:InitStack]
; 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
lss sp,[cs:PXEStack]
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
popfd ; Restore flags (incl. IF, DF)
ret
; Must be after function def due to NASM bug
global PXEEntry
PXEEntry equ pxenv.jump+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
section .data16
alignz 4
pxe_file_exit_hook:
.status: dw 0
.offset: dw gpxe_unload.resume
.seg: dw 0
%endif
section .text16
; -----------------------------------------------------------------------------
; Common modules
; -----------------------------------------------------------------------------
%include "common.inc" ; Universal modules
%include "writestr.inc" ; String output
writestr_early equ writestr
%include "writehex.inc" ; Hexadecimal output
%include "rawcon.inc" ; Console I/O w/o using the console functions
; -----------------------------------------------------------------------------
; Begin data section
; -----------------------------------------------------------------------------
section .data16
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, ' ', DATE_STR, ' ', 0
;
; Config file keyword table
;
%include "keywords.inc"
;
; Extensions to search for (in *forward* order).
; (.bs and .bss16 are disabled for PXELINUX, since they are not supported)
;
alignz 4
exten_table: db '.cbt' ; COMBOOT (specific)
db '.0', 0, 0 ; PXE bootstrap program
db '.com' ; COMBOOT (same as DOS)
db '.c32' ; COM32
exten_table_end:
dd 0, 0 ; Need 8 null bytes here
;
; Misc initialized (data) variables
;
section .data16
global KeepPXE
KeepPXE db 0 ; Should PXE be kept around?
;
; IP information. Note that the field are in the same order as the
; Linux kernel expects in the ip= option.
;
section .bss16
alignb 4
global IPInfo
IPInfo:
.IPv4 resd 1 ; IPv4 information
.MyIP resd 1 ; My IP address
.ServerIP resd 1
.GatewayIP resd 1
.Netmask resd 1