;/*static char *SCCSID = "@(#)ibmboot.asm       6.9 92/03/14";*/
        page    ,132
        title   ibmboot - Fat Boot Sector
        subttl  ****  IBM  CONFIDENTIAL  ****
        name    ibmboot
;
; ****************************************************************************
; *                                                                          *
; *                       IBM/Microsoft Confidential                         *
; *                                                                          *
; *                 Copyright (c) IBM Corporation  1987, 1991                *
; *                 Copyright (c) Microsoft Corp.  1987, 1991                *
; *                           All Rights Reserved                            *
; *                                                                          *
; ****************************************************************************

; The ROM in the IBM PC starts the boot process by performing a hardware
; initialization and a verification of all external devices.  If all goes
; well, it will then load from the boot drive the sector from track 0, head 0,
; sector 1.  This sector is placed at physical address 07C00h.
;
; The code in this sector is responsible for locating os2boot and for placing
; the directory sector with this information at DirSeg:0. After loading in
; this sector, it reads in the entirety of the os2boot at MicroSeg:0
; and does a long jump to the entry point at BiosSeg:0.
;
; If os2boot is not found an error message is displayed and the user is
; prompted to insert another disk.  If there is a disk error during the
; process, a message is displayed and the user is prompted to insert another
; disk.
;
; At the beginning of the boot sector, there is a table which describes the
; structure of the media.  This is equivalent to the BPB with some
; additional information describing the physical layout of the driver (heads,
; tracks, sectors)
;

        .xlist
        .286
        include dirent.inc      ;directory entry definition
        include doshlp.inc
        .list

SectorSize      equ     512             ; sector size

DoubleWord      struc
lsw     dw      ?
msw     dw      ?
DoubleWord      ends

DskttPrmTbl          struc
FirstSpecByte        db    ?    ; First Specify byte
SecndSpecByte        db    ?    ; Second Specify byte
NumTimerTicks        db    ?    ; Number of Timer Ticks to wait before motor off
BytesPerSec          db    ?    ; Bytes per sector FLAG byte
SecsPerTrack         db    ?    ; Sectors per track
GapLength            db    ?    ; Gap Length
DataLength           db    ?    ; Dtl (Data Length)
FormatGap            db    ?    ; Gap length for Format
FillByte             db    ?    ; Fill byte for format
SettleTime           db    ?    ; Head Settle time(milliseconds)
StartTime            db    ?    ; Motor Startup time (1/8 seconds)
DskttPrmTbl          ends

BootSeg segment at 07c0h
BootSeg ends

DirSeg  segment at 1000h
DirSeg  ends

MicrSeg segment at 0800h
MicrSeg ends

BiosData segment at 040h
         db    19 DUP(?)
MemSize  dw    ?
BiosData ends

IntVectors segment at 0
                     dd    1eh DUP(?)
DisketteParmsVector  dd    ?
IntVectors ends


;/********************** START OF SPECIFICATIONS **********************/
;/*                                                                   */
;/* SUBROUTINE NAME: IBMBOOT                                          */
;/*                                                                   */
;/* DESCRIPTIVE NAME: Bootstrap loader                                */
;/*                                                                   */
;/* FUNCTION:    To load os2boot into memory.                         */
;/*              The boot sector contains a data structure, BPB,      */
;/*              to describe the disk's parameters. It uses the       */
;/*              BIOS' interrupt 13H handler to read the              */
;/*              root directory into the memory location 1000:0000H,  */
;/*              scans root directory for os2boot, and uses the       */
;/*              BIOS' interrupt 13H handler to read os2boot from     */
;/*              disk/diskette into the memory location 0800:0000H.   */
;/*                                                                   */
;/* NOTES:       IBMBOOT is loaded by the ROM BIOS (Int 19H) at       */
;/*                 physical memory location 0000:7C00H.              */
;/*              IBMBOOT runs in real mode.                           */
;/*              This is FAT-based file system.                       */
;/*              os2boot is a contiguous file.                        */
;/*                                                                   */
;/* ENTRY POINT: IBMBOOT                                              */
;/*    LINKAGE:  Jump (far) from Int 19H                              */
;/*                                                                   */
;/* INPUT:       CS:IP = 0000:7C00H                                   */
;/*              SS:SP = 0030:00FAH (CBIOS dependent)                 */
;/*                                                                   */
;/* EXIT-NORMAL:                                                      */
;/*                                                                   */
;/* EXIT-ERROR:  None                                                 */
;/*                                                                   */
;/* EFFECTS:     os2boot is loaded into the physical memory           */
;/*                location 0800:0000H                                */
;/*              Root directory is loaded into the physical memory    */
;/*                location 1000:0000H                                */
;/*                                                                   */
;/* MESSAGES:                                                         */
;/*              A disk read error occurred.                          */
;/*              The file os2boot cannot be found.                    */
;/*              Insert a system diskette and restart the system.     */
;/*                                                                   */
;/* INTERNAL REFERENCES:                                              */
;/*    ROUTINES:                                                      */
;/*                                                                   */
;/* EXTERNAL REFERENCES:                                              */
;/*    ROUTINES: IBMBOOT - Load IBMDOS.COM                            */
;/*                                                                   */
;/*********************** END OF SPECIFICATIONS ***********************/
BootCode        segment ;would like to use BootSeg here, but LINK flips its lid
    ASSUME  CS:BootCode,DS:NOTHING,ES:NOTHING,SS:NOTHING

        public  IBMBOOT
