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