sw$hack=0
	.IF DEFINED DEBUG$LOG
	.TITLE	LOGGING_MSCP - LOGGING MSCP Server - Emulator
	.IFF
	.TITLE	MSCP - MSCP Server - Emulator
	.ENDC
	.IDENT	'X-64'

	SOFTWARE_REV == 64	; Keep software rev level equal to ident field
				;  for easier remote diagnosis

	HARDWARE_REV == 5	; Leave the hardware rev alone unless software
				;  rev overflows a byte or driver/server
				;  incompatible changes are introduced.

;****************************************************************************
;*									    *
;*  COPYRIGHT © 1985, 1986, 1987, 1988, 1996 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:	MSCP Server
;                         
; ABSTRACT:
;
;	This program implements a MSCP(Mass Storage Control Protocol) server.
;	It serves local disks to remote systems (hosts). Valid MSCP command 
;	packets are accepted as input from other cluster members. Local disks
;	are then accessed via the proper disk driver according to their type 
;	to perform the indicated operations. MSCP end messages, and possibly 
;	data (in the case of transfers) are returned.
;
; ENVIRONMENT:	Interrupt stack, system context
;
; AUTHOR:	William L. Goleman
;
; CREATION DATE:  9-August-1985
;
; MODIFIED BY:
;
;       X-64    MJM		Michael J. Moroney		 3-Apr-1997
;		Change local label to prevent conditional build error.
;
;       X-63    MJM		Michael J. Moroney		26-Mar-1997
;		Make Forced Errors return partial transfer counts rather
;		than 0 bytes, to make it consistant with non-served disks.
;
;       X-62    RFB004		Ray Boucher                      7-Aug-1996
;               Now add the rest of the code to allow building on OpenVMS
;               versions prior to V7.0
;
;	X-61	RFB003		Ray Boucher		23-Jul-1996
;		Add conditional complier code to allow building on OpenVMS
;		versions prior to V7.0
;
;	X-60	JCH710a	John C. Hallyburton, Jr.	20-May-1996
;		V6.2/V7.0 compatibility: unless DUDRIVER signals that it
;		understands new naming, do not serve devices that use
;		new naming. DUDRIVER (or any other disk class driver)
;		signals it understands new naming by supplying a nonblank
;		character in byte 14 (i.e., next-to-last) of SCS connect data.
;
;       X-59    DVE             Dennis Van Eron         15-Apr-1996
;               In routines GET_UNIT_STATUS,ONLINE add check of HQB 
;		cnt_flags before updating the UF_REPLC bit. This is in 
;		support of rolling upgrades.
;               Fix bug introduced in X-56, set up R1 with UCB in ONLINE.
;
;	X-58	JCH710	John C. Hallyburton, Jr.	14-Mar-1996
;		STAR:: DOCD$:[EVMS.PROJECT_DOCUMENTS]FS-SCSI-NAMING.PS
;		Support serving devices with port allocation classes
;		(DDB$V_PAC set in DDB$B_FLAGS.)
;
;       X-57    DVE             Dennis Van Eron         29-Feb-1996
;               In routine XFER_ERR, copy the MSCP status from the IOSB.
;
;       X-56    DVE             Dennis Van Eron         28-Feb-1996
;               Modified the setting of UF_REPLC in the UQB$W_UNIT_FLAGS
;               field to consider all non MSCP devices in routines
;               CREATE_UQB and ONLINE.
;               Update UF_REPLC state in routine GET_UNIT_STATUS.
;
;       X-55    DVE             Dennis Van Eron         27-Feb-1996
;               Add logging code to log IRP command and exit packets.
;
;       X-54    DVE             Dennis Van Eron         21-Feb-1996
;               Modified the handling of a GCS command in routine
;               GET_COMMAND_STATUS to return "progress" determined
;               by the setting of new sysgen parameter MSCP_CMD_TMO.
;
;	X-53	DVE		Dennis Van Eron		15-Nov-1995
;               Demote instructions, from word to byte, which reference
;               field IRP$B_WLG_FLAGS.
;		
;	X-52	TRC1011		Terry Cassidy		22-May-1995
;		Change ERASE under conditions of multiple segments AND
;		write history logging to WRITE instead.
;
;	X-51	DVE		Dennis Van Eron		14-Apr-1995
;		In routine ONLINE, fixed write lock problem introduced
;		in X-29. UF_WRTPH bit is updated in UQB$W_UNIT_FLAGS
;		to reflect changes in write lock status of a served device.
; 
;	X-50	GH	Gary Hughes/William Goleman	10-Mar-1995
;		Begin cancel i/o changes.
;		Load host no into irp$l_chan, add $pcbdef
;		Build dummy PCB and link it to the DSRV, load our return
;		address into PCB$L_PID
;		added pending queue scan in vc_err
;		added call to cancel in vc_err, added $candef
;		added temporary counters
;
;	X-49	LSS0324		Leonard S. Szubowicz	 2-Mar-1995
;		Rely on $IRPDEF to define IRP$L_HRB.  Locally define CDRP$L_HRB
;		in terms of IRP$L_HRB only if $CDRPDEF doesn't.  This allows
;		this cell to be independent of the definition of IRP$L_ASTPRM.
;
;       X-48    DVE             Dennis Van Eron                 23-Feb-1995
;               Change BGTRU to BGEQU in routine SCS$DISK_MSCP_NEWDEV when 
;		validity check is made for the serving of MAX_UNIT devices
;
;	X-47	PRD		Paul R. DeStefano		27-Dec-1994
;		Set IRP$B_PRI field in sequential commands to the lowest
;		priority.  WLE flags have been moved from the SHD_FLAGS
;		field to the WLG_FLAGS field in the IRP/CDRP.
;
;	X-46	TRC1004		Terry Cassidy		12-Dec-1994
;		Fix forced error write-through.
;
;	X-45	GH		Gary Hughes		 9-Nov-1994
;		Flag IRPs for request in DRV_WAIT state in VC error cleanup.
;		Cooperating drivers will detect the flag and rundown the IRP
;		immediately.
;
;	X-44	GH		Gary Hughes		28-Oct-1994
;		Modify handling of UQB-Offline state for GUS and Check_service.
;
;	X-43	JFD0636		James F. Dunham		16-SEP-1994
;		If drive state is already stalled, when calling DRIVER_STALL,
;		or drive state is already unstall, when calling DRIVER_UNSTALL,
;		just ignore request.
;
;	X-42	GH		Gary Hughes		14-Aug-1994
;		Add DRIVER_STALL and DRIVER_UNSTALL routines for callbacks
;		from DU/DK drivers. Add new entry point for drivers to
;		call mount verification routines from driver context.
;
;	X-41	NJB		Nancy Jean Burkholder	30 Aug 1994
;		Cleanup dispatch table in VC_ERR_COMMON and add comment in
;               MSG_IN.
;
;	X-39	DOS0039		Dan O'Shaughnessy	11-Jul-1994
;               Fix corruption bug caused due to clearing IRP$L_STS2 when
;		issuing write command to the controller. Introduced in
;		X-33.
;		
;		
;	X-39	NJB		Nancy Jean Burkholder	16-May-1994
;		Correctly identify bad field in ONLINE commands.
;
;	X-38	GH		Gary Hughes		12-May-1994
;		Add new HRB state, UCB_STALL, to VC_ERR and ABORT routines.
;		This allows cooperating drivers to notify the server that
;		CDRP is stalled on the UCB pending queue.
;
;	X-37	MAS0EP01	Michael A. Stams	10-May-1994
;               Remove local MAX_UNITS symbols and use the DSRV defined
;		symbol instead. Use the same symbol to size the HULB vector.
;               Correct code that manipulates host bitmaps to use MAX_HOSTS.
;
;	X-36	PJH		Paul J. Houlihan	12-Apr-1994
;		Set the software rev level to one beyond the Coral MSCP
;		Server rev level as Epsilon has the GET UNIT NAME code
;		used by DDR.
;
;	X-35			Brian Porter		 4-APR-1994
;		Clear the MSCP$K_OP_END bit in R1 before using it
;		in SEND_END as and index into the packet length table.
;
;		Correctly pass MSCP status to SEND_END in R0 from ERR_WLG.
;
;		When reporting write log INVALID COMMAND errors pass a
;		zero bytes transferred count.
;
;	X-34	JJA94_MSCP	John J. Andruszkiewicz	19-Mar-1994
;                               Kevin M. Jenkins
;		If doing an ONLINE, and RWAITCNT is bumped for SCS credit 
;		reasons, just queue the request.
;
;       X-33	RFB002		Ray Boucher		 7-Feb-1994
;               Clear IRP$L_STS2 when IRP$L_STS is cleared.
;
;	X-32	MAS0E08		Michael A. Stams	31-Jan-1994
;		Reorder code in SCS$DISK_MSCP_NEWDEV to verify MSCPness
;		prior to utilizing the CDDB.
;
;	X-31	RFB001		Ray Boucher		28-Dec-1993
;             1. Replace CANCEL_WAIT macro with IDLE_CDRP macro for 
;		 the case where CDRP FQFL queue links contain 
;		 non-paged pool addresses and crash the system.
;		 Add IDLE_CDRP macro in MAP_WAIT sub-routine of
;		 VC_ERR where CANCEL_WAIT macro should have been but 
;		 was not being used.
;             2. Fix build bug introduced in X-29. UCB needs to be
;		 setup in correct register in ONLINE routine before 
;		 proceeding to complete ONLINE command for non-MSCP 
;		 disks.
;
;	X-30	MJN001		Marc J. Noel		20-Dec-1993
;		Make correction in routine creat_uqb.  Routine did
;		not correctly set write protect bit if MSCP device.
;
;	X-29	MAS0E07		Michael A. Stams	10-Dec-1993
;		Propagate DEV$V_NOFE bit in CREATE_UQB / ONLINE.
;
;	X-28	MAS0E06		Michael A. Stams	 8-Dec-1993
;		Remove conditional assembly for port simulator.
;		Remove conditional assembly for VAX versus EVAX.
;		Add protection directives when altering UCB$L_QLEN.
;		Fix code in FIND_UQB to return poisoned packet instead
;		of bugcheck when SLUN bit not set.
;
;	X-27	WLG0E27		William Goleman 	19-Nov-1993
;		Initialize HRB$L_BD_ADDR, and make sure field is updated
;		properly from the IRP for WHM commands.
;
;	X-26	MAS0E05		Michael A. Stams	 1-Nov-1993
;		fold X-20U6 from  V15_plus
;
;	X-25	MAS0E04		Michael A. Stams	27-Oct-1993
;		Really do all of X-24.
; 
;	X-24	MAS0E03		Michael Stams		21-Sep-1993
;		Fold 20U5,20U4 from v15_plus (use correct errtbl, Call to
;		CHECK_DISK in TERM_CL_DR_CON).
;		
;		Add code to FIND_UQB to return poisoned packet instead
;		of bugcheck when SLUN bit not set.
;
;	X-23	MAS0E02		Michael Stams		06-Aug-1993
;		Fold 20U3 from V15_plus (set IRP$M_WLE correctly)
;
;	X-22	MAS0E01		Michael Stams		28-Jul-1993
;		Merge X-21
;		X-21	RWC125		Richard W. Critz, Jr.		 1-Jul-1993
;			Add GET UNIT NAME command to support DDR.
;
;		Correct WHL bit ommission in ONLINE.
;		Propagate opcode correctly in ERR_WLG and PACKET_ERROR. 
;
;	X-20U1	MAS0P01		Michael Stams        	26-Jul-1993
;		Fold WHM/MSCP logging routines from Blade.
;		
;		Happy B-day Dad!
;
;		Remove All comments prior to initial port while honoring 
;		those who got us here: 
;		
;			- Those dudes we've heard alot about them - 
;
;	        	Scott H. Davis, Rod Gamache, William Goleman,
;			Gary NMI Hughes, CW Hobbs, Drew Mason,		
;			Steve Mayhew, Mark A. Stiles, Ken Stumpf,		
;			Leonard S. Szubowicz, Wai C. Yim.		
;
;
;
;	X-20	GH024		Gary Hughes			30-Apr-1993
;		Modify SCS$DISK_MSCP_MV to not change if the unit state if
;		either of the following conditions are true:
;			- we are being called as a result of quorum loss
;			- no hosts have the unit online through this server
;
;	X-19	MAS0D02		Michael Stams			21-APR-1993
;		fold in following from BLADE.
;		X-36A3	RBK0140		R. Bruce Kelsey		21-Apr-1993
;			GUS processing neglects to load MSCP UNIT_ID field
;			under some circumstances, resulting in DISKCLASS in
;			client's POLL_FOR_UNIT/NEW_UNIT thread. Fix it!
;
;		X-36A2	GH023		Gary Hughes		16-Apr-1993
;			Change unit state handling
;			      - change state to offline in MV_SET_OFFLINE
;			      -	always set NOVOL subcode in GUS end packets for
;				offline devices (except dead DUS devices)
; 
;
;	X-18	MAS0D01		Michael Stams			30-MAR-1993
;		Modify GET_UNIT_STATUS to check the driver name correctly.
;
;	X-17	WLG0D17  Goleman for: Davis, Hughes, Snaman	18-MAR-1993
;		Modify NEWDEV routine to serve local disks when allocation
;		class is zero and serve_all set to one.
;
;	X-16	WLG0D16		William Goleman			16-Mar-1993
;		Add check for MSCP_INITING bit in UCB device status before 
;		getting CDDB address. Do not serve the device if the
;		MSCP_INITING bit is set.
;
;	X-15	WLG0D15		William Goleman			 8-Mar-1993
;		Update rules for determining whether or not a device should
;		be made accessable clusterwide. Changes made to
;		SCS$DISK_MSCP_NEWDEV.
;
;	X-14	MAS		Michael Stams			27-Jan-1993
;		Change selected branches to unsigned form
;
;	X-13	WLG0D13		William Goleman			21-Sep-1992
;		Save and restore buffer descriptor address for locally mapped
;		buffers used to transfer data between server and client nodes.
;		Change ident to match CMS generation number.
;
;	X-26	LPL0001		Lee Leahy			 4 Jul 1992
;		1.)  Restore satellite booting support.  Place the routine 
;		     descriptor address of SCS$MSCP_CHECK_SERVICE in the data 
;		     cell used by PEDRIVER (SCS$GL_MSCP_NEWDEV).  If this cell
;		     is zero, the default, PEDRIVER does not attempt to call
;		     the MSCP server (since it is not loaded).  When this cell
;		     contains a non-zero address, presumably the routne descriptor
;		     for SCS$MSCP_CHECK_SERVICE, then PEDRIVER will attempt to 
;		     call the MSCP server to verify a SOLICIT_SRV request for a 
;		     MSCP served disk.
;		2.)  Return different error status values depending on whether the
;		     disk was or was not found in SCS$MSCP_CHECK_SERVICE.
;
;	X-25	WLGFT324	William Goleman			 6-May-1992
;		Bring over Brian Porter fix to clear out CDRP$L_CDT address 
;		in UNHOOK_CDRP routine. This makes sure CDRPs associated 
;		with a CDT for a connection that has been disconnected 
;		cannot reference that (possibly re-used) CDT.
;
;	X-24	WLG0P24		William Goleman			 8-Apr-1992
;		Fix conversion of bytes to blocks in fragmented commands. 
;		Bad register being used.
;
;	X-23	WLG0A23		William Goleman			10-Mar-1992
;		Fix broken erase command for non-DSA devices. Wrong way Shift 
;		was being done to convert block count back into byte count.
;
;	X-22	MSH1212		Michael S. Harvey		17-Feb-1992
;		Byte/word promotion: $TQEDEF.
;
;	X-21	WES		William E. Snaman		29-JAN-1992 13:26 
;		Fix problem where contents of R4 was lost scs$disk_mscp_newdev.
;
;	X-20	WLG0A20		William Goleman			9-Jan-1992
;		Add misssing constant delimiters (#) to MSCP constants.
;
;	X-19	WLG0A19		William Goleman			13-Dec-1991
;		Byte offset within page was not being stored in the HRB 
;		properly when allocating local buffer space. Remove 
;		breakpoints.
;
;	X-18	BJT279		Benjamin J. Thomas III	21-Nov-1991
;		Promote a few more IRP references
;
;	X-17	WLG0A17		William Goleman			19-Nov-1991
;		Promote byte and word fields within the CDDB and reflect
;		those changes in this module.
;
;	X-16	BJT271		Benjamin J. Thomas III	15-Nov-1991
;		Promote FUNC fields
;
; 	X-15	PJH		Paul J. Houlihan	02-Nov-1991
;		Alpha Buffer Descriptor Changes. Fix code used by simulator.
;
;	X-14	WLG0A13		William Goleman/Paul Houlihan 16-Sep-1991
;		Use new SCS macros to remove CDRPs from resource wait queues
;		when cleaning up after connection loss. Also insert some code
;		to keep port simulator working.
;
;	X-13	BJT0247		Benjamin J. Thomas III	30-Sep-1991
;		Promote IRP$W_STS to IRP$L_STS
;
;	X-12	LSS0224		Leonard S. Szubowicz	 6-Sep-1991
;		Expand UCB$W_QLEN to a longword to avoid word granularity
;		problems within the same quadword.
;
;	X-11	WLG0A11		William Goleman		 4-Sep-1991
;		Remove unnecessary CLRQ before calling ALLOC_RSPID in 
;		ADD_UNIT. Fix broken shift that was causing 0 byte transfers.
;
;	X-10	RFH001		Robert F. Hoffman	21-Aug-1991
;		Remove obsolete VA$ symbols
;
;	X-9	WLG0A09		William Goleman		21-Jun-1991
;		Fix virtual address calculations, and conversions from
;		bytes to pages. Fix refrences to SVAPTE and BOFF.
;
;	X-8	GHJ069		Gregory H. Jordan	16-May-1991
;		Additional updates for the port simulator.
;
;	X-7	GHJ054		Gregory H. Jordan	19-Apr-1991
;		Fix some truncation errors.  Add support for the Port
;		Simulator.  Fix a status return from ALLOCATE to the READ
;		routine.  Match names of SCS${DISK/TAPE}_MSCP_NEW_DEVICE
;		to those in SYSTEM routines which end in NEWDEV.
;
;	X-6	William E. Snaman			25-MAR-1991 15:20 
;		More porting changes.
;
;	X-5	William E. Snaman			25-MAR-1991 15:20 
;		Initial port.  Make it an execlet.
;		Load balancing info needs to be determined.
;		For EVAX.
;		  MV_SET_OFFLINE is now SCS$DISK_MSCP_MV.
;		  NEW_DEVICE is now SCS$DISK_MSCP_NEW_DEVICE.
;		  CHECK_SERVICE is now SCS$MSCP_CHECK_SERVICE.
;
;-

	.PAGE
	.SBTTL	Data Structure Definitions

;
; INCLUDE FILES:
;

	.NOCROSS
	$CANDEF			; Cancel service reason codes
	$CDDBDEF		; Class Driver Data Block
	$CDRPDEF		; Class Driver Request Packet
	$CDTDEF			; Connection Descriptor Table
	$DDBDEF			; Driver Data Block
	$DDTDEF			; Driver Displatch Table
	$DPTDEF			; Driver Prologue Table
	$DEVDEF			; Device Characteristics
	$DSRVDEF		; Disk SeRVer structure
	$DTNDEF			; Device Type Name Structure
	$DYNDEF			; Data Structure type
	$FKBDEF			; Fork Block structure
	$HQBDEF			; Host Queue Block structure
	$HRBDEF			; Host Request Block structure
	.IIF DF,EVAX,  $HWRPBDEF; Hardware Restart Parameter Block
	$HULBDEF		; Host/Unit Load Block structure
	$IODEF			; I/O function code
	$IPLDEF			; Interupt level
	$IRPDEF			; I/O Request Packet structure
	$MSCPDEF		; Mass Storage Control Protocol packet
	$PAGEDEF		; Page and block size values
	$PBDEF			; Path Block
	$PCBDEF			; Process Control Block
	$PDTDEF			; Port Descriptor Table
	$PRDEF			; Processor Register
        $PRIDEF                 ; Priority Increment Class definitions
	$SBDEF			; System Block
	$SCSDEF			; System Communications Services
	$SLVDEF			; System Loadable Vector
	$SRVBUFDEF		; Internal buffer area
	$SPLCODDEF		; Spinlock indicies
	$SSDEF			; System Service message
	$TQEDEF			; Timer Queue Entry
	$UCBDEF			; Unit Control Block structure
	$UQBDEF			; Unit Queue Block structure
	$VADEF			; Virtual Address Field
	$VAXDEF			; VAX models
	$VCBDEF			; Volume Control Block

;
; The following assume statements are to allow the flexibility of specifying
; the Write Log Entry flags by their IRP names or CDRP names depending on
; whether the IRP$B_WLG_FLAGS field or the CDRP$B_WLG_FLAGS field is being
; referenced.  These flags are defined in both IRPDEF and CDRPDEF.
;
	ASSUME IRP$V_WLE_REUSE EQ CDRP$V_WLE_REUSE
	ASSUME IRP$M_WLE_REUSE EQ CDRP$M_WLE_REUSE
	ASSUME IRP$V_WLE_SUPWL EQ CDRP$V_WLE_SUPWL
	ASSUME IRP$M_WLE_SUPWL EQ CDRP$M_WLE_SUPWL

;
; Debugging Switches
;
;	DEBUG$PC_HISTORY	= 1	; Assemble PC history code
;	DEBUG$CURRENT_SANITY	= 1	; Assemble the current counter checks
;	DEBUG$REJECT_TRACKING	= 1	; Monitor the reason for rejects


;
; Local symbols
;
MSCP_TIMER = 20*10000*1000	; 20 second timeout for general timer thread
                                                                               
;
; Definition of constants
;
	MAX_HOSTS   =   256
	MSCP$M_SLUN =   ^X4000
	VERSION	    =	0
	INITIAL_LOAD_RATING = 4

	.IF NDF DSRV$K_MAX_UNITS
	DSRV$K_MAX_UNITS = 256
	.ENDC

	.IF NDF HRB$K_ST_SUC_STALL
HRB$K_ST_SUC_STALL = 12		; success stall fkb in use - temp define
	.ENDC

	.IF NDF HRB$K_ST_UCB_STALL
HRB$K_ST_UCB_STALL = 13
	.ENDC

	.IF NDF IRP$V_SRV_ABORT
IRP$V_SRV_ABORT = 25
IRP$M_SRV_ABORT = ^X2000000
	.ENDC
;
; MACROS:
;

        .MACRO  DUCKHUNT ,?L0
	PUSHL	R0
        BICB3   #MSCP$K_OP_END,MSCP$B_OPCODE(R2),R0
        BNEQ    L0
        BUG_CHECK        mscpserv        fatal
L0:     POPL	R0
        .ENDM   DUCKHUNT



	.MACRO	ACTION,SYSERR,MSCPERR
	 .WORD	SYSERR
	 .WORD	MSCPERR
	.ENDM		

	.MACRO	SIGNAL_EVENT
;
; stub macro for future routine that will convert a lock on MSCP$SIGNAL
; from NL->CR and then CR->NL to notify server in load balance step 2, if any.
;
	.ENDM	SIGNAL_EVENT


; CREATE_FORK
;
; Functional description:
;
;	This macro initiates a fork thead and then returns control to the 
;	instruction following the macro invocation.
;
; Parameters:
;
;	address	of the first instruction to be executed in the new 
;		fork thread
;	frkblk	address of the new fork block
;	fr3	new fork R3 value
;	fr4	new fork R4 value
;	mask	register save mask for registers to save across fork process 
;		 creation

	.MACRO	CREATE_FORK address, frkblk=(R5), fr3=R3, fr4=R4, -
				mask=<^M<R0,R1,R2,R3,R4,R5>>
						; Save registers.
	.IIF	DIFFERENT mask, ^M<>, PUSHR #mask
						; Setup new fork registers.
	.IIF	DIFFERENT frkblk, (R5), MOVAL frkblk, R5
	.IIF	DIFFERENT fr3, R3, MOVL fr3, R3
	.IIF	DIFFERENT fr4, R4, MOVL fr4, R4
	BSBW	address
						; Restore registers.
	.IIF	DIFFERENT mask, ^M<>, POPR #mask
	.ENDM	CREATE_FORK


; A longword is needed in the CDRP so that when it is returned to the server
; the corresponding HRB can be determined.  The IRP$L_HRB cell is defined for
; this purpose by $IRPDEF.  IRP$L_HRB is overlaid on an existing cell in the 
; IRP (originally IRP$L_ASTPRM) that is otherwise untouched in IRPs that are
; allocated by the server.  Unfortunately the corresponding CDRP offset is
; was not defined at the same time.  Therefore, only if necessary, we'll do
; it here.
;
	.IF	NOT_DEFINED,CDRP$L_HRB
	CDRP$L_HRB = IRP$L_HRB-IRP$L_FQFL
	.ENDC

	.CROSS
	.DEFAULT DISPLACEMENT WORD

	.PAGE
	.SBTTL	Local Storage

	.SBTTL MSCP_INIT - Server initialization routine
;++
;
; FUNCTIONAL DESCRIPTION:
;
;	Server initialization routine
;
; CALLING SEQUENCE:
;
;	CALL	MSCP_INIT  (ALPHA)
;	JSB	MSCP_INIT  (VAX)
;	IPL is at 31
;
; INPUT PARAMETERS:
;
;	None
;
; IMPLICIT INPUTS:
;
;	None
;
; OUTPUT PARAMETERS:
;
;	R0 - Completion status
;
; IMPLICIT OUTPUTS:
;
;	None
;
; SIDE EFFECTS:
;
;	R0-R1 destroyed
;
;--
	DECLARE_PSECT	EXEC$INIT_CODE

	INITIALIZATION_ROUTINE	MSCP_INIT

MSCP_INIT::
	.CALL_ENTRY 

	PUSHR	#^M<R2,R3,R4,R5>        ; Preserve reg for exec init code 
					; dispatcher
	JSB	START_SERVER		; Do initialization

	POPR 	#^M<R2,R3,R4,R5>

	RET		

	.PAGE
	.SBTTL	-	START_SERVER  -  Initialization Routine
;+
; Functional Description:
; 
; Perform server initialization  Allocate memory to be used for internal 
; transfer buffers.  If the memory is not available return an error of 
; insufficient memory. Finally, initiate a connection to the port driver, and 
; establish ourselves as a listener.
;
; Calling sequence:
;
;	JSB	START_SERVER
;	IPL is at 31
;
; Inputs:
;
;	G^CLU$GL_MSCP_BUFFER	= Amount of non-paged pool to set aside for 
;				  the servers exclusive use.
;	G^CLU$GL_MSCP_CREDITS	= Number of send credits to be extended by 
;				  the server on each connection accepted.
;
; Outputs:
;
;	R0  =	status code
;-

START_SERVER:
	.JSB_ENTRY OUTPUT=<R0>

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

	PUSHR	#^M<R1,R2,R5>		; Save the registers to be used
	MOVAB	L^DSRV,R5		; Get the address of the DSRV structure
	MOVL	R5,G^SCS$GL_MSCP	; Store it for others to see
	MOVW	#DSRV$K_LENGTH,-	; Save the length of the structure
		DSRV$W_SIZE(R5)		;  in the size field
	MOVB	#DYN$C_DSRV,-		; This structure belongs
		DSRV$B_TYPE(R5)		;  to the disk server
	MOVB	#DYN$C_DSRV_DSRV,-	; The substructure is
		DSRV$B_SUBTYPE(R5)	;  a DSRV
;
; Clear all the logging area locations (plus BUFWAIT) to prevent accidental use.
;
	MOVAL	DSRV$W_STATE(R5), R1	; Get starting address of cells to clear

	ASSUME	DSRV$W_BUFWAIT  EQ  <DSRV$W_STATE+2>

	CLRL	(R1)+			; Zero STATE and BUFWAIT

	ASSUME	DSRV$L_LOG_BUF_START  EQ  <DSRV$W_STATE+4>
	ASSUME	DSRV$L_LOG_BUF_END  EQ  <DSRV$W_STATE+8>

	CLRQ	(R1)+			; Zero LOG_BUF_START and LOG_BUF_END

	ASSUME	DSRV$L_NEXT_READ  EQ  <DSRV$W_STATE+12>
	ASSUME	DSRV$L_NEXT_WRITE  EQ  <DSRV$W_STATE+16>

	CLRQ	(R1)+			; Zero NEXT_READ and NEXT_WRITE

	ASSUME	DSRV$W_INC_LOLIM  EQ  <DSRV$W_STATE+20>
	ASSUME	DSRV$W_INC_HILIM  EQ  <DSRV$W_STATE+22>
	ASSUME	DSRV$W_EXC_LOLIM  EQ  <DSRV$W_STATE+24>
	ASSUME	DSRV$W_EXC_HILIM  EQ  <DSRV$W_STATE+26>

	CLRQ	(R1)+			; Zero include and exclude ranges
	MOVAL	DSRV$L_UNITS(R5),R1	; Get the address of the unit table
	MOVL	#DSRV$K_MAX_UNITS,R0	;  and the total number of entries
10$:	CLRL	(R1)+			; Clear the entry for each UQB
	SOBGTR	R0,10$			;  so they all start out as zero
	CLRL	DSRV$L_SPLITXFER(R5)	; Start with all counters set to zero

;
; Get the number of pagelets specified in the sysgen parameter for buffer size,
; and convert it into bytes. Bytes rounded to the next highest page boundry
; is used for buffer sizes throughout the rest of the server.
;
	ASHL	#VA$S_BYTES_PER_PAGELET,-; Get the number of buffer pages
		G^CLU$GL_MSCP_BUFFER,R1	;  Specified and convert it to bytes
	ASHL	#-3,R1,-		; Calculate part of MIN constant based
		DSRV$L_BUFFER_MIN(R5)	;  on buffer size  C=buffer/8

;
; Add the length of the data structure header to the size of the buffer to be 
; allocated. This avoids having to maintain a constant for the length of the 
; srvbuf header.
;
	MOVAL	SRVBUF$L_BUFF_START(R1),R1; Allow for a header
	JSB	G^EXE$ALONONPAGED	; Allocate the memory for buffers
	BLBS	R0,20$			; No errors, continue

	CLRL	G^SCS$GL_MSCP		; Leave evidence that init failed
	POPR	#^M<R1,R2,R5>		; Restore the registers
	RSB				; R0 contains insfmem from subroutine

;
; Set up all the pointers that have to do with the buffer pool. After 
; returning from pool allocation, R1 contains the number of bytes actually
; allocated, and R2 contains the address of the start of the allocated region.
;
20$:	MOVL	R2,DSRV$L_SRVBUF(R5)	; Save the address of the buffer
	MOVL	R1,SRVBUF$L_SIZE(R2)	; Save the size of the buffer

	ASSUME	SRVBUF$L_BLINK  EQ  SRVBUF$L_FLINK+4
	ASSUME	SRVBUF$L_BUFF_START  EQ  16

	CLRQ	SRVBUF$L_FLINK(R2)	; Clear FLINK and BLINK
	MOVB	#DYN$C_DSRV,-		; Define the type of data structure
		SRVBUF$B_TYPE(R2)	;  as belonging to the disk server
	MOVB	#DYN$C_DSRV_IOBUF,-	; Define the subtype as the local
		SRVBUF$B_SUBTYPE(R2)	;  I/O buffer for the server
	CLRL	SRVBUF$L_BUFF_START(R2)	; Set up list head and the number of
	SUBL3	#16,R1,-		;  bytes remaining in the buffer
		SRVBUF$L_FREE_SIZE(R2)	; 
	MOVL	SRVBUF$L_FREE_SIZE(R2),-; Keep another record of the sum
		DSRV$L_AVAIL(R5)	;  of free buffer fragments
	MOVAL	SRVBUF$L_BUFF_START(R2),-; Set the pointer to the start of 
		DSRV$L_FREE_LIST(R5)	;  the buffer pool free area

;
; Initialize the list heads in the DSRV.
;
	MOVAL	DSRV$L_HQB_FL(R5),-	; Initialize the forward and
		DSRV$L_HQB_FL(R5)	;  backward pointers to point to
	MOVAL	DSRV$L_HQB_FL(R5),-	;  the HQB forward link
		DSRV$L_HQB_BL(R5)	; 
	CLRW	DSRV$W_NUM_HOST(R5)	; No hosts being served yet
	MOVAL	DSRV$L_UQB_FL(R5),-	; Initialize the forward and
		DSRV$L_UQB_FL(R5)	;  backward pointers to point to
	MOVAL	DSRV$L_UQB_FL(R5),-	;  the HQB forward link
		DSRV$L_UQB_BL(R5)	; 
	CLRW	DSRV$W_NUM_UNIT(R5)	; No hosts being served yet
	MOVAL	DSRV$L_MEMW_FL(R5),-	; Initialize the forward and 
		DSRV$L_MEMW_FL(R5)	;  backward pointers to point to
	MOVAL	DSRV$L_MEMW_FL(R5),-	;  the memory wait forward link
		DSRV$L_MEMW_BL(R5)	; 
	CLRW	DSRV$W_MEMW_CNT(R5)	; No HRBs in memory wait state yet
	CLRW	DSRV$W_MEMW_MAX(R5)	; Clear the max value for the queue
	CLRL	DSRV$L_MEMW_TOT(R5)	; Clear out the total counter
        CLRL    DSRV$L_HRB_TMO_CNTR(R5) ; Initialize HRB timeout counter

;
; Initialize load balancing fields
;
	ASSUME DSRV$W_LM_LOAD1   EQ DSRV$W_LOAD_AVAIL+8
	ASSUME DSRV$W_LBINIT_CNT EQ DSRV$W_LM_LOAD1+8
	ASSUME DSRV$L_LBREQ_TIME EQ DSRV$W_LBINIT_CNT+8
	ASSUME DSRV$L_LBMON_TIME EQ DSRV$L_LBREQ_TIME+4

	CLRQ	DSRV$W_LOAD_AVAIL(R5)	
	CLRQ	DSRV$W_LM_LOAD1(R5)	
	CLRQ	DSRV$W_LBINIT_CNT(R5)
	CLRQ	DSRV$L_LBREQ_TIME(R5)
	MOVAL	DSRV$L_HULB_FL(R5),-	; Initialize the forward and
		DSRV$L_HULB_FL(R5)	;  backward pointers to point to
	MOVAL	DSRV$L_HULB_FL(R5),-	;  the HULB forward link
		DSRV$L_HULB_BL(R5)	; 

;
; The MSCP version is set to zero. Host class drivers must supply a 
; MSCP version number when setting the controller characteristics. If
; a version of MSCP 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 os using their local value.
;                                         
	MOVW	#VERSION,-		; Set the server version number 
		DSRV$W_VERSION(R5)	;  to return in Set Unit Char
                                        ; Identify ourself as a controller
	MOVW	#<MSCP$M_CF_MLTHS!-	;  that is multihost,
		MSCP$M_CF_LOAD!-	;  provides load information,
		MSCP$M_CF_REPLC>,-	;  and performs bad block replacement.
		DSRV$W_CFLAGS(R5)	;  

;
; Uniquely identify this controller among all devices accessable via MSCP.
;
	MOVQ	G^SCS$GB_SYSTEMID,-	; Construct the controller ID using
		DSRV$Q_CTRL_ID(R5)	;  the system ID for the first three
	MOVB	#MSCP$K_CM_EMULA,-	;  words, then use an emulator for
		DSRV$Q_CTRL_ID+6(R5)	;  the controller model, and 
	MOVB	#MSCP$K_CL_CNTRL,-	;  define the device class as an
		DSRV$Q_CTRL_ID+7(R5)	;  MSCP controller.

;
; Create a 'PCB' for use by the cancel routines. Load our common driver
; return address into the PID field
;
	PUSHR	#^M<R1,R2,R3,R4>	; Save the current register contents
	MOVL	#PCB$K_LENGTH,R1
	JSB	G^EXE$ALONONPAGED	; Allocate the memory for buffers
	BLBC	R0,30$			; Error, skip PCB setup.
	MOVL	R2,DSRV$L_PCB(R5)	; Store the address of the PCB
	MOVC5	#0,#0,#0,R1,(R2)	; Zero out the entire PCB
	MOVL	G^SCS$GL_MSCP,R5	; Load the DSRV address
	MOVL	DSRV$L_PCB(R5),R0	;  and then the now cleared PCB.
	MOVAB	BACK,PCB$L_PID(R0)	; Load the pseudo-PID for all SRVIOs
30$:	POPR	#^M<R1,R2,R3,R4>	; Restore register contents

;			
; Establish a link with PORT_DRIVER
;
	LOCK	LOCKNAME=SCS,-		; Lock SCS access
		SAVIPL=-(SP),-		; Save IPL
		PRESERVE=NO		; Don't preserve R0
	LISTEN	MSGADR=L^LISTN,-	; Forwarding address
		ERRADR=L^LIS_ERR,-	; If an error occurs during the listen
		LPRNAM=L^SERV_NAME,-	; 
		PRINFO=L^SERV_INFO	; 

;
;	Notify the NISCA port driver that MSCP server is now loaded.  This
;	is done by setting the cell SCS$GL_MSCP_NEWDEV to a non-zero value.
;

	MOVAB	SCS$MSCP_CHECK_SERVICE, -	; Routine called by NISCA port drvr
		G^ SCS$GL_MSCP_NEWDEV		; to check for satellite system disk

;
; Start load monitoring thread
;
LM_INIT:				; Global for debug
	CMPL	G^CLU$GL_MSCP_SERVE_ALL,#2 ; If we are serving only local devices
	BNEQ	5$			;  allocation class settings don't matter
;
; The server is only serving local disks, so load balancing is not possible,
; and there is no point in running the load monitor thread. We will set the
; load available to a large negative number to avoid this server being 
; selected as a candidate for load balancing and exit.
;
	MOVW	#^XFFFF,-		; Indicate negative load available.
		DSRV$W_LOAD_AVAIL(R5)
	BRW	INIT_DONE		; And exit.

5$:	MOVW	#20,-			; Load balancing time interval
		DSRV$W_LM_INTERVAL(R5)
	JSB	FKB_INIT		; Allocate and initialize a fork block
	MOVL	R2,DSRV$L_LM_FKB(R5)	;  for load monitoring
	JSB	FKB_INIT		; And another for the load balance
	MOVL	R2,DSRV$L_LB_FKB(R5)	;  thread
	JSB	LM_INIT_CAPACITY	; Call routine to load server capacity
;
; Build a repeating TQE that will periodically wake up to perform
; sundry timer based functions, in particular the periodic load monitor.
; If it becomes desirable to run the timer for purposes other than load 
; balancing, the INIT_DONE label should be moved to this location.
;


	MOVL	R5,R4
	MOVZWL	#TQE$K_LENGTH,R1
	JSB	G^EXE$ALONONPAGED
	BLBS	R0,10$
	BRW	999$
10$:	MOVL	R2,R5
	MOVB	#DYN$C_TQE,-		; Set type	   	
		TQE$B_TYPE(R5)		;
	MOVB	#TQE$C_LENGTH,-		; Set length
		TQE$W_SIZE(R5)		;
	MOVB	#TQE$C_SSREPT,-		; Make this a repeating TQE.
		TQE$B_RQTYPE(R5)	;
  	MOVAB	G^MSCP$TMR,-		; Store address of wakeup routine
		TQE$L_FPC(R5)		       
	MOVQ	#MSCP_TIMER,-           
		TQE$Q_DELTA(R5) 	; Compute next timeout (5sec)
	READ_SYSTIME	R0		; Get current time
	ADDL	TQE$Q_DELTA(R5),R0	; Compute next timeout (5sec)
	ADWC	TQE$Q_DELTA+4(R5),R0	; for insertion into queue
	MOVX	R3,TQE$Q_FR3(R5)   	; Save R3, R4 in TQE
	MOVX	R4,TQE$Q_FR4(R5)

	CLRL	TQE$L_RMOD(R5)		; No AST required.

	JSB	G^EXE$INSTIMQ		; Insert into timer queue
	MOVL	G^EXE$GL_ABSTIM,-
		DSRV$L_LBMON_TIME(R4)
	MOVL	R4, R5			; Restore the DSRV address
	BRB	INIT_DONE

999$:	BUG_CHECK	DISKSERVE,FATAL	; No room!

INIT_DONE:
	UNLOCK	LOCKNAME=SCS,-		; Release SCS access
		PRESERVE=NO,-		; Don't preserve R0
		NEWIPL=(SP)+		;  and restore the IPL
	MOVL	#SS$_NORMAL,R0		; Set success
	.IF DEFINED DEBUG$LOG
	BSBW	CREATE_LOG		; Allocate MSCP packet logging buffer.
	.ENDC
	POPR	#^M<R1,R2,R5>		; Restore the registers 
	RSB				; Return to caller

;
; End of initialization code
;

	DECLARE_PSECT EXEC$NONPAGED_CODE,PAGE
;
; 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.
;
LIS_ERR:
	.JSB_ENTRY -
		INPUT=<R0,R1,R3,R4>, -
		SCRATCH=<R0,R1,R2,R3,R4,R5>
	DISCONNECT #1			; Do a disconnect
	RSB				;  and return

	.PAGE
	.SBTTL	-	SCS$ADD_DISK_UNIT - Add a Disk 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. A MSCP attention message is sent out to all 
; known hosts to notify them of the availability of this disk.
;
;
; Inputs:
;
;	R4	UQB address
;
; Outputs:
;
;	R0  =  status code to be returned to the caller
;
;-


;
; First, make sure that the server has already been loaded. If it is not
; loaded, return an error message stating that MSCP messages have been 
; received out of sequence.
;

;SCS$ADD_DISK_UNIT:
	UNIVERSAL_JSB	NAME=SCS$ADD_DISK_UNIT, -
			INPUT=<R4>, -
			OUTPUT=<R0>, -
			SCRATCH=<R1>

	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	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
	MOVL	G^SCS$GL_MSCP,R5	; Pick up the DSRV address
	MOVL	DSRV$L_HQB_FL(R5),R1	; Get the address of the first HQB
	MOVL	R1,R2			;  and save it off to the side
10$:	CMPL	(R1),R2			; Check for an empty queue
	BEQL	30$			; If no entries, we are done
	TSTL	HQB$L_CDT(R1)		; Check the HQB for a good CDT address
	BNEQ	20$			;  found a CDT, proceed.
	MOVL	HQB$L_FLINK(R1),R1	; Otherwise move on to the next host
	BRB	10$			;  and try again

20$:	BSBW	ALLOCATE_HRB		; Allocate and set up a HRB
					; IRP included automatically
	BLBC	R0,40$			; Return to the caller with the error
	MOVL	R4,HRB$L_UQB(R3)	; Save this address
	INCW	UQB$W_CURRENT(R4)	; Another request active on this unit
	BSBB	SEND_ATTENTION_MSGS	; Start sending attention messages
30$:	MOVL	#SS$_NORMAL,R0		; Attention notification process
					;   started or in progress
40$:	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 disk!
;
; This routine may return to its caller before all messages have
; been sent.
;
SEND_ATTENTION_MSGS:
	.JSB_ENTRY INPUT=<R1,R3>,SCRATCH=<R0,R1,R2,R3,R4,R5>
;
; The attention message will actually be sent to all hosts that are linked
; into the HQB queue in the DSRV. 
;
30$:	MOVL	R1,HRB$L_HQB(R3)	; Store the address of the HRB
	INSQUE	(R3),@HQB$L_HRB_BL(R1)	; Insert the HRB into the host's que
	INCW	HQB$W_NUM_QUE(R1)	; Bump host queue refrence count

	BBS 	#HQB$V_HUNN,-		; Branch if host understands new naming
		HQB$W_FLAGS(R1),35$	;  'cause we can serve it anything.
	MOVL 	HRB$L_UQB(R3),R4	; Host does not do new naming. Check:
	BBS	#UQB$V_DUNN,-		;  if device uses new naming, do not
		UQB$W_FLAGS(R4),60$	;  serve it to host.

35$:	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
	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
	MOVL	HRB$L_HQB(R3),R1	; Get the address of the HQB
	MOVW	UQB$W_SLUN(R4),-	; Get the new style unit number
		MSCP$W_UNIT(R2)		;  to return to it
	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 DSRV$V_LOG_ENABLD  EQ  0

	MOVL	G^SCS$GL_MSCP,R0	; Get the DSRV address.
	BLBC	DSRV$W_STATE(R0),50$	; Branch if logging is disabled.
	PUSHL	R0
	MOVL	#PKT$C_MSCP_END,R0
	BSBW	LOG_PKT		; Otherwise, log the attention message.
	POPL	R0
50$:
	.ENDC

	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
	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.
;
60$:	REMQUE	(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 refrence 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_DSRV(R5),R5	; Get the address of the DSRV structure
	MOVAL	DSRV$L_HQB_FL(R5),R0	; If the next HQB address does not 
	CMPL	R0,R1			;  match the queue list header,
	BEQL	90$			;  then we have another host to notify
	TSTL	HQB$L_CDT(R1)		; Before sending out the message, check
					;  for a valid CDT.
	BEQL	80$			; If zero, a local connection.
	BRW	30$			; Otherwise, send the message.

80$:	MOVL	R1,R5			; Get the next one off the list and
	BRB	70$			;  ignore the dying one.

90$:	BSBW	CLEANUP_HRB		; We are finished with this request
	BRW	UNBLOCK			;  and decrement the current counter

	.PAGE
	.SBTTL	-	SCS$DISK_MSCP_NEWDEV - Serve a new DSA device unit
;+
; Functional Description:
;
; This is an entry point provided for the disk class driver to call 
; directly with the address of a newly discovered UCB. This avoids the
; time delay that used to occur when the disk 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 (disk 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 DSRV$K_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
;-

;SCS$DISK_MSCP_NEWDEV::

	UNIVERSAL_JSB	NAME=SCS$DISK_MSCP_NEWDEV,-
			INPUT=<R5>,OUTPUT=<R0,R5>,PRESERVE=<R1>

	PUSHR	#^M<R1,R2,R3,R4>	; 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 complys with the 
; serving guidelines.
;

	TSTL	G^CLU$GL_MSCP_SERVE_ALL	; Is auto-serving turned on?
	BEQL	NORMAL			;  if not, just return

	CMPB	#DC$_DISK, -		; Make sure this device is a disk
		UCB$B_DEVCLASS(R5)	;  before we go any farther
	BNEQ	NOSERV			; Not a disk, get out now!
.iif ndf,DEV$M_MSCPSRV,DEV$M_MSCPSRV=134217728
.iif ndf,DEV$V_MSCPSRV,DEV$V_MSCPSRV=27 ; Same as DEV$V_FILL_2
	BITL	#<DEV$M_NOCLU -		; If this device 
		 !DEV$M_SRV -		;  is not cluster accessable,
		 !DEV$M_CDP -		;  is already served,
		 !DEV$M_VRT>, -		;  is a class driver path
		UCB$L_DEVCHAR2(R5)	;  or is a virtual unit UCB
	BNEQ	NOSERV			;   skip over it.
	BBS	#UCB$V_MSCP_INITING, -	; If this UCB is the temporary
		UCB$L_DEVSTS(R5), -	;  structure from IOGEN 
		NOSERV			;  don't serve it.


;
; Now we are dealing with a valid disk for serving. Check the allocation class
; settings.
; 

	MOVL	UCB$L_DDB(R5),R4	; Get the DDB address
	CMPL	G^CLU$GL_ALLOCLS,-	; Compare allocation class of host
		DDB$L_ALLOCLS(R4)	;  with allocation class of device

	BEQL	SERVIT			; OK if they are the same
;
; Allocation classes differ. If this is a Port Allocation Class
; then it's necessarily a local disk and can be served.
;
	BBC	#DDB$V_PAC,-		; If not port allocation class, then
		DDB$B_FLAGS(R4),-	;  classes don't match, can't serve
		NOSERV

SERVIT:

;
; Next test for the case of a DSA device that is dual pathed between two local
; controllers. If this is the case, the primary path for the second node that
; comes up will be through the MSCP server of the first node. The second node
; however, does want to advertize that it can also serve the device, so that if
; the node currently providing the primary path fails, the other nodes of the
; cluster can still access the disk through the second MSCP server.
; 
	BBC	#DEV$V_MSCP,-		; Br if not MSCP type disk
		UCB$L_DEVCHAR2(R5),30$	;
	MOVL	UCB$L_CDDB(R5),R0	; Get a pointer to the CDDB
	BEQL	NOSERV			;  if zero, don't serve the device
	CMPB	#MSCP$K_CM_EMULA,-	; If the controller type for this device
		CDDB$B_CNTRLMDL(R0)	;  is not an emulator,
	BNEQ	10$			;  the device is still OK.
	BBC	#DEV$V_2P,-		; If this device does not support
		UCB$L_DEVCHAR2(R5),-	;  dual paths,
		NOSERV			;  do not serve it.
	MOVL	UCB$L_2P_CDDB(R5),R4	; Get a pointer to the secondary CDDB
	BEQL	NOSERV			; Give up if there is no alternate
	CMPB	#MSCP$K_CM_EMULA,-	; If the alternate path
		CDDB$B_CNTRLMDL(R4)	;  is also an emulator,
	BEQL	NOSERV			;  give up, and don't serve this device

	; Check for local disk or HSC/RF

10$:	BBC	#MSCP$V_CF_MLTHS,-	; Br if not Multi-host. eg. KDM
		CDDB$W_CNTRLFLGS(R0),30$
	;
	; Must have an HSC or RF type disk
	;
	CMPL	G^CLU$GL_MSCP_SERVE_ALL,#2 ; Serving only locally attached disks?
	BEQL	NOSERV			; Branch if so
	TSTL	G^CLU$GL_ALLOCLS	;  then the allocation class of this
	BEQL	NOSERV			;  node must be nonzero.
	;
	; Serve the disk
	;
30$:	MOVL	G^SCS$GL_MSCP,R4	; Get the address of the DSRV 
	MOVZWL	DSRV$W_NUM_UNIT(R4),R0	; Get the next free unit number
	CMPL	R0,#DSRV$K_MAX_UNITS	;  and see if we are over the max
	BGEQU	FULL			;  if we are, return an error

	CLRL	R3			; Init this reg for table index later
	BBC	#DEV$V_MNT,-		; If the disk is not mounted yet,
		UCB$L_DEVCHAR(R5),40$	;  we can begin serving it
	BBC	#DEV$V_CLU,-		; If this disk is mounted, but
		UCB$L_DEVCHAR2(R5),-	;  accessable to the cluster... OK
		NOTSHR			;  otherwise its not shareable

;
; 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.
;

40$:	MOVL	DSRV$L_UNITS(R4)[R3],R1	; Get UQB address from the table
	BEQL	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	40$			; No match, keep looking
	BRW	NORMAL			; They already have what they want

FULL:	MOVL	#SS$_DEVICEFULL,R0	; There are already MAX_UNITS
	BRW	RESTOR			;  being served
NOTSHR:	MOVL	#SS$_DEVNOTDISM,R0	; This device is not mounted for 
	BRW	RESTOR			;  cluster wide access
NOMEM:	MOVZWL	#SS$_INSFMEM,R0		; Not enough memory to allocate
	BRW	RESTOR			;  a decent UQB

NOSERV:	
NORMAL:	MOVL	#SS$_NORMAL,R0		; They already have what they want
RESTOR:	POPR	#^M<R1,R2,R3,R4>	; Restore all the registers used 
	RSB				;  and return to the caller


;
; There is no UQB for a device with this unit number.
;
CREATE_UQB:
	MOVZWL	#UQB$C_LENGTH,R1	; Set size of the UQB structure
	JSB	G^EXE$ALONONPAGED	; Grab some pool
	BLBC	R0,NOMEM		; If there was insf memory for alloc

;
; 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_DSRV,-		; This is the disk based server
		UQB$B_TYPE(R2)		;  data structure
	MOVB	#DYN$C_DSRV_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
	CLRW	UQB$W_FLAGS(R2)		; Begin with no flags set
	MOVW	UCB$W_UNIT(R5),-	; Get the "real" unit number
		UQB$W_UNIT(R2)		;  (the one off the plug)
	MOVAL	UQB$B_ONLINE(R2),R0	; Get the address of the online bitmap
	MOVL	#MAX_HOSTS,R1		;  which has one bit for each host
	DIVL	#8,R1			;  calculate the number of bytes

40$:	CLRB	(R0)+			;  and clear out the bitmap
	SOBGTR	R1,40$			;  assuming an even number of bytes
	MOVL	UCB$L_DDB(R5),R0	; Get the device data block

	; Set UQB$M_DUNN if device uses new naming ("Port Allocation Classes")

	BBC	#DDB$V_PAC,-		; Branch if standard naming
		DDB$B_FLAGS(R0),42$	;
	BISW	#UQB$M_DUNN,-		; PAC set means uses new naming, so
		UQB$W_FLAGS(R2)		;  set "Device Uses New Naming" flag

42$:	MOVL	DDB$L_ALLOCLS(R0),-	; Move the allocation class into
		UQB$L_ALLOCLS(R2)	;  the unit ID field
	MOVW	DSRV$W_NUM_UNIT(R4),-	; Then assign the local unit
		UQB$W_SLUN(R2)		;  number (unit table index)
	CLRW	UQB$W_DEVNAME(R2)	; Clear out devname to start
	BISW	#MSCP$M_SLUN,-		; Set the bit in the unit number
		UQB$W_SLUN(R2)		;  indicating a server local unit
	INCW	DSRV$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,DSRV$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

;
; Set the state of the UQB unit flags field for
;  MSCP and non MSCP devices.
;

        BBS     #DEV$V_MSCP,-           ; BS means a MSCP disk,
                UCB$L_DEVCHAR2(R5),50$  ;  branch to set unit flags.

; Check the non MSCP devices
        CLRW    UQB$W_UNIT_FLAGS(R2)    ; Clear unit flags.
        BISW    #MSCP$M_UF_REPLC,-      ; Always make sure the bad block
                UQB$W_UNIT_FLAGS(R2)    ;   replacement flag is set.
        BBC     #DEV$V_NOFE,-           ; No forced error support?
                UCB$L_DEVCHAR2(R5),53$  ;  BC means has support.
        BICW    #MSCP$M_UF_REPLC,-      ; Signal class driver no
                UQB$W_UNIT_FLAGS(R2)    ;  forced error support.
        BRB     53$                     ; Skip other unit flags setup.

50$:	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
	BISW	#MSCP$M_UF_REPLC,-	; Always make sure the bad block 
		UQB$W_UNIT_FLAGS(R2)	;  replacement flag is set 
53$:	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$:	CLRW	UQB$W_CURRENT(R2)	; No commands current for this unit
	CLRW	UQB$W_NUM_QUE(R2)	; Start with no requests pending
	CLRW	UQB$W_MAX_QUE(R2)	;  in the blocked queue for this dev
	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
	CLRL	UQB$L_EXTRA_IO(R2)	; Clear extra I/Os for this unit
	CLRL	UQB$L_IOCNT(R2)		; Total server I/Os for this disk
	CLRW	UQB$W_QLEN(R2)		; Server contribution to queue length
;
; Call IOC$CVT_DEVNAM to get a cluster unique name string for the device, save
; it in the UQB as a counted ascii string.
;
	PUSHL	R4			; Save DSRV addr
	MOVL	#1,R4			; indicate "ALLDEVNAM" required
	MOVAB	UQB$T_UNIQUE_DNAME(R2),- ; Pass address to store name string
		R1			;   in R1
	MOVL	#UQB$S_UNIQUE_DNAME,R0	; and max length in R0
	JSB	G^IOC$CVT_DEVNAM	; get the name in the UQB
	MOVB	R1,-			; and the length returned
		UQB$B_UNIQUE_DNAME_CNT(R2)
	POPL	R4			; Restore DSRV addr
;

	INSQUE	(R2),@DSRV$L_UQB_BL(R4)	; Insert the UQB into the queue

;
; Go off to MSCP to ADD a disk unit. When the ADDUNIT routine is eliminated,
; change this call format to use a simple JSB and leave the stack alone. 
;
	MOVL	R2,R4			; Copy UQB pointer
	JSB	SCS$ADD_DISK_UNIT	; Go to the routine
	BRW	NORMAL			; Return a successful status

	.PAGE
	.SBTTL	-	SCS$DISK_MSCP_MV - 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 disk is passed to this routine in
; R5. The corresponding UQB for this device is found and the
; device status for all the client nodes is changed to "offline".
;
;
; Inputs:
;
;	R5  =  UCB address
;
; Outputs:
;
;	None - all registers preserved
;-

;SCS$DISK_MSCP_MV::
	UNIVERSAL_JSB	NAME=SCS$DISK_MSCP_MV, -
			INPUT=<R5>,PRESERVE=<R0,R1,R2,R4>	

	LOCK	LOCKNAME=SCS,-		; Lock SCS access
		PRESERVE=NO,-		;  no need to preserve R0
		SAVIPL=-(SP)		;  save the current IPL
	JSB	MV_COMMON
	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
	RSB				; And resume mount verification

;SCS$DISK_MSCP_DRIVER_MV::
	UNIVERSAL_JSB	NAME=SCS$DISK_MSCP_DRIVER_MV, -
			INPUT=<R5>,PRESERVE=<R0,R1,R2,R4>	

	JSB	MV_COMMON
	RSB				; And resume mount verification


MV_COMMON:
	.JSB_ENTRY	INPUT=<R5>,-
			PRESERVE=<R0,R1,R2,R4>
	BBS	#UCB$V_CLUTRAN,-	; If we are here as a result of a cluster
		UCB$L_STS(R5),70$	;  transition, exit now.
	MOVL	G^SCS$GL_MSCP,R1	; Get the address of the server
	MOVAL	DSRV$L_UQB_FL(R1),R4	; Get the queue list head address
	MOVL	R4,R2			;  and save it to verify the whole
10$:	MOVL	UQB$L_FLINK(R4),R4	;  queue has been searched
	CMPL	R4,R2			; End of UQB list?
	BEQL	70$			; 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 bitmap
; is now searched where each bit that is set represents a client node
; that currently holds the device in an online state. For each host that
; has the device online, the corresponding bit in the bitmap is cleared,
; and the online count appropriately decremented.
;
	MOVAL	UQB$B_ONLINE(R4),R1	; R0 -> online bitmap
	SKPC	#0,#MAX_HOSTS/8,(R1)	; If there are no hosts online
	BEQL	70$			;  exit without changing the unit state
	BRB	25$			; No need to repeat the SKPC...
20$:	MOVAL	UQB$B_ONLINE(R4),R1	; R0 -> online bitmap
	SKPC	#0,#MAX_HOSTS/8,(R1)	; If there are no more hosts online
	BEQL	60$			;  we are finished looking, cont...
25$:	FFS	#0,#8,(R1),R0		; Find host online
	BEQL	55$			; If eql problem with SKIPC
	BBCC	R0,(R1),30$		; Clear this hosts bit
30$:	BBC	#DEV$V_MSCP,-		; If this isn't a MSCP device, it
		UCB$L_DEVCHAR2(R5),40$	;  can't be a shadow set.
	BBS	#MSCP$V_SHADOW,-	; If this is a shadow set virtual unit
		UCB$W_MSCPUNIT(R5),50$	;  don't change the online count
40$:	DECB	UCB$B_ONLCNT(R5)	;  and note the change in online count
50$:	TSTB	UCB$B_ONLCNT(R5)	; Set the condition codes
	BGEQ	20$			; Look for the next bit in the map

55$:	BUG_CHECK MSCPSERV, FATAL	; Online count should NEVER be negative

;
; 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.
;
	ASSUME	UQB$K_ST_OFFLINE    EQ  MSCP$K_ST_OFFLN
60$:	MOVW	#UQB$K_ST_OFFLINE,-	; Now set the state of this device to
		UQB$W_STATE(R4)		;   reflect the sum of individual states

70$:	RSB

	.PAGE
	.SBTTL	-	DRIVER_STALL - Notify server that an IRP is stalled.
	.SBTTL	-	DRIVER_UNSTALL - Notify server that an IRP has resumed
;+
; Functional Description:
;
; This routine locates the HRB for the IRP passed in R5 and updates the 
; state in the HRB to indicate that the request is stalled on the UCB pending
; queue, or if the HRB state inidicates that the request was on the UCB
; pending queue, it returns the HRB state to DRV_WAIT.
;
; The server uses the UCB stall state to identify requests that can be removed
; immediately in the event of a connection failure. Since they do not own
; any resources yet, the corresponding IRPs can be dequeued and deleted.
;
; Only requests in DRV_WAIT state can enter UCB_STALL state.
; Only requests in UCB_STALL state can enter DRV_WAIT state.
;
; Inputs:
;
;	R5  =  CDRP address
;
;	SCS spinlock held.
;
; Outputs:
;
;	None - all registers preserved
;-

;DRIVER_STALL::
	UNIVERSAL_JSB	NAME=SCS$DISK_DRIVER_STALL, -
			INPUT=<R5>,PRESERVE=<R0>	

	MOVL	CDRP$L_HRB(R5),R0	; Pick up the HRB address
	CMPW	#HRB$K_ST_UCB_STALL,-	; If driver state is already at
		HRB$W_STATE(R0)		;  stall state, ingore this request
	BEQL	98$
	CMPW	#HRB$K_ST_DRV_WAIT,-	; Check the drive wait state, and panic
		HRB$W_STATE(R0)		;  if it was not set.
	BNEQ	99$
	MOVW	#HRB$K_ST_UCB_STALL,-	; Set the UCB stall state, and panic if
		HRB$W_STATE(R0)		;  if it was already set.
98$:	RSB

99$:	BUG_CHECK	DISKCLASS, FATAL

;DRIVER_UNSTALL::
	UNIVERSAL_JSB	NAME=SCS$DISK_DRIVER_UNSTALL, -
			INPUT=<R5>,PRESERVE=<R0>	

	MOVL	CDRP$L_HRB(R5),R0	; Pick up the HRB address
	CMPW	#HRB$K_ST_DRV_WAIT,-	; If driver state is already at
		HRB$W_STATE(R0)		;  drive wait state, ignore this request
	BEQL	98$
	CMPW	#HRB$K_ST_UCB_STALL,-	; Check the UCB stall state, and panic
		HRB$W_STATE(R0)		;  if it was not set.
	BNEQ	99$
	MOVW	#HRB$K_ST_DRV_WAIT,-	; Set/restore the drive wait state,
		HRB$W_STATE(R0)		;  ignoring previous state
98$:	RSB

99$:	BUG_CHECK	DISKCLASS, FATAL

	.PAGE
	.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:                                 
;	R2  =  Connect message packet address
;	R3  =  CDT
;	R4  =  PDT
;
; Outputs:
;	None (the connection is either accepted or rejected)
;
; R0-R1 may be destroyred
;-

LISTN:
	.JSB_ENTRY INPUT=<R2,R3,R4>,SCRATCH=<R1,R0>

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

;
; Check for incompatible Class Drivers (pre-V5).
;
	MOVL	<SCS$B_CON_DAT-SCS$T_DST_PROC>-; Get first four characters of
		(R2),R0			; connect data into R0.
	CMPL	R0,#^A/DU00/		; Is it compatible with this server?
	BEQL	10$			; If EQL, compatible, proceed.
	CMPL	R0,#^A/V5.0/		; Is it a 5.0 or 5.1 driver?
	BEQL	10$			; If EQL, yes.  Supported until 5.3.
	REJECT	#SS$_ABORT		; Otherwise, reject request.
	RSB				; Exit thread.
;
; Allocate an HQB for this host.
;
10$:	MOVL	G^SCS$GL_MSCP,R1	; Are we ready to serve devices?
	BBS	#DSRV$V_CONFIG_WAIT,-	; If not, reject the connect
		DSRV$W_STATE(R1),12$	;  request.
	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
	BLBS	R0,15$			; Continue if successful
	POPL	R2			; Pop the saved address
12$:	REJECT	#1			; If we were not able to satisfy this 
	RSB				;  memory request, reject the request

;
; Fill in as many fields as possible to initialize the HQB for use.
;
15$:	MOVL	R2,R5			; Get the HQB address in R5
	POPL	R2			; Pop the saved connect message address
	MOVW	R1,HQB$W_SIZE(R5)	; Set length of the data structure
	MOVB	#DYN$C_DSRV,-		; The structure type
		HQB$B_TYPE(R5)		;  is disk server
	MOVB	#DYN$C_DSRV_HQB,-	; The structure subtype
		HQB$B_SUBTYPE(R5)	;  is Host Queue Block
	CLRB	HQB$B_STATE(R5)		; Start off with a clear state
	CLRW	HQB$W_FLAGS(R5)		;  and no flags set

	; Set HQB$M_HUNN if host understands new naming

	MOVB	<SCS$B_CON_DAT--
		 SCS$T_DST_PROC+14>-	; Get byte 14 of
		(R2),R0			;  the SCS connect data into R0.
	CMPB	R0,#^A' '		; Blank?
	BEQL	17$			; Yes, doesn't know new naming
	BISW	#HQB$M_HUNN,-		; No, understands new naming
		HQB$W_FLAGS(R5)

17$:	CLRW	HQB$W_NUM_QUE(R5)	; Clear the pending request count
	CLRW	HQB$W_MAX_QUE(R5)	; Clear the maximum pending count
	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	G^SCS$GL_MSCP,R1	; Get the DSRV address
	MOVL	R3,HQB$L_CDT(R5)	; Save away the CDT address
	MOVL	R1,HQB$L_DSRV(R5)	;  and store it away in the HQB

;
; Find the first clear bit position in the mask of hosts being served.
; the bit position that is found will be the host number for this host.
;
	CLRL	R0			; Base index in bit array (longword ptr)
20$:	CMPL	R0,#MAX_HOSTS		; Make sure we are not searching
	BNEQ	25$    			;  beyond the end of the table
	BRW	NO_ROOM
25$:	FFC	R0,#32,-		; Starting at bit R0 for 32 bits
		DSRV$B_HOSTS(R1),R0	;  put the bit number found in R0
	BEQL	20$			; No bit found, check the next longword
	BBSS	R0,DSRV$B_HOSTS(R1),30$	; Set the first good one we found,
30$:	MOVB	R0,HQB$B_HOSTNO(R5)	;  and save the bit number in the HQB
	MOVW	#60,HQB$W_HTIMO(R5)	; Default host access timeout 

;
; Allocate the HULB vector
;

	PUSHL	R2			; Save connect message address.
	MOVZWL	#DSRV$K_MAX_UNITS,R1	; Get the initial size of the HULB vector
	ASHL	#2,R1,R1		; Convert the count from longwords to bytes
	JSB	G^EXE$ALONONPAGED	;  and allocate that much memory
	BLBS	R0,33$
	.IF DEFINED DEBUG$REJECT_TRACKING
	MOVL	G^SCS$GL_MSCP,R1	; Restore the DSRV address.
	MOVL	R0,DSRV$L_REPLC_CNT+8(R1); Save away the error code
	INCL	DSRV$L_REPLC_CNT+12(R1)	; Save away a record of the reject
	.ENDC	   ;DEBUG$REJECT_TRACKING
	POPL	R2			; Restore connect message address.
	BRB	35$			; Branch to error handling following
					;  ACCEPT as it knows to deallocate
					;  the HQB and clean up.
33$:	MOVL	R2,HQB$L_HULB_VECTOR(R5); Store the HULB vector address
	MOVW	#DSRV$K_MAX_UNITS,-	;  and size in the HQB
		HQB$W_MAX_HULB(R5)
	MOVZWL	#DSRV$K_MAX_UNITS,R0	; Setup size to clear out the vector
34$:	CLRL	(R2)+			; Clear the entry for each UQB
	SOBGTR	R0,34$			;  so they all start out as zero




	MOVL	G^SCS$GL_MSCP,R1	; Restore the DSRV address.
	POPL	R2			; Restore connect message address.

;
; ACCEPT the connection request, and link in the HQB.
;		R3  =  CDT address
;		R4  =  PDT address
;
	ACCEPT	MSGADR=MSG_IN,-		; Message input address
		ERRADR=VC_ERR,-		; Virtual circuit error address
		INITCR=G^CLU$GL_MSCP_CREDITS,- ; Initial credit extended
		MINSCR=#1,-		; Minimum send credit
		CONDAT=DSRV$W_VERSION(R1),- ; CONNECT/ACCEPT data
		AUXSTR=(R5),-		; HQB address
		MOVADR=VC_PATH_MOVE,-	; SCS load sharing path move
		LOAD_RATING=#INITIAL_LOAD_RATING	; Load rating for above

	BLBS	R0,40$			; If acceptance was unsuccessful,
	MOVL	HQB$L_CDT(R5),R3	;  restore the CDT address and 
	.IF DEFINED DEBUG$REJECT_TRACKING
	MOVL	R0,DSRV$L_REPLC_CNT+16(R5); Save away the error code
	INCL	DSRV$L_REPLC_CNT+20(R5)	;  and keep a count	
	.ENDC	   ;DEBUG$REJECT_TRACKING

35$:	REJECT	#1			;  reject the request. Then,
	MOVL	G^SCS$GL_MSCP,R1	;  restore the DSRV address,
	MOVZBL	HQB$B_HOSTNO(R5),R0	;  clear out the bit set in the host
	BBCC	R0,DSRV$B_HOSTS(R1),37$	;  table, so another can try to connect
37$:	MOVL	R5,R0			; Address of the structure to deallocate
	JSB	G^EXE$DEANONPAGED	; Free the memory allocated to the HQB
	RSB

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

	RSB

NO_ROOM:
	.IF DEFINED DEBUG$REJECT_TRACKING
	MOVL	R0,DSRV$L_REPLC_CNT+24(R5); Save away the bit number
	INCL	DSRV$L_REPLC_CNT+28(R5)	;  and keep a count	
	.ENDC	   ;DEBUG$REJECT_TRACKING
	REJECT	#1			; Reject the connect application
	MOVL	R5,R0			; Address of the structure to deallocate
	JSB	G^EXE$DEANONPAGED	; Free the memory allocated to the HQB
	RSB				;  and return to the caller

	.PAGE
	.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 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.
;
; The PATH_MOVE address is also supplied to the connect call and is used
; if SCS decides to move this connection to another port for load balancing
; The sequence of events is identical to that for VC failure other than we
; send a different disconnect code so that the class driver knows to prevent
; failing over units to a different server.
;
;-

VC_PATH_MOVE:
;
; Inputs:
;
;	R3  =  CDT address
;	R4  =  PDT address
;
; Outputs:
;	
;	None
;

;	.JSB_ENTRY INPUT=<R3,R4>,SCRATCH=<R0,R1,R2,R3,R4,R5>
	.JSB_ENTRY INPUT=<R3,R4>,SCRATCH=<R0,R1,R2>

	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	CDT$L_AUXSTRUC(R3),R5	; Pick up the HQB address
	BISB	#HQB$M_PATHMOVE,-	; Remember path move state so we can
		HQB$B_STATE(R5)		;  disconnect with the appropriate 
	BRB	VC_ERR_COMMON

VC_ERR_ABORT:
	RSB				; Out of line return to caller this
					;  connection is already being terminated
VC_ERR:
;
; Inputs:
;
;	R3  =  CDT address
;	R4  =  PDT address
;
; Outputs:
;	
;	None
;	R0-R5 may be destroyed
;
;	.JSB_ENTRY INPUT=<R3,R4>,SCRATCH=<R0,R1,R2,R3,R4,R5>
	.JSB_ENTRY INPUT=<R3,R4>,SCRATCH=<R0,R1,R2>

	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	CDT$L_AUXSTRUC(R3),R5	; Pick up the HQB address

VC_ERR_COMMON:
	BBSS	#HQB$V_DISCON_INIT,-	; If this system is VERY low on NPP
		HQB$B_STATE(R5),-	;  this routine could be called from
		VC_ERR_ABORT		;  within the server (see MSG_IN)
	MOVL	HQB$L_DSRV(R5),R1	; Get the address of the server struct
	INCL	DSRV$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-		; Find the CDRPs in this queue for this
		action = UNHOOK_CDRP	;  CDT and if HRB matches, dequeue it
	SCAN_RDT-			; If request waiting for message buffer
		action = UNHOOK_CDRP	;  this scan will find it
	SCAN_MSGBUF_WAIT- 		; If request is waiting for msgbuf
		action = UNHOOK_CDRP	;  this scan will find it
	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.
;
	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
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,   40$>,-	; These two requests are within our 
		<BUF_WAIT,   50$>,-	;  domain and can be yanked immediately
		<SNDAT_WAIT, 60$>,-	; Sending or receiving data
		<DRV_WAIT,   33$>,-	; Flag the IRP and wait til driver's done for this one
		<MAP_WAIT,   65$>,-	; The only way to get this is to yank it
                <SUC_STALL,  35$>,-     ; Get Fork Block for this state.
		<UCB_STALL,  45$>,-	; Retrieve the IRP from the UCB's pending queue
                >                       
                                               
	BUG_CHECK MSCPSERV,FATAL	; Bugcheck for UNMAP_WAIT,
					; SNDMS_WAIT, MEM_WAIT, FLUSHED,
					; CACHED, or unused status.
;
; Request has been handed off to the local driver. Set the SRV_ABORT flag in the
; corresponding CDRP. Multithreaded drivers will detect this flag and cleanup the
; I/O request.
;
33$:	MOVL	HRB$L_IRP_CDRP(R1),R0	; Locate the IRP for the local driver.
	BISL	#IRP$M_SRV_ABORT,-	; Set the server abort flag. Some drivers
		IRP$L_STS(R0)		;  will complete the I/O immediately if this
					;  is set.
;
; Do NOT set any other flags in the HRB as the state (DRV_WAIT) is still
; valid. We have to wait for the driver to complete it's processing of the
; request before we can complete cleanup.
;
	BRW	30$

35$:    ; Fork block active for this HRB

	MOVL    HRB$L_RECORD(R1), R0    ; Get FKB address.
	REMQUE  (R0), R0
	PUSHR	#^M<R1,R2>
	JSB     G^EXE$DEANONPAGED       ; Deallocate.
	POPR	#^M<R1,R2>
	BRW     60$                     ; Join common ending.

;
; 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.
;
40$:	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVAL	HRB$L_WAIT_FL(R1),R0	; Get the forward link of the entry
	REMQUE	HRB$L_FLINK(R0),R0	;  and remove it from the wait queue.
	MOVL	HRB$L_UQB(R1),R0	; Get the address of the UQB
	DECW	UQB$W_NUM_QUE(R0)	;  so we can update the counter
	BRB	60$			; Join up with the common ending
;
; This command has been stalled in the driver's I/O start routine, presumably
; waiting for send credits or other system resources. The CDRP is queued to
; the UCB's pending, via CDRP$L_IOQFL/BL. These CDRPs are dequeued and
; deallocated to prevent stale I/O from restarting some time in the future.
; The originating client will reissue the I/O when it locates a path.
;
45$:	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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
	TSTL	IRP$L_IOQBL(R0)		; Check for the presence of a pointer
	BEQL	46$			; If the field is 0 don't dequeue
	CMPL	@IRP$L_IOQBL(R0),R0	; If the field is nonzero,
	BNEQ	46$			;  make sure it is in a queue
	REMQUE	IRP$L_IOQFL(R0),R0	; Remove the CDRP from its queue
	BISW	#HRB$M_UNBLOCK,-	; Mark this request to call unblock... 
		HRB$W_FLAGS(R1)		;  to decrement UQB$W_CURRENT
	BRB	60$			; Merge below

46$:	BUG_CHECK	DISKSERVE,FATAL	
; 
; This command was waiting for a buffer out of the server's local space.
; It can be removed from the memory wait queue.
; 
50$:	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVAL	HRB$L_WAIT_FL(R1),R0	; Get the forward link of the entry
	REMQUE	HRB$L_FLINK(R0),R0	;  and remove it from the wait queue
	MOVL	HRB$L_HQB(R1),R0	; Get the address of the HQB
	MOVL	HQB$L_DSRV(R0),R0	;  so we can find the server structure
	DECW	DSRV$W_MEMW_CNT(R0)	;  and adjust the counter

;
; Now all the request specific processing is done, do some common processing
; here, setting the flags and status bits.
;
60$:	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	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
	BRW	30$			; Check for more HRBs to process

65$:
        PUSHR   #^M<R5>                 ; Save HQB
        MOVL    HRB$L_IRP_CDRP(R1),R0   ; Get the address of the IRP-CDRP
        MOVAL   IRP$L_FQFL(R0),R5       ;  and move down to the CDRP portion
        IDLE_CDRP                       ; Remove the CDRP from its queue
        POPR    #^M<R5>                 ; Restore HQB
        BLBC    R0,30$                  ; BR if CDRP not queued anywhere
	BISW	#HRB$M_UNBLOCK,-	; Mark this request to call unblock... 
		HRB$W_FLAGS(R1)		;  to decrement UQB$W_CURRENT
	BRB	60$			; Merge above
	
;
; 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 command 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$
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
	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 disk 
; driver) can be deallocated.
;

90$:
	BBS	#HQB$V_PATHMOVE,-	; If we are not here as a result of
		HQB$B_STATE(R5), 95$	;  a port load balance request, the
	DISCONNECT			;  disconnect can be issued now with
	BRB	99$			;  no special status.

95$:					; Disconnect, sending path move status
					; to the remote class driver.
	DISCONNECT	#SCS$C_USE_ALTERNATE_PORT
	BICB	#HQB$M_PATHMOVE,-	; From now on, we do not care why we
		HQB$B_STATE(R5)		;  are disconnecting.

99$:	CLRL	HQB$L_CDT(R5)		; This 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_DSRV(R5),R4	; Get the address of the server struct
	MOVAL	DSRV$L_UQB_FL(R4),R2	; Loop through the UQBs making
	MOVL	R2,R4			;  any units available that have this
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!
	MOVZBL	HQB$B_HOSTNO(R5),R0	; Get the host number from the HQB
	BBC	R0,UQB$B_ONLINE(R4),100$; If unit is not online check next

;
; A unit was found that still thinks the host is out there. Begin cleanup of
; outstanding I/O, if any, by scanning the device's pending I/O queue for stalled
; IRPs from the server for this host.
;
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers across pending cleanup
					;  and cancel call to follow.
	MOVL	UQB$L_UCB(R4),R5	; Setup to scan pending queue and post
	MOVL	G^SCS$GL_MSCP,R3	;  and IRPs that we sent on behalf of 
	MOVL	DSRV$L_PCB(R3),R4	;  this host.
	BEQL	1070$			; If PCB was not allocated, skip over
;
; R0 = Channel (host number)
; R5 = UCB
; R4 = PCB
; R1,R2,R3 scratch
;
	MOVAB	UCB$L_IOQFL(R5),R1	; Get address of I/O queue listhead
	MOVL	R1,R2			; Copy address of I/O queue listhead
1020$:	MOVL	IRP$L_IOQFL(R2),R2	; Get address of next I/O packet in queue
	CMPL	R2,R1			; End Of Queue?
	BEQL	1060$			; If Eql Yes
	CMPL	IRP$L_PID(R2),PCB$L_PID(R4) ; Process ID Match?
	BNEQ	1020$			; If Neq No
	CMPL	R0,IRP$L_CHAN(R2)	; I/O Channel(host) Number Match?
	BNEQ	1020$			; If Neq No
	MOVL	IRP$L_IOQBL(R2),R2	; Get Backward Link Of Current Entry
	REMQUE	@IRP$L_IOQFL(R2),R3	; Remove I/O Packet From Queue
	PUSHR	#^M<R0,R1>
	MOVZWL	#SS$_CANCEL,R0		; Setup completion status
	CLRL	R1
; 
; R0,R1 = status
; R5 => UCB
; R3 => IRP
;
	CALL_POST_NOCNT			; Post the IRP

	POPR	#^M<R0,R1>
	BRB	1020$			; Go back and look for more

;
; Now we will call the driver at it's cancel entry point to run down any active
; IRPs, if possible. Note that even though we issue the cancel, the driver may not
; be able to actually cancel the active IRPs.
;
; Setup arguments for cancel.
;
1060$:	PUSHL	#CAN$C_MSCPSERVER	; Reason (server cancel)
	PUSHL	R5			; UCB
	PUSHL	R4			; PCB
	PUSHL	R3			; IRP to cancel
	PUSHL	R0			; Channel (host)
	MOVL	UCB$L_DDT(R5),R5	; Pickup DDT address
	CALLS	#5,@DDT$PS_CANCEL_2(R5)	;  and call the device cancel routine

1070$:	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Restore registers for normal HQB cleanup
;
; We are done cleaning up I/O in DRV_WAIT state. We will now synchronise on the
; the completion of driver I/O for this host by allocating an HRB and processing
; it as an available command, which will stall until all precedding I/O has completed.
;
	BSBW	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
	BLEQU	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	PCHIST			; 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_MSCP,R4	; Get the address of the server struct
	INCL	DSRV$L_OPCOUNT(R4)	;  then set the count of total operations
	INCL	DSRV$L_OPCOUNT(R4)[R1]	;  and the count of avails straight
	BSBW	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$:  	MOVZBL	HQB$B_HOSTNO(R5),R0	; Get the host number from the HQB
	BBCC	R0,UQB$B_ONLINE(R4),100$;  and clear out the online bit
	MOVL	UQB$L_UCB(R4),R0	; Get the UCB address
	BBC	#DEV$V_MSCP,-		; If this isn't a MSCP device,
		UCB$L_DEVCHAR2(R0),130$	;  it can't be a shadow set.
	BBS	#MSCP$V_SHADOW,-	; If this is a shadow set virtual unit
		UCB$W_MSCPUNIT(R0),140$	;  don't change the online count
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	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	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 disk
		HRB$W_STATE(R3)		;  then continue waiting...
	BEQL	150$			;  continue traversing the queue...
	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 the mapping
		HRB$W_STATE(R3)		;  then continue waiting...
	BEQL	170$			;  continue traversing the queue...
	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	CLEANUP_HRB		; Deallocate the HRB and its resources
	BSBW	UNBLOCK			;  then adjust the current count
	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
	BSBW	DEALLOC_HQB		;  deleted, and then try
	RSB

170$:	BUG_CHECK MSCPSERV,FATAL	; ERROR

	.PAGE
	.SBTTL	-	SCS$MSCP_CHECK_SERVICE - Do you serve this disk?
;+
; Functional Description:
;
; This routine can be called to check whether a disk is being served by this
; server.
;
; Inputs:                                 
;	R2 = Pointer to a .ASCIC string with the device name (unique in cluster)
;
; Implicit inputs:
;	Caller should hold the SCS spin lock.
;
; Outputs:
;	R0 = LBS means yes, try it if you feel lucky.
;	     LBC means no,  buzz off.
;
; All registers are preserved.
;-
;SCS$MSCP_CHECK_SERVICE::
	UNIVERSAL_JSB	NAME=SCS$MSCP_CHECK_SERVICE, -
			INPUT=<R2>,PRESERVE=<R1>,OUTPUT=<R0>
;
; Initialization.
;
	PUSHR	#^M<R5,R4,R3,R2,R1>	; Save registers.
	MOVL	G^SCS$GL_MSCP,R5	; Get the address of the DSRV structure.
 	MOVAL	DSRV$L_UNITS(R5),R5	; Load unit table address.
	MOVL	#DSRV$K_MAX_UNITS,R0		; Maximum number of units.
;
; Loop entire UQB table for requested unit.
;
10$:	MOVL	(R5)+,R4		; Get the address of the next entry.
	BEQL	20$			; Skip if zero.

	CMPB	(R2),-			; Length of strings same?
		UQB$B_UNIQUE_DNAME_CNT(R4)
	BNEQ	20$			; If NEQ no, skip
	PUSHR	#^M<R5,R4,R2,R0>
	MOVZBL	(R2),R0			; get length
	CMPC3	R0,-			; else check if strings match
		1(R2),UQB$T_UNIQUE_DNAME(R4)
	POPR	#^M<R5,R4,R2,R0>
	BNEQ	20$			; If NEQ no, skip
	MOVL	#SS$_NORMAL, R0		; Found the unit. set the counter in R0
					; to terminate the loop at 20$ below
	CMPW	#UQB$K_ST_AVAILABLE,-	; Is it avaiable?
		UQB$W_STATE(R4)		;
	BEQL	30$			; EQL means yes.
	CMPW	#UQB$K_ST_ONLINE,-	; Is it online?
		UQB$W_STATE(R4)		;
	BEQL	30$			; EQL means yes.
;
; The server thinks the unit is offline. Go take a closer look at the device
; UCB to determine if we should respond to this load request.
;
	MOVL	UQB$L_UCB(R4),R1	; Get the address of the UCB
	BBC	#UCB$V_ONLINE,-		; If the device is not UCB online
		UCB$L_STS(R1),15$	;  leave the device offline for now
	BBC	#DEV$V_MSCP,-		; Don't try any of the remaining tests
		UCB$L_DEVCHAR2(R1),30$	;  unless this is a class driver!
					; Online bit is still set, return
					;  available state for non-MSCP disk.
	MOVL	UCB$L_CDDB(R1),R1	; 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	15$			; If it is, leave it offline
	BITL	#<CDDB$M_NOCONN -	; If there is no connection or
		 !CDDB$M_DISABLED>,-	; the controller is disabled
		CDDB$L_STATUS(R1)	;  then leave it offline
	BEQL	30$			; If eql probably online

15$:	MOVL	#SS$_DEVOFFLINE, R0	; Device found, but not available.
	BRB	30$			; The device is not available - branch.

20$:	SOBGTR	R0,10$			; Otherwise search to the end of table
	MOVL	#SS$_NOSUCHDEV, R0	; Requested device not served by this node.

30$:	POPR	#^M<R5,R4,R3,R2,R1>	; Restore registers.
	RSB				; Return to caller.

	.PAGE
	.SBTTL	Main Line Routine
;+
; Functional Description:
;
; MSCP requests are received from client hosts in this routine. The packet 
; is inspected for validity and if its good, an HRB is created to represent
; this request. The request is then dispatched to the proper handling
; routine to be processed further.
;
; Inputs:
;
;	R1  =  Length of MSCP packet
;	R2  =  MSCP packet address
;	R3  =  CDT address
;	R4  =  PDT address
;
; Oututs:
;
;	R0-R5 may be destroyed
;-
 
;
; 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. 
;
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
	BSBW	VC_ERR			; Break and clean up virtual curcuit
	RSB	
;
; We have already begun disconnect operations on this connection, as indicated
; in the HQB, probably as a result of the port asking us to move our connection.
; Once we get beyond a certain point in the disconnect routines, but before
; we ask SCS to take down the connection, our internal data structures are no
; longer in synch and attempting to process an MSCP packet will result in
; a crash elsewhere in the server.
;
BAD_CONN:
	DEALLOC_MSG_BUF_REG		; Give back the message buffer
	RSB				; Say goodnight to the folks Gracie.

;
; 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.
;
BAD_OPC:
	ROTL	#24,#MSCP$B_OPCODE,R0	; The op-code passed in the MSCP packet
	BRW	PACKET_ERROR		;  was out of bounds or not supported

;
; First, check the state of the connection. If we have begun disconnect
; processing, most likely a result of a path move request, reject the packet
; now. Next, make sure it is 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.
;
MSG_IN::
	.JSB_ENTRY INPUT=<R1,R2,R3,R4>,SCRATCH=<R0,R1,R2,R3,R4,R5>

	MOVL	CDT$L_AUXSTRUC(R3),R5	; Check to see if we have begun
	BBS	#HQB$V_DISCON_INIT,-	;  to disconnect this connection.
		HQB$B_STATE(R5),-	;  Dismiss the request if we have,
		BAD_CONN		;  as data structures may now be
					;  unsynchronized.

	MOVL	G^SCS$GL_MSCP,R5	; Get the server data structure
	INCL	DSRV$L_OPCOUNT(R5)	;  and count each request received

;
; Allocate a 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	ALLOCATE_HRB		; Create a Host Request Buffer
	BLBC	R0,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)		;  and save it for later refrence
	MOVL	R2,HRB$L_MSGBUF(R3)	; Save the address of the MSCP packet
;
; Log the MSCP message if logging is enabled.
;
       	.IF DEFINED DEBUG$LOG
    	ASSUME DSRV$V_LOG_ENABLD  EQ  0
	MOVL	G^SCS$GL_MSCP,R5	; Get the server data structure
	BLBC	DSRV$W_STATE(R5),5$	; Branch if logging is disabled.
	PUSHL	R0
	MOVL	#PKT$C_MSCP_CMD,R0
	BSBW	LOG_PKT		; Otherwise, log the command packet.
	POPL	R0
5$:
	.ENDC
;
; Queue HRB to the appropriate HQB before further checking.  This is
; necessary because sending an end message requires the HRB to be queued
; to a HQB.
;
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
	BLEQU	20$			;  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
;
; Load the Host Number into the IRP channel cell for possible use by cancel
; routines.
;
20$:	MOVL	HRB$L_IRP_CDRP(R3),R0
	MOVZBL	HQB$B_HOSTNO(R5),-
		IRP$L_CHAN(R0)
;
; Now sanity check this request.
;
	MOVZBL	MSCP$B_OPCODE(R2),R0	; Get the op-code from the packet
	BEQL	BAD_OPC			; It cannot be zero
	CMPL	R0,#MSCP$K_OP_TERCO	; The server only supports up to 
	BGTRU	BAD_OPC			;  term cl dr con; size limitation
	MOVZBL	L^COM_PKT_LEN[R0],R5	; Get the expected length
	BEQL	BAD_OPC			; If it is non zero continue on
	CMPW	R1,#MSCP$K_MIN_SIZ	; Is the packet the minimum length?
	BLSSU	BAD_LEN			; The length is bad report an error
	CMPW	R1,R5			; Is the command big enough?
	BLSSU	BAD_LEN			; Length is BAD, report an error
	BITW	L^MOD_TBL[R0],-		; Compare modifiers we don't allow,
		MSCP$W_MODIFIER(R2)	;  to the ones that are set
	BNEQ	BAD_MOD			; Bad modifiers used with this op-code
	TSTB	MSCP$B_FLAGS(R2)	; The flags field is reserved on 
	BNEQ	BAD_FLAGS		;  incoming packets and should be clear
;
; Process the request.
;
	MOVL	HRB$L_HQB(R3),R5	; Restore the Host Queue Block address
	MOVL	HQB$L_DSRV(R5),R1	; Server structure address for stats
	INCL	DSRV$L_OPCOUNT(R1)[R0]	; Receipt of a valid operation code
;
; NOTE:  This case statement assumes that the command class can be
; determined by the 3 MSBs of the opcode.  The ASHL instruction is a
; short cut that depends on this assumption.  The MSCP architecture
; has been changed to allow opcodes that are equivalent in the 3 MSBs
; but of different class (although none are defined so far).  If any
; such opcodes ever get defined they will break the ASHL short cut.
;
	ASHL	#-3,R0,R0		; Pick up class field
	CASE	R0,-			; Dispatch on class
		<IMMEDIATE,-		; Immediate commands
		 SEQUENTIAL,-		; Sequential commands
		 NONSEQ,-		; Non-Sequential commands w/o buffers
		 SEQUENTIAL,-		; Sequential maintenance commands
		 NONSEQB,-              ; Non-Sequential commands with buffers 
		 BAD_OPC,-		; Maintenance commands (none supported)
		 IMMEDIATE>		; Immediate commands


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

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

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

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


	.PAGE
	.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:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Outputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

IMMEDIATE:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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, ABORT>,-		; Abort this command
		<GTCMD, GET_COMMAND_STATUS>,-	; Get Command Status
		<GTUNT, GET_UNIT_STATUS>,-	; Get Unit Status
		<GTUNM, GET_UNIT_NAME>,-	; Get Unit Name
		<STCON, SET_CONTROLLER_CHAR>,-	; Set Controller Characteristics
		<TERCO, TERM_CL_DR_CON>,-	; Terminate Class Driver connection
		>

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

	.PAGE
	.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:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  Status to return in the MSCP end message
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

ABORT:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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
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	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),-	; Cmpare the outstanding refrence number
		MSCP$L_CMD_REF(R4)	;  to the command refrence 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,   70$>,-	; This one will just have to finish
		<SEQ_WAIT,   40$>,-	; These two requests are within our 
		<BUF_WAIT,   50$>,-	;  domain and 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
		<SUC_STALL,  70$>,-     ; Let this take it's normal path.
		<UCB_STALL,  35$>,-	; Retrieve CDRP from UCB pending queue
		>			; Cannot receive an abort for a command
					;  in SNDMS_WAIT state (only ATN msgs)

	BUG_CHECK	MSCPSERV,FATAL	; Trap any strange behavior here!

