	SUBTTL	Main Module
        INCLUDE DEFS.INC                    ; which includes some other .incs

	include	debug.inc

StartData

;====================================================
;
; Device Header -
;
;====================================================

TAPE_DEV LABEL WORD
	DD	-1			; SYSINIT handles the segment part
	DW	DEV_CHAR_DEV or DEV_PROTECT or DEV_OPEN or DEVLEV_2 or DEV_GIOCTL
					; Use equates in DEVHDR.INC
					; to define Attribute word
	DW	STRAT			; offset to strategy routine entry point
	DW	0			; IDC entry point
	DB	'TAPE$4  '		; Name of device
DPS	DB	8 DUP (?)		; More reserved

; End of the device header.
; 
;============================================================================

;
;  data structure used by this device driver
;

	EVEN
DISPTAB LABEL WORD

	DW	TAPE_INIT	;00: Init
	DW	TAPE_EXIT1	;01: Media Check (Block Devices Only)
	DW	TAPE_EXIT1	;02: Build BPB (Block Devices Only)
	DW	TAPE_EXIT1	;03: Reserved (used to be Ioctl Input)
	DW	TAPE_INPUT	;04: Input (Read)
	DW	TAPE_INPNOWAIT 	;05: Non-Destructive Input, no wait.
	DW	TAPE_INPSTATUS 	;06: Input Status
	DW	TAPE_INPFLUSH	;07: Input Flush
	DW	TAPE_OUTPUT	;08: Output (Write)
	DW	TAPE_OUTVERIFY	;09: Output (Write) with verify
	DW	TAPE_OUTSTATUS	;0A: Output Status
	DW	TAPE_OUTFLUSH	;0B: Output Flush
	DW	TAPE_EXIT1	;0C: Reserved (used to be Ioctl Output)
	DW	TAPE_OPEN	;0D: Device Open
	DW	TAPE_CLOSE	;0E: Device Close
	DW	TAPE_EXIT1	;0F: Removable Media
	DW	TAPE_GIOCTL	;10: Generic Ioctl
	DW	TAPE_EXIT1	;11: Reset Media
	DW	TAPE_EXIT1	;12: Get Logical Drive Map
	DW	TAPE_EXIT1	;13: Set Logical Drive Map
	DW	TAPE_EXIT1	;14: DeInstall
	DW	TAPE_EXIT1	;15: Port Access
	DW	TAPE_EXIT1	;16: Partitionable HDs
	DW	TAPE_EXIT1	;17: Logical Unit Mal
	DW	TAPE_EXIT1	;18: -
	DW	TAPE_EXIT1	;19: -
	DW	TAPE_EXIT1	;1A: -
	DW	TAPE_EXIT1	;1B: -

MAXCMD = (($ - DispTab)/2) - 1

	public	DevHlpPtr

		even
DevHlpPtr	dd	0
ReqPkt		dd	0

waiting		db	0	;someone is waiting
lock_flag	db	0	;[0]: parm, [1]: data

		even
para_handle	dd	0	;ioctl param handle
data_handle	dd	0	;ioctl data handle
data_ptr	dd	0	;data pointer

cmd_read	db	08h, 01h, 0, 0,  0, 0
cmd_write	db	0Ah, 01h, 0, 0,  0, 0
cmd_sense	db	03h, 00h, 0, 0, 16, 0
sense_data	db	16 dup(?)
cmd_block	db	10 dup(?)
sense_data_ptr	dd	offset sense_data

    if SCSI_LVL
errcode		equ	12	;SCSI standard
    else
errcode		equ	14	;Tandberg
    endif

EndData

StartIData
	public	end_data
end_data	db	0
EndIData

StartICode
	public	end_code
end_code	db	0
EndICode

StartCode

	extrn	command:near
	extrn	bus_reset:near
	extrn	device_reset:near
	extrn	delay:near

page
;============================================================================
;
; - TAPE_STRAT -
;
;	Device Driver strategy entry point.
;	Parameters	- es:bx = Pointer to the request block
;

STRAT	PROC	FAR
	push	DevHlpPtr		;Make DevHlp callable w/o using DS/ES
	mov	bp, sp

	mov	ax, es
	mov	fs, ax
	mov	di, bx

	cmp	fs:[di].PktCmd, 0	;INIT command? Debug is not yet
	je	short str_1		;initialized.

	outtext	2, 'Strategy '
	outbyte	2, fs:[di].PktCmd
	outchar	2, '$'

