%ifdef FROMBUFNAS
[list -]
%include "dosmac.mac"
;.cref
[list +]
%else
;
; buffer management for MSDOS
;

%include "dosseg.nas"

section DOSCODECODE

[list -]
;.xcref
%define BUF2 1
%include "dossym.mac"
%include "devsym.mac"
%include "dosmac.mac"
;.cref
[list +]
%endif

        i_need  BuffHead,DWORD
        i_need  BuffFree,DWORD
        i_need  PreRead,WORD
        i_need  LastBuffer,DWORD
        i_need  CurBuf,DWORD
        i_need  WPErr,BYTE
	i_need	ALLOWED,BYTE
	i_need	FAILERR,BYTE
	i_need	HIGH_SECTOR,WORD	     ; DOS 4.00 >32mb			;AN000;
	i_need	DOS34_FLAG,WORD 	     ; DOS 4.00 common flag		;AN000;
	i_need  BUF2_Dirty_Count,WORD


BUFFHEAD equ BuffHead	; NASM port label

;SUBTTL SCANPLACE, PLACEBUF -- PUT A BUFFER BACK IN THE POOL
;PAGE
        procedure   ScanPlace,near
 assume ds:nothing, es:nothing, ss:DOSGROUP

; Inputs:
;       Same as PLACEBUF
; Function:
;       Save scan location and call PLACEBUF
; Outputs:
;       DS:DI Points to saved scan location
; SI destroyed, other registers unchanged

        PUSH    ES
        LES     SI,[DI + NEXTBUF]         ; Save scan location
 assume es:nothing
        CALL    PLACEBUF
        PUSH    ES
        POP     DS                      ; Restore scan location
 assume ds:nothing
        MOV     DI,SI
        POP     ES
 assume es:nothing
        return
ScanPlace   ENDP

NRETJ:  JMP     SHORT NRET

        procedure   PLACEBUF,NEAR
 assume ds:nothing, es:nothing, ss:DOSGROUP

; Input:
;       DS:DI points to buffer
; Function:
;       Remove buffer from queue and re-insert it in proper place.
;       If buffer doesn't go at end, and isn't free, decrement
;       priorities.
; NO registers altered
;
; DS:SI -- Curbuf, current buffer in list
; ES:DI -- Buf, buffer passed as argument
; BP:CX -- Pointsave, saved Buf.nextbuf
; DX:BX -- Lastbuf, previous buffer in list
; AL    -- Inserted, Buf has been inserted
; AH    -- Removed, Buf has been removed

        %IF      IBM
        %IFN IBM
        invoke  save_world
        XOR     AX,AX           ; Inserted = Removed = FALSE
        LES     CX,[DI + NEXTBUF]
        MOV     BP,ES           ; Pointsave = Buf.nextbuf
        MOV     SI,DS
        MOV     ES,SI           ; Buf is ES:DI
        LDS     SI,[ss:BUFFHEAD]   ; Curbuf = HEAD
        CALL    POINTCOMP       ; Buf == HEAD?
        JNZ     TNEWHEAD
        CMP     CX,-1           ; Buf is LAST?
        JZ      NRETJ           ; Only one buffer, nothing to do
        MOV     WORD PTR [ss:BUFFHEAD],CX
        MOV     WORD PTR [ss:BUFFHEAD+2],BP        ; HEAD = Pointsave
        INC     AH              ; Removed = TRUE
        MOV     DS,BP
        MOV     SI,CX           ; Curbuf = HEAD
TNEWHEAD:
        MOV     BL,[ES:DI + BUFPRI]
        CMP     BL,[SI + BUFPRI]
        JGE     BUFLOOP
NEWHEAD:                        ; If Buf.pri < HEAD.pri
        MOV     WORD PTR [ES:DI + NEXTBUF],SI
        MOV     WORD PTR [ES:DI + NEXTBUF+2],DS   ; Buf.nextbuf = HEAD
        MOV     WORD PTR [ss:BUFFHEAD],DI
        MOV     WORD PTR [ss:BUFFHEAD+2],ES        ; HEAD = Buf
        INC     AL                              ; Inserted = TRUE
        OR      AH,AH
        JNZ     NRET            ; If Removed == TRUE
