	.TITLE	DKDRIVER - VAX/VMS SCSI Disk Class Driver
	.IDENT	'X-47'
;****************************************************************************
;*									    *
;*  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-47	GCE		Glenn C. Everhart		14-Jan-1996
;		Prevent user mode code doing io$_audio from crashing if the
;		AUCB is bad or if mount verify might start.
;	X-46	TGG0121		Tom Goodwin			18-Jan-1996
;		Add Mode Sense Common support changes. Removed old MODE_SENSE
;		and MODE_SELECT routines and replaced  with new
;		PROCESS_MODE_INFO routine ported from OpenVMS ALPHA.
;
;		Incorporate all VAX applicable bug fixes from the OpenVMS
;		Alpha V6.2 release as detailed below:
;
;		X-30  RCL0001           Rick Lord               3-May-95
;               Fold of X18U9 into Theta: Add bounds-checking code at label 36$
;               in DK_REG_DUMP to make sure that the routine does not overflow
;		the buffer allocated by the error logging routine.
;		X-18U8  JFD0818         James F. Dunham         14-APR-1995
;               o Skip flexible disk geometry page if mode sense for rigid disk
;                 geometry page succeeds
;               o Add (disabled) support for RZ74 SCSI drive disable caching
;		  code
;               o Make sure R1 is clear (MSB of xfer count) on error paths
;                 prior to calling REQCOM
;               o Change some LOG_ERRORs, to return a fatal status .vs. 
;		  SS$_NORMAL
;               o Remove 1st error log entry for SCSI$C_BUSY, to last entry
;               o Add SS$_MEDOFL to the list of errors, which DKDRIVER doesn't
;		  log
;               o Remove DISPATCH macro usage, due to too not a dense set of
;		  values
;               o Pick up X-26 from Theta Stream
;		X-18U7  JFD0817         James F. Dunham          4-APR-1995
;               o Remove private defintions of IRP$V_FORCEMV and
;		  CAN$C_MSCPSERVER
;               o Decrease queue table depth sizes.
;               o Increate queue scan histogram counter
;		X-18U6  RCL     Rick Lord                       5-Apr-95
;               1) Make WCE a preferred bit in it's caching page descriptor
;		   rather than setting to explicitly in-line, since it is the 
;		   more common case. The only section of code which did not
;		   alter it was for non-512-byte block size devices, which for 
;		   now would be only CD-ROMs, and WCE is not an issue for them.
;               2) Create LOAD_TENBYTE and STORE_TENBYTE macros to simplify
;		   saving and restoring the TENBYTE bits in the UCB and SCDRP 
;		   (they're not the same bit offset).
;               3) In PROCESS_MODE_INFO, make the error recovery page the first
;		   page for all types of devices, not just for CD-ROMs, and if 
;		   it's successful then the state of the SCDRP.TENBYTE bit in 
;		   the UCB.
;               4) In PROCESS_MODE_INFO, refresh the SCDRP.TENBYTE bit from 
;		   UCB.TENBYTE if any DO_MODE_PAGE fails and the routine is
;		   going to continue - if it's going to exit then there's no
;		   need to do the refresh.
;               5) In PROCESS_MODE_FORMAT_FLOPPY, initialize the SCDRP.TENBYTE 
;		   bit from UCB.TENBYTE prior to the first DO_MODE_PAGE. 
;		   There's no need to refresh the SCDRP bit in this routine
;		   because it exits if DO_MODE_PAGE fails.
;		X-18U4  SCS             Sue Sommer              17-Mar-1995
;               - Convert any errors from PROCESS_MODE_INFO to SS$_MEDOFL
;                 so that retries are possible.
;		X-18    RCL             Rick Lord               1-Feb-95
;               In IO_PACKACK, if PROCESS_MODE_INFO fails for any reason and the
;               TENTYE  bit is still set, clear it and try PROCESS_MODE_INFO
;		again. This works around devices which appear to complete 
;		10-byte mode sense requests successfully but in fact don't 
;		send usable data.
;		X-17    GWW             Grace Wang              27-Jan-1995
;	        In PROCESS_MODE_INFO, default sector=4, track=6 to avoid
;               MSCP Server crash (see Zeta QAR 2688 - with UCB$B_TRACKS
;               initialized to zero, an unknown cause left it zero after
;               PROCESS_MODE_INFO and VALIDATE_GEOMETRY of a RZ25 device.
;               MSCP Server copies this zero to MSCP$W_GROUP and bugchecks
;               to show that it won't tolerate zero geometry value.)
;		X-7     SCS              Sue Sommer              7-Nov-1994
;               Add TENBYTE flag to DK_FLAGS to indicate support for 10-byte
;               mode sense commands.  Modify PROCESS_MODE_INFO to reference
;               this flag and prevent unnecessary 10-byte command attempts.
;
;       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
	$MODEDEF			; Mode page definitions
	$MSCPDEF			; Define MSCP symbols
	$NSADEF				; Security Symbols
	$ORBDEF				; Object Rights block
	$PCBDEF				; Process control block
	$PRVDEF				; Privilege mask
	$PTEDEF				; Page table entry symbols
	$SCSIDEF			; SCSI definitions
	$SCDRPDEF			; SCSI SCDRP symbols
	$SPDTDEF			; SCSI PDT symbols
	$SSDEF				; System status codes
	$UCBDEF				; Unit control block
	$VADEF				; Virtual address symbols
	$VCBDEF				; Volume Control Block 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 		= 10	; Number of times to retry PACKACK
	READY_POLL_INTERVAL	= 1	; Interval for test unit ready polling
	READY_POLL_CNT		= 30	; 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		= 3	; Read/write retry count
	REASSIGN_RETRY_CNT	= 3	; Retry count for block reassignment
	REWRITE_RETRY_CNT	= 3	; Retry count for rewrite after reassign	
	HWERR_RETRY_CNT		= 10	; Retry count for hardware errors

	DEFAULT_DISCONNECT_TIMEOUT = 20	; Default values in seconds for disconnect
	DEFAULT_PHASE_CHANGE_TIMEOUT = 4; and phase change timeouts
	RRD40_DISC_TMO 		= 20	; RRD40 disconnect timeout value
	RRD42_DISC_TMO 		= 120	; RRD42 disconnect timeout value
	RX_DISC_TMO 		= 30	; Floppy disconnect timeout value
	RX_PHS_TMO		= 10	; Floppy phase change timeout value
	RX_FMT_DISC_TMO		= 10*60	; Floppy format disconnect timeout value
	REASSIGN_DISCON_TMO	= 60	; 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	= 10		; Maximum amount of time to allow a
					; device to remain busy before resetting
					; the bus
	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
					
; 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 = 100	; Recalc qdepth after this many I/O

	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
                <TENBYTE,,M>>		; Device supports 10-byte mode sense command

$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
UCB$B_ADDL_SENSE_INFO==UCB$B_SENSE_INFO	; Make it a global symbol for use
					; by mode sense common
$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,8		; 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>>

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>>
        .IF DEFINED RZ74_CACHE
        .print ; Add comment to previous line
<<RZ74>,    RZ74,         ,       <436A>, YES,  YES, DEFAULT, DEFAULT>>
        .ENDC

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

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

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

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

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

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

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

.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,0,<^XFF>,0>,-
		DMA_LEN = 255,-
		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	 Dummy CDB Definition for Error Logging

DUMMY_MODE_SENSE_CDB:			; For error logging purposes.
		.LONG	^XFF		; Dummy status
		.LONG	6		; Command length
		.BYTE	^X1A,0,0,0,100,0 ; Command bytes
		.BLKB	2		; Pad for alignment 


	.SBTTL	 Mode Descriptor Definitions

; Descriptors for mode page fields, used by MODE SENSE operations.

PREF  = MODE_DESC$M_PREFERRED	
REQ   = MODE_DESC$M_REQUIRED	
MISS  = MODE_DESC$M_IFMISSING	
MISM  = MODE_DESC$M_IFMISMATCH	
DPTR  = MODE_DESC$M_DESPTR	
APTR  = MODE_DESC$M_ACTPTR	
LAST  = MODE_DESC$M_LAST	
MBOFF = MODE_DESC$C_MEDTYP_BOFF
DBOFF = MODE_DESC$C_DEVPAR_BOFF
DNBOFF= MODE_DESC$C_DENS_BOFF
BBOFF = MODE_DESC$C_BLKLEN_BOFF

	.MACRO	MD_DSC PAGE,NAME,BOFF,BIT_OFFSET,BIT_SIZE,DESIRED,FLAGS
		'PAGE'_'NAME'_DESC = . - DESCRIPTOR_BASE ; Offset from base
		.LONG 'BOFF'            ; Byte offset in page
		.LONG 'BIT_OFFSET'      ; Bit offset within byte
		.LONG 'BIT_SIZE'        ; Size of field in bits
		.LONG 'FLAGS'           ; Flag bits
		.QUAD 'DESIRED'		; Desired value
		.BLKL  2		; Actual value
		.BLKL  1		; Difference reason
		.BLKL  1		; Reserved bits

	.ENDM	MD_DSC

;	When performing a Mode Sense command, it isn't always possible to determine the 
;	reason for a command failure. If we are issuing a 10-byte Mode Sense command and
;	it fails, it may be because the page itself is not implemented by the device, or
;	it may be because the device just doesn't support the 10-byte Mode Sense command.
;	In an attempt to recover from an ambiguous failure, DO_MODE_PAGE backs off from
;	10-byte and tries a 6-byte command, which is a mandatory command; if that fails,
;	rather than make any broad assumption about the nature of the failure based on
;	the one page it's trying to process, it simply leaves the TENBYTE bit clear.
;
;	We're assuming that this type of ambiguity is only a concern for certain pages.
;	If the device is a disk, we can't tell if it's a floppy or not, so we don't know
;	if it supports the flexible disk parameters page or not; also, the SCSI standard
;	does not explicitly prohibit floppy disks from implementing the rigid disk geometry
;	page.
;
;	The first 10-byte Mode Sense command issued is to a required page, if
;	it completes successfully then all subsequent mode sense commands should use
;	the 10-byte format.  But any time that the 10-byte command fails the 6-byte command
;	is tried before reporting any failure.  Therefore, after the first mode sense
;	command the state of the SCDRP TENBYTE bit is saved in the UCB and it is refreshed
;	to the SCDRP from the UCB before each subsequent mode sense command.
	
;	Propagate the state of the UCB's TENBYTE bit to the SCDRP

	.MACRO	LOAD_TENBYTE	ucb=R3,scdrp=R5,?L1,?L2
 	BBC	#UCB$V_TENBYTE,UCB$L_DK_FLAGS(ucb),L1
	BISL	#SCDRP$M_TENBYTE,SCDRP$L_SCSI_FLAGS(scdrp)
	BRW	L2
L1:	BBCC	#SCDRP$V_TENBYTE,SCDRP$L_SCSI_FLAGS(scdrp),L2
L2:	.ENDM	LOAD_TENBYTE

;	Propagate the state of the SCDRP's TENBYTE bit to the UCB

	.MACRO	STORE_TENBYTE	ucb=R3,scdrp=R5,?L1,?L2
	BBC	#SCDRP$V_TENBYTE,SCDRP$L_SCSI_FLAGS(scdrp),L1
	BISL2	#UCB$M_TENBYTE,UCB$L_DK_FLAGS(ucb)
	BRW	L2
L1:	BBCC	#UCB$V_TENBYTE,UCB$L_DK_FLAGS(ucb),L2
L2:	.ENDM	STORE_TENBYTE


	.ALIGN LONG
DESCRIPTOR_BASE = .

;-------------------------------------------------------------------------------
ERR_PAGE_DESC = . - DESCRIPTOR_BASE

;          Page name       Bitname  Boff  Bit  Size  Val    Flags   
;          ---------	   -------  ----  ---  ----  ---    -----
MD_DSC	         ERR,      BLKLEN,  BBOFF, 0,   24, <^X200>,<REQ!MISS!MISM!LAST>
SIZEOF_ERR = . - <DESCRIPTOR_BASE + ERR_PAGE_DESC>

;-------------------------------------------------------------------------------
SCSI2_TCQ_ERR_PAGE_DESC = . - DESCRIPTOR_BASE    				; SCSI2 Tagged Queuing devices, R/W err rec page
										; Same values required for SCSI clusters 
;          Page name       Bitname  Boff  Bit  Size  Val    Flags   
;          ---------	   -------  ----  ---  ----  ---    -----
MD_DSC	  SCSI2_TCQ_ERR,    AWRE,     2,   7,    1,    1,   <PREF>
MD_DSC	  SCSI2_TCQ_ERR,    ARRE,     2,   6,    1,    1,   <PREF>
MD_DSC	  SCSI2_TCQ_ERR,      TB,     2,   5,    1,    1,   <PREF>
MD_DSC	  SCSI2_TCQ_ERR,      RC,     2,   4,    1,    0,   <REQ!MISS!MISM>
MD_DSC	  SCSI2_TCQ_ERR,     PER,     2,   2,    1,    1,   <PREF>
MD_DSC	  SCSI2_TCQ_ERR,     DTE,     2,   1,    1,    1,   <PREF>
MD_DSC	  SCSI2_TCQ_ERR,  MEDTYP, MBOFF,   0,    8,    0,   0
MD_DSC	  SCSI2_TCQ_ERR,  DEVSPC, DBOFF,   0,    8,    0,   <LAST>
SIZEOF_SCSI2_TCQ = . - <DESCRIPTOR_BASE + SCSI2_TCQ_ERR_PAGE_DESC>

;-------------------------------------------------------------------------------
SCSI2_NOTCQ_ERR_PAGE_DESC = . - DESCRIPTOR_BASE             			; SCSI2 non-Tagged Queuing devices, R/W err rec pg
										; If possible, should look like SCSI1 
;          Page name       Bitname  Boff  Bit  Size  Val    Flags   
;          ---------	   -------  ----  ---  ----  ---    -----
MD_DSC	  SCSI2_NOTCQ_ERR,  AWRE,     2,   7,    1,    0,   <PREF>
MD_DSC	  SCSI2_NOTCQ_ERR,  ARRE,     2,   6,    1,    0,   <PREF>
MD_DSC	  SCSI2_NOTCQ_ERR,    TB,     2,   5,    1,    1,   <PREF>
MD_DSC	  SCSI2_NOTCQ_ERR,    RC,     2,   4,    1,    0,   <REQ!MISS!MISM>
MD_DSC	  SCSI2_NOTCQ_ERR,   PER,     2,   2,    1,    1,   <PREF>
MD_DSC	  SCSI2_NOTCQ_ERR,   DTE,     2,   1,    1,    1,   <PREF>
MD_DSC	  SCSI2_NOTCQ_ERR,MEDTYP, MBOFF,   0,    8,    0,   0
MD_DSC	  SCSI2_NOTCQ_ERR,DEVSPC, DBOFF,   0,    8,    0,   <LAST>
SIZEOF_SCSI2_NOTCQ = . - <DESCRIPTOR_BASE + SCSI2_NOTCQ_ERR_PAGE_DESC>

;-------------------------------------------------------------------------------
SCSI1_ERR_PAGE_DESC = . - DESCRIPTOR_BASE                    			; SCSI1 devices, R/W error recovery page

;          Page name       Bitname  Boff  Bit  Size  Val    Flags   
;          ---------	   -------  ----  ---  ----  ---    -----
MD_DSC	  SCSI1_ERR,        AWRE,     2,   7,    1,    0,   <REQ!MISS!MISM>
MD_DSC	  SCSI1_ERR,        ARRE,     2,   6,    1,    0,   <REQ!MISS!MISM>
MD_DSC	  SCSI1_ERR,          TB,     2,   5,    1,    1,   <PREF>
MD_DSC	  SCSI1_ERR,          RC,     2,   4,    1,    0,   <REQ!MISS!MISM>
MD_DSC	  SCSI1_ERR,         PER,     2,   2,    1,    1,   <PREF>
MD_DSC	  SCSI1_ERR,         DTE,     2,   1,    1,    1,   <PREF>
MD_DSC	  SCSI1_ERR,      MEDTYP, MBOFF,   0,    8,    0,   0
MD_DSC	  SCSI1_ERR,      DEVSPC, DBOFF,   0,    8,    0,   <LAST>
SIZEOF_SCSI1 = . - <DESCRIPTOR_BASE + SCSI1_ERR_PAGE_DESC>

;-------------------------------------------------------------------------------
NON512_ERR_PAGE_DESC = . - DESCRIPTOR_BASE                      ; Devices with blk size not = 512, R/W error recovery page
								; Don't care about AWRE/ARRE for these devices
;          Page name       Bitname  Boff  Bit  Size  Val    Flags   
;          ---------	   -------  ----  ---  ----  ---    -----
MD_DSC	  NON512_ERR,         TB,     2,   5,    1,    1,   <PREF>
MD_DSC	  NON512_ERR,         RC,     2,   4,    1,    0,   <REQ!MISS!MISM>
MD_DSC	  NON512_ERR,        PER,     2,   2,    1,    1,   <PREF>
MD_DSC	  NON512_ERR,        DTE,     2,   1,    1,    1,   <PREF>
MD_DSC	  NON512_ERR,     MEDTYP, MBOFF,   0,    8,    0,   0
MD_DSC	  NON512_ERR,     DEVSPC, DBOFF,   0,    8,    0,   <LAST>
SIZEOF_NON512 = . - <DESCRIPTOR_BASE + NON512_ERR_PAGE_DESC>

;-------------------------------------------------------------------------------
CACHING_PAGE_DESC = . - DESCRIPTOR_BASE

;          Page name       Bitname  Boff  Bit  Size  Val    Flags   
;          ---------	   -------  ----  ---  ----  ---    -----
MD_DSC	       CACHE,         WCE,    2,   2,    1,    0,   <PREF!LAST>
SIZEOF_CACHING = . - <DESCRIPTOR_BASE + CACHING_PAGE_DESC>

        .IF DEFINED RZ74_CACHE
;-------------------------------------------------------------------------------
NOCACHING_PAGE_DESC = . - DESCRIPTOR_BASE

;          Page name       Bitname  Boff  Bit  Size  Val    Flags
;          ---------       -------  ----  ---  ----  ---    -----
MD_DSC         CACHE,         RCD,    2,   0,    1,    1,   <REQ!MISS!MISM>
MD_DSC         CACHE,         WCE,    2,   2,    1,    0,   <REQ!MISS!MISM!LAST>
SIZEOF_NOCACHING = . - <DESCRIPTOR_BASE + NOCACHING_PAGE_DESC>

        .ENDC
;-------------------------------------------------------------------------------
CONTROL_MODE_PAGE_DESC = . - DESCRIPTOR_BASE

;          Page name       Bitname  Boff  Bit  Size  Val    Flags   
;          ---------	   -------  ----  ---  ----  ---    -----
MD_DSC	         CTL,        QERR,    3,   1,    1,    0,   <REQ!MISS!MISM>
MD_DSC	         CTL,        EECA,    4,   7,    1,    0,   <REQ!MISS!MISM!LAST>
;-------------------------------------------------------------------------------
RIGID_DISK_PAGE_DESC = . - DESCRIPTOR_BASE

;          Page name       Bitname          Boff  Bit  Size  Val    Flags   
;          --------- -------------------    ----  ---  ----  ---    -----
MD_DSC	    RGD,     TRACKS_PER_CYLINDER,    5,   0,    8,    0,   <MISS>
MD_DSC	    RGD,           NUM_CYLINDERS,    3,   0,   16,    0,   <MISS!LAST>
SIZEOF_RIGID_DISK = . - <DESCRIPTOR_BASE + RIGID_DISK_PAGE_DESC>
;-------------------------------------------------------------------------------
FORMAT_DEVICE_PAGE_DESC = . - DESCRIPTOR_BASE

;          Page name       Bitname          Boff  Bit  Size  Val    Flags   
;          --------- -------------------    ----  ---  ----  ---    -----
MD_DSC	    FMT,      SECTORS_PER_TRACK,     11,   0,    8,    0,   <MISS>
MD_DSC	    FMT,      SECTOR_SIZE_MSB,       12,   0,    8,    0,   <MISS>
MD_DSC	    FMT,      SECTOR_SIZE_LSB,       13,   0,    8,    0,   <MISS!LAST>
SIZEOF_FORMAT = . - <DESCRIPTOR_BASE + FORMAT_DEVICE_PAGE_DESC>
;-------------------------------------------------------------------------------
FORMAT_DEVICE_SEL_PAGE_DESC = . - DESCRIPTOR_BASE

;          Page name       Bitname         Boff  Bit  Size  Val    Flags   
;          --------- -------------------   ----  ---  ----  ---    -----
MD_DSC	    FMT,     SEL_SECTORS_PER_TRACK,  10,  0,   16,    0,   <REQ!MISS!MISM!LAST>
;-------------------------------------------------------------------------------
FLEX_DISK_PAGE_DESC = . - DESCRIPTOR_BASE

;          Page name       Bitname         Boff  Bit  Size  Val    Flags   
;          --------- -------------------   ----  ---  ----  ---    -----
MD_DSC	    FLX,       TRANSFER_RATE,        2,   0,   16,   0,   <MISS>
MD_DSC	    FLX, TRACKS_PER_CYLINDER,        4,   0,    8,   0,   <MISS>
MD_DSC	    FLX,       NUM_CYLINDERS,        8,   0,   16,   0,   <MISS>
MD_DSC	    FLX,             PAG_LEN,        1,   0,    8,   0,   <MISS>
MD_DSC	    FLX,                 SPC,       22,   0,    4,   0,   <LAST>
SIZEOF_FLEX_DISK = . - <DESCRIPTOR_BASE + FLEX_DISK_PAGE_DESC>
;-------------------------------------------------------------------------------
FLEX_DISK_SELECT_PAGE_DESC = . - DESCRIPTOR_BASE

;          Page name       Bitname         Boff  Bit  Size  Val    Flags   
;          --------- -------------------   ----  ---  ----  ---    -----
MD_DSC	    FLX,   SEL_TRANSFER_RATE,        2,   0,   16,   0,   <REQ!MISS!MISM>
MD_DSC	    FLX,           NUM_HEADS,        4,   0,    8,   0,   <REQ!MISS!MISM>
MD_DSC	    FLX,   SECTORS_PER_TRACK,        5,   0,    8,   0,   <REQ!MISS!MISM>
MD_DSC	    FLX,   SEL_NUM_CYLINDERS,        8,   0,   16,   0,   <REQ!MISS!MISM>
MD_DSC	    FLX,    START_SECTOR_NUM,       21,   6,    1,   0,   <REQ!MISS!MISM>
MD_DSC	    FLX,             SEL_SPC,       22,   0,    4,   0,   <REQ!MISS!MISM!LAST>
;-------------------------------------------------------------------------------
DESCRIPTOR_SIZE = . - DESCRIPTOR_BASE

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

; This table is used to convert between the vendor specific (RX26) 
; 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_MEDIA_TYPE_MAX =<.-MODE_SENSE_MEDIA_TYPE_TABLE-1>


; Index into this table with irp$l_media-1
; Note: Tables are biased to exclude (SCSI$FMT$R_PAGE_CODE, SCSI$FMT$B_PAGE_LENGTH)
;
MODE_SELECT_FORMAT_TABLE:
	.LONG	MODE_SELECT_FORMAT_RX26_DD-MODE_SELECT_FORMAT_TABLE-SCSI$FMT$W_TRACKS
	.LONG	MODE_SELECT_FORMAT_RX26_HD-MODE_SELECT_FORMAT_TABLE-SCSI$FMT$W_TRACKS
	.LONG	MODE_SELECT_FORMAT_RX26_ED-MODE_SELECT_FORMAT_TABLE-SCSI$FMT$W_TRACKS

;
; Index into this table with irp$l_media-1
;
MODE_SELECT_FLEXIBLE_TABLE:
	.LONG	MODE_SELECT_FLEXIBLE_RX26_DD-MODE_SELECT_FLEXIBLE_TABLE-SCSI$FLX$W_TRANSFER_RATE
	.LONG	MODE_SELECT_FLEXIBLE_RX26_HD-MODE_SELECT_FLEXIBLE_TABLE-SCSI$FLX$W_TRANSFER_RATE
	.LONG	MODE_SELECT_FLEXIBLE_RX26_ED-MODE_SELECT_FLEXIBLE_TABLE-SCSI$FLX$W_TRANSFER_RATE

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


	.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$:	BISL2   #UCB$M_TENBYTE,-	; Assume 10-byte mode sense support
                UCB$L_DK_FLAGS(R5)

	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

        CLRL    R1                      ; Clear transfer byte count
        CMPL    R0,#SS$_RECOVERR        ; Recoverable error status?
        BEQL    100$                    ; Branch if so,
        CMPL    R0,#SS$_TIMEOUT         ; Timeout error status?
        BEQL    110$                    ; Branch if so,
        CMPL    R0,#SS$_DRVERR          ; Drive error status?
        BEQL    120$                    ; Branch if so,
        CMPL    R0,#SS$_CTRLERR         ; Controller error status?
        BEQL    120$                    ; Branch if so,
        CMPL    R0,#SS$_DEVOFFLINE      ; Offline error status?
        BEQL    120$                    ; Branch if so,
        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 mount verify
; 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

;+
; Check that the disk is not mounted and thus not subject to mount verification.
; A file structured mounted disk will not be an audio disk in general and
; we don't want to have any random audio errors result in mount verify
; trying to recycle IRPs after the audio code cleans up buffers.
;
; Disallow shadow set members too...we don't want anyone to be able to
; put the shadowset into MVfy somehow with this.
;
;-
	BBS	#DEV$V_SSM,UCB$L_DEVCHAR2(R5),3$ ;U A; Shadow set member? If so reject
	BBC	#DEV$V_MNT,UCB$L_DEVCHAR(R5),4$	;U A; Are we mounted?
	BBS	#DEV$V_FOR,UCB$L_DEVCHAR(R5),4$	;U A; If so if it's /for, all ok
; If mounted and NOT foreign, we still must be sure the disk in fact has
; mount verify enabled. ISO disks will not, and one can mount with /nomount_ver
; also. In those cases we won't see the IRP again. (To be safe, there is a test
; at IO_AUDIO: also that will detect failed and cleaned-up functions and
; junk the I/O there too...but we don't want random users able to put disks
; into mount verify just by issuing audio functions.)
;
	MOVL	UCB$L_VCB(R5),R0		;U A; Got a VCB?
	BGEQ	4$				;U A; if .ge.0 not valid anyhow
