;
; File:
; boot.asm
; Description:
; DOS-C boot
;
; Copyright (c) 1997;
; Svante Frey
; All Rights Reserved
;
; This file is part of DOS-C.
;
; DOS-C is free software; you can redistribute it and/or
; modify it under the terms of the GNU General Public License
; as published by the Free Software Foundation; either version
; 2, or (at your option) any later version.
;
; DOS-C is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
; the GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public
; License along with DOS-C; see the file COPYING. If not,
; write to the Free Software Foundation, 675 Mass Ave,
; Cambridge, MA 02139, USA.
;
;
; +--------+ 1FE0:7E00
; |BOOT SEC|
; |RELOCATE|
; |--------| 1FE0:7C00
; |LBA PKT |
; |--------| 1FE0:7BC0
; |--------| 1FE0:7BA0
; |BS STACK|
; |--------|
; |4KBRDBUF| used to avoid crossing 64KB DMA boundary
; |--------| 1FE0:63A0
; | |
; |--------| 1FE0:3000
; | CLUSTER|
; | LIST |
; |--------| 1FE0:2000
; | |
; |--------| 0000:7E00
; |BOOT SEC| overwritten by max 128k FAT buffer
; |ORIGIN | and later by max 134k loaded kernel
; |--------| 0000:7C00
; | |
; |--------|
; |KERNEL | also used as max 128k FAT buffer
; |LOADED | before kernel loading starts
; |--------| 0060:0000
; | |
; +--------+
;%define ISFAT12 1
;%define ISFAT16 1
; Set segment type.
segment .text
%define BASE 0x7c00
; Let offset base be 0x7C00.
org BASE
; The boot sector's entry point.
; (gdb) break *0x7C00
; Jump to `real_start`.
Entry: jmp short real_start
; NOP instruction for the third byte, as required by FAT specification.
nop
; The `jmp` and `nop` above take 3 bytes, followed by FAT info variables.
; These variables are initialized when running `SYS.COM` to install the boot
; sector.
;
; The offsets of the FAT info variables:
; bp is initialized to 7c00h
%define bsOemName bp+0x03 ; OEM label
%define bsBytesPerSec bp+0x0b ; bytes/sector
%define bsSecPerClust bp+0x0d ; sectors/allocation unit
%define bsResSectors bp+0x0e ; # reserved sectors
%define bsFATs bp+0x10 ; # of fats
%define bsRootDirEnts bp+0x11 ; # of root dir entries
%define bsSectors bp+0x13 ; # sectors total in image
%define bsMedia bp+0x15 ; media descrip: fd=2side9sec, etc...
%define sectPerFat bp+0x16 ; # sectors in a fat
%define sectPerTrack bp+0x18 ; # sectors/track
%define nHeads bp+0x1a ; # heads
%define nHidden bp+0x1c ; # hidden sectors
%define nSectorHuge bp+0x20 ; # sectors if > 65536
%define drive bp+0x24 ; drive number
%define extBoot bp+0x26 ; extended boot signature
%define volid bp+0x27
%define vollabel bp+0x2b
%define filesys bp+0x36
; Example of the first 62 bytes of the boot sector:
; 00: EB 3C 90 | 00 00 00 00 00 00 00 00 | 00 02 (512) | 01 | 01 00 |
; 10: 02 | E0 00 (224) | 40 0B (2880) | F0 | 09 00 | 12 00 | 02 00 | 00 00 00 00 |
; 20: 00 00 00 00 | 00 00 | 29 | 00 00 00 00 | 20 20 20 20 20
; 30: 20 20 20 20 20 20 | 46 41 54 31 32 20 20 20
;
; # bsOemName times 8 db 0
; (gdb) x/8xb 0x7C03 = x/8xb 0x27A03 = 0x0000000000000000
; # bsBytesPerSec dw 0
; (gdb) x/1dh 0x7C0B = x/1dh 0x27A0B = 512
; # bsSecPerClust db 0
; (gdb) x/1db 0x7C0D = x/1db 0x27A0D = 1
; # bsResSectors dw 0
; (gdb) x/1dh 0x7C0E = x/1dh 0x27A0E = 1
; # bsFATs db 0
; (gdb) x/1db 0x7C10 = x/1db 0x27A10 = 2
; # bsRootDirEnts dw 0
; (gdb) x/1dh 0x7C11 = x/1dh 0x27A11 = 224
; # bsSectors dw 0
; (gdb) x/1dh 0x7C13 = x/1dh 0x27A13 = 2880 for 1440KB.
; # bsMedia db 0
; (gdb) x/1xb 0x7C15 = x/1xb 0x27A15 = F0 for 3.5-inch 1440 KB or 2880 KB FD.
; # sectPerFat dw 0
; (gdb) x/1dh 0x7C16 = x/1dh 0x27A16 = 9
; # sectPerTrack dw 0
; (gdb) x/1dh 0x7C18 = x/1dh 0x27A18 = 18 for 1440 KB FD, 36 for 2880 KB FD.
; # nHeads dw 0
; (gdb) x/1dh 0x7C1A = x/1dh 0x27A1A = 2
; # nHidden dd 0
; (gdb) x/1dw 0x7C1C = x/1dw 0x27A1C = 0
; # nSectorHuge dd 0
; (gdb) x/1dw 0x7C20 = x/1dw 0x27A20 = 0
; # drive dw 0
; (gdb) x/1dh 0x7C24 = x/1dh 0x27A24 = 0
; # extBoot db 0
; (gdb) x/1xb 0x7C26 = x/1xb 0x27A26 = 0x29: The following three fields are present.
; # volid dd 0
; (gdb) x/1xw 0x7C27 = x/1xw 0x27A27 = 0x00000000
; # vollabel times 11 db 0
; (gdb) x/11xb 0x7C2B = x/11xb 0x27A2B = 0x2020202020202020202020
; # filesys times 8 db 0
; (gdb) x/8xb 0x7C36 = x/8xb 0x27A36 = 0x4641543132202020 (FAT12)
; Ends at 0x7C3E or 0x27A3E, exclusive.
; The user read buffer's segment.
; The starting address is 0x0060:0x0000 = 1.5KB.
; The user read buffer will store the kernel in the end.
; The reason to choose the starting address 1.5KB is because the first 1.5KB
; of the memory stores BIOS' interrupt vector table and other data thus should
; not be overwritten.
%define LOADSEG 0x0060
; The FAT buffer's offset.
; The FAT buffer stores the kernel file's cluster numbers.
; The starting address is 0x1FE0:0x2000 = 135.5KB.
%define FATBUF 0x2000 ; offset of temporary buffer for FAT
; chain
; Some extra variables
;%define StoreSI bp+3h ;temp store
;-----------------------------------------------------------------------
; Allocate 59 bytes of zeros from $$+3 (0x7C03) up to $$+0x3E (0x7C3E)
; exclusive.
; `$` is `$$+3` because the `jmp` and `nop` instructions above take 3 bytes.
times 0x3E-$+$$ db 0
; The offset of the variable storing the user read buffer's offset.
;
; using bp-Entry+loadseg_xxx generates smaller code than using just
; loadseg_xxx, where bp is initialized to Entry, so bp-Entry equals 0
%define loadsegoff_60 bp-Entry+loadseg_off
; The offset of the variable storing the user read buffer's segment.
%define loadseg_60 bp-Entry+loadseg_seg
; The offset of the LBA packet used by interrupt 0x13.
; 64B before 0x7C00.
%define LBA_PACKET bp-0x40
; The offset of the field that stores the LBA packet's size.
%define LBA_SIZE word [LBA_PACKET] ; size of packet, should be 10h
; The offset of the field that stores the number of sectors to read.
%define LBA_SECNUM word [LBA_PACKET+2] ; number of sectors to read
; The offset of the field that stores the read buffer's offset.
%define LBA_OFF LBA_PACKET+4 ; buffer to read/write to
; The offset of the field that stores the read buffer's segment.
%define LBA_SEG LBA_PACKET+6
; The offset of the field that stores the 48-bit LBA sector number.
%define LBA_SECTOR_0 word [LBA_PACKET+8 ] ; LBA starting sector #
%define LBA_SECTOR_16 word [LBA_PACKET+10]
%define LBA_SECTOR_32 word [LBA_PACKET+12]
%define LBA_SECTOR_48 word [LBA_PACKET+14]
; `readDisk`'s read buffer's offset.
; The segment is 0x1FE0.
; 0x1FE0:0x63A0 = 0x261A0 = 152.40625KB, 6240B before 0x1FE0:0x7C00 (158.5KB).
%define READBUF 0x63A0 ; max 4KB buffer (min 2KB stack), == stacktop-0x1800
; The offset of the variable storing `readDisk`'s destination buffer's unfilled
; part's segment.
; 6244B before 0x7C00.
%define READADDR_OFF BP-0x60-0x1804 ; pointer within user read buffer
; The offset of the variable storing `readDisk`'s destination buffer's unfilled
; part's offset.
; 6242B before 0x7C00.
%define READADDR_SEG BP-0x60-0x1802
; The offset of the space after the LBA packet.
%define PARAMS LBA_PACKET+0x10
;%define RootDirSecs PARAMS+0x0 ; # of sectors root dir uses
; The offset of the variable storing the first FAT's sector number.
%define fat_start PARAMS+0x2 ; first FAT sector
; The offset of the variable storing the FAT root directory's sector number.
%define root_dir_start PARAMS+0x6 ; first root directory sector
; The offset of the variable storing the FAT data region's sector number.
%define data_start PARAMS+0x0a ; first data sector
;-----------------------------------------------------------------------
; ENTRY
;-----------------------------------------------------------------------
real_start:
; Code below aims to copy the boot sector from 0x0000:0x7C00 (0x7C00 = 31KB) to
; 0x1FE0:0x7C00 (0x27A00 = 158.5KB) because the region containing 0x7C00 will
; be used to store kernel instead.
; (gdb) break *0x7C3E
; Put 0 to IF to disable maskable interrupts.
cli
; Put 0 to DF. When DF is 0, string operations increment SI and DI.
cld
; Put 0 to AX.
; `xor ax, ax` generates 2 bytes machine code compared to `mov ax, 0` 3 bytes.
xor ax, ax
; Put 0 to DS.
mov ds, ax
; Put 0x7C00 to BP.
mov bp, BASE
; a reset should not be needed here
; int 0x13 ; reset drive
; int 0x12 ; get memory available in AX
; mov ax, 0x01e0
; mov cl, 6 ; move boot sector to higher memory
; shl ax, cl
; sub ax, 0x07e0
; Put 0x1FE0 to AX.
mov ax, 0x1FE0
; Put 0x1FE0 to ES.
mov es, ax
; Put 0x7C00 to SI.
mov si, bp
; Put 0x7C00 to DI.
mov di, bp
; Put 256 to CX, to be used as repeat times below.
mov cx, 0x0100
; Copy 2 bytes from [DS:SI] to [ES:DI], repeat 256 times.
; Each time SI and DI are incremented by 2, CX is decremented by 1.
rep movsw
; Jump to `cont` in the 0x1FE0 segment.
jmp word 0x1FE0:cont
; Variable storing the user read buffer's offset.
loadseg_off dw 0
; Variable storing the user read buffer's segment.
loadseg_seg dw LOADSEG
cont:
; Code below aims read the FAT root directory's entries to the user read
; buffer.
; (gdb) break *0x27A5E
; Put 0x1FE0 to DS.
mov ds, ax
; Put 0x1FE0 to SS.
mov ss, ax
; Put 0x7BA0 (96B before 0x1FE0:0x7C00) to SP.
lea sp, [bp-0x60]
; Put 1 to IF to enable maskable interrupts.
sti
;
; Note: some BIOS implementations may not correctly pass drive number
; in DL, however we work around this in SYS.COM by NOP'ing out the use of DL
; (formerly we checked for [drive]==0xff; update sys.c if code moves)
;
; Put the drive number in DL to [drive].
; DL was set by BIOS before loading the boot sector.
; DL = 00h: 1st floppy disk.
; DL = 01h: 2nd floppy disk.
; DL = 80h: 1st hard disk.
; DL = 81h: 2nd hard disk.
; DL = e0h: 1st CD.
mov [drive], dl ; rely on BIOS drive number in DL
; Put 16 to the `LBA_SIZE` field.
mov LBA_SIZE, 10h
; Put the number of sectors to read to the `LBA_SECNUM` field.
mov LBA_SECNUM,1 ; initialise LBA packet constants
; Put `readDisk`'s read buffer's segment to the `LBA_SEG` field.
mov word [LBA_SEG],ds
; Put `readDisk`'s read buffer's offset to the `LBA_OFF` field.
mov word [LBA_OFF],READBUF
; GETDRIVEPARMS: Calculate start of some disk areas.
;
; Put lower 2 bytes of the number of hidden sectors to SI.
; E.g. SI = 0.
mov si, word [nHidden]
; Put higher 2 bytes of the number of hidden sectors to DI.
; E.g. DI = 0.
; Now DI:SI stores the number of hidden sectors.
mov di, word [nHidden+2]
; Add the number of reserved sectors to SI.
; If overflow, CF becomes 1.
; E.g. SI = 1.
add si, word [bsResSectors]
; Add CF to DI.
; Now DI:SI stores the number of hidden and reserved sectors, which is also the
; first FAT's sector number.
; E.g. DI = 0.
adc di, byte 0 ; DI:SI = first FAT sector
; Put lower 2 bytes of the first FAT's sector number to `[fat_start]`.
mov word [fat_start], si
; Put higher 2 bytes of the first FAT's sector number to `[fat_start+2]`.
mov word [fat_start+2], di
; Put the number of FATs to AL.
; E.g. AL = 2.
mov al, [bsFATs]
; Extend AL to AX.
; Now AX stores the number of FATs.
; E.g. AX = 2.
cbw
; Multiply the number of FATs by the number of sectors per FAT.
; DX:AX = AX * [sectPerFat].
; Now DX:AX stores the number of FAT sectors.
; E.g. DX = 0. AX = 18.
mul word [sectPerFat] ; DX:AX = total number of FAT sectors
; Add the number of FAT sectors to the number of hidden and reserved sectors.
; Now DI:SI stores the FAT root directory's sector number.
; E.g. SI = 19. DI = 0.
add si, ax
adc di, dx ; DI:SI = first root directory sector
; Put lower 2 bytes of the FAT root directory's sector number to
; `[root_dir_start]`.
mov word [root_dir_start], si
; Put higher 2 bytes of the FAT root directory's sector number to
; `[root_dir_start+2]`.
mov word [root_dir_start+2], di
; Calculate how many sectors the root directory occupies.
; Put the number of bytes per sector to BX.
; E.g. BX = 512.
mov bx, [bsBytesPerSec]
; Put 5 in CL, to be used as shfit count below.
mov cl, 5 ; divide BX by 32
; Divide the number of bytes per sector by the number of bytes per directory
; entry.
; Now BX stores the number of directory entries per sector.
; E.g. BX = BX / 2^5 = BX / 32 = 512 / 32 = 16.
shr bx, cl ; BX = directory entries per sector
; Put the number of directory entries of the FAT root directory to AX.
; E.g. AX = 224.
mov ax, [bsRootDirEnts]
; Put 0 to DX.
xor dx, dx
; Divide the number of directory entries of the FAT root directory by the
; number of directory entries per sector.
; DX(remainder):AX(quotient) = DX:AX / BX.
; Now AX stores the number of sectors of the FAT root directory.
; E.g. DX:AX = DX:AX / BX = 224 / 16 = 0:14.
div bx
; mov word [RootDirSecs], ax ; AX = sectors per root directory
; Push the number of sectors of the FAT root directory in AX.
push ax
; Add the number of sectors of the FAT root directory to the FAT root
; directory's sector number.
; If overflow, CF becomes 1.
; E.g. SI = SI + AX = 19 + 14 = 33.
add si, ax
; Add CF to DI.
; Now DI:SI stores the FAT data region's sector number.
; E.g. DI = 0.
adc di, byte 0 ; DI:SI = first data sector
; Put lower 2 bytes of the FAT data region's sector number to `[data_start]`.
mov [data_start], si
; Put higher 2 bytes of the FAT data region's sector number to
; `[data_start+2]`.
mov [data_start+2], di
; FINDFILE: Searches for the file in the root directory.
;
; Returns:
; AX = first cluster of file
; First, read the whole root directory
; into the temporary buffer.
; Put lower 2 bytes of the FAT root directory's sector number to AX.
mov ax, word [root_dir_start]
; Put higher 2 bytes of the FAT root directory's sector number to DX.
; DX:AX specifies the starting sector number for `readDisk` below.
mov dx, word [root_dir_start+2]
; Pop the number of sectors of the FAT root directory to DI.
; DI specifies the number of sectors to read for `readDisk` below.
; E.g. DI = 14.
pop di ; mov di, word [RootDirSecs]
; Point ES:BX to the user read buffer.
; ES:BX specifies the destination buffer for `readDisk` below.
; (gdb) break *0x27ABF
les bx, [loadsegoff_60] ; es:bx = 60:0
; Read the FAT root directory's sectors to the user read buffer.
call readDisk
; Code below aims to find the directory entry of the kernel file.
;
; Point ES:DI to the user read buffer.
; (gdb) break *0x27AC5
les di, [loadsegoff_60] ; es:di = 60:0
; Search for KERNEL.SYS file name, and find start cluster.
; Put 11 to CX, to be used as repeat times below.
; 11 means 8+3 style file name.
next_entry: mov cx, 11
; Point SI to the kernel file name.
mov si, filename
; Push the user read buffer's offset in DI.
push di
; Compare byte [DS:SI] with byte [ES:DI], repeat at most 11 times.
; Each time SI and DI are incremented by 1.
; If all 11 characters are equal, ZF becomes 1.
repe cmpsb
; Pop the user read buffer's offset to DI.
; Now ES:DI points to the user read buffer.
pop di
; Put the directory entry's first cluster number to AX.
; A directory entry's byte 0x1A stores its first cluster number.
mov ax, [es:di+0x1A]; get cluster number from directory entry
; If the kernel file name is found, jump to `ffDone`.
je ffDone
; Increment DI by 32 to point to the next directory entry.
add di, byte 0x20 ; go to next directory entry
; Test whether the first byte of the file name is 0, which means end-of-entry.
cmp byte [es:di], 0 ; if the first byte of the name is 0,
; If it is not end-of-entry, jump to `next_entry`.
jnz next_entry ; there is no more files in the directory
; If it is end-of-entry, jump to `boot_error`.
jc boot_error ; fail if not found
ffDone:
; Push the kernel file's first cluster number in AX.
; (gdb) break *0x27AE3
push ax ; store first cluster number
; GETFATCHAIN:
;
; Reads the FAT chain and stores it in a temporary buffer in the first
; 64 kb. The FAT chain is stored an array of 16-bit cluster numbers,
; ending with 0.
;
; The file must fit in conventional memory, so it can't be larger than
; 640 kb. The sector size must be at least 512 bytes, so the FAT chain
; can't be larger than 2.5 KB (655360 / 512 * 2 = 2560).
;
; Call with: AX = first cluster in chain
; Code below aims to load the first FAT to the user read buffer.
;
; Point ES:BX to the user read buffer.
; ES:BX specifies the destination buffer for `readDisk` below.
les bx, [loadsegoff_60] ; es:bx=60:0
; Put the number of sectors per FAT to DI.
; DI specifies the number of sectors to read for `readDisk` below.
mov di, [sectPerFat]
; Put lower 2 bytes of the first FAT's sector number to AX.
mov ax, word [fat_start]
; Put higer 2 bytes of the first FAT's sector number to DX.
; DX:AX specifies the starting sector number for `readDisk` below.
mov dx, word [fat_start+2]
; Read the first FAT's sectors to the user read buffer.
call readDisk
; Pop the kernel file's first cluster number to AX.
; (gdb) break *0x27AF3
pop ax ; restore first cluster number
; Code below aims to put all the cluster numbers of the kernel file to the FAT
; buffer and put a 0 in the end.
; Set ES:DI to the temporary storage for the FAT chain.
; Put DS to ES.
; ES = 0x1FE0.
push ds
pop es
; Put the user read buffer's segment to DS.
; DS = 0x0060.
; SI will be set below to point to each cluster number of the kernel file.
mov ds, [loadseg_60]
; Put the FAT buffer's offset to DI.
; DI = 0x2000.
; Now ES:DI points to the FAT buffer.
mov di, FATBUF
; Put the current cluster number in AX to [ES:DI] in the FAT buffer.
next_clust: stosw ; store cluster number
; Code below aims to get the next cluster number.
;
; Put the current cluster number in AX to SI.
mov si, ax ; SI = cluster number
%ifdef ISFAT12
; This is a FAT-12 disk.
; Each 12-bit cluster number is stored across 2 bytes.
; The bits layout of 2 consective cluster numbers (12 bits * 2 = 24 bits) in 3
; bytes is:
; 0 76543210
; 1 3210BA98
; 2 BA987654
; Multiply the current cluster number by 3.
fat_12: add si, si ; multiply cluster number by 3...
add si, ax
; Divide the current cluster number by 2 to get the offset of the 2 bytes
; storing the next cluster number.
; If the current cluster number is odd, CF becomes 1.
shr si, 1 ; ...and divide by 2
; Put the 2 bytes storing the next cluster number in [DS:SI] to AX.
lodsw
; If the cluster number was even, the cluster value is now in
; bits 0-11 of AX. If the cluster number was odd, the cluster
; value is in bits 4-15, and must be shifted right 4 bits. If
; the number was odd, CF was set in the last shift instruction.
; If CF is 0 which means the current cluster number is even, jump to
; `fat_even`.
jnc fat_even
; If CF is 1 which means the current cluster number is odd.
; Put 4 in CL, to be used as shift count below.
mov cl, 4
; Shift off the lower 4 bits.
shr ax, cl
; Mask off the higher 4 bits.
; Now AX stores the new current cluster number.
fat_even: and ah, 0x0f ; mask off the highest 4 bits
; Test whether the new current cluster number is EOF.
cmp ax, 0x0ff8 ; check for EOF
; If the new current cluster number is not EOF, jump to `next_clust`.
jb next_clust ; continue if not EOF
%endif
%ifdef ISFAT16
; This is a FAT-16 disk. The maximal size of a 16-bit FAT
; is 128 kb, so it may not fit within a single 64 kb segment.
; Put the user read buffer's segment to DX.
; DX = 0x0060.
fat_16: mov dx, [loadseg_60]
; Multiply the current cluster number by 2 to get the offset of the 2 bytes
; storing the next cluster number.
; If overflow, CF becomes 1.
add si, si ; multiply cluster number by two
; If not overflow, jump to `first_half`.
jnc first_half ; if overflow...
; If overflow, add 0x1000 to DX, which points DX to the next 0x10000 bytes
; segment. Notice SI overflows around and SI can address 0x10000 bytes (64KB).
add dh, 0x10 ; ...add 64 kb to segment value
; Put the user read buffer's segment to DS.
; Now DS:SI points to the next cluster number.
first_half: mov ds, dx ; DS:SI = pointer to next cluster
; Put the next cluster number in [DS:SI] to AX.
; Now AX stores the new current cluster number.
lodsw ; AX = next cluster
; Test whether the new current cluster number is EOF.
cmp ax, 0xfff8 ; >= FFF8 = 16-bit EOF
; If the new current cluster number is not EOF, jump to `next_clust`.
jb next_clust ; continue if not EOF
%endif
finished: ; Mark end of FAT chain with 0, so we have a single
; EOF marker for both FAT-12 and FAT-16 systems.
; Put 0 to AX.
xor ax, ax
; Put 0 in AX to [ES:DI] in the FAT buffer to mark the end.
stosw
; Code below aims to load the kernel and jump to it.
;
; Put the FAT buffer's segment in CS to DS.
push cs
pop ds
; loadFile: Loads the file into memory, one cluster at a time.
; Point ES:BX to the user read buffer.
les bx, [loadsegoff_60] ; set ES:BX to load address 60:0
; Point DS:SI to the FAT buffer.
mov si, FATBUF ; set DS:SI to the FAT chain
; Put the cluster number [DS:SI] in the FAT buffer to AX.
cluster_next: lodsw ; AX = next cluster to read
; Test whether the cluster number is 0 which means EOF.
or ax, ax ; EOF?
; If the cluster number is not EOF, jump to `load_next`.
jne load_next ; no, continue
; If the cluster number is EOF, it means the kernel has been fully loaded.
; Put the drive number to BL.
mov bl,dl ; drive (left from readDisk)
; Jump to run the kernel.
jmp far [loadsegoff_60] ; yes, pass control to kernel
; Code below aims to load one cluster of the kernel file.
;
; Decrease the cluster number by 2 to get the 0-based sector number.
load_next: dec ax ; cluster numbers start with 2
dec ax
; Put the number of sectors per cluster to DI.
; The higher byte in DI is unused.
mov di, word [bsSecPerClust]
; Mask off the higher byte in DI.
; DI specifies the number of sectors to read for `readDisk` below.
and di, 0xff ; DI = sectors per cluster
; Multiply the cluster number in AX by the number of sectors per cluster in DI
; to get the sector number, relative to the FAT data region's sector number.
; DX:AX = AX * DI.
; Now DX:AX stores the relative sector number.
mul di
; Add lower 2 bytes of the FAT data region's sector number to AX.
; If overflow, CF becomes 1.
add ax, [data_start]
; Add higher 2 bytes of the FAT data region's sector number and CF to AX.
; Now DX:AX stores the absolute sector number.
; DX:AX specifies the starting sector number for `readDisk` below.
adc dx, [data_start+2] ; DX:AX = first sector to read
; Read one cluster of the kernel file to the user read buffer.
call readDisk
; Jump to `cluster_next`.
jmp short cluster_next
; shows text after the call to this function.
; Print the string following the caller's call instruction.
; The following string's offset is pushed by the caller's call instruction.
;
; Pop the offset of next character to SI.
show: pop si
; Put the character in [DS:SI] to AL.
; SI is incremented.
; Now SI points to one byte after the character.
; If the character is `.`, it will be the last character to print, then SI
; points to the next instruction to return to.
lodsb ; get character
; Push SI as the potential return address.
push si ; stack up potential return address
; Put 0x0E to AH.
; 0x0E means TTY mode for interrupt 0x10 video service.
mov ah,0x0E ; show character
; Invoke interrupt 0x10 video service to print the character in AL.
int 0x10 ; via "TTY" mode
; Test whether the character is `.`.
cmp al,'.' ; end of string?
; If the character is not `.`, jump to print the next character.
jne show ; until done
; If the character is `.`, return.
ret
; Print error message.
boot_error: call show
; db "Error! Hit a key to reboot."
; Error message.
db "Error!."
; Put 0 to AH.
xor ah,ah
; Invoke interrupt 0x13 AH=0 service to reset the disk.
int 0x13 ; reset floppy
; Invoke interrupt 0x16 AH=0 service to wait for a key press.
int 0x16 ; wait for a key
; Invoke interrupt 0x19 AH=0 service to reboot.
int 0x19 ; reboot the machine
; readDisk: Reads a number of sectors into memory.
;
; Call with: DX:AX = 32-bit DOS sector number
; DI = number of sectors to read
; ES:BX = destination buffer
;
; Returns: CF set on error
; ES:BX points one byte after the last byte read.
; Read disk sectors to the read buffer, then copy to the destination buffer.
;
; Push SI.
readDisk: push si
; Put the sector number to read to fields `LBA_SECTOR_0` and `LBA_SECTOR_16`.
mov LBA_SECTOR_0,ax
mov LBA_SECTOR_16,dx
; Put the destination buffer's segment to [READADDR_SEG].
mov word [READADDR_SEG], es
; Put the destination buffer's offset to [READADDR_OFF].
mov word [READADDR_OFF], bx
; Print the following message.
; (gdb) break *0x27B6C
call show
; Message.
; 0x1FE0:0x7D6F
db "."
read_next:
;******************** LBA_READ *******************************
; check for LBA support
; Specify to use interrupt 0x13 AH=0x41 service.
mov ah,041h ;
; Put 0x55AA to BX, as required by interrupt 0x13 AH=0x41 service.
mov bx,055aah ;
; Put the drive number to DL, as required by interrupt 0x13 AH=0x41 service.
mov dl, [drive]
; NOTE: sys must be updated if location changes!!!
; Test whether the drive number is 0 which means the first floppy disk.
test dl,dl ; don't use LBA addressing on A:
; If the drive number is 0, jump to `read_normal_BIOS`.
jz read_normal_BIOS ; might be a (buggy)
; CDROM-BOOT floppy emulation
; Invoke interrupt 0x13 AH=0x41 service to check extensions present.
; If extensions are not present, CF is 1. If present, CX stores the flags:
; 1 – Device access using the LBA packet.
; 2 – Drive locking and ejecting.
; 4 – Enhanced disk drive support.
int 0x13
; If extensions are not present, jump to `read_normal_BIOS`.
jc read_normal_BIOS
; Right-shift CX by 1 bit.
; Now CF stores the lower 1 bit shifted out.
shr cx,1 ; CX must have 1 bit set
; Test whether CF is 1.
; BX = 0xAA55 - 1 + CF - BX = 0xAA54 + CF - 0xAA55 = CF - 1.
sbb bx,0aa55h - 1 ; tests for carry (from shr) too!
; If CF is not 1 which means LBA addressing is not supported, jump to
; `read_normal_BIOS`.
jne read_normal_BIOS
; OK, drive seems to support LBA addressing
; Put the address of the LBA packet to SI, as required by interrupt 0x13
; AH=0x42 service.
lea si,[LBA_PACKET]
; Put 0 to field `LBA_SECTOR_32`.
; setup LBA disk block
mov LBA_SECTOR_32,bx ; bx is 0 if extended 13h mode supported
; Put 0 to field `LBA_SECTOR_48`.
mov LBA_SECTOR_48,bx
; Specify to use interrupt 0x13 AH=0x42 service.
mov ah,042h
; Jump to `do_int13_read`.
jmp short do_int13_read
read_normal_BIOS:
;******************** END OF LBA_READ ************************
; Code block below aims to convert the sector number to read into CHS numbers
; to be used by interrupt 0x13 AH=0x02 service.
;
; Put the sector number to read to DX:CX.
mov cx,LBA_SECTOR_0
mov dx,LBA_SECTOR_16
;
; translate sector number to BIOS parameters
;
;
; abs = sector offset in track
; + head * sectPerTrack offset in cylinder
; + track * sectPerTrack * nHeads offset in platter
;
; Put the number of sectors per track to AL.
mov al, [sectPerTrack]
; Multiply the number of sectors per track by the number of heads to get the
; number of sectors per cylinder.
; AX = AL * [nHeads].
mul byte [nHeads]
; Exchange AX and CX.
; Now CX stores the number of sectors per cylinder.
; Now DX:AX stores the sector number to read.
xchg ax, cx
; cx = nHeads * sectPerTrack <= 255*63
; dx:ax = abs
; Divide the sector number to read by the number of sectors per cylinder.
; DX(remainder):AX(quotient) = DX:AX / CX.
; NOW AX stores the cylinder number.
; NOW DX stores the in-cylinder sector offset.
div cx
; ax = track, dx = sector + head * sectPertrack
; Exchange AX and DX.
; NOW AX stores the in-cylinder sector offset.
; NOW DX stores the cylinder number.
xchg ax, dx
; dx = track, ax = sector + head * sectPertrack
; Divide the in-cylinder sector offset by the number of sectors per track.
; AH(remainder):AL(quotient) = AX / [sectPerTrack].
; Now AL stores the head number, AH stores the in-track sector number.
div byte [sectPerTrack]
; dx = track, al = head, ah = sector
; Put the cylinder number to CX.
mov cx, dx
; cx = track, al = head, ah = sector
; the following manipulations are necessary in order to
; properly place parameters into registers.
; ch = cylinder number low 8 bits
; cl = 7-6: cylinder high two bits
; 5-0: sector
; Put the head number to DH.
mov dh, al ; save head into dh for bios
; CX stores both the cylinder number (10 bits, possible values are 0 to 1023)
; and the sector number (6 bits, possible values are 1 to 63).
; Layout:
; CX: ---CH--- ---CL---
; Cylinder : 76543210 98
; Sector : 543210
;
; Put the lower 8 bits of the cylinder number to CH.
; Put the higher 2 bits of the cylinder number to CL's lower 2 bits.
xchg ch, cl ; set cyl no low 8 bits
; Right-rotate CL's lower 2 bits to higher 2 bits.
ror cl, 1 ; move track high bits into
ror cl, 1 ; bits 7-6 (assumes top = 0)
; Put the in-track sector number to CL's lower 6 bits.
or cl, ah ; merge sector into cylinder
; Increment the in-track sector number to make it 1-based.
inc cx ; make sector 1-based (1-63)
; Point ES:BX to `readDisk`'s read buffer.
; ES:BX specifies interrupt 0x13 AH=0x02 service's read buffer.
les bx,[LBA_OFF]
; Specify to use interrupt 0x13 AH=0x02 service.
; AH=0x02 service means read sectors from drive using CHS addressing.
; AL=0x01 means read 1 sector.
mov ax, 0x0201
do_int13_read:
; Put the drive number to DL, as required by interrupt 0x13 AH=0x02 or
; interrupt 0x13 AH=0x42 service.
mov dl, [drive]
; Invoke interrupt 0x13 AH=0x02 service to read disk using CHS addressing, or
; invoke interrupt 0x13 AH=0x42 service to read disk using LBA addressing.
; If have error, CF becomes 1.
int 0x13
; If have error, jump to `boot_error`.
jc boot_error ; exit on error
; Code below aims to copy sectors from `readDisk`'s read buffer to the
; destination buffer.
;
; Put the number of bytes per sector to AX.
mov ax, word [bsBytesPerSec]
; Push the number of sectors to read in DI.
push di
; Put `readDisk`'s read buffer's offset to SI.
mov si,READBUF ; copy read in sector data to
; Put the destination buffer's unfilled part's segment and offset to ES:DI.
les di,[READADDR_OFF] ; user provided buffer
; Put the number of bytes per sector to CX.
mov cx, ax
; shr cx, 1 ; convert bytes to word count
; rep movsw
; Copy one sector from `readDisk`'s read buffer to the destination buffer.
; Copy one byte from [DS:SI] to [ES:DI], repeat CX times.
; Each time SI and DI are incremented by 1, CX is decremented by 1.
rep movsb
; Pop the number of sectors to read to DI.
pop di
; div byte[LBA_PACKET] ; luckily 16 !!
; Put 4 to CL, to be used as shift count below.
mov cl, 4
; Divide the number of bytes per sector by 16 to get the number of segments
; read.
shr ax, cl ; adjust segment pointer by increasing
; Increment the destination buffer's segment by the number of segments read.
add word [READADDR_SEG], ax ; by paragraphs read in (per sector)
; Increment the LBA packet's sector number's `LBA_SECTOR_0` field.
; If overflow, CF becomes 1.
add LBA_SECTOR_0, byte 1
; Add CF to the LBA packet's sector number's `LBA_SECTOR_16` field.
adc LBA_SECTOR_16, byte 0 ; DX:AX = next sector to read
; Decrement the number of sectors to read in DI.
; If DI becomes 0, ZF becomes 1.
dec di ; if there is anything left to read,
; If the number of sectors to read is not 0, jump to `read_next`.
jnz read_next ; continue
; Put the destination buffer's unfilled part's segment and offset to ES:BX.
les bx, [READADDR_OFF]
; clear carry: unnecessary since adc clears it
; Pop old SI, which was pushed by the first instruction of `readDisk`.
pop si
; Return from `readDisk`.
ret
; Allocate zeros up to $$:0x01F1 exclusive.
; 0x01F1 + 15 = 512.
; The remaining 15 bytes below make the boot sector exactly 512 bytes.
times 0x01f1-$+$$ db 0
; Kernel file name and two bytes of 0.
filename db "KERNEL SYS",0,0
; Mark the boot sector as bootable.
sign dw 0xAA55
%ifdef DBGPRNNUM
; DEBUG print hex digit routines
; Print lower 4 bits of AL as a hex digit.
PrintLowNibble: ; Prints low nibble of AL, AX is destroyed
; Mask off higher 4 bits of AL.
and AL, 0Fh ; ignore upper nibble
; Test whether AL is greater than 9.
cmp AL, 09h ; if greater than 9, then don't base on '0', base on 'A'
; If AL is not greater than 9, jump to `.printme`.
jbe .printme
; If AL is greater than 9, add 7 to AL to later convert AL to character A-F.
add AL, 7 ; convert to character A-F
.printme:
; Convert AL to hex digit.
add AL, '0' ; convert to character 0-9
; Put 0x0E to AH.
; 0x0E means TTY mode for interrupt 0x10 video service.
mov AH,0x0E ; show character
; Invoke interrupt 0x10.
int 0x10 ; via "TTY" mode
; Return.
retn
; Print AL as two hex digits.
PrintAL: ; Prints AL, AX is preserved
; Save AX.
push AX ; store value so we can process a nibble at a time
; Shift higher 4 bits of AL to lower 4 bits.
shr AL, 4 ; move upper nibble into lower nibble
; Print lower 4 bits of AL as a hex digit.
call PrintLowNibble
; Restore AX.
pop AX ; restore for other nibble
; Save AX.
push AX ; but save so we can restore original AX
; Print lower 4 bits of AL as a hex digit.
call PrintLowNibble
; Restore AX.
pop AX ; restore for other nibble
; Return.
retn
; Print AX as hex digits.
PrintNumber: ; Prints (in Hex) value in AX, AX is preserved
; Exchange AH and AL in order to print AH.
xchg AH, AL ; high byte 1st
; Print AL (the old AH value) as two hex digits.
call PrintAL
; Exchange AH and AL in order to print AL.
xchg AH, AL ; now low byte
; Print AL as two hex digits.
call PrintAL
; Return.
retn
%endif
; +--------+
; | |
; | |
; |--------| 4000:0000
; | |
; | FAT |
; | |
; |--------| 2000:0000
; |BOOT SEC|
; |RELOCATE|
; |--------| 1FE0:0000
; | |
; | |
; | |
; | |
; |--------|
; |BOOT SEC|
; |ORIGIN | 07C0:0000
; |--------|
; | |
; | |
; | |
; |--------|
; |KERNEL |
; |LOADED |
; |--------| 0060:0000
; | |
; +--------+
;%define MULTI_SEC_READ 1
; Set segment type.
segment .text
%define BASE 0x7c00
; Let offset base be 0x7C00.
org BASE
; The boot sector's entry point.
; Jump to `real_start`.
Entry: jmp short real_start
; NOP instruction for the third byte, as required by FAT specification.
nop
; The `jmp` and `nop` above take 3 bytes, followed by FAT info variables.
; These variables are initialized when running `SYS.COM` to install the boot
; sector.
;
; The offsets of the FAT info variables:
; bp is initialized to 7c00h
%define bsOemName bp+0x03 ; OEM label
%define bsBytesPerSec bp+0x0b ; bytes/sector
%define bsSecPerClust bp+0x0d ; sectors/allocation unit
%define bsResSectors bp+0x0e ; # reserved sectors
%define bsFATs bp+0x10 ; # of fats
%define bsRootDirEnts bp+0x11 ; # of root dir entries
%define bsSectors bp+0x13 ; # sectors total in image
%define bsMedia bp+0x15 ; media descrip: fd=2side9sec, etc...
%define sectPerFat bp+0x16 ; # sectors in a fat
%define sectPerTrack bp+0x18 ; # sectors/track
%define nHeads bp+0x1a ; # heads
%define nHidden bp+0x1c ; # hidden sectors
%define nSectorHuge bp+0x20 ; # sectors if > 65536
%define xsectPerFat bp+0x24 ; Sectors/Fat
%define xrootClst bp+0x2c ; Starting cluster of root directory
%define drive bp+0x40 ; Drive number
; Allocate 87 bytes of zeros from $$+3 (0x7C03) up to $$+0x5A (0x7C5A)
; exclusive.
times 0x5a-$+$$ db 0
; The user read buffer's segment.
; The starting address is 0x0060:0x0000 = 1.5KB.
; The user read buffer will store the kernel in the end.
; The reason to choose the starting address 1.5KB is because the first 1.5KB
; of the memory stores BIOS' interrupt vector table and other data thus should
; not be overwritten.
%define LOADSEG 0x0060
; The FAT buffer's segment.
; The FAT buffer stores the kernel file's cluster numbers.
; The starting address is 0x2000:0x0000 = 128KB.
%define FATSEG 0x2000
; The offset of the variable storing the sector number of the last FAT sector
; read.
%define fat_sector bp+0x48 ; last accessed sector of the FAT
; The offset of the variable storing the user read buffer's offset.
%define loadsegoff_60 bp+loadseg_off-Entry ; FAR pointer = 60:0
; The offset of the variable storing the user read buffer's segment.
%define loadseg_60 bp+loadseg_seg-Entry
; The offset of the variable storing the first FAT's sector number.
%define fat_start bp+0x5e ; first FAT sector
; The offset of the variable storing the FAT data region's sector number.
%define data_start bp+0x62 ; first data sector
; The offset of the variable storing the in-sector mask.
; [fat_secmask] = `NumberOfClusterNumbersPerFATSector - 1`.
%define fat_secmask bp+0x66 ; number of clusters in a FAT sector - 1
; The offset of the variable storing the shift count.
; [fat_secshift] = `log(2, NumberOfClusterNumbersPerFATSector)`.
%define fat_secshift bp+0x68 ; fat_secmask+1 = 2^fat_secshift
;-----------------------------------------------------------------------
; ENTRY
;-----------------------------------------------------------------------
real_start: cld
; Put 0 to IF to disable maskable interrupts.
cli
; Put 0 to AX.
sub ax, ax
; Put 0 to DS.
mov ds, ax
; Put 0x7C00 to BP.
mov bp, 0x7c00
; Put 0x1FE0 to AX.
mov ax, 0x1FE0
; Put 0x1FE0 to ES.
mov es, ax
; Put 0x7C00 to SI.
mov si, bp
; Put 0x7C00 to DI.
mov di, bp
; Put 256 to CX, to be used as repeat times below.
mov cx, 0x0100
; Copy 2 bytes from [DS:SI] to [ES:DI], repeat 256 times.
; Each time SI and DI are incremented by 2, CX is decremented by 1.
rep movsw ; move boot code to the 0x1FE0:0x0000
; Jump to `cont` in the 0x1FE0 segment.
jmp word 0x1FE0:cont
; Variable storing the user read buffer's offset.
loadseg_off dw 0
; Variable storing the user read buffer's segment.
loadseg_seg dw LOADSEG
; Put 0x1FE0 to DS.
cont: mov ds, ax
; Put 0x1FE0 to SS.
mov ss, ax
; Put 0x7BE0 (32B before 0x1FE0:0x7C00) to SP.
lea sp, [bp-0x20]
; Put 1 to IF to enable maskable interrupts.
sti
; Put the drive number in DL to [drive].
; DL was set by BIOS before loading the boot sector.
; DL = 00h: 1st floppy disk.
; DL = 01h: 2nd floppy disk.
; DL = 80h: 1st hard disk.
; DL = 81h: 2nd hard disk.
; DL = e0h: 1st CD.
mov [drive], dl ; BIOS passes drive number in DL
; call print
; db "Loading ",0
; Calc Params
; Fat_Start
; Put lower 2 bytes of the number of hidden sectors to SI.
mov si, word [nHidden]
; Put higher 2 bytes of the number of hidden sectors to DI.
; Now DI:SI stores the number of hidden sectors.
mov di, word [nHidden+2]
; Add the number of reserved sectors to SI.
; If overflow, CF becomes 1.
add si, word [bsResSectors]
; Add CF to DI.
; Now DI:SI stores the number of hidden and reserved sectors, which is also the
; first FAT's sector number.
adc di, byte 0
; Put lower 2 bytes of the first FAT's sector number to `[fat_start]`.
mov word [fat_start], si
; Put higher 2 bytes of the first FAT's sector number to `[fat_start+2]`.
mov word [fat_start+2], di
; Data_Start
; Put the number of FATs to AL.
mov al, [bsFATs]
; Extend AL to AX.
; Now AX stores the number of FATs.
cbw
; Push the number of FATs in AX.
push ax
; Multiply the number of FATs by higher 2 bytes of the number of sectors per
; FAT.
; DX:AX = AX * [xsectPerFat+2].
mul word [xsectPerFat+2]
; Add AX to higher 2 bytes of the first FAT's sector number.
add di, ax
; Pop the number of FATs to AX.
pop ax
; Multiply the number of FATs by lower 2 bytes of the number of sectors per
; FAT.
; DX:AX = AX * [xsectPerFat].
mul word [xsectPerFat]
; Add lower 2 bytes of the first FAT's sector number in SI to AX.
; If overflow, CF becomes 1.
add ax, si
; Add higher 2 bytes of the first FAT's sector number in DI and CF to DX.
; Now DX:AX stores the FAT data region's sector number.
adc dx, di
; Put lower 2 bytes of the FAT data region's sector number to [data_start].
mov word[data_start], ax
; Put higher 2 bytes of the FAT data region's sector number to [data_start+2].
; Now [data_start] stores the FAT data region's sector number.
mov word[data_start+2], dx
; fat_secmask
; Put the number of bytes per sector to AX.
mov ax, word[bsBytesPerSec]
; Divide AX by 4 to get the number of cluster numbers per FAT sector.
; Notice one cluster number is 4 bytes.
shr ax, 1
shr ax, 1
; Subtract AX by 1.
; Now AX stores the in-sector mask `NumberOfClusterNumbersPerFATSector - 1`.
dec ax
; Put the in-sector mask to [fat_secmask].
mov word [fat_secmask], ax
; fat_secshift
; cx = temp
; ax = fat_secshift
; Exchange AX and CX.
; Now AX is 0, CX stores `NumberOfClusterNumbersPerFATSector - 1`.
xchg ax, cx ; cx = 0 after movsw
; Add 1 to CX.
; Now CX stores `NumberOfClusterNumbersPerFATSector`.
inc cx
secshift: inc ax
; Right-shift CX by 1.
shr cx, 1
; Test whether CX is 1.
cmp cx, 1
; If CX is not 1, jump to `secshift`.
jne secshift
; Now AX stores the shift count `log(2, NumberOfClusterNumbersPerFATSector)`.
; Put the shift count to [fat_secshift].
mov byte [fat_secshift], al
; Put 0 to CX.
dec cx
; FINDFILE: Searches for the file in the root directory.
;
; Returns:
; DX:AX = first cluster of file
; Put 0 to lower 2 bytes of [fat_sector].
mov word [fat_sector], cx ; CX is 0 after "dec"
; Put 0 to higher 2 bytes of [fat_sector].
mov word [fat_sector + 2], cx
; Put lower 2 bytes of the FAT root directory's cluster number to AX.
mov ax, word [xrootClst]
; Put higher 2 bytes of the FAT root directory's cluster number to DX.
mov dx, word [xrootClst + 2]
ff_next_cluster:
; Push higher 2 bytes of the FAT root directory's current cluster number in DX.
push dx ; save cluster
; Push lower 2 bytes of the FAT root directory's current cluster number in AX.
push ax
; Convert the FAT root directory's current cluster number in DX:AX to sector
; number.
; Now DX:AX stores the FAT root directory's current sector number.
; Now BX stores the number of sectors per cluster.
; If end-of-chain is met, CF becomes 1.
call convert_cluster
; If CF is 1, jump to `boot_error`.
jc boot_error ; EOC encountered
ff_next_sector:
; Push the number of remaining sectors of the current cluster in BX.
push bx ; save sector count
; Point ES:BX to the user read buffer.
; ES:BX specifies the destination buffer for `readDisk` below.
les bx, [loadsegoff_60]
; Read one sector of the current cluster of the FAT root directory to the user
; read buffer.
; Now DX:AX stores the next sector number.
; Now ES:BX points to the unfilled part of the read buffer.
call readDisk
; Push lower 2 bytes of the next sector number in DX.
push dx ; save sector
; Push higher 2 bytes of the next sector number in AX.
push ax
; Put the number of bytes per sector to AX.
mov ax, [bsBytesPerSec]
; Search for KERNEL.SYS file name, and find start cluster.
; Put 11 to CX, to be used as repeat times below.
; 11 means 8+3 style file name.
ff_next_entry: mov cx, 11
; Point SI to the kernel file name.
mov si, filename
; Put the remaining number of bytes of the current sector to DI.
mov di, ax
; Subtract the remaining number of bytes of the current sector by 32.
; Now ES:DI points to the n-th last directory entry.
sub di, 0x20
; Compare byte [DS:SI] with byte [ES:DI], repeat at most 11 times.
; Each time SI and DI are incremented by 1.
; If all 11 characters are equal, ZF becomes 1.
repe cmpsb
; If ZF is 1, jump to `ff_done`.
jz ff_done
; Subtract AX by 32.
; Now AX stores the remaining number of bytes of the current sector.
sub ax, 0x20
; IF AX is not 0, jump to `ff_next_entry`.
jnz ff_next_entry
; Pop lower 2 bytes of the next sector number to AX.
pop ax ; restore sector
; Pop higher 2 bytes of the next sector number to DX.
pop dx
; Pop the number of remaining sectors of the current cluster to BX.
pop bx ; restore sector count
; Decrement the number of remaining sectors of the current cluster.
dec bx
; If BX is not 0, jump to `ff_next_sector`.
jnz ff_next_sector
ff_find_next_cluster:
; Pop lower 2 bytes of the FAT root directory's current cluster number to
; AX.
pop ax ; restore current cluster
; Pop higher 2 bytes of the FAT root directory's current cluster number to
; DX.
pop dx
; Put the FAT root directory's next cluster number to DX:AX.
call next_cluster
; Jump to `ff_next_cluster`.
jmp short ff_next_cluster
ff_done:
; Put lower 2 bytes of the first cluster number of the kernel file to AX.
; `-11` is because ES:DI is pointing to past the end of the file name field of
; the directory entry of the kernel file.
mov ax, [es:di+0x1A-11] ; get cluster number
; Put higher 2 bytes of the first cluster number of the kernel file to DX.
mov dx, [es:di+0x14-11]
c4:
; Put 0 to BX.
; Now ES:BX points to the read buffer.
sub bx, bx ; ES points to LOADSEG
; Push higher 2 bytes of the current cluster number of the kernel file in DX.
c5: push dx
; Push lower 2 bytes of the current cluster number of the kernel file in AX.
push ax
; Push the remaining number of sectors of the current cluster in BX.
push bx
; Convert the cluster number in DX:AX to sector number.
; Now DX:AX stores the sector number.
; Now BX stores the number of sectors per cluster.
; If end-of-chain is met, CF becomes 1.
call convert_cluster
; If end-of-chain is met, jump to `boot_success`.
jc boot_success
; Put the number of sectors per cluster to DI.
mov di, bx
; Pop the remaining number of sectors of the current cluster to BX.
pop bx
c6:
; Read one sector into the read buffer.
; Now ES:BX points to the unfilled part of the read buffer.
call readDisk
; Decrement the remaining number of sectors of the current cluster.
dec di
; If the remaining number of sectors of the current cluster is not 0, jump to
; `c6`.
jnz c6
; Pop lower 2 bytes of the current cluster number of the kernel file to AX.
pop ax
; Pop higher 2 bytes of the current cluster number of the kernel file to DX.
pop dx
; Put the FAT root directory's next cluster number to DX:AX.
call next_cluster
; Jump to `c5`.
jmp short c5
boot_error:
; Put 0 to AH.
xor ah,ah
; Invoke interrupt 0x16 AH=0 service to wait for a key press.
int 0x16 ; wait for a key
; Invoke interrupt 0x19 AH=0 service to reboot.
int 0x19 ; reboot the machine
; input:
; DX:AX - cluster
; output:
; DX:AX - next cluster
; CX = 0
; modify:
; DI
next_cluster:
; Push ES.
push es
; Put lower 2 bytes of the current cluster number to DI.
mov di, ax
; Mask DI with the in-sector mask `NumberOfClusterNumbersPerSector - 1`.
; Now DI stores the next cluster number's in-sector offset.
and di, [fat_secmask]
; Put the shift count to CX.
mov cx, [fat_secshift]
cn_loop:
; Right-shift DX by 1.
; Now CF stores the bit shifted out.
shr dx,1
; Right-carry-rotate AX by 1.
; The bit in CF becomes the highest bit of AX.
rcr ax,1
; Decrement the shift count in CX.
dec cx
; If the shift count is not 0, jump to `cn_loop`.
jnz cn_loop ; DX:AX fat sector where our
; cluster resides
; DI - cluster index in this
; sector
; Now DX:AX stores the next cluster number's FAT sector number, relative to the
; the first FAT's sector number.
; Multiply DI by 4 to get the next cluster number's byte offset from the FAT
; buffer.
; Notice a cluster number is 4 bytes.
shl di,1 ; DI - offset in the sector
shl di,1
; Add lower 2 bytes of the first FAT's sector number to AX.
; If overflow, CF becomes 1.
add ax, [fat_start]
; Add higher 2 bytes of the first FAT's sector number and CF to DX.
; Now DX:AX stores the next cluster number's FAT sector absolute number.
adc dx, [fat_start+2] ; DX:AX absolute fat sector
; Push BX.
push bx
; Put the FAT buffer's segment to BX.
mov bx, FATSEG
; Put the FAT buffer's segment to ES.
mov es, bx
; Put 0 to BX.
; Now ES:BX points to the FAT buffer.
sub bx, bx
; Compare lower 2 bytes of the sector number of the FAT sector holding the next
; cluster number with lower 2 bytes of the sector number of the FAT sector last
; read.
cmp ax, [fat_sector]
; If not equal, jump to `cn1`.
jne cn1 ; if the last fat sector we
; read was this, than skip
; Compare higher 2 bytes of the sector number of the FAT sector holding the
; next cluster number with higher 2 bytes of the sector number of the FAT
; sector last read.
cmp dx,[fat_sector+2]
; If equal, jump to `cn_exit`.
je cn_exit
cn1:
; Put lower 2 bytes of the next cluster number's FAT sector number in AX to
; [fat_sector].
mov [fat_sector],ax ; save the fat sector number,
; Put higher 2 bytes of the next cluster number's FAT sector number in DX to
; [fat_sector+2].
mov [fat_sector+2],dx ; we are going to read
; Read the next cluster number's FAT sector into the user read buffer.
call readDisk
cn_exit:
; Pop old BX.
pop bx
; Put lower 2 bytes of the next cluster number to AX.
mov ax, [es:di] ; DX:AX - next cluster
; Put higher 2 bytes of the next cluster number to DX.
mov dx, [es:di + 2] ;
; Pop old ES.
pop es
; Return.
ret
boot_success:
; Put the drive number to BL.
mov bl, [drive]
; Jump to run the loaded kernel.
jmp far [loadsegoff_60]
; Convert cluster to the absolute sector
;input:
; DX:AX - target cluster
;output:
; DX:AX - absoulute sector
; BX - [bsSectPerClust]
;modify:
; CX
convert_cluster:
; 0x0FFFFFF8 means end-of-chain.
; Compare DX with 0x0FFF.
cmp dx,0x0fff
; If DX is not 0x0FFF, jump to `c3`.
jne c3
; Compare AX with 0xFFF8.
cmp ax,0xfff8
; If AX < 0xFFF8, jump to `c3`.
jb c3 ; if cluster is EOC (carry is set), do ret
; If AX >= 0xFFF8, put 1 to CF to indicate end-of-chain.
stc
; Return.
ret
c3:
; Push higher 2 bytes of the FAT root directory's cluster number in DX to CX.
mov cx, dx ; sector = (cluster - 2)*clussize +
; + data_start
; Subtract lower 2 bytes of the FAT root directory's cluster number in AX by 2.
; If underflow, CF becomes 1.
sub ax, 2
; Subtract CX by CF.
; Now CX:AX stores the 0-based cluster number.
sbb cx, byte 0 ; CX:AX == cluster - 2
; Put the number of sectors per cluster to BL.
mov bl, [bsSecPerClust]
; Put 0 to BH.
; Now BX stores the number of sectors per cluster.
sub bh, bh
; Exchange CX and AX.
; Now AX:CX stores the 0-based cluster number.
xchg cx, ax ; AX:CX == cluster - 2
; Multiply higher 2 bytes of the 0-based cluster number in AX by the number of
; sectors per cluster in BX.
; DX:AX = AX * BX.
mul bx ; first handle high word
; DX must be 0 here
; Exchange CX and AX.
; Now AX stores lower 2 bytes of the 0-based cluster number.
; Now CX stores the result of multiplying higher 2 bytes of the 0-based cluster
; number by the number of sectors per cluster.
xchg ax, cx ; then low word
; Multiply lower 2 bytes of the 0-based cluster number in AX by the number of
; sectors per cluster in BX.
; DX:AX = AX * BX.
mul bx
; Add the result of multiplying higher 2 bytes of the 0-based cluster number by
; the number of sectors per cluster in CX to DX.
; Now DX stores higher 2 bytes of the sector number, relative to the FAT data
; region's sector number.
add dx, cx ; DX:AX target sector
; Add lower 2 bytes of the FAT data region's sector number to AX.
; Now AX stores lower 2 bytes of the absolute sector number.
; If overflow, CF becomes 1.
add ax, [data_start]
; Add higher 2 bytes of the FAT data region's sector number and CF to DX.
; Now DX stores higher 2 bytes of the absolute sector number.
adc dx, [data_start + 2]
; Return.
ret
; prints text after call to this function.
print_1char:
; Put 0 to BX.
; BH is page number.
; BL is color.
xor bx, bx ; video page 0
; Put 0x0E to AH.
; 0x0E means TTY mode for interrupt 0x10 video service.
mov ah, 0x0E ; else print it
; Invoke interrupt 0x10 video service to print the character in AL.
int 0x10 ; via TTY mode
; Pop the offset of the next character to SI.
print: pop si ; this is the first character
; Put the character in [DS:SI] to AL.
; SI is incremented.
; Now SI points to one byte after the character.
; If the character in AL is value 0, it means end of characters, then SI points
; to the next instruction to return to.
print1: lodsb ; get token
; Push SI as the potential return address.
push si ; stack up potential return address
; Test whether the character in AL is value 0.
cmp al, 0 ; end of string?
; If the character in AL is not value 0, jump to `print_1char`.
jne print_1char ; until done
; If the character in AL is value 0, return.
ret ; and jump to it
;input:
; DX:AX - 32-bit DOS sector number
; ES:BX - destination buffer
;output:
; ES:BX points one byte after the last byte read.
; DX:AX - next sector
;modify:
; ES if DI * bsBytesPerSec >= 65536, CX
readDisk:
; Push higher 2 bytes of the sector number in DX.
read_next: push dx
; Push lower 2 bytes of the sector number in AX.
push ax
;
; translate sector number to BIOS parameters
;
;
; abs = sector offset in track
; + head * sectPerTrack offset in cylinder
; + track * sectPerTrack * nHeads offset in platter
;
; Exchange AX and CX.
; Now CX stores lower 2 bytes of the sector number.
xchg ax, cx
; Put the number of sectors per track in AL.
mov al, [sectPerTrack]
; Multiply the number of sectors per track by the number of heads to get the
; number of sectors per cylinder.
; AX = AL * [nHeads].
mul byte [nHeads]
; Exchange AX and CX.
; Now AX stores lower 2 bytes of the sector number.
; Now CX stores the number of sectors per cylinder.
xchg ax, cx
; cx = nHeads * sectPerTrack <= 255*63
; dx:ax = abs
; Divide the sector number to read by the number of sectors per cylinder.
; DX(remainder):AX(quotient) = DX:AX / CX.
; NOW AX stores the cylinder number.
; NOW DX stores the in-cylinder sector offset.
div cx
; ax = track, dx = sector + head * sectPertrack
; Exchange AX and DX.
; NOW AX stores the in-cylinder sector offset.
; NOW DX stores the cylinder number.
xchg ax, dx
; dx = track, ax = sector + head * sectPertrack
; Divide the in-cylinder sector offset by the number of sectors per track.
; AH(remainder):AL(quotient) = AX / [sectPerTrack].
; Now AL stores the head number, AH stores the in-track sector number.
div byte [sectPerTrack]
; dx = track, al = head, ah = sector
; Put the cylinder number to CX.
mov cx, dx
; cx = track, al = head, ah = sector
; the following manipulations are necessary in order to
; properly place parameters into registers.
; ch = cylinder number low 8 bits
; cl = 7-6: cylinder high two bits
; 5-0: sector
; Put the head number to DH.
mov dh, al ; save head into dh for bios
; CX stores both the cylinder number (10 bits, possible values are 0 to 1023)
; and the sector number (6 bits, possible values are 1 to 63).
; Layout:
; CX: ---CH--- ---CL---
; Cylinder : 76543210 98
; Sector : 543210
;
; Put the lower 8 bits of the cylinder number in CH.
; Put the higher 2 bits of the cylinder number in CL's lower 2 bits.
xchg ch, cl ; set cyl no low 8 bits
; Right-rotate CL's lower 2 bits to higher 2 bits.
ror cl, 1 ; move track high bits into
ror cl, 1 ; bits 7-6 (assumes top = 0)
; Increment the in-track sector number to make it 1-based.
inc ah ; sector offset from 1
; Put the in-track sector number in CL's lower 6 bits.
or cl, ah ; merge sector into cylinder
; Specify to use interrupt 0x13 AH=0x02 service.
; AH 0x02 service means read sectors from drive using CHS addressing.
; AL 0x01 means read 1 sector.
mov ax, 0x0201
; Put the drive number to DL.
mov dl, [drive]
; Invoke interrupt 0x13 AH=0x02 service to read disk using CHS addressing.
; If have error, CF becomes 1.
int 0x13
; Pop lower 2 bytes of the sector number to AX.
pop ax
; Pop higher 2 bytes of the sector number to DX.
pop dx
; If no error, jump to `read_ok`.
jnc read_ok ; jump if no error
; Specify to use interrupt 0x13 AH=0 service.
xor ah, ah ; else, reset floppy
; Invoke interrupt 0x13 AH=0 service to reset the disk.
int 0x13
; Jump to `read_next`.
jmp short read_next
read_ok:
; Increment BX by the number of bytes per sector.
; Now ES:BX points to the unfilled part of the read buffer.
; If overflow, CF becomes 1.
add bx, word [bsBytesPerSec]
; If CF is 0, jump to `no_incr_es`.
jnc no_incr_es ; if overflow...
; If CF is 1.
; Put ES to CX.
mov cx, es
; Add 0x1000 to CX, which means pointing ES to the next 0x10000 bytes segment.
; Notice BX overflows around and BX can address 0x10000 bytes (64KB).
add ch, 0x10 ; ...add 1000h to ES
; Put CX to ES.
mov es, cx
no_incr_es:
; Increment AX.
; If overflow, CF becomes 1.
add ax,byte 1
; Add CF to DX.
adc dx,byte 0
; Return.
ret
; Allocate zeros up to $$:0x01F1 exclusive.
; 0x01F1 + 15 = 512.
; The remaining 15 bytes below make the boot sector exactly 512 bytes.
times 0x01f1-$+$$ db 0
; Kernel file name and two bytes of 0.
filename db "KERNEL SYS",0,0
; Mark the boot sector as bootable.
sign dw 0xAA55
; 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
; ...
; Set segment type.
segment .text
; Let offset base be 0x7C00.
org 0x7c00 ; this is a boot sector
; The boot sector's entry point.
; Jump to `real_start`.
Entry: jmp short real_start
; NOP instruction for the third byte, as required by FAT specification.
nop
; The `jmp` and `nop` above take 3 bytes, followed by FAT info variables.
; These variables are initialized when running `SYS.COM` to install the boot
; sector.
;
; The offsets of the FAT info variables:
; 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
; The user read buffer's segment.
; The starting address is 0x0060:0x0000 = 1.5KB.
; The user read buffer will store the kernel in the end.
; The reason to choose the starting address 1.5KB is because the first 1.5KB
; of the memory stores BIOS' interrupt vector table and other data thus should
; not be overwritten.
%define LOADSEG 0x0060
; The FAT buffer's segment.
; The FAT buffer stores the kernel file's cluster numbers.
; The starting address is 0x2000:0x0000 = 128KB.
%define FATSEG 0x2000
; The offset of the variable storing the shift count.
; [fat_secshift] = `log(2, NumberOfClusterNumbersPerFATSector)`.
%define fat_secshift fat_afterss-1 ; each fat sector describes 2^??
; clusters (db) (selfmodifying)
; The offset of the variable storing the sector number of the last FAT sector
; read.
%define fat_sector bp+0x44 ; last accessed FAT sector (dd)
; (overwriting unused bytes)
; The offset of the variable storing the first FAT's sector number.
%define fat_start bp+0x48 ; first FAT sector (dd)
; (overwriting unused bytes)
; The offset of the variable storing the FAT data region's sector number.
%define data_start bp+0x4c ; first data sector (dd)
; (overwriting unused bytes)
; Allocate 87 bytes of zeros from $$+3 (0x7C03) up to $$+0x5A (0x7C5A)
; exclusive.
times 0x5a-$+$$ db 0
; 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
; Put 0 to IF to disable maskable interrupts.
cli
; Put 0 to AX.
sub ax, ax
; Put 0 to DS.
mov ds, ax
; Put 0x7C00 to BP.
mov bp, 0x7c00
; Put 0x1FE0 to AX.
mov ax, 0x1FE0
; Put 0x1FE0 to ES.
mov es, ax
; Put 0x7C00 to SI.
mov si, bp
; Put 0x7C00 to DI.
mov di, bp
; Put 256 to CX, to be used as repeat times below.
mov cx, 0x0100
; Copy 2 bytes from [DS:SI] to [ES:DI], repeat 256 times.
; Each time SI and DI are incremented by 2, CX is decremented by 1.
rep movsw ; move boot code to the 0x1FE0:0x0000
; Jump to `cont` in the 0x1FE0 segment.
jmp word 0x1FE0:cont
; Variable storing the user read buffer's offset and segment.
loadseg_off dw 0, LOADSEG
; -------------
; Put 0x1FE0 to DS.
cont: mov ds, ax
; Put 0x1FE0 to SS.
mov ss, ax ; stack and BP-relative moves up, too
; Put 0x7BE0 (32B before 0x1FE0:0x7C00) to SP.
lea sp, [bp-0x20]
; Put 1 to IF to enable maskable interrupts.
sti
; Put the drive number in DL to [drive].
; DL was set by BIOS before loading the boot sector.
; DL = 00h: 1st floppy disk.
; DL = 01h: 2nd floppy disk.
; DL = 80h: 1st hard disk.
; DL = 81h: 2nd hard disk.
; DL = e0h: 1st CD.
mov [drive], dl ; BIOS passes drive number in DL
; Put the message address to SI.
mov si, msg_LoadFreeDOS
; Print the message.
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)
; Put 0 to EAX.
calc_params: xor eax, eax
; Put 0 to [fat_sector].
mov [fat_sector], eax ; init buffer status
; first, find fat_start:
; Put the number of reserved sectors to AX.
mov ax, [bsResSectors] ; no movzx eax, word... needed
; Add the number of hidden sectors to EAX.
; Now EAX stores the FAT's sector number.
add eax, [nHidden]
; Put the FAT's sector number to [fat_start].
mov [fat_start], eax ; first FAT sector
; Put the FAT's sector number to [data_start].
mov [data_start], eax ; (only first part of value)
; next, find data_start:
; Put the number of FATs to EAX.
mov eax, [bsFATs] ; no movzx ... byte needed:
; the 2 dw after the bsFATs db are 0 by FAT32 definition :-).
; Multiply the number of FATs by the number of sectors per FAT.
; EDX:EAX = EAX * [xsectPerFat].
; Now EDX:EAX stores the number of FAT sectors.
imul dword [xsectPerFat] ; (also changes edx)
; Add the number of FAT sectors to [data_start].
; Now [data_start] stores the FAT data region's sector number.
add [data_start], eax ; first DATA sector
; (adding in RAM is shorter!)
; finally, find fat_secshift:
; Put 512 to AX.
; This corresponds to the initial value `7` of the shift count [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
; Compare AX with the number of bytes per sector.
fatss_scan: cmp ax, [bsBytesPerSec]
; If equal, jump to `fatss_found`.
jz fatss_found
; If not equal, multiply AX by 2.
add ax,ax
;--- inc cx
; Increment the shift count.
; The initial value is 7.
inc word [fat_secshift] ;XXX ; initially 9-2 (byte!)
; Jump to `fatss_scan`.
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
; Put the FAT root directory's cluster number to EAX.
mov eax, [xrootClst] ; root dir cluster
; Push the FAT root directory's current cluster number in EAX.
ff_next_clust: push eax ; save cluster
; Convert the cluster number in EAX to sector number.
; Now EAX stores the sector number.
; Now EDX stores the number of sectors per cluster.
; If end-of-chain is met, CF becomes 1.
call convert_cluster
; If end-of-chain is met, jump to `boot_error`.
jc boot_error ; EOC encountered
; EDX is clust/sector, EAX is sector
; Point ES:BX to the user read buffer.
; ES:BX specifies the destination buffer for `readDisk` below.
ff_next_sector: les bx, [loadsegoff_60] ; load to loadseg:0
; Read one sector of the FAT root directory.
call readDisk
;--- push eax ; save sector
;--- xor ax, ax ; first dir. entry in this sector
; Put 0 to DI.
; Now ES:DI points to the user read buffer.
xor di, di ;XXX
; Search for KERNEL.SYS file name, and find start cluster.
; Put 11 to CX, to be used as repeat times below.
; 11 means 8+3 style file name.
ff_next_entry: mov cx, 11
; Point SI to the kernel file name.
mov si, filename
;--- mov di, ax
; Compare byte [DS:SI] with byte [ES:DI], repeat at most 11 times.
; Each time SI and DI are incremented by 1.
; If all 11 characters are equal, ZF becomes 1.
repe cmpsb
; If the file name is found, jump to `ff_done`.
jz ff_done ; note that di now is at dirent+11
;--- add ax, 0x20 ; next directory entry
;--- cmp ax, [bsBytesPerSec] ; end of sector reached?
; Increment DI by 32 bytes to point to the next directory entry.
add di, byte 0x20 ;XXX
; Bitwise-AND DI with 0xFFE0 to mask off the least 5 bits.
; The result is a multiple of 32.
and di, byte -0x20 ; 0xffe0 ;XXX
; Compare DI with the number of bytes per sector.
cmp di, [bsBytesPerSec] ;XXX
; If not equal, jump to `ff_next_entry`.
jnz ff_next_entry
;--- pop eax ; restore sector
; Decrement the number of remaining sectors of the current cluster in DX.
dec dx ; next sector in cluster
; If the number of remaining sectors of the current cluster in DX is not 0,
; jump to `ff_next_sector` to process the next sector.
jnz ff_next_sector
; Pop the FAT root directory's current cluster number in EAX.
ff_walk_fat: pop eax ; restore current cluster
; Put the FAT root directory's next cluster number to EAX.
call next_cluster ; find next cluster
; Jump to `ff_next_clust`.
jmp ff_next_clust
; Push lower 2 bytes of the first cluster number of the kernel file.
; `-11` is because ES:DI is pointing to past the end of the file name field of
; the directory entry of the kernel file.
ff_done: push word [es:di+0x14-11] ; get cluster number HI
; Push higher 2 bytes of the first cluster number of the kernel file.
push word [es:di+0x1A-11] ; get cluster number LO
; Pop the first cluster number of the kernel file to EAX.
pop eax ; convert to 32bit
; Put 0 to BX.
; Now ES:BX points to the user read buffer.
sub bx, bx ; ES points to LOADSEG
; (kernel -> ES:BX)
; -------------
; Push the current cluster number of the kernel file in EAX.
read_kernel: push eax
; Convert the current cluster number in EAX to sector number.
; Now EAX stores the starting sector number of the current cluster of the
; kernel file.
; Now EDX stores the number of sectors per cluster.
call convert_cluster
; If end-of-chain is met, jump to `boot_success`.
jc boot_success ; EOC encountered - done
; EDX is sectors in cluster, EAX is sector
; Read one sector of the kernel file.
rk_in_cluster: call readDisk
; Decrement the number of sectors to read in DX.
dec dx
; If the number of sectors to read in DX is not 0, jump to `rk_in_cluster`.
jnz rk_in_cluster ; loop over sect. in cluster
; Pop the kernel file's current cluster number to EAX.
rk_walk_fat: pop eax
; Put the kernel file's next cluster number to EAX.
call next_cluster
; Jump to read the kernel file's next cluster.
jmp read_kernel
;-----------------------------------------------------------------------
; Put the drive number to BL.
boot_success: mov bl, [drive]
; Jump to run the loaded kernel.
jmp far [loadsegoff_60]
;-----------------------------------------------------------------------
; Point SI to the error message.
boot_error: mov si, msg_BootError
; Print the error message.
call print ; modifies AX BX SI
; Put 0 to AH.
wait_key: xor ah,ah
; Invoke interrupt 0x16 AH=0 service to wait for a key press.
int 0x16 ; wait for a key
; Invoke interrupt 0x19 AH=0 service to reboot.
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
; Push ES.
next_cluster: push es
; Push DI.
push di
; Push BX.
push bx
; Put the current cluster number in AX to DI.
mov di, ax
; Multiply DI by 4 to get the next cluster number's byte offset from the FAT
; buffer.
; Notice a cluster number is 4 bytes.
shl di, 2 ; 32bit FAT
; Push lower 2 bytes of the current cluster number in AX.
push ax
; Put the number of bytes per sector to AX.
mov ax, [bsBytesPerSec]
; Subtract the number of bytes per sector in AX by 1 to get the in-sector mask.
dec ax
; Mask DI to get the in-sector offset.
and di, ax ; mask to sector size
; Pop lower 2 bytes of the current cluster number to AX.
pop ax
; Right-shift the current cluster number in EAX by the shift count to get the
; sector number of the FAT sector holding the next cluster number, relative to
; the FAT data region's sector number.
; `7` was modified to the actual shift count
; log(2, NumberOfClusterNumbersPerFATSector).
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 the FAT data region's sector number to EAX.
; Now EAX stores the sector number of the FAT sector holding the next cluster
; number.
add eax, [fat_start] ; absolute sector number now
; Put the FAT buffer's segment to BX.
mov bx, FATSEG
; Put the FAT buffer's segment to ES.
mov es, bx
; Put 0 to BX.
; Now ES:BX points to the FAT buffer.
sub bx, bx
; Compare the sector number in EAX with the sector number of the sector last
; read.
cmp eax, [fat_sector] ; already buffered?
; If equal, jump to `cn_buffered`.
jz cn_buffered
; Put the sector number of the FAT sector holding the next cluster number in
; EAX to [fat_sector].
mov [fat_sector],eax ; number of buffered sector
; Read the FAT sector holding the next cluster number.
call readDisk
; Mask off the highest 4 bits of the next cluster number, according to FAT
; specification.
cn_buffered: and byte [es:di+3],0x0f ; mask out top 4 bits
; Put the next cluster number to EAX.
mov eax, [es:di] ; read next cluster number
; Pop old BX.
pop bx
; Pop old DI.
pop di
; Pop old ES.
pop es
; Return.
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:
; Compare the cluster number in EAX with 0x0FFFFFF8.
; 0x0FFFFFF8 means end-of-chain.
cmp eax, 0x0ffffff8 ; if end of cluster chain...
; If >= 0x0FFFFFF8, jump to `end_of_chain`.
jnb end_of_chain
; Subtract EAX by 2 to get the 0-based cluster number.
; sector = (cluster-2) * clustersize + data_start
dec eax
dec eax
; Put the number of sectors per cluster to EDX.
movzx edx, byte [bsSecPerClust]
; Push the number of sectors per cluster in EDX.
push edx
; Multiply the 0-based cluster number in EAX by the number of sectors per
; cluster in EDX to get the sector number, relative to the FAT data region's
; sector number.
; EDX:EAX = EAX * EDX.
mul edx
; Pop the number of sectors per cluster to EDX.
pop edx
; Add the FAT data region's sector number to the sector number in EAX to get
; the absolute sector number.
add eax, [data_start]
; here, carry is unset (unless parameters are wrong)
; Return.
ret
; Put 1 to CF to indicate end-of-chain.
end_of_chain: stc ; indicate EOC by carry
; Return.
ret
;-----------------------------------------------------------------------
; PRINT - prints string DS:SI
; modifies AX BX SI
; Put 0 to BX.
; BH is page number.
; BL is color.
printchar: xor bx, bx ; video page 0
; Put 0x0E to AH.
; 0x0E means TTY mode for interrupt 0x10 video service.
mov ah, 0x0e ; print it
; Invoke interrupt 0x10 video service to print the character in AL.
int 0x10 ; via TTY mode
; Put the character in [DS:SI] to AL.
; SI is incremented.
; Now SI points to one byte after the character.
print: lodsb ; get token
; Test whether the character in AL is value 0.
cmp al, 0 ; end of string?
; If the character in AL is not value 0, jump to `printchar`.
jne printchar ; until done
; If the character in AL is value 0, return.
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
; Push DX.
readDisk: push dx
; Push SI.
push si
; Push DI.
push di
; Push the sector number in EAX.
read_next: push eax ; would ax be enough?
; Put SP to DI.
mov di, sp ; remember parameter block end
;--- db 0x66 ; operand size override (push dword)
; Put 0 to the higher 8 bits of the 48-bit address.
push byte 0 ;XXX ; other half of the 32 bits at [C]
; (did not trust "o32 push byte 0" opcode)
; Put 0 to the second higher 8 bits of the 48-bit address.
push byte 0 ; [C] sector number high 32bit
; Put the sector number in EAX to the lower 32 bits of the 48-bit address.
push eax ; [8] sector number low 32bit
; Specify the read buffer's segment.
push es ; [6] buffer segment
; Specify the read buffer's offset.
push bx ; [4] buffer offset
; Specify the number of sectors to read.
push byte 1 ; [2] 1 sector (word)
; Put the size of the LBA packet.
push byte 16 ; [0] size of parameter block (word)
; Point SI to the LBA packet in stack.
mov si, sp
; Put the drive number to DL, as required by interrupt 0x13 AH=0x42 service.
mov dl, [drive]
; Specify to use interrupt 0x13 AH=0x42 service.
mov ah, 42h ; disk read
; Invoke interrupt 0x13 AH=0x42 service to read disk using LBA addressing.
; If have error, CF becomes 1.
int 0x13
; Put DI to SP.
mov sp, di ; remove parameter block from stack
; (without changing flags!)
; Pop the sector number to EAX.
pop eax ; would ax be enough?
; If not have error, jump to `read_ok`.
jnc read_ok ; jump if no error
; Push AX.
push ax ; !!
; Put 0 to AH.
xor ah, ah ; else, reset and retry
; Invoke interrupt 0x13 AH=0 service to reset the disk.
int 0x13
; Pop old AX.
pop ax ; !!
; Jump to `read_next`.
jmp read_next
; Increment the sector number in EAX.
read_ok: inc eax ; next sector
; Increment BX by the number of bytes per sector.
; Now ES:BX points to the unfilled part of the read buffer.
; If overflow, CF becomes 1.
add bx, word [bsBytesPerSec]
; If overflow, jump to `no_incr_es`.
jnc no_incr_es ; if overflow...
; If not overflow.
; Put ES to DX.
mov dx, es
; Add 0x1000 to DX, which means pointing ES to the next 0x10000 bytes segment.
; Notice BX overflows around and BX can address 0x10000 bytes (64KB).
add dh, 0x10 ; ...add 1000h to ES
; Put DX to ES.
mov es, dx
; Pop old DI.
no_incr_es: pop di
; Pop old SI.
pop si
; Pop old DX.
pop dx
; Return.
ret
;-----------------------------------------------------------------------
; Message.
msg_LoadFreeDOS db "Loading FreeDOS ",0
; Allocate zeros up to $$:0x01EE exclusive.
; 0x01EE + 18 = 512.
; The remaining 18 bytes below make the boot sector exactly 512 bytes.
times 0x01ee-$+$$ db 0
; Error message.
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.
; Kernel file name.
filename db "KERNEL SYS"
; Two bytes of 0.
; Mark the boot sector as bootable.
sign dw 0, 0xAA55
; Win9x uses all 4 bytes as magic value here.
;
; File:
; oemboot.asm
; 2004, Kenneth J. Davis
; Copyright (c) 200?, <add name here>
; Description:
; OEM boot sector for FreeDOS compatible with IBM's (R) PC-DOS,
; and Microsoft's (R) MS-DOS. It may work with older OpenDOS/DR-DOS,
; although the standard FreeDOS boot sector is needed with ver 7+
; releases. May work with other versions of DOS that use
; IBMBIO.COM/IBMDOS.COM pair. This boot sector loads only up
; to 58 sectors (29KB) of the kernel (IBMBIO.COM) to 0x70:0 then
; jumps to it. As best I can tell, PC-DOS (and MS-DOS up to version
; 6.xx behaves similar) expects on entry for:
; ch = media id byte in the boot sector
; dl = BIOS drive booted from (0x00=A:, 0x80=C:, ...)
; ax:bx = the starting (LBA) sector of cluster 2 (ie the 1st
; data sector, which is 0x0000:0021 for FAT12)
; ?note? IBMBIO.COM/IO.SYS may use ax:bx and cluster # stored
; elsewhere (perhaps dir entry still at 0x50:0) to determine
; starting sector for full loading of kernel file.
; it also expects the boot sector (in particular the BPB)
; to still be at 0x0:7C00, the directory entry for IBMBIO.COM
; (generally first entry of first sector of the root directory)
; at 0x50:0 (DOS Data Area). The original boot sector may update
; the floppy disk parameter table (int 1Eh), but we don't so
; may fail for any systems where the changes (???) are needed.
; If the above conditions are not met, then IBMBIO.COM will
; print the not a bootable disk error message.
;
; For MS-DOS >= 7 (ie Win9x DOS) the following conditions
; must be met:
; bp = 0x7C00, ie offset boot sector loaded at
; [bp-4] = the starting (LBA) sector of cluster 2 (ie the 1st
; data sector [this is the same as ax:bx for earlier versions
; and dx:ax in Win9x boot sector]
; The starting cluster of the kernel file is stored in
; di for FAT 12/16 (where si is a don't care) and si:di
; for FAT 32.
; The values for ax,bx,cx,dx,ds and the stack do not
; seem to be important (used by IO.SYS) and so may be any value
; (though dx:ax=[data_start], cx=0, bx=0x0f00 on FAT12 or
; 0x0700 on FAT32, ds=0, ss:sp=0:7b??)
; the boot time stack may store the original int1E floppy
; parameter table, otherwise nothing else important seems
; stored there and I am unsure if even this value is used
; beyond boot sector code.
;
; This boot sector only supports FAT12/FAT16 as PC-DOS
; does not support FAT32 and newer FAT32 capable DOSes
; probably have different boot requirements; also do NOT
; use it to boot the FreeDOS kernel as it expects to be
; fully loaded by boot sector (> 29KB & usually to 0x60:0).
;
; WARNING: PC-DOS has additional requirements, in particular,
; it may expect that IBMBIO.COM and IBMDOS.COM be the 1st
; two entries in the root directory (even before the label)
; and that they occupy the 1st consecutive data sectors.
; Newer releases may support other positions, but still
; generally should occupy consecutive sectors. These conditions
; can usually be met by running sys on a freshly formatted
; and un-label'd disk.
;
;
; Derived From:
; boot.asm
; DOS-C boot
;
; Copyright (c) 1997, 2000-2004
; Svante Frey, Jim Hall, Jim Tabor, Bart Oldeman,
; Tom Ehlert, Eric Auer, Luchezar Georgiev, Jon Gentle
; and Michal H. Tyc (DR-DOS adaptation, boot26dr.asm)
; All Rights Reserved
;
; This file is part of FreeDOS.
;
; DOS-C is free software; you can redistribute it and/or
; modify it under the terms of the GNU General Public License
; as published by the Free Software Foundation; either version
; 2, or (at your option) any later version.
;
; DOS-C is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
; the GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public
; License along with DOS-C; see the file COPYING. If not,
; write to the Free Software Foundation, 675 Mass Ave,
; Cambridge, MA 02139, USA.
;
;
; +--------+
; | CLUSTER|
; | LIST |
; |--------| 0000:7F00
; |LBA PKT |
; |--------| 0000:7E00 (0:BP+200)
; |BOOT SEC| contains BPB
; |ORIGIN |
; |--------| 0000:7C00 (0:BP)
; |VARS | only known is 1st data sector (start of cluster 2)
; |--------| 0000:7BFC (DS:[BP-4])
; |STACK | minimal 256 bytes (1/2 sector)
; |- - - - |
; |KERNEL | kernel loaded here (max 58 sectors, 29KB)
; |LOADED | also used as FAT buffer
; |--------| 0070:0000 (0:0700)
; |DOS DA/ | DOS Data Area,
; |ROOT DIR| during boot contains directory entries
; |--------| 0000:0500
; |BDA | BIOS Data Area
; +--------+ 0000:0400
; |IVT | Interrupt Vector Table
; +--------+ 0000:0000
CPU 8086 ; enable assembler warnings to limit instruction set
;%define ISFAT12 1 ; only 1 of these should be set,
;%define ISFAT16 1 ; defines which FAT is supported
%define TRYLBAREAD 1 ; undefine to use only CHS int 13h
%define SETROOTDIR 1 ; if defined dir entry copied to 0:500
%define LOOPONERR 1 ; if defined on error simply loop forever
;%define RETRYALWAYS 1 ; if defined retries read forever
;%define WINBOOT 1 ; use win9x kernel calling conventions (name & jmp addr)
;%define MSCOMPAT 1 ; sets default filename to MSDOS IO.SYS
%ifdef WINBOOT ; if set also change from PC-DOS to
%ifndef MSCOMPAT ; kernel name to MS-DOS kernel name
%define MSCOMPAT
%endif
%endif
; Set segment type.
segment .text
; The offset base.
%define BASE 0x7c00 ; boot sector originally at 0x0:BASE
; The user read buffer's segment.
%define LOADSEG 0x0070 ; segment to load kernel at LOADSEG:0
; The user read buffer's end segment.
%define LOADEND 0x07b0 ; limit reads to below this segment
; LOADSEG+29KB, else data overwritten
; The FAT buffer's offset.
%define FATBUF bp-0x7500 ; offset of temporary buffer for FAT
; chain 0:FATBUF = 0:0700 = LOADSEG:0
; The root directory buffer's offset.
%define ROOTDIR bp-0x7700 ; offset to buffer for root directory
; entry of kernel 0:ROOTDIR
; The cluster list buffer's offset.
%define CLUSTLIST bp+0x0300 ; zero terminated list of clusters
; that the kernel occupies
; Some extra variables
; using bp-Entry+variable_name generates smaller code than using just
; variable_name, where bp is initialized to Entry, so bp-Entry equals 0
; The offset of the LBA packet used by interrupt 0x13.
%define LBA_PACKET bp+0x0200 ; immediately after boot sector
; The offset of the field that stores the LBA packet's size.
%define LBA_SIZE word [LBA_PACKET] ; size of packet, should be 10h
; The offset of the field that stores the number of sectors to read.
%define LBA_SECNUM word [LBA_PACKET+2] ; number of sectors to read
; The offset of the field that stores the read buffer's offset.
%define LBA_OFF LBA_PACKET+4 ; buffer to read/write to
; The offset of the field that stores the read buffer's segment.
%define LBA_SEG LBA_PACKET+6
; The offset of the field that stores the 48-bit LBA sector number.
%define LBA_SECTOR_0 word [LBA_PACKET+8 ] ; LBA starting sector #
%define LBA_SECTOR_16 word [LBA_PACKET+10]
%define LBA_SECTOR_32 word [LBA_PACKET+12]
%define LBA_SECTOR_48 word [LBA_PACKET+14]
; The offset of the space after the LBA packet.
%define PARAMS LBA_PACKET+0x10
;%define RootDirSecs PARAMS+0x0 ; # of sectors root dir uses
; The offset of the variable storing the first FAT's sector number.
%define fat_start PARAMS+0x2 ; first FAT sector
;%define root_dir_start PARAMS+0x6 ; first root directory sector
; The offset of the variable storing the kernel file's first cluster number.
%define first_cluster PARAMS+0x0a ; starting cluster of kernel file
; The offset of the variable storing the FAT data region's sector number.
%define data_start bp-4 ; first data sector (win9x expects here)
;-----------------------------------------------------------------------
; Let offset base be 0x7C00.
org BASE
; The boot sector's entry point.
; Jump to `real_start`.
Entry: jmp short real_start
; NOP instruction for the third byte, as required by FAT specification.
nop
; The `jmp` and `nop` above take 3 bytes, followed by FAT info variables.
; These variables are initialized when running `SYS.COM` to install the boot
; sector.
;
; The offsets of the FAT info variables:
; bp is initialized to 7c00h
%define bsOemName bp+0x03 ; OEM label
%define bsBytesPerSec bp+0x0b ; bytes/sector
%define bsSecPerClust bp+0x0d ; sectors/allocation unit
%define bsResSectors bp+0x0e ; # reserved sectors
%define bsFATs bp+0x10 ; # of fats
%define bsRootDirEnts bp+0x11 ; # of root dir entries
%define bsSectors bp+0x13 ; # sectors total in image
%define bsMedia bp+0x15 ; media descrip: fd=2side9sec, etc...
%define sectPerFat bp+0x16 ; # sectors in a fat
%define sectPerTrack bp+0x18 ; # sectors/track
%define nHeads bp+0x1a ; # heads
%define nHidden bp+0x1c ; # hidden sectors
%define nSectorHuge bp+0x20 ; # sectors if > 65536
%define drive bp+0x24 ; drive number
%define extBoot bp+0x26 ; extended boot signature
%define volid bp+0x27
%define vollabel bp+0x2b
%define filesys bp+0x36
;-----------------------------------------------------------------------
; times 0x3E-$+$$ db 0
;
; Instead of zero-fill,
; initialize BPB with values suitable for a 1440 K floppy
;
db 'IBM 5.0' ; OEM label
dw 512 ; bytes per sector
db 1 ; sectors per cluster
dw 1 ; reserved sectors
db 2 ; number of FATs
dw 224 ; root directory entries
dw 80 * 36 ; total sectors on disk
db 0xF0 ; media descriptor
dw 9 ; sectors per 1 FAT copy
dw 18 ; sectors per track
dw 2 ; number of heads
dd 0 ; hidden sectors
dd 0 ; big total sectors
db 0 ; boot unit
db 0 ; reserved
db 0x29 ; extended boot record id
dd 0x12345678 ; volume serial number
db 'NO NAME '; volume label
db 'FAT12 ' ; filesystem id
;-----------------------------------------------------------------------
; ENTRY
;-----------------------------------------------------------------------
real_start:
; Put 0 to IF to disable maskable interrupts.
cli ; disable interrupts until stack ready
; Put 0 to DF. When DF is 0, string operations increment SI and DI.
cld ; all string operations increment
; Put 0 to AX.
xor ax, ax ; ensure our segment registers ready
; Put 0 to DS.
mov ds, ax ; cs=ds=es=ss=0x0000
; Put 0 to ES.
mov es, ax
; Put 0 to SS.
mov ss, ax
; Put 0x7C00 to BP.
mov bp, BASE
; Put 0x7BFC (4B before 0x0000:0x7C00) to SP.
lea sp, [bp-4] ; for DOS <7 this may be [bp]
; For compatibility, diskette parameter vector updated.
; lea di [bp+0x3E] ; use 7c3e([bp+3e]) for PC-DOS,
; ;lea di [bp] ; but 7c00([bp]) for DR-DOS 7 bug
; mov bx, 4 * 1eh ; stored at int 1E's vector
; lds si, [bx] ; fetch current int 1eh pointer
; push ds ; store original 1eh pointer at stack top
; push si ; so can restore later if needed
;
; Copy table to new location
; mov cl, 11 ; the parameter table is 11 bytes
; rep movsb ; and copy the parameter block
; mov ds, ax ; restore DS
;
; Note: make desired changes to table here
;
; Update int1E to new location
; mov [bx+2], 0 ; set to 0:bp or 0:bp+3e as appropriate
; mov word [bx], 0x7c3e ; (use 0x7c00 for DR-DOS)
; Put 1 to IF to enable maskable interrupts.
sti ; enable interrupts
; If updated floppy parameter table then must notify BIOS
; Otherwise a reset should not be needed here.
; int 0x13 ; reset drive (AX=0)
;
; Note: some BIOS implementations may not correctly pass drive number
; in DL, however we work around this in SYS.COM by NOP'ing out the use of DL
; (formerly we checked for [drive]==0xff; update sys.c if code moves)
;
; Put the drive number in DL to [drive].
; DL was set by BIOS before loading the boot sector.
; DL = 00h: 1st floppy disk.
; DL = 01h: 2nd floppy disk.
; DL = 80h: 1st hard disk.
; DL = 81h: 2nd hard disk.
; DL = e0h: 1st CD.
mov [drive], dl ; rely on BIOS drive number in DL
; GETDRIVEPARMS: Calculate start of some disk areas.
;
; Put lower 2 bytes of the number of hidden sectors to SI.
mov si, word [nHidden]
; Put higher 2 bytes of the number of hidden sectors to DI.
; Now DI:SI stores the number of hidden sectors.
mov di, word [nHidden+2]
; Add the number of reserved sectors to SI.
; If overflow, CF becomes 1.
add si, word [bsResSectors]
; Add CF to DI.
; Now DI:SI stores the number of hidden and reserved sectors, which is also the
; first FAT's sector number.
adc di, byte 0 ; DI:SI = first FAT sector
; Put lower 2 bytes of the first FAT's sector number to `[fat_start]`.
mov word [fat_start], si
; Put higher 2 bytes of the first FAT's sector number to `[fat_start+2]`.
mov word [fat_start+2], di
; Put the number of FATs to AL.
mov al, [bsFATs]
; Extend AL to AX.
; Now AX stores the number of FATs.
cbw
; Multiply the number of FATs by the number of sectors per FAT.
; DX:AX = AX * [sectPerFat].
; Now DX:AX stores the number of FAT sectors.
mul word [sectPerFat] ; DX:AX = total number of FAT sectors
; Add the number of FAT sectors to the number of hidden and reserved sectors.
; Now DI:SI stores the FAT root directory's sector number.
add si, ax
adc di, dx ; DI:SI = first root directory sector
; Push lower 2 bytes of the FAT root directory's sector number in DI.
push di ; mov word [root_dir_start+2], di
; Push higher 2 bytes of the FAT root directory's sector number in SI.
push si ; mov word [root_dir_start], si
; Calculate how many sectors the root directory occupies.
; Put the number of bytes per sector to BX.
mov bx, [bsBytesPerSec]
; Put 5 to CL, to be used as shfit count below.
mov cl, 5 ; divide BX by 32
; Divide the number of bytes per sector by the number of bytes per directory
; entry.
; Now BX stores the number of directory entries per sector.
shr bx, cl ; BX = directory entries per sector
; Put the number of directory entries of the FAT root directory to AX.
mov ax, [bsRootDirEnts]
; Put 0 to DX.
xor dx, dx
; Divide the number of directory entries of the FAT root directory by the
; number of directory entries per sector.
; DX(remainder):AX(quotient) = DX:AX / BX.
; Now AX stores the number of sectors of the FAT root directory.
div bx ; set AX = sectors per root directory
; Push the number of sectors of the FAT root directory in AX.
push ax ; mov word [RootDirSecs], ax
; Add the number of sectors of the FAT root directory to the FAT root
; directory's sector number.
; If overflow, CF becomes 1.
add si, ax
; Add CF to DI.
; Now DI:SI stores the FAT data region's sector number.
adc di, byte 0 ; DI:SI = first data sector
; Put lower 2 bytes of the FAT data region's sector number to `[data_start]`.
mov [data_start], si
; Put higher 2 bytes of the FAT data region's sector number to
; `[data_start+2]`.
mov [data_start+2], di
; FINDFILE: Searches for the file in the root directory.
;
; Returns:
; AX = first cluster of file
; First, read the root directory into buffer.
; into the temporary buffer. (max 29KB or overruns stuff)
; Pop the number of sectors of the FAT root directory to DI.
; DI specifies the number of sectors to read for `readDisk` below.
pop di ; mov di, word [RootDirSecs]
; Pop higher 2 bytes of the FAT root directory's sector number to AX.
pop ax ; mov ax, word [root_dir_start]
; Pop lower 2 bytes of the FAT root directory's sector number to DX.
; DX:AX specifies the starting sector number for `readDisk` below.
pop dx ; mov dx, word [root_dir_start+2]
; Put the root directory buffer's offset to BX.
; ES:BX specifies the destination buffer for `readDisk` below.
lea bx, [ROOTDIR] ; es:bx = 0:0500
; Push ES.
push es ; save pointer to ROOTDIR
; Read the sectors of the FAT root directory to the root directory buffer.
call readDisk
; Pop old ES.
pop es ; restore pointer to ROOTDIR
; Put the root directory buffer's offset to SI.
lea si, [ROOTDIR] ; ds:si = 0:0500
; Search for kernel file name, and find start cluster.
; Put 11 to CX, to be used as repeat times below.
; 11 means 8+3 style file name.
next_entry: mov cx, 11
; Point DI to the kernel file name.
mov di, filename
; Push the current directory entry's offset to SI.
push si
; Compare byte [DS:SI] with byte [ES:DI], repeat at most 11 times.
; Each time SI and DI are incremented by 1.
; If all 11 characters are equal, ZF becomes 1.
repe cmpsb
; Pop the current directory entry's offset to SI.
pop si
; Put the directory entry's first cluster number to AX.
; A directory entry's byte 0x1A stores its first cluster number.
mov ax, [si+0x1A]; get cluster number from directory entry
; If the kernel file name is found, jump to `ffDone`.
je ffDone
; Increment SI by 32 to point to the next directory entry.
; If overflow, CF becomes 1.
add si, byte 0x20 ; go to next directory entry
; If overflow, jump to `boot_error`.
jc boot_error ; fail if not found and si wraps
; Test whether the first byte of the file name is 0, which means end-of-entry.
cmp byte [si], 0 ; if the first byte of the name is 0,
; If it is not end-of-entry, jump to `next_entry`.
jnz next_entry ; there are no more files in the directory
ffDone:
; Put the kernel file's first cluster number in AX to [first_cluster].
mov [first_cluster], ax ; store first cluster number
%ifdef SETROOTDIR
; copy over this portion of root dir to 0x0:500 for PC-DOS
; (this may allow IBMBIO.COM to start in any directory entry)
; Put the root directory buffer's offset to DI.
lea di, [ROOTDIR] ; es:di = 0:0500
; Put 32 to CX, to be used as repeat times below.
mov cx, 32 ; limit to this 1 entry (rest don't matter)
; Copy 2 bytes from [DS:SI] to [ES:DI], repeat 32 times.
; Each time SI and DI are incremented by 2, CX is decremented by 1.
rep movsw
%endif
; GETFATCHAIN:
;
; Reads the FAT chain and stores it in a temporary buffer in the first
; 64 kb. The FAT chain is stored an array of 16-bit cluster numbers,
; ending with 0.
;
; The file must fit in conventional memory, so it can't be larger than
; 640 kb. The sector size must be at least 512 bytes, so the FAT chain
; can't be larger than 2.5 KB (655360 / 512 * 2 = 2560).
;
; Call with: AX = first cluster in chain
; Load the complete FAT into memory. The FAT can't be larger
; than 128 kb
; Put the FAT buffer's offset to BX.
lea bx, [FATBUF] ; es:bx = 0:0700
; Put the number of sectors per FAT to DI.
; DI specifies the number of sectors to read for `readDisk` below.
mov di, [sectPerFat]
; Put lower 2 bytes of the first FAT's sector number to AX.
mov ax, word [fat_start]
; Put higer 2 bytes of the first FAT's sector number to DX.
; DX:AX specifies the starting sector number for `readDisk` below.
mov dx, word [fat_start+2]
; Read the first FAT's sectors to the FAT buffer.
call readDisk
; Set ES:DI to the temporary storage for the FAT chain.
; Put DS to ES.
push ds
pop es
; Put the cluster list buffer's offset to DI.
lea di, [CLUSTLIST]
; Set DS:0 to FAT data we loaded
; Put the user read buffer's segment to AX.
mov ax, LOADSEG
; Put the user read buffer's segment to DS.
mov ds, ax ; ds:0 = 0x70:0 = 0:FATBUF
; Put the kernel file's first cluster number to AX.
mov ax, [first_cluster] ; restore first cluster number
; Push the user read buffer's segment in DS.
push ds ; store LOADSEG
; Put the current cluster number in AX to [ES:DI] in the cluster list buffer.
next_clust: stosw ; store cluster number
; Put the current cluster number in AX to SI.
mov si, ax ; SI = cluster number
%ifdef ISFAT12
; This is a FAT-12 disk.
; Each 12-bit cluster number is stored across 2 bytes.
; The bits layout of 2 consective cluster numbers (12 bits * 2 = 24 bits) in 3
; bytes is:
; 0 76543210
; 1 3210BA98
; 2 BA987654
; Multiply the current cluster number by 3.
fat_12: add si, si ; multiply cluster number by 3...
add si, ax
; Divide the current cluster number by 2 to get the offset of the 2 bytes
; storing the next cluster number.
; If the current cluster number is odd, CF becomes 1.
shr si, 1 ; ...and divide by 2
; Put the 2 bytes storing the next cluster number in [DS:SI] to AX.
lodsw
; If the cluster number was even, the cluster value is now in
; bits 0-11 of AX. If the cluster number was odd, the cluster
; value is in bits 4-15, and must be shifted right 4 bits. If
; the number was odd, CF was set in the last shift instruction.
; If CF is 0 which means the current cluster number is even, jump to
; `fat_even`.
jnc fat_even
; If CF is 1 which means the current cluster number is odd.
; Put 4 in CL, to be used as shift count below.
mov cl, 4
; Shift off the lower 4 bits.
shr ax, cl
; Mask off the higher 4 bits.
; Now AX stores the new current cluster number.
fat_even: and ah, 0x0f ; mask off the highest 4 bits
; Test whether the new current cluster number is EOF.
cmp ax, 0x0ff8 ; check for EOF
; If the new current cluster number is not EOF, jump to `next_clust`.
jb next_clust ; continue if not EOF
%endif
%ifdef ISFAT16
; This is a FAT-16 disk. The maximal size of a 16-bit FAT
; is 128 kb, so it may not fit within a single 64 kb segment.
; Put the user read buffer's segment to DX.
fat_16: mov dx, LOADSEG
; Multiply the current cluster number by 2 to get the offset of the 2 bytes
; storing the next cluster number.
; If overflow, CF becomes 1.
add si, si ; multiply cluster number by two
; If not overflow, jump to `first_half`.
jnc first_half ; if overflow...
; If overflow, add 0x1000 to DX, which points DX to the next 0x10000 bytes
; segment. Notice SI overflows around and SI can address 0x10000 bytes (64KB).
add dh, 0x10 ; ...add 64 kb to segment value
; Put the user read buffer's segment to DS.
; Now DS:SI points to the next cluster number.
first_half: mov ds, dx ; DS:SI = pointer to next cluster
; Put the next cluster number in [DS:SI] to AX.
; Now AX stores the new current cluster number.
lodsw ; AX = next cluster
; Test whether the new current cluster number is EOF.
cmp ax, 0xfff8 ; >= FFF8 = 16-bit EOF
; If the new current cluster number is not EOF, jump to `next_clust`.
jb next_clust ; continue if not EOF
%endif
finished: ; Mark end of FAT chain with 0, so we have a single
; EOF marker for both FAT-12 and FAT-16 systems.
; Put 0 to AX.
xor ax, ax
; Put 0 in AX to [ES:DI] in the cluster list buffer to mark the end.
stosw
; Put the cluster list buffer's segment in CS to DS.
push cs
pop ds
; loadFile: Loads the file into memory, one cluster at a time.
; Pop old ES.
pop es ; set ES:BX to load address 70:0
; Put 0 to BX.
xor bx, bx
; Put the cluster list buffer's offset to SI.
lea si, [CLUSTLIST] ; set DS:SI to the FAT chain
; Put the cluster number [DS:SI] in the cluster list buffer to AX.
cluster_next: lodsw ; AX = next cluster to read
; Test whether the cluster number is 0 which means EOF.
or ax, ax ; EOF?
; If the cluster number is not EOF, jump to `load_next`.
jne load_next ; no, continue
; dl set to drive by readDisk
; Put [bsMedia] to CH.
mov ch, [bsMedia] ; ch set to media id
; Put higher 2 bytes of the FAT data region's sector number to AX.
mov ax, [data_start+2] ; ax:bx set to 1st data sector
; Put lower 2 bytes of the FAT data region's sector number to BX.
mov bx, [data_start] ;
; Put the kernel file's first cluster number to DI.
mov di, [first_cluster] ; set di (si:di on FAT32) to starting cluster #
%ifdef WINBOOT
; Jump to run the loaded kernel.
jmp LOADSEG:0x0200 ; yes, pass control to kernel
%else
; Jump to run the loaded kernel.
jmp LOADSEG:0000 ; yes, pass control to kernel
%endif
; failed to boot
boot_error:
; Print the following error message.
call show
; db "Error! Hit a key to reboot."
; Error message.
db "):."
%ifdef LOOPONERR
; Jump to self.
jmp $
%else
; Note: should restore floppy paramater table address at int 0x1E
; Put 0 to AH.
xor ah,ah
; Invoke interrupt 0x13 AH=0 service to reset the disk.
int 0x13 ; reset floppy
; Invoke interrupt 0x16 AH=0 service to wait for a key press.
int 0x16 ; wait for a key
; Invoke interrupt 0x19 AH=0 service to reboot.
int 0x19 ; reboot the machine
%endif
; Decrease the cluster number by 2 to get the 0-based sector number.
load_next: dec ax ; cluster numbers start with 2
dec ax
; Put the number of sectors per cluster to DI.
; The higher byte in DI is unused.
mov di, word [bsSecPerClust]
; Mask off the higher byte in DI.
; DI specifies the number of sectors to read for `readDisk` below.
and di, 0xff ; DI = sectors per cluster
; Multiply the cluster number in AX by the number of sectors per cluster in DI
; to get the sector number, relative to the FAT data region's sector number.
; DX:AX = AX * DI.
; Now DX:AX stores the relative sector number.
mul di
; Add lower 2 bytes of the FAT data region's sector number to AX.
; If overflow, CF becomes 1.
add ax, [data_start]
; Add higher 2 bytes of the FAT data region's sector number and CF to AX.
; Now DX:AX stores the absolute sector number.
; DX:AX is specifies the starting sector number for `readDisk` below.
adc dx, [data_start+2] ; DX:AX = first sector to read
; Read one cluster of the kernel file to the user read buffer.
call readDisk
; Jump to `cluster_next`.
jmp short cluster_next
; shows text after the call to this function.
; Pop the offset of next character to SI.
show: pop si
; Put the character in [DS:SI] to AL.
; SI is incremented.
; Now SI points to one byte after the character.
; If the character is `.`, it will be the last character to print, then SI
; points to the next instruction to return to.
lodsb ; get character
; Push SI as the potential return address.
push si ; stack up potential return address
; Put 0x0E to AH.
; 0x0E means TTY mode for interrupt 0x10 video service.
mov ah,0x0E ; show character
; Invoke interrupt 0x10 video service to print the character in AL.
int 0x10 ; via "TTY" mode
; Test whether the character is `.`.
cmp al,'.' ; end of string?
; If the character is not `.`, jump to print the next character.
jne show ; until done
; If the character is `.`, return.
ret
; readDisk: Reads a number of sectors into memory.
;
; Call with: DX:AX = 32-bit DOS sector number
; DI = number of sectors to read
; ES:BX = destination buffer
;
; Returns: CF set on error
; ES:BX points one byte after the last byte read.
; Exits early if LBA_SEG == LOADEND.
; Push SI.
readDisk: push si ; preserve cluster #
; Put the sector number to read to fields `LBA_SECTOR_0` and `LBA_SECTOR_16`.
mov LBA_SECTOR_0,ax
mov LBA_SECTOR_16,dx
; Put the destination buffer's segment to [LBA_SEG].
mov word [LBA_SEG], es
; Put the destination buffer's offset to [LBA_OFF].
mov word [LBA_OFF], bx
; Print the following message.
call show
; Message.
db "."
read_next:
; initialize constants
; Put 16 to be the LBA packet's size.
mov LBA_SIZE, 10h ; LBA packet is 16 bytes
; Put 1 to be the number of sectors to read.
mov LBA_SECNUM,1 ; reset LBA count if error
; limit kernel loading to 29KB, preventing stack & boot sector being overwritten
; Compare the read buffer's segment to the user read buffer's end segment.
cmp word [LBA_SEG], LOADEND ; skip reading if past the end
; If equal, jump to `read_skip`.
je read_skip ; of kernel file buffer
;******************** LBA_READ *******************************
; check for LBA support
%ifdef TRYLBAREAD
; Specify to use interrupt 0x13 AH=0x41 service.
mov ah,041h ;
; Put 0x55AA to BX, as required by interrupt 0x13 AH=0x41 service.
mov bx,055aah ;
; Put the drive number to DL, as required by interrupt 0x13 AH=0x41 service.
mov dl, [drive] ; BIOS drive, 0=A:, 80=C:
; Test whether the drive number is 0 which means the first floppy disk.
test dl,dl ; don't use LBA addressing on A:
; If the drive number is 0, jump to `read_normal_BIOS`.
jz read_normal_BIOS ; might be a (buggy)
; CDROM-BOOT floppy emulation
; Invoke interrupt 0x13 AH=0x41 service to check extensions present.
; If extensions are not present, CF is 1. If present, CX stores the flags:
; 1 – Device access using the LBA packet.
; 2 – Drive locking and ejecting.
; 4 – Enhanced disk drive support.
int 0x13
; If extensions are not present, jump to `read_normal_BIOS`.
jc read_normal_BIOS
; Right-shift CX by 1 bit.
; Now CF stores the lower 1 bit shifted out.
shr cx,1 ; CX must have 1 bit set
; Test whether CF is 1.
; BX = 0xAA55 - 1 + CF - BX = 0xAA54 + CF - 0xAA55 = CF - 1.
sbb bx,0aa55h - 1 ; tests for carry (from shr) too!
; If CF is not 1 which means LBA addressing is not supported, jump to
; `read_normal_BIOS`.
jne read_normal_BIOS
; OK, drive seems to support LBA addressing
; Put the address of the LBA packet to SI, as required by interrupt 0x13
; AH=0x42 service.
lea si,[LBA_PACKET]
; setup LBA disk block
; Put 0 to field `LBA_SECTOR_32`.
mov LBA_SECTOR_32,bx ; bx is 0 if extended 13h mode supported
; Put 0 to field `LBA_SECTOR_48`.
mov LBA_SECTOR_48,bx
; Specify to use interrupt 0x13 AH=0x42 service.
mov ah,042h
; Jump to `do_int13_read`.
jmp short do_int13_read
%endif
read_normal_BIOS:
;******************** END OF LBA_READ ************************
; Code block below aims to convert the sector number to read into CHS numbers
; to be used by interrupt 0x13 AH=0x02 service.
;
; Put the sector number to read to DX:CX.
mov cx, LBA_SECTOR_0
mov dx, LBA_SECTOR_16
;
; translate sector number to BIOS parameters
;
;
; abs = sector offset in track
; + head * sectPerTrack offset in cylinder
; + track * sectPerTrack * nHeads offset in platter
;
; Put the number of sectors per track to AL.
mov al, [sectPerTrack]
; Multiply the number of sectors per track by the number of heads to get the
; number of sectors per cylinder.
; AX = AL * [nHeads].
mul byte [nHeads]
; Exchange AX and CX.
; Now CX stores the number of sectors per cylinder.
; Now DX:AX stores the sector number to read.
xchg ax, cx
; cx = nHeads * sectPerTrack <= 255*63
; dx:ax = abs
; Divide the sector number to read by the number of sectors per cylinder.
; DX(remainder):AX(quotient) = DX:AX / CX.
; NOW AX stores the cylinder number.
; NOW DX stores the in-cylinder sector offset.
div cx
; ax = track, dx = sector + head * sectPertrack
; Exchange AX and DX.
; NOW AX stores the in-cylinder sector offset.
; NOW DX stores the cylinder number.
xchg ax, dx
; dx = track, ax = sector + head * sectPertrack
; Divide the in-cylinder sector offset by the number of sectors per track.
; AH(remainder):AL(quotient) = AX / [sectPerTrack].
; Now AL stores the head number, AH stores the in-track sector number.
div byte [sectPerTrack]
; dx = track, al = head, ah = sector
; Put the cylinder number to CX.
mov cx, dx
; cx = track, al = head, ah = sector
; the following manipulations are necessary in order to
; properly place parameters into registers.
; ch = cylinder number low 8 bits
; cl = 7-6: cylinder high two bits
; 5-0: sector
; Put the head number to DH.
mov dh, al ; save head into dh for bios
; CX stores both the cylinder number (10 bits, possible values are 0 to 1023)
; and the sector number (6 bits, possible values are 1 to 63).
; Layout:
; CX: ---CH--- ---CL---
; Cylinder : 76543210 98
; Sector : 543210
;
; Put the lower 8 bits of the cylinder number to CH.
; Put the higher 2 bits of the cylinder number to CL's lower 2 bits.
xchg ch, cl ; set cyl no low 8 bits
; Right-rotate CL's lower 2 bits to higher 2 bits.
ror cl, 1 ; move track high bits into
ror cl, 1 ; bits 7-6 (assumes top = 0)
; Put the in-track sector number to CL's lower 6 bits.
or cl, ah ; merge sector into cylinder
; Increment the in-track sector number to make it 1-based.
inc cx ; make sector 1-based (1-63)
; Point ES:BX to `readDisk`'s read buffer.
; ES:BX specifies interrupt 0x13 AH=0x02 service's read buffer.
les bx,[LBA_OFF]
; Specify to use interrupt 0x13 AH=0x02 service.
; AH 0x02 service means read sectors from drive using CHS addressing.
; AL 0x01 means read 1 sector.
mov ax, 0x0201
do_int13_read:
; Put the drive number to DL.
mov dl, [drive]
; Invoke interrupt 0x13 AH=0x02 service to read disk using CHS addressing.
; If have error, CF becomes 1.
int 0x13
read_finished:
%ifdef RETRYALWAYS
; If not have error, jump to `read_ok`.
jnc read_ok ; jump if no error
; Put 0 to AX.
xor ah, ah ; else, reset floppy
; Invoke interrupt 0x13 AH=0 service to reset the disk.
int 0x13
read_next_chained:
; Jump to `read_next`.
jmp short read_next ; read the same sector again
%else
; If have error, jump to `boot_error`.
jc boot_error ; exit on error
%endif
read_ok:
; Put the number of bytes per sector to AX.
mov ax, word [bsBytesPerSec]
; Put 4 to CL, to be used as shift count below.
mov cl, 4 ; adjust segment pointer by increasing
; Righit-shift the number of bytes per sector by 4 to get the number of
; segments per sector.
shr ax, cl
; Add the number of segments per sector to [LBA_SEG].
add word [LBA_SEG], ax ; by paragraphs read in (per sector)
; Add 1 to the starting sector number in [LBA_SECTOR_0].
; If overflow, CF becomes 1.
add LBA_SECTOR_0, byte 1
; Add CF to [LBA_SECTOR_16].
adc LBA_SECTOR_16, byte 0 ; DX:AX = next sector to read
; Decrement the number of sectors to read.
dec di ; if there is anything left to read,
%ifdef RETRYALWAYS
; If the number of sectors to read is not 0, jump to `read_next_chained`.
jnz read_next_chained ; continue
%else
; If the number of sectors to read is not 0, jump to `read_next`.
jnz read_next ; continue
%endif
read_skip:
; Put the read buffer's segment to ES.
mov es, word [LBA_SEG] ; load adjusted segment value
; clear carry: unnecessary since adc clears it
; Pop old SI.
pop si
; Return.
ret
; Allocate zeros up to $$:0x01F1 exclusive.
; 0x01F1 + 15 = 512.
; The remaining 15 bytes below make the boot sector exactly 512 bytes.
times 0x01f1-$+$$ db 0
%ifdef MSCOMPAT
; Kernel file name.
filename db "IO SYS"
%else
; Kernel file name.
filename db "IBMBIO COM"
%endif
; Two bytes of 0.
db 0,0
; Mark the boot sector as bootable.
sign dw 0xAA55
Comments: