;/*static char *SCCSID = "@(#)os2boot.asm       6.7 91/10/04";*/
        page    ,132
        title   os2boot - Fat OS2LDR loader
        subttl  ****  IBM  CONFIDENTIAL  ****
        name    os2boot

;/****************************************************************************/
;/*                                                                          */
;/*                       IBM/Microsoft Confidential                         */
;/*                                                                          */
;/*                 Copyright (c) IBM Corporation  1987, 1991                */
;/*                 Copyright (c) Microsoft Corp.  1987, 1991                */
;/*                           All Rights Reserved                            */
;/*                                                                          */
;/****************************************************************************/

.286p

        .xlist
        include doshlp.inc
        include dirent.inc
        .list

DoubleWord      struc
lsw     dw      ?
msw     dw      ?
DoubleWord      ends

Sec1Seg segment at 07c0h
Sec1Seg ends

Sec2Seg segment at 07e0h
Sec2Seg ends

BiosSeg segment at 2000h
BiosSeg ends

FatBPBPlus struc
BytesPerSector          dw      ?          ; Size of a physical sector
SectorsPerCluster       db      ?          ; Sectors per allocation unit
ReservedSectors         dw      ?          ; Number of reserved sectors
Fats                    db      ?          ; Number of fats
DirectoryEntries        dw      ?          ; Number of directory entries
Sectors                 dw      ?          ; No. of sectors - no. of hidden sectors
Media                   db      ?          ; Media byte
FatSectors              dw      ?          ; Number of fat sectors
SectorsPerTrack         dw      ?          ; Sectors per track
Heads                   dw      ?          ; Number of surfaces
HiddenSectors           dd      ?          ; Number of hidden sectors
SectorsLong             dd      ?          ; Number of sectors iff Sectors = 0
DriveNumber             db      ?          ; Physical drive number (0 or 80h)
LogicalDrive            db      ?          ; if bit 7 is set, lower bits have
Signature               db      ?          ; Signature Byte for bootsector
BootID                  dd      ?          ; Boot ID field.
Boot_Vol_Label          db      11 dup (?) ; Label string
Boot_System_ID          db      8  dup (?) ; Disk/diskette volume ID
ClusterBase             dw      ?          ; first sector of cluster # 2
RootDirSeg              dw      ?          ; Selector for Directory's spot in memory
RtDirBgSec              dw      ?          ; 1st sector of root directory
RtDirSecCt              dw      ?          ; number of sectors taken up by directory
FatBPBPlus ends

PostExtBS struc
PosTextCode             db    440 dup (?)  ; Size of the post-extension code
FrstCylSec              dw      ?          ; Cylinder/Sector of 1st program sector
FrstDrvHead             dw      ?          ; Drive/Head of 1st program sector
ExtBSSig                dw      ?          ; Post-Extension Signature word
PostExtBS ends

PostExtHdr struc
LenHdrCode              dw      ?        ; Size of the post-extension header/code
NextCylSec              dw      ?        ; Cylinder/Sector of Next program sector
NextDrvHead             dw      ?        ; Drive/Head of Next program
ScndCylSec              dw      ?        ; Cylinder/Sector of 2nd program sector
ScndDrvHead             dw      ?        ; Drive/Head of 2nd program sector
Reserved                db      4 dup(?) ; Reserved bytes
AttrRevLvl              dw      ?        ; Attribute and Revision level of code
AdapterID               dw      ?        ; Adapter ID being serviced by code
FrstSecSig              dw      ?        ; Post-Extension Signature word
ProgStart               db      ?        ; Offset of beginning of code
PostExtHdr ends

PostExtSig              equ     5A5Ah