; Treat no VCB as equivalent to not mounted files-11. Lord knows what will be
; there but it won't get MV.
;
; Note: Checking the VCB may fail if Spiralog doesn't emulate it right!!!
; However, here goes! If mount verify is not enabled, allow the I/O.
; Otherwise junk it right here.
;
	BBC	#VCB$V_MOUNTVER,VCB$B_STATUS2(R0),4$ ;U A; Is MV enabled?

3$:	MOVL	#SS$_ILLIOFUNC,R0		;U A; Say illegal func
	JMP	G^EXE$ABORTIO		; Abort the I/O with status in R0
4$:	

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

	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	PROCESS_MODE_INFO	; Do MODE SENSE/SELECT processing
        BLBS	R0,16$			; Branch on success
        BBSC	#UCB$V_TENBYTE,-	; Branch if we can back off and
		UCB$L_DK_FLAGS(R3),14$	; try a must-be-supported command
	BRW	30$
14$:	BSBW	PROCESS_MODE_INFO	; Do MODE SENSE/SELECT processing
	BLBC	R0,30$			; Branch on error
16$:	BSBW	READ_CAPACITY		; Execute READ_CAPACITY command
	BLBC	R0,30$			; Branch on error
	BSBW	VALIDATE_GEOMETRY	; Check disk geometry
	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 bit 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