;
; 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 = ABORT_UNHOOK_CDRP ;  CDT and if HRB matches, dequeue it
	SCAN_MSGBUF_WAIT- 		; If request is waiting for msgbuf
		action = ABORT_UNHOOK_CDRP ;  this scan will find it
	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 has been stalled in the driver's I/O start routine, presumably
; waiting for send credits or other system resources. The CDRP is queued to
; the UCB's pending, via CDRP$L_IOQFL/BL. These CDRPs are dequeued and
; deallocated to prevent stale I/O from restarting some time in the future.
; The originating client will reissue the I/O when it locates a path.
;
35$:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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
	TSTL	IRP$L_IOQBL(R0)		; Check for the presence of a pointer
	BEQL	38$			; If the field is 0 don't dequeue
	CMPL	@IRP$L_IOQBL(R0),R0	; If the field is nonzero,
	BNEQ	38$			;  make sure it is in a queue
	REMQUE	IRP$L_IOQFL(R0),R0	; Remove the CDRP from its queue
	BISW	#HRB$M_UNBLOCK,-	; Mark this request to call unblock... 
		HRB$W_FLAGS(R1)		;  to decrement UQB$W_CURRENT
	BRB	60$			; Merge below

38$:	BUG_CHECK	DISKSERVE,FATAL