;/*********************************************************************/
;/*                                                                   */
;/*     OS2BOOT -                                                     */
;/*                                                                   */
;/*     This routine is the entry point for the FAT Black Box         */
;/*     which loads OS2LDR.                                           */
;/*                                                                   */
;/*     The boot sector jumps (via far jump) to this routine.         */
;/*                                                                   */
;/*     When the Fat Black Box receives control from the FAT          */
;/*     boot sector this is the expected memory map:                  */
;/*                                                                   */
;/*             +-------------+ Top of High Memory                    */
;/*             |   unused    |                                       */
;/*             +-------------+ 1Meg                                  */
;/*             | ROM/screen  |                                       */
;/*             +-------------+ Top of Low Memory                     */
;/*             |   unused    |                                       */
;/*             +-------------+ 20000h (128k)                         */
;/*             |  Root Dir.  |                                       */
;/*             +-------------+ 10000h (64k)                          */
;/*             |   unused    |                                       */
;/*             +-------------+                                       */
;/*             |FAT Black Box|                                       */
;/*             +-------------+ 08000h                                */
;/*             |   unused    |                                       */
;/*             +-------------+ 07E00h                                */
;/*             | BPB/BootSec |                                       */
;/*             +-------------+ 07C00h                                */
;/*             |  reserved   |                                       */
;/*             +-------------+  0                                    */
;/*                                                                   */
;/*                                                                   */
;/*     ENTRY   (CS:IP) = 0800:0000H (entry point address)            */
;/*             (SS:SP) = 0000:7C00H                                  */
;/*             (DS:SI) = address of BOOT MEDIA'S bpb                 */
;/*                                                                   */
;/* EXIT-NORMAL:                                                      */
;/*             (BX)    = LSN (relative to the partition) of first    */
;/*                       sector of cluster # 2                       */
;/*             (DH)    = 0 (meaning a non-micro/mini-FSD/Ripl boot   */
;/*             (DL)    = INT 13 drive number for this boot drive     */
;/*             (DS:SI) = the boot media's BPB                        */
;/*             (ES:DI) = root directory                              */
;/*              Jmp to beginning of OS2LDR                           */
;/*                                                                   */
;/* EXIT-ERROR:  None                                                 */
;/*                                                                   */
;/* EFFECTS:     os2ldr is loaded into the physical memory            */
;/*                location 2000:0000H                                */
;/*              Root directory is loaded into the physical memory    */
;/*                location 1000:0000H                                */
;/*                                                                   */
;/* MESSAGES:                                                         */
;/*              A disk read error occurred.                          */
;/*              The file os2ldr cannot be found.                     */
;/*              Insert a system diskette and restart the system.     */
;/*                                                                   */
;/*********************************************************************/
;

OS2BOOT segment

        public  BOOTPRC
BOOTPRC proc    far
    assume  cs:OS2BOOT,ds:nothing,es:nothing,ss:nothing

        jmp     start
;
; scratch variables for the FAT black box code
;

BootBlkInfo     FatBPBPlus <>        ; BPB and other info from boot sector code
FatSeg1         dw      0            ; Selector for 1st 64k of FAT in memory
FatSeg2         dw      0            ; Selector for 2nd 64k of FAT (if needed)
FSg2SecSrt      dw      0            ; Temporary variables used when reading
FSg2SecCnt      dw      0            ;   very large FAT table into memory
FatEntrySize    db      16           ; assume 16 bits in FAT entry
EOFClus         dw      0fff7h       ; assume 16-bit FAT EOF cluster
LdrClusCnt      dw      0            ; Number of clusters allocated to OS2LDR file
SecsIn64K       dw      0            ; Number of sectors in 64k for CLREAD proc
EntryAddr       DoubleWord <>        ; Jump address that allows change cs selector
ReturnAddr      DoubleWord <>        ; Jump address that returns to current cs
NxtUsrSec       dw      0            ; Hold place on next code sector during
NxtUsrHead      dw      0            ; calling of the current code

start:
        cld                          ; clear the string flag to positive movement
        mov     dx,cs                ; put the current value of cs into es
        mov     es,dx                ; because our code and data is intermixed

; We are going to call certain POST-extension programs.  These programs
; gain control through near calls.  They also expect that the CS segment
; register to contain 0000.  Since we are no longer using a zero cs selector
; I have made "jump addresses" to facilitate the moving between the current
; cs selector and the needed 0000 cs selector.

        mov     ReturnAddr.msw,dx         ; Setup jump address for moving back
        mov     dx,offset PELoopTop       ; to 0800:0xxx
        mov     ReturnAddr.lsw,dx
        mov     dx,offset CallPgm         ; Setup jump address for moving to
        add     dx,08000h                 ; 0000:8xxx
        mov     EntryAddr.lsw,dx
        xor     dx,dx
        mov     EntryAddr.msw,dx

    assume es:OS2BOOT

        mov     di,offset es:BootBlkInfo  ; Load in offset of our data area

