doswstuff=0
.IF DF CA$_IO_DEBUG             ; if debug version
	.TITLE	IOSUBNPAG_MON - NONPAGED I/O RELATED SUBROUTINES
.IFF
	.TITLE	IOSUBNPAG - NONPAGED I/O RELATED SUBROUTINES
.ENDC
	.IDENT	'X-56'
;****************************************************************************
;*									    *
;*  COPYRIGHT © 1978, 1980, 1982, 1984, 1988, 1991, 1992, 1993, 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.		    *
;* 									    *
;*									    *
;****************************************************************************
;
; D. N. CUTLER 13-JUN-76
;
;
; NONPAGED I/O RELATED SUBROUTINES
;
; MODIFIED BY:
;
;	X-56	GCE	Glenn C. Everhart		Nov. 1996
;		The "finipl8" processing as previously defined requires
;		that intercept code determine whether to continue I/O.
;		This is too hard. Change the fast_finish macro to continue
;		I/O when called from contexts which otherwise would continue
;		it, and to just return when not. That way a routine called
;		via an irp$l_pid intercept can just execute, and the I/O
;		restart processing (normal in places like REQCOM) will be
;		done if the call came from something like REQCOM, but not
;		if it came from something like COM$POST.
;		Also arrange to check kernel stack in ioc$initiate
;		to see if it is above an "alert zone" (2 pagelets
;		has been determined experimentally to be enough). If the
;		kernel stack is in the alert zone, fork to clear it rather
;		than allowing recursive calls to overflow it.
;
;	X-55	Andy Kuehnel				18-Mar-1997
;		- IOC_STD$CVT_DEVNAM:
;		  X-47, X-53, X-54 still overlooked the case where we are not
;		  in a cluster but have a port alloc class set anyway.  Even if
;		  just the device name is requested, we must still display the
;		  PAC in this case.
;		  Every rule needs an exception: we do _not_ want to display
;		  the PAC for port devices.  All the other P* devices do not
;		  set the NNM flag in their DDB.  Unfortunately, SCSI port
;		  drivers are still setting the bit.  We are (temporarily?)
;		  ignoring this flag if the device is a PKx device.
;		  *** look for   /PK/    to locate the hack ***
;		  The real fix for this problem will be to change the PKx
;		  drivers to no longer set the NNM flag.  This fix is planned
;		  for Raven.
;		- SEARCH_INT:
;		  Permanently fix problems as reported in QAR 733, EVMS-RAVEN.
;		  This complex problem and the permanent solution is documented
;		  in STAR::DOCD$:[EVMS-PROJECT_DOCUMENTS]*SCSI-PAC-NAMING.*
;		  In short, the solution is to remove the PROZAC, MBTA, FULLSB
;		  scans.
;		  The new model says: if the device has a PAC, the only way to
;		  find it is if the PAC has been specified.  A device name of
;		  type node$ddcu or just ddcu can never match a device with
;		  a port allocation class greater than 0.
;		  Exception: if it's a PKx device (see above), we simply
;		  ignore the PAC.  This is necessary so that IOGEN can find the
;		  port.
;		  *** look for   /PK/    to locate the hack ***
;		- Remove PAC related conditional assembly instructions; the
;		  .SDL change was checked in a few months ago.
;
;	X-54	JCH710d	John C. Hallyburton, Jr.	26-Sep-1996
;		Last total lunar eclipse of the century tonight.
;		A problem with X-53 is that UCB$L_CDDB doesn't exist
;		for all UCBs. Check the MSCP bit before going after
;		the CDDB. This works because (a) the MSCP bit set implies
;		the CDDB pointer is valid and (b) we've just checked the
;		PAC bit in the DDB which is valid for local devices, so
;		at this point we only care about served devices anyway.
;
;	X-53	JCH710c	John C. Hallyburton, Jr.	 3-Sep-1996
;		Edit X-47 was too enthusiastic. Should only fold 
;		allocation classes for devices with nonzero 
;		port allocation classes. Need to infer this case
;		from DDB$L_ALLOCLS != CDDB$L_ALLOCLS in case of served device.
;
;		Add a FAST PATH line for DanO' prior to his departure.
;
;	X-52	ACG0621		Andrew C. Goldstein,	15-May-1996  18:49
;		Try again on ACG0618: we want to return DEVALLOC when the
;		device is allocated to another user (mounted or not), but
;		return DEVMOUNT if the device is mounted and allocated to
;		our own process (or a parent if applicable).
;
;	X-51	JCH710b	John C. Hallyburton, Jr.		29-Mar-1996
;		STAR:: DOCD$:[EVMS.PROJECT_DOCUMENTS]FS-SCSI-NAMING.PS
;		Make SEARCH_INT work as ff:
;
;			NODE$DKA100 - Search NODE's SB for a DKA100 with
;				      an allocls 0 if one exists, else with
;				      any allocls. May require two passes
;				      over the DDBs chained off the SB.
;
;			DKA100	    - Only search local SB for DKA100 on
;				      port "A", regardless of allocls.
;				      With new naming you could have several
;				      DKA100s on the same node.
;
;		To make this work, invent new search flags as ff:
;
;		IOC$x_FULLSB =  When searching, search the entire SB, not just
;				the first suitable DDB. Required since a node
;				can now have multiple DKA devices each with a
;				different allocation class.
;
;		IOC$x_PROZAC =  Prefer Only Zero Allocation Class. Used only
;				with FULLSB. Means search the DDB chain looking
;				for a DDB with allocation class 0 and if you
;				don't find one, search again looking for any
;				otherwise suitable DDB. Used for NODE$ddcu
;
;		IOC$x_MBTA   =	Must Be True "A". Used only for local system
;				If SEARCH finds a DDB with the right device
;				name, double-check that either (a) the PAC bit 
;				is clear or (b) if PAC is set, the port ID is 
;				ASCII "A" (e.g., DKA100) -or- the allocls is 0.
;				Note MBTA is set on DDcu: syntax. Caller might
;				be asking for, say, DKC200 and MBTA would still
;				be set.
;
;		Update copyright year. Clean up some %AMAC-I- warnings.
;
;	X-50	GCE		Glenn C. Everhart		 7-Sep-1995
;		Make small change in fast_finish macro to allow privileged
;		code to request completion via kernel routine. If IRP$L_PID
;		is negative AND if irp$m_FINIPL8 bit is set in IRP$L_STS
;		the routine in IRP$L_PID is called with arg of the IRP
;		address (in R5 too)(note: this is guaranteed to be in 32 bit
;		addresses) and a RET is done upon return. It is expected that
;		the user routine will clear the IRP$M_FINIPL8 bit, restore the
;		original value to IRP$L_PID, and arrange to issue another
;		REQCOM or equivalent to actually complete the I/O, possibly
;		after fiddling with the IRP. The reason the FINIPL8 bit is
;		reused is that fastio is a DISK service and it would generally
;		never be set where the FASTIO bit is also set. This means
;		that if (third party) privileged code sets the bit to regain
;		control after an I/O and subsequently clears it before anything
;		else sees the bit, it can be used harmlessly. This avoids too
;		the burning of another IRP status bit. The reason this can
;		be important is that some third party code (e.g. VDDRIVER)
;		uses the IRP$L_PID technique to "steal" basically ALL I/O
;		done through it. With the advent of this additional hook, it
;		becomes possible for such drivers to have their "special"
;		I/O completions done at IPL 8, rather than having to await
;		an extra IPL 4 interrupt for each IRP (or even more than once
;		per IRP in the case of split I/Os!) Thus the cost of using
;		this path is minimized. In fact, by setting the fastio bit
;		"by hand" a third party driver could conceivably use this path
;		to avoid the IPL 4 path even for IRPs that originated
;		with $qio for its own processing, thus being hit with IPL 4
;		processing only when the real REQCOM was done.
;
;	X-49	ACG0618		Andrew C. Goldstein,	28-Mar-1996  14:09
;		Fix reference to UCB$W_REFC in previous edit (porting
;		error from VAX)
;
;	X-48	ACG0618		Andrew C. Goldstein,	12-Mar-1996  9:56
;		Side effects of MOUNT redesign: in IOC$TESTUNIT add
;		IOC$V_NOLOCK flag; reverse order of DEVALLOC and
;		DEVMOUNT tests.
;
;	X-47	JCH710a	John C. Hallyburton, Jr.	29-Feb-1996
;		If new naming, don't allow IOC$CVT_DEVNAM to fold multiple
;		names to node$ddcu: format. Also change "DISPLAY_DEVNAM" to
;		return port name for SCSI devices with nonzero port allocls.
;
;	X-46	JCH710	John C. Hallyburton, Jr.	 2-Feb-1996
;		Change a couple references to DDB$L_ALLOCLS to use longword
;		addressing. Eventually we may change GETNUMBER to allow
;		allocation class numbers up to 99999, should the need arise, 
;		so we'll go to full longword arithmetic instead of word size.
;		Note: 99999 is the maximum possible, given restrictions on
;		length of device names.
;
;	X-45	MSH2081		Michael S. Harvey	 2-Jan-1996
;		Use lighter weight mechanism for synchronizing an attempt to
;		freeze something in the working set against a parallel attempt
;		to remove something from the working set.
;
;	X-44	JCH710	John C. Hallyburton, Jr.	22-Dec-1995
;		Add IOC$V_PAC (passed by IOGEN) to IOC$SEARCH routines.
;
;	X-43	NYKxxx		Nitin Y. Karkhanis	19-Dec-1995
;		Make EXE$MATCH_NAME 64-bit capable.
;
;	X-42	MSH2075		Michael S. Harvey	12-Oct-1995
;		Streamline use of "keep in WS" mechanism, especially so for
;		non-multithreaded processes.
;
;	X-41	PJH		Paul J. Houlihan	26-Jun-1995
;		Add call to pending I/O routine within initiate. Also no need
;		to preserve R2 and R4 on call to start I/O routine.
;
;	X-40	JRK369		Jim Kauffman		26-Jun-1995
;		Acquire primary capability with new mechanisms to prevent
;		affinity process hangs
;
;	X-39	PJH		Paul J. Houlihan	26-May-1995
;		- Have even port CPU I/O queue to the IOQFL if UCB is queued
;		somewhere. This is needed to preserve Start I/O ordering.
;		- Use interlocked resident queue instructions.
;
;	X-38	JCH703b	John C. Hallyburton, Jr.	25-Apr-1995
;		Don't do fast finish if negative PID. Somebody may
;		actually depend on IOPOST dispatching to happen at 4;
;		fast-finish does it at 8.
;
;	X-37	JRK369		Jim Kauffman		27-Mar-1995
;		Add support for extended capabilities
;
;	X-36	DED		Denise E. Dumas		14-Mar-1995
;		Now really add IOC_STD$REQCOM_LOCAL.
;
;	X-35	DED		Denise E. Dumas		9-Mar-1995
;		Add Fast Path support: new routines IOC$INITIATE_PORT_CPU, 
;		IOC_STD$INITIATE_NEW_IO, IOC_STD$REQCOM_LOCAL. 
;		Modify the INITIATE path significantly.
;
;	X-34	JCH703a	John C. Hallyburton, Jr.	 3-Mar-1995
;		Don't do Fast-IO fast-finish on bad status; leave that 
;		to IPL4 to decode. Also add fast-finish to ALTREQCOM.
;
;	X-33	EMB0345		Ellen M. Batbouta	09-Feb-1995
;       	Kernel thread data structure changes:  Change the reference
;		from PCB$L_CAPABILITY to KTB$L_CAPABILITY and pass the KTB
;		address to the routines, SCH$REQUIRE_CAPABILITY/SCH$RELEASE_CAPABILITY.
;
;	X-32	EMB0334		Ellen M. Batbouta	06-Jan-1995
;		Eliminate the use of the flag, PHD$V_NO_WS_CHNG.  Use the
;		PCB fields, PCB$Q_KEEP_IN_WS/PCB$Q_KEEP_IN_WS2, to keep a
;		range of pages in the working set.
;
;	X-31	JCH703	John C. Hallyburton, Jr.	19-Oct-1994
;		Fast-IO edits: PMS call and IOC$REQCOM
;
;	X-30	JFD0665		James F. Dunham		30-SEP-1994
;		During IOC$REQCOM processing, if Mount Verififcation is in
;		progress and Mount Verfication is pending, then look at the
;		head of the UCB I/O Queue to determine if there is a MVIRP
;		already (re-)queued, prior to invoking CALL_MOUNT_VER to start one.
;
;       X-29    WDB:B001        Walter D. Blaschuk, Jr. 03-Oct-1993
;               Blade Parity:  From (VAX/VMS) V5.5-2 (11A1 in Blade)
;               no merges to OpenVMS AXP that are required:
;
;		X-29  Done by Tanner with X-27 12-Aug-1993.
;		X-28  Not apllicable in AXP.
;		X-27  C2 done by Davidson with X-24 on 7-Jul-1993
;		X-26  Not apllicable in AXP.
;		X-25  Not apllicable in AXP.
;		X-24  Not apllicable in AXP.
;		X-23  Done by Szubowicz with X-15 on 2-Dec-1992
;		X-22  Not apllicable in AXP.
;		X-21  Done by Bishop with X-14 on 14-Jul-1992
;		X-20  Not apllicable in AXP.
;		X-19  Not apllicable in AXP.
;		X-18  Not apllicable in AXP.
;		X-17  Not apllicable in AXP.
;		X-16  C2 done by Davidson with X-24 on 7-Jul-1993
;		X-15  C2 done by Davidson with X-24 on 7-Jul-1993
;		X-14  Not apllicable in AXP.
;		X-13  Done by Critz with X-25 on 8-Jul-93.
;		X-12  Not apllicable in AXP.
;
;	X-28	LSS0283		Leonard S. Szubowicz	 20-Aug-1993
;		HLLDD: Use CALL_x macros instead of soon to be obsolete $x.
;
;	X-27	NT036		Nora Tanner		12-Aug-1993
;		Merge in Coral change:
;		VAX to AXP merge *** X-29 only
;		X-29	ACG0595		Andrew C. Goldstein,	23-Jul-1993  17:51
;		Allow mounting of devices allocated to parent process
;
;	X-26	JFD0132		James F. Dunham		20-JUL-1993
;		Restore line dropped in IOC$TESTUNIT during Blade merge X-24 
;
;	X-25	RWC129		Richard W. Critz, Jr.	 8-Jul-1993
;		Add support for DDR to IOC$SEARCHINT and IOC$TESTUNIT.
;		
;	X-24	SAD0282		Stuart A. Davidson	 7-JUL-1993
;		Merge Blade security code:
;
;		X-26	FAK007		Forrest A. Kenney	29-Dec-1992
;		Add code to IOC$TESTUNIT to honor UCB$V_NO_ASSIGN bit.  Setting
;		this will essentially make the device inaccessible to consumers
;		of IOC$SEARCH. 
;
;		X-16	FAK005		Forrest A. Kenney	15-January-1992
;		Add code to IOC$LAST_CHAN to restore the UCB$L_ORB field to the
;		original value.  Certain devices have the ORB switched to a new
;		one when a volume is mounted, or when a login occurs on the
;		device.  
;
;		T-2	DDP0847		Derrell D. Piper       18-SEP-1991  15:49
;		Call EXE$CHECK_ALLOC_ACCESS instead of EXE$CHKRDACCES in
;		IOC$TESTUNIT to determine device accessibility for 
;		allocation.
;		
;		T-1	DDP0829		Derrell D. Piper       21-MAR-1991  17:24
;		Privilege auditing.
;
;	X-23	LSS0276		Leonard S. Szubowicz	 25-Jun-1993
;		In routines IOC_STD$PRIMITIVE_WFIxxCH, update the commentary
;		to reflect that UCB$PS_TOUTROUT is now an implicit input that
;		contains the interrupt timeout routine procedure value.
;		In routines IOC$PRIMITIVE_WFIxxCH, copy the UCB$L_FPC procedure
;		value into UCB$PS_TOUTROUT to keep the behavior of these
;		routines identical for the benefit of any direct callers.
;
;	X-22	LSS0276		Leonard S. Szubowicz	 21-Jun-1993
;		Add support for callable driver fork routines as described in
;		EVMS$IO_CMS:HLLDD-FORK.MEM.  This includes new standard call
;		entry routines IOC_STD$PRIMITIVE_REQPHANx, IOC_STD$RELCHAN,
;		and IOC_STD$PRIMITIVE_WFIxxCH.
;
;       X-21    WDB:HLL48       Walter D. Blaschuk, Jr. 25-Mar-1993
;               HLLDD Project: JtoC Jackets.
;               EVMS$IO_CMS:[IO.CMS]J2C_JACKETS.
;               Create several IOC_STD$* routines with standard call
;               interfaces from  IOC$* routines with JSB interfaces.
;               Also, replace the "JSB" to several routines with the
;               standard "CALLS" or use the  MACRO to push the input
;               and/or output parameters and do the standard "CALLS".
;
;	X-20	JFD0155		James F. Dunham		 1-MAR-1993
;		Fold X-15U3 below from Delta to Epsilon
;
;	X-19	JFD0150		James F. Dunham		24-FEB-1993
;		Fold X-15U2 below from Delta to Epsilon
;
;	X-18	JFD0134		James F. Dunham		 4-FEB-1993
;		Fold X-15U1 below from Delta to Epsilon
;
;	X-17	WDA    		W.D. Arbo           	28-Jan-1993
;               Change JSB to inbound driver routines to CALLS in the 
;               following routines:  IOC$LAST_CHAN, IOC$CTRL_INIT, 
;               IOC$DIAGBUFILL, IOC$INITIAITE, IOC$UNIT_DELIVER,
;               IOC$UNIT_INIT.  This module now supports Step 2 inbound
;		driver routines.  The check for zero as the routine
;		address was removed from IOC$UNIT_INIT, IOC$CTRL_INIT
;		and IOC$DELIVER, since the DPTAB and DDTAB macros never
;		leave this field uninitialized.
;               
;	X-16	BAP113		Bridget Powers		25-Jan-1992
;		Use the new parameters of the UNIVERSAL_JSB routine to cause
;		a jacket routine for the JSB routine to be automatically
;		created.  See EVMS$IO_DOC:C2JIMPLEMT.DOC or C2JIMPLEMT.PS for
;		more details about the new parameters.
;
;	X-15U3	JFD0154		James F. Dunham		 1-MAR-1993
;		In the routine IOC$SEARCHINT, remove access to CTL$GL_PCB,
;		and its input (R4) as an argument to IOC$TESTUNIT. In
;		IOC$TESTUNIT access the current PCB only if needs.
;		This change revokes out X-15U2
;
;	X-15U2	JFD0149		James F. Dunham		19-FEB-1993
;		In the routine IOC$SEARCHINT, skip picking up the
;		current PCB, prior to completion of EXEC_INIT.
;
;	X-15U1	JFD0134		James F. Dunham		 4-FEB-1993
;		Correct access offset to UCB$L_MEDIA_ID, such that the DCL
;		command $ ALLOCATE/GENERIC <generic-device-name> works.
;		This change rollscback the change made by X-7.
;
;	X-15	LSS0260		Leonard S. Szubowicz	 2-Dec-1992
;		In routine IOC$LAST_CHAN, if R2 contains a valid channel 
;		number, assure that the CCB is locked in memory before calling
;		the driver cancel routine.  Also, in routine IOC$CVT_DEVNAM, 
;		correct input register list to include R5.
;
;	X-14	RAB		Richard A. Bishop	20-Jul-1992
;		GEN retirement - remove references to ENSDEF & EPBDEF, 
;		plus routines IOC$DCLSYSEVT & IOC$LOG_EVENT.
;
;	X-13	JRK361		Jim Kauffman		12-May-1992
;		Make references to UCB$L_QLEN cells atomic for both
;		uniprocessor and SMP systems
;
;	X-12	PJH		Paul J. Houlihan	16-Mar-1992
;		Change interface to SCAN_IODB_2P to save as scan context,
;		info on whether primary or secondary path queue is being
;		processed. With new Alpha local device naming we can no
;		no longer depend on a separate DDB existing for both
;		secondary and primary paths. For example, a disk dual-ported
;		between two local KDMs share the same DDB.
;
;	X-13	LSS0245		Leonard S. Szubowicz	10-Mar-1992
;		Merge in VAX/VMS V5.4-3 bug fix:
;
;		X-54U1	RLRINIAFF	Robert L. Rappaport	19-Mar-1991
;
;		In IOC$INITIATE, a problem may arise when the current I/O
;		request cannot proceed on the current CPU, and this I/O
;		request happens to be for an MSCP disk or tape device.  The
;		problem occurs because of the different way the drivers
;		for these devices use the UCB$V_BSY bit and the UCB$L_IOQFL.
;		In short these drivers 	require that the setting on of the
;		UCB$V_BSY bit, and the arrival into the driver's START_IO
;		routine be performed as an atomic action.  In order to
;		preserve the atomicity of this action, a fix has been
;		introduced into IOC$INITIATE that effectively causes the
;		UCB$V_BSY bit to be turned on once we are exectuting
;		on the CPU for which we have affinity.
;
;	X-12	LSS0242		Leonard S. Szubowicz	24-Feb-1992
;		Remove routine IOC$RETURN.  This routine is now entirely
;		contained in SYS.EXE by connecting the IOC$RETURN procedure
;		descriptor to SYS\SYSTEM_ROUTINES\EXE_RSB.
;
;	X-11	DEE0150		David E. Eiche		17-Jan-1992
;		Suppress RUNTIMSTK diagnostics in PUTNUM routine.
;
;	X-10			Susan Lewis		17-Jan-1992
;		Promote EMB ERRCNT, ERTCNT and ERTMAX fields to longwords
;
;	X-9	BJT291		Benjamin J. Thomas III	 9-Jan-1992
;		Promote UCB ERRCNT, ERTCNT and ERTMAX fields to longwords
;
;	X-8	SML		Sue Lewis
;		Change emb$w_dv_sts to a longword.
;
;	X-7	JTK		Jim Klumpp		05-Dec-1991
;		Change ADP$W_ADPTYPE and _TR to longwords.
;
;	X-6	BJT278		Benjamin J. Thomas III	21-Nov-1991
;		Promote CHAN
;
;	X-5	WDB:A701	Walter D. Blaschuk, Jr.	 20-Nov-1991
;               Put hack in for UCB$L_DDT initialization.  Remove when
;		IOGEN_CONNECT is fixed.
;
;	X-4	RWC051		Richard W. Critz, Jr.	19-Nov-1991
;		CRB field promotions.
;		
;	X-3	BJT265		Benjamin J. Thomas III	11-Nov-1991
;		More UCB, IRP promotions
;
;	X-2	BJT262		Benjamin J. Thomas III	 1-Nov-1991
;		Make architecture specific
;		Remove VAX code
;
;---------- File made architecture specific - edit numbers are reset ---------
;		
;	X-12	WDB:A700	Walter D. Blaschuk, Jr.	 23-Oct-1991
;		Added  the ordering of calls to driver initialization 
;		for units  and controllers.   The code was written in 
;		the INIT_DEVICE_PWRUP routine for the general purpose 
;		INIT_IO_DB.
;
;	X-11	LSS0230		Leonard S. Szubowicz	17-Oct-1991
;		In routine IOC$INITIATE, use the IRP or TWP address in R3 as
;		the fork block for smp$cpu_switch::SMP$CPU_SWITCH.  This corrects a problem
;		introduced by edit X-41K2.
;
;	X-10	LSS0247		Benjamin J. Thomas III	27-Sep-1991
;		Promote IRP$W_STS to IRP$L_STS
;		Promote UCB$W_STS to UCB$L_STS
;
;	X-9	LSS0223		Leonard S. Szubowicz	30-Aug-1991
;		Expand UCB$W_REFC to a longword to avoid word granularity
;		problems within the same quadword.
;
;	X-8	LSS0220		Leonard S. Szubowicz	15-Jul-1991
;		Move alpha-specific routine EXE$INIT_DEVICE_PWRUP here from 
;		module POWERFAIL.MAR which is now obsolete for Alpha.
;
;	X-7	CEG		Clair Grant		 5-Jun-1991
;		Fix CMPZV operand
;
;	X-6	ROW0745		Ralph O. Weber		17-APR-1991 12:59
;		Eliminate dependence on IRP SVAPTE/BOFF/BCNT field ordering.
;
;	---------- Ident numbering change due to master pack reorg ---------
;
;	X-4K9	GHJ036		Gregory H. Jordan	 5-Mar-1991
;		EXE$MOUNTVER has an interface and name change, the
;		routine is now called EXE$MOUNT_VER.
;
;	X-41K8	LSS0201		Leonard S. Szubowicz	21-Feb-1991
;		Add some assumes to assure that the negative CDRP$L_IOQFL
;		offset reaches back exactly to the start of the IRP.
;
;	X-41K7	LSS0197		Leonard S. Szubowicz	13-Feb-1991
;		Incorporate changes suggested by code review and replace call
;		to SMP$GET_CURPCB.
;
;	X-41K6	LSS0168		Leonard S. Szubowicz	30-Nov-1990
;		Fork block FR3 and FR4 have each been expanded to a quadword
;		to allow the preservation of their full 64-bit values on EVAX.
;		Use MOVX macro to copy these quantities in an architecture
;		independent fashion.
;
;	X-41K5	LSS0181		Leonard S. Szubowicz	29-Oct-1990
;		Put misplaced reference to CRB$L_LINK in IOC$RELCHAN inside
;		VAX-only code section.  Secondary channels do not exist on EVAX.
;		Also use IDB$PS_OWNER and VEC$PS_ADP as replacements for their
;		obsolete L tagged names.
;
;	X-41K4	LSS0180		Leonard S. Szubowicz	26-Oct-1990
;		The $OPDEF macro is defined on VAX only and is only required
;		when compiling the VAX version of this module.
;
;	X-41K2	LSS0175		Leonard S. Szubowicz	17-Oct-1990
;		Initial changes for EVMS: addition of JSB_ENTRY declarations;
;		changes to support simple fork model on EVAX; all mapping 
;		registers routines are now VAX-specific; and miscellaneous
;		EVMS related changes.
;
;	X-41	PRD0529		Paul R. DeStefano	12-Oct-1989
;		Remove reference to GEN work queue cell.  Changed
;		IOC$LOST_EVENT to IOC$LOG_EVENT.
;
;	X-40	PRD0488		Paul R. DeStefano	11-Sep-1989
;		Added Generalized Event Notification routines
;		IOC$DCLSYSEVT and IOC$LOST_EVENT.
;		Get module idents back in sync.  Yes, this is X-40.
;
;	X-40	EMB0426		Ellen M. Batbouta	27-Jul-1989
;		Change CPB$V_PRIMARY to CPB$M_PRIMARY in IOC$LAST_CHAN.
;		SCH$REQUIRE_CAPABILITY and SCH$RELEASE_CAPABILITY
;		want a capability mask as input.
;
;	X-38	EMB0368		Ellen M. Batbouta	16-Nov-1988
;	X-39	CWH5136U2	CW Hobbs		3-Dec-1988
;		Change the DISPLAY_DEVNAM option to IOC$CVT_DEVNAM
;		to respect file-oriented devices, and return a more
;		meaningful string for non-FOD devices (i.e. no more
;		"$254$VTA32: (ATHENS)" for terminals).
;
;	X-37	RJB0236		Richard J. Bouchard Jr.	22-Nov-1988
;		Change IOC$SCAN_IODB_USRCTX to work properly with
;		unordered UCB chains.  It now uses R8-R11 for context,
;		returns devices sorted by unit number, and should
;		no longer be used together with IOC$SCAN_IODB.
;		In IOC$REQCOM, IOC$POST_IRP, and IOC$ALTREQCOM, modify
;		the usage of SMP$GL_PRIMID to keep a consistent view of
;		whose primary in the event of a primary switch occurring
;		while executing one of these routines.
;
;	X-37	RJB0161		Richard J. Bouchard Jr.	12-Sep-1988
;		Add IOC$SCAN_IODB_USRCTX to scan the I/O database with
;		a user-supplied, and thus untrustable, context block
;		which consists of a DDB address and a unit number.
;		This routine was initially written for use by the
;		$DEVICE_SCAN service.
;
;		Added EXE$MATCH_NAME to be string wildcard matching.
;		This is the same routine as FMG$MATCH_NAME, but has
;		been made part of the exec with a standard vector.
;		Eventually modules using FMG$MATCH_NAME will be changed
;		to use this routine.
;
;	X-36	WMC0036		Wayne Cardoza		08-Sep-1988
;		Don't use PRIMID to determine affinity.
;
;	X-35	EMB0336	  	Ellen M. Batbouta	17-Aug-1988
;		Before calling a device driver's cancel routine in 
;		IOC$LAST_CHAN, check for affinity.  If affinity is
;		required and the process does not have it, then acquire
;		it.  Also release the primary capability after returning
;		from the driver. 
;
;	X-34	FAK0002		Forrest A. Kenney	20-Jul-1988
;		Add missing RSB in IOC$CONBRDCST, if console rejects
;		broadcast just invalidate TWP and return.
;
;	X-33	PRD0451		Paul R. DeStefano	14-Jun-1988
;		Call EXE$MNTVER_GEN_CRC on SS$_DATAOVERUN error.
;
;	X-32	RNG5032		Rod Gamache		31-Mar-1988
;		Use IOC$GQ_POSTIQ for post processing.
;
;	X-31	FAK0001		Forrest A. Kenney	04-Feb-1988
;		IN IOC$BROADCAST and IOC$CONBRDCST to fork before calling
;		EXE$ALTQUEPKT.
;
;	X-30	MAS0161		Mark A. Stiles		15-Aug-1987
;		SHD0028		Scott H. Davis		17-Aug-1987
;		A couple things for improving the life of servers and
;		mount verification:
;		o Add IOC$POST_IRP within the IOC$ALTREQCOM code, to aid
;		  in disposing of IRPs during mount verification.
;		o Modify IOC$MNTVER to use IOC$POST_IRP for IRPs with
;		  IRP$V_SRVIO set, rather than requeing them for retry.
;		  Control is still returned to mount verification.
;		o Make a minor performance optimization to ALTREQCOM
;		  processing, which removes a BSB/RSB pair on every successful
;		  class driver I/O completion if performance monitoring
;		  is disabled (ie, usually).
;
;	X-29	RLRMAPREG	Robert L. Rappaport	11-Aug-1987
;		Add optional debugging path to IOC$DALOCUBAMAP to test
;		registers being deallocated and to see if they are already
;		available.  This addition is placed out of line and is
;		activated by patching.  This means that we incur no runtime
;		cost (except for the few bytes that hold the code) for this
;		debugging code.
;
;	X-28	RNG5028		Rod Gamache		29-Jul-1987
;		Fix bug in IOC$INITIATE where on bad affinity cases it
;		wrongly clears the BSY bit, which doesn't get set again.
;
;	X-27	WCT0076		Ward C. Travis		29-Apr-1987
;		Change old  occurrences of  UCB$B_ODIPL and  SMP$C_
;		to UCB$B_DIPL and SPL$C_, respectively.
;
;	X-26	RNG5026		Rod Gamache		23-Apr-1987
;		Add support for unmodified device drivers - check
;		UCB$B_FLCK for FIPL vs. FLCK.
;
;	X-25	MAS0034		Mary A. Sullivan	 17-Feb-1987
;		Fix bug in IOC$RELMAPREG.
;		
;	X-24	HH0248		Hai Huang		09-Feb-1987
;		Reference local sb thru SCS$AR_LOCALSB.
;
;	X-23	WMC0023		Wayne Cardoza		28-Jan-1987
;		Use pointers to IO data structures.
;
;	X-22	MAS0021		Mary A. Sullivan	 14-Jan-1987
;		Remove support for extended mapping registers from
;		IOC$RELMAPREG and IOC$DALOCUBAMAP.  Separate routines
;		(in [SYSLOA]MAPSUB) will be used.
;		
;	X-21	WMC0021		Wayne Cardoza		18-Dec-1986
;		Add  a G^.
;
;	X-20	CWH5020		CW Hobbs		16-Dec-1986
;		Fix an extra RSB in IOC$CVT_DEVNAM....
;
;	X-19	WMC0019		Wayne  Cardoza		16-Dec-1986
;		Change some JSBs to BSBWs.
;
;	X-18	MAS0017		Mary A. Sullivan	12-DEC-1986
;		Change IOC$RELMAPREG to return status in R0
;
;	X-17	MAS0015		Mary A. Sullivan	11-DEC-1986
;		Change MR2FREGAR, MR2NREGAR to MRFREGARY, MRNREGARY
;		in IOC$RELMAPREG
;
;	X-16	ACG79234	Andrew C. Goldstein,	9-Dec-1986  20:42
;		Allow allocation of devices already allocated to oneself
;
;	X-15	CWH5015		CW Hobbs		8-Dec-1986
;		Add code to IOC$CVT_DEVNAM to produce displayable
;		name.
;
;	X-12	MAS0010		Mary A. Sullivan	 4-DEC-1986
;		Modify IOC$RELMAPREG and IOC$DALOCUBAMAP to support
;		extended map registers.
;
;	X-11	PRD0266		Paul R. DeStefano	19-Nov-1986
;		Modify IOC$REQCOM and IOC$ALTREQCOM to support
;		Tape Mount Verification.
;
;	X-10	JTK0011		Jim Klumpp		4-Nov-1986
;		Replace CPUDISP macro with ADPDISP.
;
;	X-9	RNG0009		Rod Gamache		10-Jul-1986
;		Fix insertion of UCB into CRB wait queue.
;
;	X-8	SJF		Stu Farnham		1-Jul-1986
;		Use per-CPU IOPOST queue
;
;	X-7	SJF		Stu Farnham		30-Jun-1986
;		Resolve merge conflicts.
;
;	X-4	WMC0002		Wayne Cardoza		18-Jun-986
;		Initialization routines should return status.
;
;	X-3D2	WMC0001		Wayne Cardoza		15-Mar-1986
;		Remove SPTE allocation routine.
;
;	X-1D1	SF04001		Stephen Fiorelli	10-Dec-1985
;		Resolve conflicts from initial merge of exec reorg
;		thread and mainline (4.4 BL7).
;
;	X-1C7	TCM0003		Trudy C. Matthews	26-Nov-1985
;		Add an initialization routine that initializes the IOC$RETURN
;		vector with an RSB.
;
;	X-1C5	TCM0002		Trudy C. Matthews	4-Oct-1985
;		Make comparison of unit init routine address against
;		IOC$RETURN work again. (routine IOC$UNITINIT)
;
;	X-1C4	TCM0001		Trudy C. Matthews	23-Aug-1985
;		Remove conflicts introduced by merge of exec_reorg version
;		with mainline 4.4 version.
;
;	X-3	ACG72968	Andrew C. Goldstein,	31-Jul-1985  14:53
;		Allow explicit allocation of an offline device
;
;	V04-003 EMB0129		Ellen M. Batbouta	21-Mar-1985
;		This is a patch to V4.2 Eco 23.  Remove non-fatal
;		bugcheck which occurs when attempting to deallocate
;		zero map registers in IOC$DALOCUBAMAP.
; 
;	V04-002	WHM0001		Bill Matthews		07-Mar-1985
;		Fix IOC$UNITINIT and IOC$CTRLINIT so that the driver's unit
;		init routine is not called by IOC$UNITINIT if the
;		EXE$TEST_CSR returned failure in IOC$CTRLINIT. The patch is
;		ECO 18 and it has been sent to customers.
;
;	V04-001	ACG73026	Andrew C. Goldstein,	15-Feb-1985  13:35
;		Fix generic device search across multiple controllers
;		for the same device type
;

	.PAGE
