#pragma module DKMK "X-13"
/*	Class driver common IO_DIAGNOSE routines
 *
 *****************************************************************************
 * 
 * Copyright © Digital Equipment Corporation, 1994-1996 All Rights Reserved.
 * Unpublished rights reserved under the copyright laws of the United States.
 * 
 * The software contained on this media is proprietary to and embodies the
 * confidential technology of Digital Equipment Corporation.  Possession, use,
 * duplication or dissemination of the software and media is authorized only
 * pursuant to a valid written license from Digital Equipment Corporation.
 * 
 * RESTRICTED RIGHTS LEGEND   Use, duplication, or disclosure by the U.S.
 * Government is subject to restrictions as set forth in Subparagraph
 * (c)(1)(ii) of DFARS 252.227-7013, or in FAR 52.227-19, as applicable.
 * 
 *****************************************************************************
 *
 *          
 */ 

/*++                    
 * FACILITY: SCSI
 *                          
 * ABSTRACT:
 *
 *	This module contains routines callable by any SCSI class
 *	driver to execute the IO$_DIAGNOSE function.
 *
 * AUTHORS:
 *
 *	Sue Sommer				CREATION DATE: 9-June-1994
 *
 * REVISION HISTORY:
 *
 *	X-13	RCL		Rick Lord			3-Apr-1996
 *		Replace cast of irp$l_qio_p6 to (int) in assignment to 
 *		scdrp$ps_qio_p6 with a cast to (void*) to eliminate a 
 *		warning message. This didn't show up in my testing after
 *		X-12 because I was not using the same compiler qualifiers
 *		as is in SCSIASM.DAT.
 *
 *	X-12	RCL		Rick Lord			1-Apr-1996
 *		Remove local definition of SCDRP$PS_QIO_P6, as this symbol
 *		has been added to SCDRPDEF
 *
 *	X-11	GWW		Grace W. Wang			29-Dec-1995
 *		(QAR Gryphon #146)
 *		Fix IO$_DIAGNOSE cancellation problem: MKDRIVER attempts to
 *		complete the current I/O when its SCSI command completes, but
 *		finds the current IRP has already been finished in MK_CANCEL.  
 *		The change in DKMK$DIAGNOSE_SIO is: walk away from cancelled 
 *		I/O if UCB$M_CANCEL in UCB$L_STS is set.  
 *
 *		Update Copyright statement.
 *
 *	X-10	SCS		Sue Sommer			29-Sep-1995
 * 		Modify the FDT routine to assign iostat1 correctly, as follows:
 *              - Change the logical-or (||) to a bit-or (|)
 *              - The assignment of iostat now goes before (not after) the
 *                deallocation of the system-supplied autosense buffer.
 *		These two changes allow the autosense byte count to be returned
 *              correctly in the IOSB.
 *
 *	X-9	EMB		Ellen M. Batbouta		 10-Mar-1995
 *		Declare the system data cells, MMG$GL_BWP_PAGE and MMG$GL_PAGE_
 *		SIZE, as CONST (their value doesn't change).
 *
 *	X-8	SCS		Sue Sommer			 27-Feb-1995
 *		Fix error routines so that they unlock any locked buffers.
 *		
 *	X-7	SCS		Sue Sommer			 15-Feb-1995
 *		Add support for 64-bit addressing:
 *			- create local copies of s2dgb fields to handle
 *			  both 32-bit and 64-bit cases more easily.
 *			- include far_pointers.h for pointer types.
 *			- cast addresses as VOID_PQ.
 *			- replace irpe$l_svapte1/boff1/bcnt1
 *			     with irpe$l_svapte/boff/bcnt.
 *
 *	X-6	NYK252		Nitin Y. Karkhanis		 1-Feb-1995
 *		Mmg$gl_sptbase should be declared as PTE *.
 *
 *	X-5	SCS		Sue Sommer			 9-Jan-1995
 *		Include additional checks in the FDT routine:
 *		  - Disallow an autosense buffer address of zero whenever
 *		    s2dgb$v_autosense is set.
 *		  - Check for legal values in s2dgb$v_tag.
 *		
 *	X-4							2-Nov-1994
 *		(Redundant checkin.  Same as X-3.)
 *
 *	X-3	SCS		Sue Sommer			25-Oct-1994
 *		Cast VMS return status code as a word, not as a byte.
 *
 *	X-2	SCS		Sue Sommer			13-Oct-1994
 *	      	Allow connection characteristics to be changed (or not)
 *		based on SCDT fields rather than SPDT fields.
 *		
 *	X-1	JFD0660		James F. Dunham			28-SEP-1994
 *		SCSI-2 Checkin
 *
 *	************* Change Edit History to Match CMS ********************
 *
 *	X-10	SCS		Sue Sommer			16-Sep-1994
 *		Support untagged requests.
 *		Make sure to zero cucb_ptr->ps_autosense when deallocating
 *		its buffer.
 *
 *	X-9	SCS		Sue Sommer			31-Aug-1994
 *		In the FDT routine, fill in the size of the allocated IRPE
 *		correctly (equals sizeof(IRP), not sizeof(IRPE)).
 *		Also, whenever the CUCB is being used for potential autosense
 *		data, move the allocated pool packet address into IRP$L_QIO_P6
 *		during the FDT routine.
 *		
 *	X-8	SCS		Sue Sommer			15-Aug-1994
 *		Set scdrp$is_queue_char correctly before sending the command.
 *
 *	X-7	SCS		Sue Sommer			29-Jul-1994
 *		Fill in SCSI status in IOSB earlier, before deallocating the
 *		command buffer.
 *
 *	X-6	SCS		Sue Sommer			19-Jul-1994
 *		- Wait until after cmd_buff_dealloc to restore connection
 *		  characteristics, in deference to non-TCQ ports with Q-buffers.
 *		- Modify the FDT routine to deallocate the CDB in error exit.
 *		- Also in FDT routine, zero out IRP fields after copying them
 *		  to the IRPE;  this prevents double unlocking in the abortio
 *		  path.
 *		- Do not call abortio in the event of readlock failure, since
 *		  readlock already calls abortio.
 *
 *	X-5	SCS		Sue Sommer			18-Jul-1994
 *		Clear irp$l_bcnt before checking for user buffer bcnt.
 *
 *	X-4	SCS		Sue Sommer			12-Jul-1994
 *		Modify FDT routine to always call ABORTIO in error paths,
 *		so that errors get correctly propagated to the user.
 *		Also return IOSB values through call_finishioc when 
 *		appropriate in the FDT routine.
 *
 *	X-3	SCS		Sue Sommer			8-Jul-1994
 *		Fix bug which corrupted size field of CUCB asense buffer.
 *		Fix address bug in deallocation of CUCB buffer.
 *		Fix bug in copying the CUCB buffer to the DATAIN buffer.
 *		Change IOSB tranfer count value to reflect scdrp$l_trans_cnt
 *		instead of scdrp$l_bcnt.
 *
 *	X-2	SCS		Sue Sommer			1-Jul-1994
 * 		Fill in arg count in connection characteristics buffer.
 *		TEMPORARY:  Hardcode sychronous = 1 in characteristics.
 *
 *	X-1	SCS		Sue Sommer			9-Jun-1994
 *		Initial version of Diagnose routines, based on Ralph Weber's
 * 		design spec.
 *--
 */