str_loop:
	cmp	ReqPkt._seg, 0		;Driver busy?
	jne	str_busy

str_1:	mov	ReqPkt._off, di		;Save the bimodal address
	mov	ReqPkt._seg, fs		;of the request packet.
	movzx	eax, fs:[di].PktCmd	;Get command from packet and execute.
	cmp	al, MAXCMD
	ja	str_bad
	call	DISPTAB[eax*2]

str_x:	xor	ax, STDONE		;Mark operation complete.
	mov	fs:[di].PktStatus, ax

	outtext	2, 'Strategy return '
	outword	2, ax
	outchar	2, '$'

	cmp	waiting, 0		;Release waiting threads.
	je	short str_x1
	outtext	3, 'Wakeup waiting driver$'
	mov	ax, ReqPkt._seg
	mov	bx, ReqPkt._off
	mov	ReqPkt._seg, 0
	DevHlp	DevHlp_Run
	jmp	short str_r
str_x1:	mov	ReqPkt._seg, 0
str_r:	lea	sp, [bp+4]
	ret

str_busy:				;Driver is busy. Wait.
	outtext	2, 'Driver busy$'
	inc	waiting
	push	fs
	push	di
	mov	ax, ReqPkt._seg		;Use bimodal pointer of running
	mov	bx, ReqPkt._off		;request packet as event ID.
	mov	di, 0			;No timeout.
	mov	cx, 1000
	mov	dh, 0			;Interruptible.
	DevHlp	DevHlp_Block
	pop	di
	pop	fs
	pushf
	dec	waiting
	popf
	jnc	str_loop		;Retry on event or timeout wakeup.
	jz	str_loop

	mov	ax, STDONE+STERR+11h	;Character I/O call interrupted.
	mov	fs:[di].PktStatus, ax
	jmp	short str_r

str_bad:
	mov	ax, STERR+03h		;Unknown command.
	jmp	str_x

STRAT	    ENDP

TAPE_EXIT1	proc near
	mov	ax, STERR+03h		;Unknown command.
	ret
TAPE_EXIT1	endp

;====================================================
;
; Operations
; ----------
;
; In:	FS:DI	= ptr to request block
; Out:	AX	= exit code (incl. DONE)
;
;====================================================

TAPE_INPUT	proc near	;04: Input (Read)
	smsw	ax			;Protected mode only.
	test	al, 1
	jz	short terr_1
	mov	dx, offset cmd_read
	jmp	rw_command
terr_1:	mov	ax, STERR+01h		;Unknown unit.
	ret
TAPE_INPUT	endp

TAPE_INPNOWAIT	proc near	;05: Non-Destructive Input, no wait.
	mov	ax, STBUSY	;busy = none avail
	ret
TAPE_INPNOWAIT	endp

TAPE_INPSTATUS	proc near	;06: Input Status
	mov	ax, STBUSY	;busy = none avail
	ret
TAPE_INPSTATUS	endp

TAPE_INPFLUSH	proc near	;07: Input Flush
	sub	ax, ax
	ret
TAPE_INPFLUSH	endp

TAPE_OUTPUT	proc near	;08: Output (Write)
	smsw	ax			;Protected mode only.
	test	al, 1
	jz	short terr_1
	mov	dx, offset cmd_write
	jmp	rw_command
TAPE_OUTPUT	endp

TAPE_OUTVERIFY	proc near	;09: Output (Write) with verify
	smsw	ax			;Protected mode only.
	test	al, 1
	jz	short terr_1
	mov	dx, offset cmd_write
	jmp	rw_command
TAPE_OUTVERIFY	endp

TAPE_OUTSTATUS	proc near	;0A: Output Status
	sub	ax, ax		;no wait
	cmp	waiting, 0
	je	short ost_r
	mov	ax, STBUSY	;busy + done = wait required
ost_r:	ret
TAPE_OUTSTATUS	endp

TAPE_OUTFLUSH	proc near	;0B: Output Flush
	sub	ax, ax
	ret
TAPE_OUTFLUSH	endp

TAPE_OPEN	proc near	;0D: Device Open
	smsw	ax		;Protected mode only.
	test	al, 1
	jz	short terr_1
	sub	ax, ax
	ret
TAPE_OPEN	endp

TAPE_CLOSE	proc near	;0E: Device Close
	sub	ax, ax
	ret
TAPE_CLOSE	endp

