	.TITLE	MKDRIVER - VAX/VMS SCSI Tape Class Driver
	.IDENT	'X-33A1A1'
;	.LIST	MEB

;****************************************************************************
;*									    *
;*  COPYRIGHT (c) 1978, 1980, 1982, 1984, 1986, 1988, 1991 1992, BY	    *
;*  DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASSACHUSETTS.		    *
;*  ALL RIGHTS RESERVED.						    *
;* 									    *
;*  THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND COPIED   *
;*  ONLY IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE   *
;*  INCLUSION OF THE ABOVE COPYRIGHT NOTICE. THIS SOFTWARE OR  ANY  OTHER   *
;*  COPIES THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY   *
;*  OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE IS  HEREBY   *
;*  TRANSFERRED.							    *
;* 									    *
;*  THE INFORMATION IN THIS SOFTWARE IS  SUBJECT TO CHANGE WITHOUT NOTICE   *
;*  AND  SHOULD  NOT  BE  CONSTRUED AS  A COMMITMENT BY DIGITAL EQUIPMENT   *
;*  CORPORATION.							    *
;* 									    *
;*  DIGITAL ASSUMES NO RESPONSIBILITY FOR THE USE  OR  RELIABILITY OF ITS   *
;*  SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DIGITAL.		    *
;* 									    *
;*									    *
;****************************************************************************
;

;+
;
; FACILITY:
;
;	VAX/VMS SCSI Tape Class Driver
;
; ABSTRACT:
;
;	This module contains the class driver to control tape devices on
;	a SCSI bus.
;		
;
; AUTHOR:
;
;	Jim Klumpp	13-Jan-1989
;
; REVISION HISTORY:
;
;	??			Glenn Everhart
;		Add "software front panel" latent density controls.
;		If ucb$l_frontpnl is nonzero, use its value as a
;		SCSI density. Otherwise normal.
;	X-33A1A1 SCS		Sue Sommer			21-Nov-1995
;               Re-instate skip-by-file, implementing fixes:
;		- Return correct space count in IOSB.
;		- Set EOF bit when appropriate.
;		- Modify TRANS_SENSE_KEY to return ENDOFVOLUME properly.
;		- Remove references to ucb$l_boff/bcnt.
;		- Exempt TSZ05/7 from skipfile due to lack of BLANK CHECK.
;		- Check the LOST bit in SPACE_FILEMARKS before exiting.
;		- For skip count of 1 or 2, skip by record instead of by file;
;		  the overhead isn't worth it.
;		- Rename for the sake of clarity:  
;			CHECK_READPOS_SUPPORT to CHECK_SKIPFILE_SUPPORT;
;		  	UCB$V_READPOS_SUPPORTED to UCB$V_SKIPFILE_SUPPORTED; 
;			UCB$V_SKIPREC_INPROG to UCB$V_SKIP_INPROG. 
;
;       X-36    TGG0117        Tom Goodwin			10-Oct-1995
;		Disable the skipfiles functionality by clearing the 
;		READPOS_SUPPORTED bit of the UCB at unit init time.
;
;       X-35    RCL             Rick Lord                       10-May-95
;               Add last-minute check of the UCB$V_CANCEL bit at the end of
;               IO_DIAGNOSE to prevent sending an already-deallocated IRP
;               off to REQCOM [QAR V6 #927]
;
;       X-34	TGG0101        Tom Goodwin                      9-Jan-1995
;               Modify skipfile operation to use Space Filemarks instead
;               of Skip Records wherever possible; major performance
;               enhancement.  Specific modifications:
;               - Add READ_POSITION command to SCSI command table
;               - Add bits to ucb$l_mk_flags
;               - In PACKACK_SEQ, BSBW to a new routine CHECK_READPOS_SUPPORT.
;               - In IO_SKIP_FILE, BSBW to a new routine SKIP_FILE.
;               - In IO_SKIP_FILE, add new code to support SPACE FILEMARKS.
;               - Exempt READ POSITION support checks from error logging
;                 in LOG_EXTND_SENSE.
;
;	X-33 	MCY		Mary Yuryan 			13-Jun-1994
;		Modification of X-23.  Branch around the INSV instruction
;               in GET_DEVICE_TYPE after checking for compaction bit
;               enabled, rather than for a specific device type.
;
;	X-32	KFM		Kevin F. Martin			07-Feb-1994
;		Add DDR support.
;
;	X-31	KFM		Kevin F. Martin			07-Feb-1994
;		Remove the Receive Diagnostic command from UNIT_INIT path
;		for the TZK50 only, due to fatal errors on the SCSI bus 
;		during DATA IN phase. The code always brought the device	
;		online regardless of the revision level check outcome but
;		did flag the user that the TZK50 did not meet a minimum 
;		revision level via errorlog output. This errorlog output
;		is also disabled for the TZK50.  
;
;	X-30	KFM		Kevin F. Martin			07-Sep-1993
;		Remove the Receive Diagnostic command from the PACKACK
;		sequence for TZK50 only. This was causing fatal errors
;		on the SCSI bus when executed as part of the PACKACK
;		sequence. The hardware/firmware revision level for the TZK50 
;		is obtained via the UNIT_INIT sequence.
; 
;	X-29	MCY		Mary Yuryan 			11-Aug-1993
;		Add latent support for TZ865.
;
;	X-28	MCY 		Mary Yuryan 			05-May-1993
;		Three Bugfixes.
;		- change error logged for BLANK CHECK in the SENSE_KEY_TABLE
;		  from DRVERR to OPINCOMPL.
;		- Add check for 0 trans_cnt (scdrp$l_trans_cnt) in 
;		  SETUP_COMPACTION. 
;               - In SETUP_CMD: increase DMA_OUT timeout to 10 secs for 
;		  TZ30 end of tape problem. (Bill Peters - 2/17/93) 
;
;	X-27	MCY		Mary Yuryan 		     	14-Apr-1993
;		Add the following SCSI devices - TZ88, TZ875, TZ885, TZ887,
;		TZ89, TZ895, TZ897, TL810, TL820, TLZ07, TLZ7.
;
;	X-26 	MCY		Mary Yuryan			13-Apr-1993
;		Add RETENSION support for the TZK11.  Add the IF_TKZ11 and 
;		IFNOT_TZK11 macros, and make the device check in IO_RETENSION.
;
;	X-25	MCY		Mary Yuryan / Rick Lord		17-Sep-1992
;		- Enhance loader support, delete old CHECK_FOR_LOADER macro,
;		  Add loader byte to SCSI_DEV_TYPES1, check for loader in
;		  GET_DEVICE_TYPE, add loader byte to device string in 
;		  SCSI_DEV_TYPES.	RCL
;		- In LOG_EXTND_SENSE, add check for BOT and BLANK CHECK error
;		  & supress logging the error - for new tapes.   MCY
;		- Add check for cancelled IO in IO_SENSECHAR: to avoid reading
;		  cancelled IO/deallocated IRP.   RCL
;		- Complete TKZ09 support - add check for TKZ09 after check for 
;		  8mm tape in GET_DEVICE_TYPE.
;		- Merge in John Meneghini's fixes below.  MCY
;
;		JAM0008	John Meneghini			23-JUL-1992
;		Lengthen Disconnect & DMA/Phase change timeouts with 
;		ERASE, SPACE and RECIEVE DIAGNOSTICS commands.
;		- Increased DISCONNECT timeouts to 5 hours w/ SPACE & ERASE
;		- Increased DMA timeouts to 1 min. w/ RECIEVE_DIAGNOSTICS 
;
;	X-24	JSSBLADE	John S. Simakauskas		24-June-1992
;		Add  TKZ09 - 5GB, 8mm, SCSI Tape (TKZ08 Follow-on) 
;
;	X-23	JAM001		John Meneghini		06-MAY-1992
;		Branch around INSV instruction in GET_DEVICE_TYPE routine
;		setting MT$S_DENSITY field in UCB$_DEVDEPEND.  Was
;		improperly resetting Compaction (to zero) w/TLZ06.
;
;		X-22 	MCY 	Mary Yuryan			25-Feb-1992
;		Merge loader support from Amber. Create the CHECK_FOR_LOADER
;		macro which will set the loader bit if TZ857 or TLZ6 is 
;		present, called from the INQUIRY routine.  
;		Sync the VSC idents...
;
;		X-20	MCY	Mary Yuryan 			20-Dec-1991
;               Add latent support for SCSI loaders - TKZ60, TZ857,
;               TZ867,TZ877, and TLZ6 (TLZ06 loader.)
;
;	X-14A1A1A1 FAK002	Forrest A. Kenney	26-Nov-1991
;		Merge in latest Blade changes.
;
;		X-19	WJG0049		W. John Guineau		15-Nov-1991
;			Add DDR support.
;
;		X-18	DWF0022		David W. Freund		12-Nov-1991
;			Fold in X-16A1 by W. John Guineau:
;			- Fix bug introduced in X-16 for rewind timout check
;			  WAIT_UNIT_READY - add a BRB around UNLOAD timeout
;			  code path.
;
;		x-17	WJG     W. John Guineau                 8-Oct-1991
;			- Fix EXABYTE (8MM) DCL COPY command problems.
;			- Fix resource contention problems due to dma buffer
;			  deadlock conditions when a SCSI error occurs and all
;	 		  buffer space is allocated. Now we allocate enough
;			  mapping resources at UNIT_INIT in ALLOC_REQSNS_RESOURCE
;			  for the REQUEST SENSE to get through (which was prevoiusly
;			  hanging waiting for resources which were locked until the 
;		 	  REQUEST SENSE completed, which coudn't because it couldn't
;			  get resources to execute!)
;
;	X-14A1A1FAK001		Forrest A. Kenney	07-Nov-1991
;		Merge in MAGIC and BLADE changes since T2 snapshot.
;
;		X-16	WJG	W. John Guineau			7-Oct-1991
;			- Change unload timeout logic from X-15 to use separate
;			  bit (UCB$M_UNLOAD_INPROG not UCB$M_REWIND_INPROG)
;			  and time field (UCB$L_UNLOAD_TIME) so we don't hang on
;			  IO$_AVAILABLE after a DISMOUNT. This is necessary 
;			  since we must not timeout after an UNLOAD with media 
;			  stackers for at least 3 minutes to give the stacker 
;			  time to get the next tape loaded.
;
;		X-15	WJG	W. John Guineau			1-Oct-1991
;			- Add REWIND timeout logic to IO_UNLOAD code path to
;			  prevent loaders from timwing out while traversing a 
;			  media stack.
;		- Make ident match VSC ident
;
;	X-14A1	FAK001		Forrest A. Kenne	23-Sep-1991
;		Merge C2 changes into Blade.  Make module ident and revision
;		history agree with CMS after the master pack cleanup.
;
;	X-25	WJG	W. John Guineau			14-Aug-1991
;		Return the compaction "density" values in the sense mode
;		data packet for BACKUP.
;
;       x-24    WJG     W. John Guineau                 22-May-1991
;               Add UCB$V_COMPCHK_IN_PROG  bit to flag when compaction
;               check is in progress to prevent bad scsi status on
;               devices which don't support compaction from setting tape
;               lost
;
;       x-23    WJG     W. John Guineau                 15-May-1991
;               Fix DMA Length field in MODE_SENSE_COMP SCSI_CMD
;               descriptor to be the exact value of data in expected
;               to work around a data pointer manipulation bug in
;               PKIDRIVER
;
;	X-22	WJG	W. John Guineau			17-April-1991
;		More compaction fixes - this time with real hardware to test
;		on!  Moved check for compaction support into separate
;		routine CHECK_COMPACTION_SUPPORT, utilized MT2$V_COMP_ENA
;		bit in DEVDEPND2 instead of density field. Also, now
;		returns SS$_NORMAL if you set a density a device doesn't
;		support (previous behaviour) -  CHECK_COMPACTION_SUPPORT is 
;		called from UNIT_INIT (via SET_UNIT_ONLINE) and from PACKACK.
;
;	X-21	WJG	W. John Guineau			3-April-1991
;		Allow SCSI-1 compliant devices to be used. The check added
;		in X-18U3 only allowed SCSI-2 via UCB$B_SCSI_VERSION
;
;	X-20	WJG	W. John Guineau			7-Mar-1991
;		Merge Magic and Sigma code streams
;
;		X-19	Was Magic code stream ident for X-16U6 below:
;       	X-16U6  MCY     Mary Yuryan                     19-Feb-1991
;               Remove default mode_select parameters from the SCSI_DEV_
;               TABLES for the TSZ05,(TZX0) & TSZ07.  The RDEW bit caused
;               mutiple volume backup problems when set, causing the backup
;               operation to fail.
;
;	X-18U3	WJG	W. John Guineau			4-March-1991
;		Make data compaction support more generic for SCSI
;		in VMS. Enable automatic recognition of devices which
;		support compaction. Add DISABLE/REENABLE_ERRLOG macros
;		from DKDRIVER. Add UCB$B_SCSI_VERSION field in UCB for 
;		recognition of SCSI2 compliant devices.
;
;	X-18U2	WJG	W. John Guineau			28-Feb-1991
;		Add TZK11 and TLZ06 data compaction support. 
;
;	X-18U1	MCY	Mary Yuryan, Howard Palmer	20-Feb-1991
;		Change default mode_select paramters for the TSZ05,
;		TSZ07 to fix the multi-volume backup failures with 
;		the RDEW bit set. 
;
;
;	X-16U5	MCY	Mary Yuryan			20-Dec-1990
;		Move the TZK10 RETENSION function from SKIPFILES to
;		the UNLOAD and REWIND functions.  Add TLZ06 tape symbol,
;		next generation RDAT. 
;
;	X-16U4  JTK	Jim Klumpp			18-Dec-1990
;		Change the algorithm for determining whether a device
;		is not ready due to a previous rewind/immediate command.
;		Before, the driver would always assume a rewind was in 
;		progress and wait at least the maximum rewind time before 
;		timing out a wait unit ready polling loop. Now, whenever 
;		a rewind/immediate is sent to the drive, the time by which 
;		this command should complete is recorded in the UCB. This 
;		information can then be used to time out wait unit ready
;		polling activity.
;
;	X-16U3  JTK	Jim Klumpp			11-Dec-1990
;		Add a workaround for the TZK50 phase error bug. Delay for
;		one second after sending a SCSI unload command to the TZK50
;		to prevent subsequent test unit ready commands from being
;		told the device is ready, when in fact it's really in the
;		process of being rewound and unloaded.
;
;	X-16U2  MCY	Mary Yuryan, Barbara Leahy	27-Nov-1990
;		Add the RETENSION command for the TZK10 tape drive.
;		Create new SCSI command packet for "RETENSION" using
;		the SCSI "LOAD" command.  Created modifier IO$M_RETENSION
;		IODEF for IO$_SKIPFILE function code.  Created IFNOT_TZK10
;		macro to check for proper device type.
;
;	X-16U1	MCY	Mary Yuryan, Howard Palmer  	20-Nov-1990
;		Remove the very short timeout period for the TSZ07
;		in the WAIT_UNIT_READY: routine.  The current timeout
;		caused DCL commands such as mount/init to timeout while
;		waiting for the tape to rewind. 
;
;	X-16	JTK	Jim Klumpp, Howard Palmer	13-Jul-1990
;		Add support for 9-track, dual density TSZ07 device.
;		IO_SETMODE, IO_SENSEMODE routines modified to provide
;		setting/reporting of TSZ07 density. Return SS$_DATAOVERUN 
;		status if a read fails with ILI status and the user 
;		requested less data than was in the actual record.
;
;	X-15	JTK	Jim Klumpp			06-Jun-1990
;		In SETUP_CMD, map the buffer with high priority to avoid
;		deadlock. The map buffers elsewhere can remain at low
;		priority, as there's no danger of deadlock on the first
;		call to map buffer per QIO function.
;
;	X-14	MCY	Mary Yuryan			06-Mar-1990
;		Increase SCSI command timeout values to match those
;		of the RDAT tape drive.  Increase the default timeout
;		from 4 seconds to 30 seconds.  Add new inquiry entry
;		for ucode change made to fix PVAX console output. 
;		Add device name for QIC tape - TZK10.
;
;	X-13	JTK	Jim Klumpp			23-Jan-1990
;		Fix rewind/immediate. The command following a 
;		rewind/immediate can fail if the rewind is still in
;		progress. Call the wait unit ready routine in this
;		situation to prevent the new command from failing.
;
;	X-12	MCY	Mary Yuryan		       18-Jan-1990
;		Fix device identification field returned by the 
;		inquiry command that changed with new micro-code.
;
;	X-11	JTK	Jim Klumpp			5-Jan-1990
;		Fix revision checking. Bring a drive online whether it's
;		out of rev or not (to prevent problems during installation).
;
;	X-10	MCY 	Mary Yuryan 			28-Dec-1989
;		Add  TLZ04 (RDAT) tape support. 
;
;	X-9	JTK	Jim Klumpp			28-Sep-1989
;		Merge changes from 5.3 stream including:
;
;		X-7U2	JTK	Jim Klumpp			25-Sep-1989
;			Fix read/reverse bug. Remove revision checking of
;			third party drives. Decrease mount timeout time.
;
;		X-7U1	DGB0318	Donald G. Blair			05-Aug-1989
;			Add DPT$V_NO_IDB_DISPATCH bit to the driver prologue table.
;
;	X-8	JTK	Jim Klumpp			4-Aug-1989
;		Change logical end of volume handling to ignore the 
;		mount status. Fix ident to match master pack. 
;
;	X-6	JTK	Jim Klumpp			22-Jun-1989
;		Add callback support and data structure version
;		checking for SPI$CONNECT. Remove TZ30-specific timeout
;		support. Add fastboot support. Fix bug in IO_SKIP_RECORD_REV.
;		Add IO_WRITEMARK routine which is equivalent to IO_WRITEOF.
;		Translate all media errors to SS$_PARITY. Add density field
;		to device type table. Fix multi volume test code. Add
;		workaround for tapemark handling synchronization bug.
;		Remove $SCDTDEF macro. Change number of arguments passed
;		to SET_CONN_CHAR. Change priv needed for IO_DIAGNOSE function.
;
;	X-5	JTK	Jim Klumpp			16-Jun-1989
;		Add TZ30-specific timeout values, retry in SEND_COMMAND
;		if drive returns BUSY status, several minor bugfixes.
;
;	X-4	JTK	Jim Klumpp			 1-Jun-1989
;		Add more robust checking of the additional field in
;		extended sense data.
;
;	X-3	JTK	Jim Klumpp			12-May-1989
;		SCSI tape class driver: complete replacement of
;		PVAX monolithic tape driver.
;-

	.SBTTL	+
	.SBTTL	+ SYMBOL DEFINITIONS
	.SBTTL	+
	.SBTTL	External symbol definitions
	.library	/sys$library:lib/
;
; External symbols
;

	$CANDEF				; Cancel reason codes
	$CRBDEF				; Channel request block
	$DCDEF				; Device classes and types
	$DDBDEF				; Device data block
	$DEVDEF				; Device characteristics
	$DYNDEF				; Data strucure types
	$EMBDEF				; Errorlog message buffer
	$FKBDEF				; Define fork block symbols
	$IDBDEF				; Interrupt data block
	$IODEF				; I/O function codes
	$IPLDEF				; Hardware IPL definitions
	$IRPDEF				; I/O request packet
	$MTDEF				; Magtape status codes
	$MT2DEF				; Extended Magtape status codes
	$NSADEF				; Security Symbols
	$ORBDEF				; Object rights block
	$PCBDEF				; Process control block
	$PRVDEF				; Privilege mask
	$PTEDEF				; Page table entry symbols
	$SCDRPDEF			; SCSI SCDRP symbols
	$SPDTDEF			; SCSI PDT symbols
	$SSDEF				; System status codes
	$UCBDEF				; Unit control block
	$VADEF				; Virtual address symbols
	$VECDEF				; Interrupt vector block
	$WCBDEF				; Window control block
	$DTNDEF				; DDR DTN offsets
	$DTUDEF				; DDR DTU offsets

	.SBTTL	Misc local symbols
;
; Local symbols
;

        LOADER = 1                      ; Specify that a device is a loader -
					;  	mcy / rcl - 9/18/92
;	DEBUG = 1			; Flag to enable various tracing and
					; debug features.
	.IF DEFINED DEBUG
	.PRINT	; - DEBUG flag is enabled
	.ENDC

;	SCSI_STACK_CHECKING = 1		; Flag to enable bounds checking by
					; SUBSAVE and SUBRETURN macros
	.IF DEFINED SCSI_STACK_CHECKING
	.PRINT	; - SCSI stack bounds checking is enabled
	.ENDC

;	MULTI_VOLUME_TEST = 1		; Flag to enable multi-volume tape
					; testing by causing ENDOFTAPE status
					; to be returned on any writes after
					; record 100 (hex).
	.IF DEFINED MULTI_VOLUME_TEST
	.PRINT	; - Remove code to test multi-volume tapes
	.ENDC

	P1 = 0		; Offset to P1 parameter in FDT routine
	P2 = 4		; Offset to P2 parameter in FDT routine

	SCDRPS_PER_UNIT		= <3+1>	; Number of SCRPs to allocate per unit
	READY_POLL_INTERVAL	= 1	; Interval for test unit ready polling
	BUSY_RETRY_CNT		= 10	; Number of times to retry sending 
					; command if device is busy

	MAX_BCNT		= 65535	; Maximum byte count per transfer
					
	UCB_STACK_SIZE		= 10	; Size of internal stack in UCB

	DEFAULT_DISCONNECT_TIMEOUT = 30	; Default values in seconds for disconnect
	DEFAULT_PHASE_CHANGE_TIMEOUT = 30 ; and phase change timeouts

	MK_ERROR_REVISION = 2		; Errorlogging revision supported by
					; this driver. This should be incremented
					; each time an incompatible change is
					; made to the errorlog packet format.

	MAX_REWIND_TIME = 3*60		; Maximum time a rewind command should
					; take

	MAX_UNLOAD_TIME = 3*60		; Maximum time an unload command should
					; take

	DEF_TAPE_PARAMS = -		; Default tape parameters: 
	 <MT$K_DEFAULT @ MT$V_FORMAT>!-	; default density
	 <MT$K_BLK_833 @ MT$V_DENSITY>	; BLK_833 format

        BPU_BIT = 2                     ; Block position unknown in READ POS data

	.SBTTL	SCSI status codes

; Define SCSI status codes

	SCSI$M_STAT_MASK	= ^XE1	; SCSI status byte mask
	SCSI$C_GOOD_STATUS	= 0	; Good (normal) status
	SCSI$C_CHK_CONDITION	= 2	; Check condition (send a request sense)
	SCSI$C_BUSY		= 8	; Device is busy

	.SBTTL	Peripheral devices types

; Peripheral device type returned by the INQUIRY command

	SCSI$C_TAPE		= 1	; Tape device

	.SBTTL	Sense key codes

; Define SCSI sense key codes.

	SCSI$C_NO_SENSE		= 0	; No sense data
	SCSI$C_RECOVERED_ERROR	= 1	; Recovered error (treated as success)
	SCSI$C_NOT_READY	= 2	; Device not ready
	SCSI$C_MEDIUM_ERROR	= 3	; Medium (parity) error
	SCSI$C_HARDWARE_ERROR	= 4	; Hardware error
	SCSI$C_ILLEGAL_REQUEST	= 5	; Illegal request
	SCSI$C_UNIT_ATTENTION	= 6	; Unit attention (media change, reset)
	SCSI$C_DATA_PROTECT	= 7	; Data protection (writelock error)
	SCSI$C_BLANK_CHECK	= 8	; Blank check (advance past end of data)
	SCSI$C_VENDOR_UNIQUE	= 9	; Vendor unique key
	SCSI$C_COPY_ABORTED	= 10	; Copy operation aborted
	SCSI$C_ABORTED_COMMAND	= 11	; Command aborted
	SCSI$C_EQUAL		= 12	; Compare operation, data match
	SCSI$C_VOLUME_OVERFLOW	= 13	; Write beyond physical end of tape
	SCSI$C_MISCOMPARE	= 14	; Compare operation, data mismatch

; Define one additional sense code. This particular one is used to distinguish
; between fatal and non-fatal medium errors. In a medium error sense key is
; returned and the additional sense code is uncorrectable ECC error, then return
; SS$_PARITY status but continue to process commands for this device. Otherwise,
; set position list as the media error may have caused the tape to loose its
; position.

	SCSI$C_UNCORECT_ECC	= ^X11	; Uncorrectable ECC error

; Define offsets in various SCSI command packets.

	SCSI_XS$B_ERR_CODE	= 0	; Extended sense error code
	SCSI_XS$B_KEY		= 2	; Extended sense KEY field
	SCSI_XS$V_KEY		= 0	; Extended sense KEY bit number
	SCSI_XS$S_KEY		= 4	; Extended sense KEY length
	SCSI_XS$B_ADDNL_INFO	= 3	; Extended sense additional code
	SCSI_XS$B_ADDNL_CODE	= 12	; Extended sense additional code
	SCSI_XS$B_ADDNL_CODE30	= 8	; "			       " (TZ30)
	SCSI_XS$B_ADDNL_CODE50	= 8	; "			       " (TZK50)
	SCSI_XS$M_EOF		= ^X80	; Extended sense end of file
	SCSI_XS$M_EOM		= ^X40	; Extended sense end of medium
	SCSI_XS$M_ILI		= ^X20	; Extended sense illegal length indicator
	SCSI_XS$V_ADDNL_VALID	= 7	; Extended sense additional data valid


	SCSI_WFM$B_CNT		= 2	; Write filemarks count
	SCSI_RD$B_LEN		= 2	; Read transfer length
	SCSI_WRT$B_LEN		= 2	; Write transfer length
	SCSI_RWND$B_IMMED	= 1	; Rewind immediate flag

	SCSI_INQ$B_DEVTYPE	= 0	; Inquiry device type
	SCSI_INQ$B_DEVQUAL	= 1	; Inquiry device qualifier field
	SCSI_INQ$V_DEVQUAL	= 0	; Inquiry device qualifier starting bit
	SCSI_INQ$S_DEVQUAL	= 7	; Inquiry device qualifier length
	SCSI_INQ$V_REMOVABLE	= 7	; Inquiry removable bit

	SCSI_SKIP$B_CNT		= 2	; Skip record count

	SCSI_RCVD$B_HW_REV	= 0	; Receive diagnostic HW revision field
	SCSI_RCVD$B_SW_REV	= 1	; Receive diagnostic SW revision field

	SCSI_MSNS$B_WP		= 2	; Mode sense write protect field
	SCSI_MSNS$V_WP		= 7	; Mode sense write protect bit
	SCSI_MSNS$B_BLOCK	= 3	; Mode sense block descriptor length
	SCSI_MSNS$V_BLOCK	= 3	; Mode sense block descriptor bit
	SCSI_MSNS$B_DENSITY	= 4	; Mode sense density code

	SCSI_MSEL$W_RSVD0	= 0	; Mode select reserved
	SCSI_MSEL$B_SPEED	= 2	; Mode select speed field
	SCSI_MSEL$B_MODE	= 2	; Mode select buffered mode
	SCSI_MSEL$B_DSCLEN	= 3	; Mode select record descriptor length
	SCSI_MSEL$C_DSCLEN	= 8	; Mode select record descriptor length
	SCSI_MSEL$B_DENS	= 4	; Mode select density
	SCSI_MSEL$B_BLOCKS	= 5	; Mode select number of blocks
	SCSI_MSEL$B_RSVD1	= 8	; Mode select reserved
	SCSI_MSEL$B_BLKLEN	= 9	; Mode select block length 
	SCSI_MSEL$B_VULEN	= 12	; Mode select vendor unique length
	SCSI_MSEL$B_VU		= 13	; Mode select vendor unique field
	SCSI_MSEL$M_BUF		= ^X10	; Mode select buffered mode
	SCSI_MSEL$B_COMP	= 14	; Mode select data compression algorithm
	SCSI_MSEL$M_NOF 	= 7	; Number of fillers for generic device
	SCSI_MSEL$M_NOF50 	= 7	; Number of fillers for TZK50
	SCSI_MSEL$M_NOF30 	= ^X0F	; Number of fillers for TZ30
	SCSI_MSEL$M_RESEL 	= ^X40	; Reselection timeout flag

; Get/set connect characteristics symbols.

	SET_CON$L_LEN		= 0	; Length field
	SET_CON$L_CON_FLAGS	= 4	; Flags field
	SET_CON$M_DISC		= 1	; Enable disconnect flag
	SET_CON$M_NORETRY	= 2	; Disable command retry flag
	SET_CON$L_SYN_FLAG	= 8	; Synchronous flag field
	SET_CON$M_SYN		= 1	; Synchronous flag

; Request Sense resource

	REQSNS_SIZE = 19		; 19 bytes for request sense data

	.SBTTL	Tape class driver extensions to the UCB
;
; Tape class driver extensions to the UCB.
;

	$DEFINI	UCB			; Start of UCB definitions

	. = UCB$K_LCL_TAPE_LENGTH
	
$DEF	UCB$L_HW_REV 	.BLKL	1	; Hardware revision field
$DEF	UCB$L_STACK_PTR .BLKL	1	; Internal stack pointer
$DEF	UCB$L_STACK	.BLKL	UCB_STACK_SIZE	; Internal stack 
$DEF	UCB$L_SCDRP	.BLKL	1	; Address of active SCDRP
$DEF	UCB$L_SCDRP_SAV1 .BLKL	1	; Address of saved SCDRP
$DEF	UCB$L_FLUSH_IOQFL .BLKL	1	; I/O flush queue forward link
$DEF	UCB$L_FLUSH_IOQBL .BLKL	1	; I/O flush queue backward link
$DEF	UCB$L_ERR_MASK	.BLKL	1	; Mask of error types logged so far
$DEF	UCB$B_ERR_ARRAY	.BLKB	16	; Saved error array used to filter errors
$DEF	UCB$L_SCDT	.BLKL	1	; SCDT address
$DEF	UCB$L_MK_FLAGS	.BLKL	1	; Class driver flags
	$VIELD	UCB,0,<-		;
		<REMOVABLE,,M>,-	; Removable media
		<WAITRDY_INPROG,,M>,-	; Wait for unit ready in progress
		<DISCONNECT,,M>,-	; Device supports disconnect
		<SYNCHRONOUS,,M>,-	; Device supports synchronous operation
		<DISABL_ERRLOG,,M>,-	; Disable errorlogging
		<REV_SKIP,,M>,-		; Reverse motion in progress
		<ADDNL_INFO,,M>,-	; Additional data is valid
		<SKIP_INPROG,,M>,-	; Skip record operation in progress
		<REWIND_INPROG,,M>,-	; Rewind operation may still be active
		<UNLOAD_INPROG,,M>,-	; Unload operation may still be active
                <COMPCHK_IN_PROG,,M>,-	; Compaction support check in progress
		<DEVICE_IS_8MM,,M>,- 	; Device is an 8MM device
                <REQSNS_PAGE_INUSE,,M>,-; Request Sense resources in use
                <SKIPFILE_SUPPORTED,,M>,-; Device supports READ POSITION cmd
                <SPACEFILE_INPROG,,M>,- ; Space filemarks operation in progress
                <READPOSCHK_IN_PROG,,M>,-; READ POSITION support check in progress
		<NO_POSITION_UPDATE,,M>,-; Position should not be updated
		<FIRST_READPOS_CMD,,M>>; Set until first READ POSITION completes
$DEF	UCB$L_SCDRPQ_FL	.BLKL	1	; Queue of free SCDRPs used to 
$DEF	UCB$L_SCDRPQ_BL	.BLKL	1	; send SCSI commands
$DEF	UCB$L_ADDNL_INFO .BLKL	1	; Additional extended sense info
$DEF	UCB$L_PREV_TM .BLKL 1		; Position on tape of last filemark
$DEF	UCB$L_MIN_REV .BLKL	1	; Minimum revision level
$DEF	UCB$L_MSEL_INFO .BLKL	1	; Pointer to vendor-unique mode select info
$DEF	UCB$L_COMP_PAGE .BLKL	1	; Pointer to mode select page 10 (compaction)
$DEF	UCB$L_TR_QIO_STS .BLKL	1	; Address in trace buf to put QIO status
$DEF    UCB$L_REWIND_TIME .BLKL 1       ; Time by which a rewind/immediate command
					; must be completed
$DEF    UCB$L_UNLOAD_TIME .BLKL 1       ; Time by which a unload command
					; must be completed
$DEF    UCB$L_SKIPFILE_STAT .BLKL 1     ; Saved status after skipfile
$DEF	UCB$B_BUSY_RETRY .BLKB	1	; Retry count for BUSY during send
$DEF	UCB$B_SENSE_KEY	 .BLKB	1	; Saved extended sense key
$DEF	UCB$B_SCSI_VERSION .BLKB 1	; SCSI version from INQUIRY
$DEF	UCB$B_LUN	.BLKB	1	; Logical unit number (LUN)
$DEF	UCB$B_COMP_STATE .BLKB	1	; saved data compaction state for
					; SETUP_COMPACTION routine
$DEF	UCB$B_SAVED_COMP .BLKB 1	; Current compaction state before
					; CHECK_COMPACTION_SUPPORT called
$DEF	UCB$W_MK_DENSITY .BLKW	1	; Saved 9-track density
$DEF	UCB$B_8MM_CHK .BLKB 1		; 8MM $ COPY check-in-progress flag
$def	ucb$l_jfill3 .blkb 3		;align!
$DEF	UCB$L_REQSNS_PAGE .BLKL 1	; Requese Sense dma page resource
$DEF	UCB$L_DISABLE_DDR .BLKL 1	; Non-zero disables DDR
					;  (DEFAULT IS DISABLED!)
$def	ucb$l_fpfind0	.blkl	4	; safety
$def	ucb$l_fpfind	.blkl	1	; pattern to make frontpanel easier to see
$def	ucb$l_frontpnl	.blkl	1	; density override
$DEF	UCB$K_MK_UCBLEN			; Length of extended UCB
					
	$DEFEND	UCB			; End of UCB definitions

	.SBTTL	Errorlog packet formats
;+
; Following are the definitions for class driver errorlog packets. Each packet
; has a section common for all error types followed by an error-specific section.
;-
	$DEFINI ERROR_PACKETS

	. = EMB$L_DV_REGSAV		; Start of area to dump error info

$DEF	ERR$LW_CNT	.BLKL	1	; Count of number of LWs that follow
$DEF	ERR$REVISION	.BLKB	1	; Revision level
$DEF	ERR$HW_REV	.BLKL	1	; Hardware revision
$DEF	ERR$TYPE	.BLKB	1	; Error type
$DEF	ERR$SCSI_ID	.BLKB	1	; SCSI ID
$DEF	ERR$SCSI_LUN	.BLKB	1	; SCSI logical unit
$DEF	ERR$SCSI_SUBLUN	.BLKB	1	; SCSI sub-logical unit
$DEF	ERR$PORT_STATUS	.BLKL	1	; Port status code
$DEF	ERR$CMD_LEN	.BLKB	1	; SCSI command length field
$DEF	ERR$SCSI_STS	.BLKB	1	; SCSI status byte
$DEF	ERR$ADDIT_LEN	.BLKB	1	; Additional length field
$DEF	ERR$K_STANDARD_LENGTH		; Standard length of error packet

; Now define packets that have one or more of the variable length fields
; filled in. These fields consist of a byte count followed by n bytes of 
; data. In the standard packet defined above, the byte count field would
; contain a zero for each possible variable length field. The list of variable 
; length fields is:
;
;	o SCSI command data (up to 12 bytes)
;	o Additional data which depends upon the error type


$DEF	ERR$CMD_BYTES	.BLKB	12	; Maximum possible command bytes
$DEF	ERR$K_COMMAND_LENGTH		; Length of packet containing SCSI command

$DEF	ERR$INQUIRY_DATA .BLKB	36	; Inquiry data
$DEF	ERR$K_INQUIRY_LENGTH		; Length of packet containing INQUIRY data

	.=ERR$K_COMMAND_LENGTH
$DEF	ERR$EXTND_SENSE_DATA .BLKB 18	; Extended sense data
$DEF	ERR$K_EXTND_SENSE_LENGTH	; Length of packet containing extended 
					; sense data

	.=ERR$K_COMMAND_LENGTH
$DEF	ERR$MODE_SENSE_DATA .BLKB 150	; Mode sense data
$DEF	ERR$K_MODE_SENSE_LENGTH		; Length of packet containing mode
					; sense data
	.=ERR$K_COMMAND_LENGTH
$DEF	ERR$REASSIGN_BLOCK_DATA .BLKB 8	; Reassign block data
$DEF	ERR$K_REASSIGN_BLOCK_LENGTH	; Length of packet containing reassign 
					; block data

	.=ERR$K_COMMAND_LENGTH
$DEF	ERR$DIAGNOSTIC_DATA .BLKB 18	; Received diagnostic data
$DEF	ERR$K_DIAGNOSTIC_DATA_LENGTH	; Length of packet containing received
					; diagnostic data

	$DEFEND	ERROR_PACKETS

	.SBTTL	+
	.SBTTL	+ MACRO DEFINITIONS
	.SBTTL	+
	.SBTTL	MEDIA		- MSCP media identifier to VMS device type conversion
;+
; MEDIA - modified version of the macro used in DUTUSUBS.MAR (DUDRIVER)
;
; Functional description:
;
;	This macro produces one entry in the MSCP media identifier to VMS 
;	device type conversion table.
;
; Parameters:
;
;	dd	the two character prefered device controller name ( the DD 
;		part of DDCn )
;	devnam	the hardware device name ( e.g. RA81 )
;	dtname	if DT$_'devnam' is not a legal VMS device type, this parameter 
;		gives the correct VMS device type for the device ( should be 
;		used only when DT$_'devnam' is not correct )
;-

	.MACRO	MEDIA DD, DEVNAM, DTNAME

$$BEGIN$$=-1
$$MEDIA$$=0
$$S$$=27
	.IRPC	$$L$$,<DD>
	$$TEMP$$ = ^A/$$L$$/ - ^X40
	.IF	GT $$TEMP$$
	$$MEDIA$$ = $$MEDIA$$ + <$$TEMP$$ @ $$S$$>
	.ENDC
	$$S$$ = $$S$$ - 5
	.ENDR
	.IRPC	$$L$$,<DEVNAM>
	.IF	GE <$$S$$ - 7>
	$$TEMP$$ = ^A/$$L$$/ - ^X40
	.IF	GT $$TEMP$$
	$$MEDIA$$ = $$MEDIA$$ + <$$TEMP$$ @ $$S$$>
	.IF_FALSE
	.IIF	LT $$BEGIN$$, $$BEGIN$$ = <17-$$S$$>/5
	.ENDC
	$$S$$ = $$S$$ - 5
	.ENDC
	.ENDR
	.IIF	LT $$BEGIN$$, $$BEGIN$$ = 3
	$$N$$ = %EXTRACT( $$BEGIN$$, 3, DEVNAM )
	$$MEDIA$$ = $$MEDIA$$ + $$N$$
	.LONG	$$MEDIA$$
	.ENDM	MEDIA

	.SBTTL	SENSE_KEY	- Build sense key to VMS status code translation table
;+
; SENSE_KEY
;
; This macro is used to build a translation table of sense key to VMS status 
; codes. Each time extended sense infromation is returned by the target, the 
; sense key is translated to a VMS status code using this table. Each entry in 
; the table has the following format:
;
;	+-----------------------+
;	|       Sense key	| 1 byte
;	+-----------------------+
;	|    VMS status code	| 4 bytes
;	+-----------------------+
;
; The table is terminated by a sense key of -1.
;-
	.MACRO	SENSE_KEY, SCSI_STATUS, VMS_STATUS

	.BYTE	SCSI$C_'SCSI_STATUS'
	.WORD	SS$_'VMS_STATUS'

	.ENDM	SENSE_KEY

	.SBTTL	LOG_ERROR	- Log a SCSI tape class driver error
;+
; LOG_ERROR
;
; This macro logs a SCSI tape class driver error. The error type and VMS status
; code are placed in R7 and R8 respectively, and the LOG_ERROR routine is 
; called, which does most of the work.
;-

	.MACRO	LOG_ERROR,TYPE,VMS_STATUS,UCB=R3

	PUSHR	#^M<R5,R7,R8>		; Save regs
	.IF DIF UCB,R5 
	MOVL	UCB,R5			; Get UCB address
	.ENDC
	MOVL	#SCSI$C_'TYPE',R7	; Get error code
	MOVL	VMS_STATUS,R8		; And VMS status code
	BSBW	LOG_ERROR		; Write an errorlog entry
	POPR	#^M<R5,R7,R8>		; Restore regs

	.ENDM	LOG_ERROR

	.SBTTL	SCSI_ERROR_CODES - Define SCSI error codes, build error length table
;+
; SCSI_ERROR_CODES
;
; This macro defines the class driver error codes and generates a table of 
; error lengths used during errorlogging to determine the size of the errorlog 
; packet to allocate. The table is indexed by the error type to find the 
; length of the packet which is stored as a word.
;-
	.MACRO	SCSI_ERROR_CODES, ERROR_LIST

	$$CODE_VALUE = 1

	.MACRO	SCSI_ERROR_CODES1 CODE, LEN

	SCSI$C_'CODE' = $$CODE_VALUE
	$$CODE_VALUE = $$CODE_VALUE + 1
	.WORD	ERR$K_'LEN'

	.ENDM	SCSI_ERROR_CODES1

	.IRP	LIST_ENTRY, <ERROR_LIST>
	SCSI_ERROR_CODES1 LIST_ENTRY
	.ENDR

	.ENDM	SCSI_ERROR_CODES


        .SBTTL  DISABLE_ERRLOG  - Temporarily disable errorlogging
        .SBTTL  REENABLE_ERRLOG - Reenable errorlogging
;+
; DISABLE_ERRLOG
; REENABLE_ERRLOG
;
; This macros are used to disable and reenable errorlogging respectively.
; The DISABL_ERRLOG flag in the UCB is used to temporarily disable errorlogging
; when the class driver prepares to do something which is likely to cause an
; error that should be supressed. For example, when checking to see if a 
; device supports COMPACTION, we don;t want a failed MODE_SELECT to generate
; an error log.
; Since the disabling of errorlogging can be nested, the old value of the 
; DISABL_ERRORLOG flag is saved in the local UCB stack.

        .MACRO  DISABLE_ERRLOG

        SUBPUSH UCB$L_MK_FLAGS(R3)      ; Save current flags value
        ASSUME  UCB$V_DISABL_ERRLOG LT 8
        BISB    #UCB$M_DISABL_ERRLOG,-  ; Temporarily disable errorlogging
                UCB$L_MK_FLAGS(R3)      ;

        .ENDM   DISABLE_ERRLOG

        .MACRO  REENABLE_ERRLOG

        SUBPOP  UCB$L_MK_FLAGS(R3)      ; Reenable errorlogging

        .ENDM   REENABLE_ERRLOG



	.SBTTL	WORD_BRANCHES	- Define word displacement branches
;+
; WORD_BRANCHES
;
; This macro defines for each Bxxx (conditional branch) instruction an equivalent 
; macro named BxxxW with a word displacement. The macro takes as an argument
; a list of tuples, each tuple containing 3 items: 1) a conditional branch 
; opcode; 2) the opcode with the opposite polarity; and 3) the number of
; arguments required by the opcode.
;-	
	.MACRO	WORD_BRANCHES LIST

	.MACRO	WORD_BRANCHES2, OPCODE1, OPCODE2, ARGCNT

	.IF EQ ARGCNT-0
	.MACRO	OPCODE1, DST, ?L
	OPCODE2	L
	BRW	DST
