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

        page    ,132
        title   bootdd - boot device driver
        name    bootdd
        .286p

.xlist
include segments.inc    ; segment declarations
include basemaca.inc
include dbprtdef.inc    ; DBPRT macro definition
include filetab.inc
include bpb.inc         ; BPB structure definition
include dirent.inc      ; directory entry definition
include mi.inc
include doshlp.inc
include timer.inc
include basemid.inc
.list

        extrn   DHReadSectors:near
        extrn   DHRealGetMessage:near
        extrn   _BootErrMem:near
        extrn   _BootErrRead:near
        extrn   _BootErrBPB:near


FAT_FILENAME_LEN    equ     11



_DATA segment

        extrn   _filetable:byte

        public _OS2KRNLname, _LDRMSGname, _OS2KRNLIname, _SYSDUMPname
	public _HYBERDATname, _HYBERLDRname
_OS2KRNLname    db      'OS2KRNL',0
_LDRMSGname     db      'OS2LDR.MSG',0
_OS2KRNLIname   db      'OS2KRNLI',0
_SYSDUMPname    db      'OS2DUMP',0
_HYBERDATname	db	'SWAPPER2.DAT',0
_HYBERLDRname	db	'HYBERLDR',0

ifdef OS2MEM
        public _OS2MEMname
_OS2MEMname     db      'OS2MEM',0
endif

dsklabel        db      'DISK 1     '
FatEntrySize    db      16              ; assume 16 bits in FAT entry
EOFClus         dw      0fff7h          ; assume 16-bit FAT EOF cluster

_DATA ends

ifdef OS2MEM
NUM_DIR_ENTRY   EQU     6
else
NUM_DIR_ENTRY   EQU     5
endif


_BSS segment

        public  BootBPB, BootDrive
        public  CurrentSector, CurrentHead, CurrentTrack
        public  SectorCount, SectorBase
        public  LastSeg
LastSeg         dw      ?       ; Upper limit of BootDD-usable memory
BootBPB         a_BPB   <>      ; Boot bpb
DirEntryArray   dir_entry NUM_DIR_ENTRY DUP (<>) ; directory entry array
CurrentDir      dir_entry<>     ; current directory entry (opened file)
BootDrive       db      ?       ; Boot drive
ClusterBase     dw      ?       ; First sector of data
CurrentSector   db      ?       ; Sector for read
CurrentHead     db      ?       ; Head..
CurrentTrack    dw      ?       ; Cylinder
SectorCount     dw      ?       ; Number of sectors for this operation.
SectorBase      dd      ?
FatSeg1         dw      ?       ; Loading point for first part of FAT
FatSeg2         dw      ?       ; Loading point for second part of FAT if any

_BSS ends



_TEXT segment

    assume cs:DGROUP



;***    BootDDInit - save boot parameters, locate kernel
;
;       Called from the boot loader to save all of the boot parameters.
;       Find directory entry for OS2KRNL or OS2KRNLI
;
;       ENTRY   (BX)    = LSN (relative to the partition) of the first
;                         sector of cluster # 2
;               (DL)    = int 13 drive number we booted from
;               (DH)    = boot flags
;               (DS:SI) -> the boot media's BPB
;               (ES:DI) -> root directory
;       EXIT    (AX)    = 0  if boot objects all reside on one disk
;                       = 1  if boot requires disk swap (Two disk boot)
;       USES    AX, ES, Flags

Procedure BootDDInit
    assume ds:nothing,es:nothing,ss:DGROUP
    LocalVar bdi_rootdir,dword
        EnterProc <es,di>               ; bdi_rootdir
        SaveReg <bx,cx,dx,di,si,bp>
        push    ds

;       DBPRT   16,1,<Init: bx=$x dx=$x ds:si=$x:$x es:di=$x:$x\n>,<bx,dx,ds,si,es,di>

        mov     [ClusterBase],bx        ; save sector number of first cluster
        mov     [BootDrive],dl          ; save boot drive

        test    dh,BF_NOMFSHVOLIO       ; BPB available?
        jz      short bdi10             ; jump if so
bdixj:  xor     ax,ax                   ; else single disk boot (RIPL/BIFS)
        jmp     bdix                    ; exit if no BPB available