BUFLOOP:
        PUSH    DS
        PUSH    SI
        LDS     SI,[SI + NEXTBUF]
        CALL    POINTCOMP
        POP     SI
        POP     DS
        JNZ     TESTINS
        MOV     WORD PTR [SI + NEXTBUF],CX        ; If Curbuf.nextbuf == buf
        MOV     WORD PTR [SI + NEXTBUF+2],BP      ; Curbuf.nextbuf = Pointsave
        INC     AH              ; Removed = TRUE
        OR      AL,AL
        JNZ     SHUFFLE         ; If Inserted == TRUE
TESTINS:
        OR      AL,AL
        JNZ     LOOKBUF
        PUSH    CX              ; If NOT Inserted
        MOV     CL,[ES:DI + BUFPRI]
        CMP     CL,[SI + BUFPRI]
        POP     CX
        JGE     LOOKBUF
        PUSH    DS              ; If Buf.pri < Curbuf.pri
        MOV     DS,DX
        MOV     WORD PTR [BX + NEXTBUF],DI
        MOV     WORD PTR [BX + NEXTBUF+2],ES      ; Lastbuf.nextbuf = Buf
        POP     DS
        MOV     WORD PTR [ES:DI + NEXTBUF],SI
        MOV     WORD PTR [ES:DI + NEXTBUF+2],DS   ; Buf.nextbuf = Curbuf
        INC     AL              ; Inserted = TRUE
        OR      AH,AH
        JNZ     SHUFFLE         ; If Removed == TRUE
LOOKBUF:
        MOV     BX,SI
        MOV     DX,DS           ; Lastbuf = Curbuf
        CMP     WORD PTR [SI + NEXTBUF],-1
        JZ      ISLAST
        LDS     SI,[SI + NEXTBUF] ; Curbuf = Curbuf.nextbuf
        JMP     SHORT BUFLOOP
ISLAST:                 ; If Curbuf is LAST
        MOV     WORD PTR [SI + NEXTBUF],DI
        MOV     WORD PTR [SI + NEXTBUF+2],ES      ; Curbuf.nextbuf = Buf
        MOV     WORD PTR [ES:DI + NEXTBUF],-1
        MOV     WORD PTR [ES:DI + NEXTBUF+2],-1      ; Buf is LAST
NRET:
        invoke  restore_world
        return

SHUFFLE:
        LDS     DI,[ss:BUFFHEAD]
DECLOOP:
        CMP     byte [DI + BUFPRI],FREEPRI
        JZ      NODEC
        DEC     byte [DI + BUFPRI]
NODEC:
        LDS     DI,[DI + NEXTBUF]
 assume ds:nothing
        CMP     DI,-1
        JNZ     DECLOOP
        JMP     SHORT NRET
        %ENDIF
        %ENDIF

        invoke  save_world
        LES     CX,[DI + NEXTBUF]
        CMP     CX,-1           ; Buf is LAST?
        JZ      NRET            ; Buffer already last
        MOV     BP,ES           ; Pointsave = Buf.nextbuf
        PUSH    DS
        POP     ES              ; Buf is ES:DI
 assume es:nothing
        LDS     SI,[ss:BUFFHEAD]   ; Curbuf = HEAD
 assume ds:nothing
POINTCOMP equ PointComp	; NASM port label
        CALL    POINTCOMP       ; Buf == HEAD?
        JNZ     BUFLOOP
        MOV     WORD PTR [ss:BUFFHEAD],CX
        MOV     WORD PTR [ss:BUFFHEAD+2],BP        ; HEAD = Pointsave
        JMP     SHORT LOOKEND