L:	.ENDM	OPCODE1
	.ENDC

	.IF EQ ARGCNT-1
	.MACRO	OPCODE1, FIELD, DST, ?L
	OPCODE2	FIELD,L
	BRW	DST
L:	.ENDM	OPCODE1
	.ENDC

	.IF EQ ARGCNT-2
	.MACRO	OPCODE1, BIT, FIELD, DST, ?L
	OPCODE2	BIT,FIELD,L
	BRW	DST
L:	.ENDM	OPCODE1
	.ENDC

	.ENDM	WORD_BRANCHES2

	.MACRO	WORD_BRANCHES1, OPCODE1, OPCODE2, ARGCNT

	WORD_BRANCHES2 'OPCODE1'W, OPCODE2, ARGCNT
	WORD_BRANCHES2 'OPCODE2'W, OPCODE1, ARGCNT

	.ENDM	WORD_BRANCHES1

	.IRP	ENTRY, <LIST>
	WORD_BRANCHES1 ENTRY
	.ENDR

	.ENDM	WORD_BRANCHES

	WORD_BRANCHES <-
		<BBC,	BBS,	2>,-
		<BBCC,	BBSC,	2>,-
		<BBCS,	BBSS,	2>,-
		<BCC,	BCS,	0>,-
		<BEQL,	BNEQ,	0>,-
		<BEQLU,	BNEQU,	0>,-
		<BGEQ,	BLSS,	0>,-
		<BGEQU,	BLSSU,	0>,-
		<BGTR,	BLEQ,	0>,-
		<BGTRU,	BLEQU,	0>,-
		<BLBC,	BLBS,	1>,-
		<BVC,	BVS,	0>>

	.SBTTL	INIT_UCB_STACK	- Initialize the internal UCB stack
	.SBTTL	SUBPUSH		- Push an item on the UCB stack
	.SBTTL	SUBPOP		- Pop an item from the UCB stack
	.SBTTL	SUBGET		- Retrieve an item from the UCB stack (but don't pop)
	.SBTTL	SUBSAVE		- Save a return address on the UCB stack
	.SBTTL	SUBRETURN	- Return to the address saved on the UCB stack
;+
; INIT_UCB_STACK
; SUBPUSH
; SUBPOP
; SUBGET
; SUBSAVE
; SUBRETURN
;
; These macros manipulate the UCB internal stack which is used to save 
; routine return address and temoprary variables.
;-
	.MACRO	INIT_UCB_STACK,UCB=R5,?l1

	.IF DEFINED SCSI_STACK_CHECKING
	.NLIST	MEB
	CMPB	UCB$B_TYPE(UCB),#DYN$C_UCB
	BEQL	L1
	BUG_CHECK INCONSTATE,FATAL
l1:
	.LIST	MEB
	.ENDC

	MOVAL	UCB$L_STACK-4(UCB),-
		UCB$L_STACK_PTR(UCB)

	.ENDM	INIT_UCB_STACK

	.MACRO	SUBPUSH,ARG,UCB=R3,?l1,?l2

	.IF DEFINED SCSI_STACK_CHECKING
	.NLIST	MEB
	PUSHAL	UCB$L_STACK+<UCB_STACK_SIZE*4>(UCB)
	CMPL	(SP)+,UCB$L_STACK_PTR(UCB)
	BGTRU	L2
l1:	BUG_CHECK INCONSTATE,FATAL		; SCSI stack overflow
L2:	.LIST	MEB
	.ENDC

	ADDL	#4,UCB$L_STACK_PTR(UCB)
	MOVL	ARG,@UCB$L_STACK_PTR(UCB)

	.ENDM	SUBPUSH

	.MACRO	SUBPOP,ARG,UCB=R3,?l1,?l2

	.IF DEFINED SCSI_STACK_CHECKING
	.NLIST	MEB
	PUSHAL	UCB$L_STACK-4(UCB)
	CMPL	(SP)+,UCB$L_STACK_PTR(UCB)
	BLSSU	L2
l1:	BUG_CHECK INCONSTATE,FATAL		; SCSI stack underflow
L2:	.LIST	MEB
	.ENDC

	MOVL	@UCB$L_STACK_PTR(UCB),ARG
	SUBL	#4,UCB$L_STACK_PTR(UCB)

	.ENDM	SUBPOP

	.MACRO	SUBSAVE,UCB=R3,?l1,?l2

	SUBPUSH	(SP)+,UCB

	.ENDM	SUBSAVE

	.MACRO	SUBGET,ARG,UCB=R3,?l1,?l2

	.IF DEFINED SCSI_STACK_CHECKING
	.NLIST	MEB
	PUSHAL	UCB$L_STACK-4(UCB)
	CMPL	(SP)+,UCB$L_STACK_PTR(UCB)
	BLSSU	L2
l1:	BUG_CHECK INCONSTATE,FATAL		; SCSI stack underflow
L2:	.LIST	MEB
	.ENDC

	MOVL	@UCB$L_STACK_PTR(UCB),ARG

	.ENDM	SUBGET

	.MACRO	SUBRETURN,UCB=R3,?l1,?l2

	SUBPOP	-(SP),UCB
	RSB

	.ENDM	SUBRETURN

	.SBTTL	MK_WAIT		- Stall a thread for a specific number of seconds
;+
; MK_WAIT
;
; This macro uses the device timeout mechanism to stall a thread for a specified
; number of seconds. The UCB address and stall time are required as inputs.
;-
	.MACRO	MK_WAIT,SECONDS,UCB=R5,SCRATCH=R0,?L

	.IF DIF UCB,R5
	MOVL	R5,SCRATCH
	MOVL	UCB,R5
	MOVL	SCRATCH,UCB
	.ENDC
	DSBINT	ENVIRON=UNIPROCESSOR
	PUSHL	SECONDS
	BSBW	MK_WAIT
	.WORD	L-.
L:	IOFORK
	BICW	#UCB$M_TIMOUT,-
		UCB$W_STS(R5)
	.IF DIF UCB,R5
	MOVL	UCB,SCRATCH
	MOVL	R5,UCB	
	MOVL	SCRATCH,R5
	.ENDC

	.ENDM	MK_WAIT

	.SBTTL	SCSI_CMD	- Define a SCSI command packet
;+
; SCSI_CMD
;
; This macro defines the contents of a SCSI command packet. Each SCSI command
; can have associated with it a DMA buffer used during the DATAIN/DATAOUT bus
; phases. A DMA length of zero indicates there is no DATA(IN/OUT) phase 
; associated with this command (except in the case of a read/write SCSI command
; which is handled specially. The SETUP_CMD routine uses this information in 
; preparing to send a SCSI command. The macro generates a label and the SCSI 
; command information as follows:
;
;	+-----------------------+
;	|    SCSI cmd length	| 1 byte
;	+-----------------------+
;	|    SCSI cmd bytes	| n bytes
;	+-----------------------+
;	|Timeout value (seconds)| 2 bytes
;	+-----------------------+
;	|   DMA buffer length	| 2 bytes
;	+-----------------------+
;	|     DMA direction	| 1 byte
;	+-----------------------+
;
; DMA direction is defined as: 0=write, 1=read.
;-

	.MACRO	SCSI_CMD, NAME, CMD_BYTES, DMA_LEN=0, DMA_DIR=READ, TIMEOUT=30
	
CMD_'NAME':
	$$$BYTE_COUNT=0
	.IRP CMD_BYTE, <CMD_BYTES>
	$$$BYTE_COUNT = $$$BYTE_COUNT + 1
	.IIF EQ $$$BYTE_COUNT-1, SCSI$C_'NAME' = CMD_BYTE	; Define opcode
	.ENDR
	.BYTE	$$$BYTE_COUNT
	.IRP CMD_BYTE, <CMD_BYTES>
	.BYTE	CMD_BYTE
	.ENDR
	.WORD	TIMEOUT
	.WORD	DMA_LEN
	$$$DIRECTION = 0
	.IIF IDN DMA_DIR, READ, $$$DIRECTION = 1
	.BYTE	$$$DIRECTION
	.IIF	IDN NAME,MODE_SELECT_TMP, MODE_SEL_TMP_LEN = .-CMD_'NAME' 

	.ENDM	SCSI_CMD

	.SBTTL	IF_TZ30		- Branch if device a TZ30
	.SBTTL	IF_TK50		- Branch if device is a TZK50
	.SBTTL	IFNOT_TZ30	- Branch if device is not a TZ300
	.SBTTL	IFNOT_TK50	- Branch if device is not a TZK50
	.SBTTL	IF_TK		- Branch if device is a TZK50 or TZ30
	.SBTTL	IFNOT_TK	- Branch if device is not a TZK50 or TZ30
	.SBTTL	IF_TSZ05	- Branch if device is a TSZ05
	.SBTTL	IFNOT_TSZ05	- Branch if device is not a TSZ05
	.SBTTL	IF_TSZ07	- Branch if device is a TSZ07
	.SBTTL	IFNOT_TSZ07	- Branch if device is not a TSZ07
	.SBTTL	IF_TZK10	- Branch if device is a TZK10
	.SBTTL	IFNOT_TZK10	- Branch if device is not a TZK10
	.SBTTL	IF_TZK11	- Branch if device is a TZK11
	.SBTTL	IFNOT_TZK11	- Branch if device is not a TZK11
	.SBTTL	IF_8MM		- Branch if device is an 8mm device
	.SBTTL	IFNOT_8MM	- Branch if device is not an 8mm device
	.SBTTL	IF_TKZ09	- Branch if device is a TKZ09

;-
; IF_TZ30
; IF_TK50
; IFNOT_TZ30
; IF_TK
; IFNOT_TK
; IF_TSZ05
; IFNOT_TSZ05
; IF_TSZ07
; IFNOT_TSZ07
; IF_TZK10
; IFNOT_TZK10
; IF_TKZ09
;
; These macros are used for device-dependent dispatching. 
;-

	.MACRO	IF_TZ30, LABEL, UCB=R3

	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TZ30?
		#DT$_TZ30		;
	BEQL	LABEL			; Branch if so

	.ENDM	IF_TZ30

	.MACRO	IF_TK50, LABEL, UCB=R3

	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TK50?
		#DT$_TK50		;
	BEQL	LABEL			; Branch if so

	.ENDM	IF_TK50

	.MACRO	IFNOT_TK50, LABEL, UCB=R3

	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TK50?
		#DT$_TK50		;
	BNEQ	LABEL			; Branch if not

	.ENDM	IFNOT_TK50

	.MACRO	IFNOT_TZ30, LABEL, UCB=R3

	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TZ30?
		#DT$_TZ30		;
	BNEQ	LABEL			; Branch if not

	.ENDM	IFNOT_TZ30

	.MACRO	IF_TK, LABEL, UCB=R3

	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TZ30?
		#DT$_TZ30		;
	BEQL	LABEL			; Branch if so
	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TK50?
		#DT$_TK50		;
	BEQL	LABEL			; Branch if so

	.ENDM	IF_TK

	.MACRO	IFNOT_TK, LABEL, UCB=R3, ?L

	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TZ30?
		#DT$_TZ30		;
	BEQL	L			; Branch if so
	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TK50?
		#DT$_TK50		;
	BNEQ	LABEL			; Branch if not
L:
	.ENDM	IFNOT_TK

	.MACRO	IF_TSZ05, LABEL, UCB=R3
	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TSZ05?
		#DT$_TSZ05		;
	BEQL	LABEL			; Branch if so
	.ENDM	IF_TSZ05

	.MACRO	IFNOT_TSZ05, LABEL, UCB=R3,
	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TSZ05?
		#DT$_TSZ05		;
	BNEQ	LABEL			; Branch if not
	.ENDM	IFNOT_TSZ05

	.MACRO	IF_TSZ07, LABEL, UCB=R3
	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TSZ07?
		#DT$_TSZ07		;
	BEQL	LABEL			; Branch if so
	.ENDM	IF_TSZ07

	.MACRO	IFNOT_TSZ07, LABEL, UCB=R3
	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TSZ07?
		#DT$_TSZ07		;
	BNEQ	LABEL			; Branch if not
	.ENDM	IFNOT_TSZ07

	.MACRO	IF_TZK10, LABEL, UCB=R3
	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TZK10 ?
		#DT$_TZK10		; for retensioning
	BEQL	LABEL			; Branch if yes
	.ENDM	IF_TZK10

	.MACRO	IFNOT_TZK10, LABEL, UCB=R3
	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TZK10 ?
		#DT$_TZK10		; for retensioning
	BNEQ	LABEL			; Branch if not
	.ENDM	IFNOT_TZK10

	.MACRO	IF_TZK11, LABEL, UCB=R3
	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TZK11 ?
		#DT$_TZK11		; for retensioning
	BEQL	LABEL			; Branch if yes
	.ENDM	IF_TZK11

	.MACRO	IFNOT_TZK11, LABEL, UCB=R3
	CMPB	UCB$B_DEVTYPE(R3),-	; Is device a TZK11 ?
		#DT$_TZK11		; for retensioning
	BNEQ	LABEL			; Branch if not
	.ENDM	IFNOT_TZK11

	.MACRO	IF_TKZ09, LABEL, UCB=R3
	CMPB	UCB$B_DEVTYPE(UCB),-	; Is device a TKZ09 ?
		#DT$_TKZ09		; 
	BEQL	LABEL			; Branch if yes
	.ENDM	IF_TKZ09

	.MACRO	IF_8MM, LABEL, UCB=R3
	BITL	#UCB$M_DEVICE_IS_8MM,-
		UCB$L_MK_FLAGS(R3)	; Is device an 8mm tape
	BNEQ	LABEL                   ; Branch if so
	.ENDM	IF_8MM

	.MACRO	IFNOT_8MM, LABEL, UCB=R3
	BITL	#UCB$M_DEVICE_IS_8MM,-
		UCB$L_MK_FLAGS(R3)	; Is device an 8mm tape
	BEQL	LABEL                   ; Branch if not
	.ENDM	IFNOT_8MM


	.SBTTL	+
	.SBTTL	+ DRIVER TABLES
	.SBTTL	+
	.SBTTL	Driver prologue table
;+
; Driver prologue table
;
; This table provides various information about the driver such as its name
; and length, and causes initialization of various fields in the I/O database 
; when the driver is loaded.
;-

	DPTAB	-				; DPT-creation macro
		END=MK_END,-			; End of driver label
		ADAPTER=NULL,-			; Adapter type
		UCBSIZE=<UCB$K_MK_UCBLEN>,-	; Length of UCB
		NAME=MKDRIVER,-			; Driver name
		FLAGS=<DPT$M_SMPMOD!-		; Driver runs in SMP environment
                       DPT$M_TPALLOC!-		; Driver uses tape allocation class
		       DPT$M_SNAPSHOT!-		; Driver supports snapshots
		       DPT$M_NO_IDB_DISPATCH>	; Don't fill in IDB$L_UCBLST
	DPT_STORE INIT				; Start of load
						; initialization table
	DPT_STORE DDB,DDB$L_ACPD,L,<^A\MTA\>	; Default ACP name
	DPT_STORE UCB,UCB$L_MAXBCNT,L,MAX_BCNT	; Max byte count
	DPT_STORE UCB,UCB$B_FLCK,B,SPL$C_IOLOCK8 ; Device FORK LOCK
	DPT_STORE UCB,UCB$B_DIPL,B,22		; Device interrupt IPL
	DPT_STORE UCB,UCB$L_DEVCHAR,L,<-	; Device characteristics
		DEV$M_FOD!-			; Files oriented
		DEV$M_DIR!-			; Directory structured
		DEV$M_AVL!-			; Available
		DEV$M_ELG!-			; Error logging enabled
		DEV$M_IDV!-			; Input device
		DEV$M_ODV!-			; Output device
 		DEV$M_SDI!-			; Single directory device
		DEV$M_SQD>			; Random Access Device
	DPT_STORE UCB,UCB$L_DEVCHAR2,L,<-	; Device characteristics
		DEV$M_SCSI!-			; device is a SCSI device
		DEV$M_NNM>			; Prefix name with "node$"
	DPT_STORE UCB,UCB$B_DEVCLASS,B,DC$_TAPE	; Sample device class
 	DPT_STORE UCB,UCB$B_DEVTYPE,B,-		; Device type (default)
		DT$_GENERIC_MK
	DPT_STORE UCB,UCB$W_DEVBUFSIZ,W,2048	; Default buffer size
 	DPT_STORE UCB,UCB$L_DEVDEPEND,W,-	; Default tape parameters
		DEF_TAPE_PARAMS			; Format=DEFAULT, Density=BLK_833
	DPT_STORE UCB,UCB$B_ERTCNT,B,16		; Error retry count
	DPT_STORE UCB,UCB$B_ERTMAX,B,16		; Max error retry count
	DPT_STORE UCB,UCB$L_MK_FLAGS,L,0	; Initialize flags field
	DPT_STORE UCB,UCB$L_ERR_MASK,L,0	; Initialize error mask field
        DPT_STORE UCB,UCB$W_MK_DENSITY,W,-      ; Initialize 9-track H/W
                MT$K_GCR_6250-2                 ; density.
        DPT_STORE UCB,UCB$L_REQSNS_PAGE,L,0     ; Initialize pointer
        DPT_STORE UCB,UCB$L_DISABLE_DDR,L,1     ; Non zero disables DDR
	DPT_STORE REINIT			; Start of reload
						; initialization table
	DPT_STORE DDB,DDB$L_DDT,D,MK$DDT	; Address of DDT
	DPT_STORE CRB,-				; Address of controller
		CRB$L_INTD+VEC$L_INITIAL,-	; initialization routine
		D,MK_CTRL_INIT
	DPT_STORE CRB,-				; Address of device
		CRB$L_INTD+VEC$L_UNITINIT,-	; unit initialization
		D,MK_UNIT_INIT			; routine
	DPT_STORE CRB,CRB$B_FLCK,B,IPL$_IOLOCK8	; Initialize fork lock field

	DPT_STORE END				; End of initialization
						; tables

	.SBTTL	Driver dispatch table
;+
; Driver dispatch table
;
; This table defines the entry points into the driver.
;-

	DDTAB	-				; DDT-creation macro
		DEVNAM=MK,-			; Name of device
		START=MK_STARTIO,-		; Start I/O routine
		FUNCTB=MK_FUNCTABLE,-		; FDT address
		CANCEL=MK_CANCEL,-		; Cancel I/O routine
		REGDMP=MK_REG_DUMP		; Register dump routine

	.SBTTL	Function decision table
;+
; Function decision table
;
; This table lists the QIO function codes implemented by the driver and the
; preprocssing routines used by each function.
;-

MK_FUNCTABLE:					; FDT for driver
 	FUNCTAB,<-				; Valid I/O functions
 		NOP,-				; No operation
 		UNLOAD,-			; Unload volume
 		SPACERECORD,-			; Space records
 		RECAL,-			; Recalibrate (rewind)
 		DRVCLR,-			; Driver clear
 		READPRESET,-			; Read in preset
 		PACKACK,-			; Pack acknowledge
 		ERASETAPE,-			; Erase tape
 		DSE,-				; Data security erase
 		SENSECHAR,-			; Sense tape characteristics
 		SETCHAR,-			; Set characteristics
		DIAGNOSE,-			; Special pass-through function
 		SPACEFILE,-			; Space file
 		WRITECHECK,-			; Write check forward
 		WRITEPBLK,-			; Write physical block
 		WRITERET,-			; Write physical block retry
 		READPBLK,-			; Read physical block
 		REREADN,-			; Reread next
 		REREADP,-			; Reread previous
 		AVAILABLE,-			; Available (rewind/nowait clear valid)
 		WRITEMARK,-			; Write tape mark
 		READLBLK,-			; Read logical block
 		WRITELBLK,-			; Write logical block
 		SENSEMODE,-			; Sense tape mode
 		SETMODE,-			; Set mode
 		REWIND,-			; Rewind
 		REWINDOFF,-			; Rewind and set offline
 		SKIPRECORD,-			; Skip records
 		SKIPFILE,-			; Skip files
 		WRITEOF,-			; Write end of file
 		READVBLK,-			; Read virtual block
 		WRITEVBLK,-			; Write virtual block
 		ACCESS,-			; Access file and/or find directory
 		ACPCONTROL,-			; ACP control function
 		CREATE,-			; Create file and/or create directory
 		DEACCESS,-			; Deaccess file
 		DELETE,-			; Delete file and/or directory entry
 		MODIFY,-			; Modify file attributes
 		MOUNT>				; Mount volume
 
 	FUNCTAB,-				; Buffered I/O functions
 		<NOP,-				; No operation
 		 UNLOAD,-			; Unload volume
 		 SPACERECORD,-			; Space records
 		 RECAL,-			; Recalibrate (rewind)
 		 DRVCLR,-			; Drive clear
 		 READPRESET,-			; Read preset
 		 PACKACK,-			; Pack acknowledge
 		 ERASETAPE,-			; Erase tape
 		 DSE,-				; Data security erase
 		 SENSECHAR,-			; Sense characteristics
 		 SETCHAR,-			; Set characteristics
 		 SPACEFILE,-			; Space files
 		 WRITEMARK,-			; Write tape mark
 		 SENSEMODE,-			; Sense mode
 		 SETMODE,-			; Set mode
 		 REWIND,-			; Rewind
 		 REWINDOFF,-			; Rewind and unload
 		 SKIPRECORD,-			; Skip records
 		 SKIPFILE,-			; Skip files
 		 WRITEOF,-			; Write end of file
 		 ACCESS,-			; Access file &/or find directory entry
 		 ACPCONTROL,-			; ACP control function
 		 CREATE,-			; Create file &/or create dir entry
 		 DEACCESS,-			; Deaccess file
 		 DELETE,-			; Delete file and/or directory entry
 		 MODIFY,-			; Modify file attributes
 		 MOUNT>				; Mount volume
 
 	FUNCTAB	+ACP$READBLK,-			; Read functions
 		<READLBLK,-			; Read logical block forward
 		 READPBLK,-			; Read physical block forward
 		 REREADN,-			; Read next
 		 REREADP,-			; Read previous
 		 READVBLK>			; Read virtual block
 
 	FUNCTAB	+ACP$WRITEBLK,-			; Write functions
 		<WRITECHECK,-			; Write check forward
 		 WRITELBLK,-			; Write logical block
 		 WRITEPBLK,-			; Write physical block
 		 WRITERET,-			; Write retry
 		 WRITEVBLK>			; Write virtual block
 
 	FUNCTAB	+ACP$ACCESS,<ACCESS,CREATE> 	; Access & create file or directory
 
 	FUNCTAB	+ACP$DEACCESS,<DEACCESS> 	; Deaccess file
 
 	FUNCTAB	+ACP$MODIFY,-			;
 		<ACPCONTROL,-			; ACP control function
 		 DELETE,-			; Delete file or directory entry
 		 MODIFY>			; Modify file attributes
 
 	FUNCTAB	+ACP$MOUNT,<MOUNT>		; Mount volume
 
 	FUNCTAB	+MT$CHECK_ACCESS,-		; Magtape check access funcitons
 		<ERASETAPE,-			; Erase tape
 		 DSE,-				; Data security erase
 		 WRITEMARK,-			; Write tape mark
 		 WRITEOF>			; Write end of file
 
 	FUNCTAB	+EXE$ZEROPARM,-			; Zero parameter functions
 		<NOP,-				; No operation
 		 UNLOAD,-			; Unload volume
 		 RECAL,-			; Recalibrate (rewind)
 		 REWIND,-			; Rewind
 		 REWINDOFF,-			; Rewind and set offline
 		 DRVCLR,-			; Drive clear
 		 READPRESET,-			; Read in preset
 		 PACKACK,-			; Pack acknowledge
 		 ERASETAPE,-			; Erase tape
 		 DSE,-				; Data security erase
 		 AVAILABLE,-			; Available (rewind/nowait clear valid)
 		 WRITEMARK,-			; Write tape mark
 		 WRITEOF>			; Write end of file
 
 	FUNCTAB	+EXE$ONEPARM,-			; One parameter functions
 		<SPACERECORD,-			; Space records
 		 SPACEFILE,-			; Space files
 		 SKIPRECORD,-			; Skip records
 		 SKIPFILE>			; Skip files
 
 	FUNCTAB	MK_SENSEMODE,-			; Sense tape characteristics
		<SENSECHAR,-			;  Sense characteristics
		 SENSEMODE>			;  Sense mode

 	FUNCTAB	+EXE$SETMODE,-			; Set tape characteristics
 		<SETCHAR,-			;
 		 SETMODE>			;

	FUNCTAB	MK_DIAGNOSE,<-			; Special pass-through function
		DIAGNOSE>			; 

	.SBTTL	SCSI_DEV_TYPES	- Build SCSI device table
;+
; SCSI_DEV_TYPES
;
; This macro builds a table of pre-defined SCSI device types. During unit 
; initialization, an inquiry command is sent to the target which returns
; 8 bytes of ID string. The table is then scanned for a matching ID. If one
; is found, information for that entry is copied into the UCB, including the 
; device type, media ID, disconnect/synchronous flags, and various timeout 
; values. If no matching entry is found, the device is assumed to be a 
; "generic" SCSI disk, and the entry for generic devices is used. Each entry 
; in the device type table has the following format:

;	+-----------------------+
;	|    VMS device type	| 1 byte
;	+-----------------------+
;	|	ID string	| 8 bytes
;	+-----------------------+
;	|  Min revision level	| 4 bytes
;	+-----------------------+
;	|	Media ID	| 4 bytes
;	+-----------------------+
;	|  Vendor-unique info	| 10 bytes
;	+-----------------------+
;	|     Density code	| 1 byte
;	+-----------------------+
;
; The table is terminated with a VMS device code of 0.
;-

	.MACRO	SCSI_DEV_TYPES, LIST

	.MACRO	SCSI_DEV_TYPES1, ID_STRING, DEVICE_TYPE, MINIMUM_REVISION, -
		DENSITY, MODE_SEL_INFO, LOADER=0 ; loader added mcy/rcl 9/18/92

	.IF IDN <ID_STRING>, TZ30
TZ30_DEV_TYPE:
	.ENDC

	.IF IDN <ID_STRING>, TK50
TK50_DEV_TYPE:
	.ENDC

	.IF IDN <ID_STRING>, GENERIC
GENERIC_DEV_TYPE:
	.ENDC

	.IIF NDF SDTE_BEGIN, SDTE_BEGIN = .	; Adjust table length for loader
						; mcy/rcl - 9/18/92
; Device type

	.BYTE	DT$_'DEVICE_TYPE'

; 8 character product ID string, padded with spaces

	.NCHR	$$$STRLEN,<ID_STRING>
	.IIF GT $$$STRLEN-8, .ERROR ;Illegal SCSI product ID: ID_STRING
	$$$PADCNT = 8-$$$STRLEN
	.ASCII	/ID_STRING/
	.REPT	$$$PADCNT
	.ASCII	/ /
	.ENDR

; Minimum revision level. This field is compared to the revision field returned
; in the inquiry data. If the device is out of rev, an error is logged.

	.ASCII	/MINIMUM_REVISION/

; Media ID field

	.IF IDN DEVICE_TYPE,GENERIC_MK
	MEDIA   <MK>, <MKX00>, DT$_GENERIC_MK   ; Media value for generic MK device
	.IFF
	MEDIA <MK>, <DEVICE_TYPE>
	.ENDC

; Vendor-unique mode select data. This is a counted string of bytes with the
; first byte specifying the number of bytes that follow. The total field length,
; including the count, is 10 bytes.

	$$$COUNT = 0
	.IRP	MODE_SEL_BYTE,<MODE_SEL_INFO>
	$$$COUNT = $$$COUNT + 1
	.ENDR

	.IIF	GT $$$COUNT-9,	.ERROR ; - Illegal number of mode sense bytes

	.BYTE	$$$COUNT
	.IRP	MODE_SEL_BYTE,<MODE_SEL_INFO>
	.BYTE	MODE_SEL_BYTE
	.ENDR

	.REPT	9-$$$COUNT
	.BYTE	0
	.ENDR
	
; 
	.BYTE	MT$K_'DENSITY'

        .BYTE   LOADER                  ; RCL/MCY - loader support 9/18/92

	.IIF NDF SDTE_END, SDTE_END = .	; Adjust table length for loader - 
					; mcy/rcl - 9/18/92
	.ENDM	SCSI_DEV_TYPES1

	.IRP	ENTRY,<LIST>
	SCSI_DEV_TYPES1 ENTRY
	.ENDR

	.ENDM	SCSI_DEV_TYPES

        .NLIST  ; RCL

	.SBTTL	SCSI device types table
;+
; SCSI_DEVICE_TABLE
;
; This table is used to translate the product ID field from the inquiry data
; to a VMS device type and internal MK device type. The internal type code is
; used so that we can support multiple SCSI tape drives with a single VMS device
; code (generic_mk), yet distinguish between these drives, which may require 
; different vendor-unique mode select data. The mode select data in this table
; is appended to the standard 4 byte parameter list header and 8 byte block
; descriptor. Several reserved entries are left in the table to allow for
; patching the driver to add support for new devices in the future. Note that
; because the TZ30 and TK50 don't conform to the standard inquiry data format,
; the GET_DEVICE_TYPE routine uses special case code to find their entries
; in the table.
;-

SCSI_DEVICE_TABLE:

	.list	meb
SCSI_DEV_TYPES <-
;
;       ID string     VMS dev type     Min Rev    Density   Mode select info   
;       ---------     ------------     -------    -------   ----------------   
;
	<<TZ30>,       TZ30,           <0701>,    BLK_833,  <1, <^X4F>>>,-     
	<<TK50>,       TK50,           <2D05>,    BLK_833,  <1, <^X47>>>,-
	<<TLZ04 19>,   TLZ04,	       <    >,	  DEFAULT,  <>>,-
	<<TLZ04   >,   TLZ04,	       <    >,	  DEFAULT,  <>>,-
	<<HSCAN 19>,   TLZ04,	       <    >,    DEFAULT,  <>>,-
	<<TZK10   >,   TZK10,	       <    >,	  DEFAULT,  <>>,-
	<<0-STD1-0>,   TSZ05,          <    >,    PE_1600,  <>>,-
	<<TSZ05>,      TSZ05,          <    >,    PE_1600,  <>>,-
	<<TSZ07>,      TSZ07,          <    >,    GCR_6250, <>>,-
	<<TZK10>,      TZK10,          <    >,    DEFAULT,  <>>>

SCSI_DEV_TYPES <-
;
;       ID string     VMS dev type     Min Rev    Density   Mode select info   
;       ---------     ------------     -------    -------   ----------------   
;
	<<TZ85>,       TZ85,           <    >,    DEFAULT,  <>>,-
	<<TZ86>,       TZ86,           <    >,    DEFAULT,  <>>,-
	<<TZ87>,       TZ87,           <    >,    DEFAULT,  <>>,-
        <<TLZ06>,      TLZ06,          <    >,    DEFAULT,  <>>,-
        <<TKZ60>,      TKZ60,          <    >,    DEFAULT,  <>>,-
        <<TZK11>,      TZK11,          <    >,    DEFAULT,  <>>,-
        <<TKZ09>,      TKZ09,          <    >,    DEFAULT,  <>>,-
        <<TZ88>,       TZ88,           <    >,    DEFAULT,  <>>,-
        <<TZ89>,       TZ89,           <    >,    DEFAULT,  <>>,-
        <<TKZ09>,      TKZ09,          <    >,    DEFAULT,  <>>,-
	<<GENERIC>,    GENERIC_MK,     <    >,    DEFAULT,  <>>,-
	<<RESERVE1>,   GENERIC_MK,     <    >,    DEFAULT,  <>>,-
	<<RESERVE2>,   GENERIC_MK,     <    >,    DEFAULT,  <>>,-
	<<RESERVE3>,   GENERIC_MK,     <    >,    DEFAULT,  <>>,-
	<<RESERVE4>,   GENERIC_MK,     <    >,    DEFAULT,  <>>>

SCSI_DEV_TYPES <-
;
;       ID string     VMS dev type     Min Rev    Density   Mode select info   
;       ---------     ------------     -------    -------   ----------------   
;
        <<TLZ6>,       TLZ06,           <    >,    DEFAULT,  <>, LOADER >,-
        <<TKZ60L>,     TKZ60,           <    >,    DEFAULT,  <>, LOADER >,-
        <<TZ857>,      TZ85,            <    >,    DEFAULT,  <>, LOADER >,-
        <<TZ867>,      TZ86,            <    >,    DEFAULT,  <>, LOADER >,-
        <<TZ877>,      TZ87,            <    >,    DEFAULT,  <>, LOADER >,-
        <<TZ875>,      TZ87,            <    >,    DEFAULT,  <>, LOADER >,-
        <<TZ885>,      TZ88,            <    >,    DEFAULT,  <>, LOADER >,-
        <<TZ887>,      TZ88,            <    >,    DEFAULT,  <>, LOADER >,-
        <<TZ895>,      TZ89,            <    >,    DEFAULT,  <>, LOADER >,-
        <<TZ897>,      TZ89,            <    >,    DEFAULT,  <>, LOADER >,-
        <<TLZ7>,       TLZ07,           <    >,    DEFAULT,  <>, LOADER >>

SCSI_DEV_TYPES <-
;
;       ID string     VMS dev type     Min Rev    Density   Mode select info   
;       ---------     ------------     -------    -------   ----------------   
        <<TZ865>,      TZ86,            <    >,    DEFAULT,  <>, LOADER >,-
	<<EXB-8200>,  GENERIC_MK,      	<    >,    DEFAULT,  <<^X22>>>,-
	<<EXB-8500>,  GENERIC_MK,      	<    >,    DEFAULT,  <<^X22>>>,-
        <<TL810>,      TL810,           <    >,    DEFAULT,  <>>,-
        <<TL820>,      TL820,           <    >,    DEFAULT,  <>>>

	.list

	.BYTE	0	; End of table
	.nlist	meb

	.SBTTL	SCSI command definition tables

	SCSI_CMD -
		NAME = TEST_UNIT_READY,-
		CMD_BYTES = <0, 0, 0, 0, 0, 0>

	SCSI_CMD -
		NAME = REQUEST_SENSE,-
		CMD_BYTES = <3, 0, 0, 0, <REQSNS_SIZE>, 0>,-
		DMA_LEN = -1,-
		DMA_DIR = READ

	SCSI_CMD -
		NAME = READ,-
		CMD_BYTES = <8, 0, 0, 0, 0, 0>,-
		DMA_LEN = -1,-
		TIMEOUT=420

	SCSI_CMD -
		NAME = WRITE,-
		CMD_BYTES = <10, 0, 0, 0, 0, 0>,-
		DMA_LEN = -1,-
		TIMEOUT=420

        SCSI_CMD -
                NAME = READ_8MM,-                      	;Special 8MM READ
                CMD_BYTES = <8, 2, 0, 0, 0, 0>,-
                DMA_LEN = 80,-
                TIMEOUT=420

        SCSI_CMD -                                      ;Special 8MM WRITE
                NAME = WRITE_8MM,-
                CMD_BYTES = <10, 0, 0, 0, 0, 0>,-
                TIMEOUT=420

	SCSI_CMD -
		NAME = INQUIRY,-
		CMD_BYTES = <18 , 0, 0, 0, 36, 0>,-
		DMA_LEN = 36,-
		DMA_DIR = READ 

	SCSI_CMD -
		NAME = MODE_SELECT,-
		CMD_BYTES = <21, 0, 0, 0, 12, 0>,-
		DMA_LEN = 12,-
		DMA_DIR = WRITE,-
		TIMEOUT = 420

	SCSI_CMD -
		NAME = MODE_SELECT_TMP,-
		CMD_BYTES = <21, 0, 0, 0, 12, 0>,-
		DMA_LEN = 12,-
		DMA_DIR = WRITE,-
		TIMEOUT = 420

	SCSI_CMD -
		NAME = MODE_SENSE,-
		CMD_BYTES = <26, 0, 0, 0, 12, 0>,-
		DMA_LEN = 100,-
		DMA_DIR = READ,-
		TIMEOUT = 420

	SCSI_CMD -
		NAME = MODE_SENSE_COMP,- 		; get page ^X10
		CMD_BYTES = <26, 0, <^X10>, 0, 12+16, 0>,- ; for compaction
		DMA_LEN = 12+16,-
		DMA_DIR = READ,-
		TIMEOUT = 420

	SCSI_CMD -
		NAME = RECEIVE_DIAG,-
		CMD_BYTES = <28, 0, 0, 6, 0, 0>,-
		DMA_LEN = 6,-
		DMA_DIR = READ,-
		TIMEOUT = 420

	SCSI_CMD -
		NAME = SEND_DIAG,-
		CMD_BYTES = <29, 0, 0, 0, 0, 0>,-
		DMA_LEN = 512,-
		DMA_DIR = WRITE

	SCSI_CMD -
		NAME = ERASE,-
		CMD_BYTES = <25, 1, 0, 0, 0, 0>,-
		TIMEOUT = 5*60*60

	SCSI_CMD -
		NAME = REWIND,-
		CMD_BYTES = <1, 0, 0, 0, 0, 0>,-
		TIMEOUT=420

	SCSI_CMD -
		NAME = UNLOAD,-
		CMD_BYTES = <27, 1, 0, 0, 0, 0>,-
		TIMEOUT=420

	SCSI_CMD -					;Create the RETENSION
		NAME = RETENSION,-			;packet using the SCSI
		CMD_BYTES = <27, 1, 0, 0, 3, 0>,-	;LOAD cmd.  Byte 5 set
		TIMEOUT=420				;with 3 to cause "RETEN,
							;load, move to BOT". Uzd
							;for RETENSIONING TZK10
							;only.  MCY - 11/27/90
	SCSI_CMD -
		NAME = SPACE,-
		CMD_BYTES = <17, 0, 0, 0, 0, 0>,-
		TIMEOUT=5*60*60

	SCSI_CMD -
		NAME = WRITE_FILEMARKS,-
		CMD_BYTES = <16, 0, 0, 0, 0, 0>,-
		TIMEOUT=420

        SCSI_CMD -
                NAME = READ_POSITION,-
                CMD_BYTES = <<^X34>, 0, 0, 0, 0, 0, 0, 0, 0, 0>,-
                DMA_LEN = 20,-
                DMA_DIR = READ,-
                TIMEOUT=420


	.SBTTL	Sense key to VMS status translation table
;+
; SENSE_KEY_TABLE
;
; This table is used to translate SCSI extended sense keys to VMS status codes.
; Each entry contains a byte of sense key followed by a word of VMS status. 
; Since the sense key space is sparse, the table is sequentially scanned, rather
; than indexed by sense key value. The table is terminated with a byte if -1.
; -

SENSE_KEY_TABLE:

;			SCSI sense key		VMS status
;			--------------		----------

	SENSE_KEY	NO_SENSE,		NORMAL
	SENSE_KEY	RECOVERED_ERROR,	NORMAL
	SENSE_KEY	NOT_READY,		DEVOFFLINE
	SENSE_KEY	MEDIUM_ERROR,		PARITY
	SENSE_KEY	HARDWARE_ERROR,		DRVERR
	SENSE_KEY	ILLEGAL_REQUEST,	DRVERR
	SENSE_KEY	UNIT_ATTENTION,		MEDOFL
	SENSE_KEY	DATA_PROTECT,		WRITLCK
	SENSE_KEY	BLANK_CHECK,		OPINCOMPL
	SENSE_KEY	ABORTED_COMMAND,	DRVERR
	SENSE_KEY	EQUAL,			NORMAL
	SENSE_KEY	VOLUME_OVERFLOW,	DRVERR
	SENSE_KEY	MISCOMPARE,		DATACHECK

	.BYTE	^XFF			; End of sense key table

	.SBTTL	SCSI error length table
;+
; SCSI_ERROR_LEN_TAB
;
; This table is indexed by the SCSI error type and specifies the length of
; the errorlog packet.
;-

SCSI_ERROR_LEN_TAB:

	SCSI_ERROR_CODES <-
		<CONNECTION_ERROR,	STANDARD_LENGTH>,-
		<MAP_BUFFER_ERROR,	STANDARD_LENGTH>,-
		<SEND_CMD_ERROR,	COMMAND_LENGTH>,-
		<INV_INQUIRY_DATA,	INQUIRY_LENGTH>,-
		<EXTND_SENSE_DATA,	EXTND_SENSE_LENGTH>,-
		<MODE_SENSE_DATA,	MODE_SENSE_LENGTH>,-
		<REASSIGN_BLOCK,	REASSIGN_BLOCK_LENGTH>,-
		<DIAGNOSTIC_DATA,	DIAGNOSTIC_DATA_LENGTH>>

	.SBTTL	+
	.SBTTL	+ DRIVER ENTRY POINTS
	.SBTTL	+
	.SBTTL	MK_CTRL_INIT	- Controller initialization routine
;+
; MK_CTRL_INIT
;
; This routine is called to perform controller-specific initialization and
; is called by the operating system in three places:
;
;	- at system startup
;	- during driver loading and reloading
;	- during recovery from a power failure
;
; Currently this routine simply creates a fork thread to setup a trace buffer.
;
; INPUTS:
;
;	R4	- address of the CSR (controller status register)
;	R5	- address of the IDB (interrupt data block)
;	R6	- address of the DDB (device data block)
;	R8	- address of the CRB (channel request block)
;
; OUTPUTS:
;
;	All registers preserved
;-

MK_CTRL_INIT:

	.IF DEFINED DEBUG
	PUSHL	R5			; Save R5
	PUSHAL	10$			; Set up dummy return address
	MOVL	R8,R5			; Copy CRB address
	FORK				; Fork using the CRB fork block
	BSBW	SETUP_TRACE		; Set up trace buffer
	RSB
	
10$:	POPL	R5			; Restore R5
	.ENDC
	RSB				; Return to caller (SYSGEN)

	.SBTTL	MK_UNIT_INIT	- Unit initialization routine
;+
; MK_UNIT_INIT
;
; This routine performs unit-specific initialization and is called for each
; tape found on the SCSI bus. A connection to the port driver is established, 
; which lasts for the life of the system. All traffic to this SCSI device is 
; directed over this connection.
;
; An inquiry command is sent to the target to determine the device type.
; In additon, several sanity checks are made on the inquiry data to determine 
; if the device is valid. If so, the unit is placed online and any I/O queued
; to the device during initialization is started.
;
; INPUTS:
;
;	R5	- UCB address
;
; OUTPUTS:
;
;	R0-R3	- Destroyed
;	All other registers preserved
;-

MK_UNIT_INIT:				; Initialize unit

	BRB	1$			; Skip call to INI$BRK
	JSB	G^INI$BRK		; *** Debug ***

1$:	BBC     #UCB$V_POWER,-          ; Branch if powerfail has not been
		UCB$W_STS(R5),2$	; NOT been detected
	RSB                             ; Otherwise, exit immediately

; Fork twice for now to allow the port driver's unit init routine to execute
; before ours.

2$:
	clrw	ucb$l_frontpnl(r5)	; initially set default density ops
	movl	#^xB00BFACE,ucb$l_fpfind(r5) ;add easy to spot pattern
	FORK				; Fork to drop IPL to SYNCH
	FORK				; Fork to drop IPL to SYNCH
	INIT_UCB_STACK			; Initialize the internal stack in the UCB
	ASSUME	UCB$V_DISCONNECT LT 8
	MOVB	#UCB$M_DISCONNECT!-	; By default, assume the target device
		 UCB$M_SYNCHRONOUS,-	; is capable of both disconnecting and
		UCB$L_MK_FLAGS(R5)	; synchronous operation
	BISW	#UCB$M_ONLINE!-		; Set unit online and busy to allow I/O
		 UCB$M_BSY,-		; to be queued
		UCB$W_STS(R5)		;
	MOVAL	UCB$L_FLUSH_IOQFL(R5),R0; Initialize the queue used to flush
	MOVL	R0,(R0)			; I/Os which are queued during unit
	MOVL	R0,4(R0)		; init when unit init fails
	CLRL	UCB$L_REWIND_TIME(R5)	; Indicate no rewind command has been
					; recently
	CLRL	UCB$L_UNLOAD_TIME(R5)	; Indicate no unload has been issued
					; recently

	.IF DEFINED DEBUG
	CLRL	R3			; Prepare to call TRACE_QIO
	BSBW	TRACE_QIO		; Trace this QIO (special case when
					; called from UNIT INIT, R3 must be 0)
	.ENDC

	MOVAL	UCB$L_SCDRPQ_FL(R5),R0	; Initialize the SCDRP queue header
	MOVL	R0,(R0)			; in the UCB
	MOVL	R0,4(R0)		;
	MOVL	#SCDRPS_PER_UNIT,R4	; Number of SCDRPs to allocate per unit
10$:	MOVL	#SCDRP$C_LENGTH,R1	; Length of SCDRP
	MOVL	R5,R3			; Copy UCB address
	BSBW	ALLOC_POOL		; Go allocate an SCDRP
	MOVW	R1,SCDRP$W_SCDRPSIZE(R2); Save length of SCDRP
	INSQUE	SCDRP$L_FQFL(R2),-	; Place SCDRP in UCB queue
		UCB$L_SCDRPQ_FL(R5)	;
	SOBGTR	R4,10$			; Repeat for all SCDRPs

; All SCSI tape unit numbers should be of the form "n0m" where n is the SCSI
; ID between 0 and 7 and m is the LUN between 0 and 7. Extract the ID from the
; LUN by dividing the unit number by 100. The quotient is the used as the ID
; while the remainder is the LUN. Note that the unit number contains three
; digits because early version of SCSI provided for sub-logical unit numbers.
; This feature has since been removed and the second digit in the unit number
; is not used.

	MOVL	#SS$_BADPARAM,R0	; Assume bad LUN or SUBLUN specified
	MOVZWL	UCB$W_UNIT(R5),R1	; Get device unit number
	CLRL	R2			; Prepare for extended divide
	EDIV	#100,R1,R1,R2		; Extract SCSI bus ID from LUN
	CMPL	R1,#7			; Valid SCSI ID (0 <= n <= 7)?
	BGTRUW	20$			; Branch if not 
	CMPL	R2,#7			; Valid LUN (0 <= n <= 7)?
	BGTRUW	20$			; Branch if not 
	MULB3	#<1@5>,R2,UCB$B_LUN(R5)	; Save LUN (shifted left 5 bits for use
					; later in SETUP_CMD)
	ASHL	#16,R1,R1		; Place SCSI ID in high-order word of R1
	ASHL	#16,R2,R2		; Place LUN in high-order word of R2
	MOVL	UCB$L_DDB(R5),R0	; Get DDB address
	SUBB3	#^A'A',-		; Translate controller letter to
		DDB$T_NAME+3(R0),R1	; SCSI bus ID.
	SPI$CONNECT			; Connect to the port driver
	BLBC	R0,20$			; Branch if connect attempt failed
	CMPL	R1,UCB$L_MAXBCNT(R5)	; For MAXBCNT, use minimum supported
	BGEQ	15$			; value of port and class drivers
	MOVL	R1,UCB$L_MAXBCNT(R5)	; Save maximum byte count in UCB
15$:	MOVL	R2,UCB$L_SCDT(R5)	; Save SCDT address
	MOVL	R4,UCB$L_PDT(R5)	; Save PDT address
        BSBW    ALLOC_REQSNS_RESOURCE   ; Get REQUEST SENSE Resource
	BLBC	R0,20$			; Branch if error
	BSBW	SET_UNIT_ONLINE		; Go bring the unit online
	RSB				; Return to caller

; Connection failure. Log an error and set the unit offline.	
	
20$:	LOG_ERROR -			; Log a connection error
		TYPE=CONNECTION_ERROR,-	;
		VMS_STATUS=R0,-		;
		UCB=R5			;
	BICW	#UCB$M_ONLINE!-		; Set the unit offline and not busy
		 UCB$M_BSY,-		;
		UCB$W_STS(R5)		;
	RSB


	.SBTTL	ALLOC_REQSNS_RESOURCE
;+
; This routine will allocate an SCDRP and call SPI$MAP_BUFFER to get
; a DMA buffer for the REQUEST SENSE command. This is done up front
; to prevent deadlocks when using tape devices and 1 or more data transfer
; requests have all the DMA resources allocated and a check_condition
; occurs on all connections. When this happens, the request_sense routine 
; hangs waiting for DMA resources which will not be freed until the 
; REQUEST_SENSE completes and allows the original command's to free their 
; buffer resources! This situation occurs frequently when doing a DUMP
; of more than 1 tape simultaneously and the record lengths used by DUMP
; do not match the record length on tape, which generates an ILI check
; condition.
;
; REQUEST_SENSE will use this pre-allocated resource instead of trying
; to get a resource on the fly. We allocate one per UCB (unit) with
; the assumption that only one command can be outstanding on a connection
; at once, and so only one REQUEST_SENSE command can happen at a time
; per UCB. This will break with command queuing.
;-
ALLOC_REQSNS_RESOURCE:
	TSTL	UCB$L_REQSNS_PAGE(R5)	; We already have a page?
	BNEQ	10$			; Branch if yes

	PUSHR	#^M<R2,R3,R5>		; Save registers
	MOVL	R5,R3			; Copy UCB address
	BSBW	ALLOC_SCDRP		; Get a fake SCDRP to interface
					; with SPI_MAP_BUFFER
	MOVL	UCB$L_SCDRP(R3),-	; Save our SCDRP address in special
		UCB$L_REQSNS_PAGE(R3)	; UCB location
	CLRL	UCB$L_SCDRP(R3)		; Not in use here!

        MOVL	UCB$L_REQSNS_PAGE(R3),R5; Pick up our SCDRP's address

	MOVL	#REQSNS_SIZE,R1	; Size of DMA buffer needed for data
	BSBW	ALLOC_POOL		; Allocate a buffer to receive response
	MOVL	R2,SCDRP$L_SVA_USER(R5)	; Save address of allocated buffer
	MOVL	#REQSNS_SIZE,SCDRP$L_BCNT(R5)	; Set DMA request size
	CLRL	SCDRP$L_PAD_BCNT(R5)	; No padding required
	BICW3	#^C<^X1FF>,R2,-		; And byte offset within page
		SCDRP$W_BOFF(R5)	;
	BISL	#IRP$M_FUNC,-		; Set FUNC bit to indicate READ
		SCDRP$W_STS(R5)		; function
	PUSHL	R3			; Save R3
	JSB	G^MMG$SVAPTECHK		; Get SVAPTE of allocated system buffer
	MOVL	R3,SCDRP$L_SVAPTE(R5)	; Save SVAPTE in SCDRP
	POPL	R3			; Restore R3
	BISB	#SCDRP$M_S0BUF!-	; This buffer is an S0 "user" buffer
		 SCDRP$M_BUFFER_MAPPED,-; and it has been mapped
		SCDRP$L_SCSI_FLAGS(R5)	; 
	SPI$MAP_BUFFER PRIO=HIGH	; Map the "user" buffer for read access
	POPR	#^M<R2,R3,R5>		; Restore registers
10$:	RSB


	.SBTTL	MK_SENSEMODE
;+
; MK_SENSEMODE - SENSE DEVICE CHARACTERISTICS AND MODE
;
; This routine gets the device characteristics and returns them
; in the IOSB and optionally in a user specified buffer.
; If the user specified buffer is of the correct length, extended
; device characteristics will also be returned.
;
; If P1 is not specified, then IRP$L_SVAPTE in the I/O packet is
; cleared and only the device-dependent information will be returned
; in the second longword of the IOSB when the packet is queued via
; EXE$QIODRVPKT.
;
; If P1 is specified, then this routine allocates a system buffer
; and builds a buffer header using the user buffer address in P1
; and the length as specified by P2.
;
; If P2 is not specified or is specified as a value other than
; 12, then a user buffer length of 8 bytes is assumed.  The device
; class, type and buffer size will be returned in the user buffer
; along with the device-dependent information.
;
; If P2 is specified as 12, then the device class, type, buffer
; size, device-dependent information and extended device
; characteristics will be returned in the user buffer.
;
; INPUTS:
;
;	R3 	- I/O packet address
;	R4 	- Current pcb
;	R5 	- UCB address
;	R6 	- Assigned CCB address
;	AP 	- Address of the QIO argumemt P1
;
; OUTPUTS:
;
;	R0 	- Status of the operation
;	R1,R2	- Destroyed
;	All other registers preserved
;
; COMPLETION CODES:
;
;	SS$_NORMAL - Successful
;	SS$_ACCVIO - Buffer access violation
;-

MK_SENSEMODE:

	CLRL	IRP$L_SVAPTE(R3)	; Assume no user supplied buffer.
	MOVL	P1(AP), R0		; Get buffer address.
	BEQL	20$			; Branch if none supplied.
 
	MOVZWL	#8, R1			; Assume buffer length of 8 bytes.
	CMPL	#12, P2(AP)		; Length of 12 specified?
	BNEQ	10$			; Branch if not.
	MOVZWL	#12, R1			; Set length in R1.
 
10$:	JSB	G^EXE$READCHK		; Check buffer for write access.
	ADDL	#12, R1			; Allow 12 bytes for system overhead.
	PUSHL	R3			; Save IRP address.

	JSB	G^EXE$DEBIT_BYTCNT_ALO
	POPL	R3			; Restore IRP address.
	BLBC	R0,99$			; Branch on error (SS$_INSFMEM).

	MOVL	R2, IRP$L_SVAPTE(R3)	; Store address of system buffer.
	ADDL3	#12, R2,- 		; Store starting address of system
		@IRP$L_SVAPTE(R3)	;  buffer data area in buffer header.
	MOVL	P1(AP), 4(R2)		; Store user buffer address in header.
	MOVW	R1, IRP$W_BOFF(R3)	; Store total size of system buffer.

20$:	JMP	G^EXE$QIODRVPKT		; Queue the packet.

90$:	POPL	R3			; Clear IRP address off stack.
99$:	JMP	G^EXE$ABORTIO		; Abort I/O request.

	.SBTTL	MK_STARTIO	- Driver QIO entry point
;+
; MK_STARTIO
;
; This routine is the QIO entry point into the driver. It's main function
; is to dispatch to the function-code-specific routine which then executes
; the QIO.
;
; INPUTS:
;
;	R3	- IRP address
;	R5	- UCB address
;
; OUTPUTS:
;
;	R0	- 1st longword of I/O status: contains status code and
;		  number of bytes transferred
;	R1	- 2nd longword of I/O status: contains a copy of the
;		  UCB$L_DEVDEPEND field
;	R2-R4	- Destroyed
;	All other registers preserved
;-

MK_STARTIO:

	INIT_UCB_STACK			; Initialize the internal stack in the UCB

	.IF DEFINED DEBUG
	BSBW	TRACE_QIO		; Trace the current I/O request
	.ENDC

	MOVL	UCB$L_PDT(R5),R4	; Get PDT address
	MOVL	R3,R2			; Copy IRP address
	MOVL	R5,R3			; Copy UCB address
	BSBW	ALLOC_SCDRP		; Allocate an SCDRP
	MOVL	R2,SCDRP$L_IRP(R5)	; Save IRP address in SCDRP
1$:	MOVW	IRP$W_FUNC(R2),-	; Copy function code and modifiers
		SCDRP$W_FUNC(R5)	; from the IRP to the SCDRP
	MOVL	IRP$L_MEDIA(R2),-	; and media field,
		SCDRP$L_MEDIA(R5)	;
	MOVL	IRP$L_SVAPTE(R2),-	; SVAPE,
		SCDRP$L_SVAPTE(R5)	;
	MOVL	IRP$L_BCNT(R2),-	; BCNT,
		SCDRP$L_BCNT(R5)	;
	MOVW	IRP$W_BOFF(R2),-	; and BOFF
		SCDRP$W_BOFF(R5)	;

; Fix a synchronization bug in the I/O subsystem which allows virtual functions 
; to sneak through FDT processing and be queued to the driver even though
; mapping is disable. 

	BBC	#IRP$V_VIRTUAL,-	; Branch if not a virtual I/O function
		IRP$W_STS(R2),5$	;
	MOVL	IRP$L_WIND(R2),R0	; Get window block
	TSTW	WCB$W_NMAP(R0)		; Mapping enabled?
	BNEQ	5$			; Branch if so
	BSBW	DEALLOC_SCDRP		; Deallocate the SCDRP
	MOVL	R3,R5			; Copy UCB address
	MOVL	UCB$L_VCB(R5),R1	; Get address of VCB listhead
	INSQUE	(R2),@4(R1)		; Insert entry in blocked I/O list
	REMQUE	@UCB$L_IOQFL(R5),R3	; Remove I/O packet from device unit queue
	BVS	3$			; Branch if no I/O queued
	JMP	G^IOC$INITIATE		; Go initiate next function
3$:	BICW	#UCB$M_BSY,UCB$W_STS(R5); Clear unit busy
	JMP	G^IOC$RELCHAN		; Release the channel
5$:

;
;
; Allow only physical I/O functions before a PACKACK is issued.
;
	BBS	#IRP$V_PHYSIO,-		; Branch if physical I/O function
		IRP$W_STS(R2),10$
	BBC	#UCB$V_VALID,-		; Logical or virtual I/O function
		UCB$W_STS(R3),-		; Branch if volume is invalid
		VOLUME_INVALID

; If the last command was a rewind/nowait, the rewind might still be in 
; progress. If so, wait for the rewind to complete before executing this
; QIO function.

10$:	BBCC	#UCB$V_REWIND_INPROG,-	; Branch if last command was not a
		UCB$L_MK_FLAGS(R3),11$	; rewind/immediate
	BSBW	WAIT_UNIT_READY		; Otherwise, wait for rewind to complete
	BLBC	R0,COMPLETE_IO		; Branch on error

	MOVL	UCB$L_IRP(R3),R2	; Restore IRP address
	BRW	1$			; Now go execute original command

11$:	EXTZV	#IRP$V_FCODE,-		; Extract I/O function code
		#IRP$S_FCODE,- 		;
		IRP$W_FUNC(R2),R1	;
	ASSUME	IRP$S_FCODE LE 7	; Allow byte mode dispatch

	DISPATCH R1,TYPE=B,<-		; Dispatch according to function
		<IO$_NOP,	IO_NOP>,- 	; ^X00
		<IO$_UNLOAD,	IO_UNLOAD>,- 	; ^X01
		<IO$_ERASETAPE,	IO_ERASETAPE>,-	; ^x06
		<IO$_PACKACK,	IO_PACKACK>,- 	; ^X08
		<IO$_WRITEPBLK,	IO_WRITEPBLK>,-	; ^X0B
		<IO$_READPBLK,	IO_READPBLK>,- 	; ^X0C
		<IO$_AVAILABLE,	IO_AVAILABLE>,-	; ^X11
		<IO$_DSE,	IO_DSE>,-	; ^X15
		<IO$_SENSECHAR,	IO_SENSECHAR>,-	; ^X1B
		<IO$_WRITEMARK,	IO_WRITEMARK>,-	; ^X1C
		<IO$_DIAGNOSE,	IO_DIAGNOSE>,- 	; ^X1D
		<IO$_WRITELBLK,	IO_WRITELBLK>,-	; ^X20
		<IO$_READLBLK,	IO_READLBLK>,- 	; ^X21
		<IO$_REWINDOFF, IO_REWINDOFF>,- ; ^X22
		<IO$_SETMODE,	IO_SETMODE>,-	; ^X23
		<IO$_REWIND,	IO_REWIND>,-	; ^X24
		<IO$_SKIPFILE, 	IO_SKIP_FILE>,- ; ^X25
		<IO$_SKIPRECORD, IO_SKIP_RECORD>,- ; ^X26
		<IO$_SENSEMODE,	IO_SENSEMODE>,-	; ^X27
		<IO$_WRITEOF,	IO_WRITEOF>,-	; ^X28
		>

; Bogus I/O function code if we fall through. Set illegal function code
; status and complete the I/O.

IO_BOGUS:

	MOVZBL	#SS$_ILLIOFUNC,R0	; Specify the error type
	BRB	COMPLETE_IO

IO_NOP:

	MOVZWL	#SS$_NORMAL,R0		; Set success status
	BRB	COMPLETE_IO		; Complete the I/O

VOLUME_INVALID:

	MOVZWL	#SS$_VOLINV,R0		; It's not a valid volume
;	BRB	COMPLETE_IO		; Fall through to complete the I/O	

COMPLETE_IO:

	BSBW	DEALLOC_SCDRP		; Deallocate the SCDRP
	MOVL	R3,R5			; Copy UCB address

; If the CANCEL bit is set in the UCB, then the I/O has been canceled and 
; REQCOM has already been called for this IRP. The SCSI command, however, is
; allowed to complete (since there's no clean way of aborting a command to 
; the tape). In the meantime, the busy bit in the UCB remains set in order to
; prevent any new I/O from being started. Now that the command for the canceled 
; I/O has completed, see if any I/O is pending. If so, initiate it.

	BBCC	#UCB$V_CANCEL,-		; Branch if I/O has not been canceled
		UCB$W_STS(R5),1$	;
	REMQUE	@UCB$L_IOQFL(R5),R3	; Any I/O queued to this device?
	BVS	0$			; Branch if not	
	JMP	G^IOC$INITIATE		; Go initiate the I/O

0$:	BICW	#UCB$M_BSY,-		; Clear busy bit to allow future I/O
		UCB$W_STS(R5)		; to be initiated
	RSB				; Return to caller

1$:
	.IF DEFINED DEBUG
	BSBW	TRACE_QIO_STAT		; Save the final QIO status in trace buf
	BLBS	R0,5$			; Branch on success status
	NOP				; Instruction to trap on QIO with bad status
5$:
	.ENDC

; The following code segment checks for an error on a virtual I/O function. In
; this case, all virtual I/O packets in the pending queue are removed and placed
; on the blocked I/O list of the volume control block. This allows the magtape 
; ACP to perform any operations necessary to recover from the error. It then 
; requeues all the I/O from the VCB to the UCB. One specific case in which this 
; action is necessary is in encountering a tape mark at the end of a volume. 
; The ACP is responsible for the activity necessary to switch to a new volume.

	PUSHL	R0			; Save final status and count
	JSB	G^IOC$DIAGBUFILL	; Fill diagnostic buffer if present
	BLBS	(SP),20$		; Branch if successful completion
	MOVL	UCB$L_IRP(R5),R4	; Get current IRP
	BBC	#IRP$V_VIRTUAL,-	; Branch if not a virtual I/O function
		IRP$W_STS(R4),20$	;

; Temporarily prevent mount verification if a virtual I/O function fails
; with a status that would normally kick in mount verification. 

	CMPW	(SP),#SS$_VOLINV	; Volume invalid status?
	BEQL	7$			; Branch if so
	CMPW	(SP),#SS$_MEDOFL	; Medium offline status?
	BNEQ	8$			; Branch if not
7$:	MOVL	#SS$_DEVOFFLINE,(SP)	; Return device offline status instead
	BRB	20$			; Don't queue I/Os to the blocked queue
8$:
	MOVL	IRP$L_WIND(R4),R4	; Get window block
	CLRW	WCB$W_NMAP(R4)		; Clear number of mapping pointers
	MOVL	UCB$L_VCB(R5),R4	; Get address of VCB listhead
	MOVAB	UCB$L_IOQFL(R5),R2	; Get address of I/O queue listhead
	MOVL	R2,R3			; Save listhead address
10$:	MOVL	(R3),R3			; Get next entry in list
	CMPL	R3,R2			; End of list?
	BEQL	20$			; Branch if so
	BBC	#IRP$V_VIRTUAL,-	; Branch if not a virtual function
		IRP$W_STS(R3),10$	;
	MOVL	4(R3),R3		; Retrieve address of previous entry
	REMQUE	@(R3),R1		; Remove entry from Driver queue
	INSQUE	(R1),@4(R4)		; Insert entry in blocked I/O list
	BRB	10$			; Repeat for all IPRs in list

20$:	POPL	R0			; Retrieve final I/O status
	MOVL	UCB$L_DEVDEPEND(R5),R1	; Set magtape status and characteristics
	REQCOM				; Complete I/O request

	.SBTTL	MK_CANCEL	- Cancel I/O routine
;+
; MK_CANCEL
;
; This routine cancels an I/O in progress. Since there is no clean way to abort
; a command in progress to the tape, pass the active IRP to REQCOM, but keep
; the BSY bit set in the UCB to prevent any new I/O from being initiated. This
; is accomplished by temporarily emptying the pending I/O queue and calling 
; REQCOM with the active IRP. REQCOM sees the pending queue empty and clears
; the BSY bit. When control returns here, the pending queue is restored and
; the BSY bit is set. The CANCEL bit in the UCB is set to indicate that the I/O
; has been canceled. All this activity is synchronized using the fork lock.
;
; Eventually, the SCSI command for the canceled I/O will complete. Just before
; that thread calls REQCOM, it checks the BSY bit. If it finds it set, it 
; avoids calling REQCOM. Instead, it attempts to initiate any I/O in the
; pending queue, or clears the BSY bit if no I/O is pending.
;
; INPUTS:
;
;	R2	- Channel index number
;	R3	- IRP address
;	R4	- PCB address of procedd canceling I/O
;	R5	- UCB address
;	R8	- Cancel reason code, one of:
;			CAN$C_CANCEL	if called through $CANCEL or 
;					$DALLOC system service
;			CAN$C_DASSGN	if called through $DASSGN system 
;					service
; OUTPUTS:
;
;	R0-R3	- Destroyed
;	All other registers preserved
;-

MK_CANCEL:

	BBS	#UCB$V_CANCEL,-		; Don't cancel the same IRP twice
		UCB$W_STS(R5),20$	;
	TSTL	R3			; IRP address of 0? (this can happen
					; during UNIT INIT in bringing the
					; device online)
	BEQL	20$			; Branch if not
	JSB	G^IOC$CANCELIO		; Set cancel bit if appropriate.
	BBC	#UCB$V_CANCEL,-		; If the cancel bit is not set,
		UCB$W_STS(R5),20$	; just return.
    	EXTZV	#IRP$V_FCODE,-		; Extract I/O function code
		#IRP$S_FCODE,-		;
		IRP$W_FUNC(R3),R0	;
	CMPB	#IO$_READPBLK,R0	; Read in progress?
	BEQL	10$			; Branch if so, can't cancel
	CMPB	#IO$_WRITEPBLK,R0	; Write in progress?
	BEQL	10$			; Branch if so, can't cancel
	MOVAL	UCB$L_IOQFL(R5),R0	; Get I/O pending queue listhead address
	MOVAL	UCB$L_FLUSH_IOQFL(R5),R1; And flush I/O queue address
	MOVQ	(R0),(R1)		; Temporarily save I/O queue contents
	MOVL	R0,(R0)			; Initialize pending queue
	MOVL	R0,4(R0)		;
	PUSHQ	R0			; Save regs
	MOVL	#SS$_ABORT,R0		; Set abort status
	MOVL	UCB$L_DEVDEPEND(R5),R1	; Set magtape status and characteristics

	.IF DEFINED DEBUG
	BSBW	TRACE_QIO_STAT		; Save the final QIO status in trace buf
	.ENDC

	JSB	G^IOC$REQCOM		; Complete the current I/O but do not
					; start a new one
	CLRL	UCB$L_IRP(R5)		; I/O packet no longer active
	CLRB	UCB$B_8MM_CHK(R5)	; Clear 8mm COPY check flag
	BISW	#UCB$M_BSY,-		; Set the busy bit to prevent any new
		UCB$W_STS(R5)		; I/O from being initiated
	POPQ	R0			; Restore regs
	MOVQ	(R1),(R0)		; Restore pending I/O queue
	MOVL	R1,(R1)			; Reinitialize flush I/O queue
	MOVL	R1,4(R1)		;
	RSB				; Return to caller

10$:	BICB	#UCB$M_CANCEL,-		; Indicate cancel is not in progress
		UCB$W_STS(R5)		;
20$:	RSB				; Return to caller

	.SBTTL	MK_REG_DUMP	- Device register dump routine
;+
; MK_REG_DUMP
;
; This routine dumps device-specific infromation into an errorlog packet.
; The format of this information is as follows:
;
;	+-----------------------+
;	|	Longword count	| 4 bytes
;	+-----------------------+
;	|	Revision	| 1 byte
;	+-----------------------+
;	|	HW revision	| 4 bytes
;	+-----------------------+
;	|	Error Type	| 1 byte
;	+-----------------------+
;	|	SCSI ID		| 1 byte
;	+-----------------------+
;	|	SCSI LUN	| 1 byte
;	+-----------------------+
;	|	SCSI SUBLUN	| 1 byte
;	+-----------------------+
;	|	Port status	| 4 bytes
;	+-----------------------+
;	|	SCSI CMD	| n bytes
;	+-----------------------+
;	|	SCSI STS	| 1 byte
;	+-----------------------+
;	|			|
;	|    Additional Data 	| n bytes
;	|			|
;	+-----------------------+
;
; The contents of the addition data field depends upon the error type. For 
; example, extended sense errors save the extended sense data returned by the
; target in this field.
;
; Inputs:
;
;	R0	- Output buffer address
;	R5	- UCB address 
;
; Outputs:
;
;	R1-R3	- Destroyed
;	All other registers perserved
;-

MK_REG_DUMP:

	PUSHAL	(R0)+			; Save start of data area
	MOVB	#MK_ERROR_REVISION,(R0)+; Save revision level
	MOVL	UCB$L_HW_REV(R5),(R0)+	; Save hardware revision level
	MOVB	R7,(R0)+		; Save error type
	MOVZWL	UCB$W_UNIT(R5),R1	; Get unit number
	CLRL	R2			; Prepare for extended divide
	EDIV	#100,R1,R1,R2		; Extract SCSI bus ID from unit number
	MOVB	R1,(R0)+		; Save SCSI bus ID
	MOVL	R2,R1			; Copy LUN, SUBLUN
	CLRL	R2			; Prepare for extended divide
	EDIV	#10,R1,R1,R2		; Extract LUN and SUBLUN 
	MOVB	R1,(R0)+		; Save LUN field
	MOVB	R2,(R0)+		; Save SUBLUN field 
	MOVL	R8,(R0)+		; Save port status code
	MOVW	#^XFF00,(R0)+		; Assume no SCSI CMD,STS available
	CLRB	(R0)+			; Assume no additional data
	MOVL	UCB$L_SCDRP(R5),R1	; Get active SCDRP address
	BEQL	50$			; Branch if none active
	MOVL	SCDRP$L_CMD_PTR(R1),R2	; Get address of SCSI command
	BEQL	50$			; Branch if none active
	SUBL	#3,R0			; Back up pointer
	MOVL	(R2)+,R3		; Get number of SCSI command bytes
	MOVB	R3,(R0)+		; Save command length
10$:	MOVB	(R2)+,(R0)+		; Save a command byte
	SOBGTR	R3,10$			; Continue for entire SCSI command
20$:	MOVB	#-1,(R0)+		; Assume no valid status byte
	MOVL	SCDRP$L_STS_PTR(R1),R2	; Get address of status byte
	BEQL	25$			; Branch if no status byte
	MOVB	(R2),-1(R0)		; Save SCSI status byte
25$:	CLRB	(R0)+			; Assume no additonal data
	DISPATCH R7,TYPE=B,<-		; Dispatch according to error code
		<SCSI$C_CONNECTION_ERROR,	50$>,-
		<SCSI$C_MAP_BUFFER_ERROR,	50$>,-
		<SCSI$C_SEND_CMD_ERROR,		50$>,-
		<SCSI$C_INV_INQUIRY_DATA,	30$>,-
		<SCSI$C_MODE_SENSE_DATA,	30$>,-
		<SCSI$C_EXTND_SENSE_DATA,	30$>,-
		<SCSI$C_DIAGNOSTIC_DATA,	30$>>

	BUG_CHECK INCONSTATE,FATAL	; Unknown error type. This should
					; never happen.

; Error types which have additional data come here.

30$:	MOVL	SCDRP$L_SVA_USER(R1),R2	; Get address of additional data
	BEQL	50$
	MOVZBL	SCDRP$L_TRANS_CNT(R1),R1; Get length of additional data
	BEQL	50$			; Branch if none available
	MOVB	R1,-1(R0)		; Save additional data length
40$:	MOVB	(R2)+,(R0)+		; Save a byte of extended sense data
	SOBGTR	R1,40$			; Repeat for all extended sense data
;	BRB	50$			; Use common exit

50$:	SUBL	(SP),R0			; Calculate number of bytes saved
	DECL	R0			; Round up to next longword (+3), but
					; don't count LW count field itelf in
					; LW count (-4)
	ASHL	#-2,R0,@(SP)+		; Save in LW count field at top of buffer
	RSB				; Return

	.SBTTL	MK_DIAGNOSE	- FDT preprocessing for Special pass-through function
;+
; MK_DIAGNOSE
;
; This routine performs FDT preprocessing including:
;
;	- Validating access to the descriptor buffer
;	- Validating access to, and locking, the read/write buffer 
;	- Copying the SCSI command to a buffer in non-paged pool
;
; INPUTS:
;
;	R0	- Address of FDT routine
;	R3	- IRP address
;	R4	- PCB address
;	R5	- UCB address
;	R6	- CCB address
;	R7	- Bit number of user-specified I/O function code
;	R8	- Address of current entry in FDT
;	AP	- Address of first function-dependent argument (P1)
;
; OUTPUTS:
;
;-

	DSC_OPCODE = 0
	DSC_FLAGS = 4
	DSC_CMDADR = 8
	DSC_CMDLEN = 12
	DSC_DATADR = 16
	DSC_DATLEN = 20
	DSC_PADCNT = 24
	DSC_PHSTMO = 28
	DSC_DSCTMO = 32

MK_DIAGNOSE:

	MOVL	R2,-(SP)			; Save R2
	MOVL	UCB$L_ORB(R5),R2		; Get orb address
	CLRQ	-(SP)				; clear terminator and retlen 
	MOVL	ORB$L_NAME_POINTER(R2),-(SP)	; bufadr for priv audit 
	MOVZWL	ORB$W_NAME_LENGTH(R2),R2	; 
	ADDL3	#<NSA$_DEVICE_NAME@16>,R2,-(SP)	; itmcod + bufsiz 
	MOVL	SP,R2				; start of item list
	AUDIT_S_ITMLST = 16			; size of item list 

	$IFPRIV	DIAGNOSE,	-	; Branch if process has diagnose priv
		10$,		-		
		MSG=DIAGNOSE_7, -
		ITMLST=R2,	-
		PRESERVE=YES

	ADDL	#AUDIT_S_ITMLST,SP	; Clear stack by removing priv audit itemlist 
	MOVL	(SP)+,R2		; Restore R2
	MOVL	#SS$_NOPRIV,R0		; Set no privilege status
	BRW	50$			; Branch to abort the I/O

; First, check that we have read access to the user's descriptor.

10$:	ADDL	#AUDIT_S_ITMLST,SP	; Clear stack by removing priv audit itemlist 
	MOVL	(SP)+,R2		; Restore R2
	MOVQ	(AP),R0			; Get user descriptor address, length
	MOVL	R0,R9			; Save a copy of descriptor address
	CMPL	R1,#60			; Valid descriptor length
	BLSSW	40$			; Branch if not
	JSB	G^EXE$WRITECHK		; Check for read access to the descriprot
					; buffer (don't return if no access)

	CMPL	DSC_OPCODE(R9),#1	; Valid opcode?
	BNEQW	40$			; Branch if not

	CMPL	DSC_DATLEN(R9),-	; Reasonable read/write data buffer
		UCB$L_MAXBCNT(R5)	; length?
	BGTRUW	40$			; Branch if not
	CMPL	DSC_PADCNT(R9),#511	; Reasonable pad count?
	BGTRU	40$			; Branch if not
	
	MOVQ	DSC_CMDADR(R9),R0	; Get SCSI command buffer address, len
	CMPL	R1,#248			; Valid command length?
	BGTRU	40$			; Branch if not
	JSB	G^EXE$WRITECHK		; Check for read access to the command
					; buffer (don't return if no access)
	ADDL	#8,R1			; Reserve space for command buf overhead
	JSB	G^EXE$ALONONPAGED	; Allocate a buffer in which to copy
					; the SCSI command
	BLBC	R0,50$			; Branch on error
	MOVL	R1,(R2)+		; Save length of buffer
	MOVL	R2,IRP$L_MEDIA(R3)	; Save the command buffer address
	MOVL	DSC_CMDLEN(R9),R0	; Get length of the SCSI command
	MOVL	R0,(R2)+		; Save it in the command buffer
	PUSHR	#^M<R2,R3,R4,R5>	; Save regs
	MOVC3	R0,@DSC_CMDADR(R9),(R2)	; Copy the SCSI command from the user's
					; buffer to the buffer in pool
	POPR	#^M<R2,R3,R4,R5>	; Restore regs
	CLRL	IRP$L_BCNT(R3)		; Assume no user read/write data
	MOVL	DSC_DATADR(R9),R0	; Get address of user data buffer
	BEQL	30$			; Branch if no user read/write data
	MOVL	DSC_DATLEN(R9),R1	; Get length of user data buffer
	BEQL	30$			; Branch if no user read/write data
	MOVAL	G^EXE$READLOCKR,R2	; Assume user is performing a read
	BLBS	DSC_FLAGS(R9),20$	; Branch if this is a read operation
	MOVAL	G^EXE$WRITELOCKR,R2	; Other check for read access
20$:	JSB	(R2)			; Check access to and lock down buffer
	BLBC	R0,60$			; Branch on error
30$:	MOVAL	IRP$C_CDRP(R3),R0	; Get address of SCDRP within IRP
	MOVL	DSC_FLAGS(R9),(R0)+	; Save flags field in IRP/CDRP
	MOVAL	DSC_PADCNT(R9),R1	; Get address of pad count field
	.REPT	3
	MOVL	(R1)+,(R0)+		; Save pad count, timeout values
	.ENDR
	JMP	G^EXE$QIODRVPKT		; Queue the packet to the driver	

40$:	MOVL	#SS$_BADPARAM,R0	; Set bad parameter status
50$:	JMP	G^EXE$ABORTIO		; Abort the I/O with status in R0

; We arrive here if the last FDT operation - checking access to and locking
; down the user's read/write buffer fails. EXE$READLOCKR or EXE$WRITELOCKR 
; returns to us through a co-routine call to allow us to give up any resources 
; which we have allocated during FDT processing. Deallocate the buffer 
; containing a copy of the SCSI command, then return from the co-routine call. 
; R0 and R1 must be preserved.

60$:	PUSHQ	R0			; Save regs
	MOVL	IRP$L_MEDIA(R3),R0	; Get address of non-paged pool buffer
					; containing SCSI command
	MOVL	-(R0),R1		; Get length of buffer
	JSB	G^EXE$DEANONPGDSIZ	; Deallocate the packet
	POPQ	R0			; Restore regs
	RSB				; Return from co-routine call

	.SBTTL	+
	.SBTTL	+ QIO INTERFACE ROUTINES
	.SBTTL	+
	.SBTTL	IO_PACKACK	- Pack acknowledge function
	.SBTTL	IO_SENSECHAR	- Get device characteristics
	.SBTTL	IO_SENSEMODE	- Get device characteristics
;+
; IO_PACKACK
; IO_SENSEMODE
; IO_SENSECHAR
;
; This routine executes a PACKACK request by polling the device with TEST UNIT
; READY commands until the device is ready, then sending INQUIRY, RECEIVE 
; DIAGNOSTIC DATA, MODE SENSE, and MODE SELECT commands. The effect of these 
; commands is to prepare the drive to accept READ, WRITE, and tape motion 
; commands.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_PACKACK:

	BSBW	PACKACK_SEQ
	BRW	COMPLETE_IO		; Complete this I/O function

PACKACK_SEQ:

	SUBSAVE				; Save SR return address
	BSBW	WAIT_UNIT_READY		; Wait for the device to spin up
	BLBC	R0,30$			; Branch on error
	BSBW	INQUIRY			; Execute an INQUIRY command
	BLBC	R0,30$			; Branch on error
	IF_TK50	10$			; If TZK50 don't do receive diagnostic
					; command. Hardware/Firmware revision
					; available via UNIT_INIT.
	BSBW	RECEIVE_DIAG		; Execute a received diagnostic data cmd
	BLBC	R0,30$			; Branch on error
10$:	BSBW	MODE_SENSE		; Execute a MODE SENSE command
	BLBC	R0,30$			; Branch on error
	BSBW	MODE_SELECT		; Set error recovery parameters, block
					; size, etc. (if necessary)
	BLBC	R0,30$			; Branch on error
        BSBW    CHECK_SKIPFILE_SUPPORT   ; Check if READ POSITION cmd supported
	BSBW	CHECK_COMPACTION_SUPPORT ; check if device supports data compaction
20$:	BSBW	SET_CONN_CHAR		; Set up the connection characteristics
	BLBC	R0,30$			; Branch on error
    	BISW	#UCB$M_VALID,-		; Set volume valid
		UCB$W_STS(R3)		; 
30$:	SUBRETURN			; Return to caller

IO_SENSEMODE:
IO_SENSECHAR:

	BSBW	PACKACK_SEQ
	BLBC	R0,20$			; Branch on error
;
; Return device characteristics if user supplied buffer
;

;
; the IFNOT_TSZO7 is commented out below to allow the DEVDEPEND info
; to be returned for all devices
;
;	IFNOT_TSZ07	10$		; Branch if not TSZ07 

        BBS     #UCB$V_CANCEL,-         ; If request has been cancelled, skip
                 UCB$L_STS(R3),-        ; to 20$ - the IRP has already been 
                 20$                    ; deallocated. - RCL 9/21/92

	MOVL	SCDRP$L_IRP(R5),R1	; Get pointer to IRP
	TSTL	IRP$L_SVAPTE(R1)	; Is there a user supplied buffer?
	BEQL	10$			; Branch if not.

	ASSUME UCB$L_DEVDEPEND EQ UCB$B_DEVCLASS+4
	ASSUME UCB$L_DEVDEPND2 EQ UCB$L_DEVDEPEND+4

	MOVL	@IRP$L_SVAPTE(R1),R0	 ; Get system buffer data area address.
	MOVL	UCB$B_DEVCLASS(R3),(R0)+ ; Store dev class, type, buffer size.
	MOVL	UCB$L_DEVDEPEND(R3),-	 ; Store device characteristics.
		(R0)+
	CMPW	IRP$W_BCNT(R1), #12	; User buffer length = 12?
	BNEQ	10$			; Branch if not.
	MOVL	UCB$L_DEVDEPND2(R3),-	; Store extended device characteristics.
		(R0)+			;
10$:	MOVZWL	#SS$_NORMAL,R0		; Success status
20$:	BRW	COMPLETE_IO		; Complete this I/O function

	.SBTTL	IO_DSE		- Data security erase
;+
; IO_DSE
;
; This routine sends an ERASE command to the tape to erase all the data from
; the current position to the end of the tape. The tape is then rewound.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_DSE:
IO_ERASETAPE:

 	BICW	#<MT$M_BOT!-		; Indicate that we're not at beginning
 		  MT$M_EOT!-		; of tape, end of tape, or end of file
 		  MT$M_EOF@-16>,-	;
 		UCB$L_DEVDEPEND+2(R3)	;
	MOVAL	CMD_ERASE,R2		; Get address of erase command
	BSBW	SETUP_CMD		; Setup the command
	BSBW	SEND_COMMAND		; Send the command
	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	BLBCW	R0,COMPLETE_IO		; Branch on error
	BRW	IO_REWIND		; Otherwise, go rewind the tape

	.SBTTL	IO_SETMODE	- Set mode
;+
; IO_SETMODE
;
; This routine sends a MODE_SELECT command to the tape to set up various drive
; characteristics including default block size, buffered (streaming) mode, 
; number of fillers, and infinite reselection timeout.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;
;-

IO_SETMODE:

	MOVW	SCDRP$L_MEDIA+2(R5),-	; Set new default buffer size
		UCB$W_DEVBUFSIZ(R3)	;
	IF_TSZ07	10$		; Branch if TSZ07
; Add code for sw front panel density control
; Density will be in frontpnl lo byte as binary number. If zero, do usual
;Note we leave tsz07 alone more or less.
	tstb	ucb$l_frontpnl(r3)	; Is the user density null?
	beql	7$			; if so skip special stuff
; Set density value.
	movw	ucb$l_frontpnl(r3),UCB$W_MK_DENSITY(R3)  ; Copy density
7$:
	BSBW	MODE_SELECT		; Send a mode select command
	BLBS	R0,10$			; attempt to set compaction/density
	BRW	30$   			; branch on error
;
; 	- TSZ07 set new density if at BOT
;       - other devices check for compaction enable/disable
;
10$:	TSTL	UCB$L_RECORD(R3)	; Are we at BOT?
	BNEQW	25$			; Branch if not,  don't change density

	MOVL	SCDRP$L_IRP(R5),R1	; Get pointer to IRP
	MOVW	IRP$L_MEDIA+4(R1),-	; Save characteristics in unused
 		UCB$W_BOFF(R3)		; UCB location.
	EXTZV	#MT$V_DENSITY,-		; Get density selection
		#MT$S_DENSITY,-
		UCB$W_BOFF(R3),R0
;
; Do density/compaction checks
;

	tstb	ucb$l_frontpnl(r3)	; Is the user density null?
	bneq	11$			; If not accept some /dens switches
	IFNOT_TSZ07	20$
11$:
;
; TSZ07 density checks
;
	ASSUME MT$K_DEFAULT EQ 0
	TSTW	R0			; default density ?
	BNEQ	13$			; NEQ = no
	CLRW	UCB$W_MK_DENSITY(R3)	; Set density to default
	BRB	17$			; issue default density mode_select
13$:
	CMPW	#MT$K_PE_1600,R0	; Set 1600 bpi?
	BEQL	15$			; Branch if so
	CMPW	#MT$K_GCR_6250,R0	; Set 6250 bpi?
	BEQL	15$			; Branch if so
	tstw	ucb$l_frontpnl(r5)
	bneq	17$
	MOVZWL	#SS$_BADPARAM,R0	; Invalid density selected
	BRW	COMPLETE_IO		; Finished
15$:
	SUBW3	#2,R0,UCB$W_MK_DENSITY(R3)  ; Compute device density (2 less
					    ; less than VMS density values).
17$:	BSBW	MODE_SELECT		; Send a mode select command
	BLBC	R0,30$			; Branch on error
	MOVL	SCDRP$L_IRP(R5),R1	; Get pointer to IRP
	MOVW	IRP$L_MEDIA+4(R1),-	; Save characteristics in unused
 		UCB$W_BOFF(R3)		; UCB location.
	EXTZV	#MT$V_DENSITY,-		; Get density selection again
		#MT$S_DENSITY,-
		UCB$W_BOFF(R3),R0
	INSV	R0,-			; Store density value in UCB
		#MT$V_DENSITY,-		; device characteristics field.
		#MT$S_DENSITY,-
		UCB$L_DEVDEPEND(R3)
	BRB	25$			; all done
;
; All other devices get checked for compaction enable/disable
;
20$:	CMPW	#MT$K_SCSI_DC1,R0	; Set Compaction mode?
	BNEQ	21$                     ; *** IGNORE ANY OTHER DENSITY VALUES
	MOVL	#1, R0			; flag for SETUP_COMPACTION = ON
	BRB	22$
21$:	CLRL	R0			; flag compaction off
22$:					; Only attempt to issue mode_select if 
	BITL	#MT2$M_COMP_SUP,-	; compaction is supported
		UCB$L_DEVDEPND2(R3)	;
	BEQL	25$			; branch if not
	tstw	ucb$l_frontpnl(r3)	; front panel overrides compaction
	bneq	23$
	BSBW	SETUP_COMPACTION	; do MODE_SELECT for compaction page
	BLBC	R0,30$			; Branch if error
23$:	MOVL	SCDRP$L_IRP(R5),R1	; Get pointer to IRP
	EXTZV	#MT$V_DENSITY,-		; Get density selection again
		#MT$S_DENSITY,-
		IRP$L_MEDIA+4(R1),R1
	INSV	R1,-			; Store density value in UCB
		#MT$V_DENSITY,-		; device characteristics field.
		#MT$S_DENSITY,-
		UCB$L_DEVDEPEND(R3)
	BRB	30$			; all done

25$:	MOVZWL	#SS$_NORMAL,R0		; Success status
30$:	BRW	COMPLETE_IO		; Complete this I/O function

	.SBTTL	IO_WRITEOF	- Write tape mark
	.SBTTL	IO_WRITEMARK	- Write tape mark
;+
; IO_WRITEOF
; IO_WRITEMARK
;
; This routine writes one tapemark at the current position on the tape.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_WRITEMARK:
IO_WRITEOF:

	tstb	ucb$l_frontpnl(r3)	; Is the user density null?
	beql	6$
	BITL	#MT$M_BOT,UCB$L_DEVDEPEND(R3)
; Are we at BOT? If so issue a density select
	BEQL	6$
	BSBW	MODE_SELECT
6$:
 	BICW	#<MT$M_BOT!-		; Indicate that we're not at beginning
 		  MT$M_EOT!-		; of tape, end of tape, or end of file
 		  MT$M_EOF@-16>,-	;
 		UCB$L_DEVDEPEND+2(R3)	;
	MOVAL	CMD_WRITE_FILEMARKS,R2	; Get address of write filemarks command
	BSBW	SETUP_CMD		; Setup the command
	MOVL	SCDRP$L_CMD_PTR(R5),R1	; Get the SCSI command address
	MOVB	#1,SCSI_WFM$B_CNT+4+2(R1); Specify one filemark
10$:	BSBW	SEND_COMMAND		; Send the command
	BLBS	R0,15$			; Branch if success
	CMPL	R0,#SS$_ENDOFTAPE	; End of tape? If so, treat as success
	BEQL	15$			; Branch if so
	CMPL	R0,#SS$_PARITY		; Media error?
	BNEQ	20$			; Branch if not
15$: 	INCL	UCB$L_RECORD(R3)	; Update record count
20$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	BRW	COMPLETE_IO		; Complete this I/O function

	.SBTTL	IO_READPBLK	- Read a set of blocks from the SCSI drive
;+
; IO_READPBLK
;
; This routine reads a record from the tape. If the function is a REVERSE READ,
; a backspace of one record is performed both prior to and after the read, as
; many SCSI tape drives, including the TZK50 and TZ30, don't support the READ
; REVERSE command.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_READPBLK:
IO_READLBLK:

 	BICW	#<MT$M_BOT!-		; Indicate that we're not at beginning
 		  MT$M_EOT!-		; of tape, end of tape, or end of file
 		  MT$M_EOF@-16>,-	;
 		UCB$L_DEVDEPEND+2(R3)	;
	BBC	#IO$V_REVERSE,-		; Branch if this is NOT a read reverse
 		SCDRP$W_FUNC(R5),10$	; function
	MNEGW	#1,R0			; Prepare to backup one record
        SUBPUSH SCDRP$L_BCNT(R5)        ; Save read byte count
        BSBW    SKIP_RECORD             ; Backup one record
        SUBPOP  SCDRP$L_BCNT(R5)        ; Restore read byte count
	BLBCW	R0,50$			; Branch on error
10$:	MOVAL	CMD_READ,R2		; Address of read command
	BSBW	SETUP_CMD		; Setup the command
	ADDL3	#<4+SCSI_RD$B_LEN+3>,-	; Get address just beyond transfer 
		SCDRP$L_CMD_PTR(R5),R0	; length field in the SCSI command
	MOVAL	SCDRP$L_BCNT(R5),R1	; Get address of byte count field in SCRRP
	.REPT	3
	MOVB	(R1)+,-(R0)		; Fill in the transfer length field
	.ENDR	
	MOVW	#IRP$M_FUNC,-		; Set the FUNC but to indicate this
		SCDRP$W_STS(R5)		; is a read function
	BISB	#SCDRP$M_BUFFER_MAPPED,-; Remember the fact that a buffer
		SCDRP$L_SCSI_FLAGS(R5)	; has been mapped
	SPI$MAP_BUFFER			; Map the user's buffer
	BSBW	SEND_COMMAND		; Send the SCSI command
	BSBW	CLEANUP_CMD		; Cleanup from the command
 	INCL	UCB$L_RECORD(R3)	; Keep track of forward tape position
	BLBS	R0,15$			; Branch on success
	CMPL	R0,#SS$_DATAOVERUN	; If overrun, treat as success
	BEQL	15$			; Branch if so
	CMPL	R0,#SS$_ENDOFTAPE	; If EOT, treat as success
	BNEQ	50$			; Branch if not
	MOVW	#SS$_NORMAL,R0		; Change EOT status to success
15$:	MOVL	SCDRP$L_TRANS_CNT(R5),R1; Get the transfer count value
	BBC	#IO$V_REVERSE,-		; Branch if this is NOT a read reverse
 		SCDRP$W_FUNC(R5),20$	; function
	SUBPUSH	R0			; Save status from read
	SUBPUSH	R1			; Save the transfer count value
	MNEGW	#1,R0			; Prepare to backup one record
	BSBW	SKIP_RECORD		; Backup one record
	BLBC	R0,50$			; If skip fails, use that as the status
					; code
	SUBPOP	R1			; Restore transfer count
	SUBPOP	R0			; Restore status from read
20$:	INSV	R1,#16,#16,R0		; Place transfer count in high-order 
					; word of R0
50$:	BRW	COMPLETE_IO		; Complete this I/O function

60$:	SUBPOP	R2			; Clean off the internal stack
	SUBPOP	R2			;
	BRB	50$			; Use common exit

	.SBTTL	IO_WRITEPBLK	- Write a set of blocks to the SCSI drive
;+
; IO_WRITEBLK
;
; This routine writes a record to the tape. If the function is a REVERSE WRITE,
; a backspace of one record is performed both prior to and after the write, as
; many SCSI tape drives, including the TZK50 and TZ30, don't support the WRITE
; REVERSE command.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_WRITEPBLK:
IO_WRITELBLK:

	tstb	ucb$l_frontpnl(r3)	; Is the user density null?
	beql	6$
	BITL	#MT$M_BOT,UCB$L_DEVDEPEND(R3)
; Are we at BOT? If so issue a density select
	BEQL	6$
	BSBW	MODE_SELECT
6$:
	BICW	#<MT$M_BOT!-		; Indicate that we're not at beginning
		  MT$M_EOT!-		; of tape, end of tape, or end of file
		  MT$M_EOF@-16>,-	;
		UCB$L_DEVDEPEND+2(R3)	;
	BBC	#IO$V_REVERSE,-		; Branch if this is NOT a write reverse
 		SCDRP$W_FUNC(R5),10$	; function
	MNEGW	#1,R0			; Prepare to backup one record
        SUBPUSH SCDRP$L_BCNT(R5)        ; Save read byte count
	BSBW	SKIP_RECORD		; Backup one record
        SUBPOP  SCDRP$L_BCNT(R5)        ; Restore read byte count
	BLBCW	R0,50$			; Branch on error
10$:	MOVAL	CMD_WRITE,R2		; Address of read command
	BSBW	SETUP_CMD		; Setup the command
	ADDL3	#<4+SCSI_WRT$B_LEN+3>,-	; Get address just beyond transfer 
		SCDRP$L_CMD_PTR(R5),R0	; length field in the SCSI command
	MOVAL	SCDRP$L_BCNT(R5),R1	; Get address of byte count field in IRP
	.REPT	3
	MOVB	(R1)+,-(R0)		; Fill in the transfer length field
	.ENDR	
	CLRW	SCDRP$W_STS(R5)		; Clear the FUNC but to indicate this
					; is a write function
	BISB	#SCDRP$M_BUFFER_MAPPED,-; Remember the fact that a buffer
		SCDRP$L_SCSI_FLAGS(R5)	; has been mapped
	SPI$MAP_BUFFER			; Map the user's buffer
	BSBW	SEND_COMMAND		; Send the SCSI command
        BLBS    R0,15$                  ; Branch on success
        IFNOT_8MM	15$             ; Br if not 8MM
        TSTB    UCB$B_8MM_CHK(R3)       ; Should 8MM COPY check be done?
        BEQL    15$                     ; Br if no
        BSBW    DO_8MM_COPY_CHK         ; Attempt recovery processing
        BLBC    R0,15$                  ; Branch on error
        BSBW    SEND_COMMAND            ; Send the SCSI WRITE command again
15$:	BSBW	CLEANUP_CMD		; Cleanup from the command
	BLBS	R0,20$			; Branch on success
	CMPL	R0,#SS$_ENDOFTAPE	; End of tape? If so, treat as success
	BEQL	20$			; Branch if so
	CMPL	R0,#SS$_PARITY		; Media error?
	BNEQ	50$			; Branch if not
20$:	INCL	UCB$L_RECORD(R3)	; Keep track of forward tape position
	MOVL	SCDRP$L_TRANS_CNT(R5),R1; Get transfer count
	BBC	#IO$V_REVERSE,-		; Branch if this is NOT a read reverse
 		SCDRP$W_FUNC(R5),30$	; function
	SUBPUSH	R0			; Save write command status
	SUBPUSH	R1			; Save the transfer count value
	MNEGW	#1,R0			; Prepare to backup one record
	BSBW	SKIP_RECORD		; Backup one record
	MOVL	R0,R2			; Copy status for backspace command
	SUBPOP	R1			; Restore write status
	SUBPOP	R0			; Restore transfer count	
	BLBS	R2,30$			; Branch if backspace suceeded
	MOVL	R2,R0			; Otherwise, return backupspace status
30$:	INSV	R1,#16,#16,R0		; Return transfer count in h.o. word of R0
50$:	BRW	COMPLETE_IO		; Complete this I/O function

	.SBTTL	IO_SKIP_RECORD	- Move n records
;+
; IO_SKIP_RECORD
;
; This routine skips n records, where n is a signed word in the SCRP$L_MEDIA
; field. The skip operation stops upon encountering a tape mark. The actual
; number of records skipped is returned in the h.o. word of R0. If two 
; consecutive tape marks are encountered while skipping forward, and the NOT 
; mounted files-11, then SS$_ENDOFVOLUME status is returned.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status, actual skip count in h.o. word
;	R1,R2	- Destroyed
;	All other registers preserved
;
;-
	.ENABL	LSB

IO_SKIP_RECORD:

	MOVW	SCDRP$L_MEDIA(R5),R0	; Number of records to skip
	BLSS	IO_SKIP_RECORD_REV	; Branch if negative
	BEQL	50$			; Branch if zero (nothing to do)
	BSBW	SKIP_RECORD		; Skip the specified number of records
	BLBS	R0,10$			; Branch on success
	CMPL	R0,#SS$_ENDOFTAPE	; End of tape?
	BEQL	10$			; Branch if so
	CMPL	R0,#SS$_ENDOFFILE	; Tapemark encountered?
	BNEQ	20$			; Branch if not
	BSBW	CHECK_EOV		; Check for logical end-of-volume
	CMPL	R0,#SS$_ENDOFVOLUME	; End of volume?
	BNEQ	10$			; Branch if so
	DECW	R1			; Tape was backed up one record
10$:	INSV	R1,#16,#16,R0		; Return skip count in high-order

20$:	BRW	COMPLETE_IO		; Complete the I/O request

IO_SKIP_RECORD_REV:

	CLRW	UCB$W_BOFF(R3)		; Initialize accumulated skip count
30$:	BSBW	SKIP_RECORD		; Skip the specified number of records
	SUBW	R1,UCB$W_BOFF(R3)	; Update accumulated skip count
	BLBS	R0,40$			; Branch on success
	CMPL	R0,#SS$_ENDOFFILE	; Tapemark encountered?
	BEQL	40$			; Branch if so
	CMPL	R0,#SS$_ENDOFTAPE	; End of tape?
	BNEQ	20$			; Branch if not
	TSTL	UCB$L_RECORD(R3)	; Are we at BOT?
	BEQL	35$			; Branch if so
	TSTW	R1			; Any records skipped?
	BGEQ	35$			; Branch if not making any progress
	ADDW3	SCDRP$L_MEDIA(R5),-	; Prepare to skip the remaining number
		UCB$W_BOFF(R3),R0	; of records
	BLSS	30$			; Branch if more records to skip

35$:
	tstb	ucb$l_frontpnl(r3)	; Is the user density null?
	beql	36$
	BSBW	MODE_SELECT
36$:
	MOVL	#SS$_NORMAL,R0		; Treat EOT or BOT as success
40$:	MOVW	UCB$W_BOFF(R3),R1	; Place total skip count in R1
	BRB	10$			; Use common exit path

50$:	CLRL	R1			; No records skipped
	MOVZWL	#SS$_NORMAL,R0		; Set success status
	BRB	10$			; Use common exit path

	.DSABL	LSB

	.SBTTL	IO_SKIP_FILE	- Move n tapemarks (in either direction)
;+
; IO_SKIP_FILE
;
; This routine spaces n tapemarks in either direction. SCDRP$L_MEDIA contains
; a signed integer specifying the number of tapemarks to space.  If the
; device supports the READ POSITION command, allowing us to keep track
; of first block location, then we can use the SCSI SPACE FILEMARKS command
; followed by the READ POSITION command.  Otherwise we will use
; the space record option, and each time a filemark is encountered, we
; accumulate the number of records skipped.
;
; When skipping forward, a record skip count less than the maximum is used 
; in order to allow for reasonable responsive I/O cancels. Because of the way
; tape drives implement reverse skip record (rewind, space forward), this same 
; method can not be used for space reverse operations (i.e., the maximum 
; reverse record count is used).
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;-
	.ENABL	LSB

IO_SKIP_FILE:
	MOVW	SCDRP$L_MEDIA(R5),R1	; Get number of files to skip
	MOVW	R1,UCB$W_BOFF(R3)	; Save it in a convenient place
	MOVW	R1,UCB$W_BCNT(R3)	; Save another copy
	CMPW	#1,R1			; Optimize for the most common case
					; of skip count = 1
	BEQL	10$
	CMPW	#2,R1			; Optimize for the next common case
					; of skip count = 2
	BEQL	10$
	BBC	#UCB$V_SKIPFILE_SUPPORTED,- 
		UCB$L_MK_FLAGS(R3),-    ; If possible, for count > 1,
		5$		        ; use SPACE FILEMARKS/READ POSITION
	BRW	SPACE_FILEMARKS
5$:	TSTW	R1			; Test space count
	BLSS	IO_SKIP_FILE_REV	; Branch if negative count
	BEQL	20$			; Branch if zero skip count
10$:	BBS	#UCB$V_CANCEL,-		; Branch if I/O has been canceled?
		UCB$L_STS(R3),20$	;
	MOVW	#^X7FFF,R0		; Forward skip count 
	BSBW	SKIP_RECORD		; Skip records (updating UCB$L_RECORD)
	BLBS	R0,10$			; Branch if no tapemark found
	CMPL	R0,#SS$_ENDOFFILE	; Tape mark encountered?
	BNEQ	40$			; Branch if not
	BSBW	CHECK_EOV		; Check for logical end-of-volume
	CMPL	R0,#SS$_ENDOFVOLUME	; Locical EOV?
	BEQL	30$			; Branch if so
	CMPL	R0,#SS$_ENDOFFILE	; EOF status?
	BNEQ	40$			; Branch if not
	DECW	UCB$W_BOFF(R3)		; One more tapemark found
	BGTR	10$			; Branch if not done yet

20$:	MOVW	#SS$_NORMAL,R0		; Set success status
30$:	SUBW3	UCB$W_BOFF(R3),-	; Calculate actual number of
		UCB$W_BCNT(R3),R1	; files skipped
	INSV	R1,#16,#16,R0		; Save in high-order word of R0
40$:	BRW	COMPLETE_IO

IO_SKIP_FILE_REV:

50$:	BBS	#UCB$V_CANCEL,-		; Branch if I/O has been canceled?
		UCB$L_STS(R3),20$	;
	MOVW	#^X8000,R0		; Maximum reverse skip count
	BSBW	SKIP_RECORD		; Skip records (updating UCB$L_RECORD)
	BLBS	R0,50$			; Branch if no tapemark found
	CMPL	R0,#SS$_ENDOFTAPE	; End of tape?
	BEQL	20$			; Branch if not
	CMPL	R0,#SS$_ENDOFFILE	; Tape mark encountered?
	BNEQ	40$			; Branch if not

60$:	INCW	UCB$W_BCNT(R3)		; One more tapemark found
	BLSS	50$			; Branch if more to do
	BRB	20$			; Use common exit

SPACE_FILEMARKS:                        ; Do Space Filemarks to move the tape

        BBS     #UCB$V_CANCEL,-         ; Branch if I/O has been canceled?
                UCB$L_STS(R3),20$       ;
        CLRL    R0                      ; Clear upper word of R0
        MOVW    R1,R0                   ; Skip count
	BGTR	65$			; Branch if positive
        BEQL    20$                     ; Exit if zero count
        BRW     SPACE_FILEMARKS_REV     ; Branch if negative count
65$:	BSBW    SKIP_FILE               ; Skip tapemarks, return count in R1
	BLBS	R0,70$			; Case 1:  SS$_NORMAL was returned
	CMPL	R0,#SS$_ENDOFVOLUME	; Case 2:  Logical EOV status?
	BEQL	775$	                ; 
	BRW	140$			; Case 3:  Fall through, serious error
775$:	BRW	75$

; Case 1:  SS$_NORMAL was returned.
;
; If here, then SS$_NORMAL has just been returned.  There are two sub-cases:
; the tape is now positioned on some tapemark in the middle of the tape,
; or the tape is positioned on the second tapemark within an EOV tapemark pair. 
; In the latter case it is important to return SS$_ENDOFVOLUME instead of
; success.  In order to determine which case we have, employ the following 
; logic:  upon SS$_NORMAL, back up one position to see if we hit a tapemark in 
; the preceding record position.  If not, then we know we are not at EOV after
; all;  so skip forward to get to our original position.  If we are in an
; an EOV situation, however, we must change the status code from SS$_NORMAL to
; SS$_ENDOFVOLUME.
;
; Check whether the preceding record position is a tape mark as well.
; Refer to the diagram below and note that:  
; At this point the tape is positioned on the end-of-partition side ("A")
; of the tape mark.  We must space back one file to get to the beginning-of-
; partition side ("B") of the tape mark, then space back one record (to "C")
; and test for a tapemark-encounterd Check Condition.
;
;	----+----------+----------+--------------------------------------
;	    |Record to | Tapemark |
;	    |be tested |          |
;	----+----------+----------+--------------------------------------
;          (C)        (B)        (A)

; Space reverse by one filemark

70$:	MOVL	#-1,R0			; Prepare to back up to beg-of-partn
	SUBPUSH	R1			; Save actual skip count
	BSBW	SKIP_FILE		; Get to beginning-of-partition side
	SUBPOP	R1			; Restore actual skip count
	BLBS	R0,71$			; Exit if error
	BRW	140$

; Space reverse by one record

71$:	MOVL	#-1,R0			; Prepare to back up one record
	SUBPUSH	R1			; Save actual skip count
	BISL	#UCB$M_NO_POSITION_UPDATE,-
		UCB$L_MK_FLAGS(R3)
	BSBW	SKIP_RECORD		; Jump back over preceding record
	BICL	#UCB$M_NO_POSITION_UPDATE,-
		UCB$L_MK_FLAGS(R3)
	SUBPOP	R1			; Restore actual skip count
	BLBS	R0,72$			; The preceding record is not a tapemk
	CMPL	R0,#SS$_ENDOFFILE	; EOF status?
	BEQL	711$
	BRW	140$			; Branch if not, serious problem exists

; Case 1a:
; We have hit the case of 2 consecutive tapemarks, so space forward one
; tapemark to position the tape between the 2 marks.

711$:	MOVL	#1,R0			; Prepare to position between 2 tm's
	SUBPUSH	R1			; Save actual skip count
	BSBW	SKIP_FILE		; Position the tape 
	SUBPOP	R1			; Restore actual skip count
	BLBS	R0,712$			; Continue on success
	BRW	140$                    ; Exit if error
712$:	MOVL	#SS$_ENDOFVOLUME,R0	; We hit a tapemark, so return end-of-
					;    volume status
	DECW	R1			; We just backed up one filemark
	BRW	80$			; Join common exit code

; Case 1b:
; The record we just backed up to is not a tapemark.  So we are not at end
; of logical volume.  So restore position by spacing forward (across both
; the record and the subsequent tapemark).  Space by file instead of by record 
; to avoid a Check Condition. 

72$:	MOVL	#1,R0			; Prepare to skip forward acros tpmark
	SUBPUSH	R1			; Save actual skip count
	BSBW	SKIP_FILE		; Skip forward one tapemark
	SUBPOP	R1			; Restore actual skip count
	BLBS	R0,80$			; Tape is now back to original position
	BRW	140$			; Branch if err, serious problem exists

; Case 2:  Logical EOV status
;
; If here, then SS$_ENDOFVOLUME has just been returned.  Back up one tapemark.
; While it may seem similar to the code above, it is kept separate in
; order to detect the error condition that SS$_ENDOVOLUME (signified by a
; BLANK CHECK) was returned by SKIP_FILE, but the tape does not end in
; a pair of tapemarks.

75$:	MOVL	#-1,R0			; Prepare to back up one record
	SUBPUSH	R1			; Save actual skip count
	BISL	#UCB$M_NO_POSITION_UPDATE,-
		UCB$L_MK_FLAGS(R3)
	BSBW	SKIP_RECORD		; Position tape between filemarks
	BICL	#UCB$M_NO_POSITION_UPDATE,-
		UCB$L_MK_FLAGS(R3)
	SUBPOP	R1			; Restore actual skip count
	CMPL	R0,#SS$_ENDOFFILE	; EOF status?
	BNEQ	140$			; Branch if not, serious problem exists
	MOVL	#SS$_ENDOFVOLUME,R0	; Return end of volume status
	DECW	R1			; We just backed up one filemark

; Common code for Space Forward processing

80$:	BSBW	READ_POSITION		; Update ucb$l_record
	BISL	#MT$M_EOF,-              ; We are positioned on a tapemark
		UCB$L_DEVDEPEND(R3)
	MOVL	UCB$L_RECORD(R3),-	; Update last tapemark found during
		UCB$L_PREV_TM(R3)       ; a forward search
	INSV	R1,#16,#16,R0		; Save in high-order word of R0
	BRW	140$                     ; Go to COMPLETE_IO

SPACE_FILEMARKS_REV:

	BSBW	SKIP_FILE		; Skip files, return actual count in R1
	BLBS	R0,100$			; Branch if success
	CMPL	R0,#SS$_ENDOFTAPE	; Beginning of tape?
	BNEQ	140$			; Other errors serious - exit
	MOVL	#SS$_NORMAL,R0		; But BOT is not an error
100$:	

	BISL	#UCB$M_REV_SKIP,-	; Indicate direction (needed by
		UCB$L_MK_FLAGS(R3)      ; READ POSITION)
	BSBW	READ_POSITION		; Update ucb$l_record
	BICL	#UCB$M_REV_SKIP,-       ; Clear flag
		UCB$L_MK_FLAGS(R3)
	MNEGW	R1,R1			; IOSB needs unsigned count
	INSV	R1,#16,#16,R0		; Save in high-order word of R0

; Space Filemarks exit path (all cases, Forward and Reverse)

140$:	BRW	40$			; Go to common exit and COMPLETE_IO


        .DSABL  LSB


        .PAGE
        .SBTTL  READ_POSITION  - read tape record position

; READ_POSITION
;
; Do Read Position command to fill in UCB$L_RECORD
;
; INPUTS:
;
;	R0	- VMS status from last Space command
;       R3      - UCB address
;       R5      - SCDRP address
;
; OUTPUTS:
;
;       R2 - Destroyed
;       All other registers preserved.
;
READ_POSITION:
        SUBSAVE
        SUBPUSH R0
        SUBPUSH R1
	MOVL	R0,-			; Save status
		UCB$L_SKIPFILE_STAT(R3)
        MOVAL   CMD_READ_POSITION,R2    ; Address of READ POSITION command
        BSBW    SETUP_CMD               ; Setup the command
        BSBW    SEND_COMMAND            ; Send the command
        BLBC    R0,30$                   ; Success, continue
        CMPL    SCDRP$L_TRANS_CNT(R5),- ; Sufficient data returned?
                #20                     ; READ POS data length should equal 20.

        BLSS    30$                     ; No, declare the command unsupported
        MOVL    SCDRP$L_SVA_USER(R5),R0 ; Get address of data
        BBS     #BPU_BIT,-              ; If block position unknown,
                (R0),30$                ; declare the command unsupported

        MOVL    UCB$L_RECORD(R3),R2     ; Save current position
        MOVAB   8(R0),R0                ; Prepare to update UCB$L_RECORD
        MOVAB   UCB$L_RECORD(R3),R1
        .REPT   4
        MOVB    -(R0),(R1)+             ; Update UCB$L_RECORD
        .ENDR

; Sanity check:  If the skip direction was forward, UCB$L_RECORD should
; increase, otherwise decrease.

	BBS	#UCB$V_REV_SKIP,-	; Is this a reverse skip?
		UCB$L_MK_FLAGS(R3),10$

; Forward skip sanity check

	CMPL	UCB$L_RECORD(R3),R2     ; Is new value > old value ?
	BGTR	20$			; Yes, good, so exit normally.
        BLSS	30$			; It went backward, something is wrong.

; Allow for the possibility that a skip forward was issued while the previous
; position was EOV, so that both old and new values are the same. 

	CMPL    UCB$L_SKIPFILE_STAT(R3),-
		#SS$_ENDOFVOLUME        ; EOV?
	BEQL	20$			; Yes, it's okay to have same position
	BRW	30$			; Else error 

; Reverse skip sanity check

10$:	CMPL	UCB$L_RECORD(R3),R2     ; Is new value < old value ?
	BLSS	20$			; Yes, so this is success

; Allow for the possibility that a skip reverse was issued while the
; previous position was BOT, so that both old and new values = 0.

	BBC	#MT$V_BOT,-		; If new value >= old value, and tape
		UCB$L_DEVDEPEND(R3),30$	; is not at BOT, something is wrong.

; Common exit

20$:    CLRL	UCB$L_SKIPFILE_STAT(R3) ; Clear UCB field
	BSBW    CLEANUP_CMD             ; Cleanup from the SCSI command
        SUBPOP  R1
        SUBPOP  R0
        SUBRETURN

; Error path when READ_POSITION fails

30$:	BICL2   #UCB$M_SKIPFILE_SUPPORTED,-; READ POSITION command unsupported
        	UCB$L_MK_FLAGS(R3)
	LOG_ERROR  -
		TYPE=SEND_CMD_ERROR,-
		VMS_STATUS=#SS$_NORMAL,-
		UCB=R3
        BSBW    SET_TAPE_LOST           ; Set tape lost
        BRW     20$


	.SBTTL	IO_UNLOAD	- Rewind and unload the tape
;+
; IO_UNLOAD
;
; This routine rewinds amd unloads the drive and clears the VALID bit in the 
; UCB.
;
; INPUTS:
;
;	R2	- IRP address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-
IO_RETENSION:
	.ENABLE LSB

	IF_TZK10	2$              ; If TZK10, do retension
	IF_TZK11	2$              ; If TZK11, do retension
	BRW	11$			
2$:
	MOVAL	CMD_RETENSION,R2	; Get address of Retension command
	BRB	5$			; Continue with CMD set up
	
IO_UNLOAD:
IO_REWINDOFF:
 	BBS	#IO$V_RETENSION,-	; Check to see if the RETENSION bit is
		SCDRP$W_FUNC(R5),-
		IO_RETENSION		; set - if so, branch to IO_RETENSION
	ADDL3   #MAX_UNLOAD_TIME,-      ; Save time at which unload must
		G^EXE$GL_ABSTIM,-       ; be complete
		UCB$L_UNLOAD_TIME(R3)   ;
	BISW    #UCB$M_UNLOAD_INPROG,-  ; Indicate unload may be in pro
		UCB$L_MK_FLAGS(R3)      ; when next QIO is issued
 	MOVAL	CMD_UNLOAD,R2		; Get address of unload command
5$:	BSBW	SETUP_CMD		; Setup the command
	BSBW	SEND_COMMAND		; Send the command
	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	BLBC	R0,7$			; Branch on error

; Workaround for the TZK50: the unload command can take up to 20 ms
; to "take", i.e., subsequent commands sent to the drive within 20 ms
; of an unload can operate as though the unload never occurred. For example, 
; a test unit ready command sent immediately after an unload will return 
; success, when it should return unit not ready. This can cause errors 
; during multi-volume backup operations. Therefore, stall for at least a 
; second to allow the unload to do its thing.

	IFNOT_TK50 7$			; Branch if not a TK50
	MK_WAIT	#2,UCB=R3		; Wait at least 1 second
	MOVZWL	#SS$_NORMAL,R0		; Restore success status
7$:	BICW	#UCB$M_VALID,-		; Clear the volume valid bit in
		UCB$W_STS(R3)		; the UCB
	BISW	#<MT$M_BOT@-16>,-	; Mark beginning of tape
 		UCB$L_DEVDEPEND+2(R3)	;
 	BICW	#<MT$M_LOST!-		; Clear position lost,
 		  MT$M_EOT!-		; end of tape,
 		  MT$M_EOF@-16>,-	; and end of file
 		UCB$L_DEVDEPEND+2(R3)	;
	CLRL	UCB$L_RECORD(R3)	; Initialize record field
 	MNEGL	#2,UCB$L_PREV_TM(R3)	; No previous tape mark
 10$:	BRW	COMPLETE_IO		; Complete this I/O function

 11$:	MOVZBL	#SS$_ILLIOFUNC,R0	; Not a TZK10, bad func code
	BRB	10$			; for this device 
	.DISABLE LSB


	.SBTTL	IO_AVAILABLE	- Make drive available (but don't unload it)
;+
; IO_AVAILABLE
;
; This routine rewinds the tape and clears the VALID bit in the UCB. Since 
; this function commonly follows an IO$_UNLOAD function, the drive can 
; potentially be unloaded and therefore unable to accept the rewind command.
; Therefore, if UCB$L_RECORD contains a zero, and MT$M_LOST is not set don't
; perform a rewind.
;
; INPUTS:
;
;	R2	- IRP address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_AVAILABLE:

	BICW	#UCB$M_VALID,-		; Clear the volume valid bit in
		UCB$W_STS(R3)		; the UCB
	TSTL	UCB$L_RECORD(R3)	; Is tape already at BOT?
	BNEQ	10$			; Branch if not
	BBC	#MT$V_LOST,-		; Branch if tape position not lost, OK
		UCB$L_DEVDEPEND(R3),20$	; to skip rewind function
10$:	MOVAL	CMD_REWIND,R2		; Get address of rewind command
	BSBW	SETUP_CMD		; Setup the command
	MOVL	SCDRP$L_CMD_PTR(R5),R1	; Get the SCSI command address
	MOVB	#1,4+SCSI_RWND$B_IMMED(R1) ; Set the immediate bit
	BSBW	SEND_COMMAND		; Send the command
	BLBC	R0,15$			; Branch on error
	ADDL3	#MAX_REWIND_TIME,-	; Save time at which rewind must
		G^EXE$GL_ABSTIM,-	; be complete
		UCB$L_REWIND_TIME(R3)	;
15$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
20$:	MOVZWL	#SS$_NORMAL,R0		; Set success status
	BISW	#<MT$M_BOT@-16>,-	; Mark beginning of tape
 		UCB$L_DEVDEPEND+2(R3)	;
 	BICW	#<MT$M_LOST!-		; Clear position lost,
 		  MT$M_EOT!-		; end of tape,
 		  MT$M_EOF@-16>,-	; and end of file
 		UCB$L_DEVDEPEND+2(R3)	;
	CLRL	UCB$L_RECORD(R3)	; Initialize record field
 	MNEGL	#2,UCB$L_PREV_TM(R3)	; No previous tape mark
	BRW	COMPLETE_IO		; Complete this I/O function

	.SBTTL	IO_REWIND	- Rewind the tape
;+
; IO_REWIND
;
; This routine rewinds the tape.
;
; INPUTS:
;
;	R2	- IRP address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_REWIND:
 	BBS	#IO$V_RETENSION,-	; Check to see if the RETENSION bit is
		SCDRP$W_FUNC(R5),25$	; set - if so, branch to IO_RETENSION
	MOVAL	CMD_REWIND,R2		; Get address of rewind command
	BSBW	SETUP_CMD		; Setup the command
	MOVL	SCDRP$L_CMD_PTR(R5),R1	; Get the SCSI command address
	BBC	#IO$V_NOWAIT,-		; Branch if command should complete
		SCDRP$W_FUNC(R5),10$	; only after tape finishes rewinding
	MOVB	#1,4+SCSI_RWND$B_IMMED(R1) ; Set the immediate bit
	ADDL3	#MAX_REWIND_TIME,-	; Save time at which rewind must
		G^EXE$GL_ABSTIM,-	; be complete
		UCB$L_REWIND_TIME(R3)	;
	BISW	#UCB$M_REWIND_INPROG,-	; Indicate rewind may be in progress
		UCB$L_MK_FLAGS(R3)	; when next QIO is issued
10$:	BSBW	SEND_COMMAND		; Send the command
	BLBC	R0,20$			; Branch on error
	BISW	#<MT$M_BOT@-16>,-	; Mark beginning of tape
 		UCB$L_DEVDEPEND+2(R3)	;
 	BICW	#<MT$M_LOST!-		; Clear position lost,
 		  MT$M_EOT!-		; end of tape,
 		  MT$M_EOF@-16>,-	; and end of file
 		UCB$L_DEVDEPEND+2(R3)	;
 	CLRL	UCB$L_RECORD(R3)	; Tape is positioned at start
 	MNEGL	#2,UCB$L_PREV_TM(R3)	; No previous tape mark
20$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	BRW	COMPLETE_IO		; Complete this I/O function
25$:	JMP	IO_RETENSION		; BRANCH is out of range, uz JMP,
					; expected use is small

	.SBTTL	IO_DIAGNOSE	- Special pass-through function
;+
; IO_DIAGNOSE
;
; INPUTS:
;
;	R2	- IPR address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_DIAGNOSE:

	MOVL	IRP$L_MEDIA(R2),-	; Copy command buffer from IRP to
		SCDRP$L_MEDIA(R5)	; SCDRP
	MOVL	IRP$L_SVAPTE(R2),-	; and SVAPE,
		SCDRP$L_SVAPTE(R5)	;
	MOVL	IRP$L_BCNT(R2),-	; BCNT,
		SCDRP$L_BCNT(R5)	;
	MOVW	IRP$W_BOFF(R2),-	; and BOFF
		SCDRP$W_BOFF(R5)	;
	MOVW	IRP$W_STS(R2),-		; and STS
		SCDRP$W_STS(R5)		;
	MOVAL	IRP$C_CDRP(R2),R0	; Get address of CDRP portion of IRP
	EXTZV	#1,#1,(R0),R1		; Get disconnect flag
	INSV	R1,#UCB$V_DISCONNECT,-	; Fill in disconnect flag in UCB
		#1,UCB$L_MK_FLAGS(R3)	;
	EXTZV	#2,#1,(R0),R1		; Get synchronous flag
	INSV	R1,#UCB$V_SYNCHRONOUS,-	; Fill in synchronous flag in UCB
		#1,UCB$L_MK_FLAGS(R3)	;
	ADDL	#4,R0			; Advance to pad count field
	MOVL	(R0)+,-			; Fill in the pad count in the SCDRP
		SCDRP$L_PAD_BCNT(R5)	;
	MOVL	(R0)+,-			; Fill in the phase change (DMA) timeout
		SCDRP$L_DMA_TIMEOUT(R5)	; in the SCDRP
	MOVL	(R0)+,-			; Fill in the disconnect timeout in the
		SCDRP$L_DISCON_TIMEOUT(R5) ; SCDRP
	BSBW	SET_CONN_CHAR		; Set up the connect characteristics

	MOVL	SCDRP$L_MEDIA(R5),R1	; Get address of SCSI command in pool
	MOVL	(R1)+,R1		; Get length of SCSI command
	ADDL	#8,R1			; Account for overhead
	SPI$ALLOCATE_COMMAND_BUFFER	; Allocate a command buffer
	MOVL	R2,SCDRP$L_CMD_BUF(R5)	; Save address of command buffer
	CLRL	(R2)+			; Reserve a longword for status
	MOVB	#^XFF,-1(R2)		; Initialize status field
	MOVAL	-1(R2),-		; Address to save status byte
		SCDRP$L_STS_PTR(R5)	;
	MOVL	R2,SCDRP$L_CMD_PTR(R5)	; Address of SCSI command in cmd buffer
	MOVL	SCDRP$L_MEDIA(R5),R0	; Get SCSI command in pool again
	MOVL	(R0),(R2)+		; Copy SCSI command length
	PUSHR	#^M<R0,R2,R3,R4,R5>	; Save regs
	MOVC3	(R0),4(R0),(R2)		; Copy SCSI command to command buffer
	POPR	#^M<R0,R2,R3,R4,R5>	; Restore regs
	MOVL	-(R0),R1		; Get length of command buffer in pool
	JSB	G^EXE$DEANONPGDSIZ	; Deallocate the buffer
	TSTL	SCDRP$L_BCNT(R5)	; Any user data buffer?
	BEQL	10$			; Branch if not
	SPI$MAP_BUFFER			; Map the user's data buffer
10$:	SPI$SEND_COMMAND		; Send the SCSI command
	PUSHL	R0			; Save returned port status
	TSTL	SCDRP$L_BCNT(R5)	; User buffer mapped?
	BEQL	20$			; Branch if not
	SPI$UNMAP_BUFFER		; Unmap the user's data buffer
20$:	MOVL	SCDRP$L_CMD_BUF(R5),R0	; Get the command buffer address
	PUSHL	(R0)			; Save the SCSI status byte
	SPI$DEALLOCATE_COMMAND_BUFFER	; Deallocate the command buffer
	POPL	R1			; Restore the SCSI status byte
	BICL	#^X00FFFFFF,R1		; Clear all but SCSI status byte
	POPL	R0			; Restore the port status
	INSV	SCDRP$L_TRANS_CNT(R5),-	; Copy the transfer count to the 
		#16,#16,R0		; high-order word of R0
        BSBW    DEALLOC_SCDRP           ; Deallocate the SCDRP
        MOVL    R3,R5                   ; Copy UCB address
        BBCC    #UCB$V_CANCEL, -        ; Branch if I/O has
                UCB$W_STS(R5),40$       ; not been cancelled
        REMQUE  @UCB$L_IOQFL(R5),R3     ; Any I/O queued to device?
        BVS     30$                     ; Branch if not
        JMP     G^IOC$INITIATE          ; Go initiate the I/O
30$:    BICW    #UCB$M_BSY, -           ; Clear busy bit to allow future I/O
                UCB$W_STS(R5)           ; to be initiated
        RSB                             ; Return to caller
40$:    REQCOM                          ; Complete I/O request

	.SBTTL	+
	.SBTTL	+ SCSI COMMAND EXECUTION ROUTINES
	.SBTTL	+
	.SBTTL	INQUIRY		- Send an inquiry command
;+
; INQUIRY
;
; This routine sends an inquiry command to the target. The peripheral device
; type field is checked to make sure we do in fact have a SCSI tape device. In
; addition, the device type qualifier is used to determine if the device is a 
; TZK50 or TZ30.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;-

INQUIRY:

	SUBSAVE				; Save return address
	MOVAL	CMD_INQUIRY,R2		; Address of INQUIRY command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBCW	R0,30$			; Branch on error
	CMPL	SCDRP$L_TRANS_CNT(R5),#5; Sufficient inquiry data returned?
	BLSSU	17$			; Branch if not
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of inquiry data
	CMPL	SCDRP$L_TRANS_CNT(R5),-	; Revision field valid?
		#36			;
	BLSSU	10$			; Branch if not
	MOVL	32(R0),UCB$L_HW_REV(R3)	; Save hardware revision level
	ASSUME	UCB$B_DEVCLASS+1 EQ UCB$B_DEVTYPE
10$:	CLRW	UCB$B_DEVCLASS(R3)	; Assume unknown device class and type
	CMPB	SCSI_INQ$B_DEVTYPE(R0),-; Is this a SCSI tape device?
		#SCSI$C_TAPE		;
	BNEQ	40$			; Branch if not, illegal device type
	MOVB	#DC$_TAPE,-		; Indicate that this a tape device
		UCB$B_DEVCLASS(R3)	;
	BBSS	#UCB$V_REMOVABLE,-	; Assume drive has removable media
		UCB$L_MK_FLAGS(R3),15$	;
	ASSUME	SCSI_INQ$B_DEVTYPE+1 EQ SCSI_INQ$B_DEVQUAL 
	ASSUME	SCSI_INQ$V_REMOVABLE EQ 7
15$:	TSTW	(R0)			; Is this a removable media device?
	BLSS	20$			; Branch if so
	BBCC	#UCB$V_REMOVABLE,-	; Clear the removable media bit
		UCB$L_MK_FLAGS(R3),20$	; in the UCB
17$:	BRB	40$			; 
20$:	EXTZV   #0,#4,3(R0),R1          ; Get response data format
        MOVB    R1, UCB$B_SCSI_VERSION(R3) ; Save for future checks
	BEQL	25$			; Does drive conform to SCSI-1?
        CMPB    R1,#1                   ; Does drive conform to CCS?
        BEQL    25$                     ; Branch if so
        CMPB    R1,#2                   ; Does drive conform to SCSI-2?
        BEQL    25$                     ; Yes
	LOG_ERROR -			; Log an invalid inquiry data error
		TYPE=INV_INQUIRY_DATA,-	; as a warning that we got unknown
		VMS_STATUS=#SS$_NORMAL,-; SCSI version - not a fatal error.
		UCB=R3			; 
25$:	BSBW	GET_DEVICE_TYPE		; Determine device type from Vendor
29$: 	MOVZWL	#SS$_NORMAL,R0		; Set success status	
30$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	SUBRETURN			; Return to caller
40$:	LOG_ERROR -			; Log an invalid inquiry data error
		TYPE=INV_INQUIRY_DATA,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
	MOVZWL	#SS$_DRVERR,R0		; Set bad status
	BRB	30$			; Use common exit

	.SBTTL	SKIP_RECORD	- Move n records (in either direction)
;+
; SKIP_RECORD
;
; This routine skips the number of records specified by the signed integer
; in R0. The operation is aborted if a tapemark is encountered.
;
; INPUTS:
;
;	R0	- Signed word containing number of records skip
;
; OUTPUTS:
;
;	R0	- Status
;	R1	- Actual number of records skipped
;
;	UCB$L_RECORD	- Updated
;-

SKIP_RECORD:

	BISB	#UCB$M_SKIP_INPROG,-	; Indicate skip command in
		UCB$L_MK_FLAGS(R3)	; progress
 	BICW	#<MT$M_BOT!-		; Clear beginning of tape
 		  MT$M_EOT!-		; end of tape,
 		  MT$M_EOF@-16>,-	; and end of file
 		UCB$L_DEVDEPEND+2(R3)	;
	SUBSAVE				; Save return address
	CVTWL	R0,R0			; Change word count to longword count
	BGEQ	5$			; Branch if positive skip count
	BISB	#UCB$M_REV_SKIP,-	; Indicate reverse motion in progress
		UCB$L_MK_FLAGS(R3)	;
5$: 	SUBPUSH	R0			; Save space count
 	MOVAB	CMD_SPACE,R2		; Get address of SPACE command
	BSBW	SETUP_CMD		; Setup the command
	ADDL3	#<4+SCSI_SKIP$B_CNT+3>,-; Get address just beyond count field 
		SCDRP$L_CMD_PTR(R5),R1	; in the SCSI command
	SUBGET	R0			; Restore space count (but leave saved
					; value on stack)
	.REPT	2	
	MOVB	R0,-(R1)		; Copy two bytes of space count
	ASHL	#-8,R0,R0		; to the SCSI command
	.ENDR
	MOVB	R0,-(R1)		; Copy the third byte
	CLRL	UCB$L_ADDNL_INFO(R3)	; Initialize additional info field
	BSBW	SEND_COMMAND		; Send the SCSI command
	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	SUBPOP	R1			; Restore space count
	BLBC	R0,20$			; Branch on error
10$:	
	BBS	#UCB$V_NO_POSITION_UPDATE,- ; It is not appropriate to update
		UCB$L_MK_FLAGS(R3),15$  ; position now because IO_SKIP_FILE
        				; will
	ADDL	R1,UCB$L_RECORD(R3)	; Update record number in UCB
	BGEQ	15$			; Branch if non-negative record value
	BSBW	SET_TAPE_LOST		; Otherwise, assume we are lost
15$:	BICB	#<UCB$M_REV_SKIP!-	; Reverse motion no longer in progress
		UCB$M_SKIP_INPROG>,-	; Indicate skip command no
		UCB$L_MK_FLAGS(R3)	; longer in progress
	SUBRETURN			; Return to caller

; Perform a sanity check on the additional info (residue) value returned by
; the tape. If the tape told us it skipped more records than we asked for
; then set tape position lost. Note that the residue field, like the skip
; count, is a signed longword. Therefore, in order to do the comparison,
; we have to distinguish between cases of skipping forward vs. reverse.

20$:	MOVL	UCB$L_ADDNL_INFO(R3),R2	; Get number of unskipped records (residue)
	BBC	#UCB$V_REV_SKIP,-	; Branch if original skip count was
		UCB$L_MK_FLAGS(R3),25$	; positive (skipping forward)
	MNEGL	R1,R1			; Invert sign of requested skip count
	MNEGL	R2,R2			; Invert sign of residue
	BGEQ	25$			; Branch if residue was negative
	MNEGL	R2,R2			; Otherwise, reinvert sign of residue
	MNEGL	R2,UCB$L_ADDNL_INFO(R3)	; Save residue value as a negative

25$:	CMPL	R2,R1			; Compare requested skip count with residue
	BLEQU	27$			; Branch if residue is reasonable
	BSBW	SET_TAPE_LOST		; Otherwise, assume we are lost
	BRB	15$			; Use common exit

27$:	BBC	#UCB$V_REV_SKIP,-	; Branch if original skip count was
		UCB$L_MK_FLAGS(R3),29$	; positive (skipping forward)
	MNEGL	R1,R1			; Otherwise, reinvert sign of requested skip count
29$:	SUBL3	UCB$L_ADDNL_INFO(R3),-	; Get the actual number of records
		R1,R2			; skipped

; If a tapemark was encountered, the actual skip count must be adjusted. This
; accounts for the fact that tape drives do not consider tapemarks to be 
; records, but the software does (i.e., the UCB$L_RECORD field includes both
; normal records AND tapemarks).

	CMPL	R0,#SS$_ENDOFFILE	; Tape mark encountered?
	BNEQ	30$			; Branch if not
	INCL	R2			; Assume forward skipping 
	TSTL	R1			; Was original skip count positive?
	BGEQ	30$			; Branch if so
	SUBL	#2,R2			; Reverse skipping, fix up skip count
30$:	MOVL	R2,R1			; Return actual space count in R1
	BRB	10$			; Use common exit

        .SBTTL  SKIP_FILE - Move n filemarks (in either direction)
;+
; SKIP_FILE
;
; This routine skips the number of filemarks specified by the signed integer
; in R0.
;
; INPUTS:
;
;       R0      - Signed word containing number of filemarks to skip
;       R3      - UCB address
;       R4      - PDT address
;       R5      - SCDRP address
;
; OUTPUTS:
;
;       R0      - Status
;       R1      - Actual number of filemarks skipped
;       R2      - Destroyed
;-

SKIP_FILE:

	BISL	#<UCB$M_SKIP_INPROG!-   ; Indicate skip command in progress
		UCB$M_SPACEFILE_INPROG>,-
		UCB$L_MK_FLAGS(R3)	; 
        BICW    #<MT$M_BOT!-            ; Clear beginning of tape
                  MT$M_EOT!-            ; end of tape,
                  MT$M_EOF@-16>,-       ; and end of file
                UCB$L_DEVDEPEND+2(R3)   ;
        SUBSAVE                         ; Save return address
        CVTWL   R0,R0                   ; Change word count to longword count
	BGEQ	10$			; Positive or negative space count?
	BISB	#UCB$M_REV_SKIP,-	; Indicate reverse motion in progress
		UCB$L_MK_FLAGS(R3)	;
10$:	SUBPUSH R0                      ; Save space count
        MOVAB   CMD_SPACE,R2            ; Get address of SPACE command
        BSBW    SETUP_CMD               ; Setup the command
        ADDL3   #<4+SCSI_SKIP$B_CNT+3>,-; Get address just beyond count field
                SCDRP$L_CMD_PTR(R5),R1  ; in the SCSI command
        SUBGET  R0                      ; Restore space count (but leave saved
                                        ; value on stack)
        .REPT   2
        MOVB    R0,-(R1)                ; Copy two bytes of space count
        ASHL    #-8,R0,R0               ; to the SCSI command
        .ENDR
        MOVB    R0,-(R1)                ; Copy the third byte
        MOVB    #1,-(R1)                ; Specify filemarks code
        CLRL    UCB$L_ADDNL_INFO(R3)    ; Initialize additional info field
        BSBW    SEND_COMMAND            ; Send the SCSI command
        BSBW    CLEANUP_CMD             ; Cleanup from the SCSI command
        SUBPOP  R1                      ; Restore space count
        BLBC    R0,20$                  ; Branch on error
	BITW	#<MT$M_BOT!MT$M_EOT@-16>,-	; Are we at either BOT or EOT?
		UCB$L_DEVDEPEND+2(R3)
	BNEQ	15$			; Yes, so the correct DEVDEPEND bit was
					; set already in TRANS_SENSE_KEY as
					; the Check Condition was processed.
	BISW	#<MT$M_EOF@-16>,-	; Else, there was a success with no
		UCB$L_DEVDEPEND+2(R3)	; Check Condition, so the correct
					; DEVDEPEND bit must be set now.
15$:	BICL	#<UCB$M_SKIP_INPROG!-   ; Indicate skip command no longer in
		UCB$M_SPACEFILE_INPROG!- ; progress
		UCB$M_REV_SKIP>,-
		UCB$L_MK_FLAGS(R3)	; 
        SUBRETURN                       ; Return to caller

; Perform a sanity check on the additional info (residue) value returned by
; the tape. If the tape told us it skipped more records than we asked for
; then set tape position lost. Note that the residue field, like the skip
; count, is a signed longword. Therefore, in order to do the comparison,
; we have to distinguish between cases of skipping forward vs. reverse.

20$:    MOVL    UCB$L_ADDNL_INFO(R3),R2 ; Get number of unskipped records (residue)
        BBC     #UCB$V_REV_SKIP,-       ; Branch if original skip count was
                UCB$L_MK_FLAGS(R3),25$  ; positive (skipping forward)
        MNEGL   R1,R1                   ; Invert sign of requested skip count
        MNEGL   R2,R2                   ; Invert sign of residue
        BGEQ    25$                     ; Branch if residue was negative
        MNEGL   R2,R2                   ; Otherwise, reinvert sign of residue
        MNEGL   R2,UCB$L_ADDNL_INFO(R3) ; Save residue value as a negative

25$:    CMPL    R2,R1                   ; Compare requested skip count with residue
        BLEQU   27$                     ; Branch if residue is reasonable
        BSBW    SET_TAPE_LOST           ; Otherwise, assume we are lost
        BRB     15$                     ; Use common exit

27$:    BBC     #UCB$V_REV_SKIP,-       ; Branch if original skip count was
                UCB$L_MK_FLAGS(R3),29$  ; positive (skipping forward)
        MNEGL   R1,R1                   ; Otherwise, reinvert sign of requested skip count
29$:    SUBL2   UCB$L_ADDNL_INFO(R3),-  ; Get the actual number of records
                R1                      ; skipped
        BRB     15$                     ; Use common exit
        .PAGE
	.SBTTL	RECEIVE_DIAG	- Receive diagnostic results
;+
; RECEIVE_DIAG
;
; This routine sends a RECEIVE DIAGNOSTIC RESULTS command to the tape
; to obtain the controller hardware and software revision levels for 
; TZ30s and TK50s. The revision info is translated from hex to text and
; saved in the UCB. All other drives return revision information their
; inquiry data, and it is already in text format.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;-

HEX_TO_TXT:	.ASCII	/0123456789ABCDEF/

RECEIVE_DIAG:

	IF_TK	5$			; Branch if device is a TZK50 or TZ30
	MOVZWL	#SS$_NORMAL,R0		; Otherwise, this routine is a NOP
	RSB				; Return to caller

5$:	SUBSAVE				; Save return address
	MOVAL	CMD_RECEIVE_DIAG,R2	; Address of RECEIVE_DIAG command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	MOVZWL	#60,-			; Set up command-specific DMA timeout
		SCDRP$L_DMA_TIMEOUT(R5)
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBC	R0,10$			; Branch on error
	CMPL	SCDRP$L_TRANS_CNT(R5),#2; Sufficient diag data returned?
	BLSSU	20$			; Branch if not
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of received diag data
	ASSUME	SCSI_RCVD$B_HW_REV+1 EQ SCSI_RCVD$B_SW_REV
	PUSHL	R3			; Save reg
	MOVAL	UCB$L_HW_REV(R3),R3	; Get address of HW rev field in UCB
	MOVL	#12,R1			; Prepare to translate HW rev field
7$:	EXTZV	R1,#4,(R0),R2		; Get a nibble of HW revision
	MOVB	HEX_TO_TXT[R2],(R3)+	; Translate to text and save in UCB
	SUBL	#4,R1			; Prepare to extract next nibble
	BGEQ	7$			; Repeat for entire REV field
	POPL	R3			; Restore reg
	MOVZWL	#SS$_NORMAL,R0		; Set success status
10$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	SUBRETURN			; Return to caller

20$:	LOG_ERROR -			; Log an invalid diagnostic data error
		TYPE=DIAGNOSTIC_DATA,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
	MOVZWL	#SS$_DRVERR,R0		; Set bad status
	BRB	10$			; Use common exit

	.SBTTL	REQUEST_SENSE	- Send a request sense command
;+
; REQUEST_SENSE
;
; This routine is called by SEND_COMMAND when a command fails with check 
; condition status. A REQEST SENSE command is sent to the target.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

REQUEST_SENSE:

	SUBSAVE				; Save return address
	MOVAL	CMD_REQUEST_SENSE,R2	; Address of REQUEST_SENSE command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	MOVL	UCB$L_REQSNS_PAGE(R3),-	; Point to our pre-allocated resource
		R0
	MOVL	SCDRP$L_SVA_USER(R0),-	; Copy relevant fields
		SCDRP$L_SVA_USER(R5)    ; so we use our pre-allocated resource
	MOVL	SCDRP$L_SVAPTE(R0),-	;
		SCDRP$L_SVAPTE(R5)      ;
	MOVL	SCDRP$L_SVA_SPTE(R0),-	;
		SCDRP$L_SVA_SPTE(R5)    ;
	MOVL	SCDRP$L_SVA_DMA(R0),-	;
		SCDRP$L_SVA_DMA(R5)     ;
	MOVW	SCDRP$W_NUMREG(R0),-    ;
		SCDRP$W_NUMREG(R5)      ;
	MOVW	SCDRP$W_MAPREG(R0),-    ;
		SCDRP$W_MAPREG(R5)      ;
	MOVL	SCDRP$L_BCNT(R0),-      ;
		SCDRP$L_BCNT(R5)        ;
	MOVL	SCDRP$W_BOFF(R0),-      ;
		SCDRP$W_BOFF(R5)        ;
	BISL	#IRP$M_FUNC,-		; Set FUNC bit to indicate READ
		SCDRP$W_STS(R5)		; function
	SPI$SEND_COMMAND		; Send the SCSI command
	BICB	#SCDRP$M_S0BUF!-	; Prevent CLEANUP_CMD from releasing
		 SCDRP$M_BUFFER_MAPPED,-; our preallocated resource.
		SCDRP$L_SCSI_FLAGS(R5)	; 
	CLRW	SCDRP$W_NUMREG(R5)      ; There are no resources leftover here!
	CLRW	SCDRP$W_MAPREG(R5)      ; 
	BLBC	R0,30$			; Branch on SPI$SEND_COMMAND error
	ASSUME	SCSI$C_GOOD_STATUS EQ 0
	MOVZBL	@SCDRP$L_STS_PTR(R5),R1	; Get SCSI status byte
	BICB	#SCSI$M_STAT_MASK,R1	; Clear reserved, vendor unique bits
	BNEQ	20$			; Branch if bad status
10$:	SUBRETURN			; Return to caller

20$:	MOVL	#SS$_MEDOFL,R0		; Set bad status
30$:	LOG_ERROR -			; Log a send command error
		TYPE=SEND_CMD_ERROR,-	;
		VMS_STATUS=R0,-		;
		UCB=R3			;
	BRB	10$			; Clean up and return to caller

	.SBTTL	TEST_UNIT_READY	- Send a test unit ready command
;+
; TEST_UNIT_READY
;
; This routine sends a TEST UNIT READY command to the tape to determine
; whether the device is ready to receive read, write, and tape motion commands.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

TEST_UNIT_READY:

	SUBSAVE				; Save return address
	BISB	#UCB$M_WAITRDY_INPROG,-	; Set wait for unit ready in progress
		UCB$L_MK_FLAGS(R3)	; 
	MOVAL	CMD_TEST_UNIT_READY,R2	; Address of TEST UNIT READY command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BSBW	SEND_COMMAND		; Send the SCSI command
	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	BICB	#UCB$M_WAITRDY_INPROG,-	; Clear wait for unit ready in progress
		UCB$L_MK_FLAGS(R3)	; 
	SUBRETURN

	.SBTTL	MODE_SENSE	- Send a mode sense command
;+
; MODE_SENSE
;
; This routine sends a MODE SENSE command. The only mode sense information 
; currently used is the write protect bit.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;-

MODE_SENSE:

	SUBSAVE				; Save return address
	MOVAL	CMD_MODE_SENSE,R2	; Address of MODE SENSE command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBCW	R0,40$			; Branch on error
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of mode sense data
	BICL	#MT$M_HWL,-		; Assume tape is not hardware write 
		UCB$L_DEVDEPEND(R3)	; locked
	BBC	#SCSI_MSNS$V_WP,-	; Branch if tape not write locked
		SCSI_MSNS$B_WP(R0),20$	;
	BISL	#MT$M_HWL,-		; Indicate that tape is in fact hardware
		UCB$L_DEVDEPEND(R3)	; write locked

20$:	IFNOT_TSZ07	30$		; Branch if not TSZ07
	BBC	#SCSI_MSNS$V_BLOCK,-	; Check for a block descriptor,
		SCSI_MSNS$B_BLOCK(R0),-	; if none, leave
		30$  
	CLRL	R1			; Clear in case default (0) density
	MOVB	SCSI_MSNS$B_DENSITY(R0),- ; Get the current device density
		UCB$W_MK_DENSITY(R3)	; and save it
	BEQL	25$			; If default (0), done
	tstw	ucb$l_frontpnl(r3)	;frontpanel
	bneq	25$
	ADDW3	#2,UCB$W_MK_DENSITY(R3),-; Translate to VMS density
		R1 			;
25$:	INSV	R1,#MT$V_DENSITY,-	; Store density in device 
		#MT$S_DENSITY,-		; characteristics
		UCB$L_DEVDEPEND(R3)	;
30$:	MOVZWL	#SS$_NORMAL,R0		; Set success status
40$:	BSBW	CLEANUP_CMD		; Cleanup from the command
	SUBRETURN			; Return to caller


	.SBTTL	MODE_SELECT	- Send a mode select command
;+
; MODE_SELECT
;
; This routine sends a MODE_SELECT command to the tape to set up various drive
; characteristics including default block size, buffered (streaming) mode, 
; number of fillers, and infinite reselection timeout.
;
; This command is different from others in that each device has its own
; vendor-unique data. At unit init, when the device type was determined, a
; pointer to this vendor-unique mode select data was placed in the UCB. This
; data is appended to the standard end of the standard mode select data. 
;
; Rather than have a unique SCSI_CMD table entry for device type, we have 
; a template entry containing the generic mode select data, and a seond entry
; used to actually build the mode select command. The template entry is first
; copied to the active entry, then the BCNT and parameter list length fields
; are increased by the length of the vendor-unique info length.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;-

MODE_SELECT:

	ASSUME	SCSI_MSEL$W_RSVD0+2 EQ SCSI_MSEL$B_SPEED
	ASSUME	SCSI_MSEL$B_SPEED EQ SCSI_MSEL$B_MODE
	ASSUME	SCSI_MSEL$B_MODE+1 EQ SCSI_MSEL$B_DSCLEN
	ASSUME	SCSI_MSEL$B_DSCLEN+1 EQ SCSI_MSEL$B_DENS
	ASSUME	SCSI_MSEL$B_DENS+1 EQ SCSI_MSEL$B_BLOCKS
	ASSUME	SCSI_MSEL$B_BLOCKS+3 EQ SCSI_MSEL$B_RSVD1
	ASSUME	SCSI_MSEL$B_RSVD1+1 EQ SCSI_MSEL$B_BLKLEN

	SUBSAVE				; Save return address
	MOVAL	CMD_MODE_SELECT_TMP,R1	; Get the mode select command template
	MOVAL	CMD_MODE_SELECT,R2	; Get the mode select command
	PUSHR	#^M<R2,R3,R4,R5>	; Save regs
	MOVC3	#MODE_SEL_TMP_LEN,-	; Copy the mode select infro from the
		(R1),(R2)		; template to the active command
	POPR	#^M<R2,R3,R4,R5>	; Restore regs
	MOVL	UCB$L_MSEL_INFO(R3),R1	; Get address of vendor-unique info
	MOVZBL	(R1)+,R0		; Get length of vendor-unique info
	ADDB	R0,5(R2)		; Increase parameter list length in cmd 
					; by vendor-unique length	
	ADDW	R0,9(R2)		; Increase BCNT by vendor-unique length	
10$:	BSBW	SETUP_CMD		; Perform setup for SCSI command
	MOVL	SCDRP$L_SVA_USER(R5),R1	; Get address of mode select buffer
	CLRW	(R1)+			; Reserved fields
	MOVB	#SCSI_MSEL$M_BUF,(R1)+	; Buffered mode, default speed 
	MOVB	#SCSI_MSEL$C_DSCLEN,-	; Block descriptor length
		(R1)+
	CLRQ	(R1)+			; Block descriptor: default density for
					; all blocks, variable length blocks
	tstw	ucb$l_frontpnl(r3)	; front panel active?
	bneq	12$			; if neq insert density
	IFNOT_TSZ07	15$		; Branch if not TSV07
	brb	13$
12$:
	movw	ucb$l_frontpnl(r3),UCB$W_MK_DENSITY(R3)
13$:
	MOVB	UCB$W_MK_DENSITY(R3),-8(R1) ; Store density field
15$:	MOVL	UCB$L_MSEL_INFO(R3),R2	; Get address of vendor-unique info
	MOVZBL	(R2)+,R0		; Get length of vendor unique info
	BEQL	30$			; Branch of none
20$:	MOVB	(R2)+,(R1)+		; Copy a byte of vendor-unique data
	SOBGTR	R0,20$			; Repeat for all vendor-unique data
30$:	BSBW	SEND_COMMAND		; Send the command
	BSBW	CLEANUP_CMD		; Cleanup from the command
	SUBRETURN			; Return to caller

	.SBTTL	DO_8MM_COPY_CHK	- 8mm COPY from BOT processing
;+
; DO_8MM_COPY_CHK 
;
; This routine is called from IO_WRITPBLK as a result of a SCSI error
; status returned in response to an attempt to begin a $COPY operation
; to a tape positioned at BOT and containing no files ($INIT done but
; tape void of files).
;
; During $COPY from BOT processing, the positioning command SPACE RECORD
; REVERSE is used to position the tape after the "VOL1..." record (1) and
; is followed by a WRITE command to write the "HDR1..." record (2). This is
; an illegal command sequence on the 8mm devices and results in CHECK 
; CONDITION - ILLEGAL REQUEST status (SS$_DRVERR).
;
; To accommodate the 8mm behavior this routine creates an additional
; SCDRP and uses it and an S0 buffer to generate a REWIND, READ rec 1
; ("VOL1..."), REWIND, WRITE rec 1 ("VOL1...") sequence to accomplish
; correct positioning for the write that follows the "VOL1..." record (1).
;
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1, R2	- Destroyed
;	All other registers preserved
;-
	
DO_8MM_COPY_CHK:

	SUBSAVE
	MOVL	R5,UCB$L_SCDRP_SAV1(R3)	; Save original SCDRP address
	BSBW	ALLOC_SCDRP		; Allocate additional SCDRP
;
;	This seems to be the COPY from BOT situation...REWIND and
;	READ the first record to determine if it is "VOL1...".
;
	BSBW	DO_8MM_REWIND		; Do 1st Rewind
	BLBS	R0,20$			; Br if ok
	BRW	70$			; Error status -- return
20$:	MOVL	SCDRP$L_CMD_BUF(R5),R0	; Get address of command buffer
	SPI$DEALLOCATE_COMMAND_BUFFER	; Dealloc command buffer
	MOVAL	CMD_READ_8MM,R2		; Address of 8mm READ command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	ADDL3	#<4+SCSI_RD$B_LEN+3>,-	; Get address just beyond transfer 
		SCDRP$L_CMD_PTR(R5),R0	; length field in the SCSI command
	MOVAL	SCDRP$L_BCNT(R5),R1	; Get addr of byte count field in SCDRP
	.REPT	3
	MOVB	(R1)+,-(R0)		; Fill in the transfer length
	.ENDR
	MOVW	#IRP$M_FUNC,-		; Set the FUNC bit to indicate a READ
		SCDRP$W_STS(R5)
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBS	R0,25$			; Br if ok
	BRW	70$			; Error status -- return
25$:	CMPL	@SCDRP$L_SVA_USER(R5),-	; Is this the "VOL1..." record
		#^A/VOL1/
	BEQL	30$			; Br if it is
	BRW	70$			; It isn't -- just return
30$:	MOVL	SCDRP$L_CMD_BUF(R5),R0	; Get address of command buffer
	SPI$DEALLOCATE_COMMAND_BUFFER	; Dealloc command buffer (but don't
					;   unmap data buffer)
	INCL	UCB$L_RECORD(R3)	; Keep track of forward tape position
;
;	Rewind again and WRITE "VOL1..."
;
	SUBPUSH	SCDRP$L_BCNT(R5)	; Save the byte count for use by WRITE
	BSBW	DO_8MM_REWIND		; Do 2nd Rewind
	BLBS	R0,40$			; Br if error
	SUBPOP	SCDRP$L_BCNT(R5)	; Restore the byte count
	BRB	70$
40$:
	tstw	ucb$l_frontpnl(r3)	; front panel stuff active?
	beql	42$
	bsbw	wait_unit_ready
	bsbw	mode_select		; select density again since at bot
42$:
	MOVL	SCDRP$L_CMD_BUF(R5),R0	; Get address of command buffer
	SPI$DEALLOCATE_COMMAND_BUFFER	; Dealloc command buffer
;
;	The SCDRP still points to the S0 buffer containing the "VOL1..."
;	record that was READ above, so WRITE it back to tape. Once this is
;	done, subsequent WRITES will complete successfully.
;
	MOVAL	CMD_WRITE_8MM,R2	; Address of 8mm WRITE command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	SUBPOP	SCDRP$L_BCNT(R5)	; Restore the byte count
	ADDL3	#<4+SCSI_WRT$B_LEN+3>,-	; Get address just beyond transfer
		SCDRP$L_CMD_PTR(R5),R0	; length field in the SCSI command
	MOVAL	SCDRP$L_BCNT(R5),R1	; Get addr of byte count field in SCDRP
	.REPT	3
	MOVB	(R1)+,-(R0)		; Fill in the transfer length
	.ENDR
	CLRW	SCDRP$W_STS(R5)		; Clr the FUNC bit to indicate a WRITE
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBS	R0,80$			; Br if no error
70$:	BSBW	SET_TAPE_LOST		; Set position lost
	LOG_ERROR -			; Log an extended sense data error
		TYPE=EXTND_SENSE_DATA,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
	MOVZWL	#SS$_DRVERR,R0		; Return error status
80$:
	INCL	UCB$L_RECORD(R3)	; Keep track of forward tape position
	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	BSBW	DEALLOC_SCDRP		; Dealloc additional SCDRP
	MOVL	UCB$L_SCDRP_SAV1(R3),R5	; Restore original SCDRP
	MOVL	R5,UCB$L_SCDRP(R3)	; copy to UCB
90$:
	CLRB	UCB$B_8MM_CHK(R3)	; Clear COPY check in progress flag
	SUBRETURN

	.SBTTL	DO_8MM_REWIND	- Rewind the 8MM device
;+
; This subroutine is called from DO_8MM_COPY_CHK to send a rewind command to
; the device.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1, R2	- Destroyed
;	All other registers preserved
;-

DO_8MM_REWIND:
	SUBSAVE
	MOVAL	CMD_REWIND,R2		; Address of REWIND command
	BSBW	SETUP_CMD		; Setup the command
	BSBW	SEND_COMMAND		; Send the command
	BLBC	R0,20$			; Br if error
	BISW	#<MT$M_BOT@-16>,-	; Mark the beginning of tape
		UCB$L_DEVDEPEND+2(R3)	;
	BICW	#<MT$M_LOST!-		; Clear position lost,
		  MT$M_EOT!-		; end of tape,
		  MT$M_EOF@-16>,-	; and end of file
		UCB$L_DEVDEPEND+2(R3)	;
	CLRL	UCB$L_RECORD(R3)	; Tape is positioned at start
	MNEGL	#2,UCB$L_PREV_TM(R3)	; No previous tape mark
20$:	SUBRETURN


	.SBTTL	+
	.SBTTL	+ UTILITY ROUTINES  
	.SBTTL	+
	.SBTTL	SET_UNIT_ONLINE	- Issue SCSI commands to bring unit online
;+
; SET_UNIT_ONLINE
;
; This routine attempts to bring a unit online. First, INQUIRY and RECEIVE
; DIAGNOSTIC RESULTS commands are sent to the drive to ensure that it can 
; communicate, that it's a valid tape-like device, and that it's up to rev. 
; A check is also done to see if the unit supports data compaction.
; If a failure occurs, a fork thread is set up to periodically poll the drive. 
;
; INPUTS:
;
;	R5	- UCB address
;
; OUTPUTS:
;
;	R0-R5	- Destroyed
;
;	ONLINE bit set in the UCB, BSY bit cleared
;-

SET_UNIT_ONLINE:

10$:	MOVL	UCB$L_PDT(R5),R4	; Get PDT address
	MOVL	R5,R3			; Copy UCB address
	BSBW	ALLOC_SCDRP		; Allocate an SCDRP
	BSBW	INQUIRY			; Send an inquiry command
	BLBC	R0,15$			; Branch on error
	BSBW	TEST_UNIT_READY		; Clear out a possible pending UNIT 
					; ATTENTION sense key
	IF_TK50		12$		; If TKZ50 do not send a RECEIVE
					; DIAGNOSTIC command.
	BSBW	RECEIVE_DIAG		; Send a receive diag data command
	BLBC	R0,15$			; Branch on error
12$:	BSBW	CHECK_COMPACTION_SUPPORT ; check if device supports data compaction

; Prohibit certain devices from skipfile support

	IF_TZK10 13$			; TZK10 does not support BT bit of 0
	IF_TSZ07 13$			; TSZ07/5 don't BLANK-CHECK when
	IF_TSZ05 13$			; hitting end-of-volume
	IF_TKZ09 13$			; TKZ09:  READ POSITION data may be
					; unpredictable after rewind
	BISL2	#<UCB$M_SKIPFILE_SUPPORTED!-  ; All other devices turn on 
		UCB$M_FIRST_READPOS_CMD>,-    ; skipfile support and indicate
		UCB$L_MK_FLAGS(R3)	      ; initial READ POSITION pending


13$:	IF_TK50		14$		; If TZK50 do not output revsion
					; check errorlog.
	BSBW	CHECK_REV_LEVEL		; Check for minimum rev level
14$:	MOVZWL	#SS$_NORMAL,R0		; Ignore revision check failure (bring
					; drive online regardless of rev)
15$:	BSBW	DEALLOC_SCDRP		; Release the SCDRP
	MOVL	R3,R5			; Copy UCB address
	BLBC	R0,30$			; Branch if error
	BISW	#UCB$M_ONLINE!-		; Set the device online, busy
		 UCB$M_BSY,-		;
		UCB$W_STS(R5)		;

; Since we don't know where the tape is at this point, initialize state in the
; UCB to indicate that position is lost. Normally the tape position is at BOT
; when the driver is loaded. However, it's possible that if we're booting
; standalone backup from tape, the position could be at some random point in
; the tape. Therefore, place it safe and assume we're lost.

	BSBW	SET_TAPE_LOST		; Indicate we don't know where tape is

	.IF DEFINED DEBUG
	MOVL	#SS$_NORMAL,R0		; Set normal status
	BSBW	TRACE_QIO_STAT		; Save the final QIO status in trace buf
	.ENDC

	REMQUE	@UCB$L_IOQFL(R5),R3	; Any I/O queued to this device?
	BVS	20$			; Branch if not	
	JMP	G^IOC$INITIATE		; Go initiate the I/O

20$:	BICW	#UCB$M_BSY,-		; Clear the busy bit
		UCB$W_STS(R5)		;
	RSB				; Return to caller

; We've tried and failed at least once to send an inquiry command. Clear the
; online and busy bits and set up a fork thread to poll the device once 
; a minute in an attempt to bring it online. In the meantime, any I/O queued to
; this device will fail with device offline status. Flush the queue of any I/O
; that might have been queued to the device during the period that it was
; online but busy. 

30$:	BICW	#UCB$M_ONLINE,-		; Set the unit offline, but leave device
		UCB$W_STS(R5)		; busy since polling is in progress
40$:	REMQUE	@UCB$L_IOQFL(R5),R3	; Remove an IRP from the queue
	BVS	45$			; Branch if queue was empty
	INSQUE	(R3),-			; Place on end of flush queue
		@UCB$L_FLUSH_IOQBL(R5)	; 
	BRB	40$			; Repeat until I/O queue is empty
45$:	REMQUE	@UCB$L_FLUSH_IOQFL(R5),-; Remove an IRP from the flush queue
		R3			;
	BVS	50$			; Branch if queue was empty
	MOVL	R3,UCB$L_IRP(R5)	; Copy IRP addess to UCB
	MOVL	#SS$_DEVOFFLINE,R0	; Set device offline status
	JSB	G^IOC$REQCOM		; Complete the QIO
	BRB	45$			; Continue to flush queue

50$:	MK_WAIT	#60			; Wait for 60 seconds
	BRW	10$			; And try again

	.SBTTL	SET_TAPE_LOST	- Set state in UCB to indicate tape position lost
;+
; SET_TAPE_LOST
;
; This routine sets state in thge UCB to indicate that the tape position is 
; lost, and is called at unit init and any time are error occurs which causes
; us to suspect that we may have lost tape position.
;
; INPUTS:
;
;	R3	- UCB
;
; OUTPUTS:
;
;	MT$M_LOST set in DEVDEPEND of UCB
;	MT$M_BOT, MT$M_EOT, MT$M_EOF cleared in DEVDEPEND of UCB
;	UCB$L_PREV_TM gets -2 (no previous TM found)
;	All registers preserved
;-

SET_TAPE_LOST:

        BITL    #UCB$M_COMPCHK_IN_PROG,-; Compaction check in progress?
                UCB$L_MK_FLAGS(R3)      ;
        BNEQ    10$                     ; Skip set lost if yes
  	BISW	#MT$M_LOST@-16,-	; Set position lost
 		UCB$L_DEVDEPEND+2(R3)	;
 	BICW	#<MT$M_BOT!-		; Clear beginning of tape,
 		  MT$M_EOT!-		; end of tape,
 		  MT$M_EOF@-16>,-	; and end of file
 		UCB$L_DEVDEPEND+2(R3)	;
 	MNEGL	#2,UCB$L_PREV_TM(R3)	; No previous tape mark
10$:	RSB				; Return to caller

	.SBTTL	WAIT_UNIT_READY	- Wait for unit to become ready
;+
; WAIT_UNIT_READY
;
; This routine polls once every READY_POLL_INTERVAL seconds, waiting for the
; drive to become ready. The drive can be not ready for two reasons: a tape
; is not mounted in the drive; or a previous rewind/nowait command is still
; in progress. In SCSI, there's no way to distinguish between these two cases.
; However, the driver keeps track of the time that the last rewind command
; sent to the drive should have completed (in UCB$L_REWIND_TIME), and polling 
; continues only until that time is exceeded. This prevents unnecessary polling 
; when the tape drive is clearly not ready. In the case of a media loader,
; a previous UNLOAD operation to change cartridges will also make the unit
; not ready for some time. In this case we wait until UCB$L_UNLOAD_TIME is
; reached to decide when the tape is ready.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

WAIT_UNIT_READY:

	SUBSAVE				; Save return address
10$:	BSBW	TEST_UNIT_READY		; Execute TEST UNIT READY command
	BLBC	R0,30$			; Branch on error
20$:	BICL    #UCB$M_UNLOAD_INPROG,-  ; Clear UNLOAD bit
		UCB$L_MK_FLAGS(R3)
	SUBRETURN			; Return to caller

30$:	CMPL	R0,#SS$_DEVOFFLINE	; Device offline?
	BNEQ	20$			; Branch if not
	MOVL	#SS$_ABORT,R0		; Assume I/O got canceled
	BBS	#UCB$V_CANCEL,-		; Branch if it did
		UCB$W_STS(R3),20$	;
	MOVZWL  #SS$_MEDOFL,R0          ; Assume device is not ready
	BBS     #UCB$V_UNLOAD_INPROG,-  ; Branch if last operation was an UNLOAD
		UCB$L_MK_FLAGS(R3),35$  ; that we may be waiting for a media stacker
	CMPL	G^EXE$GL_ABSTIM,-	; Could the not ready condition be
		UCB$L_REWIND_TIME(R3)	; due to a recent rewind or unload command?
	BGEQU	20$			; Branch if not, no need for more polling
	BRB	40$			; Continue polling for rewind completion
35$:    CMPL    G^EXE$GL_ABSTIM,-       ; Could the not ready condition be
		UCB$L_UNLOAD_TIME(R3)   ; due to a recent unload operation?
	BGEQU   20$                     ; Branch if not, no need for more polling

40$:	MK_WAIT	#READY_POLL_INTERVAL,-	; Wait a few seconds
		UCB=R3			;
	BRB	10$			; Continue polling

	.SBTTL	CHECK_EOV	- Check for logical end-of-volume
;+
; CHECK_EOV
;
; This routine checks for logical end-of-volume, which has the following
; prerequisites:
;
;	- Two consecutive tape marks are encountered during
;	  a SKIP_FILE or SKIP_RECORD operation
;
; If logical end-of-volume is detected, the tape is backspaced one record to
; position it between the two tapemarks.
;
; INPUTS:
;
;	R0	- SS$_ENDOFVOLUME
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
;	UCB$L_RECORD	- Current record (on which a tape mark has been found)
;	UCB$L_PREV_TM	- Record of last tapemark encountered
;
; OUPUTS:
;
;	R0	- Status (SS$_ENDOFVOLUME or SS$_ENDOFFILE)
;	R2	- Destroyed
;	All other registers preserved
;
;	UCB$L_PREV_TM	- Updated
;-

CHECK_EOV:

	SUBSAVE				; Save return address
	SUBL3	UCB$L_PREV_TM(R3),-	; Calculate difference between last 
		UCB$L_RECORD(R3),-(SP)	; tapemark and this one. If consecutive,
	DECL	(SP)+			; then check for logical end-of-volume
	BNEQ	20$			; Branch if not

; Logical end-of-volume detected. Position the tape between the two tapemarks
; by backspacing one record.

	MOVL	#-1,R0			; Prepare to back up one record
	SUBPUSH	R1			; Save R1
	BSBW	SKIP_RECORD		; Position tape between filemarks
	SUBPOP	R1			; Restore R1
	CMPL	R0,#SS$_ENDOFFILE	; EOF status?
	BNEQ	30$			; Branch if not, serious problem exists
	MOVL	#SS$_ENDOFVOLUME,R0	; Return end of volume status

20$:	MOVL	UCB$L_RECORD(R3),-	; Update previous tapemark field in
		UCB$L_PREV_TM(R3)	; UCB with current record number
30$:	SUBRETURN			; Return to caller

	.SBTTL	MK_WAIT		- Stall for the specified number of seconds
;+
; MK_WAIT
;
; This routine is used by the MK_WAIT macro to stall a thread for a specified 
; number of seconds. It sets the timeout bit in the UCB and relies on the 
; device timeout mechanism to resume the stalled thread.
;
; INPUTS:
;
;	IPL	- 31
;	R5	- UCB address
;	(SP)	- Return address
;	4(SP)	- Wait time in seconds
;	8(SP)	- Saved IPL
;
; OUPUTS:
;
;	Stack	- Return address, wait time, IPL removed
;	Control returns to caller's caller
;	All registers preserved
;
;	NOTE: The use of the MK_WAIT macro destroyes R0-R3
;-

MK_WAIT:

	MOVQ	R3,UCB$L_FR3(R5)	; Save R3 and R4 in fork block
	ADDL3	#2,(SP)+,UCB$L_FPC(R5)	; Save return address in fork block
	BISW	#UCB$M_TIM,UCB$W_STS(R5); Set timer expected bit
	ADDL3	(SP)+,G^EXE$GL_ABSTIM,-	; Set up timeout time in UCB
		UCB$L_DUETIM(R5)	;
	BICW	#UCB$M_TIMOUT,-		; Clear timer expired bit
		UCB$W_STS(R5)		;
	ENBINT				; Reenable interrupts
	RSB				; Return to caller's caller

	.SBTTL	SEND_COMMAND	- Send a SCSI command
;+
; SEND_COMMAND
;
; This routines sends a command to the SCSI device. It returns any failing
; port status to the caller. If the port status is success, it checks the
; SCSI status byte. If a check condition status is returned, a request 
; sense command is sent to the target and the sense key is translated into a 
; VMS status code, which is returned as status.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

SEND_COMMAND:

	SUBSAVE				; Save return address
	MOVB	#BUSY_RETRY_CNT,-	; Initialize busy retry count
		UCB$B_BUSY_RETRY(R3)	;
	BICB	#UCB$M_ADDNL_INFO,-	; Indicate additional info is not
		UCB$L_MK_FLAGS(R3)	; valid
	
1$:	SPI$SEND_COMMAND		; Send the SCSI command
	BLBC	R0,20$			; Branch on error
	ASSUME	SCSI$C_GOOD_STATUS EQ 0
	MOVZBL	@SCDRP$L_STS_PTR(R5),R1	; Get SCSI status byte
	BICB	#SCSI$M_STAT_MASK,R1	; Clear reserved, vendor unique bits
	BNEQ	30$			; Branch if bad status

	.IF DEFINED MULTI_VOLUME_TEST	; Code for testing multi-volume savesets
	MOVL	UCB$L_IRP(R3),R1	; Get IRP address
	BEQL	6$			; Branch if not available
	EXTZV	#IRP$V_FCODE,-		; Extract function code
		#IRP$S_FCODE,-		;
		IRP$W_FUNC(R1),R1	;
	CMPL	R1,#IO$_WRITEPBLK	; Write function?
	BEQL	5$			; Branch if so
	CMPL	R1,#IO$_WRITEOF		; Write tapemark?
	BNEQ	6$			; Branch if not
5$:	CMPL	UCB$L_RECORD(R3),#^X50	; Beyond artificial end of tape mark?
	BGEQUW	40$			; Branch if so
6$:
	.ENDC

10$:	SUBRETURN			; Return to caller

; The port returned bad status from SPI$SEND_CMD. Log an error and return to
; the caller.

20$:	CMPL	R0,#SS$_ABORT		; Aborted command?
	BEQL	10$			; Branch if so, don't log an error
	LOG_ERROR -			; Log a send command error
		TYPE=SEND_CMD_ERROR,-	;
		VMS_STATUS=R0,-		;
		UCB=R3			;
	BRB	10$			; Use common exit

; A bad SCSI status code was returned. If the code is a check condition, then 
; send a request sense command to the device. Otherwise, the status code is 
; something unexpected. Log an error and return SS$_MEDOFL status.

30$:	CMPB	#SCSI$C_CHK_CONDITION,R1; Check condition status?
	BEQL	40$			; Branch if so
	CMPB	#SCSI$C_BUSY,R1		; Device busy?
	BNEQ	35$			; Branch if not
	BBC	#UCB$V_WAITRDY_INPROG,-	; Branch if waiting for unit ready is
		UCB$L_MK_FLAGS(R3),35$	; not in progress
	MOVL	#SS$_DEVOFFLINE,R0	; Return device off line status
	BRW	10$			; Return to caller
	
35$:	DECB	UCB$B_BUSY_RETRY(R3)	; Decrement busy retry count
	BLEQ	37$			; Branch if count expired
	MK_WAIT	#1,UCB=R3		; Wait a second
	BRW	1$			; Go send the command again

37$:	LOG_ERROR -			; Log a send command error
		TYPE=SEND_CMD_ERROR,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
	MOVL	#SS$_MEDOFL,R0		; Return a generic status code
	BRW	10$			; Return to caller

; A check condition status code was returned. Save the original SCDRP address
; allocate a second one and send a request sense command. If the request 
; sense succeeds, translate the sense key to a VMS status code and return that
; as the status code for the original command.

40$:	TSTB    UCB$B_8MM_CHK(R3)       ; If an 8mm COPY check flag is set,
     	BEQL    45$                     ; just Cleanup and Deallocate...
;        BSBW    CLEANUP_CMD             ; Clean up the request sense command
;        BSBW    DEALLOC_SCDRP           ; Deallocate the request sense SCDRP
        BRW     10$                     ; Return to caller
45$:	CLRB	UCB$B_SENSE_KEY(R3)	; Initialize saved sense key field
	MOVL	R5,UCB$L_SCDRP_SAV1(R3)	; Save original SCDRP address
	BSBW	ALLOC_SCDRP		; Allocate an additional SCDRP
	BSBW	REQUEST_SENSE		; Send a request sense command 
	BLBC	R0,50$			; Branch on error
	BSBW	LOG_EXTND_SENSE		; Log an extended sense error
	BSBW	SAVE_ADDNL_INFO		; Save any valid additional information
					; from the extended sense data
	BSBW	TRANS_SENSE_KEY		; Translate the extended sense key to 
					; a VMS status code in R0
50$:	BSBW	CLEANUP_CMD		; Clean up the request sense command
	BSBW	DEALLOC_SCDRP		; Deallocate the request sense SCDRP
	MOVL	UCB$L_SCDRP_SAV1(R3),R5	; Restore original SCDRP address
	MOVL	R5,UCB$L_SCDRP(R3)	; Copy it to the UCB
	BRW	10$			; Return to caller

	.SBTTL	LOG_EXTND_SENSE	- Log an extended sense data error
;+
; LOG_EXTND_SENSE
;
; This routine logs an extended sense data error.
;
; Certain errors are suppressed including:
;
; - UNIT ATTENTIONS (we can't tell the difference between device resets
;   and media changes, so we log nothing)
;
; - A NOT READY sense key.
;
; Before logging the error, play a trick with the two SCDRPs currently in use.
; Since we're really interested in logging the contents of the original command,
; and not the request sense command, copy the address of the original command
; buffer into the SCDRP for the request sense command. This causes the contents
; of the original command (a read or write, for example) to be logged along 
; with the contents of the request sense data returned from the request sense 
; command.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
;	UCB$L_SCDRP_SAV1(R3)	- Address of SCDRP for original command
;
; OUTPUTS:
;
;	R0-R2	- Destroyed
;	All other registers preserved
;-

LOG_EXTND_SENSE:

	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of extended sense data
	BITB	#SCSI_XS$M_ILI,-	; Illegal length indicator?
		SCSI_XS$B_KEY(R0)	;
	BNEQW	10$			; Branch if so
	BITB	#<SCSI_XS$M_EOF!-	; Filemark?
		 SCSI_XS$M_EOM>,-	; End of medium?
		SCSI_XS$B_KEY(R0)	;
	BEQL	5$			; Branch if not

; The following sequence checks that a skip record operation which encounters
; a tape mark, BOT, or EOT returns valid additional info (residue) from the
; subsequent request sense command. If not, the driver has no way of keeping
; track of the UCB$L_RECORD value.

        BBS     #UCB$V_SKIP_INPROG,-    ; Branch if skip command in
                UCB$L_MK_FLAGS(R3),4$   ; progress
        BRW     10$                     ; Branch for no skip record in progress
4$:     ASSUME  SCSI_XS$V_ADDNL_VALID EQ 7
        TSTB    SCSI_XS$B_ERR_CODE(R0)  ; Valid additional info?
        BGEQ    7$                      ; Branch if not
        BRW     10$                     ; Otherwise, leave without logging an error

5$:     EXTZV   #SCSI_XS$V_KEY,-        ; Get the extended sense key
                #SCSI_XS$S_KEY,-        ;
                SCSI_XS$B_KEY(R0),R1    ;
        BNEQ    6$                      ; Branch if sense data
        BRW     10$                     ; Branch if no sense data
6$:     CMPB    R1,-                    ; Unit attention sense key?
                #SCSI$C_UNIT_ATTENTION  ;
        BNEQ    66$                     ; Branch if not unit attention
        BRW     10$                     ; Branch if so, don't log an error
66$:    CMPB    R1,#SCSI$C_NOT_READY    ; Device not ready sense key?
        BNEQ    7$                      ; Branch if not 'device not ready'
        BRW     10$                     ; Branch if so, don't log an error

7$:	PUSHL	SCDRP$L_CMD_PTR(R5)	; Save address of request sense cmd
	MOVL	UCB$L_SCDRP_SAV1(R3),R0	; Get original command's SCDRP address
	MOVL	SCDRP$L_CMD_PTR(R0),-	; Copy address of original command to
		SCDRP$L_CMD_PTR(R5)	; request sense's SCDRP
	MOVB	#SCSI$C_CHK_CONDITION,-	; Original command failed with request
		@SCDRP$L_STS_PTR(R5)	; sense status
;
; If this is an 8mm device we may have arrived here as a result of the
; SPACE REC REV - WRITE SCSI command sequence which $COPY from BOT processing
; generates. That command sequence is illegal on the 8mm and results
; in a CHECK CONDITION - ILLEGAL REQUEST error response from the device.
; DO_8MM_COPY_CHK will subsequently generate an alternate SCSI command sequence
; that will recover from the error and accommodate the 8mm behavior.
;
        IFNOT_8MM      700$             ; Br if not 8MM
        CMPB    R1,-                    ; Illegal Request sense key?
                #SCSI$C_ILLEGAL_REQUEST
        BNEQ    700$                    ; Br if not Illegal Request
        BBC     #DEV$V_MNT,-            ; Br if not mounted
                UCB$L_DEVCHAR(R3),8$
        BBS     #DEV$V_FOR,-            ; Br if mounted foreign
                UCB$L_DEVCHAR(R3),8$
        MOVL    UCB$L_IRP(R3),R1        ; Restore IRP address
        EXTZV   #IRP$V_FCODE,-          ; Extract I/O function code
                #IRP$S_FCODE,-          ;
                IRP$W_FUNC(R1),R1       ;
        CMPB    #IO$_WRITEPBLK,R1       ; Write in progress?
        BNEQ    8$                      ; Br if not
        CMPL    #1,UCB$L_RECORD(R3)     ; Br if the record # is not consistent
        BNEQ    8$                      ; with the $COPY from BOT error
        MOVB    #1,UCB$B_8MM_CHK(R3)   	; Set COPY check in progress flag
        BRB     9$                      ; Go to return
700$:   CMPB    R1,#SCSI$C_BLANK_CHECK  ; Check for blank-check error and BOT,
        BNEQ    8$
        BBS     #UCB$V_SPACEFILE_INPROG,- ; Suppress if SPACE FILEMARKS just
                UCB$L_MK_FLAGS(R3),9$   ; hit logical EOV
        TSTL    UCB$L_RECORD(R3)        ; Supress logging error since it is -
        BEQL    9$                      ; new tape.    mcy  8/28/92

; Don't log certain Illegal Request errors that can result from sending
; e.g. a READ POSITION commnand to a target that does not support it.

8$:     CMPL    R1,#SCSI$C_ILLEGAL_REQUEST      ; Illegal command sense key?
        BNEQ    36$                             ; No, so log it
        BBS    #UCB$V_READPOSCHK_IN_PROG,-      ; Is READ POSITION check in progress?
                UCB$L_MK_FLAGS(R3),9$           ; If so, don't log error

36$:	LOG_ERROR -			; Log an extended sense data error
		TYPE=EXTND_SENSE_DATA,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
9$:	POPL	SCDRP$L_CMD_PTR(R5)	; Restore address of request sense cmd
10$:	RSB				; Return to caller

	.SBTTL	TRANS_SENSE_KEY	- Translate extended sense key to VMS status code
;+
; TRANS_SENSE_KEY
;
; This routine translates an extended sense key to a VMS status code using
; SENSE_KEY_TABLE. If the sense key is not found in the table (indicating an
; invalid sense key), then a generic VMS status code (SS$_MEDOFL) is returned.
;
; The filemark and EOM bits generate status codes of SS$_ENDOFFILE and
; SS$_ENDOFTAPE respectively.
;
; Certain situations cause an additional translation including:
;
; - If the sense key was NOT_READY and the wait for unit ready flag is set in
;   the UCB, then this is an indication that the device is in the process of
;   becoming ready. Return a DEVOFFLINE status to allow the WAIT_UNIT_READY
;   routine to continue to poll for unit ready. Otherwise, change the
;   status to SS$_MEDOFL to cause mount verification to occur for this device.
;
; - Any medium error which does not have an additional sense code of 
;   non-recoverable ECC error generates SS$_DRVERR status. This is considered 
;   "more fatal" as it could cause us to loose tape position.
;
; INPUTS:
;
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- VMS status code
;	R1,R2	- Destroyed
;	All other registers perserved
;-

TRANS_SENSE_KEY:

	MOVL	SCDRP$L_SVA_USER(R5),R2	; Get address of request sense data

	.IF DEFINED MULTI_VOLUME_TEST	; Code for testing multi-volume savesets
	MOVL	UCB$L_IRP(R3),R1	; Get IRP address
	BEQL	3$			; Branch if not available
	EXTZV	#IRP$V_FCODE,-		; Extract function code
		#IRP$S_FCODE,-		;
		IRP$W_FUNC(R1),R1	;
	CMPL	R1,#IO$_WRITEPBLK	; Write function?
	BEQL	2$			; Branch if so
	CMPL	R1,#IO$_WRITEOF		; Write tapemark?
	BNEQ	3$			; Branch if not
2$:	CMPL	UCB$L_RECORD(R3),#^X50	; Beyond artificial end of tape mark?
	BLSSU	3$			; Branch if not
	BISB	#^X40,2(R2)		; Simulate end of tape
3$:
	.ENDC

; If the key is recorvered error or no sense, then go ahead and look at the
; EOF, EOM, and ILI flags in the extended sense data. Any other type of sense 
; key takes precedence over these flags.

	EXTZV	#SCSI_XS$V_KEY,-	; Get the extended sense key
		#SCSI_XS$S_KEY,-	;
		SCSI_XS$B_KEY(R2),R1	;
	ASSUME	SCSI$C_NO_SENSE EQ 0
	BEQL	4$			; Branch if no sense
	CMPB	R1,-			; Recovered error?
		#SCSI$C_RECOVERED_ERROR	; 
	BEQL	4$			; Branch if so

        CMPB    R1,#SCSI$C_BLANK_CHECK  ; End of data?
        BNEQ    35$                     ; No, continue
        BBC     #UCB$V_SPACEFILE_INPROG,- ; SPACE FILEMARKS in progress?
                UCB$L_MK_FLAGS(R3),35$  ;
        MOVL	#SS$_ENDOFVOLUME,R0	; Yes, just hit EOV
	BISL	#MT$M_EOF,-		; and is at a tapemark
		UCB$L_DEVDEPEND(R3)
	BRW	80$


; Workaround for a bug in the TK50: if this is a medium error sense key and the 
; EOM bit is set, then this indicates we're at BOT or EOT and the medium error
; sense key can be ignored. 

35$:	CMPB	R1,-			; Medium error?
		#SCSI$C_MEDIUM_ERROR	; 
	BNEQ	6$			; Branch if not
	IFNOT_TK50 6$			; Branch if not a TK50

4$:	BITB	#SCSI_XS$M_ILI,-	; Illegal length indicator?
		SCSI_XS$B_KEY(R2)	;
	BNEQW	90$			; Branch if so
	BITB	#SCSI_XS$M_EOF,-	; Filemark?
		SCSI_XS$B_KEY(R2)	;
	BNEQW	50$			; Branch if so
	BITB	#SCSI_XS$M_EOM,-	; End of medium?
		SCSI_XS$B_KEY(R2)	;
	BNEQW	60$			; Branch if so

; If this is a UNIT ATTENTION sense key, the volume valid bit is NOT set
; in the UCB, and the "wait for unit ready" bit is set, then return success. 
; This situation can occur for multi-volume tape backups when one tape is
; unloaded and the next is loaded. We'll eventually need a PACACK to make the 
; drive usable.

6$:	CMPB	R1,-			; Unit attention sense key?
		#SCSI$C_UNIT_ATTENTION 	;
	BNEQ	5$			; Branch if not
    	BBS	#UCB$V_VALID,-		; Branch if volume valid bit is set
		UCB$W_STS(R3),5$	; in the UCB
	BBC	#UCB$V_WAITRDY_INPROG,-	; Branch if NOT waiting for unit ready
		UCB$L_MK_FLAGS(R3),5$	;
	MOVZWL	#SS$_NORMAL,R0		; Set success status
	BRB	40$			; Use common exit path

5$:	MOVB	R1,UCB$B_SENSE_KEY(R3)	; Save sense key in the UCB
	MOVAL	SENSE_KEY_TABLE,R0	; Get address of sense key to VMS status
					; code translation table
10$:	CMPB	(R0),#^XFF		; End of table?
	BEQL	24$			; Branch of so
	CMPB	(R0)+,R1		; Sense keys match?
	BEQL	20$			; Branch if so
	ADDL	#2,R0			; Skip VMS error code
	BRB	10$			; Try next sense key
20$:	MOVZWL	(R0),R0			; Pick up VMS error code
	BRB	25$			; Join common path

24$:	MOVL	#SS$_DRVERR,R0		; Status code for unknown sense key

; A status code of SS$_DRVERR indicates a serious error occurred. If this is
; the case, set the tape position lost.

25$:	CMPL	R0,#SS$_DRVERR		; Serious error?
	BNEQ	26$			; Branch if not
	BSBW	SET_TAPE_LOST		; Set tape lost

; The following code allows the routine WAIT_UNIT_READY to poll for unit
; ready. If a NOT_READY sense key is returned by the drive, and the wait for
; unit ready flas is set, then return DEVOFFLINE status to notify the caller 
; that the device is still not ready, Otherwise, return MEDOFL status to 
; cause mount verification to kick in.

26$:	CMPL	R0,#SS$_DEVOFFLINE	; Device offline status?
	BNEQ	40$			; Branch if not
	BBS	#UCB$V_WAITRDY_INPROG,-	; Branch if waiting for unit ready is
		UCB$L_MK_FLAGS(R3),40$	; set
30$:	MOVZWL	#SS$_MEDOFL,R0		; Otherwise, set medium offline status
40$:	RSB				; Return to caller

; EOF set. This indicates a filemark was encountered during the previous tape
; motion command. Set SS$_ENDOFFILE status and set the EOF bit in the device
; dependent field.

50$: 	MOVZWL	#SS$_ENDOFFILE,R0	; Assume filemark (end of file)
	BISW	#<MT$M_EOF@-16>,-	; Set end of file bit in UCB
 		UCB$L_DEVDEPEND+2(R3)	;
	BRB	80$			; Perform sanity check on additional
					; info (if skip record in progress)

; EOM set. This indicates that the tape reached one end or the other, based
; on the direction of motion. In the case of reverse motion, BOT is set. 
; Otherwise, EOT is set. In either case, ENDOFTAPE status is returned.

60$:	MOVW	#<MT$M_BOT@-16>,R1	; Assume reverse motion
	BBS	#UCB$V_REV_SKIP,-	; Branch if reverse motion in 
		UCB$L_MK_FLAGS(R3),70$	; progress
	MOVW	#<MT$M_EOT@-16>,R1	; Prepare to set end of tape
70$:	BISW	R1,UCB$L_DEVDEPEND+2(R3); Set BOT/EOT based on current direction
	MOVL	#SS$_ENDOFTAPE,R0	; Set end of tape status

; If the original command was a skip record, and no valid additional info was
; returned, then set a fatal error status and mark the tape position as lost,
; as we need the additional info to keep track of our position on the tape.

80$:	BBC	#UCB$V_SKIP_INPROG,-	; Branch if skip command not in 
		UCB$L_MK_FLAGS(R3),40$	; progress
	BBS	#UCB$V_ADDNL_INFO,-	; Branch if valid additional info
		UCB$L_MK_FLAGS(R3),40$	; returned
	IF_TSZ05	40$		; For TSZ05 and TSZ07 devices, skip rev
	IF_TSZ07	40$		; doesn't return "addl info" flag set.
	MOVL	#SS$_DRVERR,R0		; Otherwise, change to fatal error
	BSBW	SET_TAPE_LOST		; Set tape position lost
	BRB	40$			; Use common exit

; ILI set. This indicates that the requested record length did not match the
; actual record length on tape. If the record on tape was shorter than the
; requested length, ignore the ILI bit as utility doing the read does not
; necessarily know ahead of time the length of the record on tape. If the
; record on tape was longer than the requested length, return a data overrun
; status code. The data overrun condition is detected by the ILI bit being
; set and a TRANS_CNT equal to BCNT. It's assumed that the drive would have
; returned more data if the host had requested it.

90$:	MOVZWL	#SS$_NORMAL,R0		; Assume no data overrun
	MOVL	UCB$L_SCDRP_SAV1(R3),R1	; Get SCDRP of original read command
	CMPW	SCDRP$L_TRANS_CNT(R1),-	; Data overrun condition?
		SCDRP$L_BCNT(R1)	;
	BLSSU	40$			; Branch if not
	MOVZWL	#SS$_DATAOVERUN,R0	; Return data overrun status
	BRB	40$

	.SBTTL	SAVE_ADDNL_INFO	- Save additional extended sense information
;+
; SAVE_ADDNL_INFO
;
; This routine is called whenever valid extended sense data is returned by a
; target to obtain any addition data. In the case of a skip record command, 
; this information is used later to determine the actual number of records 
; skipped. 
;
; INPUTS:
;
; 	R3	- UCB address
;	R5	- SCDRP address associated with request sense command
;
; OUTPUTS:
;
;	R0,R1	- Destroyed
;
;	UCB$L_ADDNL_INFO	- Additional info field from the extended
;				  sense data or -1 if no valid data.
;-

SAVE_ADDNL_INFO:

	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of request sense data
	MOVAL	UCB$L_ADDNL_INFO(R3),R1	; Get address of additional info field
	CLRL	(R1)			; Assume additonal info not valid
	ASSUME	SCSI_XS$V_ADDNL_VALID EQ 7
	TSTB	SCSI_XS$B_ERR_CODE(R0)	; Valid additional info?
	BGEQ	30$			; Branch if not
	BISB	#UCB$M_ADDNL_INFO,-	; Indicate additional info is valid
		UCB$L_MK_FLAGS(R3)	;
	MOVAL	SCSI_XS$B_ADDNL_INFO+4(R0),R0 ; Get address just beyond 
					; additonal info field
	.REPT	4
	MOVB	-(R0),(R1)+		; Copy a byte of additional info from 
					; the extended sense data to the UCB. 
					; (Note that bytes are reversed in the 
					; extended sense data).
	.ENDR
30$:	RSB

	.SBTTL	ALLOC_SCDRP	- Allocate an SCDRP
;+
; ALLOC_SCDRP
;
; This routine allocates an SCDRP by attempting to remove one from the queue 
; in the UCB. If the queue is empty (which should never happen), then bugcheck.
; The entire SCDRP is zero'ed and various fields are initialized.
;
; INPUTS:
;
;	R3	- UCB address
;	
;	UCB$L_SCDRPQ_FL	- Queue of SCDRPs
;
; OUTPUTS:
;
;	R5	- SCDRP address
;	All other register preserved
;
;	SCDRP$L_UCB	- UCB address
;	SCDRP$L_CDT	- CDT address
;	SCDRP$L_SCSI_FLAGS - Initialized
;	SCDRP$L_CL_STK_PTR - Initialized
;-

ALLOC_SCDRP:

	REMQUE	@UCB$L_SCDRPQ_FL(R3),R5	; Remove an SCDRP from the queue
	BVS	10$			; Branch if queue was empty
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save regs
	MOVC5	#0,.,#0,-		; Initialize the SCDRP
		#SCDRP$C_LENGTH-12,-	;
		12(R5)			;
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Restore regs
	MOVL	R5,UCB$L_SCDRP(R3)	; Save SCDRP address in UCB
	MOVL	R3,SCDRP$L_UCB(R5)	; Save UCB address in SCDRP
	MOVB	UCB$B_FLCK(R3),-	; Copy the fork lock field from the
		SCDRP$B_FLCK(R5)	; UCB to the SCDRP
	MOVL	UCB$L_SCDT(R3),-	; Save CDT address in SCDRP
		SCDRP$L_CDT(R5)		; 
	MOVAL	SCDRP$L_SCSI_STK-4(R5),-; Initialize the SCDRP stack pointer
		SCDRP$L_SCSI_STK_PTR(R5);
	RSB

10$:	BUG_CHECK INCONSTATE,FATAL	; Queue should never have been empty

	.SBTTL	DEALLOC_SCDRP	- Deallocate an SCDRP
;+
; DEALLOC_SCDRP
;
; This routine deallocates an SCDRP by returning it the the queue in the
; UCB. A sanity check is made to ensure that any map registers for this
; command have been deallocated.
;
; INPUTS:
;
;	R3	- UCB address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R3	- UCB address
;	All other register preserved
;
;	UCB$L_SCDRP	- Cleared to indicate no active SCDRP
;-

DEALLOC_SCDRP:

	TSTW	SCDRP$W_MAPREG(R5)	; Are we hanging on to any mapping regs?
	BNEQ	10$			; Branch if so
	TSTW	SCDRP$W_NUMREG(R5)	; Are we hanging on to any mapping regs?
	BNEQ	10$			; Branch if so
	INSQUE	SCDRP$L_FQFL(R5),-	; Insert SCDRP in UCB queue
		UCB$L_SCDRPQ_FL(R3)	;
	CLRL	UCB$L_SCDRP(R3)		; No active SCDRP for this UCB
	RSB

10$:	BUG_CHECK INCONSTATE,FATAL	; Should never deallocate an SCDRP 
					; without first unmapping buffer
	.DSABL	LSB

	.SBTTL	GET_DEVICE_TYPE	- Determine device type from inquiry data
;+
; GET_DEVICE_TYPE
;
; This routine translates the product ID field from the INQUIRY data to
; a VMS device type. In addition, it fills in the media ID field in the UCB.
; The TZK50 and TZ30 are handled specially, since these devices have no product
; ID field in the inquiry data. For the TZK50 and TZ30, there is exactly 5
; bytes if inquiry data, and the device qualifier fields contain 50 (hex) and
; 30 (hex), respectively.
; 
; INPUTS:
;
;	R0	- Address of INQUIRY data
;	R3	- UCB address
;
; OUTPUTS:
;
;	R0-R2	- Destroyed
;	All other registers preserved
;-

GET_DEVICE_TYPE:

	CMPL	SCDRP$L_TRANS_CNT(R5),#5; 5 bytes of inquiry data?
	BNEQ	20$			; Branch if not, can't be a TK50 or TZ30
	EXTZV	#SCSI_INQ$V_DEVQUAL,-	; Get device type qualifier
		#SCSI_INQ$S_DEVQUAL,-	;
		SCSI_INQ$B_DEVQUAL(R0),-;
		R1			;

; Check for device type of TZ30

	CMPB	R1,#^X30		; TZ30?
	BNEQ	10$			; Branch if not
	MOVAL	TZ30_DEV_TYPE,R1	; Get table entry for TZ30
	BRB	60$
	
; Check for device type of TZK50

10$:	CMPB	R1,#^X50		; TZK50?
	BNEQ	20$			; Branch if not
	MOVAL	TK50_DEV_TYPE,R1	; Get table entry for TK50
	BRB	60$			; Branch to common code

; Device is not a TZ30 or TK50. Scan the device type table to see if we have 
; a matching produce ID field. If not, then treat this device as a generic SCSI
; tape.

20$:	CMPL	SCDRP$L_TRANS_CNT(R5),-	; Enough inquiry data to have a valid
		#24			; produce ID field?
	BLSS	50$			; Branch if not
	MOVAL	SCSI_DEVICE_TABLE,R1	; Get SCSI device table
30$:	CMPL	1(R1),16(R0)		; Matching product ID (low LW)?
	BNEQ	40$			; Branch if not
	CMPL	5(R1),20(R0)		; Matching product ID (high LW)?
	BEQL	60$			; Branch if so
40$:	ADDL	#<SDTE_END-SDTE_BEGIN>,R1 ; Advance to next entry in table 
	TSTB	(R1)			; End of table
	BNEQ	30$			; Branch if not

; Device is not a TZ30 or TK50, nor was a matching product ID field found in
; the device type tape. Treat the device as a generic SCSI tape.

50$:	MOVAL	GENERIC_DEV_TYPE,R1	; Get table entry for generic tape
60$:	MOVB	(R1),UCB$B_DEVTYPE(R3)	; Save VMS device type
	MOVL	9(R1),UCB$L_MIN_REV(R3)	; Save minimum revision level
        BBC     #DEV$V_DTN,-            ; If DTN bit is set, handle DDR
                UCB$L_DEVCHAR2(R3),65$  ;  mechanism
        PUSHR   #^M<R0,R1,R5>           ; Save registers across JSB
        MOVL    R3,R5                   ; Get UCB in R5 for IOC$
        JSB     G^IOC$REMOVE_DEVICE_TYPE ; Remove our reference to the DTN
        POPR    #^M<R0,R1,R5>           ; Restore registers
65$:	MOVL	13(R1),-		; Save media ID field
		UCB$L_MEDIA_ID(R3)	; 
	MOVAL	17(R1),-		; Save address of vendor-unique mode
		UCB$L_MSEL_INFO(R3)	; select info

       	BBS     #MT2$V_COMP_ENA, -      ; Check current compaction enabled
                UCB$L_DEVDEPND2(R3), 64$;
 
	INSV	27(R1),-		; Save density information
		#MT$V_DENSITY,-		;
		#MT$S_DENSITY,-		;
		UCB$L_DEVDEPEND(R3)	;

64$:    BLBC    28(R1),67$              ; Check for loader device - mcy/rcl 9/18
        BISL2   #DEV$M_LDR,-            ; Set loader bit if device is loader	
                UCB$L_DEVCHAR2(R3)      ;
  
67$:	BICL	#UCB$M_DEVICE_IS_8MM,-	; Assume not an 8mm device
		UCB$L_MK_FLAGS(R3)
	CMPL	#^A/EXB-/, 16(R0)	; Is it an EXABYTE tape (8mm)?
	BEQL	68$               	; Branch if not
	CMPL	#^A/TKZ0/, 16(R0)	; Is it a TKZ09 tape (8mm)?
	BNEQ	70$               	; Branch if not (1st 4 bytes)
	CMPL	#^A/9   /, 20(R0)	; Check rest of TZK09 string 
	BNEQ	70$               	; Branch if not, else set 8mm flag
68$:	BISL	#UCB$M_DEVICE_IS_8MM,-	; Set "it's an 8mm device" flag
		UCB$L_MK_FLAGS(R3)
70$:	CMPB    #DT$_GENERIC_MK,- 	; Are we in DDR mode?
                UCB$B_DEVTYPE(R3)       ;
        BEQL    75$                     ; Nope
73$:	BRW	80$

75$:    CMPL    SCDRP$L_TRANS_CNT(R5),- ; A proper INQUIRY response is at least
                #32                     ;   32 bytes. Do we have that?
        BLSS    73$                     ; Nope, can't do dynamic naming

;
; At this point:
;	R0 = Address of the inquiry data
;	R3 = UCB
;
; Register usage:
;	R0 = input name pointer
;	R1 = output name pointer
;	R2 = original SP
;	R4 = end of field pointer
;	R5 = "last character was a blank" flag
;	R6 = scratch
;
;	First, process the Vendor ID, compressing multiple blanks into one.
;	
	
	PUSHR	#^M<R4,R5,R6>		; Save registers

	MOVL	SP,R2			; Save stack pointer
	CLRL	R5			; Initialize "last was blank" flag
	BICL	#7,SP			; Quad align the stack
	SUBL	#32,SP			; Allocate space for name and such
	MOVL	SP,R1			; Setup pointer into name buffer
	MOVAB	8(R0),R0		; Point to vendor ID
	MOVAB	8(R0),R4		; Vendor ID is 8 bytes long

301$:	CMPL	R0,R4			; End of field?
	BEQL	320$			; Yes, continue with product ID
	CMPB	#^X20,(R0)+		; Is character a blank
	BNEQ	302$			; No, clear flag and copy
	BLBS	R5,301$			; If previous char blank, skip this one
	MOVL	#1,R5			; Otherwise, set "last was blank" flag
	MOVB	#^X20,(R1)+		;   and store a blank
	BRB	301$			; Back for next character
302$:	CLRL	R5			; Clear "last was blank" flag
	MOVB	-1(R0),(R1)+		;   and copy the character
	BRB	301$			; Back for next character

;
;	Now, setup to process the product ID.  Again, compress multiple blanks
;	into one.  Also, the copyright symbol "(C)" or "(c)" is treated as a
;	name terminator.
;	
320$:	MOVAB	16(R0),R4		; Product ID is 16 bytes long
	BLBS	R5,321$			; If trailing blank on Vendor, continue
	MOVL	#1,R5			; Otherwise, insert a blank between
	MOVB	#^X20,(R1)+		;   vendor and product IDs
321$:	CMPL	R0,R4			; See if we have a full type name
	BEQL	330$			; Yes, we're done
	CMPB	#^X20,(R0)+		; Is the next character a blank?
	BNEQ	322$			; No, go check for copyright
	BLBS	R5,321$			; If previous char blank, skip this one
	MOVL	#1,R5			; Otherwise, set "last was blank" flag
	MOVB	#^X20,(R1)+		;   and store a blank
	BRB	321$			; Back for next character

;
;	Check for copyright...
;	
322$:	CMPB	#^A/(/,-1(R0)		; Was character a "("
	BNEQ	323$			; Nope, go copy it
	BICB3	#^X20,(R0),R6		; Convert next char to upper case
	CMPB	#^A/C/,R6		; See if it's a "C"
	BNEQ	323$			; Nope, just copy data
	CMPB	#^A/)/,1(R0)		; Look for closing paren
	BEQL	330$			; Found copyright, so we're done

323$:	CLRL	R5			; Clear "last was blank" flag
	MOVB	-1(R0),(R1)+		;   and copy the character
	BRB	321$			; Back for next character

330$:	SUBL3	SP,R1,R0		; Calculate name length
	SUBL3	R5,R0,R1		; Remove any trailing blank
					; name length
        MOVL    SP,R0  	                ; Point to INQUIRY product name

	MOVL	R3, R5			; UCB in R5 for IOC$ADD_DEVICE_TYPE
        JSB     G^IOC$ADD_DEVICE_TYPE   ; Add us to the DTN list
	MOVL	R2,SP			; Clean the stack
	POPR	#^M<R4,R5,R6>		; Save registers

80$:	RSB				; Return to caller

	.SBTTL	CHECK_REV_LEVEL	- Check for an out-of-rev device
;+
; CHECK_REV_LEVEL
;
; This routine checks the revision level returned in the INQUIRY or RECEIVE
; DIAGNOSTIC data with the minimum revision level for that device. If the 
; device's revision level is below the minimum allowed, an invalid inquiry 
; data error is logged.
;
; Note: since the revision field in the inquiry data is a 4 byte ascii string,
; the low-order byte is the most significant. Thus, the bytes must be compared 
; from low byte to high byte, and a CMPL can not be used to perform the 
; comparison.
;
; INPUTS:
;
;	R3	- UCB address
;	UCB$L_HW_REV  - Revision level
;	UCB$L_MIN_REV - Minimum revision level
;
; OUTPUTS:
;
;	R0	- Status (LBS if rev level OK)
;	R1,R2	- Destroyed
;	All other registers preserved
;	An error is logged of the device is out-of-rev
;-

CHECK_REV_LEVEL:

	CLRL	R0			; Assume out of rev
	MOVAL	UCB$L_HW_REV(R3),R1	; Get addr of actual revision level
	MOVAL	UCB$L_MIN_REV(R3),R2	; Get addr of minimum revision level
	.REPT	4
	CMPB	(R1)+,(R2)+		; Compare a byte of revision level
	BLSSU	20$			; Branch if out of rev
	BGTRU	5$			; Branch if rev OK
	.ENDR

5$:	MOVL	#SS$_NORMAL,R0		; Set success status
10$:	RSB				; Return to caller

20$:	LOG_ERROR -			; Log an invalid inquiry data error
		TYPE=INV_INQUIRY_DATA,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
	BRB	10$			; Use common exit


	.SBTTL	ALLOC_POOL	- Allocate a block of non-paged pool
;+
; ALLOC_POOL
;
; This routine allocates a block of non-paged pool no smaller than the
; size of a fork block (allowing COM$DRVDEALMEM to fork on this block 
; during deallocation). An extra quadword at the top of the block is reserved 
; to save the size field, relieving the caller this responsibility. The caller 
; is presented with the address just beyond the reserved quadword. Although a 
; word would be sufficient for this field, a quadword is used for allignment 
; purposes (some blocks are used as IRPs, which are placed on self-relative 
; queues and require quadword allignment).
;
; If an allocation failure occurs, the thread is stalled and wakes up once a
; a second to retry the allocation.
;
; INPUTS:
;
;	R1	- Size of block to allocate
;	R3	- UCB address
;
; OUTPUTS:
;
;	R0	- Destroyed
;	R1	- Size of block allocated
;	R2	- Address of allocated block
;	-8(R2)	- Length of allocated block (used by DEALLOC_POOL)
;	All other registers perserved
;-

ALLOC_POOL:

	ADDL	#8,R1			; Reserve a quadword to save size
	CMPL	R1,#FKB$C_LENGTH	; Requested size smaller than fork block?
	BGEQ	10$			; Branch if not
	MOVL	#FKB$C_LENGTH,R1	; Use fork block size as minimum
10$:	PUSHL	R1			; Save allocation length
	PUSHL	R3			; Save UCB address
	JSB	G^EXE$ALONONPAGED	; Allocate a block
	POPL	R3			; Restore reg
	BLBC	R0,20$			; Branch if error
	ADDL	#4,SP			; Remove allocation length from stack
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save regs
	MOVC5	#0,.,#0,R1,(R2)		; Initialize the packet
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Save regs
	MOVL	R1,(R2)+		; Save size of block
	ADDL	#4,R2			; Skip a longword
	RSB				; Return to caller

; A pool allocation failure occurred. Come back once a second and retry the
; operation until successful.

20$:	SUBPUSH	(SP)+			; Save allocation length (PUSHL R1 above)
	SUBSAVE				; Save return address
	MK_WAIT	#1,UCB=R3		; Wait a second
	SUBPOP	-(SP)			; Restore return address
	SUBPOP	R1			; Restore allocation length			
	BRW	10$			; Try again	

	.SBTTL	DEALLOC_POOL	- Deallocate a block of non-paged pool
;+
; DEALLOC_POOL
;
; This routine deallocates a block of non-paged pool. The size of the block
; is stored in the reserved quadword at a negative offset from the beginning 
; of the block.
; 
; INPUTS:
;
;	R0	- Address of block to deallocate
;	-8(R0)	- Length of block to deallocate
;
; OUTPUTS:
;
;	R0	- Destroyed
;	All other registers perserved
;-

DEALLOC_POOL:

	PUSHQ	R1			; Save R1,R2
	SUBL	#4,R0			; Skip a longword
	MOVL	-(R0),IRP$W_SIZE(R0)	; Copy size field
	CLRB	IRP$B_TYPE(R0)		; Clear type field (prevents block from
					; being interpreted as shared memory
					; during deallocation)
	JSB	G^EXE$DEANONPAGED	; Deallocate the block
	POPQ	R1			; Restore R1,R2
	RSB

	.SBTTL	SETUP_CMD	- Common setup for all SCSI commands
;+
; SETUP_CMD
;
; This routine performs common setup prior to the sending of a SCSI command. 
; This includes allocating a command buffer, filling in the pointers in the 
; SCDRP to the command and status fields, copying the SCSI command to the command 
; buffer, allocating an S0 "user" buffer if the command requires transferring 
; data to or from the class driver, filling in the SCDRP fields used to map 
; this buffer, and mapping the buffer.
;
; Since this routine calls SPI$ALLOCATE_COMMAND_BUFFER, which can suspend
; the thread, the return PC must be saved in the SCDRP.
;
; INPUTS:
;
;	R2	- Pointer to entry in SCSI_CMD table
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;
;	SCDRP$L_CMD_BUF	- Address of SCSI command buffer
;	SCDRP$L_CMD_PTR	- Address of SCSI command 
;	SCDRP$L_STS_PTR	- Address to save SCSI status byte
;	SCDRP$L_SVA_USER- Address of S0 "user" buffer
;	SCDRP$L_BCNT	- Length of S0 "user" buffer
;	SCDRP$W_BOFF	- Byte offset of S0 "user" buffer
;	SCDRP$L_SVAPTE	- SVAPTE of of S0 "user" buffer
;	IRP$V_FUNC	- SET/CLEAR to indicate READ/WRITE from S0 "user" buffer
;-

SETUP_CMD:

SCSI_CMD_BUF_OVHD = 4 + 4		; 4 bytes to save status byte +
					; 4 bytes for SCSI command length

	SUBSAVE				; Save return address
	MOVZBL	(R2),R1			; Get size SCSI command
	ADDL	#SCSI_CMD_BUF_OVHD,R1	; Add in command buffer overhead
	SUBPUSH	R2			; Save R2
	SPI$ALLOCATE_COMMAND_BUFFER	; Allocate a command buffer
	MOVL	R2,R1			; Copy command buffer address
	SUBPOP	R2			; Restore R2
	MOVB	#^XFF,(R1)		; Initialize status field
	MOVAL	(R1)+,-			; Address to put SCSI status byte
		SCDRP$L_STS_PTR(R5)	;
	MOVL	R1,SCDRP$L_CMD_PTR(R5)	; Save address of SCSI command
	MOVZBL	(R2)+,R0		; Get SCSI command length
	MOVL	R0,(R1)+		; Save length in command buffer

; The PUSHAB and BISB2 instructions are used to fill in the LUN field in the
; command packet. SCSI-2 suggests that this field be left with a zero and 
; that message handshaking between the port driver and target convey LUN
; information. Therefore, these two instructions have been commented out.

;	PUSHAB	1(R1)			; Save address of SCSI command LUN field
	ASHL	#-1,R0,R0		; Change byte count to word count
10$:	MOVW	(R2)+,(R1)+		; Copy a byte of SCSI command
	SOBGTR	R0,10$			; Repeat for entire SCSI command
;	BISB	UCB$B_LUN(R3),@(SP)+	; Fill in SCSI LUN field

	MOVZWL	(R2)+,-			; Set up command-specific timeout value
		SCDRP$L_DISCON_TIMEOUT(R5)
        MOVZWL  #10,-                   ; 10 seconds for -
                SCDRP$L_DMA_TIMEOUT(R5) ; DMA DATA OUT timeout value
 
	CVTWL	(R2),R1			; Get length of send data buffer
	BLSS	20$			; Branch if negative, no system buffer
					; involved, leave SCDRP$L_BCNT unchanged
	BEQL	30$			; Branch if zero length, zero SCDRP$L_BCNT 
	SUBPUSH	R2			; Save R2
	BSBW	ALLOC_POOL		; Allocate a buffer to receive response
	MOVL	R2,R1			; Copy buffer address
	SUBPOP	R2			; Restore R2
	MOVL	R1,SCDRP$L_SVA_USER(R5)	; Save address of allocated buffer
	MOVZWL	(R2)+,SCDRP$L_BCNT(R5)	; Save length of transfer
	CLRL	SCDRP$L_PAD_BCNT(R5)	; No padding required
	BICW3	#^C<^X1FF>,R1,-		; And byte offset within page
		SCDRP$W_BOFF(R5)	;
	INSV	(R2),#IRP$V_FUNC,#1,-	; Set/clear FUNC bit to indicate READ/
		SCDRP$W_STS(R5)		; WRITE function
	PUSHL	R3			; Save R3
	MOVL	SCDRP$L_SVA_USER(R5),R2	; Get user buffer address
	JSB	G^MMG$SVAPTECHK		; Get SVAPTE of allocated system buffer
	MOVL	R3,SCDRP$L_SVAPTE(R5)	; Save SVAPTE in SCDRP
	POPL	R3			; Restore R3
	BISB	#SCDRP$M_S0BUF!-	; This buffer is an S0 "user" buffer
		 SCDRP$M_BUFFER_MAPPED,-; and it has been mapped
		SCDRP$L_SCSI_FLAGS(R5)	; 
	SPI$MAP_BUFFER PRIO=HIGH	; Map the "user" buffer for read access
20$:	MOVZWL	#SS$_NORMAL,R0		; Set success status
	SUBRETURN

30$:	CLRL	SCDRP$L_BCNT(R5)	; No data being transferred
	BRB	20$			; Use common exit

		.SBTTL	CLEANUP_CMD	- Common cleanup for all SCSI commands
;+
; CLEANUP_CMD
;
; This routine performs common cleanup after the sending of a SCSI command 
; including unmapping the user buffer and deallocating the command buffer.
;
; INPUTS:
;
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R2	- Destroyed
;	All other registers preserved
;-

CLEANUP_CMD:

	PUSHR	#^M<R0,R1,R3>		; Save regs
	BBCC	#SCDRP$V_BUFFER_MAPPED,-; Branch if no buffer has been mapped
		SCDRP$L_SCSI_FLAGS(R5),-;
		10$
	SPI$UNMAP_BUFFER		; Unmap the mapped buffer
10$:	BBCC	#SCDRP$V_S0BUF,-	; Branch if this is not an S0 "user"
		SCDRP$L_SCSI_FLAGS(R5),-; buffer
		20$			;
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of S0 user buffer
	CLRL	SCDRP$L_SVA_USER(R5)	; Buffer no longer owned
	BSBW	DEALLOC_POOL		; Deallocate the buffer
20$:	MOVL	SCDRP$L_CMD_BUF(R5),R0	; Get address of command buffer
	SPI$DEALLOCATE_COMMAND_BUFFER	; Deallocate the command buffer
30$:	POPR	#^M<R0,R1,R3>		; Restore regs
	RSB	

	.SBTTL	LOG_ERROR	- Write an entry to the errorlog file
;+
; LOG_ERROR
;
; This routine writes an entry to the errorlog file. If the device is offline,
; no error is logged. This prevents the errorlog file from being filled up while
; the class driver does it its periodic polling of devices that have been set 
; offline. The assumption is that the initial error that caused the device to
; be placed offline has been logged and that subsequent errorlog entries would
; be redundent.
;
; If the device class and/or type fields in the UCB have not been initialized, 
; then fill in "TAPE" and "GENERIC SCSI TAPE" respectively so the packet can be
; properly formatted by ERF. This situation could arise if invalid inquiry data
; is received for the device, preventing these fields from being filled in
; properly.
;
; If no I/O is active for this device (for example, if an error is detected 
; during unit initialization) log a device attention. Otherwise, log a
; device error and then release the errorlog entry. This prevents errors from
; being lost if multiple errors are logged for a single QIO.
;
; Some types of errors are logged just once per system boot. For example, if 
; invalid mode sense data is returned by the target, just one INVALID_SENSE_DATA
; error is logged to prevent filling the errorlog with duplicate packets. The
; DUPLICATE_ERR_MASK table specifies those error types which should be logged
; just once. A bitmask in the UCB records those error types that have been
; logged already.
;
; INPUTS:
;
;	R5	- UCB address
;	R7	- Error type
;	R8	- VMS status code
;
; OUTPUTS:
;
;	All registers preserved
;-

DUPLICATE_ERR_MASK:			; Bitmask of error types that can
	.LONG	-			; be logged more than once
	<1@SCSI$C_MAP_BUFFER_ERROR>!-	
	<1@SCSI$C_SEND_CMD_ERROR>!-
	<1@SCSI$C_EXTND_SENSE_DATA>!-
	<1@SCSI$C_REASSIGN_BLOCK>

LOG_ERROR:

	BBS	#UCB$V_DISABL_ERRLOG,-	; Branch if errlogging is disabled
		UCB$L_MK_FLAGS(R5),40$
	BBC	#UCB$V_ONLINE,-		; Branch if device is offline (don't
		UCB$W_STS(R5),40$	; log an error)
	BBCS	R7,UCB$L_ERR_MASK(R5),-	; Branch if this error type has not
		5$			; been logged yet
	BBC	R7,DUPLICATE_ERR_MASK,-	; Branch if this is a type of error
		40$			; that should be logged just once