;
; Check for appropriate diskette type for the density requested
;
200$:	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	PROCESS_MODE_FORMAT_FLOPPY ; Change format parameters
	BLBC	R0,1000$		; using MODE_SENSE/MODE_SELECT

	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:

; First check if this might be an audio packet that failed and has been requeued
; by shadowing, MV, or whatnot and which has no start address. Fail this
; RIGHT AWAY.
;
	MOVL	IRP$L_SEGVBN(R2),R0	; Get start address of S0 buffer.
	BLSS	10$
; Bad segvbn here; this may be a MV replay of a failed audio request.
; Nothing much has been started, so just clean up.
	MOVL	#SS$_ILLIOFUNC,R0
	BRW	COMPLETE_IO
10$:
	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,-		; 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$_DRVERR,-;
		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 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	PROCESS_MODE_INFO	- Send a mode sense command
;+
; PROCESS_MODE_INFO
;
; This routine processes the mode pages for this target.  It allocates pool
; to hold a copy of field descriptors.  There is one field descriptor for
; each mode page field of interest.  The local copy of the descriptors
; will be used to receive current values as well as to specify MODE SELECT
; values for those fields which need to be read and/or written.  One call
; to the routine DO_MODE_PAGE will be made for each mode page we care about;
; DO_MODE_PAGE does a MODE SENSE current and, if appropriate, a MODE SENSE
; changeable and MODE SELECT for the page specified.
;
; Context:
;
;	SCDRP thread
;	IPL = Fork
;	Fork lock held
;
; Inputs:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; Outputs:
;
;	R0	- Status
;	All other registers preserved
;-