;
; 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.
;
40$:	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
	DECW	UQB$W_NUM_QUE(R0)	;  so we can update the counter
	BRB	60$			; Join up with the common ending

;
; This command was waiting for a buffer out of the server's local 
; space.  It can be removed from the memory wait queue.
; 
50$:	MOVAL	HRB$L_WAIT_FL(R1),R0	; Get the forward link of the entry
	REMQUE	(R0),R0			;  and remove it from the wait queue
	MOVL	HRB$L_HQB(R1),R0	; Get the address of the HQB
	MOVL	HQB$L_DSRV(R0),R0	;  so we can find the server structure
	DECW	DSRV$W_MEMW_CNT(R0)	;  and adjust 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	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	SEND_END		;  and send out the ABORT end message

	.PAGE
	.SBTTL	-	GET COMMAND STATUS			(- 2 -)
;+
; Functional Description:
;
; The amount of work remaining to be done to complete the command, expressed
; as an unsigned integer. If the command is not known to the server, the
; value returned is zero.
;
; When the HRB is allocated, a -2 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 disks attached to this processor, the command status value
; is decremented.
;
; If sysgen parameter MSCP_CMD_TMO is zero it is ignored. If it is nonzero
; and the command execution time has not exceeded the time value specified
; in MSCP_CMD_TMO, the CMD_STS field is decremented  before returning
; it in the end message.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  MSCP status to return in the end message
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

GET_COMMAND_STATUS:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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),R0	;  and save the HRB queue list head
	MOVL	R0,R5			; Prepare to start through the list
10$:	MOVL	(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	30$			;  return a command status of zero
20$:	MOVL	HRB$L_MSGBUF(R5),R4	; Get the packet address for the request
	BEQL	10$			; If EQL, already queued to SCS assume success
	CMPL	MSCP$L_OUT_REF(R2),-	; Cmpare the outstanding refrence number
		MSCP$L_CMD_REF(R4)	;  to the command refrence 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
        .WEAK   CLU$GL_MSCP_CMD_TMO
        MOVAB   G^CLU$GL_MSCP_CMD_TMO,R0; Check for a valid SYSGEN param
        BEQL    22$                     ; Branch if not valid
        TSTL    G^CLU$GL_MSCP_CMD_TMO   ; Check for user supplied value in
        BEQL    25$                     ;  sysgen parameter .. branch if none.
        SUBL3   HRB$L_CMD_TIME(R5),-    ; Figure out how much time has elapsed
                G^EXE$GL_ABSTIM,R0      ;  since the command was received.
        BICL3   #^x80000000,-           ; Get the sysgen time value
                G^CLU$GL_MSCP_CMD_TMO,- ;  and clear bit #31 since
                R1                      ;  it is not used for the time.
        CMPL    R0,R1                   ; Does this exceed the specified
        BLSSU   24$                     ;  amount of time.
        BISW    #HRB$M_CMD_TMO,-        ; Mark this HRB as "timed out"
                HRB$W_FLAGS(R5)         ;  and return the cmd_sts.
        MOVL    G^SCS$GL_MSCP,R4        ; Get pointer to DSRV
        INCL    DSRV$L_HRB_TMO_CNTR(R4) ; Increment HRB cntr in DSRV.
        BBC     #31,-                   ; High order bit set indicates
                G^CLU$GL_MSCP_CMD_TMO,- ;  that if a "timed out" command
                25$                     ;   is detected,
        BUG_CHECK MSCPSERV, FATAL       ;   BUGCHECK.
22$:    TSTL    MSCP_CMD_TMO            ; Check for user supplied value in
        BEQL    25$                     ;  sysgen parameter .. branch if none.
        SUBL3   HRB$L_CMD_TIME(R5),-    ; Figure out how much time has elapsed
                G^EXE$GL_ABSTIM,R0      ;  since the command was received.
        BICL3   #^x80000000,-           ; Get the sysgen time value
                MSCP_CMD_TMO,-          ;  and clear bit #31 since
                R1                      ;  it is not used for the time.
        CMPL    R0,R1                   ; Does this exceed the specified
        BLSSU   24$                     ;  amount of time.
        BISW    #HRB$M_CMD_TMO,-        ; Mark this HRB as "timed out"
                HRB$W_FLAGS(R5)         ;  and return the cmd_sts.
        MOVL    G^SCS$GL_MSCP,R4        ; Get pointer to DSRV
        INCL    DSRV$L_HRB_TMO_CNTR(R4) ; Increment HRB cntr in DSRV.
        BBC     #31,-                   ; High order bit set indicates
                MSCP_CMD_TMO,-          ;  that if a "timed out" command
                25$                     ;   is detected,
        BUG_CHECK MSCPSERV, FATAL       ;   BUGCHECK.
24$:    DECL    HRB$L_CMD_STS(R5)       ; Report progress being made.
25$:    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
30$:	MOVL	#MSCP$K_ST_SUCC,R0	; Zero means the command was successful
	BRW	SEND_END		; Return an end packet with status

	.PAGE
	.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. This command is also 
; used informally between the disk class driver and the MSCP server sysap
; to make sure communincations are maintained. If the class driver has not
; received any messages from the MSCP server in the controller timeout
; interval of time, it sends a get unit status command.
;
;Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

GET_UNIT_STATUS:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
;
; This request is from a VMS v5.0 (or later) class driver.  The unit number in
; this case has bit 14 set to indicate that the unit number 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 disk.  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	HRB$L_HQB(R3),R0	; Get the address of the HQB to
	MOVL	HQB$L_DSRV(R0),R5	;  get the server structure address
	MOVW	DSRV$W_LOAD_AVAIL(R5),-	; Store the current load available
		MSCP$W_LOAD_AVAIL(R2)	;  in the response
	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
	BUG_CHECK MSCPSERV, FATAL	; Class driver asking about bogus unit

10$:	INCL	R0			; Bump to next "unit" in table
20$:	CMPW	#DSRV$K_MAX_UNITS,R0	; Make sure "unit" specified is in table
	BLEQU	30$			; If not, end polling with unit 0
	MOVL	DSRV$L_UNITS(R5)[R0],R4	; Find the UQB address in the table
	BEQL	10$			; Try next unit if there isn't one here

	; Except we don't disclose this unit if it uses new naming and the
	; requesting host does not understand new naming.

	.BRANCH_LIKELY
	BBC	#UQB$V_DUNN,-		; If not new-name device, always OK
		UQB$W_FLAGS(R4),25$
	MOVL	HRB$L_HQB(R3),R1	; New-named device. Get HQB.
	BBC	#HQB$V_HUNN,-		; If host unaware of new naming, don't
		HQB$W_FLAGS(R1),10$	;  reveal this device

25$:	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 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	SEND_END		; Let the client find another path
;                           
; The MSCP command received is for a specific unit.
; 
80$:	BSBW	FIND_UQB		; Find the UQB for this request
	BLBC	R0,40$			; Branch if no such unit found

	; Except we don't disclose this unit if it uses new naming and the
	; requesting host does not understand new naming.

	MOVL	HRB$L_UQB(R3),R4	; Get the address of the UQB
	.BRANCH_LIKELY
	BBC	#UQB$V_DUNN,-		; If not new-name device, always OK
		UQB$W_FLAGS(R4),90$
	MOVL	HRB$L_HQB(R3),R1	; New-named device. Get HQB.
	BBC	#HQB$V_HUNN,-		; If host unaware of new naming,
		HQB$W_FLAGS(R1),40$	;  this device is offline to this host
;
; 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	HRB$L_HQB(R3),R5	; Get the Host Queue Block address
	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!

; 
; This is the old way of reporting geometric information through the server.
; Although it is inaccurate, it does not crash the class driver. Use this 
; method for reporting geometry when we are not using the disk class driver
; on the serving system.
;
	MOVL	UCB$L_DDB(R0),R1	; Get the DDB address
	MOVL	DDB$PS_DPT(R1),R1	; Get DPT address
	CMPL	DPT$T_NAME_STR+4(R1),-	; compare the name of the driver
		#^A/DUDR/		;  to DUDRIVER
	BEQL	105$			; If its the dudriver, we know what
					;  offsets in the UCB we can use.
	MOVZBW	UCB$B_SECTORS(R0),-	; Transfer the number of sectors
		MSCP$W_TRACK(R2)	;  per track
	MOVZBW	UCB$B_TRACKS(R0),-	; Then get the number of tracks per
		MSCP$W_GROUP(R2)	;  cylinder (best we can do)
	MOVW	#1,MSCP$W_CYLINDER(R2)	;  and fake out the cylinder field so 
	CLRW	MSCP$W_RCT_SIZE(R2)	; Clear out some fields
	CLRB	MSCP$B_RBNS(R2)		; 
	CLRB	MSCP$B_RCT_CPYS(R2)	; 
	BBC	#DEV$V_RCT,-		; Branch if no RCT
		UCB$L_DEVCHAR(R0),110$	; 
	MOVB	#1,MSCP$B_RCT_CPYS(R2)	; Lie about the RCT copies
	BRB	110$			;  the class driver won't crash
	

; system. The class driver saves away the real geometry returned by the 
; real mscp server's response to the original GUS command. Return this same
; information to the client system.
;
105$:	MOVW	UCB$W_DU_LBNPTRK(R0),-	; Transfer number of sectors
		MSCP$W_TRACK(R2)	;  per track.
	MOVW	UCB$W_DU_TRKPGRP(R0),-	; Number of tracks per group.
		MSCP$W_GROUP(R2)	;
	MOVW	UCB$W_DU_RCTSIZE(R0),-	; Get the RCT size and copy
		MSCP$W_RCT_SIZE(R2)	;  information right from the UCB
	MOVB	UCB$B_DU_RCTCPYS(R0),-	;  on the local system and return 
		MSCP$B_RCT_CPYS(R2)	;  that to the client system
	MOVW	UCB$W_DU_GRPPCYL(R0),-	; Get the number of groups per cylinder
		MSCP$W_CYLINDER(R2)	;  out of the local UCB
	MOVB	UCB$B_DU_RBNPTRK(R0),-	; And last, get the number of RBNs
		MSCP$B_RBNS(R2)		;  per track 
	ASSUME	MSCP$B_UNIT_HVR EQ MSCP$B_UNIT_SVR+1
	ASSUME	UCB$B_DU_UHVR   EQ UCB$B_DU_USVR+1
	MOVW	UCB$B_DU_USVR(R0),-	; Get the unit software and 
		MSCP$B_UNIT_SVR(R2)	;  hardware version numbers

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

110$:	BBS	#UCB$V_MNTVERIP,-	; If the volume is in local mount
		UCB$L_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$L_STATUS(R1),125$	;  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	130$			; If it is, leave it offline
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
;
; Check to see if the device we are dealing with is a shadow set virtual 
; unit. If it is, make sure the shadow set is still intact before sending
; an available status back to the client.
;
130$:	BBC	#MSCP$V_SHADOW,-	; If the device we are about to packack
		UCB$W_MSCPUNIT(R0),140$	;  is a shadow set virtual unit,
	TSTB	UCB$B_ONLCNT(R0)	;  and the online count is zero,
	BEQL	175$			;  then the virtual unit DNE anymore.
	BBC	#UCB$V_VALID,-		;  valid, return an offline status
  		UCB$L_STS(R0),175$	;  to the 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	R0, #UQB$K_ST_ONLINE	; If 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.
;
	BISW	#MSCP$M_SC_NOVOL,R0	; Reflect doubt about the volume
	MOVL	UQB$L_UCB(R4),R1	; Get the address of the UCB
	BBC	#UCB$V_ONLINE,-		; If the device is not UCB online
		UCB$L_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),160$	;  unless this is a class driver!
					; Since the online bit is still set,
					;  return available state.
	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
	BITL	#<CDDB$M_NOCONN -	; If there is no connection or
		 !CDDB$M_DISABLED>,-	; the controller is disabled
		CDDB$L_STATUS(R1)	;  then leave it offline
	BNEQ	170$			; If neq offline

160$:	MOVL	HRB$L_HQB(R3),R5	; Restore the HQB address
	MOVZBL	HQB$B_HOSTNO(R5),R1	; Put the host number in a register
	MOVL	#UQB$K_ST_AVAILABLE,R0	; Assume the device is available
	BBC	R1,UQB$B_ONLINE(R4),170$; Check the online bit for this host
	MOVZWL	#MSCP$K_ST_SUCC,R0	; Return a status of success
	TSTW	MSCP$W_TRACK(R2)	; Before copying in the multi unit 
	BEQL	180$			;  information, check to make sure the
	TSTW	MSCP$W_GROUP(R2)	;  track and group fields are non-zero.
	BEQL	180$			;  If they are, a bug check occurs in

;
; Update the state of the UF_REPLC bit in the UQB unit flags field before
;  returning it in the end message.
;

170$:   BISW    #MSCP$M_UF_REPLC,-      ; Always make sure the bad block
                UQB$W_UNIT_FLAGS(R4)    ;  replacement flag is set.
        MOVL    UQB$L_UCB(R4),R1        ; Get the address of the UCB
        BBC     #DEV$V_NOFE,-           ; No forced error support?
                UCB$L_DEVCHAR2(R1),171$ ;  BC means has support.
        BICW    #MSCP$M_UF_REPLC,-      ; Signal class driver no
                UQB$W_UNIT_FLAGS(R4)    ;  forced error support.
171$:	MOVL	UQB$W_MULT_UNIT(R4), -	;  the disk class driver, so stop here.
		MSCP$W_MULT_UNT(R2)	; Copy mult_unit & unit_flags
	MOVL    HRB$L_HQB(R3),R1        ; Get the HQB address
	BBS     #MSCP$V_CF_NFESC,-      ; Check the NFESC bit to see if remote
		HQB$W_CNT_FLGS(R1),172$ ;  host supports NOFE state changes
	BISW    #MSCP$M_UF_REPLC,-      ; Signal class driver
		MSCP$W_UNT_FLGS(R2)     ;  forced error support.
172$:	MOVQ	UQB$Q_UNIT_ID(R4), -	;  
		MSCP$Q_UNIT_ID(R2)	; Copy unit identifier
	BRW	SEND_END

175$:	MOVL	#UQB$K_ST_OFFLINE,R0	; 
	BRB	170$
        
180$:	BUG_CHECK	MSCPSERV,FATAL	; Something is very wrong

	.PAGE
	.SBTTL	-	GET UNIT NAME			(- 7 -)
;+
; Functional Description:
;
; The GET UNIT NAME command returns the DDR device type name, if it exists.
; The name is retrieved by using the routine IOC$GET_DEVICE_TYPE to locate the
; DTN (Device Type Name) block if the UCB$L_DEVCHAR2 DEV$M_DTN bit is
; set. If this bit is clear, no name is available so we just return a name
; length of ZERO in the end packet.
;
;Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

GET_UNIT_NAME:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
;
; This request is from a class driver that implements DDR.  The unit number in
; this case has bit 14 set to indicate that the unit number 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 disk.  
;

	MOVZWL	MSCP$W_UNIT(R2),R0	; Pick up the unit number passed
	BNEQ	20$			; If unit 0, return harmless offline

10$:	MOVL	#MSCP$K_ST_OFFLN,R0	; Return a status of offline
	BRW	SEND_END		; Let the client find another path
;                           
; The MSCP command received is for a specific unit.
; 
20$:	BSBW	FIND_UQB		; Find the UQB for this request
	BLBC	R0,10$			; Branch if no such unit found

;
; A UQB address was found and has been placed in the HRB.
;
30$:	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

;
; Here we call IOC$GET_DEVICE_TYPE to locate the DTN for this unit, if
; a name is available. If not, we return a zero length name string.
; In both cases, the end packet returns SUCCESS.
;

	PUSHAL	-(SP)			; Make room for DTN
	PUSHL	UQB$L_UCB(R4)		; Get the UCB address for this unit
	CALLS	#2,G^IOC$GET_DEVICE_TYPE	; Find the DTN
	POPL	R1			; Get DTN address
	BLBS	R0,40$			; Success, name available
	CLRL	MSCP$L_DDR_NAMELEN(R2)	; Zero name length in end message
	BRB	50$

40$:	MOVZBL	DTN$IB_DTNAME_LEN(R1),R0  ; Get name length from DTN
	MOVL	R0,MSCP$L_DDR_NAMELEN(R2) ; Set name length in end message
	DECL	R0			; Adjust for indexing
45$:	MOVB	DTN$T_DTNAME_STR(R1)[R0],- ; Copy bytes of name
		MSCP$T_DDR_NAME(R2)[R0]
	SOBGEQ	R0,45$			; All bytes
50$:
	MOVL    #MSCP$K_ST_SUCC,R0  	; Return a status of SUCCESS
	BRW	SEND_END		; Send the end message
        
        
	.PAGE
	.SBTTL	-	SET CONTROLLER CHARACTERISTICS		(- 4 -)
;+
; Functional Description:
;
; This command is the mechanism whereby certain host setable 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:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

SET_CONTROLLER_CHAR:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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_DSRV(R4),R5	;  get the address of the server struct
	CMPW	MSCP$W_VERSION(R2),-	; This is a check to asure the
		DSRV$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	PACKET_ERROR		;  make it no longer downward compat

10$:	MOVW	MSCP$W_CNT_FLGS(R2),-	; Copy the conrtoller 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
	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
	BLEQU	20$			;  as if 255 had been specified"
	MOVW	#255,HQB$W_HTIMO(R4)	; 			- the spec
        BRB	30$
