; This is an LBA-enabled FreeDOS FAT32 boot sector (single sector!). ; You can use and copy source code and binaries under the terms of the ; GNU Public License (GPL), version 2 or newer. See www.gnu.org for more. ; Based on earlier work by FreeDOS kernel hackers, modified heavily by ; Eric Auer and Jon Gentle in 7 / 2003. ; ; Features: Uses LBA and calculates all variables from BPB/EBPB data, ; thus making partition move / resize / image-restore easier. FreeDOS ; can boot from FAT32 partitions which start > 8 GB boundary with this ; boot sector. Disk geometry knowledge is not needed for booting. ; ; Windows uses 2-3 sectors for booting (sector stage, statistics sector, ; filesystem stage). Only using 1 sector for FreeDOS makes multi-booting ; of FreeDOS and Windows on the same filesystem easier. ; ; Requirements: LBA BIOS and 386 or better CPU. Use the older CHS-only ; boot sector if you want FAT32 on really old PCs (problems: you cannot ; boot from > 8 GB boundary, cannot move / resize / ... without applying ; SYS again if you use the CHS-only FAT32 boot sector). ; ; FAT12 / FAT16 hints: Use the older CHS-only boot sector unless you ; have to boot from > 8 GB. The LBA-and-CHS FAT12 / FAT16 boot sector ; needs applying SYS again after move / resize / ... a variant of that ; boot sector without CHS support but with better move / resize / ... ; support would be good for use on LBA harddisks. ; Memory layout for the FreeDOS FAT32 single stage boot process: ; ; ... ; |-------| 1FE0h:7E00h = 27C00h (159 KiB) ; |BOOTSEC| loader relocates itself here first thing, ; |RELOC. | before loading root directory/FAT/kernel file ; |-------| 1FE0h:7C00h = 27A00h (158 KiB) ; | STACK | below relocated loader, above FAT sector (size 22 KiB) ; ... ; |-------| 2200h:2000h = 24000h (144 KiB) ; | FAT | (only 1 sector buffered, maximum sector size 8 KiB) ; |-------| 2200h:0000h = 22000h (136 KiB) ; ... ; |-------| 0000h:7E00h = 07E00h (31.5 KiB) ; |BOOTSEC| overwritten by the kernel, so the ; |ORIGIN | bootsector relocates itself up... ; |-------| 0000h:7C00h = 07C00h (31 KiB) ; ... ; |-------| ; |KERNEL | maximum size 128 KiB (overwrites bootsec origin) ; |LOADED | (holds 1 sector directory buffer before kernel file load) ; |-------| 0060h:0000h = 00600h (1.5 KiB) ; ... ; The kernel load segment may be patched using the SYS /L switch. ; We support values between 0x60 and 0x200 here, with file size ; of up to 128 KiB (rounded to cluster size). Default is 0x60. ; NOTE: sys must be updated if magic offsets change %assign ISFAT1216DUAL 0 %include "magic.mac" segment .text org 0x7c00 ; this is a boot sector Entry: jmp short real_start nop ; bp is initialized to 7c00h ; %define bsOemName bp+0x03 ; OEM label (8) %define bsBytesPerSec bp+0x0b ; bytes/sector (dw) %define bsSecPerClust bp+0x0d ; sectors/allocation unit (db) %define bsResSectors bp+0x0e ; # reserved sectors (dw) %define bsFATs bp+0x10 ; # of fats (db) ; %define bsRootDirEnts bp+0x11 ; # of root dir entries (dw, 0 for FAT32) ; (FAT32 has root dir in a cluster chain) ; %define bsSectors bp+0x13 ; # sectors total in image (dw, 0 for FAT32) ; (if 0 use nSectorHuge even if FAT16) ; %define bsMedia bp+0x15 ; media descriptor: fd=2side9sec, etc... (db) ; %define sectPerFat bp+0x16 ; # sectors in a fat (dw, 0 for FAT32) ; (FAT32 always uses xsectPerFat) %define sectPerTrack bp+0x18 ; # sectors/track ; %define nHeads bp+0x1a ; # heads (dw) %define nHidden bp+0x1c ; # hidden sectors (dd) ; %define nSectorHuge bp+0x20 ; # sectors if > 65536 (dd) %define xsectPerFat bp+0x24 ; Sectors/Fat (dd) ; +0x28 dw flags (for fat mirroring) ; +0x2a dw filesystem version (usually 0) %define xrootClst bp+0x2c ; Starting cluster of root directory (dd) ; +0x30 dw -1 or sector number of fs.-info sector ; +0x32 dw -1 or sector number of boot sector backup ; (+0x34 .. +0x3f reserved) %define drive bp+0x40 ; Drive number %define loadsegoff_60 bp+loadseg_off-Entry %define LOADSEG 0x0060 %define FATSEG 0x2200 %define fat_secshift fat_afterss-1 ; each fat sector describes 2^?? ; clusters (db) (selfmodifying) %define fat_sector bp+0x44 ; last accessed FAT sector (dd) ; (overwriting unused bytes) %define fat_start bp+0x48 ; first FAT sector (dd) ; (overwriting unused bytes) %define data_start bp+0x4c ; first data sector (dd) ; (overwriting unused bytes) times 52h - ($ - $$) db 0 ; The filesystem ID is used by lDOS's instsect (by ecm) ; by default to validate that the filesystem matches. db "FAT32" times 5Ah - ($ - $$) db 32 ; not used: [0x42] = byte 0x29 (ext boot param flag) ; [0x43] = dword serial ; [0x47] = label (padded with 00, 11 bytes) ; [0x52] = "FAT32",32,32,32 (not used by Windows) ; ([0x5a] is where FreeDOS parts start) ;----------------------------------------------------------------------- ; ENTRY ;----------------------------------------------------------------------- real_start: cld cli sub ax, ax mov ds, ax mov bp, 0x7c00 mov ax, 0x1FE0 mov es, ax mov si, bp mov di, bp mov cx, 0x0100 rep movsw ; move boot code to the 0x1FE0:0x0000 jmp word 0x1FE0:cont loadseg_off dw 0 magicoffset "loadseg", 78h dw LOADSEG ; ------------- cont: mov ds, ax mov ss, ax ; stack and BP-relative moves up, too lea sp, [bp-0x20] sti magicoffset "set unit", 82h mov [drive], dl ; BIOS passes drive number in DL %ifndef QUIET mov si, msg_LoadFreeDOS call print ; modifies AX BX SI %else ; ensure code after this still at same location times 6 nop %endif ; ------------- ; CALCPARAMS: figure out where FAT and DATA area starts ; (modifies EAX EDX, sets fat_start and data_start variables) calc_params: xor eax, eax mov [fat_sector], eax ; init buffer status ; first, find fat_start: mov ax, [bsResSectors] ; no movzx eax, word... needed add eax, [nHidden] mov [fat_start], eax ; first FAT sector mov [data_start], eax ; (only first part of value) ; next, find data_start: mov eax, [bsFATs] ; no movzx ... byte needed: ; the 2 dw after the bsFATs db are 0 by FAT32 definition :-). imul dword [xsectPerFat] ; (also changes edx) add [data_start], eax ; first DATA sector ; (adding in RAM is shorter!) ; finally, find fat_secshift: mov ax, 512 ; default sector size (means default shift) ; shift = log2(secSize) - log2(fatEntrySize) ;--- mov cl, 9-2 ; shift is 7 for 512 bytes per sector fatss_scan: cmp ax, [bsBytesPerSec] jz fatss_found add ax,ax ;--- inc cx inc word [fat_secshift] ;XXX ; initially 9-2 (byte!) jmp short fatss_scan ; try other sector sizes fatss_found: ;--- mov [fat_secshift], cl ; ------------- ; FINDFILE: Searches for the file in the root directory. ; Returns: EAX = first cluster of file mov eax, [xrootClst] ; root dir cluster ff_next_clust: push eax ; save cluster call convert_cluster jc boot_error ; EOC encountered ; EDX is clust/sector, EAX is sector ff_next_sector: les bx, [loadsegoff_60] ; load to loadseg:0 call readDisk ;--- push eax ; save sector ;--- xor ax, ax ; first dir. entry in this sector xor di, di ;XXX ; Search for KERNEL.SYS file name, and find start cluster. ff_next_entry: mov cx, 11 mov si, filename ;--- mov di, ax repe cmpsb jz ff_done ; note that di now is at dirent+11 ;--- add ax, 0x20 ; next directory entry ;--- cmp ax, [bsBytesPerSec] ; end of sector reached? add di, byte 0x20 ;XXX and di, byte -0x20 ; 0xffe0 ;XXX cmp di, [bsBytesPerSec] ;XXX jnz ff_next_entry ;--- pop eax ; restore sector dec dx ; next sector in cluster jnz ff_next_sector ff_walk_fat: pop eax ; restore current cluster call next_cluster ; find next cluster jmp ff_next_clust ff_done: push word [es:di+0x14-11] ; get cluster number HI push word [es:di+0x1A-11] ; get cluster number LO pop eax ; convert to 32bit sub bx, bx ; ES points to LOADSEG ; (kernel -> ES:BX) ; ------------- read_kernel: push eax call convert_cluster jc boot_success ; EOC encountered - done ; EDX is sectors in cluster, EAX is sector rk_in_cluster: call readDisk dec dx jnz rk_in_cluster ; loop over sect. in cluster rk_walk_fat: pop eax call next_cluster jmp read_kernel ;----------------------------------------------------------------------- boot_success: mov dl, [drive] ; for Enhanced DR-DOS load mov bl, dl ; for FreeDOS load jmp far [loadsegoff_60] ;----------------------------------------------------------------------- boot_error: mov si, msg_BootError call print ; modifies AX BX SI wait_key: xor ah,ah int 0x16 ; wait for a key reboot: int 0x19 ; reboot the machine ;----------------------------------------------------------------------- ; given a cluster number, find the number of the next cluster in ; the FAT chain. Needs fat_secshift and fat_start. ; input: EAX - cluster ; output: EAX - next cluster next_cluster: push es push di push bx mov di, ax shl di, 2 ; 32bit FAT push ax mov ax, [bsBytesPerSec] dec ax and di, ax ; mask to sector size pop ax shr eax, 7 ; e.g. 9-2 for 512 by/sect. fat_afterss: ; selfmodifying code: previous byte is patched! ; (to hold the fat_secshift value) add eax, [fat_start] ; absolute sector number now mov bx, FATSEG mov es, bx sub bx, bx cmp eax, [fat_sector] ; already buffered? jz cn_buffered mov [fat_sector],eax ; number of buffered sector call readDisk cn_buffered: and byte [es:di+3],0x0f ; mask out top 4 bits mov eax, [es:di] ; read next cluster number pop bx pop di pop es ret ;----------------------------------------------------------------------- ; Convert cluster number to the absolute sector number ; ... or return carry if EndOfChain! Needs data_start. ; input: EAX - target cluster ; output: EAX - absolute sector ; EDX - [bsSectPerClust] (byte) ; carry clear ; (if carry set, EAX/EDX unchanged, end of chain) convert_cluster: cmp eax, 0x0ffffff8 ; if end of cluster chain... jnb end_of_chain ; sector = (cluster-2) * clustersize + data_start dec eax dec eax movzx edx, byte [bsSecPerClust] dec dl ; spc - 1 inc dx ; spc push edx mul edx pop edx add eax, [data_start] ; here, carry is unset (unless parameters are wrong) ret end_of_chain: stc ; indicate EOC by carry ret ;----------------------------------------------------------------------- ; 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 token cmp al, 0 ; end of string? jne printchar ; until done ret ; return to caller ;----------------------------------------------------------------------- ; Read a sector from disk, using LBA ; input: EAX - 32-bit DOS sector number ; ES:BX - destination buffer ; (will be filled with 1 sector of data) ; output: ES:BX points one byte after the last byte read. ; EAX - next sector readDisk: push dx push si push di read_next: push eax ; would ax be enough? mov di, sp ; remember parameter block end ;--- db 0x66 ; operand size override (push dword) push byte 0 ;XXX ; other half of the 32 bits at [C] ; (did not trust "o32 push byte 0" opcode) push byte 0 ; [C] sector number high 32bit push eax ; [8] sector number low 32bit push es ; [6] buffer segment push bx ; [4] buffer offset push byte 1 ; [2] 1 sector (word) push byte 16 ; [0] size of parameter block (word) mov si, sp mov dl, [drive] mov ah, 42h ; disk read int 0x13 mov sp, di ; remove parameter block from stack ; (without changing flags!) pop eax ; would ax be enough? jnc read_ok ; jump if no error push ax ; !! xor ah, ah ; else, reset and retry int 0x13 pop ax ; !! jmp read_next read_ok: inc eax ; next sector add bx, word [bsBytesPerSec] jnc no_incr_es ; if overflow... mov dx, es add dh, 0x10 ; ...add 1000h to ES mov es, dx no_incr_es: pop di pop si pop dx ret ;----------------------------------------------------------------------- msg_LoadFreeDOS db "Loading FreeDOS ",0 times 0x01ee-$+$$ db 0 msg_BootError db "No " ; currently, only "kernel.sys not found" gives a message, ; but read errors in data or root or fat sectors do not. magicoffset "kernel name", 1F1h filename db "KERNEL SYS" sign dw 0, 0xAA55 ; Win9x uses all 4 bytes as magic value here.