/* Define system data structure types and constants */

#include <ccbdef.h>             /* Channel control block */
#include <dyndef.h>		/* Data structure type definitions */
#include <irpdef.h>             /* I/O Request Packet */
#include <irpedef.h>            /* I/O Request Packet Extension */
#include <pcbdef.h>		/* Process control block */
#include <ptedef.h>		/* Page table entries */
#include <scdtdef.h>		/* SCSI connection descriptor table */
#include <scdrpdef.h>		/* SCSI request packet definitions */
#include <scsidef.h>		/* SCSI definitions */
#include <spidef.h>             /* SCSI class/port interface */
#include <spdtdef.h>            /* SCSI port descriptor table */
#include <ssdef.h>              /* System service status codes */
#include <stsdef.h>             /* OpenVMS status codes */
#include <ucbdef.h>             /* Unit control block          */
#include <vadef.h>		/* Virtual address definitions */
#include <vms_drivers.h>        /* Driver macros */
#include <s2dgbdef.h>           /* SCSI2 Diagnose Buffer definitions */
#include <far_pointers.h>	/* 64-bit pointer types */

/* Define function prototypes for system routines */

#include <exe_routines.h>       /* Prototypes for exe$ and exe_std$ routines */
#include <ldr_routines.h>       /* Prototypes for ldr$ and ldr_std$ routines */
#include <mmg_routines.h>	/* Prototypes for mmg$ and mmg_std$ routines */

/* Define the DEC C functions used by this driver */

#include <builtins.h>           /* OpenVMS AXP specific C builtin functions */
#include <ints.h>		/* Define int64, etc. 			    */
#include <string.h>             /* String routines provided by "kernel CRTL"*/
#include <vms_macros.h>		/* VMS macros */

/* Set up Boolean type and values */

typedef char BOOLEAN;
#define TRUE 1
#define FALSE 0

/* Local structure definitions */
typedef struct 
{
    char *ps_autosense;		/* Saved Autosense data pointer */
    PTE  *ps_as2_svapte; 	/* Double map SVAPTE for Autosense */
    char *ps_as2_sva;	   	/* Double map SVA for Autosense */
} CUCB;

/* Local copies of S2DGBDEF fields */
VOID_PQ cdbaddr;
VOID_PQ dataddr;
VOID_PQ senseaddr;
unsigned int cdblen;
unsigned int datlen;
unsigned int senselen;
unsigned int padcnt;
unsigned int phstmo;
unsigned int dsctmo;

/* External references */
extern PTE * const mmg$gl_sptbase;
extern int mmg$gl_pte_offset_to_va;
extern const int mmg$gl_bwp_mask;
extern const int mmg$gl_page_size;

/* External Function Prototypes */

/* Macros */

#define call_port(a) (* ((SPDT *)(ucb_ptr->ucb$l_pdt))->spdt$ps_##a)


/*
 *---------------------------------------------------------------------------
 *
 * DKMK$DIAGNOSE_INIT
 *
 *     Description:
 *
 *     	    The class drivers shall call this routine from
 *          the unit initialization routine.  The call shall provide the
 *          IO$_DIAGNOSE common code with an opportunity to initialize for
 *          the processing of future IO$_DIAGNOSE operations.
 *
 *     Input Parameters:
 *
 *          cucb_ptr - base address of at least 6 longwords of class driver 
 *		 UCB storage set aside by the class driver for the exclusive 
 *               use of the IO$_DIAGNOSE common code.
 *		 The first 3 longwords are:  
 *			char *cucb$ps_autosense - Saved autosense data ptr
 * 			PTE *cucb$ps_as2_svapte - Double map SVAPTE
 *			char *cucb$ps_as2_sva   - Double map SVA
 *          cucb_len - number of longwords available to the IO$_DIAGNOSE 
 *		 common code starting at the P1 base address.
 *
 *     Implicit Inputs:    None
 *
 *     Return Value:       VMS status
 *
 *     Output Parameters:  None
 *
 *     Implicit Outputs
 *
 *          Initialization of the UCB longwords defined by inputs P1 and P2.
 *
 *--------------------------------------------------------------------------*/

int dkmk$diagnose_init(CUCB *cucb_ptr,
		       int cucb_len)

{
    int status;
    int temp;

    cucb_len = 4 * cucb_len;				/* Convert to bytes */
    if (cucb_len < sizeof(CUCB))     			/* Validate size of CUCB */
        status = SS$_BADPARAM; 
    else
    {
        cucb_ptr->ps_autosense = NULL;		/* Zero the a-sense data ptr */
    
        						/* Allocate a system PTE, */
    							/* and save its addr in cucb */
        status = ldr_std$alloc_pt(1, (void *)&cucb_ptr->ps_as2_svapte);
        if (! $VMS_STATUS_SUCCESS(status))
            return (status);
        						/* Get offset into pg table */
        temp = (int) cucb_ptr->ps_as2_svapte - (int) mmg$gl_sptbase;
        temp <<= mmg$gl_pte_offset_to_va;
        cucb_ptr->ps_as2_sva = (char *)(temp | VA$M_SYSTEM); 
    }
    return (status);
}

