333 lines
11 KiB
NASM
333 lines
11 KiB
NASM
;;
|
|
;; Copyright (c) Microsoft Corporation. All rights reserved.
|
|
;;
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
; Concerns
|
|
; 1 - there is no error checking on the int13 calls
|
|
; 2 - we assume that the disk is FAT32, and that the parameters in the BPB are correct.
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
; Constants
|
|
|
|
BootSecOrigin EQU 07c00h ; the BIOS always puts the boot sector here
|
|
StackOffset EQU -12 ; we will put the stack a small bit below it (we hardly use the stack, so it is safe...)
|
|
EndOfClusterMarker EQU 0FFFFFF8h ; End of cluster is here or higher
|
|
|
|
; a little trickery here: Inside the Boot Sector data structure, there several bytes that we never use.
|
|
; whether the real file system uses them or not, we don't need them, and we could use the space for locals.
|
|
|
|
FirstDataSector EQU BPBReserved2
|
|
LBAStart EQU BPBReserved2+4
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
; Directives
|
|
|
|
.model tiny
|
|
.686p
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
; Begin Code segment
|
|
|
|
_TEXT SEGMENT use16 ; 16-bit code segment
|
|
.code
|
|
ORG BootSecOrigin ; this is going to anger the linker when the linker tries to make a COM executable
|
|
; however, this is necessary in order to get the assembler to compute offsets correctly
|
|
|
|
start:
|
|
|
|
; standard boot loader procedcure is to jump over the BPB and BS data structures, then nop
|
|
jmp short bootloader
|
|
nop
|
|
|
|
;;; Example BPB and BS for Singularity, assuming a 1GB disk. Installers must NEVER copy these 87 bytes - use what the HD has...
|
|
BPB:
|
|
OemName DB 'SINGULAR'
|
|
BytesPerSector DW 512 ; used by boot loader
|
|
SecsPerClust DB 8 ; used by boot loader
|
|
ReservedSecs DW 32 ; used by boot loader
|
|
NumFats DB 2 ; used by boot loader
|
|
NumDirEntries DW 0
|
|
TotalSectors DW 0
|
|
MediaByte DB 0F8H
|
|
NumFatSecs DW 0
|
|
SecPerTrack DW 03fh
|
|
NumHeads DW 64
|
|
HiddenSecs DD 03fh
|
|
BigTotalSecs DD 1ffdc1h
|
|
BigNumFatSecs DD 7feh ; used by boot loader
|
|
ExtFlags DW 0
|
|
BPBReserved1 DW 0
|
|
RootStrtClus DD 2 ; used by boot loader
|
|
FSInfoSec dw 1
|
|
BkUpBootSec dw 6
|
|
BPBReserved2 DD 3 DUP (0) ; used (by another name)
|
|
BootDrv DB 80h ; used by boot loader
|
|
BSReserved1 DB 0h
|
|
ExtBootSig DB 41
|
|
SerialNum DD 0
|
|
VolumeLabel DB 'SINGULARITY'
|
|
FileSysType DB 'FAT32 '
|
|
|
|
bootloader:
|
|
|
|
Step1: ; set stack and data segments
|
|
mov cx, cs
|
|
mov ss, cx
|
|
mov sp, BootSecOrigin + StackOffset
|
|
mov es, cx
|
|
mov ds, cx
|
|
mov bp, sp
|
|
|
|
Step2: ; Clear the Screen
|
|
mov ax, 02h
|
|
int 010h
|
|
|
|
Step3: ; Save the boot drive
|
|
mov [BootDrv], dl
|
|
|
|
Step4: ; Ensure Int13X is supported. (It's the only way we'll access the HD)
|
|
mov ax, 4100h
|
|
mov bx, 055aah ; required parameter
|
|
|
|
int 13h ; check availability of extended int 13 (dl holds BootDrv)
|
|
|
|
jc HWUnsupportedError ; CF is set on error
|
|
cmp bx,0aa55h
|
|
jne HWUnsupportedError ; BX != 0xaa55 on error
|
|
test cl,1
|
|
jz HWUnsupportedError ; bit 0 off on error
|
|
|
|
Step5: ; Find the first FAT16 partition in the MBR and store the starting LBA offset.
|
|
xor eax, eax
|
|
mov bx, 4000h
|
|
mov es, bx
|
|
mov cx, 1
|
|
call ReadDisk ; load the MBR to 4000:0000
|
|
|
|
mov cx, 4 ; check up to 4 entries in the partition table
|
|
mov di, 446 ; the offset of the table within the MBR
|
|
|
|
mov eax, 00ch ; 00ch is the signature for a Fat32 LBA-accessible partition
|
|
FindFat16Partition:
|
|
cmp al, es:[di+4]
|
|
je FoundFat16Partition
|
|
add di, 16
|
|
dec cx
|
|
jz PartitionNotFoundError
|
|
jmp FindFat16Partition
|
|
|
|
FoundFat16Partition:
|
|
mov eax, es:[di+8]
|
|
mov [LBAStart], eax ; save the starting lba
|
|
|
|
Step6: ; Compute the First Data Sector of the FAT
|
|
movzx ecx, [NumFats]
|
|
mov eax, [BigNumFatSecs]
|
|
mul ecx ; assume overflow is impossible here
|
|
mov cx, [ReservedSecs]
|
|
add eax, ecx
|
|
mov [FirstDataSector], eax
|
|
|
|
Step7: ; Search the Root Directory for SINGLDR, find its starting cluster
|
|
|
|
; calc # of directory entries in a cluster
|
|
xor ax, ax
|
|
mov al, [SecsPerClust]
|
|
mul [BytesPerSector]
|
|
shr ax, 4
|
|
push ax ; save this for later as BytesPerCluster/16
|
|
shr ax, 1 ; BytesPerCluster/32 = # FAT directory entries that fit in a cluster
|
|
push ax ; we will use this to init a counter
|
|
|
|
; set up ES for a disk transfer
|
|
mov ax, 4000h
|
|
mov es, ax
|
|
mov eax, [RootStrtClus] ; eax has the current cluster to read
|
|
|
|
LoadNextCluster: ; eax holds the cluster to read
|
|
call ReadCluster
|
|
|
|
; set a counter for how many directory entries we will check
|
|
pop bx
|
|
push bx ; sadly, this is cheaper than "mov bx, [bp-2]"
|
|
xor di, di ; prep for comparison between [ds:si] (search string) and [es:di] (sector data)
|
|
|
|
CompareDirEntries:
|
|
mov si, OFFSET Stage2File ; [ds:si] points to the string we are looking for...
|
|
mov cx, 11 ; we are looking for an 11-character file name
|
|
repe cmpsb
|
|
je FoundMatch ; jump on match
|
|
add di, cx
|
|
add di, 21 ; ready to test the next one, but first make sure we aren't at the last entry!
|
|
dec bx
|
|
jnz CompareDirEntries
|
|
|
|
FindNextCluster:
|
|
call CalcNextCluster
|
|
cmp eax, EndOfClusterMarker ; if this is the end of the cluster chain, we are totally sunk - file not found
|
|
jae FileNotFoundError
|
|
jmp LoadNextCluster
|
|
|
|
FoundMatch:
|
|
pop ax ; remove our temp var from the stack
|
|
|
|
; es:[di] points to 12th byte of the 32-byte
|
|
; directory entry for the 2nd stage boot loader file
|
|
mov ax, es:[di+9] ; read high 16 bits of the cluster
|
|
shl eax, 16
|
|
mov ax, es:[di+15] ; read low 16 bits of the cluster
|
|
|
|
Step8: ; Load the file whose first cluster is eax to 57c0:0000
|
|
|
|
mov cx, 057c0h
|
|
mov es, cx
|
|
|
|
DoRead:
|
|
call ReadCluster
|
|
push es
|
|
|
|
mov cx, 04000h ; use different segment to find next cluster
|
|
mov es, cx
|
|
call CalcNextCluster
|
|
pop cx
|
|
add cx, [bp-2]
|
|
mov es, cx
|
|
cmp eax, EndOfClusterMarker
|
|
jb DoRead
|
|
|
|
Step9: ; Transfer Control to the boot loader file, which we stored at 5000:7c00
|
|
|
|
movzx edx, [BootDrv]
|
|
push edx ; SINGLDR will need to know the boot drive #
|
|
pushd 04803h ; H3 boot signature = HD, Fat32
|
|
pushw offset infloop ; return address = "infloop", which is the infinite loop
|
|
push cs
|
|
|
|
db 0EAh ; emit a long jump to 5000:7c00
|
|
dd 50007c00h
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
; CalcNextCluster
|
|
;
|
|
; Inputs: eax = current cluster
|
|
; es = target segment for disk read (es:00)
|
|
; Operation: Read the FAT, lookup the value for the cluster in eax, and return its low 28 bits
|
|
|
|
CalcNextCluster PROC NEAR
|
|
shl eax, 2
|
|
xor edx, edx
|
|
movzx ecx, [BytesPerSector]
|
|
div ecx ; edx has remainder, eax has quotient
|
|
push edx ; save remainder for later - it is the offset
|
|
mov dx, [ReservedSecs]
|
|
add eax, edx
|
|
add eax, [LBAStart] ; eax has the sector to read
|
|
|
|
; Call ReadDisk for 1 sector to es:0000
|
|
mov cx, 1
|
|
call ReadDisk
|
|
|
|
; read cluster# at the offset
|
|
pop ebx ; pop the offset
|
|
mov eax, es:[ebx] ; get the value, put it in eax
|
|
and eax, 0FFFFFFFh ; make it a 28-bit value
|
|
ret
|
|
CalcNextCluster ENDP
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
; ReadCluster
|
|
;
|
|
; Inputs: eax = cluster number to read
|
|
; es = segment into which we will read (at es:0000)
|
|
; Operation: Read a cluster from the HD, using ReadDisk as a subroutine
|
|
|
|
ReadCluster PROC NEAR
|
|
pushad
|
|
|
|
; compute the sector that starts the cluster
|
|
sub eax, 2
|
|
movzx ecx, [SecsPerClust] ; cx = sectors to load (this always read full clusters)
|
|
mul ecx
|
|
add eax, [FirstDataSector]
|
|
add eax, [LBAStart] ; eax holds the sector to read
|
|
call ReadDisk
|
|
|
|
popad
|
|
ret
|
|
ReadCluster ENDP
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
; ReadDisk
|
|
;
|
|
; Inputs: eax = LBA sector to read
|
|
; cx = number of sectors to read (limit to 64 or less)
|
|
; es = destination segment (always read to offset 0)
|
|
; Assumptions: 1 - assumes request will not cause overflow of es (limit on # sectors)
|
|
; 2 - assumes int13 extensions available
|
|
; 3 - LBA is limited to 32-bit values. If the disk is > 2 TB, we have a problem
|
|
|
|
ReadDisk PROC NEAR
|
|
pushad
|
|
mov dl, [BootDrv] ; set the drive
|
|
|
|
pushd 00
|
|
push eax ; push 64-bit sector address (top half always null)
|
|
|
|
push es
|
|
pushw 00 ; push transfer address (offset always 0)
|
|
|
|
push cx ; # sectors
|
|
pushw 0010h ; this request packet is 16 bytes
|
|
mov ah,42h ; extended read
|
|
mov si,sp ; ds:si = address of params
|
|
int 13h ; perform the read
|
|
|
|
add sp, 10h ; clean the stack and return
|
|
popad
|
|
ret
|
|
ReadDisk ENDP
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
; Error Routines (these are jump points that never return)
|
|
|
|
InvalidPartitionTypeError:
|
|
mov al, "T"
|
|
jmp PrintError
|
|
PartitionNotFoundError:
|
|
mov al, "P"
|
|
jmp PrintError
|
|
FileNotFoundError:
|
|
mov al, "F"
|
|
jmp PrintError
|
|
HWUnsupportedError:
|
|
mov al, "H"
|
|
PrintError:
|
|
mov bx, 07h ; normal attribute
|
|
mov ah, 0eh ; default print 1 char
|
|
mov cx, 6
|
|
mov si, offset ErrorMsg
|
|
int 10h
|
|
printnext:
|
|
lodsb
|
|
or al, al
|
|
jz infloop
|
|
int 10h
|
|
jmp printnext
|
|
infloop:
|
|
jmp infloop
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
; String Constants
|
|
|
|
Stage2File DB "SINGLDR " ; spaces are important for FAT file name determination. Compare all 11 bytes!
|
|
ErrorMsg DB " Error", 0
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
; Boot Sector Signature
|
|
|
|
ORG BootSecOrigin+510
|
|
DW 0AA55h
|
|
|
|
end start
|