;====================================================
;
; Generic IOCtl
;
;====================================================

TAPE_GIOCTL	proc near	;10: Generic Ioctl
	push	si

	outtext	2, 'IOCtl '
	outbyte	2, fs:[di].GIOCategory
	outchar	2, ' '
	outbyte	2, fs:[di].GIOFunction
	outchar	2, '$'

	mov	lock_flag, 0

	movzx	eax, fs:[di].GIOFunction
	cmp	fs:[di].GIOCategory, 80h	;Own IOCtl codes?
	je	short gio_cat_own
	cmp	fs:[di].GIOCategory, 11		;general IOCtl codes?
	je	short gio_cat_11

gio_bad:
	test	fs:[di].GIOFunction, 80h	;Ignore invalid command?
	jnz	short gio_r0
	mov	ax, STERR+03h			;No, unknown command.
	jmp	short gio_r

gio_cat_11:				;Category 11: general device control.
	cmp	al, 01h			;Flush input.
	je	short gio_r0
	cmp	al, 02h			;Flush output.
	je	short gio_r0
	cmp	ax, 41h			;Session switch notification?
	je	short gio_r0
	cmp	ax, 60h			;Query monitor support?
	mov	ax, STERR+12h		;Monitor calls not supported.
	je	short gio_r
	jmp	gio_bad

	even
giotab	dw	gio_scsi_slow		;0
	dw	gio_scsi_fast		;1
	dw	gio_bus_reset		;2
	dw	gio_dev_reset		;3
	dw	gio_set_trace		;4

gio_cat_own:				;Own IOCtl commands.
	and	al, 1Fh
	cmp	al, 04h
	ja	short gio_bad
	jmp	giotab[eax*2]

gio_scsi_slow:				;SCSI command - slow transfer.
	mov	al, 0
	call	gen_scsi_cmd
	jmp	short gio_r

gio_scsi_fast:				;SCSI command - fast transfer.
	mov	al, 1
	call	gen_scsi_cmd
	jmp	short gio_r

gio_bus_reset:				;SCSI bus reset.
	call	bus_reset
	jc	short gio_r
	jmp	short gio_r0

gio_dev_reset:				;SCSI device reset.
	call	device_reset
	jc	short gio_r
	jmp	short gio_r0

gio_set_trace:
    if	trace
	push	di
	mov	ax, fs:[di].GIOParaPack._seg
	mov	di, fs:[di].GIOParaPack._off
	mov	cx, 1
	mov	dl, 0			;read-only
	DevHlp	DevHlp_VerifyAccess
	pop	di
	jc	gio_seg
	les	bx, fs:[di].GIOParaPack
	mov	al, es:[bx]
	mov	tracelvl, al
    endif
	jmp	short gio_r0

gio_r0:	sub	ax, ax
gio_r:	push	ax

	test	lock_flag, 1		;Unlock parameter and data segment.
	jz	short gio_r1
	mov	ax, para_handle._hi
	mov	bx, para_handle._lo
	DevHlp	DevHlp_Unlock
gio_r1:
	test	lock_flag, 2
	jz	short gio_r2
	mov	ax, data_handle._hi
	mov	bx, data_handle._lo
	DevHlp	DevHlp_Unlock
gio_r2:
	mov	lock_flag, 0
	pop	ax
	pop	si
	ret

gio_seg:mov	ax, STERR+13h		;iInvalid parameter.
	jmp	short gio_r
TAPE_GIOCTL	endp

;====================================================
;
; Command Subroutines
;
;====================================================

;
; Request sense
;
; AX = device driver error status
; CX = number from information bytes
;
req_sense	proc near
	push	si

	mov	al, 0				;Execute "request sense"
	mov	ebx, sense_data_ptr
	mov	cx, 16
	mov	dx, offset cmd_sense
	call	command
	jc	reqs_no_stat
	cmp	cx, 4
	jb	reqs_no_stat

    if	trace ge 1
	outtext	1, 'Sense data:'
	mov	si, offset sense_data
reqs_0:		lodsb
		outchar	1, ' '
		outbyte	1, al
		loop	reqs_0
	outchar	1, '$'
    endif

	mov	al, sense_data+0		;Check for unexpected
	and	al, 7Fh				;small sense block
	cmp	al, 70h
	jne	short reqs_short

	cmp	sense_data+errcode, 0Ah		;Insufficient Capacity?
	jne	short reqs_std
	mov	al, sense_data+2		;EOM set but sense key
	and	al, 4Fh				;set to "NO SENSE"?
	cmp	al, 40h
	je	short reqs_ew