PROCESS_MODE_INFO::
	SUBSAVE					; Save return address
	SUBPUSH	R1				; Save register
	SUBPUSH	R2				; Save register

; Allocate a local copy of the mode page descriptors out of pool

	MOVL	#DESCRIPTOR_SIZE,R1		; Setup size for allocation
	BSBW	ALLOC_POOL			; Get buffer space for descriptor array
	PUSHR	#^M<R2,R3,R4,R5>
	MOVC3	#DESCRIPTOR_SIZE,-		; Copy the descriptors
		DESCRIPTOR_BASE,(R2)	
	POPR	#^M<R2,R3,R4,R5>
	INSV	UCB$B_SCSI_VERSION(R3),-	; Determine SCSI1 vs SCSI2 for 
		#SCDRP$V_MODE_SENSE,-		; later use by do_mode_page
		#SCDRP$S_MODE_SENSE,-
		SCDRP$L_SCSI_FLAGS(R5)

; Read the error recovery page for two reasons: first, to establish a standard for
; whether or not the device supports 10-byte mode sense commands and second, to 
; fetch the block descriptor, which contains the block length; this is necessary
; to make sure that CD-ROMs are set to a 512-byte block size.

	LOAD_TENBYTE				; UCB => SCDRP

12$:
        MOVL	#SCSI$PGCD$C_READ_WRITE_ERR,R0	; Page code = error recovery page
        MOVAL	ERR_PAGE_DESC(R2),R1		; Address of descriptors
        BSBW	DO_MODE_PAGE			; Process the page
        BLBS	R0,18$				; Branch on error
	SUBPUSH	#SIZEOF_ERR			; Size in bytes of descriptors
        BRW     95$				; Log error

; Control Mode page (Queuing parameters)

