; ; 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