My biggest request is tips for optimization, so anything you can give me would be nice. Also, telling me whether or not it would actually work on a partitioned hard drive would be great.
Code dump (The values in the BPB evaluate to 0x0101... to make copying and pasting the binary data easier):
Code: Select all
;; Stage 1 bootloader for Firebird O.S
;; FAT32 Partition version
;; Version 2.0
org 0x7C00
bits 16
BS_jmpBoot:
jmp start
nop
;; FAT fields, labeled here for convenience
BS_OEMName:
dd 16843009
dd 16843009
BPB_BytsPerSec:
dw 257
BPB_SecPerClus:
db 1
BPB_ResvdSecCnt:
dw 257
BPB_NumFATs:
db 1
BPB_RootEntCnt:
dw 257
BPB_TotSec16:
dw 257
BPB_Media:
db 1
BPB_FATSz16:
dw 257
BPB_SecPerTrk:
dw 257
BPB_NumHeads:
dw 257
BPB_HiddSec:
dd 16843009
BPB_TotSec32:
dd 16843009
BPB_FATSz32:
dd 16843009
BPB_ExtFlags:
;; Bits 0-3 = Active FAT, 7 = !FAT mirroring
dw 257
BPB_FSVer:
dw 257
BPB_RootClus:
dd 16843009
BPB_FSInfo:
dw 257
BPB_BkBootSec:
dw 257
BPB_Reserved:
dd 16843009
dd 16843009
dd 16843009
BS_DrvNum:
db 1
BS_Reseved1:
db 1
BS_BootSig:
db 1
BS_VolID:
dd 16843009
BS_VolLab:
dd 16843009
dd 16843009
dw 257
db 1
BS_FilSysType:
dd 16843009
dd 16843009
start:
;; save DS so we can get the value at ds:si later
push ds
pop fs
;; Set CS, DS, ES, & SS to a known value
;; I used eax because I want to have the upper word cleared later
xor eax, eax
mov ds, ax
mov es, ax
jmp 0:loadCS
loadCS:
;; set up the stack ( a temporary 512-byte stack )
mov ss, ax
mov sp, 0x8000
mov bp, sp
;; LBA packet
mov [LBAindex+4], eax
mov al, 16
mov [LBApacket], ax
;; Save the boot drive
;; BootDrive = dl
push dx
;; check if we're on a partition
xor al, al
mov bx, bp
mov cl, 1
mov sp, LBApacketAddr
call readDiskLBA
lea sp, [BootDrive]
;; Now to compare.
;; If the sector we just loaded == this code,
;; we're on a flat disk. Else, let's assume a partition
mov si, 0x7C00
mov di, bp
mov cx, 512
repe cmpsb
;; get the partition start from the MBR
mov ecx, [fs:si+8]
jne .partitioned
;; if we're here, it's not actually partitioned,
;; so clear the 'offset'
xor ecx, ecx
.partitioned:
;; calculate the first data sector
;; FirstDataSector = BPB_NumFATs * BPB_FATSz32 + BPB_ResvdSecCnt + partitionStart
mov al, [BPB_NumFATs]
mul dword [BPB_FATSz32]
movzx ebx, word [BPB_ResvdSecCnt]
add eax, ebx
add eax, ecx
push eax
;; now let's get the location of the first FAT
;; FATsector = BPB_ResvdSecCnt + partitionStart
movzx eax, word [BPB_ResvdSecCnt]
add eax, ecx
push eax
;; BytsPerCluster = BPB_BytsPerSec * BPB_SecPerClus
mov ax, [BPB_BytsPerSec]
mul word [BPB_SecPerClus]
push eax
;; FATClusterMask = 0x0FFFFFFF
mov eax, 0x0FFFFFFF
push eax
;; FATEoFMask = 2nd 'cluster' value & (bitwise) FATClusterMask
;; load the FAT
mov eax, [FATsector]
mov bx, bp
mov cl, 1
mov sp, LBApacketAddr
call readDiskLBA
lea sp, [FATClusterMask]
;; Get the second cluster's value
mov eax, [bp+4]
and eax, [FATClusterMask]
push eax
;; CurrentCluster = BPB_RootClus
mov eax, [BPB_RootClus]
push eax
;; Fortunately, clusters are relative to FirstDataSector
;; Reserve the LBA packet,
;; (even though we've already been using it)
sub sp, byte 16
mov di, bp
;; Stack is now as follows:
BootDrive equ BP- 2
FirstDataSector equ BP- 6
FATsector equ BP-10
BytsPerCluster equ BP-14
FATClusterMask equ BP-18
FATEoFMask equ BP-22
CurrentCluster equ BP-26
LBAindex equ BP-36
LBAseg equ BP-38
LBAaddr equ BP-40
LBAcount equ BP-42
LBApacket equ BP-44
LBApacketAddr equ 0x8000-44
;; Load the first root directory cluster
call readCluster
;; parse first cluster to see if it has what we want
nextDirCluster:
;; Set bx to the end of the cluster
mov bx, [BytsPerCluster]
add bx, bp
;; ax = 0x8000 - sizeof(FAT_DIR_entry)
;; this simplifies the upcoming loop a bit
lea ax, [bp-32]
findloop:
;; move to next entry
add ax, 32
;; check if we're at the end of the cluster
cmp ax, bx
;; if so, handle it
jz notFound
;; I got 99 problems, bein' found is one.
;; If you're havin' directory problems,
;; I feel bad for you, son.
;; (Too much?)
;; else let's check the entry
mov si, ax
mov di, fileName
mov cx, 11
;; compare names
repe cmpsb
;; if not the same, try next entry
jnz findloop
;; file found!
;; +9 and +15 because SI is already 11 bytes into the entry
mov ax, [si+9]
sal eax, 16
mov ax, [si+15]
;; eax = cluster of file
mov [CurrentCluster], eax
mov di, bp
loadFileLoop:
;; if we're already at the EoF, this won't read anything
call readCluster
;; it will, however, set the carry flag to indicate that we're at the EoF
;; so if( !cf )
;; keep loading clusters
jnc loadFileLoop
;; else
;; jump to the second stage
jmp bp
notFound:
;; try reading the next cluster
call readCluster
jnc nextDirCluster
;; if this was the last one
;; show an error
mov si, noFile
jmp putStr
;; A few useful routines
;; readCluster :: ES:DI = dest
readCluster:
;; Check if we're already at the EoF
mov eax, [CurrentCluster]
and eax, [FATClusterMask]
cmp eax, [FATEoFMask]
;; if so, bail
jz eofLoad
;; Get the first sector of the cluster
;; Sector = (cluster - 2) * BPB_SecPerClus + FirstDataSector
sub eax, 2
movzx ebx, byte [BPB_SecPerClus]
mul ebx
add eax, [FirstDataSector]
;; eax = first sector of cluster
;; fetch the cluster and put it where the user wanted
mov bx, di
mov cl, [BPB_SecPerClus]
call readDiskLBA
;; increment the destination pointer
add di, [BytsPerCluster]
;; we've trashed eax, so let's get it back
;; so we can store the next cluster
mov eax, [CurrentCluster]
;; now, let's get the number of
;; cluster pointers in a sector
movzx ebx, word [BPB_BytsPerSec]
shr bx, 2
;; now that we have that, let's figure
;; out the offset, in sectors, of the
;; current cluster pointer
xor edx, edx
div eax, ebx
;; now that we have those, let's save the clusterpointer index
push dx
;; add in the location of the first FAT sector
;; to get the location on disk
add eax, [FATsector]
;; figure out where to put it
mov bx, 0x7C00
sub bx, [BPB_BytsPerSec]
mov si, bx
mov cl, 1
call readDiskLBA
pop dx
;; dx = index of desired clusterpointer
;; let's figure out the memory offset
;; for the cluster pointer
sal dx, 2
add si, dx
;; load the next cluster
mov eax, [si]
;; and set it to the new current cluster
mov [CurrentCluster], eax
;; clear the EoF flag and return
clc
ret
eofLoad:
;; set the EoF flag and return
stc
ret
readDiskLBA:
push si
;; Set the LBA packet parameters
mov [LBAindex], eax
mov [LBAcount], cl
mov [LBAseg], es
mov [LBAaddr], bx
;; set the error count to 0
xor al, al
push ax
.tryLoop:
mov si, LBApacketAddr
mov ah, 0x42
mov dl, [BootDrive]
int 13h
jc .readError
pop ax
pop si
ret
.readError:
;; whoops! there was an error reading the drive!
;; Let's check and see if we've already tried too many times
;; grab the value
pop ax
inc al
push ax
;; It just so happens that the number of times we want to loop
;; also can be used as the bit mask for comparing. How useful!
and al, 0x04
jz .tryLoop
freeze:
;; Too many failures, display an error and freeze
mov si, readFail
putStr:
;; ah = useTeletype
mov ah, 0x0E
;; bh = use page 0, bl = useless in Bochs
xor bx, bx
.loopPut:
;; lodsb == mov al, si \ inc si
;; too bad it doesn't do "or al, al" also
lodsb
;; check if al == 0
or al, al
;; if so, we're done
.here
jz .here
;; otherwise, let's put it
int 10h
;; and go again for the next character
jmp .loopPut
;; Some basic data
readFail:
db "Drive read failed!", 0
noFile:
db "No FBOOT.SYS!", 0
fileName:
db "FBOOT SYS"
;; this is here in case I make changes
times 510-($-$$) db 0
;; Standard end of boot sector
db 0x55, 0xAA