From:	STAR::TGOODWIN     "OpenVMS SCSI Device Support, DTN 381-0786" 14-AUG-1996 12:31:11.54
To:	EVERHART
CC:	
Subj:	FWD: OSMS WDDRIVER.MAR Alpha source

From:	TAPE::65429::LASERGUYS       5-AUG-1996 14:22:42.73
To:	TAPE::STAR::TGOODWIN
CC:	
Subj:	OSMS WDDRIVER.MAR Alpha source

	.TITLE	 WDDRIVER - ALPHA/VMS WORM Disk Class Driver
;
;  This macro is used to set the .IDENT string for the WDDRIVER images.
;  The symbols MAJVER, MINVER, and ENGREL are defined so that they may be
;  used to calculate the word value read by WDIDENT to display the version.
;
	.MACRO	WD_IDENT	MAJ,MIN,REL,RELEASE=V0
		MAJVER	= MAJ
		MINVER	= MIN
		ENGREL	= REL
		.IDENT	\'RELEASE''MAJ'.'MIN-'REL'\
		.Save_Psect	Local_Block
		.Psect	$$$111_TEXT
		.ASCIZ	\WDDRIVER Version 'MAJ'.'MIN'-'REL'/ALPHA\
		.Restore_Psect
	.ENDM	WD_IDENT

  	WD_IDENT	4,5,9
;
;+
;
; FACILITY:
;
;	VAX/VMS WORM Disk Class Driver
;
; ABSTRACT:
;
;	This module contains the WORM disk class driver for WORM disks
;	supported by LaserWare/Star. This driver relies on the SCSI port
;	driver to provide the low-level communication of commands and
;	data over the SCSI bus.
;
; REVISION HISTORY:
;
;
;	4.5-9		Rob Seneker		31-JUL-1996 14:51
;	Since the RZDRIVER now supports the SCSI jukebox robots I have
;	commented out the jukeboxes from the INQUIRY table.  I did not
;	take any code out of the rev but I don't want this driver to
;	be able to run jukebox robots.  Plus all the non-digital supported
;	devices have been removed. Only the HP and RWZ drives will work. 
;
;	4.5-8		Rob Seneker		26-JUN-1996 15:56
;	The 4.5-6 change for the removing of the two lines of code from
;	the MODE_SELECT routine was incorrect. Put code back in.
;
;	4.5-7		Rob Seneker		31-MAY-1996 15:20
;	1) Made same as VAX WDDRIVER in regards to SONY-F521.
;	2) Made same as VAX WDDRIVER in regards to LF6100/LF6600.
;	   Media size is 10,935,934 and 23,326,320 blocks.
;
;       4.5-6           Rob Seneker              8-MAY-1996 14:00
;       Don't do CHANGE_MODE command for RWZ53's.  Removed next two lines:
;       CMPB    R1, #OPTIC$C_HP_1716C   ; DEC/HP drive?
;       BEQL    80$                     ; Quick branch (doesn't follow SCSI-2)
;       from MODE_SELECT above bit test for SCSI-2.
;
;	4.5-5		Rob Seneker		24-APR-1996 15:03
;	Changes to support HP 4X drives (RWZ53).  Mode select data is
;	different.
;
;	4.5-4		Rob Seneker		12-APR-1996 15:40
;	Changes to allow SONY EDM 1DA1 media (1X capacity) to work with
;	this code.  This problem was identified by BARCO and IPMT case 39174.
;	The media has a capacity of 570783 instead of 576999 of the 3M media.
;
;	4.5-3		Rob Seneker		12-SEP-1995 18:21
;       Fixed bug in WD_WAIT by loading R5 with KPB address.  The
;       system would halt and reboot if a start or stop unit command
;       had to be retried.
;
;	4.5-2		Rob Seneker		24-AUG-1995 07:11
;	Added in unit number logging in REGDUMP, as in VAX WDDRIVER.
;
;	4.5-1		Rob Seneker		 2-AUG-1995 13:47
;	Changes in SET_CONN_CHAR to set command queueing and SCSI-2 char.
;
;	4.5-0		Rob Seneker		15-JUN-1995 12:28
;	Changed to work under 6.2.
;
;	4.4-9		Philip Brooke		 7-JUN-1995 11:22
;       (1) Correct returned byte count on error conditions where some data
;       gets transferred.
;	(2) Updated inquiry table based on VAX WDdriver V3.9-14
;
;	4.4-8		Philip Brooke		 1-JUN-1995 17:06
;	Removed all STEP2 conditionals.
;	Converted all RETURN macro calls to RET.
;
;	4.4-7		Rob Seneker		 7-APR-1995 16:08
;	Same as VAX WDDRIVER 3.9-12.
;	Changed timeout for allow remove and prevent remove to 30 seconds.
;
;	4.4-6		Rob Seneker		28-FEB-1995 11:46
;	Same as VAX WDDRIVER 3.9-11
;	Modified PACKACK to use the total capacity of a HP 1716C or RWZxx
;	type disks.  This means the max sectors per track must be computed
;	were the track size is a modulus of the capacity. This computed out
;	to be 183 sectors per track and 3153 cylinders for a 576999 block
;	disk and is 217 sectors per track and 5361 cylinders for a 1163337
;	block disk.  This method may be used for other disks in the future.
;
;	4.4-5		Rob Seneker		10-OCT-1994 15:25
;	1) Changes to make Pioneer 7001 go from WORM to RW mode automatically.
;	2) Removed restriction disallowing writes to block 0 if WORM media.
;	3) Added current (3.9-10) VAX wddriver changes in for 1804/5004 jb's.
;	4) VAX WDDRIVER (3.9-7) update, Kodak 6800 MODE_SENSE problem were
;	   R0 was not set as completion status from routine MODE_SENSE if
;	   this was a Kodak 6800 jb.
;
;	4.4-4		K.J. Cross	       	 8-Sep-1994
;	Preliminary changes to add support for Pioneer 1804 CDROM changer.
;
;	4.4-3		K.J. Cross		24-may-1994
;	Changed to V0 for Release 1.
;
;	4.4-2		Rob Seneker		17-MAY-1994 16:58
;	Same change as WDDRIVER for VAX/VMS 3.9-2.  Change NORMAL_RETURN
;	path so R0 (error code) is returned correctly.  It was being lost
;	by the call to REQUEST_SENSE. Changed label to ERROR_RETURN.
;
;	4.4-1		K.J. Cross		23-MAR-1994
;	Disable messages.
;
;	4.4-0		K.J. Cross		15-Dec-1993
;	Added STEP2 conditional for STEP 2 driver.
;
;	4.3-3		K.J. Cross		2-Dec-1993
;	Invoke SET_CONN_CHAR for all units.  Disable DEBUG messages.
;
;	4.3-2		K.J. Cross		20-Oct-1993
;	Use correct register for the UCB address in SET_CONN_CHAR.
;
;	4.3-1		K.J. Cross		14-Oct-1993
;	Force SYNCHRONOUS and DISCONNECT for all devices.
;	Set device characteristics during UNIT_INIT.
;
;	4.3-0		K.J. Cross		13-Sep-1993
;	Brought into sycn with VMS V3.8-5:
;	-  Don't issue READ_MODE/CHANGE_MODE to HP drives.
;	-  Mask off PS bit from element address assignment page
;	-  Make SPI$, SCDT$ and SPDT$ symbols external
;
;	4.2-2		K.J. Cross		30-Jun-1993
;	Set SS$_NORMAL if we don't really do a MODE_SELECT.
;
;	V4.2-1		K.J. Cross		23-Jun-1993
;	Set DEC/HP drive MODE_SELECT.
;
;	V4.2-0		K.J. Cross		21-Jun-1993
;	Enable reading 1024-byte sector media.  Any attempts to
;	write to 1024-byte media returns SS$_FORMAT.  Also, exit
;	the IO$_DIAGNOSE FDT to EXE$ABORTIO if an invalid request
;	is detected.
;
;	V4.1-5		K.J. Cross		18-Jun-1993
;	Generalized HP C1716* ident string.
;
;	V4.1-4		K.J. Cross		7-Jun-1993
;	Use SCDRP$L_TRANS_CNT for byte count on RECOVERED ERROR.
;
;	V4.1-3		K.J. Cross		4-May-1993
;	Set DEV$M_SCSI in DEVCHAR2.
;
;	V4.1-2		K.J. Cross		29-Apr-1993
;	Return SS$_IVMODE if WORM disk inserted (rather than looping forever).
;
;	V4.1-1		K.J. Cross		22-Apr-1993
;	Change references from UCB_L_SCDRP_SAV1 to UCB_L_SCDRP_SAV2.
;
;	V4.1-0		K.J. Cross		20-Apr-1993
;	Brought in line with VAX version of WDDRIVER V3.7-0 (no WORM stuff yet).
;
;	V4.0-0		K.J. Cross		13-Oct-1992
;	Initial conversion for ALPHA
;
;-

	.SBTTL	External and local symbol definitions

	.LIBRARY	/WDLIB.MLB/
	.LIBRARY	/SYS$LIBRARY:LIB.MLB/

;
; This is the ALPHA (previously called "EVAX") version of ARCH_DEFS.MAR,
; which contains architectural definitions for compiling VMS sources
; for VAX and ALPHA systems.
;
EVAX = 1
ALPHA = 1
BIGPAGE = 1
ADDRESSBITS = 32

;
; Define these guys.
;
	$ADPDEF 			; Adapter control block
	$CANDEF 			; Cancel reason codes
	$CDDBDEF 			;
	$CPUDEF 			; Per CPU data structures
	$CRBDEF 			; Channel request block
	$DCDEF				; Device classes and types
	$DDBDEF 			; Device data block
	$DEVDEF 			; Device characteristics
	$DYNDEF 			; Dynamic pool stuff
	$EMBDEF 			; Error message buffer
	$FKBDEF 			; Fork block defs
	$IDBDEF 			; Interrupt data block
	$IODEF				; I/O function codes
	$IPLDEF 			; Hardware IPL definitions
	$IRPDEF 			; I/O request packet
	$KPBDEF				; Kernel process block
	$MMGDEF 			; Memory management
	$PAGEDEF			; Page size
	$PCBDEF 			; Process control block
	$PRDEF				; Common processor registers
	$PRVDEF 			; Privilege mask
	$PTEDEF 			; Page table entry symbols
;;	$SCDTDEF	GLOBAL		; SCSI SCDT symbols
;;	$SPDTDEF	GLOBAL		; SCSI SPDT symbols
;;	$SPIDEF		GLOBAL		; SCSI SPI symbols
	$SPLCODDEF			; Spinlock code defs
	$SSDEF				; System status codes
	$UCBDEF 			; Unit control block
	$VECDEF 			; Interrupt vector block
	$VADEF				; Virtual address bits

;
; UCB extensions.
;
	$OPTICDEF	GLOBAL		; Perceptics Optical symbols
	$WDUCBDEF	GLOBAL		; Device UCB extensions

	.SBTTL	Local Symbol Definitions
;+
; Local symbols
;-

	ASSUME	IOC$C_DISK_BLKSIZ EQ 512; Assume pagelet is 512
IOC$S_DISK_BLKSIZ	= 9		; Pagelet power of 2
MAX_BUSY_TIME		= 20		; Maximum amount of time to allow a
					; device to remain busy before resetting
					; the bus

DEBUG_MESSAGES		= 1		; Set to 0 to disable debug messages
MAX_BCNT		= 63*1024	; Maximum byte count
ASSEMBLE_PASSTHRU	= 1		; If 0 don't assemble DIAG code, if 1 d
SCSI$M_STS		= ^XC1		; Used to extract vendor unique STS bits.
DIAG_BUF_LEN		= 60		; Length in bytes of DIAGNOSE input buffer.
MAX_CMD_LEN		= 248  		; Maximum size in bytes of a SCSI CMD.
LF4500$M_LOAD		= 2		; Load bit for LF4500 move command
REQUEST_SENSE_LEN	= 120		; Length of REQUEST SENSE data buffer

SCSI_BUSY_WAIT		= 120		; # of seconds to wait for BUSY to clear
LONG_TIMEOUT		= 150		; timeout if OPTIC$M_LONG_TMO set

MAILBOX_INDEX		= 254		; Special value to indicate mailbox

NKK_MAP_LENGTH		= 772		; Length of map from NKK jukebox
SONY_MAP_LENGTH		= 128		; Length of map from Sony jukebox

BUG$_INCONSTATE		= ^X198		; BUG_CHECK code

;
; Generic functions codes for a SCSI only Jukebox
;
	IO$_LOAD_ELEMENT	= IO$_FLUSH
	IO$_UNLOAD_ELEMENT	= IO$_FREECAP
	IO$_JB_READMAP		= IO$_DRVCLR

	.SBTTL	Sense Key Codes

;+
; Define SCSI sense key codes.
;-
	SCSI_C_NO_SENSE 	= 0	; No sense data
	SCSI_C_RECOVERED_ERROR	= 1	; Recovered error (treated as success)
	SCSI_C_NOT_READY	= 2	; Device not ready
	SCSI_C_MEDIUM_ERROR	= 3	; Medium (parity) error
	SCSI_C_HARDWARE_ERROR	= 4	; Hardware error
	SCSI_C_ILLEGAL_REQUEST	= 5	; Illegal request
	SCSI_C_UNIT_ATTENTION	= 6	; Unit attention (media change, reset)
	SCSI_C_DATA_PROTECT	= 7	; Data protection (write lock error)
	SCSI_C_BLANK_CHECK	= 8	; Blank check (advance past end of data)
	SCSI_C_VENDOR_UNIQUE	= 9	; Vendor unique key
	SCSI_C_COPY_ABORTED	= 10	; Copy operation aborted
	SCSI_C_ABORTED_COMMAND	= 11	; Command aborted
	SCSI_C_EQUAL		= 12	; Compare operation, data match
	SCSI_C_VOLUME_OVERFLOW	= 13	; Write beyond physical end of tape
	SCSI_C_MISCOMPARE	= 14	; Compare operation, data mismatch
	SCSI_C_RESERVED		= 15	;
;
;  SCSI error types for use in error log descriptions.	These types are valid
;  for DEC's DKDRIVER error revision 2.  Hopefully this will always work, even
;  though DEC may change DKDRIVER (this will work as long as ANALYZE/ERROR
;  supports DK error revision 2).
;
	SCSI_C_CONNECTION_ERROR = 1	; Error connecting
	SCSI_C_MAP_BUFFER_ERROR = 2	; Error mapping a buffer
	SCSI_C_SEND_CMD_ERROR	= 3	; Error sending a command
	SCSI_C_INV_INQUIRY_DATA = 4	; Invalid inquiry data received
	SCSI_C_EXTND_SENSE_DATA = 5	; Extended sense data received
	SCSI_C_MODE_SENSE_DATA	= 6	; Mode sense data
	SCSI_C_REASSIGN_BLOCK	= 7	; Reassigning block

;
; SCSI type codes.
;
	SCSI$C_WORM		= 4	; Worm device
	SCSI$C_DISK		= 0	; Mag disk
	SCSI$C_JUKEBOX		= 8	; Jukebox

;++
; Define offsets in various SCSI command packets.
;--

;+
; REQUEST SENSE data offsets.
;-
	SCSI_XS_B_ERR_CODE	= 0	; Extended sense error code
	SCSI_XS_B_KEY		= 2	; Extended sense KEY field
	SCSI_XS_V_KEY		= 0	; Extended sense KEY bit number
	SCSI_XS_S_KEY		= 4	; Extended sense KEY length
	SCSI_XS_B_ADDNL_INFO	= 3	; Extended sense additional code
	SCSI_XS_B_ADDNL_CODE	= 12	; Extended sense additional code
	SCSI_XS_B_ASC		= 12	; Extended sense additional code
	SCSI_XS_B_ASQ		= 13	; Extended sense additional code
	SCSI_XS_B_ADDNL_CODE30	= 8	; "                            " (TZ30)
	SCSI_XS_B_ADDNL_CODE50	= 8	; "                            " (TZK50)
	SCSI_XS_M_EOF		= ^X80	; Extended sense end of file
	SCSI_XS_M_EOM		= ^X40	; Extended sense end of medium
	SCSI_XS_M_ILI		= ^X20	; Extended sense illegal length indicator
	SCSI_XS_V_ADDNL_VALID	= 7	; Extended sense additional data valid
;
;  Define symbols for offsets into SCSI command buffer after it has been
;  set up.  Example:
;
;	MOVL	SCDRP$L_CMD_PTR(R5),R1		; Point to command buffer
;	MOVB	#1,SCSI_CMD_B_BYTE6(R1)		; Store 1 in byte 6
;
	$DEFINI	SCSI_CMD_BYTES			;
$DEF	SCSI_CMD_L_LENGTH	.BLKL	1	; Length of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE0	.BLKB	1	; Byte 0 of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE1	.BLKB	1	; Byte 1 of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE2	.BLKB	1	; Byte 2 of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE3	.BLKB	1	; Byte 3 of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE4	.BLKB	1	; Byte 4 of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE5	.BLKB	1	; Byte 5 of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE6	.BLKB	1	; Byte 6 of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE7	.BLKB	1	; Byte 7 of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE8	.BLKB	1	; Byte 8 of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE9	.BLKB	1	; Byte 9 of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE10	.BLKB	1	; Byte 10 of SCSI cmd buffer
$DEF	SCSI_CMD_B_BYTE11	.BLKB	1	; Byte 11 of SCSI cmd buffer
	$DEFEND	SCSI_CMD_BYTES			;* End of SCSI_CMD_BYTES



;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
;	MACROS
;
;------------------------------------------------------------------------------

	.SBTTL	WD_ALLOC_SCDRP	- Allocate an SCDRP
;+
; WD_ALLOC_SCDRP
;
; This macro is used to allocate an SCDRP on the kernel process stack.
;
; INPUTS:
;	R3	- UCB address
;
; OUTPUTS:
;	R5	- SCDRP address
;	R0	- destroyed
;
; 	Round up the SCDRP size to a quadword multiple and then allow 8
;	additional bytes to quadword align the SCDRP pointer even if the stack
;	has arbitrary alignment.
;
	SCDRP_ALLO_LEN = <<<SCDRP$K_LENGTH+7>&^C7> + 8>

	.MACRO	WD_ALLOC_SCDRP
	SUBL	#SCDRP_ALLO_LEN,SP	      ; Allocate SCDRP on the stack
	ADDL3	#7,SP,R5                      ; R5 = SCDRP address, quadword
	BICL	#7,R5			      ;      aligned
	.SET_REGISTERS	ALIGNED=<R5>
	BSBW	INIT_SCDRP		      ; Initialize allocated SCDRP
	.ENDM	WD_ALLOC_SCDRP


	.SBTTL	WD_DEACTIVATE_SCDRP	- Deactivate an SCDRP
;+
; WD_DEACTIVATE_SCDRP
;
; This macro deactivates an SCDRP by clearing the appropriate UCB field.
; A sanity check is performed to ensure that any map registers for this
; command have been deallocated.  By default, deallocation of the SCDRP
; will occur implicitly via kernel process termination, although
; explicit deallocation may be requested by specifying CLRSTK=YES.
;
; INPUTS:
;	R3	- UCB address
;	R5	- SCDRP address
;
; OUTPUTS:
;	UCB$L_SCDRP - Initialized
;
	.MACRO	WD_DEACTIVATE_SCDRP,CLRSTK=NO
	CLRL	UCB_L_SCDRP(R3)		; No active SCDRP for this SCDRP
	.IF IDN <CLRSTK>,<YES>		; If explicit deallocation is required
	    ADDL #SCDRP_ALLO_LEN,SP 	; Clear SCDRP off the stack
	.ENDC
	.ENDM	WD_DEACTIVATE_SCDRP

	.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.)
;
; Class drivers can specify on a command by command basis the DMA Timeout and
; Disconnect Timeout values. The disconnect timeout is the maximum number
; of seconds that an I/O can be disconnected from the bus. A timeout of -1
; allows an infinite timeout. The DMA timeout is the maximum timeout for
; a DMA transfer to complete or a phase change on the SCSI bus to occur;
; this timeout is also in units of seconds.
;
; The SETUP_CMD routine 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 Timeout	| 1 longword
;	+-----------------------+
;	|  Disconnect Timeout	| 1 longword
;	+-----------------------+
;
;
; DMA direction is defined as: 0=write, 1=read.
;-
	.MACRO	SCSI_CMD, NAME, CMD_BYTES, DMA_LEN=0, DMA_DIR=READ,-
			  DMA_TMO=4, DISCON_TMO=20

	.IF NOT_DEFINED $$$MAX_SCSI_CMD_LENGTH		;* Save max length
	$$$MAX_SCSI_CMD_LENGTH = 0	; Initialize maximum SCSI cmd length
	.ENDC						;*
'NAME'_CMD:
	$$$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
	.IF LT <$$$MAX_SCSI_CMD_LENGTH-$$$BYTE_COUNT>	;* Greater than MAX?
	$$$MAX_SCSI_CMD_LENGTH = $$$BYTE_COUNT		;* Make it new max
	.ENDC						;*
	  .WORD	DMA_LEN
	$$$DIRECTION = 0
	.IIF IDN DMA_DIR, READ, $$$DIRECTION = 1
	  .BYTE	$$$DIRECTION
	  .LONG	DMA_TMO
	  .LONG	DISCON_TMO
	.ENDM	SCSI_CMD

	.MACRO	LOG_ERROR	STATUS,ERROR,UCB=R3	;* Log error
	MOVL	#SCSI_C_'ERROR,UCB_L_WD_ERROR_TYPE(R3)	; Save the error type
	MOVL	STATUS,UCB_L_WD_PORT_STATUS(R3)		; Save the error status
	BSBW	LOG_ERROR				; Go log the error
	.ENDM	LOG_ERROR				;* End of macro


	.SBTTL	WD_DISABLE_ERL	- Disable error logging temporarily
	.SBTTL	WD_ENABLE_ERL	- Re-enable error logging
;+
; WD_DISABLE_ERL
;
; This macro sets a flag in the UCB to temporarily disable error logging.
;
;-
	.MACRO	WD_DISABLE_ERL	UCB=R3
	  BISL	#UCB_M_WD_ERLDIS, -	; Set the flag disable
		UCB$L_DEVDEPND2(UCB)	;  error logging
	.ENDM	WD_DISABLE_ERL

;+
; WD_ENABLE_ERL
;
; This macro clears the flag in the UCB to temporarily disable error logging,
; thus re-enabling error logging.
;
;-
	.MACRO	WD_ENABLE_ERL	UCB=R3
	  BICL	#UCB_M_WD_ERLDIS, -	; Clear the flag to re-enable
		UCB$L_DEVDEPND2(UCB)	;  error logging
	.ENDM	WD_ENABLE_ERL


	.SBTTL	WD_WAIT		- Stall a thread for a specific number of seconds
;+
; WD_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	WD_WAIT,SECONDS, SCDRP=R5
	  PUSHL	R1
	  MOVL	SECONDS, R1
	  BSBW	WD_WAIT
	  POPL	R1
	.ENDM	WD_WAIT

	.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$$
	.ENDM	MEDIA

	MEDIA	<DK>, <DKX000>, DT$_GENERIC_DK	; Test
	ASSUME	$$MEDIA$$ EQ <^X22C8BC00>

	MEDIA	<WD>, <DKX000>, DT$_GENERIC_DK	; Real thing
WD_K_MEDIA_ID = $$MEDIA$$

       .SBTTL  Standard Tables
;
; Driver Prologue Table.
;
	DPTAB	-				; Driver Prologue Table
-;		END = WD_END,-			; End of driver
		ADAPTER = NULL,-		 ; Adapter type
		UCBSIZE = UCB_K_WD_UCBLEN,-	; Length of UCB
		FLAGS = <DPT$M_SVP-		; Get a system page
			!DPT$M_SNAPSHOT-	;   enable snapshot
			!DPT$M_SMPMOD-		;   SMPized just in case
			!DPT$M_NO_IDB_DISPATCH>,- ; idb$l_ucblist is N/A
		MAXUNITS = 64,-			; Allow 8*8 for LUN mapping
		NAME = WDDRIVER,-		; Driver name
		STEP = 2,-			; ALPHA STEP 2 driver
		SMP  = YES			; Always...

	DPT_STORE INIT				; Start of load init table

	DPT_STORE DDB,DDB$L_ACPD,L,<^A\F11\>	; Default ACP name
	DPT_STORE DDB,DDB$L_ACPD+3,B,DDB$K_PACK	; ACP class
	DPT_STORE UCB,UCB$B_FLCK,B,SPL$C_IOLOCK8; Device fork lock index
	DPT_STORE UCB,UCB$B_DIPL,B,21		; Device interrupt IPL
	DPT_STORE UCB,UCB$L_DEVCHAR,L,-		; Device characteristics
		<DEV$M_FOD-			;  file oriented
		!DEV$M_DIR-			;  directory structure
		!DEV$M_AVL-			;  available
		!DEV$M_ELG-			;  error logging
		!DEV$M_SHR-			;  shareable
		!DEV$M_IDV-			;  input device
		!DEV$M_ODV-			;  output device
		!DEV$M_RND>			;  random access
	DPT_STORE UCB,UCB$B_DEVCLASS,B,DC$_DISK ; Device is a disk
	DPT_STORE UCB,UCB$B_DEVTYPE,B,DT$_GENERIC_DK	; Device is generic SCSI	01.0-13
	DPT_STORE UCB,UCB$W_DEVBUFSIZ,W,IOC$C_DISK_BLKSIZ ; Default buffer size
	DPT_STORE UCB,UCB$B_TRACKS,B,1		; Number of tracks per cylinder
	DPT_STORE UCB,UCB$L_ERTMAX,L,3		; Max error retry count
	DPT_STORE UCB,UCB$L_DEVSTS,L,<UCB$M_NOCNVRT> ; Inhibit log to phys conversion
	DPT_STORE UCB,UCB$L_MAXBCNT,L,MAX_BCNT	; Maximum byte count
	DPT_STORE UCB,UCB$L_DEVCHAR2,L,-
		<DEV$M_NNM-			; Prefix name with "node$"
		!DEV$M_SCSI-			; SCSI device
-;		!DEV$M_NOFE-			; No forced error (no shadowing)
		!DEV$M_NLT>			; No last track (no bad blocks)
	DPT_STORE UCB,UCB$L_MEDIA_ID,L,WD_K_MEDIA_ID ; Store media ID for MSCP

	DPT_STORE REINIT			; Start of reload init table

	DPT_STORE CRB,CRB$B_FLCK,B,IPL$_IOLOCK8	; Initialize fork lock field
	DPT_STORE END				; End of driver prologue table

;
; Driver Dispatch Table.
;

	DDTAB	-				; Driver Dispatch Table
		DEVNAM = WD,-			; Name of device
		START  = EXE_STD$KP_STARTIO,-	; Caller of Start I/O Routine
		KP_STARTIO = WD_STARTIO,-	; Start I/O routine
		KP_STACK_SIZE=<1024*32>,-	; Size of kernel process stack
		KP_REG_MASK=KPREG$K_HLL_REG_MASK,- ; Kernel process reg save mask
		FUNCTB = WD_FUNCTABLE,-		; FDT address
		CANCEL = WD_CANCEL,-		; Cancel I/O routine
		REGDMP = WD_REGDUMP,-		; Register dump routine
		CTRLINIT = WD_CONTROL_INIT,-	; Controller init routine
		UNITINIT = WD_UNIT_INIT,-	; Unit init routine
		ERLGBF = <<<20+1>*4> + -	; Error log buffer size
			  REQUEST_SENSE_LEN + -	;
			  EMB$L_DV_REGSAV >	;
;
; Function Decision Table.
;
	FDT_INI	WD_FUNCTABLE			; Init the FDT

	FDT_BUF <-				; Buffered I/O functions
		UNLOAD,-			;  unload
		PACKACK,-			;  pack acknowledge
		AVAILABLE,-			;  available
		SENSECHAR,-			;  sense characteristics
		SENSEMODE,-			;  sense mode
		ACCESS,-			;  access file
		ACPCONTROL,-			;  ACP control
		CREATE,-			;  create file
		DEACCESS,-			;  deaccess file
		DELETE,-			;  delete file
		MODIFY,-			;  modify file
		MOUNT-				;  mount volume
		>

	FDT_ACT +ACP_STD$READBLK,-		; Read functions
		<READLBLK,-			;  read logical block
		READPBLK,-			;  read physical block
		READVBLK>			;  read virtual block

	FDT_ACT +ACP_STD$WRITEBLK,-		; Write functions
		<WRITELBLK,-			;  write logical block
		WRITEPBLK,-			;  write physical block
		WRITEVBLK>			;  write virtual block

	FDT_ACT +ACP_STD$ACCESS,-		; Access functions
		<ACCESS,-			;  access file
		CREATE>				;  create file

	FDT_ACT +ACP_STD$DEACCESS,-		; Deaccess functions
		<DEACCESS>			;  deaccess file

	FDT_ACT +ACP_STD$MODIFY,-		; Modify functions
		<ACPCONTROL,-			;  ACP control
		DELETE,-			;  delete file
		MODIFY>				;  modify file

	FDT_ACT +ACP_STD$MOUNT,-		; Mount function
		<MOUNT>				;  mount disk

	FDT_ACT +EXE_STD$LCLDSKVALID,-		; Local disk valid functions
		<UNLOAD,-			;  unload volume
		AVAILABLE,-			;  set available
		PACKACK>			;  pack acknowledge

	FDT_ACT +EXE_STD$ONEPARM,-		; One parameter functions
		<LOAD_ELEMENT,-			;  move to drive/slot
		UNLOAD_ELEMENT>			;  move from drive/slot

	FDT_ACT +EXE_STD$SENSEMODE,-		; Sense mode functions
		<SENSECHAR,-			;  sense characteristics
		SENSEMODE>			;  sense mode

	FDT_ACT WD_DIAGNOSE,<DIAGNOSE>		; Special pass-through function


	.SBTTL	SCSI Command Packet Definition Table

WD_CMD_DEFS::
	SCSI_CMD -
		NAME = TEST_UNIT,-
		CMD_BYTES = <0, 0, 0, 0, 0, 0>

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

	ASSUME	REQUEST_SENSE_LEN LT 256
	SCSI_CMD -
		NAME = REQUEST_SENSE,-
		CMD_BYTES = <3, 0, 0, 0, REQUEST_SENSE_LEN, 0>,-
		DMA_LEN = REQUEST_SENSE_LEN,-
		DMA_DIR = READ

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

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

;
; *** NOTE ***  The Kodak 6800 sets the last byte in the WRITE_VERIFY command
; *** NOTE ***  to ^X80 to enable blank check on the write operation.
;
	SCSI_CMD -
		NAME = WRITE_VERIFY,-
		CMD_BYTES = <<^X2E>, 0, 0, 0, 0, 0, 0, 0, 0, 0>,-
		DMA_LEN = -1,-
		DMA_DIR = WRITE

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

	SCSI_CMD -
		NAME = MODE_SENSE_525,-
		CMD_BYTES = <26, 0, 63, 0, 150, 0>,-
		DMA_LEN = 150,-
		DMA_DIR = READ

	SCSI_CMD -
		NAME = MODE_SENSE_ELEM_ADDR,-
;***DEBUGGING  +
;		CMD_BYTES = <26, 0, <^X1D>, 0, 200, 0>,-
		CMD_BYTES = <26, 0, <^X1D>, 0, 100, 0>,-