5$:	PUSHR	#^M<R0,R2,R9,R10>	; Save regs
	BSBW	FILTER_ERROR		; OK to filter error?
	BLBS	R0,35$			; Branch if so
	MOVB	UCB$B_DEVTYPE(R5),R9	; Save SCSI device type
	BNEQ	10$			; Branch if device type known
	MOVB	#DT$_GENERIC_MK,-	; Fill in generic type so ERF can
		UCB$B_DEVTYPE(R5)	; translate the errlog packet

; The following code segment temporarily replaces the values TK50 and TZ30 
; with TK50S and TZ30S, respectively. This allows ERF to distinguish errlog 
; packets logged by this driver from those logged by the VS2000 driver.

10$:	IF_TZ30 11$			; Branch if this is a TZ30
	IFNOT_TK50 12$			; Branch if this is not a TK50
	MOVB	#DT$_TK50S,-		; Change TK50 to TK50S
		UCB$B_DEVTYPE(R5)
	BRB	12$			; Join common path

11$:	MOVB	#DT$_TZ30S,-		; Change TZ30 to TZ30S
		UCB$B_DEVTYPE(R5)	; 

12$:	MOVB	UCB$B_DEVCLASS(R5),R10	; Save DEVCLASS field
	MOVB	#DC$_TAPE,-		; Temporarily set this field to a tape
		UCB$B_DEVCLASS(R5)	; so ERF can translate packet
	MOVL	UCB$L_DDT(R5),R0	; Get DDT address
	MOVW	SCSI_ERROR_LEN_TAB-2[R7],-; Save required errorlog packet size
		DDT$W_ERRORBUF(R0)	; in the DDT
	TSTL	UCB$L_IRP(R5)		; I/O in progress?
	BEQL	20$			; Branch if not
	JSB	G^ERL$DEVICERR		; Log a device error
	BBCC	#UCB$V_ERLOGIP,-	; Clear error log in progress.
		UCB$W_STS(R5),30$	;
	MOVL	UCB$L_EMB(R5),R2	; Get address of error message buffer
	BEQL	30$			; Branch if none available
	JSB	G^ERL$RELEASEMB		; Realease the errorlog buffer
	BRB	30$			; Skip no-I/O-in-progress path
