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