BUFLOOP:
        PUSH    DS
        PUSH    SI
        LDS     SI,[SI + NEXTBUF]
 assume ds:nothing
        CALL    POINTCOMP
        JZ      GOTTHEBUF
        POP     AX
        POP     AX
        JMP     SHORT BUFLOOP

GOTTHEBUF:
        POP     SI
        POP     DS
 assume ds:nothing
        MOV     WORD PTR [SI + NEXTBUF],CX        ; If Curbuf.nextbuf == buf
        MOV     WORD PTR [SI + NEXTBUF+2],BP      ; Curbuf.nextbuf = Pointsave
LOOKEND:
        PUSH    DS
        PUSH    SI
        LDS     SI,[SI + NEXTBUF]
        CMP     SI,-1
        JZ      GOTHEEND
        POP     AX
        POP     AX
        JMP     SHORT LOOKEND

GOTHEEND:
        POP     SI
        POP     DS
 assume ds:nothing
        MOV     WORD PTR [SI + NEXTBUF],DI
        MOV     WORD PTR [SI + NEXTBUF+2],ES      ; Curbuf.nextbuf = Buf
        MOV     WORD PTR [ES:DI + NEXTBUF],-1
        MOV     WORD PTR [ES:DI + NEXTBUF+2],-1      ; Buf is LAST
NRET:
        invoke  restore_world
        return

PLACEBUF    ENDP

        procedure   PLACEHEAD,NEAR
 assume ds:nothing, es:nothing, ss:DOSGROUP

; SAME AS PLACEBUF except places buffer at head

        invoke  save_world
        PUSH    DS
        POP     ES
 assume es:nothing
        LDS     SI,[ss:BUFFHEAD]
 assume ds:nothing
        MOV     WORD PTR [ss:BUFFHEAD],DI
        MOV     WORD PTR [ss:BUFFHEAD+2],ES
        MOV     WORD PTR [ES:DI + NEXTBUF],SI
        MOV     WORD PTR [ES:DI + NEXTBUF+2],DS
LOOKEND2:
        PUSH    DS
        PUSH    SI
        LDS     SI,[SI + NEXTBUF]
 assume ds:nothing
        CALL    POINTCOMP
        JZ      GOTHEEND2
        POP     AX
        POP     AX
        JMP     SHORT LOOKEND2

GOTHEEND2:
        POP     SI
        POP     DS
 assume ds:nothing
        MOV     WORD PTR [SI + NEXTBUF],-1
        MOV     WORD PTR [SI + NEXTBUF+2],-1      ; Buf is LAST
        JMP     SHORT NRET

PLACEHEAD   ENDP

;SUBTTL POINTCOMP -- 20 BIT POINTER COMPARE
;PAGE
        procedure   PointComp,NEAR
 assume ds:nothing, es:nothing, ss:nothing

; Compare DS:SI to ES:DI (or DS:DI to ES:SI) for equality
; DO NOT USE FOR < or >
; No Registers altered

        CMP     SI,DI
        retnz
        PUSH    CX
        PUSH    DX
        MOV     CX,DS
        MOV     DX,ES
        CMP     CX,DX
        POP     DX
        POP     CX
        return
PointComp   ENDP

;SUBTTL GETBUFFR -- GET A SECTOR INTO A BUFFER
;PAGE
        procedure   GETBUFFR,NEAR
 assume ds:nothing, es:DPB, ss:DOSGROUP

; Input:
;       AH = Priority buffer is to have
;       AL = 0 means sector must be pre-read
;          ELSE no pre-read
;       DX = Desired physical sector number
;       ES:BP = Pointer to drive parameters
; Function:
;       Get the specified sector into one of the I/O buffers
;       And shuffle the queue
; Output:
;       [CURBUF] Points to the Buffer for the sector
;	CY iff error (user selected FAIL on int 24h)
; DX,ES:BP unchanged, all other registers destroyed

        XOR     SI,SI
        entry   GETBUFFRB
 assume ds:DOSGROUP