20$:	JSB	G^ERL$DEVICEATTN	; Log a device attention
30$:	MOVB	R10,UCB$B_DEVCLASS(R5)	; Restore original devclass value
	MOVB	R9,UCB$B_DEVTYPE(R5)	; Restore device type
35$:	POPR	#^M<R0,R2,R9,R10>	; Restore regs
40$:	RSB				; Return to caller

	.SBTTL	FILTER_ERROR	- Attempt to filter error
;+
; FILTER ERROR
;
; This routine attempts to filter the current error being logged by
; checking to see if the same error has been logged recently. If so,
; don't log it again. This prevents the errlog file from filling up
; if the same error is occurring repeatedly.
;
; An array in the UCB contains the last n errors logged. Each error is
; timestamped so that eventually it will go stale and a duplicate
; error will be logged.
;
; INPUTS:
;
;	R5	- UCB address
;	R7	- Error type
;
;	UCB$L_SCDRP	- Active SCDRP address
;
; OUPUTS:
;
;	R0	- Status (LBS if error can be filtered)
;-

FILTER_ERROR:

	PUSHR	#^M<R1,R2,R3>		; Save regs

	IFNOT_TK 45$			; Branch if not a TK50 or TZ30, can't
					; try to filter

; First, assemble the information for the current error in R1 as follows:
;
;	+-------+------+---------------+
;	|  MBZ	|  ASC |  KEY  |  ERR  |
;	+-------+------+---------------+
;	
;	ERR - Error code
;	KEY - Sense key (extended sense data errors only)
;	ASC - Additional sense key (extended sense data errors only)
;
; This will be used later to compare against the entries in the error array
; and to save information for this error if it's not being fioltered.
					
	MOVZBL	R7,R1			; Copy error type
	CMPL	R7,-			; Extended sense data error?
		#SCSI$C_EXTND_SENSE_DATA; 
	BNEQ	10$			; Branch if not
	MOVL	UCB$L_SCDRP(R5),R0	; Get SCDRP address
	BEQL	10$			; Branch if no SCDRP active
	TSTL	SCDRP$L_TRANS_CNT(R0)	; Extended sense data available?
	BEQL	10$			; Branch if not
	MOVL	SCDRP$L_SVA_USER(R0),R0	; Get address extended sense
	BEQL	10$			; Branch if none available
	INSV	SCSI_XS$B_KEY(R0),-	; Get sense key
		#8,#8,R1
	INSV	SCSI_XS$B_ADDNL_CODE30(R0),- ; Get additional sense code
		#16,#8,R1		;
	CMPB	SCSI_XS$B_KEY(R0),-	; Recovered error?
		#SCSI$C_RECOVERED_ERROR	;
	BEQL	45$			; Yes, don't filter it
	CMPB	SCSI_XS$B_KEY(R0),-	; Medium error?
		#SCSI$C_MEDIUM_ERROR	;
	BEQL	45$			; Yes, don't filter it