18$:
	STORE_TENBYTE				; SCDRP => UCB
        MOVW	#512,UCB$W_BLOCK_SIZE(R3)	; Set block size in UCB
	IF_NOT_CMDQ	20$			; If device doesn't do queuing, ignore
						; control mode page
	BICB	#UCB$M_MODE_SENSE_PAG10,-	; Assume this page not seen
		UCB$L_DK_FLAGS(R3)
	MOVL	#SCSI$PGCD$C_CONTROL_MODE,R0	; Page code = control mode page 
	MOVAL	CONTROL_MODE_PAGE_DESC(R2),R1	; Address of descriptors
	BSBW	DO_MODE_PAGE			; Process the page
	BLBS	R0,20$				; If successful, do next page
	LOAD_TENBYTE				; Refresh SCDRP bit
	BICL	#UCB$M_CMDQ,-			; Disable TCQ
		UCB$L_DK_FLAGS(R3)

; Error recovery parameters

20$:	
	CMPW	UCB$W_BLOCK_SIZE(R3),#512	; Is block size 512?
	BEQL	21$				; Branch if so
	BRW	28$				; Branch if not
21$:	CMPB	UCB$B_SCSI_VERSION(R3),#2	; Is this SCSI2?
	BLSS	26$				; Branch if not
	IF_NOT_CMDQ 24$				; Branch if not TCQ

; Case 1:  SCSI2 device that supports TCQ
	SUBPUSH	#SIZEOF_SCSI2_TCQ		; Size in bytes of descriptors
	MOVAL	SCSI2_TCQ_ERR_PAGE_DESC(R2),R1	; Address of err rec descriptors
	SUBPUSH	#SCSI2_TCQ_ERR_DEVSPC_DESC	; Offset to dev specific param
	SUBPUSH	#SCSI2_TCQ_ERR_MEDTYP_DESC	; Offset to medium type
	BRW	30$				; Join common code

; Case 2:  SCSI2 device that does not support TCQ
24$:	SUBPUSH	#SIZEOF_SCSI2_NOTCQ		; Size in bytes of descriptors
	MOVAL	SCSI2_NOTCQ_ERR_PAGE_DESC(R2),R1; Address of err rec descriptors
	SUBPUSH	#SCSI2_NOTCQ_ERR_DEVSPC_DESC	; Offset to dev specific param
	SUBPUSH	#SCSI2_NOTCQ_ERR_MEDTYP_DESC	; Offset to medium type
	BRW	30$				; Join common code

; Case 3:  SCSI1 device (by definition, does not support TCQ)
26$:	SUBPUSH	#SIZEOF_SCSI1			; Size in bytes of descriptors
	MOVAL	SCSI1_ERR_PAGE_DESC(R2),R1	; Address of err rec descriptors
	SUBPUSH	#SCSI1_ERR_DEVSPC_DESC		; Offset to dev specific param
	SUBPUSH	#SCSI1_ERR_MEDTYP_DESC		; Offset to medium type
	BRW	30$				; Join common code

; Case 4:  Device does not have block size = 512
28$:	SUBPUSH	#SIZEOF_NON512			; Size in bytes of descriptors
	MOVAL	NON512_ERR_PAGE_DESC(R2),R1	; Address of err rec descriptors
	SUBPUSH	#NON512_ERR_DEVSPC_DESC		; Offset to dev specific param
	SUBPUSH	#NON512_ERR_MEDTYP_DESC		; Offset to medium type

; Common code to process Read Write Error Recovery Page

30$:	MOVL	#SCSI$PGCD$C_READ_WRITE_ERR,R0	; Page code = error recovery page 
	BSBW	DO_MODE_PAGE			; Process the page
	BLBS	R0,32$                          ; Continue if success
	IF_NOT_CMDQ 31$				; Fatal error if noTCQ
	LOAD_TENBYTE				; Refresh SCDRP bit
	BICL	#UCB$M_CMDQ,-			; Else disable TCQ
		UCB$L_DK_FLAGS(R3)
	BRW	24$				; Retry with noTCQ desc
31$:	PUSHL	R0				; Save Status
	SUBPOP	R0				; Get rid of medium type offset from stack
	SUBPOP	R0				; Get rid of dev specific param from stack
	POPL	R0				; Restore Status
	BRW	95$				; Error path
32$:
; Since the error recovery page is normally returned, fetch and process the
; the relevant mode page header parameters (namely, the device specific
; parameter and the medium type) as part of the error recovery page processing.

	SUBPOP	R0				; Get medium type offset
	ADDL2	R2,R0				; Get address of medium type descriptor
	SUBPOP	R1				; Get dev specific param offset
						;	(used for error logging only)
	ADDL2	R2,R1				; Get address of dev specific descriptor
	BBC	#7,MODE_DESC$L_ACTUAL(R1),5$	; Is write-protect bit clear?
						; NOTE: Write-protect bit is the MSB of
						; the byte which was read, bit 7.
		
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$:	SUBPOP	R1				; Get rid of desriptor size
	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	MODE_DESC$L_ACTUAL(R0),-	; type code in the mode sense data.
		#SCSI$C_WORM			;
	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.
	PUSHL	R2				; Save descriptor pointer

; Determine diskette type in RX26 drive
	CLRL	R2				; Assume unknown or default medium
	CMPB	#DT$_RX26,UCB$B_DEVTYPE(R3)	; Only RX26 reports diskette type
	BNEQ	506$				; Not RX26, skip this check
	MOVZBL	#MODE_SENSE_MEDIA_TYPE_MAX,R2	; Start checking for match with
						; last cell in table
502$:	CMPB	MODE_DESC$L_ACTUAL(R0),-	; Check against table entry
		MODE_SENSE_MEDIA_TYPE_TABLE[R2]
	BEQL	506$				; Matches, save index
	SOBGTR	R2,502$				; No match, back up in table
506$:	INSV	R2,#UCB$V_FLOPPY_MEDIA,-	; remember medium type
		#UCB$S_FLOPPY_MEDIA,-		;
		UCB$L_DK_FLAGS(R3)		;
	POPL	R2				; Restore descriptor pointer

; Format parameters

40$:	
	MOVL	#SCSI$PGCD$C_FORMAT_DEVICE,R0	; Page code = format device page 
	MOVAL	FORMAT_DEVICE_PAGE_DESC(R2),R1	; Address of descriptors
	BSBW	DO_MODE_PAGE			; Process the page
        BLBS	R0,41$				; If success, continue
	LOAD_TENBYTE				; Refresh SCDRP bit
	CMPL	#SS$_NOSUCHSEC,R0		; Illegal page, special success
	BEQL	50$				; Just do next page
	CLRB	UCB$B_SECTORS(R3)		; Invalidate sector field
	SUBPUSH	#SIZEOF_FORMAT			; Size of format page descriptors
	BRW	95$                             ; Log error

41$:	MOVB	-				; Save # of sectors per track in UCB
		<FMT_SECTORS_PER_TRACK_DESC+MODE_DESC$L_ACTUAL>(R2),-
		UCB$B_SECTORS(R3)		;

; Geometry parameters for rigid disks

50$:
	MOVL	#SCSI$PGCD$C_RIGID_DISK,R0	; Page code = rigid disk params
	MOVAL	RIGID_DISK_PAGE_DESC(R2),R1	; Address of descriptors
	BSBW	DO_MODE_PAGE			; Process the page
        BLBS	R0,51$				; If success, continue
	LOAD_TENBYTE				; Refresh SCDRP bit
	CMPL	#SS$_NOSUCHSEC,R0		; Illegal page, special success
	BEQL	55$				; Just do next page
	CLRB	UCB$B_TRACKS(R3)		; Invalidate tracks
	CLRW	UCB$W_CYLINDERS(R3)		; Invalidate cylinders
	SUBPUSH	#SIZEOF_RIGID_DISK		; Size of rigid disk descriptors
	BRW	95$                             ; Log error

51$:	MOVB	-				; Save number of tracks per cylinder
		<RGD_TRACKS_PER_CYLINDER_DESC+MODE_DESC$L_ACTUAL>(R2),-
		UCB$B_TRACKS(R3)		;
	MOVB	-				; Save number of cylinders
		<RGD_NUM_CYLINDERS_DESC+MODE_DESC$L_ACTUAL>(R2),-
		UCB$W_CYLINDERS+1(R3)		; (high byte)
	MOVB	-				; Save number of cylinders
		<RGD_NUM_CYLINDERS_DESC+MODE_DESC$L_ACTUAL+1>(R2),-
		UCB$W_CYLINDERS(R3)		; (low byte)

        BRW     60$                             ; Skip the Flexible Disk page now