/*
 *---------------------------------------------------------------------------
 *
 * DKMK$DIAGNOSE_FDT
 *
 *     Description:
 *
 *     This common FDT support routine shall be called from the individual
 *     class driver's FDT action routine for the IO$_DIAGNOSE function. 
 *
 *     N.B. The class driver shall always use the port driver's
 *     autosense feature.  When S2DGB$V_AUTOSENSE is zero, the
 *     class driver shall save the autosense data in pool and
 *     return it to the next IO$_DIAGNOSE, if that IO$_DIAGNOSE
 *     has a REQUEST SENSE CDB.
 *
 *     Input Parameters:
 *
 *          irp_ptr  -  Pointer to I/O request packet
 *          pcb_ptr  -  Pointer to process control block 
 *          ucb_ptr  -  Pointer to class driver unit control block.
 *	    ccb_ptr  -  POinter to channel control block
 *          cucb_ptr -  Pointer to class driver UCB storage set aside for
 *                      the exclusive use of the IO$_DIAGNOSE common code 
 *			(same value as passed to DKMK$DIAGNOSE_INIT)
 *
 *     Implicit Inputs:
 *
 *          IRP$Q_QIO_P1  - base address of the S2DGB. 
 *          IRP$L_QIO_P2  - size of the S2DGB.
 *          cucb$ps_autosense - addr of saved autosense data or zero.
 *
 *     Return Value:       Status (as per HLL driver FDT rules)
 *
 *     Output Parameters:  None
 *
 *     Implicit Outputs
 *
 *          cucb$ps_autosense = zero 
 *          IRP$L_MEDIA         address of BUFIO packet containing CDB
 *          IRP$L_BCNT          BCNT of DATAIN/DATAOUT buffer (or zero)
 *          IRP$L_BOFF          BOFF of DATAIN/DATAOUT buffer (if any)
 *          IRP$L_SVAPTE        SVAPTE of DATAIN/DATAOUT buffer (if any)
 *          IRP$L_QIO_P3        copy of FLAGS from S2DGB
 *          IRP$L_QIO_P4        copy of PADCNT from S2DGB
 *          IRP$L_QIO_P5<00:15> copy of PHSTMO from S2DGB
 *          IRP$L_QIO_P5<16:31> copy of DSCTMO from S2DGB
 *          IRP$L_QIO_P6        SVA of saved autosense data packet (if any)
 *          IRP$V_EXTEND        1 if IRPE allocated for autosense
 *          IRP$L_EXTEND        pointer to autosense IRPE (if any)
 *          IRPE$L_BOFF         BOFF of autosense buffer (if any)
 *          IRPE$L_BCNT         BCNT of autosense buffer (if any)
 *          IRPE$L_SVAPTE       SVAPTE of autosense buffer (if any)
 *	    iosb contents	(call_finishio case only)
 *
 *          N.B. Class driver code may depend on IRP$L_QIO_P3 containing a
 *          copy of S2DGB$L_FLAGS.  This implicit output cannot be changed
 *          without reviewing the class drivers.
 *
 *---------------------------------------------------------------------------*/

int dkmk$diagnose_fdt(IRP *irp_ptr,
		      PCB *pcb_ptr,
		      UCB *ucb_ptr,
		      CCB *ccb_ptr,
		      CUCB *cucb_ptr)