bdi10:  push    ss                      ; (es:di) -> [BootBPB]
        pop     es
    assume es:DGROUP
        mov     di,offset DGROUP:BootBPB

;       (DS:SI) -> the boot media's BPB
;       (ES:DI) -> DGROUP:BootBPB

        mov     cx,size a_BPB           ; (cx) = length(bpb)
;       DBPRT   16,1,<Init: Copy BPB $x:$x ($B31)\n>,<ds,si,ds,si>
        .errnz  (size a_BPB - 31)
        push    di
        rep movsb
        pop     di

        push    ss
        pop     ds
    assume ds:DGROUP

        xor     bx,bx
        mov     cx,[di].BPB_TotalSectors        ; bx:cx = TS
        or      cx,cx
        jnz     short bdi20                     ; jmp if partition <= 32M
        mov     bx,[di].BPB_BigTotalSectors.hiword
        mov     cx,[di].BPB_BigTotalSectors.loword ; bx:cx = TS

;       (BX:CX) = total sectors on media
;       (DS:DI) -> DGROUP:BootBPB

bdi20:  sub     cx,[di].BPB_ReservedSectors ; bx:cx = TS-RS
        sbb     bx,0
        jc      short bdie              ; too many reserved sectors
        or      dh,dh
        jnz     short bdixj             ; exit if non-FAT boot

;       Is a FAT-specific boot sequence.  Verify the BPB and search
;       the root directory for the kernel.

        mov     al,[di].BPB_NumberOfFATs; ax = CF
        xor     ah,ah
        mul     [di].BPB_SectorsPerFAT  ; 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,[di].BPB_RootEntries ; ax = D
        mov     dx,size dir_entry       ; dx = BPD
        mul     dx                      ; dx:ax = D*BPD
        div     [di].BPB_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,[di].BPB_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:
        DBPRT   -1,2,<Init: bad BPB\n>
        jmp     _BootErrBPB

bdi40:  mov     cx,[di].BPB_RootEntries
        les     di,[bdi_rootdir]        ; ES:DI - root directory
    assume es:nothing
        push    ss                      ; DX:AX - DGROUP:DirEntryArray
        pop     dx
        mov     ax,offset DirEntryArray

;       find the OS2LDR.MSG directory entry

        mov     si, offset _LDRMSGname  ; DS:SI - file name
        call    FindDirEntry
        jc      short bdi42             ; File not found - continue with
                                        ; boot but Don't move to next dir
                                        ; entry

        add     ax,size dir_entry       ; point to next dir entry

bdi42:
;       find the HYBERLDR directory entry

        mov     si, offset _HYBERLDRname    ; DS:SI - file name
        call    FindDirEntry
        jc      short bdi43             ; File not found - continue with
                                        ; boot but Don't move to next dir
                                        ; entry

        add     ax,size dir_entry       ; point to next dir entry


bdi43:
;       find the HYBERNAT.DAT directory entry

        mov     si, offset _HYBERDATname    ; DS:SI - file name
        call    FindDirEntry
        jc      short bdi44             ; File not found - continue with
                                        ; boot but Don't move to next dir
                                        ; entry

        add     ax,size dir_entry       ; point to next dir entry

bdi44:
ifdef OS2MEM
;       find the OS2MEM directory entry

        mov     si, offset _OS2MEMname  ; DS:SI - file name
        call    FindDirEntry
        jc      short bdi45             ; File not found - continue with
                                        ; boot but Don't move to next dir
                                        ; entry

        add     ax,size dir_entry       ; point to next dir entry
bdi45:
endif
;       find the OS2DUMP directory entry

        mov     si, offset _SYSDUMPname ; DS:SI - file name
        call    FindDirEntry
        jc      short bdi46             ; File not found - continue with
                                        ; boot but Don't move to next dir
                                        ; entry

        add     ax,size dir_entry       ; point to next dir entry
bdi46:

;       find the OS2KRNL directory entry

        mov     si, offset _OS2KRNLname ; DS:SI - file name
        call    FindDirEntry
        jc      short bdi50             ; OS2KRNL not found - look
                                        ; for OS2KRNLI

        xor     ax,ax                   ; ax = 0, OS2KRNL found
        jmp     short bdix

