
	.TITLE	TMSCP - TMSCP Server - Emulator


	.SBTTL	Copyright and History
	.IDENT	'X-2A6A1'

	SOFTWARE_REV = 6		; Keep software rev level equal
					; to ident field for easier remote
					; diagnosis.
	HARDWARE_REV = 1		; Increment this field in the event
					; of incompatible driver/server
					; changes.

;****************************************************************************
;*									    *
;*  COPYRIGHT (c) 1985, 1986, 1987, 1988, 1989, 1990, 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:	TMSCP Server
;                         
; ABSTRACT:
;
;	This program implements a Tape MSCP(Mass Storage Control Protocol) 
;	server. It serves local tapes to remote systems (hosts). Valid 
;	MSCP command packets are accepted as input from other cluster 
;	members. Local tapes are then accessed via the proper tape driver 
;	according to their type to perform the indicated operations. 
;	
;	A lot of this code was gleaned from MSCP.MAR. See modification
;	history of that module if pursuing details of a particular
;	implementation.
;
;
; ENVIORNMENT:	Interrupt stack, system context
;
; AUTHOR:	Cheryl J. Bulmer
;
; CREATION DATE:  09-March-1990
;
; MODIFIED BY:
;
;	X-2A6A1 MGB0001		Michael Beeler			27-Aug-1992
;		In ABORT routine, add missing # to BBS HRB$V_STATE_INVALID
;
;	X-2A6	JSSTS0030	John S.Simakauskas		7-Apr-1992
;		Correct the UQB$W_NUM_QUE counter handling when aborting
;		sequential commands. Previously it was possible for this
;		counter to be decremented twice for a request.
;
;	X-2A5	RNH0163		Richard N. Holstein	13-Feb-1992
;		Fix SMP problem when dealing with UCB$W_QLEN.  Update ident
;		to agree with master pack reorg.
;
;	X-6A1	JSSTS0023	John Simakauskas		22-Jan-1992
;		Disable packet logging (debug).
;
;	X-6	JSSTS0021	John S. Simakauskas		 6-Jan-1992
;		Fix the order of restoring and register use in SEQ_STALL (UNBLOCK)
;
;	X-5	JSSTS0016	John S. Simakauskas		25-Oct-1991
;		Change VC failure handling in the UNBLOCK routine.
;		There are situations, when using the NI, where VC failures 
;		can cause the servING node to crash. This problem has not 
;		been reproducable when using the CI.
;
;	X-3A	JSSTS0014	John S. Simakauskas		03-Oct-1991
;		Fix VC_ERR code to prevent BUG_CHECKing during
;		an abort.
;		Change CLEANUP_HRB to decrement UQB$NUM_QUE only if the
;		UQB exists.
;		Make TMSCP abort code more like MSCP code.
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;		make ident match new masd X-3
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
;	X-7a	JSSTS00xx	John S. Simakauskas		25-June-1991
;		1.) Replace calls to TMSCP$SET_IMMED in the ERASE,
;		    REPOSITION and WRITE_TAPE_MARK routines. Use the 
;		    MD_IMMED value passed in packet in these routines.
;		2.) Modify the TMSCP$SET_UNIT_CHAR routine such 
;		    to use IO$_SETMODE in place of IO$_SETCHAR.
;
;	X-6	LPL0005		Lee Leahy			27 Aug 1990
;		1.)  Fixed TMSCP$MSCPTOVMS_DENS to use default density
;		     in table if a format is not specified.
;		2.)  Fixed TMSCP$FIND_SECTION to verify that the requested section
;		     is contained in the specified tables.
;		3.)  Fixed TMSCP$SPEEDTOMSCP to use default density
;		     in table if a format is not specified.
;		4.)  Fixed TMSCP$MSCPTOSPEED to use default density
;		     in table if a format is not specified.
;		5.)  Zero the entire AVATN message to be sent in TMSCP$ADD.
;		6.)  Properly set the tape density and speed in TMSCP$SET_UNIT_CHAR.
;		7.)  Fixed GET_TAPE_FORMAT to support non-MSCP tapes.
;		8.)  Fixed GET_TAPE_FORMENU to support non-MSCP tapes.
;		9.)  Fixed GET_TAPE_SPEED to support non-MSCP tapes.
;		10.) Fixed blown register in TMSCP$COMMON_TRANSFER that was 
;		     corrupting the UCB$L_DEVDEPEND field of the UCB on the serving
;		     system.  This was a royal pain to find!
;
;	X-5	LPL0004		Lee Leahy			23 Aug 1990
;		1.)  Fixed PC logging in SEQ_STALL.
;		2.)  Fix accvio crash caused when killing thread in 
;		     TMSCP$COMMON_TRANSFER.
;		3.)  Changed SS$_SERIOUSEXCP to MSCP$K_ST_PRESE in 
;		     TMSCP$COMMON_TRANSFER.
;		4.)  Saved registers in TMSCP$CHECK_CACHE
;		5.)  Moved queue empty check in FLUSH_CACHE.
;		6.)  Get TMSCP message address from HRB in TMSCP$ERR_EOT.
;		7.)  Get TMSCP message address from HRB in TMSCP$ERR_PLOST.
;		8.)  Get TMSCP message address from HRB in TMSCP$ERR_DLS.
;		9.)  Get IRP address in TMSCP$ERASE after call to 
;		     TMSCP$CHECK_XFER_STATUS.
;		10.) Log PCs in TMSCP$ERROR_NO_UNIT and TMSCP$PACKET_ERROR.
;		11.) Set IRP$W_FUNC field before checking for internally
;		     generated AVAILABLE request in TMSCP$AVAILABLE.
;		12.) Increased controller timeout to prevent command timeouts.
;		13.) No need to call TMSCP$SET_CLEAR_SEX twice in TMSCP$REPOSTION.
;		14.) Always update the tape position in TMSCP$REPOSITION.
;		15.) Use correct field names for return record and tape mark count
;		     in TMSCP$REPOSTION.
;		16.) Removed references to HRB$L_ABCNT.
;		17.) Fix path when AVAILABLE message received and unit is not online
;		     to any host.
;		18.) Saved command number in PC trace.
;		19.) Use MACRO to access FORMAT field in UCB.
;		20.) Use MACRO to access SPEED field in UCB.
;		21.) Removed additional calls to TMSCP$SEQ_STALL in TMSCP$REPLACE
;		     to preserve sequentiallity!
;		22.) Moved call to TMSCP$SEQ_STALL into TMSCP$SEQUENTIAL.  This
;		     fixes several synchronization bugs related to accessing the
;		     other data structures (HQB, UCB, and UQB) within the processing
;		     routines.
;		23.) Properly enable write back caching and disable read caching on
;		     a per unit basis in TMSCP$SET_UNIT_CHARACTERISTICS.
;		24.) Removed the TMSCP server write back cache since this no 
;		     feedback is available for end of tape and this cache can not
;		     be shutdown prior to end of tape.  This form of caching is
;		     broken!
;		25.) Compare the same flags in TMSCP$ONLINE.
;		26.) Use MACRO to access MAXWRCNT field in UCB.
;		27.) Use MACRO to access NOISE field in UCB.
;		28.) Converted tape speed from the MSCP to the VMS format in 
;		     TMSCP$SET_UNIT_CHAR.
;		29.) Converted tape format from the MSCP to the VMS format in
;		     TMSCP$SET_UNIT_CHAR.
;		30.) Copied the MSCP to VMS and VMS to MSCP conversion routines
;		     from TUDRIVER.
;		31.) Fixed TMSCP$SEQ_STALL and TMSCP$UNBLOCK to correctly stall 
;		     additional sequential commands.
;		32.) Moved call to TMSCP$UNBLOCK into TMSCP$CLEANUP_HRB.
;		33.) Enable MSCP modifiers in TMSCP$SET_CLEAR_CDL.
;		34.) Logging more of the IRP in the PC history buffer.
;		35.) TMSCP$ONLINE now calls TMSCP$SET_UNIT_CHAR to properly set the
;		     unit characteristics for TMSCP and non-TMSCP tapes.
;		36.) Set buffer length in TMSCP$SET_UNIT_CHAR.
;
;	X-4	LPL0003		Lee Leahy			 4 Jul 1990
;		1.)  Fixed initialization of TSRV structure.
;		2.)  Use actual PCs in the history buffer.
;		3.)  Only initialize the PC history buffer once.
;		4.)  Log a few more PCs in the history buffer.
;		5.)  Moved the calls to SET_CLEAR_SEX and SET_CLEAR_CDL
;		     in TMSCP$READ_TRANSFER.
;		6.)  Moved the calls to SET_CLEAR_SEX, SET_CLEAR_CDL, and
;		     SET_IMMED in TMSCP$WRITE_TRANSFER.
;		7.)  Changed names from TMSCP. to TMSCP$ since SDA does not
;		     allow periods (.) in symbol names.
;		8.)  Fixed initialization of UQB address in TMSCP$SET_IMMED.
;
;	X-3	LPL0002		Lee Leahy			 2 Jul 1990
;		1.)  Changed access to TSRV structure in TMSCP$ERASE.
;		2.)  Fixed build bug.  Duplicate HARDWARE_REV symbol in 
;		     object library.
;
;	X-2	LPL0001		Lee Leahy			 7 Jun 1990
;		1.)  Added debug flags.
;		2.)  Fixed bad branch in TMSCP$ERR_PLOST at 5$.  
;		             BBSS #UQB$M_ST_PLS -> $V.
;		3.)  Fixed bad branch in TMSCP$GET_UNIT_STATUS at 160$.  
;		             BBC #MSCP$M_UF_WRTPD -> $V.
;		4.)  Fixed bad branch in TMSCP$ERR_EOF.
;		             BBC #MT$M_BOT -> $V.
;		5.)  Fixed blown register in TMSCP$SET_CLEAR_SEX.  UQB uses R5 not 
;		     R3.
;		6.)  Make symbols global for symbol table generation.
;		7.)  Added data structure connection diagrams.
;
;	X-1	CJB0001		Cheryl J. Bulmer		09-March-1990
;		Initial checkin.
;-


	.SBTTL	Data Structure Definitions

	.DISABLE	GLOBAL

;
; INCLUDE FILES:
;

;;	.NOCROSS
	$CDDBDEF		; Class Driver Data Block
	$CDRPDEF		; Class Driver Request Packet
	$CDTDEF			; Connection Descriptor Table
	$DCDEF			; Device Adapter, Type and Class Codes
	$DDBDEF			; Driver Data Block
	$DDTDEF			; Driver Dispatch Table
	$DEVDEF			; Device Characteristics
	$TSRVDEF		; MSCP Server structure definition
	$DYNDEF			; Data Structure type definitions
	$HQBDEF			; Host Queue Block structure definition
	$HRBDEF			; Host Request Block structure definition
	$IODEF			; I/O function code definitions
	$IPLDEF			; Interupt level definitions
	$IRPDEF			; I/O Request Packet structure definition
	$MSCPDEF		; Tape Mass Storage Control Protocol packet
	$MTDEF			; Define MAGTAPE STATUS bits
	$MT2DEF			; Define MAGTAPE extended characteristics
	$PBDEF			; Path Block offsets
	$PDTDEF			; Port Descriptor Table
	$PRDEF			; Processor Register
	$SBDEF			; System Block offsets
	$SCSDEF			; System Communications Services
	$SDIRDEF		; SCS Directory defs
	$SLVDEF			; System Loadable Vector
	$SRVBUFDEF		; Internal buffer area definition
	$SPLCODDEF		; Define spinlock indicies
	$SSDEF			; System Service message definitions
	$UCBDEF			; Unit Control Block structure definition
	$UQBDEF			; Unit Queue Block structure definition
	$VADEF			; Virtual Address Field Definitions
	$VAXDEF			; VAX model definitions
	$VCBDEF			; Volume Control Block Definitions


;
; Debugging switches
;
;		DEBUG$PC_HISTORY	= 0	; Assemble PC history code
;		DEBUG$CURRENT_SANITY	= 0	; Assemble the current counter checks
;		DEBUG$REJECT_TRACKING	= 0	; Monitor the reason for rejects
;		DEBUG$LOG		= 0	; Enable logging of packets
;

;
; MACROS:
;

	.MACRO	ACTION,SYSERR,TMSCPERR,ROUTINE
	 .WORD	SYSERR
	 .WORD	TMSCPERR
	 .IF B ROUTINE
	  .WORD	0
	 .IFF
	  .WORD	ROUTINE-.
	 .ENDC
	.ENDM		

; A longword is needed in the CDRP so that when it is returned to the server
; the corresponding HRB can be determined.  Since the server is doing its
; own internal I/O, we can use the ASTPRM field for the HRB address.
;
	CDRP$L_HRB = CDRP$L_ASTPRM
	IRP$L_HRB  = IRP$L_ASTPRM

	.DEFAULT DISPLACEMENT WORD		; Make relative offsets a word.


	.SBTTL	Data Structure Connection Diagrams.


;	HRB  =  Host Request Block
;	HQB  =  Host Queue Block
;	UQB  =  Unit Queue Block
;
;			Host request linkage.
;
;	+-------------------+
;	!        HRB        !
;	!                   !
;	!            msgbuf !--------------------------------->+------------------+
;	!                   !                                  !   MSCP message   !
;	!          irp_cdrp !------>+-------------------+      !                  !
;	!                   !       !        IRP        !      +------------------+
;	+-------------------+       !                   !
;	                            !              fqfl !------>+------------------+
;	                            !                   !       !       CDRP       !
;	                            +-------------------+       !                  !
;	                                                        +------------------+
;
;			MSCP message processing.
;	
;	+-------------------+
;	!        HQB        !
;	!               cdt !---------------------------------->+------------------+
;	!                   !                                   !        CDT       !
;	!            hrb_fl !------>+-------------------+       !                  !
;	!            hrb_bl !<------!        HRB        !       !                  !
;	!                   !       !                   !       +------------------+
;	!                   !<------! hqb               !
;	!      f/blink      !       !      f/blink      !
;	+-------------------+       +-------------------+
;	        |   ^                       |   ^
;	         ...                         ...
;	        |   |                       |   |
;	        V   |                       V   |
;	+-------------------+       +-------------------+
;	!      f/blink      !       !      f/blink      !
;	!                   !       !                   !
;	!        HQB        !       !        HRB        !
;	+-------------------+       +-------------------+

;		Sequential command synchronization.
;
;	+-------------------+
;	!        UQB        !
;	!                   !
;	!        blocked_fl !------>+-------------------+
;	!        blocked_bl !<------!        HRB        !
;	!                   !       !                   !
;	!                   !<------! uqb               !
;	!      f/blink      !       !     wait_fl/bl    !
;	+-------------------+       +-------------------+
;	        |   ^                       |   ^
;	         ...                         ...
;	        |   |                       |   |
;	        V   |                       V   |
;	+-------------------+       +-------------------+
;	!      f/blink      !       !     wait_fl/bl    !
;	!                   !       !                   !
;	!        UQB        !       !        HRB        !
;	+-------------------+       +-------------------+
;
;	Note: The flag UQB$V_SEQ indicates that a sequential 
;	command is currently being processed.  When additional
;	sequential commands are received, they are placed on
;	the BLOCKED queue.  As each sequential command completes, 
;	the next sequential command is started until no more
;	sequential commands remain.  At this time, the flag
;	UQB$V_SEQ is cleared.  Each sequential command is noted
;	by the flag HRB$V_UNBLOCK.


	.SBTTL	External References

	.EXTERNAL	BUG$_MSCPSERV

	.EXTERNAL	CLU$GL_TAPE_ALLOCLS

	.EXTERNAL	EXE$ALONONPAGED
	.EXTERNAL	EXE$DEANONPAGED
	.EXTERNAL	EXE$DEANONPGDSIZ
	.EXTERNAL	EXE$GL_ABSTIM_TICS
	.EXTERNAL	EXE$GL_ERASEPPT
	.EXTERNAL	EXE$INSIOQC

	.EXTERNAL	INI$BRK

	.EXTERNAL	MMG$GL_SPTBASE

	.EXTERNAL	PMS$END_RQ
	.EXTERNAL	PMS$GL_IOPFMPDB
	.EXTERNAL	PMS$GL_IOPFMSEQ
	.EXTERNAL	PMS$START_RQ

	.EXTERNAL	SCS$ALLOC_RSPID
	.EXTERNAL	SCS$DEALL_RSPID
	.EXTERNAL	SCS$DISCONNECT
	.EXTERNAL	SCS$LKP_MSGWAIT
	.EXTERNAL	SCS$LKP_RDTCDRP
	.EXTERNAL	SCS$LKP_RDTWAIT

	.EXTERNAL	SCS$GB_SYSTEMID
	.EXTERNAL	SCS$GL_CDL
	.EXTERNAL	SCS$GL_TMSCP
	.EXTERNAL	SCS$GL_TMSCP_MV
	.EXTERNAL	SCS$GQ_DIRECT


	.SBTTL	Select the TMSCP server timeout.

	.MACRO	NEW_TIMEOUT	controller, timeout
	.IF	EQUAL	timeout
CNTRL_TIMEOUT_'controller	=	255
	.IFF
CNTRL_TIMEOUT_'controller	=	timeout
	.ENDC
	.IF	GREATER	CNTRL_TIMEOUT_'controller-SERVER_TIMEOUT
SERVER_TIMEOUT	=	CNTRL_TIMEOUT_'controller
	.ENDC
	.ENDM	NEW_TIMEOUT




SERVER_TIMEOUT	=	30		; Set default timeout value.

	NEW_TIMEOUT	TK50	<^X 0078>
	NEW_TIMEOUT	HSC	<^X 00FF>


	.SBTTL	Local Storage
	.PSECT	$$$100_TMSCP_VECTOR_TABLE,QUAD

	.SHOW MEB

TMSCP$BEGIN::

;
;		The following data structures are used for loading the
;		TMSCP server.  After that they are over written with the
;		TSRV data structure.
;

	PRMSW	=  1

	SLVTAB	END	= TMSCP$END,-
		INITRTN	= TMSCP$INIT,-
;		SUBTYP	= DYN$C_LC_TMSCP,-
		SUBTYP	= DYN$C_LC_MSCP,-
		FACILITY= <TMSCP_SERVER>
;	.SAVE	LOCAL_BLOCK
;	.RESTORE
;30000$:
;	.LONG	TMSCP$END-30000$
;	.LONG	TMSCP$INIT-30000$
;	.WORD	TMSCP$END-30000$
;	.BYTE	DYN$C_LOADCODE
;	.BYTE	DYN$C_LC_MSCP
;	.BYTE	PRT$C_ER
;	.BYTE	PRT$C_EW
;	.WORD	0
;	.LONG	0
;30001$:	.ASCIC	/TMSCP_SERVER/
;
;
;	.=30001$+16

	LOADVEC	TYPE	  = SLV$K_SDATA,-
		ENTRY	  = SCS$GL_TMSCP,-
		SEC_LABEL = TMSCP$BEGIN
;	   .BYTE SLV$K_SDATA
;	   .ADDRESS SCS$GL_TMSCP
;	   .LONG   <TMSCP$BEGIN-.>

	.LONG	-1,-1,-1

	.ALIGN	QUAD

;
; Allocate space for the TSRV data structure.
;

OVERLAY_AREA	=	. - TMSCP$BEGIN

	.BLKB	TSRV$K_LENGTH - OVERLAY_AREA

;
; Definition of constants
;
	MAX_UNITS   =   256
	MAX_HOSTS   =   256
	VERSION	    =	0


	.IF DEFINED DEBUG$PC_HISTORY
;
; Space is allocated here for PC history storage
;

	$EQU	PCHIST_ENTRIES	1024		; Number of entries in the history

TMSCP$PCHISTBEG::	.LONG	0		; Address of the start of the table
TMSCP$PCHISTCUR::	.LONG	0		; Pointer to next available slot
TMSCP$PCHISTEND::	.LONG	0		; Address of the end of the table
TMSCP$PCHISTFUL::	.LONG	0		; Table full counter
TMSCP$PCHISTBUF::	.BLKL	PCHIST_ENTRIES	; Table for storing server offsets
TMSCP$PCTBLEND::

	.ENDC	   ;DEBUG$PC_HISTORY

	$VIELD	TMSCP, 0, < -
		< DBG_START, , M >, -		; Breakpoint in TMSCP$START.
		< DBG_MSG_IN, , M >, -		; Breakpoint in TMSCP$MSG_IN.
		< DBG_SEND_PKT, , M > -		; Breakpoint in TMSCP$SEND_PKT.
		>

	.WEAK	TMSCP$C_DEBUG_FLAGS

TMSCP$L_DEBUG_FLAGS::	.LONG	0 \ TMSCP$C_DEBUG_FLAGS


	.SBTTL	Define the Field Access Macros.

	.MACRO	GET_TAPE_FORMAT	ucb, ?not_mscp, ?tape_format_set

;
;		Return the MSCP tape format when possible.
;

	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
		UCB$L_DEVCHAR2('ucb'), not_mscp	; Not MSCP tape - branch.
	MOVW	UCB$W_TU_FORMAT('ucb'), -	; Steal tape format from UCB.
		MSCP$W_FORMAT(R2)
	BRB	tape_format_set

;
;		Get the VMS tape format value.
;

not_mscp:
	MOVQ	R0, -(SP)
	EXTZV	# MT$V_DENSITY, -		; Extract VMS density from the UCB.
		# MT$S_DENSITY, -
		UCB$L_DEVDEPEND('ucb'), R0

;
;		Convert the VMS tape format into the MSCP equivalent.
;

	CLRL	R1				; Use default VMS density table.
	BSBW	TMSCP$VMSTOMSCP_DENS		; Convert tape density value.

;
;		Return the MSCP tape format.
;

	MOVW	R1, MSCP$W_FORMAT(R2)		; Return the tape format.
	MOVQ	(SP)+, R0

tape_format_set:
	.ENDM	GET_TAPE_FORMAT




	.MACRO	GET_TAPE_FORMENU	ucb, ?not_mscp, ?tape_formenu_set

;
;		Return the MSCP tape formenu when possible.
;

	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
		UCB$L_DEVCHAR2('ucb'), not_mscp	; Not MSCP tape - branch.
	MOVW	UCB$W_TU_FORMENU('ucb'), -	; Steal tape formenu from UCB.
		MSCP$W_FORMENU(R2)
	BRB	tape_formenu_set

;
;		Get the VMS tape formenu value.
;

not_mscp:
	MOVQ	R0, -(SP)
	EXTZV	# MT$V_DENSITY, -		; Extract VMS density from the UCB.
		# MT$S_DENSITY, -
		UCB$L_DEVDEPEND('ucb'), R0

;
;		Convert the VMS tape density into the MSCP equivalent.
;

	CLRL	R1				; Use default VMS density table.
	BSBW	TMSCP$VMSTOMSCP_DENS		; Convert tape density value.

;
;		Return the MSCP tape formenu.
;

	MOVB	R1, MSCP$W_FORMENU(R2)		; Return the current tape format.
	MOVB	R1, 1+MSCP$W_FORMENU(R2)	; Return the supported densities.
	MOVQ	(SP)+, R0

tape_formenu_set:
	.ENDM	GET_TAPE_FORMENU




	.MACRO	GET_TAPE_MAXWRCNT	ucb, tmp, ?not_mscp, ?tape_maxwrcnt_set

;
;		Return the MSCP tape maxwrcnt when possible.
;

	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
		UCB$L_DEVCHAR2('ucb'), not_mscp	; Not MSCP tape - branch.
	MOVL	UCB$L_TU_MAXWRCNT('ucb'), -	; Steal tape maxwrcnt from UCB.
		MSCP$L_MAXWTREC(R2)
	BRB	tape_maxwrcnt_set

;
;		Get the VMS tape maxwrcnt value.
;

not_mscp:
	MOVZWL	#^XFFFF, tmp			; Supply the default tape maxwrcnt.

;
;		Convert the VMS tape maxwrcnt into the MSCP equivalent.
;

;
;		Return the MSCP tape maxwrcnt.
;

	MOVL	tmp, MSCP$L_MAXWTREC(R2)	; Return the tape maxwrcnt.

tape_maxwrcnt_set:
	.ENDM	GET_TAPE_MAXWRCNT



	.MACRO	GET_TAPE_NOISE	ucb, tmp, ?not_mscp, ?tape_noise_set

;
;		Return the MSCP tape noise when possible.
;

	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
		UCB$L_DEVCHAR2('ucb'), not_mscp	; Not MSCP tape - branch.
	MOVW	UCB$W_TU_NOISE('ucb'), -	; Steal tape noise from UCB.
		MSCP$W_NOISEREC(R2)
	BRB	tape_noise_set

;
;		Get the VMS tape noise value.
;

not_mscp:
	CLRL	tmp				; Supply the default tape noise.

;
;		Convert the VMS tape noise into the MSCP equivalent.
;

;
;		Return the MSCP tape noise.
;

	MOVW	tmp, MSCP$W_NOISEREC(R2)	; Return the tape noise.

tape_noise_set:
	.ENDM	GET_TAPE_NOISE



	.MACRO	GET_TAPE_SPEED	ucb, mscp_format, ?not_mscp, ?tape_speed_set

;
;		Return the MSCP tape speed when possible.
;

	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
		UCB$L_DEVCHAR2('ucb'), not_mscp	; Not MSCP tape - branch.
	MOVW	UCB$W_TU_SPEED('ucb'), -	; Steal tape speed from UCB.
		MSCP$W_SPEED(R2)		; (Kilobytes per second)
	BRB	tape_speed_set

not_mscp:
	MOVQ	R0, -(SP)
	.IF	BLANK	mscp_format

;
;		Get the VMS tape density value.
;

	EXTZV	# MT$V_DENSITY, -		; Extract VMS density from the UCB.
		# MT$S_DENSITY, -
		UCB$L_DEVDEPEND('ucb'), R0

;
;		Convert the VMS tape density into the MSCP equivalent.
;

	CLRL	R1				; Use default VMS density table.
	BSBW	TMSCP$VMSTOMSCP_DENS		; Convert tape density value.
	.IFF
	MOVZWL	mscp_format, R1			; Get the MSCP tape format.
	.ENDC

;
;		Get the VMS tape speed value.
;

	EXTZV	# MT$V_SPEED, -			; Extract VMS tape speed from UCB
		# MT$S_SPEED, -			; and return as the tape speed.
		UCB$L_DEVDEPEND('ucb'), R0

;
;		Convert the VMS tape speed into the MSCP equivalent.
;

	BSBW	TMSCP$SPEEDTOMSCP		; Convert speed value.

;
;		Return the MSCP tape speed.
;

	MOVW	R0, MSCP$W_SPEED(R2)		; Return the tape speed.
	MOVQ	(SP)+, R0

tape_speed_set:
	.ENDM	GET_TAPE_SPEED


	.SBTTL	Server Action Routines

;+
; These routines are called to carry out actions that must be done 
; from high IPL code that resides in non-paged pool.
;
; Inputs:
;
;	AP -->	#arguments
;		action code
;		parameter(s)
;
; Outputs:
;
;	R0 = status code
;-

TMSCP$INIT::

	ASSUME	TSRV$K_AR_ADD EQ 1+TSRV$K_AR_START

	CASE	4(AP), -
		LIMIT=#TSRV$K_AR_START, -
		TYPE=B, -
		<TMSCP$START,-		; Startup
		 TMSCP$ADD>		; Add a unit
;	CASEB	4(AP),#TSRV$K_AR_START,S^#<<30003$-30002$>/2>-1
;30002$:
;	.SIGNED_WORD	TMSCP$START-30002$
;	.SIGNED_WORD	TMSCP$ADD-30002$
;30003$:

	MOVZWL	#SS$_BADPARAM, R0	; Command out of range
	RSB				; Leave


	.SBTTL	-	START  -  Initialization Routine

;+
; Functional Description:
; 
; Evaluate all values specified in the SYSGEN command used to 
; invoke the server. Examine the values for validity,  and store them 
; away in the TSRV for future use. Initiate a connection to the port 
; driver, and establish ourselves as a listener.
;
; This routine is called from the stand-alone configure process (STACONFIG) 
; at IPL 0. All registers used in this routine are preserved except for R0.
;
; Inputs:
;
;	None
;
; Outputs:
;
;	R0  =	status code
;-

TMSCP$START::
	BBC	# TMSCP$V_DBG_START, -	; Not debugging - branch.
		TMSCP$L_DEBUG_FLAGS, 0$
	JSB	G^INI$BRK

0$:

	.IF DEFINED DEBUG$PC_HISTORY
;
; Do any initialization needed for tracing a history of the PC as
; the server code is executed.
;
	TSTL	TMSCP$PCHISTBEG			; PC history buffer initialized?
	BNEQ	1$				; Yes - branch.
	MOVAL	TMSCP$PCHISTBUF,TMSCP$PCHISTBEG	; Initialize the table address
	MOVAL	TMSCP$PCHISTBUF,TMSCP$PCHISTCUR	; Start current at the table start
	MOVAL	TMSCP$PCTBLEND, TMSCP$PCHISTEND	; Address of the end of the table
	CLRL	TMSCP$PCHISTFUL			; Clear the counter to start

1$:
	BSBW	TMSCP$PCHIST			; Log the initialization.
	.ENDC  ;DEBUG$PC_HISTORY

	PUSHR	#^M<R1,R2,R5>		; Save the registers to be used
	MOVL	G^SCS$GL_TMSCP,R5	; Get the address of the TSRV structure
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers destroyed by MOVC5
	MOVC5	#0,(SP),#0,-		; Initialize entire structure in
		#TSRV$K_LENGTH-TSRV$W_SIZE, -	;  in one fell swoop. 
		TSRV$W_SIZE(R5)		; Don't initialize first 2 longwords
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers destroyed by MOVC5

; 
; Initialize size, type, and subtype.
;
	MOVW	#TSRV$K_LENGTH,-	; Save the length of the structure
		TSRV$W_SIZE(R5)		;  in the size field
	MOVB	#DYN$C_TSRV,-		; This structure belongs
		TSRV$B_TYPE(R5)		;  to the tape server.
	MOVB	#DYN$C_TSRV_TSRV,-	; The substructure is
		TSRV$B_SUBTYPE(R5)	;  a TSRV.

;
; Initialize the list heads in the TSRV.
;
	MOVAL	TSRV$L_HQB_FL(R5),-	; Initialize the forward and
		TSRV$L_HQB_FL(R5)	;  backward pointers to point to
	MOVAL	TSRV$L_HQB_FL(R5),-	;  the HQB forward link
		TSRV$L_HQB_BL(R5)	; 
	MOVAL	TSRV$L_UQB_FL(R5),-	; Initialize the forward and
		TSRV$L_UQB_FL(R5)	;  backward pointers to point to
	MOVAL	TSRV$L_UQB_FL(R5),-	;  the HQB forward link
		TSRV$L_UQB_BL(R5)	; 

;
; The TMSCP version is set to zero. Host class drivers must supply a 
; TMSCP version number when setting the controller characteristics. If
; a version of TMSCP is ever introduced that is incompatable with the 
; current version this number will be changed.
;  Set the multi-host controller flag. This notifies the class drivers
; on the remote systems to use the allocation class passed in the 
; attention message instead of using their local value.
;                                         
	MOVW	#VERSION,-		; Set the server version number 
		TSRV$W_VERSION(R5)	;  to return in Set Unit Char
	MOVW	#MSCP$M_CF_MLTHS,-	; Identify ourself as a
		TSRV$W_CFLAGS(R5)	;  multi host controller

;
; Uniquely identify this controller among all devices accessable via T/MSCP.
;
	MOVQ	G^SCS$GB_SYSTEMID,-	; Construct the controller ID using
		TSRV$Q_CTRL_ID(R5)	;  the system ID for the first three
	MOVB	#MSCP$K_CM_EMULA,-	;  words, then use an emulator for
		TSRV$Q_CTRL_ID+6(R5)	;  the controller model, and 
	MOVB	#MSCP$K_CL_CNTRL,-	;  define the device class as an
		TSRV$Q_CTRL_ID+7(R5)	;  MSCP controller.
	MOVAL	TMSCP$MV_SET_OFFLINE,-	; Set address of TMSCP$MV_SET_OFFLINE 
		G^SCS$GL_TMSCP_MV	;  into cell for mount verification
;
; Establish a link with PORT_DRIVER
;
	LOCK	LOCKNAME=SCS,-		; Lock SCS access
		PRESERVE=NO, -		; Don't preserve R0
		SAVIPL=-(SP)		; Save the current IPL.
;	.SAVE	LOCAL_BLOCK
;	.PSECT	$ABS$,ABS
;	.RESTORE
;	 MFPR	S^#PR$_IPL,-(SP)
;	BLBC	G^SMP$GL_FLAGS,30006$
;	 MOVZBL	S^#SPL$C_SCS,R0
;	 JSB	G^SMP$ACQUIRE
;	BRB	30007$
;30006$:
;		MTPR	S^#IPL$_SCS,S^#PR$_IPL
;30007$:
	LISTEN	MSGADR=W^TMSCP$LISTEN,-	; Forwarding address
		ERRADR=W^20$,-		; If an error occurs during the listen
		LPRNAM=W^TMSCP$SERV_NAME,-
		PRINFO=W^TMSCP$SERV_INFO
;		PUSHAB	B^30011$
;		PUSHAB	W^TMSCP$SERV_INFO
;		PUSHAB	W^TMSCP$SERV_NAME
;		PUSHAB	W^20$
;		PUSHAB	W^TMSCP$LISTEN
;		JMP	G^SCS$LISTEN
;30011$:
	UNLOCK	LOCKNAME=SCS,-		; Release SCS access
		PRESERVE=NO,-		; Don't preserve R0,
		NEWIPL=(SP)+,-		;  lower the IPL and
		CONDITION=RESTORE	;  restore spinlock to previous state
;	BLBC	G^SMP$GL_FLAGS,30014$
;	 MOVZBL	S^#SPL$C_SCS,R0
;	 JSB	G^SMP$RESTORE
;30014$:
;	 MTPR	(SP)+,S^#PR$_IPL
	MOVL	#SS$_NORMAL,R0		; Set success
	.IF DEFINED DEBUG$LOG
	BSBW	TMSCP$CREATE_LOG	; Allocate T/MSCP packet logging buffer.
	.ENDC	; DEBUG$LOG

10$:
	POPR	#^M<R1,R2,R5>		; Restore the registers 
	RSB				; Return to caller

;
; If an error occurs during the listen macro, control will be passed here.
; the connection is simply disconnected (no reason given) and control is
; returned to caller.
;

20$:
	.IF	DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST		; Log the PC for debugging.
	.ENDC  ;DEBUG$PC_HISTORY
	DISCONNECT #1			; Do a disconnect
;		MOVL	#1, R0
;		JSB	G^SCS$DISCONNECT
	UNLOCK	LOCKNAME=SCS,-		; Release SCS access
		PRESERVE=NO,-		; Don't preserve R0,
		NEWIPL=(SP)+,-		;  lower the IPL and
		CONDITION=RESTORE	;  restore spinlock to previous state
;	BLBC	G^SMP$GL_FLAGS,30020$
;	 MOVZBL	S^#SPL$C_SCS,R0
;	 JSB	G^SMP$RESTORE
;30020$:
;	 MTPR	(SP)+,S^#PR$_IPL
	BRB	10$


	.SBTTL	-	ADD - Add a Tape Unit

;+
; Functional Description:
;
; This routine is called from ADDUNIT when a SET DEV/SERVED command is 
; executed. This routine is called, and executes while holding the 
; SPL$C_SCS spinlock. An MSCP attention message is sent out to all 
; known hosts to notify them of the availability of this tape.
;
;
; Inputs:
;
;	AP -->	argument count
;		action code (ADD)
		UQB = 8	    		; UQB address
;
; Outputs:
;
;	R0  =  status code to be returned to the caller
;	R3  =  Host Request Buffer address
;-


TMSCP$ADD::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

;
; If this routine is stalled and has to wait for resources while trying
; to allocate a message buffer, we want to return to the caller to resume
; working on something else. 
; 
	PUSHR	#^M<R1,R2,R4,R5>	; Save all the registers before starting
	BSBB	10$			; Treat this whole routine as a big
	POPR	#^M<R1,R2,R4,R5>	;  subroutine so that if this thread
	RSB				;  stalls the registers can be restored

;
; Send out an attention message to every host we know, anouncing
; the discovery of a new tape.
;

10$:
	MOVL	G^SCS$GL_TMSCP,R5	; Pick up the TSRV address
	MOVZWL	# SS$_NORMAL, R0	; No hosts found status.
	MOVL	TSRV$L_HQB_FL(R5),R1	; Get the address of the first HQB
	MOVL	R1,R2			; and save it for checking.

	ASSUME	HQB$L_FLINK EQ 0

13$:
	CMPL	HQB$L_FLINK(R1),R2	; Check for an empty queue
	BNEQ	19$			; Queue not empty, continue

15$:
	RSB				; No good hosts, return

19$:
	TSTL	HQB$L_CDT(R1)		; Check the HQB for a good CDT address
	BNEQ	20$			;  Good CDT.
	MOVL	HQB$L_FLINK(R1),R1	;  No CDT... move on to the next host
	BRB	13$			;  and try again

20$:
	BSBW	TMSCP$ALLOCATE_HRB	; Allocate and set up an HRB
					; IRP included automatically
	BLBC	R0,15$			; Return to the caller with the error
	MOVL	UQB(AP),R4		; Get the UQB address
	MOVL	R4,HRB$L_UQB(R3)	; Save this address
	INCW	UQB$W_CURRENT(R4)	; Another request active on this unit

