;; ---------------------------------------------------------------------------- ;; ;; 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 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 0h ; BIOS puts us at 07c0h:0000 start: ; standard boot loader proceedure 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: db 0eah ; jmp far dw OFFSET Step1 ; offset dw 007c0h ; segment Step1: ; set CS,DS,SS,ES segments to 07c0h, set stack to 07c0h:0400h mov cx, cs mov ss, cx mov sp, 0400h 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 PartitionNotFoundError ; CF is set on error cmp bx,0aa55h jne PartitionNotFoundError ; BX != 0xaa55 on error test cl,1 jz PartitionNotFoundError ; 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 db 09Ah ; emit a long jump to 5000:7c00 dd 50007c00h mov al, "*" jmp PrintError ;;;;;;;;;;;;;;;;;;;; ; 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) jmp PrintError FileNotFoundError: mov al, "F" jmp PrintError PartitionNotFoundError: mov al, "P" 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 ErrorMsg DB " Err " Stage2File DB "SINGLDR " ; spaces are important for FAT file name determination. Compare all 11 bytes! DB 0 ;;;;;;;;;;;;;;;;;;;; ; Boot Sector Signature ORG 510 DW 0AA55h end start