;
; Did not find OS2KRNLname.  This may be a two-disk boot operation.
; Search for OS2KRNLIname to find out.
;
;       find the OS2KRNLI directory entry

bdi50:
        mov     si, offset _OS2KRNLIname
        call    FindDirEntry
        jc      short bdi60             ; OS2KRNL not found

        mov     ax,1                    ; ax = 1, OS2KRNLI found
        jmp     short bdix

; Did not find OS2KRNLname or OS2KRNLIname.  Cannot boot this media.
; Return as if OS2KRNL was found.  When OS2KRNL is opened, it will
; not be found and the appropriate error will be displayed

bdi60:
        xor     ax,ax                   ; Specify that OS2KRNL found

bdix:   pop     ds
    assume ds:nothing

        RestoreReg <bp,si,di,dx,cx,bx>
        LeaveProc

        ret
EndProc BootDDInit



;***    FindDirEntry - Find directory entry for given file name
;
;       Called to find the specified directory entry in the given
;       directory.  When found, the directory entry is copied to
;       the given location.
;
;       ENTRY   ES:DI - Directory to search for entry
;               CX    - Number of entries in directory
;               DS:SI - File name to search for entry (NULL terminated)
;               DX:AX - Destination directory entry
;

Procedure FindDirEntry
    assume ds:nothing,es:nothing,ss:DGROUP
    LocalVar fde_dir_entry,dword
    LocalVar fde_file_name,word
    LocalVar fde_fat_name,FAT_FILENAME_LEN
        EnterProc <dx,ax,si>
        SaveReg <es,ds,bx,cx,si,di>

        lea     bx,[fde_fat_name]
        call    MakeFatName             ;Make FAT name out of NULL terminated
                                        ;file name

        mov     bx,di
fde10:  mov     di,bx
        push    cx                      ;Save directory entry count
        mov     cx,FAT_FILENAME_LEN     ;Length of file name
        lea     si,[fde_fat_name]       ;Get pointer to file name
        repz cmpsb                      ;compare directory entry to
                                        ;  requested file name
        pop     cx                      ;Restore directory entry count
        jz      short fde70             ;directory entry contains the
                                        ;  correct file name - continue
        add     bx,size dir_entry       ;NO - try the next directory entry
        loop    fde10                   ;Continue until all directory entries
                                        ;  have been searched
        stc                             ;File name not found in directory
        jmp     short fde90             ;  set carry and exit


fde70:  push    es                      ; (DS:SI) = directory entry
        pop     ds
    assume ds:nothing
        mov     si,bx
        les     di,[fde_dir_entry]      ; (ES:DI) = destination entry
    assume es:DGROUP
        mov     cx,size dir_entry
;       DBPRT   16,1,<Init: Copy dir $x:$x ($B32)\n>,<ds,si,ds,si>
        .errnz  (size dir_entry - 32)
        rep movsb                       ; Copy directory entry for requested
                                        ;   file name into the destination
                                        ;   directory entry

        clc                             ; File found - clear carry

fde90:
        RestoreReg <di,si,cx,bx,ds,es>
        LeaveProc
        ret
EndProc FindDirEntry



;***    MakeFatName - convert null terminated file name into fat entry name
;
;       Takes the null terminated file name and creates a string representing
;       the file name in a FAT directory entry.  The entry is 11 characters
;       with no separation between the 8.3 file name.
;
;       ENTRY:  DS:SI   -   Null terminated file name
;               SS:BX   -   Pointer to save new FAT file name
;
;       EXIT:   SS:BX   -   Contains new FAT file name
;
;       NOTE    The destination buffer must be at least 11 characters
;               in size
;
;               Assumes that if a '.' exists in the null terminated file
;               name, there is at least on character following the '.'
;               before the final NULL

Procedure MakeFatName
    assume ds:nothing,es:nothing,ss:DGROUP
        EnterProc
        SaveReg <ax,cx,dx,si,di,es>

        mov     cx,ss
        mov     es,cx                   ; setup destination pointer
        mov     di,bx

        ; Fill destination with spaces
        mov     cx,11
        mov     al,' '
        cld                             ; process strings forwards
        rep     stosb                   ; blank file name

        mov     di,bx                   ; restore destination pointer

        ; Copy first 8 characters of file or until '.'
        mov     cx,8
        mov     dh,'.'                  ; separator

        lodsb                           ; get first character of file name