IBMBOOT proc    far

        JMP     Start

;       THE FOLLOWING DATA CONFIGURES THE BOOT PROGRAM
;       FOR ANY TYPE OF DRIVE OR HARDFILE
;
Version                 db      "IBM 20.0"
BPB                     label   byte
BytesPerSector          dw      SectorSize      ; Size of a physical sector
SectorsPerCluster       db      8               ; Sectors per allocation unit
ReservedSectors         dw      1               ; Number of reserved sectors
Fats                    db      2               ; Number of fats
DirectoryEntries        dw      512             ; Number of directory entries
Sectors                 dw      4*17*305-1      ; No. of sectors - no. of hidden sectors
Media                   db      0F8H            ; Media byte
FatSectors              dw      8               ; Number of fat sectors
SectorsPerTrack         dw      17              ; Sectors per track
Heads                   dw      4               ; Number of surfaces
HiddenSectors           dd      1               ; Number of hidden sectors
SectorsLong             dd      0               ; Number of sectors iff Sectors = 0

; The following byte is NOT part of the BPB but is set by SYS and format
; We should NOT change its position.
;

; keep order of DriveNumber and CurrentHead!
DriveNumber     db      80h           ; Physical drive number (0 or 80h)
LogicalDrive    db      0             ; if bit 7 is set, lower bits have
                                      ; logical drive. 0=c, 1=d, ect

Signature       db      41            ; Signature Byte for bootsector
BootID          dd      ?             ; Boot ID field.
Boot_Vol_Label  db      11 dup (?)
Boot_System_ID  db      'FAT     '    ; "FAT     " or "OTHER_FS"

;
; The following variables are not part of the Extended BPB;  they're just
; scratch variables for the boot code.
;

ClusterBase     dw      ?             ; first sector of cluster # 2
RootDirSeg      dw      DirSeg        ; Memory selector for Root Directory's
                                      ; location in memory
RtDirBgSec      dw      ?             ; 1st sector that contains root directory info
RtDirSecCt      dw      ?             ; Count of sectors that contain root directory


;****************************************************************************

Start:
        cli                           ; Turn off interrupts while setting stack
        xor     bx,bx                 ; Setup the stack to just below where
        mov     ss,bx                 ; this boot strap code has been loaded
        mov     sp,07BFFh             ; Since stacks go downward this
                                      ; gives the stack a large section of
                                      ; memory to grow into
        sti                           ; turn interrupts back on
        mov     dx,Bootseg            ; Load into DS a segment value which
        mov     ds,dx                 ; can address our current place in
                                      ; memory 0:7C00 or 07C0:0000

    ASSUME  DS:BootCode

        cmp     DriveNumber,0         ; Are we booting from diskette??
        jne     DetLSN                ; Booting from hardfile, no need to set
                                      ; diskette parameter table
        push    ds                    ; save ds

    ASSUME  DS:nothing

        mov     ax,BiosData           ; decerement the memory size in the bios
        mov     es,ax                 ; data area (40:13) by 1 (1K)
        dec     es:MemSize            ; this K will be stolen for new DPB

        int     12h                   ; get the memory size from bios data area

        shl     ax,6                  ; shift 6 left for segement address
        mov     es,ax                 ; of new DPB
        xor     di,di                 ; offset 0      es:di -> new DPB

                ; load Diskette Parm pointer into ds:si
                ; from the INT 1E vector
         xor    ax,ax
         mov    ds,ax
         lds    si,ds:DisketteParmsVector   ; ds:si -> current DPB

; ds:si => current diskette parameter table
; es:di => our(new) diskette parameter table

         cld                          ; clear string direction flag
         mov    cx,size DskttPrmTbl   ; move a copy of the current diskette
         rep    movsb                 ; parm table into our table

         pop    ds

    ASSUME  DS:BootCode

         mov    ax,SectorsPerTrack    ; get sectors per track from BPB and
         mov    es:SecsPerTrack,al    ; put it into the parm table

         push   ds

    ASSUME  DS:nothing

         xor    ax,ax
         mov    ds,ax

                ; Store our new diskette parm table address into
                ; the INT 1E entry
         mov    word ptr ds:DisketteParmsVector,ax
         mov    word ptr ds:DisketteParmsVector+2,es

         pop    ds

    ASSUME  DS:BootCode