reqs_std:
	mov	al, sense_data+errcode		;Translate error class & code.
	jmp	short reqs_scan
reqs_short:
	mov	al, sense_data+0
reqs_scan:
	and	al, 7Fh
	mov	bx, offset reqs_t
	mov	cx, reqs_tlen
reqs_1:		mov	ah, cs:[bx+0]
		mov	dx, cs:[bx+1]
		add	bx, 3
		cmp	al, ah
		loopnz	reqs_1
	jnz	short reqs_unknown
	mov	ax, dx

reqs_r:	mov	cx, word ptr sense_data+5
	xchg	ch, cl
	pop	si
	ret

reqs_unknown:
	mov	ax, STERR+07h			;Any other: unknown media.
	jmp	reqs_r

reqs_no_stat:
	mov	ax, STERR+07h			;No sense block
	mov	cx, 0FFFFh
	pop	si
	ret

reqs_ew:					;Early EOT indication
	outtext	1, 'Early Warning$'
	mov	ax, STERR+09h			;"Out of paper" :-)
	jmp	short reqs_r

sxlat	macro	sense, system
	db	sense
	dw	system
	endm

reqs_t	label	byte
	sxlat 00h, 0	     ;no sense			no error
	sxlat 02h, STERR+02h ;hardware error		drive not ready	
	sxlat 04h, STERR+02h ;drive not ready		drive not ready
	sxlat 09h, STERR+02h ;media not loaded		drive not ready
	sxlat 0Ah, STERR+0Ah ;insufficient capacity	write fault
	sxlat 11h, STERR+04h ;uncorrectable data error	CRC error
	sxlat 14h, STERR+08h ;no record found		sector not found
	sxlat 17h, STERR+00h ;write protected		write protect violation
	sxlat 19h, STERR+04h ;bad block found		CRC error
	sxlat 1Ch, 0         ;file mark detected	EOF
	sxlat 1Dh, STERR+04h ;compare error		CRC error
	sxlat 20h, STERR+03h ;invalid command		unknown command
	sxlat 30h, STERR+0Dh ;unit attention		change disk
	sxlat 33h, STERR+0Ah ;append error		write fault
	sxlat 34h, STERR+0Bh ;read EOM			read fault
reqs_tlen	equ	($ - offset reqs_t) / 3

req_sense	endp

;
; Generic R/W command
; DX = command record
;
rw_command	proc near
	push	si

	outtext	3, 'R/W '
	outword	3, fs:[di].IOpData._hi
	outword	3, fs:[di].IOpData._lo
	outchar	3, ' '
	outword	3, fs:[di].IOcount
	outchar	3, '$'

	push	dx			;Make virtual address
	mov	ax, fs:[di].IOpData._hi
	mov	bx, fs:[di].IOpData._lo
	mov	cx, fs:[di].IOcount
	mov	si, data_ptr._seg
	DevHlp	DevHlp_PhysToGDTSel
	pop	dx
	mov	ax, STERR+13h		;Invalid parameter.
	jc	rw_e

	test	cx, 01FFh		;Verify block alignment
	mov	ax, STERR+13h		;Invalid parameter.
	stc
	jnz	short rw_e

	mov	al, ch			;Insert block count into command.
	shr	al, 1
	mov	bx, dx
	mov	[bx+4], al

	mov	al, 1			;Run the command.
	mov	ebx, data_ptr
	call	command
	jc	short rw_ret
	cmp	dl, 00h
	je	short rw_ret

	stc
	cmp	dl, 02h			;Check status?
	mov	ax, STERR+02h		;No -> not ready.
	jne	short rw_ret
	call	req_sense		;Do check status.
	shl	cx, 9			;Convert number of blocks not handled
	sub	fs:[di].IOcount, cx	;to number of bytes actually xferred.
	pop	si
	ret

rw_ret:	mov	fs:[di].IOcount, cx	;Number of bytes actually transferred.
	pop	si
	ret

rw_e:	mov	fs:[di].IOcount, 0
	pop	si
	ret
rw_command	endp