;
;
; MACRO LIBRARY CALLS
;
 
	$ADPDEF				;DEFINE ADP OFFSETS
	$CADEF				;DEFINE CONDITIONAL ASSEMBLY PARAMETERS
	$CANDEF				;DEFINE CANCEL I/O REASON CODES
	$CCBDEF				;define CCB offsets
	$CDDBDEF			;DEFINE CDDB OFFSETS
	$CDRPDEF			;DEFINE CLASS DRIVER I/O REQUEST PACKET
	$CPBDEF				;DEFINE CPU CAPABILITIES
	$CPUDEF				;DEFINE PER-CPU DATA BLOCK OFFSETS
	$CRBDEF				;DEFINE CRB OFFSETS
	$DCDEF				;DEFINE DEVICE CLASSES
	$DDBDEF				;DEFINE DDB OFFSETS
	$DDTDEF				;DEFINE DDT OFFSETS
 	$DEVDEF				;DEFINE DEVICE CHARACTERISTICS FLAGS
	$DPTDEF				;DEFINE THE DEVICE PROLOGUE TABLE
	$DTNDEF				;define DTN offsets
	$DYNDEF				;DEFINE DYNAMIC POOL BLOCK TYPES
	$EMBDEF				;DEFINE EMB OFFSETS
	$FKBDEF				;DEFINE FORK BLOCK
	$IDBDEF				;DEFINE IDB OFFSETS
	$IOCDEF				;DEFINE IOC$SEARCHxxx FLAGS
	$IPLDEF				;DEFINE INTERRUPT PRIORITY LEVELS
	$IRPDEF				;DEFINE IRP OFFSETS
	.IF	NDF,IRP$M_FINIPL8
IRP$M_FINIPL8 = ^X8000000 ;temporary def of new bit
IRP$V_FINIPL8 = 27
	.ENDC
	$JIBDEF				;DEFINE JIB OFFSETS
	$LCKDEF				;DEFINE LOCK MANAGER SYMBOLS
	$MSCPDEF			;DEFINE MSCP STRUCTURES
	$NSADEF				;DEFINE PRIVILEGE AUDITING ITEM CODES
	$ORBDEF				;DEFINE OBJECT RIGHTS BLOCK OFFSETS
	$PCBDEF				;DEFINE PCB OFFSETS
	$PDTDEF				;Define PDT offsets
	$PHDDEF				;define PHD offsets
	$PRDEF				;DEFINE PROCESSOR REGISTERS
	$PRVDEF				;DEFINE PRIVILEGE BITS
	$SBDEF				; Define system block offsets
	$SPDTDEF			; SCSI PDT definitions
	$SPLCODDEF			;DEFINE SPINLOCK INDICES
	$SSDEF				;DEFINE SYSTEM STATUS CODES
	$TTYDEF				;DEFINE TERMINAL WRITE PACKET OFFSETS
	$UBMDDEF			;Define UNIBUS Map Descriptor structure
	$UCBDEF				;DEFINE UCB OFFSETS
	$VECDEF				;DEFINE CRB VECTOR OFFSETS



	.SBTTL	The FAST_FINISH macro

; Used in IOC$REQCOM, IOC$ALTREQCOM
; 
; Inputs: 
;   DONE: Where to branch to when done setting up for Fast-IO finish
;   DONT: Where to branch to when unable to do a Fast-IO finish
;
; Implicit inputs:
;   R3: IRP
;   ARG$_IOSTATUS1(AP): Final I/O status
;   TYPE: Either IRP (causes error check) or anything else
;   NOHOOK: Internal branch for no 3rd party hook...just a label
;   RESUME: IF blank, and if a user routine is called, just return
;	but if not blank, then go to DONE after calling the user
;	routine. Needed to allow THIS code to distinguish REQCOM
;	type calls from COM$POST type calls (where the former must
;	start the next I/O where the latter need not). This means
;	that the called code need not figure out by which mechanism
;	it was called, which was infeasible.
;
;
; Outputs: None
;
; Implicit Outputs:
;
;   Causes I/O completion at fork level instead of via IPL4/IPL2, if DONE
;   If DONT, expects to take standard IPL4/IPL2 completion route
;
; Registers: Uses R0,R1

	.MACRO	FAST_FINISH,TYPE,DONE,DONT,?NOHOOK,RESUME

	.BRANCH_UNLIKELY
	BBS	#IRP$V_COMPLX,-		; For right now, don't do complex
		IRP$L_STS(R3),DONT	;  buffers; complicates completion
	.BRANCH_UNLIKELY
.IIF IDN,TYPE,IRP, BLBC	ARG$_IOSTATUS1(AP),DONT ; Br if error (and not FASTPATH)

;
; Special 3rd party hook. Fast finish is for disks only, but if the app
; wants to enable fast finish AND gain access to the completion itself,
; allow this to happen by checking the IRP$V_FINIPL8 bit. This bit would
; normally never be set, and we can require that it be cleared before
; a normal completion is done on the IRP, but it allows an app like
; vddriver (virtual disks) to regain control for a FAST finish in the
; proper context, and does not burn an extra IRP status bit for the
; purpose. Since this is an exceptional thing to do, burning a status bit
; to do it seems wasteful. Note that if the FINIPL8 bit is clear, we
; leave completion alone. Note too that since this is a new interface, it
; is perfectly permissible to define it here. The code will call the user
; routine in irp$l_pid if a system address and then return WITHOUT calling
; ioc$std_initiate. The presumption is that the UCB may be still busy and
; that a "real" I/O completion must still be carried out. This code will
; simply call the user code at its normal IPL (i.e., 8) and leave things
; in a state that will allow reqcom to be reissued. The IRP and UCB are
; passed as arguments to the routine called.
; Glenn C. Everhart, Sept. 1995
;
	TSTL	IRP$L_PID(R3)		; System completion?
	.BRANCH_LIKELY
	BGEQ	NOHOOK			; if not, proceed to fast finish

;
; We know here that this is system completion. Check the special exemption.
;
	.BRANCH_LIKELY
	BBC	#IRP$V_FINIPL8, -	; If "FINIPL8" bit is clear treat
		IRP$L_STS(R3),DONT	; via IPL 4
	BBS	#IRP$V_HIFORK,-		; Don't allow this if not at IPL8
		IRP$L_STS(R3),DONT	;  either.
;
; The special case.
;     Do the call here to the user routine with the IRP address in
;     R3.
	PUSHL	R5
	PUSHL	R3
	MOVL	R3,R5
	CALLS	#1,@IRP$L_PID(R3)	; call user routine, arg = IRP addr
	POPL	R5
	.IF	B,RESUME
; No resume is needed if the RESUME argument
; is blank.
	RET				; Do not continue further.
	.IFF
; Resume IS needed if RESUME is filled in.
; This is done where the original call would have done I/O completion
; and then started new I/O.
	BRW	DONE			; Finish things off.
	.ENDC
NOHOOK:
; Fast finish. Insert the IRP on the IPL8 fork queue with an FPC of
; the fast-finish routine in SYSFIOMAC.

FQH_SIZE=8           			; Assumption required for offsetting
OFFSET8=<8-6>*FQH_SIZE			; Offset of IPL8 fork queue from start
					; of fork queue in Cpu Data Area
.IF IDN,TYPE,IRP
	EVAX_MFPR_PRBR			; R0/CPU data area
	CLRB	IRP$B_FLCK(R3)		; No fork IPL
.IFF
        CLRL    IRP$W_CDRPSIZE(R3)      ; Actually clearing fork IPL
.ENDC
	MOVAB	G^EXE$FASTIO_FINISH,-	; Routine to execute
		IRP$L_FPC(R3)
	EVAX_STQ R3,IRP$Q_FR3(R3)	; Save IRP address
	INSQUE	IRP$L_FQFL(R3),-	; Put the IRP on the queue
		CPU$Q_SWIQFL+OFFSET8(R0)
	.BRANCH_LIKELY

; Unknown if we can ever get here at IPL < FIPL (Fast-IO IRPs only)

.IF IDN,TYPE,IRP
	BBC	#IRP$V_HIFORK,-		; Branch if we're at IPL8 right now
		IRP$L_STS(R3),DONE	;  (no need to fire up IPL8 forker)
	SOFTINT	S^#IPL$_IOLOCK8		; Else request fork at 8 (rare)
	BRB	DONE			; Done with this one for now, pick up
					;  later at EXE$FASTIO_FINISH
.ENDC
	.ENDM	FAST_FINISH
.PAGE

	.SBTTL	CANCEL I/O ON CHANNEL
;+
; IOC$CANCELIO - CANCEL I/O ON CHANNEL
;
; THIS ROUTINE IS A DEVICE INDEPENDENT CANCEL I/O ROUTINE THAT CONDITIONALLY
; MARKS THE UCB SUCH THAT THE CURRENT I/O REQUEST WILL BE CANCELED IF CONDITIONS
; WARRANT SUCH A ACTION.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$CANCELIO
;
; INPUTS:
;
;	R2 = NEGATIVE OF THE CHANNEL NUMBER.
;	R3 = CURRENT IO PACKET.
;	R4 = PCB ADDRESS.
;	R5 = UCB ADDRESS.
;
; OUTPUTS:
;
;	IF THE DEVICE IS BUSY, THE REQUEST IS FOR THE CURRENT PROCESS, AND
;	THE I/O WAS ISSUED FROM THE DESIGNATED CHANNEL, THEN THE CANCEL I/O
;	BIT IS SET IN THE CORRESPONDING UCB.
;
;	R2, R3, R4, AND R5 ARE PRESERVED ACROSS CALL.
;-
 
	DECLARE_PSECT	EXEC$NONPAGED_CODE

	UNIVERSAL_JSB	IOC$CANCELIO,-
			INPUT=<R2,R3,R4,R5>,-
			PRESERVE=<R0,R1>

;IOC$CANCELIO::				;CANCEL I/O ON CHANNEL

	$COUNT_ENTRY IOC$CANCELIO   	;DEBUG COUNTING.
	CALL_CANCELIO SAVE_R0R1=NO	;PUSH PARMS AND CALL STD ROUTINE.
	RSB				;RETURN TO CALLER.

	.PAGE
	.SBTTL	CANCEL I/O ON CHANNEL
;+
; IOC_STD$CANCELIO - CANCEL I/O ON CHANNEL
;
; THIS ROUTINE IS A DEVICE INDEPENDENT CANCEL I/O ROUTINE THAT CONDITIONALLY
; MARKS THE UCB SUCH THAT THE CURRENT I/O REQUEST WILL BE CANCELED IF CONDITIONS
; WARRANT SUCH A ACTION.
;
; CALLING SEQUENCE:
;       IOC_STD$CANCELIO (CHANNELNUM, IRP, PCB, UCB)
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;	ARG$_CHANNELNUM(AP)	- Channel number, by value
; 	ARG$_IRP(AP)		- I/O Request Packet
; 	ARG$_PCB(AP)		- Process Control Block
;       ARG$_UCB(AP)    	- Unit Control Block
; OUTPUTS:
;	None
; RETURN VALUE:
;       None
$OFFDEF ARG,< -
	CHANNELNUM, -
	IRP, -
	PCB, -	
        UCB -
        >
UNIVERSAL_ENTRY	IOC_STD$CANCELIO,-
		MASK=<M^<R3,R4,R5>>
;IOC_STD$CANCELIO::			;CANCEL I/O ON CHANNEL

	$COUNT_ENTRY IOC_STD$CANCELIO	;DEBUG COUNTING.

	MOVL	ARG$_UCB(AP),R5		;GET UCB INPUT.
	MOVL    ARG$_IRP(AP),R3		;GET IRP INPUT.
	MOVL    ARG$_PCB(AP),R4         ;GET PCB INPUT.

	BBC	#UCB$V_BSY,UCB$L_STS(R5),10$ ;IF CLR, DEVICE NOT BUSY
	CMPL	IRP$L_PID(R3),PCB$L_PID(R4) ;PROCESS ID MATCH?
	BNEQ	10$			;IF NEQ NO
	CMPL	ARG$_CHANNELNUM(AP),-
		IRP$L_CHAN(R3)		;CHANNEL NUMBER MATCH
	BNEQ	10$			;IF NEQ NO
	BISL	#UCB$M_CANCEL,UCB$L_STS(R5) ;SET CANCEL PENDING
10$:	RET				;RETURN TO CALLER.


	.PAGE
	.SBTTL	HANDLE LAST CHANNEL DEASSIGN
;+
; IOC$LAST_CHAN - Last Channel Deassign Specific
; IOC$LAST_CHAN_AMBX - Last Assoc. MBX Channel Deassign Specific
;
; FUNCTIONAL DESCRIPTION:
;
;	Common functions done on last channel deassignment are handled.  The 
;	driver's cancel I/O routine is called with an appropriate reason code 
;	(CAN$C_DASSGN for regular deassign, or CAN$C_AMBXDGN for associated 
;	mailboxes).  If after the cancel routine finished UCB$V_DELETEUCB is 
;	set, the UCB is credited and deleted.
;
;	Note that the IPL on entry must be at or lower than IPL$_ASTDEL.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$LAST_CHAN
;	JSB	IOC$LAST_CHAN_AMBX
;
; INPUTS:
;
;	R5	UCB address
;	R4      PCB address for current process
;	R2	Channel index (LAST_CHAN only)
;
; OUTPUTS:
;
;	R0 thru R3 destroyed.
;	If appropriate, UCB is deallocated.
;
;
; CALLING SEQUENCE FOR STEP 2 DRIVER CANCEL ROUTINE AT DDT$PS_CANCEL_2
;
;	CANCEL (channel, irp, pcb, ucb, reason)
;
; INPUTS TO DRIVER CANCEL ROUTINE:
;
;	(Step 1 register usage is in parenthesis)
;
;	channel		(R2)	Channel index number
;	irp		(R3)	Contents of UCB$L_IRP
;	pcb		(R4)	Address of PCB
;	ucb		(R5)	Address of UCB
;	reason		(R8)	Cancel reason, either CAN$C_DASSGN or CAN$C_AMBXDGN
;
; OUTPUTS FROM DRIVER CANCEL ROUTINE:
;
;	Step 1:  R0-R3 may be destroyed
;	Step 2:  None
;-

	.ENABLE	LSB
	UNIVERSAL_JSB	IOC$LAST_CHAN_AMBX,INPUT=<R4,R5>,SCRATCH=<R0,R1,R2,R3>,-
			C2J_NAME=IOC_STD$LAST_CHAN_AMBX
;IOC$LAST_CHAN_AMBX::
	PUSHL	R8			; Save R8
	CLRQ	R2			; Clear unused cancel inputs.
	MOVZBL	#CAN$C_AMBXDGN, R8	; Set cancel reason code.
	.DSABL	FLAGGING		;suppress %AMAC-I-BRANCHBET message
	BRB	6$
	.ENABL	FLAGGING



	UNIVERSAL_JSB	IOC$LAST_CHAN,INPUT=<R2,R4,R5>,SCRATCH=<R0,R1,R2,R3>,-
			C2J_NAME=IOC_STD$LAST_CHAN
;IOC$LAST_CHAN::
	PUSHL	R8			; Save R8
	MOVL	UCB$L_IRP(R5), R3	; Get active packet address.
	MOVZBL	#CAN$C_DASSGN, R8	; Set cancel reason code.

6$:	SETIPL	IPL = #IPL$_ASTDEL,-	;assure ASTDEL IPL (may have been lower)
		ENVIRON	= UNIPROCESSOR	;Needed for SCH$SET_BREAKTHROUGH_CAP

	MOVL	#-1,-(SP)		;FLAG TO INDICATE NO AFFINITY NEEDED.
	CMPL	G^EXE$GL_AFFINITY,-	;DOES THIS UCB HAVE AFFINITY SET?
		UCB$L_AFFINITY(R5)	;
	BEQL	10$			;BR IF NO, CONTINUE

	GET_CURKTB			;LOAD KTB ADDRESS INTO R14

	BBS	#CPB$V_PRIMARY,KTB$L_CAPABILITIES(R14),10$
					;DOES CURRENT KERNEL THREAD ALREADY REQUIRE IT
	CLRL	(SP)			; INDICATE MUST REQUIRE PRIMARY.

	; Must use process affinity in order to force execution of the
	; process on the primary. This is needed in case the driver has
	; selected affinity.

	PUSHL	#0			; No mask required
	PUSHL	#CPB$M_PRIMARY		; Require the primary capability
	PUSHL	R14			; KTB of affected kernel thread
	CALLS  #3,SCH$SET_BREAKTHROUGH_CAP
	BLBC	R0,500$			;BR if error
;
; CALL THE DRIVER CANCEL ROUTINE
;
;	Call the driver cancel routine while holding the UCB fork lock and
;	at device fork IPL.
;
;	Note that if R2 contains a valid channel number, then the CCB is 
;	"locked" into memory before the driver cancel routine is called.
;	The CCB is locked into the working set of the current process by:  
;
;	(1) Writing the address of the CCB into the PCB$Q_KEEP_IN_WS field.
;	(2) For a multithreaded process, synchronize with the the critical
;		section in PAGEFAULT that reads this field to eliminate the
;		race condition that might otherwise exist.
;	(3) Touching the CCB to assure that it is faulted in. 
;	(4) Raising IPL to UCB fork level.
;	(5) Clearing the PCB field mentioned in step 1 once the CCB does not
;		need to remain in the working set.

10$:	MOVL	R2,R0			;R0 = channel number (possibly 0)
	BEQL	12$			;if nonzero channel number
	SUBL	#4,SP			;  allocate space for CCB pointer
	PUSHAL	(SP)			;  pointer to CCB pointer
	PUSHL	R2			;  channel number
	CALLS	#2,G^IOC$CHAN_TO_CCB	;  convert channel to CCB address
	POPL	R0			;  R0 = CCB addr, zero if error

12$:	TSTL	R0			;if nonzero CCB address
	BEQL	14$

	EVAX_OR	R0,R31,R1		; Copy CCB address to safe register
	EVAX_STQ R0,PCB$Q_KEEP_IN_WS(R4); Record address to lock in WS
	CMPL	PCB$L_MULTITHREAD(R4),#1; Can there be another current thread?
	BLEQ	13$			; If LEQ no, no race with PAGEFAULT

	MOVL	CTL$GL_PHD,R0		; Get current process's PHD address
	EVAX_MB				; Order KEEP_IN_WS write with flag read
125$:	BBS	#PHD$V_FREWSLE_ACTIVE,-	; If another thread is ejecting a WSLE,
		PHD$L_FLAGS(R0),125$	;  wait until it's done

13$:   	MOVL	CCB$L_STS(R1),R0	;  touch CCB to assure it's in our WS
14$:
	FORKLOCK -          		;take the device fork lock, raise IPL
		LOCK	= UCB$B_FLCK(R5),-
		PRESERVE= NO		;  no need to preserve R0

	MOVL	UCB$L_DDT(R5), R0	;Get DDT address.
	PUSHL	R8			;reason (stack arguments for CALLS)
	PUSHL	R5			;UCB
	PUSHL	R4			;PCB
	PUSHL	R3			;IRP
	PUSHL	R2			;channel
	CALLS	#5,@DDT$PS_CANCEL_2(R0) ;Call driver's cancel I/O routine.

	EVAX_SUBQ R31,#1,R0		; Fabricate a -1 constant
	EVAX_STQ R0,PCB$Q_KEEP_IN_WS(R4) ; CCB does not need to be kept in WS

	FORKUNLOCK -			;release device fork lock
		LOCK	= UCB$B_FLCK(R5),-
		NEWIPL	= #IPL$_ASTDEL,- ; drop down to IPL$_ASTDEL
		PRESERVE= NO		;  no need to save R0

; Must remove process requirement of primary if it was acquired in this
; routine only.

	TSTL	(SP)+			;REMOVE FLAG FROM STACK
	BNEQ	16$			;NEQ, AFFINITY WAS NOT ACQUIRED HERE

;
; Restore the original scheduling state
;
	PUSHL	R14			; Must be current KTB
	CALLS  #1,SCH$CLEAR_BREAKTHROUGH_CAP

16$:	BBS	#DEV$V_ALL, -		; Branch if still allocated
		UCB$L_DEVCHAR(R5),30$
	BITL	#DEV$M_TRM!DEV$M_MBX, -	; Is this a terminal, remote terminal 
		UCB$L_DEVCHAR(R5)	; or mailbox?
	BEQL	20$			; Branch if not.
	BBSC	#DEV$V_OPR, -		; Else, clear OPR bit.
		UCB$L_DEVCHAR(R5), 20$	; This is an implicit operator disable.
20$:
;+
; Certain devices can have their ORBs switched from the default template for
; the device to an new ORB.  Typically this switch is performed by LOGINOUT and
; makes the user who just logged in the owner.  When the device is freed we
; need to restore the prototype ORB and delete the new ORB.  For file oriented
; devices (according to Stu Davidson) we do not want to restore the template
; ORB the dismount code is supposed to handle it.
;-
	BBS	#DEV$V_FOD,	-	; If file oriented device do not 
		UCB$L_DEVCHAR(R5),25$	; restore the ORB
	MOVL	UCB$L_ORB(R5),R0	; Get the current ORB
	TSTL	ORB$L_ORIGINAL_ORB(R0)	; Is this a process owner ORB
	BEQL	25$			; IF EQ, nothing to clean up
	MOVL	ORB$L_ORIGINAL_ORB(R0),-
		UCB$L_ORB(R5)		; Restore the original ORB
	MOVAL	G^EXE$DEANONPAGED,R2	; Delete ORB proper to non-paged pool
	JSB	G^EXE$DELETE_ORB	; Delete ORB (and ACL segments)
25$:	BBC	#UCB$V_DELETEUCB,-	; Branch if UCB not to be deleted.
		UCB$L_STS(R5), 30$
	MOVL	#1,R0			; Note object delete event.
	JSB	G^NSA$DEVICE_AUDIT	; Audit object deletion.
	BSBW	IOC$CREDIT_UCB		; Else credit UCB quotas,
	BSBW	IOC$DELETE_UCB		; and delete the UCB.
30$:	POPL	R8			; Restore R8
	RSB
	.DISABLE LSB

500$:	BUG_CHECK  INCONSTATE,FATAL	;Can't generate affinity mask??


	.PAGE
	.SBTTL	FILL DIAGNOSTIC BUFFER
;+
; IOC$DIAGBUFILL - Fill Diagnostic Buffer
;
;	This routine is called at the end of an I/O operation, but before
;	releasing the I/O channel, to fill the final device parameters into an
;	internal diagnostic buffer if one is specified.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$DIAGBUFILL
;
; INPUTS:
;
;	R4	Parameter that is passed directly to the driver register dump
;		routine without any interpretation by IOC$DIAGBUFILL.  On VAX,
;		this is often the address of the device CSR.  On EVAX, it is
;		often the address of a CRAM.
;	R5	Device unit UCB address.
;
; OUTPUTS:
;
;	If a diagnostic buffer was specified in the original request, then
;	the completion time, final error counters, and device registers are
;	filled into the diagnostic buffer.
;
;	R0,R1,R2,R3 may be destroyed.
;
;
; CALLING SEQUENCE FOR DRIVER REGISTER DUMP ROUTINE AT DDT$PS_REGDUMP_2
;
;	REGDUMP (buffer, cram, ucb)
;
; INPUTS TO DRIVER REGISTER DUMP ROUTINE:
;
;	(Step 1 register usage is in parenthesis)
;
;	buffer	(R0)	Address of buffer for device register values
;	cram	(R4)	Value that was passed in to IOC$DIAGBUFILL
;	ucb	(R5)	Address of UCB
;
; OUTPUTS FROM REGISTER DUMP ROUTINE:
;
;	Step 1:   R0,R1,R2 may be destroyed.
;	Step 2:   None
;-
 
	UNIVERSAL_JSB	IOC$DIAGBUFILL,-
			INPUT=<R4,R5>,-
			SCRATCH=<R0,R1,R2,R3>
;IOC$DIAGBUFILL::			;FILL DIAGNOSTIC BUFFER
	$COUNT_ENTRY IOC$DIAGBUFILL	; Debug Counting.
	CALL_DIAGBUFILL			; Push parms and call the STD routine.
	RSB				; Return to caller.

	.PAGE
	.SBTTL	FILL DIAGNOSTIC BUFFER
;+
; IOC_STD$DIAGBUFILL - Fill Diagnostic Buffer
;
; This routine is called at the end of an I/O operation, but before
; releasing the I/O channel, to fill the final device parameters into an
; internal diagnostic buffer if one is specified.
;
; CALLING SEQUENCE:
;       IOC_STD$DIAGBUFILL (DRIVERPARM, UCB) 
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;	ARG$_DRIVERPARM(AP)	- Parameter passed to register dump routine.
;       ARG$_UCB(AP)    	- Unit Control Block
; OUTPUTS:
;	None
; RETURN VALUE:
;       None
$OFFDEF ARG,< -
	DRIVERPARM, - 
        UCB -
        >
 
UNIVERSAL_ENTRY	IOC_STD$DIAGBUFILL,-
		MASK=<M^<R2,R5>>

;IOC_STD$DIAGBUFILL::			;FILL DIAGNOSTIC BUFFER

	$COUNT_ENTRY IOC_STD$DIAGBUFILL	;DEBUG COUNTING.

	MOVL	ARG$_UCB(AP),R5		;GET UCB INPUT.

	MOVL	UCB$L_IRP(R5),R1	;GET ADDRESS OF I/O PACKET
	BBC	#IRP$V_DIAGBUF,IRP$L_STS(R1),10$ ;IF CLR, NO DIAGNOSTIC BUFFER
	MOVL	@IRP$L_DIAGBUF(R1),R0	;GET ADDRESS OF INTERNAL BUFFER DATA AREA
	ADDL	#8,R0			;POINT PAST START TIME
	.DSABL	FLAGGING		;% AMAC-I-QUADMEMREF, quadword memory
	MOVQ	G^EXE$GQ_SYSTIME,(R0)+	;INSERT COMPLETION TIME
	.ENABL	FLAGGING
	MOVL	UCB$L_ERTCNT(R5),(R0)+	;INSERT FINAL ERROR COUNTERS
	MOVL	UCB$L_DDT(R5),R2	;GET ADDRESS OF DDT
	PUSHL	R5			;UCB (Stack arguments for CALLS)
	PUSHL	ARG$_DRIVERPARM(AP)	;DRIVER SPECIFIC.
	PUSHL	R0			;Buffer Address
	CALLS	#3, @DDT$PS_REGDUMP_2(R2)  ;CALL DEVICE SPECIFIC REGISTER DUMP ROUTINE
10$:	RET				;RETURN TO CALLER.

	.PAGE
	.SBTTL	RELEASE I/O CHANNEL
;+
; IOC_STD$RELCHAN - RELEASE I/O CHANNEL
;
; FUNCTIONAL DESCRIPTION:
;
;	This routine is called at the end of an I/O operation to release the
;	channel, i.e. the CRB, the I/O was being performed on.
;	
;	The CRB is released and an attempt is made to remove the next waiting
;	driver fork process from the CRB queue. If a driver process is waiting,
;	then the CRB is assigned to that driver process and its driver channel
;	grant routine is invoked.  If there is no driver process waiting for 
;	the channel, then the channel status is set to idle.
;
;	Note that this routine supports channel grant routines that use the new
;	standard call interface as well as the traditional JSB interface.  The
;	details of these two interfaces are described below.
;
; CALLING CONVENTION
;
;	void IOC_STD$RELCHAN (UCB *ucb)
;
; INPUTS:
;
;       ucb		Unit Control Block address
;
; OUTPUTS:
;
;	None
;
; RETURN STATUS:
;
;       None
;
; INPUTS (to driver channel grant routine):
;
;	This routine supports channel grant routines that either use the
;	traditional JSB interface or the new standard call interface.
;
;	The channel grant standard call interface:
;
;		void channel_grant_routine (IRP *irp, IDB *idb, UCB *ucb);
;
;		irp      is a pointer to the I/O request packet
;		idb      is a pointer to the interrupt dispatch block
;		ucb      is a pointer to the unit control block.
;	
;	Fork interface for fork routines using the JSB interface:
;
;		R3      is a pointer to the I/O request packet
;		R4      is a pointer to the interrupt dispatch block
;		R5      is a pointer to the unit control block.
;
;		R0 through R5 may be scratched.
;-

	$OFFDEF ARG,<UCB>		;Define parameter offsets

UNIVERSAL_ENTRY	IOC_STD$RELCHAN,-
		MASK=<M^<R2,R3,R4,R5>>
;IOC_STD$RELCHAN::			;RELEASE I/O CHANNEL
	
	$COUNT_ENTRY IOC_STD$RELCHAN

	MOVL	ARG$_UCB(AP),R5		;R5 = UCB
	MOVL	UCB$L_CRB(R5),R0	;R0 = CRB

	BBC	#CRB$V_BSY,-		;if CRB is busy
		CRB$L_MASK(R0),50$
	MOVL	CRB$L_INTD+VEC$L_IDB(R0),-
		R1			;  R1 = IDB address
	CMPL	R5,IDB$PS_OWNER(R1)	;  if this UCB is current CRB owner
	BNEQ	50$
	REMQUE	@CRB$L_WQFL(R0),R2	;    R2 = next UCB waiting for this CRB
	BVS	40$			;    if found a waiting UCB
	MOVL	R2,R5			;      R5 = waiting UCB
	MOVX	UCB$Q_FR3(R5),R3	;      restore R3 (64-bits) for grant routine
	MOVL	R1,R4			;      R4 = IDB address
	MOVL	R5,IDB$PS_OWNER(R1)	;      make waiting UCB owner of CRB
	;
	; The channel grant routine can either use the traditional JSB
	; interface or the standard call interface.
	;
	; This is accomplished by taking advantage of the fact that on the
	; Alpha architecture JSB entry and CALL entry routines are invoked
	; identically.  The only difference is which registers are used for
	; parameters.  Thus, if the parameters are made available in both the
	; standard call parameter registers and the registers expected by the
	; JSB interface the target routine can be written to either call
	; interface.  The overhead of this approach is a few extra register
	; copies.
	;
	.SET_REGISTERS	-		;      JSB interface inputs and scratched
		READ=<R3,R4,R5>,WRITTEN=<R0,R1,R2,R3,R4,R5>	

	PUSHL	R5			;      P3 = UCB
	PUSHL	R4			;      P2 = IDB
	PUSHL	R3			;      P1 = IRP from UCB$Q_FR3
	CALLS	#3,@UCB$L_FPC(R5)	;      call driver channel grant routine
	RET				;      return
;					;    else
40$:	CLRL	IDB$PS_OWNER(R1)	;      clear owner UCB
	BICL	#CRB$M_BSY,-		;      clear CRB busy bit
		CRB$L_MASK(R0)
50$:	RET				;return


	.PAGE
	.SBTTL	RELEASE I/O CHANNEL (JSB INTERFACE)
;+
; IOC$RELCHAN - RELEASE I/O CHANNEL (JSB INTERFACE)
;
; FUNCTIONAL DESCRIPTION:
;
;	This routine is a JSB-to-CALL jacket around the IOC_STD$RELCHAN routine.
;	Please refer to the description of the IOC_STD$RELCHAN routine.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$RELCHAN
;
; INPUTS:
;
;	R5	UCB address
;
; OUTPUTS:
;
;	R0,R1,R2 are documented as scratched.  However, the current version
;	scratches only R0 and R1.
;-


	UNIVERSAL_JSB	IOC$RELCHAN,-
			INPUT=<R5>,-
			SCRATCH=<R0,R1>		;formerly scratch=R0,R1,R2
;IOC$RELCHAN::				;RELEASE I/O CHANNEL
	$COUNT_ENTRY IOC$RELCHAN	;DEBUG COUNTING.
	.SET_REGISTERS	READ=<R5>
	CALL_RELCHAN			;PUSH PARMS AND CALL THE STD ROUTINE.
	RSB				;RETURN TO CALLER.

 	.PAGE
	.SBTTL	REQUEST I/O CHANNEL
