
	title	Prepare VHARD floppies
	subttl	Prologue
	page	60,132

comment	{

******************************************************************************

File VHPREP.ASM

Author:
	Aaron L. Brenner

	BIX mail address albrenner
	GEnie address A.BRENNER

	This program is hereby released to the public domain.

Purpose:
	Prepare floppy diskettes for use with VHARD.

	This consists of the following:

	1) Format the floppies to 10 sectors per track, 40 tracks (resulting
	   in a capacity of 800K per diskette).
	2) Write the VHARD boot sector to each disk containing, along with
	   boot code, a disk ID and number.
	3) Write a DOS boot sector, 2 FATs, and root directory to the first
	   disk.

	All disk operations are performed through VHARDCTL, the character
	device driver installed with VHARD.

Errorlevel returned:
	0		All is well
	1		VHARD.SYS is not installed

Revision history:
1.00	07/11/90	ALB	Created.

******************************************************************************

endcomment {

	subttl	Included files
	page

	include	dd_defs.inc
	include	vhard.inc

	subttl	Program data and stack
	page

vhprep_data	segment	para

ID_to_use	db	8 dup(' ')	; ID to use for the diskettes
disk_num	db	0ffh		; Number for each diskette
track_buf	db	512 * 10 dup(0)	; Track buffer for reading and writing
drive_num	db	0		; The drive assigned to VHARD
vhard_vnum	dw	0		; Version of VHARD
VHARD_BPB	DOS_BPB <>		; BPB gotten from VHARD

		dw	81h		; Pointer to our command tail
our_PSP		dw	0		; Our PSP segment

;
; This block is used to pass commands to VHPREP
;
command_blk	VH_CMD <>

vhctl_name	db	'VHARDCTL',0	; Name of control driver for VHARD
vhctl_handle	dw	0		; Handle for VHARDCTL
not_installed	db	13,10,'The VHARD driver is not installed.',13,10,'$'
insert_disk	db	13,10,'Put a blank diskette in drive A:, then press'
		db	' a key $'
label_disk	db	'Label this diskette $'
disk_bad	db	7,13,10,'This diskette is unusable!',13,10,'$'
crlf_str	db	13,10,'$'

err_prefix	db	7,'Error: $'

err0		db	'spurious error message$'
err1		db	'drive/adapter failed or no disk in drive$'
err2		db	'seek operation failed$'
err3		db	'disk adapter failed$'
err4		db	'CRC error$'
err5		db	'attempt to DMA across segment boundary$'
err6		db	'DMA overrun$'
err7		db	'sector not found$'
err8		db	'diskette is write-protected$'
err9		db	'address mark not found$'
err10		db	'invalid BIOS command$'
err11		db	'internal command error$'
err0ff		db	'unknown BIOS error code$'

error_msgs	dw	err0, err1, err2, err3, err4, err5, err6, err7, err8
		dw	err9, err10, err11, err0, err0, err0ff

vhprep_data	ends


vhprep_stack	segment	word	stack

	dw	256 dup(0)

vhprep_stack	ends


;*****************************************************************************
;
; This gets written to the actual boot sector of each diskette. As you can
; see, it just spits out a message, waits for a key, then tries to IPL again.
;
;*****************************************************************************
boot_code_seg	segment	para

assume	cs:boot_code_seg, ds:boot_code_seg

	org	0
boot_code	proc	near

	jmp	short boot_msg_disp	; Jump to message display code
	db	17 dup(?)		; Place keeper for disk ID & number
boot_msg_disp:
	cld				; Make sure of direction
	call	do_display		; Do the display
	db	7,'This is NOT a bootable diskette!',13,10
	db	'Put a different disk in the drive, and press a key to'
	db	' try again',13,10,0
do_display:
	pop	si			; Point to the string to display
ddsp_l1:
	lods	byte ptr cs:[si]	; Get a byte
	or	al,al			; End of string yet?
	jz	ddsp_l2			; Yep - just hang up now
	mov	ah,14			; BIOS "Write TTY" function
	int	10h			;
	jmp	short ddsp_l1		; Loop back for the rest
ddsp_l2:
	sub	ah,ah			; Get a key
	int	16h			;
	int	19h			; Try to IPL again
	jmp	$			; Hang up now

boot_code_end	label	byte

boot_code	endp

boot_code_seg	ends


	subttl	Start of program code
	page

vhprep_code	segment

assume	cs:vhprep_code, ds:vhprep_data, es:vhprep_data, ss:vhprep_stack


start	proc

	call	initialize		; Set things up
	call	do_prep			; Do the preparation
	call	terminate		; Do program cleanup
	mov	ax,4c00h		;
	int	21h			;

start	endp


;*****************************************************************************
;
; Program initialization.
;
; Make sure the VHARDCTL driver is present, and get driver info from it.
;
;*****************************************************************************
initialize	proc	near

	mov	ax,vhprep_data		; Get our data segment
	mov	ds,ax			;
	mov	our_PSP,es		; Save our PSP segment
	mov	es,ax			;
	mov	dx,offset vhctl_name	; Try to open VHARDCTL, read/write
	mov	ax,3d02h		;
	int	21h			;
	jnc	init_l1			; If it opened, make sure it's a dev
init_noway:
	mov	dx,offset not_installed	; Complain that it isn't there
	mov	ah,9			;
	int	21h			;
	mov	ax,4c01h		; Exit with errorlevel of 1
	int	21h			;
init_l1:
	mov	vhctl_handle,ax		; Save the handle for other ops
	mov	bx,ax			; Get the handle for IOCTL call
	mov	ax,4400h		; Get info on this guy
	int	21h			;
	test	dl,80h			; Is this a device?
	jz	init_noway		; Nope - no way we're doing anything
	mov	command_blk.VC_cmd_code,CMD_GETDATA
	mov	word ptr command_blk.VC_buffer[0],offset drive_num
	mov	word ptr command_blk.VC_buffer[2],ds
	mov	ax,4403h		; Get data from VHARDCTL
	mov	dx,offset command_blk	;
	mov	cx,size VH_CMD		;
	int	21h			;
	call	get_cmdline_parms	; Get a parameters from the cmd line
	cmp	byte ptr ID_to_use[0],' '; Did they supply an ID?
	jne	init_exit		; Yep - don't change it
	mov	ah,0			; Get timer count from BIOS
	int	1ah			; Returns CX:DX
	mov	ax,'HV'			; Set up disk ID
	mov	word ptr ID_to_use[0],ax;
	mov	ax,cx			; Use the time as part of the ID
	mov	di,offset ID_to_use[2]	;
	call	store2digits		;
	mov	al,dh			;
	call	store2digits		;
	mov	al,dl			;
	call	store2digits		;
init_exit:
	ret				; Return to Main

initialize	endp


;*****************************************************************************
;
; Get any command line parameters
;
; The only parameters allowed are a disk ID and a disk number.
;
;*****************************************************************************
get_cmdline_parms	proc	near

	push	es			; Save it for this
	les	si,dword ptr our_PSP[-2]; Point to the command tail
	call	skip_spaces		; Skip any whitespace
	mov	cx,8			; Max to copy
	mov	di,offset ID_to_use	;
gcmp_l1:
	lods	byte ptr es:[si]	; Get a byte
	cmp	al,' '			; Whitespace?
	je	gcmp_l3			; Yep - stop copying
	cmp	al,9			;
	je	gcmp_l3			;
	cmp	al,0dh			; End of it?
	je	gcmp_exit		; Yep - exit
	cmp	al,'a'			; Lower case?
	jb	gcmp_l2			; No - never mind
	cmp	al,'z'			;
	ja	gcmp_l2			;
	xor	al,20h			; Make it upper case
gcmp_l2:
	jcxz	gcmp_l1			; Don't store if ID is full
	mov	[di],al			; Save the byte
	inc	di			; Point to next spot
	dec	cx			; Knock off the count
	jmp	gcmp_l1			; Loop back
gcmp_l3:
	call	skip_spaces		; Skip any other whitespace
	sub	bl,bl			; Clear accumulator
gcmp_l4:
	lods	byte ptr es:[si]	; Pick up a byte
	cmp	al,'0'			; Valid digit?
	jb	gcmp_l5			; Nope - stop now
	cmp	al,'9'			;
	ja	gcmp_l5			;
	sub	al,'0'			; Make digit binary
	mov	bh,bl			; Multiply current value by 10
	shl	bh,1			;  by (BL * 2) + (BL * 8)
	shl	bl,1			;
	shl	bl,1			;
	shl	bl,1			;
	add	bl,al			; Add in new digit
	jmp	short gcmp_l4		; Loop back
gcmp_l5:
	cmp	bl,12			; Make sure it's legal
	ja	gcmp_exit		;
	mov	disk_num,bl		; Save the number
gcmp_exit:
	pop	es			;
	ret				; Return to initialization

get_cmdline_parms	endp


;*****************************************************************************
;
; Skip whitespace characters at ES:SI
;
;*****************************************************************************
skip_spaces	proc	near

sksp_l1:
	mov	al,es:[si]		; Get a byte
	cmp	al,' '			; Whitespace?
	je	sksp_l2			; Yep - skip it
	cmp	al,9			;
	jne	sksp_exit		; No - exit now
sksp_l2:
	inc	si			; Skip this character
	jmp	short sksp_l1		; Loop back
sksp_exit:
	ret				; Return to caller

skip_spaces	endp


;*****************************************************************************
;
; Convert AL to 2 hex digits, storing them at DI.
;
;*****************************************************************************
store2digits	proc	near

	push	ax			; Save it for a second
	mov	cl,4			; Move high nybble down
	shr	al,cl			;
	call	store_nybble		; Store it as ASCII
	pop	ax			; Get low nybble back
store_nybble:
	and	al,0fh			; Just keep low nybble
	add	al,90h			; Convert to ASCII hex
	daa				;
	adc	al,40h			;
	daa				;
	stosb				; Store digit
	ret				; Return to caller

store2digits	endp


;*****************************************************************************
;
; Take care of any cleanup prior to returning to DOS.
; All we do is close the handle for VHARDCTL.
;
;*****************************************************************************
terminate	proc	near

	mov	bx,vhctl_handle		; Get the handle
	mov	ah,3eh			; Close it
	int	21h			;
	ret				; Return to Main

terminate	endp


;*****************************************************************************
;
; Do the actual preparation
;
; Format disk 0, writing a DOS boot sector, 2 copies of the FAT, and an
; empty root directory (empty except for a volume label that is the disk ID).
;
; Once disk 0 is taken care of, just format the rest of the disks.
;
;*****************************************************************************
do_prep		proc	near

	cmp	disk_num,0ffh		; Did they specify a disk?
	je	dopr_l2			; No - do all disks
	cmp	disk_num,0		; Is it the first disk?
	jne	dopr_l1			; No - do a normal disk
	call	do_first_disk		; Do just the first disk
	jmp	short dopr_exit		; Exit
dopr_l1:
	call	do_one_disk		; Just do this disk
	jmp	short dopr_exit		;
dopr_l2:
	mov	disk_num,0		;
	call	do_first_disk		; Set up the first disk
	mov	cx,12			; 12 more to do
	mov	disk_num,1		; Starting with disk 1
dopr_l3:
	call	do_one_disk		; Do this disk
	inc	disk_num		; Set for next one
	loop	dopr_l3			; Loop for all 12 disks
dopr_exit:
	ret				; Return to Main
	
do_prep		endp


;*****************************************************************************
;
; Prepare the first diskette. This requires special treatment because it also
; contains the DOS boot sector, 2 copies of a FAT, and a root directory.
;
;*****************************************************************************
do_first_disk	proc	near

dfdk_l1:
	call	format_disk		; Format the first disk
	call	copy_boot_sect		; Copy the boot sector to track buf
	mov	di,offset track_buf[512].DBS_BPB
	mov	si,offset VHARD_BPB
	mov	cx,size DOS_BPB
	rep	movsb
	mov	word ptr track_buf[512 * 2],0fffch	; Set up 2 empty FATs
	mov	track_buf[(512 * 2) + 2],0ffh		;
	mov	word ptr track_buf[6 * 512],0fffch	;
	mov	track_buf[(6 * 512) + 2],0ffh		;
	sub	ax,ax			;
	call	write_track		; Write it
	jc	dfdk_err		; If unable, complain
	call	clear_track_buf		; Wipe it clean
	mov	di,offset track_buf	; Point to first root dir entry
	mov	si,offset ID_to_use	; Use the disk ID as the vol label
	mov	cx,4			;
	rep	movsw			;
	mov	al,' '			; Fill it out with spaces
	stosb				;
	stosb				;
	stosb				;
	mov	al,8			; Attribute byte for volume label
	stosb				;
	mov	cx,5			; Fill 10 reserved bytes w/0
	sub	al,al			;
	rep	stosw			;
	mov	ah,2ch			; Get time from DOS
	int	21h			;
	mov	ah,ch			; Get the hour
	shl	ah,1			; Put it in the high 5 bits
	shl	ah,1			;
	shl	ah,1			;
	mov	ch,0			; Make the minutes a word
	mov	bx,cx			; Into a temp register
	mov	cl,5			; Move the minutes into middle 6 bits
	shl	bx,cl			;
	or	ax,bx			; Add to time
	shr	dh,1			; Make seconds 2-sec intervals
	or	al,dh			; Finish with that
	stosw				; Put the timestamp into the dir entry
	mov	ah,2ah			; Get the date from DOS
	int	21h			;
	sub	cx,1980			; Make the year the way DOS wants
	mov	ax,cx			;
	mov	cl,7			; Move to high 7 bits
	ror	ax,cl			;
	mov	bl,dh			; Get the month
	sub	bh,bh			;  as a word
	mov	cl,5			; Put it in the middle
	shl	bx,cl			;
	or	ax,bx			; Add to date
	or	al,dl			; Add the days
	stosw				; Put it in the dir entry
	mov	ax,100h			; Write track 0, head 1
	call	write_track		;
	jc	dfdk_err
	call	show_label		; Tell 'em how to label it
dfdk_exit:
	ret				; Return to caller
;
dfdk_err:
	call	report_error		; Complain about the error
	mov	dx,offset disk_bad	; Let 'em know the disk is bad
	mov	ah,9			;
	int	21h			;
	jmp	dfdk_l1

do_first_disk	endp


;*****************************************************************************
;
; Prepare a disk other than the first one. Just format it and write a boot
; sector.
;
;*****************************************************************************
do_one_disk	proc	near

dodk_l1:
	call	format_disk		; Format this disk
	call	copy_boot_sect		; Copy the boot sector to track_buf
	mov	al,0			; Write this track
	call	write_track		;
	jnc	dodk_exit		;
	call	report_error		; Complain about the error
	mov	dx,offset disk_bad	; Let 'em know the disk is bad
	mov	ah,9			;
	int	21h			;
	jmp	short dodk_l1
dodk_exit:
	call	show_label		; Tell 'em what the label is
	ret				; Return to caller

do_one_disk	endp


;*****************************************************************************
;
; Copy the boot sector into the first sector of the track buffer
;
;*****************************************************************************
copy_boot_sect	proc	near

	save	cx, si, di
	call	clear_track_buf		; Wipe out our track buffer
	push	ds			; Save Our Segment
	mov	cx,boot_code_seg	; Point to our boot code
	mov	ds,cx			;

assume	ds:boot_code_seg

	mov	si,offset boot_code	; Point to the boot code
	mov	cx,offset boot_code_end	; Number of bytes to copy
	mov	di,offset track_buf	; Where to put it
	rep	movsb			; Copy boot code to track buffer
	pop	ds			; Get our segment back

assume	ds:vhprep_data

	mov	si,offset ID_to_use	; Point to disk ID
	mov	di,offset track_buf.BS_disk_id
	mov	cx,4			;
	rep	movsw			; Copy disk ID to boot sector
	mov	cl,disk_num		; Copy disk # to boot sector
	mov	track_buf.BS_disk_num,cl;
	sub	ax,ax			; Write track 0/head 0
	call	write_track		;
	restore
	ret				; Return to caller

copy_boot_sect	endp


;*****************************************************************************
;
; Fill the track buffer with 0s.
;
;*****************************************************************************
clear_track_buf	proc	near

	save	ax, cx, di
	mov	cx,512 * 5		; Number of words to clear
	mov	di,offset track_buf	; What to clear
	sub	ax,ax			; Fill with 0
	cld				; Make sure of direction
	rep	stosw			; Fill the buffer
	restore
	ret				; Return to caller

clear_track_buf	endp


;*****************************************************************************
;
; Prompt for a disk, then format all tracks.
;
;*****************************************************************************
format_disk	proc	near

	save	ax, cx, dx
fmtd_l1:
	mov	dx,offset insert_disk	; Display the prompt
	mov	ah,9			;
	int	21h			;
	mov	ax,0c08h		; Get a key
	int	21h			;
	or	al,al			; Fn key?
	jnz	fmtd_l2			; Nope - continue
	mov	ah,8			; Read the second character
	int	21h			;
fmtd_l2:
	mov	dx,offset crlf_str	; Newline
	mov	ah,9			;
	int	21h			;
	sub	ax,ax			; Start with track 0
;	inc	ah			;
	mov	cx,40			; 40 tracks to do
fmtd_l3:
	call	format_track		; Format head 0 of the track
	jc	fmtd_err		; Complain on error
	xor	ah,1			; Other head
	call	format_track		;
	jc	fmtd_err		; Complain on error
	xor	ah,1			; Head 0 again
	inc	al			; Next cylinder
	loop	fmtd_l3			; Loop for the whole disk
	restore
	ret				; Return to caller
fmtd_err:
;
; An error occurred during the format. Report the error, let them know the
; disk is unusable, then prompt for another.
;
	call	report_error		; Report the error
	mov	dx,offset disk_bad	; Let 'em know the disk is bad
	mov	ah,9			;
	int	21h			;
	jmp	fmtd_l1			; Try with another disk

format_disk	endp


;*****************************************************************************
;
; Issue a format command for the track in AL/head in AH
;
;*****************************************************************************
format_track	proc	near

	save	ax, bx, cx, dx, di
;
; Before we can do the format, we need to set up the track buffer with the
; array of format parameters. Each entry in the array is 4 bytes, structured:
;	byte 0		track
;	byte 1		head
;	byte 2		sector
;	byte 3		sector size (2 in this case)
;
	mov	di,offset track_buf	; Point to the buffer
	mov	cx,10			; 10 entries
	mov	bx,201h			; Starting with sector 1
fmtt_l1:
	stosb				; Store the track
	mov	[di],ah			; Store the head
	inc	di			;
	mov	[di],bl			; Now the sector number
	inc	bl			;
	inc	di			;
	mov	byte ptr[di],2		; Sector size
	inc	di			;
	loop	fmtt_l1			; Loop for all entries
	mov	command_blk.VC_cmd_code,CMD_FORMAT	; Command code
	ror	ah,1			; Put the head in the high bit
	or	al,ah			; Add to track number
	mov	command_blk.VC_track,al	; Pass it in the command block
	mov	word ptr command_blk.VC_buffer[0],offset track_buf
	mov	word ptr command_blk.VC_buffer[2],ds
	mov	dx,offset command_blk	; Point to the block
	mov	ax,4403h		; IOCTL Write to the VHARDCTL device
	mov	bx,vhctl_handle
	mov	cx,size VH_CMD
	int	21h
	cmp	command_blk.VC_status,STS_OK + 1	; Get the status
	cmc				; Set CF to 1 if error
	restore
	ret				; Return to caller

format_track	endp


;*****************************************************************************
;
; Write track_buf to the track in AL/head in AH
;
;*****************************************************************************
write_track	proc	near

	save	ax, bx, cx, dx
	mov	command_blk.VC_cmd_code,CMD_WRITE
	ror	ah,1
	or	al,ah
	mov	command_blk.VC_track,al
	mov	word ptr command_blk.VC_buffer[0],offset track_buf
	mov	word ptr command_blk.VC_buffer[2],ds
	mov	dx,offset command_blk
	mov	ax,4403h
	mov	bx,vhctl_handle
	mov	cx,size VH_CMD
	int	21h
	cmp	command_blk.VC_status,STS_OK + 1
	cmc
	restore
	ret

write_track	endp

;*****************************************************************************
;
; Report an error based on the status byte in the command block.
;
;*****************************************************************************
report_error	proc	near

	save	ax, bx, dx
	mov	dx,offset err_prefix	; Display the error message prefix
	mov	ah,9			;
	int	21h			;
	mov	bl,command_blk.VC_status; Get returned status
	sub	bh,bh			; Make it a word
	shl	bx,1			; Make it a word offset
	mov	dx,error_msgs[bx]	; Get the appropriate error message
	mov	ah,9			;
	int	21h			;
	mov	dx,offset crlf_str	; Newline
	mov	ah,9			;
	int	21h			;
	restore
	ret				; Return to caller

report_error	endp


;*****************************************************************************
;
; Tell the user what label to put on this diskette
;
;*****************************************************************************
show_label	proc	near

	save	ax, bx, cx, dx
	mov	dx,offset label_disk	; Tell them to label the disk
	mov	ah,9			;
	int	21h			;
	mov	dx,offset ID_to_use	; Tell them what to label it with
	mov	cx,8			;
	mov	bx,1			;
	mov	ah,40h			; Use IOCTL Write to do it
	int	21h			;
	mov	ah,2			;
	mov	dl,' '			;
	int	21h			;
	mov	ah,2			;
	mov	dl,'#'			;
	int	21h			;
	mov	al,disk_num		; Display the disk number
	aam				;
	or	ax,3030h		;
	push	ax			;
	mov	dl,ah			;
	mov	ah,2			;
	int	21h			;
	pop	dx			;
	mov	ah,2			;
	int	21h			;
	mov	dx,offset crlf_str	; Finish off with a newline
	mov	ah,9			;
	int	21h			;
	restore
	ret				; Return to caller

show_label	endp


vhprep_code	ends


	end	start