;***DEBUGGING  -
		DMA_LEN = 100,-
		DMA_DIR = READ

	SCSI_CMD -
		NAME = MODE_SENSE,-
		CMD_BYTES = <26, 0, 0, 0, 4, 0>,-
		DMA_LEN = 4,-
		DMA_DIR = READ

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

	SCSI_CMD -
		NAME = START_UNIT,-
		CMD_BYTES = <27, 0, 0, 0, 1, 0>,-
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = STOP_UNIT,-
		CMD_BYTES = <27, 0, 0, 0, 0, 0>,-
		DISCON_TMO = 15

	SCSI_CMD -
		NAME = ALLOW_REMOVAL,-
		CMD_BYTES = <30, 0, 0, 0, 0, 0>,-
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = PREVENT_REMOVAL,-
		CMD_BYTES = <30, 0, 0, 0, 1, 0>,-
		DISCON_TMO = 30

	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 = SONY_DISK_EJECT,-
		CMD_BYTES = <<^XC0>, 0, 0, 0, 0, 0>,-
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = SONY_MO_DISK_EJECT, -
		CMD_BYTES = <27, 0, 0, 0, 2, 0>,-
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = PIONEER_DISK_EJECT,-
		CMD_BYTES = <<^XD1>, 0, 0, 0, 0, 0>,-
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = SONY_DISK_SET,-
		CMD_BYTES = <<^XD6>, 0, 0, 0, 0, 0>,-
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = SONY_DISK_RELEASE,-
		CMD_BYTES = <<^XD7>, 0, 0, 0, 0, 0>,-
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = SONY_SENDMAP,-
		CMD_BYTES = <<^X1D>, 0, 0, 0, 10, 0>,-
		DMA_LEN = 10,-
		DMA_DIR = WRITE,-
		DISCON_TMO = 30

	ASSUME	SONY_MAP_LENGTH LT 256
	SCSI_CMD -
		NAME = SONY_READMAP,-
		CMD_BYTES = <<^X1C>, 0, 0, 0, SONY_MAP_LENGTH, 0>,-
		DMA_LEN = SONY_MAP_LENGTH,-
		DMA_DIR = READ,-
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = HP_MOVE, -
		CMD_BYTES = <<^XA5>, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>,-
		DMA_LEN = 12, -
		DMA_DIR = WRITE, -
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = NKK_READMAP, -
		CMD_BYTES = <<^XB8>, 0, 0, 0, 2, 2, 0, 0, -
			NKK_MAP_LENGTH/256, NKK_MAP_LENGTH&255, 0, 0>,-
		DMA_LEN = NKK_MAP_LENGTH,-
		DMA_DIR = READ,-
		DISCON_TMO = 20

	SCSI_CMD -
		NAME = LMS_4500_MOVE, -
		CMD_BYTES = <<^X02>, 0, 0, 0, 0, 0>,-
		DMA_LEN = 6, -
		DMA_DIR = WRITE, -
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = LMS_UNLOAD, -
		CMD_BYTES = <<^X1B>, 0, 0, 0, 2, 0>,-
		DMA_LEN = 6, -
		DMA_DIR = WRITE, -
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = READ_MODE,-
		CMD_BYTES = <<^XD4>, 0, 0, 0, 2, 0>,-
		DMA_LEN = 2, -
		DMA_DIR = READ, -
		DISCON_TMO = 30

	SCSI_CMD -
		NAME = CHANGE_MODE,-
		CMD_BYTES = <<^XD3>, 0, 0, 0, 1, 0>,-
		DMA_LEN = 1, -
		DMA_DIR = WRITE, -
		DISCON_TMO = 30

WD_CMD_DEFS_END	=.
;
;  $WDUCBDEF has allocated 16 bytes for a saved SCSI command (12 bytes+length).
;  We need to make sure that's always big enough, thus the following ASSUME:

	ASSUME	$$$MAX_SCSI_CMD_LENGTH LE 12	; Make sure it's big enough

	.Sbttl	Local Storage
	.ALIGN	QUAD

;; DEBUG_AREA::				; Space for DEBUG stuff
D1::		.BLKL	1
D2::		.BLKL	1
D3::		.BLKL	1
D4::		.BLKL	1
D5::		.BLKL	1
D6::		.BLKL	1
D7::		.BLKL	1
D8::		.BLKL	1
D9::		.BLKL	1
D10::		.BLKL	1
D11::		.BLKL	1
D12::		.BLKL	1
EXPIRATION::	.QUAD	0		; Space for patched-in expiration date

SOFT_VERSION::
	.WORD	<<MAJVER@12>!<MINVER@8>!<ENGREL>>	; Software version

SERIAL_NUMBER:
	.word	29999			; Default to Serial # 29,999

;------------------------------------------------------------------------------
; Copyright notice for the image.
;------------------------------------------------------------------------------

COPYRIGHT:	.ascii	/ Copyright (c)  /		; copyright notice
		.ascii	/Perceptics Corp./
		.ascii	/ Knoxville, TN  /

;
; Variables for the DataCheck function
;
	.ALIGN	QUAD
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			;
;
; Debug Variables
;
WD_IRP::
	.long	0
WD_SCDRP::
	.long	0

	.SBTTL	HP Drive RWZ52 Mode Select Data
;
; MODE SELECT HEADER
; ------------------
RWZ52_MODE_SELECT_DATA:

	.BYTE	^X00	; (byte 0) RESERVED
	.BYTE	^X00	; (byte 1) MEDIUM TYPE
	.BYTE	^X00	; (byte 2) RESERVED
	.BYTE	^X00	; (byte 3) BLOCK DESCRIPTOR LENGTH

;	.BYTE	^X03	; (byte 4) DENSITY CODE
;	.BYTE	^X00	; (byte 5) NUMBER OF BLOCKS(MSB)
;	.BYTE	^X00	; (byte 6) NUMBER OF BLOCKS
;	.BYTE	^X00	; (byte 7) NUMBER OF BLOCKS(LSB)
;	.BYTE	^X00	; (byte 8) RESERVED
;	.BYTE	^X00	; (byte 9) BLOCK LENGTH(MSB)
;	.BYTE	^X00	; (byte 10) BLOCK LENGTH
;	.BYTE	^X00	; (byte 11) BLOCK LENGTH(LSB)

;
; READ-WRITE ERROR RECOVERY PAGE 01H
; ----------------------------------
	.BYTE	^X01	; (byte 12) RESERVED/PAGE CODE
	.BYTE	^X0A	; (byte 13) PAGE LENGTH
	.BYTE	^XA0	; (byte 14) AWRE/R/TB/R/R/PER/DTE/DCR
	.BYTE	^X05	; (byte 15) READ RETRY COUNT
	.BYTE	^X00	; (byte 16) RESERVED
	.BYTE	^X00	; (byte 17) RESERVED
	.BYTE	^X00	; (byte 18) RESERVED
	.BYTE	^X00	; (byte 19) RESERVED
	.BYTE	^X02	; (byte 20) WRITE RETRY COUNT
	.BYTE	^X00	; (byte 21) RESERVED
	.BYTE	^X00	; (byte 22) RESERVED
	.BYTE	^X00	; (byte 23) RESERVED

;
; DISCONNECT-RECONNECT PAGE 02H
; -----------------------------
	.BYTE	^X02	; (byte 12) PAGE CODE
	.BYTE	^X0E	; (byte 13) PAGE LENGTH
	.BYTE	^X80	; (byte 14) BUFFER FULL RATIO
	.BYTE	^X00	; (byte 15) RESERVED
	.BYTE	^X00	; (byte 16) RESERVED
	.BYTE	^X00	; (byte 17) RESERVED
	.BYTE	^X00	; (byte 18) RESERVED
	.BYTE	^X00	; (byte 19) RESERVED
	.BYTE	^X00	; (byte 20) RESERVED
	.BYTE	^X00	; (byte 21) RESERVED
	.BYTE	^X00	; (byte 22) MAXIMUM BURST SIZE(MSB)
	.BYTE	^X20	; (byte 23) MAXIMUM BURST SIZE(LSB)
	.BYTE	^X00	; (byte 24) RESERVED
	.BYTE	^X00	; (byte 25) RESERVED
	.BYTE	^X00	; (byte 26) RESERVED
	.BYTE	^X00	; (byte 27) RESERVED

;
; OPTICAL MEMORY PAGE 06H
; -----------------------
	.BYTE	^X06	; (byte 12) PAGE CODE
	.BYTE	^X02	; (byte 13) PAGE LENGTH
	.BYTE	^X00	; (byte 14) RESERVED
	.BYTE	^X00	; (byte 15) RESERVED

;
; VERIFY ERROR RECOVERY PAGE 07H
; ------------------------------
	.BYTE	^X07	; (byte 12) PAGE CODE
	.BYTE	^X0A	; (byte 13) PAGE LENGTH
	.BYTE	^X00	; (byte 14) PER/DCR
	.BYTE	^X05	; (byte 15) VERIFY RETRY COUNT
	.BYTE	^X00	; (byte 16) RESERVED
	.BYTE	^X00	; (byte 17) RESERVED
	.BYTE	^X00	; (byte 18) RESERVED
	.BYTE	^X00	; (byte 19) RESERVED
	.BYTE	^X00	; (byte 20) RESERVED
	.BYTE	^X00	; (byte 21) RESERVED
	.BYTE	^X00	; (byte 22) RESERVED
	.BYTE	^X00	; (byte 23) RESERVED


;
; CACHING PAGE 08H
; ----------------
	.BYTE	^X08	; (byte 12) PAGE CODE
	.BYTE	^X0A	; (byte 13) PAGE LENGTH
	.BYTE	^X00	; (byte 14) WCE/RCD
	.BYTE	^X00	; (byte 15) RESERVED
	.BYTE	^XFF	; (byte 16) DISABLE PRE-FETCH TRANSFER LENGTH(MSB)
	.BYTE	^XFF	; (byte 17) DISABLE PRE-FETCH TRANSFER LENGTH(LSB)
	.BYTE	^X00	; (byte 18) MINIMUM PRE-FETCH(MSB)
	.BYTE	^X04	; (byte 19) MINIMUM PRE-FETCH
	.BYTE	^X00	; (byte 20) MINIMUM PRE-FETCH
	.BYTE	^X08	; (byte 21) MINIMUM PRE-FETCH(LSB)
	.BYTE	^X00	; (byte 22) RESERVED
	.BYTE	^X00	; (byte 23) RESERVED

;
; MEDIUM TYPES SUPPORTED PAGE 0BH
; -------------------------------
	.BYTE	^X0B	; (byte 12) PAGE CODE
	.BYTE	^X06	; (byte 13) PAGE LENGTH
	.BYTE	^X00	; (byte 14) RESERVED
	.BYTE	^X00	; (byte 15) RESERVED
	.BYTE	^X02	; (byte 16) MEDIUM TYPE ONE SUPPORTED (02H WORM)
	.BYTE	^X03	; (byte 17) MEDIUM TYPE TWO SUPPORTED (03H ERASEABLE)
	.BYTE	^X00	; (byte 18) MEDIUM TYPE THREE SUPPORTED
	.BYTE	^X00	; (byte 19) MEDIUM TYPE FOUR SUPPORTED

;
; VENDOR UNIQUE FORMAT PAGE 20H
; -----------------------------
;	.BYTE	^X20	; (byte 12) PAGE CODE
;	.BYTE	^X0C	; (byte 13) PAGE LENGTH
;	.BYTE	^X00	; (byte 14) RESERVED
;	.BYTE	^X00	; (byte 15) GROUPS PER VOLUME(MSB)
;	.BYTE	^X00	; (byte 16) GROUPS PER VOLUME(LSB)
;	.BYTE	^X00	; (byte 17) DATA BLOCKS PER GROUP(MSB)
;	.BYTE	^X00	; (byte 18) DATA BLOCKS PER GROUP
;	.BYTE	^X00	; (byte 19) DATA BLOCKS PER GROUP(LSB)
;	.BYTE	^X00	; (byte 20) ALTERNATE BLOCKS PER GROUP(MSB)
;	.BYTE	^X00	; (byte 21) ALTERNATE BLOCKS PER GROUP
;	.BYTE	^X00	; (byte 22) ALTERNATE BLOCKS PER GROUP(LSB)
;	.BYTE	^X00	; (byte 23) SECTORS IN TRACK 0
;	.BYTE	^X00	; (byte 24) RESERVED
;	.BYTE	^X00	; (byte 25) RESERVED

;
; VENDOR UNIQUE PAGE 21H
; ----------------------
	.BYTE	^X21	; (byte 12) PAGE CODE
	.BYTE	^X0A	; (byte 13) PAGE LENGTH
	.BYTE	^X02	; (byte 14) ERR/DSP LOG/DM LOG/CM LOG/RESET/DAS/DTIS/DAIR
	.BYTE	^X98	; (byte 15) FORCE VERIFY/DLTW/Q LOG/ TASK LOG/ TIMESTAMP
	.BYTE	^X00	; (byte 16) MAXIMUM BUFFER LATENCY(MSB)
	.BYTE	^X00	; (byte 17) MAXIMUM BUFFER LATENCY
	.BYTE	^X03	; (byte 18) MAXIMUM BUFFER LATENCY
	.BYTE	^XE8	; (byte 19) MAXIMUM BUFFER LATENCY(LSB)
	.BYTE	^X02	; (byte 20) DRIVE RETRY COUNT
	.BYTE	^X96	; (byte 21) AUTOCHANGER EJECT DISTANCE
	.BYTE	^X05	; (byte 22) PHASE RETRY COUNT
	.BYTE	^X00	; (byte 23) RESERVED

RWZ52_MSD_LENGTH = . - RWZ52_MODE_SELECT_DATA ; Length of data

	.SBTTL	HP Drive RWZ53 Mode Select Data
;
; MODE SELECT HEADER
; ------------------
RWZ53_MODE_SELECT_DATA:

	.BYTE	^X00	; (byte 0) RESERVED
	.BYTE	^X00	; (byte 1) MEDIUM TYPE
	.BYTE	^X00	; (byte 2) RESERVED
	.BYTE	^X00	; (byte 3) BLOCK DESCRIPTOR LENGTH

;	.BYTE	^X03	; (byte 4) DENSITY CODE
;	.BYTE	^X00	; (byte 5) NUMBER OF BLOCKS(MSB)
;	.BYTE	^X00	; (byte 6) NUMBER OF BLOCKS
;	.BYTE	^X00	; (byte 7) NUMBER OF BLOCKS(LSB)
;	.BYTE	^X00	; (byte 8) RESERVED
;	.BYTE	^X00	; (byte 9) BLOCK LENGTH(MSB)
;	.BYTE	^X00	; (byte 10) BLOCK LENGTH
;	.BYTE	^X00	; (byte 11) BLOCK LENGTH(LSB)

;
; READ-WRITE ERROR RECOVERY PAGE 01H
; ----------------------------------
	.BYTE	^X01	; (byte 12) RESERVED/PAGE CODE
	.BYTE	^X0A	; (byte 13) PAGE LENGTH
	.BYTE	^XA0	; (byte 14) AWRE/R/TB/R/R/PER/DTE/DCR
	.BYTE	^X05	; (byte 15) READ RETRY COUNT
	.BYTE	^X00	; (byte 16) RESERVED
	.BYTE	^X00	; (byte 17) RESERVED
	.BYTE	^X00	; (byte 18) RESERVED
	.BYTE	^X00	; (byte 19) RESERVED
	.BYTE	^X02	; (byte 20) WRITE RETRY COUNT
	.BYTE	^X00	; (byte 21) RESERVED
	.BYTE	^X00	; (byte 22) RESERVED
	.BYTE	^X00	; (byte 23) RESERVED
;
; DISCONNECT-RECONNECT PAGE 02H
; -----------------------------
	.BYTE	^X02	; (byte 12) PAGE CODE
	.BYTE	^X0E	; (byte 13) PAGE LENGTH
	.BYTE	^X80	; (byte 14) BUFFER FULL RATIO
	.BYTE	^X00	; (byte 15) RESERVED
	.BYTE	^X00	; (byte 16) RESERVED
	.BYTE	^X00	; (byte 17) RESERVED
	.BYTE	^X00	; (byte 18) RESERVED
	.BYTE	^X00	; (byte 19) RESERVED
	.BYTE	^X00	; (byte 20) RESERVED
	.BYTE	^X00	; (byte 21) RESERVED
	.BYTE	^X00	; (byte 22) MAXIMUM BURST SIZE(MSB)
	.BYTE	^X20	; (byte 23) MAXIMUM BURST SIZE(LSB)
	.BYTE	^X00	; (byte 24) RESERVED
	.BYTE	^X00	; (byte 25) RESERVED
	.BYTE	^X00	; (byte 26) RESERVED
	.BYTE	^X00	; (byte 27) RESERVED
;
; CACHING PAGE 08H
; ----------------
	.BYTE	^X08	; (byte 12) PAGE CODE
	.BYTE	^X0A	; (byte 13) PAGE LENGTH
	.BYTE	^X02	; (byte 14) WCE/MF/RCD
	.BYTE	^X00	; (byte 15) RESERVED
	.BYTE	^XFF	; (byte 16) DISABLE PRE-FETCH TRANSFER LENGTH(MSB)
	.BYTE	^XFF	; (byte 17) DISABLE PRE-FETCH TRANSFER LENGTH(LSB)
	.BYTE	^X00	; (byte 18) MINIMUM PRE-FETCH(MSB)
	.BYTE	^X04	; (byte 19) MINIMUM PRE-FETCH(LSB)
	.BYTE	^X00	; (byte 20) MINIMUM PRE-FETCH(MSB)
	.BYTE	^X08	; (byte 21) MINIMUM PRE-FETCH(LSB)
	.BYTE	^X00	; (byte 22) MAXIMUM PRE-FETCH CEILING(MSB)
	.BYTE	^X40	; (byte 23) MAXIMUM PRE-FETCH CEILING(LSB)
;
; Control mode PAGE 0AH
; ------------------------------
	.BYTE	^X0A	; (byte 12) PAGE CODE
	.BYTE	^X06	; (byte 13) PAGE LENGTH
	.BYTE	^X00	; (byte 14) RESERVED
	.BYTE	^X00	; (byte 15) RESERVED
	.BYTE	^X00	; (byte 16) RESERVED
	.BYTE	^X00	; (byte 17) RESERVED
	.BYTE	^X00	; (byte 18) RESERVED
	.BYTE	^X00	; (byte 19) RESERVED
;
; MEDIUM TYPES SUPPORTED PAGE 0BH
; -------------------------------
	.BYTE	^X0B	; (byte 12) PAGE CODE
	.BYTE	^X06	; (byte 13) PAGE LENGTH
	.BYTE	^X00	; (byte 14) RESERVED
	.BYTE	^X00	; (byte 15) RESERVED
	.BYTE	^X02	; (byte 16) MEDIUM TYPE ONE SUPPORTED (02H WORM)
	.BYTE	^X03	; (byte 17) MEDIUM TYPE TWO SUPPORTED (03H ERASEABLE)
	.BYTE	^X00	; (byte 18) MEDIUM TYPE THREE SUPPORTED
	.BYTE	^X00	; (byte 19) MEDIUM TYPE FOUR SUPPORTED

;
; VENDOR UNIQUE FORMAT PAGE 20H
; -----------------------------
;	.BYTE	^X20	; (byte 12) PAGE CODE
;	.BYTE	^X0C	; (byte 13) PAGE LENGTH
;	.BYTE	^X00	; (byte 14) RESERVED
;	.BYTE	^X00	; (byte 15) GROUPS PER VOLUME(MSB)
;	.BYTE	^X00	; (byte 16) GROUPS PER VOLUME(LSB)
;	.BYTE	^X00	; (byte 17) DATA BLOCKS PER GROUP(MSB)
;	.BYTE	^X00	; (byte 18) DATA BLOCKS PER GROUP
;	.BYTE	^X00	; (byte 19) DATA BLOCKS PER GROUP(LSB)
;	.BYTE	^X00	; (byte 20) ALTERNATE BLOCKS PER GROUP(MSB)
;	.BYTE	^X00	; (byte 21) ALTERNATE BLOCKS PER GROUP
;	.BYTE	^X00	; (byte 22) ALTERNATE BLOCKS PER GROUP(LSB)
;	.BYTE	^X00	; (byte 23) SECTORS IN TRACK 0
;	.BYTE	^X00	; (byte 24) RESERVED
;	.BYTE	^X00	; (byte 25) RESERVED
;
; VENDOR UNIQUE PAGE 21H
; ----------------------
	.BYTE	^X21	; (byte 12) PAGE CODE
	.BYTE	^X0A	; (byte 13) PAGE LENGTH
	.BYTE	^X02	; (byte 14) 0/DAIR
	.BYTE	^X40	; (byte 15) DWR/QUICK DISCONNECT/RESERVED &
			;	    /FORCE VERIFY/RESERVED
	.BYTE	^X00	; (byte 16) RESERVED
	.BYTE	^X00	; (byte 17) RESERVED
	.BYTE	^X03	; (byte 18) RESERVED
	.BYTE	^XE8	; (byte 19) RESERVED
	.BYTE	^X02	; (byte 20) RESERVED
	.BYTE	^X96	; (byte 21) RESERVED
	.BYTE	^X05	; (byte 22) RESERVED
	.BYTE	^X00	; (byte 23) RESERVED

RWZ53_MSD_LENGTH = . - RWZ53_MODE_SELECT_DATA ; Length of data

	.Sbttl	Drive Configuration Table
;
; Drive Configuration Table
;
;	This table contains the block capacity and sectors per track for
;	every supported device by this driver. Table order is maximum block
;	capacity for drive, support capacity for drive, blocks per track,
;	and manufacture code.
;
;	Drive manufacture codes:
;
;	30 - LMS 1200 12 inch drives with 600 series firmware
;	31 - LMS 1200 12 inch drives with 700 series firmware
;	32 - LMS LD510 5-1/4 inch drives
;	35 - LMS LD4100 12 inch drives
;	40 - Pioneer 5-1/4 inch drives
;	50 - Sony
;	60 - Kodak 6800
;
;	The two SONY medias, CLV and CAV, differ only in capacity.  For
;	now, there is no distinction made by manufacturer code for the two.
;
;  **** Special Note:	Optimem drives only.
;
;	The Optimem drives return inquiry information about the controller.
;	Either a 1.6 controller or 2.0 controller and these controllers can
;	be used in any of the drives, ie00(M), or 2400(M). This table
;	plus the special Optimem test code in IO_PACKACK sets the manufacturer
;	code as follows:
;
;	10 - Optimem 1.6 Controller with 1.0 Gig media, 12 inch drive
;	11 - Optimem 1.6 Controller with 1.2 Gig media, 12 inch drive
;	12 - Optimem 1000 2.0 Controller with 1.0 Gig media, 12 inch drive
;	13 - Optimem 1000 2.0 Controller with 1.2 Gig media, 12 inch drive
;
;	14 - Optimem 2400 2.0 Controller with 1.0 Gig media, 12 inch drive
;	15 - Optimem 2400 2.0 Controller with 1.2 Gig media, 12 inch drive
;
;	The inquiry code sets the drive type based on the controller found,
;	1.6 or 2.0. The 1.6 controller cannot tell us if it is in a 1000 or
;	2400 and it is assigned a code of 10. The 2.0 Controller tells us it
;	is a 1000 or 2400 so it get two codes, 12 and 14. This allows the
;	drive_table search to find a valid entry and set maxblock. I wanted
;	to also identify to the size media currently in use so if the
;	maxblock is set to the 1.2 gig block size we just increment the
;	drive type. This means if you change the codes 10, 12, or 14 you
;	must make sure the new ones plus one are available also or change
;	the code in the packack routine.
;

WD_C_OPT_12G_SIZE = 2344998			; 1.2G size (used in spin up)

;
; Drive Inquiry Table
;
; The following table shows the string to be matched to the string
; returned from the device with an INQUIRY operation.  In the following
; strings, * matches 0 or more characters, % matches one character.
;

	.Macro	INQSTRING	String,Type,Max,Flags=WORM,Disconnect=YES,?L1
	  .Save
	  .Psect $$$114_TEXT
L1:	  .ASCII \String\
$$1	  = .-L1
	  .Restore
$$2	  = 0
	.IRP	$$3,<Flags>
$$2	  = $$2 ! OPTIC$M_'$$3
	.Endr
	.IIF IDENTICAL <Disconnect> <YES>, $$2 = $$2 ! OPTIC$M_DISCON
	.IRP	MaxValue,<Max>
	  .Long		L1-INQUIRY_TABLE ; Offset to string descriptor
	  .Byte		$$1		; Length of string
	  .Byte		Type		; Device type if string matches
	  .Word		$$2		; Device flags
	  .Long		MaxValue	; Capacity of the drive
	.Endr
	.Endm	INQSTRING

DRVTBL$L_STRING	= 0	; Offset to string descriptor
DRVTBL$B_S_Size	= 4	; Offset to length of string
DRVTBL$B_TYPE	= 5	; Offset to device type code
DRVTBL$W_FLAGS	= 6	; Offset to device flags
DRVTBL$L_MAX	= 8	; Offset to capacity

DRVTBL$K_SIZE	= 12	; Size of a table entry
DRVTBL$K_BLOCKS	= 240	; Blocks per track

	.ALIGN	QUAD

INQUIRY_TABLE::

;	INQSTRING	String	= <*OSI*12%0*>,-
;			Type	= OPTIC$C_LMS_1200_700,-
;			Max	= <2000000, 2048000>,-
;			Flags	= <WORM, SELFLOAD, LONG_TMO>,-
;			Disconnect = <NO>
;
;	INQSTRING	String	= <*LMS*LD*520*>,-
;			Type	= OPTIC$C_LMS_LD520,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <MULTI, S525>
;
	INQSTRING	String	= <*HP*C1716C*>,-
			Type	= OPTIC$C_HP_1716C,-
			Max	= <0>,-		; Allow any size media
			Flags	= <MULTI, WRITE_VERIFY, SCSI2, S525>

	INQSTRING	String	= <*HP*C1716T*>,- ; Double-density model
			Type	= OPTIC$C_HP_1716C,-
			Max	= <0>,-		; Allow any size media
			Flags	= <MULTI, WRITE_VERIFY, SCSI2, S525>

	INQSTRING	String	= <*DEC*RWZ52*>,- ; Double-density model
			Type	= OPTIC$C_HP_1716C,-
			Max	= <0>,-		; Allow any size media
			Flags	= <MULTI, WRITE_VERIFY, SCSI2, S525>

	INQSTRING	String	= <*HP*C1113F*>,- ; 4X model
			Type	= OPTIC$C_RWZ53,-
			Max	= <0>,-		; Allow any size media
			Flags	= <MULTI, WRITE_VERIFY, SCSI2, S525>

	INQSTRING	String	= <*DEC*RWZ53*>,- ; 4X model
			Type	= OPTIC$C_RWZ53,-
			Max	= <0>,-		; Allow any size media
			Flags	= <MULTI, WRITE_VERIFY, SCSI2, S525>

;	INQSTRING	String	= <*MaxOptixTahiti 2*>,-
;			Type	= OPTIC$C_TAHITI_2M,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <MULTI, WRITE_VERIFY, SCSI2, S525>
;
	INQSTRING	String	= <*HP*MF D*>,-
			Type	= OPTIC$C_HP_MF,-
			Max	= <0>,-		; Allow any size media
			Flags	= <MULTI, WRITE_VERIFY, S525>

