;       SCCSID = @(#)fat.asm	6.2 91/04/29
TITLE   FAT - FAT maintenance routines
NAME    FAT

SCCSID  equ     "@(#)fat.asm	6.2 91/04/29"
;
; Low level local device routines for performing disk change sequence,
;   setting cluster validity, and manipulating the FAT
;
;       Global          Local
;
;       IsEOF
;       UnPack
;       Pack
;                       MapCluster
;

;*      File Allocation Table Information
;
; The File Allocation Table uses a 12-bit entry for each allocation unit on
; the disk.  These entries are packed, two for every three bytes.  The contents
; of entry number N is found by 1) multiplying N by 1.5; 2) adding the result
; to the base address of the Allocation Table; 3) fetching the 16-bit word
; at this address; 4) If N was odd (so that N*1.5 was not an integer), shift
; the word right four bits; 5) mask to 12 bits (AND with 0FFF hex).  Entry
; number zero is used as an end-of-file trap in the OS and is passed to the
; BIOS to help determine disk format.  Entry 1 is reserved for future use.
; The first available allocation unit is assigned entry number two, and even
; though it is the first, is called cluster 2.  Entries greater than 0FF8H
; (12-bit fats) or 0FFF8H (16-bit fats) are end of file marks; entries of zero
; are unallocated.  Otherwise, the contents of a FAT entry is the number of
; the next cluster in the file.
;
; Clusters with bad sectors are tagged with FF7H.  Any non-zero number would
; do because these clusters show as allocated, but are not part of any
; allocation chain and thus will never be allocated to a file.  A particular
; number is selected so that disk checking programs know what to do (ie.  a
; cluster with entry FF7H which is not in a chain is not an error).

; ************************* BIG FAT WARNING ***********************************
;
; There is a lot of mucking around with bp in this file. It is used as both
; a pointer and a stack frame base. Be very careful if you make any changes.
; You've been warned...Let's be careful out there...
;

;
; get the appropriate segment definitions
;
.xlist
include dosseg.inc
include basemaca.inc
include osmaca.inc
include fnofs.inc
include oldmaca.inc
.xcref
include dossymc.inc
include error.inc
include buffer.inc
include harderr.inc
include tkexport.inc
include vpb.inc
include mi.inc
include protmode.inc
.cref
.list

        ModName , FNO_FAT_ASM

        ReferTask   CurrTCB,WORD        ; Current thread control block
        ReferTask   ExtErr,WORD

        ReferGDT        GDT_VPB,WORD

        EXTRNFAR        RlseBuffr
        EXTRNFAR        GetBuffrB

        EXTRNFAR        HardError

        FARCODE HIGH4
HIGH4CODE SEGMENT
    ASSUME  SS:TASKAREA


Break <IsEOF - check the quantity in BX for EOF>

;***    IsEOF - check the FAT value in BX for EOF.
;
;       ENTRY   ES:BP points to VPB
;               BX has fat value
;       EXIT    JAE eof
;       USES    Flags

Procedure IsEOF,<hybrid,far16>
    ASSUME  DS:NOTHING,ES:NOTHING

        chkvselhd es,bp
        Assert      ISVPB,<ES,BP>,"IsEOF"
;;      CMP     ES:[bp].vpb_fsd.vpdFAT_max_cluster,4096-10 ; is this 16 bit fat?   ;@@
;;      JAE     EOF16                   ; yes, check for eof there
        cmp     es:[bp].vpb_fsd.vpdFAT_FATentrysize,16                             ;@@
        je      EOF16
        CMP     BX,0FF8h                ; do the 12 bit compare
        ret
EOF16:
        CMP     BX,0FFF8h               ; 16 bit compare
        ret
EndProc IsEOF

Break   <UnPack -- Unpack FAT entries>

;**     UnPack - Unpack FAT entries
;
;       ENTRY   BX = Cluster number (may be full 16-bit quantity)
;               ES:BP points to VPB
;
;       EXIT    DI = Contents of FAT for given cluster (may be 16 bits)
;               Zero set means DI=0 (free cluster)
;               Carry set means error (currently user FAILed to I 24)
;
;       USES    SI
;
;       EFFECTS Switches Context
;               May generate fatal error (if cluster too big)

;*      The following local variables are used in common by UnPack,
;       Pack, MapCluster and CheckExtent

?frame = 0
LocalVar    hVPB,WORD           ; handle of VPB
LocalVar    ClusSplit,WORD      ; non-zero if cluster split accross sectors
LocalVar    ClusSave,WORD       ; original value of cluster if ClusSplit
LocalVar    ClusSec,WORD        ; sector of first half of cluster if ClusSplit
FRAME_SIZ = ?frame