mfn10:
        or      al,al                   ; is it NULL
        jz      short mfn40             ; yes - finished with conversion
        cmp     al,dh                   ; is it '.'
        jne     short mfn25             ; no - process character
        inc     di                      ; yes - increment destination pointer
                                        ;   and skip processing of character
        jmp     short mfn27             ;   but continue with loop
mfn25:
        stosb                           ; write character to destination
        lodsb                           ; get next character of file name
mfn27:
        loop    short mfn10             ; loop until all first 8 characters
                                        ; of destination are processed

        ; Copy last 3 characters of file or until NULL
        mov     cx,3
        cmp     al,dh                   ; Did we find a separator '.'
        jne     short mfn35             ; no - we already have next character
                                        ; so skip lodsb

mfn30:
        lodsb                           ; get next character
mfn35:
        or      al,al                   ; is it NULL
        jz      mfn40                   ; yes - finished with conversion
        stosb                           ; no - write character to destination
        loop    mfn30                   ;   and continue with loop

mfn40:
        RestoreReg <es,di,si,dx,cx,ax>
        LeaveProc
        ret
EndProc MakeFatName



;***    _FatReadInit - initialize in preparation for reading the kernel
;
;       Called from the boot loader to set up for subsequent kernel reads.
;
;       ENTRY   [fri_endloc]    = paddr of high end of free low memory
;               (DS)            = DGROUP
;       USES    AX, BX, CX, DX, ES, Flags

Procedure _FatReadInit
    assume ds:DGROUP,es:nothing,ss:DGROUP
    ArgVar fri_endloc,dword
        EnterProc
        push    si
        push    di

        mov     bx,[fri_endloc].loword  ; (ax:bx) = end of free memory
        mov     ax,[fri_endloc].hiword
        and     bx,not PARAMASK         ; (bx) = end of memory in paragraphs
        or      bx,ax
        ror     bx,PARASHIFT
        mov     [LastSeg],bx
;       DBPRT   16,2,<ReadInit: LastSeg=$x\n>,<bx>

;       Get size of fat (sectors/fat * bytes/sector)

        mov     ax,[BootBPB].BPB_SectorsPerFAT
        mul     [BootBPB].BPB_BytesPerSector    ; (dx:ax) = bytes in FAT
        add     ax,PARAMASK
        adc     dx,0
        cmp     dx,PARASIZE             ; If (dx) >= PARASIZE, we'll overflow
        jb      short fri30             ; jmp if not too big
        jmp     _BootErrRead

fri30:  mov     cx,PARASIZE
        div     cx

;       (AX) = Number of paragraphs for FAT (rounded up)
;       (BX) = end of free memory (in paragraphs)

        sub     bx,ax
        mov     [FatSeg1],bx
;       DBPRT   16,2,<ReadInit: FatSeg1=$x\n>,<bx>
        mov     [FatSeg2],0             ; assume <= 64k worth of FAT data
        cmp     ax,NUMPARAIN64K
        jbe     short fri50
        add     bx,NUMPARAIN64K         ; point to 2nd 64k of FAT data
        mov     [FatSeg2],bx            ; save paragraph of 2nd 64k of FAT data
;       DBPRT   16,2,<ReadInit: FatSeg2=$x\n>,<bx>

fri50:  call    GetFat                  ; read in the FAT sectors

        ; Return address of read function

        mov     si,offset _filetable.ft_muRead
        mov     [si].loword,offset DGROUP:FatRead
        mov     [si].hiword,cs

        ; Return address of open function

        mov     si,offset _filetable.ft_muOpen
        mov     [si].loword,offset DGROUP:FatOpen
        mov     [si].hiword,cs

        ; Return address of close function

        mov     si,offset _filetable.ft_muClose
        mov     [si].loword,offset DGROUP:FatClose
        mov     [si].hiword,cs

        ; Return address of terminate function

        mov     si,offset _filetable.ft_muTerminate
        mov     [si].loword,offset DGROUP:FatTerminate
        mov     [si].hiword,cs

        ; Return new end of low memory

        mov     si,offset _filetable.ft_museg
        mov     ax,[FatSeg1]            ; new low memory end (seg)
        mov     [si],ax

