	.TITLE	DKDRIVER - VAX/VMS SCSI Disk Class Driver
	.IDENT	'X-45'
;****************************************************************************
;*									    *
;*  COPYRIGHT (c) 1978, 1980, 1982, 1984, 1992, 1995 BY 		    *
;*  DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASSACHUSETTS.		    *
;*  ALL RIGHTS RESERVED.						    *
;* 									    *
;*  THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND COPIED   *
;*  ONLY IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE   *
;*  INCLUSION OF THE ABOVE COPYRIGHT NOTICE. THIS SOFTWARE OR  ANY  OTHER   *
;*  COPIES THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY   *
;*  OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE IS  HEREBY   *
;*  TRANSFERRED.							    *
;* 		     							    *
;*  THE INFORMATION IN THIS SOFTWARE IS  SUBJECT TO CHANGE WITHOUT NOTICE   *
;*  AND  SHOULD  NOT  BE  CONSTRUED AS  A COMMITMENT BY DIGITAL EQUIPMENT   *
;*  CORPORATION.							    *
;* 									    *
;*  DIGITAL ASSUMES NO RESPONSIBILITY FOR THE USE  OR  RELIABILITY OF ITS   *
;*  SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DIGITAL.		    *
;* 									    *
;*									    *
;****************************************************************************
;
;+
;
; FACILITY:
;
;	VAX/VMS SCSI Disk Class Driver
;
; ABSTRACT:
;
;	This module contains the disk class driver to control read/write
;	and read-only disks on the SCSI bus. The driver can have at most
;	one active I/O per device on the SCSI bus at any one time. This
;	driver relies on the SCSI port driver to provide the low-level 
;	communication of commands and data over the SCSI bus.
;
; AUTHOR:
;
;	Jim Klumpp	4-May-1988
;
; REVISION HISTORY:
;
;       X-45    JJF0040         J. Jeffery Friedrichs   17-Nov-1995
;               Backport the following fix from THETA
;
;               X-31  GWW002            Grace Wang              26-May-1995
;               Fix << the MOUNT/CLUSTER/NOWRITE does not write lock on
;               the node which own the disk>> problem
;
;	X-44	TGG0111		Tom Goodwin			 7-Apr-1995
;		Add code to change CTRLERR,DRVERR and TIMEOUT errors to
;		MEDOFL which will force mount verify to be invoked and retry
;		the command.
;
;	X-43	TGG0003		Tom Goodwin			20-Oct-1994
;		V6 QAR #774 - When a cancel I/O is issued, the
;		UCB$V_GK_CHK_COND bit in the UCB$L_DK_FLAGS is 	cleared if
;		the process ID matches the UCB$L_GK_PID.  The UCB$L_GK_PID
;		will also be cleared.
;
;	X-42	TGG0002		Tom Goodwin			19-Apr-1994
;		V6 QAR #710 The UCB$B_SCSI_VERSION field only checks for
;		CCS and SCSI-2. Checks for SCSI-1 value need to be added.
;		
;	X-41	KFM		Kevin F. Martin			07-Feb-1994
;		Add DDR support.
;
;	X-40	TGG0001		Tom Goodwin			 3-Feb-1994
;
;		1) V6 QAR #584 Add FDT dispatch for IO$_DSE function
;		   code to DK_DSE routine.
;		2) Found during work on above QAR, IO_DSE routine needs
;		   to segment large DSE's (>63.5kB) into multiple
;		   SCDRP's to prevent crash in PK_DRIVERs.
;
;		RCL0001		Rick Lord			17-Dec-1993
;
;		1) Change amount of data accepted in response to a 6-byte 
;		   MODE SENSE command from 150 to 255 bytes in both the CDB
;		   and the DMA_LEN field to prevent pages from being
;		   truncated.
;
;		2) Modify the size of the MODE SENSE data section of an 
;		   error log packet to allow up to 255 bytes to be logged.
;
;		3) Add checks to MODE_SENSE and MODE_SENSE_CHANGABLE to
;		   prevent garbage from being read as size bytes (this would
;		   have happened if a page code was the last byte of a 
;		   transfer).
;
;		4) Add Control Mode QERR and EECA bits to bits checked in 
;		   MODE_SENSE_CHANGABLE which will cause an error to be 
;		   logged if found to be in wrong state and unchangable.
;
;		5) Change UCB$B_CTRL_MODE to UCB$B_CTRL_MODE_PAR to match
;		   the naming format of the other saved MODE SENSE pages.
;
;		6) In MODE_SELECT add Control Mode QERR and EECA to the bits
;		   which will cause a MODE SELECT to be issued if found to be
;		   in the wrong state.
;
;               7) Retry a command if it fails with a SENSE KEY of 0 - the
;                  conditions under which this may occur either do not apply
;                  to direct access devices, are caught by MODE SENSE or else
;                  do not even indicate an error condition.
;
;		8) Increased the size of the UCB Control Mode MODE SENSE
;		   pages to accomodate Seagate drives which are violating
;		   the SCSI spec by returning too much data.
;
;	        9) Don't try to clear ARRE or AWRE in the Control MOde page if
;		   the device is SCSI-2 or later, as the BBR algorithms on
;		   these later devices are expected to be up to snuff.
;
;	X-39	KFM		Kevin F. Martin			08-Dec-1993
;		V552H4 QAR #121: Clear GK I/O active flag when read access to 
;		command buffer is denied.
;
;       X-38    MCY             Mary Yuryan                     14-May-1993
;               Fold in J. Guineau's AUDIO bugfix.
;               WJG             John Guineau                    09-Apr-1993
;               Fix error paths in IO$_AUDIO FDT/STARTIO routines related
;               to bad/improperly initialized AUCB fields.
;
;	X-37	RCL		Rick Lord			6-May-1993
;
;		Define OPTICAL and WORM bits in UCB$L_DK_FLAGS.
;
;		Modify INQUIRY subroutine to set OPTICAL bit in UCB$L_DK_FLAGS
;		if the SCSI device type code is optical, or the WORM bit if the
;		device type code is WORM.
;
;		Modify MODE_SENSE to set HWL and SWL if the device is WORM, or
;		if it's Optical with WORM media mounted; also, do not require
;		track and sector information to be returned from Mode Sense
;		for Optical devices.
;
;	X-36	MCY		Mary Yuryan 		     	12-Apr-1993
;		Add the following SCSI devices - RW504, RW510, RW514, 
;		RW516, RWZ51, RWZ52, RWZ53, RWZ54, RWZ31 - RZ28L, RZ26M - 
;		EZ31, EZ32, EZ33, EZ34, EZ35, EZ31L, EZ32L, EZ33L, EZ56R 
;		- HSZ15.
;			
;	X-35	WJG	W. John Guineau			6-Apr-1993
;		- No changes, FOLD VIKING TCQ stream into CORAL, only
;		  changed .IDENT to match CORAL stream
;		- uncomment the define for V60_BUILD for Coral.
;
;	X-18A7A7-Q WJG	W. John Guineau			 15-Mar-1993
;		- Lock out other processes in IO$_DIAGNOSE while 
;		  a an I/O is pending for one GK process. Also, 
;		  Make sure process for a pending check condition is 
;		  the process that caused it.
;
;	X-18A7A6-Q WJG	W. John Guineau			 3-Mar-1993
;		- Make TEST_UNIT_READY send TUR as NON-QUEUED which
;		  will still sync queues in a queued environment, but
;		  works around an HSZ10 bug where the first queued command
;		  sent when a unit attention is pending get's eaten.
;
;	X-18A7A5-Q WJG	W. John Guineau			 2-Feb-1993
;		- Set UCB$M_BSY during PACKACK just to be safe.
;		- Add BBR_IN_PROG flag to UCB$L_DK_FLAGS. This bit
;		  is set when a BBR starts, cleared when it completes.
;		  If BBR_IN_PROG is set when a second thread starts
;		  a BBR operation, that thread will FORK_WAIT until
;		  it clears. This prevents more than one thread from
;		  attempting a BBR operation at one time.
;		- Move UCB$L_SCDRP_SAV2, UCB$L_ADDNL_INFO and
;		  UCB$B_SENSE_KEY into SCDRP for multi-threaded
;		  synchronization.
;
;	X-18A7A4-Q WJG	W. John Guineau			 15-Jan-1993
;		- Add a G^ to the call to EXE$ALONONPAG in READ_CD_MCN
;		  to fix crash.
;
;	X-18A7A3-Q WJG	W. John Guineau			 11-Jan-1993
;		- Fix floppy formatting bug in MODE_SENSE where a 
;		  BGEQ was being used instead of BLSS causing
;		  UCB$L_FORMAT_PRAMS to never be allocated.
;		- Add a '-Q' to the compiled in image ident (.IDENT) for
;		  easy detection of command queuing based drivers.
;
;	X-18A7A2 WJG	W. John Guineau			 8-Jan-1993
;		- Remove unused/debug code
;		- Add HSZ20, HSZ40 (FIB) to CHECK_FOR_RAID macro
;		- fix bug in histogram which would corrupt pool on
;		  xfers larger than 127 blocks
;		- Deal with MODE_SENSE page 0x0A (queueing parameters)
;		  to make sure the QErr bit is not set
;		- Attempt to clear the ARRE/AWRE bits in MODE_SENSE page 1
;		- Make LUN information handling in INQUIRY SCSI-2 compliant
;
;	X-18A7A1 WJG	W. John Guineau			 12-17-1992
;               - Add CHECK_FOR_RAID_DEVICE macro and UCB$V_RAID
;                 bit. RAID devices use 10 byte mode sense/select
;                 commands.
;               - Fix bug in copying of additional sense bytes
;                 in SAVE_ADDNL_INFO: to do a signed compare of
;                 length of additional sense data against length
;                 of buffer field in UCB
;		- Add command queuing support
;		- merge BLADE X-31,X-32 changes for VIKING Checkin:
;       	X-32    MCY             Mary Yuryan
;               	Add "B" variant disks to device recognition tab
;               	RZ27B, RZ28B, RZ29B, RZ73B, RZ74B, RZ75B.  Add 
;               	Add HSZ20,HSZ40 - Raid subsystem.              
;                                                              
;       	X-31    MCY             Mary Yuryan            
;               	Fix 2 bugs in the ATTEMPT_REORDER routine. 
;               	1)Update R4 correctly so as not to alter the be
;                 	the elevator algorithm; currently the scan di  
;                 	be effected.                                   
;               	2)Provide UCB$L_FAIRNESS_CNT (with value of 4) 
;                 	malicious user from looping infinitely while   
;                 	same LBN.  Checks added in SWAP_IRP:, SCAN_DO  
;                 	PERFECT_IRP:.  This could slow or even hang t  
;
;	X-30	JSSBLADE2	John S. Simakauskas		 1-July-1992
;		Fix build bug introduced in X-29,
;               SCSI_DEV_TYPES table was made too long.
;
;	X-29	JSSBLADE	John S. Simakauskas		24-June-1992
;		Add device recognition for RZ26L,RRD43,RRD44.
;
;	X-28	MCY		Mary Yuryan		12-Jun-1992
;		Always return good status in the READ_CD_MCN routine
;		to keep 3rd party CDROMS that do not support SCSI-2 
;		from failing. 
;
;	X-27	MCY		Mary Yuryan 		03-Jun-1992
;		Add check for RRD40S device type in IO_PACKACK, and 
;		branch around READ_CD_MCN for audio if device is 
;
;	X-26	MCY		Mary Yuryan		03-Apr-1992
;		Add ASSUME statement before $DEFEND of UCB to check for
;		illegal System disk UCB expansion.  Value is calculated
;		in longwords.
;
;	X-25    MCY	        Mary Yuryan		11-Mar-1992
;		Add latent support for RZ28 and RZ29.  
;		Fold in John Guineau's AUDIO fix from AMBER - Change 
;		UCB$M_CDROM symbol to UCB$V_CDROM in BBC instruction
;		in IO_PACKACK routine. 
;
;       X-24    MCY             Mary Yuryan               20-Feb-1992
;		Add latent support for RAID controller - HSZ10.
;
;               Fold in Richard Napolitano's changes for AUDIO fix.
;               RLN             Richard L. Napolitano   15-Dec-1991
;               1) Format zero workaround
;               2) Modified LOG_EXTND_SENSE not to log read errors
;                  to CDROM's being mounted foreign.
;
;	X-23	BP4282		Brian Porter		04-FEB-1992
;		Use an ADAWI to increment and decrement UCB$W_QLEN.
;
;		Sync .IDENT to VSC.
;
;	X-21	MCY		Mary Yuryan		09-Jan-1992
;		Fix bug in GET_DEVICE_TYPE - Restore R0 containing 
;		INQUIRY command data pointer.
;
;	X-20	MCY		Mary Yuryan 		23-Dec-1991
;		Merge AUDIO and Latent device support with C2. 
;
;       X-16A1A1A1 FAK003       Forrest A. Kenney       26-Nov-1991
;               Merge latest round of BLADE fixes.
;
;       X-19    WJG0048         W. John Guineau         14-Nov-1991
;               Add DDR support. UCB$L_DISABLE_DDR is a flag to disable
;               the use of DDR. If non-zero, no DDR will be used.
;
;	X-18A1  MCY		Mary Yuryan		11-Nov-1991
;	        Merged Rich Napolitano's AUDIO changes into the driver.
;		Redefine READPROMPT function to AUDIO using the same IODEF
;		symbol value for READPROMPT.
;
;			Richard L. Napolitano		
;		Implemented AUDIO extension to DKDRIVER to allow AUDIO
;		commands to be sent to SCSI II compliant CD-ROM devices.
;		These extensions allow the execution of CD AUDIO commands
;		from application programs.
;
;				Mary Yuryan		10-Dec-1991
;		Add latent device support: RZ27,RZ37,RZ38,RZ75,RZ59,
;		EZ51,EZ52,EZ53,EZ54,EZ58,RZ13,RZ14,RZ15,RZ16,RZ17,RZ18,
;		RZ34L,RZ35L,RZ36L.
;
;	X-18	MCY		Mary Yuryan		10-Oct-1991
;		Add latent support for the RZ26, RZ36, adn RZ74.
;		(VSC Ident change.)
;-------------------------------------------------------------------------------
;
;       X-32    MCY             Mary Yuryan / John Guineau  2-Oct-1991
;               Fix DK_CTRL_INIT routine to properly save CDDB initialization
;               registers before forking.
;
;       X-31    JTK             Jim Klumpp              13-Aug-1991
;               Change to place in the driver where ATTEMPT_REORDER
;               is called to the end of STARTIO so third-party disk
;               caching products can continue to intercept I/Os at
;               the front of STARTIO. Change the ATTEMPT_REORDER
;               routine to sort only the pending queue, ignoring
;               the active IRP at UCB$L_IRP. Terminate the pending
;               queue scan after a finite of IRPs have been considered
;               for reorder.
;
;	X-30	WJG		W. John Guineau		16-May-1991
;		Use EXE$ZEROPARM as FDT routine for IO$_NOP to solve
;		crashes from this function code. 
;
;	X-27U2	JJM		Jeff McLeman		21-Mar-1991
;		Increase floppy disconnect timeouts to 30 seconds.
;
;	X-27U1	MCY	Mary Yuryan			20-Feb-1991
;		Add new SCSI devices: RWZ01 (SCSI_MO), RZ24L, RZ25L,
;		RZ55L, RZ56L, RZ57L.
;
;	X-27	KAH	Kenneth A. House		08-Feb-1990
;		Add support for new SCSI-FDI adapter, including
;		1)  RX23s, RX33s diskette drives may now use synchronous
;		    data transfers;
;		2)  support RX26 diskette drive, including
;		    a)  format DD, HD, and ED diskettes;
;		    b)  add two-bit FLOPPY_MEDIA field to UCB$L_DK_FLAGS;
;		    c)  fill in FLOPPY_MEDIA field in the MODE_SENSE routine;
;		3)  delete misleading comments.
;
;	X-26	JTK	Jim Klumpp			30-Jan-1991
;		Add a workaround for RZ23 read long bug. After a read 
;		long command is sent to the RZ23, the next mode select
;		command fails with an illegal request sense key. The
;		fix is to simply retry the mode select command once.
;
;	X-25	MCY	Mary Yuryan			25-Jan-1991
;		Increase RRD42 Disconnect timeout to 120 seconds.
;
;	X-14U12	GH001	Gary Hughes			21-Dec-1990
;		Fix up media ID mismatch for RZ23L, RZ57I.
;
;	X-14U11	JTK	Jim Klumpp			11-Dec-1990
;		1) Add new devices: RZ72, RZ73, RZ35.
;
;		2) In SET_UNIT_ONLINE, set the LCL_VALID bit in the UCB 
;		if the PACKACK completes successfully for the system
;		disk. This is required for host-based shadowing booting.
;
;	X-14U10	JTK	Jim Klumpp			28-Nov-1990
;		1) Change the disconnect timeout time for the RRD42 to 
;		45 seconds.
;
;		2) Add host-based shadowing support including routines to
;		check that a device is capable of supporting HBS (CHECK_HBS),
;		to force uncorrectable ECC errors on a set of blocks
;		(FORCE_ERROR), and to erase a set of blocks (IO_DSE).
;
;		3) Fix clearing of LCL_VALID bit in UCB at the end of 
;		IO_PACKACK.
;
;		4) Use byte count divisor returned by SPI$CONNECT in 
;		DK_UNIT_INIT.
;
;		WCY0146	Wai C. Yim			 7-Nov-1990
;		1) Add code to create CDDB in controller init.
;
;		2) Change all references of EXE$GL_SYSUCB to SYS$AR_BOOTUCB
;		Since it is no longer the same for shadowing.
;
;		3) Add shadowing booting support.  Synchronize with DSA
;		class driver by checking for different SYS$AR_BOOTUCB
;		from EXE$GL_SYSUCB.
;
;		4) Set SCSI bit in DEVCHAR2 in all devices by means
;		of DPT_STORE UCB macro.
;
;		5) Add CRESHAD and REMSHAD FDT routines.
;
;	X-14U9	BCL043  Barbara C. Leahy		27-Nov-1990
;		Added context save on IO_DIAGNOSE function for mixed-mode
;		media support.
;
;	X-14U8	JTK	Jim Klumpp			26-Oct-1990
;		Send a request sense command immediately after sending a
;		mode select to clear out a possible pending check condition
;		that could otherwise cause a subsequent command to fail.
;		Also, in TRANS_SENSE_KEY, treat mode select change status
;		as normal. An assumption here is we're not running in a
;		multi-initiator environment.
;
;	X-14U7  MCY	Mary Yuryan			29-Oct-1990
;		Change the spinup timeout value from 20 to 30 seconds
;		to accomodate the RZ25.  Change READY_POLL_INTERVAL to 1,
;		and READY_POLL_CNT to 30.
;	
;
;	X-14U6	JTK	Jim Klumpp			19-Sep-1990
;		Add processing of LCL_VALID bit in IO_FORMAT and IO_PACKACK.
;		Call SET_CONN_CHAR in SET_UNIT_ONLINE to prevent format
;		operations from timing out before a PACKACK function has
;		successfully executed.
;
;	X-14U5  MCY	Mary Yuryan 			23-Aug-1990
;		Change the symbol definition of LO_COST_CD to the 
;		correct device name of RRD42.
;
;	X-14U4	JTK	Jim Klumpp			13-Jul-1990
;		WJG	W. John Guineau			04-Jun-1990
;		Added UCB$B_SCSI_VERSION to save INQUIRY response data
;		format (SCSI version). This allows version-specific
;		code paths in mode select, where the setting of the
;		non-changable parameters is incompatible between SCSI-1
;		(CCS) and SCSI-2.
;
;		Fix IO$_WRITECHECK function. Prior to this change, this
;		function was identical to a write with the datacheck 
;		modifier. The correct operation is to compare the data
;		in the user's buffer with that on the disk, and not 
;		modify the contents of either the user's buffer or the 
;		disk.
;
;	X-14U3	JTK	Jim Klumpp			6-Jun-1990
;		1) In SETUP_CMD, map the buffer with high priority to avoid
;		deadlock. The map buffers elsewhere can remain at low
;		priority, as there's no danger of deadlock on the first
;		call to map buffer per QIO function.
;
;		2) Calculate the value for UCB$W_CYLINDERS using the same 
;		algorithm as DUDRIVER so that all nodes in a cluster see
;		see the same geometry.
;
;		3) Work around a bug in RZ5x drives which cause them to
;		report busy SCSI status indefinitely after a reselection
;		timeout. Pull on bus reset if this situation is detected.
;
;	X-14U2	JTK	Jim Klumpp			20-Dec-1989
;		Use extended read and write SCSI commands for transfers
;		to blocks above 1FFFFF (hex).
;
;	X-14U1	JTK	Jim Klumpp			1-Dec-1989
;		Add seek optimization routine.
;
;	X-14	JTK	Jim Klumpp			22-Sep-1989
;		Return MEDOFL status when an attempt is made to mount a
;		removable device whose media is not inserted. Increase
;		default disconnect timeout time for all RZ disks to 20
;		seconds.
;
;	X-13	JTK	Jim Klumpp			20-Sep-1989
;		Fix setting of disconnect timeout for floppy formatting.
;
;	X-12	RLN	Richard L. Napolitano		29-Aug-1989
;		Workaround a microcode problem in the RZ55 where during
;		recovered with retry read operation, the RZ55 returns
;		an additional block if the DTE bit is set.  
;
;
;	X-11	RLN	Richard L. Napolitano		25-Aug-1989
;		Fix problem where during a READ operation of a transfer
;		with a bad block that is reassigned, the blocks following
;		the reassigned blocks are erroneously written.
;
;	X-10	DGB0317	Donald G. Blair			05-Aug-1989
;		Add DPT$V_NO_IDB_DISPATCH bit to the driver prologue table.
;		In SETUP_CMD, use correct register to set up timeout
;		fields.
;
;	X-9	JTK	Jim Klumpp			12-Jul-1989
;		Add LUN support. Don't allocate the IRP portion of
;		the SCDRP. Remove references to the SCDT. Increase
;		number of arguments passed to SET_CONN_CHAR. Change
;		priv required for IO_DIAGNOSE function.
;               
;	X-8	JTK	Jim Klumpp			22-Jun-1989
;		Add callback support and data structure version
;		checking for SPI$CONNECT. Add fastboot support.
;		Fix logging of reassign block error. Check HWL bit
;		to prevent writes to a write-locked disk. Fix
;		handling of media changes in TRANS_SENSE_KEY.
;
;	X-7	JTK,KAH	Jim Klumpp, Kenneth A. House	15-Jun-1989
;
;		1) Change descriptor length in reassign parameter list to 4.
;		2) Fix checking of read/write flag in DK_DIAGNOSE.
;		3) Increase disconnect timeout for reassign command.
;		4) Allow writing of double-density (DD) microfloppies only
;		   when the UCB$L_DK_FLAGS<DD_BYPASS> flag is asserted, 
;		   which it won't be unless the driver has been patched.
;		   Changes include
;		   A) add DD_BYPASS flag to UCB$L_DK_FLAGS,
;		   B) clear DD_BYPASS in UNIT_INIT to provide space for
;		      an in-place patch,
;		   C) set UCB$L_DEVCHAR<SWL> flag in MODE_SENSE routine if an
;		      RX23 diskette has a 250 KHz transfer rate (implies DD),
;		   D) adding hooks for DD format and geometry parameters in
;		      MODE_SELECT_FORMAT_FLOPPY (only if DD_BYPASS is set).
;		5) Fix branch destination of a BBSS used for a bit set in
;		   MODE_SENSE routine.
;
;	X-6	JTK,KAH  Jim Klumpp, Kenneth A. House	25-Mar-1989
;		A number of misc changes for PVAX1 including:
;		1) Update ident to match that on master pack.
;		2) Add pass-through routine.
;		3) In INQUIRY, use just 5 bits if peripheral device type 
;		   and 4 bits of response data format to conform to SCSI-2.
;		4) Change SCSI status byte mask from ^XE1 to ^XC1 to conform
;		   to SCSI-2.
;		5) In TRANS_SENSE_KEY, ignore unit attentions caused by media
;		   changes if the device is not volume enable to avoid mount 
;		   failures.
;		6) Fix R6 bug in MODE_SELECT_FORMAT_FLOPPY.
;		7) Ensure unformatted diskette causes MODE_SENSE to return
;		   SS$_FORMAT status.
;		8) Move call of MODE_SENSE_CHANGABLE from within MODE_SENSE
;		   to follow each call to MODE_SENSE
;		9) Allow MODE_SENSE to return SS$_FORMAT in IO_FORMAT routine
;		   (implies that the diskette isn't currently formatted).
;	       10) Avoid logging error when MODE_SENSE detects an unformatted
;		   floppy.
;
;	X-20-B	KAH079	Kenneth A. House		04-May-1989
;		Increase size of MODE_SELECT maximum data transfer;
;		Merge changes from Jim Klumpp & Lee Leahy.
;
;		X-20    JTK	Jim Klumpp		31-Mar-1989
;			Clear UCB$L_BCNT in SETUP_CMD if no data is to
;			be transferred to prevent possible corruption.
;
;		X-19    LPL     Lee Leahy               29 Mar 1989
;			Changed media ID for generic DK devices from
;			0 to 22C8BC0 (DK-DKX0) to enable them to be
;			properly served via the MSCP.
;
;	X-18-B	KAH073	Kenneth A. House	17-Apr-1989
;		Debugging;
;		Merge changes from Jim Klumpp.
;
;	X-18-A	KAH070	Kenneth A. House	14-Mar-1989
;		Add SCSI floppy support.
;
;	X-18	JTK	Jim Klumpp		27-Jan-1989
;		Add MCSP extension to UCB.  Advance ident to match
;		CMS generation on LES pack.
;
;	X-4	JTK	Jim Klumpp		19-Jan-1989
;		Perform minimum revision checking on devices.
;
;	X-3	JTK	Jim Klumpp		13-Jan-1989
;		Clean up comments, disable tracing for SDC release.
;
;	X-2	RLN 	Richard L. Napolitano 	20-DEC-1988
;
;		1) Remove loop in MODE_SENSE_CHANGEABLE.
;		2) Complete fix to ignore DCR bit in MODE_SENSE routine 
;		3) Fix register useage for flexible disk geometry page.
;-


	.SBTTL	+
	.SBTTL	+ SYMBOL DEFINITIONS
	.SBTTL	+
	.SBTTL	External symbol definitions
;
; External symbols
;

	$ARBDEF				; Define ARB offsets
	$CANDEF				; Cancel reason codes
	$CDDBDEF			; Class Driver Data Block
	$CRBDEF				; Channel request block
	$DCDEF				; Device classes and types
	$DDBDEF				; Device data block
	$DEVDEF				; Device characteristics
	$DYNDEF				; Data strucure types
	$EMBDEF				; Errorlog message buffer
	$FKBDEF				; Define fork block symbols
	$IDBDEF				; Interrupt data block
	$IODEF				; I/O function codes
	$IPLDEF				; Hardware IPL definitions
	$IRPDEF				; I/O request packet
	$IRPEDEF			; I/O request packet extension
	$MSCPDEF			; Define MSCP symbols
	$NSADEF				; Security Symbols
	$ORBDEF				; Object Rights block
	$PCBDEF				; Process control block
	$PRVDEF				; Privilege mask
	$PTEDEF				; Page table entry symbols
	$SCDRPDEF			; SCSI SCDRP symbols
	$SPDTDEF			; SCSI PDT symbols
	$SSDEF				; System status codes
	$UCBDEF				; Unit control block
	$VADEF				; Virtual address symbols
	$VECDEF				; Interrupt vector block


	.SBTTL	Misc local symbols
;
; Local symbols
;

	V60_BUILD = 1			; Enable VMS V6.0 features

	COLLECT_PERF_DATA = 1		; Enable collection of various stats

;	DEBUG = 1			; Flag to enable various tracing and
					; debug features.
	.IF DEFINED DEBUG
	.print ; - DEBUG flag is enabled
	.ENDC

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




	SCDRPS_PER_UNIT		= 15	; Number of SCRPs to pre-allocate per unit
	PACKACK_RETRY 		= 20	; Number of times to retry PACKACK
	READY_POLL_INTERVAL	= 1	; Interval for test unit ready polling
	READY_POLL_CNT		= 90	; Number of times to poll for unit ready

	MAX_BCNT		= <<64*2>-1>*512 ; Maximum byte count per 
						 ; transfer = 64KB - 1 page
					
	FMT_PARAM_SIZE		= 24	; Space for format page params
	FLX_PARAM_SIZE		= 32	; Space for flex disk page params
	UCB_REC_PARAM_SIZE	= 12	; Space in UCB for error recovery params
	UCB_CTRL_MODE_SIZE	= 8+4	; Space in UCB for control mode params
	RW_RETRY_CNT		= 30	; Read/write retry count
	REASSIGN_RETRY_CNT	= 3	; Retry count for block reassignment
	REWRITE_RETRY_CNT	= 30	; Retry count for rewrite after reassign	
	HWERR_RETRY_CNT		= 60	; Retry count for hardware errors

	DEFAULT_DISCONNECT_TIMEOUT = 200 ; Default values in seconds for disconnect
	DEFAULT_PHASE_CHANGE_TIMEOUT = 40; and phase change timeouts
	RRD40_DISC_TMO 		= 40	; RRD40 disconnect timeout value
	RRD42_DISC_TMO 		= 120	; RRD42 disconnect timeout value
	RX_DISC_TMO 		= 60	; Floppy disconnect timeout value
	RX_PHS_TMO		= 10	; Floppy phase change timeout value
	RX_FMT_DISC_TMO		= 30*60	; Floppy format disconnect timeout value
	REASSIGN_DISCON_TMO	= 200	; Reassign disconnect timeout

	SS$_RECOVERR		= 2	; Recoverable error detected. Note that
					; this status code is used internlly
					; to distinguish between recoverable and
					; non-recoverable errors, but is never
					; returned as a QIO status.
	DK_ERROR_REVISION = 2		; Errorlogging revision supported by
					; this driver. This should be incremented
					; each time an incompatible change is
					; made to the errorlog packet format.

	DTE_EXTRA_BYTES	= 512		; Number of additional bytes received
	RZ55_MIN_NODTE_ERROR = ^A'0900'	; Mininum revision with fixed microcode

	EXTND_LBN_LIMIT	= ^X1FFFFF	; Maximum LBN for which a normal read or
					; write command can be used. The extended
					; read/write commands must be used for
					; LBNs above this value.

	MAX_BUSY_TIME	= 1000		; Maximum amount of time to allow a
					; device to remain busy before resetting
					; the bus (very long for robots)
	SENSE_LEN	= 18		; Sense data length
	SENSE_ALLOC	= 255		; Sense data allocation length

	CD_MCN_LEN	= 24		; Media Catalog Number (UPC/Bar code)
					;  sub-channel data for CD-ROMs
	P1		= 0		; First QIO argument
	P2		= 4		; Second QIO argument
	P3		= 8		; First QIO argument
					
; Histogram buckets are expressed as a power of 2 to make collection 
; fast - basically we logically shift the datum by the bucket factor
; to get a bucket index.
					
	XLEN_HIST_BUCKET_SHIFT = 2	; If MAX_XFER is 128, 128>>2 = 32 buckets
	XLEN_HIST_BUCKETS = 32		; we will round down to 64K (128blk) xfers
	XLEN_HIST_SIZE = <<128/4>*4>	; 1 long/bucket
	XLEN_HIST_TURNOVER = 20		; Recalc qdepth after this many I/O


	.SBTTL	SCSI status codes

; Define SCSI status codes

	SCSI$C_GOOD_STATUS	= 0
	SCSI$C_CHK_CONDITION	= 2
	SCSI$C_CONDITION_MET	= 6
	SCSI$C_BUSY		= 8
	SCSI$C_RESERV_CONFLICT	= 26
	SCSI$C_QUEUE_FULL	= 40


	.SBTTL	Peripheral devices types

; Define peripheral device type fields returned by the INQUIRY command

	SCSI$C_DISK		= 0
	SCSI$C_TAPE		= 1
	SCSI$C_PRINTER		= 2
	SCSI$C_CPU		= 3
	SCSI$C_WORM		= 4
	SCSI$C_CDROM		= 5
	SCSI$C_OPTICAL		= 7


	.SBTTL	Sense key codes

; Define SCSI sense key codes.

	SCSI$C_NO_SENSE		= 0
	SCSI$C_RECOVERED_ERROR	= 1
	SCSI$C_NOT_READY	= 2
	SCSI$C_MEDIUM_ERROR	= 3
	SCSI$C_HARDWARE_ERROR	= 4
	SCSI$C_ILLEGAL_REQUEST	= 5
	SCSI$C_UNIT_ATTENTION	= 6
	SCSI$C_DATA_PROTECT	= 7
	SCSI$C_BLANK_CHECK	= 8
	SCSI$C_VENDOR_UNIQUE	= 9
	SCSI$C_COPY_ABORTED	= 10
	SCSI$C_ABORTED_COMMAND	= 11
	SCSI$C_EQUAL		= 12
	SCSI$C_VOLUME_OVERFLOW	= 13
	SCSI$C_MISCOMPARE	= 14
	SCSI$C_READ_RETRIES	= ^X17
	SCSI$C_MEDIA_CHANGE	= ^X28
	SCSI$C_MOD_SEL_CHANGE	= ^X2A

; These might be handy add'l sense codes to have defined for the floppy,
; but they're not yet used.
;
	SCSI$C_INCOMPATIBLE_CARTRIDGE	= ^X30	; Unformatted media ?
						; Incompatible format ?
						; Incompatible medium ?
	SCSI$C_DRIVE_NOT_READY		= ^X04	; Drive initializing ?
						; Format in progress ?
	SCSI$C_MEDIUM_NOT_PRESENT	= ^X3A	; Medium not present ?

	READ_LONG_DATA_LEN = 1024		; Maximum data length associated
	WRITE_LONG_DATA_LEN = 1024		; with read long and write long
						; commands, respectively.
; Argument list offset definitions
 
	P1=0					; First function dependent parameter
	P2=4					; Second function dependent parameter
	P3=8					; Third function dependent parameter
	P4=12					; Fourth function dependent parameter
	P5=16					; Fifth function dependent parameter
	P6=20					; Sixth function dependent parameter

; seek reordering stuff

	FAIRNESS_CNT = 4


	.SBTTL	Disk class driver extensions to the UCB
;
; Disk class driver extensions to the UCB.
;

	$DEFINI	UCB			; Start of UCB definitions

	. = UCB$K_MSCP_DISK_LENGTH
	
$DEF	UCB$L_SCDRP	.BLKL	1	; Address of active SCDRP
$DEF	UCB$L_SCDRP_SAV1 .BLKL	1	; Address of saved SCDRP
$DEF	UCB$L_STACK_SCDRP .BLKL	1	; Address of spare SCDRP used soley for it's
					;   SCDRP$L_CLASS_STACK when no SCDRP is available
$DEF	UCB$L_FLUSH_IOQFL .BLKL	1	; I/O flush queue forward link
$DEF	UCB$L_FLUSH_IOQBL .BLKL	1	; I/O flush queue backward link
$DEF	UCB$L_ERR_MASK	.BLKL	1	; Mask of error types logged so far
$DEF	UCB$L_TRACE_BUF	.BLKL	1	; Address of trace buffer
$DEF	UCB$L_SCDT	.BLKL	1	; SCDT address
$DEF	UCB$L_DK_FLAGS	.BLKL	1	; Class driver flags
	$VIELD	UCB,0,<-		;
		<REMOVABLE,,M>,-	; Removable media
		<FIRST_ATTN_SEEN,,M>,-	; First unit attention seen (used to
		-			; prevent logging an error for the 
		-			; first unit attention seen)
		<SPINUP_INPROG,,M>,-	; Spin-up in progress
		<DISCONNECT,,M>,-	; Device supports disconnect
		<SYNCHRONOUS,,M>,-	; Device supports synchronous operation
		<MODE_SENSE_PAG1,,M>,-	; Modes sense page 1 recieved
		<MODE_SENSE_PAG10,,M>,-	; Modes sense page 10 recieved
		<DISABL_ERRLOG,,M>,-	; Disable errorlogging
		<OUT_OF_REV,,M>,-	; Device is out-of-rev
		<HWL,,M>-		; Hardware write-locked
		<FLOPPY,,M>,-		; Device is a flexible disk drive
		<FORMAT,,M>,-		; Device supports FORMAT opcode
		<NOREASSIGN,,M>,-	; Drive doesn't support REASSIGN_BLOCK
		<DD_BYPASS,,M>,-	; Allow writing of DD diskette
		<HBS_CHECK,,M>,-	; Check made for host-based shadowing
		<FLOPPY_MEDIA,2,M>,-	; Diskette type in floppy drive
				-	;   0 - none, or unknown
				-	;   1 - DD
				-	;   2 - HD
				-	;   3 - ED
		<CDROM,,M>,-		; Device is a CD-ROM
		<CD_VALID,,M>,-		; CD media change occured if bit is 
			      -		; cleared. Used for Audio CD's, which 
			      -		; are not mounted, to indicate UCB 
			      -		; stored CD-ROM Sub-channel data is 
			      -		; valid.
		<RAID,,M>,-		; Set if device is known to be an array
		<PORT_CMDQ,,M>,-	; Set if port supports command queing
		<CMDQ,,M>-		; Device supports command queing
		<GK_CHK_COND,,M>,-	; Check cond seen by IO$_DIAGNOSE func
		<GK_ACTIVE,,M>,-	; I/O active on IO$_DIAGNOSE
		<BBR_IN_PROG,,M>,-	; BBR in progress - lock access to UCB
		<OPTICAL,,M>,-		; Device is an optical device
		<WORM,,M>>		; Device is a WORM device, or an optical
					;  device with WORM media

$DEF	UCB$W_PHASE_TMO .BLKW	1	; Phase change timeout
$DEF	UCB$W_DISC_TMO .BLKW	1	; Disconnect timeout
$DEF	UCB$L_SCDRPQ_FL	.BLKL	1	; Queue of free SCDRPs used to 
$DEF	UCB$L_SCDRPQ_BL	.BLKL	1	; send SCSI commands
$DEF	UCB$B_RW_RETRY	.BLKB	1	; Read/write retry count
$DEF	UCB$B_REASSIGN_RETRY .BLKB 1	; Reassign block retry count
$DEF	UCB$B_REWRITE_RETRY .BLKB 1	; Rewrite after reassign retry count
$DEF	UCB$B_READY_RETRY .BLKB	1	; Polling count for unit ready
$DEF	UCB$B_HWERR_RETRY .BLKB	1	; Hardware error retry count
$DEF	UCB$W_BLOCK_SIZE .BLKW	1	; Number of bytes per block
$DEF	UCB$B_SENSE_INFO .BLKB <SENSE_LEN-8>; Balance of additional sense info
$DEF	UCB$B_SENSE_LEN  .BLKB  2	; Count of additional bytes
$DEF	UCB$L_TR_QIO_STS .BLKL	1	; Address in trace buf to put QIO status
$DEF	UCB$L_TR_SCSI_CMD .BLKL	1	; Address in trace buf to put QIO status
$DEF	UCB$L_TR_SCSI_MSG .BLKL	1	; Address in trace buf to put QIO status
$DEF	UCB$L_TR_SCSI_STS .BLKL	1	; Address in trace buf to put QIO status
$DEF	UCB$L_HW_REV	.BLKL	1	; Hardware revision info from inquiry
$DEF	UCB$L_CUR_LBN	.BLKL	1	; Current LBN, used for read/write
					; reordering
$DEF	UCB$L_BUSY_TIME	.BLKL	1	; Time at which bus should be reset if
					; device remains busy
$DEF	UCB$B_RECOV_PAR .BLKB UCB_REC_PARAM_SIZE
						; Error recovery parameters
$DEF	UCB$B_RECOV_CPAR .BLKB UCB_REC_PARAM_SIZE
						; Err rec changable parameters
$DEF	UCB$B_CTRL_MODE_PAR .BLKB UCB_CTRL_MODE_SIZE
						; Control mode parameters
$DEF	UCB$B_CTRL_MODE_CPAR .BLKB UCB_CTRL_MODE_SIZE
						; Control mode changable parameters
$DEF	UCB$A_FORMAT_PARAM .BLKL 1		; Address of sense information
						; needed to format floppies
$DEF	UCB$L_SAVE_CONN_CHAR .BLKL 1	; Address of saved connection
					; characteristics buffer for IO 
					; diagnose buffer
$DEF	UCB$B_LUN	.BLKB	1	; Logical unit number (LUN)
$DEF	UCB$B_SEEK_DIR	.BLKB	1	; Current motion of heads, used for
					; reordering of I/Os for seek optimization

$DEF	UCB$W_READL_LEN	.BLKW	1	; Value to use in read long len field
$DEF	UCB$B_SCSI_VERSION .BLKB 1	; SCSI version (CCS or SCSI-2)
$DEF    UCB$L_DISABLE_DDR .BLKL 1       ; Non zero means disable DDR
                                        ;  (default is DISABLED!)
$DEF	UCB$B_MCN_SCDATA		; For CD-ROMS, Sub-Channel data format2
		.BLKL 1 		;  Media Catalog Data (UPC)

;$DEF	UCB$B_ALIGN_RSVD		; Align UCB
;		.BLKB 3
$DEF	UCB$L_FAIRNESS_CNT .BLKL 1	; Fairness counter in ATTEMPT_REORDER
$DEF	UCB$L_QUEUED_IO_COUNT		; Number of I/O in the port
		.BLKL 1
$DEF	UCB$L_GK_PID			; Process ID of check condition owner
		.BLKL 1

.IF	DEFINED COLLECT_PERF_DATA

$DEF	UCB$L_QDEPTH			; The max qdepth for this device
		.BLKL 1
$DEF	UCB$L_QDEPTH_TURNS		; How many times we've actually changed
		.BLKL	1		; qdepth for this device
$DEF	UCB$L_READ_COUNT		; Count reads done
		.BLKL 1
$DEF	UCB$L_WRITE_COUNT		; Count writes done
		.BLKL 1
$DEF	UCB$L_OTHER_COUNT		; All other I/O functions
		.BLKL 1
$DEF	UCB$L_READ_XLEN_HIST		; Pointer to Read histogram pool
		.BLKL 1
$DEF	UCB$L_WRITE_XLEN_HIST		; Pointer to Write histogram pool
		.BLKL 1
$DEF	UCB$L_XLEN_HIST			; Pointer to total histogram pool
		.BLKL 1
$DEF	UCB$W_XLEN_HIST_CYCLE		; # I/Os per histogram turnover
		.BLKL	1

.ENDC	; DEFINED COLLECT_PERF_DATA

$DEF	UCB$K_DK_UCBLEN			; Length of extended UCB
					
.IF	DEFINED V60_BUILD
	ASSUME UCB$K_DK_UCBLEN LT-  	; Check that System disk ucb expansion
	   <UCB$K_LENGTH+<UCB$K_SYSDISK_UCB_EXPAND*4>> ; size is not exceeded.
.ENDC

	$DEFEND	UCB			; End of UCB definitions


	.SBTTL	Errorlog packet formats
;+
; Following are the definitions for class driver errorlog packets. Each packet
; has a section common for all error types followed by an error-specific section.
;-
	$DEFINI ERROR_PACKETS

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

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

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


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

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

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

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

	$DEFEND	ERROR_PACKETS


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


	.MACRO	MEDIA DD, DEVNAM, DTNAME

$$BEGIN$$=-1
$$MEDIA$$=0
$$S$$=27
	.IRPC	$$L$$,<DD>
	$$TEMP$$ = ^A/$$L$$/ - ^X40
	.IF	GT $$TEMP$$
	$$MEDIA$$ = $$MEDIA$$ + <$$TEMP$$ @ $$S$$>
	.ENDC
	$$S$$ = $$S$$ - 5
	.ENDR
	.IRPC	$$L$$,<DEVNAM>
	.IF	GE <$$S$$ - 7>
	$$TEMP$$ = ^A/$$L$$/ - ^X40
	.IF	GT $$TEMP$$
	$$MEDIA$$ = $$MEDIA$$ + <$$TEMP$$ @ $$S$$>
	.IF_FALSE
	.IIF	LT $$BEGIN$$, $$BEGIN$$ = <17-$$S$$>/5
	.ENDC
	$$S$$ = $$S$$ - 5
	.ENDC
	.ENDR
	.IIF	LT $$BEGIN$$, $$BEGIN$$ = 3
	$$N$$ = %EXTRACT( $$BEGIN$$, 3, DEVNAM )
	$$MEDIA$$ = $$MEDIA$$ + $$N$$
	.LONG	$$MEDIA$$
	.ENDM	MEDIA

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

;	+-----------------------+
;	|    VMS device type	| 1 byte
;	+-----------------------+
;	|	ID string	| 8 bytes
;	+-----------------------+
;	|  Min revision level	| 4 bytes
;	+-----------------------+
;	|	Media ID	| 4 bytes
;	+-----------------------+
;	|   Disc/synch flags	| 1 byte
;	+-----------------------+
;	|  Phase change timout	| 2 bytes
;	+-----------------------+
;	|  Disconnect timeout	| 2 bytes
;	+-----------------------+
;
; The table is terminated with a VMS device code of 0.
;-

	.MACRO	SCSI_DEV_TYPES, LIST

	.MACRO	SCSI_DEV_TYPES1, ID_STRING, DEVICE_TYPE, MEDIA_ID,-
		MINIMUM_REVISION, DISCONNECT, SYNCHRONOUS,-
		PHASE_CHANGE_TIMEOUT, DISCONNECT_TIMEOUT

; Device type

	.BYTE	DT$_'DEVICE_TYPE'

; 8 character product ID string, padded with spaces

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

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

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

	.ASCII	/MINIMUM_REVISION/

; Media ID field, derived from the device type if possible, otherwise supplied
; in the table.

	.IF DIF <ID_STRING>, GENERIC
	.IF B MEDIA_ID
	MEDIA <DK>, <DEVICE_TYPE>
	.IFF
	MEDIA <DK>, <MEDIA_ID>
	.ENDC
	.IFF
   	MEDIA   <DK>, <DKX00>, DT$_Generic_DK   ; Media value for generic DK device
	.ENDC
	
; Flags, including disconnect and synchronous

	$$$FLAGS = 0
	.IIF IDN DISCONNECT, YES, $$$FLAGS = $$$FLAGS + 1
	.IIF IDN SYNCHRONOUS, YES, $$$FLAGS = $$$FLAGS + 2
	.BYTE	$$$FLAGS

; Phase change timeout

	$$$PHASE_CHANGE_TIMEOUT = DEFAULT_PHASE_CHANGE_TIMEOUT
	.IF DIF PHASE_CHANGE_TIMEOUT, DEFAULT
		$$$PHASE_CHANGE_TIMEOUT = PHASE_CHANGE_TIMEOUT
	.ENDC
	.WORD	$$$PHASE_CHANGE_TIMEOUT

; Disconnect timeout

	$$$DISCONNECT_TIMEOUT = DEFAULT_DISCONNECT_TIMEOUT
	.IF DIF DISCONNECT_TIMEOUT, DEFAULT
		$$$DISCONNECT_TIMEOUT = DISCONNECT_TIMEOUT
	.ENDC
	.WORD	$$$DISCONNECT_TIMEOUT

	.ENDM	SCSI_DEV_TYPES1

	.IRP	ENTRY,<LIST>
	SCSI_DEV_TYPES1 ENTRY
	.ENDR

	.ENDM	SCSI_DEV_TYPES


	.SBTTL	SENSE_KEY	- Build sense key to VMS status code translation table
;+
; SENSE_KEY
;
; This macro is used to build a translation table of SCSI to VMS status codes.
; Each time extended sense infromation is returned by the target, the sense
; key is translated to a VMS status code using this table. Each entry in the
; table has the following format:
;
;	+-----------------------+
;	|       Sense key	| 1 byte
;	+-----------------------+
;	|    VMS status code	| 4 bytes
;	+-----------------------+
;
; The table is terminated by a sense key of -1.
;-
	.MACRO	SENSE_KEY, SCSI_STATUS, VMS_STATUS

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

	.ENDM	SENSE_KEY

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

	.MACRO	LOG_ERROR,TYPE,VMS_STATUS,UCB=R3

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

	.ENDM	LOG_ERROR

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

	$$CODE_VALUE = 1

	.MACRO	SCSI_ERROR_CODES1 CODE, LEN

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

	.ENDM	SCSI_ERROR_CODES1

	.IRP	LIST_ENTRY, <ERROR_LIST>
	SCSI_ERROR_CODES1 LIST_ENTRY
	.ENDR

	.ENDM	SCSI_ERROR_CODES


	.SBTTL	DISABLE_ERRLOG	- Temporarily disable errorlogging
	.SBTTL	REENABLE_ERRLOG	- Reenable errorlogging
;+
; DISABLE_ERRLOG
; REENABLE_ERRLOG
;
; This macros are used to disable and reenable errorlogging respectively.
; The DISABL_ERRLOG flag in the UCB is used to temporarily disable errorlogging
; when the class driver prepares to do something which is likely to cause an
; error that should be supressed. For example, when retrying reads or writes
; to blocks with [non-]recoverable errors, errorlogging is disabled so that
; just one error is logged. Since the disabling of errorlogging can be nested,
; the old value of the DISABL_ERRORLOG flag is saved in the local UCB stack.

	.MACRO	DISABLE_ERRLOG

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

	.ENDM	DISABLE_ERRLOG

	.MACRO	REENABLE_ERRLOG

	SUBPOP	UCB$L_DK_FLAGS(R3)	; Reenable errorlogging

	.ENDM	REENABLE_ERRLOG

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

	.MACRO	WORD_BRANCHES2, OPCODE1, OPCODE2, ARGCNT

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

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

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

	.ENDM	WORD_BRANCHES2

	.MACRO	WORD_BRANCHES1, OPCODE1, OPCODE2, ARGCNT

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

	.ENDM	WORD_BRANCHES1

	.IRP	ENTRY, <LIST>
	WORD_BRANCHES1 ENTRY
	.ENDR

	.ENDM	WORD_BRANCHES

	WORD_BRANCHES <-
		<BBC,	BBS,	2>,-
		<BBCC,	BBSC,	2>,-
		<BBCS,	BBSS,	2>,-
		<BCC,	BCS,	0>,-
		<BEQL,	BNEQ,	0>,-
		<BEQLU,	BNEQU,	0>,-
		<BGEQ,	BLSS,	0>,-
		<BGEQU,	BLSSU,	0>,-
		<BGTR,	BLEQ,	0>,-
		<BGTRU,	BLEQU,	0>,-
		<BLBC,	BLBS,	1>,-
		<BVC,	BVS,	0>>


	.SBTTL	INIT_SCDRP_STACK - Initialize the internal SCDRP stack
	.SBTTL	SUBPUSH		- Push an item on the SCDRP stack
	.SBTTL	SUBPOP		- Pop an item from the SCDRP stack
	.SBTTL	SUBSAVE		- Save a return address on the SCDRP stack
	.SBTTL	SUBRETURN	- Return to the address saved on the SCDRP stack
;+
; INIT_SCDRP_STACK
; SUBPUSH
; SUBPOP
; SUBSAVE
; SUBRETURN
;
; These macros manipulate the SCDRP internal stack which is used to save 
; routine return address and temporary variables.
;-
	.MACRO	INIT_SCDRP_STACK,SCDRP=R5,?l1   

;	.if defined scsi_stack_checking
;	.nlist	meb
;	cmpb	ucb$b_type(ucb),#dyn$c_ucb
;	beql	l1
;	BUG_CHECK INCONSTATE,FATAL
;l1:
;	.list	meb
;	.endc

	MOVAL	SCDRP$L_CLASS_STACK-4(SCDRP),-
		SCDRP$L_CLASS_STACK_PTR(SCDRP)

	.ENDM	INIT_SCDRP_STACK

	.MACRO	SUBPUSH,ARG,SCDRP=R5,?l1,?l2	

	.if defined scsi_stack_checking
	.nlist	meb
	pushal	scdrp$l_class_stack+<scdrp$s_class_stack*4>(scdrp)
	cmpl	(sp)+,scdrp$l_class_stack_ptr(scdrp)
	bgtru	l2
l1:	BUG_CHECK INCONSTATE,FATAL		; SCSI stack overflow
l2:	.list	meb
	.endc

	ADDL	#4,SCDRP$L_CLASS_STACK_PTR(SCDRP)
	MOVL	ARG,@SCDRP$L_CLASS_STACK_PTR(SCDRP)

	.ENDM	SUBPUSH

	.MACRO	SUBPOP,ARG,SCDRP=R5,?l1,?l2	

	.if defined scsi_stack_checking
	.nlist	meb
	pushal	scdrp$l_class_stack-4(scdrp)
	cmpl	(sp)+,scdrp$l_class_stack_ptr(scdrp)
	blssu	l2
l1:	BUG_CHECK INCONSTATE,FATAL		; SCSI stack underflow
l2:	.list	meb
	.endc

	MOVL	@SCDRP$L_CLASS_STACK_PTR(SCDRP),ARG
	SUBL	#4,SCDRP$L_CLASS_STACK_PTR(SCDRP)

	.ENDM	SUBPOP

	.MACRO	SUBSAVE,SCDRP=R5,?l1,?l2	

	SUBPUSH	(SP)+,SCDRP

	.ENDM	SUBSAVE

	.MACRO	SUBRETURN,SCDRP=R5,?l1,?l2	

	SUBPOP	-(SP),SCDRP
	RSB

	.ENDM	SUBRETURN


	.MACRO	ALLOC_STACK_SCDRP, UCB=R3,?L1,?L2
L1:
	MOVL	UCB$L_STACK_SCDRP(UCB),R5	; Get address of STACK UCB
	TSTL	SCDRP$L_SVA_USER(R5)		; Does anyone own it now?
	BEQL	L2				; Nope
	BUG_CHECK INCONSTATE, FATAL		; Someone else has it!
L2:	
	MOVAB	L1,SCDRP$L_SVA_USER(R5)		; Save PC of allocater

	.ENDM	ALLOC_STACK_SCDRP



	.MACRO	FREE_STACK_SCDRP, SCDRP=R5, ?L1

	TSTL	SCDRP$L_SVA_USER(SCDRP)		; Is it owned?
	BNEQ	L1                 		; If yes, looks OK.
	BUG_CHECK INCONSTATE, FATAL		; Not good!
L1:	CLRL	SCDRP$L_SVA_USER(SCDRP)		; No owner now

	.ENDM	FREE_STACK_SCDRP


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

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

	.ENDM	DK_WAIT

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

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

	.ENDM	SCSI_CMD


;+
; Macro's used by the SCSI AUDIO code to validate user provided arguments and
; other AUDIO related functions.
;-
	.MACRO	TSTBGTR, BYTE, VALUE, ?l9
	CMPB	'BYTE','VALUE'
	BLEQU	l9
	BRW	IO_BADPARM
l9:
	.ENDM	TSTBGTR

	.MACRO	TSTBLSS, BYTE, VALUE, ?l8
	CMPB	'BYTE','VALUE'
	BGEQU	l8
	BRW	IO_BADPARM
l8:
	.ENDM	TSTBLSS

;+
; This function will minimize to the smaller of val1 and val2.
; If val1 is greater than val2, then assign val1 the value of val2.
;-
	.MACRO	MINUM	val1,val2, ?l10
	CMPL	'val1','val2'
	BLEQ	l10
	MOVL	'val2','val1'
l10:
	.ENDM	MINUM


;
; Macros used for RAID management
;

;
; CHECK_FOR_RAID_DEVICE
; Checks the current INQUIRY data for one of a list of known RAID device
; product names. If it's a known RAID device, the UCB$V_RAID bit is set.
;
;	R0 - points to INQUIRY data, 16(R0) is PRODUCT_ID feild.
;	R3 - UCB
;
	.MACRO	CHECK_FOR_RAID_DEVICE, ?RAID,?NORAID,?RAIDE
	CMPL	#^A/ADP-/,16(R0)	; Is this an HSZ10 proto?
	BEQL	RAID
	CMPL	#^A/HSZ1/,16(R0)	; Is this an HSZ10?
	BEQL	RAID
	CMPL	#^A/HSZ2/,16(R0)	; Is this an HSZ20?
	BEQL	RAID
	CMPL	#^A/HSZ4/,16(R0)	; Is this an HSZ40?
	BEQL	RAID
        BRB	NORAID
RAID:	BISL	#UCB$M_RAID,UCB$L_DK_FLAGS(R3)
	BRB	RAIDE
NORAID:	BICL	#UCB$M_RAID,UCB$L_DK_FLAGS(R3)
RAIDE:
	.ENDM	CHECK_FOR_RAID_DEVICE


;
; IF_RAID, IF_NOT_RAID
;
;	R3 - UCB
;
	.MACRO	IF_RAID, LABEL
	BBS	#UCB$V_RAID,UCB$L_DK_FLAGS(R3),LABEL
	.ENDM	IF_RAID

	.MACRO	IF_NOT_RAID, LABEL
	BBC	#UCB$V_RAID,UCB$L_DK_FLAGS(R3),LABEL
	.ENDM	IF_NOT_RAID

;
; SWAB
;
	.MACRO	SWAB,REG
	.IIF	IDN	REG,R10,	.ERROR Can't SWAB R10
	PUSHL	R10		; Borrow R10
	MOVZBL	REG,R10		; Get LSB
	ASHL	#8,R10,R10	; Shift LSB to MSB
	ASHL	#-8,REG,REG	; Move MSB down
	MOVB	REG,R10		; Copy MSB
	ASHL	#8,REG,REG	; Fix REG MSW
	MOVW	R10,REG		; Copy back to REG
	POPL	R10		; Restore
	.ENDM	SWAB


;
; CHECK_FOR_CMDQ
; Checks the current INQUIRY data for the CmdQue bit to be set indicating
; that the device supports command queing. If the device is not at least
; SCSI-2, no CMDQ check is done
;
;	R0 - points to INQUIRY data, 7(R0) is INQUIRY options field
;	R3 - UCB
;
	.MACRO	CHECK_FOR_CMDQ,?L1
	BICL	#UCB$M_CMDQ,UCB$L_DK_FLAGS(R3)	; Assume no CMDQ support
	BBC	#UCB$V_PORT_CMDQ,-		; Make sure port supports CMDQ
		UCB$L_DK_FLAGS(R3),L1		; Br if no.
	CMPB	#1,UCB$B_SCSI_VERSION(R3) 	; Check SCSI version
	BGEQ	L1				; Br if CCS or SCSI-1
	BBC	#1,7(R0),L1			; Br if CmdQue bit clear
	BISL	#UCB$M_CMDQ,UCB$L_DK_FLAGS(R3)	; CMDQ supported!
L1:
	.ENDM	CHECK_FOR_CMDQ


;
; IF_CMDQ, IF_NOT_CMDQ
;
;	R3 - UCB
;
	.MACRO	IF_CMDQ,LABEL,UCB=R3
	BBS	#UCB$V_CMDQ,UCB$L_DK_FLAGS(UCB),LABEL
	.ENDM	IF_CMDQ

	.MACRO	IF_NOT_CMDQ,LABEL,UCB=R3
	BBC	#UCB$V_CMDQ,UCB$L_DK_FLAGS(UCB),LABEL
	.ENDM	IF_NOT_CMDQ


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

	DPTAB	-				; DPT-creation macro
		END=DK_END,-			; End of driver label
		ADAPTER=NULL,-			; Adapter type
		UCBSIZE=<UCB$K_DK_UCBLEN>,-	; Length of UCB
		NAME=DKDRIVER,-			; Driver name
		FLAGS=<DPT$M_SMPMOD!-		; Driver runs in SMP environment
		       DPT$M_SNAPSHOT!-		; Driver supports snapshots
		       DPT$M_NO_IDB_DISPATCH>	; Don't fill in IDB$L_UCBLST
	DPT_STORE INIT				; Start of load
						; initialization table
	DPT_STORE DDB,DDB$L_ACPD,L,<^A\F11\>	; Default ACP name
	DPT_STORE UCB,UCB$L_MAXBCNT,L,MAX_BCNT	; Max byte count
	DPT_STORE UCB,UCB$B_FLCK,B,SPL$C_IOLOCK8 ; Device FORK LOCK
	DPT_STORE UCB,UCB$B_DIPL,B,22		; Device interrupt IPL
	DPT_STORE UCB,UCB$L_DEVCHAR,L,<-	; Device characteristics
		DEV$M_DIR!-			; Directory structured
		DEV$M_FOD!-			; Files oriented
		DEV$M_AVL!-			; Available
		DEV$M_ELG!-			; Error logging enabled
		DEV$M_IDV!-			; Input device
		DEV$M_ODV!-			; Output device
		DEV$M_SHR!-			; Shareable Device
		DEV$M_RND>			; Random Access Device
	DPT_STORE UCB,UCB$L_DEVCHAR2,L,<-	; Device characteristics
		DEV$M_SCSI!-			; SCSI device
		DEV$M_NNM!-			; Prefix name with "node$"
		DEV$M_NLT>			; No bad block information on
						; last track
	DPT_STORE UCB,UCB$B_DEVCLASS,B,DC$_DISK	; Sample device class
	DPT_STORE UCB,UCB$W_DEVBUFSIZ,W,512	; Default buffer size
	DPT_STORE UCB,UCB$B_ERTCNT,B,16		; Error retry count
	DPT_STORE UCB,UCB$B_ERTMAX,B,16		; Max error retry count
	DPT_STORE UCB,UCB$L_DK_FLAGS,L,0	; Initialize flags field
	DPT_STORE UCB,UCB$L_ERR_MASK,L,0	; Initialize error mask field
	DPT_STORE UCB,UCB$L_HW_REV,L,0		; Initialize HW revision level
	DPT_STORE UCB,UCB$W_DEVSTS,W,-		; Set no logical to physical 
		  UCB$M_NOCNVRT			; block number conversion
        DPT_STORE UCB,UCB$L_DISABLE_DDR,L,1     ; Disable DDR by default

.IF	DEFINED COLLECT_PERF_DATA
	DPT_STORE UCB,UCB$L_READ_XLEN_HIST,L,0	; Make sure these are 0
	DPT_STORE UCB,UCB$L_WRITE_XLEN_HIST,L,0	; Make sure these are 0
	DPT_STORE UCB,UCB$L_XLEN_HIST,L,0	; Make sure these are 0
	DPT_STORE UCB,UCB$L_QDEPTH,W,16		; Default qdepth
	DPT_STORE UCB,UCB$L_QDEPTH_TURNS,L,1	; Set counter
.ENDC	; DEFINED COLLECT_PERF_DATA

	DPT_STORE REINIT			; Start of reload
						; initialization table
	DPT_STORE DDB,DDB$L_DDT,D,DK$DDT	; Address of DDT
	DPT_STORE CRB,-				; Address of controller
		CRB$L_INTD+VEC$L_INITIAL,-	; initialization routine
		D,DK_CTRL_INIT
	DPT_STORE CRB,-				; Address of device
		CRB$L_INTD+VEC$L_UNITINIT,-	; unit initialization
		D,DK_UNIT_INIT			; routine
	DPT_STORE CRB,CRB$B_FLCK,B,IPL$_IOLOCK8	; Initialize fork lock field

	DPT_STORE END				; End of initialization
						; tables


	.SBTTL	Driver dispatch table
;+
; Driver dispatch table
;
; This table defines the entry points into the driver.
;-

	DDTAB	-				; DDT-creation macro
		DEVNAM=DK,-			; Name of device
		START=DK_STARTIO,-		; Start I/O routine
		FUNCTB=DK_FUNCTABLE,-		; FDT address
		CANCEL=DK_CANCEL,-		; Cancel I/O routine
		REGDMP=DK_REG_DUMP		; Register dump routine

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

DK_FUNCTABLE:					; FDT for driver
	FUNCTAB,<-				; Valid I/O functions
		ACCESS,-			; Access file and/or find directory
		ACPCONTROL,-			; ACP control function
		AUDIO,-				; Invoke CD-ROM Audio Functions
		AVAILABLE,-			; Available
		CREATE,-			; Create file and/or directory entry
		CRESHAD,-			; Create a shadow set virtual unit
		DEACCESS,-			; Deaccess file
		DELETE,-			; Delete file and/or directory entry
		DIAGNOSE,-			; Special pass-through function
		FORMAT,-			; Format floppy diskette
		MODIFY,-			; Modify file attributes
		MOUNT,-				; Mount volume
		NOP,-				; No operation
		PACKACK,-			; Pack acknowledge
		READLBLK,-			; Read logical
		READPBLK,-			; Read physical
		READVBLK,-			; Read virtual
		REMSHAD,-			; Remove a shadow set member
		SETCHAR,-			; Set device chars.
		SETMODE,-			; Set device mode
		SENSEMODE,-			; Sense mode
		SENSECHAR,-			; Sense characteristics
		UNLOAD,-			; Unload volume
		WRITECHECK,-			; Write check
		WRITELBLK,-			; Write logical
		WRITEPBLK,-			; Write physical
		WRITEVBLK,-			; Write virtual
		DSE>				; Data Security Erase

	FUNCTAB,<-				; Buffered I/O functions
		ACCESS,-			; Access file and/or find directory
	        ACPCONTROL,-			; ACP control function
		AVAILABLE,-			; Available (rewind/nowait clear valid)
		CREATE,-			; Create file and/or directory entry
		DEACCESS,-			; Deaccess file
		DELETE,-			; Delete file and/or directory entry
		DRVCLR,-			; Driver clear
		MODIFY,-			; Modify file attributes
		MOUNT,-				; Mount volume
		NOP,-				; No operation
		PACKACK,-			; Pack acknowledge
		DSE,-				; Data Security Erase
		SEEK,-				; Seek
		SENSEMODE,-			; Sense mode
		SENSECHAR,-			; Sense characteristics
		UNLOAD>				; Unload volume

	FUNCTAB	+ACP$ACCESS,<-			; Access & create file or directory
		ACCESS,-
		CREATE> 

	FUNCTAB	+ACP$DEACCESS,-			; Deaccess file
		<DEACCESS> 

	FUNCTAB +ACP$MODIFY,<-			; set for dismounts
		ACPCONTROL,-			; for files 11 dismounts
		DELETE,-			; Delete file and/or directory entry
		MODIFY>				; Modify file attributes

	FUNCTAB	DK_SHAD_WCHECK,<-		; Check write to shadow set mbr
		WRITELBLK,-			; Write LOGICAL Block
		WRITEPBLK,-			; Write Physical Block
		WRITEVBLK>			; Write VIRTUAL Block

	FUNCTAB	+ACP$READBLK,<-			; Read functions
		READLBLK,-			; Read logical block forward
		READPBLK,-			; Read physical block forward
;	 	READTRACKD,-			; Read track descriptor
;		READHEAD,-			; Read track data and headers
		READVBLK>			; Read virtual block

	FUNCTAB	+ACP$WRITEBLK,<-		; Write functions
		WRITECHECK,-			; Write check
		WRITELBLK,-			; Write Logical Block
		WRITEPBLK,-			; Write Physical Block
		WRITEVBLK>			; Write Virtual Block

	FUNCTAB	+EXE$SENSEMODE,-		; Sense functions
		<SENSECHAR,-			; Sense characteristics
		SENSEMODE>			; Sense mode

	FUNCTAB	+EXE$SETMODE,<-			; FDT set mode routine
		SETCHAR,-			; for set chars. and
		SETMODE>			; set mode.

	FUNCTAB +EXE$LCLDSKVALID,<-		; Local disk valid functions
		UNLOAD,-			; Unload volume
		AVAILABLE,-			; Unit available
		PACKACK>			; Pack acknowledge

	FUNCTAB	+EXE$ONEPARM,-			; Single parameter functions
		<FORMAT>			; Format floppy diskette

	FUNCTAB	+ACP$MOUNT,-
		<MOUNT>				; Mount volume

	FUNCTAB	DK_DIAGNOSE,<-			; Special pass-through function
		DIAGNOSE>			; 

	FUNCTAB	DK_AUDIO,<-			; CD-ROM Audio FDT entry (55)
		AUDIO>				; 

	FUNCTAB	DK_CRESHAD,-			; Create a shadow set virtual unit
		<CRESHAD>

	FUNCTAB	DK_REMSHAD,-			; Remove a shadow set member
		<REMSHAD>

	FUNCTAB	DK_DSE,-			; Data Security Erase FDT entry
		<DSE>				;

	FUNCTAB	+EXE$ZEROPARM,-			; NOP
		<NOP>



	.SBTTL	SCSI device types table
;+
; SCSI_DEVICE_TABLE
;
; This table is used to translate the product ID field from the inquiry data
; to a VMS device type. Once the device type is determined, various information
; about the device such as disconnect and synchronous flags and timeout vaules
; is saved in the UCB.
;-

SCSI_DEVICE_TABLE:

	.list	meb
SCSI_DEV_TYPES <-
;
; ID string  Dev type    Media ID Min Rev Disc Synch Phas tmo Disc tmo
; ---------  --------    -------- ------- ---- ----- -------- ----------
;
  <<RZ22>,    RZ22,       ,       <0615>, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ23>,    RZ23,       ,       <0615>, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ23L>,   RZ23L,      RZL23,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ24>,    RZ24,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ25>,    RZ25,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ31>,    RZ31,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ55>,    RZ55,       ,       <0700>, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ56>,    RZ56,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ57>,    RZ57,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ57I>,   RZ57I,      RZI57,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ58>,    RZ58,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RRD40>,   RRD40S,     RRD40,  <250D>, YES,   NO, DEFAULT, RRD40_DISC_TMO>,-
  <<RRD42>,   RRD42,      ,       <    >, YES,   NO, DEFAULT, RRD42_DISC_TMO>>
;	.BYTE	DT$_RZ22
;	.ASCII	/RZ22/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/0615/
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ23
;	.ASCII	/RZ23/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/0615/
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ23L
;	.ASCII	/RZ23L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ24
;	.ASCII	/RZ24/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ25
;	.ASCII	/RZ25/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ31
;	.ASCII	/RZ31/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ55
;	.ASCII	/RZ55/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/0700/
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ56
;	.ASCII	/RZ56/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ57
;	.ASCII	/RZ57/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ57I
;	.ASCII	/RZ57I/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ58
;	.ASCII	/RZ58/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RRD40S
;	.ASCII	/RRD40/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/250D/
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RRD42
;	.ASCII	/RRD42/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT

SCSI_DEV_TYPES <-
  <<RZ72>,    RZ72,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ73>,    RZ73,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ35>,    RZ35,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RWZ01>,   RWZ01,      ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ24L>,   RZ24L,	  RZL24,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ25L>,   RZ25L,      RZL25,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ55L>,   RZ55L,	  RZL55,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ56L>,   RZ56L,	  RZL56,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ57L>,   RZ57L, 	  RZL57,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ26>,    RZ26,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ36>,    RZ36,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ74>,    RZ74,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>>
;	.BYTE	DT$_RZ72
;	.ASCII	/RZ72/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ73
;	.ASCII	/RZ73/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ35
;	.ASCII	/RZ35/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RWZ01
;	.ASCII	/RWZ01/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ24L
;	.ASCII	/RZ24L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ25L
;	.ASCII	/RZ25L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ55L
;	.ASCII	/RZ55L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ56L
;	.ASCII	/RZ56L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ57L
;	.ASCII	/RZ57L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ26
;	.ASCII	/RZ26/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ36
;	.ASCII	/RZ36/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ74
;	.ASCII	/RZ74/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT

SCSI_DEV_TYPES <-
  <<RZ27>,    RZ27,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ37>,    RZ37,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ38>,    RZ38,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ75>,    RZ75,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ59>,    RZ59,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ34L>,   RZ34L,      RZL34,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ35L>,   RZ35L,      RZL35,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ36L>,   RZ36L,      RZL36,  <    >, YES,  YES, DEFAULT, DEFAULT>>
;	.BYTE	DT$_RZ27
;	.ASCII	/RZ27/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ37
;	.ASCII	/RZ37/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ38
;	.ASCII	/RZ38/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ75
;	.ASCII	/RZ75/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ59
;	.ASCII	/RZ59/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ34L
;	.ASCII	/RZ34L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ35L
;	.ASCII	/RZ35L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ36L
;	.ASCII	/RZ36L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT

SCSI_DEV_TYPES <-
  <<RZ13>,    RZ13,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ14>,    RZ14,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ15>,    RZ15,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ16>,    RZ16,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ17>,    RZ17,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ18>,    RZ18,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ51>,    EZ51,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ52>,    EZ52,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ53>,    EZ53,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ54>,    EZ54,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ58>,    EZ58,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>>
;	.BYTE	DT$_RZ13
;	.ASCII	/RZ13/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ14
;	.ASCII	/RZ14/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ15
;	.ASCII	/RZ15/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ16
;	.ASCII	/RZ16/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ17
;	.ASCII	/RZ17/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ18
;	.ASCII	/RZ18/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ51
;	.ASCII	/EZ51/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ52
;	.ASCII	/EZ52/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ53
;	.ASCII	/EZ53/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ54
;	.ASCII	/EZ54/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ58
;	.ASCII	/EZ58/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT

SCSI_DEV_TYPES <-
  <<Cp350>,   RZ22,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<Cp3100-1> RZ23,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<1578-15>, RZ55,       ,       <    >,  NO,  YES, DEFAULT, DEFAULT>,-
  <<CM 210>,  RRD40S,     RRD40,  <    >, YES,   NO, DEFAULT, RRD40_DISC_TMO>,-
  <<RX23>,    RX23S,      RX23,   <    >, YES,   NO, RX_PHS_TMO, RX_DISC_TMO>,-
  <<RX33>,    RX33S,      RX33,   <    >, YES,   NO, RX_PHS_TMO, RX_DISC_TMO>,-
  <<RX26>,    RX26,       ,       <    >, YES,   NO, RX_PHS_TMO, RX_DISC_TMO>,-
  <<XT-4380S>, RZ55, 	  ,       <B2  >, YES,   NO, DEFAULT, DEFAULT>,-
  <<GENERIC>, GENERIC_DK, ,       <    >, YES,  YES, DEFAULT, DEFAULT>>
;	.BYTE	DT$_RZ22
;	.ASCII	/Cp350/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ23
;	.ASCII	/Cp3100-1/
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ55
;	.ASCII	/1578-15/
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RRD40S
;	.ASCII	/CM 210/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RX23S
;	.ASCII	/RX23/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RX33S
;	.ASCII	/RX33/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RX26
;	.ASCII	/RX26/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ55
;	.ASCII	/XT-4380S/
;	.ASCII	/B2  /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_GENERIC_DK
;	.ASCII	/GENERIC/
;	.ASCII	/ /
;GENERIC_SCSI_DISK:
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT

.IF	DEFINED V60_BUILD
SCSI_DEV_TYPES <-
  <<RRD43>,   RRD43,      ,       <    >, YES,   NO, DEFAULT, RRD42_DISC_TMO>,-
  <<RRD44>,   RRD44,      ,       <    >, YES,   NO, DEFAULT, RRD42_DISC_TMO>,-
  <<HSZ10>,   HSZ10,      ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ28>,    RZ28,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ29>,    RZ29,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ26L>,   RZ26L,      RZL26,  <    >, YES,  YES, DEFAULT, DEFAULT>>
;	.BYTE	DT$_RRD43
;	.ASCII	/RRD43/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RRD44
;	.ASCII	/RRD44/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_HSZ10
;	.ASCII	/HSZ10/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ28
;	.ASCII	/RZ28/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ29
;	.ASCII	/RZ29/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ26L
;	.ASCII	/RZ26L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT

SCSI_DEV_TYPES <-
  <<RZ26B>,   RZ26B,      RZB26,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ27B>,   RZ27B,      RZB27,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ28B>,   RZ28B,      RZB28,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ29B>,   RZ29B,      RZB29,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ73B>,   RZ73B,      RZB73,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ74B>,   RZ74B,      RZB74,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ75B>,   RZ75B,      RZB75,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ27L>,   RZ27L,      RZL27,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RWZ21>,   RWZ21,      ,	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<HSZ20>,   HSZ20,      ,	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<HSZ40>,   HSZ40,      ,	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<HSZ15>,   HSZ15,      ,  	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ26M>,   RZ26M,      RZM26,  <    >, YES,  YES, DEFAULT, DEFAULT>>
;	.BYTE	DT$_RZ26B
;	.ASCII	/RZ26B/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ27B
;	.ASCII	/RZ27B/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ28B
;	.ASCII	/RZ28B/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ29B
;	.ASCII	/RZ29B/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ73B
;	.ASCII	/RZ73B/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ74B
;	.ASCII	/RZ74B/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ75B
;	.ASCII	/RZ75B/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ27L
;	.ASCII	/RZ27L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RWZ21
;	.ASCII	/RWZ21/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_HSZ20
;	.ASCII	/HSZ20/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_HSZ40
;	.ASCII	/HSZ40/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_HSZ15
;	.ASCII	/HSZ15/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ26M
;	.ASCII	/RZ26M/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT

SCSI_DEV_TYPES <-
  <<RW504>,   RW504,      , 	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RW510>,   RW510,      , 	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RW514>,   RW514,      , 	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RW516>,   RW516,      , 	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RWZ51>,   RWZ51,      ,  	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RWZ52>,   RWZ52,      ,  	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RWZ53>,   RWZ53,      ,	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RWZ54>,   RWZ54,      ,	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RWZ31>,   RWZ31,      ,  	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ31>,    EZ31,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ32>,    EZ32,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ33>,    EZ33,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ34>,    EZ34,       ,       <    >, YES,  YES, DEFAULT, DEFAULT>>
;	.BYTE	DT$_RW504
;	.ASCII	/RW504/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RW510
;	.ASCII	/RW510/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RW514
;	.ASCII	/RW514/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RW516
;	.ASCII	/RW516/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RWZ51
;	.ASCII	/RWZ51/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RWZ52
;	.ASCII	/RWZ52/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RWZ53
;	.ASCII	/RWZ53/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RWZ54
;	.ASCII	/RWZ54/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RWZ31
;	.ASCII	/RWZ31/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ31
;	.ASCII	/EZ31/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ32
;	.ASCII	/EZ32/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ33
;	.ASCII	/EZ33/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ34
;	.ASCII	/EZ34/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT

SCSI_DEV_TYPES <-
  <<EZ35>,    EZ35,       ,	  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ31L>,   EZ31L,      EZL31,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ32L>,   EZ32L,      EZL32,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ33L>,   EZ33L,      EZL33,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<RZ28L>,   RZ28L,      RZL28,  <    >, YES,  YES, DEFAULT, DEFAULT>,-
  <<EZ56R>,   EZ56R,      EZR56,  <    >, YES,  YES, DEFAULT, DEFAULT>>
;	.BYTE	DT$_EZ35
;	.ASCII	/EZ35/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ31L
;	.ASCII	/EZ31L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ32L
;	.ASCII	/EZ32L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ33L
;	.ASCII	/EZ33L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_RZ28L
;	.ASCII	/RZ28L/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT
;	.BYTE	DT$_EZ56R
;	.ASCII	/EZ56R/
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/ /
;	.ASCII	/    /
;	.LONG	$$MEDIA$$
;	.BYTE	$$$FLAGS
;	.WORD	$$$PHASE_CHANGE_TIMEOUT
;	.WORD	$$$DISCONNECT_TIMEOUT

.ENDC	; DEFINED V60_BUILD

	.BYTE	0	; End of table
	.nlist	meb


	.SBTTL	SCSI command definition tables

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

	SCSI_CMD -
		NAME = REZERO_UNIT,-
		CMD_BYTES = <1, 0, 0, 0, 0, 0>

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

	SCSI_CMD -
		NAME = FORMAT_UNIT,-
		CMD_BYTES = <4, 0, 0, 0, 0, 0>,-
		DMA_LEN = 512,-
		DMA_DIR = WRITE

	SCSI_CMD -
		NAME = REASSIGN_BLOCKS,-
		CMD_BYTES = <7, 0, 0, 0, 0, 0>,-
		DMA_LEN = 8,-
		DMA_DIR = WRITE

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

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

	SCSI_CMD -
		NAME = EXTND_READ,-
		CMD_BYTES = <<^X28>, 0, 0, 0, 0, 0, 0, 0, 0, 0>,-
		DMA_LEN = -1

	SCSI_CMD -
		NAME = EXTND_WRITE,-
 		CMD_BYTES = <<^X2A>, 0, 0, 0, 0, 0, 0, 0, 0, 0>,-
		DMA_LEN = -1

	SCSI_CMD -
		NAME = SEEK,-
		CMD_BYTES = <11, 0, 0, 0, 0, 0>

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

	SCSI_CMD -
		NAME = MODE_SELECT,-
		CMD_BYTES = <21 , <^X10>, 0, 0, 20, 0>,-
		DMA_LEN = 150,-
		DMA_DIR = WRITE

	SCSI_CMD -
		NAME = MODE_SELECT_10,-
		CMD_BYTES = <85, <^X10>, 0,0,0,0,0, 0,20, 0>,-
		DMA_LEN = 255,-
		DMA_DIR = WRITE

	SCSI_CMD -
		NAME = RESERVE,-
		CMD_BYTES = <22, 0, 0, 0, 0, 0>

	SCSI_CMD -
		NAME = RELEASE,-
		CMD_BYTES = <23, 0, 0, 0, 0, 0>

	SCSI_CMD -
		NAME = COPY,-
		CMD_BYTES = <24, 0, 0, 0, 0, 0>

	SCSI_CMD -
		NAME = MODE_SENSE,-
		CMD_BYTES = <26, 0, <^X3F>, 0, 255, 0>,-
		DMA_LEN = 255,-
		DMA_DIR = READ

	SCSI_CMD -
		NAME = MODE_SENSE_10,-
		CMD_BYTES = <90, 0, <^X3F>, 0,0,0,0, <^X04>,<^X00>,0>,-
		DMA_LEN = 1024,-
		DMA_DIR = READ
 
	SCSI_CMD -
		NAME = START_UNIT,-
		CMD_BYTES = <27, 1, 0, 0, 1, 0>

	SCSI_CMD -
		NAME = STOP_UNIT,-
		CMD_BYTES = <27, 1, 0, 0, 0, 0>

	SCSI_CMD -
		NAME = RECEIVE_DIAG,-
		CMD_BYTES = <28, 0, 0, 0, 0, 0>,-
		DMA_LEN = 512,-
		DMA_DIR = READ

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

	SCSI_CMD -
		NAME = MEDIA_REMOVAL,-
		CMD_BYTES = <30, 0, 0, 0, 0, 0>

	SCSI_CMD -
		NAME = READ_CAPACITY,-
		CMD_BYTES = <37, 0, 0, 0, 0, 0, 0, 0, 0, 0>,-
		DMA_LEN = 8,-
		DMA_DIR = READ

	SCSI_CMD -
		NAME = READ_LONG,-
		CMD_BYTES = <<^X3E>, 0, 0, 0, 0, 0, 0, 0, 0, 0>,-
		DMA_LEN = READ_LONG_DATA_LEN,-
		DMA_DIR = READ

	SCSI_CMD -
		NAME = WRITE_LONG,-
		CMD_BYTES = <<^X3F>, 0, 0, 0, 0, 0, 0, 0, 0, 0>,-
		DMA_LEN = WRITE_LONG_DATA_LEN,-
		DMA_DIR = WRITE


;+
; Beginning of CD-ROM specific SCSI Audio commands
;-
	SCSI_CMD -
		NAME = PAUSE,-
		CMD_BYTES = <75, 0, 0, 0, 0, 0, 0, 0, 0, 0>

	SCSI_CMD -
		NAME = RESUME,-
		CMD_BYTES = <75, 0, 0, 0, 0, 0, 0, 0, 1, 0>

	SCSI_CMD -
		NAME = PLAY_AUDIO10,-
		CMD_BYTES = <69, 0, 0, 0, 0, 0, 0, 0, 0, 0>

	SCSI_CMD -
		NAME = PLAY_TRACK,-
		CMD_BYTES = <72, 0, 0, 0, 3, 1, 0, 11, 1, 0>

	SCSI_CMD -
		NAME = REMOVAL,-
		CMD_BYTES = <30, 0, 0, 0, 0, 0, 0>

	SCSI_CMD -
		NAME = PLAY_AUDIO_MSF,-
		CMD_BYTES = <71, 0, 0, 0, 0, 0, 0, 0, 0, 0>

	SCSI_CMD -
		NAME = CD_MODE_SENSE,-
		CMD_BYTES = <26, 0, 0, 0, 0, 0>,-
		DMA_LEN	= -1,-
		DMA_DIR = READ

	SCSI_CMD -
		NAME = CD_MODE_SELECT,-
		CMD_BYTES = <21 , <^X10>, 0, 0, 0, 0>,-
		DMA_LEN = -1,-
		DMA_DIR = WRITE

	SCSI_CMD -
		NAME = CD_READ_SUB,-
		CMD_BYTES = <66, 0, 64, 1, 0, 0, 0, 0, 48, 0>,-
		DMA_LEN = 48,-
		DMA_DIR = READ

	SCSI_CMD -
		NAME = CD_READ_TOC,-
		CMD_BYTES = <67, 0, 0, 0, 0, 0, 0, 3, 22, 0>,-
		DMA_LEN = 804,-
		DMA_DIR = READ

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

SENSE_KEY_TABLE:

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

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

	.BYTE	^XFF			; End of sense key table


	.SBTTL	SCSI error length table
;+
; SCSI_ERROR_LEN_TAB
;
; This table is indexed by the SCSI error type and specifies the length of
; the errorlog packet.
;-

SCSI_ERROR_LEN_TAB:

	SCSI_ERROR_CODES <-
		<CONNECTION_ERROR,	STANDARD_LENGTH>,-
		<MAP_BUFFER_ERROR,	STANDARD_LENGTH>,-
		<SEND_CMD_ERROR,	COMMAND_LENGTH>,-
		<INV_INQUIRY_DATA,	INQUIRY_LENGTH>,-
		<EXTND_SENSE_DATA,	EXTND_SENSE_LENGTH>,-
		<MODE_SENSE_DATA,	MODE_SENSE_LENGTH>,-
		<REASSIGN_BLOCK,	REASSIGN_BLOCK_LENGTH>>


	.SBTTL	+
	.SBTTL	+ Miscellaneous routines
	.SBTTL	+
	.SBTTL	CHECK_QDEPTH - Check histogram data for qdepth change
;+
; CHECK_QDEPTH
;
; This routine is called from startio when the UCB$W_XLEN_HIST_CYCLE
; counter reaches zero. It goes through the UCB$L_XLEN_HIST transfer
; length histogram to see where the concentration of I/O lengths
; are, and selects a new max qdepth based on this data.
;
; INPUTS:
;
;	R3	- address of the UCB
;	R5	- address of an SCDRP
;
; OUTPUTS:
;
;	R0,R1 - destroyed
;	all other registers preserved
;-

QDEPTH_TABLE:
	.BYTE	32	;   0 - 3
	.BYTE	32	;   4 - 7
	.BYTE	32	;   8 - 11
	.BYTE	31	;  12 - 15
	.BYTE	30	;  16 - 19
	.BYTE	28	;  20 - 23
	.BYTE	23	;  24 - 27
	.BYTE	20	;  28 - 31
	.BYTE	18	;  32 - 35
	.BYTE	14	;  36 - 39
	.BYTE	13	;  40 - 43
	.BYTE	12	;  44 - 47
	.BYTE	11	;  48 - 51
	.BYTE	9	;  52 - 55
	.BYTE	8	;  56 - 59
	.BYTE	8	;  60 - 63
	.BYTE	7	;  64 - 67
	.BYTE	7	;  68 - 71
	.BYTE	7	;  72 - 75
	.BYTE	6	;  76 - 79
	.BYTE	6	;  80 - 83
	.BYTE	5	;  84 - 87
	.BYTE	5	;  88 - 91
	.BYTE	5	;  92 - 95
	.BYTE	5	;  96 - 99
	.BYTE	5	; 100 - 103
	.BYTE	5	; 104 - 107
	.BYTE	5	; 108 - 111
	.BYTE	5	; 112 - 115
	.BYTE	5	; 116 - 119
	.BYTE	5	; 120 - 123
	.BYTE	4	; 124 - 127+

CHECK_QDEPTH:

	PUSHL	R6
	PUSHL	R2
	MOVL	UCB$L_XLEN_HIST(R3),R6	; Address of histogram table
	CLRL	R1			; Max hist entry count

	MOVL	#31,R0			; Index into hist table
	MOVL	R0,R2			; Save "max" index
10$:	CMPL	(R6)[R0],R1		; Check entry against max seen
	BLEQ	20$			; br if less than max
	MOVL	(R6)[R0],R1		; New max	
	MOVL	R0,R2			; Store new index
20$:	CLRL	(R6)[R0]           	; Restart histogram
	SOBGEQ	R0,10$			; Check all cells

	MOVAB	QDEPTH_TABLE,R6		; Address of qdepth vs xlen table
	MOVZBL	(R6)[R2],R0		; select new optimal depth
	CMPL	R0,UCB$L_QDEPTH(R3) 	; If same, skip SET_CONN_CHAR for speed
	BEQL	30$
	MOVL	R0,UCB$L_QDEPTH(R3)	; Set new qdepth
	INCL	UCB$L_QDEPTH_TURNS(R3)	; Count adjustments
	BSBW	SET_CONN_CHAR		; Tell port

30$:	POPL	R2
	POPL	R6
	RSB


	.SBTTL	+
	.SBTTL	+ DRIVER ENTRY POINTS
	.SBTTL	+
	.SBTTL	DK_CTRL_INIT	- Controller initialization routine
;+
; DK_CTRL_INIT
;
; This routine is called to perform controller-specific initialization and
; is called by the operating system in three places:
;
;	- at system startup
;	- during driver loading and reloading
;	- during recovery from a power failure
;
; This routine is a NOP for driver reloading and power failure recovery.
; For system startup and driver loading it allocates a CDDB.
;
; INPUTS:
;
;	R4	- address of the CSR (controller status register)
;	R5	- address of the IDB (interrupt data block)
;	R6	- address of the DDB (device data block)
;	R8	- address of the CRB (channel request block)
;
; OUTPUTS:
;
;	Registers preserved before forking.
;	R0,R1,R2,R4,R5 destroyed.
;-

DK_CTRL_INIT:
        PUSHQ   R4                      ; Save CSR & IDB
	MOVB	#SPL$C_SCS,-		; Initialize device spin lock index.
		CRB$B_FLCK(R8)		;
	TSTL	CRB$L_AUXSTRUC(R8)	; Check for CDDB already present.
	BEQL	10$			; Branch if not
5$:	POPQ	R4			; Restore registers
	RSB				; Otherwise, return to caller
10$:
;
; Create fork thread to finish controller init.
;		
	MOVL	R6,R4			; Restore DDB
	MOVL	R8,R5			; Fork with CRB
	PUSHAB	5$			;
	FORK				; 
;
; Get pool for CDDB.
;
20$:	MOVZWL	#CDDB$K_LENGTH,R1	; Size of CDDB
	JSB	G^EXE$ALONONPAGED	; Allocate some pool
	BLBS	R0,30$			; Branch if successful
	BUG_CHECK INCONSTATE,FATAL	; Otherwise, bugcheck
30$:
;
; Clear pool.
;
	PUSHR	#^M<R1,R2,R4,R5>	; Save registers.
	MOVC5	#0, (SP), #0, R1, (R2)	; Zero entire block.
	POPR	#^M<R1,R2,R4,R5>	; Restore saved registers.
;
; Initialize necessary CDDB fields.
;
	MOVW	R1,CDDB$W_SIZE(R2)	; Size
	ASSUME CDDB$B_SUBTYPE EQ CDDB$B_TYPE+1
	MOVW	#<DYN$C_CLASSDRV!-	; Type
		 <DYN$C_CD_CDDB@8>>,-	;
		CDDB$B_TYPE(R2)		;
	MOVL	G^CLU$GL_ALLOCLS,-	; Allocation class
		CDDB$L_ALLOCLS(R2)	;
	MOVL	R5,CDDB$L_CRB(R2)	; CRB address
	MOVL	R4,CDDB$L_DDB(R2)	; DDC address
	MOVL	R2,CRB$L_AUXSTRUC(R5)	; Save CDDB address in CRB.
	MOVL	#SS$_NORMAL,R0		; Set success status
	RSB				; Return to caller


	.SBTTL	DK_UNIT_INIT	- Unit initialization routine
;+
; DK_UNIT_INIT
;
; This routine performs unit-specific initialization and is called for each
; disk found on the SCSI bus. A connection to the port driver is established, 
; which lasts for the life of the system. All traffic to this SCSI device is 
; directed over this connection.
;
; The first time through this routine a set of SPTEs are allocated which are
; used to double map the user's buffer during datacheck operations. The cells 
; used to record the address of the SPTEs and the system virtual address 
; mapped by them live in the driver image. This is possible since use of the
; SPTEs is synchronized with the fork lock.
;
; An inquiry command is sent to the target to determine the device type.
; In additon, several sanity checks are made on the inquiry data to determine 
; if the device is valid. If so, the unit is placed online and any I/O queued
; to the device during initialization is started.
;
; INPUTS:
;
;	R5	- UCB address
;
; OUTPUTS:
;
;	R0-R3	- Destroyed
;	All other registers preserved
;-

DATACHECK_SPTE:				; SVA of SPTEs used to double map user
	.LONG	0			; buffer during datacheck operation
DATACHECK_SVA:				; SVA mapped by this set of SPTEs
	.LONG	0			; 


DK_UNIT_INIT:				; Initialize unit

	BRB	1$			; Skip call to INI$BRK
	jsb	g^ini$brk		; *** Debug ***

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

1$:     BBC     #UCB$V_POWER,-          ; Branch if we're not here due to a
		UCB$W_STS(R5),2$	; powerfail
	RSB                             ; Otherwise, exit immediately

2$:	FORK				; Fork to drop IPL to SYNCH
	FORK				; Fork to drop IPL to SYNCH
	.IF DEFINED DEBUG
	BSBW	SETUP_TRACE		; Set up trace buffer
	.ENDC
	ASSUME	UCB$V_DISCONNECT LT 8
	MOVB	#UCB$M_DISCONNECT!-	; By default, assume the target device
		 UCB$M_SYNCHRONOUS,-	; is capable of both disconnecting and
		UCB$L_DK_FLAGS(R5)	; synchronous operation

	ASSUME	UCB$V_DD_BYPASS LT 16
	BICW2	#UCB$M_DD_BYPASS,-	; Ensure no writes to DD diskettes
		UCB$L_DK_FLAGS(R5)	; on an RX23S drive
					; (provides space for an in-place
					; patch)

	CLRB	UCB$B_SEEK_DIR(R5)	; Initialize seek direction flag
	CLRL	UCB$L_SAVE_CONN_CHAR(R5); Initialize location to hold
					; connection characteristics used 
					; in IO_DIAGNOSE calls.
	BISW	#UCB$M_ONLINE!-		; Set unit online and busy (in case this
		 UCB$M_BSY,-		; is the system disk, we don't want to
		UCB$W_STS(R5)		; prevent I/O from being queued)
	MOVAL	UCB$L_FLUSH_IOQFL(R5),R0; Initialize the queue used to flush
	MOVL	R0,(R0)			; I/Os which are queued during unit
	MOVL	R0,4(R0)		; init when unit init fails.
;
; Setup UCB CDDB field.
;
	MOVL	UCB$L_CRB(R5),R0	; Get CRB address
	MOVL	CRB$L_AUXSTRUC(R0),-	; Get CDDB address out of the CRB.
		UCB$L_CDDB(R5)		;
;
; Check system disk.
;
	CMPL	G^SYS$AR_BOOTUCB,R5	; Is this the system disk?
	BNEQ	5$			; Branch if not
	MOVL	#^X7FFFFFFF,-		; Set up a dummy MAXBLOCK value to
		UCB$L_MAXBLOCK(R5)	; support booting
;
; Check for host based shadowed system disk.  If so, fork and wait until
; the controller init routine of SHDRIVER completes its execution.
;
	BLBC	G^EXE$GL_SHADOW_SYS_DISK,5$
					; LBC not booting hbs. Continue.

3$:	CMPL	G^SYS$AR_BOOTUCB,-	; Is the system disk pointer updated?
		G^EXE$GL_SYSUCB		;
	BNEQ	5$			; NEQ means updated. Continue.

	FORK_WAIT			; Fork and wait. (R3-R5 preserved.)
	BRB	3$			; Try again.

5$:
	.IF DEFINED DEBUG
	MOVL	TR$TRACE_BUFFER_ADDR,-	; Save address of trace buffer
		UCB$L_TRACE_BUF(R5)
	CLRL	R3			; Prepare to call TRACE_QIO
	BSBW	TRACE_QIO		; Trace this QIO (special case when
					; called from UNIT INIT, R3 must be 0)
	.ENDC

        MOVL    #SCDRP$C_LENGTH,R1      ; Length of SCDRP packet
        JSB     G^EXE$ALONONPAGED       ; Allocate a block
        BLBS    R0,7$                   ; Branch if success
        BUG_CHECK INCONSTATE,FATAL      ; Otherwise, bugcheck
7$:     PUSHR   #^M<R0,R1,R2,R3,R4,R5>  ; Save regs
        MOVC5   #0,(SP),#0,R1,(R2)      ; Initialize the packet
        POPR    #^M<R0,R1,R2,R3,R4,R5>  ; Restore regs
        MOVL    R2, UCB$L_STACK_SCDRP(R5) ; Save address of SCDRP
        INIT_SCDRP_STACK SCDRP=R2       ; Initialize the internal stack in the SCDRP

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

	CLRL	UCB$L_QUEUED_IO_COUNT(R5) ; clear count

.IF	DEFINED COLLECT_PERF_DATA
	CLRL	UCB$L_READ_COUNT(R5) 	; Clear perf counters
	CLRL	UCB$L_WRITE_COUNT(R5) 	; Clear perf counters
	CLRL	UCB$L_OTHER_COUNT(R5) 	; Clear perf counters
	TSTL	UCB$L_READ_XLEN_HIST(R5) ; Do we already have pool?
	BLSS	1000$			; Br if yes
	MOVL	#XLEN_HIST_SIZE,R1	; Size of required pool
	BSBW	ALLOC_POOL		; Allocate a histogram buffer
	MOVL	R2,UCB$L_READ_XLEN_HIST(R5) ; Save histogram address
	MOVL	#XLEN_HIST_SIZE,R1	; Size of required pool
	BSBW	ALLOC_POOL		; Allocate a histogram buffer
	MOVL	R2,UCB$L_WRITE_XLEN_HIST(R5) ; Save histogram address
	MOVL	#XLEN_HIST_SIZE,R1	; Size of required pool
	BSBW	ALLOC_POOL		; Allocate a histogram buffer
	MOVL	R2,UCB$L_XLEN_HIST(R5) 	; Save histogram address
1000$:	PUSHR	#^M<R0,R1,R2,R3,R4>	; Save regs
	PUSHL	R5			; Save UCB seperatly 
	MOVL	#XLEN_HIST_SIZE,R1	; length
	MOVL	UCB$L_READ_XLEN_HIST(R5),R2 ; Address
	MOVC5	#0,.,#0,R1,(R2)		; Clear read counters
	MOVL	(SP),R5			; Get copy of UCB
	MOVL	#XLEN_HIST_SIZE,R1	; length
	MOVL	UCB$L_WRITE_XLEN_HIST(R5),R2 ; Address
	MOVC5	#0,.,#0,R1,(R2)		; clear write counters
	MOVL	(SP),R5			; Get copy of UCB
	MOVL	#XLEN_HIST_SIZE,R1	; length
	MOVL	UCB$L_XLEN_HIST(R5),R2 	; Address
	MOVC5	#0,.,#0,R1,(R2)		; clear write counters
	POPL	R5			; Restore UCB
	POPR	#^M<R0,R1,R2,R3,R4>	; Save regs
	MOVW	#XLEN_HIST_TURNOVER,-	; Set I/O counter for qdepth check
		UCB$W_XLEN_HIST_CYCLE(R5)
	MOVAB	UCB$L_QDEPTH(R5),-	; Point to start of our perf data area
		UCB$L_2P_LINK(R5)	; for DKPERF.C

.ENDC	; DEFINED COLLECT_PERF_DATA

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

	MOVL	#SS$_BADPARAM,R0	; Assume bad LUN or SUBLUN specified
	MOVZWL	UCB$W_UNIT(R5),R1	; Get device unit number
	CLRL	R2			; Prepare for extended divide
	EDIV	#100,R1,R1,R2		; Extract SCSI bus ID from LUN
	CMPL	R1,#7			; Valid SCSI ID (0 <= n <= 7)?
	BGTRUW	20$			; Branch if not 
	CMPL	R2,#7			; Valid LUN (0 <= n <= 7)?
	BGTRUW	20$			; Branch if not 
	MULB3	#<1@5>,R2,UCB$B_LUN(R5)	; Save LUN (shifted left 5 bits for use
					; later in SETUP_CMD)
	ASHL	#16,R1,R1		; Place SCSI ID in high-order word of R1
	ASHL	#16,R2,R2		; Place LUN in high-order word of R2
	MOVL	UCB$L_DDB(R5),R0	; Get DDB address
	SUBB3	#^A'A',-		; Translate controller letter to
		DDB$T_NAME+3(R0),R1	; SCSI bus ID.
	SPI$CONNECT			; Connect to the port driver
	BLBC	R0,20$			; Branch if connect attempt failed
	BBC	#SPDT$V_CMDQ,R3,11$	; See if port supports Command Queing
	BISL	#UCB$M_PORT_CMDQ,-	; Set port_cmdq bit
		UCB$L_DK_FLAGS(R5)	;   in UCB flags
11$:	ASHL	#-24,R3,R3		; Get MAXBCNT divisor
	BNEQ	12$			; Branch if divisor supplied by port
	MOVZBL	#1,R3			; Otherwise, use a divisor of 1
12$:	DIVL	R3,R1			; Get MAXBCNT recommended by port
	BICL	#511,R1			; Make MAXBCNT an integral block count
	CMPL	R1,UCB$L_MAXBCNT(R5)	; For MAXBCNT, use minimum supported
	BGEQ	15$			; value of port and class drivers
	MOVL	R1,UCB$L_MAXBCNT(R5)	; Save maximum byte count in UCB
15$:	MOVL	R2,UCB$L_SCDT(R5)	; Save SCDT address
	MOVL	R4,UCB$L_PDT(R5)	; Save PDT address

16$:	TSTL	DATACHECK_SPTE		; Datacheck SPTEs already allocated
	BNEQ	18$			; Branch if so
	ASHL	#-9,UCB$L_MAXBCNT(R5),R2; Convert to max page count
	INCL	R2			; Account for non-page-alligned buffers
	JSB	G^LDR$ALLOC_PT		; Allocate SPTEs to double map user buf
	BLBC	R0,20$			; Branch if failure
	MOVL	R1,DATACHECK_SPTE	; Save SVA of the first SPTE
	SUBL2	G^MMG$GL_SPTBASE,R1	; Get offset into page table
	ASHL	#<VA$S_BYTE-2>,R1,R1	; Calculate system virtual address
	BISL3	#VA$M_SYSTEM,R1,-	; mapped by this set of SPTEs
		DATACHECK_SVA		; 

18$:	BSBW	SET_UNIT_ONLINE		; Go bring the unit online
	RSB				; Return to caller

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


	.SBTTL	DK_STARTIO	- Driver QIO entry point
;+
; DK_STARTIO
;
; This routine is the QIO entry point into the driver. It's main function
; is to dispatch to the function-code-specific routine which then executes
; the QIO.
;
; INPUTS:
;
;	R3	- IRP address
;	R5	- UCB address
;
; OUTPUTS:
;
;	R0	- 1st longword of I/O status: contains status code and
;		  number of bytes transferred
;	R1	- 2nd longword of I/O status: l.o. word contains h.o.
;		  word of number of bytes transferred
;	R4	- Destroyed
;	All other registers preserved
;-

DK_STARTIO:

	IF_NOT_CMDQ	1$, UCB=R5	; Br if device does not support queueing
	BICW	#UCB$M_BSY,-		; Clear the BSY bit to allow QIO to
		UCB$W_STS(R5)		; send multiple I/O's to the port
1$:
	.IF DEFINED DEBUG
	BSBW	TRACE_QIO		; Trace the current I/O request
	.ENDC

	INCL	UCB$L_QUEUED_IO_COUNT(R5) ; Bump count
.IF	DEFINED COLLECT_PERF_DATA
	INCL	UCB$L_OTHER_COUNT(R5) 	  ; Assume not a read/write for now
.ENDC

	MOVL	UCB$L_PDT(R5),R4	; Get PDT address
	MOVL	R3,R2			; Copy IRP address
	MOVL	R5,R3			; Copy UCB address
	BSBW	ALLOC_SCDRP		; Allocate an SCDRP
	MOVL	R2,SCDRP$L_IRP(R5)	; Save IRP address in SCDRP
;
; Allow only physical I/O functions before a PACKACK is issued.
;
	BBS	#IRP$V_PHYSIO,-		; Branch if physical I/O function
		IRP$W_STS(R2),10$

	BBCW	#UCB$V_VALID,-		; Logical or virtual I/O function
		UCB$W_STS(R3),-		; Branch if volume is invalid
		VOLUME_INVALID2

10$:	EXTZV	#IRP$V_FCODE,-		; Extract I/O function code
		#IRP$S_FCODE,- 		;
		IRP$W_FUNC(R2),R1	;
	ASSUME	IRP$S_FCODE LE 7	; Allow byte mode dispatch
	DISPATCH R1,TYPE=B,<-		; Dispatch according to function
		<IO$_NOP,	IO_NOP>,- 	; ^X00
		<IO$_UNLOAD,	IO_UNLOAD>,- 	; ^X01
		<IO$_SEEK,	IO_SEEK>,- 	; ^X02
		<IO$_RECAL,	IO_RECAL>,- 	; ^X03
		<IO$_PACKACK,	IO_PACKACK>,- 	; ^X08
		<IO$_WRITECHECK,IO_WRITECHECK>,-; ^X0A
		<IO$_WRITEPBLK,	IO_WRITEPBLK>,-	; ^X0B
		<IO$_READPBLK,	IO_READPBLK>,- 	; ^X0C
		<IO$_WRITEHEAD,	IO_WRITEHEAD>,-	; ^X0D
		<IO$_READHEAD,	IO_READHEAD>,- 	; ^X0E
		<IO$_AVAILABLE,	IO_AVAILABLE>,-	; ^X11
		<IO$_DSE,	IO_DSE>, -	; ^X15
		<IO$_DIAGNOSE,	IO_DIAGNOSE>,- 	; ^X1D
		<IO$_READLBLK,	IO_READLBLK>,- 	; ^X21
		<IO$_FORMAT,	IO_FORMAT>,-	; ^X30
		<IO$_AUDIO, 	IO_AUDIO>>	; ^X37

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

IO_BOGUS:
IO_SEEK:
IO_RECAL:
IO_WRITEHEAD:
IO_READHEAD:
IO_READLBLK:

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

IO_NOP:
	DISABLE_ERRLOG			; Temporarily disable errorlogging
	BSBW	TEST_UNIT_READY		; Issue an ORDERED TUR to sync queues
	REENABLE_ERRLOG			; Reenable errorlogging
	MOVZWL	#SS$_NORMAL,R0		; Set success status
	BRB	COMPLETE_IO		; Complete the I/O


;+
; The volume was not software enabled, however audio functions don't
; require that the volume (disk)  be software enabled. If the I/O function is
; an audio function then goto the audio fdt code.
;-
VOLUME_INVALID2:
	EXTZV	#IRP$V_FCODE,-		; Extract I/O function code
		#IRP$S_FCODE,- 		;
		IRP$W_FUNC(R2),R1	;
	ASSUME	IRP$S_FCODE LE 7	; Allow byte mode dispatch
	CMPB	#IO$_AUDIO,R1		; Is this an AUDIO function?
	BNEQ	VOLUME_INVALID		; No, return error
	BRW	IO_AUDIO		; Yes, do audio otherwise return error

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

COMPLETE_IO:

	DECL	UCB$L_QUEUED_IO_COUNT(R3) ; Adjust I/O count
	BGEQ	1$
	BUG_CHECK	INCONSTATE,FATAL ; Somethings wrong if this is neg!
1$:
	MOVL	SCDRP$L_IRP(R5),-	; Set up UCB$L_IRP for the actual
		UCB$L_IRP(R3)		; IRP that's completing!

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

	.IF DEFINED DEBUG
	BSBW	TRACE_QIO_STAT		; Save the final QIO status in trace buf
	BLBC	R0,2$			; Branch on error
	BRW	10$			; Branch on success status
2$:	NOP				; Instruction to trap on QIO with bad status
	.ENDC

	DISPATCH R0,TYPE=W,<-           ; Dispatch by error status
                <SS$_RECOVERR,  100$>,- ; Recoverable error status
                <SS$_TIMEOUT,   110$>,- ; Timeout error status
                <SS$_DRVERR,    120$>,- ; Drive error status
                <SS$_CTRLERR,   120$>,- ; Controller error status
                <SS$_DEVOFFLINE,120$>>  ; Offline error status
        BRW     10$                     ; Fall through


; In order to allow the lower layers to distinguish between recoverable and
; non-recoverable errors, a new status code was invented called SS$_RECOVERR.
; This status code should never make it up to this level unless the target
; returns a recoverable error sense key for a SCSI command from which we don't
; expect it. To prevent us from possibly returning a bogus status code for the 
; QIO, translate this status to one that VMS knows about, namely, SS$_PARITY.

100$:   MOVL    #SS$_PARITY,R0          ; Change to a valid error status
        BRW     10$

; Timeouts may have occurred that are merely the result of a bus reset (as
; frequently happens in multi-host configurations).  Rather than failing
; this on the spot, convert the error to SS$_MEDOFL to allow retries.

110$:   MOVL    #SS$_MEDOFL,R0          ; Change to MEDOFL
        BRW     10$

; For various other errors, force the driver into mount verify exactly
; once in order to give it a second chance.  Set IRP$V_FORCEMV to indicate
; we are forcing mount verification.

120$:   MOVL    UCB$L_IRP(R5),R2        ; Get IRP address
        BBSS    #IRP$V_FORCEMV,-
                IRP$W_STS2(R2),10$      ; Already MV'd once before,
                                        ;  so no more allowed.
        MOVL    #SS$_MEDOFL,R0          ; Convert to MEDOFL in order
					;  to force MV

; If the device does NOT support command queing and there are two or more 
; IRPs in the pending queue, attempt to reorder the queue such that the IRP 
; for the block closest to the current head position in the direction of the 
; current head motion is executed first.

10$:	IF_CMDQ	20$,UCB=R5		; Skip reordering for CMDQ devices
        MOVAL   UCB$L_IOQFL(R5),R2      ; Get address of I/O pending queue in U
        CMPL    @(R2),R2                ; Fewer than two pending IPRs?
        BEQL    20$                     ; Branch if so, no need to reorder
        PUSHQ   R0                      ; Save I/O status
        BSBW    ATTEMPT_REORDER         ; Attempt to reorder read/write request
        POPQ    R0                      ; Restore I/O status
20$:	REQCOM				; Complete the I/O


	.SBTTL	DK_CANCEL	- Cancel I/O routine

;+
; DK_CANCEL
;
; This routine cancels an I/O in progress. Since the timeouts for disks are
; relatively short, no device-dependent action is taken. Instead, it relies
; on the port driver's relatively short timeout to terminate any I/O in
; progress.
;
; Inputs:
;
;	R2	- channel index number
;	R3	- address of the current IRP (I/O request packet)
;	R4	- address of the PCB (process control block) for the
;		  process canceling I/O
;	R5	- address of the UCB (unit control block)
;	R8	- cancel reason code, one of:
;			CAN$C_CANCEL	if called through $CANCEL or 
;					$DALLOC system service
;			CAN$C_DASSGN	if called through $DASSGN system 
;					service
; Outputs:
;
;	R0-R3	- Destroyed
;	All other registers preserved
;-

DK_CANCEL:				; Cancel an I/O operation
	TSTL	R3			; IRP address of 0? (this can happen
					; during UNIT INIT in bringing the
					; device online)
	BEQL	10$			; Branch if not
                                        ;TGG0003
        CMPL    IRP$L_PID(R3),-         ; Test PID(IRP) equal GK_PID(UCB)
                UCB$L_GK_PID(R5)        ;
        BNEQ    7$                      ; Branch if no match
        CLRL    UCB$L_GK_PID(R5)        ; Allow other processes to use DIAGNOSE
        BBCC    #UCB$V_GK_CHK_COND,-    ; If prev I/O got CHECK_COND
                UCB$L_DK_FLAGS(R5),7$   ; clear CHECK_COND flag
7$:	JSB	G^IOC$CANCELIO		; Set cancel bit if appropriate.
	BBC	#UCB$V_CANCEL,-		; If the cancel bit is not set,
		UCB$W_STS(R5),10$	; just return.
10$:
	RSB				; Return

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

DK_REG_DUMP:

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

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

; Error types which have additional data come here.

30$:	MOVL	SCDRP$L_SVA_USER(R1),R2	; Get address of additional data
	BEQL	50$
	MOVZBL	SCDRP$L_TRANS_CNT(R1),R1; Get length of additional data
	BEQL	50$			; Branch if none available
	BRB	37$

; Special entry point for reassign block error. The additional data length
; is contained in SCDRP$L_BCNT rather than in SCDRP$L_TRANS_CNT.

35$:	MOVL	SCDRP$L_SVA_USER(R1),R2	; Get address of additional data
	MOVZBL	SCDRP$L_BCNT(R1),R1	; Get length of additional data
		
37$:	MOVB	R1,-1(R0)		; Save additional data length
40$:	MOVB	(R2)+,(R0)+		; Save a byte of extended sense data
	SOBGTR	R1,40$			; Repeat for all extended sense data
;	BRB	50$			; Use common exit

50$:	SUBL	(SP),R0			; Calculate number of bytes saved
	DECL	R0			; Round up to next longword (+3), but
					; don't count LW count field itelf in
					; LW count (-4)
	ASHL	#-2,R0,@(SP)+		; Save in LW count field at top of buffer
	RSB				; Return


	.SBTTL	DK_DSE 		- Data Security Erase FDT Routine
;+
; DK_DSE
;
; This is the FDT routine for the Data Security Erase operation.
; The byte count (P2) is stored in IRP$L_BCNT. The starting logical 
; block (P3) is stored in IRP$L_MEDIA. Control is transfered to 
; EXE$QIODRVPKT, thus queueing the I/O request to the driver's start 
; I/O routine.
;
; INPUTS:
;
;	R3	- IRP address
;	P2(AP)	- Byte count
;	P3(AP)	- Starting logical block
;
; OUTPUTS:
;
;	IRP$L_BCNT(R3)  - Byte count
;	IRP$L_MEDIA(R3) - Starting logical block
;-

DK_DSE:

	MOVL	P2(AP), IRP$L_BCNT(R3)		; Setup erase byte count
	MOVL	P3(AP), IRP$L_MEDIA(R3)		; Setup erase starting LBN
	JMP	G^EXE$QIODRVPKT			; Send request to STARTIO


	.SBTTL	DK_DIAGNOSE	- FDT preprocessing for Special pass-through function
;+
; DK_DIAGNOSE
;
; This routine performs FDT preprocessing including:
;
;	- Validating access to the descriptor buffer
;	- Validating access to, and locking, the read/write buffer 
;	- Copying the SCSI command to a buffer in non-paged pool
;
; INPUTS:
;
;	R0	- Address of FDT routine
;	R3	- IRP address
;	R4	- PCB address
;	R5	- UCB address
;	R6	- CCB address
;	R7	- Bit number of user-specified I/O function code
;	R8	- Address of current entry in FDT
;	AP	- Address of first function-dependent argument (P1)
;
; OUTPUTS:
;
;-

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

DK_DIAGNOSE:

	BBCS	#UCB$V_GK_ACTIVE,-	; Check for another process using
		UCB$L_DK_FLAGS(R5),1$	; the DIAGNOSE entry 
	BRW	35$			; Lock out access
1$:	BBC	#UCB$V_GK_CHK_COND,-	; If check cond pending, make sure
		UCB$L_DK_FLAGS(R5),2$	; this is the same proc that caused it
	CMPL	IRP$L_PID(R3),-		; Is this the PID that caused chk cond?
		UCB$L_GK_PID(R5)	; 
	BEQL	2$			; OK, let it through
	BICL	#UCB$M_GK_ACTIVE,-	; Clear lock out
		UCB$L_DK_FLAGS(R5)
	BRW	35$			; nope, fail the I/O
2$:	MOVL	IRP$L_PID(R3),-		; Remember the process using DIAGNOSE
		UCB$L_GK_PID(R5)	; 

.IF	DEFINED V60_BUILD

        MOVL    UCB$L_ORB(R5),R9        ; Get address of object rights block
        CLRQ    -(SP)                   ; terminator and retlen for privilege a
        MOVL    ORB$L_NAME_POINTER(R9),-(SP)    ; Bufadr for device name
        MOVZWL  ORB$W_NAME_LENGTH(R9),R9        ; save size of device name
        ADDL3   #<NSA$_DEVICE_NAME@16>,R9,-(SP) ; itmcod and bufsiz of item ent
        AUDIT_S_ITMLST = 16             ; Size of the itemlist is 16 bytes
        MOVL    SP,R9                   ; save pointer to itemlist

        $IFPRIV DIAGNOSE,       -       ; Branch if process has DIAGNOSE priv
                10$,            -
                MSG=DIAGNOSE_7, -
                 ITMLST=R9,     -
                PRESERVE=NO

        MOVL    #SS$_NOPRIV,R0          ; Set no privilege status
        ADDL    #AUDIT_S_ITMLST,SP      ; Restore stack
	BICL	#UCB$M_GK_ACTIVE,-	; Clear lock out
		UCB$L_DK_FLAGS(R5)
        BRW     50$                     ; Branch to abort the I/O

.ENDC   ; DEFINED V60_BUILD

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

10$:	MOVQ	(AP),R0			; Get user descriptor address, length
	MOVL	R0,R9			; Save a copy of descriptor address
	CMPL	R1,#60			; Valid descriptor length
	BLSSW	40$			; Branch if not
	JSB	G^EXE$WRITECHK		; Check for read access to the descriprot
					; buffer (don't return if no access)

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

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

35$:	MOVL	#SS$_CHANINTLK,R0	; channel usage interlocked
	BRB	55$
40$:	MOVL	#SS$_BADPARAM,R0	; Set bad parameter status
50$:	CLRL	UCB$L_GK_PID(R5)	; Clear DIAGNOSE user PID
	BICL	#UCB$M_GK_ACTIVE,-	; Clear lock out
		UCB$L_DK_FLAGS(R5)
55$:	JMP	G^EXE$ABORTIO		; Abort the I/O with status in R0

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

60$:	CLRL    UCB$L_GK_PID(R5)        ; Clear DIAGNOSE user PID
        BICL    #UCB$M_GK_ACTIVE,-      ; Clear lock out
                UCB$L_DK_FLAGS(R5)
	PUSHQ	R0			; Save regs
	MOVL	IRP$L_MEDIA(R3),R0	; Get address of non-paged pool buffer
					; containing SCSI command
	MOVL	-(R0),R1		; Get length of buffer
	JSB	G^EXE$DEANONPGDSIZ	; Deallocate the packet
	POPQ	R0			; Restore regs
	RSB				; Return from co-routine call


	.SBTTL	DK_SHAD_WCHECK - Check write to shadow mbr for priv
;++
;
; DK_SHAD_RWCHECK - Check read/write to shadow mbr for privilege
;
; Functional Description:
;
;	Allow only processes with SYS privilege to perform WRITES to
;	Host Based Shadowing shadow set members.
;
; Inputs:
;
;	R3	IRP address
;	R5	UCB address (member)
;
; Implicit inputs: None.
;
; Outputs:None.
;
; Implicit outputs: None.
;
; Condition codes:
;
;	SS$_ILLIOFUNC	- I/O directed to shadow set member by a process
;			  that doesn't have sys priv.
;--

DK_SHAD_WCHECK:

	BBS	#DEV$V_SHD,-			; If this device is a shadow
		UCB$L_DEVCHAR2(R5),10$		;  set member, check
	RSB					; Else, continue FDT processing

10$:	MOVL	IRP$L_ARB(R3),R0		; Get ARB address
	BEQL	99$				; If ARB absent, exit
	ASSUME	PRV$V_SYSPRV LT 32
	BBC	#PRV$V_SYSPRV,ARB$Q_PRIV(R0),99$; No SYSPRV, illegal
	RSB					; Continue FDT processing

99$:	MOVZBL	#SS$_ILLIOFUNC,R0		; Set error status
	JMP	G^EXE$FINISHIOC			; Complete I/O request


	.SBTTL	DK_CRESHAD - CRESHAD FDT routine
	.SBTTL	DK_REMSHAD - REMSHAD FDT routine
;++
;
; DK_CRESHAD - CRESHAD FDT routine
; DK_REMSHAD - REMSHAD FDT routine
;
; Functional Description:
;
;	Dispatch CRESHAD and REMSHAD requests to shadowing driver.
;
; Inputs:
;
;	R3	IRP address
;	R5	UCB address (member)
;
; Implicit inputs: Dispatch vector filled in.
;
; Outputs:None.
;
; Implicit outputs: None.
;
; Condition codes:
;
;	SS$_ILLIOFUNC	- Dispatch vector not set up.
;--

DK_CRESHAD:				; ----> IO$_CRESHAD
DK_REMSHAD:				; ----> IO$_REMSHAD

	MOVL	G^EXE$GL_HBS_PTR,R0		; Shadow Dispatcher
	BGEQ	10$				; Illegal if not filled in
	JMP	(R0)				; Jump to dispatcher

10$:	MOVZBL	#SS$_ILLIOFUNC,R0		; Set error status
	JMP	G^EXE$FINISHIOC			; Complete I/O request



	.SBTTL	DK_AUDIO	- FDT preprocessing for CD-ROM Audio functions
;+
; DK_AUDIO
;
; This routine performs FDT preprocessing for SCSI AUDIO commands.
; The application passes the address of a control structure called the Audio 
; Control Block (AUCB) in P1 and the size of the AUCB in P2. In this FDT 
; routine the AUCB and any other buffers presented by the users will be locked
; down and double mapped into S0 and P0 space. The AUCB is a control structure
; which passes audio command specific parameters as well as describes users 
; buffers that may be used by the driver to return control or CD state 
; information (TOC) to the application.
;
; In order to accommodate multiple user buffers being used in a single I/O, an
; IRP extension (IRPE) is allocated and linked to the original IRP. The 
; original IRP contains the mapping information about the AUCB and the IRPE is
; used to contain the mapping information for the optional destination and 
; sense buffers.
;
; INPUTS:
;
;	R0	- Address of FDT routine
;	R3	- IRP address
;	R4	- PCB address
;	R5	- UCB address
;	R6	- CCB address
;	R7	- Bit number of user-specified I/O function code
;	R8	- Address of current entry in FDT
;	AP	- Address of first function-dependent argument (P1)
;
; OUTPUTS:
;
;-

;+	
; Definition of the offsets into the Audio Control Block (AUCB) for DKDRIVER.
;-


	CD_FUNCTION_CODE 	= 0	; Audio function code
	CD_AUCB_VERSION 	= 2	; Version number of AUCB structure
	CD_ARG1 		= 4	; Command specific parameter
	CD_ARG2 		= 8	; Command specific parameter
	CD_ARG3 		= 12	; Command specific parameter
	CD_RSVD1 		= 16	; Reserved for future use (MBZ)
	CD_DEST_BUF_ADDR	= 20	; Buffer returned to user
	CD_DEST_BUF_CNT 	= 24	; Size of buffer returned to user
	CD_DEST_BUF_TRANS_CNT 	= 28	; Actual number of bytes received
	CD_COMMAND_STATUS 	= 32	; VMS O.S. Return status
	CD_SCSI_STATUS 		= 36	; SCSI command status (optional)
	CD_SENSE_ADDR		= 40	; Sense data buffer
	CD_SENSE_CNT		= 44	; Sense data buffer size
	CD_SENSE_TRANS_CNT 	= 48	; Sense data transfer count 
	CD_RESVD2 		= 52	; Reserved for future used (MBZ)
	CD_AUCB_SIZE 		= 52	; Size in bytes of AUCB
	CD_AUCB_CUR_VERSION 	= 1	; Current Version number of AUCB

;+
; Definition of SCSI Audio command function codes. These are used by application
; to select individual audio functions supported by the driver.
;-
	PAUSE		= 0		; Pause
	RESUME		= 1		; Resume
	PREVENT_REMOVAL = 2		; Prevent/Allow
	ALLOW_REMOVAL 	= 3		; Prevent/Allow
	PLAY_AUDIO	= 4		; Play Audio LBA
	PLAY_AUDIO_MSF	= 5		; Play MSF
	PLAY_AUDIO_TRACK= 6		; Play Audio Track
	PLAY_TRACK_REL	= 7		; Play Track Relative
	READ_HEADER	= 8		; Reserved
	GET_STATUS 	= 9		; Read Subchannel-Q
	GET_TOC		= 10		; Read TOC
	SET_VOLUME	= 11		; Mode Select
	GET_VOLUME	= 12		; Mode Sense
	SET_DEFAULT	= 13		; Reserved
	GET_DEFAULT	= 14		; Reserved

;+
; Definitions for offsets into the SCSI CCB as used by DKDRIVER.
;-
	PAGE_CODE	= 6		; Page Code Field
	A_LEN		= 8		; Allocation Length Field

DK_AUDIO:
	.ENABLE	LSB

;+
; The AUCB is the Audio Control Block which is constructed by the application and
; passed to the driver during the QIO call. The AP points at the arguments
; to the QIO. The first parameter is the address of the AUCB and the second
; parameter is the size of AUCB. 
;
; Now check to see that we have read access to the AUCB before attempting to
; access this structure and then verify the version number. The version is
; checked to be sure that it matchs the current code. In the future it may 
; become necessary to allow backward compatibility with earlier versions
; of the audio interface within this driver. 
;-

10$:	BISW	#IRP$M_PHYSIO,-		; All audio functions are physical
		 IRP$W_STS(R3)
	CLRL	IRP$L_WIND(R3)		; Clear this since we use if in startio
	CLRL	IRP$L_EXTEND(R3)	; Clear for starters..
	MOVQ	P1(AP),R0		; Get AUCB information
	BSBW	AUDIO_MAP_PAGE		; Locks and Maps AUCB.
	BLBS	R0,15$			; Success, then continue
	BRW	AUDIO_EXIT_FDT		; Exit FDT routine on error R0 = error
15$:	CMPL	#CD_AUCB_SIZE,P2(AP)	; There must be an AUCB
	BGTRW	40$			; The AUCB must be at least this big.

;+
; Now that we have locked down and prepared the AUCB for startio, determine
; whether or not there is a optional sense buffer. If there is a buffer then
; allocate an IRP extension to save the state of this buffer. If there is 
; no sense buffer required, simply continue to process this request.
;-
	MOVL	P1(AP),R0		; Get address of AUCB
	MOVL	CD_SENSE_ADDR(R0),R0	; Get Address of Sense buffer
	BEQL	30$			; No sense buffer continue
	
	BSBW	SETUP_SENSE_BUFFER	; There's a sense buffer set it up.
	BLBC	R0,AUDIO_EXIT_FDT	; Exit FDT routine on error R0 = error

;+
; All audio I/O must go through the processing above, however some audio 
; functions require additional processing before these requests can be queued
; to the startio routine.
;
; Now that we have double mapped the AUCB and Sense Buffer determine whether there are
; any additional buffers that need to be mapped. 
;-
30$:	MOVL	P1(AP),R9			; Get user AUCB address 

	DISPATCH CD_FUNCTION_CODE(R9),TYPE=B,<-	; Dispatch based on function
	    	<GET_STATUS,	FDT_READ_SUB>,- 	       
 	    	<GET_TOC,	FDT_READ_TOC>>

;+
; For all other requests, simply issue this IRP to the startio routine.
;- 
	JMP     G^EXE$QIODRVPKT         ;QUEUE DRIVER PACKET
	BRW	40$

;+
; The following two AUDIO operations return data to an optional user buffer.
;-
FDT_READ_TOC:
FDT_READ_SUB:

;+
; Check the source/destination buffer for access and protection.
;- 
	TSTL	CD_DEST_BUF_ADDR(R9)	; Test address of the dest buffer
	BEQLW	40$			; 
	TSTL	CD_DEST_BUF_CNT(R9)	; Test size of destination buffer.
	BEQLW	40$			; If there is a buffer, must <> 0

;+
; Since there is another buffer that needs to be processed, if an IRPE
; hasn't already been allocated, allocate it now.
;-
	TSTL	IRP$L_EXTEND(R3)	; Test for IRP Extension
	BEQL	NO_IRP			; No, IRPE already.
	MOVL	IRP$L_EXTEND(R3),R3	; Get IRPE address
	BRB	GOT_IRPE		; Yes, already have an IRPE.

NO_IRP:
	BSBW	ALLOC_IRPE		; Allocate an IRP extension
	BLBC	R0,AUDIO_EXIT_FDT	; Exit if no IRPE

GOT_IRPE:
	ASSUME	CD_DEST_BUF_ADDR+4 EQ CD_DEST_BUF_CNT
	MOVQ 	CD_DEST_BUF_ADDR(R9),R0	; Get address and size of dest buffer.N
	MOVL	R1,IRP$L_BCNT(R3)	; Copy Byte count to IRPE
	BICW3	#^C<VA$M_BYTE>,R0,-	; Get BOFF for Destination buffer.
		 IRP$W_BOFF(R3)
	BSBW	AUDIO_MAP_PAGE		; Locks and Maps Destination Buffer.	
        BLBC    R0,AUDIO_EXIT_FDT       ; Exit if fail.

	MOVL	IRP$L_SEQNUM(R3),R3	; Restore  Original IRP Address
	JMP	G^EXE$QIODRVPKT		; Queue the packet to the driver	


;+ 
; Error Paths for DK_AUDIO FDT routine.
;-
40$:	PUSHL	R2
	MOVL	R3,R2			; Get IRP Address in R2
	BSBW	AUDIO_EXIT_FREE		; Free Allocated resources
	POPL	R2
	MOVZBL	#SS$_BADPARAM,R0	; Set bad parameter status
50$:	JMP	G^EXE$ABORTIO		; Abort the I/O with status in R0

;+
; Error exit path from audio fdt routines.
;-
AUDIO_EXIT_FDT:
	PUSHR	#^M<R0,R2>
	MOVL	R3,R2			; Get IRP Address in R2
	BSBW	AUDIO_EXIT_FREE		; Free Allocated resources
	POPR	#^M<R0,R2>
	MOVZWL	R0,CD_COMMAND_STATUS(AP); Set bad status is AUCB.
	BRB	50$
	.DISABLE LSB			; DK_AUDIO


	.SBTTL	+
	.SBTTL	+ QIO INTERFACE ROUTINES
	.SBTTL	+


	.SBTTL	IO_PACKACK	- Pack acknowledge function
;+
; IO_PACKACK
;
; This routine executes a PACKACK request by sending and inquiry, ensuring the 
; device is spun up, reading its geometry information, and, if necessary, doing 
; device specific setup such as setting the default block size and error 
; recovery parameters.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:         
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_PACKACK:

	BISW	#UCB$M_BSY,-		; Set the BSY bit to prevent QIO from
		UCB$W_STS(R3)		; sending multiple I/O's to the port
					; While PACKACK is in progress

; Clear write protect status, since the floppy diskette can be changed.

	BICL	#UCB$M_HWL,-		; Assume medium is writeable
		UCB$L_DK_FLAGS(R3)	; until proven otherwise
					; (Removed clear of SWL here - JJF0040)

	BSBW	WAIT_UNIT_READY		; Wait for the device to spin up
	BLBC	R0,30$			; Branch on error
	BSBW	INQUIRY			; Execute an INQUIRY command
	BLBC	R0,30$			; Branch on error
	BSBW	MODE_SENSE		; Execute a MODE SENSE command
	BLBC	R0,30$			; Branch on error
	BSBW	MODE_SENSE_CHANGABLE	; Execute a MODE SENSE command
					; to get changable parameters
	BLBC	R0,30$			; Branch on error
	BSBW	MODE_SELECT		; Set error recovery parameters, block
					; size, etc. (if necessary)
	BLBS	R0,10$			; Branch on success
	BSBW	MODE_SELECT		; Retry the mode select to work around
					; an RZ23 bug around the use of read long
	BLBC	R0,30$			; Branch on error
10$:	BSBW	READ_CAPACITY		; Execute READ CAPACITY command
	BLBC	R0,30$			; Branch on error
	BSBW	SET_CONN_CHAR		; Set up the connection characteristics
	BLBC	R0,30$			; Branch on error
	BISL	#<UCB$M_VALID>,-	; Set volume valid
		UCB$L_STS(R3)		; 
	BISB	#UCB$M_FIRST_ATTN_SEEN,-; Indicate that the first unit attention
		UCB$L_DK_FLAGS(R3)	; has been seen (any future ones will
					; be logged as errors)

	BBC	#UCB$V_CDROM,-		; Is this device a CD-ROM drive?
		UCB$L_DK_FLAGS(R3),15$	; If no, skip Subchannel data fetch
	CMPB	#DT$_RRD40S,-		; Is device RRD40 ? If so, skip
		UCB$B_DEVTYPE(R3)	;  subchannel data fetch
	BEQL	15$
	BSBW	READ_CD_MCN		; else read the subchannel data and 

15$:	BBSS	#UCB$V_HBS_CHECK,-	; Branch if check for host-based 
		UCB$L_DK_FLAGS(R3),20$	; shadowing has already been made
	BSBW	CHECK_HBS		; Check for host-based shadowing support
20$:	BRW	COMPLETE_IO		; Complete this I/O function

30$:	MOVL	SCDRP$L_IRP(R5),R2	; Get IRP address
	TSTL	IRP$L_PID(R2)		; Check for interanl IRP
	BLSS	20$			; Skip local valid cleanup if internal
	BBCC	#UCB$V_LCL_VALID, -	; Clear local valid bit and
		UCB$L_STS(R3), 20$	; branch if its already clear.
	DECB	UCB$B_ONLCNT(R3)	; Else, decrement the online count.
	BRB	20$


	.SBTTL	IO_READPBLK	- Read a set of blocks from the SCSI drive
	.SBTTL	IO_WRITEPBLK	- Write a set of blocks to the SCSI drive
;+
; IO_READPBLK
; IO_WRITEBLK
;
; This routine reads/writes a set of contiguous blocks from/to the SCSI
; disk. For those transfers that have not already been segmented by the 
; exec to chunks LEQ MAXBCNT, this routine performs the segmentation.
;
; Recoverable and non-recoverable errors are dealt with by this routine.
; In summary, the algorithm is to retry the original operation several times.
; If the error persists, and is anything but a non-recoverable read, then
; a block reassignement is performed. The original data is then written to
; the reasigned block as reassignment is not guaranteed to move the data.
; Retries are performed if necessary at each stage.
;
; Because there is no forced error bit in SCSI, blocks with non-recoverable 
; errors on reads can not be reassigned. (Otherwise, undetected corruption
; would result. It's preferable to return SS$_PARITY status each time the
; failing block is read.).
;
; If the DATACHECK qualifier is specified on the QIO request, the IO_DATACHECK
; routine is invoked to read the set of blocks just read/written. No block
; reassignment is performed by IO_DATACHECK.
;
; INPUTS:
;
;	R2	- IPR address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

WRITE_LOCKED:

	MOVL	#SS$_WRITLCK,R0		; Set write-locked status
	BRW	COMPLETE_IO		; Complete QIO with error status


INVALID_BLOCK_NUMBER:

	MOVL	#SS$_IVADDR,R0		; Otherwise, set invalid address status
	BRW	COMPLETE_IO		; Complete QIO with error status

IO_WRITEPBLK:

.IF	DEFINED COLLECT_PERF_DATA
	INCL	UCB$L_WRITE_COUNT(R3)	; Count writes
;
; Figure out which XLEN bucket this transfer fits in
;
	ADDL3	#511,IRP$L_BCNT(R2),R0	; Round up byte count to block multiple
	ASHL	#-9,R0,R0		; and convert to block count
	ASHL	#-XLEN_HIST_BUCKET_SHIFT,R0,R0 ; Convert to bucket index
	CMPL	#XLEN_HIST_BUCKETS,R0	; Boundary checks...
	BGTR	1000$			; Looks OK
	MOVL	#XLEN_HIST_BUCKETS-1,R0 ; Fix index - >64K in same 64K bucket
1000$:	INCL	@UCB$L_WRITE_XLEN_HIST(R3)[R0] ; Bump count
	INCL	@UCB$L_XLEN_HIST(R3)[R0] ; Bump total count
.ENDC
	BBS	#UCB$V_HWL,-		; Branch if device is write-locked,
		UCB$L_DK_FLAGS(R3),-	; not possible to write device
		WRITE_LOCKED		;
	BICW	#IRP$M_FUNC,-		; Clear the FUNC but to indicate this
		SCDRP$W_STS(R5)		; is a write function
	BBC	#IO$V_MSCPMODIFS,-	; Branch if this is NOT a function to
		IRP$W_FUNC(R2),-	; force a bad block
		IO_RW_COMMON		; Branch to common code
	BRW	FORCE_ERROR		; Otherwise, go force an error

IO_READPBLK:

.IF	DEFINED COLLECT_PERF_DATA
	INCL	UCB$L_READ_COUNT(R3)	; Count reads
;
; Figure out which XLEN bucket this transfer fits in
;
	ADDL3	#511,IRP$L_BCNT(R2),R0	; Round up byte count to block multiple
	ASHL	#-9,R0,R0		; and convert to block count
	ASHL	#-XLEN_HIST_BUCKET_SHIFT,R0,R0 ; Convert to bucket index
	CMPL	#XLEN_HIST_BUCKETS,R0	; Boundary checks...
	BGTR	1500$			; Looks OK
	MOVL	#XLEN_HIST_BUCKETS-1,R0 ; Fix index - >64K in same 64K bucket
1500$:	INCL	@UCB$L_READ_XLEN_HIST(R3)[R0] ; Bump count
	INCL	@UCB$L_XLEN_HIST(R3)[R0] ; Bump total count
.ENDC
	BISW	#IRP$M_FUNC,-		; Set the FUNC but to indicate this
		SCDRP$W_STS(R5)		; is a read function

IO_RW_COMMON:

.IF	DEFINED COLLECT_PERF_DATA
	DECL	UCB$L_OTHER_COUNT(R3) 	  ; Fix count since this is a R/W

;
; See if we need to recalculate the qdepth
;
	DECW	UCB$W_XLEN_HIST_CYCLE(R3) ; count down I/O's till recheck
	BNEQ	100$
	BSBW	CHECK_QDEPTH		; See if we need to set new depth
					; based on xlen histogram info
	MOVW	#XLEN_HIST_TURNOVER,-	; Reset I/O counter for qdepth check
		UCB$W_XLEN_HIST_CYCLE(R3)
100$:


.ENDC
	BBS	#UCB$V_VALID,-		; Branch if volume is valid.
		UCB$W_STS(R3),5$	
	BRW	VOLUME_INVALID		; We need to have done a PACKACK
					; before we can do a read or write
5$:	ADDL3	#511,IRP$L_BCNT(R2),R0	; Round up byte count to block multiple
	ASHL	#-9,R0,R0		; and convert to block count
	ADDL	IRP$L_MEDIA(R2),R0	; Calculate highest block # accessed
	CMPL	R0,UCB$L_MAXBLOCK(R3)	; Valid block number?
	BGTRW	INVALID_BLOCK_NUMBER	; Branch if not

10$:	CLRL	SCDRP$L_ABCNT(R5)	; Initialize accumulated byte count
	MOVW	IRP$W_FUNC(R2),-	; Copy function code and modifiers,
		SCDRP$W_FUNC(R5)	; MEDIA, SVAPTE, and BOFF fields
	MOVL	IRP$L_MEDIA(R2),-	; from the IRP to the SCDRP
		SCDRP$L_MEDIA(R5)	; 
	MOVL	IRP$L_SVAPTE(R2),-	;
		SCDRP$L_SVAPTE(R5)	;
	MOVW	IRP$W_BOFF(R2),-	;
		SCDRP$W_BOFF(R5)	;

IO_RW_LOOP:

	SUBL3	SCDRP$L_ABCNT(R5),-	; Attempt to transfer all remaining 
		IRP$L_BCNT(R2),-	; bytes in user's buffer
		SCDRP$L_BCNT(R5)	;
	CMPL	SCDRP$L_BCNT(R5),-	; Transfer length greater than maximum
		UCB$L_MAXBCNT(R3)	; supported?
	BLEQU	10$			; Branch if not
	MOVL	UCB$L_MAXBCNT(R3),-	; Otherwise, transfer must be segmented
		SCDRP$L_BCNT(R5)	; into pieces of MAXBCNT length
10$:	BSBW	READ_WRITE		; Send a SCSI read or write command
	MOVL	SCDRP$L_IRP(R5),R2	; Restore IRP address
	BLBCW	R0,IO_RW_ERR		; Branch on error
	
IO_RW_ACCUM:

	SUBL3	SCDRP$L_PAD_BCNT(R5),-	; Get actual number of bytes transferred
		SCDRP$L_TRANS_CNT(R5),R0; less any possible padding
	CMPL	R0,SCDRP$L_BCNT(R5)	; Compare with requested transfer count
	BNEQ	IO_RW_MISMATCH		; Branch if mismatch occurred
	ADDL	R0,SCDRP$L_ABCNT(R5)	; Accumulate this piece of the transfer
	CMPL	SCDRP$L_ABCNT(R5),-	; Is transfer complete?
		IRP$L_BCNT(R2)
	BLSSU	IO_RW_SEGMENT_DONE	; Branch if not, accumulate this segment
					; of the transfer

	BBSW	#IO$V_DATACHECK,-	; Branch if the datacheck modifier
		SCDRP$W_FUNC(R5),-	; has been specified. Perform a 
		IO_DATACHECK		; datacheck operation.

	MOVW	#SS$_NORMAL,R0		; Set success status	

IO_RW_EXIT:

	INSV	SCDRP$L_ABCNT(R5),-	; Load low-order number of bytes 
		#16,#16,R0		; transferred into R0
	MOVZWL	SCDRP$L_ABCNT+2(R5),R1	; Load high-order number of bytes
					; transferred into R1
	BRW	COMPLETE_IO		; Complete the QIO

; Here we have completed one piece of a segmented transfer. Update the SVAPTE
; and MEDIA fields in the SCDRP and go perform the next segment of the transfer.

IO_RW_SEGMENT_DONE:

	ASHL	#-7,R0,R0		; Convert byte count to longword index
	ADDL	R0,SCDRP$L_SVAPTE(R5)	; Update SVAPTE field in SCDRP
	ASHL	#-2,R0,R0		; Convert to block count
	ADDL	R0,SCDRP$L_MEDIA(R5)	; Update logical block number in SCDRP
	BRW	IO_RW_LOOP		; Go perform next segment of transfer

; Here the transfer count returned by the port doesn't match the requested
; byte count. If it's greater, than truncate the transfer to what we
; requested. Otherwise, accumulate the piece of the transfer that just 
; completed and continue with the transfer.

IO_RW_MISMATCH:
	BLSS	10$			; Branch if the transfer count is less
					; than the requested count, accumulate
					; this piece of the transfer
	ADDL3	SCDRP$L_PAD_BCNT(R5),-	; Truncate the transfer such that the
		SCDRP$L_BCNT(R5),-	; the transfer count we got is what we
		SCDRP$L_TRANS_CNT(R5)	; expected
	BRB	IO_RW_ACCUM		; Accumulate this piece of the transfer

10$:	BICW	#^X1FF,R0		; Round transfer down to block multiple
	BEQL	IO_RW_SHORT_XFER	; Branch if no bytes to accumulate
	MOVL	R0,SCDRP$L_BCNT(R5)	; Adjust byte count and transfer count
	ADDL3	SCDRP$L_PAD_BCNT(R5),-	; to accumulate this segment of the
		R0,SCDRP$L_TRANS_CNT(R5); transfer.
	BRB	IO_RW_ACCUM		; Accumulate this segment of the transfer

IO_RW_SHORT_XFER:
	
	MOVL	#SS$_OPINCOMPL,R0	; Set bad status
	BRB	IO_RW_EXIT		; Complete I/O with error status

; Here an error occurred in performing the read or write. If it's due to a 
; recoverable or non-recoverable (MEDIA) error, then accumulate the piece of 
; the transfer that completed successfully and retry the original operation on
; failing block several times.
;
; For read-only devices, since we can't perform a reassign, retry the read
; only for non-recoverable errors.
;
; For devices (such as floppies) that don't support reassign, retry the read
; only for non-recoverable errors.
;
; To synchronize use of various UCB feilds, we set the BBR_IN_PROG bit
; in UCB$L_DK_FLAGS upon entry to IO_RW_ERR and clear it before returning.
; Any thread entering here that finds BBR_IN_PROG set will FORK_WAIT and
; try again later. This insures only one thread is doing bbr at a time.

BBR_IO_RW_EXIT:
	BICL	#UCB$M_BBR_IN_PROG,-	; Clear BBR interlock bit
		UCB$L_DK_FLAGS(R3)
	BRW	IO_RW_EXIT		; Return to mainline code

BBR_IO_RW_ACCUM:
	BICL	#UCB$M_BBR_IN_PROG,-	; Clear BBR interlock bit
		UCB$L_DK_FLAGS(R3)
	BRW	IO_RW_ACCUM		; Return to mainline code

BBR_IO_RW_SHORT_XFER:			; Clear BBR interlock bit
	BICL	#UCB$M_BBR_IN_PROG,-	; Clear BBR interlock bit
		UCB$L_DK_FLAGS(R3)
	BRW	IO_RW_SHORT_XFER	; Return to mainline code


BBR_WAIT:
	MOVL	R0,SCDRP$L_TAG(R5)	; Preserve original status
					; TAG is only used while in port, so
					; it's a safe place across FORK_WAIT
	FORK_WAIT			; R3-R5 preserved:
					;  R3 = UCB
					;  R4 = SPDT
					;  R5 = SCDRP
	MOVL	SCDRP$L_TAG(R5),R0	; Restore original status
	MOVL	SCDRP$L_IRP(R5),R2	; Restore IRP

	; Fall through to check BBR_IN_PROG
	
IO_RW_ERR:

	BBSS	#UCB$V_BBR_IN_PROG,-	; Some thread already doing BBR?
		UCB$L_DK_FLAGS(R3),-
		BBR_WAIT
	CMPL	R0,#SS$_PARITY		; Media (non-recoverable) error?
	BEQL	10$			; Branch if so
	CMPL	R0,#SS$_RECOVERR	; Recoverable error?
	BNEQ	BBR_IO_RW_EXIT		; Branch if not, return error to caller

	BBS	#DEV$V_SWL,-		; Branch if device is software
		UCB$L_DEVCHAR(R3),5$	;   write locked
	BBC	#UCB$V_NOREASSIGN,-	; Branch if device supports
		UCB$L_DK_FLAGS(R3),10$	;   reassign_block command
5$:	BRW	BBR_IO_RW_ACCUM		; Recoverable error can't be reassigned,
					;   treat as success
; The ADDNL_INFO field in the SCDRP contains a copy of the additional 
; information field from the extended sense data. Make sure that it is valid
; and that the failing LBN it contains falls within the range of the original
; read or write command. If not, then assume the failing block is the first
; one in the range. If the failing LBN is valid, then accumulate all blocks 
; of the transfer up to the failing LBN and update the MEDIA, ABCNT, and 
; SVAPTE fields in the SCDRP. 

10$:	MOVL	SCDRP$L_ADDNL_INFO(R5),R0 ; Get the failing LBN
	BLSS	IO_RW_RETRY		; Branch if LBN is invalid
	CMPL	R0,SCDRP$L_MEDIA(R5)	; Is failing LBN below range?
	BLSS	IO_RW_RETRY		; Branch if so
 	ASHL	#-9,SCDRP$L_BCNT(R5),R1	; Get number of blocks requested
	ADDL	SCDRP$L_MEDIA(R5),R1	; Get highest LBN requested
	CMPL	R0,R1			; Is failing LBN above range?
	BGEQ	IO_RW_RETRY		; Branch if so
	SUBL3	SCDRP$L_MEDIA(R5),R0,R1	; Calculate number of successfully
					; transferred blocks
	BLEQ	IO_RW_RETRY		; Branch if none
	ASHL	#-9,SCDRP$L_TRANS_CNT(R5),-; Convert to block count
		 R0			
	CMPL	R0,R1			; Were all blocks up to bad LBN transferred?
	BLSS	20$			; Branch if not
	MOVL	R1,R0			; Accumulate all blocks up to the bad one
20$:	ADDL	R0,SCDRP$L_MEDIA(R5)	; Update LBN
      	ASHL	#2,R0,R0		; Convert block count to longword index
	ADDL	R0,SCDRP$L_SVAPTE(R5)	; Update SVAPTE
	ASHL	#7,R0,R0		; Convert to byte count
	ADDL	R0,SCDRP$L_ABCNT(R5)	; Update accumulated byte count

; At this point all blocks of the transfer up to failing LBN has been 
; accumulated. Retry the transfer of the failing LBN several times to see if 
; the block can be read or written successfully. If so, then accumulate the 
; data for this block and proceed with the transfer. Otherwise, attempt to 
; reassign the block if appropriate.

IO_RW_RETRY:

	MOVB	#RW_RETRY_CNT,-		; Initialize read/write retry count
		UCB$B_RW_RETRY(R3)	;
	SUBL3	SCDRP$L_ABCNT(R5),-	; Get length of remaining transfer
		IRP$L_BCNT(R2),-	;
		SCDRP$L_BCNT(R5)	;
	CMPL	SCDRP$L_BCNT(R5),#512	; More than one block?
	BLEQ	10$			; Branch if not
	MOVL	#512,SCDRP$L_BCNT(R5)	; Limit transfer to one block
10$:	DISABLE_ERRLOG			; Temporarily disable errorlogging
	BSBW	READ_WRITE		; Perform the read or write
	REENABLE_ERRLOG			; Reenable errorlogging
	MOVL	SCDRP$L_IRP(R5),R2	; Get IRP address
	BLBSW	R0,BBR_IO_RW_ACCUM	; Branch if success, pick up original
					; transfer where we left off
	CMPL	R0,#SS$_PARITY		; Non-recoverable error?
	BEQL	20$			; Branch if so
	CMPL	R0,#SS$_RECOVERR	; Recoverable error?
	BNEQW	BBR_IO_RW_EXIT		; Branch if not, complete QIO with error

; The following code segment catches the case in which a read to a write-locked
; disk fails initially with a non-recoverable error, but returns a recoverable
; error on a read retry. In this case, we accept the data rather than risk
; loosing it with another read retry. The same logic holds true for disks that
; don't support reassign.

	BBS	#DEV$V_SWL,-		; Branch if device is software
		UCB$L_DEVCHAR(R3),15$	;   write locked
	BBC	#UCB$V_NOREASSIGN,-	; Branch if device supports
		UCB$L_DK_FLAGS(R3),20$	;   reassign_block command
15$:	BRW	BBR_IO_RW_ACCUM		; Recoverable error can't be reassigned,
					; accumulate data

20$:	DECB	UCB$B_RW_RETRY(R3)	; Decrement read/write retry count
	BGTR	10$			; Branch if count not exhausted

; Here we've been unsuccessful several times in performing the read or 
; write to the failing LBN. If it's appropriate to reassign the block, do so.
; Note that since there is no forced error flag in SCSI, we can't reassign a 
; block with a non-recoverable read error. 

IO_RW_REASSIGN:

; If we're here with a disk that doesn't support reassign, then all the 
; retries above must have faild due to non-recoverable errors. Therfore, 
; complete the QIO now with bad status.

	BBSW	#UCB$V_NOREASSIGN,-	; Branch if device doesn't support
		UCB$L_DK_FLAGS(R3),-	;   reassign_block command
		BBR_IO_RW_EXIT

	BBC	#IRP$V_FUNC,-		; Branch if this is a write, OK to
		SCDRP$W_STS(R5),10$	; try reassign
	CMPL	R0,#SS$_PARITY		; Non-recoverable error?
	BEQLW	BBR_IO_RW_EXIT		; Branch if so, NOT OK try reassign.

10$:	MOVB	#REASSIGN_RETRY_CNT,-	; Initialize the reassign retry count
		UCB$B_REASSIGN_RETRY(R3);

IO_RW_REASSIGN_LOOP:

20$:	MOVL	R5,UCB$L_SCDRP_SAV1(R3)	; Save the original SCDRP address
	PUSHL	SCDRP$L_MEDIA(R5)	; Save LBN of block to reassign
	BSBW	ALLOC_SCDRP		; Allocate a new SCDRP for the reassign
	POPL	SCDRP$L_MEDIA(R5)	; Copy LBN to reassign SCDRP
	BSBW	REASSIGN_BLOCK		; Go attempt to reassign the block
	BSBW	DEALLOC_SCDRP		; Deallocate the reassign SCDRP
	MOVL	UCB$L_SCDRP_SAV1(R3),R5	; Restore the original SCDRP address
	MOVL	R5,UCB$L_SCDRP(R3)	; Save it in the UCB
	BLBS	R0,IO_RW_REWRITE	; Branch if the reassign succeeded
	DECB	UCB$B_REASSIGN_RETRY(R3); Decrement the reassign retry count
	BGTR	20$			; Branch if count not exhausted

; Here we've attempted a reassign and failed. Write the original data to 
; whereever the block is currently assigned as the failed reassign operations
; may have corrupted the data.

;	BRB	IO_RW_REWRITE		; Rewrite the block	    

; Here the reassign has completed successfully. Write the original data
; to the reassigned block. The assumption is that the reassign command
; doesn't necessarily move the data from the old to the new location.
; If the original command was a read and the last attempt to read the
; block returned a short transfer count, then don't rewrite the block
; because the data in the user's buffer is invalid. Instead, rely on the 
; reassign command to move the data.

IO_RW_REWRITE:

	MOVB	#REWRITE_RETRY_CNT,-	; Initialize rewrite retry count
		UCB$B_REWRITE_RETRY(R3)	;
	BBC	#IRP$V_FUNC,-		; Branch if this is a write, OK to
		SCDRP$W_STS(R5),10$	; rewrite block
	CMPL	SCDRP$L_TRANS_CNT(R5),-	; Valid transfer count?
		#512			;
	BLSSW	BBR_IO_RW_SHORT_XFER	; Branch if not, can't rewrite block

10$:	BICW	#IRP$M_FUNC,-		; Clear the FUNC bit to indicate this
		SCDRP$W_STS(R5)		; is a write function
	DISABLE_ERRLOG			; Temporarily disable errorlogging
	BSBW	READ_WRITE		; Rewrite the original block
	REENABLE_ERRLOG			; Reenable errorlogging
	MOVL	SCDRP$L_IRP(R5),R2	; Restore the IRP address
	MOVW	IRP$W_STS(R2),-		; Restore the setting of the func
		SCDRP$W_STS(R5)		; bit from the IRP
	BLBSW	R0,BBR_IO_RW_ACCUM	; Branch if success, pick up original
					; transfer where we left off
	DECB	UCB$B_REWRITE_RETRY(R3)	; Decrement rewrite retry count
	BGTR	10$			; Branch if count not exhausted
	    
; Here we've failed to write the data to the reassigned block. Attempt to 
; reassign the block again. If the reassign retry count is exhausted. Then
; we've done all we can so return the status of the last failing write.

	DECB	UCB$B_REASSIGN_RETRY(R3); Decrement reassign retry count
	BGTRW	IO_RW_REASSIGN_LOOP	; Branch if count not exhausted
	CMPL	R0,#SS$_RECOVERR	; Was last write recoverable?
	BEQLW	BBR_IO_RW_ACCUM		; Branch if so, treat as successful and
					; proceed with transfer
	BRW	BBR_IO_RW_EXIT		; Otherwise, complete QIO with error status

; Workaround a problem in RZ55's with microde versions less than 900.
; RZ55 with microcode version less than 900 will return one additional
; block of data when the drive successfully recovers a block by 
; simply rereading it. This block contains ramdom data and the host assumes
; it contains valid data, the users I/O contains erroneous data.
;
; Called from TRANS_SENSE_KEY, which has R5 = SCDRP of REQUEST_SENSE command
; and SCDRP$L_SCDRP_SAV2(R5) as SCDRP of original command
;
RZ55_WORKAROUND:
                                      
; Check the version number of microcode of the RZ55. If the first digit is 
; non-zero or the second digit is greater than the ascii value of '8' then 
; this microcode has been fixed. Otherwise the class driver must workaround
; the hardware problem by decrementing TRANS_CNT.

	CMPB	#^A'0',-		; Check first digit.
		UCB$L_HW_REV(R3)	;
	BNEQ	110$			; If non-zero, no fixup needed.
  	CMPB	#^A'9',-	      	; Check second digit. If >=
		UCB$L_HW_REV+1(R3)	; '9' then no fixup is needed.
	BLEQU	110$			; Fixup needed.                        
	PUSHL	R5			; Save active SCDRP
	MOVL	SCDRP$L_SCDRP_SAV2(R5),R5 ; Restore original SCDRP address
	SUBL2	#DTE_EXTRA_BYTES,-	; Subtract off the extra block, to
		 SCDRP$L_TRANS_CNT(R5)	; determine exactly which block to
 	    	 			; resume transfer from.    
	POPL	R5			; Restore active SCDRP
110$:	RSB				; Read the rest of the I/O request.


	.SBTTL	IO_WRITECHECK	- Compare user's data with that on disk
;+
; IO_WRITECHECK
;
; This routine compares the data in the user's buffer to that on the disk.
; No write is actually performed, just a read (to a buffer allocated from
; pool) followed by the compare operation.
;
; INPUTS:
;
;	R2	- IPR address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_WRITECHECK:

;	BRB	IO_DATACHECK		; Fall through to datacheck routine

                    


	.SBTTL	IO_DATACHECK	- Check data that was just read or written
;+
; IO_DATACHECK
;
; This routine is invoked if a QIO read or write specifies the DATACHECK 
; qualifier. It reads the set of blocks that have just been read or written
; and compares the original and new data. A temporary buffer is allocated
; from non-paged pool to store the data being read, and whose contents is
; compared with that of the user's buffer.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_DATACHECK:

	BISW	#IRP$M_FUNC,-		; Set the FUNC but to indicate this
		SCDRP$W_STS(R5)		; is a read function

	MOVL	SCDRP$L_IRP(R5),R2	; Get IRP address
	CLRL	SCDRP$L_ABCNT(R5)	; Initialize accumulated byte count
	MOVW	IRP$W_FUNC(R2),-	; Copy function code and modifiers,
		SCDRP$W_FUNC(R5)	; MEDIA, SVAPTE, and BOFF fields
	MOVL	IRP$L_MEDIA(R2),-	; from the IRP to the SCDRP
		SCDRP$L_MEDIA(R5)	; 
	MOVL	IRP$L_SVAPTE(R2),-	;
		SCDRP$L_SVAPTE(R5)	;
	MOVW	IRP$W_BOFF(R2),-	;
		SCDRP$W_BOFF(R5)	;
	MOVL	IRP$L_BCNT(R2),R1	; Get transfer length
	CMPL	R1,UCB$L_MAXBCNT(R3)	; Greater than max?
	BLEQ	10$			; Branch if not
	MOVL	UCB$L_MAXBCNT(R3),R1	; Use MAXBCNT instead
10$:	BSBW	ALLOC_POOL		; Allocate a datacheck buffer
	MOVL	R2,-			; Save datacheck buffer address
		SCDRP$L_DATACHECK(R5)	;
	MOVL	R2,SCDRP$L_SVA_USER(R5)	; Datacheck buffer is also "user" buffer
	MOVL	SCDRP$L_IRP(R5),R2	; Restore IRP address

IO_DC_LOOP:

	BISB	#SCDRP$M_S0BUF,-	; Indicate that this is an S0 "user"
		SCDRP$L_SCSI_FLAGS(R5)	; buffer
	SUBL3	SCDRP$L_ABCNT(R5),-	; Attempt to transfer all remaining 
		IRP$L_BCNT(R2),-	; bytes in user's buffer
		SCDRP$L_BCNT(R5)	;
	CMPL	SCDRP$L_BCNT(R5),-	; Transfer length greater than maximum
		UCB$L_MAXBCNT(R3)	; supported?
	BLEQU	10$			; Branch if not
	MOVL	UCB$L_MAXBCNT(R3),-	; Otherwise, transfer must be segmented
		SCDRP$L_BCNT(R5)	; into pieces of MAXBCNT length
10$:	BSBW	READ_WRITE		; Send a SCSI read or write command
	BLBS	R0,IO_DC_ACCUM		; Branch on success
	CMPL	R0,#SS$_RECOVERR	; Recoverable error?
	BNEQ	IO_DC_EXIT		; Branch if not, return with error status

IO_DC_ACCUM:

	MOVL	SCDRP$L_IRP(R5),R2	; Restore IRP address
	SUBL3	SCDRP$L_PAD_BCNT(R5),-	; Get actual number of bytes transferred
		SCDRP$L_TRANS_CNT(R5),R0; less any possible padding
	CMPL	R0,SCDRP$L_BCNT(R5)	; Compare with requested transfer count
	BNEQ	IO_DC_MISMATCH		; Branch if mismatch occurred
	BSBW	DATACHECK_CMP		; Compare the original and new data
	BLBC	R0,IO_DC_EXIT		; Branch if a mismatch occurred
	SUBL3	SCDRP$L_PAD_BCNT(R5),-	; Get actual number of bytes transferred
		SCDRP$L_TRANS_CNT(R5),R0; less any possible padding
	ADDL	R0,SCDRP$L_ABCNT(R5)	; Accumulate this piece of the transfer
	CMPL	SCDRP$L_ABCNT(R5),-	; Is transfer complete?
		IRP$L_BCNT(R2)
	BLSSU	IO_DC_SEGMENT_DONE	; Branch if not, accumulate this segment
					; of the transfer

	INSV	SCDRP$L_ABCNT(R5),-	; Load low-order number of bytes 
		#16,#16,R0		; transferred into R0
	MOVZWL	SCDRP$L_ABCNT+2(R5),R1	; Load high-order number of bytes
					; transferred into R1
	MOVW	#SS$_NORMAL,R0		; Set success status	

IO_DC_EXIT:

	PUSHL	R0			; Save R0
	MOVL	SCDRP$L_DATACHECK(R5),R0; Get datacheck buffer address
	BSBW	DEALLOC_POOL		; Deallocate the datacheck buffer
	POPL	R0			; Restore R0
	BRW	COMPLETE_IO		; Complete the QIO

; Here we have completed one piece of a segmented transfer. Update the SVAPTE
; and MEDIA fields in the SCDRP and go perform the next segment of the transfer.

IO_DC_SEGMENT_DONE:

	ASHL	#-7,R0,R0		; Convert byte count to longword index
	ADDL	R0,SCDRP$L_SVAPTE(R5)	; Update SVAPTE field in SCDRP
	ASHL	#-2,R0,R0		; Convert to block count
	ADDL	R0,SCDRP$L_MEDIA(R5)	; Update logical block number in SCDRP
	BRW	IO_DC_LOOP		; Go perform next segment of transfer

; Here the transfer count returned by the port doesn't match the requested
; byte count. If it's greater, than truncate the transfer to what we
; requested. Otherwise, accumulate the piece of the transfer that just 
; completed and continue with the transfer.

IO_DC_MISMATCH:

	BLSS	10$			; Branch if the transfer count is less
					; than the requested count, accumulate
					; this piece of the transfer
	ADDL3	SCDRP$L_PAD_BCNT(R5),-	; Truncate the transfer such that the
		SCDRP$L_BCNT(R5),-	; the transfer count we got is what we
		SCDRP$L_TRANS_CNT(R5)	; expected
	BRB	IO_DC_ACCUM		; Accumulate this piece of the transfer

10$:	BICW	#^X1FF,R0		; Round transfer down to block multiple
	BEQL	20$			; Branch if no bytes to accumulate
	MOVL	R0,SCDRP$L_BCNT(R5)	; Adjust byte count and transfer count
	ADDL3	SCDRP$L_PAD_BCNT(R5),-	; to accumulate this segment of the
		R0,SCDRP$L_TRANS_CNT(R5); transfer.
	BRW	IO_DC_ACCUM		; Accumulate this segment of the transfer

20$:	MOVL	#SS$_OPINCOMPL,R0	; Set bad status
	BRB	IO_DC_EXIT		; Complete I/O with error status


	.SBTTL	IO_UNLOAD	- Make drive available (and spin it down)
	.SBTTL	IO_AVAILABLE	- Make drive available (but don't spin it down)
;+
; IO_UNLOAD
; IO_AVAILABLE
;
; This routine makes the drive available by clearing the VALID bit in the 
; UCB. For the IO_UNLOAD entry point, the device is spun down if it has
; removable media.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_UNLOAD:

	BBC	#UCB$V_REMOVABLE,-	; Branch if this is a device with
		UCB$L_DK_FLAGS(R3),-	; non-removable media
		IO_AVAILABLE		;
	BSBW	STOP_UNIT		; Spin down the unit

IO_AVAILABLE:

	BICL	#<UCB$M_VALID>,-	; Set volume valid
		UCB$L_STS(R3)		; 
	MOVZWL	#SS$_NORMAL,R0		; Set success status
	BRW	COMPLETE_IO		; Complete the I/O


	.SBTTL	IO_FORMAT	- format a floppy diskette
;+
; IO_FORMAT
;
; This routine formats a floppy diskette.
;
; An operator may request the format with the DCL command
;	INIT/DENSITY=DOUBLE <device_name> <volume_label>
; where the <density_value> may be
;	DOUBLE (or HD) for RX33s drives,
;	DOUBLE (or HD), [or SINGLE (or DD) if DD_BYPASS set] for RX23s drives,
;	DD (or SINGLE), HD (or DOUBLE), or ED for RX26 drives
;
; INPUTS:
;
;	R2	- IRP address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;
;-

IO_FORMAT:

; Format command performs an implicit AVAILABLE.
; Update the UCB to reflect an AVAILABLE command.

	ASSUME	UCB$V_VALID GE 8
	BICB	#<UCB$M_VALID @ -8>, -	; Clear software volume valid.
		UCB$W_STS+1(R3)
	BBCC	#UCB$V_LCL_VALID, -	; First FORMAT or AVAILABLE?
		UCB$L_STS(R3), 10$	; If CC not first, "Can't happen"
	DECB	UCB$B_ONLCNT(R3)	; Decrement online count.

10$:	BBS	#UCB$V_FORMAT,-		; Is FORMAT supported by the drive?
		UCB$L_DK_FLAGS(R3),100$
	MOVL	#SS$_FORMAT,R0		; No, return an error
	BRW	1000$

100$:	BBC	#UCB$V_HWL,-		; Can we write (to format) the diskette?
		UCB$L_DK_FLAGS(R3),120$
	MOVL	#SS$_WRITLCK,R0		; No, return an error
	BRW	1000$
;

; Determine that the correct /DENSITY qualifier was used on the INIT command.
;
; We support the following commands:
;
;	INIT/DENSITY=SINGLE shows up here as IRP$L_MEDIA=1
;	INIT/DENSITY=DD shows up here as IRP$L_MEDIA=1
;	INIT/DENSITY=DOUBLE shows up here as IRP$L_MEDIA=2
;	INIT/DENSITY=HD shows up here as IRP$L_MEDIA=2
;	INIT/DENSITY=ED shows up here as IRP$L_MEDIA=3
;
; The allowed formats for the various drives are:
;
;	RX23s allows IRP$L_MEDIA=2 always, and
;	  IRP$L_MEDIA=1 only when UCB$L_DK_FLAGS<DD_BYPASS> is set;
;	RX33s allows only IRP$L_MEDIA=2;
;	RX26 allows IRP$L_MEDIA= 1, 2, or 3, but the appropriate diskette
;	  must be in the drive.
;
120$:	CMPB	#DT$_RX26,-		; Check RX26-specific format params
		UCB$B_DEVTYPE(R3)
	BNEQ	170$			; Not RX26, go check the others
	CMPL	#1,IRP$L_MEDIA(R2)	; Lowest value allowed for RX26
	BGTRU	180$			; Too low, go report error
	CMPL	#3,IRP$L_MEDIA(R2)	; Highest value allowed for RX26
	BLSSU	180$			; Too high, go report error
	BRB	200$			; Value's OK
;
170$:	CMPL	#2,IRP$L_MEDIA(R2)	; Verify /density=double
	BEQL	200$			; Yep, media's = 2

					; Allow formatting with /density=single
					; only for RX23 with a bypass override
		    			; because 9-sector formats are
					; unreliable

	BBC	#UCB$V_DD_BYPASS,-	; Check for the special override bit
		UCB$L_DK_FLAGS(R3),-	; to see whether we can handle
		180$			; /density=single
	CMPB	#DT$_RX23S,-		; Only worry about RX23S drive
		UCB$B_DEVTYPE(R3)
	BNEQ	180$
	CMPL	#1,IRP$L_MEDIA(R2)	; Verify /density=single
	BEQL	200$			; Yep, media's = 1
180$:

	MOVL	#SS$_BADPARAM,R0	; Nope, report error
	BRW	1000$
;
; Adjust format-sensitive parameters in the UCB here
;
200$:	BSBW	MODE_SENSE		; Get current parameters
	BLBS	R0,400$			; Check whether they're OK
	CMPL	#SS$_FORMAT,R0		; If not, check for unformatted media
	BNEQ	1000$			; and let that through (we're
					; formatting here)

; Check for appropriate diskette type for the density requested
;
400$:	MOVL	SCDRP$L_IRP(R5),R2	; Restore IRP address
	CMPB	#DT$_RX26,-		; Only RX26 reports diskette type
		UCB$B_DEVTYPE(R3)
	BNEQ	500$			; Not RX26, skip this check
	CMPZV	#UCB$V_FLOPPY_MEDIA,-
		#UCB$S_FLOPPY_MEDIA,-
		UCB$L_DK_FLAGS(R3),-
		IRP$L_MEDIA(R2)		; Check that the requested format
					;   is compatible with the diskette
	BEQL	500$			; OK, let it through
	MOVL	#SS$_FORMAT,R0		; No, return an error
	BRB	1000$

500$:	BSBW	MODE_SENSE_CHANGABLE	; get changable parameters
	BLBC	R0,1000$

					; Set mode parameters here
	BSBW	MODE_SELECT_FORMAT_FLOPPY
	BLBC	R0,1000$

	BSBW	FORMAT			; Issue format command
	BLBC	R0,1000$

	MOVZWL	#SS$_NORMAL,R0		; Set success status

1000$:	BRW	COMPLETE_IO		; Complete the I/O


	.SBTTL	IO_DSE		- Data security erase function
;+
; IO_DSE
;
; This routine erases a set of logical blocks by writing zeros to the
; blocks.
;
; INPUTS:
;
;	R2	- IPR address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-
	MAX_BUF_SIZE = < 65535 & ^C<511> >
	ASSUME	< MAX_BUF_SIZE & 511 > eq 0

IO_DSE:

	BICW	#IRP$M_FUNC,-		; Clear the FUNC but to indicate this
		SCDRP$W_STS(R5)		; is a write function
	MOVL	IRP$L_MEDIA(R2),-	; Copy starting block number from
		SCDRP$L_MEDIA(R5)	; IRP to SCDRP
	ADDL3	#511,IRP$L_BCNT(R2),R1	; Round up byte count to a block
	BICL	#511,R1			; boundary

	MOVL	R1,IRP$L_BCNT(R2)	; save modified byte count

;
;	allocate zeroed buffer to write from
;
	CMPL	R1,#MAX_BUF_SIZE	; is the buffer too big?
	BLEQ	10$			; LEQ means it's OK
	MOVL	#MAX_BUF_SIZE ,R1	; if it's too big use max buffer size
10$:
	BSBW	ALLOC_POOL		; Allocate a DSE buffer (note that 
					; ALLOC_POOL zeros the buffer)
	MOVL	R2,SCDRP$L_SVA_USER(R5)	; Save address of the DSE buffer

	CLRL	SCDRP$L_ABCNT(R5)	; set accumulated count to 0
;
;	This is the main write loop 
;
20$:
	MOVL	SCDRP$L_IRP(R5),R2	; restore the IRP
;
;	get bytes left to write in R0   (IRP$L_BCNT(R2)-SCDRP$L_ABCNT(R5))
;
	MOVL	IRP$L_BCNT(R2),R0	; get QIO byte count

	SUBL	SCDRP$L_ABCNT(R5),R0	; get bytes left to write out
	BLEQU	32$
;
;	Check to see if we;ve gotten to the end of the disk
;
	CMPL	SCDRP$L_MEDIA(R5),-
		UCB$L_MAXBLOCK(R3)	; are we past the end of the DISK?
	BGEQ	30$			; GTR means yes don't write any more

;
;	see if bytes left to write is too big 	
;
	CMPL	R0,#MAX_BUF_SIZE	; is this too many to do now?
	BLEQ	22$			; LEQ means no write them out
	

	MOVL	#MAX_BUF_SIZE,R0	; else use max size

22$:
;
;	make sure we won't write past end of disk
;
	ASHL	#-9,R0,R1  		; convert to blocks		
	ADDL2	SCDRP$L_MEDIA(R5),R1	; get block after last write
	CMPL	R1,UCB$L_MAXBLOCK(R3)	; will we go past the end?
	BLEQ	25$			; leq means no everything is OK
	SUBL3	SCDRP$L_MEDIA(R5),-
		UCB$L_MAXBLOCK(R3),R1	; less starting block for # blocks left
	ASHL	#9,R1,R0		; convert BLOCKS to bytes
	
25$:
	MOVL	R0,SCDRP$L_BCNT(R5)	; Save byte count for this write
	BISB	#SCDRP$M_S0BUF,-	; Indicate that this is an S0 "user"
		SCDRP$L_SCSI_FLAGS(R5)	; buffer
	
	BSBW	READ_WRITE		; write out the data
	BLBC	R0,34$			; LBC means some error
	MOVL	SCDRP$L_BCNT(R5),R0	; get the # bytes that we wrote
	ADDL2	R0,SCDRP$L_ABCNT(R5)	; update the accumulated byte count 
					; with bytes we just wrote
	ASHL	#-9,R0,R0		; convert to blocks
	ADDL2	R0,SCDRP$L_MEDIA(R5)	; update next block to erase
	BRW	20$

;
;	here if we get to the end of the disk
;
30$:
	MOVL	#SS$_IVADDR,R0		
	BRW	36$

;
;	here if we finish without incident
;
32$:
	MOVL	#SS$_NORMAL,R0
	BRW	36$
	
;
;	here if we get an error from read_write
;
34$:
	BRW	36$			; return the error as is
;
;	here to deallocate pool - retuens status in R0
;
36$:
	PUSHL	R0			; Save status
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of S0 buffer
	BSBW	DEALLOC_POOL		; Deallocate the S0 buffer
	POPL	R0			; Restore R0
;
;	here after buffer is deallocated or to return errors that happen before
;	the buffer is allocated or if the allocation fails.
;
37$:
	MOVZWL	SCDRP$L_ABCNT+2(R5),R1	; Set high-order word of transfer cnt
	INSV	SCDRP$L_ABCNT(R5),-	; Copy low-order word of transfer cnt
		#16,#16,R0		;
	BRW     COMPLETE_IO       	; Complete the QIO request


	.SBTTL	IO_DIAGNOSE	- Special pass-through function
;+
; IO_DIAGNOSE
;
; INPUTS:
;
;	R2	- IPR address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_DIAGNOSE:

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

	MOVL	SCDRP$L_MEDIA(R5),R1	; Get address of SCSI command in pool
	MOVL	(R1)+,R1		; Get length of SCSI command
	ADDL	#8,R1			; Account for overhead
	SPI$ALLOCATE_COMMAND_BUFFER	; Allocate a command buffer
	MOVL	R2,SCDRP$L_CMD_BUF(R5)	; Save address of command buffer
	CLRL	(R2)+			; Reserve a longword for status
	MOVB	#^XFF,-1(R2)		; Initialize status field
	MOVAL	-1(R2),-		; Address to save status byte
		SCDRP$L_STS_PTR(R5)	;
	MOVL	R2,SCDRP$L_CMD_PTR(R5)	; Address of SCSI command in cmd buffer
	MOVL	SCDRP$L_MEDIA(R5),R0	; Get SCSI command in pool again
	MOVL	(R0),(R2)+		; Copy SCSI command length
	PUSHR	#^M<R0,R2,R3,R4,R5>	; Save regs
	MOVC3	(R0),4(R0),(R2)		; Copy SCSI command to command buffer
	POPR	#^M<R0,R2,R3,R4,R5>	; Restore regs
	MOVL	-(R0),R1		; Get length of command buffer in pool
	JSB	G^EXE$DEANONPGDSIZ	; Deallocate the buffer
	TSTL	SCDRP$L_BCNT(R5)	; Any user data buffer?
	BEQL	10$			; Branch if not
	SPI$MAP_BUFFER			; Map the user's data buffer
10$: 
	MOVZBL	#QCHAR$K_NOT_QUEUED,R0 	; Send GK stuff non-queued
	BBC	#UCB$V_GK_CHK_COND,-	; Did last I/O get check cond?
		UCB$L_DK_FLAGS(R3),15$	; Br if not
	MOVZBL	#QCHAR$K_ERROR_RECOVERY,R0 ; Else send this as ERROR_RECOVERY
15$:	SPI$QUEUE_COMMAND QCHAR=R0	; Queue the SCSI command
	BBCC	#UCB$V_GK_CHK_COND,-	; If prev I/O got CHECK_COND
		UCB$L_DK_FLAGS(R3),17$	; Clear it and unfreeze queue
	CLRL	UCB$L_GK_PID(R3)	; Allow other processes to use DIAGNOSE
	SPI$RELEASE_QUEUE		; Resume queue processing now
17$:	CMPB	#2,@SCDRP$L_STS_PTR(R5)	; Did we just get CHK COND?
	BNEQ	19$			; Br if not
	BISL	#UCB$M_GK_CHK_COND,-	; Flag we got check cond
		UCB$L_DK_FLAGS(R3)
	MOVL	SCDRP$L_IRP(R5),R2	; Get IRP
	MOVL	IRP$L_PID(R2),-		; Save this processes PID
		UCB$L_GK_PID(R3)	; For later checking
19$:	PUSHL	R0			; Save returned port status
	TSTL	SCDRP$L_BCNT(R5)	; User buffer mapped?
	BEQL	20$			; Branch if not
	SPI$UNMAP_BUFFER		; Unmap the user's data buffer
20$:	MOVL	SCDRP$L_CMD_BUF(R5),R0	; Get the command buffer address
	PUSHL	(R0)			; Save the SCSI status byte
	SPI$DEALLOCATE_COMMAND_BUFFER	; Deallocate the command buffer
	BSBW	RESTORE_CONN_CHAR	; Restore Connection Characteristics
	MOVL	R0,R2			; Save return status from subroutine 
	POPL	R1			; Restore the SCSI status byte
	POPL	R0			; Restore the port status
	BLBC	R0,30$			; If bad status from sending command
	MOVL	R2,R0			; then return that status, else
					; return status from restore_connection
30$:
	INSV	SCDRP$L_TRANS_CNT(R5),-	; Copy the transfer count to the 
		#16,#16,R0		; high-order word of R0
	BICL	#UCB$M_GK_ACTIVE,-	; Clear lock out
		UCB$L_DK_FLAGS(R3)
	BRW	COMPLETE_IO		; Complete the QIO


	.SBTTL	IO_AUDIO	- SCSI audio STARTIO function
;+
; IO_AUDIO
;
; This routine is executed in the STARTIO context. It converts an AUCB into a SCSI AUDIO
; Command Descriptor Block (CDB). This CDB is then sent to and executed by the target
; device. If there is data to be returned to a user buffer the data is copied within
; this routine. The AUCB Operating System status and SCSI status fields are updated.
;
; After the I/O has been completed by the target, the AUDIO_EXIT code is called to unlock
; any users buffers other than the AUCB. The AUCB will be unlocked during I/O post-
; processing. The System PTE's allocated to double map any of the buffer will be 
; deallocated by this routine, prior to calling REQCOM.
; 
; 
; INPUTS:
;	R2	- IRP address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:         
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

IO_AUDIO:

	MOVL	IRP$L_SEGVBN(R2),R0	; Get start address of S0 buffer.
	MOVL	R0,SCDRP$L_MEDIA(R5)	; Get copy of AUCB address in SCDRP.

;+
; Dispatch to the appropriate path based on the function code in the low
; order byte of the AUCB's function code field.
;
; R0 is the SVA of the allocated buffered I/O buffer or AUCB Address.
; R2 is the address of the original IRP.
; R5 is the address of the SCDRP.
;-

	DISPATCH CD_FUNCTION_CODE(R0),TYPE=B,<-	; Dispatch according to function
	    	<PAUSE,		CD_PAUSE>,- 	       
	    	<RESUME,	CD_RESUME>,- 	       
	    	<PREVENT_REMOVAL,CD_PREVENT_REMOVAL>,- 	
	    	<ALLOW_REMOVAL,	CD_ALLOW_REMOVAL>,- 	
	    	<PLAY_AUDIO,	CD_PLAY_AUDIO>,- 	
	    	<PLAY_AUDIO_MSF,CD_PLAY_AUDIO_MSF>,- 	       
	    	<PLAY_AUDIO_TRACK,CD_PLAY_AUDIO_TRACK>,- 	       
	    	<PLAY_TRACK_REL,CD_PLAY_TRACK_REL>,-
	    	<GET_STATUS,	CD_READ_SUB>,- 	       
 	    	<GET_TOC,	CD_READ_TOC>,-
 	    	<GET_VOLUME,	CD_GET_VOLUME>,-
 	    	<SET_VOLUME,	CD_SET_VOLUME>>

;+
; Note: The SONY CDROM does not implement this command.
;-
CD_PLAY_TRACK_REL:

;+
; All unsupported I/O functions will be failed at this point.
;-
        MOVL    SCDRP$L_MEDIA(R5),R0    ; Get copy of AUCB address from SCDRP.
        MOVZWL  #SS$_BADPARAM,-         ; Set bad parameter status
                CD_COMMAND_STATUS(R0)
        MOVZWL  CD_COMMAND_STATUS(R0),R0 ; Copy VMS status to R0 for COMPLETE_IO
        BRW     AUDIO_BAD_CMD_EXIT      ; Complete this I/O function


	.SBTTL	AUDIO_PLAY_FUNCTIONS	- Audio CDROM play startio code
;+
; AUDIO_PLAY_FUNCTIONS
;
; The following code, makes up the startio routine for all of the Audio
; play functions supported by this driver. Execution of these functions cause
; the CD to begin a play operation.
;
; INPUTS:
;	R0	- AUCB address
;	R2	- IRP address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:         
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-
AUDIO_PLAY_FUNCTIONS:

;+
; Now we will allocate a command buffer and fill it in with a SCSI CDB
; with all the required fields initialized.
;
; The PLAY_TRACK command requires a starting and ending track. The starting
; track is passed AUCB ARG1 and ending track in AUCB ARG2.
;-
CD_PLAY_AUDIO_TRACK:
	MOVAL	CMD_PLAY_TRACK,R2	; Address of Play Track command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	ADDL3	#4,SCDRP$L_CMD_PTR(R5),-; Get address of SCSI CDB (Command).
		 R0 			; The CDB doesn't start at 0, but 4.
	MOVL	SCDRP$L_MEDIA(R5),R1	; Restore address of AUCB.

;+
; The command bytes must be moved one byte at a time into the CDB, since
; the byte ordering is not the same as VAX!
;-
	TSTBGTR	CD_ARG1(R1),#99		; IF starting track > 99, then error.
	MOVB	CD_ARG1(R1),4(R0)	; Copy Starting Track number to CDB.
	MOVB	CD_ARG1+1(R1),5(R0)	; Copy Starting Index number.
	TSTBLSS	CD_ARG2(R1),#1		; If ending track is < 1 then error.
	MOVB	CD_ARG2(R1),7(R0)	; Copy Ending Track number to CDB.
	MOVB	CD_ARG2+1(R1),8(R0)	; Copy Ending Index number.
10$:	BSBW	SEND_COMMAND		; Send the SCSI command
	BRW	AUDIO_EXIT		; Complete an audio SCSI function


;+
; The Play Audio command requires a starting LBA address and block count which
; are passed in ARG1 and ARG2 of the AUCB respectively.
;-

CD_PLAY_AUDIO:
	MOVAL	CMD_PLAY_AUDIO10,R2	; Address of Play Audio10 (45) command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	ADDL3	#4,SCDRP$L_CMD_PTR(R5),-; Get address of SCSI CDB (Command).
		 R0 			; The CDB doesn't start at 0, but 4.
	MOVL	SCDRP$L_MEDIA(R5),R1	; Restore address of AUCB.
					
;+
; Setup the starting Logical Block Address(LBA) for the Play Audio 10 command.
;
; The command bytes must be moved one byte at a time into the CDB, since
; the byte ordering is not the same as VAX!
;-					
	MOVB	CD_ARG1+0(R1),5(R0)	; Copy Starting Logical Block Addr(LSB)
	MOVB	CD_ARG1+1(R1),4(R0)	; Copy middle LBA byte to CDB.
	MOVB	CD_ARG1+2(R1),3(R0)	; Copy middle LBA byte to CDB.
	MOVB	CD_ARG1+3(R1),2(R0)	; Copy MSB LBA byte to CDB
					
;+
; Now setup the transfer length in the CDB for the Play Audio 10 command.
;-					
	MOVB	CD_ARG2+0(R1),8(R0)	; LSB of the Transfer Length into CDB.
	MOVB	CD_ARG2+1(R1),7(R0)	; MSB of Transfer Length.
	TSTBGTR	CD_ARG2+2(R1),#0	; This field must be zero.
	TSTBGTR	CD_ARG2+3(R1),#0	; This field must be zero.

10$:	BSBW	SEND_COMMAND		; Send the SCSI command
	BRW	AUDIO_EXIT		; Complete an audio SCSI function


;+
; The Play MSF command requires a starting and ending time expressed in Minutes, Seconds
; and frames format and are passed in ARG1 and ARG2 of the AUCB respectively.
;-
CD_PLAY_AUDIO_MSF:
	MOVAL	CMD_PLAY_AUDIO_MSF,R2	; Address of Play Audio MSF (47) command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	ADDL3	#4,SCDRP$L_CMD_PTR(R5),-; Get address of SCSI CDB (Command).
		 R0 			; The CDB doesn't start at 0, but 4.
	MOVL	SCDRP$L_MEDIA(R5),R1	; Restore address of AUCB.
					
;+
; Setup the starting Minute, Seconds and Frame fields for the Play MSF command.
;
; The command bytes must be moved one byte at a time into the CDB, since
; the byte ordering is not the same as VAX!
;-					
	MOVB	CD_ARG1+0(R1),5(R0)	; Copy Starting Frame byte to the CDB.
	MOVB	CD_ARG1+1(R1),4(R0)	; Copy Seconds byte to the CDB.
	MOVB	CD_ARG1+2(R1),3(R0)	; Copy Minutes byte to the CDB.
					
;+
; Now setup the ending MSF fields for the Play Audio 12 command.
;-					
	MOVB	CD_ARG2+0(R1),8(R0)	; Copy ending Frames byte to the CDB.
	MOVB	CD_ARG2+1(R1),7(R0)	; Copy Seconds byte to the CDB.
	MOVB	CD_ARG2+2(R1),6(R0)	; Copy Minutes byte to the CDB.

10$:	BSBW	SEND_COMMAND		; Send the SCSI command
	BRW	AUDIO_EXIT		; Complete an audio SCSI function



	.SBTTL	AUDIO_CONTROL_FUNCTIONS	- Audio CDROM control startio code
;+
; AUDIO_CONTROL_FUNCTIONS
;
; The functions in this section of code are used to executed various audio
; control functions including pausing and resuming the CD.
; 
; INPUTS:
;	R0	- AUCB address
;	R2	- IRP address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:         
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-
AUDIO_CONTROL_FUNCTIONS:

CD_PAUSE:
	MOVAL	CMD_PAUSE,R2		; Address of PAUSE command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BSBW	SEND_COMMAND		; Send the SCSI command
	BRW	AUDIO_EXIT		; Exit Audio Start I/O.

CD_RESUME:
	MOVAL	CMD_RESUME,R2		; Address of RESUME command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BSBW	SEND_COMMAND		; Send the SCSI command
	BRW	AUDIO_EXIT		; Exit Audio Start I/O.

CD_PREVENT_REMOVAL:
	MOVAL	CMD_REMOVAL,R2		; Address of Prevent Removal command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	ADDL3	#4,SCDRP$L_CMD_PTR(R5),-; Get address of SCSI CDB (Command).
		 R0 			; The CDB doesn't start at 0, but 4.
	MOVB	#01,4(R0)		; Set the Prevent Removal bit.
	BSBW	SEND_COMMAND		; Send the SCSI command
	BRW	AUDIO_EXIT		; Exit Audio Start I/O.

CD_ALLOW_REMOVAL:
	MOVAL	CMD_REMOVAL,R2		; Address of Prevent Removal command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	ADDL3	#4,SCDRP$L_CMD_PTR(R5),-; Get address of SCSI CDB (Command).
		 R0 			; The CDB doesn't start at 0, but 4.
	CLRB	4(R0)			; Clear the Prevent Removal bit.
	BSBW	SEND_COMMAND		; Send the SCSI command
	BRW	AUDIO_EXIT		; Exit Audio Start I/O.



	.SBTTL	AUDIO_GETSET_FUNCTIONS	- Audio CDROM get/set parameter code
;+
; AUDIO_GETSET_FUNCTIONS
;
; The functions in this section of code are used to query or initialize 
; specific parameters or data from the CDROM.  For example, the Table Of 
; Contents (TOC) can be read, the current position of the CDROM 
; can be read and returned to the user or the volume of the CDROM can be set.
; 
; INPUTS:
;	R0	- AUCB address
;	R2	- IRP address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:         
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-
AUDIO_GETSET_FUNCTIONS:

;+
; The GET_STATUS AUCB function does a SCSI READ SUBQ command and returns the 
; requested subchannel data (format 1 and 2 are supported) to the application.
; From this information the application can determine the current track and 
; head location of the CD-ROM as well as the UPC bar code data.
;-
CD_READ_SUB:
	DISPATCH CD_ARG2(R0),TYPE=B,<-	; Dispatch based on format
	    	<0,  80$>,-		;  fmt #0, Format 1 and 2 combined.
	    	<1,  40$>,-		;  fmt #1, current location data
 	    	<2,  20$>-		;  fmt #2, Media Catalog Number data
		>
	MOVL	#SS$_BADPARAM,R0	; else bad parameter
	BRW	AUDIO_EXIT_NO_CMD	
;+
;  Get format 2 (MCN) data from UCB or by issuing a SCSI READ SUBQ command
;-
20$:	
	BSBW	FETCH_MCN		; get format 2 data
	BLBS	R0,30$			;  R0 with status
	BRW	AUDIO_EXIT_NO_CMD	

30$:
;
; MCN data in UCB is good, copy to users buffer
;
	PUSHR 	#^M<R0,R1,R2,R3,R4,R5>
	MOVL	SCDRP$L_IRP(R5),R1	; Get original IRP Address
	MOVL	IRP$L_SEGVBN(R1),R2	; Get start address of the AUCB.
	MOVL	CD_DEST_BUF_CNT(R2),R2	; Get size of destination buffer.
	MOVL	#CD_MCN_LEN,R0		; and size of stored MCN data
	MINUM	R0,R2			; Minimize between these two values.
	MOVL	IRP$L_EXTEND(R1),R1	; Get the linked/extended IRP address
	MOVC3	R0,-			; Copy the number of bytes received
		 @UCB$B_MCN_SCDATA(R3),-;  from UCB to the 
		  @IRP$L_SEGVBN(R1)	;  start of the mapped user buffer
	POPR 	#^M<R0,R1,R2,R3,R4,R5>
	MOVL	#SS$_NORMAL,R0		; return success
	BRW	AUDIO_EXIT_NO_CMD	

40$:	BSBW	READ_CD_SUBQ		; Get format 1 data.
	BLBCW	R0,200$			; On error exit

;+
; Move READ SUBQ data from intermediate buffer to users destination buffer.
;-	
	PUSHR 	#^M<R0,R1,R2,R3,R4,R5>
	MOVL	SCDRP$L_IRP(R5),R1	; Get original IRP Address
	MOVL	IRP$L_SEGVBN(R1),R2	; Get start address of the AUCB.
	MOVL	CD_DEST_BUF_CNT(R2),R2	; Get size of destination buffer.
	MINUM	SCDRP$L_TRANS_CNT(R5),-	; Minimize between these two values.
		 R2
	MOVL	IRP$L_EXTEND(R1),R1	; Get the linked/extended IRP address
	MOVC3	SCDRP$L_TRANS_CNT(R5),-	; Copy the number of bytes received
		 @SCDRP$L_SVA_USER(R5),-; Copy from Nonpaged, to the 
		  @IRP$L_SEGVBN(R1)	; start of the mapped user buffer
	POPR 	#^M<R0,R1,R2,R3,R4,R5>
	BRW	200$

;+ 
; issue the format 0 "read", this is done by combining format 1 and format
; 2 data.
;-
80$:	BSBW	READ_CD_SUBQ		; Get format 1 data.
	BLBCW	R0,200$			; On error exit
;+
; Move READ SUBQ data from intermediate buffer to users destination buffer.
;-	
	MOVL	SCDRP$L_IRP(R5),R1	; Get original IRP Address
	MOVL	IRP$L_SEGVBN(R1),R2	; Get start address of the AUCB.
	MOVL	CD_DEST_BUF_CNT(R2),R2	; Get size of destination buffer.
	MINUM	SCDRP$L_TRANS_CNT(R5),-	; Minimize between these two values.
		 R2
	PUSHR 	#^M<R0,R2,R3,R4,R5>
	MOVL	IRP$L_EXTEND(R1),R1	; Get the linked/extended IRP address
	MOVC3	SCDRP$L_TRANS_CNT(R5),-	; Copy the number of bytes received
		 @SCDRP$L_SVA_USER(R5),-; Copy from Nonpaged, to the 
		  @IRP$L_SEGVBN(R1)	; start of the mapped user buffer
	MOVL	R3,R1			; copy addr of next byte in user's
					;  buffer
	POPR 	#^M<R0,R2,R3,R4,R5>
	SUBL	SCDRP$L_TRANS_CNT(R5),-	; Subtract what's already transfered
		R2			;  from size of users buffer
	SUBPUSH	R2			; save register across call
	SUBPUSH	R1
	BSBW	CLEANUP_CMD		; cleanup from READ SUBQ command
	BSBW	FETCH_MCN		; get format 2 data
	SUBPOP	R1			
	SUBPOP	R2
	BLBS	R0,90$			;  R0 with status
	BRW	AUDIO_EXIT_NO_CMD	
90$:	
	PUSHR 	#^M<R0,R3,R4,R5>
	MOVL	#CD_MCN_LEN,R0		; get size of stored MCN data
	MINUM	R0,R2			; Minimize between these two values.
	MOVC3	R0,-			; Copy the number of bytes received
		 @UCB$B_MCN_SCDATA(R3),-;  from UCB to the 
		  (R1)			;  remainder of user's buffer
	POPR 	#^M<R0,R3,R4,R5>
	MOVL	#SS$_NORMAL,R0		; return success
	BRW	AUDIO_EXIT_NO_CMD	

200$:	BRW	AUDIO_EXIT		; Complete an audio SCSI function


;+
; The GET_TOC AUCB function does a SCSI READ command and returns the Table Of Contents
; (TOC) data to the application. From this information the appliaction can determine
; the starting location and data type for each track on the CD-ROM.
;-
CD_READ_TOC:
	MOVAL	CMD_CD_READ_TOC,R2	; Address of READ TOC command
	BSBW	SETUP_CMD		; Set Command
	ADDL3	#4,SCDRP$L_CMD_PTR(R5),-; Get address of SCSI CDB (Command).
		 R0 			; The CDB doesn't start at 0, but 4.
	MOVL	SCDRP$L_MEDIA(R5),R1	; Restore address of AUCB.
	TSTBGTR	CD_ARG2(R1),#99		; Can't have more than 99 tracks, now!
	MOVB	CD_ARG2(R1),6(R0)	; Write Starting Track to CDB
	TSTBGTR	CD_ARG1+0(R1),#1	; If greater than one error
	ASHL	#1,CD_ARG1(R1),R1	; Get LBA/MSF bit in left bit position
	BISB	R1,1(R0)		; Select MSF or LBA address format.
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBCW	R0,250$			; Branch on error

;+
; Move TOC data from intermediate buffer to the destination buffer.
;-	
	PUSHR 	#^M<R0,R1,R2,R3,R4,R5>
	MOVL	SCDRP$L_IRP(R5),R1	; Get original IRP Address
	MOVL	IRP$L_SEGVBN(R1),R2	; Get start address of the AUCB.
	MOVL	CD_DEST_BUF_CNT(R2),R2	; Get size of destination buffer.
	MINUM	SCDRP$L_TRANS_CNT(R5),-	; Minimize between these two values.
		 R2
	MOVL	IRP$L_EXTEND(R1),R1	; Get the linked/extended IRP address
	MOVC3	SCDRP$L_TRANS_CNT(R5),-	; Copy the number of bytes received
		 @SCDRP$L_SVA_USER(R5),-; Copy from Nonpaged, to the 
		  @IRP$L_SEGVBN(R1)	; start of the mapped user buffer
	POPR 	#^M<R0,R1,R2,R3,R4,R5>
250$:	BRW	AUDIO_EXIT		; Complete an audio SCSI function



;+
; The GET_VOLUME AUCB function does a SCSI MODE SENSE command and returns the Sense Data
; containing the channel information to the application, from this the current volume
; can be determined.
;-
CD_GET_VOLUME:

;+
; Before doing a mode select (set volume) do a mode sense to get the current
; values of the parameters.
;-
	BSBW	CD_GET_SENSE		; Issue CD-ROM specific Mode Sense
	BLBCW	R0,AUDIO_EXIT		; Branch on error	

;+ 
; Copy the volume and channel control from the Sense data buffer to the
; AUCB.
;-
	PUSHL	R0
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of MODE SENSE Data
	MOVL	SCDRP$L_MEDIA(R5),R1	; Get copy of AUCB address from SCDRP.
	MOVL	20(R0),CD_ARG1(R1)	; Copy port(0,1) Volume and port info
	MOVL	24(R0),CD_ARG2(R1)	; Copy port(2,3) Volume and port info
	POPL	R0
	BRW	AUDIO_EXIT		; Complete an audio SCSI function



;+
; The SET_VOLUME AUCB function does a SCSI MODE SENSE, followed by a MODE SELECT command
; and sets the volume and channel information on the target device.
;-
CD_SET_VOLUME:

;+
; Before doing a mode select (set volume) do a mode sense to get the current
; values of the parameters.
;-
	BSBW	CD_GET_SENSE		; Issue CD-ROM specific Mode Sense
	BLBCW	R0,190$			; Branch on error	

;+
; Since we want to reuse this SCDRP and call SETUP_CMD, deallocate the 
; previously allocated Mode Sense COMMAND buffer. SETUP_CMD will allocate
; a new one for the MODE SELECT command.
;
; Note: We are using the previously allocated MODE SENSE buffer as our MODE
; Select buffer. CLEANUP_CMD will deallocate this buffer when we are done.
;-
	SPI$DEALLOCATE_COMMAND_BUFFER	; Deallocate the command buffer

	MOVAL	CMD_CD_MODE_SELECT,R2	; Address of MODE SENSE
	BSBW	SETUP_CMD		; Set Command
	BLBCW	R0,190$			; Branch on error	

;+
; We must force this request to be a write request, since we are bye-passing 
; some of the SETUP_CMD setup to allow us to use the MODE SENSES buffer.
;-
	BICW	#IRP$M_FUNC,-		; Mode Select is "write" operation,
		 SCDRP$W_STS(R5)	; with data going to the target.
;+
; Now that all the setup is done, fixup the MODE SENSE bytes to be exactly
; the way we need them.
;
; Copy the volume and channel control from the AUCB into the CCB.
;-
	MOVL	SCDRP$L_CMD_PTR(R5),R1	; Get address of command buffer
	MOVB	#<4+8+16>,A_LEN(R1)	; Number of Mode Select bytes.
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of MODE SENSE Data
	CLRB	(R0)			; Clear the Sense Data Length
	MOVL	SCDRP$L_MEDIA(R5),R1	; Get copy of AUCB address from SCDRP.
	MOVL	CD_ARG1(R1),20(R0)	; Copy port(0,1) Volume and port info
	MOVL	CD_ARG2(R1),24(R0)	; Copy port(2,3) Volume and port info
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBCW	R0,190$			; Branch on error	

190$:	BRW	AUDIO_EXIT		; Complete an audio SCSI function

;+
; Subroutine used to get the volume information by issuing a mode sense
; command for the Audio Control Parameters.
;-
CD_GET_SENSE:
	SUBSAVE
	MOVAL	CMD_MODE_SENSE,R2	; Address of MODE SENSE
	BSBW	SETUP_CMD		; Set Command
;+
; Now that all the setup is done, fixup the MODE SENSE bytes to be exactly
; the way we need them.
;-
	MOVL	SCDRP$L_CMD_PTR(R5),R1	; Get address of command buffer
;+
; For now just get the value for the CDROM page, later get it from the
; AUCB.
;-
	MOVB	#^X0E,PAGE_CODE(R1)	; Interested in CDROM Audio Ctrl Page
	MOVB	#28,A_LEN(R1)		; Number of Mode Sense bytes to receive.
	BSBW	SEND_COMMAND		; Send the SCSI command.
					; Status checked by caller.
	SUBRETURN
	RSB

IO_BADPARM:
	MOVZBL	#SS$_BADPARAM,R0	; Set bad parameter status
	BRW	AUDIO_EXIT


	.SBTTL	READ_CD_MCN - reads CD-ROM Sub-Channel to get the Media Cat. #
;+
;
; This routine sends a READ SUB-CHANNEL command requesting the 02h Data
; format code, Media Catalog Number, be returned for a CD-ROM media.  
; This command causes the CD to stop playing audio, therefore it is only
; issued in the following cases (all indicated by the UCB$M_CD_VALID flag
; being clear):
;
;	1) When the "mount" of the device occurs (called from PACKACK
;	   routine).
;
;	2) If the CD_READ_SUB format 2 command is issued and a UNIT ATTENTION
;	   Sense key indicating a media change has occured was returned for
;	   a previous SCSI command.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;	R1,R2   - Destroyed
;	R0	- Status
;
;  	UCB$B_MCN_DATA - filled in
;	UCB$M_CD_VALID - set to indicate MCN data valid if command succeeded
;
;-
READ_CD_MCN:
	SUBSAVE
	BICL	#UCB$M_CD_VALID,-	; Indicate stored sub-channel data for
		UCB$L_DK_FLAGS(R3)	;  CD-ROM is invalid.
	MOVAL	CMD_CD_READ_SUB,R2	; Address of READ SUB command
	BSBW	SETUP_CMD		; Set Command
	ADDL3	#4,SCDRP$L_CMD_PTR(R5),-; Get address of SCSI CDB (Command).
		 R0 			; The CDB doesn't start at 0, but 4.
	MOVB	#2,3(R0)		; Write Sub Channel Data Format 2.
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBCW	R0,20$			; Branch on error

;+
; Get some pool to save the MCN data in
;-
	TSTL	UCB$B_MCN_SCDATA(R3)	; Do we already have a buffer?
	BLSS	10$			; Branch if yes
	MOVL	#CD_MCN_LEN,R1		; get required length
	PUSHL	R3			; Save UCB address
	JSB	G^EXE$ALONONPAGED	; Get pool
	POPL	R3			; Restore UCB address
	BLBS	R0,5$			; Got the pool, OK
	BUG_CHECK INCONSTATE,FATAL	; BUGCHECK for now
5$:	MOVL	R2,UCB$B_MCN_SCDATA(R3)	; Save pointer
;+ 
; Copy Subchannel format 2 data to UCB
;-
10$:	PUSHR 	#^M<R0,R1,R2,R3,R4,R5>
	MOVL	#CD_MCN_LEN,R2		; Get size of destination buffer.
	MINUM	SCDRP$L_TRANS_CNT(R5),-	; Minimize between these two values.
		 R2
	MOVC3	SCDRP$L_TRANS_CNT(R5),-	; Copy the number of bytes received
		 @SCDRP$L_SVA_USER(R5),-; Copy from Nonpaged, to the 
		  @UCB$B_MCN_SCDATA(R3)	; start of the area in the UCB
	POPR 	#^M<R0,R1,R2,R3,R4,R5>
	BISL	#UCB$M_CD_VALID,-	; Indicate stored sub-channel data for
		UCB$L_DK_FLAGS(R3)	;  CD-ROM is valid.

20$:	BSBW	CLEANUP_CMD		; cleanup from SCSI command
	MOVL	#SS$_NORMAL,R0		; return success
	SUBRETURN			; and return


;+
;
; This routine sends a READ SUB-CHANNEL command requesting the 01h Data
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;	R1,R2   - Destroyed
;	R0	- Status
;
;-
READ_CD_SUBQ:
	SUBSAVE
;+
; Read format #1 sub-channel, The Q-Sub channel with current location info.
;-
	MOVAL	CMD_CD_READ_SUB,R2	; Address of READ SUB command
	BSBW	SETUP_CMD		; Set Command
	ADDL3	#4,SCDRP$L_CMD_PTR(R5),-; Get address of SCSI CDB (Command).
		 R0 			; The CDB doesn't start at 0, but 4.
	MOVL	SCDRP$L_MEDIA(R5),R1	; Restore address of AUCB.
	MOVB	#1,3(R0)		; Write Sub Channel Data Format.
	TSTBGTR	CD_ARG1+0(R1),#1	; If greater than one error
	ASHL	#1,CD_ARG1+0(R1),R1	; Get LBA/MSF bit in right bit position
	BISB	R1,1(R0)		; Select MSF or LBA address format.
	BSBW	SEND_COMMAND		; Send the SCSI command
	SUBRETURN			; and return


;+
;
; This routine gets the SUB-CHANNEL format 2 data, by issuing a SCCI 
; READ SUB-CHANNEL command requesting the 02h Data if the data in the
; UCB is invalid.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;	R1,R2   - Destroyed
;	R0	- Status
;
;-
MCN_DEF_STATUS:
	.BYTE	0			; Status buffer if no SCSI cmd issued.

FETCH_MCN:
	SUBSAVE
;+
; We issue a extra read_sub command here to guarantee that the CD has not been
; changed. This command will clear the CD_VALID flag in the UCB if the a unit
; attention occurs during this commmand.
;-
	BSBW	READ_CD_SUBQ		; Get format 1 data.
	BSBW	CLEANUP_CMD		; toss data and cleanup
	BLBC	R0,30$			; On error exit

	MOVAB	MCN_DEF_STATUS,-	; Assume no SCSI command and setup
		 SCDRP$L_STS_PTR(R5)	;  status buffer in SCDRP here.
	MOVB	#SCSI$C_GOOD_STATUS,-	; Assume status of sucesss
		 MCN_DEF_STATUS	
	BITL	#UCB$M_CD_VALID,-	; Is the stored sub-channel data for
		UCB$L_DK_FLAGS(R3)	;  CD-ROM is valid?
	BNEQW	30$			; If NEQ yes just return
	
	BSBW	READ_CD_MCN		; read format #2 data to UCB
30$:	SUBRETURN


	.SBTTL	+
	.SBTTL	+ SCSI COMMAND EXECUTION ROUTINES
	.SBTTL	+
	.SBTTL	INQUIRY		- Send an inquiry command
;+
; INQUIRY
;
; This routine sends an inquiry command to the target. At least 36 bytes of
; inquiry data are expected to be returned. The peripheral device type field
; is check to ensure the target is a disk-like device. The product ID field
; is used to determine the device type. In addition, several sanity checks are 
; made on the inquiry data.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;-

INQUIRY:

	SUBSAVE				; Save return address
	MOVAL	CMD_INQUIRY,R2		; Address of INQUIRY command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBCW	R0,30$			; Branch on error
	CMPL	SCDRP$L_TRANS_CNT(R5),-	; Sufficient inquiry data returned?
		#36			;
	BLSSUW	40$			; Branch if not
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of inquiry data
      	MOVL	32(R0),UCB$L_HW_REV(R3)	; Save hardware revision level
	ASSUME	UCB$B_DEVCLASS+1 EQ UCB$B_DEVTYPE
	CLRW	UCB$B_DEVCLASS(R3)	; Assume unknown device class and type
	BICL2	#<UCB$M_WORM+-		; Some optical device can change their
		  UCB$M_OPTICAL>,-	; type based on media - make sure
		  UCB$L_DK_FLAGS(R3)	; that only one of these gets set
	EXTZV	#5,#3,(R0),R1		; Get peripheral qualifier
	BNEQW	40$			; If not 0, no device here
	EXTZV	#0,#5,(R0),R1		; Get peripheral device type
	CMPB	R1,#SCSI$C_DISK		; Is this a SCSI disk device?
	BEQL	10$			; Branch if so
	CMPB	R1,#SCSI$C_WORM		; Or a write-once read-multiple device?
	BNEQ	5$			; Branch if not
	BISL2	#UCB$M_WORM,-		; Set a bit to indicate that it is a
		 UCB$L_DK_FLAGS(R3)	;  WORM device
	BRB	10$			; Branch to continue disk device code
5$:	CMPB	R1,#SCSI$C_OPTICAL	; Or a optical device?
	BNEQ	7$			; Branch if not
	BISL2	#UCB$M_OPTICAL,-	; Set a bit to indicate that it is an
		 UCB$L_DK_FLAGS(R3)	;  optical device
	BRB	10$			; Branch to continue disk device code
7$:	CMPB	R1,#SCSI$C_CDROM	; Or a CDROM-like device
	BNEQ	40$			; Branch if not, illegal device type
	BISL	#UCB$M_CDROM,-		; Indicate CDROM-like device
		UCB$L_DK_FLAGS(R3)	; 
	BISL2	#UCB$M_HWL,-		; This a read-only device so set
		UCB$L_DK_FLAGS(R3)	; the hardware write-lock bit
	BBSS	#DEV$V_SWL,-		; This a read-only device so set
		UCB$L_DEVCHAR(R3),10$	; the software write-lock bit
10$:	MOVB	#DC$_DISK,-		; Indicate that this a disk device
		UCB$B_DEVCLASS(R3)	;
	BBSS	#UCB$V_REMOVABLE,-	; Assume drive has removable media
		UCB$L_DK_FLAGS(R3),15$	;
15$:	TSTW	(R0)			; Is this a removable media device?
	BLSS	20$			; Branch if so
	BBCC	#UCB$V_REMOVABLE,-	; Clear the removable media bit
		UCB$L_DK_FLAGS(R3),20$	; in the UCB
20$:	EXTZV	#0,#4,3(R0),R1		; Get response data format
	MOVB	R1, UCB$B_SCSI_VERSION(R3) ; Save for future checks
					; Does drive conform to CCS?
	BEQL	25$			; Branch if so - TGG0002
	CMPB	R1,#1			; Does drive conform to CCS?
	BEQL	25$			; Branch if so
	CMPB	R1,#2			; Does drive conform to SCSI-2?
	BNEQ	40$			; Branch if not
25$:	CMPB	4(R0),#^X1F		; Valid additional length field?
	BLSSU	40$			; Branch if not
	BSBW	GET_DEVICE_TYPE		; Determine device type from Vendor
	MOVZWL	#SS$_NORMAL,R0		; Set success status	
      
30$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	SUBRETURN			; Return to caller

40$:	LOG_ERROR -			; Log an invalid inquiry data error
		TYPE=INV_INQUIRY_DATA,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
	MOVZWL	#SS$_DRVERR,R0		; Set bad status
	BRB	30$			; Use common exit


	.SBTTL	REQUEST_SENSE	- Send a request sense command
;+
; REQUEST_SENSE
;
; This routine is called by SEND_COMMAND when a command fails with check 
; condition status. A reqest sense command is sent to the target.
;
; Request sense is always sent as  QCHAR$K_ERROR_RECOVERY since the
; port will convert it to QCHAR$K_NOT_QUEUED if the connection does not
; support command queueing.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

REQUEST_SENSE:

	SUBSAVE				; Save return address
	MOVAL	CMD_REQUEST_SENSE,R2	; Address of REQUEST_SENSE command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	SPI$QUEUE_COMMAND QCHAR=#QCHAR$K_ERROR_RECOVERY ; Send the SCSI command
	BLBC	R0,30$			; Branch on error
	SPI$RELEASE_QUEUE		; Resume queue processing now
	ASSUME	SCSI$C_GOOD_STATUS EQ 0
	MOVZBL	@SCDRP$L_STS_PTR(R5),R1	; Get SCSI status byte
	BICB	#^XC1,R1		; Clear reserved, vendor unique bits
	BNEQ	20$			; Branch if bad status
10$:	SUBRETURN			; Return to caller

20$:	MOVL	#SS$_MEDOFL,R0		; Set bad status
30$:	SPI$RELEASE_QUEUE		; Resume queue processing now
	LOG_ERROR -			; Log a send command error
		TYPE=SEND_CMD_ERROR,-	;
		VMS_STATUS=R0,-		;
		UCB=R3			;
	BRB	10$			; Clean up and return to caller


	.SBTTL	TEST_UNIT_READY	- Send a test unit ready command
;+
; TEST_UNIT_READY
;
; This routine sends a test unit ready command to the target to determine
; whether the device is spun up and ready.
;
; NOTE: The TUR command is also used by IO$_NOP for a cluster sequential
; 	NOP to sync queues. If this code is used in a multi-host environment,
;	we may need to make this a ORDERED QUEUED command to sync with other
;	initiators as well...
;	
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

TEST_UNIT_READY:

	SUBSAVE				; Save return address
	BISB	#UCB$M_SPINUP_INPROG,-	; Set spinup-in-progress
		UCB$L_DK_FLAGS(R3)	; 
	MOVAL	CMD_TEST_UNIT_READY,R2	; Address of TEST UNIT READY command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BSBW	SEND_COMMAND		; Send non-queued TUR
	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	BICB	#UCB$M_SPINUP_INPROG,-	; Clear spinup-in-progress
		UCB$L_DK_FLAGS(R3)	; 
	SUBRETURN


	.SBTTL	MODE_SENSE	- Send a mode sense command
;+
; MODE_SENSE
;
; This routine sends a mode sense command to the target requesting all mode
; sense pages be returned. The mode sense data is then scanned to obtain
; useful information such as device geometry and error recovery parameters.
; The MODE_SELECT routine uses this information, which is saved in the UCB,
; to change any of the default settings of the device if necessary.
;
; If the UCB$V_RAID bit is set, we use a 10 byte MODE_SENSE command since
; RAID devices have lot's of mode pages.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

; Define mode sense page code paramaters

PGC$ERR_RECOV = 1			; Error recovery parameters
PGC$DSC_PARAM = 2			; Disconnect parameters
PGC$FMT_PARAM = 3			; Format parameters
PGC$RGD_PARAM = 4			; Rigid disk geometry parameters
PGC$FLX_PARAM = 5			; Flexible disk geometry parameters
PGC$CTRL_MODE = 10                      ; Control Mode (queueing parms)

; Define type codes for optical device media

OPT$C_MEDIA_DEFAULT    = 0		; Default media (only one supported)
OPT$C_MEDIA_RO	       = 1		; Read Only
OPT$C_MEDIA_WORM       = 2		; WORM
OPT$C_MEDIA_ERASE      = 3		; Erasable or Reversible
OPT$C_MEDIA_RO_WORM    = 4		; Combination of Read Only + WORM
OPT$C_MEDIA_RO_ERASE   = 5		; Combination of Read Only + Erasable
OPT$C_MEDIA_WORM_ERASE = 6		; Combination of WORM and Erasable

; Define storage offsets for format parameters in non-paged pool
;
	$DEFINI	FORMAT_PARAMETERS

$DEF	FMT$B_FMT_PAR		.BLKB  	FMT_PARAM_SIZE
$DEF	FMT$B_FMT_CPAR		.BLKB  	FMT_PARAM_SIZE
$DEF	FMT$B_FLX_PAR		.BLKB  	FLX_PARAM_SIZE
$DEF	FMT$B_FLX_CPAR		.BLKB  	FLX_PARAM_SIZE
$DEF	FMT$C_BLOCK_SIZE

	$DEFEND	FORMAT_PARAMETERS

; This table is used to convert between the MEDIA_TYPE field in the
;   MODE_SENSE header and the IRP$L_MEDIA value
;
MODE_SENSE_MEDIA_TYPE_TABLE:
	.BYTE	^X00		; default or unknown
	.BYTE	^X80		; DD (double density)
	.BYTE	^X81		; HD (high density)
	.BYTE	^X82		; ED (extended density)

MODE_SENSE:

	SUBSAVE				; Save return address
	MOVAL	CMD_MODE_SENSE,R2	; Address of MODE SENSE command
	IF_NOT_RAID	100$            ; If RAID, use 10 byte version
	MOVAL	CMD_MODE_SENSE_10,R2    ; Address of 10 byte MODE SENSE command
100$:	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBCW	R0,80$			; Branch on error

	; Given that we've asked for all supported pages, it would be unheard of 
	; for us to get back less than 8 bytes, so make this the minimum checked
	; for - this will get us well into the later sections where we're checking
	; for truncated pages. 

	CMPW	SCDRP$L_TRANS_CNT(R5),#8 ; Check for a reasonable minimum
	BLSSW	95$			 ; amount of data, branch if not

	ASSUME	UCB$B_SECTORS+1 EQ UCB$B_TRACKS
	ASSUME	UCB$B_TRACKS+1 EQ UCB$W_CYLINDERS
	CLRL	UCB$B_SECTORS(R3)	; Initialize gemoetry fields in UCB
	MOVW	#512,-			; Assume 512 bytes per block
		UCB$W_BLOCK_SIZE(R3)	;
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of mode sense data
	IF_RAID	110$			; Need a word if RAID bit set
	MOVZBL	(R0),R1			; Get byte length of mode sense data
	BEQLW	70$			; Branch if none
	INCL	R1			; Account for length byte itself
	BRB	120$			; Join common code
110$:	MOVZWL	(R0),R1			; Get word length of mode sense data
	SWAB	R1			; Fix SCSI vs VAX endian byte order
	TSTL	R1			; Did we get data?
	BEQLW	70$			; Branch if none
	ADDL	#2,R1			; Account for length word itself
120$:	CMPL	R1,SCDRP$L_TRANS_CNT(R5); Mode sense data length may exceed 
					; actual length of data received
	BLEQ	2$			; Branch if not
	MOVL	SCDRP$L_TRANS_CNT(R5),R1; Use actual number of bytes received
	BEQLW	70$			; Branch if none
2$:	IF_RAID	130$			; write-protect is in differnet offset for 10 byte
	BBC	#7,2(R0),5$		; Is write-protect bit clear?
	BRB	140$			; Branch if not
130$:	BBC	#7,3(R0),5$		; Is write-protect bit clear?
140$:	BISL2	#UCB$M_HWL,-		; This a read-only device so set
		UCB$L_DK_FLAGS(R3)	; the hardware write-lock bit

	BBSS	#DEV$V_SWL,-		; Set the software write-lock bit to
		UCB$L_DEVCHAR(R3),5$	; indicate device is write-locked
5$:

	BICL2	#<UCB$M_NOREASSIGN!-	; Assume REASSIGN_BLOCK supported,
		  UCB$M_FLOPPY!-	; Assume not a flexible diskette,
		  UCB$M_FORMAT>,-	; Assume FORMAT unsupported
		UCB$L_DK_FLAGS(R3)

	BBS	#UCB$V_WORM,-		; If this is a WORM device we need to 
		 UCB$L_DK_FLAGS(R3),7$	; protect it from the file system.
	BBC	#UCB$V_OPTICAL,-	; If it's not WORM, it might be optical
		 UCB$L_DK_FLAGS(R3),67$	; with WORM media - check the media 
	CMPB	1(R0),#OPT$C_MEDIA_WORM ; type code in the mode sense data,
	BNEQ	67$			; branch past *WL bit sets if not.
7$:	BISL2	#UCB$M_HWL,-		; Mark the device as Hardware Write
		 UCB$L_DK_FLAGS(R3)	; Locked.
	BISL2	#DEV$M_SWL,-		; Mark the device as Software Write
		 UCB$L_DEVCHAR(R3)	; Locked.
67$:					; Pre-Optical code path.

; Determine diskette type in RX26 drive
;
	CLRL	R2			; Assume unknown or default medium
	CMPB	#DT$_RX26,-		; Only RX26 reports diskette type
		UCB$B_DEVTYPE(R3)
	BNEQ	506$			; Not RX26, skip this check
;
	MOVZBL	#3,R2			; Start checking for match with
					;   IRP$L_MEDIA = 3
502$:	CMPB	1(R0),MODE_SENSE_MEDIA_TYPE_TABLE[R2]
					; Check against table entry
	BEQL	506$			; Matches, save index
	SOBGTR	R2,502$			; No match, back up in table
;
506$:	INSV	R2,#UCB$V_FLOPPY_MEDIA,-
		#UCB$S_FLOPPY_MEDIA,-
		UCB$L_DK_FLAGS(R3)	; remember medium type
	
; Early check for flexible disk geometry page, allocate memory if needed
; to save sense information
;
	SUBPUSH	R0			; Save R0 and R1
	SUBPUSH	R1
	TSTL	UCB$A_FORMAT_PARAM(R3)	; Check whether space already allocated
	BLSS	9$			; Yes, skip check

	MOVZBL	3(R0),R2		; Get total length of block descriptors
	ADDL	#4,R2			; Include length of mode sense header
	IF_NOT_RAID     6$             	; If 10 byte comman used, header looks 
	MOVZWL  6(R0),R2                ; Get total length (word) of block desc
	SWAB    R2                      ; Fix SCSI vs VAX endian byte order
	ADDL    #8,R2                   ; Include length of mode sense header
6$:	ADDL	R2,R0			; Skip to first/next page of sense data
	SUBL	R2,R1			; Update count of mode sense data left
	CMPW	R1,#2			; Do we have page code and size bytes ?
	BLEQ	9$			; Branch if no more mode sense data
	CMPZV	#0,#6,(R0),-
		#PGC$FLX_PARAM		; Check for flex disk geom page code
	BEQL	8$			; Yes, go allocate memory
					; If not, set up to check next page
	MOVZBL	1(R0),R2		; Get length of current page
	ADDL	#2,R2			; Account for page code, length byte
	BRW	6$			; Go try next page

8$:	MOVL	#FMT$C_BLOCK_SIZE,R1	; Ask for nonpaged pool to store data
	BSBW	ALLOC_POOL
	MOVL	R2,UCB$A_FORMAT_PARAM(R3)

9$:	SUBPOP	R1			; Restore R0 and R1
	SUBPOP	R0
	MOVZBL	3(R0),R2		; Get total length (byte) of block descriptors
	ADDL	#4,R2			; Include length of mode sense header
	IF_NOT_RAID	10$		; If 10 byte comman used, header looks differnet.
	MOVZWL	6(R0),R2		; Get total length (word) of block descriptors
	SWAB	R2			; Fix SCSI vs VAX endian byte order
	ADDL	#8,R2			; Include length of mode sense header
10$:	ADDL	R2,R0			; Skip to first/next page of sense data
	SUBL	R2,R1			; Update count of mode sense data left
	BLEQW	70$			; Branch if no more mode sense data
	CMPW	R1,#2			; Page code and size bytes available?
	BLSSW	95$			; Branch if not

	EXTZV	#0,#6,(R0),R2		; Get page code
	DISPATCH R2,TYPE=B,<-		; Dispatch according to page code
		<PGC$ERR_RECOV,	20$>,- 
		<PGC$DSC_PARAM,	30$>,-
		<PGC$FMT_PARAM,	40$>,-
		<PGC$RGD_PARAM,	50$>,-
		<PGC$FLX_PARAM, 55$>,-
		<PGC$CTRL_MODE,	200$>>

; Unknown/unsupported mode sense page.

	BRW	60$			; Join common code

; Control Mode page (Queuing parameters)

200$:
	BICB	#UCB$M_MODE_SENSE_PAG10,-; Assume this page not seen
		UCB$L_DK_FLAGS(R3)
	IF_NOT_CMDQ	230$		; If device doesn't do queuing, ignore
					; this page
	ASSUME	UCB$V_MODE_SENSE_PAG10 LT 8
	BISB	#UCB$M_MODE_SENSE_PAG10,-; Indicate that the control mode
		UCB$L_DK_FLAGS(R3)	; page has been received

	MOVZBL	1(R0),R2		; Get bytes in page
	ADDL2	#2,R2			; Allow for the page code/length bytes
	CMPL	#UCB_CTRL_MODE_SIZE,R2	; Check for too much data to save
	BLSS	225$			; If so, we can't deal with this
	CMPL	R1,R2			; Check whether we have all the data
	BGEQ	228$			; If not, we can't deal with this
225$:	BRW	95$			; Error
228$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Move bytes to UCB
	MOVC3	R2,(R0),UCB$B_CTRL_MODE_PAR(R3)
	POPR	#^M<R0,R1,R2,R3,R4,R5>
	CMPB	#1,UCB$B_CTRL_MODE_PAR+1(R3)
	BGTRUW	95$			; Error: not enough bytes in page
230$:
	BRW	60$			; Join common code

; Error recovery parameters

20$:	ASSUME	UCB$V_MODE_SENSE_PAG1 LT 8
	BISB	#UCB$M_MODE_SENSE_PAG1,-; Indicate that the error recovery
		UCB$L_DK_FLAGS(R3)	; page has been received

	MOVZBL	1(R0),R2		; Get bytes in page
	ADDL2	#2,R2			; Allow for the page code/length bytes
	CMPL	#UCB_REC_PARAM_SIZE,R2	; Check for too much data to save
	BLSS	25$			; If so, we can't deal with this
	CMPL	R1,R2			; Check whether we have all the data
	BGEQ	28$			; If not, we can't deal with this
25$:	BRW	95$			; Error
28$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Move bytes to UCB
	MOVC3	R2,(R0),UCB$B_RECOV_PAR(R3)
	POPR	#^M<R0,R1,R2,R3,R4,R5>
	CMPB	#1,UCB$B_RECOV_PAR+1(R3)
	BGTRUW	95$			; Error: not enough bytes in page

; Disconnect parameters

30$:	BRW	60$			; Join common code

; Format parameters

40$:	MOVB	11(R0),-		; Save number of sectors per track
		UCB$B_SECTORS(R3)	; in UCB
	MOVB	12(R0),-		; Save number of bytes per block
		UCB$W_BLOCK_SIZE+1(R3)	;
	MOVB	13(R0),-		;
		UCB$W_BLOCK_SIZE(R3)	;

	TSTL	UCB$A_FORMAT_PARAM(R3)	; Check whether storage available
	BEQL	49$			; No, don't bother saving data

	MOVZBL	1(R0),R2		; Get bytes in page
	ADDL2	#2,R2			; Allow for the page code/length bytes
	CMPL	#FMT_PARAM_SIZE,R2	; Check for too much data to save
	BLSS	45$			; If so, we can't deal with this
	CMPL	R1,R2			; Check whether we got all the data
	BGEQ	48$			; If not, we can't deal with this
45$:	BRW	95$			; Error
48$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Move bytes to UCB
	MOVL	UCB$A_FORMAT_PARAM(R3),R3
	MOVC3	R2,(R0),FMT$B_FMT_PAR(R3)
	POPR	#^M<R0,R1,R2,R3,R4,R5>

49$:	BRB	60$			; Join common code

; Geometry parameters for rigid disks

50$:	MOVB	5(R0),UCB$B_TRACKS(R3)	; Save number of tracks per cylinder
	MOVB	3(R0),-			; Save number of cylinders (high byte)
		UCB$W_CYLINDERS+1(R3)	;
	MOVB	4(R0),-			; Save number of cylinders (low byte)
		UCB$W_CYLINDERS(R3)	;
	BRB	60$			; Join common code

; Geometry parameters for flexible disks

55$:	MOVB	4(R0),UCB$B_TRACKS(R3)	; Save number of tracks per cylinder
	MOVB	8(R0),-			; Save number of cylinders (high byte)
		UCB$W_CYLINDERS+1(R3)	;
	MOVB	9(R0),-			; Save number of cylinders (low byte)
		UCB$W_CYLINDERS(R3)	;

	BISL2	#<UCB$M_NOREASSIGN!-	; Floppies don't do REASSIGN_BLOCK
		  UCB$M_FORMAT!-	; This unit is formatable
		  UCB$M_FLOPPY>,-	; Remember that this is a floppy
		UCB$L_DK_FLAGS(R3)

					; Check for 250 KHz RX23 diskette,
					; if so, then mark it write-protected
					; unless UCB$L_DK_FLAGS<DD_BYPASS> is
					; set to allow writing of these media.

	BBS	#UCB$V_DD_BYPASS,-	; Don't bother checking if the
		UCB$L_DK_FLAGS(R3),-	; special override bit is set
		558$
	CMPB	#DT$_RX23S,-		; Only worry about RX23S drive
		UCB$B_DEVTYPE(R3)
	BNEQ	558$
	CMPW	#^XFA00,2(R0)		; Check for 250 KHz (00FAh with the
					; bytes reversed)
	BEQL	559$			; If 250 KHz, then write protect it

					; Check for 48 TPI diskette,
					; if so, then mark it
					; software write-protected

558$:	CMPB	#20,1(R0)		; Check length of page
	BGTR	56$			; Skip test for SPC if page too short
	CMPZV	#0,#4,22(R0),#0		; Test for SPC (steps per cylinder) = 0
	BEQL	56$			; If so, then skip
559$:	BBSS	#DEV$V_SWL,-		; If not, then set the software
		UCB$L_DEVCHAR(R3),56$	; write-lock bit (can't write 48 TPI
					; diskettes)

56$:	TSTL	UCB$A_FORMAT_PARAM(R3)	; Check whether storage available
	BEQL	60$			; No, don't bother saving data

	MOVZBL	1(R0),R2		; Get bytes in page
	ADDL2	#2,R2			; Allow for the page code/length bytes
	CMPL	#FLX_PARAM_SIZE,R2	; Check for too much data to save
	BLSS	57$			; If so, we can't deal with this
	CMPL	R1,R2			; Check whether we got all the data
	BGEQ	58$			; If not, we can't deal with this
57$:	BRW	95$			; Error
58$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Move bytes to UCB
	MOVL	UCB$A_FORMAT_PARAM(R3),R3
	MOVC3	R2,(R0),FMT$B_FLX_PAR(R3)
	POPR	#^M<R0,R1,R2,R3,R4,R5>
;	BRB	60$			; Join common code

60$:	MOVZBL	1(R0),R2		; Get length of current page
	ADDL	#2,R2			; Account for page code, length byte
	BRW	10$			; Go try next page

70$:	BBC	#UCB$V_MODE_SENSE_PAG1,-; Branch if error recovery parameters 
		UCB$L_DK_FLAGS(R3),95$	; unavailable
	ASSUME	UCB$B_SECTORS+1 EQ UCB$B_TRACKS
	ASSUME	UCB$B_TRACKS+1 EQ UCB$W_CYLINDERS
	MOVAL	UCB$B_SECTORS(R3),R1	; Get address sectors field in UCB
	TSTB	(R1)+			; Check that the tracks, sectors, and
	BEQL	90$			; cylinders fields in the UCB are all
	TSTB	(R1)+			; non-zero. If any are zero, then
	BEQL	90$			; there's a problem and error status
	TSTW	(R1)+			; should be returned to prevent the
	BEQL	90$			; volume valid bit from being set
75$:	MOVZWL	#SS$_NORMAL,R0		; Set success status	
80$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	SUBRETURN			; Return to caller

90$:	BITL	#<UCB$M_OPTICAL+-	; Is this an optical or a WORM device?
		  UCB$M_WORM>,-		; These devices do not always return
		  UCB$L_DK_FLAGS(R3)	; cylinder and track info.
	BNEQ	75$			; Exit normally if so.

; Handle possibility of unformatted media in a floppy drive, which
; returns zeroes in the geometry fields.
;

	BBC	#UCB$V_FLOPPY,-		; Report different error if floppy
		UCB$L_DK_FLAGS(R3),92$

	MOVZWL	#SS$_FORMAT,R0		; Set bad status:  unformatted media
	BRB	80$			; Use common exit path

; If this is a read-only device such as a CDROM, it may not have real tracks,
; sectors, and cylinders. If this is the case, READ_CAPACITY will set up a 
; dummy values for the geometry parameters. For now, assume success.

92$:	BBS	#DEV$V_SWL,-		; If this is a read-only device, it's not 
		UCB$L_DEVCHAR(R3),75$	; required to have non-zero values of
					; tracks, sectors, and cylinders
95$:	LOG_ERROR -			; Log a mode sense data error
		TYPE=MODE_SENSE_DATA,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
	MOVZWL	#SS$_DRVERR,R0		; Set bad status
	BRB	80$			; Use common exit path


	.SBTTL	MODE_SENSE_CHANGABLE - Send a mode sense changable command
;+
; MODE_SENSE_CHANGABLE
;
; This routine executes a SCSI MODE SENSE CHANGABLE command.
;
; If the UCB$V_RAID bit is set, a 10 byte mode sense command is used.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

MODE_SENSE_CHANGABLE:

	SUBSAVE				; Save return address
	MOVAL	CMD_MODE_SENSE,R2	; Address of 6 byte MODE SENSE command
	IF_NOT_RAID	100$
	MOVAL	CMD_MODE_SENSE_10,R2	; Address of 10 byte MODE SENSE command
100$:	BSBW	SETUP_CMD		; Perform setup for SCSI command

	MOVL	SCDRP$L_CMD_PTR(R5),R0	; Get address of command buffer
	MOVB	#^X7F,6(R0)		; Interested in changable parameters

	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBCW	R0,95$			; Branch on error

	; Given that we've asked for all supported pages, it would be unheard of 
	; for us to get back less than 8 bytes, so make this the minimum checked
	; for - this will get us well into the later sections where we're checking
	; for truncated pages. 

	CMPW	SCDRP$L_TRANS_CNT(R5),#8 ; Check for a reasonable minimum
	BLSSW	95$			 ; amount of data, branch if not

	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of mode sense data
	IF_RAID	110$
	MOVZBL	(R0),R1			; Get length byte of mode sense data
	BEQLW	95$			; Branch if none
	INCL	R1			; Account for length byte itself
	BRB	120$
110$:	MOVZWL	(R0),R1			; Get length word of mode sense data
	SWAB	R1			; Fix SCSI vs VAX endian byte order
	TSTL	R1			; Did we get data?
	BEQLW	95$			; Branch if none
	ADDL	#2,R1			; Account for length word itself
120$:	CMPL	R1,SCDRP$L_TRANS_CNT(R5); Mode sense data length may exceed 
					; actual length of data received
	BLEQ	10$			; Branch if not

	MOVL	SCDRP$L_TRANS_CNT(R5),R1; Use actual number of bytes received
	BEQLW	95$			; Branch if none

10$:	IF_RAID	130$
	MOVZBL	3(R0),R2		; Get total length of block descriptors
	ADDL	#4,R2			; Include length of mode sense header
	BRB	15$
130$:	MOVZWL	6(R0),R2		; Get total length of block descriptors
	SWAB	R2			; Fix SCSI vs VAX endian byte order
	ADDL	#8,R2			; Include length of mode sense header
15$:	ADDL	R2,R0			; Skip to first/next page of sense data
	SUBL	R2,R1			; Update count of mode sense data left
	BLEQW	70$			; Branch if not
	CMPW	R1,#2			; Page code and size bytes available?
	BLSSW	95$			; Branch if not

	EXTZV	#0,#6,(R0),R2		; Get page code
	DISPATCH R2,TYPE=B,<-		; Dispatch according to page code
		<PGC$ERR_RECOV,	20$>,- 
		<PGC$FMT_PARAM,	40$>,-
		<PGC$FLX_PARAM, 55$>,-
		<PGC$CTRL_MODE, 200$>>
;
; Unknown/unsupported mode sense page.
;
	BRW	60$			; Join common code

;
; control mode parameters
;
200$:	BBC	#UCB$V_MODE_SENSE_PAG10,- ; Branch if this is not a
		UCB$L_DK_FLAGS(R3),230$	  ; queuing device or if we have
					  ; no current value of the bits
					  ; we need to check.
	MOVZBL	1(R0),R2		; Get bytes in page
	ADDL2	#2,R2			; Allow for the page code/length bytes
	CMPL	#UCB_CTRL_MODE_SIZE,R2	; Check for too much data to save
	BLSS	225$			; If so, we can't deal with this
	CMPL	R1,R2			; Check whether we got all the data
	BGEQ	228$			; If not, we can't deal with this
225$:	BRW	95$			; Error
228$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Move bytes to UCB
	MOVC3	R2,(R0),UCB$B_CTRL_MODE_CPAR(R3)
	POPR	#^M<R0,R1,R2,R3,R4,R5>

	; Make sure the Enable Extended Contingent Alliegence and Queue Error
	; bits are either clear or changable.

	BBC	#CTRL_MODE$V_EECA,-		; Check current EECA bit 
		UCB$B_CTRL_MODE_PAR+4(R3),-
		229$
	BBC	#CTRL_MODE$V_EECA,-		; Check changable EECA bit
		UCB$B_CTRL_MODE_CPAR+4(R3),-
		225$
229$:	BBC	#CTRL_MODE$V_QERR,-		; Check current QERR bit
		UCB$B_CTRL_MODE_PAR+3(R3),-
		230$
	BBC	#CTRL_MODE$V_QERR,-		; Check changable QERR bit
		UCB$B_CTRL_MODE_CPAR+3(R3),-
		225$
230$:	BRW	60$			

;
; Error recovery parameters
;
20$:	MOVZBL	1(R0),R2		; Get bytes in page
	ADDL2	#2,R2			; Allow for the page code/length bytes
	CMPL	#UCB_REC_PARAM_SIZE,R2	; Check for too much data to save
	BLSS	25$			; If so, we can't deal with this
	CMPL	R1,R2			; Check whether we got all the data
	BGEQ	28$			; If not, we can't deal with this
25$:	BRW	95$			; Error
28$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Move bytes to UCB
	MOVC3	R2,(R0),UCB$B_RECOV_CPAR(R3)
	POPR	#^M<R0,R1,R2,R3,R4,R5>

; Workaround for a mode select bug in version B2 of the Maxtor RZ55 drive.

	CMPB	UCB$B_DEVTYPE(R3),-	; RZ55?
		#DT$_RZ55		;
	BNEQ	30$			; Branch if not
	CMPL	UCB$L_HW_REV(R3),-	; Rev B2?
		#^A'B2  '		;
	BNEQ	30$			; Branch if not
	MOVB	#^XFF,-			; Modify changable parameters to prevent
		UCB$B_RECOV_CPAR+7(R3)	; mode select error
30$:

; Now make sure that any of the error recovery parameters that don't have the
; correct default value are changable.

;
; TEMPORARY HACK FOR TOTO
;
	IF_RAID 60$

	XORB3	UCB$B_RECOV_PAR+2(R3),-	; Find those params whose default value
		#<ERR_RECOV$M_TB!-	; doesn't match the desired value
		  ERR_RECOV$M_PER>,R2	;
	BICB	#<ERR_RECOV$M_EEC!-	; Clear don't cares
		  ERR_RECOV$M_DCR!-	;
		  ERR_RECOV$M_DTE>,R2	;
	BICB	UCB$B_RECOV_CPAR+2(R3),-; Find any non-changable params which
		R2			; don't have the right default value
	BEQLW	60$			; Branch if none found
	BRW	95$			; Otherwise, log error and exit
;
; Format parameters
;
40$:	TSTL	UCB$A_FORMAT_PARAM(R3)	; Check whether storage available
	BEQL	49$			; No, don't bother with this page

	MOVZBL	1(R0),R2		; Get bytes in page
	ADDL2	#2,R2			; Allow for the page code/length bytes
	CMPL	#FMT_PARAM_SIZE,R2	; Check for too much data to save
	BLSS	45$			; If so, we can't deal with this
	CMPL	R1,R2			; Check whether we got all the data
	BGEQ	48$			; If not, we can't deal with this
45$:	BRW	95$			; Error
48$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Move bytes to UCB
	MOVL	UCB$A_FORMAT_PARAM(R3),R3 ; Point to storage
	MOVC3	R2,(R0),FMT$B_FMT_CPAR(R3)
	POPR	#^M<R0,R1,R2,R3,R4,R5>

49$:	BRB	60$			; Join common code
;
; Geometry parameters for flexible disks
;
55$:	TSTL	UCB$A_FORMAT_PARAM(R3)	; Check whether storage available
	BEQL	60$			; No, don't bother with this page

	MOVZBL	1(R0),R2		; Get bytes in page
	ADDL2	#2,R2			; Allow for the page code/length bytes
	CMPL	#FLX_PARAM_SIZE,R2	; Check for too much data to save
	BLSS	56$			; If so, we can't deal with this
	CMPL	R1,R2			; Check whether we got all the data
	BGEQ	58$			; If not, we can't deal with this
56$:	BRW	95$			; Error
58$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Move bytes to UCB
	MOVL	UCB$A_FORMAT_PARAM(R3),R3 ; Point to storage
	MOVC3	R2,(R0),FMT$B_FLX_CPAR(R3)
	POPR	#^M<R0,R1,R2,R3,R4,R5>
;
;	BRB	60$			; Fall into common code
;
60$:	MOVZBL	1(R0),R2		; Get length of current page
	ADDL	#2,R2			; Account for page code, length byte
	BRW	15$			; Go try next page

70$:	MOVZWL	#SS$_NORMAL,R0		; Set success status	

80$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	SUBRETURN			; Return to caller

95$:	LOG_ERROR -			; Log a mode sense data error
		TYPE=MODE_SENSE_DATA,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
	MOVL	#SS$_DRVERR,R0		; Set bad status
	BRB	80$			; Use common exit


	.SBTTL	MODE_SELECT	- Send a mode select command
;+
; MODE_SELECT
;
; This routine is called immediately after the MODE_SENSE routine to change
; any device characteristics which don't have the required default values such 
; as error recovery parameters and default block size. The settings required
; are:
;
;	- Block size of 512
;	- TB (transfer block flag) of 1 (transfer contents of block for which
;	  error occurred)
;	- PER (post error bit) of 1 (signal recoverable errors with check
;	  condition status)
;	
;
;       Since we only send 2 pages, there is no need to use 10 byte
;       mode select for RAID devices
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

	$DEFINI	ERROR_RECOVERY_FLAGS

	$VIELD	ERR_RECOV,0,<-
		<DCR,,M>,-		; Disable correction
		<DTE,,M>,-		; Disable transfer on error
		<PER,,M>,-		; Post error
		<EEC,,M>,-		; Enable early correction
		<RC,,M>,-		; Read continuous
		<TB,,M>,-		; Transfer block
		<ARRE,,M>,-		; Automatic read relocation of blocks
		<AWRE,,M>>		; Automatic write relocation of blocks

	$DEFEND	ERROR_RECOVERY_FLAGS

	CTRL_MODE$M_QERR = ^X02		; QErr bit in MODE PAGE 10
	CTRL_MODE$V_QERR =  1		; QErr bit in MODE PAGE 10
	CTRL_MODE$M_EECA = ^X80		; EECA bit in MODE PAGE 10
	CTRL_MODE$V_EECA =  7		; QErr bit in MODE PAGE 10
	

MODE_SELECT:

	SUBSAVE				; Save return address
	MOVL	#SS$_NORMAL,R0		; Assume success
	CMPW	UCB$W_BLOCK_SIZE(R3),-	; Correct default block size?
		#512			;
	BNEQ	10$			; Branch if not
	BICB3	#^C<ERR_RECOV$M_TB!-	; Get the error recovery flags of
		    ERR_RECOV$M_PER>,-	; don't cares or have already been
		UCB$B_RECOV_PAR+2(R3),- ; checked for zero values in 
		R1 			; MODE_SENSE)
	CMPB	#<ERR_RECOV$M_TB!-	; Are the transfer block, disable 
		  ERR_RECOV$M_PER>,-	; flags already set?
		R1			;
	BNEQ	10$			; Branch if not, we need to do mode select

	; SCSI-1 devices did BBR very badly, so disable it 

	CMPB	UCB$B_SCSI_VERSION(R3),#2 ; SCSI-2 or later device?

	BGEQ	3$			  ; Branch if so

	BITB	#<ERR_RECOV$M_ARRE!-	; If either of these are set,
		  ERR_RECOV$M_AWRE>,-	; try to clear them
		UCB$B_RECOV_PAR+2(R3)
	BNEQ	10$			; Br if we need to do the MODE_SELECT

3$:	BITB	#ERR_RECOV$M_RC,-	; If this is set,
		UCB$B_RECOV_PAR+2(R3)	; try to clear it
	BNEQ	10$			; Br if we need to do the MODE_SELECT
   	BBC	#UCB$V_MODE_SENSE_PAG10,- ; Br If we didn't get the CONTROL MODE page
		UCB$L_DK_FLAGS(R3),5$
	BBS	#CTRL_MODE$V_QERR,-	; Is the Qerr bit set?
		UCB$B_CTRL_MODE_PAR+3(R3),10$ ; Br if so, need to issue mode select
	BBS	#CTRL_MODE$V_EECA,-	; Is the EECA bit set?
		UCB$B_CTRL_MODE_PAR+4(R3),10$ ; Br if so, need to issue mode select
5$:	BRW	20$			; No need for mode_select
	
10$:	MOVAL	CMD_MODE_SELECT,R2	; Address of 6 byte mode select command
	BSBW	SETUP_CMD		; Perform setup for SCSI command

					; Compute the parameter list length
					; 4-byte header,
					; 8-byte block descriptor,
					; 2-byte rec page header + page length,
	MOVL	SCDRP$L_CMD_PTR(R5),R0	; Get address of command buffer
	ADDL3	#<4+8+2>,UCB$B_RECOV_PAR+1(R3),R1
	BBC	#UCB$V_MODE_SENSE_PAG10,- ; See if we need to send this page also
		UCB$L_DK_FLAGS(R3),7$	; br if not
	ADDL	UCB$B_CTRL_MODE_PAR+1(R3),R1 ; Add in page 10's length
	ADDL	#2,R1			; And add in page header length
7$:	MOVB	R1,8(R0)		; Set length in SCSI CDB
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of mode select buffer
	MOVB	#8,3(R0)		; Set block descriptor length
	CLRB	9(R0)			; Set block length (200 hex) high order byte
	MOVB	#2,10(R0)		; middle byte
	CLRB	11(R0)			; low order byte

	PUSHR	#^M<R0,R1,R4,R5,R6>
	MOVAB	12(R0),R0		; Point at the page
	MOVZBL	UCB$B_RECOV_PAR+1(R3),R5
					; Get number of bytes in the page

	BICW3	#^x00C0,UCB$B_RECOV_PAR(R3),(R0)+
					; Copy page code and page length,
					;   stripping reserved bits
					; (R0 left pointing to destination data)

	MOVAB	UCB$B_RECOV_PAR+2(R3),R6
					; Point at default data
	BISB	#<ERR_RECOV$M_TB! -	; Set the TB and PER flags in the
		  ERR_RECOV$M_PER>, -	;  recovery parameters page
		UCB$B_RECOV_PAR+2(R3)

	BBC	# ERR_RECOV$V_DTE,-		; Branch if the DTE bit is 
		 UCB$B_RECOV_CPAR+2(R3),8$	; not changable

	BICB	#ERR_RECOV$M_DTE,-		; Clear the DTE bit in the 
		UCB$B_RECOV_PAR+2(R3)		; Error Recovery Parameters page

8$:	CMPB	UCB$B_SCSI_VERSION(R3),#2 ; SCSI-2 or later device?
	
	BGEQ	9$			; Branch if so

	BICB	#<ERR_RECOV$M_ARRE!-	; Clear 'em
		  ERR_RECOV$M_AWRE>,-	;
		  UCB$B_RECOV_PAR+2(R3) ;

9$:	BICB	#ERR_RECOV$M_RC,-	;
		UCB$B_RECOV_PAR+2(R3)	;

	MOVAB	UCB$B_RECOV_CPAR+2(R3),R1
					; Point to changable bitmask


					; TGG0002
	CMPB	#1, UCB$B_SCSI_VERSION(R3) ; SCSI-1/CCS or SCSI 2?
	BLSS	13$			; SCSI-1/CCS requires changeable fields = 0

12$:	MCOMB	(R1)+,R4		; Dest <--  (new .and. changable)
	BICB3	R4,(R6)+,(R0)+
	SOBGTR	R5,12$			; Loop through all bytes in page
	BRB	14$			; Skip SCSI 2 path

13$:					; SCSI 2 requires changeable fields
					;  to not be modified at all
	MOVB	(R6)+,(R0)+		; So Just copy page data
	SOBGTR	R5,13$			; Loop through all bytes in page

14$:	BBS	#UCB$V_MODE_SENSE_PAG10,- ; Br If we got the CONTROL MODE page
		UCB$L_DK_FLAGS(R3),50$
    	BRW	100$			; Otherwise go do mode select
50$:	
;+
;       handle the control mode page and add it to the mode select data
;-
	MOVZBL	UCB$B_CTRL_MODE_PAR+1(R3),R5
					; Get number of bytes in the page

	BICW3	#^x00C0,UCB$B_CTRL_MODE_PAR(R3),(R0)+
					; Copy page code and page length,
					;   stripping reserved bits
					; (R0 left pointing to destination data)

	MOVAB	UCB$B_CTRL_MODE_PAR+2(R3),R6
					; Point at default data
	BICL	#CTRL_MODE$M_QERR,-	; Clear the Qerr bit
		UCB$B_CTRL_MODE_PAR+3(R3)	; 
	BICL	#CTRL_MODE$M_EECA,-	; No ECA either
		UCB$B_CTRL_MODE_PAR+4(R3)

	CMPB	#1, UCB$B_SCSI_VERSION(R3) ; SCSI-1/CCS or SCSI 2?
	BLSS	60$			; SCSI-1/CCS requires changeable fields = 0

	MOVAB	UCB$B_CTRL_MODE_CPAR+2(R3),R1
					; Point to changable bitmask

55$:	MCOMB	(R1)+,R4		; Dest <--  (new .and. changable)
	BICB3	R4,(R6)+,(R0)+
	SOBGTR	R5,55$			; Loop through all bytes in page
	BRB	100$			; Skip SCSI 2 path

60$:					; SCSI 2 requires changeable fields
					;  to not be modified at all
	MOVB	(R6)+,(R0)+		; So Just copy page data
	SOBGTR	R5,60$			; Loop through all bytes in page

100$:	POPR	#^M<R0,R1,R4,R5,R6>

	BSBW	SEND_COMMAND		; Send the command
	BLBC	R0,15$			; Branch on error

; Sending a mode select command to some drives can cause the next command
; to that drive to fail with UNIT ATTENTION / MODE PARAMETERS CHANGED status.
; Since we don't support multi-initiator mode, send the drive a request sense 
; command to clear out this state.

	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	BSBW	REQUEST_SENSE		; Send a request sense command 
	MOVL	R5,SCDRP$L_SCDRP_SAV2(R5) ; For TRANS_SENSE_KEY
	BSBW	TRANS_SENSE_KEY		; Translate the sense key. If there's
					; a real error we want to know about it

15$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	movl	#1,r0			; Force success regardless
20$:	SUBRETURN			; Return to caller


	.SBTTL	MODE_SELECT_FORMAT_FLOPPY - Send a mode select command
;+
; MODE_SELECT_FORMAT_FLOPPY
;
; This routine is called to change fields in the format parameters and the
; flexible disk parameter pages as necessary to conform to the requested
; format.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

;
; index into this table with irp$l_media-1
;
MODE_SELECT_FORMAT_TABLE:
	.LONG	MODE_SELECT_FORMAT_RX26_DD-MODE_SELECT_FORMAT_TABLE
	.LONG	MODE_SELECT_FORMAT_RX26_HD-MODE_SELECT_FORMAT_TABLE
	.LONG	MODE_SELECT_FORMAT_RX26_ED-MODE_SELECT_FORMAT_TABLE

;
; index into this table with irp$l_media-1
;
MODE_SELECT_FLEXIBLE_TABLE:
	.LONG	MODE_SELECT_FLEXIBLE_RX26_DD-MODE_SELECT_FLEXIBLE_TABLE
	.LONG	MODE_SELECT_FLEXIBLE_RX26_HD-MODE_SELECT_FLEXIBLE_TABLE
	.LONG	MODE_SELECT_FLEXIBLE_RX26_ED-MODE_SELECT_FLEXIBLE_TABLE

MODE_SELECT_FORMAT_RX26_HD:
MODE_SELECT_FORMAT_RX23_18:
	.BYTE	0,0		; tracks per zone
	.BYTE	0,0		; alt sectors per zone
	.BYTE	0,0		; alt tracks per zone
	.BYTE	0,0		; alt tracks per logical unit
	.BYTE	0,18		; sectors per track			ffh,ffh
	.BYTE	2,0		; bytes per sector
	.BYTE	0,1		; interleave
	.BYTE	0,0		; track skew
	.BYTE	0,0		; cylinder skew
	.BYTE	^xA0		; SSEC, HSEC, RMB, SURF, reserved_4	10h
	.BYTE	0,0,0		; reserved

MODE_SELECT_FLEXIBLE_RX26_HD:
MODE_SELECT_FLEXIBLE_RX23_18:
	.BYTE	^x01,^xF4	; transfer rate = 500 KHz		ffh,ffh
	.BYTE	2		; number of heads = 2			ffh
	.BYTE	18		; sectors per track = 18		ffh
	.BYTE	2,0		; data bytes per sector = 512
	.BYTE	0,80		; number of cylinders = 80		ffh,ffh
	.BYTE	0,80		; starting write precomp = 80
	.BYTE	0,80		; starting reduced write current = 80
	.BYTE	0,0		; step rate = default			ffh,ffh
	.BYTE	0		; step pulse width = default		
	.BYTE	0,0		; head settle delay = default		ffh,ffh
	.BYTE	5		; motor on delay = 1/2 Second		ffh
	.BYTE	30		; motor off delay = 3 Seconds		ffh
	.BYTE	^x40		; trdy=0, ssn=1, mo=0			60h
	.BYTE	0		; extra step pulses per cylinder = 0	1fh
	.BYTE	0		; write compensation level unsupported
	.BYTE	0		; head load delay unsupported
	.BYTE	0		; head unload delay unsupported
	.BYTE	^x23		; pin 34=2, pin 2=3
	.BYTE	^x00		; pin 4=0
	.BYTE	0,0,0,0		; reserved

MODE_SELECT_FORMAT_RX26_DD:
MODE_SELECT_FORMAT_RX23_9:
	.BYTE	0,0		; tracks per zone
	.BYTE	0,0		; alt sectors per zone
	.BYTE	0,0		; alt tracks per zone
	.BYTE	0,0		; alt tracks per logical unit
	.BYTE	0,9		; sectors per track			ffh,ffh
	.BYTE	2,0		; bytes per sector
	.BYTE	0,1		; interleave
	.BYTE	0,0		; track skew
	.BYTE	0,0		; cylinder skew
	.BYTE	^xA0		; SSEC, HSEC, RMB, SURF, reserved_4	10h
	.BYTE	0,0,0		; reserved

MODE_SELECT_FLEXIBLE_RX26_DD:
MODE_SELECT_FLEXIBLE_RX23_9:
	.BYTE	^x00,^xFA	; transfer rate = 250 KHz		ffh,ffh
	.BYTE	2		; number of heads = 2			ffh
	.BYTE	9		; sectors per track = 9			ffh
	.BYTE	2,0		; data bytes per sector = 512
	.BYTE	0,80		; number of cylinders = 80		ffh,ffh
	.BYTE	0,80		; starting write precomp = 80
	.BYTE	0,80		; starting reduced write current = 80
	.BYTE	0,0		; step rate = default			ffh,ffh
	.BYTE	0		; step pulse width = default		
	.BYTE	0,0		; head settle delay = default		ffh,ffh
	.BYTE	5		; motor on delay = 1/2 Second		ffh
	.BYTE	30		; motor off delay = 3 Seconds		ffh
	.BYTE	^x40		; trdy=0, ssn=1, mo=0			60h
	.BYTE	0		; extra step pulses per cylinder = 0	1fh
	.BYTE	0		; write compensation level unsupported
	.BYTE	0		; head load delay unsupported
	.BYTE	0		; head unload delay unsupported
	.BYTE	^x23		; pin 34=2, pin 2=3
	.BYTE	^x00		; pin 4=0
	.BYTE	0,0,0,0		; reserved

MODE_SELECT_FORMAT_RX33_15:
	.BYTE	0,0		; tracks per zone
	.BYTE	0,0		; alt sectors per zone
	.BYTE	0,0		; alt tracks per zone
	.BYTE	0,0		; alt tracks per logical unit
	.BYTE	0,15		; sectors per track
	.BYTE	2,0		; bytes per sector
	.BYTE	0,1		; interleave
	.BYTE	0,0		; track skew
	.BYTE	0,0		; cylinder skew
	.BYTE	^xA0		; SSEC, HSEC, RMB, SURF, reserved_4
	.BYTE	0,0,0		; reserved

MODE_SELECT_FLEXIBLE_RX33_15:
	.BYTE	^x01,^xF4	; transfer rate = 500 KHz
	.BYTE	2		; number of heads = 2
	.BYTE	15		; sectors per track = 15
	.BYTE	2,0		; data bytes per sector = 512
	.BYTE	0,80		; number of cylinders = 80
	.BYTE	0,80		; starting write precomp = 80
	.BYTE	0,80		; starting reduced write current = 80
	.BYTE	0,0		; step rate = default
	.BYTE	0		; step pulse width = default
	.BYTE	0,0		; head settle delay = default
	.BYTE	5		; motor on delay = 1/2 Second
	.BYTE	30		; motor off delay = 3 Seconds
	.BYTE	^x40		; trdy=0, ssn=1, mo=0
	.BYTE	0		; extra step pulses per cylinder = 0
	.BYTE	0		; write compensation level unsupported
	.BYTE	0		; head load delay unsupported
	.BYTE	0		; head unload delay unsupported
	.BYTE	^x23		; pin 34=2, pin 2=3
	.BYTE	^x00		; pin 4=0
	.BYTE	0,0,0,0		; reserved

MODE_SELECT_FORMAT_RX26_ED:
	.BYTE	0,0		; tracks per zone
	.BYTE	0,0		; alt sectors per zone
	.BYTE	0,0		; alt tracks per zone
	.BYTE	0,0		; alt tracks per logical unit
	.BYTE	0,36		; sectors per track			ffh,ffh
	.BYTE	2,0		; bytes per sector
	.BYTE	0,1		; interleave
	.BYTE	0,0		; track skew
	.BYTE	0,0		; cylinder skew
	.BYTE	^xA0		; SSEC, HSEC, RMB, SURF, reserved_4	10h
	.BYTE	0,0,0		; reserved

MODE_SELECT_FLEXIBLE_RX26_ED:
	.BYTE	^x03,^xE8	; transfer rate = 1000 KHz		ffh,ffh
	.BYTE	2		; number of heads = 2			ffh
	.BYTE	36		; sectors per track = 36		ffh
	.BYTE	2,0		; data bytes per sector = 512
	.BYTE	0,80		; number of cylinders = 80		ffh,ffh
	.BYTE	0,80		; starting write precomp = 80
	.BYTE	0,80		; starting reduced write current = 80
	.BYTE	0,0		; step rate = default			ffh,ffh
	.BYTE	0		; step pulse width = default		
	.BYTE	0,0		; head settle delay = default		ffh,ffh
	.BYTE	5		; motor on delay = 1/2 Second		ffh
	.BYTE	30		; motor off delay = 3 Seconds		ffh
	.BYTE	^x40		; trdy=0, ssn=1, mo=0			60h
	.BYTE	0		; extra step pulses per cylinder = 0	1fh
	.BYTE	0		; write compensation level unsupported
	.BYTE	0		; head load delay unsupported
	.BYTE	0		; head unload delay unsupported
	.BYTE	^x23		; pin 34=2, pin 2=3
	.BYTE	^x00		; pin 4=0
	.BYTE	0,0,0,0		; reserved

MODE_SELECT_FORMAT_FLOPPY:

	SUBSAVE				; Save return address
	MOVL	#SS$_NORMAL,R0		; Assume success

	MOVAL	CMD_MODE_SELECT,R2	; Address of mode select command
	BSBW	SETUP_CMD		; Perform setup for scsi command

	SUBPUSH	R7			; Save r7
	MOVL	UCB$A_FORMAT_PARAM(R3),R7
					; Point to storage for format data
	BNEQ	50$

	BUG_CHECK INCONSTATE,FATAL	; Should never attempt to format
					; if we don't have storage for the
					; information from mode_sense

50$:	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of mode select buffer

	CLRL	(R0)+			; Clear header:
					;   byte 0 - reserved,
					;   byte 1 - medium type (default),
					;   byte 2 - reserved,
					;   byte 3 - block descriptor length (0)

					; R0 now pointing to first page

	SUBPUSH	R6			; Save R6

					; Set up format parameters depending
					; on device:
					; RX33s -> 15 sectors
					; RX23s & /dens=double -> 18 sectors
					; RX23s & /dens=single & bypass
					;   -> 9 sectors
					; RX26 & /dens=DD -> 9 sectors
					; RX26 & /dens=HD -> 18 sectors
					; RX26 & /dens=ED -> 36 sectors

	MOVAB	MODE_SELECT_FORMAT_RX33_15,R6
	CMPB	#DT$_RX33S,-		; Check for RX33 drive
		UCB$B_DEVTYPE(R3)
	BEQL	70$			; Yep, use RX33 parameters

	MOVQ	R1,-(SP)		; Save registers R1 and R2
	MOVL	SCDRP$L_IRP(R5),R2	; Point to IRP
	SUBL3	#1,IRP$L_MEDIA(R2),R2	;   to get /density qualifier
					; (adjusting for index offset of one)
	MOVAL	MODE_SELECT_FORMAT_TABLE,R1
					; Point to table of addresses
	ADDL3	R1,(R1)[R2],R6		; Get address from table
	MOVQ	(SP)+,R1		; Restore registers

;
; Build the format parameters page
;
70$:	CMPW	#^XFFFF,FMT$B_FMT_CPAR+10(R7)
					; Can we change sectors per track?
	BNEQ	110$			; Error - can't change format
	MOVW	8(R6),FMT$B_FMT_PAR+10(R7)
					; New value for sectors/track

100$:	CMPB	#10,FMT$B_FMT_PAR+1(R7)	; Is the page big enough?
	BLEQU	120$			; Yes
110$:	BRW	1100$			; Error - can't change format

120$:	PUSHR	#^M<R1,R2,R4,R5>
	MOVZBL	FMT$B_FMT_PAR+1(R7),R5	; Get number of bytes in the page

	MOVW	FMT$B_FMT_PAR(R7),(R0)+	; Copy page code and page length
					; (R0 left pointing to destination data)

	MOVAB	FMT$B_FMT_CPAR+2(R7),R1	; Point to changable bitmask
	MOVAB	FMT$B_FMT_PAR+2(R7),R2	; Point to old data

150$:	MCOMB	(R1)+,R4		; Dest <-- (value .and. changable)
	BICB3	R4,(R2)+,(R0)+
	SOBGTR	R5,150$			; Loop through all bytes in page

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

					; R0 left pointing at next page
;
; Build the flexible disk geometry parameters page
;
					; Set up flexible geometry depending
					; on device:
					; RX33s -> 15 sectors
					; RX23s & /dens=double -> 18 sectors
					; RX23s & /dens=single & bypass
					;   -> 9 sectors
					; RX26 & /dens=DD -> 9 sectors
					; RX26 & /dens=HD -> 18 sectors
					; RX26 & /dens=ED -> 36 sectors

	MOVAB	MODE_SELECT_FLEXIBLE_RX33_15,R6
	CMPB	#DT$_RX33S,-
		UCB$B_DEVTYPE(R3)
	BEQL	170$

	MOVQ	R1,-(SP)		; Save registers R1 and R2
	MOVL	SCDRP$L_IRP(R5),R2	; Point to IRP
	SUBL3	#1,IRP$L_MEDIA(R2),R2	;   to get /density qualifier
					; (adjusting for index offset of one)
	MOVAL	MODE_SELECT_FLEXIBLE_TABLE,R1
					; Point to table of addresses
	ADDL3	R1,(R1)[R2],R6		; Get address from table
	MOVQ	(SP)+,R1		; Restore registers

170$:	CMPW	#^XFFFF,FMT$B_FLX_CPAR+2(R7)
					; Can we change transfer rate?
	BNEQ	210$			; Error - can't change
	MOVW	(R6),FMT$B_FLX_PAR+2(R7)
					; Use our transfer rate

	CMPB	#^XFF,FMT$B_FLX_CPAR+4(R7)
					; Can we change number of heads?
	BNEQ	210$			; Error - can't change
	MOVB	2(R6),FMT$B_FLX_PAR+4(R7)
					; Use our number of heads

	CMPB	#^XFF,FMT$B_FLX_CPAR+5(R7)
					; Can we change sectors per track?
	BNEQ	210$			; Error - can't change
	MOVB	3(R6),FMT$B_FLX_PAR+5(R7)
					; Use our sectors per track

	CMPW	#^XFFFF,FMT$B_FLX_CPAR+8(R7)
					; Can we change number of cylinders?
	BNEQ	210$			; Error - can't change
	MOVW	6(R6),FMT$B_FLX_PAR+8(R7)
					; Use our number of cylinders

	BBC	#6,FMT$B_FLX_CPAR+21(R7),210$
					; Can we change start sector number?
	BICB2	#^X40,FMT$B_FLX_PAR+21(R7)
	BISB2	19(R6),FMT$B_FLX_PAR+21(R7)
					; Use our start sector number

	CMPZV	#0,#4,FMT$B_FLX_CPAR+22(R7),#^xF
					; Can we change step pulses per cyl?
	BNEQ	210$			; Error - can't change
					; Use our step-pulse-per-cylinder
	BICB2	#^X0F,FMT$B_FLX_PAR+22(R7)
	BISB2	20(R6),FMT$B_FLX_PAR+22(R7)

200$:	CMPB	#21,FMT$B_FLX_PAR+1(R7)	; Is the page big enough?
	BLEQU	220$			; Yes
210$:	BRW	1100$			; Error - can't change format

220$:	PUSHR	#^M<R1,R2,R4,R5,R6>
	MOVZBL	FMT$B_FLX_PAR+1(R7),R5	; Get number of bytes in the page

	MOVW	FMT$B_FLX_PAR(R7),(R0)+	; Copy page code and page length
					; (R0 left pointing to destination data)

	MOVAB	FMT$B_FLX_CPAR+2(R7),R1	; Point to changable bitmask
	MOVAB	FMT$B_FLX_PAR+2(R7),R2	; Point to old data

250$:	MCOMB	(R1)+,R4		; Dest <-- (value .and. changable)
	BICB3	R4,(R2)+,(R0)+
	SOBGTR	R5,250$			; Loop through all bytes in page

	POPR	#^M<R1,R2,R4,R5,R6>
;
; Finish building and send the mode-select command
;
					; Compute the parameter list length
					; 4-byte header,
					; 2-byte fmt page header + page length,
					; 2-byte flx page header + page length
	MOVL	SCDRP$L_CMD_PTR(R5),R0	; Get address of command buffer
	ADDL3	#<4+2+2>,FMT$B_FMT_PAR+1(R7),R1
	ADDL3	FMT$B_FLX_PAR+1(R7),R1,R1
	MOVB	R1,8(R0)
	SUBPOP	R6			; Restore registers
	SUBPOP	R7
	BSBW	SEND_COMMAND		; Send the command
	BLBC	R0,900$			; Branch on error

	MOVL	#SS$_NORMAL,R0		; Set success status

900$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	SUBRETURN			; Return to caller

1100$:	SUBPOP	R6			; restore registers in error path
	SUBPOP	R7

	MOVL	#SS$_DRVERR,R0
	BRB	900$			; Join common exit code


	.SBTTL	FORMAT		- Send a format command
;+
; FORMAT
;
; This routine sends a format command to the target.
;
; INPUTS:
;
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

FORMAT:

	SUBSAVE				; Save return address
	MOVAL	CMD_FORMAT_UNIT,R2 	; Address of FORMAT command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	MOVL	#RX_FMT_DISC_TMO,-	; Increase the disconnect timeout
		SCDRP$L_DISCON_TIMEOUT(R5); value for the format operation
	BSBW	SEND_COMMAND		; Send the SCSI command
	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	SUBRETURN


	.SBTTL	READ_CAPACITY	- Send a read capacity command
;+
; READ_CAPACITY
;
; This routine reads the capacity of the target. It is called AFTER MODE_SENSE
; and MODE_SELECT so that a possible change of block size will be reflected in
; the device's block count. It then ensures that the geometry information
; returned by MODE_SENSE agrees with the device's capacity (the product of
; tracks, sectors, and cylinders must not exceed the maximum block number).
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

READ_CAPACITY:

	SUBSAVE				; Save return address
	MOVAL	CMD_READ_CAPACITY,R2	; Address of READ CAPACITY command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBC	R0,20$			; Branch on error
	MOVL	SCDRP$L_SVA_USER(R5),R1	; Get address of read capacity data
	MOVAL	UCB$L_MAXBLOCK+4(R3),R2	; Get address just beyond MAXBLOCK 
	.REPT	4			; Copy four bytes of max block data
	MOVB	(R1)+,-(R2)		; from SCSI buffer to UCB. Note that
	.ENDR				; bytes are reversed in SCSI buffer
	INCL	(R2)			; Convert max block # to total blocks
	MOVZBL	UCB$B_TRACKS(R3),R1	; Get number of tracks
	MOVZBL	UCB$B_SECTORS(R3),R0	; Get number of sectors
	MULL	R0,R1			; Multiply
	BEQL	30$			; Branch if tracks or sectors is zero
	MOVZWL	UCB$W_CYLINDERS(R3),R0	; Get number of cylinders
	MULL	R0,R1			; Get tracks * sectors * cylinders
	CMPL	R1,(R2)			; Tracks * sectors * cyl = maxblock?
	BNEQ	40$			; Branch if not
10$:	MOVL	#SS$_NORMAL,R0		; Set success status
20$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	SUBRETURN

; Either tracks or sectors field (or both) is zero. Set up dummy values 
; for these two fields.

	ASSUME	UCB$B_SECTORS+1 EQ UCB$B_TRACKS
30$:	MOVW	#^X604,-		; Fill in dummy values for sector
		UCB$B_SECTORS(R3)	; and track fields

; Here we've found that MAXBLOCK is not equal to tracks * sectors * cylinders.
; Calculate a new value for cylinders to ensure that the product is greater
; than maxblock. Use the same algorithm as DUDRIVER so that SCSI disks served 
; in a cluster appear to have the same geometry on every node.

40$:	MOVZBL	UCB$B_TRACKS(R3),R1	; Get number of tracks
	MOVZBL	UCB$B_SECTORS(R3),R0	; Get number of sectors
	MULL	R1,R0			; Multiply
	MOVL	UCB$L_MAXBLOCK(R3),R1	; Get number of blocks
	CLRL	R2			; Prepare for extended divide
	EDIV	R0,R1,R0,R1		; Calculate number of cylinders, remainder
; if we already bashed geom don't do it twice
	cmpw	ucb$b_sectors(r3),#^x4040
	beql	205$
	cmpl	r0,#65534		; Will cylinder count fit?
	bgtru	204$			; if not branch
205$:
	MOVW	R0,UCB$W_CYLINDERS(R3)	; Save number of cylinders
	TSTL	R1			; Zero remaineder?
	BEQL	10$			; Branch if so
	INCW	UCB$W_CYLINDERS(R3)	; Otherwise, increment cylinders
					; (tracks * sectors * cylinders must
					; be >= maxblock)
	BRB	10$			; Return with success status

; if 6x4xn geom fails, try a larger one once. Same one is used on alpha
; though that tries longer
204$:
	movw	#^x2020,ucb$b_sectors(r3) ;try 32 x 32 x n
	brw	40$

	.SBTTL	START_UNIT	- Send a start unit command
	.SBTTL	STOP_UNIT	- Send a stop unit command
;+
; START_UNIT
; STOP_UNIT
;
; This routine sends a start or stop command to the target to spin up or
; spin down the device, respectively.
;
; INPUTS:
;
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

START_UNIT:

	MOVAL	CMD_START_UNIT,R2	; Address of START UNIT command
	BRB	START_STOP_COMMON	; Use common path

STOP_UNIT:

	MOVAL	CMD_STOP_UNIT,R2	; Address of START UNIT command

START_STOP_COMMON:

	SUBSAVE				; Save return address
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	MOVL	SCDRP$L_CMD_PTR(R5),R0	; Get address of command buffer
	BSBW	SEND_COMMAND		; Send the SCSI command
	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	SUBRETURN


	.SBTTL	READ_WRITE	- Send a read or write command
;+
; READ_WRITE
;
; This routine sends either a SCSI read or write command to the target based on
; setting of the FUNC bit in the SCDRP. The LBN in the command is filled in
; from the MEDIA field and the block count is filled in from the BCNT. The
; pad count field is filled in with the number of additional bytes over BNCT 
; that must be transferred to make an integral number of blocks. Note that 
; SCSI disks transfer an integral number of blocks, while a user can specify
; request for a fraction of a block.
;
; INPUTS:
;
;	R2	- IPR address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

READ_WRITE:

        MOVL    SCDRP$L_MEDIA(R5),-     ; Save the current head position
                UCB$L_CUR_LBN(R3)       ;
	CMPL	SCDRP$L_MEDIA(R5),-	; Does the LBN specified require
		#EXTND_LBN_LIMIT	; an extended read or write command?
	BGTRU	EXTND_READ_WRITE	; Branch if so
	SUBSAVE				; Save return address
	MOVAL	CMD_WRITE,R2		; Assume this is write command
	BBC	#IRP$V_FUNC,-		; Branch if this is a write function
		SCDRP$W_STS(R5),10$	;
	MOVAL	CMD_READ,R2		; Get address of SCSI read command
10$:	BSBW	SETUP_CMD		; Set up the command
	CLRL	SCDRP$L_PAD_BCNT(R5)	; Assume no padding of last page required
	MOVL	SCDRP$L_BCNT(R5),R0	; Get byte count
	BICL3	#^C<^X1FF>,R0,R1	; Number of bytes in last block
	BEQL	20$			; Branch if block-integral transfer
	SUBL3	R1,#512,- 		; Number of bytes of padding required
		SCDRP$L_PAD_BCNT(R5)	; for block-integral transfer
20$:	ADDL2	#^X1FF,R0		; Round up byte count to page boundary
	ASHL	#-9,R0,R0		; Convert to block count
	ADDL3	#<4+4>,-		; Address of transfer length field in
		SCDRP$L_CMD_PTR(R5),R1	; SCSI command
	MOVB	R0,(R1)			; Copy block count to transfer length 
	MOVAL	SCDRP$L_MEDIA(R5),R0	; Get logical block number field in SCDRP
	.REPT	2
	MOVB	(R0)+,-(R1)		; Fill in logical block address
	.ENDR
	INSV	(R0),#0,#5,-(R1)	; High-order 5 bits of logical block addr
	BISB	#SCDRP$M_BUFFER_MAPPED,-; Remember the fact that a buffer
		SCDRP$L_SCSI_FLAGS(R5)	; has been mapped
	SPI$MAP_BUFFER			; Map the user buffer
	MOVL	#QCHAR$K_UNORDERED,R0	; Use simple queue tag
	BSBW	QUEUE_COMMAND		; Queue the SCSI command
	BICL	#SCDRP$M_S0BUF,-	; Don't deallocate the S0 "user" buffer
		SCDRP$L_SCSI_FLAGS(R5)	; at this time
	BSBW	CLEANUP_CMD		; Clean up from the current command
	SUBRETURN			; Return to caller


	.SBTTL	EXTND_READ_WRITE - Send an extended read or write command
;+
; EXTND_READ_WRITE
;
; This routine sends either a SCSI extended read or write command to the 
; target based on setting of the FUNC bit in the SCDRP. An extended command
; must be used when the LBN specified in the QIO won't fit in the LBN field
; of the normal read/write SCSI command. The LBN in the command is filled in
; from the MEDIA field and the block count is filled in from the BCNT. The
; pad count field is filled in with the number of additional bytes over BNCT 
; that must be transferred to make an integral number of blocks. Note that 
; SCSI disks transfer an integral number of blocks, while a user can specify
; request for a fraction of a block.
;
; INPUTS:
;
;	R2	- IPR address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

EXTND_READ_WRITE:

	SUBSAVE				; Save return address
	MOVAL	CMD_EXTND_WRITE,R2	; Assume this is an extended write command
	BBC	#IRP$V_FUNC,-		; Branch if this is a write function
		SCDRP$W_STS(R5),10$	;
	MOVAL	CMD_EXTND_READ,R2	; Get address of SCSI extended read command
10$:	BSBW	SETUP_CMD		; Set up the command
	CLRL	SCDRP$L_PAD_BCNT(R5)	; Assume no padding of last page required
	MOVL	SCDRP$L_BCNT(R5),R0	; Get byte count
	BICL3	#^C<^X1FF>,R0,R1	; Number of bytes in last block
	BEQL	20$			; Branch if block-integral transfer
	SUBL3	R1,#512,- 		; Number of bytes of padding required
		SCDRP$L_PAD_BCNT(R5)	; for block-integral transfer
20$:	ADDL2	#^X1FF,R0		; Round up byte count to page boundary
	ASHL	#-9,R0,R0		; Convert to block count
	ADDL3	#<4+8>,-		; Address of transfer length field in
		SCDRP$L_CMD_PTR(R5),R1	; SCSI command
	MOVB	R0,(R1)			; Copy block count to transfer length 
	MOVAL	SCDRP$L_MEDIA(R5),R0	; Get logical block number field in SCDRP
	SUBL	#2,R1			; Point just beyond LSB of LBN field in
					; SCSI command packet
	.REPT	4
	MOVB	(R0)+,-(R1)		; Fill in logical block address
	.ENDR
	BISB	#SCDRP$M_BUFFER_MAPPED,-; Remember the fact that a buffer
		SCDRP$L_SCSI_FLAGS(R5)	; has been mapped
	SPI$MAP_BUFFER			; Map the user buffer
	MOVL	#QCHAR$K_UNORDERED,R0	; Use simple queue tag
	BSBW	QUEUE_COMMAND		; Queue the SCSI command
	BICL	#SCDRP$M_S0BUF,-	; Don't deallocate the S0 "user" buffer
		SCDRP$L_SCSI_FLAGS(R5)	; at this time
	BSBW	CLEANUP_CMD		; Clean up from the current command
	SUBRETURN			; Return to caller


	.SBTTL	REASSIGN_BLOCK	- Send a reassign block command
;+
; REASSIGN_BLOCK
;
; This routine sends a reassign blocks command to a target to physically 
; relocate the data for a specific logical block on the disk. This is done 
; after a read operation fails with a recoverable error or a write operations 
; fails with either a recoverable or non-recoverable error. Read operations 
; with non-recoverable errors do not result in reassignment operations as this 
; could result in undetected user data corruption.
;
; INPUTS:
;
;	(R2)	- Block number to reassign
;	R3	- UCB address
;	R5	- SCDRP address of original read/write command
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;-

REASSIGN_BLOCK:

	SUBSAVE				; Save return address
	BBS	#DEV$V_SWL,-		; Branch if device is read-only,
		UCB$L_DEVCHAR(R3),10$	; not possible to reassign block

	BBS	#UCB$V_NOREASSIGN,-	; Branch if device doesn't support
		UCB$L_DK_FLAGS(R3),10$	; reassign_block command

	MOVAL	CMD_REASSIGN_BLOCKS,R2	; Address of reassign blocks command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	ADDL3	SCDRP$L_SVA_USER(R5),-	; Get defect list buffer address (and
		#3,R0			; advance to defect list length field)
	MOVB	#4,(R0)+		; Just one defective block being 
					; reassigned 
	MOVAL	SCDRP$L_MEDIA+4(R5),R1	; Get address beyond block number to 
					; reassign
	.REPT	4
	MOVB	-(R1),(R0)+		; Fill in a byte of LBN (note bytes are swapped)
	.ENDR
	LOG_ERROR -			; Log a reassign block error (if the
		TYPE=REASSIGN_BLOCK,-	; reassign fails, SEND_COMMAND will
		VMS_STATUS=#SS$_NORMAL,-; log another error).
		UCB=R3			;
	MOVL	#REASSIGN_DISCON_TMO,-	; Set up extended disconnect timeout
		SCDRP$L_DISCON_TIMEOUT(R5) ; for reassign
	BSBW	SEND_COMMAND		; Send the SCSI command
	BSBW	CLEANUP_CMD		; Cleanup from the command
	SUBRETURN			; Return to caller

10$:	BUG_CHECK INCONSTATE,FATAL	; Should never attempt to reassign a
					; block for a write-locked disk,
					; or for a drive which doesn't support
					; reassign_block command


	.SBTTL	+
	.SBTTL	+ UTILITY ROUTINES  
	.SBTTL	+
	.SBTTL	SET_UNIT_ONLINE	- Issue SCSI commands to bring unit online
;+
; SET_UNIT_ONLINE
;
; This routine attempts to bring a unit online. First, an inquiry command is 
; sent to the drive to ensure that it can communicate and that it's a valid
; disk-like device. If the INQUIRY fails, a fork thread is set up to 
; periodically poll the drive. 
;
; If the drive is the system disk, a PACKACK IRP is initiated for this unit
; to make the volume valid. The drive is then set online, and any I/O that had 
; been queued to the device while the decice had been busy or offline is 
; initiated.
;
; INPUTS:
;
;	R5	- UCB address
;
; OUTPUTS:
;
;	R0-R5	- Destroyed
;
;	ONLINE bit set in the UCB, BSY bit cleared
;-

SET_UNIT_ONLINE:
10$:	MOVL	UCB$L_PDT(R5),R4	; Get PDT address
	MOVL	R5,R3			; Copy UCB address
	BSBW	ALLOC_SCDRP		; Allocate an SCDRP
	BSBW	INQUIRY			; Send an inquiry command
	BLBC	R0,11$			; Branch on error
	BSBW	SET_CONN_CHAR		; Initial connection characteristics
11$:	BSBW	DEALLOC_SCDRP		; Release the SCDRP
	MOVL	R3,R5			; Copy UCB address
	BLBC	R0,20$			; Branch if error
	BISW	#UCB$M_ONLINE!-		; Set the device online, busy
		 UCB$M_BSY,-		;
		UCB$W_STS(R5)		;

; If the MSCP server is loaded, call its "new device" routine to allow this
; unit to be served.

	MOVL	G^SCS$GL_MSCP_NEWDEV,R2	; Get address of MSCP routine
	BGEQ	12$			; Branch if not available
	JSB	(R2)			; Make this unit available to be served

12$:	CMPL	G^SYS$AR_BOOTUCB,R5	; Is this the system disk?
	BNEQ	15$			; Branch if not
	BSBW	ALLOC_IRP		; Get an IRP
	MOVW	#IO$_PACKACK,-		; Store function code
		IRP$W_FUNC(R3)		; 
	BISW	#IRP$M_PHYSIO,-		; Set physical I/O bit
		IRP$W_STS(R3)		;
	ADAWI	#1,UCB$W_QLEN(R5)	; Increment the queue length
	MOVAL	13$,IRP$L_PID(R3)	; Set I/O completion return address
	JMP	G^IOC$INITIATE		; Start the I/O

13$:	BLBC	IRP$L_IOST1(R5),14$	; Branch if PACKACK failed
	MOVL	IRP$L_UCB(R5),R0	; Get UCB address
	BBSS	#UCB$V_LCL_VALID, -	; Set local valid bit and
		UCB$L_STS(R0),14$	; branch if its already set
	INCB	UCB$B_ONLCNT(R0)	; Increment online count.
14$:	BRW	DEALLOC_IRP		; Deallocate the PACKACK IRP

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

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

; We've tried and failed at least once to send an inquiry command. Clear the
; online and busy bits and set up a fork thread to poll the device once 
; a minute in an attempt to bring it online. In the meantime, any I/O queued to
; this device will fail with device offline status. Flush the queue of any I/O
; that might have been queued to the device during the period that it was
; online but busy. In the case of the system disk, don't set the unit offline
; and don't flush the queues as this could cause a boot to fail. Instead, just 
; poll and hope that eventually the system disk eventually comes back to life.

20$:	CMPL	G^SYS$AR_BOOTUCB,R5	; Is this the system disk?
	BNEQ	30$			; Branch if not
	DK_WAIT	#2			; Wait for 2 seconds
	BRW	10$			; And try again

30$:	BICW	#UCB$M_ONLINE!-		; Set the unit offline, not busy
		 UCB$M_BSY,-		;
		UCB$W_STS(R5)		;
40$:	REMQUE	@UCB$L_IOQFL(R5),R3	; Remove an IRP from the queue
	BVS	45$			; Branch if queue was empty
	INSQUE	(R3),-			; Place on end of flush queue
		@UCB$L_FLUSH_IOQBL(R5)	; 
	BRB	40$			; Repeat until I/O queue is empty
45$:	REMQUE	@UCB$L_FLUSH_IOQFL(R5),-; Remove an IRP from the flush queue
		R3			;
	BVS	50$			; Branch if queue was empty
	MOVL	R3,UCB$L_IRP(R5)	; Copy IRP addess to UCB
	MOVL	#SS$_DEVOFFLINE,R0	; Set device offline status
	JSB	G^IOC$REQCOM		; Complete the QIO
	BRB	45$			; Continue to flush queue
50$:	DK_WAIT	#60			; Wait for 60 seconds
	BRW	10$			; And try again


	.SBTTL	WAIT_UNIT_READY	- Wait for unit to become ready
;+
; WAIT_UNIT_READY
;
; This routine polls once every READY_POLL_INTERVAL seconds waiting for the
; drive to become ready (spun up). The first time a test unit ready fails with 
; DEVOFFLINE status (which comes from a NOT_READY status from the drive), a 
; start unit command is sent to the drive in case the failure was
; due to the drive being spun down. Polling continues for READY_POLL_INTERVAL *
; READ_POLL_CNT seconds or until the device returns success status to a to a 
; test unit ready command.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

WAIT_UNIT_READY:

	SUBSAVE				; Save return address
	MOVB	#READY_POLL_CNT,-	; Initialize retry count
		UCB$B_READY_RETRY(R3)	;
10$:	BSBW	TEST_UNIT_READY		; Execute TEST UNIT READY command
	BLBC	R0,30$			; Branch on error
20$:	SUBRETURN			; Return to caller

30$:	CMPL	R0,#SS$_DEVOFFLINE	; Is device spun down?
	BNEQ	40$			; Branch if not
	BBS	#UCB$V_FLOPPY,-		; Branch if this is a floppy, return
		UCB$L_DK_FLAGS(R3),50$	; immediately
	CMPB	#DC$_DISK,-		; Is this really a disk device?
		UCB$B_DEVCLASS(R3)	;
	BNEQ	20$			; Branch if not, can't issue start unit
	BBSS	#SCDRP$V_DISK_SPUN_UP,-	; Attempt to spin up the disk just
		SCDRP$L_SCSI_FLAGS(R5),-; once per PACKACK (branch if this
		40$			; has already been done)
	BSBW	START_UNIT		; Start up the drive
40$:	DK_WAIT	#READY_POLL_INTERVAL,-	; Wait a few seconds
		UCB=R3			;
	DECB	UCB$B_READY_RETRY(R3)	; Decrement retry count and try again
	BGTR	10$			; if appropriate
50$:	MOVZWL	#SS$_MEDOFL,R0		; Set bad status
	BRB	20$			; Return with error status


	.SBTTL	DATACHECK_CMP	- Compare user buffer with datacheck buffer
;+
; DATACHECK_CMP
;
; This routine is called by IO_DATACHECK and performs the actual comparison of 
; the data in the user buffer with the data in the datacheck buffer. In doing 
; this, the user's buffer is temporarily mapped into system space in order to 
; perform the CMPC3. The SPTEs used to double map the user buffer were allocate 
; when the driver was loaded and are shared between all units.
;
; INPUTS:
;
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status (SS$_NORMAL or SS$_DATACHECK)
;	All other registers preserved
;-

DATACHECK_CMP:

	MOVL	#SS$_DATACHECK,R0	; Assume datacheck error
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save regs
	ASSUME	DATACHECK_SPTE+4 EQ DATACHECK_SVA
	MOVQ	DATACHECK_SPTE,R0	; Get SVA of datacheck SPTEs and SVA
					; which these SPTEs map
	MOVL	SCDRP$L_SVAPTE(R5),R2	; SVAPTE of user buffer
	MOVZWL	SCDRP$W_BOFF(R5),R4	; Byte count
	ADDL2	SCDRP$L_BCNT(R5),R4	; Byte offset
	ADDL	#^X1FF,R4		; Round up to next page
	ASHL	#-9,R4,R4		; Convert to page count

10$:	MOVL	(R2)+,R3		; Get next user buffer SPTE
	BLSS	20$			; Branch if valid
	JSB	G^IOC$PTETOPFN		; Otherwise convert to valid PFN
20$:	INVALIDATE_TB R1,ENVIRON=LOCAL,-; Double map next page of user's buffer
		 INST1=<MOVL #<PTE$M_VALID!PTE$C_KW!PTE$C_KOWN>,(R0)>,-
		 INST2=<INSV R3,#PTE$V_PFN,#PTE$S_PFN,(R0)>
;				PUSHL	R2
;			MOVL	R1,R2
;			BLBC	G^MMG$GL_TBI_FLAGS,30267$
;			JSB	G^MMG$TBIS_PROLOG_SIMPLE
;		30267$:
;			MOVL #<PTE$M_VALID!PTE$C_KW!PTE$C_KOWN>,(R0)
;			INSV R3,#PTE$V_PFN,#PTE$S_PFN,(R0)
;			BLBC	G^MMG$GL_TBI_FLAGS,30268$
;				JSB	G^MMG$TBIS_EPILOG_SIMPLE
;			BRB	30269$
;		30268$:	MTPR	R2,S^#PR$_TBIS
; 		30269$:
;					POPL	R2
	ADDL	#^X200,R1		; Advance to next user buffer page
	ADDL	#4,R0			; Advance to next SPTE
	SOBGTR	R4,10$			; Repeat for entire user buffer

	MOVZWL	SCDRP$W_BOFF(R5),R0	; Get user buffer byte offset
	ADDL	DATACHECK_SVA,R0	; Address of double mapped user buffer
	CMPC3	SCDRP$L_BCNT(R5),(R0),-	; Compare user's buffer with datacheck
		@SCDRP$L_DATACHECK(R5)	; buffer
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Restore regs
	BNEQ	30$			; Branch if mismatch occurred
	MOVL	#SS$_NORMAL,R0		; Set success status
30$:	RSB				; Return to caller

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

DK_WAIT:

	MOVQ	R3,UCB$L_FR3(R5)	; Save R3 and R4 in fork block
	ADDL3	#2,(SP)+,UCB$L_FPC(R5)	; Save return address in fork block
	BISW	#UCB$M_TIM,UCB$W_STS(R5); Set timer expected bit
	ADDL3	(SP)+,G^EXE$GL_ABSTIM,-	; Set up timeout time in UCB
		UCB$L_DUETIM(R5)	;
	BICW	#UCB$M_TIMOUT,-		; Clear timer expired bit
		UCB$W_STS(R5)		;
	ENBINT				; Reenable interrupts
	RSB				; Return to caller's caller


	.SBTTL	ALLOC_IRP	- Allocate an I/O request packet
;+
; ALLOC_IRP
;
; This routine is called during unit init for the system disk to allocate 
; and IRP, which is used to force a PACKACK QIO through the driver.
;
; INPUTS:
;
;	R5	- UCB address
;
; OUTPUTS:
;
;	R3	- IRP address
;	R0-R2	- Destroyed
;	All other registers preserved
;-

ALLOC_IRP:

	MOVZWL	#IRP$C_LENGTH,R1	; Load length of IRP
	JSB	G^EXE$ALONONPAGED	; Allocate a block
	BLBC	R0,10$			; Branch if error
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save regs
	MOVC5	#0,.,#0,R1,(R2)		; Initialize the packet
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Save regs
	MOVL	R2,R3			; Set up IRP pointer
	MOVB	#DYN$C_IRP,-		; Store type
		IRP$B_TYPE(R3)		;
	MOVW	#IRP$C_LENGTH,-		; Store size
		IRP$W_SIZE(R3)		;
	MOVL	R5,IRP$L_UCB(R3)	; Set up UCB address
	MOVAL	W^DEALLOC_IRP,-		; Store deallocation routine address
		IRP$L_PID(R3)		;
	RSB
10$:
	BUG_CHECK INCONSTATE, FATAL



	.SBTTL	DEALLOC_IRP	- Deallocate an I/O request packet
;+
; DEALLOC_IRP
;
; The routine deallocates the IPR allocated during unit init to queue a PACKACK
; to the system disk.
; 
; INPUTS:
;
;	R5 	- IRP address
;
; OUTPUTS:
;
;	R0	- Destroyed
;	All other registers preserved
;-

DEALLOC_IRP:

	MOVL	IRP$L_UCB(R5),R0	; Get UCB address
	ADAWI	#-1,UCB$W_QLEN(R0)	; Adjust queue length
	MOVL	R5,R0			; Copy IRP address
	JSB     G^EXE$DEANONPAGED       ; DEALLOCATE IRPE
	RSB


	.SBTTL	SEND_COMMAND	- Send a SCSI command
;+
; SEND_COMMAND
;
; This routines sends a command to the SCSI device. It returns any failing
; port status to the caller. If the port status is success, it checks the
; SCSI status byte. If a check condition status is returned, a request 
; sense command is sent to the target and the sense key is translated into a 
; VMS status code, which is returned as status.
;
; If the target returns a hardware error, one retry is performed, on the 
; assumption that the error could have been caused by vibration or some other 
; spurious event.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

SEND_COMMAND:

	SUBSAVE				; Save return address
	MOVB	#HWERR_RETRY_CNT,-	; Initialize hardware error retry count
		UCB$B_HWERR_RETRY(R3)	;

SEND_COMMAND_RETRY:

	SPI$SEND_COMMAND		; Send the SCSI command
	BLBC	R0,20$			; Branch on error
	ASSUME	SCSI$C_GOOD_STATUS EQ 0
	MOVZBL	@SCDRP$L_STS_PTR(R5),R1	; Get SCSI status byte
	BICB	#^XC1,R1		; Clear reserved, vendor unique bits
	BNEQ	30$			; Branch if bad status
	CLRL	UCB$L_BUSY_TIME(R3)	; Device is not busy
10$:	SUBRETURN			; Return to caller

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

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

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

30$:	CMPB	#SCSI$C_BUSY,R1		; Busy status?
	BEQL	35$			; Branch if so
	CLRL	UCB$L_BUSY_TIME(R3)	; Otherwise, indicate device is not busy
	CMPB	#SCSI$C_CHK_CONDITION,R1; Check condition status?
	BEQL	40$			; Branch if so
31$:	LOG_ERROR -			; Log a send command error
		TYPE=SEND_CMD_ERROR,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
32$:	MOVL	#SS$_MEDOFL,R0		; Return a generic status code
	BRW	10$

; The device has returned busy status. In order to catch the case in which
; the device continually returns busy status, set a limit on the time a device
; can continuously return busy status. If the doesn't successfully execute
; a command or return a status other than busy within this time, assume
; the device is hung and pull on bus reset.

35$:	MOVL	UCB$L_BUSY_TIME(R3),R0	; Was device busy last time through?
	BNEQ	36$			; Branch if so
	ADDL3	#MAX_BUSY_TIME,-	; Calculate busy timeout time
		G^EXE$GL_ABSTIM,-	; 
		UCB$L_BUSY_TIME(R3)	;
	BRB	31$			; Log an error and exit

36$:	CMPL	R0,G^EXE$GL_ABSTIM	; Had device been busy for an excessive
					; amount of time
	BGTRU	32$			; Branch if not
	CLRL	UCB$L_BUSY_TIME(R3)	; Indicate device no longer busy
	SPI$RESET			; Reset the SCSI bus to attempt to
					; bring the device back to life
	BRB	32$			; Use common exit

; A check condition status code was returned. Save the original SCDRP address
; allocate a second one and send a request sense command. If the request 
; sense succeeds, translate the sense key to a VMS status code and return that
; as the status code for the original command.
	
40$:	CLRB	SCDRP$B_SENSE_KEY(R5)	; Initialize saved sense key field
	MOVL	R5,R2			; Save this SCDRP address
	BSBW	ALLOC_SCDRP		; Allocate an additional SCDRP
	MOVL	R2,SCDRP$L_SCDRP_SAV2(R5) ; Save original SCDRP address
	BSBW	REQUEST_SENSE		; Send a request sense command 
	BLBC	R0,50$			; Branch on error
	BSBW	LOG_EXTND_SENSE		; Log an extended sense error
	BSBW	SAVE_ADDNL_INFO		; Save any valid additional information
					; from the extended sense data
	BSBW	TRANS_SENSE_KEY		; Translate the extended sense key to 
					; a VMS status code in R0
50$:	BSBW	CLEANUP_CMD		; Clean up the request sense command
	MOVL	SCDRP$L_SCDRP_SAV2(R5),R2 ; Get original SCDRP address
	BSBW	DEALLOC_SCDRP		; Deallocate the request sense SCDRP
	MOVL	R2,R5			; Restore original SCDRP address
	MOVL	R5,UCB$L_SCDRP(R3)	; Copy it to the UCB
	TSTB	SCDRP$B_SENSE_KEY(R5)	; Sense key of 0?
	BEQL	55$			; Branch to retry command if so
	CMPB	SCDRP$B_SENSE_KEY(R5),-	; Device (aborted command) error?
		#SCSI$C_ABORTED_COMMAND	;
	BEQL	55$			; Branch if so, retry command
	CMPB	SCDRP$B_SENSE_KEY(R5),-	; Device (hardware) error?
		#SCSI$C_HARDWARE_ERROR	;
	BNEQ	60$			; Branch if not, no need to retry
55$:	DECB	UCB$B_HWERR_RETRY(R3)	; Decrement hardware retry count
	BGEQW	SEND_COMMAND_RETRY	; Branch if retry not exhausted, retry
					; the send command
60$:	BRW	10$			; Otherwise, return to caller


	.SBTTL	QUEUE_COMMAND	- Queue a SCSI command
;+
; QUEUE_COMMAND
;
; This routines queues a command (QCHAR$K_UNORDERED) to the SCSI device. 
; It returns any failing port status to the caller. If the port status is 
; success, it checks the SCSI status byte. If a check condition status is 
; returned, a request sense command is sent as a QCHAR$K_ERROR_RECOVERY 
; command to the target and the sense key is translated into a VMS status 
; code, which is returned as status.
;
; If the target returns a hardware error, one retry is performed, on the 
; assumption that the error could have been caused by vibration or some other 
; spurious event.
;
; INPUTS:
;
;	R0	- Queue characteristic
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

QUEUE_COMMAND:

	SUBSAVE				; Save return address
	MOVB	#HWERR_RETRY_CNT,-	; Initialize hardware error retry count
		UCB$B_HWERR_RETRY(R3)	;

QUEUE_COMMAND_RETRY:

	SPI$QUEUE_COMMAND QCHAR=R0	; Queue the SCSI command
	BLBC	R0,20$			; Branch on error
	ASSUME	SCSI$C_GOOD_STATUS EQ 0
	MOVZBL	@SCDRP$L_STS_PTR(R5),R1	; Get SCSI status byte
	BICB	#^XC1,R1		; Clear reserved, vendor unique bits
	BNEQ	30$			; Branch if bad status
	CLRL	UCB$L_BUSY_TIME(R3)	; Device is not busy
10$:	SUBRETURN			; Return to caller

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

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

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

30$:	CMPB	#SCSI$C_BUSY,R1		; Busy status?
	BEQL	35$			; Branch if so
	CLRL	UCB$L_BUSY_TIME(R3)	; Otherwise, indicate device is not busy
	CMPB	#SCSI$C_CHK_CONDITION,R1; Check condition status?
	BEQL	40$			; Branch if so
31$:	LOG_ERROR -			; Log a send command error
		TYPE=SEND_CMD_ERROR,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
32$:	MOVL	#SS$_MEDOFL,R0		; Return a generic status code
	BRW	10$

; The device has returned busy status. In order to catch the case in which
; the device continually returns busy status, set a limit on the time a device
; can continuously return busy status. If the doesn't successfully execute
; a command or return a status other than busy within this time, assume
; the device is hung and pull on bus reset.

35$:	MOVL	UCB$L_BUSY_TIME(R3),R0	; Was device busy last time through?
	BNEQ	36$			; Branch if so
	ADDL3	#MAX_BUSY_TIME,-	; Calculate busy timeout time
		G^EXE$GL_ABSTIM,-	; 
		UCB$L_BUSY_TIME(R3)	;
	BRB	31$			; Log an error and exit

36$:	CMPL	R0,G^EXE$GL_ABSTIM	; Had device been busy for an excessive
					; amount of time
	BGTRU	32$			; Branch if not
	CLRL	UCB$L_BUSY_TIME(R3)	; Indicate device no longer busy
	SPI$RESET			; Reset the SCSI bus to attempt to
					; bring the device back to life
	BRB	32$			; Use common exit

; A check condition status code was returned. Save the original SCDRP address
; allocate a second one and send a request sense command. If the request 
; sense succeeds, translate the sense key to a VMS status code and return that
; as the status code for the original command.
	
40$:	CLRB	SCDRP$B_SENSE_KEY(R5)	; Initialize saved sense key field
	MOVL	R5,R2			; Save this SCDRP address
	BSBW	ALLOC_SCDRP		; Allocate an additional SCDRP
	MOVL	R2,SCDRP$L_SCDRP_SAV2(R5) ; Save original SCDRP address
	BSBW	REQUEST_SENSE		; Send a request sense command 
	BLBC	R0,50$			; Branch on error
	BSBW	LOG_EXTND_SENSE		; Log an extended sense error
	BSBW	SAVE_ADDNL_INFO		; Save any valid additional information
					; from the extended sense data
	BSBW	TRANS_SENSE_KEY		; Translate the extended sense key to 
					; a VMS status code in R0
50$:	BSBW	CLEANUP_CMD		; Clean up the request sense command
	MOVL	SCDRP$L_SCDRP_SAV2(R5),R2 ; Get original SCDRP address
	BSBW	DEALLOC_SCDRP		; Deallocate the request sense SCDRP
	MOVL	R2,R5			; Restore original SCDRP address
	MOVL	R5,UCB$L_SCDRP(R3)	; Copy it to the UCB
	TSTB	SCDRP$B_SENSE_KEY(R5)	; Sense key of 0?
	BEQL	55$			; Branch to retry command if so
	CMPB	SCDRP$B_SENSE_KEY(R5),-	; Device (aborted command) error?
		#SCSI$C_ABORTED_COMMAND	;
	BEQL	55$			; Branch if so, retry command
	CMPB	SCDRP$B_SENSE_KEY(R5),-	; Device (hardware) error?
		#SCSI$C_HARDWARE_ERROR	;
	BNEQ	60$			; Branch if not, no need to retry
55$:	DECB	UCB$B_HWERR_RETRY(R3)	; Decrement hardware retry count
	BLSS	60$     		; Exit if count exhausted
	MOVW	SCDRP$W_QUEUE_CHAR(R5),R0; Restore QCHAR in R0 for SPI$QUEUE_CMD
	BRW	QUEUE_COMMAND_RETRY	; Branch if retry not exhausted, retry
					; the send command
60$:	BRW	10$			; Otherwise, return to caller


	.SBTTL	LOG_EXTND_SENSE	- Log an extended sense data error
;+
; LOG_EXTND_SENSE
;
; This routine logs an extended sense data error.
;
; Certain errors are suppressed including:
;
; - UNIT ATTENTIONS caused by media changes.
;
; - The first UNIT ATTENTION seen after a boot but before a successful PACKACK 
;   has completed. This unit attention is generally due to a bus reset having 
;   been issued during a boot or crash. IO_PACKACK sets the ATTN_SEEN flag so 
;   that any unit attentions seen after the first successful PACKACK will 
;   generate errors. 
;
; - A NOT READY sense key if the original command was a test unit ready. This 
;   is considered normal as it's likely the disk is in the process of being 
;   spun up.
;
; - A recoverable error if the addition sense code is RECOVERED ERROR WITH READ 
;   RETRIES. This can occur due to disk vibrations and is thus considered 
;   acceptable.
;
; Before logging the error, play a trick with the two SCDRPs currently in use.
; Since we're really interested in logging the contents of the original command,
; and not the request sense command, copy the address of the original command
; buffer into the SCDRP for the request sense command. This causes the contents
; of the original command (a read or write, for example) to be logged along 
; with the contents of the request sense data returned from the request sense 
; command.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address for REQUEST_SENSE command
;
;	SCDRP$L_SCDRP_SAV2(R5)	- Address of SCDRP for original command
;
; OUTPUTS:
;
;	R0-R2	- Destroyed
;	All other registers preserved
;-
                        
LOG_EXTND_SENSE:

	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of extended sense data
	EXTZV	#0,#4,2(R0),R1		; Get sense key
	CMPB	R1,-			; Unit attention sense key?
		#SCSI$C_UNIT_ATTENTION 	;
	BNEQ	10$			; Branch if not
	CMPB    #SCSI$C_MEDIA_CHANGE,-  ; Unit attention caused by media change?
                12(R0)                  ;
	BEQL	40$			; Branch if so, don't log an error
	BBCS    #UCB$V_FIRST_ATTN_SEEN,-; Branch if this is the first unit
                UCB$L_DK_FLAGS(R3),40$	; attention seen. Don't log an error

; Log all recoverable errors for floppies. Since errors tend to grow on
; floppies, we want to alert the operator as soon as possible.

10$:	BBS	#UCB$V_FLOPPY,-		; Branch if this is a floppy, return
		UCB$L_DK_FLAGS(R3),15$	; immediately

	CMPL	R1,-			; Recovered error sense key?
		#SCSI$C_RECOVERED_ERROR	;
	BNEQ	15$			; Branch if not
	CMPB	#SCSI$C_READ_RETRIES,-	; Recovered error with read retries?
		12(R0)			;
	BEQL	40$			; Branch if so, dont log an error
	brw	40$			; always ignore recovered err
;
; For CDROM devices, don't log commands that fail with a sense key of 
; Illegal Command, if original QIO is a READ command (readvblk).
; This will prevent the driver from logging errors for reads 
; of the home block on cdrom disks mounted foreign.
;
15$:	BBC	#UCB$V_CDROM,-		; Is this device a CD-ROM drive?
		 UCB$L_DK_FLAGS(R3),19$	; If NOT then proceed.
	CMPL	R1,-			; Illegal command sense key
		#SCSI$C_ILLEGAL_REQUEST	; Returned for reads of audio tracks
	BNEQ	19$			; Branch if not
;
; Look up the orginal commands SCDRP and determine whether or not this 
; SCDRP is for a READPBLK QIO. If the command is then this is final condition
; in determining whether or not to log an error.
;
	PUSHL	R0
	MOVL	SCDRP$L_SCDRP_SAV2(R5),R0 ; Get original command's SCDRP address
	CMPB	#IO$_READPBLK,-		; Determine whether the original I/O
		 SCDRP$W_FUNC(R0)	; was for a READ QIO request?
	BNEQ	17$			; If not continue on,
	POPL	R0			; Otherwise, don't log this error
	BRB	40$			; Branch if so, dont log an error
;
; Restore register R0 and continue on determining whether other errors 
; need to be loggged.
;
17$:	POPL	R0

19$:	PUSHL	SCDRP$L_CMD_PTR(R5)	; Save address of request sense cmd
	MOVL	SCDRP$L_SCDRP_SAV2(R5),R0 ; Get original command's SCDRP address
	MOVL	SCDRP$L_CMD_PTR(R0),-	; Copy address of original command to
		SCDRP$L_CMD_PTR(R5)	; request sense's SCDRP
	MOVB	#SCSI$C_CHK_CONDITION,-	; Original command failed with request
		@SCDRP$L_STS_PTR(R5)	; sense status

; Don't log errors with NOT_READY sense keys.

	CMPB	R1,#SCSI$C_NOT_READY	; Device not ready sense key?
	BEQL	30$			; Yes, don't log an error

	LOG_ERROR -			; Log an extended sense data error
		TYPE=EXTND_SENSE_DATA,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
30$:	POPL	SCDRP$L_CMD_PTR(R5)	; Restore address of request sense cmd
40$:	RSB				; Return to caller


	.SBTTL	TRANS_SENSE_KEY	- Translate extended sense key to VMS status code
;+
; TRANS_SENSE_KEY
;
; This routine translates an extended sense key to a VMS status code using
; SENSE_KEY_TABLE. If the sense key is not found in the table (indicating an
; invalid sense key), then a generic VMS status code (SS$_MEDOFL) is returned.
;
; Certain situations cause an additional translation including:
;
; - Recoverable errors with read retries are considered acceptable. Thus, if
;   this combination of sense key and additional sense code occurs, change the
;   VMS status to SS$_NORMAL.
;
; - If the sense key was NOT_READY and the spinup-in-progress flag is set in
;   the UCB, then this is an indication that the device is in the process of
;   being spun up. Return a DEVOFFLINE status to allow the WAIT_UNIT_READY
;   routine to continue to poll for spinup complete. Otherwise, change the
;   status to SS$_MEDOFL to cause mount verification to occur for this device.
;   In either case clear the CD_VALID bit to indicate any UCB stored media 
;   specific data is no longer valid.
;
; INPUTS:
;
;	R5	- SCDRP address - of REQUEST SENSE SCDRP
;
; OUTPUTS:
;
;	R0	- VMS status code
;	R1,R2	- Destroyed
;	All other registers perserved
;-

TRANS_SENSE_KEY:

	MOVL	SCDRP$L_SVA_USER(R5),R2	; Get address of request sense data
	EXTZV	#0,#4,2(R2),R1		; Get the sense key
	MOVL	SCDRP$L_SCDRP_SAV2(R5),R0 ; Get original SCDRP
	MOVB	R1,SCDRP$B_SENSE_KEY(R0)  ; Save sense key in it

; If this is a UNIT ATTENTION sense key, a media change additional sense code,
; the device is NOT mounted, and the "spinup-in-progress" bit is set, then 
; return success. This situation can occur if a piece of removable media, such 
; as a floppy, is changed in between mounts.

	CMPB	R1,-			; Unit attention sense key?
		#SCSI$C_UNIT_ATTENTION 	;
	BNEQ	5$			; Branch if not
	CMPB    #SCSI$C_MOD_SEL_CHANGE,-; Unit attention caused by mode select
                12(R2)                  ; parameters change?
	BEQL	3$			; Branch if so
	CMPB    #SCSI$C_MEDIA_CHANGE,-  ; Unit attention caused by media change?
                12(R2)                  ;
	BNEQ	5$			; Branch if not
	BBS     #DEV$V_MNT,-            ; Branch if device is mounted
                UCB$L_DEVCHAR(R3),5$	;
	BBC	#UCB$V_SPINUP_INPROG,-	; Branch if NOT waiting for unit ready
		UCB$L_DK_FLAGS(R3),5$	;
3$:	MOVZWL	#SS$_NORMAL,R0		; Set success status
	BRB	40$			; Use common exit path

5$:	MOVAL	SENSE_KEY_TABLE,R0	; Get address of sense key to VMS status
					; code translation table
10$:	CMPB	(R0),#^XFF		; End of table?
	BEQL	30$			; Branch of so
	CMPB	(R0)+,R1		; Sense keys match?
	BEQL	20$			; Branch if so
	ADDL	#2,R0			; Skip VMS error code
	BRB	10$			; Try next sense key
20$:	MOVZWL	(R0),R0			; Pick up VMS error code

; If this is a recoverable error due to read retries, then treat the error
; as success.

	CMPL	R0,#SS$_RECOVERR	; Recoverable error?
	BNEQ	35$			; Branch if not
	CMPB	UCB$B_DEVTYPE(R3),-	; If this is an RZ55 then
 		#DT$_RZ55		; perform the required workaround.
	BNEQ	12$			; If not a RZ55, continue.
	BSBW	RZ55_WORKAROUND		;
; Following was above the devtype check for rz55
; Now we ignore any recoverable error
;	CMPB	12(R2),-		; Recoverable error with read retries?
;		#SCSI$C_READ_RETRIES	; 
;	BNEQ	35$			; Branch if not
12$:	MOVZWL	#SS$_NORMAL,R0		; Treat this error as success

; The following code allows allows automatic spin-up of disks to work. If
; a NOT_READY SCSI status is returned by the drive, and the disk spinup is
; in progress, then return DEVOFFLINE status to notify the caller that the
; device is still not fully spun up. Otherwise, return MEDOFL status to 
; cause mount verification to kick in.

35$:	CMPL	R0,#SS$_DEVOFFLINE	; Device offline status?
	BNEQ	40$			; Branch if not
	BBS	#UCB$V_SPINUP_INPROG,-	; Branch if spinup is in progress
		UCB$L_DK_FLAGS(R3),40$	;
30$:	MOVZWL	#SS$_MEDOFL,R0		; Otherwise, set medium offline status
40$:	RSB				; Return to caller


	.SBTTL	SAVE_ADDNL_INFO	- Save additional extended sense information
;+
; SAVE_ADDNL_INFO
;
; This routine is called whenever valid extended sense data is returned by a
; target to obtain any addition data. If a recoverable or non-recoverable error
; occurred, the additional data specifies the the failing LBN, which is saved 
; in the UCB. Higher level routines (READPBLK, WRITEPBLK) use this information 
; later to reassign a block if appropriate.
;
; The addition data is saved in the ORIGINAL SCDRP pointed to by
; SCDRP$L_SCDRP_SAV2(R5) (R5 has REQUEST_SENSE SCDRP). 
;
; INPUTS:
;
; 	R3	- UCB address
;	R5	- SCDRP address associated with request sense command
;
;	SCDRP$L_SCDRP_SAV2(R5)	- Address of original SCDRP
;
; OUTPUTS:
;
;	R0,R1	- Destroyed
;
;	SCDRP$B_ADDNL_INFO	- Additional info field from the extended
;				  sense data or -1 if no valid data.
;	UCB$B_SENSE_INFO	- Balance of sense info returned by target.
;	UCB$B_SENSE_LEN		- Additional info length field set to number of
;				  additional sense data bytes.
;-
	.ENABLE		LSB

SAVE_ADDNL_INFO:
	MOVL	SCDRP$L_SCDRP_SAV2(R5),R0 ; Get original SCDRP address
	MOVAL	SCDRP$L_ADDNL_INFO(R0),R1 ; Get address of additional info field
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of request sense data
	MOVL	#-1,(R1)		; Assume additonal info not valid
	TSTB	(R0)			; Valid additional info?
	BGEQ	30$			; Branch if not
	MOVAL	7(R0),R0		; Get address just beyond additonal 
					; info field
	.REPT	4
	MOVB	-(R0),(R1)+		; Copy a byte of additional info from 
					; the extended sense data to the UCB. 
					; (Note that bytes are reversed in the 
					; extended sense data).
	.ENDR
;+
; The code above copies the first four bytes of additional sense data, if the
; valid bit in byte 1 is set. This code segment copies the balanace of the 
; additional sense data bytes returned by the target to the UCB. These
; additional bytes are used by the audio portion of the driver, but can be
; used for any device type.
;-
30$:	PUSHL	R7			; Save scratch register
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of request sense data
	MOVAB	UCB$B_SENSE_INFO(R3),R1	; Get address of additional sense field
	MOVZBL	7(R0),R7		; Get count of addl sense bytes
	CMPB	R7,#<SENSE_LEN-8>	; Is the count <= the size of buffer?
	BLEQU	40$			; Yes, use this count
	MOVZBL	#<SENSE_LEN-8>,R7
40$:	MOVAB	8(R0),R0		; Get address just beyond Add Sense Len
	MOVB	R7,UCB$B_SENSE_LEN(R3)	; Save count of actual number of bytes
					;  saved in UCB

COPY_BYTE:
	MOVB	(R0)+,(R1)+		; Copy a byte of additional info from 
					; the extended sense data to the UCB. 
	SOBGTR	R7,COPY_BYTE		; Copy all Addl sense bytes
	POPL	R7			; Restore scratch register.

	RSB
	.DISABLE	LSB


	.SBTTL	ALLOC_SCDRP	- Allocate an SCDRP
;+
; ALLOC_SCDRP
;
; This routine allocates an SCDRP by attempting to remove one from the queue 
; in the UCB. If the queue is empty we allocate a new SCDRP from pool on
; the fly.
;
; The entire SCDRP is zero'ed and various fields are initialized.
;
; INPUTS:
;
;	R3	- UCB address
;	
;	UCB$L_SCDRPQ_FL	- Queue of SCDRPs
;
; OUTPUTS:
;
;	R5	- SCDRP address
;	All other register preserved
;
;	SCDRP$L_UCB	- UCB address
;	SCDRP$L_IRP	- IRP address
;	SCDRP$L_CDT	- CDT address
;	SCDRP$L_SCSI_FLAGS - Initialized
;	SCDRP$L_CL_STK_PTR - Initialized
;-

ALLOC_SCDRP:

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

10$:	
	PUSHR	#^M<R0,R1,R2>		; Save regs
	MOVL	#SCDRP$C_LENGTH,R1	; Length of SCDRP
        ALLOC_STACK_SCDRP               ; Get the SCDRP for a STACK
	BSBW	ALLOC_POOL		; Go allocate an SCDRP
        FREE_STACK_SCDRP                ; Give it back
	MOVW	R1,SCDRP$W_SCDRPSIZE(R2); Save length of SCDRP
	MOVL	R2,R5			; Copy new SCDRP address
	POPR	#^M<R0,R1,R2>		; Restore regs
	BRW	5$

BUG_CHECK INCONSTATE,FATAL	; Queue should never have been empty


	.SBTTL	DEALLOC_SCDRP	- Deallocate an SCDRP
;+
; DEALLOC_SCDRP
;
; This routine deallocates an SCDRP by returning it the the queue in the
; UCB. A sanity check is made to ensure that any map registers for this
; command have been deallocated.
;
; INPUTS:
;
;	R3	- UCB address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R3	- UCB address
;	R5	- UCB address (for _R5 entry point)
;	All other register preserved
;
;	UCB$L_SCDRP	- Cleared to indicate no active SCDRP
;-

DEALLOC_SCDRP:

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

10$:	BUG_CHECK INCONSTATE,FATAL	; Should never deallocate an SCDRP 
					; without first unmapping buffer
	.DSABL	LSB


	.SBTTL	GET_DEVICE_TYPE	- Determine device type from inquiry data
;+
; GET_DEVICE_TYPE
;
; This routine translates the product ID field from the INQUIRY data to
; a VMS device type. In addition, it fills in the media ID, disconnect and 
; synchronous flags, and device timeout values in the UCB.
; 
; If the revision field in the inquiry data indicates the device is out-of-rev,
; then don't allow disconnects for this device. It's been shown that one of the
; hardest things to get right is the disconnect/reconnect logic. Thus, if the
; device is not up to rev there's a good chance this logic is broken and 
; attempting to use the device with disconnects enabled could cause bus problems.
;
; INPUTS:
;               
;	R0	- Address of INQUIRY data
;	R3	- UCB address
;-

GET_DEVICE_TYPE:

	MOVAL	SCSI_DEVICE_TABLE,R1	; Get product ID translation table
10$:	TSTB	(R1)+			; End of table
	BEQLW	40$			; Branch if so
	CMPL	(R1)+,16(R0)		; Product IDs match (low-order)?
	BEQL	20$			; Branch if so
	ADDL	#17,R1			; Advance to next entry in table
	BRB	10$			; Try next entry
20$:	CMPL	(R1)+,20(R0)		; Product IDs match (high-order)?
	BEQL	25$			; Branch if so
	ADDL	#13,R1			; Advance to next entry in table
	BRB	10$			; Try next entry in table
25$:	MOVB	-9(R1),UCB$B_DEVTYPE(R3); Found a match, extract device type
	BSBW	CHECK_REV_LEVEL		; Check for an acceptable rev level
.IF	DEFINED V60_BUILD
        BBC     #DEV$V_DTN,-            ; If DTN bit is set, handle DDR
                UCB$L_DEVCHAR2(R3),27$  ;  mechanism
        PUSHR   #^M<R0,R1,R5>           ; Save registers across JSB
        MOVL    R3,R5                   ; Get UCB in R5 for IOC$
        JSB     G^IOC$REMOVE_DEVICE_TYPE ; Remove our reference to the DTN
        POPR    #^M<R0,R1,R5>           ; Restore registers
.ENDC	; DEFINED V60_BUILD

27$:    CHECK_FOR_RAID_DEVICE		; Set RAID bit if it's an Array.
	CHECK_FOR_CMDQ			; Set CMDQ bit if supported
	MOVL    (R1)+,UCB$L_MEDIA_ID(R3); Save media ID
        PUSHL   R0                      ; Save pointer to INQUIRY data
        MOVB    (R1)+,R0                ; Get disconnect, synchronous flags
        ASSUME  UCB$V_DISCONNECT+1 EQ UCB$V_SYNCHRONOUS
        INSV    R0,#UCB$V_DISCONNECT,-  ; Initialize these flagse in the UCB
                #2,UCB$L_DK_FLAGS(R3)   ;
        POPL    R0                      ; Restore pointer to INQUIRY data
        ASSUME  UCB$W_PHASE_TMO+2 EQ UCB$W_DISC_TMO
        MOVL    (R1),UCB$W_PHASE_TMO(R3)
        BBC     #UCB$V_OUT_OF_REV,-     ; Branch if the device is NOT out-of-rev
                UCB$L_DK_FLAGS(R3),30$  ;
        BICB    #UCB$M_DISCONNECT,-     ; If the device is out-of-rev, then
                UCB$L_DK_FLAGS(R3)      ; don't allow disconnects

30$:    
.IF	DEFINED V60_BUILD
	CMPB    #DT$_GENERIC_DK,-       ; Are we in DDR mode?
                UCB$B_DEVTYPE(R3)       ;
        BEQL    32$                     ; Yes
	BRW	35$			; Nope



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

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

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

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

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

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

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

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

.ENDC	; DEFINED V60_BUILD

35$:    RSB                             ; Return to caller

; No matching product ID field was found in the table. Assume this is a generic
; SCSI disk.

40$:	MOVAL	GENERIC_SCSI_DISK,R1	; Use generic SCSI disk type
	BRW	25$			; 


	.SBTTL	CHECK_REV_LEVEL	- Check for an out-of-rev device
;+
; CHECK_REV_LEVEL
;
; This routine checks the revision level returned in the inquiry data with
; the minimum revision level stored in the SCSI_DEVICE_TABLE. If the device's
; revision level is below the minimum allowed, an invalid inquiry data error
; is logged.
;
; Note: since the revision field in the inquiry data is a 4 byte ascii string,
; the low-order byte is the most significant. Thus, the bytes must be compared 
; from low byte to high byte, and a CMPL can not be used to perform the 
; comparison.
;
; INPUTS:
;               
;	R0	- Address of inquiry data retured by target
; 	R1	- Address of minimum rev level field in SCSI_DEVICE_TABLE
;
; OUTPUTS:
;
;	R1	- Advanced past revision level field
;	All other registers preserved
;	An error is logged of the device is out-of-rev
;-

CHECK_REV_LEVEL:

	BICW	#UCB$M_OUT_OF_REV,-	; Clear out-of-rev flag in the UCB
		UCB$L_DK_FLAGS(R3)	;
  	PUSHQ	R1			; Save regs
	MOVAL	32(R0),R2		; Address of rev level in inquiry data
	.REPT	4			; Check four bytes of revision data
	CMPB	(R2)+,(R1)+		; Check a byte of revision 
	BGTRU	10$			; Branch if above minimum
	BLSSU	20$			; Branch of below minimum
	.ENDR				;
10$:	POPQ	R1			; Restore regs
	ADDL	#4,R1			; Advance beyond minimum rev field in table
	RSB				; Return to caller

20$:	LOG_ERROR -			; Log an invalid inquiry data error
		TYPE=INV_INQUIRY_DATA,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
	BISW	#UCB$M_OUT_OF_REV,-	; Set out-of-rev flag in the UCB
		UCB$L_DK_FLAGS(R3)	;
	BRB	10$			; Use common exit

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

ALLOC_POOL:

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

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

20$:	SUBPUSH	(SP)+			; Save allocation length (PUSHL R1 above)
	SUBSAVE				; Save return address
	DK_WAIT	#1,UCB=R3		; Wait a second
	SUBPOP	-(SP)			; Restore return address
	SUBPOP	R1			; Restore allocation length			
	BRW	10$			; Try again	


	.SBTTL	DEALLOC_POOL	- Deallocate a block of non-paged pool
;+
; DEALLOC_POOL
;
; This routine deallocates a block of non-paged pool. The size of the block
; is stored in the reserved quadword at a negative offset from the beginning 
; of the block.
; 
; INPUTS:
;
;	R0	- Address of block to deallocate
;	-8(R0)	- Length of block to deallocate
;
; OUTPUTS:
;
;	R0	- Destroyed
;	All other registers perserved
;-

DEALLOC_POOL:

	PUSHQ	R1			; Save R1,R2
	SUBL	#4,R0			; Skip a longword
	MOVL	-(R0),IRP$W_SIZE(R0)	; Copy size field
	CLRB	IRP$B_TYPE(R0)		; Clear type field (prevents block from
					; being interpreted as shared memory
					; during deallocation)
	JSB	G^EXE$DEANONPAGED	; Deallocate the block
	POPQ	R1			; Restore R1,R2
	RSB



	.SBTTL  AUDIO_EXIT 	- Audio Function Completion Code
;+
; AUDIO_EXIT -
; 
; All audio functions that are not aborted during FDT processing exit the 
; driver by executing this code. After a command has been sent, error 
; recovery has been executed, SCSI status copied to the AUCB then deallocate 
; the SCDRP and any other resources allocated for this request. The audio exit
; code is fairly complex due to the fact that we are double mapping and copying
; between three distinct user buffers.
;
; The original IRP (IRP pointed to by SCDRP$L_IRP) contains the mapping 
; information for the AUCB. If a user destination or sense buffer is required
; then an IRP extension is allocated in the FDT code to contain the mapping
; information for these buffers. This routine will deallocated all the 
; resources allocated for use in the original IRP and IRPE.
;
;
; INPUTS:
;	R5 = SCDRP for this request.
;	R3 = UCB 
;	R0 = VMS return status code
;
;
; OUTPUTS:
;	AUCB = User Audio Control Block
;		CD_COMMAND_STATUS  	= VMS O.S. Return status
;		CD_SCSI_STATUS 		= SCSI command status and Sense Key 
;		CD_SENSE_ADDR		= Sense data buffer
;		CD_SENSE_TRANS_CNT 	= Sense data transfer count 
;		CD_DEST_BUF_TRANS_CNT	= Number of bytes in destination buf
;-
AUDIO_EXIT:
	.ENABLE		LSB		; AUDIO_EXIT
	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command

AUDIO_EXIT_NO_CMD:
;+
; Now update the AUCB with the VMS Status, SCSI Status and Transfer Count.
;-
	PUSHR	#^M<R2,R7>		; Save some registers
	MOVL	SCDRP$L_MEDIA(R5),R7	; Get copy of AUCB address from SCDRP.
	MOVL	SCDRP$L_TRANS_CNT(R5),-	; Get Transfer Count in Destination
		 CD_DEST_BUF_TRANS_CNT(R7); XFER count field in the AUCB.
	TSTL	SCDRP$L_STS_PTR(R5)	; Test for existance of status buffer.
	BEQL	10$			; If no SCSI status buffer skip this..
	MOVZBL	@SCDRP$L_STS_PTR(R5),-	; Copy SCSI Status to AUCB.
		 CD_SCSI_STATUS(R7)
10$:	MOVZWL	R0,CD_COMMAND_STATUS(R7); Copy VMS status to AUCB.

;+
; If there was a check condition as the result of an audio command, copy
; the sense key and additional sense data to the AUCB from the UCB.
; During SEND_COMMAND, if a check condition occurs a request sense is
; issued to the target and the sense data is copied to the UCB.
;-
	ASSUME	 SCSI$C_GOOD_STATUS EQ 0
	TSTB	CD_SCSI_STATUS(R7)	; Was status success?
	BLEQ	50$			; If yes continue, else get sense data

	MOVZBW	SCDRP$B_SENSE_KEY(R5),-	; Copy the Sense key to the AUCB.
		CD_SCSI_STATUS+2(R7)

	MOVL	SCDRP$L_IRP(R5),R2	; Get original IRP
	MOVL	IRP$L_EXTEND(R2),R2	; Get any IRP extension
	BEQL	50$			; No, IRPE exit
	TSTL	IRP$L_PID(R2)		; Test for Sense Buffer existance
	BEQL	50$			; In no buffer don't copy sense data.

	PUSHR 	#^M<R0,R1,R2,R3,R4,R5>
	MOVAL	UCB$B_SENSE_INFO(R3),R0	; Get address of additional info field
	MOVZBL	UCB$B_SENSE_LEN(R3),R1	; Get count of number of sense bytes
	MOVL	CD_SENSE_CNT(R7),R5	; Get size of user sense buffer
	MINUM	R1,R5			; Minimize between these two values
	MOVL	R1,-			; Return Count of sense bytes to AUCB
		 CD_SENSE_TRANS_CNT(R7)
	MOVC3	R1,(R0),-		; Copy SENSE Data to user buffer
		  @IRP$L_SEGVBN(R2)	; SVA of SENSE buffer in SEGVBN.
	POPR 	#^M<R0,R1,R2,R3,R4,R5>

50$:	POPR	#^M<R2,R7>		; Restore saved registers

;+
; Unlock any locked pages and free any allocated PTE's, then clear any fields
; in the IRP that might confuse IOPOST and return status in R0 and R1, which
; will be copied to the IOSB.
;-
AUDIO_BAD_CMD_EXIT:
	PUSHR	#^M<R0,R2>
	MOVL	SCDRP$L_IRP(R5),R2	; Get original IRP address
	BSBW	AUDIO_EXIT_FREE		; Unlock buffers, dealloc PTE's and IRPE
	MOVL	SCDRP$L_IRP(R5),R2	; Get original IRP address
	CLRL	IRP$L_MEDIA(R2)		; Clear for IOPOST
	CLRL	IRP$L_OBCNT(R2)		; ...
	CLRL	IRP$L_SEGVBN(R2)	; ...
	POPR	#^M<R0,R2>		; Restore reg's
	CLRL	R1			; Clear R1, no additional info.
	BRW	COMPLETE_IO		; Complete this I/O function
	.DISABLE	LSB		; AUDIO_EXIT
	


	.SBTTL	ALLOC_IRPE	- Allocate IRP Extension.
;+
; ALLOC_IRPE:
;
; This routine is called to allocated and link in an IRP extension (IRPE).
; IRP extensions are used in cases where a single IRP doesn't have enough
; space to maintain the entire state of a single I/O request. In this driver
; there are cases where a single request may have 1, 2 or 3 user buffers that
; must be made accessable to the startio routine. For this purpose we use an
; IRP extension.
;
; The original IRP will contain, the AUCB state. The IRPE will contain the
; state of destination buffers (TOC Data) and the Optional Sense Buffers.
;
;
; INPUTS:        
;
; 	R3 - Original IRP
; 	Other registers have normal FDT context.
;
; OUTPUTS:         
;	R0	- Status
;	R3	- Address of allocated IRPE.
;
;	Original IRP field modified:
;		IRPE$L_EXTEND 	- Assigned address of IRPE
;		IRP$W_STS	- IRP$M_EXTEND flag set
;
;	Extended IRP field modified:
;		IRP$L_SEQNUM	- Address of original IRP
;		IRPE$L_SVAPTE1	- Cleared
;		IRPE$L_SVAPTE2	- Cleared
;
;-
ALLOC_IRPE:
	.ENABLE		LSB
	JSB	G^EXE$ALLOCIRP		; Allocate extended IRP
	BLBC	R0,50$			; Exit if fails
	MOVL	R2,IRP$L_EXTEND(R3)	; Link orig IRP to extended IRP.
	BISW	#IRP$M_EXTEND,-		; Flag this as an extended IRP
		 IRP$W_STS(R3)
	MOVW	IRP$W_FUNC(R3),-	; Copy IO function code.
		 IRP$W_FUNC(R2)	
	MOVL	R3,IRP$L_SEQNUM(R2)	; Save Original IRP Address
	MOVL 	R2,R3			; Get IRPE address
	MOVW	#<IRP$M_FUNC!IRP$M_PHYSIO>,-; Signal to IOPOST that
		  IRP$W_STS(R3) 	; this is a Direct, Read, Physical I/O

;+
; Clear the following fields in the IRPE, since the exit code in the audio
; path determines whether there are sense or destination buffers used for this
; I/O, based on whether or not these fields are zero.
;-
	CLRL	IRPE$L_SVAPTE1(R3)	; Clear SVAPTE1 in IRPE
	CLRL	IRPE$L_SVAPTE2(R3)	; Clear SVAPTE2 in IRPE
	CLRL	IRP$L_PID(R3)		; If not clear SENSE buffer assumed!
	CLRL	IRP$L_OBCNT(R3)		; If not zero destination buffer used.
	CLRL	IRP$L_DIAGBUF(R3)	; Clear SVAPTE for IRPE(SENSE) buffer

50$:	RSB
	.DISABLE	LSB		; ALLOC_IRPE



	.SBTTL  DEALLOC_IRPE 	- Deallocate an I/O request packet extension
;+
;
; DEALLOC_IRPE - SUBROUTINE TO DEALLOCATE AN I/O REQUEST PACKET EXTENSION
;
; INPUTS:
;
;       R2 = I/O REQUEST PACKET ADDRESS.
;
; OUTPUTS:
;
;       THE I/O REQUEST PACKET EXTENSION IS DEALLOCATED TO NON-PAGED
;       POOL.
;
;     R0,R1,R2 ARE PRESERVED.
;
;-
DEALLOC_IRPE:                            ; DEALLOCATE AN IRPE
	
	PUSHR	#^M<R0,R1,R2>
	MOVL    IRP$L_EXTEND(R2),R0     ; GET IRPE ADDRESS
        BEQL    20$                     ; BR IF NONE
	CLRL	IRP$L_EXTEND(R2)	; Delete extended IRPE pointer.
        BICW    #IRP$M_EXTEND,-		; CLEAR EXTEND FLAG
		 IRP$W_STS(R2)
	JSB     G^EXE$DEANONPAGED       ; DEALLOCATE IRPE
20$:    POPR    #^M<R0,R1,R2>           ; RESTORE REGISTERS
	RSB                             ; DEALLOC_IRPE


	.SBTTL	SETUP_CMD	- Common setup for all SCSI commands
;+
; SETUP_CMD
;
; This routine common setup prior to the sending of a SCSI command. This 
; includes allocating a command buffer, filling in the pointers in the SCDRP 
; to the command and status fields, copying the SCSI command to the command 
; buffer, allocating an S0 "user" buffer if the command requires transferring 
; data to or from the class driver, filling in the SCDRP fields used to map 
; this buffer, and mapping the buffer.
;
; Since this routine calls SPI$ALLOCATE_COMMAND_BUFFER, which can suspend
; the thread, the return PC must be saved in the SCDRP.
;
; INPUTS:
;
;	R2	- Pointer to entry in SCSI_CMD table
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;
;	SCDRP$L_CMD_BUF	- Address of SCSI command buffer
;	SCDRP$L_CMD_PTR	- Address of SCSI command 
;	SCDRP$L_STS_PTR	- Address to save SCSI status byte
;	SCDRP$L_SVA_USER- Address of S0 "user" buffer
;	SCDRP$L_BCNT	- Length of S0 "user" buffer
;	SCDRP$W_BOFF	- Byte offset of S0 "user" buffer
;	SCDRP$L_SVAPTE	- SVAPTE of of S0 "user" buffer
;	IRP$V_FUNC	- SET/CLEAR to indicate READ/WRITE from S0 "user" buffer
;-

SETUP_CMD:

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

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

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

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

	MOVZWL	UCB$W_PHASE_TMO(R3),-	; Fill in phase change and disconnect
		SCDRP$L_DMA_TIMEOUT(R5)	; timeout fields in the SCDRP
	MOVZWL	UCB$W_DISC_TMO(R3),-	;
		SCDRP$L_DISCON_TIMEOUT(R5)

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

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

CLEANUP_CMD:

	PUSHR	#^M<R0,R1,R3>		; Save regs
	BBCC	#SCDRP$V_BUFFER_MAPPED,-; Branch if no buffer has been mapped
		SCDRP$L_SCSI_FLAGS(R5),-;
		10$
	SPI$UNMAP_BUFFER		; Unmap the mapped buffer
10$:	BBCC	#SCDRP$V_S0BUF,-	; Branch if this is not an S0 "user"
		SCDRP$L_SCSI_FLAGS(R5),-; buffer
		20$			;
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of S0 user buffer
   	CLRL	SCDRP$L_SVA_USER(R5)	; Buffer no longer owned
	BSBW	DEALLOC_POOL		; Deallocate the buffer
20$:	MOVL	SCDRP$L_CMD_BUF(R5),R0	; Get address of command buffer
	SPI$DEALLOCATE_COMMAND_BUFFER	; Deallocate the command buffer
30$:	POPR	#^M<R0,R1,R3>		; Restore regs
	RSB	



	.SBTTL	SETUP_SENSE_BUFFER - Setup user sense data buffer
;+
; SETUP_SENSE_BUFFER:
;
; This routine is called to allocate an IRPE and prepare the user supplied
; sense data buffer. The sense data buffer will be locked down and double
; mapped. The mapping information for this buffer will be saved in the IRPE.
;
; INPUTS:        
;
; 	R3 - Original IRP
; 	Other registers have normal FDT context.
;
; OUTPUTS:         
;	R0	- Status
;-
SETUP_SENSE_BUFFER:
	.ENABLE		LSB

	BSBW	ALLOC_IRPE		; Allocate an IRP extension
	BLBC	R0,90$			; Exit if no IRPE
	
;+
; Now, lockdown and double map the users sense buffer, then save the 
; the mapping information for this buffer in a "special" place in the IRPE.
;-

	ASSUME	CD_SENSE_ADDR+4 EQ CD_SENSE_CNT
	MOVL	(AP),R0			; Get AUCB pointer
	MOVQ 	CD_SENSE_ADDR(R0),R0	; Get address and size of SENSE buffer.
	MOVL	R1,IRP$L_BCNT(R3)	; Copy Byte count to IRPE
	BICW3	#^C<VA$M_BYTE>,R0,-	; Get BOFF for Destination buffer.
		 IRP$W_BOFF(R3)
	BSBW	AUDIO_MAP_PAGE		; Locks and Maps AUCB.	
	BLBC	R0,90$

;+
; Since this IRPE may be used to map two buffers, copy the SENSE buffer 
; information into another place in the IRPE. 
;
; IRPE$L_PID	- The SVA of the first PTE that maps the Sense buffer.
; IRPE$L_AST 	- The number of PTE's allocated to double map the Sense buffer.
; IRPE$L_ASTPRM - The SVA of the user buffer to copy the SENSE data to.
; IRPE$L_DIAGBUF- The SVA of P0 PTE that maps first page of sense buffer.
;-
	MOVL	IRP$L_WIND(R3),-	; Save SVAPTE of system buffer
		 IRP$L_PID(R3)		; Not really the PID field!
	MOVL	IRP$L_OBCNT(R3),-	; Save number of PTE's allocated
		 IRP$L_AST(R3)		; Not really the AST field 
	MOVL	IRP$L_SVAPTE(R3),-	; Copy SVAPTE created by exe$writelock
		 IRP$L_DIAGBUF(R3)	
	MOVL	IRP$L_SEGVBN(R3),-	; SVA User buffer saved here
		 IRP$L_ASTPRM(R3)	; Not really the AST param field

	CLRL	IRP$L_WIND(R3)		; Clear IRP WIND field
	CLRL	IRP$L_SVAPTE(R3)	; Clear invalid svapte info.
	CLRL	IRP$L_OBCNT(R3)		; Clear what will be dest page count
90$:	MOVL	IRP$L_SEQNUM(R3),R3	; Restore  Original IRP Address

	RSB
	.DISABLE	LSB		; SETUP_SENSE_BUFFER


	.SBTTL	AUDIO_MAP_BUFFER   - Lock down and double map user buffer
;+
; AUDIO_MAP_PAGE:
;
; This routine locks the users buffer down, allocates the PTE's required to 
; double map the user buffer and saves this mapping information in the IRP.
; The STARTIO routine will use the information in the IRP to move data to
; or from the users buffer.
;
; INPUTS:        
;
; 	R0 - P0 address of buffer
; 	R1 - Byte count
; 	R3 - IRP
; 	Other registers have normal FDT context.
;
; OUTPUTS:         
;
;	IRP$L_WIND   - 	SVAPTE of mapped  buffer.
;	IRP$L_OBCNT  -	Number of PTE's allocated.
; 	IRP$L_SEGVBN -  The SVA of the first PTE that maps the Sense buffer.
;
;	R0	- Status
;	All other registers preserved
;-
AUDIO_MAP_PAGE:
	.ENABLE		LSB
	JSB	G^EXE$MODIFYLOCKR	; Lock pages and return
	BLBS	R0,10$			; Success, continue.
	BRW	LOCK_ERROR		; Exit, if fail to lock user buffer.

;+
; To check to see how many PTE's to allocate,  ADD BOFF and BCNT if > 512 add one to page count.
; do a sobgtr to loop through and copy all the pte's
;-
10$:	MOVL	R1,R11			; Save the SVAPTE for the AUCB
	MOVL    IRP$L_BCNT(R3),R9	; get requested transfer byte count
    	MOVZWL  IRP$W_BOFF(R3),R10	; get byte offset in page
	MOVAB   511(R9)[R10],R9         ; combine offset and count and round
	ASHL    #-VA$S_BYTE,R9,R2	; Get number of PTE's needed.
        JSB     G^LDR$ALLOC_PT          ; Allocate the SPT's
	BLBS	R0,15$			; Success, then continue.
	BRW	MAP_ERROR		; Exit, if can't map buffer.

15$:	MOVL	R1,IRP$L_WIND(R3)	; Save SVAPTE of system buffer
	MOVL	R2,IRP$L_OBCNT(R3)	; Save number of PTE's allocated

;+ 
; Now convert the SPTE address to a SVA address so that the startio routine
; can access the AUCB via this address.
;-
	MOVL	R1,R10			; Save the SVAPTE 
        SUBL2   G^MMG$GL_SPTBASE,R1     ; Get offset into page table
        ASHL    #<VA$S_BYTE-2>,R1,R9	; Save the S0 virtual address
	BISW	IRP$W_BOFF(R3),R9	; Add in byte offset of AUCB.
        BISL    #VA$M_SYSTEM,R9		; Set 80000000 bit for system address
	MOVL	R9,IRP$L_SEGVBN(R3)	; SVA User buffer saved here
;+
; Now loop through all of the PTE's required to map the buffer and copy the
; the PTE's from user page tables into the system page tables.
;
; Remember that R11 has the SVAPTE for the AUCB.
;- 
	PUSHL	R3			; Save R3
COPY_PTE:
	MOVL	(R11)+,R3		; Copy buffers PTE's to R3.
	BLSS	60$			; Valid PTE if LSS, otherwise NOT valid.
	JSB	G^IOC$PTETOPFN		; Create vaild PTE from invalid PTE.
60$:	INVALIDATE_TB	R9,ENVIRON=LOCAL- ; Invalidate this address in the TB.
	  	INST1=<MOVL #<PTE$M_VALID!PTE$C_KW!PTE$C_KOWN>,(R10)>,-
 	    	INST2=<INSV R3,#PTE$V_PFN,#PTE$S_PFN,(R10)>
;				PUSHL	R2
;			MOVL	R9,R2
;			BLBC	G^MMG$GL_TBI_FLAGS,30326$
;			JSB	G^MMG$TBIS_PROLOG_SIMPLE
;		30326$:
;			MOVL #<PTE$M_VALID!PTE$C_KW!PTE$C_KOWN>,(R10)
;			INSV R3,#PTE$V_PFN,#PTE$S_PFN,(R10)
;			BLBC	G^MMG$GL_TBI_FLAGS,30327$
;				JSB	G^MMG$TBIS_EPILOG_SIMPLE
;			BRB	30328$
;		30327$:	MTPR	R2,S^#PR$_TBIS
; 		30328$:
;					POPL	R2
    					; Set VALID, KERNEL WRITE KERNEL MODE 
					; into the PTE.
					; Set entry in SPT
	MOVAB	512(R9),R9		; Point to next page
	ADDL2	#4,R10			; Point to next entry in SPT
	SOBGTR	R2,COPY_PTE		; Copy all required PTE's
	POPL	R3			; Restore R3
	RSB				; AUDIO_MAP_PAGE
;+
; An error occured in EXE$MODIFYLOCKR, we are called here as a coroutine.
;
; we need to clean up all allocated resources and rsb back to EXE$MODIFYLOCKR,
; which will unwind the stack, fail the I/O. Control DOES NOT return
; back to this routine (AUDIO_MAP_PAGE)
;
;-
LOCK_ERROR:
        PUSHQ   R0                      ; Must preserve R0, R1
        TSTL    IRP$L_SEQNUM(R3)        ; Is this the IRPE?
        BGEQ    100$                    ; Nope, this is the original IRP
        MOVL    IRP$L_SEQNUM(R3),R3     ; Restore original IRP
100$:   PUSHL   R2                      ; Save R2
        MOVL    R3,R2                   ; For AUDIO_EXIT_FREE
        JSB     AUDIO_EXIT_FREE         ; Free resources
        POPL    R2                      ; Restore R2
        POPQ    R0                      ; Restore R0,R1
        RSB                             ; Return to Coroutine, fail I/O,
                                        ; and exit FDT
;+
; Error occured while trying to double map a user buffer, unlock the buffer
; that we just locked down and return to caller.
;
; R2	= Number of pages to unlock.
; R3	= IRP Address
;-
MAP_ERROR:
	PUSHL	R3
	MOVL	IRP$L_SVAPTE(R3),R3	; Get SVAPTE for IRPE(Dest) buffer
	BEQL	150$			; No buffer to unlock.
	MOVL	R2,R1			; Get number of pages locked down.
	BEQL	150$			; No buffer to unlock
	PUSHL	R0
	JSB	G^MMG$UNLOCK		; Unlock those pages.
	POPL	R0
150$:	POPL	R3
	BRB	LOCK_ERROR

	.DISABLE	LSB		; AUDIO_MAP_PAGE

                        


	.SBTTL  AUDIO_EXIT_FREE - Audio exit resource deallocation.
;+
; AUDIO_EXIT_FREE -
; 
; Given the original IRP address deallocate any allocated system resources.
; Unlock user buffers and the release PTE's
;
; INPUTS:
;	R2 - Original IRP Address
;
; OUTPUTS:
;	TBD
;-
AUDIO_EXIT_FREE:
	.ENABLE		LSB

	PUSHR 	#^M<R0,R2,R3>

;+ 
; If there is an IRP extension in use then, unlock any locked pages.
;-
	PUSHL	R2			; Save original IRP address
30$:	BBC	#IRP$V_EXTEND,-		; Is this an extended IRP?
		 IRP$W_STS(R2),REL_PTE	; If not exit, continue..

;+
; Since there was an IRP extension, unlock any pages that we locked down
; for use by the IRPE in startio. First unlock the destination buffer, then
; unlock the sense data buffer if they exist.
;
; IRP$L_WIND(R2)	= S0 SVAPTE for IRPE Destination (user) buffer
; IRP$L_SVAPTE(R2)	= P0 SVAPTE for IRPE Destination (user) buffer
; IRP$L_OBCNT(R2)	= Number of pages locked down.
;-
	MOVL	IRP$L_EXTEND(R2),R2	; Get IRPE Address
	MOVL	IRP$L_SVAPTE(R2),R3	; Get SVAPTE for IRPE(Dest) buffer
	BEQL	NO_DEST_BUF		; No destintation buffer to unlock.
	MOVL	IRP$L_OBCNT(R2),R1	; Get number of pages locked down.
	BEQL	NO_DEST_BUF		; No SENSE buffer to unlock
	JSB	G^MMG$UNLOCK		; Unlock those pages.

;+ 
; Unlock Sense buffer pages, if in use. Use information in IRPE.
;
; IRP(E)$_PID 		= S0 SVAPTE Sense Buffer
; IRP(E)$_DIAGBUF	= P0 SVAPTE Sense Buffer
; IRP(E)$_AST 		= Number of pages/pte's
; IRP(E)$_ASTPRM	= SVA of Sense Buffer in S0.
;-
NO_DEST_BUF:
	MOVL	(SP),R2			; "Restore" R2
	MOVL	IRP$L_EXTEND(R2),R2	; Get IRPE Address
	MOVL	IRP$L_DIAGBUF(R2),R3	; Get SVAPTE for IRPE(SENSE) buffer
	BEQL	REL_PTE			; No SENSE buffer to unlock
	MOVL	IRP$L_AST(R2),R1	; Get number of pages locked down.
	BEQL	REL_PTE			; No SENSE buffer to unlock
	JSB	G^MMG$UNLOCK		; Unlock those pages.

;+
; After unlocking Destination and Sense Buffer deallocated PTE's
;-
REL_PTE:
	POPL	R2			; Restore Original IRP Address
	BSBW	AUDIO_FREE_PTES		; R2 = IRP, free allocated PTE's

;+
; If there was an IRPE, now deallocate it before starting IOPOST.
;-	
	BBC	#IRP$V_EXTEND,-		; Is this an extended IRP?
		 IRP$W_STS(R2),DONE_FREE; If not exit, continue..
	BSBW	DEALLOC_IRPE		; Deallocated IRPE, if the exist.

;+
; Later Start Autosense if check condition.
;-
DONE_FREE:
	POPR	#^M<R0,R2,R3>
	RSB				; AUDIO_EXIT_FREE
	.DISABLE	LSB		; AUDIO_EXIT_FREE


	.SBTTL	AUDIO_FREE_PTES	- Free any allocated PTE's
;+
; AUDIO_FREE_PTES:
;
; Routine used by AUDIO code to free the PTE's allocated for double mapping.
; This routine is called to deallocated all of the PTE's that could possibly
; be in use by the original IRP or the IRP extension.
;
; INPUTS:
;	R2 = IRP
;		IRP$L_WIND 	= SVAPTE of PTE's for the AUCB
;		IRP$L_OBCNT	= Number of PTE's for the AUCB
;		IRP$L_EXTEND	= Valid if IRPE used.
;		IRP(E)$L_WIND 	= SVAPTE of PTE's for Destination Buffer
;		IRP(E)$L_OBCNT	= Number of PTE's for Destination Buffer
;		IRP(E)$L_PID	= SVAPTE of Sense Buffer PTE's
;		IRP(E)$L_AST	= Number of PTE's for Sense Buffer
;
; OUTPUTS:
;	R0 = Return status.
;
; 	R0,R1 Modified
;-
AUDIO_FREE_PTES:

	.ENABLE		LSB

	PUSHL	R2			; Save IRP address
	MOVL	IRP$L_WIND(R2),R1	; Get Address of PTE's Allocated
	BEQL	OTHER_PTE		; If none allocated, check for others
	MOVL	IRP$L_OBCNT(R2),R0	; Get number of PTE's.
	BEQL	OTHER_PTE		; If none allocated, check for others
	
;+
; Before deallocating AUCB PTE's they must be zeroed.
;-
	PUSHL	R9
	MOVL	IRP$L_SEGVBN(R2),R9	; Get SVA of AUCB as saved by FDT call

AUCB_PTE:
	INVALIDATE_TB	R9,ENVIRON=LOCAL- ; Invalidate this address in the TB.
	  	INST1=<CLRL	(R1)+ ; Clear PTE before dealloc>
;				PUSHL	R2
;			MOVL	R9,R2
;			BLBC	G^MMG$GL_TBI_FLAGS,30331$
;			JSB	G^MMG$TBIS_PROLOG_SIMPLE
;		30331$:
;			CLRL	(R1)+ ; Clear PTE before dealloc
;			BLBC	G^MMG$GL_TBI_FLAGS,30332$
;				JSB	G^MMG$TBIS_EPILOG_SIMPLE
;			BRB	30333$
;		30332$:	MTPR	R2,S^#PR$_TBIS
; 		30333$:
;					POPL	R2
	MOVAB	512(R9),R9		; Point to next page
	SOBGTR	R0,AUCB_PTE		; Loop through all PTE's
	POPL	R9

	MOVL	(SP),R2			; "Restore" R2
	MOVL	IRP$L_WIND(R2),R1	; Get Address of PTE's Allocated
	MOVL	IRP$L_OBCNT(R2),R2	; Get number of PTE's.
	JSB	G^LDR$DEALLOC_PT	; Free the allocated PTE's
	BLBS	R0,OTHER_PTE		; If success continue, otherwise
	BUG_CHECK INCONSTATE,FATAL	; BUGCHECK, this can never happen...

;+
; Check for PTE's allocated for the SENSE buffer. But first we must be sure that
; this is an IRP extension, since only IRPE have additional PTE's allocated.
;-
OTHER_PTE:
	MOVL	(SP),R2			; "Restore" R2
	BBS	#IRP$V_EXTEND,-		; Is this an extended IRP?
		 IRP$W_STS(R2),20$	; If so, continue.
	BRW	FREE_DONE		; If not exit, otherwise continue..
20$:	MOVL	IRP$L_EXTEND(R2),R2	; Get IRPE Address
	MOVL	IRP$L_PID(R2),R1	; Get Address of PTE's allocated
	BEQL	DEST_PTE		; If none allocated, check for others
	MOVL	IRP$L_AST(R2),R0	; Get number of PTE's.
	BEQL	DEST_PTE		; If none allocated, check for others
	
;+
; Before deallocating Sense buffer PTE's they must be zeroed and invalidated
;-
	PUSHL	R9
	MOVL	IRP$L_ASTPRM(R2),R9	; Get SVA of Sense Buf from FDT call

SENSE_PTE:	
	INVALIDATE_TB	R9,ENVIRON=LOCAL- ; Invalidate this address in the TB.
	  	INST1=<CLRL	(R1)+ ; Clear PTE before dealloc>
;				PUSHL	R2
;			MOVL	R9,R2
;			BLBC	G^MMG$GL_TBI_FLAGS,30336$
;			JSB	G^MMG$TBIS_PROLOG_SIMPLE
;		30336$:
;			CLRL	(R1)+ ; Clear PTE before dealloc
;			BLBC	G^MMG$GL_TBI_FLAGS,30337$
;				JSB	G^MMG$TBIS_EPILOG_SIMPLE
;			BRB	30338$
;		30337$:	MTPR	R2,S^#PR$_TBIS
; 		30338$:
;					POPL	R2
	MOVAB	512(R9),R9		; Point to next page
	SOBGTR	R0,SENSE_PTE		; Loop through all PTE's
	POPL	R9

	MOVL	(SP),R2			; "Restore" R2
	MOVL	IRP$L_EXTEND(R2),R2	; Get IRPE Address
	MOVL	IRP$L_PID(R2),R1	; Get Address of PTE's Allocated
	MOVL	IRP$L_AST(R2),R2	; Get number of PTE's.
	JSB	G^LDR$DEALLOC_PT	; Free the allocated PTE's
	BLBS	R0,DEST_PTE		; If success continue, otherwise
	BUG_CHECK INCONSTATE,FATAL	; BUGCHECK, this can never happen...

;+
; Check for PTE's allocated for the Destination  buffer.  If allocated then
; deallocate them.
;-
DEST_PTE:
	MOVL	(SP),R2			; "Restore" R2
	MOVL	IRP$L_EXTEND(R2),R2	; Get IRPE Address
	MOVL	IRP$L_WIND(R2),R1	; Get Address of PTE's Allocated
	BEQL	FREE_DONE		; If none allocated, check for others
	MOVL	IRP$L_OBCNT(R2),R0	; Get number of PTE's.
	BEQL	FREE_DONE		; If none allocated, check for others
	
;+
; Before deallocating Destination buffer PTE's they must be zeroed.
;-
	PUSHL	R9
	MOVL	IRP$L_SEGVBN(R2),R9	; Get SVA of Dest Buf from FDT call

50$:	INVALIDATE_TB	R9,ENVIRON=LOCAL- ; Invalidate this address in the TB.
	  	INST1=<CLRL	(R1)+ ; Clear PTE before dealloc>
;				PUSHL	R2
;			MOVL	R9,R2
;			BLBC	G^MMG$GL_TBI_FLAGS,30341$
;			JSB	G^MMG$TBIS_PROLOG_SIMPLE
;		30341$:
;			CLRL	(R1)+ ; Clear PTE before dealloc
;			BLBC	G^MMG$GL_TBI_FLAGS,30342$
;				JSB	G^MMG$TBIS_EPILOG_SIMPLE
;			BRB	30343$
;		30342$:	MTPR	R2,S^#PR$_TBIS
; 		30343$:
;					POPL	R2
	MOVAB	512(R9),R9		; Point to next page
	SOBGTR	R0,50$			; Loop through all PTE's
	POPL	R9

	MOVL	(SP),R2			; "Restore" R2
	MOVL	IRP$L_EXTEND(R2),R2	; Get IRPE Address
	MOVL	IRP$L_WIND(R2),R1	; Get Address of PTE's Allocated
	MOVL	IRP$L_OBCNT(R2),R2	; Get number of PTE's.
	JSB	G^LDR$DEALLOC_PT	; Free the allocated PTE's
	BLBS	R0,FREE_DONE		; If success continue, otherwise
	BUG_CHECK INCONSTATE,FATAL	; BUGCHECK, this can never happen...

FREE_DONE:
	MOVL	#SS$_NORMAL,R0		; Assume success
ERROR_DONE:
	POPL	R2			; Restore IRP  address
	RSB				
	.DISABLE	LSB		; AUDIO_FREE_PTES   


	.SBTTL	LOG_ERROR	- Write an entry to the errorlog file
;+
; LOG_ERROR
;
; This routine writes an entry to the errorlog file. If the device is offline,
; no error is logged. This prevents the errorlog file from being filled up while
; the class driver does it its periodic polling of devices that have been set 
; offline. The assumption is that the initial error that caused the device to
; be placed offline has been logged and that subsequent errorlog entries would
; be redundent.
;
; If the device class and/or type fields in the UCB have not been initialized, 
; then fill in "DISK" and "GENERIC SCSI DISK" respectively so the packet can be
; properly formatted by ERF. This situation could arise if invalid inquiry data
; is received for the device, preventing these fields from being filled in
; properly.
;
; If no I/O is active for this device (for example, if an error is detected 
; during unit initialization) log a device attention. Otherwise, log a
; device error and then release the errorlog entry. This prevents errors from
; being lost if multiple errors are logged for a single QIO.
;
; Some type of errors are logged just oncs per system boot. For example, if 
; invalid mode sense data is returned by the target, just one INVALID_SENSE_DATA
; error is logged to prevent filling the errorlog with duplicate packets. The
; DUPLICATE_ERR_MASK table specifies those error types which should be logged
; just once. A bitmask in the UCB records those error types that have been
; logged already.
;
; INPUTS:
;
;	R5	- UCB address
;	R7	- Error type
;	R8	- VMS status code
;
; OUTPUTS:
;
;	All registers preserved
;-

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

LOG_ERROR:

	BBS	#UCB$V_DISABL_ERRLOG,-	; Branch if errorlogging is disabled
		UCB$L_DK_FLAGS(R5),40$	; for this device
	BBC	#UCB$V_ONLINE,-		; Branch if device is offline (don't
		UCB$W_STS(R5),40$	; log an error)
	BBCS	R7,UCB$L_ERR_MASK(R5),-	; Branch if this error type has not
		5$			; been logged yet
	BBC	R7,DUPLICATE_ERR_MASK,-	; Branch if this is a type of error
		40$			; that should be logged just once
5$:	PUSHR	#^M<R0,R2,R9,R10>	; Save regs
	MOVB	UCB$B_DEVTYPE(R5),R9	; Save SCSI device type
	BNEQ	10$			; Branch if device type known
	MOVB	#DT$_GENERIC_DK,-	; Fill in generic type so ERF can
		UCB$B_DEVTYPE(R5)	; translate the errlog packet
10$:	MOVB	UCB$B_DEVCLASS(R5),R10	; Save DEVCLASS field
	MOVB	#DC$_DISK,-		; Temporarily set this field to a disk
		UCB$B_DEVCLASS(R5)	; so ERF can translate packet
	MOVL	UCB$L_DDT(R5),R0	; Get DDT address
	MOVW	SCSI_ERROR_LEN_TAB-2[R7],-; Save required errorlog packet size
		DDT$W_ERRORBUF(R0)	; in the DDT
	TSTL	UCB$L_QUEUED_IO_COUNT(R5) ; I/O in progress?
	BEQL	20$			; Branch if not
	JSB	G^ERL$DEVICERR		; Log a device error
	BBCC	#UCB$V_ERLOGIP,-	; Clear error log in progress.
		UCB$W_STS(R5),30$	;
	MOVL	UCB$L_EMB(R5),R2	; Get address of error message buffer
	BEQL	30$			; Branch if none available
	JSB	G^ERL$RELEASEMB		; Realease the errorlog buffer
	BRB	30$			; Skip no-I/O-in-progress path
20$:	JSB	G^ERL$DEVICEATTN	; Log a device attention
30$:	MOVB	R10,UCB$B_DEVCLASS(R5)	; Restore original devclass value
	MOVB	R9,UCB$B_DEVTYPE(R5)	; Restore device type
	POPR	#^M<R0,R2,R9,R10>	; Restore regs
40$:	RSB				; Return to caller


	.SBTTL	SET_CONN_CHAR	- Modify connection characteristics
;+
; SET_CONN_CHAR
;
; This routine is called at the end of PACKACK to initialize the connection
; characteristics, which specify such things as whether the device supports 
; disconnect and synchronous operation, and the bus busy, arbitration, and
; selection retry counters.
;
; This routine first does a SPI$GET_CONNECTION_CHAR to get the current
; values of the connection characteristics, modifies the values of interest,
; then does a SPI$SET_CONNECTION_CHAR to set up the new values. This allows
; the class driver to change a subset of the characteristics and leave the
; rest unmodified.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- SPDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0-R2	- Destroyed
;	All other registers preserved
;-

	NUM_ARGS = 11

SET_CONN_CHAR:

	SUBSAVE				; Save return address
	MOVL	#<<NUM_ARGS+1>*4>,R1	; Size of get/set conn char buffer
	BSBW	ALLOC_POOL		; Allocate the buffer
	SUBPUSH	R2			; Save address of buffer
	MOVL	#NUM_ARGS,(R2)		; Set argument count in buffer
	SPI$GET_CONNECTION_CHAR		; Get current connection characteristics
	BLBC	R0,10$			; Branch on error
	EXTZV	#UCB$V_DISCONNECT,#1,-	; Fill in disconnect flag
		UCB$L_DK_FLAGS(R3),4(R2);
	EXTZV	#UCB$V_SYNCHRONOUS,#1,-	; Fill in synchronous flag
		UCB$L_DK_FLAGS(R3),8(R2);
	CLRL	^X2C(R2)		; Clear FLAGS field
	IF_NOT_CMDQ	1$		; Br if no CMDQ support
	BISL	#1,^X2C(R2)		; Yes, set CMDQ char bit
					; We leave FLUSHQ zero since we
					; Resume queues on error
	MOVW	UCB$L_QDEPTH(R3),^X2E(R2) ; Set max queue depth
1$:	CMPB	#2,UCB$B_SCSI_VERSION(R3) ; Are we at least SCSI-2?
	BGTR	2$			; Nope
	BISL	#4,^X2C(R2)		; Yes, set SCSI_2 char bit
2$:	SPI$SET_CONNECTION_CHAR		; Set the connection characteristics
10$:	PUSHL	R0			; Save return status
	SUBPOP	R0			; Get address of characteristics buffer
	BSBW	DEALLOC_POOL		; Deallocate the buffer
	POPL	R0			; Restore return status
	BLBS	R0,20$			; Branch if success status
	MOVL	#SS$_CTRLERR,R0		; Otherwise, return a reasonable status
20$:	SUBRETURN			; Return to caller


	.SBTTL	SAVE_CONN_CHAR	- Save connection characteristics
;+
; SAVE_CONN_CHAR
; 
; This routine saves the connection characteristics into a storage area
; allocated by this routine, and pointed to by the field UCB$L_SAVE_CONN_CHAR.
; The reason behind doing this is to save the device's characteristics prior
; to performing an operation that would change the device characteristics from
; what a set mode operation does.  
;
; An example of such an operation is mixed-mode media support of CDROMS.
;
; INPUTS:
;	R2	- IRP address
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;
;-

SAVE_CONN_CHAR:
 
; Add code to save connection characteristics that may be changed from
; the functionality that is being performed.  This code is specifically
; being added to support mixed mode audio/data disks, but can be used
; for other processing as well. 

	MOVL	UCB$L_SAVE_CONN_CHAR(R3),R2 ; Pick up the connection char buffer
	BNEQ	10$			; Branch if we do indeed have one
					; If we do not have one for this UCB,
					; then allocate a permanent one
	MOVL	#<<NUM_ARGS+1>*4>,R1	; Size of get/set conn char buffer
	BSBW	ALLOC_POOL		; Allocate the buffer
	MOVL	R2,-			; Save the location into our storage
		UCB$L_SAVE_CONN_CHAR(R3); area in the UCB
10$:	MOVL	#NUM_ARGS,(R2)		; Set argument count in buffer
	SPI$GET_CONNECTION_CHAR		; Get current connection characteristics
	RSB


	.SBTTL	RESTORE_CONN_CHAR - Restore connection characteristics
;+
; RESTORE_CONN_CHAR
;
; The objective of this routine is to restore the connection characteristics
; save by a previous call to the SAVE_CONNECT_CHAR routine.
;
; INPUTS:
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;-

RESTORE_CONN_CHAR:

	MOVL	UCB$L_SAVE_CONN_CHAR(R3),-
		R2			; Restore Address of Connection Buffer
	SPI$SET_CONNECTION_CHAR		; Set the connection characteristics
					; Return status back from this routine
	RSB


	.SBTTL	ATTEMPT_REORDER	- Attempt to reorder read/write requests for
	.SBTTL	-		  seek optimization
;+
; ATTEMPT_REORDER
;
; This routine is called at the end of STARTIO to scan the pending queue in
; the UCB and determine the most more optimal request to service next.
; An elevator algorithm is used. Thus, the disk head moves in one direction,
; servicing all requests in that direction until no more exist. The CUR_LBN
; field keeps track of the current location of the heads, while the SEEK_DIR
; bit specifies the current direction of motion. An assumption here is that
; the disk is laid out in LBN order.
;
; Note that it's OK to reorder read and write operations in the pending
; queue, but not most other operations, such as PACKACKs. Therefore, scan
; of the pending queue stops if a non-read or -write operation is encountered.
; Also, reordering is not performed if the disk is currently in mount
; verification.
;
; Register usage within this routine:
;
;	R0	- Contains the function code of each successive IRP in
;		  order to check that the function is a read or write
;	R1	- Bitmap used to determine whether an IRP in the pending
;		  queue is "better" than the "best" IRP found so far
;	R2	- Address of pending I/O queue listehead in UCB
;	R3	- Address of the "best" IRP found at any point
;	R4	- Signed displacement from the head position to the LBN of the 
;		  "best" IRP found so far
;	R5	- UCB address
;	R6	- Address of each successive IRP in pending queue
;		  being considered as a posible optimal IRP
;	R7	- Signed displacement from the heads to the LBN of each 
;		  successive IRP in pending queue as the list is scanned
;       R8      - Limit to the number of IRPs in the pending queue that
;                 get scanned
; INPUTS:
;
;	R2	- Address of I/O pending queue listhead in UCB
;	R5	- UCB address
;
; OUTPUTS:
;
;	R0-R4 - Destroyed
;	All other registers preserved
;       Pending queue in UCB is sorted, with most appropriate IRP for current
;       head position at front of queue
;-

MAX_IRPS_SCANNED = 12

CANT_REORDER:

	RSB

ATTEMPT_REORDER:

; If mount verification is in progress, don't perform any reordering.

	BBS	#UCB$V_MNTVERIP,-	; Branch if mount verification is
		UCB$L_STS(R5),-		; in progress, can't reorder
		CANT_REORDER		;

; Check that the active IRP is either a read or write function. If not, 
; we can't reorder. The following code sequence increments the function code,
; and clears the low-order bit so that both IO$_READPBLK and IO$_WRITEPBLK
; get converted to IO$_READPBLK, reducing the number of compare operations
; needed to determine whether the function is a read or write.

        MOVL    (R2),R3                 ; Get first IRP in pending queue
	ASSUME	IRP$S_FCODE LE 7	; Allow byte compare
	ASSUME	IO$_WRITEPBLK EQ <^B1011>
	ASSUME	IO$_READPBLK EQ <^B1100>
	ADDB3	#1,IRP$W_FUNC(R3),R0	; Increment function code
	BICB	#<^C<IRP$M_FCODE>>+1,R0	; Extract function code and clear LSB
	CMPB	R0,#IO$_READPBLK	; Read or write function?
	BNEQ	CANT_REORDER		; Branch if not, can't reorder

	PUSHQ	R6			; Save regs
	PUSHL	R8			;
	MOVL	R2,R6			; Copy pending queue listhead address
        MOVZBL  #MAX_IRPS_SCANNED,R8    ; Set limit on number of IRPs scanned
 
	SUBL3	UCB$L_CUR_LBN(R5),-	; Assume LBN of active IRP is directly
		IRP$L_MEDIA(R3),R4	; under heads (ideal displacement)
	BNEQ	NEXT_IRP		; Branch if not, continue scan
	BRW	SCAN_DONE		; Otherwise, no need to continue scan
	
SCAN_LOOP:

	ASSUME	IRP$S_FCODE LE 7	; Allow byte compare
	ASSUME	IO$_WRITEPBLK EQ <^B1011>
	ASSUME	IO$_READPBLK EQ <^B1100>
	ADDB3	#1,IRP$W_FUNC(R6),R0	; Increment function code
	BICB	#<^C<IRP$M_FCODE>>+1,R0	; Extract function code and clear LSB
	CMPB	R0,#IO$_READPBLK	; Read or write function?
	BNEQ	SCAN_DONE		; Branch if not, stop scan

; Here we build a bitmap in R1 to indicate the following:
;
;	Bit 3	- Set if the heads are moving towards higher LBNs
;	Bit 2	- Set if the optimal I/O found so far is requesting a 
;		  higher LBN than the current head position
;	Bit 1	- Set if the pending I/O currently being considered 
;		  is requesting a higher LBN than the current head position
;	Bit 0	- Set if the pending I/O currently being considered is
;		  requesting a higher LBN than the optimal I/O found so far
;
; We can then use this bitmap to dispatch to the appropriate code to
; either continue to use the optimal IRP or to replace it with the
; IRP in the pending queue.

5$:	MOVZBL	UCB$B_SEEK_DIR(R5),R1	; Set/clear seek direction bit in R1
10$:	TSTL	R4			; Is optimal I/O LBN > head position?
	BLSS	20$			; Branch if not
	ADDL	#4,R1			; Indicate optimal I/O LBN > head position
20$:	SUBL3	UCB$L_CUR_LBN(R5),-	; Calculate displacement to pending I/O
		IRP$L_MEDIA(R6),R7	;
	BEQL	PERFECT_IRP		; Found the perfect I/O, get out now
	BLSS	30$			; Branch if pending I/O LBN < head position
	ADDL	#2,R1			; Indicate pending I/O LBN > head position
30$:	CMPL	IRP$L_MEDIA(R6),-	; Compare LBNs of optimal and pending 
		IRP$L_MEDIA(R3)		; I/Os
	BLEQ	40$			; Branch if pending <= optimal
	INCL	R1			; Indicate pending > optimal

40$:	DISPATCH R1,TYPE=B,<-		; Dispatch based on the state in R1
		<0,	NEXT_IRP>,- 	; to either keep the current optimal
		<1,	SWAP_IRP>,-	; IRP or replace it with one from the
		<3,	NEXT_IRP>,-	; pending queue
		<4,	SWAP_IRP>,-
		<6,	SWAP_IRP>,-
		<7,	NEXT_IRP>,-
		<8,	NEXT_IRP>,-
		<9,	SWAP_IRP>,-
		<11,	SWAP_IRP>,-
		<12,	NEXT_IRP>,-
		<14,	SWAP_IRP>,-
		<15,	NEXT_IRP>>
			
	BUG_CHECK INCONSTATE,FATAL	; This should never happen

; The IRP in the pending queue was found to be closer to the heads than
; the optimal one. Therefore replace the optimal IRP with the one from
; the pending queue.

SWAP_IRP:

	MOVQ	R6,R3			; Replace the optimal IRP with the
					; one from the pending queue
        REMQUE  (R3),R3                 ; Remove IRP from current position in
        INSQUE  (R3),UCB$L_IOQFL(R5)    ; I/O queue and place at front of queue
	SUBL3	UCB$L_CUR_LBN(R5),-	; Update distance from current to optim
		IRP$L_MEDIA(R3),R4

NEXT_IRP:

	MOVL	(R6),R6			; Get address of next IRP in list
	CMPL	R6,R2			; End of list?
        BEQL    SCAN_DONE               ; Branch if so
        SOBGTR  R8,SCAN_LOOP            ; Branch if more IRPs are allowed to be

SCAN_DONE:

; R4 contains the signed displacement between the current head position and
; the LBN of the IRP that we've chosen to execute. Change the SEEK_DIR
; based on the which direction the heads will now be moving. If the LBN of
; the current IRP is directly under the heads, continue to scan in the same
; direction.

	INCL	UCB$L_FAIRNESS_CNT(R5)	; Assume perfect IRP
	TSTL	R4			; Time to change seek direction?
	BEQL	SCAN_RETURN		; Branch if not
	BGTR	10$			; 
	CLRB	UCB$B_SEEK_DIR(R5)	; Start motion head towards lower LBNs
	BRB	SCAN_RETURN		; Use common exit

10$:	MOVB	#8,UCB$B_SEEK_DIR(R5)	; Start motion towards higher LBNs
20$:	CLRL	UCB$L_FAIRNESS_CNT(R5)	; Indicate this is a new LBN

SCAN_RETURN:

        POPL    R8                      ; Restore registers
        POPQ    R6                      ;
        RSB                             ; Return to caller

; While scanning the pending queue, an IRP was found which is requesting 
; the LBN at the current head position. No need to scan any longer. Choose
; this IRP as the optimal one and get out.

PERFECT_IRP:

        CMPL    #FAIRNESS_CNT,-         ; Is this LBN monopolizing service? 
                UCB$L_FAIRNESS_CNT(R5)
        BLSS    NEXT_IRP                ; If so, don't choose this IRP after all
        REMQUE  (R6),R3                 ; Remove IRP from current position in
        INSQUE  (R3),UCB$L_IOQFL(R5)    ; I/O queue and place at front of queue
        SUBL3   UCB$L_CUR_LBN(R5),-     ; Update distance from current to -
                IRP$L_MEDIA(R3),R4      ;   optimal.
        BRB     SCAN_DONE


	.SBTTL	CHECK_HBS	- Determine if the device supports host-based 
	.SBTTL	-		  shadowing
;+
; CHECK_HBS
;
; This routine is called during the first PACKACK function to each unit to 
; determine if the device is capable of supporting host-based shadowing. The
; criteria is that the device must allow the forcing of bad blocks using
; the read long / write long SCSI commands. This routine performs a series
; of read longs with varying length fields trying to find one that succeeds.
; The problem here is that these commands are both optional and vendor-unique.
; If a read long command succeeds, the length used for the command is saved
; in the UCB so it can be used for future read long / write long commands.
; If no read long commands succeed, then the device is marked as not supporting 
; host-based shadowing.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- SPDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- SS$_NORMAL
;	R1-R2	- Destroyed
;	All other registers preserved
;
;	UCB$L_		- UCB$V_ set if device supports host-based 
;			  shadowing
;	UCB$W_READL_LEN	- Data length to use with future read long / write long
;			  commands
;-

CHECK_HBS:

	SUBSAVE				; Save return address
;
; Assume failure.
;
	BISL	#DEV$M_NOFE,-		; Drive does not support host based
		UCB$L_DEVCHAR2(R3)	;   shadowing. (No Forced Error)

	BBS	#DEV$V_SWL,-		; Branch if device is software
		UCB$L_DEVCHAR(R3),25$	; write locked. Can't support HBS.
	MOVAL	CMD_READ_LONG,R2	; Address of read long command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	ADDL3	#<2+4>,-		; Address of logical block address
		SCDRP$L_CMD_PTR(R5),R1	; field in SCSI command
	CLRL	(R1)			; Attempt a read long on block 0

; Since the read long command is vendor-unique, the transfer length field
; is not not known apriori. Instead, we must send the device a number of
; read long commands with varying length fields until the correct value is
; determined. The current values we try are: 0, 1, 512 -> 1024. In addition
; to checking for good SCSI status returned from the command, we also check
; for a transfer length returned of >= 512 bytes, since read long should return
; at least a block of data (and hopefully some ECC bytes as well).

	CLRW	UCB$W_READL_LEN(R3)	; First read long length to try

10$:	ADDL3	#<7+4>,-		; Address of length field in SCSI
		SCDRP$L_CMD_PTR(R5),R1	; command
	MOVB	UCB$W_READL_LEN+1(R3),-	; Fill in high-order byte of transfer
		(R1)+			; length
	MOVB	UCB$W_READL_LEN(R3),(R1); Fill in low-order byte

; Use the SPI$SEND_COMMAND macro directly rather than the SEND_COMMAND routine
; as we don't all the error checking performed by that routine.

	SPI$SEND_COMMAND		; Send the command
	BLBC	R0,30$			; Branch on error
	ASSUME	SCSI$C_GOOD_STATUS EQ 0
	MOVZBL	@SCDRP$L_STS_PTR(R5),R1	; Get SCSI status byte
	BICB	#^XC1,R1		; Clear reserved, vendor unique bits
	BNEQ	30$			; Branch if bad status
	CMPL	SCDRP$L_TRANS_CNT(R5),-	; Reasonable transfer length returned?
		#512			;
	BLSSU	30$			; Branch if not
	
; We've successfully sent a read long command and received a reasonable 
; amount of data in return. Clear the bit in the UCB indicating this device
; supports host-based shadowing.

	BICL	#DEV$M_NOFE,-		; Drive supports host based
		UCB$L_DEVCHAR2(R3)	;   shadowing.

20$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
25$:	MOVZWL	#SS$_NORMAL,R0		; Set unconditional success
	SUBRETURN			; Return to caller

; We've been unsuccessful in sending a read long command to the drive. Try
; the next data length value in the sequence, or, if we've reached the end of 
; the sequence, return without setting the "host-based shadowing supported" 
; bit in the UCB.

30$:	SPI$RELEASE_QUEUE		; Free the queue from the CHK COND
	CMPW	UCB$W_READL_LEN(R3),#1	; First or second value in sequence?
	BLSSU	40$			; Branch if first
	BEQL	50$			; Branch if second
40$:	INCW	UCB$W_READL_LEN(R3)	; Go to next value in sequence
	CMPW	UCB$W_READL_LEN(R3),-	; End of data length sequence?
		#READ_LONG_DATA_LEN	;
	BGTRU	20$			; Branch if so
	BRB	10$			; Try sending another read long command

50$:	MOVW	#512,UCB$W_READL_LEN(R3); Go to next value in sequence
	BRB	10$			; Try sending another read long command
;+
; FORCE_ERROR
;
; This routine is used to generate unrecoverable ECC errors on a set of
; logical blocks. It is used by shadowing code when the master copy of a 
; particular block has gone bad and the "forced error" bit for this block
; must be set for all members of the shadow set. Since SCSI doesn't support
; the forced error bit, the alternative is simply to generate an unrecoverable
; ECC error, so all subsequent reads to this block generate an error.
;
; The ECC error is generated by performing a read long, inverting every byte
; in the block, and performing a write long. 
;
; This routine performs it action by repeatedly calling FORCE_ONE_ERROR for
; each of the blocks associated with the QIO.
;
; INPUTS:
;
;	R2	- IRP address
;	R3	- UCB address
;	R4	- SPDT address
;	R5	- SCDRP address
;
;	UCB$W_READL_LEN	- Length to use for the read long / write long commands
;
; OUTPUTS:
;
;	R0	- Status
;	R1-R2	- Destroyed
;	All other registers preserved
;
;-

FORCE_ERROR:

	CLRL	SCDRP$L_ABCNT(R5)	; Initialize accumulated byte count
	MOVL	IRP$L_MEDIA(R2),-	; Copy disk logical block number from
		SCDRP$L_MEDIA(R5)	; the IRP to the SCDRP
	MOVL	#512,SCDRP$L_BCNT(R5)	; Force errors on one block at a time
	BBC	#MSCP$V_MD_ERROR,-	; Branch if the MD_ERROR bit not set.
		IRP$L_MEDIA+6(R2),50$	; This bit must be set to force an error
	BBC	#UCB$V_HBS_CHECK,-	; Branch if check for host-based 
		UCB$L_DK_FLAGS(R3),50$	; shadowing has not yet been made
	BBS	#DEV$V_SWL,-		; Branch if device is software
		UCB$L_DEVCHAR(R3),60$	; write locked. Can't support HBS.
10$:	BSBB	FORCE_ONE_ERROR		; Force an error on the next block
	BLBC	R0,30$			; Branch on error
	INCL	SCDRP$L_MEDIA(R5)	; Advance to next block
	ADDL	#512,SCDRP$L_ABCNT(R5)	; Update accumulated byte count
	MOVL	SCDRP$L_IRP(R5),R2	; Restore IRP address
	CMPL	SCDRP$L_ABCNT(R5),-	; Done with entire transfer?
		IRP$L_BCNT(R2)		;
	BLSSU	10$			; Branch if not
30$:	MOVL	SCDRP$L_ABCNT(R5),R1	; Get accumulated transfer count
	INSV	R1,#16,#16,R0		; Load low-order transfer count
	BRW	COMPLETE_IO		; Complete the QIO

; The check to determine if the device supports host-based shadowing has
; not yet been made. Therefore, we can attempt to for an error at this time.

50$:	MOVZWL	#SS$_UNSUPPORTED,R0	; Set unsupported status
	BRB	30$			; Use common exit	

; The device is write-locked and therefore can not accept the commands
; necessary to cause an uncorrectable ECC error.

60$:	MOVL	#SS$_WRITLCK,R0		; Set write-locked status
	BRB	30$			; Use common exit	


	.SBTTL	FORCE_ONE_ERROR	- Force an unrecoverable error on a block
;+
; FORCE_ONE_ERROR
;
; This routine is used to generate an unrecoverable ECC error on the specified
; logical block and is called by the routine FORCE_ERROR. The ECC error is 
; generated by performing a read long, inverting every byte in the block, and 
; performing a write long. Prior to performing the read long / write long
; sequence, the block is read to determine if it already contains a
; non-recoverable error. If so, no action is taken.
;
; INPUTS:
;
;	R2	- IRP address
;	R3	- UCB address
;	R4	- SPDT address
;	R5	- SCDRP address
;
;	UCB$W_READL_LEN	- Length to use for the read long / write long commands
;
; OUTPUTS:
;
;	R0	- Status
;	R1-R2	- Destroyed
;	All other registers preserved
;
;-

FORCE_ONE_ERROR:

	SUBSAVE				; Save return address

; First, perform a normal read command on the block to determine if the block
; already contains an uncorrectable error. If so, don't bother forcing another.


	BISW	#IRP$M_FUNC,-		; Set the FUNC but to indicate this
		SCDRP$W_STS(R5)		; is a read function
	MOVL	#512,R1			; Allocate an S0 buffer in which to read
	BSBW	ALLOC_POOL		; the block
	BISB	#SCDRP$M_S0BUF,-	; Indicate that this is an S0 "user"
		SCDRP$L_SCSI_FLAGS(R5)	; buffer
	MOVL	R2,SCDRP$L_SVA_USER(R5)	; Save address of S0 "user" buffer
	DISABLE_ERRLOG			; Temporarily disable errorlogging
	BSBW	READ_WRITE		; Send out a read command
	REENABLE_ERRLOG			; Reenable errorlogging
	PUSHL	R0			; Save status
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of S0 buffer
	BSBW	DEALLOC_POOL		; Deallocate the S0 buffer
	POPL	R0			; Restore R0
	CMPL	R0,#SS$_PARITY		; Already a hard error on the block?
	BEQLW	40$			; Branch if so

; Issue a read long command to get the current contents of the logical
; block.

	MOVAL	CMD_READ_LONG,R2	; Address of read long command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	ADDL3	#<7+4>,-		; Address of length field in SCSI
		SCDRP$L_CMD_PTR(R5),R1	; command
	MOVB	UCB$W_READL_LEN+1(R3),-	; Use length determined by CHECK_HBS
		(R1)+			; fill in high-order byte
	MOVB	UCB$W_READL_LEN(R3),(R1); and low-order byte
	ADDL3	#<6+4>,-		; Address just beyond logical block 
		SCDRP$L_CMD_PTR(R5),R1	; field in SCSI command
	MOVAL	SCDRP$L_MEDIA(R5),R0
	.REPT	4
	MOVB	(R0)+,-(R1)		; Copy LBN number to SCSI command
	.ENDR
	BSBW	SEND_COMMAND		; Send the command
	BLBC	R0,20$			; Branch on error

; Invert every byte in the original block to ensure that subsequent reads to
; the block will result in uncorrectable ECC errors.

	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of read long data
	MOVZBL	#<512/4>,R1		; Length of disk block in longwords
10$:	XORL2	#^XFFFFFFFF,(R0)+	; Invert a longword
	SOBGTR	R1,10$			; Repeat for the entire disk block

; Save the original (read long) SCDRP and allocate another one in preparation
; for sending a write long command.

	MOVL	R5,UCB$L_SCDRP_SAV1(R3)	; Save the original SCDRP 
	BSBW	ALLOC_SCDRP		; Allocate a new SCDRP for the write long
	MOVAL	CMD_WRITE_LONG,R2	; Address of write long command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	ADDL3	#<7+4>,-		; Address of length field in SCSI
		SCDRP$L_CMD_PTR(R5),R1	; command
	MOVB	UCB$W_READL_LEN+1(R3),-	; Use length determined by CHECK_HBS
		(R1)+			; fill in high-order byte
	MOVB	UCB$W_READL_LEN(R3),(R1); and low-order byte
	ADDL3	#<6+4>,-		; Address just beyond logical block 
		SCDRP$L_CMD_PTR(R5),R1	; field in SCSI command
	MOVL	UCB$L_SCDRP_SAV1(R3),R0	; Restore original SCDRP address
	MOVAL	SCDRP$L_MEDIA(R0),R2	; Address of LBN field from original SCDRP
	.REPT	4
	MOVB	(R2)+,-(R1)		; Copy LBN number to SCSI command
	.ENDR

; Copy the data, which is now innverted, from the read long buffer to the 
; write long buffer.

	MOVL	SCDRP$L_SVA_USER(R0),R0	; Get address of read long buffer
	MOVL	SCDRP$L_SVA_USER(R5),R1	; Get address of write long buffer
	ASSUME READ_LONG_DATA_LEN EQ WRITE_LONG_DATA_LEN
	MOVZWL	#READ_LONG_DATA_LEN,R2	; Number of bytes to copy 
	PUSHR	#^M<R3,R4,R5>		; Save regs
	MOVC3	R2,(R0),(R1)		; Copy read long buf to write long buf
	POPR	#^M<R3,R4,R5>		; Restore regs

; Send the write long command, then perform the cleanup necessary for both
; commands.

	BSBW	SEND_COMMAND		; Send the command
	BSBW	CLEANUP_CMD		; Cleanup from the write long command
	BSBW	DEALLOC_SCDRP		; Deallocate the write long SCDRP
	MOVL	UCB$L_SCDRP_SAV1(R3),R5	; Restore original SCDRP address
20$:	BSBW	CLEANUP_CMD		; Cleanup from the read long command
30$:	SUBRETURN			; Return to caller

; The block already has a non-recoverable error associated with it. Don't
; bother forcing a second error, since if the current error is due to a
; previous force error function, issuing a second force error will have the
; effect of making the block good again. Instead, simply return success status.

40$:	MOVZWL	#SS$_NORMAL,R0		; Set success status
	BRB	30$			; Use common exit
	BRB	30$			; Use common exit	

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

	$DEFINI	TRACE_BUFFER_HEADER

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

	$DEFEND	TRACE_BUFFER_HEADER

	$DEFINI	TRACE_BUFFER_QIO_HEADER

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

	$DEFEND	TRACE_BUFFER_QIO_HEADER

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

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

TR$TRACE_BUFFER_ADDR:
	.LONG	0

TR$TRACE_ROUTINES:

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

	.MACRO	TRACE_ROUTINES, LIST, ?L

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

	.ENDM	TRACE_ROUTINES

	TRACE_ROUTINES	<-
		DK$DDT,-
		DK_CTRL_INIT,-
		DK_UNIT_INIT,-
		DK_STARTIO,-
		DK_REG_DUMP,-
		IO_PACKACK,-
		IO_READPBLK,-
		IO_WRITEPBLK,-
		INQUIRY,-
		MODE_SENSE,-
		MODE_SELECT,-
		SET_UNIT_ONLINE,-
		SEND_COMMAND,-
		LOG_ERROR,-
		SET_CONN_CHAR>


	.SBTTL	SETUP_TRACE,	Allocate and set up trace buffer
;+
; SETUP_TRACE
;
; This routine is called by unit init to allocate a trace buffer, which
; is used to trace QIOs through the driver. The auxstruc field in the CRB is
; filled in with the address of a cell which contains the address of the trace
; buffer. Since the driver can have more than one unit, only the first
; call to this routine actually performs the allocation. Since pool allocation 
; can stall this thread, the TR$TRACE_BUFFER_ADDR cell is given a value of
; 1 (positive integer) to indicate that poll allocation is on progress. This
; prevents a second thread from allocating another buffer.
;
; INPUTS:
;
;	R5	- UCB address
;
; OUTPUTS:
;
;	R0-R3	- Destroyed
;	All other registers preserved
;
;	CRB$L_AUXSTRUC	- Address of cell containing trace buffer address
;-

SETUP_TRACE:
					
	MOVL	R5,R3			; Copy UCB address
	SUBSAVE				; Save return address

	.IF DEFINED DEBUG
	TSTL	TR$TRACE_BUFFER_ADDR	; Trace buffer already set up?
	BNEQ	10$			; Branch if so
	INCL	TR$TRACE_BUFFER_ADDR	; Trace buffer setup in progress
	MOVL	#TR$TRACE_BUFFER_SIZE,R1; Get size of trace buffer
	BSBW	ALLOC_POOL		; Allocate the trace buffer
	BLBC	R0,10$			; Branch if failure
	MOVL	#TR$TRACE_BUFFER_SIZE,-	; Save trace buffer size
		TR$L_SIZE(R2)		;
	MOVL	#TR$C_HEADER_SIZE,-	; Save header size
		TR$L_HEADER_SIZE(R2)	;
	MOVL	#TR$TOTAL_QIOS,-	; Save total number of QIOs in
		TR$L_TOTAL_QIOS(R2)	; buffer
	MOVL	#TR$QIO_SIZE,-		; Save size of QIO
		TR$L_QIO_SIZE(R2)	;
	MOVL	#TR$C_QIO_HEADER_SIZE,-	; Save size of QIO header
		TR$L_QIO_HEADER_SIZE(R2); 
	MOVL	R2,TR$TRACE_BUFFER_ADDR	; Save trace buffer address
10$:
	.ENDC

	MOVL	UCB$L_CRB(R5),R0	; Get CRB address
	MOVL	CRB$L_AUXSTRUC(R0),R0	; Get CDDB address
	MOVAL	TR$TRACE_BUFFER_ADDR,-	; Save address of cell pointing to 
		CDDB$L_PDT(R0)		; trace buffer in CRB
	SUBRETURN			; Return to caller
		
	.IF DEFINED DEBUG


	.SBTTL	TRACE_QIO	- Trace QIO requests
;+
; TRACE_QIO
;
; This routine logs information from the current QIO packet including the
; function code, MEDIA, BCNT, and BOFF fields.
;
; INPUTS:
;
;	R3	- IPR address or 0 if UNIT INIT
;	R5	- UCB address
;
; OUTPUTS:
;
;	All registers preserved
;-

TRACE_QIO:

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


	.SBTTL	TRACE_QIO_STAT	- Save final status from QIO in trace buffer
;+
; TRACE_QIO_STAT
;
; This routine saves the final QIO status in the trace buffer.
;-

TRACE_QIO_STAT:

	MOVL	UCB$L_TR_QIO_STS(R5),R2	; Get address to put status
	BEQL	10$			; Branch if uninitialized
	MOVL	R0,(R2)			; Save status in trace buffer
10$:	RSB


	.SBTTL	TRACE_SCSI_CMD	- Trace SCSI command bytes
;+
; TRACE_SCSI_CMD
;
; This routine saves the contents of the SCSI command in the trace buffer.
;-

TRACE_SCSI_CMD:

	rsb
	.if defined trace_scsi_commands
	PUSHR	#^M<R0,R1,R2,R3>	; Save regs
	MOVL	SCDRP$L_UCB(R5),R3	; Get UCB address
	MOVL	TR$CURRENT_QIO,R0	; Get QIO number
	BLSS	20$			; Branch if negative, must be in UNIT INIT
	MULL3	#TR$QIO_SIZE,R0,R1	; Get offset of this QIO in trace buffer
	MOVAL	TR$TRACE_BUFFER,R2	; Get trace buffer address
	ADDL	R1,R2			; Address to place info for this QIO
	ADDL	#8,R2			; Skip QIO number, valid flag fields
	CMPL	(R2),-			; Maximum SCSI commands exceeded?
		#TR$SCSI_CMDS_PER_QIO
	BGEQ	20$			; Branch if so
	INCL	(R2)			; One more SCSI command sent
	MOVL	SCDRP$L_CMD_PTR(R5),R0	; Get address of SCSI command
	MOVL	(R0)+,R1		; Get size of SCSI command
	MOVL	UCB$L_TR_SCSI_CMD(R3),R2; Address to save CMD in
	MOVL	R1,(R2)+		; Save length of command
10$:	MOVB	(R0)+,(R2)+		; Save a command byte
	SOBGTR	R1,10$			; Repeat for entire command
	MOVL	UCB$L_TR_SCSI_CMD(R3),R2; Restore command pointer
	ADDL3	R2,#TR$CMD_BYTES+4,-	; Save address to put SCSI status byte
		UCB$L_TR_SCSI_STS(R5)	;
	ADDL3	R2,#TR$SCSI_CMD_SIZE,-	; Save address to put next SCSI command
		UCB$L_TR_SCSI_CMD(R3)	;
20$:	POPR	#^M<R0,R1,R2,R3>	; Restore regs
	RSB				; Return to caller
	.endc


	.SBTTL	TRACE_SCSI_STS	- Trace SCSI status bytes
;+
; TRACE_SCSI_STS
;
; This routine saves the SCSI status byte in the trace buffer.
;-

TRACE_SCSI_STS:

	rsb
	.if defined trace_scsi_status
	PUSHQ	R1			; Save reg
	MOVL	SCDRP$L_UCB(R5),R1	; Get UCB address
	MOVL	UCB$L_TR_SCSI_STS(R1),R1; Get address to put status bytes
	CMPL	(R1),#TR$STATUS_BYTES	; Status bytes exceeded?
	BGEQ	10$			; Branch if so
	MOVAL	4(R1),R2		; Get address of first status byte
	ADDL	(R1),R2			; Place to put this message byte
	MOVB	R0,(R2)			; Save status byte
	INCL	(R1)			; One more status byte received
10$:	POPQ	R1			; Restore reg
	RSB				; Return to caller
	.endc
	.ENDC				; .IF DEFINED DEBUG

DK_PATCH:
	.BLKB	200			; Patch space

DK_END:					; Last location in driver
	.END