;	INQSTRING	String	= <*PION*C7*>,-
;			Type	= OPTIC$C_PIONEER_7001,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <MULTI, WRITE_VERIFY, S525>
;
;	INQSTRING	String	= <*PION*ROM*1804*>,-; Pioneer CD 1804 Drive
;			Type	= OPTIC$C_CDROM,-
;			Max	= <0>,-         ; Allow any size media
;			Flags	= <S525, DOUBLE, LONG_TMO>,-
;			Disconnect = <YES>
;;
;; The Pioneer CD-ROM DRM-604X comes in the 604 six disk changer which is
;; controlled like six drives, or as drives in a 5004 were it is only seen
;; as a drive.
;;
;	INQSTRING	String	= <*CD*ROM*DRM*>,- ; Pioneer CD-ROM Changer
;			Type	= OPTIC$C_CDROM,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <JUKEBOX, S525, DOUBLE, LONG_TMO>,-
;			Disconnect = <YES>
;;
;; The Pioneer 1804 and 5004 jukeboxes act similar but have different inquiry
;; strings.  AXP must allow LUNS to disconnect, so this code table is different
;; for the 1804 as compared to the VAX.  Also do not prevent media removal for
;; the 5004.
;;
;	INQSTRING	String	= <*PION*CHANG*1804*>,-; Pioneer CD 1804 Changer
;			Type	= OPTIC$C_CD_CHANGER,-
;			Max	= <0>,-         ; Allow any size media
;			Flags	= <JUKEBOX, S525, LONG_TMO, ELEM_ADDR, PREVENT_REM>,-
;			Disconnect = <YES>
;
;	INQSTRING	String	= <*PION*CHANG*5004*>,-; Pioneer CD 5004 Changer
;			Type	= OPTIC$C_CD_CHANGER,-
;			Max	= <0>,-         ; Allow any size media
;			Flags	= <JUKEBOX, S525, DOUBLE, LONG_TMO, ELEM_ADDR>,-
;			Disconnect = <YES>
;
;	INQSTRING	String	= <*CD*ROM*>,-
;			Type	= OPTIC$C_CDROM,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <S525>
;
;	INQSTRING	String	= <*LMS*12%0*>,-
;			Type	= OPTIC$C_LMS_1200_700,-
;			Max	= <2000000, 2048000>,-
;			Flags	= <WORM, SELFLOAD, LONG_TMO>,-
;			Disconnect = <NO>
;
;	INQSTRING	String	= <*LMS*LD*510*>,-
;			Type	= OPTIC$C_LMS_LD510,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <WORM, S525, WRITE_VERIFY>,-
;			Disconnect = <NO>
;
;	INQSTRING	String	= <*LMS*LD*4100*>,-
;			Type	= OPTIC$C_LMS_LD4100,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <WORM, DOUBLE, SELFLOAD, -
;					WRITE_VERIFY, LONG_TMO>
;
;	INQSTRING	String	= <*LMS*LD*6100*>,-
;			Type	= OPTIC$C_LMS_LD4100,-
;			Max	= <10935934, 23326320>,-
;			Flags	= <WORM, DOUBLE, SELFLOAD, -
;					WRITE_VERIFY, LONG_TMO>
;
;	INQSTRING	String	= <*LMS*LF*51*>,-
;			Type	= OPTIC$C_HP_JB,-
;			Max	= 0,-
;			Flags	= <JUKEBOX, S525, ELEM_ADDR>
;
;	INQSTRING	String	= <*HP*C17*>,-
;			Type	= OPTIC$C_HP_JB,-
;			Max	= 0,-
;			Flags	= <JUKEBOX, S525, ELEM_ADDR, PREVENT_REM>
;
;	INQSTRING	String	= <*HP*C116*>,-
;			Type	= OPTIC$C_HP_JB,-
;			Max	= 0,-
;			Flags	= <JUKEBOX, S525, ELEM_ADDR, PREVENT_REM>
;
;	INQSTRING	String	= <*DEC*RW5*>,-
;			Type	= OPTIC$C_HP_JB,-
;			Max	= 0,-
;			Flags	= <JUKEBOX, S525, ELEM_ADDR, PREVENT_REM>
;
;	INQSTRING	String	= <*LMS*LF*52*>,-
;			Type	= OPTIC$C_LMS_LF520,-
;			Max	= 0,-
;			Flags	= <JUKEBOX, S525, ELEM_ADDR>
;
;	INQSTRING	String	= <*NKK*556*>,-
;			Type	= OPTIC$C_NKK_556,-
;			Max	= 0,-
;			Flags	= <JUKEBOX, S525, ELEM_ADDR>
;
;	INQSTRING	String	= <*K-560E*>,-
;			Type	= OPTIC$C_NKK_556,-
;			Max	= 0,-
;			Flags	= <JUKEBOX, S525, ELEM_ADDR>,-
;			Disconnect = <NO>
;
;	INQSTRING	String	= <*INSPIRE*55*>,-
;			Type	= OPTIC$C_NKK_556,-
;			Max	= 0,-
;			Flags	= <JUKEBOX, S525, ELEM_ADDR>
;
;	INQSTRING	String	= <*CYGNET*1800*>,-
;			Type	= OPTIC$C_CYGNET,-
;			Max	= 0,-
;			Flags	= <JUKEBOX, ELEM_ADDR>
;
;	INQSTRING	String	= <*LMS*LF*4500*>,-
;			Type	= OPTIC$C_LMS_LF4500,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <WORM, DOUBLE, JUKEBOX, SELFLOAD, -
;					WRITE_VERIFY, LONG_TMO>
;
;	INQSTRING	String	= <*LMS*LF*6600*>,-
;			Type	= OPTIC$C_LMS_LF4500,-
;			Max	= <10935934, 23326320>,-
;			Flags	= <WORM, DOUBLE, JUKEBOX, SELFLOAD, -
;					WRITE_VERIFY, LONG_TMO>
;
;	INQSTRING	String	= <*PION*C5*>,-
;			Type	= OPTIC$C_PIONEER_5001,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <WORM, S525, MULTI>
;
;	INQSTRING	String	= <KODA*6800*>,-
;			Type	= OPTIC$C_KODAK_6800,-
;			Max	= <10012358, 6643470>,-
;			Flags	= <WORM, WRITE_VERIFY, LONG_TMO, SELFLOAD>,-
;			Disconnect = <NO>	; Can't disconnect with LUNs
;
;	INQSTRING	String	= <*SONY*WDD-93*>,-
;			Type	= OPTIC$C_SONY_WDD_931,-
;			Max	= <0>, -	; Allow any size media
;			Flags	= <WORM, LONG_TMO, WRITE_VERIFY>
;
;	INQSTRING	String	= <*DEC*RVZ72*>,-
;			Type	= OPTIC$C_SONY_WDD_931,-
;			Max	= <0>, -	; Allow any size media
;			Flags	= <WORM, LONG_TMO, WRITE_VERIFY>
;
;	INQSTRING	String	= <*SONY*WDA*33*>,-
;			Type	= OPTIC$C_SONY_WDA_330,-
;			Max	= <0>, -	; Allow any size media
;			Flags	= <JUKEBOX, LONG_TMO, ELEM_ADDR>
;
;	INQSTRING	String	= <*DEC*RV72*>,-
;			Type	= OPTIC$C_SONY_WDA_330,-
;			Max	= <0>, -	; Allow any size media
;			Flags	= <JUKEBOX, LONG_TMO, ELEM_ADDR>
;
;	INQSTRING	String	= <*SONY*WDA*93*>,-
;			Type	= OPTIC$C_SONY_WDA_930,-
;			Max	= <0>, -	; Allow any size media
;			Flags	= <JUKEBOX, LONG_TMO, ELEM_ADDR>
;
;	INQSTRING	String	= <*DEC*RV73*>,-
;			Type	= OPTIC$C_SONY_WDA_930,-
;			Max	= <0>, -	; Allow any size media
;			Flags	= <JUKEBOX, LONG_TMO, ELEM_ADDR>
;
;	INQSTRING	String	= <*SONY*SMO-S*>,-
;			Type	= OPTIC$C_SONY_SMOS,-
;			Max	= <0>,-		; Any media size
;			Flags	= <S525, LONG_TMO>
;
;	INQSTRING	String	= <*SONY*SMO-F511*>,-
;			Type	= OPTIC$C_SONY_SMOF,-
;			Max	= <0>,-		; Any media size
;			Flags	= <RW, WRITE_VERIFY, S525>
;
;	INQSTRING	String	= <*SONY*SMO-F521*>,-
;			Type	= OPTIC$C_SONY_F521,-
;			Max	= <0>,-		; Any media size
;			Flags	= <MULTI, WRITE_VERIFY, S525>
;
;	INQSTRING	String	= <*SONY*OSL-2000*>,-
;			Type	= OPTIC$C_OSL2000_JB,-
;			Max	= <0>,-		; Any media size
;			Flags	= <JUKEBOX, S525, ELEM_ADDR, PREVENT_REM>
;
;	INQSTRING	String	= <*OPTI*600*>,-
;			Type	= OPTIC$C_OPTIMEM_600,-
;			Max	= <637696, 637440>,-
;			Flags	= <WORM, S525>,-
;			Disconnect = <NO>
;
;	INQSTRING	String	= <*OPTI*1000*>,-
;			Type	= OPTIC$C_OPTIMEM_1000_20_10,-
;			Max	= <2000000, 2345000>,-
;			Flags	= <WORM, SELFLOAD>,-
;			Disconnect = <NO>
;
;	INQSTRING	String	= <*GIGADISC*-J*>,-
;			Type	= OPTIC$C_GIGADISC_JB,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <WORM, JUKEBOX, WRITE_VERIFY, -
;					SELFLOAD, SCSI2, LONG_TMO>,-
;			Disconnect = <NO>
;
;	INQSTRING	String	= <*GIGADISC*>,-
;			Type	= OPTIC$C_GIGADISC,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <WORM, WRITE_VERIFY, SCSI2>,-
;			Disconnect = <NO>
;
;	INQSTRING	String	= <*ATG*>,-
;			Type	= OPTIC$C_OPTIMEM_16_10,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <WORM, WRITE_VERIFY, SELFLOAD, LONG_TMO>
;
;	INQSTRING	String	= <*DISC*DISC*>,-
;			Type	= OPTIC$C_DISC_JB,-
;			Max	= <0>,-		; Allow any size media
;			Flags	= <JUKEBOX, ELEM_ADDR, SCSI2, S525, LONG_TMO>
;
;	INQSTRING	String	= <*OPTI*2400*>,-
;			Type	= OPTIC$C_OPTIMEM_2400_20_10,-
;			Max	= <2000000, 2345000>,-
;			Flags	= <WORM, WRITE_VERIFY, SELFLOAD>,-
;			Disconnect = <NO>
;
OPTIMEM_16_10_INQSTRING:
;	INQSTRING	String	= <XXX>,-			; No INQ data
;			Type	= OPTIC$C_OPTIMEM_16_10,-
;			Max	= <2000000, 2345000>,-
;			Flags	= <WORM, SELFLOAD>,-
;			Disconnect = <NO>
;
LMS_1200_600_INQSTRING:
;	INQSTRING	String	= <XXX>,-			; No INQ data
;			Type	= OPTIC$C_LMS_1200_600,-
;			Max	= 2000000,-
;			Flags	= <WORM, SELFLOAD, LONG_TMO>,-
;			Disconnect = <NO>

	INQSTRING	String	= <*>,-				; Generic
			Type	= OPTIC$C_GENERIC,-		; Type
			Max	= <0>,-				; Any size
			Flags	= <S525>			; Assume 5-1/4"

	.LONG	0						; End of table

	.ALIGN	QUAD

INQUIRY_TABLE_END::


	.sbttl	WD_CONTROL_INIT - Controller Initialization Routine
;
; WD_CONTROL_INIT - Ready controller for I/O operations.
;
; The controller init routine does nothing in this driver.  All
; initialization is handled by the unit init routine.
;
; Inputs:
;
;	R4 - Address of the IDB
;
; Outputs:
;
;	R0 = status
;	All registers are preserved across the call.
;

	DRIVER_CODE

WD_CONTROL_INIT::
	$DRIVER_CTRLINIT_ENTRY

	MOVQ	EXPIRATION,R1		; Get the expiration date
	BEQL	5$			; Branch if no expiration
	SUBL2	G^EXE$GQ_SYSTIME,R1	; Subtract low-order longword
	SBWC	G^EXE$GQ_SYSTIME+4,R2	; Subtract high-order longword
	BGEQ	5$			; Branch if OK
	MOVL	#SS$_TIMEOUT,R0		; Return "device timeout"
	BRW	9$			; Exit

5$:	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
	MOVL	#SS$_NORMAL,R0		; Set success status
9$:	RET				; Exit

15$:	MOVL	#SS$_NORMAL,R0		; Set success status
	RET				; Return to caller
;
; Create fork thread to finish controller init.
;
10$:
	MOVL	R8,R5			; Fork with CRB
	MOVL	#SS$_NORMAL,R0		; Set success status
	FORK CONTINUE=15$,ENVIRONMENT=CALL
;
; 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,R5>		; Save registers.
	MOVC5	#0, (SP), #0, R1, (R2)	; Zero entire block.
	POPR	#^M<R1,R2,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	R8,CDDB$L_CRB(R2)	; CRB address
	MOVL	R6,CDDB$L_DDB(R2)	; DDC address
	MOVL	R2,CRB$L_AUXSTRUC(R5)	; Save CDDB address in CRB.
	MOVL	#SS$_NORMAL,R0		; Set success status
	RET				; Return to caller

	.SBTTL	WD_UNIT_INIT - Unit Initialization Routine
;
; WD_UNIT_INIT - Readies unit for I/O operations.
;
; The operating system calls this routine after calling the controller
; initialization routine:
;
;		at system startup
;		during driver loading
;		during recovery from a power failure
;
; 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.
;
; This routine allocates a set of SCDRPs and places them on a queue in the
; UCB, forms a connection to the port driver by calling SPI$CONNECT, and
; sets the unit online.
;
; INPUTS:
;
;	R5	- UCB address
;
; OUTPUTS:
;
;	R0 = status
;	R1-R3	- Destroyed
;	All other registers preserved
;
WD_UNIT_INIT::
	$DRIVER_UNITINIT_ENTRY

	MOVL	#SS$_NORMAL, R0		; Assume success
	BBC	#UCB$V_POWER,-		; Branch if we're not here due to a
		UCB$L_STS(R5),10$	; powerfail
	RET				; Otherwise, exit immediately

 15$:	MOVL	#SS$_NORMAL, R0		; Assume success
	RET

 10$:	FORK CONTINUE=15$,ENVIRONMENT=CALL

	KP_ALLOCATE_KPB -		; Allocate kernel process block
		KPB=UCB_PS_UNITINIT_KPB(R5),-
		FLAGS=#KP$M_IO		; Specify deallocation after init ends
	BLBC	R0,130$			; Branch if error allocating KPB
	MOVL	UCB_PS_UNITINIT_KPB(R5),- ; Save KPB address in UCB
		R2
	MOVL	R5,KPB$PS_UCB(R2)	; Save UCB address in KPB

	KP_START -			; Start the kernel process that does
		KPB=R2, -		;  most of the unit init work
		ROUTINE=WD_KP_UNIT_INIT, -
		REGISTERS=#KPREG$K_HLL_REG_MASK

	MOVL	#SS$_NORMAL, R0		; Success
	RET				; Return from WD_UNIT_INIT

130$:	MOVL	#SS$_BUGCHECK, R0
	RET				; Return from WD_UNIT_INIT

;
; The remainder of this routine runs as a kernel process.
; This is necessary to make SPI$ calls and access the port driver.
;
; The KP register save mask (above) saves all registers appropriate to a HLL
; driver.  This permits port drivers to be written in a high-level language.
; The starting KP routine (call entry below) saves only the registers used
; by that routine.
;
WD_KP_UNIT_INIT:
	.CALL_ENTRY PRESERVE=<R2,R3,R4,R5,R6,R7,R8>, HOME_ARGS=TRUE, -
				MAX_ARGS=2

	MOVL	4(AP),R2		; Pick up pointer to KPB
	MOVL	KPB$PS_UCB(R2),R5	; Restore UCB address

	MOVZWL	UCB$W_UNIT(R5), R0
	PUT_N2	#20, <"WDD-WD_KP_UNIT_INIT: KP Unit init for unit # %d ">,R0

;
; Store the serial number in the UCB.
;
 20$:	MOVW	SOFT_VERSION, -		; Defined in source code
		UCB_W_WD_SOFTVER(R5)	; Save in UCB
	MOVW	SERIAL_NUMBER, -	; Set by PATCH
		UCB_W_WD_SERNUM(R5)	; Save in UCB
	CLRL	UCB$L_DEVDEPND2(R5)	; Init flags for this drive
	MOVL	#MAX_BUSY_TIME, -	; Init value for default
		UCB_L_MAX_BUSY(R5)	;   BUSY timeout

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

;+
; All SCSI device 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 then used as the ID
; while the remainder is the LUN. Note that the unit number contains three
; digits because early versions of SCSI provided for sublogical 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)?
	BGTRU	99$			; Branch if not
	CMPL	R2,#7			; Valid LUN (0 <= n <= 7)?
	BGTRU	99$			; 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
;
; Connect with the Port Driver now that we're set up.  If a port driver isn't
; loaded, then set the device offline and exit.
;
	SPI$CONNECT			; Connect to the port driver
	BLBC	R0,99$			; Branch if connect attempt failed
	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	#<IOC$C_DISK_BLKSIZ-1>,R1 ; Make MAXBCNT an integral block count
	CMPL	R1,UCB$L_MAXBCNT(R5)	; For MAXBCNT, use minimum supported
	BGEQ	40$			; value of port and class drivers
	MOVL	R1,UCB$L_MAXBCNT(R5)	; Save maximum byte count in UCB

 40$:	MOVL	R2,UCB_L_SCDT(R5)	; Save SCDT address
	MOVL	R4,UCB$L_PDT(R5)	; Save SPDT address

;
; Allocate SPTE's for DataCheck
;
	TSTL	DATACHECK_SPTE		; Datacheck SPTEs already allocated?
	BNEQ	50$			; Branch if so
	$BYTES_TO_PAGES -		; Convert to max page count
		SOURCE_BYTCNT=UCB$L_MAXBCNT(R5),-
		DEST_PAGCNT=R2,-
		ROUNDUP=YES
	INCL	R2			; Account for non-page-aligned buffers
	JSB	G^LDR$ALLOC_PT		; Allocate SPTEs to double map user buf

	BLBS	R0,1112$		; Branch if connect attempt failed
	movl	#3, r0
1112$:
	BLBC	R0,99$			; 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	G^MMG$GL_PTE_OFFSET_TO_VA,R1,R1	; Calculate system virtual addr
	BISL3	#VA$M_SYSTEM,R1,-	; mapped by this set of SPTEs
		DATACHECK_SVA		;
;
; Set connection characteristics -- DISCONNECT and SYNCHRONOUS operation
; are assumed on all devices, so we don't need to know the device-specific
; characteristics.
;
 50$:	MOVL	R5, R3				; UCB address
	WD_ALLOC_SCDRP				; Get an SCDRP
	MOVL	UCB_PS_UNITINIT_KPB(R3),-	; Save KPB address in SCDRP
		SCDRP$PS_KPB(R5)		;
	BSBW	SET_CONN_CHAR			; Set up the connect characteristics
	WD_DEACTIVATE_SCDRP CLRSTK=YES		; Done with SCDRP
	MOVL	R3, R5				; Restore UCB

;
; Set Unit Online
;
	BISW	#UCB$M_ONLINE,-		; Set unit online
		UCB$L_STS(R5)		;
	BISL2	#DEV$M_CLU,-		; Set device shareable cluster-wide
		UCB$L_DEVCHAR2(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	60$			; Branch if not available
	JSB	(R2)			; Make this unit available to be served

 60$:	MOVL	#SS$_NORMAL, R0		; Ignore errors at this point
 99$:	RET				; Return to caller within KP services

	.SBTTL	WD_DIAGNOSE - FDT preprocessing for special pass-thru function
;++
; WD_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 nonpaged pool
;
; INPUTS:
;
;	R3	- IRP address
;	R4	- PCB address
;	R5	- UCB address
;	R6	- CCB address
;	P1,P2	- QIO function parameters P1 and P2 in the IRP
;
; OUTPUTS:
;
;	R0-R2	- Destroyed
;--

	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

	DIAG_SCSI	= 1		; "Normal" SCSI command
	DIAG_IDENTIFY	= 10		; Identify the device
	DIAG_RESET	= 11		; Reset the SCSI bus

WD_DIAGNOSE::
	$DRIVER_FDT_ENTRY

;;;	IFPRIV	DIAGNOSE,10$		; Branch if process has DIAGNOSE priv
;;;	MOVL	#SS$_NOPRIV,R0		; Set no privilege status
;;;	BRW	50$			; Branch to abort the I/O

;+
; First, check that we have read access to the user's descriptor.
;-
 10$:	MOVL	IRP$L_QIO_P1(R3),R0	; Get user descriptor address
	MOVL	IRP$L_QIO_P2(R3),R1	; Get user descriptor length
	MOVL	R0,R9			; Save a copy of descriptor address
	CMPL	R1,#DIAG_BUF_LEN	; Valid descriptor length
	BLSS	40$			; Branch if not
	CALL_WRITECHK			; Check buffer, lock in memory

	CMPL	DSC_OPCODE(R9),#DIAG_SCSI ; Valid opcode?
	BEQL	15$			; Good, branch
	CMPL	DSC_OPCODE(R9),#DIAG_IDENTIFY ; Valid opcode?
	BEQL	55$			; Good, branch
	CMPL	DSC_OPCODE(R9),#DIAG_RESET ; Valid opcode?
	BEQL	58$			; Good, branch
	BRW	40$			; Branch, invalid opcode

 15$:	CMPL	DSC_DATLEN(R9),-	; Reasonable read/write data buffer
		UCB$L_MAXBCNT(R5)	; length?
	BGTRU	40$			; Branch if not
	CMPL	DSC_PADCNT(R9),-	; Reasonable pad count?
		#<IOC$C_DISK_BLKSIZ-1>
	BGTRU	40$			; Branch if not

	MOVQ	DSC_CMDADR(R9),R0	; Get SCSI command buffer address, leng
	CMPL	R1,#MAX_CMD_LEN		; Valid command length?
	BGTRU	40$			; Branch if not
	CALL_WRITECHK			; Check buffer, lock in memory
	ADDL	#8,R1			; Reserve space for command buffer over
	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 registers
	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 registers
	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	60$,R2			; Get address of error routine

	BLBS	DSC_FLAGS(R9),20$	; Branch if this is a read operation
	CALL_WRITELOCK_ERR -		; Check for read access
		INTERFACE_WARNING=NO	;   Suppress warning message
	BRB	30$
20$:	CALL_READLOCK_ERR -		; Check for write access
		INTERFACE_WARNING=NO	;   Suppress warning message

30$:	MOVAL	IRP$C_CDRP(R3),R0	; Get address of SCDRP within IRP
	MOVL	DSC_FLAGS(R9),(R0)+	; Save flags field in IRP/SCDRP
	MOVAL	DSC_PADCNT(R9),R1	; Get address of pad count field
	.REPT	3
	MOVL	(R1)+,(R0)+		; Save pad count, timeout values
	.ENDR
	BRB	59$			; Queue the packet to the driver

 40$:	MOVL	#SS$_BADPARAM,R0	; Set bad parameter status
 50$:	CALL_ABORTIO DO_RET=YES		; Abort the I/O

;
; Do setup for diagnose DIAG_IDENTIFY commands:
; All this command wants use to do is call the INQUIRY routine to
; setup UCB$L_DEVDEPND2. To accomplish this all we do is move the
; move the opcode into modifier part of the IRP's function code.
;
 55$:	INSV	#DIAG_IDENTIFY,#IRP$V_FMOD,- ; Modify I/O function
		#IRP$S_FMOD,-		; .. modifier bits to
		IRP$L_FUNC(R3)		; ... show type of diagnose
	BRB	59$			; Queue the packet to the driver

;
; Execute a SCSI RESET operation.
;
 58$:	INSV	#DIAG_RESET,#IRP$V_FMOD,- ; Modify I/O function
		#IRP$S_FMOD,-		; .. modifier bits to
		IRP$L_FUNC(R3)		; ... show type of diagnose
 59$:	CALL_QIODRVPKT DO_RET=YES	; Queue the packet

;
; We arrive here if the last FDT operation - checking access to and locking
; down the user's read/write buffer fails.  EXE$READLOCK_ERR or
; EXE$WRITELOCK_ERR JSB'S to us 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 to EXE$READLOCK_ERR /
; EXE$WRITELOCK_ERR.  R0 and R1 must be preserved.
;
60$:	$DRIVER_ERRRTN_ENTRY

	PUSHR	#^M<R0, R1, R2>		; Save some registers
	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
	POPR	#^M<R0, R1, R2>		; Restore registers
	RET				; Return from co-routine call

	.SBTTL	WD_STARTIO	- Driver STARTIO entry point
;++
; WD_STARTIO
;
; This routine is the STARTIO entry point into the driver. Its main function
; is to dispatch to the function-code-specific routine that starts a specific
; I/O function.  The routine runs as a kernel process, which terminates through
; a call to the KP_REQCOM macro.
;
; INPUTS:
;
;	4(AP)	- KPB 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
;	R2-R8	- Preserved
;-

WD_STARTIO::

	$DRIVER_START_ENTRY PRESERVE=<R2,R3,R4,R5,R6,R7,R8>, FETCH=YES
	MOVL	4(AP),R0		; Get KPB address
	MOVL	KPB$PS_UCB(R0),R5	; Get UCB address
	MOVL	KPB$PS_IRP(R0),R3	; Get IRP address

	PUT_N2	#20,<"WDD-STARTIO: IRP stuff, media: %x segvbn: %x bcnt: %x ">,-
		irp$l_media(r3),irp$l_segvbn(r3),irp$l_bcnt(r3)
	PUT_N2	#20,<"WDD-STARTIO: IRP stuff, boff: %x svapte: %x func: %x ">,-
		irp$l_boff(r3),irp$l_svapte(r3),irp$l_func(r3)




	MOVL	R3,WD_IRP			;** debug **

	BBS	#UCB$V_ONLINE,UCB$L_STS(R5),10$	; Return error if offline
	CLRL	R1				;   and zap byte count
	MOVL	#SS$_DEVOFFLINE,R0		;    show error
	BRB	COMPLETE_IO2			;     and go return

 10$:	MOVL	UCB$L_ERTMAX(R5), -		; Initialize error retry count
		UCB$L_ERTCNT(R5)

WD_RETRY_START_1:

	BICL	#UCB_M_WD_DCHECK,-		; Clear datacheck flag
		UCB$L_DEVDEPND2(R5)
	CLRL	UCB_L_WD_CMDBUFS(R5)		; 0 # command buffers allocated
	MOVL	UCB$L_BCNT(R5),UCB_L_WD_BCNT(R5); Save for datacheck write
	MOVL	IRP$L_MEDIA(R3),UCB_L_MEDIA(R5) ; Copy media address
	MOVL	IRP$L_SVAPTE(R3),UCB$L_SVAPTE(R5) ; Copy SVAPTE
	MOVL	UCB_L_MEDIA(R5),-		; Save for datacheck write
		UCB_L_WD_MEDIA(R5)
	MOVL	UCB$L_PDT(R5),R4		; Get SPDT address
	MOVL	R3,R2				; Copy IRP address
	MOVL	R5,R3				; Copy UCB address
	WD_ALLOC_SCDRP 				; Allocate an SCDRP
	MOVL	R2,SCDRP$L_IRP(R5)		; Save IRP address in SCDRP
	MOVL	IRP$PS_KPB(R2),- 		; Save KPB address in SCDRP
		SCDRP$PS_KPB(R5)
	MOVL	R5,WD_SCDRP			; *** DEBUG ***
	MOVL	R2,UCB$L_IRP(R3)		; INITIATE isn't filling this in
	MOVL	IRP$L_FUNC(R2),R1		; Get I/O function code
	MOVL	R1,UCB$L_FUNC(R3)		; Save for error logging
	EXTZV	#IO$V_FCODE,#IO$S_FCODE,R1,R1	; Get rid of modifier bits
	ASSUME	IRP$S_FCODE LE 7		; Allow byte mode dispatch

	CMPL	#12, R1				; READLBLK?
	BNEQ	1900$
1900$:

	DISPATCH R1,TYPE=B,<-			; Dispatch according 2 function
		<IO$_DIAGNOSE,	IO_DIAGNOSE>,-
		<IO$_PACKACK,	IO_PACKACK>,-
		<IO$_AVAILABLE,	IO_AVAILABLE>,-
		<IO$_UNLOAD,	IO_UNLOAD>,-
		<IO$_READPBLK,	IO_READ>,-
		<IO$_READLBLK,	IO_READ>,-
		<IO$_READVBLK,	IO_READ>,-
		<IO$_WRITEPBLK,	IO_READ_WRITE>,-
		<IO$_WRITELBLK,	IO_READ_WRITE>,-
		<IO$_LOAD_ELEMENT, LOAD_MAP_UNLOAD>,-
		<IO$_UNLOAD_ELEMENT, LOAD_MAP_UNLOAD>,-
		<IO$_WRITEVBLK,	IO_READ_WRITE> -
		>

;+
; Bogus I/O function code will fall through. Set illegal function code
; status and complete the I/O.
;-
IO_BOGUS:
	MOVZBL	#SS$_ILLIOFUNC,R0	; Specify the error type

COMPLETE_IO:
	CMPW	#SS$_RETRY,R0		; Last error need retry
	BEQL	WD_RETRY		; Yes, give it a chance

FINISH_IO:
	PUT_N2	#20,-
	<"WDD-FINISH_IO: PAD_BCNT: %d, TRANS_CNT: %d, ABCNT: %d ">,-
	SCDRP$L_PAD_BCNT(R5),SCDRP$L_TRANS_CNT(R5),SCDRP$L_ABCNT(R5)

	PUT_N2	#20,-
	<"WDD-FINISH_IO: Before WD_DEACTIVATE_SCDRP:  R0: %x, R1: %x ">,R0,R1

	WD_DEACTIVATE_SCDRP CLRSTK=YES	; Deactivate the SCDRP

	PUT_N2	#20,-
	<"WDD-FINISH_IO: After WD_DEACTIVATE_SCDRP: R0: %x, R1: %x ">, R0,R1

	MOVL	R3,R5			; Copy UCB address

COMPLETE_IO2:				; Label for pre-allocation ret
	KP_REQCOM			;     and go return

WD_RETRY:
	BBC	#UCB$V_CANCEL,-		; If the cancel bit is not set,
		UCB$L_STS(R3),20$	; just return.
	MOVZWL	#SS$_CANCEL, R0		; Set status
	BRB	FINISH_IO		; And exit

 20$:	DECL	UCB$L_ERTCNT(R3)	; Any retries left?
	BLEQ	FINISH_IO		;  if eql no so go
	PUT_N2	#30,<"WDD-WD_RETRY: retry count: %d ">,UCB$L_ERTCNT(R3)
	WD_DEACTIVATE_SCDRP CLRSTK=YES	; Deactivate the SCDRP
	MOVL	R3,R5			; restore original ucb register
	MOVL	UCB$L_IRP(R5),R3	; get irp address back
	BRW	WD_RETRY_START_1	;   else go retry the request

	.SBTTL	IO_DIAGNOSE	- Special pass-through function
;++
; IO_DIAGNOSE
;
; STARTIO routine for the passthru function of the template SCSI
; class driver. This routine assumes that the user has provided
; a buffer that contains the SCSI command packet and that the
; FDT routines in the driver have made the appropriate checks
; during I/O preprocessing to allow access to the user data areas
; during STARTIO.
;
; IO_DIAGNOSE makes calls into the port driver to allocate command
; buffer areas, maps the user buffer such that the port driver
; can access user areas, and then calls the port driver's SEND_CMD
; entry point to send the SCSI command to a target. When the port driver
; returns from this call, the user's data has been moved, the
; command status is in the status-in buffer and the SCSI bus
; is free. The class driver releases its resources and
; completes the I/O with a call to REQCOM.
;
; INPUTS:
;
;	R2	- IRP address
;	R3	- UCB address
;	R4	- SPDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0,R1	- Status
;	R2	- Destroyed
;	All other registers preserved
;--

	.ENABLE LSB			; IO_DIAGNOSE
IO_DIAGNOSE:

	PUT_N2	#20,<"WDD-IO_DIAGNOSE: ">
;
; Make checks for special diagnose functions.
;
	MOVL	SCDRP$L_IRP(R5),R2	; Restore IRP address
	EXTZV	#IRP$V_FMOD,#IRP$S_FMOD,- ; Get modifiers if any.
		IRP$L_FUNC(R2),R0	;
	BEQL	30$			; Branch if none
	CMPL	R0,#DIAG_RESET		; Is it RESET?
	BNEQ	5$			; No, branch
	MOVL	UCB$L_PDT(R3),R4	; Get SPDT address
	MOVL	SPDT$L_PORT_UCB(R4), R2	; Get port's UCB address
	SPI$RESET_SCSI_BUS		; Reset the SCSI bus
	BRW	99$			; Complete I/O

 5$:	CMPL	R0,#DIAG_IDENTIFY	; Is it IDENTIFY?
	BNEQ	10$			; No, branch
;
; Do the Inquiry routine to load UCB$L_DEVDEPND2
;
	BSBW	INQUIRY			; Do work
	BSBW	INQUIRY			; Do work again in case of SS$_MEDOFL
	MOVL	UCB$L_DEVDEPND2(R3), R1	; Return DEVDEPND2 in I/O status
	BRW	99$			; Complete I/O

 10$:	MOVZBL	#SS$_ILLIOFUNC,R0	; Specify the error type
 20$:	BRW	99$			; Complete I/O
;
; Start of normal Diagnose processing.
;
 30$:	MOVL	IRP$L_MEDIA(R2),-	; Copy command buffer from IRP to
		SCDRP$L_MEDIA(R5)	; SCDRP
	MOVL	IRP$L_SVAPTE(R2),-	; and SVAPTE,
		SCDRP$L_SVAPTE(R5)	;
	MOVL	IRP$L_BCNT(R2),-	; BCNT,
		SCDRP$L_BCNT(R5)	;
	MOVL	IRP$L_BOFF(R2),-	; and BOFF
		SCDRP$L_BOFF(R5)	;
	MOVL	IRP$L_STS(R2),-		; and STS
		SCDRP$IS_STS(R5)	;
	MOVAL	IRP$C_CDRP(R2),R0	; Get address of SCDRP portion of IRP
	EXTZV	#1,#1,(R0),R1		; Get disconnect flag
	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

	MOVL	SCDRP$L_MEDIA(R5),R1	; Get address of SCSI command in pool

	PUT_N2	#20,<"WDD-IO_DIAGNOSE: SCSI_CMD: %x ">,  (r1)
	PUT_N2	#20,<"WDD-IO_DIAGNOSE:           %x ">, 4(r1)
	PUT_N2	#20,<"WDD-IO_DIAGNOSE:           %x ">, 8(r1)
	PUT_N2	#20,<"WDD-IO_DIAGNOSE:           %x ">,12(r1)

	MOVZBL	(R1),R1			; Get length of SCSI command
	ADDL	#8,R1			; Account for overhead

	PUT_N2	#20,<"WDD-IO_DIAGNOSE: SCSI CMD LENGTH (R1): %x ">,r1
	PUT_N2	#20,<"WDD-IO_DIAGNOSE: R2: %x, R4: %x, R5: %x ">,r2,r4,r5

	PUT_N2	#20,<"WDD-IO_DIAGNOSE: Calling SPI$CMD_BUFFER_ALLOC ">
	SPI$CMD_BUFFER_ALLOC		; Allocate a command buffer-V6.2-later
	PUT_N2	#20,<"WDD-IO_DIAGNOSE: Returning from SPI$CMD_BUFFER_ALLOC ">

	PUT_N2	#20,<"WDD-IO_DIAGNOSE: CMD BUF addr: %x ">,R2
;
;
; If no buffer address, get out with error.
;
	MOVL	#SS$_ABORT,R0
	TSTL	R2
	BEQL	99$

	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
	SUBL3	#1,R2,SCDRP$L_STS_PTR(R5)  ; Address to save status byte
	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 registers
	MOVC3	(R0),4(R0),(R2)		; Copy SCSI command to command buffer
	POPR	#^M<R0,R2,R3,R4,R5>	; Restore registers
	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	40$			; Branch if not
	SPI$BUFFER_MAP			; Map the user's data buffer
 40$:	SPI$SEND_COMMAND		; Send the SCSI command
	PUSHL	R0			; Save returned port status
	TSTL	SCDRP$L_BCNT(R5)	; User buffer mapped?
	BEQL	50$			; Branch if not
	SPI$BUFFER_UNMAP		; Unmap the user's data buffer
 50$:	MOVL	SCDRP$L_CMD_BUF(R5),R0	; Get the command buffer address
	PUSHL	(R0)			; Save the SCSI status byte
	SPI$CMD_BUFFER_DEALLOC		; Deallocate the command buffer
	POPL	R1			; Restore the SCSI status byte
	POPL	R0			; Restore the port status
	INSV	SCDRP$L_TRANS_CNT(R5),- ; Copy the transfer count to the
		#16,#16,R0		; high-order word of R0

	CMPB	#OPTIC$C_LMS_LF4500, -	; Is this a 4500?
		UCB$B_FEX(R3)		;
	BNEQ	99$			; Nope
	INSV	UCB_L_SLOT(R3), -	; Store the last slot number used in
		#16, #8, R1		;  a LOAD operation (cartridge in drive)

 99$:	BRW	COMPLETE_IO		; Complete the QIO

	.DISABLE LSB			; IO_DIAGNOSE

	.SBTTL	IO_READ - Perform Read Functions
	.SBTTL	IO_READ_WRITE - Perform Read and Write Functions
;
; IO_READ - Perform Read Functions.
; IO_READ_WRITE - Perform Read and Write Functions.
;
; Read/write blocks from/to the disk, breaking the original I/O request into
; as many SCSI requests as needed to perform the required splicing.  Splicing
; is required when a request is less than a sector size in length or does not
; start on a sector boundary.
;
; Support for datacheck writes is also included.
;
; Inputs:
;
;	r3 - Address of the UCB
;	r4 - Address of the SPDT
;	r5 - Address of the SCDRP
;
; Outputs:
;
;	r0 - VMS status code
;	r1 - Number of bytes transferred
;	r2 - destroyed
;
IO_READ:					; Reading
	MOVL	UCB$L_IRP(R3), R2		; Get irp address
	BISL	#IRP$M_FUNC,-			; Set the FUNC bit to indicate
		SCDRP$IS_STS(R5)		; this is a read function
	BBSS	#IRP$V_FUNC, IRP$L_STS(R2), -	; Set function bit for reads
		IO_READ_WRITE_1			;  continue

IO_READ_WRITE:					; Writing
	CLRL	R1				; Init bytes transferred
	MOVL	UCB$L_IRP(R3), R2		; Get irp address
	CMPL	UCB_L_WD_SECTBLK(R3), #1	; One sector per block?
	BEQL	IO_READ_WRITE_1			; Yup -- go ahead
	MOVL	#SS$_FORMAT, R0			; Wrong format for writing
	BRW	COMPLETE_IO			; Exit

IO_READ_WRITE_1:
	MOVL	IRP$L_BCNT(R2), UCB$L_BCNT(R3)	; Make sure this is inited
IO_READ_WRITE_NEXT:
	CLRL	SCDRP$L_ABCNT(R5)	; Initialize accumulated byte count
	MOVL	IRP$L_FUNC(R2),-	; Copy function code and modifiers,
		SCDRP$L_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)	;
	MOVL	IRP$L_BOFF(R2),-	;
		SCDRP$L_BOFF(R5)	;
	MOVL	IRP$L_BCNT(R2),-	;
		SCDRP$L_BCNT(R5)	;