;
; Verify and lock ioctl parameter block.
;
vfy_para	proc near
	cmp	fs:[di].GIOParaLength, 0
	je	vfyp_1
	and	lock_flag, not 1
	mov	ax, fs:[di].GIOParaPack._seg
	mov	bh, 0				;Lock, short-term, any memory.
	mov	bl, 0				;Block until available.
	DevHlp	DevHlp_Lock
	jc	short vfyp_1
	mov	para_handle._hi, ax
	mov	para_handle._lo, bx
	or	lock_flag, 1
	push	di
	mov	cx, fs:[di].GIOParaLength	;Verify access.
	mov	ax, fs:[di].GIOParaPack._seg
	mov	di, fs:[di].GIOParaPack._off
	mov	dh, 0				;Read-only.
	DevHlp	DevHlp_VerifyAccess
	pop	di
vfyp_1:	ret
vfy_para	endp

;
; Verify and lock ioctl data block.
; The data block can be NULL.
;
vfy_data	proc near
	cmp	fs:[di].GIODataLength, 0
	je	vfyd_1
	and	lock_flag, not 2
	movzx	eax, fs:[di].GIODataLength	;NULL pointer & length?
	or	eax, fs:[di].GIODataPack
	jz	short vfyd_1
	mov	ax, fs:[di].GIODataPack._seg
	mov	bh, 0				;Lock, short-term, any memory.
	mov	bl, 0				;Block until available.
	DevHlp	DevHlp_Lock
	jc	short vfyd_1
	mov	data_handle._hi, ax
	mov	data_handle._lo, bx
	or	lock_flag, 2
	push	di
	mov	cx, fs:[di].GIODataLength	;Verify access.
	mov	ax, fs:[di].GIODataPack._seg
	mov	di, fs:[di].GIODataPack._off
	mov	dh, 1				;Read+write.
	DevHlp	DevHlp_VerifyAccess
	pop	di
vfyd_1:	ret
vfy_data	endp

;
; Generic IOCTL,  Subfunction "general SCSI command"
; AL = 0 for slow, 1 for fast xfer
;
gen_scsi_cmd	proc near
	push	ax				;Slow/fast flag.

	call	vfy_para
	jc	short gen_e1
	call	vfy_data
	jc	short gen_e1

	mov	cx, fs:[di].GIOParaLength	;Copy command to data segment.
	cmp	cx, 10
	ja	short gen_e1
	push	di
	push	si
	lgs	si, fs:[di].GIOParaPack
	mov	ax, ds
	mov	es, ax
	mov	di, offset cmd_block
	cld
	rep	movs byte ptr es:[di], byte ptr gs:[si]
	pop	si
	pop	di

	pop	ax				;Slow/fast flag
	mov	ebx, fs:[di].GIODataPack	;Data pointer
	mov	cx, fs:[di].GIODataLength
	mov	dx, offset cmd_block		;Execute command
	call	command
	jc	short gen_r
	cmp	dl, 0				;Is ok, test SCSI status byte.
	je	short gen_r
	mov	al, 80h				;Not ok, return SCSI status byte.
	or	al, dl
	jmp	short gen_r

gen_e1:	add	sp, 2
gen_e:	mov	ax, STERR+13h			;Invalid parameters.
gen_r:	ret
gen_scsi_cmd	endp

;====================================================
;
; Initialization
;
;====================================================

	extrn	init_io:near
	extrn	init_debug:near

TAPE_INIT	proc near	;00: Init
	mov	eax, fs:[di].InitDevHlp	;Set device helper vector.
	mov	DevHlpPtr, eax
	mov	ss:[bp], eax

	mov	sense_data_ptr._seg, ds	;Fixup.

	push	di			;Allocate selector for data pointer.
	mov	ax, ds
	mov	es, ax
	mov	di, offset data_ptr._seg
	mov	cx, 1
	DevHlp	DevHlp_AllocGDTSel
	pop	di
	jc	init_e

;	les	bx, fs:[di].InitParms
;	FIXME: Interpret command line.

	call	init_debug
	jc	init_e

	call	init_io
	jc	init_e

 	outtext	2, 'ST_Tape Init done$'

init_r:	mov	fs:[di].InitcUnit, 0
	mov	fs:[di].InitEcode, offset end_code
	mov	fs:[di].InitEdata, offset end_data
	mov	fs:[di].InitpBPB, 0
	sub	ax, ax
	ret

init_e:	mov	fs:[di].InitcUnit, 0
	mov	fs:[di].InitEcode, 0
	mov	fs:[di].InitEdata, 0
	mov	ax, STERR+0Ch
	ret
TAPE_INIT	endp

EndCode

	end