{
    S2DGB_PQ s2dgb_ptr;
    int s2dgb_len;
    int status, iostat1, iostat2;
    IRPE *irpe_ptr;
    char *opcode;
    void error_rtn1();
    void error_rtn2();
    unsigned int phstmo_tmp, dsctmo_tmp;
    typedef struct 
        {
	int flink;
	int blink;
	short size;
	char type;
	char subtype;
        } BUFIO_HEADER;
    BUFIO_HEADER *asense_hdr_ptr, *cdb_hdr_ptr, *cucb_asense_hdr_ptr;
    char *cdb_data_ptr, *cucb_asense_data_ptr;
    int32 asense_buff_len, cdb_buff_len;
    asense_buff_len = sizeof(BUFIO_HEADER) + 256; /* System-supplied buff */

    s2dgb_ptr = (S2DGB *) irp_ptr->irp$q_qio_p1;
    s2dgb_len = irp_ptr->irp$l_qio_p2;

    /* Validate the S2DGB for read accessibility */

    status = exe_std$writechk(irp_ptr, pcb_ptr, ucb_ptr,s2dgb_ptr,s2dgb_len);
    if (! $VMS_STATUS_SUCCESS(status))
        return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, status));

    /* Validate S2DGB length */

    if (s2dgb_len < S2DGB$K_XCDB_MIN_LENGTH)
	return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, SS$_BADPARAM)); 

    /* Copy S2DGB fields into local variables */

    if (s2dgb_ptr->s2dgb$l_opcode == S2DGB$K_OP_XCDB32)   /* 32-bit case   */
    {                                                         
	cdbaddr = s2dgb_ptr->s2dgb$l_32cdbaddr; 	  /* Cmd desc blk  */ 
	dataddr = s2dgb_ptr->s2dgb$l_32dataddr; 	  /* Data buffer   */
	senseaddr = s2dgb_ptr->s2dgb$l_32senseaddr; 	  /* Asense addr   */
	cdblen = s2dgb_ptr->s2dgb$l_32cdblen;             /* CDB length    */
	datlen = s2dgb_ptr->s2dgb$l_32datlen;             /* Data buff len */
	senselen = s2dgb_ptr->s2dgb$l_32senselen;         /* Asense len    */
	padcnt = s2dgb_ptr->s2dgb$l_32padcnt;             /* Pad count     */
	phstmo = s2dgb_ptr->s2dgb$l_32phstmo;             /* Phase tmo     */
	dsctmo = s2dgb_ptr->s2dgb$l_32dsctmo;             /* Diconnect tmo */
    }

    else if (s2dgb_ptr->s2dgb$l_opcode == S2DGB$K_OP_XCDB64) /* 64-bit case*/
    {                                                         
	cdbaddr = (VOID_PQ) s2dgb_ptr->s2dgb$pq_64cdbaddr; /* Cmd desc blk  */ 
	dataddr = (VOID_PQ) s2dgb_ptr->s2dgb$pq_64dataddr; /* Data buffer   */
	senseaddr = (VOID_PQ) s2dgb_ptr->s2dgb$pq_64senseaddr; /* Asense addr*/
	cdblen = s2dgb_ptr->s2dgb$l_64cdblen;              /* CDB length    */
	datlen = s2dgb_ptr->s2dgb$l_64datlen;              /* Data buff len */
	senselen = s2dgb_ptr->s2dgb$l_64senselen;          /* Asense len    */
	padcnt = s2dgb_ptr->s2dgb$l_64padcnt;              /* Pad count     */
	phstmo = s2dgb_ptr->s2dgb$l_64phstmo;              /* Phase tmo     */
	dsctmo = s2dgb_ptr->s2dgb$l_64dsctmo;              /* Diconnect tmo */
    }

    else                                                  /* Bad opcode    */
	return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, SS$_BADPARAM)); 


    /* Validate S2DGB values */
    if ((cdblen < 2) || 
	(cdblen > 248))
	return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, SS$_BADPARAM)); 

    if ((datlen < 0) || 
	(datlen > ((DT_UCB *) ucb_ptr)->ucb$l_maxbcnt))
	return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, SS$_BADPARAM)); 

    if ((padcnt < 0) || 
	(padcnt > 511))
	return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, SS$_BADPARAM)); 

    if ((phstmo < 0) || 
	(phstmo > 300))
	return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, SS$_BADPARAM)); 

    if ((dsctmo < 0) || 
        (dsctmo > 65535))
	return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, SS$_BADPARAM)); 

    if ((senselen < 0) || 
        (senselen > 255))
	return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, SS$_BADPARAM)); 

    /* Check for legal queue characteristic fields */
    if (s2dgb_ptr->s2dgb$v_tagged_req) 
    {
        if ((s2dgb_ptr->s2dgb$v_tag != S2DGB$K_SIMPLE) && 
            (s2dgb_ptr->s2dgb$v_tag != S2DGB$K_ORDERED) &&
            (s2dgb_ptr->s2dgb$v_tag != S2DGB$K_EXPRESS))
	    return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, SS$_BADPARAM)); 
    }

    /* Process the autosense buffer:

       If (v_autosense != 0) and (asense buflen == 0) and (asense bufaddr == 0)
		then accept autosense data and discard it.
       If (v_autosense != 0) and (asense buflen == 0) and (asense bufaddr <> 0)
		then accept autosense data and discard it.
       If (v_autosense != 0) and (asense buflen <> 0) and (asense bufaddr == 0)
		then return SS$_ACCVIO.
       If (v_autosense != 0) and (asense buflen <> 0) and (asense bufaddr <> 0)
		then return autosense data if present.
    */

    if (s2dgb_ptr->s2dgb$v_autosense != 0) 
    {    
        if (senselen != 0)
	/* Supplied (S2DGB) autosense buffer has non-zero length */
        {
            if (senseaddr == 0)
	        return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, 
			SS$_ACCVIO)); 
	
            /* Allocate an IRPE to describe the autosense buffer */
	    status = exe_std$allocirp ((IRP **)&irpe_ptr);
            if (! $VMS_STATUS_SUCCESS(status))
                return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, status));

	    /* Zero the IRPE and initialize fields */
	    memset( (char*)irpe_ptr+sizeof(BUFIO_HEADER),0,
		    (sizeof(IRPE) - sizeof(BUFIO_HEADER)) );
	    irpe_ptr->irpe$b_type = DYN$C_IRPE; 
	    irpe_ptr->irpe$b_rmod = irp_ptr->irp$b_rmod; 

	    /* Modify IRP fields */
	    irp_ptr->irp$l_extend = irpe_ptr;
	    irp_ptr->irp$v_extend = 1;

	    /* Get parent pointer in case we have a readlock error and
	       need to switch back to the original IRP */
	    irpe_ptr->irpe$l_extend = (IRPE *) irp_ptr;

	    /* Validate, probe and lock the autosense buffer for write */
	    status = exe_std$readlock((IRP *)irpe_ptr, pcb_ptr, ucb_ptr, 
				 ccb_ptr, senseaddr,
				 senselen,
				 error_rtn2);
            if (! $VMS_STATUS_SUCCESS(status))
                return (status);
        }

        else			
	/* Supplied (S2DDGB) autosense buffer has length of zero */
	{
	    irp_ptr->irp$v_extend = 0;

        }
        if (cucb_ptr->ps_autosense != NULL)
	/* Both CUCB and S2DGB buffers exist, so deallocate the CUCB one */
        {
            status = exe_std$deanonpaged(cucb_ptr->ps_autosense);
            if (! $VMS_STATUS_SUCCESS(status))
                return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, status));
	    cucb_ptr->ps_autosense = NULL;
        }

    }
    else
    /* No buffer was supplied in the S2DGB */
    {
        if (cucb_ptr->ps_autosense == NULL)
	/* Nor does prior autosense data exist as described by the CUCB.
        Allocate pool buffer to hold any possible upcoming autosense data. */
        {

            status = exe_std$allocbuf(asense_buff_len,&asense_buff_len,
				      (void **) &asense_hdr_ptr);
            if (! $VMS_STATUS_SUCCESS(status))
                return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, status));

            /* Store its address in QIO P6 parameter */
	    irp_ptr->irp$l_qio_p6 = (int) asense_hdr_ptr;

	    /* Initialize the packet */
            asense_hdr_ptr->size = asense_buff_len;
            asense_hdr_ptr->flink = (int) ucb_ptr;
            asense_hdr_ptr->blink = 0;
            asense_hdr_ptr->type = DYN$C_BUFIO;
            asense_hdr_ptr->subtype = 0;
        }
        else
        /* No S2DGB buffer supplied, but the CUCB buffer indicates valid data
           from a previous autosense operation. */
        {
            cucb_asense_hdr_ptr = (BUFIO_HEADER *)(cucb_ptr->ps_autosense);
            cucb_asense_data_ptr = (char *)cucb_asense_hdr_ptr + sizeof(BUFIO_HEADER);
	    opcode = (char *) cdbaddr;
            if (*opcode == 3)
	    /* If the CDB specifies a REQUEST SENSE command,
               that means we want the prior autosense data */
            {
	        /* Validate, probe the DATAIN buffer for write */
	        status = exe_std$readchk(irp_ptr, pcb_ptr, ucb_ptr,  
				 dataddr,
				 datlen);
                if (! $VMS_STATUS_SUCCESS(status))
                    return (status);

                /* Copy the data from CUCB buffer to DATAIN buffer */
                memmove(dataddr,
			cucb_asense_data_ptr,
                        cucb_asense_hdr_ptr->blink);

		/* Save the autosense data length in IOSB format */
		iostat1 = ((cucb_asense_hdr_ptr->blink << 16) & 0xffff0000) |
			    SS$_NORMAL;
		iostat2 = ((cucb_asense_hdr_ptr->blink >> 16) & 0x0000ffff); 

	        /* Deallocate the CUCB buffer now that we have the data */
                status = exe_std$deanonpaged(cucb_ptr->ps_autosense);
	        cucb_ptr->ps_autosense = NULL;
                if (! $VMS_STATUS_SUCCESS(status))
                    return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, status));

	  	/* Autosense data has been transferred, no need to go
		on to startio */

                return (call_finishio (irp_ptr, ucb_ptr, iostat1, iostat2));
            }
            else 
	    /* If the CDB does not specify a REQUEST SENSE command */
            {
                /* Clear 2nd longword of CUCB buff header, indicating that
		no autosense data is available */
                cucb_asense_hdr_ptr->blink = 0;
                /* Store its address in QIO P6 parameter */
	        irp_ptr->irp$l_qio_p6 = (int) (cucb_ptr->ps_autosense); 
            }
        }
    }

    /* Now that the autosense processing is handled, take the
    necessary steps to send the SCSI command */

    /* Allocate a CDB */
    cdb_buff_len = sizeof(BUFIO_HEADER) + cdblen;
    status = exe_std$allocbuf(cdb_buff_len,(int32 *)&cdb_buff_len,
	(void **) &cdb_hdr_ptr);
    if (! $VMS_STATUS_SUCCESS(status))
        return (call_abortio (irp_ptr, pcb_ptr, ucb_ptr, status));
    cdb_data_ptr = (char *) cdb_hdr_ptr + sizeof(BUFIO_HEADER);

    /* Store address of CDB packet in irp */
    irp_ptr->irp$l_media = (int) cdb_hdr_ptr;

    /* Initialize the packet */
    cdb_hdr_ptr->flink = (int) ucb_ptr;
    cdb_hdr_ptr->blink = 0;
    cdb_hdr_ptr->size = cdb_buff_len;
    cdb_hdr_ptr->type = DYN$C_BUFIO;
    cdb_hdr_ptr->subtype = 0;

    /* Copy CDB from user address to CDB packet */
    memmove(cdb_data_ptr,cdbaddr,
	    cdblen);

    /* Assume a user buffer byte count of 0 */
    irp_ptr->irp$l_bcnt = 0;

    if ((dataddr != 0) &&
        (datlen != 0))
    /* If user data buffer is involved, probe it  */
    {
        if (s2dgb_ptr->s2dgb$v_read == 1)
	    /* Validate, probe and lock DATAIN buffer for write accessibility*/
	    /* Also fills in irp$l_svapte/boff/bcnt */
	    status = exe_std$readlock(irp_ptr, pcb_ptr, ucb_ptr, ccb_ptr, 
				 dataddr,
				 datlen,
				 error_rtn1);
        else
	    /* Validate, probe and lock DATAOUT buffer for read accessibility*/
	    /* Also fills in irp$l_svapte/boff/bcnt */
	    status = exe_std$writelock(irp_ptr, pcb_ptr, ucb_ptr, ccb_ptr, 
				 dataddr,
				 datlen,
				 error_rtn1);
        if (! $VMS_STATUS_SUCCESS(status))
                return (status);
    }

    /* Set up flags and pad count */
    irp_ptr->irp$l_qio_p3 = s2dgb_ptr->s2dgb$l_flags;
    irp_ptr->irp$l_qio_p4 = padcnt;
    
    /* Get phstmo in low 16 bits, dsctmo in upper 16 bits of irp$l_qio_p5 */
    phstmo_tmp = phstmo;
    dsctmo_tmp = dsctmo; 
    if (phstmo_tmp == 1)
        phstmo_tmp = 0;
    if (dsctmo_tmp == 1)
        dsctmo_tmp = 0;
    irp_ptr->irp$l_qio_p5 = phstmo_tmp | (dsctmo_tmp << 16);

    /* Queue the request to the driver's startio routine */
    return (call_qiodrvpkt(irp_ptr,ucb_ptr));

}

