Files
DR-DOS-OpenDOS/IBMDOS/BUFFERS.A86
2020-11-04 23:59:28 +01:00

1301 lines
36 KiB
Plaintext

title 'BUFFERS - buffer handling routines'
; File : $BUFFERS.A86$
;
; Description :
;
; Original Author : DIGITAL RESEARCH
;
; Last Edited By : $CALDERA$
;
;-----------------------------------------------------------------------;
; Copyright Work of Caldera, Inc. All Rights Reserved.
;
; THIS WORK IS A COPYRIGHT WORK AND CONTAINS CONFIDENTIAL,
; PROPRIETARY AND TRADE SECRET INFORMATION OF CALDERA, INC.
; ACCESS TO THIS WORK IS RESTRICTED TO (I) CALDERA, INC. EMPLOYEES
; WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE OF
; THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN CALDERA, INC. WHO
; HAVE ACCEPTED THE CALDERA OPENDOS SOURCE LICENSE OR OTHER CALDERA LICENSE
; AGREEMENTS. EXCEPT UNDER THE EXPRESS TERMS OF THE CALDERA LICENSE
; AGREEMENT NO PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED,
; COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED,
; CONDENSED, EXPANDED, COLLECTED, COMPILED, LINKED, RECAST,
; TRANSFORMED OR ADAPTED WITHOUT THE PRIOR WRITTEN CONSENT OF
; CALDERA, INC. ANY USE OR EXPLOITATION OF THIS WORK WITHOUT
; AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO CRIMINAL AND
; CIVIL LIABILITY.
;-----------------------------------------------------------------------;
;
; *** Current Edit History ***
; *** End of Current Edit History ***
;
; $Log$
;
; BUFFERS.A86 1.13 94/11/30 16:26:08
; added support for using multiple FAT copies on reads if one fails
; BUFFERS.A86 1.12 93/08/06 16:19:11
; make geblk public
; BUFFERS.A86 1.8 93/07/07 21:06:25
; Smirnoff'd
; BUFFERS.A86 1.6 93/03/16 22:30:29
; UNDELETE support changes
; BUFFERS.A86 1.5 93/03/05 18:00:26
; Fix bug clearing cluster of new sub directory
; ENDLOG
; Date Who Modification
; --------- --- ---------------------------------------
; 9 Sep 91 Initial version created for VLADIVAR
; 3 mar 93 correct zeroblk bug
NOLIST
eject ! include i:fdos.equ
eject ! include bdos.equ
eject ! include i:doshndl.def
eject
LIST
eject
PCMODE_DATA dseg
extrn current_ddsc:dword
if DELWATCH
extrn fdos_stub:dword
endif
BDOS_DATA dseg word
fatrec rw 1 ; current FAT record
fatbytl rb 1 ; low byte of split FAT entry
fatbyth rb 1 ; high byte of split FAT entry
split_fat rb 1 ; 0/FFh to indicate split entry
eject
extrn adrive:byte
EXTRN chdblk:WORD
EXTRN clsize:WORD
EXTRN cur_dma:WORD
EXTRN cur_dma_seg:WORD
extrn dosfat:WORD
EXTRN fatadd:WORD
extrn lastcl:word
EXTRN mult_sec:WORD
EXTRN nfatrecs:WORD
EXTRN nfats:WORD
extrn pblock:dword
extrn physical_drv:byte
extrn psecsiz:word
extrn rwmode:byte ; data/directory/FAT, read/write
extrn secperclu:word
extrn bcb_root:dword ; PCMODE disk buffer root
extrn deblock_seg:word
BDOS_CODE cseg
extrn clus2sec:near
extrn discard_dirbuf:near
extrn fdos_error:near
extrn flush_dirbuf:near
extrn hshdscrd:near
extrn read_block:near
extrn select_adrive:near ; select drive AL
extrn write_block:near
public alloc_cluster ; allocate data block
public alloc_chain ; allocate a chain
public buffers_check ; check if buffers exist for this drive
PUBLIC delfat ; release data blocks
PUBLIC discard_all ; discard all buffers on ADRIVE
public discard_dir ; discard directory buffers on ADRIVE
public discard_dirty ; discard directory buffers on ADRIVE
PUBLIC fixfat ; set value of FAT entry
public flush_drive ; flush buffers to disk
public locate_buffer ; locate a buffer
PUBLIC update_dat ; flush write pending buffers
public update_ddsc_free ; count free blocks on drive
PUBLIC update_dir ; update directory entry
PUBLIC update_fat ; write out modified FAT records
public zeroblk ; zero cluster (MKDIR)
if DELWATCH
public allocate_cluster ; allocate free cluster on adrive
public change_fat_entry ; write a new value into the FAT
endif
update_ddsc_free:
;----------------
; make sure DDSC_FREE is up to date
; a by-product of this is to checksum the FAT, so we can spot changes
; of removable media
push es
les bx,ss:current_ddsc
mov cx,es:DDSC_FREE[bx] ; get current free space
jcxz update_ddsc_free30 ; if none recount to make sure
inc cx ; is count uninitialised ? (=FFFF)
jz update_ddsc_free30 ; if so better count the free space
update_ddsc_free10:
pop es
ret
update_ddsc_free30:
; rebuild our free space count
xor ax,ax ; assume no free space yet
lea di,DDSC_BLOCK[bx] ; ES:DI -> DDSC_BLOCK
stosw ; DDSC_BLOCK = 0
stosw ; DDSC_FREE = 0
inc ax ; skip reserved block #'s 0 and 1
update_ddsc_free40:
inc ax ; move to next data block #
cmp ax,lastcl ; are we beyond end of disk
ja update_ddsc_free10 ; stop if all free blocks counted
push ax ; save current index
call getblk ; get contents of FAT entry, update ZF
pop ax ; restore current FAT index
jnz update_ddsc_free40 ; try next block if not free
inc es:DDSC_FREE[bx] ; one more free block
jmps update_ddsc_free40 ; try next block
discard_dirty:
;-------------
; This gets called after a write-protect error is returned
mov ah,BF_DIRTY ; discard dirty FAT, dir & data
jmps discard_buffers
discard_all:
;-----------
mov ah,BF_ISFAT+BF_ISDIR+BF_ISDAT
jmps discard_buffers ; discard all the buffers
discard_dir:
;-----------
mov ah,BF_ISDIR ; dir only, leave data and FAT
; jmps discard_buffers
discard_buffers:
;---------------
; entry: adrive = drive to discard
; AH = flags for type to discard i.e. BF_ISFAT, etc.
mov al,adrive ; get the work drive
call discard_dirbuf ; discard 32-byte directory buffer
call hshdscrd ; discard hashing info for drive
les si,bcb_root ; get first buffer
discard_buffers10:
cmp al,es:BCB_DRV[si] ; does the drive match?
jne discard_buffers20 ; try next one if not
test ah,es:BCB_FLAGS[si] ; does the type match?
jz discard_buffers20 ; try next one if not
mov es:BCB_DRV[si],0FFh ; else discard the buffer
mov es:BCB_FLAGS[si],0
discard_buffers20:
if DOS5
mov si,es:BCB_NEXT[si] ; get next buffer address
cmp si,word ptr bcb_root
else
les si,es:BCB_NEXT[si] ; get next buffer address
cmp si,0ffffh
endif
jne discard_buffers10 ; and repeat until all done
discard_buffers30:
push ds ! pop es ; restore ES and return
ret
;-------------
buffers_check:
;-------------
; entry: AL = drive to check (preserved)
; AH = flags
; exit: ZF = 1 if all buffers clean on this drive
push ds ; we use DS here cause it's quicker...
lds si,ss:bcb_root ; start with most recently used
buffers_check10:
cmp al,BCB_DRV[si] ; check if for different drive
jne buffers_check20 ; skip if not our problem
test ah,BCB_FLAGS[si] ; test if its one we are looking for
jnz buffers_check30 ; return with non-zero condition
buffers_check20:
if DOS5
mov si,BCB_NEXT[si] ; get next buffer address
cmp si,ss:word ptr bcb_root
else
lds si,BCB_NEXT[si] ; get next buffer address
cmp si,0ffffh
endif
jne buffers_check10 ; loop back if more to do
xor dx,dx ; set ZF = 1
buffers_check30:
pop ds ; restore DS after BCBs done
ret
eject
; entry: AX = first block to release
; exit: AX and following released
delfat: ; release chain of clusters
;------
cmp ax,2 ; is block number too small?
jb delfat10 ; yes, then stop it
cmp ax,lastcl ; is block number too large?
ja delfat10 ; yes, then stop it
push ax ; else save the number
call getblk ; get the next link
xchg ax,cx ; CX = link
pop ax ; AX = this block
sub bx,bx ; set it to 0000
push cx ; save the link for next pass
call fixfat ; release the block
pop ax ; AX = next block or end
jmps delfat ; try again until all released
delfat10: ; all blocks in chain freed
ret
; On Entry:
; AX = block to read
; On Exit:
; AX = next FAT block index
;
Public getnblk
getnblk: ;UWORD getnblk(blk);
;-------
;
push ax
call getblk ; get current setting
pop bx
jz getnblk10 ; return if something there
ret
getnblk10:
mov ax,dosfat ; if unallocated then allocate it
push ax
xchg ax,bx ; AX = blk, BX = i
call fixfat
pop ax
mov dx,ax ; DX = end of chain
xor cx,cx ; no blocks follow this one
ret
; On Entry:
; AX = block to read
; On Exit:
; AX = contents
; ZF = 1 if AX == 0000h (disk full)
Public getblk
;------
getblk:
;------
push es ! push bx
call fatptr ; get address of block AX in buffer
mov ax,es:[bx] ; get the word from FAT
jnz getblk10 ; skip if on odd address (must be 12 bit)
cmp dosfat,FAT12 ; else check if 16 or 12 bit
je getblk20 ; skip if even 12 bit
pop bx ! pop es
test ax,ax ; update ZF
ret
getblk10:
shr ax,1 ; shift top 12 bits down
shr ax,1
shr ax,1
shr ax,1
getblk20:
and ax,0FFFh ; leave bottom 12 bits only
pop bx ! pop es
ret
alloc_cluster:
;-------------
; On Entry:
; AX = previous cluster (hint for desired start)
; On Exit:
; AX = start of chain
; CY set on failure
;
mov cx,1
; jmp alloc_chain
alloc_chain:
;-----------
; On Entry:
; AX = previous cluster (hint for desired start)
; CX = # clusters wanted
; On Exit:
; AX = start of chain, 0 on failure
; CY set on failure
;
; We want to allocate a chain of CX clusters, AX was previous cluster
; We return with CY clear and AX = 1st cluster in chain on success,
; CY set on failure
;
; When allocating a new chain we first ask SSTOR how much physical space is
; present on the disk. Until SSTOR reports at least 2 clusters free we
; repeatedly call DELWATCH to purge files and recover space. If DELWATCH is
; unable to free space we return "disk full".
;
; When allocating a block we normally are normally given a target block to
; start searching from. We allow DELWATCH to alter this value when it frees
; space to optimise the search.
;
push ax ! push cx ; save entry parameters
call update_ddsc_free ; make sure DDSC_FREE is correct
if DELWATCH
alloc_chain10:
pop dx ! push dx ; DX = clusters wanted
les bx,ss:current_ddsc
mov cx,es:DDSC_FREE[bx] ; CX = clusters available
mov al,adrive ; AL = current drive
cmp cx,dx ; do we have enough room in the FAT ?
jb alloc_chain20 ; if not ask DELWATCH to purge
mov ah,SSTOR_SPACE ; does Superstore have room for data?
callf ss:fdos_stub ; call stub routine
test cx,cx ; are we out of space ?
jnz alloc_chain40 ; no, go ahead and allocate the chain
mov es:DDSC_FREE[bx],cx ; SSTOR says there's none, lets agree
call update_fat ; flush FAT to bring SSTOR up to date
jmps alloc_chain10 ; go round again and ask DELWATCH to
; free up some more space
; we loop until either SSTOR says OK
; or DELWATCH frees all it can
alloc_chain20:
mov ah,DELW_FREECLU ; ask DELWATCH to purge a file
callf ss:fdos_stub ; call stub routine
cmp cx,es:DDSC_FREE[bx] ; can DELWATCH free up any space ?
jne alloc_chain10 ; yes, go and try again
alloc_chain30:
pop cx ! pop ax ; failure, restore stack
jmps alloc_chain80 ; and exit in failure
alloc_chain40:
endif
pop cx ! pop ax ; restore entry parameters
push cx ; save # required
call allocate_cluster ; try to allocate 1st cluster
pop cx ; recover # required
test ax,ax ; could we ?
jz alloc_chain80
dec cx ; one less to allocate
push ax ; save head of chain
jcxz alloc_chain60
alloc_chain50:
push cx
push ax ; save current end of chain
call allocate_cluster ; allocate another cluster
pop bx ; BX = end of chain
test ax,ax ; could we allocate anything ?
jz alloc_chain70 ; no, bail out and free partial chain
xchg ax,bx ; AX = previous cluster, link cluster
push bx ; BX to end of the chain
call fixfat
pop ax ; AX = new end of chain
pop cx
loop alloc_chain50
alloc_chain60:
pop ax ; return the start of the chain as it's
clc ; long enough now...
ret
alloc_chain70:
; We haven't enough free clusters - lets free what we allocated so far
pop cx ; discard count
pop ax ; AX = start of chain
call delfat ; release the chain
alloc_chain80:
xor ax,ax
stc ; we couldn't manage it
ret
allocate_cluster:
;----------------
; On Entry:
; AX = cluster to start from (AX = none known)
; On Exit:
; AX = cluster allocated
;
test ax,ax ; previous block known?
jnz alloc_cl10 ; skip if it is
push ds
lds bx,ss:current_ddsc
mov ax,ds:DDSC_BLOCK[bx] ; else continue from last allocated block
pop ds
alloc_cl10:
mov bx,lastcl ; highest block number on current disk
cmp ax,bx ; is it within disk size?
jb alloc_cl20 ; skip if it is
sub ax,ax ; start at the beginning
alloc_cl20:
mov si,ax ; remember start of search
test ax,ax ; is this the 1st block?
jnz alloc_cl30 ; no
inc ax ; start at beginning
alloc_cl30: ; main loop:
inc ax ; skip to block after current
push ax ! push si ; quick save
call getblk ; get the content of this block
pop si ! pop ax
jz alloc_cl50 ; return if free
cmp ax,bx ; are we at the end yet?
jb alloc_cl30 ; no, try next block
xor ax,ax ; wrap to start of disk
mov bx,si ; remember starting position last time
test bx,bx ; have we been all the way round ?
jnz alloc_cl20 ; no, lets search from start
push ds
lds bx,ss:current_ddsc
mov ds:DDSC_FREE[bx],ax ; we definitely have none left
pop ds
ret ; return (0);
alloc_cl50:
push ds ; block # AX is available
lds bx,ss:current_ddsc
mov ds:DDSC_BLOCK[bx],ax ; remember for next time
pop ds
push ax
mov bx,dosfat ; mark this block as end of file
call fixfat ; for convenience
pop ax
test ax,ax ; update ZF from AX
ret ; return block number
if DELWATCH
; Update a FAT entry with a new value
change_fat_entry:
;----------------
; On Entry:
; AX = block number to change
; DX = new value
; On Exit:
; None
;
mov bx,dx
; jmps fixfat
endif
; entry: AX = block number to change
; BX = new value
; exit: DS,ES = sysdat
;------
fixfat:
;------
push bx ; save new value
push ax
call update_ddsc_free ; make sure DDSC_FREE is correct
pop ax
cmp dosfat,FAT16 ; check if 16-bit FAT
jne fixfat30 ; skip if 12 bit FAT
call fatptr ; ES:BX -> FAT word to modify
pop ax ; restore new value
xor dx,dx ; get a zero (no change of space)
test ax,ax ; are we setting to 0 or non-zero?
xchg ax,es:[bx] ; set the word in the buffer
jnz fixfat10 ; skip if releasing block
test ax,ax ; check if word was 0 before
jz fixfat20 ; skip if setting 0 to 0
inc dx ; DX = 0001h, one free cluster more
jmps fixfat15
fixfat10: ; allocating or fixing block
test ax,ax ; check if word was 0 before
jnz fixfat20 ; skip if setting non-0 to non-0
dec dx ; one free cluster less now
fixfat15: ; DX = change in free space (-1,1)
les si,current_ddsc
add es:DDSC_FREE[si],dx ; update free space count
fixfat20:
les si,bcb_root ; ES:SI -> buffer control block
or es:BCB_FLAGS[si],BF_DIRTY
; mark the buffer as dirty
push ds ! pop es ; ES back to local DS
ret
; We're dealing with a 12-bit FAT...
fixfat30: ; changing 12-bit FAT entry
call fatptr ; get address of block AX in ES:BX
pop cx ; get new value
mov dx,es:[bx] ; get old value
jz fixfat40 ; skip if even word
mov ax,0FFF0h ; set mask for new value
add cx,cx ; else shift new value into top bits
add cx,cx
add cx,cx
add cx,cx
jmps fixfat50 ; set the new word
fixfat40:
mov ax,00FFFh ; set mask for new value
and cx,ax
fixfat50: ; AX = mask, CX = new, DX = old
mov si,0 ; assume space doesn't change
jnz fixfat60 ; skip if new value is zero
test dx,ax ; test if old value was zero as well
jz fixfat70 ; yes, no change in free space
inc si ; else one more block available
jmps fixfat70
fixfat60: ; new value is non-zero
test dx,ax ; is old value non-zero as well?
jnz fixfat70 ; yes, no change in free space
dec si ; else one block less free now
fixfat70:
not ax ; flip the mask bits around
and dx,ax ; zero out old value
or dx,cx ; combine old & new value
mov es:[bx],dx ; update the FAT
xchg ax,si ; AX = free space change (-1, 0 , 1)
les si,current_ddsc
add es:DDSC_FREE[si],ax ; update free space count
les si,bcb_root ; get buffer control block
or es:BCB_FLAGS[si],BF_DIRTY
; mark the buffer as dirty
cmp split_fat,0 ; is 12-bit entry split across sectors
je fixfat80 ; need some magic if so
; handle a split FAT update
mov dx,fatrec ; lower sector number
inc dx ; get the upper sector
call locate_fat ; find the buffer
or es:BCB_FLAGS[si],BF_DIRTY
; mark buffer as write pending
mov al,fatbyth ; get the high byte
mov es:BCB_DATA[si],al ; store the high byte at the beginning
mov dx,fatrec ; get the previous sector
call locate_fat ; read into memory
or es:BCB_FLAGS[si],BF_DIRTY
; mark buffer as write pending
mov bx,psecsiz
dec bx ; BX = sector size - 1
mov al,fatbytl ; get the low byte
mov es:BCB_DATA[si+bx],al
fixfat80:
push ds ! pop es ; ES back to local DS
ret
; On Entry:
; AX = cluster number
; On Exit:
; AX preserved
; ES:BX -> address of word
; BCBSEG = segment of FAT FCB
; ZF = 1 if word on even address
; SPLIT_FAT = 0FFh if xing sector boundary
;
; CX = entries left in sector (if FAT16 - performance optimisation)
;
Public fatptr
fatptr:
;------
push ax ; save block number
mov bx,ax
sub dx,dx ; AX/DX = cluster #
cmp dosfat,FAT16 ; is it 16 bit FAT?
je fatptr10
shr ax,1 ; shift for 1 1/2 byte, else 2 byte
fatptr10:
add ax,bx ; AX = offset into FAT
adc dx,0 ; AX/DX = 32 bit offset
mov cx,psecsiz ; CX = sector size
div cx ; AX = sector offset
dec cx ; CX = sector size - 1
push dx ; DX = offset within FAT sector
push cx
add ax,fatadd ; make it absolute sector address
mov fatrec,ax ; save FAT sector for FIXFAT
xchg ax,dx ; DX = FAT sector
call locate_fat ; locate the sector
pop cx ; CX = sector size - 1
pop bx ; restore offset within FAT sector
pop ax ; restore cluster #
sub cx,bx ; CX = bytes left in sector - 1
lea bx,BCB_DATA[si+bx] ; ES:BX -> buffer data
cmp dosfat,FAT16 ; is it 16 bit media
jne fatptr20 ; skip if 12 bit media
shr cx,1 ; CX = extra entries left in sector
cmp ax,ax ; always set ZF = 1
ret ; return ES:BX -> word in FAT
fatptr20: ; it's a 12 bit FAT, is it a split FAT?
mov split_fat,0 ; assume no boundary crossing
jcxz fatptr30 ; end of sector, it's a split FAT
test al,1 ; ZF = 1 if even cluster
ret ; return ES:BX -> word in FAT buffer
fatptr30: ; block split across two sectors
push ax
mov split_fat,0FFh ; yes, the difficult case
mov al,es:[bx] ; get the low byte from 1st sector
mov fatbytl,al ; save it for later
mov dx,fatrec ; get the FAT record is
inc dx ; get 2nd sector
call locate_fat ; read the 2nd sector
sub bx,bx
lea bx,BCB_DATA[si+bx] ; ES:BX -> buffer data
mov al,es:[bx] ; get 1st byte from next sector
mov fatbyth,al ; save the high byte
push ds ; ES = local DS
pop es
mov bx,offset fatbytl ; ES:BX -> <fatbytl,fatbyh>
pop ax
test al,1 ; set non-zero condition, odd word
ret
if DOS5
; entry: DX = sector number to read
; exit: ES:SI = BCB
locate_fat:
;----------
mov ah,0 ; set sector address overflow = 0
mov cx,0ff00h+BF_ISFAT ; request a FAT buffer w/ preread
locate_buffer:
;-------------
; On Entry:
; AH:DX = sector to locate
; adrive = driver
; CH = 0FFh if preread required
; CL = buffer type
; On Exit:
; ES:SI -> BCB_
;
mov al,adrive ; get our drive number
les si,bcb_root ; get it from the right buffer list
locate10:
cmp dx,es:BCB_REC[si] ; does our sector address match?
jne locate20 ; skip if it doesn't
cmp ah,es:BCB_REC2[si] ; does record address overflow match?
jne locate20 ; skip if not
cmp al,es:BCB_DRV[si] ; does the drive match?
je locate30 ; found if it all matches
locate20: ; MRU buffer doesn't match
mov si,es:BCB_NEXT[si] ; try the next
cmp si,word ptr bcb_root ; while there are more buffers
jne locate10
push ax ! push cx ! push dx ; save all registers
mov si,es:BCB_PREV[si] ; recycle least recently used buffer
call flush_buffer ; write buffer to disk
pop dx ! pop cx ! pop ax ; restore all registers
mov es:BCB_DRV[si],al ; fill in the BCB: drive
mov es:BCB_REC[si],dx ; record low,middle
mov es:BCB_REC2[si],ah ; record high
mov es:BCB_FLAGS[si],cl ; mark as clean, ISFAT,ISDIR or ISDAT
test ch,ch ; is preread required?
jz locate30 ; skip if it isn't
call fill_buffer ; read it from disk
locate30:
cmp si,word ptr bcb_root ; are we already at the head ?
jne locate40 ; if not move ourself there
ret
locate40:
mov bx,es:BCB_NEXT[si] ; BX = next buffer
mov di,es:BCB_PREV[si] ; DI = previous buffer
mov es:BCB_NEXT[di],bx ; unlink buffer from the
mov es:BCB_PREV[bx],di ; chain
mov bx,si
xchg bx,word ptr bcb_root ; become the new head, BX = old head
mov es:BCB_NEXT[si],bx ; old chain follow us
mov di,si
xchg di,es:BCB_PREV[bx] ; back link to our buffer, DI = LRU buffer
mov es:BCB_PREV[si],di ; link ourselves to LRU buffer
mov es:BCB_NEXT[di],si ; forward link to our buffer
ret
; Flush all dirty FAT buffers for drive AL
; entry: AL = drive to flush (0-15)
; exit: CY = 0 if no error
; ax,bx,cx,dx,es preserved
flush_fat:
;---------
; entry: AL = drive for FAT flush
mov ah,BF_ISFAT ; flush all dirty FAT buffers
jmps flush_drive ; shared code for all flushes
;----------
update_dir:
;----------
call flush_dirbuf ; flush local dirbuf to buffers
;---------
flush_dir:
;---------
mov ah,BF_ISDIR ; write out dirty directories
jmps flush_adrive ; update the disk
;----------
update_dat:
;----------
mov ah,BF_ISDAT ; write out dirty data
jmps flush_adrive ; update the disk
;----------
update_fat: ;write out modified FAT buffers
;----------
mov ah,BF_ISFAT ; flush all dirty FAT buffers
; jmp flush_adrive ; update the disk if dirty
flush_adrive:
;------------
mov al,adrive ; AL = currently selected drive
; jmp flush_drive
; Write out all dirty data buffers for a given drive
; entry: AL = drive to be flushed
; AH = mask of buffer types to be flushed
; exit: AX,DX preserved
; Note: sector buffers will be written in the
; sequence in which they appear on disk (low to high)
flush_drive:
;-----------
push es
push si
flush_drive10:
les si,bcb_root ; start with the first buffer
mov bx,0FFFFh ; assume no buffer found
flush_drive20:
test es:BCB_FLAGS[si],BF_DIRTY
; has buffer been written to?
jz flush_drive40 ; no, do the next one
test es:BCB_FLAGS[si],ah ; is it one of these buffers?
jz flush_drive40 ; no, do the next one
cmp al,es:BCB_DRV[si] ; does the drive match?
jne flush_drive40 ; skip if wrong drive
; we've found a buffer to flush
cmp bx,0FFFFh ; first buffer ever found in list?
jz flush_drive30 ; yes, save as new best candidate
; else check if < previous lowest addr
mov dx,es:BCB_REC[si]
sub dx,ds:BCB_REC[bx]
mov dl,es:BCB_REC2[si] ; compare the disk addresss
sbb dl,ds:BCB_REC2[bx]
jnb flush_drive40 ; CY = 0 if new BCB higher
flush_drive30: ; else ES = best BCB so far
mov bx,si ; save it for later
flush_drive40:
mov si,es:BCB_NEXT[si] ; get next buffer address
cmp si,ss:word ptr bcb_root
jne flush_drive20
cmp bx,0FFFFh ; did we find a dirty buffer?
jz flush_drive50 ; no, all buffers cleaned
mov si,bx ; ES:SI -> BCB to flush
call flush_buffer ; write sector to disk
jmps flush_drive10 ; check if more dirty buffers
flush_drive50:
pop si
pop es
ret
else
; entry: DX = sector number to read
; exit: ES:SI = BCB
locate_fat:
;----------
mov ah,0 ; set sector address overflow = 0
mov cx,0ff00h+BF_ISFAT ; request a FAT buffer w/ preread
locate_buffer:
;-------------
; On Entry:
; AH:DX = sector to locate
; adrive = driver
; CH = 0FFh if preread required
; CL = buffer type
; On Exit:
; ES:SI -> BCB_
;
mov al,adrive ; get our drive number
les si,bcb_root ; get it from the right buffer list
mov bx,0FFFFh ; no previous buffers yet
locate1:
cmp dx,es:BCB_REC[si] ; does our sector address match?
jne locate2 ; skip if it doesn't
cmp ah,es:BCB_REC2[si] ; does record address overflow match?
jne locate2 ; skip if not
cmp al,es:BCB_DRV[si] ; does the drive match?
je locate6 ; found if it all matches
locate2: ; MRU buffer doesn't match
cmp es:BCB_LINK_OFF[si],0FFFFh
je locate3 ; are there more buffers?
push es ! pop ds
mov bx,si ; remember previous buffer
les si,es:BCB_NEXT[si] ; move on to next buffer
jmps locate1
locate3: ; we found the LRU buffer
push ax ! push cx ! push dx ; save all registers
call pick_cheapest ; determine cheapest buffer
; ES:SI -> cheapest buffer
; DS:BX -> previous link
test es:BCB_FLAGS[si],BF_DIRTY
jz locate5 ; skip if buffer not dirty
mov al,es:BCB_DRV[si] ; get the buffer's drive
mov ah,es:BCB_FLAGS[si] ; flush all buffers of same type
and ah,BF_ISFAT+BF_ISDIR+BF_ISDAT
push ss ! pop ds
call flush_drive ; gives us burst mode behaviour
; but might re-arrange buffers
call find_prev ; find preceding BCB for re-link
; so DS:BX -> BCB_LINK == ES:SI
locate5:
pop dx ! pop cx ! pop ax ; restore all registers
mov es:BCB_DRV[si],al ; fill in the BCB: drive
mov es:BCB_REC[si],dx ; record low,middle
mov es:BCB_REC2[si],ah ; record high
mov es:BCB_FLAGS[si],cl ; mark as clean, ISFAT,ISDIR or ISDAT
test ch,ch ; is preread required?
jz locate6 ; skip if it isn't
push es ! push si
push ds ! push bx ; save the previous buffer segment
push ss ! pop ds ; for unlinking our buffer
call fill_buffer ; read it from disk, don't return
; on physical errors
pop bx ! pop ds
pop si ! pop es
locate6:
cmp bx,0FFFFh ; are we the MRU buffer
jz locate9 ; yes, leave it at the head
locate8: ; arrive here if found as not 1st
mov ax,es:BCB_LINK_OFF[si]
mov BCB_LINK_OFF[bx],ax
mov ax,es:BCB_LINK_SEG[si]
mov BCB_LINK_SEG[bx],ax ; unlink this buffer
; we now want to attach the
push ss ! pop ds ; BCB at ES:BX to the root
mov ax,ds:word ptr bcb_root
mov es:BCB_LINK_OFF[si],ax
mov ax,ds:word ptr bcb_root+2
mov es:BCB_LINK_SEG[si],ax
mov ds:word ptr bcb_root,si ; insert the new entry
mov ds:word ptr bcb_root+2,es
locate9:
push ss ! pop ds ; DS back to normal
ret
pick_cheapest: ; find cheapest replacement sector
;-------------
; entry: ES:SI = least recently used BCB
; DS:BX = previous buffer
; exit: ES, BX unmodified if LRU buffer marked as cheap
; or no other cheap buffer found
; -or-
; ES:SI, DS:BX modified to cheapest buffer and previous buffer
test es:BCB_FLAGS[si],BF_DIRTY
; is this buffer very expensive?
jz pck_chp5 ; return if it is cheapest "cheap" buffer
; else is cheapest "expensive" buffer
; find the cheapest "cheap" buffer
les si,ss:bcb_root ; as we start at the root with
mov ax,0FFFFh ; no previous buffer
pck_chp1: ; check if buffer at DS is cheap
test es:BCB_FLAGS[si],BF_ISFAT+BF_ISDIR+BF_DIRTY
; check the "not cheap" flag
jnz pck_chp2 ; skip if expensive buffer
mov ds,dx
mov bx,ax ; remember previous buffer
pck_chp2:
mov dx,es ; remember this buffer
mov ax,si ; when we move onto next one
les si,es:BCB_NEXT[si] ; get next buffer
cmp si,0FFFFh ; end of the line ?
jne pck_chp1 ; go again if still buffers
pck_chp3: ; done all buffers
cmp bx,0FFFFh ; did we find the root ?
jne pck_chp4
les si,ss:bcb_root ; return ES:SI -> root
ret
pck_chp4:
les si,ds:BCB_NEXT[bx] ; ES:SI = cheapest buffer
pck_chp5: ; ES:SI = cheapest buffer
ret ; DS:BX = previous
find_prev:
;---------
; entry: ES:SI = BCB to find previous buffer for
; exit: DS:BX = predecessor, BX = FFFF if first
;
mov ax,es
mov dx,si ; AX:DX -> buffer we want to find link for
mov bx,0FFFFh ; assume it's the first buffer
les si,ss:bcb_root ; we start at the root with
find_prv1:
mov cx,es
cmp dx,si ; does offset match ?
jne find_prv2
cmp ax,cx ; does segment match ?
je find_prv3 ; yes, return this one then
find_prv2:
mov ds,cx ; remember previous link
mov bx,si
les si,es:BCB_NEXT[si] ; else have a go at the next one
jmps find_prv1 ; and repeat until match
find_prv3:
ret
; Flush all dirty FAT buffers for drive AL
; entry: AL = drive to flush (0-15)
; exit: CY = 0 if no error
; ax,bx,cx,dx,es preserved
flush_fat:
;---------
; entry: AL = drive for FAT flush
mov ah,BF_ISFAT ; flush all dirty FAT buffers
jmps flush_drive ; shared code for all flushes
;----------
update_dir:
;----------
call flush_dirbuf ; flush local dirbuf to buffers
;---------
flush_dir:
;---------
mov ah,BF_ISDIR ; write out dirty directories
jmps flush_adrive ; update the disk
;----------
update_dat:
;----------
mov ah,BF_ISDAT ; write out dirty data
jmps flush_adrive ; update the disk
;----------
update_fat: ;write out modified FAT buffers
;----------
mov ah,BF_ISFAT ; flush all dirty FAT buffers
; jmp flush_adrive ; update the disk if dirty
flush_adrive:
;------------
mov al,adrive ; AL = currently selected drive
; jmp flush_drive
; Write out all dirty data buffers for a given drive
; entry: AL = drive to be flushed
; AH = mask of buffer types to be flushed
; exit: AX,DX preserved
; Note: sector buffers will be written in the
; sequence in which they appear on disk (low to high)
flush_drive:
;-----------
push ds
push es
push bx ; save registers
push si
flush_dr0:
les si,ss:bcb_root ; start with the first buffer
mov bx,0FFFFh ; assume no buffer found
flush_dr1:
test es:BCB_FLAGS[si],BF_DIRTY
; has buffer been written to?
jz flush_dr3 ; no, do the next one
test es:BCB_FLAGS[si],ah ; is it one of these buffers?
jz flush_dr3 ; no, do the next one
cmp al,es:BCB_DRV[si] ; does the drive match?
jne flush_dr3 ; skip if wrong drive
; we've found a buffer to flush
cmp bx,0FFFFh ; first buffer ever found in list?
jz flush_dr2 ; yes, save as new best candidate
; else check if < previous lowest addr
mov dl,es:BCB_REC2[si] ; compare the disk addresss
sub dl,ds:BCB_REC2[bx]
mov dx,es:BCB_REC[si]
sbb dx,ds:BCB_REC[bx]
jnb flush_dr3 ; CY = 0 if old BCB lower
flush_dr2: ; else ES = best BCB so far
push es
pop ds
mov bx,si ; save it for later
flush_dr3:
les si,es:BCB_NEXT[si] ; get next buffer address
cmp si,0ffffh
jne flush_dr1
flush_dr4: ; DS:BX = best BCB
cmp bx,0FFFFh ; did we find a dirty buffer?
jz flush_dr5 ; no, all buffers cleaned
mov si,bx ; ES:SI -> BCB to flush
push ds ! pop es
push ss ! pop ds
call flush_buffer ; write sector to disk
jmps flush_dr0 ; check if more dirty buffers
flush_dr5:
pop si
pop bx
pop es
pop ds ; restore registers
ret
endif
flush_buffer:
;------------
; entry: ES:SI = address of BCB
; exit: buffer flushed if BCB_FLAGS & BF_DIRTY
; note: preserves AX,BX,CX,DX,ES
test es:BCB_FLAGS[si],BF_DIRTY
; is the buffer dirty?
jz flush_buf9 ; skip update if not modified
flush_buf1:
push es ! push si
push ax ! push bx ; else save all registers
push cx ! push dx
mov al,es:BCB_DRV[si] ; get the buffer drive
cmp al,adrive ; same as the selected drive?
je flush_buf2 ; skip if already selected
push es ; save the BCB
push si
push ds ! pop es ; ES = SYSDAT
call select_adrive ; select drive AL, ZF = 1 if logged in
pop si
pop es ; recover BCB
jc flush_buf5 ; don't flush to bad drive
flush_buf2:
mov cx,nfats ; else FAT sectors written CX times
mov al,0000$0011b ; mark as FAT write
test es:BCB_FLAGS[si],BF_ISFAT
jnz flush_buf3 ; go ahead
mov cx,1 ; directory/data written once only
mov al,0000$0101b ; mark as directory write
test es:BCB_FLAGS[si],BF_ISDIR
jnz flush_buf3 ; if not dir, must be data
mov al,0000$0111b ; mark as data buffer write
flush_buf3: ; CX = # of times to write sector
mov rwmode,al
sub ax,ax ; offset for write = 0
flush_buf4: ; loop back to here for other copies
push ax
push cx ; save loop variables
call setup_rwx ; compute disk address
call write_buff ; write the sector
pop cx
pop ax
add ax,nfatrecs ; move to next FAT copy
loop flush_buf4 ; repeat for all FAT copies
flush_buf5:
and es:BCB_FLAGS[si],not BF_DIRTY
; mark it as no longer dirty
mov al,physical_drv ; work drive for BDOS function
cmp al,adrive ; drive from last IO_SELDSK
je flush_buf6 ; skip if flush to work drive
; else reselect BDOS drive after flush
push ds ! pop es ; ES = SYSDAT
call select_adrive ; reselect the work drive
flush_buf6:
pop dx ! pop cx ; restore all registers
pop bx ! pop ax
pop si ! pop es
flush_buf9: ; all done, CY = 0 if O.K.
ret
;-------
zeroblk: ; AX = blk
;-------
xor bx,bx ; Start at begining of cluster
call clus2sec ; translate to sector address
xchg ax,dx ; DX = low 16 bits of address
mov ah,al ; AH:DX = 24 bit sector address
mov cx,secperclu ; CX == sectors/cluster
zeroblk10: ; repeat for all sectors in cluster
push ax
push cx
push dx
mov cx,BF_ISDIR ; locate directory sector w/o preread
call locate_buffer ; this will find the cheapest buffer
or es:BCB_FLAGS[si],BF_DIRTY
lea di,BCB_DATA[si] ; ES:DI -> disk buffer
mov cx,psecsiz ; CX = byte count for REP STOSB
xor ax,ax
rep stosb ; zero the whole data buffer
pop dx
pop cx
pop ax
add dx,1 ; onto the next block
adc ah,0
loop zeroblk10 ; repeat for all sectors in cluster
jmp flush_dir
fill_buffer:
;-----------
; On Entry:
; ES:SI = address of BCB to be filled
; On Exit:
; ES:SI preserved
; data read into buffer
;
test es:BCB_FLAGS[si],BF_ISFAT
; are we reading a FAT sector?
jz fill_buf1 ; skip if directory/data
mov al,es:BCB_DRV[si] ; get the drive
call flush_fat ; write out all dirty buffers
mov al,0000$0010b ; reading from FAT area
jmps fill_buf3 ; go ahead
fill_buf1:
mov al,0000$0100b ; else mark as directory
test es:BCB_FLAGS[si],BF_ISDIR; test if directory read
jnz fill_buf3 ; go ahead
fill_buf2: ; neither FAT nor directory => data
mov al,0000$0110b ; mark read as data buffer read
fill_buf3:
mov rwmode,al
push cx
xor cx,cx
cmp al,0000$0010b
jne fill_buf4
mov cx,nfats
dec cx
fill_buf4:
mov es:BCB_DRV[si],0FFh ; discard in case of error
sub ax,ax ; no offset for 2nd copy yet
fill_buf5:
push ax
call setup_rwx ; compute disk address
call read_buff ; read the sector
pop ax
jns fill_buf6
; we can end here only if CX was non-zero above and we failed to read a
; FAT copy while there is still another one we could use
add ax,nfatrecs
dec cx
jmps fill_buf5
fill_buf6:
pop cx
mov al,adrive ; restore the drive
mov es:BCB_DRV[si],al ; set the drive #
ret
read_buff:
;---------
push es
push si ; save BCB_
push cur_dma_seg
push cur_dma ; save DMA address
push cx
mov cx,ss:deblock_seg
jcxz read_buff10
mov cur_dma_seg,cx
mov cur_dma,0 ; xfer via deblocking buffer
read_buff10:
pop cx
call read_block
pop cur_dma ; restore DMA address
pop cur_dma_seg
js read_buff20 ; can happen only on FAT read
mov cx,ss:deblock_seg ; if deblocked, copy data
jcxz read_buff20
les di,dword ptr cur_dma ; point to destination
mov cx,psecsiz ; CX = sector size
shr cx,1 ; CX = words per sector
push ds
mov ds,ss:deblock_seg
xor si,si ; DS:SI = source
rep movsw ; copy the data
pop ds
read_buff20: ; SF still indicating error here
pop si ; recover BCB_
pop es
ret
write_buff:
;----------
push es
push si
push cur_dma_seg
push cur_dma
mov cx,ss:deblock_seg ; if deblocking we have to
jcxz write_buff10 ; copy the data first
push ds ; save SYSDAT
les si,dword ptr cur_dma ; ES:SI -> source
push es ; save source seg
mov es,cx
xor di,di ; ES:DI -> deblocking buffer
mov cur_dma_seg,es
mov cur_dma,di ; do xfer via deblocking buffer
mov cx,psecsiz ; CX = sector size
shr cx,1 ; CX = words per sector
pop ds ; DS:SI -> source
rep movsw ; copy to deblocking buffer
pop ds ; restore SYSDAT
write_buff10:
call write_block
pop cur_dma
pop cur_dma_seg
pop si
pop es
ret
setup_rwx:
;---------
; entry: AX = sector offset (multiple FAT writes)
; ES:SI = BCB, BCB_REC filled in
; exit: all values set up for RWXIOSIF
mov cur_dma_seg,es ; segment = BCB_SEGMENT
lea dx,BCB_DATA[si]
mov cur_dma,dx ; offset
xor dx,dx
add ax,es:BCB_REC[si]
adc dl,es:BCB_REC2[si]
mov word ptr pblock,ax ; xfer starts at this block
mov word ptr pblock+WORD,dx
mov mult_sec,1 ; single sector transfer
ret
END