;       (DS:SI) -> the boot sector's info
;       (ES:DI) -> our BootBlkInfo

        mov     cx,size FatBPBPlus        ; (cx) = length(FatBPBPlus struct)
        push    di                        ; save offset pointer
        rep movsb                         ; move info from boot sector data area
        pop     di                        ; retrieve data area offset

        push    es                        ; copy the value of es into ds for
        pop     ds                        ; use during rest of our work

    assume ds:OS2BOOT,es:nothing

;       (DS:DI) -> our BootBlkInfo
; Determine if we are booting from diskette.  If yes then check the 1st
; hardfile(80) for post-extensions.  If there are post-extensions, load and
; call them.

        push    ds                    ; Save Pointer to our BootBlkInfo
        push    di
        cmp     [BootBlkInfo].DriveNumber,80h ; Are we booting from hardfile??
        jne     CheckMBR              ; No, Lets try to find and run Post-exts
        jmp     ClearCount            ; Yes, posting already done, continue boot
CheckMBR:
        mov     bx,Sec1Seg            ; Load into ES a segment value which
        mov     es,bx                 ; can address our current place in
                                      ; memory 0:7C00 or 07C0:0000
        xor     bx,bx                 ; set offset to 0
        mov     dx,0080h              ; Set head to 0, drive to 80(1st hardfile)
        mov     cx,0001h              ; set cylinder to 0, sector to 1(the MBR)
        call    ReadSect              ; Read hardfile MasterBootRecord
        cmp     ah,0ffh               ; If read failure, we can't run PostExts
        jne     GoodRead
        jmp     ClearCount            ; so jump to the boot code
GoodRead:
        cmp     word ptr es:[bx].ExtBSSig,PostExtSig ; Are there Post extensions
        je      GoodSig                              ; on this hardfile
        jmp     ClearCount                      ; No, just continue with boot
GoodSig:
        mov     ax,word ptr es:[bx].FrstCylSec  ; Get drive/cylinder/head/sector
        mov     NxtUsrSec,ax                    ; location of 1st postext program
        mov     ax,word ptr es:[bx].FrstDrvHead ; and put them into the variable
        mov     NxtUsrHead,ax                   ; locations expected by the loop
PELoopTop:
        mov     cx,NxtUsrSec                    ; Obtain the location of the
        mov     dx,NxtUsrHead                   ; next program
        test    dl,040h                         ; Has the last program already
                                                ; been executed(termination flag)
        jnz     ClearCount                      ; If term flag is set, we are done
                                                ; so continue with boot process
        mov     ax,Sec1Seg                      ; Else read this program 1st
        mov     es,ax                           ; sector into Sec1Seg:0000
        xor     bx,bx
        call    ReadSect
        cmp     ah,0ffh                         ; If read failure, we can't run PostExts
        je      ClearCount                      ; so jump to the boot code
        cmp     word ptr es:[bx].FrstSecSig,PostExtSig ; Is this a valid Post-
                                                ; extension code sector
        jnz     ClearCount                      ; If not, no code to run, so
                                                ; jump to boot code
        mov     ax,word ptr es:[bx].NextCylSec  ; Else retrieve and store
        mov     NxtUsrSec,ax                    ; the drive/cylinder/head/sector
        mov     ax,word ptr es:[bx].NextDrvHead ; of the next postext program
        mov     NxtUsrHead,ax
        mov     cx,word ptr es:[bx].ScndCylSec  ; Load in drive/cyl/head/sector
        mov     dx,word ptr es:[bx].ScndDrvHead ; of program's 2nd sector
        test    dl,040h                         ; Is there a 2nd sector to read in
        jz      Read2In                         ; If yes jump to read in code
        jmp     dword ptr EntryAddr             ; Else jump to call program envelope
Read2In:
        mov     ax,Sec2Seg                      ; Read in Post-ext 2nd sector into
        mov     es,ax                           ; Sec2Seg:0000 which should be
        call    ReadSect                        ; 512 bytes after Sec1Seg:0000
        cmp     ah,0ffh                         ; If read failure, we can't run PostExts
        je      ClearCount                      ; so jump to the boot code
        jmp     dword ptr EntryAddr             ; Jump to call program envelope