; Next, scan the error array to see if an identical error has already
; been logged.

10$:	MOVL	#2,R3			; Number of entries in error array
	MOVAL	UCB$B_ERR_ARRAY(R5),R2	; Get address of error array
20$:	CMPL	(R2),G^EXE$GL_ABSTIM	; Stale entry?
	BLSSU	30$			; Branch if so
	CMPL	R1,4(R2)		; Matching error?
	BEQL	60$			; Branch if so, OK to filter
	ADDL	#8,R2			; Advance to next entry in array
	SOBGTR	R3,20$			; Repeat for all entries in array

; This error was not found in the array. Push all the entries in the array
; down (the array is ordered on expiration time) and add this error to the
; array.

30$:	MOVL	#2-1,R3			; Number of entries in array less 1
	MOVAQ	UCB$B_ERR_ARRAY(R5)[R3],R2 ; Get address just beyond error array
40$:	MOVQ	-(R2),8(R2)		; Push an entry down in the array
	SOBGTR	R3,40$			; Repeat for entire array
	ADDL3	#60,G^EXE$GL_ABSTIM,-	; Set expiration time for new entry
		(R2)+			; to one minute from now
	MOVL	R1,(R2)			; Save new entry
45$:	CLRL	R0			; Can't filter this entry
50$:	POPR	#^M<R1,R2,R3>		; Save regs
	RSB				; Return to caller	
	
