Writing a FAT16 Bootsector for USB booting
Posted: Thu Mar 19, 2009 11:53 am
Hi everyone,
I've been working on this for a week now and haven't had much luck. Hopefully someone here may know more.
I am writing a FAT16 boot sector that loads a second level loader. It works fine in a virtual machine booting from a 32MB HD but what I really want to get working is booting from a FAT16 formatted USB drive.
This issue I am having is with the BIOS calls to read the disk. What BIOS calls can I use? I have tried INT 0x13/AH=0x42 but get an error if I try to read more than 1 block (which I assume is a sector, aka 512bytes). I modified my code to only read one at a time and get no error but it also doesn't appear to read anything in.
Does anyone have an ideas on this? Will I need to use INT 0x13/AH=0x02 instead?
Here is the code I have at present:
I've been working on this for a week now and haven't had much luck. Hopefully someone here may know more.
I am writing a FAT16 boot sector that loads a second level loader. It works fine in a virtual machine booting from a 32MB HD but what I really want to get working is booting from a FAT16 formatted USB drive.
This issue I am having is with the BIOS calls to read the disk. What BIOS calls can I use? I have tried INT 0x13/AH=0x42 but get an error if I try to read more than 1 block (which I assume is a sector, aka 512bytes). I modified my code to only read one at a time and get no error but it also doesn't appear to read anything in.
Does anyone have an ideas on this? Will I need to use INT 0x13/AH=0x02 instead?
Here is the code I have at present:
Code: Select all
; FAT16 Boot sector using BIOS HD reads
; Version 1.0 - March 17, 2009
; Written by Ian Seyler (www.returninfinity.com)
;
; =============================================================================
; = License =
; =============================================================================
; Copyright (c) 2009 Ian Seyler
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
; =============================================================================
;
;
; =============================================================================
; = Notes =
; =============================================================================
; This boot sector looks for the file in the "Filename" variable and loads it
; to address 0x0000:0x8000. Once loaded it is executed.
; The file can be up to 32KB in size.
;
; This boot sector and the file it loads all stay within one 64K segment.
;
; No error checking is really done.. but as of version 1.0 there are 84 bytes
; left to implement some.
;
; Memory map (not to scale)
;
; +--------+ 0000:FFFF
; | Load |
; |Location| The file in Filename is loaded at 0x8000
; |--------| 0000:8000
; | Vars | A few local variables are stored starting at 0x7E00
; |--------| 0000:7E00
; | Boot |
; | Sector | The boot sector from the drive is loaded to 0x7C00 by the BIOS
; |--------| 0000:7C00
; | Temp |
; | Buffer | A 512 byte temp buffer starts at 0x7A00
; |--------| 0000:7A00
; | Stack | Stack moves down from 0x7A00 (shouldn't grow above 256 bytes)
; |--------| 0000:7900
; | BIOS |
; | Misc |
; +--------+ 0000:0000
; =============================================================================
[BITS 16] ; We are in 16-bit real mode
[ORG 0x7c00] ; This is a boot sector
;-----------------------------------------------------------------------
; Entry for boot sector. Jumps over the FAT data
;-----------------------------------------------------------------------
start:
jmp short real_start
nop
;-----------------------------------------------------------------------
; FAT Info
;-----------------------------------------------------------------------
OEMName times 8 db 0
BytesPerSector dw 0
SectorsPerCluster db 0
ReservedSectors dw 0
NumberOfCopiesOfFAT db 0
MaximumRootDirectoryEntries dw 0
NumberOfSectorsInPartitionSmallerThan32MB dw 0
MediaDescriptor db 0
SectorsPerFAT dw 0
SectorsPerTrack dw 0
NumberOfHeads dw 0
NumberOfHiddenSectorsInPartition dd 0
NumberOfSectorsInPartition dd 0
LogicalDriveNumberOfPartition dw 0
ExtendedSignature db 0
SerialNumber dd 0
VolumeName times 11 db 0
FATName times 8 db 0
;-----------------------------------------------------------------------
; Boot Code
; For debugging purposes this starts at 0x7C3E
;-----------------------------------------------------------------------
real_start:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax ; stack and BP-relative moves up, too
mov [drive], dl ; BIOS passes drive number in DL
xor esi, esi
xor edi, edi
xor ebp, ebp
mov esp, 0x7A00 ; set stack to move down from this address
mov si, msg_Loading
call print ; modifies AX BX SI
; Calculate the starting sector of the Root Directory
xor eax, eax
xor ebx, ebx
mov ax, [SectorsPerFAT]
mov bl, [NumberOfCopiesOfFAT]
mul ebx ;EDX:EAX = EAX * EBX
mov bx, [ReservedSectors] ; was bl.. oops
add eax, ebx
mov [fat16_rootdirstart], eax ; Store the sector number of the start of the root dir
; Calculate the starting sector of the Data Area
xor eax, eax
mov ax, [MaximumRootDirectoryEntries]
shr eax, 4 ; quick multiply by 32 and divide by 512
add eax, [fat16_rootdirstart]
mov [fat16_clusterstart], eax
; Load the root dir
mov ebx, [fat16_rootdirstart]
dec ebx
ff_next_sector:
inc ebx
; We need a check to make sure we only look through the FAT.. or do we. look for a null record instead.
mov di, 0x8000
push di ; Save DI to stack
call readsector ; Read one sector from the hard drive to 0x0000:0x8000
pop di ; Get DI from stack as readsector modifies DI
;dump the sector we read to the screen for debugging
; mov cx, 512
; mov si, 0x8000
;poo:
; lodsb
; xor bx, bx ; video page 0
; mov ah, 0x0e ; print it
; int 0x10 ; via TTY mode
; dec cx
; cmp cx, 0
; jne poo
ff_next_entry:
; Do a quick compare of the byte at DI to se if it is 0. If so we are at the end of the records. Fail.
; if not continue search for file name
mov cx, 11
mov si, Filename
repe cmpsb ; compare the Filename in the fat record against the name we are looking for.
jz ff_done ; note that di now is at dirent+11
add di, byte 0x20 ; A 3 byte command. "add di, 0x0020" is 4 bytes
and di, byte -0x20 ; nice trick from FreeDOS! 3 bytes. "and di, 0xFFE0" is 4 bytes
; Add something here to use ff_next_sector
cmp di, [BytesPerSector+0x8000]
jnz ff_next_entry
jmp fail
ff_done: ; record the starting cluster
mov si, di ; copy DI to SI since lodsw uses DS:SI
add si, 15 ; we are left 11 bytes into the record.. add 15 more to get the starting cluster
lodsw ; load AX with the number of the first cluster
mov bx, ax ; put the value in BX since readcluster needs it in there
mov di, 0x8000
readnext:
call readcluster
cmp bx, 0xFFFF ; check if we have reached the end of the file
jne readnext ; if not read the next cluster in the chain
mov si, msg_OK ; file is loaded.. print an 'ok' message
call print ; modifies AX BX SI
jmp 0x0000:0x8000 ; Jump to what was loaded
fail:
mov si, msg_BootError
call print ; modifies AX BX SI
jmp $
;-----------------------------------------------------------------------
; print - prints string DS:SI
; modifies AX BX SI
printchar:
xor bx, bx ; video page 0
mov ah, 0x0e ; print it
int 0x10 ; via TTY mode
print:
lodsb ; get byte from string
cmp al, 0 ; end of string?
jne printchar ; until done
ret ; return
;-----------------------------------------------------------------------
; -----------------------------------------------------------------
; readsector -- Read sector from disk into memory
; IN: EBX(sector), ES:DI (memory location to store sectors)
; OUT: EBX(next sector), ES:DI points past end of data read
; Notes: We don't use the high 32bits so this only works for LBA28
readsector:
push edx
push ecx
push eax
xor edx, edx
push eax ; remember the amount of sectors we want to read (we need it for later)
mov cx, sp ; remember parameter block end
push byte 0 ; [E] the rest of C
push byte 0 ; [C] sector number high 32bit
push ebx ; [8] sector number low 32bit
push es ; [6] buffer segment
push di ; [4] buffer offset
push byte 1 ; [2] number of sectors to read
push byte 16 ; [0] size of parameter block (actually pushes a word)
mov si, sp ; The BIOS call expects the above "packet" to be located at DS:SI
mov dl, [drive] ; Read from the drive that we booted from
mov ah, 0x42 ; LBA disk read
int 0x13 ; http://www.ctyme.com/intr/rb-0708.htm
jc fail ; CF is clear if the BIOS call was successful, AH holds the error code
mov sp, cx ; remove parameter block from stack (without changing flags!)
pop eax ; recall the amount of sectors we wanted to read
shl ax, 9 ; quickly multiply AX (number of sectors to read) by 512
add di, ax ; Add this to DI since the BIOS call does not increment it for us
inc ebx
pop eax
pop ecx
pop edx
ret
; -----------------------------------------------------------------
; -----------------------------------------------------------------
; readcluster -- read a FAT16 cluster from the disk
; IN: BX(cluster), ES:DI (memory location to store at least 32KB)
; OUT: BX(next cluster), ES:DI (points past the last byte read)
readcluster:
push edx
push ecx
push eax
and ebx, 0x0000FFFF ; clear the high word of EBX as we only want to use BX
mov [tempcluster], bx ; store the cluster number we are about to read. We will use this value later on to calculate where the next cluster is (if there is one)
cmp bx, 2 ; the cluster value has to be at least 2. Cluster 0 and 1 are not used
jl near readcluster_bailout
dec bx
dec bx
mov eax, ebx
movzx edx, byte [SectorsPerCluster]
mul edx ; RDX:RAX = RAX * RDX
add eax, [fat16_clusterstart]
mov ebx, eax
; ebx now contains the starting sector for this cluster
again:
mov al, [SectorsPerCluster]
call readsector
dec al
cmp al, 0
jne again
push di
push si
mov bx, [ReservedSectors] ; The first FAT starts right after the reserved sectors
mov di, 0x7A00 ; Push and pop this? save 1 byte
call readsector ; the root cluster is now in memory at DI
xor ebx, ebx
mov bx, [tempcluster] ; bx now stores the cluster number that we just read
; ;multipy ebx by 2 since each record is 2 bytes
xor ecx, ecx
mov ecx, ebx
shl ecx, 1
mov si, 0x7A00
add esi, ecx
lodsw
pop si
pop di
mov bx, ax ; bx now stores the cluster number that is next in the chain.
; if BX is 0xFFFF then we have read the whole file
readcluster_bailout:
pop eax
pop ecx
pop edx
ret
; -----------------------------------------------------------------
;-----------------------------------------------------------------------
; Variables and strings
;-----------------------------------------------------------------------
; We store the variables starting at 0x7E00 in memory. Saves us a few
; bytes by not storing them in the code. ex: blah dd 0x00000000
fat16_rootdirstart equ 0x7E00 ; sector where fat table starts
fat16_clusterstart equ 0x7E04 ; sector where data starts
drive equ 0x7E08 ; we only use 1 byte here
tempcluster equ 0x7E0A
msg_Loading db "Loading... ", 0
msg_OK db "ok", 0
msg_BootError db "No "
Filename db "PURE64 SYS" ; Make sure this is 11 bytes in length and capital letters
;-----------------------------------------------------------------------
; Misc
;-----------------------------------------------------------------------
times 510-$+$$ db 0 ; Fill the rest of the binary to 510 bytes
sign dw 0xAA55 ; The boot signiture we need