; dl => current drive number
; ah => 0 (Reset diskette system)
        mov     dl,DriveNumber
                                      ; ax still set to 0
        int     13h                   ; Reset the diskette system


; The system is now prepared for us to begin reading.  First, determine
; logical sector numbers of the start of the directory and the start of the
; data area.
;
DetLSN:
        mov     al,Fats               ; Load in the number of FATs (usually two)
        cbw                           ; zero out the high word for safety
        mul     FatSectors            ; Mult # of FATs by # of sectors per FAT
        add     ax,ReservedSectors    ; AX = Fats*FatSectors + ReservedSectors
        push    ax                    ; Save this value for later use
        xchg    cx,ax                 ; CX = start of DIR
;
; Take into account size of directory (only know number of directory entries)
;
        mov     ax,size dir_entry     ; Get directory entry size
        mul     DirectoryEntries      ; Mult by # or root directory entries
        mov     bx,BytesPerSector     ; add in sector size so that when we
        add     ax,bx                 ; can round up any parital sector
        dec     ax                    ;
        div     bx                    ; convert to sector number
        push    ax                    ; save # sectors in directory for later use
        add     ax,cx                 ; ClusterBase = Fats*FatSectors + ReservedSectors
                                      ;               + RootDirSectors
        mov     ClusterBase,ax        ; save it so others can use it
;
; Load in the root directory.
;
        mov     ax,DirSeg             ; es -> directory segment
        mov     es,ax
    ASSUME  ES:DirSeg
        xor     di,di                 ; Load directory starting at 0 offset
        pop     cx                    ; sector count = # of RootDirSectors
        mov     RtDirSecCt,cx
        pop     ax                    ; LSN from which to start reading =
        mov     RtDirBgSec,ax
        xor     dx,dx                 ; start of DIR
        call    DoRead                ; Read the root directory into memory

; Now we scan for the presence of FAT micro-loader

        xor     bx,bx                 ; Initalize directory offset to 0
        mov     cx,DirectoryEntries   ; move the # of entries into cx
ScanTop:
        mov     di,bx                 ; move current dirseg offset into di
        push    cx                    ; save current count of dir entries left to check
        mov     cx,11                 ; load cx with dir entry name size
        mov     si,offset IBMMICRO    ; have ds:si point to memory holding
                                      ; name of FAT micro-loader
        repe    cmpsb                 ; do the string compare
        pop     cx                    ; retrieve current dir entry count
        je      ScanEnd               ; if compare set the z flag jump to end scan
        add     bx,size dir_entry     ; else increment offset to next dir entry
        loop    ScanTop               ; and start loop again
ScanEnd:
        jcxz    BootErr$bnf           ; If we get here the code did not find
                                      ; the micro-loader in the root directory
                                      ; jump to print an error to the user

; Found it - Set the number of sectors to read in.

        mov     ax,es:[bx].dir_size_l    ; get file size
        mov     dx,es:[bx].dir_size_h
        div     BytesPerSector           ; convert to sectors
        inc     al                       ; reading in one more can't hurt
        mov     cl,al                    ; Store running count

; And the sector to start reading from. NOTE since the first two cluster numbers
; in the FAT do not describe allocations, we subtract two(2) from any cluster
; number to determine its LSN(Logical Sector Number).

        mov     dx,es:[bx].dir_firstfile ; dx = starting cluster number
        dec     dx                       ; Subtract cluster # by 2
        dec     dx
        mov     al,SectorsPerCluster     ; ax = sectors/cluster
        xor     ah,ah
        mul     dx                       ; dx:ax = relative starting logical sector number
        add     ax,ClusterBase           ; adjust for FATs, root dir, boot sector
        adc     dx,0

; We are going to put the micro-loader into memory between the boot sector
; and the root directory.  This is all right as long as the micro-loader and its
; data are less than 32k in size

        mov     bx,MicrSeg            ; es -> micro-loader segment
        mov     es,bx
    ASSUME  ES:MicrSeg
        xor     di,di                 ; Load micro-Loader at MicrSeg:0
        push    es                    ; Save this address on the stack for
        push    di                    ; use by the far return at the end
        call    DoRead                ; read the sectors in
