295 lines
9.5 KiB
NASM
295 lines
9.5 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 FAT16, and that the parameters in the BPB are correct.
|
||
|
; 3 - There is a risk that the LBA->CHS conversion will overflow
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;
|
||
|
; 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 0FFF8h ; 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 OemName
|
||
|
RootDirSectors EQU OemName+2
|
||
|
RootDirStart EQU OemName+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 64MB disk. Installers must NEVER copy these 59 bytes - use what the HD has...
|
||
|
BPB:
|
||
|
OemName DW 'IS' ; setting up the first 8 bytes like this lets us address them in other ways
|
||
|
DB 'NGULAR'
|
||
|
BytesPerSector DW 512
|
||
|
SecsPerClust DB 4
|
||
|
ReservedSecs DW 4
|
||
|
NumFats DB 2
|
||
|
RootDirEntries DW 512
|
||
|
TotalSectors DW 0
|
||
|
MediaByte DB 0F8H
|
||
|
NumFatSecs DW 00fah
|
||
|
SecPerTrack DW 03fh
|
||
|
NumHeads DW 00FFh
|
||
|
HiddenSecs DD 020h
|
||
|
BigTotalSecs DD 03e7e0h
|
||
|
BootDrv DB 00h
|
||
|
BSReserved1 DB 0h
|
||
|
ExtBootSig DB 29h
|
||
|
SerialNum DD 0
|
||
|
VolumeLabel DB 'SINGULARITY'
|
||
|
FatId DB 'FAT16 '
|
||
|
|
||
|
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: ; Save the boot drive (dl holds it on boot)
|
||
|
mov [BootDrv], dl
|
||
|
mov al, dl
|
||
|
|
||
|
Step3: ; Clear the Screen
|
||
|
mov ax, 02h
|
||
|
int 010h
|
||
|
|
||
|
Step4: ; Compute the First Data Sector of the FAT
|
||
|
movzx eax, [RootDirEntries]
|
||
|
shl eax, 5 ; (assume no overflow)
|
||
|
movzx ecx, [BytesPerSector]
|
||
|
add eax, ecx
|
||
|
dec eax
|
||
|
div ecx ; (discard remainder)
|
||
|
mov [RootDirSectors], ax ; RootDirSectors = ((RootDirEntries * 32) + (BytesPerSector - 1)) / BytesPerSector
|
||
|
|
||
|
movzx eax, [SecsPerClust]
|
||
|
mul ecx
|
||
|
shr eax, 4
|
||
|
push ax ; save the cluster size on the stack
|
||
|
|
||
|
movzx eax, [NumFats]
|
||
|
mul [NumFatSecs] ; (assume no overflow)
|
||
|
add ax, [ReservedSecs]
|
||
|
mov [RootDirStart], ax ; RootDirStart = ReservedSecs + (NumFats * NumFatSecs)
|
||
|
|
||
|
add ax, [RootDirSectors]
|
||
|
mov [FirstDataSector], ax ; FirstDataSector = RootDirStart + RootDirSectors
|
||
|
|
||
|
|
||
|
Step5: ; Search the Root Directory for 'SINGLDR ', find its starting cluster
|
||
|
|
||
|
mov dx, [RootDirSectors] ; set counter for # sectors in root dir
|
||
|
|
||
|
mov ax, 4000h
|
||
|
mov es, ax ; set up ES for a disk transfer
|
||
|
|
||
|
movzx eax, [RootDirStart] ; compute true first sector of root dir
|
||
|
|
||
|
LoadNextSector:
|
||
|
xor bx,bx
|
||
|
call ReadDiskCHS ; read sector eax to es:bx
|
||
|
|
||
|
mov bx, [BytesPerSector] ; set a counter for how many directory entries we will check (32 bytes per entry)
|
||
|
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 matching an 11-character file name
|
||
|
repe cmpsb
|
||
|
je FoundMatch
|
||
|
add di, cx
|
||
|
add di, 21 ; move to next dir entry, but first make sure we aren't at the last entry!
|
||
|
sub bx, 32
|
||
|
jnz CompareDirEntries;
|
||
|
|
||
|
inc eax ; prep to read next entry
|
||
|
dec edx
|
||
|
jz FileNotFoundError
|
||
|
jmp LoadNextSector
|
||
|
|
||
|
FoundMatch:
|
||
|
; es:[di] points to 12th byte of the 32-byte
|
||
|
; directory entry for the 2nd stage boot loader file
|
||
|
mov ax, es:[di+15] ; read the cluster at offset 26
|
||
|
|
||
|
|
||
|
Step6: ; Load the file whose first cluster is ax 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 ax, EndOfClusterMarker
|
||
|
jb DoRead
|
||
|
|
||
|
Step7: ; 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 05544h ; UD boot signature (UsbDisk)
|
||
|
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: ax = current cluster
|
||
|
; es = target segment for disk read (es:00)
|
||
|
; Operation: Read the FAT, lookup the value for the cluster in ax, and return it as ax
|
||
|
|
||
|
CalcNextCluster PROC NEAR
|
||
|
and eax, 0FFFFh ; clear top half of eax
|
||
|
shl ax, 1
|
||
|
xor dx, dx ; clear top half of dividend
|
||
|
div [BytesPerSector]
|
||
|
push dx ; save remainder for later - it is the offset
|
||
|
add ax, [ReservedSecs] ; eax has the sector to read
|
||
|
|
||
|
; Call ReadDiskCHS to load sector eax at es:bx
|
||
|
xor bx, bx
|
||
|
call ReadDiskCHS
|
||
|
|
||
|
; read cluster# at the offset
|
||
|
pop bx ; pop the offset
|
||
|
mov ax, es:[bx] ; get the value, put it in ax
|
||
|
ret
|
||
|
CalcNextCluster ENDP
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;
|
||
|
; ReadCluster
|
||
|
;
|
||
|
; Inputs: ax = 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
|
||
|
xor bx, bx ; initially write to es:0000
|
||
|
and eax, 0FFFFh ; clear top half of eax for safety.
|
||
|
sub eax, 2
|
||
|
movzx ecx, [SecsPerClust] ; cx = sectors to load (this always read full clusters)
|
||
|
mul ecx
|
||
|
add ax, [FirstDataSector] ; eax holds the sector to read
|
||
|
LoadSector:
|
||
|
call ReadDiskCHS
|
||
|
add bx, [BytesPerSector]
|
||
|
inc eax
|
||
|
dec cx
|
||
|
jnz LoadSector
|
||
|
|
||
|
popad
|
||
|
ret
|
||
|
ReadCluster ENDP
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;
|
||
|
; ReadDiskCHS read one sector as indicated by eax to es:bx
|
||
|
;
|
||
|
; Inputs: eax = LBA sector to read
|
||
|
; es:bx = destination address
|
||
|
;
|
||
|
; Assumptions: 1 - assumes LBA is not beyond CHS representation
|
||
|
|
||
|
ReadDiskCHS PROC NEAR
|
||
|
pushad
|
||
|
|
||
|
xor edx, edx ; edx:eax = absolute sector number
|
||
|
movzx ecx, [SecPerTrack] ; ecx = sectors per track
|
||
|
div ecx ; eax = track, edx = sector within track (0-62)
|
||
|
inc dl ; dl = sector within track (1-63)
|
||
|
mov cl, dl ; cl = sector within track
|
||
|
mov edx, eax
|
||
|
shr edx, 16 ; dx:ax = track
|
||
|
div [NumHeads] ; ax = cylinder (0-1023), dx = head (0-255)
|
||
|
xchg dl, dh ; dh = head
|
||
|
mov dl, [BootDrv] ; dl = int13 unit #
|
||
|
mov ch, al ; ch = bits 0-7 of cylinder
|
||
|
shl ah, 6
|
||
|
or cl, ah ; bits 6-7 of cl = bits 8-9 of cylinder
|
||
|
mov ax, 201h ; read 1 sector
|
||
|
int 13h
|
||
|
|
||
|
popad
|
||
|
ret
|
||
|
ReadDiskCHS ENDP
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;
|
||
|
; Error Routines (these are jump points that never return)
|
||
|
|
||
|
FileNotFoundError:
|
||
|
mov al, "F"
|
||
|
jmp PrintError
|
||
|
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
|