; The next four(4) instructions run while the processor is executing this
; with cs=0000

CallPgm:
        mov     ax,ProgStart                    ; Get program start offset
        add     ax,07c00h                       ; Add offset to 7C00, which is
                                                ; where the code was loaded
        push    ds                              ; Save value in DS
        call    ax                              ; call the post extension program
        pop     ds                              ; Restore DS value
        jmp     dword ptr ReturnAddr            ; return to top of readin/call loop
;
; ReadSect - Read one sector into memory from diskette or hardfile
;
;          input: es:bx = Buffer
;                 ch = track to be read
;                 cl = sector number to be read(1-based)
;                 dh = head/cyl to be read
;                 dl = drive to be read
;
;          uses:  ax and flags
;
ReadSect proc
        xor     ax,ax                ; Call reset function on passed in drive
        int     13h
        jnc     ReadinSect           ; If there is a problem, tell caller by
        mov     al,0ffh              ; loading FF into ah and returning
        jmp short RSLeave
ReadinSect:
        mov     ax,0201h             ; Else do a 1 sector read with given info
        int     13h
        jnc     RSLeave              ; If no error, just return
        mov     al,0ffh              ; If there was an error, load ah with FF
RSLeave:                             ; and return to user
        ret
ReadSect endp


; If post code ran and counted up memory, lets clear the screen, since
; the post-extension doesn't.  This code is run even if the post extension code
; has not be called, since the code to determine whether is is needed would be
; complicated.

ClearCount:
        pop     di                   ; Retrieve ds:di, pointer to bootblock
        pop     ds
        push    ds
        pop     es                   ; Have es:bp point to string of blanks
        mov     bp,offset WRTSTRING
        mov     cx,0ch               ; String has a count of 12 characters
        xor     dx,dx                ; Write string at cursur position 0,0
        mov     bx,dx                ; Page Number 0
        mov     ax,01302h            ; Function 13, sub-function 2
        int     10h                  ; write the string to the screen
        xor     ax,ax                ; Let us reset the diskette, since we
        mov     dx,ax                         ; might have done a bunch of
        mov     dl,[BootBlkInfo].DriveNumber  ; hardfile reads
        int     13h
        jnc     FindMem              ; If there is an error , report to the
        jmp     BootErr$he           ; user

; Determine how low memory we have, The micro-loader will use this info
; to load the FAT at the top of low memory.

FindMem:
        int     12h                          ; ax = Kbytes in low mem
        and     ax,0FFF0h                    ; round down to multiple of 16
        shl     ax,6                         ; convert Kbytes to paragraphs
        xchg    ax,bx                        ; save number of paragraphs in bx
        mov     ax,[BootBlkInfo].FatSectors  ; get number of FAT sectors
        mul     [BootBlkInfo].BytesPerSector ; (dx:ax) = FAT sectors * BytesPerSector
        add     ax,PARAMASK                  ; remake ax so that we get no remainder
        adc     dx,0                         ; on the division
        cmp     dx,PARASIZE                  ; If (dx) >= PARASIZE, we'll overflow
        jb      fri30                        ; no overflow problem, continue
        jmp     BootErr$he                   ; jmp to print error if too big

fri30:  mov     cx,PARASIZE                  ; divide dx:ax by bytes per paragraph
        div     cx

;       (DS:DI) -> our BootBlkInfo
;       (AX) = Number of paragraphs for FAT (rounded up)
;       (BX) = end of free memory (in paragraphs)
;       first determine the number of sectors in 64 kbytes of memory

        push    ax                            ; save info currently in ax
        mov     dx,1                          ; have dx:ax = 0001:0000 which equals
        xor     ax,ax                         ; 64k
        div     [BootBlkInfo].BytesPerSector  ; divide by number of bytes/sector
        mov     SecsIn64K,ax                  ; save value for later use
        pop     ax                            ; retrieve # of FAT paragraphs