;
;   Stack contains MicrSeg:0 (Beginning of the micro-loader code/data
;   DS:SI -> the boot media's BPB
;
        lea     si,BPB                  ; ds:si -> BPB
        ret                             ; far ret to Fat Black Box entry point
IBMBOOT endp

; BootErr - print error message and hang the system.
;
BootErr proc
BootErr$bnf:
        mov     si,offset TXT_MSG_SYSINIT_BOOT_NOT_FD +2
        jmp short BootErr2
BootErr$he:
        mov     si,offset TXT_MSG_SYSINIT_BOOT_ERROR +2
BootErr2:
        call    BootErr$print
        mov     si,offset TXT_MSG_SYSINIT_INSER_DK +2
        call    BootErr$print
        sti
        jmp short $                     ;Wait forever
BootErr$print:
        lodsb                           ; Get next character
        or      al,al
        jz      PrintEnd
        mov     ah,14                   ; Write teletype
        mov     bx,7                    ; Attribute
        int     10h                     ; Print it
        jmp short BootErr$print
PrintEnd:
        ret
BootErr endp

;
; DoRead - read SectorCount sectors into address starting from sector
;          SectorBase.
;
;          input: cl=sectorcount
;                 es:di=address
;                 dx:ax=sectorbase
;
;          uses:  bl=currentsector
;                 bh=currenthead
;
;          exits: regs messed up
;
DoRead  proc
drloop:
        push    ax                      ; save current sectorbase
        push    dx
        push    cx                      ; save current count

; DoDiv - convert logical sector number in AX to physical Head/Track/Sector
;         in CurrentHead/CurrentTrack/CurrentSector.
;

; @90479 removed the following...

;       add     ax,HiddenSectors.lsw    ; adjust for partition's base sector
;       adc     dx,HiddenSectors.msw
;       div     SectorsPerTrack         ; divide sec by sec/trk
;       inc     dl                      ; remain is sector number, 1-based
;       mov     bl,dl                   ; save current sector
;       xor     dx,dx
;       div     Heads                   ; divide to get trk and head
;       mov     bh,dl                   ; save current head
;       mov     dx,ax                   ; save current track


; @90479 NEW code.  The old code used a divide that could overflow.  This
; code calculates the CYLINDER first, instead of the SECTOR, thereby avoiding
; an overflow.

        add     ax,HiddenSectors.lsw    ; adjust for partition's base sector
        adc     dx,HiddenSectors.msw
        push    ax                      ; save lsw of sector number
        mov     ax, Heads               ; get head count
        mul     byte ptr SectorsPerTrack ; AX = sectors per cylinder
        mov     bx, ax                  ; save result in bx
        pop     ax                      ; restore lsw of sector number
        div     bx                      ; ax=cyl number, dx=remainder
        xchg    ax,dx                   ; put cyl number in dx
        div     byte ptr SectorsPerTrack ; AL=heads, ah=remainder (sectors)
        xchg    ah,al
        mov     bx,ax                   ; put head/sector count in BX
        inc     bl                      ; sector number is 1-based

; @90479 ends.

; CurrentHead bh is the head for this next disk request
; CurrentSector bl is the beginning sector number for this request
; CurrentTrack dx is the track for this next request

; Compute the number of sectors that we may be able to read in a single ROM
; request


        mov   ax,SectorsPerTrack
        sub   al,bl
        inc   ax

; AX is the number of sectors that we may read.

        push    ax                      ; save this count

; DoCall - call ROM BIOS to read AL sectors into ES:BX.
;

        mov     ah,2                    ; do a read

;CurrentTrack is in dx

        mov     cl,6                    ; shift factor
        shl     dh,cl                   ; get cyl hi's
        or      dh,bl                   ; add in CurrentSector
        mov     cx,dx                   ; put cyl/sec in right register
        xchg    ch,cl
        mov     dl,DriveNumber
        mov     dh,bh                   ; CurrentHead
        mov     bx,di                   ; get address
        int     13h
        jc      BootErr$he              ; If errors report and stop
        pop     bx                      ; get count, saved as ax
        pop     cx                      ; get left count
        mov     ax,bx                   ; adjust data address in anticipation
        mul     BytesPerSector          ; compute length of read in bytes
        add     di,ax                   ; adjust data address
        pop     dx                      ; get sector base
        pop     ax                      ;
        add     ax,bx                   ; adjust sector base
        adc     dx,0                    ;
        sub     cl,bl                   ; reduce total count
        jg      drloop
        ret

DoRead  endp

        include ibmboot.inc             ; suck in the message text

IBMMICRO      DB      "OS2BOOT    "

Free    EQU 510-($-IBMBOOT)

if Free lt 0
    %out FATAL PROBLEM: boot sector is too large
    %out Shrink boot code!!
    .err
endif

if Free gt 0
        DB      Free DUP(0)
endif

db      55h,0aah

        public  BootSectorEnd
BootSectorEnd   label   dword

BootCode        ends
        END     IBMBOOT