20$:	CMPW	HQB$W_HTIMO(R4),#10	; "Treat all values between 1 and 9
	BGEQU	30$			;  as if 10 had been specified"
	MOVW	#10,HQB$W_HTIMO(R4)	; 			- the spec

;
; Return controller information to host
;
30$:	MOVB	G^CLU$GL_ALLOCLS,-	; Transfer the allocation class
		MSCP$B_CNT_ALCS(R2)	; 
	MOVW	DSRV$W_VERSION(R5),-	; Return all the current settings of
		MSCP$W_VERSION(R2)	;  the server software version number,
	MOVW	DSRV$W_CFLAGS(R5),-	; 
		MSCP$W_CNT_FLGS(R2)	;  the server controller flag settings,
	MOVW	#20,MSCP$W_CNT_TMO(R2)	;  and the controller timeout value
	MOVQ	DSRV$Q_CTRL_ID(R5),-	; Also, uniquely identify this MSCP
		MSCP$Q_CNT_ID(R2)	;  device to the host

	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	SEND_END		;  and send off the end packet

	.PAGE
	.SBTTL	-	Terminate Class Driver Connection	(- 48 -)
;+
; Functional Description:
;
; Controller that receives this command terminates the connection with
; the class driver specified.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  Status code
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;-

TERM_CL_DR_CON::
	.JSB_ENTRY -
		INPUT=<R2,R3>, -
                OUTPUT=<R0,R2,R3,R4>
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

	BSBW	FIND_UQB		; Find the uqb.
	BLBS	R0,10$			;
	MOVL	#MSCP$K_ST_AVLBL,R0	; Set success
	CLRL	HRB$L_UQB(R3)		;  make sure the unblock won't work
	BRW	20$
10$:
	MOVL	HRB$L_UQB(R3),R4	; Load UQB address.
	MOVL	UQB$L_UCB(R4),R4	; Get UCB.
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Load IRP.
	MOVL	#<IO$_WHM!-		; Load function code and
		  IO$M_BREAK_CONN>,-	;  set break_conn modifier
		IRP$L_FUNC(R5)		;
	MOVL	MSCP$W_HRN(R2),-	; Load CRN
		IRP$L_CLN_WLE(R5)	;
	MOVL	R4,IRP$L_UCB(R5)	; Load UCB.
        MOVAB	15$,R0
	BSBW	CHECK_DISK
	RSB
;+
;   Inputs to the async completion routine:
;	R0 contains the I/O status  block
;	R1 contains the address of the UCB
;	R2 contains the message buffer address
;	R3 contains the address of the HRB
;	R4 contains the address of the UQB
;	R5 contains the address of the IRP
;
;   Outputs required from the async completion routine
;	R0-R5 may be destroyed
;-
15$:
	.JSB_ENTRY	INPUT=<R0,R1,R2,R3,R4,R5>, -
			OUTPUT=<R0,R1,R2,R3,R4,R5>

	MOVL	HRB$L_MSGBUF(R3),R2	; Load message address.
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Load irp address.
	MOVL	IRP$L_CLN_WLE(R5),-	;
		MSCP$W_HRN(R2)		;

	MOVL	#MSCP$K_ST_SUCC,R0	; Set success
20$:	BRW	SEND_END		; Exit.

	.PAGE
	.SBTTL	SEQUENTIAL class commands
;+
; Functional Description:
;
; 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 receicved may begin execution until the 
; completion of the sequential command. If a sequential command is received, 
; and other commands are pending, the sequential command may not begin until 
; all the pending commands are completed. Using the BLOCKED queue and BLKD_CNT
; data structures in the UQB, the following rules are followed on receipt of a
; sequential command:
;
;	a) If there is no sequential command active for this unit, no
;	   requests currently being processed, and no requests on the 
;	   blocked queue, process this command.
;	b) If there is no sequential command active on this unit, and there
;	   are requests currently being processed for this unit, put this 
; 	   request at the tail of the blocked queue.
;	c) If there is a sequential command already in progress for this
;	   unit, insert this request at the tail of the blocked queue.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Outputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;-

SEQUENTIAL:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	BSBW	FIND_UQB		; Find the UQB that corresponds to 
	BLBS	R0,SEQ_ALT		;  this request and put addr in HRB
	BRW	ERROR_NO_UNIT		; Return an error (see 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.
;
; Input:
;	R2 message buffer address or zero
;	R3 HQB address
; Outputs:
;	R0-R5 may be destroyed
;
SEQ_ALT:
	.JSB_ENTRY INPUT=<R2,R3>,SCRATCH=<R0,R1,R2,R3,R4,R5>

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

;
; The following conditions must be met to asure that this command obeys
; the conditions of sequentiality set forth in the MSCP spec.
;
	CMPW	UQB$W_CURRENT(R4),#1	; If there are no requests other than
	BLEQU	20$			;  this one active now, continue...
	MOVAB	15$,R0			; Routine to invoke when unblocked
10$:	BSBW	BLOCKED			; Restart at the entry point if blocked
	RSB				; Done for now

; 
; If the blocked subroutine is called above, this request is stalled until
; it is removed from the blocked queue. At that time execution is resumed
; below.
;
15$:	.JSB_ENTRY INPUT=<R3>,SCRATCH=<R0,R1,R2,R3,R4,R5>
	MOVL	HRB$L_UQB(R3),R4	; Get the address of the UQB

20$:	BISW	#UQB$M_SEQ,-		; Set the flag that signals that a 
		UQB$W_FLAGS(R4)		;  sequential command is in progress
	BBS	#HRB$V_VCFAILED,-	; If this packet is part of the VC
		HRB$W_FLAGS(R3),30$	;  cleanup, there is no message buffer
	MOVL	HRB$L_MSGBUF(R3),R2	; Get the address of the message buffer
	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,	AVAILABLE>,-	; available op-code 8
		<ONLIN,	ONLINE>,-	; online op-code 9
		<STUNT,	SET_UNIT_CHR>,-	; set unit characteristics op-code 10
		<DTACP,	DET_ACC_PATH>,-	; determine access path op-code 11
		<WRHIM, WRHIM>,-	; write history management op-code 25
		>

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

	.PAGE
	.SBTTL	-	AVAILABLE				(- 8 -)
;+
; Functional Description:
;
; The available command is sent as part of the dismount processing.
; All I/O pending for this unit is completed and the unit is made
; available for other processes to mount.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

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

;
; Locate the HULB and delete it. If the load balance requested flag is set,
; clear the corresponding flag in DSRV$W_STATE.
;

	CLRL	R0			; Ensure that R0 has no excess baggage
	BICW3	#MSCP$M_SLUN,-		; Extract unit number for use as index,
		UQB$W_SLUN(R4),R0	;  clearing the SLUN bit
	MOVL	HRB$L_HQB(R3),R1	; Pick up HQB
	MOVL	HQB$L_HULB_VECTOR(R1),R1; Get pointer to HQB vector
	BEQL	4$			; No vector? 
	MOVL	(R1)[R0],R5		; Index into vector with unit number to
					;  locate HULB for and increment counter
	BEQL	4$			; No HULB? Better not try to delete it!
	CLRL	(R1)[R0]		; Clear the HULB vector entry.
	REMQUE	(R5),R0			; Remove HULB from queue.
	BBSC	#HULB$V_LB_REQ,-	; If this HULB was marked for a load
		HULB$W_STATUS(R0),3$	;  balance move, clear the LB_REQ bit
	MOVL	G^SCS$GL_MSCP,R5	;  in DSRV.
	BICW	#DSRV$M_LB_REQ,-
		DSRV$W_STATE(R5)
3$:	JSB	G^EXE$DEANONPAGED	; Return the HULB to pool.

4$:	MOVL	HRB$L_HQB(R3),R5	; Get the host number from the Host
	MOVZBL	HQB$B_HOSTNO(R5),R1	;  Queue Block, and clear the bit 
	MOVL	UQB$L_UCB(R4),R5	; Get the UCB address for this unit
	BBCC	R1,UQB$B_ONLINE(R4),39$	; If not online to this host, get out.
	BBC	#DEV$V_MSCP,-		; If this isn't a MSCP disk, it
		UCB$L_DEVCHAR2(R5),5$	;  can't be a shadow set.
	BBS	#MSCP$V_SHADOW,-	; If this is a shadow set virtual unit
		UCB$W_MSCPUNIT(R5),10$	;  don't change the online count
5$:	DECB	UCB$B_ONLCNT(R5)	; Drop the count

;
; Now go through the bitmap of client hosts, checking to see if the host
; issuing the available, was the last host holding this unit online.
;
10$:	MOVAL	UQB$B_ONLINE(R4),R0	; Get the address of the online bitmap
	MOVAL	UQB$L_EXTRA_IO(R4),R1	; Save the end of table address
20$:	TSTL	(R0)+			; Test for bits set and move on
	BEQL	30$			; There are no other hosts online!
	BRW	80$			; If there are others, let them worry
30$:	CMPL	R0,R1			; See if we are finished searching
	BNEQ	20$			; Check the next longword

;
; The check of the host online bitmap indicates that there are no client
; hosts currently holding this unit online. Change the state of the unit to
; available.
;
	MOVW	#UQB$K_ST_AVAILABLE,-	; Change the state of this unit
		UQB$W_STATE(R4)		;  to "available" for client hosts
	TSTB	UCB$B_ONLCNT(R5)	;  then check for a "non-server" path
	BNEQ	70$			; If there is a local path skip the I/O
	BBC	#DEV$V_MSCP,-		; If this isn't a MSCP disk, then
		UCB$L_DEVCHAR2(R5),40$	;  it can't be a shadow set.
	BBC	#MSCP$V_SHADOW,-	; If this isn't a shadow set, do the
		UCB$W_MSCPUNIT(R5),40$	;  actual Available or Unload I/O
	MOVW	#UQB$K_ST_OFFLINE,-	; Otherwise, shadowset with zero online
		UQB$W_STATE(R4)		;  count is no good, make it dead.
	BRB	70$			; And go away.

39$:	BRW	80$			; Branch Assist

;
; The host that issued the available was the last host that had this unit
; online, and there is no local path to the device. If the spindown modifier 
; has been set, issue an unload command to the class driver. If the spindown
; modifier is not used, issue an available command. If this available command 
; was the result of the loss of connection to the only client node that had 
; the unit online, do not spin down the disk.

;
40$:	MOVL	R5,R4			; Save the UCB address for CHECK_DISK
	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

	MOVL	#IO$_AVAILABLE!-	; Set function to AVAILABLE,
		IO$M_INHERLOG,-		;  assuming no spindown
		IRP$L_FUNC(R5)		; 
        MOVB    #PRI$C_MAX_VMS_PRIO,-   ; Set lowest priority.
                IRP$B_PRI(R5)           ;
	MOVL	HRB$L_MSGBUF(R3),R2	; If this was an internally generated
	BEQL	50$			;  available, don't spin down the drive
	BBC	#MSCP$V_MD_SPNDW,-	; If SPINDOWN modifier not set,
		MSCP$W_MODIFIER(R2),50$	;  send the Available
	MOVL	#IO$_UNLOAD!-		; Else, set function to UNLOAD
		IO$M_INHERLOG,-		;  since the disk is being unloaded
		IRP$L_FUNC(R5)		;  

50$:	MOVAB	55$,R0			; Set Completion routine
	BSBW	CHECK_DISK		; Send the packet to the disk
	RSB				; 
;+
;   Inputs to the async completion routine:
;	R0 contains the I/O status  block
;	R1 contains the address of the UCB
;	R2 contains the message buffer address
;	R3 contains the address of the HRB
;	R4 contains the address of the UQB
;	R5 contains the address of the IRP
;
;   Outputs required from the async completion routine
;	R0-R5 may be destroyed
;-
55$:
	.JSB_ENTRY	INPUT=<R0,R1,R2,R3,R4,R5>, -
			OUTPUT=<R0,R1,R2,R3,R4,R5>
;
; Control returns here from entry point BACK. R0 contains the I/O
; status block, R3 contains the Host Request Block, and R5 contains
; the address of the I/O Request Packet.
; 
	BLBS	R0,70$			; If the I/O was successful cont...
	TSTL	HRB$L_MSGBUF(R3)	; If the I/O was not successful, but
	BEQL	70$			;  there is no msgbuf, ignore the error
	MOVAB	ERR_TBL,R2		; Get the address of the error table
	BSBW 	XFER_ERR		; Otherwise get the MSCP error code
	BICL3	#^cMSCP$M_ST_MASK, -	; Extract major MSCP status
		R0, R1			;  
	CMPL	R1,#MSCP$K_ST_OFFLN	; Was this one of the offline errors?
	BNEQ	60$			; If not test for write lock
	BSBW	ERR_OFFLINE		; Otherwise do some offline processing
	BRW	65$			;  and go to the common exit
60$:	CMPL	R1,#MSCP$K_ST_WRTPR	; Was this a write lock error?
	BNEQ	65$			; If not just return the error
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address 
	BBSS	#MSCP$V_UF_WRTPH,-	; Set the write protect bit in
		UQB$W_FLAGS(R4),65$	;  the unit flag field
65$:	MOVL	HRB$L_MSGBUF(R3),R2	; MSCP packet address from the HRB
	MOVL	HRB$L_ABCNT(R3),-	; Set the byte count in the MSCP 
		MSCP$L_BYTE_CNT(R2)	;  to return to the requestor
	BRW	SEND_END		; Send an end packet with R0 status

70$:	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address back after I/O
	BICW	#<MSCP$M_UF_WRTPS!-	; Clear out the hardware
    		MSCP$M_UF_WRTPH>,-	;  and software
		UQB$W_FLAGS(R4)		;  write protect flags
80$:	CLRL	R0			; Send success
	MOVL	HRB$L_MSGBUF(R3),R2	; Make sure the packet address is there
	BEQL	90$			; This must be a call from VC_ERR
	BRW	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
	BICW	#UQB$M_SEQ,-		; Clear out the sequential flag since
		UQB$W_FLAGS(R4)		;  the request just completed was seq
	BSBW	CLEANUP_HRB		; Leave this request in the queue to
	BRW	UNBLOCK			;  and startup any blocked request
					;  be cleaned up in the VC_ERR routine

	.PAGE
	.SBTTL	-	ONLINE					(- 9 -)
;+
; Functional Description:
;
; The ONLINE command makes the disk media generally available for access.
; This MSCP command is sent to the disk controller as a result of a mount
; command being issued.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;- 
INV_FLAGS:
	ROTL	#24,#MSCP$W_UNT_FLGS,R0	; Identify the bad field
	BRW	PACKET_ERROR		; Return an end msg with error status

ONLINE:


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

	

	BITW	#<MSCP$M_UF_SSMST!-	; This controller does not support
		MSCP$M_UF_SSMEM>,-	;  shadowing, and so must return an
		MSCP$W_UNT_FLGS(R2)	;  invalid command end message 
	BNEQ	INV_FLAGS		;  specifying the unit flags field
					;  as the field containing the bad value
	MOVL	R4,R0			; Save the UQB address for later
	MOVL	UQB$L_UCB(R4),R4	; Get UCB address from UQB
	.if	df,sw$hack
; If this is a switched device, it may be an MSCP server UCB at some
; point and later become a direct path, or vice versa. We don't want EVER
; to serve the device during the time it is served already. Since SWdriver
; (currently) toggles DEV$M_NOSRV to tell us, pay attention to that bit
; and return device offline if the noclu bit is set.
; This can of course change if we switch again.
	BBS	#DEV$V_SRV,-
		UCB$L_DEVCHAR2(R4),10$	; If already served, leave
					; the device were offline for now.
	.endc
	BBC	#DEV$V_MSCP,-		; If this is NOT an MSCP device, 
		UCB$L_DEVCHAR2(R4),30$	;  branch. 
  	BITL	#<UCB$M_MSCP_MNTVERIP -	; If this MSCP device has any of these 
		 !UCB$M_MSCP_WAITBMP -	;  bits set, return status of 
		 !UCB$M_MSCP_PKACK>, -	;  offline now, and let it try 
		UCB$L_DEVSTS(R4)	;  some more. 
	BEQL	20$			; Branch if none are set.

10$:	MOVL	#MSCP$K_ST_OFFLN,R0	;  Return a status of offline and let
	
	BRW	SEND_END		;  the client find another path
;
; Check to see if the device we are dealing with is a shadow set virtual 
; unit. If it is, make sure the shadow set is still entact before sending
; the PACKACK out.
;
20$:	BBC	#MSCP$V_SHADOW,-	; If the device we are about to packack
		UCB$W_MSCPUNIT(R4),30$	;  is a shadow set virtual unit,
	TSTB	UCB$B_ONLCNT(R4)	;  and the online count is zero,
	BEQL	10$			;  then the virtual unit DNE anymore.
	BBC	#UCB$V_VALID,-		;  valid, return an offline status
  		UCB$L_STS(R4),10$	;  to the client node.

30$:
;
; Check to see if we really need to do a packack. If the device is already
; online to another host, we can try to skip sending the packack as this can
; be a lengthy operation on some controllers.
;
	CMPW	#UQB$K_ST_ONLINE,-	; Is the unit already online to another
		UQB$W_STATE(R0)		;  host?
	BEQL	75$			; We can skip the packack if it is.

35$:
;
; A PACKACK is sent to the local driver to determine if the device is 
; reachable. A DSA device that receives a PAKACK returns a status of success.
; Non-DSA devices cannot handle PAKACKs and return an illegal I/O end message
; for the command. Either status is acceptable, since we were really just 
; trying to determine if the disk was reachable.
;
	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	#IO$_PACKACK!-		; Set up the function code,
		IO$M_INHERLOG,-		;  and function modifier fields
		IRP$L_FUNC(R5)		;  to do the mount verification
        MOVB    #PRI$C_MAX_VMS_PRIO,-   ; Set lowest priority.
                IRP$B_PRI(R5)           ;
	MOVAB	37$,R0			; Completion routine
	BSBW	CHECK_DISK		; Call a subroutine to do the I/O
	
	RSB

;+
;   Inputs to the async completion routine:
;	R0 contains the I/O status  block
;	R1 contains the address of the UCB
;	R2 contains the message buffer address
;	R3 contains the address of the HRB
;	R4 contains the address of the UQB
;	R5 contains the address of the IRP
;
;   Outputs required from the async completion routine
;	R0-R5 may be destroyed
;-
37$:
	.JSB_ENTRY	INPUT=<R0,R1,R2,R3,R4,R5>, -
			OUTPUT=<R0,R1,R2,R3,R4,R5>

;
; Control returns here from entry point BACK. Registers are loaded by
; BACK as follows:
;	R0 contains the I/O status  block
;	R1 contains the address of the UCB
;	R3 contains the address of the HRB
;	R4 contains the address of the UQB
;	R5 contains the address of the IRP
; 
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	
	BLBS	R0,80$			; If the I/O was successful cont...
	CMPW	R0,#SS$_ILLIOFUNC	; PACKACK not supported by drive,
	BEQL	80$			;  but we got what we wanted

;
; The rest of these cases are error codes. For each of them, return the
; proper MSCP code and subcode so the class driver will do the right thing
; on the client node.
;
	CMPW	R0,#SS$_MEDOFL		; This is the most common error code
	BNEQ	50$			; If not this one continue...
40$:	MOVL	#MSCP$K_ST_OFFLN,R0	; If the media is offline
	BRW	SEND_END		;  set the MSCP status and return

50$:	CMPW	R0,#SS$_NOSUCHDEV	; This is form of the above error
	BEQL	40$			;  is to avoid multiple server hops.
	CMPW	R0,#SS$_DEVOFFLINE	; This is another form of the above 
	BEQL	40$			;  error.
	CMPW	R0,#SS$_DUPUNIT		; If the PACACK was sent to a drive
	BNEQ	60$			;  that is a duplicate of another
	MOVZWL	#<MSCP$K_SC_DUPUN@ -	; Set the subcode indicating that
		MSCP$V_ST_SBCOD! -	;  this is a duplicate unit,
		MSCP$K_ST_OFFLN>,R0	;  along with the offline status
	BRW	SEND_END		;  and return

60$:	CMPW	R0,#SS$_DEVNOTSHR	; If this device is mounted exclusive,
	BNEQ	70$			;  is part of a shadow set, etc.
	MOVZWL	#<MSCP$K_SC_EXUSE@ -	; Set the subcode indicating the 
		MSCP$V_ST_SBCOD! -	;  device cannot be shared, and
		MSCP$K_ST_OFFLN>,R0	;  set up a status of offline
	BRW	SEND_END		;  and return

70$:	MOVL	#MSCP$K_ST_DRIVE,R0	; If nothing else, it must be
	BRW	SEND_END		;  some sort of drive error

75$:
;
; We have received an online request for a device that is already online to
; another host. We will skip the packack as it can be a time consuming
; operation. If the device being served is an MSCP device, we issue an IO$_NOP
; in order to set device characteristics in the local UCB.  The MSCP server
; knows that DUDRIVER will turn the IO$_NOP into a Set Unit Characteristics
; command.  For non-MSCP devices, we can skip this too.
;
	BBS	#DEV$V_MSCP,-		; If this is an MSCP disk send
		UCB$L_DEVCHAR2(R4),77$	;  a seq command, otherwise..
	MOVL	R4,R1			; Setup R1 with UCB value
	MOVL	R0,R4			; Restore UQB pointer.
	BRB	90$			; And complete the online request
					; (we can skip the MSCP specific
					; code at 80$).

;
; This is an MSCP device, so we will send an IO$_NOP to the device. This
; becomes a Set Unit Characteristics which is an MSCP sequential
; command. If the I/O fails for any reason, go back and issue a real
; packack. This will ensure that the correct status is returned to
; the client for use by mount verification and/or connection walking.
;

77$:	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	#IO$_NOP!-		; Set up the function code,
		IO$M_INHERLOG,-		;  and function modifier fields
		IRP$L_FUNC(R5)		;  to do the NOP.
        MOVB    #PRI$C_MAX_VMS_PRIO,-   ; Set lowest priority for
                IRP$B_PRI(R5)           ;  consistency.
	MOVAB	79$,R0			; Completion routine
	BSBW	CHECK_DISK		; Call a subroutine to do the I/O
	
	RSB

;+
;   Inputs to the async completion routine:
;	R0 contains the I/O status  block
;	R1 contains the address of the UCB
;	R2 contains the message buffer address
;	R3 contains the address of the HRB
;	R4 contains the address of the UQB
;	R5 contains the address of the IRP
;
;   Outputs required from the async completion routine
;	R0-R5 may be destroyed
;-
79$:
	.JSB_ENTRY	INPUT=<R0,R1,R2,R3,R4,R5>, -
			OUTPUT=<R0,R1,R2,R3,R4,R5>

; Control returns here from entry point BACK. Registers are loaded by
; BACK as follows:
;	R0 contains the I/O status  block
;	R1 contains the address of the UCB
;	R3 contains the address of the HRB
;	R4 contains the address of the UQB
;	R5 contains the address of the IRP
; 
	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	
	BLBS	R0,80$			; If the I/O was successful cont...
	MOVL	R1,R4			;  otherwise, move UCB addr to R4
	BRW	35$			;  and branch back and
					;  really do the packack.



;
; The PACKACK was a success which means the disk is accessable. Make sure
; all the status codes are set properly to reflect an "online" status and
; then return the device charactistics in the online end message.
; 

80$:    BICW    #MSCP$M_UF_WRTPH,-      ; Update the unit flags in UQB
                UQB$W_UNIT_FLAGS(R4)    ;
        BBC     #DEV$V_SWL,-            ; Check the device characteristics
                UCB$L_DEVCHAR(R1),82$   ;  for software writelock
        BISW    #MSCP$M_UF_WRTPH,-      ; If it is, set the hw flag
                UQB$W_UNIT_FLAGS(R4)    ;  so we don't issue writes to it
                                                                                                                                    
82$:	BBC	#DEV$V_MSCP,-		; If not a MSCP disk,
		UCB$L_DEVCHAR2(R1),90$	;  just let the request through.

	BICW	#MSCP$M_UF_WHL, -       ; Update unit flags...
		UQB$W_UNIT_FLAGS(R4)
	BBC	#MSCP$V_UF_WHL,-        ; write logging?
		UCB$W_UNIT_FLAGS(R1),85$

	BISW	#MSCP$M_UF_WHL,-	; Update unit flags
		UQB$W_UNIT_FLAGS(R4)	; (for the ones that do not get set
					; until the unit is online)
85$:	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	BICW	#^C<-		     	; Allow this cmd to set only the following:
		MSCP$M_UF_CMPRD!-	;  compare reads 
		MSCP$M_UF_CMPWR!-	;  compare writes
		MSCP$M_UF_576!-		;  set 576 byte sectors
		MSCP$M_UF_SCCHH!-	;  supress high speed caching
		MSCP$M_UF_WBKNV!-	;  non-volatile write-back
		MSCP$M_UF_WRTPS>,-	;  software write protect
		MSCP$W_UNT_FLGS(R2)	; Clear all the other bits out
	BICW3	#<-			; Check only the bits in the UQB
		MSCP$M_UF_REPLC!-	;  that we permit to be set in the 
		MSCP$M_UF_RMVBL!-	;  MSCP command packet.
		MSCP$M_UF_WRTPH!-	;  
		MSCP$M_UF_SSMEM!-	; 
		MSCP$M_UF_SSMST!-       ;
		MSCP$M_UF_WHL>,-	; Put the bits that remain set
		UQB$W_UNIT_FLAGS(R4),R1	;  into a register for comparison
	
	CMPW	MSCP$W_UNT_FLGS(R2),R1	; Compare the MSCP flags to the UQB flgs
	BEQL	91$			; If they are the same, let it by
	BRW	INV_FLAGS		; The flags set don't agree with request
90$:    BISW    #MSCP$M_UF_REPLC,-      ; Signal class driver
                UQB$W_UNIT_FLAGS(R4)    ;  forced error support.
        MOVL    UQB$L_UCB(R4),R1        ; Get the address of the UCB
        BBC     #DEV$V_NOFE,-           ; No forced error support?
                UCB$L_DEVCHAR2(R1),91$  ;  BC means has support.
        BICW    #MSCP$M_UF_REPLC,-      ; Signal class driver no
                UQB$W_UNIT_FLAGS(R4)    ;  forced error support.
91$:	MOVW	#UQB$K_ST_ONLINE,-	; Update the state field
		UQB$W_STATE(R4)		;  in the UQB
	MOVL	HRB$L_HQB(R3),R5	; Get the HQB address
	MOVL	HQB$L_HULB_VECTOR(R5),R1; Get the HULB vector
	MOVZWL	MSCP$W_UNIT(R2),R0	; Put the unit number in a register
	BBCC	#MSCP$V_SLUN,R0,999$	; Check SLUN bit and clear index
	CMPW	R0,HQB$W_MAX_HULB(R5)	; Past end of table?
	BLSSU	93$			; No, continue, unit # is in range
	JSB	EXTEND_HULB             ; Otherwise we need to extend the HULB vector
93$:	MOVL	(R1)[R0],R5		; Find the HULB address in the vector
	BNEQ	94$			; Continue if there is one
	JSB	ALLOCATE_HULB		; Otherwise go off and create one

94$:	CLRW	HULB$W_STATUS(R5)	; Clear status word in HULB marked for delete
	ASSUME	HULB$W_PREV_OPC EQ HULB$W_OPCOUNT+2
	CLRL	HULB$W_OPCOUNT(R5)	; Clear operation counters
	CLRL	HULB$L_TIME(R5)		;  and time of last load balance
	MOVL	#MSCP$K_ST_SUCC,R0	; Assume a successful status
	MOVL	HRB$L_HQB(R3),R5	; Get the HQB address
	MOVZBL	HQB$B_HOSTNO(R5),R1	; Set the bit for this host in the UQB
	BBSS 	R1,UQB$B_ONLINE(R4),110$;  signifying that this unit is online
	MOVL	UQB$L_UCB(R4),R1	; Get the address of the UCB
	BBC	#DEV$V_MSCP,-		; If this isn't a MSCP device, it
		UCB$L_DEVCHAR2(R1),95$	;  can't be a shadow set.
	BBS	#MSCP$V_SHADOW,-	; If this is a shadow set virtual unit
		UCB$W_MSCPUNIT(R1),100$	;  don't change the online count
95$:	INCB	UCB$B_ONLCNT(R1)	; Bump the online counter
100$:	BRW	COPY_CHAR		; Return the device charactistics
	
110$:	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	COPY_CHAR		;  and finish off the request


999$:	BUG_CHECK MSCPSERV, FATAL	; No SLUN bit set from V5+ class driver
;	MNEGW	#1,MSCP$L_CMD_REF+2(R2) ; code to poison packet, this will
					; force remote class driver down 
;	CLRL	R0
;	BRW	COPY_CHAR

	.PAGE
	.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:
; 
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;-

SET_UNIT_CHR:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
;
; It is meaningless to set host settable characteristics for a unit that is
; unit-available or unit-offline. Check to make sure that the unit is 
; unit-online to the requestor.
;
	MOVL	HRB$L_HQB(R3),R5	; Get the host queue block address
	MOVZBL	HQB$B_HOSTNO(R5),R0	; Put the host number in a register
	BBS	R0,UQB$B_ONLINE(R4),10$	; If online contnue...
	MOVL	#MSCP$K_ST_OFFLN,R0	; Otherwise this is an invalid command
	BRW	SEND_END		; Return the end packet
;
; Unit ONLINE to this host - Check for allowable modifiers (Enable Set Write
; Protect, and Shadow Unit Specified). The server does not support shadowing,
; so there is only one to check.
;
10$:	MOVL	#MSCP$K_ST_SUCC,R0	; From here out this cmd is a breeze
	BBS	#MSCP$V_MD_STWRP,-	; Check "enable set write protect"
		MSCP$W_MODIFIER(R2),-	;  if its set continue...
		COPY_CHAR		; 
	BICW	#MSCP$M_UF_WRTPS,-	; If the above bit was not set
		MSCP$W_UNT_FLGS(R2)	;  write protect is not host settable

;
; Prepare the end message to be returned to the host. The MSCP packet
; address is stored in R2, and the UQB address is in R4.
;
COPY_CHAR:
	MOVL	UQB$L_UCB(R4),R5	; Get the UCB address
	MOVL	UCB$L_MAXBLOCK(R5),-	; Retrieve the maximum block number
		MSCP$L_UNT_SIZE(R2)	;  and use that for the unit's size
	MOVL	UCB$L_VCB(R5),R1	; Get the volume control block
	BEQL	10$			; There may not be a VCB
	MOVL	VCB$L_SERIALNUM(R1),-	; Put the serial number from the VCB
		MSCP$L_VOL_SER(R2)	;  into the returned message
	BRB	20$			;  and continue...
10$:	MOVZWL	#^X1234,-		; Just set a recognizable bogus
		MSCP$L_VOL_SER(R2)	;  value in there for now
20$:	MOVW	UQB$W_MULT_UNIT(R4),-	; Move the multi-unit code
		MSCP$W_MULT_UNT(R2)	;  to the end packet
	MOVW	UQB$W_UNIT_FLAGS(R4),-	; Put the set unit flags in the
		MSCP$W_UNT_FLGS(R2)	;  end packet to return
        MOVL    HRB$L_HQB(R3),R1        ; Get the HQB address
        BBS     #MSCP$V_CF_NFESC,-      ; Check the NFESC bit to see if remote
                HQB$W_CNT_FLGS(R1),25$  ;  host supports NOFE state changes
        BISW    #MSCP$M_UF_REPLC,-      ; Signal class driver
                MSCP$W_UNT_FLGS(R2)     ;  forced error support.
25$:	MOVQ	UQB$Q_UNIT_ID(R4),-	; Return a unit identifier
		MSCP$Q_UNIT_ID(R2)	;  in the end packet also
	MOVL	UCB$L_MEDIA_ID(R5),-	; Get the media identifier from
		MSCP$L_MEDIA_ID(R2)	;  the unit control block
	CLRW	MSCP$W_SHDW_UNT(R2)	; Clear out the shadow information
	CLRW	MSCP$W_SHDW_STS(R2)	;  no host based shadowing
	BRW	SEND_END		; Send it all back as an end message

	.PAGE
	.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 MSCP 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:
; 
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;
; Outputs:
;
;	R0  =  Completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;-

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

	MOVL	#MSCP$K_ST_SUCC,R0	; Set a successful status code
	BRW	SEND_END		;  and return the end packet
	.PAGE
	.SBTTL	-	WRITE HISTORY MANAGEMENT		(- 25 -)
;+
; Functional Description:
;
; The WRITE HISTORY MANAGEMENT command allows host class drivers to
; manage the "write history entries" associated with a unit via the
; following operations:
;  o  Deallocate all entries.
;
;  o  Deallocate  the  group  of  entries  associated  with  a
;     particular "host reference number."
;
;  o  Deallocate a specific entry.
;
;  o  Read all entries.
;
;  o  Read the group of entries associated with  a  particular
;     "host reference number."
;
;  o  Decrement the  count  of  an  Allocation  Failure  Table
;     entry.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;
; Outputs:
;
;	R0  =  I/O function code
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;-

WRHIM::
	.JSB_ENTRY INPUT=<R2,R3,R4>,-
		   OUTPUT=<R0,R2,R3,R4>

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

	MOVL	UQB$L_UCB(R4),R5	; Check controller support.
	MOVL	UCB$L_CDDB(R5),R5	;
	BBS	#MSCP$V_CF_WHL,-	;
		CDDB$W_CNTRLFLGS(R5),-	;
		WRHIM_SUPPORTTED	;

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

WRHIM_SUPPORTTED:

	MOVL	HRB$L_IRP_CDRP(R3),R5	; Load IRP.
	MOVL	UQB$L_UCB(R4),-		; Load ucb address
		IRP$L_UCB(R5)		;
	MOVL	#IO$_WHM,IRP$L_FUNC(R5)	; Load function code
	MOVW	MSCP$W_OPER(R2),R1	; Load operation code

	DISPATCH R1,-			; Dispatch on operation code
		type=W,-		;  which is a word field 
		prefix=MSCP$K_WHM_,<-	;  and the beginning looks like this
		<DALL,DEALLOCATE_ALL>,-	
		<DHRN,DEALLOCATE_BY_HRN>,-
		<DELO,DEALLOCATE_BY_LOCATOR>,-
		<DAFC,DECREMENT_FAIL_COUNT>,-
		<RALL,READ_ALL>,-
		<RHRN,READ_BY_HRN>,-
		>

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

DEALLOCATE_ALL:
	BISL	#IO$M_DEALC_ALL,IRP$L_FUNC(R5)
	BRB	WRHIM_NONTRANSFER

DEALLOCATE_BY_HRN:
	BISL	#IO$M_DEALC_HRN,IRP$L_FUNC(R5)
	BRB	WRHIM_NONTRANSFER

DEALLOCATE_BY_LOCATOR:
	BISL	#IO$M_DEALC_ENTLOC,IRP$L_FUNC(R5)
	BRB	WRHIM_NONTRANSFER

DECREMENT_FAIL_COUNT:
	BISL	#IO$M_DECR_AFC,IRP$L_FUNC(R5)
	BRB	WRHIM_NONTRANSFER

READ_ALL:
	BISL	#IO$M_READ_ALL,IRP$L_FUNC(R5)
	BRB	WRHIM_READ

READ_BY_HRN:
	BISL	#IO$M_READ_HRN,IRP$L_FUNC(R5)
	BRB	WRHIM_READ

WRHIM_NONTRANSFER:
	MOVL	MSCP$W_HRN(R2),-	; Load write logging related
		IRP$L_CLN_WLE(R5)	;  information.

        MOVAB	10$,R0
	BSBW	DO_DISK
	BLBC	R0,3$
        RSB

3$:	MOVAB	WRHIM_ERR_TBL,R2	; Get the address of the error table
	BSBW 	XFER_ERR		; Otherwise get the MSCP error code
	BICL3	#^cMSCP$M_ST_MASK, -	; Extract major MSCP status
		R0, R1			;  
	CMPL	R1,#MSCP$K_ST_OFFLN	; Was this one of the offline errors?
	BNEQ	5$			; If not test for CNTLR
	BSBW	ERR_OFFLINE		; Otherwise do some offline processing
5$:	MOVL	HRB$L_MSGBUF(R3),R2	; MSCP packet address from the HRB
	MOVL	HRB$L_ABCNT(R3),-	; Set the byte count in the MSCP 
		MSCP$L_BYTE_CNT(R2)	;  to return to the requestor
	BRW	SEND_END		; Send an end packet with R0 status

10$:    .JSB_ENTRY	INPUT=<R0,R1,R2,R3,R4,R5>, -
			SCRATCH=<R0,R1,R2,R3,R4,R5>
	MOVL	HRB$L_MSGBUF(R3),R2	; Load message address.
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Load irp address.
	MOVL	IRP$L_IOST2(R5),R1	; Load the rest of the iost block

	ASHQ	#16,R0,R0		; Get byte count in R1.
	MOVL	#MSCP$K_ST_SUCC,R0	; Set a successful status to return
	MOVL	R1,MSCP$L_BYTE_CNT(R2)	; Return byte count.
	MOVW	R1,MSCP$W_COUNT(R2)	; Return "# of entries" count.
20$:	BRW	SEND_END		; Exit.

WRHIM_READ:

	TSTL	MSCP$L_BYTE_CNT(R2)	; Check for "# of entries"
	BEQL	WRHIM_NONTRANSFER	;  requests.

WRHIM_TRANSFER:
;
; Code copied from non-optimized read code.
;
; Allocate and load mapping resources to map the transfer buffer area
; in the requesting system.
;
	CLRL	HRB$L_LBN(R3)		; Fake LBN since it is a controller
					;  data transfer.
	MOVAB	30$,R0
	BSBW	ALLOCATE		; Find some memory for the transfer
	BLBS	R0,30$
	RSB

;
; Now prepare the CDRP for mapping the local buffer just allocated
; for use by SCS.
;

30$:    .JSB_ENTRY	INPUT=<R0,R1,R2,R3,R4,R5>, -
 			SCRATCH=<R0,R1,R2,R3,R4,R5>

	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
	MOVAL	HRB$L_SVAPTE(R3),R1	; Address of the three longword buffer
	CLRL	R2			; Access mode of transfer is kernel
	MAP				; Map the buffer
	MOVL	CDRP$L_BD_ADDR(R5), -	; Save the buffer descriptor address
		HRB$L_BD_ADDR(R3)	; 
	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	WRHIM_READ_LOOP		; Request was not aborted, continue...
	BRW	WRHIM_ABORT_READ	; Otherwise cleanup this request

;
; Prepare the IRP for the disk transfer. 
;
WRHIM_READ_LOOP:
	MOVL	HRB$L_IRP_CDRP(R3),R4	; Get the address of the IRP
	MOVL	HRB$L_UQB(R3),R5	; Get the UQB address
	MOVL	UQB$L_UCB(R5),-		;  so we can pull out the UCB address
		IRP$L_UCB(R4)		;  and save it away in the IRP
	MOVL	HRB$L_SVAPTE(R3),-	; Move the local buffer
		IRP$L_SVAPTE(R4)	;  virtual page table entry,
	MOVZWL	HRB$W_BOFF(R3),-	;  the byte offset within the page,
		IRP$L_BOFF(R4)		;  of the start of the buffer
	MOVL	HRB$L_BCNT(R3),-	;  and the byte count to be used for
		IRP$L_BCNT(R4)		;  this (portion of the) transfer.
	ASSUME	WHIS$K_WRITELOGLEN EQ 16; Derive offset from accum. bcnt.
	ASHL	#-4,HRB$L_ABCNT(R3),-	; Shift the byte count to get
		HRB$L_LBN(R3)		;  the offset.
	MOVL	HRB$L_MSGBUF(R3),R2	; Add in original offset.
	ADDL	MSCP$W_OFFSET(R2),-	;
		HRB$L_LBN(R3)		;
;
; Actually send off the IRP to the disk in this subroutine. If for some
; reason this request is aborted while the IRP is "out to disk", the entire
; request is cleaned up and finished off in the subroutine BACK and never
; heard from again.
;

	MOVAB	60$,R0
	BSBW	DO_DISK			; Execute a disk transfer
	BLBC	R0,50$			; We got the requested bytes from disk
	RSB

50$:	MOVAB	WRHIM_ERR_TBL,R2	; Get the address of the error table
	BSBW 	XFER_ERR		; Otherwise get the MSCP error code
	BICL3	#^cMSCP$M_ST_MASK, -	; Extract major MSCP status
		R0, R1			;  
	CMPL	R1,#MSCP$K_ST_OFFLN	; Was this one of the offline errors?
	BNEQ	55$			; If not test for CNTLR
	BSBW	ERR_OFFLINE		; Otherwise do some offline processing
55$:	MOVL	HRB$L_MSGBUF(R3),R2	; MSCP packet address from the HRB
	MOVL	HRB$L_ABCNT(R3),-	; Set the byte count in the MSCP 
		MSCP$L_BYTE_CNT(R2)	;  to return to the requestor
	BRW	SEND_END		; Send an end packet with R0 status


;
; Send the data to the host buffer
;
60$:    .JSB_ENTRY	INPUT=<R0,R1,R2,R3,R4,R5>, -
			SCRATCH=<R0,R1,R2,R3,R4,R5>

	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

;
; 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
	MOVL	HRB$L_ABCNT(R3),-	; Figure out the offset into the 
		CDRP$L_RBOFF(R5)	;  remote buffer 
	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 host buffer

;
; 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	PCHIST			; 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,WRHIM_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	WRHIM_ABORT_READ

;
; Update the accumulated byte count and compare to original to
; determine if another round is needed.  
;
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	DECL	HRB$L_CMD_STS(R3)	; Record the progress on this request
	MOVL	CDRP$L_XCT_LEN(R5),R0	; Pick up transfered bytes
	ASHL	#-IOC$V_BLOCK_BLKNUM,R0,R1	; Make it blocks
	ADDL	R0,HRB$L_ABCNT(R3)	; Calc accumulated bytes xfered
	SUBL3	HRB$L_ABCNT(R3), -	; Calc how much left to do
		HRB$L_OBCNT(R3),R2
	BLEQ	80$			; None, we are finished
	CMPL	R2,HRB$L_BCNT(R3)	; Compare the number of bytes remaining
	BGEQ	70$			;  to the number just transferred and
	MOVL	R2,HRB$L_BCNT(R3)	;  use the smaller of the two values
70$:	ADDL	R1,HRB$L_LBN(R3)	; Update LBN
	MOVL	HRB$L_UQB(R3),R0	; Record the fact that the server
	INCL	UQB$L_EXTRA_IO(R0)	;  An extra I/O has to be done
	BRW	WRHIM_READ_LOOP		; Loop again

;
; Update the byte count transferred, and send the end packet.
;
80$:	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	MOVL	HRB$L_ABCNT(R3),-	; Set byte count
		MSCP$L_BYTE_CNT(R2)	; 
	MOVL	#MSCP$K_ST_SUCC,R0	; This command has completed successfuly
	.IF DEFINED DEBUG$CURRENT_SANITY
	BSBW	CHECK_CURRENT		; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY
	BRW	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.
; 
WRHIM_ABORT_READ:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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	SEND_END		;  and send out an end message

10$:	BSBW	CLEANUP_HRB		; Deallocate the resources used
	BRW	UNBLOCK			;  and start up any eligible requests

	.PAGE
	.SBTTL	NON-SEQUENTIAL NON-BUFFER class commands

;+
; Functional Description:
;
; Non-sequential commands may be re-ordered by the controller to optimize
; performance. If there are any host requests in the blocked queue for a
; unit, the current request is also blocked. The presence of other blocked
; requests implies the presence of a sequential command, either executing,
; or pending.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  Status to be returned to requestor (on failure)
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;-

NONSEQ:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	BSBW	FIND_UQB		; Find the UQB that belongs to this
	BLBC	R0,70$			;  request and put the addr in the HRB
	MOVL	HRB$L_UQB(R3),R4	; Get the unit queue block address
	MOVL	HRB$L_HQB(R3),R5	; Get the host queue block address
	MOVZBL	HQB$B_HOSTNO(R5),R0	;  in order to get the host number
	BBS	R0,UQB$B_ONLINE(R4),40$	; If the online bit is set, continue
	MOVL	#MSCP$K_ST_AVLBL,R0	;  otherwise, load an available status
	BRW	SEND_END		;  and return in an end message

40$:	BBS	#UQB$V_SEQ,-		; If there is a sequential command
		UQB$W_FLAGS(R4),50$	;  currently in progress block this one
	TSTW	UQB$W_NUM_QUE(R4)	; If there are requests in this queue
	BEQL	60$			;  there is a sequential cmd pending
50$:	MOVAB	55$,R0			; Routine to invoke when unblocked
	BSBW	BLOCKED			; Block this request!
	RSB				; Done for now

55$:	.JSB_ENTRY INPUT=<R3>,SCRATCH=<R0,R1,R2,R3,R4,R5>
	MOVL	HRB$L_MSGBUF(R3),R2	; Get the address of the MSCP packet
	MOVL	HRB$L_UQB(R3),R4	;  and the address of the UQB

60$:	DISPATCH MSCP$B_OPCODE(R2),-	; Dispatch on command subtype
	type=B,-			; Field type we are dispatching on
	prefix=MSCP$K_OP_,<-		; Common prefix
	<ACCES,	ACCESS>,-		; Access data
	<CMPCD,	COMP_CTRL_DATA>,-	; Compare controller data
	<ERASE,	ERASE>,-		; Erase data
	<FLUSH,	FLUSH>,-		; Flush host buffers
	<REPLC,	REPLACE>,-		; Replace data
	>				; 

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

70$:	MOVL	#MSCP$K_ST_OFFLN,R0	; If the disk could not be found
	BRW	SEND_END		;  return an offline (unknown) status

	.PAGE
	.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:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; 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
;-

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

	.PAGE
	.SBTTL	-	COMPARE CONTROLLER DATA			(- 17 -)
;+
; Functional Description:
;
; All controllers that do not support either caching or shadowing must
; treat this command as a no-op that always succeeds. This command is 
; included in the minimal disk MSCP subset soley for compatability with
; controllers that do support caching or shadowing.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  Success
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

COMP_CTRL_DATA:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	#MSCP$K_ST_SUCC,R0	; This command always returns success
	BRW	SEND_END		; Send an end packet with R0 status

	.PAGE
	.SBTTL	-	ERASE					(- 18 -)
;+
; Functional Description:
;
; "All data in the specified region of the unit is erased by overwriting
; it with zero." This command is capable of spanning the buffer - non-buffer
; class boundary depending on a number of factors including:
;	- disk type
;	- history logging requirements
;	- segmentation requirements
; If a request meets requirements as detailed in the following table,
; no buffer is mapped, and the ERASE request is sent on to the driver. Failing
; to meet the requirements for forwarding an ERASE, the request is treated
; very much like a write command referencing a buffer of zeroes.
;
;			ERASE/WRITE decision table

; Request Type		|		Device Type			     |
;			| non-MSCP	| MSCP				     |
;			|		|				     |
; single-segment	| WRITE zeroes	| ERASE				     |
; multi-segment/	| WRITE zeroes	| ERASE				     |
;  no history log	|		|				     |
; multi-segment/	| N/A		| WRITE zeroes			     |
;  history log - seg 0	|		|				     |
; multi-segment/	| N/A		| WRITE zeroes			     |
;  history log - seg 1+	|		|  and force: Supplementary Write Log|
;			|		|	      ReUse		     |
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;
; 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
;-

ERASE:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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_WRTPH>,-	;  hardware write protected,
		UQB$W_UNIT_FLAGS(R4)	;  then
	BEQL	10$			;  go ahead and allow the write
	MOVL	#MSCP$K_ST_WRTPR,R0	; Otherwise, set an error status
	BRW	SEND_END		;  and return an end message

10$:	MOVL	HRB$L_HQB(R3),R5	; Get the Host Queue Block
	MOVL	HQB$L_DSRV(R5),R5	;  so we can get the server structure
	MOVL	MSCP$L_BYTE_CNT(R2),-	; Get the byte count passed
		HRB$L_OBCNT(R3)		;  and save it as the original byte cnt
	BNEQ	20$			; If its nonzero then go through with it
	INCL	DSRV$L_BLKCOUNT(R5)	; Count the zero block transfer
	MOVL	#MSCP$K_ST_SUCC,R0	; Set a successful status to return
	BRW	SEND_END		;  and we are finished!

; It is clear now that a disk transfer is necessary. Make sure that we are not
; going to go off the end of the device.

20$:	MOVL	HRB$L_OBCNT(R3),R1	; Set up the number of bytes to send
	DECL	R1			; Adjust for one less than the block cnt
	ASHL	#-IOC$V_BLOCK_BLKNUM,-	; Convert the number of bytes specified
		R1,R1			;  into a block count
	MOVL	MSCP$L_LBN(R2),R0	; Transfer the starting LBN
	MOVL	R0,HRB$L_LBN(R3)	; Save it away while it is handy
	ADDL	R1,R0			; Calculate ending block number
	MOVL	UQB$L_UCB(R4),R4	; Get the UCB address for this unit
	CMPL	R0,UCB$L_MAXBLOCK(R4)	; Make sure the ending block is on unit
	BLEQU	30$			; If it is, continue..
	MOVZWL	#MSCP$K_ST_ICMD -	; Return an illegal command
		!<MSCP$L_BYTE_CNT@8>,R0	;  with the byte count field as bad
	BRW	SEND_END		; Leave in disgrace

; By this point we have determined that the disk is in a receptive state, 
; and we actually do have to do some work  to complete this request. Set up
; the IRP for the transaction no matter what case it turns out to be.
 
30$:	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the address of the IRP
	MOVL	R4,IRP$L_UCB(R5)	; Save away the UCB address in the IRP
	MOVZWL	HRB$W_BOFF(R3),-	;  the byte offset within the page,
		IRP$L_BOFF(R5)		;  of the start of the buffer
	MOVL	R3,IRP$L_HRB(R5)	; Set the HRB address for IO completion
	MOVL	HRB$L_UQB(R3),R1	; Go through the structures to get the
	MOVL	UQB$L_UCB(R1),R1	;  UCB so we can look at the device type
	BBC	#DEV$V_MSCP,-		; MSCP-type device?
		UCB$L_DEVCHAR2(R1),35$	;  No  - change to write
	; Set up write log information.
	BBC	#MSCP$V_MD_HISLO,-	; Just start if no write log is
		MSCP$W_MODIFIER(R2),31$	;  requested.
	BISL	#IRP$M_WLE,-		; Indicate write log.
		IRP$L_STS2(R5)		;
	MOVL	MSCP$W_HRN(R2),-	; Copy write log entry information.
		IRP$L_CLN_WLE(R5)	;
	BBC	#MSCP$V_MD_REUSE,-	; Check for entry reuse.
		MSCP$W_MODIFIER(R2),31$	;
	BISB	#IRP$M_WLE_REUSE,-	; Indicate write log.
		IRP$B_WLG_FLAGS(R5)	;
31$:	MOVL	HRB$L_OBCNT(R3),-	; Do the whole erase in one command
		HRB$L_BCNT(R3)		;  since it is a supported command
	CMPL	HRB$L_BCNT(R3),-	; Requested byte count
		UCB$L_MAXBCNT(R1)	;  supported for this device?
	BLEQU	32$			;   Yes - no problem
	MOVL	UCB$L_MAXBCNT(R1),-	;   No - limit the size of this
		HRB$L_BCNT(R3)		;	 transfer to the device limit
	BBS	#MSCP$V_MD_HISLO,-	; Write log history?
		MSCP$W_MODIFIER(R2),35$	;   Yes - change to write
32$:	CLRL	HRB$L_SVAPTE(R3)	; No SVAPTE is necessary if supported
	MOVL	#IO$_DSE,R0		; Put the function code in a register
	MOVL	MSCP$L_LBN(R2),-	;  then save it in the HRB and put
		IRP$L_MEDIA(R5)		;  another copy of it in the IRP
	BRB	50$			; Perform the I/O

; If the device we are interested in is:
;  a) not an MSCP-type device or
;  b) an MSCP-type device to which an ERASE is to be issued with history
;     logging and which is forced into segmentation,
; then the erase is emulated by writing to the specified area with zeroes.
 
