;++
;
;Copyright (c) 1995  Microsoft Corporation
;
;Module Name:
;
; bioschk.asm
;
;Abstract:
;
; The code in this "image" is responsible for checking if is appropriate
; for us to start setupldr.bin. We consider this appropriate when we pass
; BIOS checkings. Setupldr.bin is binary appended at the end of this image.
;
;Author:
;
;    Calin Negreanu (calinn) 16-Dec-1999
;
;Environment:
;
;    Real mode
;
;    Case 1:
;       Complete image has been loaded at 2000:0000 by the boot code
;       DL = INT 13h drive number we've booted from
;
;    Case 2:
;       First 512 bytes of this image has been loaded at 2000:000 by the boot code
;       BX = Starting Cluster Number of this image
;       DL = INT 13h drive number we've booted from
;       DS:SI -> boot media's BPB
;       DS:DI -> argument structure
;
;Revision History:
;
;--
        page    ,132
        title   boot - BIOS check
        name    bioschk

.8086

CODE SEGMENT
ASSUME  CS:CODE,DS:CODE,SS:NOTHING,ES:NOTHING

ORG  0000H

_BiosChk                label       byte

BiosChkDestSeg          EQU 1000h
SetupLdrDestSeg         EQU 2000h
MaxCodeSize             EQU 0800h   ;number of paragraphs (32k)
MaxSetupLdrSize         EQU 4000h   ;number of paragraphs (256k)

StackSeg                EQU 1000h   ;stack goes from here

MAXREAD                 EQU 10000h
MAXSECTORS              EQU MAXREAD/0200h

DoubleWord      struc
        lsw                     dw      ?
        msw                     dw      ?
DoubleWord      ends

SHARED  struc
        ReadClusters            dd      ?               ; function pointer
        ReadSectors             dd      ?               ; function pointer
        SectorBase              dd      ?               ; starting sector for
                                                        ; ReadSectors callback
SHARED  ends

BPB     struc
        BytesPerSector          dw      ?
        SectorsPerCluster       db      ?
        ReservedSectors         dw      ?
        Fats                    db      ?
        DirectoryEntries        dw      ?
        Sectors                 dw      ?
        Media                   db      ?
        FatSectors              dw      ?
        SectorsPerTrack         dw      ?
        Heads                   dw      ?
        HiddenSectors           dd      ?
        SectorsLong             dd      ?
        BootDriveNumber         db      ?
BPB     ends

JMPFAR  MACRO   DestOfs,DestSeg
        db      0eah
        dw      OFFSET  DestOfs
        dw      DestSeg
        endm

START:
;
; The FAT boot sector only reads in the first 512 bytes of NTLDR.  This is
; the module that contains those 512 bytes, so we are now responsible for
; loading the rest of the file.  Other filesystems will load the whole file,
; so the default entrypoint branches around the FAT-specific code.
;
        jmp     RealStart

FatBegin:
.386
;
; If we're here, we've booted off a FAT system and we must load the rest
; of the binary image at 2000:0200 (right behind this sector). The boot
; sector passes us the following:
;       BX = Starting Cluster Number of this image
;       DL = INT 13h drive number we've booted from
;       DS:SI -> boot media's BPB
;       DS:DI -> argument structure
;

;
; Save away the boot drive and the starting cluster number
;
        push    dx
        push    bx
;
; Blast the FAT into memory at 6000:0000 - 8000:0000
;

.386
        push    06000h
.8086
        pop     es
        xor     bx,bx                           ; (es:bx) = 6000:0000
        mov     cx,ds:[si].ReservedSectors
        mov     ds:[di].SectorBase.msw,0
        mov     ds:[di].SectorBase.lsw,cx       ; set up Sector Base

        mov     ax,ds:[si].FatSectors           ; (al) = # Sectors to read
        cmp     ax,080h
        jbe     FatLt64k

