Page 1 of 1

Writing a FAT16 Bootsector for USB booting

Posted: Thu Mar 19, 2009 11:53 am
by IanSeyler
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:

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

Re: Writing a FAT16 Bootsector for USB booting

Posted: Thu Mar 19, 2009 12:10 pm
by djmauretto
Ciao ,
Before to use EDD extension you must check for that :

Code: Select all

	MOV     AH,41h			; EDD INSTALLATION CHECK
	MOV     BX,55AAh
	INT     13h
	CMP     BX,0AA55h		; Supported ?
	JNZ     @EDD_Ko		        ; Jump IF not Supported
	TEST    CX,0001h		        ; EDD functions (AH=42h-44h,47h,48h) supported ?
	JZ      @EDD_Ko		        ; Jump IF not Supported
Anyway INT 13h AH = 02h work fine with USB :wink:

Re: Writing a FAT16 Bootsector for USB booting

Posted: Thu Mar 19, 2009 2:24 pm
by IanSeyler
I had some debug code in there earlier to check for the INT 0x13 extensions. The BIOS does support it but it still doesn't work.

Looks like I'll use INT 0x13 / AH 0x02 instead.. does anyone have some quick ASM code that takes an LBA value and does a CHS read? :lol:

-Ian

Re: Writing a FAT16 Bootsector for USB booting

Posted: Thu Mar 19, 2009 2:36 pm
by Firestryke31
Such code can be found in the Wiki here (if you haven't already seen it, just replace forum in the URL with wiki). I can't remember which article it is in, but if you searched for it I'm sure you can find it. IIRC there's a similar code clip on the actual Wikipedia somewhere too.

Re: Writing a FAT16 Bootsector for USB booting

Posted: Thu Mar 19, 2009 3:08 pm
by johnsa
I had similar issues with USB where the boot code would read the sector, not report any errors but the data read into memory was all zeros... I believe I fixed my problem by removing the cli at the start of my boot code. I only do the cli/sti stuff around pic remapping and pmode switches etc.. where it's necessary.. the rest of the boot code is all in real or unreal mode so it can have interrupts enabled.

Maybe that will fix your problem.

Cheers
John

Re: Writing a FAT16 Bootsector for USB booting

Posted: Thu Mar 19, 2009 7:45 pm
by Dex
iseyler wrote:Hi everyone,

I've been working on this for a week now and haven't had much luck. Hopefully someone here may know more.
Thats funny, because i have spent the last week doing the same, i have written a usb boot loader program for my OS, for fdd or hdd emulation, to run under windows.
Now looking at your code, first thing i would ask is :
1. How are you puting the boot sector on to the USB ?, as how it stands your BPB is full of zeros eg:

Code: Select all

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
Do you have a program, to jmp over the BPB on the fat16 usb drive and put your program on from

Code: Select all

real_start:
If not it will not work, So to test if this is the problem you need to get a disk editor and use the method i describe here:
http://www.dex4u.com/USBboot.htm
Or you could hard code the BPB from your usb drive into your code.
That is why i have spent a week on this coding a special windows program, to take the BPB off the formated drive and mix it with my boot code and put it back on the disk, this is not easy in window as its very low down and need many error checking.

PS: Remember that the MBR should not be touch ( other than mark the partion as active), but the Boot Sector of the Active Partition need changing.
But than again it depends on how window formats it, as it sometimes does not use the MBR, but just adds the BPB, other times it does format with MBR :( .

Re: Writing a FAT16 Bootsector for USB booting

Posted: Sat Mar 21, 2009 11:49 am
by octavio
Recently i did some test on a computer trying to boot Octaos on it from the usb.It seems that when the usb is formatted like a hard disk the MBR code is ignored and the bios just loads the first sector of the active partition.int 13h extensions are not available , so i think that the bios uses the old chs int 13h services and it emulates a hard disk with the geometry especified on the bios parameters table in the boot sector.And probably only fat filesystems are supported for booting.When the usb is formatted like a floppy, the first sector code is executed and it works like a floppy.
Still my OS do not boot because it can not read the usb pendrive,and since the computer was not mine ,my tests finished here.