35$:	MOVL	HRB$L_OBCNT(R3),R1	; Get the original byte count back
	ASHL	#-IOC$V_BLOCK_BLKNUM,-	; Convert the number of bytes specified
		R1,R1			;  into a block count
	CMPL	R1,#127			; If less than 128,
	BLEQU	40$			;  just use the original value
	MOVL	#127,R1			; Else make the size 127 blocks
40$:	MOVL	G^SCS$GL_MSCP,R4	; Get the server's private structure
	INCL	DSRV$L_BLKCOUNT(R4)[R1]	; Keep the counters current

; 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.
 
	ASHL	#IOC$V_BLOCK_BLKNUM,-	; Convert the number of blocks just 
		R1,R1			;  calculated back into number of bytes
	CMPL	R1,HRB$L_OBCNT(R3)	; Compare this back to the original
	BLEQU	45$			; If the new value is smaller use it
	MOVL	HRB$L_OBCNT(R3),R1	;  otherwise keep the original size
45$:	MOVL	R1,HRB$L_BCNT(R3)	; Save the number of bytes to transfer
	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 disk. 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
	BBC	#MSCP$V_MD_COMP,-	; If the compare modifier is not set
		MSCP$W_MODIFIER(R2),60$	;  then continue...
	BISL	#IO$M_DATACHECK,R0	;  otherwise, set the datacheck modifier
60$:	BBC	#MSCP$V_MD_SEREC,-	; If the supress error correct modidier
		MSCP$W_MODIFIER(R2),70$	;  is not set then continue...
	BISW	#IO$M_INHRETRY,R0	;  else, set the inhibit retry modifier
70$:	MOVL	R0,IRP$L_FUNC(R5)	; Set the completed code in the IRP

; Send this request to the disk. If the request is aborted while on the
; disk queue, it is cleaned up when control is returned to BACK, and 
; never heard from again.

72$:	MOVAB	80$,R0			; Completion routine
	BSBW	DO_DISK			; Give the request to the disk
	BLBC	R0,73$			; Branch if request not queued
	RSB				; Exit for now if request queued

73$:	MOVAB	ERR_TBL,R2	; Get the address of the error table
	BSBW 	XFER_ERR		; Otherwise get the MSCP error code
	BICL3	#^cMSCP$M_ST_MASK, -	; Extract major MSCP status
		R0, R1			;  
	CMPL	R1,#MSCP$K_ST_OFFLN	; Was this one of the offline errors?
	BNEQ	74$			; If not test for write lock
	BSBW	ERR_OFFLINE		; Otherwise do some offline processing
	BRW	76$			;  and go to the common exit
74$:	CMPL	R1,#MSCP$K_ST_ICMD	; invalid command?
	BNEQ	75$			; If NEQ no
	BSBW	ERR_WLG			; Report WLG invalid command
	CLRL	HRB$L_ABCNT(R3)		; Zero transferred bytecount
	BRW	76$			; Merge
75$:	CMPL	R1,#MSCP$K_ST_WRTPR	; Was this a write lock error?
	BNEQ	76$			; If not just return the error
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address 
	BBSS	#MSCP$V_UF_WRTPH,-	; Set the write protect bit in
		UQB$W_FLAGS(R4),76$	;  the unit flag field
76$:	MOVL	HRB$L_MSGBUF(R3),R2	; MSCP packet address from the HRB
	MOVL	HRB$L_ABCNT(R3),-	; Set the byte count in the MSCP 
		MSCP$L_BYTE_CNT(R2)	;  to return to the requestor
	BRW	SEND_END		; Send an end packet with R0 status

;+
; Resumed here when the transfer has completed
;
;   Inputs to the async completion routine:
;	R0 contains the I/O status  block
;	R1 contains the address of the UCB
;	R2 contains the message buffer address
;	R3 contains the address of the HRB
;	R4 contains the address of the UQB
;	R5 contains the address of the IRP
;
;   Outputs;
;
;	R0-R5 may be destroyed
;
;-

80$:	.JSB_ENTRY 	INPUT=<R0,R1,R2,R3,R4,R5>, -
			SCRATCH=<R0,R1,R2,R3,R4,R5>

	BLBC	R0,110$			; Branch if transfer error
	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
	MOVL	IRP$L_BCNT(R5),R0	;  and find out how many bytes sent
	ASHL	#-IOC$V_BLOCK_BLKNUM,-	; Convert the number of bytes specified
		R0,R1			;  into a block count
	ADDL	R0,HRB$L_ABCNT(R3)	; Update the bytes sent so far
	SUBL3	HRB$L_ABCNT(R3),-	; Determine how many characters 
		HRB$L_OBCNT(R3),R2	;  remain to be sent
	BLEQU	100$			; Nothing left, the erase is finished
	CMPL	R2,HRB$L_BCNT(R3)	; If the number of bytes remaining
	BGEQU	90$			;  is less than that just transferred,
	MOVL	R2,HRB$L_BCNT(R3)	;  then shrink the size of the request
90$:	MOVL	HRB$L_BCNT(R3),-	; Move the size of the request we
		IRP$L_BCNT(R5)		;  finally decided on into the IRP
	ADDL	R1,HRB$L_LBN(R3)	; Move logically down the disk
	MOVL	HRB$L_UQB(R3),R4	; Record the fact that the server
	INCL	UQB$L_EXTRA_IO(R4)	;  had to split the transfer
	BBC	#IRP$V_WLE,-		; Write logging?
		IRP$L_STS2(R5),72$	;  No  - process next segment

; At this point a check is made to see if there was an allocation failure
; for the write log entry. If so, the server must still attempt to complete
; the Erase operation for all segments (per MSCP spec: 6.9.3) with write
; logging turned off. If there was no allocation failure, there is only
; one entry for the client node to do the write so the supplemental write
; log- and force-reuse flags are set. The client request might or might not
; have the reuse flag set, but it is appropriate to set it now since
; the write/erase is being fragmented.

	CMPW	#^XFFFF,IRP$L_CLN_WLE(R5); Watch out for exhaustion
	BNEQ	95$			;
	BICL	#IRP$M_WLE,-		; Stop write log.
		IRP$L_STS2(R5)		;
	BRB	72$			;
95$:
	BISB	#<IRP$M_WLE_REUSE!-	; Force the reuse bit
		  IRP$M_WLE_SUPWL>,-	;  and  the supplement bit
		IRP$B_WLG_FLAGS(R5)	;  in IRP.

	BRB	72$			; Send the next chunk of zeros

100$:	MOVL	#MSCP$K_ST_SUCC,R0	; Set the completion code to success
	BRW	130$

110$:		
	MOVAB	ERR_TBL,R2		; Get the address of the error table
	BSBW 	XFER_ERR		; Otherwise get the MSCP error code
	BICL3	#^cMSCP$M_ST_MASK, -	; Extract major MSCP status
		R0, R1			;  
	CMPL	R1,#MSCP$K_ST_OFFLN	; Was this one of the offline erroR
	BNEQ	115$			; If NEQ no
	BSBW	ERR_OFFLINE             ; Otherwise do some offline processing 
	BRW	130$			; Merge
115$:	CMPL	R1,#MSCP$K_ST_DATA	; Parity or Forced Error
	BNEQ	118$			; If NEQ no
	MOVL	IRP$L_IOST1+2(R5), -	; Pick up the number of bytes
		HRB$L_ABCNT(R3)		;  transfered and update ABCNT.
	BRW	130$			; Merge
118$:	CMPL	R1,#MSCP$K_ST_ICMD	; Invalid command
	BNEQ	120$			; If NEQ no
	BSBW	ERR_WLG			; Report WLG invalid command
	CLRL	HRB$L_ABCNT(R3)		; Zero transferred bytecount
	BRW	130$			; Merge
120$:	CMPL	R1,#MSCP$K_ST_WRTPR	; Was this a write lock error?
	BNEQ	130$			; If not just return the error
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address 
	BBSS	#MSCP$V_UF_WRTPH,-	; Set the write protect bit in
		UQB$W_FLAGS(R4),130$	;  the unit flag field

130$:	MOVL	HRB$L_MSGBUF(R3),R2	; MSCP packet address from the HRB
	BBC	#MSCP$V_MD_HISLO,-	; If not write logging, continue
		MSCP$W_MODIFIER(R2),135$;
        MOVL    IRP$L_CLN_WLE(R5),-	; Copy (entry id, entrylocator)
                MSCP$W_HRN(R2)		;
135$:	MOVL	HRB$L_ABCNT(R3),-	; Set the byte count in the MSCP 
		MSCP$L_BYTE_CNT(R2)	;  to return to the requestor
	BRW	SEND_END		; Send an end packet with R0 status

	.PAGE
	.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
; disk MSCP subset soley for compatability with controllers that do support
; caching.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;
; Outputs:
;
;	R0  =  Success
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

FLUSH:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVL	#MSCP$K_ST_SUCC,R0	; This command is always successful
	BRW	SEND_END		; Send an end packet with R0 status

	.PAGE
	.SBTTL	-	REPLACE					(- 20 -)
;+
; Functional Description:
;
; The specified logical block is flagged to indicate that it has been replaced
; with the specified replacement block. The volume's Replacement and Caching
; Table must have been updated prior to using this command, and the replacement
; block should be initialized with a write command to the same logical block
; number after using this command. 
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; 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
;-

REPLACE:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	ROTL	#24,#MSCP$B_OPCODE,R0	; Unsupported command
	BRW	PACKET_ERROR		; Return an error packet

	.PAGE
	.SBTTL	NON-SEQUENTIAL BUFFER class commands

;+
; Functional Description:
;
; Buffer class commands imply that part of the local transfer buffer 
; (allocated by the server during initialization) is used during the 
; execution of this command.
; 
; Non-sequential commands may be re-ordered by the controller to optimize
; performance. If there are any host requests in the blocked queue for a
; unit, the current request is also blocked. The presence of other blocked
; requests implies the presence of a sequential command, either executing,
; or pending.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  Status to be returned to requestor (on failure)
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;-

NONSEQB:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	BSBW	FIND_UQB		; Find the UQB that belongs to this
	BLBC	R0,50$			;  request and put the addr in the HRB

;
; Make sure the disk is on line to the requestor, before we permit any of 
; these commands to go through.
;
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address
	MOVL	HRB$L_HQB(R3),R5	; Otherwise, get the host queue block
	MOVZBL	HQB$B_HOSTNO(R5),R0	;  so we can find out the host number
	BBS	R0,UQB$B_ONLINE(R4),20$	;  and check the online bit in the UQB
	MOVL	#MSCP$K_ST_AVLBL,R0	; This disk is online, but not to the
	BRW	SEND_END		;  client node that made this request

;
; The disk looks fine. Now check to make sure that there are no sequential
; commands in progress, or pending on the blocked queue.
;
20$:	BBS	#UQB$V_SEQ,-		; If there are sequential commands
		UQB$W_FLAGS(R4),30$	;  in progress, just hold it right here
	TSTL	UQB$W_NUM_QUE(R4)	; If there are NO requests in the queue
	BEQL	40$			;  no cmds are pending and we can cont..
30$:	MOVAB	35$,R0			; Routine to invoke when unblocked
	BSBW	BLOCKED			; Block this request
	RSB				; Done for now

35$:	.JSB_ENTRY INPUT=<R3>,SCRATCH=<R0,R1,R2,R3,R4,R5>
	MOVL	HRB$L_MSGBUF(R3),R2	; Get the address of the MSCP packet
	MOVL	HRB$L_UQB(R3),R4	;  and the address of the UQB

; 
; If the blocked subroutine is called, this request is stalled until it is
; removed from the blocked queue, and execution resumed below.
;
40$:	DISPATCH MSCP$B_OPCODE(R2),-	; Dispatch on command subtype
		type=B,-		; Data type we are dispatching on
		prefix=MSCP$K_OP_,<-		; 
		<COMP,	COMPARE_HOST_DATA>,- 	; Compare host data
		<READ,	READ>,-			; Read physical
		<WRITE,	WRITE>,-		; Write physical
		>

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

50$:	MOVL	#MSCP$K_ST_OFFLN,R0	; If the disk could not be found,
	BRW	SEND_END		;  return an offline (unknown) status

	.PAGE
	.SBTTL	-	COMPARE HOST DATA			(- 32 -)
;+
; Functional Description:
;
; Data is read from te requesting cluster member and compared to the data
; on the specified disk. 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 funtion code.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;
; Outputs:
;
;	R0  =  I/O function code
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;-

COMPARE_HOST_DATA:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	BRW	DO_WRT			;  and go do it

	.PAGE
	.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:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;
; Outputs:
;
;	R0  =  Read completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-
READ:
	.JSB_ENTRY	INPUT=<R2,R3,R4>

	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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.
;
	MOVAB	READ_RESUME,R0		; Completion routine in case of stall	
	BSBW	ALLOCATE		; Find some memory for the transfer
	BLBS	R0,READ_RESUME		; Continue upon success
	RSB				; Else exit for now
;+
; Input:
;	R2  =  MSCP packet address
;	R3  =  HRB address
; Output:
;	R0-R5 may be destroyed
;-
READ_RESUME:
	.JSB_ENTRY	INPUT=<R2,R3>,SCRATCH=<R0,R1,R2,R3,R4,R5>
;
; 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
	MOVAL	HRB$L_SVAPTE(R3),R1	; Address of the three longword buffer
	CLRL	R2			; Access mode of transfer is kernel
	MAP				; Map the buffer
	MOVL	CDRP$L_BD_ADDR(R5), -	; Save the buffer descriptor address
		HRB$L_BD_ADDR(R3)	; 
	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	READ_LOOP		; Request was not aborted, continue...
	BRW	ABORT_READ		; Otherwise cleanup this request

;
; Prepare the IRP for the disk transfer. 
;
READ_LOOP:
	MOVL	HRB$L_IRP_CDRP(R3),R4	; Get the address of the IRP
	MOVL	HRB$L_UQB(R3),R5	; Get the UQB address
	MOVL	UQB$L_UCB(R5),-		;  so we can pull out the UCB address
		IRP$L_UCB(R4)		;  and save it away in the IRP
	MOVL	HRB$L_SVAPTE(R3),-	; Move the local buffer
		IRP$L_SVAPTE(R4)	;  virtual page table entry,
	MOVZWL	HRB$W_BOFF(R3),-	;  the byte offset within the page,
		IRP$L_BOFF(R4)		;  of the start of the buffer
	MOVL	HRB$L_BCNT(R3),-	;  and the byte count to be used for
		IRP$L_BCNT(R4)		;  this (portion of the) transfer.

;
; Check for a READ RCT request. If a READ command is received by the server 
; for a LBN beyond the user visible portion of the volume, it must be a READ
; for the RCT. To verify this, make sure the byte count is exactly 512 bytes, 
; and the LBN specified is not beyond the end of the Revector Cache Tables for
; this device. If these tests are passed, set the function code to IO$_READRCT 
; and join the common code. 
;
; 
;
	MOVL	HRB$L_MSGBUF(R3),R2	; Get back the MSCP packet address
	MOVL	UQB$L_UCB(R5),R1	; Get the UCB address for later refrence
	CMPL	MSCP$L_LBN(R2),-	; Check to see if the LBN specified
		UCB$L_MAXBLOCK(R1)	;  for this read is beyond host range
					; Note: this is an implicit check for a
					;  mscp device.
	BLEQU	20$			; If not, continue with a normal read
	CMPL	MSCP$L_BYTE_CNT(R2),#512; If the read that is meant for the RCT
	BGTRU	10$			;  is larger than one block it's bad
	CMPL	MSCP$L_LBN(R2), -	; Then see if the read is beyond 
		UCB$L_DU_TOTSZ(R1)	;  the end of the RCT area too 
	BGTRU	10$			; If so, it is invalid
	MOVL	#IO$_READRCT,R0		; Set the I/O function code to be used
	BRB	30$			;  and continue with the main line code
10$:	MOVL	#MSCP$K_ST_ICMD,R0	; If there was an error in the read
	BRW	SEND_END		;  return an invalid command

20$:	MOVL	#IO$_READPBLK,R0	; Pass I/O function code to be used

;
; 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.
;
30$:	BBC	#MSCP$V_MD_COMP,-	; If the compare modifier was set in
		MSCP$W_MODIFIER(R2),40$	;  the MSCP packet,
	BISL	#IO$M_DATACHECK,R0	;  then set it in the IRP also
40$:	BBC	#MSCP$V_MD_SEREC,-	; If the  modifier was set in
		MSCP$W_MODIFIER(R2),50$	;  the MSCP packet,
	BISL	#IO$M_INHRETRY,R0	;  then set it in the IRP also
50$:	MOVL	R0,IRP$L_FUNC(R4)	; Fill in the function code

;
; Actually send off the IRP to the disk in this subroutine. If for some
; reason this request is aborted while the IRP is "out to disk", the entire
; request is cleaned up and finished off in the subroutine BACK and never
; heard from again.
;
	MOVAB	55$,R0			; Completion routine
	BSBW	DO_DISK			; Execute a disk transfer
	BLBC	R0,51$			; Error if not queued
	RSB				; Exit for now if queued

51$:	MOVAB	ERR_TBL,R2		; Get the address of the error table
	BSBW 	XFER_ERR		; Get the MSCP error code
	BICL3	#^cMSCP$M_ST_MASK,R0,R1	; Extract major MSCP status
	CMPL	R1,#MSCP$K_ST_OFFLN	; Was this one of the offline errors?
	BNEQ	52$			; If not test for write lock
	BSBW	ERR_OFFLINE		; Otherwise do some offline processing
	BRW	53$			;  and go to the common exit
52$:	CMPL	R1,#MSCP$K_ST_WRTPR	; Was this a write lock error?
	BNEQ	53$			; If not just return the error
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address 
	BBSS	#MSCP$V_UF_WRTPH,-	; Set the write protect bit in
		UQB$W_FLAGS(R4),53$	;  the unit flag field
53$:	MOVL	HRB$L_MSGBUF(R3),R2	; MSCP packet address from the HRB
	MOVL	HRB$L_ABCNT(R3),-	; Set the byte count in the MSCP 
		MSCP$L_BYTE_CNT(R2)	;  to return to the requestor
	BRW	SEND_END		; Send an end packet with R0 status

;+
; Resumed here when the transfer has completed
;
;   Inputs to the async completion routine:
;	R0 I/O status  block
;	R1 address of the UCB
;	R2 message buffer address
;	R3 address of the HRB
;	R4 address of the UQB
;	R5 address of the IRP
;
;   Outputs;
;	R0-R5 may be destroyed
;-

55$:	.JSB_ENTRY 	INPUT=<R0,R1,R2,R3,R4,R5>, -
			SCRATCH=<R0,R1,R2,R3,R4,R5>

	BLBS	R0,64$			; The request succeeded...

	MOVAB	ERR_TBL,R2		; Get the address of the error table
	BSBW 	XFER_ERR		; Get the MSCP error code
	BICL3	#^cMSCP$M_ST_MASK,R0,R1	; Extract major MSCP status
	CMPL	R1,#MSCP$K_ST_DATA	; Parity or Forced Error
	BNEQ	61$			; If NEQ no
