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 ; This is what we need to do when idle
64 ; The following structure is used for "virtual kernels"; i.e. LILO-style
65 ; option labels. The options we permit here are `kernel' and `append
66 ; Since there is no room in the bottom 64K for all of these, we
67 ; stick them in high memory and copy them down before we need them.
70 vk_vname: resb FILENAME_MAX ; Virtual name **MUST BE FIRST!**
71 vk_rname: resb FILENAME_MAX ; Real name
73 vk_type: resb 1 ; Type of file
75 vk_append: resb max_cmd_len+1 ; Command line
77 vk_end: equ $ ; Should be <= vk_size
81 ; File structure. This holds the information for each currently open file.
84 file_sector resd 1 ; Sector pointer (0 = structure free)
85 file_bytesleft resd 1 ; Number of bytes left
86 file_left resd 1 ; Number of sectors left
91 ; Structure for codepage files
94 .magic resd 2 ; 8-byte magic number
95 .reserved resd 6 ; Reserved for future use
96 .uppercase resb 256 ; Internal upper-case table
97 .unicode resw 256 ; Unicode matching table
98 .unicode_alt resw 256 ; Alternate Unicode matching table
102 %if (open_file_t_size & (open_file_t_size-1))
103 %error "open_file_t is not a power of 2"
107 ; ---------------------------------------------------------------------------
109 ; ---------------------------------------------------------------------------
112 ; Memory below this point is reserved for the BIOS and the MBR
115 trackbufsize equ 8192
116 trackbuf resb trackbufsize ; Track buffer goes here
121 FAT resd 1 ; Location of (first) FAT
122 RootDirArea resd 1 ; Location of root directory area
123 RootDir resd 1 ; Location of root directory proper
124 DataArea resd 1 ; Location of data area
125 RootDirSize resd 1 ; Root dir size in sectors
126 TotalSectors resd 1 ; Total number of sectors
127 ClustSize resd 1 ; Bytes/cluster
128 ClustMask resd 1 ; Sectors/cluster - 1
129 CopySuper resb 1 ; Distinguish .bs versus .bss
130 ClustShift resb 1 ; Shift count for sectors/cluster
131 ClustByteShift resb 1 ; Shift count for bytes/cluster
133 alignb open_file_t_size
134 Files resb MAX_OPEN*open_file_t_size
137 ; Common bootstrap code for disk-based derivatives
139 %include "diskstart.inc"
142 ; Common initialization code
145 %include "cpuinit.inc"
148 ; Compute some information about this filesystem.
151 ; First, generate the map of regions
156 mov edx,[bsHugeSectors]
158 mov [TotalSectors],edx
160 mov eax,[bxResSectors]
161 mov [FAT],eax ; Beginning of FAT
165 mov edx,[bootsec+36] ; FAT32 BPB_FATsz32
169 mov [RootDirArea],eax ; Beginning of root directory
170 mov [RootDir],eax ; For FAT12/16 == root dir location
172 mov edx,[bxRootDirEnts]
173 add dx,SECTOR_SIZE/32-1
174 shr dx,SECTOR_SHIFT-5
175 mov [RootDirSize],edx
177 mov [DataArea],eax ; Beginning of data area
179 ; Next, generate a cluster size shift count and mask
180 mov eax,[bxSecPerClust]
185 mov [ClustByteShift],cl
194 ; FAT12, FAT16 or FAT28^H^H32? This computation is fscking ridiculous.
197 mov eax,[TotalSectors]
199 shr eax,cl ; cl == ClustShift
200 mov cl,nextcluster_fat12-(nextcluster+2)
201 cmp eax,4085 ; FAT12 limit
203 mov cl,nextcluster_fat16-(nextcluster+2)
204 cmp eax,65525 ; FAT16 limit
207 ; FAT32, root directory is a cluster chain
210 mov eax,[bootsec+44] ; Root directory cluster
215 mov cl,nextcluster_fat28-(nextcluster+2)
216 mov byte [SuperSize],superblock_len_fat32
218 mov byte [nextcluster+1],cl
222 ; Initialize the metadata cache
227 ; Now, everything is "up and running"... patch kaboom for more
228 ; verbosity and using the full screen system
239 ; Now we're all set to start with our *real* business. First load the
240 ; configuration file (if any) and parse it.
242 ; In previous versions I avoided using 32-bit registers because of a
243 ; rumour some BIOSes clobbered the upper half of 32-bit registers at
244 ; random. I figure, though, that if there are any of those still left
245 ; they probably won't be trying to install Linux on them...
247 ; The code is still ripe with 16-bitisms, though. Not worth the hassle
248 ; to take'm out. In fact, we may want to put them back if we're going
249 ; to boot ELKS at some point.
253 ; Load configuration file
255 mov si,config_name ; Save configuration file name
258 mov word [CurrentDirName],ROOT_DIR_WORD ; Write '/',0 to the CurrentDirName
260 mov eax,[RootDir] ; Make the root directory ...
261 mov [CurrentDir],eax ; ... the current directory
281 mov di,CurrentDirName
282 ; This is inefficient as it will copy more than needed
283 ; but not by too much
285 mov ax,config_name ;Cut it down
288 mov di,CurrentDirName
292 mov eax,[PrevDir] ; Make the directory with syslinux.cfg ...
293 mov [CurrentDir],eax ; ... the current directory
296 ; Now we have the config file open. Parse the config file and
297 ; run the user interface.
302 ; allocate_file: Allocate a file structure
315 .check: cmp dword [bx], byte 0
317 add bx,open_file_t_size ; ZF = 0
319 ; ZF = 0 if we fell out of the loop
325 ; Allocate then fill a file structure for a directory starting in
328 ; Assumes DS == ES == CS.
343 mov [si+file_sector],eax ; Current sector
344 mov dword [si+file_bytesleft],0 ; Current offset
345 mov [si+file_left],eax ; Beginning sector
351 xor eax,eax ; ZF <- 1
356 ; Search a specific directory for a pre-mangled filename in
357 ; MangledBuf, in the directory starting in sector EAX.
359 ; NOTE: This file considers finding a zero-length file an
360 ; error. This is so we don't have to deal with that special
361 ; case elsewhere in the program (most loops have the test
364 ; Assumes DS == ES == CS.
369 ; EAX = file length (MAY BE ZERO!)
370 ; DL = file attribute
374 ; EAX, SI, DX clobbered
388 ; Compute the value of a possible VFAT longname
389 ; "last" entry (which, of course, comes first...)
404 ; EAX <- directory sector to scan
406 ; GS:SI now points to this sector
408 mov cx,SECTOR_SIZE/32 ; 32 == directory entry size
411 jz .failure ; Hit directory high water mark
412 cmp word [gs:si+11],0Fh ; Long filename
415 ; Process a VFAT long entry
423 ; Get the initial checksum value
428 jne .not_us ; Checksum mismatch
431 jz .not_us ; Can't be zero...
433 mov [VFATNext],al ; Optimistically...
449 movzx bx,byte [bx+di]
451 cmp ax,[cp_unicode+bx] ; Primary case
453 cmp ax,[cp_unicode_alt+bx] ; Alternate case
458 ; *AT* the end we should have 0x0000, *AFTER* the end
459 ; we should have 0xFFFF...
461 inc ax ; 0xFFFF -> 0x0000
472 .vfat_adj_add3: inc si
473 .vfat_adj_add2: inc si
474 .vfat_adj_add1: inc si
477 ; Okay, if we got here we had a match on this particular
478 ; entry... live to see another one.
489 test byte [gs:si+11],8 ; Ignore volume labels
492 cmp byte [VFATNext],0 ; Do we have a longname match?
495 ; We already have a VFAT longname match, however,
496 ; the match is only valid if the checksum matches
511 je .found ; Got a match on longname
513 .no_long_match: ; Look for a shortname match
525 ; Reset the VFAT matching state machine
534 jnc .scansector ; CF is set if we're at end
536 ; If we get here, we failed
543 xor eax,eax ; ZF <- 1
546 mov eax,[gs:si+28] ; File size
547 add eax,SECTOR_SIZE-1
549 mov [bx+4],eax ; Sector count
552 mov dx,[gs:si+20] ; High cluster word
554 mov dx,[gs:si+26] ; Low cluster word
558 mov [bx],edx ; Starting sector
560 mov eax,[gs:si+28] ; File length again
561 mov dl,[gs:si+11] ; File attribute
562 mov si,bx ; File pointer...
573 ; Note: we have no use of the first 32 bytes (header),
574 ; nor of the folloing 32 bytes (case mapping of control
575 ; characters), as long as we adjust the offsets appropriately.
576 codepage equ $-(32+32)
577 codepage_data: incbin "codepage.cp",32+32
578 cp_uppercase equ codepage+cp.uppercase
579 cp_unicode equ codepage+cp.unicode
580 cp_unicode_alt equ codepage+cp.unicode_alt
585 ; Input: UCS-2 character in AX
586 ; Output: Single byte character in AL, ZF = 1
587 ; On failure, returns ZF = 0
602 not ax ; Doesn't change the flags!
613 ; Deallocates a file structure (pointer in SI)
619 mov dword [si],0 ; First dword == file_sector
625 ; Deallocates a directory structure (pointer in SI)
631 mov dword [si],0 ; First dword == file_sector
645 ; EAX = file length in bytes
649 ; Assumes CS == DS == ES, and trashes BX and CX.
653 cmp byte [di],'/' ; Root directory?
660 push eax ; <A> Current directory sector
669 xchg si,di ; GRC: si begin; di end[ /]+1
670 pop eax ; <A> Current directory sector
672 ; GRC Here I need to check if di-1 = si which signifies
673 ; we have the desired directory in EAX
674 ; What about where the file name = "."; later
680 mov [PrevDir],eax ; Remember last directory searched
683 call mangle_dos_name ; MangledBuf <- component
686 jz .notfound ; Pathname component missing
688 cmp byte [di-1],'/' ; Do we expect a directory
691 ; Otherwise, it should be a file
693 test dl,18h ; Subdirectory|Volume Label
694 jnz .badfile ; If not a file, it's a bad thing
696 ; SI and EAX are already set
697 mov [si+file_bytesleft],eax
699 add eax,SECTOR_SIZE-1
701 mov [si+file_left],eax ; Sectors left
703 and eax,eax ; EAX != 0
707 ; If we expected a directory, it better be one...
709 test dl,10h ; Subdirectory
713 xchg eax,[si+file_sector] ; Get sector number and free file structure
714 jmp .pathwalk ; Walk the next bit of the path
716 ; Found the desired directory; ZF set but EAX not 0
722 mov [si],eax ; Free file structure
725 xor eax,eax ; Zero out EAX
729 ; readdir: Read one file from a directory
731 ; ES:DI -> String buffer (filename)
732 ; DS:SI -> Pointer to open_file_t
733 ; DS Must be the SYSLINUX Data Segment
735 ; Returns the file's name in the filename string buffer
736 ; EAX returns the file size
737 ; EBX returns the beginning sector (currently without offsetting)
738 ; DL returns the file type
739 ; The directory handle's data is incremented to reflect a name read.
743 push bp ; Using bp to transfer between segment registers
746 push fs ; Using fs to store the current es (from COMBOOT)
753 mov eax,[ds:si+file_sector] ; Current sector
754 mov ebx,[ds:si+file_bytesleft] ; Current offset
760 add si,bx ; Resume last position in sector
761 mov ecx,SECTOR_SIZE ; 0 out high part
763 shr cx,5 ; Number of entries left
767 cmp word [gs:si+11],0Fh ; Long filename
775 .vfat_ln_info: ; Get info about the line that we're on
780 mov ah,1 ; On beginning line
783 .vfat_tail_ln: ; VFAT tail line processing (later in VFAT, head in name)
784 test al,80h ; Invalid data?
786 mov ah,0 ; Not on beginning line
788 jne .vfat_abort ; Is this the entry we need?
794 .vfat_ck_ln: ; Load this line's VFAT CheckSum
797 .vfat_cp_ln: ; Copy VFAT line
798 dec al ; Store the next line we need
799 mov dx,ax ; Use DX to store the progress
800 mov cx,13 ; 13 characters per VFAT DIRENT
802 mul cl ; Offset for DI
803 add di,ax ; Increment DI
804 inc si ; Align to the real characters
806 gs lodsw ; Unicode here!!
807 call ucs2_to_cp ; Convert to local codepage
808 jnz .vfat_abort ; Use short name if character not on codepage
809 stosb ; CAN NOT OVERRIDE es
811 jz .vfat_find_next ; Null-terminated string; don't process more
816 .vfat_adj_add3: inc si
817 .vfat_adj_add2: inc si
818 .vfat_adj_add1: inc si
821 cmp dh,1 ; Is this the first round?
823 .vfat_null_term: ; Need to null-terminate if first line as we rolled over the end
827 .vfat_find_next: ;Find the next part of the name
833 jz .vfat_find_info ; We're done with the name
838 jnc .vfat_entry ; CF is set if we're at end
840 .vfat_find_info: ; Fetch next entry for the size/"INode"
845 jnc .get_info ; CF is set if we're at end
847 .vfat_abort: ; Something went wrong, skip
855 test byte [gs:si+11],8 ; Ignore volume labels //HERE
857 mov edx,eax ;Save current sector
870 loop .short_file_loop
872 .short_skip_bs: ; skip blank spaces in FILENAME (before EXT)
893 mov al,0 ; Null-terminate the short strings
900 mov ebx,[gs:si+28] ; length
901 mov dl,[gs:si+11] ; type
907 jnc .store_sect ; CF is set if we're at end
915 jnc .scanentry ; CF is set if we're at end
923 mov [ds:si+file_sector],eax
924 mov eax,0 ; Now at beginning of new sector
931 pop si ; cx=num remain; SECTOR_SIZE-(cx*32)=cur pos
938 mov [ds:si+file_bytesleft],eax
939 ; "INode" number = ((CurSector-RootSector)*SECTOR_SIZE + Offset)/DIRENT_SIZE)
941 mov eax,[ds:si+file_sector]
947 xchg eax,ebx ; -> EBX=INode, EAX=FileSize
966 PrevDir resd 1 ; Last scanned directory
972 ; kaboom2: once everything is loaded, replace the part of kaboom
973 ; starting with "kaboom.patch" with this part
976 mov si,err_bootfailed
978 cmp byte [kaboom.again+1],18h ; INT 18h version?
982 int 19h ; And try once more to boot...
983 .norge: jmp short .norge ; If int 19h returned; this is the end
987 .noreg: jmp short .noreg ; Nynorsk
990 ; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed
991 ; to by ES:DI; ends on encountering any whitespace.
994 ; This verifies that a filename is < FILENAME_MAX characters,
995 ; doesn't contain whitespace, zero-pads the output buffer,
996 ; and removes trailing dots and redundant slashes, plus changes
997 ; backslashes to forward slashes,
998 ; so "repe cmpsb" can do a compare, and the path-searching routine
999 ; gets a bit of an easier job.
1006 mov cx,FILENAME_MAX-1
1011 cmp al,' ' ; If control or space, end
1013 cmp al,'\' ; Backslash?
1015 mov al,'/' ; Change to forward slash
1017 cmp al,ah ; Repeated slash?
1024 .mn_skip: loop .mn_loop
1026 cmp bx,di ; At the beginning of the buffer?
1028 cmp byte [es:di-1],'.' ; Terminal dot?
1030 cmp byte [es:di-1],'/' ; Terminal slash?
1032 .mn_kill: dec di ; If so, remove it
1036 inc cx ; At least one null byte
1037 xor ax,ax ; Zero-fill name
1044 ; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled
1045 ; filename to the conventional representation. This is needed
1046 ; for the BOOT_IMAGE= parameter for the kernel.
1047 ; NOTE: A 13-byte buffer is mandatory, even if the string is
1048 ; known to be shorter.
1050 ; DS:SI -> input mangled file name
1051 ; ES:DI -> output buffer
1053 ; On return, DI points to the first byte after the output name,
1054 ; which is set to a null byte.
1056 unmangle_name: call strcpy
1057 dec di ; Point to final null byte
1062 ; Mangle a DOS filename component pointed to by DS:SI
1063 ; into [MangledBuf]; ends on encountering any whitespace or
1066 ; WARNING: saves pointers into the buffer for longname
1069 ; Assumes CS == DS == ES.
1077 mov cx,11 ; # of bytes to write
1078 mov bx,cp_uppercase ; Case-conversion table
1081 cmp al,' ' ; If control or space, end
1083 cmp al,'/' ; Slash, too
1085 cmp al,'.' ; Period -> space-fill
1087 xlatb ; Convert to upper case
1088 mov ah,cl ; If the first byte (only!)...
1089 cmp ax,0BE5h ; ... equals E5 hex ...
1091 mov al,05h ; ... change it to 05 hex
1093 loop .loop ; Don't continue if too long
1094 ; Find the end for the benefit of longname search
1105 mov al,' ' ; Space-fill name
1106 rep stosb ; Doesn't do anything if CX=0
1111 mov al,' ' ; We need to space-fill
1112 .period_loop: cmp cx,3 ; If <= 3 characters left
1113 jbe .loop ; Just ignore it
1114 stosb ; Otherwise, write a space
1115 loop .period_loop ; Dec CX and *always* jump
1125 ; getfssec_edx: Get multiple sectors from a file
1127 ; This routine makes sure the subtransfers do not cross a 64K boundary,
1128 ; and will correct the situation if it does, UNLESS *sectors* cross
1132 ; EDX -> Current sector number
1133 ; CX -> Sector count (0FFFFh = until end of file)
1134 ; Must not exceed the ES segment
1135 ; Returns EDX=0, CF=1 on EOF (not necessarily error)
1136 ; All arguments are advanced to reflect data read.
1142 xor ebp,ebp ; Fragment sector count
1143 push edx ; Starting sector pointer
1151 add ax,bx ; Now AX = how far into 64K block we are
1152 not ax ; Bytes left in 64K block
1154 shr eax,SECTOR_SHIFT ; Sectors left in 64K block
1156 jnb .do_read ; Unless there is at least 1 more sector room...
1157 mov eax,edx ; Current sector
1158 inc edx ; Predict it's the linearly next sector
1161 cmp edx,eax ; Did it match?
1164 pop eax ; Starting sector pointer
1166 lea eax,[eax+ebp-1] ; This is the last sector actually read
1168 add bx,bp ; Adjust buffer pointer
1184 ; getfssec: Get multiple sectors from a file
1186 ; Same as above, except SI is a pointer to a open_file_t
1189 ; DS:SI -> Pointer to open_file_t
1190 ; CX -> Sector count (0FFFFh = until end of file)
1191 ; Must not exceed the ES segment
1192 ; Returns CF=1 on EOF (not necessarily error)
1193 ; ECX returns number of bytes read.
1194 ; All arguments are advanced to reflect data read.
1199 push edx ; Zero-extended CX
1200 cmp edx,[si+file_left]
1202 mov edx,[si+file_left]
1205 sub [si+file_left],edx
1206 mov edx,[si+file_sector]
1208 mov [si+file_sector],edx
1209 pop ecx ; Sectors requested read
1210 shl ecx,SECTOR_SHIFT
1211 cmp ecx,[si+file_bytesleft]
1214 sub [si+file_bytesleft],ecx ; CF <- 0
1218 mov ecx,[si+file_bytesleft]
1225 ; nextcluster: Advance a cluster pointer in EDI to the next cluster
1226 ; pointed at in the FAT tables. CF=0 on return if end of file.
1229 jmp strict short nextcluster_fat28 ; This gets patched
1239 pushf ; Save the shifted-out LSB (=CF)
1268 ; FAT16 decoding routine.
1275 shr eax,SECTOR_SHIFT-1
1280 movzx edi,word [gs:si+bx]
1287 ; FAT28 ("FAT32") decoding routine.
1294 shr eax,SECTOR_SHIFT-2
1300 mov edi,dword [gs:si+bx]
1301 and edi,0FFFFFFFh ; 28 bits only
1309 ; nextsector: Given a sector in EAX on input, return the next sector
1310 ; of the same filesystem object, which may be the root
1311 ; directory or a cluster chain. Returns EOF.
1331 test edi,[ClustMask]
1334 ; It's not the final sector in a cluster
1339 push gs ; nextcluster trashes gs
1346 ; Now EDI contains the cluster number
1349 jc .exit ; There isn't anything else...
1351 ; New cluster number now in EDI
1353 shl edi,cl ; CF <- 0, unless something is very wrong
1364 ; getfatsector: Check for a particular sector (in EAX) in the FAT cache,
1365 ; and return a pointer in GS:SI, loading it if needed.
1370 add eax,[FAT] ; FAT starting address
1373 ; -----------------------------------------------------------------------------
1375 ; -----------------------------------------------------------------------------
1377 %include "common.inc" ; Universal modules
1378 %include "plaincon.inc" ; writechr
1379 %include "writestr.inc" ; String output
1380 %include "writehex.inc" ; Hexadecimal output
1381 %include "cache.inc" ; Metadata disk cache
1382 %include "localboot.inc" ; Disk-based local boot
1384 ; -----------------------------------------------------------------------------
1385 ; Begin data section
1386 ; -----------------------------------------------------------------------------
1389 copyright_str db ' Copyright (C) 1994-'
1391 db ' H. Peter Anvin et al', CR, LF, 0
1392 err_bootfailed db CR, LF, 'Boot failed: please change disks and press '
1393 db 'a key to continue.', CR, LF, 0
1394 syslinux_cfg1 db '/boot' ; /boot/syslinux/syslinux.cfg
1395 syslinux_cfg2 db '/syslinux' ; /syslinux/syslinux.cfg
1396 syslinux_cfg3 db '/' ; /syslinux.cfg
1397 config_name db 'syslinux.cfg', 0 ; syslinux.cfg
1400 ; Config file keyword table
1402 %include "keywords.inc"
1405 ; Extensions to search for (in *forward* order).
1407 exten_table: db '.cbt' ; COMBOOT (specific)
1408 db '.bss' ; Boot Sector (add superblock)
1409 db '.bs', 0 ; Boot Sector
1410 db '.com' ; COMBOOT (same as DOS)
1413 dd 0, 0 ; Need 8 null bytes here
1416 ; Misc initialized (data) variables
1418 %ifdef debug ; This code for debugging only
1419 debug_magic dw 0D00Dh ; Debug code sentinel
1423 BufSafe dw trackbufsize/SECTOR_SIZE ; Clusters we can load into trackbuf
1424 BufSafeBytes dw trackbufsize ; = how many bytes?
1426 %if ( trackbufsize % SECTOR_SIZE ) != 0
1427 %error trackbufsize must be a multiple of SECTOR_SIZE