pxelinux: cleaner test for OACK trailing null bytes
[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                 ; If we find an option which starts with a NUL byte,
1109                 ; (a null option), we're either seeing garbage that some
1110                 ; TFTP servers add to the end of the packet, or we have
1111                 ; no clue how to parse the rest of the packet (what is
1112                 ; an option name and what is a value?)  In either case,
1113                 ; discard the rest.
1114                 cmp byte [si],0
1115                 je .done_pkt
1116
1117 .get_opt_name:
1118                 mov di,si
1119                 mov bx,si
1120 .opt_name_loop: lodsb
1121                 and al,al
1122                 jz .got_opt_name
1123                 or al,20h                       ; Convert to lowercase
1124                 stosb
1125                 loop .opt_name_loop
1126                 ; We ran out, and no final null
1127                 jmp .done_pkt                   ; Ignore runt option
1128 .got_opt_name:  ; si -> option value
1129                 dec cx                          ; bytes left in pkt
1130                 jz .done_pkt                    ; Option w/o value, ignore
1131
1132                 ; Parse option pointed to by bx; guaranteed to be
1133                 ; null-terminated.
1134                 push cx
1135                 push si
1136                 mov si,bx                       ; -> option name
1137                 mov bx,tftp_opt_table
1138                 mov cx,tftp_opts
1139 .opt_loop:
1140                 push cx
1141                 push si
1142                 mov di,[bx]                     ; Option pointer
1143                 mov cx,[bx+2]                   ; Option len
1144                 repe cmpsb
1145                 pop si
1146                 pop cx
1147                 je .get_value                   ; OK, known option
1148                 add bx,6
1149                 loop .opt_loop
1150
1151                 pop si
1152                 pop cx
1153                 ; Non-negotiated option returned, no idea what it means...
1154                 jmp .err_reply
1155
1156 .get_value:     pop si                          ; si -> option value
1157                 pop cx                          ; cx -> bytes left in pkt
1158                 mov bx,[bx+4]                   ; Pointer to data target
1159                 add bx,[bp-6]                   ; TFTP socket pointer
1160                 xor eax,eax
1161                 xor edx,edx
1162 .value_loop:    lodsb
1163                 and al,al
1164                 jz .got_value
1165                 sub al,'0'
1166                 cmp al, 9
1167                 ja .err_reply                   ; Not a decimal digit
1168                 imul edx,10
1169                 add edx,eax
1170                 mov [bx],edx
1171                 loop .value_loop
1172                 ; Ran out before final null, accept anyway
1173                 jmp short .done_pkt
1174
1175 .got_value:
1176                 dec cx
1177                 jnz .get_opt_name               ; Not end of packet
1178
1179                 ; ZF == 1
1180
1181                 ; Success, done!
1182 .done_pkt:
1183                 pop si                  ; Junk
1184                 pop si                  ; We want the packet ptr in SI
1185
1186                 mov eax,[si+tftp_filesize]
1187 .got_file:                              ; SI->socket structure, EAX = size
1188                 and eax,eax             ; Set ZF depending on file size
1189                 jz .error_si            ; ZF = 1 need to free the socket
1190 .ret:
1191                 leave                   ; SP <- BP, POP BP
1192                 pop cx
1193                 pop bx
1194                 pop es
1195                 ret
1196
1197
1198 .no_oack:       ; We got a DATA packet, meaning no options are
1199                 ; suported.  Save the data away and consider the length
1200                 ; undefined, *unless* this is the only data packet...
1201                 mov bx,[bp-6]           ; File pointer
1202                 sub cx,2                ; Too short?
1203                 jb .failure
1204                 lodsw                   ; Block number
1205                 cmp ax,htons(1)
1206                 jne .failure
1207                 mov [bx+tftp_lastpkt],ax
1208                 cmp cx,TFTP_BLOCKSIZE
1209                 ja .err_reply           ; Corrupt...
1210                 je .not_eof
1211                 ; This was the final EOF packet, already...
1212                 ; We know the filesize, but we also want to ack the
1213                 ; packet and set the EOF flag.
1214                 mov [bx+tftp_filesize],ecx
1215                 mov byte [bx+tftp_goteof],1
1216                 push si
1217                 mov si,bx
1218                 ; AX = htons(1) already
1219                 call ack_packet
1220                 pop si
1221 .not_eof:
1222                 mov [bx+tftp_bytesleft],cx
1223                 mov ax,pktbuf_seg
1224                 push es
1225                 mov es,ax
1226                 mov di,tftp_pktbuf
1227                 mov [bx+tftp_dataptr],di
1228                 add cx,3
1229                 shr cx,2
1230                 rep movsd
1231                 pop es
1232                 jmp .done_pkt
1233
1234 .err_reply:     ; TFTP protocol error.  Send ERROR reply.
1235                 ; ServerIP and gateway are already programmed in
1236                 mov si,[bp-6]
1237                 mov ax,[si+tftp_remoteport]
1238                 mov word [pxe_udp_write_pkt.rport],ax
1239                 mov word [pxe_udp_write_pkt.buffer],tftp_proto_err
1240                 mov word [pxe_udp_write_pkt.buffersize],tftp_proto_err_len
1241                 mov di,pxe_udp_write_pkt
1242                 mov bx,PXENV_UDP_WRITE
1243                 call pxenv
1244
1245                 ; Write an error message and explode
1246                 mov si,err_damage
1247                 call writestr_early
1248                 jmp kaboom
1249
1250 .bailnow:
1251                 ; Immediate error - no retry
1252                 mov word [bp-2],TimeoutTableEnd-1
1253
1254 .failure:       pop bx                  ; Junk
1255                 pop bx
1256                 pop si
1257                 pop ax
1258                 inc ax
1259                 cmp ax,TimeoutTableEnd
1260                 jb .sendreq             ; Try again
1261
1262 .error:         mov si,bx               ; Socket pointer
1263 .error_si:                              ; Socket pointer already in SI
1264                 call free_socket        ; ZF <- 1, SI <- 0
1265                 jmp .ret
1266
1267
1268 %if GPXE
1269 .gpxe:
1270                 push bx                 ; Socket pointer
1271                 mov di,gpxe_file_open
1272                 mov word [di],2         ; PXENV_STATUS_BAD_FUNC
1273                 mov word [di+4],packet_buf+2    ; Completed URL
1274                 mov [di+6],ds
1275                 mov bx,PXENV_FILE_OPEN
1276                 call pxenv
1277                 pop si                  ; Socket pointer in SI
1278                 jc .error_si
1279
1280                 mov ax,[di+2]
1281                 mov word [si+tftp_localport],-1 ; gPXE URL
1282                 mov [si+tftp_remoteport],ax
1283                 mov di,gpxe_get_file_size
1284                 mov [di+2],ax
1285
1286 %if 0
1287                 ; Disable this for now since gPXE doesn't always
1288                 ; return valid information in PXENV_GET_FILE_SIZE
1289                 mov bx,PXENV_GET_FILE_SIZE
1290                 call pxenv
1291                 mov eax,[di+4]          ; File size
1292                 jnc .oksize
1293 %endif
1294                 or eax,-1               ; Size unknown
1295 .oksize:
1296                 mov [si+tftp_filesize],eax
1297                 jmp .got_file
1298 %endif ; GPXE
1299
1300 ;
1301 ; allocate_socket: Allocate a local UDP port structure
1302 ;
1303 ;               If successful:
1304 ;                 ZF set
1305 ;                 BX     = socket pointer
1306 ;               If unsuccessful:
1307 ;                 ZF clear
1308 ;
1309 allocate_socket:
1310                 push cx
1311                 mov bx,Files
1312                 mov cx,MAX_OPEN
1313 .check:         cmp word [bx], byte 0
1314                 je .found
1315                 add bx,open_file_t_size
1316                 loop .check
1317                 xor cx,cx                       ; ZF = 1
1318                 pop cx
1319                 ret
1320                 ; Allocate a socket number.  Socket numbers are made
1321                 ; guaranteed unique by including the socket slot number
1322                 ; (inverted, because we use the loop counter cx); add a
1323                 ; counter value to keep the numbers from being likely to
1324                 ; get immediately reused.
1325                 ;
1326                 ; The NextSocket variable also contains the top two bits
1327                 ; set.  This generates a value in the range 49152 to
1328                 ; 57343.
1329 .found:
1330                 dec cx
1331                 push ax
1332                 mov ax,[NextSocket]
1333                 inc ax
1334                 and ax,((1 << (13-MAX_OPEN_LG2))-1) | 0xC000
1335                 mov [NextSocket],ax
1336                 shl cx,13-MAX_OPEN_LG2
1337                 add cx,ax                       ; ZF = 0
1338                 xchg ch,cl                      ; Convert to network byte order
1339                 mov [bx],cx                     ; Socket in use
1340                 pop ax
1341                 pop cx
1342                 ret
1343
1344 ;
1345 ; Free socket: socket in SI; return SI = 0, ZF = 1 for convenience
1346 ;
1347 free_socket:
1348                 push es
1349                 pusha
1350                 xor ax,ax
1351                 mov es,ax
1352                 mov di,si
1353                 mov cx,tftp_pktbuf >> 1         ; tftp_pktbuf is not cleared
1354                 rep stosw
1355                 popa
1356                 pop es
1357                 xor si,si
1358                 ret
1359
1360 ;
1361 ; parse_dotquad:
1362 ;               Read a dot-quad pathname in DS:SI and output an IP
1363 ;               address in EAX, with SI pointing to the first
1364 ;               nonmatching character.
1365 ;
1366 ;               Return CF=1 on error.
1367 ;
1368 ;               No segment assumptions permitted.
1369 ;
1370 parse_dotquad:
1371                 push cx
1372                 mov cx,4
1373                 xor eax,eax
1374 .parseloop:
1375                 mov ch,ah
1376                 mov ah,al
1377                 lodsb
1378                 sub al,'0'
1379                 jb .notnumeric
1380                 cmp al,9
1381                 ja .notnumeric
1382                 aad                             ; AL += 10 * AH; AH = 0;
1383                 xchg ah,ch
1384                 jmp .parseloop
1385 .notnumeric:
1386                 cmp al,'.'-'0'
1387                 pushf
1388                 mov al,ah
1389                 mov ah,ch
1390                 xor ch,ch
1391                 ror eax,8
1392                 popf
1393                 jne .error
1394                 loop .parseloop
1395                 jmp .done
1396 .error:
1397                 loop .realerror                 ; If CX := 1 then we're done
1398                 clc
1399                 jmp .done
1400 .realerror:
1401                 stc
1402 .done:
1403                 dec si                          ; CF unchanged!
1404                 pop cx
1405                 ret
1406
1407 ;
1408 ; is_url:      Return CF=0 if and only if the buffer pointed to by
1409 ;              DS:SI is a URL (contains ://).  No registers modified.
1410 ;
1411 %if GPXE
1412 is_url:
1413                 push si
1414                 push eax
1415 .loop:
1416                 mov eax,[si]
1417                 inc si
1418                 and al,al
1419                 jz .not_url
1420                 and eax,0FFFFFFh
1421                 cmp eax,'://'
1422                 jne .loop
1423 .done:
1424                 ; CF=0 here
1425                 pop eax
1426                 pop si
1427                 ret
1428 .not_url:
1429                 stc
1430                 jmp .done
1431
1432 ;
1433 ; is_gpxe:     Return CF=0 if and only if the buffer pointed to by
1434 ;              DS:SI is a URL (contains ://) *and* the gPXE extensions
1435 ;              API is available.  No registers modified.
1436 ;
1437 is_gpxe:
1438                 call is_url
1439                 jc .ret                 ; Not a URL, don't bother
1440 .again:
1441                 cmp byte [HasGPXE],1
1442                 ja .unknown
1443                 ; CF=1 if not available (0),
1444                 ; CF=0 if known available (1).
1445 .ret:           ret
1446
1447 .unknown:
1448                 ; If we get here, the gPXE status is unknown.
1449                 push es
1450                 pushad
1451                 push ds
1452                 pop es
1453                 mov di,gpxe_file_api_check
1454                 mov bx,PXENV_FILE_API_CHECK     ; BH = 0
1455                 call pxenv
1456                 jc .nogood
1457                 cmp dword [di+4],0xe9c17b20
1458                 jne .nogood
1459                 mov ax,[di+12]          ; Don't care about the upper half...
1460                 not ax                  ; Set bits of *missing* functions...
1461                 and ax,01001011b        ; The functions we care about
1462                 setz bh
1463                 jz .done
1464 .nogood:
1465                 mov si,gpxe_warning_msg
1466                 call writestr_early
1467 .done:
1468                 mov [HasGPXE],bh
1469                 popad
1470                 pop es
1471                 jmp .again
1472
1473                 section .data
1474 gpxe_warning_msg:
1475                 db 'URL syntax, but gPXE extensions not detected, '
1476                 db 'trying plain TFTP...', CR, LF, 0
1477 HasGPXE         db -1                   ; Unknown
1478                 section .text
1479
1480 %endif
1481
1482 ;
1483 ; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed
1484 ;              to by ES:DI; ends on encountering any whitespace.
1485 ;              DI is preserved.
1486 ;
1487 ;              This verifies that a filename is < FILENAME_MAX characters
1488 ;              and doesn't contain whitespace, and zero-pads the output buffer,
1489 ;              so "repe cmpsb" can do a compare.
1490 ;
1491 ;              The first four bytes of the manged name is the IP address of
1492 ;              the download host, 0 for no host, or -1 for a gPXE URL.
1493 ;
1494 ;              No segment assumptions permitted.
1495 ;
1496 mangle_name:
1497                 push di
1498 %if GPXE
1499                 call is_url
1500                 jc .not_url
1501                 or eax,-1                       ; It's a URL
1502                 jmp .prefix_done
1503 .not_url:
1504 %endif ; GPXE
1505                 push si
1506                 mov eax,[cs:ServerIP]
1507                 cmp byte [si],0
1508                 je .noip                        ; Null filename?!?!
1509                 cmp word [si],'::'              ; Leading ::?
1510                 je .gotprefix
1511
1512 .more:
1513                 inc si
1514                 cmp byte [si],0
1515                 je .noip
1516                 cmp word [si],'::'
1517                 jne .more
1518
1519                 ; We have a :: prefix of some sort, it could be either
1520                 ; a DNS name or a dot-quad IP address.  Try the dot-quad
1521                 ; first...
1522 .here:
1523                 pop si
1524                 push si
1525                 call parse_dotquad
1526                 jc .notdq
1527                 cmp word [si],'::'
1528                 je .gotprefix
1529 .notdq:
1530                 pop si
1531                 push si
1532                 call dns_resolv
1533                 cmp word [si],'::'
1534                 jne .noip
1535                 and eax,eax
1536                 jnz .gotprefix
1537
1538 .noip:
1539                 pop si
1540                 xor eax,eax
1541                 jmp .prefix_done
1542
1543 .gotprefix:
1544                 pop cx                          ; Adjust stack
1545                 inc si                          ; Skip double colon
1546                 inc si
1547
1548 .prefix_done:
1549                 stosd                           ; Save IP address prefix
1550                 mov cx,FILENAME_MAX-5
1551
1552 .mn_loop:
1553                 lodsb
1554                 cmp al,' '                      ; If control or space, end
1555                 jna .mn_end
1556                 stosb
1557                 loop .mn_loop
1558 .mn_end:
1559                 inc cx                          ; At least one null byte
1560                 xor ax,ax                       ; Zero-fill name
1561                 rep stosb                       ; Doesn't do anything if CX=0
1562                 pop di
1563                 ret                             ; Done
1564
1565 ;
1566 ; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled
1567 ;                filename to the conventional representation.  This is needed
1568 ;                for the BOOT_IMAGE= parameter for the kernel.
1569 ;
1570 ;                NOTE: The output buffer needs to be able to hold an
1571 ;                expanded IP address.
1572 ;
1573 ;                DS:SI -> input mangled file name
1574 ;                ES:DI -> output buffer
1575 ;
1576 ;                On return, DI points to the first byte after the output name,
1577 ;                which is set to a null byte.
1578 ;
1579 unmangle_name:
1580                 push eax
1581                 lodsd
1582                 and eax,eax
1583                 jz .noip
1584                 cmp eax,-1
1585                 jz .noip                        ; URL
1586                 call gendotquad
1587                 mov ax,'::'
1588                 stosw
1589 .noip:
1590                 call strcpy
1591                 dec di                          ; Point to final null byte
1592                 pop eax
1593                 ret
1594
1595 ;
1596 ; pxenv
1597 ;
1598 ; This is the main PXENV+/!PXE entry point, using the PXENV+
1599 ; calling convention.  This is a separate local routine so
1600 ; we can hook special things from it if necessary.  In particular,
1601 ; some PXE stacks seem to not like being invoked from anything but
1602 ; the initial stack, so humour it.
1603 ;
1604 ; While we're at it, save and restore all registers.
1605 ;
1606 pxenv:
1607                 pushfd
1608                 pushad
1609 %if USE_PXE_PROVIDED_STACK == 0
1610                 mov [cs:PXEStack],sp
1611                 mov [cs:PXEStack+2],ss
1612                 lss sp,[cs:InitStack]
1613 %endif
1614                 ; Pre-clear the Status field
1615                 mov word [es:di],cs
1616
1617                 ; This works either for the PXENV+ or the !PXE calling
1618                 ; convention, as long as we ignore CF (which is redundant
1619                 ; with AX anyway.)
1620                 push es
1621                 push di
1622                 push bx
1623 .jump:          call 0:0
1624                 add sp,6
1625                 mov [cs:PXEStatus],ax
1626 %if USE_PXE_PROVIDED_STACK == 0
1627                 lss sp,[cs:PXEStack]
1628 %endif
1629                 mov bp,sp
1630                 and ax,ax
1631                 setnz [bp+32]                   ; If AX != 0 set CF on return
1632
1633                 ; This clobbers the AX return, but we already saved it into
1634                 ; the PXEStatus variable.
1635                 popad
1636                 popfd                           ; Restore flags (incl. IF, DF)
1637                 ret
1638
1639 ; Must be after function def due to NASM bug
1640 PXEEntry        equ pxenv.jump+1
1641
1642                 section .bss
1643                 alignb 2
1644 PXEStatus       resb 2
1645
1646                 section .text
1647
1648 ;
1649 ; getfssec: Get multiple clusters from a file, given the starting cluster.
1650 ;
1651 ;       In this case, get multiple blocks from a specific TCP connection.
1652 ;
1653 ;  On entry:
1654 ;       ES:BX   -> Buffer
1655 ;       SI      -> TFTP socket pointer
1656 ;       CX      -> 512-byte block count; 0FFFFh = until end of file
1657 ;  On exit:
1658 ;       SI      -> TFTP socket pointer (or 0 on EOF)
1659 ;       CF = 1  -> Hit EOF
1660 ;       ECX     -> number of bytes actually read
1661 ;
1662 getfssec:
1663                 push eax
1664                 push edi
1665                 push bx
1666                 push si
1667                 push fs
1668                 mov di,bx
1669                 mov ax,pktbuf_seg
1670                 mov fs,ax
1671
1672                 xor eax,eax
1673                 movzx ecx,cx
1674                 shl ecx,TFTP_BLOCKSIZE_LG2      ; Convert to bytes
1675                 push ecx                        ; Initial request size
1676                 jz .hit_eof                     ; Nothing to do?
1677
1678 .need_more:
1679                 call fill_buffer
1680                 movzx eax,word [si+tftp_bytesleft]
1681                 and ax,ax
1682                 jz .hit_eof
1683
1684                 push ecx
1685                 cmp ecx,eax
1686                 jna .ok_size
1687                 mov ecx,eax
1688 .ok_size:
1689                 mov ax,cx                       ; EAX<31:16> == ECX<31:16> == 0
1690                 mov bx,[si+tftp_dataptr]
1691                 sub [si+tftp_bytesleft],cx
1692                 xchg si,bx
1693                 fs rep movsb                    ; Copy from packet buffer
1694                 xchg si,bx
1695                 mov [si+tftp_dataptr],bx
1696
1697                 pop ecx
1698                 sub ecx,eax
1699                 jnz .need_more
1700
1701 .hit_eof:
1702                 call fill_buffer
1703
1704                 pop eax                         ; Initial request amount
1705                 xchg eax,ecx
1706                 sub ecx,eax                     ; ... minus anything not gotten
1707
1708                 pop fs
1709                 pop si
1710
1711                 ; Is there anything left of this?
1712                 mov eax,[si+tftp_filesize]
1713                 sub eax,[si+tftp_filepos]
1714                 jnz .bytes_left
1715
1716                 cmp [si+tftp_bytesleft],ax      ; AX == 0
1717                 jne .bytes_left
1718
1719                 cmp byte [si+tftp_goteof],0
1720                 je .done
1721                 ; I'm 99% sure this can't happen, but...
1722                 call fill_buffer                ; Receive/ACK the EOF packet
1723 .done:
1724                 ; The socket is closed and the buffer drained
1725                 ; Close socket structure and re-init for next user
1726                 call free_socket
1727                 stc
1728                 jmp .ret
1729 .bytes_left:
1730                 clc
1731 .ret:
1732                 pop bx
1733                 pop edi
1734                 pop eax
1735                 ret
1736
1737 ;
1738 ; Get a fresh packet if the buffer is drained, and we haven't hit
1739 ; EOF yet.  The buffer should be filled immediately after draining!
1740 ;
1741 ; expects fs -> pktbuf_seg and ds:si -> socket structure
1742 ;
1743 fill_buffer:
1744                 cmp word [si+tftp_bytesleft],0
1745                 je .empty
1746                 ret                             ; Otherwise, nothing to do
1747
1748 .empty:
1749                 push es
1750                 pushad
1751                 mov ax,ds
1752                 mov es,ax
1753
1754                 ; Note: getting the EOF packet is not the same thing
1755                 ; as tftp_filepos == tftp_filesize; if the EOF packet
1756                 ; is empty the latter condition can be true without
1757                 ; having gotten the official EOF.
1758                 cmp byte [si+tftp_goteof],0
1759                 jne .ret                        ; Already EOF
1760
1761 %if GPXE
1762                 cmp word [si+tftp_localport], -1
1763                 jne .get_packet_tftp
1764                 call get_packet_gpxe
1765                 jmp .ret
1766 .get_packet_tftp:
1767 %endif ; GPXE
1768
1769                 ; TFTP code...
1770 .packet_loop:
1771                 ; Start by ACKing the previous packet; this should cause the
1772                 ; next packet to be sent.
1773                 mov bx,TimeoutTable
1774
1775 .send_ack:      push bx                         ; <D> Retry pointer
1776                 movzx cx,byte [bx]              ; Timeout
1777
1778                 mov ax,[si+tftp_lastpkt]
1779                 call ack_packet                 ; Send ACK
1780
1781                 ; We used to test the error code here, but sometimes
1782                 ; PXE would return negative status even though we really
1783                 ; did send the ACK.  Now, just treat a failed send as
1784                 ; a normally lost packet, and let it time out in due
1785                 ; course of events.
1786
1787 .send_ok:       ; Now wait for packet.
1788                 mov dx,[BIOS_timer]             ; Get current time
1789
1790 .wait_data:     push cx                         ; <E> Timeout
1791                 push dx                         ; <F> Old time
1792
1793                 mov bx,[si+tftp_pktbuf]
1794                 mov [pxe_udp_read_pkt.buffer],bx
1795                 mov [pxe_udp_read_pkt.buffer+2],fs
1796                 mov [pxe_udp_read_pkt.buffersize],word PKTBUF_SIZE
1797                 mov eax,[si+tftp_remoteip]
1798                 mov [pxe_udp_read_pkt.sip],eax
1799                 mov eax,[MyIP]
1800                 mov [pxe_udp_read_pkt.dip],eax
1801                 mov ax,[si+tftp_remoteport]
1802                 mov [pxe_udp_read_pkt.rport],ax
1803                 mov ax,[si+tftp_localport]
1804                 mov [pxe_udp_read_pkt.lport],ax
1805                 mov di,pxe_udp_read_pkt
1806                 mov bx,PXENV_UDP_READ
1807                 call pxenv
1808                 jnc .recv_ok
1809
1810                 ; No packet, or receive failure
1811                 mov dx,[BIOS_timer]
1812                 pop ax                          ; <F> Old time
1813                 pop cx                          ; <E> Timeout
1814                 cmp ax,dx                       ; Same time -> don't advance timeout
1815                 je .wait_data                   ; Same clock tick
1816                 loop .wait_data                 ; Decrease timeout
1817
1818                 pop bx                          ; <D> Didn't get any, send another ACK
1819                 inc bx
1820                 cmp bx,TimeoutTableEnd
1821                 jb .send_ack
1822                 jmp kaboom                      ; Forget it...
1823
1824 .recv_ok:       pop dx                          ; <F>
1825                 pop cx                          ; <E>
1826
1827                 cmp word [pxe_udp_read_pkt.buffersize],byte 4
1828                 jb .wait_data                   ; Bad size for a DATA packet
1829
1830                 mov bx,[si+tftp_pktbuf]
1831                 cmp word [fs:bx],TFTP_DATA      ; Not a data packet?
1832                 jne .wait_data                  ; Then wait for something else
1833
1834                 mov ax,[si+tftp_lastpkt]
1835                 xchg ah,al                      ; Host byte order
1836                 inc ax                          ; Which packet are we waiting for?
1837                 xchg ah,al                      ; Network byte order
1838                 cmp [fs:bx+2],ax
1839                 je .right_packet
1840
1841                 ; Wrong packet, ACK the packet and then try again
1842                 ; This is presumably because the ACK got lost,
1843                 ; so the server just resent the previous packet
1844                 mov ax,[fs:bx+2]
1845                 call ack_packet
1846                 jmp .send_ok                    ; Reset timeout
1847
1848 .right_packet:  ; It's the packet we want.  We're also EOF if the
1849                 ; size < blocksize
1850
1851                 pop cx                          ; <D> Don't need the retry count anymore
1852
1853                 mov [si+tftp_lastpkt],ax        ; Update last packet number
1854
1855                 movzx ecx,word [pxe_udp_read_pkt.buffersize]
1856                 sub cx,byte 4                   ; Skip TFTP header
1857
1858                 ; Set pointer to data block
1859                 lea ax,[bx+4]                   ; Data past TFTP header
1860                 mov [si+tftp_dataptr],ax
1861
1862                 add [si+tftp_filepos],ecx
1863                 mov [si+tftp_bytesleft],cx
1864
1865                 cmp cx,[si+tftp_blksize]        ; Is it a full block?
1866                 jb .last_block                  ; If not, it's EOF
1867
1868 .ret:
1869                 popad
1870                 pop es
1871                 ret
1872
1873
1874 .last_block:    ; Last block - ACK packet immediately
1875                 mov ax,[fs:bx+2]
1876                 call ack_packet
1877
1878                 ; Make sure we know we are at end of file
1879                 mov eax,[si+tftp_filepos]
1880                 mov [si+tftp_filesize],eax
1881                 mov byte [si+tftp_goteof],1
1882
1883                 jmp .ret
1884
1885 ;
1886 ; TimeoutTable: list of timeouts (in 18.2 Hz timer ticks)
1887 ;
1888 ; This is roughly an exponential backoff...
1889 ;
1890                 section .data
1891 TimeoutTable:
1892                 db 2, 2, 3, 3, 4, 5, 6, 7, 9, 10, 12, 15, 18
1893                 db 21, 26, 31, 37, 44, 53, 64, 77, 92, 110, 132
1894                 db 159, 191, 229, 255, 255, 255, 255
1895 TimeoutTableEnd equ $
1896
1897                 section .text
1898 ;
1899 ; ack_packet:
1900 ;
1901 ; Send ACK packet.  This is a common operation and so is worth canning.
1902 ;
1903 ; Entry:
1904 ;       SI      = TFTP block
1905 ;       AX      = Packet # to ack (network byte order)
1906 ; Exit:
1907 ;       All registers preserved
1908 ;
1909 ; This function uses the pxe_udp_write_pkt but not the packet_buf.
1910 ;
1911 ack_packet:
1912                 pushad
1913                 mov [ack_packet_buf+2],ax       ; Packet number to ack
1914                 mov ax,[si]
1915                 mov [pxe_udp_write_pkt.lport],ax
1916                 mov ax,[si+tftp_remoteport]
1917                 mov [pxe_udp_write_pkt.rport],ax
1918                 mov eax,[si+tftp_remoteip]
1919                 mov [pxe_udp_write_pkt.sip],eax
1920                 xor eax,[MyIP]
1921                 and eax,[Netmask]
1922                 jz .nogw
1923                 mov eax,[Gateway]
1924 .nogw:
1925                 mov [pxe_udp_write_pkt.gip],eax
1926                 mov [pxe_udp_write_pkt.buffer],word ack_packet_buf
1927                 mov [pxe_udp_write_pkt.buffersize], word 4
1928                 mov di,pxe_udp_write_pkt
1929                 mov bx,PXENV_UDP_WRITE
1930                 call pxenv
1931                 popad
1932                 ret
1933
1934 %if GPXE
1935 ;
1936 ; Get a fresh packet from a gPXE socket; expects fs -> pktbuf_seg
1937 ; and ds:si -> socket structure
1938 ;
1939 ; Assumes CS == DS == ES.
1940 ;
1941 get_packet_gpxe:
1942                 mov di,gpxe_file_read
1943
1944                 mov ax,[si+tftp_remoteport]     ; gPXE filehandle
1945                 mov [di+2],ax
1946                 mov ax,[si+tftp_pktbuf]
1947                 mov [di+6],ax
1948                 mov [si+tftp_dataptr],ax
1949                 mov [di+8],fs
1950
1951 .again:
1952                 mov word [di+4],PKTBUF_SIZE
1953                 mov bx,PXENV_FILE_READ
1954                 call pxenv
1955                 jnc .ok                         ; Got data or EOF
1956                 cmp word [di],PXENV_STATUS_TFTP_OPEN    ; == EWOULDBLOCK
1957                 je .again
1958                 jmp kaboom                      ; Otherwise error...
1959
1960 .ok:
1961                 movzx eax,word [di+4]           ; Bytes read
1962                 mov [si+tftp_bytesleft],ax      ; Bytes in buffer
1963                 add [si+tftp_filepos],eax       ; Position in file
1964
1965                 and ax,ax                       ; EOF?
1966                 mov eax,[si+tftp_filepos]
1967
1968                 jnz .got_stuff
1969
1970                 ; We got EOF here, make sure the upper layers know
1971                 mov [si+tftp_filesize],eax
1972
1973 .got_stuff:
1974                 ; If we're done here, close the file
1975                 cmp [si+tftp_filesize],eax
1976                 ja .done                ; Not EOF, there is still data...
1977
1978                 ; Reuse the previous [es:di] structure since the
1979                 ; relevant fields are all the same
1980                 mov byte [si+tftp_goteof],1
1981
1982                 mov bx,PXENV_FILE_CLOSE
1983                 call pxenv
1984                 ; Ignore return...
1985 .done:
1986                 ret
1987 %endif ; GPXE
1988
1989 ;
1990 ; unload_pxe:
1991 ;
1992 ; This function unloads the PXE and UNDI stacks and unclaims
1993 ; the memory.
1994 ;
1995 unload_pxe:
1996                 cmp byte [KeepPXE],0            ; Should we keep PXE around?
1997                 jne reset_pxe
1998
1999                 push ds
2000                 push es
2001
2002                 mov ax,cs
2003                 mov ds,ax
2004                 mov es,ax
2005
2006                 mov si,new_api_unload
2007                 cmp byte [APIVer+1],2           ; Major API version >= 2?
2008                 jae .new_api
2009                 mov si,old_api_unload
2010 .new_api:
2011
2012 .call_loop:     xor ax,ax
2013                 lodsb
2014                 and ax,ax
2015                 jz .call_done
2016                 xchg bx,ax
2017                 mov di,pxe_unload_stack_pkt
2018                 push di
2019                 xor ax,ax
2020                 mov cx,pxe_unload_stack_pkt_len >> 1
2021                 rep stosw
2022                 pop di
2023                 call pxenv
2024                 jc .cant_free
2025                 mov ax,word [pxe_unload_stack_pkt.status]
2026                 cmp ax,PXENV_STATUS_SUCCESS
2027                 jne .cant_free
2028                 jmp .call_loop
2029
2030 .call_done:
2031                 mov bx,0FF00h
2032
2033                 mov dx,[RealBaseMem]
2034                 cmp dx,[BIOS_fbm]               ; Sanity check
2035                 jna .cant_free
2036                 inc bx
2037
2038                 ; Check that PXE actually unhooked the INT 1Ah chain
2039                 movzx eax,word [4*0x1a]
2040                 movzx ecx,word [4*0x1a+2]
2041                 shl ecx,4
2042                 add eax,ecx
2043                 shr eax,10
2044                 cmp ax,dx                       ; Not in range
2045                 jae .ok
2046                 cmp ax,[BIOS_fbm]
2047                 jae .cant_free
2048                 ; inc bx
2049
2050 .ok:
2051                 mov [BIOS_fbm],dx
2052 .pop_ret:
2053                 pop es
2054                 pop ds
2055                 ret
2056
2057 .cant_free:
2058                 mov si,cant_free_msg
2059                 call writestr_early
2060                 push ax
2061                 xchg bx,ax
2062                 call writehex4
2063                 mov al,'-'
2064                 call writechr
2065                 pop ax
2066                 call writehex4
2067                 mov al,'-'
2068                 call writechr
2069                 mov eax,[4*0x1a]
2070                 call writehex8
2071                 call crlf
2072                 jmp .pop_ret
2073
2074                 ; We want to keep PXE around, but still we should reset
2075                 ; it to the standard bootup configuration
2076 reset_pxe:
2077                 push es
2078                 push cs
2079                 pop es
2080                 mov bx,PXENV_UDP_CLOSE
2081                 mov di,pxe_udp_close_pkt
2082                 call pxenv
2083                 pop es
2084                 ret
2085
2086 ;
2087 ; gendotquad
2088 ;
2089 ; Take an IP address (in network byte order) in EAX and
2090 ; output a dotted quad string to ES:DI.
2091 ; DI points to terminal null at end of string on exit.
2092 ;
2093 gendotquad:
2094                 push eax
2095                 push cx
2096                 mov cx,4
2097 .genchar:
2098                 push eax
2099                 cmp al,10               ; < 10?
2100                 jb .lt10                ; If so, skip first 2 digits
2101
2102                 cmp al,100              ; < 100
2103                 jb .lt100               ; If so, skip first digit
2104
2105                 aam 100
2106                 ; Now AH = 100-digit; AL = remainder
2107                 add ah,'0'
2108                 mov [es:di],ah
2109                 inc di
2110
2111 .lt100:
2112                 aam 10
2113                 ; Now AH = 10-digit; AL = remainder
2114                 add ah,'0'
2115                 mov [es:di],ah
2116                 inc di
2117
2118 .lt10:
2119                 add al,'0'
2120                 stosb
2121                 mov al,'.'
2122                 stosb
2123                 pop eax
2124                 ror eax,8       ; Move next char into LSB
2125                 loop .genchar
2126                 dec di
2127                 mov [es:di], byte 0
2128                 pop cx
2129                 pop eax
2130                 ret
2131 ;
2132 ; uchexbytes/lchexbytes
2133 ;
2134 ; Take a number of bytes in memory and convert to upper/lower-case
2135 ; hexadecimal
2136 ;
2137 ; Input:
2138 ;       DS:SI   = input bytes
2139 ;       ES:DI   = output buffer
2140 ;       CX      = number of bytes
2141 ; Output:
2142 ;       DS:SI   = first byte after
2143 ;       ES:DI   = first byte after
2144 ;       CX = 0
2145 ;
2146 ; Trashes AX, DX
2147 ;
2148
2149 lchexbytes:
2150         mov dl,'a'-'9'-1
2151         jmp xchexbytes
2152 uchexbytes:
2153         mov dl,'A'-'9'-1
2154 xchexbytes:
2155 .loop:
2156         lodsb
2157         mov ah,al
2158         shr al,4
2159         call .outchar
2160         mov al,ah
2161         call .outchar
2162         loop .loop
2163         ret
2164 .outchar:
2165         and al,0Fh
2166         add al,'0'
2167         cmp al,'9'
2168         jna .done
2169         add al,dl
2170 .done:
2171         stosb
2172         ret
2173
2174 ;
2175 ; pxe_get_cached_info
2176 ;
2177 ; Get a DHCP packet from the PXE stack into the trackbuf.
2178 ;
2179 ; Input:
2180 ;       DL = packet type
2181 ; Output:
2182 ;       CX = buffer size
2183 ;
2184 ; Assumes CS == DS == ES.
2185 ;
2186 pxe_get_cached_info:
2187                 pushad
2188                 mov al,' '
2189                 call writechr
2190                 mov al,dl
2191                 call writehex2
2192                 mov di,pxe_bootp_query_pkt
2193                 push di
2194                 xor ax,ax
2195                 stosw           ; Status
2196                 movzx ax,dl
2197                 stosw           ; Packet type
2198                 mov ax,trackbufsize
2199                 stosw           ; Buffer size
2200                 mov ax,trackbuf
2201                 stosw           ; Buffer offset
2202                 xor ax,ax
2203                 stosw           ; Buffer segment
2204
2205                 pop di          ; DI -> parameter set
2206                 mov bx,PXENV_GET_CACHED_INFO
2207                 call pxenv
2208                 jc .err
2209
2210                 popad
2211                 mov cx,[pxe_bootp_query_pkt.buffersize]
2212                 ret
2213
2214 .err:
2215                 mov si,err_pxefailed
2216                 call writestr_early
2217                 call writehex4
2218                 call crlf
2219                 jmp kaboom
2220
2221                 section .data
2222 get_packet_msg  db 'Getting cached packet', 0
2223
2224                 section .text
2225 ;
2226 ; ip_ok
2227 ;
2228 ; Tests an IP address in EAX for validity; return with ZF=1 for bad.
2229 ; We used to refuse class E, but class E addresses are likely to become
2230 ; assignable unicast addresses in the near future.
2231 ;
2232 ip_ok:
2233                 push ax
2234                 cmp eax,-1              ; Refuse the all-ones address
2235                 jz .out
2236                 and al,al               ; Refuse network zero
2237                 jz .out
2238                 cmp al,127              ; Refuse loopback
2239                 jz .out
2240                 and al,0F0h
2241                 cmp al,224              ; Refuse class D
2242 .out:
2243                 pop ax
2244                 ret
2245
2246 ;
2247 ; parse_dhcp
2248 ;
2249 ; Parse a DHCP packet.  This includes dealing with "overloaded"
2250 ; option fields (see RFC 2132, section 9.3)
2251 ;
2252 ; This should fill in the following global variables, if the
2253 ; information is present:
2254 ;
2255 ; MyIP          - client IP address
2256 ; ServerIP      - boot server IP address
2257 ; Netmask       - network mask
2258 ; Gateway       - default gateway router IP
2259 ; BootFile      - boot file name
2260 ; DNSServers    - DNS server IPs
2261 ; LocalDomain   - Local domain name
2262 ; MACLen, MAC   - Client identifier, if MACLen == 0
2263 ;
2264 ; This assumes the DHCP packet is in "trackbuf" and the length
2265 ; of the packet in in CX on entry.
2266 ;
2267
2268 parse_dhcp:
2269                 mov byte [OverLoad],0           ; Assume no overload
2270                 mov eax, [trackbuf+bootp.yip]
2271                 call ip_ok
2272                 jz .noyip
2273                 mov [MyIP], eax
2274 .noyip:
2275                 mov eax, [trackbuf+bootp.sip]
2276                 and eax, eax
2277                 call ip_ok
2278                 jz .nosip
2279                 mov [ServerIP], eax
2280 .nosip:
2281                 sub cx, bootp.options
2282                 jbe .nooptions
2283                 mov si, trackbuf+bootp.option_magic
2284                 lodsd
2285                 cmp eax, BOOTP_OPTION_MAGIC
2286                 jne .nooptions
2287                 call parse_dhcp_options
2288 .nooptions:
2289                 mov si, trackbuf+bootp.bootfile
2290                 test byte [OverLoad],1
2291                 jz .nofileoverload
2292                 mov cx,128
2293                 call parse_dhcp_options
2294                 jmp short .parsed_file
2295 .nofileoverload:
2296                 cmp byte [si], 0
2297                 jz .parsed_file                 ; No bootfile name
2298                 mov di,BootFile
2299                 mov cx,32
2300                 rep movsd
2301                 xor al,al
2302                 stosb                           ; Null-terminate
2303 .parsed_file:
2304                 mov si, trackbuf+bootp.sname
2305                 test byte [OverLoad],2
2306                 jz .nosnameoverload
2307                 mov cx,64
2308                 call parse_dhcp_options
2309 .nosnameoverload:
2310                 ret
2311
2312 ;
2313 ; Parse a sequence of DHCP options, pointed to by DS:SI; the field
2314 ; size is CX -- some DHCP servers leave option fields unterminated
2315 ; in violation of the spec.
2316 ;
2317 ; For parse_some_dhcp_options, DH contains the minimum value for
2318 ; the option to recognize -- this is used to restrict parsing to
2319 ; PXELINUX-specific options only.
2320 ;
2321 parse_dhcp_options:
2322                 xor dx,dx
2323
2324 parse_some_dhcp_options:
2325 .loop:
2326                 and cx,cx
2327                 jz .done
2328
2329                 lodsb
2330                 dec cx
2331                 jz .done        ; Last byte; must be PAD, END or malformed
2332                 cmp al, 0       ; PAD option
2333                 je .loop
2334                 cmp al,255      ; END option
2335                 je .done
2336
2337                 ; Anything else will have a length field
2338                 mov dl,al       ; DL <- option number
2339                 xor ax,ax
2340                 lodsb           ; AX <- option length
2341                 dec cx
2342                 sub cx,ax       ; Decrement bytes left counter
2343                 jb .done        ; Malformed option: length > field size
2344
2345                 cmp dl,dh       ; Is the option value valid?
2346                 jb .opt_done
2347
2348                 mov bx,dhcp_option_list
2349 .find_option:
2350                 cmp bx,dhcp_option_list_end
2351                 jae .opt_done
2352                 cmp dl,[bx]
2353                 je .found_option
2354                 add bx,3
2355                 jmp .find_option
2356 .found_option:
2357                 pushad
2358                 call [bx+1]
2359                 popad
2360
2361 ; Fall through
2362                 ; Unknown option.  Skip to the next one.
2363 .opt_done:
2364                 add si,ax
2365                 jmp .loop
2366 .done:
2367                 ret
2368
2369                 section .data
2370 dhcp_option_list:
2371                 section .text
2372
2373 %macro dopt 2
2374                 section .data
2375                 db %1
2376                 dw dopt_%2
2377                 section .text
2378 dopt_%2:
2379 %endmacro
2380
2381 ;
2382 ; Parse individual DHCP options.  SI points to the option data and
2383 ; AX to the option length.  DL contains the option number.
2384 ; All registers are saved around the routine.
2385 ;
2386         dopt 1, subnet_mask
2387                 mov ebx,[si]
2388                 mov [Netmask],ebx
2389                 ret
2390
2391         dopt 3, router
2392                 mov ebx,[si]
2393                 mov [Gateway],ebx
2394                 ret
2395
2396         dopt 6, dns_servers
2397                 mov cx,ax
2398                 shr cx,2
2399                 cmp cl,DNS_MAX_SERVERS
2400                 jna .oklen
2401                 mov cl,DNS_MAX_SERVERS
2402 .oklen:
2403                 mov di,DNSServers
2404                 rep movsd
2405                 mov [LastDNSServer],di
2406                 ret
2407
2408         dopt 15, local_domain
2409                 mov bx,si
2410                 add bx,ax
2411                 xor ax,ax
2412                 xchg [bx],al    ; Zero-terminate option
2413                 mov di,LocalDomain
2414                 call dns_mangle ; Convert to DNS label set
2415                 mov [bx],al     ; Restore ending byte
2416                 ret
2417
2418         dopt 43, vendor_encaps
2419                 mov dh,208      ; Only recognize PXELINUX options
2420                 mov cx,ax       ; Length of option = max bytes to parse
2421                 call parse_some_dhcp_options    ; Parse recursive structure
2422                 ret
2423
2424         dopt 52, option_overload
2425                 mov bl,[si]
2426                 mov [OverLoad],bl
2427                 ret
2428
2429         dopt 54, server
2430                 mov eax,[si]
2431                 cmp dword [ServerIP],0
2432                 jne .skip               ; Already have a next server IP
2433                 call ip_ok
2434                 jz .skip
2435                 mov [ServerIP],eax
2436 .skip:          ret
2437
2438         dopt 61, client_identifier
2439                 cmp ax,MAC_MAX          ; Too long?
2440                 ja .skip
2441                 cmp ax,2                ; Too short?
2442                 jb .skip
2443                 cmp [MACLen],ah         ; Only do this if MACLen == 0
2444                 jne .skip
2445                 push ax
2446                 lodsb                   ; Client identifier type
2447                 cmp al,[MACType]
2448                 pop ax
2449                 jne .skip               ; Client identifier is not a MAC
2450                 dec ax
2451                 mov [MACLen],al
2452                 mov di,MAC
2453                 jmp dhcp_copyoption
2454 .skip:          ret
2455
2456         dopt 67, bootfile_name
2457                 mov di,BootFile
2458                 jmp dhcp_copyoption
2459
2460         dopt 97, uuid_client_identifier
2461                 cmp ax,17               ; type byte + 16 bytes UUID
2462                 jne .skip
2463                 mov dl,[si]             ; Must have type 0 == UUID
2464                 or dl,[HaveUUID]        ; Capture only the first instance
2465                 jnz .skip
2466                 mov byte [HaveUUID],1   ; Got UUID
2467                 mov di,UUIDType
2468                 jmp dhcp_copyoption
2469 .skip:          ret
2470
2471         dopt 209, pxelinux_configfile
2472                 mov di,ConfigName
2473                 or byte [DHCPMagic],2   ; Got config file
2474                 jmp dhcp_copyoption
2475
2476         dopt 210, pxelinux_pathprefix
2477                 mov di,PathPrefix
2478                 or byte [DHCPMagic],4   ; Got path prefix
2479                 jmp dhcp_copyoption
2480
2481         dopt 211, pxelinux_reboottime
2482                 cmp al,4
2483                 jne .done
2484                 mov ebx,[si]
2485                 xchg bl,bh              ; Convert to host byte order
2486                 rol ebx,16
2487                 xchg bl,bh
2488                 mov [RebootTime],ebx
2489                 or byte [DHCPMagic],8   ; Got RebootTime
2490 .done:          ret
2491
2492                 ; Common code for copying an option verbatim
2493                 ; Copies the option into ES:DI and null-terminates it.
2494                 ; Returns with AX=0 and SI past the option.
2495 dhcp_copyoption:
2496                 xchg cx,ax      ; CX <- option length
2497                 rep movsb
2498                 xchg cx,ax      ; AX <- 0
2499                 stosb           ; Null-terminate
2500                 ret
2501
2502                 section .data
2503 dhcp_option_list_end:
2504                 section .text
2505
2506                 section .data
2507 HaveUUID        db 0
2508 uuid_dashes     db 4,2,2,2,6,0  ; Bytes per UUID dashed section
2509                 section .text
2510
2511 ;
2512 ; genipopt
2513 ;
2514 ; Generate an ip=<client-ip>:<boot-server-ip>:<gw-ip>:<netmask>
2515 ; option into IPOption based on a DHCP packet in trackbuf.
2516 ; Assumes CS == DS == ES.
2517 ;
2518 genipopt:
2519                 pushad
2520                 mov di,IPOption
2521                 mov eax,'ip='
2522                 stosd
2523                 dec di
2524                 mov eax,[MyIP]
2525                 call gendotquad
2526                 mov al,':'
2527                 stosb
2528                 mov eax,[ServerIP]
2529                 call gendotquad
2530                 mov al,':'
2531                 stosb
2532                 mov eax,[Gateway]
2533                 call gendotquad
2534                 mov al,':'
2535                 stosb
2536                 mov eax,[Netmask]
2537                 call gendotquad ; Zero-terminates its output
2538                 popad
2539                 ret
2540
2541 ; -----------------------------------------------------------------------------
2542 ;  Common modules
2543 ; -----------------------------------------------------------------------------
2544
2545 %include "getc.inc"             ; getc et al
2546 %include "conio.inc"            ; Console I/O
2547 %include "writestr.inc"         ; String output
2548 writestr_early  equ writestr
2549 %include "writehex.inc"         ; Hexadecimal output
2550 %include "configinit.inc"       ; Initialize configuration
2551 %include "parseconfig.inc"      ; High-level config file handling
2552 %include "parsecmd.inc"         ; Low-level config file handling
2553 %include "bcopy32.inc"          ; 32-bit bcopy
2554 %include "loadhigh.inc"         ; Load a file into high memory
2555 %include "font.inc"             ; VGA font stuff
2556 %include "graphics.inc"         ; VGA graphics
2557 %include "highmem.inc"          ; High memory sizing
2558 %include "strcpy.inc"           ; strcpy()
2559 %include "rawcon.inc"           ; Console I/O w/o using the console functions
2560 %include "dnsresolv.inc"        ; DNS resolver
2561 %include "idle.inc"             ; Idle handling
2562 %include "pxeidle.inc"          ; PXE-specific idle mechanism
2563 %include "adv.inc"              ; Auxillary Data Vector
2564
2565 ; -----------------------------------------------------------------------------
2566 ;  Begin data section
2567 ; -----------------------------------------------------------------------------
2568
2569                 section .data
2570
2571 copyright_str   db ' Copyright (C) 1994-'
2572                 asciidec YEAR
2573                 db ' H. Peter Anvin et al', CR, LF, 0
2574 err_bootfailed  db CR, LF, 'Boot failed: press a key to retry, or wait for reset...', CR, LF, 0
2575 bailmsg         equ err_bootfailed
2576 err_nopxe       db "No !PXE or PXENV+ API found; we're dead...", CR, LF, 0
2577 err_pxefailed   db 'PXE API call failed, error ', 0
2578 err_udpinit     db 'Failed to initialize UDP stack', CR, LF, 0
2579 err_noconfig    db 'Unable to locate configuration file', CR, LF, 0
2580 err_damage      db 'TFTP server sent an incomprehesible reply', CR, LF, 0
2581 found_pxenv     db 'Found PXENV+ structure', CR, LF, 0
2582 apiver_str      db 'PXE API version is ',0
2583 pxeentry_msg    db '!PXE entry point found (we hope) at ', 0
2584 pxenventry_msg  db 'PXENV+ entry point found (we hope) at ', 0
2585 viaplan_msg     db ' via plan '
2586 plan            db 'A', CR, LF, 0
2587 trymempxe_msg   db 'Scanning memory for !PXE structure... ', 0
2588 trymempxenv_msg db 'Scanning memory for PXENV+ structure... ', 0
2589 undi_data_msg   db 'UNDI data segment at ',0
2590 undi_code_msg   db 'UNDI code segment at ',0
2591 len_msg         db ' len ', 0
2592 cant_free_msg   db 'Failed to free base memory, error ', 0
2593 notfound_msg    db 'not found', CR, LF, 0
2594 myipaddr_msg    db 'My IP address seems to be ',0
2595 tftpprefix_msg  db 'TFTP prefix: ', 0
2596 localboot_msg   db 'Booting from local disk...', CR, LF, 0
2597 trying_msg      db 'Trying to load: ', 0
2598 default_str     db 'default', 0
2599 syslinux_banner db CR, LF, 'PXELINUX ', VERSION_STR, ' ', DATE_STR, ' ', 0
2600 cfgprefix       db 'pxelinux.cfg/'              ; No final null!
2601 cfgprefix_len   equ ($-cfgprefix)
2602
2603 ; This one we make ourselves
2604 bootif_str      db 'BOOTIF='
2605 bootif_str_len  equ $-bootif_str
2606 ;
2607 ; Config file keyword table
2608 ;
2609 %include "keywords.inc"
2610
2611 ;
2612 ; Extensions to search for (in *forward* order).
2613 ; (.bs and .bss are disabled for PXELINUX, since they are not supported)
2614 ;
2615                 alignz 4
2616 exten_table:    db '.cbt'               ; COMBOOT (specific)
2617                 db '.0', 0, 0           ; PXE bootstrap program
2618                 db '.com'               ; COMBOOT (same as DOS)
2619                 db '.c32'               ; COM32
2620 exten_table_end:
2621                 dd 0, 0                 ; Need 8 null bytes here
2622
2623 ;
2624 ; PXE unload sequences
2625 ;
2626 new_api_unload:
2627                 db PXENV_UDP_CLOSE
2628                 db PXENV_UNDI_SHUTDOWN
2629                 db PXENV_UNLOAD_STACK
2630                 db PXENV_STOP_UNDI
2631                 db 0
2632 old_api_unload:
2633                 db PXENV_UDP_CLOSE
2634                 db PXENV_UNDI_SHUTDOWN
2635                 db PXENV_UNLOAD_STACK
2636                 db PXENV_UNDI_CLEANUP
2637                 db 0
2638
2639 ;
2640 ; PXE query packets partially filled in
2641 ;
2642                 section .bss
2643 pxe_bootp_query_pkt:
2644 .status:        resw 1                  ; Status
2645 .packettype:    resw 1                  ; Boot server packet type
2646 .buffersize:    resw 1                  ; Packet size
2647 .buffer:        resw 2                  ; seg:off of buffer
2648 .bufferlimit:   resw 1                  ; Unused
2649
2650                 section .data
2651 pxe_udp_open_pkt:
2652 .status:        dw 0                    ; Status
2653 .sip:           dd 0                    ; Source (our) IP
2654
2655 pxe_udp_close_pkt:
2656 .status:        dw 0                    ; Status
2657
2658 pxe_udp_write_pkt:
2659 .status:        dw 0                    ; Status
2660 .sip:           dd 0                    ; Server IP
2661 .gip:           dd 0                    ; Gateway IP
2662 .lport:         dw 0                    ; Local port
2663 .rport:         dw 0                    ; Remote port
2664 .buffersize:    dw 0                    ; Size of packet
2665 .buffer:        dw 0, 0                 ; seg:off of buffer
2666
2667 pxe_udp_read_pkt:
2668 .status:        dw 0                    ; Status
2669 .sip:           dd 0                    ; Source IP
2670 .dip:           dd 0                    ; Destination (our) IP
2671 .rport:         dw 0                    ; Remote port
2672 .lport:         dw 0                    ; Local port
2673 .buffersize:    dw 0                    ; Max packet size
2674 .buffer:        dw 0, 0                 ; seg:off of buffer
2675
2676 %if GPXE
2677
2678 gpxe_file_api_check:
2679 .status:        dw 0                    ; Status
2680 .size:          dw 20                   ; Size in bytes
2681 .magic:         dd 0x91d447b2           ; Magic number
2682 .provider:      dd 0
2683 .apimask:       dd 0
2684 .flags:         dd 0
2685
2686 gpxe_file_open:
2687 .status:        dw 0                    ; Status
2688 .filehandle:    dw 0                    ; FileHandle
2689 .filename:      dd 0                    ; seg:off of FileName
2690 .reserved:      dd 0
2691
2692 gpxe_get_file_size:
2693 .status:        dw 0                    ; Status
2694 .filehandle:    dw 0                    ; FileHandle
2695 .filesize:      dd 0                    ; FileSize
2696
2697 gpxe_file_read:
2698 .status:        dw 0                    ; Status
2699 .filehandle:    dw 0                    ; FileHandle
2700 .buffersize:    dw 0                    ; BufferSize
2701 .buffer:        dd 0                    ; seg:off of buffer
2702
2703 %endif ; GPXE
2704
2705 ;
2706 ; Misc initialized (data) variables
2707 ;
2708                 alignz 4
2709 BaseStack       dd StackBuf             ; ESP of base stack
2710                 dw 0                    ; SS of base stack
2711 NextSocket      dw 49152                ; Counter for allocating socket numbers
2712 KeepPXE         db 0                    ; Should PXE be kept around?
2713
2714 ;
2715 ; TFTP commands
2716 ;
2717 tftp_tail       db 'octet', 0                           ; Octet mode
2718 tsize_str       db 'tsize' ,0                           ; Request size
2719 tsize_len       equ ($-tsize_str)
2720                 db '0', 0
2721 blksize_str     db 'blksize', 0                         ; Request large blocks
2722 blksize_len     equ ($-blksize_str)
2723                 asciidec TFTP_LARGEBLK
2724                 db 0
2725 tftp_tail_len   equ ($-tftp_tail)
2726
2727                 alignz 2
2728 ;
2729 ; Options negotiation parsing table (string pointer, string len, offset
2730 ; into socket structure)
2731 ;
2732 tftp_opt_table:
2733                 dw tsize_str, tsize_len, tftp_filesize
2734                 dw blksize_str, blksize_len, tftp_blksize
2735 tftp_opts       equ ($-tftp_opt_table)/6
2736
2737 ;
2738 ; Error packet to return on TFTP protocol error
2739 ;
2740 tftp_proto_err  dw TFTP_ERROR                           ; ERROR packet
2741                 dw TFTP_EUNDEF                          ; ERROR 0: undefined
2742                 db 'TFTP protocol error', 0             ; Error message
2743 tftp_proto_err_len equ ($-tftp_proto_err)
2744
2745                 alignz 4
2746 ack_packet_buf: dw TFTP_ACK, 0                          ; TFTP ACK packet
2747
2748 ;
2749 ; IP information (initialized to "unknown" values)
2750 MyIP            dd 0                    ; My IP address
2751 ServerIP        dd 0                    ; IP address of boot server
2752 Netmask         dd 0                    ; Netmask of this subnet
2753 Gateway         dd 0                    ; Default router
2754 ServerPort      dw TFTP_PORT            ; TFTP server port
2755
2756 ;
2757 ; Variables that are uninitialized in SYSLINUX but initialized here
2758 ;
2759                 alignz 4
2760 BufSafe         dw trackbufsize/TFTP_BLOCKSIZE  ; Clusters we can load into trackbuf
2761 BufSafeBytes    dw trackbufsize         ; = how many bytes?
2762 %ifndef DEPEND
2763 %if ( trackbufsize % TFTP_BLOCKSIZE ) != 0
2764 %error trackbufsize must be a multiple of TFTP_BLOCKSIZE
2765 %endif
2766 %endif