;
; Send the data to the host buffer
;
	MOVL	IRP$L_IOST1+2(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_MSGBUF(R3),R2	; Get the MSCP packet address back
	MOVW	R0,MSCP$W_STATUS(R2)	; Indicate Parity or Forced Error in msg
;
; Increment the HULB counter for load balancing purposes
; (note: if the transfer is segmented, the counter will be bumped for each
; segment)
;
	CLRL	R0			; Ensure that R0 has no excess baggage
	BICW3	#MSCP$M_SLUN,-		; Extract unit number for use as index,
		MSCP$W_UNIT(R2),R0	;  clearing the SLUN bit
	MOVL	HRB$L_HQB(R3),R1	; Pick up HQB
	MOVL	HQB$L_HULB_VECTOR(R1),R1; Get pointer to HQB vector
	BEQL	60$			; HULB vector has vanished...
	MOVL	(R1)[R0],R1		; Index into vector with unit number.
	BEQL	60$			; HULB has vanished...
	INCW	HULB$W_OPCOUNT(R1)	; Increment HULB counter.
;
; Initialize a CDRP that SCS can use to send the retrieved data to the 
; requesting system.
;
60$:	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
	CLRL	CDRP$L_RWCPTR(R5)	; Clear RWAITCNT reference to avoid
					;  incorrect stalls.
	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
	MOVL	HRB$L_ABCNT(R3),-	; Figure out the offset into the 
		CDRP$L_RBOFF(R5)	;  remote buffer 
	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
;
; fill in the End Packet information
; 
	ADDL3	HRB$L_ABCNT(R3),-	; bytes sent prior to this
		CDRP$L_XCT_LEN(R5),-	;  + bytes to be sent this transfer 
		MSCP$L_BYTE_CNT(R2)	;  = total bytes transfered
	MOVL	MSCP$L_BYTE_CNT(R2),-	; update the HRB, why not... 
		HRB$L_ABCNT(R3)		;  nothing short of disaster could stop
					;  the last packet now!
	MOVZBL	MSCP$B_OPCODE(R2),R1	; get opcode
	BISB2	#MSCP$K_OP_END,-	; Reset the op-code to
		MSCP$B_OPCODE(R2)	;  make an end packet
	MOVZBL	L^END_PKT_LEN[R1],R1	; Get the message length from the table
	MOVL	R2,CDRP$L_MSG_BUF(R5)	; Put the message buffer address into
	.IF DEFINED DEBUG$LOG

	ASSUME DSRV$V_LOG_ENABLD  EQ  0

	MOVL	G^SCS$GL_MSCP,R0	; Get the DSRV address.
	BLBC	DSRV$W_STATE(R0),57$	; Branch if logging is disabled.
        PUSHL	R0
	MOVL	#PKT$C_MSCP_END,R0
	BSBW	LOG_PKT		; Otherwise, log the end packet.
	POPL	R0
57$:	
	.ENDC

	CLRL	HRB$L_MSGBUF(R3)	; Message buffer belongs to SCS again
	MOVW	#HRB$K_ST_SNDAT_WAIT,-	; Set the state for this request
		HRB$W_STATE(R3)		;  before calling SCS service
	
	SEND_DATA_WMSG			; send the last SEND_DATA w/piggyback
					;  end message. NOTE: SEND_DATA_WMSG will
					;  get *all* required send credits for the
					;  block transfer, this is why RECYCL_MSG_BUF
					;  is not needed.

	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis
	BSBW	CLEANUP_HRB		; Deallocate all HRB held resources
	BRW	UNBLOCK			;  and drop the "current" counter



61$:	CMPL	R1,#MSCP$K_ST_OFFLN	; Was this one of the offline errors?
	BNEQ	62$			; If not test for write lock
	BSBW	ERR_OFFLINE		; Otherwise do some offline processing
	BRW	63$			;  and go to the common exit
62$:	CMPL	R1,#MSCP$K_ST_WRTPR	; Was this a write lock error?
	BNEQ	63$			; If not just return the error
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address 
	BBSS	#MSCP$V_UF_WRTPH,-	; Set the write protect bit in
		UQB$W_FLAGS(R4),53$	;  the unit flag field
63$:	MOVL	HRB$L_MSGBUF(R3),R2	; MSCP packet address from the HRB
	MOVL	HRB$L_ABCNT(R3),-	; Set the byte count in the MSCP 
		MSCP$L_BYTE_CNT(R2)	;  to return to the requestor
	BRW	SEND_END		; Send an end packet with R0 status
;
; Send the data to the host buffer
;
64$:	MOVL	IRP$L_IOST1+2(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

;
; Increment the HULB counter for load balancing purposes
; (note: if the transfer is segmented, the counter will be bumped for each
; segment)
;
	CLRL	R0			; Ensure that R0 has no excess baggage
	BICW3	#MSCP$M_SLUN,-		; Extract unit number for use as index,
		MSCP$W_UNIT(R2),R0	;  clearing the SLUN bit
	MOVL	HRB$L_HQB(R3),R1	; Pick up HQB
	MOVL	HQB$L_HULB_VECTOR(R1),R1; Get pointer to HQB vector
	BEQL	65$			; HULB vector has vanished...
	MOVL	(R1)[R0],R1		; Index into vector with unit number.
	BEQL	65$			; HULB has vanished...
	INCW	HULB$W_OPCOUNT(R1)	; Increment HULB counter.
;
; Initialize a CDRP that SCS can use to send the retrieved data to the 
; requesting system.
;
65$:	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
	CLRL	CDRP$L_RWCPTR(R5)	; Clear RWAITCNT reference to avoid
					;  incorrect stalls.
	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
	MOVL	HRB$L_ABCNT(R3),-	; Figure out the offset into the 
		CDRP$L_RBOFF(R5)	;  remote buffer 
	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
;
; Check if this will be the last block tranfer for this request, that is
; bytes left to send =< size of each block transfer.
;
	SUBL3	HRB$L_ABCNT(R3),-	; Get bytes left to tranfer for this
		HRB$L_OBCNT(R3),R1	;  Request (OBCNT-ABCNT)
	CMPL	R1,HRB$L_BCNT(R3)	; (OBCNT - ABCNT) =< BCNT)?
	BGTRU	80$			; If GTR no, use plain SEND_DATA
;
; This block transfer can contain all remaining bytes for the request.
; Issue combined SEND_DATA with piggyback end message request for this final
; transfer of the request.

;
; fill in the End Packet information
; 
	ADDL3	HRB$L_ABCNT(R3),-	; bytes sent prior to this
		CDRP$L_XCT_LEN(R5),-	;  + bytes to be sent this transfer 
		MSCP$L_BYTE_CNT(R2)	;  = total bytes transfered
	ASSUME  MSCP$K_ST_SUCC EQ 0	; set status success (Note: either the 
	CLRW	MSCP$W_STATUS(R2)	;  SEND_DATA_WMSG will complete sucessfully
					;  or SCS will not deliver the piggyback
					;  end packet!)
	MOVL	MSCP$L_BYTE_CNT(R2),-	; update the HRB, why not... 
		HRB$L_ABCNT(R3)		;  nothing short of disaster could stop
					;  the last packet now!
	MOVZBL	MSCP$B_OPCODE(R2),R1	; get opcode
	BISB2	#MSCP$K_OP_END,-	; Reset the op-code to
		MSCP$B_OPCODE(R2)	;  make an end packet
	MOVZBL	L^END_PKT_LEN[R1],R1	; Get the message length from the table
	MOVL	R2,CDRP$L_MSG_BUF(R5)	; Put the message buffer address into
	.IF DEFINED DEBUG$LOG

	ASSUME DSRV$V_LOG_ENABLD  EQ  0

	MOVL	G^SCS$GL_MSCP,R0	; Get the DSRV address.
	BLBC	DSRV$W_STATE(R0),70$	; Branch if logging is disabled.
        PUSHL	R0
	MOVL	#PKT$C_MSCP_END,R0
	BSBW	LOG_PKT		; Otherwise, log the end packet.
	POPL	R0
70$:	
	.ENDC

	CLRL	HRB$L_MSGBUF(R3)	; Message buffer belongs to SCS again
	MOVW	#HRB$K_ST_SNDAT_WAIT,-	; Set the state for this request
		HRB$W_STATE(R3)		;  before calling SCS service
	
	SEND_DATA_WMSG			; send the last SEND_DATA w/piggyback
					;  end message. NOTE: SEND_DATA_WMSG will
					;  get *all* required send credits for the
					;  block transfer, this is why RECYCL_MSG_BUF
					;  is not needed.

	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis
	BSBW	CLEANUP_HRB		; Deallocate all HRB held resources
	BRW	UNBLOCK			;  and drop the "current" counter
	
;
; not the last packet, just use a regular old SEND_DATA
; 
80$:
	MOVW	#HRB$K_ST_SNDAT_WAIT,-	; Set the state of this request to
		HRB$W_STATE(R3)		;  SCS block transfer
	SEND_DATA			; Send to host buffer

;
; 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	PCHIST			; 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,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	ABORT_READ

;
; Update the accumulated byte count and compare to original to
; determine if another round is needed.  
;
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	DECL	HRB$L_CMD_STS(R3)	; Record the progress on this request
	MOVL	CDRP$L_XCT_LEN(R5),R0	; Pick up transfered bytes
	ASHL	#-IOC$V_BLOCK_BLKNUM,-	; Convert the number of bytes specified
		R0,R1			;  into a block count
	ADDL	R0,HRB$L_ABCNT(R3)	; Calc accumulated bytes xfered
	SUBL3	HRB$L_ABCNT(R3), -	; Calc how much left to do
		HRB$L_OBCNT(R3),R2
	BLEQU	100$			; None, we are finished
	CMPL	R2,HRB$L_BCNT(R3)	; Compare the number of bytes remaining
	BGEQU	90$			;  to the number just transferred and
	MOVL	R2,HRB$L_BCNT(R3)	;  use the smaller of the two values
90$:	ADDL	R1,HRB$L_LBN(R3)	; Update LBN
	MOVL	HRB$L_UQB(R3),R0	; Record the fact that the server
	INCL	UQB$L_EXTRA_IO(R0)	;  An extra I/O has to be done
	BRW	READ_LOOP		; Loop again

100$:	BUG_CHECK MSCPSERV, FATAL	; bytes remaining =< BCNT? Should have
					;  exited through SEND_DATA_WMSG
;
; If the send data was unsuccessful, just free the allocated resources, and
; this request is finished off.
; 
ABORT_READ:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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	SEND_END		;  and send out an end message

10$:	BSBW	CLEANUP_HRB		; Deallocate the resources used
	BRW	UNBLOCK			;  and start up any eligible requests

	.PAGE
	.SBTTL	-	WRITE					(- 34 -)
;+
; Functional Description:
;
; This routine is called to process a MSCP write request. The information 
; the cluster member wishes to place on the disk is transferred to the local
; serving processor (subject to availability of resources on that processor),
; and from there is written on to the proper disk.
;
; Inputs:
;
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;
; Outputs:
;
;	R0  =  Write completion status
;	R2  =  MSCP packet address
;	R3  =  HRB address
;-

WRITE:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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_WRTPH>,-	;  or software write protected,
		UQB$W_UNIT_FLAGS(R4)	;  then
	BEQL	DO_WRT			;  go ahead and allow the write
	MOVL	#MSCP$K_ST_WRTPR,R0	; Otherwise, set an error status
	BRW	SEND_END		; ... and return an end message

DO_WRT:

;
; Allocate a local buffer area to use during the transfer, and map 
; that area for use by SCS routines.
;
	MOVAB	WRT_RESUME,R0		; Completion routine in case of stall
	BSBW	ALLOCATE		; Find some local memory for the xfer
	BLBS	R0,WRT_RESUME		; Continue if buffer available
	RSB

;+
; Input:
;	R2  =  MSCP packet address
;	R3  =  HRB address
; Output:
;	R0-R5 may be destroyed
;-
WRT_RESUME:
	.JSB_ENTRY	INPUT=<R2,R3>,SCRATCH=<R0,R1,R2,R3,R4,R5>
;
; 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
	MOVAL	HRB$L_SVAPTE(R3),R1	; Address of the three longword buffer
	CLRL	R2			; Access mode of transfer is kernel
	MAP				; Map the buffer
	MOVL	CDRP$L_BD_ADDR(R5), -	; Save the buffer descriptor address
		HRB$L_BD_ADDR(R3)	; 
	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	WRITE_LOG		; 
	BRW	ABORT_WRITE		;  otherwise abort this request here

;
; Set up write logging information before the first transfer.
;
WRITE_LOG:
	MOVL	HRB$L_MSGBUF(R3),R2	; Get the MSCP packet address back
	MOVL	HRB$L_IRP_CDRP(R3),R5	; Get the address of the IRP

	BBC	#MSCP$V_MD_HISLO,-	; Just start if no write log is
		MSCP$W_MODIFIER(R2),-	;  requested.
		WRITE_LOOP		;
	BISL	#IRP$M_WLE,-		; Indicate write log.
		IRP$L_STS2(R5)		;
	MOVL	MSCP$W_HRN(R2),-	; Copy write log entry information.
		IRP$L_CLN_WLE(R5)	;
	BBC	#MSCP$V_MD_REUSE,-	; Check for entry reuse.
		MSCP$W_MODIFIER(R2),-	;
		WRITE_LOOP		;
	BISB	#IRP$M_WLE_REUSE,-	; Indicate write log.
		IRP$B_WLG_FLAGS(R5)	;


WRITE_LOOP:
	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

;
; Increment the HULB counter for load balancing purposes
; (note: if the transfer is segmented, the counter will be bumped for each
; segment)
;
	MOVL	HRB$L_MSGBUF(R3),R2	; Get the MSCP packet address back
	CLRL	R0			; Ensure that R0 has no excess baggage
	BICW3	#MSCP$M_SLUN,-		; Extract unit number for use as index,
		MSCP$W_UNIT(R2),R0	;  clearing the SLUN bit
	MOVL	HRB$L_HQB(R3),R1	; Pick up HQB
	MOVL	HQB$L_HULB_VECTOR(R1),R1; Get pointer to HQB vector
	BEQL	2$			; HULB vector has vanished...
	MOVL	(R1)[R0],R1		; Index into vector with unit number.
	BEQL	2$			; HULB has vanished...
	INCW	HULB$W_OPCOUNT(R1)	; Increment HULB counter.
;
; Initialize a CDRP that SCS can use to send the retrieved data to the 
; requesting system.
;       
2$:	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
	CLRL	CDRP$L_RWCPTR(R5)	; Clear RWAITCNT reference to avoid
					;  incorrect stalls.
	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
	MOVL	HRB$L_ABCNT(R3),-	; Figure out the offset into the 
		CDRP$L_RBOFF(R5)	;  remote buffer 
	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

;
; 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	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	7$			; If it has not been aborted, continue..
	BRW	ABORT_WRITE		;  otherwise, do the proper cleanup
        
;
; Prepare the IRP for a disk transfer, so the data can be transferred to
; the disk.
;
7$:	DECL	HRB$L_CMD_STS(R3)	; Report progress on this request
	MOVL	HRB$L_IRP_CDRP(R3),R4	; Get the address of the IRP
	MOVL	HRB$L_UQB(R3),R5	; Get the UQB address
	MOVL	UQB$L_UCB(R5),-		;  so we can pull out the UCB address
		IRP$L_UCB(R4)		;  and save it away in the IRP
	CLRL	IRP$L_WIND(R4)		; No window control blocks in the server
	CLRB	IRP$B_EFN(R4)		; Clear out the event flag number
	CLRL	IRP$L_STS(R4)		; Start out with a clear status field
	MOVL	HRB$L_SVAPTE(R3),-	; Move the local buffer
		IRP$L_SVAPTE(R4)	;  virtual page table entry,
	MOVZWL	HRB$W_BOFF(R3),-	;  the byte offset within the page,
		IRP$L_BOFF(R4)		;  of the start of the buffer
	MOVL	HRB$L_BCNT(R3),-	;  and the byte count to be used for
		IRP$L_BCNT(R4)		;  this (portion of the) transfer.
	MOVL	R3,IRP$L_HRB(R4)	; Set the HRB address for IO completion

;
; Check for host compare.
;
	MOVL	#IO$_WRITECHECK,R0	; Assume write check
	MOVL	HRB$L_MSGBUF(R3),R2	; Get back the MSCP packet address
	CMPB	MSCP$B_OPCODE(R2),-	; Is this a host compare?
		#MSCP$K_OP_COMP		;
	BEQL	10$			; EQL means host compare, skip write.

;
; 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.
;
	MOVL	#IO$_WRITEPBLK,R0	; Set the function code
	BBC	#MSCP$V_MD_COMP,-	; If the compare modifier was set in
		MSCP$W_MODIFIER(R2),10$	;  the MSCP packet,
	BISL	#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),15$	;  the MSCP packet,
	BISL	#IO$M_INHRETRY,R0	;  then set it in the IRP also
15$:	BBC	#MSCP$V_MD_ERROR,-      ; If the  modifier was set in
		MSCP$W_MODIFIER(R2),20$	;  the MSCP packet,
	BISL	#IO$M_MSCPMODIFS,R0	;  then set it in the IRP also
	MOVW	#MSCP$M_MD_ERROR,-	;  and set up the irp field
		IRP$L_MEDIA+6(R4)	;
20$:	MOVL	R0,IRP$L_FUNC(R4)	; Fill in the function code

;
; Actually send off the IRP to the disk in this subroutine. If for some
; reason this request is aborted while the IRP is "out to disk", the entire
; request is cleaned up and finished off in the subroutine BACK and never
; heard from again.
;
	MOVAB	30$,R0			; Completion routine
	BSBW	DO_DISK			; Do the disk transfer
	BLBC	R0,24$			; Branch if not queued
	RSB				; Else, exit for now

24$:
	MOVAB	ERR_TBL,R2		; Get the address of the error table
	BSBW 	XFER_ERR		; Otherwise get the MSCP error code
	BICL3	#^cMSCP$M_ST_MASK, -	; Extract major MSCP status
		R0, R1			;  
	CMPL	R1,#MSCP$K_ST_OFFLN	; Was this one of the offline errors?
	BNEQ	25$			; If NEQ no
	BSBW	ERR_OFFLINE             ; Otherwise do some offline processing
	BRW	27$			; Merge
25$:	CMPL	R1,#MSCP$K_ST_ICMD	; Invalid command
	BNEQ	26$			; If NEQ no
	BSBW	ERR_WLG			; Report WLG invalid command
	CLRL	HRB$L_ABCNT(R3)		; Zero transferred bytecount
	BRW	27$			; Merge
26$:	CMPL	R1,#MSCP$K_ST_WRTPR	; Was this a write lock error?
	BNEQ	27$			; If not just return the error
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address 
	BBSS	#MSCP$V_UF_WRTPH,-	; Set the write protect bit in
		UQB$W_FLAGS(R4),27$	;  the unit flag field
27$:	MOVL	HRB$L_MSGBUF(R3),R2	; MSCP packet address from the HRB
	MOVL	HRB$L_ABCNT(R3),-	; Set the byte count in the MSCP 
		MSCP$L_BYTE_CNT(R2)	;  to return to the requestor
	BRW	SEND_END		; Send an end packet with R0 status

;+
; Resumed here when the transfer has completed
;
;   Inputs to the async completion routine:
;	R0 contains the I/O status  block
;	R1 contains the address of the UCB
;	R2 contains the message buffer address
;	R3 contains the address of the HRB
;	R4 contains the address of the UQB
;	R5 contains the address of the IRP
;
;   Outputs;
;	R0-R5 may be destroyed
;-

30$:	.JSB_ENTRY 	INPUT=<R0,R1,R2,R3,R4,R5>, -
			SCRATCH=<R0,R1,R2,R3,R4,R5>

	BLBC	R0,45$			; Branch if error
	MOVL	IRP$L_BCNT(R5),R0	; Pick up the number of bytes transfered
	ASHL	#-IOC$V_BLOCK_BLKNUM,-	; Convert the number of bytes specified
		R0,R1			;  into a block count
	ADDL	R0,HRB$L_ABCNT(R3)	; Update bytes sent so far
	SUBL3	HRB$L_ABCNT(R3),-	; Determine how many characters
		HRB$L_OBCNT(R3),R2	;  remain to be sent
	BLEQU	50$			; Nothing left we must be done!
	CMPL	R2,HRB$L_BCNT(R3)	; If the characters remaining is smaller
	BGEQU	40$			;  than characters just transfered,
	MOVL	R2,HRB$L_BCNT(R3)	;  then shrink the size of the request
40$:	ADDL	R1,HRB$L_LBN(R3)	; Move logically down the disk
	MOVL	HRB$L_PDT(R3),R4	;  for the SCS call
	MOVL	HRB$L_UQB(R3),R0	; Record the fact that the server
	INCL	UQB$L_EXTRA_IO(R0)	; An extra I/O has to be done

	; Modify write log entry flags since we are fragmenting write.
	; We only have one entry from the client node to do the write,
	; so we need to set the supplement write log flag and force
	; the reuse flag.  The client request might or might not have
	; the reuse flag set, but it is appropriate to set it now since
	; we are fragmenting the write.

	CMPW	#^XFFFF,IRP$L_CLN_WLE(R5); Watch out for exhaustion
	BNEQ	42$			;
	BICL	#IRP$M_WLE,-		; Stop write log.
		IRP$L_STS2(R5)		;
	BRW	WRITE_LOOP		;
42$:	BISB	#<IRP$M_WLE_REUSE!-	; Force the reuse bit
		  IRP$M_WLE_SUPWL>,-	;  and  the supplement bit
		IRP$B_WLG_FLAGS(R5)	;  in IRP.
	BRW	WRITE_LOOP		; Get another chunk of data
45$:    
	MOVAB	ERR_TBL,R2		; Get the address of the error table
	BSBW    XFER_ERR                ; Otherwise get the MSCP error code
	BICL3	#^cMSCP$M_ST_MASK, -	; Extract major MSCP status
		R0, R1			;  
	CMPL	R1,#MSCP$K_ST_OFFLN	; Was this one of the offline errors?
	BNEQ	46$			; If not test for write lock
	BSBW	ERR_OFFLINE		; Otherwise do some offline processing
	BRW	49$			;  and go to the common exit
46$:	CMPL	R1,#MSCP$K_ST_DATA	; Parity or Forced Error
	BEQL	47$			; If EQL yes
	CMPL	R1,#MSCP$K_ST_COMP	; Datacheck or Host Compare Error
	BNEQ	48$			; If NEQ no
47$:	MOVL	IRP$L_IOST1+2(R5), -	; Pick up the number of bytes
		HRB$L_ABCNT(R3)		;  transfered and update ABCNT.
	BRB	49$			; Merge
48$:	CMPL	R1,#MSCP$K_ST_WRTPR	; Was this a write lock error?
	BNEQ	49$			; If not just return the error
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address 
	BBSS	#MSCP$V_UF_WRTPH,-	; Set the write protect bit in
		UQB$W_FLAGS(R4),49$	;  the unit flag field
49$:	MOVL	HRB$L_MSGBUF(R3),R2	; MSCP packet address from the HRB
	MOVL	HRB$L_ABCNT(R3),-	; Set the byte count in the MSCP 
		MSCP$L_BYTE_CNT(R2)	;  to return to the requestor
	BRW	SEND_END		; Send an end packet with R0 status
;
; Final clean up for transfer commands.  Set status, release resources.
;
50$:	MOVL	HRB$L_MSGBUF(R3),R2	; Restore packet address
	BBC	#MSCP$V_MD_HISLO,-	; If not write logging, continue
		MSCP$W_MODIFIER(R2),55$;
        MOVL    IRP$L_CLN_WLE(R5),-	; Copy (entry id, entrylocator)
                MSCP$W_HRN(R2)		;
55$:	MOVL	HRB$L_ABCNT(R3),-	; Set byte count
		MSCP$L_BYTE_CNT(R2)	; 
	MOVL	#MSCP$K_ST_SUCC,R0	; Return a successful completion
	BRW	SEND_END		; Send an end packet with R0 status

ABORT_WRITE:
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; 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	SEND_END		;  and send out an end message

10$:	BSBW	CLEANUP_HRB		; Deallocate the resources used
	BRW	UNBLOCK			;  and restart any blocked requests,

	.PAGE
	.SBTTL	Load Balancing Routines
	.SBTTL	-	LOAD_MONITOR - Periodic load monitoring thread
;+
; Functional Description
;
; This routine executes in the context of a seperate fork thread, its fork
; block located by DSRV$L_LM_FKB. Its task is wake up every load monitor
; interval seconds and walk the HULB queue totalling the operations count,
; storing the current operations count in the previous count field and zeroing
; the current field. The total operations count is used to calculate load 
; available. If load available drops below a threashold, the load balancing
; thread is started.
;
; Inputs:
; R5	fork block
;
; Outputs:
;
; Register usage
; R0	time delta
; R1	current HULB pointer
; R2	pointer to last HULB
; R3	opcount
; R4	DSRV pointer
; R5	fork block
;-

LOAD_MONITOR:
	.JSB_ENTRY INPUT=<R5>,SCRATCH=<R0,R1,R2,R3,R4>
	MOVL	G^SCS$GL_MSCP,R4	; Get pointer to DSRV for later use
	BISW	#DSRV$M_MON_ACTIVE,-	; Tell the world we are using the LM
		DSRV$W_STATE(R4)	;  fork block.
;
; Main loop for walking the HULB queue.
;
LM_PASS:
	SUBL3	DSRV$L_LBMON_TIME(R4),-	; Figure out how long we slept
		G^EXE$GL_ABSTIM, R0
	MOVL	DSRV$L_HULB_FL(R4),R1	; Pick up pointers for the HULB queue
	MOVAB	DSRV$L_HULB_FL(R4),R2
	CLRL	R3			; Clear the operation counter
LM_LOOP:
	CMPL	R1,R2			; Are we at the end of the queue?
	BEQL	LM_DONE			; Branch if yes to store load
	BBS	#HULB$V_DELETE,-	; Ignore this HULB if it is marked for
		HULB$W_STATUS(R1),10$	;  delete. It is offline.
	ADDW2	HULB$W_OPCOUNT(R1),R3	; Increment the operation count and
	MOVW	HULB$W_OPCOUNT(R1),-	;  store the current counter in case
		HULB$W_PREV_OPC(R1)	;  we need it later for load balancing
	CLRW	HULB$W_OPCOUNT(R1)	; Clear the current counter
10$:	MOVL	HULB$L_FLINK(R1),R1	; Pick up the next HULB and repeat
	BRB	LM_LOOP			;  the process

;
; We have scanned all of the HULBs and we will now calculate the load in
; operations per second and store it in the DSRV. We store values for the
; previous three passes to perform averaging.
;
LM_DONE:
	MOVL	G^EXE$GL_ABSTIM,-	; Init the counter for the next
		DSRV$L_LBMON_TIME(R4)	; pass.
	MOVW	DSRV$W_LM_LOAD3(R4),-	; Age the previous pass counters
		DSRV$W_LM_LOAD4(R4)
	MOVL	DSRV$W_LM_LOAD1(R4),-
		DSRV$W_LM_LOAD2(R4)
	TSTW	R3			; Check for zero operations this
	BEQL	10$			;  interval.
	DIVW3	R0,R3,R3		; Divide total operations by the time
10$:	MOVW	R3,DSRV$W_LM_LOAD1(R4)	; Store the value.
	ADDW2	DSRV$W_LM_LOAD2(R4),R3	; Add the values calculated for the
	ADDW2	DSRV$W_LM_LOAD3(R4),R3	;  previous three passes.
	ADDW2	DSRV$W_LM_LOAD4(R4),R3	;
	ASHL	#-2,R3,R3		; And average them
	SUBW3	R3,-			;  interval and subtract it from the
		DSRV$W_LOAD_CAPACITY(R4),-;load capacity to give a load
		DSRV$W_LOAD_AVAIL(R4);  available figure.
		
	CMPW	#10,-			; If load available is below threshold,
		DSRV$W_LOAD_AVAIL(R4)	;  start the load balancing thread
	BLSS	LM_EXIT			; Otherwise go clean up.

;	PUSH	R5
;	MOVL	DSRV$L_LB_FKB(R4),R5 
;	CREATE_FORK	LOAD_BALANCE
;	POP	R5

LM_EXIT:
	BICW	#DSRV$M_MON_ACTIVE,-	; Wipe our fingerprints off the DSRV
		DSRV$W_STATE(R4)
	RSB				; And cease to exist

	.PAGE
	.SBTTL	-	LOAD_BALANCE - Load balancing thread

	.PAGE
	.SBTTL	-	LM_INIT_CAPACITY - Setup load capacity field
;+
; Functional Description
;
; This routine sets up the DSRV$W_LOAD_CAPACITY field to reflect the
; serving capacity of this node. This is loaded from the sysgen parameter
; MSCP_LOAD. If this parameter is set to '1', the load available is set
; to 100.
;
; The default values is this module are based on the following assumptions:
;	one ethernet adapter (fastest supported type)
;	disk I/O is not a bottleneck
; Note that for systems that can only serve single host disks, these figures
; have minimal impact on load balancing.
;
; The LOAD1-4 fields, used to average load figures, and the current load
; available are initialized to the load capacity.
;
; Inputs:
;	R5	DSRV
; Outputs:
;	DSRV$W_LOAD_CAPACITY calculated for this CPU.
;	DSRV$W_LOAD_AVAIL set to the same value.
;-

	DECLARE_PSECT	EXEC$INIT_CODE

LM_INIT_CAPACITY:
	.JSB_ENTRY INPUT=<R5>

	CMPW	G^CLU$GL_MSCP_LOAD,#1	; If a value greater than 1 was set
	BGTRU	LOAD_GEN		;  for MSCP_LOAD, use it instead.

; List all supported server nodes in this CPUDISP. Any others will default
; to 100 I/Os per second.


; ALPHA SPECIFIC SETUP 

	.PRINT ; LOAD BALANCING INFO NEEDS TO BE DETERMINED FOR ALPHA

	SYSDISP	CONTINUE=YES,-	
		<<ADU,LOAD_ADU>,-
		 <MANNEQUIN,LOAD_MANNEQUIN>,-
		 <COBRA,LOAD_COBRA>>
	BRW	LOAD_UNKNOWN			; Unknow cpu type

LOAD_UNKNOWN:
LOAD_MANNEQUIN:
LOAD_COBRA:
LOAD_ADU:
	MOVW	#340,DSRV$W_LOAD_CAPACITY(R5)
	BRW	LOAD_END

LOAD_GEN:					; Use load capacity from
	MOVW	G^CLU$GL_MSCP_LOAD,-		;  SYSGEN parameter.
		DSRV$W_LOAD_CAPACITY(R5)
LOAD_END:
	MOVW	DSRV$W_LOAD_CAPACITY(R5),-; Init the current load fields.
		DSRV$W_LOAD_AVAIL(R5)
	CLRQ	DSRV$W_LM_LOAD1(R5)	; Clear load history.
	
	RSB

	.PAGE
	.SBTTL  -	FKB_INIT - Create and initialize a fork block
;+
; Functional Description
;
; This routine calls EXE$ALONONPAGED to create a fork block. Basic fields
; in the fork block are initialised and the address of the new FKB returned
; in R2.
;
; Inputs:
;	none
;
; Outputs:
;	R2	address of FKB
;
; R0 and R1 are destroyed.
;
;-

	DECLARE_PSECT	EXEC$INIT_CODE

FKB_INIT:
	.JSB_ENTRY INPUT=<>,OUTPUT=<R2>,SCRATCH=<R0,R1>
	MOVZWL	#FKB$K_LENGTH,R1	; Allocate pool for FKB
	JSB	G^EXE$ALONONPAGED
	BLBC	R0,999$
	ASSUME	FKB$B_FLCK EQ FKB$B_TYPE+1
	MOVW	#<<SPL$C_SCS@8>!DYN$C_FRK>,-
		FKB$B_TYPE(R2)
	MOVW	#FKB$K_LENGTH,FKB$W_SIZE(R2)
	RSB

999$:	BUG_CHECK	DISKSERVE, FATAL


	.PAGE
	.SBTTL	Utility Routines
	.SBTTL	-	MSCP$TMR - General purpose timer thread
;
; General purpose server timer thread. Can be used to initiate any time
; based functions in the server. It is initiated in the start routine with
; a repeating TQE. Any function that may take longer than the timer interval
; (currently 20 seconds) should be performed in a seperate fork thread.
;
	DECLARE_PSECT EXEC$NONPAGED_CODE,PAGE

MSCP$TMR::
	.JSB_ENTRY INPUT=<R3,R4,R5>,OUTPUT=<R3,R4,R5>,SCRATCH=<R0,R1,R2>
	LOCK	LOCKNAME=SCS,-		; Lock SCS spinlock for this thread
		PRESERVE=NO		;  and any created by it.
	MOVL	G^SCS$GL_MSCP,R4	; Locate DSRV
	BBS	#DSRV$V_MON_ACTIVE,-	; If the monitor fork thread
		DSRV$W_STATE(R4),90$	;  is already active, exit.
	PUSHL	R5	
	MOVL	DSRV$L_LM_FKB(R4),R5	; Locate monitor fork block,
	CREATE_FORK	LOAD_MONITOR	;  and away we go.
	POPL	R5			; Restore TQE address.
90$:	UNLOCK	LOCKNAME=SCS,-		; Release the SCS spinlock.
		PRESERVE=NO
	RSB				; And return to dispatcher. 

	.PAGE
	.SBTTL	-	ALLOCATE - Allocate a local buffer for the request
;+
; Functional Description:
;
; This routine allocates a buffer on the serving routine based on the 
; transfer size described in the MSCP request received. 
;
; Inputs:
;
;	R0  =  Address of routine to invoke after stalling for resources
;	R2  =  MSCP packet address
;	R3  =  HRB address
;	R4  =  UQB address
;
; Output:
;
;	R0  =  Completion Status
;		SS$_NORMAL -- request satisfied
;		SS$_UNWIND -- request completed caller should exit
;		SS$_SUSPENDED -- request queued. Caller will be notified upon
;				completion via async completion routine
;	R2  =  MSCP packet address
;	R3  =  HRB address
;
; Side Effects:
;
;	HRB$L_BCNT	Number of bytes to be transfered per IRP
;	HRB$L_LBN	Logical block number on the disk
;	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
;-

ALLOCATE:
	.JSB_ENTRY INPUT=<R2,R3,R4>,OUTPUT=<R0,R2,R3>
	MOVL	R0,HRB$L_SAVD_RTN(R3)	; Save the asynch completion routine address
	MOVL	MSCP$L_BYTE_CNT(R2),-	; Pick up size of request
		HRB$L_OBCNT(R3)		;  and store it away
	BNEQ	40$			; Continue if length is non-zero

;
; If this is a zero block request, it can be finished off right here.
;
	MOVL	HRB$L_HQB(R3),R5	; Get the address of the HQB so that
	MOVL	HQB$L_DSRV(R5),R5	;  we can get the server structure
	INCL	DSRV$L_BLKCOUNT(R5)	; Count zero block transfer
	MOVL	#MSCP$K_ST_SUCC,R0	; Set a successful status to return
	BSBW	SEND_END		;  and we are finished!
	MOVL	#SS$_UNWIND,R0		; Tell caller to exit
	RSB
;
; Set up the count fields in the HRB to their initial values. Since the IRP
; is used for both the disk transfers and SCS function calls, the byte count
; accounting will be performaed in the Host Request Block structure. Translate
; the number of bytes requested into a block count to be transfered. If the 
; read request is for more than 127 blocks, break it up into 127 page chunks.
; By subtracting one from the number of bytes to be transferred, the conversion
; to blocks actually results in one less than the number to be transfered, 
; however when added to the starting LBN it yields the proper ending LBN.
;
40$:	MOVL	HRB$L_OBCNT(R3),R1	; Set number of bytes to be transferred
	DECL	R1			; Subtract one from the byte count
	ASHL	#-IOC$V_BLOCK_BLKNUM,-	; Convert the number of bytes specified
		R1,R1			;  into a block count
50$:    CMPB	#MSCP$K_OP_WRHIM,-	; Is it write history management?
		MSCP$B_OPCODE(R2)
	BEQL	55$			; If so, do not check maxblock.

	MOVL	MSCP$L_LBN(R2),R0	; Transfer the starting LBN
	MOVL	R0,HRB$L_LBN(R3)	;  and keep a copy of it in the HRB
	ADDL	R1,R0			; Calculate ending block number
	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address
	MOVL	UQB$L_UCB(R4),R4	; Get the UCB address for this unit
	CMPL	R0,UCB$L_MAXBLOCK(R4)	; Make sure the ending block is on unit
	BLEQU	55$			; If it is, continue..
	MOVZWL	#MSCP$K_ST_ICMD -	; Return an illegal command
		!<MSCP$L_BYTE_CNT@8>,R0	;  with the byte count field as bad
	BSBW	SEND_END		; Leave in disgrace
	MOVL	#SS$_UNWIND,R0		; Tell caller to exit
	RSB

55$:	CMPL	R1,#126			; If less than 127,
	BLEQU	60$			;  just use the original value
	MOVZBL	#126,R1			; Else make the size 127 blocks
60$:	MOVL	HRB$L_HQB(R3),R5	; Get the HQB address for 
	MOVL	HQB$L_DSRV(R5),R5	;  the server structure address
	INCL	R1			; Get the true block count
	INCL	DSRV$L_BLKCOUNT(R5)[R1]	; Keep statistics

;
; All the accounting is done. Prepare to allocate local memory by calculating
; the maximum amount of local pool the server can afford to use for this 
; transfer, and the minimum size to which this transfer is permitted to 
; fragment before waiting for more local buffer to free up. Compare these
; values with the number of bytes requested (rounded to the next highest block
; boundry).
;
	ASHL	#IOC$V_BLOCK_BLKNUM,-	; Convert the number of blocks back
		R1,R1			;  into a byte count
70$:	MOVL	R1,HRB$L_BUFLEN(R3)	; Save away the length and the

	ASHL	#-1,DSRV$L_AVAIL(R5),R0	; The calculated maximum value is
	BICL	#511,R0			;   MAX (avail/2,512)
	BNEQ	72$			; rounded down to the block boundary
	MOVL	#512,R0			; 

72$:	CMPL	R1,R0			; If requested bytes is less,
	BLEQU	80$			;  use that number.
	MOVL	R0,R1			; If not, use the constrained value.

75$:	ASHL	#2,R1,R2		; R1=amount of space we are trying for
	CMPL	R2,HRB$L_OBCNT(R3)	; Is size >= tsize/4
	BGEQU	80$			; Its bigger try for IT
	CMPL	R1,DSRV$L_BUFFER_MIN(R5); Is size >= buffer/8
	BLSSU	90$			; This is fragmenting too far, wait
;
; Allocate the buffer, or decide to wait.
;
80$:	PUSHR	#^M<R1,R3>		; Save the requested size, and HRB add
	MOVAL	DSRV$L_FREE_LIST(R5),R3	; Get the allocation region list head
	BSBW	MSCP$ALLOCATE		;  and try for the required memory
	POPR	#^M<R1,R3>		; Restore the saved registers
	BLBS	R0,110$			; The buffer space was granted!
	CMPL	R1,#512			;  otherwise check for a 1 block transfer
	BEQLU	90$			;  if failed on one block, wait
	ASHL	#-1,R1,R1		;  otherwise, cut our request in half
	MOVAB	511(R1),R1		; Adjust the count for partial blocks
	BICW	#511,R1			;  and calculate the required blocks
	BRB	75$			;  and try again

90$:	MOVW	#HRB$K_ST_BUF_WAIT,-	; Set the state so we can clean up
		HRB$W_STATE(R3)		;  in the event of an emergency
	INSQUE	HRB$L_WAIT_FL(R3),-	; Insert on the waiting que
		@DSRV$L_MEMW_BL(R5)	;  for internal buffer resources
	INCW	DSRV$W_BUFWAIT(R5)	; Total mem wait counter for Monitor
	INCL	DSRV$L_MEMW_TOT(R5)	; Bump memory wait overall counter
	INCW	DSRV$W_MEMW_CNT(R5)	; Bump memory wait queue counter
	CMPW	DSRV$W_MEMW_CNT(R5),-	; If the current counter is not
		DSRV$W_MEMW_MAX(R5)	;  larger than the max,
	BLEQU	100$			;  then continue...
	MOVW	DSRV$W_MEMW_CNT(R5),-	; If the current count is larger,
		DSRV$W_MEMW_MAX(R5)	;  replace the max with the new value
100$:	MOVL	HRB$L_UQB(R3),R4	; Get the address of the unit block
	DECW	UQB$W_CURRENT(R4)	;  and reduce the current count
	.IF DEFINED DEBUG$CURRENT_SANITY
	BSBW	CHECK_CURRENT		; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY
	MOVAB	L^105$,HRB$L_RESPC(R3)	; Save the resume address
	MOVL	#SS$_SUSPENDED,R0	; Indicate request queued
	RSB				; Caller should exit for now
;
; Control is now passed to the DUDRIVER. Processing resumes here when this 
; request is restarted. Requests are pulled off the memory wait queue when 
; another request deallocates local buffer transfer memory. This is done in 
; cleanup_hrb.
;
105$:	.JSB_ENTRY 	INPUT=<R3>,SCRATCH=<R0,R1,R2,R3,R4,R5>
	MOVL	HRB$L_HQB(R3),R5	; Get the HQB address so we can find
	MOVL	HQB$L_DSRV(R5),R5	;  the server structure
	MOVL	HRB$L_BUFLEN(R3),R1	; Restore the length in bytes
	BRW	70$			; Try again

110$:	SUBL	R1,DSRV$L_AVAIL(R5)	; Record the allocation
	MOVL	R1,HRB$L_BUFLEN(R3)	; Save away the length and the
	MOVL	R2,HRB$L_BUFADR(R3)	;  address of the buffer allocated
	CMPL	R1,HRB$L_OBCNT(R3)	; See how final length compared to the
	BEQL	120$			;  start, if its the same continue...
	BGTRU	115$			; If its been rounded up, branch
	INCL	DSRV$L_SPLITXFER(R5)	; If it was split increment the count
	BRB	120$			;  and then continue
115$:	MOVL	HRB$L_OBCNT(R3),R1	; Restore the original byte count
120$:	MOVL	R1,HRB$L_BCNT(R3)	; Set the number of important bytes

	MCOML	G^MMG$GL_BWP_MASK,R0	; Compliment byte within page msk
	BICL3	R0,R2,R0		; Clear out all except BWP
	MOVW	R0,HRB$W_BOFF(R3)	; Store byte within page
	EVAX_SLL R2,#<64-VA$V_SYSTEM>,R2; Shift away sign extension portion
	EVAX_SRL R2,MMG$GQ_64SYS_SHIFT,R2; and then the byte within page part
	MOVAQ    @MMG$GL_SPTBASE[R2], -	; Use whats left as an index into
		 HRB$L_SVAPTE(R3)	;  the SVA page table 

	MOVL	HRB$L_MSGBUF(R3),R2	; Restore the MSCP packet address
	; Input to completion routine:
	;	R2  =  MSCP packet address
	;	R3  =  HRB address
	; Output expected from completion routine
	;	R0-R5 may be destroyed
	JSB	@HRB$L_SAVD_RTN(R3)	; Invoke async completion routine
	MOVL	#SS$_UNWIND,R0		; Tell caller to exit
	RSB

	.PAGE
	.SBTTL  -	CHECK_DISK    -  prepare and issue an I/O to a disk
;+
; Functional Description:
;
; This subroutine prepares an IRP for a disk I/O without worrying about 
; the LBN conversion or checking on the disk status. This routine is
; called by the ONLINE and AVAILABLE code paths. The return to back is 
; still the same.
;
; Inputs:
;
;	R0  =  Address of routine to invoke when the I/O completes
;	R3  =  HRB address
;	R4  =  UCB address
;	R5  =  IRP address
;
; Outputs:
;
;	None
;
; Side Effects:
;
;	R0-R4 may be destroyed
;	The IRP associated with the HRB is queued to be sent to the disk.
;
; Async completion:
;
;   The asynchronous completion routine is invoked when the I/O completes
;   with the following context setup by routine BACK.
;
;   Inputs to the async completion routine:
;	R0 contains the I/O status  block
;	R1 contains the address of the UCB
;	R2 contains the message buffer address
;	R3 contains the address of the HRB
;	R4 contains the address of the UQB
;	R5 contains the address of the IRP
;
;   Outputs required from the async completion routine
;	R0-R5 may be destroyed
;-

CHECK_DISK::
	.JSB_ENTRY	INPUT=<R0,R3,R4,R5>, -
			OUTPUT=<>, -
			SCRATCH=<R0,R1,R2,R3,R4>

	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	BISL	#<IRP$M_SRVIO!IRP$M_PHYSIO>,- ; Set physical I/O and server I/O
		IRP$L_STS(R5)		;  for ONLINE or AVAILABLE
	MOVL	R0,HRB$L_RESPC(R3)	; Where to return after IOPOST
	MOVAB	BACK,IRP$L_PID(R5)	; Address to which IOPOST returns IRP
	MOVL	HRB$L_UQB(R3),R0	; Get the address of the unit block
	INCL	UQB$L_IOCNT(R0)		;  record the queue and total I/O
	INCW	UQB$W_QLEN(R0)		;  contribution the server initiates
	LOCK	LOCKNAME=PERFMON,-	; Lock PERFMON access
		PRESERVE=NO,-		;  don't preserve R0
		SAVIPL=-(SP)		;  save the current IPL
	MOVL	G^PMS$GL_IOPFMSEQ,-	; Insert the I/O sequence number
		IRP$L_SEQNUM(R5)	;  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
	MOVW	#HRB$K_ST_DRV_WAIT,-	; Set the state of this request
		HRB$W_STATE(R3)		;  as queued to the driver
	MOVL	R5,R3			; Put the IRP, and UCB addresses in the
	MOVL	R4,R5			;  proper places for queue insertion

        .IF DEFINED DEBUG$LOG

        ASSUME DSRV$V_LOG_ENABLD  EQ  0

        PUSHR   #^M<R3,R5>
        MOVL    R5,R0                   ; Swap R3 & R5
        MOVL    R3,R5                   ; to log
        MOVL    R0,R3                   ; an IRP command packet
        MOVL    G^SCS$GL_MSCP,R0        ; Get the DSRV address
        BLBC    DSRV$W_STATE(R0),50$    ; Branch if logging is disabled.
        BSBW    MSCP$LOG_IRP_PKT        ; Log an IRP packet if tracing.

50$:
        POPR    #^M<R3,R5>
        .ENDC   ; DEBUG$LOG

	JSB	G^EXE$INSIOQC		; Queue the IRP to the driver
	RSB
	.PAGE
	.SBTTL	-	DO_DISK -  prepare and issue an I/O for block transfer
;+
; Functional Description:
;
; This routine validates the request by making sure the disk is online 
; to the requesting host, and assuring that the LBNs requested indeed, 
; reside on the volume. If everything is OK, the I/O request is issued 
; to the disk. 
;
; Inputs:
;
;	R0  =  Address of routine to invoke when the I/O completes
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  Completion status
;		LBC indicates that async completion routine will not be invoked
;		LBS indicates that async completion will be invoked.
;
; Side Effects:
;
;	R0-R1	destroyed
; 
; Async completion:
;
;   The asynchronous completion routine is invoked when the I/O completes
;   with the following context setup by routine BACK.
;
;   Inputs to the async completion routine:
;	R0 I/O status  block
;	R1 address of the UCB
;	R2 message buffer address
;	R3 address of the HRB
;	R4 address of the UQB
;	R5 address of the IRP
;
;   Outputs required from the async completion routine
;	R0-R5 may be destroyed
;-

DO_DISK::
	.JSB_ENTRY INPUT=<R0,R3>,OUTPUT=<R0>
	PUSHR	#^M<R2,R3,R4,R5>
	MOVL	R0,HRB$L_RESPC(R3)	; Where to return after IOPOST
	MOVL	HRB$L_UQB(R3),R4	; Set up the UQB address
	MOVL	HRB$L_HQB(R3),R5	; Get the HQB address
	MOVZBL	HQB$B_HOSTNO(R5),R0	; Get the assigned host number
	BBS	R0,UQB$B_ONLINE(R4),10$	; If this unit is not online to the
					;  requesting host, then
	MOVL	#SS$_MEDOFL,R0		;  return a device offline message
	POPR	#^M<R2,R3,R4,R5>
	RSB				;  and return

10$:	MOVL	HRB$L_LBN(R3),R0	; Logical block number to be converted
	MOVL	HRB$L_IRP_CDRP(R3),R3	; Get the IRP address in order to fill
	MOVAB	BACK,IRP$L_PID(R3)	;  addr to which IOPOST will return IRP
	BISL	#IRP$M_SRVIO,-		; Designate IRP as server I/O
		IRP$L_STS(R3)		;
	INCL	UQB$L_IOCNT(R4)		; Record the queue and total I/O
	INCW	UQB$W_QLEN(R4)		;  contribution the server initiates
	MOVL	UQB$L_UCB(R4),R5	; Get the UCB address
	TSTL	G^PMS$GL_IOPFMPDB	; I/O performance monitoring enabled?
	BEQL	20$			; Skip around this stuff if its not
	JSB	G^PMS$START_RQ		; Gather Start I/O Request statistics
	BRB	30$			; Go ahead with the I/O

20$:	LOCK	LOCKNAME=PERFMON,-	; Lock PERFMON access
		PRESERVE=YES,-		;  don't preserve R0
		SAVIPL=-(SP)		;  save the current IPL
	MOVL	G^PMS$GL_IOPFMSEQ,-	; Insert I/O sequence # into IRP
		IRP$L_SEQNUM(R3)
	INCL	G^PMS$GL_IOPFMSEQ	; and bump global counter
	UNLOCK	LOCKNAME=PERFMON,-	; Release PERFMON access
		PRESERVE=YES,-		;  don't preserve R0
		NEWIPL=(SP)+		;  restore the saved IPL
30$:	JSB	G^IOC$CVTLOGPHY		; Convert logical to physical block
	MOVL	IRP$L_HRB(R3),R1	; Recover the HRB address temporarily
	MOVW	#HRB$K_ST_DRV_WAIT,-	; Sent the request to the
		HRB$W_STATE(R1)		;  disk driver
	;
	; Insert this I/O request packet on the I/O queue.
	; The completion thread is invoked by routine BACK when the request 
	; completes.
	;


        .IF DEFINED DEBUG$LOG

        ASSUME DSRV$V_LOG_ENABLD  EQ  0

        PUSHR   #^M<R3,R5>
        MOVL    R5,R0                   ; Swap R3 & R5
        MOVL    R3,R5                   ; to log
        MOVL    R0,R3                   ; an IRP command packet
        MOVL    G^SCS$GL_MSCP,R0        ; Get the DSRV address
        BLBC    DSRV$W_STATE(R0),40$    ; Branch if logging is disabled.
        BSBW    MSCP$LOG_IRP_PKT        ; Log an IRP packet if tracing.
40$:
        POPR    #^M<R3,R5>
        .ENDC   ; DEBUG$LOG

	JSB	G^EXE$INSIOQC		; Queue the IRP to the driver
	POPR	#^M<R2,R3,R4,R5>
	MOVL	#SS$_NORMAL,R0		;  return success.  Async completion
	RSB				;  will follow someday

	.PAGE
	.SBTTL	-	BACK  -  return from disk I/O and continue
;+
; Functional Description:
;
; On returning from performing a disk I/O, the state of the HRB is checked 
; to see if the request has been aborted while we were out to disk. If it 
; was, the allocated resources are deallocated and the request finished off 
; without returning to the caller.
;
; Inputs:
;
;	R5  =  IRP address returned from IOPOST
;
; Outputs:
;
;	R0-R5 may be destroyed upon return to IOPOST
;
; Side Effects:
;
;	Control is resumed at the address stored in the resume PC 
;	field of the Host Request Block.
;-                               

BACK::
	.JSB_ENTRY INPUT=<R5>,OUTPUT=<R0,R1,R2,R3,R4>

	LOCK	LOCKNAME=SCS,-		; Lock SCS access
		PRESERVE=NO,-		;  don't preserve R0
		SAVIPL=-(SP)		;  save the current IPL

        .IF DEFINED DEBUG$LOG

        ASSUME DSRV$V_LOG_ENABLD  EQ  0

        MOVL    IRP$L_UCB(R5),R3        ; Pick up UCB pointer
        MOVL    G^SCS$GL_MSCP,R0        ; Get the DSRV address.
        BLBC    DSRV$W_STATE(R0),5$     ; Branch if logging is disabled.
        MOVL    IRP$L_IOST1(R5),R0      ; Put the I/O status 1 in R0
        MOVL    IRP$L_IOST2(R5),R1      ; Put the I/O status 2 in R1
        BSBW    MSCP$LOG_IRP_EXIT_PKT   ; Log an IRP exit packet if tracing.
5$:
        .ENDC   ; DEBUG$LOG

	MOVL	IRP$L_HRB(R5),R3	; Restore the HRB address
	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis
	BICL	#<IRP$M_PHYSIO!IRP$M_SRVIO>,- ; Clear physical I/O and server I/O flags
		IRP$L_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	IRP$L_UCB(R5),R1	; Pick up UCB pointer
	.PRESERVE ATOMICITY
	DECL	UCB$L_QLEN(R1)		; Drop queue length counter
	.NOPRESERVE ATOMICITY
	MOVL	HRB$L_UQB(R3),R4	; Restore the UQB address and
	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 disk
	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 noone 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.
;
	BBS	#HRB$V_ABORT,-		; If this abort was the result of error
		HRB$W_FLAGS(R3),20$	;  or client disconection, no end msg.
	MOVL	HRB$L_MSGBUF(R3),R2	; Get the message buffer address 
	BEQL	20$			;  if none, return no status
	
	MOVL	#MSCP$K_ST_ABRTD,R0	;  otherwise return an aborted status
	BSBW	SEND_END		;  in an end message before returning
	BRB	40$			;  back to a lower IPL

20$:	BSBW	CLEANUP_HRB		; Clean up this request
	BSBW	UNBLOCK			;  and restart any blocked requests
	BRB	40$			; Return back to a lower IPL

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

	;
	; Invoke suspended thread.  The register usage below is also documented in
	; routines CHECK_DISK and DO_DISK.
	;
	;   Input to the thread:
	;	R0 contains the I/O status  block
	;	R1 contains the address of the UCB
	;	R2 contains the message buffer address
	;	R3 contains the address of the HRB
	;	R4 contains the address of the UQB
	;	R5 contains the address of the IRP
	;
	;   Outputs required:
	;	R0-R5 may be destroyed
	;
	JSB	@HRB$L_RESPC(R3)	; Resume where we left off

40$:	UNLOCK	LOCKNAME=SCS,-		; Release SCS access
		PRESERVE=NO,-		;  don't preserve R0
		NEWIPL=(SP)+,-		;  restore the saved IPL
		CONDITION=RESTORE	;  conditionally release spinlock
	RSB				;  and return

	.PAGE
	.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 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
;-

IRP_ALLOC_ERR:
	MOVL	R3,R0			; Address of structure to deallocate
	JSB	G^EXE$DEANONPAGED	; Return the HRB to nonpaged pool
NOMEM_ERR:
	MOVZWL	#SS$_INSFMEM,R0		; Set an error code
	POPR	#^M<R1,R2,R4,R5>	; Restore the registers used
	RSB

ALLOCATE_HRB::
	.JSB_ENTRY INPUT=<>,OUTPUT=<R0,R3>,PRESERVE=<R1>
	PUSHR	#^M<R1,R2,R4,R5>	; Save the registers to be used
	MOVL	#HRB$K_LENGTH,R1	; Length of the requested buffer
	JSB	G^EXE$ALONONPAGED	; Get the memory
	BLBC	R0,NOMEM_ERR		; Unsuccessful return an error

;
; Initialize known fields in the newly allocated HRB.
;
10$:	MOVW	R1,HRB$W_SIZE(R2)	; Fill in the size field
	MOVB	#DYN$C_DSRV,-		; The structure type 
		HRB$B_TYPE(R2)		;  is Server
	MOVB	#DYN$C_DSRV_HRB,-	; The structure subtype
		HRB$B_SUBTYPE(R2)	;  is a Host Request Buffer
	CLRW	HRB$W_STATE(R2)		; Clear out the state field
	CLRW	HRB$W_FLAGS(R2)		; No flags set to start out with
	CLRL	HRB$L_RESPC(R2)		; Clear resume PC for debugging
	CLRL	HRB$L_MSGBUF(R2)	; Clear message buffer pointer
	CLRL	HRB$L_BD_ADDR(R2)	; Clear buffer descriptor address
	CLRL	HRB$L_IRP_CDRP(R2)	; Clear the IRP pointer (none allocated)
	CLRL	HRB$L_BUFADR(R2)	; No buffer allocated yet
	CLRL	HRB$L_SVAPTE(R2)	; Clear out these field for when
	CLRW	HRB$W_BOFF(R2)		;  they are used in the erase
	CLRL	HRB$L_BCNT(R2)		;  command
	CLRL	HRB$L_ABCNT(R2)		; Clear out the accumulated byte count
	CLRL	HRB$L_UQB(R2)		; Clear pointer to the UQB
	MNEGL	#2,HRB$L_CMD_STS(R2)	; Initialize the status field 
	MOVL	R2,R3			; Save the address of the request buffer
        MOVL    G^EXE$GL_ABSTIM,-       ; Record time of this command
                HRB$L_CMD_TIME(R2)      ;  in the HRB.

;
; Allocate and link the IRP into the HRB.
;
	MOVL	#IRP$K_LENGTH,R1	; Size of buffer to be allocated
	JSB	G^EXE$ALONONPAGED	; Get enough memory for an IRP-CDRP
	BLBC	R0,IRP_ALLOC_ERR	; Return an error if unsuccessful
	PUSHR	#^M<R1,R2,R3,R4,R5>	; Save the current register contents
	MOVC5	#0,(SP),#0,R1,(R2)	; Zero out the IRP
	POPR	#^M<R1,R2,R3,R4,R5>	; Restore register contents

;
; 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	#IRP$K_LENGTH,-		; Use the length constant to 
		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
	CLRL	IRP$L_CHAN(R2)		; Clear the process I/O channel number
	ASSUME  IRP$L_STS2 EQ IRP$L_STS+4
	CLRQ	IRP$L_STS(R2)		; Start out with a clear status field
	CLRL	IRP$L_RSPID(R2)		; Signify no response ID allocated
	MOVL	R3,IRP$L_HRB(R2)	; Set the HRB address for IO completion

;
; 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)	; 
	SCS_INIT_CDRP CDRP=R2		; Init SCS state
	MOVL	#SS$_NORMAL,R0		; Set success status