;+
; IOC_STD$PRIMITIVE_REQCHANH - Request I/O Channel High Priority
; IOC_STD$PRIMITIVE_REQCHANL - Request I/O Channel Low Priority
;
;	These routines are called to request an I/O channel to perform an I/O
;	operation on.
;
;	If the specified I/O channel is idle, then it is immediately
;	assigned to the current driver process.  Otherwise, the driver process
;	context is saved in its fork block, the fork block is inserted
;	in the channel wait queue, and control returns to the caller.
;
; CALLING CONVENTION
;
;	status = IOC_STD$PRIMITIVE_REQCHANH (IRP *irp, UCB *ucb, IDB **idb_p)
;	status = IOC_STD$PRIMITIVE_REQCHANL (IRP *irp, UCB *ucb, IDB **idb_p)
;
; INPUTS:
;
;	irp		IRP address
;	ucb		UCB address
;	ucb->ucb$l_fpc	Contains the channel grant fork routine procedure value.
;			If the channel cannot be granted immediately then this
;			fork routine is invoked when it eventually is granted.
;			See routine header for IOC_STD$RELCHAN for full calling
;			convention.
;
; OUTPUTS:
;
;	idb_p		Address of a pointer to the IDB.  The IDB pointer is
;			written to this location.
;
; RETURN VALUE:
;
;	status		Contains SS$_NORMAL if the specified channel was
;			assigned immediately.
;			Contains 0 is the specified channel is busy and will
;			will be assigned later to channel granted fork routine.
;-

	.ENABL	LSB

	$OFFDEF	ARG,<IRP,UCB,IDB_P>

	UNIVERSAL_ENTRY	IOC_STD$PRIMITIVE_REQCHANH,MASK=<M^<R4,R5>>
;IOC_STD$PRIMITIVE_REQCHANH::		;Request I/O channel high priority
	$COUNT_ENTRY	IOC_STD$PRIMITIVE_REQCHANH

	MOVL	ARG$_UCB(AP),R5		;R5 = UCB
	MOVL	UCB$L_CRB(R5),R0	;R0 = address of CRB
	MOVAL	CRB$L_WQFL(R0),R1	;R1 = adddress of wait queue listhead
	.DSABL	FLAGGING		;suppress % AMAC-I-BRANCHBET message
	BRB	30$			;Join common REQCHANH/L code
	.ENABL	FLAGGING


	UNIVERSAL_ENTRY	IOC_STD$PRIMITIVE_REQCHANL,MASK=<M^<R4,R5>>
;IOC_STD$PRIMITIVE_REQCHANL::		;Request I/O channel low priority
	$COUNT_ENTRY	IOC_STD$PRIMITIVE_REQCHANL

	MOVL	ARG$_UCB(AP),R5		;R5 = UCB
	MOVL	UCB$L_CRB(R5),R0	;R0 = address of CRB
	MOVL	CRB$L_WQBL(R0),R1	;R1 = address of last entry in queue
	;
	; Common code for IOC_STD$PRIMITIVE_REQCHANH/L
	;
30$:	MOVL	CRB$L_INTD+VEC$L_IDB(R0),-
		R4			;Get address of IDB into R4 for output
	MOVL	R4,@ARG$_IDB_P(AP)	;write output IDB parameter
	BBSS	#CRB$V_BSY,-		;Set channel busy.
		CRB$L_MASK(R0),40$	;If was clear then
	MOVL	R5,IDB$PS_OWNER(R4)	;  Set UCB as owner in IDB
	MOVZWL	#SS$_NORMAL,R0		;  Set status to indicate immediate grant
	RET				;  Return

40$:	MOVL	ARG$_IRP(AP),R0		;load IRP parameter into UCB$Q_FR3
	MOVX	R0,UCB$Q_FR3(R5)
	INSQUE	UCB$L_FQFL(R5),(R1) 	;Insert driver UCB in channel wait queue
	CMPL	R5,IDB$PS_OWNER(R4)	;If current driver process owner
	BNEQ	50$
	.SET_REGISTERS	READ=<R5>
	CALL_RELCHAN			;  release channel (P1=R5=UCB)
50$:	CLRL	R0			;Set status to indicate fork queued
	RET				;Return
	.DSABL	LSB

	.PAGE
	.SBTTL	REQUEST I/O CHANNEL (JSB INTERFACE)
;+
; IOC$PRIMITIVE_REQCHANH - Request I/O Channel High Priority
; IOC$PRIMITIVE_REQCHANL - Request I/O Channel Low Priority
;
; FUNCTIONAL DESCRIPTION:
;
;	The IOC$PRIMITIVE_REQCHANH and IOC$PRIMITIVE_REQCHANL routines are
;	JSB-to-CALL jackets around the IOC_STD$PRIMITIVE_REQCHANH and
;	IOC_STD$PRIMITIVE_REQCHANL routines respectively.  Please refer to the
;	description of the corresponding standard call interface routines.
;
;
; CALLING SEQUENCE:
;
;	JSB	IOC$PRIMITIVE_REQCHANH
;	JSB	IOC$PRIMITIVE_REQCHANL
;
; INPUTS:
;
;	R5		UCB address
;	R3		Passed on to channel grant fork routine, if called.
;	UCB$L_FPC(R5)	Contains the channel grant fork routine procedure value.
;
; OUTPUTS:
;
;	R0		Contains SS$_NORMAL if the specified channel was
;			assigned immediately.
;			Contains 0 is the specified channel is busy and will
;			will be assigned later to channel granted fork routine.
;
;	R4		Contains the IDB address.
;
;	R1,R2		Are allowd to be destroyed by the documented versions
;			of this routines.  However, the current version only
;			scratches R1.
;-

	UNIVERSAL_JSB	IOC$PRIMITIVE_REQCHANH,INPUT=<R3,R5>,OUTPUT=<R0,R4>,-
					       SCRATCH=<R1>
	$COUNT_ENTRY	IOC$PRIMITIVE_REQCHANH
	SUBL	#4,SP			;allocate space for returned IDB pointer
	PUSHAB	(SP)			;P3 = pointer to IDB pointer
	PUSHL	R5			;P2 = UCB
	PUSHL	R3			;P1 = IRP
	CALLS	#3,IOC_STD$PRIMITIVE_REQCHANH
	POPL	R4			;R4 = IDB for return
	RSB				;returns status in R0


	UNIVERSAL_JSB	IOC$PRIMITIVE_REQCHANL,INPUT=<R3,R5>,OUTPUT=<R0,R4>,-
					       SCRATCH=<R1>
	$COUNT_ENTRY	IOC$PRIMITIVE_REQCHANL
	SUBL	#4,SP			;allocate space for returned IDB pointer
	PUSHAB	(SP)			;P3 = pointer to IDB pointer
	PUSHL	R5			;P2 = UCB
	PUSHL	R3			;P1 = IRP
	CALLS	#3,IOC_STD$PRIMITIVE_REQCHANL
	POPL	R4			;R4 = IDB for return
	RSB				;returns status in R0

	.PAGE
	.SBTTL	I/O REQUEST COMPLETION PROCESSING FOR CLASS DRIVERS
;+
; IOC$ALTREQCOM - I/O Request Complete Alternate Entry.
;
;	This routine is entered when an I/O operation is completed on
;	a device using the disk or tape class drivers.  The packet
;	is inserted in the I/O finish queue for I/O post processing.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$ALTREQCOM
;
; INPUTS:
;
;	R0	First  longword of I/O status
;	R1	Second longword of I/O status
;	R5	CDRP address
;
; OUTPUTS:
;
;	R3	IRP address
;	R5	UCB address
;
;	R0,R1,R2,R4 may be destroyed.
;-
	UNIVERSAL_JSB	IOC$ALTREQCOM,-
			INPUT=<R0,R1,R5>,-
			OUTPUT=<R3,R5>,-
			SCRATCH=<R0,R1,R2,R4>
;IOC$ALTREQCOM::

	$COUNT_ENTRY IOC$ALTREQCOM	; Debug counting.
	CALL_ALTREQCOM			; Push parms and call the STD routine.
	RSB				; Return to caller.

	.PAGE
	.SBTTL	I/O REQUEST COMPLETION PROCESSING FOR CLASS DRIVERS
;+
; IOC_STD$ALTREQCOM - I/O Request Complete Alternate Entry.
;
; This routine is entered when an I/O operation is completed on
; a device using the disk or tape class drivers.  The packet
; is inserted in the I/O finish queue for I/O post processing.
;
; CALLING SEQUENCE:
;       IOC_STD$ALTREQCOM (IOSTATUS1, IOSTATUS2, CDRP, IRP, UCB)
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;	ARG$_IOSTATUS1(AP) - First Longword of I/O Status
;	ARG$_IOSTATUS2(AP) - Second Longword of I/O Status
;	ARG$_CDRP(AP)	   - Class Driver Request Packet
; OUTPUTS:
;	ARG$_IRP(AP)	- I/O Request Packet
;       ARG$_UCB(AP)    - Unit Control Block
; RETURN STATUS:
;       None
$OFFDEF ARG,< -
        IOSTATUS1, -
	IOSTATUS2, -
	CDRP, -
	IRP, -
        UCB -
        >

UNIVERSAL_ENTRY	IOC_STD$ALTREQCOM, -
		MASK=<M^<R2,R3,R4,R5>>

;IOC_STD$ALTREQCOM::

	; Verify that the negative CDRP offset CDRP$L_IOQFL reaches exactly
	; back to the start of the IRP.
	;

	$COUNT_ENTRY IOC_STD$ALTREQCOM	; Debug counting.

	ASSUME	CDRP$L_IOQFL+IRP$K_CDRP  EQ  IRP$L_IOQFL
	ASSUME	IRP$L_IOQFL  EQ  0

	MOVL    ARG$_IOSTATUS1(AP),R0	; R0 => 1st LW of final I/O status. 
	MOVL    ARG$_IOSTATUS2(AP),R1	; R1 => 2nd LW of final I/O status.
	MOVL	ARG$_CDRP(AP),R5	; R5 => CDRP from input.

	MOVAB	CDRP$L_IOQFL(R5),R3	; R3 => IRP section of CDRP. This is
					;  for compatibility with rest of QIO
					;  logic.
	MOVL	IRP$L_UCB(R3),R5	; R5 => UCB.
	INCL	UCB$L_OPCNT(R5)		; Increment operations completed
	BLBC	R0, 40$			; Branch if I/O error.
;
; If this I/O completes with success and the device is a tape, then
; we may need to generate a volume validation CRC for this record.
; Call EXE$MNTVER_GEN_CRC to determine if this I/O affects the stored
; CRCs.
;
	CMPB	#DC$_TAPE,-		; Special case success and tape device.
		UCB$B_DEVCLASS(R5)	;
	BEQL	30$			; Branch out of line if tape.

	ASSUME	IRP$L_IOST1 EQ IRP$L_MEDIA
	ASSUME	IRP$L_IOST2 EQ <IRP$L_MEDIA+4>
20$:	MOVL	R0,IRP$L_IOST1(R3)	; Save final I/O status in IRP.
	MOVL	R1,IRP$L_IOST2(R3)

	.IF DF	CA$_MEASURE_IOT

	BBS	#IRP$V_DOPMS,-		; Data collection enabled?
		IRP$L_STS(R3),50$	; Branch if yes.
25$:					; Branch back here when PMS done
	.ENDC

	BBS	#IRP$V_FAST_FINISH,-	; Fast-finish?
		IRP$L_STS(R3),29$	; Branch if yes.

	; IOC_STD$POST_IRP (IRP(R3))
27$:	CALL_POST_IRP			; Finally, insert IRP on post queue.
	
28$:	MOVL	R3,@ARG$_IRP(AP)	; Move IRP to output.
	MOVL	R5,@ARG$_UCB(AP)	; Move UCB to output.
	
	RET				; and return.

; Fast-IO finish

29$:	FAST_FINISH TYPE=IRP, DONE=28$,DONT=27$,RESUME=YES ; Do fast-finish

;
; Out of line special case processing.
;

40$:	CMPB	#DC$_TAPE,-		; Special case failure and tape device.
		UCB$B_DEVCLASS(R5)	;
	BEQL	48$			; Branch if tape.
45$:    ;STATUS(R2)=EXE_STD$$MOUNT_VER(IOSTAT1(R0),IOSTAT2(R1),IRP(R3),UCB(R5)) 
	JSB	G^EXE$MOUNT_VER		; Keep the JSB until FORK code is ready .
;	$MOUNT_VER SAVE_R0R1=YES	; Call to start Mount Verification.
					; (R2 - continue status)
	BLBS	R2,20$			; Go back to normal flow.
	
	MOVL	R3,@ARG$_IRP(AP)	; Move IRP to output.
	MOVL	R5,@ARG$_UCB(AP)	; Move UCB to output.
	
	RET				; Return to caller.

48$:	CMPW	R0, #SS$_ENDOFFILE	; Was error end-of-file?
	BEQL	30$			; If so, don't treat as error.
	CMPW	R0, #SS$_ENDOFVOLUME	; End-of-volume error?
	BEQL	30$			; If so, don't treat as error.
	CMPW	R0, #SS$_DATAOVERUN	; Data-overrun error?
	BNEQ	45$			; If not, must be some other error.

30$:	JSB	G^EXE$MNTVER_GEN_CRC	; See if we need to generate CRC.
	BRB	20$			; Go back to normal flow.

	.IF DF	CA$_MEASURE_IOT

50$:	BSBW	PMS$END_IO		; Insert end of I/O transaction message.
	BRB	25$			; Rejoin common code.

	.ENDC


	.PAGE
	.SBTTL	QUEUE IRP TO POST QUEUE, POST HASTE.
;+
; IOC$POST_IRP	- Queue IRP to Post Queue, post haste.
;
;	This subroutine is used to INSQ an IRP on the IOPOST queue for
;	the current CPU.  One particular instance where this routine
;	is needed is during mount verification processing when mount 
;	verification has been started but the IRP is not to be stalled 
;	or retryed.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$POST_IRP
;
; INPUTS:
;
;	R3	IRP address
;
; OUTPUTS:
;
;	R0 and R1 are destroyed.
;-

	UNIVERSAL_JSB	IOC$POST_IRP,-
			INPUT=<R3>,-
			SCRATCH=<R0,R1>
;IOC$POST_IRP::

	$COUNT_ENTRY IOC$POST_IRP	; Debug counting.
	CALL_POST_IRP			; Push parms and call the STD routine.
	RSB				; Reurn to caller.

	.PAGE
	.SBTTL	QUEUE IRP TO POST QUEUE, POST HASTE.
;+
; IOC_STD$POST_IRP	- Queue IRP to Post Queue, post haste.
;
; This subroutine is used to INSQ an IRP on the IOPOST queue for
; the current CPU.  One particular instance where this routine
; is needed is during mount verification processing when mount 
; verification has been started but the IRP is not to be stalled 
; or retried.
;
; CALLING SEQUENCE:
;       IOC_STD$POST_IRP (IRP)
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;       ARG$_IRP(AP)	- Unit Control Block
; OUTPUTS:
;       None
; RETURN STATUS:
;       None
$OFFDEF ARG,< -
        IRP -
        >
UNIVERSAL_ENTRY	IOC_STD$POST_IRP,-
		MASK=<M^<R3>>
;IOC_STD$POST_IRP::			;Queue IRP to Post Queue

	$COUNT_ENTRY IOC_STD$POST_IRP	; Debug counting.

	MOVL	ARG$_IRP(AP),R3		; Get input.

	ASSUME	SMP$V_ENABLED  EQ  0
	BLBS	G^SMP$GL_FLAGS,80$	; Br if a multi-processor
	$INSQTI_R (R3),G^IOC$GQ_POSTIQ	; INSERT PACKET ON QUEUE
	SOFTINT	S^#IPL$_IOPOST		; REQUEST FORK
	RET				; RETURN

80$:	; This is a multiprocessor, don't allow rescheduling so that
	; we guarantee the SOFTINT will happen on the correct CPU.

	SAVIPL	-(SP)			; Save the current IPL
	CMPB	(SP),S^#IPL$_IOPOST	; Is current IPL high enough?
	BGEQ	90$			; Br if yes, continue
	SETIPL	S^#IPL$_IOPOST,ENVIRON=UNIPROCESSOR ; Else, raise IPL
90$:	$INSQTI_R (R3),G^IOC$GQ_POSTIQ,-; INSERT PACKET ON QUEUE
		  Q_NOT_EMPTY=100$      ; If Q not empty before insert then all done
	FIND_CPU_DATA	R1		; GET ADDRESS OF PER-CPU DATA
	MOVL	G^SMP$GL_PRIMID,R0	; Store primary id in a GPR.
	CMPL	R0,CPU$L_PHY_CPUID(R1)  ; Are we the primary?
	BNEQ	110$			; Br if not primary
	SOFTINT	S^#IPL$_IOPOST		; REQUEST FORK
100$:	ENBINT				; RESTORE IPL
	RET				; RETURN

110$:	; This is not the primary CPU on an MP

	IPINT_CPU   IOPOST		; Tell the primary to do a softint
	BRB	100$			; Non-optimal case, don't bother


	.PAGE
	.SBTTL	I/O REQUEST COMPLETION PROCESSING
;+
; IOC$REQCOM - I/O Request Complete
;
;	This routine is entered when an I/O operation is completed on a
;	device unit. The final I/O status is stored in the associated I/O
;	packet and the packet is inserted in the I/O finish queue for
;	I/O post processing. Device unit busy is cleared and an attempt
;	is made to start another I/O request on the device unit.
;
;	If the I/O request completed with an error, and the device is
;	a disk or tape, then branch to the mount verification code, which
;	will determine if the situation requires mount verification.
;
;	If mount verification is in progress, no further I/O requests will
;	be initiated.  This has a side effect of keeping the 'BSY' bit in
;	whatever state it is currently in.  For conventional disk/tape
;	drivers, the BSY bit will be left on, which will block $QIO from
;	initiating any new I/O on the device.  For the disk and tape class
;	drivers, the BSY bit will be off, which will allow $QIO to initiate
;	new I/O.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$REQCOM
;
; INPUTS:
;
;	R0 = First longword of I/O status.
;	R1 = Second longword of I/O status.
;	R5 = UCB address of device unit.
;
; OUTPUTS:
;
;	The I/O packet is inserted in the I/O post processing queue
;	and device unit busy is cleared.  A software interrupt is
;	requested to initiate I/O post processing.
;
;	R0,R1,R3,R4 may be destroyed.
;-
	UNIVERSAL_JSB	IOC$REQCOM,-
			INPUT=<R0,R1,R5>,-
			SCRATCH=<R0,R1,R2,R3,R4>
;IOC$REQCOM::				; I/O done processing.

	$COUNT_ENTRY IOC$REQCOM    	; Debug counting.
	CALL_REQCOM			; Push parms and call the STD routine.
	RSB				; Return to caller.

	.PAGE
	.SBTTL	I/O REQUEST COMPLETION PROCESSING
;+
; IOC_STD$REQCOM - I/O Request Complete
;
; This routine is entered when an I/O operation is completed on a
; device unit. The final I/O status is stored in the associated I/O
; packet and the packet is inserted in the I/O finish queue for
; I/O post processing. Device unit busy is cleared and an attempt
; is made to start another I/O request on the device unit.
;
; If the I/O request completed with an error, and the device is
; a disk or tape, then branch to the mount verification code, which
; will determine if the situation requires mount verification.
;
; If mount verification is in progress, no further I/O requests will
; be initiated.  This has a side effect of keeping the 'BSY' bit in
; whatever state it is currently in.  For conventional disk/tape
; drivers, the BSY bit will be left on, which will block $QIO from
; initiating any new I/O on the device.  For the disk and tape class
; drivers, the BSY bit will be off, which will allow $QIO to initiate
; new I/O.
;
; CALLING SEQUENCE:
;       IOC_STD$REQCOM (IOSTATUS1, IOSTATUS2, UCB) 
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;	ARG$_IOSTATUS1(AP) - First Longword of I/O Status
;	ARG$_IOSTATUS2(AP) - Second Longword of I/O Status
;       ARG$_UCB(AP)       - Unit Control Block
; OUTPUTS:
;       None
; RETURN STATUS:
;       None
$OFFDEF ARG,< -
        IOSTATUS1, -
	IOSTATUS2, -
        UCB -
        >

	.ENABL	LSB
	ASSUME	UCB$V_ERLOGIP LE 7

;
; Out of line code to handle error log in progress.
;
10$:	BICL	#UCB$M_ERLOGIP,-	; Clear error log in progress.
		UCB$L_STS(R5)		;
	MOVL	UCB$L_EMB(R5),R2	; Get address of error message buffer.
	MOVL	UCB$L_STS(R5),-		; Insert final device status.
		EMB$L_DV_STS(R2)	;
	MOVL	UCB$L_ERTCNT(R5),-	; Insert final error counters.
		EMB$L_DV_ERTCNT(R2)	;
	MOVL	UCB$L_ERTMAX(R5),-	; Insert final error counters.
		EMB$L_DV_ERTMAX(R2)	;
	MOVL	ARG$_IOSTATUS1(AP),-
		EMB$Q_DV_IOSB(R2)	; Insert first LW of final I/O status.
	MOVL	ARG$_IOSTATUS2(AP),-
		EMB$Q_DV_IOSB+4(R2)	; Insert second LW of final I/O status.
	; ERL_STD$RELEASEMB (ERRMESSBUF(R2))
	CALL_RELEASEMB			; Release error message buffer (R0 scratched)
	BRB	30$			; Go back to normal flow.
;
; Out of line code to handle successful I/O to magtape.
;

20$:	JSB	G^EXE$MNTVER_GEN_CRC	; See if we need to generate CRC.
	BRB	40$			; Go back to normal flow.
;
; If this is a disk or tape device, call the mount verification routine
; to determine if mount verification is necessary.  If not, control
; will return, and the request will be completed in the normal manner.
;

DEVCHK:
	CMPB	#DC$_DISK,-		; Is this device a disk?
		UCB$B_DEVCLASS(R5)	;
	BEQL	50$			; Branch if so.
	CMPB	#DC$_TAPE,-		; Is this device a tape?
		UCB$B_DEVCLASS(R5)	;
	BNEQ	40$			; Branch if not.
	CMPW	R0, #SS$_ENDOFFILE	; End-of-file error?
	BEQL	20$			; If so, don't treat as error.
	CMPW	R0, #SS$_ENDOFVOLUME	; End-of-volume error?
	BEQL	20$			; If so, don't treat as error.
	CMPW	R0, #SS$_DATAOVERUN	; Data-overrun error?
	BEQL	20$			; If so, don't treat as error.
50$:	BBCC	#UCB$V_MNTVERPND,-	; Check for mount verification pending.
		UCB$L_STS(R5),60$	; If not, just enter mount verification.
	BBCC	#UCB$V_MNTVERIP,-	; Clear in-progress bit before call
		UCB$L_STS(R5),60$	;  so it will really start.

60$:    ;STATUS(R2)=EXE_STD$$MOUNT_VER(IOSTAT1(R0),IOSTAT2(R1),IRP(R3),UCB(R5)) 
	JSB	G^EXE$MOUNT_VER 	; Keep JSB until fork code is ready.
;	$MOUNT_VER SAVE_R0R1=YES	; Call to start Mount Verification.
					; (R2 - continue sts)
	BLBS	R2,40$			; Complete I/O request.
	RET				; Return to caller.

	.IF DF	CA$_MEASURE_IOT

DO_PMS:	BSBW	PMS$END_IO		; Insert end of I/O transaction message.
	BRB	PMSEND			; Rejoin common code.

	.ENDC

;
; Start of mainline code
;
UNIVERSAL_ENTRY IOC_STD$REQCOM,-
        MASK=<M^<R2,R3,R4,R5>>          

;IOC_STD$REQCOM::				; I/O done processing.

	$COUNT_ENTRY IOC_STD$REQCOM    	; Debug counting.

	MOVL	ARG$_UCB(AP),R5	; Get the UCB input.

	BITL	#UCB$M_ERLOGIP,-	; Error log in progress?
		UCB$L_STS(R5)		;
	BNEQ	10$			; Branch if so.

30$:	MOVL	ARG$_IOSTATUS1(AP),R0	; Get 1st LW of I/O Status from input.
	MOVL	ARG$_IOSTATUS2(AP),R1	; Get 2nd LW of I/O Status from input.
	MOVL	UCB$L_IRP(R5),R3	; Get address of I/O packet.
	INCL	UCB$L_OPCNT(R5)		; Increment operations completed.
	BLBC	R0,DEVCHK		; If I/O error, check for disk or tape.
;
; If this I/O completes with success and the device is a tape, then
; we may need to generate a volume validation CRC for this record.
; Call EXE$MNTVER_GEN_CRC to determine if this I/O affects the stored
; CRCs.
;
	CMPB	#DC$_TAPE,-		; Special case success and tape device.
		UCB$B_DEVCLASS(R5)	;
	BEQL	20$			; Branch if tape.
;
; Do not save the I/O status in the IRP until it has been decided that
; mount verification is not necessary.  This is to avoid overwriting the
; physical disk address stored in the IRP at offset IRP$L_MEDIA.
;
	ASSUME	IRP$L_IOST1 EQ IRP$L_MEDIA
	ASSUME	IRP$L_IOST2 EQ <IRP$L_MEDIA+4>
40$:	MOVL	ARG$_IOSTATUS1(AP),-
		IRP$L_IOST1(R3)	; Store final I/O status.
	MOVL	ARG$_IOSTATUS2(AP),-
		IRP$L_IOST2(R3)

	.IF DF	CA$_MEASURE_IOT

	.BRANCH_UNLIKELY
	BBS	#IRP$V_DOPMS,IRP$L_STS(R3),DO_PMS
	.ENDC

PMSEND: 
	BBS	#IRP$V_FAST_FINISH,-	; Fast-IO express finish?
		IRP$L_STS(R3),47$

	ASSUME	SMP$V_ENABLED  EQ  0
41$:	BLBC	G^SMP$GL_FLAGS,48$	; Br if a not a multi-processor

	; This is a multiprocessor, don't allow rescheduling so that
	; we guarantee the SOFTINT will happen on the correct CPU.

	SAVIPL	-(SP)			; Save the current IPL
	CMPB	(SP),S^#IPL$_IOPOST	; Is current IPL high enough?
	BGEQ	42$			; Br if yes, continue
	SETIPL	S^#IPL$_IOPOST,ENVIRON=UNIPROCESSOR ; Else, raise IPL
42$:	$INSQTI_R (R3),G^IOC$GQ_POSTIQ,-; INSERT PACKET ON QUEUE
		  Q_NOT_EMPTY=44$	; If Q not empty before insert then all done
	FIND_CPU_DATA	R1		; GET ADDRESS OF PER-CPU DATA
	MOVL	G^SMP$GL_PRIMID,R0	; Store primary id in a GPR.
	CMPL	R0,CPU$L_PHY_CPUID(R1)  ; Are we the primary?
	BNEQ	46$			; Br if not primary
	SOFTINT	S^#IPL$_IOPOST		; REQUEST FORK
44$:	ENBINT				; RESTORE IPL
	BRB	49$			; continue

46$:	; This is not the primary CPU on an MP

	IPINT_CPU   IOPOST		; Tell the primary to do a softint
	BRB	44$			; Non-optimal case, don't bother

	; Fast-IO fast finish
47$:	FAST_FINISH TYPE=IRP,DONE=49$,DONT=41$,RESUME=YES

48$:	$INSQTI_R (R3),G^IOC$GQ_POSTIQ	; INSERT PACKET ON QUEUE
	SOFTINT	S^#IPL$_IOPOST		; REQUEST FORK
49$:	BBS	#UCB$V_MNTVERIP,-	; Branch if mount ver. in progress.
		UCB$L_STS(R5),-		;  (NOTE this leaves 'BSY' as is)
		MNTVERPNDCHK		;
NXTIRP:	REMQUE	@UCB$L_IOQFL(R5),R3	; Remove I/O packet from device unit
					;  queue
	BVS	51$			; if found one then
	; IOC_STD$INITIATE (IRP(R3), UCB(R5))
	CALL_INITIATE			;   initiate next function
	RET				;   and return

51$:	BICL	#UCB$M_BSY,UCB$L_STS(R5); else Clear unit busy.
RELEASE:				; Release all channels.
	; IOC_STD$RELCHAN (UCB(R5))
	CALL_RELCHAN			;  Call to release channel.
	RET				; Return to caller.

;
; The Mount-Verification-Pending bit is used to indicate that a disk or tape
; should go into mount verification as soon as the current I/O is done.  This
; is intended for use in a cluster to stall I/O when quorum is lost.
;

MNTVERPNDCHK:
	BBCC	#UCB$V_MNTVERPND,-	; Check for mount verification pending.
		UCB$L_STS(R5),RELEASE	; If not, just clean up.
	CMPB	#DC$_DISK,-		; Is this device a disk?
		UCB$B_DEVCLASS(R5)	;
	BEQL	70$			; Branch if so.
	CMPB	#DC$_TAPE,-		; Is this device a tape?
		UCB$B_DEVCLASS(R5)	;
	BNEQ	RELEASE			; Branch if not.
70$:	MOVL	UCB$L_IOQFL(R5), R3	; Get current IRP at head of Queue
	CMPL	@(R3), R3		; Is the queue empty?
	BEQL	80$			; Yes, can't look at first entry
	BBC	#IRP$V_MVIRP,-		; Is first entry a Mount Verification IRP?
		IRP$L_STS(R3),80$	; No, start Mount Verification
	BRB	NXTIRP			; Yes, go process it
80$:	BBCC	#UCB$V_MNTVERIP,-	; Clear in-progress bit before call.
		UCB$L_STS(R5),90$	;
90$:	CLRL	R3			; No IRP passed to mount verification.
        ;STATUS(R2)=EXE_STD$$MOUNT_VER(IOSTAT1(R0),IOSTAT2(R1),IRP(R3),UCB(R5)) 
	CALL_MOUNT_VER SAVE_R0R1=NO		; Call to start Mount Verification.
					; (R2 - continue status)
	BLBS	R2,NXTIRP		; Wasn't necessary.
	RET				; Return to caller
	.DSABL	LSB


	.PAGE
	.SBTTL	I/O Request Completion on Local CPU
;+
; IOC_STD$REQCOM_LOCAL - I/O Request Completion on Local CPU 
;
;	A class driver calls this routine for an I/O operation completed on
;	a device unit. The driver must ensure that the I/O request completed
;	successfully, and must store the final I/O status in the 
; 	associated I/O packet (a CDRP).  
; 
; 	This routine only handles only disk class drivers at the moment.
;  	It inserts the CDRP in the local CPU's I/O postprocessing 
;	queue, rather than the IOPOST queue serviced by the Primary CPU.
;
; 	Because this is designed to be called from Fast Path, code assumes
;	no Forklock is held. Access to the local IOPOST queue is synchronized
;	by the INSQUE (see Finishio/Abortio).  Thus, the code increments
;	UCB$L_IOCNT without synchronization.
;
;	Note that unlike normal REQCOM, this routine does not initiate
;	further I/O. 
;
; CALLING SEQUENCE:
;
;	IOC_STD$REQCOM_LOCAL (CDRP)
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
;
; INPUTS:
;
;	ARG$_CDRP(AP)	Class driver request packet containing 
;			UCB, IRP, and filled-in I/O status
;
; IMPLICIT INPUTS: 	Caller is a Fast Path IPL 8 fork thread 
;
; OUTPUTS:
;
;	The Fast Path I/O packet is inserted in the I/O post processing queue
;	and a software interrupt is requested if necessary to initiate 
;	I/O post processing. 
;
;	For Fast I/O packets, insert instead on the local IPL8 fork queue.
;
; RETURN STATUS:
;       None
$OFFDEF ARG,< -
        CDRP >
	.ENABL	LSB

UNIVERSAL_ENTRY IOC_STD$REQCOM_LOCAL,-
        MASK=<M^<R3,R5>>          

;IOC_STD$REQCOM_LOCAL::

	ASSUME	CDRP$L_IOQFL+IRP$K_CDRP  EQ  IRP$L_IOQFL
	ASSUME	IRP$L_IOQFL  EQ  0

	MOVL	ARG$_CDRP(AP),R5	; R5 => CDRP from input
	MOVAB	CDRP$L_IOQFL(R5),R3	; R3 => IRP section of CDRP
	MOVL	IRP$L_UCB(R3),R5	; R5 => UCB
	INCL	UCB$L_OPCNT(R5)		; Increment operations completed

    .IF DF  CA$_IO_DEBUG 
	INCREMENT_IOCNT COUNTER=FP_REQCOM ; Increment debug counter
    .ENDC

    .IF DF  CA$_MEASURE_IOT		; PMS code
    .BRANCH_LIKELY
	BBC	#IRP$V_DOPMS,-		; Branch if performance monitoring disabled
		IRP$L_STS(R3),5$
	BSBW	PMS$END_IO		; Insert end of I/O transaction message.
    .ENDC

5$:	EVAX_MFPR_PRBR			; find cpu data to R0
	BBS	#IRP$V_FAST_FINISH,-	; If Fast-IO express finish, go 
		IRP$L_STS(R3),20$	; put IRP on IPL8 queue
;
; Make these I/O's complete using local IOPOST queue
;
10$:
	.BRANCH_LIKELY
	BBC	#IRP$V_FINIPL8, -		; If "FINIPL8" bit is clear treat
		IRP$L_STS(R3),14$	; via IPL 4