; In real mode addressing on INTEL processors you can use paragraph values as
; selectors. For example to address physical address 2000, you can either
; put 0000 in ds and 2000 in si or put 200 in ds and 0000 in si.
; Using this as a base we will put the FAT into highest part of low memory
; and then "make" some selectors to address it more easily.

        sub     bx,ax                         ; Beginning FAT para = End -
                                              ;   # of paragraphs need for FAT
        mov     FatSeg1,bx                    ; Save this "selector" value
        cmp     ax,NUMPARAIN64K               ; If FAT Table larger than 64k bytes
        mov     cx,[BootBlkInfo].FatSectors   ; Both paths need this value, so load it now
        jbe     short fri50                   ; Go start FAT read is 64k or less
        add     bx,NUMPARAIN64K               ; "make" 2nd FAT selector using para values
        mov     FatSeg2,bx                    ; Save this selector value
        mov     ax,SecsIn64K                  ; Retrieve sectors/64k value
        sub     cx,ax                         ; Since two reads are necessary,
                                              ; Total - secs/64k = sectors to read
                                              ; in 2nd read operation
        mov     FSg2SecCnt,cx                 ; save this count for later use
        mov     FSg2SecSrt,ax                 ; Since FAT starts at Sector 1(0-based)
                                              ; Sec/64k give starting sector for
                                              ; the second read
        mov     cx,ax                         ; and count of sectors for 1st read
fri50:

; On OS2 FAT volumes the FAT WILL start at sector 1.  We now are setting the
; registers to follow the DoRead convention:
;
; DoRead - read SectorCount sectors into address starting from sector
;          SectorBase.
;
;          input: cl=sectorcount
;                 es:di=address
;                 dx:ax=sectorbase

        xor     dx,dx                   ; Have dx:ax = 1st sector on volume
        mov     ax,1                    ; dx:ax = 0000:0001
        mov     es,FatSeg1              ; memory location to FAT into
        xor     di,di                   ; es:di = FatSeg1:0000
        call    DoRead                  ; Read fat into memory
        mov     ax,FatSeg2              ; Check to see if we need to do
                                        ; another read
        or      ax,ax                   ; yes if FatSeg2 is non-zero
        jz      FatReadIn               ; If zero, then whole FAT is in memory
        mov     es,ax                   ; Else setup es:di to point to memory
        xor     di,di                   ; after 1st part of FAT es:di = FatSeg2:0
        xor     dx,dx                   ; Sectorbase = 0:Fsg2SecSrt+1
        mov     ax,FSg2SecSrt
        inc     ax
        mov     cx,FSg2SecCnt           ; Sectorcount = Fsg2SecCnt
        call    DoRead                  ; Read remaining FAT into memory
;
; The FAT that is currently in Memory can be in two different formats, either
; 12-bit FAT or 16-bit FAT.  Since the FAT allocation scheme uses a link-list
; format, we must be able to decifer the "fat pointers" correctly.  These
; pointers are just the cluster number of the next allocated cluster for the
; file.  If the total number of cluster is less than 0FF7 Hex then we have
; 12-bit FAT, else we have a 16-bit FAT.
;
FatReadIn:
        xor     bx,bx
        mov     cx,[BootBlkInfo].Sectors           ; bx:cx = TS
        or      cx,cx
        jnz     short bdi20                        ; jmp if partition <= 32M
        mov     bx,[BootBlkInfo].SectorsLong.msw
        mov     cx,[BootBlkInfo].SectorsLong.lsw   ; bx:cx = TS

;       (BX:CX) = total sectors on media

bdi20:  sub     cx,[BootBlkInfo].ReservedSectors   ; bx:cx = TS-RS
        sbb     bx,0
        jc      short bdie                         ; too many reserved sectors
        mov     al,[BootBlkInfo].Fats              ; ax = CF
        xor     ah,ah
        mul     [BootBlkInfo].FatSectors           ; dx:ax = CF*SPF
        jc      short bdie                         ; too many FAT sectors
        sub     cx,ax
        sbb     bx,0                               ; bx:cx = TS-RS-(CF*SPF)
        jc      short bdie                         ; too many FAT sectors
        mov     ax,[BootBlkInfo].DirectoryEntries  ; ax = D
        mov     dx,size dir_entry                  ; dx = BPD
        mul     dx                                 ; dx:ax = D*BPD
        div     [BootBlkInfo].BytesPerSector       ; ax = D*BPD/BPS
        or      dx,dx
        jnz     short bdie                         ; uneven # of root dir. sectors
        sub     cx,ax
        sbb     bx,0                               ; bx:cx = TS-RS-(CF*SPF)-(D*BPD/BPS)
        mov     dx,bx
        mov     ax,cx                              ; dx:ax = TS-RS-(CF*SPF)-(D*BPD/BPS)
        mov     cl,[BootBlkInfo].SectorsPerCluster ; cx = SPC
        xor     ch,ch
        div     cx                      ; ax = (TS-RS-(CF*SPF)-(D*BPD/BPS))/SPC
        cmp     ax,4096-10-1            ; is 16-bit FAT?
        jae     short bdi40
        mov     [FatEntrySize],12       ; no - make it a 12-bit FAT
        mov     [EOFClus],0ff7h         ; and reset EOFClus
        jmp     short bdi40