;
; The attention message will actually be sent to all hosts that are linked
; into the HQB queue in the TSRV. 
;

30$:
	MOVL	R1,HRB$L_HQB(R3)	; Store the address of the HRB.
	INSQUE	HRB$L_FLINK(R3), -	; Insert the HRB into tail of queue.
		@HQB$L_HRB_BL(R1)
	INCW	HQB$W_NUM_QUE(R1)	; Bump host queue reference count
	CMPW	HQB$W_NUM_QUE(R1),-	; Is this number <= max?
		HQB$W_MAX_QUE(R1)	; 
	BLEQU	40$			; Yes, skip
	MOVW	HQB$W_NUM_QUE(R1),-	; No, set new max
		HQB$W_MAX_QUE(R1)	;

40$:
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Put the IRP address in R5
	MOVAL	IRP$L_FQFL(R5),R5	; Move down to the CDRP portion
	MOVL	HQB$L_CDT(R1),R4	; Get the address of the CDT
;
; Initialize the CDRP
;
	MOVL	R4,CDRP$L_CDT(R5)	; Get the address of the CDT 
	MOVL	CDT$L_PDT(R4),R4	; Prepare the PDT address for SCS
	MOVL	R4,HRB$L_PDT(R3)	;  and also store it away for future use

;
; Allocate and send out the attention message. Set the state, in case
; the connection to the host is lost, and we need to clean up. This module
; is called like a subroutine to allow us an opportunity to clean up the
; registers that were pushed on the stack in the event of failure.
;
	MOVW	#HRB$K_ST_MSG_WAIT,-	; Set the state of the request to
		HRB$W_STATE(R3)		;  reflect possible stall 
	ALLOC_MSG_BUF			; Allocate a message buffer
;		JSB	@PDT$L_ALLOCMSG(R4)
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers destroyed by MOVC5

	ASSUME	MSCP$K_MXCMDLEN LE MSCP$K_LEN

	MOVC5	#0,(SP),#0,-		; Initialize entire structure in
		#MSCP$K_LEN,(R2)	;  in one fell swoop. 
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers destroyed by MOVC5
	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis
	BLBC	R0, 60$			; If LBC connection is broken, skip host

;
; The address of the allocated message buffer is returned in R2.
; Initialize the proper fields to turn the message buffer into an
; attention message.
;
	MOVL	R2,HRB$L_MSGBUF(R3)	; Save the message buffer address
	MOVB	#MSCP$K_OP_AVATN,-	; Set the attn msg op-code
		MSCP$B_OPCODE(R2)	;  
	MOVL	HRB$L_UQB(R3),R4	; Get the address of the UQB
	MOVW	UQB$W_SLUN(R4),-	; Get the unit number
		MSCP$W_UNIT(R2)
	MOVQ	UQB$Q_UNIT_ID(R4),-	; Copy unit identifier
		MSCP$Q_UNIT_ID(R2)	; 
	MOVW	UQB$W_UNIT_FLAGS(R4),-	; Get the unit_flags
		MSCP$W_UNT_FLGS(R2)	; 
	MOVL	UQB$L_UCB(R4),R1	; Get the UCB address
	MOVL	UCB$L_MEDIA_ID(R1),-	;  then copy the media identifier
		MSCP$L_MEDIA_ID(R2)	;  from the UCB into the msg packet

;
; Get the CDRP fields and the registers properly set up to send out the 
; attention message to all interested hosts.
;
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the address of IRP for this 
	MOVAL	IRP$L_FQFL(R5),R5	;  request and position at the CDRP
	CLRL	CDRP$L_RWCPTR(R5)	; 
	CLRL	CDRP$L_RSPID(R5)	; Clear since no response is expected
	MOVL	HRB$L_PDT(R3),R4	; Pick up the PDT address for SCS
	MOVL	#MSCP$K_LEN,R1		; Size of the message to be sent
	CLRL	HRB$L_MSGBUF(R3)	; The message buffer is no longer ours

	.IF DEFINED DEBUG$LOG

	ASSUME TSRV$V_LOG_ENABLD  EQ  0

	MOVL	G^SCS$GL_TMSCP,R0	; Get the TSRV address.
	BLBC	TSRV$W_STATE(R0),50$	; Branch if logging is disabled.
	BSBW	TMSCP$LOG_END_PKT	; Otherwise, log the attention message.

50$:
	.ENDC	; DEBUG$LOG

	MOVW	#HRB$K_ST_SNDMS_WAIT,-	; Set the state for this request
		HRB$W_STATE(R3)		;  before calling the SCS service
	SEND_CNT_MSG_BUF		; Send it out
;		JSB	@PDT$L_SNDCNTMSG(R4)
	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis

;
; Remove the HRB from the queue and loop to send the same attention 
; message to the next host. Continue sending out these messages until 
; all the hosts have been notified.
;

	ASSUME	HRB$L_FLINK EQ 0

60$:
	REMQUE	HRB$L_FLINK(R3), R3	; Take the HRB out of the queue
	MOVL	HRB$L_HQB(R3),R5	; Restore the HQB address
	DECW	HQB$W_NUM_QUE(R5)	; Take care of the reference count
	CLRL	HRB$L_FLINK(R3)		; Show this HRB as unhooked

70$:
	MOVL	HQB$L_FLINK(R5),R1	; Get the addr of the next HQB
	MOVL	HQB$L_TSRV(R5),R5	; Get the address of the TSRV structure
	MOVAL	TSRV$L_HQB_FL(R5),R0	; If the next HQB address does not 
	CMPL	R0,R1			;  not match the queue header
	BEQL	90$			;  then we have another host to notify
	TSTL	HQB$L_CDT(R1)		; Before sending out the message, check
	BNEQ	85$			;  for a valid CDT. If none, this host.
	MOVL	R1,R5			;  Get the next one off the queue.
	BRB	70$			;

85$:
	BRW	30$			; This one is good, send the message

;
;		Final status is set by CLEANUP_HRB which places SS$_NORMAL in R0.
;

90$:
	BRW	TMSCP$CLEANUP_HRB	; We are finished with this request.


	.SBTTL	-	NEW_DEVICE - Serve a new DSA device unit

;+
; Functional Description:
;
; This is an entry point provided for the tape class driver to call 
; directly with the address of a newly discovered UCB. This avoids the
; time delay that used to occur when the tape had to be discovered by
; the configure process. This change was originally conceived to solve
; a problem with mounting shadow set virtual units cluster wide, but
; has found universal benefit, and so now is used for all newly found
; DSA (tape class driver) devices.
;
;
; Inputs:
;
;	R5  =  UCB address of device to be served
;
; Outputs:
;
;	R0  =  status code to be returned to the caller
;		SS$_NORMAL 	- Unit Available attention messages have been sent
;		SS$_DEVACTIVE	- This device is already being served
;		SS$_DEVICEFULL	- There are already MAX_UNITS being served
;		SS$_DEVNOTDISM	- This device is not mounted for cluster access
;		SS$_INSFMEM	- Not enough memory to allocate HQB
;
;	R5  =  UCB address of device to be served
;-

; 
; This is a list of the premature end codes. 
;

TMSCP$NOSERV::	
TMSCP$NORMAL::
	MOVL	#SS$_NORMAL,R0		; They already have what they want

TMSCP$RESTOR::
	POPR	#^M<R1,R2,R3,R4,AP>	; Restore all the registers used 
	RSB				;  and return to the caller

TMSCP$NEW_DEVICE::
	.IF	DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST		; Log the PC for debugging.
	.ENDC  ;DEBUG$PC_HISTORY
	PUSHR	#^M<R1,R2,R3,R4,AP>	; Save all the registers before starting

;
; If auto-serving is not turned on, we know this device should not be served. 
; Control can be returned to caller with no further action. Otherwise, the 
; device  information needs to be checked to make sure it complies with the 
; serving guidelines.
;

	CMPB	#DC$_TAPE, -		; Make sure this device is a tape
		UCB$B_DEVCLASS(R5)	;  before we go any farther
	BNEQ	TMSCP$NORMAL		; Not a tape, get out now!
	MOVL	UCB$L_DDB(R5),R4	; Get the DDB address
	CMPL	G^CLU$GL_TAPE_ALLOCLS,-	; Compare allocation class of host
		DDB$L_ALLOCLS(R4)	;  with allocation class of device
	BNEQ	TMSCP$NOSERV		; Must be the same (for now at least)
	BBS	#DEV$V_SRV,-		; If the device is already
		UCB$L_DEVCHAR2(R5),-	;  served,
		TMSCP$NOSERV		;  don't serve it again

;
; Now we are dealing with a valid device for serving. Check for a local 
; connection that the allocation class is properly set.
; 
	BBC	#DEV$V_MSCP,-		; If not a MSCP tape, this device is
		UCB$L_DEVCHAR2(R5),10$	;  ok to serve due to non-zero serve_all
	BBS	#DEV$V_CDP,-		; If the class driver created this UCB
		UCB$L_DEVCHAR2(R5),-	;  for temporary use, this is not a real
		TMSCP$NOSERV		;  device and should not be served.
	MOVL	UCB$L_CDDB(R5),R0	; Get a pointer to the CDDB
	BEQL	TMSCP$NOSERV		; If this is a "temp" UCB don't serve
	TSTL	G^CLU$GL_TAPE_ALLOCLS	; If the allocation class of this
	BNEQ	7$			;  node is zero,

5$:
	BBC	#MSCP$V_CF_MLTHS,-	; or the controller for this device
		CDDB$W_CNTRLFLGS(R0),7$	;  is an HSC, don't serve it.

6$:
	BRW	TMSCP$NOSERV		; Provide a place to reach exit

7$:
	CMPB	#MSCP$K_CM_EMULA,-	; Otherwise,
		CDDB$B_CNTRLMDL(R0)	; If this is not a served path
	BNEQ	10$			; Serve the device       

	BBC	#DEV$V_2P,-		; Check to make sure this is a 
		UCB$L_DEVCHAR2(R5),6$	;  dual path device
	MOVL	UCB$L_2P_CDDB(R5),R0	; Get a pointer to the CDDB
	BEQL	6$			; Give up if there is no alternate
	CMPB	#MSCP$K_CM_EMULA,-	; Check to see if the alternate path
		CDDB$B_CNTRLMDL(R0)	;  is to a server also
	BEQL	6$			; This is a server path too give up 

10$:
	MOVL	G^SCS$GL_TMSCP,R4	;  and the address of the TSRV 
	CLRL	R3			; Init this reg for table index later
	MOVZWL	TSRV$W_NUM_UNIT(R4),R0	; Get the number of units
	CMPL	R0,#MAX_UNITS		;  and see if we are over the max
	BGTR	TMSCP$FULL		;  if we are, return an error

	BBC	#DEV$V_MNT,-		; If the tape is not mounted yet,
		UCB$L_DEVCHAR(R5),20$	;  we can begin serving it
	BRB	TMSCP$NOTSHR		; Can't serve an allocated device.

;
; Search through the UQB queue to make sure that a UQB for this unit does 
; not already exist. If none is found, memory is allocated, the UQB is 
; filled in, and linked into the UQB list in proper order by ascending 
; unit number.
;

20$:
	MOVL	TSRV$L_UNITS(R4)[R3],R1	; Get UQB address from the table
	BEQL	TMSCP$CREATE_UQB	; If this is the end, create one
	INCL	R3			; Add one to the index
	CMPL	UQB$L_UCB(R1),R5	; See if the UCB for this unit matches
	BNEQ	20$			; No match, keep looking
	BRW	TMSCP$NORMAL		; They already have what they want

TMSCP$FULL::
	MOVL	#SS$_DEVICEFULL,R0	; There are already MSX_UNITS
	BRW	TMSCP$RESTOR		;  being served

TMSCP$NOTSHR::
	MOVL	#SS$_DEVNOTDISM,R0	; This device is not mounted for 
	BRW	TMSCP$RESTOR		;  cluster wide access

TMSCP$NOMEM::
	MOVZWL	#SS$_INSFMEM,R0		; Not enough memory to allocate
	BRW	TMSCP$RESTOR		;  a decent UQB

;
; There is no UQB for a device with this unit number.
;

TMSCP$CREATE_UQB::
	.IF	DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST		; Log the PC for debugging.
	.ENDC  ;DEBUG$PC_HISTORY
	MOVZWL	#UQB$K_LENGTH,R1	; Set size of the UQB structure
	JSB	G^EXE$ALONONPAGED	; Grab some pool
	BLBC	R0,TMSCP$NOMEM		; If there was insf memory for alloc

	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers destroyed by MOVC5
	MOVC5	#0,(SP),#0,-		; Initialize entire structure in
		#UQB$K_LENGTH,(R2)	;  in one fell swoop. 
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers destroyed by MOVC5
;
; Make the allocated memory look like a UQB. R2 points to the allocated region
;
	BISL	#<DEV$M_CLU!-		; Set the 'Avail Cluster wide' bit
		DEV$M_SRV>,-		;  and 'MSCP SERVER' bits
		UCB$L_DEVCHAR2(R5)
	MOVW	R1,UQB$W_SIZE(R2)	; Set the size
	MOVL	R5,UQB$L_UCB(R2)	; Save away the UCB address
	MOVB	#DYN$C_TSRV,-		; This is the tape based server
		UQB$B_TYPE(R2)		;  data structure
	MOVB	#DYN$C_TSRV_UQB,-	; More specifically, it is a 
		UQB$B_SUBTYPE(R2)	;  Unit Queue Block structure
	MOVW	#UQB$K_ST_AVAILABLE,-	; Set the initial state of this
		UQB$W_STATE(R2)		;  unit to available
	MOVW	UCB$W_UNIT(R5),-	; Get the "real" unit number
		UQB$W_UNIT(R2)		;  (the one off the plug)

	MOVL	UCB$L_DDB(R5),R0	; Get the device data block
	MOVL	DDB$L_ALLOCLS(R0),-	; Move the allocation class into
		UQB$L_ALLOCLS(R2)	;  the unit ID field
	MOVW	TSRV$W_NUM_UNIT(R4),-	; Then assign the local unit
		UQB$W_SLUN(R2)		;  number (unit table index)
	BISW	#MSCP$M_SLUN,-		; Set the bit in the unit number
		UQB$W_SLUN(R2)		;  indicating a server local unit
	INCW	TSRV$W_NUM_UNIT(R4)	; Ratchet the number of known units
	MOVZBL	DDB$T_NAME+1(R0),R1	; Put the first letter in a reg
	SUBB2	#^X40,R1		;  and normalize it 
	INSV	R1,-			; Add to those the first letter of 
		#UQB$V_D0,-		;  the device name
		#UQB$S_D0,-		; 
		UQB$W_DEVNAME(R2)	; 
	MOVZBL	DDB$T_NAME+2(R0),R1	; Put the second character in a reg
	SUBB2	#^X40,R1		;  and normalize it
	INSV	R1,-			; Get the second letter
		#UQB$V_D1,-		;  of the device name
		#UQB$S_D1,-		;  and save that also
		UQB$W_DEVNAME(R2)	; 
	MOVZBL	DDB$T_NAME+3(R0),R1	; Put the controller letter in a reg
 	SUBB2	#^X40,R1		;  and normalize that before saving
	INSV	R1,-			; Retrieve the controller letter
		#UQB$V_C,-		;  and save that away as the
       		#UQB$S_C,-		;  last of the device name
		UQB$W_DEVNAME(R2)	;  to be saved

;
; For the unique multi unit code, use the server local unit number as the 
; spindle specifier in the high byte, and set the low byte (access path) 
; to zero.
;
	MOVZWL	UQB$W_SLUN(R2),R0	; Get the "local" unit number
	BICL	#<MSCP$M_SHADOW!-	; Clear out the shadow bit
		MSCP$M_SLUN>,R0		;  and the SLUN bit before range checking
	MOVL	R2,TSRV$L_UNITS(R4)[R0]	; Save the UQB address in the table
	ASHL	#8,R0,R0		; Shift it into the high byte
	MOVW	R0,UQB$W_MULT_UNIT(R2)	; Save it away in the UQB
	MOVW	UCB$W_UNIT_FLAGS(R5),-	; Use the unit flags from the UCB 
		UQB$W_UNIT_FLAGS(R2)	;  just as they are in the UQB
	BBC	#DEV$V_SWL,-		; Check the device characteristics
		UCB$L_DEVCHAR(R5),60$	;  for software writelock
	BISW	#MSCP$M_UF_WRTPH,-	; If it is, set the hw flag 
		UQB$W_UNIT_FLAGS(R2)	;  so we don't issue writes to it

;
; Set the queue header counters and list heads to their initial values.
;

60$:
	MOVAL	UQB$L_BLOCKED_FL(R2),-	; Initialize the forward and 
		UQB$L_BLOCKED_FL(R2)	; 
	MOVAL	UQB$L_BLOCKED_FL(R2),-	;  backward pointers to point to
		UQB$L_BLOCKED_BL(R2)	;  the forward link of the blocked que
	INSQUE	UQB$L_FLINK(R2), -	; Insert the UQB into the queue
		@TSRV$L_UQB_BL(R4)

;
; Go off to MSCP to ADD a tape unit. When the ADDUNIT routine is eliminated,
; change this call format to use a simple JSB and leave the stack alone. 
;
	PUSHL	R2			; Copy UQB pointer
	PUSHL	#TSRV$K_AR_ADD		; Opcode for MSCP action routines
	PUSHL	#2			; Only 2 parameters
	MOVL	SP,AP			; Set address of parameter area
	JSB	TMSCP$ADD		; Go to the routine
	ADDL	#12,SP			; Clean up the stack before returning
	BRW	TMSCP$NORMAL		; Return a successful status


	.SBTTL	-	MV_SET_OFFLINE - Mount Verification set a device to the "offline" state.

;+
; Functional Description:
;
; This routine is JSB'd to from a mount verification thread. 
; The UCB of a particular tape is passed to this routine in
; R5. The corresponding UQB for this device is found, the
; field UQB$L_ONLINE_HQB is cleared, and the state is changed to
; "AVAILABLE".
;
;
; Inputs:
;
;	R5  =  UCB address
;
; Outputs:
;
;	None - all registers preserved
;-

TMSCP$MV_SET_OFFLINE::
	PUSHR	#^M<R0,R1,R2,R4>	; Save all the registers before starting
	LOCK	LOCKNAME=SCS,-		; Lock SCS access
		PRESERVE=NO,-		;  no need to preserve R0
		SAVIPL=-(SP)		;  save the current IPL
;	 MFPR	S^#PR$_IPL,-(SP)
;	BLBC	G^SMP$GL_FLAGS,30026$
;	 MOVZBL	S^#SPL$C_SCS,R0
;	 JSB	G^SMP$ACQUIRE
;	BRB	30027$
;30026$:
;		MTPR	S^#IPL$_SCS,S^#PR$_IPL
;30027$:
	.IF	DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST		; Log the PC for debugging.
	.ENDC  ;DEBUG$PC_HISTORY
	MOVL	G^SCS$GL_TMSCP,R1	; Get the address of the server
	MOVAL	TSRV$L_UQB_FL(R1),R4	; Get the queue list head address
	MOVL	R4,R2			;  and save it to verify the whole

	ASSUME	UQB$L_FLINK EQ 0

10$:
	MOVL	UQB$L_FLINK(R4),R4	;  queue has been searched
	CMPL	R4,R2			; End of UQB list?
	BEQL	60$			; If eql yes
	CMPL	UQB$L_UCB(R4),R5	; Is this the UQB for this unit?
	BNEQ	10$			; If neq no

;
; A UQB was found that corresponds to the UCB address passed.
; The HQB field that represented the host that held 
; the tape online is cleared. The UCB online count is sanity tested.
;

	MOVW	#UQB$K_ST_AVAILABLE,-	; Now set the state of this device to
		UQB$W_STATE(R4)		;   offline.
	CLRL	UQB$L_ONLINE_HQB(R4)	; Not online to any host now.
	DECB	UCB$B_ONLCNT(R5)	;  and note the change in online count
	TSTB	UCB$B_ONLCNT(R5)	; Online count should be zero
	BEQL	60$			;

	BUG_CHECK MSCPSERV, FATAL	; Online count was not "1" when we entered
;		.WORD	^XFEFF
;		.IIF IDN <FATAL>,<FATAL> , .WORD	BUG$_MSCPSERV!4
					;  this routine.

60$:
	UNLOCK	LOCKNAME=SCS,-		; Release SCS access
		PRESERVE=NO,-		;  don't preserve R0
		NEWIPL=(SP)+,-		;  return to former IPL
		CONDITION=RESTORE	;  restore spinlock to previous state
;	BLBC	G^SMP$GL_FLAGS,30033$
;	 MOVZBL	S^#SPL$C_SCS,R0
;	 JSB	G^SMP$RESTORE
;30033$:
;	 MTPR	(SP)+,S^#PR$_IPL
	POPR	#^M<R0,R1,R2,R4>	; Restore all the registers 
	RSB				; And resume mount verification


	.SBTTL	-	LISTEN - Listen for a Connect Request

;+
; Functional Description:
;
; When another cluster member wants to connect with the server, control
; is passed here as a fork process initiated by the port driver at IPL$_SCS.
; Upon a successful acceptance, this routine allocates a host queue block 
; and is then prepared to receive MSCP request packets from that host.
;
; The connection application itself is not treated as a true request. No 
; HRB is created for it, and no context is saved. It is either accepted or 
; rejected in this routine.
;
; Inputs:                                 
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Connect message packet address
;	R3  =  CDT
;	R4  =  PDT
;	R5  =  Scratch
;
; Outputs:
;	None (the connection is either accepted or rejected)
;
; All registers are preserved.
;-

TMSCP$LISTEN::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

;
; Allocate an HQB for this host.
;

10$:
	PUSHL	R2			; Save the connect message address
	MOVZWL	#HQB$K_LENGTH,R1	; Get the size of the host queue block
	JSB	G^EXE$ALONONPAGED	;  and allocate that much memory
	MOVL	R2,R5			; Get the HQB address in R5
	POPL	R2			; Pop the saved connect message address
	BLBS	R0,15$			; Continue if successful
	.IF DEFINED DEBUG$REJECT_TRACKING
	MOVL	G^ SCS$GL_TMSCP, R1	; Get TSRV address.
	MOVL	R0,TSRV$L_REPLC_CNT+8(R1); Save away the error code
	INCL	TSRV$L_REPLC_CNT+12(R1)	; Save away a record of the reject
	.ENDC	   ;DEBUG$REJECT_TRACKING
	BRW	TMSCP$REJECT		; Reject this connection.

;
; Fill in as many fields as possible to initialize the HQB for use.
;

15$:
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers destroyed by MOVC5
	MOVC5	#0,(SP),#0,-		; Initialize entire structure in
		#HQB$K_LENGTH,(R5)	;  in one fell swoop. 
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers destroyed by MOVC5
	MOVW	R1,HQB$W_SIZE(R5)	; Set length of the data structure
	MOVB	#DYN$C_TSRV,-		; The structure type
		HQB$B_TYPE(R5)		;  is tape server
	MOVB	#DYN$C_TSRV_HQB,-	; The structure subtype
		HQB$B_SUBTYPE(R5)	;  is Host Queue Block

	MOVL	<SCS$B_CON_DAT-SCS$T_DST_PROC>(R2),R0	; Get the address of
					;  the data field
	BEQL	25$			; Can't talk to V4.x class drivers - branch.
	CMPL	R0,#^A/V5.2/		; Does remote class driver support tape
	BEQL	30$			; servers?  Yes - branch.

25$:					; If not, we can't communicate.
	BRW	TMSCP$REJECT_REQUEST	; Kill this request.

30$:
	BISW	#HQB$M_V5CL,-		; Tape servers supported, use SLUNS (V5).
		HQB$W_FLAGS(R5)		;  flag (let's save a flag in $HQBDEF.)
	MOVAL	HQB$L_HRB_FL(R5),-	; Initialize the list head for the
		HQB$L_HRB_FL(R5)	;  Host Request Blocks that are
	MOVAL	HQB$L_HRB_FL(R5),-	;  currently being processed on 
		HQB$L_HRB_BL(R5)	;  behalf of this host
	MOVL	R3,HQB$L_CDT(R5)	; Save away the CDT address
	MOVL	G^SCS$GL_TMSCP,R1	; Get the TSRV address
	MOVL	R1,HQB$L_TSRV(R5)	;  and store it away in the HQB

	CMPW	TSRV$W_NUM_HOST(R1),-	; Max number of hosts being served?
		#MAX_HOSTS		;
	BGEQ	TMSCP$NO_ROOM		;  beyond the end of the table
	MOVW	#60,HQB$W_HTIMO(R5)	; Default host access timeout 

;
; ACCEPT the connection request, and link in the HQB.
;		R3  =  CDT address
;		R4  =  PDT address
;		R5  =  HQB address
;

	ASSUME	HQB$L_FLINK EQ 0

	ACCEPT	MSGADR=TMSCP$MSG_IN,-	; Message input address
		ERRADR=TMSCP$VC_ERR,-	; Virtual circuit error address
		INITCR=#30,-		; Initial credit extended
		MINSCR=#1,-		; Minimum send credit
		CONDAT=TSRV$W_VERSION(R1),- ; CONNECT/ACCEPT data
		AUXSTR=HQB$L_FLINK(R5)	; HQB address
;		PUSHAB	B^30037$
;	PUSHL	 #4
;		PUSHL	#0
;		PUSHL	#0
;		PUSHAB	HQB$L_FLINK(R5)
;		PUSHAB	TSRV$W_VERSION(R1)
;		MOVZBW	#0,-(SP)
;		MOVW	#0,-(SP)
;		MOVW	#1,-(SP)
;		MOVW	#30,-(SP)
;		PUSHAB	TMSCP$VC_ERR
;		PUSHL	#0
;		PUSHAB	TMSCP$MSG_IN
;		MOVL	#12,-(SP)
;		JMP	G^SCS$ACCEPT
;30037$:
	BLBS	R0,40$			; If acceptance was unsuccessful,
	MOVL	HQB$L_CDT(R5),R3	;  restore the CDT address and 
	.IF DEFINED DEBUG$REJECT_TRACKING
	MOVL	G^ SCS$GL_TMSCP, R1	; Get TSRV address.
	MOVL	R0,TSRV$L_REPLC_CNT+16(R1); Save away the error code
	INCL	TSRV$L_REPLC_CNT+20(R1)	;  and keep a count	
	.ENDC	   ;DEBUG$REJECT_TRACKING
	BRB	TMSCP$REJECT_CONNECTION	; Reject this request.

40$:
	MOVL	HQB$L_TSRV(R5),R1	; Restore the TSRV address 
	INSQUE	HQB$L_FLINK(R5),-	; Link this HQB into the list of
		@TSRV$L_HQB_BL(R1)	;  all hosts being served
	INCW	TSRV$W_NUM_HOST(R1)	; Add one to the host count
	MOVL	R3,HQB$L_CDT(R5)	; Keep the CDT address
	RSB

TMSCP$NO_ROOM::
TMSCP$REJECT_REQUEST::
	.IF DEFINED DEBUG$REJECT_TRACKING
	MOVL	G^ SCS$GL_TMSCP, R1	; Get TSRV address.
	MOVL	R0,TSRV$L_REPLC_CNT+24(R1); Save away the bit number
	INCL	TSRV$L_REPLC_CNT+28(R1)	;  and keep a count	
	.ENDC	   ;DEBUG$REJECT_TRACKING

TMSCP$REJECT_CONNECTION::
	MOVL	R5,R0			; Address of the structure to deallocate
	JSB	G^EXE$DEANONPAGED	; Free the memory allocated to the HQB

TMSCP$REJECT::
	REJECT	#1			; Reject the connect application
;		MOVL	#1, R0
;		JSB	@PDT$L_REJECT(R4)
	RSB				;  and return to the caller


	.SBTTL	-	Virtual Circuit Error Routine

;+
; Functional Description:
;
; This routine handles a virtual circuit failure, cleaning up all the 
; requests that were outstanding for the host whose connection failed,
; and then removing the HQB for the failed host from the T/MSCP server
; data structures. 
;
; This routine is set up as the virtual circuit error handling routine
; when the connect is issued. Control is passed here in fork context when
; a link failure is detected, or a disconnect is issued.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  CDT address
;	R4  =  PDT address
;	R5  =  Scratch
;
; Outputs:
;	
;	None
;
;-

TMSCP$VC_ERR_ABORT::
	RSB				; Out of line return to caller this
					;  connection is already being terminated

TMSCP$VC_ERR::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	CDT$L_AUXSTRUC(R3),R5	; Pick up the HQB address
	BBSS	#HQB$V_DISCON_INIT,-	; If this system is VERY low on NPP
		HQB$B_STATE(R5),-	;  this routine could be called from
		TMSCP$VC_ERR_ABORT	;  within the server (see TMSCP$MSG_IN)
	MOVL	HQB$L_TSRV(R5),R1	; Get the address of the server struct
	INCL	TSRV$L_VCFAIL_CNT(R1)	; Keep a count of the link disconnects

;
; Retrieve all CDRPs that are active on this CDT.
;
	PUSHR	#^M<R5>			; Save the registers from destruction
	SCAN_RSPID_WAIT-		; Scan message buffer and send credit wait
		action = TMSCP$UNHOOK_CDRP  ;  queues for CDRP with given CDT in R3.
;		MOVAB	TMSCP$UNHOOK_CDRP, R0
;		JSB	G^SCS$LKP_RDTWAIT
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	SCAN_RDT-			; Scan RDT for CDRP.
		action = TMSCP$UNHOOK_CDRP
;		MOVAB	TMSCP$UNHOOK_CDRP, R0
;		JSB	G^SCS$LKP_RDTCDRP
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	SCAN_MSGBUF_WAIT- 		; Scan RSPID wait queue for CDRP.
		action = TMSCP$UNHOOK_CDRP
;		MOVAB	TMSCP$UNHOOK_CDRP, R0
;		JSB	G^SCS$LKP_MSGWAIT
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	POPR	#^M<R5>			; Restore the saved registers

;
; Go through the list of host request blocks and dequeue all the outstanding
; requests that are not in progress. If the request involves a transfer that 
; is in progress, the HRB is marked "abort", and is dequeued when the transfer
; completes.
; 
; States: 
;	
;	HRB$K_ST_MSG_WAIT is set in 1) ADD when a message buffer is allocated
;			  to use as the buffer to notify all hosts of a new
;			  unit and 2) in TMSCP$SEND_END when a command packet is
;			  recycled to be an end packet.
;
;	HRB$K_ST_SEQ_WAIT is set in BLOCKED when a sequential command is put on
;			  the blocked queue.
;	
;	HRB$K_ST_SNDAT_WAIT is set in 1) READ when sending data to host buffer
;			    and 2) in WRITE when receiving data from the host.
;
;	HRB$K_ST_DRV_WAIT is set in SEND_IRP, the routine which queues an
;			  IRP to the local driver.
;
;	HRB$K_ST_MAP_WAIT is set in 1) READ when mapping a buffer for a read request
;			  and 2) in WRITE when mapping a buffer for a write request
;
;
	MOVAL	HQB$L_HRB_FL(R5),R1	; Get the address of the HRB list head
	MOVL	R1,R2			;  and move it to a new register

	ASSUME	HRB$L_FLINK EQ 0

30$:
	MOVL	HRB$L_FLINK(R1),R1	; Get the address of the next HRB
	CMPL	R1,R2			; If we are back at the list head,
	BEQL	70$			;  the queue had been traversed
	BISW	#HRB$M_ABORT,-		; Set the abort bit to signify that
		HRB$W_FLAGS(R1)		;  this transaction has been visited
	DISPATCH HRB$W_STATE(R1),-	; Dispatch based on request state
		type=W,-		;  which is a word field
		prefix=HRB$K_ST_,<-	; 
		<MSG_WAIT,   60$>,-	; Leave this request with the abort flag set
		<SEQ_WAIT,   60$>,-	; Command on the blocked queue can be punted now.
		<SNDAT_WAIT, 60$>,-	; Sending or receiving data
		<DRV_WAIT,   60$>,-	; Wait til the drivers done for this one
		<MAP_WAIT,   50$>,-	; The only way to get this is to yank it
		>			; Should not fail with requests in SNDMS state
;	CASEW	HRB$W_STATE(R1),#$$BASE,#$$LIMIT
;30038$:
;	.SIGNED_WORD	60$-30038$
;	.SIGNED_WORD	60$-30038$
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.SIGNED_WORD	60$-30038$
;	.SIGNED_WORD	60$-30038$
;	.SIGNED_WORD	50$-30038$
                                               
     	BUG_CHECK MSCPSERV,FATAL	; ERROR
;		.WORD	^XFEFF
;		.IIF IDN <FATAL>,<FATAL> , .WORD	BUG$_MSCPSERV!4

;
; Now all the request specific processing is done, do some common processing
; here, setting the flags and status bits.
;

50$:
	.IF DEFINED DEBUG$PC_HISTORY
	MOVL	R1, R0			; Get HRB address.
	BSBW	TMSCP$PCHIST_R3R0	; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	HRB$L_IRP_CDRP(R1),R0	; Get the address of the IRP-CDRP
	MOVAL	IRP$L_FQFL(R0),R0	;  and move down to the CDRP portion
	TSTL	CDRP$L_FQBL(R0)		; Check for the presence of a pointer
	BEQL	30$			; If the field is 0 don't dequeue

	ASSUME	CDRP$L_FQFL EQ 0

	CMPL	@CDRP$L_FQBL(R0),R0	; If the field is nonzero,
	BNEQ	30$			;  make sure it is in a queue

	ASSUME	CDRP$L_FQFL EQ 0

	REMQUE	CDRP$L_FQFL(R0), R0	; Remove the CDRP from its queue

	CMPW	#HRB$K_ST_MAP_WAIT,-	; Are we in a map wait
		 HRB$W_STATE(R1)	;  state ?
	BNEQ	60$			; BR if No
	BISW	#HRB$M_UNBLOCK,-	; Unblock this request after
		 HRB$W_FLAGS(R1)	;  cleanup
	

60$:
	.IF DEFINED DEBUG$PC_HISTORY
	MOVL	R1, R0			; Get HRB address.
	BSBW	TMSCP$PCHIST_R3R0	; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	TSTL	HRB$L_WAIT_FL(R1)	; Are we on the blocked queue? 
	BEQL	30$			; SNDDAT state during READ means 
					; we've already dequeued this request
	REMQUE	HRB$L_WAIT_FL(R1),R0	; Remove from UQB sequential wait queue.
	MOVL	HRB$L_UQB(R1),R0	; Get the address of the UQB
	DECW	UQB$W_NUM_QUE(R0)	; Decrement the number on the queue
	BISW	#HRB$M_STATE_INVALID,-	; The state of the request as indicated
		HRB$W_STATE(R1)		;  is no longer valid, change it
	BISW	#HRB$M_DEQUEUED,-	; Mark this request as unhooked... 
		HRB$W_FLAGS(R1)		;  its resources can be deallocated
	BRW	30$			; Check for more HRBs to process

;
; All requests for this host have been removed from the resource wait queues,
; we are almost ready to disconnect. There is one resource that must be dealt
; with before we issue the disconnect though, and that is the message buffer.
; Since we have to use the CDT to deallocate message buffers, they must all be 
; deallocated before the disconnect is issued.
;
; The current sanity subroutine cannot be called from any point from here to
; the end of this routine. If there were commands outstanding for the client
; node that just failed, the algorithm used to verify that the current counter
; is correct cannot be used. This is because the request in question has been
; removed from the wait queue (and the UQB num_que counter adjusted), but it
; has not been cleaned up (and the current counter adjusted). These counters
; are resynchronized at the end of this routine.
;

70$:
	MOVAL	HQB$L_HRB_FL(R5),R1	; Get the address of the HRB list head
	MOVL	R1,R2			;  and move it to a new register
	MOVL	HQB$L_CDT(R5),R3	; Get the CDT address for disc @90$

	ASSUME	HRB$L_FLINK EQ 0

80$:
	MOVL	HRB$L_FLINK(R1),R1	; Get the address of the next HRB
	CMPL	R1,R2			; If we are back at the list head,
	BEQL	90$			;  the queue had been traversed

;
; Deallocate any MSCP message buffer that is found. This may start up another
; thread, but it won't be one associated with this CDT since we have unhooked 
; all the requests pertaining to the connection that has failed before getting
; to this point.
;
	TSTL	HRB$L_MSGBUF(R1)	; Check for allocated message buffer
	BEQL	80$			; Continue, if none is allocated
	PUSHR	#^M<R1,R2,R3,R5>	; Save the registers from destruction
	MOVL	HRB$L_IRP_CDRP(R1),R5	; Get the address of the IRP-CDRP
	MOVAL	IRP$L_FQFL(R5),R5	;  and move down to the CDRP portion
	MOVL	HRB$L_MSGBUF(R1),R2	; Get the message buffer address
	CLRL	HRB$L_MSGBUF(R1)	;  and clear out the address in the HRB
	MOVL	HRB$L_HQB(R1),R0	; Get the address of the HQB
	MOVL	HQB$L_CDT(R0),R3	;  so we can get the CDT address
	DEALLOC_MSG_BUF_REG		; Get rid of the message buffer