; Geometry parameters for flexible disks

55$:
	MOVL	#SCSI$PGCD$C_FLEXIBLE_DISK,R0	; Page code = flexible disk params 
	MOVAL	FLEX_DISK_PAGE_DESC(R2),R1	; Address of descriptors
	BSBW	DO_MODE_PAGE			; Process the page
        BLBS	R0,56$				; If success, continue
	LOAD_TENBYTE				; UCB => SCDRP
	CMPL	#SS$_NOSUCHSEC,R0		; Illegal page, special success
	BEQL	60$				; Just do next page
	CLRW	UCB$W_CYLINDERS(R3)		; Invalidate cylinders
	CLRB	UCB$B_TRACKS(R3)		; Invalidate tracks
	SUBPUSH	#SIZEOF_FLEX_DISK		; Size of flex. disk descriptors
	BRW	95$                             ; Log error

56$:	MOVB	-				; Save number of tracks per cylinder
		<FLX_TRACKS_PER_CYLINDER_DESC+MODE_DESC$L_ACTUAL>(R2),-
		UCB$B_TRACKS(R3)		;
	MOVB	-				; Save number of cylinders
                <FLX_NUM_CYLINDERS_DESC+MODE_DESC$L_ACTUAL>(R2),-
		UCB$W_CYLINDERS+1(R3)		; (high byte)
	MOVB	-				; Save number of cylinders
                <FLX_NUM_CYLINDERS_DESC+MODE_DESC$L_ACTUAL+1>(R2),-
		UCB$W_CYLINDERS(R3)		; (low byte)

	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),558$		; special override bit is set
	CMPB	#DT$_RX23S,UCB$B_DEVTYPE(R3)	; Only worry about RX23 media
	BNEQ	558$				;
	CMPW	#SCSI$FLX$C_XFR_250KHZ,-	; Check for 250 KHz
		<FLX_TRANSFER_RATE_DESC+MODE_DESC$L_ACTUAL>(R2)
	BEQL	559$				; If 250 KHz, then write protect it

						; Check for 48 TPI diskette,
						; if so, then mark it
						; software write-protected
558$:	CMPB	#SCSI$FLX$R_FLAGS,-		; Check length of page
		<FLX_PAG_LEN_DESC+MODE_DESC$L_ACTUAL>(R2)
						; Skip test for SPC if page does
	BGEQ	60$				; not include flags word
	CMPZV	#SCSI$FLX$V_SPC,-		; Test for SPC
		#SCSI$FLX$S_SPC,-		; (steps per cylinder)
		<FLX_SPC_DESC+MODE_DESC$L_ACTUAL>(R2),-
		#0				; equal to 0
	BEQL	60$				; If so, then skip
559$:	BBSS	#DEV$V_SWL,UCB$L_DEVCHAR(R3),60$; If not, then set the SWL
						; Note: can't write 48 TPI diskettes
; Caching page

60$:    .IF     DEFINED RZ74_CACHE
        CMPB    #DT$_RZ74,UCB$B_DEVTYPE(R3)     ; Check for RZ74 disk drive
        BNEQ    65$                             ; No, use caching page
        BBS     #UCB$V_OUT_OF_REV,-             ; Is this RZ74 out of rev?
                UCB$L_DK_FLAGS(R3),70$          ; Yes, use no-caching page
65$:    .ENDC

	BBS	#UCB$V_HWL,-          		; Skip if writelocked
		UCB$L_DK_FLAGS(R3),75$
	MOVL	#SCSI$PGCD$C_CACHING,R0		; Page code = caching page 
	MOVAL	CACHING_PAGE_DESC(R2),R1	; Address of descriptors
	BSBW	DO_MODE_PAGE			; Process the page
        BLBS	R0,75$				; If success, continue
        LOAD_TENBYTE                            ; Refresh SCDRP bit
	CMPL	#SS$_NOSUCHSEC,R0		; Illegal page, special success
	BEQL	75$				; Continue
	SUBPUSH	#SIZEOF_CACHING			; Size of caching page descriptors
	BRW	95$                             ; Log error

        .IF     DEFINED RZ74_CACHE
; NoCaching page

70$:    MOVL	#SCSI$PGCD$C_CACHING,R0		; Page code = caching page
        MOVAL	NOCACHING_PAGE_DESC(R2),R1	; Address of descriptors
        BSBW	DO_MODE_PAGE			; Process the page
        BLBS    R0,75$				; If success, continue
        LOAD_TENBYTE				; Refresh SCDRP bit
        CMPL    #SS$_NOSUCHSEC,R0		; Illegal page, special success
        BEQL    75$				; Continue
        SUBPUSH	#SIZEOF_NOCACHING		; Size of nocaching descriptors
        BRW     95$				; Log error
        .ENDC

75$:	MOVZWL	#SS$_NORMAL,R0			; Set success status
80$:	PUSHL	R0				; Save return status
	MOVL	R2,R0				; Setup for deallocation
	BSBW	DEALLOC_POOL			; Deallocate descriptors
	POPL	R0				; Restore return status
	SUBPOP	R2				; Restore register
	SUBPOP	R1				; Restore register
	SUBRETURN				; Return to caller

95$:	CMPL	R0,#SS$_CANCEL			; If cancel status
	BEQL	80$                             ; Then leave now

; Since the real CDB has been deallocated by CLEANUP_CMD already, set up
; a dummy CDB for error logging purposes only.  Note:  All mode sense/select
; errors get logged as a 6-byte MODE_SENSE command with SCSI status FF.
; Since the register dump routine limits additional data to 255. bytes,
; We can store up to 6 descriptors (at 40. bytes each) in the error packet.

	PUSHL	SCDRP$L_CMD_PTR(R5)		; Save values which will be
	PUSHL	SCDRP$L_STS_PTR(R5)		; overwritten by the error
	PUSHL	SCDRP$L_SVA_USER(R5)		; logging code path
	PUSHL	SCDRP$L_TRANS_CNT(R5)
	PUSHL	R2				; Save descriptor pointer

	MOVAL	DUMMY_MODE_SENSE_CDB+4,-	; Address of length longword
		SCDRP$L_CMD_PTR(R5)
	MOVAL	DUMMY_MODE_SENSE_CDB,-          ; Address of SCSI sts byte
		SCDRP$L_STS_PTR(R5)
	MOVL	R1,SCDRP$L_SVA_USER(R5)   	; Address of descriptors for
						; this page
	SUBPOP	R2				; Get size of descriptors
	ASSUME MODE_DESC$S_FIELD_DESCRIPTOR EQ 40	
	CMPL	R2,-				; Do we have too many to save?
		#<6*MODE_DESC$S_FIELD_DESCRIPTOR>	
	BLEQ	98$				; Good, we can save all desc
	MOVL	#<6*MODE_DESC$S_FIELD_DESCRIPTOR>,-
		R2				; Can save first six only.	
98$:	MOVL	R2,SCDRP$L_TRANS_CNT(R5)       	; Byte count of all descriptors
						; for this page

	LOG_ERROR -				; Log a mode sense data error
		TYPE=MODE_SENSE_DATA,-
		VMS_STATUS=R0,-
		UCB=R3

	POPL	R2				; Restore descriptor pointer
	POPL	SCDRP$L_TRANS_CNT(R5)		; Restore values which were
	POPL	SCDRP$L_SVA_USER(R5)		; overwritten by the error
	POPL	SCDRP$L_STS_PTR(R5)		; logging code path
	POPL	SCDRP$L_CMD_PTR(R5)
	MOVL	#SS$_MEDOFL,R0			; Convert debug-usable error
						;   to user-visible error
	BRW	80$				; Use common exit path

	.PAGE
	.SBTTL	PROCESS_MODE_FORMAT_FLOPPY 