;
; The special case.
; Because of the control flow of this area, which I don't want to alter
; heavily (lest I introduce bugs), I add special case code outside of
; the fast_finish macro here. The fast_finish macro will never see
; IRP$L_PID negative cases, so the code in it will never be called. Also,
; this code needs to take out the forklock to be consistent with all
; other cases here where the completion code is called.
;
;     Do the call here to the user routine with the IRP address in
;     R3.
; In this case we need to fork also since the presumption will normally be
; that the forklock is held. In this way, any path to the call will be at
; IPL 8 and with forklock held, even this one. This means that this one 
; special case has to await forklock, but this code should never be
; entered with forklock held and this is a NON default case.
	FORKLOCK LOCK=UCB$B_FLCK(R5),PRESERVE=YES
	PUSHL	R5
	MOVL	R3,R5
	PUSHL	R3
	CALLS	#1,@IRP$L_PID(R3)	; call user routine
	POPL	R5
	FORKUNLOCK LOCK=UCB$B_FLCK(R5),PRESERVE=YES,CONDITION=RESTORE
	RET				; Do not continue further.
;	(It is the user responsibility to continue via cleanup of the
;	IRP and reissue of REQCOM (or alt... or whatever) if a system
;	completion at IPL 8 is requested by this mechanism.)
14$:
	INSQUE	(R3),@CPU$L_PSBL(R0)	; INSQUE into local IOPOST queue
	BNEQ	30$			; Branch if queue not empty
	SOFTINT	#IPL$_IOPOST		; Else signal I/O post interrupt
 	BRW	30$			; and exit 

20$:	.REPT	0
	.BRANCH_UNLIKELY
	BBS	#IRP$V_COMPLX,-		; For right now, Fast I/O can't deal 
		IRP$L_STS(R3),10$	; with complex buffers

	.BRANCH_UNLIKELY
.IIF IDN,TYPE,IRP, BLBC	ARG$_IOSTATUS1(AP),DONT ; Br if error (and not FASTPATH)
	.BRANCH_UNLIKELY
	TSTL	IRP$L_PID(R3)		; System dispatch?
	BLSS	10$			; Yes, don't fast-finish

;
; Fast finish. Insert the IRP on the IPL8 fork queue with an FPC of
; the fast-finish routine in SYSFIOMAC.
;

ASSUME CPU$S_SWIQFL/CPU$K_NUM_SWIQS EQ 8 ; Assumption required for offest 
FQH_SIZE=8                      	;  Size of queue header 
OFFSET8=<8-6>*FQH_SIZE			; Offset of IPL8 fork queue from start
					; of fork queue in Cpu Data Area
ASSUME IRP$B_FLCK EQ IRP$W_CDRPSIZE+3   ; To clear FLCK, we're using CLRL
ASSUME IRP$B_CD_TYPE EQ IRP$W_CDRPSIZE+2   ; not CLRB.
	CLRL	IRP$W_CDRPSIZE(R3)	; Actually clearing fork IPL 
	MOVAB	G^EXE$FASTIO_FINISH,-	; Routine to execute
		IRP$L_FPC(R3)
	EVAX_STQ R3,IRP$Q_FR3(R3)	; Save IRP address
	INSQUE	IRP$L_FQFL(R3),-	; Put the IRP on the queue head
		CPU$Q_SWIQFL+OFFSET8(R0) ; No need to softint - can only be
					; here as IPL8 fork thread, so will
					; RET back to dispatcher
	.ENDR

	FAST_FINISH TYPE=CDRP,DONE=30$,DONT=10$
30$: 	RET				; Return to caller
	.DSABL	LSB

	.PAGE
	.SBTTL	MOUNT VERIFICATION HELPER
;+
; IOC$MNTVER - Assist driver with mount verification.
;
; This routine is called by G^EXE$MOUNT_VER to perform some driver-specific
; actions necessary for mount verification.  This routine is used by non-
; CLASS drivers, and is called by default if G^EXE$MOUNT_VER finds the address
; of IOC$RETURN in DDT$L_MNTVER.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$MNTVER
;
; INPUTS:
;
;	R3	IRP address or 0
;	R5	UCB address
;
; OUTPUTS:
;
;	R0,R1,R3 may be destroyed.
;
; SIDE EFFECTS:
;
;	If R3 contains an IRP address, the IRP will be queued to the
;	head of the UCB's IRP work queue.  If R3 contains is zero, then
;	remove the IRP from the head of the UCB's work queue and attempt
;	to initiate the I/O.
;
;	If the supplied IRP (start of mount verification) has the SRVIO
;	bit set, the IRP is simply to be posted and control returned to
;	mount verification.
;-

	UNIVERSAL_JSB	IOC$MNTVER,INPUT=<R3,R5>,SCRATCH=<R0,R1,R3>
;IOC$MNTVER::				;Driver-specific mount verification code

	$COUNT_ENTRY IOC$MNTVER		; Debug counting.
	CALL_MNTVER			; Push parms and call STD routine.
	RSB				; Return to caller.

	.PAGE
	.SBTTL	MOUNT VERIFICATION HELPER
;+
; IOC_STD$MNTVER - Assist driver with mount verification.
;
; This routine is called by EXE_STD$MOUNT_VER to perform some driver-specific
; actions necessary for mount verification.  This routine is used by non-
; CLASS drivers, and is called by default if EXE_STD$MOUNT_VER finds the address
; of IOC$RETURN in DDT$L_MNTVER.
;
; CALLING SEQUENCE:
;       IOC_STD$MNTVER (IRP, UCB) 
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;	ARG$_IRP(AP)	- I/O Request Packet
;       ARG$_UCB(AP)    - Unit Control Block
; OUTPUTS:
;       None
; RETURN STATUS:
;       None
$OFFDEF ARG,< -
        IRP, -
        UCB -
        >
UNIVERSAL_ENTRY	IOC_STD$MNTVER,-
		MASK=<M^<R3,R5>>
;IOC_STD$MNTVER::			;Driver-specific mount verification code

	$COUNT_ENTRY IOC_STD$MNTVER	;Debug counting.

	MOVL 	ARG$_IRP(AP),R3		;Get IRP from input.

	TSTL	R3			;Check IRP address
	BEQL	10$			;Branch if none
	BBS	#IRP$V_SRVIO, -		;If this is a SRVIO, then it should be
		IRP$L_STS(R3), 5$	; posted, not requeued
	MOVL 	ARG$_UCB(AP),R5		;Get UCB from input.
	INSQUE	IRP$L_IOQFL(R3),-	;Requeue the IRP
		UCB$L_IOQFL(R5)		;
	RET				;Return to caller.

5$:	; IOC_STD$POST_IRP (IRP(R3))
	CALL_POST_IRP			;Punt the IRP, and return to mount...
	RET				; verification after posting

10$:	MOVL 	ARG$_UCB(AP),R5		;Get UCB from input.
	REMQUE	@UCB$L_IOQFL(R5),R3	;Remove I/O packet from device unit
					; queue into R3.
	BVS	15$			; If UCB$L_IOQFL queue not empty then
	; IOC_STD$INITIATE (IRP(R3), UCB(R5))
	CALL_INITIATE			;  initiate next function...
	RET				;  and return

15$:	BICL	#UCB$M_BSY,UCB$L_STS(R5); Else clear unit busy.
	; IOC_STD$RELCHAN (UCB(R5))
	CALL_RELCHAN			;  Release all channels.
	RET				;  Return to caller.


	.PAGE
	.SBTTL	INITIATE I/O FUNCTION ON DEVICE
;+
; IOC$INITIATE - INITIATE NEXT FUNCTION ON DEVICE
;
; 	This routine is called to initiate the next function on a device by
;	clearing status bits, setting the operation start time if a diagnostic
;	buffer is specified, and calling the driver at its start I/O entry
;	point.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$INITIATE
;
; INPUTS:
;
;	R3 	Address of I/O request packet.
;	R5	Device unit UCB address.
;
; OUTPUTS:
;
;	Cancel I/O, powerfail, and time out status bits are cleared, the
;	current system time is filled into the internal diagnostic buffer
;	if one is specified, and the driver is called at its start I/O entry
;	point.
;
;	R0 thru R4 may be destroyed.
;
;
; CALLING SEQUENCE FOR DRIVER START I/O ROUTINE AT DDT$PS_START_2
;
;	START (irp, ucb)	
;
; INPUTS TO DRIVER START I/O ROUTINE:
;
;	(Step 1 register usage is in parenthesis)
;
;	irp	(R3) 	Address of I/O request packet.
;	ucb	(R5)	Device unit UCB address.
;
; OUTPUTS FROM DRIVER START I/O ROUTINE:
;
;	Step 1: R0 thru R4 may be destroyed.
;	Step 2: None
;-
 
	UNIVERSAL_JSB	IOC$INITIATE,-
			INPUT=<R3,R5>,-
			SCRATCH=<R0,R1,R2,R4>
;IOC$INITIATE::				;INITIATE I/O FUNCTION
	
	$COUNT_ENTRY IOC$INITIATE	; Debug counting.
	CALL_INITIATE			; Push parms and call the STD routine.
	RSB				; Return to caller.
 
	.PAGE
	.SBTTL	INITIATE I/O FUNCTION ON DEVICE
;+
; IOC_STD$INITIATE - INITIATE NEXT FUNCTION ON DEVICE
;
; This routine is called to initiate the next function on a device by
; clearing status bits, setting the operation start time if a diagnostic
; buffer is specified, and calling the driver at its start I/O entry
; point.
; CALLING SEQUENCE:
;       IOC_STD$INITIATE (IRP, UCB)
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;	ARG$_IRP(AP)	- I/O Request Packet
;       ARG$_UCB(AP)    - Unit Control Block
; OUTPUTS:
;       None
; RETURN STATUS:
;       None
$OFFDEF ARG,< -
	IRP,-
        UCB -
        >

UNIVERSAL_ENTRY	IOC_STD$INITIATE,-
			MASK=<M^<R2,R3,R4,R5>>
;IOC_STD$INITIATE::				;INITIATE I/O FUNCTION

	$COUNT_ENTRY IOC_STD$INITIATE	; Debug counting.
	CLRL	R2			; Resumed ("old") IO inserted at q head
	MOVL	ARG$_UCB(AP),R5		; Get UCB input.
	MOVL	ARG$_IRP(AP),R3		; Get IRP input.
	BICL	#IRP$M_LOCK_RELEASEABLE, - ; Traditional Initiates can't tolerate
		IRP$L_STS(R3)		   ; release of forklock
	.DSABL	FLAGGING		;suppress % AMAC-I-BRANCHBET message
	BRW	COMMON			; go to common code
	.ENABL	FLAGGING

;++
; IOC_STD$INITIATE_NEW_IO is an alternate entry to INITIATE, used by INSIOQ/C.
;
; INPUTS:
;	ARG$_IRP(AP)	- I/O Request Packet
;       ARG$_UCB(AP)    - Unit Control Block
;
;	Register use same as IOC$INITIATE
;--
$OFFDEF ARG,< -
	IRP,-
        UCB -
        >

; Flag bits used in R2 
M_NEW_IO    = 1	; New I/O if set
M_QFULL     = 2	; IOQ populated if set
M_FLCK_REL  = 4	; Forklock releaseable if set
V_FLCK_REL  = 2	;  and V form of above

BLINK	    = 4 ; Offset to backward link in queue header

UNIVERSAL_ENTRY	IOC_STD$INITIATE_NEW_IO,-
			MASK=<M^<R2,R3,R4,R5>>

;IOC_STD$INITIATE_NEW_IO:: 		;INITIATE new I/O FUNCTION

	MOVL	#1, R2			; Initialize R2 and set new IO 

	MOVL	ARG$_UCB(AP),R5		; Get UCB input.
	MOVL	ARG$_IRP(AP),R3		; Get IRP input.

; First, dispatch anything that needs the normal method - MVIRPs and I/O 
; for non-FastPath devices. Whether UCB$V_FAST_PATH is set or not, a device 
; already on the startaff queue is considered a FastPath device.
; 		
COMMON: ASSUME	SMP$V_ENABLED  EQ  0
	 .BRANCH_LIKELY
	BLBC	SMP$GL_FLAGS,5$		; If uniprocessor, reg. dispatch 

	EVAX_MFPR_PRBR			; Get addr of CPUDB in R0
	MOVL	R0,R4			; Place CPUDB into R4 

	BBS	#IRP$V_MVIRP, -   	; If mntver, regular dispatch
		IRP$L_STS(R3),4$

	TSTL	UCB$PS_START_AFF_QFL(R5); If UCB on FastPath queue already,
	BNEQ	20$			;    skip both reg and FP dispatch -
					;    must queue IRP.
	BBS	#UCB$V_FAST_PATH,-	; If FastPath device, 
		UCB$L_STS(R5),15$	;    go try FP dispatch 

4$:	MOVL	CPU$L_PHY_CPUID(R4),R0	; Get our CPU ID 
	BBC	R0,UCB$L_AFFINITY(R5),90$ ; Br if we can't run on this CPU


;
;  Regular dispatch (traditional driver Start I/O entry)
;
5$:	MOVL	R3,UCB$L_IRP(R5)	;SAVE I/O PACKET ADDRESS
	MOVL	IRP$L_SVAPTE(R3),-	;COPY TRANSFER PARAMETERS
		UCB$L_SVAPTE(R5)	;  SVAPTE
	MOVL	IRP$L_BCNT(R3),-	;  BCNT
		UCB$L_BCNT(R5)
	MOVL	IRP$L_BOFF(R3),-	;  BOFF
		UCB$L_BOFF(R5)
	
        .DSABL  FLAGGING                ;% AMAC-I-QUADMEMREF, quadword memory
10$:	INITIATE_SETUP			; set up UCB, IRP fields
	.ENABL	FLAGGING

  .IF DF CA$_IO_DEBUG              	       ; for MON version of code
	INCREMENT_IOCNT COUNTER=FP_PORT_CPU_IO ;  Correct CPU, we win 
  .ENDC

;
; Check kernel stack for imminent overflow. If this may happen, fork
; first, using the IRP's fork block, and then continue, knowing that then
; the kernel stack is cleared.
; 
;
; The following guard zone size works in cases tested, where 512 is
; too small. It is an experimental, not an analytical, number.
;
KSP_GUARD_ZONE=1024
;
; The tests are:
; First, if SP is not within the guard zone of the current page (2
; pagelets here) we don't need to fork.
; Second:
; If SP is negative, we expect a guard page to exist. Then probe the
; next page down and if that is ok, do not fork. If SP is positive,
; we cannot rely on a guard page existing so fork.
	MCOML	G^MMG$GL_BWP_MASK,R0	; Get the page mask complement
	BICL3	R0,SP,R0		; Mask off all but in-page bits of SP
	CMPL	R0,#KSP_GUARD_ZONE	; Is this value above the guard?
	BGTR	12$			; If gtr, yes, no need to fork
; We are within the guard zone. If the SP is positive we must fork.
	TSTL	SP			; Is SP positive?
	BGTR	13$			; Yes, we must fork.
; Form an address in next page down to probe. The "+8" can be anything in
; the range 1 to <page size - guard zone size> but just go down to the
; next quadword here.
	MOVAB	-<KSP_GUARD_ZONE+8>(SP),R0 ; Get an address in next page down
	IFNOWRT #8,(R0),13$,PRVMOD=#0	; Check kernel access
; If we fall through, we don't need to fork, so go ahead and call the 
; driver start entry.

; Note: There is another copy of the code between 12$ and 13$ below in
; the area below label 150$. Be sure any changes here are reflected there
; also.
12$:
	MOVL	UCB$L_DDT(R5),R0	;GET ADDRESS OF DRIVER DISPATCH TABLE
	PUSHL	R5			;UCB (Stack Arguments for CALLS)
	PUSHL	R3			;IRP
	CALLS	#2, @DDT$PS_START_2(R0)	;START I/O OPERATION
	RET				;RETURN TO CALLER.
; If we get to 13$, we must fork to clear the stack.
13$:
; Store R5 in R4; we'll get it back after the fork.
	MOVL	R5,R4			; Store R5 in R4 for fork
	MOVB	UCB$B_FLCK(R5),IRP$B_FLCK(R3) ; Set the fork lock correctly
	MOVAB	IRP$L_FQFL(R3),R5	; Fork on the IRP fork block
	FORK	ROUTINE=150$,CONTINUE=14$,ENVIRONMENT=CALL
14$:	MOVL	R4,R5			; Get back R5 (safety)
	RET

;
; Fast Path dispatching - for fastpath IRPs on correct CPU with 
;	lock_releaseable set.
;
15$:	CMPL 	R4,UCB$L_PORT_CPUDB(R5)		; Already on correct CPU?
	BNEQ	20$				; No, can't dispatch it, have to queue
	BBS 	#IRP$V_LOCK_RELEASEABLE, -  	; If safe to release forklock, 
		IRP$L_STS(R3),10$	    	; go invoke driver 
;
; Can't dispatch IRP here, insert it on UCB IOQ
;
20$:
  .IF DF CA$_IO_DEBUG	; for MON version of code
	INCREMENT_IOCNT COUNTER=FP_IO_QUEUED	;  IRP queued to UCB
  .ENDC

	BBS	#1,SGN$GL_FAST_PATH,10$		; branch if Start I/O affinity disabled

	BBCC	#IRP$V_LOCK_RELEASEABLE, -	; Safer to leave queued IRPs
		IRP$L_STS(R3),27$		;   with bit cleared
	BISL	#M_FLCK_REL,R2			; But remember old state in R2
27$:	BLBC	R2,35$				; If Old I/O, branch
;
;  New I/O so place on pending queue by calling the Pending I/O routine
;  Note changes to the Pending I/O routine interface must be coordinated
;  with the EXE_STD$INSERT_IRP interface.
;
        .DSABL  FLAGGING                	;% AMAC-I-QUADMEMREF, quadword 
	INITIATE_SETUP				; Do init setup
	.ENABL	FLAGGING
	MOVL	UCB$L_DDT(R5), R0		; Get driver dispatch table
	PUSHL	R3				; I/O Request Packet
	PUSHAB	UCB$L_IOQFL(R5)			; I/O Queue Listhead
	CALLS	#2, @DDT$PS_PENDING_IO(R0)	; Call Pending I/O routine
	TSTL	R0				; Examine return status
	BEQL	40$				; Branch as queue was empty, no optimization possible
	BRW	50$				; Else queue was full before
						; enqueue or IRP not queued.
						; Branch as no need for IPINT.
;
; Old I/O (MSCP / SCSI / other) is inserted at head of queue
;
35$:	INSERT_EL -				; Queue IRP
		EL	= (R3),-		; to head of UCB IOQ
		QUE	= UCB$L_IOQFL(R5),-
		SCRATCH = R0
;
; IRP is inserted - see if we're done yet
;
40$:	TSTL	UCB$PS_START_AFF_QFL(R5)	; UCB on FastPath queue?
	BEQL	60$				; No, so go queue UCB
50$:
  .IF DF CA$_IO_DEBUG				; for MON version of code
	INCREMENT_IOCNT COUNTER=FP_UCB_WAS_QUEUED ; UCB found already queued
  .ENDC
	RET					; Else we're done
;
; If old I/O or IOQ was empty, queue UCB to CPUDB. 
; Insert UCB at tail of queue. Test whether this is first UCB on queue -
; if so, we might also need to generate an IPINT or queue the fkb.
;
60$:
  .IF DF CA$_IO_DEBUG              	    	; for MON version of code
	INCREMENT_IOCNT COUNTER=FP_UCB_QUEUED 	; Have to queue UCB
  .ENDC
	MOVL	UCB$L_PORT_CPUDB(R5),R3		; Get desired CPUDB address

	MOVL	CPU$PS_IO_START_AFF_QFL(R3),R1  ; Save first UCB on q (if any)
	INSERT_EL -				; insert UCB on start aff queue
		EL 	= UCB$PS_START_AFF_QFL(R5),-  
		QUE 	= @CPU$PS_IO_START_AFF_QBL(R3),- 
		SCRATCH = R0             	
	MOVAL	CPU$PS_IO_START_AFF_QFL(R3),R0	; Get CPU queue head address
	CMPL	R1,R0				; Was CPU queue empty?
	BEQL	70$				; Yes, more to do

  .IF DF CA$_IO_DEBUG              	    	 ; for MON version of code
	INCREMENT_IOCNT COUNTER=FP_OTHER_UCB_QUEUED ; CPU queue was populated
  .ENDC
	RET 					; else done
;
;  R4 is current CPUDB 
;  R3 is desired CPUDB 
;
70$:	CMPL	R3,R4 				; Queuing to current CPU?
	BEQL	80$				; Yes, branch and queue fkb
;
;  UCB queued to different CPU so IPINT must be done. First, release forklock
;  if safe for this thread. Then generate the IPINT and exit.
;
	BBC 	#V_FLCK_REL,R2,75$		; Skip if lock not releaseable
	FORKUNLOCK   -                          ; Else release the forklock
		LOCK=UCB$B_FLCK(R5),- 		;   
		PRESERVE=NO		   	; Don't preserve R0
75$:
  .IF DF CA$_IO_DEBUG              	   	; for MON version of code
	INCREMENT_IOCNT COUNTER=FP_IPINT 	;  IPINT required from Initiate
  .ENDC

	MOVAL	CPU$L_WORK_REQ(R3),R1		; Get addr of work request
77$:	EVAX_LDLL -
		 R0,(R1) 			; Load temp from work req
	BISL 	#CPU$M_IO_START_AFF,R0 		; Tell other CPU why the IPINT
	EVAX_STLC -
		R0,(R1)				; Store back to work req	
	.BRANCH_UNLIKELY 
  	EVAX_BEQ R0,77$ 			; Try again on failure

	IPINT_CPU_FAST -			; generate MB and IPINT
		CPUDB=R3			; note r0,r1,r16 unpredictable
						; but doesn't matter here
	RET 