bdie:
        jmp     BootErr$he

;
; Let us try to load in the OS2LDR, first find its directory entry
;   in the root directory
;
; Load in the root directory.
;
bdi40:  mov     es,[BootBlkInfo].RootDirSeg        ; have es:bx = RootDirSeg:0
        xor     di,di                              ; Load directory starting at 0 offset
        mov     cx,[BootBlkInfo].RtDirSecCt
        mov     ax,[BootBlkInfo].RtDirBgSec
        xor     dx,dx                              ; start of DIR
        call    DoRead                             ; Read the root directory into memory
        xor     bx,bx
        mov     cx,[BootBlkInfo].DirectoryEntries  ; put # of root directory
                                                   ;  entries into cx
ScanTop:
        mov     di,bx                              ; put current DirSeg offset into di
        push    cx                                 ; save count of dir entries
                                                   ; left to search
        mov     cx,11                              ; put filename size count into cx
        mov     si,offset IBMBIONAME               ; have ds:si point to LOADER name string
        repe    cmpsb                              ; do compare
        pop     cx                                 ; retrieve dir entry count
        je      ScanEnd                            ; If z flag set, found entry
                                                   ; so jump out of loop
        add     bx,size dir_entry                  ; else have bx point to next entry
        loop    ScanTop                            ; loop will dec cx and jmp to ScanTop
ScanEnd:
        jcxz    LdrMissing                         ; If we get here and cx = 0
                                                   ; then we did not find OS2LDR
                                                   ; dir entry, jmp to error path
        jmp short LdrFound                         ; else continue boot process
LdrMissing:
        jmp     BootErr$bnf                        ; put out loader not found msg

;       (ES:BX) -> the OS2LDR directory entry

LdrFound:
        mov     dx,es:[bx].dir_size_h              ; put OS2LDR file size into
        mov     ax,es:[bx].dir_size_l              ; dx:ax
        div     [BootBlkInfo].BytesPerSector       ; div by # of bytes/sector
        or      dx,dx                              ; dx = remainder, ax = quotient
        jz      NoRemain1                          ; if there is a reminder then
        inc     ax                                 ; add another sector to count for overflow
NoRemain1:
        div     [BootBlkInfo].SectorsPerCluster    ; div by # of sectors/cluster
        or      ah,ah                              ; ah = remainder, al = quotient
        jz      NoRemain2                          ; if there is a remainder then
        inc     al                                 ; add another cluster to count for overflow
NoRemain2:
        xor     ah,ah                              ; clear out ah for later 0 compare of
        mov     LdrClusCnt,ax                      ; this saved cluster count
        mov     dx,es:[bx].dir_firstfile           ; dx = first cluster of the file
        mov     bx,dx
        mov     ax,BiosSeg                         ; Have es:di point to the place
        mov     es,ax                              ; in memory where OS2LDR will
        xor     di,di                              ; be loaded; es:di = BiosSeg:0
        mov     cl,[BootBlkInfo].SectorsPerCluster ; We will at least read in the first
        xor     ch,ch                              ; cluster of the file
        dec     LdrClusCnt

EOFCmp: call    UnPack                             ; si = current cluster number
                                                   ; bx = next cluster number
        cmp     bx,[EOFClus]                       ; Does next cluster number = EOF
        jb      NextClus                           ; No, continue searching down list