/*----------------------------------------------------------------------------
 *
 * error_rtn1
 *
 * We arrive here if the last FDT operation - checking access to and locking
 * down the user's read/write buffer - fails.  EXE$READLOCK or EXE$WRITELOCK
 * calls us to allow us to give up any resources which we have allocated 
 * during FDT processing. Deallocate the buffer containing a copy of the 
 * SCSI command, then return to EXE$READLOCK/WRITELOCK.
 *
 * Note that any autosense buffer for cucb_ptr->ps_autosense will be
 * deallocated appropriately in some future IO$_DIAGNOSE.
 *---------------------------------------------------------------------------*/

void error_rtn1 (IRP *irp_ptr, PCB *pcb, UCB *ucb, CCB *ccb, int errsts)
{
    /* Deallocate the CDB */
    exe_std$deanonpaged((void *)irp_ptr->irp$l_media);
    /* Unlock buffers */
    exe_std$lock_err_cleanup(irp_ptr);
}



/*__________________________________________________________________________
 *
 * error_rtn2
 *
 * We arrive here if the call to exe_std$readlock fails for
 * the autosense buffer.
 *---------------------------------------------------------------------------*/

void error_rtn2(IRPE *irpe_ptr, PCB *pcb, UCB *ucb, CCB *ccb, int errsts,
		  IRP **irp_p)
{
    IRP	*irp_ptr;

    
    irp_ptr = (IRP *) irpe_ptr->irpe$l_extend;  /* Restore parent IRP address*/
    exe_std$lock_err_cleanup(irp_ptr);          /* Unlock any buffers	     */
    *irp_p = irp_ptr;                           /* Return original IRP 	     */

}