60$:	MOVL	#1,R0			; This error can be filtered
	BRB	50$			; Use common exit

	.SBTTL	SET_CONN_CHAR	- Modify connection characteristics
;+
; SET_CONN_CHAR
;
; This routine is called at the end of PACKACK to initialize the connection
; characteristics, which specify such things as whether the device supports 
; disconnect and synchronous operation, and the bus busy, arbitration, and
; selection retry counters.
;
; This routine first does a SPI$GET_CONNECTION_CHAR to get the current
; values of the connection characteristics, modifies the values of interest,
; then does a SPI$SET_CONNECTION_CHAR to set up the new values. This allows
; the class driver to change a subset of the characteristics and leave the
; rest unmodified.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- SPDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0-R2	- Destroyed
;	All other registers preserved
;-

	NUM_ARGS = 10

SET_CONN_CHAR:

	SUBSAVE				; Save return address
	MOVL	#<<NUM_ARGS+1>*4>,R1	; Size of get/set conn char buffer
	BSBW	ALLOC_POOL		; Allocate the buffer
	SUBPUSH	R2			; Save address of buffer
	MOVL	#NUM_ARGS,-		; Set argument count in buffer
		SET_CON$L_LEN(R2)	;
	SPI$GET_CONNECTION_CHAR		; Get current connection characteristics
	BLBC	R0,10$			; Branch on error
	ASSUME	SET_CON$M_DISC EQ 1
	EXTZV	#UCB$V_DISCONNECT,#1,-	; Fill in disconnect flag
		UCB$L_MK_FLAGS(R3),-	;
		SET_CON$L_CON_FLAGS(R2)	;
	BISL	#SET_CON$M_NORETRY,-	; Disable port driver command retry
		SET_CON$L_CON_FLAGS(R2)	;
	ASSUME	SET_CON$M_SYN EQ 1
	EXTZV	#UCB$V_SYNCHRONOUS,#1,-	; Fill in synchronous flag
		UCB$L_MK_FLAGS(R3),-	;
		SET_CON$L_SYN_FLAG(R2)	;
	SPI$SET_CONNECTION_CHAR		; Set the connection characteristics