;       DBPRT   16,2,<ReadInit: FAT read, NextFree Segment=$x\n>,<ax>
        pop     di
        pop     si
        LeaveProc
        ret
EndProc _FatReadInit


;***    FatOpen - Open file for successive reads by FatRead
;
;       Called *FAR* from the boot loader for file open.
;
;       ENTRY   [fto_filename] = 16:16 pointer to file name
;               [fto_pcbfile]  = 16:16 pointer to size of file (returned)
;
;       EXIT    AX - <>0 if file is not found
;               AX - 0 on success (file requested for open
;                    will be accessed with calls to FatRead
;
;       NOTE    The action of this function is to place the directory
;               entry for the requested file in the current directory
;               entry structure used by FatRead

Procedure FatOpen,far,local
    assume ds:nothing,es:DGROUP,ss:DGROUP
    ArgVar  fto_filename,dword
    ArgVar  fto_pcbfile,dword
        EnterProc
        SaveReg <ds,es,si,di>

;       Find and copy the directory entry for the file name passed,
;       if the file is not found, return with AX <> 0

        mov     dx,ss
        mov     es,dx
        mov     ax,OFFSET CurrentDir
        mov     di,OFFSET DirEntryArray
        mov     cx,NUM_DIR_ENTRY
        lds     si,[fto_filename]
        call    FindDirEntry                ; Search for file in the
                                            ;   DirEntryArray copying
                                            ;   the found directory
                                            ;   entry into CurrentDir
        jnc     short fto10                 ; continue if file is found
        mov     ax,1                        ; set up to return an error
        jmp     short fto20

;       File is found, Return the size of the file in [fto_pcbfile]

fto10:
        lds     si,[fto_pcbfile]
        mov     ax,[CurrentDir].dir_size_l
        mov     [si].loword,ax
        mov     ax,[CurrentDir].dir_size_h
        mov     [si].hiword,ax
        xor     ax,ax                       ; file is found
fto20:
        RestoreReg <di,si,es,ds>
        LeaveProc
        ret
EndProc FatOpen



;***    FatClose - Close currently open file
;***    FatTerminate - Terminate use of Micro File System
;
;       Called *FAR* from the boot loader for file close.
;
;       ENTRY   NONE
;
;       EXIT    NONE
;
;       NOTE    There is no action needed when this function is
;               called, just return.

Procedure FatClose,far,local
    assume ds:nothing,es:DGROUP,ss:DGROUP

Entry   FatTerminate,far,local

        ret

EndProc FatClose




;***    FatRead - read in a portion of the a file
;
;       Called *FAR* from the boot loader for file reads.
;
;       ENTRY   [ftr_offset] = seek offset
;               [ftr_pbuf]   = 16:16 pointer to read buffer
;               [ftr_count]  = 32 bit request size
;               (DS)         = DGROUP
;       EXIT    (DX:AX) = size actually read
;       USES    AX, BX, CX, DX, ES, Flags

Procedure FatRead,far,local
    assume ds:DGROUP,es:nothing,ss:DGROUP
    ArgVar ftr_offset,dword
    ArgVar ftr_pbuf,dword
    ArgVar ftr_count,dword

    LocalVar ftr_actual,dword
    LocalVar ftr_cluster,word
    LocalVar ftr_remain,dword

        xor     ax,ax
        EnterProc <ax,ax,ax>            ; ftr_actual, ftr_cluster
        push    si
        push    di

;       DBPRT   17,1,<FatRd\n>

        test    [ftr_offset].loword,SECTMASK    ; test seek offset alignment
        jnz     short ftre2
        test    [ftr_count].loword,SECTMASK     ; test count alignment
        jnz     short ftre3
        cmp     [ftr_pbuf].loword,0             ; test buffer alignment
        jz      short ftr00
        DBPRT   17,1,<BRd1\n>
ftre2:  DBPRT   17,1,<BRd2\n>
ftre3:  DBPRT   17,1,<BRd3\n>
ftre:   jmp     _BootErrRead

ftr00:  mov     dx,[ftr_offset].loword          ; (cx:dx) = seek offset
        mov     cx,[ftr_offset].hiword
        mov     di,[ftr_count].loword           ; (si:di) = count
        mov     si,[ftr_count].hiword
        add     dx,di                           ; (cx:dx) += count
        adc     cx,si

        mov     bx,[CurrentDir].dir_size_l      ; (ax:bx) = file size
        mov     ax,[CurrentDir].dir_size_h
        cmp     cx,ax                           ; is seek+count > filesize?
        jnz     short ftr10
        cmp     dx,bx
ftr10:  jbe     short ftr30                     ; jmp if not

        sub     dx,bx                           ; (cx:dx) = seek+count - size
        sbb     cx,ax
        sub     di,dx                           ; count -= overrun (cx:dx)
        sbb     si,cx

ftr30:  mov     [ftr_remain].loword,di          ; remain = count
        mov     [ftr_remain].hiword,si

ftr40:;DBPRT   17,1,<Read: off=$x$x buf=$x:$x cb=$x$x rem=$x$x\n>, \
;                   <<[ftr_offset].hiword>,<[ftr_offset].loword>, \
;                    <[ftr_pbuf].hiword>,<[ftr_pbuf].loword>, \
;                    <[ftr_count].hiword>,<[ftr_count].loword>, \
;                    <[ftr_remain].hiword>,<[ftr_remain].loword>>

        mov     ax,[ftr_remain].loword  ; (bx:ax) = remain
        mov     bx,[ftr_remain].hiword
        add     ax,SECTMASK             ; round up to SECTSIZE boundary
        adc     bx,0
        and     ax,not SECTMASK
        or      ax,bx                   ; (ax) = sector count
        ror     ax,SECTSHIFT

        mov     bx,[ftr_cluster]        ; (bx) = cluster

        mov     cx,[ftr_offset].loword  ; (cx) = 0-based sector file offset
        or      cx,[ftr_offset].hiword
        ror     cx,SECTSHIFT

        mov     dx,[ftr_pbuf].hiword    ; (dx) = buffer paragraph address

;       ax=csec read bx=nextclus = ReadKernel(ax=csec, bx=clus, cx=sec, dx=buf)

        call    ReadKernel

        mov     [ftr_cluster],bx        ; save next cluster
        shl     ax,SECTSHIFT-PARASHIFT  ; log2(sector size) - log2(para size)
        add     [ftr_pbuf].hiword,ax    ; bump buffer paragraph address
        rol     ax,PARASHIFT            ; (bx:ax) = size actually read
        mov     bx,ax
        and     bx,SECTMASK
        and     ax,not SECTMASK

        cmp     [ftr_remain].hiword,bx  ; if request count < size read
        jnz     short ftr48
        cmp     [ftr_remain].loword,ax
ftr48:  jae     short ftr50
        mov     ax,[ftr_remain].loword  ; (bx:ax) = partial read size
        mov     bx,[ftr_remain].hiword

ftr50:  add     [ftr_offset].loword,ax  ; bump seek offset
        adc     [ftr_offset].hiword,bx
        add     [ftr_actual].loword,ax  ; bump actual count
        adc     [ftr_actual].hiword,bx
        sub     [ftr_remain].loword,ax  ; reduce count remaining
        sbb     [ftr_remain].hiword,bx
        jnz     short ftr40j            ; jmp if more to read
        cmp     [ftr_remain].loword,0
        jz      short ftr90             ; jmp if no more to read
ftr40j: jmp     ftr40                   ; go read the rest

ftr90:  mov     ax,[ftr_actual].loword  ; (dx:ax) = actual count
        mov     dx,[ftr_actual].hiword

;       DBPRT   17,1,<Read: actual=$x$x\n>,<dx,ax>

        pop     di
        pop     si
        LeaveProc
        ret
EndProc FatRead



;***    ReadKernel - Read a contiguous block of sectors
;
;       ENTRY   (AX) = sector count
;               (BX) = next cluster to read, 0 if unknown
;               (CX) = 0-based sector offset within file to read
;               (DX) = I/O buffer address (paragraph address)
;       EXIT    (AX) = count of sectors actually read
;               (BX) = next cluster to read
;       USES    AX, BX, CX, DX, SI, DI, ES, Flags
;
;       ALGORITHM:
;               1) Get the number of the first cluster of OS2 from
;                  the directory sector that BOOT left at RootDir
;               2) convert offset to logical sector
;               3) read in sector