IO_READ_MORE:
	TSTL	UCB$L_BCNT(R3)			; Any bytes to transfer?
	BEQL	100$				; No, exit
	BSBW	CHECK_BOUNDARY			; Check for starting point
	TSTL	R0				; Are we on a boundary?
	BEQL	90$				; Yes, branch
	BBS	#IRP$V_FUNC, IRP$L_STS(R2), 60$	; OK if reading
	MOVL	#SS$_FORMAT, R0			; Wrong format for writing
	BRW	200$				; Exit
;
; Non-boundary aligned I/O
;
 60$:	MOVL	UCB_L_WD_SECTBYT(R3),R1 	; Number of bytes to allocate
	MULL2	#2,R1				; * for non-sector aligned
	BSBW	ALLOC_SYS_BUF			; Go allocate a system buffer
	BLBC	R0, 100$			; exit with status
	MOVL	UCB_L_WD_SECTBYT(R3),-
		SCDRP$L_BCNT(R5)		; Get # of bytes to transfer
	BBS	#IRP$V_FUNC,IRP$L_STS(R2),30$	; Go for reads
;
; Here for writes.
;
	BSBW	COPY_DATA			; Copy data from the user
	BSBW	READ_WRITE			; Go write the data
	BSBW	CLEANUP_CMD			; Cleanup SCDRP & fields
	BLBC	R0,100$				;  on failure, exit

	BSBW	UPDATE_USER_BUFFER		; Update the user's buffer
	BRW	IO_READ_MORE			;  go complete the i/o
;
; Here for reads.
;
 30$:	BSBW	READ_WRITE			; Go read a sector
	BLBS	R0,40$				;  go if OK
	BSBW	CLEANUP_CMD			; Cleanup SCDRP & fields
	BRW	100$				;  & exit on failure

 40$:	BSBW	COPY_DATA			; Copy data to the user
	BSBW	CLEANUP_CMD			; Cleanup SCDRP & fields
	BRW	IO_READ_MORE			;  go complete the i/o
;
; Transfer is at least a sector long from a sector boundary.
;
 90$:	BSBW	USER_READ_WRITE			; Go do the transfer
	BSBW	CLEANUP_CMD			; Cleanup SCDRP & fields
	BLBC	R0,200$				;  go if failure
	BRW	IO_READ_MORE			;  go complete the i/o
;
; Common exit point.  Check to see if a datacheck write was requested.
; If so, try to read data into the driver buffer, one sector at-a-time.  Only
; try to read sectors that were supposedly written successfully.
;
 100$:	MOVL	UCB$L_IRP(R3),R2		; get irp address
	SUBL3	UCB$L_BCNT(R3),IRP$L_BCNT(R2),R1; Calculate bytes transferred
	BICL	#UCB_M_WD_DCHECK,-		; Assume no datacheck
		UCB$L_DEVDEPND2(R3)
	BITL	#IO$M_DATACHECK,IRP$L_FUNC(R2)	; Datacheck specified?
	BEQL	110$				;   else exit
	MOVL	UCB$L_PDT(R3), R2		; Get SPDT address
	BBS	#SPDT$V_PFLG_DIR_DMA, -		; If the port driver supports
		SPDT$L_PORT_FLAGS(R2), 110$	;   direct DMA, skip datacheck

	.IF DF	WD_DATACHECK
	BRW	IO_DATACHECK			; Branch to do datacheck
	.ENDC	; DF WD_DATACHECK

	ASSUME	MAX_BCNT LT 65536		; Assume it fits in a word

110$:	INSV	R1,-				; Load low-order # of bytes
		#16,#16,R0			; transferred into R0
	CLRL	R1				; Load high-order # of bytes
						; transferred into R1
	BICL	#UCB_M_WD_DCHECK,-		; Clear datacheck bit
		UCB$L_DEVDEPND2(R3)
200$:	BRW	COMPLETE_IO			;  and go finish off the i/o

	.IF DEFINED DATACHECK_CODE
	.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::
	BISL	#UCB_M_WD_DCHECK,-	; Set the datacheck bit
		UCB$L_DEVDEPND2(R3)	; ... so READ_WRITE works
	BISW	#IRP$M_FUNC,-		; Set the FUNC but to indicate this
		SCDRP$IS_STS(R5)		; is a read function

	MOVL	UCB$L_IRP(R3),R2	; Get IRP address
	CLRL	SCDRP$L_ABCNT(R5)	; Initialize accumulated byte count
	MOVL	IRP$L_FUNC(R2),-	; Copy function code and modifiers,
		SCDRP$L_FUNC(R5)	; MEDIA, SVAPTE, and BOFF fields
					; from the IRP to the SCDRP
;
;  We need the *original* UCB_L_MEDIA value so we can read the blocks
;  just written. Since READ_WRITE uses UCB_L_MEDIA, we have to juggle
;  the values a little bit.
;
	MOVL	UCB_L_WD_MEDIA(R3),-	; Copy original MEDIA value to SCDRP
		SCDRP$L_MEDIA(R5)	; ...
	MOVL	UCB_L_MEDIA(R3),-	; Save the current media address
		UCB_L_WD_MEDIA(R3)	; ...
	MOVL	SCDRP$L_MEDIA(R5),-	; Copy the original media address to
		UCB_L_MEDIA(R3)		; ...  UCB_L_MEDIA

	MOVL	IRP$L_SVAPTE(R2),-	;
		SCDRP$L_SVAPTE(R5)	;
	MOVL	IRP$L_BOFF(R2),-	;
		SCDRP$L_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
	BISB	#<SCDRP$M_FLAG_S0BUF ! - ; Indicate that this is an S0 "user"
		 SCDRP$M_FLAG_BUFFER_MAPPED>,- ; This buffer has been mapped
		SCDRP$L_SCSI_FLAGS(R5)	; buffer
;
; To support V5.3 systems we added this macro to our V5.3 development
; systems SYS$LIBRARY:LIB.MLB file from a V5.4 system.
;
	SPI$BUFFER_MAP	PRIO=HIGH	; Map the user buffer

IO_DC_LOOP::

	MOVL	UCB$L_IRP(R3),R2	; Restore IRP address
	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
	TSTW	UCB_L_WD_CMDBUFS(R3)	; Do we have command buffers allocated?
	BEQL	30$			;   branch if not
	DECW	UCB_L_WD_CMDBUFS(R3)	; Decrement # of command buffers left
	PUSHR	#^M<R0,R1,R2,R3>	; Save regs
	MOVL	SCDRP$L_CMD_BUF(R5),R0	; Get address of command buffer
	SPI$CMD_BUFFER_DEALLOC		; Deallocate the command buffer
	POPR	#^M<R0,R1,R2,R3>	; Restore regs
 30$:	BLBS	R0,IO_DC_ACCUM		; Branch on success
	BRW	IO_DC_EXIT		; Branch if not, return with error stat

IO_DC_ACCUM:

	MOVL	UCB$L_IRP(R3),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
	PUT_N2	#20,<"WDD-IO_DC_ACCUM: SCDRP$L_ABCNT: %d, IRP$L_BCNT: %d ">,-
		SCDRP$L_ABCNT(R5),IRP$L_BCNT(R2)
	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

;
; Deallocate the datacheck buffer.  Note that the command buffer allocated
; in READ_WRITE (via a call to SETUP_CMD which calls
; SPI$ALLOCATE_COMMAND_BUFFER) has already been deallocated above.
;
IO_DC_EXIT:

	PUSHL	R0			; Save R0
	BBCC	#SCDRP$V_FLAG_BUFFER_MAPPED,-; Branch if no buffer has been mapped
		SCDRP$L_SCSI_FLAGS(R5),-;
		10$
	SPI$BUFFER_UNMAP		; Unmap the mapped buffer
10$:	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 transf

 20$:	MOVL	#SS$_OPINCOMPL,R0	; Set bad status
	BRB	IO_DC_EXIT		; Complete I/O 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 allocated
; when the driver was loaded and are shared between all units.
;
; INPUTS:
;
;	R3	- UCB address
;	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

;
; *** Debugging -- req'd if driver is RELOADED
;
	TSTL	DATACHECK_SPTE		; Datacheck SPTEs already allocated?
	BNEQ	50$			; Branch if so
	$BYTES_TO_PAGES -		; Convert to max page count
		SOURCE_BYTCNT=UCB$L_MAXBCNT(R5),-
		DEST_PAGCNT=R2,-
		ROUNDUP=YES
	INCL	R2			; Account for non-page-aligned buffers
	JSB	G^LDR$ALLOC_PT		; Allocate SPTEs to double map user buf
	BLBC	R0,30$			; 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		;
50$:
; *** Debugging

	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
	MOVL	SCDRP$L_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 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

	MOVL	SCDRP$L_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
 29$:	MOVL	#SS$_NORMAL,R0		; Set success status
 30$:	RSB				; Return to caller

	.ENDC		; .IF DEFINED DATACHECK_CODE

	.SBTTL	USER_READ_WRITE - Transfer to/from user buffer
;
; USER_READ_WRITE - Transfer data to/from user buffer.
;
; Transfer sectors between the disk and the user's buffer. The mapping registers
; required for the transfer are assumed to have been allocated before this
; routine is called.
;
;
; Inputs:
;
;	r3 - Address of the UCB
;	r5 - Address of the SCDRP
;
; Outputs:
;
;	r0 - VMS status code
;	r1 - Number of bytes read
;
USER_READ_WRITE::
	.JSB_ENTRY INPUT=<R3,R5>,OUTPUT=<R0,R1,R3,R5>,SCRATCH=<R2>

	MOVZBL	#SS$_NORMAL, R0			; Assume success

IO_RW_LOOP:
	MOVL	UCB$L_IRP(R3), R2	; Get IRP address
	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
	PUT_N2	#20, <"WDD-URW: Segmenting I/O... ">
	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
	BSBW	CLEANUP_CMD		; Clean up
	SUBL3	SCDRP$L_PAD_BCNT(R5),-	; Get actual number of bytes transferred
		SCDRP$L_TRANS_CNT(R5),R1; less any possible padding
	ADDL	R1,SCDRP$L_ABCNT(R5)	; Accumulate this piece of the transfer
	BLBS	R0, IO_RW_ACCUM		; Continue if transfer OK
	PUT_N2	#20, <"WDD-URW: PAD_BCNT = %D, TRANS_CNT = %D, ABCNT = %D ">,-
		SCDRP$L_PAD_BCNT(R5),SCDRP$L_TRANS_CNT(R5),SCDRP$L_ABCNT(R5)
	BRB	IO_RW_EXIT		; Exit on error

IO_RW_ACCUM:

	CMPL	R1,SCDRP$L_BCNT(R5)	; Compare with requested transfer count
	BNEQ	IO_RW_MISMATCH		; Branch if mismatch occurred
	SUBL	R1,UCB$L_BCNT(R3)	; Fix byte count for 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
	MOVL	#SS$_NORMAL,R0		; Set success status
	BRB	IO_RW_EXIT

IO_RW_MISMATCH:
	MOVL	#SS$_OPINCOMPL, R0	; Set bad 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
	RSB				; All done

;
; Here we have completed one piece of a segmented transfer. Update the MEDIA,
; SVAPTE and BOFF fields in the SCDRP and go perform the next segment of
; the transfer.
;
IO_RW_SEGMENT_DONE:
	ASHL	#-IOC$S_DISK_BLKSIZ,-   ; Convert byte count to block count
		R0,R1
	ADDL2	R1,SCDRP$L_MEDIA(R5)	; Update logical block number in SCDRP
	MOVL	SCDRP$L_BOFF(R5),R1	; Load byte offset into register
	ADDL2	R1,R0			; Byte offset plus byte count
	MOVL	G^MMG$GL_VPN_TO_VA,R1	; Get right shift factor
	EVAX_SRL R0,R1,R1		; Calculate number of VPN's to advance
	EVAX_SLL R1,#PTE$C_SHIFT_SIZE,R1; Get number of PTE's to advance
	ADDL2	R1,SCDRP$L_SVAPTE(R5)	; Update SVAPTE field in SCDRP
	MOVL	G^MMG$GL_BWP_MASK,R1	; Byte within page mask of virtual addr
	EVAX_AND R0,R1,R1		; Get new byte offset within page
	MOVL	R1,SCDRP$L_BOFF(R5)	; Save byte offset in SCDRP
	BRW	IO_RW_LOOP		; Go perform next segment of transfer

	.SBTTL	COPY_DATA - Copy data to/from user buffer
;
; COPY_DATA - Copy data to/from the user's buffer.
;
; Copy data between the driver's buffer and the user's buffer. This process is
; described by the following psuedo-code:
;
;	if on sector boundary
;	 then
;	   transfer UCB$L_BCNT bytes
;	 else if UCB$L_BCNT lt (buffer_length - offset) then
;	   transfer UCB$L_BCNT bytes
;	 else
;	   transfer (buffer_length - offset) bytes
;	endif
;	if computed transfer size gt buffer_length then
;	   transfer buffer_length bytes
;
;    where, offset is determined from relative LBN within sector
;
; Inputs:
;
;	r3 - Address of the UCB
;	r5 - Address of the SCDRP
;
; Outputs:
;
;	UCB$L_BCNT, UCB_L_MEDIA, UCB$L_SVAPTE are modified to reflect the
;	  amount of data transferred.
;	All registers preserved across the call.
;
COPY_DATA::
	.JSB_ENTRY INPUT=<R3,R5>, OUTPUT=<R0,R1>, PRESERVE=<R2,R3,R4,R5,R6,R7>

	CMPL	UCB_L_WD_SECTBYT(R3), #1024	; 1024-byte sectors?
	BNEQ	10$				; Better be...
	CMPL	SCDRP$L_BCNT(R5), #1024		; Reading one sector?
	BEQL	30$				; Better be...
10$:	BUG_CHECK INCONSTATE,FATAL		; bugcheck

30$:	BSBW	CHECK_BOUNDARY			; Get relative block number
	MOVL	SCDRP$L_SVA_USER(R5),R1		; Point to driver buffer
	TSTL	R0				; On boundary?
	BEQL	40$				;  if eql yes so go

	ASHL	#IOC$S_DISK_BLKSIZ, R0, R0	; Else, convert offset to bytes
	ADDL	R0,R1				;  correct pointer
40$:	SUBL3	R0,UCB_L_WD_SECTBYT(R3),R2	;   and calc (buf_len - offset)
	CMPL	R2,UCB$L_BCNT(R3)		; Does request exceed buffer?
	BLSSU	50$				;  if lssu, yes so go
	MOVL	UCB$L_BCNT(R3),R2		;   else xfer ucb$L_BCNT bytes

50$:	MOVL	UCB$L_IRP(R3),R0		; get irp address
	BBS	#IRP$V_FUNC,IRP$L_STS(R0),60$	; Go for reads
	MOVL	#SS$_FORMAT, R0			; Can't do writes yet