PREREAD equ PreRead	; NASM port label
        MOV     [PREREAD],AX
        MOV     AL,[ES:BP + dpb_drive]
	or word [BuffFree], -1
LASTBUFFER equ LastBuffer	; NASM port label
        LDS     DI,[LASTBUFFER]
 ASSUME DS:NOTHING
	MOV	CX,[ss:HIGH_SECTOR]		; F.C. >32mb			;AN000;
	CMP	DI,-1				; Recency pointer valid?
	JZ	SKBUF				; No
	CMP	DX,WORD PTR [DI + BUFSECNO]
	JNZ	SKBUF				; Wrong sector
	CMP	CX,WORD PTR [DI + BUFSECNO+2]	; F.C. >32mb			;AN000;
	JNZ	SKBUF				; F.C. >32mb			;AN000;
        CMP     AL,[DI + BUFDRV]
        jz JUSTBUF				; Just asked for same buffer
						; Wrong Drive
SKBUF:
        LDS     DI,[ss:BUFFHEAD]
 assume ds:nothing
NXTBFF:
	cmp byte [di + BUFDRV], 0FFh
	jne .notfree
	mov word [ss:BuffFree], di
	mov word [ss:BuffFree + 2], ds
.notfree:
	CMP	DX,WORD PTR [DI + BUFSECNO]	; F.C. >32mb			;AN000;
	jne	BUMP
	CMP	CX,WORD PTR [DI + BUFSECNO+2]	; F.C. >32mb			;AN000;
	jne	BUMP				; F.C. >32mb			;AN000;
        CMP     AL,[DI + BUFDRV]
	je SETINF
BUMP:
        LDS     DI,[DI + NEXTBUF]
 assume ds:nothing
        CMP     DI,-1
        JNZ     NXTBFF
	lds di, [ss:BuffFree]
 assume ds:nothing
	cmp di, -1
	jne .gotfree
        LDS     DI,[ss:BUFFHEAD]
 assume ds:nothing
.gotfree:
	PUSH	word [ss:HIGH_SECTOR]			;F.C. >32mb			;AN000;
        PUSH    SI
        PUSH    DX
        PUSH    BP
        PUSH    ES
BUFWRITE equ BufWrite	; NASM port label
        CALL    BUFWRITE        ; Write out the dirty buffer
        POP     ES
 assume es:DPB
        POP     BP
        POP     DX
        POP     SI
	POP	word [ss:HIGH_SECTOR]			;F.C. >32mb			;AN000;
RDSEC:                          ; Read in the new sector
	JC	GETBERR
        TEST    BYTE PTR [ss:PREREAD],-1
        JNZ     SETBUF
BufInSiz equ BUFINSIZ	; NASM port equate
        LEA     BX,[DI + BufInSiz]        ; Point at buffer
        MOV     CX,1
        PUSH    SI
        PUSH    DI
        PUSH    DX
        test    SI,SI
        JZ      NORMSEC
	invoke	FATSECRD
	MOV	AH,buf_isFAT		; Set buf_flags
	JMP	SHORT GOTTHESEC 	; Buffer is marked free if read barfs
NORMSEC:
	invoke	DREAD			; Buffer is marked free if read barfs
	MOV	AH,0			; Set buf_flags to no type, DO NOT XOR!
GOTTHESEC:				; Carry set by either FATSECRD or DREAD
        POP     DX
        POP     DI
        POP     SI
        jc GETBERR
SETBUF:
	MOV	CX,[ss:HIGH_SECTOR]	       ; F.C. >32mb			;AN000;
	MOV	WORD PTR [DI + BUFSECNO+2],CX  ; F.C. >32mb			;AN000;
	MOV	WORD PTR [DI + BUFSECNO],DX    ; F.C. >32mb			;AN000;
        MOV     WORD PTR [DI + BUFDRVDP],BP
        MOV     WORD PTR [DI + BUFDRVDP+2],ES
        MOV     AL,[ES:BP + dpb_drive]
        MOV     WORD PTR [DI + BUFDRV],AX	; set buf_flags too