/*
 *---------------------------------------------------------------------------
 *
 * DKMK$DIAGNOSE_SIO
 *
 *     Description:
 *
 *          This start I/O action routine shall be called from
 *          the class drivers' start I/O routine.  DKMK$DIAGNOSE_SIO shall
 *          execute all the port driver calls necessary to send the CDB,
 *          transfer any DATAIN or DATAOUT data, recover SCSI status, and
 *          return autosense data.
 *     Input Parameters:
 *
 *          irp_ptr   base address of the IRP being processed
 *          scdt_ptr  base address of the SCDT for the current connection
 *          scdrp_ptr base address of the SCDRP being processed
 *          cucb_ptr  base address of the class driver UCB storage set aside 
 *                    for the exclusive use of the IO$_DIAGNOSE common code 
 *                    (same value as passed to DKMK$DIAGNOSE_INIT)
 *
 *     Implicit Inputs:
 *
 *          cucb$ps_as2_svapte  SVA of PTE for autosense double map page
 *          cucb$ps_as2_sva     system virtual address of double map page
 *          IRP$L_MEDIA         address of BUFIO packet containing CDB
 *          IRP$L_BCNT          BCNT of DATAIN/DATAOUT buffer (or zero)
 *          IRP$L_BOFF          BOFF of DATAIN/DATAOUT buffer (if any)
 *          IRP$L_SVAPTE        SVAPTE of DATAIN/DATAOUT buffer (if any)
 *          IRP$L_QIO_P3        copy of FLAGS from S2DGB
 *          IRP$L_QIO_P4        copy of PADCNT from S2DGB
 *          IRP$L_QIO_P5<00:15> copy of PHSTMO from S2DGB
 *          IRP$L_QIO_P5<16:31> copy of DSCTMO from S2DGB
 *          IRP$L_QIO_P6        SVA of saved autosense data packet (if any)
 *          IRP$V_EXTEND        1 if IRPE allocated for autosense
 *          IRP$L_EXTEND        pointer to autosense IRPE (if any)
 *          IRPE$L_BOFF        BOFF of autosense buffer (if any)
 *          IRPE$L_BCNT        BCNT of autosense buffer (if any)
 *          IRPE$L_SVAPTE      SVAPTE of autosense buffer (if any)
 *
 *          DKMK$DIAGNOSE_SIO assumes that the class driver has single
 *          threaded IO$_DIAGNOSE operations in accordance with the function-
 *          al definition.  No guarantees of proper DKMK$DIAGNOSE_SIO opera-
 *          tion can be provided if the functional single threading rules are
 *          not observed by the class driver.
 *
 *     Return Value:       64-bit IOSB contents in R0 (as functionally specified:)
 *
 *    +----------------------------+-----------------------------+
 *    |      byte count <15:0>     |       port VMS status       | :00
 *    +-------------+--------------+-----------------------------+
 *    | SCSI status |     zero     |      byte count <31:16>     | :04
 *    +-------------+--------------+-----------------------------+
 *
 *     Output Parameters:  None
 *
 *     Implicit Outputs
 *
 *          cucb$ps_autosense   address of BUFIO packet containing valid
 *                              autosense data or zero
 *
 *---------------------------------------------------------------------------*/

int64 dkmk$diagnose_sio(IRP *irp_ptr,
		      SCDT *scdt_ptr,
		      SCDRP *scdrp_ptr,
		      CUCB *cucb_ptr)

