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

1475 lines
41 KiB
Plaintext

title 'BDEVIF - Block DEVice Input/Output support'
; File : $BDEVIO.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$
; BDEVIO.A86 1.27 94/11/30 16:25:22
; added delayed retry for read/write to locked region
; added support for using multiple FAT copies on reads if one fails;
; BDEVIO.A86 1.26 94/02/22 17:11:25
; Fix where corrupt dir entry results in read beyond end-of-chain (Filelink bug)
; BDEVIO.A86 1.25 93/12/15 03:07:11
; New ddioif entry point so Int 25/26 bypasses address normalisation
; BDEVIO.A86 1.24 93/12/08 03:15:14
; Force rebuild_ldt_root if root in JOIN's subdirectory
; BDEVIO.A86 1.23 93/11/19 18:29:29
; Fix for SERVER print queue viewing problem
; BDEVIO.A86 1.22 93/09/21 12:43:37
; On fdos read/write do EOF checks before SHARE LOCK checks
; BDEVIO.A86 1.21 93/09/14 20:02:50
; Trust LFLG_PHYSICAL
; BDEVIO.A86 1.20 93/09/02 22:22:56
; Use 32 bit sectors to read fat for build bpb if appropriate (SYQUEST bug)
; BDEVIO.A86 1.19 93/08/27 18:46:49
; int 26 discards hash codes
; BDEVIO.A86 1.18 93/07/20 22:42:25
; Even fewer checks on int 25/26
; BDEVIO.A86 1.12 93/06/23 02:57:07
; Add auto-commit to fdowrw
; BDEVIO.A86 1.11 93/05/14 13:47:41
; Shorten media change code slightly
; BDEVIO.A86 1.9 93/03/16 22:30:21 IJACK
; UNDELETE support changes
; ENDLOG
eject ! include i:mserror.equ ; F_DOS erros
eject ! include i:fdos.equ
eject ! include i:driver.equ
eject ! include i:doshndl.def
eject ! include bdos.equ
eject ! include rh.equ
;*****************************************************
;*
;* bdos data area
;*
;*****************************************************
PCMODE_DATA dseg word
extrn current_ddsc:dword
extrn current_dhndl:dword
extrn current_dsk:byte ; default drive
extrn current_ldt:dword ; currently selected LDT
extrn dma_offset:word ; DTA offset
extrn dma_segment:word ; DTA segment
extrn ddsc_ptr:dword
extrn err_drv:byte
extrn error_dev:dword ; failing device for Int 24's
extrn fdos_stub:dword
extrn ioexerr:byte
extrn last_drv:byte
extrn ldt_ptr:dword
extrn lock_bios:dword
extrn phys_drv:byte
extrn rwmode:byte
extrn share_stub:dword
extrn unlock_bios:dword
extrn verify_flag:byte
extrn net_retry:word
BDOS_DATA dseg word
extrn bcb_root:dword
extrn deblock_seg:word
extrn fdos_hds_drv:byte
extrn fdos_hds_blk:word
extrn fdos_hds_root:word
extrn fdos_ret:word
public adrive
public clsize
public dosfat
public cur_dma
public cur_dma_seg
public datadd
public diradd
public dirinroot
public dirperclu
public fatadd
public hdsaddr
public lastcl
public logical_drv
public mult_sec
public nfatrecs
public nfats
public pblock
public physical_drv
public psecsiz
public req_hdr
public secperclu
eject
; The following specify the drive selected for the current operation
hdsaddr dw 0 ; current HDS address (0 means at root)
logical_drv db 0 ; logical drive number
physical_drv db 0 ; physical disk number
; The following describe the currently active drive - not this may differ from
; the currently selected drive above due to eg. flushing dirty buffers
; Local copy of DDSC_ variables - ORDER CRITICAL - must match DDSC_
local_ddsc rb 0
psecsiz dw 0 ; byte size of sector
clmsk db 0
clshf db 0
fatadd dw 0 ; sector offset of 1st FAT sector
byte_nfats db 0 ; number of FAT's
dirinroot dw 0 ; # dir entries in root
datadd dw 0 ; sector offset of data sector
lastcl dw 0 ; # last cluster (after adjustment)
if DOS5
dw 0 ; # sectors per FAT
else
db 0 ; # sectors per FAT (nb. may be inaccurate on large drives)
endif
diradd dw 0 ; sector offset of 1st root DIR sector
LOCAL_DDSC_LEN equ offset $ - offset local_ddsc
; some extra parameters calculated from local_ddsc for convenience
nfats dw 0 ; # FAT's (WORD is handier)
nfatrecs dw 0 ; # sectors per FAT (accurate version)
clsize dw 0 ; cluster size in bytes
secperclu dw 0 ; # sectors per cluster
dirperclu dw 0 ; # dir enrties in subdir
dosfat dw 0 ; FAT length indicator (FAT12 or FAT16)
; The following specify the next block read/write operation on the active drive
adrive db 0ffh ; currently active disk
pblock dw 0, 0 ; absolute block address
mult_sec dw 1 ; multi sector count passed to xios
cur_dma dw 0
cur_dma_seg dw 0
fdrwreq dw 0 ; requested count (roundup)
public fdrwflg
fdrwflg db 0 ; bdosrw flags
fdrwcnt dw 0 ; requested byte count for read/write
fdrwptr rd 0 ; disk transfer address for read/write
fdrwoff dw 0 ; offset for R/W DTA
fdrwseg dw 0 ; segment for R/W DTA
fdrwsec rd 1 ; physical block for fdosrw
fdrwsecoff dw 0 ; offset within sector
fdrwdircnt dw 0 ; # sectors in direct xfer
byteoff dw 0 ; fdosrw local variable
dw 0 ; byte offset with file
blk dw 0 ; current cluster of filepos
blkidx dw 0 ; current cluster index within file
blkoffset dw 0 ; offset within cluster
; static request header for DOS device driver I/O
Public req_hdr
req_hdr rb 0
req_len db 22
req_unit rb 1
req_cmd rb 1
req_status rw 1
req_rwmode db 0 ; action hint for device drivers
rb 7
req_media rb 1
rb 16
req1_return equ byte ptr req_media+1
req1_volid equ word ptr req_media+2
req2_buffer equ word ptr req_media+1
req2_bpb equ word ptr req_media+5
req3_buffer equ word ptr req_media+1
req3_count equ word ptr req_media+5
req3_sector equ word ptr req_media+7
req3_volid equ word ptr req_media+9
req4_buffer equ word ptr req_media+1
req4_count equ word ptr req_media+5
req4_sector equ word ptr req_media+7
req4_volid equ dword ptr req_media+9
req4_bigsector equ dword ptr req_media+13
eject
BDOS_CODE cseg
extrn alloc_chain:near
extrn bpb2ddsc:near ; converts BPB to DDSC
extrn buffers_check:near ; look for buffers
extrn delfat:near
extrn discard_all:near ; discard all buffers
extrn discard_dir:near ; discard directory buffers
extrn discard_dirty:near ; discard all dirty buffers
extrn discard_files:near ; discard open files
extrn fdos_error:near
extrn fdos_restart:near
extrn file_update:near
extrn fixfat:near
extrn getnblk:near ; get block value from FAT
extrn get_ldt:near
extrn get_ldt_raw:near
extrn hdsblk:near ; get current HDS block
extrn hshdscrd:near
extrn locate_buffer:near
extrn rebuild_ldt_root:near
extrn timestamp_dhndl:near
extrn update_dat:near
extrn update_fat:near
extrn share_delay:near
public block_device_driver
public clus2sec
public device_driver
public read_block
public select_adrive
public select_logical_drv
public select_physical_drv
public write_block
eject
eject
Public get_ddsc
get_ddsc:
;--------
; On Entry:
; AL = physical drive
; On Exit:
; CY set if bad drive, else
; ES:BX -> DDSC_
; (All other registers preserved)
;
cmp al,ss:phys_drv
jae get_ddsc30
les bx,ss:ddsc_ptr
get_ddsc10:
cmp bx,0FFFFh ; end of the line
je get_ddsc30
cmp al,es:DDSC_UNIT[bx] ; does the unit match ?
je get_ddsc20 ; no, try the next
les bx,es:DDSC_LINK[bx]
jmps get_ddsc10
get_ddsc20:
; clc
ret
get_ddsc30:
stc
ret
eject
; Read/Write from/to disk file
; entry: CURRENT_DNHDL -> file handle
; BDRWFLG = 1 => read
; 0 => write
; ES:DI = buffer (32 bit: off/seg)
; CX = requested byte count (16 bit)
; exit: FDOS_RET = number of bytes read/written
; CURRENT_DHNDL incremented by FDOS_RET
public fdosrw ; read/write to/from disk file
fdosrw:
;------
call fdrw_prepare ; set up address, where we are in file
jc fdrw_error ; stop if we have a problem
call fdrw_size ; extend file if necessary
jc fdrw_error ; bail out if we can't
cmp fdrwcnt,0 ; are we truncating?
jne fdrw_loop ; read/write if non-zero count
test fdrwflg,1 ; writing zero bytes?
jnz fdrw_error ; (reading has no meaning)
call fdw_trunc ; writing truncates the file
jmps fdrw_nobigger
fdrw_error:
ret
fdrw_loop: ; loop here for long reads/writes
call fdrw_seek ; seek to position for xfer
jc fdrw_exit ; should get error's now...
jnz fdrw_buffered ; deblocking required if not aligned
mov cx,fdrwcnt ; CX = requested transfer size
cmp cx,psecsiz ; at least one sector transferred?
jb fdrw_buffered ; if less, need deblocked transfer
mov fdrwreq,cx ; requested count for direct r/w
call direct_rw ; transfer straight to/from TPA
jmps fdrw_more
fdrw_buffered: ; perform deblocked read/write
call deblock_rw ; transfer via BDOS buffer
fdrw_more:
add fdrwoff,ax ; adjust buffer address
add fdos_ret,ax ; adjust return code
add byteoff,ax ; adjust file offset
adc byteoff+2,0
sub fdrwcnt,ax ; adjust remaining count
ja fdrw_loop ; still more to do
fdrw_exit:
les bx,current_dhndl
mov ax,fdos_ret ; get total xfered and update position
add es:DHNDL_POSLO[bx],ax
adc es:DHNDL_POSHI[bx],0
test fdrwflg,1
jnz fdrw_return ; skip if reading
mov ax,byteoff ; has the file grown ?
mov dx,byteoff+WORD
sub ax,es:DHNDL_SIZELO[bx]
sbb dx,es:DHNDL_SIZEHI[bx]
jb fdrw_nobigger ; yes, update the file size
add es:DHNDL_SIZELO[bx],ax
adc es:DHNDL_SIZEHI[bx],dx
fdrw_nobigger:
call timestamp_dhndl ; record the current time
test es:DHNDL_MODE[bx],DHM_COMMIT
jz fdrw_return ; is auto-commit in place ?
call file_update ; yes, commit the file
fdrw_return:
ret
fdw_trunc:
;---------
; On Entry:
; BLKIDX = block number within file
; BLKOFFSET = block offset
; On Exit:
; DHNDL_SIZE adjusted, any excess clusters freed
;
les bx,current_dhndl
mov cx,blkoffset ; get offset within current block
mov ax,blkidx ; get logical block number
jcxz fdw_t10 ; skip if no data in last block
inc ax ; else add in another cluster
fdw_t10: ; AX = # of clusters required in file
test ax,ax
jnz fdw_t20
xchg ax,es:DHNDL_BLK1[bx] ; forget about chain
jmps fdw_t50
fdw_t20:
xchg ax,cx ; CX = # of blocks to keep
mov ax,es:DHNDL_BLK1[bx] ; get first block in file
fdw_t30: ; scan all block we want to keep
push cx
push ax
call getnblk ; get next block
pop bx
pop cx
cmp ax,lastcl ; stop on premature end of chain
ja fdw_t60
loop fdw_t30
push ax ; yep, remember what
mov ax,dosfat
xchg ax,bx ; truncate chain at cluster AX
call fixfat ; as thats all we need
pop ax
fdw_t50:
call delfat ; release the chain
fdw_t60:
les bx,current_dhndl
mov ax,byteoff ; now truncate the file
mov es:DHNDL_SIZELO[bx],ax
mov ax,byteoff+2
mov es:DHNDL_SIZEHI[bx],ax
xor ax,ax ; cause reads/writes to scan
mov es:DHNDL_BLK[bx],ax ; block chain from start
mov es:DHNDL_IDX[bx],ax
mov fdos_ret,ax ; no logical errors
ret
fdrw_prepare:
;------------
; Normalise the xfer address and count
; Calculate current position in the file
;
; On Entry:
; ES:DI -> buffer
; CX = bytes to xfer
; On Exit:
; FDRWSEG:FDRWOFF -> normalised buffer
; FDRWCNT = bytes to xfer
; FDOS_RET = bytes xfer'd (0)
; PREREAD = TRUE
; BYTEOFF = current offset in file
; BLKIDX = cluster containing current file position
; BLKOFFSET = offset within cluster
; CY set if current position theoretically impossible
;
xor ax,ax ; AX = 0
mov fdos_ret,ax ; initialize byte return count
mov fdrwcnt,cx ; save byte count for read/write
mov ax,000Fh
and ax,di ; get offset within paragraph
mov fdrwoff,ax ; save normalized offset for read/write
add ax,cx ; do we overflow 64k ?
jnc fdrw_p10 ; yes, then forget about what would
sub fdrwcnt,ax ; overflow this segment
fdrw_p10:
mov cl,4
shr di,cl ; DI = paragraph offset
mov ax,es
add ax,di ; AX = effective segment
jnc fdrw_p20 ; if above 1 MByte base it at FFFF
inc ax ; AX = para's above FFFF
shl ax,cl ; make it bytes
add fdrwoff,ax ; add to offset
mov ax,0ffffh ; use our magic segment
fdrw_p20:
mov fdrwseg,ax ; save normalized segment for read/write
les bx,current_dhndl
mov ax,es:DHNDL_POSLO[bx]
mov byteoff,ax ; copy position to local variables
mov ax,es:DHNDL_POSHI[bx]
mov byteoff+WORD,ax
mov cx,clsize
mov ax,lastcl
mul cx ; DX:AX = maximum size of disk
sub ax,byteoff
sbb dx,byteoff+WORD ; beyond this we can't go
jc fdrw_p30
mov ax,byteoff ; DX:AX = current file size
mov dx,byteoff+WORD
div clsize
mov blkidx,ax ; save it for later
mov blkoffset,dx ; DX = offset within cluster
clc ; theoretically possible
fdrw_p30:
ret
fdrw_size:
;---------
; On reads check xfer starts within file, and clip size to reflect EOF.
; On writes try to extend to cluster chain so it is big enough to contain
; the data we wish to write.
;
; On Entry:
; BYTEOFF = current position in file
; FDRWCNT = extra bytes requested
; On Exit:
; FDRWCNT adjusted if read past EOF
; CY set if problem extending file
;
les bx,current_dhndl
mov ax,es:DHNDL_SIZELO[bx] ; are we past the end of file
mov dx,es:DHNDL_SIZEHI[bx] ; if so we may wish to extend on write
sub ax,byteoff ; AX,DX = current offset
sbb dx,byteoff+WORD ; are we already beyond EOF ?
jb fdrw_s40
sub ax,fdrwcnt ; will we be going beyond EOF ?
sbb dx,0
jnb fdrw_s10 ; no, whole xfer is OK
test fdrwflg,1 ; check if we're reading
jz fdrw_s50 ; if we are just adjust the
add fdrwcnt,ax ; amount we can xfer
fdrw_s10:
; We call share concerning the XFER to check if any of the proposed
; file region is locked.
; les bx,current_dhndl ; check for locked regions
mov cx,net_retry
fdrw_s15:
push cx
mov cx,fdrwcnt ; in the file
callf share_stub+S_FDOSRW
pop cx
jnc fdrw_s20 ; CY set on error
dec cx
jz fdrw_s30
call share_delay
jmps fdrw_s15
fdrw_s20:
ret
fdrw_s30:
jmp fdos_error ; CY clear, AX = error code
fdrw_s40:
; We are going beyond EOF - if it is a read we fail it, if a write
; try to extend the file
test fdrwflg,1 ; check if we're reading
stc ; assume failure
jnz fdrw_s20 ; reads fail now, writes extend file
fdrw_s50:
call fdrw_s10 ; make sure SHARE doesn't object
; jmp fdwrite_extend ; if not try to extend the file
fdwrite_extend:
;--------------
; Try to extend to file to the required size before we write to it
; On Entry:
; ES:BX -> DHNDL_
; BYTEOFF = current position in file
; FDRWCNT = extra requested
; On Exit:
; CY clear if cluster chain now big enough for desired file size
;
mov ax,byteoff ; AX,DX = current offset
mov dx,byteoff+2
add ax,fdrwcnt ; AX,DX = offset after r/w if success
adc dx,0 ; add offset from lower 16 bits
div clsize ; AX whole blocks required
test dx,dx ; any remainder ?
jz fdw_e05 ; yes, we have a partial block
inc ax ; round up blocks required
fdw_e05:
xchg ax,cx ; CX blocks are required
mov ax,es:DHNDL_BLK1[bx] ; assume we need to follow from start
test ax,ax
jz fdw_e30 ; if no starting block do the lot
dec cx ; else count # extra blocks required
mov dx,es:DHNDL_BLK[bx] ; do we have a current block ?
test dx,dx ; if not we have to start
jz fdw_e10 ; with the first block
mov ax,dx ; new starting block as this must
sub cx,es:DHNDL_IDX[bx] ; be less than extended size
fdw_e10:
jcxz fdw_e20 ; bail out of we have enough
fdw_e15:
push ax ; save current block
push cx ; save # required
call getnblk ; AX = next block in chain
pop cx ; restore # required
pop bx ; recover previous block
cmp ax,lastcl ; end of chain yet ?
ja fdw_e40
loop fdw_e15 ; try another one
fdw_e20:
clc ; chain is already long enough
ret
fdw_e30:
; We have no initial block, so allocate them all
; xor ax,ax ; no preconceptions over where we
call alloc_chain ; allocate chain of CX clusters
jc fdw_e35
les bx,current_dhndl
mov es:DHNDL_BLK1[bx],ax ; remember initial block
clc
fdw_e35:
ret
fdw_e40:
; We have a partial chain, ending at cluster BX
push bx ; save current end of chain
xchg ax,bx ; start allocating from cluster AX a
call alloc_chain ; a chain of CX clusters
pop bx
jc fdw_e45
xchg ax,bx ; AX = previous cluster, link cluster
call fixfat ; BX to end of the chain
clc
fdw_e45:
ret
fdrw_seek:
;---------
; On Entry:
; BYTEOFF = offset within file
; On Exit:
; BLK = cluster containing current filepos
; BLKOFFSET = offset within cluster
; BLKIDX = cluster index within file
; PBLOCK = sector containing current filepos
; POFFSET = offset within sector (reflected in ZF)
;
mov ax,byteoff ; where are we now ?
mov dx,byteoff+WORD
div clsize
mov blkidx,ax ; save cluster
mov blkoffset,dx ; and offset within it
les bx,current_dhndl
cmp ax,es:DHNDL_IDX[bx] ; do we know this block ?
jb fdrw_seek10 ; we can't go backwards, use 1st block
mov cx,es:DHNDL_BLK[bx] ; get last index block
jcxz fdrw_seek10 ; use 1st block if it isn't valid
sub ax,es:DHNDL_IDX[bx] ; skip this many
jmps fdrw_seek20
fdrw_seek10:
mov cx,es:DHNDL_BLK1[bx] ; start with 1st block
fdrw_seek20:
xchg ax,cx ; AX = starting cluster
jcxz fdrw_seek40 ; CX = clusters to skip
fdrw_seek30:
push cx
call getnblk ; get next block
pop cx
cmp ax,lastcl ; stop on premature end of chain
ja fdrw_seek_error ; (file size must be wrong..)
loop fdrw_seek30
fdrw_seek40:
les bx,current_dhndl
mov dx,blkidx
mov es:DHNDL_IDX[bx],dx ; remember this position for next time
mov es:DHNDL_BLK[bx],ax
mov blk,ax ; save the block for coniguous checks
mov bx,blkoffset
call clus2sec ; convert to sector/offset
mov word ptr fdrwsec,ax ; remember this block
mov word ptr fdrwsec+WORD,dx
mov fdrwsecoff,bx ; and offset within it
test bx,bx ; set ZF
; clc ; no problems
ret
fdrw_seek_error:
stc ; we hit unexpected end of chain
ret ; (shouldn't happen)
; Read/write partial sector via deblocking code
; On Entry:
; FDRWSEC = sector address on disk
; FDRWSECOFF = offset within sector
; FDRWCNT = byte count for read/write
; On Exit:
; AX = # of bytes transferred
deblock_rw:
;----------
mov cx,0FF00h+BF_ISDAT ; CH = preread, buffer is data
mov dx,word ptr fdrwsec ; set sector to xfer from
mov ah,byte ptr fdrwsec+WORD
call locate_buffer ; ES:SI -> buffer
mov bx,fdrwsecoff ; BX = offset within sector
mov ax,psecsiz
mov dx,ax ; DX = physical sector size
sub ax,bx ; AX = bytes left in sector
cmp ax,fdrwcnt ; more than we want to transfer?
jb deblkrw10 ; yes, only do up to end of sector
mov ax,fdrwcnt ; else do up to end of request
deblkrw10:
mov cx,ax ; AX, CX = byte count
; (AX for return, CX for MOVSW)
push ds
test fdrwflg,1 ; check if reading or writing
jz dblkrw30 ; skip if writing
push es
les di,fdrwptr ; destination is user memory
pop ds ; source segment is data buffer
lea si,BCB_DATA[si+bx] ; DS:SI -> data
jmps dblkrw40 ; copy the data
dblkrw30: ; we're writing
or es:BCB_FLAGS[si],BF_DIRTY; mark buffer as dirty
lea di,BCB_DATA[si+bx] ; ES:DI -> data
lds si,fdrwptr ; source is user memory
dblkrw40:
shr cx,1 ; make it a word count
rep movsw ; move the words
jnc dblkrw50 ; skip if even # of bytes
movsb ; else move last byte
dblkrw50:
pop ds ; restore registers
ret
; entry: BYTEOFF = 32-bit offset into file
; BLKOFFSET = byte offset within cluster
; PRVBLK = block in which transfer starts
; FDRWREQ = requested transfer length
;---------
direct_rw:
;---------
sub dx,dx ; assume no extra blocks required
mov ax,fdrwreq ; total byte count
mov cx,clsize ; get number of bytes
sub cx,blkoffset ; CX = bytes remaining in this block
sub ax,cx ; if wholly containined within block
jbe direct_rw10 ; then leave it alone
div clsize ; else get # of extra clusters
xchg ax,dx ; DX = clusters, AX = remainder
or ax,ax ; round up if any remainder
jz direct_rw10 ; skip if even number
inc dx ; else one more cluster
direct_rw10: ; DX = # of contiguous clusters req'd
call check_cont ; check how many contiguous blocks
mov ax,clsize ; space = cnt * dpbptr->clsize;
mul cx ; AX:DX = # of bytes transferrable
sub ax,blkoffset ; BX = skipped bytes in 1st cluster
sbb dx,0
; AX:DX = max # of bytes transferrable
; from current position
test dx,dx
jnz direct_rw20 ; if > 64 K, use up request
cmp ax,fdrwreq ; if less than we requested
jb direct_rw30 ; then lets do it
direct_rw20:
xor dx,dx
mov ax,fdrwreq ; else use requested count
direct_rw30:
div psecsiz ; AX = # complete sectors
mov fdrwdircnt,ax ; save direct sector count
mov mult_sec,ax ; set multi sector count
mul psecsiz ; AX = bytes to xfer
push ax ; save for later
mov ax,fdrwoff ; FDRWPTR = disk transfer address
mov cur_dma,ax
mov ax,fdrwseg
mov cur_dma_seg,ax
mov ax,word ptr fdrwsec ; set sector to xfer from
mov word ptr pblock,ax
mov ax,word ptr fdrwsec+WORD
mov word ptr pblock+WORD,ax
mov rwmode,0000$0110b ;data read/write
mov cl,fdrwflg
and cl,1 ; CL = read/write flag
jz direct_rw40
xor cx,cx ; indicate no retries
call read_block ; read in the data
jmps direct_rw50
direct_rw40:
call write_block ; write out the data
direct_rw50:
call SynchroniseBuffers ; synchronize BCBs with direct transfer
pop ax ; recover bytes xfered
push ds ! pop es ; restore ES = SYSDAT
ret
check_cont: ; check for adjacent blocks or space
;----------
; entry: DX = # of extra contiguous blocks req'd
; exit: CX = # of contiguous blocks available
; We first check all adjacent allocated clusters.
; If we'd like more and we find the end of file
; and we are writing and the adjacent blocks aren't
; allocated, then we count them as well and link
; them into the file.
mov ax,blk ; current block number
xor cx,cx ; contiguous blocks found = 0
test dx,dx ; any extra required ?
jz check_cont20
check_cont10: ; get link of current block
push ax ; save current block
push cx ; save extra blocks so far
push dx ; save extra blocks we'd like
call getnblk ; get the link
pop dx
pop cx
pop bx
inc bx ; BX = current block + 1
cmp ax,bx ; check if next block is contiguous
jne check_cont20 ; and try for another
inc cx ; extra contiguous cluster
dec dx ; one less block to check
jnz check_cont10 ; try again if we still want more
check_cont20: ; we can do CX extra clusters
inc cx ; include 1st cluster too..
ret
;------------------
SynchroniseBuffers: ; synchronize BCBs after multi sector transfer
;------------------
; On Entry:
; FDRWSEG:FDRWOFF = transfer address for IO_READ/IO_WRITE
; FDRWDIRCNT = physical sector count for direct transfer
; FDRWSEC = sector address for transfer
; FDWRFLG = even for write, odd for read
; On Exit:
; direct transfer buffer or BCB updated if BCB overlap
;
; If any data buffer is found, that falls into the region affected
; by the direct sector transfer, the following action is performed:
; If the operation was a read and the sector buffer is clean,
; no action is required. If it was dirty, the buffer contents is
; copied to the corresponding location in the DTA buffer.
; If the operation was a write, the sector buffer is discarded.
;
;
mov dx,word ptr fdrwsec
mov ah,byte ptr fdrwsec+WORD
mov al,adrive ; get our drive number
lds bx,bcb_root ; DS:BX -> 1st buffer
SynchroniseBuffers10:
test ds:BCB_FLAGS[bx],BF_ISDAT; is this a data buffer?
jz SynchroniseBuffers30 ; skip if directory or FAT
cmp al,ds:BCB_DRV[bx] ; does the drive match?
jne SynchroniseBuffers30 ; skip if different
mov si,ds:BCB_REC[bx] ; compute bcb->rec - prec
sub si,dx ; result in SI,CL (lsb..msb)
mov cl,ds:BCB_REC2[bx]
sbb cl,ah ; get bits 16-23 of result
jne SynchroniseBuffers30 ; skip if bcb->rec < prec
cmp si,ss:fdrwdircnt ; else check against transfer length
jae SynchroniseBuffers30 ; skip if beyond transfer length
test ss:fdrwflg,1 ; test direction: read or write
jz SynchroniseBuffers20 ; skip if disk write
test ds:BCB_FLAGS[bx],BF_DIRTY; if buffer dirty, did read old data
jz SynchroniseBuffers30 ; else data read was valid
push ax ! push dx ; save record address
mov ax,ss:psecsiz ; # of bytes in sector buffer
mov cx,ax
shr cx,1 ; CX = words per sector
mul si ; AX = byte offset from start buffer
add ax,ss:fdrwoff ; AX = offset
xchg ax,di ; DI = offset
mov es,ss:fdrwseg ; ES:DI -> data to be replaced
lea si,BCB_DATA[bx]
rep movsw ; move CX words (one physical sector)
pop dx ! pop ax ; restore record address
jmps SynchroniseBuffers30
SynchroniseBuffers20: ; multi sector write
mov ds:BCB_DRV[bx],0FFh ; discard this sector
SynchroniseBuffers30:
if DOS5
mov bx,ds:BCB_NEXT[bx]
cmp bx,ss:word ptr bcb_root
else
lds bx,ds:BCB_NEXT[bx] ; get next buffer address
cmp bx,0ffffh
endif
jne SynchroniseBuffers10 ; if so stop
push ss ! pop ds ; restore DS
ret
eject
Public blockif, ddioif
;======= ================================
blockif: ; disk read/write bios interface
;======= ================================
; entry: AL = BIOS Request function number
; ADRIVE = block device to xfer to/from
; RWMODE = read/write mode
; CUR_DMA_SEG:CUR_DMA -> xfer address
; PBLOCK = starting block of xfer
; MULT_CNT = # blocks to xfer
; exit: AX = BX = output
mov req_cmd,al
mov al,rwmode ; copy rwmode to where the device
mov req_rwmode,al ; driver can get the hint
mov ax,cur_dma ; get DMA offset
push ax ; (save it)
and ax,000Fh ; get offset within paragraph
mov req4_buffer,ax ; set transfer offset
pop ax ; (restore offset)
mov cl,4
shr ax,cl ; convert to paragraphs
add ax,cur_dma_seg ; add in the segment
mov req4_buffer+2,ax ; set transfer segment
mov ax,mult_sec ; get requested sector count
mov req4_count,ax ; set requested sector count
;------
ddioif:
;------
push es
mov al,adrive ; get selected drive
call get_ddsc ; ES:BX -> DDSC
mov ax,word ptr pblock
mov dx,word ptr pblock+WORD ; DX:AX = starting block
push es
les si,es:DDSC_DEVHEAD[bx] ; ES:SI -> device driver
if DOS5
; DOS 4 support
mov word ptr req4_bigsector,ax
mov word ptr req4_bigsector+2,dx
mov req_len,RH4_LEN ; set length of request header
test es:DH_ATTRIB[si],DA_BIGDRV ; large sector number support?
jz blockif10 ; no, normal request header
mov ax,-1 ; indicate we use 32-bit sector number
blockif10:
mov req4_sector,ax ; set requested sector address
else
mov word ptr req4_bigsector,ax
mov word ptr req4_bigsector+2,dx
mov req4_sector,ax ; set requested sector address
mov req4_sector+2,dx ; (support large DOS drives)
mov req_len,RH4_LEN ; assume 22 bytes in request header
test es:DH_ATTRIB[si],DA_BIGDRV ; large sector number support?
jz blockif10 ; no, normal request header
mov req_len,RH4_LEN+2 ; else indicate long request
blockif10:
endif
pop es
call block_device_driver ; make call to device driver
js blockif20
xor ax,ax ; no error
blockif20:
mov mult_sec,1 ; reset sector count
mov bx,ax ; AX, BX = return code
pop es
ret
block_device_driver:
;------------------
; entry: ES:BX -> DDSC, req_hdr partly filled in
; exit: AX = status after function
; SF = 1 if error occurred
; note: BX preserved
mov al,es:DDSC_MEDIA[bx]
mov req_media,al ; set current media byte
mov al,es:DDSC_RUNIT[bx] ; get relative unit #
mov req_unit,al ; set the unit
push ds
push es
push bx
push ds
lds si,es:DDSC_DEVHEAD[bx]
pop es
mov bx,offset req_hdr ; ES:BX -> request packet
call device_driver ; do operation
pop bx
pop es
pop ds
ret
; On Entry:
; DS:SI Device Header
; ES:BX Current Request Header
;
; On Exit:
; AX Request Header Status
;
device_driver:
;------------
xor ax,ax
mov es:RH_STATUS[bx],ax ; Initialise return status
push es
push bx
push bp
callf ss:lock_bios ; lock access to BIOS
push cs
call device_driver10 ; fake a callf
callf ss:unlock_bios ; unlock access to BIOS
pop bp
pop bx
pop es
sti
cld ; Restore Flags
mov ax,es:RH_STATUS[bx] ; Return the Status to the caller
test ax,ax ; set SF=1 if error
ret
device_driver10:
push ds
push ds:DH_INTERRUPT[si] ; interrupt routine address on stack
push ds
push ds:DH_STRATEGY[si] ; strategy routine address on stack
retf ; retf to strategy, interrupt, us
eject
; Select drive and check for door open ints
; Build fdos_hds to refer to the drive
; Exit: DL = drive to be selected (0-15)
select_logical_drv:
;------------------
; On Entry:
; AL = logical drive to select (with change media checks)
; On Exit:
; ES:BX -> LDT_
;
cmp al,last_drv ; is it a legal drive ?
jae select_drv_bad ; no, reject it now
mov logical_drv,al ; save logical drive
call get_ldt ; ES:BX -> LDT_ for drive
jc select_physical_drv ; no LDT_ during init, must be physical
mov word ptr current_ldt,bx
mov word ptr current_ldt+WORD,es
mov al,es:byte ptr LDT_FLAGS+1[bx] ; is the drive valid ?
test al,(LFLG_NETWRKD+LFLG_JOINED)/100h
jnz select_drv_bad ; reject networked/joined drives
test al,LFLG_PHYSICAL/100h
jz select_drv_bad ; reject non-physical drives
mov al,es:LDT_NAME[bx] ; get the drive from the ascii name
and al,1fh ; as the drive may require rebuilding
dec ax ; make it zero based
push es ! push bx
call select_physical_drv ; select the physical root
pop bx ! pop es
cmp es:LDT_ROOTLEN[bx],2 ; if logical and physical roots
jbe select_logical_drv30 ; are the same we are OK now
if JOIN
mov al,es:LDT_DRV[bx] ; should we be on a different
cmp al,fdos_hds_drv ; physical drive ?
jne select_logical_drv10 ; if so then we'd better rebuild
endif
cmp es:LDT_BLK[bx],0FFFFh ; did we have a media change ?
jne select_logical_drv20 ; then we'd better rebuild
select_logical_drv10:
call rebuild_ldt_root ; the LDT_ root block
select_logical_drv20:
mov ax,es:LDT_ROOT[bx] ; get virtual root from LDT
mov fdos_hds_root,ax ; move there
mov fdos_hds_blk,ax
if JOIN
mov al,es:LDT_DRV[bx] ; same with drive
mov fdos_hds_drv,al
endif
select_logical_drv30:
ret
select_physical_drv:
;-------------------
; On Entry:
; AL = physical drive to select (with change media checks)
; On Exit:
; None
;
xor dx,dx
mov fdos_hds_blk,dx ; put it in the root by default
mov fdos_hds_root,dx
mov fdos_hds_drv,al ; set physical drive in working HDS
cmp al,phys_drv ; should we have a DDSC_ for this drive
jae select_drv_bad ; no, we can't select it then
mov physical_drv,al ; save physical drive number
call select_adrive ; no, better select it
jc select_drv_critical_error
ret
select_drv_bad:
;--------------
; An attempt has been made to select a bad drive,
; return a logical error "invalid drive"
mov ax,ED_DRIVE ; ED_DRIVE "invalid drive"
jmp fdos_error
select_drv_critical_error:
;-------------------------
; The drive is logically correct, so all error at this point must
; be physical ones - so we want a critical error
jmp generate_critical_error
eject
select_adrive:
;-------------
; This entry is called to physically select a drive (eg. when flushing buffers)
; It does not alter the current physical_drv setting, which must be re-selected
; afterwards by the caller.
;
; On Entry:
; AL = disk to select (range validated)
; On Exit:
; CY set if a problem selecting the drive
mov adrive,al
mov err_drv,al ; save error drive
call get_ddsc ; ES:BX -> DDSC_ for drive
mov al,1 ; AL = "Unknown Unit"
jc select_drv_err ; error if no DDSC_
mov ax,es:word ptr DDSC_DEVHEAD[bx]
mov word ptr error_dev+0,ax
mov ax,es:word ptr DDSC_DEVHEAD+2[bx]
mov word ptr error_dev+2,ax
push es ; remember driver address for error's
push bx ; preserve DDSC_
call check_media ; see if media has changed
pop bx ; restore DDSC_
pop es
jc select_drv_err
; select the disk drive and fill the drive specific variables
; entry: ES:BX -> DDSC_ of disk to select
; AX <> 0 if drive requires BPB rebuilt
; exit: CY flag set on error
test ax,ax ; device driver, new select?
jz select_ddsc ; use current DDSC if old select
call build_ddsc_from_bpb ; else get BPB and build new DDSC
jc select_drv_err ; carry flag reset
call select_ddsc ; use to DDSC for select
if DELWATCH
mov ah,DELW_NEWDISK ; we have a new disk so I guess
mov al,physical_drv ; I'd better tell delwatch
les bx,current_ddsc ; about the new disk so it
callf fdos_stub ; knows to update itself
endif
clc ;select disk function ok
ret
select_drv_err:
; On Entry:
; AL = extended error code
; CY set
;
mov ioexerr,al ; save error code
ret
select_ddsc:
;-----------
; On Entry:
; ES:BX -> DDSC_ of drive to be selected
mov word ptr current_ddsc,bx
mov word ptr current_ddsc+WORD,es
push ds ! push es
pop ds ! pop es ; swap ES and DS
lea si,DDSC_SECSIZE[bx] ; DS:SI -> DDSC_ original
mov di,offset local_ddsc ; ES:DI -> DDSC_ copy
mov cx,LOCAL_DDSC_LEN
rep movsb ; make a local copy of interesting bits
push es ! pop ds ; DS=ES=local data segment
mov ax,psecsiz ; now initialise some other vaiiables
mov cl,clshf
shl ax,cl ; AX = bytes per cluster
mov clsize,ax
xor ax,ax
mov al,clmsk
inc ax ; AX = sectors per cluster
mov secperclu,ax
mov al,byte_nfats ; AX = number of FATs
mov nfats,ax ; (it's handier as a word
mov ax,diradd ; number of FAT records can be
sub ax,fatadd ; bigger than 255
xor dx,dx
div nfats
mov nfatrecs,ax
mov cx,FCBLEN
mov ax,clsize ; convert from cluster size
xor dx,dx ; to number of dir entries
div cx ; per cluster - handy for
mov dirperclu,ax ; subdirectories
mov ax,FAT12
cmp lastcl,MAX12 ; is it a 12 bit FAT ?
jbe select_ddsc10
mov ax,FAT16 ; no, it's 16 bit
select_ddsc10:
mov dosfat,ax ; remember which for later
clc ; drive all selected
ret
eject
build_ddsc_from_bpb: ; call device driver to build BPB, convert to DDSC_
;-------------------
; On Entry:
; ES:BX -> DDSC_ to rebuild
; On Exit:
; ES:BX preserved
; CY set on error
; AL = error code
push es
push bx ; save DDSC_ address
xor di,di
mov ax,deblock_seg
mov es,ax ; ES:DI -> deblock seg
test ax,ax ; if we are deblocking spare buffer
jnz build_bpb10 ; might be in high memory
dec ax ; AX = FFFF
mov dx,ax ; compute impossible record #
mov cx,BF_ISDIR ; locate directory sector w/o preread
call locate_buffer ; this will find the cheapest buffer
mov es:BCB_DRV[si],0FFh ; don't really want this...
lea di,BCB_DATA[si] ; ES:DI -> disk buffer
build_bpb10:
mov req4_buffer,di ; xfer to ES:DI
mov req4_buffer+2,es
pop bx ; restore DDSC_ address
pop es
push ds
lds si,es:DDSC_DEVHEAD[bx] ; DS:SI -> device header
mov ax,ds:DH_ATTRIB[si] ; non-FAT ID driver ("non-IBM") bit
pop ds ; in device header attributes
test ax,DA_NONIBM
jnz bldbpb30 ; skip if media byte in FAT not used
mov req_rwmode,0 ; read of system area
mov req_len,RH4_LEN ; set length field
mov req_cmd,CMD_INPUT ; read first FAT sector off disk
if DOS5
test ax,DA_BIGDRV ; large sector numbers ?
endif
mov ax,1
mov req4_count,ax ; read 1st FAT sector
cwd ; DS:AX = sector 1
mov word ptr req4_bigsector,ax
mov word ptr req4_bigsector+2,dx
if DOS5
jz bldbpb20
dec ax ! dec ax ; AX = 0FFFFh
bldbpb20:
endif
mov req4_sector,ax ; set requested sector address
mov req4_sector+2,dx ; (support large DOS drives)
call block_device_driver ; try to read FAT sector, AX = status
js bldbpb_err ; skip if errors (AX negative)
bldbpb30:
mov req_len,RH2_LEN ; length of req
mov req_cmd,CMD_BUILD_BPB ; "build bpb"
call block_device_driver ; call the device driver
js bldbpb_err ; skip if errors (AX negative)
push ds
push es
push bx
mov di,bx ; ES:DI -> DDSC_ to initialise
lds si,dword ptr req2_bpb ; DS:SI -> BPB to convert
call bpb2ddsc ; rebuild the DDSC_
pop bx
pop es
pop ds
clc ; success - we have a new DDSC_
ret
bldbpb_err:
stc ; we had a problem
ret
eject
;-----------
check_media: ; check media if DPH media flag set
;-----------
; On Entry:
; ES:BX -> DDSC_ of physical drive to check
; On Exit:
; CY set on error, AX = error code
; else
; AX <> 0 if disk requires BPB rebuild
; If definite/possible change then LDT's marked as invalid
; If possible then buffers/hashing discarded provided they are clean
; If definite then all buffers/hashing for drive discarded even if dirty
;
mov req_len,RH1_LEN ; set length field
mov req_cmd,CMD_MEDIA_CHECK ; media check routine
call block_device_driver ; call the device driver
jns chkmed10
stc ; we have a problem, generate
ret ; an error
chkmed10:
mov al,req_media+1 ; else get returned value
xor ah,ah ; watch out for 1st access too..
xchg ah,es:DDSC_FIRST[bx] ; treat never accessed as changed
cmp al,1 ; 1 = no change
jne chkmed20
dec ax ; AL=0, build bpb only if DDSC_FIRST
; clc ; it all went OK
ret
chkmed20:
mov dl,adrive ; media may have/has changed
call mark_ldt_unsure ; so force LDT's to unsure
; AL = 00 if maybe changed, FF for definitely changed
test al,al
jz chkmed_maybe ; media may have changed
chkmed_changed: ; disk has changed for sure
call discard_files ; discard open files
jmps chkmed30 ; discard buffers, build bpb required
chkmed_maybe: ; disk has possibly changed
call discard_dir ; we can always discard dir as they
mov ah,BF_DIRTY ; won't be dirty
mov al,adrive
call buffers_check ; any dirty buffers on adrive?
jnz chkmed40 ; yes, can't discard FAT
chkmed30:
call discard_all ; discard buffers for drive
chkmed40:
or ax,0FFFFh ; better rebuild bpb
; clc
ret
Public mark_ldt_unsure
mark_ldt_unsure:
;---------------
; On Entry:
; DL = physical drive
; On Exit:
; All corresponding LDT's marked as unsure
; All reg preserved
;
push es
push ax
push bx
xor ax,ax ; start with drive A:
mlu10:
call get_ldt_raw ; ES:BX -> LDT_
jc mlu30 ; CY = no more LDT's
test es:LDT_FLAGS[bx],LFLG_NETWRKD+LFLG_JOINED
jnz mlu20 ; if networked leave it alone
cmp dl,es:LDT_DRV[bx] ; does the physical drive match ?
jne mlu20
mov es:LDT_BLK[bx],0FFFFh ; indicate we shouldn't trust BLK
mlu20:
inc ax ; onto next LDT
jmps mlu10
mlu30:
pop bx
pop ax
pop es
ret
;-----------
write_block:
;-----------
; entry: RWMODE = write type
; bit 0:
; 1 - write, not read
; bits 2-1 (affected disk area)
; 0 0 - system area
; 0 1 - FAT area
; 1 0 - root or sub directory
; 1 1 - data area
or rwmode,1 ; mark it as a write
xor cx,cx ; indicate no second attempt
mov al,CMD_OUTPUT ; assume normal write
cmp verify_flag,0 ; is verify on ?
je rdwr_block
mov al,CMD_OUTPUT_VERIFY ; assume use write w/ verify
jmps rdwr_block
;----------
read_block:
;----------
; entry: RWMODE = read type
; bit 0:
; 0 - read, not write
; bits 2-1 (affected disk area)
; 0 0 - system area
; 0 1 - FAT area
; 1 0 - root or sub directory
; 1 1 - data area
; CX <> 0 if FAT retry possible (critical error should then
; be avoided)
; exit: SF = 0 if success
; SF = 1 if failure (CX was non-zero on call)
and rwmode,not 1 ;mark it as a read
mov al,CMD_INPUT
rdwr_block:
push cx
call blockif ;current drive, track,....
pop cx
jns rdwrb5
jcxz rdwrb10 ; test if any disk error detected
rdwrb5:
ret ; skip if yes
rdwrb10:
mov ioexerr,al ; save extended error
test al,al ; is it write protect error ?
jnz rdwrb20 ; we have dirty buffers we can't write
call discard_dirty ; out, so throw 'em away
rdwrb20:
mov al,adrive ; if error on different drive
cmp al,physical_drv ; treat error as media change
je generate_critical_error ; if same drive, report error
call discard_all ; discard all buffers on drive
call discard_files ; and flush files
jmp fdos_restart ; try to restart the instruction
generate_critical_error:
;-----------------------
; On Entry:
; err_drv, rwmode, ioexerr set up
; On Exit:
; None - we don't come back
;
mov al,ioexerr ; AL = BIOS error return byte
cbw ; make it a word
cmp ax,15 ; only handle sensible errors
jb gen_crit_err10 ; anything else becomes
mov ax,12 ; general failure
gen_crit_err10:
neg ax ; convert to our negative errors
add ax,ED_PROTECT ; and start with write protect
jmp fdos_error ; now return with error
eject
clus2sec: ; convert from cluster/offset to sector/offset
;--------
; On Entry:
; AX = cluster
; BX = byte offset in cluster
; On Exit:
; DX:AX = sector
; BX = byte offset in sector
;
xchg ax,cx ; remember cluster in CX
xor dx,dx
xchg ax,bx ; DX:AX = byte offset
div psecsiz ; AX = sector offset, DX = byte offset
mov bx,dx ; BX = byte offset in sector
xchg ax,cx ; AX = cluster, CX = sector offset
dec ax
dec ax ; forget about 2 reserved clusters
mul secperclu ; DX:AX = offset of cluster
add ax,datadd
adc dx,0 ; DX:AX = offset of start of dir
add ax,cx ; DX:AX - add in sector offset
adc dx,0
ret
end