;
; Restore the registers to their original state and return.
;
	POPR	#^M<R1,R2,R4,R5>	; Put everything back
	RSB				;  and return to the caller

	.PAGE
	.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
;-

CLEANUP_HRB::
	.JSB_ENTRY INPUT=<R3>,OUTPUT=<R0,R4>

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

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

	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
	MOVL	HRB$L_BD_ADDR(R3), -	; Restore the descriptor address
		CDRP$L_BD_ADDR(R5)	; 
	UNMAP				; If it does, release them

;
; 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

;
; 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

;
; Deallocate any local buffer, and check for any requests that may be
; waiting for buffer space.
;
30$:	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 allocated
	MOVL	HRB$L_HQB(R3),R5	; Get the HQB structure
	MOVL	HQB$L_DSRV(R5),R5	; Get the server structure address
	PUSHL	R3			; Save the HRB address
	MOVAL	DSRV$L_FREE_LIST(R5),R3	; Address of allocation region listhead
	ADDL	R1,DSRV$L_AVAIL(R5)	; Adjust the available count
	BSBW	MSCP$DEALLOCATE		; Return the memory to the internal pool
	POPL	R3			; Restore R3 so we can use it again
	CLRL	HRB$L_BUFADR(R3)	; Clear out the buffer address
	BRB	35$

;
; Deallocate any IRP that was allocated.
;
35$:	MOVL	HRB$L_IRP_CDRP(R3),R0	; Get the address of the IRP
	BEQL	40$			; None was allocated, continue...
	MOVB	#DYN$C_IRP, -		; Fill in the type and size so the 
		IRP$B_TYPE(R0)		;  deallocate routine will know what
	MOVW	#IRP$K_LENGTH,-		;  it is deallocating
		IRP$W_SIZE(R0)		; 
	PUSHL	R3			; Make sure the HRB address is saved
	JSB	G^EXE$DEANONPAGED	; Free the memory
	POPL	R3			; Restore the saved address
	CLRL	HRB$L_IRP_CDRP(R3)	;  and clear out the buffer pointer

;
; Deallocate the HRB, we don't need it any more.
;
40$:	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address
	BEQL	50$			; If this is a cmd w/o a unit
	BICW	#UQB$M_SEQ,-		; Clear out the sequential flag in case
		UQB$W_FLAGS(R4)		;  the request just completed was seq

;
; Unhook host request block from the list of requests pending for this host.
;
50$:	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
	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
55$:	MOVL	R3,R0			; Set up the HRB
	JSB	G^EXE$DEANONPAGED	;  and deallocate the data structure
	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),60$	;  just continue on...
	BSBW	DEALLOC_HQB		; Otherwise send out the disconnect

60$:	RSB				; Return to the caller

	.PAGE
	.SBTTL	-	BLOCKED! - Sequential Command In Progress
;+
; Functional Description:
;
; At the BLOCKED enty point, one of two senarios could be true: A MSCP 
; command was received for a unit that currently has a sequential command 
; in progress, or a sequential command was received with non-sequential 
; commands pending on the blocked queue. If this request arrived here because
; of a non-zero blocked queue length, a scan is done of the elements in the 
; blocked queue to determine whether or not there is a sequential command
; on the queue. If there is not, there is no need to place this request in the
; queue. If a sequential command is found on the queue, the request is placed 
; on the tail of the blocked queue and processed when the requests ahead of it
; in the queue have completed.
;
; Inputs:
;
;	R0  =  Routine to invoke when unblocked
;	R3  =  HRB address 
;	R4  =  UQB address
;
; Outputs:
;
;	None
;
; Inputs to routine that is invoked when unblocked:
;   R3 HRB address
;   R4 UQB address
;
; Outputs expected from that routine:
;   R0-R5 may be destroyed
;
;-

BLOCKED::
	.JSB_ENTRY INPUT=<R3,R4>

	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	.IF DEFINED DEBUG$CURRENT_SANITY
	BSBW	CHECK_CURRENT		; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY
	MOVW	#HRB$K_ST_SEQ_WAIT,-	; Set the state of this request
		HRB$W_STATE(R3)		;  to BLOCKED before placing on queue
	INSQUE	HRB$L_WAIT_FL(R3), -	; Insert this packet on the 
		@UQB$L_BLOCKED_BL(R4)	;  blocked queue for execution later
	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$:	MOVL	R0,HRB$L_RESPC(R3)	; Save the resume address	;
	BSBW	UNBLOCK			; Fix the count of active requests
	RSB				; Return to caller's caller

	.PAGE
	.SBTTL	-	UNBLOCK - Restart Any Blocked Commands
;+
; Functional Description:
;
; This routine is responsible for decrementing the current counter, and the
; associated checking of queues for restartable requests.
;
; Inputs:
;
;	R4  =  UQB address
;
; Outputs:
;
;	None
;-

UNBLOCK::
	.JSB_ENTRY INPUT=<R4>

	PUSHR	#^M<R2,R3,R5>		; Save any registers used
	TSTL	R4			; Make sure we have a valid address
	BEQL	50$			;  if not, just return
	DECW	UQB$W_CURRENT(R4)	; Take care of the counter first
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	.IF DEFINED DEBUG$CURRENT_SANITY
	BSBW	CHECK_CURRENT		; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY

;
; Check to see if there are any requests on the blocked queue. If the queue
; is populated, remove the next request off the queue. If that request is
; non-sequential, start it up and keep removing requests and incrementing the 
; current counter until a sequential request is found, or there are no more 
; entries on the queue. If the request just pulled off the queue is a 
; sequential request, the current counter for this unit is checked. The 
; sequential command is only started if there are no other commands being 
; processed for this unit.
;
10$:	BBS	#UQB$V_SEQ,-		; If there is a sequential command
		UQB$W_FLAGS(R4),40$	;  executing now, skip the rest
	REMQUE	@UQB$L_BLOCKED_FL(R4),R3; Get the next request off the queue
	BVS	40$			; The queue was null, skip the rest
	SUBL	#HRB$L_WAIT_FL,R3	; Point to the start of the HRB
	DECW	UQB$W_NUM_QUE(R4)	; Adjust the queue element counter
	BBS	#HRB$V_VCFAILED,-	; If this command is part of the VC
		HRB$W_FLAGS(R3),20$	;  cleanup, there is no message buffer
	MOVL	HRB$L_MSGBUF(R3),R2	; Get the MSCP packet for this request
	CMPB	MSCP$B_OPCODE(R2),-	; If the command just dequeued
		#MSCP$K_OP_ACCES	;  is not a sequential command
	BGEQU	30$			;  start it executing where it stopped
20$:	TSTW	UQB$W_CURRENT(R4)	; Otherwise, check to make sure this is
	BEQL	30$			;  going to be the only cmd executing
	INSQUE	HRB$L_WAIT_FL(R3),-	; If its not, replace this request
		UQB$L_BLOCKED_FL(R4)	;  on the front of the blocked queue,
	INCW	UQB$W_NUM_QUE(R4)	;  set the counter for number on queue
	BRB	40$			;  and continue
30$:	INCW	UQB$W_CURRENT(R4)	; Record the unblocking
	.IF DEFINED DEBUG$CURRENT_SANITY
	BSBW	CHECK_CURRENT		; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY
	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis
	;
	; Resume a suspended thread.
	;
	; Inputs to resumed thread:
	;   R3 HRB address
	;   R4 UQB address
	;
	; Outputs expected:
	;   R0-R5 may be destroyed
	;
	PUSHL	R4			; Save the UQB address for this unit
	JSB	@HRB$L_RESPC(R3)	;  and restart the blocked request
	POPL	R4			; Restore the UQB address
	BRW	10$			; See if another request can be started

;
; Get rid of requests that are waiting for memory. We may not have freed up
; very much memory ourselves, but since we are using non-paged pool, there
; may be extra available from other sources.
;
40$:	MOVL	G^SCS$GL_MSCP,R5	; Get the address of the private struct
	REMQUE	@DSRV$L_MEMW_FL(R5),R3	; Get the next request off the MEMW que
	BVS	50$			; If this is a null queue, we are done!
	DECW	DSRV$W_MEMW_CNT(R5)	; Adjust the counter
	SUBL	#HRB$L_WAIT_FL,R3	; Reset to the beginning of the HRB
	MOVL	HRB$L_UQB(R3),R4	; Restore the UQB address so we can
	INCW	UQB$W_CURRENT(R4)	;  update the active request counter
	.IF DEFINED DEBUG$CURRENT_SANITY
	BSBW	CHECK_CURRENT		; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY
	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis
	;
	; Resume a suspended thread.
	;
	; Inputs to resumed thread:
	;   R3 HRB address
	;   R4 UQB address
	;
	; Outputs expected:
	;   R0-R5 may be destroyed
	;
	JSB	@HRB$L_RESPC(R3)	; Transfer control to the runable req

50$:	POPR	#^M<R2,R3,R5>		; Restore the saved registers
	RSB

	.PAGE
	.SBTTL	-	XFER_ERR - 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 
;	R2  =  table to use for translation
;       R3  =  HRB
;
; Outputs:
;
;	R0  =  MSCP error code
;
;-

XFER_ERR:

	.JSB_ENTRY	INPUT=<R0,R2,R3>, -
			OUTPUT=<R0>

	TSTL	R2        		; Did we enter with an table?
	BNEQ	10$                     ; yes
	MOVAB	ERR_TBL,R2              ; no lets use the "default"

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	#2,R2			; Step to next entry
	BRB	10$			;  and start it all over again

20$:	MOVZWL	(R2)+,R0		; Pick up the corresponding MSCP error

;
; Do some final cleaning up. If this is an MSCP device
;  check for the MSCP status DUDRIVER may have stored in
;  the IOSB and if present, return it in the end message.
;
        PUSHR   #^M<R4,R5>              ; Save registers
        MOVL    HRB$L_UQB(R3), R4       ; Get the UQB
        MOVL    UQB$L_UCB(R4), R4       ; Get the UCB
        BBC     #DEV$V_MSCP,-           ; If this is not an MSCP device,
                UCB$L_DEVCHAR2(R4),25$  ;  don't check for MSCP status.
        MOVL    HRB$L_IRP_CDRP(R3),R5   ; Get the address of the IRP
        BEQL    25$                     ; Just in case no IRP
        MOVZWL  IRP$L_IOST2+2(R5),R4    ; Get the MSCP status from
        BEQL    25$                     ;  the IOSB .. make sure its there
        MOVL    R4,R0                   ; Move the MSCP status
25$:    POPR    #^M<R4,R5>              ; Restore the saved registers
	RSB				;  and return it to the caller


	.PAGE
	.SBTTL	-	ERR_OFFLINE - Take Care of the Online Bitmap.
;+
; Functional Description:
;
; This routine is used to take care of the online bitmap for a device that 
; has reported a device offline error. If this client was the only host that
; had the device online, the state in the UQB is changed in addition to
; clearing the bit in the mask.
;
; Inputs:
;
;	R0  =  system service error code 
;	R3  =  HRB address
;
; Outputs:
;
;	R0  =  MSCP error code
;	R3  =  HRB address
;
;-

ERR_OFFLINE:

	.JSB_ENTRY	INPUT=<R0,R3>, -
			OUTPUT=<R0,R3>

	MOVL	HRB$L_UQB(R3),R4	; Get the UQB address
	MOVL	UQB$L_UCB(R4),R5	;  and follow that to the UCB address
	PUSHL	R0			; Save the MSCP error to return
20$:	MOVAL	UQB$B_ONLINE(R4),R1	; R1 -> online bitmap
	SKPC	#0,#MAX_HOSTS/8,(R1)	; If there are no other hosts online,
	BEQL	60$			; we are finished, continue...
	FFS	#0,#8,(R1),R0		; Find host online
	BEQL	55$			; If EQL there is something wrong with SKIPC
	BBCC	R0,(R1),30$		; Clear this hosts bit
30$:	BBC	#DEV$V_MSCP,-		; If this isn't a MSCP device, it
		UCB$L_DEVCHAR2(R5),40$	;  can't be a shadow set.
	BBS	#MSCP$V_SHADOW,-	; If this is a shadow set virtual unit
		UCB$W_MSCPUNIT(R5),50$	;  don't change the online count
40$:	DECB	UCB$B_ONLCNT(R5)	;  and note the change in online count
50$:	TSTB	UCB$B_ONLCNT(R5)	; Set the condition codes
	BGEQ	20$			; Look for the next bit in the map

55$:	BUG_CHECK MSCPSERV, FATAL	; Online count should NEVER be negative

;
; 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.
;
	ASSUME	UQB$K_ST_OFFLINE    EQ  MSCP$K_ST_OFFLN
60$:	POPL	R0			; Restore the MSCP error to return
	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


;
; Write logging related error.
;
ERR_WLG:
	.JSB_ENTRY	INPUT=<R0,R3>, -
			OUTPUT=<R0,R1,R3>

	MOVL	#<MSCP$W_HRN@8!MSCP$K_ST_ICMD>,R0 ; MSCP status and subcode
	RSB

	.PAGE
	.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
; refrences 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.
;-

FIND_UQB::
	.JSB_ENTRY INPUT=<R2,R3>,OUTPUT=<R0,R2,R3>,PRESERVE=<R1>
	PUSHR	#^M<R1,R4,R5>		; Save any registers we plan on using
	MOVL	HRB$L_HQB(R3),R5	; Get the address of the HQB 
;
; The request just received was sent from a client system running a version
; of the disk 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_DSRV(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	#DSRV$K_MAX_UNITS,R0		; Past end of table?
	BGTRU	13$			;  No, continue, unit # is in range
	BRB	14$			; Give up if unit off the scale

13$:	MOVL	DSRV$L_UNITS(R5)[R0],R4	; Find the UQB address in the table
	BNEQ	120$			; Continue if there is one
14$:	BRW	BAD_UNIT		; Else indicate no such unit

15$:
	MNEGW	#1,MSCP$L_CMD_REF+2(R2) ; Poison end packet
	BRW	BAD_UNIT

;
; 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	DSRV$L_UNITS(R5),R1	;  determine the start of the unit table
	MOVL	#DSRV$K_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	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
50$:	BRB	10$			; Return to the main path
;
; 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	CHECK_CURRENT		; Sanity check the current counters
	.ENDC	   ;DEBUG$CURRENT_SANITY
	MOVL	#SS$_NORMAL,R0		; Set a normal return status
	POPR	#^M<R1,R4,R5>		; Restore the registers we used
	RSB				;  and return to the caller

BAD_UNIT:
	CLRL	R0			; Return an unsuccessful status
	POPR	#^M<R1,R4,R5>		; Restore the registers we used
	RSB				;  and return to the caller

	.PAGE
	.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 (send_end only)
;	R1  =  command length (send_pkt only)
;	R2  =  MSCP packet address
;	R3  =  HRB address 
;
; Outputs:
;
;	R3  =  HRB address
;	R4  =  PDT address
;	R5  =  CDRP address
;-

;
; Entry here, means that the message buffer is to be returned with status.
; 
SEND_END:
	.JSB_ENTRY INPUT=<R0,R2,R3>,OUTPUT=<R3,R4,R5>
	

	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	MOVW	R0,MSCP$W_STATUS(R2)	; Set the status
	MOVZBL	MSCP$B_OPCODE(R2),R1	; Pick up opcode field
	BICL2	#MSCP$K_OP_END,R1	; Isolate OPCODE for use as index
	BISB2	#MSCP$K_OP_END,-	; Reset the op-code to
		MSCP$B_OPCODE(R2)	;  make an end packet
	MOVZBL	L^END_PKT_LEN[R1],R1	; Get the message length from the table
	

; 
; Prepare the CDRP in the HRB, send off the end message to the port driver.
;
SEND_PKT:
	.JSB_ENTRY INPUT=<R1,R2,R3>,OUTPUT=<R3,R4,R5>
	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.
;
	CLRL	CDRP$L_RWCPTR(R5)	; Clear RWAITCNT reference to avoid
					;  incorrect stalls.
	MOVL	HRB$L_HQB(R3),R4	; Then get the CDT address
	MOVL	HQB$L_CDT(R4),R4	;  describing the link to the system for
	MOVL	R4,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
	
	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 DSRV$V_LOG_ENABLD  EQ  0

	MOVL	G^SCS$GL_MSCP,R0	; Get the DSRV address.
	BLBC	DSRV$W_STATE(R0),10$	; Branch if logging is disabled.
	PUSHL	R0
	MOVL	#PKT$C_MSCP_END,R0
	BSBW	LOG_PKT		; Otherwise, log the end packet.
	POPL	R0
10$:	
	.ENDC

	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)
20$:	BISW	#HRB$M_STATE_INVALID,-	; The state of this request is "current"
		HRB$W_STATE(R3)		;  leave the old state for diagnosis
	BSBW	CLEANUP_HRB		; Deallocate all HRB held resources
	BRW	UNBLOCK			;  and drop the "current" counter

;
; Entry here means that a request was made to the server requiring a valid
; unit, and no UQB was found for the unit specified.
;
ERROR_NO_UNIT:
	MOVL	#MSCP$K_ST_OFFLN,R0	; Return an offline status with the
	BRW	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.
;
PACKET_ERROR:
	MOVB	MSCP$B_OPCODE(R2),R0    ; 'add' the opcode to R0
	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	SEND_PKT		;  and send out the end message

	.PAGE
	.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 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. 
;
ABORT_UNHOOK_CDRP:
	.JSB_ENTRY INPUT=<R1,R3,R4,R5>,OUTPUT=<R1>
	CMPL	CDRP$L_HRB(R5),R1	; See if this is the right CDRP
	BEQL	UNHOOK_CDRP		;  if it is, go ahead and unhook
	RSB				;  otherwise, just return

UNHOOK_CDRP::
	.JSB_ENTRY INPUT=<R1,R3,R4,R5>,OUTPUT=<R1>
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY

	IDLE_CDRP			; 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.
;
	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	PCHIST			; Save this PC if we are keeping track
	.ENDC	   ;DEBUG$PC_HISTORY
	PUSHR	#^M<R2,R4>		; Save any registers used in cleanup
	MOVL	CDRP$L_HRB(R5),R2	; Get the HRB address
	MOVL	HRB$L_UQB(R2),R4	; Get the UQB address
	BICW	#UQB$M_SEQ,-		; Clear the sequential bit in case
		UQB$W_FLAGS(R4)		;  this command was sequential
	BISW	#HRB$M_DEQUEUED,-	; Mark this request as unhooked,
		HRB$W_FLAGS(R2)		;  its resources can be deallocated
	BISW	#HRB$M_UNBLOCK,-	; Set a bit in the flags field to signal
		HRB$W_FLAGS(R2)		;  unblock must be called on cleanup
	POPR	#^M<R2,R4>		; Restore the registers used here

	RSB				; The CDRP found was not the right one

	.PAGE
	.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 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 disk 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 DSRV 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