SETINF:
        CALL    PLACEBUF
JUSTBUF:
CURBUF equ CurBuf	; NASM port label
        MOV     WORD PTR [ss:CURBUF+2],DS
        MOV     WORD PTR [ss:LASTBUFFER+2],DS
        PUSH    SS
        POP     DS
 ASSUME DS:DOSGROUP
        MOV     WORD PTR [CURBUF],DI
        MOV     WORD PTR [LASTBUFFER],DI
GETBERR:
        PUSH    SS
        POP     DS
 ASSUME DS:DOSGROUP
        return
GETBUFFR    ENDP


;SUBTTL FLUSHBUF -- WRITE OUT DIRTY BUFFERS
;PAGE
        procedure   FlushBuf,NEAR
 assume ds:DOSGROUP, es:nothing, ss:DOSGROUP

; Input:
;       DS = DOSGROUP
;       AL = Physical unit number
;          = -1 for all units
; Function:
;       Write out all dirty buffers for unit, and flag them as clean
; DS Preserved, all others destroyed (ES too)

        LDS     DI,[BUFFHEAD]
 ASSUME DS:NOTHING
        MOV     AH,-1
NXTBUFF:
	CALL	CHECKFLUSH	; Ignore Carry return from CHECKFLUSH.
				; FAILERR is set if user FAILed.
	PUSH	AX
	MOV	AL,[DI + BUFDRV]
WPERR equ WPErr	; NASM port label
	CMP	AL,BYTE PTR [ss:WPERR]
ZAP equ Zap	; NASM port label
	JZ	ZAP
	TEST	word [ss:DOS34_FLAG],FROM_DISK_RESET ;MS. from disk reset ?
	jz NOZAP
Zap:
	MOV WORD PTR [DI + BUFDRV], 00FFh
				; (clears buf_flags as well)
				; Invalidate buffer, it is inconsistent
NOZAP:
	POP	AX
        LDS     DI,[DI + NEXTBUF]
        CMP     DI,-1
        JNZ     NXTBUFF
        PUSH    SS
        POP     DS
 assume ds:DOSGROUP
	CMP	byte [FAILERR],0
	je .good		; if no int 24h fail --> (NC)
	STC			; Return error if user FAILed (CY)
.good:
        return
FlushBuf    ENDP


	procedure CHECKFLUSH,NEAR
 assume ds:nothing, es:nothing, ss:DOSGROUP
; Carry set if problem (currently user FAILed to I 24)

	Assert	ISBUF,<DS,DI>,"CheckFlush"
	CMP	[DI + BUFDRV],AH
	retz				; Skip free buffers, carry clear
	CMP	AH,AL
	JZ	DOBUFFER		; Do all dirty buffers
	CMP	AL,[DI + BUFDRV]
	CLC
	retnz				; Buffer not for this unit or SFT
DOBUFFER:
	TEST	byte [DI + buf_flags],buf_dirty
	retz				; Buffer not dirty, carry clear by TEST
	PUSH	AX
	PUSH	WORD PTR [DI + BUFDRV]	; (reads buf_flags as well)
	CALL	BUFWRITE
	POP	AX
	JC	LEAVE_BUF		; Leave buffer marked free (lost).
	and ah, ~ buf_dirty		; Buffer is clean
	MOV	WORD PTR [DI + BUFDRV],AX
					; (sets buf_flags as well)
LEAVE_BUF:
	POP	AX			; Search info
	return
EndProc CHECKFLUSH


;SUBTTL BUFWRITE -- WRITE OUT A BUFFER IF DIRTY
;PAGE
        procedure   BufWrite,NEAR
 assume ds:nothing, es:nothing, ss:DOSGROUP