Procedure   UnPack,<near,faronly,far16>
        SegAssert SS,<DS>
    ASSUME  DS:TASKAREA, ES:NOTHING


        Assert      ISVPB,<ES,BP>,"UnPack"
        chkvselhd es,bp

?frame = FRAME_SIZ
        mov     si,bp                   ; save VPB handle
        EnterProc
        mov     hVPB, si
        push    bp
        mov     bp, hVPB
        chkvselhd es,bp
        CMP     BX,ES:[bp].vpb_fsd.vpdFAT_max_cluster
        JA      HURTFAT
        pop     bp                      ; Restore frame pointer
        CALL    MapCluster
        push    bp
        mov     bp, hVPB
    ASSUME      DS:NOTHING
        jc      DoContext
        MOV     DI,[DI]
        JNZ     High12                  ; MZ if high 12 bits, go get 'em
        MOV     SI,ES:[bp].vpb_fsd.vpdFAT_max_cluster ; MZ is this 16-bit fat?
;;      CMP     SI,4096-10                                              ;@@
;;      JB      UnPack12                ; No, go 'AND' off bits         ;@@
        cmp     es:[bp].vpb_fsd.vpdFAT_FATentrysize,12                             ;@@
        je      UnPack12                                                ;@@
        JMP     SHORT unprel            ; go release buffer

High12:
        shr     di,4
UnPack12:
        AND     DI,0FFFH                ; Clears carry
unprel:
        CALLFAR RlseBuffr
        OR      DI,DI                   ; set zero condition code, clears carry
DoContext:
        pop     bp
        TaskContext DS,,noassume
        LeaveProc
        ret

HURTFAT:
        MOV     ES:[bp].vpb_fsd.vpdFAT_free_cnt,-1 ; Err in FAT must force recomp
                                        ; of freespace
        lds     si, es:[bp].vpb_fsi.vpi_pDPB    ; argument for harderr
    ASSUME DS:NOTHING
        pop     bp

        push    ax
        push    bp
        mov     bp,CurrTCB
        mov     TASKAREA:[bp].TCBAllowed,ALLOWED_FAIL
        pop     bp
;
; Signal Bad FAT to INT int_fatal_abort handler.  We have an invalid cluster.
;
        MOV     ax,HEBadFS
        mov     ExtErr,ERROR_NOT_DOS_DISK
        CALLFAR HardError
        POP     AX
        TaskContext DS,,noassume
        LeaveProc NoCheck
        ret
EndProc UnPack

Break   <Pack -- Pack FAT entries>

;       ENTRY   BX = Cluster number
;               DX = Data
;               ES:BP pointer to VPB
;
;       EXIT    The data is stored in the FAT at the given cluster.
;               Carry set means error (user FAILed to I 24)
;
;       USES    SI,DX,DI

Procedure   Pack,<hybrid,far16>
        SegAssert SS,<DS>
    ASSUME  DS:TASKAREA, ES:NOTHING

        Assert      ISVPB,<ES,BP>,"Pack"
        chkvselhd es,bp
        mov     si,bp

?frame = FRAME_SIZ
        EnterProc
        mov     hVPB,si
        SaveReg <bp>
        mov     bp,hVPB
        chkvselhd es,bp
        cmp     bx,2
        jae     pack1
        InternalError <Attempt to pack small cluster>
pack1:
        RestoreReg <bp>                 ; Restore frame pointer
        CALL    MapCluster
        SaveReg <bp>
        mov     bp, hVPB
    ASSUME      DS:NOTHING
        LJC     DoContext
        MOV     SI,[DI]
        JZ      ALIGNED                 ; byte (not nibble) aligned
        SHL     DX,4                    ; move data to upper 12 bits
        AND     SI,0FH                  ; leave in original low 4 bits
        JMP     SHORT PackIn
ALIGNED:
        chkvselhd es,bp
;;      CMP     ES:[bp].vpb_fsd.vpdFAT_max_cluster,4096-10 ; MZ 16 bit fats?       ;@@
;;      JAE     pack16                  ; MZ yes, go clobber original da;@@
        cmp     es:[bp].vpb_fsd.vpdFAT_FATentrysize,16                             ;@@
        je      pack16                                                  ;@@
        AND     SI,0F000H               ; MZ leave in upper 4 bits of original
        AND     DX,0FFFh                ; MZ store only 12 bits
        JMP     SHORT PackIn            ; MZ go store