;-


DEALLOC_HQB::
	.JSB_ENTRY INPUT=<R5>,OUTPUT=<R0,R5>,PRESERVE=<R1>

	.IF DEFINED DEBUG$PC_HISTORY
	BSBW	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, locate load balancing strucures belonging to this connection and
; delete them.
;
; Locate the HULB vector and delete it.
;
	MOVL	HQB$L_HULB_VECTOR(R5),R0; Locate the HULB vector and clear
	BEQL	90$			;  the pointer to it.
	CLRL	HQB$L_HULB_VECTOR(R5)	
	MOVZWL	HQB$W_MAX_HULB(R5),R1	; Get the size of the HULB vector in bytes
	ASHL	#2,R1,HQB$W_SIZE(R0) 	;  and store it in the appropriate offset.
	PUSHL	R2			; Return it to pool.
	JSB	G^EXE$DEANONPAGED
	POPL	R2
	
90$:
;
; Walk the HULB queue and remove any belonging to this host.
;
	MOVL	HQB$L_DSRV(R5),R4	; Get the server structure address
	MOVZBL	HQB$B_HOSTNO(R5),R3	; Get the host number used into a
					;  register.
	MOVL	DSRV$L_HULB_FL(R4),R1	; Pick up pointers for the HULB queue
	MOVAB	DSRV$L_HULB_FL(R4),R2
100$:	CMPL	R1,R2			; Are we at the end of the queue?
	BEQL	120$			; Branch if yes to delete HQB
        CMPW	R3,HULB$W_HOSTNO(R1)	; Does this HULB belong to us?
	BNEQ	110$			; No
	MOVL	R1,R0			; Save the HULB address
	MOVL	HULB$L_FLINK(R1),R1	; Pick up the FLINK
	REMQUE	(R0),R0			; Remove the HULB from the queue.
	PUSHR	#^M<R1,R2>		; Return it to pool.
	JSB	G^EXE$DEANONPAGED
	POPR	#^M<R1,R2>
	BRB	100$

110$:	MOVL	HULB$L_FLINK(R1),R1	; Pick up the next HULB and repeat
	BRB	100$			;  the process

120$:
;
; Check to make sure that there are no requests still outstanding
; for this host.
;
	CLRL	R0			; Assume there are more requests
	MOVL	HQB$L_HRB_FL(R5),R3	; Get the address of the first HRB
	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, return any load balancing resources owned by this HQB and return
; the host number to be used by another incoming connect request.
; 

	MOVZBL	HQB$B_HOSTNO(R5),R3	; Get the host number used into a
					;  register so that bit can be cleared
	BBCC	R3,DSRV$B_HOSTS(R4),10$	
10$:	REMQUE	(R5),R0			; Dequeue the HQB from the list
	DECW	DSRV$W_NUM_HOST(R4)	; Adjust the count of hosts being served
	JSB	G^EXE$DEANONPAGED	;  found, 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

	.PAGE
	.SBTTL	-	ALLOCATE_HULB - Allocate a new HULB
;+
; Functional Description:
;
; This routine will allocate a new HULB (Host Unit Load Block) from
; nonpaged pool and link it to the HULB vector for the current host.
;
; Inputs:
;	R0	unit number index
;	R1	base of HULB vector
;	R3	HRB
;	R4	UQB
;
; Outputs:
;	R5	address of newly created HULB
;	R0,R1 destroyed
;
; Implicit Outputs:
;	address of newly created HULB loaded into vector
;	HULB is linked into DSRV based queue
;-

ALLOCATE_HULB:
	.JSB_ENTRY INPUT=<R0,R1,R3,R4>,OUTPUT=<R5>,SCRATCH=<R0,R1>

	PUSHR	#^M<R0,R2,R4>		; Save UQB and unit number
	MOVAL	(R1)[R0],R4		; Save address of vector entry
	MOVZWL	#HULB$K_LENGTH,R1	; Set size of the UQB structure
	JSB	G^EXE$ALONONPAGED	; Grab some pool
	BLBC	R0,999$			; If there was insf memory for alloc
	MOVL	R2,(R4)			; Store the HULB address in the vector
	MOVL	R2,R5			;  and in R5
	MOVL	G^SCS$GL_MSCP,R0	; Locate DSRV
	INSQUE	(R5),@DSRV$L_HULB_BL(R0); Insert HULB in DSRV based queue
	MOVW	#HULB$K_LENGTH,-	; Initialize data structure stuff
		HULB$W_SIZE(R5)
	MOVB	#DYN$C_DSRV,-
		HULB$B_TYPE(R5)
	MOVB	#DYN$C_DSRV_HULB,-
		HULB$B_SUBTYPE(R5)
	POPR	#^M<R0,R2,R4>		; Restore UQB and unit number
	MOVW	R0,HULB$W_UNITNO(R5)	; Load unit number
	MOVL	HRB$L_HQB(R3),R0	; Locate HQB
	MOVZBW	HQB$B_HOSTNO(R0),-	;  and load host no into HULB
		HULB$W_HOSTNO(R5)	;
	RSB

999$:	BUG_CHECK	MSCPSERV, FATAL

	.PAGE
	.SBTTL	-	EXTEND_HULB - Expand the size of the HULB vector
;+
; Functional Description:
;
; This routine will allocate a new HULB vector from nonpaged pool
; and copy the old vector to the new one.
;
; Inputs:
;
; Outputs:
;	none
;
; Implicit Outputs:
;	address of newly created HULB loaded into vector
;-
EXTEND_HULB:
	.JSB_ENTRY
	RSB				; Dummy routine

	.IF DEFINED DEBUG$PC_HISTORY
	.PAGE
	.SBTTL	Debugging Routines
	.SBTTL	-	PC History recording
;+
; Functional Description:
;
; This routine is used to maintain a running history of the Program
; Counter as the MSCP 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
;
; Outputs:
;
;	None
;
; Implicit Outputs:
;
;	Address is logged in the PC history table
;	PCHISTCUR is moved to point to the next available cell
;-

PCHIST::
	.JSB_ENTRY PRESERVE=<R0,R1>
	PUSHR	#^M<R0,R1>		; Save any registers used

	MOVL	8(SP),R0		; Get the address to save
	SUBL	G^SCS$GL_MSCP,R0	; Calculate the server offset
	MOVL	PCHISTCUR,R1		; Get the cell in the table
	MOVW	R0,(R1)+		;  and store the offset value
	CMPL	R1,PCHISTEND		; Check for the end of the table
	BLSSU	10$			;  still plenty of room
	MOVL	PCHISTBEG,R1		; Wrap around back to the start
	INCL	PCHISTFUL		;  and bump the filled counter

10$:	MOVL	R1,PCHISTCUR		; Get the new table offset
	POPR	#^M<R0,R1>		; Restore the registers
	RSB				;  and return

	.ENDC	   ;DEBUG$PC_HISTORY

	.IF DEFINED DEBUG$CURRENT_SANITY
	.PAGE
	.SBTTL	-	Current Counter Sanity Test
;+
; 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.
;-

CHECK_CURRENT::
	.JSB_ENTRY PRESERVE=<R0,R1>

	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_MSCP,R5	; Get the DSRV address
	MOVAL	DSRV$L_HQB_FL(R5),R1	; Get the HQB list head
	MOVL	R1,R2			;  and save a copy away
10$:	MOVL	(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$:	SUBW	DSRV$W_MEMW_CNT(R5),R0	; Subtract out memory waiters
	MOVAL	DSRV$L_UQB_FL(R5),R1	; Get the address of the UQB
	MOVL	R1,R2			;  list head and perform the 
30$:	MOVL	(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	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 DSRV field which
;	point to the buffer.
;
; Inputs:
;
;	R5	DSRV address
;
; Outputs:
;
;	R1, R2	Scratched
;	R0	Return status frem EXE$ALONONPAGED
;
; Implicit Outputs:
;
;	DSRV$L_BUF_START	Address of start of buffer
;	DSRV$L_BUF_END		Address of end of buffer
;	DSRV$L_NEXT_READ	Address if next packet to read
;	DSRV$L_NEXT_WRITE	Address if next packet to write
;	DSRV$W_STATE		Indicates logging code is present
;				 and initialized
;	DSRV$W_INC_LOLIM	Low unit number to include
;	DSRV$W_INC_HILIM	High unit number to include
;	DSRV$W_EXC_LOLIM	Low unit number to exclude
;	DSRV$W_EXC_HILIM	High unit number to exclude
;
;-

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

CREATE_LOG:
	.JSB_ENTRY INPUT=<R5>,OUTPUT=<R0>,SCRATCH=<R1,R2>,PRESERVE=<>

	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.
	movl	r2,g^exe$gl_sitespec	; R2 = address / R1 = size.
	CLRQ	(R2)+			; Clear FLINK and BLINK.
	MOVW	R1, (R2)+		; Save size.
	MOVW	#DYN$C_DSRV, (R2)+	; Set type and sub-type.

	MOVL	R2,-			; Save address of start of ring buffer.
		DSRV$L_LOG_BUF_START(R5);
	ADDL3	#LOG_BUF_SIZE, R2,-	; Save address of end of ring buffer.
		DSRV$L_LOG_BUF_END(R5)	;
	MOVL	R2, DSRV$L_NEXT_READ(R5); Init read pointer.
	MOVL	R2,- 			; Init write pointer.
		DSRV$L_NEXT_WRITE(R5)	;
	MOVL	#-1@16,-		; Set default include unit low/high
		DSRV$W_INC_LOLIM(R5)	;  limits to include all units.
	MOVZWL	#-1,-			; Set default exclude unit low/high
		DSRV$W_EXC_LOLIM(R5)	;  limits to exclude no units.
	MOVW	#DSRV$M_LOG_PRESENT,-	; Buffer present and accounted for, sir.
		DSRV$W_STATE(R5)	;
99$:	RSB

	.PAGE
	.SBTTL	-	LOG_CMD_PKT - Log a MSCP command packet
	.SBTTL	-	LOG_END_PKT - Log a MSCP end packet
;+
;
; Functional Description:
;
;	This following routines log a MSCP packet into the server's
;	ring buffer. 
;
; Inputs: 
;
;	R0	PACKET TYPE
;	R1	Length of pkt to be logged
;	R2	Address of pkt to be logged
;	R3	HRB address
;	R4	CDT address (COMMAND PKT)
;
;
; Implicit inputs:
;
;	DSRV$L_NEXT_READ	Address of next packet to be read
;	DSRV$L_NEXT_WRITE	Address to write next packet
;	DSRV$L_LOG_BUF_START	Address of start of buffer
;	DSRV$L_LOG_BUF_END	Address of end of buffer
;	DSRV$W_INC_LOLIM	Low unit number to include
;	DSRV$W_INC_HILIM	High unit number to include
;	DSRV$W_EXC_LOLIM	Low unit number to exclude
;	DSRV$W_EXC_HILIM	High unit number to exclude
;
;	Status Bits in DSRV$W_STATE:
;
;	DSRV$V_PKT_LOGGED	Packet has been logged since last read
;	DSRV$V_PKT_LOST		One or more packets overwritten since
;				last read
;-

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

	.ENABLE LSB

LOG_PKT:
	.JSB_ENTRY 	INPUT=<R0,R1,R2,R3>,-
		  	OUTPUT=<>,-
			PRESERVE=<R0,R1>

	MOVL	R0,R6				;save pkt type
	CMPL	#PKT$C_MSCP_CMD,R0              ;end message?
	BEQL	10$                             ;no
	MNEGL	#1,R7
	MOVL	HRB$L_HQB(R3), R3		; Get HQB address.
	MOVL	HQB$L_CDT(R3), R4		; Get CDT address.
10$:    MOVL	G^SCS$GL_MSCP, R5		; Get DSRV address.
	MOVAL	DSRV$W_INC_LOLIM(R5), R0	; Get address of range settings.
	MOVW	MSCP$W_UNIT(R2), R3		; Get unit number.
        MNEGL	#1,R7

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

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

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

; Log header info to buffer

20$:	READ_SYSTIME (R0)+			; Fill in the system time
	MOVL	CDT$L_PB(R4), R3		; Get PB address.
	BEQL	21$
	MOVL	PB$L_SBLINK(R3), R3		; Get SB address.
	BNEQ	22$

21$:    CLRQ    (R0)+                           ; Show no SYSTEMID
        MOVW    R1, -2(R0)             ; Packet length
        ASSUME  PKT$T_NODENAME   EQ   PKT$W_LENGTH+2
        MOVL    #^X2A2A2A03, (R0)+              ; Make nodename ***
        CLRQ    (R0)+
        CLRL    (R0)+
        BRB     25$
 
22$:	MOVQ	SB$B_SYSTEMID(R3), (R0)+	; Fill in system id.
	MOVW	R1, -2(R0)			; Fill in logged pkt length.
	MOVQ	SB$T_NODENAME(R3), (R0)+	; Grab nodename, part 1.
	MOVQ	SB$T_NODENAME+8(R3), (R0)+	; Grab nodename, part 2.
25$:	MOVL	R7,(R0)+                        ; IRP ADDR
	MOVL	R6,(R0)+                        ; type
	MOVW	MSCP$W_UNIT(R2),-2(R0)          ; unit
; Actually log the packet now

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

; now R3 = new address for next write

	MOVL	G^SCS$GL_MSCP, R5		; Restore DSRV address.
	CMPL	R3, DSRV$L_LOG_BUF_END(R5)	; At end of ring buffer?
	BLSSU	30$				; Branch if not.
	MOVL	DSRV$L_LOG_BUF_START(R5), R3	; Else reset to beginning.
30$:	MOVL	R3, DSRV$L_NEXT_WRITE(R5)	; Set next write address.
35$:	RSB
	.DISABLE LSB


        .PAGE
        .SBTTL  ----- IRP/EXIT LOGGING ROUTINES -----
        .SBTTL  MSCP$LOG_IRP_PKT       - Log an IRP packets
;++
;
; MSCP$LOG_IRP_PKT     - Log an IRP packet
;
; Functional Description:
;
;       The following routines copy an IRP packet into MSCP's IRP
;       ring buffer.  Log an IRP command packet when MSCP CMD and an IRP exit
;       packet when MSCP END.
;
;       Separate ranges of unit numbers can be selectively included
;       or excluded from being logged.  The included units range is
;       checked first, then the excluded units range.  If the unit
;       number is within the include range and NOT within the exclude
;       range, the packet is logged.  The default conditions are:
;               INCLUDE=ALL  (low =  0, high = -1)
;               EXCLUDE=NONE (low = -1, high =  0)
;
;
; Inputs: (MSCP$LOG_IRP...PKT)
;
;
;       R0,R1   IOSB status
;       R3      UCB address
;       R5      IRP address
;
; Implicit inputs:
;
;       DSRV$L_NEXT_READ                Address of next packet to be read
;       DSRV$L_NEXT_WRITE               Address to write next packet
;       DSRV$L_LOG_BUF_START            Address of start of buffer
;       DSRV$L_LOG_BUF_END              Address of end of buffer
;       DSRV$W_INC_LOLIM                Included units low limit
;       DSRV$W_INC_HILIM                Included units high limit
;       DSRV$W_EXC_LOLIM                Excluded units low limit
;       DSRV$W_EXC_HILIM                Excluded units high limit
;
;       Status Bits in DSRV$W_STATE:
;
;       DSRV$V_PKT_LOGGED               Packet has been logged since last read
;       DSRV$V_PKT_LOST                 One or more packets overwritten since
;                                        last read
;--
        ASSUME  DSRV$W_INC_HILIM EQ DSRV$W_INC_LOLIM+2
        ASSUME  DSRV$W_EXC_LOLIM EQ DSRV$W_INC_HILIM+2
        ASSUME  DSRV$W_EXC_HILIM EQ DSRV$W_EXC_LOLIM+2

        .ENABLE LSB

MSCP$LOG_IRP_PKT:
        .JSB_ENTRY      INPUT=<R3,R5>,-
                        OUTPUT=  <>,-
                        SCRATCH= <>,-
                        PRESERVE=<R0,R1,R2,R3,R4,R5,R6>

        MOVL    #PKT$C_IRP,R6                   ; It's an IRP CMD packet
        BRW     MSCP$LOG_IRP_COMMON

MSCP$LOG_IRP_EXIT_PKT:
        .JSB_ENTRY      INPUT=<R0,R1,R3,R5>,-
                                OUTPUT=  <>,-
                                SCRATCH= <>,-
                                PRESERVE=<R0,R1,R2,R3,R4,R5,R6>

        MOVL    #PKT$C_EXIT,R6                  ; It's an IRP exit packet

MSCP$LOG_IRP_COMMON:

        PUSHR   #^M<R0,R1>
        MOVAL   IRP$L_FQFL(R5),R5               ; Move to start of the CDRP
        MOVL    G^SCS$GL_MSCP, R2               ; Get DSRV address.
        MOVW    UCB$W_UNIT(R3), R4              ; Get unit number.
        MOVAL   DSRV$W_INC_LOLIM(R2), R0        ; Get include range low limit.

        CMPW    R4, (R0)+                       ; Within include range low end?
        BLSSU   35$                             ; LSS means not included.
        CMPW    R4, (R0)+                       ; Within include range hi end?
        BGTRU   35$                             ; GTR means not included.

        CMPW    R4, (R0)+                       ; Outside of exclude range?
        BLSSU   15$                             ; LSS means NOT excluded.
        CMPW    R4, (R0)+                       ; Within exclude range?
        BLEQU   35$                             ; LEQ means excluded.

15$:    MOVL    DSRV$L_NEXT_WRITE(R2), R0       ; Get location to write pkt.

        BBCS    #DSRV$V_PKT_LOGGED,     -       ; If bit was clear, buffer
                DSRV$W_STATE(R2), 20$           ;  is logically empty.
        CMPL    R0, DSRV$L_NEXT_READ(R2)        ; Check for full buffer.
        BNEQ    20$                             ; Branch if buffer not full or
                                                ;  already clobbered.
        BISW    #DSRV$M_PKT_LOST, -             ; Else, we have wrapped around
                DSRV$W_STATE(R2)                ;  the buffer.

; Log header info to buffer


20$:
        ASSUME  PKT$Q_SYSTIME           EQ      0
        READ_SYSTIME    (R0)+                   ; Fill in system time.

        ASSUME  PKT$B_SYSTEMID          EQ      PKT$Q_SYSTIME+8
        BBC     #DEV$V_MSCP,-                   ; If this is not an MSCP
                UCB$L_DEVCHAR2(R3),21$          ;  device,then there's no CDT
        MOVL    UCB$L_CDT(R3), R2               ; R2 => CDT.
        BEQL    21$                             ;  name ***
        MOVL    CDT$L_PB(R2), R2                ; Get R2 => PB address.
        BNEQ    22$                             ;  name ***
        ASSUME PKT$W_LENGTH   EQ    PKT$B_SYSTEMID+6
21$:    CLRQ    (R0)+                           ; Show no SYSTEMID
        MOVW    #IRP$C_CDRP, -2(R0)             ; Packet length
        ASSUME  PKT$T_NODENAME   EQ   PKT$W_LENGTH+2
        MOVL    #^X2A2A2A03, (R0)+              ; Make nodename ***
        CLRQ    (R0)+
        CLRL    (R0)+
        BRB     23$

22$:
        MOVL    PB$L_SBLINK(R2), R2             ; Get R2 => SB address.
        MOVQ    SB$B_SYSTEMID(R2), (R0)+        ; Fill in system id.

        ASSUME  PKT$W_LENGTH            EQ      PKT$B_SYSTEMID+6
        MOVW    #IRP$C_CDRP, -2(R0)             ; Fill in logged pkt length.

        ASSUME  PKT$T_NODENAME          EQ      PKT$W_LENGTH+2
        MOVQ    SB$T_NODENAME(R2), (R0)+        ; Grab nodename, part 1.
        MOVQ    SB$T_NODENAME+8(R2), (R0)+      ; Grab nodename, part 2.
23$:
        ASSUME  PKT$L_IRP_ADDR          EQ      PKT$T_NODENAME+16
        MOVAB   CDRP$L_IOQFL(R5), (R0)+         ; Save IRP address

        ASSUME  PKT$B_TYPE              EQ      PKT$L_IRP_ADDR+4
        MOVW    R6, (R0)+                       ; Fill in type (IRP or Exit)

        ASSUME  PKT$W_UNIT              EQ      PKT$B_TYPE+2
        MOVW    R4, (R0)+                       ; Fill in MSCP unit.

        ASSUME  PKT$C_HDR_SIZE          EQ      PKT$W_UNIT+2


; Actually log the packet now

        PUSHR   #^M<R0,R5>
        MOVC5   #IRP$C_CDRP,-                   ; Copy packet into buffer.
                CDRP$L_IOQFL(R5),-
                #0,-
                #PKT$C_DATA_LEN,-
                (R0)
        POPR    #^M<R0,R5>
; now R3 = new address for next write.  Also it is just beyond the copied
; IRP.  As such, we could use CDRP offsets to get at IRP fields in the copy.

        MOVL    G^SCS$GL_MSCP, R2               ; Restore DSRV address.
        CMPL    R3, DSRV$L_LOG_BUF_END(R2)      ; At end of ring buffer?
        BLSSU   30$                             ; Branch if not.
        MOVL    DSRV$L_LOG_BUF_START(R2), R3    ; Else reset to beginning.
30$:    MOVL    R3, DSRV$L_NEXT_WRITE(R2)       ; Set next write address.

        CMPL    #PKT$C_EXIT,R6                  ; Is it an exit packet
        BNEQ    35$                             ; BR around if not
        MOVL    R0,R5                           ; Need to use R0 so move it
        POPR    #^M<R0,R1>                      ; Restore IOSB
        MOVL    R0,IRP$L_IOST1(R5)              ; Copy IOST1 to logged exit IRP
        MOVL    R1,IRP$L_IOST2(R5)              ; Copy IOST2 to logged exit IRP
        RSB

35$:    POPR    #^M<R0,R1>
        RSB

        .DISABLE LSB
        .ENDC

	.PAGE
	.SBTTL	MSCP$ALLOCATE	General allocate memory subroutine
;+
; MSCP$ALLOCATE - Allocate memory subroutine
;
; This routine is called to allocate a block of memory from a pool whose entries
; are maintained in a memory order sorted list. This subroutine is a local copy
; EXE$ALLOCATE with the pool checking code removed. The pool checker was a
; prime cause of CPU overhead during served I/O requests. It is not needed here
; because the server data pool is preallocated and private.
;
; INPUTS:
;
;	R1 = size of block required in bytes.
;	R3 = address of allocation region listhead.
;
; OUTPUTS:
;
;	R0 = low bit clear if memory is not available.
;
;		R2 = 0 (used to be the size of largest block found)
;
;	R0 = low bit set if memory allocated with:
;
;		R1 = size of allocated block.
;		R2 = address of allocated block.
;-

MSCP$ALLOCATE::				;Allocate memory
	.JSB_ENTRY INPUT=<R1,R3>,OUTPUT=<R0,R1,R2>,PRESERVE=<>
	MOVL	R3,R0			;Copy address of first free block address
10$:	MOVL	R0,R2			;Save address of previous free block
	MOVL	(R2),R0			;Get address of next free block
	BEQL	30$			;If eql no memory available
	CMPL	R1,4(R0)		;Free block big enough?
	BGTRU	10$			;If gtru no
	BEQL	20$			;If eql free block is exact size
	ADDL3	R0,R1,R3		;Calculate address of new free block
	MOVL	(R0)+,(R3)+		;Copy link to next free block
	SUBL3	R1,(R0),(R3)		;Calculate size of new free block
	MOVAL	-(R3),-(R0)		;Set link to new free block
20$:	MOVL	(R0),(R2)		;Copy link to new free block
	MOVAB	(R0)+,R2		;Set adr of allocated block, indicate success
	RSB				;Return

; No block of the required size could be found.  Return 0 in R2 where the length of
; the largest free block used to go.

30$:	CLRL	R2			;Initial value of largest free block seen
	RSB				;Return failure

	.PAGE
	.SBTTL	MSCP$DEALLOCATE	    General Deallocation Subroutine
;+
; MSCP$DEALLOCATE - Deallocation Subroutine
;
;  This is a local copy of the general EXE$DEALLOCATE routine without the poolchecking 
;  code.
;
; INPUTS:
;
;	R0 = Address of block to be deallocated.
;	R1 = Size of block in bytes
;	R3 = Address of allocation region listhead.
;
; OUTPUTS:
;
;	none
;-

MSCP$DEALLOCATE::			;Deallocate block
	.JSB_ENTRY INPUT=<R0,R1,R3>,OUTPUT=<>,PRESERVE=<>
	PUSHL	R4			;Save registers
	PUSHL	R3
10$:	MOVL	R3,R2			;Save address of previous free block
	MOVL	(R2),R3			;Get address of next free block
	BEQL	20$			;If eql end of list
	CMPL	R0,R3			;Block logically go here?
	BGTRU	10$			;If gtru no
	BEQLU	60$			;If eqlu double deallocation
20$:	MOVL	R3,(R0)			;Assume no agglomeration
	BEQL	30$			;End of list - no agglomeration
	ADDL3	R0,R1,R4		;Calculate address of end of block
	CMPL	R3,R4			;End of block equal to next in list?
	BGTRU	30$			;If gtr do not agglomerate
	BLSSU	60$			;If lss overlapping deallocate
	MOVL	(R3)+,(R0)		;Move link to block being released
	ADDL	(R3),R1			;Accumulate length of new free block
30$:	MOVL	R2,R4			;Calculate ending address of previous block
	MOVL	R0,(R2)+		;Assume no agglomeration
	ADDL	(R2),R4			;Add length to block base address
	CMPL	R0,R4			;End address equal to block being released?
	BGTRU	40$			;If gtr do not agglomerate
	BLSSU	70$			;If lss may be overlapping deallocate
	ADDL	(R2),R1			;Accumulate size of new free block
	MOVL	(R0),-(R2)		;Move link to previous free block
	MOVL	R2,R0			;Set address of new free block
40$:	MOVL	R1,4(R0)		;Set size of free block
50$:	MOVQ	(SP)+,R3		;Restore registers
	RSB

60$:	BUG_CHECK DOUBLDEALO,FATAL	;Double deallocation of memory block

	; If we come here it is either an overlapping deallocate or R2
	; (the previous block pointer) is pointing to the list head.

70$:	SUBL	#4,R2			;Back up R2
	CMPL	R2,(SP)			;Is it pointing to the list head?
	BEQL	40$			;Yes, resume in normal path
	BRB	60$			;No, bugcheck

	.PAGE
	.SBTTL	Read Only Length and Modifier Tables

	DECLARE_PSECT EXEC$NONPAGED_DATA

	.ALIGN	QUAD

;
; DSRV structure
;  

DSRV:
	.BLKB	DSRV$C_LENGTH

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

	$EQU	PCHIST_ENTRIES	512	; Number of entries in the history

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

	.ENDC	   ;DEBUG$PC_HISTORY

	.ALIGN LONG
LIS_CDT:.LONG	0
;
	.ALIGN LONG
SERV_INFO:
SERV_NAME:
	.ASCII	/MSCP$DISK       /
;
	.ALIGN LONG
COM_PKT_LEN:
	.BYTE	0*4,4*4,5*4,3*4,7*4,0*4,0*4,3*4	;?,ABORT,GTCMD,GTUNT,STCON,?,?,GTUNM
	.BYTE	3*4,9*4,9*4,3*4,0*4,0*4,0*4,0	;AVAIL,ONLIN,STUNT,DTACP,?,?,?,?
	.BYTE	0*4,8*4,8*4,8*4,8*4,0*4,0*4,0	;ACCES,CMPCT,ERASE,FLUSH,REPLC,?,?,?
	.BYTE	0*4,9*4,0*4,0*4,0*4,0*4,0*4,0	;FMT,WRHIM,?,?,?,?,?,?
	.BYTE	8*4,8*4,8*4,0*4,0*4,0*4,0*4,0	;COMP,READ,WRITE,?,?,?,?,?
	.BYTE	0*4,0*4,0*4,0*4,0*4,0*4,0*4,0	;?,?,?,?,?,?,?,?
	.BYTE	9*4,0*4,0*4,0*4,0*4,0*4,0*4,0	;TERCO,?,?,?,?,?,?,?
;
	.ALIGN LONG
END_PKT_LEN:
	.BYTE	0*4,4*4,5*4,13*4,7*4,0*4,0*4,11*4;?,ABORT,GTCMD,GTUNT,STCON,?,?,GTUNM
	.BYTE	3*4,11*4,11*4,3*4,0*4,0*4,0*4,0	;AVAIL,ONLIN,STUNT,DTACP,?,?,?,?
	.BYTE	8*4,8*4,9*4,8*4,3*4,0*4,0*4,0	;ACCES,CMPCT,ERASE,FLUSH,REPLC,?,?,?
	.BYTE	0*4,9*4,0*4,0*4,0*4,0*4,0*4,0	;FMT,WRHIM,?,?,?,?,?,?
	.BYTE	8*4,8*4,9*4,0*4,0*4,0*4,0*4,0	;COMP,READ,WRITE,?,?,?,?,?
	.BYTE	0*4,0*4,0*4,0*4,0*4,0*4,0*4,0	;?,?,?,?,?,?,?,?
	.BYTE	9*4,0*4,0*4,0*4,0*4,0*4,0*4,0	;TERCO,?,?,?,?,?,?,?
;
	.ALIGN LONG
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
		MSCP$M_MD_CLSEX>	;    clear serious exception
	.WORD	^C0			;  4-SET CONTROLLER CHARACTERISTICS
	.WORD	^C0			;  5-unused
	.WORD	^C0			;  6-unused
	.WORD	^C0			;  7-GET-UNIT-NAME
	.WORD	^C<-			;  8-AVAILABLE
		MSCP$M_MD_SPNDW!-	;    spin down
		MSCP$M_MD_ALLCD!-	;    all class drivers
		MSCP$M_MD_CLSEX>	;    clear serious exception
	.WORD	^C<-			;  9-ONLINE
		MSCP$M_MD_RIP!-		;    allow self destruct
		MSCP$M_MD_IGNMF!-	;    ignore media format error
		MSCP$M_MD_STWRP!-	;    enable set write protect
		MSCP$M_MD_SHDSP!-	;    shadow unit specified
		MSCP$M_MD_CLSEX>	;    clear serious exception
	.WORD	^C<-			; 10-SET UNIT CHAR
		MSCP$M_MD_STWRP!-	;    enable set write protect
		MSCP$M_MD_SHDSP!-	;    shadow unit specified
		MSCP$M_MD_CLSEX>	;    clear serious exception
	.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_EXPRS!-	;    express request
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_SCCHH!-	;    supress caching (high speed)
		MSCP$M_MD_SEREC!-	;    supress error recovery
		MSCP$M_MD_SECOR!-	;    supress error correction
		MSCP$M_MD_SSHDW>	;    supress shadowing
	.WORD	^C<-			; 17-COMP CTRL DATA
		MSCP$M_MD_EXPRS!-	;    express request
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_SCCHH!-	;    supress caching (high speed)
		MSCP$M_MD_SEREC!-	;    supress error recovery
		MSCP$M_MD_SECOR!-	;    supress error correction
		MSCP$M_MD_SSHDW>	;    supress shadowing
	.WORD	^C<-			; 18-ERASE
		MSCP$M_MD_EXPRS!-	;    express request
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_ERROR!-	;    force error
		MSCP$M_MD_WRSEQ!-	;    write shadow set 1 unit at a time
		MSCP$M_MD_WBKNV!-	;    write back (non volatile)
		MSCP$M_MD_WBKVL!-	;    write back (volatile)
		MSCP$M_MD_SEREC!-	;    supress error recovery
		MSCP$M_MD_SSHDW!-	;    supress shadowing
		MSCP$M_MD_HISLO!-	;    history logging
		MSCP$M_MD_REUSE>	;    reuse entry
	.WORD	^C<-			; 19-FLUSH
		MSCP$M_MD_EXPRS!-	;    express request
		MSCP$M_MD_FLENU!-	;    flush entire unit
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_SECOR!-	;    supress error correction
		MSCP$M_MD_SEREC!-	;    supress error recovery
		MSCP$M_MD_SSHDW!-	;    supress shadowing
		MSCP$M_MD_VOLTL>	;    flush volatile memory only
	.WORD	^C<-			; 20-REPLACE
		MSCP$M_MD_EXPRS!-	;    express request
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_PRIMR>	;    primary replacement block
	.WORD	^C0			; 21-unused
	.WORD	^C0			; 22-unused
	.WORD	^C0			; 23-unused
	.WORD	^C0			; 24-MAINT READ
	.WORD	^C0			; 25-WRITE HISTORY MANAGEMENT
	.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_EXPRS!-	;    express request
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_SCCHH!-	;    supress caching (high speed)
		MSCP$M_MD_SEREC!-	;    supress error recovery
		MSCP$M_MD_SECOR!-	;    supress error correction
		MSCP$M_MD_SSHDW>	;    supress shadowing
	.WORD	^C<-			; 33-READ
		MSCP$M_MD_EXPRS!-	;    express request
		MSCP$M_MD_COMP!-	;    compare
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_SCCHH!-	;    supress caching (high speed)
		MSCP$M_MD_SEREC!-	;    supress error recovery
		MSCP$M_MD_SECOR!-	;    supress error correction
		MSCP$M_MD_SSHDW>	;    supress shadowing

	.WORD	^C<-			; 34-WRITE
		MSCP$M_MD_EXPRS!-	;    express request
		MSCP$M_MD_COMP!-	;    compare
		MSCP$M_MD_CLSEX!-	;    clear serious exception
		MSCP$M_MD_ERROR!-	;    force error
		MSCP$M_MD_WRSEQ!-	;    write shadow set 1 unit at a time
		MSCP$M_MD_WBKNV!-	;    write back (non volatile)
		MSCP$M_MD_WBKVL!-	;    write back (volatile)
		MSCP$M_MD_SEREC!-	;    supress error recovery
		MSCP$M_MD_SECOR!-	;    supress error correction
		MSCP$M_MD_SSHDW!-	;    supress shadowing
		MSCP$M_MD_HISLO!-	;    history logging
		MSCP$M_MD_REUSE>	;    reuse entry
	.WORD	^C0			; 35-unused
	.WORD	^C0			; 36-unused
	.WORD	^C0			; 37-unused
	.WORD	^C0			; 38-unused
	.WORD	^C0			; 39-unused
	.WORD	^C0			; 40-unused
	.WORD	^C0			; 41-unused
	.WORD	^C0			; 42-unused
	.WORD	^C0			; 43-unused    
	.WORD	^C0			; 44-unused
	.WORD	^C0			; 45-unesed
	.WORD	^C0			; 46-unused
	.WORD	^C0			; 47-unused
	.WORD	^C0			; 48-TERMINATE CLASS DRIVER CONNECTION
	.WORD	^C0			; 49-unused


	.ALIGN LONG
ERR_TBL:
	ACTION	SS$_ABORT,	MSCP$K_ST_ABRTD
	ACTION	SS$_MEDOFL,	MSCP$K_ST_OFFLN
	ACTION	SS$_VOLINV,	MSCP$K_ST_OFFLN
	ACTION	SS$_DEVOFFLINE,	MSCP$K_ST_OFFLN
	ACTION	SS$_WRITLCK,	MSCP$K_ST_WRTPR
	ACTION	SS$_DATACHECK,	MSCP$K_ST_COMP
	ACTION	SS$_CTRLERR,	MSCP$K_ST_CNTLR
	ACTION	SS$_FORMAT,	MSCP$K_ST_MFMTE
	ACTION	SS$_FORCEDERROR,MSCP$K_ST_DATA
	ACTION	SS$_PARITY,	<<MSCP$K_ST_DATA>!<1*MSCP$K_ST_SBCOD>>
	ACTION	SS$_IVBUFLEN,	MSCP$K_ST_HSTBF
	ACTION	SS$_TIMEOUT,	<MSCP$K_ST_OFFLN!MSCP$K_SC_UNKNO>
        ACTION	SS$_NOENTRY,	<MSCP$K_ST_ICMD>
	ACTION	0,		MSCP$K_ST_DRIVE	; PATCH SPACE
	ACTION	0,		MSCP$K_ST_DRIVE	; End of table - default error

	.ALIGN LONG

WRHIM_ERR_TBL:

	ACTION	SS$_MEDOFL,MSCP$K_ST_OFFLN
	ACTION	SS$_DEVOFFLINE,MSCP$K_ST_OFFLN
	ACTION	SS$_CTRLERR,MSCP$K_ST_CNTLR
	ACTION	SS$_IVBUFLEN,MSCP$K_ST_HSTBF
	ACTION	SS$_NOENTRY,MSCP$K_ST_WHEAE
	ACTION	SS$_IVLOGTAB,<MSCP$K_ST_WHEAE!MSCP$K_SC_ALLOF>
	ACTION	0,MSCP$K_ST_DRIVE	; PATCH SPACE
	ACTION	0,MSCP$K_ST_DRIVE	; End of table - default error
	
        .ALIGN LONG
MSCP_CMD_TMO:   .LONG 600
MSCP_END::
	.END
