blob: de1b10c362908f06b302a04b8f4a64eb7581765f [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
FILENAME_MAX_LG2 equ 7 ; log2(Max filename size Including final null)
FILENAME_MAX equ (1 << FILENAME_MAX_LG2)
NULLFILE equ 0 ; Zero byte == null file name
NULLOFFSET equ 4 ; 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
MAX_OPEN_LG2 equ 5 ; log2(Max number of open sockets)
MAX_OPEN equ (1 << MAX_OPEN_LG2)
PKTBUF_SIZE equ (65536/MAX_OPEN) ; Per-socket packet buffer size
TFTP_PORT equ htons(69) ; Default TFTP port
; Desired TFTP block size
; For Ethernet MTU is normally 1500. Unfortunately there seems to
; be a fair number of networks with "substandard" MTUs which break.
; The code assumes TFTP_LARGEBLK <= 2K.
TFTP_MTU equ 1440
TFTP_LARGEBLK equ (TFTP_MTU-20-8-4) ; MTU - IP hdr - UDP hdr - TFTP hdr
; Standard TFTP block size
TFTP_BLOCKSIZE_LG2 equ 9 ; log2(bytes/block)
TFTP_BLOCKSIZE equ (1 << TFTP_BLOCKSIZE_LG2)
%assign USE_PXE_PROVIDED_STACK 1 ; Use stack provided by PXE?
SECTOR_SHIFT equ TFTP_BLOCKSIZE_LG2
SECTOR_SIZE equ TFTP_BLOCKSIZE
;
; TFTP operation codes
;
TFTP_RRQ equ htons(1) ; Read request
TFTP_WRQ equ htons(2) ; Write request
TFTP_DATA equ htons(3) ; Data packet
TFTP_ACK equ htons(4) ; ACK packet
TFTP_ERROR equ htons(5) ; ERROR packet
TFTP_OACK equ htons(6) ; OACK packet
;
; TFTP error codes
;
TFTP_EUNDEF equ htons(0) ; Unspecified error
TFTP_ENOTFOUND equ htons(1) ; File not found
TFTP_EACCESS equ htons(2) ; Access violation
TFTP_ENOSPACE equ htons(3) ; Disk full
TFTP_EBADOP equ htons(4) ; Invalid TFTP operation
TFTP_EBADID equ htons(5) ; Unknown transfer
TFTP_EEXISTS equ htons(6) ; File exists
TFTP_ENOUSER equ htons(7) ; No such user
TFTP_EOPTNEG equ htons(8) ; Option negotiation failure
;
; 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
;
; BOOTP/DHCP packet pattern
;
struc bootp_t
bootp:
.opcode resb 1 ; BOOTP/DHCP "opcode"
.hardware resb 1 ; ARP hardware type
.hardlen resb 1 ; Hardware address length
.gatehops resb 1 ; Used by forwarders
.ident resd 1 ; Transaction ID
.seconds resw 1 ; Seconds elapsed
.flags resw 1 ; Broadcast flags
.cip resd 1 ; Client IP
.yip resd 1 ; "Your" IP
.sip resd 1 ; Next server IP
.gip resd 1 ; Relay agent IP
.macaddr resb 16 ; Client MAC address
.sname resb 64 ; Server name (optional)
.bootfile resb 128 ; Boot file name
.option_magic resd 1 ; Vendor option magic cookie
.options resb 1260 ; Vendor options
endstruc
BOOTP_OPTION_MAGIC equ htonl(0x63825363) ; See RFC 2132
;
; TFTP connection data structure. Each one of these corresponds to a local
; UDP port. The size of this structure must be a power of 2.
; HBO = host byte order; NBO = network byte order
; (*) = written by options negotiation code, must be dword sized
;
; For a gPXE connection, we set the local port number to -1 and the
; remote port number contains the gPXE file handle.
;
struc open_file_t
tftp_localport resw 1 ; Local port number (0 = not in use)
tftp_remoteport resw 1 ; Remote port number
tftp_remoteip resd 1 ; Remote IP address
tftp_filepos resd 1 ; Bytes downloaded (including buffer)
tftp_filesize resd 1 ; Total file size(*)
tftp_blksize resd 1 ; Block size for this connection(*)
tftp_bytesleft resw 1 ; Unclaimed data bytes
tftp_lastpkt resw 1 ; Sequence number of last packet (NBO)
tftp_dataptr resw 1 ; Pointer to available data
tftp_goteof resb 1 ; 1 if the EOF packet received
resb 3 ; Currently unusued
; At end since it should not be zeroed on socked close
tftp_pktbuf resw 1 ; Packet buffer offset
endstruc
%ifndef DEPEND
%if (open_file_t_size & (open_file_t_size-1))
%error "open_file_t is not a power of 2"
%endif
%endif
; ---------------------------------------------------------------------------
; BEGIN CODE
; ---------------------------------------------------------------------------
;
; Memory below this point is reserved for the BIOS and the MBR
;
section .earlybss
trackbufsize equ 8192
trackbuf resb trackbufsize ; Track buffer goes here
; ends at 2800h
alignb open_file_t_size
Files resb MAX_OPEN*open_file_t_size
alignb FILENAME_MAX
BootFile resb 256 ; Boot file from DHCP packet
PathPrefix resb 256 ; Path prefix derived from boot file
DotQuadBuf resb 16 ; Buffer for dotted-quad IP address
IPOption resb 80 ; ip= option buffer
InitStack resd 1 ; Pointer to reset stack (SS:SP)
PXEStack resd 1 ; Saved stack during PXE call
section .bss
alignb 4
RebootTime resd 1 ; Reboot timeout, if set by option
StrucPtr resd 1 ; Pointer to PXENV+ or !PXE structure
APIVer resw 1 ; PXE API version found
LocalBootType resw 1 ; Local boot return code
RealBaseMem resw 1 ; Amount of DOS memory after freeing
OverLoad resb 1 ; Set if DHCP packet uses "overloading"
DHCPMagic resb 1 ; PXELINUX magic flags
; The relative position of these fields matter!
MAC_MAX equ 32 ; Handle hardware addresses this long
MACLen resb 1 ; MAC address len
MACType resb 1 ; MAC address type
MAC resb MAC_MAX+1 ; Actual MAC address
BOOTIFStr resb 7 ; Space for "BOOTIF="
MACStr resb 3*(MAC_MAX+1) ; MAC address as a string
; The relative position of these fields matter!
UUIDType resb 1 ; Type byte from DHCP option
UUID resb 16 ; UUID, from the PXE stack
UUIDNull resb 1 ; dhcp_copyoption zero-terminates
;
; PXE packets which don't need static initialization
;
alignb 4
pxe_unload_stack_pkt:
.status: resw 1 ; Status
.reserved: resb 10 ; Reserved
pxe_unload_stack_pkt_len equ $-pxe_unload_stack_pkt
alignb 16
; BOOTP/DHCP packet buffer
section .bss2
alignb 16
packet_buf resb 2048 ; Transfer packet
packet_buf_size equ $-packet_buf
section .text
;
; PXELINUX needs more BSS than the other derivatives;
; therefore we relocate it from 7C00h on startup.
;
StackBuf equ $ ; Base of stack if we use our own
;
; Primary entry point.
;
bootsec equ $
_start:
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
jmp 0:_start1 ; Canonicalize address
_start1:
; 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
%if USE_PXE_PROVIDED_STACK
; Apparently some platforms go bonkers if we
; set up our own stack...
mov [BaseStack],sp
mov [BaseStack+4],ss
%endif
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
;
; Assume API version 2.1, in case we find the !PXE structure without
; finding the PXENV+ structure. This should really look at the Base
; Code ROM ID structure in have_pxe, but this is adequate for now --
; if we have !PXE, we have to be 2.1 or higher, and we don't care
; about higher versions than that.
;
mov word [APIVer],0201h
;
; Now we need to find the !PXE structure.
; We search for the following, in order:
;
; a. !PXE structure as SS:[SP+4]
; b. PXENV+ structure at [ES:BX]
; c. INT 1Ah AX=5650h -> PXENV+
; d. Search memory for !PXE
; e. Search memory for PXENV+
;
; If we find a PXENV+ structure, we try to find a !PXE structure from
; it if the API version is 2.1 or later.
;
; Plan A: !PXE structure as SS:[SP+4]
lgs bp,[InitStack] ; GS:BP -> original stack
les bx,[gs:bp+48]
call is_pxe
je have_pxe
; Plan B: PXENV+ structure at [ES:BX]
inc byte [plan]
mov bx,[gs:bp+24] ; Original BX
mov es,[gs:bp+4] ; Original ES
call is_pxenv
je have_pxenv
; Plan C: PXENV+ structure via INT 1Ah AX=5650h
inc byte [plan]
mov ax, 5650h
%if USE_PXE_PROVIDED_STACK == 0
lss sp,[InitStack]
%endif
int 1Ah ; May trash regs
%if USE_PXE_PROVIDED_STACK == 0
lss esp,[BaseStack]
%endif
sti ; Work around Etherboot bug
jc no_int1a
cmp ax,564Eh
jne no_int1a
call is_pxenv
je have_pxenv
no_int1a:
; Plan D: !PXE memory scan
inc byte [plan]
call memory_scan_for_pxe_struct ; !PXE scan
je have_pxe
; Plan E: PXENV+ memory scan
inc byte [plan]
call memory_scan_for_pxenv_struct ; PXENV+ scan
je have_pxenv
; Found nothing at all!!
no_pxe:
mov si,err_nopxe
call writestr_early
jmp kaboom
have_pxenv:
mov [StrucPtr],bx
mov [StrucPtr+2],es
mov si,found_pxenv
call writestr_early
mov si,apiver_str
call writestr_early
mov ax,[es:bx+6]
mov [APIVer],ax
call writehex4
call crlf
cmp ax,0201h ; API version 2.1 or higher
jb .old_api
cmp byte [es:bx+8],2Ch ; Space for !PXE pointer?
jb .pxescan
les bx,[es:bx+28h] ; !PXE structure pointer
call is_pxe
je have_pxe
; Nope, !PXE structure missing despite API 2.1+, or at least
; the pointer is missing. Do a last-ditch attempt to find it.
.pxescan:
call memory_scan_for_pxe_struct
je have_pxe
; Otherwise, no dice, use PXENV+ structure
.old_api:
les bx,[StrucPtr]
push word [es:bx+22h] ; UNDI data len
push word [es:bx+20h] ; UNDI data seg
push word [es:bx+26h] ; UNDI code len
push word [es:bx+24h] ; UNDI code seg
push dword [es:bx+0Ah] ; PXENV+ entry point
mov si,pxenventry_msg
jmp have_entrypoint
have_pxe:
mov [StrucPtr],bx
mov [StrucPtr+2],es
push word [es:bx+2Eh] ; UNDI data len
push word [es:bx+28h] ; UNDI data seg
push word [es:bx+36h] ; UNDI code len
push word [es:bx+30h] ; UNDI code seg
push dword [es:bx+10h] ; !PXE entry point
mov si,pxeentry_msg
have_entrypoint:
push cs
pop es ; Restore CS == DS == ES
call writestr_early ; !PXE or PXENV+ entry found
pop dword [PXEEntry]
mov ax,[PXEEntry+2]
call writehex4
mov al,':'
call writechr
mov ax,[PXEEntry]
call writehex4
mov si,viaplan_msg
call writestr_early
mov si,undi_code_msg
call writestr_early
pop ax ; UNDI code segment
call writehex4
xchg dx,ax
mov si,len_msg
call writestr_early
pop ax ; UNDI code length
call writehex4
call crlf
add ax,15
shr ax,4
add dx,ax ; DX = seg of end of UNDI code
mov si,undi_data_msg
call writestr_early
pop ax ; UNDI data segment
call writehex4
xchg bx,ax
mov si,len_msg
call writestr_early
pop ax ; UNDI data length
call writehex4
call crlf
add ax,15
shr ax,4
add ax,bx ; AX = seg of end of UNDI data
cmp ax,dx
ja .data_on_top
xchg ax,dx
.data_on_top:
; Could we safely add 63 here before the shift?
shr ax,6 ; Convert to kilobytes
mov [RealBaseMem],ax
;
; Network-specific initialization
;
xor ax,ax
mov [LocalDomain],al ; No LocalDomain received
;
; The DHCP client identifiers are best gotten from the DHCPREQUEST
; packet (query info 1).
;
query_bootp_1:
mov si,get_packet_msg
call writestr_early
mov dl,1
call pxe_get_cached_info
call parse_dhcp
; We don't use flags from the request packet, so
; this is a good time to initialize DHCPMagic...
; Initialize it to 1 meaning we will accept options found;
; in earlier versions of PXELINUX bit 0 was used to indicate
; we have found option 208 with the appropriate magic number;
; we no longer require that, but MAY want to re-introduce
; it in the future for vendor encapsulated options.
mov byte [DHCPMagic],1
;
; Now attempt to get the BOOTP/DHCP packet that brought us life (and an IP
; address). This lives in the DHCPACK packet (query info 2).
;
query_bootp_2:
mov dl,2
call pxe_get_cached_info
call parse_dhcp ; Parse DHCP packet
;
; Save away MAC address (assume this is in query info 2. If this
; turns out to be problematic it might be better getting it from
; the query info 1 packet.)
;
.save_mac:
movzx cx,byte [trackbuf+bootp.hardlen]
cmp cx,16
jna .mac_ok
xor cx,cx ; Bad hardware address length
.mac_ok:
mov [MACLen],cl
mov al,[trackbuf+bootp.hardware]
mov [MACType],al
mov si,trackbuf+bootp.macaddr
mov di,MAC
rep movsb
; Enable this if we really need to zero-pad this field...
; mov cx,MAC+MAC_MAX+1
; sub cx,di
; xor ax,ax
; rep stosb
;
; Now, get the boot file and other info. This lives in the CACHED_REPLY
; packet (query info 3).
;
query_bootp_3:
mov dl,3
call pxe_get_cached_info
call parse_dhcp ; Parse DHCP packet
call crlf
;
; Generate the bootif string, and the hardware-based config string.
;
make_bootif_string:
mov si,bootif_str
mov di,BOOTIFStr
mov cx,bootif_str_len
rep movsb
movzx cx,byte [MACLen]
mov si,MACType
inc cx
.hexify_mac:
push cx
mov cl,1 ; CH == 0 already
call lchexbytes
mov al,'-'
stosb
pop cx
loop .hexify_mac
mov [di-1],cl ; Null-terminate and strip final dash
;
; Generate ip= option
;
call genipopt
;
; Print IP address
;
mov eax,[MyIP]
mov di,DotQuadBuf
push di
call gendotquad ; This takes network byte order input
xchg ah,al ; Convert to host byte order
ror eax,16 ; (BSWAP doesn't work on 386)
xchg ah,al
mov si,myipaddr_msg
call writestr_early
call writehex8
mov al,' '
call writechr
pop si ; DotQuadBuf
call writestr_early
call crlf
mov si,IPOption
call writestr_early
call crlf
;
; Check to see if we got any PXELINUX-specific DHCP options; in particular,
; if we didn't get the magic enable, do not recognize any other options.
;
check_dhcp_magic:
test byte [DHCPMagic], 1 ; If we didn't get the magic enable...
jnz .got_magic
mov byte [DHCPMagic], 0 ; If not, kill all other options
.got_magic:
;
; Initialize UDP stack
;
udp_init:
mov eax,[MyIP]
mov [pxe_udp_open_pkt.sip],eax
mov di,pxe_udp_open_pkt
mov bx,PXENV_UDP_OPEN
call pxenv
jc .failed
cmp word [pxe_udp_open_pkt.status], byte 0
je .success
.failed: mov si,err_udpinit
call writestr_early
jmp kaboom
.success:
;
; Common initialization code
;
%include "cpuinit.inc"
;
; Detect NIC type and initialize the idle mechanism
;
call pxe_detect_nic_type
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.
;
;
; Store standard filename prefix
;
prefix: test byte [DHCPMagic], 04h ; Did we get a path prefix option
jnz .got_prefix
mov si,BootFile
mov di,PathPrefix
cld
call strcpy
mov cx,di
sub cx,PathPrefix+1
std
lea si,[di-2] ; Skip final null!
.find_alnum: lodsb
or al,20h
cmp al,'.' ; Count . or - as alphanum
je .alnum
cmp al,'-'
je .alnum
cmp al,'0'
jb .notalnum
cmp al,'9'
jbe .alnum
cmp al,'a'
jb .notalnum
cmp al,'z'
ja .notalnum
.alnum: loop .find_alnum
dec si
.notalnum: mov byte [si+2],0 ; Zero-terminate after delimiter
cld
.got_prefix:
mov si,tftpprefix_msg
call writestr_early
mov si,PathPrefix
call writestr_early
call crlf
; Set CurrentDirName
push di
mov si,PathPrefix
mov di,CurrentDirName
call strcpy
pop di
;
; Load configuration file
;
find_config:
;
; Begin looking for configuration file
;
config_scan:
test byte [DHCPMagic], 02h
jz .no_option
; We got a DHCP option, try it first
call .try
jnz .success
.no_option:
mov di,ConfigName
mov si,cfgprefix
mov cx,cfgprefix_len
rep movsb
; Have to guess config file name...
; Try loading by UUID.
cmp byte [HaveUUID],0
je .no_uuid
push di
mov bx,uuid_dashes
mov si,UUID
.gen_uuid:
movzx cx,byte [bx]
jcxz .done_uuid
inc bx
call lchexbytes
mov al,'-'
stosb
jmp .gen_uuid
.done_uuid:
mov [di-1],cl ; Remove last dash and zero-terminate
pop di
call .try
jnz .success
.no_uuid:
; Try loading by MAC address
push di
mov si,MACStr
call strcpy
pop di
call .try
jnz .success
; Nope, try hexadecimal IP prefixes...
.scan_ip:
mov cx,4
mov si,MyIP
call uchexbytes ; Convert to hex string
mov cx,8 ; Up to 8 attempts
.tryagain:
mov byte [di],0 ; Zero-terminate string
call .try
jnz .success
dec di ; Drop one character
loop .tryagain
; Final attempt: "default" string
mov si,default_str ; "default" string
call strcpy
call .try
jnz .success
mov si,err_noconfig
call writestr_early
jmp kaboom
.try:
pusha
mov si,trying_msg
call writestr_early
mov di,ConfigName
mov si,di
call writestr_early
call crlf
mov si,di
mov di,KernelName ; Borrow this buffer for mangled name
call mangle_name
call open
popa
ret
.success:
;
; 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
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.
;
%if HAS_LOCALBOOT
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
lss sp,[InitStack]
pop gs
pop fs
pop es
pop ds
popad
mov ax,[cs:LocalBootType]
popfd
retf ; Return to PXE
%endif
;
; kaboom: write a message and bail out. Wait for quite a while,
; or a user keypress, then do a hard reboot.
;
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
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
;
; memory_scan_for_pxe_struct:
; memory_scan_for_pxenv_struct:
;
; If none of the standard methods find the !PXE/PXENV+ structure,
; look for it by scanning memory.
;
; On exit, if found:
; ZF = 1, ES:BX -> !PXE structure
; Otherwise:
; ZF = 0
;
; Assumes DS == CS
; Clobbers AX, BX, CX, DX, SI, ES
;
memory_scan_for_pxe_struct:
mov dx,is_pxe
mov ax,[BIOS_fbm] ; Starting segment
shl ax,(10-4) ; Kilobytes -> paragraphs
jmp memory_scan_common
memory_scan_for_pxenv_struct:
mov ax,1000h ; Starting segment
mov dx,is_pxenv
; fall through
memory_scan_common:
dec ax ; To skip inc ax
.mismatch:
inc ax
cmp ax,0A000h-1 ; End of memory
ja .not_found ; ZF = 0 on not found
mov es,ax
xor bx,bx
call dx
jne .mismatch
.not_found:
ret
;
; is_pxe:
; Validity check on possible !PXE structure in ES:BX
; is_pxenv:
; Validity check on possible PXENV+ structure in ES:BX
;
; Return ZF = 1 on success
;
; Clobbers CX and SI
;
is_struc:
.pxe:
cmp dword [es:bx],'!PXE'
jne .bad
movzx cx,byte [es:bx+4]
cmp cx,58h
jae .checksum
ret
.pxenv:
cmp dword [es:bx],'PXEN'
jne .bad
cmp word [es:bx+4],'V+'
jne .bad
movzx cx,[es:bx+8]
cmp cx,28h
jb .bad
.checksum:
push ax
mov si,bx
xor ax,ax
.loop:
es lodsb
add ah,al
loop .loop
pop ax
.bad:
ret
is_pxe equ is_struc.pxe
is_pxenv equ is_struc.pxenv
;
; close_file:
; Deallocates a file structure (pointer in SI)
; Assumes CS == DS.
;
; XXX: We should check to see if this file is still open on the server
; side and send a courtesy ERROR packet to the server.
;
close_file:
and si,si
jz .closed
mov word [si],0 ; Not in use
.closed: ret
;
; searchdir:
;
; Open a TFTP connection to the server
;
; On entry:
; DS:DI = mangled filename
; If successful:
; ZF clear
; SI = socket pointer
; EAX = file length in bytes, or -1 if unknown
; If unsuccessful
; ZF set
;
searchdir:
push es
push bx
push cx
mov ax,ds
mov es,ax
mov si,di
push bp
mov bp,sp
call allocate_socket
jz .ret
mov ax,TimeoutTable ; Reset timeout
.sendreq: push ax ; [bp-2] - Timeout pointer
push si ; [bp-4] - File name
mov di,packet_buf
mov [pxe_udp_write_pkt.buffer],di
mov ax,TFTP_RRQ ; TFTP opcode
stosw
lodsd ; EAX <- server override (if any)
and eax,eax
jnz .noprefix ; No prefix, and we have the server
push si ; Add common prefix
mov si,PathPrefix
call strcpy
dec di
pop si
mov eax,[ServerIP] ; Get default server
.noprefix:
call strcpy ; Filename
%if GPXE
mov si,packet_buf+2
call is_gpxe
jnc .gpxe
%endif
mov [bx+tftp_remoteip],eax
push bx ; [bp-6] - TFTP block
mov bx,[bx]
push bx ; [bp-8] - TID (local port no)
mov [pxe_udp_write_pkt.sip],eax
; Now figure out the gateway
xor eax,[MyIP]
and eax,[Netmask]
jz .nogwneeded
mov eax,[Gateway]
.nogwneeded:
mov [pxe_udp_write_pkt.gip],eax
mov [pxe_udp_write_pkt.lport],bx
mov ax,[ServerPort]
mov [pxe_udp_write_pkt.rport],ax
mov si,tftp_tail
mov cx,tftp_tail_len
rep movsb
sub di,packet_buf ; Get packet size
mov [pxe_udp_write_pkt.buffersize],di
mov di,pxe_udp_write_pkt
mov bx,PXENV_UDP_WRITE
call pxenv
jc .failure
cmp word [pxe_udp_write_pkt.status],byte 0
jne .failure
;
; Danger, Will Robinson! We need to support timeout
; and retry lest we just lost a packet...
;
; Packet transmitted OK, now we need to receive
.getpacket: mov bx,[bp-2]
movzx bx,byte [bx]
push bx ; [bp-10] - timeout in ticks
push word [BIOS_timer] ; [bp-12]
.pkt_loop: mov bx,[bp-8] ; TID
mov di,packet_buf
mov [pxe_udp_read_pkt.buffer],di
mov [pxe_udp_read_pkt.buffer+2],ds
mov word [pxe_udp_read_pkt.buffersize],packet_buf_size
mov eax,[MyIP]
mov [pxe_udp_read_pkt.dip],eax
mov [pxe_udp_read_pkt.lport],bx
mov di,pxe_udp_read_pkt
mov bx,PXENV_UDP_READ
call pxenv
jnc .got_packet ; Wait for packet
.no_packet:
mov dx,[BIOS_timer]
cmp dx,[bp-12]
je .pkt_loop
mov [bp-12],dx
dec word [bp-10]
jnz .pkt_loop
pop ax ; Adjust stack
pop ax
jmp .failure
.got_packet:
mov si,[bp-6] ; TFTP pointer
mov bx,[bp-8] ; TID
; Make sure the packet actually came from the server
; This is technically not to the TFTP spec?
mov eax,[si+tftp_remoteip]
cmp [pxe_udp_read_pkt.sip],eax
jne .no_packet
; Got packet - reset timeout
mov word [bp-2],TimeoutTable
pop ax ; Adjust stack
pop ax
mov ax,[pxe_udp_read_pkt.rport]
mov [si+tftp_remoteport],ax
; filesize <- -1 == unknown
mov dword [si+tftp_filesize], -1
; Default blksize unless blksize option negotiated
mov word [si+tftp_blksize], TFTP_BLOCKSIZE
movzx ecx,word [pxe_udp_read_pkt.buffersize]
sub cx,2 ; CX <- bytes after opcode
jb .failure ; Garbled reply
mov si,packet_buf
lodsw
cmp ax, TFTP_ERROR
je .bailnow ; ERROR reply: don't try again
; If the server doesn't support any options, we'll get
; a DATA reply instead of OACK. Stash the data in
; the file buffer and go with the default value for
; all options...
cmp ax, TFTP_DATA
je .no_oack
cmp ax, TFTP_OACK
jne .err_reply ; Unknown packet type
; Now we need to parse the OACK packet to get the transfer
; and packet sizes.
; SI -> first byte of options; [E]CX -> byte count
.parse_oack:
jcxz .done_pkt ; No options acked
.get_opt_name:
; Some TFTP servers have junk NUL bytes at the end of the packet.
; If all that is left is NUL, then consider the packet processed.
mov di,si
push cx
xor ax,ax
repz scasb
pop cx
jz .done_pkt
mov di,si
mov bx,si
.opt_name_loop: lodsb
and al,al
jz .got_opt_name
or al,20h ; Convert to lowercase
stosb
loop .opt_name_loop
; We ran out, and no final null
jmp .done_pkt ; Ignore runt option
.got_opt_name: ; si -> option value
dec cx ; bytes left in pkt
jz .done_pkt ; Option w/o value, ignore
; Parse option pointed to by bx; guaranteed to be
; null-terminated.
push cx
push si
mov si,bx ; -> option name
mov bx,tftp_opt_table
mov cx,tftp_opts
.opt_loop:
push cx
push si
mov di,[bx] ; Option pointer
mov cx,[bx+2] ; Option len
repe cmpsb
pop si
pop cx
je .get_value ; OK, known option
add bx,6
loop .opt_loop
pop si
pop cx
; Non-negotiated option returned, no idea what it means...
jmp .err_reply
.get_value: pop si ; si -> option value
pop cx ; cx -> bytes left in pkt
mov bx,[bx+4] ; Pointer to data target
add bx,[bp-6] ; TFTP socket pointer
xor eax,eax
xor edx,edx
.value_loop: lodsb
and al,al
jz .got_value
sub al,'0'
cmp al, 9
ja .err_reply ; Not a decimal digit
imul edx,10
add edx,eax
mov [bx],edx
loop .value_loop
; Ran out before final null, accept anyway
jmp short .done_pkt
.got_value:
dec cx
jnz .get_opt_name ; Not end of packet
; ZF == 1
; Success, done!
.done_pkt:
pop si ; Junk
pop si ; We want the packet ptr in SI
mov eax,[si+tftp_filesize]
.got_file: ; SI->socket structure, EAX = size
and eax,eax ; Set ZF depending on file size
jz .error_si ; ZF = 1 need to free the socket
.ret:
leave ; SP <- BP, POP BP
pop cx
pop bx
pop es
ret
.no_oack: ; We got a DATA packet, meaning no options are
; suported. Save the data away and consider the length
; undefined, *unless* this is the only data packet...
mov bx,[bp-6] ; File pointer
sub cx,2 ; Too short?
jb .failure
lodsw ; Block number
cmp ax,htons(1)
jne .failure
mov [bx+tftp_lastpkt],ax
cmp cx,TFTP_BLOCKSIZE
ja .err_reply ; Corrupt...
je .not_eof
; This was the final EOF packet, already...
; We know the filesize, but we also want to ack the
; packet and set the EOF flag.
mov [bx+tftp_filesize],ecx
mov byte [bx+tftp_goteof],1
push si
mov si,bx
; AX = htons(1) already
call ack_packet
pop si
.not_eof:
mov [bx+tftp_bytesleft],cx
mov ax,pktbuf_seg
push es
mov es,ax
mov di,tftp_pktbuf
mov [bx+tftp_dataptr],di
add cx,3
shr cx,2
rep movsd
pop es
jmp .done_pkt
.err_reply: ; TFTP protocol error. Send ERROR reply.
; ServerIP and gateway are already programmed in
mov si,[bp-6]
mov ax,[si+tftp_remoteport]
mov word [pxe_udp_write_pkt.rport],ax
mov word [pxe_udp_write_pkt.buffer],tftp_proto_err
mov word [pxe_udp_write_pkt.buffersize],tftp_proto_err_len
mov di,pxe_udp_write_pkt
mov bx,PXENV_UDP_WRITE
call pxenv
; Write an error message and explode
mov si,err_damage
call writestr_early
jmp kaboom
.bailnow:
; Immediate error - no retry
mov word [bp-2],TimeoutTableEnd-1
.failure: pop bx ; Junk
pop bx
pop si
pop ax
inc ax
cmp ax,TimeoutTableEnd
jb .sendreq ; Try again
.error: mov si,bx ; Socket pointer
.error_si: ; Socket pointer already in SI
call free_socket ; ZF <- 1, SI <- 0
jmp .ret
%if GPXE
.gpxe:
push bx ; Socket pointer
mov di,gpxe_file_open
mov word [di],2 ; PXENV_STATUS_BAD_FUNC
mov word [di+4],packet_buf+2 ; Completed URL
mov [di+6],ds
mov bx,PXENV_FILE_OPEN
call pxenv
pop si ; Socket pointer in SI
jc .error_si
mov ax,[di+2]
mov word [si+tftp_localport],-1 ; gPXE URL
mov [si+tftp_remoteport],ax
mov di,gpxe_get_file_size
mov [di+2],ax
%if 0
; Disable this for now since gPXE doesn't always
; return valid information in PXENV_GET_FILE_SIZE
mov bx,PXENV_GET_FILE_SIZE
call pxenv
mov eax,[di+4] ; File size
jnc .oksize
%endif
or eax,-1 ; Size unknown
.oksize:
mov [si+tftp_filesize],eax
jmp .got_file
%endif ; GPXE
;
; allocate_socket: Allocate a local UDP port structure
;
; If successful:
; ZF set
; BX = socket pointer
; If unsuccessful:
; ZF clear
;
allocate_socket:
push cx
mov bx,Files
mov cx,MAX_OPEN
.check: cmp word [bx], byte 0
je .found
add bx,open_file_t_size
loop .check
xor cx,cx ; ZF = 1
pop cx
ret
; Allocate a socket number. Socket numbers are made
; guaranteed unique by including the socket slot number
; (inverted, because we use the loop counter cx); add a
; counter value to keep the numbers from being likely to
; get immediately reused.
;
; The NextSocket variable also contains the top two bits
; set. This generates a value in the range 49152 to
; 57343.
.found:
dec cx
push ax
mov ax,[NextSocket]
inc ax
and ax,((1 << (13-MAX_OPEN_LG2))-1) | 0xC000
mov [NextSocket],ax
shl cx,13-MAX_OPEN_LG2
add cx,ax ; ZF = 0
xchg ch,cl ; Convert to network byte order
mov [bx],cx ; Socket in use
pop ax
pop cx
ret
;
; Free socket: socket in SI; return SI = 0, ZF = 1 for convenience
;
free_socket:
push es
pusha
xor ax,ax
mov es,ax
mov di,si
mov cx,tftp_pktbuf >> 1 ; tftp_pktbuf is not cleared
rep stosw
popa
pop es
xor si,si
ret
;
; parse_dotquad:
; Read a dot-quad pathname in DS:SI and output an IP
; address in EAX, with SI pointing to the first
; nonmatching character.
;
; Return CF=1 on error.
;
; No segment assumptions permitted.
;
parse_dotquad:
push cx
mov cx,4
xor eax,eax
.parseloop:
mov ch,ah
mov ah,al
lodsb
sub al,'0'
jb .notnumeric
cmp al,9
ja .notnumeric
aad ; AL += 10 * AH; AH = 0;
xchg ah,ch
jmp .parseloop
.notnumeric:
cmp al,'.'-'0'
pushf
mov al,ah
mov ah,ch
xor ch,ch
ror eax,8
popf
jne .error
loop .parseloop
jmp .done
.error:
loop .realerror ; If CX := 1 then we're done
clc
jmp .done
.realerror:
stc
.done:
dec si ; CF unchanged!
pop cx
ret
;
; is_url: Return CF=0 if and only if the buffer pointed to by
; DS:SI is a URL (contains ://). No registers modified.
;
%if GPXE
is_url:
push si
push eax
.loop:
mov eax,[si]
inc si
and al,al
jz .not_url
and eax,0FFFFFFh
cmp eax,'://'
jne .loop
.done:
; CF=0 here
pop eax
pop si
ret
.not_url:
stc
jmp .done
;
; is_gpxe: Return CF=0 if and only if the buffer pointed to by
; DS:SI is a URL (contains ://) *and* the gPXE extensions
; API is available. No registers modified.
;
is_gpxe:
call is_url
jc .ret ; Not a URL, don't bother
.again:
cmp byte [HasGPXE],1
ja .unknown
; CF=1 if not available (0),
; CF=0 if known available (1).
.ret: ret
.unknown:
; If we get here, the gPXE status is unknown.
push es
pushad
push ds
pop es
mov di,gpxe_file_api_check
mov bx,PXENV_FILE_API_CHECK ; BH = 0
call pxenv
jc .nogood
cmp dword [di+4],0xe9c17b20
jne .nogood
mov ax,[di+12] ; Don't care about the upper half...
not ax ; Set bits of *missing* functions...
and ax,01001011b ; The functions we care about
setz bh
jz .done
.nogood:
mov si,gpxe_warning_msg
call writestr_early
.done:
mov [HasGPXE],bh
popad
pop es
jmp .again
section .data
gpxe_warning_msg:
db 'URL syntax, but gPXE extensions not detected, '
db 'trying plain TFTP...', CR, LF, 0
HasGPXE db -1 ; Unknown
section .text
%endif
;
; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed
; to by ES:DI; ends on encountering any whitespace.
; DI is preserved.
;
; This verifies that a filename is < FILENAME_MAX characters
; and doesn't contain whitespace, and zero-pads the output buffer,
; so "repe cmpsb" can do a compare.
;
; The first four bytes of the manged name is the IP address of
; the download host, 0 for no host, or -1 for a gPXE URL.
;
; No segment assumptions permitted.
;
mangle_name:
push di
%if GPXE
call is_url
jc .not_url
or eax,-1 ; It's a URL
jmp .prefix_done
.not_url:
%endif ; GPXE
push si
mov eax,[cs:ServerIP]
cmp byte [si],0
je .noip ; Null filename?!?!
cmp word [si],'::' ; Leading ::?
je .gotprefix
.more:
inc si
cmp byte [si],0
je .noip
cmp word [si],'::'
jne .more
; We have a :: prefix of some sort, it could be either
; a DNS name or a dot-quad IP address. Try the dot-quad
; first...
.here:
pop si
push si
call parse_dotquad
jc .notdq
cmp word [si],'::'
je .gotprefix
.notdq:
pop si
push si
call dns_resolv
cmp word [si],'::'
jne .noip
and eax,eax
jnz .gotprefix
.noip:
pop si
xor eax,eax
jmp .prefix_done
.gotprefix:
pop cx ; Adjust stack
inc si ; Skip double colon
inc si
.prefix_done:
stosd ; Save IP address prefix
mov cx,FILENAME_MAX-5
.mn_loop:
lodsb
cmp al,' ' ; If control or space, end
jna .mn_end
stosb
loop .mn_loop
.mn_end:
inc cx ; At least one null byte
xor ax,ax ; Zero-fill name
rep stosb ; Doesn't do anything if CX=0
pop di
ret ; Done
;
; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled
; filename to the conventional representation. This is needed
; for the BOOT_IMAGE= parameter for the kernel.
;
; NOTE: The output buffer needs to be able to hold an
; expanded IP address.
;
; DS:SI -> input mangled file name
; ES:DI -> output buffer
;
; On return, DI points to the first byte after the output name,
; which is set to a null byte.
;
unmangle_name:
push eax
lodsd
and eax,eax
jz .noip
cmp eax,-1
jz .noip ; URL
call gendotquad
mov ax,'::'
stosw
.noip:
call strcpy
dec di ; Point to final null byte
pop eax
ret
;
; 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.
;
pxenv:
pushfd
pushad
%if USE_PXE_PROVIDED_STACK == 0
mov [cs:PXEStack],sp
mov [cs:PXEStack+2],ss
lss sp,[cs:InitStack]
%endif
; 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
%if USE_PXE_PROVIDED_STACK == 0
lss sp,[cs:PXEStack]
%endif
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
PXEEntry equ pxenv.jump+1
section .bss
alignb 2
PXEStatus resb 2
section .text
;
; getfssec: Get multiple clusters from a file, given the starting cluster.
;
; In this case, get multiple blocks from a specific TCP connection.
;
; On entry:
; ES:BX -> Buffer
; SI -> TFTP socket pointer
; CX -> 512-byte block count; 0FFFFh = until end of file
; On exit:
; SI -> TFTP socket pointer (or 0 on EOF)
; CF = 1 -> Hit EOF
; ECX -> number of bytes actually read
;
getfssec:
push eax
push edi
push bx
push si
push fs
mov di,bx
mov ax,pktbuf_seg
mov fs,ax
xor eax,eax
movzx ecx,cx
shl ecx,TFTP_BLOCKSIZE_LG2 ; Convert to bytes
push ecx ; Initial request size
jz .hit_eof ; Nothing to do?
.need_more:
call fill_buffer
movzx eax,word [si+tftp_bytesleft]
and ax,ax
jz .hit_eof
push ecx
cmp ecx,eax
jna .ok_size
mov ecx,eax
.ok_size:
mov ax,cx ; EAX<31:16> == ECX<31:16> == 0
mov bx,[si+tftp_dataptr]
sub [si+tftp_bytesleft],cx
xchg si,bx
fs rep movsb ; Copy from packet buffer
xchg si,bx
mov [si+tftp_dataptr],bx
pop ecx
sub ecx,eax
jnz .need_more
.hit_eof:
call fill_buffer
pop eax ; Initial request amount
xchg eax,ecx
sub ecx,eax ; ... minus anything not gotten
pop fs
pop si
; Is there anything left of this?
mov eax,[si+tftp_filesize]
sub eax,[si+tftp_filepos]
jnz .bytes_left
cmp [si+tftp_bytesleft],ax ; AX == 0
jne .bytes_left
cmp byte [si+tftp_goteof],0
je .done
; I'm 99% sure this can't happen, but...
call fill_buffer ; Receive/ACK the EOF packet
.done:
; The socket is closed and the buffer drained
; Close socket structure and re-init for next user
call free_socket
stc
jmp .ret
.bytes_left:
clc
.ret:
pop bx
pop edi
pop eax
ret
;
; Get a fresh packet if the buffer is drained, and we haven't hit
; EOF yet. The buffer should be filled immediately after draining!
;
; expects fs -> pktbuf_seg and ds:si -> socket structure
;
fill_buffer:
cmp word [si+tftp_bytesleft],0
je .empty
ret ; Otherwise, nothing to do
.empty:
push es
pushad
mov ax,ds
mov es,ax
; Note: getting the EOF packet is not the same thing
; as tftp_filepos == tftp_filesize; if the EOF packet
; is empty the latter condition can be true without
; having gotten the official EOF.
cmp byte [si+tftp_goteof],0
jne .ret ; Already EOF
%if GPXE
cmp word [si+tftp_localport], -1
jne .get_packet_tftp
call get_packet_gpxe
jmp .ret
.get_packet_tftp:
%endif ; GPXE
; TFTP code...
.packet_loop:
; Start by ACKing the previous packet; this should cause the
; next packet to be sent.
mov bx,TimeoutTable
.send_ack: push bx ; <D> Retry pointer
movzx cx,byte [bx] ; Timeout
mov ax,[si+tftp_lastpkt]
call ack_packet ; Send ACK
; We used to test the error code here, but sometimes
; PXE would return negative status even though we really
; did send the ACK. Now, just treat a failed send as
; a normally lost packet, and let it time out in due
; course of events.
.send_ok: ; Now wait for packet.
mov dx,[BIOS_timer] ; Get current time
.wait_data: push cx ; <E> Timeout
push dx ; <F> Old time
mov bx,[si+tftp_pktbuf]
mov [pxe_udp_read_pkt.buffer],bx
mov [pxe_udp_read_pkt.buffer+2],fs
mov [pxe_udp_read_pkt.buffersize],word PKTBUF_SIZE
mov eax,[si+tftp_remoteip]
mov [pxe_udp_read_pkt.sip],eax
mov eax,[MyIP]
mov [pxe_udp_read_pkt.dip],eax
mov ax,[si+tftp_remoteport]
mov [pxe_udp_read_pkt.rport],ax
mov ax,[si+tftp_localport]
mov [pxe_udp_read_pkt.lport],ax
mov di,pxe_udp_read_pkt
mov bx,PXENV_UDP_READ
call pxenv
jnc .recv_ok
; No packet, or receive failure
mov dx,[BIOS_timer]
pop ax ; <F> Old time
pop cx ; <E> Timeout
cmp ax,dx ; Same time -> don't advance timeout
je .wait_data ; Same clock tick
loop .wait_data ; Decrease timeout
pop bx ; <D> Didn't get any, send another ACK
inc bx
cmp bx,TimeoutTableEnd
jb .send_ack
jmp kaboom ; Forget it...
.recv_ok: pop dx ; <F>
pop cx ; <E>
cmp word [pxe_udp_read_pkt.buffersize],byte 4
jb .wait_data ; Bad size for a DATA packet
mov bx,[si+tftp_pktbuf]
cmp word [fs:bx],TFTP_DATA ; Not a data packet?
jne .wait_data ; Then wait for something else
mov ax,[si+tftp_lastpkt]
xchg ah,al ; Host byte order
inc ax ; Which packet are we waiting for?
xchg ah,al ; Network byte order
cmp [fs:bx+2],ax
je .right_packet
; Wrong packet, ACK the packet and then try again
; This is presumably because the ACK got lost,
; so the server just resent the previous packet
mov ax,[fs:bx+2]
call ack_packet
jmp .send_ok ; Reset timeout
.right_packet: ; It's the packet we want. We're also EOF if the
; size < blocksize
pop cx ; <D> Don't need the retry count anymore
mov [si+tftp_lastpkt],ax ; Update last packet number
movzx ecx,word [pxe_udp_read_pkt.buffersize]
sub cx,byte 4 ; Skip TFTP header
; Set pointer to data block
lea ax,[bx+4] ; Data past TFTP header
mov [si+tftp_dataptr],ax
add [si+tftp_filepos],ecx
mov [si+tftp_bytesleft],cx
cmp cx,[si+tftp_blksize] ; Is it a full block?
jb .last_block ; If not, it's EOF
.ret:
popad
pop es
ret
.last_block: ; Last block - ACK packet immediately
mov ax,[fs:bx+2]
call ack_packet
; Make sure we know we are at end of file
mov eax,[si+tftp_filepos]
mov [si+tftp_filesize],eax
mov byte [si+tftp_goteof],1
jmp .ret
;
; TimeoutTable: list of timeouts (in 18.2 Hz timer ticks)
;
; This is roughly an exponential backoff...
;
section .data
TimeoutTable:
db 2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18
db 21, 26, 31, 37, 44, 53, 64, 77, 92, 110, 132
db 159, 191, 229, 255, 255, 255, 255
TimeoutTableEnd equ $
section .text
;
; ack_packet:
;
; Send ACK packet. This is a common operation and so is worth canning.
;
; Entry:
; SI = TFTP block
; AX = Packet # to ack (network byte order)
; Exit:
; All registers preserved
;
; This function uses the pxe_udp_write_pkt but not the packet_buf.
;
ack_packet:
pushad
mov [ack_packet_buf+2],ax ; Packet number to ack
mov ax,[si]
mov [pxe_udp_write_pkt.lport],ax
mov ax,[si+tftp_remoteport]
mov [pxe_udp_write_pkt.rport],ax
mov eax,[si+tftp_remoteip]
mov [pxe_udp_write_pkt.sip],eax
xor eax,[MyIP]
and eax,[Netmask]
jz .nogw
mov eax,[Gateway]
.nogw:
mov [pxe_udp_write_pkt.gip],eax
mov [pxe_udp_write_pkt.buffer],word ack_packet_buf
mov [pxe_udp_write_pkt.buffersize], word 4
mov di,pxe_udp_write_pkt
mov bx,PXENV_UDP_WRITE
call pxenv
popad
ret
%if GPXE
;
; Get a fresh packet from a gPXE socket; expects fs -> pktbuf_seg
; and ds:si -> socket structure
;
; Assumes CS == DS == ES.
;
get_packet_gpxe:
mov di,gpxe_file_read
mov ax,[si+tftp_remoteport] ; gPXE filehandle
mov [di+2],ax
mov ax,[si+tftp_pktbuf]
mov [di+6],ax
mov [si+tftp_dataptr],ax
mov [di+8],fs
.again:
mov word [di+4],PKTBUF_SIZE
mov bx,PXENV_FILE_READ
call pxenv
jnc .ok ; Got data or EOF
cmp word [di],PXENV_STATUS_TFTP_OPEN ; == EWOULDBLOCK
je .again
jmp kaboom ; Otherwise error...
.ok:
movzx eax,word [di+4] ; Bytes read
mov [si+tftp_bytesleft],ax ; Bytes in buffer
add [si+tftp_filepos],eax ; Position in file
and ax,ax ; EOF?
mov eax,[si+tftp_filepos]
jnz .got_stuff
; We got EOF here, make sure the upper layers know
mov [si+tftp_filesize],eax
.got_stuff:
; If we're done here, close the file
cmp [si+tftp_filesize],eax
ja .done ; Not EOF, there is still data...
; Reuse the previous [es:di] structure since the
; relevant fields are all the same
mov byte [si+tftp_goteof],1
mov bx,PXENV_FILE_CLOSE
call pxenv
; Ignore return...
.done:
ret
%endif ; GPXE
;
; unload_pxe:
;
; This function unloads the PXE and UNDI stacks and unclaims
; the memory.
;
unload_pxe:
cmp byte [KeepPXE],0 ; Should we keep PXE around?
jne reset_pxe
push ds
push es
mov ax,cs
mov ds,ax
mov es,ax
mov si,new_api_unload
cmp byte [APIVer+1],2 ; Major API version >= 2?
jae .new_api
mov si,old_api_unload
.new_api:
.call_loop: xor ax,ax
lodsb
and ax,ax
jz .call_done
xchg bx,ax
mov di,pxe_unload_stack_pkt
push di
xor ax,ax
mov cx,pxe_unload_stack_pkt_len >> 1
rep stosw
pop di
call pxenv
jc .cant_free
mov ax,word [pxe_unload_stack_pkt.status]
cmp ax,PXENV_STATUS_SUCCESS
jne .cant_free
jmp .call_loop
.call_done:
mov bx,0FF00h
mov dx,[RealBaseMem]
cmp dx,[BIOS_fbm] ; Sanity check
jna .cant_free
inc bx
; Check that PXE actually unhooked the INT 1Ah chain
movzx eax,word [4*0x1a]
movzx ecx,word [4*0x1a+2]
shl ecx,4
add eax,ecx
shr eax,10
cmp ax,dx ; Not in range
jae .ok
cmp ax,[BIOS_fbm]
jae .cant_free
; inc bx
.ok:
mov [BIOS_fbm],dx
.pop_ret:
pop es
pop ds
ret
.cant_free:
mov si,cant_free_msg
call writestr_early
push ax
xchg bx,ax
call writehex4
mov al,'-'
call writechr
pop ax
call writehex4
mov al,'-'
call writechr
mov eax,[4*0x1a]
call writehex8
call crlf
jmp .pop_ret
; We want to keep PXE around, but still we should reset
; it to the standard bootup configuration
reset_pxe:
push es
push cs
pop es
mov bx,PXENV_UDP_CLOSE
mov di,pxe_udp_close_pkt
call pxenv
pop es
ret
;
; gendotquad
;
; Take an IP address (in network byte order) in EAX and
; output a dotted quad string to ES:DI.
; DI points to terminal null at end of string on exit.
;
gendotquad:
push eax
push cx
mov cx,4
.genchar:
push eax
cmp al,10 ; < 10?
jb .lt10 ; If so, skip first 2 digits
cmp al,100 ; < 100
jb .lt100 ; If so, skip first digit
aam 100
; Now AH = 100-digit; AL = remainder
add ah,'0'
mov [es:di],ah
inc di
.lt100:
aam 10
; Now AH = 10-digit; AL = remainder
add ah,'0'
mov [es:di],ah
inc di
.lt10:
add al,'0'
stosb
mov al,'.'
stosb
pop eax
ror eax,8 ; Move next char into LSB
loop .genchar
dec di
mov [es:di], byte 0
pop cx
pop eax
ret
;
; uchexbytes/lchexbytes
;
; Take a number of bytes in memory and convert to upper/lower-case
; hexadecimal
;
; Input:
; DS:SI = input bytes
; ES:DI = output buffer
; CX = number of bytes
; Output:
; DS:SI = first byte after
; ES:DI = first byte after
; CX = 0
;
; Trashes AX, DX
;
lchexbytes:
mov dl,'a'-'9'-1
jmp xchexbytes
uchexbytes:
mov dl,'A'-'9'-1
xchexbytes:
.loop:
lodsb
mov ah,al
shr al,4
call .outchar
mov al,ah
call .outchar
loop .loop
ret
.outchar:
and al,0Fh
add al,'0'
cmp al,'9'
jna .done
add al,dl
.done:
stosb
ret
;
; pxe_get_cached_info
;
; Get a DHCP packet from the PXE stack into the trackbuf.
;
; Input:
; DL = packet type
; Output:
; CX = buffer size
;
; Assumes CS == DS == ES.
;
pxe_get_cached_info:
pushad
mov al,' '
call writechr
mov al,dl
call writehex2
mov di,pxe_bootp_query_pkt
push di
xor ax,ax
stosw ; Status
movzx ax,dl
stosw ; Packet type
mov ax,trackbufsize
stosw ; Buffer size
mov ax,trackbuf
stosw ; Buffer offset
xor ax,ax
stosw ; Buffer segment
pop di ; DI -> parameter set
mov bx,PXENV_GET_CACHED_INFO
call pxenv
jc .err
popad
mov cx,[pxe_bootp_query_pkt.buffersize]
ret
.err:
mov si,err_pxefailed
call writestr_early
call writehex4
call crlf
jmp kaboom
section .data
get_packet_msg db 'Getting cached packet', 0
section .text
;
; ip_ok
;
; Tests an IP address in EAX for validity; return with ZF=1 for bad.
; We used to refuse class E, but class E addresses are likely to become
; assignable unicast addresses in the near future.
;
ip_ok:
push ax
cmp eax,-1 ; Refuse the all-ones address
jz .out
and al,al ; Refuse network zero
jz .out
cmp al,127 ; Refuse loopback
jz .out
and al,0F0h
cmp al,224 ; Refuse class D
.out:
pop ax
ret
;
; parse_dhcp
;
; Parse a DHCP packet. This includes dealing with "overloaded"
; option fields (see RFC 2132, section 9.3)
;
; This should fill in the following global variables, if the
; information is present:
;
; MyIP - client IP address
; ServerIP - boot server IP address
; Netmask - network mask
; Gateway - default gateway router IP
; BootFile - boot file name
; DNSServers - DNS server IPs
; LocalDomain - Local domain name
; MACLen, MAC - Client identifier, if MACLen == 0
;
; This assumes the DHCP packet is in "trackbuf" and the length
; of the packet in in CX on entry.
;
parse_dhcp:
mov byte [OverLoad],0 ; Assume no overload
mov eax, [trackbuf+bootp.yip]
call ip_ok
jz .noyip
mov [MyIP], eax
.noyip:
mov eax, [trackbuf+bootp.sip]
and eax, eax
call ip_ok
jz .nosip
mov [ServerIP], eax
.nosip:
sub cx, bootp.options
jbe .nooptions
mov si, trackbuf+bootp.option_magic
lodsd
cmp eax, BOOTP_OPTION_MAGIC
jne .nooptions
call parse_dhcp_options
.nooptions:
mov si, trackbuf+bootp.bootfile
test byte [OverLoad],1
jz .nofileoverload
mov cx,128
call parse_dhcp_options
jmp short .parsed_file
.nofileoverload:
cmp byte [si], 0
jz .parsed_file ; No bootfile name
mov di,BootFile
mov cx,32
rep movsd
xor al,al
stosb ; Null-terminate
.parsed_file:
mov si, trackbuf+bootp.sname
test byte [OverLoad],2
jz .nosnameoverload
mov cx,64
call parse_dhcp_options
.nosnameoverload:
ret
;
; Parse a sequence of DHCP options, pointed to by DS:SI; the field
; size is CX -- some DHCP servers leave option fields unterminated
; in violation of the spec.
;
; For parse_some_dhcp_options, DH contains the minimum value for
; the option to recognize -- this is used to restrict parsing to
; PXELINUX-specific options only.
;
parse_dhcp_options:
xor dx,dx
parse_some_dhcp_options:
.loop:
and cx,cx
jz .done
lodsb
dec cx
jz .done ; Last byte; must be PAD, END or malformed
cmp al, 0 ; PAD option
je .loop
cmp al,255 ; END option
je .done
; Anything else will have a length field
mov dl,al ; DL <- option number
xor ax,ax
lodsb ; AX <- option length
dec cx
sub cx,ax ; Decrement bytes left counter
jb .done ; Malformed option: length > field size
cmp dl,dh ; Is the option value valid?
jb .opt_done
mov bx,dhcp_option_list
.find_option:
cmp bx,dhcp_option_list_end
jae .opt_done
cmp dl,[bx]
je .found_option
add bx,3
jmp .find_option
.found_option:
pushad
call [bx+1]
popad
; Fall through
; Unknown option. Skip to the next one.
.opt_done:
add si,ax
jmp .loop
.done:
ret
section .data
dhcp_option_list:
section .text
%macro dopt 2
section .data
db %1
dw dopt_%2
section .text
dopt_%2:
%endmacro
;
; Parse individual DHCP options. SI points to the option data and
; AX to the option length. DL contains the option number.
; All registers are saved around the routine.
;
dopt 1, subnet_mask
mov ebx,[si]
mov [Netmask],ebx
ret
dopt 3, router
mov ebx,[si]
mov [Gateway],ebx
ret
dopt 6, dns_servers
mov cx,ax
shr cx,2
cmp cl,DNS_MAX_SERVERS
jna .oklen
mov cl,DNS_MAX_SERVERS
.oklen:
mov di,DNSServers
rep movsd
mov [LastDNSServer],di
ret
dopt 15, local_domain
mov bx,si
add bx,ax
xor ax,ax
xchg [bx],al ; Zero-terminate option
mov di,LocalDomain
call dns_mangle ; Convert to DNS label set
mov [bx],al ; Restore ending byte
ret
dopt 43, vendor_encaps
mov dh,208 ; Only recognize PXELINUX options
mov cx,ax ; Length of option = max bytes to parse
call parse_some_dhcp_options ; Parse recursive structure
ret
dopt 52, option_overload
mov bl,[si]
mov [OverLoad],bl
ret
dopt 54, server
mov eax,[si]
cmp dword [ServerIP],0
jne .skip ; Already have a next server IP
call ip_ok
jz .skip
mov [ServerIP],eax
.skip: ret
dopt 61, client_identifier
cmp ax,MAC_MAX ; Too long?
ja .skip
cmp ax,2 ; Too short?
jb .skip
cmp [MACLen],ah ; Only do this if MACLen == 0
jne .skip
push ax
lodsb ; Client identifier type
cmp al,[MACType]
pop ax
jne .skip ; Client identifier is not a MAC
dec ax
mov [MACLen],al
mov di,MAC
jmp dhcp_copyoption
.skip: ret
dopt 67, bootfile_name
mov di,BootFile
jmp dhcp_copyoption
dopt 97, uuid_client_identifier
cmp ax,17 ; type byte + 16 bytes UUID
jne .skip
mov dl,[si] ; Must have type 0 == UUID
or dl,[HaveUUID] ; Capture only the first instance
jnz .skip
mov byte [HaveUUID],1 ; Got UUID
mov di,UUIDType
jmp dhcp_copyoption
.skip: ret
dopt 209, pxelinux_configfile
mov di,ConfigName
or byte [DHCPMagic],2 ; Got config file
jmp dhcp_copyoption
dopt 210, pxelinux_pathprefix
mov di,PathPrefix
or byte [DHCPMagic],4 ; Got path prefix
jmp dhcp_copyoption
dopt 211, pxelinux_reboottime
cmp al,4
jne .done
mov ebx,[si]
xchg bl,bh ; Convert to host byte order
rol ebx,16
xchg bl,bh
mov [RebootTime],ebx
or byte [DHCPMagic],8 ; Got RebootTime
.done: ret
; Common code for copying an option verbatim
; Copies the option into ES:DI and null-terminates it.
; Returns with AX=0 and SI past the option.
dhcp_copyoption:
xchg cx,ax ; CX <- option length
rep movsb
xchg cx,ax ; AX <- 0
stosb ; Null-terminate
ret
section .data
dhcp_option_list_end:
section .text
section .data
HaveUUID db 0
uuid_dashes db 4,2,2,2,6,0 ; Bytes per UUID dashed section
section .text
;
; genipopt
;
; Generate an ip=<client-ip>:<boot-server-ip>:<gw-ip>:<netmask>
; option into IPOption based on a DHCP packet in trackbuf.
; Assumes CS == DS == ES.
;
genipopt:
pushad
mov di,IPOption
mov eax,'ip='
stosd
dec di
mov eax,[MyIP]
call gendotquad
mov al,':'
stosb
mov eax,[ServerIP]
call gendotquad
mov al,':'
stosb
mov eax,[Gateway]
call gendotquad
mov al,':'
stosb
mov eax,[Netmask]
call gendotquad ; Zero-terminates its output
popad
ret
; -----------------------------------------------------------------------------
; Common modules
; -----------------------------------------------------------------------------
%include "getc.inc" ; getc et al
%include "conio.inc" ; Console I/O
%include "writestr.inc" ; String output
writestr_early equ writestr
%include "writehex.inc" ; Hexadecimal output
%include "configinit.inc" ; Initialize configuration
%include "parseconfig.inc" ; High-level config file handling
%include "parsecmd.inc" ; Low-level config file handling
%include "bcopy32.inc" ; 32-bit bcopy
%include "loadhigh.inc" ; Load a file into high memory
%include "font.inc" ; VGA font stuff
%include "graphics.inc" ; VGA graphics
%include "highmem.inc" ; High memory sizing
%include "strcpy.inc" ; strcpy()
%include "rawcon.inc" ; Console I/O w/o using the console functions
%include "dnsresolv.inc" ; DNS resolver
%include "idle.inc" ; Idle handling
%include "pxeidle.inc" ; PXE-specific idle mechanism
%include "adv.inc" ; Auxillary Data Vector
; -----------------------------------------------------------------------------
; Begin data section
; -----------------------------------------------------------------------------
section .data
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
err_nopxe db "No !PXE or PXENV+ API found; we're dead...", CR, LF, 0
err_pxefailed db 'PXE API call failed, error ', 0
err_udpinit db 'Failed to initialize UDP stack', CR, LF, 0
err_noconfig db 'Unable to locate configuration file', CR, LF, 0
err_damage db 'TFTP server sent an incomprehesible reply', CR, LF, 0
found_pxenv db 'Found PXENV+ structure', CR, LF, 0
apiver_str db 'PXE API version is ',0
pxeentry_msg db '!PXE entry point found (we hope) at ', 0
pxenventry_msg db 'PXENV+ entry point found (we hope) at ', 0
viaplan_msg db ' via plan '
plan db 'A', CR, LF, 0
trymempxe_msg db 'Scanning memory for !PXE structure... ', 0
trymempxenv_msg db 'Scanning memory for PXENV+ structure... ', 0
undi_data_msg db 'UNDI data segment at ',0
undi_code_msg db 'UNDI code segment at ',0
len_msg db ' len ', 0
cant_free_msg db 'Failed to free base memory, error ', 0
notfound_msg db 'not found', CR, LF, 0
myipaddr_msg db 'My IP address seems to be ',0
tftpprefix_msg db 'TFTP prefix: ', 0
localboot_msg db 'Booting from local disk...', CR, LF, 0
trying_msg db 'Trying to load: ', 0
default_str db 'default', 0
syslinux_banner db CR, LF, 'PXELINUX ', VERSION_STR, ' ', DATE_STR, ' ', 0
cfgprefix db 'pxelinux.cfg/' ; No final null!
cfgprefix_len equ ($-cfgprefix)
; This one we make ourselves
bootif_str db 'BOOTIF='
bootif_str_len equ $-bootif_str
;
; Config file keyword table
;
%include "keywords.inc"
;
; Extensions to search for (in *forward* order).
; (.bs and .bss 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
;
; PXE unload sequences
;
new_api_unload:
db PXENV_UDP_CLOSE
db PXENV_UNDI_SHUTDOWN
db PXENV_UNLOAD_STACK
db PXENV_STOP_UNDI
db 0
old_api_unload:
db PXENV_UDP_CLOSE
db PXENV_UNDI_SHUTDOWN
db PXENV_UNLOAD_STACK
db PXENV_UNDI_CLEANUP
db 0
;
; PXE query packets partially filled in
;
section .bss
pxe_bootp_query_pkt:
.status: resw 1 ; Status
.packettype: resw 1 ; Boot server packet type
.buffersize: resw 1 ; Packet size
.buffer: resw 2 ; seg:off of buffer
.bufferlimit: resw 1 ; Unused
section .data
pxe_udp_open_pkt:
.status: dw 0 ; Status
.sip: dd 0 ; Source (our) IP
pxe_udp_close_pkt:
.status: dw 0 ; Status
pxe_udp_write_pkt:
.status: dw 0 ; Status
.sip: dd 0 ; Server IP
.gip: dd 0 ; Gateway IP
.lport: dw 0 ; Local port
.rport: dw 0 ; Remote port
.buffersize: dw 0 ; Size of packet
.buffer: dw 0, 0 ; seg:off of buffer
pxe_udp_read_pkt:
.status: dw 0 ; Status
.sip: dd 0 ; Source IP
.dip: dd 0 ; Destination (our) IP
.rport: dw 0 ; Remote port
.lport: dw 0 ; Local port
.buffersize: dw 0 ; Max packet size
.buffer: dw 0, 0 ; seg:off of buffer
%if GPXE
gpxe_file_api_check:
.status: dw 0 ; Status
.size: dw 20 ; Size in bytes
.magic: dd 0x91d447b2 ; Magic number
.provider: dd 0
.apimask: dd 0
.flags: dd 0
gpxe_file_open:
.status: dw 0 ; Status
.filehandle: dw 0 ; FileHandle
.filename: dd 0 ; seg:off of FileName
.reserved: dd 0
gpxe_get_file_size:
.status: dw 0 ; Status
.filehandle: dw 0 ; FileHandle
.filesize: dd 0 ; FileSize
gpxe_file_read:
.status: dw 0 ; Status
.filehandle: dw 0 ; FileHandle
.buffersize: dw 0 ; BufferSize
.buffer: dd 0 ; seg:off of buffer
%endif ; GPXE
;
; Misc initialized (data) variables
;
alignz 4
BaseStack dd StackBuf ; ESP of base stack
dw 0 ; SS of base stack
NextSocket dw 49152 ; Counter for allocating socket numbers
KeepPXE db 0 ; Should PXE be kept around?
;
; TFTP commands
;
tftp_tail db 'octet', 0 ; Octet mode
tsize_str db 'tsize' ,0 ; Request size
tsize_len equ ($-tsize_str)
db '0', 0
blksize_str db 'blksize', 0 ; Request large blocks
blksize_len equ ($-blksize_str)
asciidec TFTP_LARGEBLK
db 0
tftp_tail_len equ ($-tftp_tail)
alignz 2
;
; Options negotiation parsing table (string pointer, string len, offset
; into socket structure)
;
tftp_opt_table:
dw tsize_str, tsize_len, tftp_filesize
dw blksize_str, blksize_len, tftp_blksize
tftp_opts equ ($-tftp_opt_table)/6
;
; Error packet to return on TFTP protocol error
;
tftp_proto_err dw TFTP_ERROR ; ERROR packet
dw TFTP_EUNDEF ; ERROR 0: undefined
db 'TFTP protocol error', 0 ; Error message
tftp_proto_err_len equ ($-tftp_proto_err)
alignz 4
ack_packet_buf: dw TFTP_ACK, 0 ; TFTP ACK packet
;
; IP information (initialized to "unknown" values)
MyIP dd 0 ; My IP address
ServerIP dd 0 ; IP address of boot server
Netmask dd 0 ; Netmask of this subnet
Gateway dd 0 ; Default router
ServerPort dw TFTP_PORT ; TFTP server port
;
; Variables that are uninitialized in SYSLINUX but initialized here
;
alignz 4
BufSafe dw trackbufsize/TFTP_BLOCKSIZE ; Clusters we can load into trackbuf
BufSafeBytes dw trackbufsize ; = how many bytes?
%ifndef DEPEND
%if ( trackbufsize % TFTP_BLOCKSIZE ) != 0
%error trackbufsize must be a multiple of TFTP_BLOCKSIZE
%endif
%endif