;+
; PROCESS_MODE_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.
;
; Context:
;
;	SCDRP thread
;	IPL = Fork
;	Fork lock held
;
; Inputs:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; Outputs:
;
;	R0	- Status
;	All other registers preserved
;-
PROCESS_MODE_FORMAT_FLOPPY::			; Send a mode select command
	SUBSAVE					; Save return address
	SUBPUSH	R1				; Save register
	SUBPUSH	R2				; Save register

; Allocate a local copy of the mode page descriptors out of pool

	MOVL	#DESCRIPTOR_SIZE,R1		; Setup size for allocation
	BSBW	ALLOC_POOL			; Get buffer space for descriptor array
	PUSHR	#^M<R2,R3,R4,R5>
	MOVC3	#DESCRIPTOR_SIZE,-		; Copy the descriptors
		DESCRIPTOR_BASE,(R2)	
	POPR	#^M<R2,R3,R4,R5>

	INSV	UCB$B_SCSI_VERSION(R3),-	; Set SCSI version in SCDRP
		#SCDRP$V_MODE_SENSE,-
		#SCDRP$S_MODE_SENSE,-
		SCDRP$L_SCSI_FLAGS(R5)

						; 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+-	; RX33s -> 15 sectors
		SCSI$FMT$W_TRACKS>,R0
	CMPB	#DT$_RX33S,UCB$B_DEVTYPE(R3)	; Check for RX33 drive
	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],R0			; Get address from table
	MOVQ	(SP)+,R1			; Restore registers

;
; Build the format parameters page
;
70$:	
	MOVW	SCSI$FMT$W_SECTORS(R0),-	; Change sectors per track
		<FMT_SEL_SECTORS_PER_TRACK_DESC+MODE_DESC$L_DESIRED>(R2)

       LOAD_TENBYTE				; Copy UCB.TENBYTE bit to SCDRP

	MOVL	#SCSI$PGCD$C_FORMAT_DEVICE,R0	; Page code = format device page 
	MOVAL	FORMAT_DEVICE_SEL_PAGE_DESC(R2),R1
						; Address of descriptors
	BSBW	DO_MODE_PAGE			; get the mode page info
	BLBS	R0,75$				; Branch on success
	BRW	1100$				; Branch on failure
;
; 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

75$:	MOVAB	<MODE_SELECT_FLEXIBLE_RX33_15+-	; RX33s -> 15 sectors
		SCSI$FLX$W_TRANSFER_RATE>,R0	;
	CMPB	#DT$_RX33S,UCB$B_DEVTYPE(R3)	; Check for RX33 drive
	BEQL	170$				; Yes, take branch

	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],R0			; Get address from table
	MOVQ	(SP)+,R1			; Restore registers
170$:	

 	MOVW	SCSI$FLX$W_TRANSFER_RATE(R0),-				; Use our transfer rate
		<FLX_SEL_TRANSFER_RATE_DESC+MODE_DESC$L_DESIRED>(R2)
	MOVB	SCSI$FLX$B_HEADS(R0),-					; Use our number of heads
		<FLX_NUM_HEADS_DESC+MODE_DESC$L_DESIRED>(R2)
	MOVB	SCSI$FLX$B_SECTORS_TRACK(R0),-				; Use our sectors per track
		<FLX_SECTORS_PER_TRACK_DESC+MODE_DESC$L_DESIRED>(R2)
	MOVW	SCSI$FLX$W_CYLINDERS(R0),-				; Use our number of cylinders
		<FLX_SEL_NUM_CYLINDERS_DESC+MODE_DESC$L_DESIRED>(R2)
	EXTZV	#SCSI$FLX$V_SSN,#1,-					; Use our start sector number
		SCSI$FLX$R_FLAGS(R0),-		
                <FLX_START_SECTOR_NUM_DESC+MODE_DESC$L_DESIRED>(R2)
	EXTZV	#SCSI$FLX$V_SPC,#SCSI$FLX$S_SPC,- 			; Use our steps/cyl
		SCSI$FLX$R_FLAGS(R0),-		
		<FLX_SEL_SPC_DESC+MODE_DESC$L_DESIRED>(R2)

	MOVL	#SCSI$PGCD$C_FLEXIBLE_DISK,R0	; Page code = flexible disk params
	MOVAL	FLEX_DISK_SELECT_PAGE_DESC(R2),R1
						; Address of descriptors
	BSBW	DO_MODE_PAGE
	BLBC	R0,1100$

	MOVL	#SS$_NORMAL,R0			; Set success status

900$:	PUSHL	R0				; Save return status
	MOVL	R2,R0				; Setup for deallocation
	BSBW	DEALLOC_POOL			; Deallocate descriptors
	POPL	R0				; Restore return status
	SUBPOP	R2				; Restore register
	SUBPOP	R1				; Restore register
	SUBRETURN				; Return to caller

1100$:	MOVL	#SS$_DRVERR,R0
	BRB	900$				; Join common exit code

	.PAGE
	.SBTTL	VALIDATE_GEOMETRY
;+
; VALIDATE_GEOMETRY 
;
; In general, cylinders/tracks/sectors must be nonzero in order to set the device valid.
; This routine checks for zero values in various geometry fields.  In special cases it
; will provide dummy values for fields;  in other cases of zero fields it will return
; an error.

; Context:
;
;	SCDRP thread
;	IPL = Fork
;	Fork lock held
;
; Inputs:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; Outputs:
;
;	R0	- Status
;	All other registers preserved
;-

VALIDATE_GEOMETRY:

	PUSHR	#^M<R1,R2,R3,R4,R5>		; Save registers
	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	10$ 				; cylinders fields in the UCB are all
	TSTB	(R1)+				; non-zero. If any are zero, then possibly
	BEQL	10$				; there's a problem and error status
	TSTW	(R1)+				; should be returned to prevent the
						; volume valid bit from being set.
	BNEQ	30$				; All fields nonzero, good.

; If here, at least one of cylinder/track/sector is zero.

10$:	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	30$				; Set dummy values 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),20$
	MOVZWL	#SS$_FORMAT,R0			; Set bad status:	unformatted media
	BRB	200$				; 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, set up dummy values for the 
; geometry parameters. 

20$:	BBS	#DEV$V_SWL,-			; If this is a read-only device, it's not
		UCB$L_DEVCHAR(R3),30$		; required to have non-zero values of
						; tracks, sectors, and cylinders

; If here, at least one of cylinder/track/sector is zero, and this is an error.
; However, allow geometry to be generated if maxblock is nonzero
; since many opticals and the like do not supply geometry beyond disk
; size.

	TSTL	UCB$L_MAXBLOCK(R3)		; Ensure not 0 len. dsk
	BNEQ	30$
	MOVZWL	#SS$_DRVERR,R0			; Set bad status
	BRB	200$				; Use common exit path

; Proceed to check geometry further. Set dummy values where appropriate so we may continue.

30$:
	MOVL	UCB$L_MAXBLOCK(R3),R2		; Get max block number
	INCL	R2				; Get max 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	35$				; 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
	BRW	190$				; Set success status and exit

; 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
35$:	MOVW	#^X604,UCB$B_SECTORS(R3)	; Fill in dummy values for sector
						; 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
	CMPL	R0,#65534			;S FF; Is cylinder number legal?
	BGTR	202$				;S FF; If so go try a crude fix
	MOVW	R0,UCB$W_CYLINDERS(R3)		; Save number of cylinders
	TSTL	R1				; Zero remaineder?
	BEQL	190$				; Branch if so
	INCW	UCB$W_CYLINDERS(R3)		; Otherwise, increment cylinders
						; (tracks * sectors * cylinders must
						; be >= maxblock)
190$:	MOVL	#SS$_NORMAL,R0
200$:	POPR	#^M<R1,R2,R3,R4,R5>		; Restore registers
	RSB