; si = last cluster in chain
; bx = EOF marker
; cx = current count of sectors to read in
; dx = starting cluster for the file
;
; The OS2LDR file cannot be bigger than 64k, but since we try to read in whole
; clusters there is a small chance that we would try to do a greater than 64k
; read.  Since at this point we are either reading the entire file or doing
; the last read to get the OS2LDR into memory; we will check to see if we were
; going to try to read an illegal number of bytes in.  If yes, then we will just
; back up to the next to last cluster(the last cluster number holds the EOF).
; And then treat the situation like a discontigous read, i.e. jump to EndOfRun
; to read in the current run.  After the read is done, a jump back to EOFCmp will
; get the last cluster into memory.

        cmp     cx,SecsIn64K                       ; Is # of sectors > 64k
        jbe     FinalRead                          ; no, just do call to clread
        sub     cl,[BootBlkInfo].SectorsPerCluster ; else dec count by one cluster
        inc     LdrClusCnt
        mov     ax,si                              ; and put "current" nummber
                                                   ; into ax for use by EndOfRun
        jmp short EndOfRun
FinalRead:
        call    ClRead                             ; Do Read with Cluster number
                                                   ; as input
        jmp short ReadsDone                        ; OS2LDR is in memory, so
                                                   ; get ready for call to it

NextClus:
        mov     ax,bx                              ; save next cluster number
        sub     bx,si                              ; subtract current from next
        cmp     bx,1                               ; If delta = 1 then clusters
                                                   ; are contigous
        jne     EndOfRun                           ; Else we have hit the end
                                                   ; of a run of contigous clusters
                                                   ; So lets read in this run
        add     cl,[BootBlkInfo].SectorsPerCluster ; else clusters are contigous,
        dec     LdrClusCnt                         ; so add another cluster to
                                                   ; read in sector count
        mov     bx,ax                              ; retrieve next cluster number
        jmp short EOFCmp                           ; do call to find next cluster number

;       (ES:DI) -> current address of memory where sectors will be read into
;       (DX)    -> current starting cluster number of sector run
;       (CL)    -> number of sectors in the current run
EndOfRun:
        push    ax                     ; save the next cluster number
        push    di                     ; save the current offset into the buffer
        push    cx                     ; save the current sector count
        push    es                     ; just in case someone trashes it
        call    ClRead                 ; Do Read, cluster style

; back from the read, update the currents and try to find contigous clusters

        pop     es                                 ; restore es selector
        pop     ax                                 ; put read sector count in ax
        mul     [BootBlkInfo].BytesPerSector       ; mul by bytes/sector
                                                   ; so that dx:ax = bytes read in
        or      dx,dx                              ; if dx != 0, we are messed up
        jz      CalcNewOfs                         ; if 0, continue boot process
        jmp     BootErr$he                         ; if non-0 then print boot error
CalcNewOfs:
        pop     di                                 ; restore current pointer inside
                                                   ; the OS2LDR "memory segment"
        add     di,ax                              ; add bytes read in to it to
                                                   ; obtain new base read in pointer
        mov     cl,[BootBlkInfo].SectorsPerCluster ; We assume we will read in
        xor     ch,ch                              ; the next cluster in the file
        dec     LdrClusCnt
        pop     dx
        mov     bx,dx                              ; Put this next cluster into the
                                                   ; starting cluster reg (dx)
        jmp short EOFCmp                           ; and bx for use by UnPack

ReadsDone:
;   The OS2LDR requires:
;   BX = LSN (relative to the partition) of first sector of cluster # 2
;   DH = 0
;   DL = INT 13 drive number we booted from
;   DS:SI -> the boot media's BPB
;   ES:DI -> root directory
;
        xor     ax,ax                              ; Check the LdrClusCnt variable
        or      ax,LdrClusCnt                      ; to determine if file as
                                                   ; been read completely as defined
                                                   ; by its directory entry
        jz      WholeLdrIn                         ; if 0 , then the answer is yes
                                                   ; so continue boot process
        jmp     BootErr$he                         ; else jump to print error