10$:	PUSHL	R0			; Save return status
	SUBPOP	R0			; Get address of characteristics buffer
	BSBW	DEALLOC_POOL		; Deallocate the buffer
	POPL	R0			; Restore return status
	BLBS	R0,20$			; Branch if success status
	MOVL	#SS$_CTRLERR,R0		; Otherwise, return a reasonable status
20$:	SUBRETURN			; Return to caller


       .PAGE
        .SBTTL  CHECK_SKIPFILE_SUPPORT - Check if device supports READ POSITION command
;+
; CHECK_SKIPFILE_SUPPORT
;
; This routine is called by IO$_PACKACK code in order to determine whether
; the device supports the READ POSITION command.  If it does, then the
; ucb$l_mk_flags bit SKIPFILE_SUPPORTED will be set.  That bit is later
; checked when doing IO$_SKIPFILE operations;  if READ POSITION is supported,
; a skipfiles operation may be done most efficiently by sending Space
; Filemarks advance the tape, followed by a Read Position to obtain
; position information needed to fill in ucb$l_record.
;
; INPUTS:
;
;       R3      - UCB address
;       R4      - PDT address
;       R5      - SCDRP address
;
; OUPUTS:
;
;       R0      - Status
;       R1,R2   - destroyed
;-
CHECK_SKIPFILE_SUPPORT:

        SUBSAVE                         ; Save return address
	BBC	#UCB$V_SKIPFILE_SUPPORTED,-  ; If cmd was previously declared
                UCB$L_MK_FLAGS(R3),40$      ; unsupported, don't even try.
        MOVAL   CMD_READ_POSITION,R2        ; Address of READ POSITION command
        BSBW    SETUP_CMD                   ; Setup the command
        BISL    #UCB$M_READPOSCHK_IN_PROG,- ; Set READ POSITION chk-in-prog
                UCB$L_MK_FLAGS(R3)
        BSBW    SEND_COMMAND                ; Send the command
        BICL    #UCB$M_READPOSCHK_IN_PROG,- ; Clear READ POSITION chk-in-prog
                UCB$L_MK_FLAGS(R3)
        BLBC    R0,20$                      ; Branch on error
        CMPL    SCDRP$L_TRANS_CNT(R5),-     ; Sufficient data returned?
                #20                         ; READ POS data len should equal 20
        BLSS    20$                         ; No, so declare cmd unsupported
        MOVL    SCDRP$L_SVA_USER(R5),R0     ; Get address of data
        BBS     #BPU_BIT,-                  ; If block position unknown,
                (R0),20$                    ; declare the command unsupported