;  The FAT is > 64k, so we read the first 64k chunk, then the rest.
;  (A 16-bit FAT can't be bigger than 128k)

        push    cx
        mov     ax,080h         ; (al) = # of sectors to read
        call    ds:[di].ReadSectors
        pop     cx                      ; (cx) = previous SectorBase
.386
        push    07000h
.8086
        pop     es
        xor     bx,bx                   ; (es:bx) = 7000:0000
        mov     ax,ds:[si].FatSectors
        sub     ax,080h                 ; (ax) = # Sectors left to read
        add     cx,080h                 ; (cx) = SectorBase for next read
        mov     ds:[di].SectorBase.lsw,cx
        adc     ds:[di].SectorBase.msw,0        ; set up SectorBase

;
; (al) = # of sectors to read
;
FatLt64k:
        call    ds:[di].ReadSectors

;
; FAT is in memory, now we restore our starting cluster number
;
        pop     dx                      ; (dx) = starting cluster number
        xor     bx,bx

;
; set up FS and GS for reading the FAT
;
.386
        mov     ax,6000h
        mov     fs,ax
        mov     ax,7000h
        mov     gs,ax
.8086

;
; set up ES for reading in the rest of us
;
        push    cs
        pop     es
        mov     ah,MAXSECTORS           ; (ah) = number of sectors we can read

FatLoop:
;
; (dx) = next cluster to load
;
        push    dx
        mov     al,ds:[si].SectorsPerCluster    ; (al) = number of contiguous sectors
                                                ;        found
        sub     ah,ds:[si].SectorsPerCluster                                                    ;        can read before 64k

;
; Check to see if we've reached the end of the file
;
        cmp     dx,0ffffh
        jne     Fat10

;
; The entire file has been loaded.  Throw away the saved next cluster,
; restore the boot drive, and let NTLDR do its thing.
;
        pop     dx
        pop     dx
        jmp     RealStart

Fat10:
        mov     cx,dx
;
; (dx) = (cx) = last contiguous cluster
; (al) = # of contiguous clusters found
;

        call    NextFatEntry
;
; (dx) = cluster following last contiguous cluster

;
; Check to see if the next cluster is contiguous.  If not, go load the
; contiguous block we've found.
;
        inc     cx
        cmp     dx,cx

        jne     LncLoad

;
; Check to see if we've reached the 64k boundary.  If so, go load the
; contiguous block so far.  If not, increment the number of contiguous
; sectors and loop again.
;
        cmp     ah,0
        jne     Lnc20
        mov     ah,MAXSECTORS           ; (ah) = number of sectors until
        jmp     short LncLoad

Lnc20:
        add     al,ds:[si].SectorsPerCluster
        sub     ah,ds:[si].SectorsPerCluster
        jmp     short Fat10


LncLoad:
;
; (TOS) = first cluster to load
; (dx)  = first cluster of next group to load
; (al)  = number of contiguous sectors
;
        pop     cx
        push    dx
        mov     dx,cx
        mov     cx,10                   ; (cx) = retry count

;
; N.B.
;       This assumes that we will never have more than 255 contiguous clusters.
;       Since that would get broken up into chunks that don't cross the 64k
;       boundary, this is ok.
;
; (dx) = first cluster to load
; (al) = number of contiguous sectors
; (TOS) = first cluster of next group to load
; (es:bx) = address where clusters should be loaded
;
FatRetry:
        push    bx
        push    ax
        push    dx
        push    cx
        call    [di].ReadClusters
        jnc     ReadOk
;
; error in the read, reset the drive and try again
;
        mov     ax,01h
        mov     al,ds:[si].BootDriveNumber
        int     13h
        xor     ax,ax
        mov     al,ds:[si].BootDriveNumber
        int     13h

;
; pause for a while
;
        xor     ax,ax
FatPause:
        dec     ax
        jnz     FatPause

        pop     cx
        pop     dx
        pop     ax
        pop     bx

        dec     cx
        jnz     FatRetry

;
; we have re-tried ten times, it still doesn't work, so punt.
;
        push    cs
        pop     ds
        mov     si,offset FAT_ERROR
FatErrPrint:
        lodsb
        or      al,al
        jz      FatErrDone
        mov     ah,14           ; write teletype
        mov     bx,7            ; attribute
        int     10h             ; print it
        jmp     FatErrPrint

FatErrDone:
        jmp     $
        ; BUGBUG this should be replaced by a mechanism to get a pointer
        ; passed to us in the param block. since the boot sector msg itself
        ; is properly localized but this one isn't.
FAT_ERROR       db      13,10,"Disk I/O error",0dh,0ah,0


ReadOk:
        pop     cx
        pop     dx
        pop     ax
        pop     bx
        pop     dx                      ; (dx) = first cluster of next group
                                        ;        to load.

.386
;
; Convert # of sectors into # of bytes.
;
        mov     cl,al
        xor     ch,ch
        shl     cx,9
.8086
        add     bx,cx
        jz      FatLoopDone
        jmp     FatLoop

FatLoopDone:
;
; (bx) = 0
;   This means we've just ended on a 64k boundary, so we have to
;   increment ES to continue reading the file.  We are guaranteed to
;   always end on a 64k boundary and never cross it, because we
;   will reduce the number of contiguous clusters to read
;   to ensure that the last cluster read will end on the 64k boundary.
;   Since we start reading at 0, and ClusterSize will always be a power
;   of two, a cluster will never cross a 64k boundary.
;
        mov     ax,es
        add     ax,01000h
        mov     es,ax
        mov     ah,MAXSECTORS
        jmp     FatLoop

;++
;
; NextFatEntry - This procedure returns the next cluster in the FAT chain.
;                It will deal with both 12-bit and 16-bit FATs.  It assumes
;                that the entire FAT has been loaded into memory.
;
; Arguments:
;    (dx)   = current cluster number
;    (fs:0) = start of FAT in memory
;    (gs:0) = start of second 64k of FAT in memory
;
; Returns:
;    (dx)   = next cluster number in FAT chain
;    (dx)   = 0ffffh if there are no more clusters in the chain
;
;--
NextFatEntry    proc    near
        push    bx

;
; Check to see if this is a 12-bit or 16-bit FAT.  The biggest FAT we can
; have for a 12-bit FAT is 4080 clusters.  This is 6120 bytes, or just under
; 12 sectors.
;
; A 16-bit FAT that's 12 sectors long would only hold 3072 clusters.  Thus,
; we compare the number of FAT sectors to 12.  If it's greater than 12, we
; have a 16-bit FAT.  If it's less than or equal to 12, we have a 12-bit FAT.
;
        call    IsFat12
        jnc     Next16Fat

Next12Fat:
        mov     bx,dx                   ; (fs:bx) => temporary index
        shr     dx,1                    ; (dx) = offset/2
                                        ; (CY) = 1  need to shift
        pushf                           ;      = 0  don't need to shift
        add     bx,dx                   ; (fs:bx) => next cluster number
.386
        mov     dx,fs:[bx]              ; (dx) = next cluster number
.8086
        popf
        jc      shift                   ; carry flag tells us whether to
        and     dx,0fffh                ; mask
        jmp     short N12Tail
shift:
.386
        shr     dx,4                    ; or shift
.8086

N12Tail:
;
; Check for end of file
;
        cmp     dx,0ff8h                ; If we're at the end of the file,
        jb      NfeDone                 ; convert to canonical EOF.
        mov     dx,0ffffh
        jmp     short NfeDone

Next16Fat:
        add     dx,dx                   ; (dx) = offset
        jc      N16high

        mov     bx,dx                   ; (fs:bx) => next cluster number
.386
        mov     dx,fs:[bx]              ; (dx) = next cluster number
.8086
        jmp     short N16Tail

N16high:
        mov     bx,dx
.386
        mov     dx,gs:[bx]
.8086

N16Tail:
        cmp     dx,0fff8h
        jb      NfeDone
        mov     dx,0ffffh               ; If we're at the end of the file
                                        ; convert to canonical EOF.

NfeDone:
        pop     bx
        ret
NextFatEntry    endp

;++
;
; IsFat12 - This function determines whether the BPB describes a 12-bit
;           or 16-bit FAT.
;
; Arguments - ds:si supplies pointer to BPB
;
; Returns
;       CY set -   12-bit FAT
;       CY clear - 16-bit FAT
;
;--
IsFat12 proc    near

.386
        push    eax
        push    ebx
        push    ecx
        push    edx

        movzx   ecx, ds:[si].Sectors
        or      cx,cx
        jnz     if10
        mov     ecx, ds:[si].SectorsLong
if10:
;
; (ecx) = number of sectors
;
        movzx   ebx, byte ptr ds:[si].Fats
        movzx   eax, word ptr ds:[si].FatSectors
        mul     ebx
        sub     ecx,eax

;
; (ecx) = (#sectors)-(sectors in FATs)
;
        movzx   eax, word ptr ds:[si].DirectoryEntries
        shl     eax, 5
;
; (eax) = #bytes in root dir
;
        mov     edx,eax
        and     edx,0ffff0000h
        div     word ptr ds:[si].BytesPerSector
        sub     ecx,eax

;
; (ecx) = (#sectors) - (sectors in fat) - (sectors in root dir)
;
        movzx   eax, word ptr ds:[si].ReservedSectors
        sub     ecx, eax
        mov     eax, ecx
        movzx   ecx, byte ptr ds:[si].SectorsPerCluster
        xor     edx,edx
        div     ecx

        cmp     eax, 4087
        jae     if20
        stc
        jmp     short if30
if20:
        clc
if30:
        pop     edx
        pop     ecx
        pop     ebx
        pop     eax
        ret
.8086
IsFat12 endp


Free    EQU     512-($-Start)
if Free lt 0
        %out FATAL PROBLEM: FAT-specific startup code is greater than
        %out 512 bytes.  Fix it!
        .err
endif

RealStart:

;
; we are completely done with the boot sector, we can party on it's memory as we like.
; set up the stack
;
        mov     ax,StackSeg
        mov     ss,ax
        xor     sp,sp

        mov     ax,cs
        mov     ds,ax
        mov     es,ax

;
; save setupldr data
;
        mov     Preserve, dl

; move ourselves from 2000:0000 to 1000:0000, one paragraph at a time.

        mov     ax, BiosChkDestSeg
        mov     es, ax
        mov     dx, MaxCodeSize
        cld
Again1:
        xor     di, di
        xor     si, si
        mov     cx, 10h
        rep     movsb
        mov     ax, ds
        inc     ax
        mov     ds, ax
        mov     ax, es
        inc     ax
        mov     es, ax
        dec     dx
        jnz     Again1
        mov     ax, BiosChkDestSeg
        mov     ds, ax
        mov     es, ax
        JMPFAR  Continue1, BiosChkDestSeg

Continue1:

; insert your BIOS check code here
; for now we will just display a message and wait for the user to press a key
        mov     si,OFFSET MsgPressKey
        call    PrintMsg
QueueEmpty:
        mov     ah, 01h
        int     16h
        jz      QueueEmpty

; instead of a real BIOS check we will check if the user holds down CTRL.
; If yes, we will behave like BIOS check failed

;        mov     ah,02h
;        int     16h
;        and     al,00000100b
;        jz      MoveSetupLdr

; at this point, the BIOS check failed. You can add whatever code you want here
; to give a message or to make the computer crash. If you don't do anything, the
; code will jump to 2000:0000 so an infinite loop is going to happen.
;        jmp     Continue2

MoveSetupLdr:
; move Setupldr code from 2000+MaxCodeSize:0000 to 2000:0000, one paragraph at a time.

        push    ds
        push    es
        mov     ax, SetupLdrDestSeg
        mov     es, ax
        add     ax, MaxCodeSize
        mov     ds, ax
        mov     dx, MaxSetupLdrSize
        cld
Again2:
        xor     di, di
        xor     si, si
        mov     cx, 10h
        rep     movsb
        mov     ax, ds
        inc     ax
        mov     ds, ax
        mov     ax, es
        inc     ax
        mov     es, ax
        dec     dx
        jnz     Again2
        pop     es
        pop     ds

Continue2:
        mov     dl, Preserve
        JMPFAR  0,SetupLdrDestSeg

;
;EXPECTS DS:SI - MESSAGE ADDR
;
PrintMsg proc    near
        push    ax
        push    bx
PrintMsgLoop:
        lodsb
        cmp     al,0
        je      PrintMsgEnd
        mov     ah,0eh
        mov     bx,0007h
        int     10h
        jmp     PrintMsgLoop
PrintMsgEnd:
        pop     bx
        pop     ax
        ret
PrintMsg endp

Preserve                db      ?
MsgPressKey             db      0dh, 0ah, "Press any key to continue..."
                        db      0

.errnz  ($-_BiosChk) GT (MaxCodeSize*16 - 2)    ;FATAL: BiosChk code is too large

        org     MaxCodeSize*16 - 2
        db      55h,0aah


CODE ENDS
END  START