Procedure ReadKernel
    assume ds:DGROUP,es:nothing,ss:DGROUP
    LocalVar rdk_csec,word
    LocalVar rdk_cluster,word
    LocalVar rdk_pbuf,word
        EnterProc <ax,bx,dx>            ; rdk_csec,rdk_cluster,rdk_pbuf

;       DBPRT   17,1,<ReadK: csec=$x clus=$x firstsec=$x buf=$x:0\n>, \
;                    <ax,bx,cx,dx>

        or      ax,ax
        jz      short rdke              ; non-zero sector count only

;       Convert file-relative sector offset to cluster-relative sector offset.
;
;       (BX) = next cluster (or 0 if unknown)
;       (CX) = 0-based sector offset (relative to the start of the file)

        xor     dx,dx                   ; (dx:ax) = 0-based sector offset
        mov     ax,cx
        xor     ch,ch                   ; (cx) = sectors/cluster
        mov     cl,[BootBPB].BPB_SectorsPerCluster
        div     cx

;       (AX) = 0-based cluster offset (quotient)
;       (BX) = next cluster (or 0 if unknown)
;       (DX) = sector # within cluster to read (remainder)

        or      bx,bx                   ; starting cluster given?
        jnz     short rdk25             ; jmp if so, skip UnPack loop.

        mov     cx,ax                        ; (cx) = # of clusters to skip
        mov     bx,[CurrentDir].dir_firstfile ; (bx) = first cluster in file