; Sanity check:  the value just returned should equal UCB$L_RECORD.

        MOVAB   8(R0),R0                    ; Prepare to get READ POS value
        MOVAB   UCB$L_RECORD(R3),R1         ; Prepare to get UCB$L_RECORD
        .REPT   4
        CMPB    -(R0),(R1)+                 ; Compare READ POS, UCB$L_RECORD
        BNEQ    20$                         ; If unequal, declare READ POS
        .ENDR                               ;   cmd to be unsupported

        BISL2   #UCB$M_SKIPFILE_SUPPORTED,-  ; READ POSITION command supported
                UCB$L_MK_FLAGS(R3)
30$:    BSBW    CLEANUP_CMD                 ; Cleanup from the SCSI command
40$:    MOVL    #SS$_NORMAL,R0              ; Always return success
	BICL	#UCB$M_FIRST_READPOS_CMD,-  ; The first one is done 
		UCB$L_MK_FLAGS(R3)		
        SUBRETURN                           ; Return to caller

; Error path:  READ POSITION cannot be supported.

20$:   	BICL2   #UCB$M_SKIPFILE_SUPPORTED,-  ; READ POSITION command unsupported
                UCB$L_MK_FLAGS(R3)
	BBS	#UCB$V_FIRST_READPOS_CMD,-   ; Suppress logging the very first
		UCB$L_MK_FLAGS(R3),30$       ; one (eg. for devices that do not
					     ; support the READ POSITION cmd)
	LOG_ERROR -
		TYPE=SEND_CMD_ERROR,-
		VMS_STATUS=#SS$_NORMAL,-
		UCB=R3
        BRW     30$                         ; Common exit



	.SBTTL	CHECK_COMPACTION_SUPPORT - Check if device supports data compaction
;+
; CHECK_COMPACTION_SUPPORT
;
; This routine calls the SETUP_COMPACTION routine attempting to enable
; compaction on the device. If it succeeds in enabling compaction, it is
; assumed that the device supports compaction and the MT2$M_COMP_SUP bit is
; set in UCB$L_DEVDEPND2, otherwise it is cleared. If supported, compaction 
; set back to whatever state it was previously in (MT2$M_COMP_ENA).
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;	R1,R2	- destroyed
;-
CHECK_COMPACTION_SUPPORT:

	SUBSAVE				; Save return address
	CLRB	UCB$B_SAVED_COMP(R3)	; Assume compaction is off
	BBC	#MT2$V_COMP_ENA, -	; Check current compaction state
		UCB$L_DEVDEPND2(R3), 5$; 
	MOVB	#1, UCB$B_SAVED_COMP(R3); Flag: compaction was enabled
5$:	CMPB	#2,-			; Only attempt compaction on SCSI-2+
		UCB$B_SCSI_VERSION(R3)	; compliant devices
	BGTR	10$			; Branch if scsi-1

	DISABLE_ERRLOG			; Temporarily disable errorlogging
        BISL    #UCB$M_COMPCHK_IN_PROG,-; Set compaction check in progress flag
                UCB$L_MK_FLAGS(R3)
	MOVL	#1, R0			; Attempt to turn on compaction
	BSBW	SETUP_COMPACTION	
        BICL    #UCB$M_COMPCHK_IN_PROG,-; Clear compaction check in progress fl
                UCB$L_MK_FLAGS(R3)
	REENABLE_ERRLOG			; Reenable errorlogging
	BLBC	R0,10$			; LBC = not supported
	BISL	#MT2$M_COMP_SUP,-	; Set DEVDPEND2 comp supported bit
		UCB$L_DEVDEPND2(R3)	;
	TSTB	UCB$B_SAVED_COMP(R3)	; If compaction was already on, 
	BNEQ	20$			; leave it on
	CLRL	R0			; Compaction off flag
	BSBW	SETUP_COMPACTION	; Turn it back off
	BRB	20$

10$:	BICL	#MT2$M_COMP_SUP,-	; Clear DEVDPEND2 comp supported bit
		UCB$L_DEVDEPND2(R3)	;
20$:	MOVL	#1, R0			; Always success
	SUBRETURN			; Return to caller




	.SBTTL	SETUP_COMPACTION - Use mode sense/select page 10 for compaction
;+
; SETUP_COMPACTION
;
; This routine sends a MODE SENSE command which requests page 10. This is
; the Device Configuratin Page. At offset SCSI_MSEL$B_COMP is a byte
; which specifies if data compaction enabled or not. 
;
; INPUTS:
;
;	R0	- desired compaction state (0 = off, 1 = on)
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;	R1,R2	- destroyed
;-

	ASSUME MT$K_DEFAULT EQ 0

SETUP_COMPACTION:

	; 
	; issue a MODE_SENSE command to get the contents of page 10
	;

	SUBSAVE				; Save return address
	MOVB	R0, UCB$B_COMP_STATE(R3); Save requested compaction state
	MOVAL	CMD_MODE_SENSE_COMP,R2	; Address of MODE SENSE command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBCW	R0,40$			; Branch on error

	MOVL	SCDRP$L_TRANS_CNT(R5),R1; Get length of data recieved
        BNEQ    10$                     ; If no data returned, error
        BSBW    CLEANUP_CMD             ; Dealloate command resources
        MOVL    #SS$_DRVERR, R0         ; Set error status
        SUBRETURN                       ; Return to caller
10$:	INCL	R1			; Add a byte for length
	BSBW	ALLOC_POOL		; Get pool to save page data in
	MOVL	R2,UCB$L_COMP_PAGE(R3)	; Save pointer in UCB
	MOVL	SCDRP$L_TRANS_CNT(R5),R0; Get length of mode sense data
	MOVL	SCDRP$L_SVA_USER(R5),R1	; Get address of mode sense data
	MOVB	R0,(R2)+		; Put in length of data
	PUSHR	#^M<R3,R4,R5>
	MOVC3	R0,(R1),(R2) 		; Copy data
	POPR	#^M<R3,R4,R5>
	BSBW	CLEANUP_CMD		; Clean up MODE_SENSE command

	; 
	; now issue a MODE_SELECT command to set the compaction state
	;
	MOVAL	CMD_MODE_SELECT_TMP,R1	; Get the mode select command template
	MOVAL	CMD_MODE_SELECT,R2	; Get the mode select command
	PUSHR	#^M<R2,R3,R4,R5>	; Save regs
	MOVC3	#MODE_SEL_TMP_LEN,-	; Copy the mode select infro from the
		(R1),(R2)		; template to the active command
	POPR	#^M<R2,R3,R4,R5>	; Restore regs
	BISB	#^X10, 2(R2)		; Set PF bit in CDB for SCSI-2 page format
					;    PF is bit 4 in byte 1 of CDB
	MOVL	UCB$L_COMP_PAGE(R3),R1	; Get address of compaction page info
	MOVZBL	(R1)+,R0		; Get length of info
	MOVB	R0,5(R2)		; Put parameter list length in cmd 
	MOVZBW	R0,9(R2)		; Put BCNT in SCSI_CMD description
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	MOVL	UCB$L_COMP_PAGE(R3),R1	; Get address of page info
	MOVL	SCDRP$L_SVA_USER(R5),R2	; Get address of mode select buffer
	MOVZBL	(R1)+,R0		; Get length of page info
	PUSHR	#^M<R2,R3,R4,R5>	; Save regs
	MOVC3	R0, (R1), (R2)		; Copy the mode select page from pool
	POPR	#^M<R2,R3,R4,R5>	; Restore regs

	MOVL	UCB$L_COMP_PAGE(R3),R0	; Get address of our saved page
	BSBW	DEALLOC_POOL		; Return the pool
	CLRL	UCB$L_COMP_PAGE(R3)	; No longer valid pool address

	MOVL	SCDRP$L_SVA_USER(R5),R1	; Get address of mode select info
	CLRW	(R1)+			; Zero out mode sense data len field
	MOVB	#SCSI_MSEL$M_BUF,(R1)+	; Set buffered mode
	MOVB	#SCSI_MSEL$C_DSCLEN,(R1)+ ; Block descriptor len
	CLRQ	(R1)+			; Default density etc.
	BICL2	#^X80,(R1)		; Clear PS bit in page descriptor
	MOVB	UCB$B_COMP_STATE(R3),R0	; Restore requested compaction state
	MOVB	R0,SCSI_MSEL$B_COMP(R1)	; Put compaction state into page data
	BSBW	SEND_COMMAND
	BLBC	R0, 40$
	BICL	#MT2$M_COMP_ENA,-       ; Assume compaction is disabled
                UCB$L_DEVDEPND2(R3)
	TSTB	UCB$B_COMP_STATE(R3)	; Check for state of comp_enabled flag
	BEQL	40$			;   0 == compaction disabled
	BISL	#MT2$M_COMP_ENA,-	; Indicate compaction is enabled
		UCB$L_DEVDEPND2(R3)	;
40$:	BSBW	CLEANUP_CMD		; Cleanup from the command
	SUBRETURN			; Return to caller


	.SBTTL	+
	.SBTTL	+ TRACE ROUTINES  
	.SBTTL	+
	.SBTTL	Trace buffer, symbols
;+
; TRACE_BUFFER
;
; The driver uses a ring buffer to trace at the QIO, SCSI command, and SCSI 
; cmd/status byte level. For each QIO issued, there can be zero or more SCSI 
; commands sent, each of which can result in SCSI command, message, and status 
; bytes. The trace buffer has the following format:
;
;	+-----------------------+ <---- Start of trace buffer header
;	|    Size of trace buf	|
;	+-----------------------+
;	|    Trace header size	|
;	+-----------------------+
;	|    Total QIOs in buf	|
;	+-----------------------+
;	|    Size of each QIO	|
;	+-----------------------+
;	|    QIO header size	|
;	+-----------------------+
;	|    # SCSI cmds/QIO	|
;	+-----------------------+
;	|    Size of SCSI cmd	|
;	+-----------------------+
;	|    Offset to SCSI sts	|
;	+-----------------------+
;	|    Current QIO #	|
;	+-----------------------+
;	|			|
;	|	RESERVED	|
;	|			|
;	+-----------------------+ <---- End of trace buffer header
;	|	  QIO 0		|
;	+-----------------------+
;	|	  QIO 1		|
;	+-----------------------+
;	|	    .		|
;	|	    .		|
;	+-----------------------+
;	|	  QIO n-1	|
;	+-----------------------+
;
; For each QIO, the following information is logged:
;
;	+-----------------------+ <---- Start of QIO header
;	|    QIO number		|
;	+-----------------------+
;	|    Valid flag		|
;	+-----------------------+
;	|    SCSI command cnt	|
;	+-----------------------+
;	|    SCSI bus ID	|
;	+-----------------------+
;	|    Function code	|
;	+-----------------------+
;	|    Media (LBN)	|
;	+-----------------------+
;	|    Byte count		|
;	+-----------------------+
;	|    Byte offset	|
;	+-----------------------+
;	|    QIO status		|
;	+-----------------------+
;	|    Sequence number	|
;	+-----------------------+
;	|    			|
;	|    RESERVED		|
;	|    			|
;	+-----------------------+ <---- End of QIO header
;	|    SCSI CMD 0		|
;	+-----------------------+
;	|    SCSI CMD 1		|
;	+-----------------------+
;	|	    .		|
;	|	    .		|
;	+-----------------------+
;	|    SCSI CMD n-1	|
;	+-----------------------+
;
; Finally, for each SCSI command, the following information is logged:
;
;	+-----------------------+
;	|    CMD byte cnt	|
;	+-----------------------+
;	|    SCSI CMD bytes	|
;	+-----------------------+
;	|    SCSI status byte	|
;	+-----------------------+
;
; Note that CMD byte count is kept to allow the tracing routines to know where 
; to put the next CMD byte and to allow the user to know how much data to 
; interpret. Also note that, in order to make it easier to interpret, the data 
; is not packed in as well as it could be.
;-

	$DEFINI	TRACE_BUFFER_HEADER

$DEF	TR$L_SIZE		.BLKL	1
$DEF	TR$L_HEADER_SIZE	.BLKL	1
$DEF	TR$L_TOTAL_QIOS		.BLKL	1
$DEF	TR$L_CURRENT_QIO	.BLKL	1
$DEF	TR$L_QIO_SEQNUM		.BLKL	1
$DEF	TR$L_QIO_SIZE		.BLKL	1
$DEF	TR$L_QIO_HEADER_SIZE	.BLKL	1
$DEF	TR$L_SCSI_CMDS_PER_QIO	.BLKL	1
$DEF	TR$L_SCSI_CMD_SIZE	.BLKL	1
$DEF	TR$L_SCSI_MSG_OFFSET	.BLKL	1
$DEF	TR$L_SCSI_STATUS_OFFSET	.BLKL	1
$DEF	TR$C_HEADER_SIZE

	$DEFEND	TRACE_BUFFER_HEADER

	$DEFINI	TRACE_BUFFER_QIO_HEADER

$DEF	TR$L_QIO_NUMBER		.BLKL	1
$DEF	TR$L_VALID_FLAG		.BLKL	1
$DEF	TR$L_SCSI_CMD_CNT	.BLKL	1
$DEF	TR$L_DEVICE_NAME	.BLKL	1
$DEF	TR$L_SCSI_UNIT_NUMBER	.BLKL	1
$DEF	TR$L_QIO_FUNC		.BLKL	1
$DEF	TR$L_QIO_MEDIA		.BLKL	1
$DEF	TR$L_QIO_BCNT		.BLKL	1
$DEF	TR$L_QIO_BOFF		.BLKL	1
$DEF	TR$L_QIO_STATUS		.BLKL	1
$DEF	TR$L_SEQNUM		.BLKL	1
$DEF	TR$L_RECORD		.BLKL	1
$DEF	TR$L_DEVDEPEND		.BLKL	1
$DEF	TR$C_QIO_HEADER_SIZE

	$DEFEND	TRACE_BUFFER_QIO_HEADER

TR$TOTAL_QIOS = 200
TR$SCSI_CMDS_PER_QIO = 0
TR$CMD_BYTES = 12
TR$MSG_BYTES = 12
TR$STATUS_BYTES = 1
TR$SCSI_MSG_OFFSET = TR$CMD_BYTES+4
TR$SCSI_STATUS_OFFSET = TR$CMD_BYTES+4 + TR$MSG_BYTES+4

TR$SCSI_CMD_SIZE = 	<<TR$CMD_BYTES+4 + TR$STATUS_BYTES+4> + 15> & ^C15
TR$QIO_SIZE = 		<<TR$C_QIO_HEADER_SIZE + <TR$SCSI_CMDS_PER_QIO * TR$SCSI_CMD_SIZE>> + 15> & ^C15
TR$TRACE_BUFFER_SIZE =	TR$C_HEADER_SIZE + <TR$TOTAL_QIOS * TR$QIO_SIZE>

TR$TRACE_BUFFER_ADDR:
	.LONG	0

TR$TRACE_ROUTINES:

;+
; TRACE_ROUTINES
;
; This macro generates a table of routine names and relative offsets. A kernel
; mode program can use this to display the address of various driver routines,
; making debug easier.
;-

	.MACRO	TRACE_ROUTINES, LIST, ?L

	.IRP	ROUTINE, <LIST>
	.ENABL	LSB
	.LONG	ROUTINE-.
	.SAVE_PSECT
	.PSECT	$$$114_DRIVER
L:	.ASCIZ	/ROUTINE/
	.RESTORE
	.LONG	L-.
	.DSABL	LSB
	.ENDR
	.LONG	-1

	.ENDM	TRACE_ROUTINES

	TRACE_ROUTINES	<-
		MK$DDT,-
		MK_CTRL_INIT,-
		MK_UNIT_INIT,-
		MK_STARTIO,-
		MK_REG_DUMP,-
		IO_PACKACK,-
		IO_READPBLK,-
		IO_WRITEPBLK,-
		IO_REWIND,-
		IO_SKIP_FILE,-
		IO_SKIP_RECORD,-
		IO_SETMODE,-
		INQUIRY,-
		MODE_SENSE,-
		MODE_SELECT,-
		SET_UNIT_ONLINE,-
		SEND_COMMAND,-
		LOG_ERROR,-
		SET_CONN_CHAR>

	.SBTTL	SETUP_TRACE	- Allocate and set up trace buffer
;+
; SETUP_TRACE
;
; This routine is called by unit init to allocate a trace buffer, which
; is used to trace QIOs through the driver. The auxstruc field in the CRB is
; filled in with the address of a cell which contains the address of the trace
; buffer. Since the driver can have more than one unit, only the first
; call to this routine actually performs the allocation. Since pool allocation 
; can stall this thread, the TR$TRACE_BUFFER_ADDR cell is given a value of
; 1 (positive integer) to indicate that poll allocation is on progress. This
; prevents a second thread from allocating another buffer.
;
; INPUTS:
;
;	R5	- CRB address
;
; OUTPUTS:
;
;	R0-R3	- Destroyed
;	All other registers preserved
;
;	CRB$L_AUXSTRUC	- Address of cell containing trace buffer address
;-

SETUP_TRACE:
					
	.IF DEFINED DEBUG
	TSTL	TR$TRACE_BUFFER_ADDR	; Trace buffer already set up?
	BNEQ	10$			; Branch if so
	INCL	TR$TRACE_BUFFER_ADDR	; Trace buffer setup in progress
	MOVL	#TR$TRACE_BUFFER_SIZE,R1; Get size of trace buffer
	JSB	G^EXE$ALONONPAGED	; Allocate pool
	BLBC	R0,30$			; Branch if failure
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save regs
	MOVC5	#0,.,#0,R1,(R2)		; Initialize the packet
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Save regs
	MOVL	#TR$TRACE_BUFFER_SIZE,-	; Save trace buffer size
		TR$L_SIZE(R2)		;
	MOVL	#TR$C_HEADER_SIZE,-	; Save header size
		TR$L_HEADER_SIZE(R2)	;
	MOVL	#TR$TOTAL_QIOS,-	; Save total number of QIOs in
		TR$L_TOTAL_QIOS(R2)	; buffer
	MOVL	#TR$QIO_SIZE,-		; Save size of QIO
		TR$L_QIO_SIZE(R2)	;
	MOVL	#TR$C_QIO_HEADER_SIZE,-	; Save size of QIO header
		TR$L_QIO_HEADER_SIZE(R2); 
	MOVL	R2,TR$TRACE_BUFFER_ADDR	; Save trace buffer address
10$:
	.ENDC

20$:	MOVAL	TR$TRACE_BUFFER_ADDR,-	; Save address of cell pointing to 
		CRB$L_AUXSTRUC(R5)	; trace buffer in CRB
	RSB

30$:	CLRL	TR$TRACE_BUFFER_ADDR	; Trace buffer not allocated
	BRB	20$			; Use common exit
		

	.IF DEFINED DEBUG
	.SBTTL	TRACE_QIO	- Trace QIO requests
;+
; TRACE_QIO
;
; This routine logs information from the current QIO packet including the
; function code, MEDIA, BCNT, and BOFF fields.
;
; INPUTS:
;
;	R3	- IPR address or 0 if UNIT INIT
;	R5	- UCB address
;
; OUTPUTS:
;
;	All registers preserved
;-

TRACE_QIO:

	PUSHR	#^M<R0,R1,R2>		; Save regs
	MOVL	TR$TRACE_BUFFER_ADDR,R0	; Get trace buffer address
	BGEQ	30$			; Branch if none
	ADDL3	#1,-			; Bump QIO number
		TR$L_CURRENT_QIO(R0),R1	;
	CMPL	R1,#TR$TOTAL_QIOS	; Time to wrap QIO number?
	BLSS	10$			; Branch if not
	CLRL	R1			; Wrap QIO number
10$:	MOVL	R1,TR$L_CURRENT_QIO(R0)	; Save QIO number
	MULL3	#TR$QIO_SIZE,R1,R2	; Get offset of this QIO in trace buffer
	ADDL	R0,R2			; Address to place info for this QIO
	ADDL	#TR$C_HEADER_SIZE,R2	; Account for trace buffer header
	PUSHR	#^M<R0,R2,R3,R4,R5>	; Save regs
	MOVC5	#0,.,#0,#TR$QIO_SIZE,-	; Clear entire buffer used to trace
		(R2)			; this QIO
	POPR	#^M<R0,R2,R3,R4,R5>	; Restore regs
	ADDL3	#TR$C_QIO_HEADER_SIZE,-	; Get address of place to store
		R2,R1			; first SCSI command
	MOVL	TR$L_CURRENT_QIO(R0),-	; Save QIO number
		(R2)+			;
	MOVL	#1,(R2)+		; Set valid flag
	CLRL	(R2)+			; Clear SCSI command count
	MOVL	UCB$L_DDB(R5),R1	; Get DDB address
	MOVL	DDB$T_NAME_STR(R1),(R2)+; Save SCSI device name
	MOVZWL	UCB$W_UNIT(R5),(R2)+	; Trace SCSI bus ID
	TSTL	R3			; IRP address of 0 (unit init)?
	BEQL	15$			; Branch if so
	MOVZWL	IRP$W_FUNC(R3),(R2)+	; Trace QIO function code
	MOVL	IRP$L_MEDIA(R3),(R2)+	; Trace media number (LBN)
	MOVL	IRP$W_BCNT(R3),(R2)+	; Trace byte count
	MOVL	IRP$W_BOFF(R3),(R2)+	; Trace byte offset
	BRB	20$			; Skip unit init path
15$:	MCOML	#0,(R2)+		; Special function code for build UNIT INIT
	ADDL	#12,R2			; Skip MEDIA, BCNT, BOFF fields
20$:	MOVL	R2,UCB$L_TR_QIO_STS(R5)	; Save address to put QIO status
	MCOML	#0,(R2)+		; Initialize QIO status
	MOVL	TR$L_QIO_SEQNUM(R0),-	; Save QIO sequence number
		(R2)+			;
	INCL	TR$L_QIO_SEQNUM(R0)	; Bump QIO sequence number
30$:	POPR	#^M<R0,R1,R2>		; Restore regs
	RSB				; Return to caller
	.ENDC				; .IF DEFINED DEBUG

	.SBTTL	TRACE_QIO_STAT	- Save final status from QIO in trace buffer
;+
; TRACE_QIO_STAT
;
; This routine saves the final QIO status in the trace buffer.
;-

TRACE_QIO_STAT:

	MOVL	UCB$L_TR_QIO_STS(R5),R2	; Get address to put status
	BEQL	10$			; Branch if uninitialized
	MOVL	R0,(R2)			; Save status in trace buffer
	ASSUME	TR$L_QIO_STATUS+8 EQ TR$L_RECORD
	ASSUME	TR$L_QIO_STATUS+12 EQ TR$L_DEVDEPEND
	MOVL	UCB$L_RECORD(R5),8(R2)	; Save current record number
	MOVL	UCB$L_DEVDEPEND(R5),-	; Save DEVDEPND info
		12(R2)			;
10$:	RSB

MK_PATCH:
	.BLKB	200			; Patch space

MK_END:					; Last location in driver
	.END
;==============================================================================
