📄 fat16-hd.asm
字号:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; First-stage bootloader for FAT16 (DOS) hard disk partition
; Chris Giese <geezer@execpc.com>, http://www.execpc.com/~geezer/os
;
; INSTALLATION:
; 1. The kernel or second-stage bootloader loaded by this code must
; meet these restrictions:
; - Must start running in real mode (16-bit mode)
; - Must be in binary format (entry point == start of file)
; - Must be stored in the root directory of the disk
;
; 2. Change the equates/defines below as necessary:
; equate default value what is it?
; ------ ------------- -----------
; SS_ADR 10000h Load address of kernel/second stage
; - Must be >= 500h
; - Preferably >= 8600h
; - Must be a multiple of 10h
;
; SS_ORG 00100h ORG (origin) value of assembled
; kernel/second stage. Must be a
; multiple of 10h
;
; SS_NAME "LOADER BIN" Name of second-stage code on disk.
; This name must be in FAT format:
; - 8-character filename
; - 3-character extension immediately
; following filename
; - Filename and extension are both
; capitalized
; - Filename and extension are both
; padded on the right with spaces
;
; 3. Assemble this file with NASM:
; nasm -f bin -o fat16-hd.bin fat16-hd.asm
;
; 4. Make a FAT16 filesystem on a hard disk partition:
; (DOS) format d:
; (Linux) mkdosfs /dev/hda2
;
;
;
; 5. Copy the kernel/second stage to the root directory of
; the hard disk partition, and name it according to SS_NAME:
; (DOS) copy second_stage d:\loader.bin
; (Linux) mount /dev/hda2 /mnt
; cp second_stage /mnt/loader.bin
; umount /mnt
;
; 6. Install this first-stage code into the bootsector (sector 0) of
; the hard disk partition, avoiding the existing BIOS parameter block:
; (DOS) partcopy fat16-hd.bin 0 3 -ad
; partcopy fat16-hd.bin 24 1DC -ad 24
; (Linux) dd if=fat16-hd.bin skip=0 count=3 of=/dev/hda2
; dd if=fat16-hd.bin skip=36 count=476 of=/dev/hda2 seek=36
;
; I don't recommend using RAWRITE because:
; - the version of RAWRITE I used was buggy, and trashed sector 1
; (the first FAT) when I used it to install code in sector 0
; - RAWRITE can not write partial sectors, so this code will work
; only if your partition has geometry CHS=261:255:63 (unless the
; default BIOS parameter block is changed)
;
; DOS users can get John Fine's PARTCOPY (Partial Copy; PCOPY) here:
; http://www.execpc.com/~geezer/johnfine
;
; MEMORY MAP:
; real-mode interrupt vectors (IVT): 00000-003FF
; BIOS data segment: 00400-004FF
; (unused memory): 00500-07B7F
; first-stage variables: 07B80-07BFF
; first-stage code (512 bytes): 07C00-07DFF FS_ADR
; first-stage stack: 07E00-07FFF (max) FS_ADR + 200h
; first-stage directory buffer: 08000-081FF FS_ADR + 400h
; first-stage FAT buffer: 08200-085FF FS_ADR + 600h
; (unused memory): 08600-0FFFF
; default second-stage load address: 10000-
;
; OPERATION:
; If there is an error loading the second stage (LOADER.BIN was not
; found, or there was a disk error), then this code will display a
; short error message, beep, await a key pressed, and re-start the
; boot process via INT 19h.
;
;
; POSSIBLE BUGS:
; - Do I need CLI and STI?
; - Should I set SP to something safe before calling the 2nd stage?
; - Testing:
; - test on 8088-based machine
; - test with various disk sizes (different CHS values)
; - see if it works with large kernel/second stage
; - see if it works on a hard disk with no partition table
; and a FAT16 filesystem starting at sector 0
;
;
;
;
;
; TO DO:
; - Make this code smaller?
; - Some parts of this code assume 512 bytes per sector.
; Is this a problem?
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SS_ADR equ 10000h
SS_ORG equ 100h
%define SS_NAME "LOADER BIN"
;
; "No user-serviceable parts beyond this point" :)
;
FAT16_EOF equ 0FFF8h
; first-stage address
%ifdef DOS
%define FS_ADR 100h ; .COM file for testing
%else
%define FS_ADR 7C00h
%endif
; 512 bytes for stack
ADR_STACK equ (FS_ADR + 400h)
; 512 bytes for one-sector directory buffer
ADR_DIRBUF equ (FS_ADR + 400h)
; 512 bytes for one-sector FAT buffer
ADR_FATBUF equ (FS_ADR + 600h)
ORG FS_ADR
start:
; define memory used for scratchpad variables. These are initialized
; at run-time, so there's no need to waste first-stage memory on them.
; Put them just below 'start'
; (1 byte) drive we booted from; 0=A, 80h=C
boot_drive EQU (start - 1)
; (2 bytes) sector where root directory starts
root_start EQU (boot_drive - 2)
; (2 bytes) sector where the actual disk data starts
data_start EQU (root_start - 2)
; (2 bytes) number of 16-byte paragraphs per sector
para_per_sector EQU (data_start - 2)
; (2 bytes) number of 16-byte paragraphs per cluster
para_per_cluster EQU (para_per_sector - 2)
jmp short over ; skip over BPB
nop
; minimal, default BIOS Parameter Block (BPB)
oem_id: ; 03h not used by this code
db "GEEZER", 0, 0
bytes_per_sector: ; 0Bh
dw 512
sectors_per_cluster: ; 0Dh
db 64 ; maximum (2 gig partition)
fat_start:
num_reserved_sectors: ; 0Eh
dw 1
num_fats: ; 10h
db 2
num_root_dir_ents: ; 11h
dw 512
total_sectors: ; 13h not used by this code
dw 0
media_id: ; 15h not used by this code
db 0F8h
sectors_per_fat: ; 16h
dw 256 ; maximum (2 gig partition)
sectors_per_track: ; 18h
dw 63 ; maximum (2 gig partition)
heads: ; 1Ah
dw 255 ; maximum (2 gig partition)
hidden_sectors: ; 1Ch
dd 16065 ; partition 1 (CHS=1:0:1)
total_sectors_large: ; 20h not used by this code
dd 4192965 ; CHS=261:255:63 (2 gig partition)
over:
%ifdef DOS
mov dl,80h
%else
; evidently some buggy BIOSes jump to 07C0:0000, so fix that now
jmp 0:over2
%endif
over2:
; ...and some BIOSes don't zero DS, so do that as well
mov ax,cs
mov ds,ax
mov ss,ax ; zero SS, too
mov sp,ADR_STACK
mov bp,over ; for relative addressing
cld ; string operations go up
; save [boot_drive] from DL. BIOS sets DL=0 if
; booting from drive A, DL=80h if booting from drive C
mov [bp - (over - boot_drive)],dl
; compute first sector of root directory
mov al,[num_fats] ; number of FATs
cbw ; "mov ah,0"
mul word [bp - (over - sectors_per_fat)] ; multiply by sectors/FAT
add ax,[bp - (over - fat_start)] ; plus reserved sectors
mov [root_start],ax
; compute first sector of disk data area
mov bx,ax
mov ax,[num_root_dir_ents] ; entries in root dir
mov dl,32 ; * bytes/entry (assume DH=0)
mul dx ; == bytes in root dir
div word [bp - (over - bytes_per_sector)] ; / bytes per sector
add ax,bx ; = sectors
mov [data_start],ax
; compute number of 16-byte paragraphs per disk sector
mov dx,[bp - (over - bytes_per_sector)]
mov cl,4
shr dx,cl
mov [bp - (over - para_per_sector)],dx
; compute number of 16-byte paragraphs per FAT cluster
mov al,[sectors_per_cluster]
cbw
mul dx ; DX still=paragraphs/sector
mov dx,ax
mov [bp - (over - para_per_cluster)],dx
; OK, everything is set up for 'find_file', 'walk_fat', and 'read_sectors'
; Find the file named at 'second_stage:' in the root directory
mov si,second_stage
call find_file
jc err ; disk error
; if second-stage file not found, display blinking 'F'
mov ax,9F46h
jne err2
; get conventional memory size
int 12h
; subtract starting second stage address to get available mem
sub ax,(SS_ADR >> 10)
; convert from K to bytes
xor dh,dh
mov dl,ah
mov ah,al
xor al,al
shl ax,1
rcl dx,1
shl ax,1
rcl dx,1
; if second stage file is too big...
sub ax,[es:di + 28]
sbb dx,[es:di + 30]
; ...display a blinking 'M'
mov ax,9F4Dh
jc err2
; found second-stage, load it
%ifdef DOS
mov di,ds
add di,(SS_ADR >> 4)
%else
mov di,(SS_ADR >> 4)
%endif
load_2nd:
mov es,di
; convert cluster BX to sector value in DX:AX, and get next cluster in BX
call walk_fat
jc err
; read an entire cluster
xor ch,ch
mov cl,[bp - (over - sectors_per_cluster)]
call read_sectors
jc err
add di,[bp - (over - para_per_cluster)] ; advance mem ptr 1 cluster
cmp bx,FAT16_EOF ; EOF cluster value
jb load_2nd
; jump to second stage that is ORGed to address "SS_ORG"
%ifdef DOS
mov ax,ds
add ax,((SS_ADR - SS_ORG) >> 4)
%else
mov ax,((SS_ADR - SS_ORG) >> 4)
%endif
mov ds,ax
mov es,ax
mov ss,ax
push ax
push word SS_ORG
retf
; disk read error; display blinking 'R'
err:
mov ax,9F52h
err2:
mov bx,0B800h ; assumes color emulation
mov es,bx
xor bx,bx
mov [es:bx],ax
; *** BEEEP ***
mov ax,0E07h
int 10h
; await key pressed
mov ah,0
int 16h
; re-start the boot process
int 19h
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: find_file
; action: searches for file in root dir
; in: 11-char all-caps filename at SI, [root_start] set
; out (disk error): CY=1
; out (file not found): CY=0, ZF=0
; out (found it): CY=0, ZF=1, BX=starting cluster of file,
; ES:DI is left pointing to the directory entry
; modifies: AX, BX, CX, DX, DI, ES
; minimum CPU: 8088
; notes:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
find_file:
xor dx,dx
mov ax,[root_start] ; 1st sector in root dir
%ifdef DOS
mov bx,ds ; where to load it
add bx,(ADR_DIRBUF >> 4)
%else
mov bx,(ADR_DIRBUF >> 4)
%endif
mov es,bx
find_file_1:
mov cx,1
call read_sectors ; one sector at a time
jc find_file_5 ; disk read error
xor di,di
find_file_2:
test [es:di],byte 0FFh ; 0 byte = end of root dir
je find_file_3
mov cl,11 ; 'read_sectors' zeroed CH
push si
push di ; compare filename to dirent
rep cmpsb
pop di
pop si
je find_file_4
; if filename comparision failed, advance 32 bytes to next dir entry
add di,byte 32
cmp di,[bp - (over -bytes_per_sector)]
jb find_file_2
inc ax
cmp ax,[bp - (over - data_start)]
jb find_file_1 ; go to next sector of root
find_file_3:
; did not find the file: return with CY=0, ZF=0
or al,1
find_file_4:
; found the file
mov bx,[es:di + 26] ; get first cluster of file
find_file_5:
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: walk_fat
; action: converts cluster value to sector,
; and gets next cluster of file from FAT
; in: BX=cluster
; out (disk error): CY=1
; out (success): CY=0, DX:AX=first sector of cluster, BX=next cluster
; modifies: AX, BX, DX
; minimum CPU: 8088
; notes:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
walk_fat:
push es
push cx
mov ax,bx
; cluster 2 is the first data cluster
dec ax
dec ax
; convert from clusters to sectors
mov dh,0
mov dl,[bp - (over - sectors_per_cluster)]
mul dx
add ax,[bp - (over - data_start)]
adc dx,byte 0
; DX:AX is return value; save it
push dx
push ax
%ifdef DOS
mov ax,ds
add ax,(ADR_FATBUF >> 4)
%else
mov ax,(ADR_FATBUF >> 4)
%endif
mov es,ax
; FAT16 entries are 16 bits, bytes are 8 bits, so multiply by 2
xor dx,dx
mov ax,bx
shl ax,1
rcl dx,1
; figure out which FAT sectors to load
div word [bp - (over - bytes_per_sector)]
; remainder is byte offset into FAT; put it in BX
mov bx,dx
; quotient in AX is FAT sector: add FAT starting sector
add ax,[bp - (over - fat_start)]
xor dx,dx
; check the FAT buffer to see if this sector is already loaded
cmp ax,[bp - (over - curr_sector)]
je walk_fat_1
mov [curr_sector],ax
; read the target FAT sector
mov cx,1
call read_sectors
jc walk_fat_4
; get 16 bits from FAT
walk_fat_1:
mov bx,[es:bx]
; clear CY bit to signal success
clc
walk_fat_4:
pop ax
pop dx
pop cx
pop es
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name: read_sectors
; action: reads one or more disk sectors into memory
; in: ES:0=address of memory where sectors should be read
; DX:AX=first sector to read
; CX=number of sectors to read
; out (disk error): CY=1
; out (success): CY=0
; modifies: DX, CX, AX
; minimum CPU: 8088
; notes:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
read_sectors:
push es
push bx
xor bx,bx
; add partition start
add ax,[bp - (over - hidden_sectors) + 0]
adc dx,[bp - (over - hidden_sectors) + 2]
read_sectors_1:
push dx
push cx
push ax
; DX:AX=LBA sector number
push ax
mov ax,dx
xor dx,dx
; divide LBA by sectors_per_track (spt).
; Use a two-step 32-bit/16-bit divide to avoid overflow.
; See "9.3.5 Extended Precision Division" in Randall Hyde's "Art of Assembly"
div word [bp- (over - sectors_per_track)]
; DX=high LBA % spt, AX=high LBA / spt
mov cx,ax
pop ax
; DX=high LBA % spt, CX=high LBA / spt, AX=low LBA
; New 32-bit dividend is DX:AX
div word [bp- (over - sectors_per_track)]
; CX:AX=LBA / spt, DX=LBA % spt
xchg cx,dx
inc cx ; CL=sector
; DX:AX=LBA / spt, do normal one-step divide by heads value
div word [bp - (over - heads)]
mov dh,dl ; DH=head
mov ch,al ; CH=cylinder 7:0
mov dl,[bp - (over - boot_drive)]
shl cl,1
shl cl,1 ; trying to use only...
shr ah,1 ; ...8088 asm here
rcr cl,1
shr ah,1
rcr cl,1 ; CL7:6=cyl 9:8, CL5:0=sector
; now: read one sector
mov ax,0201h
int 13h
jnc read_sectors_3
; disk error; recalibrate/reset drive
mov ah,0
int 13h
jc read_sectors_2
; try the read again. If it fails a second time, give up.
mov ax,0201h
int 13h
jnc read_sectors_3
read_sectors_2:
pop ax
pop cx
pop dx
jmp short read_sectors_5
; advance memory pointer
read_sectors_3:
mov ax,es
add ax,[bp - (over - para_per_sector)]
mov es,ax
pop ax
pop cx
pop dx
; increment DX:AX to advance to next sector
inc ax
jne read_sectors_4
inc dx
read_sectors_4:
loop read_sectors_1
; clear CY bit to signal success
clc
read_sectors_5:
pop bx
pop es
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 1st-stage data
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
curr_sector: ; which sector is in the FAT buffer
dw -1
; name of the second stage bootloader
second_stage:
db SS_NAME
; pad with NOPs to offset 510
times (510 + $$ - $) nop
; 2-byte magic bootsector signature
db 55h, 0AAh
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -