Merge commit 'origin/nolen' into gpxe-support
[syslinux.git] / pxelinux.asm
blob9154dfa39199590eccfd765d502e982cabedb245
1 ; -*- fundamental -*- (asm-mode sucks)
2 ; ****************************************************************************
4 ; pxelinux.asm
6 ; A program to boot Linux kernels off a TFTP server using the Intel PXE
7 ; network booting API. It is based on the SYSLINUX boot loader for
8 ; MS-DOS floppies.
10 ; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved
12 ; This program is free software; you can redistribute it and/or modify
13 ; it under the terms of the GNU General Public License as published by
14 ; the Free Software Foundation, Inc., 53 Temple Place Ste 330,
15 ; Boston MA 02111-1307, USA; either version 2 of the License, or
16 ; (at your option) any later version; incorporated herein by reference.
18 ; ****************************************************************************
20 %define IS_PXELINUX 1
21 %include "head.inc"
22 %include "pxe.inc"
24 ; gPXE extensions support
25 %define GPXE 1
28 ; Some semi-configurable constants... change on your own risk.
30 my_id equ pxelinux_id
31 FILENAME_MAX_LG2 equ 7 ; log2(Max filename size Including final null)
32 FILENAME_MAX equ (1 << FILENAME_MAX_LG2)
33 NULLFILE equ 0 ; Zero byte == null file name
34 NULLOFFSET equ 4 ; Position in which to look
35 REBOOT_TIME equ 5*60 ; If failure, time until full reset
36 %assign HIGHMEM_SLOP 128*1024 ; Avoid this much memory near the top
37 MAX_OPEN_LG2 equ 5 ; log2(Max number of open sockets)
38 MAX_OPEN equ (1 << MAX_OPEN_LG2)
39 PKTBUF_SIZE equ (65536/MAX_OPEN) ; Per-socket packet buffer size
40 TFTP_PORT equ htons(69) ; Default TFTP port
41 PKT_RETRY equ 6 ; Packet transmit retry count
42 PKT_TIMEOUT equ 12 ; Initial timeout, timer ticks @ 55 ms
43 ; Desired TFTP block size
44 ; For Ethernet MTU is normally 1500. Unfortunately there seems to
45 ; be a fair number of networks with "substandard" MTUs which break.
46 ; The code assumes TFTP_LARGEBLK <= 2K.
47 TFTP_MTU equ 1440
48 TFTP_LARGEBLK equ (TFTP_MTU-20-8-4) ; MTU - IP hdr - UDP hdr - TFTP hdr
49 ; Standard TFTP block size
50 TFTP_BLOCKSIZE_LG2 equ 9 ; log2(bytes/block)
51 TFTP_BLOCKSIZE equ (1 << TFTP_BLOCKSIZE_LG2)
52 %assign USE_PXE_PROVIDED_STACK 1 ; Use stack provided by PXE?
54 SECTOR_SHIFT equ TFTP_BLOCKSIZE_LG2
55 SECTOR_SIZE equ TFTP_BLOCKSIZE
58 ; This is what we need to do when idle
59 ; *** This is disabled because some PXE stacks wait for unacceptably
60 ; *** long if there are no packets receivable.
62 %define HAVE_IDLE 0 ; idle is not a noop
64 %if HAVE_IDLE
65 %macro RESET_IDLE 0
66 call reset_idle
67 %endmacro
68 %macro DO_IDLE 0
69 call check_for_arp
70 %endmacro
71 %else
72 %macro RESET_IDLE 0
73 ; Nothing
74 %endmacro
75 %macro DO_IDLE 0
76 ; Nothing
77 %endmacro
78 %endif
81 ; TFTP operation codes
83 TFTP_RRQ equ htons(1) ; Read request
84 TFTP_WRQ equ htons(2) ; Write request
85 TFTP_DATA equ htons(3) ; Data packet
86 TFTP_ACK equ htons(4) ; ACK packet
87 TFTP_ERROR equ htons(5) ; ERROR packet
88 TFTP_OACK equ htons(6) ; OACK packet
91 ; TFTP error codes
93 TFTP_EUNDEF equ htons(0) ; Unspecified error
94 TFTP_ENOTFOUND equ htons(1) ; File not found
95 TFTP_EACCESS equ htons(2) ; Access violation
96 TFTP_ENOSPACE equ htons(3) ; Disk full
97 TFTP_EBADOP equ htons(4) ; Invalid TFTP operation
98 TFTP_EBADID equ htons(5) ; Unknown transfer
99 TFTP_EEXISTS equ htons(6) ; File exists
100 TFTP_ENOUSER equ htons(7) ; No such user
101 TFTP_EOPTNEG equ htons(8) ; Option negotiation failure
104 ; The following structure is used for "virtual kernels"; i.e. LILO-style
105 ; option labels. The options we permit here are `kernel' and `append
106 ; Since there is no room in the bottom 64K for all of these, we
107 ; stick them in high memory and copy them down before we need them.
109 struc vkernel
110 vk_vname: resb FILENAME_MAX ; Virtual name **MUST BE FIRST!**
111 vk_rname: resb FILENAME_MAX ; Real name
112 vk_ipappend: resb 1 ; "IPAPPEND" flag
113 vk_type: resb 1 ; Type of file
114 vk_appendlen: resw 1
115 alignb 4
116 vk_append: resb max_cmd_len+1 ; Command line
117 alignb 4
118 vk_end: equ $ ; Should be <= vk_size
119 endstruc
122 ; Segment assignments in the bottom 640K
123 ; 0000h - main code/data segment (and BIOS segment)
125 real_mode_seg equ 3000h
126 pktbuf_seg equ 2000h ; Packet buffers segments
127 xfer_buf_seg equ 1000h ; Bounce buffer for I/O to high mem
128 comboot_seg equ real_mode_seg ; COMBOOT image loading zone
131 ; BOOTP/DHCP packet pattern
133 struc bootp_t
134 bootp:
135 .opcode resb 1 ; BOOTP/DHCP "opcode"
136 .hardware resb 1 ; ARP hardware type
137 .hardlen resb 1 ; Hardware address length
138 .gatehops resb 1 ; Used by forwarders
139 .ident resd 1 ; Transaction ID
140 .seconds resw 1 ; Seconds elapsed
141 .flags resw 1 ; Broadcast flags
142 .cip resd 1 ; Client IP
143 .yip resd 1 ; "Your" IP
144 .sip resd 1 ; Next server IP
145 .gip resd 1 ; Relay agent IP
146 .macaddr resb 16 ; Client MAC address
147 .sname resb 64 ; Server name (optional)
148 .bootfile resb 128 ; Boot file name
149 .option_magic resd 1 ; Vendor option magic cookie
150 .options resb 1260 ; Vendor options
151 endstruc
153 BOOTP_OPTION_MAGIC equ htonl(0x63825363) ; See RFC 2132
156 ; TFTP connection data structure. Each one of these corresponds to a local
157 ; UDP port. The size of this structure must be a power of 2.
158 ; HBO = host byte order; NBO = network byte order
159 ; (*) = written by options negotiation code, must be dword sized
161 ; For a gPXE connection, we set the local port number to -1 and the
162 ; remote port number contains the gPXE file handle.
164 struc open_file_t
165 tftp_localport resw 1 ; Local port number (0 = not in use)
166 tftp_remoteport resw 1 ; Remote port number
167 tftp_remoteip resd 1 ; Remote IP address
168 tftp_filepos resd 1 ; Bytes downloaded (including buffer)
169 tftp_filesize resd 1 ; Total file size(*)
170 tftp_blksize resd 1 ; Block size for this connection(*)
171 tftp_bytesleft resw 1 ; Unclaimed data bytes
172 tftp_lastpkt resw 1 ; Sequence number of last packet (NBO)
173 tftp_dataptr resw 1 ; Pointer to available data
174 tftp_goteof resb 1 ; 1 if the EOF packet received
175 resb 3 ; Currently unusued
176 ; At end since it should not be zeroed on socked close
177 tftp_pktbuf resw 1 ; Packet buffer offset
178 endstruc
179 %ifndef DEPEND
180 %if (open_file_t_size & (open_file_t_size-1))
181 %error "open_file_t is not a power of 2"
182 %endif
183 %endif
185 ; ---------------------------------------------------------------------------
186 ; BEGIN CODE
187 ; ---------------------------------------------------------------------------
190 ; Memory below this point is reserved for the BIOS and the MBR
192 section .earlybss
193 trackbufsize equ 8192
194 trackbuf resb trackbufsize ; Track buffer goes here
195 ; ends at 2800h
197 alignb open_file_t_size
198 Files resb MAX_OPEN*open_file_t_size
200 alignb FILENAME_MAX
201 BootFile resb 256 ; Boot file from DHCP packet
202 PathPrefix resb 256 ; Path prefix derived from boot file
203 DotQuadBuf resb 16 ; Buffer for dotted-quad IP address
204 IPOption resb 80 ; ip= option buffer
205 InitStack resd 1 ; Pointer to reset stack (SS:SP)
206 PXEStack resd 1 ; Saved stack during PXE call
208 section .bss
209 alignb 4
210 RebootTime resd 1 ; Reboot timeout, if set by option
211 StrucPtr resd 1 ; Pointer to PXENV+ or !PXE structure
212 APIVer resw 1 ; PXE API version found
213 IPOptionLen resw 1 ; Length of IPOption
214 IdleTimer resw 1 ; Time to check for ARP?
215 LocalBootType resw 1 ; Local boot return code
216 PktTimeout resw 1 ; Timeout for current packet
217 RealBaseMem resw 1 ; Amount of DOS memory after freeing
218 OverLoad resb 1 ; Set if DHCP packet uses "overloading"
219 DHCPMagic resb 1 ; PXELINUX magic flags
221 ; The relative position of these fields matter!
222 MAC_MAX equ 32 ; Handle hardware addresses this long
223 MACLen resb 1 ; MAC address len
224 MACType resb 1 ; MAC address type
225 MAC resb MAC_MAX+1 ; Actual MAC address
226 BOOTIFStr resb 7 ; Space for "BOOTIF="
227 MACStr resb 3*(MAC_MAX+1) ; MAC address as a string
229 ; The relative position of these fields matter!
230 UUIDType resb 1 ; Type byte from DHCP option
231 UUID resb 16 ; UUID, from the PXE stack
232 UUIDNull resb 1 ; dhcp_copyoption zero-terminates
235 ; PXE packets which don't need static initialization
237 alignb 4
238 pxe_unload_stack_pkt:
239 .status: resw 1 ; Status
240 .reserved: resw 10 ; Reserved
241 pxe_unload_stack_pkt_len equ $-pxe_unload_stack_pkt
243 alignb 16
244 ; BOOTP/DHCP packet buffer
246 section .bss2
247 alignb 16
248 packet_buf resb 2048 ; Transfer packet
249 packet_buf_size equ $-packet_buf
251 section .text
253 ; PXELINUX needs more BSS than the other derivatives;
254 ; therefore we relocate it from 7C00h on startup.
256 StackBuf equ $ ; Base of stack if we use our own
259 ; Primary entry point.
261 bootsec equ $
262 _start:
263 pushfd ; Paranoia... in case of return to PXE
264 pushad ; ... save as much state as possible
265 push ds
266 push es
267 push fs
268 push gs
270 xor ax,ax
271 mov ds,ax
272 mov es,ax
274 %ifndef DEPEND
275 %if TEXT_START != 0x7c00
276 ; This is uglier than it should be, but works around
277 ; some NASM 0.98.38 bugs.
278 mov di,section..bcopy32.start
279 add di,__bcopy_size-4
280 lea si,[di-(TEXT_START-7C00h)]
281 lea cx,[di-(TEXT_START-4)]
282 shr cx,2
283 std ; Overlapping areas, copy backwards
284 rep movsd
285 %endif
286 %endif
287 jmp 0:_start1 ; Canonicalize address
288 _start1:
289 mov bp,sp
290 les bx,[bp+48] ; ES:BX -> !PXE or PXENV+ structure
292 ; That is all pushed onto the PXE stack. Save the pointer
293 ; to it and switch to an internal stack.
294 mov [InitStack],sp
295 mov [InitStack+2],ss
297 %if USE_PXE_PROVIDED_STACK
298 ; Apparently some platforms go bonkers if we
299 ; set up our own stack...
300 mov [BaseStack],sp
301 mov [BaseStack+4],ss
302 %endif
304 cli ; Paranoia
305 lss esp,[BaseStack]
307 sti ; Stack set up and ready
308 cld ; Copy upwards
311 ; Initialize screen (if we're using one)
313 push es ; Save ES -> PXE entry structure
314 push ds
315 pop es ; ES <- DS
316 %include "init.inc"
317 pop es ; Restore ES -> PXE entry structure
319 ; Tell the user we got this far
321 mov si,syslinux_banner
322 call writestr
324 mov si,copyright_str
325 call writestr
328 ; Assume API version 2.1, in case we find the !PXE structure without
329 ; finding the PXENV+ structure. This should really look at the Base
330 ; Code ROM ID structure in have_pxe, but this is adequate for now --
331 ; if we have !PXE, we have to be 2.1 or higher, and we don't care
332 ; about higher versions than that.
334 mov word [APIVer],0201h
337 ; Now we need to find the !PXE structure. It's *supposed* to be pointed
338 ; to by SS:[SP+4], but support INT 1Ah, AX=5650h method as well.
339 ; FIX: ES:BX should point to the PXENV+ structure on entry as well.
340 ; We should make that the second test, and not trash ES:BX...
342 cmp dword [es:bx], '!PXE'
343 je have_pxe
345 ; Uh-oh, not there... try plan B
346 mov ax, 5650h
347 %if USE_PXE_PROVIDED_STACK == 0
348 lss sp,[InitStack]
349 %endif
350 int 1Ah ; May trash regs
351 %if USE_PXE_PROVIDED_STACK == 0
352 lss esp,[BaseStack]
353 %endif
355 jc no_pxe
356 cmp ax,564Eh
357 jne no_pxe
359 ; Okay, that gave us the PXENV+ structure, find !PXE
360 ; structure from that (if available)
361 cmp dword [es:bx], 'PXEN'
362 jne no_pxe
363 cmp word [es:bx+4], 'V+'
364 je have_pxenv
366 ; Nothing there either. Last-ditch: scan memory
367 call memory_scan_for_pxe_struct ; !PXE scan
368 jnc have_pxe
369 call memory_scan_for_pxenv_struct ; PXENV+ scan
370 jnc have_pxenv
372 no_pxe: mov si,err_nopxe
373 call writestr
374 jmp kaboom
376 have_pxenv:
377 mov [StrucPtr],bx
378 mov [StrucPtr+2],es
380 mov si,found_pxenv
381 call writestr
383 mov si,apiver_str
384 call writestr
385 mov ax,[es:bx+6]
386 mov [APIVer],ax
387 call writehex4
388 call crlf
390 cmp ax,0201h ; API version 2.1 or higher
391 jb old_api
392 mov si,bx
393 mov ax,es
394 les bx,[es:bx+28h] ; !PXE structure pointer
395 cmp dword [es:bx],'!PXE'
396 je have_pxe
398 ; Nope, !PXE structure missing despite API 2.1+, or at least
399 ; the pointer is missing. Do a last-ditch attempt to find it.
400 call memory_scan_for_pxe_struct
401 jnc have_pxe
403 ; Otherwise, no dice, use PXENV+ structure
404 mov bx,si
405 mov es,ax
407 old_api: ; Need to use a PXENV+ structure
408 mov si,using_pxenv_msg
409 call writestr
411 mov eax,[es:bx+0Ah] ; PXE RM API
412 mov [PXENVEntry],eax
414 mov si,undi_data_msg
415 call writestr
416 mov ax,[es:bx+20h]
417 call writehex4
418 call crlf
419 mov si,undi_data_len_msg
420 call writestr
421 mov ax,[es:bx+22h]
422 call writehex4
423 call crlf
424 mov si,undi_code_msg
425 call writestr
426 mov ax,[es:bx+24h]
427 call writehex4
428 call crlf
429 mov si,undi_code_len_msg
430 call writestr
431 mov ax,[es:bx+26h]
432 call writehex4
433 call crlf
435 ; Compute base memory size from PXENV+ structure
436 xor esi,esi
437 movzx eax,word [es:bx+20h] ; UNDI data seg
438 cmp ax,[es:bx+24h] ; UNDI code seg
439 ja .use_data
440 mov ax,[es:bx+24h]
441 mov si,[es:bx+26h]
442 jmp short .combine
443 .use_data:
444 mov si,[es:bx+22h]
445 .combine:
446 shl eax,4
447 add eax,esi
448 shr eax,10 ; Convert to kilobytes
449 mov [RealBaseMem],ax
451 mov si,pxenventry_msg
452 call writestr
453 mov ax,[PXENVEntry+2]
454 call writehex4
455 mov al,':'
456 call writechr
457 mov ax,[PXENVEntry]
458 call writehex4
459 call crlf
460 jmp have_entrypoint
462 have_pxe:
463 mov [StrucPtr],bx
464 mov [StrucPtr+2],es
466 mov eax,[es:bx+10h]
467 mov [PXEEntry],eax
469 mov si,undi_data_msg
470 call writestr
471 mov eax,[es:bx+2Ah]
472 call writehex8
473 call crlf
474 mov si,undi_data_len_msg
475 call writestr
476 mov ax,[es:bx+2Eh]
477 call writehex4
478 call crlf
479 mov si,undi_code_msg
480 call writestr
481 mov ax,[es:bx+32h]
482 call writehex8
483 call crlf
484 mov si,undi_code_len_msg
485 call writestr
486 mov ax,[es:bx+36h]
487 call writehex4
488 call crlf
490 ; Compute base memory size from !PXE structure
491 xor esi,esi
492 mov eax,[es:bx+2Ah]
493 cmp eax,[es:bx+32h]
494 ja .use_data
495 mov eax,[es:bx+32h]
496 mov si,[es:bx+36h]
497 jmp short .combine
498 .use_data:
499 mov si,[es:bx+2Eh]
500 .combine:
501 add eax,esi
502 shr eax,10
503 mov [RealBaseMem],ax
505 mov si,pxeentry_msg
506 call writestr
507 mov ax,[PXEEntry+2]
508 call writehex4
509 mov al,':'
510 call writechr
511 mov ax,[PXEEntry]
512 call writehex4
513 call crlf
515 have_entrypoint:
516 push cs
517 pop es ; Restore CS == DS == ES
520 ; Network-specific initialization
522 xor ax,ax
523 mov [LocalDomain],al ; No LocalDomain received
526 ; The DHCP client identifiers are best gotten from the DHCPREQUEST
527 ; packet (query info 1).
529 query_bootp_1:
530 mov dl,1
531 call pxe_get_cached_info
532 call parse_dhcp
534 ; We don't use flags from the request packet, so
535 ; this is a good time to initialize DHCPMagic...
536 ; Initialize it to 1 meaning we will accept options found;
537 ; in earlier versions of PXELINUX bit 0 was used to indicate
538 ; we have found option 208 with the appropriate magic number;
539 ; we no longer require that, but MAY want to re-introduce
540 ; it in the future for vendor encapsulated options.
541 mov byte [DHCPMagic],1
544 ; Now attempt to get the BOOTP/DHCP packet that brought us life (and an IP
545 ; address). This lives in the DHCPACK packet (query info 2).
547 query_bootp_2:
548 mov dl,2
549 call pxe_get_cached_info
550 call parse_dhcp ; Parse DHCP packet
552 ; Save away MAC address (assume this is in query info 2. If this
553 ; turns out to be problematic it might be better getting it from
554 ; the query info 1 packet.)
556 .save_mac:
557 movzx cx,byte [trackbuf+bootp.hardlen]
558 cmp cx,16
559 jna .mac_ok
560 xor cx,cx ; Bad hardware address length
561 .mac_ok:
562 mov [MACLen],cl
563 mov al,[trackbuf+bootp.hardware]
564 mov [MACType],al
565 mov si,trackbuf+bootp.macaddr
566 mov di,MAC
567 rep movsb
569 ; Enable this if we really need to zero-pad this field...
570 ; mov cx,MAC+MAC_MAX+1
571 ; sub cx,di
572 ; xor ax,ax
573 ; rep stosb
576 ; Now, get the boot file and other info. This lives in the CACHED_REPLY
577 ; packet (query info 3).
579 mov dl,3
580 call pxe_get_cached_info
581 call parse_dhcp ; Parse DHCP packet
584 ; Generate the bootif string, and the hardware-based config string.
586 make_bootif_string:
587 mov si,bootif_str
588 mov di,BOOTIFStr
589 mov cx,bootif_str_len
590 rep movsb
592 movzx cx,byte [MACLen]
593 mov si,MACType
594 inc cx
595 .hexify_mac:
596 push cx
597 mov cl,1 ; CH == 0 already
598 call lchexbytes
599 mov al,'-'
600 stosb
601 pop cx
602 loop .hexify_mac
603 mov [di-1],cl ; Null-terminate and strip final dash
605 ; Generate ip= option
607 call genipopt
610 ; Print IP address
612 mov eax,[MyIP]
613 mov di,DotQuadBuf
614 push di
615 call gendotquad ; This takes network byte order input
617 xchg ah,al ; Convert to host byte order
618 ror eax,16 ; (BSWAP doesn't work on 386)
619 xchg ah,al
621 mov si,myipaddr_msg
622 call writestr
623 call writehex8
624 mov al,' '
625 call writechr
626 pop si ; DotQuadBuf
627 call writestr
628 call crlf
630 mov si,IPOption
631 call writestr
632 call crlf
635 ; Check to see if we got any PXELINUX-specific DHCP options; in particular,
636 ; if we didn't get the magic enable, do not recognize any other options.
638 check_dhcp_magic:
639 test byte [DHCPMagic], 1 ; If we didn't get the magic enable...
640 jnz .got_magic
641 mov byte [DHCPMagic], 0 ; If not, kill all other options
642 .got_magic:
646 ; Initialize UDP stack
648 udp_init:
649 mov eax,[MyIP]
650 mov [pxe_udp_open_pkt.sip],eax
651 mov di,pxe_udp_open_pkt
652 mov bx,PXENV_UDP_OPEN
653 call pxenv
654 jc .failed
655 cmp word [pxe_udp_open_pkt.status], byte 0
656 je .success
657 .failed: mov si,err_udpinit
658 call writestr
659 jmp kaboom
660 .success:
663 ; Common initialization code
665 %include "cpuinit.inc"
668 ; Now we're all set to start with our *real* business. First load the
669 ; configuration file (if any) and parse it.
671 ; In previous versions I avoided using 32-bit registers because of a
672 ; rumour some BIOSes clobbered the upper half of 32-bit registers at
673 ; random. I figure, though, that if there are any of those still left
674 ; they probably won't be trying to install Linux on them...
676 ; The code is still ripe with 16-bitisms, though. Not worth the hassle
677 ; to take'm out. In fact, we may want to put them back if we're going
678 ; to boot ELKS at some point.
682 ; Store standard filename prefix
684 prefix: test byte [DHCPMagic], 04h ; Did we get a path prefix option
685 jnz .got_prefix
686 mov si,BootFile
687 mov di,PathPrefix
689 call strcpy
690 mov cx,di
691 sub cx,PathPrefix+1
693 lea si,[di-2] ; Skip final null!
694 .find_alnum: lodsb
695 or al,20h
696 cmp al,'.' ; Count . or - as alphanum
697 je .alnum
698 cmp al,'-'
699 je .alnum
700 cmp al,'0'
701 jb .notalnum
702 cmp al,'9'
703 jbe .alnum
704 cmp al,'a'
705 jb .notalnum
706 cmp al,'z'
707 ja .notalnum
708 .alnum: loop .find_alnum
709 dec si
710 .notalnum: mov byte [si+2],0 ; Zero-terminate after delimiter
712 .got_prefix:
713 mov si,tftpprefix_msg
714 call writestr
715 mov si,PathPrefix
716 call writestr
717 call crlf
720 ; Load configuration file
722 find_config:
725 ; Begin looking for configuration file
727 config_scan:
728 test byte [DHCPMagic], 02h
729 jz .no_option
731 ; We got a DHCP option, try it first
732 call .try
733 jnz .success
735 .no_option:
736 mov di,ConfigName
737 mov si,cfgprefix
738 mov cx,cfgprefix_len
739 rep movsb
741 ; Have to guess config file name...
743 ; Try loading by UUID.
744 cmp byte [HaveUUID],0
745 je .no_uuid
747 push di
748 mov bx,uuid_dashes
749 mov si,UUID
750 .gen_uuid:
751 movzx cx,byte [bx]
752 jcxz .done_uuid
753 inc bx
754 call lchexbytes
755 mov al,'-'
756 stosb
757 jmp .gen_uuid
758 .done_uuid:
759 mov [di-1],cl ; Remove last dash and zero-terminate
760 pop di
761 call .try
762 jnz .success
763 .no_uuid:
765 ; Try loading by MAC address
766 push di
767 mov si,MACStr
768 call strcpy
769 pop di
770 call .try
771 jnz .success
773 ; Nope, try hexadecimal IP prefixes...
774 .scan_ip:
775 mov cx,4
776 mov si,MyIP
777 call uchexbytes ; Convert to hex string
779 mov cx,8 ; Up to 8 attempts
780 .tryagain:
781 mov byte [di],0 ; Zero-terminate string
782 call .try
783 jnz .success
784 dec di ; Drop one character
785 loop .tryagain
787 ; Final attempt: "default" string
788 mov si,default_str ; "default" string
789 call strcpy
790 call .try
791 jnz .success
793 mov si,err_noconfig
794 call writestr
795 jmp kaboom
797 .try:
798 pusha
799 mov si,trying_msg
800 call writestr
801 mov di,ConfigName
802 mov si,di
803 call writestr
804 call crlf
805 mov si,di
806 mov di,KernelName ; Borrow this buffer for mangled name
807 call mangle_name
808 call open
809 popa
813 .success:
816 ; Linux kernel loading code is common. However, we need to define
817 ; a couple of helper macros...
820 ; Handle "ipappend" option
821 %define HAVE_SPECIAL_APPEND
822 %macro SPECIAL_APPEND 0
823 test byte [IPAppend],01h ; ip=
824 jz .noipappend1
825 mov si,IPOption
826 mov cx,[IPOptionLen]
827 rep movsb
828 mov al,' '
829 stosb
830 .noipappend1:
831 test byte [IPAppend],02h
832 jz .noipappend2
833 mov si,BOOTIFStr
834 call strcpy
835 mov byte [es:di-1],' ' ; Replace null with space
836 .noipappend2:
837 %endmacro
839 ; Unload PXE stack
840 %define HAVE_UNLOAD_PREP
841 %macro UNLOAD_PREP 0
842 call unload_pxe
843 %endmacro
846 ; Now we have the config file open. Parse the config file and
847 ; run the user interface.
849 %include "ui.inc"
852 ; Boot to the local disk by returning the appropriate PXE magic.
853 ; AX contains the appropriate return code.
855 local_boot:
856 push cs
857 pop ds
858 mov [LocalBootType],ax
859 call vgaclearmode
860 mov si,localboot_msg
861 call writestr
862 ; Restore the environment we were called with
863 lss sp,[InitStack]
864 pop gs
865 pop fs
866 pop es
867 pop ds
868 popad
869 mov ax,[cs:LocalBootType]
870 popfd
871 retf ; Return to PXE
874 ; kaboom: write a message and bail out. Wait for quite a while,
875 ; or a user keypress, then do a hard reboot.
877 kaboom:
878 RESET_STACK_AND_SEGS AX
879 .patch: mov si,bailmsg
880 call writestr ; Returns with AL = 0
881 .drain: call pollchar
882 jz .drained
883 call getchar
884 jmp short .drain
885 .drained:
886 mov edi,[RebootTime]
887 mov al,[DHCPMagic]
888 and al,09h ; Magic+Timeout
889 cmp al,09h
890 je .time_set
891 mov edi,REBOOT_TIME
892 .time_set:
893 mov cx,18
894 .wait1: push cx
895 mov ecx,edi
896 .wait2: mov dx,[BIOS_timer]
897 .wait3: call pollchar
898 jnz .keypress
899 cmp dx,[BIOS_timer]
900 je .wait3
901 loop .wait2,ecx
902 mov al,'.'
903 call writechr
904 pop cx
905 loop .wait1
906 .keypress:
907 call crlf
908 mov word [BIOS_magic],0 ; Cold reboot
909 jmp 0F000h:0FFF0h ; Reset vector address
912 ; memory_scan_for_pxe_struct:
914 ; If none of the standard methods find the !PXE structure, look for it
915 ; by scanning memory.
917 ; On exit, if found:
918 ; CF = 0, ES:BX -> !PXE structure
919 ; Otherwise CF = 1, all registers saved
921 memory_scan_for_pxe_struct:
922 push ds
923 pusha
924 mov ax,cs
925 mov ds,ax
926 mov si,trymempxe_msg
927 call writestr
928 mov ax,[BIOS_fbm] ; Starting segment
929 shl ax,(10-4) ; Kilobytes -> paragraphs
930 ; mov ax,01000h ; Start to look here
931 dec ax ; To skip inc ax
932 .mismatch:
933 inc ax
934 cmp ax,0A000h ; End of memory
935 jae .not_found
936 call writehex4
937 mov si,fourbs_msg
938 call writestr
939 mov es,ax
940 mov edx,[es:0]
941 cmp edx,'!PXE'
942 jne .mismatch
943 movzx cx,byte [es:4] ; Length of structure
944 cmp cl,08h ; Minimum length
945 jb .mismatch
946 push ax
947 xor ax,ax
948 xor si,si
949 .checksum: es lodsb
950 add ah,al
951 loop .checksum
952 pop ax
953 jnz .mismatch ; Checksum must == 0
954 .found: mov bp,sp
955 xor bx,bx
956 mov [bp+8],bx ; Save BX into stack frame (will be == 0)
957 mov ax,es
958 call writehex4
959 call crlf
960 popa
961 pop ds
964 .not_found: mov si,notfound_msg
965 call writestr
966 popa
967 pop ds
972 ; memory_scan_for_pxenv_struct:
974 ; If none of the standard methods find the PXENV+ structure, look for it
975 ; by scanning memory.
977 ; On exit, if found:
978 ; CF = 0, ES:BX -> PXENV+ structure
979 ; Otherwise CF = 1, all registers saved
981 memory_scan_for_pxenv_struct:
982 pusha
983 mov si,trymempxenv_msg
984 call writestr
985 ; mov ax,[BIOS_fbm] ; Starting segment
986 ; shl ax,(10-4) ; Kilobytes -> paragraphs
987 mov ax,01000h ; Start to look here
988 dec ax ; To skip inc ax
989 .mismatch:
990 inc ax
991 cmp ax,0A000h ; End of memory
992 jae .not_found
993 mov es,ax
994 mov edx,[es:0]
995 cmp edx,'PXEN'
996 jne .mismatch
997 mov dx,[es:4]
998 cmp dx,'V+'
999 jne .mismatch
1000 movzx cx,byte [es:8] ; Length of structure
1001 cmp cl,26h ; Minimum length
1002 jb .mismatch
1003 xor ax,ax
1004 xor si,si
1005 .checksum: es lodsb
1006 add ah,al
1007 loop .checksum
1008 and ah,ah
1009 jnz .mismatch ; Checksum must == 0
1010 .found: mov bp,sp
1011 mov [bp+8],bx ; Save BX into stack frame
1012 mov ax,bx
1013 call writehex4
1014 call crlf
1017 .not_found: mov si,notfound_msg
1018 call writestr
1019 popad
1024 ; close_file:
1025 ; Deallocates a file structure (pointer in SI)
1026 ; Assumes CS == DS.
1028 ; XXX: We should check to see if this file is still open on the server
1029 ; side and send a courtesy ERROR packet to the server.
1031 close_file:
1032 and si,si
1033 jz .closed
1034 mov word [si],0 ; Not in use
1035 .closed: ret
1038 ; searchdir:
1040 ; Open a TFTP connection to the server
1042 ; On entry:
1043 ; DS:DI = mangled filename
1044 ; If successful:
1045 ; ZF clear
1046 ; SI = socket pointer
1047 ; EAX = file length in bytes, or -1 if unknown
1048 ; If unsuccessful
1049 ; ZF set
1052 searchdir:
1053 push es
1054 push bx
1055 push cx
1056 mov ax,ds
1057 mov es,ax
1058 mov si,di
1059 push bp
1060 mov bp,sp
1062 call allocate_socket
1063 jz .ret
1065 mov ax,PKT_RETRY ; Retry counter
1066 mov word [PktTimeout],PKT_TIMEOUT ; Initial timeout
1068 .sendreq: push ax ; [bp-2] - Retry counter
1069 push si ; [bp-4] - File name
1071 mov di,packet_buf
1072 mov [pxe_udp_write_pkt.buffer],di
1074 mov ax,TFTP_RRQ ; TFTP opcode
1075 stosw
1077 lodsd ; EAX <- server override (if any)
1078 and eax,eax
1079 jnz .noprefix ; No prefix, and we have the server
1081 push si ; Add common prefix
1082 mov si,PathPrefix
1083 call strcpy
1084 dec di
1085 pop si
1087 mov eax,[ServerIP] ; Get default server
1089 .noprefix:
1090 call strcpy ; Filename
1091 %if GPXE
1092 mov si,packet_buf+2
1093 call is_url
1094 jnc .gpxe
1095 %endif
1097 mov [bx+tftp_remoteip],eax
1099 push bx ; [bp-6] - TFTP block
1100 mov bx,[bx]
1101 push bx ; [bp-8] - TID (local port no)
1103 mov [pxe_udp_write_pkt.status],byte 0
1104 mov [pxe_udp_write_pkt.sip],eax
1105 ; Now figure out the gateway
1106 xor eax,[MyIP]
1107 and eax,[Netmask]
1108 jz .nogwneeded
1109 mov eax,[Gateway]
1110 .nogwneeded:
1111 mov [pxe_udp_write_pkt.gip],eax
1112 mov [pxe_udp_write_pkt.lport],bx
1113 mov ax,[ServerPort]
1114 mov [pxe_udp_write_pkt.rport],ax
1115 mov si,tftp_tail
1116 mov cx,tftp_tail_len
1117 rep movsb
1118 sub di,packet_buf ; Get packet size
1119 mov [pxe_udp_write_pkt.buffersize],di
1121 mov di,pxe_udp_write_pkt
1122 mov bx,PXENV_UDP_WRITE
1123 call pxenv
1124 jc .failure
1125 cmp word [pxe_udp_write_pkt.status],byte 0
1126 jne .failure
1129 ; Danger, Will Robinson! We need to support timeout
1130 ; and retry lest we just lost a packet...
1133 ; Packet transmitted OK, now we need to receive
1134 .getpacket: push word [PktTimeout] ; [bp-10]
1135 push word [BIOS_timer] ; [bp-12]
1137 .pkt_loop: mov bx,[bp-8] ; TID
1138 mov di,packet_buf
1139 mov word [pxe_udp_read_pkt.status],0
1140 mov [pxe_udp_read_pkt.buffer],di
1141 mov [pxe_udp_read_pkt.buffer+2],ds
1142 mov word [pxe_udp_read_pkt.buffersize],packet_buf_size
1143 mov eax,[MyIP]
1144 mov [pxe_udp_read_pkt.dip],eax
1145 mov [pxe_udp_read_pkt.lport],bx
1146 mov di,pxe_udp_read_pkt
1147 mov bx,PXENV_UDP_READ
1148 call pxenv
1149 and ax,ax
1150 jz .got_packet ; Wait for packet
1151 .no_packet:
1152 mov dx,[BIOS_timer]
1153 cmp dx,[bp-12]
1154 je .pkt_loop
1155 mov [bp-12],dx
1156 dec word [bp-10] ; Timeout
1157 jnz .pkt_loop
1158 pop ax ; Adjust stack
1159 pop ax
1160 shl word [PktTimeout],1 ; Exponential backoff
1161 jmp .failure
1163 .got_packet:
1164 mov si,[bp-6] ; TFTP pointer
1165 mov bx,[bp-8] ; TID
1167 ; Make sure the packet actually came from the server
1168 ; This is technically not to the TFTP spec?
1169 mov eax,[si+tftp_remoteip]
1170 cmp [pxe_udp_read_pkt.sip],eax
1171 jne .no_packet
1173 ; Got packet - reset timeout
1174 mov word [PktTimeout],PKT_TIMEOUT
1176 pop ax ; Adjust stack
1177 pop ax
1179 mov ax,[pxe_udp_read_pkt.rport]
1180 mov [si+tftp_remoteport],ax
1182 ; filesize <- -1 == unknown
1183 mov dword [si+tftp_filesize], -1
1184 ; Default blksize unless blksize option negotiated
1185 mov word [si+tftp_blksize], TFTP_BLOCKSIZE
1187 movzx ecx,word [pxe_udp_read_pkt.buffersize]
1188 sub cx,2 ; CX <- bytes after opcode
1189 jb .failure ; Garbled reply
1191 mov si,packet_buf
1192 lodsw
1194 cmp ax, TFTP_ERROR
1195 je .bailnow ; ERROR reply: don't try again
1197 ; If the server doesn't support any options, we'll get
1198 ; a DATA reply instead of OACK. Stash the data in
1199 ; the file buffer and go with the default value for
1200 ; all options...
1201 cmp ax, TFTP_DATA
1202 je .no_oack
1204 cmp ax, TFTP_OACK
1205 jne .err_reply ; Unknown packet type
1207 ; Now we need to parse the OACK packet to get the transfer
1208 ; and packet sizes.
1209 ; SI -> first byte of options; [E]CX -> byte count
1210 .parse_oack:
1211 jcxz .done_pkt ; No options acked
1212 .get_opt_name:
1213 mov di,si
1214 mov bx,si
1215 .opt_name_loop: lodsb
1216 and al,al
1217 jz .got_opt_name
1218 or al,20h ; Convert to lowercase
1219 stosb
1220 loop .opt_name_loop
1221 ; We ran out, and no final null
1222 jmp .err_reply
1223 .got_opt_name: ; si -> option value
1224 dec cx ; bytes left in pkt
1225 jz .err_reply ; Option w/o value
1227 ; Parse option pointed to by bx; guaranteed to be
1228 ; null-terminated.
1229 push cx
1230 push si
1231 mov si,bx ; -> option name
1232 mov bx,tftp_opt_table
1233 mov cx,tftp_opts
1234 .opt_loop:
1235 push cx
1236 push si
1237 mov di,[bx] ; Option pointer
1238 mov cx,[bx+2] ; Option len
1239 repe cmpsb
1240 pop si
1241 pop cx
1242 je .get_value ; OK, known option
1243 add bx,6
1244 loop .opt_loop
1246 pop si
1247 pop cx
1248 jmp .err_reply ; Non-negotiated option returned
1250 .get_value: pop si ; si -> option value
1251 pop cx ; cx -> bytes left in pkt
1252 mov bx,[bx+4] ; Pointer to data target
1253 add bx,[bp-6] ; TFTP socket pointer
1254 xor eax,eax
1255 xor edx,edx
1256 .value_loop: lodsb
1257 and al,al
1258 jz .got_value
1259 sub al,'0'
1260 cmp al, 9
1261 ja .err_reply ; Not a decimal digit
1262 imul edx,10
1263 add edx,eax
1264 mov [bx],edx
1265 loop .value_loop
1266 ; Ran out before final null, accept anyway
1267 jmp short .done_pkt
1269 .got_value:
1270 dec cx
1271 jnz .get_opt_name ; Not end of packet
1273 ; ZF == 1
1275 ; Success, done!
1276 .done_pkt:
1277 pop si ; Junk
1278 pop si ; We want the packet ptr in SI
1280 mov eax,[si+tftp_filesize]
1281 .got_file: ; SI->socket structure, EAX = size
1282 and eax,eax ; Set ZF depending on file size
1283 jz .error_si ; ZF = 1 need to free the socket
1284 .ret:
1285 pop bp
1286 pop cx
1287 pop bx
1288 pop es
1292 .no_oack: ; We got a DATA packet, meaning no options are
1293 ; suported. Save the data away and consider the length
1294 ; undefined, *unless* this is the only data packet...
1295 mov bx,[bp-6] ; File pointer
1296 sub cx,2 ; Too short?
1297 jb .failure
1298 lodsw ; Block number
1299 cmp ax,htons(1)
1300 jne .failure
1301 mov [bx+tftp_lastpkt],ax
1302 cmp cx,TFTP_BLOCKSIZE
1303 ja .err_reply ; Corrupt...
1304 je .not_eof
1305 ; This was the final EOF packet, already...
1306 ; We know the filesize, but we also want to ack the
1307 ; packet and set the EOF flag.
1308 mov [bx+tftp_filesize],ecx
1309 mov byte [bx+tftp_goteof],1
1310 push si
1311 mov si,bx
1312 ; AX = htons(1) already
1313 call ack_packet
1314 pop si
1315 .not_eof:
1316 mov [bx+tftp_bytesleft],cx
1317 mov ax,pktbuf_seg
1318 push es
1319 mov es,ax
1320 mov di,tftp_pktbuf
1321 mov [bx+tftp_dataptr],di
1322 add cx,3
1323 shr cx,2
1324 rep movsd
1325 pop es
1326 jmp .done_pkt
1328 .err_reply: ; Option negotiation error. Send ERROR reply.
1329 ; ServerIP and gateway are already programmed in
1330 mov si,[bp-6]
1331 mov ax,[si+tftp_remoteport]
1332 mov word [pxe_udp_write_pkt.rport],ax
1333 mov word [pxe_udp_write_pkt.buffer],tftp_opt_err
1334 mov word [pxe_udp_write_pkt.buffersize],tftp_opt_err_len
1335 mov di,pxe_udp_write_pkt
1336 mov bx,PXENV_UDP_WRITE
1337 call pxenv
1339 ; Write an error message and explode
1340 mov si,err_damage
1341 call writestr
1342 jmp kaboom
1344 .bailnow: mov word [bp-2],1 ; Immediate error - no retry
1346 .failure: pop bx ; Junk
1347 pop bx
1348 pop si
1349 pop ax
1350 dec ax ; Retry counter
1351 jnz .sendreq ; Try again
1353 .error: mov si,bx ; Socket pointer
1354 .error_si: ; Socket pointer already in SI
1355 call free_socket ; ZF <- 1, SI <- 0
1356 jmp .ret
1359 %if GPXE
1360 .gpxe:
1361 pop si
1362 pop si
1364 push bx
1365 mov si,packet_buf+2 ; Completed URL
1366 mov di,gpxe_file_open
1367 mov [di+4],si
1368 mov [di+6],ds
1369 mov bx,PXENV_FILE_OPEN
1370 call pxenv
1371 pop si ; Packet pointer in SI
1372 jc .error_si
1374 mov ax,[di+2]
1375 mov word [si+tftp_localport],-1 ; gPXE URL
1376 mov [si+tftp_remoteport],ax
1377 mov di,gpxe_get_file_size
1378 mov [di+2],ax
1380 mov bx,PXENV_GET_FILE_SIZE
1381 call pxenv
1382 jc .error
1384 mov eax,[di+4]
1385 mov [si+tftp_filesize],eax
1386 jmp .got_file
1387 %endif ; GPXE
1390 ; allocate_socket: Allocate a local UDP port structure
1392 ; If successful:
1393 ; ZF set
1394 ; BX = socket pointer
1395 ; If unsuccessful:
1396 ; ZF clear
1398 allocate_socket:
1399 push cx
1400 mov bx,Files
1401 mov cx,MAX_OPEN
1402 .check: cmp word [bx], byte 0
1403 je .found
1404 add bx,open_file_t_size
1405 loop .check
1406 xor cx,cx ; ZF = 1
1407 pop cx
1409 ; Allocate a socket number. Socket numbers are made
1410 ; guaranteed unique by including the socket slot number
1411 ; (inverted, because we use the loop counter cx); add a
1412 ; counter value to keep the numbers from being likely to
1413 ; get immediately reused.
1415 ; The NextSocket variable also contains the top two bits
1416 ; set. This generates a value in the range 49152 to
1417 ; 57343.
1418 .found:
1419 dec cx
1420 push ax
1421 mov ax,[NextSocket]
1422 inc ax
1423 and ax,((1 << (13-MAX_OPEN_LG2))-1) | 0xC000
1424 mov [NextSocket],ax
1425 shl cx,13-MAX_OPEN_LG2
1426 add cx,ax ; ZF = 0
1427 xchg ch,cl ; Convert to network byte order
1428 mov [bx],cx ; Socket in use
1429 pop ax
1430 pop cx
1434 ; Free socket: socket in SI; return SI = 0, ZF = 1 for convenience
1436 free_socket:
1437 push es
1438 pusha
1439 xor ax,ax
1440 mov es,ax
1441 mov di,si
1442 mov cx,tftp_pktbuf >> 1 ; tftp_pktbuf is not cleared
1443 rep stosw
1444 popa
1445 pop es
1446 xor si,si
1450 ; parse_dotquad:
1451 ; Read a dot-quad pathname in DS:SI and output an IP
1452 ; address in EAX, with SI pointing to the first
1453 ; nonmatching character.
1455 ; Return CF=1 on error.
1457 ; No segment assumptions permitted.
1459 parse_dotquad:
1460 push cx
1461 mov cx,4
1462 xor eax,eax
1463 .parseloop:
1464 mov ch,ah
1465 mov ah,al
1466 lodsb
1467 sub al,'0'
1468 jb .notnumeric
1469 cmp al,9
1470 ja .notnumeric
1471 aad ; AL += 10 * AH; AH = 0;
1472 xchg ah,ch
1473 jmp .parseloop
1474 .notnumeric:
1475 cmp al,'.'-'0'
1476 pushf
1477 mov al,ah
1478 mov ah,ch
1479 xor ch,ch
1480 ror eax,8
1481 popf
1482 jne .error
1483 loop .parseloop
1484 jmp .done
1485 .error:
1486 loop .realerror ; If CX := 1 then we're done
1488 jmp .done
1489 .realerror:
1491 .done:
1492 dec si ; CF unchanged!
1493 pop cx
1497 ; is_url: Return CF=0 if and only if the buffer pointed to by
1498 ; DS:SI is a URL (contains ://). No registers modified.
1500 %if GPXE
1501 is_url:
1502 push si
1503 push eax
1504 .loop:
1505 mov eax,[si]
1506 inc si
1507 and al,al
1508 jz .not_url
1509 and eax,0FFFFFFh
1510 cmp eax,'://'
1511 jne .loop
1512 .done:
1513 ; CF=0 here
1514 pop eax
1515 pop si
1517 .not_url:
1519 jmp .done
1520 %endif
1523 ; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed
1524 ; to by ES:DI; ends on encountering any whitespace.
1525 ; DI is preserved.
1527 ; This verifies that a filename is < FILENAME_MAX characters
1528 ; and doesn't contain whitespace, and zero-pads the output buffer,
1529 ; so "repe cmpsb" can do a compare.
1531 ; The first four bytes of the manged name is the IP address of
1532 ; the download host, 0 for no host, or -1 for a gPXE URL.
1534 ; No segment assumptions permitted.
1536 mangle_name:
1537 push di
1538 %if GPXE
1539 call is_url
1540 jc .not_url
1541 or eax,-1 ; It's a URL
1542 jmp .prefix_done
1543 .not_url:
1544 %endif ; GPXE
1545 push si
1546 mov eax,[cs:ServerIP]
1547 cmp byte [si],0
1548 je .noip ; Null filename?!?!
1549 cmp word [si],'::' ; Leading ::?
1550 je .gotprefix
1552 .more:
1553 inc si
1554 cmp byte [si],0
1555 je .noip
1556 cmp word [si],'::'
1557 jne .more
1559 ; We have a :: prefix of some sort, it could be either
1560 ; a DNS name or a dot-quad IP address. Try the dot-quad
1561 ; first...
1562 .here:
1563 pop si
1564 push si
1565 call parse_dotquad
1566 jc .notdq
1567 cmp word [si],'::'
1568 je .gotprefix
1569 .notdq:
1570 pop si
1571 push si
1572 call dns_resolv
1573 cmp word [si],'::'
1574 jne .noip
1575 and eax,eax
1576 jnz .gotprefix
1578 .noip:
1579 pop si
1580 xor eax,eax
1581 jmp .prefix_done
1583 .gotprefix:
1584 pop cx ; Adjust stack
1585 inc si ; Skip double colon
1586 inc si
1588 .prefix_done:
1589 stosd ; Save IP address prefix
1590 mov cx,FILENAME_MAX-5
1592 .mn_loop:
1593 lodsb
1594 cmp al,' ' ; If control or space, end
1595 jna .mn_end
1596 stosb
1597 loop .mn_loop
1598 .mn_end:
1599 inc cx ; At least one null byte
1600 xor ax,ax ; Zero-fill name
1601 rep stosb ; Doesn't do anything if CX=0
1602 pop di
1603 ret ; Done
1606 ; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled
1607 ; filename to the conventional representation. This is needed
1608 ; for the BOOT_IMAGE= parameter for the kernel.
1610 ; NOTE: The output buffer needs to be able to hold an
1611 ; expanded IP address.
1613 ; DS:SI -> input mangled file name
1614 ; ES:DI -> output buffer
1616 ; On return, DI points to the first byte after the output name,
1617 ; which is set to a null byte.
1619 unmangle_name:
1620 push eax
1621 lodsd
1622 and eax,eax
1623 jz .noip
1624 cmp eax,-1
1625 jz .noip ; URL
1626 call gendotquad
1627 mov ax,'::'
1628 stosw
1629 .noip:
1630 call strcpy
1631 dec di ; Point to final null byte
1632 pop eax
1636 ; pxenv
1638 ; This is the main PXENV+/!PXE entry point, using the PXENV+
1639 ; calling convention. This is a separate local routine so
1640 ; we can hook special things from it if necessary. In particular,
1641 ; some PXE stacks seem to not like being invoked from anything but
1642 ; the initial stack, so humour it.
1645 pxenv:
1646 %if USE_PXE_PROVIDED_STACK == 0
1647 mov [cs:PXEStack],sp
1648 mov [cs:PXEStack+2],ss
1649 lss sp,[cs:InitStack]
1650 %endif
1651 .jump: call 0:pxe_thunk ; Default to calling the thunk
1652 %if USE_PXE_PROVIDED_STACK == 0
1653 lss sp,[cs:PXEStack]
1654 %endif
1655 cld ; Make sure DF <- 0
1658 ; Must be after function def due to NASM bug
1659 PXENVEntry equ pxenv.jump+1
1662 ; pxe_thunk
1664 ; Convert from the PXENV+ calling convention (BX, ES, DI) to the !PXE
1665 ; calling convention (using the stack.)
1667 ; This is called as a far routine so that we can just stick it into
1668 ; the PXENVEntry variable.
1670 pxe_thunk: push es
1671 push di
1672 push bx
1673 .jump: call 0:0
1674 add sp,byte 6
1675 cmp ax,byte 1
1676 cmc ; Set CF unless ax == 0
1677 retf
1679 ; Must be after function def due to NASM bug
1680 PXEEntry equ pxe_thunk.jump+1
1683 ; getfssec: Get multiple clusters from a file, given the starting cluster.
1685 ; In this case, get multiple blocks from a specific TCP connection.
1687 ; On entry:
1688 ; ES:BX -> Buffer
1689 ; SI -> TFTP socket pointer
1690 ; CX -> 512-byte block count; 0FFFFh = until end of file
1691 ; On exit:
1692 ; SI -> TFTP socket pointer (or 0 on EOF)
1693 ; CF = 1 -> Hit EOF
1694 ; ECX -> number of bytes actually read
1696 getfssec: push eax
1697 push edi
1698 push bx
1699 push si
1700 push fs
1701 mov di,bx
1702 mov ax,pktbuf_seg
1703 mov fs,ax
1705 xor eax,eax
1706 movzx ecx,cx
1707 shl ecx,TFTP_BLOCKSIZE_LG2 ; Convert to bytes
1708 push ecx ; Initial request size
1709 jz .hit_eof ; Nothing to do?
1711 .need_more:
1712 call fill_buffer
1713 movzx eax,word [si+tftp_bytesleft]
1714 and ax,ax
1715 jz .hit_eof
1717 push ecx
1718 cmp ecx,eax
1719 jna .ok_size
1720 mov ecx,eax
1721 .ok_size:
1722 mov ax,cx ; EAX<31:16> == ECX<31:16> == 0
1723 mov bx,[si+tftp_dataptr]
1724 sub [si+tftp_bytesleft],cx
1725 xchg si,bx
1726 fs rep movsb ; Copy from packet buffer
1727 xchg si,bx
1728 mov [si+tftp_dataptr],bx
1730 pop ecx
1731 sub ecx,eax
1732 jnz .need_more
1734 .hit_eof:
1735 call fill_buffer
1737 pop eax ; Initial request amount
1738 xchg eax,ecx
1739 sub ecx,eax ; ... minus anything not gotten
1741 pop fs
1742 pop si
1744 ; Is there anything left of this?
1745 mov eax,[si+tftp_filesize]
1746 sub eax,[si+tftp_filepos]
1747 jnz .bytes_left
1749 cmp [si+tftp_bytesleft],ax ; AX == 0
1750 jne .bytes_left
1752 cmp byte [si+tftp_goteof],0
1753 je .done
1754 ; I'm 99% sure this can't happen, but...
1755 call fill_buffer ; Receive/ACK the EOF packet
1756 .done:
1757 ; The socket is closed and the buffer drained
1758 ; Close socket structure and re-init for next user
1759 call free_socket
1761 jmp .ret
1762 .bytes_left:
1764 .ret:
1765 pop bx
1766 pop edi
1767 pop eax
1771 ; Get a fresh packet if the buffer is drained, and we haven't hit
1772 ; EOF yet. The buffer should be filled immediately after draining!
1774 ; expects fs -> pktbuf_seg and ds:si -> socket structure
1776 fill_buffer:
1777 cmp word [si+tftp_bytesleft],0
1778 je .empty
1779 ret ; Otherwise, nothing to do
1781 .empty:
1782 push es
1783 pushad
1784 mov ax,ds
1785 mov es,ax
1787 ; Note: getting the EOF packet is not the same thing
1788 ; as tftp_filepos == tftp_filesize; if the EOF packet
1789 ; is empty the latter condition can be true without
1790 ; having gotten the official EOF.
1791 cmp byte [si+tftp_goteof],0
1792 jne .gotten ; Alread EOF
1794 %if GPXE
1795 cmp word [si+tftp_localport], -1
1796 jne .get_packet_tftp
1797 call get_packet_gpxe
1798 jmp .gotten
1799 .get_packet_tftp:
1800 %endif ; GPXE
1801 call get_packet
1802 .gotten:
1803 pop es
1804 popad
1807 %if GPXE
1809 ; Get a fresh packet from a gPXE socket; expects fs -> pktbuf_seg
1810 ; and ds:si -> socket structure
1812 ; Assumes CS == DS == ES.
1814 get_packet_gpxe:
1815 mov di,gpxe_file_read
1816 mov ax,[si+tftp_remoteport] ; gPXE filehandle
1817 mov [di+2],ax
1818 mov word [di+4],PKTBUF_SIZE
1819 mov ax,[si+tftp_pktbuf]
1820 mov [di+6],ax
1821 mov [si+tftp_dataptr],ax
1822 mov [di+8],fs
1824 .again:
1825 mov bx,PXENV_FILE_READ
1826 call pxenv
1827 ; XXX: FIX THIS: Need to be able to distinguish
1828 ; error, EOF, and no data
1829 jc .again
1831 movzx eax,word [di+4] ; Bytes read
1832 mov [si+tftp_bytesleft],ax ; Bytes in buffer
1833 add [si+tftp_filepos],eax ; Position in file
1835 and ax,ax
1836 jnz .got_stuff
1838 ; We got EOF here, make sure the upper layers know
1839 mov byte [si+tftp_goteof],1
1840 mov eax,[si+tftp_filepos]
1841 mov [si+tftp_filesize],eax
1843 .got_stuff:
1844 ; If we're done here, close the file
1845 mov eax,[si+tftp_filepos]
1846 cmp [si+tftp_filesize],eax
1847 ja .done
1849 ; Reuse the previous [es:di] structure since the
1850 ; relevant fields are all the same
1851 mov bx,PXENV_FILE_CLOSE
1852 call pxenv
1853 ; Ignore return...
1854 .done:
1856 %endif ; GPXE
1859 ; Get a fresh packet; expects fs -> pktbuf_seg and ds:si -> socket structure
1861 ; Assumes CS == DS == ES.
1863 get_packet:
1864 .packet_loop:
1865 ; Start by ACKing the previous packet; this should cause the
1866 ; next packet to be sent.
1867 mov cx,PKT_RETRY
1868 mov word [PktTimeout],PKT_TIMEOUT
1870 .send_ack: push cx ; <D> Retry count
1872 mov ax,[si+tftp_lastpkt]
1873 call ack_packet ; Send ACK
1875 ; We used to test the error code here, but sometimes
1876 ; PXE would return negative status even though we really
1877 ; did send the ACK. Now, just treat a failed send as
1878 ; a normally lost packet, and let it time out in due
1879 ; course of events.
1881 .send_ok: ; Now wait for packet.
1882 mov dx,[BIOS_timer] ; Get current time
1884 mov cx,[PktTimeout]
1885 .wait_data: push cx ; <E> Timeout
1886 push dx ; <F> Old time
1888 mov bx,[si+tftp_pktbuf]
1889 mov [pxe_udp_read_pkt.buffer],bx
1890 mov [pxe_udp_read_pkt.buffer+2],fs
1891 mov [pxe_udp_read_pkt.buffersize],word PKTBUF_SIZE
1892 mov eax,[si+tftp_remoteip]
1893 mov [pxe_udp_read_pkt.sip],eax
1894 mov eax,[MyIP]
1895 mov [pxe_udp_read_pkt.dip],eax
1896 mov ax,[si+tftp_remoteport]
1897 mov [pxe_udp_read_pkt.rport],ax
1898 mov ax,[si+tftp_localport]
1899 mov [pxe_udp_read_pkt.lport],ax
1900 mov di,pxe_udp_read_pkt
1901 mov bx,PXENV_UDP_READ
1902 push si ; <G>
1903 call pxenv
1904 pop si ; <G>
1905 and ax,ax
1906 jz .recv_ok
1908 ; No packet, or receive failure
1909 mov dx,[BIOS_timer]
1910 pop ax ; <F> Old time
1911 pop cx ; <E> Timeout
1912 cmp ax,dx ; Same time -> don't advance timeout
1913 je .wait_data ; Same clock tick
1914 loop .wait_data ; Decrease timeout
1916 pop cx ; <D> Didn't get any, send another ACK
1917 shl word [PktTimeout],1 ; Exponential backoff
1918 loop .send_ack
1919 jmp kaboom ; Forget it...
1921 .recv_ok: pop dx ; <F>
1922 pop cx ; <E>
1924 cmp word [pxe_udp_read_pkt.buffersize],byte 4
1925 jb .wait_data ; Bad size for a DATA packet
1927 mov bx,[si+tftp_pktbuf]
1928 cmp word [fs:bx],TFTP_DATA ; Not a data packet?
1929 jne .wait_data ; Then wait for something else
1931 mov ax,[si+tftp_lastpkt]
1932 xchg ah,al ; Host byte order
1933 inc ax ; Which packet are we waiting for?
1934 xchg ah,al ; Network byte order
1935 cmp [fs:bx+2],ax
1936 je .right_packet
1938 ; Wrong packet, ACK the packet and then try again
1939 ; This is presumably because the ACK got lost,
1940 ; so the server just resent the previous packet
1941 mov ax,[fs:bx+2]
1942 call ack_packet
1943 jmp .send_ok ; Reset timeout
1945 .right_packet: ; It's the packet we want. We're also EOF if the
1946 ; size < blocksize
1948 pop cx ; <D> Don't need the retry count anymore
1950 mov [si+tftp_lastpkt],ax ; Update last packet number
1952 movzx ecx,word [pxe_udp_read_pkt.buffersize]
1953 sub cx,byte 4 ; Skip TFTP header
1955 ; Set pointer to data block
1956 lea ax,[bx+4] ; Data past TFTP header
1957 mov [si+tftp_dataptr],ax
1959 add [si+tftp_filepos],ecx
1960 mov [si+tftp_bytesleft],cx
1962 cmp cx,[si+tftp_blksize] ; Is it a full block?
1963 jb .last_block ; If not, it's EOF
1965 .ret:
1966 popad
1967 pop es
1971 .last_block: ; Last block - ACK packet immediately
1972 TRACER 'L'
1973 mov ax,[fs:bx+2]
1974 call ack_packet
1976 ; Make sure we know we are at end of file
1977 mov eax,[si+tftp_filepos]
1978 mov [si+tftp_filesize],eax
1979 mov byte [si+tftp_goteof],1
1981 jmp .ret
1984 ; ack_packet:
1986 ; Send ACK packet. This is a common operation and so is worth canning.
1988 ; Entry:
1989 ; SI = TFTP block
1990 ; AX = Packet # to ack (network byte order)
1991 ; Exit:
1992 ; ZF = 0 -> Error
1993 ; All registers preserved
1995 ; This function uses the pxe_udp_write_pkt but not the packet_buf.
1997 ack_packet:
1998 pushad
1999 mov [ack_packet_buf+2],ax ; Packet number to ack
2000 mov ax,[si]
2001 mov [pxe_udp_write_pkt.lport],ax
2002 mov ax,[si+tftp_remoteport]
2003 mov [pxe_udp_write_pkt.rport],ax
2004 mov eax,[si+tftp_remoteip]
2005 mov [pxe_udp_write_pkt.sip],eax
2006 xor eax,[MyIP]
2007 and eax,[Netmask]
2008 jz .nogw
2009 mov eax,[Gateway]
2010 .nogw:
2011 mov [pxe_udp_write_pkt.gip],eax
2012 mov [pxe_udp_write_pkt.buffer],word ack_packet_buf
2013 mov [pxe_udp_write_pkt.buffersize], word 4
2014 mov di,pxe_udp_write_pkt
2015 mov bx,PXENV_UDP_WRITE
2016 call pxenv
2017 cmp ax,byte 0 ; ZF = 1 if write OK
2018 popad
2022 ; unload_pxe:
2024 ; This function unloads the PXE and UNDI stacks and unclaims
2025 ; the memory.
2027 unload_pxe:
2028 test byte [KeepPXE],01h ; Should we keep PXE around?
2029 jnz reset_pxe
2031 push ds
2032 push es
2034 mov ax,cs
2035 mov ds,ax
2036 mov es,ax
2038 mov si,new_api_unload
2039 cmp byte [APIVer+1],2 ; Major API version >= 2?
2040 jae .new_api
2041 mov si,old_api_unload
2042 .new_api:
2044 .call_loop: xor ax,ax
2045 lodsb
2046 and ax,ax
2047 jz .call_done
2048 xchg bx,ax
2049 mov di,pxe_unload_stack_pkt
2050 push di
2051 xor ax,ax
2052 mov cx,pxe_unload_stack_pkt_len >> 1
2053 rep stosw
2054 pop di
2055 call pxenv
2056 jc .cant_free
2057 mov ax,word [pxe_unload_stack_pkt.status]
2058 cmp ax,PXENV_STATUS_SUCCESS
2059 jne .cant_free
2060 jmp .call_loop
2062 .call_done:
2063 mov bx,0FF00h
2065 mov dx,[RealBaseMem]
2066 cmp dx,[BIOS_fbm] ; Sanity check
2067 jna .cant_free
2068 inc bx
2070 ; Check that PXE actually unhooked the INT 1Ah chain
2071 movzx eax,word [4*0x1a]
2072 movzx ecx,word [4*0x1a+2]
2073 shl ecx,4
2074 add eax,ecx
2075 shr eax,10
2076 cmp ax,dx ; Not in range
2077 jae .ok
2078 cmp ax,[BIOS_fbm]
2079 jae .cant_free
2080 ; inc bx
2082 .ok:
2083 mov [BIOS_fbm],dx
2084 .pop_ret:
2085 pop es
2086 pop ds
2089 .cant_free:
2090 mov si,cant_free_msg
2091 call writestr
2092 push ax
2093 xchg bx,ax
2094 call writehex4
2095 mov al,'-'
2096 call writechr
2097 pop ax
2098 call writehex4
2099 mov al,'-'
2100 call writechr
2101 mov eax,[4*0x1a]
2102 call writehex8
2103 call crlf
2104 jmp .pop_ret
2106 ; We want to keep PXE around, but still we should reset
2107 ; it to the standard bootup configuration
2108 reset_pxe:
2109 push es
2110 push cs
2111 pop es
2112 mov bx,PXENV_UDP_CLOSE
2113 mov di,pxe_udp_close_pkt
2114 call pxenv
2115 pop es
2119 ; gendotquad
2121 ; Take an IP address (in network byte order) in EAX and
2122 ; output a dotted quad string to ES:DI.
2123 ; DI points to terminal null at end of string on exit.
2125 gendotquad:
2126 push eax
2127 push cx
2128 mov cx,4
2129 .genchar:
2130 push eax
2131 cmp al,10 ; < 10?
2132 jb .lt10 ; If so, skip first 2 digits
2134 cmp al,100 ; < 100
2135 jb .lt100 ; If so, skip first digit
2137 aam 100
2138 ; Now AH = 100-digit; AL = remainder
2139 add ah,'0'
2140 mov [es:di],ah
2141 inc di
2143 .lt100:
2144 aam 10
2145 ; Now AH = 10-digit; AL = remainder
2146 add ah,'0'
2147 mov [es:di],ah
2148 inc di
2150 .lt10:
2151 add al,'0'
2152 stosb
2153 mov al,'.'
2154 stosb
2155 pop eax
2156 ror eax,8 ; Move next char into LSB
2157 loop .genchar
2158 dec di
2159 mov [es:di], byte 0
2160 pop cx
2161 pop eax
2164 ; uchexbytes/lchexbytes
2166 ; Take a number of bytes in memory and convert to upper/lower-case
2167 ; hexadecimal
2169 ; Input:
2170 ; DS:SI = input bytes
2171 ; ES:DI = output buffer
2172 ; CX = number of bytes
2173 ; Output:
2174 ; DS:SI = first byte after
2175 ; ES:DI = first byte after
2176 ; CX = 0
2178 ; Trashes AX, DX
2181 lchexbytes:
2182 mov dl,'a'-'9'-1
2183 jmp xchexbytes
2184 uchexbytes:
2185 mov dl,'A'-'9'-1
2186 xchexbytes:
2187 .loop:
2188 lodsb
2189 mov ah,al
2190 shr al,4
2191 call .outchar
2192 mov al,ah
2193 call .outchar
2194 loop .loop
2196 .outchar:
2197 and al,0Fh
2198 add al,'0'
2199 cmp al,'9'
2200 jna .done
2201 add al,dl
2202 .done:
2203 stosb
2207 ; pxe_get_cached_info
2209 ; Get a DHCP packet from the PXE stack into the trackbuf.
2211 ; Input:
2212 ; DL = packet type
2213 ; Output:
2214 ; CX = buffer size
2216 ; Assumes CS == DS == ES.
2218 pxe_get_cached_info:
2219 pushad
2220 mov di,pxe_bootp_query_pkt
2221 push di
2222 xor ax,ax
2223 stosw ; Status
2224 movzx ax,dl
2225 stosw ; Packet type
2226 mov ax,trackbufsize
2227 stosw ; Buffer size
2228 mov ax,trackbuf
2229 stosw ; Buffer offset
2230 xor ax,ax
2231 stosw ; Buffer segment
2233 pop di ; DI -> parameter set
2234 mov bx,PXENV_GET_CACHED_INFO
2235 call pxenv
2236 jc .err
2237 and ax,ax
2238 jnz .err
2240 popad
2241 mov cx,[pxe_bootp_query_pkt.buffersize]
2244 .err:
2245 mov si,err_pxefailed
2246 jmp kaboom
2249 ; ip_ok
2251 ; Tests an IP address in EAX for validity; return with ZF=1 for bad.
2252 ; We used to refuse class E, but class E addresses are likely to become
2253 ; assignable unicast addresses in the near future.
2255 ip_ok:
2256 push ax
2257 cmp eax,-1 ; Refuse the all-ones address
2258 jz .out
2259 and al,al ; Refuse network zero
2260 jz .out
2261 cmp al,127 ; Refuse loopback
2262 jz .out
2263 and al,0F0h
2264 cmp al,224 ; Refuse class D
2265 .out:
2266 pop ax
2270 ; parse_dhcp
2272 ; Parse a DHCP packet. This includes dealing with "overloaded"
2273 ; option fields (see RFC 2132, section 9.3)
2275 ; This should fill in the following global variables, if the
2276 ; information is present:
2278 ; MyIP - client IP address
2279 ; ServerIP - boot server IP address
2280 ; Netmask - network mask
2281 ; Gateway - default gateway router IP
2282 ; BootFile - boot file name
2283 ; DNSServers - DNS server IPs
2284 ; LocalDomain - Local domain name
2285 ; MACLen, MAC - Client identifier, if MACLen == 0
2287 ; This assumes the DHCP packet is in "trackbuf" and the length
2288 ; of the packet in in CX on entry.
2291 parse_dhcp:
2292 mov byte [OverLoad],0 ; Assume no overload
2293 mov eax, [trackbuf+bootp.yip]
2294 call ip_ok
2295 jz .noyip
2296 mov [MyIP], eax
2297 .noyip:
2298 mov eax, [trackbuf+bootp.sip]
2299 and eax, eax
2300 call ip_ok
2301 jz .nosip
2302 mov [ServerIP], eax
2303 .nosip:
2304 sub cx, bootp.options
2305 jbe .nooptions
2306 mov si, trackbuf+bootp.option_magic
2307 lodsd
2308 cmp eax, BOOTP_OPTION_MAGIC
2309 jne .nooptions
2310 call parse_dhcp_options
2311 .nooptions:
2312 mov si, trackbuf+bootp.bootfile
2313 test byte [OverLoad],1
2314 jz .nofileoverload
2315 mov cx,128
2316 call parse_dhcp_options
2317 jmp short .parsed_file
2318 .nofileoverload:
2319 cmp byte [si], 0
2320 jz .parsed_file ; No bootfile name
2321 mov di,BootFile
2322 mov cx,32
2323 rep movsd
2324 xor al,al
2325 stosb ; Null-terminate
2326 .parsed_file:
2327 mov si, trackbuf+bootp.sname
2328 test byte [OverLoad],2
2329 jz .nosnameoverload
2330 mov cx,64
2331 call parse_dhcp_options
2332 .nosnameoverload:
2336 ; Parse a sequence of DHCP options, pointed to by DS:SI; the field
2337 ; size is CX -- some DHCP servers leave option fields unterminated
2338 ; in violation of the spec.
2340 ; For parse_some_dhcp_options, DH contains the minimum value for
2341 ; the option to recognize -- this is used to restrict parsing to
2342 ; PXELINUX-specific options only.
2344 parse_dhcp_options:
2345 xor dx,dx
2347 parse_some_dhcp_options:
2348 .loop:
2349 and cx,cx
2350 jz .done
2352 lodsb
2353 dec cx
2354 jz .done ; Last byte; must be PAD, END or malformed
2355 cmp al, 0 ; PAD option
2356 je .loop
2357 cmp al,255 ; END option
2358 je .done
2360 ; Anything else will have a length field
2361 mov dl,al ; DL <- option number
2362 xor ax,ax
2363 lodsb ; AX <- option length
2364 dec cx
2365 sub cx,ax ; Decrement bytes left counter
2366 jb .done ; Malformed option: length > field size
2368 cmp dl,dh ; Is the option value valid?
2369 jb .opt_done
2371 mov bx,dhcp_option_list
2372 .find_option:
2373 cmp bx,dhcp_option_list_end
2374 jae .opt_done
2375 cmp dl,[bx]
2376 je .found_option
2377 add bx,3
2378 jmp .find_option
2379 .found_option:
2380 pushad
2381 call [bx+1]
2382 popad
2384 ; Fall through
2385 ; Unknown option. Skip to the next one.
2386 .opt_done:
2387 add si,ax
2388 jmp .loop
2389 .done:
2392 section .data
2393 dhcp_option_list:
2394 section .text
2396 %macro dopt 2
2397 section .data
2398 db %1
2399 dw dopt_%2
2400 section .text
2401 dopt_%2:
2402 %endmacro
2405 ; Parse individual DHCP options. SI points to the option data and
2406 ; AX to the option length. DL contains the option number.
2407 ; All registers are saved around the routine.
2409 dopt 1, subnet_mask
2410 mov ebx,[si]
2411 mov [Netmask],ebx
2414 dopt 3, router
2415 mov ebx,[si]
2416 mov [Gateway],ebx
2419 dopt 6, dns_servers
2420 mov cx,ax
2421 shr cx,2
2422 cmp cl,DNS_MAX_SERVERS
2423 jna .oklen
2424 mov cl,DNS_MAX_SERVERS
2425 .oklen:
2426 mov di,DNSServers
2427 rep movsd
2428 mov [LastDNSServer],di
2431 dopt 16, local_domain
2432 mov bx,si
2433 add bx,ax
2434 xor ax,ax
2435 xchg [bx],al ; Zero-terminate option
2436 mov di,LocalDomain
2437 call dns_mangle ; Convert to DNS label set
2438 mov [bx],al ; Restore ending byte
2441 dopt 43, vendor_encaps
2442 mov dh,208 ; Only recognize PXELINUX options
2443 mov cx,ax ; Length of option = max bytes to parse
2444 call parse_some_dhcp_options ; Parse recursive structure
2447 dopt 52, option_overload
2448 mov bl,[si]
2449 mov [OverLoad],bl
2452 dopt 54, server
2453 mov eax,[si]
2454 cmp dword [ServerIP],0
2455 jne .skip ; Already have a next server IP
2456 call ip_ok
2457 jz .skip
2458 mov [ServerIP],eax
2459 .skip: ret
2461 dopt 61, client_identifier
2462 cmp ax,MAC_MAX ; Too long?
2463 ja .skip
2464 cmp ax,2 ; Too short?
2465 jb .skip
2466 cmp [MACLen],ah ; Only do this if MACLen == 0
2467 jne .skip
2468 push ax
2469 lodsb ; Client identifier type
2470 cmp al,[MACType]
2471 pop ax
2472 jne .skip ; Client identifier is not a MAC
2473 dec ax
2474 mov [MACLen],al
2475 mov di,MAC
2476 jmp dhcp_copyoption
2477 .skip: ret
2479 dopt 67, bootfile_name
2480 mov di,BootFile
2481 jmp dhcp_copyoption
2483 dopt 97, uuid_client_identifier
2484 cmp ax,17 ; type byte + 16 bytes UUID
2485 jne .skip
2486 mov dl,[si] ; Must have type 0 == UUID
2487 or dl,[HaveUUID] ; Capture only the first instance
2488 jnz .skip
2489 mov byte [HaveUUID],1 ; Got UUID
2490 mov di,UUIDType
2491 jmp dhcp_copyoption
2492 .skip: ret
2494 dopt 209, pxelinux_configfile
2495 mov di,ConfigName
2496 or byte [DHCPMagic],2 ; Got config file
2497 jmp dhcp_copyoption
2499 dopt 210, pxelinux_pathprefix
2500 mov di,PathPrefix
2501 or byte [DHCPMagic],4 ; Got path prefix
2502 jmp dhcp_copyoption
2504 dopt 211, pxelinux_reboottime
2505 cmp al,4
2506 jne .done
2507 mov ebx,[si]
2508 xchg bl,bh ; Convert to host byte order
2509 rol ebx,16
2510 xchg bl,bh
2511 mov [RebootTime],ebx
2512 or byte [DHCPMagic],8 ; Got RebootTime
2513 .done: ret
2515 ; Common code for copying an option verbatim
2516 ; Copies the option into ES:DI and null-terminates it.
2517 ; Returns with AX=0 and SI past the option.
2518 dhcp_copyoption:
2519 xchg cx,ax ; CX <- option length
2520 rep movsb
2521 xchg cx,ax ; AX <- 0
2522 stosb ; Null-terminate
2525 section .data
2526 dhcp_option_list_end:
2527 section .text
2529 section .data
2530 HaveUUID db 0
2531 uuid_dashes db 4,2,2,2,6,0 ; Bytes per UUID dashed section
2532 section .text
2535 ; genipopt
2537 ; Generate an ip=<client-ip>:<boot-server-ip>:<gw-ip>:<netmask>
2538 ; option into IPOption based on a DHCP packet in trackbuf.
2539 ; Assumes CS == DS == ES.
2541 genipopt:
2542 pushad
2543 mov di,IPOption
2544 mov eax,'ip='
2545 stosd
2546 dec di
2547 mov eax,[MyIP]
2548 call gendotquad
2549 mov al,':'
2550 stosb
2551 mov eax,[ServerIP]
2552 call gendotquad
2553 mov al,':'
2554 stosb
2555 mov eax,[Gateway]
2556 call gendotquad
2557 mov al,':'
2558 stosb
2559 mov eax,[Netmask]
2560 call gendotquad ; Zero-terminates its output
2561 sub di,IPOption
2562 mov [IPOptionLen],di
2563 popad
2567 ; Call the receive loop while idle. This is done mostly so we can respond to
2568 ; ARP messages, but perhaps in the future this can be used to do network
2569 ; console.
2571 ; hpa sez: people using automatic control on the serial port get very
2572 ; unhappy if we poll for ARP too often (the PXE stack is pretty slow,
2573 ; typically.) Therefore, only poll if at least 4 BIOS timer ticks have
2574 ; passed since the last poll, and reset this when a character is
2575 ; received (RESET_IDLE).
2577 %if HAVE_IDLE
2579 reset_idle:
2580 push ax
2581 mov ax,[cs:BIOS_timer]
2582 mov [cs:IdleTimer],ax
2583 pop ax
2586 check_for_arp:
2587 push ax
2588 mov ax,[cs:BIOS_timer]
2589 sub ax,[cs:IdleTimer]
2590 cmp ax,4
2591 pop ax
2592 jae .need_poll
2594 .need_poll: pushad
2595 push ds
2596 push es
2597 mov ax,cs
2598 mov ds,ax
2599 mov es,ax
2600 mov di,packet_buf
2601 mov [pxe_udp_read_pkt.status],al ; 0
2602 mov [pxe_udp_read_pkt.buffer],di
2603 mov [pxe_udp_read_pkt.buffer+2],ds
2604 mov word [pxe_udp_read_pkt.buffersize],packet_buf_size
2605 mov eax,[MyIP]
2606 mov [pxe_udp_read_pkt.dip],eax
2607 mov word [pxe_udp_read_pkt.lport],htons(9) ; discard port
2608 mov di,pxe_udp_read_pkt
2609 mov bx,PXENV_UDP_READ
2610 call pxenv
2611 ; Ignore result...
2612 pop es
2613 pop ds
2614 popad
2615 RESET_IDLE
2618 %endif ; HAVE_IDLE
2620 ; -----------------------------------------------------------------------------
2621 ; Common modules
2622 ; -----------------------------------------------------------------------------
2624 %include "getc.inc" ; getc et al
2625 %include "conio.inc" ; Console I/O
2626 %include "writestr.inc" ; String output
2627 writestr equ cwritestr
2628 %include "writehex.inc" ; Hexadecimal output
2629 %include "configinit.inc" ; Initialize configuration
2630 %include "parseconfig.inc" ; High-level config file handling
2631 %include "parsecmd.inc" ; Low-level config file handling
2632 %include "bcopy32.inc" ; 32-bit bcopy
2633 %include "loadhigh.inc" ; Load a file into high memory
2634 %include "font.inc" ; VGA font stuff
2635 %include "graphics.inc" ; VGA graphics
2636 %include "highmem.inc" ; High memory sizing
2637 %include "strcpy.inc" ; strcpy()
2638 %include "rawcon.inc" ; Console I/O w/o using the console functions
2639 %include "dnsresolv.inc" ; DNS resolver
2640 %include "adv.inc" ; Auxillary Data Vector
2642 ; -----------------------------------------------------------------------------
2643 ; Begin data section
2644 ; -----------------------------------------------------------------------------
2646 section .data
2648 copyright_str db ' Copyright (C) 1994-', year, ' H. Peter Anvin'
2649 db CR, LF, 0
2650 err_bootfailed db CR, LF, 'Boot failed: press a key to retry, or wait for reset...', CR, LF, 0
2651 bailmsg equ err_bootfailed
2652 err_nopxe db "No !PXE or PXENV+ API found; we're dead...", CR, LF, 0
2653 err_pxefailed db 'PXE API call failed, error ', 0
2654 err_udpinit db 'Failed to initialize UDP stack', CR, LF, 0
2655 err_noconfig db 'Unable to locate configuration file', CR, LF, 0
2656 err_damage db 'TFTP server sent an incomprehesible reply', CR, LF, 0
2657 found_pxenv db 'Found PXENV+ structure', CR, LF, 0
2658 using_pxenv_msg db 'Old PXE API detected, using PXENV+ structure', CR, LF, 0
2659 apiver_str db 'PXE API version is ',0
2660 pxeentry_msg db 'PXE entry point found (we hope) at ', 0
2661 pxenventry_msg db 'PXENV entry point found (we hope) at ', 0
2662 trymempxe_msg db 'Scanning memory for !PXE structure... ', 0
2663 trymempxenv_msg db 'Scanning memory for PXENV+ structure... ', 0
2664 undi_data_msg db 'UNDI data segment at: ',0
2665 undi_data_len_msg db 'UNDI data segment size: ',0
2666 undi_code_msg db 'UNDI code segment at: ',0
2667 undi_code_len_msg db 'UNDI code segment size: ',0
2668 cant_free_msg db 'Failed to free base memory, error ', 0
2669 notfound_msg db 'not found', CR, LF, 0
2670 myipaddr_msg db 'My IP address seems to be ',0
2671 tftpprefix_msg db 'TFTP prefix: ', 0
2672 localboot_msg db 'Booting from local disk...', CR, LF, 0
2673 trying_msg db 'Trying to load: ', 0
2674 fourbs_msg db BS, BS, BS, BS, 0
2675 default_str db 'default', 0
2676 syslinux_banner db CR, LF, 'PXELINUX ', version_str, ' ', date, ' ', 0
2677 cfgprefix db 'pxelinux.cfg/' ; No final null!
2678 cfgprefix_len equ ($-cfgprefix)
2681 ; Command line options we'd like to take a look at
2683 ; mem= and vga= are handled as normal 32-bit integer values
2684 initrd_cmd db 'initrd='
2685 initrd_cmd_len equ $-initrd_cmd
2687 ; This one we make ourselves
2688 bootif_str db 'BOOTIF='
2689 bootif_str_len equ $-bootif_str
2691 ; Config file keyword table
2693 %include "keywords.inc"
2696 ; Extensions to search for (in *forward* order).
2697 ; (.bs and .bss are disabled for PXELINUX, since they are not supported)
2699 align 4, db 0
2700 exten_table: db '.cbt' ; COMBOOT (specific)
2701 db '.0', 0, 0 ; PXE bootstrap program
2702 db '.com' ; COMBOOT (same as DOS)
2703 db '.c32' ; COM32
2704 exten_table_end:
2705 dd 0, 0 ; Need 8 null bytes here
2708 ; PXE unload sequences
2710 new_api_unload:
2711 db PXENV_UDP_CLOSE
2712 db PXENV_UNDI_SHUTDOWN
2713 db PXENV_UNLOAD_STACK
2714 db PXENV_STOP_UNDI
2715 db 0
2716 old_api_unload:
2717 db PXENV_UDP_CLOSE
2718 db PXENV_UNDI_SHUTDOWN
2719 db PXENV_UNLOAD_STACK
2720 db PXENV_UNDI_CLEANUP
2721 db 0
2724 ; PXE query packets partially filled in
2726 section .bss
2727 pxe_bootp_query_pkt:
2728 .status: resw 1 ; Status
2729 .packettype: resw 1 ; Boot server packet type
2730 .buffersize: resw 1 ; Packet size
2731 .buffer: resw 2 ; seg:off of buffer
2732 .bufferlimit: resw 1 ; Unused
2734 section .data
2735 pxe_udp_open_pkt:
2736 .status: dw 0 ; Status
2737 .sip: dd 0 ; Source (our) IP
2739 pxe_udp_close_pkt:
2740 .status: dw 0 ; Status
2742 pxe_udp_write_pkt:
2743 .status: dw 0 ; Status
2744 .sip: dd 0 ; Server IP
2745 .gip: dd 0 ; Gateway IP
2746 .lport: dw 0 ; Local port
2747 .rport: dw 0 ; Remote port
2748 .buffersize: dw 0 ; Size of packet
2749 .buffer: dw 0, 0 ; seg:off of buffer
2751 pxe_udp_read_pkt:
2752 .status: dw 0 ; Status
2753 .sip: dd 0 ; Source IP
2754 .dip: dd 0 ; Destination (our) IP
2755 .rport: dw 0 ; Remote port
2756 .lport: dw 0 ; Local port
2757 .buffersize: dw 0 ; Max packet size
2758 .buffer: dw 0, 0 ; seg:off of buffer
2760 %if GPXE
2762 gpxe_file_open:
2763 .status: dw 0 ; Status
2764 .filehandle: dw 0 ; FileHandle
2765 .filename: dd 0 ; seg:off of FileName
2766 .reserved: dd 0
2768 gpxe_get_file_size:
2769 .status: dw 0 ; Status
2770 .filehandle: dw 0 ; FileHandle
2771 .filesize: dd 0 ; FileSize
2773 gpxe_file_read:
2774 .status: dw 0 ; Status
2775 .filehandle: dw 0 ; FileHandle
2776 .buffersize: dw 0 ; BufferSize
2777 .buffer: dd 0 ; seg:off of buffer
2779 %endif ; GPXE
2782 ; Misc initialized (data) variables
2784 alignb 4, db 0
2785 BaseStack dd StackBuf ; ESP of base stack
2786 dw 0 ; SS of base stack
2787 NextSocket dw 49152 ; Counter for allocating socket numbers
2788 KeepPXE db 0 ; Should PXE be kept around?
2791 ; TFTP commands
2793 tftp_tail db 'octet', 0 ; Octet mode
2794 tsize_str db 'tsize' ,0 ; Request size
2795 tsize_len equ ($-tsize_str)
2796 db '0', 0
2797 blksize_str db 'blksize', 0 ; Request large blocks
2798 blksize_len equ ($-blksize_str)
2799 asciidec TFTP_LARGEBLK
2800 db 0
2801 tftp_tail_len equ ($-tftp_tail)
2803 alignb 2, db 0
2805 ; Options negotiation parsing table (string pointer, string len, offset
2806 ; into socket structure)
2808 tftp_opt_table:
2809 dw tsize_str, tsize_len, tftp_filesize
2810 dw blksize_str, blksize_len, tftp_blksize
2811 tftp_opts equ ($-tftp_opt_table)/6
2814 ; Error packet to return on options negotiation error
2816 tftp_opt_err dw TFTP_ERROR ; ERROR packet
2817 dw TFTP_EOPTNEG ; ERROR 8: bad options
2818 db 'tsize option required', 0 ; Error message
2819 tftp_opt_err_len equ ($-tftp_opt_err)
2821 alignb 4, db 0
2822 ack_packet_buf: dw TFTP_ACK, 0 ; TFTP ACK packet
2825 ; IP information (initialized to "unknown" values)
2826 MyIP dd 0 ; My IP address
2827 ServerIP dd 0 ; IP address of boot server
2828 Netmask dd 0 ; Netmask of this subnet
2829 Gateway dd 0 ; Default router
2830 ServerPort dw TFTP_PORT ; TFTP server port
2833 ; Variables that are uninitialized in SYSLINUX but initialized here
2835 alignb 4, db 0
2836 BufSafe dw trackbufsize/TFTP_BLOCKSIZE ; Clusters we can load into trackbuf
2837 BufSafeBytes dw trackbufsize ; = how many bytes?
2838 %ifndef DEPEND
2839 %if ( trackbufsize % TFTP_BLOCKSIZE ) != 0
2840 %error trackbufsize must be a multiple of TFTP_BLOCKSIZE
2841 %endif
2842 %endif