pack16:
        XOR     SI,SI                   ; MZ no original data
PackIn:
        RestoreReg <bp>
        OR      SI,DX
        MOV     [DI],SI
        mov     si,CurrTCB
        lds     si,TASKAREA:[si].TCBCurBuf
        or      [si.buf_flags],BUF_DIRTY
        cmp     byte ptr ClusSplit,0
        TaskContext DS
        JZ      PackRet
        SaveReg <AX,BX,CX>
        mov     ax,ClusSave
        push    bp
        mov     bp,CurrTCB
        mov     ds,word ptr [bp].TCBCurBuf+2
        pop     bp
    ASSUME      DS:NOTHING
        ADD     SI,BUFINSIZ
        MOV     [SI],AH
        CALLFAR RlseBuffr
        TaskContext DS
        PUSH    AX
        mov     dx,ClusSec
; NOTE: It is OK on current systems to zero out the high word of the FAT
; sector since the FAT resides at the beginning of the disk. This may change.
        XOR     CX,CX                   ; CX:DX = sector for FAT
        MOV     SI,1
        XOR     AL,AL
        push    bp
        mov     bp,hVPB                 ; (BP) = VPB handle
        CALLFAR GetBuffrB
        pop     bp
        POP     AX
        JC      POPP_RET
        mov     di,CurrTCB
        lds     di,TASKAREA:[di].TCBCurBuf
    ASSUME      DS:NOTHING
        or      [di.buf_flags],BUF_DIRTY
        ADD     DI,BUFINSIZ
        DEC     DI
        mov     si, hVPB
        ADD     DI,ES:[si].vpb_fsi.vpi_cbSector
        MOV     [DI],AL
        CLC
POPP_RET:
        TaskContext DS
        RestoreReg <CX,BX,AX>
PackRet: pushf                  ; save return code
        CALLFAR RlseBuffr
        popf
        LeaveProc
        ret

EndProc Pack

Break   <MapCluster - BUFFER A FAT SECTOR>

;**     MapCluster - Get a FAT sector into a buffer
;
;       Get a pointer to the cluster.  A buffer is allocated to
;       the process and must be released by the caller.
;
;       The caller must allocate local variables on the frame
;       which are used by this procedure.  See Pack for details
;       of how they are used.
;
;       ENTRY   hVPB    handle to VPB
;               BX      cluster number
;
;       EXIT    DS:DI   Points to contents of FAT for given cluster
;               ClusSplit set :
;                       The cluster number crosses a
;                       sector boundary.  In the case, the buffer will
;                       contain the second sector, ClusSave will be
;                       original value of the cluster and ClusSec will
;                       be the sector where the first part of the cluster
;                       resides.
;                       SI = Buffer offset, DS is NOT Buffer segment.
;               ClusSplit clear :
;                       DS:SI points to buffer.
;               Zero Not set if cluster data is in high 12 bits of word
;               Zero set if cluster data is in low 12 or 16 bits
;               Carry set if failed.
;
;       USES    SI,DI,DS
;
;       EFFECTS Switches Context

Procedure   MapCluster,NEAR,local
        SegAssert SS,<DS>
    ASSUME  DS:TASKAREA, ES:NOTHING

        Assert      ISVPB,<ES,BP>,"MapCluster"

        mov     byte ptr ClusSplit,0
        SaveReg <AX,BX,CX,DX, bp>
        MOV     AX,BX                   ; AX = BX
        MOV     CX,4096-10
        mov     bp, hVPB
        VPBContext es,bp                        ; es:bp -> VPB
;;      CMP     ES:[bp].vpb_fsd.vpdFAT_max_cluster,CX      ; MZ 16 bit fat?        ;@@
;;      jae     map16                   ; MZ yes, do 16 bit algorithm   ;@@
        cmp     es:[bp].vpb_fsd.vpdFAT_FATentrysize,16                             ;@@
        je      map16                                                   ;@@
        SHR     AX,1                    ; AX = BX/2
map16:                                  ; MZ skip prev => AX=2*BX
        XOR     DX,DX                   ; Zero out high word of index
        ADD     AX,BX
        ADC     DX,0            ; DX:AX = 1.5*fat OR 2*FAT = byte offset in fat
        MOV     CX,ES:[bp].vpb_fsi.vpi_cbSector
        DIV     CX                      ; AX is FAT sector # DX is sector index
        ADD     AX,ES:[bp].vpb_fsd.vpdFAT_first_FAT
        DEC     CX                      ; CX is sector size - 1