; Input:
;       DS:DI Points to the buffer
; Function:
;       Write out all the buffer if dirty.
; Output:
;       Buffer marked free
;	Carry set if error (currently user FAILed to I 24)
; DS:DI Preserved, ALL others destroyed (ES too)

        MOV     AX,00FFH
        XCHG    AX,WORD PTR [DI + BUFDRV] ; Free, in case write barfs
					; (sets buf_flags as well)
        CMP     AL,0FFH
        retz                            ; Buffer is free. (NC)
	test ah, buf_dirty
        retz                            ; Buffer is clean. (NC)
	invoke	DEC_DIRTY_COUNT		; LB. decrement dirty count
        CMP     AL,BYTE PTR [ss:WPERR]
        retz                            ; If in WP error zap buffer
        LES     BP,[DI + BUFDRVDP]
        LEA     BX,[DI + BufInSiz]        ; Point at buffer
	MOV	DX,WORD PTR [DI + BUFSECNO]	;F.C. >32mb			;AN000;
	MOV	CX,WORD PTR [DI + BUFSECNO+2]	;F.C. >32mb			;AN000;
	MOV	[ss:HIGH_SECTOR],CX		;F.C. >32mb			;AN000;

	mov cx, 1			; Default to not a FAT sector
	test byte [di + buf_flags], buf_isFAT
	jz .notfat
	mov ax, [es:bp + dpb_FAT_size]
	mov cl, [es:bp + dpb_FAT_count]
.notfat:

allowed_RETRY equ Allowed_RETRY	; NASM port equate
allowed_FAIL equ Allowed_FAIL	; NASM port equate
	MOV	byte [ss:ALLOWED],allowed_RETRY + allowed_FAIL
	TEST	byte [DI + buf_flags],buf_isDATA
	JZ	NO_IGNORE
allowed_IGNORE equ Allowed_IGNORE	; NASM port equate
	OR	byte [ss:ALLOWED],allowed_IGNORE
NO_IGNORE:
	PUSH	DI		; Save buffer pointer
	XOR	DI,DI		; Indicate failure
WRTAGAIN:
	SaveReg <DI,CX,AX>
	MOV	CX,1
	SaveReg <BX,DX,DS>

	invoke	DWRITE		; Write out the dirty buffer

	RestoreReg  <DS,DX,BX>
	RestoreReg  <AX,CX,DI>
	JC	NOSET
	INC	DI		; If at least ONE write succeedes, the operation
NOSET:				;	succeedes.
	ADD	DX,AX
	LOOP	WRTAGAIN
	test	DI,DI		; Clears carry
	JNZ	BWROK		; At least one write worked --> (NC)
	STC			; DI never got INCed, all writes failed.
BWROK:
	POP	DI
	return
BufWrite    ENDP

Break	<INC_DIRTY_COUNT-increment dirty count>

; Input:
;	none
; Function:
;	increment dirty buffers count
; Output:
;	dirty buffers count in the current hash entry is incremented
;
; All registers preserved

	procedure   INC_DIRTY_COUNT,NEAR
 assume ds:nothing, es:nothing, ss:DOSGROUP
	INC	word [ss:BUF2_Dirty_Count]    ;LB. add 1					;AN000;
	return
EndProc INC_DIRTY_COUNT 	    ;LB. return 				;AN000;

Break	<DEC_DIRTY_COUNT-decrement dirty count>

; Input:
;	none
; Function:
;	decrement dirty buffers count
; Output:
;	dirty buffers count in the current hash entry is decremented
;
; All registers preserved

	procedure   DEC_DIRTY_COUNT,NEAR
 assume ds:nothing, es:nothing, ss:DOSGROUP
	CMP	word [ss:BUF2_Dirty_Count],0  ;LB. in case if 0				;AN000;
	je	nodec		    ;LB. do nothing				;AN000;
	DEC	word [ss:BUF2_Dirty_Count]    ;LB. sub 1					;AN000;
nodec:
	return
EndProc DEC_DIRTY_COUNT 	    ;LB. return 				;AN000;

    END