;       Skip clusters up to the starting point.
;
;       (BX) = start cluster number
;       (CX) = number of clusters to skip
;       (DX) = sector # within cluster to read

;       DBPRT   17,1,<ReadK: clus=$x skip=$x clsec=$x\n>,<bx,cx,dx>

        jcxz    short rdk25

rdk20:  call    UnPack                  ; (bx)=nextcluster = Unpack(bx=cluster)
        loop    rdk20

;       Set up to count contiguous sectors.
;
;       (BX) = cluster number of first sector to read
;       (DX) = sector # within cluster to read

rdk25:  push    dx              ; save sector number
        push    bx              ; save start cluster number
        xor     ch,ch
        mov     cl,[BootBPB].BPB_SectorsPerCluster
        neg     dx              ; (dx) = contiguous sectors in start cluster
        add     dx,cx
        mov     ax,[rdk_csec]   ; (ax) = number of sectors requested

;       ((SP+2)) = cluster-relative sector number
;       ((SP))   = start cluster number
;       (AX)     = number of sectors requested
;       (BX)     = cluster number of last contiguous cluster found so far
;       (CX)     = sectors per cluster
;       (DX)     = number of contiguous sectors found so far

;       DBPRT   17,1,<ReadK: csec=$x clus=$x sec/clus=$x cseccont=$x\n>, \
;                    <ax,bx,cx,dx>

rdk30:  cmp     dx,ax           ; enough contiguous sectors to satisfy request?
        jae     short rdk40
        call    UnPack          ; (SI) = (BX), (BX) = next allocation unit
        sub     si,bx
        cmp     si,-1           ; one apart?
        jnz     short rdk40     ; jmp if next cluster is not contiguous
        add     dx,cx
        jmp     short rdk30

rdke:   jmp     _BootErrRead

rdk40:  cmp     [EOFClus],bx    ; IsEof(bx=cluster)
        jz      short rdke      ; jmp if EOF
        mov     [rdk_cluster],bx ; save next cluster
;       DBPRT   17,1,<ReadK: clus=$x clsec=$x\n>,<bx,dx>

        cmp     dx,ax           ; enough contiguous sectors to satisfy request?
        jae     short rdk50     ; jmp if so
        mov     ax,dx           ; else (ax) = number of contiguous sectors
rdk50:  mov     si,ax           ; (si) = number of sectors to read

;       Convert cluster number to starting logical sector
;
;       ((SP+2)) = cluster-relative sector number
;       ((SP))   = start cluster number
;       (CX)     = sectors per cluster
;       (SI)     = number of sectors to read

        pop     ax                      ; (ax) = cluster number - 2
        dec     ax
        dec     ax
        mul     cx                      ; (dx:ax) = start logical sector
        add     ax,[ClusterBase]        ; (dx:ax) += 1st data sector
        adc     dx,0
        pop     bx                      ; (bx) = cluster-relative sector number
        add     ax,bx                   ; (dx:ax) = requested sector number
        adc     dx,0

