+;
+; This boot sector was based on VnutZ's example available online
+; at http://www.geocities.com/mvea/bootstrap.htm. It also made use
+; of info at http://support.microsoft.com/support/kb/articles/Q140/4/18.asp
+; and the source code for geekos.
+;
+; It searches the root directory of a floppy for two files: boot.com and
+; kernel.bin. If either is missing the boot fails. If they are found, they
+; are loaded into memory at BOOT1SEG:BOOT1OFF and KERNSEG:KERNOFF. Then
+; control is passed to the next stage bootloader, boot.com.
+;
+%include "../inc/defs.asm"
+
+[BITS 16]
+[ORG 0]
+
+START:
+ jmp AROUND_DATA
+
+szOEM_ID db "SCOTTOS", 0 ; 8 bytes, null not required
+wBytesPerSector dw 0x0200 ; bytes per sector
+bSectorsPerCluster db 0x01 ; sectors per cluster
+wReservedSectors dw 0x0001 ; reserved sectors (boot sector)
+bTotalFATs db 0x02 ; num copies of the FAT
+wMaxRootEntries dw 0x00E0 ; max num entries in root directory
+wTotalSectorsSmall dw 0x0B40 ;
+bMediaDescriptor db 0xF0 ;
+wSectorsPerFAT dw 0x0009 ; length of single FAT copy in sectors
+wSectorsPerTrack dw 0x0012 ; sectors per track
+wNumHeads dw 0x0002 ; num heads
+dwHiddenSectors dd 0x00000000 ; num hidden sectors
+dwTotalSectorsLarge dd 0x00000000 ;
+bDriveNumber db 0x00 ; 0x00=A: 0x01=B: 0x80=C: ...
+bFlags db 0x00 ;
+bSignature db 0x29 ;
+dwVolumeID dd 0xFFFFFFFF ;
+szVolumeLabel db "SCOTT BOOT", 0 ; 11 bytes, null not required
+szSystemID db "FAT12 " ;
+
+AROUND_DATA:
+;;; setup segment registers, this code is located at 7C00:0000
+ mov dx, BOOT0SEG ; 7C00
+ mov ds, dx
+ mov es, dx
+
+;;; create a temporary stack at the top of segment 7C00
+ mov ss, dx
+ mov sp, 0xFFFF ; top of this segment
+
+LOAD_ROOT_DIRECTORY:
+;;;
+;;; Compute the length (in number of disk sectors) of the floppy's
+;;; root directory information:
+;;;
+;;; wNumRootDirectorySectors = sizeof(directory_entry) *
+;;; wMaxRootEntries / wBytesPerSector
+;;;
+;;; Store this in cx and in the wNumRootDirectorySectors identifier.
+;;;
+ mov ax, 0x0020 ; sizeof(directory_entry)
+ mul WORD [wMaxRootEntries]
+ div WORD [wBytesPerSector]
+ mov cx, ax ; count of num sectors to read for dir
+
+;;;
+;;; Compute the starting location of the root directory (sector number):
+;;;
+;;; wRootDirectoryStartSector = wReservedSectors +
+;;; (bTotalFATs * wSectorsPerFAT)
+;;;
+ mov al, BYTE [bTotalFATs]
+ mul WORD [wSectorsPerFAT]
+ add ax, WORD [wReservedSectors]
+
+;;;
+;;; Compute the first sector after the root directory -- this is the
+;;; sector where real data starts on the floppy.
+;;;
+ mov WORD [wDriveDataStartSector], ax
+ add WORD [wDriveDataStartSector], cx
+
+;;;
+;;; Read the floppy's root directory into memory at BOOT0SEG:0200 by
+;;; calling ReadSectors. This function reads cx count (wNumRootDirSectors)
+;;; starting at sector ax (wRootDirStartSector) into memory at es:bx
+;;;
+ mov bx, 0x0200
+ call ReadSectors
+
+SEARCH_ROOT_DIRECTORY:
+;;;
+;;; Search the root directory for the stage two loader and the kernel.
+;;;
+ mov cx, WORD [wMaxRootEntries]
+ mov di, 0x0200 ; offset of first directory entry
+.LOOP:
+ push cx ; save current entry count
+ mov cx, 0x000B ; 11 char name
+ mov si, szStage2Image ; name we are seeking
+ push di ; save current entry ptr
+ rep cmpsb ; test for match
+ pop di ; restore entry ptr
+ jne .TRY_KERN ; match? if not see if it matches the kernel
+ mov dx, WORD [di+1Ah] ; match. save the starting cluster
+ mov WORD [wStage2Cluster], dx
+.TRY_KERN:
+ mov cx, 0x000B ; 11 char name
+ mov si, szKernelImage ; name we are seeking
+ push di ; save current entry ptr
+ rep cmpsb ; test for match
+ pop di ; restore entry ptr
+ jne .NEXT ; match? if not next entry
+ mov dx, WORD [di+1Ah] ; match. save the starting cluster
+ mov WORD [wKernelCluster], dx
+.NEXT:
+ pop cx ; cx = entry count again
+ add di, 0x0020 ; next directory entry (+32 bytes later)
+ loop .LOOP ; cx -= 1, jmp .LOOP if cx != 0
+
+.DONE:
+;;;
+;;; Did we find both the kernel and the second stage boot loader? If not
+;;; give them a "missing operating system" message.
+;;;
+ mov ax, [wStage2Cluster]
+ test ax, 0xFFFF
+ jz .NOOS
+ mov ax, [wKernelCluster]
+ test ax, 0xFFFF
+ jnz LOAD_FAT
+.NOOS:
+ mov si, szNoOperatingSystem
+ call Write
+FAIL: mov ah, 0x00 ; wait for a keystroke and reboot
+ int 0x16
+ int 0x19
+
+LOAD_FAT:
+;;;
+;;; Prepare the load the FAT into memory. Compute the size of the FAT (in
+;;; number of sectors) and store in cx for a call to ReadSectors.
+;;;
+ xor ax, ax
+ mov al, BYTE [bTotalFATs]
+ mul WORD [wSectorsPerFAT]
+ mov cx, ax
+
+;;;
+;;; Compute location of FAT and store in ax
+;;;
+ mov ax, WORD [wReservedSectors]
+
+;;;
+;;; Read the FAT into memory at BOOT0SEG:0200. This overwrites the root
+;;; directory information but we already have the cluster number for the
+;;; stage two boot loader and the kernel.
+;;;
+ mov bx, 0x0200
+ call ReadSectors
+
+;;;
+;;; Prepare to read the stage two boot loader into memory at
+;;; BOOT1SEG:BOOT1OFF
+;;;
+ ;; point es:bx at where we want the stage two loader read into memory
+ mov ax, BOOT1SEG
+ mov es, ax ; destination for image
+ mov bx, BOOT1OFF ; destination for image
+
+ ;; use the starting cluster we found by reading the directory
+ mov ax, WORD [wStage2Cluster]
+ mov WORD [wCluster], ax
+
+ ;; call LOAD_IMAGE to read in the file
+ call LOAD_IMAGE
+
+;;;
+;;; Prepare to read the kernel into memory at KERNSEG:KERNOFF
+;;;
+ ;; point es:bx at where we want the kernel read into memory
+ mov ax, KERNSEG
+ mov es, ax
+ mov bx, KERNOFF
+
+ ;; use the starting cluster we found by reading the directory
+ mov ax, WORD [wKernelCluster]
+ mov WORD [wCluster], ax
+
+ ;; call LOAD_IMAGE to read in the file
+ call LOAD_IMAGE
+
+DONE:
+;;;
+;;; We're done, transfer control to the second stage loader which we
+;;; put at BOOT1SEG:BOOT1OFF.
+;;;
+ push WORD BOOT1SEG
+ push WORD BOOT1OFF
+ retf
+
+;; *************************************************************************
+;; PROCEDURE ClusterLBA
+;; convert FAT cluster into LBA addressing scheme
+;; LBA = (cluster - 2) * sectors per cluster
+;; *************************************************************************
+ClusterLBA:
+ sub ax, 0x0002 ; cluster number is zero-based
+ xor cx, cx
+ mov cl, BYTE [bSectorsPerCluster]; convert byte to word
+ mul cx
+ add ax, WORD [wDriveDataStartSector]; base data sector
+ ret
+
+;;; *************************************************************************
+;;; PROCEDURE LBACHS
+;;;
+;;; convert ax LBA addressing scheme to CHS addressing scheme using these
+;;; formulas:
+;;;
+;;; absolute sector = (logical sector / sectors per track) + 1
+;;; absolute head = (logical sector / sectors per track) MOD number of heads
+;;; absolute track = logical sector / (sectors per track * number of heads)
+;;; *************************************************************************
+LBACHS:
+ xor dx, dx
+ div WORD [wSectorsPerTrack]
+ inc dl ; adjust for sector 0
+ mov BYTE [bAbsoluteSector], dl
+ xor dx, dx ; prepare dx:ax for operation
+ div WORD [wNumHeads]
+ mov BYTE [bAbsoluteHead], dl
+ mov BYTE [bAbsoluteTrack], al
+ ret
+
+;;; *************************************************************************
+;;; PROCEDURE ReadSectors
+;;;
+;;; reads disk sectors ax..ax+cx from the disk into memory at es:bx
+;;;
+;;; *************************************************************************
+ReadSectors:
+.MAIN
+ mov di, 5 ; five retries for disk errors
+.SECTORLOOP
+ push ax
+ push bx
+ push cx
+ call LBACHS ; convert LBA sector in ax into absolute CHT
+ mov ah, 0x02 ; BIOS read sector
+ mov al, 0x01 ; read one sector
+ mov ch, BYTE [bAbsoluteTrack]
+ mov cl, BYTE [bAbsoluteSector]
+ mov dh, BYTE [bAbsoluteHead]
+ mov dl, BYTE [bDriveNumber]
+ int 0x13
+ jnc .SUCCESS ; CF=1 on read error
+
+ ;; read error, reset the disk and try again
+ xor ax, ax ; BIOS reset disk
+ int 0x13
+ dec di ; error counter -= 1
+ pop cx ; restore registers
+ pop bx
+ pop ax
+ jnz .SECTORLOOP ; if error counter != 0, try to read again
+
+ ;; give up
+ mov si, szReadError
+ call Write
+ jmp FAIL
+
+ ;; sector read succeeded
+.SUCCESS
+ pop cx ; restore registers
+ pop bx
+ pop ax
+ inc ax ; next sector
+
+ ;;; bx += wBytesPerSector
+ add bx, WORD [wBytesPerSector]
+ loop .MAIN ; cx -= 1, jmp .MAIN if cx != 0
+ ret
+
+;;; *************************************************************************
+;;; PROCEDURE Write
+;;;
+;;; display ASCIIZ string at ds:si via BIOS
+;;; *************************************************************************
+Write:
+ cld ; make sure direction flag is correct
+ lodsb ; load char to al, increment si
+ or al, al ; see if it's null
+ jz .DONE ; if so, we're finished
+ mov ah, 0x0E ; service 0x0E: output one char
+ mov bh, 0x00 ; display page 0
+ mov bl, 0x07 ; char text attrib (white on black)
+ int 0x10 ; invoke bios
+ jmp Write ; next char
+.DONE:
+ ret
+
+LOAD_IMAGE:
+ mov ax, WORD [wCluster] ; cluster to read
+ call ClusterLBA ; convert cluster to LBA
+ xor cx, cx
+ mov cl, BYTE [bSectorsPerCluster]
+ call ReadSectors ; this increments bx
+
+ ;; compute next cluster
+ mov ax, WORD [wCluster] ; identify current cluster
+ mov cx, ax ; copy current cluster
+ mov dx, ax ; copy current cluster
+ shr dx, 0x0001 ; dx = wCluster / 2
+ add cx, dx ; cx = wCluster + (wCluster / 2)
+ push bx
+ mov bx, 0x0200 ; location of FAT in memory
+ add bx, cx ; index into FAT
+ mov dx, WORD [bx] ; read two bytes from FAT
+ pop bx
+ test ax, 0x0001 ; is the cluster even or odd?
+ jnz .ODD
+.EVEN:
+ and dx, 0x0FFF ; take low twelve bits
+ jmp .DONE
+.ODD:
+ shr dx, 0x0004 ; take high twelve bits
+.DONE:
+ mov WORD [wCluster], dx ; store new cluster
+ cmp dx, 0x0FF0 ; test for end of file
+ jb LOAD_IMAGE ; if not, go get the next one
+ ret
+
+wDriveDataStartSector dw 0x0000
+szStage2Image db "BOOT COM", 0
+wStage2Cluster dw 0x0000
+szKernelImage db "KERNEL BIN", 0
+wKernelCluster dw 0x0000
+szNoOperatingSystem db "Missing OS File", 13, 10, 0
+szReadError db "Disk read error", 13, 10, 0
+wCluster dw 0x0000
+bAbsoluteSector db 0x00
+bAbsoluteHead db 0x00
+bAbsoluteTrack db 0x00
+szProgress db ".", 0
+
+ TIMES 510-($-$$) DB 0 ; 0 padding
+ DW 0xAA55 ; bootsector signature