;       (ax) is sector number to read
;       (dx) is offset within sector of cluster

        SaveReg <AX,DX,CX>
        MOV     DX,AX
        XOR     AL,AL
        MOV     SI,1
; NOTE: It is OK on current systems to zero out the high word of the FAT
; sector since the FAT resides at the beginning of the disk. This may change.
        XOR     CX,CX                   ; CX:DX = sector for FAT
        CALLFAR GetBuffrB
        LJC     map_POP_Err
        RestoreReg  <CX,AX,DX>          ; CX is sec siz-1, AX is offset in sec
                                        ; NOTE AX, DX swapped by save, restore
;       (ax) = offset in sector
;       (cx) = sec size - 1
;       (0:dx) = sector number
;       (bp) = hVPB

        mov     si,CurrTCB
        lds     si,TASKAREA:[si].TCBCurBuf
    ASSUME      DS:NOTHING

ifdef FSSTRICT
        cmp     word ptr [si].buf_sector+2,0
        jne     ie_mc                   ; high sector is bad
        cmp     word ptr [si].buf_sector,dx
        jne     ie_mc                   ; low sector is bad
        cmp     [si].buf_hVPB,bp
        je      map18                   ; This is the buffer we wanted
ie_mc:
        InternalError <MapCluster - Invalid buffer from GetBuffrB>
map18:
endif   ; FSSTRICT

        RestoreReg  <bp>

        LEA     DI,[SI.BUFINSIZ]
        ADD     DI,AX
        CMP     AX,CX
        jnz     mapRET
        MOV     AL,[DI]
        CALLFAR RlseBuffr               ; release first sector
        TaskContext DS
        inc     byte ptr ClusSplit      ; cluster number is split across sectors
        mov     byte ptr ClusSave,al    ; save low half of cluster
        mov     ClusSec,dx              ;   and its sector
        INC     DX
        XOR     AL,AL
        MOV     SI,1
        push    bp
        mov     bp,hVPB                 ; (BP) = VPB handle
; NOTE: It is OK on current systems to zero out the high word of the FAT
; sector since the FAT resides at the beginning of the disk. This may change.
        XOR     CX,CX                   ; CX:DX = sector for FAT
        CALLFAR GetBuffrB
        pop     bp
        jc      map_POP
        mov     si,CurrTCB
        lds     si,TASKAREA:[si].TCBCurBuf
    ASSUME      DS:NOTHING
        LEA     DI,[SI.BUFINSIZ]
        MOV     AL,[DI]
        TaskContext DS
        mov     byte ptr ClusSave+1,al
        lea     di,ClusSave
mapRET:
        RestoreReg  <DX,CX,BX>
        XOR     AX,AX                   ; MZ allow shift to clear carry
        push    bp
        mov     bp,hVPB                 ; (BP) = VPB handle
        chkvselhd es,bp
;;      CMP     ES:[bp].vpb_fsd.vpdFAT_max_cluster,4096-10 ; MZ is this 16-bit fat?;@@
;;      pop     bp                                                      ;@@
;;      jae     mapSet                  ; MZ no, set flags              ;@@
        cmp     es:[bp].vpb_fsd.vpdFAT_FATentrysize,16                             ;@@
        pop     bp                                                      ;@@
        je      mapSet                                                  ;@@
        MOV     AX,BX
mapSet:
        TEST    AL,1                    ; set zero flag if not on boundary
        RestoreReg  <AX>
        ret

;       we come to map_pop if there was an error on the GetBuffB call
;       Clean up the stack.  Return with carry set.  Leave AX as
;       error code!!

map_POP_Err:
        add     sp, 6                   ; clean up the stack
        RestoreReg  <bp>                ; restore the frame pointer
map_POP:
        RestoreReg  <DX,CX,BX>
        add     sp, 2                   ; don't POP AX -- preserve error code
        stc
        ret

EndProc MapCluster,NoCheck

Break   <CheckExtent -- Determine how long an extent is>

;**     CheckExtent - Determine how long an extent is>
;
;       ENTRY   BX = Cluster number (may be full 16-bit quantity)
;               CX = Number of sectors so far
;               DX = Number of sectors desired
;               AX = sectors per cluster
;               ES:BP points to VPB
;
;       EXIT    DI = first cluster of next extent
;               BX = last cluster in this extent
;               CX = number of sectors in extent
;               Zero set means DI=0 (free cluster)
;               Carry set means error (currently user FAILed to I 24)
;
;       USES    SI
;
;       EFFECTS Switches Context
;               May generate fatal error (if cluster too big)

