	.TITLE	PKSDRIVER - SIMport SCSI Port Driver Datalink
	.IDENT	'X-28'

;****************************************************************************
;*									    *
;*  COPYRIGHT © 1994, 1995 BY					  	    *
;*  DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASSACHUSETTS.		    *
;*  ALL RIGHTS RESERVED.						    *
;* 									    *
;*  THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND COPIED   *
;*  ONLY IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE   *
;*  INCLUSION OF THE ABOVE COPYRIGHT NOTICE. THIS SOFTWARE OR  ANY  OTHER   *
;*  COPIES THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY   *
;*  OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE IS  HEREBY   *
;*  TRANSFERRED.							    *
;* 									    *
;*  THE INFORMATION IN THIS SOFTWARE IS  SUBJECT TO CHANGE WITHOUT NOTICE   *
;*  AND  SHOULD  NOT  BE  CONSTRUED AS  A COMMITMENT BY DIGITAL EQUIPMENT   *
;*  CORPORATION.							    *
;* 									    *
;*  DIGITAL ASSUMES NO RESPONSIBILITY FOR THE USE  OR  RELIABILITY OF ITS   *
;*  SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DIGITAL.		    *
;* 									    *
;*									    *
;****************************************************************************
;
;
; FACILITY:
;
;	ALPHA VMS Executive, device drivers
;
; ABSTRACT:
; 
;	This module contains the SIMport TZA (TURBOCHANNEL to SCSI bus)
;	and PZA (PCI to SCSI bus) SCSI port driver datalink code. PKSDRIVER,
;	SCSIAUTO, and SCSICOMMON must be built together to create the
;	SCSI port driver image SYS$PKSDRIVER.EXE.
;
; ENVIRONMENT:
;
;	Kernel mode
;
; AUTHOR:
;
;	David Fairbanks
;
; CREATION DATE: Decemder 1993
;
; REVISION HISTORY:
;
;       X-28    DOF             Dave Fairbanks		22-Nov-96
;		- Setup SPDT$L_DMA_SIZE correctly in routine SETUP_CSRS.
;		  The upper 12 bits were still set after the calculation.
;		  This prevented the use of map registers for IO requests
;		  that needed them.
;
;       X-27    DOF             Dave Fairbanks		30-Oct-96
;		- Fix operand ordering in EVAX_SLL instruction in routine
;		  MAP_CSRS. This caused map registers to be used in cases
;		  where they were not required.
;		- Increase the size of the error log buffer. A previous
;		  edit increased the number of longwords in the error log
;		  buffer, however, the error log buffer size was not
;		  increased accordingly.
;
;       X-26    DOF             Dave Fairbanks		20-Sept-96
;		- Change 32 bit math to 64 bit math when computing physical
;		  addresses for BSD's. The upper 32 bits of the physical
;		  address were getting cleared. This involves changing ADDL
;		  instructions to EVAX_ADDQ instructions.
;		- Comput BSD physical addresses correctly for 1 and 2 byte
;		  data transfers. Use the physical address already computed
;		  for the BSM.
;		- Return resources to the ADFQ when invalid responses are
;		  received.
;		- Change error log revision to 2.
;		- Change ident of previous edit history to X-25.
;
;       X-25    RCL             Rick Lord               2-Aug-96
;               1) Modify PK$CONNECTION_CHAR_SET and PK$NEGOTIATE_SYNCH
;                  to honor STDT$V_DFLG_SUPPRESS_SDTR
;               2) Modify PK$SEND_COMMAND to support user-requested
;                  asynchronous mode SDTR negotiation.
;
;	X-24	DOF		Dave Fairbanks		1-Aug-96
;		- Insert the Path Inquiry information and the Adapter
;		  Status Register into the error log packet in routine
;		  PKS_REGDUMP.
;		- Reduce the time to initialize the adapter by waiting
;		  for 1/100 of a second when calling EXE$KP_TQE_WAIT
;		  in macro WAIT_FOR_RESPONSE.
;
;	X-23	DOF		Dave Fairbanks		24-Jul-96
;               Re-insert the clearing of bit CRCTX$M_ITEM_VALID in
;               CRCTX$L_FLAGS. The previous edit removed this instruction.
;               Clearing this bit is required to prevent an INCONSTATE
;               crash due to a double deallocation of map registers.
;
;	X-22	DOF		Dave Fairbanks		28-May-96
;               Remove the clearing of bit CRCTX$M_ITEM_VALID in
;               CRCTX$L_FLAGS. The previous edit inserted this instruction.
;               Clearing this bit causes an ACCVIO during target mode
;               operations.
;
;	X-21	DOF		Dave Fairbanks		2-Apr-96
;		1.) Merge GOBLIN 15A3 into GRYPHON. This edit was dropped
;		in GRYPHON:
;		    X-15A3  DOF             Dave Fairbanks          28-Mar-1996
;		    Fix physical address sign extension bug for Rawhide.
;		    SPDT$L_DMA_BASE is 80000000 for Rawhide. This causes
;		    bit 31 to be sign extended into the upper longword.
;
;		2.) Merge GOBLIN 15A3A1 into GRYPHON:
;		    X-15A3A1 DOF            Dave Fairbanks          2-Apr-1996
;		    In routine PKS$UNMAP_BUFFER_MR, if map registers were used
;		    to map the user buffer, clear bit CRCTX$M_ITEM_VALID in
;		    CRCTX$L_FLAGS to invalidate the counted resource for this
;		    CRCTX.
;
;		    In routine SETUP_ACCEPT_CCB_LIST, setup the FLINKs of each
;		    Queue Buffer to point to the next entry in the queue. The
;		    FLINKs were getting zeroed during queue setup which resulted
;		    in only one Queue Buffer on the list.
;
;		    In routine RET_TARGET_MODE_RESOURCES, when deallocating the
;		    Queue Buffers used for the Accept Target IO CCB list, if
;		    there are less buffers than we expect to be on the list,
;		    then BUG_CHECK.
;
;		    Increase NUM_ADFQ_ENTRIES to 8. We were running out of
;		    entries during heavy SCSI cluster initialization traffic.
;
;		3.) Allow for memory hole support. After calling IOC$NODE_DATA
;		    with key IOC$K_DIRECT_DMA_SIZE, compare it to
;		    MMG$GL_MAXPFN+1 to determine if to use mapping registers.
;
;	X-20	SGS0092		Steve Skonetski		19-Mar-96
;		Merge Dave Fairbanks fixes from 15a2 into gryphon.
;		
;		X-15A2	DOF		Dave Fairbanks 	26-Feb-1996
;		Map the BSM physical address correctly for systems with memory
;		greater than DMA_WINDOW size. On systems with greater than
;		DMA_WINDOW size of memory, when PK$MAP_BUFFER_MR was called, if
;		all of the user pages and the pad block were under the
;		DMA_WINDOW size, the PK$MAP_BUFFER routine was called.
;		However, if a BSM was needed, and non-paged pool has grown over
;		the DMA_WINDOW size boundary, the BSM was allocated and used
;		without the use of mapping registers. This caused the IO to be
;		aborted by the PCI bus.
;
;	X-19	RCL003		Rick Lord		5-Jan-95
;		1) Correct several memory deallocations which used
;		   constants instead of actual allocation sizes.
;		2) Delete obsolete routine RESET_SDTR_FLAG, several
;		   unreferenced local constants and UCB$PS_SCDT,
;		   which is no longer necessary with edit X-13.
;		3) Add missing ASSUMEs for S0 buffer bit
;		4) Use configured bus width as maximum SCSI ID to
;		   scan in PORT_SHUTDOWN instead of a constant.
;		5) Add and load field AB$IW_SIZE to hold AB size
;		   and allow safe deallocation.
;		6) Change a MOVL of type+subtype+size to a MOVZWL
;		   for use as a buffer deallocation size.
;
;	X-18	SCS		Sue Sommer		29-Dec-1995
;		Modify pk$connection_char_set so that it no longer 
;		sets up unnecessary synchronous negotiation.
;
;	X-17	RCL002		Rick Lord		13-Dec-95
;		In INIT_PORT initialize SPDT maximum and configured bus
;		width fields. By default they are loaded with 8 and 8,
;		but this adapter supports wide busses.
;
;	X-16	JCH710	John C. Hallyburton, Jr.	11-Dec-1995
;		STAR:: DOCD$:[EVMS.PROJECT_DOCUMENTS]FS-SCSI-NAMING.PS
;		Implement new naming format Inquiry Response message for
;		ports with port allocation classes assigned.
;
;	X-15	DOF		Dave Fairbanks		17-NOV-1995 12:00
;		Fix memory deallocation bug in RET_TARGET_MODE_RESOURCES.
;		A register was corrupted across a call to EXE$DEANONPGDSIZ.
;
;	X-14	DOF		Dave Fairbanks		6-NOV-1995 13:20
;		Add code for SCSI clusters. This includes:
;		- Data structure definitions for ENABLE_LUN, ACCEPT_TARGET_IO, 
;		  CONTINUE_TARGET_IO, SET_TARGET_STATE, and
;		  TARGET_EVENT_ACKNOWLEDGE commands.
;		- Adding Target mode status code definitions for Carriers
;		  and Queue Buffers.
;		- Initializing static Inquiry and Request Sense data during
;		  unit initialization.
;		- Building and sending an ENABLE_LUN command to the adapter
;		  during unit initialization to allow the adapter to respond
;		  to selections.
;		- Adding interrupt dispatch routines to handle CDBs whenever
;		  the adapter is selected.
;		- Re-enable the adapter to respond to selections after
;		  certain unsolicited Target events occur.
;		- Deallocating all Target Mode resources when shutting down
;		  the port.
;
;	X-13	DOF		Dave Fairbanks		30-OCT-1995 13:40
;		Fix the way SCSI bus resets are handled. The first entry
;		in SPDT$PS_STDT_HASH_TABLE was getting destroyed. This
;		caused a device at target ID 0 to be lost after a reset.
;		- Delete routines GET_FAKE_CONNECTION, CLEAR_FAKE_CONNECTION,
;		  GET_SCDT, GET_STDT.
;		- Only use routine GET_SCDRP when resetting SCSI bus.
;		- Delete SCDRP by calling DRVDEALMEM.
;
;	X-11A3	DOF		Dave Fairbanks		11-OCT-1995 11:08
;		Force quadword alignment for SPDT cell SPDT$PS_BUSRESET_FKBLK
;		and beyond to eliminate alignment faults.
;
;	X-12	LDD		Dan Duval		18-Aug-1995 10:10
;		DOF		Dave Fairbanks
;		Ensure that PA's don't get sign-extended.  Calculate the
;		correct number of pages when checking (in
;		PKS$MAP_BUFFER_MR::) whether the entire transfer is within
;		the direct-DMA window, and don't use a scratch register for
;		the loop counter in this test.  Don't use the 
;		CRCTX$M_ITEM_VALID bit to determine whether to deallocate
;		map registers; it doesn't get cleared.
;
;	X-11A1	RCL001		Rick Lord		25-Jul-95
;		Replace use of the standard UCB$L_CDT field, which is
;		actually past the end of the port driver's UCB, with 
;		use of a PKSDRIVER-specific field, UCB$PS_SCDT. 
;
;	X-11	JFD820		James F. Dunham		14-JUN-1995 11:51 
;		In the routine PKS$UNMAP_BUFFER, make sure that the
;		BSM which was allocated off the QBUF is deallocated.
;		Also prevent the (re-)allocation of the PADBLOCK in
;		PKS$MAP_BUFFER, if called from PKS$MAP_BUFFER_MR:
;
;	X-10	DOF		Dave Fairbanks		26-APR-1995
;		- When sending a SCSI bus reset command to the adapter,
;		  get the Queue Buffer and Carrier from the ADFQ instead
;		  of the free pool.
;		- Setup the Carrier fields correctly in routine SETUP_ADFQ.
;
;	X-9	DOF		Dave Fairbanks		5-APR-1995
;		- Add two entries (SS$_CTRLERR, SS$_MEDOFL) to the end of
;		  the CAM_STATUS_TABLE. This fixes the RMS crash where
;		  R0 was being returned with a zero status code.
;
;	X-8	JFD0812		James F. Dunham		21-MAR-1995
;		Make data structure alignment changes to data segement
;
;	X-7	DOF		Dave Fairbanks		7-Feb-1995
;		- Fix the initialization of SPDT$L_SPTE_BASE.
;
;	X-6	DOF		Dave Fairbanks		19-Jan-1995
;		- Fix data corruption bug under heavy load. This includes
;		  modifying the MAP_BUFF routine to call PTETOPFN with the
;		  argument set up correctly.
;
;	X-5	LDD		Dan Duval		08-Jan-1995
;		- Add support for DMA map registers, for systems that
;		  have more physical memory than is directly addressable
;		  via DMA.
;
;	X-4	DOF		Dave Fairbanks		29-NOV-1994
;		- Setup the device connection id correctly when sending
;		  a SET DEVICE STATE command to the adapter. Deallocate
;		  buffer resource after a DEVICE STATE SET response message
;		  is received.
;		- When a CAM status of data overrun is returned, map this
;		  status to SS$_DATAOVERUN instead of SS$_NORMAL.
;
;	X-3	LDD		Dan Duval		04-NOV-1994
;		- Use IOC$NODE_DATA calls IOC$K_DIRECT_DMA_BASE and
;		  IOC$K_DIRECT_DMA_SIZE to find the direct-DMA window
;		  on PCI systems (KZPSA), and use the resulting value
;		  to pass the correct (bus) physical address to the
;		  adapter for DMA operations.
;
;	X-2	DOF		Dave Fairbanks		26-OCT-1994
;		- Remove all queue insertions and removals for KPBs
;		  and Queue Buffers. There is no need for the queuing.
;		- In PKS$FORK don't search a queue to find the KPB to resume.
;		  Just resume the KPB in the SCDRP in the Queue Buffer.
;		- When allocating a block of memory, if the allocation failed,
;		  then KP_STALL_FORK_WAIT instead of crashing.
;		- Fix bus reset code when channel is re-enabled. Call
;		  SPDT$PS_CD_RESET_SCSI_BUS in Kernel Process context.
;		  Add KP routine RESET_SCSI_BUS.
;
;	************* Change Edit History to Match CMS ********************
;
;	X-1	JFD0660		James F. Dunham			28-SEP-1994
;		SCSI-2 Checkin
;
;	X-19	DOF		Dave Fairbanks		27-SEP-1994
;		- Move REMOVE_EL from top of PKS$FORK to command specific
;		  dispatch routines.
;		- Return resources to DAFQ when an unsolicited reselection
;		  occurs.
;		- Make DEALLOC_RESOURCES pass the correct block size in R1
;		  when calling SPDT$PS_RL_POOL_DEALLOC to deallocate the
;		  command buffers.
;		- Clean up comments in PK$SEND_COMMAND.
;
;	X-18	DOF		Dave Fairbanks		22-SEP-1994
;		- Fix offsets in routine MAP_CSRS for KZPSA.
;
;	X-17	DOF		Dave Fairbanks		16-SEP-1994
;		- Set up queuing characteristics using SCDRP$V_FLAG_QUEUED_IO.
;		- Use a BBCC to clear the CAR$V_AUTOSENSE_DATA_VALID bit.
;		- Do not insert Carriers onto AB$PS_CAR_BLINK.
;		- Use R1 as scratch in routine GET_QUEUE_CARRIER.
;		- Use IOC$RETURN as the stall routine in GET_QUEUE_CARRIER.
;
;	X-16	DOF		Dave Fairbanks		12-SEP-1994
;		- The previous edit caused the UCB to be set online too late.
;		  Set the UCB online (UCB$M_ONLINE bit in UCB$L_STS) in
;		  routine REINIT_PORT.
;		- Remove the test for BUS_RESET_DETECTED from the top of
;		  routine PK$SEND_COMMAND.
;		- Add routines to handle all responses returned from adapter.
;		- Move the call to routine SETUP_QUEUES to routine INIT_PORT.
;
;	X-15	DOF		Dave Fairbanks		9-SEP-1994
;		- After a SCSI bus reset command is sent to the adapter, do
;		  not set the port online (SPDT$M_STS_ONLINE in SPDT$L_STS)
;		  until the adapter has returned the channel enabled response
;		  indicating the reset is complete.
;
;	X-14	DOF		Dave Fairbanks		8-SEP-1994
;		- Do not set the SPDT online bit SPDT$M_STS_ONLINE at the
;		  end of routine INIT_ADAPTER. Instead set it in routine
;		  PK$RESET_SCSI_BUS after the SCSI bus reset has occurred.
;		- Correct some of the SCSI bus reset handling code.
;		- Call routine RESET_SDTR_FLAG at the correct places.
;		- Save R10 in routine RESET_SDTR_FLAG. It gets destroyed.
;		- Don't disable autosense when resetting the SCSI bus.
;
;	X-13	DOF		Dave Fairbanks		6-SEP-1994
;		- Fix SCSI bus resets. Reorganize code to perform a
;		  SCSI bus reset at adapter initialization time.
;		- Add header SIZE/TYPE/SUBTYPE fields for Queue Buffer
;		  memory.
;
;	X-12	DOF		Dave Fairbanks		17-AUG-1994
;		- Fix Spinlock release error crash. This involves
;		  removing all references to KPB$PS_SCSI_PTR2. Use the
;		  SCDRP$V_DSF_NOWAIT bit in SCDRP$IS_DIPL_SCSI_FLAGS
;		  instead.
;		- In the TAG_QUEUE_ACTION_TABLE make untagged IO requests
;		  (SCDRP$K_QCHAR_NOT_QUEUED) and ACA IO requests
;		  (SCDRP$K_QCHAR_ACA) into tagged ordered IO requests.
;
;	X-11	DOF		Dave Fairbanks		12-AUG-1994
;		- Fix Queue Carrier/Queue Buffer corruption problem.
;		- Dispatch on the Carrier function code for response handling
;		  in routine PKS$FORK. Also add Carrier function code constants.
;		- Add STDT version number (STDT$C_VERSION) in
;		  SPDT$L_VERSION_CHECK in routine REINIT_PORT.
;		- Add routine PK$INIT_STDT to complete STDT Initialization.
;		- Add synchronous negotiation routine PK$NEGOTIATE_SYNCH.
;		  Also fix synchronous negotiation setup in PK$SEND_COMMAND.
;		- Do not return the autosense data length in cell
;		  SCDRP$IS_SENSE_BUFFER_LEN during response processing.
;		- Fix register usage in routine SETUP_DAFQ. R6 was getting
;		  overwritten.
;		- Fix ACCVIO in PKS_REGDUMP for STDT and KPB addresses.
;		- Add code for adapter initiated SCSI bus resets.
;		- Miscellaneous comment cleanup
;
;	X-10	DOF		Dave Fairbanks		13-JUL-1994
;		- Update to base level 4. This includes:
;		  - In routine PK$SEND_COMMAND return a valid status in R0.
;		  - Delete all references to SCDRP$IS_REQUEST_STATUS.
;		  - Move autosense handling from PK$CMD_WAIT_COMPLETION to
;		    PKS$FORK. Check for valid autosense by testing bit 7
;		    of the returned CAM status. Add Carrier status field
;		    definitions.
;
;	X-9	DOF		Dave Fairbanks		12-JUL-1994
;		- In routine PK$SEND_COMMAND when setting up the
; 		  queuing characteristics, change the way
;		  QBUF$IB_TAG_QUEUE_ACTION and QBUF$IB_CAM_FLAGS_1
;		  are setup by using a table.
;
;	X-8	DOF		Dave Fairbanks		20-JUN-1994
;		- Make WAIT_FOR_RESPONSE macro test correct bit.
;		  (Bit 0 in CAR$PQ_NEXT_PTR(R6))
;
;	X-7	DOF		Dave Fairbanks		20-JUN-1994
;		- Move DEVICEUNLOCK from PKS_STALL to PK$SEND_COMMAND.
;		- Set up SCDRP$IS_REQUEST_STATUS for error returns.
;		- Return SS$_NORMAL from PK$SEND_COMMAND.
;
;	X-6	DOF		Dave Fairbanks		17-JUN-1994
;		- Add code for Tagged Command Queuing
;
;	X-5	DOF		Dave Fairbanks		16-JUN-1994
;		- Add code for autosense.
;
;	X-4	DOF/LDD	  Dave Fairbanks/Dan Duval	3-JUN-1994
;		- Modify to handle KZPSA adapter.
;
;	X-3	DOF		Dave Fairbanks		25-MAY-1994
;		- Correct SCSI bus reset handling.
;		- Modify code for SCSI2 non-autosense, non-TCQ.
;
;	X-2	DOF		Dave Fairbanks		18-MAY-1994
;		- Add code to handle unaligned buffers. This includes
;		  code necessary to map/unmap user buffers, and copy the
;		  unaligned data to/from the user buffer at the appropriate
;		  time.
;		- Add REQPORT/RELPORT macros.
;		- Use macro BUILD_BSD to create all BSDs.
;		- Fix bug when resuming stalled processes. We were
;		  resuming the wrong process.
;		- Delete SPO$L_xxx register offset definitions. These
;		  were never used.
;		- Miscellaneous comment corrections.
;
;	X-1	DOF		Dave Fairbanks		18-JAN-1994
;		- Replace TIMEDWAITs with PK_TQE_WAITs.
;		- Remove unnecessary DEVICELOCK/DEVICEUNLOCK in SETUP_ADFQ.
;		- After an I/O has been completed, if a BSM was allocated,
;		  deallocate the BSM in routine PKS$UNMAP_BUFFER.
;
;	X-0	DOF		Dave Fairbanks		December-1993
;		Initial version.
;

.PAGE
	.SBTTL	Include Files
;
; Macro library calls               
;                     
	$ADPDEF				; ADP offsets
	$BUSDEF				; Bus-type constants
	$BUSARRAYDEF			; ADP bus-array definitions
	$CRAMDEF			; I/O Mailbox definitions
	$CRBDEF				; CRB offsets
	$CRCTXDEF			; CRCTX offsets
	$DCDEF				; Device types
	$DDBDEF				; DDB offsets
	$DEVDEF				; Device types
	$DPTDEF				; DPT offsets
	$DYNDEF				; Dynamic data structure types
	$EMBDEF				; EMB offsets
	$FKBDEF				; Fork block offsets
	$IDBDEF				; IDB offsets
	$IODEF				; I/O function codes
	$IOCDEF				; I/O definitions
	$IOHANDLEDEF			; I/O handle offsets
	$IPLDEF				; Interrupt priority level usage
	$IRPDEF				; IRP offsets
	$KPBDEF				; KPB offsets
	$MSGDEF				; Operator message types
	$PDSCDEF			; PDSC definitions
	$PFNDEF				; PFN Data base definitions
	$PKDEF				; SCSI Port Definitions
	$PTEDEF				; Page table entry bits & fields
	$SCDRPDEF			; SCSI CDRP definitions
	$SCDTDEF			; SCSI Connection Descriptor Definitions
	$SCSIDEF			; SCSI2 definitions
	$SPDTDEF			; SCSI Port Descriptor Definitions
	$SPLCODDEF			; Spinlock code definitions
	$SSDEF				; QIO status return codes
	$STDTDEF			; SCSI Target Descriptor definitions
	$TQEDEF				; Timer Queue Entry Definitions
	$UCBDEF				; UCB definitions
	$VADEF				; Virtual address fields
	$VECDEF				; Interrupt dispatch vector offsets
	$VLEDEF				; Vector List Extension definitions

.PAGE
	.SBTTL Local Definitions for PKSDRIVER
;
; DEBUG ONLY -- take this flag out once testing is completed.
;
;;PKSDEBUG = 1				; Conditional compile flag for debug
;;PKSHISTORY = 1
;
; Port specific constants
;
NUM_CHANNELS = 1			; Number of SCSI channels supported
PKS_NORM_CMDSIZE = 20			; For SNDSCSI commands, the command
					;  buffer requested by a class driver
					;  should be max. of 20 bytes
					;  (12 bytes of CDB + 8 bytes class
					;   driver overhead) 
PKS_MAXBCNT	= 65535			; Max data byte count supported by
					;  port driver
PKS$M_BYTE_IN_PAGE = <^X1FFF>		; Byte-within-page bit mask for 
					;  8K-byte page
PKS$S_BYTE_IN_PG = 13			; # of bits to shift for 8K-byte page

PKS_ERROR_REV = 2			; Error log revision number
PKS_ERROR_LEN = 256			; Maximum length of error log packet

PKS_INIT_TIMEOUT = 300			; Port init timeout value = 5 minutes
PKS_ENAB_TIMEOUT = 120			; Port re-enable timeout value = 2 min
PKS_XPD1_TIMEOUT = 50			; Timeout value (1/2 second) in 10 ms
					;  units for reading XPD1 register
PKS_BUSRESET = 1			; Flag saying bus reset was performed
AB_PAGES = 1				; Number of pages needed for AB
NUM_QUEUES = 4				; Number of queues in AB
NUM_QUEUE_BUFFERS = 256			; Number of Queue Buffers
QUEUE_BUFFER_SIZE = 512			; Size of a Queue Buffer
HEADER_SIZE = 16			; Number of bytes for data structure
QUEUE_BUFFER_POOL_SIZE = <NUM_QUEUE_BUFFERS*QUEUE_BUFFER_SIZE+HEADER_SIZE>
					;  header SIZE/TYPE/SUBTYPE
NUM_ADFQ_ENTRIES = 8			; Number of free entries to insert
					;  onto the ADFQ
;
; Target mode specific constants
;
NUM_ATIO_CCBS = 4			; Number of Accept Target IO CCBs
NUM_TARGET_LUNS = 1			; Number of target mode LUNs active
LUN_NOT_SUPPORTED = ^X7F		; Inquiry data for LUN not supported
;
; CDB offsets
;
CDB$IB_OPCODE = 0			; Operation code
CDB$IB_LUN = 1				; LUN byte
CDB$IB_ALLOC_LENGTH = 4			; Allocation length byte
;
; For permanent KPBs, we use the standard I/O KPB flags except that
; KP$M_DEALLOC_AT_END is not set. We don't want the permanent KPBs
; deallocated after each usage.
;
PKS_KP_REGMSK = ^M<R2,R3,R4,R5,R6,R7,R8,R9,R10,R11,AP,FP> 
					; Register save mask for kernel process routine
KPB_FLAGS = KP$M_IO & ^C<KP$M_DEALLOC_AT_END>

DEVICEIPL = 21				; Interrupt IPL for this driver

READCNTR_INTERVAL = 60			; Interval between two RDCNT commands
					; issued to the port, currently set to
					; 60 seconds

TQE_TIMER_EXPIRE = 10*100000*100	; 10 Seconds timeout for TQE
IO_TIMEOUT = 180			; I/O thread wait timeout value in
					; seconds. Currently 3 minutes
ATIO_COMMAND_TIMEOUT = 5		; Command timeout for Accept Target IO
;
; Alignment constants used for calling EXE$ALONONPAGED_ALN
;
ALN_LONG	= 2			; Longword aligned
ALN_QUAD	= 3			; Quadword aligned
ALN_DQUAD	= 4			; Double quadword aligned
ALN_HEXWORD	= 5			; Hexaword aligned (32-byte)
ALN_DHEXWORD	= 6			; Double hexaword aligned
ALN_PORTPAGE	= 13			; 8KB aligned

;
; Alignment constants for testing buffer alignment
;
BYTE_ALIGNED	= 1			; Byte aligned
LW_ALIGNED	= 3			; Longword aligned
QUAD_ALIGNED	= 7			; Quadword aligned

;
; SCSI Command codes referred to in this driver
;
SCSI$K_REQ_SENSE = ^X3			; Request Sense command code
SCSI$K_INQUIRY = ^X12			; INQUIRY command code
SCSI$K_MSGCMD = 0                       ; Command complete message code
;
; Error subtype codes used for error logging.
;
SPO$C_ADSE = 1
SPO$C_AMSE = 2
SPO$C_AAC  = 3
SPO$C_AME  = 4
SPO$C_UNKNOWN_ERR = -1

.PAGE
	.SBTTL	Unit Control Block Definitions
	.SYMBOL_ALIGNMENT QUAD

	$DEFINI	UCB,GLOBAL
	. = UCB$K_PK_LENGTH

	.IF DEFINED PKSDEBUG
$DEF	UCB$IS_TOP_OF_RING	.BLKL 1
$DEF	UCB$IS_RING_POINTER	.BLKL 1
$DEF	UCB$IS_RING_COUNTER	.BLKL 1
	.ENDC

UCB$K_PKS_LENGTH = .
	$DEFEND UCB
	.SYMBOL_ALIGNMENT NONE

.PAGE
	.SBTTL	SCDRP Definitions
;
; Define SCSI port specific field in SCDRP using the existing field.
;
	$DEFINI SCDRP,GLOBAL

SCDRP$PS_QBUF = SCDRP$L_SAVE_DATA_CNT	; Address of the Q_Buffer structure
					;  associated with this I/O request
	$DEFEND SCDRP

.PAGE
	.SBTTL	SPDT Definitions
;
; Define PKS specific extension to the SPDT offsets.
;
	$DEFINI SPDT,GLOBAL,SPDT$C_PKSLENGTH

. = .+<4-<.&3>>				; Make sure it is longword aligned here

$DEF	SPDT$IS_CHANSTATE .BLKL 1	; Channel specific state transition code
	$VIELD SPDT,0,<-		; Bits used with port status
	<CHAN_UNIN,,M>,-		;  Channel in UNINitialized state
	<CHAN_INIT,,M>,-		;  Channel init, adapter disabled
	<CHAN_ENABLED,,M>,-		;  Channel init, adapter enabled
	<RESET_DETECTED,,M>,-		;  Reset detected on SCSI bus
	<FILLER,28,M>>			;  Filler

$DEF	SPDT$IS_PORTSTS	.BLKL 1		; Port status
	$VIELD SPDT,0,<-		; Bits used with port status
	<STRUCT_ALLOCATED,,M>,-		;  Port resource structures allocated
	<PORTONLY,,M>,-			;  Reinit the port only
	<INIT_ADAPTER,,M>,-		;  Reinitialize the adapter
	<LOG_ERROR,,M>,-		;  Need to log an error
	<TARGET_MODE,,M>,-		;  Target mode supported
	<FILLER,27,M>>			;  Filler

$DEF	SPDT$IS_CHAN_DIS_STS	.BLKL 1	; Channel disabled status
	SPDT$C_BUS_RESET = 2		; Bus reset detected on SCSI bus
	SPDT$C_RESET_REQ_DENIED = 3	; Host denied adapter reset request
	SPDT$C_CHAN_HW_ERROR = 5	; Channel hardware error
$DEF	SPDT$PS_DACQ_T	.BLKL 1		; Driver-Adapter Command queue tail ptr
;
; Queue pointers.
;
$DEF	SPDT$PS_QCMDFL	.BLKL 1		; Forward link of currently outstanding 
					;  command Q_Buffers
$DEF	SPDT$PS_QCMDBL	.BLKL 1		; Backward link of currently
					;  outstanding command Q_Buffers
$DEF	SPDT$PS_RSPFL	.BLKL 1		; Forward link of response Q_Buffers to 
					;  be processed
$DEF	SPDT$PS_RSPBL	.BLKL 1		; Backward link of response Q_Buffers
					;  to be processed
$DEF	SPDT$PS_DAFQFL	.BLKL 1		; Forward link of free Q_Buffers in the
					;  Driver-Adapter Free Queue  
$DEF	SPDT$PS_DAFQBL	.BLKL 1		; Backward link of free Q_Buffers in the
					;  Driver-Adapter Free Queue  
$DEF	SPDT$PS_WCMDFL  .BLKL 1		; SCSI command completion wait queue
					;  forward link
$DEF    SPDT$PS_WCMDBL  .BLKL 1         ; SCSI command completion wait queue
					;  backward link
$DEF	SPDT$PS_PCMDFL	.BLKL 1		; Port command completion wait queue
					;  forward link
$DEF	SPDT$PS_PCMDBL	.BLKL 1		; Port command completion wait queue
					;  backward link
$DEF	SPDT$PS_CARFL	.BLKL 1		; Carrier free list forward link
$DEF	SPDT$PS_CARBL	.BLKL 1		; Carrier free list backward link
$DEF	SPDT$PS_CARWQFL	.BLKL 1		; Carrier wait queue forward link
$DEF	SPDT$PS_CARWQBL	.BLKL 1		; Carrier wait queue backward link
$DEF	SPDT$PS_AB	.BLKL 1		; Pointer to Adapter Block
$DEF	SPDT$PS_RD_CTR_Q_BUFFER	.BLKL 1	; Pointer to static read counter
					;  Q_Buffer
;
; CRAM pointers for SIMport registers.
;
	START_OF_SPDT_CRAMS = .
$DEF	SPDT$PS_CRAM_HW_REV	.BLKL 1	; Hardware Revision Register (Read)
$DEF	SPDT$PS_CRAM_AMCSR	.BLKL 1	; Adapter Maint Ctl and Sts Reg (Read)
$DEF	SPDT$PS_CRAM_ABBR	.BLKL 1	; Adapter Block Base Register (Write)
$DEF	SPDT$PS_CRAM_DACQIR	.BLKL 1	; Driver Adapter Command Queue
					;  Insertion Register (WRITE)
$DEF	SPDT$PS_CRAM_DAFQIR	.BLKL 1	; Driver Adapter Free Queue Insertion
					;  Register (WRITE)
$DEF	SPDT$PS_CRAM_ASR	.BLKL 1	; Adapter Status Register (READ)
$DEF	SPDT$PS_CRAM_AFAR	.BLKL 1	; Adapter Failing Addr Register (READ)
$DEF	SPDT$PS_CRAM_AFPR	.BLKL 1	; Adapter Failing Param Register (READ)
$DEF	SPDT$PS_CRAM_ERRLOG1	.BLKL 1	; Error Log Data Register 1 (READ)
$DEF	SPDT$PS_CRAM_ERRLOG2	.BLKL 1	; Error Log Data Register 2 (READ)
$DEF	SPDT$PS_CRAM_ERRLOG3	.BLKL 1	; Error Log Data Register 3 (READ)
$DEF	SPDT$PS_CRAM_ERRLOG4	.BLKL 1	; Error Log Data Register 4 (READ)
$DEF	SPDT$PS_CRAM_ERRLOG5	.BLKL 1	; Error Log Data Register 5 (READ)
$DEF	SPDT$PS_CRAM_ERRLOG6	.BLKL 1	; Error Log Data Register 6 (READ)
$DEF	SPDT$PS_CRAM_ERRLOG7	.BLKL 1	; Error Log Data Register 7 (READ)
$DEF	SPDT$PS_CRAM_ERRLOG8	.BLKL 1	; Error Log Data Register 8 (READ)
$DEF	SPDT$PS_CRAM_ERRLOG9	.BLKL 1	; Error Log Data Register 9 (READ)
$DEF	SPDT$PS_CRAM_ERRLOGA	.BLKL 1	; Error Log Data Register A (READ)
$DEF	SPDT$PS_CRAM_CLEAR_INT	.BLKL 1 ; Clear interrupt (WRITE)
$DEF	SPDT$PS_CRAM_RESET	.BLKL 1	; Module RESET Register (WRITE)
        CRAM_COUNT = <. - START_OF_SPDT_CRAMS>/4  ; Number of allocated CRAMs
;
; Saved SIMport registers.
;
	START_OF_SAVE_REGS = .
$DEF	SPDT$IS_SAVE_HW_REV	.BLKL 1	; Saved HW revision
$DEF	SPDT$IS_SAVE_AMCSR	.BLKL 1	; Saved AMCSR register
$DEF	SPDT$IS_SAVE_ASR	.BLKL 1	; Saved ASR register
$DEF	SPDT$IS_SAVE_AFAR	.BLKL 1	; Saved AFAR register
$DEF	SPDT$IS_SAVE_AFPR	.BLKL 1	; Saved AFPR register
$DEF	SPDT$IS_SAVE_ERRLOG1	.BLKL 1	; Saved error Log Data Register 1
$DEF	SPDT$IS_SAVE_ERRLOG2	.BLKL 1	; Saved error Log Data Register 2
$DEF	SPDT$IS_SAVE_ERRLOG3	.BLKL 1	; Saved error Log Data Register 3
$DEF	SPDT$IS_SAVE_ERRLOG4	.BLKL 1	; Saved error Log Data Register 4
$DEF	SPDT$IS_SAVE_ERRLOG5	.BLKL 1	; Saved error Log Data Register 5
$DEF	SPDT$IS_SAVE_ERRLOG6	.BLKL 1	; Saved error Log Data Register 6
$DEF	SPDT$IS_SAVE_ERRLOG7	.BLKL 1	; Saved error Log Data Register 7
$DEF	SPDT$IS_SAVE_ERRLOG8	.BLKL 1	; Saved error Log Data Register 8
$DEF	SPDT$IS_SAVE_ERRLOG9	.BLKL 1	; Saved error Log Data Register 9
$DEF	SPDT$IS_SAVE_ERRLOGA	.BLKL 1	; Saved error Log Data Register A
	SAVE_REG_COUNT = <. - START_OF_SAVE_REGS>/4 ; Number of saved registers

$DEF	SPDT$PS_RDCNT_KPB .BLKL 1	; KPB used to issue a RDCNT command 
$DEF	SPDT$PS_COUNTERS  .BLKL 1	; Pointer to the port counter block
					;  area in the Adapter Block free
					;  memory space
$DEF	SPDT$IS_OVERFLOW  .BLKL	1	; Counter overflow flag
	$VIELD SPDT,0,<-		; Bits used with counter overflow flags
	<CTR_OVERFLOW,,M>,-		;  Port counters overflowed
	<FILLER,31,M>,-			;  Filler
	>
$DEF	SPDT$IS_ENABTMR	.BLKL 1		; Port re-enable timeout value
$DEF	SPDT$PS_KPB	.BLKL 1		; Adapter-wide KPB address
$DEF	SPDT$PS_INQUIRY_DATA	.BLKL 1	; Pointer to target mode inquiry data
$DEF	SPDT$PS_SENSE_DATA	.BLKL 1	; Pointer to target mode sense data
$DEF	SPDT$PS_ATIO_FL .BLKL 1		; Accept target IO CCB forward link
$DEF	SPDT$IS_TARGET_EVENT .BLKL 1	; Target Event data
$DEF	SPDT$IB_TARGET_FLAGS .BLKB 4	; Target Event flags
$DEF	SPDT$IS_NUM_ACCEPTS  .BLKL 1	; Number Target Accepts
$DEF	SPDT$IS_NUM_CONTINUES  .BLKL 1	; Number Target Continues
$DEF	SPDT$IS_NUM_TARGET_EVENTS .BLKL 1 ; Number Target Events
$DEF	SPDT$IS_ATIO_LIST_HEAD	.BLKL 1	; Accept list head
$DEF	SPDT$IS_ATIO_LIST_TAIL	.BLKL 1	;   and tail pointers
;
; Make sure following fork block is quadword aligned
;
. = <<.+7>&^C7>                         ; Make sure we are quadword aligned
$DEF	SPDT$PS_BUSRESET_FKBLK .BLKB 32 ; Fork block used for getting KPB 
					;  for BUS RESET
	SPDT$C_BUSRESET_FKBLK = 32
$DEF	SPDT$PS_BUSRESET_KPB  .BLKL 1	; KPB used for SCSI BUS RESET operation
					;  which is triggered by the port driver

$DEF	SPDT$IS_CHANNEL .BLKL 1		; Channel number of this port
	$VIELD SPDT,0,<-		; Define bit used with SPDT$IS_CHANNEL
	<FILLER,30,M>,-			;  Filler
	<PWF_CLNUP,,M>,-		;  Clean buffers after powerfail/crash
	<CHNL_CLNUP,,M>,-		;  Clean channel resources after RESET
	>
$DEF	SPDT$IB_XFER_ALIGN	.BLKB 1	; Transfer alignment capabilities
$DEF	SPDT$IB_FILL		.BLKB 3	; Filler for alignment
;
; The following locations hold the path inquiry data that was received
; during adapter (re)initialization.
;
	SPDT$C_START_PI_DATA = .
$DEF	SPDT$IB_PI_VERSION	   .BLKB 1	; Version
	SPDT$IB_PI_DATA = SPDT$IB_PI_VERSION
$DEF	SPDT$IB_PI_SCSI_CAPABILITY  .BLKB 1	; SCSI capabilities
	$VIELD SPDT,0,<-
	<SOFT_RESET,,M>,-			;  Soft reset
	<TCQ,,M>,-				;  Tagged command queueing
	<RSVD1,,M>,-				;  Reserved
	<LINKED_CMDS,,M>,-			;  Linked commands
	<SYNCH_TRANS,,M>,-			;  Synchronous transfers 
	<WIDE_BUS_16,,M>,-			;  Wide bus 16
	<WIDE_BUS_32,,M>,-			;  Wide bus 32
	<MOD_DATA_PTRS,,M>,-			;  Modify data pointers
	>
$DEF	SPDT$IB_PI_TARGET_MODE	   .BLKB 1	; Target mode support
	$VIELD SPDT,0,<-
	<RSVD2,6,M>,-				;  Reserved
	<PHASE_COG,,M>,-			;  Phase cognizant mode
	<PROCESSOR,,M>,-			;  Processor mode
	>
$DEF	SPDT$IB_PI_MISCELLANEOUS    .BLKB 1	; Miscellaneous
	$VIELD SPDT,0,<-
	<RSVD3,5,M>,-				;  Reserved
	<PI_DATA_XPT,,M>,-			;  PI data not kept by XPT
	<REMOVABLES,,M>,-			;  Removables included in scan
	<SCAN_DIR,,M>,-				;  Scan direction
	>
$DEF	SPDT$IW_PI_ENGINE_COUNT	   .BLKB 2	; Engine count
$DEF	SPDT$IB_PI_VENDOR_UNIQUE   .BLKB 14	; Vendor unique
$DEF	SPDT$IS_PI_PDA_SIZE	   .BLKL 1	; Size of private data area
$DEF	SPDT$IS_PI_ASYNC_EVENT	   .BLKL 1	; Asynchronous event
						;  capabilities
	$VIELD SPDT,0,<-
	<UNSOL_BUS_RESET,,M>,-			;  Unsolicited SCSI bus reset
	<UNSOL_RESEL,,M>,-			;  Unsolicited reselection
	<RSVD4,,M>,-				;  Reserved
	<SCSI_AEN,,M>,-				;  SCSI AEN
	<BDR_TARGET,,M>,-			;  BDR sent to target
	<SIM_REG,,M>,-				;  SIM module registered
	<SIM_DEREG,,M>,-			;  SIM module de-registered
	<NEW_DEV_RESCAN,,M>,-			;  New devices found on rescan
	<RSVD5,16,M>,-				;  Reserved
	<VU,8,M>,-				;  Vendor unique
	>
$DEF	SPDT$IB_PI_PATH_ID	   .BLKB 1	; Path id assigned
$DEF	SPDT$IB_PI_SCSI_DEV_ID	   .BLKB 1	; SCSI device id (of initiator)
$DEF	SPDT$IW_PI_RESERVED	   .BLKB 2	; Reserved
$DEF	SPDT$IB_PI_VENDOR_ID_SIM	   .BLKB 16 ; Vendor id of SIM supplier
$DEF	SPDT$IB_PI_VENDOR_ID_HBA	   .BLKB 16 ; Vendor id of HBA supplier
$DEF	SPDT$IS_PI_OSD		   .BLKL 1	; OSD specific use
	SPDT$C_PI_DATA_SIZE= <. - SPDT$C_START_PI_DATA>
$DEF	SPDT$IS_CAR_ADDR	   .BLKL 1	; Carrier address
$DEF	SPDT$IS_STOPPER_ADDR	   .BLKL 1	; Stopper address
$DEF	SPDT$IS_QBUF_ADDR	   .BLKL 1	; Queue Buffer address
$DEF	SPDT$IS_CAM_STS		   .BLKL 1	; Returned CAM status
$DEF	SPDT$IS_DEV_CONNECT_ID	   .BLKL 1	; Saved device connection id

. = .+<8-<.&7>>					; Make sure it is lw aligned
$DEF	SPDT$PQ_IOHANDLE_REG	   .BLKQ 1	; IOHANDLE for SIMport regs.
$DEF	SPDT$PQ_IOHANDLE_CLEAR_INT .BLKQ 1	; IOHANDLE for CLEAR_INT
$DEF	SPDT$PQ_IOHANDLE_RESET	   .BLKQ 1	; IOHANDLE for RESET
$DEF	SPDT$IQ_PHYS_ADDR	   .BLKQ 1	; Bus phys offset for MAP_IO
$DEF	SPDT$IS_ADAPTER_TYPE	   .BLKL 1	; Adapter type
	SPDT$C_TURBOCHANNEL = 0			;  Turbochannel
	SPDT$C_PCI = 1				;  PCI
$DEF	SPDT$IQ_DMA_SIZE	   .BLKQ 1	; Direct-DMA window size.
	.IF DEFINED PKSHISTORY
$DEF	SPDT$IS_CARRIER_PTR	   .BLKL 1
$DEF	SPDT$IS_BUFFER_PTR	   .BLKL 1
$DEF	SPDT$IS_CARRIER_HIST	   .BLKL 50	; History of carrier addresses
SPDT$C_CHIST_END = .
$DEF	SPDT$IS_BUFFER_HIST	   .BLKL 100	; History of buffer indexes
SPDT$C_BHIST_END = .
	.ENDC
	.SYMBOL_ALIGNMENT QUAD
SPDT$C_PKSLENGTH = .
	$DEFEND SPDT

.PAGE
	.SBTTL	Adapter Block Definitions
;
; Adapter Block definition
;
; Only the first 80 bytes are visible to the SIMport adapter. The remaining
; bytes are reserved for PKSDRIVERs private use.
;
; NOTE: All pointers are quadword-long addresses in SIMport, and hence
; they are defined as PQ type.
;
	.SYMBOL_ALIGNMENT QUAD

	$DEFINI AB,GLOBAL
$DEF	AB$IW_QB_SIZE		.BLKW	1	; Size (bytes) of a queue buffer
	AB$C_QB_SIZE = ^XC0			; Max. size of the adapter
						;  visible Queue Buffer area
$DEF	AB$IB_CCB_PTR_SIZE	.BLKB	1	; Bytes allocated for CCB ptr
	AB$C_CCB_PTR_SIZE = 8			; Size of CCB pointer
$DEF	AB$IB_SBZ		.BLKB	13	; Should be zero
$DEF	AB$PQ_DACQ_HEAD		.BLKQ	1	; DACQ head pointer (physical)
$DEF	AB$PQ_DACQ_TAIL		.BLKQ	1	; DACQ tail pointer (virtual)
$DEF	AB$PQ_ADRQ_HEAD		.BLKQ	1	; ADRQ head pointer (virtual)
$DEF	AB$PQ_ADRQ_TAIL		.BLKQ	1	; ADRQ tail pointer (physical)
$DEF	AB$PQ_DAFQ_HEAD		.BLKQ	1	; DAFQ head pointer (physical)
$DEF	AB$PQ_DAFQ_TAIL		.BLKQ	1	; DAFQ tail pointer (virtual)
$DEF	AB$PQ_ADFQ_HEAD		.BLKQ	1	; ADFQ head pointer (virtual)
$DEF	AB$PQ_ADFQ_TAIL		.BLKQ	1	; ADFQ tail pointer (physical)
;
; The following additional fields are used by PKSDRIVER for its private use.
; They are not visible to the adapter.
;
$DEF	AB$IQ_FILL		.BLKQ 4		; Separate port driver specific
						;  section
$DEF	AB$PQ_PA		.BLKQ 1		; Physical address of this AB
$DEF	AB$PS_CAR_FLINK		.BLKL 1		; Forward link of Carriers
$DEF	AB$PS_CAR_BLINK		.BLKL 1		; Backward link of Carriers
$DEF    AB$IS_INT_HOLDOFF	.BLKL 1		; Interrupt Holdoff Timer field
$DEF	AB$IS_RSP_FKLCK		.BLKL 1		; Response interrupt fork
						;  block lock field
	$VIELD	AB,0,<-				;  Interlock bit 
	<RSP_FKLOCK,,M>,-
	>
$DEF	AB$IS_ADP_FKLCK		.BLKL 1		; Adapter error fork block
						;  lock field
	$VIELD	AB,0,<-				;  Interlock bit 
	<ADP_FKLOCK,,M>,-
	>
;
; Make sure the following fork blocks are quadword aligned.
;
$DEF	AB$PS_RSP_FKBLK		.BLKB 32	; Fork block for response
						;  interrupts
$DEF	AB$PS_ADP_FKBLK		.BLKB 32	; Fork block used for Adapter
						;  error interrupts

;
; The following block of memory is used for keeping port counters.
; The port counters read from the CNTRD response are added to the current
; totals kept in this block. When the port is re-initialized, this
; block is cleared to reset the counters.
;
	AB$C_NUM_PORT_CNTRS = 28		; Number of port counters
	AB$C_PORT_CNTRS_SIZE = <AB$C_NUM_PORT_CNTRS * 4> ; Size (in bytes) of
						         ; port counter block
$DEF	AB$PS_COUNTERS		.BLKL AB$C_NUM_PORT_CNTRS

;
; CRCTX pointer for mapping the AB, if necessary.
;
$DEF	AB$PS_CRCTX		.BLKL 1

$DEF	AB$IW_SIZE		.BLKW 1		; RCL003

AB$C_SIZE = .					; Adapter Block size
	$DEFEND AB
	.SYMBOL_ALIGNMENT NONE

.PAGE
	.SBTTL	Carrier Definitions
;
; Carrier structure definition
;
; PKSDRIVER creates a quadword aligned Queue Carrier, located in host memory, 
; for each command to form a one way linked list representing a queue. Each
; Queue Carrier is 72 bytes long. The first 32 bytes are for PKSDRIVER internal
; use only. The adapter-visible part is a 32-byte aligned, 40 byte long
; structure.
;
	.SYMBOL_ALIGNMENT QUAD

	$DEFINI CAR,GLOBAL

$DEF	CAR$PS_FLINK		.BLKL 1		; Forward link of Carrier
$DEF	CAR$PS_BLINK		.BLKL 1		; Backward link of Carrier
$DEF	CAR$IW_SIZE		.BLKW 1		; Structure size in bytes
$DEF    CAR$IB_TYPE		.BLKB 1		; Structure type
$DEF	CAR$IB_SUBTYPE		.BLKB 1		; Structure subtype
$DEF	CAR$PS_CRCTX		.BLKL 1		; CRCTX to map this carrier
$DEF	CAR$PQ_PA		.BLKQ 1		; Physical address of this
						;  Carrier's adapter-visible
						;  section
$DEF	CAR$IQ_FILL2		.BLKQ 1		; Filler for 32-byte alignment
	CAR$C_DRVLEN = .
;
; Adapter-visible part of Carrier begins here.
;
$DEF	CAR$PQ_NEXT_PTR		.BLKQ 1		; Physical address of the next
						;  Carrier in a port queue
$DEF	CAR$PQ_QBUF_PTR		.BLKQ 1		; Phys addr of the associated
						;  Q_Buffer when driver writes
						;  to this location, VA of the
						;  associated Q_Buffer when
						;  the adpater writes to this
						;  location
$DEF    CAR$PQ_QBUF_TOKEN	.BLKQ 1		; VA of the associated
						;  Q_Buffer header
$DEF	CAR$PQ_CAR_TOKEN	.BLKQ 1		; VA of this Carrier header


$DEF	CAR$IB_FUNC_CODE	.BLKB 1		; CAM or SIMport Function code
	;
	; CAM command function code definitions.
	;
	CAR$C_NOP		= ^X00		; No operation
	CAR$C_EXEC_SCSI_IO	= ^X01		; Execute SCSI I/O
	CAR$C_PATH_INQUIRY	= ^X03		; Path inquiry
	CAR$C_REL_SIM_QUEUE	= ^X04		; Release SIM queue
	CAR$C_SET_ASYNC_CB	= ^X05		; Set asynch callback
	CAR$C_ABORT_SCSI_IO	= ^X10		; Abort specified SCSI I/O CCB
	CAR$C_RESET_SCSI_BUS	= ^X11		; Reset indicated SCSI channel
	CAR$C_RESET_SCSI_DEV	= ^X12		; Reset indicated SCSI device
	CAR$C_TERM_SCSI_IO	= ^X13		; Terminate specified SCSI CCB
	CAR$C_ENABLE_LUN	= ^X30		; Enable LUN for target mode
	CAR$C_ACCEPT_TARGET_IO	= ^X32		; Accept host target mode CCB
	CAR$C_CONT_TARGET_IO	= ^X33		; Continue host target mode CCB
	;
	; SIMport vendor unique command function code definitions.
	;
	CAR$C_SET_ADAP_STATE   = ^X80		; Set adapter state
	CAR$C_SET_PARAM        = ^X81		; Set parameters
	CAR$C_SET_CHAN_STATE   = ^X82		; Set channel state
	CAR$C_SET_DEV_STATE    = ^X83		; Set device state
	CAR$C_VER_ADAP_SANITY  = ^X84		; Verify adapter sanity
	CAR$C_READ_CNTRS       = ^X85		; Read counters
	CAR$C_BSD_RESPONSE     = ^X86		; BSD response
	CAR$C_BUS_RESET_CMD    = ^X87		; Bus reset command
	CAR$C_UNSOL_RESEL      = ^X88		; Unsolicited reselection
	CAR$C_CHAN_DISABLED    = ^X89		; Channel disabled
	CAR$C_DEV_DISABLED     = ^X8A		; Device disabled
	CAR$C_TARGET_EVENT_ACK = ^X8B		; Target event unsolicited msg
	CAR$C_SET_TARGET_STATE = ^X8C		; Marker for target event
						;  rundown
	CAR$C_ADAP_RECOV_COMP  = ^X8E		; Adapter recovery completed
	;
	; SIMport vendor unique response code definitions.
	;
	CAR$C_ADAP_STATE_SET   = ^X80		; Adapter state set response
	CAR$C_PARAMETER_SET    = ^X81		; Parameter set response
	CAR$C_CHAN_STATE_SET   = ^X82		; Channel state set response
	CAR$C_DEV_STATE_SET    = ^X83		; Device state set response
	CAR$C_ADAP_SANITY_VER  = ^X84		; Adapter sanity verified rsp
	CAR$C_CNTRS_READ       = ^X85		; Counters read response
	CAR$C_BSD_REQUEST      = ^X86		; BSD request response
	CAR$C_BUS_RESET_REQ    = ^X87		; Bus reset request response
	CAR$C_TARGET_EVENT     = ^X8B		; Target event unsolicited msg
	CAR$C_TARGET_STATE_SET = ^X8C		; Marker for target event
						;  rundown
$DEF	CAR$IB_STATUS		.BLKB 1		; Status of CAM or SIMport cmd
	$VIELD CAR,0,<-				; Carrier status definitions
	<CAM_STATUS,6,M>,-			; CAM status
	<SIM_QUEUE_FROZEN,1,M>,-		; SIM queue is frozen
	<AUTOSENSE_DATA_VALID,1,M>,-		; Autosense data is valid
	>
	CAR$C_REQ_IN_PROGRESS = ^X00		; Request in progress
	CAR$C_REQ_COMP_NO_ERROR = ^X01		; Request completed wo error
	CAR$C_HOST_ABORTED_REQ = ^X02		; Request aborted by host
	CAR$C_UNABLE_ABORT_REQ = ^X03		; Unable to abort request
	CAR$C_REQ_COMP_WITH_ERROR = ^X04	; Request completed with error
	CAR$C_CAM_BUSY = ^X05			; CAM busy
	CAR$C_INVALID_REQ = ^X06		; Invalid request
	CAR$C_INVALID_PATH_ID = ^X07		; Invalid path ID
	CAR$C_DEV_NOT_THERE = ^X08		; SCSI device not installed
	CAR$C_UNABLE_TERM_IOP = ^X09		; Unable to term IO process
	CAR$C_TARGET_SEL_TO = ^X0A		; Target selection timeout
	CAR$C_COMMAND_TO = ^X0B			; Command timeout
	CAR$C_MSG_REJECT_REC = ^X0D		; Message reject received
	CAR$C_BUS_RESET_TXRX = ^X0E		; SCSI bus reset sent/received
	CAR$C_PARITY_ERR = ^X0F			; Uncorrectable parity error
	CAR$C_REQ_SENSE_FAILED = ^X10		; Autosense request sense failed
	CAR$C_NO_HBA = ^X11			; No HBA detected
	CAR$C_DATA_OVER_UNDER_RUN = ^X12	; Date overrun/underrun
	CAR$C_UNEXP_BUS_FREE = ^X13		; Unexpected bus free
	CAR$C_BUS_PHASE_SEQ_FAIL = ^X14		; Bus phase sequence failure
	CAR$C_INVAL_CCB_SIZE = ^X15		; CCB length inadquate
	CAR$C_NO_REQ_CAPABILITY = ^X16		; Cannot provide req capability
	CAR$C_DEVICE_RESET_TX = ^X17		; Bus device reset sent
	CAR$C_TERM_IOP = ^X18			; Terminate IO process
	CAR$C_UNRECOV_HBA_ERROR = ^X19		; Unrecoverable HBA error
	CAR$C_SCSI_BUS_RESET_DENIED = ^X1A	; SCSI bus reset denied
;
; Target mode only status codes.
;
	CAR$C_INIT_DET_ERROR = ^X33		; Initiator detected error
	CAR$C_RESOURCE_UNAVAIL = ^X34		; Resource unavailable
	CAR$C_UNACKED_EVENT_BY_HOST = ^X35	; Unacked event by host
	CAR$C_MESSAGE_REC = ^X36		; Message received
	CAR$C_INVALID_CDB = ^X37		; Invalid CDB
	CAR$C_INVALID_LUN = ^X38		; Invalid LUN
	CAR$C_INVALID_TARGET_ID = ^X39		; Invalid target ID
	CAR$C_FUNC_NOT_IMPLEMENTED = ^X3A	; Function not implemented
	CAR$C_NO_NEXUS = ^X3B			; Nexus not established
	CAR$C_INVALID_INIT_ID = ^X3C		; Invalid initiator ID
	CAR$C_SCSI_CDB_REC = ^X3D		; SCSI CDB received
	CAR$C_LUN_ALREADY_ENABLED = ^X3E	; LUN already enabled
	CAR$C_SCSI_BUS_BUSY = ^X3F		; SCSI bus busy

$DEF	CAR$IW_FLAGS		.BLKW 1		; Flags for queue insert
						;  priority and status return
	$VIELD CAR,0,<-				; Carrier flag bits definitions
	<SIM_QUEUE_PRI,,M>,-			; SIM queue priority
	<RETURN_STS_FORMAT,,M>,-		; Return status format
	<FILLER1,14,M>,-			; Unused (SBZ)
	>
$DEF	CAR$IS_CONNECT_ID	.BLKL 1		; CAM defined Connect id
	CAR$IB_RESERVED  = CAR$IS_CONNECT_ID
	CAR$IB_PATH_ID   = CAR$IB_RESERVED + 1
	CAR$IB_TARGET_ID = CAR$IB_PATH_ID + 1
	CAR$IB_LUN       = CAR$IB_TARGET_ID + 1
CAR$C_LENGTH = .				; Length of Queue Carrier
CAR$C_ADPLEN = . - CAR$C_DRVLEN			; Length of adapter-visible 
						; section.
CAR$C_NUM_CARRIERS = 256			; Number of Queue Carriers
	$DEFEND CAR
	.SYMBOL_ALIGNMENT NONE

.PAGE
	.SBTTL	Queue Buffer Definitions
; 
; Queue Buffer structure definition
;
; PKSDRIVER returns a quadword aligned Queue Buffer to a class driver whenever
; a class driver invokes the SPI$ALLOC_CMD_BUFFER macro. Each Queue Buffer is
; 512 bytes long. The Queue Buffer is used as follows:
;
; 1.) The first 40 bytes contain a private area for explicit use by PKSDRIVER.
;     This section is not visible to the adapter.
;
; 2.) The next 24 bytes contain the CDB, built by the class driver, that is
;     passed to PKSDRIVER with the SPI$SEND call. This section is not visible
;     to the adapter.
;
; 3.) The next 192 bytes are the adapter visible section. They contain the CCB, 
;     built by PKSDRIVER, that is passed to the adapter. The first 24 bytes in
;     each CCB are the CAM Control Block common header. All CCBs have this same
;     common header. Additional fields beyond the CCB header are command
;     specific. The length of the CCB depends on the specific command.
;
; 4.) The remaining 256 bytes are the autosense buffer for returning autosense
;     data. The autosense buffer is pointed to by the QBUF$PS_SEN_BSD field in
;     the private data area of the Execute SCSI I/O command.
;
	.SYMBOL_ALIGNMENT QUAD

	$DEFINI QBUF,GLOBAL
;
; First 40 bytes are PKSDRIVER private area. This section is not visible to
; the adapter.
;
$DEF	QBUF$PS_FLINK		.BLKL 1		; Forward link of Q_Buffers
						;  in a queue
	QBUF$PS_INIT_CRCTX_PTR = QBUF$PS_FLINK    ; Init CRCTX pointer
$DEF	QBUF$PS_BLINK		.BLKL 1		; Backward link of Q_Buffers
						;  in a queue
$DEF	QBUF$IW_SIZE		.BLKW 1		; Structure size
$DEF	QBUF$IB_TYPE		.BLKB 1		; Structure type
$DEF	QBUF$IB_SUBTYPE		.BLKB 1		; Structure subtype
$DEF	QBUF$PS_SCDT		.BLKL 1		; Corresponding SCDT addr for
						;  this Queue Buffer
	QBUF$PS_KPB = QBUF$PS_SCDT		; KPB address of stalled thread
$DEF	QBUF$PQ_PA_QBUF		.BLKQ 1		; Physical address of
						;  adapter-visible section
$DEF	QBUF$PS_CARRIER		.BLKL 1		; Corresponding carrier header
						;  address. Used when we need
						;  to do garbage collection on
						;  port crash
$DEF	QBUF$PS_SCDRP		.BLKL 1		; Address of SCDRP associated
						;  with this I/O request
$DEF	QBUF$PS_PADBLK		.BLKL 1		; Pad block address
$DEF	QBUF$IW_NUMSEG		.BLKW 1		; Data segments for this cmd
$DEF	QBUF$IW_PORT_STS	.BLKW 1		; Port status to be returned
						;  to class driver
;
; The next 24 bytes contain the CDB, built by the class driver, that is
; passed to PKSDRIVER with the SPI$SEND call. This section is not visible
; to the adapter.
;
$DEF	QBUF$IB_CMDBUF		.BLKB 24	; 24 bytes of command buffer
						;  area to be passed back to
						;  class driver
	QBUF$IS_STATUS = QBUF$IB_CMDBUF		;  First longword is SCSI status
	QBUF$IS_CMDLEN = QBUF$IS_STATUS+4	;  Second longword is SCSI
						;  command length
	QBUF$IB_CMD    = QBUF$IS_CMDLEN+4	;  The rest is for SCSI command
QBUF$C_DRVLEN = .				; Port driver private header
						;  length 
;
; Adapter-visible Queue Buffer section begins here.
;
; The first 24 bytes are the CAM Control Block common header. All CCBs
; have this same common header.
;
$DEF	QBUF$PQ_CCB_ADDR	.BLKQ 1		; CCB header address (PA)
$DEF	QBUF$IW_CCB_LENGTH	.BLKW 1		; Length (in bytes) of CCB
$DEF	QBUF$IB_FUNC_CODE	.BLKB 1		; CAM/SIMport function code
	;
	; CAM command function code definitions.
	;
	QBUF$C_NOP		= ^X00		; No operation
	QBUF$C_EXEC_SCSI_IO	= ^X01		; Execute SCSI I/O
	QBUF$C_PATH_INQUIRY	= ^X03		; Path inquiry
	QBUF$C_REL_SIM_QUEUE	= ^X04		; Release SIM queue
	QBUF$C_SET_ASYNC_CB	= ^X05		; Set asynch callback
	QBUF$C_ABORT_SCSI_IO	= ^X10		; Abort specified SCSI I/O CCB
	QBUF$C_RESET_SCSI_BUS	= ^X11		; Reset indicated SCSI channel
	QBUF$C_RESET_SCSI_DEV	= ^X12		; Reset indicated SCSI device
	QBUF$C_TERM_SCSI_IO	= ^X13		; Terminate specified SCSI CCB
	QBUF$C_ENABLE_LUN	= ^X30		; Enable LUN for target mode
	QBUF$C_ACCEPT_TARGET_IO	= ^X32		; Accept host target IO
	QBUF$C_CONT_TARGET_IO	= ^X33		; Continue host target IO
	;
	; SIMport vendor unique command function code definitions.
	;
	QBUF$C_SET_ADAP_STATE   = ^X80		; Set adapter state
	QBUF$C_SET_PARAM        = ^X81		; Set parameters
	QBUF$C_SET_CHAN_STATE   = ^X82		; Set channel state
	QBUF$C_SET_DEV_STATE    = ^X83		; Set device state
	QBUF$C_VER_ADAP_SANITY  = ^X84		; Verify adapter sanity
	QBUF$C_READ_CNTRS       = ^X85		; Read counters
	QBUF$C_BSD_RESPONSE     = ^X86		; BSD response
	QBUF$C_BUS_RESET_CMD    = ^X87		; Bus reset command
	QBUF$C_UNSOL_RESEL      = ^X88		; Unsolicited reselection
	QBUF$C_CHAN_DISABLED    = ^X89		; Channel disabled
	QBUF$C_DEV_DISABLED     = ^X8A		; Device disabled
	QBUF$C_TARGET_EVENT_ACK = ^X8B		; Target event acknowledge msg
	QBUF$C_SET_TARGET_STATE = ^X8C		; Set Target state
	;
	; SIMport vendor unique response code definitions.
	;
	QBUF$C_ADAP_STATE_SET   = ^X80		; Adapter state set response
	QBUF$C_PARAM_SET        = ^X81		; Parameter set response
	QBUF$C_CHAN_STATE_SET   = ^X82		; Channel state set response
	QBUF$C_DEV_STATE_SET    = ^X83		; Device state set response
	QBUF$C_ADAP_SANITY_VER  = ^X84		; Adapter sanity verified rsp
	QBUF$C_CNTRS_READ       = ^X85		; Counters read response
	QBUF$C_BSD_REQUEST      = ^X86		; BSD request response
	QBUF$C_BUS_RESET_REQ    = ^X87		; Bus reset request response

$DEF	QBUF$IB_CAM_STS		.BLKB 1		; CAM status returned from SIM

$DEF	QBUF$IS_CONNECT_ID	.BLKL 1		; Connection id
	QBUF$IB_RSVD      = QBUF$IS_CONNECT_ID	;  Reserved field
	QBUF$IB_PATH_ID	  = QBUF$IB_RSVD+1	;  Path id
	QBUF$IB_TARGET_ID = QBUF$IB_PATH_ID+1	;  Target id
	QBUF$IB_LUN       = QBUF$IB_TARGET_ID+1	;  LUN

$DEF	QBUF$IS_CAM_FLAGS	.BLKL 1		; CAM flags
	QBUF$IB_CAM_FLAGS_1 = QBUF$IS_CAM_FLAGS
	$VIELD QBUF,0,<-		; Define bit used with CAM flags 1
	<CDB_POINTER,,M>,-		;  CDB is a pointer
	<TQ_ACTION_ENB,,M>,-		;  Tagged queue action enable
	<LINKED_CDB,,M>,-		;  Linked CDB
	<CALLBACK_DIS,,M>,-		;  Disable callback on completion
	<SCATTER_GATHER,,M>,-		;  Scatter/gather
	<AUTOSENSE_DIS,,M>,-		;  Disable autosense
	<DATA_TRAN_DIR,2,M>,-		;  Data transfer direction
	>
	QBUF$M_NO_DATA_TX = ^XC0	; No data to transfer
	QBUF$M_WRITE      = ^X80	; Write data (from initiator to target)
	QBUF$M_READ       = ^X40	; Read data (from target to initiator)

	QBUF$IB_CAM_FLAGS_2 = QBUF$IB_CAM_FLAGS_1+1
	$VIELD QBUF,0,<-		; Define bit used with CAM flags 2
	<RSVD,,M>,-			;  Reserved
	<ENGINE_SYNC,,M>,-		;  Engine Syncronize
	<SIM_Q_FREEZE_DIS,,M>,-		;  SIM queue freeze disable
	<SIM_Q_FREEZE,,M>,-		;  SIM queue freeze
	<SIM_Q_PRIORITY,,M>,-		;  SIM queue priority
	<SYNC_TRAN_DIS,,M>,-		;  Synchronous transfer disable
	<SYNC_TRAN_INIT,,M>,-		;  Synchronous transfer initiate
	<DIS_DISCONNECT,,M>,-		;  Disable disconnect
	>

	QBUF$IB_CAM_FLAGS_3 = QBUF$IB_CAM_FLAGS_2+1
	QBUF$IB_CAM_FLAGS_4 = QBUF$IB_CAM_FLAGS_3+1
;
; CAM flag bits defined for Host Target mode.
;
	$VIELD QBUF,0,<-		; Define bit used with CAM flags 2
	<RSVD,3,M>,-			;  Reserved
	<TARGET_MODE,,M>,-		;  Target mode 0 = Host 1 = Ph cog
	<RSVD,,M>,-			;  Reserved
	<TERMINATE_IO,,M>,-		;  Terminate IO process supported
	<DISCONS_MANDATORY,,M>,-	;  Disconnect for each CCB for LUN
	<SEND_STATUS,,M>,-		;  Send status byte after data phase
	>

$DEF	QBUF$IS_PADDING		.BLKL 1		; Padding for memory alignment

QBUF$C_COMMON_HDR_SIZE = . - QBUF$C_DRVLEN 	; Common header length
QBUF$C_END_OF_COMMON_HDR = .
;
; CAM Control Block common header ends here. The following definitions
; define the specific offsets for each command. 
;
;
; Set Adapter State command specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IB_ADP_STATE 	.BLKB	1	; Requested adapter state
	QBUF$C_SET_ADAP_STATE_ENB = ^X01	; Request ENABLED state
	QBUF$C_SET_ADAP_STATE_DIS = ^X02	; Request DISABLED state
$DEF	QBUF$IB_SBZ0 		.BLKB	3	; Should be zero
	QBUF$C_SET_ADAP_STATE_SIZE = . - QBUF$C_DRVLEN	; Set adap state length
;
; Adapter State Set response specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IB_ADP_STATE_RSP	.BLKB	1	; Requested adapter state
$DEF	QBUF$IB_N_HOST_SQ	.BLKB	1
$DEF	QBUF$IW_FLAGS		.BLKW	1
$DEF	QBUF$IW_N_ADAP_QC	.BLKW	1
$DEF	QBUF$IB_KA_TIME		.BLKB	1
$DEF	QBUF$IB_N_FREEQ		.BLKB	1
$DEF	QBUF$IB_N_CHAN		.BLKB	1
$DEF	QBUF$IB_XFER_ALIGN	.BLKB	1
$DEF	QBUF$IB_N_SAC		.BLKB	1
$DEF	QBUF$IB_SBZ1 		.BLKB	5
$DEF	QBUF$IQ_NODES_ON_CHAN	.BLKQ	1
;
; Set Parameters command specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IB_NT_LUNS		.BLKB	1	; Target mode LUNs active
$DEF	QBUF$IB_N_HOST_SG	.BLKB	1	; Number of host segments
$DEF	QBUF$IW_SET_PARAM_FLAGS	.BLKW	1	; Flags
	$VIELD QBUF,0,<-			; Bits used with CAM flags 1
	<ENB_COUNTERS,,M>,-			;  Enable port counters
	<FILLER,15,M>,-				;  Filler
	>
$DEF	QBUF$IS_SYSTEM_TIME	.BLKL	1	; System time in milliseconds
$DEF	QBUF$IS_INT_HOLDOFF	.BLKL	1	; Interrupt holdoff time
$DEF	QBUF$IS_RP_TIMER	.BLKL	1	; Reset reply time
$DEF	QBUF$IQ_HOST_SG_BSD	.BLKQ	1	; BSD of host segment
	QBUF$C_SET_PARAM_SIZE = . - QBUF$C_DRVLEN ; Set Parameter length
;
; The Parameters Set response message does not contain any additional fields
; in the CCB. The following are the valid values for the status field in
; the Queue Carrier.
;
	QBUF$C_INV_COMMAND = 0			; Cmd not supported or the
						;  cmd format is incorrect
	QBUF$C_SUCCESS = 1			; Param set as requested
	QBUF$C_NOT_DISABLED = -2		; Adapter not in disabled state
	QBUF$C_INV_INT_HOLDOFF = -5		; Int holdoff timer is not
						;  valid or not supported
	QBUF$C_INV_HOST_SG = -6			; Insufficient host memory
						;  allocated
;
; Set Channel State command and Channel State Set response specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IB_CHN_STATE	.BLKB	1	; Requested channel state
	QBUF$C_SET_CHAN_STATE_ENB = 1		; Request enabled state 
	QBUF$C_SET_CHAN_STATE_DIS = 2		; Request disabled state 

$DEF	QBUF$IB_SBZ3		.BLKB	3	; Should be zero
$DEF	QBUF$IB_CHAN_ID		.BLKB	1	; Channel ID
$DEF	QBUF$IB_NODE_ID		.BLKB	1	; Node ID
$DEF	QBUF$IB_SBZ4		.BLKB	2	; Should be zero
	QBUF$C_SET_CHAN_STATE_SIZE = . - QBUF$C_DRVLEN	; Set chan state length
;
; Set Device State command and Device State Set response specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IB_DEV_STATE	.BLKB	1	; Requested device state
	QBUF$C_SET_DEV_STATE_ENB = 1		; Request enabled state 
$DEF	QBUF$SBZ5		.BLKB	7	; Should be zero
	QBUF$C_SET_DEV_STATE_SIZE = . - QBUF$C_DRVLEN ; Set device state length
;
; Read Counters command specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IW_SBZ6		.BLKW	1	; Should be zero
$DEF	QBUF$IW_CTR_FLAGS	.BLKW	1	; Counter flags
	$VIELD QBUF,0,<-			; Bits used with counters flags
	<CLR_COUNTERS,,M>,-			;  Clear port counters
	<FILLER,15,M>,-				;  Filler
	>
$DEF	QBUF$IB_SBZ7		.BLKB	4	; Should be zero
	QBUF$C_READ_COUNTERS_SIZE = . - QBUF$C_DRVLEN ; Read counters length
;
; Counters Read response specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IB_SBZ8		.BLKB	4	; Should be zero
$DEF	QBUF$IS_REQ_COUNTERS	.BLKL	21	; Required counters
$DEF	QBUF$IS_IMP_COUNTERS	.BLKL	20	; Implementation specific
	QBUF$C_NUM_COUNTERS = <. - QBUF$IS_REQ_COUNTERS>/4 ; Number of counters
;
; Bus Reset Request command specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IB_REASON		.BLKB	1	; Bus reset reason code
	QBUF$C_REJECT_BDR  = 1			; Target rejected BDR attempt
	QBUF$C_PHASE_ERROR = 2			; Phase error 
	QBUF$C_DATA_OUT    = 3			; Data overrun in dataout phase
	QBUF$C_RSVD_PHASE  = 4			; Target entered reserved phase
	QBUF$C_NO_MSG_OUT  = 5			; Target did not enter msg out
$DEF	QBUF$IB_TRGT_ID		.BLKB	1	; Target id
$DEF	QBUF$IB_SBZ10		.BLKB	6	; Should be zero
$DEF	QBUF$IQ_CCB		.BLKQ	1	; VA of CCB active on the bus
	QBUF$C_BUS_RESET_REQ_SIZE = . - QBUF$C_DRVLEN ; Bus reset request length
;
; The Bus Reset command does not define any fields in the CCB. The action
; authorized by the host is in the status field of the Queue Carrier.
; The folowing defines the values for the status field in the Queue Carrier.
;
	CAR$C_INV_COMMAND = 0			; Command not supported
	CAR$C_SUCCESS     = 1			; Adapter should reset bus
	CAR$C_NO_RESET    = -13			; Adapter should not reset bus
;
; Execute SCSI I/O command specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$PS_PERIPH_DRIVER_PTR .BLKQ	1	; Periph driver pointer
	QBUF$PS_BSM_PTR = QBUF$PS_PERIPH_DRIVER_PTR ; BSM pointer
$DEF	QBUF$PS_NEXT_CCB_PTR	  .BLKQ	1	; Next CCB pointer
;
; N.B.: the following field is described as "not used" by the SIMport
; spec.  It's being used here, on platforms that have map registers, as
; a CRCTX pointer for allocating/deallocating map registers to map the
; Queue Buffer.  Implicit in this mechanism is the assumption that the
; adapter DOES NOT ALTER this location, i.e., that it either leaves it
; alone completely or reads and rewrites it with the same value.  Current
; controllers (KZTSA and KZPSA) exhibit the expected behavior.  There is
; some danger, however, that some future SIMport adapter will break this
; assumption.
;
; Done properly, the CRCTX pointer should be carved out of the autosense
; buffer, by shortening up the latter by a longword.  This has not been
; done yet, due to scheduling exigencies/de-risking.
;
$DEF	QBUF$PS_REQ_MAP_INFO	  .BLKQ	1	; Req map info
	QBUF$PS_CRCTX_PTR = QBUF$PS_REQ_MAP_INFO    ; CRCTX pointer
$DEF	QBUF$PS_CB_COMPLETION	  .BLKQ	1	; Callback routine
$DEF	QBUF$PS_SG_LIST_BUFF_PTR  .BLKQ	1	; SG buff pointer
$DEF	QBUF$IS_DATA_TRAN_SIZE	  .BLKL	1	; Data tranfer size
$DEF	QBUF$IS_PAD1		  .BLKL	1	; Padding for alignment
$DEF	QBUF$PS_SENSE_BUFF_PTR	  .BLKQ	1	; Autosense buffer ptr
$DEF	QBUF$IB_SENSE_BUFF_SIZE	  .BLKB	1	; Sense buffer size
$DEF	QBUF$IB_CDB_SIZE	  .BLKB	1	; CDB size
$DEF	QBUF$IW_NUM_SG_ENTRIES	  .BLKW	1	; Number SG entries
$DEF	QBUF$IS_SCSI_IO_VU	  .BLKL	1	; Vendor unique
$DEF	QBUF$IB_SCSI_STS	  .BLKB	1	; SCSI status
$DEF	QBUF$IB_AS_RESID_LENGTH	  .BLKB	1	; Autosense resid length
$DEF	QBUF$IW_RSVD1		  .BLKW	1	; Reserved
$DEF	QBUF$IS_RESID_LENGTH	  .BLKL	1	; Residual length
$DEF	QBUF$IB_CDB		  .BLKB	12	; CDB bytes
$DEF	QBUF$IS_PAD2		  .BLKL	1	; Padding
$DEF	QBUF$IS_TIMEOUT_VALUE	  .BLKL	1	; Timeout value
$DEF	QBUF$IS_PAD3		  .BLKL	1	; Padding
$DEF	QBUF$PS_MSG_BUFF_PTR	  .BLKQ	1	; Msg buffer pointer
$DEF	QBUF$IW_MSG_BUFF_SIZE	  .BLKW	1	; Msg buffer size
$DEF	QBUF$IW_VU_FLAGS	  .BLKW	1	; VU flags
$DEF	QBUF$IB_TAG_QUEUE_ACTION  .BLKB	1	; Tag queue action
	QBUF$C_SIMPLE_TAG    = ^X20		; Simple tag request
	QBUF$C_HEAD_OF_QUEUE = ^X21		; Queue head request
	QBUF$C_ORDERED_QUEUE = ^X22		; Ordered queue request
$DEF	QBUF$IB_TAG_ID		  .BLKB	1	; Tag ID
$DEF	QBUF$IB_INITIATOR_ID	  .BLKB	1	; Initiator ID
$DEF	QBUF$IB_RSVD2		  .BLKB	1	; Reserved
QBUF$C_EXEC_SCSI_IO_COMMON_SIZE = . - QBUF$C_DRVLEN - QBUF$C_COMMON_HDR_SIZE
;
; The following section describes the Execute SCSI I/O Private Data Area.
;
$DEF	QBUF$IB_SCSI_STAT	  .BLKB	1	; SCSI status
$DEF	QBUF$IB_SEN_RES		  .BLKB	1	; Sense residual length
$DEF	QBUF$IW_SBZ9		  .BLKW	1	; Should be zero
$DEF	QBUF$IS_DATA_RES	  .BLKL	1	; Data residual length
$DEF	QBUF$PS_DATA_BSD1	  .BLKQ	1	; Data 1 BSD
$DEF	QBUF$PS_DATA_BSD2	  .BLKQ	1	; Data 2 BSD
$DEF	QBUF$PS_CDB_BSD		  .BLKQ	1	; CDB BSD
$DEF	QBUF$PS_SEN_BSD		  .BLKQ	1	; Autosense BSD
$DEF	QBUF$PS_MSG_BSD		  .BLKQ	1	; Message BSD
$DEF	QBUF$PS_CCB_NEXT_PTR	  .BLKQ	1	; Next CCB pointer
	QBUF$C_EXEC_SCSI_IO_SIZE = . - QBUF$C_DRVLEN	; Exec SCSI I/O length
;
; Path Inquiry command specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IB_PI_VERSION	  .BLKB	1	; Version
$DEF	QBUF$IB_PI_SCSI_CABAP	  .BLKB	1	; SCSI capabilities
$DEF	QBUF$IB_PI_TARGET_MODE	  .BLKB	1	; Target mode
$DEF	QBUF$IB_PI_MISC		  .BLKB	1	; Miscellaneous
$DEF	QBUF$IW_PI_ENGINE_CNT	  .BLKW	1	; Engine count
$DEF	QBUF$IB_PI_PATH_INQ_VU	  .BLKB	14	; Vendor unique
$DEF	QBUF$IS_PI_PDA_SIZE	  .BLKL	1	; Private data area size
$DEF	QBUF$IS_PI_ASYNCH_EVENTS  .BLKL	1	; Asynchronous events
$DEF	QBUF$IB_PI_HIGH_PATH_ID	  .BLKB	1	; Highest path ID
$DEF	QBUF$IB_PI_SCSI_DEV_ID	  .BLKB	1	; SCSI device ID
$DEF	QBUF$IW_PI_RSVD7	  .BLKW	1	; Reserved
$DEF	QBUF$IB_PI_VENDOR_ID_SIM  .BLKB	16	; SIM vendor ID
$DEF	QBUF$IB_PI_VENDOR_ID_HBA  .BLKB	16	; HBA vendor ID
$DEF	QBUF$IS_PI_OSD		  .BLKL	1	; OS use
	QBUF$C_PATH_INQ_SIZE = . - QBUF$C_DRVLEN ; Path Inquiry length
;
; Enable LUN command specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IW_GROUP_6_VU_CDB	  .BLKW	1	; Group 6 VU CDB lengths
$DEF	QBUF$IW_GROUP_7_VU_CDB	  .BLKW	1	; Group 7 VU CDB lengths
$DEF	QBUF$IB_PAD1		  .BLKB	4	; Padding for alignment
$DEF	QBUF$PS_IMMED_NOTIFY_CCBS .BLKQ	1	; Immediate notify CCB list
$DEF	QBUF$IS_NUM_IMMED_NOTIFY_CCBS .BLKL 1	; Number immediate notify CCBs
$DEF	QBUF$IB_PAD2		  .BLKB	4	; Padding for alignment
$DEF	QBUF$PS_TARGET_CCBS	  .BLKQ	1	; Target CCB list
$DEF	QBUF$IS_NUM_TARGET_CCBS   .BLKL 1	; Number target CCBs
$DEF	QBUF$IB_PAD3		  .BLKB	4	; Padding for alignment
;
; Enable LUN Private Data Area offsets.
;
$DEF	QBUF$PS_ACCEPT_LIST_HEAD  .BLKQ	1	; Accept target IO list head
$DEF	QBUF$PS_ACCEPT_LIST_TAIL  .BLKQ	1	; Accept target IO list tail
	QBUF$C_ENABLE_LUN_SIZE = . - QBUF$C_DRVLEN ; Enable LUN length
;
; Accept Target IO command specific offsets.
;
; The Accept Target IO CCB has the same format as the Execute SCSI IO CCB
; except for the Private Data Area. The Accept Target IO CCB will use the
; same offsets for the common area of the CCB as the Execute SCSI IO command.
;
. = QBUF$C_END_OF_COMMON_HDR + QBUF$C_EXEC_SCSI_IO_COMMON_SIZE
;
; The following section describes the Accept Target IO Private Data Area.
;
$DEF	QBUF$IB_ATIO_SCSI_STAT	  .BLKB	1	; SCSI status
$DEF	QBUF$IB_ATIO_RSVD1	  .BLKB	1	; Reserved
$DEF	QBUF$IB_ATIO_CDB_LEN	  .BLKB	1	; CDB length
	QBUF$C_ATIO_CDB_BUFF_SIZE = ^X12	; CDB buffer size
$DEF	QBUF$IB_ATIO_SEN_LEN	  .BLKB	1	; Autosense buffer length
$DEF	QBUF$IS_ATIO_RSVD2	  .BLKB	20	; Reserved
$DEF	QBUF$PS_ATIO_CDB_BSD	  .BLKQ	1	; CDB BSD
$DEF	QBUF$PS_ATIO_SEN_BSD	  .BLKQ	1	; Autosense BSD
$DEF	QBUF$IS_ATIO_CMD_TIMEOUT  .BLKL	1	; Command timeout
$DEF	QBUF$IB_ATIO_TQA	  .BLKB	1	; Tagged queue action
$DEF	QBUF$IB_ATIO_TAG_ID	  .BLKB	1	; Tag ID
$DEF	QBUF$IB_ATIO_INIT_ID	  .BLKB	1	; Intiator ID
$DEF	QBUF$IB_ATIO_RSVD3	  .BLKB	9	; Reserved
	QBUF$C_ACCEPT_TARGET_IO_SIZE = . - QBUF$C_DRVLEN ; Accept target length
;
; Continue Target IO command specific offsets.
;
; The Continue Target IO CCB has the same format as the Execute SCSI IO CCB
; except for the Private Data Area. The Continue Target IO CCB will use the
; same offsets for the common area of the CCB as the Execute SCSI IO command.
;
. = QBUF$C_END_OF_COMMON_HDR + QBUF$C_EXEC_SCSI_IO_COMMON_SIZE
;
; The following section describes the Continue Target IO Private Data Area.
;
$DEF	QBUF$IB_CTIO_SCSI_STAT	  .BLKB	1	; SCSI status
$DEF	QBUF$IW_CTIO_RSVD1	  .BLKW	1	; Reserved
$DEF	QBUF$IB_CTIO_SEN_LEN	  .BLKB	1	; Autosense buffer length
$DEF	QBUF$IS_CTIO_DATA_RES	  .BLKL	1	; Data buffer residual length
$DEF	QBUF$PS_CTIO_DATA_BSD1	  .BLKQ	1	; First BSD for data buffer
$DEF	QBUF$PS_CTIO_DATA_BSD2	  .BLKQ	1	; Second BSD for data buffer
$DEF	QBUF$IQ_CTIO_RSVD2	  .BLKQ	1	; Reserved
$DEF	QBUF$PS_CTIO_SEN_BSD	  .BLKQ	1	; Autosense BSD
$DEF	QBUF$IQ_CTIO_RSVD3	  .BLKQ	1	; Reserved
$DEF	QBUF$PS_CTIO_CCB_NEXT_PTR .BLKQ	1	; PA of next CCB if linked cmd
	QBUF$C_CONTINUE_TARGET_IO_SIZE = . - QBUF$C_DRVLEN ; Cont. target length
;
; Set Target State command and Target State Set response specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IW_TS_SEQUENCE_ID	  .BLKW	1	; Sequence ID of target event
$DEF	QBUF$IB_TS_IID		  .BLKB	1	; Initiator ID of event
$DEF	QBUF$IB_TS_TAG_ID	  .BLKB	1	; Tag ID
$DEF	QBUF$IB_TS_FLAGS	  .BLKB	1	; Flags
$DEF	QBUF$IB_RSVD3		  .BLKB	3	; Reserved (SBZ)
	QBUF$C_TARGET_STATE_SIZE = . - QBUF$C_DRVLEN ; Set target length
;
; Target Event response specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IW_TE_SEQUENCE_ID	  .BLKW	1	; Sequence ID of event
$DEF	QBUF$IB_TE_IID		  .BLKB	1	; Initiator ID of event
$DEF	QBUF$IB_TE_TAG_ID	  .BLKB	1	; Tag ID
$DEF	QBUF$IB_TE_FLAGS	  .BLKB	1	; Flags
	$VIELD QBUF,0,<-			; Bits used with TE flags
	<TE_TAG_ID_VALID,,M>,-			;  Tag ID field is valid
	<RSVD,7,M>,-				;  Reserved (SBZ)
	>
$DEF	QBUF$IB_TE_SENSE_LEN	  .BLKB	1	; Number of sense bytes returned
$DEF	QBUF$IB_RSVD4		  .BLKB	2	; Reserved (SBZ)
$DEF	QBUF$IB_TE_MSG_CODE	  .BLKB	1	; SCSI message code
$DEF	QBUF$IB_TE_MSG_ARGS	  .BLKB	7	; SCSI message code data
$DEF	QBUF$IB_TE_SENSE_BUFF	  .BLKB	32	; Sense data returned
	QBUF$C_TARGET_EVENT_SIZE = . - QBUF$C_DRVLEN ; Target Event CCB length
;
; Target Event Acknowledged command specific offsets.
;
. = QBUF$C_END_OF_COMMON_HDR
$DEF	QBUF$IW_TEA_SEQUENCE_ID	  .BLKW	1	; Sequence ID
$DEF	QBUF$IB_TEA_IID		  .BLKB	1	; Initiator ID
$DEF	QBUF$IB_TEA_TAG_ID	  .BLKB	1	; Tag ID
$DEF	QBUF$IB_TEA_FLAGS	  .BLKB	1	; Flags
	$VIELD QBUF,0,<-			; Bits used with TEA flags
	<TEA_TAG_ID_VALID,,M>,-			;  Tag ID field is valid
	<CLEAR_ALL_EVENTS,,M>,-			;  Clear all events
	<RSVD,6,M>,-				;  Reserved (SBZ)
	>
$DEF	QBUF$IB_RSVD5		  .BLKB	3	; Reserved (SBZ)
	QBUF$C_TARGET_EVENT_ACK_SIZE = . - QBUF$C_DRVLEN ; TE Ack CCB length
;
; The remaining 256 bytes are the autosense buffer for returning autosense
; data. The autosense buffer is pointed to by the QBUF$PS_SEN_BSD field in
; the private data area of the Execute SCSI I/O command.
;
. = 256
	QBUF$C_AUTOSENSE_OFFSET = . - QBUF$C_DRVLEN
						; AS offset within adapter-
						; visible section.
$DEF    QBUF$IB_AUTOSENSE_BUF	.BLKB 256	; Autosense buffer
	QBUF$C_AUTOSENSE_BUF_SIZE = 255		; Autosense buffer length

QBUF$C_LENGTH = .				; Total Q_Buffer length
						;  (512 bytes)
QBUF$C_ADPLEN = . - QBUF$C_DRVLEN		; Length of adapter-visible
						; portion.
	$DEFEND QBUF

	.SYMBOL_ALIGNMENT NONE

.PAGE
	.SBTTL	Inquiry Data Definitions

	.SYMBOL_ALIGNMENT QUAD
	$DEFINI	INQ,GLOBAL

$DEF	INQ$PS_FLINK		.BLKL 1		; Forward link
$DEF	INQ$PS_BLINK		.BLKL 1		; Backward link
$DEF	INQ$IW_SIZE		.BLKW 1		; Structure size
$DEF	INQ$IB_TYPE		.BLKB 1		; Structure type
$DEF	INQ$IB_SUBTYPE		.BLKB 1		; Structure subtype
INQ$C_HEADER_LENGTH = .				; Inquiry header length
$DEF	INQ$IB_PERIPH_DEV_TYPE	.BLKB 1		; Peripheral device type
	INQ$C_PROC_DEV = ^X3			; Processor device
$DEF	INQ$IB_DEV_TYPE_MOD	.BLKB 1		; Device type modifier
$DEF	INQ$IB_ANSI_VERSION	.BLKB 1		; ANSI approved version
	INQ$C_COMPLIES_TO_SCSI2 = ^X2		; Complies to SCSI2
$DEF	INQ$IB_RESP_DATA_FORMAT	.BLKB 1		; Response data format
	INQ$C_SCSI2_FORMAT = ^X2		; Inquiry data in SCSI2 format
$DEF	INQ$IB_ADDITIONAL_LEN	.BLKB 1		; Additional inquiry length
	INQ$C_ADDITIONAL_LEN = ^X1F		; Additional length
$DEF	INQ$IB_Reserved		.BLKB 2		; Reserved
$DEF	INQ$IB_FLAGS		.BLKB 1		; Flags
$DEF	INQ$IB_VENDOR_ID	.BLKB 8		; Vendor ID
$DEF	INQ$IB_PRODUCT_ID	.BLKB 16	; Product ID
$DEF	INQ$IS_PRODUCT_REV	.BLKB 4		; Product revision
INQ$C_DATA_LENGTH = . - INQ$C_HEADER_LENGTH 	; Inquiry data length
INQ$C_LENGTH = .			 	; Total Inquiry data length
	$DEFEND INQ

	.SYMBOL_ALIGNMENT NONE

.PAGE
	.SBTTL	Request Sense Data Definitions

	.SYMBOL_ALIGNMENT QUAD
	$DEFINI	SENSE,GLOBAL

$DEF	SENSE$PS_FLINK		.BLKL 1		; Forward link
$DEF	SENSE$PS_BLINK		.BLKL 1		; Backward link
$DEF	SENSE$IW_SIZE		.BLKW 1		; Structure size
$DEF	SENSE$IB_TYPE		.BLKB 1		; Structure type
$DEF	SENSE$IB_SUBTYPE	.BLKB 1		; Structure subtype
SENSE$C_HEADER_LENGTH = .			; Sense data header length
$DEF	SENSE$IB_ERROR_CODE	.BLKB 1		; Error code
	SENSE$C_CURRENT_ERROR = ^X70		;  Current error
	SENSE$C_DEFERRED_ERROR = ^X71		;  Deferred error
	$VIELD SENSE,0,<-			; Bits used with info field
	<FILLER,7,M>,-				;  Filler
	<VALID,,M>,-				;  Valid bit
	>
$DEF	SENSE$IB_SEGMENT_NUMBER	.BLKB 1		; Segment number
$DEF	SENSE$IB_SENSE_KEY	.BLKB 1		; Sense key
	SENSE$C_ILLEGAL_REQUEST = ^X5		;  Illegal request
	$VIELD SENSE,0,<-			; Bits used with info field
	<FILLER,4,M>,-				;  Filler
	<RSVD,,M>,-				;  Reserved
	<ILI,,M>,-				;  Incorrect length indicator
	<EOM,,M>,-				;  End of medium
	<FILEMARK,,M>,-				;  Filemark
	>
$DEF	SENSE$IB_INFORMATION	.BLKB 4		; Information
$DEF	SENSE$IB_ADDITIONAL_LEN	.BLKB 1		; Additional sense length
	SENSE$C_ADDITIONAL_LEN = ^XA		;  Additional length
$DEF	SENSE$IB_CMD_SPEC_INFO	.BLKB 4		; Command specific Information
$DEF	SENSE$IB_ADD_SENSE_CODE	.BLKB 1		; Additional sense code
	SENSE$C_LUN_NOT_SUPPORTED = ^X25	;  LUN not supported
$DEF	SENSE$IB_ADD_SENSE_QUAL	.BLKB 1		; Additional sense qualifier
$DEF	SENSE$IB_FRU_CODE	.BLKB 1		; Field replaceable unit code
$DEF	SENSE$IB_SENSE_KEY_SPEC	.BLKB 3		; Sense key specific
SENSE$C_DATA_LENGTH = . - SENSE$C_HEADER_LENGTH	; Sense data length
SENSE$C_LENGTH = .				; Total Sense data length
	$DEFEND SENSE

	.SYMBOL_ALIGNMENT NONE

.PAGE
	.SBTTL	Buffer Segment Descriptor/Map Definitions

	.SYMBOL_ALIGNMENT QUAD
	$DEFINI	BSD,GLOBAL

$DEF	BSD$PQ_PA		.BLKB 6		; PA of BSD
	BSD$C_PA_SHIFT = 2			; Number of bits to shift PA
	$VIELD	BSD,0,<-
	<TYP,1,M>,-				; Data buffer pointer type 
	<RSVD,1,M>,-				; Reserved field
	>
$DEF	BSD$IW_BYTE_COUNT	.BLKW 1		; Size in bytes of host buffer
	BSD$C_BYTE_COUNT_SHIFT = 48		; Number of bits to shift byte
						;  count into upper word
	BSD$M_BYTE_COUNT = ^X<ffff000000000000>	; Byte count mask bits 63:48
BSD$C_LENGTH = .				; Total BSD length
	$DEFEND BSD


	.SYMBOL_ALIGNMENT QUAD
	$DEFINI	BSM,GLOBAL
;
; First 20 bytes are PKSDRIVER private area. This section is not visible to
; the adapter.
;
$DEF	BSM$PS_FLINK		.BLKL 1		; Forward link of BSMs in queue
$DEF	BSM$PS_BLINK		.BLKL 1		; Backward link of BSMs in queue
$DEF	BSM$IW_SIZE		.BLKW 1		; Structure size
$DEF	BSM$IB_TYPE		.BLKB 1		; Structure type
$DEF	BSM$IB_SUBTYPE		.BLKB 1		; Structure subtype
$DEF	BSM$PS_CRCTX		.BLKL 1		; CRCTX pointer for mapping
$DEF	BSM$PQ_PA		.BLKQ 1		; PA of BSM
$DEF	BSM$IS_LEADING_COUNT	.BLKL 1		; Leading unaligned bytes
$DEF	BSM$IS_TRAILING_COUNT	.BLKL 1		; Trailing unaligned bytes
$DEF	BSM$PS_LEADING_BUFF	.BLKL 1		; Leading buffer
$DEF	BSM$PS_TRAILING_BUFF	.BLKL 1		; Trailing buffer
BSM$C_HEADER_LENGTH = .				; BSM header length
;
; The adapter visible section begins here.
;
$DEF	BSM$PS_NEXT_BSM_BSD	.BLKQ 1		; BSD for next BSM in list
$DEF	BSM$IW_NUM_ENTRIES	.BLKW 1		; Number of valid BSDs in BSM
$DEF	BSM$SBZ			.BLKB 6		; Should be zero
$DEF	BSM$IS_TOTAL_COUNT	.BLKL 1		; Total bytes mapped by this BSM
$DEF	BSM$IS_BUFFER_OFFSET	.BLKL 1		; Byte offset from the start of
						;  the host buffer for this BSM
;
; The following BSDs each define one physicaly contiguous segment of the
; host buffer.
;
$DEF	BSM$PS_BSD0		.BLKQ 1
$DEF	BSM$PS_BSD1		.BLKQ 1
$DEF	BSM$PS_BSD2		.BLKQ 1
$DEF	BSM$PS_BSD3		.BLKQ 1
$DEF	BSM$PS_BSD4		.BLKQ 1
$DEF	BSM$PS_BSD5		.BLKQ 1
$DEF	BSM$PS_BSD6		.BLKQ 1
$DEF	BSM$PS_BSD7		.BLKQ 1
$DEF	BSM$PS_BSD8		.BLKQ 1
$DEF	BSM$PS_BSD9		.BLKQ 1
$DEF	BSM$PS_BSD10		.BLKQ 1
$DEF	BSM$PS_BSD11		.BLKQ 1
$DEF	BSM$PS_BSD12		.BLKQ 1
$DEF	BSM$PS_BSD13		.BLKQ 1
$DEF	BSM$PS_BSD14		.BLKQ 1
$DEF	BSM$PS_BSD15		.BLKQ 1
$DEF	BSM$PS_BSD16		.BLKQ 1

BSM$C_LENGTH = .				; Total BSM length
	$DEFEND BSM

.PAGE
	.SBTTL	Pad block definition
;
; Pad buffer block header definition
;
	.SYMBOL_ALIGNMENT QUAD
	$DEFINI	PAD,GLOBAL
$DEF	PAD$PS_FLINK            .BLKL 1         ; Forward link
$DEF	PAD$PS_BLINK            .BLKL 1         ; Backward link
$DEF    PAD$IW_SIZE             .BLKW 1         ; Structure size in bytes
$DEF    PAD$IB_TYPE             .BLKB 1         ; Structure type
$DEF    PAD$IB_SUBTYPE          .BLKB 1         ; Structure subtype
$DEF	PAD$PS_CRCTX		.BLKL 1		; CRCTX pointer for mapping
$DEF	PAD$PQ_PA		.BLKQ 1		; PA of pad buffer
PAD$C_HEADER = .
	$DEFEND PAD
	.SYMBOL_ALIGNMENT NONE

;
; Type 0 buffer pointer fields definition. Type 0 buffer pointers point to
; a segment of a host memory buffer (BSD).
;
	$DEFINI TP0
	$VIELD	TP0,0,<-
	<TYP,1,M>,-				; Data buffer pointer type 
	<MBZ,1,M>,-				; MBZ field
	<PA,46,M>,-				; Bits <47:2> of BSD PA
	<COUNT,16,M>,-				; Bytes in host buffer segment
	>
	TP$C_TYPE0 = 0				; Type 0 constant
	$DEFEND TP0
;
; Type 1 buffer pointer fields definition. Type 1 buffer pointers point to
; a Buffer Segment Map (BSM) for the buffer.
;
	$DEFINI TP1
	$VIELD	TP1,0,<-
	<TYP,1,M>,-				; Data buffer pointer type 
	<MBZ,1,M>,-				; MBZ field
	<PA,46,M>,-				; Bits <47:2> of BSM PA
	<COUNT,16,M>,-				; Bytes in host buffer segment
	>
	TP$C_TYPE1 = 1				; Type 1 constant
	$DEFEND TP1

.PAGE
	.SBTTL	KZTSA Register Definitions

;
; KZTSA register offsets and bit/field definitions.
;
$DEFINI TZAREG				; TZA specific registers

.=^X0004
$DEF	TZA_HW_REV		.BLKQ 1	; Hardware revision register

.=^X0040
$DEF	TZA_AMCSR		.BLKQ 1	; Adapter maintenance control and
					;  status register
	$VIELD TZA_AMCSR,0,<-		; Define bits used with AMCSR register
	<MIN,1,M>,-			; Maintenance initialize
	<FILL1,2,M>,-			; SBZ
	<INT_ENB,1,M>,-			; Interrupt enable
	>

.=^X0048
$DEF	TZA_ABBR		.BLKQ 1	; Adapter block base register
	TZA_ABBR$C_PA_SHIFT = 5		; # of bits to shift to get PA <39:5>

.=^X0050
$DEF    TZA_DAFQIR		.BLKQ 1	; Driver adapter free queue entry
					;  insertion register
	TZA_DAFQIR$C_PA_SHIFT = 3	; # of bits to shift to get PA <47:3>

.=^X0058
$DEF    TZA_DACQIR		.BLKQ 1	; Driver adapter command queue entry
					;  insertion register
	TZA_DACQIR$C_PA_SHIFT = 3	; # of bits to shift to get PA <47:3>

.=^X0080
$DEF	TZA_ASR			.BLKQ 1	; Adapter Status Register

	$VIELD TZA_ASR,0,<-		; Define bits used with ASR register
	<FILL1,2,M>,-			; SBZ
	<ADSE,1,M>,-			; Host data structure has occurred
	<AMSE,1,M>,-			; Host memory system error has occurred
	<AAC,1,M>,-			; Abnormal condition in adapter FW
	<FILL2,3,M>,-			; SBZ
	<ASIC,1,M>,-			; Adapter requesting completion int.
	<UNIN,1,M>,-			; Adapter in uninitialized state
	<FILL3,21,M>,-			; SBZ
	<AME,1,M>,-			; Adapter maintenance error occurred
	>

.=^X0088
$DEF	TZA_AFAR		.BLKQ 1	; Adapter failing address register

.=^X0090
$DEF	TZA_AFPR		.BLKQ 1	; Adapter failing parameter register
	;
	; AFPR error codes if ADSE bit is set in ASR.
	;
	AFPR_ILL_AB_FORMAT = 4		; Illegal adapter block format
	AFPR_ILL_QB_ALIGN = 8		; Illegal queue buffer alignment
	AFPR_QCAR_INVAL_FIELD = 16	; Queue carrier field contains
					;  invalid data
	AFPR_BSM_INVAL_FIELD = 17	; A BSM or BSD field contains
					;  invalid data
	AFPR_BSM_LINK= 18		; Offset for current BSM does not equal
					;  offset + length of previous BSM
	;
	; AFPR error codes if AMSE bit is set in ASR.
	;
	AFPR_ERR_ACCESS_AB   = 1	; Error while accessing adapter block
	AFPR_ERR_ACCESS_QCAR = 2	; Error while accessing a queue carrier
	AFPR_ERR_ACCESS_QBUF = 3	; Error while accessing q queue buffer
	AFPR_ERR_ACCESS_BSM  = 4	; Error while accessing a BSM or BSD

.=^X00C0
$DEF	TZA_ERRLOG1		.BLKL 1	; Adapter error log data register 1
$DEF	TZA_ERRLOG2		.BLKL 1	; Adapter error log data register 2
$DEF	TZA_ERRLOG3		.BLKL 1	; Adapter error log data register 3
$DEF	TZA_ERRLOG4		.BLKL 1	; Adapter error log data register 4
$DEF	TZA_ERRLOG5		.BLKL 1	; Adapter error log data register 5
$DEF	TZA_ERRLOG6		.BLKL 1	; Adapter error log data register 6
$DEF	TZA_ERRLOG7		.BLKL 1	; Adapter error log data register 7
$DEF	TZA_ERRLOG8		.BLKL 1	; Adapter error log data register 8
$DEF	TZA_ERRLOG9		.BLKL 1	; Adapter error log data register 9
$DEF	TZA_ERRLOGA		.BLKL 1	; Adapter error log data register A
;
; Note: the following register offsets are relative to the page on which
; they occur.
;
.=^X0
$DEF	TZA_CLEAR_INT	.BLKL 1		; Clear Turbochannel interrupt register

.=^X0
$DEF	TZA_RESET		.BLKQ 1	; Adapter reset register

	$DEFEND TZAREG

.PAGE
	.SBTTL	KZPSA Register Definitions

;
; KZPSA register offsets and bit/field definitions.
;
$DEFINI PZAREG				; PZA specific registers

.=^X0000
$DEF	PZA_AMCSR		.BLKQ 1	; Adapter maintenance control and
					;  status register
	$VIELD PZA_AMCSR,0,<-		; Define bits used with AMCSR register
	<MIN,1,M>,-			; Maintenance initialize
	<FILL1,2,M>,-			; SBZ
	<INT_ENB,1,M>,-			; Interrupt enable
	>

.=^X0004
$DEF	PZA_HW_REV		.BLKQ 1	; Hardware revision register

.=^X0008
$DEF	PZA_ABBR		.BLKQ 1	; Adapter block base register
	PZA_ABBR$C_PA_SHIFT = 5		; # of bits to shift to get PA <39:5>

.=^X0010
$DEF    PZA_DAFQIR		.BLKQ 1	; Driver adapter free queue entry
					;  insertion register
	PZA_DAFQIR$C_PA_SHIFT = 3	; # of bits to shift to get PA <47:3>

.=^X0018
$DEF    PZA_DACQIR		.BLKQ 1	; Driver adapter command queue entry
					;  insertion register
	PZA_DACQIR$C_PA_SHIFT = 3	; # of bits to shift to get PA <47:3>

.=^X0040
$DEF	PZA_ASR			.BLKQ 1	; Adapter Status Register

	$VIELD PZA_ASR,0,<-		; Define bits used with ASR register
	<FILL1,2,M>,-			; SBZ
	<ADSE,1,M>,-			; Host data structure has occurred
	<AMSE,1,M>,-			; Host memory system error has occurred
	<AAC,1,M>,-			; Abnormal condition in adapter FW
	<FILL2,3,M>,-			; SBZ
	<ASIC,1,M>,-			; Adapter requesting completion int.
	<UNIN,1,M>,-			; Adapter in uninitialized state
	<FILL3,21,M>,-			; SBZ
	<AME,1,M>,-			; Adapter maintenance error occurred
	>

.=^X0048
$DEF	PZA_AFAR		.BLKQ 1	; Adapter failing address register

.=^X0050
$DEF	PZA_AFPR		.BLKQ 1	; Adapter failing parameter register
	;
	; AFPR error codes if ADSE bit is set in ASR.
	;
	AFPR_ILL_AB_FORMAT = 4		; Illegal adapter block format
	AFPR_ILL_QB_ALIGN = 8		; Illegal queue buffer alignment
	AFPR_QCAR_INVAL_FIELD = 16	; Queue carrier field contains
					;  invalid data
	AFPR_BSM_INVAL_FIELD = 17	; A BSM or BSD field contains
					;  invalid data
	AFPR_BSM_LINK= 18		; Offset for current BSM does not equal
					;  offset + length of previous BSM
	;
	; AFPR error codes if AMSE bit is set in ASR.
	;
	AFPR_ERR_ACCESS_AB   = 1	; Error while accessing adapter block
	AFPR_ERR_ACCESS_QCAR = 2	; Error while accessing a queue carrier
	AFPR_ERR_ACCESS_QBUF = 3	; Error while accessing q queue buffer
	AFPR_ERR_ACCESS_BSM  = 4	; Error while accessing a BSM or BSD

.=^X00C0
$DEF	PZA_ERRLOG1		.BLKL 1	; Adapter error log data register 1
$DEF	PZA_ERRLOG2		.BLKL 1	; Adapter error log data register 2
$DEF	PZA_ERRLOG3		.BLKL 1	; Adapter error log data register 3
$DEF	PZA_ERRLOG4		.BLKL 1	; Adapter error log data register 4
$DEF	PZA_ERRLOG5		.BLKL 1	; Adapter error log data register 5
$DEF	PZA_ERRLOG6		.BLKL 1	; Adapter error log data register 6
$DEF	PZA_ERRLOG7		.BLKL 1	; Adapter error log data register 7
$DEF	PZA_ERRLOG8		.BLKL 1	; Adapter error log data register 8
$DEF	PZA_ERRLOG9		.BLKL 1	; Adapter error log data register 9
$DEF	PZA_ERRLOGA		.BLKL 1	; Adapter error log data register A
;
; Note: the following register offsets are relative to the page on which
; they occur.
;
.=^X0
$DEF	PZA_CLEAR_INT	.BLKL 1		; Clear interrupt register

.=^X0
$DEF	PZA_RESET		.BLKQ 1	; Adapter reset register

	$DEFEND PZAREG

.PAGE
	.SBTTL	CRAM Table and Macro Definitions
;
; Define offsets to reference fields within each table entry as generated
; by the SETUP_CRAMCMD macro.
;
	$DEFINI	TBL
$DEF	TBL$IS_INDEX		.BLKL 1		; Command index
	TBL$IS_CSROFF = .			; Start of CSR offset list.
$DEF	TBL$IS_TZA_CSROFF	.BLKL 1		; Offset from base of SIMport
						; adap regs. (KZTSA)
$DEF	TBL$IS_PZA_CSROFF	.BLKL 1		; Offset from base of SIMport
						; adap regs. (KZPSA)
$DEF	TBL$IS_SPDTOFF		.BLKL 1		; Offset to CRAM ptr in SPDT
$DEF	TBL$IS_IOHANDLEOFF 	.BLKL 1		; Offset to IOHANDLE in SPDT
	TBL$C_ENTRY_SIZE = .
	$DEFEND TBL
;
; SETUP_CRAMCMD
;
; This macro generates data which will be used to initialize a single CRAM
; representing a single I/O register. The generated data will be read and
; used to help calculate input/output arguments to an IOC$CRAM_CMD call
; during unit init.
;
	.MACRO	SETUP_CRAMCMD	CMD_INDEX,REG,IOHANDLE=REG

	.LONG	CRAMCMD$K_'CMD_INDEX	; Command index
	.LONG	TZA_'REG'		; TZA Offset from base of SIMport regs.
	.LONG	PZA_'REG'		; PZA Offset from base of SIMport regs.
	.LONG	SPDT$PS_CRAM_'REG'	; Offset to CRAM ptr within SPDT
	.LONG	SPDT$PQ_IOHANDLE_'IOHANDLE'
					; Offset to IOHANDLE ptr within SPDT
	.ENDM	SETUP_CRAMCMD

.PAGE
	.SBTTL	PKSDRIVER Data
	DRIVER_DATA

;
; The following two tables are used in routine PK$SEND_COMMAND when
; building the command buffer to send to the Simport adapter.
;
; Table for loading QBUF$IB_TAG_QUEUE_ACTION. Indexed by SCDRP$IS_QUEUE_CHAR.
; Each entry in this table is a queuing characteristic indicating how the
; I/O request should be queued to the device. Entries of zero indicate that
; the I/O request is not a queued request.
;
TAG_QUEUE_ACTION_TABLE:
;
;		QBUF$IB_TAG_QUEUE_ACTION	SCDRP$IS_QUEUE_CHAR
;		------------------------	-------------------
	.BYTE   QBUF$C_SIMPLE_TAG		; SCDRP$K_QCHAR_UNORDERED
        .BYTE   QBUF$C_ORDERED_QUEUE		; SCDRP$K_QCHAR_ORDERED
        .BYTE   QBUF$C_HEAD_OF_QUEUE		; SCDRP$K_QCHAR_HEAD
        .BYTE   QBUF$C_ORDERED_QUEUE		; SCDRP$K_QCHAR_NOT_QUEUED
        .BYTE   QBUF$C_ORDERED_QUEUE		; SCDRP$K_QCHAR_ACA

;
; Table for setting or clearing the TC action enable bit in the CAM flags.
; Indexed by SCDRP$IS_QUEUE_CHAR.
;
; Each entry in this table is a bit mask used for setting or clearing the
; Tagged Command action enable bit in QBUF$IB_CAM_FLAGS_1.
;
TC_ACTION_ENABLE_TABLE:
;
;		QBUF$V_TQ_ACTION_ENB		SCDRP$IS_QUEUE_CHAR
;		--------------------		-------------------
	.BYTE	QBUF$M_TQ_ACTION_ENB		; SCDRP$K_QCHAR_UNORDERED
	.BYTE	QBUF$M_TQ_ACTION_ENB		; SCDRP$K_QCHAR_ORDERED
	.BYTE	QBUF$M_TQ_ACTION_ENB		; SCDRP$K_QCHAR_HEAD
	.BYTE	0				; SCDRP$K_QCHAR_NOT_QUEUED
	.BYTE	0				; SCDRP$K_QCHAR_ACA

;
; Wait time constants.
;
	. = <<.+7>&^C7>				; Make sure we're at least quadword aligned
SECOND =	10000000 			; Num clock ticks in a second
ONE_HUNDR_SEC:	.QUAD	<SECOND/100>		; 1/100 second
ONE_SECOND:	.QUAD	<1*SECOND>		; 1 second
TWO_SECONDS:	.QUAD	<2*SECOND>		; 2 seconds
THIRTY_SECONDS:	.QUAD	<30*SECOND>		; 30 seconds

.PAGE

;
; This table is read during unit initialization, as a setup to calling 
; IOC$CRAM_CMD. Driver loading has already allocated enough CRAMs to allow
; one CRAM per each I/O register (and two CRAMs for any register that is both
; read and written, one for each direction). The calls to IOC$CRAM_CMD in
; the PKS$UNIT_INIT routine will initialize certain fields in each CRAM,
; calculating its arguments from the table below as follows:
;
; CMD_INDEX	- Command index of the command to be returned by IOC$CRAM_CMD.
; REGISTER	- Name of the register being mapped, used to build offsets
; IOHANDLE	- Name of IOHANDLE structure to use for this register.
;		  Defaults to "REG" (SPDT$PS_IOHANDLE_REG).
;
;
;	       CMD_INDEX  REGISTER	IOHANDLE
;	       
	. = <<.+3>&^C3>				 ; Make sure we're at least longword aligned
CRAMCMD_TABLE:
						
SETUP_CRAMCMD  RDLONG32,  HW_REV
SETUP_CRAMCMD  WTLONG32,  AMCSR
SETUP_CRAMCMD  WTLONG32,  ABBR
SETUP_CRAMCMD  WTLONG32,  DAFQIR
SETUP_CRAMCMD  WTLONG32,  DACQIR
SETUP_CRAMCMD  RDLONG32,  ASR
SETUP_CRAMCMD  RDLONG32,  AFAR
SETUP_CRAMCMD  RDBYTE32,  AFPR
SETUP_CRAMCMD  RDLONG32,  ERRLOG1
SETUP_CRAMCMD  RDLONG32,  ERRLOG2
SETUP_CRAMCMD  RDLONG32,  ERRLOG3
SETUP_CRAMCMD  RDLONG32,  ERRLOG4
SETUP_CRAMCMD  RDLONG32,  ERRLOG5
SETUP_CRAMCMD  RDLONG32,  ERRLOG6
SETUP_CRAMCMD  RDLONG32,  ERRLOG7
SETUP_CRAMCMD  RDLONG32,  ERRLOG8
SETUP_CRAMCMD  RDLONG32,  ERRLOG9
SETUP_CRAMCMD  RDLONG32,  ERRLOGA
SETUP_CRAMCMD  WTLONG32,  CLEAR_INT,	CLEAR_INT
SETUP_CRAMCMD  WTLONG32,  RESET,	RESET
	.BLKL	TBL$C_ENTRY_SIZE		; End of CRAM command table

.PAGE
	.SBTTL	Macro Definitions
;
; MAP_PAGE
;
; This macro is a wrapper around IOC$MAP_IO for mapping XZA registers into
; virtual address space.
;
	.MACRO	MAP_PAGE		NAME,OFFSET,NODE,ADP,IDB=R3,SPDT=R4,?L1

	PUSHAB	SPDT$PQ_IOHANDLE_'NAME'(SPDT)	; Where to put IOHANDLE.
	PUSHL	#IOC$K_BUS_MEM_BYTE_GRAN	; Space to map.
	PUSHL	G^MMG$GL_PAGE_SIZE		; Just map the whole page.

	PUSHL	R1
	MOVL	OFFSET,R1
	TSTL	SPDT$IS_ADAPTER_TYPE(SPDT)	; Add bus physical base addr
	BEQL	L1				;    if not on Turbochannel
	EVAX_ADDQ IDB$Q_CSR(IDB),R1,R1
L1:	EVAX_STQ  R1,SPDT$IQ_PHYS_ADDR(SPDT)
	POPL	R1

	PUSHAB	SPDT$IQ_PHYS_ADDR(SPDT)		; Physical offset ptr.
	PUSHL	NODE				; Bus node # of adapter.
	PUSHL	ADP				; ADP pointer.

	CALLS	#6,IOC$MAP_IO
	.ENDM	MAP_PAGE

;
; WRITE_CSR
;
; This macro writes sets up the WDATA field in the CRAM of the specified
; register and then calls the CRAM_IO macro to write data into the 
; appropriate register.
;
; Note:  The argument passed as the CRAM pointer (R0 by default) is destroyed.
;	 The CRAM_IO macro also destroys R0.
;
	.MACRO	WRITE_CSR	REG,DATA,TYPE=L,SPDT=R4,CRAM=R0,?L1
	
	MOVL	SPDT$PS_CRAM_'REG'(SPDT),CRAM	; Get CRAM pointer for this reg
	.IF NB DATA
	MOV'TYPE' DATA,CRAM$Q_WDATA(CRAM)	; Set up data in the CRAM
	.ENDC
	CRAM_IO CRAM				; Write the data to register
	.IF DEFINED DEBUG
	    .BRANCH_LIKELY
            BLBS  R0,L1				; Continue if success
	    BSBW  FATAL_ERROR			; Else bugcheck
L1:      
	.ENDC
	.ENDM	WRITE_CSR

;
; READ_CSR
;
; This macro calls the CRAM_IO macro to read the specified register.
; It then transfers the data to the specified destination byte.
;
; Note:  The argument passed as the CRAM pointer (R0 by default) is destroyed.
;	 The CRAM_IO macro also destroys R0.
;
	.MACRO	READ_CSR	REG,DEST,TYPE=L,SPDT=R4,CRAM=R0,?L1
	
	CRAM_IO	SPDT$PS_CRAM_'REG'(SPDT)	; Read the SIMport register

	.IF DEFINED DEBUG
	    .BRANCH_LIKELY
            BLBS  R0,L1				; Continue if success
	    BSBW  FATAL_ERROR			; Else bugcheck
L1:      
	.ENDC

	MOVL	SPDT$PS_CRAM_'REG'(SPDT),CRAM	; Get CRAM ptr for this reg
	.IF NB DEST
	  MOV'TYPE' CRAM$Q_RDATA(CRAM),DEST	; Move data from CRAM to dst
        .ENDC
	.ENDM	READ_CSR

.PAGE

;
; WAIT_FOR_RESPONSE
;
; This macro waits for a response on the ADRQ by polling bit 0 of the
; Stopper Carrier. When bit 0 becomes clear, the adapter has inserted a
; response onto the ADRQ.
;
; INPUT:
;	WAIT_TIME - Time to wait in seconds
;	SPDT - register containing SPDT address
;
; OUTPUT:
;	None.
;
	.MACRO	WAIT_FOR_RESPONSE  WAIT_TIME,SPDT,?L1,?L2

	PUSHL	R6				; Save R6
	MOVL	#<WAIT_TIME*100>,R0		; Get wait time
.Disable Flagging				; Turn off info. it's aligned
L1:	PUSHL	#SPL$C_IOLOCK8			; Fork lock
	PUSHAQ	ONE_HUNDR_SEC			; Time delay in ticks
	PUSHL	R8				; KPB address
	CALLS	#3,G^EXE$KP_TQE_WAIT		; Fork and wait the desired time
.Enable Flagging
	MOVL	SPDT$PS_AB(SPDT),R6		; Get Adapter Block address
	MOVL	AB$PQ_ADRQ_HEAD(R6),R6		; Point to ADRQ Stopper Carrier
	BLBC	CAR$PQ_NEXT_PTR(R6),L2		; Do we have a response yet?
	SOBGTR	R0,L1				; No, have we timed out?
	POPL	R6				; Yes, restore R6
	BRW	PORT_OFFLINE			; Set port offline
L2:
	POPL	R6				; Restore R6
	.ENDM	WAIT_FOR_RESPONSE

.PAGE

;
; BUILD_BSD
;
; This macro builds a Buffer Segment Descriptor (BSD).
;
; INPUT:
;	BSD_ADDR - Address of where to build the BSD
;	BUFFER_SVAPTE - System Virtual address of PTE of this buffer segment
;	BUFFER_SVA - System Virtual address of this buffer segment
;	BUFFER_PA - Physical address of this buffer segment
;	BYTE_OFFSET - Byte offset in page to start of buffer. This field
;		      is valid only if BUFFER_SVAPTE is specified.
;	BYTE_COUNT - Byte count for this buffer segment
;	TYPE - Type of BSD
;	       Type 0 = Pointer to a data buffer segment.
;	       Type 1 = Pointer to a Buffer Segment Map (BSM).
;	PRESERVE_R0 - Flag for preserving R0 across macro invocation
;		      YES = Preserve R0
;		      NO  = Do not preserve R0
;	R4 - SPDT address
;
; N.B.: exactly one of BUFFER_SVAPTE, BUFFER_SVA, and BUFFER_PS should be
; specified in any given invocation of BUILD_BSD.
;
; OUTPUT:
;	BSD setup for a buffer segment.
;
	.MACRO	BUILD_BSD  BSD_ADDR, BUFFER_SVAPTE, BYTE_OFFSET, BUFFER_SVA,-
			   BYTE_COUNT, TYPE=TP$C_TYPE0, PRESERVE_R0=NO,-
			   BUFFER_PA

	.IIF IDN PRESERVE_R0, YES, PUSHL R0	; Save R0

	.IF NB BUFFER_SVAPTE
	.IF NB BUFFER_SVA
	.ERROR	; Can't use both BUFFER_SVAPTE and BUFFER_SVA in BUILD_BSD
	.ENDC
	.IF NB BUFFER_PA
	.ERROR	; Can't use both BUFFER_SVAPTE and BUFFER_PA in BUILD_BSD
	.ENDC
	$SVAPTE_TO_PA -				; Translate buffer SVAPTE to PA
		SVAPTE=BUFFER_SVAPTE,-		; R0 has PA on return
		BOFF = BYTE_OFFSET
	ADDL2	SPDT$L_DMA_BASE(R4),R0		; add in DMA base address
	.ENDC

	.IF NB BUFFER_SVA
	.IF NB BUFFER_PA
	.ERROR	; Can't use both BUFFER_SVA and BUFFER_PA in BUILD_BSD
	.ENDC
	$SVA_TO_PA -				; Translate buffer SVA to PA
		SVA=BUFFER_SVA			; R0 has PA on return
	ADDL2	SPDT$L_DMA_BASE(R4),R0		; add in DMA base address
	.ENDC

	.IF NB BUFFER_PA
	MOVL	BUFFER_PA,R0
	.ENDC

	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_SRL R0,SPDT$IB_XFER_ALIGN(R4),R0	; Shift PA to adapter alignment
	EVAX_SLL R0,#BSD$C_PA_SHIFT,R0		; Shift PA 2 bits left
	EVAX_STQ R0,BSD_ADDR			; Store data segment addr
	MOVL	BYTE_COUNT,R0			; Get byte count
	EVAX_SLL R0,#BSD$C_BYTE_COUNT_SHIFT,R0	; Shift byte count to last word
	EVAX_OR	 BSD_ADDR,R0,BSD_ADDR		; Insert byte count into BSD

	.IF GT TYPE
	EVAX_OR BSD_ADDR,#TP$C_TYPE1,BSD_ADDR	; Make BSD a Type 1 pointer
	.ENDC

	.IIF IDN PRESERVE_R0, YES, POPL R0	; Restore R0

	.ENDM	BUILD_BSD

;
; MAP_S0
;
; This macro uses map registers to map a region of S0 space; the driver
; uses it for the Adapter Block, Queue Carriers, Queue Buffers, and pad
; blocks.
;
; If the CRCTX pointer provided has a zero value, a CRCTX is allocated
; from non-paged pool.
;
; The KPB provided is used to stall if an allocation fails.
;
; Note: this macro destroys R0 and R1.
;
	.MACRO	MAP_S0	SVA,NBYTES,CRCTX,KPB$,DMA_ADDR,SPDT=R4,?L1,?L2,?L3
;
; Have we allocated a CRCTX yet?
;
	TSTL	CRCTX
	BNEQ	L2
;
; Nope; allocate and initialize one.
;
L1:	PUSHL	SPDT$IS_FLCK(SPDT)		; Fork lock index.
	PUSHAB	CRCTX				; CRCTX ref.
	PUSHL	SPDT$PS_CRAB(SPDT)		; CRAB address
	CALLS	#3,IOC$ALLOC_CRCTX
	.BRANCH_LIKELY
	BLBS	R0,L2				; Did we get one?
	KP_STALL_FORK_WAIT  KPB = KPB$		; Fork and wait
	BRB	L1				; Try allocation again
;
; How many map registers do we need?
;
L2:	EVAX_AND SVA,-				; Calculate byte within map
		SPDT$IS_CRCTX_BWP_MASK(SPDT),R0	; unit.
	ADDL2	NBYTES,R0			; Add byte count, plus
	ADDL2	SPDT$IS_CRCTX_BWP_MASK(SPDT),R0	; a map unit to round up.
	MNEGL	SPDT$IS_CRCTX_SHIFT(SPDT),R1	; Convert bytes to map units.
	ASHL	R1,R0,R1
	MOVL	CRCTX,R0
	ADDL3	R1,#2,CRCTX$L_ITEM_CNT(R0)	; Add 2 for guard entries.
;
; Attempt to allocate the necessary map registers.  Stall if immediate
; allocation fails; MAP_CALLBACK will restart us.
;
	MOVAB	MAP_CALLBACK,-			; Set up to restart when
		CRCTX$L_CALLBACK(R0)		; allocation succeeds after
	MOVL	KPB$,-				; initial failure.
		CRCTX$L_AUX_CONTEXT(R0)
	PUSHL	R0				; CRCTX address
	PUSHL	SPDT$PS_CRAB(SPDT)		; CRAB address
	CALLS	#2,IOC$ALLOC_CNT_RES
	BLBS	R0,L3				; Did we get 'em?
	MOVL	KPB$,R0
	MOVAB	B^IOC$RETURN,-			; Set stall routine
		KPB$PS_SCH_STALL_RTN(R0)
	PUSHL	R0
	CALLS	#1,EXE$KP_STALL_GENERAL
;
; At this point, one way or the other, we have the necessary map registers.
; Calculate the SVAPTE of the region, so we can map it.
;
L3:	MOVL	SVA,R0
	EVAX_SLL  R0,#<64-VA$V_SYSTEM>,R0	; Clear space select bit.
	EVAX_SRL  R0,MMG$GQ_64SYS_SHIFT,R0	; Get virtual page number
	MOVAQ	@MMG$GL_SPTBASE[R0],R0		; Get SVAPTE that maps VA
;	
; Map the region, putting the DMA-able address into DMA_ADDR.
;
	PUSHAL	DMA_ADDR			; Where to put DMA addr.
	EVAX_AND SVA,-				; Calculate byte-within-page
		SPDT$IS_CRCTX_BWP_MASK(SPDT),R1
	PUSHL	R1				; Byte offset
	PUSHL	R0				; Buffer SVAPTE
	PUSHL	CRCTX				; CRCTX addr
	PUSHL	SPDT$L_ADP(SPDT)		; ADP addr
	CALLS	#5,IOC$LOAD_MAP

	.ENDM					; MAP_S0

.PAGE
	.SBTTL	CAM Status Table
;
; This table is used to map the returned CAM status to a VMS status code.
;
	. = <<.+3>&^C3>				 ; Make sure we're at least longword aligned
CAM_STATUS_TABLE:
	.LONG	SS$_CONTINUE			; Request in progress
	.LONG	SS$_NORMAL			; Request completed w/o error
	.LONG	SS$_ABORT			; CCB request aborted by host
	.LONG	SS$_DEVCMDERR			; Unable to abort request
	.LONG	SS$_NORMAL			; Completed w/error (Check cond)
	.LONG	SS$_MEDOFL			; CAM subsystem busy 
	.LONG	SS$_CTRLERR			; CCB request is invalid
	.LONG	SS$_BADPARAM			; Path ID supplied is invalid
	.LONG	SS$_TIMEOUT			; SCSI device not installed
	.LONG	SS$_ABORT			; Unable to term. CCB request
	.LONG	SS$_TIMEOUT			; Target selection timeout
	.LONG	SS$_TIMEOUT			; Command timeout
	.LONG	SS$_CTRLERR			; Reserved (place holder)
	.LONG	SS$_CTRLERR			; Message reject received
	.LONG	SS$_MEDOFL			; SCSI bus reset sent/received
	.LONG	SS$_PARITY			; Uncorrectable parity error
	.LONG	SS$_CTRLERR			; Request sense command failed
	.LONG	SS$_NOSUCHDEV			; No HBA detected
	.LONG	SS$_DATAOVERUN			; Data overrun or underrun
	.LONG	SS$_CTRLERR			; Unexpected SCSI bus free
	.LONG	SS$_CTRLERR			; Target phase sequence error
	.LONG	SS$_BADPARAM			; Invalid CCB length
	.LONG	SS$_MEDOFL			; No requested capability
	.LONG	SS$_MEDOFL			; SCSI BDR sent to target
	.LONG	SS$_ABORT			; CCB Request term. by host
	.LONG	SS$_CTRLERR			; Reserved (place holder)
	.LONG	SS$_MEDOFL			; Bus hung, re-issue IO

.PAGE
	.SBTTL	Driver Prologue Table
;
; Driver prologue table
;
	. = <<.+3>&^C3>				 ; Make sure we're at least longword aligned
	DPTAB	-				; Define driver prologue table
		ADAPTER  = TURBO_SCSI,-		; Turbo channel adapter.
		UCBSIZE  = UCB$K_PKS_LENGTH,-	; Size of a UCB.
		UCB_CRAMS = CRAM_COUNT,-	; CRAMs allocated by loader
		STEP	 = 2,-			; Step 2 ALPHA
		SMP	 = YES,-		; SMP supported
		MAXUNITS = 1,-			; Only 1 unit allowed
		NAME     = PKSDRIVER,-		; Driver name      
		FLAGS    = <DPT$M_SCSI_PORT-	; Driver is a SCSI port driver
		            !DPT$M_NOUNLOAD-	; Driver is not to be unloaded
		            !DPT$M_SNAPSHOT>	; System snapshot allowed

	DPT_STORE INIT				; Control block init value
	DPT_STORE UCB,UCB$B_FLCK,B,SPL$C_IOLOCK8 ; Fork lock
	DPT_STORE UCB,UCB$B_DIPL,B,DEVICEIPL	; Device IPL
	DPT_STORE UCB,UCB$L_DEVCHAR,L,-		; Device characteristics
		<DEV$M_AVL-			; Available
		!DEV$M_ELG-			; Error logging enabled
		!DEV$M_IDV-			; Input device, ROM
		!DEV$M_ODV>
	DPT_STORE UCB,UCB$L_DEVCHAR2,L,-	; Device characteristics
		<DEV$M_NNM>			; Prefix name with "node$"
	DPT_STORE UCB,UCB$B_DEVCLASS,B,DC$_BUS	; Device class
	DPT_STORE UCB,UCB$B_DEVTYPE,B,DT$_TZA_SCSI ; Device type
	DPT_STORE UCB,UCB$L_DEVSTS,L,-		; Device Permanent Status
		<UCB$M_NOCNVRT>			; No conversion of log to phys
                                         
	DPT_STORE REINIT			; Control block re-init values
	DPT_STORE_ISR CRB$L_INTD,PKS$INTERRUPT	; ISR address
	DPT_STORE END

.PAGE
	.SBTTL	Driver Dispatch Table
;
; Driver dispatch table
;
        PK$K_MAX_SCSI_ID == 7			; SCSIAUTO uses this definition

        .EXTERNAL SA$FUNCTAB                    ; SCSI auto configure function
        .EXTERNAL SA$STARTIO                    ;  decision table and routines
        .EXTERNAL SA$CANCELIO                   ;
        .EXTERNAL SA$K_KP_STKSIZ                ; KP stack size for auto configure
        .EXTERNAL SA$K_KP_REGMSK                ; KP register save mask
	.EXTERNAL CLU$GL_ALLOCLS		; Allocation class
	.EXTERNAL CLU$GL_TAPE_ALLOCLS		; Tape allocation
	.EXTERNAL IOC$GL_NAMING			; DEVICE_NAMING parameter
	.EXTERNAL IOC$GL_DEVLIST		; DDB chain head
	.EXTERNAL SCS$GB_SYSTEMID		; SYSSTSTEMID

	DDTAB   -                               ; Driver dispatch table
		DEVNAM   = PKS,-                ; Name of device
		START    = EXE_STD$KP_STARTIO,- ; Common KP start I/O
		KP_STARTIO = SA$STARTIO,-       ; Start I/O routine
		CTRLINIT = PKS$CNTRL_INIT,-     ; Controller Init
		UNITINIT = PKS$UNIT_INIT,-      ; Unit Init
		FUNCTB   = SA$FUNCTAB,-         ; Function decision table
		CANCEL   = SA$CANCELIO,-        ; Cancel I/O routine
		KP_STACK_SIZE = SA$K_KP_STKSIZ,-; Set KP stack size
		KP_REG_MASK = SA$K_KP_REGMSK,-  ; Set KP register save mask
		REGDMP   = PKS_REGDUMP,-        ; Register dump routine
		DIAGBF   = 0,-                  ; Diagnostic buffer size
		ERLGBF   = 4+EMB$L_DV_REGSAV+PKS_ERROR_LEN ; Error log buf size

;
; Function decision table is located in SCSIAUTO.MAR.
;
  
.PAGE

;
; Debug messages and other port messages to be output to OPA0 terminal.
;
; NOTE:  If SGN$GL_VMSD1 (VMSD1 SYSGEN parameter) is not set, the debug
;	 messages will not be displayed.
;
	DRIVER_DATA				; Driver Data Psect

CR   = 13					; ASCII for carriage return
LF   = 10					; ASCII for line feed
BELL = 7					; ASCII for bell
CTRLR_LETTER = 17				; Byte offset to device
						;  controller letter (x) in
						;  the following messages.

PORT_OFF_MSG:
	.ASCIZ  <CR><LF><BELL>/%PKSDRVR-W- PKx0, Port is going OFFLINE./<CR><LF>
UNIT_INIT_MSG:
	.ASCIZ	<CR><LF><BELL>/%PKSDRVR-I- PKx0, Port (re)initialization completed./<CR><LF>
MISC_INTR_MSG:
	.ASCIZ  <CR><LF>/%PKSDRVR-I- Adapter error interrupt processing.../<CR><LF>

.PAGE

	.IF DEFINED PKSDEBUG			; Conditional assembly for debug
;
; Keep ring buffer for send and receive carrier/q_buffer pairs to keep track
; of last 20 VA/PA information for debug. Also timestamp each entry in ABSTIME
; unit to keep track of time spent processing a command. Thus each entry
; consists of 5 quadwords in the order of carrier VA, PA, Q_Buffer VA, PA,
; and timestamp.
;
	. = <<.+3>&^C3>				 ; Make sure we're at least longword aligned
SND_RING:
	.LONG	0				; SEND RING buffer for car/qbuf
SND_END:
	.LONG	0				; End of ring buffer
SND_INDEX:
	.LONG	0				; Current send location
RCV_RING:
	.LONG	0				; RECEIVE RING buff for car/qbuf
RCV_END:
	.LONG	0				; End of ring buffer
RCV_INDEX:
	.LONG	0				; Current receive location
        
	.ENDC

.PAGE
	.SBTTL	REV_TABLE - Legal hardware/firmware version table
;
; This table is used to validate the adapter's hardware and firmware versions
; so that the port driver may either (1) shut down the adapter or
; (2) print a warning message.
;
; The revision table has the following format:
;
;	First longword:     Maximum Firmware version
;	Second longword:    Maximum Hardware version
;
; If either the maximum Firmware or Hardware version is exceeded, then we
; assume that an experimental version of the adapter is in use and bypass all
; further checks.
;
; Each additional 3 longwords contain the following information:
;
;	First longword:	 Legal Firmware version (-1 for end of the table)
;	Second longword: Legal Hardware version
;	Third longword:	 0 for no warning; 1 for a warning
;
;	NOTE:  Firmware version entries must appear in ascending order
;
	. = <<.+3>&^C3>				 ; Make sure we're at least longword aligned
REV_TABLE_TZA:
	.LONG	0			; Maximum Firmware revision
	.LONG	0			; Maximum Hardware revision
	.LONG	0,0,0			; Firmware/Hardware/No warning
	.LONG	-1,-1,-1		; Patch Space
	.LONG	-1			; End of table

REV_TABLE_PZA:
	.LONG	0			; Maximum Firmware revision
	.LONG	0			; Maximum Hardware revision
	.LONG	0,0,0			; Firmware/Hardware/No warning
	.LONG	-1,-1,-1		; Patch Space
	.LONG	-1			; End of table

.PAGE
	.SBTTL	PKS$CNTRL_INIT - SIMport Controller Initialization Routine
	DRIVER_CODE			; Driver Code Psect
;
; This routine is called when PKSDRIVER is loaded or on powerfail recovery.
; For this driver, this routine is a NOP.
;
; CALLING SEQUENCE:
;	CALL	PKS$CNTRL_INIT
;
; INPUT:
;	IPL$_POWER
;	R4 = Address of IDB
;	R6 = Address of DDB
;	R8 = Address of CRB
; 
; OUTPUT:
;
;	R0 = Status
;
        .ENABLE LSB
PKS$CNTRL_INIT:
	$DRIVER_CTRLINIT_ENTRY FETCH=NO

	MOVZWL	#SS$_NORMAL,R0			; Set success status
	RET					; Return

	.DISABLE LSB				; PKS$CNTRL_INIT

.PAGE
	.SBTTL  PKS$UNIT_INIT - SIMport Unit Initialization Routine
;
; This routine is called when PKSDRIVER is loaded or on powerfail recovery.
; It allocates and initializes the necessary resources for the SIMport SCSI
; port driver to function.
;
; This routine should be called once for each channel the SIMport adapter
; supports as follows:
;
; 1.) KZTSA (TZA) SIMport adapters have one SCSI channel/port. This routine
;     should be called once.
; 2.) KZPSA (PZA) SIMport adapters have one SCSI channel/port. This routine
;     should be called once.
;
; Adapter-wide resources shared by two or more channels will be allocated
; only once during the first channel's unit initialization and will remain
; allocated throughout the system's life.
;
; CALLING SEQUENCE:
;	CALL	PKS$UNIT_INIT
;
; INPUT:
;	IPL$_POWER
;	R4 = Address of IDB
;	     IDB$Q_CSR - TZA or PZA I/O space base address
;	     IDB$L_VECTOR - Pointer to IDB extension for vectors, VLE
;	     VLE$L_VECTOR_LIST - Interrupt vector offsets in SCB
;	     IDB$L_DEVICE_SPECIFIC - TZA or PZA channel number (0)
;	R5 = Address of UCB
;
; OUTPUT:
;	R0 = Status
;	The unit is set online
;	Other registers preserved
;
        .ENABLE LSB

PKS$UNIT_INIT:
	$DRIVER_UNITINIT_ENTRY FETCH=YES PRESERVE=<R4,R5>

	MOVL	R5,IDB$PS_OWNER(R4)		; Copy our UCB address to IDB
	MOVL	R5,IDB$PS_AUXSTRUC(R4)		; Copy our UCB address to IDB
						;  so that it can be used in 
						;  interrupt routines
;
; The creation of a port must occur at FIPL holding IOLOCK8 (FORK) lock.
; This is achieved by forking before invoking the CREATE_PORT macro.
;
ASSUME	FKB$K_LENGTH EQ UCB$S_PK_INIFKBLK	; Be sure FKB is big enough

	BBSSI   #UCB$V_PK_IFKB_LOCK,-		; Check to see that this FKB
		UCB$IL_PK_EXFLAGS(R5),10$	; is not in use

	MOVAB	UCB$IB_PK_INIFKBLK(R5),R5	; Fork on alternate FKB
        MOVB    #SPL$C_IOLOCK8,FKB$B_FLCK(R5)	; Set correct spinlock

	FORK	ROUTINE  = INIT_PORT,-		; Queue the fork block
		CONTINUE = 10$

10$:	MOVZWL  #SS$_NORMAL,R0                  ; Set success status
	RET	 				; Return
	.DISABLE LSB				; PKS$UNIT_INIT

.PAGE
	.SBTTL  INIT_PORT - Port Initialization Fork Routine
;
; This fork routine is called when PKSDRIVER is loaded or on powerfail recovery.
; It allocates and initializes the necessary resources for the SIMport SCSI
; port driver to function.
;
; This routine should be called once for each channel the SIMport adapter
; supports as follows:
;
; 1.) KZTSA (TZA) SIMport adapters have one SCSI channel/port. This routine
;     should be called once.
; 2.) KZPSA (PZA) SIMport adapters have one SCSI channel/port. This routine
;     should be called once.
;
; Adapter-wide resources shared by two or more channels will be allocated
; only once during the first channel's unit initialization and will remain
; allocated throughout the system's life.
;
; CALLING SEQUENCE:
;	FORK	ROUTINE = INIT_PORT
;
; INPUT:
;	IPL$_IOLOCK8
;	R4 = Address of IDB
;	     IDB$Q_CSR - TZA or PZA I/O space base address
;	     IDB$L_VECTOR - Pointer to IDB extension for vectors, VLE
;	     VLE$L_VECTOR_LIST - Interrupt vector offsets in SCB
;	     IDB$L_DEVICE_SPECIFIC - TZA or PZA channel number (0)
;	R5 = Address of UCB
;
; OUTPUT:
;	SIMport adapter initialized
;	The unit is set online
;
        .ENABLE LSB

INIT_PORT:
	.JSB_ENTRY	INPUT=<R3,R4,R5>,-
			SCRATCH=<R0,R1,R2>,-
			PRESERVE=<R3,R4,R5,R6>

	MOVAB	-UCB$IB_PK_INIFKBLK(R5),R5	; Point to start of UCB
	BBCCI	#UCB$V_PK_IFKB_LOCK,-		; This FKB is no longer busy
		UCB$IL_PK_EXFLAGS(R5),10$	;  Just clear and proceed

10$:	MOVL	R4,R3				; Save IDB address in R3
        BBC     #UCB$V_POWER,-			; Skip setup of buffers and
                UCB$L_STS(R5),20$		;  CREATE_PORT if powerfail
        MOVL    UCB$L_PDT(R5),R4		; Get SPDT address
        BRW     60$				; Go re-initialize the port
;
; The following section allocates and initializes all the resources necessary
; to run a SIMport SCSI channel.
;
20$:	MOVZWL  #SPDT$C_PKSLENGTH,R1		; Get SPDT size to allocate
        CREATE_PORT				; Create and initialize an SPDT
        MOVL    UCB$L_PDT(R5),R4		; Get SPDT address
        MOVW    #32,SPDT$IW_MAX_BUS_WIDTH(R4)	; Load adapter-specific max bus width
        MOVW    #16,SPDT$IW_CONFIG_BUS_WIDTH(R4); Load adapter-specific configured bus width

;
; Set up the timeout routine to read the port counters. Port counters will
; be read each time the timeout routine is called.
;
	PUSHL	R3				; Save IDB address on stack
	MOVL	UCB$L_CRB(R5),R3		; Get CRB address of port
	MOVL	R4,CRB$L_SCS_STRUC(R3)		; Save SPDT address in CRB
	MOVAB	READ_COUNTER,CRB$L_TOUTROUT(R3)	; Store the address of timeout
						;  routine to read port counters
	MOVB	UCB$B_FLCK(R5),CRB$B_FLCK(R3)	; Initialize the fork lock level
	MNEGL   #1,CRB$L_DUETIME(R3)		; Disable wakeups for now
	POPL	R3				; Restore IDB address in R3
;
; Initialize queues in SPDT to empty.
;
	MOVAL	SPDT$PS_QCMDFL(R4),R0		; Get command Q_Buffer queue
						;  listhead address
	MOVL	R0,(R0)				; Initialize a queue for
						;  command Q_Buffers
	MOVL	R0,4(R0)
        MOVAL   SPDT$PS_RSPFL(R4),R0		; Get response Q_Buffer queue
						;  listhead address
        MOVL    R0,(R0)				; Initialize a queue for
						;  response Q_Buffers
        MOVL    R0,4(R0)
        MOVAL   SPDT$PS_DAFQFL(R4),R0		; Get free Q_Buffer queue
						;  listhead address
        MOVL    R0,(R0)				; Initialize a queue for free
						;  Q_Buffers
        MOVL    R0,4(R0)
	MOVAL	SPDT$PS_WCMDFL(R4),R0		; Get SCSI cmd completion wait
						;  queue listhead address
	MOVL	R0,(R0)				; Initialize the queue
	MOVL	R0,4(R0)
	MOVAL   SPDT$PS_PCMDFL(R4),R0		; Get port cmd completion wait
						;  queue listhead address
        MOVL    R0,(R0)				; Initialize the queue
        MOVL    R0,4(R0)

	MOVAL   SPDT$PS_CARFL(R4),R0		; Get Carrier queue listhead
        MOVL    R0,(R0)				; Initialize the queue
        MOVL    R0,4(R0)

	MOVAL   SPDT$PS_CARWQFL(R4),R0		; Get Carrier wait queue head
        MOVL    R0,(R0)				; Initialize the queue
        MOVL    R0,4(R0)
;
; Set up miscellaneous port related stuff.
;
	.IF DEFINED PKSHISTORY
	MOVAL	SPDT$IS_CARRIER_HIST(R4),-
		SPDT$IS_CARRIER_PTR(R4)
	MOVAL	SPDT$IS_BUFFER_HIST(R4),-
		SPDT$IS_BUFFER_PTR(R4)
	.ENDC

	MOVL	#<SPDT$M_PFLG_DIR_DMA-		; Port uses direct DMA
		!SPDT$M_PFLG_SYNCH-		; Port can do synch operation
		!SPDT$M_PFLG_ASYNCH-		; Port can do asynch operation
		!SPDT$M_PFLG_LUNS-		; Port supports LUNs
		!SPDT$M_PFLG_CMDQ-		; Port supports TCQ
		!SPDT$M_PFLG_PORT_AUTOSENSE-	; Port supports autosense
		!SPDT$M_PFLG_SMART_PORT>,-	; Intelligent SCSI adapter
		SPDT$L_PORT_FLAGS(R4)
	MOVL	IDB$L_DEVICE_SPECIFIC(R3),-	; Save channel # of port
		SPDT$IS_CHANNEL(R4)
	MOVL	UCB$L_DDB(R5),R0		; Get DDB address
	SUBB3	#^A'A',-			; Translate controller letter to
		DDB$T_NAME+3(R0),-		;  SCSI bus # and save it in the
		SPDT$L_SCSI_PORT_ID(R4)		;  SPDT
;
; Set up inquiry data for target mode.
; Allocate some pool for the inquiry data and zero the block.
;
30$:	SUBL    #8,SP				; Allocate two lw on stack
	MOVL    SP,R2				; Store address of lw to receive
						;  address of allocated block
        ADDL3   #4,R2,R6			; Store address of lw to receive
						;  size of allocated block
        PUSHL   R6				; Arg 4 = addr to receive size
        PUSHL   R2				; Arg 3 = addr to receive addr
						;  of allocated block
	PUSHL	#ALN_LONG			; Arg 2 = 4 byte alignment
	PUSHL	#INQ$C_LENGTH  			; Arg 1 = size of block
	CALLS   #4,EXE$ALONONPAGED_ALN		; Allocate a block
	POPL	R2				; Save addr of allocated block
	POPL	R6				; Save allocated size
	BLBS	R0,35$				; Did we get the block?
	KP_STALL_FORK_WAIT  KPB=R8,-		; No, fork and wait
		FKB=KPB$PS_UCB(R8)		;  using UCB fork block
	BRB	30$				; Try allocation again

35$:	MOVL	R2,SPDT$PS_INQUIRY_DATA(R4)	; Save inquiry data address
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC5   #0,(SP),#0,R6,(R2)		; Zero initialize the block
	POPR	#^M<R0,R1,R2,R3,R4,R5>		; Restore registers
;
; Initialize static fields in the inquiry data. R2 points to buffer.
;
	MOVW	R6,INQ$IW_SIZE(R2)		; Structure size
	MOVB	#DYN$C_MISC,INQ$IB_TYPE(R2)	; Set buffer type
	MOVB	#DYN$C_QBUF,INQ$IB_SUBTYPE(R2)	; Set buffer subtype
	MOVB	#INQ$C_PROC_DEV,-		; Peripheral device type
		INQ$IB_PERIPH_DEV_TYPE(R2)
	MOVB	#INQ$C_COMPLIES_TO_SCSI2,-	; ANSI version
		INQ$IB_ANSI_VERSION(R2)
	MOVB	#INQ$C_SCSI2_FORMAT,-		; Response data format
		INQ$IB_RESP_DATA_FORMAT(R2)
	MOVB	#INQ$C_ADDITIONAL_LEN,-		; Additional length
		INQ$IB_ADDITIONAL_LEN(R2)
.Disable Flagging				; Turn off info. it's aligned
        MOVQ    #^A/DEC     /,-			; Vendor ID
		INQ$IB_VENDOR_ID(R2)
        MOVQ    #^A/OpenVMS /,-			; Product ID
		INQ$IB_PRODUCT_ID(R2)
.Enable Flagging
	MOVL	SYS$GQ_VERSION,-		; Product revision
		INQ$IS_PRODUCT_REV(R2)
	MOVL	UCB$L_DDB(R5),R0		; Get DDB address
	MOVB	DDB$T_NAME+3(R0),-		; Put ASCII port letter into
		INQ$IB_PRODUCT_ID+8(R2)		;  Product ID
	MOVB	G^CLU$GL_ALLOCLS,-		; Put allocation class into
		INQ$IB_PRODUCT_ID+9(R2)		;  Product ID
	MOVB	G^CLU$GL_TAPE_ALLOCLS,-		; Put tape allocation class
		INQ$IB_PRODUCT_ID+10(R2)	;  into Product ID

; (X-16) If new naming and this port has a port allocation class defined,
; overwrite 8 bytes of the above data with a new naming format message.
; The port allocation class is stored in the port DDB.
	
	BLBC	IOC$GL_NAMING,40$	; If old naming, "never mind"
	MOVL	SPDT$L_PORT_UCB(R4),R6	; Get port UCB
	MOVL	UCB$L_DDB(R6),R0	; Get port DDB
	BBC	#DDB$V_PAC,-		; If no PAC, nothing to do
		DDB$B_FLAGS(R0),40$	;

	MOVL	SCS$GB_SYSTEMID,-
		INQ$IB_PRODUCT_ID+8(R2)	; Save SCSSYSTEMID
	MOVL	DDB$L_ALLOCLS(R0),-
		INQ$IB_PRODUCT_ID+12(R2); Save ALLOCLS
;
; Set up Sense data for target mode.
; Allocate some pool for the Sense data and zero the block.
;
40$:	SUBL    #8,SP				; Allocate two lw on stack
	MOVL    SP,R2				; Store address of lw to receive
						;  address of allocated block
        ADDL3   #4,R2,R6			; Store address of lw to receive
						;  size of allocated block
        PUSHL   R6				; Arg 4 = addr to receive size
        PUSHL   R2				; Arg 3 = addr to receive addr
						;  of allocated block
	PUSHL	#ALN_LONG			; Arg 2 = 4 byte alignment
	PUSHL	#SENSE$C_LENGTH  		; Arg 1 = size of block
	CALLS   #4,EXE$ALONONPAGED_ALN		; Allocate a block
	POPL	R2				; Save addr of allocated block
	POPL	R6				; Save allocated size
	BLBS	R0,45$				; Did we get the block?
	KP_STALL_FORK_WAIT  KPB=R8,-		; No, fork and wait
		FKB=KPB$PS_UCB(R8)		;  using UCB fork block
	BRB	40$				; Try allocation again

45$:	MOVL	R2,SPDT$PS_SENSE_DATA(R4)	; Save sense data address
	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC5   #0,(SP),#0,R6,(R2)		; Zero initialize the block
	POPR	#^M<R0,R1,R2,R3,R4,R5>		; Restore registers
;
; Initialize static fields in the Sense data. R2 points to buffer.
;
	MOVW	R6,SENSE$IW_SIZE(R2)		; Structure size
	MOVB	#DYN$C_MISC,SENSE$IB_TYPE(R2)	; Set buffer type
	MOVB	#DYN$C_QBUF,SENSE$IB_SUBTYPE(R2) ; Set buffer subtype

	MOVB	#SENSE$C_CURRENT_ERROR,-	; Setup error code
		SENSE$IB_ERROR_CODE(R2)
	MOVB	#SENSE$C_ILLEGAL_REQUEST,-	; Setup Sense key
		SENSE$IB_SENSE_KEY(R2)
	MOVB	#SENSE$C_ADDITIONAL_LEN,-	; Additional sense length
		SENSE$IB_ADDITIONAL_LEN(R2)
	MOVB	#SENSE$C_LUN_NOT_SUPPORTED,-	; Additional sense code
		SENSE$IB_ADD_SENSE_CODE(R2)
;
; Discriminate TZA from PZA, based (for now) on the type of bus we're on.
;
	CLRL	SPDT$IS_ADAPTER_TYPE(R4)	; Assume KZTSA
	MOVL	IDB$PS_ADP(R3),R2		; Get ADP address
	MOVL	ADP$PS_BUS_ARRAY(R2),R2		; Get BUS_ARRAY address
	CMPL	BUSARRAY$L_BUS_TYPE(R2),-	; Check bus type
		#BUS$_PCI
	BNEQ	50$				; Are we on a PCI bus (KZPSA)?
	MOVL	#SPDT$C_PCI,-			; Yes, indicate so
		SPDT$IS_ADAPTER_TYPE(R4)
;
; This port driver will impose a maximum transfer size of 64KB for now.
; The class driver will use this value to segment all IO's to be
; less than or equal to MAXBCNT.
;
50$:	MOVZWL	#PKS_MAXBCNT,-			; Setup maximum segment size
		SPDT$L_MAXBYTECNT(R4)
;
; If this is the first time the adapter is being initialized, then allocate
; Adapter wide resources. Resources include Queue Buffers, Queue Carriers,
; the Adapter Block, I/O mailboxes (CRAMS), and a KPB for (re)initializing
; the port.
	BBS	#SPDT$V_STRUCT_ALLOCATED,-	; Skip adapter resource init 	
		SPDT$IS_PORTSTS(R4),CHNL_SETUP	;  if already done
;
; First allocate the command Queue Buffers and mark command slots as free.
;
	CLRQ	-(SP)				; Create temporary buffer for
						;  return buffer and length
	PUSHAL	4(SP)				; Buffer address for SPDT addr
	PUSHAL	4(SP)				; Buffer address for length
	PUSHL	#QUEUE_BUFFER_POOL_SIZE		; Buffer length
	PUSHL	R4				; SCSI Port Descriptor Table
	CALLS	#4, @SPDT$PS_RL_POOL_ALLOC(R4)	; Allocate command buffers
	MOVQ	(SP)+, R1			; Allocation length and address
	BLBC	R0,80$				; Did we get the pool?
	MOVL	R4,QBUF$PS_FLINK(R2)		; Yes, Setup FLINK
	MOVL	R4,QBUF$PS_BLINK(R2)		; Setup BLINK
	MOVW	R1,QBUF$IW_SIZE(R2)		; Save Queue Buffer size
	MOVB	#DYN$C_MISC,QBUF$IB_TYPE(R2)	; Set buffer type
	MOVB	#DYN$C_QBUF,QBUF$IB_SUBTYPE(R2)	; Set buffer subtype

	ADDL2	#HEADER_SIZE,R2			; Point past header info
	MOVL	R2,SPDT$PS_CMD_BASE(R4)		; DMA area and CMD area are one
						; and the same if using mapping
						; registers.
	CLRL	SPDT$IL_CMD_BITS(R4)		; Mark command slots as free
	CLRL	SPDT$IL_CMD_BITS+4(R4)
	CLRL	SPDT$IL_CMD_BITS+8(R4)
	CLRL	SPDT$IL_CMD_BITS+12(R4)
;
; Map the CSRs, and set up the CRAMs.
;
	BSBW	MAP_CSRS
;
; Setup vectors in SPDT to point to PKS specific mapping routines.  The
; _MR versions use DMA map registers.
;
	BITL	#SPDT$M_PFLG_MAPPING_REG,-	; Are we using mapping regs?
		SPDT$L_PORT_FLAGS(R4)
	BEQL	43$
	MOVAL	PKS$MAP_BUFFER_MR,-		; Map Buffer routine w/regs
		SPDT$PS_CD_BUFFER_MAP(R4)
	MOVAL   PKS$UNMAP_BUFFER_MR,-		; Unmap Buffer routine w/regs
		SPDT$PS_CD_BUFFER_UNMAP(R4)
	BRB	46$
43$:	MOVAL	PKS$MAP_BUFFER,-		; Map Buffer routine
		SPDT$PS_CD_BUFFER_MAP(R4)
	MOVAL   PKS$UNMAP_BUFFER,-		; Unmap Buffer routine
		SPDT$PS_CD_BUFFER_UNMAP(R4)
;
; Now allocate the Carriers and Adapter Block, and initialize the queues.
;
46$:	BSBW	ALLOC_CARRIERS			; Allocate Carriers
	BLBC	R0,80$				; Do not initialize port if we
						;  did not get Carriers
	BSBW	ALLOC_AB			; Allocate the Adapter Block
	BLBC	R0,80$				; Do not initialize port if we
						;  did not get an Adapter Block
	BSBW	SETUP_QUEUES			; Set up queues in AB
	BLBC	R0,80$				; Do not initialize port if we
						;  did not get a SPTEs
	BSBW	SETUP_CRAMS			; Set up the CRAMs
;
; ALlocate a PTE for mapping the user data buffer for unaligned data transfers.
;
	MOVL	#1,R2				; Need 1 PTE
	JSB	G^LDR$ALLOC_PT			; Get a PTE
	MOVL	R1,SPDT$L_SPTE_SVAPTE(R4)	; Save it in SPDT
	SUBL2	G^MMG$GL_SPTBASE,R1		; Get offset into page table
	ASHL	#-PTE$C_SHIFT_SIZE,R1,R1
	ASHL	G^MMG$GL_VPN_TO_VA,R1,R1	; ... virtual address
 	BISL3	#VA$M_SYSTEM,R1, -		; Set bit for system address
		SPDT$L_SPTE_BASE(R4)
;
; For channel initialization, we need to allocate a KPB (Kernel Process Block)
; to be used for (re)initializing the port. The KPB pointer will be kept in
; the SPDT for easy access.
;
	KP_ALLOCATE_KPB  KPB=SPDT$PS_KPB(R4),-	; Allocate KPB for init process	
			 STACK=G^MMG$GL_PAGE_SIZE,- ;  Size of kernel process stack
			 FLAGS=#KPB_FLAGS	;  Make permanent KPB
	BLBC	R0,80$				; Do not initialize port if we
						;  did not get a KPB

	BISL	#SPDT$M_STRUCT_ALLOCATED,-	; Set flag that adapter-wide
		SPDT$IS_PORTSTS(R4)		; resources are allocated now

CHNL_SETUP:
;
; Set up pointer in SPDT to the port counters area for this channel.
;
	MOVL    SPDT$PS_AB(R4),R0		; Get the Adapter Block address
	MOVAB   AB$PS_COUNTERS(R0),-		; Save the address of the
		SPDT$PS_COUNTERS(R4)		;  channel port counters area
;
; (Re-)initialize the port.
;
60$:	MOVL	SPDT$PS_KPB(R4),R0		; Get KPB address
	MOVL	R5,KPB$PS_UCB(R0)		; Save port's UCB addr in KPB
	MOVL	R3,KPB$PS_SCSI_PTR1(R0)		; Save IDB addr in KPB
	KP_START  KPB = R0, -			; Call Kernel Process routine
		  REGISTERS = #PKS_KP_REGMSK, -	;  to reinit the port
		  ROUTINE   = REINIT_PORT
;;	BSBW	SETUP_SANITY_TQE		; Set up TQE for adapter sanity

70$:	CLRL	UCB$L_FPC(R5)			; Clear fork semaphore
	RSB					; Return from INIT_PORT
;
; If we got here, then we failed allocating a resource. Do not set port online
; but do remember that it failed initialization.
;
80$:	BISL	#SPDT$M_STS_FAILED,-		; Initialization failed
		SPDT$L_STS(R4)
	BICL	#UCB$M_ONLINE,UCB$L_STS(R5)	; Clear UCB online
	BICL    #SPDT$M_STS_ONLINE,-		; Clear SPDT online
		SPDT$L_STS(R4)
	BSBW	DEALLOC_RESOURCES		; Deallocate acquired resources
	BRB	70$				; Return
	.DISABLE LSB				; INIT_PORT

.PAGE
	.SBTTL	ALLOC_CARRIERS - Allocate the Queue Carriers
;
; This subroutine allocates the Queue Carriers. Carriers are linked onto
; the SPDT SPDT$PS_CARFL and SPDT$PS_CARBL links. 
;
; Static fields in the Carriers are also initialized in this routine.
;
; INPUT:
;	IPL$_IOLOCK8
;	R4 = SPDT address
;
; OUTPUT:
;	R0 = SS$_NORMAL  - Successful completion
;	     SS$_INSFMEM - No memory for Queue Carriers
;	R4 = SPDT address
;	     SPDT$PS_CARFL, SPDT$PS_CARBL Forward and backward link to Carriers
;
	.ENABLE LSB
ALLOC_CARRIERS:
	.JSB_ENTRY	INPUT=<R4>,OUTPUT=<R0>, -
			SCRATCH=<R1>,PRESERVE=<R2,R3,R4,R5,R6,R7>

	MOVL	#CAR$C_NUM_CARRIERS,R8		; Set up loop counter
;
; Allocate some pool for a Carrier and zero the block.
;
10$:	SUBL    #8,SP				; Allocate two lw on stack
	MOVL    SP,R2				; Store address of lw to receive
						;  address of allocated block
        ADDL3   #4,R2,R6			; Store address of lw to receive
						;  size of allocated block
        PUSHL   R6				; Arg 4 = addr to receive size
        PUSHL   R2				; Arg 3 = addr to receive addr
						;  of allocated block
	PUSHL	#ALN_HEXWORD			; Arg 2 = 32 byte alignment
	PUSHL	#CAR$C_LENGTH			; Arg 1 = size of block
	CALLS   #4,EXE$ALONONPAGED_ALN		; Allocate a block
	POPL	R2				; Get addr of allocated block
	POPL	R6				; Get allocated size
	BLBS	R0,20$				; Did we get the block?
	MOVZWL	#SS$_INSFMEM,R0			; No, insufficient memory
	BRB	30$				; Return error

20$:	PUSHR	#^M<R2,R3,R4,R5>		; Save registers
	MOVC5   #0,(SP),#0,R6,(R2)		; Zero initialize the block
	POPR	#^M<R2,R3,R4,R5>		; Restore registers
;
; Fill in static fields of the Carrier.
;
	MOVW	R6,CAR$IW_SIZE(R2)		; Set the allocated block size
	MOVB	#DYN$C_MISC,CAR$IB_TYPE(R2)	; Set type
	MOVB	#DYN$C_CAR,CAR$IB_SUBTYPE(R2)	; Set subtype
	MOVL	R2,CAR$PQ_CAR_TOKEN(R2)		; Save Carrier VA for port use
	MOVAB	CAR$PQ_NEXT_PTR(R2),R6		; VA of adapter-visible section
	$SVA_TO_PA   SVA = R6			; PA of the carrier address
	ADDL2	SPDT$L_DMA_BASE(R4),R0		; add in DMA window base
	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_STQ R0,CAR$PQ_PA(R2)		; Save PA
;
; Place this Carrier onto a doubly linked list pointed to by SPDT$PS_CARFL
; and SPDT$PS_CARBL.
;
	INSERT_EL -
		EL  = (R2),-			; Link this Carrier into
		QUE = @SPDT$PS_CARBL(R4),-	;  the SPDT
		SCRATCH = R0
	SOBGTR	R8,10$				; Link next Carrier

	MOVZWL	#SS$_NORMAL,R0			; Set success status
30$:	RSB					; Return
	.DISABLE LSB				; ALLOC_CARRIERS

.PAGE
	.SBTTL	ALLOC_AB - Allocate the Adapter Block
;
; This subroutine allocates the Adapter Block (AB). The adapter requested
; size of the AB is 80 bytes. We allocate 316 bytes of memory for the Adapter
; Block. The port driver uses the remaining free memory area for various
; internal variables and to keep the running total of port counters.
;
; The Adapter Block must be port page aligned (8 KB aligned). 
;
; Static fields in the AB are also initialized in this routine. The AB is
; allocated once for a SIMport adapter and will not be deallocated for the
; duration of the system.
;
; INPUT:
;	IPL$_IOLOCK8
;	R4 = SPDT address
;
; OUTPUT:
;	R0 = SS$_NORMAL  - Successful completion
;	     SS$_INSFMEM - No memory for Adapter Block
;	R4 = SPDT address
;	     SPDT$PS_AB - Address of the allocated Adapter Block
;
	.ENABLE LSB
ALLOC_AB:
	.JSB_ENTRY	INPUT=<R4>,OUTPUT=<R0>, -
			SCRATCH=<R1>,PRESERVE=<R2,R3,R4,R5,R6,R7>
;
; Allocate and zero the memory for the adapter block.
;
	SUBL	#4,SP				; Allocate a longword on stack
	PUSHL	SP				; Arg 4 = Addr to receive size
	PUSHAB	SPDT$PS_AB(R4)			; Arg 3 = Addr to receive addr
						;  of allocated AB
	PUSHL	#ALN_HEXWORD			; Arg 2 = Hexword alignment 
	PUSHL	#AB$C_SIZE			; Arg 1 = Block size
	CALLS	#4,EXE$ALONONPAGED_ALN		; Allocate adapter block
	POPL	R6				; Get the allocated size
	BLBS	R0,10$				; Did we get the block?
	MOVZWL	#SS$_INSFMEM,R0			; No, insufficient memory for AB
	BRB	40$				; Return error

10$:	MOVL	SPDT$PS_AB(R4),R2		; Get the address of AB
	PUSHR   #^M<R1,R2,R4,R5>		; Save registers
	MOVC5   #0,(SP),#0,R6,(R2)		; Zero initialize the block
	POPR    #^M<R1,R2,R4,R5>		; Restore registers
;
; Put physical address of adapter block in the adapter block.  Allocate and
; use map registers if needed.
;
	$SVA_TO_PA	SVA = R2
	BITL	#SPDT$M_PFLG_MAPPING_REG,-	; Are we using map regs?
		SPDT$L_PORT_FLAGS(R4)
	BEQL	20$				; Nope; just use PA.
;
; Is the AB within the direct-DMA window? Note that even if we allocated
; a larger-than-AB$C_SIZE block, only that much has to be visible to the
; adapter; we can safely use that size to calculate the ending PA.
;
	EVAX_ADDQ #AB$C_SIZE,R0,R3		; Yes, point to last byte in AB
	EVAX_CMPULE R3,SPDT$IQ_DMA_SIZE(R4),R7	; Compare to DMA window size
	BLBS	R7,20$				; Branch if LE window size
;
; O.K., we hafta map it.  Can't use MAP_S0, 'cause we're not executing in
; a KP, so we can't stall if we don't get the memory or registers.
;
; Allocate and init a CRCTX.
;
	PUSHL	SPDT$IS_FLCK(R4)		; Fork lock index
	PUSHAB	AB$PS_CRCTX(R2)			; CRCTX ref.
	PUSHL	SPDT$PS_CRAB(R4)		; CRAB address
	CALLS	#3,IOC$ALLOC_CRCTX
	BLBC	R0,40$				; Did we get one?
;
; How many map registers do we need?
;
	EVAX_AND R2,-				; Calculate byte within map
		SPDT$IS_CRCTX_BWP_MASK(R4),R0	; unit
	ADDL2	#AB$C_SIZE,R0			; Add byte count, plus
	ADDL2	SPDT$IS_CRCTX_BWP_MASK(R4),R0	; a map unit to round up.
	MNEGL	SPDT$IS_CRCTX_SHIFT(R4),R1	; Convert bytes to map units.
	ASHL	R1,R0,R1
	MOVL	AB$PS_CRCTX(R2),R0
	ADDL3	R1,#2,CRCTX$L_ITEM_CNT(R0)	; Add 2 for guard entries.
;
; Attempt to allocate the necessary map registers.  Give up (return error)
; if allocation fails.
;
	PUSHL	R0				; CRCTX address
	PUSHL	SPDT$PS_CRAB(R4)		; CRAB address
	CALLS	#2,IOC$ALLOC_CNT_RES
	BLBC	R0,40$				; Did we get them?
;
; At this point, we have the necessary map registers.
; Calculate the SVAPTE of the AB, so we can map it.
;
	EVAX_SLL  R2,#<64-VA$V_SYSTEM>,R0	; Clear space select bit.
	EVAX_SRL  R0,MMG$GQ_64SYS_SHIFT,R0	; Get virtual page number
	MOVAQ	@MMG$GL_SPTBASE[R0],R0		; Get SVAPTE that maps VA
;	
; Map the region, putting the DMA-able address into AB$PQ_PA.
;
	PUSHAL	AB$PQ_PA(R2)			; Where to put DMA addr.
	EVAX_AND R2,-				; Calculate byte-within-page
		MMG$GL_BWP_MASK,R1
	PUSHL	R1				; Byte offset
	PUSHL	R0				; Buffer SVAPTE
	PUSHL	AB$PS_CRCTX(R2)			; CRCTX addr
	PUSHL	SPDT$L_ADP(R4)			; ADP addr
	CALLS	#5,IOC$LOAD_MAP
	BLBC	R0,40$
	BRB	30$
;
; No mapping registers needed, just use PA (+ DMA base).
;
20$:	ADDL2	SPDT$L_DMA_BASE(R4),R0		; Add in DMA window base
	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_STQ R0,AB$PQ_PA(R2)		; Save it in the Adapter Block
;
; Set up static fields in the adapter block.
;
30$:	MOVW	#AB$C_QB_SIZE,AB$IW_QB_SIZE(R2)	; Set up queue buffer size
	MOVB	#AB$C_CCB_PTR_SIZE,-		; Set up CCB pointer size
		AB$IB_CCB_PTR_SIZE(R2)
	MOVAL	AB$PS_CAR_FLINK(R2),R0		; Carrier queue listhead addr.
	MOVL	R6,AB$IW_SIZE(R2)		; Save allocation size
	MOVL    R0,(R0)				; Initialize forward link and
	MOVL    R0,4(R0)			;  backward link to empty

	MOVZWL	#SS$_NORMAL,R0			; Set success status
40$:	RSB					; Return
	.DISABLE LSB				; ALLOC_AB

.PAGE
	.SBTTL  SETUP_CRAMS - I/O Mailbox Initialization

; This subroutine sets up and initializes all of the I/O Mailboxes (CRAMs)
; necessary for accessing all SIMport adapter registers.
;
; The required number of have been previously allocated (by the DPT
; from driver loading) and are pointed to by the UCB (UCB$PS_CRAM). For each
; CRAM, this routine performs the following:
;
;	1.) Points an SPDT extension location to the CRAM
;	2.) Initializes the CRAM with a system specific command
;
; The CRAMs are not removed the UCB list. Upon completion of this routine,
; the CRAMs are pointed to by the UCB (UCB$PS_CRAM) and the SPDT
; (SPDT$PS_cram_ptr_name).
;
; CALLING SEQUENCE:
;	BSBW	SETUP_CRAMS
;
; INPUT:
;	IPL$_IOLOCK8
;	R3 = IDB address
;	R4 = SPDT address
;	R5 = UCB address
;
; OUTPUT:
;	R0 = Return status
;	     SS$_NORMAL - Successful completion
;	     SS$_INSFMEM - Insufficient memory for CRAMs. Device should be
;			   set offline.
;	R4 = SPDT address 
;		SPDT$PS_AB - Address of the allocated CRAMs
;	CRAMS linked from the SPDT and initialized
;
        .ENABLE LSB
SETUP_CRAMS:
	.JSB_ENTRY	INPUT=<R3,R4,R5>,OUTPUT=<R0>, -
			SCRATCH=<R1>,PRESERVE=<R2,R3,R4,R5,R6,R7,R8,R9>

	MOVAB	CRAMCMD_TABLE,R2		; Table of CRAM pointers
	MOVL	UCB$PS_ADP(R5),R6		; Get ADP address
	MOVL	UCB$PS_CRAM(R5),R7		; Point to preallocated CRAMs
	BNEQ	5$				; Do we have any CRAMs?
	MOVZWL	#SS$_INSFMEM,R0			; No, return and set offline
	BRB	20$

5$:	MOVL	SPDT$IS_ADAPTER_TYPE(R4),R9	; Set up adapter type
10$:	ADDL3	TBL$IS_SPDTOFF(R2),R4,R0	; SPDT extension for CRAM ptr
	MOVL	R7,(R0)				; Point SPDT to CRAM
	ADDL3	TBL$IS_IOHANDLEOFF(R2),R4,R8	; SPDT extension for IOHANDLE
;
; Initialize this CRAM with a system specific command.
;
	CRAM_CMD    INDEX  = TBL$IS_INDEX(R2),-	; Command index
		    OFFSET = TBL$IS_CSROFF(R2)[R9],-; CSR offset from IDB$Q_CSR
		    ADP    = R6,-		; ADP address
		    CRAM   = R7,-		; CRAM address
		    COMMAND = R8		; IOHANDLE address

	MOVL	CRAM$L_FLINK(R7),R7		; Point to next CRAM in list
	ADDL2	#TBL$C_ENTRY_SIZE,R2		; Get next table entry
	TSTL	TBL$IS_SPDTOFF(R2)		; Are we at end of table?
	BNEQ	10$				; If not, initialize next CRAM

	MOVZWL	#SS$_NORMAL,R0			; Return success status
20$:	RSB

	.DISABLE LSB				; SETUP_CRAMS

.PAGE
	.SBTTL  MAP_CSRS - Map CSRs

; This subroutine maps the CSRs accessed by this driver to virtual address
; space. There are three pages required. For the TZA, the pages are as follows:
;
; (Base physical address of I/O space for slot + ^XD0000)
; (Base physical address of I/O space for slot + ^XE0000)
; (Base physical address of I/O space for slot + ^XF0000)
;
; For the PZA, the pages are as follows:
;
; (Base physical address of I/O space for slot + ^X100)
; (Base physical address of I/O space for slot + ^X300)
; (Base physical address of I/O space for slot + ^X000)
;
; Since the pages span so much physical address space (121 8k pages in the
; TZA case), we use three separate IOHANDLES, and three calls to IOC$MAP_IO,
; to map them.
;
; IDB$Q_CSR is also setup with the SVA of CSR base address.
;
; CALLING SEQUENCE:
;	BSBW	MAP_CSRS
;
; INPUT:
;	IPL$_IOLOCK8
;	R3 = IDB address
;	R4 = SPDT address
;	R5 = UCB address
;
; OUTPUT:
;	R0 = Return status
;	     SS$_NORMAL - Successful completion
;	     SS$_INSFSPTS - Insufficient SPTEs for CSRS. Device should be
;			    set offline.
;	R3 = IDB address 
;	     IDB$Q_CSR - Base VA of the CSRs
;	CSRs mapped.
;
        .ENABLE LSB
MAP_CSRS:
	.JSB_ENTRY	INPUT=<R3,R4,R5>,OUTPUT=<R0>,SCRATCH=<R1>, -
			PRESERVE=<R2,R3,R4,R5,R6,R7,R8>
;
; Save our ADP address and bus node number for MAP_IO.
;
	MOVL	IDB$PS_ADP(R3),R7		; ADP address
	MOVL	UCB$L_CRB(R5),R2		; Point to CRB
	MOVL	CRB$L_NODE(R2),R2		; Extract Node number
;
; On the PCI bus, we need to determine the bus physical address where the
; CSR's reside. On Turbochannel, addressing is slot-relative, so we don't need
; to do this.
;
	CLRL	SPDT$L_DMA_BASE(R4)
.Disable Flagging				; Turn off info. it's aligned
	CLRQ	SPDT$IQ_PHYS_ADDR(R4)		; For TC offsets relative to
.Enable Flagging
	MOVL	#^XD0000,R6			;  zero, and first page @D0000
	TSTL	SPDT$IS_ADAPTER_TYPE(R4)	; 0=TC, 1=PCI
	BEQL	10$
	MOVL	#^X100,R6			; For PCI first page @100

;
; See if the Direct DMA window can map all of physical memory.  If it can,
; we need not mess with allocating and loading map registers.
;
; If the call fails, we assume that the bus can access all of memory
; directly.
;
	CLRQ	SPDT$IQ_DMA_SIZE(R4)		; Clear all bits
	PUSHAQ	SPDT$IQ_DMA_SIZE(R4)
	PUSHL	#IOC$K_DIRECT_DMA_SIZE
	PUSHL	UCB$L_CRB(R5)			; CRB address
	CALLS	#3,G^IOC$NODE_DATA
	BLBC	R0,7$
	EVAX_SLL  SPDT$IQ_DMA_SIZE(R4),#20,R1	; Convert MB to bytes
	EVAX_STQ  R1,SPDT$IQ_DMA_SIZE(R4)
	MOVL	ADP$L_CRAB(R7),-		; Save CRAB pointer for
		SPDT$PS_CRAB(R4)		; possible mapping operations.
	BEQL	7$				; Branch if no regs available
	ASHL	G^MMG$GL_VA_TO_VPN,R1,R1	; Convert bytes to pages
	MOVL	MMG$GL_MAXPFN,R8		; Get max PFN number
	INCL	R8				;   plus one
	CMPL	R1,R8				; Compare with phys. mem.
	BGEQU	7$				; Big enough?
	BISL	#SPDT$M_PFLG_MAPPING_REG,-	; Nope, we have to use mapping
		SPDT$L_PORT_FLAGS(R4)		; regs.
;
; Now find out where in bus address space the window lives.  We'll need to
; add this value to all physical addresses passed to the adapter for DMA
; operations.
7$:	PUSHAL	SPDT$L_DMA_BASE(R4)
	PUSHL	#IOC$K_DIRECT_DMA_BASE
	PUSHL	UCB$L_CRB(R5)			; CRB address
	CALLS	#3,G^IOC$NODE_DATA
;
; Set up to call IOC$READ_PCI_CONFIG to get memory base register. This
; tells us where *in bus space* the CSR's live.
;
9$:	CLRQ	IDB$Q_CSR(R3)
	PUSHAB	IDB$Q_CSR(R3)			; Where to put PA
	PUSHL	#4				; # bytes to read
	PUSHL	#16				; Offset in config space (BAR 0)
	PUSHL	R2				; Bus node number
	PUSHL	IDB$PS_ADP(R3)			; Point to ADP

	CALLS	#5,IOC$READ_PCI_CONFIG		; Get PCI mem space PA
  	BLBC	R0,20$				; Don't init if can't read
;
; PCI defines the lower nybble for its own use.
;
	BICL2	#^XF,IDB$Q_CSR(R3)
;
; Map the SIMport registers page.
;
10$:	MAP_PAGE -
		NAME = REG,-			; Use the REG IOHANDLE
		OFFSET = R6,-			; Offset of reg page
		NODE = R2,-			; Bus node no
		ADP = R7			; ADP address
	BLBC	R0,40$
;
; Map the CLEAR_INT page.
;
	MOVL	#^XE0000,R6			; TC: second page @E0000
	TSTL	SPDT$IS_ADAPTER_TYPE(R4)	; 0=TC, 1=PCI
	BEQL	20$
	MOVL	#^X300,R6			; PCI: second page @300

20$:	MAP_PAGE -
		NAME = CLEAR_INT,-		; Use the CLEAR_INT IOHANDLE
		OFFSET = R6,-			; Offset of CLEAR_INT page
		NODE = R2,-			; Bus node number
		ADP = R7			; ADP address
	BLBC	R0,40$
;
; Map the RESET page.
;
	MOVL	#^XF0000,R6			; TC: third page @F0000
	TSTL	SPDT$IS_ADAPTER_TYPE(R4)	; 0=TC, 1=PCI
	BEQL	30$
	MOVL	#^X0,R6				; PCI: third page @0

30$:	MAP_PAGE -
		NAME = RESET,-			; Use the RESET IOHANDLE
		OFFSET = R6,-			; Offset of CLEAR_INT page
		NODE = R2,-			; Bus node nunmber
		ADP = R7			; ADP address
	BLBC	R0,40$

	MOVZWL	#SS$_NORMAL,R0			; Success status
40$:	RSB					; Return
	.DISABLE LSB				; MAP_CSRS

.PAGE
	.SBTTL DEALLOC_RESOURCES - Return acquired Initialization Resources

; This subroutine deallocates any resources (if any) that the driver has
; acquired so far. This routine is called if the port is being set offline.
;
; Resources may include:
;	1.) Queue Buffers
;	2.) Carriers
;	3.) Adapter Block
;	4.) Target Mode resources
;
; CALLING SEQUENCE:
;	BSBW	DEALLOC_RESOURCES
;
; INPUT:
;	IPL$_IOLOCK8
;	R4 = SPDT address
;	R5 = UCB address
;
; OUTPUT:
;	R0 = Return status
;	     SS$_NORMAL - Successful completion
;
        .ENABLE LSB
DEALLOC_RESOURCES:
	.JSB_ENTRY	INPUT=<R4,R5>,OUTPUT=<R0>, -
			SCRATCH=<R1>,PRESERVE=<R2,R4,R5,R6,R7>
;
; First return the Command Queue Buffers.
;
	MOVL	SPDT$PS_CMD_BASE(R4),R2		; Get Queue Buffer base address
	BEQL	10$				; Do we have any Buffers?
	SUBL2	#HEADER_SIZE,R2			; Yes, point to header info
	MOVL	QBUF$IW_SIZE(R2),R1		; Get block size
	PUSHL	R4				; SCSI Port Descriptor Table
	CALLS	#1,@SPDT$PS_RL_POOL_DEALLOC(R4)	; Deallocate command buffers
;
; Return the Carriers. The Carriers have been inserted onto a doubly linked
; list in the SPDT pointed to by SPDT$PS_CARFL.
;
; Assumption here is that none of the carriers is in use, so we don't have
; to deallocate any map registers.
;
10$:	MOVAL	SPDT$PS_CARFL(R4),R6		; Address of Carrier wait queue
20$:	REMOVE_HEAD -                           ; Get address of the next
		QUE        = R6,-		;  Carrier
		OUT_EL     = R0,-
		EMPTY_ADDR = 30$,-		;  Branch if no more entries
		SCRATCH    = R1
	MOVL	R0,R2
	MOVL	CAR$PS_CRCTX(R0),R7		; Have we allocated a CRCTX?
	BEQL	29$
	PUSHL	R7				; Now deallocate the CRCTX.
	CALLS	#1,G^IOC$DEALLOC_CRCTX
;
; Now deallocate the carrier.
;
29$:	MOVL	R2,R0
	MOVZWL  CAR$IW_SIZE(R0),R1              ; Get size field
	JSB     EXE$DEANONPGDSIZ                ; Deallocate this Carrier
	BRB	20$				; Get next Carrier in the queue
;
; Now return the Adapter Block. The Adapter Block is pointed to by the SPDT.
;
30$:	MOVL	SPDT$PS_AB(R4),R0		; Get the address of AB
	BEQL	40$				; Exit if no AB
	MOVL	AB$PS_CRCTX(R0),R7		; Have we allocated a CRCTX?
	BEQL	39$
	PUSHL	R7				; Yep, deallocate the regs.
	PUSHL	SPDT$PS_CRAB(R4)
	CALLS	#2,G^IOC$DEALLOC_CNT_RES
	PUSHL	R7				; Now deallocate the CRCTX.
	CALLS	#1,G^IOC$DEALLOC_CRCTX
39$:	MOVL	SPDT$PS_AB(R4),R0
	MOVL	#AB$C_SIZE,R1			; Get AB size
	JSB	EXE$DEANONPGDSIZ		; Deallocate AB
;
; If Target Mode has been enabled, deallocate all Target Mode resources.
;
40$:	BBCC	#SPDT$V_TARGET_MODE,-		; Is Target Mode enabled?
		SPDT$IS_PORTSTS(R4),50$
	BSBW	RET_TARGET_MODE_RESOURCES	; Yes, return resources

50$:	RSB					; Return
	.DISABLE LSB				; DEALLOC_RESOURCES

.PAGE
	.SBTTL RET_TARGET_MODE_RESOURCES - Return Target Mode Resources

; This subroutine deallocates all resources that the driver has acquired
; for Target Mode.
;
; Resources include:
; - Buffer used for holding the inquiry data
; - Buffer used for holding the sense data
; - Queue Buffers used for the Accept Target IO CCB list
;
; CALLING SEQUENCE:
;	BSBW	RET_TARGET_MODE_RESOURCES
;
; INPUT:
;	IPL$_IOLOCK8
;	R4 = SPDT address
;
; OUTPUT:
;	R0 = SS$_NORMAL - Successful completion
;
        .ENABLE LSB
RET_TARGET_MODE_RESOURCES:
	.JSB_ENTRY	INPUT=<R4>,OUTPUT=<R0>, -
			SCRATCH=<R1>,PRESERVE=<R2,R4,R5,R6,R7>
;
; Deallocate the buffer used for holding the inquiry data for target mode.
;
	TSTL	SPDT$PS_INQUIRY_DATA(R4)	; Check inquiry buffer
	BNEQ	10$				; Do we have one?
	BUG_CHECK INCONSTATE,FATAL		; No, we want to capture this
10$:	MOVL	SPDT$PS_INQUIRY_DATA(R4),R0	; Yes, get the address
	MOVZWL	INQ$IW_SIZE(R0),R1		; Get the buffer size
	JSB	EXE$DEANONPGDSIZ		; Deallocate the inquiry buffer
	CLRL	SPDT$PS_INQUIRY_DATA(R4)	; Clear inquiry buffer pointer
;
; Deallocate the buffer used for holding the sense data for target mode.
;
	TSTL	SPDT$PS_SENSE_DATA(R4)		; Check sense buffer
	BNEQ	20$				; Do we have one?
	BUG_CHECK INCONSTATE,FATAL		; No, we want to capture this
20$:	MOVL	SPDT$PS_SENSE_DATA(R4),R0	; Yes, get the address
	MOVZWL	SENSE$IW_SIZE(R0),R1		; Get the buffer size
	JSB	EXE$DEANONPGDSIZ		; Deallocate the sense buffer
	CLRL	SPDT$PS_SENSE_DATA(R4)		; Clear sense buffer pointer
;
; Deallocate the Queue Buffers used for the Accept Target IO CCB list.
;
	MOVL	SPDT$PS_ATIO_FL(R4),R6		; Point to first Queue Buffer
	BNEQ	30$				; Do we have one?
	BUG_CHECK INCONSTATE,FATAL		; No, we want to capture this
30$:	MOVL	#NUM_ATIO_CCBS,R7		; Yes, init loop counter
40$:	MOVL	R6,R0				; Point to next Queue Buffer
	BNEQ	50$				; Do we have one?
	BUG_CHECK INCONSTATE,FATAL		; No, we want to capture this
50$:	MOVL	QBUF$PS_FLINK(R0),R6		; Save next Queue Buffer ptr
	MOVZWL	QBUF$IW_SIZE(R0),R1		; Pass Queue Buffer size
	JSB	EXE$DEANONPGDSIZ		; Deallocate the Queue buffer
	SOBGTR	R7,40$				; Loop for all CCBs
	CLRL	SPDT$PS_ATIO_FL(R4)		; Clear Buffer list pointer

	MOVZWL	#SS$_NORMAL,R0			; Setup for return
	RSB					; Return
	.DISABLE LSB				; RET_TARGET_MODE_RESOURCES

.PAGE
	.SBTTL	REINIT_PORT - Reinitialize the SIMport adapter
;
; This subroutine is called to (re)initialize a TZA/PZA SCSI channel.
; (NOTE: The words "channel" and "port" are used interchangeably here.)
; It can be called at the following times:
;   1.) During the first-time initialization of the SIMport adapter
;       and the channels during system configuration
;   2.) To reinitialize the channels after the SIMport adapter crashed
;       via an error interrupt
;
; This routine will first reset the adapter to the UNINITIALIZED state
; if this is the first-time initialization of the adapter or if this is
; the reinitialization after an adapter crash. This is done by setting
; the MIN bit in the AMCSR. Then, for the channel reinitialization,
; necessary parameters are set up and a command is sent to the adapter to
; bring the channel to the DISABLED state. Once the channel is in the
; DISABLED state, further parameters are set and a command is sent to the
; adapter to bring this channel to the ENABLED state.
;
; If any error is encountered during the (re)initialization sequences, the
; sequence will be restarted from the top. If more than 5 minutes have elapsed
; since the start of the (re)initialization and it had an error, the port
; will be set to offline for the duration of the current system's lifetime.
; Also an OFFLINE warning message will be output to the operator's console.
;  
; INPUT:
;	4(AP) = KPB address
;
; OUTPUT:
;	If no error, the channel is set to ENABLED state and is ready to
;	process commands.
;	R2-R10 preserved
;
	.ENABLE LSB

        $ARG_DEF        <-
        kpb	         >       		; KPB address.

REINIT_PORT: 
	.CALL_ENTRY	<R2,R3,R4,R5,R6,R7,R8,R9,R10,R11>

	MOVL	kpb(AP),R8			; Get the KPB address

	MOVL	KPB$PS_UCB(R8),R5		; Get port's UCB address
	MOVL	UCB$L_PDT(R5),R4		; Get the port's SPDT address
	MOVL	R4,KPB$PS_SCSI_PTR1(R8)		; Store SPDT into KPB
	BICL	#UCB$M_ONLINE,UCB$L_STS(R5)     ; Clear port UCB ONLINE
	BICL2	#SPDT$M_STS_ONLINE,-		; Clear port SPDT ONLINE
		SPDT$L_STS(R4)
	PUSHL	KPB$PS_SCSI_SCDRP(R8)		; Save SCDRP from KPB
	BSBW	GET_SCDRP			; Allocate the SCDRP
	MOVL	R2,R5				; Save the SCDRP
	MOVL	R2,KPB$PS_SCSI_SCDRP(R8)	; Save SCDRP in KPB
	MOVL	R8,SCDRP$PS_KPB(R5)		; Store KPB into SCDRP
	MOVL	R4,SCDRP$PS_SPDT(R5)		; Store SPDT into SCDRP
	BISL	#SPDT$M_INIT_ADAPTER,-		; We need to init the adapter
		SPDT$IS_PORTSTS(R4)
	PUSHL	R5				; Push SCDRP on stack
	PUSHL	R4				; Push SPDT on stack
	CALLS	#2,@SPDT$PS_CD_RESET_SCSI_BUS(R4) ; Reset SCSI bus
	MOVL	R5,R0				; Deallocate the SCDRP
	CALL_DRVDEALMEM	SAVE_R0R1=NO		; COM_STD$DRVDEALMEM
	POPL	KPB$PS_SCSI_SCDRP(R8)		; Clear the SCDRP pointer
	MOVL	SPDT$L_PORT_UCB(R4),R5		; Get port UCB
	BISL	#UCB$M_ONLINE,UCB$L_STS(R5)     ; Set UCB online
	BISL    #SPDT$M_STS_ONLINE,-		; Set SPDT online
		SPDT$L_STS(R4)
	RET					; Return
	.DISABLE LSB				; REINIT_PORT

.PAGE
	.SBTTL	INIT_ADAPTER - (Re)initialize the SIMport adapter
;
; This subroutine is called to (re)initialize a TZA/PZA SCSI channel.
; (NOTE: The words "channel" and "port" are used interchangeably here.)
; It can be called at the following times:
;   1.) During the first-time initialization of the SIMport adapter
;       and the channels during system configuration
;   2.) To reinitialize the channels after the SIMport adapter crashed
;       via an error interrupt
;
; This routine will first reset the adapter to the UNINITIALIZED state
; if this is the first-time initialization of the adapter or if this is
; the reinitialization after an adapter crash. This is done by setting
; the MIN bit in the AMCSR. Then, for the channel reinitialization,
; necessary parameters are set up and a command is sent to the adapter to
; bring the channel to the DISABLED state. Once the channel is in the
; DISABLED state, further parameters are set and a command is sent to the
; adapter to bring this channel to the ENABLED state.
;
; If any error is encountered during the (re)initialization sequences, the
; sequence will be restarted from the top. If more than 5 minutes have elapsed
; since the start of the (re)initialization and it had an error, the port
; will be set to offline for the duration of the current system's lifetime.
; Also an OFFLINE warning message will be output to the operator's console.
;  
; INPUT:
;	R8 = KPB address
;
; OUTPUT:
;	If no error, the channel is set to ENABLED state and is ready to
;	process commands.
;	R2-R10 preserved
;
	.ENABLE LSB

INIT_ADAPTER: 
	.JSB_ENTRY	<R2,R3,R4,R5,R6,R7,R8,R9,R10,R11>

	MOVL    KPB$PS_UCB(R8),R5		; Get UCB address
	MOVL	UCB$L_PDT(R5),R4		; Get SPDT address
	MOVL	SPDT$L_ADP(R4),R2		; Get ADP address
	MOVL    ADP$L_CRB(R2),R3		; Get CRB address

	BICL	#UCB$M_ONLINE,UCB$L_STS(R5)     ; Clear UCB ONLINE to reinit
	BICL    #SPDT$M_STS_ONLINE,-		; Clear SPDT ONLINE to reinit
		SPDT$L_STS(R4)
	BSBW	CHECK_REV			; Check FW/HW revision levels
	BLBC	R0,PORT_OFFLINE			; Set port offline if revision
						;  levels are not correct
	BBS     #SPDT$V_PORTONLY,-		; Skip adapter (re)init if
		SPDT$IS_PORTSTS(R4),90$		;  already done
;
; For adapter reinitialization, clean up all loose ends hanging from previous
; state. This includes cleaning up all Carriers and Q_Buffers and resuming all
; I/O threads that were suspended waiting for responses.
;
;;	BSBW	CLEANUP_BUF			; Do buffer cleanup
;
; Begin SIMport adapter initialization by setting the Maintenance
; Initialize (MIN) bit in AMCSR to reset the adapter.
;
	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = AMCSR,-		; Set MIN bit in AMCSR
		DATA = #TZA_AMCSR$M_MIN
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO
;
; Wait for the UNIN bit to be set in the ASR. When the UNIN bit becomes set,
; the SIMport adapter has completed its reset.
;
.Disable Flagging				; Turn off info. it's aligned
	PUSHL	#SPL$C_IOLOCK8			; Fork lock
	PUSHAQ	ONE_SECOND			; Time delay in ticks
	PUSHL	R8				; KPB address
	CALLS	#3,G^EXE$KP_TQE_WAIT		; Fork and wait the desired time
.Enable Flagging
	PUSHL R0				; Save R0
	READ_CSR REG=ASR,DEST=R6		; Read the ASR
	POPL R0					; Restore R0
	BBS #TZA_ASR$V_UNIN,R6,20$		; If UNIN set, reset complete
10$:
.Disable Flagging				; Turn off info. it's aligned
	PUSHL	#SPL$C_IOLOCK8			; Fork lock
	PUSHAQ	ONE_SECOND			; Time delay in ticks
	PUSHL	R8				; KPB address
	CALLS	#3,G^EXE$KP_TQE_WAIT		; Fork and wait the desired time
.Enable Flagging
	PUSHL R0				; Save R0
	READ_CSR REG=ASR,DEST=R6		; Read the ASR
	POPL R0					; Restore R0
	BBS #TZA_ASR$V_UNIN,R6,20$		; If UNIN set, reset complete
;
; The UNIN bit failed to set. We need to do a board level reset.
;
	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = RESET,-		; Do board level reset
		DATA = #0
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO

.Disable Flagging				; Turn off info. it's aligned
	PUSHL	#SPL$C_IOLOCK8			; Fork lock
	PUSHAQ	TWO_SECONDS			; Time delay in ticks
	PUSHL	R8				; KPB address
	CALLS	#3,G^EXE$KP_TQE_WAIT		; Fork and wait the desired time
.Enable Flagging
	PUSHL R0				; Save R0
	READ_CSR REG=ASR,DEST=R6		; Read the ASR
	POPL R0					; Restore R0
	BBS #TZA_ASR$V_UNIN,R6,20$		; If UNIN set, reset complete
	BRW	PORT_OFFLINE			; UNIN failed, set port offline
;
; The SIMport adapter is now reset. Build a SET ADAPTER STATE command
; requesting DISABLED state and place it on the Driver Adapter Command
; Queue (DACQ).
;
; Allocate some pool for a buffer and zero the block.
;
20$:	SUBL    #8,SP				; Allocate two lw on stack
	MOVL    SP,R2				; Store address of lw to receive
						;  address of allocated block
        ADDL3   #4,R2,R6			; Store address of lw to receive
						;  size of allocated block
        PUSHL   R6				; Arg 4 = addr to receive size
        PUSHL   R2				; Arg 3 = addr to receive addr
						;  of allocated block
	PUSHL	#ALN_LONG			; Arg 2 = 4 byte alignment
	PUSHL	#256				; Arg 1 = size of block
	CALLS   #4,EXE$ALONONPAGED_ALN		; Allocate a block
	POPL	R2				; Get addr of allocated block
	POPL	R7				; Get allocated size
	BLBS	R0,25$				; Did we get the block?
	KP_STALL_FORK_WAIT  KPB=R8,-		; No, fork and wait
		FKB=KPB$PS_UCB(R8)		;  using UCB fork block
	BRB	20$				; Try allocation again

25$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC5   #0,(SP),#0,R7,(R2)		; Zero initialize the block
	POPR	#^M<R0,R1,R2,R3,R4,R5>		; Restore registers
;
; Initialize static fields in the Queue Buffer. R2 points to Queue Buffer.
;
	MOVW	R7,QBUF$IW_SIZE(R2)		; Save Queue Buffer size
	MOVB	#DYN$C_MISC,QBUF$IB_TYPE(R2)	; Set buffer type
	MOVB	#DYN$C_QBUF,QBUF$IB_SUBTYPE(R2)	; Set buffer subtype

	MOVAB	QBUF$PQ_CCB_ADDR(R2),R6		; VA of adapter visible section
	$SVA_TO_PA   SVA=R6			; PA of adapter visible section

	BITL	#SPDT$M_PFLG_MAPPING_REG,-	; Are we using map regs?
		SPDT$L_PORT_FLAGS(R4)
	BEQL	27$				; Nope; just use PA.
;
; Is the Queue Buffer within the direct-DMA window? Only the first
; QBUF$C_ADPLEN bytes starting at QBUF$PQ_CCB_ADDR have to be, that's
; the only portion accessed by the adapter.
;
	EVAX_ADDQ #QBUF$C_ADPLEN,R0,R3		; Yes, just use PA
	EVAX_CMPULE R3,SPDT$IQ_DMA_SIZE(R4),R7	; Compare to DMA window size
	BLBS	R7,27$				; Branch if LE window size
;
; O.K., we hafta map it.
;
	MAP_S0	-
		SVA = R6,-			; Queue Buffer address (VA).
		NBYTES = #QBUF$C_ADPLEN,-	; Size to map.
		CRCTX = QBUF$PS_INIT_CRCTX_PTR(R2),- ; CRCTX pointer reference.
		KPB$ = SPDT$PS_KPB(R4),-	; KPB to stall on alloc fail
		DMA_ADDR = QBUF$PQ_PA_QBUF(R2)	; Where to put DMA-able addr
	BRB	29$
;
; No mapping registers needed, just use PA (+ DMA base).
;
27$:	ADDL2	SPDT$L_DMA_BASE(R4),R0		; Add in DMA window base
	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_STQ R0,QBUF$PQ_PA_QBUF(R2)		; Save physical address

29$:	MOVL    R2,R7                           ; Copy Q_Buffer pointer
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_SET_ADAP_STATE_SIZE,-	; Setup CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_SET_ADAP_STATE,-	; Set SIMport function code to
		QBUF$IB_FUNC_CODE(R7)		;  Set Adapter State
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM Status
	MOVB	#QBUF$C_SET_ADAP_STATE_DIS,-	; Request disabled state 
		QBUF$IB_ADP_STATE(R7)
	BSBW	GET_QUEUE_CARRIER		; Get a queue carrier
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	MOVB    QBUF$IB_FUNC_CODE(R7),-		; Put SIMport function code
		CAR$IB_FUNC_CODE(R2)		;  into carrier
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  Flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
	BSBW	INSERT_DACQ			; Insert carrier onto DACQ
;
; Write the physical address of the Adapter Block to the Adapter Block Base
; Register and wait for the ADAPTER STATE SET response from the adapter.
;
	MOVL	SPDT$PS_AB(R4),R6		; Get Adapter Block address
	MOVL	AB$PQ_PA(R6),R7			; Get physical address of AB
	EVAX_ZAP  R7,#^XF0,R7			; Clear the upper longword
	EVAX_SRL  R7,#TZA_ABBR$C_PA_SHIFT,R7	; Shift PA right 5 bits
	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = ABBR,-			; Give AB physical address to
		  DATA = R7,-			;  the adapter
		  TYPE = L
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	WAIT_FOR_RESPONSE-			; Wait for response on ADRQ
		WAIT_TIME = 2,-			;  Wait for 2 seconds
		SPDT = R4
;
; We have received an ADAPTER STATE SET response message from the SIMport
; adapter. The adapter should now be in the DISABLED state. Interpret any
; configuration parameters within the response message and place the required
; number of queue elements onto the Driver Adapter Free Queue (DAFQ).
;
	BSBW	REMOVE_ADRQ			; Get response from ADRQ
	MOVL	CAR$PQ_QBUF_PTR(R2),R7		; Get Q_Buffer virtual address
						;  written by the adapter
	MOVB	QBUF$IB_XFER_ALIGN(R7),-	; Save adapter alignment
		SPDT$IB_XFER_ALIGN(R4)		;  capabilities
	MOVZBL	QBUF$IB_N_FREEQ(R7),R6		; Free queue entries requested
	BSBW	SETUP_DAFQ			; Set up free queue entries
	BSBW	SETUP_ADFQ			; Set up ADFQ free queue entries
;
; Send a SET PARAMETERS command to the adapter based on configuration
; information from the ADAPTER STATE SET response message. 
; We still have a Queue Buffer in R7 and a Carrier in R2.
;
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_SET_PARAM_SIZE,-	; Setup CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_SET_PARAM,-		; Set SIMport function code to
		QBUF$IB_FUNC_CODE(R7)		;  Set Parameters
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM Status
	MOVB	#NUM_TARGET_LUNS,-		; Target mode LUNs active
		QBUF$IB_NT_LUNS(R7)
	CLRB	QBUF$IB_N_HOST_SG(R7)		; No host 4K segments
	BISW	#QBUF$M_ENB_COUNTERS,-		; Enable port counters
		QBUF$IW_SET_PARAM_FLAGS(R7)
	CLRL	QBUF$IS_SYSTEM_TIME(R7)		; Setup system time
	CLRL	QBUF$IS_INT_HOLDOFF(R7)		; No interrupt holdoff timer
	CLRL	QBUF$IS_RP_TIMER(R7)		; Reply timer
	CLRQ	QBUF$IQ_HOST_SG_BSD(R7)		; No buffer segment descriptor
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL	R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	MOVB	QBUF$IB_FUNC_CODE(R7),-		; Put SIMport function code
		CAR$IB_FUNC_CODE(R2)		;  into carrier
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
	BSBW	INSERT_DACQ			; Insert command on DACQ
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = DACQIR,-		; Notify adapter of an entry
		  DATA = R9,-			;  on the DACQ
		  TYPE = L
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	WAIT_FOR_RESPONSE-			; Wait for response on ADRQ
		WAIT_TIME = 2,-			;  Wait for 2 seconds
		SPDT = R4
;
; Issue a second SET ADAPTER STATE command to request the adapter to
; transition to the ENABLED state.
;
	BSBW	REMOVE_ADRQ			; Get response from ADRQ
	MOVL	CAR$PQ_QBUF_PTR(R2),R7		; Get Q_Buffer virtual address
						;  written by the adapter
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_SET_ADAP_STATE_SIZE,-	; Setup CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_SET_ADAP_STATE,-	; Set SIMport function code to
		QBUF$IB_FUNC_CODE(R7)		;  Set Adapter State
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM Status
	MOVB	#QBUF$C_SET_ADAP_STATE_ENB,-	; Request enabled state 
		QBUF$IB_ADP_STATE(R7)
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	MOVB    QBUF$IB_FUNC_CODE(R7),-		; Put SIMport function code
		CAR$IB_FUNC_CODE(R2)		;  into carrier
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
	BSBW	INSERT_DACQ			; Insert command on DACQ
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits

	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = DACQIR,-		; Notify adapter of an entry
		  DATA = R9,-			;  on the DACQ
		  TYPE = L
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	WAIT_FOR_RESPONSE-			; Wait for response on ADRQ
		WAIT_TIME = 2,-			;  Wait for 2 seconds
		SPDT = R4
;
; The adapter is now in the ENABLED state. Issue a SET CHANNEL STATE command
; to request the channel to transition to the ENABLED state.
;
	BSBW	REMOVE_ADRQ			; Get response from ADRQ
	MOVL	CAR$PQ_QBUF_PTR(R2),R7		; Get Q_Buffer virtual address
						;  written by the adapter
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_SET_CHAN_STATE_SIZE,-	; Setup CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_SET_CHAN_STATE,-	; Set SIMport function code to
		QBUF$IB_FUNC_CODE(R7)		;  Set Channel State
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM Status
	MOVB	#QBUF$C_SET_CHAN_STATE_ENB,-	; Request enabled state 
		QBUF$IB_CHN_STATE(R7)
	CLRB	QBUF$IB_CHAN_ID(R7)		; Clear channel id
	CLRB	QBUF$IB_NODE_ID(R7)		; Clear node id
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	MOVB    QBUF$IB_FUNC_CODE(R7),-		; Put SIMport function code
		CAR$IB_FUNC_CODE(R2)		;  into carrier
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
	BSBW	INSERT_DACQ			; Give the command to adapter
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits

	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = DACQIR,-		; Notify adapter of an entry
		  DATA = R9,-			;  on the DACQ
		  TYPE = L
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	WAIT_FOR_RESPONSE-			; Wait for response on ADRQ
		WAIT_TIME = 2,-			;  Wait for 2 seconds
		SPDT = R4
;
; The Adapter and channel are now in the ENABLED state. Save the host
; adapter SCSI ID in the SPDT.
;
	BSBW	REMOVE_ADRQ			; Get response from ADRQ
	MOVL	CAR$PQ_QBUF_PTR(R2),R7		; Get Q_Buffer virtual address
	MOVZBL	QBUF$IB_NODE_ID(R7),R9		; Get host adapter SCSI id
	MOVL	R9,SPDT$IS_SCSI_ID_NUM(R4)	; Save adapter SCSI ID
	BBSS	R9,SPDT$L_SCSI_BUS_ID(R4),30$	; Save SCSI ID as a mask also
;
; Issue a Path Inquiry command to obtain CAM and SCSI parameter information.
;
30$:	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_PATH_INQ_SIZE,-		; Setup CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_PATH_INQUIRY,-		; Set CAM function code to
		QBUF$IB_FUNC_CODE(R7)		;  Path Inquiry
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM Status
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	MOVB    QBUF$IB_FUNC_CODE(R7),-		; Put CAM function code into
		CAR$IB_FUNC_CODE(R2)		;  carrier
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
	BSBW	INSERT_DACQ			; Give the command to adapter
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits

	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = DACQIR,-		; Notify adapter of an entry
		  DATA = R9,-			;  on the DACQ
		  TYPE = L
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	WAIT_FOR_RESPONSE-			; Wait for response on ADRQ
		WAIT_TIME = 2,-			;  Wait for 2 seconds
		SPDT = R4
;
; Save the path inquiry information that was returned from the adapter
; into the SPDT.
;
	BSBW	REMOVE_ADRQ			; Get response from ADRQ
	MOVL	CAR$PQ_QBUF_PTR(R2),R7		; Get Q_Buffer virtual address
						;  written by the adapter
	MOVAB	SPDT$IB_PI_DATA(R4),R10		; Get start addr of PI data
	MOVAB	QBUF$IB_PI_VERSION(R7),R3
	PUSHR   #^M<R1,R2,R4,R5>		; Save registers
	MOVC3   #SPDT$C_PI_DATA_SIZE,(R3),(R10)	; Copy returned PI data to SPDT
	POPR    #^M<R1,R2,R4,R5>		; Restore registers
;
; Set up for target mode. This includes sending an ENABLE_LUN command to the
; adapter. Start by formatting an Enable LUN command in the Queue Buffer.
;
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_ENABLE_LUN_SIZE,-	; Setup CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_ENABLE_LUN,-		; Set CAM function code to
		QBUF$IB_FUNC_CODE(R7)		;  Enable LUN
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM Status
	CLRL	QBUF$IS_CONNECT_ID(R7)		; Clear connect id
	BISB	#QBUF$M_DISCONS_MANDATORY,-	; Discon for each CCB for LUN
		QBUF$IB_CAM_FLAGS_4(R7)
	MOVB	SPDT$IS_SCSI_ID_NUM(R4),-	; Setup target id
		QBUF$IB_TARGET_ID(R7)
	MOVW	#6,QBUF$IW_GROUP_6_VU_CDB(R7)	; 6 byte CDB's
	MOVW	#6,QBUF$IW_GROUP_7_VU_CDB(R7)	; 6 byte CDB's
.Disable Flagging				; Turn off info. it's aligned

	CLRL	QBUF$IB_PAD2(R7)		; DOF for debug Clear padding

	CLRQ	QBUF$PS_IMMED_NOTIFY_CCBS(R7)	; Immediate notify CCB list
	MOVL	#1,QBUF$IS_NUM_IMMED_NOTIFY_CCBS(R7) ; Number immediate notify CCBs
	CLRQ	QBUF$PS_TARGET_CCBS(R7)		; Target CCB list
.Enable Flagging
	MOVL	#NUM_ATIO_CCBS,-		; Number Accept target IO CCBs 
		QBUF$IS_NUM_TARGET_CCBS(R7)
	BSBW	SETUP_ACCEPT_CCB_LIST		; Setup accept CCB list
;
; Fill in the Private Data Area of the Enable LUN command by inserting the
; Accept target IO Carrier/Queue Buffer pair onto the Accept Target IO list
; head and tail.
;
	MOVL	SPDT$IS_ATIO_LIST_HEAD(R4),R0	; Get Accept list head pointer

.Disable Flagging				; Turn off info. it's aligned
	EVAX_LDQ R0,CAR$PQ_PA(R0)		; Get Accept Carrier PA
	EVAX_STQ R0,QBUF$PS_ACCEPT_LIST_HEAD(R7) ; Setup Accept list head

	MOVL	SPDT$IS_ATIO_LIST_TAIL(R4),R1	; Get Accept list tail pointer

	EVAX_LDQ R1,CAR$PQ_PA(R1)		; Get Accept Carrier PA
	EVAX_STQ R1,QBUF$PS_ACCEPT_LIST_TAIL(R7) ; Setup Accept list tail
.Enable Flagging
;
; Send the Enable LUN command to the SIMport adapter.
;
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	MOVB    QBUF$IB_FUNC_CODE(R7),-		; Put CAM function code into
		CAR$IB_FUNC_CODE(R2)		;  Carrier
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
	BSBW	INSERT_DACQ			; Give the command to adapter
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits

	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = DACQIR,-		; Notify adapter of an entry
		  DATA = R9,-			;  on the DACQ
		  TYPE = L
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	WAIT_FOR_RESPONSE-			; Wait for response on ADRQ
		WAIT_TIME = 2,-			;  Wait for 2 seconds
		SPDT = R4
	BSBW	REMOVE_ADRQ			; Get response from ADRQ
	CMPB	CAR$IB_STATUS(R2),-		; Check Enable LUN status
		#CAR$C_REQ_COMP_NO_ERROR
	BEQL	34$				; Is the LUN enabled?
	BSBW	RET_TARGET_MODE_RESOURCES	; No, return resources
	LOG_ERROR -				; Log an error log entry
		TYPE = CTL_ERR,-		;  Controller error
		CDT  = #0			;  No associated SCDT
	BRB	35$				; Join common code

34$:	CLRL	SPDT$IS_NUM_ACCEPTS(R4)		; Clear Accept counter
	CLRL	SPDT$IS_NUM_CONTINUES(R4)	; Clear Coutinue counter
	CLRL	SPDT$IS_NUM_TARGET_EVENTS(R4)	; Clear Target Event counter
	BISL	#SPDT$M_TARGET_MODE,-		; Yes, target mode is enabled
		SPDT$IS_PORTSTS(R4)
;
; Enable the functions supported on the Turbochannel.
;
35$:	PUSHL   #IOC$K_DISABLE_PAR		; Disable parity on bus.
	PUSHL   UCB$L_CRB(R5)                   ; CRB address
	CALLS   #2,IOC$NODE_FUNCTION            ; Enable interrupts

	PUSHL   #IOC$K_ENABLE_INTR		; Enable interrupts on TC
	PUSHL   UCB$L_CRB(R5)                   ; CRB address
	CALLS   #2,IOC$NODE_FUNCTION            ; Enable interrupts
;
; Return resources and allow adapter completion and miscellaneous interrupts.
;
	MOVL	CAR$PQ_QBUF_PTR(R2),R7		; Get Q_Buffer virtual address
						;  written by the adapter
	BSBW	RET_QUEUE_CARRIER		; Return Carrier to pool
	MOVL	QBUF$PS_INIT_CRCTX_PTR(R7),R0	; Did we alloc a CRCTX?
	BEQL	50$
	BITL	#CRCTX$M_ITEM_VALID,-		; Yep.  Any registers?
		CRCTX$L_FLAGS(R0)
	BEQL	40$
	PUSHL	R0				; CRCTX
	PUSHL	SPDT$PS_CRAB(R4)		; CRAB address
	CALLS	#2,IOC$DEALLOC_CNT_RES
40$:	PUSHL	QBUF$PS_INIT_CRCTX_PTR(R7)
	CALLS	#1,IOC$DEALLOC_CRCTX
50$:	MOVL	R7,R0				; Get buffer address
	MOVZWL	QBUF$IW_SIZE(R0),R1		; Get buffer size
	JSB	EXE$DEANONPGDSIZ		; Deallocate buffer
	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = AMCSR,-		; Allow adapter interrupts
		  DATA = #TZA_AMCSR$M_INT_ENB
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO
;
; Initialization has now been completed. Setup miscellaneous SPDT fields.
;
	MOVW	#SPDT$C_TYPE_PKS,-		; Setup Port Type
		SPDT$W_SPDT_TYPE(R4)
	MOVL	#SCDRP$C_VERSION!-		; Save SCDRP version number,
		 <SPDT$C_VERSION@8>!-		;  SPDT version number,
		 <SCDT$C_VERSION@16>!-		;  SCDT version number,
		 <STDT$C_VERSION@24>,-		;  STDT version number,
		SPDT$L_VERSION_CHECK(R4)	;  Data structure version

	.IF DEFINED PKSDEBUG
	MOVAB	UNIT_INIT_MSG,R1		; Get addr of msg to output
	JSB	G^EXE$OUTZSTRING		; Send the message
	.ENDC
90$:	RSB					; Return
;
; We have had some type of error. The port cannot be used. Put the port
; offline and return. By setting the FAILED bit and not setting the port
; ONLINE bit we will prevent connections from being established or commands
; being sent using this port. (We may want to reset the port before we finish.)
;
PORT_OFFLINE:
	.IF DEFINED PKSDEBUG
	MOVAB	PORT_OFF_MSG,R1			; Get addr of msg to output
	JSB	G^EXE$OUTZSTRING		; Send the message
	.ENDC

	BICL	#UCB$M_ONLINE,UCB$L_STS(R5)     ; Clear UCB online
	BICL    #SPDT$M_STS_ONLINE,-		; Clear SPDT online
		SPDT$L_STS(R4)
	BSBW	PORT_SHUTDOWN			; Go shut down this port
	BRB	90$				; Return
	.DISABLE LSB				; INIT_ADAPTER

	.PAGE
        .SBTTL  SETUP_ACCEPT_CCB_LIST - Setup Accept Target IO CCB list
;
; This subroutine is called during (re)initialization to setup the
; Accept Target IO CCB list.
;
; It performs the following:
; - Allocates and formats a CCB for the Accept Target IO CCB list.
; - Allocates and links a Carrier for the Accept Target IO CCB.
;
; Note that this routine assumes that it is running in a Kernel Process context.
;
; INPUT:
;	R4 = SPDT address
;	R8 = KPB address to use for fork stalling
;
; OUTPUT:
;	SPDT$IS_ATIO_LIST_HEAD = Carrier VA of Accept list head
;	SPDT$IS_ATIO_LIST_TAIL = Carrier VA of Accept list tail
;	SPDT$PS_ATIO_FL = Accept Target IO CCB list head pointer
;
        .ENABLE LSB
SETUP_ACCEPT_CCB_LIST:
	.JSB_ENTRY	INPUT=<R4,R8>, OUTPUT=<R0>, -
			PRESERVE=<R2,R3,R4,R5,R6,R7,R8,R9,R10>
;
; Allocate and format CCBs for the Accept Target IO CCB list.
;
	CLRL	R10				; Clear CCB list previous ptr
	CLRL	SPDT$IS_ATIO_LIST_HEAD(R4)	; Clear Accept list head
	CLRL	SPDT$IS_ATIO_LIST_TAIL(R4)	;   and tail pointers
	CLRL	SPDT$PS_ATIO_FL(R4)		; Clear Queue Buffer next ptr
	MOVL	#NUM_ATIO_CCBS,R9		; Init loop counter
10$:	MOVL	#QUEUE_BUFFER_SIZE,R1		; Load length first
	SUBL	#8,SP				; Allocate two longword in stack
	MOVL	SP,R2				; Store addr of lw to receive
						;  address of allocated block
	ADDL3	#4,R2,R7			; Store addr of lw to receive
						;  size of allocated block
	PUSHL	R7				; Arg 4 = addr to receive size
	PUSHL	R2				; Arg 3 = addr to receive addr
						;  of allocated block
	PUSHL	#ALN_QUAD			; Arg 2 = alignment bit
	PUSHL	R1				; Arg 1 = requested size 
	CALLS	#4,EXE$ALONONPAGED_ALN		; Allocate a block
	POPL	R2				; Get addr of allocated block
	POPL	R7				; Get allocated size of block
	BLBS	R0,20$				; Branch if failure to retry
	KP_STALL_FORK_WAIT  KPB=R8,-		; Fork and wait
			    FKB=KPB$PS_UCB(R8)	;  using UCB fork block
	BRB     10$				; Try allocation again

20$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC5	#0,(SP),#0,R7,(R2)		; Zero initialize the block
	POPR	#^M<R0,R1,R2,R3,R4,R5>		; Restore registers
	MOVL	SPDT$PS_ATIO_FL(R4),-		; Set FLINK to whatever the
		QBUF$PS_FLINK(R2)		; queue header points to
	MOVL	R2,SPDT$PS_ATIO_FL(R4)		; Save current Queue Buffer ptr
;
; Fill in static Accept Target IO CCB fields.
;
	MOVW	R7,QBUF$IW_SIZE(R2)		; Save Queue Buffer size
	MOVB	#DYN$C_MISC,QBUF$IB_TYPE(R2)	; Set buffer type
	MOVB	#DYN$C_QBUF,QBUF$IB_SUBTYPE(R2)	; Set buffer subtype
	MOVAB	QBUF$PQ_CCB_ADDR(R2),R6		; VA of adapter visible section
	$SVA_TO_PA   SVA=R6			; PA of adapter visible section
	BITL	#SPDT$M_PFLG_MAPPING_REG,-	; Are we using map regs?
		SPDT$L_PORT_FLAGS(R4)
	BEQL	30$				; No, just use PA
;
; Is the Accept Target IO Queue Buffer within the direct-DMA window?
;
	EVAX_ADDQ #QBUF$C_ADPLEN,R0,R3		; Yes, just use PA
	EVAX_CMPULE R3,SPDT$IQ_DMA_SIZE(R4),R7	; Compare to DMA window size
	BLBS	R7,30$				; Branch if LE window size
;
; We have to map it.
;
	MAP_S0	SVA = R6,-			; Queue Buffer address (VA)
		NBYTES = #QBUF$C_ADPLEN,-	; Size to map
		CRCTX = QBUF$PS_CRCTX_PTR(R2),-	; CRCTX pointer reference
		KPB$ = SPDT$PS_KPB(R4),-	; KPB to stall on alloc fail
		DMA_ADDR = QBUF$PQ_PA_QBUF(R2)	; Where to put DMA-able addr
	BRB	40$				; Join common code
;
; No mapping registers needed, just use PA (+ DMA base).
;
30$:	ADDL2	SPDT$L_DMA_BASE(R4),R0		; Add in DMA window base
	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_STQ R0,QBUF$PQ_PA_QBUF(R2)		; Save physical address

40$:	MOVL	R2,R7				; Working copy
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_ACCEPT_TARGET_IO_SIZE,-	; Setup CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_ACCEPT_TARGET_IO,-	; Set function code to
		QBUF$IB_FUNC_CODE(R7)		;  Accept Target IO
	MOVB	SPDT$IS_SCSI_ID_NUM(R4),-	; Setup target id
		QBUF$IB_TARGET_ID(R7)
;
; Setup autosense buffer pointer and size.
;
	TSTL	QBUF$PS_CRCTX_PTR(R7)		; Do we have map registers?
	BEQL	50$
	ADDL3	#QBUF$C_AUTOSENSE_OFFSET,-	; Yes, just add offset to
		QBUF$PQ_PA_QBUF(R7),R0		; DMA_ADDR.
	BRB	60$
50$:	MOVAB	QBUF$IB_AUTOSENSE_BUF(R7),R6	; Get autosense buffer pointer
	$SVA_TO_PA   SVA=R6			; Convert to PA
	ADDL2	SPDT$L_DMA_BASE(R4),R0		; Add in DMA window base
60$:	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_STQ  R0,QBUF$PS_SENSE_BUFF_PTR(R7)	; Save autosense PA in CCB
	MOVB	#QBUF$C_AUTOSENSE_BUF_SIZE,-	; Autosense buffer length
		QBUF$IB_SENSE_BUFF_SIZE(R7)
	MOVL	#ATIO_COMMAND_TIMEOUT,-		; Setup command timeout
		QBUF$IS_TIMEOUT_VALUE(R7)
;
; Setup the Private Data Area of Accept Target IO CCB.
;
	MOVB    #QBUF$C_ATIO_CDB_BUFF_SIZE,-	; Setup CDB size
		QBUF$IB_ATIO_CDB_LEN(R7)
	MOVB	QBUF$IB_SENSE_BUFF_SIZE(R7),-	; Autosense buffer length
		QBUF$IB_ATIO_SEN_LEN(R7)
	BUILD_BSD -				; Build a BSD for autosense
		BSD_ADDR   = QBUF$PS_ATIO_SEN_BSD(R7),-
		BUFFER_PA  = R0,-
		BYTE_COUNT = #QBUF$C_AUTOSENSE_BUF_SIZE

	MOVL	QBUF$IS_TIMEOUT_VALUE(R7),-	; Setup command timeout
		QBUF$IS_ATIO_CMD_TIMEOUT(R7)
;
; Get a Queue Carrier and link it to the Accept Target IO CCB.
;
	BSBW	GET_QUEUE_CARRIER		; Get a Queue Carrier
	TSTL	SPDT$IS_ATIO_LIST_TAIL(R4)	; Check if first Carrier
	BNEQ	65$				; Is this the first Carrier?
	MOVL	R2,SPDT$IS_ATIO_LIST_TAIL(R4)	; Yes, setup tail pointer
65$:	CLRQ	CAR$PQ_NEXT_PTR(R2)		; Clear old NEXT_PTR
	MOVL	R10,CAR$PQ_NEXT_PTR(R2)		; Link this Carrier in list

	MOVL	CAR$PQ_PA(R2),R10		; Save Queue Carrier PA
	MOVQ    QBUF$PQ_PA_QBUF(R7),-		; Link Q_Buffer physical addr
		CAR$PQ_QBUF_PTR(R2)		;  into Carrier	
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual address
	MOVL    R2,QBUF$PS_CARRIER(R7)		; Save Carrier's address
						;  in Queue Buffer
	EVAX_MB					; Make sure Q_Buf contents and
						;  ptr are visible to the
						;  adapter  before NEXT_PTR
						;  becomes valid
	EVAX_ADDQ  #1,CAR$PQ_NEXT_PTR(R2),-	; Make it a valid Carrier
		   CAR$PQ_NEXT_PTR(R2)
.Enable Flagging
	MOVB	QBUF$IB_FUNC_CODE(R7),-		; Fill in function code
		CAR$IB_FUNC_CODE(R2)
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  Flags
	MOVL	QBUF$IS_CONNECT_ID(R7),-	; Setup connect id
		CAR$IS_CONNECT_ID(R2)
	EVAX_MB					; Make sure addr are visible
	SOBGTR	R9,10$				; Loop for all CCBs
	MOVL	R2,SPDT$IS_ATIO_LIST_HEAD(R4)	; Setup head pointer

	RSB					; Return
        .DISABLE LSB                            ; SETUP_ACCEPT_CCB_LIST

	.PAGE
        .SBTTL  GET_SCDRP - Get an SCDRP
;
; This subroutine is called during (re)initialization to provide an
; SCDRP.  This is necessary because the initialization of the SCSI
; subsystem requires the use of an SCDRP but does not have one provieded
; by the class driver.  It is an IO performed by and for the port driver.
; The class driver does not know about it.
;
; All SCDRPs are QUAD aligned and do not cross a port page (8KB) boundary.
;
; Note that this routine assumes that it is running in a Kernel Process context.
;
; INPUT:
;       R8 = KPB address to use for fork stalling
;
; OUTPUT:
;       R0 = Status
; If SS$_NORMAL status,
;       R2 = Address of the allocated SCDRP
;
        .ENABLE LSB
GET_SCDRP:
	.JSB_ENTRY	INPUT=<R8>,OUTPUT=<R0,R2>, -
			PRESERVE=<R1,R3,R4,R5,R6,R7,R8,R9>

10$:	MOVL    #SCDRP$K_LENGTH,R1 		; Load length first

        SUBL    #8,SP                           ; Allocate two longword in stack
        MOVL    SP,R2                           ; Store address of longword to receive
                                                ;  address of allocated block
        ADDL3   #4,R2,R7                        ; Store address of longword to receive
                                                ;  size of allocated block
        PUSHL   R7                              ; Arg 4 = addr to receive size
        PUSHL   R2                              ; Arg 3 = addr to receive addr of
                                                ;  allocated block
        PUSHL   #ALN_QUAD                   	; Arg 2 = alignment bit = 64 byte alignment
        PUSHL   R1                              ; Arg 1 = size of block requested
        CALLS   #4,EXE$ALONONPAGED_ALN          ; Allocate a block
        POPL    R2                              ; Get the addr of allocated block
        POPL    R7                              ; Get the allocated size
        BLBC    R0,30$                          ; Branch if failure to retry

	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save registers
        MOVC5   #0,(SP),#0,R7,(R2)              ; Zero initialize the block
	POPR	#^M<R0,R1,R2,R3,R4,R5>		; Restore registers
        MOVW    R7,SCDRP$W_SCDRPSIZE(R2)	; Set the allocated block size
	MOVB	#DYN$C_SCDRP,SCDRP$B_CD_TYPE(R2); Set type
        MOVZWL  #SS$_NORMAL,R0                  ; Set success status
        RSB                                     ; Return
30$:
        KP_STALL_FORK_WAIT  KPB=R8,-            ; Fork and wait
                            FKB=KPB$PS_UCB(R8)  ;  using UCB fork block
        BRB     10$                             ; Try allocation again
        .DISABLE LSB                            ; GET_SCDRP

	.PAGE
	.SBTTL	PK$INIT_STDT - Complete STDT Initialization
;
; This routine will perform any STDT initialization necessary for the target.
; This includes:
;	- Setting up for synchronous negotiation.
;
;	Inputs:
;		spdt(AP) = SPDT address
;		stdt(AP) = STDT address
;		FORK IPL
;		FORK lock held
;		Auto configuration thread
;		FORK thread
;
;	Outputs:
;		R0 = Status
;		     SS$_NORMAL = Initialization successful.
;		FORK IPL
;		FORK lock held
;		Auto configuration thread
;		FORK thread
;
	.ENABLE	LSB

	$ARG_DEF	<-
	spdt,		-			; SPDT address
	stdt		>			; STDT address
PK$INIT_STDT::
	.CALL_ENTRY	INPUT=<>,OUTPUT=<R0>,SCRATCH=<R0,R1>,PRESERVE=<R10>

	MOVL	spdt(AP),R4			; Get the SPDT address
	MOVL	stdt(AP),R10			; Get the STDT address
;
; Take out the lock for data structure synchronization.
;
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
;
; Update the requested synchronization type.
;
	PUSHL	R10				; STDT address.
	PUSHL	R4				; SPDT address.
	CALLS	#2,PK$NEGOTIATE_SYNCH		; Attempt to negotiate
						;  synchronous operation
;
; Release the data structure synchronization.
;
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO

	MOVL	#SS$_NORMAL,R0
	RET					; Return
	.DISABLE LSB				; PK$INIT_STDT

.PAGE
	.SBTTL	PK$CONNECTION_CHAR_SET - Set connection characteristics
;
; This routine is called from the SC$CONNECTION_CHAR_SET routine in SCSICOMMON
; module to perform any port specific sections of the SET_CONNECTION_CHAR
; function.  
;
;	SCDRP thread
;	FORK IPL
;	Fork lock may be held
;
; OUTPUT:
;	R0 = SS$_NORMAL
;
	.ENABLE	LSB
;
; Define the offsets to the parameters pointed to by AP.
;
	$ARG_DEF	<-
	spdt,		-			; SPDT address
	scdrp,		-			; SCDRP address
	scdt,		-			; SCDT address
	stdt,		-			; STDT address
	spi_cc		>			; SPI Connection Characteristics

PK$CONNECTION_CHAR_SET::

        .CALL_ENTRY     INPUT=<>, -
                        SCRATCH=<>, -
                        OUTPUT=<R0>, -
                        PRESERVE=<R10>

        MOVL    stdt(AP),R10                    ; Get STDT address

        BBC     #STDT$V_DFLG_SUPPRESS_SDTR,-    ; Branch if the caller is not trying
                STDT$IS_DIPL_FLAGS(R10),10$     ; to suppress SDTR negotiation
        BICL2   #STDT$M_DFLG_RENEGOTIATE_SYNC,- ; Clear the bit which tells the port driver
                STDT$IS_DIPL_FLAGS(R10)         ; send an SDTR message at the start of a command
        BRW     20$                             ; Take common success path exit

10$:    BBCC    #STDT$V_DFLG_CLASS_REQ_SDTR,-   ; Branch if the caller is not trying to initiate SDTR,
                STDT$IS_DIPL_FLAGS(R10),20$     ; but go ahead and clear the bit anyway
        PUSHL   stdt(AP)                        ; Pass STDT address
        PUSHL   spdt(AP)                        ; Pass SPDT address
        CALLS   #2, PK$NEGOTIATE_SYNCH          ; Attempt to negotiate synchronous operation.

20$:    MOVZWL  #SS$_NORMAL,R0                  ; Set success status
        RET                                     ; Return

	.DISABLE LSB				; PK$CONNECTION_CHAR_SET

.PAGE
	.SBTTL	PK$RESET_SCSI_BUS - Reset the SCSI Bus
;
; This routine is called to RESET the SCSI bus from the SC$RESET routine in
; the SCSICOMMON module at the request of the class driver.
;
; The SCSI bus reset command causes the indicated SCSI channel to return
; all outstanding I/O with bus reset status and then enter the disabled state.
; While in the disabled state, all new I/O is returned with bus reset status.
; The host is required to issue a Set Channel State command to re-enable
; the channel.
;
; Once the channel is back in the ENABLED state, the port driver will log
; the fact that the bus was reset.
;
; NOTE:  Due to the fact that the current class driver doesn't issue a
;	 DEVICE RESET and there is no defined class-port driver interface
;	 for the DEVICE RESET function, the DEVICE RESET function is not
;	 implemented at this time.
;
; INPUT:
;	spdt(AP)  = SPDT address
;	scdrp(AP) = SCDRP address
;		    SCDRP$L_CDT = SCDT address
;		    SCDRP$PS_KPB - Address of associated Kernel Process Block
;	FORK IPL
;	FORK lock held
;	Fork thread
;
; OUTPUT:
;	None
;
	.ENABLE LSB
;
; Define the offsets to the parameters pointed to by AP.
;
	$ARG_DEF	<-
	spdt,		-			; SPDT address
	scdrp		>			; SCDRP address

PK$RESET_SCSI_BUS::
	.CALL_ENTRY	PRESERVE=<R1,R2,R3,R4,R5,R6,R7,R8,R9>

	MOVL	spdt(AP), R4			; Get SPDT address
	MOVL	scdrp(AP),R5			; Get SCDRP address
	MOVL	SCDRP$PS_KPB(R5),R8		; Get the KPB address to pass
	MOVL	R4,KPB$PS_SCSI_PTR1(R8)		; Save SPDT address
;
; If this is unit initialization or an adapter error has occurred, then we
; must first (re)initialize the adapter.
;
	BBCC	#SPDT$V_INIT_ADAPTER,-		; Do we need to init adapter?
		SPDT$IS_PORTSTS(R4),10$
	BSBW	INIT_ADAPTER			; Yes, initialize the adapter
;
; Reset the SCSI bus by sending a Reset SCSI Bus message to the adapter.
; Get a Queue Buffer and Carrier from the Adapter Driver Free Queue (ADFQ).
; Set up the Queue Buffer (CCB) for the Reset SCSI Bus command.
;
10$:	MOVL	SPDT$PS_AB(R4),R6		; Get Adapter Block address
	MOVL	AB$PQ_ADFQ_HEAD(R6),R2		; Get Carrier at ADFQ head
	MOVL	CAR$PQ_QBUF_PTR(R2),R7		; Get Q_Buffer virtual address
	EVAX_MB                                 ; Make sure Queue Buffer
						;  contents are visible
	MOVL	CAR$PQ_NEXT_PTR(R2),-		; Update ADFQ head pointer
		AB$PQ_ADFQ_HEAD(R6)		;  with next Carrier address
	EVAX_MB                                 ; Make sure Queue Buffer
						;  contents are visible
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_COMMON_HDR_SIZE,-	; CCB length
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_RESET_SCSI_BUS,-	; Setup function code
		QBUF$IB_FUNC_CODE(R7)
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM status
	MOVB	SPDT$IB_PI_PATH_ID(R4),-	; Setup path id
		QBUF$IB_PATH_ID(R7)
	BISB	#QBUF$M_NO_DATA_TX,-		; Direction is no data
		QBUF$IB_CAM_FLAGS_1(R7)
	BISB	#QBUF$M_SIM_Q_FREEZE_DIS,-	; Set SIM queue freeze disable
		QBUF$IB_CAM_FLAGS_2(R7)
;
; The Reset SCSI Bus CCB is set up. Allocate a Carrier, setup Carrier fields.
;
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  Flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
;
; Send the command to the SIMport adapter.
;
	BSBW	INSERT_DACQ			; Insert command on DACQ
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR=SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL=SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE=NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
;
; Log an error if necessary that a bus reset occurred.
;
	BBCC	#SPDT$V_LOG_ERROR,-		; Do we need to log this?
		SPDT$IS_PORTSTS(R4),20$
	LOG_ERROR -				; Yes, log the event that the
		TYPE = BUS_RESET_ISSUED,-	;  port issued a bus reset
		CDT = #0			; No associated CDT available

20$:	MOVZWL	#SS$_NORMAL,R0			; Set sucess status
	RET					; Return
	.DISABLE LSB				; PK$RESET_SCSI_BUS

.PAGE
	.SBTTL	PK$ABORT_COMMAND - Port specific abort command routine
;
; This routine is called to ABORT the outstanding SCSI command on a given
; connection. The current version of the class-port driver interface
; assumes that there is only one outstanding command per target LUN.
; Therefore this routine will try to abort one command per call at this time.
;
; This routine first checks the outstanding commands in the SPDT to find
; the Q_Buffer with the matching SCDT. When it is found, an ABORT command
; Q_Buffer and an ABORT MARKER Q_Buffer are allocated. The ABORT Q_Buffer
; is queued to the DCCQ1 (higher priority command queue) and the ABORT MARKER
; Q_Buffer is queued to the DCCQ0.
;
; Note that this routine will return to the class driver immediately after
; sending the ABORT command. If the ABORT operation is successful, the aborted
; command is returned via the SEND_CMD interface with the SS$_ABORT return
; status.
;
; This routine assumes that the class driver has called this routine in Kernel
; Process context. If not, no abort attempt is made.
;
; NOTE:  Current VMS SCSI class drivers do not abort any commands. This routine
;	 is provided for any third-party class drivers that might want to abort
;	 commands.
;
; INPUT:
;	spdt(AP)   = SPDT address
;	scdrp(AP)  = SCDRP address
;		     SCDRP$PS_KPB - Address of associated Kernel Process Block
;	scdt(AP)   = SCDT address
;
; OUTPUT:
; 	R0 = SS$_NORMAL
;
	.ENABLE LSB
;
; Define the offsets to the parameters pointed to by AP
;
	$ARG_DEF <-
	spdt,	-				; SPDT address
	scdrp,	-				; SCDRP address
	scdt	>				; SCDT address

PK$ABORT_COMMAND::
	.CALL_ENTRY	OUTPUT=<R0>,SCRATCH=<R1>,-
			PRESERVE=<R2,R3,R4,R5,R6,R7,R8,R9>

	MOVL	spdt(AP),R4			; Get SPDT address
	MOVL	scdrp(AP),R5			; Get SCDRP address
	MOVL	scdt(AP),R3			; Get SCDT address
	MOVL	SCDRP$PS_KPB(R5),R8		; Is there a KPB?
	BEQL	200$				; Exit if not
	MOVAL	SPDT$PS_QCMDFL(R4),R2		; Get queue header for 
						;  outstanding commands 
	MOVL	R2,R6				; Get working copy

10$:	MOVL	(R6),R6				; Get next Q_Buffer in queue
	CMPL	R6,R2				; End of the queue?
	BEQL	200$				; Branch if yes
	CMPL	R3,QBUF$PS_SCDT(R6)		; Is this the command to abort?
	BNEQ	10$				; If not, try next command

	BSBW	GET_QUEUE_BUFFER		; Get a Q_Buffer
						; R2 = just allocated Q_Buffer
	MOVL    R2,R7				; Copy ptr to Q_Buffer
;;	MOVB    #QBUF$C_SNDPM,QBUF$IB_OPC(R7)	; Set Q_Buffer OPC = SNDPM
;;	MOVB	QBUF$IB_CHNL_IDX(R6),-		; Copy channel index from the
;;		QBUF$IB_CHNL_IDX(R7)		;  original command buffer
;;	MOVB	#QBUF$C_ABRTCMD,-		; Set SCSI command opcode to 
;;		 QBUF$IB_SCSI_OPC(R7)		;  ABORT COMMAND
.Disable Flagging				; Turn off information since 
						;  it's ALIGNED
;;	MOVQ	QBUF$IQ_XCT_ID(R6),-		; Copy the original command's
;;		 QBUF$IQ_XCT_ID(R7)		;  transaction ID field
.Enable Flagging
;;	BISW    #QBUF$M_R,QBUF$IW_FLAGS(R7)	; Response requested
	BSBW    GET_QUEUE_CARRIER		; Get a Carrier, R2 has addr
	MOVL    R2,QBUF$PS_CARRIER(R7)		; Save the new Carrier's 
						;  address in Q_Buf
	MOVL	R2,R9				; Save this Carrier's address
        BSBW    GET_QUEUE_BUFFER		; Get a Q_Buffer
						;  R2 = just allocated Q_Buffer
	MOVL	R2,R6				; Copy ptr to Q_Buffer
;;	MOVB    #QBUF$C_SNDPM,QBUF$IB_OPC(R6)	; Set Q_Buffer OPC = SNDPM
;;	MOVB    QBUF$IB_CHNL_IDX(R7),-		; Copy channel index from the
;;		QBUF$IB_CHNL_IDX(R6)		;  ABRTCMD Q_Buffer
;;	MOVB    #QBUF$C_MRKABRT,-		; Set SCSI command opcode to 
;;		 QBUF$IB_SCSI_OPC(R6)		;  MARK ABORT
.Disable Flagging                               ; Turn off information since 
						;  it's ALIGNED
;;	MOVQ    QBUF$IQ_XCT_ID(R7),-		; Copy the transaction ID from
;;		 QBUF$IQ_XCT_ID(R6)		;  the ABRTCMD Q_Buffer
.Enable Flagging
;;	BISW    #QBUF$M_R,QBUF$IW_FLAGS(R6)	; Response requested
	BSBW    GET_QUEUE_CARRIER		; Get a Carrier, R2 has addr
						;  on return
	MOVL    R2,QBUF$PS_CARRIER(R6)		; Save new Carrier's address
						;  in Q_Buf
;
; At this point, we have:
;	R2 = MRKABRT Carrier address
;	R6 = MRKABRT Q_Buffer address
;	R7 = ABRTCMD Q_Buffer address
;	R9 = ABRTCMD Carrier address
;
;;	MOVL    SPDT$PS_DCCQ1T(R4),R1           ; Get the DCCQ1 tail ptr (stopper addr)
.Disable Flagging                               ; Turn off information since it's ALIGNED
	MOVQ    QBUF$PQ_PA_QBUF(R7),-           ; Link Q_Buffer physical addr
                 CAR$PQ_QBUF_PTR(R1)            ;  into stopper Carrier

	MOVL    R7,CAR$PQ_QBUF_TOKEN(R1)        ; Copy Q_Buffer virtual addr
	EVAX_MB                                 ; Make sure Q_Buf contents and
						;  ptr are visible to the
						;  adapter before NEXT_PTR
						;  becomes valid
	EVAX_ADDQ  #1,CAR$PQ_PA(R9),-           ; Link in new stopper and set bit 0 to
                    CAR$PQ_NEXT_PTR(R1)         ;  make it a valid carrier
.Enable Flagging
	EVAX_MB                                 ; Make sure new stopper is visible to
						;  the adapter before the adapter accesses
						;  the command queue

	DEVICELOCK -                            ; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
;
; Now we need to write to the CQIB (Command Queue Insertion Block) and QIR
; register to notify the adapter about the DCCQ1 entry insertion of the
; ABRTCMD.
;
	MOVL    SPDT$PS_AB(R4),R3		; Get the Adapter Block address
	EVAX_LDQ  R0,CAR$PQ_PA(R9)		; Get the new stopper address
;;	EVAX_SRL  R0,#CQIB$V_SHIFT,R0		; Shift right to start with PA bit <5>
;;	EVAX_BIC  R0,#CQIB$M_SBZ,R0             ; Clear out upper bits to get PA <39:5>
;;	MOVL    AB$PS_AMP_VA(R3),R1		; Get Adapter Memory Page/CQIB address
;;	CRAM_IO SPDT$PS_QIR(R4)                 ; Signal DCCQ1 entry insertion
;;	MOVL    R9,SPDT$PS_DCCQ1T(R4)           ; Update tail link with new stopper VA
;
; Now set up the pointers for MRKABRT command
;
	MOVL    SPDT$PS_DACQ_T(R4),R1		; Get DACQ tail ptr (stopper addr)
.Disable Flagging                               ; Turn off information since it's ALIGNED
	MOVQ    QBUF$PQ_PA_QBUF(R6),-		; Link Q_Buffer physical addr
                 CAR$PQ_QBUF_PTR(R1)            ;  into stopper Carrier

	MOVL    R6,CAR$PQ_QBUF_TOKEN(R1)        ; Copy Q_Buffer virtual addr
	EVAX_MB                                 ; Make sure Q_Buf contents and ptr are
                                                ;  visible to the adapter before NEXT_PTR
                                                ;  becomes valid
	EVAX_ADDQ  #1,CAR$PQ_PA(R2),-           ; Link in new stopper and set bit 0 to
                    CAR$PQ_NEXT_PTR(R1)         ;  make it a valid carrier
.Enable Flagging
	EVAX_MB                                 ; Make sure new stopper is visible to
                                                ;  the adapter before the adapter accesses
                                                ;  the command queue

;;        CRAM_IO  SPDT$PS_GCQIR(R4)              ; Signal the port about DCCQ0 insertion

	MOVL    R2,SPDT$PS_DACQ_T(R4)           ; Update tail link with new stopper VA

	DEVICEUNLOCK -                          ; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
                NEWIPL   = (SP)+,-
                PRESERVE = NO	  

200$:	MOVZWL	#SS$_NORMAL,R0			; Set sucess status
	RET					; Return
	.DISABLE LSB				; PK$ABORT_COMMAND

.PAGE
	.SBTTL	PK$CMD_BUFFER_ALLOC - Command buffer allocate routine
; 
; This routine is called by the port driver from the routine
; SC$COMMAND_BUFFER_ALLOC (in SCSICOMMON) to allocate a command
; buffer for the current I/O request. First of all, a Q_Buffer must be
; allocated. This is done by calling the subroutine, GET_QUEUE_BUFFER. Once
; a Q_Buffer is allocated and initialized for this I/O request, the pointer
; to the SCSI command buffer area is returned to the class driver. Note that
; the SCSI command buffer area returned to the class driver resides in the
; area of the Q_Buffer which is not visible to the adapter. This is due to
; the fact that the class driver requires 8 bytes of overhead for SCSI status
; and command length fields in front of the SCSI command descriptor block.
;
; NOTE:  FUTURE SUPPORT -- The following function can be added when
;			   we support vendor-unique command size greater
;			   than 12 bytes.
;	If the requested command buffer size is greater than 20 bytes
;	(12 bytes of command + 8 bytes class driver overhead), this command
;	will be assumed to be a vendor unique command and hence the Q_Buffer
;	will be initialized to conform to the vendor unique command format.
;	Also a separate command buffer will be  allocated to hold the bigger
;	vendor unique command and will be pointed to by the Q_Buffer.
;
; For now, if the requested size is greater than 20 bytes, an error status
; of SS$_BADPARAM will be returned.
;
; INPUT:
;	spdt(AP) = Port SPDT address
;	scdrp(AP) = Class SCDRP address
;	buf_size(AP) = Requested size of command buffer
;	ret_param_ptr(AP) = Return address to store address of
;			    class driver-visible command buffer.
;	FORK IPL
;	FORK lock held
;	FORK thread
;	R1 = Size of command buffer
;	R4 = SPDT address
;	R5 = SCDRP address
;	     SCDRP$PS_KPB - Address of the associated Kernel Process Block
;
; OUTPUT:
;	R0 = Status
;	If SS$_NORMAL status:
;		@ret_param_ptr(AP) = Address of class driver-visible command
;				     buffer
;		scdrp(AP) = Class SCDRP address
;			    SCDRP$PS_QBUF - Address of allocated Queue Buffer 
;
	.ENABLE	LSB
;
; Define the offsets to the parameters pointed to by AP
;
	$ARG_DEF	<-
	spdt,		-			; Port SPDT address
	scdrp,		-			; Class SCDRP address
	buf_size,	-			; Requested command buffer size
	ret_param_ptr	>			; Return address to store
						;  address of class driver
						;  visible command buffer
PK$CMD_BUFFER_ALLOC::
	.CALL_ENTRY	OUTPUT=<R0>,PRESERVE=<R1,R2,R3,R4,R5,R8>

	MOVL	spdt(AP),R4			; Get SPDT address
	MOVL	scdrp(AP),R5			; Get SCDRP address
	MOVL	buf_size(AP),R1			; Get requested buffer size

	CMPL	R1,#PKS_NORM_CMDSIZE		; Is this normal SCSI command?
	BGTR	20$				; Branch if not
	MOVL	R1,SCDRP$IS_CMD_BCNT(R5)   	; Save byte count in SCDRP in
						;  case we're stalled
	MOVL	R1,SCDRP$L_CMD_BUF_LEN(R5) 	; Set both locations
	MOVL	SCDRP$PS_KPB(R5),R8		; Get KPB address
	BSBW	GET_QUEUE_BUFFER		; Allocate a Queue Buffer
	MOVL	R8,QBUF$PS_KPB(R2)		; Save KPB address in Q_Buffer
	MOVL	R5,QBUF$PS_SCDRP(R2)		; Use SCDRP address as the
						;  command transaction ID
	MOVL	R2,SCDRP$PS_QBUF(R5)		; Save address of Queue Buffer

	MOVAB	QBUF$IB_CMDBUF(R2),R2		; Get command buffer address
	MOVL	R2,@ret_param_ptr(AP)		; Return class driver-visible 
						;  command buffer address
	MOVZWL	#SS$_NORMAL,R0			; Set success status
10$:	RET					; Return

20$:	MOVZWL	#SS$_BADPARAM,R0		; Set error status
	BRB	10$				; Return
	.DISABLE LSB				; PK$CMD_BUFFER_ALLOC

.PAGE
	.SBTTL	PK$CMD_BUFFER_DEALLOC - Command buffer deallocate routine
;
; This routine deallocates the Queue Buffer associated with the SCSI command
; buffer being returned by the class driver.
;
; NOTE:  FUTURE SUPPORT -- The following function can be added when
;                          we support vendor-unique command size greater
;                          than 12 bytes.
;	If the command buffer being returned was for a vendor-unique command
;	larger than 12 bytes, deallocate the vendor unique command descriptor
;	block that was previously allocated, before deallocating the Q_Buffer.
;
; INPUT:
;	spdt(AP) = SPDT address
;	scdrp(AP) = Class SCDRP address
;
; OUTPUT:
;	R0 = Status
;
	.ENABLE LSB
;
; Define the offsets to the parameters pointed to by AP
;
	$ARG_DEF	<-
	spdt,		-			; Port SPDT address
	scdrp,		>			; Class SCDRP address

PK$CMD_BUFFER_DEALLOC::
	.CALL_ENTRY	OUTPUT=<R0>,PRESERVE=<R3,R4,R5>

	MOVL	spdt(AP),R4			; Get SPDT address
	MOVL	scdrp(AP),R5			; Get SCDRP address

	MOVL	SCDRP$PS_QBUF(R5),R0		; Queue Buffer to deallocate
	BSBW	RET_QUEUE_BUFFER		; Return the Queue Buffer

	MOVZWL	#SS$_NORMAL,R0			; Set to success status
	RET					; Return
	.DISABLE LSB				; PK$CMD_BUFFER_DEALLOC

.PAGE
	.SBTTL	PKS$MAP_BUFFER - Map Buffer Routine
;
; This routine serves the same purpose as the SC$MAP_BUFFER routine in
; SCSICOMMON module. The routine will check the user data buffer and
; allocate the necessary resources to represent the data buffer to the
; channel. Resources include:
;
; 1.) Allocating and setting up a pad block if required.
; 2.) Allocating and setting up a Buffer Segment Map (BSM) if required.
; 3.) Setting up one or two Buffer Segment Descriptors (BSDs) in the Queue
;     Buffer if required.
;
; A pad block will be allocated if required.
;
; If the data buffer spans more than two physical port pages, A 32-byte
; aligned Buffer Segment Map (BSM) will be allocated to point to each page
; including the beginning page and the end page which might not be a full
; page of the data buffer in physical address format.  
;
; If the data buffer only spans two physical port pages, the Buffer Segment
; Descriptors (BSDs) in the Queue Buffer can be used instead of a Buffer
; Segment Map (BSM) to point to these two pages.
;
; If there is no data to be transferred, this routine just returns SS$_NORMAL.
;
; INPUT:
; 	spdt(AP)  = SPDT address
;	scdrp(AP) = SCDRP address
;		    SCDRP$L_BCNT - Transfer byte count
;		    SCDRP$L_BOFF - Byte offset in page
;		    SCDRP$L_SVAPTE - System Virtual Address of the first
;				     P0 Page Table Entry
;		    SCDRP$L_SCSI_FLAGS - Flags used for mapping and unmapping
;					 functions
;				         SCDRP$V_FLAG_S0BUF - If set then data
;							  buffer is in S0 space
;		    SCDRP$L_SVA_USER - S0 buffer if SCDRP$V_FLAG_S0BUF is set
;		    SCDRP$PS_QBUF - Address of the Q_Buffer allocated
;		    SCDRP$PS_KPB - Address of the associated KPB
;
; OUTPUT:
;	R0 = Status
;	If SCDRP$L_PAD_BCNT <> 0 or # of 8 KB segments greater than 2,
;		QBUF$PS_SGMAP - Address of the scatter-gather map
;
	.ENABLE LSB
;
; Define the offsets to the parameters pointed to by AP.
;
	$ARG_DEF	<-
	spdt,		-			; SPDT address
	scdrp		>			; SCDRP address

PKS$MAP_BUFFER::
	.CALL_ENTRY	OUTPUT=<R0>,SCRATCH=<R1>,-
			PRESERVE=<R2,R3,R4,R5,R6,R7,R8>

	MOVL	spdt(AP),R4			; Get SPDT address
	MOVL	scdrp(AP),R5			; Get SCDRP address

	TSTL    SCDRP$L_BCNT(R5)		; Is there a data buffer?
	BEQL	100$				; No, then just return success
;
; Get the SVAPTE that maps the data buffer.
;
ASSUME  SCDRP$V_FLAG_S0BUF EQ 0			; S0 bit must be bit zero of
						;  SCSI_FLAGS
	BLBC    SCDRP$L_SCSI_FLAGS(R5),10$	; Is this an S0 data buffer?
	MOVL	SCDRP$L_SVA_USER(R5),R2		; Yes, get S0 virtual address
	EVAX_SLL  R2,#<64-VA$V_SYSTEM>,R0	; Clear space select bit
	EVAX_SRL  R0,MMG$GQ_64SYS_SHIFT,R0	; Get virtual page number
	MOVAQ   @MMG$GL_SPTBASE[R0],R7		; Get SVAPTE that maps VA
	BRB	20$				; Join common code
10$:	MOVL	SCDRP$L_SVAPTE(R5),R7		; Get SVAPTE of P0 data buffer
;
; Allocate a pad block for this transfer if required.
;
20$:	MOVL    SCDRP$PS_QBUF(R5),R6		; Get Queue Buffer address
	TSTL	SCDRP$L_PAD_BCNT(R5)		; Is the pad count 0?
	BEQL	40$				; Yes, don't need pad block
	TSTL	QBUF$PS_PADBLK(R6)		; Do we already had a Pad Block?
	BNEQ	40$				; Yes, must have been call by _MR
	INCW	QBUF$IW_NUMSEG(R6)		; Pad block counts as a segment
30$:	ADDL3	#PAD$C_HEADER,-			; # of bytes to allocate
		SCDRP$L_PAD_BCNT(R5),R1
	SUBL	#4,SP				; Allocate a longword on stack
	PUSHL	SP				; Arg 4 = addr to receive size
	PUSHAB	QBUF$PS_PADBLK(R6)		; Arg 3 = addr to receive addr
						;  of allocated pad block
	PUSHL	#ALN_LONG			; Arg 2 = longword alignment 
	PUSHL	R1				; Arg 1 = requested block size
	CALLS	#4,EXE$ALONONPAGED_ALN		; Allocate a pad block
	POPL	R1				; Get the allocated size
	.BRANCH_LIKELY
	BLBS	R0,35$				; Did we get a block?
	MOVL    SCDRP$PS_KPB(R5),R0		; No, get KPB address
	KP_STALL_FORK_WAIT  KPB=R0,-		; Fork and wait
			    FKB=KPB$PS_UCB(R0)	;  using class driver UCB
	BRB	30$				; Try allocation again
35$:	MOVL	QBUF$PS_PADBLK(R6),R2		; Get the address of pad block
	PUSHR	#^M<R1,R2,R4,R5>		; Save registers
	MOVC5	#0,(SP),#0,R1,(R2)		; Zero initialize the block
	POPR	#^M<R1,R2,R4,R5>		; Restore registers
	MOVW	R1,PAD$IW_SIZE(R2)		; Set the allocated block size
	MOVB	#DYN$C_MISC,PAD$IB_TYPE(R2)	; Set type
	MOVB    #DYN$C_PADBLK,PAD$IB_SUBTYPE(R2); Set subtype
;
; Setup data transfer size in CCB.
;
40$:	ADDL3	SCDRP$L_BCNT(R5),-		; Calculate and store 
		SCDRP$L_PAD_BCNT(R5),-		;  data transfer length
		QBUF$IS_DATA_TRAN_SIZE(R6)	;  in Queue Buffer
;
; Calculate the number of segments we need to set up the pointers (BSDs) for
; and the number of current system's PTEs to deal with. 
;
;;	ADDL3	SCDRP$L_BCNT(R5),-		; Get transfer byte count
;;		SCDRP$L_BOFF(R5),R8		; Add byte offset in page
;;	$BYTES_TO_PAGES-			; Compute segment count
;;		SOURCE_BYTCNT = R8,-
;;		DEST_PAGCNT   = R3,-
;;		ROUNDUP       = YES
;;	ADDW	R3,QBUF$IW_NUMSEG(R6)		; Add to data segment count

	MOVL	SCDRP$L_BCNT(R5),R3		; Get transfer byte count
	MOVL	SCDRP$L_BOFF(R5),R1		; Get byte offset in page
	ADDL3	R3,R1,R8			; Add BCNT and BOFF
	ADDL	MMG$GL_BWP_MASK,R8		; Calculate highest relative
						;  byte and round
	ASHL	MMG$GL_VA_TO_VPN,R8,R8		; Calculate the number of PTEs
	BICL	#^C<PKS$M_BYTE_IN_PAGE>,R1	; Clear upper bits for offsets
						;  greater than PAGE_SIZE
	MOVAB	PKS$M_BYTE_IN_PAGE(R3)[R1],R3	; Calculate highest relative
						;  byte and round
	ASHL	#-PKS$S_BYTE_IN_PG,R3,R3	; Number of PAGE_SIZE segments
	ADDW	R3,QBUF$IW_NUMSEG(R6)		; Add to data segment count
;
; Now setup the mapping pointers for the data buffer. If there are more than
; two segments, then we must use a Buffer Segment Map (BSM). If two or less
; segments are required, then we can use the Buffer Segment Descriptors (BSDs)
; in the CCB.
;
; Note:
; The Turbochannel requires all transfers to start on a longword boundary.
; Determine if the data buffer is unaligned. If so, we must move the data
; in the first longword to a temporary aligned buffer so that the data starts
; on a longword boundary. If this is a write function and the data does not
; end on a longword boundary, then we must also re-map the data in the last
; longword as well.
;
; We must check for unaligned buffers here because the mapping for unaligned
; buffers requires one or two additional segments (QBUF$IW_NUMSEG).
;
; If there is any leading or trailing unalignment, then a BSM will be used
; (regardless of the number of BSDs required).
;
; At this point:
;	R4 = SPDT address 
;	R5 = SCDRP address 
;	R6 = Queue Buffer address 
;	R7 = SVAPTE of S0 or P0 data buffer
;	QBUF$IW_NUMSEG(R6) = Number of PAGE_SIZE data segments
;
	BITL	#LW_ALIGNED,SCDRP$L_BOFF(R5)	; Check leading alignment
	BNEQ	45$				; Is it longword aligned?
	CMPL    SCDRP$L_BCNT(R5),#2		; Yes, check transfer size
	BLEQ	45$				; Is it a small transfer?
	BBC	#IRP$V_FUNC,-			; No, are we reading from the
		SCDRP$IS_STS(R5),42$		;  device?
	ADDL3	SCDRP$L_BOFF(R5),-		; Yes, point to last byte
		SCDRP$L_BCNT(R5),R0
	BITL	#LW_ALIGNED,R0			; Check trailing alignment
	BNEQ	45$				; Is it longword aligned?
42$:	CMPW	QBUF$IW_NUMSEG(R6),#2		; Yes, get the segment count
	BLEQ	50$				; Is it a small transfer?
45$:	BSBW	SETUP_BSM			; No, set up a BSM
	BRB	100$				; Return
;
; If we come here, then the following conditions exist:
; - There is no leading or trailing unalignment.
; - The user buffer can be mapped by one or two Buffer Segment Descriptors.
; - We will setup Buffer Segment Descriptors located in the CCB.
;
50$:	TSTL	SCDRP$L_PAD_BCNT(R5)		; If the pad count is not 0
	BNEQ	70$				;  then we have a pad block
;
; No pad block is required. The data buffer spans one or two pages.
;
	CMPW	QBUF$IW_NUMSEG(R6),#2		; Check number of segments
	BEQL	55$				; Is there another segment?
	MOVL	SCDRP$L_BCNT(R5),R3		; No, get transfer byte count
	BRW	57$				; Setup the BSD
55$:	SUBL3	SCDRP$L_BOFF(R5),-		; Number of bytes transferred
		G^MMG$GL_PAGE_SIZE,R3		;  by first BSD
57$:	BUILD_BSD -				; Build BSD for first segment
		BSD_ADDR = QBUF$PS_DATA_BSD1(R6),-
		BUFFER_SVAPTE = R7,-
		BYTE_OFFSET = SCDRP$L_BOFF(R5),-
		BYTE_COUNT = R3
	CMPW	QBUF$IW_NUMSEG(R6),#2		; Check number of segments
	BNEQ	100$				; Is there another segment?
	ADDL	#PTE$C_BYTES_PER_PTE,R7		; Yes, point to second PTE 
	SUBL3	SCDRP$L_BOFF(R5),-		; Get number of bytes transferred
		G^MMG$GL_PAGE_SIZE,R3		;  by first BSD
	SUBL3	R3,SCDRP$L_BCNT(R5),R3		; Get number of bytes transferred
						;  by second BSD
	BUILD_BSD -				; Build BSD for second segment
		BSD_ADDR = QBUF$PS_DATA_BSD2(R6),-
		BUFFER_SVAPTE = R7,-
		BYTE_OFFSET = #0,-
		BYTE_COUNT = R3
	BRW	100$				; Return
;
; A pad block is required. The data buffer spans one page. The pad block
; is pointed to by the second BSD. (Always need 2 BSDs in this case.)
; First setup BSD for the data buffer.
;
70$:	BUILD_BSD -				; Build a BSD for data segment
		BSD_ADDR = QBUF$PS_DATA_BSD1(R6),-
		BUFFER_SVAPTE = R7,-
		BYTE_OFFSET = SCDRP$L_BOFF(R5),-
		BYTE_COUNT = SCDRP$L_BCNT(R5)
;
; Now setup the BSD for the pad block.
;
	MOVL	QBUF$PS_PADBLK(R6),R1		; Get PAD block address
	ADDL2	#PAD$C_HEADER,R1		; Point to pad data
	BUILD_BSD -				; Build a BSD for the pad block
		BSD_ADDR   = QBUF$PS_DATA_BSD2(R6),-
		BUFFER_SVA = R1,-
		BYTE_COUNT = SCDRP$L_PAD_BCNT(R5)

100$:	MOVZWL	#SS$_NORMAL,R0			; Set success status
	RET					; Return
	.DISABLE LSB				; PKS$MAP_BUFFER

.PAGE
	.SBTTL	SETUP_BSM - Set up Buffer Segment Map
;
; This subroutine is called from the PKS$MAP_BUFFER routine when
; we need to use type 1 data pointers in a Buffer Segment Map to
; pass down the data buffer to the SIMport channel.
;
; INPUT:
;	R4 = SPDT address
;	R5 = SCDRP address
;	     SCDRP$L_BCNT - Transfer byte count
;	     SCDRP$L_PAD_BCNT - Number of bytes required to pad to end of
;				block
;	     SCDRP$L_BOFF - Byte offset in page
;	     SCDRP$PS_KPB - Address of the associated Kernel Process Block
;	R6 = Queue Buffer address
;	     QBUF$IW_NUMSEG - Number of 8KB data segments
;	R7 = SVAPTE of S0 or P0 data buffer
;	R8 = Number of PTEs that maps the data buffer
;
; OUTPUT:
;	R6 = Queue Buffer address
;	     QBUF$PS_BSM_PTR - Address of Buffer Segment Map
;	     QBUF$PS_DATA_BSD1 - Has type 1 data pointer which points to
;				 the Buffer Segment map
;--
	.ENABLE LSB
SETUP_BSM:
        .JSB_ENTRY      INPUT=<R4,R5,R6,R7,R8>, -
                        SCRATCH=<R0,R1,R2>, PRESERVE=<R3,R4,R5,R9,R10,R11>
;
; Allocate a hexword aligned block for a BSM.
;
10$:	MOVL	#BSM$C_LENGTH,R1		; Size of BSM
	SUBL    #4,SP				; Allocate a longword in stack
	PUSHL   SP				; Arg 4 = addr to receive size
	PUSHAB	QBUF$PS_BSM_PTR(R6)		; Arg 3 = addr to receive addr
						;  of allocated BSM
	PUSHL	#ALN_HEXWORD			; Arg 2 = hexword alignment
	PUSHL	R1				; Arg 1 = block size requested
	CALLS	#4,EXE$ALONONPAGED_ALN		; Allocate a BSM
	POPL    R1				; Get the allocated size
	BLBS	R0,12$				; Branch if success		

	MOVL    SCDRP$PS_KPB(R5),R0		; Get KPB address
	KP_STALL_FORK_WAIT  KPB=R0,-		; Fork and wait
		     FKB=KPB$PS_UCB(R0)		;  using class driver UCB
	BRB	10$				; Try allocation again
;
; Zero the BSM block and fill in static fields.
;
12$:	MOVL	QBUF$PS_BSM_PTR(R6),R2		; Get addr of BSM
	PUSHR	#^M<R1,R2,R3,R4,R5>		; Save registers
	MOVC5	#0,(SP),#0,R1,(R2)		; Zero initialize the block
	POPR    #^M<R1,R2,R3,R4,R5>	        ; Restore registers
	MOVW    R1,BSM$IW_SIZE(R2)    		; Set the allocated block size
	MOVB    #DYN$C_MISC,BSM$IB_TYPE(R2)	; Set type
	MOVB    #DYN$C_SGMAP,BSM$IB_SUBTYPE(R2)	; Set subtype
	ADDL3	SCDRP$L_BCNT(R5),-		; Total number of bytes plus
		SCDRP$L_PAD_BCNT(R5),-		;  PAD bytes equals total count
		BSM$IS_TOTAL_COUNT(R2)		;  mapped by this BSM
;
; Put physical address of BSM in the BSM. Allocate and use map registers
; if needed.
;
	$SVA_TO_PA	SVA = R2		; Get physical address
	BBC	#SPDT$V_PFLG_MAPPING_REG,-	; Are we using map registers?
		SPDT$L_PORT_FLAGS(R4),18$
	EVAX_ADDQ #BSM$C_LENGTH,R0,R3		; Yes, point to last byte in BSM
	EVAX_CMPULE R3,SPDT$IQ_DMA_SIZE(R4),R11	; Compare to DMA window size
	BLBS	R11,18$				; Branch if LE window size
;
; The BSM is above the direct DMA window so we have to map it. We can use
; MAP_S0, because we are executing in a KP, so we can stall if we don't get
; the registers.
;
	MAP_S0	SVA = R2,-			; BSM address (VA)
		NBYTES = #BSM$C_LENGTH,-	; Size to map
		CRCTX = BSM$PS_CRCTX(R2),-	; CRCTX pointer reference
		KPB$ = SCDRP$PS_KPB(R5),-	; KPB to stall on alloc fail
		DMA_ADDR = BSM$PQ_PA(R2)	; DMA-able physical address
	BRB	19$				; Join common code
;
; Map registers not needed. Just use physical address + DMA base.
;
18$:	ADDL2	SPDT$L_DMA_BASE(R4),R0		; Add in DMA window base
	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_STQ R0,BSM$PQ_PA(R2)		; Store BSM PA in BSM
;
; Build a BSD to point to this BSM. Put the BSD into the Private Data Area
; of the CCB (QBUF$PS_DATA_BSD1) and mark this BSD as a type 1 pointer.
;
19$:	EVAX_LDQ R9,BSM$PQ_PA(R2)		; PA of start of BSM
	EVAX_ADDQ #BSM$PS_NEXT_BSM_BSD,R9,R9	; PA of adapter visible section
	BUILD_BSD -				; Build a BSD for this BSM
		BSD_ADDR   = QBUF$PS_DATA_BSD1(R6),-
		BUFFER_PA  = R9,-
		BYTE_COUNT = BSM$IS_TOTAL_COUNT(R2),-
		TYPE       = TP$C_TYPE1
	MOVAB	BSM$PS_BSD0(R2),R11		; Point to first BSD entry
;
; At this point:
;	R2 = BSM address
;	R5 = SCDRP address
;	R6 = Queue Buffer address
;	R7 = SVAPTE of S0 or P0 data buffer
;	R8 = # of PTEs that maps the data buffer
;	R11= Pointer to first BSD entry in BSM
;
; Dispatch based on transfer size to build the BSM.
;
	CMPL    SCDRP$L_BCNT(R5),#2		; Check byte count
	BGTR	LARGE_TRANSFER			; Branch for large transfers
	BEQL	TWO_BYTES			;  and two byte transfers
;
; This is a one byte transfer.
;
ONE_BYTE:
	MOVL	SCDRP$L_BCNT(R5),-		; Setup lead data count
		BSM$IS_LEADING_COUNT(R2)
	EVAX_LDQ R10,BSM$PQ_PA(R2)		; PA of start of BSM
	EVAX_ADDQ #BSM$PS_LEADING_BUFF,R10,R10	; PA of aligned data buffer
	BUILD_BSD -				; Build a BSD for leading
		BSD_ADDR   = (R11),-		;  unaligned segment
		BUFFER_PA  = R10,-
		BYTE_COUNT = SCDRP$L_BCNT(R5)
	ADDL2	#BSD$C_LENGTH,R11		; Point to next BSM entry

	BBS	#IRP$V_FUNC,SCDRP$IS_STS(R5),95$ ; Are we writing to the device?
	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBC    SCDRP$L_SCSI_FLAGS(R5),21$	; Yes, is this an S0 buffer?
	MOVL	SCDRP$L_SVA_USER(R5),R1		; Yes, get S0 virtual address
	BRW	215$				; Join common code
21$:	JSB	MAP_BUFF			; Yes, map to user data buffer
						;  R1 = address of page with
						;  unaligned user data buffer
	MOVL	R1,R9				; Save page address
	ADDL	SCDRP$L_BOFF(R5),R1		; Point to unaligned data byte
215$:	MOVB	(R1),BSM$PS_LEADING_BUFF(R2)	; Align the byte in lead buffer
	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBS    SCDRP$L_SCSI_FLAGS(R5),95$	; Is this an S0 buffer?
	MOVL	R9,R1				; Restore page address
	JSB	UNMAP_BUFF			; No, unmap from user buffer
	BRW	95$				; Check for padding
;
; This is a two byte transfer.
;
TWO_BYTES:
	MOVL	G^MMG$GL_PAGE_SIZE,R9		; Get page size
	DECL	R9				;  minus 1
	CMPL	SCDRP$L_BOFF(R5),R9		; Check if bytes span a page
	BGEQ	24$				; Do the bytes span a page?
;
; If here, then the 2 bytes are on the same page.
;
	BBS	#IRP$V_FUNC,-			; No, are we writing to the
		SCDRP$IS_STS(R5),23$		;  device?
	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBC    SCDRP$L_SCSI_FLAGS(R5),224$	; Yes, is this an S0 buffer?
	MOVL	SCDRP$L_SVA_USER(R5),R1		; Yes, get S0 virtual address
	BRB	225$				; Join common code
224$:	JSB	MAP_BUFF			; Map to user P0 data buffer
						;  R1 = address of page with
						;  unaligned user data buffer
	MOVL	R1,R9				; Save page address
	ADDL	SCDRP$L_BOFF(R5),R1		; Point to unaligned data bytes
225$:	MOVB	(R1)+,BSM$PS_LEADING_BUFF(R2)	; Align 1st byte in lead buffer
	MOVB	(R1),BSM$PS_LEADING_BUFF+1(R2)	; Align 2nd byte in lead buffer
	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBS    SCDRP$L_SCSI_FLAGS(R5),23$	; Is this an S0 buffer?
	MOVL	R9,R1				; No, restore page address
	JSB	UNMAP_BUFF			; Unmap from user buffer

23$:	MOVL	SCDRP$L_BCNT(R5),-		; Setup lead data count in BSM
		BSM$IS_LEADING_COUNT(R2)
	EVAX_LDQ R10,BSM$PQ_PA(R2)		; PA of start of BSM
	EVAX_ADDQ #BSM$PS_LEADING_BUFF,R10,R10	; PA of aligned data buffer
	BUILD_BSD -				; Build a BSD for leading
		BSD_ADDR   = (R11),-		;  unaligned segment
		BUFFER_PA  = R10,-
		BYTE_COUNT = SCDRP$L_BCNT(R5)
	ADDL2	#BSD$C_LENGTH,R11		; Point to next BSM entry
	BRW	95$				; Check for padding
;
; If here, then the 2 bytes span a page boundary.
;
24$:	BBS	#IRP$V_FUNC,-			; Are we writing to the device?
		SCDRP$IS_STS(R5),26$
	BLBC    SCDRP$L_SCSI_FLAGS(R5),25$	; Yes, is this an S0 buffer?
	MOVL	SCDRP$L_SVA_USER(R5),R1		; Yes, get S0 virtual address
	BRB	255$				; Join common code
25$:	JSB	MAP_BUFF			; Map to user P0 data buffer
						;  R1 = address of page with
						;  1st unaligned user data byte
	MOVL	R1,R9				; Save page address
	ADDL	SCDRP$L_BOFF(R5),R1		; Point to unaligned data bytes
255$:	MOVB	(R1),BSM$PS_LEADING_BUFF(R2)	; Align 1st byte in lead buffer
	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBC    SCDRP$L_SCSI_FLAGS(R5),256$	; Is this an S0 buffer?
	INCB	R1				; Yes, point to next byte
	BRW	257$				; Join common code
256$:	MOVL	R9,R1				; Restore page address
	JSB	UNMAP_BUFF			; Unmap from user buffer

	ADDL2	#PTE$C_BYTES_PER_PTE,R7		; Point to next Page frame
	JSB	MAP_BUFF			; Map to P0 user data buffer
						;  R1 = address of page with
						;  2nd user data byte
	MOVL	R1,R9				; Save page address
257$:	MOVB	(R1),BSM$PS_TRAILING_BUFF(R2)	; Align 2nd byte in trail buffer
	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBS    SCDRP$L_SCSI_FLAGS(R5),26$	; Is this an S0 buffer?
	MOVL	R9,R1				; Restore page address
	JSB	UNMAP_BUFF			; No, unmap from P0 user buffer

26$:	MOVL	#1,BSM$IS_LEADING_COUNT(R2)	; Setup leading count
	MOVL	#1,BSM$IS_TRAILING_COUNT(R2)	; Setup trailing count
	EVAX_LDQ R10,BSM$PQ_PA(R2)		; PA of start of BSM
	EVAX_ADDQ #BSM$PS_LEADING_BUFF,R10,R10	; PA of aligned data buffer
	BUILD_BSD -				; Build a BSD for leading
		BSD_ADDR   = (R11),-		;  unaligned segment
		BUFFER_PA  = R10,-
		BYTE_COUNT = #1
	ADDL2	#BSD$C_LENGTH,R11		; Point to next BSM entry
	EVAX_LDQ R10,BSM$PQ_PA(R2)		; PA of start of BSM
	EVAX_ADDQ #BSM$PS_TRAILING_BUFF,R10,R10	; PA of aligned data buffer
	BUILD_BSD -				; Build a BSD for trailing
		BSD_ADDR   = (R11),-		;  unaligned segment
		BUFFER_PA  = R10,-
		BYTE_COUNT = #1
	ADDL2	#BSD$C_LENGTH,R11		; Point to next BSM entry
	BRW	95$				; Check for padding
;
; If here, then this I/O request is larger than 2 bytes.
;
; - If there is leading unalignment:
;   - Setup the unaligned lead count.
;   - Build a BSD to point to the aligned leading buffer.
;   - If the leading unaligned bytes are the last bytes on a page AND this is
;     a user P0 data buffer, then point R7 (SVAPTE) to the next PTE.
;   - If this I/O request is writing to a device, then copy the leading
;     unaligned data bytes from the user buffer to the aligned leading buffer
;     in the BSM.
; - Build BSDs for any aligned data.
; - If there is trailing unalignment AND this I/O request is reading from a
;   device, setup the trailing unaligned byte count and build a BSD to point
;   to the aligned trailing bytes buffer in the BSM.
;
; Note: If there is trailing unalignment AND this I/O request is writing to
;       a device, nothing has to be done to setup BSDs. The adapter can read
;       an odd number of aligned bytes from the user data buffer.
;
LARGE_TRANSFER:
	BITL	#LW_ALIGNED,SCDRP$L_BOFF(R5)	; Check leading alignment
	BEQL	28$				; Is it longword aligned?
	MOVL	SCDRP$L_BOFF(R5),R3		; No, get byte offset
	BICL2	#^C<LW_ALIGNED>,R3		; Clear upper bits to get
						;  offset into longword
	SUBL3	R3,#4,R3			; Get number of unaligned bytes
	MOVL	R3,BSM$IS_LEADING_COUNT(R2)	; Setup lead unalignment count
	MOVL	R3,R10				; Working copy of byte count
	EVAX_LDQ R9,BSM$PQ_PA(R2)		; PA of start of BSM
	EVAX_ADDQ #BSM$PS_LEADING_BUFF,R9,R9	; PA of aligned data buffer
	BUILD_BSD -				; Build a BSD for leading
		BSD_ADDR   = (R11),-		;  unaligned segment
		BUFFER_PA  = R9,-
		BYTE_COUNT = R10
	ADDL2	#BSD$C_LENGTH,R11		; Point to next BSM entry

	BBS	#IRP$V_FUNC,SCDRP$IS_STS(R5),28$ ; Is this a write function?
	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBC    SCDRP$L_SCSI_FLAGS(R5),274$	; Yes, is this an S0 buffer?
	MOVL	SCDRP$L_SVA_USER(R5),R1		; Yes, get S0 virtual address
	BRW	275$				; Join common code
274$:	JSB	MAP_BUFF			; Map to P0 user data buffer
	MOVL	R1,R9				; Save VA of first page
	ADDL	SCDRP$L_BOFF(R5),R1		; Point to first data byte
275$:	PUSHR   #^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC3   R3,(R1),BSM$PS_LEADING_BUFF(R2)	; Copy unaligned data bytes to
						;  LEADING_BUFF
	POPR    #^M<R0,R1,R2,R3,R4,R5>		; Restore registers
	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBS    SCDRP$L_SCSI_FLAGS(R5),276$	; Is this an S0 buffer?
	MOVL	R9,R1				; No, restore VA of first page
	JSB	UNMAP_BUFF			; Unmap from P0 user buffer
276$:	CMPL	BSM$IS_LEADING_COUNT(R2),-	; Unaligned bytes = byte count?
		SCDRP$L_BCNT(R5)
	BEQL	90$				; Branch if yes
;
; If here, then the following conditions exist:
; - Any leading longword unaligned data has been set up in the first BSD.
;   If this is a write to disk function, then any leading unaligned data
;   has been copied to LEADING_BUFF.
; - The remaining data is longword aligned (although it may end with
;   unaligned bytes.)
; - R7 (SVAPTE) is pointing to the correct page table entry.
;
; Before filling in BSDs for aligned data, check for transfers that have
; only leading unaligned and trailing unaligned data. If so, then just
; fill in the trailing unaligned data. Else, fill in the BSDs for any aligned
; data.
;
28$:	BBC	#IRP$V_FUNC,SCDRP$IS_STS(R5),29$ ; Is this a read function?
	ADDL3	SCDRP$L_BOFF(R5),-		; Yes, point to last data byte
		SCDRP$L_BCNT(R5),R3
	BICL	#^C<LW_ALIGNED>,R3		; Clear upper bits to get # of
						;  unaligned trailing bytes
	MOVL	R3,BSM$IS_TRAILING_COUNT(R2)	; Setup unaligned trailing count
	ADDL3	BSM$IS_LEADING_COUNT(R2),R3,R3	; Get total unaligned bytes
	CMPL	R3,SCDRP$L_BCNT(R5)		; Unaligned bytes = byte count?
	BEQL	90$				; Branch if yes
;
; We need to setup BSDs for any aligned data. The first page might not
; start on a page boundary so the first BSD requires special handling.
;
29$:	MOVZWL	QBUF$IW_NUMSEG(R6),R9		; Get number of BSDs required
	TSTL    SCDRP$L_PAD_BCNT(R5)		; Get PAD byte count
	BEQL    30$                     	; Is the pad count 0?
	DECL	R9				; No, subtract 1 from # segments

30$:	MOVL	SCDRP$L_BCNT(R5),R10		; Number of data bytes
	SUBL2	BSM$IS_LEADING_COUNT(R2),R10	;  minus leading unaligned bytes
	MOVL	SCDRP$L_BOFF(R5),R1		; Get byte offset in first page
	ADDL2	BSM$IS_LEADING_COUNT(R2),R1	;  plus leading unaligned bytes
	CMPL	R1,G^MMG$GL_PAGE_SIZE		; Check byte offset
	BLSS	31$				; Are we at the end of a page?
	CLRL	R1				; Yes, rollover to next page
	ADDL    #PTE$C_BYTES_PER_PTE,R7		; Point to next PTE	

31$:	BICL    #^C<PKS$M_BYTE_IN_PAGE>,R1	; Clear upper bits for offsets
						;  greater than 8 KB
	MOVL	G^MMG$GL_PAGE_SIZE,R0		; Get # of bytes in port page
	SUBL2	R1,R0				; Get # bytes in first segment
	CMPL	R0,R10				; Is size bigger than BCNT?
	BLEQ	35$				; Branch if not
	MOVL	R10,R0				; Set correct size if BCNT is
						;  smaller than port page size
						;  minus BOFF)
35$:	SUBL2	R0,R10				; Subtract from total byte count
	MOVL	R0,R3				; Setup byte count
	BUILD_BSD -				; Build a BSD for this segment
		BSD_ADDR = (R11),-
		BUFFER_SVAPTE = R7,-
		BYTE_OFFSET = R1,-
		BYTE_COUNT = R3
	ADDL	#BSD$C_LENGTH,R11		; Point to next BSM entry
	DECL	R9				; Account for this segment
	BEQL	90$				; Branch if no more data segment
;
; Loop for remaining aligned pages to map.
;
40$:	CMPL	R10,BSM$IS_TRAILING_COUNT(R2)	; Check remaining byte count
	BLEQ	90$				; Are just trailing bytes left?
	ADDL    #PTE$C_BYTES_PER_PTE,R7		; Point to next PTE	
	MOVL	G^MMG$GL_PAGE_SIZE,R3		; Get # of bytes in port page
	CMPL	R10,R3				; Check remaining byte count
	BGEQ	45$				; Greater or equal to pagesize?
	MOVL	R10,R3				; No, get remaining byte count
45$:	BUILD_BSD -				; Build a BSD for this segment
		BSD_ADDR = (R11),-
		BUFFER_SVAPTE = R7,-
		BYTE_OFFSET = #0,-
		BYTE_COUNT = R3
	ADDL	#BSD$C_LENGTH,R11		; Point to next BSM entry
	SUBL	R3,R10				; Subtract length from
						;  remaining data count
	SOBGTR	R9,40$				; Process next data segment
;
; Check for trailing unalignment.
;
90$:	ADDL3	SCDRP$L_BOFF(R5),-		; Point to last data byte
		SCDRP$L_BCNT(R5),R3
	BITL	#LW_ALIGNED,R3			; Check trailing alignment
	BEQL	95$				; Is it longword aligned?
	BBC	#IRP$V_FUNC,-			; No, are we reading from the
		SCDRP$IS_STS(R5),95$		;  device?
	BICL	#^C<LW_ALIGNED>,R3		; Yes, get # of unaligned bytes
	MOVL	R3,BSM$IS_TRAILING_COUNT(R2)	; Setup trail unalignment count
	EVAX_LDQ R10,BSM$PQ_PA(R2)		; PA of start of BSM
	EVAX_ADDQ #BSM$PS_TRAILING_BUFF,R10,R10	; PA of aligned data buffer
	BUILD_BSD -				; Build a BSD for trailing
		BSD_ADDR   = (R11),-		;  unaligned segment
		BUFFER_PA  = R10,-
		BYTE_COUNT = R3
;
; Since the unaligned trailing bytes require another BSD, we must subtract
; the unaligned trailing byte count from the byte count in the previous BSD.
; However, if this I/O request contains only leading and trailing bytes,
; (that is if LEAD_COUNT + TRAIL_COUNT = BCNT) then the leading count is
; already correct. No need to subtract.
;
; In addition, if the trailing bytes are the first bytes on a page, then
; no need to subtract either.
;
	ADDL3	BSM$IS_LEADING_COUNT(R2),-	; Get total unaligned bytes
		BSM$IS_TRAILING_COUNT(R2),R3
	CMPL	R3,SCDRP$L_BCNT(R5)		; Check against total byte count
	BEQL	94$				; Unaligned bytes = byte count?
93$:	ADDL3	SCDRP$L_BOFF(R5),-		; No, point to last byte
		SCDRP$L_BCNT(R5),R3
	SUBL2	BSM$IS_TRAILING_COUNT(R2),R3	; Make it longword aligned
	BITL	#^X1FFF,R3			; Check page alignment
	BEQL	94$				; Are we on a page boundary?
	SUBL2	#BSD$C_LENGTH,R11		; No, point to previous BSD
	EVAX_LDQ R10,(R11)			; Get previous BSD entry
	EVAX_SRL R10,#BSD$C_BYTE_COUNT_SHIFT,R10 ; Clear out PA
	SUBW2	BSM$IS_TRAILING_COUNT(R2),R10	; Decrement byte count
	EVAX_SLL R10,#BSD$C_BYTE_COUNT_SHIFT,R10 ; Shift byte count to last word
	EVAX_BIC  (R11),#BSD$M_BYTE_COUNT,(R11)	; Clear previous byte count
	EVAX_OR	 (R11),R10,(R11)		; Insert new byte count into BSD
	ADDL2	#BSD$C_LENGTH,R11		; Point back to our BSD entry
94$:	ADDL2	#BSD$C_LENGTH,R11		; Point to next BSD entry
;
; If we allocated a pad buffer, set up the next BSD in the BSM to point to
; the pad buffer.
;
95$:	TSTL	SCDRP$L_PAD_BCNT(R5)		; Check the pad count
	BEQL	100$				; Is the pad count 0?
	ADDL3	#PAD$C_HEADER,-			; No, point to pad block
		QBUF$PS_PADBLK(R6),R1
	BUILD_BSD -				; Build a BSD for pad buffer
		BSD_ADDR   = (R11),-
		BUFFER_SVA = R1,-
		BYTE_COUNT = SCDRP$L_PAD_BCNT(R5)
	ADDL2	#BSD$C_LENGTH,R11		; Point to next BSM entry
;
; Setup the number of segments (BSDs) in this BSM.
;
100$:	MOVAB	BSM$PS_BSD0(R2),R10		; Address of first BSD entry
	SUBL2	R10,R11				; Subtract from current BSD addr
	ASHL	#-3,R11,R11			; Divide by the size of a BSD
	MOVW	R11,BSM$IW_NUM_ENTRIES(R2)	; Setup number of BSDs in BSM
	RSB					; Return to PKS$MAP_BUFFER
	.DISABLE LSB				; SETUP_BSM

.PAGE
	.SBTTL	MAP_BUFF - Map User Date Buffer Routine
;
; This routine maps the user data buffer for performing unaligned data
; transfers.
; 
; INPUT:
;	R4 = SPDT address
;	R5 = SCDRP address
;	R7 = SVAPTE of user data buffer
;
; OUTPUT:
;	R1 = Address of page with longword that contains unaligned user data
;
	.ENABLE LSB
MAP_BUFF:
	.JSB_ENTRY	INPUT=<R4,R5,R7>, OUTPUT=<R1>, -
			PRESERVE=<R0,R3,R4,R5,R7>
	MOVL	SPDT$L_SPTE_SVAPTE(R4),R0
	EVAX_LDQ R3,(R7)			; Get user PTE
	BLBC	R3,32$				; If LBS,  then it's valid
	EVAX_SRL R3,#PTE$V_PFN,R3		;S FF; Else, shift PFN to low-order bits
	BRB	33$				;S FF; Join common new PTE builder code
32$:	CALL_PTETOPFN SAVE_R0R1=YES		;S FF; If PTE not valid, convert to PFN
33$:	EVAX_SLL R3,#PTE$V_PFN,R3		;S FF; Get PFN in the right place
	EVAX_OR	 R3,-				;S FF; Set software PTE bits
		#<PTE$M_VALID!PTE$C_KW!PTE$C_KOWN!PTE$M_ASM!PTE$M_WINDOW>,R3
	EVAX_STQ R3,(R0)			;S FF; Store the new PTE
	MOVL	SPDT$L_SPTE_BASE(R4),R1
	TBI_SINGLE R1				;S FF; Do TB invalidation

	RSB
	.DISABLE LSB				; MAP_BUFF

.PAGE
	.SBTTL	UNMAP_BUFF - Unmap User Data Buffer Routine
;
; This routine unmaps the user data buffer for performing unaligned data
; transfers.
; 
; INPUT:
;	R4 = SPDT address
;	R5 = SCDRP address
;
; OUTPUT:
;	R0 = Status
;
	.ENABLE LSB
UNMAP_BUFF:
	.JSB_ENTRY	INPUT=<R4,R5,R7>, OUTPUT=<R0>, -
			PRESERVE=<R1,R4,R5,R7>

	CLRL	@SPDT$L_SPTE_SVAPTE(R4)		; Clear the SPTE
	TBI_SINGLE R1				; Invalidate translation buffer

	RSB
	.DISABLE LSB				; UNMAP_BUFF

.PAGE
	.SBTTL	PKS$UNMAP_BUFFER - Unmap Buffer Routine
;
; This routine replaces the SC$UNMAP_BUFFER routine in the SCSICOMMON module.
; It checks the Queue Buffer private data area to see if a BSM or pad block
; was used for this I/O request. If so, the resources are deallocated.
; 
; INPUT:
;	spdt(AP) = SPDT address
;	scdrp(AP)= SCDRP address
;
; OUTPUT:
;	R0 = Status
;
	.ENABLE LSB
;
; Define the offsets to the parameters pointed to by AP.
;
	$ARG_DEF	<-
	spdt,		-			; SPDT address
	scdrp		>			; SCDRP address

PKS$UNMAP_BUFFER::
	.CALL_ENTRY	OUTPUT=<R0>,SCRATCH=<R1>,PRESERVE=<R4,R5,R6>

	MOVL	spdt(AP),R4			; Get SPDT address
	MOVL	scdrp(AP),R5			; Get SCDRP address

	MOVL	SCDRP$PS_QBUF(R5),R6		; Get Queue Buffer address

	MOVL	QBUF$PS_BSM_PTR(R6),R0		; Is there a BSM?
	BEQL	10$				; Branch if not
	MOVZWL	BSM$IW_SIZE(R0),R1		; Get BSM size
	JSB	EXE$DEANONPGDSIZ		; Deallocate BSM

10$:	MOVL	QBUF$PS_PADBLK(R6),R0		; Is there a pad buffer?
	BEQL	20$				; Branch if not
	MOVZWL  PAD$IW_SIZE(R0),R1		; Get pad block size
	JSB     EXE$DEANONPGDSIZ		; Deallocate pad buffer

20$:	MOVZWL  #SS$_NORMAL,R0			; Success
	RET					; Return
	.DISABLE LSB				; PKS$UNMAP_BUFFER


.PAGE
	.SBTTL	PKS$MAP_BUFFER_MR - Map Buffer Routine for Map Registers
;
; This routine serves the same purpose as the SC$MAP_BUFFER routine in
; SCSICOMMON module. The routine will check the user data buffer and
; allocate the necessary resources to represent the data buffer to the
; channel. Resources include:
;
; 1.) Allocating and setting up a pad block if required.
; 2.) Setting up one or two Buffer Segment Descriptors (BSDs) in the Queue
;     Buffer if required.
;
; A pad block will be allocated if required.
;
; Note: since current platforms that provide map registers are PCI-based,
; and since the KZPSA/PCI does byte-aligned I/O properly, the unalignment
; handling used in PKS$MAP_BUFFER isn't needed here.
;
; Note: since map registers support scatter-gather over non-contiguous
; physical pages, at most two BSD's will ever be needed, one for the user
; buffer and one for a possible pad block.  The Queue Buffer (QBUF) structure
; contains two BSD's.  This routine therefore never needs to allocate a BSM.
;
; If there is no data to be transferred, this routine just returns SS$_NORMAL.
;
; INPUT:
; 	SPDT(AP)  = SPDT address
;	SCDRP(AP) = SCDRP address
;		    SCDRP$L_BCNT - Transfer byte count
;		    SCDRP$L_BOFF - Byte offset in page
;		    SCDRP$L_SVAPTE - System Virtual Address of the first
;				     P0 Page Table Entry
;		    SCDRP$L_SCSI_FLAGS - Flags used for mapping and unmapping
;					 functions
;				         SCDRP$V_FLAG_S0BUF - If set then data
;							  buffer is in S0 space
;		    SCDRP$L_SVA_USER - S0 buffer if SCDRP$V_FLAG_S0BUF is set
;		    SCDRP$PS_QBUF - Address of the Q_Buffer allocated
;		    SCDRP$PS_KPB - Address of the associated KPB
;
; OUTPUT:
;	R0 = Status
;
	.ENABLE LSB
;
; Define the offsets to the parameters pointed to by AP.
;
	$ARG_DEF	<-
	SPDT,		-			; SPDT address
	SCDRP		>			; SCDRP address

PKS$MAP_BUFFER_MR::
	.CALL_ENTRY	OUTPUT=<R0>,SCRATCH=<R1>,-
			PRESERVE=<R2,R3,R4,R5,R6,R7,R8,R9>

	MOVL	SPDT(AP),R4			; Get SPDT address
	MOVL	SCDRP(AP),R5			; Get SCDRP address
	MOVL	SCDRP$PS_QBUF(R5),R7            ; Get Queue Buffer address
	TSTL    SCDRP$L_BCNT(R5)		; Is there a data buffer?
	BEQL	110$				; No, then just return success
;
; Find the first SVAPTE for the user buffer.
;
ASSUME  SCDRP$V_FLAG_S0BUF EQ 0			; S0 bit must be bit zero of
						;  SCSI_FLAGS
	BLBC    SCDRP$L_SCSI_FLAGS(R5),5$	; Is this an S0 data buffer?
	MOVL	SCDRP$L_SVA_USER(R5),R0		; Yes, get S0 virtual address
	EVAX_SLL  R0,#<64-VA$V_SYSTEM>,R0	; Clear space select bit
	EVAX_SRL  R0,MMG$GQ_64SYS_SHIFT,R0	; Get virtual page number
	MOVAQ   @MMG$GL_SPTBASE[R0],R6		; Get SVAPTE that maps VA
	BRB	7$				; Join common code
5$:	MOVL	SCDRP$L_SVAPTE(R5),R6		; Get SVAPTE of P0 data buffer
;
; Allocate a pad block for this transfer if required.
;
7$:	TSTL	SCDRP$L_PAD_BCNT(R5)		; Is the pad count 0?
	BEQL	30$				; Yes, don't need pad block
	INCW	QBUF$IW_NUMSEG(R7)		; Pad block counts as a segment
10$:	ADDL3	#PAD$C_HEADER,-			; # of bytes to allocate
		SCDRP$L_PAD_BCNT(R5),R1
	SUBL	#4,SP				; Allocate a longword on stack
	PUSHL	SP				; Arg 4 = addr to receive size
	PUSHAB	QBUF$PS_PADBLK(R7)		; Arg 3 = addr to receive addr
						;  of allocated pad block
	PUSHL	#ALN_LONG			; Arg 2 = longword alignment 
	PUSHL	R1				; Arg 1 = requested block size
	CALLS	#4,EXE$ALONONPAGED_ALN		; Allocate a pad block
	POPL	R1				; Get the allocated size
	.BRANCH_LIKELY
	BLBS	R0,20$				; Did we get a block?
	MOVL    SCDRP$PS_KPB(R5),R0		; No, get KPB address
	KP_STALL_FORK_WAIT  KPB=R0,-		; Fork and wait
			    FKB=KPB$PS_UCB(R0)	;  using class driver UCB
	BRB	10$				; Try allocation again
20$:	MOVL	QBUF$PS_PADBLK(R7),R2		; Get the address of pad block
	PUSHR	#^M<R1,R2,R4,R5>		; Save registers
	MOVC5	#0,(SP),#0,R1,(R2)		; Zero initialize the block
	POPR	#^M<R1,R2,R4,R5>		; Restore registers
	MOVW	R1,PAD$IW_SIZE(R2)		; Set the allocated block size
	MOVB	#DYN$C_MISC,PAD$IB_TYPE(R2)	; Set type
	MOVB    #DYN$C_PADBLK,PAD$IB_SUBTYPE(R2); Set subtype
;
; Optimization: if all of the memory (pad block and user buffer) that
; this transfer touches is within the bus's direct-DMA window, we have no
; need to allocate and fill map registers.
;
	$SVA_TO_PA	SVA=R2			; Pad block address
	EVAX_ADDQ R1,R0,R0			; Add size of pad block
	EVAX_CMPULT R0,SPDT$IQ_DMA_SIZE(R4),R9	; Compare to DMA window size
	BLBC	R9,70$				; Branch if GT window size
;
; Calculate the number of pages involved in the request.
;
30$:	ADDL3	SCDRP$L_BOFF(R5),-
		SCDRP$L_BCNT(R5),R8
	ADDL2	G^MMG$GL_BWP_MASK,R8		; To round up.
	MNEGL	G^MMG$GL_VPN_TO_VA,R1
	ASHL	R1,R8,R8			; Convert to pages.
;
; Now cycle through the pages, checking each PA against the DMA window size.
;
	EVAX_LDQ  R2,SPDT$IQ_DMA_SIZE(R4)	; Window size
	MOVL	R6,R3				; Working copy of SVAPTE.
60$:	$SVAPTE_TO_PA -				; Convert SVAPTE to PA.
		SVAPTE = R3
	EVAX_CMPULT R0,R2,R9			; Compare to DMA window size
	BLBC	R9,70$				; Branch if GT window size
	ADDL	#PTE$C_BYTES_PER_PTE,R3		; Point to next SVAPTE.
	SOBGTR	R8,60$
;
; Here, we've verified that all pages we care about are in the window.  Set
; up and call the non-register buffer-mapping routine, and then return.
;
	PUSHL	R5				; SCDRP addr
	PUSHL	R4				; SPDT addr
	CALLS	#2,PKS$MAP_BUFFER
	BRW	120$				; Return
;
; Map the pad block and build a BSD with the resulting address.
;
70$:	MOVL	QBUF$PS_PADBLK(R7),R2		; Point to allocated mem.
	BEQL	90$
	ADDL3	#PAD$C_HEADER,R2,R3		; Pass pad header
	MAP_S0 -
		SVA = R3,-
		NBYTES = SCDRP$L_PAD_BCNT(R5),-
		CRCTX = PAD$PS_CRCTX(R2),-
		KPB$ = SCDRP$PS_KPB(R5),-
		DMA_ADDR = PAD$PQ_PA(R2)
	BUILD_BSD -				; Build BSD for data buffer.
		BSD_ADDR = QBUF$PS_DATA_BSD2(R7),-
		BUFFER_PA = PAD$PQ_PA(R2),-
		BYTE_COUNT = SCDRP$L_PAD_BCNT(R5)
;
; Initialize the CRCTX for this I/O's user buffer.
;
90$:	MOVAL	SCDRP$R_CRCTX_BASE(R5),R2	; Get CRCTX address
	PUSHL	SPDT$IS_FLCK(R4)
	PUSHL	R2
	PUSHL	SPDT$PS_CRAB(R4)
	CALLS	#3,G^IOC$INIT_CRCTX
;
; Calculate the number of map registers required to map the I/O buffer.
;
;	MOVL	SCDRP$L_BOFF(R5),R0		; Byte-within-page, converted
;	BICL	SPDT$IS_CRCTX_BWP_MASK(R4),R0	; to byte within map unit
;	ADDL2	SCDRP$L_BCNT(R5),R0		; Add in byte count
	ADDL3	SCDRP$L_BOFF(R5),-
		SCDRP$L_BCNT(R5),R0
	ADDL2	SPDT$IS_CRCTX_BWP_MASK(R4),R0	; Plus map unit size, so
	MNEGL	SPDT$IS_CRCTX_SHIFT(R4),R1	; the shift will round
	ASHL	R1,R0,R0			; upward.
	ADDL	#2,R0				; Add two for guard entries.
	MOVL	R0,CRCTX$L_ITEM_CNT(R2)
;
; Attempt to allocate the necessary map registers.  Stall if immediate
; allocation fails; MAP_CALLBACK will restart us.
;
	MOVAB	MAP_CALLBACK,-			; Set up to restart when
		CRCTX$L_CALLBACK(R2)		; allocation succeeds after
	MOVL	SCDRP$PS_KPB(R5),-		; initial failure.
		CRCTX$L_AUX_CONTEXT(R2)
	PUSHL	R2				; CRCTX address
	PUSHL	SPDT$PS_CRAB(R4)		; CRAB address
	CALLS	#2,IOC$ALLOC_CNT_RES
	BLBS	R0,100$				; Did we get 'em?
	MOVL	SCDRP$PS_KPB(R5),R8		; Nope; stall.
	MOVAB	B^IOC$RETURN,-			; Set stall routine
		KPB$PS_SCH_STALL_RTN(R8)
	PUSHL	R8
	CALLS	#1,EXE$KP_STALL_GENERAL
;
; At this point, one way or the other, we have the necessary map registers.
; Map the data buffer, and build a BSD with the resulting DMA-able address.
;
100$:	SUBL	#4,SP				; Make room for DMA addr
	PUSHL	SP				;
	PUSHL	SCDRP$L_BOFF(R5)		; Byte offset
	PUSHL	R6				; Buffer SVAPTE
	PUSHL	R2				; CRCTX addr
	PUSHL	SPDT$L_ADP(R4)			; ADP addr
	CALLS	#5,IOC$LOAD_MAP
	POPL	R2				; DMA address.
	BLBC	R0,120$				; Return error on map failure
	BUILD_BSD -				; Build BSD for data buffer.
		BSD_ADDR = QBUF$PS_DATA_BSD1(R7),-
		BUFFER_PA = R2,-
		BYTE_COUNT = SCDRP$L_BCNT(R5)
	INCW	QBUF$IW_NUMSEG(R7)		; Maps as one segment.
;
; Setup data transfer size in CCB.
;
	ADDL3	SCDRP$L_BCNT(R5),-		; Calculate and store 
		SCDRP$L_PAD_BCNT(R5),-		;  data transfer length
		QBUF$IS_DATA_TRAN_SIZE(R7)	;  in Queue Buffer

110$:	MOVZWL	#SS$_NORMAL,R0			; Set success status
120$:	RET					; Return
	.DISABLE LSB				; PKS$MAP_BUFFER_MR

	.SBTTL	MAP_CALLBACK -- callback for map-register allocation success
;
; When PKS$MAP_BUFFER_MR attempts to allocate a set of map registers, and
; the immediate attempt is unsuccessful, the utility routines for register
; allocation arrange for this routine to be called when the allocation
; eventually succeeds.  All we have to do is restart the thread (KP) that is
; waiting on the allocation.
;
; INPUT:
;
;	R1 -- CRCTX address for allocation request
;
; OUTPUT:
;
;	None
;
	.ENABLE LSB
MAP_CALLBACK:
        .JSB_ENTRY      INPUT=<R1>

	PUSHL	#SS$_NORMAL
	PUSHL	CRCTX$L_AUX_CONTEXT(R1)
	CALLS	#2,EXE$KP_RESTART

	RSB					; Return
	.DISABLE LSB				; MAP_CALLBACK

.PAGE
	.SBTTL	PKS$UNMAP_BUFFER_MR - Unmap Buffer Routine with Map Registers
;
; This routine replaces the SC$UNMAP_BUFFER routine in the SCSICOMMON module,
; for devices (currently, KZPSA) on buses (currently, PCI) that provide DMA
; map registers.
;
; It deallocates the map registers that were used to map the user buffer for
; this request.  If a pad block was used for this request, the map registers
; for it, as well as the buffer itself, are deallocated.
; 
; INPUT:
;	spdt(AP) = SPDT address
;	scdrp(AP)= SCDRP address
;
; OUTPUT:
;	R0 = Status
;
	.ENABLE LSB
;
; Define the offsets to the parameters pointed to by AP.
;
	$ARG_DEF	<-
	SPDT,		-			; SPDT address
	SCDRP		>			; SCDRP address

PKS$UNMAP_BUFFER_MR::
	.CALL_ENTRY	OUTPUT=<R0>,SCRATCH=<R1>,PRESERVE=<R4,R5,R6,R7>

	MOVL	SPDT(AP),R4			; Get SPDT address
	MOVL	SCDRP(AP),R5			; Get SCDRP address
	MOVL	SCDRP$PS_QBUF(R5),R6		; Get Queue Buffer address
;
; If there is a BSM, release any map registers that may be allocated to it.
; Then release the BSM itself.
;
	MOVL	QBUF$PS_BSM_PTR(R6),R6		; Get BSM address
	BEQL	5$				; Is there a BSM?
	MOVL	BSM$PS_CRCTX(R6),R7		; Yes, point to BSM CRCTX
	BEQL	3$				; Is there a CRCTX?
	PUSHL	R7				; Yes, deallocate resources
	PUSHL	SPDT$PS_CRAB(R4)
	CALLS	#2,IOC$DEALLOC_CNT_RES
	PUSHL	R7				; Deallocate the CRCTX
	CALLS	#1,IOC$DEALLOC_CRCTX
3$:	MOVL	R6,R0				; Get BSM address
	MOVZWL	BSM$IW_SIZE(R6),R1		; Get BSM size
	JSB	EXE$DEANONPGDSIZ		; Deallocate BSM
;
; If we've used map registers to map the user buffer, deallocate said
; registers.
;
5$:	MOVAB	SCDRP$R_CRCTX_BASE(R5),R6
	BITL	#CRCTX$M_ITEM_VALID,-		; Do we have a valid CRCTX?
		CRCTX$L_FLAGS(R6)
	BEQL	10$
	PUSHL	R6				; Yep, deallocate resources
	PUSHL	SPDT$PS_CRAB(R4)
	CALLS	#2,IOC$DEALLOC_CNT_RES
        BICL2   #CRCTX$M_ITEM_VALID, -  	; Invalidate the counted res
                CRCTX$L_FLAGS(R6)       	; extent described by this CRCTX
;
; Now release the registers used to map the pad block, if there was one, and
; deallocate the CRCTX, and then the buffer itself.
;
10$:	MOVL	SCDRP$PS_QBUF(R5),R6		; Get Queue Buffer address
	MOVL	QBUF$PS_PADBLK(R6),R6		; Is there a pad buffer?
	BEQL	30$				; Branch if not
	MOVL	PAD$PS_CRCTX(R6),R7		; Point to Pad Buffer CRCTX
	BEQL	25$
	PUSHL	R7				; Yep, deallocate resources
	PUSHL	SPDT$PS_CRAB(R4)
	CALLS	#2,IOC$DEALLOC_CNT_RES
	PUSHL	R7				; Now deallocate the CRCTX.
	CALLS	#1,IOC$DEALLOC_CRCTX

25$:	MOVZWL  PAD$IW_SIZE(R6),R1		; Get pad block size
	MOVL	R6,R0
	JSB     EXE$DEANONPGDSIZ		; Deallocate pad buffer

30$:	MOVZWL  #SS$_NORMAL,R0			; Success
	RET					; Return
	.DISABLE LSB				; PKS$UNMAP_BUFFER_MR

.PAGE
	.SBTTL	PK$NEGOTIATE_SYNCH - Start synchronous Negotiation
;+
; DESCRIPTION:
;
;       The KZ*SA adapters will perform synchronous SDTR negotiation at
;       the start of each command if the current data transfer mode is
;       unknown or in doubt; it will also respond to a CAM (QBUF) bit
;       which tells it to negotiate SDTR unconditionally.
;
;       It is possible for the KZ*SA to get stuck negotiating SDTR with
;       a SCSI device with which negotiation consistently fails; in that
;       event a utility must be used to configure the adapter to suppress
;       the negotiation to that specific ID. There is no CAM bit available
;       which the driver can use to suppress this negotiation.
;
;       For that reason, this driver's use of STDT$M_DFLG_SUPPRESS_SDTR is
;       a NOP - but we'll put it in anyway because it's harmless, consistent
;       with the rest of the drivers and will be useful if the FW should
;       ever change it's default behavior.
;
; For this port this routine will set up for synchronous I/O if requested.
;
; INPUT:
;	spdt(AP) = SPDT address
;	stdt(AP) = STDT address
;	Device IPL
;	Fork lock may be held
;	SPDT lock held
;	Auto configuration thread
;	FORK thread
;	Queue Manager thread
;
; OUTPUT:
;	R0 = Status
;		SS$_NORMAL = Success
;		SS$_ACCVIO = Error returned by the optional debugging code.
;	R1 = Destroyed
;	R2 - R12 = Preserved
;	Device IPL
;	Fork lock still held if held upon entry.
;	SPDT lock held
;	Auto configuration thread
;	FORK thread
;	Queue Manager thread
;
	.ENABLE LSB
;
; Define the offsets to the parameters pointed to by AP.
;
	$ARG_DEF	<-
	spdt,		-			; SPDT address
	stdt		>			; STDT address

PK$NEGOTIATE_SYNCH::

	.CALL_ENTRY	OUTPUT=<R0>,-
			SCRATCH=<>,-
			PRESERVE=<R10>

	MOVL	stdt(AP),R10			; Get STDT address

        BBS     #STDT$V_DFLG_SUPPRESS_SDTR,-    ; Branch if SDTR negotiation
                STDT$IS_DIPL_FLAGS(R10),10$     ; has been suppressed

	BISL	#STDT$M_DFLG_RENEGOTIATE_SYNC,-	; Setup to renegotiate synch
		STDT$IS_DIPL_FLAGS(R10)		;  for this connection

10$:	MOVZWL  #SS$_NORMAL,R0			; Success
	RET					; Return
	.DISABLE LSB				; PK$NEGOTIATE_SYNCH 

.PAGE
	.SBTTL	PK$SEND_COMMAND - SCSI port specific I/O Initiate routine
;
; This routine is called by SC$SEND_CMD in SCSICOMMON to send the
; I/O request to the SIMport adapter.
;
; This routine will complete filling in the appropriate fields in the
; Queue Buffer, allocate a Carrier to point to the Queu eBuffer and then
; insert the Carrier on the DACQ tail. Since the SIMport channel handles
; the SCSI bus phase transitions instead of the port driver, once the
; command is queued to the DACQ, this I/O request is suspended/stalled until
; the corresponding response is returned from the adapter or until the
; adapter exits the ENABLED state due to some error. In the case of
; adapter state transition, a cleanup process will be initiated to resume
; any stalled I/O request, returning error status to the class driver.
;
; When the response is processed, the corresponding Carrier is returned
; to the non-paged pool by the FORK routine.
;
; INPUT:
;	spdt(AP) = Port SPDT address.
;	scdrp(AP) = Class SCDRP address.
;            SCDRP$L_CMD_PTR	- Command out buffer
;				  The first longword contains the count of 
;				  the number of cmd bytes.
;            SCDRP$L_BCNT	- Number of bytes of user data
;            SCDRP$L_STS_PTR	- Address of the buffer to be used for status
;				  bytes from the target
;            SCDRP$L_CDT	- Connection id of connection to send command
;            SCDRP$PS_KPB	- Address of associated Kernel Process Block
;	scdt(AP) = SCDT address.
;	stdt(AP) = STDT address.
;	ucb(AP) = Port UCB address.
;	SCDRP ownership
;	FORK IPL
;	FORK lock held
;	Queue manager thread
;
; OUTPUT:
;	R0 = Port status
;	     SS$_NORMAL	- Success
;	     SS$_ABORT	- Command was aborted 
;	     SS$_CTRLERR - Port failed and there should not be retry
;	     SS$_DEVOFFLINE - Port is permanently offline
;	     SS$_MEDOFL	- Port is being reinitialized, retry later
;	No SCDRP ownership - relinquished to PK$CMD_WAIT_COMPLETION.
;	FORK IPL
;	FORK lock held
;	Queue manager thread
;
	.ENABLE LSB
;
; Define the offsets to the parameters pointed to by AP.
;
	$ARG_DEF	<-
	spdt,		-			; SPDT address
	scdrp		-			; SCDRP address
	scdt,		-			; SCDT address
	stdt		>			; STDT address

PK$SEND_COMMAND::
	.CALL_ENTRY	OUTPUT=<R0>,PRESERVE=<R2,R3,R4,R5,R6,R7,R8,R9,R10>

	MOVL	spdt(AP),R4			; Get the SPDT address.
	MOVL	scdrp(AP),R5			; Get the SCDRP address.
	MOVL	scdt(AP),R3			; Get the SCDT address.
	MOVL	stdt(AP),R10			; Get the STDT address.
;
; Check port online status to be certain that the port is in ENABLED state
; and is capable of handling commands before proceeding.
;
	BBS	#SPDT$V_STS_ONLINE,-		; Is port online?
		SPDT$L_STS(R4),10$
	MOVZWL	#SS$_MEDOFL,R0			; No, medium offline
	BRW	100$				; Return
;
; The following instruction is not necessary for the SIMport adapter, but
; is needed to be compatible with existing SCSICOMMON SC$SEND_COMMAND routine.
;
10$:	MOVAB	SCDRP$B_SCSIMSGI_BUF(R5),-	; Setup pointer to message-in
		SCDRP$L_SCSIMSGI_PTR(R5)	;  buffer
;
; Set up the Queue Buffer (CCB) for the Execute SCSI I/O command.
; Start with the common CCB header.
;
	MOVL	SCDRP$PS_QBUF(R5),R7		; Get address of Queue Buffer
	MOVL	R7,SPDT$IS_QBUF_ADDR(R4)	; Save Queue Buffer address
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_EXEC_SCSI_IO_SIZE,-	; CCB length
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_EXEC_SCSI_IO,-		; Setup function code
		QBUF$IB_FUNC_CODE(R7)
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM status
	MOVB	SPDT$IB_PI_PATH_ID(R4),-	; Setup path id
		QBUF$IB_PATH_ID(R7)
	MOVB	STDT$IS_SCSI_ID_NUM(R10),-	; Setup target id
		QBUF$IB_TARGET_ID(R7)
	MOVB	SCDT$L_SCSI_LUN(R3),-		; Setup LUN
		QBUF$IB_LUN(R7)
;
; Setup the CAM flags longword. (Bytes 3 and 4 are kept cleared.)
; Start with the data transfer direction.
;
	TSTL	SCDRP$L_BCNT(R5)		; Check transfer byte count
	BNEQ	20$				; Is there data to transfer?
	BISB	#QBUF$M_NO_DATA_TX,-		; No, direction is no data
		QBUF$IB_CAM_FLAGS_1(R7)
	BRW	50$
20$:	BBS	#IRP$V_FUNC,SCDRP$IS_STS(R5),40$ ; Is this a write function?
	BISB	#QBUF$M_WRITE,-			; Yes, direction is out
		QBUF$IB_CAM_FLAGS_1(R7)
	BRW	50$	
40$:	BISB	#QBUF$M_READ,-			; Read, direction is in
		QBUF$IB_CAM_FLAGS_1(R7)
;
; Now setup the queuing characteristics.
;
	.BRANCH_UNLIKELY
50$:	BBC	#SCDRP$V_FLAG_QUEUED_IO,-	; Queued request?
		SCDRP$L_SCSI_FLAGS(R5),520$
	MOVL	SCDRP$IS_QUEUE_CHAR(R5),R1	; Yes, Get queue characteristics
	BISB	TC_ACTION_ENABLE_TABLE(R1),-	; Set TCQ action enable
		QBUF$IB_CAM_FLAGS_1(R7)
	MOVB	TAG_QUEUE_ACTION_TABLE(R1),-	; Setup tag queue action
		QBUF$IB_TAG_QUEUE_ACTION(R7)
	BRW	521$				; Join common code

520$:	BICB	#QBUF$M_TQ_ACTION_ENB,-		; Not a queued request
		QBUF$IB_CAM_FLAGS_1(R7)		;  Clear TCQ action enable
	CLRB	QBUF$IB_TAG_QUEUE_ACTION(R7)	; Clear tag queue action
;
; Setup sync/async mode.
;
521$:   BBCC    #STDT$V_DFLG_RENEGOTIATE_SYNC,-         ; Branch if user has not requested
                STDT$IS_DIPL_FLAGS(R10),51$             ; SDTR negotation (of any mode)
        TSTL    STDT$IS_REQUESTED_REQACK_OFFSET(R10)    ; Do they want synchronous mode?
        BNEQ    522$                                    ; Branch if so (REQ/ACK <> 0)
        BISB    #QBUF$M_SYNC_TRAN_DIS,-                 ; Negotiate asynchronous mode
                QBUF$IB_CAM_FLAGS_2(R7)                 ; data transfers if necessary
        BRW     51$                                     ; Join common code
522$:   BISB    #QBUF$M_SYNC_TRAN_INIT,-                ; Negotiate synchronous mode
                QBUF$IB_CAM_FLAGS_2(R7)                 ; data transfers unconditionally
;
; Do not allow the SIM queue to be frozen.
;
51$:	BISB	#QBUF$M_SIM_Q_FREEZE_DIS,-	; Set SIM queue freeze disable
		QBUF$IB_CAM_FLAGS_2(R7)
;
; The common CCB header is set up. Set up the Execute SCSI I/O extension.
;
515$:
	MOVL    SCDRP$L_CMD_PTR(R5),R9		; Get pointer to CDB
	MOVL    (R9)+,R0			; Get command length
	BICL    #^C^XFF,R0			; Clear upper bytes
	MOVB    R0,QBUF$IB_CDB_SIZE(R7)		; Save command length
        PUSHR   #^M<R0,R1,R2,R3,R4,R5>		; Save registers
        MOVC3   R0,(R9),QBUF$IB_CDB(R7)		; Copy CDB into CCB
        POPR    #^M<R0,R1,R2,R3,R4,R5>		; Restore registers
;
; Setup disconnect enable.
;
	CMPB	QBUF$IB_CDB(R7),#SCSI$K_INQUIRY	; Is this an inquiry command?
	BEQL	516$				; Enable disconnects if so
	BBS	#SCDT$V_CFLG_ENA_DISCON,-	; Are disconnects allowed?
		SCDT$IS_CON_FLAGS(R3),516$
	BISB	#QBUF$M_DIS_DISCONNECT,-	; No, disable disconnect
		QBUF$IB_CAM_FLAGS_2(R7)

516$:	MOVL	SCDRP$L_DMA_TIMEOUT(R5),R0	; Get DMA/phase change timeout
	ADDL3	SCDRP$L_DISCON_TIMEOUT(R5),R0,-	; Setup command timeout value
		QBUF$IS_TIMEOUT_VALUE(R7)
;
; If we've set up mapping registers for this Queue Buffer, the mapping covers
; the autosense buffer.  Otherwise, we need to supply the latter's PA to the
; adapter.
;
	TSTL	QBUF$PS_CRCTX_PTR(R7)		; Do we have map registers?
	BEQL	60$
	ADDL3	#QBUF$C_AUTOSENSE_OFFSET,-	; Yep, just add offset to
		QBUF$PQ_PA_QBUF(R7),R0		; DMA_ADDR.
	BRB	70$
60$:	MOVAB	QBUF$IB_AUTOSENSE_BUF(R7),R6	; Get autosense buffer pointer
	$SVA_TO_PA   SVA=R6			; Convert to PA
	ADDL2	SPDT$L_DMA_BASE(R4),R0		; Add in DMA window base
70$:	EVAX_ZAP  R0,#^XF0,R0			; Clear the upper longword.
	EVAX_STQ  R0,QBUF$PS_SENSE_BUFF_PTR(R7)	; Save autosense PA in CCB
	MOVB	#QBUF$C_AUTOSENSE_BUF_SIZE,-	; Autosense buffer length
		QBUF$IB_SENSE_BUFF_SIZE(R7)
	MOVAB	QBUF$IB_AUTOSENSE_BUF(R7),R6	; Get VA of autosense buffer
	BUILD_BSD -				; Build a BSD for autosense
		BSD_ADDR   = QBUF$PS_SEN_BSD(R7),-
		BUFFER_PA = R0,-
		BYTE_COUNT = #QBUF$C_AUTOSENSE_BUF_SIZE
;
; Allocate a Carrier and setup Carrier fields.
;
	MOVL    SCDRP$PS_KPB(R5),R8		; Get KPB address
	BSBW    GET_QUEUE_CARRIER		; Get a Carrier. R2 = Car ptr
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  Flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
;
; Send the command to the SIMport adapter.
;
	MOVL    SPDT$L_DLCK(R4),KPB$PS_DLCK(R8) ; Device lock pointer to KPB
	BSBW	INSERT_DACQ			; Insert command on DACQ
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR=SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL=SPDT$B_DIPL(R4),-
		SAVIPL=KPB$IS_NEWIPL(R8),-
		PRESERVE=NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR=SPDT$L_DLCK(R4),-
		NEWIPL=KPB$IS_NEWIPL(R8),-
		PRESERVE=NO

	MOVZWL	#SS$_NORMAL,R0			; Return normal status
100$:	RET					; Return to queue manager
	.DISABLE LSB				; PK$SEND_COMMAND

.PAGE
	.SBTTL  PKS_STALL - Kernel Process Stall Routine
;
; This routine performs the corresponding DEVICEUNLOCK for the lock that
; was done in the PK$INITIATE_IO routine.
;
; DEVICEUNLOCK is done here so that the port cannot interrupt us until
; this thread is stalled properly.
;
; CALLED BY:  I/O interrupt dispatcher
;
; INPUT:
;	4(AP) = KPB address
;
; OUTPUT:
;	None
;
;  NOTE: R2 is saved here since SS$RELEASE_PORT destroys it.
;
	.ENABLE LSB

	$ARG_DEF <-
	kpb	 >				; Kernel Process Block

PKS_STALL:
	.CALL_ENTRY	<R2,R3,R4,R5,R6,R7,R8,R9,R10>

	MOVL	kpb(AP),R1			; Get KPB parameter
	MOVL	KPB$PS_SCSI_PTR1(R1),R4		; Get SPDT address from KPB
	MOVL	KPB$PS_SCSI_SCDRP(R1),R5	; Get SCDRP address
	ADDL3	G^EXE$GL_ABSTIM,#IO_TIMEOUT,-	; Set up I/O thread timeout
		SCDRP$L_DUETIME(R5)		;  value in seconds
        MOVL    SCDRP$L_CDT(R5),R3              ; Get SCDT address

	RET					; Return

.PAGE
	.SBTTL	PK$CMD_WAIT_COMPLETION - Stall for I/O completion/reselection
;
; This routine is called by SC$SEND_COMMAND to wait for the I/O
; completion. If a reselection occurs during the processing of this I/O
; request then this routine is responsible for driving the I/O to completion
; in the SCDRP thread context.
;
; This is a NOP right now until we figure out what really goes here.
;
; INPUTS:
;
;	spdt(AP) = SPDT address
;	scdrp(AP) = SCDRP address
;	scdt(AP) = SCDT address
;	stdt(AP) = STDT address
;
; OUTPUTS:
;		SCDRP ownership - Ownership was transferred by PK$SEND_COMMAND
;		SCDRP thread
;	R0 = status returned to SC$SEND_COMMAND.
;	     SS$_NORMAL	- SCSI request completed
;	     SS$_MEDOFL	- Port is being reinitialized, retry later
;
	.ENABLE LSB
;
; Define the offsets to the parameters pointed to by AP.
;
	$ARG_DEF < -
	spdt,	-				; SPDT address
	scdrp,	-				; SCDRP address
	scdt,	-				; SCDT address
	stdt	>				; STDT address

PK$CMD_WAIT_COMPLETION::
	.CALL_ENTRY	OUTPUT=<R0>

	MOVL	spdt(AP),R4			; Get the SPDT address.
	MOVL	scdrp(AP),R5			; Get the SCDRP address.
	MOVL	SCDRP$PS_KPB(R5),R8		; Get KPB address
;
; Wait for the channel to return a response to this command by suspending
; this I/O request to wait for the command completion interrupt.
;
	BBCC	#SCDRP$V_DSF_NOWAIT,-		; Are we stalled?
		SCDRP$IS_DIPL_SCSI_FLAGS(R5),10$
        DEVICEUNLOCK -				; Yes, release device lock
                LOCKADDR=SPDT$L_DLCK(R4),-
                NEWIPL=KPB$IS_NEWIPL(R8),-            
                PRESERVE=NO
	BRW	20$

10$:	MOVL	R4,KPB$PS_SCSI_PTR1(R8)		; Save SPDT address
	MOVL	R5,KPB$PS_SCSI_SCDRP(R8)	; Save SCDRP address
	MOVAB	B^PKS_STALL,-			; Set stall routine address
		KPB$PS_SCH_STALL_RTN(R8)
	CLRL	KPB$PS_SCH_RESTRT_RTN(R8)	; No restart routine required
	PUSHL	R8				; Pass KPB address
	CALLS	#1,G^EXE$KP_STALL_GENERAL	; Call the general KP staller
;
; This is the point at which the stalled request resumes when the response
; is returned. At this point:
;	 R4 = SPDT address
;	 R5 = SCDRP address
;
20$:	MOVL	KPB$PS_SCSI_SCDRP(R8),R5	; Get SCDRP address
	MOVL	SCDRP$PS_QBUF(R5),R7		; Get Queue Buffer address
;
; If this is a read from device data transfer with unaligned data, we have
; to copy the unaligned bytes back to the user buffer. Any unaligned leading
; and/or trailing bytes are currently in the BSM (BSM$PS_LEADING_BUFF, and/or
; BSM$PS_TRAILING_BUFF). The number of unaligned bytes (if any) are in
; BSM$IS_LEADING_COUNT and/or BSM$IS_TRAILING_COUNT.
;
; If this is a write to device data transfer, there is nothing required for
; copying data to/from the user buffer.
;
	BBC	#IRP$V_FUNC,-			; Is this a read function?
		SCDRP$IS_STS(R5),70$
	MOVL	QBUF$PS_BSM_PTR(R7),R2		; Yes, get BSM address
	BEQL	70$				; Is there a BSM?
	TSTL	BSM$IS_LEADING_COUNT(R2)	; Yes, check leading alignment
	BEQL	55$				; Is there leading unalignment?
	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBC    SCDRP$L_SCSI_FLAGS(R5),52$	; Yes, is this an S0 buffer?
	MOVL	SCDRP$L_SVA_USER(R5),R1		; Yes, get S0 virtual address
	BRB	54$				; Join common code
52$:	MOVL	SCDRP$L_SVAPTE(R5),R7		; Get SVAPTE of P0 data buffer
53$:	JSB	MAP_BUFF			; Map to user P0 data buffer
						;  R1 = address of page that
						;  contains unaligned data
	MOVL	R1,R9				; Save VA of first page
	ADDL	SCDRP$L_BOFF(R5),R1		; Point to unaligned data byte
54$:	PUSHR   #^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC3   BSM$IS_LEADING_COUNT(R2),-	; Copy unaligned data back to
		BSM$PS_LEADING_BUFF(R2),(R1)	;  user buffer
	POPR    #^M<R0,R1,R2,R3,R4,R5>		; Restore registers
	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBS    SCDRP$L_SCSI_FLAGS(R5),55$	; Is this an S0 buffer?
	MOVL	R9,R1				; No, restore R1 to page addr
	JSB	UNMAP_BUFF			; Unmap from P0 user buffer

55$:	TSTL	BSM$IS_TRAILING_COUNT(R2)	; Check trailing alignment
	BNEQ	56$				; Is there trailing unalignment?
	MOVL	SCDRP$PS_QBUF(R5),R7		; No, get Q_Buffer address back
	BRW	70$				; Join common code

56$:	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBC    SCDRP$L_SCSI_FLAGS(R5),565$	; Is this an S0 buffer?
	MOVL	SCDRP$L_SVA_USER(R5),R1		; Yes, get S0 virtual address
	ADDL2	SCDRP$L_BCNT(R5),R1		; Point to last data byte
	SUBL2	BSM$IS_TRAILING_COUNT(R2),R1	; Backup to unaligned data bytes
	BRW	58$

565$:	MOVL	SCDRP$L_SVAPTE(R5),R7		; Get SVAPTE of first page of
						;  P0 data buffer
	ADDL3	SCDRP$L_BCNT(R5),-		; Point to last data byte
		SCDRP$L_BOFF(R5),R9
	$BYTES_TO_PAGES-			; Compute page count that
		SOURCE_BYTCNT = R9,-		;  user buffer spans
		DEST_PAGCNT   = R3,-
		ROUNDUP       = NO
	MULL2	#PTE$C_BYTES_PER_PTE,R3		; Convert pages to PTE pages
	ADDL2	R3,R7				; Add PTE(s) size to starting
						;  SVAPTE of first page
	JSB	MAP_BUFF			; Map to P0 user data buffer
	MOVL	R1,R10				; Save VA of first page
	MCOML   G^MMG$GL_BWP_MASK, R3		; Get complement of BWP mask
	BICL3   R3,R9,R3	                ; get offset to last by in page
	SUBL2	BSM$IS_TRAILING_COUNT(R2),R3	; Backup to unaligned data bytes
	ADDL2	R3,R1				; Set start of unali
58$:	PUSHR   #^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC3   BSM$IS_TRAILING_COUNT(R2),-	; Copy unaligned data back to
		BSM$PS_TRAILING_BUFF(R2),(R1)	;  user buffer
	POPR    #^M<R0,R1,R2,R3,R4,R5>		; Restore registers
	ASSUME  SCDRP$V_FLAG_S0BUF EQ 0		; Verify position of S0 bit
	BLBS    SCDRP$L_SCSI_FLAGS(R5),59$	; Is this an S0 buffer?
	MOVL	R10,R1				; No, restore R1 to page addr
	JSB	UNMAP_BUFF			; No, unmap from P0 user buffer
59$:	MOVL	SCDRP$PS_QBUF(R5),R7		; Get Queue Buffer address back
;
; Return the SCSI status that was returned in the Queue Buffer (CCB).
;
70$:	MOVB	QBUF$IB_SCSI_STAT(R7),-		; Return SCSI status from
		@SCDRP$L_STS_PTR(R5)		;  Private Data Area
	MOVB    #SCSI$K_MSGCMD,-		; Set up command complete msg
		SCDRP$B_SCSIMSGI_BUF(R5)	; for SC$SEND_CMD
;
; The SIMport adapter does not provide a transferred byte count. Instead it
; provides a residual byte count of bytes that were not transferred.
; Return the actual transferred byte count.
;
	SUBL3	QBUF$IS_DATA_RES(R7),-		; Compute actual transfer count
		QBUF$IS_DATA_TRAN_SIZE(R7),-
		SCDRP$L_TRANS_CNT(R5)
	MOVZWL	QBUF$IW_PORT_STS(R7),R0		; Get return port status
        MOVL    SCDRP$L_CDT(R5),R3		; Get the SCDT address

	RET					; Return to Queue Manager

.PAGE
	.SBTTL  PKS$INTERRUPT - SIMport Adapter Interrupt Service Routine
;
; This routine handles all SIMport adapter interrupts.
;
; The host may be interrupted for the following two reasons:
;
; 1.) To notify the host that new entries are on the adapter driver
;     response queue (ADRQ).
; 2.) To notify the host that the adapter status register (ASR) has changed
;     and requires servicing. The required service is indicated in the adapter
;     status register. The adapter miscellaneous interrupt is typically used
;     to inform the host of an adapter error condition that has caused the
;     adapter to transition to the uninitialized state.
;
; A fork thread is started (if one is not already running) to handle the
; interrupt. The fork PC in the UCB (UCB$L_FPC) is used as a semaphore to
; determine if a fork thread is currently running. The fork routine must
; clear the fork PC upon routine exit.
;
; The fork routine will handle response interrupts (if any) and then check
; for adapter error interrupts. In the case of an adapter error interrupt,
; all of the responses on the ADRQ will be processed before adapter
; re-initialization is performed.
;
; CALLED BY:  I/O interrupt dispatcher
;
; INPUT:
;	4(AP) = IDB address of the channel
;
; OUTPUT:
;	None
;
	.ENABLE	LSB
PKS$INTERRUPT::
	.CALL_ENTRY	<R2,R3,R4,R5,R6,R7,R8,R9,R10>

	MOVL    4(AP),R3			; Get IDB address 
	MOVL	IDB$PS_ADP(R3),R0		; Get ADP address
	MOVL	ADP$L_CRB(R0),R0		; Get CRB address
	MOVL    IDB$PS_AUXSTRUC(R3),R5		; Get UCB address
	MOVL    UCB$L_PDT(R5),R4		; Get SPDT address
;
; Read the ASR register to determine what caused this interrupt.
;
	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	READ_CSR REG  = ASR,-			; Read ASR register
		 DEST = SPDT$IS_SAVE_ASR(R4)
	TSTL	UCB$L_FPC(R5)			; Check fork semaphore
	BNEQ	10$				; Is a fork thread running?
	FORK	ROUTINE = PKS$FORK,-		; No, start one running
		CONTINUE = 10$

10$:	MOVL    UCB$L_PDT(R5),R4		; Get SPDT address back
	WRITE_CSR REG  = CLEAR_INT,-		; Re-enable interrupts
		  DATA = #0
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO

	RET					; Dismiss this interrupt
	.DISABLE LSB				; PKS$INTERRUPT

.PAGE
	.SBTTL  PKS$FORK - Interrupt Handling Fork Routine
;
; This fork routine handles all SIMport adapter interrupts.
;
; The fork routine will handle response interrupts (if any) and then check
; for adapter error interrupts. In the case of an adapter error interrupt,
; all of the responses on the ADRQ will be processed before adapter
; re-initialization is performed.
;
; CALLED BY:  Fork dispatcher
;
; INPUT:
;	R3 = IDB address
;	R4 = SPDT address
;	R5 = UCB address
;
; OUTPUT:
;	None
;
	.ENABLE	LSB
PKS$FORK:
	.CALL_ENTRY	<R2,R3,R4,R5,R6,R7,R8,R9,R10>
;
; Start by handling all (if any) responses on the ADRQ.
;
RSP_LOOP:
	MOVL	SPDT$PS_AB(R4),R6		; Get Adapter Block address
	MOVL	AB$PQ_ADRQ_HEAD(R6),R2		; Get next response Carrier
	BBS	#0,CAR$PQ_NEXT_PTR(R2),-	; Done if Stopper Carrier
		CHECK_ADAPTER_ERROR
	.BRANCH_LIKELY
	BBS	#VA$V_SYSTEM,R2,10$		; Branch if VA
	BUG_CHECK INCONSTATE,FATAL		; We want to capture this
.Disable Flagging
10$:	MOVL	CAR$PQ_NEXT_PTR(R2),-		; Update ADRQ head pointer
		AB$PQ_ADRQ_HEAD(R6)		;  with next Carrier address
.Enable Flagging
	EVAX_MB                                 ; Make sure Q_Buf contents
	MOVL	CAR$PQ_QBUF_PTR(R2),R7		; Get Queue Buffer VA
	.BRANCH_LIKELY
	BBS	#VA$V_SYSTEM,R7,20$		; Branch if VA
	BUG_CHECK INCONSTATE,FATAL		; We want to capture this
;
; Dispatch to handle the response. R7 = Queue Buffer, R2 = Carrier.
;
20$:	DISPATCH  CAR$IB_FUNC_CODE(R2),TYPE=B,PREFIX=CAR$C_,-
		<-
		<EXEC_SCSI_IO,    EXEC_SCSI_IO_RSP>,-
		<ADAP_STATE_SET,  ADAP_STATE_SET_RSP>,-
		<PARAMETER_SET,   PARAMETER_SET_RSP>,-
		<CHAN_STATE_SET,  CHAN_STATE_SET_RSP>,-
		<DEV_STATE_SET,   DEV_STATE_SET_RSP>,-
		<ADAP_SANITY_VER, ADAP_SANITY_VER_RSP>,-
		<CNTRS_READ,      CNTRS_READ_RSP>,-
		<BSD_REQUEST,     BSD_REQ_RSP>,-
		<BUS_RESET_REQ,   BUS_RESET_REQ_RSP>,-
		<UNSOL_RESEL,     UNSOL_RESEL_RSP>,-
		<CHAN_DISABLED,   CHAN_DISABLED_RSP>,-
		<DEV_DISABLED,    DEV_DISABLED_RSP>-
		<ACCEPT_TARGET_IO ACCEPT_TARGET_IO_RSP>,-
		<CONT_TARGET_IO   CONT_TARGET_IO_RSP>-
		<TARGET_EVENT     TARGET_EVENT_RSP>-
		<TARGET_STATE_SET TARGET_STATE_SET_RSP>-
		>
;
; If we fall through to here, we have an invalid response function code.
; Return resources and log the error.
;
	MOVL	SPDT$PS_AB(R4),R6		; Get Adapter Block address
	MOVL	AB$PQ_ADFQ_HEAD(R6),-		; Insert Carrier at ADFQ head
		CAR$PQ_NEXT_PTR(R2)
	MOVL	R2,AB$PQ_ADFQ_HEAD(R6)		; Update ADFQ head pointer

	MOVL	SPDT$L_PORT_UCB(R4),R5		; Get UCB for error logger
	LOG_ERROR -				; Log illegal response
		TYPE = ILLEGAL_RSP,-
		CDT = #0			; No associated CDT available
	BRW	RSP_LOOP			; Check for more responses
;
; We received a SCSI I/O executed response message.
;
EXEC_SCSI_IO_RSP:
	MOVL	QBUF$PS_SCDRP(R7),R5		; Get SCDRP address
;
; Check if there is any valid autosense data. If so, return the autosense
; buffer address.
;
	MOVZBL	CAR$IB_STATUS(R2),R1		; Get returned CAM status
	MOVL	R1,SPDT$IS_CAM_STS(R4)		; Save CAM status
	BBCC	#CAR$V_AUTOSENSE_DATA_VALID,-	; Valid autosense data?
		R1,35$
	MOVAB	QBUF$IB_AUTOSENSE_BUF(R7),-	; Yes, return autosense buffer
		SCDRP$PS_SENSE_BUFFER(R5)	;  pointer
	BISL	#SCDRP$M_FLAG_ASENSE_VALID,-	; Indicate valid autosense data
		SCDRP$L_SCSI_FLAGS(R5)
;
; Map the returned CAM status in the Carrier to a VMS status code and return
; resources for this response.
;
35$:	ASHL	#2,R1,R1			; Make longword offset
	MOVAB	CAM_STATUS_TABLE,R3		; Point to beginning of table
	ADDL2	R1,R3				; Point to VMS status code
	MOVL	(R3),R1				; Get VMS status code
	MOVW	R1,QBUF$IW_PORT_STS(R7)		; Setup VMS return status
	BSBW	RET_QUEUE_CARRIER		; Return Carrier to pool
;
; Resume the thread that was waiting for this response.
;
40$:	MOVL	SCDRP$PS_KPB(R5),R0		; Get KPB address
	BBC	#KPB$V_ACTIVE,-			; Is KPB stalled?
		KPB$IS_FLAGS(R0),45$
	BISL	#SCDRP$M_DSF_NOWAIT,-		; Indicate no stall needed
		SCDRP$IS_DIPL_SCSI_FLAGS(R5)	;  in CMD_WAIT_FOR_COMPLETION
	BRW	RSP_LOOP			; See if more responses

45$:	BICL	#SCDRP$M_DSF_NOWAIT,-		; Indicate stall needed
		SCDRP$IS_DIPL_SCSI_FLAGS(R5)	;  in CMD_WAIT_FOR_COMPLETION
	PUSHL	R0				; Pass KPB address
	CALLS	#1,G^EXE$KP_RESTART		; Resume this thread
	BRW	RSP_LOOP			; See if more responses
;
; We received a SCSI bus reset request response message. Send a BUS RESET
; RESPONSE command to adapter allowing the adapter to perform the reset.
; We can use the same Queue Buffer and Carrier that was sent by the adapter.
;
BUS_RESET_REQ_RSP:
; The Bus Reset response command does not define any fields in the CCB.
; The action authorized by the host is contained in the status field of
; the Carrier.
;
	MOVB	#QBUF$C_BUS_RESET_CMD,-		; Queue Buffer function code
		QBUF$IB_FUNC_CODE(R7)
	MOVL	CAR$IS_CONNECT_ID(R2),-		; Set up connect id
		QBUF$IS_CONNECT_ID(R7)
	BSBW	INSERT_DACQ			; Insert command on DACQ
	MOVB	#CAR$C_SUCCESS,-		; Allow adapter to reset bus
		CAR$IB_STATUS(R1)
;
; Send the command to the SIMport adapter. This will cause the adapter to
; return all I/O with a reset status. When the Channel State Set response
; is returned, routine CHAN_STATE_SET_RSP will continue with the reset.
;
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	BRW	RSP_LOOP			; Check for more responses
;
; We received an unsolicited reselection response message. Log the error.
;
UNSOL_RESEL_RSP:
	BSBW	INSERT_DAFQ			; Return resources to free queue
	LOG_ERROR -				; Log an error log entry
		TYPE = CTL_ERR,-		;  Controller error
		CDT  = #0			;  No associated SCDT
	BRW	RSP_LOOP			; Check for more responses
;
; We received a Channel State Disabled response message. Issue a Set Channel
; State Enabled command to the adapter. R2 = Carrier, R7 = Queue Buffer.
;
CHAN_DISABLED_RSP:
	MOVZBL	CAR$IB_STATUS(R2),-		; Save channel disabled status
		SPDT$IS_CHAN_DIS_STS(R4)
	BSBW	INSERT_DAFQ			; Return resources to free queue
;
; Determine the reason for the channel becoming disabled. If a SCSI bus reset
; has been detected, indicate a bus reset is in progress. This will cause
; IO requests to be returned with the appropriate status.
;
	CMPL	SPDT$IS_CHAN_DIS_STS(R4),-	; Check channel disabled status
		#SPDT$C_BUS_RESET
	BNEQ	50$				; SCSI bus reset been detected?
	BISL	#SPDT$M_RESET_DETECTED,-	; Yes, set reset detected
		SPDT$IS_CHANSTATE(R4)
;
; Allocate some pool for a Queue Buffer (CCB).
;
50$:	SUBL    #8,SP				; Allocate two lw on stack
	MOVL    SP,R2				; Store address of lw to receive
						;  address of allocated block
        ADDL3   #4,R2,R6			; Store address of lw to receive
						;  size of allocated block
        PUSHL   R6				; Arg 4 = addr to receive size
        PUSHL   R2				; Arg 3 = addr to receive addr
						;  of allocated block
	PUSHL	#ALN_LONG			; Arg 2 = 4 byte alignment
	PUSHL	#256				; Arg 1 = size of block
	CALLS   #4,EXE$ALONONPAGED_ALN		; Allocate a block
	POPL	R2				; Get addr of allocated block
	POPL	R6				; Get allocated size
	BLBS	R0,55$				; Did we get the block?
	KP_STALL_FORK_WAIT  KPB=R8,-		; No, fork and wait
		FKB=KPB$PS_UCB(R8)		;  using UCB fork block
	BRB	50$				; Try allocation again

55$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC5   #0,(SP),#0,R6,(R2)		; Zero initialize the block
	POPR	#^M<R0,R1,R2,R3,R4,R5>		; Restore registers
	MOVL    R2,R7                           ; Copy Queue Buffer pointer
;
; Set up Queue Buffer fields for the Set Channel State Enabled command.
;
	MOVW	R6,QBUF$IW_SIZE(R7)		; Save Queue Buffer size
	MOVB	#DYN$C_MISC,QBUF$IB_TYPE(R7)	; Set buffer type
	MOVB	#DYN$C_QBUF,QBUF$IB_SUBTYPE(R7)	; Set buffer subtype

	MOVAB	QBUF$PQ_CCB_ADDR(R7),R6		; VA of adapter visible section
	$SVA_TO_PA   SVA = R6			; PA of adapter visible section

	BITL	#SPDT$M_PFLG_MAPPING_REG,-	; Are we using map regs?
		SPDT$L_PORT_FLAGS(R4)
	BEQL	57$				; Nope; just use PA.
;
; Is the Queue Buffer within the direct-DMA window?
;
	EVAX_ADDQ #QBUF$C_ADPLEN,R0,R3		; Yes, just use PA
	EVAX_CMPULE R3,SPDT$IQ_DMA_SIZE(R4),R10	; Compare to DMA window size
	BLBS	R10,57$				; Branch if LE window size
;
; O.K., we hafta map it.
;
	MAP_S0	-
		SVA = R6,-			; Queue Buffer address (VA).
		NBYTES = #QBUF$C_ADPLEN,-	; Size to map.
		CRCTX = QBUF$PS_CRCTX_PTR(R7),-	; CRCTX pointer reference.
		KPB$ = SPDT$PS_KPB(R4),-	; KPB to stall on alloc fail
		DMA_ADDR = QBUF$PQ_PA_QBUF(R7)	; Where to put DMA-able addr
	BRB	59$
;
; No mapping registers needed, just use PA (+ DMA base).
;
57$:	ADDL2	SPDT$L_DMA_BASE(R4),R0		; Add in DMA window base
	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_STQ R0,QBUF$PQ_PA_QBUF(R7)		; Save physical address

59$:	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_SET_CHAN_STATE_SIZE,-	; Setup CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_SET_CHAN_STATE,-	; Setup function code
		QBUF$IB_FUNC_CODE(R7)
	MOVB	#QBUF$C_SET_CHAN_STATE_ENB,-	; Request enabled state 
		QBUF$IB_CHN_STATE(R7)
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM status
	MOVB	SPDT$IB_PI_PATH_ID(R4),-	; Setup path id
		QBUF$IB_PATH_ID(R7)
	BISB	#QBUF$M_NO_DATA_TX,-		; Direction is no data
		QBUF$IB_CAM_FLAGS_1(R7)
	BISB	#QBUF$M_SIM_Q_FREEZE_DIS,-	; Set SIM queue freeze disable
		QBUF$IB_CAM_FLAGS_2(R7)
;
; Get a Queue Carrier and setup Carrier fields.
;
	BSBW	GET_QUEUE_CARRIER		; Get a Queue Carrier
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  Flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
;
; Send the command to the SIMport adapter. This will cause the adapter to
; return all I/O with a reset status. When the Channel State Set response
; is returned, routine CHAN_STATE_SET_RSP will continue with the reset.
;
	BSBW	INSERT_DACQ			; Insert command on DACQ
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	BRW	RSP_LOOP			; Check for more responses
;
; We received a Channel State Set Response message. Deallocate command
; resources. If the channel is now disabled, set the port offline. If the
; channel is enabled, reset the SCSI bus if necessary and set the port online.
;
CHAN_STATE_SET_RSP:
	BSBW	RET_QUEUE_CARRIER		; Return Carrier
	CMPB	QBUF$IB_CHN_STATE(R7),-		; Check channel state
		#QBUF$C_SET_CHAN_STATE_ENB
	BEQL	60$				; Is the channel enabled?
	MOVL	QBUF$PS_CRCTX_PTR(R7),R3	; Did we alloc a CRCTX?
	BEQL	594$
	PUSHL	R3				; Yep, deallocate the regs.
	PUSHL	SPDT$PS_CRAB(R4)
	CALLS	#2,G^IOC$DEALLOC_CNT_RES
	PUSHL	R3
	CALLS	#1,G^IOC$DEALLOC_CRCTX		; And deallocate the CRCTX
594$:	MOVL	R7,R0				; Get buffer address
	MOVZWL	QBUF$IW_SIZE(R0),R1		; Get buffer size
	JSB	EXE$DEANONPGDSIZ		; Deallocate command Buffer
	MOVL	SPDT$PS_KPB(R4),R8		; Get init process KPB
	MOVL	KPB$PS_UCB(R8),R5		; Get port's UCB address
	BICL	#UCB$M_ONLINE,UCB$L_STS(R5)     ; Clear UCB online
	BICL    #SPDT$M_STS_ONLINE,-		; Clear SPDT online
		SPDT$L_STS(R4)
	BSBW	PORT_SHUTDOWN			; Shut down this port
	LOG_ERROR -				; Log an error log entry
		TYPE = CTL_ERR,-		;  Controller error
		CDT  = #0			;  No associated SCDT
	BRW	RSP_LOOP			; Check for more responses
;
; The channel is now in the enabled state.  Free up the resources used
; by the Set Channel State request.  Check why the channel was
; previously disabled. If it was not on account of a SCSI bus reset, then
; reset the SCSI bus in order to resynchronize device context with the adapter.
;
60$:	MOVL	QBUF$PS_CRCTX_PTR(R7),R3	; Did we alloc a CRCTX?
	BEQL	64$
	PUSHL	R3				; Yep, deallocate the regs.
	PUSHL	SPDT$PS_CRAB(R4)
	CALLS	#2,G^IOC$DEALLOC_CNT_RES
	PUSHL	R3
	CALLS	#1,G^IOC$DEALLOC_CRCTX		; And deallocate the CRCTX.
64$:	MOVL	R7,R0				; Get buffer address
	MOVZWL	QBUF$IW_SIZE(R0),R1		; Get buffer size
	JSB	EXE$DEANONPGDSIZ		; Deallocate command Buffer
	CMPL	SPDT$IS_CHAN_DIS_STS(R4),-	; Check channel disabled status
		#SPDT$C_BUS_RESET
	BEQL	70$				; Do we need to do a bus reset?
	MOVL	SPDT$PS_KPB(R4),R8		; Yes, get init process KPB
	MOVL	KPB$PS_UCB(R8),R5		; Get port's UCB address
	BICL	#UCB$M_ONLINE,UCB$L_STS(R5)     ; Clear port UCB ONLINE
	MOVL	R4,KPB$PS_SCSI_PTR1(R8)		; Store SPDT into KPB
	KP_START  KPB = R8, -			; Call Kernel Process routine
		  REGISTERS = #PKS_KP_REGMSK, -	;  to reset the SCSI bus
		  ROUTINE   = RESET_SCSI_BUS
70$:	BICL	#SPDT$M_RESET_DETECTED,-	; Indicate reset complete
		SPDT$IS_CHANSTATE(R4)
;
; If target mode has been enabled, then we have to send a TARGET_EVENT_
; ACKNOWLEDGED command to the adapter to allow the adapter to resume
; responding to selections.
;
	BBC	#SPDT$V_TARGET_MODE,-		; Is target mode enabled?
		SPDT$IS_PORTSTS(R4),80$
	MOVL	SPDT$PS_AB(R4),R6		; Yes, get Adapter Block address
	MOVL	AB$PQ_ADFQ_HEAD(R6),R2		; Get Carrier at head of queue
	MOVL	CAR$PQ_NEXT_PTR(R2),-		; Update ADFQ head pointer
		AB$PQ_ADFQ_HEAD(R6)		;  with next Carrier address
	MOVL	CAR$PQ_QBUF_PTR(R2),R7		; Get Queue Buffer VA
;
; Setup Queue Buffer fields for the Target Event Acknowledge command.
;
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; CCB address (PA)
		QBUF$PQ_CCB_ADDR(R7)
	MOVW	#QBUF$C_TARGET_EVENT_ACK_SIZE,-	; CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB	#QBUF$C_TARGET_EVENT_ACK,-	; Function code
		QBUF$IB_FUNC_CODE(R7)
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM status
	CLRL	QBUF$IS_CONNECT_ID(R7)		; Clear Connect id

	MOVB	SPDT$IS_SCSI_ID_NUM(R4),-	; Setup target id
		QBUF$IB_TARGET_ID(R7)

	CLRL	QBUF$IS_CAM_FLAGS(R7)		; Clear CAM flags
	BISB	#QBUF$M_NO_DATA_TX,-		; Direction is no data
		QBUF$IB_CAM_FLAGS_1(R7)
	CLRL	QBUF$IS_DATA_TRAN_SIZE(R7)	; Clear data transfer length

	CLRW	QBUF$IW_TEA_SEQUENCE_ID(R7)	; Clear sequence id
	MOVB	SPDT$IS_SCSI_ID_NUM(R4),-	; Setup initiator id
		QBUF$IB_TEA_IID(R7)
	CLRB	QBUF$IB_TEA_TAG_ID(R7)		; Clear tag id
	CLRB	QBUF$IB_TEA_FLAGS(R7)		; Clear flags
	BISB	#QBUF$M_CLEAR_ALL_EVENTS,-	; Clear all events
		QBUF$IB_TEA_FLAGS(R7)
;
; Setup Carrier fields.
;
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  Flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
;
; Send the command to the SIMport adapter. This will cause the adapter to
; resume responding to selections.
;
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	BSBW	INSERT_DACQ			; Insert command on DACQ
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
80$:
	MOVL	SPDT$L_PORT_UCB(R4),R5		; Get port UCB
	BISL	#UCB$M_ONLINE,UCB$L_STS(R5)     ; Set UCB online
	BISL    #SPDT$M_STS_ONLINE,-		; Set SPDT online
		SPDT$L_STS(R4)
	BRW	RSP_LOOP			; Check for more responses
;
; We received a device disabled response message. a Set Device State Enabled
; command to the adapter. R2 = Carrier, R7 = Queue Buffer.
;
DEV_DISABLED_RSP:
	MOVL	CAR$IS_CONNECT_ID(R2),-		; Save device connection id
		SPDT$IS_DEV_CONNECT_ID(R4)
	BSBW	INSERT_DAFQ			; Return resources to free queue
;
; Allocate some pool for a Queue Buffer (CCB).
;
85$:	SUBL    #8,SP				; Allocate two lw on stack
	MOVL    SP,R2				; Store address of lw to receive
						;  address of allocated block
        ADDL3   #4,R2,R6			; Store address of lw to receive
						;  size of allocated block
        PUSHL   R6				; Arg 4 = addr to receive size
        PUSHL   R2				; Arg 3 = addr to receive addr
						;  of allocated block
	PUSHL	#ALN_LONG			; Arg 2 = 4 byte alignment
	PUSHL	#256				; Arg 1 = size of block
	CALLS   #4,EXE$ALONONPAGED_ALN		; Allocate a block
	POPL	R2				; Get addr of allocated block
	POPL	R6				; Get allocated size
	BLBS	R0,90$				; Did we get the block?
	MOVL	SPDT$PS_KPB(R4),R8		; No, get KPB address
	KP_STALL_FORK_WAIT  KPB=R8,-		; Fork and wait
		FKB=KPB$PS_UCB(R8)		;  using UCB fork block
	BRB	85$				; Try allocation again

90$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC5   #0,(SP),#0,R6,(R2)		; Zero initialize the block
	POPR	#^M<R0,R1,R2,R3,R4,R5>		; Restore registers
	MOVL    R2,R7                           ; Copy Queue Buffer pointer
;
; Set up Queue Buffer fields for the Set Device State Enabled command.
;
	MOVW	R6,QBUF$IW_SIZE(R7)		; Save Queue Buffer size
	MOVB	#DYN$C_MISC,QBUF$IB_TYPE(R7)	; Set buffer type
	MOVB	#DYN$C_QBUF,QBUF$IB_SUBTYPE(R7)	; Set buffer subtype
	MOVAB	QBUF$PQ_CCB_ADDR(R7),R6		; VA of adapter visible section
	$SVA_TO_PA   SVA = R6			; PA of adapter visible section

	BITL	#SPDT$M_PFLG_MAPPING_REG,-	; Are we using map regs?
		SPDT$L_PORT_FLAGS(R4)
	BEQL	95$				; Nope; just use PA.
;
; Is the Queue Buffer within the direct-DMA window?
;
	EVAX_ADDQ #QBUF$C_ADPLEN,R0,R3		; Yes, point to last byte in AB
	EVAX_CMPULE R3,SPDT$IQ_DMA_SIZE(R4),R10	; Compare to DMA window size
	BLBS	R10,95$				; Branch if LE window size
;
; O.K., we hafta map it.
;
	MAP_S0	-
		SVA = R6,-			; Queue Buffer address (VA).
		NBYTES = #QBUF$C_ADPLEN,-	; Size to map.
		CRCTX = QBUF$PS_CRCTX_PTR(R7),-	; CRCTX pointer reference.
		KPB$ = SPDT$PS_KPB(R4),-	; KPB to stall on alloc fail
		DMA_ADDR = QBUF$PQ_PA_QBUF(R7)	; Where to put DMA-able addr
	BRB	100$
;
; No mapping registers needed, just use PA (+ DMA base).
;
95$:	ADDL2	SPDT$L_DMA_BASE(R4),R0		; Add in DMA window base
	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_STQ R0,QBUF$PQ_PA_QBUF(R7)		; Save physical address
100$:	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_SET_DEV_STATE_SIZE,-	; Setup CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_SET_DEV_STATE,-		; Setup function code
		QBUF$IB_FUNC_CODE(R7)
	MOVB	#QBUF$C_SET_DEV_STATE_ENB,-	; Request enabled state 
		QBUF$IB_DEV_STATE(R7)
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM status
	MOVL	SPDT$IS_DEV_CONNECT_ID(R4),-	; Set up device connection id
		QBUF$IS_CONNECT_ID(R7)
	BISB	#QBUF$M_NO_DATA_TX,-		; Direction is no data
		QBUF$IB_CAM_FLAGS_1(R7)
	BISB	#QBUF$M_SIM_Q_FREEZE_DIS,-	; Set SIM queue freeze disable
		QBUF$IB_CAM_FLAGS_2(R7)
;
; Get a Queue Carrier and setup Carrier fields.
;
	BSBW	GET_QUEUE_CARRIER		; Get a Queue Carrier
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  Flags
	MOVL	SPDT$IS_DEV_CONNECT_ID(R4),-	; Set up device connection id
		CAR$IS_CONNECT_ID(R2)
;
; Send the command to the SIMport adapter.
;
	BSBW	INSERT_DACQ			; Insert command on DACQ
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	BRW	RSP_LOOP			; Check for more responses
;
; We received a Device State Set Response message. Deallocate command
; resources.
;
DEV_STATE_SET_RSP:
	BSBW	RET_QUEUE_CARRIER		; Return Carrier
	MOVL	QBUF$PS_CRCTX_PTR(R7),R3	; Did we alloc a CRCTX?
	BEQL	120$
	PUSHL	R3				; Yep, deallocate the regs.
	PUSHL	SPDT$PS_CRAB(R4)
	CALLS	#2,G^IOC$DEALLOC_CNT_RES
	PUSHL	R3
	CALLS	#1,G^IOC$DEALLOC_CRCTX		; And deallocate the CRCTX
120$:	MOVL	R7,R0				; Get buffer address
	MOVZWL	QBUF$IW_SIZE(R7),R1		; and size
	JSB	EXE$DEANONPAGED			; Deallocate command Buffer
	BRW	RSP_LOOP			; Check for more responses
;
; We received the following response messages. Currently we should never
; get here. These messages are handled at unit init time by polling on
; the ADRQ (e.g. Interrupts are not enabled). These messages are included
; here in case these routines are needed in the future.
;
ADAP_STATE_SET_RSP:
PARAMETER_SET_RSP:
	BRW	RSP_LOOP			; Check for more responses
;
; We received the following response messages. Resume the thread that was
; waiting for this response.
;
CNTRS_READ_RSP:
ADAP_SANITY_VER_RSP:
	BSBW	RET_QUEUE_CARRIER		; Return Carrier
	MOVL	QBUF$PS_SCDRP(R7),R5		; Get SCDRP address
	MOVL	SCDRP$PS_KPB(R5),R0		; Get KPB address
	PUSHL	R0				; Pass KPB address
	CALLS   #1,G^EXE$KP_RESTART		; Arrange for waiter to resume
	BRW	RSP_LOOP			; Check for more responses
;
; We received a request for another Buffer Segment Descriptor (BSD).
; PKSDRIVER currently does not support this mechanism. Enough BSDs are
; sent to the adapter for each IO request to map the entire buffer.
; Just return the resources (Carrier/Queue Buffer) to the DAFQ.
;
BSD_REQ_RSP:
	BSBW	INSERT_DAFQ			; Return resources to free queue
	BRW	RSP_LOOP			; Check for more responses
;
; We received an Accept Target IO response. If the CAM status is CDB_RECEIVED,
; determine the CDB function. We handle Inquiry and Request Sense commands.
; For all other commands, send back check condition status.
; If the CAM status is not CDB_RECEIVED, then just return this CCB to the
; adapter as a free Accept.
;
ACCEPT_TARGET_IO_RSP:
	MOVL	R2,SPDT$IS_CAR_ADDR(R4)		; DOF for debug
	MOVL	R7,SPDT$IS_QBUF_ADDR(R4)	; DOF for debug
	INCL	SPDT$IS_NUM_ACCEPTS(R4)		; Keep track of these
	CMPB	CAR$IB_STATUS(R2),-		; Check returned CAM status
		#CAR$C_SCSI_CDB_REC
	BEQL	125$				; Is it CDB received?
;
; The CAM status is not CDB_RECEIVED. Just return this CCB to the adapter.
;
	MOVW	#QBUF$C_ACCEPT_TARGET_IO_SIZE,- ; CCB length
		QBUF$IW_CCB_LENGTH(R7)
	MOVB	#QBUF$C_ACCEPT_TARGET_IO,-	; Queue Buffer function code
		QBUF$IB_FUNC_CODE(R7)
	CLRL	QBUF$IS_CAM_FLAGS(R7)		; Clear CAM flags
	BISB	#QBUF$M_DISCONS_MANDATORY,-	; Discon for each CCB for LUN
		QBUF$IB_CAM_FLAGS_4(R7)
	CLRB	QBUF$IB_SCSI_STS(R7)		; Send back good SCSI status
	CLRB	QBUF$IB_CTIO_SCSI_STAT(R7)	;  in Private Data Area also
;
; Return the free Accept CCB to the SIMport adapter.
; 
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	BSBW	INSERT_DACQ			; Insert command on DACQ
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	BRW	RSP_LOOP			; Check for more responses

125$:	CMPB	QBUF$IB_CDB+CDB$IB_OPCODE(R7),-	; Check CDB op code
		#SCSI$K_INQUIRY
	BEQL	130$				; Is this an Inquiry command?
	CMPB	QBUF$IB_CDB+CDB$IB_OPCODE(R7),-	; No, check for Request Sense
		#SCSI$K_REQ_SENSE
	BEQL	140$				; Is this a Req Sense command?
;
; This is an invalid command. Send back Check Condition status.
;
	MOVW	#QBUF$C_CONTINUE_TARGET_IO_SIZE,- ; CCB length
		QBUF$IW_CCB_LENGTH(R7)
	MOVB	#QBUF$C_CONT_TARGET_IO,-	; Queue Buffer function code
		QBUF$IB_FUNC_CODE(R7)
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM status
	MOVL	CAR$IS_CONNECT_ID(R2),-		; Set up connect id
		QBUF$IS_CONNECT_ID(R7)
	CLRL	QBUF$IS_CAM_FLAGS(R7)		; Clear CAM flags
	BISB	#QBUF$M_NO_DATA_TX,-		; Direction is no data
		QBUF$IB_CAM_FLAGS_1(R7)
	BISB	#QBUF$M_SEND_STATUS,-		; Send status after data phase
		QBUF$IB_CAM_FLAGS_4(R7)
	CLRL	QBUF$IS_DATA_TRAN_SIZE(R7)	; Clear data transfer length
	MOVB	#SCSI$C_CHECK_CONDITION,-	; Return check Condition status
		QBUF$IB_SCSI_STS(R7)
	MOVB	QBUF$IB_ATIO_INIT_ID(R7),-	; Setup initiator id
		QBUF$IB_INITIATOR_ID(R7)
	MOVB	#SCSI$C_CHECK_CONDITION,-	; Return Check Condition status
		QBUF$IB_CTIO_SCSI_STAT(R7)	;  in Private Data Area also
;
; Send the CONTINUE_TARGET_IO command to the SIMport adapter.
; 
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	BSBW	INSERT_DACQ			; Insert command on DACQ
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	BRW	RSP_LOOP			; Check for more responses
;
; This is an Inquiry command. Send back Inquiry data.
;
130$:	MOVW	#QBUF$C_CONTINUE_TARGET_IO_SIZE,- ; CCB length
		QBUF$IW_CCB_LENGTH(R7)
	MOVB	#QBUF$C_CONT_TARGET_IO,-	; Queue Buffer function code
		QBUF$IB_FUNC_CODE(R7)
	BISB	#QBUF$M_SEND_STATUS,-		; Send status after data phase
		QBUF$IB_CAM_FLAGS_4(R7)
	MOVB	QBUF$IB_ATIO_INIT_ID(R7),-	; Setup initiator id
		QBUF$IB_INITIATOR_ID(R7)
	CLRB	QBUF$IB_SCSI_STS(R7)		; Send back good SCSI status
	CLRB	QBUF$IB_CTIO_SCSI_STAT(R7)	;  in Private Data Area also
;
; Build a BSD to point to the Inquiry data. Check Inquiry allocation
; length in CDB. Only return up to the allocated or standard length,
; whichever is smaller.
;
	TSTB	QBUF$IB_CDB+CDB$IB_ALLOC_LENGTH(R7) ; Check requested alloc length
	BNEQ	134$				; Is it non-zero?
	BISB	#QBUF$M_NO_DATA_TX,-		; No, direction is no data
		QBUF$IB_CAM_FLAGS_1(R7)
	CLRQ	QBUF$PS_CTIO_DATA_BSD1(R7)	; Clear BSD
	CLRL	QBUF$IS_DATA_TRAN_SIZE(R7)	; Clear data transfer length
	BRB	138$				; Join common code
134$:	CMPB	QBUF$IB_CDB+CDB$IB_ALLOC_LENGTH(R7),- ; Check requested alloc
		#INQ$C_DATA_LENGTH		   ;  length to our length
	BLSSU	135$				; Are they requesting more?
	MOVL	#INQ$C_DATA_LENGTH,R9		; Yes, send back our length
	BRB	137$				; Join common code
135$:	MOVZBL	QBUF$IB_CDB+CDB$IB_ALLOC_LENGTH(R7),R9; Send back what they
						   ;  are requesting
137$:	MOVL	SPDT$PS_INQUIRY_DATA(R4),R6	; Point to Inquiry data
	ADDL	#INQ$C_HEADER_LENGTH,R6		; Point past header
	BUILD_BSD -				; Build BSD for Inquiry data
		BSD_ADDR   = QBUF$PS_CTIO_DATA_BSD1(R7),-
		BUFFER_SVA = R6,-
		BYTE_COUNT = R9
	BISB	#QBUF$M_WRITE,-			; Data direction is out
		QBUF$IB_CAM_FLAGS_1(R7)
	MOVL	R9,QBUF$IS_DATA_TRAN_SIZE(R7)	; Setup data transfer length
;
; Send the CONTINUE_TARGET_IO command to the SIMport adapter.
; 
138$:	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	BSBW	INSERT_DACQ			; Insert command on DACQ

	MOVB	#CAR$C_SUCCESS,-		; Send back good status
		CAR$IB_STATUS(R1)

	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	BRW	RSP_LOOP			; Check for more responses
;
; This is a Request Sense command. Send back Sense data.
;
140$:	MOVW	#QBUF$C_CONTINUE_TARGET_IO_SIZE,- ; CCB length
		QBUF$IW_CCB_LENGTH(R7)
	MOVB	#QBUF$C_CONT_TARGET_IO,-	; Queue Buffer function code
		QBUF$IB_FUNC_CODE(R7)
	BISB	#QBUF$M_SEND_STATUS,-		; Send status after data phase
		QBUF$IB_CAM_FLAGS_4(R7)
	MOVB	QBUF$IB_ATIO_INIT_ID(R7),-	; Setup initiator id
		QBUF$IB_INITIATOR_ID(R7)
	CLRB	QBUF$IB_SCSI_STS(R7)		; Send back good SCSI status
	CLRB	QBUF$IB_CTIO_SCSI_STAT(R7)	;  in Private Data Area also
;
; Build a BSD to point to the Sense data. Check Sense allocation
; length in CDB. Only return up to the allocated or standard length,
; whichever is smaller.
;
	TSTB	QBUF$IB_CDB+CDB$IB_ALLOC_LENGTH(R7) ; Check requested alloc length
	BNEQ	144$				; Is it non-zero?
	BISB	#QBUF$M_NO_DATA_TX,-		; No, direction is no data
		QBUF$IB_CAM_FLAGS_1(R7)
	CLRQ	QBUF$PS_CTIO_DATA_BSD1(R7)	; Clear BSD
	CLRL	QBUF$IS_DATA_TRAN_SIZE(R7)	; Clear data transfer length
	BRB	148$				; Join common code
144$:	CMPB	QBUF$IB_CDB+CDB$IB_ALLOC_LENGTH(R7),- ; Check requested len
		#SENSE$C_DATA_LENGTH		   ;  to our length
	BLSSU	145$				; Are they requesting more?
	MOVL	#SENSE$C_DATA_LENGTH,R9		; Yes, send back our length
	BRB	147$				; Join common code
145$:	MOVZBL	QBUF$IB_CDB+CDB$IB_ALLOC_LENGTH(R7),R9; Send back what they
						   ;  are requesting
147$:	MOVL	SPDT$PS_SENSE_DATA(R4),R6	; Point to Sense data
	ADDL	#SENSE$C_HEADER_LENGTH,R6	; Point past header
	BUILD_BSD -				; Build BSD for Sense data
		BSD_ADDR   = QBUF$PS_CTIO_DATA_BSD1(R7),-
		BUFFER_SVA = R6,-
		BYTE_COUNT = R9
	BISB	#QBUF$M_WRITE,-			; Data direction is out
		QBUF$IB_CAM_FLAGS_1(R7)
	MOVL	R9,QBUF$IS_DATA_TRAN_SIZE(R7)	; Setup data transfer length
;
; Send the CONTINUE_TARGET_IO command to the SIMport adapter.
; 
148$:	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	BSBW	INSERT_DACQ			; Insert command on DACQ

	MOVB	#CAR$C_SUCCESS,-		; Send back good status
		CAR$IB_STATUS(R1)

	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	BRW	RSP_LOOP			; Check for more responses
;
; We received a Continue Target IO response. Change the CCB into an Accept
; and return it to the adapter.
;
CONT_TARGET_IO_RSP:
	MOVL	R2,SPDT$IS_CAR_ADDR(R4)		; DOF for debug
	MOVL	R7,SPDT$IS_QBUF_ADDR(R4)	; DOF for debug
	INCL	SPDT$IS_NUM_CONTINUES(R4)	; Keep track of these
	MOVW	#QBUF$C_ACCEPT_TARGET_IO_SIZE,- ; CCB length
		QBUF$IW_CCB_LENGTH(R7)
	MOVB	#QBUF$C_ACCEPT_TARGET_IO,-	; Queue Buffer function code
		QBUF$IB_FUNC_CODE(R7)
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM status
	MOVB	SPDT$IS_SCSI_ID_NUM(R4),-	; Setup target id
		QBUF$IB_TARGET_ID(R7)
	CLRL	QBUF$IS_CAM_FLAGS(R7)		; Clear CAM flags
	BISB	#QBUF$M_DISCONS_MANDATORY,-	; Discon for each CCB for LUN
		QBUF$IB_CAM_FLAGS_4(R7)
	CLRB	QBUF$IB_SCSI_STS(R7)		; Send back good SCSI status
	CLRB	QBUF$IB_ATIO_SCSI_STAT(R7)	;  in Private Data Area also
;
; Send the ACCEPT_TARGET_IO command to the SIMport adapter.
; 
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	BSBW	INSERT_DACQ			; Insert command on DACQ
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	BRW	RSP_LOOP			; Check for more responses
;
; We received a TARGET_EVENT response. Send a SET_TARGET_STATE message
; to the adapter.
;
TARGET_EVENT_RSP:
	INCL	SPDT$IS_NUM_TARGET_EVENTS(R4)	; Keep track of these
	MOVL	QBUF$IW_TE_SEQUENCE_ID(R7),-	; Save Target Event sequence id
		SPDT$IS_TARGET_EVENT(R4)
	MOVB	QBUF$IB_TE_FLAGS(R7),-		; Save Target Event flags
		SPDT$IB_TARGET_FLAGS(R4)
	BSBW	INSERT_DAFQ			; Return resources to free queue
	MOVL	SPDT$PS_AB(R4),R6		; Get Adapter Block address
	MOVL	AB$PQ_ADFQ_HEAD(R6),R2		; Get Carrier at head of queue
	MOVL	CAR$PQ_NEXT_PTR(R2),-		; Update ADFQ head pointer
		AB$PQ_ADFQ_HEAD(R6)		;  with next Carrier address
	MOVL	CAR$PQ_QBUF_PTR(R2),R7		; Get Queue Buffer VA
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; CCB address (PA)
		QBUF$PQ_CCB_ADDR(R7)
	MOVW	#QBUF$C_TARGET_STATE_SIZE,-	; CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB	#QBUF$C_SET_TARGET_STATE,-	; Function code
		QBUF$IB_FUNC_CODE(R7)
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM status
	CLRL	QBUF$IS_CONNECT_ID(R7)		; Clear Connect id
	MOVB	SPDT$IS_SCSI_ID_NUM(R4),-	; Setup target id
		QBUF$IB_TARGET_ID(R7)
	CLRL	QBUF$IS_CAM_FLAGS(R7)		; Clear CAM flags
	BISB	#QBUF$M_NO_DATA_TX,-		; Direction is no data
		QBUF$IB_CAM_FLAGS_1(R7)
	CLRL	QBUF$IS_DATA_TRAN_SIZE(R7)	; Clear data transfer length
	MOVL	SPDT$IS_TARGET_EVENT(R4),-	; Setup sequence id, IID, Tag ID
		QBUF$IW_TS_SEQUENCE_ID(R7)
	MOVB	SPDT$IB_TARGET_FLAGS(R4),-	; Setup Target Event flags
		QBUF$IB_TS_FLAGS(R7)
;
; Setup Carrier fields.
;
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  Flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
;
; Send the SET_TARGET_STATE command to the SIMport adapter.
;
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	BSBW	INSERT_DACQ			; Insert command on DACQ
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	BRW	RSP_LOOP			; Check for more responses
;
; We received a TARGET_STATE_SET response message.
; Send a TARGET_EVENT_ACKNOWLEDGE command to the adapter.
;
TARGET_STATE_SET_RSP:
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; CCB address (PA)
		QBUF$PQ_CCB_ADDR(R7)
	MOVW	#QBUF$C_TARGET_EVENT_ACK_SIZE,-	; CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB	#QBUF$C_TARGET_EVENT_ACK,-	; Function code
		QBUF$IB_FUNC_CODE(R7)
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM status
	CLRL	QBUF$IS_CONNECT_ID(R7)		; Clear Connect id
	MOVB	SPDT$IS_SCSI_ID_NUM(R4),-	; Setup target id
		QBUF$IB_TARGET_ID(R7)
	CLRL	QBUF$IS_CAM_FLAGS(R7)		; Clear CAM flags
	BISB	#QBUF$M_NO_DATA_TX,-		; Direction is no data
		QBUF$IB_CAM_FLAGS_1(R7)
	CLRL	QBUF$IS_DATA_TRAN_SIZE(R7)	; Clear data transfer length

	CLRW	QBUF$IW_TEA_SEQUENCE_ID(R7)	; Clear sequence id
	MOVB	SPDT$IS_SCSI_ID_NUM(R4),-	; Setup initiator id
		QBUF$IB_TEA_IID(R7)
	CLRB	QBUF$IB_TEA_TAG_ID(R7)		; Clear tag id
	CLRB	QBUF$IB_TEA_FLAGS(R7)		; Clear flags
	BISB	#QBUF$M_CLEAR_ALL_EVENTS,-	; Clear all events
		QBUF$IB_TEA_FLAGS(R7)
;
; Setup Carrier fields.
;
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  Flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
;
; Send the TARGET_EVENT_ACKNOWLEDGED command to the SIMport adapter.
; This will cause the adapter to resume responding to selections.
;
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)		; Clear Q_Buffer virtual addr
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual addr
	BSBW	INSERT_DACQ			; Insert command on DACQ
	DEVICELOCK -				; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-	;  to prevent interrupts
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	WRITE_CSR REG  = DACQIR,-		; Give DACQ head PA to adapter
		  DATA = R9,-
		  TYPE = L
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	BRW	RSP_LOOP			; Check for more responses
;
; Now that all of the responses have been handled, make sure that there are
; no other responses on the ADRQ. If none, see if an adapter error interrupt
; has occurred. If so, log the error, and reinitialize the adapter.
;
CHECK_ADAPTER_ERROR:
	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	MOVL	AB$PQ_ADRQ_HEAD(R6),R2		; Get next response Carrier
	BBS	#0,CAR$PQ_NEXT_PTR(R2),200$	; Is response queue empty?
	DEVICEUNLOCK -				; No, unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO
	BRW	RSP_LOOP			; Handle remaining responses
;
; The response queue is empty. 
;
200$:	MOVL	SPDT$L_PORT_UCB(R4),R5		; Get UCB address
	CLRL	UCB$L_FPC(R5)			; Clear fork thread semaphore
	MOVL	SPDT$IS_SAVE_ASR(R4),R9		; Get copy of ASR
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO

	.BRANCH_UNLIKELY
	BBC	#TZA_ASR$V_ASIC,R9,-		; If ASIC bit clear then an
		PKS$ADP_ERROR_INTERRUPT		;  error interrupt ocurred

	RET					; Return to fork dispatcher
	.DISABLE LSB				; PKS$FORK

.PAGE
	.SBTTL	RESET_SCSI_BUS - Reset the SCSI Bus
;
; This routine is called to perform a SCSI bus reset. It creates a fake
; connection to set up required data structures, calls the SCSI bus reset
; routine, and clears the fake connection.
;
; This routine assumes that it is running in a Kernel Process context.
;
; INPUT:
;       kpb(AP) = KPB address
;
; OUTPUT: None.
;
	.ENABLE LSB

        $ARG_DEF        <-
        kpb	         >       		; KPB address.

RESET_SCSI_BUS: 
	.CALL_ENTRY	<R2,R3,R4,R5,R6,R7,R8,R9,R10,R11>

	MOVL	kpb(AP),R8			; Get the KPB address
	MOVL	KPB$PS_UCB(R8),R5		; Get port's UCB address
	MOVL	UCB$L_PDT(R5),R4		; Get the port's SPDT address
	MOVL	R4,KPB$PS_SCSI_PTR1(R8)		; Store SPDT into KPB
	BICL2	#UCB$M_ONLINE,UCB$L_STS(R5)     ; Clear port UCB ONLINE
	BICL2	#SPDT$M_STS_ONLINE,-		; Clear port SPDT ONLINE
		SPDT$L_STS(R4)
	PUSHL	KPB$PS_SCSI_SCDRP(R8)		; Save SCDRP from KPB
	BSBW	GET_SCDRP			; Allocate the SCDRP
	MOVL	R2,R5				; Save the SCDRP
	MOVL	R2,KPB$PS_SCSI_SCDRP(R8)	; Save SCDRP in KPB
	MOVL	R8,SCDRP$PS_KPB(R5)		; Store KPB into SCDRP
	MOVL	R4,SCDRP$PS_SPDT(R5)		; Store SPDT into SCDRP
	BICL	#SPDT$M_INIT_ADAPTER,-		; Do not reinit the adapter
		SPDT$IS_PORTSTS(R4)
	BISL	#SPDT$M_LOG_ERROR,-		; Log this in error log
		SPDT$IS_PORTSTS(R4)
	PUSHL	R5				; Pass SCDRP
	PUSHL	R4				; Pass SPDT
	CALLS	#2,@SPDT$PS_CD_RESET_SCSI_BUS(R4) ; Reset SCSI bus
	MOVL	R5,R0				; Deallocate the SCDRP
	CALL_DRVDEALMEM	SAVE_R0R1=NO		; COM_STD$DRVDEALMEM
	POPL	KPB$PS_SCSI_SCDRP(R8)		; Clear the SCDRP pointer
	RET					; Return
	.DISABLE LSB				; RESET_SCSI_BUS

.PAGE
	.SBTTL  PKS$ADP_ERROR_INTERRUPT - Adapter error interrupt routine
;
; This routine handles all adapter-wide error interrupts.
; Adapter error interrupts are indicated by one of the following bits set in
; the adapter status register (ASR):
;
;	ADSE - Host data structure error
;	AMSE - Host memory system error
;	AAC  - Abnormal condition in adapter firmware
;	AME  - Adapter maintenance (hardware) error
;
; Adapter errors cause the adapter to go into the "uninitialized" state.
; The port driver will do the following:
;
; 1.) Save necessary registers.
; 2.) Log the adapter error in the system error log.
; 3.) Re-initialize the adapter.
; 
; CALLED BY:  PKS$FORK
;
; INPUT:
;	R3 = IDB address
;	R4 = SPDT address
;	R5 = UCB address
;	IPL$_IOLOCK8
;
; OUTPUT:
;	None
;
	.ENABLE	LSB
PKS$ADP_ERROR_INTERRUPT:

	.IF DEFINED PKSDEBUG
	MOVAB	MISC_INTR_MSG,R1		; Get addr of msg to output
	JSB	G^EXE$OUTZSTRING		; Send the message
	.ENDC
;
; Save necessary registers.
;
	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	READ_CSR REG  = ASR,     DEST = SPDT$IS_SAVE_ASR(R4)
	READ_CSR REG  = AFAR,    DEST = SPDT$IS_SAVE_AFAR(R4)
	READ_CSR REG  = AFPR,    DEST = SPDT$IS_SAVE_AFPR(R4)
	READ_CSR REG  = ERRLOG1, DEST = SPDT$IS_SAVE_ERRLOG1(R4)
	READ_CSR REG  = ERRLOG2, DEST = SPDT$IS_SAVE_ERRLOG2(R4)
	READ_CSR REG  = ERRLOG3, DEST = SPDT$IS_SAVE_ERRLOG3(R4)
	READ_CSR REG  = ERRLOG4, DEST = SPDT$IS_SAVE_ERRLOG4(R4)
	READ_CSR REG  = ERRLOG5, DEST = SPDT$IS_SAVE_ERRLOG5(R4)
	READ_CSR REG  = ERRLOG6, DEST = SPDT$IS_SAVE_ERRLOG6(R4)
	READ_CSR REG  = ERRLOG7, DEST = SPDT$IS_SAVE_ERRLOG7(R4)
	READ_CSR REG  = ERRLOG8, DEST = SPDT$IS_SAVE_ERRLOG8(R4)
	READ_CSR REG  = ERRLOG9, DEST = SPDT$IS_SAVE_ERRLOG9(R4)
	READ_CSR REG  = ERRLOGA, DEST = SPDT$IS_SAVE_ERRLOGA(R4)
	DEVICEUNLOCK -                          ; Release device lock
                LOCKADDR = SPDT$L_DLCK(R4),-
                NEWIPL   = (SP)+,-
                PRESERVE = NO
;
; The SIMport adapter returns the physical address of host memory associated
; with ASR errors. Convert the physical address to a virtual address for
; error logging.
;
	MOVL	SPDT$IS_SAVE_AFAR(R4),R6	; AFAR register PA
;;	$PA_TO_SVA  PA=R6			; Convert to VA (how?)
	MOVL	R0,SPDT$IS_SAVE_AFAR(R4)	; AFAR register VA
;
; Determine the cause of this interrupt. Set up the error type and subtype
; field for error logging. SPDT$IW_ERL_TYPE = SUBTYPE!TYPE.
; Log the adapter error in the system error log.
;
	BBS	#TZA_ASR$V_ADSE,-		; Host data structure error
		SPDT$IS_SAVE_ASR(R4),10$
	BBS	#TZA_ASR$V_AMSE,-		; Host memory system error
		SPDT$IS_SAVE_ASR(R4),20$
	BBS	#TZA_ASR$V_AAC,-		; Adapter FW error
		SPDT$IS_SAVE_ASR(R4),30$
	BBS	#TZA_ASR$V_AME,-		; Adapter maintenance error
		SPDT$IS_SAVE_ASR(R4),40$
	MOVW	#SPO$C_UNKNOWN_ERR,-		; We should not get here
		SPDT$IW_ERL_TYPE(R4)
	BRB	50$

10$:	INSV	#SPO$C_ADSE,-			; Set up error subtype
		#8,#8,SPDT$IW_ERL_TYPE(R4)
	BRW	50$				; Log the error
20$:	INSV	#SPO$C_AMSE,-			; Set up error subtype
		#8,#8,SPDT$IW_ERL_TYPE(R4)
	BRW	50$				; Log the error
30$:	INSV	#SPO$C_AAC,-			; Set up error subtype
		#8,#8,SPDT$IW_ERL_TYPE(R4)
	BRW	50$				; Log the error
40$:	INSV	#SPO$C_AME,-			; Set up error subtype
		#8,#8,SPDT$IW_ERL_TYPE(R4)
50$:	MOVB	#SCSI$C_CTL_ERR,-		; Set up error type
		SPDT$IW_ERL_TYPE(R4)
	LOG_ERROR -				; Log error in error log
		TYPE = CTL_ERR,-
		CDT  = #0
;
; Re-initialize the adapter.
;
	BICL	#UCB$M_ONLINE,UCB$L_STS(R5)     ; Clear port UCB online
	BICL	#SPDT$M_STS_ONLINE,-		; Clear channel online bit
		SPDT$L_STS(R4)
	CLRL	SPDT$IS_CHANSTATE(R4)		; Channel state is UNIN

	MOVL	SPDT$PS_KPB(R4),R8		; Get KPB address
	BBS	#KPB$V_VALID,KPB$IS_FLAGS(R8),-	; If REINIT_PORT kernel
		60$				;  process is running now, the
						;  adapter reset condition will
						;  be picked up later
	MOVL	SPDT$L_ADP(R4),R1		; Get ADP address
	BICL	#SPDT$M_PORTONLY,-		; Clear flag so that we will
		SPDT$IS_PORTSTS(R4)		;  reinit the adapter
	MOVL	SPDT$L_PORT_UCB(R4),-		; Set the port's UCB address
		KPB$PS_UCB(R8)			;  in KPB
	KP_START  KPB       = R8, -		; Call Kernel Process routine
		  REGISTERS = #PKS_KP_REGMSK, -	;  to reinit the adapter
		  ROUTINE   = REINIT_PORT
60$:	RET					; Return
	.DISABLE LSB				; PKS$ADP_ERROR_INTERRUPT

.PAGE
	.SBTTL	PKS_REGDUMP - Register dump routine
;
; This routine logs information about the current command and port counters
; when various error conditions are detected. If this is an adapter
; miscellaneous error, SIMport register contents will be logged as well.
;
; CALLED BY:
;	error logging routine (ERL$DEVICEATTN) in ERRORLOG
;
; INPUT:
;	R0 = Address of the buffer to fill
;	R4 = Address of SPDT
;		SPDT$IW_ERL_TYPE = error code, SUBTYPE!TYPE
;		SPDT$PS_ERL_SCDT = SCDT address if non-zero
;		SPDT$PS_COUNTERS = Pointer to port counter block in AB
;	R5 = Address of UCB
;
; OUTPUT:
;	None
;
	.ENABLE LSB
PKS_REGDUMP:
	$DRIVER_REGDUMP_ENTRY -
		PRESERVE=<R1,R2,R3,R4,R5,R6,R7,R8,R9,R10>,FETCH=YES

	MOVL	R0,R9				; Use R9 for address of the
						;  error log buffer 
	MOVZWL	SPDT$IW_ERL_TYPE(R4),R7		; Get error code
	MOVL	SPDT$PS_ERL_SCDT(R4),R8		; Get SCDT address
	PUSHAL  (R9)+				; Save buffer address
	MOVB    #PKS_ERROR_REV,(R9)+		; Save revision level
	MOVW	R7,(R9)+			; Save error type and subtype
	TSTL    R8				; Any CDT address?
	BEQL	5$				; Branch if not
	MOVL	SCDT$PS_STDT(R8),R3		; Get STDT address
	TSTL    R3				; Any STDT address?
	BEQL	5$				; Branch if so
	MOVB	STDT$IS_SCSI_ID_NUM(R3),(R9)+	; Save target SCSI ID 
	BRB	10$
5$:	MOVB	SPDT$IS_SCSI_ID_NUM(R4),(R9)+	; No STDT. Use adapter id
;
; Save information about the current SCSI command including the command,
; message, and status bytes if SCDT address was passed as a parameter.	
;
10$:	CLRW    (R9)+				; Assume no SCSI CMD,MSG
	MOVB    #^XFF,(R9)+			; Assume no SCSI STS byte
	TSTL    R8				; Any CDT?
	BEQL    30$				; Branch if not
	MOVL    SPDT$PS_CHIP_KPB(R4),R3		; Get the KPB address
	BEQL    30$				; Branch if not
	MOVL	KPB$PS_SCSI_SCDRP(R3),R3	; Get the SCDRP address
	BEQL    30$				; Branch if none
	MOVL    SCDRP$L_CMD_PTR(R3),R1		; Get address of SCSI command
	BEQL    30$				; Branch if none
	SUBL    #3,R9				; Back up ptr into errlog buffer
	MOVL    (R1)+,R2			; Get command byte count
	MOVB    R2,(R9)+			; Save command byte count
20$:	MOVB    (R1)+,(R9)+			; Save a byte of command	
	SOBGTR  R2,20$				; Repeat for entire command
	CLRB    (R9)+				; Assume no message byte since
						;  adap doesn't return MSG byte
	MOVB    @SCDRP$L_STS_PTR(R3),(R9)+	; Save status byte
;
; Save the path inquiry information that was returned from the adapter
; during initialization.
;
30$:	MOVAB	SPDT$IB_PI_DATA(R4),R2		; Get start addr of PI data
	PUSHR   #^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC3   #SPDT$C_PI_DATA_SIZE,(R2),(R9)	; Copy path inquiry data into
						;  error log buffer
	MOVL	R3,R9				; Point past inquiry data
	POPR    #^M<R0,R1,R2,R3,R4,R5>		; Restore registers
;
; Save the contents of the port error counters.
;
	MOVB	#AB$C_NUM_PORT_CNTRS,(R9)+	; Save number of port counters
	MOVZBL	#AB$C_NUM_PORT_CNTRS,R6		; Number of port error counters
	MOVL	SPDT$PS_COUNTERS(R4),R7		; Point to first error counter
35$:	MOVL	(R7)+,(R9)+			; Save next port counter
	SOBGTR	R6,35$				;  until all counters are saved
;
; Save the contents of the SIMport registers.
;
	DEVICELOCK -                            ; Get device lock and raise IPL
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	READ_CSR REG  = ASR,DEST = (R9)+	; Save ASR register
	READ_CSR REG  = AFAR,DEST = (R9)+	; Save AFAR register
	READ_CSR REG  = AFPR,DEST = (R9)+	; Save AFPR register
	READ_CSR REG  = ERRLOG1,DEST = (R9)+	; Save error log1 register
	READ_CSR REG  = ERRLOG2,DEST = (R9)+	; Save error log2 register
	READ_CSR REG  = ERRLOG3,DEST = (R9)+	; Save error log3 register
	READ_CSR REG  = ERRLOG4,DEST = (R9)+	; Save error log4 register
	READ_CSR REG  = ERRLOG5,DEST = (R9)+	; Save error log5 register
	READ_CSR REG  = ERRLOG6,DEST = (R9)+	; Save error log6 register
	READ_CSR REG  = ERRLOG7,DEST = (R9)+	; Save error log7 register
	READ_CSR REG  = ERRLOG8,DEST = (R9)+	; Save error log8 register
	READ_CSR REG  = ERRLOG9,DEST = (R9)+	; Save error log9 register
	READ_CSR REG  = ERRLOGA,DEST = (R9)+	; Save error logA register

	DEVICEUNLOCK -                          ; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),-
		NEWIPL   = (SP)+,-
		PRESERVE = NO

50$:	SUBL    (SP),R9				; Get number of bytes saved
	ADDL    #3,R9				; Round up and translate to lw
	ASHL    #-2,R9,@(SP)+			; Save in top of errlog buffer
	MOVL	R9,R0				; Copy current pos. in buffer

	RET					; Return
	.DISABLE LSB				; PKS_REG_DUMP

.PAGE
	.SBTTL  SETUP_QUEUES - Set up the queues in the Adapter Block
;
; This subroutine initializes the adapter queue headers in the Adapter Block
; by placing Stopper Queue Carriers on all queues in the AB.
; 
; Note: The virtual address of a Carrier points to the start of the
;	Carrier. This allows the Carrier offsets to be correct.
;	The physical address of a Carrier points to the start of the
;	adapter visible section of the Carrier at offset CAR$PQ_NEXT_PTR
;	(which is not the beginning of the Carrier).
;
; This routine should be called only during adapter initialization when the
; SIMport channel is NOT in the ENABLED state.
;
; This routine assumes that it is running in a Kernel Process context.
;
; INPUT:
;	R4 = SPDT address
;	R8 = KPB address of the initialization sequence
;
; OUTPUT:
;	R0 = Status
;	All queue headers are initialized with Stopper Queue Carriers.
;
.ENABLE LSB
SETUP_QUEUES:
	.JSB_ENTRY	INPUT=<R4,R8>, SCRATCH=<R1>,-
			OUTPUT=<R0>, PRESERVE=<R2,R3,R4,R5,R6,R8>

	MOVL	SPDT$PS_AB(R4),R5		; Get Adapter Block address
;
; Set up and insert a Stopper Carrier on the DACQ queue.
;
	BSBW	GET_QUEUE_CARRIER		; Get a Carrier
						;  R2 = VA of allocated Carrier
	MOVL	R2,AB$PQ_DACQ_TAIL(R5)		; Store Stopper Carrier VA in
						;  DACQ tail
.Disable Flagging
	MOVQ	CAR$PQ_PA(R2),-			; Store Stopper Carrier PA in
		AB$PQ_DACQ_HEAD(R5)		;  DACQ head
.Enable Flagging
;
; Set up and insert a Stopper Carrier on the ADRQ queue.
;
	BSBW	GET_QUEUE_CARRIER		; Get a Carrier
						;  R2 = VA of allocated Carrier
	MOVL	R2,AB$PQ_ADRQ_HEAD(R5)		; Store Stopper Carrier VA in
						;  ADRQ head
.Disable Flagging
	MOVQ	CAR$PQ_PA(R2),-			; Store Stopper Carrier PA in
		AB$PQ_ADRQ_TAIL(R5)		;  ADRQ tail
.Enable Flagging
	EVAX_OR	 CAR$PQ_NEXT_PTR(R2),#1,-	; Make this a Stopper Carrier
		 CAR$PQ_NEXT_PTR(R2)
;
; Set up and insert a Stopper Carrier on the DAFQ queue.
;
	BSBW	GET_QUEUE_CARRIER		; Get a Carrier
						;  R2 = VA of allocated Carrier
	MOVL	R2,AB$PQ_DAFQ_TAIL(R5)		; Store Stopper Carrier VA in
						;  DAFQ tail
.Disable Flagging
	MOVQ	CAR$PQ_PA(R2),-			; Store Stopper Carrier PA in
		AB$PQ_DAFQ_HEAD(R5)		;  DAFQ head
.Enable Flagging
;
; Set up and insert a Stopper Carrier on the ADFQ queue.
;
	BSBW	GET_QUEUE_CARRIER		; Get a Carrier
						;  R2 = VA of allocated Carrier
	MOVL	R2,AB$PQ_ADFQ_HEAD(R5)		; Store Stopper Carrier VA in
						;  ADFQ head
.Disable Flagging
	MOVQ	CAR$PQ_PA(R2),-			; Store Stopper Carrier PA in
		AB$PQ_ADFQ_TAIL(R5)		;  ADFQ tail
.Enable Flagging
	EVAX_OR	 CAR$PQ_NEXT_PTR(R2),#1,-	; Make this a Stopper Carrier
		 CAR$PQ_NEXT_PTR(R2)

	MOVZWL	#SS$_NORMAL,R0			; Success return status
	RSB					; Return
	.DISABLE LSB				; SETUP_QUEUES

.PAGE
	.SBTTL	SETUP_DAFQ - Set up Driver Adapter Free Queue entries 
;
; This subroutine is called to set up free queue entries in the DAFQ
; (Driver Adapter Free Queue). It is called from the REINIT_PORT routine after
; the adapter/channel has transitioned to the ENABLED state.
;
; It allocates Q_Buffers and Carriers for the DAFQ (Driver Adapter Free
; Queue) and writes the physical address of the Stopper Carrier on the DAFQ
; to the DAFQIR (Driver Adapter Free Queue Insertion Register) so that the
; adapter can use the free queue entries if needed.
;
; Note: The virtual address of a Carrier points to the start of the
;	Carrier. This allows the Carrier offsets to be correct.
;	The physical address of a Carrier points to the start of the
;	adapter visible section of the Carrier at offset CAR$PQ_NEXT_PTR
;	(which is not the beginning of the Carrier).
;
; This routine assumes that it is running in a Kernel Process context.
;
; INPUT:
;       R4 = SPDT address
;	R6 = Number of free queue entries to insert onto DAFQ
;	R8 = KPB address
;
; OUTPUT:
;       R0 = SS$_NORMAL Status
;
	.ENABLE LSB
SETUP_DAFQ:
	.JSB_ENTRY      INPUT=<R4,R6,R8>, SCRATCH=<R1>,-
			OUTPUT=<R0>, PRESERVE=<R2,R3,R4,R5,R6,R7,R8,R9>
;
; Allocate some pool for a buffer and zero the block.
;
10$:	SUBL    #8,SP				; Allocate two lw on stack
	MOVL    SP,R2				; Store address of lw to receive
						;  address of allocated block
        ADDL3   #4,R2,R9			; Store address of lw to receive
						;  size of allocated block
        PUSHL   R9				; Arg 4 = addr to receive size
        PUSHL   R2				; Arg 3 = addr to receive addr
						;  of allocated block
	PUSHL	#ALN_LONG			; Arg 2 = 4 byte alignment
	PUSHL	#256				; Arg 1 = size of block
	CALLS   #4,EXE$ALONONPAGED_ALN		; Allocate a block
	POPL	R2				; Get addr of allocated block
	POPL	R9				; Get allocated size
	BLBS	R0,20$				; Did we get the block?
	KP_STALL_FORK_WAIT  KPB=R8,-		; No, fork and wait
		FKB=KPB$PS_UCB(R8)		;  using UCB fork block
	BRB	10$				; Try allocation again

20$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC5   #0,(SP),#0,R9,(R2)		; Zero initialize the block
	POPR	#^M<R0,R1,R2,R3,R4,R5>		; Restore registers
;
; Initialize static fields in the Queue Buffer. R2 points to Queue Buffer.
;
	MOVW	R9,QBUF$IW_SIZE(R2)		; Save Queue Buffer size
	MOVB	#DYN$C_MISC,QBUF$IB_TYPE(R2)	; Set buffer type
	MOVB	#DYN$C_QBUF,QBUF$IB_SUBTYPE(R2)	; Set buffer subtype

	MOVAB	QBUF$PQ_CCB_ADDR(R2),R9		; VA of adapter visible section
	$SVA_TO_PA   SVA = R9			; PA of adapter visible section
	BITL	#SPDT$M_PFLG_MAPPING_REG,-	; Are we using map regs?
		SPDT$L_PORT_FLAGS(R4)
	BEQL	30$				; Nope; just use PA.
;
; Is the Queue Buffer within the direct-DMA window?
;
	EVAX_ADDQ #QBUF$C_ADPLEN,R0,R3		; Yes, just use PA
	EVAX_CMPULE R3,SPDT$IQ_DMA_SIZE(R4),R7	; Compare to DMA window size
	BLBS	R7,30$				; Branch if LE window size
;
; O.K., we hafta map it.
;
	MAP_S0	-
		SVA = R9,-			; Queue Buffer address (VA).
		NBYTES = #QBUF$C_ADPLEN,-	; Size to map.
		CRCTX = QBUF$PS_CRCTX_PTR(R2),-	; CRCTX pointer reference.
		KPB$ = SPDT$PS_KPB(R4),-	; KPB to stall on alloc fail
		DMA_ADDR = QBUF$PQ_PA_QBUF(R2)	; Where to put DMA-able addr
	BRB	40$
;
; No mapping registers needed, just use PA (+ DMA base).
;
30$:	ADDL2	SPDT$L_DMA_BASE(R4),R0		; Add in DMA window base
	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_STQ R0,QBUF$PQ_PA_QBUF(R2)		; Save physical address
40$:	MOVL	R2,R7				; Save the Q_Buffer address

	BSBW	GET_QUEUE_CARRIER		; Get a Carrier

	MOVL	SPDT$PS_AB(R4),R5		; Get the Adapter Block address
	MOVL	AB$PQ_DAFQ_TAIL(R5),R1		; Get the DAFQ tail ptr

.Disable Flagging
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Link Q_Buffer phys. address
		CAR$PQ_QBUF_PTR(R1)		;  into stopper Carrier
	MOVL	R7,CAR$PQ_QBUF_TOKEN(R1)	; Copy Q_Buffer virtual address
	MOVL    R1,QBUF$PS_CARRIER(R7)		; Save new carrier's address
						;  in Q_Buffer
	EVAX_MB                                 ; Make sure Q_Buffer contents
						;  and pointer are visible to
						;  the adapter before NEXT_PTR
						;  becomes valid
	EVAX_ADDQ  #1,CAR$PQ_PA(R2),-           ; Link in new Stopper and
		   CAR$PQ_NEXT_PTR(R1)		;  make it a valid Carrier
.Enable Flagging
	CLRB	CAR$IB_FUNC_CODE(R1)		; Clear CAM function code
	CLRB	CAR$IB_STATUS(R1)		; Clear CAM status
	CLRW	CAR$IW_FLAGS(R1)		; Clear CAM flags
	EVAX_MB                                 ; Make sure new Stopper is
						;  visible to adapter before
						;  adapter accesses DAFQ
	MOVL    R2,AB$PQ_DAFQ_TAIL(R5)		; Update tail with new Stopper
	SOBGTR	R6,10$				; Loop for all DAFQ entries
;
; We must notify the adapter of the entries and Stopper Carrier on the DAFQ.
; Write the physical address of the Stopper Carrier (shifted right 3 bits) on
; the DAFQ to the Driver Adapter Free Queue Insertion Register (DAFQIR).
;
	MOVL	AB$PQ_DAFQ_TAIL(R5),R2		; VA of DAFQ Stopper Carrier
	EVAX_LDQ  R7,CAR$PQ_PA(R2)		; PA of DAFQ Stopper Carrier
	EVAX_SRL  R7,#TZA_DAFQIR$C_PA_SHIFT,R7	; Shift PA right 3 bits
	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = DAFQIR,-		; Give DAFQ Stopper PA to adap
		  DATA = R7,-
		  TYPE = Q
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO

	MOVZWL	#SS$_NORMAL,R0			; Success return status
	RSB                                     ; Return
        .DISABLE LSB                            ; SETUP_DAFQ

.PAGE
	.SBTTL	INSERT_DAFQ - Insert a Driver Adapter Free Queue entry
;
; This subroutine is called to insert a free queue entry onto the DAFQ
; (Driver Adapter Free Queue).
;
; After inserting the Queue Buffer and Carrier onto the DAFQ, it writes
; the physical address of the Stopper Carrier on the DAFQ to the
; DAFQIR (Driver Adapter Free Queue Insertion Register) so that the adapter
; can use the free queue entries if needed.
;
; Note: The virtual address of a Carrier points to the start of the
;	Carrier. This allows the Carrier offsets to be correct.
;	The physical address of a Carrier points to the start of the
;	adapter visible section of the Carrier at offset CAR$PQ_NEXT_PTR
;	(which is not the beginning of the Carrier).
;
; This routine assumes that it is running in a Kernel Process context.
;
; INPUT:
;       R4 = SPDT address
;	R2 = Address of Carrier to insert onto DAFQ
;	R7 = Address of Queue Buffer to insert onto DAFQ
;
; OUTPUT:
;       R0 = SS$_NORMAL Status
;
	.ENABLE LSB
INSERT_DAFQ:
	.JSB_ENTRY      INPUT=<R2,R4,R7>, SCRATCH=<R1>, OUTPUT=<R0>,-
			PRESERVE=<R5>

	MOVL	SPDT$PS_AB(R4),R5		; Get the Adapter Block address
	MOVL	AB$PQ_DAFQ_TAIL(R5),R1		; Get the DAFQ tail ptr

.Disable Flagging
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Link Q_Buffer phys. address
		CAR$PQ_QBUF_PTR(R1)		;  into stopper Carrier
	MOVL	R7,CAR$PQ_QBUF_TOKEN(R1)	; Copy Q_Buffer virtual address
	MOVL    R1,QBUF$PS_CARRIER(R7)		; Save new carrier's address
						;  in Q_Buffer
	EVAX_MB                                 ; Make sure Q_Buffer contents
						;  and pointer are visible to
						;  the adapter before NEXT_PTR
						;  becomes valid
	EVAX_ADDQ  #1,CAR$PQ_PA(R2),-           ; Link in new Stopper and
		   CAR$PQ_NEXT_PTR(R1)		;  make it a valid Carrier
.Enable Flagging
	CLRB	CAR$IB_FUNC_CODE(R1)		; Clear CAM function code
	CLRB	CAR$IB_STATUS(R1)		; Clear CAM status
	CLRW	CAR$IW_FLAGS(R1)		; Clear CAM flags
	EVAX_MB                                 ; Make sure new Stopper is
						;  visible to adapter before
						;  adapter accesses DAFQ
	MOVL    R2,AB$PQ_DAFQ_TAIL(R5)		; Update tail with new Stopper
;
; We must notify the adapter of the new entry on the DAFQ. Write the
; physical address of the Stopper Carrier (shifted right 3 bits) on the
; DAFQ to the Driver Adapter Free Queue Insertion Register (DAFQIR).
;
	MOVL	AB$PQ_DAFQ_TAIL(R5),R2		; VA of DAFQ Stopper Carrier
	EVAX_LDQ  R7,CAR$PQ_PA(R2)		; PA of DAFQ Stopper Carrier
	EVAX_SRL  R7,#TZA_DAFQIR$C_PA_SHIFT,R7	; Shift PA right 3 bits
	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = DAFQIR,-		; Give DAFQ Stopper PA to adap
		  DATA = R7,-
		  TYPE = Q
	DEVICEUNLOCK -				; Unlock device access
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = (SP)+,-
		PRESERVE = NO

	MOVZWL	#SS$_NORMAL,R0			; Success return status
	RSB                                     ; Return
        .DISABLE LSB                            ; INSERT_DAFQ

.PAGE
	.SBTTL	SETUP_ADFQ - Set up Adapter Driver Free Queue entries 
;
; This subroutine is called to set up free queue entries in the ADFQ
; (Adapter Driver Free Queue). It is called from the REINIT_PORT routine after
; the adapter/channel has transitioned to the ENABLED state.
;
; It allocates the required Queue Buffers and Carriers for the ADFQ.
;
; Note: The virtual address of a Carrier points to the start of the
;	Carrier. This allows the Carrier offsets to be correct.
;	The physical address of a Carrier points to the start of the
;	adapter visible section of the Carrier at offset CAR$PQ_NEXT_PTR
;	(which is not the beginning of the Carrier).
;
; This routine assumes that it is running in a Kernel Process context.
;
; INPUT:
;       R4 = SPDT address
;	R8 = KPB address
;
; OUTPUT:
;       R0 = SS$_NORMAL Status
;
	.ENABLE LSB
SETUP_ADFQ:
	.JSB_ENTRY	INPUT=<R4,R8>, SCRATCH=<R1>,-
			OUTPUT=<R0>, PRESERVE=<R2,R3,R4,R5,R6,R7,R8,R9,R10>
	
	MOVL	SPDT$PS_AB(R4),R9		; Get the Adapter Block address
	MOVL	AB$PQ_ADFQ_HEAD(R9),R2		; Point to Stopper Carrier
	MOVL	#NUM_ADFQ_ENTRIES,R10		; Number of entries to insert
10$:	MOVL	R2,R3				; Save next Carrier address
;
; Allocate some pool for a buffer and zero the block.
;
15$:	SUBL    #8,SP				; Allocate two lw on stack
	MOVL    SP,R2				; Store address of lw to receive
						;  address of allocated block
        ADDL3   #4,R2,R6			; Store address of lw to receive
						;  size of allocated block
        PUSHL   R6				; Arg 4 = addr to receive size
        PUSHL   R2				; Arg 3 = addr to receive addr
						;  of allocated block
	PUSHL	#ALN_LONG			; Arg 2 = 4 byte alignment
	PUSHL	#256				; Arg 1 = size of block
	CALLS   #4,EXE$ALONONPAGED_ALN		; Allocate a block
	POPL	R2				; Get addr of allocated block
	POPL	R6				; Get allocated size
	BLBS	R0,20$				; Did we get the block?
	KP_STALL_FORK_WAIT  KPB=R8,-		; No, fork and wait
		FKB=KPB$PS_UCB(R8)		;  using UCB fork block
	BRB	15$				; Try allocation again

20$:	PUSHR	#^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC5   #0,(SP),#0,R6,(R2)		; Zero initialize the block
	POPR	#^M<R0,R1,R2,R3,R4,R5>		; Restore registers
;
; Initialize static fields in the Queue Buffer. R2 points to Queue Buffer.
;
	MOVW	R6,QBUF$IW_SIZE(R2)		; Save Queue Buffer size
	MOVB	#DYN$C_MISC,QBUF$IB_TYPE(R2)	; Set buffer type
	MOVB	#DYN$C_QBUF,QBUF$IB_SUBTYPE(R2)	; Set buffer subtype

	MOVAB	QBUF$PQ_CCB_ADDR(R2),R6		; VA of adapter visible section
	$SVA_TO_PA   SVA = R6			; PA of adapter visible section

	BITL	#SPDT$M_PFLG_MAPPING_REG,-	; Are we using map regs?
		SPDT$L_PORT_FLAGS(R4)
	BEQL	30$				; Nope; just use PA.
;
; Is the Queue Buffer within the direct-DMA window?
;
	EVAX_ADDQ #QBUF$C_ADPLEN,R0,R0		; Yes, just use PA
	EVAX_CMPULE R0,SPDT$IQ_DMA_SIZE(R4),R7	; Compare to DMA window size
	BLBS	R7,30$				; Branch if LE window size
;
; O.K., we hafta map it.
;
	MAP_S0	-
		SVA = R6,-			; Queue Buffer address (VA).
		NBYTES = #QBUF$C_ADPLEN,-	; Size to map.
		CRCTX = QBUF$PS_CRCTX_PTR(R2),-	; CRCTX pointer reference.
		KPB$ = SPDT$PS_KPB(R4),-	; KPB to stall on alloc fail
		DMA_ADDR = QBUF$PQ_PA_QBUF(R2)	; Where to put DMA-able addr
	BRB	40$
;
; No mapping registers needed, just use PA (+ DMA base).
;
30$:	ADDL2	SPDT$L_DMA_BASE(R4),R0		; Add in DMA window base
	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_STQ R0,QBUF$PQ_PA_QBUF(R2)		; Save physical address
40$:	MOVL	R2,R7				; Save the Queue Buffer address

	BSBW	GET_QUEUE_CARRIER		; Get a Carrier
.Disable Flagging
	MOVL	R7,CAR$PQ_QBUF_PTR(R2)		; Get Queue Buffer VA
	MOVL	R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Q_Buffer virtual address
	MOVL    R2,QBUF$PS_CARRIER(R7)		; Save new Carrier's address
						;  in Queue Buffer
	EVAX_MB                                 ; Make sure Q_Buffer contents
						;  and pointer are visible to
						;  the adapter before NEXT_PTR
						;  becomes valid
	MOVL	R3,CAR$PQ_NEXT_PTR(R2)		; Link in new Carrier
.Enable Flagging
	CLRB	CAR$IB_FUNC_CODE(R2)		; Clear CAM function code
	CLRB	CAR$IB_STATUS(R2)		; Clear CAM status
	CLRW	CAR$IW_FLAGS(R2)		; Clear CAM flags
	MOVL	R2,AB$PQ_ADFQ_HEAD(R9)		; Point head to inserted Carrier
	EVAX_MB                                 ; Make sure new Stopper is
						;  visible to adapter before
						;  adapter accesses ADFQ
	SOBGTR	R10,10$				; Loop for all ADFQ entries

	MOVZWL	#SS$_NORMAL,R0			; Success return status
	RSB                                     ; Return
        .DISABLE LSB                            ; SETUP_ADFQ

.PAGE
	.SBTTL  INSERT_DACQ - Insert a command on the DACQ
;
; This subroutine is called to insert a command to the port's DACQ.
; It performs the following:
;
; 1.) Sets up pointers in the current Stopper Carrier to point to the
;     Queue Buffer.
; 2.) Makes the Carrier that was just allocated previous to this call a new
;     Stopper. 
; 3.) Stores the virtual address of the new Stopper Carrier in the Q_Buffer
;     to facilitate the recovery of Buffers during adapter reinitialization.
;
; Note that this routine can be called only for those I/O threads that need
; not be stalled waiting for the response.
;
; INPUT:
;	R2 = Allocated Carrier address
;	R4 = SPDT address
;	R7 = Allocated Queue Buffer address
;	IPL$_IOLOCK8
;	
; OUTPUT:
;	R1 = Address of Carrier pointing to Queue Buffer
;	Allocated Carrier inserted onto the DACQ.
;	AB$PQ_DACQ_TAIL Updated with new Stopper Carrier VA
;
INSERT_DACQ:
	.JSB_ENTRY	INPUT=<R2,R4,R7>, OUTPUT=<R1>,-
			SCRATCH=<R0>,PRESERVE=<R2,R4,R5,R6,R7,R8,R9,R10>

	MOVL	SPDT$PS_AB(R4),R6		; Get Adapter Block address
	MOVL	AB$PQ_DACQ_TAIL(R6),R1		; Get DACQ tail ptr. (Stopper)
	MOVL	R1,SPDT$IS_CAR_ADDR(R4)		; Debug, Carrier address
	MOVL	R2,SPDT$IS_STOPPER_ADDR(R4)	; Debug, Stopper address
.Disable Flagging
	MOVQ    QBUF$PQ_PA_QBUF(R7),-		; Link Q_Buffer physical addr
		CAR$PQ_QBUF_PTR(R1)		;  into Stopper Carrier	
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R1)	; Copy Q_Buffer virtual address
	EVAX_MB					; Make sure Q_Buf contents and
	MOVL    R1,QBUF$PS_CARRIER(R7)		; Save Carrier's address
						;  in Queue Buffer
	EVAX_MB					; Make sure Q_Buf contents and
						;  ptr are visible to the
						;  adapter  before NEXT_PTR
						;  becomes valid
	CLRQ	CAR$PQ_NEXT_PTR(R2)		; Clear old NEXT_PTR
.Enable Flagging
	MOVB	QBUF$IB_FUNC_CODE(R7),-		; Fill in function code
		CAR$IB_FUNC_CODE(R1)
	MOVB	QBUF$IB_PATH_ID(R7),-		; Setup path id
		CAR$IB_PATH_ID(R1)
	MOVB	QBUF$IB_TARGET_ID(R7),-		; Setup target id
		CAR$IB_TARGET_ID(R1)
	MOVB	QBUF$IB_LUN(R7),-		; Setup LUN
		CAR$IB_LUN(R1)
	EVAX_MB					; Make sure new Stopper is
						;  visible to the adapter
						;  before adapter accesses DACQ
	MOVL	R2,AB$PQ_DACQ_TAIL(R6)		; Update AB DACQ tail pointer
						;  with new Stopper VA
	EVAX_ADDQ  #1,CAR$PQ_PA(R2),-		; Link new Stopper and make
		   CAR$PQ_NEXT_PTR(R1)		;  it a valid Carrier
	EVAX_MB					; Make sure tail ptr is visible
	RSB					; Return
	.DISABLE LSB				; INSERT_DACQ

.PAGE
	.SBTTL  REMOVE_ADRQ - Remove a command from the ADRQ
;
; This subroutine is called to remove a command from the port's ADRQ.
; It performs the following:
;
; 1.) Removes a Carrier from the ADRQ head.
; 2.) Dequeues the Queue Buffer from the previous link.
; 3.) Updates the ADRQ head pointer with the next Carrier address.
;
; INPUT:
;	R4 = SPDT address
;	IPL$_IOLOCK8
;	
; OUTPUT:
;	R2 = Address of Carrier removed from ADRQ
;
	.ENABLE LSB	
REMOVE_ADRQ:
	.JSB_ENTRY	INPUT=<R4>,OUTPUT=<R2>,SCRATCH=<R0>,-
			PRESERVE=<R4,R6,R7,R8,R9,R10>

	MOVL	SPDT$PS_AB(R4),R6		; Get Adapter Block address
	MOVL	AB$PQ_ADRQ_HEAD(R6),R2		; Get Carrier at ADRQ head
	MOVL	CAR$PQ_QBUF_PTR(R2),R7		; Get Q_Buffer virtual address
						;  written by the adapter
	EVAX_MB                                 ; Make sure Q_Buf contents
	MOVL	CAR$PQ_NEXT_PTR(R2),-		; Update ADRQ head pointer
		AB$PQ_ADRQ_HEAD(R6)		;  with next Carrier address
	EVAX_MB                                 ; Make sure Q_Buf contents

	RSB					; Return
	.DISABLE LSB				; REMOVE_ADRQ

.PAGE
	.SBTTL  GET_QUEUE_BUFFER - Get a Q_Buffer
;
; This subroutine is called whenever a Q_Buffer is requested. For all
; commands, a 512 byte long Q_Buffer is allocated. The first 40 bytes
; in the allocated block is used internally by PKSDRIVER and is not visible
; to the adapter. The next 24 bytes contain the CDB built by the class driver
; and is not visible to the adapter. The next 192 bytes are the adapter
; visible section and contain the CCB built by PKSDRIVER. The last 256 bytes
; are the autosense buffer.
;
; All Q_Buffers are 64-byte aligned and do not cross a page (8KB) boundary.
;
; Note: This routine assumes that it is running in a Kernel Process context.
;
; INPUT:
;	R4 = SPDT address
;	R5 = SCDRP address
;	R8 = KPB address
;	IPL$_IOLOCK8
;	
; OUTPUT:
;	R2 = Address of the allocated Q_Buffer
;
	.ENABLE LSB
GET_QUEUE_BUFFER:
	.JSB_ENTRY	INPUT=<R4,R5,R8>,OUTPUT=<R2>, -
			PRESERVE=<R0,R1,R3,R4,R5,R6,R7,R8,R9,R10>

	PUSHL	R5				; SCDRP address
	PUSHL	R4				; SPDT address

	CALLS	#2,@SPDT$PS_RL_CMD_SLOT_ALLOC(R4) ; Allocate command buffer slot
	BLBC	R0,60$				; Did we get a buffer slot?
	ASHL	#9,SCDRP$IS_CMD_SLOT(R5),R2	; Yes, multiply slot num by 512
						;  to get offset 
	ADDL2	SPDT$PS_CMD_BASE(R4),R2       	; Calculate addr of cmd buffer

	.IF DEFINED PKSHISTORY
	MOVL	SPDT$IS_BUFFER_PTR(R4),R9
	MOVL	SCDRP$IS_CMD_SLOT(R5),(R9)+
	MOVL	R2,(R9)+
	MOVL	R9,SPDT$IS_BUFFER_PTR(R4)
	.ENDC
;
; Zero the queue buffer, saving and restoring the CRCTX pointer.
;
	PUSHL	QBUF$PS_CRCTX_PTR(R2)
        PUSHR   #^M<R0,R1,R2,R3,R4,R5>		; Save registers
	MOVC5   #0,.,#0,#QBUF$C_LENGTH,(R2)	; Zero initialize the block
        POPR    #^M<R0,R1,R2,R3,R4,R5>		; Restore registers
	POPL	QBUF$PS_CRCTX_PTR(R2)
;
; Initialize static fields in the Queue Buffer. R2 points to Queue Buffer.
;
	MOVW	#QUEUE_BUFFER_SIZE,-		; Save Queue Buffer size
		QBUF$IW_SIZE(R2)
	MOVB	#DYN$C_MISC,QBUF$IB_TYPE(R2)	; Set buffer type
	MOVB	#DYN$C_QBUF,QBUF$IB_SUBTYPE(R2)	; Set buffer subtype
;
; Do we need map registers to map this Queue Buffer?
;
	MOVAB	QBUF$PQ_CCB_ADDR(R2),R6		; VA of adapter visible section
	$SVA_TO_PA   SVA = R6			; PA of adapter visible section

	BITL	#SPDT$M_PFLG_MAPPING_REG,-	; Are we using map regs?
		SPDT$L_PORT_FLAGS(R4)
	BEQL	40$				; Nope, just use PA.
;
; Is this Queue Buffer within the direct-DMA window?
;
	EVAX_ADDQ #QBUF$C_ADPLEN,R0,R3		; Yes, just use PA
	EVAX_CMPULE R3,SPDT$IQ_DMA_SIZE(R4),R7	; Compare to DMA window size
	BLBS	R7,40$				; Branch if LE window size
;
; O.K., we have to map it.
;
	MOVAB	QBUF$PQ_CCB_ADDR(R2),R3
	MAP_S0 -
		SVA = R3,-
		NBYTES = #QBUF$C_ADPLEN,-
		CRCTX = QBUF$PS_CRCTX_PTR(R2),-
		KPB$ = R8,-
		DMA_ADDR = QBUF$PQ_PA_QBUF(R2)
	BRB	50$
;
; We come here if we're not using map registers, or if the Queue Buffer is
; entirely within the direct-DMA window.  In either case, we can simply
; use the physical address as the DMA-able address.
;
40$:	ADDL2	SPDT$L_DMA_BASE(R4),R0		; Add in DMA window base
	EVAX_ZAP R0,#^XF0,R0			; Clear the upper longword
	EVAX_STQ R0,QBUF$PQ_PA_QBUF(R2)		; Save physical address
50$:	RSB					; Return with address in R2

60$:	CLRL	R2				; Indicate no buffer allocated
	BRW	50$				; Return error
	.DISABLE LSB				; GET_QUEUE_BUFFER

.PAGE
	.SBTTL	RET_QUEUE_BUFFER - Return a Queue Buffer
;
; This routine is called to return a Queue Buffer to non-paged pool
; or the Driver Adapter Free Queue (DAFQ). All queue buffers with
; a function code of execute SCSI I/O are returned to non-paged pool.
; All others are returned to the DAFQ.
;
; INPUT:
;	R4 - SPDT address.
;	R5 - SCDRP address (If Queue Buffer from Execute SCSI I/O request).
;	IPL$_IOLOCK8
;
; OUTPUT:
;	Queue Buffer returned to pool or DAFQ.
;
	.ENABLE LSB
RET_QUEUE_BUFFER:
	.JSB_ENTRY	INPUT=<R4,R5,R7>,SCRATCH=<R1>, -
			PRESERVE=<R0,R2,R3,R4,R5,R6,R7,R8,R9,R10>
;
; Find the address of the Queue Buffer.
;
	ASHL	#9,SCDRP$IS_CMD_SLOT(R5),R2	; Yes, multiply slot num by 512
						;  to get offset 
	ADDL2	SPDT$PS_CMD_BASE(R4),R2       	; Calculate addr of cmd buffer
;
; Does this buffer have a CRCTX associated?  If so, deallocate the map
; registers associated therewith.
;
	MOVL	QBUF$PS_CRCTX_PTR(R2),R0	; Fetch the CRCTX ptr
	BEQL	10$
	PUSHL	R0				; Yup.  Push the CRCTX addr.
	PUSHL	SPDT$PS_CRAB(R4)		; ...and the CRAB addr.
	CALLS	#2,IOC$DEALLOC_CNT_RES		; And deallocate the regs.
;
; This Queue Buffer is from an Execute SCSI I/O request. Return the
; Queue Buffer to buffer free pool.
;
10$:	PUSHL	R5				; SCDRP address
	PUSHL	R4				; SPDT address
	CALLS	#2,@SPDT$PS_RL_CMD_SLOT_DEALLOC(R4) ; Deallocate buffer slot

	RSB					; Return
	.DISABLE LSB				; RET_QUEUE_BUFFER

.PAGE
        .SBTTL  GET_QUEUE_CARRIER - Get a Queue Carrier
;
; This subroutine is called whenever a Carrier is requested. A Carrier
; consists of 32 bytes of port driver specific fields followed by 40 bytes
; of adapter-visible fields.
;
; Queue Carriers are statically allocated at driver load time and inserted
; onto a doubly linked list pointed to by SPDT$PS_CARFL and SPDT$PS_CARBL.
;
; All Carriers must be 32-byte aligned.
;
; Note: This routine assumes that it is running in a Kernel Process context.
;
; INPUT:
;	R4 = SPDT address
;	     SPDT$PS_AB = pointer to the Adapter Block 
;       R8 = KPB address to use for fork stalling
;	IPL$_IOLOCK8
;
; OUTPUT:
;	R0 = SS$_NORMAL
;	R2 = Address of the allocated Carrier
;
        .ENABLE LSB
GET_QUEUE_CARRIER:
	.JSB_ENTRY      INPUT=<R4,R8>,OUTPUT=<R0,R2>, -
			SCRATCH=<R1>,PRESERVE=<R3,R4,R5,R6,R7,R8,R9,R10>
;
; Get the next Carrier from the Carrier free list in the SPDT.
;
10$:	MOVAL	SPDT$PS_CARFL(R4),R6		; Get Carrier free list address
	REMOVE_HEAD -
		QUE = R6,-			; Carrier free list in SPDT
		OUT_EL = R2,-			; Carrier address
		EMPTY_ADDR = 30$,-		; Wait routine if list empty
		SCRATCH = R1

	.IF DEFINED PKSHISTORY
	MOVL	SPDT$IS_CARRIER_PTR(R4),R9
	MOVL	R2,(R9)+
	MOVL	R9,SPDT$IS_CARRIER_PTR(R4)
	.ENDC
;
; Do we need map registers to map this carrier?
;
	BITL	#SPDT$M_PFLG_MAPPING_REG,-	; Are we using map regs?
		SPDT$L_PORT_FLAGS(R4)
	BEQL	20$				; Nope; just use PA.
;
; Is this carrier within the direct-DMA window?
;
	MOVAB	CAR$PQ_NEXT_PTR(R2),R3
	$SVA_TO_PA	SVA = R3
	EVAX_ADDQ #CAR$C_ADPLEN,R0,R0		; Yes, just use PA
	EVAX_CMPULE R0,SPDT$IQ_DMA_SIZE(R4),R7	; Compare to DMA window size
	BLBS	R7,20$				; Branch if LE window size
;
; O.K., we have to map it.
;
	MAP_S0 -
		SVA = R3,-
		NBYTES = #CAR$C_ADPLEN,-
		CRCTX = CAR$PS_CRCTX(R2),-
		KPB$ = R8,-
		DMA_ADDR = CAR$PQ_PA(R2)

20$:	MOVZWL	#SS$_NORMAL,R0			; Set success status
	RSB					; Return
;
; No Carriers available. Suspend this I/O request to wait for one to be
; returned. Routine RET_QUEUE_CARRIER will restart the kernel process when
; a Carrier is returned.
;
30$:	INSERT_EL -
		EL  = KPB$PS_FQFL(R8),-		; Insert KP at the end of
		QUE = @SPDT$PS_CARWQBL(R4),-	;  Carrier wait queue in SPDT
		SCRATCH = R1

	MOVL	R4,KPB$PS_SCSI_PTR1(R8)		; Save SPDT address
	MOVL	R5,KPB$PS_SCSI_SCDRP(R8)	; Save SCDRP address
	MOVAB	B^IOC$RETURN,-			; Set stall routine
		KPB$PS_SCH_STALL_RTN(R8)
	CLRL	KPB$PS_SCH_RESTRT_RTN(R8)	; No restart routine required
	PUSHL	R8				; Pass KPB address
	CALLS	#1,G^EXE$KP_STALL_GENERAL	; Call the general KP staller
;
; This is the point at which the stalled KP resumes when a Carrier is returned.
;
        BRB     10$                             ; Get the Carrier just returned 
	.DISABLE LSB				; GET_QUEUE_CARRIER

.PAGE
	.SBTTL	RET_QUEUE_CARRIER - Return a Queue Carrier
;
; This subroutine is called to return a Carrier to the Carrier free list.
; It performs the following:
;
; 1.) Dequeues the Carrier from the linked list in the Adapter Block.
; 2.) Checks to see if there are any I/O requests waiting for a Carrier.
;     If so, restart the Kernel Process passing back the Carrier address.
; 3.) If there are no I/O requests waiting for a Carrier, the Carrier is
;     returned to the free list in the SPDT (SPDT$PS_CARBL).
;
; INPUT:
;	R2 = Address of the Carrier to deallocate
;	R4 = Address of SPDT
;	IPL$_IOLOCK8
;
; OUTPUT:
;	Carrier returned to pool
;	R1 destroyed
;
	.ENABLE LSB
RET_QUEUE_CARRIER:
	.JSB_ENTRY	INPUT=<R2,R4>,SCRATCH=<R1>,-
			PRESERVE=<R0,R2,R3,R4,R5,R6,R7,R8,R9,R10>
;
; Does this carrier have a CRCTX associated, and have map registers
; been allocated to it?  If so, free them up.
;
	MOVL	CAR$PS_CRCTX(R2),R0	; Fetch CRCTX ptr.
	BEQL	5$
	PUSHL	R0			; Yep.  Push CRCTX addr.
	PUSHL	SPDT$PS_CRAB(R4)	; ...and CRAB addr.
	CALLS	#2,IOC$DEALLOC_CNT_RES	; Deallocate the map regs.
;
; Check to see if there are any I/O requests waiting for a Carrier.
; If so, restart the Kernel Process passing back the Carrier address.
;
5$:	MOVAL	SPDT$PS_CARWQFL(R4),R6		; Address of Carrier wait queue
	REMOVE_HEAD -				; Get next KPB
		QUE = R6,-			;  Carrier wait queue
		OUT_EL = R3,-
		EMPTY_ADDR = 10$,-		;  Branch if no KPBs waiting
		SCRATCH = R1

	MOVAB	-KPB$PS_FQFL(R3),R3		; Point to beginning of KPB
	MOVL	KPB$PS_SCSI_PTR1(R3),R4		; Get SPDT address saved in KPB
	MOVL	KPB$PS_SCSI_SCDRP(R3),R5	; Get SCDRP address saved in KPB
	PUSHL	R2				; Pass Carrier address
	PUSHL	R3				; Pass KPB address
	CALLS	#2,G^EXE$KP_RESTART		; Resume the I/O thread that
						;  was waiting
	BRB	20$				; Return
;
; There are no I/O requests waiting for a Carrier. Just return the Carrier 
; to the free list.
;
10$:	INSERT_EL -
		EL  = CAR$PS_FLINK(R2),-	; Insert Carrier on linked
		QUE = @SPDT$PS_CARBL(R4),-	;  list in SPDT
		SCRATCH = R1
20$:	RSB					; Return
	.DISABLE LSB				; RET_QUEUE_CARRIER

.PAGE
        .SBTTL	CHECK_REV - Check adapter revision numbers
;
; This subroutine reads the SIMport SCSI adapter's hardware and firmware rev
; levels from the HW_REV register and checks them against the valid values in
; the port driver's device revision table. This routine will be called at each
; SIMport adapter configuration time. If the adapter's revision levels are
; greater than the maximum values in the revision table, this adapter will be
; considered as an experimental version and be accepted. Otherwise, if the
; matching revision levels cannot be found in the revision table, an error
; status is returned so that the adapter can be taken off-line.
;
; INPUT:
;	None.
;
; OUTPUT:
;	R0 - SS$_NORMAL, Revision levels valid
;	     SS$_CTRLERR, Revision levels not valid
;
	.ENABLE LSB
CHECK_REV:
	.JSB_ENTRY	OUTPUT=<R0>

	MOVZWL	#SS$_NORMAL,R0			; Set success status
	RSB					; Just return for now
	.DISABLE LSB				; CHECK_REV

.PAGE
	.SBTTL	CLEANUP_BUF - Clean up buffer resources
;
; This subroutine is called from the REINIT_PORT routine when an adapter
; miscellaneous error occurs and at powerfail recovery time as part of the
; adapter reinitialization process.
;
; This routine will recover as many Carriers and Q_Buffers as possible from the
; ADRQ, the DAFQ, the IN_PORT queue, and the IN_DEVICE queue. The recovered
; Carriers are returned to the pool while the Q_Buffers associated with
; currently stalled I/O requests are retained until the stalled I/O is resumed
; with SS$_MEDOFFLIN return status and the class driver calls the command buffer
; deallocation routine explicitly.
;
; Note that this routine assumes that it is running in a Kernel Process context.
;
; INPUT:
;	R2 = ADP address
;	R4 = SPDT address
;	R5 = UCB address
;	R8 = KPB address
;
; OUTPUT:
;	None
;
	.ENABLE LSB
CLEANUP_BUF:
	.JSB_ENTRY     INPUT=<R2,R4,R5,R8>, -
		       SCRATCH=<R0,R1>,PRESERVE=<R2,R3,R4,R5,R6,R7,R8>

	BISL   #SPDT$M_PWF_CLNUP,-		; Set cleanup in progress flag
		SPDT$L_STS(R4)			;  for channel
	MOVL	SPDT$PS_AB(R4),R5		; Get Adapter Block address
	BSBW	LOOP_ADRQ			; Clean up ADRQ
	MOVL	AB$PQ_ADRQ_HEAD(R5),R0		; Get response queue head ptr
	BEQL	10$				; Branch if empty and reset
	BSBW	RET_QUEUE_CARRIER		; Return stopper Carrier in ADRQ
	CLRL	AB$PQ_ADRQ_HEAD(R5)		; Clear the head ptr. to reset
;
; Now clean up all buffers in the free queues.
;
10$:	MOVL	AB$PQ_ADFQ_HEAD(R5),R0		; Get ADFQ head
	BEQL	50$				; Exit loop if ADFQ is reset
	BBS	#0,CAR$PQ_NEXT_PTR(R0),40$	; Exit loop if this is a stopper
.Disable Flagging
	MOVQ    CAR$PQ_NEXT_PTR(R0),-		; Update ADFQ head pointer
		AB$PQ_ADFQ_HEAD(R5)		;  with next Carrier address
.Enable Flagging
	MOVL    CAR$PQ_QBUF_PTR(R0),R6		; Get Q_Buffer virtual address
						;  written by the adapter
	BSBW    RET_QUEUE_CARRIER		; Return the Carrier
	MOVL	R6,R0				; Get the Q_Buffer address
	BSBW	RET_QUEUE_BUFFER		; Return this free Q_Buffer
	BRB	10$				; Check next free entry in ADFQ
40$:	BSBW	RET_QUEUE_CARRIER		; Return last Carrier in ADFQ
	CLRL	AB$PQ_ADFQ_HEAD(R5)		; Clear the head ptr. to reset
;
; Clean up DAFQ entries.
;
50$:	MOVAL	SPDT$PS_DAFQFL(R4),R7		; Get address of free queue hdr
52$:	MOVL	QBUF$PS_CARRIER(R6),R0		; Get Carrier address
	BSBW    RET_QUEUE_CARRIER		; Return associated Carrier
	MOVL    R6,R0				; Get the Q_Buffer address
	BSBW    RET_QUEUE_BUFFER		; Return this Q_Buffer
	BRB	52$				; Go check next free Q_Buffer	
;
; Clean up any remaining Carriers allocated for this adapter. Note that the
; Carriers pointed to by the DCCQx and DAFQ head pointers are returned to the
; pool via this method.
;
55$:	MOVAL	AB$PS_CAR_FLINK(R5),R7		; Get addr of Carrier que hdr
65$:	REMOVE_HEAD -                           ; Get address of the next
		QUE = R7,-                        ;  Carrier
		OUT_EL = R0,-
		EMPTY_ADDR = 70$,-                ;  Branch if no more entries
		SCRATCH = R1
	MOVL	R0,R2
	MOVL	CAR$PS_CRCTX(R0),R7		; Have we allocated a CRCTX?
	BEQL	69$
	PUSHL	R7				; Yep, deallocate the regs.
	PUSHL	SPDT$PS_CRAB(R4)
	CALLS	#2,G^IOC$DEALLOC_CNT_RES
	PUSHL	R7
	CALLS	#1,G^IOC$DEALLOC_CRCTX		; And deallocate the CRCTX
69$:	MOVL	R2,R0
	MOVZWL  CAR$IW_SIZE(R0),R1              ; Get size field
	JSB     EXE$DEANONPGDSIZ                ; Deallocate this Carrier
	BRB	65$				; Get next Carrier in the queue

70$:	BICL	#SPDT$M_PWF_CLNUP,-             ; Clear cleanup in progress flag
		SPDT$L_STS(R4)
	RSB					; Return
	.DISABLE LSB				; CLEANUP_BUF

.PAGE
	.SBTTL  LOOP_ADRQ - Dequeue entries from the ADRQ
;
; This routine dequeues each response entry pair (a Carrier and a Q_Buffer)
; from the ADRQ until the Stopper Carrier is found. For each response entry
; pair dequeued, the Carrier is returned to the pool while the Q_Buffer is
; queued to the channel's SPDT to be processed later. Once all the entries
; have been queued to the SPDT, routine PROCESS_RESPONSE is called to process
; the responses.
;
; This routine can also be called from the CLEANUP_BUF and ENABLE_CHNL routines
; as a part of the adapter/channel reinitialization after the adapter/channel
; crash or power failure.
;
; INPUT:
;       R4 = SPDT address of Channel
;	R5 = Adapter Block address
;
        .ENABLE LSB
LOOP_ADRQ:
	.JSB_ENTRY	INPUT=<R4,R5>,SCRATCH=<R0,R1>,-
			PRESERVE=<R2,R3,R4,R5,R6>

10$:	MOVL	AB$PQ_ADRQ_HEAD(R5),R0		; Get ADRQ head pointer
	BEQL	60$				; Done if empty
	BBS	#VA$V_SYSTEM,R0,20$		; Branch if VA
	BUG_CHECK INCONSTATE,FATAL		; We want to capture this

20$:	BBC	#0,CAR$PQ_NEXT_PTR(R0),50$	; Exit loop if stopper
.Disable Flagging
	MOVQ	CAR$PQ_NEXT_PTR(R0),-		; Update ADRQ head pointer
		AB$PQ_ADRQ_HEAD(R5)		;  with next Carrier address
.Enable Flagging
	MOVL	CAR$PQ_QBUF_PTR(R0),R6		; Get Q_Buffer virtual address
						;  written by the adapter
	BBS	#VA$V_SYSTEM,R6,30$		; Branch if VA
	BUG_CHECK INCONSTATE,FATAL		; We want to capture this
;
; Map the returned CAM status in the Carrier to a VMS status code.
;
30$:	MOVZBL	CAR$IB_STATUS(R0),R1		; Get returned CAM status
	ASHL	#2,R1,R1			; Make longword offset
	MOVAB	CAM_STATUS_TABLE,R2		; Point to beginning of table
	ADDL2	R1,R2				; Point to VMS status code
	MOVL	(R2),R1				; Get VMS status code
	MOVL    QBUF$PS_SCDRP(R6),R7	        ; Get SCDRP address in Q_buffer
	MOVW	R1,QBUF$IW_PORT_STS(R6)		; Setup VMS return status
;
; Return resources for this response.
;
	BSBW	RET_QUEUE_CARRIER		; Return Carrier to pool
	BRW	10$				; See if more responses
;
; A stopper Carrier was reached. All Carriers have been taken off of the ADRQ.
;
50$:	MOVAL	SPDT$PS_RSPFL(R4),R5		; Get address of response queue
	MOVL	(R5),R2				; Get first entry in the queue
	CMPL	R2,R5				; Check for stopper carrier
	BEQL	60$				; Is the response queue empty?
;;	BSBW	PROCESS_RESPONSE		; No, go process responses

60$:	RSB					; Return
        .DISABLE LSB				; LOOP_ADRQ

.PAGE
	.SBTTL	ENABLE_CHNL - Clean up and re-enable the channel
;
; This subroutine is called either by the channel error handling routine,
; PROCESS_CHNL_ERR or the bus reset routine, RESET_SCSI_BUS. By the time
; this routine is called, the channel should be in the DISABLED state.
;
; The routine will clean up all buffer resources of the DISABLED channel,
; re-initialize the channel's DCCQ pointers, and re-enable the channel.
;
; INPUT:
;	R4 = SPDT address
;	R6 = KPB address
;
; OUTPUT:
;	None
;
	.ENABLE LSB
ENABLE_CHNL:
	.JSB_ENTRY	INPUT=<R4,R6>,-
			SCRATCH=<R0,R1>,PRESERVE=<R2,R3,R4,R5,R6,R7,R8,R9,R10>

	BBS     #SPDT$V_PWF_CLNUP,-		; Skip channel cleanup if in
		SPDT$L_STS(R4),200$		;  middle of adapter reinit
	BBSSI	#SPDT$V_CHNL_CLNUP,-		; Set cleanup in progress after
		SPDT$L_STS(R4),200$		;  this channel's BUS RESET
						; If bit already set, someone
						;  already started this process
	MOVL    SPDT$PS_AB(R4),R5               ; Get Adapter Block address
	BSBW    LOOP_ADRQ                       ; Check ADRQ and take care of
						;  any returned Q_Buffers from
						;  the disabled channel
;
; Now clean up the Carriers pointed to by the DCCQx Head_Ptrs. The Head_Ptr
; fields of the DACQ queues in the Adapter Block should have been updated by
; the SIMport FW by this time. These will be in the physical address format,
; so we need to go through the linked list of Carriers to find out the
; matching Carriers. Once we find the matching Carrier, instead of returning
; the Carrier to the pool and then reallocating another one as a new stopper
; Carrier, we re-use the one we found.
;
	MOVAB	AB$PQ_DACQ_HEAD(R5),R3		; Get DACQ head pointer
35$:	EVAX_LDQ R1,(R3)			; Get DACQ head pointer contents
	MOVAL	AB$PS_CAR_FLINK(R5),R7		; Get queue header for
						;  allocated Carriers
	MOVL	R7,R2				; Copy for reference
40$:	MOVL	(R2),R2				; Get next Carrier in the queue
	CMPL	R2,R7				; End of the queue?
	BEQL	50$				; Yes, the Head_Ptr might not
						;  have a valid Carrier addr,
						;  so just lose this one
	EVAX_CMPEQ R1,CAR$PQ_PA(R2),R0		; Is this the matching Carrier?
	BLBC	R0,40$				; Check next Carrier if not
.Disable Flagging                               ; Turn off info, it's ALIGNED
	CLRQ	CAR$PQ_NEXT_PTR(R2)		; Clear fields to reinit Carrier
	CLRQ	CAR$PQ_QBUF_PTR(R2)
	CLRQ	CAR$PQ_QBUF_TOKEN(R2)
.Enable Flagging
	BRB	60$				; Go join common code
;
; We need to allocate a new Stopper Carrier since firmware didn't return us
; a valid Carrier in the Head pointer.
;
50$:	MOVL	R6,R8				; Setup KPB
	BSBW    GET_QUEUE_CARRIER		; Get a Carrier
60$:	MOVL	R2,8(R3)			; Save tail pointer (VA)
.Disable Flagging				; Turn off info, it's aligned
	MOVQ	CAR$PQ_PA(R2),(R3)		; Store stopper Carrier PA in
						;  DACQ queue head
.Enable Flagging
	CMPL	R10,#1				; Did we write to DACQ head?	
	BEQL	70$				; Yes, skip next step then
	EVAX_LDQ  R0,CAR$PQ_PA(R2)              ; Get the Stopper address
;;	EVAX_SRL  R0,#CQIB$V_SHIFT,R0           ; Start with PA bit <5>
;;	EVAX_BIC  R0,#CQIB$M_SBZ,R0             ; Clear bits to get PA <39:5>
	EVAX_STQ  R0,(R9)			; Write Stopper address
	ADDL	#8,R9				; Point to next CCQxIR field

	ADDL	#16,R3				; Point to next DACQ Head Ptr
	SOBGTR	R10,35$				; Go process next DCCQ Head Ptr
;
; Now try re-enabling the channel.
;
70$:	BICL    #SPDT$M_CHNL_CLNUP,-		; Clear channel cleanup in
		SPDT$L_STS(R4)			;  progress flag
	KP_START  KPB = R8, -			; Call Kernel Process routine
		  REGISTERS = #PKS_KP_REGMSK, -	;  to reinit the port
		  ROUTINE = REINIT_PORT
;
; Clear the port counters area to reset the counters and set up the timer to
; call READ_COUNTER routine.
;
	MOVL    SPDT$PS_COUNTERS(R4),R1		; Get the port counter address
	PUSHR   #^M<R2,R4,R5>			; Save registers
	MOVC5   #0,(SP),#0,-			; Clear the counters
		#AB$C_PORT_CNTRS_SIZE,(R1)
	POPR    #^M<R2,R4,R5>			; Restore registers
	MOVL    UCB$L_CRB(R5),R0		; Get CRB address of this port
	ADDL3   #READCNTR_INTERVAL,-		; Set READ_COUNTER wakeup
		G^EXE$GL_ABSTIM,-		;  and save the timeout value
		CRB$L_DUETIME(R0)
190$:	BICL	#SPDT$M_CHNL_CLNUP,-		; Clear channel cleanup in
		SPDT$L_STS(R4)			;   progress flag
200$:	RSB					; Return
	.DISABLE LSB				; ENABLE_CHNL

.PAGE
	.SBTTL	PORT_SHUTDOWN - Shut down the Port
;
; This subroutine is called to shut down the channel permanently until
; the next system boot.
;
; INPUT:
;	R4 = SPDT address
;	R5 = UCB address
;
; OUTPUT:
;	None
;
	.ENABLE LSB
PORT_SHUTDOWN:
	.JSB_ENTRY	INPUT=<R4,R5>,-
			SCRATCH=<R0,R1>, PRESERVE=<R2,R3,R4,R5,R7>
;
; By setting the FAILED bit and not setting the port ONLINE bit we will prevent
; connections from being established or commands being sent using this port.
;
	BISL	#SPDT$M_STS_FAILED,-		; Remember that it is dead
		SPDT$L_STS(R4)
;
; Set all the SCDTs belonging to this port to be in CLOSED state
; (SCDT$C_STATE_CLOSED) so that no further send request can be processed.
;
	CLRL    R3				; Init. the target ID
	CLRL    R1				; Init the LUN

	MOVZWL	SPDT$IW_CONFIG_BUS_WIDTH(R4),R7	; Set maximum ID to check

30$:						; Loop for 64 SCDT vectors
	FIND_STDT_VECTOR -			; Find STDT address needed
		TARGET_ID = R3, -		;  by FIND_SCDT_VECTOR
		STDT_ADDR = R6, -
		SPDT = R4, -
		STDT = R0
	TSTL	R0				; Got STDT if = 0 try next ID
	BEQL	45$
35$:	FIND_SCDT_VECTOR -	               ; Translate the LUN to the 
	        LUN = R1, -
		STDT = R0, -			;  address of the SCDT entry
                SCDT_ADDR = R6, -
		SCDT = R10
	TSTL	R10				; Got SCDT if = 0 try next LUN
	BEQL    40$				; Branch if no
	MOVW    #SCDT$C_STATE_CLOSED, -		; Set the state to CLOSED
		SCDT$W_STATE(R10)
40$:	AOBLSS  #8,R1,35$			; Try next LUN for the same target ID
45$:	CLRL	R1				;  Reset the LUN to 0
	AOBLSS  R7,R3,30$			; Try next target's LUN 0

	MOVL    UCB$L_CRB(R5),R0		; Get CRB address of this port
	MNEGL   #1,CRB$L_DUETIME(R0)		; Disable further wakeups of READ_COUNTER
;
; Print PORT OFFLINE message to operator's terminal.
;
	.IF DEFINED PKSDEBUG
	MOVAB   PORT_OFF_MSG,R2			; Get addr of msg to output
	MOVZBL  (R2)+,R1			; Get message size and address
	MOVL    UCB$L_DDB(R5),R0		; Get DDB addr in R0
	MOVB    DDB$T_NAME+3(R0),-		; Copy device controller
		CTRLR_LETTER(R2)		;  letter from DDB to ASCII msg
	MOVL    G^OPA$AR_UCB0,R5		; Get console terminal UCB address
	JSB     G^IOC$BROADCAST			; Send the message
	.ENDC
;
; Do we want to error log this?
;
;	LOG_ERROR -				; Log error in error log
;		TYPE = CTL_ERR,-
;		CDT  = #0
	RSB					; Return
	.DISABLE LSB				; PORT_SHUTDOWN

.PAGE
	.SBTTL	READ_COUNTER - Read port counters
;
; This routine is invoked by the EXE$TIMEOUT routine (in TIMESCHDL.MAR) through
; the CRB timeout mechanism to issue a RDCNT command to the port. The RDCNT
; response contains the cumulative port counters values that the SIMport 
; kept since the time the port was enabled or the last time we asked to clear
; all counters. We will ask the port to clear all counters if the last counter
; response contained any counter that overflowed (e.g. set to FFFFFFFF).
;
; This routine allocates a Queue Buffer and a Carrier to queue this request and
; then suspends itself until the response is returned. If the port is not in
; the ENABLED state at the time of the call to this routine, no command is
; issued to the port since port is not processing the commands.
;
; Once the response is returned, the port counter values read will be stored as
; cumulative counts kept by the port driver.  
; 
; Note that this routine allocates a temporary KPB, and switches itself to be
; a Kernel Process routine in order to wait for the response from the port.
;
; PARANOID WARNING: If the number, the type, or the order of counters returned
;		    by the port is ever changed, this routine must be modifed
;		    accordingly.
;
; INPUT:
;	R3 = Address of the port's CRB
;
; OUTPUT:
;	None
;
	.ENABLE LSB
READ_COUNTER:
	.JSB_ENTRY	INPUT=<R3>, SCRATCH=<R0,R1>,-
			PRESERVE=<R2,R3,R4,R6,R7,R8>
;
; If the port is not online or there is currently a Read Counter KPB running,
; then just return.
;
	MOVL    CRB$L_SCS_STRUC(R3),R4		; Get SPDT address of the port
	BBC	#SPDT$V_STS_ONLINE,-		; Exit without resetting due
		SPDT$L_STS(R4),-		;  time if port is not ENABLED	
		60$
	TSTL	SPDT$PS_RDCNT_KPB(R4)		; Is there a RDCNT KP running?
	BNEQ	60$				; Branch and exit if yes
;
; Allocate a KPB and start the Kernel process.
;
	KP_ALLOCATE_KPB -			; Allocate Kernel Process block
		KPB = SPDT$PS_RDCNT_KPB(R4),-
		STACK = G^MMG$GL_PAGE_SIZE,-	;  Set the stack size
		FLAGS = #KP$M_IO		;  VEST, dealloc when done
	BLBC	R0,50$				; Branch and exit if no KPB
	MOVL	SPDT$PS_RDCNT_KPB(R4),R8	; Copy KPB pointer
	MOVL	R4,KPB$PS_SCSI_PTR1(R8)		; Save SPDT address in KPB
	MOVL	R3,KPB$PS_SCSI_PTR2(R8)		; Save CRB address in KPB
	MOVL	SPDT$L_PORT_UCB(R4),-		; Save UCB address in KPB
		KPB$PS_UCB(R8)
	KP_SWITCH_TO_KP_STACK -			; Start the Kernel Process
		KPB = R8,-			;  KPB pointer
		REGISTERS = <R2,R3,R4,R5,R6,R7,R8,R9,R10,R11> ; Save registers
;
; The remainder of this routine runs as a Kernel Process. It is CALLed as a
; procedure by the Kernel Process Services and will therefore terminate with a
; RET instruction.
;
; Get a Queue Buffer and build a Read Counters command.
;
	MOVL	KPB$PS_SCSI_PTR1(R8),R4		; Restore SPDT address
	BSBW    GET_QUEUE_BUFFER                ; Get a Queue Buffer
	MOVL    R2,R7                           ; Copy Queue Buffer pointer
	MOVL	R8,QBUF$PS_KPB(R7)		; Save KPB address in Q_Buffer
	MOVQ	QBUF$PQ_PA_QBUF(R7),-		; Setup PA of CCB
		QBUF$PQ_CCB_ADDR(R7)	
	MOVW	#QBUF$C_READ_COUNTERS_SIZE,-	; Setup CCB size
		QBUF$IW_CCB_LENGTH(R7)
	MOVB    #QBUF$C_READ_CNTRS,-		; Set SIMport function code to
		QBUF$IB_FUNC_CODE(R7)		;  Read Counters
	CLRB	QBUF$IB_CAM_STS(R7)		; Clear CAM Status
	MOVB	SPDT$IB_PI_PATH_ID(R4),-	; Setup path id
		QBUF$IB_PATH_ID(R7)
	BISB	#QBUF$M_NO_DATA_TX,-		; Direction is no data
		QBUF$IB_CAM_FLAGS_1(R7)
	BISB	#QBUF$M_SIM_Q_FREEZE_DIS,-	; Set SIM queue freeze disable
		QBUF$IB_CAM_FLAGS_2(R7)
	BBCC	#SPDT$V_CTR_OVERFLOW,-		; Previous counter overflow?
		SPDT$IS_OVERFLOW(R4),10$
	BISW    #QBUF$M_CLR_COUNTERS,-		; Clear counters after read
		QBUF$IW_CTR_FLAGS(R7)
;
; Get a Carrrier and link the Carrier to the Queue Buffer and insert the
; Carrier onto the DACQ.
;
10$:	BSBW	GET_QUEUE_CARRIER		; Get a queue carrier
	MOVL    R7,CAR$PQ_QBUF_TOKEN(R2)	; Copy Queue Buffer virtual addr
	MOVB    QBUF$IB_FUNC_CODE(R7),-		; Put SIMport function code
		CAR$IB_FUNC_CODE(R2)		;  into Carrier
	CLRB	CAR$IB_STATUS(R2)		; Clear Status
	CLRW	CAR$IW_FLAGS(R2)		;  Flags
	CLRL	CAR$IS_CONNECT_ID(R2)		;  Connect id
	BSBW	INSERT_DACQ			; Insert Carrier onto DACQ
	MOVL	SPDT$L_DLCK(R4),KPB$PS_DLCK(R8)	; Copy device lock ptr to KPB
	EVAX_LDQ  R9,CAR$PQ_PA(R2)		; Get PA of new Stopper Carrier
	EVAX_SRL  R9,#TZA_DACQIR$C_PA_SHIFT,R9	; Shift PA right 3 bits
	DEVICELOCK -                            ; Lock device access
		LOCKADDR = SPDT$L_DLCK(R4),-
		LOCKIPL  = SPDT$B_DIPL(R4),-
		SAVIPL   = -(SP),-
		PRESERVE = NO
	WRITE_CSR REG  = DACQIR,-		; Notify adapter of an entry
		  DATA = R9,-			;  on the DACQ
		  TYPE = L
;
; Now wait for the channel to return a response to this command by
; suspending this I/O request to wait for the command completion interrupt.
;
	MOVAB	B^RDCNT_STALL, -		; Set stall routine address
		KPB$PS_SCH_STALL_RTN(R8)
	CLRL    KPB$PS_SCH_RESTRT_RTN(R8)	; No restart routine required
	PUSHL	R8				; Pass KPB address
	CALLS   #1,G^EXE$KP_STALL_GENERAL	; Call the general KP staller
;
; This is the point at which the stalled request resumes when the CNTRD
; response is returned. Copy the counters to the counters area in the AB.
; At this point:
;	R4 = SPDT address
;	R7 = Response Queue Buffer address
;	R8 = KPB address
;
PROCESS_COUNTERS:
	CMPB	#CAR$C_REQ_COMP_NO_ERROR,-	; Check return status
		QBUF$IB_CAM_STS(R7)
	BNEQ	40$				; Branch if the command failed
	CLRL	SPDT$IS_OVERFLOW(R4)		; Clear overflowed counter flag
	MOVAL	QBUF$IS_REQ_COUNTERS(R7),R3	; Point to beginning of counters
	MOVL	SPDT$PS_COUNTERS(R4),R1		; Get addr of port counter area
	MOVL	#QBUF$C_NUM_COUNTERS,R0		; Loop for all counters

20$:	CMPL	#^XFFFFFFFF,(R3)		; Did this counter overflow?
	BNEQ	30$				; Branch if no
	BISL	#SPDT$M_CTR_OVERFLOW,-		; Set counter overflow flag
		SPDT$IS_OVERFLOW(R4)		;  for next RDCNT cmd
30$:	MOVL	(R3)+,(R1)+			; Copy the counter to the AB
	SOBGTR	R0,20$				; Loop for all counters
;
;  Return resources and reschedule the next Read Counters command.
;
40$:	MOVL    R7,R0				; Get the Queue Buffer address
	BSBW    RET_QUEUE_BUFFER		; Return Queue Buffer to pool
	CLRL	SPDT$PS_RDCNT_KPB(R4)		; Indicate that a new KPB can
						;  be allocated next time
	MOVL	KPB$PS_SCSI_PTR2(R8),R3		; Restore CRB address
	ADDL3   #READCNTR_INTERVAL,-		; Set READ_COUNTER wakeup in
		G^EXE$GL_ABSTIM,-		;  1 minute and save timeout
		CRB$L_DUETIME(R3)		;  value in CRB
	RET					; Return and delete the KPB	

50$:	ADDL3   #READCNTR_INTERVAL,-		; Set READ_COUNTER wakeup in
		G^EXE$GL_ABSTIM,-		;  1 minute and save timeout
		CRB$L_DUETIME(R3)		;  value in CRB
60$:	RSB					; Return
	.DISABLE LSB				; READ_COUNTER

.PAGE
	.SBTTL	RDCNT_STALL - Read Counter stall routine
;
; RDCNT_STALL
;
; Do the DEVICEUNLOCK for the lock that was done in READ_COUNTER routine.
; 
; DEVICEUNLOCK is done here so that the port cannot interrupt us until
; this thread is stalled properly.
;
RDCNT_STALL:
	.CALL_ENTRY	<R1>

	MOVL	4(AP),R1			; Get KPB parameter
	MOVL	KPB$PS_SCSI_PTR1(R1),R4		; Get SPDT address from KPB
	DEVICEUNLOCK -				; Release device lock
		LOCKADDR = SPDT$L_DLCK(R4),- 
		NEWIPL   = KPB$IS_NEWIPL(R1),-
		PRESERVE = NO
	RET					; Return

.PAGE
	.SBTTL	SETUP_SANITY_TQE - Set up the TQE for Adapter Sanity
;
; This subroutine is called once per port at the end of the initialization
; of the port to set up a repeating TQE entry for checking adapter sanity.
; This routine makes sure that TQE is set up only once per port. The TQE
; timeout interval is set to 10 seconds.
;
; INPUTS:
;	R4 = SPDT address
;	IPL$_IOLOCK8
;
; OUTPUTS:
;	None
;
;  R2 and R3 are destroyed by EXE$INSTIMQ routine.  Hence we
;  need to save R3 here.
;
	.ENABLE LSB
SETUP_SANITY_TQE:
	.JSB_ENTRY	INPUT=<R4>,SCRATCH=<R0,R1,R2>,PRESERVE=<R3,R4,R5>

	MOVAL   SPDT$B_TQE(R4),R5		; Get TQE address
	TSTB	TQE$B_TYPE(R5)			; Is the TQE set up already?
	BNEQ	10$				; Branch and exit if yes
	MOVB    #DYN$C_TQE,-			; Set type
		TQE$B_TYPE(R5)
	MOVW    #TQE$C_LENGTH,-			; Set length
		TQE$W_SIZE(R5)
	MOVB    #TQE$C_SSREPT,-			; Make this a repeating TQE
		TQE$B_RQTYPE(R5)
	CLRL    TQE$L_RMOD(R5)			; No AST required
	MOVAB   B^ADP_SANITY_TIMEOUT,-		; Address of wakeup routine
		TQE$L_FPC(R5)
.Disable Flagging				; Turn off info, it's aligned
	MOVQ    #TQE_TIMER_EXPIRE,-
		TQE$Q_DELTA(R5)			; Compute next timeout (10sec)
.Enable Flagging
	MOVX    R4,TQE$Q_FR4(R5)		; Save SPDT address in TQE
	READ_SYSTIME    R0			; Get current time
	ADDL    TQE$Q_DELTA(R5),R0		; Compute next timeout (10sec)
	ADWC    TQE$Q_DELTA+4(R5),R1		; for insertion into queue
	JSB     G^EXE$INSTIMQ			; Insert into timer queue

10$:	RSB					; Return
	.DISABLE LSB				; SETUP_SANITY_TQE

.PAGE
	.SBTTL	ADP_SANITY_TIMEOUT - TQE routine to check Adapter Sanity
;
; This routine is called as a recurring system timer event (TQE) every 
; TQE_TIMER_EXPIRE seconds.
;
; It traverses the port's I/O completion wait queue searching for any 
; stalled I/O thread which timed out waiting for a response. If the port is 
; hung for some reason and cannot even generate an error interrupt, the system
; can hang indefinitely unless we reset the port. Hence if this routine finds 
; a timed out thread, it will start initiating the resetting of the port.  
; This is accomplished by issuing a bus reset and then re-enabling the port.
; The timeout time for the I/O thread is set sufficiently large enough
; (currently 3 minutes) so that we will not encounter a case where the port
; was still functioning but we end up doing the reset.
;
; INPUTS:
;	R4 = SPDT address
;	R5 = Address of the TQE that causes this routine to execute
;
; OUTPUTS:
;	None
;
	.ENABLE LSB
ADP_SANITY_TIMEOUT:
	.JSB_ENTRY	INPUT=<R4,R5>,-
			SCRATCH=<R0,R1,R2,R3>,-
			PRESERVE=<R5,R6>
;
; Get the fork lock for the port.
;
	MOVL	SPDT$IS_FLCK(R4),R5		; Get fork lock index
	FORKLOCK -				; Lock it
		LOCK     = R5, -		;  (do not preserve R0)
		SAVIPL   = -(SP), -
		PRESERVE = NO

	MOVAL	SPDT$PS_WCMDFL(R4),R1		; Get wait queue header address
	MOVL	R1,R2				; Copy for reference
10$:	MOVL	(R2),R2				; Get next KPB in the queue
	CMPL	R2,R1				; End of the queue?
	BEQL	40$				; Branch if yes
	MOVAB	-KPB$PS_FQFL(R2),R3		; Point to beginning of KPB
	MOVL	KPB$PS_SCSI_SCDRP(R3),R5	; Get SCDRP address in KPB
	CMPL	SCDRP$L_DUETIME(R5),-		; Has the timeout time passed?
		G^EXE$GL_ABSTIM
	BGTRU	10$				; No, check next I/O thread
;
; The port must be hung somehow since an I/O thread waited for more than 3
; minutes. Try to do bus reset and re-enable the port. As part of the process,
; the stalled I/O threads will be resumed. Since RESET_SCSI_BUS needs to run
; in a Kernel Process context, we need to switch to a Kernel Process here.
;
20$:	BBS	#SPDT$V_PWF_CLNUP,-		; Skip resetting the bus if we are in the
		SPDT$L_STS(R4),40$		;  middle of adapter cleanup/reinit.
	BBS	#SPDT$V_CHNL_CLNUP,-		; Skip resetting the bus if we already did
		SPDT$L_STS(R4),40$		;  and we are cleaning up channel resources

	TSTL    SPDT$PS_BUSRESET_KPB(R4)	; Is there a reset KP running already?
	BNEQ    40$				; Branch and exit if yes
	KP_ALLOCATE_KPB -			; Allocate a kernel process block
		KPB   = SPDT$PS_BUSRESET_KPB(R4),-
		STACK = G^MMG$GL_PAGE_SIZE,-	;  Set the stack size
		FLAGS = #KP$M_IO			;  Use VEST format and deallocate
						;   the KPB when done
	BLBS	R0,50$				; Branch if KPB allocated
	CMPL    #SS$_INSFRPGS,R0		; Check for no free PFNs
        BEQL    30$				; Yes, try waiting
        CMPL    #SS$_INSFMEM,R0			; Check for allocation failure
        BEQL	30$				; Yes, try waiting
	BUG_CHECK  INCONSTATE,FATAL		; Die since we couldn't allocate KPB

ASSUME FKB$K_LENGTH EQ SPDT$C_BUSRESET_FKBLK	; Be sure FKB is big enough

30$:	MOVAB	SPDT$PS_BUSRESET_FKBLK(R4),R5	; Fork on SPDT fork block
	MOVB	#SPL$C_IOLOCK8,FKB$B_FLCK(R5)	; Set correct fork lock
	MOVL	SPDT$IS_FLCK(R4),R6		; Get fork lock index
	FORKUNLOCK -				; Unlock it
		LOCK     = R6, -		;  (do not preserve R0)
		NEWIPL   = (SP)+, -
		PRESERVE = NO
	FORK_WAIT				; Fork wait
        MOVL    SPDT$IS_FLCK(R4),R5             ; Get fork lock index
	FORKLOCK -				; Lock it
		LOCK     = R5, -		;  (do not preserve R0)
		SAVIPL   = -(SP), -
		PRESERVE = NO
.Disable Flagging				; Turn off information
	BRB	20$				; Try allocation again
.Enable Flagging				; Turn on information

40$:	MOVL	SPDT$IS_FLCK(R4),R5		; Get fork lock index
	FORKUNLOCK -				; Unlock it
		LOCK     = R5, -		;  (do not preserve R0)
		NEWIPL   = (SP)+, -
		PRESERVE = NO
	MOVAL	SPDT$B_TQE(R4),R5		; Restore TQE address
	RSB					; Return to TQE dispatcher

50$:	MOVL	SPDT$PS_BUSRESET_KPB(R4),R6	; Get the KPB address in R6
	MOVL	R4,KPB$PS_SCSI_PTR1(R6)		; Save the SPDT address in KPB
	MOVL	SPDT$L_PORT_UCB(R4),-		; Set this port's UCB addr in
                 KPB$PS_UCB(R6)			;  KPB
;
; Note that the following macro has to be expanded out here explicitly so 
; that we can do FORKUNLOCK before RSBing back to the TQE dispatcher to
; avoid synchronization problem with the rest of the system.
;
;        KP_SWITCH_TO_KP_STACK -                 ; Start the kernel process
;                KPB = R6,-                        ; KPB pointer
;                REGISTERS = <R2,R3,R4,R5,R6,R7,R8,R9,R10,R11> ; Preserved registers
;
;
	KP_START R6,SWITCH_LOC,<#^M<R2,R3,R4,R5,R6,R7,R8,R9,R10,R11>> 
						; Start the kernel process

	MOVL    SPDT$IS_FLCK(R4),R5             ; Get fork lock index
	FORKUNLOCK -                            ; Unlock it
		LOCK     = R5, -		;  (do not preserve R0)
		NEWIPL   = (SP)+, -
		PRESERVE = NO
	MOVAL	SPDT$B_TQE(R4),R5		; Restore TQE address
	RSB					;  and return to TQE dispatcher

SWITCH_LOC:	.CALL_ENTRY PRESERVE=<R2,R3,R4,R5,R6,R7,R8>        
						; Embedded kernel process routine
	MOVL	4(AP),R6			; Pick up pointer to KPB

;
; The remainder of this routine runs as a kernel process. It is CALLed as a
; procedure by the Kernel Process Services and will therefore terminate with a
; RET instruction.
;
	MOVL	KPB$PS_SCSI_PTR1(R6),R4		; Get SPDT address
	CLRL	R3				; No associated SCDT to pass
	MOVL	SPDT$PS_KPB(R4),R8		; Get init process KPB
	MOVL	KPB$PS_UCB(R8),R5		; Get port's UCB address
	BICL	#UCB$M_ONLINE,UCB$L_STS(R5)     ; Clear port UCB ONLINE
	MOVL	R4,KPB$PS_SCSI_PTR1(R8)		; Store SPDT into KPB
	PUSHL	KPB$PS_SCSI_SCDRP(R8)		; Save SCDRP from KPB
	BSBW	GET_SCDRP			; Allocate the SCDRP
	MOVL	R2,R5				; Save the SCDRP
	MOVL	R2,KPB$PS_SCSI_SCDRP(R8)	; Save SCDRP in KPB
	MOVL	R8,SCDRP$PS_KPB(R5)		; Store KPB into SCDRP
	BISL	#SPDT$M_INIT_ADAPTER,-		; Reinit the adapter
		SPDT$IS_PORTSTS(R4)
	BISL	#SPDT$M_LOG_ERROR,-		; Log this in error log
		SPDT$IS_PORTSTS(R4)
	PUSHL	R5				; Pass SCDRP
	PUSHL	R4				; Pass SPDT
	CALLS	#2,@SPDT$PS_CD_RESET_SCSI_BUS(R4) ; Reset SCSI bus
	MOVL	R5,R0				; Deallocate the SCDRP
	CALL_DRVDEALMEM	SAVE_R0R1=NO		; COM_STD$DRVDEALMEM
	POPL	KPB$PS_SCSI_SCDRP(R8)		; Clear the SCDRP pointer
	BISL    #SPDT$M_STS_ONLINE,-		; Set SPDT online
		SPDT$L_STS(R4)
	CLRL	SPDT$PS_BUSRESET_KPB(R4)	; Indicate that a new KPB can
						;  be allocated next time
	RET					; Return
	.DISABLE LSB				; TQE_TIMEOUT
	.END
