diff options
| author | H. Peter Anvin <hpa@zytor.com> | 2008-05-29 15:11:38 -0700 |
|---|---|---|
| committer | H. Peter Anvin <hpa@zytor.com> | 2008-05-29 15:11:38 -0700 |
| commit | b536209dfb7bd50c37061735fe10d2c19a97d26d (patch) | |
| tree | 9d8ca6882fc5d9721fb0efea1abfd6dc09886814 /core/ldlinux.asm | |
| parent | 3ec40a0119587f63411475c76c69f9db24c7598e (diff) | |
| download | syslinux-b536209dfb7bd50c37061735fe10d2c19a97d26d.tar.gz | |
Move files out of root into core, dos, and utils
Move source files out of the root directory; the root is a mess and
has become virtually unmaintainable. The Syslinux core now lives in
core/; the Linux and generic utilities has moved into utils/, and
copybs.com has moved into dos/; it had to go somewhere, and it seemed
as good a place as any.
Diffstat (limited to 'core/ldlinux.asm')
| -rw-r--r-- | core/ldlinux.asm | 1605 |
1 files changed, 1605 insertions, 0 deletions
diff --git a/core/ldlinux.asm b/core/ldlinux.asm new file mode 100644 index 00000000..86de4588 --- /dev/null +++ b/core/ldlinux.asm @@ -0,0 +1,1605 @@ +; -*- fundamental -*- (asm-mode sucks) +; **************************************************************************** +; +; ldlinux.asm +; +; A program to boot Linux kernels off an MS-DOS formatted floppy disk. This +; functionality is good to have for installation floppies, where it may +; be hard to find a functional Linux system to run LILO off. +; +; This program allows manipulation of the disk to take place entirely +; from MS-LOSS, and can be especially useful in conjunction with the +; umsdos filesystem. +; +; Copyright 1994-2008 H. Peter Anvin - All Rights Reserved +; +; This program is free software; you can redistribute it and/or modify +; it under the terms of the GNU General Public License as published by +; the Free Software Foundation, Inc., 53 Temple Place Ste 330, +; Boston MA 02111-1307, USA; either version 2 of the License, or +; (at your option) any later version; incorporated herein by reference. +; +; **************************************************************************** + +%ifndef IS_MDSLINUX +%define IS_SYSLINUX 1 +%endif +%include "head.inc" + +; +; Some semi-configurable constants... change on your own risk. +; +my_id equ syslinux_id +FILENAME_MAX_LG2 equ 6 ; log2(Max filename size Including final null) +FILENAME_MAX equ (1<<FILENAME_MAX_LG2) ; Max mangled filename size +NULLFILE equ 0 ; First char space == null filename +NULLOFFSET equ 0 ; Position in which to look +retry_count equ 16 ; How patient are we with the disk? +%assign HIGHMEM_SLOP 0 ; Avoid this much memory near the top +LDLINUX_MAGIC equ 0x3eb202fe ; A random number to identify ourselves with + +MAX_OPEN_LG2 equ 6 ; log2(Max number of open files) +MAX_OPEN equ (1 << MAX_OPEN_LG2) + +SECTOR_SHIFT equ 9 +SECTOR_SIZE equ (1 << SECTOR_SHIFT) + +; +; This is what we need to do when idle +; +%macro RESET_IDLE 0 + ; Nothing +%endmacro +%macro DO_IDLE 0 + ; Nothing +%endmacro + +; +; The following structure is used for "virtual kernels"; i.e. LILO-style +; option labels. The options we permit here are `kernel' and `append +; Since there is no room in the bottom 64K for all of these, we +; stick them in high memory and copy them down before we need them. +; + struc vkernel +vk_vname: resb FILENAME_MAX ; Virtual name **MUST BE FIRST!** +vk_rname: resb FILENAME_MAX ; Real name +vk_appendlen: resw 1 +vk_type: resb 1 ; Type of file + alignb 4 +vk_append: resb max_cmd_len+1 ; Command line + alignb 4 +vk_end: equ $ ; Should be <= vk_size + endstruc + +; +; Segment assignments in the bottom 640K +; Stick to the low 512K in case we're using something like M-systems flash +; which load a driver into low RAM (evil!!) +; +; 0000h - main code/data segment (and BIOS segment) +; +real_mode_seg equ 3000h +cache_seg equ 2000h ; 64K area for metadata cache +xfer_buf_seg equ 1000h ; Bounce buffer for I/O to high mem +comboot_seg equ real_mode_seg ; COMBOOT image loading zone + +; +; File structure. This holds the information for each currently open file. +; + struc open_file_t +file_sector resd 1 ; Sector pointer (0 = structure free) +file_bytesleft resd 1 ; Number of bytes left +file_left resd 1 ; Number of sectors left + resd 1 ; Unused + endstruc + +%ifndef DEPEND +%if (open_file_t_size & (open_file_t_size-1)) +%error "open_file_t is not a power of 2" +%endif +%endif + +; --------------------------------------------------------------------------- +; BEGIN CODE +; --------------------------------------------------------------------------- + +; +; Memory below this point is reserved for the BIOS and the MBR +; + section .earlybss +trackbufsize equ 8192 +trackbuf resb trackbufsize ; Track buffer goes here + ; ends at 2800h + + section .bss + alignb 8 + + ; Expanded superblock +SuperInfo equ $ + resq 16 ; The first 16 bytes expanded 8 times +FAT resd 1 ; Location of (first) FAT +RootDirArea resd 1 ; Location of root directory area +RootDir resd 1 ; Location of root directory proper +DataArea resd 1 ; Location of data area +RootDirSize resd 1 ; Root dir size in sectors +TotalSectors resd 1 ; Total number of sectors +ClustSize resd 1 ; Bytes/cluster +ClustMask resd 1 ; Sectors/cluster - 1 +CopySuper resb 1 ; Distinguish .bs versus .bss +DriveNumber resb 1 ; BIOS drive number +ClustShift resb 1 ; Shift count for sectors/cluster +ClustByteShift resb 1 ; Shift count for bytes/cluster + + alignb open_file_t_size +Files resb MAX_OPEN*open_file_t_size + + section .text +; +; Some of the things that have to be saved very early are saved +; "close" to the initial stack pointer offset, in order to +; reduce the code size... +; +StackBuf equ $-44-32 ; Start the stack here (grow down - 4K) +PartInfo equ StackBuf ; Saved partition table entry +FloppyTable equ PartInfo+16 ; Floppy info table (must follow PartInfo) +OrigFDCTabPtr equ StackBuf-8 ; The 2nd high dword on the stack +OrigESDI equ StackBuf-4 ; The high dword on the stack + +; +; Primary entry point. Tempting as though it may be, we can't put the +; initial "cli" here; the jmp opcode in the first byte is part of the +; "magic number" (using the term very loosely) for the DOS superblock. +; +bootsec equ $ +_start: jmp short start ; 2 bytes + nop ; 1 byte +; +; "Superblock" follows -- it's in the boot sector, so it's already +; loaded and ready for us +; +bsOemName db 'SYSLINUX' ; The SYS command sets this, so... +; +; These are the fields we actually care about. We end up expanding them +; all to dword size early in the code, so generate labels for both +; the expanded and unexpanded versions. +; +%macro superb 1 +bx %+ %1 equ SuperInfo+($-superblock)*8+4 +bs %+ %1 equ $ + zb 1 +%endmacro +%macro superw 1 +bx %+ %1 equ SuperInfo+($-superblock)*8 +bs %+ %1 equ $ + zw 1 +%endmacro +%macro superd 1 +bx %+ %1 equ $ ; no expansion for dwords +bs %+ %1 equ $ + zd 1 +%endmacro +superblock equ $ + superw BytesPerSec + superb SecPerClust + superw ResSectors + superb FATs + superw RootDirEnts + superw Sectors + superb Media + superw FATsecs + superw SecPerTrack + superw Heads +superinfo_size equ ($-superblock)-1 ; How much to expand + superd Hidden + superd HugeSectors + ; + ; This is as far as FAT12/16 and FAT32 are consistent + ; + zb 54 ; FAT12/16 need 26 more bytes, + ; FAT32 need 54 more bytes +superblock_len equ $-superblock + +SecPerClust equ bxSecPerClust +; +; Note we don't check the constraints above now; we did that at install +; time (we hope!) +; +start: + cli ; No interrupts yet, please + cld ; Copy upwards +; +; Set up the stack +; + xor ax,ax + mov ss,ax + mov sp,StackBuf ; Just below BSS + push es ; Save initial ES:DI -> $PnP pointer + push di + mov es,ax +; +; DS:SI may contain a partition table entry. Preserve it for us. +; + mov cx,8 ; Save partition info + mov di,PartInfo + rep movsw + + mov ds,ax ; Now we can initialize DS... + +; +; Now sautee the BIOS floppy info block to that it will support decent- +; size transfers; the floppy block is 11 bytes and is stored in the +; INT 1Eh vector (brilliant waste of resources, eh?) +; +; Of course, if BIOSes had been properly programmed, we wouldn't have +; had to waste precious space with this code. +; + mov bx,fdctab + lfs si,[bx] ; FS:SI -> original fdctab + push fs ; Save on stack in case we need to bail + push si + + ; Save the old fdctab even if hard disk so the stack layout + ; is the same. The instructions above do not change the flags + mov [DriveNumber],dl ; Save drive number in DL + and dl,dl ; If floppy disk (00-7F), assume no + ; partition table + js harddisk + +floppy: + mov cl,6 ; 12 bytes (CX == 0) + ; es:di -> FloppyTable already + ; This should be safe to do now, interrupts are off... + mov [bx],di ; FloppyTable + mov [bx+2],ax ; Segment 0 + fs rep movsw ; Faster to move words + mov cl,[bsSecPerTrack] ; Patch the sector count + mov [di-8],cl + ; AX == 0 here + int 13h ; Some BIOSes need this + + jmp short not_harddisk +; +; The drive number and possibly partition information was passed to us +; by the BIOS or previous boot loader (MBR). Current "best practice" is to +; trust that rather than what the superblock contains. +; +; Would it be better to zero out bsHidden if we don't have a partition table? +; +; Note: di points to beyond the end of PartInfo +; +harddisk: + test byte [di-16],7Fh ; Sanity check: "active flag" should + jnz no_partition ; be 00 or 80 + mov eax,[di-8] ; Partition offset (dword) + mov [bsHidden],eax +no_partition: +; +; Get disk drive parameters (don't trust the superblock.) Don't do this for +; floppy drives -- INT 13:08 on floppy drives will (may?) return info about +; what the *drive* supports, not about the *media*. Fortunately floppy disks +; tend to have a fixed, well-defined geometry which is stored in the superblock. +; + ; DL == drive # still + mov ah,08h + int 13h + jc no_driveparm + and ah,ah + jnz no_driveparm + shr dx,8 + inc dx ; Contains # of heads - 1 + mov [bsHeads],dx + and cx,3fh + mov [bsSecPerTrack],cx +no_driveparm: +not_harddisk: +; +; Ready to enable interrupts, captain +; + sti + +; +; Do we have EBIOS (EDD)? +; +eddcheck: + mov bx,55AAh + mov ah,41h ; EDD existence query + mov dl,[DriveNumber] + int 13h + jc .noedd + cmp bx,0AA55h + jne .noedd + test cl,1 ; Extended disk access functionality set + jz .noedd + ; + ; We have EDD support... + ; + mov byte [getlinsec.jmp+1],(getlinsec_ebios-(getlinsec.jmp+2)) +.noedd: + +; +; Load the first sector of LDLINUX.SYS; this used to be all proper +; with parsing the superblock and root directory; it doesn't fit +; together with EBIOS support, unfortunately. +; + mov eax,[FirstSector] ; Sector start + mov bx,ldlinux_sys ; Where to load it + call getonesec + + ; Some modicum of integrity checking + cmp dword [ldlinux_magic+4],LDLINUX_MAGIC^HEXDATE + jne kaboom + + ; Go for it... + jmp ldlinux_ent + +; +; getonesec: get one disk sector +; +getonesec: + mov bp,1 ; One sector + ; Fall through + +; +; getlinsec: load a sequence of BP floppy sector given by the linear sector +; number in EAX into the buffer at ES:BX. We try to optimize +; by loading up to a whole track at a time, but the user +; is responsible for not crossing a 64K boundary. +; (Yes, BP is weird for a count, but it was available...) +; +; On return, BX points to the first byte after the transferred +; block. +; +; This routine assumes CS == DS, and trashes most registers. +; +; Stylistic note: use "xchg" instead of "mov" when the source is a register +; that is dead from that point; this saves space. However, please keep +; the order to dst,src to keep things sane. +; +getlinsec: + add eax,[bsHidden] ; Add partition offset + xor edx,edx ; Zero-extend LBA (eventually allow 64 bits) + +.jmp: jmp strict short getlinsec_cbios + +; +; getlinsec_ebios: +; +; getlinsec implementation for EBIOS (EDD) +; +getlinsec_ebios: +.loop: + push bp ; Sectors left +.retry2: + call maxtrans ; Enforce maximum transfer size + movzx edi,bp ; Sectors we are about to read + mov cx,retry_count +.retry: + + ; Form DAPA on stack + push edx + push eax + push es + push bx + push di + push word 16 + mov si,sp + pushad + mov dl,[DriveNumber] + push ds + push ss + pop ds ; DS <- SS + mov ah,42h ; Extended Read + int 13h + pop ds + popad + lea sp,[si+16] ; Remove DAPA + jc .error + pop bp + add eax,edi ; Advance sector pointer + sub bp,di ; Sectors left + shl di,SECTOR_SHIFT ; 512-byte sectors + add bx,di ; Advance buffer pointer + and bp,bp + jnz .loop + + ret + +.error: + ; Some systems seem to get "stuck" in an error state when + ; using EBIOS. Doesn't happen when using CBIOS, which is + ; good, since some other systems get timeout failures + ; waiting for the floppy disk to spin up. + + pushad ; Try resetting the device + xor ax,ax + mov dl,[DriveNumber] + int 13h + popad + loop .retry ; CX-- and jump if not zero + + ;shr word [MaxTransfer],1 ; Reduce the transfer size + ;jnz .retry2 + + ; Total failure. Try falling back to CBIOS. + mov byte [getlinsec.jmp+1],(getlinsec_cbios-(getlinsec.jmp+2)) + ;mov byte [MaxTransfer],63 ; Max possibe CBIOS transfer + + pop bp + ; ... fall through ... + +; +; getlinsec_cbios: +; +; getlinsec implementation for legacy CBIOS +; +getlinsec_cbios: +.loop: + push edx + push eax + push bp + push bx + + movzx esi,word [bsSecPerTrack] + movzx edi,word [bsHeads] + ; + ; Dividing by sectors to get (track,sector): we may have + ; up to 2^18 tracks, so we need to use 32-bit arithmetric. + ; + div esi + xor cx,cx + xchg cx,dx ; CX <- sector index (0-based) + ; EDX <- 0 + ; eax = track # + div edi ; Convert track to head/cyl + + ; We should test this, but it doesn't fit... + ; cmp eax,1023 + ; ja .error + + ; + ; Now we have AX = cyl, DX = head, CX = sector (0-based), + ; BP = sectors to transfer, SI = bsSecPerTrack, + ; ES:BX = data target + ; + + call maxtrans ; Enforce maximum transfer size + + ; Must not cross track boundaries, so BP <= SI-CX + sub si,cx + cmp bp,si + jna .bp_ok + mov bp,si +.bp_ok: + + shl ah,6 ; Because IBM was STOOPID + ; and thought 8 bits were enough + ; then thought 10 bits were enough... + inc cx ; Sector numbers are 1-based, sigh + or cl,ah + mov ch,al + mov dh,dl + mov dl,[DriveNumber] + xchg ax,bp ; Sector to transfer count + mov ah,02h ; Read sectors + mov bp,retry_count +.retry: + pushad + int 13h + popad + jc .error +.resume: + movzx ecx,al ; ECX <- sectors transferred + shl ax,SECTOR_SHIFT ; Convert sectors in AL to bytes in AX + pop bx + add bx,ax + pop bp + pop eax + pop edx + add eax,ecx + sub bp,cx + jnz .loop + ret + +.error: + dec bp + jnz .retry + + xchg ax,bp ; Sectors transferred <- 0 + shr word [MaxTransfer],1 + jnz .resume + ; Fall through to disk_error + +; +; kaboom: write a message and bail out. +; +disk_error: +kaboom: + xor si,si + mov ss,si + mov sp,StackBuf-4 ; Reset stack + mov ds,si ; Reset data segment + pop dword [fdctab] ; Restore FDC table +.patch: ; When we have full code, intercept here + mov si,bailmsg + + ; Write error message, this assumes screen page 0 +.loop: lodsb + and al,al + jz .done + mov ah,0Eh ; Write to screen as TTY + mov bx,0007h ; Attribute + int 10h + jmp short .loop +.done: + cbw ; AH <- 0 +.again: int 16h ; Wait for keypress + ; NB: replaced by int 18h if + ; chosen at install time.. + int 19h ; And try once more to boot... +.norge: jmp short .norge ; If int 19h returned; this is the end + +; +; Truncate BP to MaxTransfer +; +maxtrans: + cmp bp,[MaxTransfer] + jna .ok + mov bp,[MaxTransfer] +.ok: ret + +; +; Error message on failure +; +bailmsg: db 'Boot error', 0Dh, 0Ah, 0 + + ; This fails if the boot sector overflows + zb 1F8h-($-$$) + +FirstSector dd 0xDEADBEEF ; Location of sector 1 +MaxTransfer dw 0x007F ; Max transfer size + +; This field will be filled in 0xAA55 by the installer, but we abuse it +; to house a pointer to the INT 16h instruction at +; kaboom.again, which gets patched to INT 18h in RAID mode. +bootsignature dw kaboom.again-bootsec + +; +; =========================================================================== +; End of boot sector +; =========================================================================== +; Start of LDLINUX.SYS +; =========================================================================== + +ldlinux_sys: + +syslinux_banner db 0Dh, 0Ah +%if IS_MDSLINUX + db 'MDSLINUX ' +%else + db 'SYSLINUX ' +%endif + db version_str, ' ', date, ' ', 0 + db 0Dh, 0Ah, 1Ah ; EOF if we "type" this in DOS + + align 8, db 0 +ldlinux_magic dd LDLINUX_MAGIC + dd LDLINUX_MAGIC^HEXDATE + +; +; This area is patched by the installer. It is found by looking for +; LDLINUX_MAGIC, plus 8 bytes. +; +patch_area: +LDLDwords dw 0 ; Total dwords starting at ldlinux_sys +LDLSectors dw 0 ; Number of sectors - (bootsec+this sec) +CheckSum dd 0 ; Checksum starting at ldlinux_sys + ; value = LDLINUX_MAGIC - [sum of dwords] + +; Space for up to 64 sectors, the theoretical maximum +SectorPtrs times 64 dd 0 + +ldlinux_ent: +; +; Note that some BIOSes are buggy and run the boot sector at 07C0:0000 +; instead of 0000:7C00 and the like. We don't want to add anything +; more to the boot sector, so it is written to not assume a fixed +; value in CS, but we don't want to deal with that anymore from now +; on. +; + jmp 0:.next +.next: + +; +; Tell the user we got this far +; + mov si,syslinux_banner + call writestr + +; +; Tell the user if we're using EBIOS or CBIOS +; +print_bios: + mov si,cbios_name + cmp byte [getlinsec.jmp+1],(getlinsec_ebios-(getlinsec.jmp+2)) + jne .cbios + mov si,ebios_name +.cbios: + mov [BIOSName],si + call writestr + + section .bss +%define HAVE_BIOSNAME 1 +BIOSName resw 1 + + section .text +; +; Now we read the rest of LDLINUX.SYS. Don't bother loading the first +; sector again, though. +; +load_rest: + mov si,SectorPtrs + mov bx,7C00h+2*SECTOR_SIZE ; Where we start loading + mov cx,[LDLSectors] + +.get_chunk: + jcxz .done + xor bp,bp + lodsd ; First sector of this chunk + + mov edx,eax + +.make_chunk: + inc bp + dec cx + jz .chunk_ready + inc edx ; Next linear sector + cmp [si],edx ; Does it match + jnz .chunk_ready ; If not, this is it + add si,4 ; If so, add sector to chunk + jmp short .make_chunk + +.chunk_ready: + call getlinsecsr + shl bp,SECTOR_SHIFT + add bx,bp + jmp .get_chunk + +.done: + +; +; All loaded up, verify that we got what we needed. +; Note: the checksum field is embedded in the checksum region, so +; by the time we get to the end it should all cancel out. +; +verify_checksum: + mov si,ldlinux_sys + mov cx,[LDLDwords] + mov edx,-LDLINUX_MAGIC +.checksum: + lodsd + add edx,eax + loop .checksum + + and edx,edx ; Should be zero + jz all_read ; We're cool, go for it! + +; +; Uh-oh, something went bad... +; + mov si,checksumerr_msg + call writestr + jmp kaboom + +; +; ----------------------------------------------------------------------------- +; Subroutines that have to be in the first sector +; ----------------------------------------------------------------------------- + +; +; +; writestr: write a null-terminated string to the console +; This assumes we're on page 0. This is only used for early +; messages, so it should be OK. +; +writestr: +.loop: lodsb + and al,al + jz .return + mov ah,0Eh ; Write to screen as TTY + mov bx,0007h ; Attribute + int 10h + jmp short .loop +.return: ret + + +; getlinsecsr: save registers, call getlinsec, restore registers +; +getlinsecsr: pushad + call getlinsec + popad + ret + +; +; Checksum error message +; +checksumerr_msg db ' Load error - ', 0 ; Boot failed appended + +; +; BIOS type string +; +cbios_name db 'CBIOS', 0 +ebios_name db 'EBIOS', 0 + +; +; Debug routine +; +%ifdef debug +safedumpregs: + cmp word [Debug_Magic],0D00Dh + jnz nc_return + jmp dumpregs +%endif + +rl_checkpt equ $ ; Must be <= 8000h + +rl_checkpt_off equ ($-$$) +%ifndef DEPEND +%if rl_checkpt_off > 400h +%error "Sector 1 overflow" +%endif +%endif + +; ---------------------------------------------------------------------------- +; End of code and data that have to be in the first sector +; ---------------------------------------------------------------------------- + +all_read: +; +; Let the user (and programmer!) know we got this far. This used to be +; in Sector 1, but makes a lot more sense here. +; + mov si,copyright_str + call writestr + + +; +; Insane hack to expand the superblock to dwords +; +expand_super: + xor eax,eax + mov si,superblock + mov di,SuperInfo + mov cx,superinfo_size +.loop: + lodsw + dec si + stosd ; Store expanded word + xor ah,ah + stosd ; Store expanded byte + loop .loop + +; +; Compute some information about this filesystem. +; + +; First, generate the map of regions +genfatinfo: + mov edx,[bxSectors] + and dx,dx + jnz .have_secs + mov edx,[bsHugeSectors] +.have_secs: + mov [TotalSectors],edx + + mov eax,[bxResSectors] + mov [FAT],eax ; Beginning of FAT + mov edx,[bxFATsecs] + and dx,dx + jnz .have_fatsecs + mov edx,[bootsec+36] ; FAT32 BPB_FATsz32 +.have_fatsecs: + imul edx,[bxFATs] + add eax,edx + mov [RootDirArea],eax ; Beginning of root directory + mov [RootDir],eax ; For FAT12/16 == root dir location + + mov edx,[bxRootDirEnts] + add dx,SECTOR_SIZE/32-1 + shr dx,SECTOR_SHIFT-5 + mov [RootDirSize],edx + add eax,edx + mov [DataArea],eax ; Beginning of data area + +; Next, generate a cluster size shift count and mask + mov eax,[bxSecPerClust] + bsr cx,ax + mov [ClustShift],cl + push cx + add cl,SECTOR_SHIFT + mov [ClustByteShift],cl + pop cx + dec ax + mov [ClustMask],eax + inc ax + shl eax,SECTOR_SHIFT + mov [ClustSize],eax + +; +; FAT12, FAT16 or FAT28^H^H32? This computation is fscking ridiculous. +; +getfattype: + mov eax,[TotalSectors] + sub eax,[DataArea] + shr eax,cl ; cl == ClustShift + mov cl,nextcluster_fat12-(nextcluster+2) + cmp eax,4085 ; FAT12 limit + jb .setsize + mov cl,nextcluster_fat16-(nextcluster+2) + cmp eax,65525 ; FAT16 limit + jb .setsize + ; + ; FAT32, root directory is a cluster chain + ; + mov cl,[ClustShift] + mov eax,[bootsec+44] ; Root directory cluster + sub eax,2 + shl eax,cl + add eax,[DataArea] + mov [RootDir],eax + mov cl,nextcluster_fat28-(nextcluster+2) +.setsize: + mov byte [nextcluster+1],cl + +; +; Common initialization code +; +%include "cpuinit.inc" +%include "init.inc" + +; +; Initialize the metadata cache +; + call initcache + +; +; Now, everything is "up and running"... patch kaboom for more +; verbosity and using the full screen system +; + ; E9 = JMP NEAR + mov dword [kaboom.patch],0e9h+((kaboom2-(kaboom.patch+3)) << 8) + +; +; Now we're all set to start with our *real* business. First load the +; configuration file (if any) and parse it. +; +; In previous versions I avoided using 32-bit registers because of a +; rumour some BIOSes clobbered the upper half of 32-bit registers at +; random. I figure, though, that if there are any of those still left +; they probably won't be trying to install Linux on them... +; +; The code is still ripe with 16-bitisms, though. Not worth the hassle +; to take'm out. In fact, we may want to put them back if we're going +; to boot ELKS at some point. +; + +; +; Load configuration file +; + mov si,config_name ; Save configuration file name + mov di,ConfigName + call strcpy + + mov di,syslinux_cfg1 + call open + jnz .config_open + mov di,syslinux_cfg2 + call open + jnz .config_open + mov di,syslinux_cfg3 + call open + jz no_config_file +.config_open: + mov eax,[PrevDir] ; Make the directory with syslinux.cfg ... + mov [CurrentDir],eax ; ... the current directory + +; +; Now we have the config file open. Parse the config file and +; run the user interface. +; +%include "ui.inc" + +; +; allocate_file: Allocate a file structure +; +; If successful: +; ZF set +; BX = file pointer +; In unsuccessful: +; ZF clear +; +allocate_file: + TRACER 'a' + push cx + mov bx,Files + mov cx,MAX_OPEN +.check: cmp dword [bx], byte 0 + je .found + add bx,open_file_t_size ; ZF = 0 + loop .check + ; ZF = 0 if we fell out of the loop +.found: pop cx + ret + +; +; search_dos_dir: +; Search a specific directory for a pre-mangled filename in +; MangledBuf, in the directory starting in sector EAX. +; +; NOTE: This file considers finding a zero-length file an +; error. This is so we don't have to deal with that special +; case elsewhere in the program (most loops have the test +; at the end). +; +; Assumes DS == ES == CS. +; +; If successful: +; ZF clear +; SI = file pointer +; EAX = file length (MAY BE ZERO!) +; DL = file attributes +; If unsuccessful +; ZF set +; + +search_dos_dir: + push bx + call allocate_file + jnz .alloc_failure + + push cx + push gs + push es + push ds + pop es ; ES = DS + +.scansector: + ; EAX <- directory sector to scan + call getcachesector + ; GS:SI now points to this sector + + mov cx,SECTOR_SIZE/32 ; 32 == directory entry size +.scanentry: + cmp byte [gs:si],0 + jz .failure ; Hit directory high water mark + test byte [gs:si+11],8 ; Ignore volume labels and + ; VFAT long filename entries + jnz .nomatch + push cx + push si + push di + mov di,MangledBuf + mov cx,11 + gs repe cmpsb + pop di + pop si + pop cx + jz .found +.nomatch: + add si,32 + loop .scanentry + + call nextsector + jnc .scansector ; CF is set if we're at end + + ; If we get here, we failed +.failure: + pop es + pop gs + pop cx +.alloc_failure: + pop bx + xor eax,eax ; ZF <- 1 + ret +.found: + mov eax,[gs:si+28] ; File size + add eax,SECTOR_SIZE-1 + shr eax,SECTOR_SHIFT + mov [bx+4],eax ; Sector count + + mov cl,[ClustShift] + mov dx,[gs:si+20] ; High cluster word + shl edx,16 + mov dx,[gs:si+26] ; Low cluster word + sub edx,2 + shl edx,cl + add edx,[DataArea] + mov [bx],edx ; Starting sector + + mov eax,[gs:si+28] ; File length again + mov dl,[gs:si+11] ; File attribute + mov si,bx ; File pointer... + and si,si ; ZF <- 0 + + pop es + pop gs + pop cx + pop bx + ret + +; +; close_file: +; Deallocates a file structure (pointer in SI) +; Assumes CS == DS. +; +close_file: + and si,si + jz .closed + mov dword [si],0 ; First dword == file_sector + xor si,si +.closed: ret + +; +; searchdir: +; +; Open a file +; +; On entry: +; DS:DI = filename +; If successful: +; ZF clear +; SI = file pointer +; EAX = file length in bytes +; If unsuccessful +; ZF set +; +; Assumes CS == DS == ES, and trashes BX and CX. +; +searchdir: + mov eax,[CurrentDir] + cmp byte [di],'/' ; Root directory? + jne .notroot + mov eax,[RootDir] + inc di +.notroot: + +.pathwalk: + push eax ; <A> Current directory sector + mov si,di +.findend: + lodsb + cmp al,' ' + jbe .endpath + cmp al,'/' + jne .findend +.endpath: + xchg si,di + pop eax ; <A> Current directory sector + + mov [PrevDir],eax ; Remember last directory searched + + push di + call mangle_dos_name ; MangledBuf <- component + call search_dos_dir + pop di + jz .notfound ; Pathname component missing + + cmp byte [di-1],'/' ; Do we expect a directory + je .isdir + + ; Otherwise, it should be a file +.isfile: + test dl,18h ; Subdirectory|Volume Label + jnz .badfile ; If not a file, it's a bad thing + + ; SI and EAX are already set + mov [si+file_bytesleft],eax + push eax + add eax,SECTOR_SIZE-1 + shr eax,SECTOR_SHIFT + mov [si+file_left],eax ; Sectors left + pop eax + and eax,eax ; EAX != 0 + jz .badfile + ret ; Done! + + ; If we expected a directory, it better be one... +.isdir: + test dl,10h ; Subdirectory + jz .badfile + + xor eax,eax + xchg eax,[si+file_sector] ; Get sector number and free file structure + jmp .pathwalk ; Walk the next bit of the path + +.badfile: + xor eax,eax + mov [si],eax ; Free file structure + +.notfound: + xor eax,eax + ret + + section .bss + alignb 4 +CurrentDir resd 1 ; Current directory +PrevDir resd 1 ; Last scanned directory + + section .text + +; +; +; kaboom2: once everything is loaded, replace the part of kaboom +; starting with "kaboom.patch" with this part + +kaboom2: + mov si,err_bootfailed + call cwritestr + cmp byte [kaboom.again+1],18h ; INT 18h version? + je .int18 + call getchar + call vgaclearmode + int 19h ; And try once more to boot... +.norge: jmp short .norge ; If int 19h returned; this is the end +.int18: + call vgaclearmode + int 18h +.noreg: jmp short .noreg ; Nynorsk + +; +; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed +; to by ES:DI; ends on encountering any whitespace. +; DI is preserved. +; +; This verifies that a filename is < FILENAME_MAX characters, +; doesn't contain whitespace, zero-pads the output buffer, +; and removes trailing dots and redundant slashes, plus changes +; backslashes to forward slashes, +; so "repe cmpsb" can do a compare, and the path-searching routine +; gets a bit of an easier job. +; +; +mangle_name: + push di + push bx + xor ax,ax + mov cx,FILENAME_MAX-1 + mov bx,di + +.mn_loop: + lodsb + cmp al,' ' ; If control or space, end + jna .mn_end + cmp al,'\' ; Backslash? + jne .mn_not_bs + mov al,'/' ; Change to forward slash +.mn_not_bs: + cmp al,ah ; Repeated slash? + je .mn_skip + xor ah,ah + cmp al,'/' + jne .mn_ok + mov ah,al +.mn_ok stosb +.mn_skip: loop .mn_loop +.mn_end: + cmp bx,di ; At the beginning of the buffer? + jbe .mn_zero + cmp byte [es:di-1],'.' ; Terminal dot? + je .mn_kill + cmp byte [es:di-1],'/' ; Terminal slash? + jne .mn_zero +.mn_kill: dec di ; If so, remove it + inc cx + jmp short .mn_end +.mn_zero: + inc cx ; At least one null byte + xor ax,ax ; Zero-fill name + rep stosb + pop bx + pop di + ret ; Done + +; +; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled +; filename to the conventional representation. This is needed +; for the BOOT_IMAGE= parameter for the kernel. +; NOTE: A 13-byte buffer is mandatory, even if the string is +; known to be shorter. +; +; DS:SI -> input mangled file name +; ES:DI -> output buffer +; +; On return, DI points to the first byte after the output name, +; which is set to a null byte. +; +unmangle_name: call strcpy + dec di ; Point to final null byte + ret + +; +; mangle_dos_name: +; Mangle a DOS filename component pointed to by DS:SI +; into [MangledBuf]; ends on encountering any whitespace or slash. +; Assumes CS == DS == ES. +; + +mangle_dos_name: + pusha + mov di,MangledBuf + + mov cx,11 ; # of bytes to write +.loop: + lodsb + cmp al,' ' ; If control or space, end + jna .end + cmp al,'/' ; Slash, too + je .end + cmp al,'.' ; Period -> space-fill + je .is_period + cmp al,'a' + jb .not_lower + cmp al,'z' + ja .not_uslower + sub al,020h + jmp short .not_lower +.is_period: mov al,' ' ; We need to space-fill +.period_loop: cmp cx,3 ; If <= 3 characters left + jbe .loop ; Just ignore it + stosb ; Otherwise, write a period + loop .period_loop ; Dec CX and (always) jump +.not_uslower: cmp al,ucase_low + jb .not_lower + cmp al,ucase_high + ja .not_lower + mov bx,ucase_tab-ucase_low + xlatb +.not_lower: stosb + loop .loop ; Don't continue if too long +.end: + mov al,' ' ; Space-fill name + rep stosb ; Doesn't do anything if CX=0 + popa + ret ; Done + + section .bss +MangledBuf resb 11 + + section .text +; +; Case tables for extended characters; this is technically code page 865, +; but code page 437 users will probably not miss not being able to use the +; cent sign in kernel images too much :-) +; +; The table only covers the range 129 to 164; the rest we can deal with. +; + section .data + +ucase_low equ 129 +ucase_high equ 164 +ucase_tab db 154, 144, 'A', 142, 'A', 143, 128, 'EEEIII' + db 142, 143, 144, 146, 146, 'O', 153, 'OUUY', 153, 154 + db 157, 156, 157, 158, 159, 'AIOU', 165 + + section .text +; +; getfssec_edx: Get multiple sectors from a file +; +; This routine makes sure the subtransfers do not cross a 64K boundary, +; and will correct the situation if it does, UNLESS *sectors* cross +; 64K boundaries. +; +; ES:BX -> Buffer +; EDX -> Current sector number +; CX -> Sector count (0FFFFh = until end of file) +; Must not exceed the ES segment +; Returns EDX=0, CF=1 on EOF (not necessarily error) +; All arguments are advanced to reflect data read. +; +getfssec_edx: + push ebp + push eax +.getfragment: + xor ebp,ebp ; Fragment sector count + push edx ; Starting sector pointer +.getseccnt: + inc bp + dec cx + jz .do_read + xor eax,eax + mov ax,es + shl ax,4 + add ax,bx ; Now AX = how far into 64K block we are + not ax ; Bytes left in 64K block + inc eax + shr eax,SECTOR_SHIFT ; Sectors left in 64K block + cmp bp,ax + jnb .do_read ; Unless there is at least 1 more sector room... + mov eax,edx ; Current sector + inc edx ; Predict it's the linearly next sector + call nextsector + jc .do_read + cmp edx,eax ; Did it match? + jz .getseccnt +.do_read: + pop eax ; Starting sector pointer + call getlinsecsr + lea eax,[eax+ebp-1] ; This is the last sector actually read + shl bp,9 + add bx,bp ; Adjust buffer pointer + call nextsector + jc .eof + mov edx,eax + and cx,cx + jnz .getfragment +.done: + pop eax + pop ebp + ret +.eof: + xor edx,edx + stc + jmp .done + +; +; getfssec: Get multiple sectors from a file +; +; Same as above, except SI is a pointer to a open_file_t +; +; ES:BX -> Buffer +; DS:SI -> Pointer to open_file_t +; CX -> Sector count (0FFFFh = until end of file) +; Must not exceed the ES segment +; Returns CF=1 on EOF (not necessarily error) +; ECX returns number of bytes read. +; All arguments are advanced to reflect data read. +; +getfssec: + push edx + movzx edx,cx + push edx ; Zero-extended CX + cmp edx,[si+file_left] + jbe .sizeok + mov edx,[si+file_left] + mov cx,dx +.sizeok: + sub [si+file_left],edx + mov edx,[si+file_sector] + call getfssec_edx + mov [si+file_sector],edx + pop ecx ; Sectors requested read + shl ecx,SECTOR_SHIFT + cmp ecx,[si+file_bytesleft] + ja .eof +.noteof: + sub [si+file_bytesleft],ecx ; CF <- 0 + pop edx + ret +.eof: + mov ecx,[si+file_bytesleft] + call close_file + pop edx + stc + ret + +; +; nextcluster: Advance a cluster pointer in EDI to the next cluster +; pointed at in the FAT tables. CF=0 on return if end of file. +; +nextcluster: + jmp strict short nextcluster_fat28 ; This gets patched + +nextcluster_fat12: + push eax + push edx + push bx + push cx + push si + mov edx,edi + shr edi,1 + pushf ; Save the shifted-out LSB (=CF) + add edx,edi + mov eax,edx + shr eax,9 + call getfatsector + mov bx,dx + and bx,1FFh + mov cl,[gs:si+bx] + inc edx + mov eax,edx + shr eax,9 + call getfatsector + mov bx,dx + and bx,1FFh + mov ch,[gs:si+bx] + popf + jnc .even + shr cx,4 +.even: and cx,0FFFh + movzx edi,cx + cmp di,0FF0h + pop si + pop cx + pop bx + pop edx + pop eax + ret + +; +; FAT16 decoding routine. +; +nextcluster_fat16: + push eax + push si + push bx + mov eax,edi + shr eax,SECTOR_SHIFT-1 + call getfatsector + mov bx,di + add bx,bx + and bx,1FEh + movzx edi,word [gs:si+bx] + cmp di,0FFF0h + pop bx + pop si + pop eax + ret +; +; FAT28 ("FAT32") decoding routine. +; +nextcluster_fat28: + push eax + push si + push bx + mov eax,edi + shr eax,SECTOR_SHIFT-2 + call getfatsector + mov bx,di + add bx,bx + add bx,bx + and bx,1FCh + mov edi,dword [gs:si+bx] + and edi,0FFFFFFFh ; 28 bits only + cmp edi,0FFFFFF0h + pop bx + pop si + pop eax + ret + +; +; nextsector: Given a sector in EAX on input, return the next sector +; of the same filesystem object, which may be the root +; directory or a cluster chain. Returns EOF. +; +; Assumes CS == DS. +; +nextsector: + push edi + push edx + mov edx,[DataArea] + mov edi,eax + sub edi,edx + jae .isdata + + ; Root directory + inc eax + cmp eax,edx + cmc + jmp .done + +.isdata: + not edi + test edi,[ClustMask] + jz .endcluster + + ; It's not the final sector in a cluster + inc eax + jmp .done + +.endcluster: + push gs ; nextcluster trashes gs + push cx + not edi + mov cl,[ClustShift] + shr edi,cl + add edi,2 + + ; Now EDI contains the cluster number + call nextcluster + cmc + jc .exit ; There isn't anything else... + + ; New cluster number now in EDI + sub edi,2 + shl edi,cl ; CF <- 0, unless something is very wrong + lea eax,[edi+edx] +.exit: + pop cx + pop gs +.done: + pop edx + pop edi + ret + +; +; getfatsector: Check for a particular sector (in EAX) in the FAT cache, +; and return a pointer in GS:SI, loading it if needed. +; +; Assumes CS == DS. +; +getfatsector: + add eax,[FAT] ; FAT starting address + jmp getcachesector + +; ----------------------------------------------------------------------------- +; Common modules +; ----------------------------------------------------------------------------- + +%include "getc.inc" ; getc et al +%include "conio.inc" ; Console I/O +%include "plaincon.inc" ; writechr +%include "writestr.inc" ; String output +%include "configinit.inc" ; Initialize configuration +%include "parseconfig.inc" ; High-level config file handling +%include "parsecmd.inc" ; Low-level config file handling +%include "bcopy32.inc" ; 32-bit bcopy +%include "loadhigh.inc" ; Load a file into high memory +%include "font.inc" ; VGA font stuff +%include "graphics.inc" ; VGA graphics +%include "highmem.inc" ; High memory sizing +%include "strcpy.inc" ; strcpy() +%include "cache.inc" ; Metadata disk cache +%include "adv.inc" ; Auxillary Data Vector +%include "localboot.inc" ; Disk-based local boot + +; ----------------------------------------------------------------------------- +; Begin data section +; ----------------------------------------------------------------------------- + + section .data +copyright_str db ' Copyright (C) 1994-', year, ' H. Peter Anvin' + db CR, LF, 0 +err_bootfailed db CR, LF, 'Boot failed: please change disks and press ' + db 'a key to continue.', CR, LF, 0 +syslinux_cfg1 db '/boot' ; /boot/syslinux/syslinux.cfg +syslinux_cfg2 db '/syslinux' ; /syslinux/syslinux.cfg +syslinux_cfg3 db '/' ; /syslinux.cfg +config_name db 'syslinux.cfg', 0 ; syslinux.cfg + +; +; Command line options we'd like to take a look at +; +; mem= and vga= are handled as normal 32-bit integer values +initrd_cmd db 'initrd=' +initrd_cmd_len equ 7 + +; +; Config file keyword table +; +%include "keywords.inc" + +; +; Extensions to search for (in *forward* order). +; +exten_table: db '.cbt' ; COMBOOT (specific) + db '.bss' ; Boot Sector (add superblock) + db '.bs', 0 ; Boot Sector + db '.com' ; COMBOOT (same as DOS) + db '.c32' ; COM32 +exten_table_end: + dd 0, 0 ; Need 8 null bytes here + +; +; Misc initialized (data) variables +; +%ifdef debug ; This code for debugging only +debug_magic dw 0D00Dh ; Debug code sentinel +%endif + + alignb 4, db 0 +BufSafe dw trackbufsize/SECTOR_SIZE ; Clusters we can load into trackbuf +BufSafeBytes dw trackbufsize ; = how many bytes? +%ifndef DEPEND +%if ( trackbufsize % SECTOR_SIZE ) != 0 +%error trackbufsize must be a multiple of SECTOR_SIZE +%endif +%endif |
