
	title	Fake hard disk driver
	subttl	Prologue
	page	60,132

comment	{

******************************************************************************

File VHARD.ASM

Author:
	Aaron L. Brenner

	BIX mail address albrenner
	GEnie address A.BRENNER

	This program is hereby released to the public domain.

Purpose:
	A DOS device driver that will make a floppy drive double as a small
	hard disk.

	This is accomplished by spreading the fake HD over a series of
	diskettes. The disks must be prepared (with VHPREP) by formatting to
	10 sectors/track for a capacity of 400KB per diskette. Also, VHPREP
	will stamp each diskette with a unique ID built from the date and time
	the first diskette was prepared and the sequence number of it. VHPREP
	is also responsible for performing the low and high level format on
	all disks. Formatting is performed by the driver, but under VHPREP
	control.

	VHPREP communicates with the driver via a dummy character device
	driver named VHARDCTL, which will provide access to driver functions.
	The only driver command supported is IOCTL Write (all others are
	NOPs), and only 7 bytes are actually used. These 7 bytes are treated
	as a command block, structured as follows:

	Format command
	byte 	0
	byte	track to format (head in bit 7, cylinder in low 7 bits)
	byte	return status
	dword	pointer to format parameter buffer (see details in int 13h)

	Read Track command
	byte	1
	byte	track to read (head in bit 7, cylinder in low 7 bits)
	byte	return status
	dword	pointer to buffer to read into

	Write Track command
	byte	2
	byte	track to write (head in bit 7, cylinder in low 7 bits)
	byte	return status
	dword	pointer to buffer to write from

	Verify Track command
	byte	3
	byte	track to verify (head in bit 7, cylinder in low 7 bits)
	byte	return status

	Set FAT cache buffer
	byte	4
	byte	== 0, disable cache
		== 1, enable cache
		== 2, flush cache
		== 3, disable cache autoflush
		== 4, enable cache autoflush (DOS 3+, SHARE installed)
		== 5, return information about the cache
	byte	return status
	dword	pointer to cache buffer
	This command is used to enable or disable the FAT/directory cache.
	Once the cache is enabled, logical sectors 1 through 24 will be cached
	for both read and write. They will only be written when driver
	function 14 (Device close) is called AND the reference count is 0.
	To accomodate older versions of DOS, subcommand 2 will manually flush
	the cache to disk. Subcommands 3 & 4 will disable or enable the
	auto-flush.

	Retrieve Driver Data
	byte	5
	byte	unused
	byte	set to 0 by VHARD
	dword	pointer to buffer to receive the following data:
		byte		drive number assigned by DOS
		word		VHARD version
		19 bytes	BPB for VHARD

Revision history:
0.90	06/27/90	ALB	Created. This version uses 13 360KB diskettes
				for appx. 5MB of faked hard disk space. Note:
				cache handling is NOT yet implemented.

******************************************************************************

endcomment {

	subttl	Included files
	page

	include	dd_defs.inc
	include	vhard.inc		; Some structures we use

DEBUG	equ	0


	subttl	Macros and templates
	page

BIOS_seg	segment at 40h

	org	62h
active_page	db	?

	org	84h
crt_lines	db	?

BIOS_seg	ends


;*****************************************************************************
;
; Version of VHARD
;
;*****************************************************************************
VHARD_version	equ	100h
VHARD_text_ver	equ	<'1.00'>


	subttl	Device driver headers
	page

vhard_seg	segment

	org	0

assume	cs:vhard_seg, ds:nothing, es:nothing, ss:nothing

;
; Declare the header for the dummy char device used to communicate with
; the block driver
;
		dw	offset next_hdr
nextdvrseg	dw	-1
		dw	DA_IS_CHAR or DA_IOCTL
		dw	strategy
		dw	du_int
		db	'VHARDCTL'

;
; Declare the header for the device driver
; Attributes:
;	block device
;	removable media
;
next_hdr	dw	-1, -1
		dw	DA_IS_BLK or DA_IS_REM
		dw	strategy
		dw	vh_int
		db	1, 'VHARD  '

	subttl	Device driver data
	page

;
; Pointer passed by DOS to the request packet
;
req_ptr		dw	0, 0

;
; Version of DOS
;
dos_ver		dw	0

;
; Local copy of the diskette parameters
;
diskparms	db	4 dup(0)	; Gets filled in at init time
sect_per_track	db	0		; Sectors per track (set at init time)
gap_len		db	0
		db	0
fmt_gap_len	db	0
		db	3 dup(0)

;
; Save area for old parm pointer
;
old_pptr	dw	0, 0

;
; ID and # for the current disk
;
curr_disk_id	db	'NO NAME',0
		db	0
curr_disk_num	db	0ffh

;
; Counter of how many sectors read/written
;
io_count	dw	0

;
; Count of how many sectors to go
;
io_to_go	dw	0

;
; Flag indicating whether or not we have EGA/VGA video available. This is used
; in the prompt_user routine - if have_evga != 0, then we read the number of
; character lines on screen from 40:84. If have_evga == 0, then we always use
; line 24.
;
have_evga	db	0
last_curs	dw	0

;
; If we're running under DOS 3.10 or higher, we get the drive number assigned
; from the the request block and save it here.
;
our_drive	db	0ffh

;
; Data used to manage the FAT/dir cache.
;
cache_ptr	dw	0,0		; Pointer to the cache
cache_flags	db	0		; Bit flags

;
; Messages that get displayed at various times
;
need_disk	db	'Put disk ID '
pr_disk_id	db	8 dup(' ')
		db	' #'
pr_disk_num	db	'  '
		db	' in drive A:, then press a key',0

need_first_disk	db	'Put disk #0 of the set you want to use'
		db	' in drive A:, then press a key',0

;
; Save area for the chunk of screen we diddle
;
screen_save	dw	80 dup(0)

;
; Buffer for the boot sector (containing ID, etc.)
;
boot_sect_buf	db	512 dup(0)

;*****************************************************************************
;
; These 2 tables are used to translate BIOS error codes into the status codes
; expected by DOS and VHPREP, respectively
;
;*****************************************************************************
DOS_sts_table	db	80h,	DE_NOT_READY
		db	40h,	DE_SEEK_ERROR
		db	20h,	DE_GEN_FAIL
		db	10h,	DE_CRC_ERROR
		db	9,	DE_WRITE_FAULT
		db	8,	DE_WRITE_FAULT
		db	4,	DE_NOT_FOUND
		db	3,	DE_WRITE_PROT
		db	2,	DE_GEN_FAIL
		db	1,	DE_GEN_FAIL

CMD_sts_table	db	80h,	STS_TIMEOUT
		db	40h,	STS_BAD_SEEK
		db	20h,	STS_BAD_NEC
		db	10h,	STS_BAD_CRC
		db	9,	STS_DMA_BOUND
		db	8,	STS_BAD_DMA
		db	4,	STS_NOT_FND
		db	3,	STS_WRITE_PROT
		db	2,	STS_ADDR_MARK
		db	1,	STS_BAD_CMD


;*****************************************************************************
;
; BIOS parameter block (BPB) that DOS is going to use
;
;*****************************************************************************
our_bpb	DOS_BPB	<512, 8, 1, 2, 256, (13 * 799), 0fch, 4, 47, 26, 0>

our_bpb_array	dw	offset our_bpb


;*****************************************************************************
;
; For this version, at least, this driver will support the OPEN and CLOSE
; calls. These will increment and decrement a reference counter that will be
; used to know when to the write the FAT cache (if caching is enabled).
;
;*****************************************************************************
ref_count	dw	0


	subttl	Device driver STRATEGY and INTERRUPT routines
	page

;*****************************************************************************
;
; Strategy entry point. Used for both drivers
;
;*****************************************************************************
strategy	proc	far

	mov	cs:req_ptr[0],bx	; Save the pointer
	mov	cs:req_ptr[2],es	;
	ret				; Return to DOS

strategy	endp


;*****************************************************************************
;
; Interrupt routine for the VH driver
;
;*****************************************************************************
vh_int		proc	far

assume	ds:nothing, es:nothing

	call	save_regs		; Save the registers
	cld				; Make sure of direction
	push	cs			; Set DS to our segment
	pop	ds			;

assume	ds:vhard_seg

if	DEBUG
	call	dump_entry
endif

	les	si,dword ptr req_ptr	; Point to the request packet
	mov	bl,es:[si].DDrh_command	; Get the command
	mov	es:[si].DDrh_status,0	; Default to no error
	cmp	bl,DC_REM_MEDIA		; Check for the highest we support
	jbe	vhint_l1		; Use it - it's in range
	call	err_fnc			; Return that it's a bad command
	jmp	short vhint_exit	; And return
vhint_l1:
	sub	bh,bh			; Make the command a word
	shl	bx,1			; Now a word offset
	call	vh_dispatch[bx]		; Vector to the routine
	les	si,dword ptr req_ptr	; Point back to the request packet
	or	es:[si].DDrh_status,DS_DONE; Say we're done
vhint_exit:

if	DEBUG
	call	dump_exit
endif

	call	rest_regs		; Restore the registers
	ret				; Return to DOS

vh_int		endp


if	DEBUG
entry_str	db	'Entry:',0
exit_str	db	'Exit: ',0

dump_entry	proc	near

	mov	si,offset entry_str
	jmp	short dump_either

dump_entry	endp

dump_exit	proc	near

	mov	si,offset exit_str
dump_either:
	call	outp_str
	les	si,req_ptr
	mov	cl,es:[si]
	sub	ch,ch
dmpe_l1:
	mov	al,' '
	call	outp_char
	lods	byte ptr es:[si]
	call	outp_byte
	loop	dmpe_l1
	mov	al,13
	call	outp_char
	mov	al,10
	call	outp_char
	ret

dump_exit	endp

outp_str	proc	near

	lodsb
	or	al,al
	jz	otps_l1
	call	outp_char
	jmp	short outp_str
otps_l1:
	ret

outp_str	endp
	
outp_char	proc	near

	push	ax
	push	dx
	sub	ah,ah
	sub	dx,dx
	int	17h
	pop	dx
	pop	ax
	ret

outp_char	endp

outp_byte	proc	near

	push	ax
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1
	call	outp_nyb
	pop	ax
outp_nyb:
	and	al,0fh
	add	al,90h
	daa
	adc	al,40h
	daa
	call	outp_char
	ret

outp_byte	endp
endif

;*****************************************************************************
;
; Interrupt routine for the dummy VHARDCTL driver
;
;*****************************************************************************
du_int		proc	far

	call	save_regs		; Save all the registers
	push	cs			; Set DS to our segment
	pop	ds

assume	ds:vhard_seg

	les	si,dword ptr req_ptr	; Point to the request packet
	mov	bl,es:[si].DDrh_command	; Get the command
	cmp	bl,DC_IOCTL_WRITE	; Check for the highest we support
	jbe	duint_l1		; Use it - it's in range
	call	err_fnc			; Return that it's a bad command
	jmp	short duint_exit	; And return
duint_l1:
	sub	bh,bh			; Make the command a word
	shl	bx,1			; Now a word offset
	call	du_dispatch[bx]		; Vector to the routine
	les	bx,dword ptr req_ptr	; Point back to the request packet
	or	es:[bx].DDrh_status,DS_DONE; Say we're done
	mov	es:[bx].DDir_count,size VH_CMD
duint_exit:
	call	rest_regs		; Restore them now
	ret				; Return to DOS

du_int		endp


	subttl	Dispatch tables for INTERRUPT routines
	page

;*****************************************************************************
;
; Dispatch table for the VH driver
;
;*****************************************************************************
vh_dispatch	dw	drv_init	; 0 - Init
		dw	vh_med_check	; 1 - Media check
		dw	vh_build_bpb	; 2 - Build BPB
		dw	err_fnc		; 3 - IOCTL read (not supported)
		dw	vh_read		; 4 - Read
		dw	err_fnc		; 5 - Peek input (not for blk)
		dw	err_fnc		; 6 - Input status (not for blk)
		dw	err_fnc		; 7 - Input flush (not for blk)
		dw	vh_write	; 8 - Write
		dw	vh_write	; 9 - Write w/verify
		dw	err_fnc		; A - Output status (not for blk)
		dw	err_fnc		; B - Output flush (not for blk)
		dw	err_fnc		; C - IOCTL write (not supported)
		dw	vh_open		; D - Device open
		dw	vh_close	; E - Device close
		dw	vh_set_rem	; F - "Removable media" check

du_dispatch	dw	du_init		; 0 - Init
		dw	err_fnc		; 1 - Media check
		dw	err_fnc		; 2 - Build BPB
		dw	err_fnc		; 3 - IOCTL read (not supported)
		dw	err_fnc		; 4 - Read
		dw	err_fnc		; 5 - Peek input (not for blk)
		dw	err_fnc		; 6 - Input status (not for blk)
		dw	err_fnc		; 7 - Input flush (not for blk)
		dw	err_fnc		; 8 - Write
		dw	err_fnc		; 9 - Write w/verify
		dw	err_fnc		; A - Output status (not for blk)
		dw	err_fnc		; B - Output flush (not for blk)
		dw	du_write	; C - IOCTL Write


	subttl	Error function
	page

;*****************************************************************************
;
; Error function - sets the error bit to 1, and the error code to 3 (Unknown
; command).
;
;*****************************************************************************
err_fnc		proc	near

	mov	al,DE_BAD_COMMAND	; Set error code
;
; Alternate entry point. Call with AL set to error code
;
err_return:
	mov	ah,DS_ERROR shr 8	; Set ERROR bit
	les	bx,dword ptr req_ptr	; Point to the request packet
	mov	es:[bx].DDrh_status,ax	; Set return status
	ret				; Return to caller

err_fnc		endp


	subttl	Driver function 1 - Media check
	page

;*****************************************************************************
;
; Driver function 1 - Media check
;
;*****************************************************************************
vh_med_check	proc	near

assume	ds:vhard_seg, es:nothing

	call	set_disk_parms		; Set up the diskette parameters
	call	check_boot_sect		; See if the disk ID has changed
	jc	vhmc_error		; Exit if error
	jz	vhmc_nochange		; Return properly if no change
	les	si,dword ptr req_ptr	; Point to the DOS request block
	mov	es:[si].DDmr_return,0ffh; Note that it's changed
	test	cache_flags,CACHE_INSTALLED	; Is the cache there?
	jz	vhmc_l1				; Nope - never mind
	test	cache_flags,CACHE_DIRTY	; Is there anything in it?
	jz	vhmc_l1			; Nope - never mind
	call	do_flush_cache		; Flush the FAT cache
vhmc_l1:
	and	cache_flags,not (CACHE_VALID or CACHE_DIRTY)
	mov	ref_count,0		; Force the ref count to 0
	call	get_disk_0		; Make sure we get the first disk
	cmp	byte ptr dos_ver[0],3	; Is it DOS 3.x (or higher)?
	jb	vhmc_exit		; Nope - exit
	mov	word ptr es:[si].DDmr_idptr[0],offset curr_disk_id
	mov	word ptr es:[si].DDmr_idptr[2],cs
	jmp	short vhmc_exit		;
vhmc_nochange:
	les	si,dword ptr req_ptr	; Point to the DOS request block
	mov	es:[si].DDmr_return,1	; Set the return value
	test	cache_flags,CACHE_INSTALLED	; Is the cache there?
	jz	vhmc_exit		; No - never mind
	test	cache_flags,CACHE_VALID	; Does it hold valid data?
	jnz	vhmc_exit		; Yep - just exit
	call	get_disk_0		; Load the cache
	jmp	short vhmc_exit		; Exit now
vhmc_error:
	mov	ax,DE_NOT_READY		;
	call	err_return		; Indicate the error
vhmc_exit:
	call	rest_disk_parms		; Restore the diskette parameters
	ret				; Return to dispatcher

vh_med_check	endp


	subttl	Driver function 2 - Build BPB
	page

;*****************************************************************************
;
; Driver function 2 - Build BPB
;
; Also, the disk ID is gotten from the disk in the drive. If there is no disk
; in the drive, we prompt for it.
;
;*****************************************************************************
vh_build_bpb	proc	near

assume	ds:vhard_seg, es:nothing

	save	si, es
	mov	word ptr es:[si].DDbr_bpb_ptr[0],offset our_bpb
	mov	word ptr es:[si].DDbr_bpb_ptr[2],cs
	call	get_disk_0		; Get the first disk
	jnc	vhbb_exit		; If they didn't press Esc, exit
	mov	ax,DE_NOT_READY		; Say that the drive wasn't ready
	call	err_return		;
vhbb_exit:
	restore
	ret				; Return to DOS

vh_build_bpb	endp


	subttl	Driver function 4 - Read
	page

;*****************************************************************************
;
; Driver function 4 - Read
;
;*****************************************************************************
vh_read		proc	near

assume	ds:vhard_seg, es:nothing

	mov	bp,offset sect_read	; Address of I/O routine
	jmp	short vhwt_l1		; Do the I/O operation

vh_read		endp


	subttl	Driver functions 8 & 9 - Write & Write w/Verify
	page

;*****************************************************************************
;
; Driver function 8 - Write (also fn 9, Write w/Verify)
;
;*****************************************************************************
vh_write	proc	near

assume	ds:vhard_seg, es:nothing

	mov	bp,offset sect_write	; Point to the right routine
vhwt_l1:
	call	set_disk_parms		; Set up the diskette parameters
	mov	ax,es:[si].DDir_start	; Get starting sector
	mov	cx,es:[si].DDir_count	; Get the count
	les	bx,es:[si].DDir_buffer	; Point to the buffer
	or	ax,ax			; See if DOS wants a sector that falls
	jz	vhwt_l99		;  in the range 1 to 24
	cmp	ax,24			;
	ja	vhwt_l99		;
	test	cache_flags,CACHE_INSTALLED	; Is the cache there?
	jz	vhwt_l99		; No - never mind
	test	cache_flags,CACHE_VALID	; Do we have valid data in the cache?
	jz	vhwt_l99		; No - read from disk
	dec	ax			; Adjust start sector for cache
	push	cx			; Save the count
	mov	cl,9			; Make sector # offset into cache
	shl	ax,cl			;
	pop	cx
	mov	dx,cx			; Turn sector count into byte count
	mov	cl,9			;
	shl	dx,cl			;
	mov	cx,dx			;
	cmp	bp,offset sect_read	; Are we reading?
	jne	vhwt_l2			; No - set up for writing
	lds	si,dword ptr cache_ptr	; Point to the cache
	add	si,ax			; Point to start of first sector
	mov	di,bx			; Point into the buffer
	jmp	short vhwt_l3		; Continue
vhwt_l2:
	or	cache_flags,CACHE_DIRTY	; The cache is dirty now
	push	es			; Need DS:SI pointing to the buffer
	pop	ds			;
	mov	si,bx			;
	les	di,dword ptr cs:cache_ptr; And ES:DI pointing to the cache
	add	di,ax			; Point to the sector in the cache
vhwt_l3:
	shr	cx,1			; Move as many words as possible
	rep	movsw			;
	rcl	cx,1			; Move the odd byte
	rep	movsb			;
	push	cs			; Set DS back to our segment
	pop	ds			;
	jmp	short vhwt_exit		; Exit now
vhwt_l99:
	call	io_operation		; Do the I/O operation
	jnc	vhwt_exit		; Exit if no error
	mov	bx,offset DOS_sts_table	; Point to translation table
	call	xlat_status		; Make it a DOS error code
	cmp	al,DE_WRITE_FAULT	; Generic "Write Fault"?
	jne	vhwt_err		; No - just exit
	cmp	bp,offset sect_write	; Was it a write op?
	je	vhwt_err		; Yep - never mind
	mov	al,DE_READ_FAULT	; Make that a "Read Fault"
vhwt_err:
	call	err_return		; Return the error
vhwt_exit:
	call	rest_disk_parms		; Restore the disk parameters
	ret				; Return to int routine

vh_write	endp


;*****************************************************************************
;
; Perform a disk I/O operation.
;
; Input:
;	AX		starting sector
;	CX		# of sectors
;	ES:BX		pointer to buffer
;	BP		address of routine to call to read or write
;
; Output:
;	CF = 1		an error occurred
;			AL = error code to return
;	CF = 0:
;	CX		number of sectors actually transferred
;
; Registers:
;	All but AX and CX preserved.
;
;*****************************************************************************
io_operation	proc	near

	mov	io_to_go,cx		; Save count as number to do
	mov	di,ax			; Save start sector for a bit
	sub	ax,ax			; Figure out if it could wrap
	dec	ax			;
	sub	ax,bx			;
	mov	cl,9			;
	shr	ax,cl			;
	cmp	ax,io_to_go		; Can it hold that many?
	jae	io_op1			; Yep - never mind
	mov	io_to_go,ax		; Set to just that many
io_op1:
	sub	ax,ax			; Clear the current count
	mov	io_count,ax		;
	mov	ax,di			; Get the starting sector back
	call	calc_disk		; Figure out where that is
	call	get_num_disk		; Get the right disk
	jc	io_op_error		; Don't bother if they hit Esc or error
;
; At this point, we have figured out what disk to use, and it's in the drive.
; Now, all we have to do is start reading or writing.
;
	mov	al,11			; Figure out how many sectors to start
	sub	al,cl			;
	cbw				;
	mov	si,ax			; Save it for later
io_op3:
	cmp	si,io_to_go		; See if there are that many to go
	jbe	io_op4			; Yep - just do the operation
	mov	si,io_to_go		; Just do that many
	mov	ax,si			;
io_op4:
	call	bp			; Call the appropriate routine
	jc	io_op_err2		; Exit if error
io_op5:
	add	io_count,si		; Add to number complete
	sub	io_to_go,si		; Knock that many off the count to do
	jz	io_op_exit		; If it went to 0, we're done
	mov	ax,si			;
	push	cx			; Save it (gets used for shift count)
	mov	cl,9			; Multiply by 512
	shl	ax,cl			;
	pop	cx			;
	add	bx,ax			; Move the buffer pointer
	jnc	io_op6			; If it wraps, we're done
	clc				; No error
	jmp	short io_op_exit	; Just exit
io_op6:
	mov	al,10			; Do 10 the next time 'round
	cbw				;
	mov	si,ax			;
	mov	cl,1			; Start at sector 1
	xor	dh,1			; Next head (0 or 1)
	jnz	io_op3			; If just going to other head, loop
	inc	ch			; Next track
	cmp	ch,40			; Still on this disk?
	jb	io_op3			; Yep - do more I/O
	inc	curr_disk_num		; Next disk
	mov	al,curr_disk_num	; Get the disk number
	call	get_num_disk		; Get that disk in the drive
	jc	io_op_error		; Exit if error or Esc pressed
	mov	cx,2			; Start back at track 0, sector 2
	mov	ax,9			; Do up to 9 more sectors on next disk
	mov	si,ax			;
	jmp	short io_op3		; Loop back
io_op_error:
	mov	ah,80h			; Say that it wasn't ready
io_op_err2:
	stc				;
io_op_exit:
	mov	cx,io_count		; Get number transferred
	ret				; Return to caller

io_operation	endp


	subttl	Driver function 0DH - Device open
	page

;*****************************************************************************
;
; Driver function 0DH - Device open
;
;*****************************************************************************
vh_open	proc	near

	inc	ref_count		; Just increment the reference count
	ret				; Return to caller

vh_open	endp


	subttl	Driver function 0EH - Device close
	page

;*****************************************************************************
;
; Driver function 0EH - Device close
;
;*****************************************************************************
vh_close	proc	near

	dec	ref_count		; Decrement the reference count
	jnz	vhcl_exit		; If there are still files open, exit
	test	cache_flags,CACHE_INSTALLED	; Is the cache there?
	jz	vhcl_exit			; Nope - just exit
	test	cache_flags,CACHE_AUTO	; Do we want to flush automagically?
	jz	vhcl_exit		; Nope - just exit
	test	cache_flags,CACHE_DIRTY	; Is the cache dirty?
	jz	vhcl_exit		; Nope - never mind
	call	set_disk_parms		; Set up the diskette parameters
	call	do_flush_cache		; Flush that cache
	call	rest_disk_parms		; Get the old diskette parms back
vhcl_exit:
	ret				; Return to caller

vh_close	endp


	subttl	Driver function 0FH - "Removable media" check
	page

;*****************************************************************************
;
; Driver function 0FH - "Removable media" check
;
;*****************************************************************************
vh_set_rem	proc	near

	and	es:[si].DDrh_status,not DS_BUSY	; Say we're removable
	ret				; Return to caller

vh_set_rem	endp


	subttl	Cache functions
	page

;*****************************************************************************
;
; Flush the FAT/root dir cache
;
; Registers:
;	All preserved.
;
;*****************************************************************************
do_flush_cache	proc	near

assume	ds:vhard_seg, es:nothing

	save	ax, bx, cx, bp, es
	les	bx,dword ptr cache_ptr	; Point to the cache
	mov	bp,offset sect_write	; Routine to do the I/O
dflc_l1:
	mov	ax,1			; Start with logical sector 1
	mov	cx,24			; Write 24 Sectors
	call	io_operation		;
	jc	dflc_l1			; No matter what, it must be done
	and	cache_flags,not CACHE_DIRTY	; Turn off the "dirty" bit
	restore
	ret				; Return to caller

do_flush_cache	endp


	subttl	VHARDCTL IOCTL write function
	page

;*****************************************************************************
;
; This is the only driver function supported by the dummy control driver
; VHARDCTL. It dispatches to one of the commands.
; On entry, ES:SI points to the driver request packet.
;
;*****************************************************************************
du_write	proc	near

assume	ds:vhard_seg, es:nothing

	mov	es:[si].DDrh_status,0	; Start with no error
	les	bx,es:[si].DDir_buffer	; Point to the buffer
	mov	al,es:[bx].VC_cmd_code	; Get the command to perform
	cmp	al,LAST_CMD		; Valid command?
	jbe	duwr_ok			; Yep - dispatch it
	call	err_fnc			; Report an invalid driver command
	jmp	short duwr_exit		; Exit now
duwr_ok:
	cbw				; Make it a word
	shl	ax,1			; Make it a dispatch table offset
	mov	di,ax			; Into a usable register
	call	cmd_dispatch[di]	; Call the routine
duwr_exit:
	ret				; Return to caller

du_write	endp

	subttl	VHARDCTL handling routines
	page

;*****************************************************************************
;
; The following routines handle the commands to VHARDCTL. Each of them expects
; ES:BX to point to the command block (NOT the driver request block!), and
; DS to be the same as CS.
;
;*****************************************************************************


;*****************************************************************************
;
; Called when VHARDCTL gets a "Format Track" command
;
;*****************************************************************************
cmd_format_trk	proc	near

assume	ds:vhard_seg, es:nothing

	push	bp			; Save retry counter
	call	set_disk_parms		; Set up the diskette parameters
	mov	al,es:[bx].VC_track	; Get the track/head to format
	mov	ch,7fh			; Mask to keep track
	and	ch,al			; Get the track
	rol	al,1			; Get the head
	and	al,1			;
	mov	dh,al			; Get the head in the right place
	mov	dl,$OUR_DRIVE		;
	mov	bp,3			; 3 tries
cfmt_l1:
	mov	ax,50ah			; Format command
	push	es			; Save these
	push	bx			;
	les	bx,es:[bx].VC_buffer	; Point to the buffer
	int	13h			; Call BIOS to do it
	pop	bx			; Get pointer back
	pop	es			;
	jnc	cfmt_l3			; If it went, continue
	dec	bp			; Count off a try
	jz	cfmt_l2			; If it failed, return error
	mov	ah,0			; Do a disk reset
	int	13h			;
	jnc	cfmt_l1			; If it FAILED to reset, drop
cfmt_l2:
	push	bx			; Save cmd block ptr
	mov	bx,offset CMD_sts_table	; Point to translation table
	call	xlat_status		; Translate to our error code
	pop	bx			; Point back to the cmd block
	mov	es:[bx].VC_status,al	; Set the return status
	jmp	short cfmt_exit		; Exit now
cfmt_l3:
	mov	es:[bx].VC_status,STS_OK; Report success
cfmt_exit:
	call	rest_disk_parms		; Restore the diskette parameters
	pop	bp			; Get back what we used
	ret				; Return to caller

cmd_format_trk	endp


;*****************************************************************************
;
; Called when VHARDCTL gets a "Read Track" command
;
;*****************************************************************************
cmd_read_trk	proc	near

	call	set_disk_parms		; Set up the diskette parameters
	mov	al,es:[bx].VC_track	; Get the track to read
	mov	ch,7fh			; Get mask for the track
	and	ch,al			; Get the track
	mov	cl,1			; Start with sector 1
	rol	al,1			; Get the head
	and	al,1			; Mask it out
	mov	dh,al			; Into the right register
	mov	dl,$OUR_DRIVE		; Drive to read from
	mov	al,10			; Number to read
	push	bx			; Save this ptr so we can set status
	push	es			;
	les	bx,es:[bx].VC_buffer	; Point to the buffer
	call	sect_read		; Try to read 'em
	pop	es			; Restore the pointer
	pop	bx			;
	jnc	crdt_ok			; If ok, return success
	push	bx
	mov	bx,offset CMD_sts_table	; Point to the translation table
	call	xlat_status		; Get the status code we want
	pop	bx			; Get the pointer back
	mov	es:[bx].VC_status,al	; Set the return status
	jmp	short crdt_exit		; Exit now
crdt_ok:
	mov	es:[bx].VC_status,STS_OK; Return that it succeeded
crdt_exit:
	call	rest_disk_parms		; Restore the diskette parameters
	ret				; Return to caller

cmd_read_trk	endp


;*****************************************************************************
;
; Called when VHARDTCL gets a "Write Track" command
;
;*****************************************************************************
cmd_write_trk	proc	near

	call	set_disk_parms		; Set up the diskette parameters
	mov	al,es:[bx].VC_track	; Get the track to write
	mov	ch,7fh			; Get mask for the track
	and	ch,al			; Get the track
	mov	cl,1			; Start with sector 1
	rol	al,1			; Get the head
	and	al,1			; Mask it out
	mov	dh,al			; Into the right register
	mov	dl,$OUR_DRIVE		; Drive to write to
	mov	al,10			; Number to write
	push	bx			; Save this ptr so we can set status
	push	es			;
	les	bx,es:[bx].VC_buffer	; Point to the buffer
	call	sect_write		; Try to write 'em
	pop	es			; Restore the pointer
	pop	bx			;
	jnc	cwrt_ok			; If ok, return success
	push	bx
	mov	bx,offset CMD_sts_table	; Point to the translation table
	call	xlat_status		; Get the status code we want
	pop	bx			; Get the pointer back
	mov	es:[bx].VC_status,al	; Set the return status
	jmp	short cwrt_exit		; Exit now
cwrt_ok:
	mov	es:[bx].VC_status,STS_OK; Return that it succeeded
cwrt_exit:
	call	rest_disk_parms		; Restore the diskette parameters
	ret				; Return to caller

cmd_write_trk	endp


;*****************************************************************************
;
; Called when VHARDCTL gets a "Verify Track" command
;
;*****************************************************************************
cmd_verify_trk	proc	near

	call	set_disk_parms		; Set up the diskette parameters
	mov	al,es:[bx].VC_track	; Get the track to verify
	mov	ch,7fh			; Get mask for the track
	and	ch,al			; Get the track
	mov	cl,1			; Start with sector 1
	rol	al,1			; Get the head
	and	al,1			; Mask it out
	mov	dh,al			; Into the right register
	mov	dl,$OUR_DRIVE		; Drive to read from
	mov	al,10			; Number to verify
	call	sect_verify		; Try to verify 'em
	jnc	crdt_ok			; If ok, return success
	push	bx
	mov	bx,offset CMD_sts_table	; Point to the translation table
	call	xlat_status		; Get the status code we want
	pop	bx			; Get the pointer back
	mov	es:[bx].VC_status,al	; Set the return status
	jmp	short crdt_exit		; Exit now
cvft_ok:
	mov	es:[bx].VC_status,STS_OK; Return that it succeeded
cvft_exit:
	call	rest_disk_parms		; Restore the diskette parameters
	ret				; Return to caller

cmd_verify_trk	endp


;*****************************************************************************
;
; Called when VHARDCTL gets a "Set Cache" command
;
; There are 5 sub-commands supported. The subcommand is gotten from the byte
; used by the other routines to specify the track.
;
; The 6 sub-commands are:
;	0	disable the cache
;		On return, the status byte will be set to either STS_OK,
;		STS_NOT_ENAB (meaning that the cache is not installed), or
;		STS_CACHE_DIRTY (meaning that the cache needs to be flushed).
;		If the cache is successfully disabled, the VC_track byte will
;		be set to the current flags for the cache and the VC_buffer
;		will be set to the address of the cache.
;	1	enable the cache
;		On return, the status byte will be set to either STS_OK or
;		STS_ALREADY (meaning that the cache is already installed).
;		If the cache is already installed, the VC_track byte will be
;		set to the current flags for the cache and the VC_buffer will
;		be set to the current cache address.
;	2	flush the cache
;		On return, the status byte will be set to indicate the result.
;		If the cache isn't dirty, this is a no-op.
;
;	3	disable cache autoflush
;
;	4	enable cache autoflush. VHCACHE will only make this call if
;		the DOS version is 3.00 or higher AND SHARE.EXE is loaded.
;
;	5	get cache status. Returns the following at VC_buffer:
;			byte	cache flags
;			dword	address of cache
;
;*****************************************************************************
cmd_set_cache	proc	near

	mov	al,es:[bx].VC_track	; Get the sub-command
	cmp	al,5			; See if it's valid
	jbe	cmsc_l1			; Yep - handle it
	mov	es:[bx].VC_status,STS_UNK_CMD	; Return a bad command
	ret				; Return to caller
cmsc_l1:
	cbw				; Make it a word
	shl	ax,1			; Make it a table offset
	mov	di,ax			; Into a pointer reg
	jmp	cache_dispatch[di]	; Switch to the appropriate routine

cmd_set_cache	endp


;*****************************************************************************
;
; Handle a "Cache Disable" command
;
; If the cache isn't enabled, return STS_NOT_ENAB.
; If the CACHE_DIRTY bit is set (meaning that the cache needs to be flushed),
; set ES:[BX].VC_track to the current cache flags, set ES:[BX].VC_buffer to
; point to the cache, and return STS_CACHE_DIRTY.
;
; Otherwise, disable it, set ES:[BX].VC_track to the current
; cache flags, set ES:[BX].VC_buffer to point to the cache, and return STS_OK.
;
;*****************************************************************************
disable_cache	proc	near

	test	cache_flags,CACHE_INSTALLED	; Is the cache installed?
	jnz	dsch_l1				; Yep - see if it's dirty
	mov	es:[bx].VC_status,STS_NOT_ENAB	; Return it wasn't installed
	jmp	short dsch_exit			; Exit now
dsch_l1:
	test	cache_flags,CACHE_DIRTY		; Is the cache dirty?
	jz	dsch_l2				; Nope - disable it
	mov	al,cache_flags			; Get the current flags
	mov	es:[bx].VC_track,al		; Return 'em
	mov	al,STS_CACHE_DIRTY		; Report it's dirty
	jmp	short dsch_l3			; Continue
dsch_l2:
	mov	al,STS_OK			; Return it's ok
	mov	cache_flags,0			; Get rid of all cache flags
dsch_l3:
	mov	es:[bx].VC_status,al		; Save return status
	mov	ax,cache_ptr[0]			; Return address of cache
	mov	word ptr es:[bx].VC_buffer[0],ax;
	mov	ax,cache_ptr[2]			;
	mov	word ptr es:[bx].VC_buffer[2],ax;
dsch_exit:
	ret					; Return to caller

disable_cache	endp


;*****************************************************************************
;
; Handle a "Cache Enable" command.
;
; If the cache is enabled already, set ES:[BX].VC_track to the current cache
; flags, set ES:[BX].VC_buffer to point to the cache, and return STS_ALREADY.
;
; Otherwise, set our internal cache pointer to ES:[BX].VC_buffer, set the
; cache flags to CACHE_INSTALLED, and return STS_OK.
;
;*****************************************************************************
enable_cache	proc	near

	test	cache_flags,CACHE_INSTALLED	; Is it installed?
	jz	ench_l1				; No - install now
	mov	es:[bx].VC_status,STS_ALREADY	; Say it's already installed
	mov	al,cache_flags			; Get the current flags
	mov	es:[bx].VC_track,al		; Return 'em
	mov	ax,cache_ptr[0]			; Return cache address
	mov	word ptr es:[bx].VC_buffer[0],ax;
	mov	ax,cache_ptr[2]			;
	mov	word ptr es:[bx].VC_buffer[2],ax;
	jmp	short ench_exit			; Exit now
ench_l1:
;
; The cache isn't installed yet. Install it now.
; Address is in ES:[BX].VC_buffer.
;
	mov	ax,word ptr es:[bx].VC_buffer[0]; Point to new cache
	mov	cache_ptr[0],ax			;
	mov	ax,word ptr es:[bx].VC_buffer[2];
	mov	cache_ptr[2],ax			;
	mov	cache_flags,CACHE_INSTALLED	;
	mov	es:[bx].VC_status,STS_OK	; Return ok status
ench_exit:
	ret

enable_cache	endp


;*****************************************************************************
;
; Handle a "Flush Cache" command.
;
; If the cache is not installed, return STS_NOT_ENAB.
;
; If the cache is not dirty, return STS_OK.
;
; Otherwise, try to flush the cache, returning the status from the disk I/O
; operation.
;
;*****************************************************************************
flush_cache	proc	near

	test	cache_flags,CACHE_INSTALLED	; Is the cache installed?
	jnz	flch_l1				; Yep - see if it's dirty
	mov	al,STS_NOT_ENAB			; Return that it ain't there
	jmp	short flch_done
flch_l1:
	test	cache_flags,CACHE_DIRTY		; Is it dirty?
	jz	flch_ok				; No - just return ok
	call	set_disk_parms			; Set the diskette parameters
	call	do_flush_cache			; Do the flush
	pushf					; Save result flag
	call	rest_disk_parms			; Restore the parameters
	popf					; Get error flag back
	jnc	flch_ok				; If it went, return success
	push	bx				; Save command blk ptr
	mov	bx,offset CMD_sts_table		; Point to translate table
	call	xlat_status			; Translate the status
	pop	bx				;
	jmp	short flch_done			; Exit now
flch_ok:
	mov	al,STS_OK			; Return success
flch_done:
	mov	es:[bx].VC_status,al		; Save the status
	ret

flush_cache	endp


;*****************************************************************************
;
; Handle the "Disable Auto-Flush" command.
;
; If the cache is not installed, return STS_NOT_ENAB.
;
;*****************************************************************************
disab_auto	proc	near

	test	cache_flags,CACHE_INSTALLED	; Is that baby installed?
	jnz	dsau_l1				; Yep - disable autoflush
	mov	es:[bx].VC_status,STS_NOT_ENAB	; Return it ain't there
	jmp	short dsau_exit			; Exit now
dsau_l1:
	mov	es:[bx].VC_status,STS_OK	; Return success
	and	cache_flags,not CACHE_AUTO	; Disable it
dsau_exit:
	ret					; Return to caller

disab_auto	endp


;*****************************************************************************
;
; Handle the "Enable Autoflush" command
;
; If the cache is not installed, return STS_NOT_ENAB.
;
;*****************************************************************************
enab_auto	proc	near

	test	cache_flags,CACHE_INSTALLED	; Is that baby installed?
	jnz	enau_l1				; Yep - disable autoflush
	mov	es:[bx].VC_status,STS_NOT_ENAB	; Return it ain't there
	jmp	short enau_exit			; Exit now
enau_l1:
	mov	es:[bx].VC_status,STS_OK	; Return success
	or	cache_flags,CACHE_AUTO		; Enable it
enau_exit:
	ret					; Return to caller

enab_auto	endp


;*****************************************************************************
;
; Handle a "Get Cache Information" command.
;
; Returns the cache flags and the address of the cache.
;
;*****************************************************************************
get_cache_info	proc	near

	mov	es:[bx].VC_status,STS_OK; Always returns success
	les	di,es:[bx].VC_buffer	; Point to the buffer
	mov	al,cache_flags		; Get the current cache flags
	stosb				; Return 'em
	mov	ax,cache_ptr[0]		; Get the cache address offset
	stosw				; Stuff it
	mov	ax,cache_ptr[2]		; Same with the cache address segment
	stosw				;
	ret				; Return to caller

get_cache_info	endp


;*****************************************************************************
;
; Called when VHARDCTL gets a "Get Driver Data" command. This copies data from
; our data area into the buffer.
; Data returned in the buffer:
;	byte	drive number assigned by DOS
;	word	VHARD version
;	struc	BPB used
;
;*****************************************************************************
cmd_get_vhdata	proc	near

	les	di,es:[bx].VC_buffer	; Point to their buffer
	mov	al,our_drive		; Get the drive number
	stosb				; Store it
	mov	ax,VHARD_version	; Store the driver version
	stosw				;
	mov	si,offset our_bpb	; Copy our BPB
	mov	cx,size DOS_BPB		;
	rep	movsb			;
	ret				; Return to caller

cmd_get_vhdata	endp


;*****************************************************************************
;
; This dispatch table is used to handle the special commands called via
; VHARDCTL.
;
;*****************************************************************************
cmd_dispatch	dw	cmd_format_trk	; Format a track
		dw	cmd_read_trk	; Read a track
		dw	cmd_write_trk	; Write a track
		dw	cmd_verify_trk	; Verify a track
		dw	cmd_set_cache	; Deal with the cache
		dw	cmd_get_vhdata	; Get data

;*****************************************************************************
;
; Yet another dispatch table (this one's for the cache commands)
;
;*****************************************************************************
cache_dispatch	dw	disable_cache	; Disable the cache
		dw	enable_cache	; Enable the cache
		dw	flush_cache	; Flush the cache
		dw	disab_auto	; Disable auto-flush
		dw	enab_auto	; Enable auto-flush
		dw	get_cache_info	; Get cache information


	subttl	Utility routines
	page

;*****************************************************************************
;
; U T I L I T Y   R O U T I N E S
;
;*****************************************************************************


;*****************************************************************************
;
; Save all registers
;
;*****************************************************************************
save_regs	proc	near

assume	ss:nothing

	save	ax, bx, cx, dx, si, di, ds, es
	mov	ax,bp			; Get current BP value
	mov	bp,sp			; Point into the stack
	xchg	ax,[bp + 16]		; Swap it with the return address
	push	ax			; Put that on the stack
	mov	ax,[bp + 14]		; Set AX back to original value
	mov	bp,[bp + 16]		; Same with BP
	ret				; Return to caller

save_regs	endp


;*****************************************************************************
;
; Restore all registers
;
;*****************************************************************************
rest_regs	proc	near

	pop	ax			; Get return address
	mov	bp,sp			; Get a pointer into the stack
	xchg	ax,[bp + 16]		; Swap retaddr with the saved BP value
	mov	bp,ax			; This restores BP
	restore
	ret				; Return to caller

rest_regs	endp


;*****************************************************************************
;
; Read sectors from disk.
;
; Input:
;	DH	Head number
;	CH	Track number
;	CL	Starting sector number
;	AL	Number of sectors to read
;	ES:BX	Address of buffer
;
; Output:
;	AH	Status returned by BIOS
;	CF	Error indicator
;
; Registers:
;	BP preserved, all others trashed.
;
;*****************************************************************************
sect_read	proc	near

	save	si, bp
	mov	bp,3			; Retry 3 times
	mov	si,ax			; Save # of sectors
sctr_l1:
	mov	ah,2			; BIOS function to read
	mov	dl,$OUR_DRIVE		; Drive to read from
	int	13h			; Call the BIOS to do the work
	jnc	sctr_exit		; If it went, exit now
	dec	bp			; That was 1 try
	jz	sctr_exit		; If that was the last, exit
	mov	ah,0			; Do a reset
	int	13h			;
	jc	sctr_exit		; If that fails, we got problems...
	mov	ax,si			; Get # to read again
	jmp	short sctr_l1		; Try again
sctr_exit:
	restore
	ret				; Return to caller

sect_read	endp


;*****************************************************************************
;
; Verify sectors.
;
; Input:
;	DH	Head number
;	CH	Track number
;	CL	Starting sector number
;	AL	Number of sectors to verify
;
; Output:
;	AH	Status returned by BIOS
;	CF	Error indicator
;
; Registers:
;	BP preserved, all others trashed.
;
;*****************************************************************************
sect_verify	proc	near

	save	si, bp
	mov	bp,3			; Retry 3 times
	mov	si,ax			; Save # of sectors
sctv_l1:
	mov	ah,4			; BIOS function to verify
	mov	dl,$OUR_DRIVE		; Drive to read from
	int	13h			; Call the BIOS to do the work
	jnc	sctv_exit		; If it went, exit now
	dec	bp			; That was 1 try
	jz	sctv_exit		; If that was the last, exit
	mov	ah,0			; Do a reset
	int	13h			;
	jc	sctv_exit		; If that fails, we got problems...
	mov	ax,si			; Get # to read again
	jmp	short sctv_l1		; Try again
sctv_exit:
	restore
	ret				; Return to caller

sect_verify	endp


;*****************************************************************************
;
; Write sectors to disk.
;
; Input:
;	DH	Head number
;	CH	Track number
;	CL	Starting sector number
;	AL	Number of sectors to write
;	ES:BX	Address of buffer
;
; Output:
;	AH	Status returned by BIOS
;	CF	Error indicator
;
; Registers:
;	BP preserved, all others trashed.
;
;*****************************************************************************
sect_write	proc	near

	save	si,bp
	mov	bp,3			; Retry 3 times
	mov	si,ax			; Save # to write
sctw_l1:
	mov	ah,3			; BIOS function to write
	mov	dl,$OUR_DRIVE		; Drive to write to
	int	13h			; Call the BIOS to do the work
	jnc	sctw_exit		; If it went, exit now
	dec	bp			; That was 1 try
	jz	sctw_exit		; If that was the last, exit
	mov	ah,0			; Do a reset
	int	13h			;
	jc	sctw_exit		; If that fails, we got problems...
	mov	ax,si			; Get # to write
	jmp	short sctw_l1		; Try again
sctw_exit:
	restore
	ret				; Return to caller

sect_write	endp


;*****************************************************************************
;
; Set the diskette parameters for 10 sectors per track
;
; Input:
;	None.
;
; Output:
;	An internal copy of the parameters is made, the address at the vector
;	for int 1eh is saved, and the vector is set to point to our copy.
;
; Registers:
;	All preserved.
;
;*****************************************************************************
set_disk_parms	proc	near

assume	ds:vhard_seg, es:nothing

	save	cx, si, di, es

	push	ds			; Save our segment, too
	sub	si,si			; Set it to segment for int vectors
	mov	ds,si			;
	lds	si,ds:[1eh * 4]		; Point to the current disk parms
	mov	cs:old_pptr[0],si	; Save it for restore later
	mov	cs:old_pptr[2],ds	;
	push	cs			; Set ES to our segment
	pop	es

assume	ds:nothing, es:vhard_seg

	mov	di,offset diskparms	; Point to our copy of the parameters
	mov	cx,11			; Only need 11 bytes
	cld				; Make sure of direction
	rep	movsb			; Copy them
	pop	ds			; Get our segment back

assume	ds:vhard_seg

	mov	sect_per_track,10	; Set that aright
	mov	gap_len,14h
	mov	fmt_gap_len,20h
	sub	cx,cx			; Point back to the vectors
	mov	es,cx			;
	mov	word ptr es:[1eh * 4],offset diskparms
	mov	es:[(1eh * 4) + 2],cs	; Set pointer to new parms

	restore
	ret				; Return to caller

set_disk_parms	endp


;*****************************************************************************
;
; Restore the diskette parameters
;
; Input:
;	None.
;
; Output:
;	The vector for int 1eh is restored.
;
; Registers:
;	All preserved.
;
;*****************************************************************************
rest_disk_parms	proc	near

assume	ds:vhard_seg, es:nothing

	save	ax, es
	sub	ax,ax			; Get segment for int vectors
	mov	es,ax			;
	mov	ax,old_pptr[0]		; Restore the old parm pointer
	mov	es:[1eh * 4],ax		;
	mov	ax,old_pptr[2]		;
	mov	es:[(1eh * 4) + 2],ax	;
	restore
	ret				; Return to caller

rest_disk_parms	endp


;*****************************************************************************
;
; Translate a BIOS error code to a status code according to the table at BX.
;
; Input:
;	AH	BIOS return code
;	DS:BX	Pointer to translation table
;
; Output:
;	AL	Corresponding status code
;
; Registers:
;	AX trashed; all others preserved
;
;*****************************************************************************
xlat_status	proc	near

assume	ds:vhard_seg

	save	bx, cx
	mov	cx,10			; Check for 10 BIOS codes
xlts_l1:
	cmp	ah,[bx]			; Is it this one?
	je	xlts_l2			; Yep - return the status code
	inc	bx			; Point to next one
	inc	bx
	loop	xlts_l1			;
	mov	al,STS_BAD_ERROR	; Unknown error code
	jmp	short xlts_exit
xlts_l2:
	mov	al,1[bx]		; Get the corresponding status code
xlts_exit:
	restore
	ret				; Return to caller

xlat_status	endp


;*****************************************************************************
;
; Calculate the disk, track, head, and sector for a given logical sector.
;
; Input:
;	AX	DOS' logical sector
;
; Output:
;	AX	Disk number
;	DH	Head number
;	CH	Track number
;	CL	Sector number
;
; Registers:
;	All register not used for parameters are preserved.
;
;*****************************************************************************
calc_disk	proc	near

	save	bx, si
	mov	bx,799			; Divide by # of sectors/disk
	sub	dx,dx			;  to get AX = disk #,
	div	bx			;  DX = logical sector on that disk
	mov	si,ax			; Save the disk #
	mov	ax,dx			; Get the logical sector on the disk
	inc	ax			; Allow for boot sector
	mov	bx,10			; Divide by # of sectors/track
	sub	dx,dx			;  to get AX = track,
	div	bx			;  DX = logical sector on the track
	mov	cl,dl			; Get the sector
	inc	cl			; Since sectors start at 1...
	shr	ax,1			; Figure out the cylinder
	rcl	dh,1			; Set DH to the head
	mov	ch,al			; Return the track
	mov	ax,si			; Get the disk # back
	restore
	ret				; Return to caller

calc_disk	endp


;*****************************************************************************
;
; Read in the boot sector, checking to see if the ID matches our current ID.
;
; Input:
;	curr_disk_id		the ID we're expecting.
;
; Output:
;	CF = 1			if the diskette can't be read
;	ZF = 1			if the disk IDs match
;
; Registers:
;	All preserved.
;
;*****************************************************************************
check_boot_sect	proc	near

assume	ds:vhard_seg, es:vhard_seg

	save	ax, bx, cx, dx, si, di, es

	push	cs			; Make sure ES is right
	pop	es

assume	es:vhard_seg

	call	read_boot_sect		; Try to read the boot sector
	jc	ckbs_exit		; Return w/error flag set if problem
	mov	si,offset boot_sect_buf.BS_disk_id
	mov	di,offset curr_disk_id
	mov	cx,8			; Check 8 bytes
	rep	cmpsb			;
	clc				; Clear error flag
ckbs_exit:
	restore
	ret				; Return to caller

check_boot_sect	endp


;*****************************************************************************
;
; Read the boot sector of the diskette in drive A: into the buffer at
; boot_sect_buf.
;
; Input:
;	None.
;
; Output:
;	CF = 0		Success.
;	   = 1		Unable to read the diskette.
;
; Registers:
;	All preserved.
;
;*****************************************************************************
read_boot_sect	proc	near

assume	ds:vhard_seg, es:nothing

	save	ax, bx, cx, dx, es
	mov	dh,0			; Need to read Track 0, Head 0, Sect 1
	mov	cx,1			;
	mov	al,1			; Just 1 sector
	push	cs			; Into our buffer
	pop	es			;

assume	es:vhard_seg

	mov	bx,offset boot_sect_buf	;
	call	sect_read		; Read it
	restore				;

assume	es:nothing

	ret				; Return to caller

read_boot_sect	endp


;*****************************************************************************
;
; Save the last line of the screen, display a prompt there, and wait for a
; keystroke.
;
; Input:
;	DS:SI			pointer to prompt to display
;
; Output:
;	CF = 1			Esc was pressed
;
; Registers:
;	All preserved.
;
;*****************************************************************************
prompt_user	proc	near

assume	ds:vhard_seg, es:nothing

	save	ax, bx, cx, dx, si, es
	mov	dh,24			; Assume no EGA/VGA
	mov	ax,40h			; Get at the number of lines on screen
	mov	es,ax			;

assume	es:BIOS_seg

	cmp	have_evga,0		; Do we have an EGA/VGA on board?
	je	prpu_l1			; No - use line 24
	mov	dh,es:[crt_lines]	; Get last line on screen
prpu_l1:
	mov	bh,es:[active_page]	; Get the current video page
	push	cs			; Set ES to our segment
	pop	es			;

assume	es:vhard_seg

	push	dx			; Save line to use
	mov	ah,3			; Get the current cursor position
	int	10h			;
	mov	last_curs,dx		; Save it
	pop	dx			;
	mov	dl,0			; Start at the beginning of the line
	mov	bl,4fh			; Use hi-white on red
	mov	cx,1			; Only doing 1 char at a time
	mov	di,offset screen_save	; Point to save area
prpu_l2:
	lodsb				; Get a character to display
	or	al,al			; Done yet?
	jz	prpu_l3			; Yep - exit
	push	ax			; Save it for a sec
	mov	ah,2			; Set the cursor position
	int	10h			;
	mov	ah,8			; Read the char/attr there
	int	10h			;
	stosw				; Save it
	pop	ax			; Get the new one back
	mov	ah,9			; Write that character
	int	10h			;
	inc	dl			; Next column
	jmp	short prpu_l2		; Loop back for more
prpu_l3:
	mov	ah,1			; See if there are any keys
	int	16h			;
	jz	prpu_l31		; No - wait for one
	mov	ah,0			; Read the key
	int	16h			;
	jmp	short prpu_l3		; Loop until buffer is empty
prpu_l31:
	sub	ah,ah			; Read a key
	int	16h			;
	push	ax			; Save the key read
	mov	si,offset screen_save	; Point back to the save area
	mov	dl,0			; Start back at the beginning of line
prpu_l4:
	cmp	si,di			; Reached the end yet?
	je	prpu_l5			; Nope - keep restoring
	lodsw				; Get char/attr
	mov	bl,ah			; Get the attribute for the write
	push	ax			; Save the character to write
	mov	ah,2			; Set the cursor
	int	10h			;
	pop	ax			; Get the character back
	mov	ah,9			; Write it out now
	int	10h
	inc	dl			; Next column
	jmp	short prpu_l4
prpu_l5:
	mov	ah,2			; Set cursor back to last position
	mov	dx,last_curs		;
	int	10h			;
	pop	ax			; Get keystroke back
	cmp	ax,11bh			; Escape key?
	je	prpu_exit		; Yep - return w/CF=1
	stc				; Return CF=0
prpu_exit:
	cmc				; Make CF the way we want
	restore
	ret				; Return to caller

prompt_user	endp


;*****************************************************************************
;
; Prompt for a specific disk, waiting for a key.
;
; Input:
;	curr_disk_id			ID of the disk set
;	curr_disk_num			disk number to prompt for
;
; Output:
;	CF = 1			The user pressed Esc. This indicates that they
;				want an error returned to DOS.
;
; Registers:
;	All preserved.
;
;*****************************************************************************
prompt_for_disk	proc	near

assume	ds:vhard_seg, es:nothing

	save	ax, cx, si, di, es
	push	cs			; Make sure ES is right
	pop	es			;

assume	es:vhard_seg

	mov	si,offset curr_disk_id	; Point to the ID to ask for
	mov	di,offset pr_disk_id	; Point to where to put it
	mov	cx,4			; Copy 8 bytes (4 words)
	rep	movsw			;
	mov	al,curr_disk_num	; Get the number to ask for
	aam				; Split it up
	or	ax,3030h		; Make it 2 ASCII digits
	xchg	al,ah			; Put 'em in the right order
	mov	word ptr pr_disk_num,ax	; Store 'em in the string
	mov	si,offset need_disk	; Point to the prompt string
	call	prompt_user		; Prompt them for it
	restore
	ret				; Return to caller

prompt_for_disk	endp


;*****************************************************************************
;
; Prompt for the first disk of a set, and make sure it's loaded.
;
; Input:
;	None.
;
; Output:
;	CF = 0	Disk is successfully loaded
;
; Reserved:
;	All preserved.
;
;*****************************************************************************
get_disk_0	proc	near

	save	ax, bx, cx, si, di, bp, es
	call	read_boot_sect		; Load the boot sector
	jnc	gtd0_l2			; If we got it, continue
gtd0_l1:
	mov	si,offset need_first_disk; Prompt for a disk
	call	prompt_user		;
	jc	gtd0_exit		; Exit if Esc pressed
	call	read_boot_sect		; Read the boot sector
	jc	gtd0_l1			; Loop back if unable
gtd0_l2:
	cmp	boot_sect_buf.BS_disk_num,0	; Is it disk 0?
	jne	gtd0_l1				; No - try again
	mov	si,offset boot_sect_buf.BS_disk_id	; Copy disk ID
	mov	di,offset curr_disk_id			;
	mov	cx,4					;
	push	cs					;
	pop	es					;
	rep	movsw					;
	mov	curr_disk_num,0		; Set the disk to 0
	test	cache_flags,CACHE_INSTALLED	; Is the cache there?
	jz	gtd0_l3				; No - just get the ID
	test	cache_flags,CACHE_VALID		; Is it already valid?
	jnz	gtd0_l3				; Yep - just get the ID
	mov	bp,offset sect_read	; Routine to use for the I/O
	mov	ax,1			; Start at sector 1
	mov	cx,24			; Read 24 sectors
	les	bx,cache_ptr		; Point to the cache
	call	io_operation		; Read 'em
	jc	gtd0_exit		; If error, return it
	or	cache_flags,CACHE_VALID	; It's got valid data now
gtd0_l3:
	clc				; Indicate success
gtd0_exit:
	restore
	ret				; Return to caller

get_disk_0	endp



;*****************************************************************************
;
; Prompt for a disk, and make sure it's loaded
;
; Input:
;	AL	Number of disk to prompt for
;
; Output:
;	CF = 0	Disk is successfully loaded
;
; Registers:
;	All preserved.
;
;*****************************************************************************
get_num_disk	proc	near

	cmp	curr_disk_num,0ffh	; Do we have one yet?
	mov	curr_disk_num,al	;
	jne	gtnd_l1			; Yes - never mind
	call	get_disk_0		; Get disk 0 to get a disk ID
	jc	gtnd_exit		; Exit if unable
	or	al,al			; Do we want disk 0?
	jz	gtnd_l2			; Yep - don't bother reading again
gtnd_l1:
	call	check_boot_sect		; Check the boot sector
	jc	gtnd_exit		; Exit if error
	jnz	gtnd_l11		; If IDs don't match, prompt for it
	cmp	al,boot_sect_buf.BS_disk_num	; Right number?
	je	gtnd_l2			; Yep - exit w/CF clear
gtnd_l11:
	call	prompt_for_disk		; Prompt for the disk
	jc	gtnd_exit		; Exit if Esc was pressed
	jmp	short gtnd_l1		; Loop back now
gtnd_l2:
	clc				; Clear error flag
gtnd_exit:
	ret				; Return to caller

get_num_disk	endp


$drv_end	label	byte


	subttl	INIT routines for both drivers
	page

;*****************************************************************************
;
; INIT routine for VHARD itself
;
;*****************************************************************************
drv_init		proc	near

assume	ds:vhard_seg, es:nothing

	mov	es:[si].DDir_media_id,1	; Just 1 unit
	mov	word ptr es:[si].DDir_buffer,offset $drv_end
	mov	word ptr es:[si].DDir_buffer[2],cs
	mov	word ptr es:[si].DDir_count,offset our_bpb_array
	mov	word ptr es:[si].DDir_start,cs
	mov	ah,30h			; Get the DOS version
	int	21h			;
	xchg	al,ah			;
	mov	dos_ver,ax		; Save it
	cmp	ax,30ah			; At least 3.10?
	jb	drvi_l0			; Nope - can't get the drive
	mov	al,byte ptr es:22[si]	; Get the drive assigned
	mov	our_drive,al		; Save it
drvi_l0:
	mov	ax,1a00h		; See if there's a VGA on board
	mov	bx,0a5a5h		;
	int	10h			;
	cmp	bx,0a5a5h		; Did it change?
	jne	drvi_l1			; Yep - say that we have EGA/VGA
	mov	ah,12h			; See if we have an EGA
	mov	bx,0ff10h		;
	int	10h			;
	cmp	bh,0ffh			; Did it change?
	je	drvi_exit		; No - no EGA or VGA
drvi_l1:
	or	have_evga,1		; Set the flag
drvi_exit:
	mov	dx,offset banner	; Display the program banner
	mov	ah,9			;
	int	21h			;
	ret				; Return to dispatcher

drv_init		endp


banner	db	13, 10, 'VHARD virtual disk driver v'
%	db	VHARD_text_ver
	db	' - Public Domain Software', 13, 10, '$'


du_init		proc	near

	mov	word ptr es:[si][0].DDir_buffer,offset $drv_end
	mov	word ptr es:[si][2].DDir_buffer,cs
	ret

du_init		endp


vhard_seg	ends

	end