WholeLdrIn:
        mov     bx,[BootBlkInfo].ClusterBase       ; Get sector offset into the
                                                   ; volume where sectors are
                                                   ; defined by FAT cluster table
        mov     dl,[BootBlkInfo].DriveNumber       ; dl = Int 13 boot drive
        lea     si,BootBlkInfo                     ; ds:si -> BPB
        mov     ax,[BootBlkInfo].RootDirSeg        ; es:di -> root directory
        mov     es,ax
        xor     di,di
        xor     dh,dh                              ; dh = 0 -> FAT boot system
        mov     ax,BiosSeg                         ; Setup return for far call
        push    ax                                 ; to the loader at BiosSeg:0
        xor     ax,ax
        push    ax
        ret                                        ; Do Far return to beginning
                                                   ; the OS2LDR code

BOOTPRC endp


;
; ClRead - Cluster front end for DoRead sector procedure.
;
;          input: cx=sectorcount
;                 es:di=address
;                 dx=starting cluster
;
;          uses:  all regs and flags
;
ClRead  proc
        dec     dx
        dec     dx
        mov     al,[BootBlkInfo].SectorsPerCluster   ; ax = sectors/cluster
        xor     ah,ah
        mul     dx                                   ; dx:ax = starting sector number
        add     ax,[BootBlkInfo].ClusterBase         ; adjust for FATs, root dir, boot sector
        adc     dx,0
        call    DoRead
        ret
ClRead  endp


;***    UnPack - get a FAT entry
;
;       ENTRY   (BX) = FAT entry to lookup
;       EXIT    (SI) = old cluster number: entry (BX)
;               (BX) = new cluster number
;       USES    BX, SI, Flags

UnPack  proc

        push    es
        push    bx                      ; save old cluster
        mov     es,[FatSeg1]            ; Assume we want first part of FAT
        cmp     [FatEntrySize],16       ; Do we have 16 bit FAT's?
        jnz     short upk10

;       The disk has 16 bit FAT's.  Compute the next cluster via the
;       following algorithm
;
;       oldclus = clus;
;       clus = fat[2*clus];

        shl     bx,1                    ; if high bit set, use FatSeg2
        jnc     short upk60             ; We have the correct part of the FAT
        mov     es,[FatSeg2]

upk60:  mov     bx,es:[bx]
        jmp     short upkx

;       The disk we are reading has 12 bit FAT's.  We utilize the following
;       algorigthm to determine the next cluster.
;
;               p = fat[clus+clus/2];
;               if (clus&1) p >>= 4;
;               oldclus = clus;
;               clus = p & 0xfff

upk10:  mov     si,bx
        shr     si,1                    ; if odd, use high 12 bits
        mov     bx,es:[si+bx]
        jnc     short upk20
        shr     bx,4                    ; use high 12 bits
upk20:  and     bx,0fffh

upkx:   pop     si                      ; restore old cluster in (SI)
        pop     es
        ret

UnPack  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,[BootBlkInfo].HiddenSectors.lsw  ; adjust for partition's base sector
;       adc     dx,[BootBlkInfo].HiddenSectors.msw
;       div     [BootBlkInfo].SectorsPerTrack       ; divide sec by sec/trk
;       inc     dl                                  ; remain is sector number, 1-based
;       mov     bl,dl                               ; save current sector
;       xor     dx,dx
;       div     [BootBlkInfo].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,[BootBlkInfo].HiddenSectors.lsw    ; adjust for partition's base sector
        adc     dx,[BootBlkInfo].HiddenSectors.msw
        push    ax                                    ; save lsw of sector number
        mov     ax, [BootBlkInfo].Heads               ; get head count
        mul     byte ptr [BootBlkInfo].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 [BootBlkInfo].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,[BootBlkInfo].SectorsPerTrack
        sub   al,bl
        inc   ax

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

        cmp     cx,ax
        ja      savenum
        mov     ax,cx
savenum:
        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,[BootBlkInfo].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     [BootBlkInfo].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
        ja      drloop
        ret

DoRead  endp

;
; BootErr - print error message and hang the system.
;
BootErr proc
BootErr$bnf:
        mov     si,offset TXT_MSG_SYSINIT_BIO_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

        include os2boot.inc             ; suck in the message text

IBMBIONAME      DB      "OS2LDR     "

; String of blanks to wipe out memory count on screen if it exits

WRTSTRING       DB      20h,07h,20h,07h,20h,07h,20h,07h
                DB      20h,07h,20h,07h,20h,07h,20h,07h
                DB      20h,07h,20h,07h,20h,07h,20h,07h

OS2BOOT   ends
        END     BOOTPRC