;       (DX:AX) = start logical sector number
;       (BX)    = number of sectors to read
;       (SI)    = number of sectors to read

        mov     es,[rdk_pbuf]           ; (es:0) points to load location
    assume es:nothing
        mov     cx,si                   ; (cx) = sector count

;       GetClus(dx:ax=logicalsector,cx=count,es:0=buffer)

;       DBPRT   17,1,<ReadK: lsec=$x$x csec=$x DTA=$x:0\n>,<dx,ax,cx,es>

        push    cx                      ; save actual sector count
        call    GetClus                 ; read in contiguous sectors
        pop     ax                      ; (ax) = actual sector count
        mov     bx,[rdk_cluster]        ; (bx) = next cluster

;       DBPRT   17,1,<ReadK: Done csec=$x nextclus=$x\n>,<ax,bx>

        LeaveProc
        ret
EndProc ReadKernel




;***    GetFat - Read the FAT into FatSeg1
;
;       ENTRY   NONE
;       EXIT    NONE
;       USES    AX, CX, DX, DI, Flags

Procedure GetFat
    assume ds:DGROUP,es:nothing,ss:DGROUP
        xor     dx,dx
        mov     ax,1                    ; Relative sector (1st sector of disk)
        mov     cx,[BootBPB].BPB_SectorsPerFAT   ; Read N sectors.
        mov     es,[FatSeg1]            ; location to read
    assume es:nothing
        xor     di,di                   ; es:di points to read data buffer
;       DBPRT   64,2,<GetFat: Reading $x FAT sectors into $x:$x\n>,<cx,es,di>
        push    bx
        mov     bx,[BootBPB].BPB_BytesPerSector ; size of last sector to read
        push    cs              ; to allow for the far return to a near func
        call    DHReadSectors
        pop     bx
        jnc     short gftx
        jmp     _BootErrRead

gftx:   ret
EndProc GetFat



;***    GetClus - Read contiguous sectors
;
;       ENTRY   (CX)    = sector count
;               (DX:AX) = logical sector to read
;               (ES:0)  = transfer address
;       EXIT    NONE
;       USES    AX, BX, CX, DX, SI, DI, ES, Flags

Procedure GetClus
    assume ds:DGROUP,es:nothing,ss:DGROUP
        mov     bx,cx           ; (bx) = sector count

;       Convert sector count to paragraph count and compute DTA end address,
;       to make sure we won't write over the FAT.

        test    bx,-1 shl (16 - (SECTSHIFT-PARASHIFT))
        jnz     short gcle2
        shl     bx,SECTSHIFT-PARASHIFT  ; log2(sector size) - log2(para size)
        mov     si,es
        add     bx,si
;       DBPRT   17,2,<GetClus: read up to $x:0\n>,<bx>
        jc      short gcle
        cmp     bx,[FatSeg1]
        jbe     short gcl40

gcle:   DBPRT   -1,2,<GetClus: overlayed the FAT\n>
        jmp     _BootErrMem

gcle2:  DBPRT   -1,2,<GetClus: request too large\n>
        jmp     _BootErrMem

;       (CX)    = sector count
;       (DX:AX) = start sector to read
;       (ES:0)  = DTA

gcl40:  xor     di,di           ; es:di points to read data buffer
        mov     bx,[BootBPB].BPB_BytesPerSector
        push    cs              ; to allow for the far return to a near func
        call    DHReadSectors
        jnc     short gclx
        jmp     _BootErrRead

gclx:   ret
EndProc GetClus



;***    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, ES, Flags

Procedure UnPack
    assume ds:DGROUP,es:nothing,ss:DGROUP
        push    bx                      ; save old cluster
        mov     es,[FatSeg1]            ; Assume we want first part of FAT
    assume es:nothing
        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:
;       DBPRT   128,1,<UnPack: clus=$x\n>,<bx>
        cmp     [EOFClus],bx            ; IsEof(bx=cluster)
        jz      short upke              ; jmp if EOF
        pop     si                      ; restore old cluster in (SI)
        ret

upke:   jmp     _BootErrRead
EndProc UnPack


_TEXT ends
        end
