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
