FreeDOS/boot/boot32lb.asm
C. Masloch 92ae538f1d boot: allow instsect to match the filesystem ID string
Reference: https://hg.ulukai.org/ecm/instsect/file/33218c729b43/instsect.asm#l1257

lDOS's instsect recently started to default to checking
the filesystem ID string in order to validate that the
boot sector loader to write matches the detected FS.
Using the /S=filename option or building instsect to
include the FreeDOS kernel's loaders would require use
of the /SN switch without this commit.

In addition, boot.asm and oemboot.asm are made to check
that exactly one of the ISFAT12 and ISFAT16 defs is
defined so as to select the FS. Prior to this commit
using both or neither def would silently result in a
broken loader.
2020-11-28 12:11:16 -05:00

408 lines
11 KiB
NASM

; 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:
; ...
; |-------| 1FE0:7E00
; |BOOTSEC|
; |RELOC. |
; |-------| 1FE0:7C00
; ...
; |-------| 2000:0200
; | FAT | (only 1 sector buffered)
; |-------| 2000:0000
; ...
; |-------| 0000:7E00
; |BOOTSEC| overwritten by the kernel, so the
; |ORIGIN | bootsector relocates itself up...
; |-------| 0000:7C00
; ...
; |-------|
; |KERNEL | maximum size 134k (overwrites bootsec origin)
; |LOADED | (holds 1 sector directory buffer before kernel load)
; |-------| 0060:0000
; ...
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 0x2000
%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, LOADSEG
; -------------
cont: mov ds, ax
mov ss, ax ; stack and BP-relative moves up, too
lea sp, [bp-0x20]
sti
mov [drive], dl ; BIOS passes drive number in DL
mov si, msg_LoadFreeDOS
call print ; modifies AX BX SI
; -------------
; 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 bl, [drive]
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]
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.
filename db "KERNEL SYS"
sign dw 0, 0xAA55
; Win9x uses all 4 bytes as magic value here.