574 lines
12 KiB
Plaintext
574 lines
12 KiB
Plaintext
; -*- 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
|