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
218 ; Now, everything is "up and running"... patch kaboom for more
219 ; verbosity and using the full screen system
230 ; Now we're all set to start with our *real* business. First load the
231 ; configuration file (if any) and parse it.
233 ; In previous versions I avoided using 32-bit registers because of a
234 ; rumour some BIOSes clobbered the upper half of 32-bit registers at
235 ; random. I figure, though, that if there are any of those still left
236 ; they probably won't be trying to install Linux on them...
238 ; The code is still ripe with 16-bitisms, though. Not worth the hassle
239 ; to take'm out. In fact, we may want to put them back if we're going
240 ; to boot ELKS at some point.
244 ; Load configuration file
246 mov si,config_name ; Save configuration file name
249 mov word [CurrentDirName],ROOT_DIR_WORD ; Write '/',0 to the CurrentDirName
251 mov eax,[RootDir] ; Make the root directory ...
252 mov [CurrentDir],eax ; ... the current directory
272 mov di,CurrentDirName
273 ; This is inefficient as it will copy more than needed
274 ; but not by too much
276 mov ax,config_name ;Cut it down
279 mov di,CurrentDirName
283 mov eax,[PrevDir] ; Make the directory with syslinux.cfg ...
284 mov [CurrentDir],eax ; ... the current directory
287 ; Now we have the config file open. Parse the config file and
288 ; run the user interface.
293 ; allocate_file: Allocate a file structure
306 .check: cmp dword [bx], byte 0
308 add bx,open_file_t_size ; ZF = 0
310 ; ZF = 0 if we fell out of the loop
316 ; Allocate then fill a file structure for a directory starting in
319 ; Assumes DS == ES == CS.
334 mov [si+file_sector],eax ; Current sector
335 mov dword [si+file_bytesleft],0 ; Current offset
336 mov [si+file_left],eax ; Beginning sector
342 xor eax,eax ; ZF <- 1
347 ; Search a specific directory for a pre-mangled filename in
348 ; MangledBuf, in the directory starting in sector EAX.
350 ; NOTE: This file considers finding a zero-length file an
351 ; error. This is so we don't have to deal with that special
352 ; case elsewhere in the program (most loops have the test
355 ; Assumes DS == ES == CS.
360 ; EAX = file length (MAY BE ZERO!)
361 ; DL = file attribute
365 ; EAX, SI, DX clobbered
379 ; Compute the value of a possible VFAT longname
380 ; "last" entry (which, of course, comes first...)
395 ; EAX <- directory sector to scan
396 pm_call get_cache_block
397 ; GS:SI now points to this sector
399 mov cx,SECTOR_SIZE/32 ; 32 == directory entry size
402 jz .failure ; Hit directory high water mark
403 cmp word [gs:si+11],0Fh ; Long filename
406 ; Process a VFAT long entry
414 ; Get the initial checksum value
419 jne .not_us ; Checksum mismatch
422 jz .not_us ; Can't be zero...
424 mov [VFATNext],al ; Optimistically...
440 movzx bx,byte [bx+di]
442 cmp ax,[cp_unicode+bx] ; Primary case
444 cmp ax,[cp_unicode_alt+bx] ; Alternate case
449 ; *AT* the end we should have 0x0000, *AFTER* the end
450 ; we should have 0xFFFF...
452 inc ax ; 0xFFFF -> 0x0000
463 .vfat_adj_add3: inc si
464 .vfat_adj_add2: inc si
465 .vfat_adj_add1: inc si
468 ; Okay, if we got here we had a match on this particular
469 ; entry... live to see another one.
480 test byte [gs:si+11],8 ; Ignore volume labels
483 cmp byte [VFATNext],0 ; Do we have a longname match?
486 ; We already have a VFAT longname match, however,
487 ; the match is only valid if the checksum matches
502 je .found ; Got a match on longname
504 .no_long_match: ; Look for a shortname match
516 ; Reset the VFAT matching state machine
525 jnc .scansector ; CF is set if we're at end
527 ; If we get here, we failed
534 xor eax,eax ; ZF <- 1
537 mov eax,[gs:si+28] ; File size
538 add eax,SECTOR_SIZE-1
540 mov [bx+4],eax ; Sector count
543 mov dx,[gs:si+20] ; High cluster word
545 mov dx,[gs:si+26] ; Low cluster word
549 mov [bx],edx ; Starting sector
551 mov eax,[gs:si+28] ; File length again
552 mov dl,[gs:si+11] ; File attribute
553 mov si,bx ; File pointer...
564 ; Note: we have no use of the first 32 bytes (header),
565 ; nor of the folloing 32 bytes (case mapping of control
566 ; characters), as long as we adjust the offsets appropriately.
567 codepage equ $-(32+32)
568 codepage_data: incbin "codepage.cp",32+32
569 cp_uppercase equ codepage+cp.uppercase
570 cp_unicode equ codepage+cp.unicode
571 cp_unicode_alt equ codepage+cp.unicode_alt
576 ; Input: UCS-2 character in AX
577 ; Output: Single byte character in AL, ZF = 1
578 ; On failure, returns ZF = 0
593 not ax ; Doesn't change the flags!
604 ; Deallocates a file structure (pointer in SI)
610 mov dword [si],0 ; First dword == file_sector
616 ; Deallocates a directory structure (pointer in SI)
622 mov dword [si],0 ; First dword == file_sector
636 ; EAX = file length in bytes
640 ; Assumes CS == DS == ES, and trashes BX and CX.
644 cmp byte [di],'/' ; Root directory?
651 push eax ; <A> Current directory sector
660 xchg si,di ; GRC: si begin; di end[ /]+1
661 pop eax ; <A> Current directory sector
663 ; GRC Here I need to check if di-1 = si which signifies
664 ; we have the desired directory in EAX
665 ; What about where the file name = "."; later
671 mov [PrevDir],eax ; Remember last directory searched
674 call mangle_dos_name ; MangledBuf <- component
677 jz .notfound ; Pathname component missing
679 cmp byte [di-1],'/' ; Do we expect a directory
682 ; Otherwise, it should be a file
684 test dl,18h ; Subdirectory|Volume Label
685 jnz .badfile ; If not a file, it's a bad thing
687 ; SI and EAX are already set
688 mov [si+file_bytesleft],eax
690 add eax,SECTOR_SIZE-1
692 mov [si+file_left],eax ; Sectors left
694 and eax,eax ; EAX != 0
698 ; If we expected a directory, it better be one...
700 test dl,10h ; Subdirectory
704 xchg eax,[si+file_sector] ; Get sector number and free file structure
705 jmp .pathwalk ; Walk the next bit of the path
707 ; Found the desired directory; ZF set but EAX not 0
713 mov [si],eax ; Free file structure
716 xor eax,eax ; Zero out EAX
720 ; readdir: Read one file from a directory
722 ; ES:DI -> String buffer (filename)
723 ; DS:SI -> Pointer to open_file_t
724 ; DS Must be the SYSLINUX Data Segment
726 ; Returns the file's name in the filename string buffer
727 ; EAX returns the file size
728 ; EBX returns the beginning sector (currently without offsetting)
729 ; DL returns the file type
730 ; The directory handle's data is incremented to reflect a name read.
734 push bp ; Using bp to transfer between segment registers
737 push fs ; Using fs to store the current es (from COMBOOT)
744 mov eax,[ds:si+file_sector] ; Current sector
745 mov ebx,[ds:si+file_bytesleft] ; Current offset
749 pm_call get_cache_block
751 add si,bx ; Resume last position in sector
752 mov ecx,SECTOR_SIZE ; 0 out high part
754 shr cx,5 ; Number of entries left
758 cmp word [gs:si+11],0Fh ; Long filename
766 .vfat_ln_info: ; Get info about the line that we're on
771 mov ah,1 ; On beginning line
774 .vfat_tail_ln: ; VFAT tail line processing (later in VFAT, head in name)
775 test al,80h ; Invalid data?
777 mov ah,0 ; Not on beginning line
779 jne .vfat_abort ; Is this the entry we need?
785 .vfat_ck_ln: ; Load this line's VFAT CheckSum
788 .vfat_cp_ln: ; Copy VFAT line
789 dec al ; Store the next line we need
790 mov dx,ax ; Use DX to store the progress
791 mov cx,13 ; 13 characters per VFAT DIRENT
793 mul cl ; Offset for DI
794 add di,ax ; Increment DI
795 inc si ; Align to the real characters
797 gs lodsw ; Unicode here!!
798 call ucs2_to_cp ; Convert to local codepage
799 jnz .vfat_abort ; Use short name if character not on codepage
800 stosb ; CAN NOT OVERRIDE es
802 jz .vfat_find_next ; Null-terminated string; don't process more
807 .vfat_adj_add3: inc si
808 .vfat_adj_add2: inc si
809 .vfat_adj_add1: inc si
812 cmp dh,1 ; Is this the first round?
814 .vfat_null_term: ; Need to null-terminate if first line as we rolled over the end
818 .vfat_find_next: ;Find the next part of the name
824 jz .vfat_find_info ; We're done with the name
829 jnc .vfat_entry ; CF is set if we're at end
831 .vfat_find_info: ; Fetch next entry for the size/"INode"
836 jnc .get_info ; CF is set if we're at end
838 .vfat_abort: ; Something went wrong, skip
846 test byte [gs:si+11],8 ; Ignore volume labels //HERE
848 mov edx,eax ;Save current sector
861 loop .short_file_loop
863 .short_skip_bs: ; skip blank spaces in FILENAME (before EXT)
884 mov al,0 ; Null-terminate the short strings
891 mov ebx,[gs:si+28] ; length
892 mov dl,[gs:si+11] ; type
898 jnc .store_sect ; CF is set if we're at end
906 jnc .scanentry ; CF is set if we're at end
914 mov [ds:si+file_sector],eax
915 mov eax,0 ; Now at beginning of new sector
922 pop si ; cx=num remain; SECTOR_SIZE-(cx*32)=cur pos
929 mov [ds:si+file_bytesleft],eax
930 ; "INode" number = ((CurSector-RootSector)*SECTOR_SIZE + Offset)/DIRENT_SIZE)
932 mov eax,[ds:si+file_sector]
938 xchg eax,ebx ; -> EBX=INode, EAX=FileSize
957 PrevDir resd 1 ; Last scanned directory
963 ; kaboom2: once everything is loaded, replace the part of kaboom
964 ; starting with "kaboom.patch" with this part
967 mov si,err_bootfailed
969 cmp byte [kaboom.again+1],18h ; INT 18h version?
973 int 19h ; And try once more to boot...
974 .norge: jmp short .norge ; If int 19h returned; this is the end
978 .noreg: jmp short .noreg ; Nynorsk
981 ; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed
982 ; to by ES:DI; ends on encountering any whitespace.
985 ; This verifies that a filename is < FILENAME_MAX characters,
986 ; doesn't contain whitespace, zero-pads the output buffer,
987 ; and removes trailing dots and redundant slashes, plus changes
988 ; backslashes to forward slashes,
989 ; so "repe cmpsb" can do a compare, and the path-searching routine
990 ; gets a bit of an easier job.
997 mov cx,FILENAME_MAX-1
1002 cmp al,' ' ; If control or space, end
1004 cmp al,'\' ; Backslash?
1006 mov al,'/' ; Change to forward slash
1008 cmp al,ah ; Repeated slash?
1015 .mn_skip: loop .mn_loop
1017 cmp bx,di ; At the beginning of the buffer?
1019 cmp byte [es:di-1],'.' ; Terminal dot?
1021 cmp byte [es:di-1],'/' ; Terminal slash?
1023 .mn_kill: dec di ; If so, remove it
1027 inc cx ; At least one null byte
1028 xor ax,ax ; Zero-fill name
1035 ; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled
1036 ; filename to the conventional representation. This is needed
1037 ; for the BOOT_IMAGE= parameter for the kernel.
1038 ; NOTE: A 13-byte buffer is mandatory, even if the string is
1039 ; known to be shorter.
1041 ; DS:SI -> input mangled file name
1042 ; ES:DI -> output buffer
1044 ; On return, DI points to the first byte after the output name,
1045 ; which is set to a null byte.
1047 unmangle_name: call strcpy
1048 dec di ; Point to final null byte
1053 ; Mangle a DOS filename component pointed to by DS:SI
1054 ; into [MangledBuf]; ends on encountering any whitespace or
1057 ; WARNING: saves pointers into the buffer for longname
1060 ; Assumes CS == DS == ES.
1068 mov cx,11 ; # of bytes to write
1069 mov bx,cp_uppercase ; Case-conversion table
1072 cmp al,' ' ; If control or space, end
1074 cmp al,'/' ; Slash, too
1076 cmp al,'.' ; Period -> space-fill
1078 xlatb ; Convert to upper case
1079 mov ah,cl ; If the first byte (only!)...
1080 cmp ax,0BE5h ; ... equals E5 hex ...
1082 mov al,05h ; ... change it to 05 hex
1084 loop .loop ; Don't continue if too long
1085 ; Find the end for the benefit of longname search
1096 mov al,' ' ; Space-fill name
1097 rep stosb ; Doesn't do anything if CX=0
1102 mov al,' ' ; We need to space-fill
1103 .period_loop: cmp cx,3 ; If <= 3 characters left
1104 jbe .loop ; Just ignore it
1105 stosb ; Otherwise, write a space
1106 loop .period_loop ; Dec CX and *always* jump
1116 ; getfssec_edx: Get multiple sectors from a file
1118 ; This routine makes sure the subtransfers do not cross a 64K boundary,
1119 ; and will correct the situation if it does, UNLESS *sectors* cross
1123 ; EDX -> Current sector number
1124 ; CX -> Sector count (0FFFFh = until end of file)
1125 ; Must not exceed the ES segment
1126 ; Returns EDX=0, CF=1 on EOF (not necessarily error)
1127 ; All arguments are advanced to reflect data read.
1133 xor ebp,ebp ; Fragment sector count
1134 push edx ; Starting sector pointer
1142 add ax,bx ; Now AX = how far into 64K block we are
1143 not ax ; Bytes left in 64K block
1145 shr eax,SECTOR_SHIFT ; Sectors left in 64K block
1147 jnb .do_read ; Unless there is at least 1 more sector room...
1148 mov eax,edx ; Current sector
1149 inc edx ; Predict it's the linearly next sector
1152 cmp edx,eax ; Did it match?
1155 pop eax ; Starting sector pointer
1157 lea eax,[eax+ebp-1] ; This is the last sector actually read
1159 add bx,bp ; Adjust buffer pointer
1175 ; getfssec: Get multiple sectors from a file
1177 ; Same as above, except SI is a pointer to a open_file_t
1180 ; DS:SI -> Pointer to open_file_t
1181 ; CX -> Sector count (0FFFFh = until end of file)
1182 ; Must not exceed the ES segment
1183 ; Returns CF=1 on EOF (not necessarily error)
1184 ; ECX returns number of bytes read.
1185 ; All arguments are advanced to reflect data read.
1190 push edx ; Zero-extended CX
1191 cmp edx,[si+file_left]
1193 mov edx,[si+file_left]
1196 sub [si+file_left],edx
1197 mov edx,[si+file_sector]
1199 mov [si+file_sector],edx
1200 pop ecx ; Sectors requested read
1201 shl ecx,SECTOR_SHIFT
1202 cmp ecx,[si+file_bytesleft]
1205 sub [si+file_bytesleft],ecx ; CF <- 0
1209 mov ecx,[si+file_bytesleft]
1216 ; nextcluster: Advance a cluster pointer in EDI to the next cluster
1217 ; pointed at in the FAT tables. CF=0 on return if end of file.
1220 jmp strict short nextcluster_fat28 ; This gets patched
1230 pushf ; Save the shifted-out LSB (=CF)
1259 ; FAT16 decoding routine.
1266 shr eax,SECTOR_SHIFT-1
1271 movzx edi,word [gs:si+bx]
1278 ; FAT28 ("FAT32") decoding routine.
1285 shr eax,SECTOR_SHIFT-2
1291 mov edi,dword [gs:si+bx]
1292 and edi,0FFFFFFFh ; 28 bits only
1300 ; nextsector: Given a sector in EAX on input, return the next sector
1301 ; of the same filesystem object, which may be the root
1302 ; directory or a cluster chain. Returns EOF.
1322 test edi,[ClustMask]
1325 ; It's not the final sector in a cluster
1330 push gs ; nextcluster trashes gs
1337 ; Now EDI contains the cluster number
1340 jc .exit ; There isn't anything else...
1342 ; New cluster number now in EDI
1344 shl edi,cl ; CF <- 0, unless something is very wrong
1355 ; getfatsector: Check for a particular sector (in EAX) in the FAT cache,
1356 ; and return a pointer in GS:SI, loading it if needed.
1361 add eax,[FAT] ; FAT starting address
1362 pm_call get_cache_block
1364 ; -----------------------------------------------------------------------------
1366 ; -----------------------------------------------------------------------------
1368 %include "common.inc" ; Universal modules
1369 %include "plaincon.inc" ; writechr
1370 %include "writestr.inc" ; String output
1371 %include "writehex.inc" ; Hexadecimal output
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