blob: a2543dfe6d2d45212bec89931cd041ce6528ee54 [file] [log] [blame]
; -*- 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