;
; Out of line code to handle really large geometries to the limits of what
; VMS can currently do.
;
; First method can waste 1024 blocks.
; Second method can waste 9216 blocks. Third can waste 65025. This is rather
; crude, but will at least allow the structure to be used.
;
; Note that we test against 65534 cylinders because cyl count may be 
; incremented if maxblock field is more than trk*sect*cyl. This guarantees
; the cylinder field will be legal and thus the device will be usable, other
; things being equal. Note that because we accept devices with zeroes
; in trk or sect and will have filled in ^X604 above, we have four fake
; geometries in all:
;    6 x  4 x n
;   32 x 32 x n
;   96 x 96 x n
;  255 x255 x n
;
; This means geometry based loss of up to 23, 1023, 9215, or 65024
; blocks as the device gets bigger, but the device will be usable over
; most of its surface.
;
202$:	MOVB	#32,UCB$B_TRACKS(R3)
	MOVB	#32,UCB$B_SECTORS(R3)
	CMPL	UCB$L_MAXBLOCK(R3),#<65534*32*32> ;does 32 by 32 by n work?
	BLSSU	40$
	MOVB	#96,UCB$B_TRACKS(R3)		;S FF; Set 96 by 96 by n geom
	MOVB	#96,UCB$B_SECTORS(R3)
	CMPL	UCB$L_MAXBLOCK(R3),#<65534*96*96> ;S FF; Be sure disk not too big
	BLSSU	40$				;S FF; Redo computation if ok
; If disk is over 300Gb, try to allow 2TB
	MOVB	#255,UCB$B_TRACKS(R3)		;S FF; Go for broke
	MOVB	#255,UCB$B_SECTORS(R3)
	BRW	40$				;This is as big as we can go.
; This should be adequate for any currently supported disk size (i.e.,
; with a 32-bit block number a la SCSI-2), losing at most a small bit of
; capacity but allowing disk access for most of the surface.

	.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,30$			; 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
	ADDL	#2,R1				; Step over irrelevant data
	MOVB	(R1)+,UCB$W_BLOCK_SIZE+1(R3)	; Get MSB of block size
	MOVB	(R1),UCB$W_BLOCK_SIZE(R3)       ; Get LSB of block size

	CMPW	UCB$W_BLOCK_SIZE(R3),#512	; Is block size 512?
	BEQL	10$				; If so, exit normally

	BITL	#<UCB$M_OPTICAL+-		; Is this an optical or a WORM device?
		  UCB$M_WORM>,-			; These devices may have ACPs to
		  UCB$L_DK_FLAGS(R3)		; handle special geometry.
	BNEQ	10$				; Exit normally if so.

        BBSS	#DEV$V_SWL,UCB$L_DEVCHAR(R3),10$;S FF; Otherwise software writelock this device.

10$:	MOVL	#SS$_NORMAL,R0		; Set success status
20$:	BSBW	CLEANUP_CMD		; Cleanup from the SCSI command
	SUBRETURN
30$:	BBC	#UCB$V_FLOPPY,-			; Report different error if floppy
		UCB$L_DK_FLAGS(R3),20$
	MOVZWL	#SS$_FORMAT,R0			; Set bad status:	unformatted media
	BRB	20$				; Use common exit path


	.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
        CLRL    R1			; Clear transfer byte count
	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)>
	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 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. 
; Conditionally 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
        CMPL    R0,#SS$_TIMEOUT		; Command timed out?
        BEQL    10$                     ; Branch if so, don't log an error
        CMPL    R0,#SS$_MEDOFL          ; Medium offline?
        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_CHECK_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$_NOTQUEUED,-;
		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
        LOG_ERROR -                     ; Log a send command error
                TYPE=SEND_CMD_ERROR,-
                VMS_STATUS=#SS$_RETRY,-
                UCB=R3
 	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 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_CHECK_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:
	PUSHL	R7			; Save register
	PUSHL	R8			; Save register
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of extended sense data
	EXTZV	#SCSI$SNS$V_SENSE_KEY,-
		#SCSI$SNS$S_SENSE_KEY,-
		2(R0),R1		; Get sense key
	MOVZBL	SCSI$SNS$B_ADD_SENSE_CODE(R0),R7	; Get ASC
	MOVZBL	SCSI$SNS$B_ADD_SENSE_QUAL(R0),R8	; Get ASQ
	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)                  ;
	BNEQ	14$
12$:	BRW	40$
14$:
;;	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),12$	; 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_RECOVERED_DATA,- ; Recovered error with read retries?
		12(R0)			;
	BEQL	12$			; Branch if so, dont log an error
	CMPB	#^x18,- ; Recovered error with ecc or retries?
		12(R0)			;
	BEQL	12$			; Branch if so, dont log an error

;
; 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_CHECK_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	38$			; Yes, don't log an error

; Don't log certain Illegal Request errors that can result from sending
; e.g. a 10-byte mode sense to a target that does not support it.

	CMPL	R1,#SCSI$C_ILLEGAL_REQUEST	; Illegal command sense key
        BNEQ	36$
	CMPL	R8,#0
        BNEQ	36$
        CMPL	R7,#^X20
	BEQL	38$ 				; Skip ASC/ASQ of 20/00
        CMPL	R7,#^X24
	BEQL	38$                            	; Skip ASC/ASQ of 24/00

36$:	CMPL	R1,#SCSI$C_ABORTED_COMMAND	; ABORTED command sense key
	BEQL	38$				; BRANCH IF ABORTED COMMAND
						; WITHOUT LOGGING ERROR
	LOG_ERROR -			; Log an extended sense data error
		TYPE=EXTND_SENSE_DATA,-	;
		VMS_STATUS=#SS$_NORMAL,-;
		UCB=R3			;
38$:	POPL	SCDRP$L_CMD_PTR(R5)	; Restore address of request sense cmd
40$:	POPL	R8			; Restore register
	POPL	R7			; Restore register
	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_MODE_CHANGE,-	; Unit attention caused by mode select
                12(R2)                  ; parameters change?
	BEQL	4$			; 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
4$:     EXTZV   #IRP$V_FCODE,#IRP$S_FCODE,-     ; Extract I/O function code
                SCDRP$W_FUNC(R0),R3	;
        CMPL    #IO$_PACKACK, R3        ; Was mode_change due to an IO$_PACKACK?
        BEQL    3$                      ; If so, then unit attention was expected
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	12(R2),-		; Recoverable error with read retries?
		#SCSI$C_RECOVERED_DATA	; 
	Beql	121$			; Branch if so
	cmpb	12(r2),#^x18		; Other recovered data?
	bneq	35$			; br if not
121$:	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		;
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$  ;
       .IF DEFINED RZ74_CACHE
        CMPB    #DT$_RZ74,UCB$B_DEVTYPE(R3)     ; Is this the RZ74 disk drive?
        BEQL    30$                             ; If so, don't disable disconnects
        .ENDC
        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.
	BEQL	50$			; Be sure it's valid
	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 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>
	MOVAB	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
	MOVL	R1,SCDRP$L_BCNT(R5)	; Save length of transfer
	BEQL	20$			; Branch if zero length, zero SCDRP$L_BCNT
	CLRL	SCDRP$L_PAD_BCNT(R5)	; No padding required
	INSV	(R2),#IRP$V_FUNC,#1,-	; Set/clear FUNC bit to indicate READ/
		SCDRP$W_STS(R5)		; WRITE function
	BBC -                           ; Did we already allocate a buffer?
		#SCDRP$V_CL_PRIVATE_BUFF,-
		SCDRP$L_SCSI_FLAGS(R5),15$
	MOVL	SCDRP$L_SVA_USER(R5),R2	; Get start address of buffer		
        BRW	18$                     ; Branch to common code

15$:
	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
18$:
	BICW3	#^C<^X1FF>,R1,-		; And byte offset within page
		SCDRP$W_BOFF(R5)	;
	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


		.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
	BBS -                           ; Private buffer to be handled by other code
		#SCDRP$V_CL_PRIVATE_BUFF,-
		SCDRP$L_SCSI_FLAGS(R5),20$
	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)>
    					; 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>
	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>
	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>
	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 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,-
		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