{
    int status, send_status = SS$_NORMAL, cdb_size;
    int bytes_left_in_page, bcnt_first_seg, bcnt_second_seg, second_seg_addr;
    char *s0_addr;
    int p5_lo, p5_hi, discpriv_mask;
    IRPE *irpe_ptr = irp_ptr->irp$l_extend;
    SPI saved_params_buff;
    SPI modified_params_buff;
    UCB *ucb_ptr = irp_ptr->irp$l_ucb;
    SPDT *spdt_ptr = (SPDT *)ucb_ptr->ucb$l_pdt;
    int64 iosb;
    int jnkstat;
    typedef struct 
        {
	short int vms_sts;
	short int bcnt_lo;
	short int bcnt_hi;
	char fill;
	unsigned char scsi_sts;
        } IOSB;
    IOSB *iosb_ptr = (IOSB *)(&iosb);
    typedef struct 
        {
	int flink;
	int blink;
	short size;
	char type;
	char subtype;
        } BUFIO_HEADER;
    BUFIO_HEADER *media_hdr_ptr = (BUFIO_HEADER *)irp_ptr->irp$l_media;
    BUFIO_HEADER *p6_hdr_ptr;
    char *p6_data_ptr;
    typedef struct
        {
	int status;
	int cmd_len;
        } CMD_BUF_OVHD;
    CMD_BUF_OVHD *cdb_ptr;    
    char *cmd_bytes_ptr;
    char *media_data_ptr = (char *)media_hdr_ptr + sizeof(BUFIO_HEADER);
    struct sense_data *sense_data_ptr; 
    int sense_data_len;
    PTE scratch_pte;
    PTE *svapte_second_seg;

    /* Copy buffer mapping fields from IRP to SCDRP */
    scdrp_ptr->scdrp$l_media = (void *)irp_ptr->irp$l_media;
    scdrp_ptr->scdrp$l_svapte = irp_ptr->irp$l_svapte;
    scdrp_ptr->scdrp$l_bcnt = irp_ptr->irp$l_bcnt;
    scdrp_ptr->scdrp$l_boff = irp_ptr->irp$l_boff;
    scdrp_ptr->scdrp$is_sts = irp_ptr->irp$l_sts;
    scdrp_ptr->scdrp$ps_qio_p6 = (void*)irp_ptr->irp$l_qio_p6;


    saved_params_buff.spi$il_cc_count = SPI$K_CC_QNUM_ARGS;
    status = call_port(cd_connection_char_get)(scdrp_ptr,&saved_params_buff); 
    if (!(scdt_ptr->scdt$v_cap_cmdq)) 
    { 
	memmove(&modified_params_buff,&saved_params_buff,sizeof(SPI));

        /* Change connection characteristics */

        p5_lo = irp_ptr->irp$l_qio_p5 & 0xffff;
        p5_hi = (irp_ptr->irp$l_qio_p5 >> 16) & 0xffff;
        if (p5_lo != 0)
	    modified_params_buff.spi$il_cc_dma_timeout = p5_lo;
        if (p5_hi != 0)
	    modified_params_buff.spi$il_cc_disc_timeout = p5_hi;
        if (((irp_ptr->irp$l_qio_p3) & S2DGB$M_DISCPRIV) == 0) 
            modified_params_buff.spi$v_cc_ena_discon = 0;
        else
            modified_params_buff.spi$v_cc_ena_discon = 1;

        /* Lower layers ignore the synchronous bit, so hardcode it to 1 */
	modified_params_buff.spi$il_cc_synchronous = 1;


        /* Fill in SCDRP fields */
        scdrp_ptr->scdrp$l_pad_bcnt = irp_ptr->irp$l_qio_p4;
        scdrp_ptr->scdrp$l_dma_timeout = p5_lo;
        scdrp_ptr->scdrp$l_discon_timeout = p5_hi;

	/* Set the connection characteristics */
        status = call_port(cd_connection_char_set)(scdrp_ptr,&modified_params_buff); 
    }
   
    /* Get size of command */
    cdb_size = media_hdr_ptr->size - sizeof(BUFIO_HEADER);
    /* Add 4 bytes each for status field and length field, and allocate a 
       command buffer of that size */
    status = call_port(cd_cmd_buffer_alloc)
          (spdt_ptr,scdrp_ptr,cdb_size+sizeof(CMD_BUF_OVHD),&cdb_ptr);
    if (! $VMS_STATUS_SUCCESS(status))
        return (status);

    scdrp_ptr->scdrp$l_cmd_buf = cdb_ptr;
    scdrp_ptr->scdrp$l_sts_ptr = &cdb_ptr->status;
    scdrp_ptr->scdrp$l_cmd_ptr = (char *)&cdb_ptr->cmd_len;


    /* Initialize SCSI status byte to -1 in the command buffer */
    cdb_ptr->status = 0xff;
    /* Initialize the length field in the command buffer */
    cdb_ptr->cmd_len = cdb_size;
    /* Copy the SCSI command to the command buffer */
    cmd_bytes_ptr = (char *) cdb_ptr + sizeof(CMD_BUF_OVHD);
    memmove(cmd_bytes_ptr,media_data_ptr,cdb_size);

    /* Deallocate the command buffer described by scdrp$l_media */
    status = exe_std$deanonpaged(media_hdr_ptr);
    if (! $VMS_STATUS_SUCCESS(status))
        return (status);


    /*****************************  X-11  **************************
    There are two possibilities that a cancel may get to the current 
    IRP of a tape IO in MKdriver's MK_CANCEL and leave an IO without 
    its IRP:

     (1) The allocation of buffer (call_port(cd_cmd_buffer_alloc) 
       above) may be stalled in port driver due to insufficient 
       memory, and 
     (2) call_port(cd_send_command) always stalls in port driver 
       while waiting for SCSI command completion.                

    After cancel took away the current IO's IRP, we walk away from 
    the current IO without normal completion.	

    Note: Neither of the two groups of cancel IO code below will 
    be excuted by the Dkdriver.  For disk IO, every UCB$L_IRP was 
    zeroed in STARTIO routine to mean there is no 'current IRP'.  
    As a result, UCB$M_CANCEL in UCB$L_STS can never be set by 
    CALL_CANCELIO in DK_CANCEL.  Therefore, DKdriver will never 
    execute either of the two cancel code below.  
    ***************************************************************/

    /**/
    /**  X-11 Cancel Handler if cancelled during cmd_buffer_alloc */
    /**/
    if ((ucb_ptr->ucb$l_sts & UCB$M_CANCEL) != 0) 
    {
	if ((BUFIO_HEADER *)scdrp_ptr->scdrp$ps_qio_p6 != NULL)
        {
           /* Deallocate the buffer pointed to by irp$l_qio_p6 */
           status = exe_std$deanonpaged(
			(BUFIO_HEADER *)scdrp_ptr->scdrp$ps_qio_p6);
           cucb_ptr->ps_autosense = NULL;	/* Zero the a-sense data ptr */
        }

    	/* Restore connection characteristics if they had been changed */
    	if (!(scdt_ptr->scdt$v_cap_cmdq)) 
           status = call_port(cd_connection_char_set)(scdrp_ptr,&saved_params_buff); 
    	status = call_port(cd_cmd_buffer_dealloc)(spdt_ptr,scdrp_ptr);
        iosb_ptr->vms_sts = SS$_ABORT;
    	iosb_ptr->bcnt_lo = 0;
    	iosb_ptr->bcnt_hi = 0;
    	iosb_ptr->fill = 0;
	return (iosb);
    }

    /* Map the user data buffer, is there is one */
    if (scdrp_ptr->scdrp$l_bcnt != 0)
       status = call_port(cd_buffer_map)(spdt_ptr,scdrp_ptr,0);
    if (! $VMS_STATUS_SUCCESS(status)){
    	jnkstat = call_port(cd_cmd_buffer_dealloc)(spdt_ptr,scdrp_ptr);
        return (status);}

    if (((irp_ptr->irp$l_qio_p3) & (S2DGB$M_TAGGED_REQ)) != 0)
    /* Setup information for tagged requests */
    {
        scdrp_ptr->scdrp$is_queue_char = 
	    (irp_ptr->irp$l_qio_p3 & S2DGB$M_TAG) >> S2DGB$K_TAG_POSITION;
    }

    else
    /* Setup information for untagged requests */
        scdrp_ptr->scdrp$is_queue_char = SCDRP$K_QCHAR_NOT_QUEUED;


    send_status = call_port(cd_send_command)(spdt_ptr,scdrp_ptr);

    /**/
    /**  X-11 --  Cancel handler if cancelled during send_command */
    /**/
    if ((ucb_ptr->ucb$l_sts & UCB$M_CANCEL) != 0)
    {
	if ((BUFIO_HEADER *)scdrp_ptr->scdrp$ps_qio_p6 != NULL)
        {
           /* Deallocate the buffer pointed to by irp$l_qio_p6 */
           status = exe_std$deanonpaged(
			(BUFIO_HEADER *)scdrp_ptr->scdrp$ps_qio_p6);
           cucb_ptr->ps_autosense = NULL;		/* Zero the a-sense data ptr */
        }

        if (scdrp_ptr->scdrp$l_bcnt != 0)
	   status = call_port(cd_buffer_unmap)(spdt_ptr,scdrp_ptr);
    	/* Restore connection characteristics if they had been changed */
    	if (!(scdt_ptr->scdt$v_cap_cmdq)) 
           status = call_port(cd_connection_char_set)(scdrp_ptr,&saved_params_buff); 
    	status = call_port(cd_cmd_buffer_dealloc)(spdt_ptr,scdrp_ptr);
        iosb_ptr->vms_sts = SS$_ABORT;
    	iosb_ptr->bcnt_lo = 0;
    	iosb_ptr->bcnt_hi = 0;
    	iosb_ptr->fill = 0;
	return (iosb);
    }

    if (scdrp_ptr->scdrp$l_bcnt != 0)
        status = call_port(cd_buffer_unmap)(spdt_ptr,scdrp_ptr);


    if (scdrp_ptr->scdrp$v_flag_asense_valid)
    {    
        if ((irp_ptr->irp$l_qio_p3 & S2DGB$M_AUTOSENSE) != 0)
        {
            if (irp_ptr->irp$v_extend)
            {
	        /* Double map the user buffer */
		/* Transfer autosense data to user buffer */


		/* Set the PFN into a scratch PTE */
		if (irpe_ptr->irpe$l_svapte->pte$v_valid)
		    scratch_pte.pte$v_pfn = 
			irpe_ptr->irpe$l_svapte->pte$v_pfn;
		else
		    scratch_pte.pte$v_pfn = 
                        ioc_std$ptetopfn(irpe_ptr->irpe$l_svapte);

		/* Set in the software bits in the PTE */
		scratch_pte.pte$v_valid = 1;
		scratch_pte.pte$v_for   = 0;
		scratch_pte.pte$v_fow   = 0;
		scratch_pte.pte$v_foe   = 0;
		scratch_pte.pte$v_asm   = 1;
		scratch_pte.pte$v_gh    = PTE$C_GROUP_OF_1;
		scratch_pte.pte$v_prot  = 0;
		scratch_pte.pte$v_kre   = 1;
		scratch_pte.pte$v_kwe   = 1;
		scratch_pte.pte$v_rsrvd_hw = 0;
		scratch_pte.pte$v_software = 0;
		scratch_pte.pte$v_window   = 1;
 
		/* Load the scratch PTE into the CUCB field */
		*cucb_ptr->ps_as2_svapte = scratch_pte;

		/* Invalidate the TB */
                mtpr_tbis(cucb_ptr->ps_as2_sva);
		
		/* Get our starting S0 VA */
		s0_addr = cucb_ptr->ps_as2_sva +
				irpe_ptr->irpe$l_boff;

		/* How many bytes from start of buffer to next page boundary */
		bytes_left_in_page = (int) cucb_ptr->ps_as2_sva +
				mmg$gl_page_size - (int) s0_addr;

		if (irpe_ptr->irpe$l_bcnt <= bytes_left_in_page)
		/* If entire buffer is within a page */
		{
		    /* Move the autosense data to double-mapped user buffer */
		    memmove(s0_addr, scdrp_ptr->scdrp$ps_sense_buffer,
			    irpe_ptr->irpe$l_bcnt);
                }
		else
		/* Sense buffer crosses a page boundary */
		{
		    /* Move first segment */
		    bcnt_first_seg = bytes_left_in_page;
		    memmove(s0_addr, scdrp_ptr->scdrp$ps_sense_buffer,
			    bcnt_first_seg);

		    /* Move second segment by re-using our PTE to double 
		    map the second page */
		    bcnt_second_seg = irpe_ptr->irpe$l_bcnt - bcnt_first_seg;

		    /* Advance one PTE for the sense buffer */
		    svapte_second_seg = irpe_ptr->irpe$l_svapte + 1; 

		    if (svapte_second_seg->pte$v_valid)
		        scratch_pte.pte$v_pfn = 
			    svapte_second_seg->pte$v_pfn;
		    else
		        scratch_pte.pte$v_pfn = 
                            ioc_std$ptetopfn(svapte_second_seg);

		    /* Set in the software bits in the PTE */
		    scratch_pte.pte$v_valid = 1;
		    scratch_pte.pte$v_for   = 0;
		    scratch_pte.pte$v_fow   = 0;
		    scratch_pte.pte$v_foe   = 0;
		    scratch_pte.pte$v_asm   = 1;
		    scratch_pte.pte$v_gh    = PTE$C_GROUP_OF_1;
		    scratch_pte.pte$v_prot  = 0;
		    scratch_pte.pte$v_kre   = 1;
		    scratch_pte.pte$v_kwe   = 1;
		    scratch_pte.pte$v_rsrvd_hw = 0;
		    scratch_pte.pte$v_software = 0;
		    scratch_pte.pte$v_window   = 1;
                    
		    /* Load the scratch PTE into the CUCB field */
		    *cucb_ptr->ps_as2_svapte = scratch_pte;
                    
		    /* Invalidate the TB */
                    mtpr_tbis(cucb_ptr->ps_as2_sva);

		    /* Move second segment of buffer */
		    second_seg_addr = (int) scdrp_ptr->scdrp$ps_sense_buffer + 
				      bcnt_first_seg;
                    memmove(cucb_ptr->ps_as2_sva,
			    (char *) second_seg_addr,
			    bcnt_second_seg);
		}
            }
        }
        else
        {
            /* Use irp$l_qio_p6 to save and return the data */
            sense_data_ptr = (struct sense_data*)scdrp_ptr->scdrp$ps_sense_buffer;

            /* The length field is byte 7 of the sense_data and represents
	    the number of bytes after itself.  So add 8 to get total length. */
	    sense_data_len = (sense_data_ptr->scsi$sns$b_add_sense_len) + 8;
	
	    /* Move the autosense data to the address in P6 */
    	    p6_hdr_ptr = (BUFIO_HEADER *)irp_ptr->irp$l_qio_p6;
            p6_data_ptr = (char *)p6_hdr_ptr + sizeof(BUFIO_HEADER);
            memmove(p6_data_ptr,sense_data_ptr,sense_data_len);	    

	    /* Fill in autosense length in second longword */
	    cucb_ptr->ps_autosense = (char *) p6_hdr_ptr;
	    p6_hdr_ptr->blink = sense_data_len;
        }
    }
    else
    {
        if (!(irp_ptr->irp$v_extend) && 
	    ((BUFIO_HEADER *)irp_ptr->irp$l_qio_p6 != NULL))
        {
            /* Deallocate the buffer pointed to by irp$l_qio_p6 */
            status = exe_std$deanonpaged((BUFIO_HEADER *)irp_ptr->irp$l_qio_p6);
            cucb_ptr->ps_autosense = NULL;		/* Zero the a-sense data ptr */
        }
    }

    /* Save SCSI status before deallocating command buffer */
    iosb_ptr->scsi_sts = *(scdrp_ptr->scdrp$l_sts_ptr);

    /* Deallocate the command buffer (and the returned autosense data */
    status = call_port(cd_cmd_buffer_dealloc)(spdt_ptr,scdrp_ptr);

    /* Restore connection characteristics if they had been changed */
    if (!(scdt_ptr->scdt$v_cap_cmdq)) 
        status = call_port(cd_connection_char_set)(scdrp_ptr,&saved_params_buff); 

    /* Fill in the rest of the IOSB */
    if (! $VMS_STATUS_SUCCESS(send_status))
        iosb_ptr->vms_sts = (short int) send_status;
    else
        iosb_ptr->vms_sts = (short int) status; 
    iosb_ptr->bcnt_lo = (short int) (scdrp_ptr->scdrp$l_trans_cnt & 0xffff);
    iosb_ptr->bcnt_hi = (short int) (scdrp_ptr->scdrp$l_trans_cnt >> 16) & 0xffff;
    iosb_ptr->fill = 0;
    return (iosb);
}
