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