;
; Here for writes (from user's buffer).
;
	BRB	110$				;  and return
;
; Here for reads (to user's buffer).  R2 = byte count
;
60$:	MOVL	R2, R6			; Save byte count
	MOVL	R1, R7			; Save system buffer starting location

	$BYTES_TO_PAGES -		; Convert to page count
		SOURCE_BYTCNT=R2,-
		DEST_PAGCNT=R4,-
		ROUNDUP=YES		; Round up

	PUT_N2	#22, <"COPY_DATA: Copying %d bytes (%d pages) from LBN %d ">,-
		R6, R4, SCDRP$L_MEDIA(R5)

	ASSUME	DATACHECK_SPTE+4 EQ DATACHECK_SVA
	MOVQ	DATACHECK_SPTE,R0	; Get SVA of datacheck SPTEs and SVA
					; which these SPTEs map
	MOVL	UCB$L_SVAPTE(R3),R2	; SVAPTE of user buffer
70$:	EVAX_LDQ R3,(R2)		; Get user's PTE
	ADDL	#PTE$C_BYTES_PER_PTE,R2	; Advance to next PTE
	ASSUME	PTE$M_VALID EQ 1
	BLBC	R3,80$			; Branch if PTE not valid
	EVAX_SRL R3,#PTE$V_PFN,R3	; Else, shift PFN to low-order bits
	BRB	90$			; Join common new PTE builder code
80$:	CALL_PTETOPFN	SAVE_R0R1=NO	; If PTE not valid, convert to PFN
90$:	EVAX_SLL R3,#PTE$V_PFN,R3	; Get PFN in the right place
	EVAX_OR  R3,-			; Set software PTE bits
		#<PTE$M_VALID!PTE$C_KW!PTE$C_KOWN!PTE$M_ASM!PTE$M_WINDOW>,R3
	EVAX_STQ R3,(R0)		; Store the new PTE
	TBI_SINGLE R1			; Do TB invalidation

	ADDL	G^MMG$GL_PAGE_SIZE,R1	; Point to next page
	ADDL	#PTE$C_BYTES_PER_PTE,R0	; Point to next entry in SPT
	SOBGTR	R4,70$			; Loop till done

	MOVL	SCDRP$L_UCB(R5), R3	; Restore UCB address
	MOVL	UCB$L_IRP(R3),R0	; get irp address
	MOVL	IRP$L_BOFF(R0),R0	; Get user buffer byte offset
	ADDL	DATACHECK_SVA,R0	; Address of double mapped user buffer
	PUSHR	#^M<R3, R5>		; Save context destroyed by MOVC

	PUT_N2	#22,<"COPY_DATA: Copying %d bytes from %x to %x, data: %x ">,-
		R6, R7, R0, (R7)

	MOVC3	R6, (R7), (R0)		; Copy data from system buffer into
					; user's buffer
	POPR	#^M<R3, R5>		; Restore context
	SUBL3	SCDRP$L_PAD_BCNT(R5),-	; Get actual number of bytes transferred
		SCDRP$L_TRANS_CNT(R5),R6; less any possible padding
	ADDL	R6, SCDRP$L_ABCNT(R5)	; Add to accumulated byte count
	SUBL	R6, UCB$L_BCNT(R3)	;  adjust for bytes copied
	BGEQ	100$			; Make sure we don't go negative
	CLRL	UCB$L_BCNT(R3)
100$:	ADDL	#1, UCB_L_MEDIA(R3)	;  adjust for blocks copied
	MOVL	#SS$_NORMAL,R0		; Set success status
110$:	RSB					;  and return

	.sbttl	UPDATE_USER_BUFFER  - Update the user buffer
;
; UPDATE_USER_BUFFER - Update the user buffer.
;
; Update the user's buffer following a write from the user's buffer to the
; driver's buffer to the disk.	This module uses the same logic as described
; in the copy_data module to determine the number of bytes transferred
; to the disk.	This module will only be called after a sector has been
; successfully written from the driver's buffer to the disk.
;
; Inputs:
;
;	R1 - Number of bytes transferred
;	R3 - Address of the IRP
;	R5 - Address of the UCB
;
; Outputs:
;
;	Ucb$L_BCNT, UCB_L_MEDIA, Ucb$l_Svapte are modified to reflect the
;	  amount of data transferred.
;	All registers preserved across the call.
;
UPDATE_USER_BUFFER::
	.JSB_ENTRY INPUT=<R3,R5>, PRESERVE=<R1,R3,R5>

	CMPL	R1,UCB$L_BCNT(R3)		; Does request exceed buffer?
	BLSSU	10$				;  if lssu, yes so go
	MOVL	UCB$L_BCNT(R3),R1		;   else xfer ucb$L_BCNT bytes

 10$:	SUBL	R1,UCB$L_BCNT(R3)		; Adjust for bytes copied
	ASHL	#<-9>,R1,R1			; Convert bytes to blocks
	ADDL	R1,UCB_L_MEDIA(R3)		;  adjust for blocks copied
	BITL	#UCB_M_WD_PAGEBN,-		; Did xfer end on page boundary?
		UCB$L_DEVDEPND2(R3)
	BEQL	20$				;  if eql no so go
	ADDL	#<4>,UCB$L_SVAPTE(R3)		;   else fix up SVAPTE

 20$:	RSB					;  and return

	.SBTTL	CHECK_BOUNDARY - Check for transfer on sector boundary
;
; CHECK_BOUNDARY - Check for transfer on sector boundary
;
; This routine returns the relative block number of a transfer in relation
; to a sector boundary.  If the transfer starts on a sector boundary, the
; returned value is ZERO.
;
; Inputs:
;
;	r3 - Address of the UCB
;
; Outputs:
;
;	r0 - block number relative to sector boundary
;	     (note: zero if on sector boundary)
;
;	All other registers preserved across the call.
;
CHECK_BOUNDARY::
	.JSB_ENTRY INPUT=<R3>,OUTPUT=<R0>,PRESERVE=<R1>

	MOVL	UCB_L_WD_SECTBLK(R3),R1		; Get sector size in blocks
	DIVL3	R1,UCB_L_MEDIA(R3),R0		; Do integer divide
	MULL2	R1,R0				; Calculate
	SUBL3	R0,UCB_L_MEDIA(R3),R0		;  remainder
	RSB					;  and return

	.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:
;
;	R3	- UCB address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1	- Number of bytes transferred
;	R2	- Destroyed
;	All other registers preserved
;-
READ_WRITE::
	.JSB_ENTRY INPUT=<R3,R5>,OUTPUT=<R0,R1>,PRESERVE=<R2>

	MOVAL	READ_CMD,R2			; Get address of SCSI read cmd
	MOVL	UCB$L_IRP(R3),R1		; Get address of IRP
	MOVL	UCB$L_PDT(R3),R4		; Get SPDT address
	BBS	#IRP$V_FUNC,-
		IRP$L_STS(R1),20$		; Branch if read function
	BBS	#UCB_V_WD_DCHECK,-		; If datacheck, go do read
		UCB$L_DEVDEPND2(R3),20$
	MOVAL	WRITE_CMD,R2			; Assume this is write command
	BBC	#OPTIC$V_WRITE_VERIFY,-		; Branch unless WRITE VERIFY
		UCB$L_DEVDEPND2(R3),10$		; ... is needed
	MOVAL	WRITE_VERIFY_CMD,R2		; Make it WRITE VERIFY
 10$:	BBC	#UCB_V_WD_WRTLCK,-		;   else check for write-lock
		UCB$L_DEVDEPND2(R3),20$		;    if bc, write enabled so go
	CLRL	R1				;     else zap byte count
	MOVL	#SS$_WRITLCK,R0			;      show write-locked
	BRW	80$				;	and go return
 20$:	BBC	#OPTIC$V_RW,-			; If not a rewritable disk,
		UCB$L_DEVDEPND2(R3),40$		;  then let it go through
 30$:	CMPL	UCB_L_WD_SECTBYT(R3), -		; 512-byte sectors?
		#IOC$C_DISK_BLKSIZ		;
	BEQL	40$				; Yup -- go ahead
	MOVL	#SS$_UNSUPPORTED,R0		;    say we can't write to it
	BRW	80$				;     and return
 40$:	BSBW	SETUP_CMD			; Set up the command
	BLBC	R0,80$				; sigh...
;
; The following is a generalization of READ_WRITE to handle byte counts
; that are not an increment of the block size.
;
	CLRL	SCDRP$L_PAD_BCNT(R5)		; Assume No padding
	MOVL	SCDRP$L_BCNT(R5), R1		; Get bytes to transfer
	MOVL	UCB_L_WD_SECTBYT(R3), R2	; Bytes per sector
	DECL	R2				; Make a mask
	MCOML	R2,R2				;  ... and complement
	BICL3	R2, R1, R2			; Compute even # of sectors
	BEQL	50$				; If eql, no padding req'd
	SUBL3	R2, UCB_L_WD_SECTBYT(R3),-	; Set pad byte count
		SCDRP$L_PAD_BCNT(R5)		;
50$:	MOVL	UCB_L_WD_SECTBYT(R3),R1		; get # of bytes in a sector
	ADDL3	SCDRP$L_BCNT(R5), -		; get # of bytes to transfer
		SCDRP$L_PAD_BCNT(R5), R0;	;
	DIVL3	R1, R0, R1			; get number of sectors
	MOVL	R1,UCB_L_NO_SECTORS(R3)		; save sector count
	ADDL3	#<4+8>,-			; Addr of transfer length field
		SCDRP$L_CMD_PTR(R5),R2		; in SCSI command
	MOVB	R1,(R2)				; Put in LSB of transfer length
	ASHL	#-8,R1,R1			; Get next byte
	MOVB	R1,-(R2)			; Put in MSB of transfer length
	TSTB	-(R2)				; Skip reserved byte
	MOVL	UCB_L_WD_SECTBLK(R3),R1		; get # blocks in a sector
	DIVL3	R1,SCDRP$L_MEDIA(R5),R1		; compute sector address

	PUT_N2	#22,-
	<"WDD-RW: Reading %d blocks, %d bytes from physical block %d ">,-
	UCB_L_NO_SECTORS(R3), R0, R1

	MOVB	R1,-(R2)			; Fill in logical block address
	ASHL	#-8,R1,R1			; Get next byte
	MOVB	R1,-(R2)			; Fill in logical block address
	ASHL	#-8,R1,R1			; Get next byte
	MOVB	R1,-(R2)			; Fill in logical block address
	ASHL	#-8,R1,R1			; Get next byte
	MOVB	R1,-(R2)			; Fill in logical block address

	CMPB	#OPTIC$C_KODAK_6800,UCB$B_FEX(R3) ; Is this a Kodak 6800?
	BNEQ	60$				; Nope -- continue
	MOVL	SCDRP$L_CMD_PTR(R5),R2		; SCSI command address
	CMPB	4(R2), #^X2E			; Write verify?
	BNEQ	60$				; Nope -- continue
	BISB	#^X80, <4+9>(R2)		; Enable blank check

 60$:
	BISB	#SCDRP$M_FLAG_BUFFER_MAPPED,-	; This buffer has been mapped
		SCDRP$L_SCSI_FLAGS(R5)		;
	SPI$BUFFER_MAP				; Map the user buffer
	BSBW	SEND_COMMAND			; Send the SCSI command

	BLBS	R0, 70$
	PUT_N2	#23, <"WDD-RW: Status from SEND_COMMAND on unit %d:  %x ">,-
			UCB$W_UNIT(R3), R0
70$:
	MOVZWL	UCB_L_NO_SECTORS(R3),R1		; Get number of sectors read
	MOVZWL	UCB_L_WD_SECTBYT(R3),R2		; Get conversion factor
	MULL2	R2,R1				;  convert to bytes
80$:	RSB					; Return to caller

	.SBTTL	IO_PACKACK - Perform Packack Function
;++
; IO_PACKACK
;
; This routine spins up the unit, does mode sense to get write protection,
; and capacity and sets ucb$l_maxblock.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0 - VMS status code
;
;	R1,R2 destroyed
;
;--
	.ENABLE LSB				; IO_PACKACK
IO_PACKACK:
	PUT_N2	#30,<"WDD-IO_PACKACK: ">
;
; Spin up disks.
;
	PUT_N2	#30,<"WDD-IO_PACKACK: Requesting START ">
	MOVL	#1,R0				; Request spin-up command
 	BSBW	START_STOP_UNIT 		; go do it
	BLBC	R0, 180$			; Go on error
;
; For multi-function media the Inquiry data will not be completely valid until
; after the disk is spun up and the drive can determine if the media is WORM
; or RWable. This is needed to support jukeboxs that contain WORM and RW media.
;
	PUT_N2	#30,<"WDD-IO_PACKACK: Requesting INQUIRY ">
	BSBW	INQUIRY				; Determine drive type
	BLBC	R0, 180$			; Go on error
;
; Get the capacity of the disk.
;
	PUT_N2	#30,<"WDD-IO_PACKACK: Requesting READ_CAPACITY ">
	BSBW	READ_CAPACITY			;  read capacity
	BLBC	R0, 180$			; Go on error
;
; Upon return from scsi_read_capacity, r1 contains the total capacity of the
; disk in 512-byte blocks.
;
; Check the manufacture type from the UCB against the code in the drive
; table, if they match then continue checks, else skip to next table entry.
; Set maxblock and sectors when a match from table is found.  UCB$B_FEX
; is used to tell manufacture and is set by INQUIRY.
;
 10$:	MOVAB	INQUIRY_TABLE,R0		; Set up pointer to table
	MOVAB	INQUIRY_TABLE_END,R2		; Set up pointer to table end
 20$:	CMPB	DRVTBL$B_TYPE(R0),UCB$B_FEX(R3)	; Check manufacture code
	BNEQ	30$				; No, skip next check
	TSTL	DRVTBL$L_MAX(R0)		; Allow any size?
	BEQL	40$				; If zero, yes - automatic match
	CMPL	DRVTBL$L_MAX(R0),R1		; Check for size match
	BEQL	40$				; If eql match so go
 30$:	ADDL	#DRVTBL$K_SIZE,R0		; Point to next entry
	CMPL	R2,R0				; Finished, beyond table?
	BGTRU	20$				; No, get next
	MOVL	#SS$_DEVOFFLINE,R0		;  If here show no device found
	BRW	180$				;    and return

 40$:	BISW	DRVTBL$W_FLAGS(R0),-		; Set the low-order byte of the
		UCB$L_DEVDEPND2(R3)		;  flags word for this drive
	BBC	#OPTIC$V_MULTI,-		; If not multifunction drive
		UCB$L_DEVDEPND2(R3), 46$	;  then continue
;
	PUT_N2	#30,<"WDD-IO_PACKACK: Requesting CHANGE_MODE ">
	PUSHL	R1				; Save the size
	BSBW	CHANGE_MODE			; Change mode
	POPL	R1				; Restore size
;
; A fixed value is used (DRVTBL$K_BLOCKS) for the number of sectors per track.
; The highest block address (UCB$L_MAXBLOCK) must be a multiple of the
; sectors per track.
;
; If LF4500/6600 type disk, compute the number of sectors per track so the
; complete capacity of the disk can be used.
;
 46$:
	CMPB	#OPTIC$C_LMS_LF4500, UCB$B_FEX(R3); Is this an LF6600 type disk?
	BNEQ	47$				; No, Branch
	CMPL	R1,#15000000			; Disk > 15 million blocks
	BLEQ	47$				; No, branck
	MOVB	#8, UCB$B_TRACKS(R3)		; Set number of tracks to 8
	BRB	50$				;

;***February 28, 1995
;
; If HP 1716C type disk, compute the number of sectors per track so the
; complete capacity of the disk can be used.
;
 47$:	CMPB	#OPTIC$C_RWZ53,UCB$B_FEX(R3)	; Is this an RWZ53 type drive?
	BEQL	50$				; No, branch
	CMPB	#OPTIC$C_HP_1716C,UCB$B_FEX(R3)	; Is this an HP 1716C type drv?
	BNEQ	90$				; No, branch
;
; **			*************
; NO PUT_N2's inside this loop between the PUSHL's and POPL's.
; **			*************
;
 50$:	PUSHL	R4
	PUSHL	R3
	PUSHL	R1				; Save the size
	MOVL	#255,R0				; Set initial track size
	CLRL	R2				; Dividend is a quadword
 60$:	EDIV	R0,R1,R3,R4			; Track size modulo of capacity
	TSTL	R4				; Remainder zero showing modulo
	BEQL	80$				; yes, branch
	DECW	R0				; Decrement track size
	CMPL	R0,#17				; Track size less than 17
	BLSS	70$				; Yes, Use other alogorithm
	TSTL	R0				; Is track size zero?
	BEQL	80$				; Yes, Use other algorithm
	BRB	60$				; Loop, using next track size.
 70$:	CLRL	R0                              ; Track size may be to small,
						; force zero... See CMPL above
 80$:	POPL	R2				; Restore disk capacity size
	POPL	R3
	POPL	R4

	PUT_N2	#21,-
	<"WDD-IO_PACKACK: Algorithm 1, Disk size: %d sec/trk %d ">,r2,r0

	MOVL	R0,R1				; Set R1 to sectors per track
	BEQL	110$				; If zero, branch so set ok
	BRB	120$				; Go set UCB fields
 90$:
;***February 28, 1995

 100$:	MOVL	R1, R2				; Copy number of sectors
 110$:	MOVZBL	#DRVTBL$K_BLOCKS, R1		; Get number of sectors / track
 120$:	MOVB	R1, UCB$B_SECTORS(R3)		; Set sectors per track

	DIVL2	R1, R2				; Make the highest block address
	MULL2	R1, R2 				;   a multiple of sect/track
	MOVL	R2, UCB$L_MAXBLOCK(R3)		; Save highest block address

	PUT_N2	#21,-
	<"WDD-IO_PACKACK: Algorithm 2, Disk size: %d sec/trk %d ">,r2,r0

	MOVL	UCB_L_WD_SECTBLK(R3), R0	; Get # blocks per sector
	ADDL2	R0,R2				; Bump R2 so we can correctly
	DIVL3	R1,R2,R1			;   calc number of cylinders
	MOVW	R1,UCB$W_CYLINDERS(R3)		;    and save it
;
; **** Special tests for Optimem drives ****
; I don't branch out after I find a match to save code space in memory,
; None of the other branches should succeed and this code is only executed
; at drive spin up.
;
	ASSUME  OPTIC$C_OPTIMEM_16_12	   EQ	OPTIC$C_OPTIMEM_16_10+1
	ASSUME  OPTIC$C_OPTIMEM_1000_20_12 EQ	OPTIC$C_OPTIMEM_1000_20_10+1
	ASSUME  OPTIC$C_OPTIMEM_2400_20_12 EQ	OPTIC$C_OPTIMEM_2400_20_10+1

;
; Note that the value stored in UCB$L_MAXBLOCK is rounded
; to a multiple of DRVTBL$K_BLOCKS, so check value accordingly.
;
OPT_12G = <WD_C_OPT_12G_SIZE / DRVTBL$K_BLOCKS> * DRVTBL$K_BLOCKS

	CMPL	UCB$L_MAXBLOCK(R3),-		; Is maxblock Optimem 1.2G
		#OPT_12G			;  block size?
	BNEQ	150$				; No, so continue
	CMPB	#OPTIC$C_OPTIMEM_16_10,-	; Is it a 1.6 Controller
		UCB$B_FEX(R3)			;
	BNEQ	130$				; No, check for 2.0
	INCB	UCB$B_FEX(R3)			; Yes, set to 1.6 and 1.2G media
 130$:	CMPB	UCB$B_FEX(R3), -		; Is it a 2.0 Controller for a
		#OPTIC$C_OPTIMEM_1000_20_10	;  Optimem 1000 drive
	BNEQ	140$				; No, must be ok, so continue
	INCB	UCB$B_FEX(R3)			; Yes, set 2.0,1000, 1.2G media
 140$:	CMPB	UCB$B_FEX(R3), -		; Is it a 2.0 Controller for a
		#OPTIC$C_OPTIMEM_2400_20_10	;  Optimem 2400 drive
	BNEQ	150$				; No, must be ok, so continue
	INCB	UCB$B_FEX(R3)			; Yes, set 2.0,2400, 1.2G media
;
; **** End special tests for Optimem ****
;
;******
;  Development note: this code should determine whether or not the disk
;  is physically write-protected and at least set the SWL bit if it is.
;******
;
;  For the Optimem and SONY, a READ DRIVE STATUS is required.  For the
;  LMS LD-510 a MODE SENSE is required.
;
 150$:	PUT_N2	#30,<"WDD-IO_PACKACK: Requesting MODE_SENSE ">
	BSBW	MODE_SENSE			; mode sense
	BLBC	R0, 180$			; Go on error
;
; We must do a mode select on most drives to enable blank
; check on reads and writes.
;
 160$:	PUT_N2	#30,<"WDD-IO_PACKACK: Requesting MODE_SELECT ">
	BSBW	MODE_SELECT			; Yes, mode select to set EBC
	BLBC	R0, 180$			; Go on error
;
; Prevent medium removal via "eject" button.
;
 170$:	PUT_N2	#30,<"WDD-IO_PACKACK: Requesting PREVENT_ALLOW_REMOVE ">
	MOVL	#1, R0				; Set prevent bit
	BSBW	PREVENT_ALLOW_REMOVE		;  go do it
	BLBC	R0,180$				;   abort if it failed

	BISL	#UCB$M_VALID,UCB$L_STS(R3)	; We think the volume is valid
	MOVL	#SS$_NORMAL,R0			; Show success
;
; Normal return point.
;
180$:	CLRL	R1				; Zap second status
	BRW	COMPLETE_IO			; Complete the user's I/O
;
; Retry return point. If the error is any of those listed below in the
; code just return the error.
;
;CHECK_RETRY:
;	CMPW	R0,#SS$_DEVOFFLINE		; Select problem?
;	BEQL	200$				; Yes, branch
;	CMPW	R0,#SS$_CTRLERR			; Controller error?
;	BEQL	200$				; Yes, branch
;	CMPW	R0,#SS$_DRVERR			; Fatal drive error?
;	BEQL	200$				; Yes, branch
;	CMPW	R0,#SS$_TIMEOUT			; Time out error?
;	BEQL	200$				; Yes, branch
;;
;; The error status indicates another error type.  Remap the
;; status to SS$_MEDOFL, so that MOUNT will retry the packack.
;;
;	CLRL	R1				; Zap second status
;	MOVL	#SS$_MEDOFL,R0			; Show medium offline
;	BRW	WD_RETRY			;  and go retry the i/o

	.DISABLE LSB				; io_packack

	.SBTTL	IO_AVAILABLE - Make drive available to other users
;+
; IO_AVAILABLE
;
; This routine makes a drive available for use by clearing the volume valid
; bit and enabling the eject button on the drive.
;
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;
;			SS$_NORMAL   - I/O completed successfully.
;			SS$_ILLSEQOP - I/O failed, bad sense key.
;			SS$_IVSTSFLG - Invalid SCSI status returned.
;			SS$_OPINCMPL - I/O failed, insufficient data returned.
;
;--

IO_AVAILABLE:
	BICL	#UCB$M_VALID,UCB$L_STS(R3)	; We think the volume is invalid
	CLRL	UCB$L_MAXBLOCK(R3)		;   zap block count
;
; Allow medium removal via "eject" button.  Must do here to support
; DISM/NOUNLOAD.
;
	CLRL	R0				; Clear prevent bit
	BSBW	PREVENT_ALLOW_REMOVE		;  go do it

	BRW	COMPLETE_IO			; Complete the user's I/O

	.SBTTL	IO_UNLOAD - Spin-down drive & make it available to other users
;+
; IO_UNLOAD
;
; This routine spins down the drive and makes it available for use by clearing
; the volume valid bit and enabling the eject button on the drive.
;
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0	- Status
;
;			SS$_NORMAL   - I/O completed successfully.
;			SS$_ILLSEQOP - I/O failed, bad sense key.
;			SS$_IVSTSFLG - Invalid SCSI status returned.
;			SS$_OPINCMPL - I/O failed, insufficient data returned.
;
;--
IO_UNLOAD:
;
; Allow medium removal via "eject" button.
;
	CLRL	R0				; Clear prevent bit
	BSBW	PREVENT_ALLOW_REMOVE		;  go do it
;
; Must do before spin down to support jukebox drives.
;
	CLRL	R0				; Select spin down
	BSBW	START_STOP_UNIT			;  go do it
	BLBS	R0, 40$

40$:	BICL	#UCB$M_VALID,UCB$L_STS(R3)	; We think the volume is invalid
	CLRL	UCB$L_MAXBLOCK(R3)		;   zap block count
;
; Also for these drive type we must eject disk to support
; jukebox robot removal from drive.
;
	CMPB	#OPTIC$C_SONY_WDD_931,UCB$B_FEX(R3); Is it a Sony drive?
	BNEQ	50$				; No, branch
45$:	BSBW	EJECT_SONY_DRIVE		; Eject the disk
	BRB	80$				; go complete I/O
;
; Is it a Pioneer?
;
 50$:	CMPB	#OPTIC$C_PIONEER_5001,UCB$B_FEX(R3); Is it a Pioneer drive?
	BEQL	70$				; Yes, branch
	CMPB	#OPTIC$C_PIONEER_7001,UCB$B_FEX(R3); Is it a Pioneer drive?
	BEQL	70$				; Yes, branch
;
; Is it a LMS LD 520?
;
 60$:	CMPB	#OPTIC$C_LMS_LD520,UCB$B_FEX(R3); Is it a LMS LD 520 drive?
	BNEQ	75$				; No, branch
;
; Pioneer and LD520 use same command to eject...
;
 70$:	BSBW	EJECT_PIONEER_DRIVE		; Eject the disk
	brb	80$				; go complete I/O
;
; Is it a Sony MO or HP Multifunction?
;
 75$:	BBS	#OPTIC$V_SCSI2,	-		; All SCSI-2 disks
		UCB$L_DEVDEPND2(R3), 76$	;  need this set
	CMPB	#OPTIC$C_HP_MF,UCB$B_FEX(R3)	; Is it a HP MF drive?
	BEQL	76$				; Yes
;?	CMPB	#OPTIC$C_SONY_SMOF,UCB$B_FEX(R3) ; Is it a Sony drive?
;?	BEQL	79$				; Yes
	CMPB	#OPTIC$C_SONY_SMOS,UCB$B_FEX(R3) ; Is it a Sony MO drive?
	BNEQ	79$				; No, branch
 76$:	BSBW	EJECT_SONY_MO_DRIVE		; Eject the disk
	BRB	80$				; go complete I/O

 79$:
 80$:	CLRL	R1				; No, bytes transferred
 	BRW	COMPLETE_IO			; Complete the user's I/O


	.sbttl	Generic load/map/unload routines
;
; LOAD_ELEMENT	- Calls jukebox specific load element routines
; UNLOAD_ELEMENT- Calls jukebox specific unload element routines
; JB_READMAP	- Calls jukebox specific read map routines
;
; Load_map_unload does generic inquiry overhead and then dispatches to
; correct routine based on I/O function.
;
; Inputs:
;
;	r1 - I/O function code
;	r2 - Address of the IRP
;	r3 - Address of the UCB
;
; Outputs:
;
;	r0 - VMS status code
;	r1 - Zero
;
;	Destroys r2.
;
LOAD_MAP_UNLOAD:

	PUSHL	R1				; Save function code
	BSBW	INQUIRY				; determine drive type
	POPL	R1				; Restore function code
;
; Dispatch to specific processing based on i/o function
;
	ASSUME	IRP$S_FCODE LE 7		; Allow byte mode dispatch

	DISPATCH R1,TYPE=B,<-			; Dispatch according to func
		<IO$_LOAD_ELEMENT,	LOAD_ELEMENT>,-
		<IO$_UNLOAD_ELEMENT,	UNLOAD_ELEMENT>,-
		<IO$_JB_READMAP,	JB_READMAP>>
;+
; Bogus I/O function code will fall through. Set illegal function code
; status and complete the I/O.
;-
	CLRL	R1				; Show no data transfer
	MOVZBL	#SS$_ILLIOFUNC,R0		; Specify the error type
	BRW	COMPLETE_IO			; complete I/O
;
; Load element operation.  Move a cartridge from a slot to a drive.
;
LOAD_ELEMENT:
	MOVL	UCB$L_IRP(R3),R2		; Get address of IRP
	MOVZBL	UCB$B_FEX(R3), R0		; Get device type

	DISPATCH R0,<-				; Dispatch according to type
		<OPTIC$C_SONY_WDA_930,	SCSI_MOVE_MEDIA>,-
		<OPTIC$C_SONY_WDA_330,	SCSI_MOVE_MEDIA>,-
		<OPTIC$C_HP_JB,		SCSI_MOVE_MEDIA>,-
		<OPTIC$C_DISC_JB,	SCSI_MOVE_MEDIA>,-
		<OPTIC$C_NKK_556,	SCSI_MOVE_MEDIA>,-
		<OPTIC$C_CYGNET,	SCSI_MOVE_MEDIA>,-
		<OPTIC$C_KODAK_560E_JB,	SCSI_MOVE_MEDIA>,-
		<OPTIC$C_CD_CHANGER,	SCSI_MOVE_MEDIA>,-
		<OPTIC$C_LMS_LF4500,	LMS_4500_MOVE_MEDIA>>

;
; No known jukebox?
;
	CLRL	R1				; Show no data
	MOVL	#SS$_ILLIOFUNC,R0		; Show error
	BRW	COMPLETE_IO			; finish I/O
;
; Unload element operation.  Move a cartridge from a drive to a slot.
;
UNLOAD_ELEMENT:
	MOVL	UCB$L_IRP(R3),R2		; Get address of IRP
	MOVZBL	UCB$B_FEX(R3), R0		; Get device type

	DISPATCH R0,<-				; Dispatch according to type
		<OPTIC$C_HP_JB,		SCSI_MOVE_MEDIA>,-
		<OPTIC$C_DISC_JB,	SCSI_MOVE_MEDIA>,-
		<OPTIC$C_NKK_556,	SCSI_MOVE_MEDIA>,-
		<OPTIC$C_CYGNET,	SCSI_MOVE_MEDIA>,-
		<OPTIC$C_CD_CHANGER,	SCSI_MOVE_MEDIA>,-
		<OPTIC$C_LMS_LF4500,	LMS_4500_MOVE_MEDIA>>

;
; No known jukebox?
;
	CLRL	R1				; Show no data
	MOVL	#SS$_ILLIOFUNC,R0		; Show unknown jukebox
	BRW	COMPLETE_IO			; finish I/O

;
; READ MAP OPERATION.
;
JB_READMAP:
	MOVL	UCB$L_IRP(R3),R2		; Get address of IRP
;
; HP?
;
40$:	CMPB	#OPTIC$C_HP_JB,UCB$B_FEX(R3)	; Are we an HP jukebox?
	BEQL	55$
;
; NKK?
;
50$:	CMPB	#OPTIC$C_NKK_556,UCB$B_FEX(R3)	; Are we an NKK jukebox?
	BNEQ	60$				; No, branch
55$:	CMPW	#NKK_MAP_LENGTH, UCB$L_BCNT(R3)	; Buffer size correct?
	BGTRU	60$				; No, branch
	BSBW	NKK_READ_MAP			; Do the NKK
	BRB	99$				; Exit
;
; No known jukebox:
;
60$:	MOVL	#SS$_ILLIOFUNC,R0		; Show no known jukebox
	CLRL	R1				; Show no data
99$:	BRW	COMPLETE_IO			; finish I/O

	.SBTTL	SCSI_MOVE_MEDIA - Generic SCSI Move Media routine
;++
;  SCSI_MOVE_MEDIA
;
; This routine is called to move a cartridge from a slot, drive, or
; mailbox to a drive, slot, or mailbox.
;
; INPUTS:
;
;	R1	- function code
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
;	IRP$L_MEDIA, first word contains higher level device unit number
;	IRP$L_MEDIA+2, second word contains drive or mailbox designator
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;--
;
SCSI_MOVE_MEDIA:

	BBC	#OPTIC$V_PREVENT_REM,-		; If device is not set as
		UCB$L_DEVDEPND2(R3),10$		;  prevent removal then continue
	WD_DISABLE_ERL				; Disable error logging
	PUSHL	R1				; Save function code
	MOVL	#1,R0				; Disallow removal
	BSBW	PREVENT_ALLOW_REMOVE		; Set device correctly
	POPL	R1				; Restore function code
	WD_ENABLE_ERL				; Re-enable error logging

10$:
	CLRL	UCB$L_DPC(R3)			; Desperate for temp. storage
	MOVB	R1, UCB$L_DPC(R3)		; Save function code in dpc
	MOVL	UCB$L_IRP(R3), R0		; Get IRP address
	MOVW	IRP$L_MEDIA(R0),-		; Get unit number
		UCB_L_SLOT(R3)			;
	MOVW	<IRP$L_MEDIA + 2>(R0),-		; Get drive # or mailbox
		UCB_L_DRIVE(R3)			;
	MOVAL	HP_MOVE_CMD, R2			; Get command addr in R2
	BSBW	SETUP_CMD			; Perform setup for SCSI command
	BLBC	R0,20$				; Branch if OK
	BSBW	ALLOC_SYS_BUF			; Allocate system buffer
	BLBS	R0,30$				; Branch if OK
20$:	BRW	COMPLETE_IO
;
; Extract slot and side from unit number in UCB_L_SLOT.
;
30$:	MOVL	UCB_L_WD_SLOT_OFFSET(R3), R0	; Get offset for slot
	CLRL	R2				; Ensure r2 not hosed
;
; If Pioneer CD Changer do not divide unit by 2, bump change unit number to
; slot number of JB by adding 1.
;
    	CMPB	#OPTIC$C_CD_CHANGER,-		; This a CD Changer
		UCB$B_FEX(R3)			;
	BNEQ	40$				; No, branch
	INCL	UCB_L_SLOT(R3)			; Change unit # to slot #
	BRB	50$

40$:	MOVL	UCB_L_SLOT(R3), R1		; Get unit number for ediv
	EDIV	#2, R1, R1, R2			; r1 = slot, r2 = side
	ADDW3	R0, R1, UCB_L_SLOT(R3)		; Adjust for start slot addr.
50$:	MOVL	SCDRP$L_CMD_PTR(R5),R0		; Point to the command buffer
	CLRQ	SCSI_CMD_B_BYTE2(R0)		; Clear bytes 2-9
	CMPB	#IO$_LOAD_ELEMENT,- 		; Load?
		UCB$L_DPC(R3)			;
	BNEQ	70$				; No
;
; Yes, we have a load. Get source
;
 	CMPW	#MAILBOX_INDEX, UCB_L_DRIVE(R3)	; Is the mailbox the source
	BEQL	60$				; Yes
;
; From slot to drive
;
	MOVL	UCB_L_WD_DRIVE_OFFSET(R3), -(SP) ; Get offset for drive
	MOVB 	UCB_L_SLOT(R3),-		; Load slot # (LSB)
		SCSI_CMD_B_BYTE5(R0)		;
	MOVB 	UCB_L_SLOT+1(R3),-		; Load slot # (MSB)
		SCSI_CMD_B_BYTE4(R0)		;
	MOVL	UCB_L_DRIVE(R3), R1		; Get drive #
	ADDL	R1, (SP)			; Convert to internal number
	MOVB	(SP), SCSI_CMD_B_BYTE7(R0)	; Load low-order drive #
	MOVB	1(SP), SCSI_CMD_B_BYTE6(R0)	; Load high-order drive #
	MOVB	R2, SCSI_CMD_B_BYTE10(R0)	; Load invert bit
	ADDL	#4, SP				; Clean stack
	BRW	90$				; Leave
;
; From mailbox to slot
;
60$:	MOVL	UCB_L_WD_MAILBOX_OFFSET(R3), -(SP) ; Get offset for mailbox
	MOVB 	UCB_L_SLOT(R3),-		; Load slot # (LSB)
		SCSI_CMD_B_BYTE7(R0)		;
	MOVB 	UCB_L_SLOT+1(R3),-		; Load slot # (MSB)
		SCSI_CMD_B_BYTE6(R0)		;
	MOVL	UCB_L_DRIVE(R3), R1		; Get drive #
	ADDL	R1, (SP)			; Convert to internal number
	MOVB	(SP), SCSI_CMD_B_BYTE5(R0)	; Load low-order drive #
	MOVB	1(SP), SCSI_CMD_B_BYTE4(R0)	; Load high-order drive #
	MOVB	R2, SCSI_CMD_B_BYTE10(R0)	; Load invert bit
	ADDL	#4, SP				; Clean stack
	BRB	90$				; Leave
;
; We have an unload function
;
70$:
 	CMPW	#MAILBOX_INDEX, UCB_L_DRIVE(R3)	; Is the mailbox the source
	BEQL	80$				; Yes
;
; No.  Must be from drive to slot
;
	MOVL	UCB_L_WD_DRIVE_OFFSET(R3), -(SP) ; Get offset for drive
	MOVB 	UCB_L_SLOT(R3),-		; Load slot # (LSB)
		SCSI_CMD_B_BYTE7(R0)		;
	MOVB 	UCB_L_SLOT+1(R3),-		; Load slot # (LSB)
		SCSI_CMD_B_BYTE6(R0)		;
	MOVL	UCB_L_DRIVE(R3), R1		; Get drive #
	ADDL	R1, (SP)			; Convert to internal number
	MOVB	(SP), SCSI_CMD_B_BYTE5(R0)	; Load low-order drive #
	MOVB	1(SP), SCSI_CMD_B_BYTE4(R0)	; Load high-order drive #
	MOVB	R2, SCSI_CMD_B_BYTE10(R0)	; Load invert bit
	ADDL	#4, SP				; Clean stack
	BRB	90$				; Leave
;
; From slot to mailbox
;
80$:	MOVL	UCB_L_WD_MAILBOX_OFFSET(R3), -(SP) ; Get offset for mailbox
	MOVB 	UCB_L_SLOT(R3),-		; Load slot # (LSB)
		SCSI_CMD_B_BYTE5(R0)		;
	MOVB 	UCB_L_SLOT+1(R3),-		; Load slot # (MSB)
		SCSI_CMD_B_BYTE4(R0)		;
	MOVL	UCB_L_DRIVE(R3), R1		; Get drive #
	ADDL	R1, (SP)			; Convert to internal number
	MOVB	(SP), SCSI_CMD_B_BYTE7(R0)	; Load low-order drive #
	MOVB	1(SP), SCSI_CMD_B_BYTE6(R0)	; Load high-order drive #
	MOVB	R2, SCSI_CMD_B_BYTE10(R0)	; Load invert bit
	ADDL	#4, SP				; Clean stack
;
; Send command and exit stage right
;
90$:	BSBW	SEND_COMMAND			; Send command
	PUT_N2	#30, <"WDD-SCSI_MOVE_MEDIA: SEND_COMMAND Status: %x ">,R0

100$:	BSBW	CLEANUP_CMD			; Clean-up SCDRP
    	BRW	COMPLETE_IO

	.SBTTL	LMS_4500_MOVE_MEDIA - LMS LF4500 move element routine
;++
;  LMS_4500_MOVE_MEDIA
;
; This routine is called to move an LMS LF4500 cartridge to or from
; a drive and to perform an eject function
;
; INPUTS:
;
;	R1	- function code
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;--
;
LMS_4500_MOVE_MEDIA:

	CLRL	UCB$L_DPC(R3)			; Desperate for temp. storage
	MOVB	R1, UCB$L_DPC(R3)		; Save function code in dpc
	MOVL	UCB$L_IRP(R3),-                 ; Get IRP address
		R0    				;
	MOVW	IRP$L_MEDIA(R0),-		; Get unit number
		UCB_L_SLOT(R3)			;
	CMPB	#IO$_LOAD_ELEMENT,- 		; Load?
		UCB$L_DPC(R3)			;
	BNEQ	10$				; No
;
; Yes, we have a load.
;
	MOVAL	LMS_4500_MOVE_CMD,R2		; Get command addr in R2
	BSBW	SETUP_CMD			; Perform setup for SCSI command
 	BLBC	R0,COMPLETE_IO			; Branch if error
	MOVL	SCDRP$L_CMD_PTR(R5),R0		; Point to the command buffer
	BISB	#LF4500$M_LOAD,-		; Set load bit
		SCSI_CMD_B_BYTE1(R0)		;
	ADDB3 	#1,UCB_L_SLOT(R3),-		; Load cartridge #
		SCSI_CMD_B_BYTE4(R0)		;
	BRB	20$				; Leave
;
; Well then it must be an unload function
;
10$:	MOVAL	LMS_UNLOAD_CMD, R2	; Load stop unit with eject bit set
	BSBW	SETUP_CMD		; Perform setup for SCSI command
 	BLBC	R0,COMPLETE_IO		; Branch if error
	CLRL	UCB_L_SLOT(R3)		; nothing in the drive now

;
; R1 = DMA length from SETUP_CMD
;
20$:	BSBW	ALLOC_SYS_BUF		; allocate system buffer
	BLBC	R0,30$			; Branch on error
	BSBW	SEND_COMMAND		; Send the SCSI command
30$:	BSBW	CLEANUP_CMD		; Clean-up SCDRP
	BRW	COMPLETE_IO		; Finish

	.SBTTL	+
	.SBTTL	+ UTILITY ROUTINES
	.SBTTL	+
	.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 request sense command is sent to the target.
;
; *** NOTE ***
; CLEANUP_CMD is not call in this routine since that data needs to be looked
; at by SEND_COMMAND.  You must call CLEANUP_CMD yourself after a call to
; REQUEST_SENSE in order to cleanup the SCDRP for the next call.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- SPDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;			SS$_IVSTSFLG - Bad SCSI status returned during
;				       REQUEST SENSE.
;	R1,R2	- Destroyed
;	All other registers preserved
;--

REQUEST_SENSE::
	.ENABLE	LSB			; REQUEST_SENSE

	.JSB_ENTRY INPUT=<R3,R4,R5>

	PUT_N2	#20,<"WDD-REQUEST_SENSE: UCB: %x SPDT: %x SCDRP: %x ">,R3,R4,R5

	MOVAL	REQUEST_SENSE_CMD,R2	; Address of REQUEST_SENSE command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BLBC	R0,20$			; sigh...
	BSBW	ALLOC_SYS_BUF		; allocate system buffer
	BLBC	R0,20$			; Branch on error
	SPI$SEND_COMMAND		; Send the SCSI command
	PUT_N2	#30,<"WDD-REQUEST_SENSE: SPI$SEND_COMMAND status: %x ">,R0
	BLBC	R0,20$			; Return on error
	MOVZBL	@SCDRP$L_STS_PTR(R5),R1	; Get SCSI status byte
	BICB	#SCSI$M_STS,R1		; Clear reserved, vendor unique bits
	BEQL	20$			; Branch if good status
	MOVL	#SS$_IVSTSFLG,R0	; Return bad SCSI status to caller.
 20$:	PUT_N2	#20,<"WDD-REQUEST_SENSE: R0: %x R1: %x ">,R0,R1
	RSB				; Return to caller

	.DISABLE LSB			; REQUEST_SENSE

	.SBTTL	START_STOP_UNIT	- Send a start/stop command
;++
; START_STOP_UNIT
;
; This routine is called to spin a drive up or down.
;
; INPUTS:
;
;	R0	- 0 indicates spin-down, 1 indicates spin-up
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1	- Destroyed
;	All other registers preserved
;--
START_STOP_UNIT::
	.JSB_ENTRY INPUT=<R0,R3,R4,R5>

	PUT_N2	#30,<"WDD-START_STOP_UNIT: ">

	TSTL	R0			; Test flag?
	BEQL	20$			; Zero, yes branch
	MOVAL	START_UNIT_CMD,R2	; Do Start Unit command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BLBC	R0,40$			; Branch on error
	CMPB	#OPTIC$C_SONY_WDD_931,-	; Is it this type of SONY drive?
		UCB$B_FEX(R3)		; ...
	BNEQ	10$			; Nope
	MOVL	SCDRP$L_CMD_PTR(R5),R0	; Point to the command buffer
	MOVB	#3, SCSI_CMD_B_BYTE4(R0); Set to load and spin up
 10$:	BRB	30$			; skip spin down
 20$:	MOVAL	STOP_UNIT_CMD,R2	; Do Stop Unit command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BLBC	R0,40$			; Branch on error
 30$:	BSBW	ALLOC_SYS_BUF		; allocate system buffer
	BLBC	R0,40$			; Branch on error
	BSBW	SEND_COMMAND		; Send the SCSI command
 40$:	BSBW	CLEANUP_CMD		; Clean-up SCDRP
	RSB				; Return to caller

	.Sbttl	Change_Mode - Change mode in a multifunction drive
;++
; Change_Mode
;
; This routine is used to change the mode of a multifunction drive.
; The mode is read and then set to the mode (WORM or rewritable) as
; indicated by the media present in the drive.
;
; INPUTS:
;
;	R3	- UCB Address
;	R4	- PDT Address
;	R5	- SCDRP Address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;
CHANGE_MODE::
	.JSB_ENTRY INPUT=<R3,R4,R5>

	MOVL	#SS$_NORMAL, R0		; Assume success
	CMPB	#OPTIC$C_HP_1716C, -	; HP drive?
		UCB$B_FEX(R3)		; ...
	BEQL	60$			; If yes, don't do it

        CMPB    #OPTIC$C_RWZ53, -       ; HP/RWZ53 drive?
                UCB$B_FEX(R3)           ; ...
        BEQL    60$                     ; If yes, don't do it

        CMPB    #OPTIC$C_SONY_F521, -   ; SONY SMO-F521 drive?
                UCB$B_FEX(R3)           ; ...
        BEQL    60$                     ; If yes, don't do it

	MOVAL	READ_MODE_CMD,R2	; Address of READ_MODE command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BLBC	R0,40$			; sigh...
	BSBW	ALLOC_SYS_BUF		; allocate system buffer
	BLBC	R0,40$			; Branch on error
	WD_DISABLE_ERL			; Don't log errors
	BSBW	SEND_COMMAND		; Send the SCSI command
	WD_ENABLE_ERL			; Restore logging of errors
	BLBC	R0,40$			; Return on error

 10$:	MOVL	SCDRP$L_SVA_USER(R5),R2 ; Get address of mode data
	BICL	#UCB_M_WD_WRTLCK,-	; Assume not write locked
		UCB$L_DEVDEPND2(R3)	;  ...
	BLBC	(R2), 20$		; Skip of not write protected
	BISL	#UCB_M_WD_WRTLCK,-	; The medium is write locked
		UCB$L_DEVDEPND2(R3)	;  ...

 20$:	BICL	#OPTIC$M_RW, -		; Init flags
		UCB$L_DEVDEPND2(R3)	;  ...
	BISL	#OPTIC$M_WORM, -	; to WORM mode
		UCB$L_DEVDEPND2(R3)	;  ...
	MOVL	#^X40, R0		; Assume worm mode
	CMPB	#^X10, 1(R2)		; Is it WORM medium?
	BEQL	30$			; If eql, yes
	CLRL	R0			; Set to rewritable mode
	BICL	#OPTIC$M_WORM, - 	; Init flags
		UCB$L_DEVDEPND2(R3)	;  ...
	BISL	#OPTIC$M_RW, -		; to RW mode
		UCB$L_DEVDEPND2(R3)	;  ...
 30$:	PUSHL	R0			; Save mode
	BSBW	CLEANUP_CMD		; Cleanup read mode command
 	MOVAL	CHANGE_MODE_CMD,R2	; Address of CHANGE_MODE command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BLBC	R0,50$			; sigh...
	BSBW	ALLOC_SYS_BUF		; allocate system buffer
	POPL	R2			; Restore mode we want
	BLBC	R0,40$			; Branch on error
	MOVB	R2, @SCDRP$L_SVA_USER(R5) ; Set mode in command
	BSBW	SEND_COMMAND		; Send the SCSI command
	BRB	40$			; Exit
 50$:	POPL	R2			; Clean stack
 40$:	BSBW	CLEANUP_CMD		; Clean up
 60$:	RSB				; Return to caller

	.SBTTL	EJECT_SONY_DRIVE - Send an eject command to the SONY
	.SBTTL	EJECT_SONY_MO_DRIVE - Send an eject command to the SONY MO
	.SBTTL	EJECT_PIONEER_DRIVE - Send an eject command to the PIONEER
;++
;  EJECT_SONY_MO_DRIVE
;  EJECT_SONY_DRIVE
;  EJECT_PIONEER_DRIVE
;
; This routine is called to eject a SONY or PIONEER disk from the drive.
; LMS LD 520 disk look like Pioneers.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1,R2	- Destroyed
;	All other registers preserved
;--
EJECT_PIONEER_DRIVE::				; Pioneer/LMS LD520 entry
	.JSB_ENTRY INPUT=<R3,R4,R5>,OUTPUT=<R2>
	MOVAL	PIONEER_DISK_EJECT_CMD,R2	; Eject if it's a SONY drive
	BSBB	STANDARD_EJECT			; Do common eject work.
	RSB

EJECT_SONY_MO_DRIVE::				; Sony entry
	.JSB_ENTRY INPUT=<R3,R4,R5>,OUTPUT=<R2>
	MOVAL	SONY_MO_DISK_EJECT_CMD,R2	; Eject if it's a SONY MO drive
	BSBB	STANDARD_EJECT			; Do common eject work.
	RSB

EJECT_SONY_DRIVE::				; Sony entry
	.JSB_ENTRY INPUT=<R3,R4,R5>,OUTPUT=<R2>
	MOVAL	SONY_DISK_EJECT_CMD,R2		; Eject if it's a SONY drive
	BSBB	STANDARD_EJECT			; Do common eject work.
	RSB

STANDARD_EJECT::				; Standard part
	.JSB_ENTRY INPUT=<R2,R3,R4,R5>,OUTPUT=<R0>
	BSBW	SETUP_CMD			; Perform setup for SCSI command
	BLBC	R0,20$				; Branch on error
	BSBW	ALLOC_SYS_BUF			; allocate system buffer
	BLBC	R0,10$				; Branch on error
	BSBW	SEND_COMMAND			; Send the SCSI command
 10$:	BSBW	CLEANUP_CMD			; Clean-up SCDRP 1
 20$:	RSB					; Return to caller

	.SBTTL	HP_READ_MAP - Reads the jukebox map data
	.SBTTL	NKK_READ_MAP - Reads the jukebox map data
;++
; HP_READ_MAP
;
; This routine is called to read current map information from an HP
; jukebox.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- SPDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0 - VMS status code
;
;	R1,R2	- Destroyed
;	All other registers preserved
;--
;HP_READ_MAP::
;	.JSB_ENTRY INPUT=<R3,R4,R5>,OUTPUT=<R2>
;	moval	hp_readmap_cmd,r2	; save command address
;	brb	generic_read_map	; continue

NKK_READ_MAP::
	.JSB_ENTRY INPUT=<R3,R4,R5>,OUTPUT=<R2>
	moval	nkk_readmap_cmd,r2	; save command address

GENERIC_READ_MAP:
;	PUSHL	r2			; Save for later
;	bsbw	request_sense		; Request sense to zap errors
;	bsbw	cleanup_cmd		; Clean-up SCDRP
;	POPL	r2			; get command address

 	bsbw	setup_cmd		; Perform setup for SCSI command
	blbc	r0,60$			; Branch on error
	movl	ucb$L_BCNT(r3),r1	; get buffer size
	bsbw	alloc_sys_buf		; allocate system buffer
	blbc	r0,50$			; Branch on error
;
; Send the command
;
	bsbw	send_command		; Send the SCSI command
	blbc	r0,50$			; Branch on error
;
; Copy data to users buffer
;
	insv	scdrp$l_trans_cnt(R5),- ; Copy the low-order transfer count to
		#16,#16,r0		; high-order word of R0
	movzwl	scdrp$l_trans_cnt+2(r5),- ; Copy the high-order xfer count to
		r1			; R1

	pushr	#^m<r0,r1,r2,r5>	; save stuff
	movl	scdrp$l_sva_user(r5),r1	; Get address of buffer
					;  containing map data
	movl	ucb$L_BCNT(r3),r2	; get buffer size
	movl	r3,r5			; Get UCB in R5 for movtouser
	jsb	g^ioc$movtouser		; copy data to user buffer
	popr	#^m<r0,r1,r2,r5>	; restore stuff
;
; All done cleanup
;
50$:	bsbw	cleanup_cmd		; Clean-up SCDRP
60$:	RSB				; Exit

	.SBTTL	REZERO_UNIT	- Reposition over Track Zero
;++
; REZERO_UNIT
;
; This routine is called to send a rezero unit command.  This is basically
; used as a reset command on the occasion that we receive a sense key value
; of 9 which is defined as "miscellaneous" for Optimem drives.
;
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0 - VMS status code
;
;	R1,R2	- Destroyed
;	All other registers preserved
;--
REZERO_UNIT::
	.JSB_ENTRY INPUT=<R3,R4,R5>
	MOVAL	REZERO_UNIT_CMD,R2	; Address of REZERO_UNIT command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BLBC	R0,10$			; Branch on error
	BSBW	ALLOC_SYS_BUF		; allocate system buffer
	BLBC	R0,10$			; Branch on error
	BSBW	SEND_COMMAND		; Send the SCSI command
 10$:	BSBW	CLEANUP_CMD		; Clean-up SCDRP 3
	RSB				; Return to caller

	.SBTTL	TEST_UNIT	- Send a test unit ready command
;++
; TEST_UNIT
;
; This routine is called to send a test unit ready command.  Returns success
; if unit ready; else returns error status.
;
; INPUTS:
;
;	R3	- UCB address
;
; OUTPUTS:
;
;	R0	- Status
;			SS$_NORMAL - if unit is ready
;
;			SS$_IVSTSFLG - Bad SCSI status returned during
;				       REQUEST SENSE.
;	R1,R2	- Destroyed
;	All other registers preserved
;--
TEST_UNIT::
	.JSB_ENTRY INPUT=<R3>

	PUT_N2	#30,<"WDD-TEST_UNIT: ">

	MOVAL	TEST_UNIT_CMD,R2	; Address of TEST_UNIT command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BLBC	R0,10$			; Branch on error
	BSBW	ALLOC_SYS_BUF		; allocate system buffer
	BLBC	R0,10$			; Branch on error
	BSBW	SEND_COMMAND		; Send the SCSI command
 10$:	BSBW	CLEANUP_CMD		; Clean-up SCDRP 4
	RSB				; Return to caller

	.SBTTL	INQUIRY	- Send SCSI INQUIRY command.
;++
; INQUIRY
;
; This routine sends an inquiry command to the target.	Currently the
; routine is just used to determine drive manufacture.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUPUTS:
;
;	R0 = zero not found, one if manufacture is found.
;	Sets   UCB$B_FEX to correct manufacture code if determined.
;	Clears UCB$B_FEX if not manufacture found.
;
;--
INQUIRY::
	.JSB_ENTRY INPUT=<R3,R4,R5>
	CLRB	UCB$B_FEX(R3)		; Set manufacture to unknown
	CLRL	UCB$L_DEVDEPND2(R3)	; Init flags and mfg ID
	MOVAL	INQUIRY_CMD,R2		; Address of INQUIRY command
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BLBC	R0,250$			;
	BSBW	ALLOC_SYS_BUF		; allocate system buffer
	BLBC	R0,250$			; Branch on error
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBC	R0,250$			; Branch on error
;
; Use returned data to determine the drive manufacturer
;
	MOVL	SCDRP$L_SVA_USER(R5),R2		; Get address of inquiry data
	BICL	#OPTIC$M_WRITE_VERIFY,-		; Assume no write verify...
		UCB$L_DEVDEPND2(R3)		; ....
;
; Test for Optimem 1.6 Contoller first
;
	TSTB	2(R2)				; Optimem is set to 0
	BNEQ	10$				; Not Optimem
	TSTB	5(R2)				; Optimem is non-zero
	BEQL	10$				; Zero=Not Optimem so go
	CMPB	4(R2),#3			; Is it a 1.6 Controller?
	BNEQ	10$				; No, Hope its 2.0 controller
	MOVB	#OPTIC$C_OPTIMEM_16_10,-	; Set manufacture to Optimem 1.6
		UCB$B_FEX(R3)			; ...
	MOVAW	OPTIMEM_16_10_INQSTRING, R1	; Location of table entry
	BISW	DRVTBL$W_FLAGS(R1), -		; Make sure this is set
		UCB$L_DEVDEPND2(R3)
	BRW	230$				; Branch to return

;
; Test for LMS, LD1200's short format response
;
 10$:	CMPB	1(R2),#^X84			; LMS short format
	BNEQ	50$				; No, maybe long format
	CMPB	4(R2),#3			; Check additional length
	BNEQ	50$				; No, check next drive
	MOVB	#OPTIC$C_LMS_1200_600,UCB$B_FEX(R3); Set to LMS 1200/600
	MOVAW	LMS_1200_600_INQSTRING, R1	; Location of table entry
	BISW	DRVTBL$W_FLAGS(R1), -		; Make sure this is set
		UCB$L_DEVDEPND2(R3)
	BRW	230$				; Branch to return
;
; Test for strings in the inquiry table
;

 50$:	PUSHR	#^M<R2,R3,R4,R5,R6,R7>		; Save some registers
	MOVAL	5(R2), R3			; Address of candidate string
	MOVZBL	4(R2), R2			; Length of candidate string

	MOVAL	INQUIRY_TABLE, R7		; Address of table
	MOVL	R7, R6				; Table index register

 60$:	MOVL	DRVTBL$L_STRING(R6), R5		; Offset to pattern descriptor
	BEQL	200$				; End of table
	ADDL	R7, R5				; Address of pattern descr
	MOVZBL	DRVTBL$B_S_SIZE(R6), R4		; Length of pattern descr
	JSB	STR_MATCH_WILD			; Compare them
	BLBS	R0, 70$				; If a match, we got it
	ADDL	#DRVTBL$K_SIZE, R6		; Skip to next entry
	BRB	60$				; Continue

;
; We got a match.
;
 70$:	MOVB	DRVTBL$B_TYPE(R6), R0		; Save the type code
	MOVW	DRVTBL$W_FLAGS(R6), R1		; Save the flags
	POPR	#^M<R2,R3,R4,R5,R6,R7>		; Restore registers
	MOVB	R0, UCB$B_FEX(R3)		; Save in UCB
	BISW	R1, UCB$L_DEVDEPND2(R3)		; Save device flags

;
; Check for the device type and set if WORM and/or RW.
;
	MOVB	(R2), R0			; Get device type
	CMPB	R0, #SCSI$C_WORM		; WORM?
	BNEQ	80$				; Nope
	PUT_N2	#20, <"WDD-INQUIRY:  WORM ">
	BICL	#OPTIC$M_RW, -		 	; Init flags
		UCB$L_DEVDEPND2(R3)		;  ...
	BISL	#OPTIC$M_WORM, -		; to WORM mode
		UCB$L_DEVDEPND2(R3)		;  ...
	BRB	89$				; Continue

80$:	CMPB	R0, #SCSI$C_DISK		; Mag disk?
	BNEQ	89$				; Nope
	BICL	#OPTIC$M_WORM, - 		; Init flags
		UCB$L_DEVDEPND2(R3)		;  ...
	BISL	#OPTIC$M_RW, -			; to RW mode
		UCB$L_DEVDEPND2(R3)		;  ...
89$:	BICB3	#^C<7>, 2(R2), R0		; Get ANSI SCSI Version
	CMPB	R0, #2				; SCSI 2?
	BLSS	90$				; If neq, no
	PUT_N2	#20, <"WDD-INQUIRY:  SCSI2 ">
;?	BISW	#OPTIC$M_SCSI2, -		; It is SCSI 2
;?		UCB$L_DEVDEPND2(R3)		;  ...
 90$:
;
; If this device handles the ELEMENT ADDRESS Mode Sense operation,
; then now's the time to read the information contained therein.
;
	BBC	#OPTIC$V_ELEM_ADDR, R1, 230$	; Continue if not supported
	BSBW	CLEANUP_CMD			; Clean-up SCDRP
	MOVAL	MODE_SENSE_ELEM_ADDR_CMD,R2	; Address of command
	BSBW	SETUP_CMD			; Perform setup for SCSI command
	BLBC	R0,250$				;
	BSBW	ALLOC_SYS_BUF			; allocate system buffer
	BLBC	R0,250$				; Branch on error
	BSBW	SEND_COMMAND			; Send the SCSI command
	BLBC	R0,250$		       		; Branch on error
	MOVL	SCDRP$L_SVA_USER(R5),R2		; Get address of mode sense data
	MOVZBL	3(R2), R0			; Get length of block descriptor
	ADDL	R0, R2				; Add to get start of data

;;; *** Debugging
	MOVZBL	4(R2), R0			; Get the page code
	BICL	#^XC0, R0			; Clear PS/reserved bits
	CMPL	R0, #^X1D			; Right page from device?
	BEQL	100$				; If eql, yes
	MOVB	#-1, UCB$B_FEX(R3)		; Trash the device type
	MOVL	#SS$_WRONGSTATE, R0		; Status
	BRB	250$				; Exit
100$:
;;; *** Debugging

	MOVB	11+4(R2), UCB_L_WD_MAILBOX_OFFSET(R3)	; Store mailbox offset
	MOVB	10+4(R2), UCB_L_WD_MAILBOX_OFFSET+1(R3)	; ...

	MOVB	7+4(R2), UCB_L_WD_SLOT_OFFSET(R3)	; Store slot offset
	MOVB	6+4(R2), UCB_L_WD_SLOT_OFFSET+1(R3)	; ...

	MOVB	15+4(R2), UCB_L_WD_DRIVE_OFFSET(R3)	; Store drive offset
	MOVB	14+4(R2), UCB_L_WD_DRIVE_OFFSET+1(R3)	; ...

	BRB	230$				; Exit

;
; Come here if we don't find manufacture
;
 200$:	POPR	#^M<R2,R3,R4,R5,R6,R7>		; Restore registers
 210$:	CLRL	R0				; No success
	BRB	250$				; Branch to clean up & return
;
;  Here for success....
;
 230$:	MOVL	#SS$_NORMAL,R0			; Set success status
	MOVB	UCB$B_FEX(R3), -		; Save device type
		UCB$L_DEVDEPND2+3(R3)		;   in hi byte of DEVDEPND2
	CLRL	UCB$L_DEVDEPND4(R3)
	BBC	#OPTIC$V_DISCON, UCB$L_DEVDEPND2(R3), 250$
	MOVL	#1, UCB$L_DEVDEPND4(R3)

 250$:	BSBW	CLEANUP_CMD			; Clean-up SCDRP
	PUT_N2	#20, <"WDD-INQUIRY: All done ">
	RSB					; Return to caller

	.SBTTL	READ_CAPACITY - Read Capacity Of SCSI Unit
;
; READ_CAPACITY
;
; This routine is used to determine drive capacity.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0 - VMS status code
;	R1 - On success = capacity in 512-byte blocks
;	     On failure = 0
;
;	Loads UCB sector size fields:
;
;		UCB_L_WD_SECTBYT -- sector size in bytes
;		UCB_L_WD_SECTBLK -- sector size in 512-byte blocks
;
;	R1,R2	- Destroyed
;	All other registers preserved
;--
READ_CAPACITY::

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

	MOVAL	READ_CAPACITY_CMD,R2		; no - prevent removal
	BSBW	SETUP_CMD			; Perform setup for SCSI command
	BLBC	R0,10$				; Branch on error
	BSBW	ALLOC_SYS_BUF			; allocate system buffer
	BLBC	R0,10$				; Branch on error
	BSBW	SEND_COMMAND			; Send the SCSI command
	BLBC	R0,10$				; Return on error
	MOVL	SCDRP$L_SVA_USER(R5),R2		; Get address of data

	MOVZBL	(R2)+,R1			; Get MSB of max sector address
	ASHL	#<8>,R1,R1			;  shift into place
	BISB	(R2)+,R1			; Get MSB-1
	ASHL	#<8>,R1,R1			;  shift into place
	BISB	(R2)+,R1			; Get MSB-2
	ASHL	#<8>,R1,R1			;  shift into place
	BISB	(R2)+,R1			; Get LSB
	INCL	R1				; Calc number of sectors
	PUSHL	R1				;  and save it

	MOVZBL	(R2)+,R1			; Get MSB of sector size
	ASHL	#<8>,R1,R1			;  shift into place
	BISB	(R2)+,R1			; Get MSB-1
	ASHL	#<8>,R1,R1			;  shift into place
	BISB	(R2)+,R1			; Get MSB-2
	ASHL	#<8>,R1,R1			;  shift into place
	BISB	(R2)+,R1			; Get LSB
	MOVL	R1,UCB_L_WD_SECTBYT(R3)		; Save sector size in bytes

	EXTZV	#<9>,#<23>,R1,R1		; Get sector size in blocks
	MOVL	R1,UCB_L_WD_SECTBLK(R3)		; Save sector size in blocks
	MULL3	R1,(SP)+,R1			;  calc number of blocks

;;	CMPL	#IOC$C_DISK_BLKSIZ, -		; For now, we only handle
;;		UCB_L_WD_SECTBYT(R3)		;  512-byte media
;;	BEQL	10$				; If OK, continue
;;	MOVL	#SS$_FORMAT, R0			; Should never get here

 10$:	BSBW	CLEANUP_CMD			; Clean-up SCDRP 5
	RSB					; Return to caller

	.SBTTL	MODE_SENSE -  Read Mode Parameters
;++
; MODE_SENSE
;
; This routine is called to read current operating parameters of SCSI unit.
; Page control/code bits are set if UCB$L_DEVDEPND2 bit OPTIC$V_S525 is set
; showing unit as controlling a 5-1/4 drive.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0 - VMS status code
;
;	Updates a bit in the extended UCB to indicate if a volume
;	is write enabled.
;
;	R1,R2	- Destroyed
;	All other registers preserved
;--
MODE_SENSE::
	.ENABLE	LSB			; MODE_SENSE
	.JSB_ENTRY INPUT=<R3,R4,R5>,OUTPUT=<R0>,SCRATCH=<R1,R2>

	CMPB	#OPTIC$C_KODAK_6800,-	; Is this the Kodak 6800?
		UCB$B_FEX(R3)		; ...
	BEQL	10$			; Yes, branch
	PUT_N2	#20, <"WDD-MODE_SENSE:  not Kodak ">
	CMPB	#OPTIC$C_LMS_LD4100,-	; Is this the LMS LD4100?
		UCB$B_FEX(R3)		; ...
	BEQL	10$			; Yes, branch
	PUT_N2	#20, <"WDD-MODE_SENSE:  not LMS LD4100 ">
	CMPB	#OPTIC$C_LMS_LF4500,-	; Is this the LMS LF4500?
		UCB$B_FEX(R3)		; ...
	BEQL	10$			; Yes, branch
	PUT_N2	#20, <"WDD-MODE_SENSE:  not LMS LF4500 ">
	CMPB	#OPTIC$C_SONY_WDD_931,-	; Is this the Sony 931?
		UCB$B_FEX(R3)		; ...
	BEQL	10$			; Yes, branch
	PUT_N2	#20, <"WDD-MODE_SENSE:  not Sony 931 ">
	BBC	#OPTIC$V_S525,-		; Skip page control/code
		UCB$L_DEVDEPND2(R3),20$	;  if 12 inch drive
 10$:	MOVAL	MODE_SENSE_525_CMD,R2	; mode sense for 5 1/4
	BRB	30$			; go set up command
 20$:	MOVAL	MODE_SENSE_CMD,R2	; mode sense for 12"
 30$:	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BLBC	R0,60$			; Branch on error
	PUT_N2	#20, <"WDD-MODE_SENSE:  Setup for SCSI2 OK ">
	BSBW	ALLOC_SYS_BUF		; allocate system buffer
	BLBC	R0,60$			; Branch on error
	PUT_N2	#20, <"WDD-MODE_SENSE:  Allocated system buffer OK ">
	BSBW	SEND_COMMAND		; Send the SCSI command
	BLBC	R0,60$			; Return on error
	PUT_N2	#20, <"WDD-MODE_SENSE:  Performed setup OK ">
	MOVL	SCDRP$L_SVA_USER(R5),R2	; Get address of data

	BBC	#OPTIC$V_SCSI2, -	; If this isn't a SCSI-2 device,
		UCB$L_DEVDEPND2(R3), 5$	;  then handle it the hard way
	PUT_N2	#20, <"WDD-MODE_SENSE:  SCSI2 ">
	BBC	#OPTIC$V_MULTI, -	; If this isn't a multifunction device,
		UCB$L_DEVDEPND2(R3), 5$	;  then continue
	PUT_N2	#20, <"WDD-MODE_SENSE:  MULTI-FUNCTION DEVICE ">
	BICW	#OPTIC$M_WORM, - 	; Init flags
		UCB$L_DEVDEPND2(R3)	;  ...
	BISW	#OPTIC$M_RW, -		; to RW mode
		UCB$L_DEVDEPND2(R3)	;  ...
	CMPB	1(R2), #2		; Is the media type WORM?
	BNEQ	5$			; If neq, no -- assume rewritable
	PUT_N2	#20, <"WDD-MODE_SENSE:  MEDIA TYPE is WORM ">
	XORW2	#<OPTIC$M_RW!OPTIC$M_WORM>, - ; Set flags
		UCB$L_DEVDEPND2(R3)	; to WORM mode

  5$:	BISL	#UCB_M_WD_WRTLCK,-	; Assume volume write-locked
		UCB$L_DEVDEPND2(R3)
	BISL	#DEV$M_SWL,-		; Set device soft. write lock.
		UCB$L_DEVCHAR(R3)	; ....

	CMPB	#OPTIC$C_CDROM,-	; Is this a CDROM?
		UCB$B_FEX(R3)		; ...
	BEQL	60$			; Branch if yes -- write locked

;
;  For the Kodak, we need to check byte 2 on pagecode 24.  There are 4 6-byte
;  "pages" before pagecode 24, so we need to check byte 26 from the beginning
;  of the sense data.
;
	CMPB	#OPTIC$C_KODAK_6800,-	; Is this the Kodak 6800?
		UCB$B_FEX(R3)		; ...
	BNEQ	40$			; Branch if not
	ADDL3	SCDRP$L_TRANS_CNT(R5),-	; Compute the end of the sense data
		R2,R0			;
	MOVZBL	3(R2),R1		; Get length of block descriptor
	MOVAB	4(R2)[R1],R2		; Skip the header and block descriptor
					;  Point to first page
 31$:	CMPB	#^X24,(R2)		; Is this page 24?
	BEQL	36$			; Yes, branch
	MOVZBL	1(R2),R1		; Get length of this page
	MOVAB	2(R2)[R1],R2		; Skip the header and block
	CMPL	R2,R0			; Past the end?
	BGEQU	59$			; Yes, leave disk right locked, branch
	BRB	31$			; Try again

 36$:	BITB	#7,26(R2)		; Are any of the Kodak WP bits set?
	BNEQ	60$			; Branch if not
	BRB	50$			; Branch to clear write enabled bit
;
;  Bit 7 (the high bit) of byte 2 of the MODE SENSE data is the Write-Protect
;  (WP) bit.  If set to one, the currently accessible medium is write protected.
;  If 0, the medium is enabled for writes.
;
 40$:	BBS	#7, 2(R2), 60$		; Go if WP bit is set

 50$:	BICL	#UCB_M_WD_WRTLCK,-	; else show write enabled
		UCB$L_DEVDEPND2(R3)	; and...
	BICL	#DEV$M_SWL,-		; Clear device soft. write lock.
		UCB$L_DEVCHAR(R3)	; ....

 59$:	MOVL	#SS$_NORMAL,R0		; Show success
 60$:	BSBW	CLEANUP_CMD		; Clean-up SCDRP
	RSB				; Return to caller

	.DISABLE LSB			; MODE_SENSE

	.SBTTL	MODE_SELECT - Set Mode Parameters of SCSI Unit
;++
; MODE_SELECT
;
; This routine set the EBC bit to enable blank check.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0 - VMS status code
;
;	R1,R2	- Destroyed
;	All other registers preserved
;--
MODE_SELECT::
	.ENABLE	LSB			; MODE_SELECT

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

	MOVAL	MODE_SELECT_CMD,R2	; allow removal
	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BLBC	R0,40$			; Branch on error
	MOVZBL	#200,R1			; Allocate space for parameter list
	BSBW	ALLOC_SYS_BUF		; allocate system buffer
	BLBC	R0,30$			; Branch on error
	MOVL	SCDRP$L_CMD_PTR(R5),R0	; Point to the command buffer

	MOVZBL	UCB$B_FEX(R3), R1	; Get device type
	CMPB	R1, #OPTIC$C_SONY_F521	; Sony F521 drive?
	BEQL	41$			; Yes, branch
	CMPB    R1, #OPTIC$C_HP_1716C   ; DEC/HP drive?
	BEQL    80$                     ; Quick branch
	; (RWZ52 doesn't follow SCSI-2 attributes as defined by this driver)
	BBC	#OPTIC$V_SCSI2, -	; If we're SCSI 2, then
		UCB$L_DEVDEPND2(R3), 4$	;  assume some intelligence
	MOVB	#4,8(R0)		; Parameter list length
	MOVL	SCDRP$L_SVA_USER(R5),R0 ; Get address of mode select buffer
	CLRW	(R0)+			; Zap the first word
	MOVZBW	#^X<01>,(R0)+		; Set EBC and Block Descr Length
	BRW	20$			; Proceed

  4$:	DISPATCH R1, TYPE=B, <-		; Dispatch according to drive type
		<OPTIC$C_HP_1716C,	80$>,-	; Is it a DEC/HP 2X drive?
		<OPTIC$C_RWZ53	,	90$>,-	; Is it a DEC/HP 4X drive?
		<OPTIC$C_SONY_WDD_931,	70$>,-	; Is it a SONY drive?
		<OPTIC$C_KODAK_6800,	50$>,-	; Is it a Kodak?
		<OPTIC$C_GIGADISC,	60$>,-	; Is it a GIGADISC?
		<OPTIC$C_GIGADISC_JB,	60$>,-	; Is it a GIGADISC JB?
		<OPTIC$C_LMS_LD520,	5$>,-	; Is it a LD520?
		<OPTIC$C_PIONEER_7001,	5$>,-	; Is it a PIONEER?
		<OPTIC$C_PIONEER_5001,	5$>>	; Is it a PIONEER?

  41$:	MOVL	#SS$_NORMAL, R0		; Success
	BRW	30$			;
;
;  Pioneer-specific stuff
;
  5$:	MOVL	#SS$_NORMAL, R0		; Assume success
	BBS	#OPTIC$V_RW, -		; Don't try it if the drive
		UCB$L_DEVDEPND2(R3), 30$;  is in rewritable mode
	MOVL	SCDRP$L_CMD_PTR(R5),R0	; Point to the command buffer
	MOVB	#7,8(R0)		; Parameter list length is 7
	BISB	#^X10,5(R0)		; Set PF (Page Format) bit to 1 because
					; ...  the manual says to
	MOVL	SCDRP$L_SVA_USER(R5),R0 ; Get address of mode select buffer
	CLRW	(R0)+			; Zap the first word
	MOVZBW	#^X<01>,(R0)+		; Set EBC and Block Descr Length
;
; Enable read blank check for Pioneer drive -- Error Recovery Parameter 2
;
	MOVB	#^X<20>,(R0)+		; Page code 20
	MOVB	#^X<01>,(R0)+		; Page length = 1 byte
	MOVB	#^X<01>,(R0)+		; Enable read blank check
	BRW	20$			; Skip over SONY stuff
;
; Kodak-specific -- Disable multi-buffer caching in the drive.
;
 50$:	MOVB	#6,8(R0)		; Parameter list length is 6
	BISB	#^X10,5(R0)		; Set PF (Page Format) bit to 1 because
					; ...  the manual says to
	MOVL	SCDRP$L_SVA_USER(R5),R0 ; Get address of mode select buffer
	MOVB	#^X<26>,(R0)+		; Page code 26
	MOVB	#^X<04>,(R0)+		; Page length = 4 bytes
	MOVB	#^X<00>,(R0)+		; Disable multi-buffering
	BRW	20$			; Exit
;
; Gigadisc-specific -- enable blank check and automatic write reallocate
; *NOTE* Following format requires controller in SCSI 2 mode.
;
 60$:	MOVB	#16,8(R0)		; Parameter list length is 16
	BISB	#^X10,5(R0)		; Set PF (Page Format) bit to 1 because
					; ...  the manual says to
	MOVL	SCDRP$L_SVA_USER(R5),R0 ; Get address of mode select buffer
	CLRB	(R0)+			; Reserved
	CLRB	(R0)+			; Medium type
	MOVB	#^X<01>,(R0)+		; Enable blank check
	CLRB	(R0)+			; Block descriptor length
	MOVB	#^X<01>,(R0)+		; Page code 01
	MOVB	#^X<0A>,(R0)+		; Parameter list length
	MOVB	#^X<00>,(R0)+		; Disable Auto Write Reallocation
					;  Set error handling to use read
					;  retries, only report unrecoverable
					;  errors
	CLRL	(R0)+			; Clear rest of the page
	CLRL	(R0)+			;
	CLRB	(R0)+			;
	BRB	20$			; Exit
;
; Sony WDD-931-specific -- enable blank check and automatic write reallocate
;
;	EALT (Enable Alternation) - Enables reallocation during WRITE VERIFY
;	EVRF (Enable Verify) - Enables the VERIFY function for WRITE commands
;	DTRE (Disable Transfer on Read Error) - Stop on unreadable block
;
 70$:	BISB	#^X10,5(R0)		; Set PF (Page Format) bit to 1
	MOVL	SCDRP$L_SVA_USER(R5),R0 ; Get address of mode select buffer
	CLRB	(R0)+			; Reserved
	MOVB	#^X<02>,(R0)+		; Medium type must be 2
	MOVB	#^X<01>,(R0)+		; Enable blank check
	CLRB	(R0)+			; Block descriptor length

	MOVB	#^X<01>,(R0)+		; Page code 01
	MOVB	#^X<0A>,(R0)+		; Parameter list length
	MOVB	#^X<C2>,(R0)+		;  Enable Auto Write Reallocation - 80
					;  Enable Auto Read Reallocation - 40
					;  Enable DTE - disable transfer on err
					;  Set error handling to use read
					;  retries, only report unrecoverable
					;  errors
	CLRL	(R0)+			; Clear rest of the page
	CLRL	(R0)+			;
	CLRB	(R0)+			;

	MOVB	#^X<08>,(R0)+		; 0 Page code 08
	MOVB	#^X<0A>,(R0)+		; 1 Parameter list length
	MOVB	#^X<01>,(R0)+		; 2 Disable read-ahead cache (bit 0=1)
	MOVB	#^X<00>,(R0)+		; 3 Demand Read Retention = default
	MOVB	#^X<20>,(R0)+		; 4 Disable Prefetch = 8K blocks
	MOVB	#^X<00>,(R0)+		; 5  ( = 8 MB)
	MOVB	#^X<00>,(R0)+		; 6 Minimum Prefetch
	MOVB	#^X<01>,(R0)+		; 7  = 1
	MOVB	#^X<00>,(R0)+		; 8 Maximum Prefetch
	MOVB	#^X<40>,(R0)+		; 9  = 64
	CLRW	(R0)+			; 10-11 Maximum Prefetch Ceiling = 0

	MOVB	#^X<00>,(R0)+		; 0 Page code 00
	MOVB	#^X<0E>,(R0)+		; 1 Parameter list length
	MOVB	#^X<07>,(R0)+		; 2 Set EALT, EVRF, DTRE
	MOVB	#^X<00>,(R0)+		; 3
	MOVB	#^X<03>,(R0)+		; 4 Enable autostart, laser save=8 min
	MOVB	#^X<08>,(R0)+		; 5 Read data buffer size = 512 KB
	MOVB	#^X<08>,(R0)+		; 6 Write data buffer size = 512 KB
	MOVB	#^X<00>,(R0)+		; 7 Disable alternation warning
	CLRQ	(R0)+			; 8-15 Allow writing to all sectors

	SUBL	SCDRP$L_SVA_USER(R5),R0 ; Get size of area
	MOVL	SCDRP$L_CMD_PTR(R5),R1	; Point to the command buffer
	MOVB	R0, 8(R1)		; Parameter list length
	BRB	20$			; Exit

;
; DEC/HP 2X drives.
;
 80$:	MOVZBL	#RWZ52_MSD_LENGTH, R1	; Get the length
	MOVL	SCDRP$L_CMD_PTR(R5),R2	; Point to the command buffer
	MOVB	#^X11, 5(R2)		; Set PF and save in non-volatile RAM
	MOVB	R1, 8(R2)		; Set the length
	MOVL	SCDRP$L_SVA_USER(R5),R0 ; Get address of mode select buffer
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers
	MOVAB	RWZ52_MODE_SELECT_DATA, R3 ; Get PIC address of mode select data
	MOVC3	R1, (R3), (R0)		; Store data in buffer
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Restore registers
	BRW	20$			; Exit
;
; DEC/HP 4X drives.
;
 90$:	MOVZBL	#RWZ53_MSD_LENGTH, R1	; Get the length
	MOVL	SCDRP$L_CMD_PTR(R5),R2	; Point to the command buffer
	MOVB	#^X11, 5(R2)		; Set PF and save in non-volatile RAM
	MOVB	R1, 8(R2)		; Set the length
	MOVL	SCDRP$L_SVA_USER(R5),R0 ; Get address of mode select buffer
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers
	MOVAB	RWZ53_MODE_SELECT_DATA, R3 ; Get PIC address of mode select data
	MOVC3	R1, (R3), (R0)		; Store data in buffer
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Restore registers
;	BRW	20$			; Exit

 20$:	BSBW	SEND_COMMAND		; Send the SCSI command
 30$:	BSBW	CLEANUP_CMD		; Clean-up SCDRP
 40$:	RSB				; Return to caller

	.DISABLE LSB			; MODE_SELECT

	.SBTTL	PREVENT_ALLOW_REMOVE - Send prevent/allow media removal command
;++
; PREVENT_ALLOW_REMOVE
;
; This routine is called to enable or disable the eject button.
;
; INPUTS:
;
;	R0	- 0 allow media removal, 1 prevent media removal
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;
;			SS$_NORMAL   - I/O completed successfully.
;			SS$_ILLSEQOP - I/O failed, bad sense key.
;			SS$_IVSTSFLG - Invalid SCSI status returned.
;			SS$_OPINCMPL - I/O failed, insufficient data returned.
;
;	R1,R2	- Destroyed
;	All other registers preserved
;--
PREVENT_ALLOW_REMOVE::

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

	PUT_N2	#30, <"WDD-PREVENT_ALLOW_REMOVE: ">

	CMPB	#OPTIC$C_KODAK_6800,-	; Is this the Kodak 6800?
		UCB$B_FEX(R3)		; ...
	BEQL	5$			; Branch if Kodak
	CMPB	#OPTIC$C_CDROM,-	; Is this a CD-ROM?
		UCB$B_FEX(R3)		; ...
	BNEQ	10$			; Branch if not CDROM
  5$:	MOVL	#SS$_NORMAL,R0		; Set success status
	BRB	50$			; Branch to return
 10$:	TSTL	R0			; Allow removal?
	BEQL	20$			; yes
	MOVAL	PREVENT_REMOVAL_CMD,R2	; no - prevent removal
	BRB	30$			; go on
 20$:	MOVAL	ALLOW_REMOVAL_CMD,R2	; allow removal
 30$:	BSBW	SETUP_CMD		; Perform setup for SCSI command
	BLBC	R0,40$			; Branch on error
	BSBW	ALLOC_SYS_BUF		; allocate system buffer
	BLBC	R0,40$			; Branch on error
	BSBW	SEND_COMMAND		; Send the SCSI command
	PUT_N2	#30, <"WDD-PREVENT_ALLOW_REMOVE: SEND_COMMAND Status: %x ">,R0
 40$:	BSBW	CLEANUP_CMD		; Clean-up SCDRP
 50$:	RSB				; Return to caller

	.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.
;
; INPUTS:
;
;	R3	- UCB address
;	R4	- SPDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;			SS$_DEVICERR - Select problem
;			SS$_ILLSEQOP - I/O operation failed.
;	R1,R2	- Destroyed
;	All other registers preserved
;--
SEND_COMMAND::
	.ENABLE LSB			; SEND_COMMAND

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

	PUT_N2	#24,<"WDD-SEND_COMMAND: UCB: %x SPDT: %x SCDRP: %x ">,R3,R4,R5
;
;  Save the SCSI command buffer in the UCB for debugging purposes.
;
	MOVL	SCDRP$L_CMD_PTR(R5),R0		; Get SCSI command buffer addr
	CMPB	#SCSI_C_REQUEST_SENSE,4(R0)	; Is it a REQUEST SENSE?
	BEQL	20$				; Branch if yes
	CMPB	#SCSI_C_REZERO_UNIT,4(R0)	; Is it REZERO UNIT?
	BEQL	20$				; Branch if yes
	MOVAL	UCB_L_WD_SAVCMD(R3),R2		; Get address of save buffer
	MOVL	(R0)+,R1			; Get length of SCSI cmd
	MOVL	R1,(R2)+			; Save length away
 10$:	MOVB	(R0)+,(R2)+			; Copy byte of SCSI command
	SOBGTR	R1,10$				; Loop until finished

 20$:	BBC	#OPTIC$V_LONG_TMO, -		; Check for long timeout in
		UCB$L_DEVDEPND2(R3),30$		;  flags word for this drive
	CMPL	#LONG_TIMEOUT, -		; Is the timeout at least the minimum?
		SCDRP$L_DMA_TIMEOUT(R5)		;
	BLEQ	30$				; If LEQ, it's OK
;;	MOVL	#LONG_TIMEOUT, -		; Set minimum timeout
;;		SCDRP$L_DMA_TIMEOUT(R5)		;
	MOVL	#LONG_TIMEOUT, -		; Set minimum timeout
		SCDRP$L_DISCON_TIMEOUT(R5) ;
 30$:	SPI$SEND_COMMAND			; Send the SCSI command
;*********************debugging*********************************
	PUSHL	R0
	MOVL	SCDRP$L_CMD_PTR(R5),R0
	MOVZBL	4(r0),D1
	MOVZBL	5(r0),D2
	MOVZBL	6(r0),D3
	MOVZBL	7(r0),D4
	MOVZBL	8(r0),D5
	MOVZBL	9(r0),D6
	MOVZBL	10(r0),D7
	MOVZBL	11(r0),D8
	MOVZBL	12(r0),D9
	MOVZBL	13(r0),D10
	MOVZBL	14(r0),D11
	MOVZBL	15(r0),D12
;
; Display 6 byte command, or 1st 6 bytes of command
;
31$:	PUT_N2	#30,<"WDD-SEND_COMMAND: SCSI_CMD: %x %x %x %x %x %x ">,-
		D1, D2, D3, D4, D5, D6
	CMPL	#10,(r0)
	BEQL	32$
	CMPL	#12,(r0)
	BEQL	33$
	BRW	34$
;
; Display bytes 7-10 byte command
;
32$:	PUT_N2	#30,<"WDD-SEND_COMMAND:           %x %x %x %x %x %x ">,-
	D7, D8, D9, D10
	BRW	34$
;
; Display bytes 7-12 byte command
;
33$:	PUT_N2	#30,<"WDD-SEND_COMMAND:           %x %x %x %x %x %x ">,-
		D7, D8, D9, D10, D11, D12
	BRW	34$

34$:	POPL	R0
	PUT_N2	#30,<"WDD-SEND_COMMAND: SPI$SEND_COMMAND VMS status: %x ">,R0
;
; Case 2:
;
	cmpl	r0,#ss$_timeout
	beql	55$
;*********************debugging*********************************


	BLBC	R0,SEND_CMD_EXIT		; If port failed, return
	MOVL	SCDRP$L_CMD_PTR(R5),R1		; Get SCSI command buffer addr
	CMPB	SCSI_CMD_B_BYTE0(R1),#1		; Rezero_unit command?
	BEQL	SEND_CMD_EXIT
	MOVZBL	@SCDRP$L_STS_PTR(R5),R1		; Get SCSI status byte
	BICB	#SCSI$M_STS,R1			; Clear reserved, vendor-unique bits
	BNEQ	50$				; Check bad status

	BRW	SEND_CMD_EXIT			; Return to caller if good status
;
; 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.
;
 50$:
;*********************debugging*********************************
	PUT_N2	#30, -
	<"WDD-SEND_COMMAND: SCSI status %x on unit %d ">,R1, UCB$W_UNIT(R3)
;*********************debugging*********************************

	CMPB	R1,#2				; Check condition status?
	BEQL	110$				; Branch if so
;
; If the status returned for the last command was anything other than
; check condition, log an error and return a status of SS$_IVSTSFLG to
; indicate that command failed and that there is no request sense data.
;
	CMPB	R1,#8				; Device busy?
	BNEQ	90$				; No, then go for generic

	PUT_N2	#30, -
	<"WDD-SEND_COMMAND: Dev. busy on unit %d ">, UCB$W_UNIT(R3)
;
; 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.
;
; Case 2:
; SPI$SEND_COMMAND returns SS$_TIMEOUT status.
;
 55$:	ADDL3	UCB_L_MAX_BUSY(R3),-		; Calculate busy timeout time
		G^EXE$GL_ABSTIM,-		;
		UCB_L_BUSY_TIME(R3)		;

	PUT_N2	#30,<"WDD-SEND_COMMAND: Set %d second timeout on unit %d ">,-
		UCB_L_MAX_BUSY(R3), UCB$W_UNIT(R3)

 60$:	WD_WAIT	SECONDS=#3

 70$:	CMPL	UCB_L_BUSY_TIME(R3),-		; Had device been busy for an excessive
		G^EXE$GL_ABSTIM			; amount of time
	BGTRU	60$				; Branch if not

	CLRL	UCB_L_BUSY_TIME(R3)		; Indicate device no longer busy

	PUT_N2	#30,<"WDD-SEND_COMMAND: Reset bus after timeout on unit %d ">,-
		UCB$W_UNIT(R3)

	MOVL	SPDT$L_PORT_UCB(R4), R2		; Get port's UCB address
	SPI$RESET_SCSI_BUS			; Reset the SCSI bus to attempt to
						; bring the device back to life
	BSBW	REQUEST_SENSE			; Send a request sense command
	BSBW	CLEANUP_CMD
	CMPL	#SS$_MEDOFL,R0
	BNEQ	75$
	BSBW	REQUEST_SENSE			; Send a request sense command
	BSBW	CLEANUP_CMD
 75$:

	MOVL	#SS$_MEDOFL,R0			; Set and error code
	BRW	SEND_CMD_EXIT

;
; Some other error.
;
 90$:	MOVL	#SS$_CTRLERR,R0			; Return a generic status code
100$:	LOG_ERROR -				; Go log the error
		STATUS=R0,-			;  Port status is in R0
		ERROR=EXTND_SENSE_DATA		;  Make look like extended sense
	BRB	SEND_CMD_EXIT			; return
;
; See if AUTOSENSE is valid and process.
;
110$:	MOVL	R5,UCB_L_SCDRP_SAV2(R3)		; Save original SCDRP address
	BBC	#scdrp$v_flag_asense_valid,-	; Branch if auto sense
		scdrp$l_scsi_flags(r5),115$	; buffer is invalid
	movl	scdrp$ps_sense_buffer(r5),r2	; Get auto sense buf
	PUT_N2	#30,<"WDD-SEND_COMMAND: Auto sense buf: %x ">,r2
	brw	118$
;
; 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.
;
115$:	MOVL	SCDRP$PS_KPB(R5),-		; Save original KPB address
		UCB_L_KPB_SAV2(R3)
	WD_ALLOC_SCDRP 				; Allocate an additional SCDRP
	MOVL	UCB_L_KPB_SAV2(R3),-		; Store KPB address in new SCDRP
		SCDRP$PS_KPB(R5)
	BSBW	REQUEST_SENSE			; Send a request sense command

;***DEBUGG
	CMPL	#SS$_MEDOFL,R0
	BNEQ	117$
	BSBW	REQUEST_SENSE			; Send a request sense command
117$:
;***DEBUGG

	BLBC	R0,290$				; Branch on error
						; a VMS status code in R0
;
; Look at the results of the request sense to determine the exact nature
; of the event.
;
	MOVL	SCDRP$L_SVA_USER(R5),R2		; Get addr REQUEST SENSE DATA.
118$:	BICB3	#^XF0,SCSI_XS_B_ERR_CODE(R2),R0	; First check ERROR CODE.
						; In this case zero is good,
	BNEQ	290$				; but this is really device specific.
	BICB3	#^XF0,SCSI_XS_B_KEY(R2),R0	; Mask off SENSE KEY.

;***** DEBUGGING
	PUSHL	R1
	PUSHL	R3
	MOVZBL	SCSI_XS_B_ASC(R2),R1
	MOVZBL	SCSI_XS_B_ASQ(R2),R3
	PUT_N2	#30,<"WDD-SEND_COMMAND: Sense Key: %x, ASC: %x ASQ: %x ">,-
		R0,R1,R3
	POPL	R3
	POPL	R1
;***** DEBUGGING

;
; Depending on the value of the sense key, dispatch to the appropriate
; error recovery.
;
120$:	DISPATCH R0,TYPE=B,<-				; Dispatch according to SENSE K
		<SCSI_C_NO_SENSE,NO_SENSE>,-		; No sense data
		<SCSI_C_RECOVERED_ERROR,RECOVERED>,-	; Recovered error
		<SCSI_C_NOT_READY,NOT_READY>,-		; Device not ready
		<SCSI_C_MEDIUM_ERROR,MEDIA>,-		; Medium (parity) error
		<SCSI_C_HARDWARE_ERROR,HARDWARE_ERROR>,-; Hardware error
		<SCSI_C_ILLEGAL_REQUEST,ILLEGAL_REQUEST>,- ; Illegal request
		<SCSI_C_UNIT_ATTENTION,UNIT_ATTENTION>,-; Unit attention ( reset... )
		<SCSI_C_DATA_PROTECT,DATA_PROTECT>,-	; Data protection (write lock)
		<SCSI_C_BLANK_CHECK,BLANK_CHECK>,-	; Blank check
		<SCSI_C_VENDOR_UNIQUE,MISC>,-		; Vendor unique key
		<SCSI_C_COPY_ABORTED,DEVICE_ERROR>,-	; Copy operation aborted
		<SCSI_C_ABORTED_COMMAND,ABORTED_COMMAND>,- ; Command aborted
		<SCSI_C_EQUAL,DEVICE_ERROR>,-			; Data match
		<SCSI_C_VOLUME_OVERFLOW,DEVICE_ERROR>,- ; Write past physical end
		<SCSI_C_MISCOMPARE,DEVICE_ERROR>>	; Data mismatch
;
; Sense Key 1 == RECOVERED ERROR.
;
NO_SENSE:					; Same as RECOVERED ERROR
RECOVERED:
	PUT_N2	#30,<"WDD-SEND_COMMAND: No Sense or Recovered error (1) ">
	MOVL	#SS$_NORMAL,R0			; Show success
	MOVL	UCB_L_SCDRP_SAV2(R3),R1		; Restore original SCDRP address
	MOVL	SCDRP$L_TRANS_CNT(R1), R1	; Get # bytes actually xferred
	BRW	290$				;   and go return (no log)
;
; Sense Key 2 == NOT READY.
;
NOT_READY:
	PUT_N2	#30,<"WDD-SEND_COMMAND: Not Ready (2) ">

	CMPB	#^x83,SCSI_XS_B_ADDNL_CODE(R2)	; Is it the wrong mode?
	BNEQ	130$				; No, branch

	MOVL	UCB_L_SCDRP_SAV2(R3),R1		; Restore original SCDRP address
	BBS	#SCDRP$V_FLAG_ASENSE_VALID,-	; Branch if auto sense
		SCDRP$L_SCSI_FLAGS(R1),125$	; buffer is valid
	PUT_N2	#30,<"WDD-SEND_COMMAND: N-R Request sense cleanup ">

	BSBW	CLEANUP_CMD			; Clean up request sense command
125$:	BSBW	CHANGE_MODE			; Change mode
	WD_DEACTIVATE_SCDRP CLRSTK=YES		; Deactivate the SCDRP
	MOVL	UCB_L_SCDRP_SAV2(R3),R5		; Restore original SCDRP address
	MOVL	R5,UCB_L_SCDRP(R3)		; Copy it to the UCB
	BRW	20$				; Re-send the command

130$:	BICW	#UCB$M_VALID,UCB$L_STS(R3)	; Show volume invalid
	MOVL	#SS$_MEDOFL,R0			;  set error
	BRW	270$				;   and go return (log)
;
; Sense Key 3 == MEDIA ERROR.
;
MEDIA:
	PUT_N2	#30,<"WDD-SEND_COMMAND: Media Error (3) ">

	MOVL	#SS$_FORCEDERROR,R0		; Set error
;
; Since Optimem returns sense key 3 when attempting to read blank sectors,
; we must dig a little deeper into the sense data to get the actual cause for
; the error.  NOTE:  this code assumes that the Optimem is distinguishable by
; the fact that it returns an Additional Sense Length of 2 in the sense data
; packet.
;
; Also, the LD520 returns sense key 3 when reading an erased block on
; MO media.  This should be translated into a blank check for our purposes.
;
	ADDL2	#7,R2				; Look for more sense data
	CMPB	#2,(R2)+			; Is this an Optimem drive?
	BNEQ	140$				;  if neq no so go
	TSTB	(R2)+				; Point to Controller Fault Code
	CMPB	#^X<34>,(R2)			; Was it a blank check?
	BNEQ	190$				;  if neq no so go log it
	BRB	BLANK_CHECK			; Branch to return PARITY

140$:	MOVL	SCDRP$L_SVA_USER(R5),R2		; Get addr REQUEST SENSE DATA.
	CMPB	#OPTIC$C_KODAK_6800,UCB$B_FEX(R3) ; Is is a Kodak 6800?
	BNEQ	160$				; Branch if not
	CMPB	#^X<29>,18(R2)			; Spinup in wrong direction?
	BNEQ	150$				; No, branch
	MOVL	#SS$_BADRCT,R0			; Use this to mean spin
	BRW	BLANK_CHECK2			; direction is wrong

150$:	CMPB	#^X<F9>,18(R2)			; Is it a BLANK CHECK?
	BEQL	BLANK_CHECK			; Branch if yes

160$:	BBC	#OPTIC$V_RW, -			; RW mode?
		UCB$L_DEVDEPND2(R3), 180$	;  If not, skip this check
	CMPB	#OPTIC$C_LMS_LD520, UCB$B_FEX(R3) ; Is is an LMS LD520?
	BEQL	170$				; Yes, branch
	CMPB	#OPTIC$C_PIONEER_7001, UCB$B_FEX(R3) ; Is it a Pioneer drive?
	BNEQ	190$				; Branch if not
170$:	CMPB	#^X<11>,12(R2)			; Is it an erased block?
	BEQL	BLANK_CHECK			;

180$: 	MOVL	UCB_L_SCDRP_SAV2(R3),R1		; Restore original SCDRP address
	MOVL	SCDRP$L_TRANS_CNT(R1), R1	; Get # bytes actually xferred
	PUT_N2	#24,<"WDD-SEND_COMMAND: xfered bytes: %d ">,R1
190$:	BRW	280$				; Go return (log)

;
; Sense Key 4 == HARDWARE ERROR.
;
HARDWARE_ERROR:
	PUT_N2	#30,<"WDD-SEND_COMMAND: Hardware Error (4) ">

	CMPB	#OPTIC$C_SONY_WDD_931,-		; Sony drive?
		UCB$B_FEX(R3)			; ...?
	BEQL	220$				; If eql, yes

	CMPB	#OPTIC$C_LMS_LD4100,-		; Is this the LMS LD4100?
		UCB$B_FEX(R3)			; ...
	BEQL	200$				; Branch if yes

	CMPB	#OPTIC$C_LMS_LF4500,-		; Is this the LMS LD4100?
		UCB$B_FEX(R3)			; ...
	BEQL	200$				; Branch if yes

	CMPB	#OPTIC$C_KODAK_6800,-		; Is is a Kodak 6800?
		UCB$B_FEX(R3)			; ...?
	BNEQ	DEVICE_ERROR			; Unknown (fatal) error

	CMPB	#^X<D1>,18(R2)			; Spinup in wrong direction?
	BNEQ	DEVICE_ERROR			; Unknown (fatal) error
	BRB	240$				; Try it again

;
; Check for halter call or NO SEEK COMPLETE in LD4100/4500.
;
200$:	CMPB	#^X<02>, 12(R2)			; NO SEEK COMPLETE?
	BEQL	210$				; Yes -- try again
	CMPB	#^X<44>, 12(R2)			; Halter call?
	BNEQ	DEVICE_ERROR			; Unknown (fatal) error
	WD_WAIT	SECONDS=#30				; Wait for device to reset
210$:	LOG_ERROR -				; Log the error
		STATUS=#SS$_MCNOTVALID,-	; ...  "Microcode not valid"
		ERROR=EXTND_SENSE_DATA		; ...  Make it extended sense
	BRB	240$				; Try it again

220$:	CMPB	#^x11, 12(R2)			; Really a media error?
	BNEQ	DEVICE_ERROR			; Unknown (fatal) error

230$:	MOVL	#SS$_FORCEDERROR, R0		; Set for a retry
	CLRL	R1				; Byte count = 0
	BRW	280$				; Exit

;
; Sense Key B == ABORTED COMMAND.
;
ABORTED_COMMAND:
	PUT_N2	#30,<"WDD-SEND_COMMAND: Aborted Command (B) ">

	CMPB	#OPTIC$C_KODAK_6800,UCB$B_FEX(R3) ; Is is a Kodak 6800?
	BNEQ	DEVICE_ERROR			; Branch if not
240$:	DECL	UCB$L_ERTCNT(R3)		; Any retries left?
	BLEQ	DEVICE_ERROR			; Nope -- log it
	INCL	UCB$L_ERTCNT(R3)		; Undo the DECB
	MOVL	#SS$_RETRY, R0			; Retry the operation
	CLRL	R1				; Byte count = 0
	BRW	290$				; Continue (no log)

;
; Sense Key A == COPY ABORTED.
; Sense Key C == EQUAL
; Sense Key D == VOLUME OVERFLOW
; Sense Key E == MISMATCH
; Sense Key F == <RESERVED>
;
DEVICE_ERROR:
	PUT_N2	#30,<"WDD-SEND_COMMAND: Device errors (A,C,D,E, or F) ">

;
; If device is a jukebox, return SS$_CTRLERR error instead of fatal drive
;  error. Use this error code for now to match the RS-232 type devices.
;
	MOVL	#SS$_DRVERR,R0			; Set error assuming drive
	BBC	#OPTIC$V_JUKEBOX,-		; Is it a jukebox?
		UCB$L_DEVDEPND2(R3),250$	; No, branch
	CMPB	#OPTIC$C_LMS_LF4500,-		; LMSI 4500?
		UCB$B_FEX(R3)			;
	BEQL	250$				; Yes, branch
	MOVL	#SS$_CTRLERR,R0			; Show robot failed.
250$:	BRW	260$				;  and go return (log)
;
; Sense Key 5 == ILLEGAL REQUEST.
;
ILLEGAL_REQUEST:
	PUT_N2	#30,<"WDD-SEND_COMMAND: Illegal Request (5) ">

	MOVL	#SS$_DEVREQERR,R0		; Set error
	CMPB	#OPTIC$C_CDROM, UCB$B_FEX(R3)	; CD-ROM?
	BEQL	290$				; Yes - don't log errors that
						;   are probably musical CDROMs
	CMPB	#OPTIC$C_CD_CHANGER,-		; CD Changer?
		UCB$B_FEX(R3)			;
	BNEQ	270$				; No -- log it
;
; Check for Pioneer CD Changer error (Media not present)
;
	CMPB	#^X<3B>,12(R2)			; Media problem
	BNEQ	270$
	CMPB	#^X<0E>,13(R2)			; No media present?
	BEQL	290$				; Yes, do not log error.
	BRW	270$
;
; Sense Key 6 == UNIT ATTENTION.
;
UNIT_ATTENTION:
	PUT_N2	#30,<"WDD-SEND_COMMAND: Unit Attention (6) ">

	BICW	#UCB$M_VALID,UCB$L_STS(R3)	; Show volume invalid
	MOVL	#SS$_MEDOFL,R0			; Set error
	BRW	290$				;  and go return (no log)
;
; Sense Key 7 == DATA PROTECT.
;
DATA_PROTECT:
	PUT_N2	#30,<"WDD-SEND_COMMAND: Data Protect (7) ">

	MOVL	#SS$_WRITLCK,R0			; Set error
	CLRL	R1				;  zap byte count
	BRW	290$				;   and go return (no log)
;
; Sense Key 8 == BLANK CHECK.
;
BLANK_CHECK:
	PUT_N2	#30,<"WDD-SEND_COMMAND: Blank Check (8) ">

	MOVL	#SS$_PARITY,R0			; Set error
BLANK_CHECK2:
	MOVL	UCB_L_SCDRP_SAV2(R3),R1		; Restore original SCDRP address
	MOVL	SCDRP$L_TRANS_CNT(R1), R1	; Get # bytes actually xferred
	BRW	290$				;  else go return (no log)

;
; Sense Key 9 == <RESERVED>.
;
; For Optimem drives, do a SCSI rezero unit to reset from fault condition.  Then
; return medium offline status so that mount verification has a chance.
; *** This is a special case in SEND_COMMAND processing, if not handled and
; *** the REZERO UNIT command encountered errors this code could loop until
; *** all the SCDRP's were exhausted which would crash the system. The way
; *** it is currently handled is to check for REZERO UNIT command in
; *** SEND_COMMAND and do not attempt any type of error processsing.
;
MISC:
	PUT_N2	#30,<"WDD-SEND_COMMAND: Misc or Reserved (9) ">

;xxx;	BSBW	REZERO_UNIT			; Rezero the unit
;xxx;	CMPW	R0, #SS$_NORMAL			; Did it work?
;xxx;	BNEQ	270$				;  if neq no so go

	MOVL	#SS$_MEDOFL,R0			;   else set error
	BRB	270$				;    and go return (log)

260$:
270$:	CLRL	R1				; Zap byte count

280$:	LOG_ERROR -				; Go log the error
		STATUS=R0,-			; ...  Port status is in R0
		ERROR=EXTND_SENSE_DATA		; ...  Make it extended sense

290$:	PUSHL	R1				; Save byte count
	MOVL	UCB_L_SCDRP_SAV2(R3),R1		; Restore original SCDRP address
	BBS	#SCDRP$V_FLAG_ASENSE_VALID,-	; Branch if auto sense
		SCDRP$L_SCSI_FLAGS(R1),300$	; buffer is valid
	PUT_N2	#30,<"WDD-SEND_COMMAND: Request sense cleanup ">

	BSBW	CLEANUP_CMD			;
	WD_DEACTIVATE_SCDRP CLRSTK=YES		; Deactivate the SCDRP
300$:	POPL	R1				; Restore byte count
	MOVL	UCB_L_SCDRP_SAV2(R3),R5		; Restore original SCDRP address
	MOVL	R5,UCB_L_SCDRP(R3)		; Copy it to the UCB

SEND_CMD_EXIT:
	PUT_N2	#24, <"WDD-SEND_CMD_EXIT: ">

	RSB			; Return to caller

	.DISABLE LSB			; SEND_COMMAND

	.SBTTL	INIT_SCDRP	- Initialize an SCDRP
;++
; INIT_SCDRP
;
; This routine initializes an SCDRP.
; The entire SCDRP is zeroed and various fields are initialized.
;
; INPUTS:
;
;	R3	- UCB address
;	R5	- SCDRP address
;
; OUTPUTS:
;	SCDRP$L_UCB	- UCB address
;	SCDRP$L_IRP	- IRP address
;	SCDRP$L_CDT	- SCDT address
;	SCDRP$L_SCSI_FLAGS - Initialized
;	SCDRP$L_SCSIMSGI_PTR - Initialized
;	SCDRP$L_SCSIMSGO_PTR - Initialized
;	SCDRP$L_CL_SWD_PTR - Initialized
;--

INIT_SCDRP::
	.JSB_ENTRY INPUT=<R3,R4,R5>,PRESERVE=<R0,R1,R2,R3,R4,R5>

	PUSHR	#^M<R3,R5>		; Preserve registers
	MOVC5	#0,(SP),#0,-		; Initialize the SCDRP
		#SCDRP$C_LENGTH-12,-	;
		12(R5)			;
	POPR	#^M<R3,R5>		; Restore registers

;
; from Jim Dunham start
;
	MOVW	#SCDRP$K_LENGTH, -	; Setup SCDRP length field
		SCDRP$W_SCDRPSIZE(R5)	;
	MOVB	#DYN$C_SCDRP, -		; Setup SCDRP structure
		SCDRP$B_CD_TYPE(R5)	;
;
; from Jim Dunham end
;
	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 SCDT address in SCDRP
		SCDRP$L_CDT(R5)		;
	RSB

	.SBTTL	ALLOC_POOL	- Allocate a block of nonpaged pool
;++
; ALLOC_POOL
;
; This routine allocates a block of nonpaged 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 of 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
; alignment purposes (some blocks are used as IRPs, which are placed on
; self-relative queues and require quadword alignment.)
;
; If an allocation failure occurs, the thread is stalled and wakes up once a
; second to retry the allocation.
;
; INPUTS:
;
;	R1	- Size of block to allocate
;	R3	- UCB address
;	R5	- KPB 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 preserved
;--

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

	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$:	ADDL	#7, R1			; Make it a quadword increment
	BICL	#7, R1			; ...
 15$:	PUSHL	R1			; Save allocation length
	JSB	G^EXE$ALONONPAGED	; Allocate a block
	BLBC	R0,20$			; Branch if error
	ADDL	#4, SP			; Remove allocation length from stack
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>	; Save registers
	MOVC5	#0,(SP),#0,R1,(R2)	; Initialize the packet
	POPR	#^M<R0,R1,R2,R3,R4,R5>	; Restore registers
	MOVL	R1,(R2)			; Save size of block
	ADDL	#8, R2			; Skip length
	RSB				; Return to caller
;+
; A pool allocation failure occurred. Come back once a second and retry the
; operation until successful.
;-
 20$:	KP_STALL_FORK_WAIT KPB=R5	; Wait a second
	KP_STALL_FORK_WAIT KPB=R5	; Wait a second
	POPL	R1			; Restore allocation length
	BRW	15$			; Try again

	.SBTTL	DEALLOC_POOL	- Deallocate a block of nonpaged pool
;++
; DEALLOC_POOL
;
; This routine deallocates a block of nonpaged 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 preserved
;--

DEALLOC_POOL::
	.JSB_ENTRY INPUT=<R0>,PRESERVE=<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
	RSB

	.SBTTL	SETUP_CMD	- Common setup for all SCSI commands
;++
; SETUP_CMD
;
; This routine performs common setup prior to the sending of a SCSI command.
; Setup 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, and filling in various SCDRP fields which must be set for
; SPI$SEND_COMMAND but which are NOT related to the buffer(s) involved in the
; transfer.
;
; 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
;	R3	- UCB address
;	R4	- PDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	R0	- Status
;	R1	- DMA length
;	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_DMA_TIMEOUT - Time in seconds for a DMA timeout.
;	SCDRP$L_DISCON_TIMEOUT - Time in seconds for a disconnect to time out.
;
;--
SETUP_CMD::

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

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

	MOVZBL	(R2),R1			; Get size SCSI command
	ADDL	#SCSI_CMD_BUF_OVHD,R1	; Add in command buffer overhead
	PUSHL	R2			; Save R2
	SPI$CMD_BUFFER_ALLOC		; Allocate a command buffer
	BLBS	R0,10$			; Go on success
	POPL	R2			;   Clean ucb stack on error
	BRB	30$			;   Go on error
10$:	INCL	UCB_L_WD_CMDBUFS(R3)	; incr # command buffers allocated
	MOVL	R2,SCDRP$L_CMD_BUF(R5)	; Save address of command buffer
	MOVL	R2,R1			; Copy command buffer address
	POPL	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
	MOVB	(R2)+,(R1)+		; Copy the command code byte
	MOVB	(R2)+,(R1)		; Copy byte 1
	BISB	UCB_B_LUN(R3),(R1)+	; Save the LUN in the command
	SUBL2	#2,R0			; Subtract 2 bytes from total cmd length
20$:	MOVB	(R2)+,(R1)+		; Copy a byte of SCSI command
	SOBGTR	R0,20$			; Repeat for entire SCSI command
;+
; There is a dependency here that the format of the SCSI_COMMAND record
; does not change.
;
; Get length of DMA transfer.
;
; Set SCDRP$IS_STS bit to appropriate DMA direction.
;
; Copy the per command timeout values from the SCSI_CMD block to the
; SCSI Class Driver Request Packet.
;-
	MOVZWL	(R2)+,R1		; get DMA buffer length
	INSV	(R2),#IRP$V_FUNC,#1,-	; Set/clear FUNC bit to indicate READ/
		SCDRP$IS_STS(R5)	; WRITE function
	INCL	R2			; Get address past DMA direction
	MOVL	(R2)+,-			; Time in seconds for a DMA timeout.
		 SCDRP$L_DMA_TIMEOUT(R5)
	MOVL	(R2),-			; Disconnect timeout in seconds.
		 SCDRP$L_DISCON_TIMEOUT(R5)
	MOVL	UCB$L_IRP(R3),R2	; Get address of IRP
	MOVL	IRP$L_FUNC(R2),-	; Copy function code and modifiers
		SCDRP$L_FUNC(R5)
	MOVL	#SS$_NORMAL,R0		; Set success status
30$:	RSB


	.SBTTL	MAP_USER_BUF	- Map the user buffer to system space
;+
; MAP_USER_BUF	- Map the user buffer to system space
;
; During the STARTIO operation in the class driver, the user's QIO parameters
; must be copied from the UCB to SCDRP (SCSI Class Driver Request Packet).
; The user data is then mapped, the SCSI CMD packet is allocated, and the
; command is sent to a target, over the connection established during UNIT INIT.
;
; Note that the UCB values are used rather than the IRP values, because the
; UCB values are updated during partial read/write operations and the IRP
; values are not.
;-
; INPUTS:
;
;	R1	- Number of bytes to transfer
;
; OUTPUTS:
;
;	R0	- Status
;	R1	- Number of bytes to transfer
;	R2	- Destroyed
;
;	SCDRP$L_BCNT		- Number of bytes in transfer
;	SCDRP$L_MEDIA		- Address of block on disk to be accesses
;	SCDRP$L_SVAPTE		- SVAPTE of user buffer
;	SCDRP$L_BOFF		- Byte offset of user buffer
;	SCDRP$L_PAD_BCNT	- Set to zero, no padding required
;	SCDRP$L_SCSI_FLAGS	- SCDRP$M_BUFFER_MAPPED is set
;	SCDRP$L_FUNC		- function specified in IRP
;
;--
;MAP_USER_BUF::
;	.JSB_ENTRY INPUT=<R1,R3,R4,R5>,OUTPUT=<R0,R1>
;
;	MOVL	R1,SCDRP$L_BCNT(R5)	; save number of bytes to transfer
;;;;	MOVL	UCB$L_IRP(R3),R2	; Get address of IRP (not needed)
;	MOVL	UCB_L_MEDIA(R3),-	; from the UCB to the SCDRP
;		SCDRP$L_MEDIA(R5)	;
;	MOVL	UCB$L_SVAPTE(R3),-	;
;		SCDRP$L_SVAPTE(R5)	;
;	MOVL	UCB$L_BOFF(R3),-	;
;		SCDRP$L_BOFF(R5)	;
;	CMPL	SCDRP$L_BCNT(R5),-	; Transfer length greater than maximum
;		UCB$L_MAXBCNT(R3)	; supported?
;	BGTR	20$			; GTR, therefore I/O must be segmented
;	BISB	#SCDRP$M_FLAG_BUFFER_MAPPED,-; This buffer has been mapped
;		SCDRP$L_SCSI_FLAGS(R5)	;
;	SPI$BUFFER_MAP			; Map the user buffer
;	BRB	10$
;
; 20$:	MOVL	#SS$_IVBUFLEN,R0	; Bad byte count
; 10$:	RSB

	.SBTTL	ALLOC_SYS_BUF	- Allocate a system buffer for I/O
;++
; ALLOC_SYS_BUF
;
; This routine allocates a system buffer to send/receive blocks to/from the
; disk.  This is done when the bcnt is less than a sector or when a transfer
; either starts or ends on a non-sector-aligned boundary.  This routine also
; sets up the SCDRP fields which relate to the buffer and are required for
; SPI$SEND_COMMAND.
;
; INPUTS:
;
;	R1	- Number of bytes to allocate
;	SCDRP$
;
; OUTPUTS:
;
;	R0	- Status
;	R1	- Length of buffer allocated
;
;	SCDRP$L_SVA_USER	- Address of S0 "user" buffer
;	SCDRP$L_BCNT		- Number of bytes to transfer
;	SCDRP$L_BOFF		- Byte offset of S0 "user" buffer
;	SCDRP$L_PAD_BCNT	- Set to zero, no padding required
;	SCDRP$L_SVAPTE		- SVAPTE of S0 "user" buffer
;	SCDRP$L_SCSI_FLAGS	- SCDRP$M_S0BUF & SCDRP$M_BUFFER_MAPPED are set
;
;--
ALLOC_SYS_BUF::
	.JSB_ENTRY INPUT=<R1,R3,R5>,OUTPUT=<R0,R1>

	TSTL	R1			; Any data to transfer?
	BNEQ	10$			; yes, go
	CLRL	SCDRP$L_BCNT(R5)	; No data being transferred
	BRB	20$			; Use common exit
 10$:	CMPL	R1,UCB$L_MAXBCNT(R3)	; Transfer length > max supported?
	BGTR	30$			; GTR, therefore I/O must be segmented
	MOVL	R1,SCDRP$L_BCNT(R5)	; Save length of transfer
	PUSHL	R5			; Save SCDRP address
	MOVL	SCDRP$PS_KPB(R5),R5	; Get KPB address
	BSBW	ALLOC_POOL		; Allocate a buffer to receive response
	POPL	R5			; Restore SCDRP address
	MOVL	R2,SCDRP$L_SVA_USER(R5)	; Save address of allocated buffer
	MOVL	R2,R1			; Copy address
	MOVL	G^MMG$GL_BWP_MASK,R2	; Mask of BWP portion of virtual addr
	EVAX_AND R1,R2,R2		; Get byte offset within page
	MOVL	R2,SCDRP$L_BOFF(R5)	; Save byte offset
	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_FLAG_S0BUF!-	; This buffer is an S0 "user" buffer
		 SCDRP$M_FLAG_BUFFER_MAPPED,-; This buffer has been mapped
		SCDRP$L_SCSI_FLAGS(R5)	;
	SPI$BUFFER_MAP	PRIO=HIGH	; Map the "user" buffer

 20$:	MOVL	#SS$_NORMAL,R0		; Set success status
	RSB


 30$:	MOVL	#SS$_IVBUFLEN,R0	; Bad byte count
	BRB	20$

	.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	- SPDT address
;	R5	- SCDRP address
;
; OUTPUTS:
;
;	All other registers preserved
;--
CLEANUP_CMD::
	.JSB_ENTRY INPUT=<R4,R5>,PRESERVE=<R0,R1,R2,R3>

	BBCC	#SCDRP$V_FLAG_BUFFER_MAPPED,-; Branch if no buffer mapped
		SCDRP$L_SCSI_FLAGS(R5),-;
		10$

	SPI$BUFFER_UNMAP		; Unmap the mapped buffer
 10$:	BBCC	#SCDRP$V_FLAG_S0BUF,-	; Branch if this is not an S0 "user"
		SCDRP$L_SCSI_FLAGS(R5),-; buffer
		20$			;

	PUT_N2	#24,<"WDD-CLEANUP_CMD: SVA_USER: %x ">,SCDRP$L_SVA_USER(R5)

	TSTL	SCDRP$L_SVA_USER(R5)	; Is there a S0 user buffer?
	BEQL	20$			; No, branch
	MOVL	SCDRP$L_SVA_USER(R5),R0	; Get address of S0 user buffer
	CLRL	SCDRP$L_SVA_USER(R5)	; Buffer no longer owned
	BSBW	DEALLOC_POOL		; Deallocate the buffer
 20$:	TSTL	UCB_L_WD_CMDBUFS(R3)	; Do we have command buffers allocated?
	BEQL	30$			;   branch if not
	DECL	UCB_L_WD_CMDBUFS(R3)	; Decrement # of command buffers left
	MOVL	SCDRP$L_CMD_BUF(R5),R0	; Get address of command buffer
	SPI$CMD_BUFFER_DEALLOC		; Deallocate the command buffer
 30$:	RSB

	.SBTTL	WD_WAIT		- Stall for the specified number of seconds
;++
; WD_WAIT
;
; This routine is used by the WD_WAIT macro to stall a thread for a specified
; number of seconds in R1.
;
; INPUTS:
;
;	R1 = number of seconds to wait
;	R3 = UCB
;	R4 = SPDT
;	R5 = SCDRP
;
; OUPUTS:
;
;	All registers preserved
;--
WD_WAIT::
	.JSB_ENTRY INPUT=<R1,R2,R4,R5>, PRESERVE=<R0>, SCRATCH=<R1>

	MOVL	R1,UCB_L_RW_RETRY(R3)		; Init retry counter

10$:	tstl	UCB_L_RW_RETRY(R3)		; Second counter less/equal zero
	bleq	20$				; Yes, branch

	KP_STALL_FORK_WAIT KPB=SCDRP$PS_KPB(R5)	; Wait a second
	decl	UCB_L_RW_RETRY(R3)		; another second gone
	brw	10$				; loop

20$:	clrl	UCB_L_RW_RETRY(R3)		; Zero retry counter
	RSB					; Return

	.SBTTL	WD_CANCEL	- Cancel I/O routine
;+
; WD_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
;-

WD_CANCEL::				; Cancel an I/O operation
	$DRIVER_CANCEL_ENTRY

	TSTL	R3			; IRP address of 0? (this can happen
					; during UNIT INIT in bringing the
					; device online)
	BEQL	10$			; Branch if not
	CALL_CANCELIO SAVE_R0R1=NO	; Set cancel bit if appropriate.
	BBC	#UCB$V_CANCEL,-		; If the cancel bit is not set,
		UCB$L_STS(R5),10$	; just return.
 10$:
	RET				; Return

	.SBTTL	LOG_ERROR
;+
;
;  Routine:	LOG_ERROR
;
;  Functional Description:
;
;	Calls ERL$DEVICERR after moving the UCB address from R3 to R5.
;
;  Inputs:
;
;	R3		= UCB address
;	UCB$L_DEVDEPND2	ignore error if UCB_M_WD_ERLDIS is set
;
;  Outputs:
;
;	None.
;
;-
LOG_ERROR::
	.JSB_ENTRY INPUT=<R3>
	BBS	#UCB_V_WD_ERLDIS,-		; If disabled,
		UCB$L_DEVDEPND2(R3),90$		;  ignore it
	PUSHL	R5				; Save contents of R5
	MOVL	R3,R5				; Copy UCB address to R5
	CLRL	R4				; CRAM address
	CALL_DEVICERR	SAVE_R0R1=YES		; Go log the device error
	POPL	R5				; Restore contents of R5
90$:	RSB					; Retto caller

	.SBTTL	SET_CONN_CHAR	- Modify connection characteristics
;+
; SET_CONN_CHAR
;
; This routine is called to initialize the connection
; characteristics, which specify such things as whether
; the device supports disconnect and synchronous operation.
;
; 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.
;
; ALL AXP devices are assumed to be able to disconnect and operate
; synchronously.
;
; INPUTS:
;
;	R3	- UCB address
;	R5	- SCDRP address
;	SCDRP$L_CDT - SCDT address
;
; OUTPUTS:
;
;	R0	- Status
;	R1-R4	- Destroyed
;	All other registers preserved
;-

SPI_LENGTH = <SPI$K_CC_LENGTH + ^X0F> & ^C^X0F

SET_CONN_CHAR::
	.JSB_ENTRY INPUT=<R3,R5>,OUTPUT=<R0>

	MOVL	UCB$L_PDT(R3), R4	; Get SPDT address

	SUBL	#SPI$K_CC_LENGTH, SP	; Allocate buffer on KP stack
	MOVL	SP, R2			; Save address of buffer
	MOVL	#SPI$K_CC_NUM_ARGS,-	; Set argument count in buffer
		SPI$IL_CC_COUNT(R2)	;
	SPI$CONNECTION_CHAR_GET		; Get current connection characteristics
	BLBC	R0,40$			; Branch on error
	BISL	#SPI$M_CC_ENA_DISCON, -	; Set the disconnect flag
		SPI$IL_CC_CON_FLAGS(R2)	; ...
10$:	MOVL	#1, SPI$IL_CC_SYNCHRONOUS(R2) ; Enable synchronous operation
;
; If we have long timeouts for this device, then set it as the default.
; Since disconnect is always enabled, only use the disconnect timeout.
;
	BBC	#OPTIC$V_LONG_TMO, -	; Check for long timeout in
		UCB$L_DEVDEPND2(R3), 30$	;  flags word for this drive
	MOVL	#LONG_TIMEOUT, R0	; Get the minimum long timeout value
	CMPL	R0, SPI$IL_CC_DISC_TIMEOUT(R2) ; Is the disconnect timeout the minimum?
	BLEQ	30$			; If LEQ, it's OK
	MOVL	R0, SPI$IL_CC_DISC_TIMEOUT(R2) ; Set default disconnect timeout
;;
;;??? START
;;??? RLS August 2, 1995

	CLRL	SPI$IL_CC_SCSI_FLAGS(R2)	; Make FLUSHQ and FREEZEQ zero
	BBC	#OPTIC$V_SCSI2, -		; If this isn't a SCSI-2 device,
		UCB$L_DEVDEPND2(R3), 20$	;  then branch
	BISL	SPI$M_CC_SCSI_2,-		; Set SCSI-2 char bit
		SPI$IL_CC_SCSI_FLAGS(R2)	;
20$:

;;??? END
;;

30$:	SPI$CONNECTION_CHAR_SET		; Set the connection characteristics
40$:	ADDL	#SPI$K_CC_LENGTH, SP	; Get rid of buffer
	BLBS	R0,50$			; Branch if success status
	MOVL	#SS$_CTRLERR,R0		; Otherwise, return a reasonable status
50$:	RSB				; Return to caller

	.SBTTL	WD_REGDUMP	- Device register dump routine
;+
; WD_REGDUMP
;
; This routine dumps device-specific information 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.
;
;	NOTE: Borrowed from DEC's DKDRIVER.
;
; Inputs:
;
;	R0	- Output buffer address
;	R5	- UCB address
;
; Outputs:
;
;	R1-R3	- Destroyed
;	All other registers perserved
;-

WD_REGDUMP::
	$DRIVER_REGDUMP_ENTRY

DK_ERROR_REVISION = 2			; VMS V5.3 DKDRIVER error rev.
	PUSHAL	(R0)+			; Save start of data area
	MOVB	#DK_ERROR_REVISION,(R0)+; Save revision level
;
;  Instead of storing the hardware revision level, we store our manufacture
;  code (from UCB$B_FEX) in the low word and the original SCSI command code
;  that caused the error in the high word.
;
;	MOVL	UCB$L_HW_REV(R5),(R0)+	; Save hardware revision level
	MOVZBW	UCB$B_FEX(R5),(R0)+	; Save manufacture code as HW_REV
	MOVAL	UCB_L_WD_SAVCMD(R5),R1	; Get address of saved SCSI command
	MOVZBW	4(R1),(R0)+		; Save offending SCSI command
;
;  DKDRIVER passed the error type in R7---they assumed or knew that R7 would
;  not be wiped out by ERL$DEVICERR.  To be safe, we stored the error type
;  in our extended UCB.
;
;	MOVB	R7,(R0)+		; Save error type
	MOVB	UCB_L_WD_ERROR_TYPE(R5),(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
;
;  R8 was also to pass the port status.  As noted above, the port status has
;  been stored in the WD device's extended UCB.
;
;	MOVL	R8,(R0)+		; Save port status code
	MOVL	UCB_L_WD_PORT_STATUS(R5),(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	90$			; Branch if none active
	MOVL	SCDRP$L_CMD_PTR(R1),R2	; Get address of SCSI command
	BEQL	90$			; Branch if none active
;
;  We attempted to put the offending SCSI command in the error packet (instead
;  of the nearly meaningless REQUEST SENSE), but ANALYZE/ERROR knew the
;  difference and wouldn't print the extended sense data unless the SCSI
;  command was REQUEST SENSE.
;
;	MOVAL	UCB_L_WD_SAVCMD(R5),R2	; Get address of saved SCSI command
	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	30$			; Branch if no status byte
	MOVB	(R2),-1(R0)		; Save SCSI status byte
 30$:	CLRB	(R0)+			; Assume no additonal data
;
;  Currently WDDRIVER only logs EXTND_SENSE_DATA errors, so the following
;  DISPATCH has been commented out.  As new errors are logged, this can
;  be uncommented and modified.
;
;	DISPATCH R7,TYPE=B,<-		; Dispatch according to error code
;		<SCSI$C_CONNECTION_ERROR,	90$>,-
;		<SCSI$C_MAP_BUFFER_ERROR,	90$>,-
;		<SCSI$C_SEND_CMD_ERROR,		90$>,-
;		<SCSI$C_INV_INQUIRY_DATA,	40$>,-
;		<SCSI$C_MODE_SENSE_DATA,	40$>,-
;		<SCSI$C_EXTND_SENSE_DATA,	40$>,-
;		<SCSI$C_REASSIGN_BLOCK,		50$>>
;	BRB	40$
;
;	BUG_CHECK INCONSTATE,FATAL	; Unknown error type. This should
;					; never happen.

; Error types which have additional data come here.

 40$:	MOVL	SCDRP$L_SVA_USER(R1),R2	; Get address of additional data
	BEQL	90$
	MOVL	SCDRP$L_TRANS_CNT(R1),R1; Get length of additional data
	BRB	60$
;
; Special entry point for reassign block error. The additional data length
; is contained in SCDRP$L_BCNT rather than in SCDRP$L_TRANS_CNT.
;
; 50$:	MOVL	SCDRP$L_SVA_USER(R1),R2	; Get address of additional data
;	MOVZBL	SCDRP$L_BCNT(R1),R1	; Get length of additional data
;

 60$:	MOVAL	UCB_L_WD_SAVCMD(R5),R3	; Get address of saved SCSI command
	ADDB3	(R3),R1,-1(R0)		; Save length of all data
	ADDB	#3,-1(R0)		; Include length of Driver Version data
;
; If the JK unit number is valid in UCB$L_DEVDEPND4, include it in the
; error log. Bracket it by hex FF's to make it easy to spot.
;
	BBC	#OPTIC$V_JKUNIT_VALID,-	; Branch JK unit number is not valid?
		UCB$L_DEVDEPND3(R5),65$	;
	ADDB	#4,-1(R0)		; Include length of JK unit data.

 65$: 	TSTL	R1			; Any extended data?
	BLEQ	75$			; If leq, no
 70$:	MOVB	(R2)+,(R0)+		; Save a byte of extended sense data
	SOBGTR	R1,70$			; Repeat for all extended sense data
 75$:	MOVL	(R3)+,R1		; Get length of original SCSI command
	BEQL	90$			; Branch if nothing to copy
 80$:	MOVB	(R3)+,(R0)+		; Save a byte of saved SCSI command
	SOBGTR	R1,80$			; Repeat for all of saved SCSI command

 90$:
;
; If the JK unit number is valid in UCB$L_DEVDEPND4, include it in the
; error log. Bracket it by hex FF's to make it easy to spot.
;
        BBC     #OPTIC$V_JKUNIT_VALID,- ; Branch JK unit number is not valid?
                UCB$L_DEVDEPND3(R5),95$ ;
        MOVB    #^XFF,(R0)+             ; Add bracket data
        MOVW    UCB$L_DEVDEPND4(R5),-   ; Add JK unit number associated with
                (R0)+                   ; this WD device.
        MOVB    #^XFF,(R0)+             ; Add bracket data
 95$:

	MOVB	#MAJVER,(R0)+		; Store the driver's version - major
	MOVB	#MINVER,(R0)+		; Store the driver's version - minor
	MOVB	#ENGREL,(R0)+		; Store the driver's version - release

	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
	RET				; Return

	.Sbttl	STR_MATCH_WILD		Match General Wild Card Specification
;
; Inputs:
;	R2,R3	= length, address of candidate string
;	R4,R5	= length, address of pattern string
;
; Outputs:
;	R0 = 1 if a match, 0 if not
;
; R0, R1 destroyed
;

STR_MATCH_WILD::
	.JSB_ENTRY INPUT=<R2,R3,R4,R5>

	CLRL	R0			; Assume failure
	CLRL	R6			; Clear saved candidate count
;
; Main scanning loop.
;
10$:	DECL	R4			; Pattern exhausted?
	BLSS	30$			; Branch if yes
	MOVZBL	(R5)+,R1		; Get next character in pattern
	CMPB	R1,#^A'*'		; Pattern specifies wild string?
	BEQL	60$			; Branch if yes
	DECL	R2			; Candidate exhausted?
	BLSS	50$			; Branch if yes
	CMPB	R1,(R3)+		; Compare pattern to candidate
	BEQL	10$			; Branch if pattern equals candidate
	CMPB	R1,#^A'%'		; Pattern specifies wild character?
	BEQL	10$			; Branch if yes
;
; We have detected a mismatch, or we are out of pattern while there is
; candidate left.  Back up to the last '*', advance a candidate character,
; and try again.
;
20$:	DECL	R6			; Count a saved candidate character
	BLSS	50$			; Branch if no saved candidate
	INCL	R7			; Set to try next character
	MOVQ	R6,R2			; Restore descriptors to backup point
	MOVQ	R8,R4			;
	BRB	10$			; Continue testing
;
; Here when pattern is exhausted.
;
30$:	TSTL	R2			; Candidate exhausted?
	BNEQ	20$			; Branch if no
;
; Here to return.
;
40$:	MOVL	#SS$_NORMAL,R0		; Set success return
50$:	RSB				; Return
;
; We have detected a '*' in the pattern.  Save the pointers for backtracking.
;
60$:	TSTL	R4			; Pattern null after '*'?
	BEQL	40$			; Branch if yes
	MOVQ	R2,R6			; Save descriptors of both strings
	MOVQ	R4,R8			;
	BRB	10$			; Continue testing

	.SBTTL	PUT -- Console character output
PUT::
	.CALL_ENTRY PRESERVE=<R0,R1,R2,R3,R4,R5,R6,R7,R8,R9,R10,R11,R12>, -
				MAX_ARGS=9, HOME_ARGS=TRUE

	.ENABLE LSB

	DSBINT	ENVIRON=UNIPROCESSOR	; Disable *ALL* interrupts

;;	WHAMI	R1			; WHO AM I?
;;	CMPL	R1, G^SMP$GL_PRIMID	; PRIMARY?
;;	BNEQ	99$			; NOPE -- ABORT OUTPUT

	JSB	G^CON$SAVE_CTY		; DISABLE CONSOLE INTERRUPTS
	MOVL	R0, -(SP)		; SAVE R0-R1

	MOVL	AP, R12			; Get the argument pointer
	MOVL	(R12)+,R2		; GET NUMBER OF ARGUMENTS
	DECL	R2			; COUNT ONE ARGUMENT USED
	BLSS	120$			; NONE TO BEGIN WITH!
	MOVL	(R12)+,R3		; GET ADDRESS OF STRING
100$:	MOVZBL	(R3)+,R16		; GET NEXT BYTE
	BEQL	120$			; BRANCH IF ALL DONE
	CMPB	#^A'%',R16		; IS IT A PUT COMMAND?
	BEQL	130$			;
	JSB	G^CON$PUTCHAR		; SEND IT OUT
	BRB	100$			; LOOP AROUND FOR MORE WORK

120$:	MOVL	(SP)+, R16		; RESTORE RETURNED REGISTERS
	JSB	G^CON$RESTORE_CTY	; ENABLE CONSOLE INTERRUPTS

99$:	ENBINT				; Re-enable interrupts
999$:	RET				; RETURN TO CALLER

130$:	MOVZBL	(R3)+,R16		; GET CONVERSION FORMAT TO USE
	CMPB	R16, #^A'a'		; LOWER CASE?
	BLSS	131$			; IF LSS, NO
	BICB	#^A'a'-^A'A', R16	; MAKE IT UPPER CASE

131$:	CMPB	#^A'C',R16               ; CHARACTERS REQUESTED?
	BNEQ	500$			; NOPE
	DECL	R2			; ARE THERE ANY ARGUMENTS LEFT?
	BLSS	505$			; NOPE, IGNORE THIS put COMMAND
	MOVL	(R12)+,R4		; GET NUMBER OF CHARACTERS TO PRINT
	DECL	R2			; ARE THERE ANY ARGUMENTS LEFT?
	BLSS	505$			; NOPE, IGNORE THIS put COMMAND
	MOVL	(R12)+,R5		; GET ADDRESS OF CHARACTERS
	BEQL	505$			; NONE THERE!
	TSTL	R4			; MAKE SURE THERE IS WORK TO DO
	BLEQ	505$			; THERE AIN'T
510$:	CMPB	#^A' ',-1(R5)[R4]       ; TRIM TRAILING SPACES
	BNEQ	501$			;
	SOBGTR	R4,510$ 		;
	BRB	506$			; THAT LEAVES NOTHING!
501$:	MOVB	(R5)+,R16		; GET NEXT CHARACTER
	JSB	G^CON$PUTCHAR		; PRINT IT
	SOBGTR	R4,501$ 		; LOOP UNTIL ALL DONE
505$:	BRW	100$			;

500$:	CMPB	#^A'D',R16		; DECIMAL REQUESTED?
	BNEQ	170$			; NOPE
	DECL	R2			; ARE THERE ANY ARGUMENTS LEFT?
	BLSS	506$			; NOPE, IGNORE THIS put COMMAND
	MOVL	(R12)+,R4		; GET VALUE TO WORK ON
	BGEQ	140$			; IT'S OKAY
	MOVB	#^A'-',R16		; PUT OUT A MINUS SIGN
	JSB	G^CON$PUTCHAR		;
	MNEGL	R4,R4			; AND DO IT RIGHT
	BVC	140$			;
	CLRL	R4			; LARGEST NEGATIVE INTEGER GETS -0
140$:	BSBB	PUT1			; CONVERT IT
506$:	BRW	100$			; AND WE'RE DONE

170$:	CMPB	#^A'H',R16               ; HEXADECIMAL REQUESTED?
	BEQL	171$			; YUP
	CMPB	#^A'X',R16               ; HEXADECIMAL REQUESTED?
	BNEQ	600$			; NOPE
171$:	DECL	R2			; ARE THERE ANY ARGUMENTS LEFT?
	BLSS	507$			; NOPE, IGNORE THIS put COMMAND
	MOVL	(R12)+,R4		; GET VALUE TO WORK ON
	BSBB	PUT2			; CONVERT IT
507$:	BRW	100$			; AND WE'RE DONE

600$:	CMPB	#^A'O',R16               ; OCTAL REQUESTED?
	BNEQ	502$			; NOPE
	DECL	R2			; ARE THERE ANY ARGUMENTS LEFT?
	BLSS	607$			; NOPE, IGNORE THIS put COMMAND
	MOVL	(R12)+,R4		; GET VALUE TO WORK ON
	BSBB	PUT3			; CONVERT IT
607$:	BRW	100$			; AND WE'RE DONE

502$:	CMPB	#^A'S',R16               ; STRING REQUESTED?
	BNEQ	504$			; NOPE
	DECL	R2			; ARE THERE ANY ARGUMENTS LEFT?
	BLSS	504$			; NOPE, IGNORE THIS put COMMAND
	MOVL	(R12)+,R5		; GET ADDRESS OF CHARACTERS
	BEQL	504$			; NONE THERE!
503$:	MOVB	(R5)+,R16		; GET NEXT CHARACTER
	BEQL	504$			; ALL DONE
	JSB	G^CON$PUTCHAR		; PRINT IT
	BRB	503$			; LOOP
504$:	JSB	G^CON$PUTCHAR		; PRINT THE UNRECOGNIZED CHARACTER
	BRW	100$			;

PUT1:					; Output value in decimal
	.JSB_ENTRY INPUT=<R4>,SCRATCH=<R5>
	DIVL3	#10,R4,R5		; GET LOW ORDER DIGIT
	MULL3	#10,R5,R0		;
	SUBL	R0,R4			;
	MOVB	DIGITS[R4],R3		; SAVE IT FOR PRINTING LATER
	MOVL	R5,R4			; THIS IS THE REST OF THE VALUE
	BEQL	160$			; ALL DONE
	BSBB	PUT1			; DO IT RECURSIVELY
160$:	MOVB	R3,R16			; PRINT A DIGIT
	JSB	G^CON$PUTCHAR		;
	RSB

PUT2:					; Output value in hex
	.JSB_ENTRY INPUT=<R4>
180$:	BICL3	#^C<^X0F>,R4,R0		; GET FOUR LOWEST BITS
	MOVB	DIGITS[R0],R3		; SAVE IT FOR PRINTING LATER
	BICL	#^X0F,R4 		; THIS IS THE REST OF THE VALUE
	ROTL	#-4,R4,R4		;
	BEQL	190$			; ALL DONE
	BSBB	PUT2			; DO IT RECURSIVELY
190$:	MOVB	R3,R16			; PRINT A DIGIT
	JSB	G^CON$PUTCHAR		;
	RSB

PUT3:					; Output value in octal
	.JSB_ENTRY INPUT=<R4>
680$:	BICL3	#^C^X7,R4,R0		; GET THREE LOWEST BITS
	MOVB	DIGITS[R0],R3		; SAVE IT FOR PRINTING LATER
	BICL	#^X7,R4 		; THIS IS THE REST OF THE VALUE
	ROTL	#-3,R4,R4		;
	BEQL	690$			; ALL DONE
	BSBB	PUT3			; DO IT RECURSIVELY
690$:	MOVB	R3,R16			; PRINT A DIGIT
	JSB	G^CON$PUTCHAR		;
	RSB

	.DSABL LSB


WD_END:				; Last location in driver

	DRIVER_DATA

	.ALIGN	QUAD

DIGITS:
	.BYTE	^A'0',^A'1',^A'2',^A'3',^A'4',^A'5',^A'6',^A'7'
	.BYTE	^A'8',^A'9',^A'A',^A'B',^A'C',^A'D',^A'E',^A'F'

	.END