;*      The following local variables are used in common by UnPack,
;       Pack and MapCluster

Procedure   CheckExtent,<near>
        SegAssert SS,<DS>
    ASSUME  DS:NOTHING, ES:NOTHING, SS:TASKAREA

        Assert      ISVPB,<ES,BP>,"CheckExtent"
        chkvselhd es,bp

?frame = FRAME_SIZ
        mov     si,bp                   ; (si) = hVPB
        EnterProc
        mov     hVPB, si                ; save VPB handle
        cmp     bx,es:[si].vpb_fsd.vpdFAT_max_cluster   ; cluster in range?
        ja      cke25                   ;   no, FAT is damaged
        push    bp                      ; save frame pointer
        push    ax
        mov     bp,hVPB
        chkvselhd es,bp                 ; es:bp -> VPB
        jmp     short cke40             ;   yes, go do the look up

cke25:  jmp     cke100

cke30:  TaskContext DS,,noassume
        CALLFAR RlseBuffr               ; entry split release buffer to get next

cke40:  pop     ax
        pop     bp                      ; bp -> argframe
        call    MapCluster              ; fetch the cluster
        jc      ckexx                   ; error getting cluster, go return it
        push    bp                      ; save the argframe
        push    ax
        mov     ax,[di]                 ; (di) = fat entry.
        test    byte ptr ClusSplit,1    ; Split across sector boundary ?
        jz      cke45                   ; No.
        mov     di,TASKAREA:[CurrTCB]
        lds     si,TASKAREA:[di].TCBCurBuf
        mov     di,si                   ; After di is incremented it
        add     di,BUFINSIZ-1           ; will point to new sec buffer
cke45:  mov     bp,hVPB                 ; es:bp -> VPB
        add     si,BUFINSIZ-1
        add     si,es:[bp].vpb_fsi.vpi_cbSector
        mov     bp,word ptr es:[bp].vpb_fsd.vpdFAT_FATentrysize
        and     bp,0ffh
        jmp     cke51

cke50:  mov     ax,[di]                 ; (di) = fat entry.
cke51:  add     di,2
        cmp     bp,12                   ; 12 Bit FAT ?
        jne     cke80                   ;   no, 16 bit do not mask cluster num
        dec     di
        test    bx,01                   ; Is the entry in the high 12 bits?
        jz      short cke70             ; low 12 bits, just mask
        shr     ax,4                    ; high 12 bits
        inc     di
cke70:  and     ax,0fffh                ; mask to 12 bits

; BX = old cluster, AX = new cluster

cke80:  inc     bx                      ; (bx) = next index into cluster map
        cmp     bx,ax                   ; Does the extent continue?
        jne     cke90                   ;   no, the extent ends

        pop     ax
        add     cx,ax                   ; (cx) = length of extent in sectors
        push    ax
        mov     ax,bx
        cmp     cx,dx                   ; Do we have a large enough extent?
        jae     cke90                   ; Yes - done.
        cmp     di,si
        jb      cke50
        jmp     cke30


cke90:  mov     di,ax
        pop     ax
        pop     bp
        mov     si,hVPB
        dec     bx                      ; (bx) = last cluster in the extent
        cmp     bx,es:[si].vpb_fsd.vpdFAT_max_cluster   ; cluster in range?
        ja      cke100                  ;   no, FAT is damaged
        CALLFAR RlseBuffr               ; release the buf gotten by MapCluster
        or      di,di                   ; set zero condition code, clears carry

ckex:
ckexx:  TaskContext DS,,noassume
        LeaveProc
        ret

; This is exactly the code from HURTFAT in UnPack

cke100: MOV     ES:[si].vpb_fsd.vpdFAT_free_cnt,-1 ; Err in FAT must force recomp
                                        ; of freespace
        lds     si, es:[si].vpb_fsi.vpi_pDPB    ; argument for harderr
    ASSUME DS:NOTHING

        push    bp
        mov     bp,CurrTCB
        mov     TASKAREA:[bp].TCBAllowed,ALLOWED_FAIL
        pop     bp
;
; Signal Bad FAT to INT int_fatal_abort handler.  We have an invalid cluster.
;
        push    ax
        MOV     ax,HEBadFS
        mov     ExtErr,ERROR_NOT_DOS_DISK
        CALLFAR HardError
        POP     AX
        TaskContext DS,,noassume
        LeaveProc NoCheck
        ret
EndProc CheckExtent

HIGH4CODE ENDS
        END