;		JSB	@PDT$L_DEALRGMSG(R4)
	POPR	#^M<R1,R2,R3,R5>	; Restore the registers
	BRB	80$			; Move on to the next request

;
; In order to assure that none of the pending requests for this host are
; started up after the invocation of this routine, no resources held by
; pending requests are deallocated until the disconnect is issued. At that 
; time, resources allocated for all requests (except ones held by the tape 
; driver) can be deallocated.
;

90$:
	DISCONNECT			; The disconnect can be issued now, but
;		JSB	G^SCS$DISCONNECT
	CLRL	HQB$L_CDT(R5)		;  the CDT should not be used anymore!

;
; Now that the disconnect has been issued and the CDT has been returned, start
; any requests that had been blocked, and then continue cleaning up the 
; requests that were left aborted, making sure the units reflect the 
; change in state for the host that just went away.
;
	MOVL	HQB$L_TSRV(R5),R4	; Get the address of the server struct
	MOVAL	TSRV$L_UQB_FL(R4),R2	; Loop through the UQBs making
	MOVL	R2,R4			;  any units available that have this

	ASSUME	UQB$L_FLINK EQ 0

100$:
	MOVL	UQB$L_FLINK(R4),R4	;  host's online bit set.
	CMPL	R4,R2			; Check for the end of the queue
	BEQL	140$			; If they are the same...All done!
	CMPL	R5,UQB$L_ONLINE_HQB(R4)	; Online to the host represented by
					;  the HQB in R5?
	BNEQ	100$			; If not, check next unit.
;
; A unit was found that still thinks the host is out there. An HRB needs to 
; be allocated and the available processing done for this host. Since we
; no longer have a CDT, we will call the sequential command subroutine at
; its alternate entry point and get along the best we can without actually
; allocating a message buffer of our own.
;
	BSBW	TMSCP$ALLOCATE_HRB	; Returns the HRB address in R3
	BLBC	R0,120$			; Out of memory? BIG trouble
	BISW	#HRB$M_STATE_INVALID,-	; Start off with the state
		HRB$W_STATE(R3)		;  of this request as current
	BISW	#HRB$M_VCFAILED,-	; Set the bit indicating that this is
		HRB$W_FLAGS(R3)		;  not really a request from the host
	MOVL	R4,HRB$L_UQB(R3)	; Save the Unit Queue Block address
	MOVL	R5,HRB$L_HQB(R3)	; Save the Host Queue Block address
	INSQUE	HRB$L_FLINK(R3),-	; Link this HRB to the host that is
		@HQB$L_HRB_BL(R5)	;  failing to clean it up later
	INCW	HQB$W_NUM_QUE(R5)	; Up the counter of queue elements
	CMPW	HQB$W_NUM_QUE(R5),-	; Check to see if the new value is
		HQB$W_MAX_QUE(R5)	;  higher than any previous value
	BLEQ	110$			;  if its not, conitnue on
	MOVW	HQB$W_NUM_QUE(R5),-	; If it is, make the current value
		HQB$W_MAX_QUE(R5)	;  a new high water mark

110$:
	INCW	UQB$W_CURRENT(R4)	; Keep the HRB counter current
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	PUSHR	#^M<R2,R4,R5>		; Save the termination criteria
	CLRL	R2			; There is no MSCP packet availale
	MOVL	#MSCP$K_OP_AVAIL,R1	; Set an opcode value
	MOVL	G^SCS$GL_TMSCP,R4	; Get the address of the server struct
	INCL	TSRV$L_OPCOUNT(R4)	;  then set the count of total operations
	INCL	TSRV$L_OPCOUNT(R4)[R1]	;  and the count of avails straight
	BSBW	TMSCP$SEQ_ALT		; Call the seq command alt entry
	POPR	#^M<R2,R4,R5>		; Restore the saved ending criteria
	BRB	100$			; Continue checking...

;
; As an emergency measure, if there is insufficient memory to allocate a
; HRB, the online bits in the UQB for this host are cleared anyway, so that
; when the host establishes a new connection, it will not hang.
;

120$:
	CLRL	UQB$L_ONLINE_HQB(R4)	; No longer online to this host
	MOVL	UQB$L_UCB(R4),R0	; Get the UCB address
	BICW	#UQB$M_ONLINE,-		; Clear online bit in state field
		UQB$W_FLAGS(R4)		;

130$:
	DECB	UCB$B_ONLCNT(R0)	; Decrement the online count
	BRW	100$			; And continue with next unit...
;
; Now all the units that the failed host was holding online have been freed. 
; The next items to be taken care of are the requests that were pending when
; the virtual circuit to the host failed. First, try to deallocate the HQB.
; If there are no requests pending for this host, the HQB will be deallocated
; and we are finished. If there are HRBs that still have to be cleaned up,
; the deallocate attempt will return a failed status, indicating that more
; cleanup is necessary.
;

140$:
	CLRL	R4			; No UQB addr if no cmds active
	MOVAL	HQB$L_HRB_FL(R5),R2	; Save away the queue header address
	MOVL	R2,R3			; Get set up for the first element

150$:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

	ASSUME	HRB$L_FLINK EQ 0

	MOVL	HRB$L_FLINK(R3),R3	; Get the address of the next HRB 
	CMPL	R3,R2			; If it is the same as the header, 
	BEQL	160$			;  we are finished checking the queue 
	MOVL	HRB$L_UQB(R3),R4	; Get the addr of UQB from last command 
	BBS	#HRB$V_VCFAILED,-	; If this request is the result of the 
		HRB$W_FLAGS(R3),150$	;  VC failure let it finish normally 
	CMPW	#HRB$K_ST_DRV_WAIT,-	; If we are waiting for the tape 
		HRB$W_STATE(R3)		;  then continue waiting... 
	BEQL	150$			;  continue traversing the queue... 
	BBS	#HRB$V_DEQUEUED,-	; If the request has been dequeued
		HRB$W_FLAGS(R3),155$	;    cleanup
	CMPW	#HRB$K_ST_MAP_WAIT,-	; If we are waiting for the mapping 
		HRB$W_STATE(R3)		;  then continue waiting... 
	BEQL	150$			;  continue traversing the queue... 
	CMPW	#HRB$K_ST_SNDAT_WAIT,-	; If we are waiting for data transfer 
		HRB$W_STATE(R3)		;   to complete 
	BEQL	170$			;  we are hopelessly lost 
	BBS	#HRB$V_UNBLOCK,-	; If this bit is set we need to call 
		HRB$W_FLAGS(R3),155$	;  the UNBLOCK routine after cleanup 
	CLRL	HRB$L_UQB(R3)		;  Otherwise we can defuse it  
 
155$: 
	PUSHL	R5			; Save the HQB address
	BSBW	TMSCP$CLEANUP_HRB	; Deallocate the HRB and its resources
	POPL	R5			; Restore the saved HQB
	BRB	140$			;  Continue through the list

160$:
	BISB2	#HQB$M_VC_FAILED,-	; Set this bit in the status field
		HQB$B_STATE(R5)		;  indicating that the HQB can now be
	BRW	TMSCP$DEALLOC_HQB	;  deleted, and then try

170$:
	BUG_CHECK MSCPSERV,FATAL	; ERROR
;		.WORD	^XFEFF
;		.IIF IDN <FATAL>,<FATAL> , .WORD	BUG$_MSCPSERV!4


	.SBTTL	Main Line Routine

;+
; Functional Description:
;
; MSCP requests are received from remote hosts in this routine. The packet 
; is inspected for validity and if its good, an HRP is made up to represent
; this request. The request is then dispatched to the proper handling
; routine to be processed further.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Length of MSCP packet
;	R2  =  MSCP packet address
;	R3  =  CDT address
;	R4  =  PDT address
;	R5  =  Scratch
;
; Oututs:
;
;	R0  =  When an error is encountered R0 is packed with an op-code
;		an error code and the offset of the bad field within the
;		MSCP packet.
;	R3  =  HRB address
;-
 
;
; Out of line error routines are placed before this module so they can be 
; reached with condition code branch instructions to optimize the main path.
;
; There was insufficient memory on the server system to allocate the required
; data structures to accept this request. In this case we will deallocate the
; MSCP packet we have, and take down the virtual circuit. 
;

TMSCP$NO_MEM::
	MOVL	R4,R3			; Put the CDT address back
	MOVL	CDT$L_PDT(R3),R4	;  and then restore the PDT address
	DEALLOC_MSG_BUF_REG		; Deallocate the message buffer
;		JSB	@PDT$L_DEALRGMSG(R4)
	BRW	TMSCP$VC_ERR		; Break and clean up virtual curcuit

;
; First, make sure we have a valid MSCP command packet. Check to make sure
; the incoming packet is the proper length, and that it does not have any 
; modifiers set that the server does not support.
;

TMSCP$MSG_IN::
	.IF	DEFINED DEBUG$PC_HISTORY
	MOVL	MSCP$L_CMD_REF(R2), R0	; Save command reference number in PC trace.
	BSBW	TMSCP$PCHIST_R3R0	; Log the PC for debugging.
	.ENDC  ;DEBUG$PC_HISTORY
	BBC	# TMSCP$V_DBG_MSG_IN, -	; Not debugging - branch.
		TMSCP$L_DEBUG_FLAGS, 0$
	JSB	G^ INI$BRK

0$:
	MOVL	G^SCS$GL_TMSCP,R5	; Get the server data structure
	INCL	TSRV$L_OPCOUNT(R5)	;  and count each request received

;
; Allocate an HRB and an IRP to represent this request, and save the addresses
; that are important for this request.
;
	MOVL	R3,R4			; Move the CDT address to a safe place
	BSBW	TMSCP$ALLOCATE_HRB	; Create a Host Request Buffer
	BLBC	R0,TMSCP$NO_MEM		; Insufficient memory, shutdown the link
	BISW	#HRB$M_STATE_INVALID,-	; Change the state of this request 
		HRB$W_STATE(R3)		;  to "current"
	MOVL	CDT$L_PDT(R4),-		; Get the address of the PDT
		HRB$L_PDT(R3)		;  save it to make requests faster
	MOVL	CDT$L_AUXSTRUC(R4),-	; Get the HQB address passed by SCS
		HRB$L_HQB(R3)		; Save it for later reference
	MOVL	R2,HRB$L_MSGBUF(R3)	; Save the address of the MSCP packet

;
; Now continue processing this request.
;
	CMPW	R1, #MSCP$K_MIN_SIZ	; Packet big enough?
	BLSS	TMSCP$BAD_LEN		; Too small...
	MOVZBL	MSCP$B_OPCODE(R2),R0	; Get the op-code from the packet
	BEQL	TMSCP$BAD_OPC		; It cannot be zero
;	ASSUME	TMSCP_MAX_OPCODE EQ MSCP$K_OP_REPOS
	CMPL	R0,#MSCP$K_OP_REPOS	; The server only supports up to write
	BGTR	TMSCP$BAD_OPC		;  table size limitation
	MOVZBL	TMSCP$COM_PKT_LEN[R0],R5	; Get the expected length
	BEQL	TMSCP$BAD_OPC		; If it is zero, then unsupported opcode
;
; This next test assumes all tape controllers return the
; command length value specified by the T/MSCP spec.
; This assumption may not be valid.
;
	CMPW	R1,R5			; Is command packet correct length?
	BLSS	TMSCP$BAD_LEN
	BITW	TMSCP$MOD_TBL[R0],-	; Compare modifiers we don't allow,
		MSCP$W_MODIFIER(R2)	;  to the ones that are set
	BNEQ	TMSCP$BAD_MOD		; Bad modifiers used with this op-code
	TSTB	MSCP$B_FLAGS(R2)	; The flags field is reserved on 
	BNEQ	TMSCP$BAD_FLAGS		;  incoming packets and should be clear

       	.IF DEFINED DEBUG$LOG

	ASSUME TSRV$V_LOG_ENABLD  EQ  0

	MOVL	G^SCS$GL_TMSCP,R5	; Get the server data structure
	BLBC	TSRV$W_STATE(R5),10$	; Branch if logging is disabled.
	BSBW	TMSCP$LOG_CMD_PKT	; Otherwise, log the command packet.
	.ENDC	; DEBUG$LOG

10$:
	MOVL	HRB$L_HQB(R3),R5	; Get the Host Queue Block adress
	INSQUE	HRB$L_FLINK(R3),-	; Link this HRB to the host 
		@HQB$L_HRB_BL(R5)	;  it came from
	INCW	HQB$W_NUM_QUE(R5)	; Up the counter of queue elements
	CMPW	HQB$W_NUM_QUE(R5),-	; Check to see if the new value is
		HQB$W_MAX_QUE(R5)	;  higher than any previous value.
	BLEQ	20$			; If it's not, continue on.
	MOVW	HQB$W_NUM_QUE(R5),-	; If it is, make the current value
		HQB$W_MAX_QUE(R5)	;  a new high water mark.

20$:
	MOVL	HQB$L_TSRV(R5),R1	; Server structure address for stats
	INCL	TSRV$L_OPCOUNT(R1)[R0]	; Receipt of a valid operation code
	ASHL	#-3,R0,R0		; Pick up class field.
;
; The TMSCP spec does not neatly divide up the 3 opcode classes of
; IMMEDIATE, SEQUENTIAL(IMMEDIATE COMPLETION), and SEQUENTIAL 
; Therefore, this case statement will take advantage of the only 
; clear division, between IMMEDIATE and the rest of the world. 
;
;	R0  =  Opcode class, scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  Scratch
;	R5  =  Scratch
;

	CASE	R0, < -			; Dispatch on opcode.
		 TMSCP$IMMEDIATE, -	; Immediate.
		 TMSCP$SEQUENTIAL, -	; Sequential commands.
		 TMSCP$SEQUENTIAL, -	; Sequential commands.
		 TMSCP$BAD_OPC, -	; Maintenance commands.		
		 TMSCP$SEQUENTIAL >	; Sequential commands.
;	CASEW	R0,#0,S^#<<30040$-30039$>/2>-1
;30039$:
;	.SIGNED_WORD	TMSCP$IMMEDIATE-30039$
;	.SIGNED_WORD	TMSCP$SEQUENTIAL-30039$
;	.SIGNED_WORD	TMSCP$SEQUENTIAL-30039$
;	.SIGNED_WORD	TMSCP$BAD_OPC-30039$
;	.SIGNED_WORD	TMSCP$SEQUENTIAL-30039$
;30040$:

	
;
; When a packet with illegal modifiers, or an improper length is received,
; the bad opcode or other information is placed in the high byte of R0 and
; control is passed on to here. The packet received is altered and sent back
; with the error information.
;

TMSCP$BAD_OPC::
	ROTL	#24,#MSCP$B_OPCODE,R0	; The op-code passed in the MSCP packet
	BRW	TMSCP$PACKET_ERROR	;  was out of bounds or not supported

TMSCP$BAD_LEN::
	CLRL	R0			; Since there is no length field to 
	BRW	TMSCP$PACKET_ERROR	;  point to, just clear R0

TMSCP$BAD_MOD::
	ROTL	#24,#MSCP$W_MODIFIER,R0	; Prepare for a modifier error return
	BRW	TMSCP$PACKET_ERROR	;  point to, just clear R0

TMSCP$BAD_FLAGS::
	ROTL	#24,#MSCP$B_FLAGS,R0	; The incoming packet did not have the 
	BRW	TMSCP$PACKET_ERROR	;  flags field cleared


	.SBTTL	IMMEDIATE class commands

;+
; Functional Description:
;
; Immediate commands require very little time to complete and do not 
; cause any unit context switches. Servers process immediate commands 
; without waiting for any other commands to complete. The server guarantees
; the completion of all outstanding immediate commands plus an additional
; GET COMMAND STATUS within the controller timeout interval.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  Scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$IMMEDIATE::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	DISPATCH MSCP$B_OPCODE(R2),-	; Dispatch on command subtype
		type=B,-		; Data type we are dispatching on
		prefix=MSCP$K_OP_,<-			; 
		<ABORT,	TMSCP$ABORT>, -			; Abort command
		<GTCMD, TMSCP$GET_COMMAND_STATUS>, -	; Get Command Status
		<GTUNT, TMSCP$GET_UNIT_STATUS>, -	; Get Unit Status
		<STCON, TMSCP$SET_CONTROLLER_CHAR>, -	; Set Controller 
		>					;    Characteristics
;	CASEB	MSCP$B_OPCODE(R2),#$$BASE,#$$LIMIT
;30041$:
;	.SIGNED_WORD	TMSCP$ABORT-30041$
;	.SIGNED_WORD	TMSCP$GET_COMMAND_STATUS-30041$
;	.SIGNED_WORD	TMSCP$GET_UNIT_STATUS-30041$
;	.SIGNED_WORD	TMSCP$SET_CONTROLLER_CHAR-30041$

	ROTL	#24,#MSCP$B_OPCODE,R0	; Identify the bad field
	BRW	TMSCP$PACKET_ERROR	; Return an end msg with error status


	.SBTTL	-	ABORT					(- 1 -)

;+
; Functional Description:
;
; The abort command causes a specified command to be aborted at the earliest
; time possible for the server. If the specified request is found and cannot
; be aborted immediately because it is outside the servers jurisdiction, a
; successful end message is returned and the request is aborted when the 
; server regains control of the IRP_CDRP data structure.
;
;Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  Scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Status to return in the MSCP end message
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$ABORT::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	HRB$L_HQB(R3),R5	; Get the host queue block
	MOVAL	HQB$L_HRB_FL(R5),R1	;  and get the HRB queue list head
	MOVL	R1,R5			; Prepare to start through the list

	ASSUME	HRB$L_FLINK EQ 0

10$:
	MOVL	HRB$L_FLINK(R1),R1	; Move on to the next request in line
	CMPL	R1,R5			; If the new value is not the list head
	BNEQ	12$			;  check to see if it's the one we want
	MOVL	#MSCP$K_ST_SUCC,R0	; If we are back at the queue header and 
	BRW	TMSCP$SEND_END		;  still not successful, it doesn't matter

12$:
	MOVL	HRB$L_MSGBUF(R1),R4	; Get the packet address for the request
	BEQL	10$			; Request with no msgbuf cannot be aborted
	CMPL	MSCP$L_OUT_REF(R2),-	; Compare the outstanding reference number
		MSCP$L_CMD_REF(R4)	;  to the command reference number
	BNEQ	10$			; This is not the one... keep looking
	CMPW	MSCP$W_UNIT(R2),-	; Compare the unit number passed
		MSCP$W_UNIT(R4)		;  to the request we found
	BNEQ	10$			; If they are not the same keep looking

;
; The request was found, now process it depending on its state
;
	PUSHR	#^M<R3>			; Save the abort command HRB address
	MOVL	HRB$L_PDT(R1),R4	; Set up the registers here for the 
	MOVL	HRB$L_HQB(R1),R5	;  calls to the SCAN SCS routines
	MOVL	HQB$L_CDT(R5),R3	;  this way they ser set up only once
	BISW	#HRB$M_ABORTWS,-	; Mark the request we just found
		HRB$W_FLAGS(R1)		;  as "aborted"
	DISPATCH HRB$W_STATE(R1),-	; Dispatch based on request state
		type=W,-		;  which is a word field           
		prefix=HRB$K_ST_,<-	; 
		<MSG_WAIT,   30$>,-	; This one will just have to finish
		<SEQ_WAIT,   30$>,-	; These requests can be yanked immediately
		<SNDAT_WAIT, 20$>,-	; Sending or receiving data
		<DRV_WAIT,   70$>,-	; Wait til the drivers done for this one
		<MAP_WAIT,   70$>,-	; The only way to get this is to yank it
		>			; Cannot receive an abort for a command
;	CASEW	HRB$W_STATE(R1),#$$BASE,#$$LIMIT
;30042$:
;	.SIGNED_WORD	30$-30042$
;	.SIGNED_WORD	30$-30042$
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.SIGNED_WORD	20$-30042$
;	.SIGNED_WORD	70$-30042$
;	.SIGNED_WORD	70$-30042$
					;  in SNDMS_WAIT state (only ATN msgs)

	BUG_CHECK	MSCPSERV,FATAL	; Trap any strange behavior here!
;		.WORD	^XFEFF
;		.IIF IDN <FATAL>,<FATAL> , .WORD	BUG$_MSCPSERV!4

;
; Commands that have already started sending or receiving data, or messages
; could actually be in one of three states. They could be waiting for RSPIDs,
; waiting for message buffers, or they could actually be involved in the 
; transfer of data. If they are in one of the first two states, the CDRP
; can be pulled off the queue and the request terminated. If, however, the
; CDRP is not waiting for a SCS resource, the best we can do is flag the 
; request, and abort the command when the transfer completes.
;

20$:
	SCAN_RSPID_WAIT-		; Find the CDRPs in this queue for this
		action =TMSCP$ABORT_UNHOOK_CDRP ; CDT and if HRB matches, dequeue it
;		MOVAB	TMSCP$ABORT_UNHOOK_CDRP, R0
;		JSB	G^SCS$LKP_RDTWAIT
	SCAN_MSGBUF_WAIT- 		; If request is waiting for msgbuf
		action =TMSCP$ABORT_UNHOOK_CDRP ;  this scan will find it
;		MOVAB	TMSCP$ABORT_UNHOOK_CDRP, R0
;		JSB	G^SCS$LKP_MSGWAIT
	BBS	#HRB$V_DEQUEUED,-	; If the request has been dequeued,
		HRB$W_FLAGS(R1),60$	;  the CDRP was found in UNHOOK
	BRB	70$			; Continue with the common code

;
; This command is found on the blocked queue awaiting the completion
; of a sequential command. It can be removed from the queue and the 
; count of queue elements decremented.
;

30$:
	TSTL	HRB$L_WAIT_FL(R1)	; Are we still on the blocked queue?
	BEQL	60$			; EQL implies not.
	REMQUE	HRB$L_WAIT_FL(R1),R0	; Remove the request from the queue
	MOVL	HRB$L_UQB(R1),R0	; Get the address of the UQB