;
; We are on correct CPU, yet we didn't dispatch this fastpath IRP earlier,
; so forklock must not be releaseable (thus we don't check for early release). 
; If our fkb available, queue IPL8 dispatcher fork thread. Otherwise, someone
; has already queued the thread so we're done.
; 
80$: 	MOVAL   CPU$L_IO_AFF_FLAGS(R4), R0	; Get address of flag field

85$: 	EVAX_LDLL R1,(R0)			; Read the flags
	BBS 	#CPU$V_IO_AFF_FKB_INUSE,R1,88$  ; If forkblock inuse, done
	BISL	#CPU$M_IO_AFF_FKB_INUSE,R1 	; Mark it inuse 
	EVAX_STLC R1,(R0) 			; Write it out
	.BRANCH_UNLIKELY 
  	EVAX_BEQ  R1,85$ 			; Try again on failure
;
; 	Insert the FKB on the IPL 8 fork queue and softint if needed.
;
ASSUME CPU$S_SWIQFL/CPU$K_NUM_SWIQS EQ 8 ; Assumption required for offest 
FQH_SIZE=8                      	;  Size of queue header 
OFFSET8=<<8-6>*FQH_SIZE>+BLINK		; Offset to tail of IPL8 fork queue 

  .IF DF CA$_IO_DEBUG              	   	; for MON version of code
	INCREMENT_IOCNT COUNTER=FP_QUEUE_SELF 	;  Queued fkb to ourself
  .ENDC
	INSQUE  CPU$L_IO_AFF_FKB(R4),- 	; Insert FKB on tail of IPL8 fork Q
                @CPU$Q_SWIQFL+OFFSET8(R4)

        BNEQ    87$                     ; Branch if queue is populated to avoid
                                        ; unneeded IPL8 interrupt
        SOFTINT S^#IPL$_IOLOCK8         ; Request fork at IPL 8

87$:	RET

88$:
  .IF DF CA$_IO_DEBUG              	   	 ; for MON version of code
	INCREMENT_IOCNT COUNTER=FP_AFF_FKB_INUSE ; Affinity FKB was found inuse
  .ENDC
	RET

90$:	;	Original Affinity Code 
	; One last check to see if we can run on this CPU.  If UCB$L_AFFINITY
	; is equal to 0, then this implies that this device is tied to the
	; primary CPU.  So if this CPU is the primary, then the I/O can be
	; started now.  Otherwise, a switch to the right CPU must be made.
	;
	TSTL	UCB$L_AFFINITY(R5)	; Check for logical primary
	BNEQ	95$			; Br if not primary
    	CMPL	R0,G^SMP$GL_PRIMID	; Now is this CPU the primary?
	BEQL	5$			; Br if yes, all set to start I/O
95$:	;
	; Obviously we can't run on this CPU, so it would be silly to
	; attempt to start any other IRPs from the UCB wait queue. Therefore,
	; we will queue this request packet to the right CPU and start things
	; going from there.
	;
	; Also, due to the fact that the MSCP class drivers require the setting
	; on of the UCB$V_BSY bit, and the entrance into the START_IO routine
	; of the driver to be done as an atomic action, we here undo the setting
	; of the UCB$V_BSY bit and we undo the incrementing of the UCB$L_QLEN
	; cell (both originally done in routine EXE$INSIOQ in SYSQIOREQ) so that
	; we can reperform these actions once we are executing on a CPU for
	; which we have affinity.
	;
	.PRESERVE ATOMICITY
	DECL	UCB$L_QLEN(R5)		; Undo increment done in EXE$INSIOQ
	.NOPRESERVE ATOMICITY
	BICL	#UCB$M_BSY,UCB$L_STS(R5); Undo setting of BSY bit.
	;
	; Call EXE$INSIOQC as a fork routine on the correct CPU.  This will
	; bring us back to IOC$_STDINITIATE but this time on the correct CPU.
	;
	PUSHL	R3			; Fork block address (IRP or TWP)
	PUSHL	R5			; Address of UCB with affinity mask
	PUSHAB	EXE$INSIOQC		; Address of fork context routine
	CALLS	#3,G^SMP$CPU_SWITCH	; Create fork thread on correct CPU
	RET				; return from IOC$INITIATE on this CPU

; Fork routine to clear stack before calling driver start.
; NOTE!!! We use ENV=JSB here to avoid extra register manipulation,
; since R3 and R5 are set up as needed already. This saves a small
; number of instructions (but this code may be executed relatively
; often).
150$:	FORK_ROUTINE,ENVIRONMENT=JSB
	MOVL	R4,R5			; Get back R5 = UCB address
; The rest of this code is an exact copy of what is at 12$ above. Be sure
; any edits to one happen in the other.
	.iif df,ini$doc, incw g^sgn$gl_vms6+2	;bump hi word for fork
	MOVL	UCB$L_DDT(R5),R0	;GET ADDRESS OF DRIVER DISPATCH TABLE
	PUSHL	R5			;UCB (Stack Arguments for CALLS)
	PUSHL	R3			;IRP
	CALLS	#2, @DDT$PS_START_2(R0)	;START I/O OPERATION
	RSB				;RETURN TO CALLER.


	.PAGE
	.SBTTL	Initiate Next Function on Port CPU 
;+
; IOC$INITIATE_PORT_CPU - Initiate Next Function On Post CPU
;
;	This routine initiates Fast Path I/O on the correct port CPU.
;	It executes only as an IPL 8 fork thread, and no forklock
;	is held on entry. Thus, any driver Start I/O routine called by
; 	this thread is free to release the Forklock in favor of a
;	port-specific spinlock.
; 
;	The routine dispatches I/O for all UCBs found queued on the 
;	local CPU database in the Start I/O affinity queue. It acquires 
;	the SCS/IOLOCK8 forklock to synchronize access to these UCBs.
;
; CALLING SEQUENCE:
;
;	JSB IOC$INITIATE_PORT_CPU
;
; INPUTS:
;
;	R3	= Current CPUDB address from FKB
;
; OUTPUTS:
;
;	R0-R5	- Destroyed
;	IRPs are dispatched to driver Start I/O routines with IRP and
;	UCB status bits set/cleared as done by IOC$INITIATE. However, 
; 	unlike IOC$INITIATE, UCB SVAPTE, BOFF and BCNT are not set up.
;	Current system time is filled into the internal diagnostic buffer
;	if one is specified.
;
; Calling Sequence for Driver START I/O Routine (at DDT$PS_START_2)
;
;	START (irp, ucb)	
;
; Inputs to Driver START I/O Routine:
;
;	(Step 1 register usage is in parenthesis)
;
;	irp	(R3) 	Address of I/O request packet.
;	ucb	(R5)	Device unit UCB address.
;
; OUTPUTS FROM DRIVER START I/O ROUTINE:
;
;	Step 1: R0 thru R4 may be destroyed.
;	Step 2: None
;-
	.ENABLE	LSB

	UNIVERSAL_JSB	IOC$INITIATE_PORT_CPU,-
			INPUT  =<                >,-
			OUTPUT =<                >,-
			SCRATCH=<R0,R1,R2,R3,R4,R5>

MAX_IO=64
IO_COUNT = 0
;IOC$INITIATE_PORT_CPU::			; Initiate I/O Function

	MOVL	R3,R2				; Use R2 as CPUDB  
	CLRL	-(SP)				; Zero IO_COUNT
	MOVL	S^#SPL$C_IOLOCK8,R0		; Get number of spinlock
	MOVL	@SMP$AR_SPNLKVEC[R0],R0 	; Point to lock address
	MOVL	R0,R4				; Save lockaddr in R4
	JSB	SMP$ACQNOIPL			; Don't modify IPL when locking

;  .IF DF CA$_IO_DEBUG              		; for MON version of code
;	MOVL	CPU$PS_IO_START_AFF_QFL(R2),R5 	; get next UCB address
;	MOVAL	CPU$PS_IO_START_AFF_QFL(R2),R0 	; R0 = address of queue header
;	CMPL	R5,R0				; CPUDB UCB queue empty?
;	BNEQ	30$ 				; No, so skip counter increment 
;	INCL	CPU$L_EMPTY_START_IOQ(R2)	; Count exceeding of limit
;  .ENDC

	BRW 	30$				; go process first UCB
;
; Process next UCB of CPU queue
;
; General register usage in routine:
; R0,R1 - scratch
; R2 	- CPUDB
; R3	- IRP
; R4	- Lockaddr
; R5	- UCB
;
PROCESS_NEXT_UCB:
	REMOVE_EL -				 ; remove completed UCB from
		EL = UCB$PS_START_AFF_QFL(R5), - ; Start I/O affinity queue
		SCRATCH = R0			 ; into R5 
	CLRL	UCB$PS_START_AFF_QFL(R5)	 ; and clear 'i-am-queued' flag

; Start processing the next UCB
;
30$:	MOVL	CPU$PS_IO_START_AFF_QFL(R2),R5 	; get next UCB address
	MOVAL	CPU$PS_IO_START_AFF_QFL(R2),R0 	; R0 = address of queue header
	CMPL	R5,R0				; CPUDB UCB queue empty?
	BEQL	INITIATE_PORT_CPU_DONE		; yes, no more UCBs - exit

	MOVAB	-UCB$PS_START_AFF_QFL(R5),R5 	; adjust to start of UCB

  .IF DF CA$_IO_DEBUG              		; for MON version of code
	MOVL	UCB$L_IOQFL(R5),R3		; Get first IRP address
	MOVAL	UCB$L_IOQFL(R5),R0		; Get queue head address
	CMPL	R3,R0				; queue empty?
	BNEQ	PROCESS_NEXT_IRP		; No, so skip counter increment
	INCREMENT_IOCNT COUNTER=FP_EMPTY_UCB 	; No IRPs awaiting service
  .ENDC

; Start processing the next IRP 
;	
PROCESS_NEXT_IRP:
	MOVL	UCB$L_IOQFL(R5),R3		; Get next IRP address
	MOVAL	UCB$L_IOQFL(R5),R0		; Get queue head address
	CMPL	R3,R0				; queue empty?
	BEQL	PROCESS_NEXT_UCB		; Yes, so done with this UCB
	

  .IF DF CA$_IO_DEBUG              		; for MON version of code
	INCREMENT_IOCNT COUNTER=FP_AFF_IO_FOUND	; count an IRP found
  .ENDC

	BBC 	#DEV$V_MSCP, - 			; Branch if not MSCP device       
		UCB$L_DEVCHAR2(R5),50$		; 
	TSTW	UCB$W_RWAITCNT(R5)		; If nonzero, can't resume I/O
	.BRANCH_UNLIKELY
	BNEQU	PROCESS_NEXT_UCB		; So go get next UCB
	BRW	55$				; else process this one
;
; Assumption:  once one IRP is sent to SCSI class driver Start I/O rtn,
; the driver removes and processes subsequent IRPs itself.  Note that only
; the first IRP, dispatched by this code, will have IRP$V_LOCK_RELEASEABLE 
; set and INITIATE_SETUP performed.
;
	.DSABL	FLAGGING			; %AMAC-I-QUADMEMREF, quadword 
50$:	INITIATE_SETUP 				; Set up UCB/IRP for SCSI
	.ENABL	FLAGGING

55$:	BISL	#IRP$M_LOCK_RELEASEABLE, - 	; Driver can release forklock
		IRP$L_STS(R3)	

	REMOVE_EL -				; Remove IRP from UCB
		EL=(R3), -
		SCRATCH=R0
	MOVL	UCB$L_DDT(R5), R0		; Get driver dispatch table
	PUSHL	R5                     		; UCB
	PUSHL	R3                              ; IRP
	CALLS	#2, @DDT$PS_START_2(R0)         ; Call start I/O

  .IF DF CA$_IO_DEBUG              		; for MON version of code
	INCREMENT_IOCNT COUNTER=FP_AFF_IO_STARTED; Count an I/O started 
  .ENDC

	VERIFY_LOCK_OWNERSHIP -			; See whether driver returned
		LOCKADDR=R4, -			; with SCS/IOLOCK8 held
		CPUDB=R2                        ; (result is in R0)

	INCL	IO_COUNT(SP) 			; Count this I/O 
	CMPL	IO_COUNT(SP), #MAX_IO		; More than MAX_IO Limit?
	BGEQU	REQUEUE_FKB 			; Yes, so go requeue

	BLBS	R0,60$				; Branch if Still own spinlock
	MOVL	R4,R0				; Pass lockaddr in R0
	JSB	SMP$ACQNOIPL			; Don't modify IPL when locking

60$:	BBS 	#DEV$V_MSCP, - 			; If MSCP, process next IRP
		UCB$L_DEVCHAR2(R5),PROCESS_NEXT_IRP		
	BRW	PROCESS_NEXT_UCB		; If SCSI, process next UCB


;
; Have dispatched enough I/Os - must give others a chance. Release spinlock
; if needed. Requeue fork thread, leaving FKB_INUSE set.
;
REQUEUE_FKB:
	BLBC	R0,70$				; Branch if don't own spinlock
	MOVL	R4,R0				; Pass lockaddr in R0
	JSB	SMP$RELEASEL 			; Unlock IOLOCK8 spinlock

; 	Reinsert the FKB on the IPL 8 fork queue - no softint needed. 
;
ASSUME CPU$S_SWIQFL/CPU$K_NUM_SWIQS EQ 8 ; Assumption required for offest 
FQH_SIZE=8                      	;  Size of queue header 
OFFSET8=<<8-6>*FQH_SIZE>+4		; Offset to tail of IPL8 fork queue 

70$:    INSQUE  CPU$L_IO_AFF_FKB(R2),- 	; Insert FKB on tail of IPL8 fork Q
                @CPU$Q_SWIQFL+OFFSET8(R2) ; No need to SOFTINT - we are IPL8
;  .IF DF CA$_IO_DEBUG              	; for MON version of code
;	INCL	CPU$L_OVER_START_LIMIT(R2); Count exceeding of limit
;  .ENDC
	BRW	90$			; Clean up and exit


;
; All Done, free fork block and dismiss this fork thread
;
INITIATE_PORT_CPU_DONE:
	MOVAL   CPU$L_IO_AFF_FLAGS(R2), R0	; Get address of flag field

80$: 	EVAX_LDLL R1,(R0)			; Read the flags
	BICL	#CPU$M_IO_AFF_FKB_INUSE,R1 	; Mark forkblock available
	EVAX_STLC R1,(R0) 			; Write it out
	.BRANCH_UNLIKELY 
	EVAX_BEQ R1,80$ 			; Try again on failure

	MOVL	R4,R0				; Pass lockaddr in R0
	JSB	SMP$RELEASEL 			; Unlock IOLOCK8 spinlock

90$:	TSTL	(SP)+				; Clean up iocnt 
	RSB					; RSB to dispatcher

	.DISABLE	LSB


	.PAGE
	.SBTTL	WAIT FOR INTERRUPT OR TIMEOUT AND KEEP CHANNEL
;+
; IOC_STD$PRIMITIVE_WFIKPCH - Wait For Interrupt Or Timeout And Keep Channel
;
; FUNCTIONAL DESCRIPTION:
;
;	This routine is called to software enable interrupts and timeout on
;	a device unit and to keep the channel. This routine can be called at 
;	either fork or device interrupt level.
;
;	The device timeout value is computed and stored in the UCB due time
;	cell, two driver fork context values are saved in the UCB fork block,
;	interrupts and timeout are enabled.
;
;	The driver interrupt resume routine whose procedure value is in
;	ucb->ucb$l_fpc may either use a standard call interface or the
;	traditional JSB interface as determined by the driver's interrupt
;	service routine.
;
;	The driver interrupt timeout routine whose procedure value is in
;	ucb->ucb$ps_toutrout may either use a standard call interface or the
;	traditional JSB interface as described below.
;
; CALLING CONVENTION:
;
;	void  IOC_STD$PRIMITIVE_WFIKPCH (IRP *irp, int64 fr4, UCB *ucb,
;					 int tmo, int restore_ipl)
;
; INPUTS:
;
;	irp		Address of IRP
;	fr4		Arbitrary 64-bit value passed to driver fork routine
;	ucb		Address of UCB
;	tmo		Timeout value in seconds.
;	restore_ipl	IPL to lower to after setting up wait.
;	ucb->ucb$l_fpc	Contains the interrupt resume routine procedure value.
;			This routine is invoked by the driver interrupt
;			service routine or the EXE$TIMEOUT routine.
;	ucb->ucb$ps_toutrout  Contains the interrupt timeout routine procedure
;			value.  This routine is invoked by EXE$TIMEOUT.
;
; OUTPUTS:
;
;	None.
;
; CALLING SEQUENCE FOR INTERRUPT/TIMEOUT ROUTINE:
;
;	The driver interrupt resume routine whose procedure value is in
;	ucb->ucb$l_fpc is invoked from the driver's interrupt service routine.
;	Thus this routine must conform to the calling interface used by
;	the driver's interrupt service routine.
;
;	The driver interrupt timeout routine whose procedure value is in
;	ucb->ucb$ps_toutrout is invoked from the EXE$TIMEOUT routine.
;	EXE$TIMEOUT transparently supports driver timeout routines that either
;	use the new standard call interface or the traditional JSB interface.
;	Similarly, the ucb->ucb$l_fpc routine can use either interface if the
;	driver's interrupt service routine invokes it via EXE_STD$QUEUE_FORK or
;	EXE$QUEUE_FORK.  However, if the ucb->ucb$l_fpc routine is invoked
;	directly by the driver's interrupt service routine, then is must conform
;	to the calling interface used there.
;
;	The interrupt/timeout standard call interface:
;
;		void driver_fork_routine (IRP *irp, int64 *fr4, UCB *ucb);
;
;		irp      is a pointer to the I/O request packet
;		fr4      is an arbitrary 64-bit value.
;		ucb      is a pointer to the unit control block.
;	
;	The interrupt/timeout JSB interface:
;
;		R3      is a pointer to the I/O request packet
;		R4      is an arbitrary 64-bit value.
;		R5      is a pointer to the unit control block.
;
;		R0,R1,R2	May be destroyed.
;-

	$OFFDEF	ARG,<IRP,FR4,UCB,TMO,RESTORE_IPL>
 
	UNIVERSAL_ENTRY	IOC_STD$PRIMITIVE_WFIKPCH,-
				MASK=<^M<R5>>,INPUT=<^M<R16,R17>>
;IOC_STD$PRIMITIVE_WFIKPCH::		;waitfor interrupt/timeout and keep channel
	$COUNT_ENTRY	IOC_STD$PRIMITIVE_WFIKPCH

	MOVL	ARG$_UCB(AP),R5			;R5 = UCB
	ASSUME	ARG$_IRP EQ <1*4>		;direct ref to R16, R17 for
	ASSUME	ARG$_FR4 EQ <2*4>		;full 64-bits
	EVAX_STQ R16,UCB$Q_FR3(R5)		;ucb$l_fr3 = 64-bits P1
	EVAX_STQ R17,UCB$Q_FR4(R5)		;ucb$l_fr4 = 64-bits P2

	BISL	#UCB$M_INT!UCB$M_TIM,-		;enable interrupt and timeout
		UCB$L_STS(R5)
	ADDL3	ARG$_TMO(AP),G^EXE$GL_ABSTIM,-	;set timeout time
		UCB$L_DUETIM(R5)
	BICL	#UCB$M_TIMOUT,UCB$L_STS(R5)	;clear unit timed out
	DEVICEUNLOCK -				;release device lock
		LOCKADDR=UCB$L_DLCK(R5),-
		NEWIPL  =ARG$_RESTORE_IPL(AP),-	;  restore IPL
		CONDITION=RESTORE,-		;  conditionally release spinlock
		PRESERVE=NO			;  no need to preserve R0
	RET					;return

	.PAGE
	.SBTTL	WAIT FOR INTERRUPT OR TIMEOUT AND KEEP CHANNEL (JSB)
;+
; IOC$PRIMITIVE_WFIKPCH - Wait For Interrupt Or Timeout And Keep Channel
;
; FUNCTIONAL DESCRIPTION:
;
;	This routine is a JSB-to-CALL jacket around the IOC_STD$PRIMITIVE_WFIKPCH
;	routine.  Please refer to the description of that routine.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$PRIMITIVE_WFIKPCH
;
; INPUTS:
;
;	R1		Timeout value in seconds.
;	R2		IPL to lower to after setting up wait.
;	R3,R4		Context registers passed to interrupt/timeout routine
;	R5		Address of UCB
;	UCB$L_FPC(R5)	Contains the interrupt/timeout routine procedure value.
;			This procedure value is copied to UCB$PS_TOUTROUT such
;			that it is called by the driver interrupt service
;			routine or the EXE$TIMEOUT routine.
;
; OUTPUTS:
;
;	R0,R1,R2	Are allowed to be destroyed by the documented versions
;			of this routines.  However, the current version only
;			scratches R0 and R1.
;
;-
 
	UNIVERSAL_JSB	IOC$PRIMITIVE_WFIKPCH,INPUT=<R1,R2,R3,R4,R5>,-
					      SCRATCH=<R0,R1>
;IOC$PRIMITIVE_WFIKPCH::		;waitfor interrupt/timeout and keep channel
	$COUNT_ENTRY	IOC$PRIMITIVE_WFIKPCH

	MOVL	UCB$L_FPC(R5),-		;timeout routine same as resume
		UCB$PS_TOUTROUT(R5)	;  (preserves original routine interf)

	EVAX_OR	R31,R4,R17		;R17 (P2) = full 64-bits of R4
	PUSHL	R2			;P5 = restore_ipl
	PUSHL	R1			;P4 = time out value
	PUSHL	R5			;P3 = UCB
	PUSHL	R17			;P2 = full 64-bits of R4
	PUSHL	R3			;P1 = IRP
	CALLS	#5,IOC_STD$PRIMITIVE_WFIKPCH
	RSB				;return

	.PAGE
	.SBTTL	WAIT FOR INTERRUPT OR TIMEOUT AND RELEASE CHANNEL
;+
; IOC_STD$PRIMITIVE_WFIRLCH - Wait For Interrupt Or Timeout And Release Channel
;
; FUNCTIONAL DESCRIPTION:
;
;	This routine is called to software enable interrupts and timeout on
;	a device unit and to release the channel. This routine can be called at 
;	either fork or device interrupt level.
;
;	The device timeout value is computed and stored in the UCB due time
;	cell, two driver fork context values are saved in the UCB fork block,
;	interrupts and timeout are enabled.
;
;	The driver interrupt resume routine whose procedure value is in
;	ucb->ucb$l_fpc may either use a standard call interface or the
;	traditional JSB interface as determined by the driver's interrupt
;	service routine.
;
;	The driver interrupt timeout routine whose procedure value is in
;	ucb->ucb$ps_toutrout may either use a standard call interface or the
;	traditional JSB interface as described below.
;
; CALLING CONVENTION:
;
;	void  IOC_STD$PRIMITIVE_WFIRLCH (IRP *irp, int64 fr4, UCB *ucb,
;					 int tmo, int restore_ipl)
;
; INPUTS:
;
;	irp		Address of IRP
;	fr4		Arbitrary 64-bit value passed to driver fork routine
;	ucb		Address of UCB
;	tmo		Timeout value in seconds.
;	restore_ipl	IPL to lower to after setting up wait.
;	ucb->ucb$l_fpc	Contains the interrupt resume routine procedure value.
;			This routine is invoked by the driver interrupt
;			service routine or the EXE$TIMEOUT routine.
;	ucb->ucb$ps_toutrout  Contains the interrupt timeout routine procedure
;			value.  This routine is invoked by EXE$TIMEOUT.
;
; OUTPUTS:
;
;	None.
;
; CALLING SEQUENCE FOR INTERRUPT/TIMEOUT ROUTINE:
;
;	The driver interrupt resume routine whose procedure value is in
;	ucb->ucb$l_fpc is invoked from the driver's interrupt service routine.
;	Thus this routine must conform to the calling interface used by
;	the driver's interrupt service routine.
;
;	The driver interrupt timeout routine whose procedure value is in
;	ucb->ucb$ps_toutrout is invoked from the EXE$TIMEOUT routine.
;	EXE$TIMEOUT transparently supports driver timeout routines that either
;	use the new standard call interface or the traditional JSB interface.
;	Similarly, the ucb->ucb$l_fpc routine can use either interface if the
;	driver's interrupt service routine invokes it via EXE_STD$QUEUE_FORK or
;	EXE$QUEUE_FORK.  However, if the ucb->ucb$l_fpc routine is invoked
;	directly by the driver's interrupt service routine, then is must conform
;	to the calling interface used there.
;
;	The interrupt/timeout standard call interface:
;
;		void driver_fork_routine (IRP *irp, int64 *fr4, UCB *ucb);
;
;		irp      is a pointer to the I/O request packet
;		fr4      is an arbitrary 64-bit value.
;		ucb      is a pointer to the unit control block.
;	
;	The interrupt/timeout JSB interface:
;
;		R3      is a pointer to the I/O request packet
;		R4      is an arbitrary 64-bit value.
;		R5      is a pointer to the unit control block.
;
;		R0,R1,R2	May be destroyed.
;-

	$OFFDEF	ARG,<IRP,FR4,UCB,TMO,RESTORE_IPL>
 
	UNIVERSAL_ENTRY	IOC_STD$PRIMITIVE_WFIRLCH,-
				MASK=<^M<R5>>,INPUT=<^M<R16,R17>>
;IOC_STD$PRIMITIVE_WFIRLCH::		;waitfor interrupt/timeout and keep channel
	$COUNT_ENTRY	IOC_STD$PRIMITIVE_WFIRLCH

	MOVL	ARG$_UCB(AP),R5			;R5 = UCB
	ASSUME	ARG$_IRP EQ <1*4>		;direct ref to R16, R17 for
	ASSUME	ARG$_FR4 EQ <2*4>		;full 64-bits
	EVAX_STQ R16,UCB$Q_FR3(R5)		;ucb$l_fr3 = 64-bits P1
	EVAX_STQ R17,UCB$Q_FR4(R5)		;ucb$l_fr4 = 64-bits P2

	BISL	#UCB$M_INT!UCB$M_TIM,-		;enable interrupt and timeout
		UCB$L_STS(R5)
	ADDL3	ARG$_TMO(AP),G^EXE$GL_ABSTIM,-	;set timeout time
		UCB$L_DUETIM(R5)
	BICL	#UCB$M_TIMOUT,UCB$L_STS(R5)	;clear unit timed out
	DEVICEUNLOCK -				;release device lock
		LOCKADDR=UCB$L_DLCK(R5),-
		NEWIPL  =ARG$_RESTORE_IPL(AP),-	;  restore IPL
		CONDITION=RESTORE,-		;  conditionally release spinlock
		PRESERVE=NO			;  no need to preserve R0
	.SET_REGISTERS	READ=<R5>
	CALL_RELCHAN				;release channel(P1=R5=UCB)
	RET					;return

	.PAGE
	.SBTTL	WAIT FOR INTERRUPT OR TIMEOUT AND RELEASE CHANNEL (JSB)
;+
; IOC$PRIMITIVE_WFIKPCH - Wait For Interrupt Or Timeout And Keep Channel
;
; FUNCTIONAL DESCRIPTION:
;
;	This routine is a JSB-to-CALL jacket around the IOC_STD$PRIMITIVE_WFIRLCH
;	routine.  Please refer to the description of that routine.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$PRIMITIVE_WFIRLCH
;
; INPUTS:
;
;	R1		Timeout value in seconds.
;	R2		IPL to lower to after setting up wait.
;	R3,R4		Context registers passed to interrupt/timeout routine
;	R5		Address of UCB
;	UCB$L_FPC(R5)	Contains the interrupt/timeout routine procedure value.
;			This procedure value is copied to UCB$PS_TOUTROUT such
;			that it is called by the driver interrupt service
;			routine or the EXE$TIMEOUT routine.
;
; OUTPUTS:
;
;	R0,R1,R2	Are allowed to be destroyed by the documented versions
;			of this routines.  However, the current version only
;			scratches R0 and R1.
;
;-
 
	UNIVERSAL_JSB	IOC$PRIMITIVE_WFIRLCH,INPUT=<R1,R2,R3,R4,R5>,-
					      SCRATCH=<R0,R1>
;IOC$PRIMITIVE_WFIRLCH::		;waitfor interrupt/timeout and keep channel
	$COUNT_ENTRY	IOC$PRIMITIVE_WFIRLCH

	MOVL	UCB$L_FPC(R5),-		;timeout routine same as resume
		UCB$PS_TOUTROUT(R5)	;  (preserves original routine interf)

	EVAX_OR	R31,R4,R17		;R17 (P2) = full 64-bits of R4
	PUSHL	R2			;P5 = restore_ipl
	PUSHL	R1			;P4 = time out value
	PUSHL	R5			;P3 = UCB
	PUSHL	R17			;P2 = full 64-bits of R4
	PUSHL	R3			;P1 = IRP
	CALLS	#5,IOC_STD$PRIMITIVE_WFIRLCH
	RSB				;return

	.PAGE
	.SBTTL	CONVERT DEVICE NAME AND UNIT
;+
; IOC$CVT_DEVNAM - Convert device name and unit
;
; This routine is called to convert a device name and unit number to a physical
; device name string. It can be called from EXEC or KERNEL mode.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$CVT_DEVNAM
;
; INPUTS:
;
;	The caller is assumed to have PROBEd the output buffer for write access.
;	The I/O data base is locked for read access.  This means that the caller
;	owns the I/O data base mutex and/or is at IPL SYNCH or higher.
;
;	R0 = length of output buffer.
;	R1 = address of output buffer.
;	R4 = name string formation mode, one of:
;	    -2 (DVI$_DISPLAY_DEVNAM) -- a name suitable for displays
;		but not suitable for $ASSIGN
;		    if DEV$V_NNM and node available
;		    then
;			if allocation class
;			then
;			    if local port allocation class
;			    then
;				return "$alloclass$ddAn: (host1 PKc[, host2])"
;			    else
;				if file oriented device
;				then
;				    return "$alloclass$ddcn: (host1[, host2])"
;				else
;				    return "node$ddcn"
;			else
;			    return "node$ddcn"
;		    else
;		    	return ddcn
;
;	    -1 (DVI$_DEVNAM) -- a name suitable for displays
;		    if DEV$V_NNM and node available
;		    then
;			if port allocation class
;		    	then
;			    return "_$alloclass$ddAn:"
;		    	else
;			    if cluster and file-oriented device
;			    then
;			    	return "_node$ddcn:"
;			    else
;			    	return "_ddcn:"
;		    else
;		    	return "_ddcn:"
;
;	     0 (DVI$_FULLDEVNAM) -- a name with appropriate node information
;		    if DEV$V_NNM and node available
;		    then
;			if port allocation class
;			then
;			    return "_$alloclass$ddAn:"
;			else
;			    if allocation class and file-oriented device
;			    then
;				return "_$alloclass$ddcn:"
;			    else
;				return "_node$ddcn:"
;		    else
;			return "_ddcn:"
;
;	     1 (DVI$_ALLDEVNAM) -- a name with allocation class information
;		    if DEV$V_NNM and node available
;		    then
;			if (port or node) allocation class
;			then
;			    return "_$alloclass$ddAn:"
;			else
;			    return "_node$ddcn:"
;		    else
;			return "_ddcn:"
;
;	     2 (no GETDVI item code) -- an old fashioned name
;		return "_ddcn:"
;
;	     3 (no GETDVI item code) -- a secondary path name for displays
;		same as -1 except secondary path name returned
;
;	     4 (no GETDVI item code) -- path controller name for displays
;		same as -1 except no unit number is appended
;
;	     Note: if the node name string is null, node$ is not returned.
;
;	R5 = address of device UCB.
;
; OUTPUTS:
;
;	The device name and unit number are converted and stored in the specified
;	output buffer. The following register values are returned:
;
;		R0 = Final conversion status.
;			SS$_NORMAL or
;			SS$_BUFFEROVF (an alternate success status which 
;				indicates that the supplied buffer could not
;				hold the device name string)
;		R1 = Length of conversion string.  R1 = 0 if the alternate
;		     path name was requested but none exists.
;-
 
 
	UNIVERSAL_JSB	IOC$CVT_DEVNAM,-
			INPUT=<R0,R1,R4,R5>,-
			OUTPUT=<R0,R1>

;IOC$CVT_DEVNAM::			;Convert device name and unit
					
;	$COUNT_ENTRY IOC$CVT_DEVNAM	; Debug counting. Opps can't call this 
;					;  from EXEC mode code.
	CALL_CVT_DEVNAM			; Push parms and call STD routine.
	RSB				; Return to caller. 

	.PAGE
	.SBTTL	CONVERT DEVICE NAME AND UNIT
;+
; IOC_STD$CVT_DEVNAM - Convert device name and unit
;
; This routine is called to convert a device name and unit number to a physical
; device name string.
;
; CALLING SEQUENCE:
;       Status = IOC_STD$CVT_DEVNAM (BUFFSIZE, BUFF, FORMMODE, UCB, CONVLEN) 
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;	ARG$_BUFFSIZE(AP)	- Length of output buffer, by value
;	ARG$_BUFF(AP)		- Output buffer
;	ARG$_FORMMODE(AP)	- Name string formation mode, by value.
;       ARG$_UCB(AP)    	- Unit Control Block
; OUTPUTS:
;	ARG$_CONVLEN(AP)	- Length of conversion string
; RETURN VALUE:
;       Status - SS$_NORMAL or SS$_BUFFEROVR (both successful)
$OFFDEF ARG,< -
	BUFFSIZE, -
	BUFF, -
	FORMMODE, -
        UCB, -
	CONVLEN - 
	>
;
; Working storage (offsets from R7)
;
	$OFFSET	0,POSITIVE,< -
		<BINNUM,8>, -		;Binary value to convert to ASCII
		-	; add new working storage cells before this line
		<RESR0>, -		;Result R0
		<RESR1>, -		;Result R1
		<SCRLEN,0> -		;amount of working storage
		<RESR2>, -		;saved R2
		<RESR3>, -		;saved R3
		<RESR4>, -		;saved R4
		>

UNIVERSAL_ENTRY	IOC_STD$CVT_DEVNAM,-
		MASK=<M^<R2,R3,R4,R5,R6,R7,R8>>

;IOC_STD$CVT_DEVNAM::			;Convert device name and unit
	
;	$COUNT_ENTRY IOC_STD$CVT_DEVNAM ; Debug counting. Opps can't call this
					; from EXEC mode code.
	MOVL	ARG$_UCB(AP),R5		; Get UCB input.
	MOVL	ARG$_FORMMODE(AP),R4	; Get form mode input.
	MOVL	ARG$_BUFF(AP),R1	; Get output buffer address input.
	MOVL	ARG$_BUFFSIZE(AP),R0	; Get output buffer size input.

	PUSHR	#^M<R2,R3,R4,R5,R6,R7,R8>	;Save registers
;
; Push a quadword onto the stack.  The quadword will land
; on the stack so that when the POPR at the end of the routine
; is executed, R0 will contain the routine value, and R1 will
; contain the length of the formatted device name.
;
	CLRL	-(SP)			;Put a 0 on the stack
	PUSHL	#SS$_NORMAL		;Put a 1 on the stack
	CLRL	-(SP)			;Init binary number working area.
	CLRL	-(SP)
	ASSUME	SCRLEN EQ 16
	MOVL	SP, R7			;Setup result R0 and R1 pointer in R7.
	CLRL	R8			;Clear flags longword
;
; Precede the device name with a "_" (underscore character) to
; indicate that this is a physical device name.
;
	CMPB	#-2, R4			;Is it DISPLAY_DEVNAM?
	BEQL	10$			;Yes, we don't want the underscore
	MOVZBL	#^A/_/,R3		;Put underscore character in R3
	BSBW	PUTCHAR			;Put it in the output buffer
10$:
;
; Check for a possible nodename. If it exists, determine which format
; of name was requested by the caller.
;
	MOVL	UCB$L_DDB(R5),R6	;Get DDB address
	MOVL	DDB$L_SB(R6),R2		;Get System Block address
	BEQL	LOCAL_NAME		;None, leave
	BBC	#DEV$V_NNM,-		;Branch if nodename not wanted
		UCB$L_DEVCHAR2(R5),LOCAL_NAME
	CMPW	DDB$T_NAME_STR(R6),-	;*** hack ***/
		#^A/PK/			;PK devices are handled as if NNM 
	BEQL	LOCAL_NAME		;  were clear	*** end hack ***

	CASE	R4, -			;Dispatch on type of output requested:
		limit=#-2, displist=< -
		DISPLAY_NAME, -		; -2 ==> $allocls$dev: (node1, node2)
		DEV_NAME, -		; -1 ==> node$dev: for disks, else dev:
		FULL_NAME, -		;  0 ==> $allocls$dev: or node$dev:
		ALLOC_NAME, -		;  1 ==> $allocls$dev: or node$dev:
		LOCAL_NAME, -		;  2 ==> just dev:
		SECONDARY_NAME, -	;  3 ==> secondary path
		DEV_NAME -		;  4 ==> same as -1 sans unit number
		>
	BRB	EXDVNM			; All others are NOPs.

; Here we display the allocation class name for file oriented devices and the
; node$ddcu form otherwise; however, for divices with a port allocation class
; we must always display the allocation class name.  The port allocls check
; happens at ADD_NODE.
FULL_NAME:
	BBC	#DEV$V_FOD, -		;A file oriented device?
		UCB$L_DEVCHAR(R5), -
		ADD_NODE		;Branch if not file oriented device.

ALLOC_NAME:
	MOVL	DDB$L_ALLOCLS(R6), -	;Setup allocation class value
		BINNUM(R7)		; for conversion.
	BEQL	ADD_NODE_NAME		;If none return node+device name.
	BSBW	PUTDOLLAR		;Prepend allocation class with a '$'
	BSBW	PUTNUM			;Convert allocation class number to
					;ASCII and put it in the buffer
	BRB	ADD_DOLLAR		;Append dollar sign to alloc. class
					; and add device name to buffer.

SECONDARY_NAME:
	BBC	#DEV$V_2P,-		;Branch if device not dual-pathed.
		UCB$L_DEVCHAR2(R5),-	; (I.E. there is no secondary path to 
		NO_SECONDARY		; return.)
	MOVL	UCB$L_DP_DDB(R5),R6	;Get secondary DDB.
	BEQL	NO_SECONDARY		;Branch to no sec. path if none.
	MOVL	DDB$L_SB(R6),R2		;Get alternate SB.

DEV_NAME:
	.BRANCH_LIKELY
	BBC	#DDB$V_PAC,-		;Port alloc class set?
		DDB$B_FLAGS(R6), 1$	;  branch if not set
	TSTL	DDB$L_ALLOCLS(R6)	;Is it zero?
	BNEQ	ALLOC_NAME		;  not zero: display alloc class

1$:					;Port alloc class zero or not set
	CMPL	R2,G^SCS$AR_LOCALSB	;Is it the perm local system block?
	BNEQ	ADD_NODE		;Return node+devnam for non-local devs.
	IFNOCLSTR LOCAL_NAME		;Return devnam if not part of a cluster.
	BBC	#DEV$V_FOD, -		;A file oriented device?
		UCB$L_DEVCHAR(R5), -
		LOCAL_NAME		;Branch if not a file oriented device.
					;Its a local disk in a cluster: return
					;node+device name format.
;
; See if new-named device, in which case must return alloclass format. Else
; return node name plus device name.  Copy node name to buffer and
; suffix with a "$" before moving in rest of device name.
;
ADD_NODE:
	TSTL	DDB$L_ALLOCLS(R6)	; A/C 0?
	BEQL	ADD_NODE_NAME		; Yes, use nodename

	.BRANCH_UNLIKELY
	BBS	#DDB$V_PAC,-		; Local port allocation class?
		DDB$B_FLAGS(R6),-	;
		ALLOC_NAME		;   Yes, display allocls.

	; See if MSCP device (meaning CDDB pointer is valid). If not, and
	; no PAC is defined, this cannot be a device with a port allocls

	BBC	#DEV$V_MSCP,-		; MSCP device?
		UCB$L_DEVCHAR2(R5),-
		ADD_NODE_NAME		; Branch if not MSCP device

	; CDDB pointer valid. CDDB$L_ALLOCLS contains server -NODE- allocls.

	MOVL	UCB$L_CDDB(R5),R3	; Not PAC, see if served PAC: get CDDB
	BEQL	ADD_NODE_NAME		; If none, can't be MSCP (paranoia)
	CMPL	DDB$L_ALLOCLS(R6),-	; If device's allocls different from 
		CDDB$L_ALLOCLS(R3)	;  server node's, must be port allocls
	.BRANCH_UNLIKELY
	BNEQ	ALLOC_NAME		; Port Allocls, use  $nnn$ddcu:
					;  else fall thru to node$ddcu:
ADD_NODE_NAME:
	MOVAB	SB$T_NODENAME(R2),R2	;Point to name field
	TSTB	(R2)			;Is the node name null?
	BEQL	LOCAL_NAME		;Skip inserting node name, if its null.
	BSBW	PUTASCIC		;Copy counted ASCII str. to output buf.
ADD_DOLLAR:
	BSBW	PUTDOLLAR		;Append dollar sign to node name
;
; Copy device name to buffer.
;
LOCAL_NAME:
	MOVAB	DDB$T_NAME(R6),R2	;Get address of ASCIC device name.
	BSBW	PUTASCIC		;Copy counted ASCII str. to output buf.
	CMPW	RESR4(R7),#4		;Do we want the unit number?
	BEQL	EXDVNM			;Nope
	MOVZWL	UCB$W_UNIT(R5), -	;Setup device unit number for
		BINNUM(R7)		; converstion to ASCII.
	BSBB	PUTNUM			;Convert unit number to ASCII.
;
; Terminate the device name with a ":" (colon).
;
	MOVZBL	#^A/:/,R3		;Put a ":" in R3
	BSBW	PUTCHAR			;Put the ":" in output buffer
;
; If we are doing DISPLAY_NAME, then go and continue with with the second part.
;
	BLBS	R8, DISPLAY_NAME_CONT	;If LBS in R8 then was in DISPLAY_NAME
;
; Clean up the stack and exit.  The stack has been set up so that
; the proper values will be stored in R0 and R1 by the POPR.
;
EXDVNM:	ADDL	#RESR0,SP		;Remove everything upto result R0
					;from the stack
	POPR	#^M<R0,R1,R2,R3,R4,R5,R6,R7,R8> ;Restore registers
					;R0 contains VMS condition value.
	MOVL	R1,@ARG$_CONVLEN(AP)	;Put conversion length to output.
	RET				;Return to caller.

;
; Come here when the secondary device name was requested but none exists.
;
NO_SECONDARY:
	CLRL	RESR1(R7)		;Clear count of characters
	BRB	EXDVNM			;and return.

;
; Create a name for displays, but one which is not necessarily a valid input
; to $ASSIGN
;
DISPLAY_NAME:
	INCL	R8			;Set low bit (for subroutine) flag
	BRB	FULL_NAME		;Place alloc name (code 1) in the buffer

DISPLAY_NAME_CONT:			;Continue with DISPLAY_NAME processing
	BBC	#DEV$V_FOD, -		;A file oriented device?
		UCB$L_DEVCHAR(R5), -
		EXDVNM			;Done if not file oriented device.
	TSTL	DDB$L_ALLOCLS(R6)	;Is allocation class non-zero?
	BEQL	EXDVNM			;Zero, we are done
	BSBB	PUTSPACE		;Add a space to the buffer
	MOVZBL	#^A"(",R3		;Add the open parens to the buffer
	BSBB	PUTCHAR
	MOVL	DDB$L_SB(R6),R2		;Get System Block address
	MOVAB	SB$T_NODENAME(R2),R2	;Point to the node name field

	CMPL	DDB$L_SB(R6),-		; Is it the local SB?
		SCS$AR_LOCALSB
	BNEQ	20$			; No, just output the SB name
	BBC	#DDB$V_PAC,-		; Done if no port allo-class
		DDB$B_FLAGS(R6),20$	;  even if local system
	TSTL	DDB$L_ALLOCLS(R6)
	BEQL	20$			; Or if allocation class is 0

; Nonzero port allocation class for local system. Say which port, if SCSI.
; Pity there's no class driver entry point that says "return port device name".

	MOVL	UCB$L_PDT(R5),R6	; Get Port DDB via PDT,
	BGEQ	20$			;  quit if no PDT
	ASSUME	SPDT$B_SUBTYP EQ SPDT$B_TYPE+1
	CMPW	#<<DYN$C_SPDT@8>!DYN$C_MISC>,-
		SPDT$B_TYPE(R6)		; SPDT?
	BNEQ	20$			; Not SPDT, forget it
	MOVL	SPDT$L_PORT_UCB(R6),R6	; Get UCB for SCSI port
	MOVL	UCB$L_DDB(R6),R6	; Get DDB for SCSI port
	BSBB	PUTASCIC		; Copy the node name
	BSBB	PUTSPACE		; Add a space
	MOVAB	DDB$T_NAME(R6),R2	; Point to port name

20$:	BSBB	PUTASCIC		; Output system or port DDB name
	BBC	#DEV$V_2P,-		;Branch if device not dual-pathed.
		UCB$L_DEVCHAR2(R5),-	; (I.E. there is no secondary path to 
		ENDPARENS		; return.)
	MOVL	UCB$L_DP_DDB(R5),R6	;Get secondary DDB.
	BEQL	ENDPARENS		;Branch to no sec. path if none.
	MOVL	DDB$L_SB(R6),R2		;Get alternate SB.
	BEQL	ENDPARENS		;Shouldn't be...
	MOVZBL	#^A",",R3		;Add the comma to the buffer
	BSBB	PUTCHAR
	BSBB	PUTSPACE		;Add a space to the buffer
	MOVAB	SB$T_NODENAME(R2),R2	;Point to the node name field
	BSBB	PUTASCIC		;Copy the node name
ENDPARENS:
	MOVZBL	#^A")",R3		;Add the close parens to the buffer
	BSBB	PUTCHAR
	BRB	EXDVNM			;All done

;++
; The following code is a local subroutine to convert binary to ASCII and
; put the ASCII equivalent in the output name buffer.
;
; Inputs:
;
;	BINNUM(R7)	binary number to be converted (a quadword with high 
;			longword zeroed
;
; Outputs:
;	The number at BINNUM(R7) is converted to ASCII and stored in the
;	device name buffer.
;--
PUTNUM:	.JSB_ENTRY	INPUT=<R0,R1,R7>,OUTPUT=<R0,R1>,SCRATCH=<R3>

	.DISABLE FLAGGING		;Suppress RUNTIMSTK diagnostics
					; also suppresses QUADMEMREF on EDIV
	MNEGB	#1, R3			;Get end-of-number marker.
10$:	MOVZBL	R3, -(SP)		;Move digit/marker to scratch.
	EDIV	#10, BINNUM(R7), -	;Divide number by 10, overwrite number
		BINNUM(R7), R3		;with quotient, put remainder in R3.
	BNEQ	10$			;If quotient not zero, go save this 
					; digit and get the next one.
;
; Get digits -- most significant first (then saved ones), convert them to
; ASCII, and put them in the output buffer
;
50$:	ADDB	#^A/0/, R3		;Convert binary digit to ASCII
	BSBB	PUTCHAR			;Copy digit to output buffer
	CVTLB	(SP)+, R3		;Get another digit
	BGEQ	50$			;Branch if the end
	RSB
	.ENABLE FLAGGING		;Re-enable diagnostics

;++
; The following code is a local subroutine to copy a counted ASCII string 
; to the output name buffer.
;
; Inputs:
;
;	R2	Beginning address of a counted ASCII string
;
; Outputs:
;	The counted ASCII string pointed to by R2 is copied to the device
;	name buffer.
;--
PUTASCIC:
	.JSB_ENTRY	INPUT=<R0,R1,R2,R7>,OUTPUT=<R0,R1,R2>,SCRATCH=<R3,R4>

	MOVZBL	(R2)+, R4		;Get counted string length.
	BEQL	90$			;If no characters, leave.
5$:	MOVB	(R2)+, R3		;Move one byte to output buffer.
	BSBB	PUTCHAR			;Put the character in the output buffer.
	SOBGTR	R4, 5$			;Branch if more to copy.
90$:	RSB				;All done, return.

;++
;
; The following code is a local subroutine to place a given
; byte in the output buffer.  A count is kept of all characters
; placed in the output buffer.  If the output buffer is full,
; the byte is not copied, the count is not increased, and the
; return status for IOC$CVT_DEVNAM is changed to SS$_BUFFEROVF 
; (an alternate success status).
;
; Inputs:
;	R0	Count of unstored character slots remaining in output buffer
;	R1	Address of next unused character slot in output buffer
;	R3	Character to be placed in the buffer
;
; Implicit inputs:
;	RESR0(R7)	longword holding final IOC$CVT_DEVNAM status
;	RESR1(R7)	longword holding final IOC$CVT_DEVNAM count of
;				characters stored in the buffer (to be
;				returned in R1
;
; Outputs:
;	None.
;
; Implicit outputs:
;	If R0 >= zero:
;		R0		<== R0 - 1
;		(R1)		<== R3
;		R1		<== R1 + 1
;		RESR1(R7)	<== RESR1(R7) + 1
;	otherwise:
;		RESR0(R7)	<== SS$_BUFFEROVF

;++
; PUTSPACE is an internal routine which is the equivalent of:
;
;	MOVZBL	#^A" ", R3
;	BSBB	PUTCHAR
;--
PUTSPACE:
	.JSB_ENTRY	INPUT=<R0,R1,R7>,OUTPUT=<R0,R1>,SCRATCH=<R3>
	MOVZBL	#^A" ",R3		;Add a space to the buffer

	.DSABL	FLAGGING	; Branch between routines from routine PUTSPACE
	BRB	PUTCHAR
	.ENABL	FLAGGING	; Branch between routines from routine PUTSPACE

;++
; PUTDOLLAR is an internal routine which is the equivalent of:
;
;	MOVZBL	#^A/$/, R3
;	BSBB	PUTCHAR
;--
PUTDOLLAR:
	.JSB_ENTRY	INPUT=<R0,R1,R7>,OUTPUT=<R0,R1>,SCRATCH=<R3>
	MOVZBL	#^A/$/, R3	;Setup to put "$" in output buffer.

PUTCHAR:
	.JSB_ENTRY	INPUT=<R0,R1,R3,R7>,OUTPUT=<R0,R1>
	DECL	R0			;Decrease characters remaining count.
	BLSS	90$			;Branch if no more characters remaining.
	MOVB	R3, (R1)+		;Copy character to output buffer
	INCL	RESR1(R7)		;Count characters stored
	RSB				;Return

90$:	MOVZWL	#SS$_BUFFEROVF, -	;Set buffer overflow status
		RESR0(R7)
	RSB


	.PAGE
	.SBTTL	BROADCAST TO A TERMINAL
;+
; IOC$BROADCAST
;
;	This routine will allow driver fork processes to broadcast a
;	given message to given terminal.  The broadcast request is
;	dispatched to the proper terminal and control returns immediately
;	to the caller.  Some time later the broadcast will complete, and
;	at that time all the necessary post-processing will be done.
;
;	This routine does not implement all the features of the $BRDCST system
;	service, but only the bare minimum necessary to send a message to a 
;	single terminal.  For more information about the terminal broadcast
;	mechanism, see the module SYSBRDCST.
;
;	Do not use this routine to broadcast messages to remote terminals.  
;	This has not worked since V4.0 and will result in the loss of pool.
;
;
; Input:
;
;	R1 = Message length
;	R2 = Message address
;	R5 = Address of target terminal's UCB
;
; Implicit input:
;
;	IPL$_ASTDEL  <=  CURRENT_IPL  <=  IPL$_POOL
;
; Output:
;
;	None.  The contents of R1 .. R5 are preserved across the call.
;
; Routine value:
;
;	SS$_NORMAL	- The broadcast completed successfully.
;	SS$_INSFMEM	- Insufficient dynamic nonpaged pool for the request.
;	SS$_ILLIOFUNC	- The specified UCB does not belong to a terminal.
;			  (Therefore a BROADCAST is an illegal I/O function.)
;-
	UNIVERSAL_JSB	IOC$BROADCAST,-
			INPUT=<R1,R2,R5>,-
			OUTPUT=<R0>,-
			PRESERVE=<R1>
;IOC$BROADCAST::				; Broadcast to a terminal

	$COUNT_ENTRY IOC$BROADCAST  	; Debug counting.
	CALL_BROADCAST SAVE_R1=NO	; Push parms and call STD routine.
	RSB				; Return to caller with R1 preserved.

	.PAGE
	.SBTTL	BROADCAST TO A TERMINAL
;+
; IOC_STD$BROADCAST
;
; This routine will allow driver fork processes to broadcast a
; given message to given terminal.  The broadcast request is
; dispatched to the proper terminal and control returns immediately
; to the caller.  Some time later the broadcast will complete, and
; at that time all the necessary post-processing will be done.
;
; This routine does not implement all the features of the $BRDCST system
; service, but only the bare minimum necessary to send a message to a 
; single terminal.  For more information about the terminal broadcast
; mechanism, see the module SYSBRDCST.
;
; Do not use this routine to broadcast messages to remote terminals.  
; This has not worked since V4.0 and will result in the loss of pool.
;
; CALLING SEQUENCE:
;       Status = IOC_STD$BROADCAST (MSGLEN, MSG, UCB) 
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;	ARG$_MSGLEN(AP)	- Length of message, by value
;	ARG$_MSG(AP)	- Message
;       ARG$_UCB(AP)    - Unit Control Block
; OUTPUTS:
;	None
; RETURN VALUE:
;       Status - Standard VMS condition value
$OFFDEF ARG,< -
	MSGLEN, -
	MSG, -
        UCB -
	>

SAVED_R0 = 0				;.
SAVED_R1 = 4				; .
SAVED_R2 = 8				; .
SAVED_R3 = 12				; . Symbolic offsets to saved registers
SAVED_R4 = 16				; .
SAVED_R5 = 20				;.

UNIVERSAL_ENTRY	IOC_STD$BROADCAST,-
		MASK=<M^<R2,R3,R4,R5>>
;IOC_STD$BROADCAST::			; Broadcast to a terminal

	$COUNT_ENTRY IOC_STD$BROADCAST	; Debug counting.

	MOVL	ARG$_MSG(AP),R2		; Get message input.
	MOVL	ARG$_MSGLEN(AP),R1	; Get message length input.
	MOVL    ARG$_UCB(AP),R5		; Get UCB input.
	
	MOVZWL	#SS$_ILLIOFUNC,R0	; Assume device not a terminal
	BBC	#DEV$V_TRM,-		; Branch if not a terminal
		UCB$L_DEVCHAR(R5),30$	; 
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save R0 .. R5
	ADDL2	#TTY$K_WB_LENGTH,R1	; Calculate the total pool required
	MOVZWL	#SS$_INSFMEM,SAVED_R0(SP); Assume allocation failure
	JSB	G^EXE$ALONONPAGED	; Allocate the pool
	BLBC	R0,20$			; Exit if error
	;
	; Fill in the Terminal Write Packet (TWP).
	; Note that G^EXE$ALONONPAGED the pool size
	; in R1 and the pool address in R2.
	;
	MOVW	R1,TTY$W_WB_SIZE(R2)	; Set TWP size
	MOVB	#DYN$C_TWP,-		; Set TWP structure type
		TTY$B_WB_TYPE(R2)	;
	MOVAB	TTY$L_WB_DATA(R2),-	; Set address of message start
		TTY$L_WB_NEXT(R2)	;
	ADDL3	SAVED_R1(SP),-		; Set address of message end
		TTY$L_WB_NEXT(R2),-	;
		TTY$L_WB_END(R2)	;
	MOVAB	B^END_BROADCAST,-	; Set callback address
		TTY$L_WB_RETADDR(R2)	;
	CLRL	TTY$L_WB_IRP(R2)	; Clear pointer to associated IRP
	PUSHL	R2			; Save TWP address
	MOVC3	4+SAVED_R1(SP),-	; Copy the message text to the TWP
		@4+SAVED_R2(SP),-	; (note the stack depth changed)
		TTY$L_WB_DATA(R2)	;
 	POPL	R5			; Get TWP address
	MOVL	SAVED_R5(SP),R4		; Get UCB address
	MOVZWL	#SS$_NORMAL,SAVED_R0(SP); Assume success 
	MOVL	#1,R3			; Request refresh of read prompt
 	MOVB	UCB$B_FLCK(R4),-	; Device fork IPL
		TTY$B_WB_FLCK(R5)	;

	FORK	CONTINUE=20$		; Create fork, but continue at 20$

	;
	; Queue the broadcast request to the terminal.
	; The disposition of the broadcast request will be determined
	; by the contents of TTY$L_WB_END.  Note that if the request is
	; accepted by a remote terminal, or is rejected outright, the
	; TWP is no longer needed, and may be deallocated.  The TTY$L_WB_END
	; field of the TWP will contain one of the following values:
	;
	;	System address:	request accepted by TTDRIVER
	;	1:		request accepted by RTTDRIVER
	;	2:		request rejected
	;
	MOVB	S^#SPL$C_QUEUEAST,-	; Set the TWP fork LOCK (for later use)
		TTY$B_WB_FLCK(R5)	;
	; EXE_STD$ALTQUEPKT (IRP(R5), UCB(R4))
					; Push input parameters...
        PUSHL   R4              	;  Unit Control Block,
        PUSHL   R5              	;  I/O Request Packet. (TWP address)
        CALLS   #2,G^EXE_STD$ALTQUEPKT  ; Queue the request to the terminal
	TSTL	TTY$L_WB_END(R5)	; Check for rejection
	BLSS	10$
	; COM_STD$DRVDEALMEM (TWP(R5))
					; Push input parameter...
        PUSHL   R5                      ;  Block to be dealocated.
        CALLS   #1,G^COM_STD$DRVDEALMEM ; Deallocate the TWP
					; Do not save R1 or R0.
10$:	RSB	

;+
; Return here after queueing the fork.
;-
20$:	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Restore the registers
30$:	RET				; Return to caller.

;
; The following code performs all of the necessary broadcast post-processing.
; This entry point is FORKed to at IPL IPL$_QUEUEAST from the terminal driver.
; The fork block is the TWP.
;
	FORK_ROUTINE	-
END_BROADCAST				; Post-processor for broadcast requests

	MOVL	R5,R0			; Copy TWP address
	JSB	G^EXE$DEANONPAGED	; Deallocate the TWP
	RSB				; Return


	.PAGE
	.SBTTL	BROADCAST EMERGENCY MESSAGE TO CONSOLE
;+
; IOC$CONBRDCST
;
;	This routine will allow emergency messages to be put on the console
;	terminal.  Some time later the broadcast will complete, and
;	at that time all the necessary post-processing will be done.
;
; Input:
;
;	R1 = Message length
;	R2 = Message address
;
; Implicit input:
;
;	IPL$_ASTDEL  <=  CURRENT_IPL
;
;	A dedicated TWP block must immediately preced the message.
;	The low bit of the first byte of the TWP is assumed to remain clear
;	while it is in use.
;
; Output:
;
;	None.  The contents of R1 .. R5 are preserved across the call.
;
; Routine value:
;
;	SS$_NORMAL	- The broadcast completed successfully.
;-

SAVED_R0 = 0				;.
SAVED_R1 = 4				; .
SAVED_R2 = 8				; .
SAVED_R3 = 12				; . Symbolic offsets to saved registers
SAVED_R4 = 16				; .
SAVED_R5 = 20				;.

	UNIVERSAL_JSB	IOC$CONBRDCST,INPUT=<R1,R2>,OUTPUT=<R0>,PRESERVE=<R1>,-
			C2J_NAME=IOC_STD$CONBRDCST
;IOC$CONBRDCST::				; Broadcast to a terminal
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save R0 .. R5
	MOVL	G^OPA$AR_UCB0,R5	; Get the console terminal UCB
	SUBL2	#TTY$K_WB_LENGTH,R2	; Retreat to the start of the TWP
	;
	; Fill in the Terminal Write Packet (TWP).
	;
	MOVW	R1,TTY$W_WB_SIZE(R2)	; Set TWP size
	MOVB	#DYN$C_TWP,-		; Set TWP structure type
		TTY$B_WB_TYPE(R2)	;
	MOVAB	TTY$L_WB_DATA(R2),-	; Set address of message start
		TTY$L_WB_NEXT(R2)	;
 	ADDL3	SAVED_R1(SP),-		; Set address of message end
		TTY$L_WB_NEXT(R2),-	;
		TTY$L_WB_END(R2)	;
 	MOVAB	B^END_CONBRDCST,-	; Set callback address
		TTY$L_WB_RETADDR(R2)	;
	CLRL	TTY$L_WB_IRP(R2)	; Clear pointer to associated IRP
	MOVB	UCB$B_FLCK(R5),-	; Use OPA0 fork lock
		TTY$B_WB_FLCK(R2)	;
	MOVL	#1,R3			; Request refresh of read prompt
	MOVL	R2,R5			; Use TWP as fork block

	FORK	CONTINUE=10$		; Create fork thread and continue at 10$

	MOVB	S^#SPL$C_QUEUEAST,-	; Set the TWP fork LOCK (for later use)
		TTY$B_WB_FLCK(R5)	;
 	PUSHL	R5			; Save TWP address
	;
	; Queue the broadcast request to the terminal.
	;
	MOVL	R5,R3			; Put TWP address in R3
	MOVL	G^OPA$AR_UCB0,R5	; Get the console terminal UCB
	BSBW	EXE$ALTQUEPKT		; Queue the request to the terminal
	POPL	R0			; Remove TWP address from the stack
	TSTL	TTY$L_WB_END(R0)	; Check for rejection
	BNEQ	5$			; Branch if request rejected
	MNEGL	#1,(R0)			; Mark the TWP free
5$:	RSB				; Return
;+
; Return here after queueing the fork.
;-
10$:	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Restore the registers
	MOVL	#SS$_NORMAL,R0		; Indicate success
	RSB				; Return


;
; The following code performs all of the necessary broadcast post-processing.
; This entry point is FORKed to at IPL IPL$_QUEUEAST from the terminal driver.
; The fork block is the TWP.
;
	FORK_ROUTINE	-
END_CONBRDCST				; Post-processor for broadcast requests
	MNEGL	#1,(R5)			; Mark the TWP free
	RSB


	.PAGE
	.SBTTL	SCAN THE I/O DATA BASE
;+
; IOC$SCAN_IODB - Scan the I/O data base and return next block.
;
; This routine is called to scan the device lists in the IO data base and
; return a pointer to the next block in the list.  Context is kept in R11
; and by using back pointers.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$SCAN_IODB
;
; INPUTS:
;
;	The I/O data base is locked for read access.  This means that the caller
;	owns the I/O data base mutex and/or is at IPL SYNCH or higher.
;
;		R11 =  0 implies first call
;		R11 <> 0 indicates that R11 is pointer to current DDB
;		R10 =  0 implies end of UCB chain
;		R10 <> 0 indicates that R10 is pointer to current UCB
;
; OUTPUTS:
;
;		R0  = Success status.
;		R10 = Pointer to UCB
;		R11 = Pointer to DDB
;
;	All other registers preserved.
;
;-
 
	UNIVERSAL_JSB	IOC$SCAN_IODB,INPUT=<R10,R11>,OUTPUT=<R0,R10,R11>
;IOC$SCAN_IODB::

	MOVL	#1,R0			; Success
	TSTL	R11			; Initial condition?
	BEQL	50$			; Yes
	TSTL	R10			; End of chain?
	BEQL	10$			; Yes
	MOVL	UCB$L_LINK(R10),R10	; Get next UCB
	BEQL	10$			; None
	RSB

10$:	TSTL	DDB$L_LINK(R11)		; At end of DDB chain?
	BEQL	30$			; Yes
	MOVL	DDB$L_LINK(R11),R11	; No, get next one
20$:	MOVL	DDB$L_UCB(R11),R10	; Pick up first UCB
	BEQL	10$			; None, get next DDB
	RSB

30$:	MOVL	DDB$L_SB(R11),R11	; Get back to parent system block
40$:	MOVL	SB$L_FLINK(R11),R11	; Get next system block
	CMPL	R11,#SCS$GQ_CONFIG	; End of chain?
	BNEQ	60$			; No
	DECL	R0
	RSB

50$:	MOVL	G^SCS$GQ_CONFIG,R11	; Pick up first system block
60$:	TSTL	SB$L_DDB(R11)		; Is there a DDB chain?
	BEQL	40$			; No, go try next SB
	MOVL	SB$L_DDB(R11),R11	; Yes, get the first DDB
	BRB	20$


	.PAGE
	.SBTTL	SCAN THE I/O DATA BASE BOTH PRIMARY & SECONDARY PATHS
;+
; IOC$SCAN_IODB_2P
;
; This routine is called to scan the device lists in the IO data base and
; return a pointer to the next block in the list.  Context is kept in R10
; and R11 and by using back pointers.
;
; SCAN_IODB_2P differs from SCAN_IODB in that it will scan both the primary
; and secondary UCB chain for each DDB.  This means that if a device is
; dual-pathed, SCAN_IODB_2P will return the address of its UCB twice, once in
; the context of the primary controller and once in the context of the
; secondary.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$SCAN_IODB_2P
;
; INPUTS:
;
;	The I/O data base is locked for read access.  This means that the caller
;	owns the I/O data base mutex and/or is at IPL SYNCH or higher.
;
;	R10, R11 can be set by caller to affect the scan, setting R11 = 0 starts
;	scan over at the beginning, setting R10 = 0 moves the scan on the next
;	DDB. Note however R9 should never be set by the caller.
;
;		R11 =  0 implies first call
;		R11 <> 0 indicates that R11 is pointer to current DDB
;		R10 =  0 implies end of UCB chain
;		R10 <> 0 indicates that R10 is pointer to current UCB
;		R9  =  1 implies primary   path chain being processed
;		R9  =  2 implies secondary path chain being processed
;
; OUTPUTS:
;
;		R0  = Success status.
;		R9  = Primary/Secondary path currently being processed context
;		R10 = Pointer to UCB
;		R11 = Pointer to DDB
;
;	All other registers preserved.
;-
 
	UNIVERSAL_JSB	IOC$SCAN_IODB_2P,INPUT=<R9,R10,R11>,-
			OUTPUT=<R0,R9,R10,R11>,-
			C2J_NAME=IOC_STD$SCAN_IODB_2P
;IOC$SCAN_IODB_2P::

	MOVL	#1,R0			; Success
	TSTL	R11			; Initial condition?
	BEQL	60$			; Yes
	TSTL	R10			; Caller signalled end of chain?
	BEQL	30$			; Yes, done with this DDB
;
; At this point we must decide if we're following the primary or secondary
; chain of UCBs on this DDB.
;
	CMPL	#1,R9			; Are we traversing the primary chain?
	BNEQ	10$			; Branch if we're following secondary
	MOVL	UCB$L_LINK(R10),R10	; Get next UCB on primary chain
	BEQL	20$			; Branch if none to try secondary chain
	RSB				; Else return UCB address to caller
;
; Get next UCB on secondary chain.
;
10$:	MOVL	UCB$L_DP_LINK(R10),R10	; Get next UCB on secondary chain
	BEQL	30$			; Branch if none left
	RSB				; Else return UCB address to caller
;
; No UCBs left on primary chain; traverse secondary chain if present.
;
20$:	MOVL	DDB$L_DP_UCB(R11),R10	; Get first UCB on secondary chain
	BEQL	30$			; Branch if none to try next DDB
	MOVL	#2,R9			; Indicate secondary path being scanned
	RSB				; Else return UCB address to caller
;
; Step to next DDB.
;
30$:	TSTL	DDB$L_LINK(R11)		; At end of DDB chain?
	BEQL	40$			; Yes, try next system block
	MOVL	DDB$L_LINK(R11),R11	; No, get next one
35$:	MOVL	DDB$L_UCB(R11),R10	; Pick up first UCB on primary chain
	BEQL	20$			; None, try for UCB on secondary chain
	MOVL	#1,R9			; Indicate primary path being scanned
	RSB				; Else return UCB address to caller

;
; Step to next system block.
;
40$:	MOVL	DDB$L_SB(R11),R11	; Get back to parent system block
50$:	MOVL	SB$L_FLINK(R11),R11	; Get next system block
	CMPL	R11,#SCS$GQ_CONFIG	; End of chain?
	BNEQ	70$			; No
	DECL	R0			; Signal end of IO scan
	RSB

60$:	MOVL	G^SCS$GQ_CONFIG,R11	; Pick up first system block
70$:	TSTL	SB$L_DDB(R11)		; Is there a DDB chain?
	BEQL	50$			; No, go try next SB
	MOVL	SB$L_DDB(R11),R11	; Yes, get the first DDB
	BRB	35$			; Try for UCB on primary chain


	.PAGE
	.SBTTL	SCAN THE I/O DATA BASE WITH USER-MODE CONTEXT
;+
; IOC$SCAN_IODB_USRCTX - Scan the I/O data base with user-mode context
;			 and return next block in unit order.
;
; This routine is called to scan the device lists in the I/O data base and
; return a pointer to the next block in the list.  Context is kept in R8, R9
; R10 and R11.
;
; SCAN_IODB_USRCTX differs from SCAN_IODB in that it does not always assume that
; the I/O data base remains locked between calls, and it does not rely on
; the accuracy of the context values passed to it.  SCAN_IODB_USRCTX returns
; context values in R8, R9, R10 and R11 to allow subsequent call to SCAN_IODB_USRCTX.
; as long as the I/O data base remains locked against write access.
;
; SCAN_IODB_USRCTX also differs in the order that devices are returned.
; Devices are returned in the order they are in the DDB chain, but for an
; individual DDB chain devices are returned sorted by unit number, not
; using the order within the UCB chain.
;
; This routine is logically two different routines; one to verify an untrusted
; context, and one to return the next item in the database.  It is currently
; one routine to minimize the scope of the changes needed for VMS V5.2.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$SCAN_IODB_USRCTX
;
; INPUTS:
;
;	The I/O data base is locked for read access.  This means that the caller
;	owns the I/O data base mutex and/or is at IPL SYNCH or higher.
;
;	The input parameters differ depending if this is the first call made
;	to this routine since the I/O database lock was taken out.
;
;	First call since I/O Database lock was taken out:
;
;		R11 =  0 starts at beginning of database
;		R11 <> 0 indicates that R11 is pointer to current DDB (untrusted)
;		R8  =  0, not required if R11=0
;
;	Subsequent calls made without releasing I/O Database lock
;
;		R11,R10,R9,R8 - context values return from previous call
;
;		R11 =  Address of last used DDB
;		R10 =  Address of last used UCB
;		R9  =  Unit number of last UCB
;		R8  =  1, if UCB list is sorted by ascending unit number
;		       2 otherwise (R8 contains the value returned from
;				    the previous call to IOC$SCAN_IODB_USRCTX)
;
; OUTPUTS:
;
;		R0  = Success status (0=no more devices, 1=normal, 2=bad DDB)
;		R1  = destroyed
;		R2  = destroyed
;		R3  = destroyed
;		R8  = 1 if UCB chain is sorted, 2 otherwise
;		      R8 is for use in subsequent calls to IOC$SCAN_IODB_USRCTX,
;		      and should not be relied on for other purposes.
;		R9  = (Word) unit number of device.
;		R10 = Pointer to UCB
;		R11 = Pointer to DDB
;
;	All other registers preserved.
;
;-
 
	UNIVERSAL_JSB	IOC$SCAN_IODB_USRCTX,INPUT=<R8,R9,R10,R11>,-
					     OUTPUT=<R0,R8,R9,R10,R11>,-
					     SCRATCH=<R1,R2,R3>,-
					     C2J_NAME=IOC_STD$SCAN_IODB_USRCTX
			
;IOC$SCAN_IODB_USRCTX::
	.ENABL	LSB

;
; Values stored in R8 to determine how to perform subsequent
; searches.  A much more efficient search can be performed if
; the UCBs are sorted, since we can just look at the next
; forward link as long as the I/O Database lock is held.
;
; These values are internal to this routine, except that 0
; represents an initial call.  A caller to IOC$SCAN_IODB_USRCTX
; should clear R8 on the first call, and simply pass back the
; value returned on subsequent calls.
;
INITIAL_CALL  = 0
UCBS_SORTED   = 1
UCBS_UNSORTED = 2

	MOVL	#SS$_NORMAL,R0		; Assume Success
	TSTL	R11			; Initial condition?
	BNEQ	NOT_FIRST		; No, use context
	BRW	GET_FIRST_SB		; Yes, get first SB

NOT_FIRST:
	CASE	R8,displist=< -
		INITIAL, -		; Initial call since losing I/O DB lock
		SORTED,	 -		; Subsequent call with sorted UCBs
		UNSORTED -		; Subsequent call with unsorted UCBs
		>

	MOVL	#SS$_BADPARAM,R0
	RSB	
;
; Initial call since losing I/O Database lock and possibly going to user
; mode.  Therefore we must verify that this DDB is valid.
;
INITIAL:
	MOVL	G^SCS$GQ_CONFIG,R1	; Pick up first system block
20$:	TSTL	SB$L_DDB(R1)		; Is there a DDB chain?
	BEQL	50$			; No, go try next SB
	MOVL	SB$L_DDB(R1),R1		; Get the first DDB 

30$:	CMPL	R1,R11			; Match context DDB?
	BEQL	DDB_VALIDATED		; Yes, DDB context validated.
        TSTL	DDB$L_LINK(R1)		; At end of DDB chain?
	BEQL	40$			; Yes, try next system block
	MOVL	DDB$L_LINK(R1),R1	; No, get next DDB
	BRB	30$

40$:	MOVL	DDB$L_SB(R1),R1		; Get back to parent system block
50$:	MOVL	SB$L_FLINK(R1),R1	; Get next system block
	CMPL	R1,#SCS$GQ_CONFIG	; End of chain?
	BNEQ	20$			; No
	MOVL	#SS$_BADPARAM,R0	; Yes; context DDB is bad
	RSB


; ------------------------------------------------------------
; Logical break in IOC$SCAN_IODB_USRCTX.  Code above validates
; untrusted context values.  Code below scans I/O Database
; ------------------------------------------------------------

;
; UNSORTED_START performs a search against a list when we have
; not yet looked at this UCB chain, and thus we want the 
; absolute lowest unit number.  Essentially this just means
; setting R9 to -1 before going to UNSORTED
;
UNSORTED_START:
	MOVL	#-1,R9

;
; Get here after user-supplied context DDB has been validated.
; Skip UNSORTED_START, since we want to continue "where we
; left off"; i.e. use previously returned unit number.
;
DDB_VALIDATED:

;
; Scan the UCB chain, looking for the lowest unit number >= the
; last unit number used (R9).
;
UNSORTED:
	MOVL	DDB$L_UCB(R11),R10	; Pick up first UCB
	BEQL	GET_NEXT_DDB		; No UCBs for this device
	
	MOVL	#UCBS_SORTED,R8		; Assume list is sorted
	MOVZWL	#-1,R1			; Unit -1 is before anything.
	MOVL	#^X7FFF,R2		; Largest possible unit
	CLRL	R3			; No UCB yet...

UCB_SCAN_LOOP:
;
; Check if this unit number is greater than the last one.  Although this could
; be coded in a simpler fashion with a BGEQ in the normal case (units are
; sorted) it is more efficient to take a branch only on the failure case,
; since this permits more efficient pipelining of subsequent instructions
; in the normal case.
;
	CMPW	UCB$W_UNIT(R10),R1	; Is this unit number >= previous one?
	BLSS	THIS_ISNT_SORTED	; No; list is unsorted

CONTINUE_LOOP:
	MOVW	UCB$W_UNIT(R10),R1	; Store this unit number, both to
					; compare later and for comparisons below

	CMPW	R1,R9			; Compare to last unit returned
	BLEQ	NOT_NEXT_UNIT		; if not larger, this can't be next
	CMPW	R1,R2			; Compare to smallest candidate so far
	BGEQ	NOT_NEXT_UNIT		; This isn't the largest unit

	MOVL	R10,R3			; Store this UCB; it is the best so far!
	MOVZWL	R1,R2			; New "best guess" next unit

NOT_NEXT_UNIT:
	MOVL	UCB$L_LINK(R10),R10	; Get next UCB in chain
	BNEQ	UCB_SCAN_LOOP		; Keep looking until out of UCBs

	TSTL	R3			; Did we find a candidate?
	BEQL	GET_NEXT_DDB		; No, go to next DDB

	MOVL	R3,R10			; Return this UCB
	MOVL	R2,R9			; Return this unit number
					; R8 (sorted flag) and R11 (DDB)
					; already have values
	RSB
;
; The code branches here is the UCB list is found to be unsorted.
; Simply store an "unsorted" flag in R8 and branch back into
; the main loop.
;
THIS_ISNT_SORTED:
	MOVL	#UCBS_UNSORTED,R8	; Store unsorted flag
	BRB	CONTINUE_LOOP		; Continue loop through UCB chain

; ---------------------------------------------------------------

;
; UCB list is sorted; return the next unit number
; by using the forward link from the last UCB.  If at end of UCB
; list, advance to next DDB and check next UCB for being sorted.
;
SORTED:
	MOVL	UCB$L_LINK(R10),R10	; Get next UCB in chain
	BEQL	GET_NEXT_DDB		; No next UCB; get next DDB
	MOVZWL	UCB$W_UNIT(R10),R9	; Store unit number
	RSB
;
; Get the next DDB in the chain.  If no more, check for next SB.
; If no next SB, return an error 
;
GET_NEXT_DDB:
	TSTL	DDB$L_LINK(R11)		; At end of DDB chain?
	BEQL	GET_PARENT_SB		; Yes, go to next system
	MOVL	DDB$L_LINK(R11),R11	; No, get next DDB
	BRB	UNSORTED_START
;
GET_PARENT_SB:
	MOVL	DDB$L_SB(R11),R11	; Get back to parent system block
GET_NEXT_SB:
	MOVL	SB$L_FLINK(R11),R11	; Get next system block
	CMPL	R11,#SCS$GQ_CONFIG	; End of chain?
	BNEQ	CHECK_DDB_CHAIN		; No, look for DDB
	MOVL	#SS$_NOMOREDEV,R0	; Out of devices
	RSB

GET_FIRST_SB:
	MOVL	G^SCS$GQ_CONFIG,R11	; Pick up first system block
CHECK_DDB_CHAIN:
	TSTL	SB$L_DDB(R11)		; Is there a DDB chain?
	BEQL	GET_NEXT_SB		; No, go try next SB
	MOVL	SB$L_DDB(R11),R11	; Yes, get first DDB
	BRW	UNSORTED_START		; Assume list is unsorted

	.DSABL	LSB	


	.PAGE
	.SBTTL	GENERAL WILD CARD MATCHING
;+
; EXE$MATCH_NAME - Wildcard string matching
;
;
; CALLING SEQUENCE:
;
;	JSB	EXE$MATCH_NAME
;
; INPUTS:
;
;       R2 = Length of candidate string.
;	R3 = Address of candidate string.
;	R4 = Length of pattern string.
;	R5 = Address of pattern string.
;
; OUTPUTS:
;
;	R0  True if the strings match
;
;	R1-R5 are destroyed
;
;-
 
	 UNIVERSAL_ENTRY EXE_STD$MATCH_NAME, QUAD_ARGS=TRUE
	 $COUNT_ENTRY    EXE_STD$MATCH_NAME		;# of times thru jacket routine

	 EVAX_LDQ R2,4(AP)
         EVAX_LDQ R3,8(AP)
	 EVAX_LDQ R4,12(AP)
	 EVAX_LDQ R5,16(AP)

         .SET_REGISTERS READ=<R2,R3,R4,R5>		;Input regs for JSB routine
         .SET_REGISTERS WRITTEN=<R1,R2,R3,R4,R5>	;Put scratch regs in preserve mask
         JSB     EXE$MATCH_NAME
         .SET_REGISTERS WRITTEN=<R0>			;Put output regs in preserve mask

         RET

	UNIVERSAL_JSB	EXE$MATCH_NAME,INPUT=<R2,R3,R4,R5>,OUTPUT=<R0>,-
				       SCRATCH=<R1,R2,R3,R4,R5>
;EXE$MATCH_NAME::
	PUSHR	#^M<R6,R7,R8,R9>	; Save registers 
	CLRL	R0			; Assume failure
	CLRL	R6			; Clear saved candidate count
;
; Main scanning loop.
;
10$:	EVAX_SUBQ R4,#1,R4		; Pattern exhausted?
	EVAX_CMPLT R4,R31,R22
	BLBS	R22,30$			; Branch if yes
	MOVZBL	(R5)+,R1		; Get next character in pattern
	CMPB	R1,#^A'*'		; Pattern specifies wild string?
	BEQL	60$			; Branch if yes
	EVAX_SUBQ R2,#1,R2		; Candidate exhausted?
	EVAX_CMPLT R2,R31,R22
	BLBS	R22,50$			; Branch if yes
	CMPB	R1,(R3)+		; Compare pattern to candidate
	BEQL	10$			; Branch if pattern equals candidate
	CMPB	R1,#^A'%'		; Pattern specifies wild character?
	BEQL	10$			; Branch if yes
;
; We have detected a mismatch, or we are out of pattern while there is
; candidate left.  Back up to the last '*', advance a candidate character,
; and try again.
;
20$:	EVAX_SUBQ R6,#1,R6		; Count a saved candidate character
	EVAX_CMPLT R6,R31,R22
	BLBS	R22,50$			; Branch if no saved candidate
	EVAX_ADDQ R7,#1,R7		; Set to try next character
	EVAX_OR	R6,R31,R2		; Restore descriptors to backup point
	EVAX_OR R7,R31,R3
	EVAX_OR	R8,R31,R4		;
	EVAX_OR	R9,R31,R5
	BRB	10$			; Continue testing
;
; Here when pattern is exhausted.
;
30$:	EVAX_BNE R2,20$			; Candidate exhausted?
					; Branch if no
;
; Here to return.
;
40$:	MOVL	#1,R0			; Set success return
50$:	POPR	#^M<R6,R7,R8,R9>	; Restore registers 
	RSB				; Return
;
; We have detected a '*' in the pattern.  Save the pointers for backtracking.
;
60$:	EVAX_BEQ R4,40$			; Pattern null after '*'?
					; Branch if yes
	EVAX_OR R2,R31,R6		; Save descriptors of both strings
	EVAX_OR R3,R31,R7
	EVAX_OR	R4,R31,R8		;
	EVAX_OR	R5,R31,R9
	BRB	10$			; Continue testing


	.PAGE
	.SBTTL	IOC$CTRL_INIT - CALL DRIVER CONTROLLER INIT. ROUTINE

;+
; FUNCTIONAL DESCRIPTION:
;
;	Call device driver controller initialization routine.
;
; CALLING SEQUENCE:
;
;	CALL	IOC$CTRL_INIT( crb, ddb )
;
; INPUTS:
;
;	CRB	Address of CRB
;	DDB	Address of DDB
;
; OUTPUTS:
;
;	R0	SS$_NORMAL if there is no driver specific controller init
;		routine, i.e. DDT$PS_CTRLINIT(ddt) contains 0.  Otherwise, the
;		status returned from the driver controller init routine.
;
;
; CALLING SEQUENCE FOR DRIVER CONTROLLER INIT ROUTINE AT DDT$PS_CTRLINIT_2:
;
;	status  = CTRLINIT (idb, ddb, crb)
;
; INPUTS TO DRIVER CONTROLLER INIT ROUTINE:
;
;	(Step 1 register usage is in parenthesis)
;
;	idb	(R4)	IDB address (differs from VAX where R4 is CSR address)
;		(R5)	IDB address (not documented, but for backwards comp. with VAX)
;	ddb	(R6)	DDB address
;	crb	(R8)	CRB address
;
; OUTPUTS FROM DRIVER CONTROLLER INIT ROUTINE:
;
;	Step 1:  R0 = Status to be returned from IOC$CTRL_INIT
;		 R1 thru R2 may be destroyed.
;	Step 2:  R0 = Status to be returned from IOC$CTRL_INIT
;-


; Define AP relative offsets into argument list for IOC$CTRL_INIT
;
CRB	=  4		;Address of CRB
DDB	=  8		;Address of DDB


	UNIVERSAL_ENTRY	-
IOC$CTRL_INIT	MASK=<^M<R2>>,OUTPUT=<^M<R0>>

	MOVL	DDB(AP),R0			;R0 = DDB address
	MOVL	DDB$PS_DDT(R0),R2		;R2 = DDT address
	MOVL	CRB(AP),R1			;R1 = CRB address
	PUSHL   R1				;CRB (Stack Arguments for CALLS)
	PUSHL   R0				;DDB
	PUSHL	CRB$L_INTD+VEC$L_IDB(R1)	;IDB 
	CALLS   #3, @DDT$PS_CTRLINIT_2(R2)	;call driver ctrl init routine
50$:	RET					;return	status



	.PAGE
	.SBTTL	IOC$UNIT_INIT - CALL DRIVER UNIT INIT. ROUTINE

;+
; FUNCTIONAL DESCRIPTION:
;
;	Call device driver unit initialization routine.
;
; CALLING SEQUENCE:
;
;	CALL	IOC$UNIT_INIT( ucb )
;
;  INPUTS:
;
;	UCB	Address of UCB
;
;  OUTPUTS:
;
;	R0	SS$_NORMAL if there is no driver specific unit init
;		routine, i.e. DDT$PS_UNITINIT(ddt) contains 0.  Otherwise, the
;		status returned from the driver unit init routine.
;
;
; CALLING SEQUENCE FOR DRIVER UNIT INIT ROUTINE AT DDT$PS_UNITINIT_2
;
;	status = UNITINIT (idb, ucb)
;
; INPUTS TO DRIVER UNIT INIT ROUTINE:
;
;	(Step 1 register usage is in parenthesis)
;
;	idb	(R4)	IDB address (differs from VAX where R4 is CSR address)
;	ucb	(R5)	UCB address
;
; OUTPUTS FROM DRIVER UNIT INIT ROUTINE:
;
;	Step 1:	 R0 = Status to be returned from IOC$UNIT_INIT
;		 R1 thru R2 may be destroyed.
;	Step 2:  R0 = Status to be returned from IOC$UNIT_INIT
;-



; Define AP relative offsets into argument list for IOC$UNIT_INIT
;
UCB	=  4		;Address of UCB


	UNIVERSAL_ENTRY	-
IOC$UNIT_INIT	MASK=<^M<R2>>,OUTPUT=<^M<R0>>

	MOVL	UCB(AP),R1			;R1 = UCB address
	MOVL	UCB$L_DDT(R1),R2		;R2 = DDT address
	MOVL	UCB$L_CRB(R1),R0		;R0 = CRB address
	MOVL	CRB$L_INTD+VEC$L_IDB(R0),R0	;R0 = IDB address
	PUSHL	R1				;Push UCB onto stack
	PUSHL	R0				;Push IDB onto stack
	CALLS	#2, @DDT$PS_UNITINIT_2(R2)	;call driver unit init routine
50$:	RET					;return status


	.PAGE
	.SBTTL	IOC$UNIT_DELIVER - CALL DRIVER UNIT DELIVER ROUTINE

;+
; FUNCTIONAL DESCRIPTION:
;
;	Call device driver unit delivery routine.
;
; CALLING SEQUENCE:
;
;	CALL	IOC$UNIT_DELIVER( crb, ddb, unit_num, addr_scratch )
;
;  INPUTS:
;
;	CRB		Address of CRB
;	DDB		Address of IDB
;	UNIT_NUM        Unit number (passed by value)
;	ADDR_SCRATCH	Address of quadword scratch area
;
;  OUTPUTS:
;
;	R0	SS$_NORMAL if there is no driver specific unit delivery
;		routine, i.e. DPT$PS_DELIVER(dpt) contains 0.  Otherwise, the
;		status returned from the driver unit delivery routine.
;
;
; CALLING SEQUENCE FOR DRIVER UNIT DELIVERY ROUTINE AT DPT$PS_DELIVER_2
;
;	status = DELIVER (idb, unit_number, scratch_area, adp)
;
; INPUTS TO DRIVER UNIT DELIVERY ROUTINE:
;
;	(Step 1 register usage is in parenthesis)
;
;			(R3)	IDB address (not documented, but for backwards comp. with VAX)
;	idb		(R4)	IDB address (differs from VAX where R4 is CSR address)
;	unit_number	(R5)	Unit number to be configured
;	scratch_area	(R7)	Address of quadword scratch area
;	adp		(R8)	ADP address
;
; OUTPUTS FROM DRIVER UNIT DELIVERY ROUTINE:
;
;	Step 1:  R0 = Status to be returned from IOC$UNIT_DELIVER
;		 R1 thru R2 may be destroyed.
;	Step 2:  R0 = Status to be returned from IOC$UNIT_DELIVER
;-



; Define AP relative offsets into argument list for IOC$UNIT_DELIVER
;
CRB		=  4		;Address of CRB
DDB		=  8		;Address of DDB
UNIT_NUM	= 12		;Unit number (passed by value)
ADDR_SCRATCH	= 16		;Address of quadword scratch area


	UNIVERSAL_ENTRY	-
IOC$UNIT_DELIVER	MASK=<^M<R2>>,OUTPUT=<^M<R0>>

	MOVL	DDB(AP),R0			;R0 = DDB address
	MOVL	DDB$PS_DPT(R0),R2		;R2 = DPT address
	MOVL	CRB(AP),R1			;R1 = CRB address
	PUSHL	CRB$L_INTD+VEC$PS_ADP(R1)	;ADP (Stack Args for CALLS)
	PUSHL	ADDR_SCRATCH(AP)		;Address of Quad Scratch
	PUSHL	UNIT_NUM(AP)			;Unit number to configure
	PUSHL	CRB$L_INTD+VEC$L_IDB(R1)	;IDB 
	PUSHL	R0				;DDB
	CALLS	#5, @DPT$PS_DELIVER_2(R2)	;call driver unit deliver routine
50$:	RET					;return status


	.PAGE
	.SBTTL	PARSE DEVICE NAME STRING
;+
;
; IOC$PARSDEVNAM - parse device name string
;
;	This routine parses a device name string, checking syntax and
;	extracting node name, allocation class number, and unit number.
;	If device type format is specified, it is converted into the internal
;	compressed format.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$PARSDEVNAM
;
; INPUTS:
;
;	R8 = size of name string
;	R9 = address of name string
;	R10 = flags
;
; OUTPUTS:
;
;	R0 = SS$_NORMAL - valid name string
;	   = SS$_IVDEVNAM - invalid device name string
;	R2 = unit number
;	R3 = length of SCS node name at head of name string
;	     or allocation class number
;	     or device type code
;	R8 = size of name string
;	R9 = address of name string
;	R10 = flags
;	R4 - R7, R11 preserved
;
;-
	UNIVERSAL_JSB	IOC$PARSDEVNAM,INPUT=<R8,R9,R10>,-
				       OUTPUT=<R0,R2,R3,R8,R9,R10>,-
				       SCRATCH=<R1>
;IOC$PARSDEVNAM::

;	$COUNT_ENTRY	IOC$PARSDEVNAM	; Debug counting. Can't call this from 
					;  from EXEC mode code.
	CALL_PARSDEVNAM			; Push parms and call the STD routine.
	RSB				; Return to caller.

	.PAGE
	.SBTTL	PARSE DEVICE NAME STRING
;+
; IOC_STD$PARSDEVNAM - parse device name string
;
; This routine parses a device name string, checking syntax and
; extracting node name, allocation class number, and unit number.
; If device type format is specified, it is converted into the internal
; compressed format.
;
; CALLING SEQUENCE:
;       Status = IOC_STD$PARSDEVNAM (DEVNAMESIZ, DEVNAME, FLAGS, -
;			    UNITNUM, MISC, DEVNAMESIZOUT, DEVNAMEOUT, FLAGSOUT)
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;	ARG$_DEVNAMSIZ(AP)	- Device name string size, by value 
;	ARG$_DEVNAM(AP)		- Device name string
;	ARG$_FLAGS(AP)		- Flags, by value
; OUTPUTS:
;	ARG$_UNITNUM(AP)	- Unit number
;	ARG$_MISC(AP)		- SCS node name or alloc class or device type
;	ARG$_DEVNAMSIZOUT(AP)	- Device name string size
;	ARG$_DEVNAMOUT(AP)	- Device name string
;	ARG$_FLAGSOUT(AP)	- Flags
; RETURN STATUS:
;       Status 	- SS$_NORMAL 	- valid name string
;	     	- SS$_IVDEVNAM - invalid device name string
$OFFDEF ARG,< -
        DEVNAMSIZ, -
        DEVNAM, -
        FLAGS, -
        UNITNUM, -
        MISC, -
        DEVNAMSIZOUT, -
        DEVNAMOUT, -
        FLAGSOUT -
        >
	.ENABLE	LSB

UNIVERSAL_ENTRY	IOC_STD$PARSDEVNAM,-
		MASK=<M^<R2,R3,R4,R5,R6,R8,R9,R10>>
;IOC_STD$PARSDEVNAM::			; Parse device name string

;	$COUNT_ENTRY IOC_STD$PARSDEVNAM	; Debug counting.

	MOVL	ARG$_DEVNAMSIZ(AP),R8	; Get device name string size input.
	MOVL	ARG$_DEVNAM(AP),R9	; Get device name string input.	
	MOVL	ARG$_FLAGS(AP),R10	; Get flags input.

	TSTL	R8			; check name string length
	BEQL	30$			; branch if null - error
	MOVL	R8,R4			; copy name string size and...
	MOVL	R9,R5			;  name string address.
	SUBL3	#1,R9,R6		; default is no node no allocation 
					; class, set pointer before beginning 
					; of the string
	LOCC	#^A'$',R8,(R9)		; scan name for a "$"
	BEQL	10$			; failed to find one - no nodename
	MOVL	R1,R6			; found it, save pointer
10$:	CLRQ	R2			; init unit number and node name
20$:	MOVZBL	(R5),R0			; get next character
	BBC	#6,R0,40$		; br if code 0-^X3F - numeric or $
	BICB	#^X20,R0		; collapse lower case to upper case
	CMPB	R0,#^A'Z'		; possible alphabetic?
	BGTRU	150$			; br if not
	CMPB	R0,#^A'A'		; possible alphabetic?
	BGEQU	70$			; branch if OK - store it
30$:	BRB	150$			; no - error
;
; Non alphabetic - may be numeric or "$"
;
40$:	CMPL	R5,R6			; hit the "$" yet?
	BEQL	50$			; yes, deal with it
	BGTRU	80$			; past it, digits are unit number
	CMPB	R0,#^A'9'		; legal?
	BGTRU	150$			; no, error
	CMPB	R0,#^A'0'		; legal?
	BGEQU	70$			; yes, accept it as alpha
	BRB	150$			; no, error
;
; $ in device name - either node name or allocation class.
;
50$:	SUBL3	R9,R5,R3		; compute length of node name
	BNEQ	70$			; branch if non-null - process the $
;
; Process allocation class number.
;
60$:	INCL	R5			; move over "$" to allocation
	DECL	R4			; class digits.
	BSBB	GETNUMBER		; convert allocation class.
	BLBC	R0,130$			; if error then return failure status
	MOVL	R2,R3			; store requested allocation class.
	BLEQ	150$			; leq zero is not legal.
	BISB	#IOC$M_CLASS,R10	; set allocation class flag
	CMPB	#^A'$',-1(R5)		; was terminator a "$"?
	BNEQ	150$			; if not, invalid device name.
	MOVQ	R4,R8			; reset device name - unit size.
	TSTL	R4			; check remaining string count
	BGTR	20$			; if characters remain, process them.
	BRB	150$			; else, invalid device name.

70$:	MOVB	R0,(R5)+		; store potentially upcased character
	SOBGTR	R4,20$			; any more characters to scan?
;
; End of alpha scan. Make sure we actually got a non-null device name.
;
80$:	SUBL	R4,R8			; subtract unit number from string
	BEQL	150$			; if eql no device name specified
	INCL	R6			; point past $ in node name
	CMPL	R6,R5			; see if we've processed any more chars
	BLSSU	90$			; branch if yes
	BLBS	R10,150$		; branch if physical - not valid
	BBC	#IOC$V_ANY,R10,150$	; or if not generic search for any
	BRB	100$			; node name only - verify end of string
;
; Process unit number and make sure there's no trailing junk.
;
90$:	CLRL	R2			; init unit number to 0
	TSTL	R4			; see if there's anything left
	BLEQ	110$			; branch if not
	BISB	#IOC$M_PHY,R10		; set physical flag
	BSBB	GETNUMBER		; convert unit number
	BLBC	R0,130$			; if error then return failure status
	INCL	R4			; return terminator to string count
100$:	TSTL	R4			; reached end of string?
	BGTR	150$			; branch if not - error
110$:	BBS	#IOC$V_TYPE,R10,190$	; branch if name is a device type
120$:	MOVL	#SS$_NORMAL,R0		; successful parse
130$:	MOVL	R8,@ARG$_DEVNAMSIZOUT(AP);Set device name size output.
	MOVL	R9,@ARG$_DEVNAMOUT(AP)	; Set device name string output.
	MOVL	R10,@ARG$_FLAGSOUT(AP)	; Set flags output.
	MOVL	R2,@ARG$_UNITNUM(AP)	; Set unit number output.
	MOVL	R3,@ARG$_MISC(AP)	; Set misc outputs.
	RET				; and return to caller.
;
; Invalid device name
;
150$:	MOVZWL	#SS$_IVDEVNAM,R0	; set invalid device name
	BRW	130$			; return to caller.
;
; Parse device type name. We do this last because all the regular device
; name validation is necessary anyway. Now we just have to do the
; additional checks and pack the characters.
;
190$:	TSTL	R3			; check if we saw node or alloc class
	BNEQ	150$			; branch if so - not valid
	SUBL3	R9,R5,R0		; compute total length of string
	SUBL	R8,R0			; compute length of unit number string
	CMPL	R0,#2			; must be two digits
	BNEQ	150$			; branch if not - not valid
	MOVL	R9,R5			; copy name address again
	CMPL	R8,#2			; check minimum name length
	BLSSU	150$			; too short - out
	SUBB3	#^A'A'-1,(R5)+,R0	; get char and convert to compressed
	INSV	R0,#17,#5,R3		; store compressed code
	SUBB3	#^A'A'-1,(R5)+,R0	; get char and convert to compressed
	INSV	R0,#12,#5,R3		; store compressed code
	CMPL	R8,#3			; check how many chars left
	BGTRU	150$			; string was longer than 5 - error
	BLSSU	200$			; short - don't take 3rd alpha
	SUBB3	#^A'A'-1,(R5)+,R0	; get char and convert to compressed
	INSV	R0,#7,#5,R3		; store compressed code
200$:	ADDL	R2,R3			; add in unit number
	BICB	#IOC$M_PHY,R10		; clear physical flag
	BRW	120$			; done

;+
; GETNUMBER - Local routine to convert ASCII to integer
;
; Inputs:
;
;	R2	assumed zero
;	R4	size of string
;	R5	starting address of string
;
; Outputs:
;
;	R0	SS$_NORMAL or SS$_IVDEVNAM
;	R2	converted number
;	R4	size of string with number and terminator character removed
;	R5	address of first character after number terminator character
;-

GETNUMBER:
	.JSB_ENTRY	INPUT=<R2,R4,R5>,OUTPUT=<R0,R2,R4,R5>

	SOBGEQ	R4,160$			; count off a character

170$:	CMPL	R2,#32768		; check number value
	BGEQU	175$			; branch if not valid
	MOVZWL	#SS$_NORMAL,R0		; R0 = success
	RSB				; return to caller.

175$:	MOVZWL	#SS$_IVDEVNAM,R0	; R0 = invalid device name
	RSB


160$:	MOVZBL	(R5)+,R0		; get next character.
	SUBB	#^A'0',R0		; base it at decimal digits.
	BLSSU	170$			; branch if not a decimal digit.
	CMPB	R0,#9			; is it a digit?
	BGTRU	170$			; branch if not a decimal digit.
	MULL	#10,R2			; scale current unit number by 10
	ADDL	R0,R2			; add new digit to accumulation.
	SOBGEQ	R4,160$			; count off a character
	BRW	170$



	.PAGE
	.SBTTL	SEARCH I/O DATABASE FOR DEVICE
;+
; IOC$SEARCHINT - internal I/O database search
;
;	This routine searches the I/O database for the specified device, using
;	the specified search rules. Depending on the search, a lock may or may
;	not be taken out on the device when it is found.
;
;	Note! While this routine is non-paged and therefore may be called at
;	elevated IPL, the device locking and auditing code it calls is not.
;	Therefore, calls from elevated IPL must specify IOC$V_ANY.
;	(IOC$V_ALLOC and IOC$V_MOUNT are not available.)
;
; CALLING SEQUENCE:
;
;	JSB	IOC$SEARCHINIT
;
; INPUTS:
;
;	R2 = unit number
;	R3 = length of SCS node name at head of name string
;	     or allocation class number
;	     or device type code
;	R8 = size of name string
;	R9 = address of name string
;	R10 = flags
;	R11 = address to store lock value block
;	I/O database mutex held, IPL 2
;
; OUTPUTS:
;
;	R0 = SS$_NORMAL - device found
;	   = SS$_NOSUCHDEV - device not found
;	   = SS$_NODEVAVL - device exists but not available according to rules
;	   = SS$_DEVALLOC - device allocated to other user
;	   = SS$_NOPRIV - failed device protection
;	   = SS$_TEMPLATEDEV - can't allocate template device
;	   = SS$_DEVMOUNT - device already mounted
;	   = SS$_DEVOFFLINE - device marked offline
;	R5 = UCB
;	R6 = DDB
;	R7 = system block
;	R10 - R4, R8 - R11 preserved
;
;	Note: If failure, R5 - R7 point to the last structures looked at.
;
;-
	UNIVERSAL_JSB	IOC$SEARCHINT,-
			INPUT=<R2,R3,R8,R9,R10,R11>,-
			OUTPUT=<R0,R5,R6,R7>
;IOC$SEARCHINT::
;	$COUNT_ENTRY IOC$SEARCHINT	; Debug counting.
	CALL_SEARCHINT			; Push parms and call STD routine.
	RSB				; Return to caller.

	.PAGE
	.SBTTL	SEARCH I/O DATABASE FOR DEVICE
;+
; IOC_STD$SEARCHINT - internal I/O database search
;
; This routine searches the I/O database for the specified device, using
; the specified search rules. Depending on the search, a lock may or may
; not be taken out on the device when it is found.
;
; Note! While this routine is non-paged and therefore may be called at
; elevated IPL, the device locking code it calls is not. Therefore, only
; searches with IOC$V_ANY may be called from elevated IPL.
;
; CALLING SEQUENCE:
;       Status = IOC_STD$SEARCHINT (UNITNUM, MISC, DEVNAMESIZ, DEVNAME, FLAGS,-
;			    	    UCB, DDB, SB, LVB) 
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;	ARG$_UNITNUM(AP)    - Unit number, by value
;	ARG$_MISC(AP)	    - SCS node name, alloc class or dev type, by value
;	ARG$_DEVNAMSIZ(AP)  - Device name string size, by value 
;	ARG$_DEVNAM(AP)	    - Device name string
;	ARG$_FLAGS(AP)	    - Flags, by value
; OUTPUTS:
;	ARG$_UCB(AP)	    - Unit Control Block
;	ARG$_DDB(AP)	    - Device Data Block
;	ARG$_SB(AP)	    - System Block
;	ARG$_LVB(AP)	    - Lock Value Block
; RETURN STATUS:
;       Status - Standard VMS condition code.
$OFFDEF ARG,< -
        UNITNUM, -
        MISC, -
        DEVNAMSIZ, -
        DEVNAM, -
        FLAGS, -
        UCB, -
        DDB, -
	SB, -
        LVB -
        >

UNIVERSAL_ENTRY	IOC_STD$SEARCHINT,-
		MASK=<M^<R2,R3,R5,R6,R7,R8,R9,R10,R11>>
;IOC_STD$SEARCHINT::

;	$COUNT_ENTRY IOC_STD$SEARCHINT	; Debug counting.

	MOVL	ARG$_UNITNUM(AP),R2	; Get unit number input.
	MOVL	ARG$_MISC(AP),R3
	MOVL	ARG$_DEVNAMSIZ(AP),R8  	; Get device name string size. 
	MOVL	ARG$_DEVNAM(AP),R9	; Get device name string
	MOVL	ARG$_FLAGS(AP),R10	; Get flags input.
	MOVL	ARG$_LVB(AP),R11	; Get addr of the LVB output storage.

	JSB	SEARCH_INT		; JSB to searchint.

	MOVL	R5,@ARG$_UCB(AP)	; Get UCB output.
	MOVL	R6,@ARG$_DDB(AP)	; Get DDB output.
        MOVL    R7,@ARG$_SB(AP)		; Get SB output.

	RET				; Return to caller.


	.ENABLE	LSB
;
; Stack use:
;
SAVR2	= 0
SAVR3	= 4
SAVR4	= 8
SAVR8	= 12
SAVR9	= 16

SEARCH_INT: 
	.JSB_ENTRY	INPUT=<R2,R3,R8,R9,R10,R11>,-
			OUTPUT=<R5,R6,R7>,-
			PRESERVE=<R4>

        PUSHR   #^M<R2,R3,R4,R8,R9>     ; save registers
	.DSABL	FLAGGING		; % AMAC-I-BRANCHBET, branch between 
	BBS	#IOC$V_DTN,R10,200$	; if this is DTN search, go handle it
	.ENABL	FLAGGING
;
; Search the system blocks for a suitable node. If we are doing a search
; by allocation class, generic device type, or no node name is given,
; all system blocks qualify.
;
	MOVAL	G^SCS$GQ_CONFIG,R7	; get head of SCS SB list
10$:	MOVL	SB$L_FLINK(R7),R0	; get next system block
	CMPL	R0,#SCS$GQ_CONFIG	; are we back at list head?
	BEQL	50$			; branch if yes - all done

	MOVL	R0,R7
	MOVAL	SB$L_DDB-DDB$L_LINK(R7),R6 ; pick up DDB listhead
	MOVL	R6,R5			; make sure UCB is non-zero
					; if allocation class or generic dev,
	BITB	#IOC$M_CLASS!IOC$M_TYPE,R10
	BNEQ	30$			; check every system block
       	MOVQ    SAVR8(SP),R8            ; get orig dev name descriptor
        MOVL    SAVR3(SP),R3            ; get node name length
	BEQL	18$			; branch if none - go ahead
	CMPB	R3,SB$T_NODENAME(R7)	; check node name length
	BNEQ	10$			; branch if not
	CMPC3	R3,SB$T_NODENAME+1(R7),(R9) ; node names match?
	BNEQ	10$			; branch if not

.IIF NDF,IOC$V_NOLOCK,IOC$V_NOLOCK=IOC$V_PAC+1

;
; Here when we found a suitable DDB but no unit number match. Next DDB, please.
; Need to reinitialize possibly-blown registers.
;
18$:	MOVQ	SAVR8(SP),R8		; get orig dev name descriptor (again)
	MOVQ	SAVR2(SP),R2		; and get orig unit number, R3
	MOVAL	SB$L_DDB-DDB$L_LINK(R7),R6 ; pick up DDB listhead (again)
	BICL	#IOC$M_2P,R10		; reset flags to original if pass 2
	EVAX_BEQ R3,30$			; If no nodename, no name adjustment
;
; Found a suitable system block. Search its DDB list.
;
20$:	MOVZWL	#SS$_NORMAL,R0
        ADDL3   #1,SAVR3(SP),R3         ; include the "$"
	ADDL	R3,R9			; skip over the nodename
	SUBL	R3,R8			; adjust the length
	BLEQ	60$			; if no device name, just return SB

30$:	MOVL	DDB$L_LINK(R6),R0	; get address of next DDB
	BEQL	80$			; if eql end of list
	MOVL	R0,R6
	MOVAL	<DDB$L_UCB-UCB$L_LINK>(R6),R5 ; initialize primary UCB address
	BICB	#IOC$M_2P,R10		; new DDB - clear secondary flag
	BBS	#IOC$V_TYPE,R10,100$	; branch if generic type search
	BBC	#IOC$V_CLASS,R10,35$	; branch if no allo class specified
        CMPL    SAVR3(SP),DDB$L_ALLOCLS(R6) ; else, is allo. class right?
	BNEQ	30$			; branch if not, try next DDB
	BRB	40$			; good ddb; look for ucb
;
; Here if no allo class was specified; this is a good candidate if either
; the DDB allo class is zero or it is a system allo class.  Devices with a
; port allo class must only be found with the correctly specified allo class.
; Exception: if this is a PKx device, the PAC doesn't really apply and we
; don't check it.
;
35$:	
	TSTL	DDB$L_ALLOCLS(R6)	; all set if allo class zero
	BEQL	40$
					; Non-zero allocls
	CMPW	DDB$T_NAME_STR(R6),-	; *** hack ***
		#^A/PK/			;   PK devices are handled like null
	BEQL	40$			;     alloclass    *** end hack ***
	BBS	#DDB$V_PAC,-		; If device with local PAC other than
		DDB$B_FLAGS(R6),-	;    PKx, don't allow to find it when
		30$			;    no allocls specified

; Check if this is a MSCP device from a system with a non-zero allocls or a
; served device with a PAC
;
	MOVL	DDB$L_UCB(R6),R0	; Get a (any) UCB	
	BEQL	39$			;   if nothing there: try other list
37$:	BBC	#DEV$V_MSCP,-		; If not MSCP served: must be local
		UCB$L_DEVCHAR2(R0),40$	;   device with system allocls. Try it!
		
					; MSCP served device with allocls != 0
	MOVL	UCB$L_CDDB(R0),R0	;  Get system allocls from cddb
	CMPL	CDDB$L_ALLOCLS(R0),-	;   Same as in DDB?
		DDB$L_ALLOCLS(R6)
	BEQL	40$			;    Yes - good DDB: search for device
	BRB	30$			;    No - DDB has non-zero port allocls

39$:	MOVL	DDB$L_2P_UCB(R6),R0	; Try UCB on secondary path
	BEQL	30$			;    Nothing here either: give up
	BRB	37$			;    Found one: try it

40$:	CMPC3	R8,(R9),DDB$T_NAME+1(R6) ; check device name
	BNEQ	30$			; if no match, try next DDB
	MOVZBL	DDB$T_NAME(R6),R0	; get length of name in DDB
	CMPL	R8,R0			; check name lengths
	BEQL	100$			; if they match - OK
	DECL	R0			; try subtracting out controller letter
	CMPL	R8,R0			; and see if this matches
	BNEQ	30$			; if not, keep trying
	BLBC	R10,100$		; branch if not physical search - OK
	.DSABL	FLAGGING		; %AMAC-I-FIEINALI, Field in aligned 
	CMPB	DDB$T_NAME+1(R6)[R0],#^A'A' ; is this controller A?
	.ENABL	FLAGGING
	BEQL	100$			; if so, search it
	BRB	30$			; if not, keep looking

;
; End of search - no suitable device has been found
;
50$:	MOVZWL	#SS$_NOSUCHDEV,R0	; no device found
	BBC	#IOC$V_EXISTS,R10,105$	; branch if not seen
	MOVZWL	#SS$_NODEVAVL,R0	; otherwise status is not available
	BRB	105$
;
; To here if we're just returning a system block, with no device specified.
;
60$:	MOVL	(R6),R6			; get first DDB
	MOVL	DDB$L_UCB(R6),R5	; and first UCB
	BRB	105$			; and return
;
; To here when all UCB's on a DDB have been searched.
;
70$:	BLBC	R10,30$			; if not physical search, try next DDB

;
; To here when all DDB's on a system block have been searched.
;
80$:	BBS	#IOC$V_PAC,R10,50$	; Done if port allocation class
	BITB	#IOC$M_CLASS!IOC$M_TYPE,R10 ; if generic type or alloc class
	BNEQ	90$			; keep searching system blocks
	BITB	#IOC$M_PHY!IOC$M_LOCAL,R10 ; if physical or local only
	BNEQ	50$			; we're done
        TSTL    SAVR3(SP)               ; if there was an explicit node
	BNEQ	50$			; we're done
90$:	BRW	10$			; else go try next system block

;
; Found a suitable DDB. Search both its UCB lists for the right UCB.
;
100$:	MOVL    SAVR2(SP),R2            ; get unit number and device type
        MOVL    SAVR3(SP),R3
	.DSABL	FLAGGING		; % AMAC-I-BRANCHBET, branch between 
	BRW	110$
	.ENABL	FLAGGING

105$:   POPR    #^M<R2,R3,R4,R8,R9>     ; restore registers
	RSB				; Return to caller.

NEXTUCB: 
	.JSB_ENTRY	INPUT=<R2,R3,R8,R9,R10,R11>,-
			OUTPUT=<R5,R6,R7>,-
			PRESERVE=<R4>

        PUSHR   #^M<R2,R3,R4,R8,R9>     ; save registers

110$:	BBC	#IOC$V_2P,R10,120$	; branch if on primary path
	MOVL	UCB$L_2P_LINK(R5),R5	; link to next secondary unit.
	BRB	130$
120$:	MOVL	UCB$L_LINK(R5),R5	; link to next primary unit.
130$:	BEQL	150$			; branch if no more units.
	BSBB	IOC$TESTUNIT		; is this unit ok?
	BLBS	R0,140$			; branch if successful
	BBC	#IOC$V_EXISTS,R10,110$	; keep going if we haven't seen it yet
	BLBC	R10,110$		; or if not physical search

140$:   POPR    #^M<R2,R3,R4,R8,R9>     ; restore registers
	RSB				; Return to caller.

	.DSABL	FLAGGING		; % AMAC-I-BRANCHBET, branch between 
150$:	BBSS	#IOC$V_2P,R10,70$	; branch if secondary path already searched
	.ENABL	FLAGGING
	MOVAL	<DDB$L_2P_UCB -		; initialize secondary UCB address.
		-UCB$L_2P_LINK>(R6),R5
	BRB	110$			; go search secondary path

;
; This is a search for a dynamically named device.  Look for an appropriate DTN.
; 
200$:	BICL	#IOC$M_PHY,R10		; Cannot be a physical device search
	SUBL	#4,SP			; Allocate space for DTN pointer
	PUSHAL	(SP)			; ioc$search_device_type(
	PUSHL	R8			;   dtName, dtNameLen, dtn)
	PUSHL	R9
	CALLS	#3,G^IOC$SEARCH_DEVICE_TYPE
	POPL	R2			; Returned DTN address in R2
	BLBC	R0,140$			; None found, just exit
	MOVL	DTN$PS_UCBLIST(R2),R5	; Get first UCB
	.DSABL	FLAGGING		; % AMAC-I-BRANCHBET, branch between 
	BEQL	50$			; If none, search is finished
	.ENABL	FLAGGING
210$:	BSBB	IOC$TESTUNIT		; See if device passes muster
	BLBS	R0,220$			; Yes, finish up
	MOVL	UCB$PS_DTN_LINK(R5),R5	; Move to next UCB of this type
	BNEQ	210$			;  and see how it fares
	.DSABL	FLAGGING		; % AMAC-I-BRANCHBET, branch between 
	BRB	50$			; Failed to find an available device
	.ENABL	FLAGGING
220$:	MOVL	UCB$L_DDB(R5),R6	; Set DDB address
	MOVL	DDB$PS_SB(R6),R7	;  and SB address for return
	BRB	140$			; Branch to common exit

	.DISABLE LSB


	.PAGE
	.SBTTL	CONTINUE I/O DATABASE SEARCH
;+
; IOC$SEARCHCONT - internal I/O database search
;
;	This routine continues a search started with a call to IOC$SEARCHINT.
;	It uses IOC$SEARCHINT's outputs as the starting point at which to
;	resume.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$SEARCHCONT
;
; INPUTS:
;
;	R2 = unit number
;	R3 = length of SCS node name at head of name string
;	     or allocation class number
;	     or device type code
;	R5 = last UCB
;	R6 = last DDB
;	R7 = last system block
;	R8 = size of name string
;	R9 = address of name string
;	R10 = flags
;	R11 = address to store lock value block
;	I/O database mutex held, IPL 2
;
; OUTPUTS:
;
;	R0 = SS$_NORMAL - device found
;	   = SS$_NOSUCHDEV - device not found
;	   = SS$_NODEVAVL - device exists but not available according to rules
;	   = SS$_DEVALLOC - device allocated to other user
;	   = SS$_NOPRIV - failed device protection
;	   = SS$_TEMPLATEDEV - can't allocate template device
;	   = SS$_DEVMOUNT - device already mounted
;	   = SS$_DEVOFFLINE - device marked offline
;	R5 = UCB
;	R6 = DDB
;	R7 = system block
;	R10 - R4, R8 - R11 preserved
;
;	Note: If failure, R5 - R7 point to the last structures looked at.
;
;-

	UNIVERSAL_JSB	IOC$SEARCHCONT,INPUT=<R2,R3,R5,R6,R7,R8,R9,R10,R11>,-
				       OUTPUT=<R0,R5,R6,R7>,-
				       C2J_NAME=IOC_STD$SEARCHCONT
;IOC$SEARCHCONT::
;	PUSHR	#^M<R2,R3,R4,R8,R9>	; save registers
	BBCC	#IOC$V_ALT,R10,10$	; check if alternate UCB in use
	MOVL	UCB$L_DP_ALTUCB(R5),R5	; link back to other to continue
10$:	JSB	NEXTUCB			; continue search
	RSB

	.PAGE
	.SBTTL	CHECK UCB AGAINST SEARCH RULES
;+
;
; IOC$TESTUNIT - Check UCB Against Search Rules
;
; CALLING SEQUENCE:
;
;	JSB	IOC$TESTUNIT
;
; INPUTS:
;
;	R2 = unit number
;	R3 = device type code
;	R5 = UCB address
;	R10 = flags
;	R11= address of lock value block
;
;	Interpretation of flags argument (R10):
;	IOC$V_PHY (low bit): match specific unit number in R2
;	IOC$V_TYPE: match generic device type in R3
;	IOC$V_DTN: match device type name (done outside this routine)
;		   (negates IOC$V_TYPE)
;	IOC$V_ANY: skip device allocation / availability checks
;	IOC$V_MOUNT: find shareable mountable device
;	IOC$V_ALLOC: find non-shared mountable device
;		     IOC$V_ALLOC requires IOC$V_MOUNT
;	Neither of the above two: find device for $ALLOC
;	IOC$V_NOLOCK: do not take device locks
;
; OUTPUTS:
;
;	R0 = SS$_NORMAL - eligible for use according to flags
;	   = SS$_NOSUCHDEV - wrong unit number
;	   = SS$_DEVALLOC - device allocated to other user
;	   = SS$_NOPRIV - failed device protection
;	   = SS$_TEMPLATEDEV - can't allocate template device
;	   = SS$_DEVMOUNT - device already mounted
;	   = SS$_DEVOFFLINE - device marked offline
;
;-

	UNIVERSAL_JSB	IOC$TESTUNIT,INPUT=<R2,R3,R5,R10,R11>,-
				     OUTPUT=<R0,R5,R10>,SCRATCH=<R1>,-
				     C2J_NAME=IOC_STD$TESTUNIT
;IOC$TESTUNIT::
	MOVZWL	#SS$_NOSUCHDEV,R0	; assume wrong device
	BBS	#UCB$V_NO_ASSIGN, -	; See is access is refused
		UCB$L_STS(R5),100$	; 
	BLBC	R10,10$			; branch if not physical search
	CMPW	R2,UCB$W_UNIT(R5)	; is the unit number exactly right?
	BNEQ	70$			; branch to error if not right.

10$:	BBC	#IOC$V_TYPE,R10,20$	; branch if not searching for dev type
	BBS	#IOC$V_DTN,R10,20$	; if DTN search, device is eligible
	CMPZV	#MSCP$V_MTYP_N,-
		#MSCP$V_MTYP_D1,-
		UCB$L_MEDIA_ID(R5),R3	; is this the requested type?
	BNEQ	70$			; branch if not
20$:	BISB	#IOC$M_EXISTS,R10	; note eligible device seen
	BBC	#DEV$V_CDP,UCB$L_DEVCHAR2(R5),30$ ; is this served path to a local device?
	MOVL	UCB$L_DP_ALTUCB(R5),R5	; yes, get local path UCB address.
	BISW	#IOC$M_ALT,R10		; note alternate UCB in use
30$:	BBC	#IOC$V_ANY,R10,40$	; if SEARCHALL, finish with success.
	BRW	150$
;
; Check the device reference count and allocation status. If the tests
; fail, return SS$_DEVALLOC unless the device is mounted and not allocated.
;
40$:	MOVZWL	#SS$_DEVALLOC,R0	; assume device allocated status
	TSTL	UCB$L_REFC(R5)		; is reference count zero?
	BEQL	80$			; branch if reference count is zero.
	BLBC	R10,70$			; error on any generic search
;
; Non-zero ref count: check if device is allocated, and if so, to whom.
;
	TSTL	UCB$L_PID(R5)		; check if allocated
	BEQL	55$			; if eql device not allocated
	MOVL	G^CTL$GL_PCB,R1		; Get current PCB address
45$:	CMPL	UCB$L_PID(R5),PCB$L_PID(R1) ; process ID match?
	BEQL	75$			; if eql yes 
	BBC	#IOC$V_MOUNT,R10,70$	; if not mounting, error
	BBC	#DEV$V_ALL,UCB$L_DEVCHAR(R5),50$ ; check if really allocated
	BBC	#IOC$V_ALLOC,R10,70$	; disallow shared mount if alloc to parent
;
; For non-shared mounts only, walk up the process tree to see if the device is
; allocated to the process or a parent.
;
50$:	MOVZWL	PCB$L_OWNER(R1),R1	; get parent process index
	BEQL	70$			; if eql allocated elsewhere
	MOVL	@W^SCH$GL_PCBVEC[R1],R1	; get address of creator PCB
	BRB	45$			; try next PID

;
; Device refcount nonzero but not allocated. Allow only a shared mount.
;
55$:	BBC	#IOC$V_MOUNT,R10,60$	; branch if allocate request
	BBC	#IOC$V_ALLOC,R10,80$	; allow shared mount only
60$:	BBC	#DEV$V_MNT,UCB$L_DEVCHAR(R5),70$ ; branch if not mounted
65$:	MOVZWL	#SS$_DEVMOUNT,R0	; indicate device mounted
70$:	BRW	100$			; br assist - error

;
; Device is allocated to current process. No further tests if this is
; a redundant allocate.
;
75$:	BBC	#IOC$V_MOUNT,R10,150$	; branch if not a mount
;
; Device is accessible to process. Check if mounted.
;
80$:	BBS	#UCB$V_MOUNTING,UCB$L_STS(R5),65$ ; branch if mount in progress
	BBS	#DEV$V_MNT,UCB$L_DEVCHAR(R5),65$ ; check if device is mounted
;
; Check all the other miscellaneous junk that can make a device not
; available.
;
	MOVZWL	#SS$_NOPRIV,R0		; check if device is spooled
	BBC	#DEV$V_SPL,-
		UCB$L_DEVCHAR(R5),90$	; branch if device not spooled
	MOVL	UCB$L_ORB(R5),R1	; get ORB address
;
; Build a privilege audit item list containing the device name in case we have
; to do a privilege audit.  This item list is built on the stack.
;
	PUSHL	R3			; save R3
	CLRQ	-(SP)			; termination + RETLEN	=> 8
	MOVL	ORB$L_NAME_POINTER(R1),-(SP)	; BUFADR	=> 4
	MOVZWL	ORB$W_NAME_LENGTH(R1),R0 ; get name length
	ADDL3	#<NSA$_DEVICE_NAME@16>,R0,-(SP)	; BUFSIZ	=> 2 / (16)
	AUDIT_S_ITMLST = 16		; size of auditing item list
	MOVL	SP,R3			; item list address
	MOVZWL	#SS$_NOPRIV,R0		; check if device is spooled
	$IFNPRIV ALLSPOOL,115$,-
		MSG=ALLSPOOL_1,-
		ITMLST=R3		; process must have ALLSPOOL
	ADDL	#AUDIT_S_ITMLST,SP	; clean up stack
	POPL	R3			; restore R3
90$:	MOVZWL	#SS$_DEVOFFLINE,R0	; check if device is available
	BBC	#DEV$V_AVL,UCB$L_DEVCHAR(R5),100$
	BLBS	R10,95$			; skip offline check if physical name
	BBC	#UCB$V_ONLINE,UCB$L_STS(R5),100$
95$:	MOVZWL	#SS$_TEMPLATEDEV,R0	; check if device is a template
	BBS	#UCB$V_TEMPLATE,UCB$L_STS(R5),100$
	PUSHL	R8			; save R8
	CLRL	R8			; note no deaccess audit necessary
	JSB	G^EXE$CHECK_ALLOC_ACCESS; check device protection
	POPL	R8			; restore R8
	BLBS	R0,120$			; continue if accessible
;
; To here on any error.
;
100$:	BBCC	#IOC$V_ALT,R10,110$	; check if alternate UCB in use
	MOVL	UCB$L_DP_ALTUCB(R5),R5	; link back to other to continue
110$:	RSB				; return
115$:	ADDL	#AUDIT_S_ITMLST,SP	; clean up stack
	POPL	R3			; restore R3
	BRB	100$			; join common error
;
; We've passed all the local tests. Now try to take out the appropriate
; lock on the device.
;
120$:	MOVL	R11,R1			; value block address
	BEQL	130$			; branch if none
	.DSABL	FLAGGING		; % AMAC-I-QUADMEMREF, quadword memory
	CLRQ	(R1)			; initialize value block
	CLRQ	8(R1)
	.ENABL	FLAGGING
130$:	BBC	#DEV$V_CLU,UCB$L_DEVCHAR2(R5),150$ ; br. if not cluster visible
	BBS	#IOC$V_NOLOCK,R10,150$	; branch if no locking requested
	MOVL	#LCK$K_EXMODE,R0	; assume exclusive lock
	BBS	#IOC$V_ALLOC,R10,140$	; branch if allocation requested
	BBC	#IOC$V_MOUNT,R10,140$	; branch if not mount mode
	BBS	#DEV$V_ALL,UCB$L_DEVCHAR(R5),140$ ; br. if allocated
	MOVL	#LCK$K_PWMODE,R0	; mount, no allocation - use PW
140$:	BSBW	IOC$LOCK_DEV		; and try to take device lock
	BLBC	R0,100$
150$:	MOVL	#SS$_NORMAL,R0		; indicate success
	RSB


	.PAGE
	.SBTTL	IOC$THREADCRB
;++
;
; FUNCTIONAL DESCRIPTION:
;
;	This routine will thread a CRB onto the duetime chain headed by
;	IOC$CRBTMOUT.
;
; CALLING SEQUENCE:
;
;	JSB	IOC$THREADCRB
;
; INPUTS:
;
;	R3 -->	CRB
;
; OUTPUTS:
;
;	NONE
;
;--

	UNIVERSAL_JSB	IOC$THREADCRB,-
			INPUT=<R3>,-
			PRESERVE=<R0>
;IOC$THREADCRB::

	$COUNT_ENTRY IOC$THREADCRB	; Debug counting.
	CALL_THREADCRB SAVE_R0=NO	; Push parms and call the STD routine.
	RSB				; Return to caller with R0 preserved.


	.PAGE
	.SBTTL	Thread a CRB onto the duetime chain.
;++
; IOC_STD$THREADCRB - Thread a CRB onto the duetime chain
;
; This routine will thread a CRB onto the duetime chain headed by
; IOC$CRBTMOUT.
;
; CALLING SEQUENCE:
;       IOC_STD$THREADCRB (CRB)
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; INPUTS:
;       ARG$_CRB(AP)	- Channel Request Block
; OUTPUTS:
;	None
; RETURN VALUE:
;       None (actually R0 is preserved)
$OFFDEF ARG,< -
        CRB -
        >

UNIVERSAL_ENTRY IOC_STD$THREADCRB,-
		MASK=<M^<R3>>
;IOC_STD$THREADCRB::

	$COUNT_ENTRY IOC_STD$THREADCRB	; Debug counting.

	MOVL	ARG$_CRB(AP),R3		; Get the CRB input.

	PUSHL	R0			; Save a register
	MOVAL	G^IOC$GL_CRBTMOUT, R0	; Pointer to list head
10$:	TSTL	(R0)			; End of the line?
	BEQL	20$			; Yes, go add new one
	MOVL	(R0), R0		; No, get next block
	BRB	10$			; Try, try again

20$:	MOVAL	CRB$L_TIMELINK(R3),(R0)	; Link the new block in
	POPL	R0			; Restore register
	RET				; Return to caller.


	.PAGE
	.SBTTL	EXE$INIT_DEVICE_PWRUP - Initialize device drivers

;+
;
; FUNCTIONAL DESCRIPTION:
;
;	Call device drivers at their controller and unit initialization
;	routines.
;
; Calling Sequence:
;
;	CALL	EXE$INIT_DEVICE_PWRUP( flags, tr_number )
;
; INPUTS:
;
;	4(AP)	Flags, passed by value.   If low bit set, then set UCB$M_POWER
;		for all devices that are initialized.
;	8(AP)	TR number, passed by value.  If non-negative, then only those
;		devices with the specified TR number are initialized.  If
;		negative, then all devices are initialized.        
;
; OUTPUTS:
;
;	None.
;
;-

; Define parameter offsets
;
SET_POWER	=  4		;If low bit set, then set UCB$M_POWER
SELECT_TR	=  8		;Init only devices selected by TR, -1 for all
SENTINEL	= 536870911	;Large number for DPT$IS_BTORDER compare.
 
	DECLARE_PSECT	EXEC$NONPAGED_CODE

	UNIVERSAL_ENTRY	-
EXE$INIT_DEVICE_PWRUP	MASK=<^M<R8,R10,R11>>,SCRATCH=<^M<R0,R1>>

	CLRL	R8			; Clear last seen CRB address
	CLRL	R11			; Force scan at start of I/O database
	CLRL	R5			; Start with low number in current...
					;  btorder number to be initialized. 

5$:	MOVL	#^X1FFFFFFF,R6		; Set sentinel for DPT btorder number.
10$:	JSB	G^IOC$SCAN_IODB		; Scan the I/O data base...
					;  R0=status, R10=UCB, R11=DDB
	BLBC	R0,80$			; if no more then return
;
; First determine if we want to initialize this device.
;
	BBS	S^#DEV$V_MBX,-		; If mailbox then skip to next device
		UCB$L_DEVCHAR(R10),10$
	MOVL	UCB$L_CRB(R10),R1	; R1 = CRB for current device

	TSTL	SELECT_TR(AP)		; if selecting by specific TR
	BLSS	15$
	MOVL	CRB$L_INTD+VEC$PS_ADP(R1),-
		R0			;   R0 = pointer to ADP
	BEQL	10$			;   if no ADP then skip to next device
	CMPL	SELECT_TR(AP),-		;   if select TR and device TR don't match
		ADP$L_TR(R0)
	BNEQ	10$			;     skip to next device
	BRW	20$			;   Skip the DPT init ordering code.
;
;	Code for driver initialization ordering.
;
15$:
	MOVL	DDB$PS_DPT(R11),R4	; Get the DPT associated with this DDB.
	CMPL	DPT$IS_BTORDER(R4),R5	; Compare this DPT BTORDER number...
					;  with the btorder to be initialized.
	BLSS	10$			; If DPT < R5 - Get the next DDB/UCB.
	BEQL	20$			; If DPT = R5 - Call init routines.
					; IF DPT > R5 - Update next to be initd.
	CMPL    DPT$IS_BTORDER(R4),R6 	; Compare this DPT BTORDER number...
        	                      	;  with the next btorder to be init'd.
 	BGEQ	10$			; IF DPT >= next - Get next DDB/UCB.
	MOVL	DPT$IS_BTORDER(R4),R6 	; Move the DPT BTORDER number...
					;  to the next btorder to be init'd.
	BRW	10$			; Get the next DDB/UCB.
 
;
; The device will be initialized.
; Now, check if the "power has failed and come back" bit should be set.
;
20$:    MOVL    DPT$PS_DDT(R4),-        ; Init the initialization routines.
                UCB$L_DDT(R10)          ; HACK until LOAD_DRIVER does this.
	BLBC	SET_POWER(AP),30$	; if flag set to set UCB$M_POWER
	BISL	#UCB$M_POWER,-		;   set power failed status
		UCB$L_STS(R10)
;
; If the controller for the current device is not the same as the most recently
; initialized controller then we must first initialize the controller.
;
30$:	CMPL	R1,R8			; if not same CRB as last initialize
	BEQL	40$
	MOVL	R1,R8			;   save new last init CRB address.

	PUSHL	R11			;   Push DDB arg
	PUSHL	R8			;   Push CRB arg
	CALLS	#2,G^IOC$CTRL_INIT	;   Do driver controller initialization.
	BLBC	R0, 70$			;   If error, then skip unit inits
;
; Now init the unit.
;
40$:	PUSHL	R10			; Push UCB arg
	CALLS	#1,G^IOC$UNIT_INIT	; Do driver unit initialization.
;
; Then check for a unit that was busy prior to the power failure.
;
	BITL	#UCB$M_INT!UCB$M_TIM,-
		UCB$L_STS(R10)		; Interrupt or timeout expected?
	BEQL	10$			; If eql then no
	BICL	#UCB$M_INT,UCB$L_STS(R10); Clear interrupt expected
	BISL	#UCB$M_TIM,UCB$L_STS(R10); Set timeout expected
	CLRL	UCB$L_DUETIM(R10)	; Now
;
; Look for busy, non-MSCP disks that are not in mount verification.  Clear 
; volume-valid and set mount-verification-pending so that restarted I/Os will
; fail and the volume will be revalidated.  Non-busy disks are handled 
; independently.
;
	CMPB	UCB$B_DEVCLASS(R10),-	; Make sure it is a disk
		#DC$_DISK
	BNEQ	10$
	BBC	#DEV$V_FOD,-		; Not file oriented
		UCB$L_DEVCHAR(R10),10$
	BBS	#DEV$V_SQD,-		; Sequential device
		UCB$L_DEVCHAR(R10),10$
	BBS	#DEV$V_MSCP,-		; MSCP disks are handled independently
		UCB$L_DEVCHAR2(R10),10$
	BBSS	#UCB$V_MNTVERIP,-	; Mount verification already in progress
		UCB$L_STS(R10),10$
	BBSS	#UCB$V_MNTVERPND,-	; Mark it mount verification pending
		UCB$L_STS(R10),50$
50$:	BBCC	#UCB$V_VALID,-		; Cause I/O to fail
		UCB$L_STS(R10),51$
51$:	BRW  	10$			; Next unit

70$:	CLRL	R8			; Zap CRB to force CRB search
	BICL	#UCB$M_ONLINE,UCB$L_STS(R10)	; Set unit offline
	BRW	10$			; Continue search

80$:	CMPL	#^X1FFFFFFF,R6		; Have we found any new DPTs to init?
	BEQL	90$			; NO - Exit the routine.
	CLRL 	R11			; YES- Star at beginning of DDB list.
	MOVL	R6,R5			; Move next btorder to curr btorder.
	BRW	5$ 			; Start DDB loop with new btorder...
					;  number of DPT to init.
90$:	RET				; return.
	.if	df,doswstuff
	.SBTTL	Hook in switching driver control for multiple disk paths
;++
; IOC_STD$SETUP_SWITCH	-	Set up switching driver
;
; This routine will add a secondary UCB to the list of switching
; paths for a primary UCB, setting up the primary UCB and its path
; as the primary switch path first if necessary. 
;
; CALLING SEQUENCE:
;       IOC_STD$SETUP_SWITCH(PrimaryUCB,SecondaryUCB)
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; ENVIRONMENT:
;	This routine must be called holding forklock and the I/O database
;	write mutex. Code underlying just assumes these and will not
;	disturb them.
; INPUTS:
;       ARG$_PUCB(AP)	- Primary path UCB
;	ARG$_SUCB(AP)	- Secondary path UCB
; OUTPUTS:
;	None
; RETURN VALUE:
;       None (actually R0 is preserved)
$OFFDEF ARG,< -
        PUCB, -
	SUCB -
        >

UNIVERSAL_ENTRY IOC_STD$SETUP_SWITCH, -
		MASK=<M^<R2,R3,R4,R5,R6,R7,R8,R9,R11>>
;IOC_STD$SETUP_SWITCH::
	SUBL2	#<560>,SP		; GET SOME SPACE
	MOVL	SP,R11			; POINT AT THE BASE
; 528 BYTES BUFFER for swdriver
;   4 sw ucb
;   4 length
;   4 descriptor hdr word
;   4 buffer address for descriptor
lc.desc=0
lc.dsca=4
lc.len=8
lc.ucb=12
lc.buf=16
	MOVL	#^A/SWA0/,LC.BUF(R11)	; SET UP DATA FOR DEVICE NAME
	MOVB	#^A/:/,<LC.BUF+4>(R11)	; SWA1: TO BEGIN WITH
	MOVAB	LC.BUF(R11),LC.DSCA(R11) ; SET UP DESCRIPTOR
	MOVW	#5,LC.DESC(R11)		; LENGTH=5 CHARS
	MOVB	#DSC$K_DTYPE_T,<LC.DESC+2>(R11)
	MOVB	#1,<LC.DESC+3>(R11)	; SET UP STATIC TEXT BUFFER
	MOVAB	LC.DESC(R11),R1		; POINT AT DESCRIPTOR
	JSB	G^IOC$SEARCHDEV		; HUNT FOR THE DEVICE
; Since we will call an internal routine it doesn't matter that the
; device is a template one. The routine will work in any case.
	BLBC	R0,999$			; If no SW device exists, bail out.
;
; We now have a pointer to the SW UCB. Store it.
	MOVL	R1,LC.UCB(R11)		; SW UCB SWA0:
	MOVL	R1,R9			; Keep a SWA0: pointer
	MOVL	UCB$L_DDT(R1),R10	; SW DDT
;
; CALL THE SWDRIVER ROUTINE TO SEE IF THE PRIMARY PATH IS ALREADY
; INTERCEPTED.
;
	MOVL	#5,LC.BUF(R11)		; REPORT IF DEVICE HAS SW INTC
	MOVL	ARG$_PUCB(AP),R7
	MOVL	R7,<LC.BUF+4>(R11) 	; ASK OF PRIMARY UCB
	MOVL	ARG$_SUCB(AP),R8	; Load the secondary UCB address here
	PUSHL	R1			; SW UCB ADDRESS
	PUSHL	#528			; BUFFER SIZE
	PUSHAB	LC.BUF(R11)		; BUFFER ADDRESS
	CALLS	#3,@DDT$PS_MNTVER_2(R10) ; CALL THE ENTRY
; SW UCB USED OR 0 RETURNS IN BUF+8
	TSTL	<LC.BUF+8>(R11)		; IS A SW UCB THERE NOW FOR PRIMARY?
	BLSS	20$			; IF LSS, APPARENTLY YES
;
;
; If the primary does NOT have a UCB associated, guard against a whole passel
; of possible race conditions by checking also that what was passed as our
; secondary UCB is also not intercepted at this point. If it is intercepted
; we want to switch roles for the arguments and use the existing
; SW UCB and hook in the primary instead...
;
	MOVL	R8,<LC.BUF+4>(R11)	; Is the secondary intercepted already?
	MOVL	#5,LC.BUF(R11)		; REPORT IF DEVICE HAS SW INTC
	PUSHL	R9			; SW UCB address for a SW unit
	PUSHL	#528			; buffer size
	PUSHAB	LC.BUF(R11)		; buffer address
	CALLS	#3,@DDT$PS_MNTVER_2(R10) ; CALL THE ENTRY
; SW UCB USED OR 0 RETURNS IN BUF+8
	TSTL	<LC.BUF+8>(R11)		; IS A SW UCB THERE NOW FOR SECONDARY?
	BGEQ	15$			; If not negative then no
;OOPS. We have an intercepted secondary UCB (thus already hooked in).
; Reverse roles and skip cloning a new UCB.
	MOVL	R7,R0
	MOVL	R8,R7
	MOVL	R0,R8			; Switch primary and secondary
	BRW	20$			; And go hook the "secondary" in now
15$:
; NO SW UCB SEEN FOR THE PRIMARY UCB. THEREFORE GO CLONE ONE.
	PUSHAB	LC.UCB(R11)		; Store UCB Address here
	PUSHL	LC.UCB(R11)		; The template is SWA0:
	CALLS	#2,G^IOC_STD$CLONE_UCB	; Clone the UCB
	BLBC	R0,999$
; Now we have one. Now call the SWA0: cloned UCB routine
	PUSHL	R9			; Template UCB
	PUSHL	#1			; flag so swdriver will NOT need fork
	PUSHL	r10			; DDT
	PUSHL	LC.UCB(R11)		; Cloned UCB we just got
	CALLS	#4,@DDT$PS_CLONEDUCB_2(R10) ; Call cloned UCB routine
;
; By passing the address 1 for a "PCB" we trigger a mod in the SWDRIVER
; cloned UCB routine so it will not fork but rather just run all its
; startup before returning. Therefore on return, the full UCB of the
; SW unit is set up for use and we now have a pointer to it.
; Now we must do the necessary setup of the primary path so that the
; SW unit just cloned will contain it.
;
	MOVL	#9,LC.BUF(R11)		; Set up to mung
	MOVL	R7,<LC.BUF+8>(R11)	; Primary UCB is what to mung
	CLRL	<LC.BUF+4>(R11)
	PUSHL	R9			; R9 is the SW UCB address
	PUSHL	#528			; magic buffer size
	PUSHAB	LC.BUF(R11)		; Push the buffer address
	CALLS	#3,@DDT$PS_MNTVER_2(R10)	; Call the setup routine
	BLBC	R0,999$			; if we fail, exit
; At this point the master path is set up.
20$:
	MOVL	<LC.BUF+8>(R11),LC.UCB(R11)	;Save the SW UCB actually hooked
; SW UCB FOR PRIMARY IS SET UP NOW. ADD THE SECONDARY TO IT.
	MOVL	#9,LC.BUF(R11)		; Set up to mung
	MOVL	R8,<LC.BUF+8>(R11)	; Mung secondary UCB
	MOVL	#65535,<LC.BUF+4>(R11)
	PUSHL	R9			; R9 is the SW UCB address
	PUSHL	#528			; magic buffer size
	PUSHAB	LC.BUF(R11)		; Push the buffer address
	CALLS	#3,@DDT$PS_MNTVER_2(R10)	; Call the setup routine
; Now the secondary path should be set up, if all went as we
; may hope
;
999$:
	ADDL2	#<560>,SP		; GET SOME SPACE
	RET
;
; 
	.SBTTL	Test for duplicate device and hook switch
;++
; IOC_STD$TEST_SWITCH
;
; This routine will look for a device with the same name as the
; device pointed at by the supplied UCB. If one is found, it will
; call IOC_STD$SETUP_SWITCH to connect it with the switching
; system. This routine is needed for places like iogen's scsi configuration
; code. The idea is that the first such UCB to be found will be the
; primary path as a general matter (with exception in DKdriver where its
; search knows of two at least and has a preference). Subsequent code can
; request a preferred path be made current if it is inactive.
;
; CALLING SEQUENCE:
;       IOC_STD$TEST_SWITCH(PrimaryUCB)
;       NOTE: If calling sequence changes, the MACRO in SYSMAR must be changed.
; ENVIRONMENT:
;	This routine must be called holding forklock and the I/O database
;	write mutex. Code underlying just assumes these and will not
;	disturb them.
; INPUTS:
;       ARG$_PUCB(AP)	- UCB to be tested for duplication
; OUTPUTS:
;	None
; RETURN VALUE:
;       Success if we find a dupliate and hook it in.
$OFFDEF ARG,< -
        PUCB -
        >

UNIVERSAL_ENTRY IOC_STD$TEST_SWITCH, -
		MASK=<M^<R2,R3,R4,R5,R6,R7,R8,R9,R11>>
;IOC_STD$TEST_SWITCH::
; Search in the SB that is pointed to via the supplied UCB
	MOVL	ARG$_PUCB(AP),R5		; Get the supplied UCB
	MOVL	UCB$L_DDB(R5),R6	; Point at the DDB
	MOVL	DDB$L_SB(R6),R7		; Point at the SB also
	CLRL	R11			; Set to scan the IODB
10$:
	JSB	G^IOC$SCAN_IODB		; Look for a device
	BLBC	R0,999$			; If done with the search scram
; R10 is the found UCB now, and R11 is the found DDB.
; First, don't use our own UCB
	CMPL	R10,R5			; Same UCB is what we know thanks
	BEQL	10$			; So look for a different one
; UCBs are not the same, so see if their name IS the same.
	CMPW	UCB$W_UNIT(R5),UCB$W_UNIT(R10)	; diff unit number =>no
	BNEQ	10$
; Compare name in the DDB
	CMPL	DDB$T_NAME_STR(R6),DDB$T_NAME_STR(R11) ; Names differ?
	BNEQ	10$			; Compare all positions
	CMPL	<DDB$T_NAME_STR+4>(R6),<DDB$T_NAME_STR+4>(R11) ; Names differ?
	BNEQ	10$			; Compare all positions
	CMPL	<DDB$T_NAME_STR+8>(R6),<DDB$T_NAME_STR+8>(R11) ; Names differ?
	BNEQ	10$			; Compare all positions
	CMPL	<DDB$T_NAME_STR+12>(R6),<DDB$T_NAME_STR+12>(R11) ; Names differ?
	BNEQ	10$			; Compare all positions
; Names are the same and unit number the same. Same alloc class?
	CMPL	DDB$L_ALLOCLS(R6),DDB$L_ALLOCLS(R11)	;same?
	BNEQ	10$			; if not, not what we want
; If the alloc classes are the same and ZERO are the SBs the same?
	TSTL	DDB$L_ALLOCLS(R11)	; Allo class zero?
	BNEQ	15$			; if ne no, names ARE the same
; alloc class zero might still differ if the SB differs
	CMPL	DDB$L_SB(R6),DDB$L_SB(R11) ; If SB differs, diff. node
	BNEQ	10$
15$:
; Now we have a second device UCB that differs from the first but has the
; same name.
; Bind them together. Since we have presumably just created the UCB passed
; here, treat the one that we found already in the I/O database as the
; primary one.
	PUSHL	R5			; The new UCB is secondary
	PUSHL	R10			; the existing one was found earlier
	CALLS	#2,G^IOC_STD$SETUP_SWITCH	; Set the switch up
	MOVL	#SS$_NORMAL,R0		; And claim success
999$:
	RET
	.endc	;doswstuff
	.END

