1 ; -*- fundamental -*- (asm-mode sucks)
2 ; ****************************************************************************
6 ; A program to boot Linux kernels off an MS-DOS formatted floppy disk. This
7 ; functionality is good to have for installation floppies, where it may
8 ; be hard to find a functional Linux system to run LILO off.
10 ; This program allows manipulation of the disk to take place entirely
11 ; from MS-LOSS, and can be especially useful in conjunction with the
14 ; Copyright 1994-2009 H. Peter Anvin - All Rights Reserved
15 ; Copyright 2009 Intel Corporation; author: H. Peter Anvin
17 ; This program is free software; you can redistribute it and/or modify
18 ; it under the terms of the GNU General Public License as published by
19 ; the Free Software Foundation, Inc., 53 Temple Place Ste 330,
20 ; Boston MA 02111-1307, USA; either version 2 of the License, or
21 ; (at your option) any later version; incorporated herein by reference.
23 ; ****************************************************************************
31 ; Some semi-configurable constants... change on your own risk.
34 FILENAME_MAX_LG2 equ 6 ; log2(Max filename size Including final null)
35 FILENAME_MAX equ (1<<FILENAME_MAX_LG2) ; Max mangled filename size
36 NULLFILE equ 0 ; First char space == null filename
37 NULLOFFSET equ 0 ; Position in which to look
38 retry_count equ 16 ; How patient are we with the disk?
39 %assign HIGHMEM_SLOP 0 ; Avoid this much memory near the top
40 LDLINUX_MAGIC equ 0x3eb202fe ; A random number to identify ourselves with
42 MAX_OPEN_LG2 equ 6 ; log2(Max number of open files)
43 MAX_OPEN equ (1 << MAX_OPEN_LG2)
46 SECTOR_SIZE equ (1 << SECTOR_SHIFT)
49 DIRENT_SIZE equ (1 << DIRENT_SHIFT)
51 ROOT_DIR_WORD equ 0x002F
54 ; The following structure is used for "virtual kernels"; i.e. LILO-style
55 ; option labels. The options we permit here are `kernel' and `append
56 ; Since there is no room in the bottom 64K for all of these, we
57 ; stick them in high memory and copy them down before we need them.
60 vk_vname: resb FILENAME_MAX ; Virtual name **MUST BE FIRST!**
61 vk_rname: resb FILENAME_MAX ; Real name
63 vk_type: resb 1 ; Type of file
65 vk_append: resb max_cmd_len+1 ; Command line
67 vk_end: equ $ ; Should be <= vk_size
71 ; File structure. This holds the information for each currently open file.
74 file_sector resd 1 ; Sector pointer (0 = structure free)
75 file_bytesleft resd 1 ; Number of bytes left
76 file_left resd 1 ; Number of sectors left
81 ; Structure for codepage files
84 .magic resd 2 ; 8-byte magic number
85 .reserved resd 6 ; Reserved for future use
86 .uppercase resb 256 ; Internal upper-case table
87 .unicode resw 256 ; Unicode matching table
88 .unicode_alt resw 256 ; Alternate Unicode matching table
92 %if (open_file_t_size & (open_file_t_size-1))
93 %error "open_file_t is not a power of 2"
97 ; ---------------------------------------------------------------------------
99 ; ---------------------------------------------------------------------------
102 ; Memory below this point is reserved for the BIOS and the MBR
105 trackbufsize equ 8192
106 trackbuf resb trackbufsize ; Track buffer goes here
111 FAT resd 1 ; Location of (first) FAT
112 RootDirArea resd 1 ; Location of root directory area
113 RootDir resd 1 ; Location of root directory proper
114 DataArea resd 1 ; Location of data area
115 RootDirSize resd 1 ; Root dir size in sectors
116 TotalSectors resd 1 ; Total number of sectors
117 ClustSize resd 1 ; Bytes/cluster
118 ClustMask resd 1 ; Sectors/cluster - 1
119 CopySuper resb 1 ; Distinguish .bs versus .bss
120 ClustShift resb 1 ; Shift count for sectors/cluster
121 ClustByteShift resb 1 ; Shift count for bytes/cluster
123 alignb open_file_t_size
124 Files resb MAX_OPEN*open_file_t_size
127 ; Common bootstrap code for disk-based derivatives
129 %include "diskstart.inc"
132 ; Common initialization code
135 %include "cpuinit.inc"
138 ; Compute some information about this filesystem.
141 ; First, generate the map of regions
146 mov edx,[bsHugeSectors]
148 mov [TotalSectors],edx
150 mov eax,[bxResSectors]
151 mov [FAT],eax ; Beginning of FAT
155 mov edx,[bootsec+36] ; FAT32 BPB_FATsz32
159 mov [RootDirArea],eax ; Beginning of root directory
160 mov [RootDir],eax ; For FAT12/16 == root dir location
162 mov edx,[bxRootDirEnts]
163 add dx,SECTOR_SIZE/32-1
164 shr dx,SECTOR_SHIFT-5
165 mov [RootDirSize],edx
167 mov [DataArea],eax ; Beginning of data area
169 ; Next, generate a cluster size shift count and mask
170 mov eax,[bxSecPerClust]
175 mov [ClustByteShift],cl
184 ; FAT12, FAT16 or FAT28^H^H32? This computation is fscking ridiculous.
187 mov eax,[TotalSectors]
189 shr eax,cl ; cl == ClustShift
190 mov cl,nextcluster_fat12-(nextcluster+2)
191 cmp eax,4085 ; FAT12 limit
193 mov cl,nextcluster_fat16-(nextcluster+2)
194 cmp eax,65525 ; FAT16 limit
197 ; FAT32, root directory is a cluster chain
200 mov eax,[bootsec+44] ; Root directory cluster
205 mov cl,nextcluster_fat28-(nextcluster+2)
206 mov byte [SuperSize],superblock_len_fat32
208 mov byte [nextcluster+1],cl
212 ; Initialize the metadata cache
217 ; Now, everything is "up and running"... patch kaboom for more
218 ; verbosity and using the full screen system
229 ; Now we're all set to start with our *real* business. First load the
230 ; configuration file (if any) and parse it.
232 ; In previous versions I avoided using 32-bit registers because of a
233 ; rumour some BIOSes clobbered the upper half of 32-bit registers at
234 ; random. I figure, though, that if there are any of those still left
235 ; they probably won't be trying to install Linux on them...
237 ; The code is still ripe with 16-bitisms, though. Not worth the hassle
238 ; to take'm out. In fact, we may want to put them back if we're going
239 ; to boot ELKS at some point.
243 ; Load configuration file
245 mov si,config_name ; Save configuration file name
248 mov word [CurrentDirName],ROOT_DIR_WORD ; Write '/',0 to the CurrentDirName
250 mov eax,[RootDir] ; Make the root directory ...
251 mov [CurrentDir],eax ; ... the current directory
271 mov di,CurrentDirName
272 ; This is inefficient as it will copy more than needed
273 ; but not by too much
275 mov ax,config_name ;Cut it down
278 mov di,CurrentDirName
282 mov eax,[PrevDir] ; Make the directory with syslinux.cfg ...
283 mov [CurrentDir],eax ; ... the current directory
286 ; Now we have the config file open. Parse the config file and
287 ; run the user interface.
292 ; allocate_file: Allocate a file structure
305 .check: cmp dword [bx], byte 0
307 add bx,open_file_t_size ; ZF = 0
309 ; ZF = 0 if we fell out of the loop
315 ; Allocate then fill a file structure for a directory starting in
318 ; Assumes DS == ES == CS.
333 mov [si+file_sector],eax ; Current sector
334 mov dword [si+file_bytesleft],0 ; Current offset
335 mov [si+file_left],eax ; Beginning sector
341 xor eax,eax ; ZF <- 1
346 ; Search a specific directory for a pre-mangled filename in
347 ; MangledBuf, in the directory starting in sector EAX.
349 ; NOTE: This file considers finding a zero-length file an
350 ; error. This is so we don't have to deal with that special
351 ; case elsewhere in the program (most loops have the test
354 ; Assumes DS == ES == CS.
359 ; EAX = file length (MAY BE ZERO!)
360 ; DL = file attribute
364 ; EAX, SI, DX clobbered
378 ; Compute the value of a possible VFAT longname
379 ; "last" entry (which, of course, comes first...)
394 ; EAX <- directory sector to scan
396 ; GS:SI now points to this sector
398 mov cx,SECTOR_SIZE/32 ; 32 == directory entry size
401 jz .failure ; Hit directory high water mark
402 cmp word [gs:si+11],0Fh ; Long filename
405 ; Process a VFAT long entry
413 ; Get the initial checksum value
418 jne .not_us ; Checksum mismatch
421 jz .not_us ; Can't be zero...
423 mov [VFATNext],al ; Optimistically...
439 movzx bx,byte [bx+di]
441 cmp ax,[cp_unicode+bx] ; Primary case
443 cmp ax,[cp_unicode_alt+bx] ; Alternate case
448 ; *AT* the end we should have 0x0000, *AFTER* the end
449 ; we should have 0xFFFF...
451 inc ax ; 0xFFFF -> 0x0000
462 .vfat_adj_add3: inc si
463 .vfat_adj_add2: inc si
464 .vfat_adj_add1: inc si
467 ; Okay, if we got here we had a match on this particular
468 ; entry... live to see another one.
479 test byte [gs:si+11],8 ; Ignore volume labels
482 cmp byte [VFATNext],0 ; Do we have a longname match?
485 ; We already have a VFAT longname match, however,
486 ; the match is only valid if the checksum matches
501 je .found ; Got a match on longname
503 .no_long_match: ; Look for a shortname match
515 ; Reset the VFAT matching state machine
524 jnc .scansector ; CF is set if we're at end
526 ; If we get here, we failed
533 xor eax,eax ; ZF <- 1
536 mov eax,[gs:si+28] ; File size
537 add eax,SECTOR_SIZE-1
539 mov [bx+4],eax ; Sector count
542 mov dx,[gs:si+20] ; High cluster word
544 mov dx,[gs:si+26] ; Low cluster word
548 mov [bx],edx ; Starting sector
550 mov eax,[gs:si+28] ; File length again
551 mov dl,[gs:si+11] ; File attribute
552 mov si,bx ; File pointer...
563 ; Note: we have no use of the first 32 bytes (header),
564 ; nor of the folloing 32 bytes (case mapping of control
565 ; characters), as long as we adjust the offsets appropriately.
566 codepage equ $-(32+32)
567 codepage_data: incbin "codepage.cp",32+32
568 cp_uppercase equ codepage+cp.uppercase
569 cp_unicode equ codepage+cp.unicode
570 cp_unicode_alt equ codepage+cp.unicode_alt
575 ; Input: UCS-2 character in AX
576 ; Output: Single byte character in AL, ZF = 1
577 ; On failure, returns ZF = 0
592 not ax ; Doesn't change the flags!
603 ; Deallocates a file structure (pointer in SI)
609 mov dword [si],0 ; First dword == file_sector
615 ; Deallocates a directory structure (pointer in SI)
621 mov dword [si],0 ; First dword == file_sector
635 ; EAX = file length in bytes
639 ; Assumes CS == DS == ES, and trashes BX and CX.
643 cmp byte [di],'/' ; Root directory?
650 push eax ; <A> Current directory sector
659 xchg si,di ; GRC: si begin; di end[ /]+1
660 pop eax ; <A> Current directory sector
662 ; GRC Here I need to check if di-1 = si which signifies
663 ; we have the desired directory in EAX
664 ; What about where the file name = "."; later
670 mov [PrevDir],eax ; Remember last directory searched
673 call mangle_dos_name ; MangledBuf <- component
676 jz .notfound ; Pathname component missing
678 cmp byte [di-1],'/' ; Do we expect a directory
681 ; Otherwise, it should be a file
683 test dl,18h ; Subdirectory|Volume Label
684 jnz .badfile ; If not a file, it's a bad thing
686 ; SI and EAX are already set
687 mov [si+file_bytesleft],eax
689 add eax,SECTOR_SIZE-1
691 mov [si+file_left],eax ; Sectors left
693 and eax,eax ; EAX != 0
697 ; If we expected a directory, it better be one...
699 test dl,10h ; Subdirectory
703 xchg eax,[si+file_sector] ; Get sector number and free file structure
704 jmp .pathwalk ; Walk the next bit of the path
706 ; Found the desired directory; ZF set but EAX not 0
712 mov [si],eax ; Free file structure
715 xor eax,eax ; Zero out EAX
719 ; readdir: Read one file from a directory
721 ; ES:DI -> String buffer (filename)
722 ; DS:SI -> Pointer to open_file_t
723 ; DS Must be the SYSLINUX Data Segment
725 ; Returns the file's name in the filename string buffer
726 ; EAX returns the file size
727 ; EBX returns the beginning sector (currently without offsetting)
728 ; DL returns the file type
729 ; The directory handle's data is incremented to reflect a name read.
733 push bp ; Using bp to transfer between segment registers
736 push fs ; Using fs to store the current es (from COMBOOT)
743 mov eax,[ds:si+file_sector] ; Current sector
744 mov ebx,[ds:si+file_bytesleft] ; Current offset
750 add si,bx ; Resume last position in sector
751 mov ecx,SECTOR_SIZE ; 0 out high part
753 shr cx,5 ; Number of entries left
757 cmp word [gs:si+11],0Fh ; Long filename
765 .vfat_ln_info: ; Get info about the line that we're on
770 mov ah,1 ; On beginning line
773 .vfat_tail_ln: ; VFAT tail line processing (later in VFAT, head in name)
774 test al,80h ; Invalid data?
776 mov ah,0 ; Not on beginning line
778 jne .vfat_abort ; Is this the entry we need?
784 .vfat_ck_ln: ; Load this line's VFAT CheckSum
787 .vfat_cp_ln: ; Copy VFAT line
788 dec al ; Store the next line we need
789 mov dx,ax ; Use DX to store the progress
790 mov cx,13 ; 13 characters per VFAT DIRENT
792 mul cl ; Offset for DI
793 add di,ax ; Increment DI
794 inc si ; Align to the real characters
796 gs lodsw ; Unicode here!!
797 call ucs2_to_cp ; Convert to local codepage
798 jnz .vfat_abort ; Use short name if character not on codepage
799 stosb ; CAN NOT OVERRIDE es
801 jz .vfat_find_next ; Null-terminated string; don't process more
806 .vfat_adj_add3: inc si
807 .vfat_adj_add2: inc si
808 .vfat_adj_add1: inc si
811 cmp dh,1 ; Is this the first round?
813 .vfat_null_term: ; Need to null-terminate if first line as we rolled over the end
817 .vfat_find_next: ;Find the next part of the name
823 jz .vfat_find_info ; We're done with the name
828 jnc .vfat_entry ; CF is set if we're at end
830 .vfat_find_info: ; Fetch next entry for the size/"INode"
835 jnc .get_info ; CF is set if we're at end
837 .vfat_abort: ; Something went wrong, skip
845 test byte [gs:si+11],8 ; Ignore volume labels //HERE
847 mov edx,eax ;Save current sector
860 loop .short_file_loop
862 .short_skip_bs: ; skip blank spaces in FILENAME (before EXT)
883 mov al,0 ; Null-terminate the short strings
890 mov ebx,[gs:si+28] ; length
891 mov dl,[gs:si+11] ; type
897 jnc .store_sect ; CF is set if we're at end
905 jnc .scanentry ; CF is set if we're at end
913 mov [ds:si+file_sector],eax
914 mov eax,0 ; Now at beginning of new sector
921 pop si ; cx=num remain; SECTOR_SIZE-(cx*32)=cur pos
928 mov [ds:si+file_bytesleft],eax
929 ; "INode" number = ((CurSector-RootSector)*SECTOR_SIZE + Offset)/DIRENT_SIZE)
931 mov eax,[ds:si+file_sector]
937 xchg eax,ebx ; -> EBX=INode, EAX=FileSize
956 PrevDir resd 1 ; Last scanned directory
962 ; kaboom2: once everything is loaded, replace the part of kaboom
963 ; starting with "kaboom.patch" with this part
966 mov si,err_bootfailed
968 cmp byte [kaboom.again+1],18h ; INT 18h version?
972 int 19h ; And try once more to boot...
973 .norge: jmp short .norge ; If int 19h returned; this is the end
977 .noreg: jmp short .noreg ; Nynorsk
980 ; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed
981 ; to by ES:DI; ends on encountering any whitespace.
984 ; This verifies that a filename is < FILENAME_MAX characters,
985 ; doesn't contain whitespace, zero-pads the output buffer,
986 ; and removes trailing dots and redundant slashes, plus changes
987 ; backslashes to forward slashes,
988 ; so "repe cmpsb" can do a compare, and the path-searching routine
989 ; gets a bit of an easier job.
996 mov cx,FILENAME_MAX-1
1001 cmp al,' ' ; If control or space, end
1003 cmp al,'\' ; Backslash?
1005 mov al,'/' ; Change to forward slash
1007 cmp al,ah ; Repeated slash?
1014 .mn_skip: loop .mn_loop
1016 cmp bx,di ; At the beginning of the buffer?
1018 cmp byte [es:di-1],'.' ; Terminal dot?
1020 cmp byte [es:di-1],'/' ; Terminal slash?
1022 .mn_kill: dec di ; If so, remove it
1026 inc cx ; At least one null byte
1027 xor ax,ax ; Zero-fill name
1034 ; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled
1035 ; filename to the conventional representation. This is needed
1036 ; for the BOOT_IMAGE= parameter for the kernel.
1037 ; NOTE: A 13-byte buffer is mandatory, even if the string is
1038 ; known to be shorter.
1040 ; DS:SI -> input mangled file name
1041 ; ES:DI -> output buffer
1043 ; On return, DI points to the first byte after the output name,
1044 ; which is set to a null byte.
1046 unmangle_name: call strcpy
1047 dec di ; Point to final null byte
1052 ; Mangle a DOS filename component pointed to by DS:SI
1053 ; into [MangledBuf]; ends on encountering any whitespace or
1056 ; WARNING: saves pointers into the buffer for longname
1059 ; Assumes CS == DS == ES.
1067 mov cx,11 ; # of bytes to write
1068 mov bx,cp_uppercase ; Case-conversion table
1071 cmp al,' ' ; If control or space, end
1073 cmp al,'/' ; Slash, too
1075 cmp al,'.' ; Period -> space-fill
1077 xlatb ; Convert to upper case
1078 mov ah,cl ; If the first byte (only!)...
1079 cmp ax,0BE5h ; ... equals E5 hex ...
1081 mov al,05h ; ... change it to 05 hex
1083 loop .loop ; Don't continue if too long
1084 ; Find the end for the benefit of longname search
1095 mov al,' ' ; Space-fill name
1096 rep stosb ; Doesn't do anything if CX=0
1101 mov al,' ' ; We need to space-fill
1102 .period_loop: cmp cx,3 ; If <= 3 characters left
1103 jbe .loop ; Just ignore it
1104 stosb ; Otherwise, write a space
1105 loop .period_loop ; Dec CX and *always* jump
1115 ; getfssec_edx: Get multiple sectors from a file
1117 ; This routine makes sure the subtransfers do not cross a 64K boundary,
1118 ; and will correct the situation if it does, UNLESS *sectors* cross
1122 ; EDX -> Current sector number
1123 ; CX -> Sector count (0FFFFh = until end of file)
1124 ; Must not exceed the ES segment
1125 ; Returns EDX=0, CF=1 on EOF (not necessarily error)
1126 ; All arguments are advanced to reflect data read.
1132 xor ebp,ebp ; Fragment sector count
1133 push edx ; Starting sector pointer
1141 add ax,bx ; Now AX = how far into 64K block we are
1142 not ax ; Bytes left in 64K block
1144 shr eax,SECTOR_SHIFT ; Sectors left in 64K block
1146 jnb .do_read ; Unless there is at least 1 more sector room...
1147 mov eax,edx ; Current sector
1148 inc edx ; Predict it's the linearly next sector
1151 cmp edx,eax ; Did it match?
1154 pop eax ; Starting sector pointer
1156 lea eax,[eax+ebp-1] ; This is the last sector actually read
1158 add bx,bp ; Adjust buffer pointer
1174 ; getfssec: Get multiple sectors from a file
1176 ; Same as above, except SI is a pointer to a open_file_t
1179 ; DS:SI -> Pointer to open_file_t
1180 ; CX -> Sector count (0FFFFh = until end of file)
1181 ; Must not exceed the ES segment
1182 ; Returns CF=1 on EOF (not necessarily error)
1183 ; ECX returns number of bytes read.
1184 ; All arguments are advanced to reflect data read.
1189 push edx ; Zero-extended CX
1190 cmp edx,[si+file_left]
1192 mov edx,[si+file_left]
1195 sub [si+file_left],edx
1196 mov edx,[si+file_sector]
1198 mov [si+file_sector],edx
1199 pop ecx ; Sectors requested read
1200 shl ecx,SECTOR_SHIFT
1201 cmp ecx,[si+file_bytesleft]
1204 sub [si+file_bytesleft],ecx ; CF <- 0
1208 mov ecx,[si+file_bytesleft]
1215 ; nextcluster: Advance a cluster pointer in EDI to the next cluster
1216 ; pointed at in the FAT tables. CF=0 on return if end of file.
1219 jmp strict short nextcluster_fat28 ; This gets patched
1229 pushf ; Save the shifted-out LSB (=CF)
1258 ; FAT16 decoding routine.
1265 shr eax,SECTOR_SHIFT-1
1270 movzx edi,word [gs:si+bx]
1277 ; FAT28 ("FAT32") decoding routine.
1284 shr eax,SECTOR_SHIFT-2
1290 mov edi,dword [gs:si+bx]
1291 and edi,0FFFFFFFh ; 28 bits only
1299 ; nextsector: Given a sector in EAX on input, return the next sector
1300 ; of the same filesystem object, which may be the root
1301 ; directory or a cluster chain. Returns EOF.
1321 test edi,[ClustMask]
1324 ; It's not the final sector in a cluster
1329 push gs ; nextcluster trashes gs
1336 ; Now EDI contains the cluster number
1339 jc .exit ; There isn't anything else...
1341 ; New cluster number now in EDI
1343 shl edi,cl ; CF <- 0, unless something is very wrong
1354 ; getfatsector: Check for a particular sector (in EAX) in the FAT cache,
1355 ; and return a pointer in GS:SI, loading it if needed.
1360 add eax,[FAT] ; FAT starting address
1363 ; -----------------------------------------------------------------------------
1365 ; -----------------------------------------------------------------------------
1367 %include "common.inc" ; Universal modules
1368 %include "plaincon.inc" ; writechr
1369 %include "writestr.inc" ; String output
1370 %include "writehex.inc" ; Hexadecimal output
1371 %include "cache.inc" ; Metadata disk cache
1372 %include "localboot.inc" ; Disk-based local boot
1374 ; -----------------------------------------------------------------------------
1375 ; Begin data section
1376 ; -----------------------------------------------------------------------------
1379 copyright_str db ' Copyright (C) 1994-'
1381 db ' H. Peter Anvin et al', CR, LF, 0
1382 err_bootfailed db CR, LF, 'Boot failed: please change disks and press '
1383 db 'a key to continue.', CR, LF, 0
1384 syslinux_cfg1 db '/boot' ; /boot/syslinux/syslinux.cfg
1385 syslinux_cfg2 db '/syslinux' ; /syslinux/syslinux.cfg
1386 syslinux_cfg3 db '/' ; /syslinux.cfg
1387 config_name db 'syslinux.cfg', 0 ; syslinux.cfg
1390 ; Config file keyword table
1392 %include "keywords.inc"
1395 ; Extensions to search for (in *forward* order).
1397 exten_table: db '.cbt' ; COMBOOT (specific)
1398 db '.bss' ; Boot Sector (add superblock)
1399 db '.bs', 0 ; Boot Sector
1400 db '.com' ; COMBOOT (same as DOS)
1403 dd 0, 0 ; Need 8 null bytes here
1406 ; Misc initialized (data) variables
1408 %ifdef debug ; This code for debugging only
1409 debug_magic dw 0D00Dh ; Debug code sentinel
1413 BufSafe dw trackbufsize/SECTOR_SIZE ; Clusters we can load into trackbuf
1414 BufSafeBytes dw trackbufsize ; = how many bytes?
1416 %if ( trackbufsize % SECTOR_SIZE ) != 0
1417 %error trackbufsize must be a multiple of SECTOR_SIZE