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