;
; If for some reason we land here with the state INVALID (Abort requst while
; a sequetial request is stalled while MAPping or transfering data,
; we don't want to decrement the counter.
; 
   	BBS	#HRB$V_STATE_INVALID, -	; don't decrement the counter
   		HRB$W_STATE(R1), -	;  here - do it in UNBLOCK   
   		60$
   	DECW	UQB$W_NUM_QUE(R0)	;  so we can update the counter 
 
;                                                                        
; Now all the request specific processing is done, do some common processing 
; here, setting the flags and status bits. Then send off the end message for 
; the aborted command.  
; 
 
60$: 
	BISW	#HRB$M_STATE_INVALID,-	; The state of the request as indicated
		HRB$W_STATE(R1)		;  is no longer valid, change it
	BISW	#HRB$M_DEQUEUED,-	; Mark this request as unhooked... 
		HRB$W_FLAGS(R1)		;  its resounces can be deallocated
	CLRL	HRB$L_UQB(R1)		; Defuse UCB$W_CURRENT update by UNBLOCK
	MOVL	#MSCP$K_ST_ABRTD,R0	; Return a status of aborted
	MOVL	HRB$L_IRP_CDRP(R1),R5	; Get the address of the IRP
	MOVAL	IRP$L_FQFL(R5),R5	;  so we can get the CDRP address
	MOVL	HRB$L_MSGBUF(R1),R2	; Get the associated MSCP packet addr
	MOVL	CDRP$L_ABCNT(R5),-	; Load up the accumulated byte count
		MSCP$L_BYTE_CNT(R2)	;  to show how far we got
	MOVL	R1,R3			; Prepare to return the aborted msg
	BSBW	TMSCP$SEND_END		;  and then send the packet out

;
; Now that the target request has been finished off (or it has been 
; determined that it cannot be finished off at this time), the end 
; message for the abort command itself can be sent out.
;

70$:
	POPR	#^M<R3>			; Restore the original HRB address
	MOVL	#MSCP$K_ST_SUCC,R0	; Set the status to success
	MOVL	HRB$L_MSGBUF(R3),R2	;  restore the message buffer addr
	BRW	TMSCP$SEND_END		;  and send out the ABORT end message


	.SBTTL	-	GET COMMAND STATUS			(- 2 -)

;+
; Functional Description:
;
; The GET COMMAND STATUS command is used to monitor the progress of
; a command towards completion. The command status of a TMSCP server's
; oldest outstanding command is guaranteed to decrease within the
; controller timeout interval. The GET COMMAND STATUS command always
; succeeds. If the command being referenced is not known, then the server
; returns a zero as the command status.
;
; When the HRB is allocated, a -1 is placed in the command status field. As
; any information is requested or sent from one of the other cluster members, 
; or one of the tapes attached to this processor, the command status value
; is decremented.
;
; For all devices, the the Host Queue Block for the remote host is
; grabbed. The list of HRBs is scanned looking for a match between the RSPID
; and the unit number to the command, to those in the requesting packet. If
; a match is not made, then a zero is returned.
;
; If the unit is a non-TMSCP device, the status in that HRB is returned.
; If the unit is a TMSCP device, the server maintains progress counters that 
; mirror the criteria in the TMSCP class driver's timeout mechanism. If this is
; the first time a GCS is being done on this command, return the status from
; the HRB status counter, which should be -2 or less, and initialize the UQB
; counters. If it is not the first time a GCS was done, if the oldest 
; outstanding command in the local controller has made progress, decrement
; UQB counter and return this value in the MSCP packet.
;
; The flag HRB$V_DRIVER is set any time that a thread forks to the local driver.
; Upon return, this bit is cleared. HRB$L_CMD_STS is decremented in this
; routine as well as strategic locations throughout the server to ensure that
; progress is being reflected.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  Scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  MSCP status to return in the end message
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$GET_COMMAND_STATUS::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

	MOVL	HRB$L_HQB(R3),R4	; R4 <= HQB
	MOVAL	HQB$L_HRB_FL(R4),R0	; R0 <= HRB queue list head
	MOVL	R0,R5			; Prepare to start through the list

	ASSUME	HRB$L_FLINK EQ 0

10$:
	MOVL	HRB$L_FLINK(R5),R5	; Move on to the next request in line
	CMPL	R5,R0			; If it is not the the list head
	BNEQ	20$			;  continue checking...
	CLRL	MSCP$L_CMD_STS(R2)	; If the command was not found,
	BRB	150$			;  return a command status of zero

20$:
	MOVL	HRB$L_MSGBUF(R5),R4	; Get the packet address for the request
	CMPL	MSCP$L_OUT_REF(R2),-	; Compare outstanding reference number
		MSCP$L_CMD_REF(R4)	;  to the command reference number
	BNEQ	10$			; This is not the one... keep looking
	CMPW	MSCP$W_UNIT(R2),-	; Compare the unit number passed
		MSCP$W_UNIT(R4)		;  to the request we found
	BNEQ	10$			; If they are not the same keep looking
	CMPW	#HRB$K_ST_DRV_WAIT,-	; If we are waiting for driver to
		HRB$W_STATE(R3)		;  return control to us, then we check
	BNEQ	140$			;  for progress within the driver by
					;  falling through.
	MOVL	HRB$L_UQB(R5),R4	; R4 <= UQB
	MOVL	UQB$L_UCB(R4),R1	; R1 <= UCB
	BBC	#DEV$V_MSCP,-		; Fall through if an MSCP device, to
		UCB$L_DEVCHAR2(R1),130$	;  check if progress if being made in
					;  the class driver.
					; For now, if it is a non-MSCP class
					;  driver, always indicate progress
					;  so that lengthy commands such as
					;  REPOSITION and ERASE will have time
					;  to complete.
	MOVL	UCB$L_CDDB(R1),R1	; R1 <= CDDB

;	R1 <= CDDB
;	R2 <= MSCP packet address.
;	R4 <= UQB
;	R5 <= HRB containing the I/O request we are doing the GCS on.
;

	CMPL	MSCP$L_OUT_REF(R2),-	; Is this the first time we did a GCS
		UQB$L_SERV_RSPID(R4)	;  for this packet?
	BNEQ	110$			; Yes, initialize all counters.
	CMPL	CDDB$L_OLDRSPID(R1),-	; Is the class driver's oldest message
		UQB$L_CLASS_RSPID(R4)	;  the same as the last time the server
					;  was called to do a GCS?
	BNEQ	115$			; If the server and class RSPIDs match,
	CMPL	CDDB$L_OLDCMDSTS(R1),-	; is the command status still the same
		UQB$L_OLD_CLSSTS(R4)	; since the last GCS?
	BNEQ	120$			; If not, decrement counter to
					;  indicate progress.
	BRB	140$			;  else, no progress is being made in
					;  the local class driver.

110$:
	MOVL	MSCP$L_OUT_REF(R2),-	; Save RSPID of command we are currently
		UQB$L_SERV_RSPID(R4)	;  doing a GCS for.

115$:
	MOVL	CDDB$L_OLDRSPID(R1),-	; Save RSPID of oldest command that the
		UQB$L_CLASS_RSPID(R4)	;  local class driver has queued.

120$:
	MOVL	CDDB$L_OLDCMDSTS(R1),-	; is the command status still the same
		UQB$L_OLD_CLSSTS(R4)	; since the last GCS?

130$:
	DECL	HRB$L_CMD_STS(R5)	; Indicate that progress is being made.

140$:
	MOVL	HRB$L_CMD_STS(R5),-	; Get the status from the target rqst
		MSCP$L_CMD_STS(R2)	;  and put it in the requesting packet

150$:
	MOVL	#MSCP$K_ST_SUCC,R0	; Zero means the command was successful
	BRW	TMSCP$SEND_END		; Return an end packet with status


	.SBTTL	-	GET UNIT STATUS				(- 3 -)

;+
; Functional Description:
;
; The GET UNIT STATUS command returns the current state of a unit plus
; certain unit characteristics.  In particular, this command is used to
; obtain host settable characteristics and those fixed unit characteristics
; that are not normally needed by the class driver. 
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  Scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$GET_UNIT_STATUS::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	HRB$L_HQB(R3),R0	; Get the address of the HQB to
	BBC	#HQB$V_V5CL,-		; If this request is from an old class
		HQB$W_FLAGS(R0),5$	; driver, we're in trouble.

;
; The unit number has bit 14 set to indicate that the unit no. is a Server Local
; Unit Number (SLUN).  After the bit is cleared, the unit number that remains
; is used as an index into the unit table to locate the address of the Unit 
; Queue Block (UQB) for this tape.  If the next unit specifier was set
; and the table location for the specified unit number is zero, the remaining
; locations are examined until a nonzero value is found or the end of the 
; table is reached.  The only occasions when SLUN should not be set is if
; the unit is 0 (NOP sent by class driver to prohibit timeout) or if the
; unit is 1 (used at the very start of unit polling to find the first unit).
;

	MOVL	HQB$L_TSRV(R0),R5	;  get the server structure address
	MOVZWL	MSCP$W_UNIT(R2),R0	; Pick up the unit number passed
	BEQL	40$			; If unit 0, return harmless offline
	BBC	#MSCP$V_MD_NXUNT,-	; If the next unit modifier is clear
		MSCP$W_MODIFIER(R2),-	;  branch to process the specified unit
		80$			;  with common code

;
; The next unit modifier requests that the controller return the status of the
; next unit (in order of ascending unit numbers) that the controller knows to
; exist and whose unit number is greater than or equal to the unit number 
; specified in the command.
;

	BBSC	#MSCP$V_SLUN,R0,20$	; Branch if SLUN set, clear it for index
	DECL	R0			; Is this the start of unit polling?
	BEQL	20$			; Branch if unit was = 1, start lookup

5$:
	BUG_CHECK MSCPSERV, FATAL	; Wrong class driver 
;		.WORD	^XFEFF
;		.IIF IDN <FATAL>,<FATAL> , .WORD	BUG$_MSCPSERV!4

10$:
	INCL	R0			; Bump to next "unit" in table

20$:
	CMPW	#MAX_UNITS,R0		; Make sure "unit" specified is in table
	BLEQU	30$			; If not, end polling with unit 0
	MOVL	TSRV$L_UNITS(R5)[R0],R4	; Find the UQB address in the table
	BEQL	10$			; Try next unit if there isn't one here
	MOVL	R4,HRB$L_UQB(R3)	; Save away the UQB address we found,
	INCL	UQB$W_CURRENT(R4)	;  increment the current count for unit,
	BRB	90$			;  and continue in the common code

;
; If there are no units that are both known to the controller and whose
; unit numbers are greater than or equal to the unit number specified in
; the command message, then zero is returned in the "unit number" field
; of the end message.  Since in SLUN versions there is no true unit "0",
; the rest of the status is set harmlessly to OFFLN via TMSCP$ERROR_NO_UNIT.
;

30$:
	CLRW	MSCP$W_UNIT(R2)		; Give special code (Unit 0)

40$:
	MOVL	#MSCP$K_ST_OFFLN,R0	; Return a status of offline
	BRW	TMSCP$SEND_END		; Let the client find another path

;
; The request is for a specific unit
;

80$:
	BSBW	TMSCP$FIND_UQB		; Find the UQB for this request
	BLBC	R0,40$			; Branch if no such unit found.

;
; A UQB address was found and has been placed in the HRB.
;

90$:
	MOVL	HRB$L_UQB(R3),R4	; Get the address of the UQB
	MOVW	UQB$W_SLUN(R4),-	; Assume we are using the new driver
		MSCP$W_UNIT(R2)		; Unit whose status we are returning
	MOVL	UQB$L_UCB(R4),R0	; Get the UCB address for this unit
	MOVL	UCB$L_MEDIA_ID(R0),-	; Get the media identifier out of the
		MSCP$L_MEDIA_ID(R2)	;  UCB just in case it changes!
	CLRB	MSCP$B_FREECAP(R2)	; Unknown amount of free capacity.
;	CLRL	MSCP$B_FMTR_SRV(R2)	; MSCP$B_FMTR_SVR <= 0
					; MSCP$B_FMTR_HVR <= 0
					; MSCP$B_UNIT_SVR <= 0
					; MSCP$B_UNIT_HVR <= 0

;
;		The following fields are documented in Tables C-1 and C-2
;		in the TMSCP specification.
;

	GET_TAPE_FORMENU	R0	; Supported tape formats.
;	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
;		UCB$L_DEVCHAR2(R0), 30043$	; Not MSCP tape - branch.
;	MOVW	UCB$W_TU_FORMENU(R0), -	; Steal tape formenu from UCB.
;	BRB	30044$
;30043$:
;	MOVQ	R0, -(SP)
;	EXTZV	# MT$V_DENSITY, -		; Extract VMS density from the UCB.
;		# MT$S_DENSITY, -
;		UCB$L_DEVDEPEND(R0), R0
;	CLRL	R1				; Use default VMS density table.
;	BSBW	TMSCP$VMSTOMSCP_DENS		; Convert tape density value.
;	MOVB	R1, MSCP$W_FORMENU(R2)		; Return the current tape format.
;	MOVB	R1, 1+MSCP$W_FORMENU(R2)	; Return the supported densities.
;	MOVQ	(SP)+, R0
;30044$:
	GET_TAPE_FORMAT		R0	; Current tape format.
;	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
;		UCB$L_DEVCHAR2(R0), 30045$	; Not MSCP tape - branch.
;	MOVW	UCB$W_TU_FORMAT(R0), -	; Steal tape format from UCB.
;	BRB	30046$
;30045$:
;	MOVQ	R0, -(SP)
;	EXTZV	# MT$V_DENSITY, -		; Extract VMS density from the UCB.
;		# MT$S_DENSITY, -
;		UCB$L_DEVDEPEND(R0), R0
;	CLRL	R1				; Use default VMS density table.
;	BSBW	TMSCP$VMSTOMSCP_DENS		; Convert tape density value.
;	MOVW	R1, MSCP$W_FORMAT(R2)		; Return the tape format.
;	MOVQ	(SP)+, R0
;30046$:
	GET_TAPE_SPEED		R0, -	; Data rate in kilobytes per second.
				MSCP$W_FORMAT(R2)
;	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
;		UCB$L_DEVCHAR2(R0), 30047$	; Not MSCP tape - branch.
;	MOVW	UCB$W_TU_SPEED(R0), -	; Steal tape speed from UCB.
;	BRB	30048$
;30047$:
;	MOVQ	R0, -(SP)
;	MOVZWL	MSCP$W_FORMAT(R2), R1			; Get the MSCP tape format.
;	EXTZV	# MT$V_SPEED, -			; Extract VMS tape speed from R0
;		# MT$S_SPEED, -			; and return as the tape speed.
;		UCB$L_DEVDEPEND(R0), R0
;	BSBW	TMSCP$SPEEDTOMSCP		; Convert speed value.
;	MOVW	R0, MSCP$W_SPEED(R2)		; Return the tape speed.
;	MOVQ	(SP)+, R0
;30048$:

;
; If this is an MSCP device and it is in mount verification or no connection
; return a status of offline.
;

	BBS	#UCB$V_MNTVERIP,-	; If the volume is in local mount
		UCB$W_STS(R0),125$	;  verification then offline
	BBC	#DEV$V_MSCP,-		; If this is a MSCP device,
		UCB$L_DEVCHAR2(R0),140$	;  and there are already requests
	MOVL	UCB$L_CDDB(R0),R1	; Follow the link to its CDDB
	BBS	#CDDB$V_NOCONN,-	; If there is no connection to the
		CDDB$W_STATUS(R1),120$	;  device, set it offline
	CMPB	#MSCP$K_CM_EMULA,-	; Check to see if the controller
		CDDB$B_CNTRLMDL(R1)	;  model is an emulator (server)
	BNEQ	140$			; If it is, leave it offline

120$:
	MOVL	#MSCP$K_ST_OFFLN,R0	;  Return a status of offline and let
	BRW	TMSCP$SEND_END		;  the client find another path

125$:
	MOVL	#MSCP$K_ST_OFFLN,R0	; Return a status of offline, but
	BISW	#MSCP$M_SC_NOVOL,R0	;  reflect doubt about the volume
	BRB	170$			;  UCB will be built on client node
;
; Everything is alright with this unit. The MSCP end message
; is now filled in and returned to the client node.
;
	ASSUME	UQB$K_ST_AVAILABLE  EQ  MSCP$K_ST_AVLBL
	ASSUME	UQB$K_ST_OFFLINE  EQ  MSCP$K_ST_OFFLN

140$:
	MOVZWL	UQB$W_STATE(R4),R0	; Get the state for offline or avail
	CMPW	R0, #UQB$K_ST_AVAILABLE	; State of the device available?
	BEQL	170$			; If eql yes, return it
	CMPW	UQB$W_STATE(R4),-	; If the state of the device
		#UQB$K_ST_ONLINE	;  is online, skip on down
	BEQL	160$			; The status in R0 will be fine

;
; The status of this device as stored in the UQB indicates that some sort of
; error has been encountered that has caused this device to go offline. The
; server must now determine if the device is still offline, or may now be
; made available to client nodes.
;
	MOVL	UQB$L_UCB(R4),R1	; Get the address of the UCB
	BBC	#UCB$V_ONLINE,-		; If the device is not UCB online
		UCB$W_STS(R1),170$	;  leave the device offline for now
	BBC	#DEV$V_MSCP,-		; Don't try any of the remaining tests
		UCB$L_DEVCHAR2(R1),170$	;  unless this is a class driver!
	MOVL	UCB$L_CDDB(R1),R1	;  and follow the link to its CDDB
	CMPB	#MSCP$K_CM_EMULA,-	; Check to see if the controller
		CDDB$B_CNTRLMDL(R1)	;  model is an emulator (server)
	BEQL	170$			; If it is, leave it offline
	BITW	#<CDDB$M_NOCONN! -	; If there is no connection or
		CDDB$M_DISABLED>,-	; the controller is disabled
		CDDB$W_STATUS(R1)	;  then leave it offline
	BNEQ	170$			; If neq offline

160$:
	MOVL	#UQB$K_ST_AVAILABLE,R0	; Assume the device is available
	CMPL	UQB$L_ONLINE_HQB(R4),-	; Does the host who requested this GUS
		HRB$L_HQB(R3)		;  have this unit online?
	BNEQ	170$			; If not, return status in R0 (offline)
	MOVZWL	#MSCP$K_ST_SUCC,R0	; Return a status of success
	BBC	#MSCP$V_UF_WRTPD,-	; If Data Safety Write Protect 
		UQB$W_UNIT_FLAGS(R4),170$;  due to some condition in the unit,
	BISW	#MSCP$M_SC_RDONY,R0	;  then set Read Only subcode in R0.

170$:
	MOVW	UQB$W_UNIT_FLAGS(R4),-	; Put the set unit flags in the
		MSCP$W_UNT_FLGS(R2)	;  end packet to return
	MOVL	UQB$W_MULT_UNIT(R4), -	;  the tape class driver, so stop here.
		MSCP$W_MULT_UNT(R2)	; Copy mult_unit & unit_flags
	MOVQ	UQB$Q_UNIT_ID(R4), -	;  
		MSCP$Q_UNIT_ID(R2)	; Copy unit identifier
	BRW	TMSCP$SEND_END


	.SBTTL	-	SET CONTROLLER CHARACTERISTICS		(- 4 -)

;+
; Functional Description:
;
; This command is the mechanism whereby certain host settable controller
; characteristics are determined. The information sent to the server by 
; the host includes; controller flags, the host timeout interval, the
; current time and date on the host system, and controller dependant
; tuning parameters. In the end message, the server returns the following
; information as it is currently set; the controller flags, the controller
; timeout interval, the controller hardware and software revision numbers,
; and a unique MSCP device identifier.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  Scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$SET_CONTROLLER_CHAR::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	HRB$L_HQB(R3),R4	; Get the HQB address so we can
	MOVL	HQB$L_TSRV(R4),R5	;  get the address of the server struct
	CMPW	MSCP$W_VERSION(R2),-	; This is a check to assure the
		TSRV$W_VERSION(R5)	;  compatability of versions of MSCP
	BEQL	10$			; We are speaking the same language
	ROTL	#24,#MSCP$W_VERSION,R0	; A change has been made to MSCP to
	BRW	TMSCP$PACKET_ERROR	;  make it no longer downward compat

10$:
	MOVW	MSCP$W_CNT_FLGS(R2),-	; Copy the controller flag values
		HQB$W_CNT_FLGS(R4)	;  to the server structure
	READ_SYSTIME -			; Get the time this command was
		HQB$Q_TIME(R4)		;  issued on the remote machine
;	 MFPR	S^#PR$_IPL,-(SP)
;	BLBC	G^SMP$GL_FLAGS,30051$
;	 PUSHL	R0
;	 MOVZBL	S^#SPL$C_HWCLK,R0
;	 JSB	G^SMP$ACQUIRE
;	 POPL	R0
;	BRB	30052$
;30051$:
;		MTPR	S^#IPL$_HWCLK,S^#PR$_IPL
;30052$:
;	  MOVQ	G^EXE$GQ_SYSTIME,HQB$Q_TIME(R4)
;	BLBC	G^SMP$GL_FLAGS,30058$
;	 PUSHL	R0
;	 MOVZBL	S^#SPL$C_HWCLK,R0
;	 JSB	G^SMP$RELEASE
;	 POPL	R0
;30058$:
;	 MTPR	(SP)+,S^#PR$_IPL
	MOVW	MSCP$W_HST_TMO(R2),-	; Copy the host access timeout
		HQB$W_HTIMO(R4)		;  interval to the HQB
	BEQL	30$			; Disables host access timeouts
	CMPW	HQB$W_HTIMO(R4),#255	; "Treat all values greater than 255
	BLEQ	20$			;  as if 255 had been specified"
	MOVW	#255,HQB$W_HTIMO(R4)	; 			- the spec

20$:
	CMPW	HQB$W_HTIMO(R4),#10	; "Treat all values between 1 and 9
	BGEQ	30$			;  as if 10 had been specified"
	MOVW	#10,HQB$W_HTIMO(R4)	; 			- the spec

30$:

;
; Return controller information to host
;

	MOVB	G^CLU$GL_TAPE_ALLOCLS,-	; Transfer the allocation class
		MSCP$B_CNT_ALCS(R2)	; 
	MOVW	TSRV$W_VERSION(R5),-	; Return all the current settings of
		MSCP$W_VERSION(R2)	;  the server software version number,
	MOVW	TSRV$W_CFLAGS(R5),-	; 
		MSCP$W_CNT_FLGS(R2)	;  the server controller flag settings,
	MOVW	#<SERVER_TIMEOUT*2>+1,-	;  and the controller timeout value
		MSCP$W_CNT_TMO(R2)
	MOVQ	TSRV$Q_CTRL_ID(R5),-	; Also, uniquely identify this MSCP
		MSCP$Q_CNT_ID(R2)	;  device to the host
	CLRL	MSCP$L_MAXBCNT(R2)	; Class driver will use 64K as max.

	ASSUME	SOFTWARE_REV  GT  0	; Software and hardware revisions
	ASSUME	SOFTWARE_REV  LE  255	;  are byte fields. The following
	ASSUME	HARDWARE_REV  GT  0	;  two MOVB instructions are not a MOVW
	ASSUME	HARDWARE_REV  LE  255	;  for easier patching, if necessary.

	MOVB	#SOFTWARE_REV, -	; Set software version number in
		MSCP$B_CNT_SVR(R2)	;  end message.
	MOVB	#HARDWARE_REV, -	; Set hardware version number in
		MSCP$B_CNT_HVR(R2)	;  end message as well.

	MOVL	#MSCP$K_ST_SUCC,R0	; Set success
;	CLRL	HRB$L_UQB(R3)		;  make sure the unblock won't work
	BRW	TMSCP$SEND_END		;  and send off the end packet


	.SBTTL	SEQUENTIAL class commands

;+
; Functional Description:
;
; All TMSCP command that are not immediate are sequential. This section 
; of code sets up for sequential command processing.  Steps are 
; taken in this routine to asure sequentiality. When a sequential command is 
; in progress, no other commands received may begin execution until the 
; completion of the sequential command. These commands are queued up on
; the UQB$L_BLOCKED queue and retrieved in a FIFO manner.
;
; There are two types of SEQUENTIAL commands, SEQUENTIAL and SEQUENTIAL 
; IMMEDIATE. SEQUENTIAL IMMEDIATE commands can be used so that lengthy
; non-data transfer operations complete asynchronously. These commands
; are ERASE, REPOSITION (rewind) and AVAILABLE. When the server receives
; one of these commands, it sets the immediate completion modifier in
; the function code and sends the request off to the local driver. Upon
; receipt of the completed I/O, the server sends the end message back to
; the requesting host. So in this case, immediate completion is accomplished
; by the actual controller, not the emulator.
;
; SEQUENTIAL IMMEDIATE commands can also be executed as "write-back" caching
; enabled "write" type commands. In this case, the TMSCP server emulator will
; return the end message to the remote host as soon as the data for the write
; data operation is completely buffered by the TMSCP server in a local buffer
; These commands are WRITE, WRITE TAPE MARK, and ERASE GAP.
; 
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  Scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;-

TMSCP$SEQUENTIAL::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

10$:
	BSBW	TMSCP$FIND_UQB		; Find the UQB that corresponds to 
	BLBS	R0,TMSCP$SEQ_ALT	;  this request and put addr in HRB
	BRW	TMSCP$ERROR_NO_UNIT	; Return an error (see TMSCP$SEND_END)

;
; This alternate entry point into the processing of a sequential command is
; used during the cleanup of a virtual circuit error. Control is passed here
; with  the HRB address, and the UQB address, but there is no MSCP packet
; to be used.
;

TMSCP$SEQ_ALT::
	MOVL	HRB$L_UQB(R3),R4	; Get the address of the UQB

;
;		Serialize all the sequential commands.
;

	BSBW	TMSCP$SEQ_STALL		; Serialize all the sequential commands.

;
;		R0  =  Scratch
;		R1  =  Scratch
;		R2  =  Scratch
;		R3  =  HRB address
;		R4  =  UQB address, scratch
;		R5  =  Scratch
;

	MOVL	HRB$L_MSGBUF(R3),R2	; Get the address of the message buffer
	BEQL	30$			; Cleanup after VC failure - branch.
	MOVZBL	MSCP$B_OPCODE(R2),R1	; Put the op code into a safe reg
	BRB	40$			;  and skip around

30$:
	MOVL	#MSCP$K_OP_AVAIL,R1	; Cleanup always uses the same code

40$:
	DISPATCH R1,-			; Dispatch on command opcode
		type=B,-		;  which is a byte field 
		prefix=MSCP$K_OP_,< -	;  and the beginning looks like this
		<AVAIL,	TMSCP$AVAILABLE>, -	; available, ALWAYS immediate 
		-				;   completion!
		<ONLIN,	TMSCP$ONLINE>, -	; online opcode 9
		<STUNT,	TMSCP$SET_UNIT_CHAR>, -	; set unit characteristics opcode 10
		<DTACP,	TMSCP$DET_ACC_PATH>, -	; determine access path opcode 11
		<ACCES, TMSCP$ACCESS>, -	; access
		<ERASE, TMSCP$ERASE>, -		; erase from current position to PEOT
		<FLUSH, TMSCP$FLUSH>, -		; flush write-back cache
		<ERGAP, TMSCP$ERASE_GAP>, -	; fill with specified block pattern
		<COMP,  TMSCP$COMPARE_HOST_DATA>, - ; compare data with host buffer
		<READ,  TMSCP$READ>, -		; read from tape
		<WRITE, TMSCP$WRITE>, -		; write to tape
		<WRITM, TMSCP$WRITE_TAPE_MARK>,-; write tape mark
		<REPOS, TMSCP$REPOSITION>, -	; reposition
		>
;	CASEB	R1,#$$BASE,#$$LIMIT
;30062$:
;	.SIGNED_WORD	TMSCP$AVAILABLE-30062$
;	.SIGNED_WORD	TMSCP$ONLINE-30062$
;	.SIGNED_WORD	TMSCP$SET_UNIT_CHAR-30062$
;	.SIGNED_WORD	TMSCP$DET_ACC_PATH-30062$
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.SIGNED_WORD	TMSCP$ACCESS-30062$
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.SIGNED_WORD	TMSCP$ERASE-30062$
;	.SIGNED_WORD	TMSCP$FLUSH-30062$
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.SIGNED_WORD	TMSCP$ERASE_GAP-30062$
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.SIGNED_WORD	TMSCP$COMPARE_HOST_DATA-30062$
;	.SIGNED_WORD	TMSCP$READ-30062$
;	.SIGNED_WORD	TMSCP$WRITE-30062$
;	.IIF	EQ  $$GENSW,	.WORD	2*<$$LIMIT+1>
;	.SIGNED_WORD	TMSCP$WRITE_TAPE_MARK-30062$
;	.SIGNED_WORD	TMSCP$REPOSITION-30062$

	ROTL	#24,#MSCP$B_OPCODE,R0	; Identify the bad field
	BRW	TMSCP$PACKET_ERROR	; Return an end msg with error status


	.SBTTL	-	SET_CLEAR_SEX

;+
; This routine sets clear serious exception if the remote host requested it
; and this device supports it.
;-
; Inputs:
;
;	R3  =  HRB address
;
; Outputs:
;
;	All registers preserved.
;
;	IO$M_CLSEREXCP set in IRP if appropriate
;-

TMSCP$SET_CLEAR_SEX::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	PUSHR	#^M<R2,R5>
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore MSCP buffer address.
	MOVL	HRB$L_UQB(R3),R5	; Restore UQB so that
	BBC	#MSCP$V_MD_CLSEX,-	; Clear serious exception enabled?
		MSCP$W_MODIFIER(R2),999$;
	BBCC	#UQB$V_ST_SEREX,-	; Clear the serious exception
		UQB$W_ST_FLAGS(R5),10$	;

10$:
	MOVL	UQB$L_UCB(R5),R5	;  that we can get the UCB
	BBC	#DEV$V_MSCP,-		; If we are not dealing with a MSCP type
		UCB$L_DEVCHAR2(R5),999$	;  device, then we can't  enable it
	MOVL	HRB$L_IRP_CDRP(R3),R5	;
	BISW	#IO$M_CLSEREXCP,-	;
		IRP$W_FUNC(R5)

999$:
	POPR	#^M<R2,R5>
	RSB


	.SBTTL	-	SET_IMMED

;+
; This routine sets the immediate completion modifier if the device
; supports it. It always sets the HRB immediate completion modifier
; so that caching can at least be supported at the server level
;-
; Inputs:
;
;	R3  =  HRB address
;
; Outputs:
;
;	All registers preserved.
;
;	IO$M_CLSEREXCP set in IRP if appropriate
;-

TMSCP$SET_IMMED::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	PUSHR	#^M<R2,R5>
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore MSCP buffer address.
	MOVL	HRB$L_UQB(R3),R5	; Restore the UQB address
      	BBS	#MSCP$V_MD_IMMED,-	; Immediate completion specified?
		MSCP$W_MODIFIER(R2),10$	; If so, set flag in HRB.
      	BBC	#MSCP$V_UF_WBKNV,-	; Write back caching enabled on
		UQB$W_UNIT_FLAGS(R5),999$; a per unit basis?

10$:
	MOVL	UQB$L_UCB(R5),R5	;  that we can get the UCB
	BBC	#DEV$V_WBC,-		; If we are not dealing with a caching
		UCB$L_DEVCHAR2(R5),999$	;  controller, then we can't  enable it
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Restore CDRP/IRP address
	BISW	#IO$M_NOWAIT,-		; Set nowait in the function itself
		IRP$W_FUNC(R5)
999$:
	POPR	#^M<R2,R5>
	RSB


	.SBTTL	-	SET_CLEAR_CDL

;+
; This routine clears the cached data lost condition in the server if
; clear cached data lost was specified in the MSCP packet. It also
; sends the clear cached data modifier in the IRP to the local unit,
; if the unit is an MSCP device.
;-
; Inputs:
;
;	R3  =  HRB address
;
; Outputs:
;
;	All registers preserved.
;
;	MSCP$M_MD_CDATL set in IRP$L_MEDIA+6 if appropriate.
;-

TMSCP$SET_CLEAR_CDL::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	PUSHR	#^M<R2,R5>
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore MSCP buffer address.
	BBC	#MSCP$V_MD_CDATL,-	; Clear cached data lost set?
		MSCP$W_MODIFIER(R2),999$; 
	MOVL	HRB$L_UQB(R3),R5	; Restore the UQB address
	BICW	#UQB$M_ST_DLS,-		; If clear cached data lost set
		UQB$W_ST_FLAGS(R5)	;  then clear the state in the UQB
	MOVL	UQB$L_UCB(R5),R5	; Restore the UCB
	BBC	#DEV$V_WBC,-		; If we are not dealing with a caching
		UCB$L_DEVCHAR2(R5),999$	;  controller, then we can't  enable it
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Restore CDRP/IRP address
	BISW	#MSCP$M_UF_WBKNV,-	; Clear cached data lost set in IRP
		IRP$L_MEDIA+6(R5)
	BISW	#IO$M_MSCPMODIFS, -	; Enable MSCP modifiers.
		IRP$W_FUNC(R5)

999$:
	POPR	#^M<R2,R5>
	RSB


	.SBTTL	-	AVAILABLE				(- 8 -)

;+
; Functional Description:
;
; All outstanding host commands are completed after which the tape unit
; is placed in the "Unit Available" state relative to the host. The tape
; is rewound to BOT.  For TMSCP tapes, the Available command is always treated 
; as an immediate command, regardless of the Immediate Completion
; modifier being set or clear. IO$V_NOWAIT is set in the function code,
; and as soon as the I/O completes in the local driver, the end
; message is returned to the requesting host.
; 
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$AVAILABLE::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

;
; Make sure the unit this request is for is ONLINE to the requesting host.
;

	CMPL	UQB$L_ONLINE_HQB(R4),-	; Is this unit online
		HRB$L_HQB(R3)		;  to the requesting host?
	BEQL	3$			; If not, get out.
	MOVL	#MSCP$K_ST_SUCC,R0	; Set success status
	TSTL	UQB$L_ONLINE_HQB(R4)	; Is unit online to another host?
	BEQL	2$			; No, unit is available - branch.
	MOVL	#MSCP$K_ST_OFFLN,R0	; Set unit offline.

2$:
	BRW	87$			; Branch assist.

3$:
	CLRL	UQB$L_ONLINE_HQB(R4)	; We're no longer ONLINE.
	MOVL	UQB$L_UCB(R4),R5	; 
	DECB	UCB$B_ONLCNT(R5)	; Drop the count
	BEQL	7$			; Should be at zero.

	BUG_CHECK MSCPSERV,FATAL	; Online count zapped.
;		.WORD	^XFEFF
;		.IIF IDN <FATAL>,<FATAL> , .WORD	BUG$_MSCPSERV!4
;
; Change the state of this unit to available.
;

7$:
	MOVW	#UQB$K_ST_AVAILABLE,-	; Change the state of this unit
		UQB$W_STATE(R4)		;  to "available" for client hosts
	MOVL	R5,R4			; Save the UCB address for TMSCP$DRIVE_CHECK
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the IRP address for the I/O
	MOVL	R4,IRP$L_UCB(R5)	; Save the UCB address in the IRP

;
; The class driver can issue three possible modifiers for the AVAILABLE
; command: MSCP$M_MD_CLSEX, MSCP$M_MD_UNLOD, MSCP$M_MD_CDATL. Two other
; modifiers are allowed by the spec but are not currently used: 
; MSCP$M_MD_EXCAC and MSCP$M_MD_ALLCD.
;
; The server recognizes MSCP$M_MD_CLSEX and MSCP$M_MD_UNLOD. It cannot
; issue any I/O function modifier for clearing cached data.
;

	MOVW	#IO$_AVAILABLE, -	; Assume it is an AVAILABLE function.
		IRP$W_FUNC(R5)		; 
	TSTL	R2			; If this was an internally generated
	BEQL	60$			;  available, don't unload the drive
	BBC	#MSCP$V_MD_UNLOD,-	; If UNLOAD modifier set,
		MSCP$W_MODIFIER(R2),10$	;  set function
	MOVW	#IO$_UNLOAD, -		;  to UNLOAD
		IRP$W_FUNC(R5)		;  

10$:
	BSBW	TMSCP$SET_CLEAR_SEX
	BBC	#DEV$V_MSCP,-		; If this is a TMSCP device, set
		UCB$L_DEVCHAR2(R4),60$	;  immediate completion modifier, else
	BISW	#IO$M_NOWAIT,-		;  leave clear until non-class tape
		IRP$W_FUNC(R5)		;  drivers support this.
	BSBW	TMSCP$SET_CLEAR_CDL

60$:
	MOVL	R3,IRP$L_HRB(R5)	; Save HRB in IRP.
	MOVL	HRB$L_UQB(R3),R4	; Restore UQB for seq synch.

;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	BSBW	TMSCP$DRIVE_CHECK	; Send the packet to the tape

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
; 
; The host-settable unit flags are cleared.
;
	MOVL	HRB$L_HQB(R3),R2	; Restore HQB
	CLRL	UQB$L_POSITION(R4)	; We are back to the top.

	ASSUME	UQB$W_MAX_IO EQ 2+UQB$W_NUM_IO

	CLRL	UQB$W_NUM_IO(R4)	; Zero out outstanding IOs, max IOs
	MOVW	# UQB$M_ST_BOT, -	; Clear all status flags except BOT.
		UQB$W_ST_FLAGS(R4)
	BICW	#<MSCP$M_UF_WRTPS!-	; Clear out the hardware write protect
		MSCP$M_UF_VSMSU!-	;  and variable speed suppression mode
		MSCP$M_UF_WBKNV!-	;  and write back caching enabled
		MSCP$M_UF_WRTPD!-	;  and data safety write protect
    		MSCP$M_UF_WRTPH>,-	;  and software write protect
		UQB$W_UNIT_FLAGS(R4)	;  flags

80$:
	MOVL	HRB$L_MSGBUF(R3),R2	; Make sure the packet address is there
	BEQL	90$			; This must be a call from TMSCP$VC_ERR
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Restore CDRP/IRP
	MOVL	IRP$L_IOST1(R5),R0	; Restore the IOSB
	BLBC	R0,85$			; If the I/O was successful, cont..
	MOVL	#MSCP$K_ST_SUCC,R0	; Set success status
	BRB	87$			;  and send the end packet

85$:
	BSBW	TMSCP$CHECK_XFER_STATUS	;  otherwise, return proper status.	

87$:
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore end packet
	BRW	TMSCP$SEND_END		; Return the status back to the client

;
; A zero for a message buffer address in the HRB is an indication that this 
; routine was called as part of the cleanup effort after a VC error. Since
; there is no longer a circuit over which to send out an end message, just 
; cleanup the request without returning any status.
;

90$:
	BICW	#HRB$M_VCFAILED,-	; Clear this flag when we return so
		HRB$W_FLAGS(R3)		;  the HRB will be cleaned up
	BRW	TMSCP$CLEANUP_HRB	; Free resources used by this request.


	.SBTTL	-	ONLINE					(- 9 -)

;+
; Functional Description:
;
; The ONLINE command is used to bring a tape unit into the "Unit-Online" state,
; set host settable unit characteristics, and obtain those unit characteristics
; that are essential for proper class driver operation.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;- 

TMSCP$ONLINE::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

;
; If the unit is already online to this host, then the same unit
; flags must be specified.
;

WRITABLE_UNIT_FLAGS =	MSCP$M_UF_CMPRD ! -	;  compare reads 
			MSCP$M_UF_CMPWR ! -	;  compare writes
			MSCP$M_UF_VSMSU ! -	;  variable speed mode suppression
			MSCP$M_UF_SCCHH ! -	;  supress high speed caching
			MSCP$M_UF_WBKNV ! -	;  write back caching enabled
			MSCP$M_UF_WRTPS		;  software write protect

	TSTL	UQB$L_ONLINE_HQB(R4) 	; Pointer to online host	
	BEQL	5$			; If not online to anyone, ok - branch.
	CMPL	HRB$L_HQB(R3),-		; Were we already online to
		UQB$L_ONLINE_HQB(R4) 	;  the remote host
	BNEQ	5$			; Different host - branch.
	BICW3	#^C WRITABLE_UNIT_FLAGS, -	; Verify that the same unit flags 
		MSCP$W_UNT_FLGS(R2), R0		; were specified.
	BICW3	#^C WRITABLE_UNIT_FLAGS, -
		UQB$W_UNIT_FLAGS(R4), R1
	CMPW	R0, R1			; Compare the MSCP flags to the UQB flags
	BEQL	5$			; Same unit flags specified - branch.
	ROTL	#24,#MSCP$W_UNT_FLGS,R0	; Identify the bad field
	BRW	TMSCP$PACKET_ERROR	; Return an end msg with error status

5$:
	MOVL	UQB$L_UCB(R4),R4	; Get UCB address from UQB
	BBC	#DEV$V_MSCP,-		; If this is a MSCP device,
		UCB$L_DEVCHAR2(R4),30$	;  and there are already requests
	TSTW	UCB$W_RWAITCNT(R4)	;  waiting, this might be a trap!
	BEQL	20$			;  Instead of falling in,

10$:
	MOVL	#MSCP$K_ST_OFFLN,R0	;  Return a status of offline and let
	BRW	TMSCP$SEND_END		;  the client find another path

;
; A PACKACK is sent to the local class driver to determine if the device is 
; reachable. If a success is returned, the device is accessible. Any error
; returns causes a status of offline to return.
;

20$:
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Make sure we have the IRP address
	MOVL	R4,IRP$L_UCB(R5)	; Save the UCB address
	MOVL	R3,IRP$L_HRB(R5)	; Save the HRB address
	MOVW	#IO$_PACKACK,-		; Set up the function code,
		IRP$W_FUNC(R5)		;  to do the mount verification
	BSBW	TMSCP$SET_CLEAR_SEX	; Set clear serious exception
	BSBW	TMSCP$SET_CLEAR_CDL	; Set clear cached data lost
	MOVL	HRB$L_UQB(R3),R4	; Restore UQB for seq. synch.

;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	BSBW	TMSCP$DRIVE_CHECK	; Call a subroutine to do the I/O

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
;

	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	BLBS	R0,80$			; If the I/O was successful cont...

30$:
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB so that 
	MOVL	UQB$L_UCB(R4),R4	;  we can restore the UCB
	BBC	#UCB$V_ONLINE,-		; If device is online locally
		UCB$L_STS(R4), 40$	;  then the device cannot be shared
	MOVZWL	#<MSCP$K_SC_EXUSE@ -	; Return offline with exclusive use
		MSCP$V_ST_SBCOD! -	;
		MSCP$K_ST_OFFLN>,R0	;
	BRW	TMSCP$SEND_END		;  set the MSCP status and return

40$:
	BBS	#DEV$V_AVL,-		; If the device is offline, set status and
		UCB$L_DEVCHAR(R4), 10$	;  return MSCP end packet.

;
; Either a PACKACK to a TMSCP tape was successful or for non-TMSCP tapes, the
; unit was found to be available, which means the tape is accessible. Make sure
; all the status codes are set properly to reflect an "online" status and
; then return the device characteristics in the online end message.
; 

80$:
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	MOVL	HRB$L_UQB(R3),R4	; Restore the UQB address
	MOVW	#UQB$K_ST_ONLINE,-	; Update the state field
		UQB$W_STATE(R4)		;  in the UQB
	MOVL	#MSCP$K_ST_SUCC,R0	; Assume a successful status
	TSTL	UQB$L_ONLINE_HQB(R4) 	; Pointer to online host	
	BEQL	90$			; If not online to anyone, ok
	CMPL	HRB$L_HQB(R3),-		; Were we already online to
		UQB$L_ONLINE_HQB(R4) 	;  the remote host
	BEQL	100$

;
; Remove this check before shipping.
;

	BUG_CHECK MSCPSERV,FATAL	; The assign system service should
;		.WORD	^XFEFF
;		.IIF IDN <FATAL>,<FATAL> , .WORD	BUG$_MSCPSERV!4
					;  have guaranteed that we would
					;  not have gotten this far.

90$:
	MOVL	UQB$L_UCB(R4),R1	; Get the address of the UCB
	MOVL	HRB$L_HQB(R3),-		; Get the HQB address
		UQB$L_ONLINE_HQB(R4) 	; Pointer to online host	
	INCB	UCB$B_ONLCNT(R1)	; Bump the online counter
	BRB	TMSCP$SET_UNIT_CHAR	; Update the device characteristics.
	
100$:
	MOVZWL	#<MSCP$K_SC_ALONL@ -	; Set the subcode indicating the unit
		MSCP$V_ST_SBCOD! -	;  was "already online"
		MSCP$K_ST_SUCC>,R0	;  and a status code of success
	BRW	TMSCP$RECORD_COMMON	;  and finish off the request


	.SBTTL	-	SET UNIT CHARACTERISTICS		(- 10 -)

;+
; Functional Description:
;
; This command is used to control host settable unit characteristics and obtain
; those characteristics that are necessary for proper class driver operation.
;
; Inputs:
; 
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$SET_UNIT_CHAR::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

;
; Is unit online to the requestor?
;
	
	CMPL	UQB$L_ONLINE_HQB(R4),-	; Is the host that has this unit
		HRB$L_HQB(R3)		;  online the same as the requesting host?
	BEQL	10$			; If online, continue...
	MOVL	#MSCP$K_ST_OFFLN,R0	; Otherwise this is an invalid command
	BRW	TMSCP$SEND_END

;
; Unit ONLINE to this host - Check to see if write protect enabled.
;

10$:
	BBS 	#MSCP$V_MD_STWRP,-	; Check "enable set write protect"
		MSCP$W_MODIFIER(R2),15$	;  if it's set continue...
	BICW	#MSCP$M_UF_WRTPS,-	; If the set write protection bit
		MSCP$W_UNT_FLGS(R2)	;  was set, write protect is host settable
;
; Check all user specified device characteristics stored in 
; MSCP$W_UNT_FLGS. Set them in the CDRP$L_MEDIA field.
;

15$:
	MOVL	UQB$L_UCB(R4),R1	; Get the UCB address
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the IRP address.

	ASSUME	UCB$L_DEVDEPND2 EQ 4+UCB$L_DEVDEPEND

	MOVW	# 8, IRP$W_BCNT(R5)	; Set the characteristics length.
	BBC	#DEV$V_MSCP,-		; Not a TMSCP type device - branch.
		UCB$L_DEVCHAR2(R1), 17$
	MOVW	# 12, IRP$W_BCNT(R5)	; Specify extended device characteristics.

17$:
	BICW	# MSCP$M_UF_WBKNV ! -	; Disable write-back cache for this unit.
		  MSCP$M_UF_SCCHH, -	; Enable read-ahead caching for this unit.
		UQB$W_UNIT_FLAGS(R4)
	BBC	#MSCP$V_UF_WBKNV,-	; Write-back caching should be 
		MSCP$W_UNT_FLGS(R2),20$ ;  disabled - branch.
	BISW	#MSCP$M_UF_WBKNV,-	; Write-back caching enabled for this unit.
		UQB$W_UNIT_FLAGS(R4)
	BISL	#MT2$M_WBC_ENABLE,-	; Request write-back caching.
		IRP$L_MEDIA+8(R5)

;
;		Kludge alert:  The buffer size, device class and device type
;		are passed using one of the reserved fields in the TMSCP message.
;		The location of this field must match the location used by the 
;		TUDRIVER in the routine SETMODE_BEGIN_IVCMD.
;

20$:
	MOVL	MSCP$W_UNT_FLGS+2(R2),-	; Restore buffer size, class and type
		IRP$L_MEDIA(R5)		;
	BBC	#MSCP$V_UF_SCCHH,-	; Read-ahead caching should be enabled for
		MSCP$W_UNT_FLGS(R2),-	;  this unit - branch.
		30$
	BISW	#MSCP$M_UF_SCCHH,-	; Disable read-ahead caching for this unit.
		UQB$W_UNIT_FLAGS(R4)
	BISL	#MT2$M_RDC_DISABLE,-	; Request read-ahead caching be disabled.
		IRP$L_MEDIA+8(R5)

30$:
	MOVL	MSCP$L_DEV_PARM(R2),-	; Set up device dependent
		UCB$L_MSCPDEVPARAM(R1)	;  in the local UCB

;
;		Convert the MSCP tape FORMAT to the VMS tape format.
;

	MOVQ	R0, -(SP)		; Save the required registers.
	MOVZWL	MSCP$W_FORMAT(R2), R1	; Get the MSCP format value.
	BSBW	TMSCP$MSCPTOVMS_DENS	; Convert to the VMS density value.
	INSV	R0, # MT$V_DENSITY, -	; Set the VMS tape density.
		# MT$S_DENSITY, -
		IRP$L_MEDIA+4(R5)
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$R0HIST		; Save the density value.
	.ENDC	   ;DEBUG$PC_HISTORY


;
;		Convert the MSCP tape SPEED to the VMS tape speed.
;

	MOVZWL	MSCP$W_FORMAT(R2), R1	; Get the MSCP format value.
	MOVZWL	MSCP$W_SPEED(R2), R0	; Get the MSCP speed value.
	BSBW	TMSCP$MSCPTOSPEED	; Convert to the VMS speed value.
	INSV	R0, # MT$V_SPEED, -	; Set the VMS tape speed.
		# MT$S_SPEED, -
		IRP$L_MEDIA+4(R5)
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$R0HIST		; Save the speed value.
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVQ	(SP)+, R0		; Restore the registers.

;
; Issue an IO$_SETMODE to the local driver.
;

	MOVL	R2,HRB$L_MSGBUF(R3)	; Restore packet address
	MOVL	R1,IRP$L_UCB(R5)	; Save the UCB address
	MOVW	#IO$_SETMODE,-		; Set up the function code,
		IRP$W_FUNC(R5)		;  to do the mount verification
	BSBW	TMSCP$SET_CLEAR_SEX	; Set clear serious exception
	BSBW	TMSCP$SET_CLEAR_CDL	; Set clear cached data lost
	MOVL	R3,IRP$L_HRB(R5)	; Save HRB for I/O completion

;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	BSBW	TMSCP$DRIVE_CHECK	; Call a subroutine to do the I/O

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
;

	BLBS	R0,40$			; If the transfer was successful, cont...
	BSBW	TMSCP$CHECK_XFER_STATUS	;  otherwise, return proper status.

40$:
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Restore IRP address
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore packet address
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB to as to
	MOVL	UQB$L_UCB(R4),R1	;  get the UCB
	MOVL	#MSCP$K_ST_SUCC,R0	; Return success status.

;
; The IO$_SETMODE will cause the local driver to update several fields in
; the UCB. These fields will be returned to the remote host in the MSCP
; end packet.
;
;	R0  =  MSCP status value
;	R1  =  UCB address
;	R2  =  MSCP packet
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

TMSCP$RECORD_COMMON::
	.IF	DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Log the PC for debugging.
	.ENDC  ;DEBUG$PC_HISTORY

;
;		Update the fields kept on the local host.
;

	MOVQ	UQB$Q_UNIT_ID(R4),-	;  returning a unit identifier
		MSCP$Q_UNIT_ID(R2)	;  in the end packet also.
	MOVW	UQB$W_UNIT_FLAGS(R4),-	; Put the set unit flags in the
		MSCP$W_UNT_FLGS(R2)	;  end packet to return
	MOVW	UQB$W_MULT_UNIT(R4),-	;  Update the multi-unit flags
		MSCP$W_MULT_UNT(R2)	;  in the end packet.

;
;		Get the fields from the local UCB.
;

	MOVL	UCB$L_MEDIA_ID(R1),-	; Get the media identifier from
		MSCP$L_MEDIA_ID(R2)	;  the unit control block

	GET_TAPE_FORMAT		R1
;	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
;		UCB$L_DEVCHAR2(R1), 30063$	; Not MSCP tape - branch.
;	MOVW	UCB$W_TU_FORMAT(R1), -	; Steal tape format from UCB.
;	BRB	30064$
;30063$:
;	MOVQ	R0, -(SP)
;	EXTZV	# MT$V_DENSITY, -		; Extract VMS density from the UCB.
;		# MT$S_DENSITY, -
;		UCB$L_DEVDEPEND(R1), R0
;	CLRL	R1				; Use default VMS density table.
;	BSBW	TMSCP$VMSTOMSCP_DENS		; Convert tape density value.
;	MOVW	R1, MSCP$W_FORMAT(R2)		; Return the tape format.
;	MOVQ	(SP)+, R0
;30064$:
	GET_TAPE_MAXWRCNT	R1, R5	; Maximum write record size.
;	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
;		UCB$L_DEVCHAR2(R1), 30065$	; Not MSCP tape - branch.
;	MOVL	UCB$L_TU_MAXWRCNT(R1), -	; Steal tape maxwrcnt from UCB.
;	BRB	30066$
;30065$:
;	MOVZWL	#^XFFFF, R5			; Supply the default tape maxwrcnt.
;	MOVL	R5, MSCP$L_MAXWTREC(R2)	; Return the tape maxwrcnt.
;30066$:
	GET_TAPE_NOISE		R1, R5	; Size of noise records.
;	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
;		UCB$L_DEVCHAR2(R1), 30067$	; Not MSCP tape - branch.
;	MOVW	UCB$W_TU_NOISE(R1), -	; Steal tape noise from UCB.
;	BRB	30068$
;30067$:
;	CLRL	R5				; Supply the default tape noise.
;	MOVW	R5, MSCP$W_NOISEREC(R2)	; Return the tape noise.
;30068$:
	GET_TAPE_SPEED		R1, -
				MSCP$W_FORMAT(R2)
;	BBC	# DEV$V_MSCP, -			; Check for a MSCP tape device.
;		UCB$L_DEVCHAR2(R1), 30069$	; Not MSCP tape - branch.
;	MOVW	UCB$W_TU_SPEED(R1), -	; Steal tape speed from UCB.
;	BRB	30070$
;30069$:
;	MOVQ	R0, -(SP)
;	MOVZWL	MSCP$W_FORMAT(R2), R1			; Get the MSCP tape format.
;	EXTZV	# MT$V_SPEED, -			; Extract VMS tape speed from R1
;		# MT$S_SPEED, -			; and return as the tape speed.
;		UCB$L_DEVDEPEND(R1), R0
;	BSBW	TMSCP$SPEEDTOMSCP		; Convert speed value.
;	MOVW	R0, MSCP$W_SPEED(R2)		; Return the tape speed.
;	MOVQ	(SP)+, R0
;30070$:

	BRW	TMSCP$SEND_END		; Send it all back as an end message


	.SBTTL	-	DETERMINE ACCESS PATHS			(- 11 -)

;+
; Functional Description:
;
; The DETERMINE ACCESS PATHS command is used by class drivers to determine
; the topology of multi-access drive configurations. When sent to a unit
; that is "Unit-Online", it causes that unit and any other units that share
; the same access path to identify themselves to any other controllers to 
; which they are connected. For the Host Based TMSCP Server, this command
; is treated as a no-op that always returns success, since units cannot be 
; connected to more than one controller (Host Based MSCP Server).
;
; Inputs:
; 
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;-

TMSCP$DET_ACC_PATH::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	#MSCP$K_ST_SUCC,R0	; Set a successful status code
	BRW	TMSCP$SEND_END		;  and return the end packet


	.SBTTL	-	ACCESS					(- 16 -)

;+
; Functional Description:
;
; Data is read from the unit, checked for errors, and discarded. The
; purpose of this command is to verify that the designated data can be
; accessed (read) without error. 
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  The high byte contains the offset (within the MSCP packet)
;		of the field in which the error was found.
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$ACCESS::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	ROTL	#24,#MSCP$B_OPCODE,R0	; This command is not supported 
	BRW	TMSCP$PACKET_ERROR	;  by the host based tape server


	.SBTTL	-	COMPARE HOST DATA			(- 32 -)

;+
; Functional Description:
;
; Data is read from the requesting cluster member and compared to the data
; on the specified tape. A compare error is reported unless the data is 
; identical. Note that the occurance of any other error, except a forced
; error, at the same point as the compare error overrrides the compare 
; error.
; 
; This command provides a write function. The write routine can be used if
; we just skip over the check for writelock and use a different function code.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  I/O function code
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$COMPARE_HOST_DATA::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVZWL	#IO$_WRITECHECK,R0	; Set the function code to write check
	BRW	TMSCP$DO_WRT		;  and go do it


	.SBTTL	-	ERASE					(- 18 -)

;+
; Functional Description:
;
; If the tape is a TMSCP type device, no buffer is mapped, and the request is 
; sent on to the driver. If however, the tape does not support the erase
; command, the request is treated very much like a write command referencing
; a buffer of zeroes out to the end of the tape. The tape is then rewound to
; BOT.
;
; If the device is not MSCP device, WRITEs of (maximum controller byte count)
; are issued to the local driver until SS$_ENDOFTAPE is reached. Since non-TMSCP
; drivers can not issue immediate completion WRITE commands, we have to wait
; for every I/O to return, even if the user specified IO$M_NOWAIT. However, during
; the IO$_REWIND, all drivers can take advantage of the immediate completion 
; modifier.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  The high byte contains the offset (within the MSCP packet)
;		of the field in which the error was found.
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$ERASE::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	BITW	#<MSCP$M_UF_WRTPS!-	; If this unit is not software or
		MSCP$M_UF_WRTPD!-	;  data safety write protected or
		MSCP$M_UF_WRTPH>,-	;  hardware write protected,
		UQB$W_UNIT_FLAGS(R4)	;  then
	BEQL	10$			;  go ahead and allow the write
	BISW	#UQB$M_ST_SEREX,-	; Set serious exception
		UQB$W_ST_FLAGS(R4)
	MOVL	#MSCP$K_ST_WRTPR,R0	; Otherwise, set an error status
	BRW	TMSCP$SEND_END		;  and return an end message

10$:
	MOVL	G^SCS$GL_TMSCP, R5	; Get TSRV structure address.
	MOVL	MSCP$L_BYTE_CNT(R2),-	; Get the byte count passed
		HRB$L_OBCNT(R3)		;  and save it as the original byte cnt
	BNEQ	30$			; If its nonzero then go through with it
	INCL	TSRV$L_BLKCOUNT(R5)	; Count the zero block transfer
	MOVL	#MSCP$K_ST_SUCC,R0	; Set a successful status to return
	BRW	TMSCP$SEND_END		;  and we are finished!

;
; Set up the IRP for a transfer, regardless of whether or not the
; device is a TMSCP device or not.
;

30$:
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the address of the IRP
	MOVL	UQB$L_UCB(R4),R1	; Get the UCB address for this unit.
	MOVL	R1,IRP$L_UCB(R5)	; Save away the UCB address in the IRP
	MOVW	HRB$W_BOFF(R3),-	;  the byte offset within the page,
		IRP$W_BOFF(R5)		;  of the start of the buffer
	MOVL	R3,IRP$L_HRB(R5)	; Set the HRB address for IO completion
	MOVL	UCB$L_MAXBCNT(R1),-	; Limit the size of this
		HRB$L_BCNT(R3)		;  transfer to the device limit
	BBC	#DEV$V_MSCP,-		; If we are not dealing with a MSCP type
		UCB$L_DEVCHAR2(R1),40$	;  device, then we may have more work to do
	CMPB	UCB$B_DEVTYPE(R1),-	; The TU78 can support IO$_DSE
		#DT$_TU78		;  as well.
	BEQL	40$			; 
	MOVL	#IO$_DSE,R0		; Put the function code in a register
;
; Use the IMMEDIATE COMPLETION opcode modifier bit passed in the packet.
;
	BBC	#MSCP$V_MD_IMMED,-	; Immediate completion specified?
		MSCP$W_MODIFIER(R2),35$	; If not, don't change op code.
	BISW	#IO$M_NOWAIT,-		; Set nowait in the function itself
		R0			;   (to return immediately)
35$:
	CLRL	HRB$L_SVAPTE(R3)	; No SVAPTE is necessary if supported
	BRB	50$			; Perform the I/O

;
; Since the erase command is using the pseudo page table and not actually
; allocating and mapping memory for its transfers, we will not check the
; byte count of the transfer.
; 

40$:
	MOVL	#IO$_WRITEPBLK,R0	; Set the function code...
	MOVL	G^EXE$GL_ERASEPPT,-	; Save the system PPT in which all the 
		HRB$L_SVAPTE(R3)	;  PTEs point to the same system EPB
;
; The IRP packet is ready to send to the tape. R0 contains the function code.
;

50$:
	MOVL	HRB$L_BCNT(R3),-	; Save the byte count to be used for
		IRP$L_BCNT(R5)		;  this (portion of the) transfer.
	MOVL	HRB$L_SVAPTE(R3),-	; Move the local buffer
		IRP$L_SVAPTE(R5)	;  virtual page table entry,
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	MOVL	R3,IRP$L_HRB(R5)	; Save the HRB address
	MOVW	R0,IRP$W_FUNC(R5)	; Set the completed code in the IRP
	BSBW	TMSCP$SET_CLEAR_SEX	; Set clear serious exception
	BSBW	TMSCP$SET_CLEAR_CDL	; Set clear cached data lost
;
; Send this request to the tape. If the request is aborted while on the
; tape queue, it is cleaned up when control is returned to TMSCP$BACK, and 
; never heard from again.
;

70$:

;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	BSBW	TMSCP$DRIVE_TRANSFER	; Give the request to the tape

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
;

	DECL	HRB$L_CMD_STS(R3)	; Report progress on this request
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the address of the IRP back
	MOVW	IRP$W_FUNC(R5),R1	; If this was a device which didn't
	BICW	#IO$M_NOWAIT,R1		;  support IO$_DSE, then we may have
	CMPW	R1,#IO$_DSE		;  to do more writes of zeroes.
	BEQL	200$
	CMPW	#SS$_ENDOFTAPE,R0       ; If we reached EOT, we can start
	BEQL	100$			;  the rewind.
	BLBS	R0,80$			; If the transfer was successful, cont...
	BSBW	TMSCP$CHECK_XFER_STATUS	;  otherwise, return proper status.
	MOVL	HRB$L_IRP_CDRP(R3), R5	; Get the IRP address

80$:
	MOVL	HRB$L_BCNT(R3),-	; Use the maximum controller supported byte
		IRP$L_BCNT(R5)		;  count
	BRB	70$			; Send the next chunk of zeros

100$:
	MOVW	#IO$_REWIND,-		;  before checking anything
		IRP$W_FUNC(R5)		;  else.
	MOVL	R3,IRP$L_HRB(R5)	; Save HRB address
	BSBW	TMSCP$SET_CLEAR_SEX
;
; Use the IMMEDIATE COMPLETION opcode modifier bit passed in the packet.
;
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore MSCP buffer address.
	BBC	#MSCP$V_MD_IMMED,-	; Immediate completion specified?
		MSCP$W_MODIFIER(R2),110$ ; If not, don't change op code.
	BISW	#IO$M_NOWAIT,-		; Set nowait in the function itself
		IRP$W_FUNC(R5)		;    (to returm imediately)
110$:                                        

;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP address
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	BSBW	TMSCP$DRIVE_TRANSFER	; Call a subroutine to do the I/O

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
;

	BLBS	R0,200$			; If the transfer was successful, cont...
	BSBW	TMSCP$CHECK_XFER_STATUS	;  otherwise, return proper status.


200$:
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	MOVL	#MSCP$K_ST_SUCC,R0	; Set the completion code to success
	BRW	TMSCP$SEND_END		;  and return a response to the request


	.SBTTL	-	ERGAP

;+
;Functional Description:
;
; The Write Inter-Record Gap command records a format dependent record gap, at
; the current tape position. It is used for bypassing tape defects when write 
; error recovery is host controlled. Since only TMSCP tapes support this function,
; and there is no way to simulate this function in other drivers, this function
; cannot be supported.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  MSCP completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-


TMSCP$ERASE_GAP::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	#MSCP$K_ST_ICMD, R0	; This command is not supported.
	BRW	TMSCP$SEND_END


	.SBTTL	-	REPOSITION

;+
;Functional Description:
;
; The REPOSITION command is used to position the tape from one location to 
; another. There are four subcommands defined within the realms of
; the REPOSITION command. IO$_REWIND is implemented if MSCP$M_MD_REWND 
; modifier is set. If the Object Count command modifier MSCP$M_MD_OBJCT
; is set and the field MSCP$L_POSITION is non-zero, then a pseudo
; Skip Tape Object is performed by using IO$_SKIPRECORDs. The Skip Tape
; Object is requested by the class driver in routine BRING_UNIT_ONLINE.
; After we have brought a unit back ONLINE after a connection failure,
; we reposition back to where the tape was positioned at the time
; of the failure by skipping the correct number of objects.
;
; If the object Count command modifier is clear and the field MSCP$L_REC_CNT
; is non-zero, then an IO$_SKIPRECORD is issued for that number of
; records. If the Object Count command modifier is clear and the
; field MSCP$L_TMGP_CNT is non-zero, then an IO$_SKIPFILE is
; issued.
;
; Table D-1 in the TMSCP spec shows all the possible combinations
; of modifiers and field values for the REPOSITION command, and
; the correct controller actions for each. Fortunately, the class
; driver uses a subset of these actions, and only those actions
; are implemented here.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$REPOSITION::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
;
; Is unit online to the requestor?
;
	
	CMPL	UQB$L_ONLINE_HQB(R4),-	; Is the host that has this unit
		HRB$L_HQB(R3)		;  online the same as the requesting host?
	BEQL	10$			; If online, continue...
	MOVL	#MSCP$K_ST_OFFLN,R0	; Otherwise this is an invalid command
	BRW	TMSCP$SEND_END

10$:
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the IRP address.
;
; First, check for the Rewind modifier bit set. If set, no matter what other
; modifier is set, an IO$_REWIND is always issued first.
;
	BBS	#MSCP$V_MD_REWND,-	; If REWIND modifier set,
		MSCP$W_MODIFIER(R2),15$	;  issue an IO$_REWIND 
	BRW	80$			; Branch assist.

15$:
	MOVW	#IO$_REWIND,-		;  Set rewind function in IRP.
		IRP$W_FUNC(R5)
	BSBW	TMSCP$SET_CLEAR_SEX
;
; Use the IMMEDIATE COMPLETION opcode modifier bit passed in the packet.
;
	BBC	#MSCP$V_MD_IMMED,-	; Immediate completion specified?
		MSCP$W_MODIFIER(R2),20$ ; If not, don't change op code.
	BISW	#IO$M_NOWAIT,-		; Set nowait in the function itself
		IRP$W_FUNC(R5)		;    (to returm imediately)
20$:
	MOVL	R3,IRP$L_HRB(R5)	; Save HRB for I/O completion
	MOVL	UQB$L_UCB(R4),-		; Save UCB for I/O completion
		IRP$L_UCB(R5)

;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	BSBW	TMSCP$DRIVE_TRANSFER	; Call a subroutine to do the I/O

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
;
; The IO$_REWIND will cause the local driver to begin the first operation
; of this REPOSITION command. Possible further I/Os to the drive are
; investigated.
;

	CLRL	HRB$L_OBJECT_SKIP(R3)	; Initiate object counter
	MOVL	HRB$L_UQB(R3),R4	; Get the host queue block address
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore packet address
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Restore IRP-CDRP pair.
	BLBS	R0,30$			; If the transfer was successful, cont...
	BSBW	TMSCP$CHECK_XFER_STATUS	;  otherwise, return proper status.
	BRB	32$

30$:
	MOVL	#MSCP$K_ST_SUCC,R0	; This command has completed successfuly
	BISW	#UQB$M_ST_BOT,-		; If rewind was successful,
		UQB$W_ST_FLAGS(R4)	;  we must be at BOT
	BICW	#UQB$M_ST_EOT,-		; If rewind was successful,
		UQB$W_ST_FLAGS(R4)	;  we can clear the EOT flag.

32$:
	CLRW	UQB$W_TAPEM_SKIP(R4) 	; Initialize tape mark counter
	CLRW	UQB$W_RECORD_SKIP(R4)	;  as well as record counter.
	BBS	#MSCP$V_MD_OBJCT,-	; If Skip Object specified, begin
		MSCP$W_MODIFIER(R2),35$	;  issuing IO$_SKIPRECORD to simulate.
	BRW	1000$			;  else we are done.
;
; None of the tape drivers support skipping objects, only tape marks or records.
; The class driver issues this command during the routine to bring an available
; unit online after a connection failure. The command is a REPOSITION rewind
; followed by a repositioning back to UCB$L_RECORD, which was the object count
; of the drive before the connection was lost.
;
; Issue an IO$_SKIPRECORD specifying the number of objects to skip as the number
; of records to skip. Either this many records will be skipped or a tape mark
; will be encountered. If a tape mark is encountered before the reposition is
; satisfied, subtract the object count by the number of records already skipped
; plus the tape mark, from the original count and reissue the IO$_SKIPRECORD
; with this new count.
;

35$:
	MOVL	MSCP$L_REC_CNT(R2),-	; Initialize the number of objects 
		HRB$L_OBJECT_SKIP(R3)	;   to skip

40$:
 	MOVZWL	HRB$L_OBJECT_SKIP(R3),- ; Skip records until tape mark hit
		IRP$L_MEDIA(R5)		;  or object count satisfied
	MOVW	#IO$_SKIPRECORD,-	; Issue an IO$_SKIPRECORD for the
		IRP$W_FUNC(R5)		;  number of objects to skip.
	BSBW	TMSCP$SET_CLEAR_SEX	; Set clear serious exception.

50$:
	CLRW	IRP$W_BOFF(R5)		; No bytes to offset to.
	CLRL	IRP$L_SVAPTE(R5)	; No buffer.
	CLRL	IRP$L_BCNT(R5)		; No bytes to transfer.
	MOVL	R3,IRP$L_HRB(R5)	; Save HRB for I/O completion
	MOVL	UQB$L_UCB(R4),-		; Save UCB for I/O completion
		IRP$L_UCB(R5)

;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	BSBW	TMSCP$DRIVE_TRANSFER	; Call a subroutine to do the I/O

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
;

	MOVL	HRB$L_MSGBUF(R3),R2	; Restore packet address
	CMPW	R0,#SS$_ENDOFFILE	; Tape mark encountered?
	BNEQ	60$			; If so then add a tape mark
	INCW	UQB$W_TAPEM_SKIP(R4) 	; to the total number of tape marks
	DECW	UQB$W_RECORD_SKIP(R4)   ; and compensate now for the tape mark
					;  that is included in the count field
					;  of the IOSB.
	BRB	70$			; 

60$:
	BLBS	R0,70$			; If the transfer was successful, cont...
	BSBW	TMSCP$CHECK_XFER_STATUS
	BRW	1000$

70$:
	ASHL	#-16,R0,R0		; # of records skipped in low word.
	ADDW2	R0,UQB$W_RECORD_SKIP(R4); Add records skipped (and tapem) to total.
	MOVL	HRB$L_IRP_CDRP(R3), R5	; Get the IRP address.
	SUBW	R0,HRB$L_CURRENT_SKIP(R3); Determine number of remaining objects left.
	BNEQ	40$			; Continue until all objects skipped
;
; Fill in appropriate MSCP fields.
;
	MOVL	UQB$W_TAPEM_SKIP(R4),- 	; Update number of tape
		MSCP$L_TMGP_CNT(R2)	;  marks skipped.
	MOVL	UQB$W_RECORD_SKIP(R4),-	; Update number of records
		MSCP$L_REC_CNT(R2)	;  skipped.
	MOVL	UQB$L_UCB(R4),R4	;  get the UCB
	MOVL	#MSCP$K_ST_SUCC,R0	; This command has completed successfuly
	BRW	1000$			

;
; Process IO$_SKIPRECORD or IO$_SPACERECORD
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP message address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  IRP address, scratch
;

80$:
	MOVL	MSCP$L_REC_CNT(R2),-	; Set up number of records to skip and
		IRP$L_MEDIA(R5)		;  if non-zero, this is a Skip Record command
	BEQL	100$			;  else it is a Skip File or Seq Nop command

	MOVW	#IO$_SKIPRECORD,-	; Issue the logical command IO$_SKIPRECORD for the
		IRP$W_FUNC(R5)		;  number of objects to skip
	BBC	#MSCP$V_MD_DLEOT,-	; If LEOT detection is not enabled, this
		MSCP$W_MODIFIER(R2), 90$;  is a physical command
	MOVW	#IO$_SPACERECORD,-	; Issue an IO$_SPACERECORD for the
		IRP$W_FUNC(R5)		;  number of objects to skip

90$:
	BRB	200$			;  and issue the I/O
;
; Process IO$_SKIPFILE and IO$_SPACEFILE.
;

100$:
	MOVL	MSCP$L_TMGP_CNT(R2),-	; Set up number of files 
		IRP$L_MEDIA(R5)		;  to skip and 
	MOVW	#IO$_SKIPFILE,-		;  issue an IO$_SKIPFILES.
		IRP$W_FUNC(R5)		;  
	BBC	#MSCP$V_MD_DLEOT,-	; If LEOT detection is not enabled, this
		MSCP$W_MODIFIER(R2),-	;  is a physical command
		200$
	MOVW	#IO$_SPACEFILE,-	;   so issue an IO$_SPACEFILE
		IRP$W_FUNC(R5)		;   instead

200$:
	BSBW	TMSCP$SET_CLEAR_SEX
	BBC	#MSCP$V_MD_REVRS,-	; If the reverse modifier is set, then
		MSCP$W_MODIFIER(R2),220$ ;  the original I/O had a negative record count.
	MNEGW	IRP$L_MEDIA(R5),-	; Negate the request                    
		IRP$L_MEDIA(R5)		;  so the a Skip Record reverse is performed.

220$:
	MOVL	R3,IRP$L_HRB(R5)	; Save HRB for I/O completion
	MOVL	UQB$L_UCB(R4),-		; Save UCB for I/O completion
		IRP$L_UCB(R5)

;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	BSBW	TMSCP$DRIVE_TRANSFER	; Call a subroutine to do the I/O

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
;

	MOVL	HRB$L_IRP_CDRP(R3),R5	; Restore IRP address
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore packet address
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB to as to
	MOVL	UQB$L_UCB(R4),R4	;  get the UCB
	ASHL	#-16,R0,R1		; Calculate record or file skipped count.
	TSTL	MSCP$L_REC_CNT(R2)	; Was this an IO$_SKIPRECORD or IO$_SPACERECORD?
	BEQL	400$			; If so, then
	MOVZWL	R1,MSCP$L_RCSKIPED(R2)	;  update number of records skipped
	CLRL	MSCP$L_TMSKIPED(R2)
	BRB	450$

400$:
	MOVZWL	R1,MSCP$L_TMSKIPED(R2)	;  else update number of files skipped
	CLRL	MSCP$L_RCSKIPED(R2)

450$:
	MOVL	UCB$L_RECORD(R4),-	; Update position of tape
		MSCP$L_POSITION(R2)
	BBC	#DEV$V_MSCP,-		; If we are not dealing with an MSCP
		UCB$L_DEVCHAR2(R4),500$	;  device, then we can get the record
	MOVL	HRB$L_RECORD(R3),-	;  count out of the HRB since the 
		MSCP$L_POSITION(R2)	;  class driver set it for us.

500$:
	BLBS	R0,600$			; If the transfer was successful, cont...
	BSBW	TMSCP$CHECK_XFER_STATUS	;  otherwise, return proper status.
	BRB	1000$

600$:
	MOVL	#MSCP$K_ST_SUCC,R0	; This command has completed successfuly
	BBC	#MT$V_BOT,-
		UCB$L_DEVDEPEND(R4),1000$
	MOVL	HRB$L_UQB(R3), R4	; Get unit queue block address.
	MOVL	#MSCP$K_ST_BOT,R0
	BICW	#UQB$M_ST_EOT,-		; Clear the end of tape flag
		UQB$W_ST_FLAGS(R4)	;
	BISW	#MSCP$M_EF_SEREX,-	; Set the serious exception flag
		MSCP$B_FLAGS(R2)	
	.IF DEFINED DEBUG$CURRENT_SANITY
	BSBW	TMSCP$CHECK_CURRENT	; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY

1000$:
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore packet address
	BRW	TMSCP$SEND_END		; Send an end packet with R0 status


	.SBTTL	-	FLUSH					(- 19 -)

;+
; Functional Description:
;
; All controllers that do not support caching must treat this command as
; a no-op that always succeeds. This command is included in the minimal
; tape MSCP subset soley for compatability with controllers that do support
; caching.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Success
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$FLUSH::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Make sure we have the IRP address
	MOVL	UQB$L_UCB(R4), -	; Save the UCB address
		IRP$L_UCB(R5)
	MOVL	R3,IRP$L_HRB(R5)	; Save the HRB address
	MOVW	#IO$_FLUSH,-		; Set up the function code,
		IRP$W_FUNC(R5)		;  to do the mount verification
	BSBW	TMSCP$SET_CLEAR_SEX	; Set clear serious exception
	BSBW	TMSCP$SET_CLEAR_CDL	; Set clear cached data lost

;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	MOVL	UQB$L_UCB(R4),R1	; R1 <= UCB
	BBC	#DEV$V_WBC,-		; Flush hardware cache if possible
		UCB$L_DEVCHAR2(R1),10$	;  
	BSBW	TMSCP$DRIVE_CHECK	; Call a subroutine to do the I/O

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
; 
		
	BLBS	R0,10$			; On success, continue
	BSBW	TMSCP$CHECK_XFER_STATUS	; Check the error code
	BRB	20$			;  and send the end packet	

10$:
	MOVZWL	#MSCP$K_ST_SUCC,R0	; Return an illegal command

20$:
	BRW	TMSCP$SEND_END		; Send an end packet with R0 status


	.SBTTL	-	READ					(- 33 -)

;+
; Functional Description:
;
; This routine is called to process a MSCP read packet. It is called from the
; non-sequential buffer class command routine. When processing of this 
; command is complete, control is returned to the main line routine.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Read completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$READ::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

;
; Allocate and load mapping resources to map the transfer buffer area
; in the requesting system.
;

	TSTW	MSCP$L_BYTE_CNT(R2)	; Zero request size?
	BNEQ	10$
	MOVZWL	#MSCP$K_ST_SUCC,R0	; Set success
	BRW	TMSCP$SEND_END		; and return.

10$:
	BSBW	TMSCP$ALLOCATE		; Find some memory for the transfer

;
; Now prepare the CDRP for mapping the local buffer just allocated
; for use by SCS.
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;

	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the IRP base address
	MOVAL	IRP$L_FQFL(R5),R5	; Move down to the CDRP portion
	MOVAL	HRB$B_LBUFF(R3),-	; Define the local buffer handle as
		CDRP$L_LBUFH_AD(R5)	;  the address of LBUFF
	MOVL	HRB$L_HQB(R3),R4	; Get the HQB temporarily
	MOVL	HQB$L_CDT(R4),-		;  so we can put a fresh CDT address
		CDRP$L_CDT(R5)		;  in the CDRP
	MOVL	HRB$L_PDT(R3),R4	; Pick up the PDT address
	MOVW	#HRB$K_ST_MAP_WAIT,-	; Set the state of this request to 
		HRB$W_STATE(R3)		;  mapping a buffer

;		Verify that the HRB fields are in the same order as the IRP fields.
;		This is  a requirement of the MAP function.

	ASSUME	<HRB$W_BOFF-HRB$L_SVAPTE> EQ <IRP$W_BOFF-IRP$L_SVAPTE>
	ASSUME	<HRB$L_BCNT-HRB$L_SVAPTE> EQ <IRP$L_BCNT-IRP$L_SVAPTE>

;		Verify that the correct base address is used.

	ASSUME	HRB$W_BOFF GT HRB$L_SVAPTE
	ASSUME	HRB$L_BCNT GT HRB$L_SVAPTE

	MOVAL	HRB$L_SVAPTE(R3),R1	; Address of the three longword buffer
	CLRL	R2			; Access mode of transfer is kernel
	MAP				; Map the buffer
;		JSB	@PDT$L_MAP(R4)
	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis

;
; Since the processing of this request can be stalled while waiting for
; the local buffer to be mapped, we need to check to see if the request
; has been canceled before going on.
;
	BISW	#HRB$M_MAP,-		; Indicate that we have allocated 
		HRB$W_FLAGS(R3)		;  mapping resources for this request
	BITW	#<HRB$M_ABORT!-		; Check to see if this request has been
		HRB$M_ABORTWS>,-	;  aborted either by ^Y or by the 
		HRB$W_FLAGS(R3)		;  loss of connection to the client
	BEQL	TMSCP$READ_TRANSFER	; Request was not aborted, continue...
	BRW	TMSCP$ABORT_READ	; Otherwise cleanup this request

;
; Prepare the IRP for the transfer from tape
;

TMSCP$READ_TRANSFER::
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the address of the IRP
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address
	MOVL	UQB$L_UCB(R4),-		;  so we can pull out the UCB address
		IRP$L_UCB(R5)		;  and save it away in the IRP
	MOVL	HRB$L_SVAPTE(R3),-	; Move the local buffer
		IRP$L_SVAPTE(R5)	;  virtual page table entry,
	MOVW	HRB$W_BOFF(R3),-	;  the byte offset within the page,
		IRP$W_BOFF(R5)		;  of the start of the buffer
	MOVL	HRB$L_BCNT(R3),-	;  and the byte count to be used for
		IRP$L_BCNT(R5)		;  this (portion of the) transfer.
	.IF	DEFINED DEBUG$PC_HISTORY
	MOVL	IRP$L_BCNT(R5), R0	; Get the transfer count.
	BSBW	TMSCP$PCHIST_R3R0	; Log the PC for debugging.
	.ENDC  ;DEBUG$PC_HISTORY
;
; Check for any supported modifiers in the MSCP request. If they are set
; there, then we should set them in the IRP we send out also.
;
	MOVZWL	#IO$_READPBLK,R0	; Pass I/O function code to be used
	MOVL	HRB$L_MSGBUF(R3),R2	; Get back the MSCP packet address
	BBC	#MSCP$V_MD_COMP,-	; If the compare modifier was set in
		MSCP$W_MODIFIER(R2),3$	;  the MSCP packet,
	BISW	#IO$M_DATACHECK,R0	;  then set it in the IRP also

3$:
	BBC	#MSCP$V_MD_SEREC,-	; If the  modifier was set in
		MSCP$W_MODIFIER(R2),4$	;  the MSCP packet,
	BISW	#IO$M_INHRETRY,R0	;  then set it in the IRP also

4$:
	BBC	#MSCP$V_MD_REVRS,-	; If the reverse modifier was set
		MSCP$W_MODIFIER(R2),5$	;  in the MSCP packet
	BISW	#IO$M_REVERSE,R0	;  then set it in the IRP also

5$:
	MOVW	R0,IRP$W_FUNC(R5)	; Fill in the function code
	BSBW	TMSCP$SET_CLEAR_SEX	; Set clear serious exception
	BSBW	TMSCP$SET_CLEAR_CDL	; Set clear cached data lost
	MOVL	R3,IRP$L_HRB(R5)	; Save the HRB address
	.IF	DEFINED DEBUG$PC_HISTORY
	MOVZWL	IRP$W_FUNC(R5), R0	; Get the function value.
	BSBW	TMSCP$R0HIST		; Log the value for debugging.
	.ENDC  ;DEBUG$PC_HISTORY

;
; The IRP is queue to the local driver. If the command is aborted during
; this thread, the request is finished off in subroutine TMSCP$BACK and 
; resources are deallocated.
;
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	BSBW	TMSCP$DRIVE_TRANSFER	; Execute a tape transfer

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
;

	BLBS	R0,10$			; If the transfer was successful, cont...
	BSBW	TMSCP$CHECK_XFER_STATUS	;  otherwise, return proper status.
	BRW	40$
;
; Send the data to the host buffer
;

10$:
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Restore IRP address
	MOVL	IRP$L_BCNT(R5),-	; Get the number of bytes transferred
		HRB$L_BCNT(R3)		;  and save it away in the request block
	MOVAL	IRP$L_FQFL(R5),R5	; Move from the IRP to the CDRP address
	MOVL	HRB$L_UQB(R3),R1	; Get the UQB
	MOVL	UQB$L_UCB(R1),R1	;  and then the UCB.
	MOVL	HRB$L_MSGBUF(R3),R2	; Get the MSCP packet address back
	BBC	#MT$V_BOT,-
		UCB$L_DEVDEPEND(R1),20$
	MOVL	#MSCP$K_ST_BOT,R0
	BISW	#MSCP$M_EF_SEREX,-
		MSCP$B_FLAGS(R2)	

;
; Initialize a CDRP that SCS can use to send the retrieved data to the 
; requesting system.
;

20$:
	MOVL	HRB$L_HQB(R3),R1	; Get the HQB address 
	MOVL	HQB$L_CDT(R1),-		; Get the address of the CDT 
		CDRP$L_CDT(R5)		;  and place it in the CDRP for the call
	MOVL	HRB$L_BCNT(R3),-	; Use the number of bytes retrieved
		CDRP$L_XCT_LEN(R5)	;  as the number to send to host
	MOVAL	MSCP$B_BUFFER(R2),-	; Get the remote buffer handle
		CDRP$L_RBUFH_AD(R5)	;  and save it for the call
	CLRL	CDRP$L_RBOFF(R5)	; Set the remote buffer offset.
	MOVAL	HRB$B_LBUFF(R3),-	; Define the local buffer handle as
		CDRP$L_LBUFH_AD(R5)	;  the address of LBUFF
	CLRL	CDRP$L_LBOFF(R5)	;  in the remote and local buffers
	MOVL	HRB$L_PDT(R3),R4	; Get the PDT address for the SCS call
	MOVW	#HRB$K_ST_SNDAT_WAIT,-	; Set the state of this request to
		HRB$W_STATE(R3)		;  SCS block transfer
	SEND_DATA			; Send to remote host's buffer.
;		JSB	G^SCS$ALLOC_RSPID
;		JSB	@PDT$L_ALLOCMSG(R4)
;		BLBC	R0,30071$
;		JSB	@PDT$L_SENDDATA(R4)
;	30071$:

;
; This thread is suspended until the block transfer completes. At that time,
; control is returned here with the following registers:
;
;		R0  =  status
; 		R3  =  Host Request Buffer address
;		R4  =  PDT address
;		R5  =  CDRP address
;
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis
	BLBC	R0,TMSCP$ABORT_READ	; If the send was not successful 
	BITW	#<HRB$M_ABORT!-		;  or if this request has been aborted,
		HRB$M_ABORTWS>,-	;  or canceled,
		HRB$W_FLAGS(R3)		;  just clean everything up
	BNEQ	TMSCP$ABORT_READ
;
; Update the byte count transferred, and send the end packet.
;

30$:
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	MOVL	HRB$L_IRP_CDRP(R3),R1	; Get the address of the IRP
	MOVZWL	2+IRP$L_IOST1(R1), -	; Get number of bytes actually 
		MSCP$L_TAPEREC(R2)	;  transferred.
	MOVL	HRB$L_UQB(R3),R1	; Get the UQB
	MOVL	UQB$L_UCB(R1),R1	;  and then the UCB.
	MOVL	UCB$L_RECORD(R1),-	; Retrieve the position information
		MSCP$L_POSITION(R2)	;  to send back in the end packet.
	BBC	#DEV$V_MSCP,-		; If we are not dealing with an MSCP
		UCB$L_DEVCHAR2(R1),35$	;  device, then we can get the record
	MOVL	HRB$L_RECORD(R3),-	;  count out of the HRB since the 
		MSCP$L_POSITION(R2)	;  class driver set it for us.

35$:
	MOVL	HRB$L_IRP_CDRP(R3),R1	; Get the address of the IRP
	MOVZWL	IRP$L_IOST1(R1),R0	; Get transfer completion status.
	BLBS	R0,37$			; If status is success, continue
	BSBW	TMSCP$CHECK_XFER_STATUS	; Check the error code
	BRB	40$			;  and send back end packet

37$:
	MOVL	#MSCP$K_ST_SUCC,R0	; This command has completed successfuly
	.IF DEFINED DEBUG$CURRENT_SANITY
	BSBW	TMSCP$CHECK_CURRENT	; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY

40$:
	BRW	TMSCP$SEND_END		; Send an end packet with R0 status

;
; If the send data was unsuccessful, just free the allocated resources, and
; this request is finished off.
; 

TMSCP$ABORT_READ::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	BBS	#HRB$V_ABORT,-		; If this request was aborted due to a
		HRB$W_FLAGS(R3),10$	;  disconnect, no end msg is necessary
	MOVL	HRB$L_MSGBUF(R3),R2	; Get the message buffer address 
	MOVL	#MSCP$K_ST_ABRTD,R0	; Set the status to aborted
	BRW	TMSCP$SEND_END		;  and send out an end message

10$:
	BRW	TMSCP$CLEANUP_HRB	; Deallocate the resources used.


	.SBTTL	-	WRITE_TAPE_MARK

;+
;
; Write an extended interrecord gap followed by a tape mark.
;	
; If the immediate completion modifier was set, send the end packet back 
; immediately, returning success.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Write completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$WRITE_TAPE_MARK::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Set up IRP-CDRP pair.
	MOVW	#IO$_WRITEMARK,-	;  before checking anything
		IRP$W_FUNC(R5)		;  else.
	BSBW	TMSCP$SET_CLEAR_SEX	; Set clear serious exception
;
; Use the IMMEDIATE COMPLETION opcode modifier bit passed in the packet.
;
	BBC	#MSCP$V_MD_IMMED,-	; Immediate completion specified?
		MSCP$W_MODIFIER(R2),10$ ; If not, don't change op code.
	BISW	#IO$M_NOWAIT,-		; Set nowait in the function itself
		IRP$W_FUNC(R5)		;    (to returm imediately)
10$:
	BSBW	TMSCP$SET_CLEAR_CDL	; Set clear cached data lost

;
; The IRP is queue to the local driver. If the command is aborted during
; this thread, the request is finished off in subroutine TMSCP$BACK and 
; resources are deallocated.
;

	MOVL	UQB$L_UCB(R4),-		;  Save the UCB address in IRP.
		IRP$L_UCB(R5)
	MOVL	R3,IRP$L_HRB(R5)	; Save HRB for I/O completion

;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	BSBW	TMSCP$DRIVE_TRANSFER	; Do the tape transfer

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
;

	BLBS	R0,25$			; If the transfer was successful, cont...
	BSBW	TMSCP$CHECK_XFER_STATUS	;  otherwise, return proper status.
	BRB	30$			;

25$:
	MOVZWL	#MSCP$K_ST_SUCC,R0	; Set success for return
;
; Final clean up for transfer commands.  Set status, release resources.
;

30$:
	MOVL	HRB$L_IRP_CDRP(R3), R5	; Get the IRP address
	MOVL	HRB$L_UQB(R3),R4	; Restore UQB address
	MOVL	UQB$L_UCB(R4),R1	; Restore UCB address
	MOVL	UCB$L_RECORD(R1),-	; Initialize the MCSP position
		UQB$L_POSITION(R4)	;  with the UCB record count
	BBC	#DEV$V_MSCP,-		; If we are not dealing with an MSCP
		UCB$L_DEVCHAR2(R1),50$	;  device, then we can get the record
	MOVL	HRB$L_RECORD(R3),-	;  count out of the HRB since the 
		UQB$L_POSITION(R4)	;  class driver set it for us.

50$:
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	MOVZWL	2+IRP$L_IOST1(R5), -	; Save the byte count 
		MSCP$L_TAPEREC(R2)	;  to be returned in the end packet
	MOVL	UQB$L_POSITION(R4),-	; Retrieve the position information
		MSCP$L_POSITION(R2)	;  to send back in the end packet.
	BRW	TMSCP$SEND_END		; Send an end packet with R0 status


	.SBTTL	-	WRITE					(- 34 -)

;+
; Functional Description:
;
; This routine is called to process an MSCP write request. The information 
; the cluster member wishes to place on the tape is transferred to the local
; serving processor (subject to availability of resources on that processor),
; and from there is written on to tape.
;
; If the immediate completion modifier was set, send the end packet back as
; soon as the remote host's data is buffered locally. In this case, the
; end packet always returns success.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Outputs:
;
;	R0  =  Write completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

TMSCP$WRITE::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

;
; Before doing anything make sure that the unit is not write locked.
;
	BITW	#<MSCP$M_UF_WRTPS!-	; If this unit is not hardware
		MSCP$M_UF_WRTPD!-	;  data safety write protected or
		MSCP$M_UF_WRTPH>,-	;  or software write protected,
		UQB$W_UNIT_FLAGS(R4)	;  then
	BEQL	TMSCP$DO_WRT		;  go ahead and allow the write
	BISW	#UQB$M_ST_SEREX,-	; Set serious exception
		UQB$W_ST_FLAGS(R4)
	MOVL	#MSCP$K_ST_WRTPR,R0	; Otherwise, set an error status
	BRW	TMSCP$SEND_END		; ... and return an end message

TMSCP$DO_WRT::

;
; Allocate a local buffer area to use during the transfer, and map 
; that area for use by SCS routines.
;

	BSBW	TMSCP$ALLOCATE		; Find some local memory for the xfer

;
; Now prepare the CDRP for mapping the local buffer just allocated
; for use by SCS.
;
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the IRP base address
	MOVAL	IRP$L_FQFL(R5),R5	; Move down to the CDRP portion
	MOVAL	HRB$B_LBUFF(R3),-	; Define the local buffer handle as
		CDRP$L_LBUFH_AD(R5)	;  the address of LBUFF
	MOVL	HRB$L_HQB(R3),R4	; Get the HQB temporarily
	MOVL	HQB$L_CDT(R4),-		;  so we can put a fresh CDT address
		CDRP$L_CDT(R5)		;  in the CDRP
	MOVL	HRB$L_PDT(R3),R4	; Pick up the PDT address
	MOVW	#HRB$K_ST_MAP_WAIT,-	; Set the state of this request to 
		HRB$W_STATE(R3)		;  mapping a buffer

;		Verify that the HRB fields are in the same order as the IRP fields.
;		This is  a requirement of the MAP function.

	ASSUME	<HRB$W_BOFF-HRB$L_SVAPTE> EQ <IRP$W_BOFF-IRP$L_SVAPTE>
	ASSUME	<HRB$L_BCNT-HRB$L_SVAPTE> EQ <IRP$L_BCNT-IRP$L_SVAPTE>

;		Verify that the correct base address is used.

	ASSUME	HRB$W_BOFF GT HRB$L_SVAPTE
	ASSUME	HRB$L_BCNT GT HRB$L_SVAPTE

	MOVAL	HRB$L_SVAPTE(R3),R1	; Address of the three longword buffer
	CLRL	R2			; Access mode of transfer is kernel
	MAP				; Map the buffer
;		JSB	@PDT$L_MAP(R4)
	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis

;
; Since the processing of this request can be stalled while waiting for
; the local buffer to be mapped, we need to check to see if the request
; has been canceled before going on.
;
	BISW	#HRB$M_MAP,-		; Indicate that we have allocated 
		HRB$W_FLAGS(R3)		;  mapping resources for this request
	BITW	#<HRB$M_ABORT!-		; If this command has not been aborted
		HRB$M_ABORTWS>,-	;  or canceled,
		HRB$W_FLAGS(R3)		;  then continue on...
	BEQL	TMSCP$WRITE_TRANSFER
	BRW	TMSCP$ABORT_WRITE	;  otherwise abort this request here

TMSCP$WRITE_TRANSFER::
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the address of the IRP
	MOVAL	IRP$L_FQFL(R5),R5	; Move from the IRP to the CDRP address

;
; Initialize a CDRP that SCS can use to send the retrieved data to the 
; requesting system.
;
	MOVL	HRB$L_HQB(R3),R1	; Get the HQB address 
	MOVL	HQB$L_CDT(R1),-		; Get the address of the CDT 
		CDRP$L_CDT(R5)		;  and place it in the CDRP for the call
	MOVL	HRB$L_BCNT(R3),-	; Use the number of bytes retrieved
		CDRP$L_XCT_LEN(R5)	;  as the number to send to host
	MOVL	HRB$L_MSGBUF(R3),R2	; Get the MSCP packet address back
	MOVAL	MSCP$B_BUFFER(R2),-	; Get the remote buffer handle
		CDRP$L_RBUFH_AD(R5)	;  and save it for the call
	CLRL	CDRP$L_RBOFF(R5)	; Set the remote buffer offset.
	MOVAL	HRB$B_LBUFF(R3),-	; Define the local buffer handle as
		CDRP$L_LBUFH_AD(R5)	;  the address of LBUFF
	CLRL	CDRP$L_LBOFF(R5)	;  in the remote and local buffers
	MOVL	HRB$L_PDT(R3),R4	; Get the PDT address for the SCS call
	MOVW	#HRB$K_ST_SNDAT_WAIT,-	; Reflect in the state, that we are
		HRB$W_STATE(R3)		;  awaiting data arrival from the host
	REQUEST_DATA			; Get the data from host
;		JSB	G^SCS$ALLOC_RSPID
;		JSB	@PDT$L_ALLOCMSG(R4)
;		BLBC	R0,30072$
;		JSB	@PDT$L_REQDATA(R4)
;	30072$:

;
; This thread is suspended until the block transfer completes. At that time,
; control is returned here with the following registers:
;
;		R0  =  Status
;		R3  =  HRB address
;		R4  =  PDT address
;		R5  =  CDRP address
;
	BISW	#HRB$M_STATE_INVALID,-	; Set the invalid bit showing this
		HRB$W_STATE(R3)		;  as a stale state
	BLBS	R0,5$			; Everything looks good so far
	BRW	TMSCP$ABORT_WRITE	; If the request failed, abort the cmd

5$:
	BITW	#<HRB$M_ABORT!-		; Check to make sure this request 
		HRB$M_ABORTWS>,-	;  has not been aborted while receiving
		HRB$W_FLAGS(R3)		;  data from the client system
	BEQL	9$			; If it has not been aborted, continue..
	BRW	TMSCP$ABORT_WRITE	;  otherwise, do the proper cleanup

;
; Prepare the IRP for a tape transfer, so the data can be transferred to
; the tape.
;

9$:
	DECL	HRB$L_CMD_STS(R3)	; Report progress on this request
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the address of the IRP
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address
	MOVL	UQB$L_UCB(R4),-		;  so we can pull out the UCB address
		IRP$L_UCB(R5)		;  and save it away in the IRP
	CLRL	IRP$L_WIND(R5)		; No window control blocks in the server
	CLRB	IRP$B_EFN(R5)		; Clear out the event flag number
	CLRW	IRP$W_CHAN(R5)		; Clear the process I/O channel number
	CLRW	IRP$W_STS(R5)		; Start out with a clear status field
	MOVL	HRB$L_SVAPTE(R3),-	; Move the local buffer
		IRP$L_SVAPTE(R5)	;  virtual page table entry,
	MOVW	HRB$W_BOFF(R3),-	;  the byte offset within the page,
		IRP$W_BOFF(R5)		;  of the start of the buffer
	MOVL	HRB$L_BCNT(R3),-	;  and the byte count to be used for
		IRP$L_BCNT(R5)		;  this (portion of the) transfer.
	MOVL	R3,IRP$L_HRB(R5)	; Set the HRB address for IO completion

;
; Check for any supported modifiers in the MSCP request. If they are set
; there, then we should set them in the IRP we send out also.
;
	MOVW	#IO$_WRITEPBLK,R0	; Set the function code
	MOVL	HRB$L_MSGBUF(R3),R2	; Get back the MSCP packet address
	BBC	#MSCP$V_MD_COMP,-	; If the compare modifier was set in
		MSCP$W_MODIFIER(R2),10$	;  the MSCP packet,
	BISW	#IO$M_DATACHECK,R0	;  then set it in the IRP also

10$:
	BBC	#MSCP$V_MD_SEREC,-	; If the  modifier was set in
		MSCP$W_MODIFIER(R2),12$	;  the MSCP packet,
	BISW	#IO$M_INHRETRY,R0	;  then set it in the IRP also

12$:
	MOVW	R0,IRP$W_FUNC(R5)	; Fill in the function code
	BSBW	TMSCP$SET_CLEAR_SEX
	BSBW	TMSCP$SET_IMMED		; Set immediate completion
	BSBW	TMSCP$SET_CLEAR_CDL	; Set clear cached data lost
	.IF	DEFINED DEBUG$PC_HISTORY
	MOVL	IRP$L_BCNT(R5), R0	; Get the transfer length.
	BSBW	TMSCP$PCHIST_R3R0	; Log the value for debugging.
	.ENDC  ;DEBUG$PC_HISTORY
	.IF	DEFINED DEBUG$PC_HISTORY
	MOVZWL	IRP$W_FUNC(R5), R0	; Get the function value.
	BSBW	TMSCP$R0HIST		; Log the value for debugging.
	.ENDC  ;DEBUG$PC_HISTORY

;
; The IRP is queue to the local driver. If the command is aborted during
; this thread, the request is finished off in subroutine TMSCP$BACK and 
; resources are deallocated.
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  Scratch
;

	BSBW	TMSCP$DRIVE_TRANSFER	; Do the tape transfer

;
; Control returns here from entry point TMSCP$BACK. 
;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
;

	BLBS	R0,26$			; If the transfer was successful, cont...
	BSBW	TMSCP$CHECK_XFER_STATUS	;  otherwise, return proper status.
	BRB	30$			;

26$:
	MOVZWL	#MSCP$K_ST_SUCC,R0	; Set success for return

;
; Final clean up for transfer commands.  Set status, release resources.
;

30$:
	MOVL	HRB$L_UQB(R3),R4	; Restore UQB address
	MOVL	UQB$L_UCB(R4),R1	; Restore UCB address
	MOVL	UCB$L_RECORD(R1),-	; Initialize the MCSP position
		UQB$L_POSITION(R4)	;  with the UCB record count
	BBC	#DEV$V_MSCP,-		; If we are not dealing with an MSCP
		UCB$L_DEVCHAR2(R1),50$	;  device, then we can get the record
	MOVL	HRB$L_RECORD(R3),-	;  count out of the HRB since the 
		UQB$L_POSITION(R4)	;  class driver set it for us.

50$:
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	MOVL	HRB$L_IRP_CDRP(R3),R1	; Get the address of the IRP
	MOVZWL	2+IRP$L_IOST1(R1), -	; Save the byte count
		MSCP$L_TAPEREC(R2)	;  to be returned in the end packet
	MOVL	UQB$L_POSITION(R4),-	; Retrieve the position information
		MSCP$L_POSITION(R2)	;  to send back in the end packet.
	BRW	TMSCP$SEND_END		; Send an end packet with R0 status

TMSCP$ABORT_WRITE::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	BBS	#HRB$V_ABORT,-		; If this request was aborted due to a
		HRB$W_FLAGS(R3),10$	;  disconnect, no end msg is necessary
	MOVL	HRB$L_MSGBUF(R3),R2	; Get the message buffer address 
	MOVL	#MSCP$K_ST_ABRTD,R0	; Set the status to aborted
	BRW	TMSCP$SEND_END		;  and send out an end message

10$:
	BRW	TMSCP$CLEANUP_HRB	; Deallocate the resources used.


	.SBTTL	Utility Routines
	.SBTTL	-	ALLOCATE - Allocate a local buffer for the request

;+
; Functional Description:
;
; This routine allocates a buffer on the serving node based on the 
; transfer size described in the MSCP request received. 
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;
; Output:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Side Effects:
;
;	HRB$L_BCNT	filled in with the byte count from the MSCP packet.
;	HRB$L_OBCNT	filled in with the byte count from the MSCP packet
;	HRB$L_BUFLEN	is filled in with the length of the allocated buffer
;	HRB$L_BUFADR	is filled in with the address of the allocated buffer
;	HRB$L_SVAPTE	address of virtual page table entry for the buffer
;	HRB$W_BOFF	byte offset into the page table of start of buffer
;-

	.ENABLE	LSB

TMSCP$ALLOCATE::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

;
; Set up the count fields in the HRB to their initial values. 
;

	MOVL	MSCP$L_BYTE_CNT(R2),-	; Initialize byte count
		HRB$L_OBCNT(R3)		;
	MOVL	HRB$L_OBCNT(R3),R1	; Set number of bytes to be transferred
	MOVL	R2,HRB$L_MSGBUF(R3)	; Save message buffer address.
	JSB	G^EXE$ALONONPAGED
	BLBS	R0,250$			; Continue if successful
	.IF DEFINED DEBUG$TMSCP_REJECT_TRACKING
	MOVL	G^SCS$GL_TMSCP, R5	; Get TSRV structure address.
	MOVL	R0,TSRV$L_REPLC_CNT+8(R5); Save away the error code
	INCL	TSRV$L_REPLC_CNT+12(R5)	; Save away a record of the reject
	.ENDC	   ;DEBUG$TMSCP_REJECT_TRACKING
	REJECT	#1			; If we were not able to satisfy this 
;		MOVL	#1, R0
;		JSB	@PDT$L_REJECT(R4)
	RSB				;  memory request, reject the request

250$:
	MOVL	R1,HRB$L_BUFLEN(R3)	; Save away the length and the
	MOVL	R2,HRB$L_BUFADR(R3)	;  address of the buffer allocated
	MOVL	HRB$L_OBCNT(R3),-	; Byte count is original request
		HRB$L_BCNT(R3)		;  size from the packet.
	BICW3	#^C<VA$M_BYTE>,R2,-	; Determine the offset into the page
		HRB$W_BOFF(R3)		;  and store that value away
	EXTZV	#VA$V_VPN,#VA$S_VPN,-	; Then find the virtual page number
		R2, R2			;  to use as the destination.
	MOVL	G^MMG$GL_SPTBASE,R0	; Get the address of the sys page table.
	MOVAL	(R0)[R2],-		; Then get the address of the page
		HRB$L_SVAPTE(R3)	;  table entry into the HRB.
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	RSB

	.DISABLE LSB


	.SBTTL  -	SEND_IRP -  prepare and issue an I/O to a tape
	.SBTTL  -	DRIVE_TRANSFER -  prepare and issue read/write type I/O
	.SBTTL  -	DRIVE_CHECK -  prepare and issue ONLINE, AVAIL, SUC, and FLUSH

;+
; Functional Description:
;
; This routine can be called at two entry points. Entry DRIVE_TRANSFER is
; used for read/write type of commands. The tape drive is checked for being 
; unit online to the requesting host. Performance monitoring is activated 
; if enabled, and the I/O is then queued to the driver. We then RSB back to
; the caller's caller (most likely the port driver.) This thread suspends
; until I/O post calls us and resumes at subroutine TMSCP$BACK.
;
; Inputs:
;
;	R3  =  HRB address
;
; The second entry point, DRIVE_CHECK prepares an IRP for a tape I/O without 
; checking drive status. This routine is called by the ONLINE, AVAILABLE, and 
; SET UNIT CHAR code paths. The return to TMSCP$BACK is still the same.
;
; Inputs:
;
;	R3  =  HRB address
;
; Outputs:
;
;	None (See Outputs from routine TMSCP$BACK)
;
; Side Effects:
;
;	The IRP associated with the HRB is queued to be sent to the tape.
;-

TMSCP$BLOCK_SEREX::
	.BYTE	IO$_AVAILABLE
	.BYTE	IO$_UNLOAD
	.BYTE	IO$_DSE
	.BYTE	IO$_FLUSH
	.BYTE	IO$_READPBLK
	.BYTE	IO$_SKIPFILE
	.BYTE	IO$_SKIPRECORD
	.BYTE	IO$_SPACEFILE
	.BYTE	IO$_SPACERECORD
	.BYTE	IO$_WRITEPBLK
	.BYTE	IO$_WRITEMARK
	.BYTE	IO$_WRITECHECK

TMSCP$BLOCK_SEREX_LEN	=	. - TMSCP$BLOCK_SEREX

TMSCP$DRIVE_TRANSFER::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the IRP address 
	BISW	#IRP$M_SRVIO,-		; Set server I/O for all transfer
		 IRP$W_STS(R5)		;  commands.
	BRB	TMSCP$COMMON_TRANSFER	; Set up fields for I/O post.

TMSCP$DRIVE_CHECK::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the IRP address 
	BISW	#<IRP$M_SRVIO!IRP$M_PHYSIO>,- ; Set physical I/O and server I/O
		IRP$W_STS(R5)		;  for ONLINE, AVAILABLE, and SUC

TMSCP$COMMON_TRANSFER::
	.IF	DEFINED DEBUG$PC_HISTORY
	PUSHL	R0
	MOVZWL	IRP$W_FUNC(R5), R0	; Get the function value.
	BSBW	TMSCP$R0HIST		; Log the value for debugging.
	MOVL	IRP$L_MEDIA(R5), R0	; Log the MEDIA values.
	BSBW	TMSCP$R0HIST
	MOVL	4+IRP$L_MEDIA(R5), R0
	BSBW	TMSCP$R0HIST
	MOVL	8+IRP$L_MEDIA(R5), R0
	BSBW	TMSCP$R0HIST
	POPL	R0
	.ENDC  ;DEBUG$PC_HISTORY
	MOVL	HRB$L_UQB(R3),R4	; Set up the UQB address in order to
	BBC	#MSCP$V_MD_CLSEX,-	; Is the clear serious exception modifier
		IRP$L_MEDIA+6(R5),10$	;  set?
	BICW	#UQB$M_ST_SEREX,-	; If so, clear serious exception.
		UQB$W_ST_FLAGS(R4)
	BRB	15$			;	

10$:
	BBC	#UQB$V_ST_SEREX,-	; Is the serious exception bit set?
		UQB$W_ST_FLAGS(R4),15$
	EXTZV	#IO$V_FCODE,-		; Extract the function code
		#IO$S_FCODE,-		;  
		IRP$W_FUNC(R5),R2	;
	PUSHR	#^M<R0,R1>		; Preserve registers destroyed by LOCC
	LOCC	R2,#TMSCP$BLOCK_SEREX_LEN,-	; Is this a command which gets
		TMSCP$BLOCK_SEREX	;  blocked by serious exception?
	POPR	#^M<R0,R1>		; Preserve registers destroyed by LOCC
	BEQL	15$			; EQL <= not a blocked command
	MOVL	HRB$L_MSGBUF(R3),R2	; MSCP packet address from the HRB
	BEQL	12$			; Caching command; already sent end_packt
	ADDL	# 4, SP			; Kill this thread by discarding return addr
	MOVL	#MSCP$K_ST_PRESE,R0	; Return serious exception and don't
	BSBW	TMSCP$SEND_END		;  even bother sending it to the drive

12$:
	RSB				; Return to lower IPL

15$:
	POPL	HRB$L_RESPC(R3)		; Where to return after IOPOST
	MOVL	R3,IRP$L_ASTPRM(R5)	; Store HRB in IRP for class driver use
	MOVAB	W^TMSCP$BACK, -		; Address to which IOPOST returns IRP
		IRP$L_PID(R5)
	INCL	UQB$L_IOCNT(R4)		;  record the queue and total I/O
	INCW	UQB$W_QLEN(R4)		;  contribution the server initiates
	MOVL	R5,R3			; Set up R3 with IRP for EXE$INSIOQC
	MOVL	UQB$L_UCB(R4),R5	; Set up the UCB address for EXE$INSIOQC
	TSTL	G^PMS$GL_IOPFMPDB	; I/O performance monitoring enabled?
	BEQL	20$			; If not, don't monitor
	JSB	G^PMS$START_RQ		; Gather Start I/O Request statistics
	BRB	30$			;  and start the I/O

20$:
	LOCK	LOCKNAME=PERFMON,-	; Lock PERFMON access
		PRESERVE=NO,-		;  don't preserve R0
		SAVIPL=-(SP)		;  save the current IPL
;	 MFPR	S^#PR$_IPL,-(SP)
;	BLBC	G^SMP$GL_FLAGS,30075$
;	 MOVZBL	S^#SPL$C_PERFMON,R0
;	 JSB	G^SMP$ACQUIRE
;	BRB	30076$
;30075$:
;		MTPR	S^#IPL$_PERFMON,S^#PR$_IPL
;30076$:
	MOVL	G^PMS$GL_IOPFMSEQ,-	; Insert the I/O sequence number
		IRP$L_SEQNUM(R3)	;  into the IRP
	INCL	G^PMS$GL_IOPFMSEQ	; And bump the global counter
	UNLOCK	LOCKNAME=PERFMON,-	; Release PERFMON access
		PRESERVE=NO,-		;  don't preserve R0
		NEWIPL=(SP)+		;  restore the saved IPL
;	BLBC	G^SMP$GL_FLAGS,30082$
;	 MOVZBL	S^#SPL$C_PERFMON,R0
;	 JSB	G^SMP$RELEASE
;30082$:
;	 MTPR	(SP)+,S^#PR$_IPL

30$:
	MOVL	IRP$L_HRB(R3),R1	; Restore HRB address
	MOVW	#HRB$K_ST_DRV_WAIT,-	; Set the state of this request
		HRB$W_STATE(R1)		;  as queued to the driver
	INCW	UQB$W_NUM_IO(R4)	; Incrememt current IOs to the drive
	CMPW	UQB$W_MAX_IO(R4),-	; Did we exceed the maximum current
		UQB$W_NUM_IO(R4)	;  counter?
	BGEQ	40$			; No, go issue the IO
	MOVW	UQB$W_NUM_IO(R4),-	; Update maximum drive IO counter
		UQB$W_MAX_IO(R4)	;  

40$:
	JMP	G^EXE$INSIOQC		; Queue the IRP to the driver
;
; This routine now RSBs back to the caller's caller. This thread is
; suspended and will resume when IOPOST calls us at entry TMSCP$BACK.
;


	.SBTTL	-	BACK  -  return from tape I/O and continue

;+
; Functional Description:
;
; On returning from performing an I/O, the state of the HRB is checked 
; to see if the request has been aborted while we were out to tape. If it 
; was, the allocated resources are deallocated and the request finished off 
; without returning to the caller.
;
; Inputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  Scratch
;	R4  =  Scratch
;	R5  =  IRP address returned from IOPOST
;
; Outputs:
;
;	R0  =  The first longword of the IOSB
;	R2  =  preserved
;	R3  =  HRB address
;
; Side Effects:
;
;	Control is resumed at the address stored in the resume PC 
;	field of the Host Request Block.
;-                               

TMSCP$BACK::
	LOCK	LOCKNAME=SCS,-		; Lock SCS access
		PRESERVE=NO,-		;  don't preserve R0
		SAVIPL=-(SP)		;  save the current IPL
;	 MFPR	S^#PR$_IPL,-(SP)
;	BLBC	G^SMP$GL_FLAGS,30088$
;	 MOVZBL	S^#SPL$C_SCS,R0
;	 JSB	G^SMP$ACQUIRE
;	BRB	30089$
;30088$:
;		MTPR	S^#IPL$_SCS,S^#PR$_IPL
;30089$:
	MOVL	IRP$L_HRB(R5),R3	; Restore the HRB address
	.IF	DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Log the PC for debugging.
	.ENDC  ;DEBUG$PC_HISTORY
	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis
	BICW	#<IRP$M_PHYSIO!IRP$M_SRVIO>,- ; Clear physical I/O and server I/O flags
		IRP$W_STS(R5)		;  only needed for ONLINE and AVAILABLE (physio)
	TSTL	G^PMS$GL_IOPFMPDB	; I/O performance monitoring is 
	BEQL	10$			;  not enabled, skip
	JSB	G^PMS$END_RQ		; Gather End I/O Request statistics

10$:
	MOVL	HRB$L_UQB(R3),R4	; Restore UQB address
	DECW	UQB$W_NUM_IO(R4)	; Decrement counter for IOs to the drive.
	MOVL	IRP$L_UCB(R5),R1	; Pick up UCB pointer
	ADAWI	#-1,UCB$W_QLEN(R1)	; Drop queue length counter
	DECW	UQB$W_QLEN(R4)		; Drop the local queue length counter
	BITW	#<HRB$M_ABORT!-		; Check to see if we lost the connection
		HRB$M_ABORTWS>,-	;  or if this request was canceled,
		HRB$W_FLAGS(R3)		;  since this I/O was sent to tape
	BEQL	30$

;
; If this command has been canceled by a ^Y, it is aborted with status (an
; end message is returned). However, if this command has been aborted for any
; other reason there is no one to send and end message to. In this case, start 
; the next entry on the blocked queue if there is one, and deallocate any 
; resources held by this request.
;

	MOVL	HRB$L_MSGBUF(R3),R2	; Get the message buffer address 
	BEQL	20$			;  if none, don't return status
	MOVL	#MSCP$K_ST_ABRTD,R0	;  otherwise return an aborted status
	BSBW	TMSCP$SEND_END		;  in an end message before returning
	BRB	40$			;  back to a lower IPL

20$:
	BSBW	TMSCP$CLEANUP_HRB	; Clean up this request
	BRB	40$			; Return back to a lower IPL

30$:
	DECL	HRB$L_CMD_STS(R3)	; Report progress on this request
	MOVL	IRP$L_IOST1(R5),R0	; Return the I/O status block in R0

;
;	R0  =  I/O status block, scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address
;	R5  =  IRP address
;

	.IF	DEFINED DEBUG$PC_HISTORY
	PUSHAB	35$			; Set the return address.
	PUSHL	HRB$L_RESPC(R3)		; Resume previous thread.
	BRW	TMSCP$PCHIST_R3R0	; Log the PC for debugging.

35$:
	BSBW	TMSCP$PCHIST		; Log the PC for debugging.
	.IFF
	JSB	@HRB$L_RESPC(R3)	; Resume where we left off
	.ENDC  ;DEBUG$PC_HISTORY

40$:
	UNLOCK	LOCKNAME=SCS,-		; Release SCS access
		PRESERVE=NO,-		;  don't preserve R0
		NEWIPL=(SP)+,-		;  restore the saved IPL
		CONDITION=RESTORE	;  conditionally release spinlock
;	BLBC	G^SMP$GL_FLAGS,30095$
;	 MOVZBL	S^#SPL$C_SCS,R0
;	 JSB	G^SMP$RESTORE
;30095$:
;	 MTPR	(SP)+,S^#PR$_IPL
	RSB				;  and return


	.SBTTL	-	ALLOCATE_HRB -  Host Request Block Allocation

;+
; Functional Description:
;
; This module allocates enough memory for a Host Request Buffer, and
; fills in the preliminary fields. The Connection Decsriptor Table and 
; the Port Descriptor Table addresses are juggled around in this module 
; too. When control is passed to the server at TMSCP$MSG_IN, the CDT is in
; register 3 and the PDT is in register 4. Since R3 has been designated 
; as the HRB address, and the PDT is stored away in the HRB, control is
; returned to the caller with the CDT address in R4.
;
; Inputs:
;
;	None
;
; Outputs:
;
;	R0  =  completion status code
;		SS$_INSFMEM - not enough memory to allocate the HRB
;		SS$_NORMAL  - HRB and IRP were allocated and linked together
;	R3  =  HRB address
;
;	R1,R2,R4,R5  -  preserved
;-

TMSCP$ALLOCATE_HRB::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	PUSHR	#^M<R1,R2,R4,R5>	; Save the registers to be used
	MOVZBL	#HRB$K_LENGTH,R1	; Length of the requested buffer
	JSB	G^EXE$ALONONPAGED	; Get the memory
	BLBS	R0, 10$			; HRB allocated - branch.
	BRW	40$			; Unsuccessful return an error

10$:
	MOVL	R2,R3			; Save the address of the request buffer
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$R3HIST		; Save this HRB address.
	.ENDC	   ;DEBUG$PC_HISTORY

;
; Initialize known fields in the newly allocated HRB.
;

	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers destroyed by MOVC5
	MOVC5	#0,(SP),#0,-		; Initialize entire structure in
		#HRB$K_LENGTH,(R3)		;  in one fell swoop. 
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers destroyed by MOVC5
	MOVW	R1,HRB$W_SIZE(R3)	; Fill in the size field

	ASSUME	HRB$B_SUBTYPE EQ 1+HRB$B_TYPE

	MOVW	#<DYN$C_TSRV_HRB@8>! -	; Set data structure subtype and type.
		DYN$C_TSRV, -
		HRB$B_TYPE(R3)
	MNEGL	#2,HRB$L_CMD_STS(R3)	; Initialize the status field 

;
; Allocate and link the IRP into the HRB.
;

	MOVZBL	#IRP$K_LENGTH,R1	; Size of buffer to be allocated
	JSB	G^EXE$ALONONPAGED	; Get enough memory for an IRP-CDRP
	BLBC	R0, 30$			; Return an error if unsuccessful

;
; Fill in any of the IRP that we can at this point.
;
	MOVL	R2,HRB$L_IRP_CDRP(R3)	; Save away the IRP address
	MOVB	# DYN$C_IRP,-		; Fill in the type
		IRP$B_TYPE(R2)		; 
	CLRB	IRP$B_RMOD(R2)		; Clear the access mode of the request
	MOVW	R1, IRP$W_SIZE(R2)	; Fill in the size
	CLRL	IRP$L_WIND(R2)		; No window control blocks in the server
	CLRB	IRP$B_EFN(R2)		; Clear out the event flag number

	ASSUME	IRP$W_STS	EQ  2+IRP$W_CHAN

	CLRL	IRP$W_CHAN(R2)		; Clear channel and status.
	CLRL	IRP$L_RSPID(R2)		; Signify no response ID allocated
	CLRW	IRP$W_BOFF(R2)		; No bytes to offset to.
	CLRL	IRP$L_SVAPTE(R2)	; No buffer.
	CLRL	IRP$L_BCNT(R2)		; No bytes to transfer.
	CLRW	IRP$W_FUNC(R2)		; No function yet.
	MOVL	R3,IRP$L_HRB(R2)	; Set the HRB address for IO completion

	ASSUME	IRP$L_IOST1	EQ    IRP$L_MEDIA
	ASSUME	IRP$L_IOST2	EQ  4+IRP$L_IOST1
	ASSUME	IRP$B_CARCON	EQ  4+IRP$L_MEDIA

	CLRQ	IRP$L_MEDIA(R2)		; Initialize the I/O parameters.

	ASSUME	IRP$L_ABCNT	EQ  8+IRP$L_MEDIA
	ASSUME	IRP$L_OBCNT	EQ  4+IRP$L_ABCNT

	CLRQ	IRP$L_ABCNT(R2)

;
; Initialize a few of the CDRP fields.
;

	MOVAL	IRP$L_FQFL(R2),R2	; Move down to the CDRP portion
	CLRQ	CDRP$L_FQFL(R2)		; Show that we are not on any queue
	MOVB	#DYN$C_CDRP, -		; Fill in the CDRP type field
		CDRP$B_CD_TYPE(R2)	; 
	MOVW	#CDRP$L_IOQFL,-		; Negative offset to start of IRP
		CDRP$W_CDRPSIZE(R2)	;  on the front of this CDRP
	MOVB	#SPL$C_SCS,-		; Load the type of spinlock held
		CDRP$B_FLCK(R2)		;  for the server
	CLRL	CDRP$L_RWCPTR(R2)	; 
	MOVL	#SS$_NORMAL,R0		; Set success status

;
; Restore the registers to their original state and return.
;

20$:
	POPR	#^M<R1,R2,R4,R5>	; Put everything back
	RSB				;  and return to the caller

30$:
	MOVL	R3,R0			; Address of structure to deallocate
	JSB	G^EXE$DEANONPAGED	; Return the HRB to nonpaged pool

40$:
	MOVZWL	#SS$_INSFMEM,R0		; Set an error code
	BRB	20$


	.SBTTL	-	CLEANUP_HRB -  Host Request Resource Deallocation

;+
; Functional Description:
;
; This routine runs down through a HRB checking for resources that are 
; allocated for this request. As it finds them it deallocates the resources
; and cleanes up all the pointers used to link them all together.
;
; Inputs:
;
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  status code
;	R4  =  UQB address
;-

TMSCP$CLEANUP_HRB::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

;
; Start the next sequential command. 
; 
 
	BBC	#HRB$V_UNBLOCK, -	; If this is not a sequential command 
		HRB$W_FLAGS(R3), 5$	; then kill this thread - branch. 
	MOVL	HRB$L_UQB(R3), R4	; Get the Unit Queue Block address. 
	BEQL	5$			; VC_ERR processing - branch. 
	BSBW	TMSCP$UNBLOCK		; Start the next sequential command. 
 
5$: 
 
; 
; Dequeue this request from the blocked queue, if it's still hooked. 
; Commands that were aborted during the MAP_WAIT state, or the SNDAT_WAIT 
; can still be on the blocked for sequential completion queue. 
; 
 
	TSTL	HRB$L_WAIT_FL(R3)	; Are we still on the blocked queue? 
	BEQL	7$			; EQL implies not. 
	REMQUE	HRB$L_WAIT_FL(R3),R4	; Remove the request from the queue 
	MOVL	HRB$L_UQB(R3),R5	; Get the address of the UQB 
	BEQL	7$			;  we can't decrement what doesn't exist
	DECW	UQB$W_NUM_QUE(R5)	;  so we can update the counter 

;
; Set up the standard inputs for SCS calls; PDT in R4 and CDRP in R5
;

7$:
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the address of the IRP
	MOVAL	IRP$L_FQFL(R5),R5	; CDRP starting offset
	MOVL	HRB$L_PDT(R3),R4	; Set up the PDT address for SCS calls

;
; Release any mapping resources held.
;
	BBCC	#HRB$V_MAP,-		; Check to see if this request has
		HRB$W_FLAGS(R3),10$	;  any mapping resources allocated
	MOVAL	HRB$B_LBUFF(R3),-	; Get the local buffer descriptor addres
		CDRP$L_LBUFH_AD(R5)	;  from the HRB and put it in the CDRP
	UNMAP				; If it does, release them
;		JSB	@PDT$L_UNMAP(R4)

;
; Release a RSPID if one is held.
;

10$:
	TSTL	CDRP$L_RSPID(R5)	; Check the response ID field
	BEQL	20$			; If it is zero none is held
	DEALLOC_RSPID			; Return the response ID to SCS
;		JSB	G^SCS$DEALL_RSPID

;
; Deallocate the MSCP message buffer. (may start up another thread)
;

20$:
	TSTL	HRB$L_MSGBUF(R3)	; Check for allocated message buffer
	BEQL	30$			; Continue, if none is allocated
	MOVL	HRB$L_MSGBUF(R3),- 	; Get the message buffer address
		CDRP$L_MSG_BUF(R5)	;  from the HRB and put it in the CDRP
	CLRL	HRB$L_MSGBUF(R3)	;  and clear out the address in the HRB
	MOVL	HRB$L_HQB(R3),R0	; Get the address of the HQB
	MOVL	HQB$L_CDT(R0),-		;  so we can get the CDT address
		CDRP$L_CDT(R5)		;  and put it in the CDRP for the call
	DEALLOC_MSG_BUF			; Get rid of the message buffer
;		JSB	@PDT$L_DEALLOMSG(R4)

;
; Deallocate any local buffer, and check for any requests that may be
; waiting for buffer space.
;

30$:
	MOVL	HRB$L_UQB(R3),R4	; Restore UQB address
	BEQL	35$			; Command without unit from VC_ERR - branch.
	TSTL	HRB$L_BUFADR(R3)	; Check for any local buffers
	BEQL	35$			; No transfer buffer allocated to req
	MOVL	HRB$L_BUFADR(R3),R0	; Address of block to be deallocated
	MOVL	HRB$L_BUFLEN(R3),R1	; Size of block to be deallocated
	JSB	G^EXE$DEANONPGDSIZ	; Return the local buffer to pool

;
; Deallocate any IRP that was allocated.
;

35$:
	CLRL	HRB$L_BUFADR(R3)	; Clear out the buffer address
	MOVL	HRB$L_IRP_CDRP(R3),R0	; Get the address of the IRP
	BEQL	40$			; None was allocated, continue...
	JSB	G^EXE$DEANONPAGED	; Free the memory
	CLRL	HRB$L_IRP_CDRP(R3)	;  and clear out the buffer pointer

;
; Unhook host request block from the list of requests pending for this host.
;

40$:
	MOVL	HRB$L_HQB(R3),R5	; R5 -> HQB structure
	TSTL	HRB$L_FLINK(R3)		; If this HRB is already unhooked
	BEQL	55$			;  (from add) skip the remque

	ASSUME	HRB$L_FLINK EQ 0

	REMQUE	HRB$L_FLINK(R3),R3	; Remove entry but keep address handy
	CLRL	HRB$L_FLINK(R3)		; Show the entry removed from the queue
	DECW	HQB$W_NUM_QUE(R5)	; Decrement pending for this host

;
; Deallocate the HRB, we don't need it any more.
;

55$:
	MOVL	R3,R0			; Address to deallocate as well
	JSB	G^EXE$DEANONPAGED	; Deallocate the HRB
	MOVL	#SS$_NORMAL,R0		; Return a successful status
	CLRL	R3			; Clear out the old pointer

;
; Check to see if this is the last request pending for this host. If it is,
; we can send out the disconnect and delete the HQB for this host.
;
	BBC	#HQB$V_VC_FAILED,-	; If the circuit has NOT failed,
		HQB$B_STATE(R5),100$	;  just continue on...
	BSBW	TMSCP$DEALLOC_HQB	; Otherwise send out the disconnect

100$:
	RSB				; Return to the caller


	.SBTTL	-	UNBLOCK - Restart Any Blocked Commands
	.SBTTL	-	SEQ_STALL - Synchronize Sequential Commands
	.ENABLE LSB

;+
; Functional Description:
;
; At the SEQ_STALL entry point, a TMSCP sequential command has been received
; and must be synchronized with the other sequential commands for this unit
; before it can be processed.  This routine increments the queued request
; count and sets the sequential command processing flag (UQB$V_SEQ).
;
; The UNBLOCK entry point, releases the next TMSCP sequential command for
; processing.  This routine decrements the queued request count and clears
; the sequential command processing flag (UQB$V_SEQ) when all the commands
; have completed.
;
; Inputs:
;
;	R3  =  HRB address
;	R4  =  UQB address
;
; Outputs:
;
;	R3  =  HRB address
;	R4  =  UQB address
;	SEQ_STALL returns to caller's caller.
;
; Restored thread outputs:
;
;	R0  =  Scratch
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  UQB address, scratch
;	R5  =  Scratch
;-

TMSCP$SEQ_STALL::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	.IF DEFINED DEBUG$CURRENT_SANITY
	BSBW	TMSCP$CHECK_CURRENT	; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY

;
;		Mark this request as a sequential command and prepare it
;		for delayed processing.
;

	BISW	# HRB$M_UNBLOCK, -	; This command must call TMSCP$UNBLOCK
		HRB$W_FLAGS(R3)		; upon command completion.
	MOVW	#HRB$K_ST_SEQ_WAIT,-	; We are now ready for transfer and
		HRB$W_STATE(R3)		;  only stalled be sequentiality.
	POPL	HRB$L_RESPC(R3)		; Save the resume address

;
;		Place this command in the BLOCKED queue and leave it there
;		until all prior sequential commands have completed.
;

	INCW	UQB$W_NUM_QUE(R4)	; Bump number of packets queued to unit
	CMPW	UQB$W_NUM_QUE(R4),-	; If this number is not a new high
		UQB$W_MAX_QUE(R4)	;  for this unit,
	BLEQU	10$			;  then continue processing
	MOVW	UQB$W_NUM_QUE(R4),-	; If it is a new max, save the new high
		UQB$W_MAX_QUE(R4)	;  for the show MSCP command

10$:
	INSQUE	HRB$L_WAIT_FL(R3), -	; Insert this packet on the 
		@UQB$L_BLOCKED_BL(R4)	;  blocked queue for execution later

;
;		Start the first sequential command.
;

	BBSS	# UQB$V_SEQ, -		; Flag sequential command processing and
		UQB$W_FLAGS(R4), 130$	; delay this thread if not first - branch.

;
;		Resume execution of the next command on the BLOCKED queue.
;

TMSCP$UNBLOCK::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	.IF DEFINED DEBUG$CURRENT_SANITY
	BSBW	TMSCP$CHECK_CURRENT	; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY
	TSTL	R4			; Make sure we have a valid address
	BEQL	130$			;  if not, just return

;
; Check to see if there are any requests on the blocked queue. If the queue
; is populated, remove the next request off the queue
;

	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save any registers used
	REMQUE	@UQB$L_BLOCKED_FL(R4),R3; Dequeue first request
	BVS	140$			; If the queue was empty, kill this 
					;  request.
	SUBL	#HRB$L_WAIT_FL,R3	; Point to the start of the HRB
	CLRL	HRB$L_WAIT_FL(R3)	; Clear the queue link.
;
; If the sequential command is waiting to be started, we can do that now and
; update the queue count.
;
	CMPW	#HRB$K_ST_SEQ_WAIT,-	; Is this sequential command ready
		HRB$W_STATE(R3)		;  for transfer?
	BEQL	20$			; ready for transfer, continue
;
; If it is not a sequential command, bail out
; The sequential command cannot be stopped at this point.
;
	CMPW	#<HRB$K_ST_SEQ_WAIT! -	   ; Sequential command in progress?
		  HRB$M_STATE_INVALID>, -
		HRB$W_STATE(R3)		   
	BEQL	25$                       ; Yes, continue processing.
	BRW	150$			  ;  No, bail out
20$:

	DECW	UQB$W_NUM_QUE(R4)	; Decrement queue counter.
	BISW	#HRB$M_STATE_INVALID,-	; Set the state to invalid
		HRB$W_STATE(R3)		;
25$:
	.IF	DEFINED DEBUG$PC_HISTORY
	PUSHAB	115$			; Set the return address.
	PUSHL	HRB$L_RESPC(R3)		; Restart the blocked request.
	BRW	TMSCP$PCHIST_R3		; Log the PC for debugging.

115$:
	BSBW	TMSCP$PCHIST		; Log the PC for debugging.
	.IFF
	JSB	@HRB$L_RESPC(R3)	; Resume where we left off
	.ENDC  ;DEBUG$PC_HISTORY

120$:
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; restore
130$:
	RSB

140$:
	BICW	# UQB$M_SEQ, -		; All sequential processing has completed.
		UQB$W_FLAGS(R4)
	TSTW	UQB$W_NUM_QUE(R4)	; Any requests in queue?
	BEQL	120$			; No, kill this thread - branch.
	BUG_CHECK MSCPSERV,FATAL	; REMQUE indicates no requests in the queue,
;		.WORD	^XFEFF
;		.IIF IDN <FATAL>,<FATAL> , .WORD	BUG$_MSCPSERV!4
					; yet the count is non-zero, this is a bug.

150$:
	BUG_CHECK MSCPSERV,FATAL	; Only HRBs in the SEQ_WAIT state
;		.WORD	^XFEFF
;		.IIF IDN <FATAL>,<FATAL> , .WORD	BUG$_MSCPSERV!4
					; should be in the BLOCKED queue.

	.DISABLE LSB


	.SBTTL	-	CHECK_XFER_STATUS - Translate the class driver message to a MSCP message

;+
; Functional Description:
;
; This routine is called to translate the error from a system service 
; error message to the proper MSCP error code to return to the client 
; processor.
;
; Inputs:
;
;	R0  =  system service error code 
;	R1  =  Scratch
;	R2  =  Scratch
;	R3  =  HRB address
;	R4  =  Scratch
;	R5  =  Scratch
;
; Outputs:
;	R0  =  TMSCP error code.
;	R2  =  MSCP packet address.
;	R3  =  HRB address
;	R4  =  UQB address
;
;	R1, R5  Destroyed.
;
;-

TMSCP$CHECK_XFER_STATUS::
	.IF	DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3R0	; Log the PC for debugging.
	.ENDC  ;DEBUG$PC_HISTORY
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address
	MOVAW	TMSCP$ERR_TBL,R2	; Get the address of the error table

10$:
	MOVZWL	(R2)+,R1		; Take the VMS error code out
	BEQL	20$			; Check for the end of the table
	CMPW	R1,R0			; Compare the VMS error with that passed
	BEQL	20$			; They matched! do the translation
	ADDL	#4,R2			; Step to next entry
	BRB	10$			;  and start it all over again

20$:
	MOVZWL	(R2)+,R0		; Pick up the corresponding MSCP error
	CVTWL	(R2),R1			; Get possible routine
	BEQL	30$			; None
	.IF	DEFINED DEBUG$PC_HISTORY
	PUSHAB	25$			; Set the return address.
	PUSHAB	(R1)[R2]		; Get the routine to invoke.
	BRW	TMSCP$PCHIST		; Log the PC for debugging.

25$:
	BSBW	TMSCP$PCHIST		; Log the PC for debugging.
	.IFF
	JSB	(R1)[R2]		; Invoke the routine
	.ENDC  ;DEBUG$PC_HISTORY
;
; Do some final cleaning up
;
;		R0  =  TMSCP error code
;		R1  =  Scratch
;		R2  =  MSCP packet address
;		R3  =  HRB address
;		R4  =  UQB address
;		R5  =  Scratch
;

30$:
	MOVL	HRB$L_MSGBUF(R3),R2	; MSCP packet address from the HRB
	BEQL	35$
	TSTL	(SP)+			; Return to caller's caller.
	BRW	TMSCP$SEND_END		; Send an end packet with R0 status

35$:
	RSB

	.NOSHOW	MEB

TMSCP$ERR_TBL::
	ACTION	SS$_ABORT,	MSCP$K_ST_ABRTD
	ACTION	SS$_BUGCHECK,	MSCP$K_ST_ICMD
	ACTION	SS$_CTRLERR,	MSCP$K_ST_CNTLR,	TMSCP$SET_SEX
	ACTION	SS$_DATACHECK,	MSCP$K_ST_COMP,		TMSCP$SET_SEX
	ACTION	SS$_DATALATE,	<MSCP$K_SC_DLATE@MSCP$V_ST_SBCOD!MSCP$K_ST_CNTLR>
	ACTION	SS$_DATALOST,	MSCP$K_ST_DATA,		TMSCP$ERR_DLS
	ACTION	SS$_DATAOVERUN,	MSCP$K_ST_RDTRN,	TMSCP$SET_SEX
	ACTION	SS$_DEVOFFLINE,	MSCP$K_ST_OFFLN,	TMSCP$ERR_OFFLINE
	ACTION	SS$_DRVERR,	MSCP$K_ST_DRIVE,	TMSCP$SET_SEX
	ACTION	SS$_DUPUNIT,	<MSCP$K_SC_DUPUN@MSCP$V_ST_SBCOD!MSCP$K_ST_OFFLN>
	ACTION	SS$_ENDOFFILE,	MSCP$K_ST_TAPEM,	TMSCP$ERR_EOF
	ACTION	SS$_ENDOFTAPE,	MSCP$K_ST_SUCC,		TMSCP$ERR_EOT
	ACTION	SS$_ENDOFVOLUME,MSCP$K_ST_LED,		TMSCP$ERR_LEOT
	ACTION	SS$_FORMAT,	MSCP$K_ST_MFMTE
	ACTION	SS$_ILLIOFUNC,	MSCP$K_ST_ICMD,		TMSCP$SET_SEX
	ACTION	SS$_IVBUFLEN,	MSCP$K_ST_HSTBF,	TMSCP$SET_SEX
	ACTION	SS$_MEDOFL,	MSCP$K_ST_OFFLN,	TMSCP$ERR_OFFLINE
	ACTION	SS$_OPINCOMPL,	MSCP$K_ST_DATA
	ACTION	SS$_PARITY,	<MSCP$K_SC_LGE@MSCP$V_ST_SBCOD!MSCP$K_ST_DATA>
	ACTION	SS$_SERIOUSEXCP,MSCP$K_ST_PRESE
	ACTION	SS$_TAPEPOSLOST	MSCP$K_ST_PLOST,	TMSCP$ERR_PLOST
	ACTION	SS$_TIMEOUT,	<MSCP$K_SC_UNKNO@MSCP$V_ST_SBCOD!MSCP$K_ST_OFFLN>,TMSCP$ERR_OFFLINE
	ACTION	SS$_VOLINV,	MSCP$K_ST_OFFLN,	TMSCP$ERR_OFFLINE
	ACTION	SS$_WRITLCK,	MSCP$K_ST_WRTPR,	TMSCP$ERR_WRITLCK
	ACTION	0,MSCP$K_ST_DRIVE	; PATCH SPACE
	ACTION	0,MSCP$K_ST_DRIVE	; End of table - default error

	.SHOW	MEB

;
;		R0  =  TMSCP error code
;		R1  =  Scratch
;		R2  =  Scratch
;		R3  =  HRB address
;		R4  =  UQB address
;		R5  =  Scratch
;

TMSCP$ERR_OFFLINE::
	MOVL	UQB$L_UCB(R4),R5	;  and follow that to the UCB address
	TSTL	UQB$L_ONLINE_HQB(R4)
	BEQL	60$			;
	CLRL	UQB$L_ONLINE_HQB(R4)

40$:
	DECB	UCB$B_ONLCNT(R5)	;  and note the change in online count

;
; All the bits for this unit have been cleared. Now the status of this 
; device is changed in the UQB to "offline". This status may be changed
; to "available" by the GUS command, and then eventually to "online" 
; after the successful completion of an online command.
;

60$:
	MOVW	#UQB$K_ST_OFFLINE,-	; Now set the state of this device to
		UQB$W_STATE(R4)		;   reflect the sum of individual states
	RSB				;  and return with the device offline


;
;		R0  =  TMSCP error code
;		R1  =  Scratch
;		R2  =  Scratch
;		R3  =  HRB address
;		R4  =  UQB address
;		R5  =  Scratch
;

TMSCP$ERR_WRITLCK::
	BBSS	#MSCP$V_UF_WRTPH,-	; Set the write protect bit in
		UQB$W_UNIT_FLAGS(R4),10$;  the unit flag field
	BISW	#UQB$M_ST_WRTPH,-
		UQB$W_ST_FLAGS(R4)

10$:
	RSB

;
;	End of tape encountered
;
;		R0  =  TMSCP error code
;		R1  =  Scratch
;		R2  =  Scratch
;		R3  =  HRB address
;		R4  =  UQB address
;		R5  =  Scratch
;

TMSCP$ERR_EOT::
	BBSS	#UQB$V_ST_EOT,-		; Reflect it in the UQB
		UQB$W_ST_FLAGS(R4),5$	;	
	BSBW	TMSCP$SET_SEX
	MOVL	HRB$L_MSGBUF(R3), R2	; No end packet means it is a
	BEQL	5$			;  caching type command
	BISW	#MSCP$M_EF_EOT,-	; Set end of tape flag in MSCP
		MSCP$B_FLAGS(R2)	;   packet

5$:
	BRB	TMSCP$RECOVERABLE_ERROR

;
;	Position of tape lost
;
;		R0  =  TMSCP error code
;		R1  =  Scratch
;		R2  =  Scratch
;		R3  =  HRB address
;		R4  =  UQB address
;		R5  =  Scratch
;

TMSCP$ERR_PLOST::
	MOVL	HRB$L_MSGBUF(R3), R2	; No end packet means it is a
	BEQL	5$			;  caching type command
	BISB	#MSCP$M_EF_PLS,-	; Set end of tape flag in MSCP
		MSCP$B_FLAGS(R2)	;   packet

5$:
	BBSS	#UQB$V_ST_PLS,-		; Reflect it in the UQB
		UQB$W_ST_FLAGS(R4),10$	;
	RSB

10$:
	BSBW	TMSCP$SET_SEX
	BRB	TMSCP$RECOVERABLE_ERROR

;
;	Logical end of tape encountered
;
;		R0  =  TMSCP error code
;		R1  =  Scratch
;		R2  =  Scratch
;		R3  =  HRB address
;		R4  =  UQB address
;		R5  =  Scratch
;

TMSCP$ERR_LEOT::
	BISW	#UQB$M_ST_LEOT,-	; Reflect it in the UQB
		UQB$W_ST_FLAGS(R4)
	BSBW	TMSCP$SET_SEX
	BRB	TMSCP$RECOVERABLE_ERROR

;
;	Cached data lost
;
;		R0  =  TMSCP error code
;		R1  =  Scratch
;		R2  =  Scratch
;		R3  =  HRB address
;		R4  =  UQB address
;		R5  =  Scratch
;
		
TMSCP$ERR_DLS::
	MOVL	HRB$L_MSGBUF(R3), R2	; No end packet means it is a
	BEQL	5$			;  caching type command
	BISB	#MSCP$M_EF_DLS,-	; Set end of tape flag in MSCP
		MSCP$B_FLAGS(R2)	;   packet

5$:
	BISW	#UQB$M_ST_DLS,-		; Reflect it in the UQB
		UQB$W_ST_FLAGS(R4)	
	BRB	TMSCP$SET_SEX

;
;	A Serious Exception condition was encountered...set the serious
;	exception state flag in the end packet.
;

;
; End of file can not only mean we encountered a tape mark in the
; forward direction, but that we hit on in reverse, and possibly
; at BOT.
;
;		R0  =  TMSCP error code
;		R1  =  Scratch
;		R2  =  Scratch
;		R3  =  HRB address
;		R4  =  UQB address
;		R5  =  Scratch
;

TMSCP$ERR_EOF::
	BSBW	TMSCP$SET_SEX
	MOVL	UQB$L_UCB(R4),R5	; Restore UCB
	BBC	#MT$V_BOT,-		; Are we at BOT?
		UCB$L_DEVDEPEND(R5),10$	;
	MOVZWL	#MSCP$K_ST_BOT,R0	; Then return MSCP status as such
	BBCC	#UQB$V_ST_EOT,-		;
		UQB$W_ST_FLAGS(R4),10$	;

10$:
	RSB
	
;
;		R0  =  TMSCP error code
;		R1  =  Scratch
;		R2  =  Scratch
;		R3  =  HRB address
;		R4  =  UQB address
;		R5  =  Scratch
;

TMSCP$SET_SEX::
	BISW	#UQB$M_ST_SEREX,-	; Reflect it in the UQB
		UQB$W_ST_FLAGS(R4)
;	BBC	#UQB$V_FLUSH,-		; If the cache is being flushed and
;		UQB$W_FLAGS(R4),10$	;  we have gotten a serious error,
;	BISW	#UQB$M_ST_DLS,-		;  set the cached data lost bit
;		UQB$W_ST_FLAGS(R4)	;  in the UQB

10$:
	RSB

TMSCP$ANCILLARY_TABLE::
	.BYTE	MSCP$K_OP_ERASE
	.BYTE	MSCP$K_OP_ERGAP
	.BYTE	MSCP$K_OP_READ
	.BYTE	MSCP$K_OP_REPOS
	.BYTE	MSCP$K_OP_WRITE
	.BYTE	MSCP$K_OP_WRITM
TMSCP$ANCILLARY_TABLE_LEN	=	. - TMSCP$ANCILLARY_TABLE

;
;		R0  =  TMSCP error code
;		R1  =  Scratch
;		R2  =  Scratch
;		R3  =  HRB address
;		R4  =  UQB address
;		R5  =  Scratch
;

TMSCP$RECOVERABLE_ERROR::
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore MSCP packet address
	BEQL	10$			; Immediate command - branch.
	MOVZBL	MSCP$B_OPCODE(R2),R1	; Restore opcode
	PUSHR	#^M<R0,R1>		; Save registers destroyed by LOCC
	LOCC	R1,#TMSCP$ANCILLARY_TABLE_LEN,-; Is this a "move" command?
		TMSCP$ANCILLARY_TABLE
	POPR	#^M<R0,R1>
	BEQL	20$			; EQL <= not a "move" command

10$:
	POPL	R1			; Remove caller's address and
					;   return to caller of CHECK_XFER_STATUS.

20$:
	RSB


	.SBTTL	-	FIND UQB - Find the UQB corresponding to this request

;+
; Functional Description:
;
; This routine is called when a MSCP command packet is being processed that
; references a specific unit. This routine searches the list of units known to
; the server and returns the UQB with the unit number corresponding to the one
; specified in the MSCP packet. If the proper UQB cannot be found we die trying.
; An illegal command end packet is returned to the host and the request is
; finished off. Control is not passed back to the caller unless we are 
; successful.
; 
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address 
;
; Outputs:
;
;	R0  =  returned status
;		0 => UQB for the unit was not found
; 		SS$_NORMAL => the UQB was located
;	R2  =  MSCP packet address
;	R3  =  HRB address  (the UQB address is placed in the proper offset)
;
; All registers used in this subroutine are returned to their original state
; on return to the caller.
;-

	.ENABLE	LOCAL_BLOCK

TMSCP$FIND_UQB::
	PUSHR	#^M<R1,R4,R5>		; Save any registers we plan on using
	MOVL	HRB$L_HQB(R3),R5	; Get the address of the HQB 
	BBC	#HQB$V_V5CL,-		; If this is a request from the old
		HQB$W_FLAGS(R5),100$	;  class driver, use the old code

;
; The request just received was sent from a client system running a version
; of the tape class driver that is V5.0 or later. This means that the unit 
; number is of the new format. Bit 14 of the unit number, which indicates the
; unit number is a Server Local Unit Number (SLUN) is cleared, and the unit 
; number becomes the index into the unit table where the address of the UQB
; for this unit can be found.
;                                                                   
	MOVL	HQB$L_TSRV(R5),R5	; Get the server structure address
	CMPW	#MSCP$K_SLUN_RSVP,-	; Is this the magic SLUN unit number
		MSCP$W_UNIT(R2)		;  that requests a search is needed?
 	BEQL	20$			; Branch if yes and search for UQB

10$:
	MOVZWL	MSCP$W_UNIT(R2),R0	; Put the unit number in a register
	BBCC	#MSCP$V_SLUN,R0,15$	; Check SLUN bit and clear index
	CMPW	#MAX_UNITS,R0		; Past end of table?
	BLEQU	13$			; Unit out of range - branch.
	MOVL	TSRV$L_UNITS(R5)[R0],R4	; Find the UQB address in the table
	BNEQ	120$			; Continue if there is one

13$:
	BRW	TMSCP$BAD_UNIT		; Else indicate no such unit

15$:
	BUG_CHECK MSCPSERV, FATAL	; No SLUN bit set from V5+ class driver
;		.WORD	^XFEFF
;		.IIF IDN <FATAL>,<FATAL> , .WORD	BUG$_MSCPSERV!4

;
; The magic unit number means that this is the first get unit status command 
; received from a v5.0 class driver after a new connection has formed. The
; class driver is asking for the server local unit number that corresponds 
; to the unit with the unit identifier that is passed in the two longwords 
; beyond the end of the "normal" get unit status command. The server searches
; the list of known units for a match of the unit identifier, and returns the
; server local unit number as the unit number in the MSCP end message.
;                                                                

20$:
 	MOVAL	TSRV$L_UNITS(R5),R1	;  determine the start of the unit table
	MOVL	#MAX_UNITS,R0		; Number of elements in the unit table

30$:
	MOVL	(R1)+,R4		; Get the address of the next table entry
	BEQL	35$			;  skip it if zero
	PUSHR	#^M<R0,R1,R2,R3>	; Save the registers
	CMPC3	#8,MSCP$Q_UNIT_ID(R2),-	;  and compare the unit-id passed 
		UQB$Q_UNIT_ID(R4)	;  to the id in the UQB
	POPR	#^M<R0,R1,R2,R3>	;  and restore them back
	BEQL	40$			; If it is a match exit the loop

35$:
	SOBGTR	R0,30$			; Otherwise search to the end of table
	BRB	TMSCP$BAD_UNIT		; If still not found return an error

40$:
	MOVW	UQB$W_SLUN(R4),-	; Return the local unit number in the
		MSCP$W_UNIT(R2)		;  end message to the client
	BRB	10$			; Return to the main path

;
; This request was received from a pre-V5.0 class driver. Since ONLY V5 
; connections are established, this is a BUG!
;

100$:
	BUG_CHECK MSCPSERV, FATAL	; Wrong class driver 
;		.WORD	^XFEFF
;		.IIF IDN <FATAL>,<FATAL> , .WORD	BUG$_MSCPSERV!4

;
; The proper UQB for this request was found. Place its address in the Host
; Request Block and return to the caller.
;

120$:
	MOVL	R4,HRB$L_UQB(R3)	; Otherwise save the UQB address
	INCW	UQB$W_CURRENT(R4)	; One more active request on this unit
	.IF DEFINED DEBUG$CURRENT_SANITY
	BSBW	TMSCP$CHECK_CURRENT	; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY
	MOVL	#SS$_NORMAL,R0		; Set a normal return status

130$:
	POPR	#^M<R1,R4,R5>		; Restore the registers we used
	RSB				;  and return to the caller

TMSCP$BAD_UNIT::
	CLRL	R0			; Return an unsuccessful status
	BRB	130$			; Restore registers and return.

	.DISABLE	LOCAL_BLOCK


	.SBTTL	-	SEND END - Respond To The Request

;+
; Functional Description:
;
; This routine is called (with several entry points) to respond to the 
; MSCP message packet received. It reuses the MSCP packet received to
; send a MSCP end message response.
;
; Inputs:
;
;	R0  =  status to be returned
;	R2  =  MSCP packet address
;	R3  =  HRB address 
;
; Outputs:
;
;	R3  =  HRB address
;	R4  =  altered
;	R5  =  CDRP address
;-

;
; Entry here, means that the message buffer is to be returned with status.
; 

TMSCP$SEND_END::
	.IF DEFINED DEBUG$PC_HISTORY
	PUSHL	R0			; Save the MSCP command status.
	MOVL	MSCP$L_CMD_REF(R2), R0	; Save the command reference number.
	BSBW	TMSCP$PCHIST_R3R0	; Save this PC if we are keeping track
	POPL	R0			; Get the MSCP command status.
	BSBW	TMSCP$R0HIST		; Save this value.
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVW	R0,MSCP$W_STATUS(R2)	; Set the status
	MOVL	HRB$L_UQB(R3),R4	; Restore UQB
	BEQL	40$			; No UQB for TMSCP$VC_ERR routine
	BBC	#UQB$V_ST_SEREX,-	; Serious exception condition exists?
		UQB$W_ST_FLAGS(R4),10$	;
	BISB	#MSCP$M_EF_SEREX,-	; Set serious exception state flag
		MSCP$B_FLAGS(R2)	;  in MSCP end packet

10$:
	BBC	#UQB$V_ST_DLS,-		; If the cached data lost flag
		UQB$W_ST_FLAGS(R4),20$	;  is set in the UQB, we'd better
	MOVW	#MSCP$K_ST_DATA,-	;  set cached data lost in
		MSCP$W_STATUS(R2)	;  the end packet		

20$:
	BBCC	#UQB$V_ST_LEOT,-	; If the logical end of tape flag
		UQB$W_ST_FLAGS(R4),30$	;  is set in the UQB, we'd better
	MOVW	#MSCP$K_ST_LED,-	;  set logical end of tape in
		MSCP$W_STATUS(R2)	;  the end packet		

30$:
	BBC	#UQB$V_ST_EOT,-		; If end of tape flag
		UQB$W_ST_FLAGS(R4),40$	;  flag is set,
	BISB	#MSCP$M_EF_EOT,-	; then set it in the next
		MSCP$B_FLAGS(R2)	;  available end packet

40$:
	MOVZBL	MSCP$B_OPCODE(R2),R1	; Pick up opcode
	BISB2	#MSCP$K_OP_END,-	; Reset the op-code to
		MSCP$B_OPCODE(R2)	;  make an end packet
	MOVZBL	TMSCP$END_PKT_LEN[R1],R1; Get the message length from the table

;
;		R0  =  Scratch
;		R1  =  TMSCP message length
;		R2  =  TMSCP message address
;		R3  =  HRB address
;		R4  =  Scratch
;		R5  =  Scratch
;

TMSCP$SEND_PKT::
	
; 
; Prepare the CDRP in the HRB, send off the end message to the port driver.
;

	BBC	# TMSCP$V_DBG_SEND_PKT, -	; Not debugging - branch.
		TMSCP$L_DEBUG_FLAGS, 0$
	JSB	G^ INI$BRK

0$:
	MOVL	HRB$L_HQB(R3),R4	; Restore the HQB address
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Address of the IRP for end message
	MOVAL	IRP$L_FQFL(R5),R5	;  move down to the start of the CDRP

;
; Initialize the fields of the CDRP.
;

	MOVL	HQB$L_CDT(R4), -	; CDT describing the link to the system for
		CDRP$L_CDT(R5)		; which this end message is destined.
	MOVL	R2,CDRP$L_MSG_BUF(R5)	; Put the message buffer address into
	CLRL	HRB$L_MSGBUF(R3)	;  a CDRP for the SCS call and zero HRB
	MOVL	HRB$L_PDT(R3),R4	; Set the PDT address for the SCS calls
	MOVL	R1,HRB$L_RESPC(R3)	; Hide the length away
	MOVW	#HRB$K_ST_MSG_WAIT,-	; Set the state for this request
		HRB$W_STATE(R3)		;  before calling SCS service
	RECYCL_MSG_BUF			; Recycle the message
;		JSB	@PDT$L_RCLMSGBUF(R4)
	BLBC	R0, 20$			; If LBC connection broke, skip host
	MOVL	R2,HRB$L_MSGBUF(R3)	; Save the address of the buffer again
	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis
	MOVL	HRB$L_RESPC(R3),R1	; Restore the length of the end message
	.IF DEFINED DEBUG$LOG

	ASSUME TSRV$V_LOG_ENABLD  EQ  0

	MOVL	G^SCS$GL_TMSCP,R0	; Get the TSRV address.
	BLBC	TSRV$W_STATE(R0),10$	; Branch if logging is disabled.
	BSBW	TMSCP$LOG_END_PKT	; Otherwise, log the end packet.

10$:
	.ENDC	; DEBUG$LOG

	CLRL	HRB$L_MSGBUF(R3)	; Message buffer belongs to SCS again
	MOVW	#HRB$K_ST_SNDMS_WAIT,-	; Set the state for this request
		HRB$W_STATE(R3)		;  before calling SCS service
	SEND_CNT_MSG_BUF		; Send the message (call returns immed)
;		JSB	@PDT$L_SNDCNTMSG(R4)

20$:
	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis
	BBS	#HRB$V_ABORTWS,-	;
		HRB$W_FLAGS(R3),-
		TMSCP$CLEANUP_AND_START_NEXT
	BBC	#HRB$V_WBC_IMMED,-	; If immediate completion specified for
		HRB$W_FLAGS(R3),-	;  a write-back caching command, don't
		TMSCP$CLEANUP_AND_START_NEXT	;  clean up yet!
;
; Deallocate the MSCP message buffer. (may start up another thread)
;

30$:
	TSTL	HRB$L_MSGBUF(R3)	; Check for allocated message buffer
	BEQL	40$			; Continue, if none is allocated
	MOVL	HRB$L_MSGBUF(R3),- 	; Get the message buffer address
		CDRP$L_MSG_BUF(R5)	;  from the HRB and put it in the CDRP
	CLRL	HRB$L_MSGBUF(R3)	;  and clear out the address in the HRB
	MOVL	HRB$L_HQB(R3),R0	; Get the address of the HQB
	MOVL	HQB$L_CDT(R0),-		;  so we can get the CDT address
		CDRP$L_CDT(R5)		;  and put it in the CDRP for the call
	DEALLOC_MSG_BUF			; Get rid of the message buffer
;		JSB	@PDT$L_DEALLOMSG(R4)

40$:
	RSB				; Finish sending request to the drive

TMSCP$CLEANUP_AND_START_NEXT::
	BRW	TMSCP$CLEANUP_HRB	; Deallocate all HRB held resources

;
; Entry here means that a request was made to the server requiring a valid
; unit, and no UQB was found for the unit specified.
;

TMSCP$ERROR_NO_UNIT::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3R0	; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	#MSCP$K_ST_OFFLN,R0	; Return an offline status with the
	BRW	TMSCP$SEND_END		; device unknown subcode (0)

;
; When an error is detected with a packet, the address of the field that
; contains the error is shifted into the high byte of R0 and control is
; passed here.
;

TMSCP$PACKET_ERROR::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3R0	; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	BISL3	#<MSCP$K_ST_ICMD@16-	; The command sent was invalid
		!MSCP$K_OP_END>,-	; Identify this as an end message
		R0,MSCP$B_OPCODE(R2)	; Put all this info back in the packet
	MOVL	#MSCP$K_MXCMDLEN,R1	; Use the minimum size possible
	BRW	TMSCP$SEND_PKT		;  and send out the end message


	.SBTTL	-	UNHOOK CDRP - Action routine to dequeue CDRP's

;+
; Functional Description:
;
; This routine is called as an action routine for the SCS routines that
; scan the various wait queues for CDRPs that have been stalled. This        
; routine is invoked when a virtual circuit has broken and all the requests
; pending for the failed host must be cleaned up.
;
; Inputs:
;
;	R1  =  HRB address (passed only from TMSCP$ABORT)
;	R3  =  CDT address matched
;	R4  =  PDT address 
;	R5  =  Address of the CDRP found with the scan
;
; Outputs:
;
;	R1  =  HRB address of the CDRP found
;		(the invalid bit in the state field is set)
;-

;
; Dequeue the CDRP, if it is the one that goes with the HRB we are looking
; for, and we can prove that it is in a queue. 
;

TMSCP$ABORT_UNHOOK_CDRP::
	CMPL	CDRP$L_HRB(R5),R1	; See if this is the right CDRP
	BEQL	TMSCP$UNHOOK_CDRP	;  if it is, go ahead and unhook
	RSB				;  otherwise, just return

TMSCP$UNHOOK_CDRP::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST_R3		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	TSTL	CDRP$L_FQBL(R5)		; Check for the presence of a pointer
	BEQL	10$			; If the field is 0 don't dequeue

	ASSUME	CDRP$L_FQFL EQ 0

	CMPL	@CDRP$L_FQBL(R5),R5	; If the field is nonzero,
	BNEQ	10$			;  make sure it is in a queue

	ASSUME	CDRP$L_FQFL EQ 0

	REMQUE	CDRP$L_FQFL(R5), R5	; Remove the CDRP from its queue

;
; Once the CDRP has been removed from the queue, set the invalid
; bit in the state field, to indicate that the displayed state is 
; an old one. On a later pass through all the pending requests 
; for this host, requests in this state will be eligible for cleanup
; with no further processing.
;

10$:
	PUSHL	R2			; Save any registers used in cleanup
	MOVL	CDRP$L_HRB(R5),R2	; Get the HRB address
	.IF DEFINED DEBUG$PC_HISTORY
	PUSHL	R0
	MOVL	R2, R0			; Get the HRB address.
	BSBW	TMSCP$PCHIST_R0		; Save this PC if we are keeping track
	POPL	R0
	.ENDC	   ;DEBUG$PC_HISTORY
	BISW	#HRB$M_DEQUEUED,-	; Mark this request as unhooked,
		HRB$W_FLAGS(R2)		;  its resources can be deallocated
	POPL	R2			; Restore the registers used here

	RSB				; Return to SCS


	.SBTTL	-	DEALLOCATE HQB - Remove an HQB if there are no requests 

;+
; Functional Description:
;
; This routine is called as part of the virtual circuit error cleanup routine.
; When TMSCP$VC_ERR has done its best to clean up all the outstanding requests for
; this host, control is passed here to see if they have all been dealt with.
; If there are none outstanding, the HQB is returned to pool. If there are 
; still some requests that are held by the tape driver, this routine is
; revisited when the request is returned to the server, and as each outstanding
; request is aborted, this routine is called. Only when the last request
; pending for this host is deleted will the HQB be deallocated, and the host
; number used returned to the TSRV so it can be used again.
;
; Inputs:
;
;	R5  =  HQB address
;
; Outputs:
;
;	R0  =  0 (if there are still requests outstanding)
;	       SS$_NORMAL (if the HQB was deleted)
;	R5  =  Cleared
;	
;	All other registers preserved.
;-

TMSCP$DEALLOC_HQB::
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$PCHIST		; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

	PUSHR	#^M<R1,R3,R4>	; Save any registers we mess with

;
; First, check to make sure that there are no requests still outstanding
; for this host.
;

	CLRL	R0			; Assume there are more requests
	MOVAL	HQB$L_HRB_FL(R5),R3	; Get the address of the request queue.
	CMPL	(R3),R3			; See if this is a null queue
	BNEQ	20$			; If it's not, we aren't ready for this

; 
; Now we know that this virtual circuit is no good anymore. It must have
; failed in the first place to be marked bad in the HQB, and now all the 
; pending requests have been cleaned up, remove this host queue block from 
; the list, and return the host number to be used by another incoming 
; connect request.
; 

	MOVL	HQB$L_TSRV(R5),R4	; Get the server structure address

10$:
	DECW	TSRV$W_NUM_HOST(R4)	; Adjust the count of hosts being served

	ASSUME	HQB$L_FLINK EQ 0

	REMQUE	HQB$L_FLINK(R5), R0	; Dequeue the HQB from the list
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	TMSCP$R0HIST		; Save this value.
	.ENDC	   ;DEBUG$PC_HISTORY
	JSB	G^EXE$DEANONPAGED	;  and return the memory to pool
	CLRL	R5			; Clear out the old pointer
	MOVL	#SS$_NORMAL,R0		; Set success

20$:
	POPR	#^M<R1,R3,R4>		; Restore these registers
	RSB				;  and return


	.SBTTL	Macros for Density Tables

;++
;  Macros used to build the MSCP -- VMS density tables
;
; Functional Description:
;
;	The following macro (and its two sub-macros) create a table used in 
;	converting VMS notions of density and speed to equivalent MSCP values 
;	and vice. versa.  Each section of the table corresponds to an MSCP
;	format group.  The entries in the two lists within each section
;	correspond to the formats within the group.  Each section consists
;	of a byte indicating the number of entries in each list followed by
;	a list of byte value VMS density codes for given (subscripted) MSCP
;	density bit positions, and a list of word numeric density values for
;	given (subscripted) MSCP density bit positions.  Multiple invocations
;	of this macro add additional sections to the table.  
;	
;	Label TU_VMSDENS points to the byte preceeding the list of VMS density
;	codes in the first table section.  Label TU_ABSDENS points to the start
;	of the list of numeric density values in the first table section.  The
;	byte preceeding each list of VMS density codes is used to locate the
;	next table section.
;
; Note:	Invocations of this macro must be ordered by increasing MSCP format
;	group values.  The lists within each invocation must be ordered by 
;	increasing MSCP density bit values.
;
;	For more information, see TMSCP ECO TMSCP16-43.
;
;--

TABLE_SECTIONS	= 0

	.MACRO	DENSITY_TABLE, DF_MT, DF_DENS, DF_MSCP, LIST
TABLE_SECTIONS	= TABLE_SECTIONS + 1
	.IIF NDF, TMSCP$TU_VMSDENS, TMSCP$TU_VMSDENS::	; Start of table.
	. = .+1					; Reserve byte for table size.
TABLE$$$START = .
	.IRP	ITEM, <LIST>			; Build VMS density
	SET_VMSDENS ITEM			;  values table.
	.ENDR
	.BYTE	MT$K_'DF_MT'			; Set default VMS density.
TABLE$$$SIZE = . - TABLE$$$START		; Calculate section table size.
TABLE$$$END = .					; Save current pointer.
	. = TABLE$$$START - 1			; Set pointer to table start.
	.BYTE TABLE$$$SIZE			; Fill in table size.
	. = TABLE$$$END				; Restore pointer

	.IIF NDF, TMSCP$TU_ABSDENS, TMSCP$TU_ABSDENS::
	.IRP	ITEM, <LIST>			; Build table of absolute
	SET_ABSDENS ITEM			;  density values.
	.ENDR
	.LONG	DF_DENS

	.ENDM	DENSITY_TABLE

	.MACRO	SET_VMSDENS, MT, DENS, MSCP
	.IF NB MT
	ASSUME	<. - TABLE$$$START> EQ MSCP$V_'MSCP'
	.BYTE	MT$K_'MT'
	.ENDC
	.IIF B, MT,	.BYTE 0
	.ENDM

	.MACRO	SET_ABSDENS, MT, DENS, MSCP
	.IF NB MT
	.LONG	DENS
	.ENDC
	.IIF B, MT,	.LONG 0
	.ENDM


	.SBTTL	Density and Speed Conversion Tables

;++
;
; Density / Speed Conversion Tables
;
; NOTE: Multiple invocations of DENSITY_TABLE must be in increasing order of
;	MSCP$K_TC_xxxx.  Entries within each DENSITY_TABLE must be ordered by
;	increasing values of MSCP$V_TF_xxx, and no values of MSCP$V_TF_xxx can
;	be omitted.  Failure to observe these rules will result in errors.
;--

	ASSUME	MSCP$K_TC_OLD@-8 EQ 0

	DENSITY_TABLE -				; Old devices
		PE_1600,  1600, TF_PE, -	; Defaults
		< -
		< NRZI_800,  800, TF_800 >, -
		< PE_1600,  1600, TF_PE >, -
		< GCR_6250, 6250, TF_GCR >, -
		< BLK_833,   833, TF_BLK > -
		>
;	.IIF NDF, TMSCP$TU_VMSDENS, TMSCP$TU_VMSDENS::	; Start of table.
;	. = .+1					; Reserve byte for table size.
;	.BYTE	MT$K_NRZI_800
;	.BYTE	MT$K_PE_1600
;	.BYTE	MT$K_GCR_6250
;	.BYTE	MT$K_BLK_833
;	.BYTE	MT$K_PE_1600			; Set default VMS density.
;	. = TABLE$$$START - 1			; Set pointer to table start.
;	.BYTE TABLE$$$SIZE			; Fill in table size.
;	. = TABLE$$$END				; Restore pointer
;	.IIF NDF, TMSCP$TU_ABSDENS, TMSCP$TU_ABSDENS::
;	.LONG	800
;	.LONG	1600
;	.LONG	6250
;	.LONG	833
;	.LONG	1600

	ASSUME	MSCP$K_TC_9TR@-8 EQ 1

	DENSITY_TABLE -				; New 9-track devices
		PE_1600,  1600, TF_PE, -	; Defaults
		< -
		< NRZI_800,  800, TF_800 >, -
		< PE_1600,  1600, TF_PE  >, -
		< GCR_6250, 6250, TF_GCR >, -
		>
;	. = .+1					; Reserve byte for table size.
;	.BYTE	MT$K_NRZI_800
;	.BYTE	MT$K_PE_1600
;	.BYTE	MT$K_GCR_6250
;	.BYTE	MT$K_PE_1600			; Set default VMS density.
;	. = TABLE$$$START - 1			; Set pointer to table start.
;	.BYTE TABLE$$$SIZE			; Fill in table size.
;	. = TABLE$$$END				; Restore pointer
;	.LONG	800
;	.LONG	1600
;	.LONG	6250
;	.LONG	1600

	ASSUME	MSCP$K_TC_CTP@-8 EQ 2

	DENSITY_TABLE -				; Block mode devices
		BLK_833,   833, TF_NOR, -	; Defaults
		< -
		< BLK_833,   833, TF_NOR >, -	; TK50 = 6666/8
		< BLK_1250, 1250, TF_BHD > -	; TK70 = 10000/8
		>
;	. = .+1					; Reserve byte for table size.
;	.BYTE	MT$K_BLK_833
;	.BYTE	MT$K_BLK_1250
;	.BYTE	MT$K_BLK_833			; Set default VMS density.
;	. = TABLE$$$START - 1			; Set pointer to table start.
;	.BYTE TABLE$$$SIZE			; Fill in table size.
;	. = TABLE$$$END				; Restore pointer
;	.LONG	833
;	.LONG	1250
;	.LONG	833

	ASSUME	MSCP$K_TC_HPC@-8 EQ 3

	DENSITY_TABLE -				; High performance cartridge
		HPC_40K,   39872, TF_NOR, -	; Defaults
		< -
		< HPC_40K, 39872, TF_NOR> -	; HPC_40K = 39872 bytes/inch
		< XPC_80K, 80000, TF_ENH> -	; XPC_80K
		< HPC_COMP,70000, TF_NDC> -	; HPC_COMP = 2 to 30 times HPC_40K
		< XPC_COMP,160000, TF_EDC> -	; XPC_COMP
		>
;	. = .+1					; Reserve byte for table size.
;	.BYTE	MT$K_HPC_40K
;	.BYTE	MT$K_XPC_80K
;	.BYTE	MT$K_HPC_COMP
;	.BYTE	MT$K_XPC_COMP
;	.BYTE	MT$K_HPC_40K			; Set default VMS density.
;	. = TABLE$$$START - 1			; Set pointer to table start.
;	.BYTE TABLE$$$SIZE			; Fill in table size.
;	. = TABLE$$$END				; Restore pointer
;	.LONG	39872
;	.LONG	80000
;	.LONG	70000
;	.LONG	160000
;	.LONG	39872

	ASSUME	MSCP$K_TC_WOD@-8 EQ 4

	DENSITY_TABLE -				; Write-once optical disks
		WOD_6250,   6250, TF_NOR, -	; Defaults
		< -
		< WOD_6250,   6250, TF_NOR > -	; RV20
		>
;	. = .+1					; Reserve byte for table size.
;	.BYTE	MT$K_WOD_6250
;	.BYTE	MT$K_WOD_6250			; Set default VMS density.
;	. = TABLE$$$START - 1			; Set pointer to table start.
;	.BYTE TABLE$$$SIZE			; Fill in table size.
;	. = TABLE$$$END				; Restore pointer
;	.LONG	6250
;	.LONG	6250

TMSCP$TU_ABSPEED::				; Speed in IPS.
	.WORD	0				; Default speed.
	.WORD	MT$K_SPEED_25			; 25  IPS
	.WORD	MT$K_SPEED_75			; 75  IPS
	.WORD	125				; 125 IPS
	.WORD	255				; Returned if not in table.
	.WORD	-1				; End of table.


	.SBTTL	Density Conversion Routines
	.SBTTL	.   TMSCP$VMSTOMSCP_DENS

;++
;
; TMSCP$VMSTOMSCP_DENS
;
; Convert a VMS density code to a MSCP density code.  For single density 
; devices, use the only density available.
;
; Inputs:
;
;	R0 	VMS density code 
;	R1	bits  0 -  7 = supported densities
;		bits  8 - 15 = MSCP Tape format flag
;		bits 16 - 31 = MBZ
;
; Outputs:
;
;	R0	Status
;		   LBS ==> VMS density lookup succeeded
;		   LBC ==> VMS density lookup failed or single density device, 
;			   default density or single device density used
;	R1	MSCP format / density code
;	All other registers are preserved.
;--

TMSCP$VMSTOMSCP_DENS::
	PUSHR	#^M<R2,R3,R4>
	BICW3	#^X0FF, R1, R4		; Save format.
	BSBW	TMSCP$FIND_SECTION	; Locate table section for this format.
	DECL	R2			; R2 = number of densities to search.
					; (Minus one to skip default density).
7$:
	LOCC	R0, R2, (R3)		; Find VMS density in this table
					;  section.
	BNEQ	10$			; Branch if found.
	MOVB	(R3)[R2], R0		; Get default VMS density.
	LOCC	R0, R2, (R3)		; Find default density in the table.
	CLRL	R0			; Set "default" density flag.
	BRB	20$			; Branch to convert to MSCP density.

10$:
	MOVL	#SS$_NORMAL, R0		; Indicate success.

20$:
	SUBL	R3, R1			; Get offset into table.
	ASHL	R1, #1, R1		; Develop MSCP density code.
	BISW	R4, R1			; Combine with format.
	POPR	#^M<R2,R3,R4>
	RSB


	.SBTTL	.   TMSCP$MSCPTOVMS_DENS

;++
;
; TMSCP$MSCPTOVMS_DENS
;
; Convert a MSCP density code to a VMS density code.
;
; Inputs:
;
;	R1	bits  0 -  7 = MSCP density (Tape format bitflag)
;		bits  8 - 15 = MSCP Tape format flag for this device
;		bits 16 - 31 = MBZ
;
; Outputs:
;
;	R0	VMS density code
;	R1	destroyed
;	All other registers are preserved.
;--

TMSCP$MSCPTOVMS_DENS::
	PUSHR	#^M<R2,R3>
	BSBW	TMSCP$FIND_SECTION	; Locate table section for this format.
	DECL	R2			; R2 = number of densities to search.
					; (Minus one to skip default density).
	BICL	#^CMSCP$M_TF_MASK, R1	; Isolate format bitflag.
	FFS	#0, R2, R1, R1		; Get byte index into VMS
	MOVZBL	(R3)[R1], R0		; density table, lookup value,
	POPR	#^M<R2,R3>		; and return.
	RSB


	.SBTTL	.   TMSCP$FIND_SECTION

;++
; TMSCP$FIND_SECTION
;
; This routine uses the format flag value in bits 8 through 15 of R1 to
; locate the correct density table section for a given format.
;
; Inputs:
;
;	R1	bits  0 -  7 = Don't care
;		bits  8 - 15 = MSCP Tape format flag
;		bits 16 - 31 = MBZ
;
; Outputs:
;
;	R2	Number of density entries in table section	
;	R3	Address of table section
;	All other registers are preserved.
;--

TMSCP$FIND_SECTION::
	PUSHL	R1
	MOVAB	TMSCP$TU_VMSDENS, R3	; Get address of VMS densities.
	MOVZBL	(R3)+, R2		; Get number of densities for this format.
	ASHL	#-8, R1, R1		; Get tape format flag in R1.
	BEQL	20$			; Branch if = MSCP$K_TC_OLD.
	CMPL	R1, # TABLE_SECTIONS	; Is the requested section in the table?
	BGEQU	20$			; No, use VMS densities - branch.

10$:
	MOVAB	(R3)[R2], R3		; Point to next table section.
	MOVAL	(R3)[R2], R3
	MOVZBL	(R3)+, R2		; Get number of densities for this format.
	SOBGTR	R1, 10$			; Loop to we find the right section.

20$:
	POPL	R1
	RSB


	.SBTTL	Speed Conversion Routines
	.SBTTL	.   TMSCP$SPEEDTOMSCP

;++
;
; TMSCP$SPEEDTOMSCP
;
; Convert IPS to MSCP speed value using formula:
;
;	MSCP_SPEED = ( IPS * BPI ) / 1000
;
; Inputs:
;
;	R0	IPS
;	R1	bits  0 -  7 = MSCP density
;		bits  8 - 15 = MSCP Tape format flag
;		bits 16 - 31 = MBZ
;
; Outputs:
;
;	R0	MSCP speed value
;	R1 is destroyed.
;	All other registers are preserved.
;--

TMSCP$SPEEDTOMSCP::
	PUSHR	#^M<R2,R3>
	BSBW	TMSCP$FIND_SECTION	; Locate table section for this format.
	MOVAB	(R3)[R2], R3		; Point to absolute density values.
	DECL	R2			; R2 = number of densities to search.
					; (Minus one to skip default density).
	BICL	#^C MSCP$M_TF_MASK, R1	; Isolate format bitflag.
	FFS	#0, R2, R1, R1		; Convert MSCP density to index.
	MOVL	(R3)[R1], R1		; Get numerical BPI value.
	MULL	R1, R0			; Compute (IPS * BPI).
	DIVL	#1000, R0		; Compute (IPS * BPI) / 1000.
	POPR	#^M<R2,R3>
	RSB


	.SBTTL	.   TMSCP$MSCPTOSPEED

;++
;
; TMSCP$MSCPTOSPEED
;
; Convert MSCP speed value using formula:
;
;	IPS = ( MSCP_SPEED * 1000 ) / BPI
;
; Round IPS speed to nearest value in TU_ABSPEED which is less than or equal 
; to computed value.
;
; Inputs:
;
;	R0	MSCP speed value
;	R1	bits  0 -  7 = MSCP density
;		bits  8 - 15 = MSCP Tape format flag for this device
;		bits 16 - 31 = MBZ
;
; Outputs:
;
;	R0	IPS
;	R1 is modified.
;	All other registers are preserved.
;--

TMSCP$MSCPTOSPEED::
	PUSHR	#^M<R2,R3>
	BSBW	TMSCP$FIND_SECTION	; Locate table section for this format.
	MOVAB	(R3)[R2], R3		; Point to absolute density values.
	DECL	R2			; R2 = number of densities to search.
					; (Minus one to skip default density).
	BICL	#^C MSCP$M_TF_MASK, R1	; Isolate format bitflag.
	FFS	#0, R2, R1, R1		; Convert MSCP density to index.
	MOVL	(R3)[R1], R1		; Get numerical BPI value.
	MULL	#1000, R0		; Compute (MSCP_SPEED * 1000).
	DIVL	R1, R0			; Divide that by BPI.
	ADDL	#5, R0			; Round up for search.
	MOVAB	TMSCP$TU_ABSPEED, R1	; Get speeds table base.

10$:
	CMPW	(R1)+, R0		; Find first speed > R0.
	BLSSU	10$			; Loop till bigger speed found.
	MOVZWL	-4(R1), R0		; Then, use the previous speed.
	POPR	#^M<R2,R3>
	RSB


	.SBTTL	Debugging Routines
	.SBTTL	-	PC History recording
	.IF DEFINED DEBUG$PC_HISTORY

;+
; Functional Description:
;
; This routine is used to maintain a running history of the Program
; Counter as the TMSCP server executes. This routine is called from 
; from various places throughout the server using a BSBW instruction.
; The return address of the caller is stored in the PC history table
; as the offset into the server. This allows us to save space by only
; using one word to save the address, and makes it easier to find the
; address within a listing of the server.
;
; Inputs:
;
;	8(SP)  =  Address to be logged (PCHIST* only)
;	R3     =  Value to be logged (PCHIST_R3, PCHIST_R3R0, and R3HIST only)
;	R0     =  Value to be logged (PCHIST_R0, PCHIST_R3R0, and R0HIST only)
;
; Outputs:
;
;	None
;
; Implicit Outputs:
;
;	Address is logged in the PC history table
;	TMSCP$PCHISTCUR is moved to point to the next available cell
;-

	.MACRO	LOG_VALUE	value, ?buffer_not_full
	MOVL	value,(R1)+		; Save the PC.
	CMPL	R1, TMSCP$PCHISTEND	; Check for the end of the table
	BLSSU	buffer_not_full			;  still plenty of room
	MOVL	TMSCP$PCHISTBEG, R1	; Wrap around back to the start
	INCL	TMSCP$PCHISTFUL		;  and bump the filled counter

buffer_not_full:
	.ENDM	LOG_VALUE

TMSCP$PCHIST::
	PUSHL	R1			; Save any registers used
	MOVL	TMSCP$PCHISTCUR, R1	; Get the cell in the table
	LOG_VALUE	4(SP)		; Save the PC.
	MOVL	R1, TMSCP$PCHISTCUR	; Get the new table offset
	POPL	R1			; Restore the registers
	RSB				;  and return

TMSCP$R0HIST::
	PUSHL	R1			; Save any registers used
	MOVL	TMSCP$PCHISTCUR, R1	; Get the cell in the table
	LOG_VALUE	R0		; Save the value
	MOVL	R1, TMSCP$PCHISTCUR	; Get the new table offset
	POPL	R1			; Restore the registers
	RSB				;  and return

TMSCP$R3HIST::
	PUSHL	R1			; Save any registers used
	MOVL	TMSCP$PCHISTCUR, R1	; Get the cell in the table
	LOG_VALUE	R3		; Save the value
	MOVL	R1, TMSCP$PCHISTCUR	; Get the new table offset
	POPL	R1			; Restore the registers
	RSB				;  and return

TMSCP$PCHIST_R0::
	PUSHL	R1			; Save any registers used
	MOVL	TMSCP$PCHISTCUR, R1	; Get the cell in the table
	LOG_VALUE	4(SP)		; Save the PC.
	LOG_VALUE	R0		; Save the value
	MOVL	R1, TMSCP$PCHISTCUR	; Get the new table offset
	POPL	R1			; Restore the registers
	RSB				;  and return

TMSCP$PCHIST_R3::
	PUSHL	R1			; Save any registers used
	MOVL	TMSCP$PCHISTCUR, R1	; Get the cell in the table
	LOG_VALUE	4(SP)		; Save the PC.
	LOG_VALUE	R3		; Save the value
	MOVL	R1, TMSCP$PCHISTCUR	; Get the new table offset
	POPL	R1			; Restore the registers
	RSB				;  and return

TMSCP$PCHIST_R3R0::
	PUSHL	R1			; Save any registers used
	MOVL	TMSCP$PCHISTCUR, R1	; Get the cell in the table
	LOG_VALUE	4(SP)		; Save the PC.
	LOG_VALUE	R3		; Save the value
	LOG_VALUE	R0		; Save the value
	MOVL	R1, TMSCP$PCHISTCUR	; Get the new table offset
	POPL	R1			; Restore the registers
	RSB				;  and return

	.ENDC	   ;DEBUG$PC_HISTORY


	.SBTTL	-	Current Counter Sanity Test
	.IF DEFINED DEBUG$CURRENT_SANITY

;+
; Functional Description:
;
; This routine calculates the value that should be held for the first
; UQB in the Unit Queue Block linked list. Note that this routine is
; designed for the case where the first unit in the list is the one
; seeing the most activity.
;
; Inputs:
;
;	None
;
; Outputs:
;
;	None
;
; Implicit Outputs:
;
;	The list of Host Queue Blocks is traversed and a running count
; 	of currently outstanding requests from all known hosts is made.
; 	Then the list of Unit Queue Blocks is traversed and requests
; 	that are currently pending on a unit other than the target, or
; 	requests that are blocked are subtracted from the total above.
; 	If the value calculated proves to be different than that 
; 	maintained in the counter, the system is bug checked.
;-

TMSCP$CHECK_CURRENT::
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers to be used
	CLRL	R0			; total requests - blocked
	CLRL	R4			; sum of current counters
	MOVL	G^SCS$GL_TMSCP,R5	; Get the TSRV address
	MOVAL	TSRV$L_HQB_FL(R5),R1	; Get the HQB list head
	MOVL	R1,R2			;  and save a copy away

	ASSUME	HQB$L_FLINK EQ 0

10$:
	MOVL	HQB$L_FLINK(R2), R2	; Next HQB in the list
	CMPL	R2,R1			; If this is the list head
	BEQL	20$			;  then the list is traversed
	ADDW	HQB$W_NUM_QUE(R2),R0	; Otherwise sum the count
	BRB	10$			;  and loop until finished

20$:
	MOVAL	TSRV$L_UQB_FL(R5),R1	; Get the address of the UQB
	MOVL	R1,R2			;  list head and perform the 

	ASSUME	UQB$L_FLINK EQ 0

30$:
	MOVL	UQB$L_FLINK(R2), R2	;  same operation as above,
	CMPL	R2,R1			;  looping through the list
	BEQL	40$			;  this time decrementing the
	SUBW	UQB$W_NUM_QUE(R2),R0	;  the total by the number of
	ADDW	UQB$W_CURRENT(R2),R4	;  requests on the blocked queue,
	BRB	30$			;  and summing the current counts

40$:
	CMPW	R4,R0			; Now compare the two counters,
	BEQL	50$			;  the should be equal
	BUG_CHECK MSCPSERV,FATAL	; If they are not lets get a dump

50$:
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Put the registers back
	RSB				;  and return

	.ENDC	   ;DEBUG$CURRENT_SANITY

	.IF DEFINED DEBUG$LOG
	.PAGE


	.SBTTL	-	LOG_CMD_PKT - Log an MSCP command packet
	.SBTTL	-	LOG_END_PKT - Log an MSCP end packet

;+
;
; Functional Description:
;
;	This following routines log an MSCP packet into the server's
;	ring buffer.  Separate entry points are provided for logging
;	a command packet (TMSCP$LOG_CMD_PKT), or an end packet or attention
;	message (TMSCP$LOG_END_PKT).
;
; Inputs: (TMSCP$LOG_CMD_PKT)
;
;	R1	Length of pkt to be logged
;	R2	Address of pkt to be logged
;	R3	HRB address
;	R4	CDT address
;
; Inputs: (TMSCP$LOG_END_PKT)
;
;	R1	Length of pkt to be logged
;	R2	Address of pkt to be logged
;	R3	HRB address
;
; Implicit inputs:
;
;	TSRV$L_NEXT_READ	Address of next packet to be read
;	TSRV$L_NEXT_WRITE	Address to write next packet
;	TSRV$L_LOG_BUF_START	Address of start of buffer
;	TSRV$L_LOG_BUF_END	Address of end of buffer
;	TSRV$W_INC_LOLIM	Low unit number to include
;	TSRV$W_INC_HILIM	High unit number to include
;	TSRV$W_EXC_LOLIM	Low unit number to exclude
;	TSRV$W_EXC_HILIM	High unit number to exclude
;
;	Status Bits in TSRV$W_STATE:
;
;	TSRV$V_PKT_LOGGED	Packet has been logged since last read
;	TSRV$V_PKT_LOST		One or more packets overwritten since
;				last read
;-

LOG_PKT_SIZE	=	0

	.ENABLE LSB

TMSCP$LOG_CMD_PKT::
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save R0-R5 due to MOVC.
	BRB	10$				; Branch to common code.

5$:
	BRW	40$				; Not a selected unit, so
						;  restore registers and exit.

TMSCP$LOG_END_PKT::
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save R0-R5 due to MOVC.
	MOVL	HRB$L_HQB(R3), R3		; Get HQB address.
	MOVL	HQB$L_CDT(R3), R4		; Get CDT address.

10$:
	MOVL	G^SCS$GL_TMSCP, R5		; Get TSRV address.
	MOVAL	TSRV$W_INC_LOLIM(R5), R0	; Get address of range settings.
	MOVW	MSCP$W_UNIT(R2), R3		; Get unit number.

	CMPW	R3, (R0)+			; Within include range low end?
	BLSSU	5$				; LSS means not included.

	ASSUME	TSRV$W_INC_HILIM EQ 2+TSRV$W_INC_LOLIM

	CMPW 	R3, (R0)+			; Within include range high end?
	BGTRU	5$				; GTR means not included.

	ASSUME	TSRV$W_EXC_LOLIM EQ 2+TSRV$W_INC_HILIM

	CMPW 	R3, (R0)+			; Outside of exclude range?
	BLSSU	15$				; LSS means NOT excluded.

	ASSUME	TSRV$W_EXC_HILIM EQ 2+TSRV$W_EXC_LOLIM

	CMPW	R3, (R0)+			; Within exclude range?
	BLEQU	5$				; LEQ means excluded.

15$:
	MOVL	TSRV$L_NEXT_WRITE(R5), R0	; Get location to write pkt.
	BBCS	#TSRV$V_PKT_LOGGED, -		; If bit was clear, buffer
		TSRV$W_STATE(R5), 20$		;  is logically empty.
	CMPL	R0, TSRV$L_NEXT_READ(R5)	; Check for full buffer.
	BNEQ	20$				; Branch if buffer not full or
						;  already clobbered.
	BISW	#TSRV$M_PKT_LOST, -		; Else, we have wrapped around
		TSRV$W_STATE(R5)		;  the buffer.

; Log header info to buffer

20$:
	READ_SYSTIME (R0)+			; Fill in the system time

LOG_PKT_SIZE	=	LOG_PKT_SIZE + 8

	MOVL	CDT$L_PB(R4), R3		; Get PB address.
	MOVL	PB$L_SBLINK(R3), R3		; Get SB address.
	MOVQ	SB$B_SYSTEMID(R3), (R0)+	; Fill in system id.

LOG_PKT_SIZE	=	LOG_PKT_SIZE + 8

	MOVW	R1, -2(R0)			; Fill in logged pkt length.
	MOVQ	SB$T_NODENAME(R3), (R0)+	; Grab nodename, part 1.

LOG_PKT_SIZE	=	LOG_PKT_SIZE + 8

	MOVQ	SB$T_NODENAME+8(R3), (R0)+	; Grab nodename, part 2.

LOG_PKT_SIZE	=	LOG_PKT_SIZE + 8

; Actually log the packet now

	MOVC5	R1,(R2),#0,#MSCP$K_LEN,(R0)	; Zero fill packet into buffer.

LOG_PKT_SIZE	=	LOG_PKT_SIZE + MSCP$K_LEN

; now R3 = new address for next write

	MOVL	G^SCS$GL_TMSCP, R5		; Restore TSRV address.
	CMPL	R3, TSRV$L_LOG_BUF_END(R5)	; At end of ring buffer?
	BLSSU	30$				; Branch if not.
	MOVL	TSRV$L_LOG_BUF_START(R5), R3	; Else reset to beginning.

30$:
	MOVL	R3, TSRV$L_NEXT_WRITE(R5)	; Set next write address.

40$:
	POPR	#^M<R0,R1,R2,R3,R4,R5>		; Restore registers.
	RSB

	.DISABLE LSB
	.PAGE


	.SBTTL	Packet Logging Routines
	.SBTTL	-	CREATE_LOG - Create log buffer

;+
;
; CREATE_LOG - Create and initialize log buffer
;
; Functional Description:
;
;	This routine allocates a ring buffer from non-paged pool
;	for logging MSCP packets and initializes TSRV field which
;	point to the buffer.
;
; Inputs:
;
;	R5	TSRV address
;
; Outputs:
;
;	R1, R2	Scratched
;	R0	Return status from EXE$ALONONPAGED
;
; Implicit Outputs:
;
;	TSRV$L_BUF_START	Address of start of buffer
;	TSRV$L_BUF_END		Address of end of buffer
;	TSRV$L_NEXT_READ	Address if next packet to read
;	TSRV$L_NEXT_WRITE	Address if next packet to write
;	TSRV$W_STATE		Indicates logging code is present
;				 and initialized
;	TSRV$W_INC_LOLIM	Low unit number to include
;	TSRV$W_INC_HILIM	High unit number to include
;	TSRV$W_EXC_LOLIM	Low unit number to exclude
;	TSRV$W_EXC_HILIM	High unit number to exclude
;
;-

	$EQU	LOG_BUF_SIZE	1024*LOG_PKT_SIZE	; Bytes in history buffer.

	ASSUME TSRV$W_INC_HILIM  EQ  TSRV$W_INC_LOLIM+2
	ASSUME TSRV$W_EXC_LOLIM  EQ  TSRV$W_INC_HILIM+2
	ASSUME TSRV$W_EXC_HILIM  EQ  TSRV$W_EXC_LOLIM+2

TMSCP$CREATE_LOG::
	ADDL3	#12, #LOG_BUF_SIZE, R1	; Get ring buffer size.
	JSB	G^EXE$ALONONPAGED	; Attempt to allocate space.
	BLBC	R0, 99$			; Branch if allocation failed.
					; R2 = address / R1 = size.
	CLRQ	(R2)+			; Clear FLINK and BLINK.
	MOVW	R1, (R2)+		; Save size.
	MOVZBW	#DYN$C_TSRV, (R2)+	; Set type and sub-type.

	MOVL	R2,-			; Save address of start of ring buffer.
		TSRV$L_LOG_BUF_START(R5);
	ADDL3	#LOG_BUF_SIZE, R2,-	; Save address of end of ring buffer.
		TSRV$L_LOG_BUF_END(R5)	;
	MOVL	R2, TSRV$L_NEXT_READ(R5); Init read pointer.
	MOVL	R2,- 			; Init write pointer.
		TSRV$L_NEXT_WRITE(R5)

	ASSUME	TSRV$W_INC_HILIM EQ 2+TSRV$W_INC_LOLIM

	MOVL	#-1@16,-		; Set default include unit low/high
		TSRV$W_INC_LOLIM(R5)	;  limits to include all units.

	ASSUME	TSRV$W_EXC_HILIM EQ 2+TSRV$W_EXC_LOLIM

	MOVZWL	#-1,-			; Set default exclude unit low/high
		TSRV$W_EXC_LOLIM(R5)	;  limits to exclude no units.
	MOVW	#TSRV$M_LOG_PRESENT,-	; Buffer present and accounted for, sir.
		TSRV$W_STATE(R5)	;

99$:
	RSB

	.ENDC	; DEBUG$LOG


	.SBTTL	Read Only Length and Modifier Tables

	.ALIGN	LONG

TMSCP$SERV_INFO::
TMSCP$SERV_NAME::
	.ASCII	/MSCP$TAPE       /
;

;

TMSCP$COM_PKT_LEN::
	.BYTE	0*4,4*4,4*4,3*4,8*4,0*4,0*4,0
	.BYTE	3*4,9*4,9*4,3*4,0*4,0*4,0*4,0
	.BYTE	4*4,0*4,3*4,3*4,0*4,0*4,3*4,0
	.BYTE	0*4,0*4,0*4,0*4,0*4,0*4,0*4,0
	.BYTE	7*4,7*4,7*4,0*4,3*4,5*4

;

TMSCP$END_PKT_LEN::
	.BYTE	0*4,4*4,5*4,11*4,8*4,0*4,0*4,0
	.BYTE	3*4,11*4,11*4,3*4,0*4,0*4,0*4,0
	.BYTE	9*4,0*4,3*4,8*4,0*4,0*4,3*4,0
	.BYTE	0*4,0*4,0*4,0*4,0*4,0*4,0*4,0
	.BYTE	9*4,9*4,9*4,0*4,8*4,8*4

;

TMSCP$MOD_TBL::
	.WORD	^C0			;  0-unused
	.WORD	^C0			;  1-ABORT
	.WORD	^C0			;  2-GET COMMAND STATUS
	.WORD	^C<-			;  3-GET UNIT STATUS
		MSCP$M_MD_NXUNT>	;    next unit
	.WORD	^C0			;  4-SET CONTROLLER CHARACTERISTICS
	.WORD	^C0			;  5-unused
	.WORD	^C0			;  6-unused
	.WORD	^C0			;  7-unused
	.WORD	^C<-			;  8-AVAILABLE
		MSCP$M_MD_ALLCD!-	;    all class drivers
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_EXCAC!-	;    exclusive access
		MSCP$M_MD_UNLOD>	;    clear serious exception
	.WORD	^C<-			;  9-ONLINE
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_STWRP!-	;    enable set write protect
		MSCP$M_MD_EXCAC>	;    exclusive access
	.WORD	^C<-			; 10-SET UNIT CHAR
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_STWRP!-	;    enable set write protect
		MSCP$M_MD_EXCAC>	;    exclusive access
	.WORD	^C0			; 11-DETERMINE ACCESS PATH
	.WORD	^C0			; 12-unused
	.WORD	^C0			; 13-unused
	.WORD	^C0			; 14-unused
	.WORD	^C0			; 15-unused
	.WORD	^C<-			; 16-ACCESS
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_REVRS!-	;    reverse
		MSCP$M_MD_SCCHH!-	;    supress caching (high speed)
		MSCP$M_MD_SEREC!-	;    supress error recovery
		MSCP$M_MD_SECOR>	;    supress error correction
	.WORD	^C0			; 17-unused
	.WORD	^C<-			; 18-ERASE
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_IMMED>	;    immediate completion
	.WORD	^C<-			; 19-FLUSH
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX>	;    clear serious exception
	.WORD	^C0			; 20-unused
	.WORD	^C0			; 21-unused
	.WORD	^C<-			; 22-ERASE GAP
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_IMMED>	;    immediate completion
	.WORD	^C0			; 23-unused
	.WORD	^C0			; 24-unused
	.WORD	^C0			; 25-unused
	.WORD	^C0			; 26-unesed
	.WORD	^C0			; 27-unused
	.WORD	^C0			; 28-unused
	.WORD	^C0			; 29-unused
	.WORD	^C0			; 30-unused
	.WORD	^C0			; 31-unused
	.WORD	^C<-			; 32-COMPARE HOST DATA
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_REVRS!-	;    reverse
		MSCP$M_MD_SCCHH!-	;    supress caching (high speed)
		MSCP$M_MD_SECOR!-	;    supress error correction
		MSCP$M_MD_SEREC>	;    supress error recovery
	.WORD	^C<-			; 33-READ
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_COMP!-	;    compare
		MSCP$M_MD_REVRS!-	;    reverse
		MSCP$M_MD_SCCHH!-	;    supress caching (high speed)
		MSCP$M_MD_SECOR!-	;    supress error correction
		MSCP$M_MD_SEREC>	;    supress error recovery
	.WORD	^C<-			; 34-WRITE
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_COMP!-	;    compare
		MSCP$M_MD_ENRWR!-	;    enable re-write error recovery
		MSCP$M_MD_IMMED!-	;    immediate completion
		MSCP$M_MD_SECOR!-	;    supress error correction
		MSCP$M_MD_SEREC>	;    supress error recovery
	.WORD	^C0			; 35-unused
	.WORD	^C<-			; 36-WRITE TAPE MARK
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_IMMED>	;    immediate completion
	.WORD	^C<-			; 37-REPOSITION
		MSCP$M_MD_CDATL!-	;    clear cached data lost
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_DLEOT!-	;    detect LEOT
		MSCP$M_MD_IMMED!-	;    immediate completion
		MSCP$M_MD_OBJCT!-	;    object count
		MSCP$M_MD_REVRS!-	;    reverse
		MSCP$M_MD_REWND!-	;    rewind
		MSCP$M_MD_SCCHH!-	;    supress caching (high speed)
		MSCP$M_MD_SECOR!-	;    supress error correction
		MSCP$M_MD_SEREC>	;    supress error recovery


	.SBTTL	Patch Area

;
; Patch area
;

	. = <.+15>&-16

TMSCP$PATCH::	.BLKB		128

	. = <.+15>&-16

TMSCP$END::
	.END
