#pragma module PKQDRIVER "X-6A13A7" /* ***************************************************************************** * * Copyright © Digital Equipment Corporation, 1993-1997 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: * * Alpha VMS/SCSI device driver * * ABSTRACT: * * PKQDRIVER is a SCSI port driver. It supports the QLogic ISP1020 chip. * * AUTHOR: * * David E. Eiche * * CREATION DATE: * * 1-Jun-1994 * * REVISION HISTORY: * * X-6A13A7 * RCL003 Rick Lord 1-Jul-97 * - Moved the clear of qbuf->qbuf$l_port_status (see RCL002 * just 32 lines below this one) so it's done immediately on * entry to pk$send_command. This prevents a queue manager * stall (done in stall_qman and pkq_send_marker) from causing * control to return to the SCDRP thread with a non-0 value, * which causes a subsequent call to pk$cmd_wait_completion to * not stall; control returns all the way back to the class * driver even though the queue manager still thinks it owns * the SCDRP. * * X-6A13A6 * RJS003 Richard Stammers 22-May-1997 * - Made the call to init_target_reply unconditional upon * target mode, and placed it back in pkq_unit_init_fork * so that the spdt now always contains valid inquiry response * data (even if target mode is NOT subsequently enabled). This * eliminates the time window in which all zero's could be * sent as a reply to a target mode inquiry. * * X-6A13A5 * RJS002 Richard Stammers 22-May-1997 * - Fixed problem when extended sense data length > 32 bytes * in which case ISP sends 1st 32 bytes of sense data * in the status entry and subsequent bytes of sense data * are sent as (maximum of 4) status continuation entries * which may each contain up to 60 bytes of sense data. * Major areas changed were: * Changes to QBUF structure. * Changes to pkq_interrupt_fork routine * Changes to pkq_process_error routine * Re enabled extended sense (which was disabled X-6A13A3) * Also involved changing ISP_ENTRY structure in isp1020def.SDL * * X-6A13A4 RCL002 Rick Lord 6-May-97 * - Zero QBUF$L_PORT_STATUS prior to sending a command. This * corrects the problem in which reusing a QBUF causes the * driver to not stall the KPB associated with an active * command because it sees a non-0 port status and assumes * that the request has already completed (see the routine * pk$cmd_wait_completion for use of QBUF$L_PORT_STATUS) * * X-6A13A3 RTS011 Bob Silver 5-May-1997 * - Temporarily disable extended sense. Not yet fully functional. * * X-6A13A2 RTS006 Bob Silver 9-Mar-1997 * - Enable extended sense. This will cause the QLogic firmware * to request 256 bytes of sense data instead of 32 bytes. * - Add Mark DiFabio's fix to attempted deallocation of non- * existant command buffer in pk$cmd_buffer_dealloc. * - Change interpretation of ISP INCOMPLETE and TRANSPORT status * to SS$_TIMEOUT. * * X-6A13A1 RTS004 Bob Silver 27-Mar-1997 * - Move conditional call to init_target_reply to a place after * pkq_select_firmware call so that target_mode condition is not * undefined. (Moved to pkq_set_parameters). This should fix * problems with missing inquiry data from other host. * - Change release_risc so that it waits for Pause bit to clear. * - Return missing status from pkq_set_speed when not fast20. * - Remove change in previous generation where routine to set * clock rate is called only with firmware > 3.00. * - Fix 2nd strncpy argument in pkq_dump_nvram routine. * - Reinsert Steve Skonetski's fix from checkin X-6A8. This had * accidentally been removed by out-of-sync cms operations during * checkin X-6A9. * - Modify routine calls inside pkq_port_init to make return * status more visible during debug. * - Fix fifo_threshold setting in spdt. If nvram can be read, * then use that value. Otherwise, use a value of '0' for * version 1 of the 1040B or '2' for any other hardware rev. * - Add Mark DiFabio's 2nd ERASEPB fix that accounts for * Continuation entries. * * RJS001 Richard Stammers 23-Mar-1997 * - Changed pk$send_command to test the CDB for an INQUIRE * command being sent. If an INQUIRE is being sent then * re-negotiation of wide/synch with the target is forced if * if required, and if firmware version >= 2.00 * - Made corresponding changes to ISP1020DEF.SDL to * describe required bits in control flags * * X-6A13 MED003 Mark DiFabio 05-Feb-1997 * Change PK$SEND_COMMAND to use EXE$GL_BLAKHOLE as the pad * buffer for unaligned reads. For writes, EXE$GL_ERASEPB is * used. By using EXE$GL_ERASEPB for reads, the ERASEPB buffer * was getting corrupted with the 'pad' data. * * X-6A12 RTS001 Bob Silver 15-Nov-1996 * - Do mailbox I/O to change clock rate only when the 60MHZ_CLOCK * flag is set. Otherwise, leave it as is. * - Call routine to set clock rate only with firmware > 3.00. * * X-6A11 RAR088 Buzzy Ritter 28-Oct-1996 * - Make target mode code conditional on running firmware > 3.00. * - fix uninitialized pointer in queue full path. * - fix register dump routine to get right registers. * * X-6A10 RAR086 Buzzy Ritter 16-Sep-1996 * Fix pause_risc problems with x-7. * * X-6A9 RAR086 Buzzy Ritter 13-Sep-1996 * - Fix fast20 implementation by pausing risc execution before * reading trig pin ( bit 5 of processor status register). * - Setup IO's so they will timeout on firmware hang. * - minimize synchronous offset with 8 and fifo threshold with * 0 ( 0 represents a threshold of 8). * - V3.2 of the isp firmware hung at high IO load. V3.21 of the * fw fixes that problem but sometimes caused an asynchronous * status code of 0x8002 which is a system error. The driver * wasn't handling that error proberly and the system hangs. * This change reinits to chip on system error. * - Take out troubling message if a fast20 board's nvram is set * to fast10. This will be a normal thing. * - Add debugging code to dump nvram contents to the console * at uint init. * * X-6A8 SGS0093 Steve Skonetski 29-Aug-1996 * Change PK$MAP_BUFFER to use map reg if pa is >= direct dma * window size, not just > direct dma window size. If PA = dma * window size then it is really one byte beyond the window. * * X-6A7 TLC Tony Camuso 11-June-1996 * pkq_struc_init(): Raise DIPL to 21 for all platforms * * X-6A6 RAR078 Buzzy Ritter 6-Feb-1996 * - Add support for fast20 boards , compare nvram to istat line 5 * if they're the same set speed accordingly. If diffrent output * message and set slower speed. * - fix additional length of sense response message. * * X-6A5 RAR077 Buzzy Ritter 17-Jan-1996 * - Add support for target mode for clusters. * This includes canges to unit init to initialize data * structures, changes to the interrupt service routine to handle * target mode messages in the response queue and changes in * send command to handle reset synchronization and ensure * enough room in the request queue for target messages send * from the interrupt fork. * - Fix bug in call to deallocate on dump_ram failure. * - Fix isr to avoid fork routine reentrancy. (and take out * double fork) * * X-6A4 RCL001 Rick Lord 1-Dec-95 * - Modify the newly-created SPDT to reflect maximum and * current bus widths of 16, which is the largest that * this port can support. Search for "RCL001". There are * hardcoded 16's in several places in the driver so I'm * assuming that this is the most it can handle. * * X-6A3 RAR076 Buzzy Ritter 6-Nov-1995 * Merge the following ghost changes into gryphon. * X-4U4A3 RAR075 Buzzy Ritter 2-Nov-1995 * Add check for rawhide platform in DIPL init code. Rawhide uses * IPL21 (like ALCOR) rather than IPL20. * * X-4U4A2 RAR073 Buzzy Ritter 3-Oct-1995 * -Add support for overrun recovery. Setup FW to interrupt on * overrun, pad IO's (using scdrp$l_pad_cnt) and reset the bus * if an interrupt occurs. * - Fix bug in dump_ram implementation for segmented dumps. * * X-4U4A1 RAR071 Buzzy Ritter 3-Oct-1995 * Add delay into wait loops in pkq_mailbox_io CSR wait loops * to avoid PCI bus saturation. * * X-6A2 RAR071 Buzzy Ritter 2-Aug-1995 * Add the following zeta-zeta tima fixes. * X-4U5 DEE0235 David E. Eiche 17-Jul-1995 * Modify pkq_dump_firmware to checksum the firmware image in * non-paged pool and to retry the dump operation if the * checksum fails to recover from intermittent ISP1020 DUMP_RAM * mailbox command failures. In pkq_mailbox_io, add code to * return an error on command failure rather than issuing a * bugcheck. Provide additional console messages to reflect * the error conditions. * * RAR070 Buzzy Ritter 24-Jul-1995 * Replace mailbox DUMP_RAM call with call to dump_ram routine * that reads the ram a word at a time. This avoids a problem that * we were seeing on PnSE with the DUMP_RAM mailbox command * terminating early. * * X-4U4 DEE0234 David E. Eiche 14-Jun-1995 * Fix problems in error logging code: force byte alignment of * error logging structures and fix miscellaneous typos which * cause data to be overwritten; also add code to freeze the * ISP1020 RISC processor while doing the register dump and to * allow SXP register selection and dumping. * * Add code to set the SCSI ID in the standard SPDT field, so * that SDA/CLUE can find it. * * X-6A1 JPJ James P. Janetos 07-Aug-1995 * IOC_ROUTINES.H now contains the prototype for * IOC$READ_PCI_CONFIG, so remove the prototype * declaration from this module. Fix the 5th argument in * the call to IOC$READ_PCI_CONFIG -- it is supposed to * be a pointer to an int rather than a pointer to an * int64. * * X-6 RAR068 Buzzy Ritter 26-Apr-1995 * Merge the following zeta changes: * * X-4U3 DEE0230 David E. Eiche 31-Mar-1995 * Change prototype for sc$rl_create_port to contain argument * types. * * Add routines to implement response IDs in order to eliminate a * stale pointer problem when the adaptor returns a completion * response for a request that had already been completed as the * result of a bus reset. Invoke the new routines from unit * initialization, command buffer allocate and deallocate and * the interrupt fork routine. * * Incorporate Buzzy's change to pk$cmd_wait_completion to check * the error status returned by exe$kp_stall_general as a result * of a bus reset. Also incorporate his change to correctly * terminate the version string in pkq_convert_ver. * * Add routines to print the firmware version and to indicate * whether the firmware came from the console or driver image. * Do check of firmware checksum. * * Add comments to include files. * Set device ipl to 21 for alcor. * * Add flag to indicate to reset_scsi_bus that the reset is from * unit_init. * * X-4U2 DEE0229 David E. Eiche 12-Mar-1995 * In pkq_load_firmware() correct the firmware length calculation * and add a mailbox command to verify the firmware checksum * before starting it up. * RAR066 Buzzy Ritter 20-Mar-1995 * fix multiple interrupt problems with read of icr after it is * written. * * X-4U1 DEE0228 David E. Eiche 02-Mar-1995 * Correct length calculation in pkq_dump_firmware. Also incorporate * Buzzy's change to pkq_interrupt_fork to add another call to * bus reset. * * DEE0227 David E. Eiche 23-Feb-1995 * Relocate the target parameters to the SPDT because the STDT * don't exist at port unit initialization time. Correct a data * type error in pkq_nvram_read_array() which caused a checksum error. * * RAR065 BUZZY RITTER 06-MAR-1995 * Add flow control to stall queue manager when request queue * is full. Also add debug tracing. Add hysteresis to the stall * mechanism so that 25% of buffer size must complete. * * X-5 EMB Ellen M. Batbouta 10-Mar-1995 * Declare the system data cells, MMG$GL_PAGE_SIZE and MMG$GL_ * BWP_MASK, as external integers which have a constant value. * * X-4 DEE0226 David E. Eiche 03-Feb-1995 * Add code to get adaptor operating parameters from the on-board * NVRAM. Add code to compare the version of the firmware loaded * by the console to the version linked with the driver and use * the newer of the two. Also, provide the ability to force the * use of default operating parameters rather than the NVRAM and * force the use of the firmware loaded with the driver. * * Add code to enable bus reset processing and to detect firmware * hangs. * * X-3 DEE0224 David E. Eiche 22-Dec-1994 * Add code to pk$buffer_map and pk$send_command to convert * a PTE to a PFN if the PTE is not valid. Remove odd byte * padding code. Add mailbox completion codes to pkq_interrupt_fork * and ignore mailbox command completion and self-test completion * interrupts. * * X-2 DEE0223 David E. Eiche 09-Dec-1994 * Get isp1020def.h from obj$: * * X-1 DEE0222 David E. Eiche 09-Dec-1994 * Initial Checkin. * */ /* Include files for system structures from SYS$LIB_C.TLB */ #include /* Adaptor control block definitions */ #include /* Bus array header and entry definitions */ #include /* Channel control block definitions */ #include /* Channel request block definitions */ #include /* Character type macro definitions */ #include /* Device adaptor, class and type definitions */ #include /* Device data block definitions */ #include /* Driver dispatch table definitions */ #include /* Device characteristics definitions */ #include /* Driver prologue table definitions */ #include /* Data structure type definitions */ #include /* Function decision table definitions */ #include /* Hardware RPB definitions */ #include /* Interrupt dispatch block definitions */ #include /* Integer type definitions */ #include /* Ioc$node_data function code definitions */ #include /* I/O function code definitions */ #include /* I/O mapping handle definitions */ #include /* I/O request packet definitions */ #include /* Fork block definitions */ #include /* Object rights block definitions */ #include /* Process control block definitions */ #include /* PCI bus definitions */ #include /* SCSI definitions */ #include /* SCSI connection descriptor table definitions */ #include /* SCSI class driver request packet definitions */ #include /* SCSI port descriptor table definitions */ #include /* SCSI target descriptor table definitions */ #include /* Spinlock control block definitions */ #include /* Spinlock index definitions */ #include /* System service status code definitions */ #include /* System service status definitions */ #include /* ISO common definitions */ #include /* String processing function definitions */ #include /* Unit control block definitions */ #include /* CRB interrupt transfer vector definitions */ /* Include files which define exec routine prototypes, from SYS$LIB_C.TLB */ #include #include #include #include /* Driver Macros from SYS$LIB_C.TLB */ #include #include /* Include files from DECC$RTLDEF.TLB */ #include /* QLogic ISP 1020 definitions */ #include "obj$:isp1020def.h" /* Local macro definitions */ #define SUCCESS( _status ) (( _status ) & 1 ) #define ERROR( _status ) ( !(( _status ) & 1 )) #define TRUE 1 #define FALSE 0 #ifndef PKQ_DEBUG #define PKQ_DEBUG 0 #endif #define PKQ_MAXBCNT 65535 /* Port maximum byte count */ #define PKQ_NUM_RSPID 255 /* Number of RSPIDs in table */ #define PKQ_USE_NVRAM_DEFAULTS FALSE /* Forces use of compiled-in defaults */ #define PKQ_USE_LINKED_FIRMWARE FALSE /* Forces use of linked-in firmware */ #define PKQ_WATCHDOG_INTERVAL 30 #define MAX_ATIO_ENTRIES 4 /* max atio entries from fw */ #define MAX_NOTIFY_ENTRIES 4 /* max notify entries from fw */ #define MAX_MARKER_ENTRIES 2 /* max marker entries requires */ /* ** min_free entries is the space that is needed in the request queue before ** command initiation can happen. */ #define MIN_FREE_ENTRIES (MAX_ENTRIES_PER_REQUEST+MAX_ATIO_ENTRIES+MAX_NOTIFY_ENTRIES+MAX_MARKER_ENTRIES) /* ** These two values should probably be in scsidef.h but they aren't. ** They are the command codes for the inquiry and request sense commands. */ #define SCSI$K_REQUEST_SENSE 0x3 #define SCSI$K_INQUIRY 0x12 #define min(a,b) (((a)<(b))?(a):(b)) #define max(a,b) (((a)>(b))?(a):(b)) #define DECLARE_ISP() \ uint64 _temp; \ uint32 _offset; \ ISP_REGISTERS *_isp #define READ_ISP( _adp, _iohandle, _regname ) \ (( ioc$read_io(_adp, _iohandle, \ ( _offset = ( uint64 )(( uint8 * )&_isp->_regname - ( uint8 * )&_isp->isp$w_bus_id_low )), \ sizeof( uint16 ), &_temp ) & 1 ) ? \ (( _offset & 2 ) ? \ (( _temp >> 16 ) & 0xffff ) : \ ( _temp & 0xffff )) : \ pkq_bugcheck() ) #define WRITE_ISP( _adp, _iohandle, _regname, _wvalue ) \ _offset = ( uint64 )(( uint8 * )&_isp->_regname - ( uint8 * )&_isp->isp$w_bus_id_low ); \ _temp = (( _offset & 2 ) ? (( _wvalue & 0xffff ) << 16 ) : ( _wvalue & 0xffff )); \ if( !(ioc$write_io( _adp, \ _iohandle, \ _offset, \ sizeof( uint16 ), \ &_temp ) & 1 )) { \ pkq_bugcheck(); \ } #if PKQ_DEBUG /* ** define structures for debugging purposes here. */ /* ** trace buffer definitions */ struct pkq_trace_entry { uint32 trace_type; uint32 trace_p1; uint32 trace_p2; uint32 trace_p3; }; #define TRACE_ENTRIES 1000 #define TRACE_SIZE sizeof(struct pkq_trace_entry)*TRACE_ENTRIES #endif /* Response ID structure definitions: * - the RSPID itself * - the RSPID Table Entry */ #pragma member_alignment save #pragma nomember_alignment LONGWORD typedef struct _header { void *flink; void *blink; uint16 size; uint8 type; uint8 subtype; uint32 fill; } HEADER; typedef struct _rspid { /* ReSPonse IDentifer */ uint16 index; /* RSPID index */ uint16 seq_no; /* RSPID sequence number */ } RSPID; #pragma member_alignment restore #pragma member_alignment save #pragma nomember_alignment QUADWORD typedef struct _rte { struct _rte *flink; /* Free entry forward link */ RSPID rspid; /* RSPID itself */ void *p1; /* User structure pointer */ void *spare; /* Spare, for expansion */ } RTE; #pragma member_alignment restore /* ** The definitions for inquiry data and sense data are in ** scsidef.h. ** These are just typedefs for those structures */ typedef struct inquiry_data INQ_RESP; typedef struct sense_data REQ_SEN_RESP; /* ** pkq_fill is the # of bytes required to longword align the spdt extensiion ** after adding the inquiry response and the request sense response. ** The +1 is to accomodate the 1 byte message returned for a short inquiry ** request. */ #define pkq_fill ((4-((sizeof(INQ_RESP)+sizeof(REQ_SEN_RESP))&3))&3) /* Define the port-specific extensions to the SPDT */ #pragma member_alignment save #pragma nomember_alignment QUADWORD typedef struct _pkq_spdt { SPDT spdt$r_base_spdt; /* Base SPDT */ uint64 spdt$q_iohandle_reg; /* I/O Handle that maps ISP registers */ uint64 spdt$q_mem_base; /* Adaptor's memory space base address */ uint64 spdt$q_direct_dma_base; /* Base PA of direct DMA window */ uint32 spdt$l_direct_dma_size; /* Size of direct DMA window in bytes */ KPB *spdt$ps_kpb; /* Pointer to adaptor-wide KPB */ ISP_ENTRY *spdt$ps_requestq_va; /* Request queue virtual address */ ISP_ENTRY *spdt$ps_responseq_va; /* Response queue virtual address */ uint64 spdt$ps_requestq_pa; /* Response queue physical address */ uint64 spdt$ps_responseq_pa; /* Response queue physical address */ uint32 spdt$l_requestq_in; /* Request queue in index */ uint32 spdt$l_responseq_out; /* Response queue out index */ uint16 *spdt$ps_firmware_va; /* Firmware virtual address */ uint32 spdt$l_firmware_version; /* Version of firmware in use */ uint32 spdt$l_pkq_flags; /* PKQ port-specific flags */ uint16 spdt$w_mailbox_0; /* Outgoing mailboxes */ uint16 spdt$w_mailbox_1; /* from most */ uint16 spdt$w_mailbox_2; /* recently */ uint16 spdt$w_mailbox_3; /* issued */ uint16 spdt$w_mailbox_4; /* mailbox */ uint16 spdt$w_mailbox_5; /* command */ uint16 spdt$w_mailbox_6; /* (reserved for future expansion) */ uint16 spdt$w_mailbox_7; /* (reserved for future expansion) */ uint16 spdt$w_initiator_scsi_id; /* Initiator SCSI ID */ uint16 spdt$w_bus_reset_delay; /* Delay after reset until 1st command */ uint16 spdt$w_retry_count; /* Error retry count */ uint16 spdt$w_retry_delay; /* Delay between error retries */ uint16 spdt$w_tag_age_limit; /* Tag aging limit counter */ uint16 spdt$w_selection_timeout; /* Selection timeout (ms) */ uint16 spdt$w_max_queue_depth; /* Maximum queue depth */ uint16 spdt$v_fifo_threshhold : 2; /* FIFO threshhold */ uint16 spdt$v_adaptor_enable : 1; /* Adaptor is enabled if 1 (ignored) */ uint16 spdt$v_asynch_setup_time : 4; /* Asynch setup time (clock periods) */ uint16 spdt$v_req_ack_active_negation : 1; /* Enable SCSI REQ/ACK active pullups */ uint16 spdt$v_data_active_negation : 1; /* Enable SCSI Data active pullups */ uint16 spdt$v_data_dma_burst_enable : 1; /* Data DMA channel burst enable */ uint16 spdt$v_command_dma_burst_enable : 1; /* Command DMA channel burst enable */ uint16 spdt$v_termination_low_enable : 1; /* Enable termination of low 8 bits */ uint16 spdt$v_termination_high_enable : 1; /* Enable termination of high 8 bits */ uint16 spdt$v_pcmc_burst_enable : 1; /* PCMC burst enable */ uint16 spdt$v_60mhz_enable : 1; /* Enable 60MHZ ISP1020 clock */ uint32 : 32; /* Pad to QUAD boundary */ #pragma member_alignment save #pragma nomember_alignment WORD struct _tparams { union { uint16 spdt$w_capabilities; struct { /* Bit order required by firmware */ uint16 : 8; /* Not used */ uint16 spdt$v_renegotiate_on_error : 1; /* Renegotiate on check condition */ uint16 spdt$v_stop_queue_on_check : 1; /* Stop queue on check */ uint16 spdt$v_auto_request_sense : 1; /* Auto request sense on check */ uint16 spdt$v_tagged_queuing : 1; /* Tagged queuing support */ uint16 spdt$v_synch_data_transfers : 1; /* Synchronous data transfers */ uint16 spdt$v_wide_data_transfers : 1; /* Wide data transfers enabled */ uint16 spdt$v_parity_checking : 1; /* Parity checking enabled */ uint16 spdt$v_disconnect_allowed : 1; /* Disconnect privilege enabled */ } c_flags; } u1; union { uint16 spdt$w_synch_params; struct { /* Field order required by firmware */ uint8 spdt$b_synch_period; /* Synchronous transfer period */ uint8 spdt$b_synch_offset; /* REQ/ACK offset */ } synch; } u2; uint16 spdt$w_execution_throttle; /* Maximum cmds firmware to device */ union { uint16 spdt$w_misc_flags; struct { uint16 spdt$v_device_enable : 1; /* Device enabled (ignored) */ uint16 : 15; /* Unused */ } m_flags; } u3; } spdt$r_tparams[ 16 ]; #pragma member_alignment restore uint32 spdt$l_qman_stalled; /* Queue manager stalled flag */ RTE *spdt$ps_rspid_table; /* array of rspid blocks*/ RTE *spdt$ps_rspid_listhead; /* RSPID table listhead */ uint32 spdt$is_pkq_rspid_alloc_failures; /* Count of RSPID allocation failures */ uint32 spdt$is_pkq_rspid_stale_count; /* Count of stale RSPIDs encountered */ INQ_RESP spdt$s_inquiry_response; /* inquiry command response */ REQ_SEN_RESP spdt$s_request_sense_response; /* request sense command response */ char align_fill[pkq_fill]; /* fill */ ISP_ENTRY spdt$s_atio_entries[ MAX_ATIO_ENTRIES ]; /* buffers for saving atio buffers */ char spdt$b_short_inquiry_response; char spdr$b_short_inq_resp_fill[3]; /* ** add following line if an odd number of longwords is added ** uint32 spdt$l_fill; */ #if PKQ_DEBUG uint32 spdt$l_requestq_out; /* Request queue out index */ uint32 spdt$l_responseq_in; /* Response queue in index */ struct pkq_trace_entry *spdt$ps_log_buffer; /* log buffer start */ struct pkq_trace_entry *spdt$ps_next_log; /* next log buffer entry*/ uint32 spdt$l_log_size; /* log buffer size */ uint32 spdt$l_fill_deb; #endif } PKQ_SPDT; #define basespdt spdt$r_base_spdt #define SPDT$M_PKQ_SEND_MARKER 1 /* Need to send marker after bus reset */ #define SPDT$M_PKQ_PARAMS_READ 2 /* Params are stored in SPDT and STDT */ #define SPDT$M_PKQ_UNIT_INIT 4 /* Unit initialization in progress */ #define SPDT$M_PKQ_CONSOLE_FW 8 /* Active firmware originated in console */ #define SPDT$M_PKQ_SEND_ENABLE_LUN 16 /* send enable_lun sent */ #define SPDT$M_PKQ_GOT_ENABLE_LUN 32 /* enable_lun returned */ #if PKQ_DEBUG #define trace_event(a,b,c,d,e) pkq_trace_event((a),(b),(c),(d),(e)) #define trace_entry(a,b) pkq_trace_entry((a),(b)) #else #define trace_event(a,b,c,d,e) #define trace_entry(a,b) #endif #pragma member_alignment restore /* resore for save just before nvram definitions */ #define spdt$v_auto_request_sense u1.c_flags.spdt$v_auto_request_sense #define spdt$w_capabilities u1.spdt$w_capabilities #define spdt$v_device_enable u3.m_flags.spdt$v_device_enable #define spdt$v_disconnect_allowed u1.c_flags.spdt$v_disconnect_allowed #define spdt$w_misc_flags u3.spdt$w_misc_flags #define spdt$v_parity_checking u1.c_flags.spdt$v_parity_checking #define spdt$v_renegotiate_on_error u1.c_flags.spdt$v_renegotiate_on_error #define spdt$v_stop_queue_on_check u1.c_flags.spdt$v_stop_queue_on_check #define spdt$v_synch_data_transfers u1.c_flags.spdt$v_synch_data_transfers #define spdt$b_synch_offset u2.synch.spdt$b_synch_offset #define spdt$w_synch_params u2.spdt$w_synch_params #define spdt$b_synch_period u2.synch.spdt$b_synch_period #define spdt$v_tagged_queuing u1.c_flags.spdt$v_tagged_queuing #define spdt$v_wide_data_transfers u1.c_flags.spdt$v_wide_data_transfers /* Define the QBUF structure which is used as an intermediate * buffer to hold the SCSI CDB, status and autosense information * Autosense information is defined in such a way as to reflect * the status entry and status continuation entries that are * used to send it from the ISP. */ #define QBUF$K_CDB_SIZE 44 /* SCSI Command Data Block size */ #define QBUF$K_AUTOSENSE_SIZE 256 /* SCSI Total Autosense size */ #define QBUF$K_STAT_AUTOSENSE_SIZE 32 /* SCSI Autosense size in status entry */ #define QBUF$K_CON1_AUTOSENSE_SIZE 60 /* SCSI Autosense size in 1st cont entry*/ #define QBUF$K_CON2_AUTOSENSE_SIZE 60 /* SCSI Autosense size in 2nd cont entry*/ #define QBUF$K_CON3_AUTOSENSE_SIZE 60 /* SCSI Autosense size in 3rd cont entry*/ #define QBUF$K_CON4_AUTOSENSE_SIZE 44 /* SCSI Autosense size in 4th cont entry*/ #define QBUF$M_FL_AUTOSENSE_VALID 1 /* Autosense info is valid */ #pragma member_alignment save #pragma nomember_alignment QUADWORD typedef struct _qbuf { void *qbuf$ps_flink; /* Forward link */ void *qbuf$ps_blink; /* Backward link */ uint16 qbuf$w_size; /* Structure size */ uint8 qbuf$b_type; /* Structure type */ uint8 qbuf$b_subtype; /* Structure subtype */ RSPID qbuf$r_rspid; /* Response ID */ SCDRP *qbuf$ps_scdrp; /* SCDRP pointer */ uint32 qbuf$l_bytes_not_xfrd; /* Residual byte count */ uint32 qbuf$l_flags; /* Reserved for QBUF status flags */ uint16 qbuf$w_autosense_length; /* Length of returned autosense data */ uint8 qbuf$b_scsi_status; /* SCSI status */ uint8 qbuf$b_spare; /* Not used */ uint32 qbuf$l_port_status; /* Completion status returned by port */ uint32 qbuf$l_class_driver; /* Class-driver overhead space */ uint32 qbuf$l_scsi_cdb_size; /* including Command Data Block size */ uint8 qbuf$b_scsi_cdb[ QBUF$K_CDB_SIZE ]; /* and SCSI Command Data Block */ union { uint8 qbuf_autosense[ QBUF$K_AUTOSENSE_SIZE ]; /* Total Autosense data */ struct{ uint8 qbuf_stat_autosense[ QBUF$K_STAT_AUTOSENSE_SIZE ]; /* Status entry sense data */ uint8 qbuf_con1_autosense[ QBUF$K_CON1_AUTOSENSE_SIZE ]; /* Cont entry 1 sense data */ uint8 qbuf_con2_autosense[ QBUF$K_CON2_AUTOSENSE_SIZE ]; /* Cont entry 2 sense data */ uint8 qbuf_con3_autosense[ QBUF$K_CON3_AUTOSENSE_SIZE ]; /* Cont entry 3 sense data */ uint8 qbuf_con4_autosense[ QBUF$K_CON4_AUTOSENSE_SIZE ]; /* Cont entry 4 sense data */ } qbuf_autosense_struc; } qbuf_autosense_union; uint8 qbuf$b_expected_entry_count; /* Number of status+continuation entries*/ /* used for extended sense data */ uint8 qbuf$b_received_entry_count; /* Number of status+continuation entries*/ /* received */ uint8 qbuf$b_filler[6]; /* Preserve QUADWORD alignment */ } QBUF; #pragma member_alignment restore /* restore from just before PKQ_SPDT start */ #define qbuf$b_autosense qbuf_autosense_union.qbuf_autosense #define qbuf$b_stat_autosense qbuf_autosense_union.qbuf_autosense_struc.qbuf_stat_autosense #define qbuf$b_con1_autosense qbuf_autosense_union.qbuf_autosense_struc.qbuf_con1_autosense #define qbuf$b_con2_autosense qbuf_autosense_union.qbuf_autosense_struc.qbuf_con2_autosense #define qbuf$b_con3_autosense qbuf_autosense_union.qbuf_autosense_struc.qbuf_con3_autosense #define qbuf$b_con4_autosense qbuf_autosense_union.qbuf_autosense_struc.qbuf_con4_autosense /* Define the layout of the ISP1020 firmware header * * ** TODO ** relocate this to ISP1020DEF.SDL */ typedef struct _isp_fw_hdr { uint16 jmpop; /* ISP1020 jump opcode */ uint16 jmpaddr; /* Jump address (end of copyright + 1) */ uint16 keyword; /* A keyword of unknown use */ uint16 firmware_length; /* Firmware length in words */ uint16 copy_flag; /* A copy flag of unknown use */ } ISP_FW_HDR; /* Error status mapping table, used to convert a QLogic * completion status code into a VMS status. */ typedef struct _err_status_map { int vms_status; int flags; } ERR_STATUS_MAP; #define PKQ_M_ESM_USEOF 1 /* Use operation flags to aid mapping */ #define PKQ_M_ESM_LOG 2 /* Log this error */ #define PKQ_M_ESM_LOGI 4 /* Log this error only after dev init */ ERR_STATUS_MAP err_status_map[] = { { SS$_NORMAL, 0 }, /* 0x00 Normal xport completion */ { SS$_TIMEOUT, /* 0x01 Incomplete xport (sel timeout) */ ( PKQ_M_ESM_USEOF | PKQ_M_ESM_LOGI )}, { SS$_DEVCMDERR, PKQ_M_ESM_LOG }, /* 0x02 DMA direction error */ { SS$_TIMEOUT, /* 0x03 Transport error error */ ( PKQ_M_ESM_USEOF | PKQ_M_ESM_LOG ) }, { SS$_MEDOFL, PKQ_M_ESM_LOG }, /* 0x04 SCSI bus reset */ { SS$_ABORT, PKQ_M_ESM_LOG }, /* 0x05 Driver aborted command */ { SS$_TIMEOUT, PKQ_M_ESM_LOG }, /* 0x06 Transport timeout */ { SS$_NORMAL, PKQ_M_ESM_LOG }, /* 0x07 Data overrun */ { SS$_NORMAL, PKQ_M_ESM_LOG }, /* 0x08 Command overrun */ { SS$_NORMAL, PKQ_M_ESM_LOG }, /* 0x09 Status overrun */ { SS$_CTRLERR, PKQ_M_ESM_LOG }, /* 0x0a Bad message after status phase */ { SS$_CTRLERR, PKQ_M_ESM_LOG }, /* 0x0b No message out after select */ { SS$_CTRLERR, PKQ_M_ESM_LOG }, /* 0x0c Extended ID message rejected */ { SS$_CTRLERR, PKQ_M_ESM_LOG }, /* 0x0d IDE message rejected */ { SS$_CTRLERR, PKQ_M_ESM_LOG }, /* 0x0e Abort message rejected */ { SS$_CTRLERR, PKQ_M_ESM_LOG }, /* 0x0f Reject message rejected */ { SS$_CTRLERR, PKQ_M_ESM_LOG }, /* 0x10 NOP message rejected */ { SS$_CTRLERR, PKQ_M_ESM_LOG }, /* 0x11 Parity error message rejected */ { SS$_CTRLERR, PKQ_M_ESM_LOG }, /* 0x12 Device reset message rejected */ { SS$_CTRLERR, PKQ_M_ESM_LOG }, /* 0x13 Identify message rejected */ { SS$_CTRLERR, PKQ_M_ESM_LOG }, /* 0x14 Unexpected bus free */ { SS$_NORMAL, PKQ_M_ESM_LOG }, /* 0x15 Data underrun */ { SS$_NORMAL, PKQ_M_ESM_LOG }, /* 0x16 Command underrun */ { SS$_NORMAL, PKQ_M_ESM_LOG }, /* 0x17 Message underrun */ { SS$_DEVCMDERR, PKQ_M_ESM_LOG }, /* 0x18 Q'd xaction w/o disconnect priv */ { SS$_DEVCMDERR, PKQ_M_ESM_LOG }, /* 0x19 Q'd xaction w/ target routine */ { SS$_DEVCMDERR, PKQ_M_ESM_LOG } /* 0x1a Q'd xaction w/ q'ing disabled */ }; static int err_status_map_size = sizeof( err_status_map ) / sizeof( err_status_map[ 0 ] ); /* Error log structure definitions */ #pragma member_alignment save #pragma nomember_alignment BYTE typedef struct _pkq_erl_hdr { uint32 pkq_erl$l_lth_in_lw; /* Entry length (longwords!) */ uint8 pkq_erl$b_rev; /* Entry revision level */ uint16 pkq_erl$w_type; /* Entry type and subtype */ uint8 pkq_erl$b_scsi_id; /* SCSI ID */ } PKQ_ERL_HDR; typedef struct _pkq_erl_cmd { /* SCSI command */ uint8 pkq_erl$b_scsi_cdb_size; /* SCSI CDB Size (bytes) */ uint8 pkq_erl$b_scsi_cdb[ QBUF$K_CDB_SIZE ]; /* SCSI Command Data Block */ } PKQ_ERL_CMD; typedef struct _pkq_erl_msg { uint8 pkq_erl$b_msg_lth; /* Length of error log message portion */ uint8 pkq_erl$b_request[ ISP$K_ENTRY_LTH ]; /* Request entry */ uint8 pkq_erl$b_response[ ISP$K_ENTRY_LTH ]; /* Response entry */ } PKQ_ERL_MSG; typedef struct _pkq_erl_status { uint8 pkq_erl$b_status; /* The SCSI status byte itself */ } PKQ_ERL_STATUS; #define ISP$K_REG_FILE_CNT ( ISP$K_REG_FILE_SIZE / 2 ) typedef struct _pkq_erl_regs { uint8 pkq_erl$b_regs_lth; /* Length of ISP registers portion */ uint16 pkq_erl$w_regs[ ISP$K_REG_FILE_CNT ]; /* The ISP register contents */ } PKQ_ERL_REGS; #define ISP$K_SXP_REG_CNT ( ISP$K_SXP_REG_SIZE / 2 ) typedef struct _pkq_erl_sxp_regs { uint8 pkq_erl$b_sxp_regs_lth; /* Length of ISP SXP registers portion */ uint16 pkq_erl$w_sxp_regs[ ISP$K_SXP_REG_CNT ]; /* The ISP SXP register contents */ } PKQ_ERL_SXP_REGS; #define PKQ_ERL_REV 1 /* Save error log revision level */ #define PKQ_ERL_BUFSIZE ( sizeof(PKQ_ERL_HDR) + sizeof(PKQ_ERL_CMD) + sizeof(PKQ_ERL_MSG) + \ sizeof(PKQ_ERL_STATUS) + sizeof(PKQ_ERL_REGS) + sizeof(PKQ_ERL_SXP_REGS)) /* Error log buffer size */ /* Define SCSI error log type and subtype code */ #define SCSI$C_BUS_HUNG 1 /* SCSI BUS (or chip) is hung */ #define SCSI$C_ARB_FAIL 2 /* Port didn't arbitrate */ #define SCSI$C_SEL_FAIL 3 /* Target device selection failed */ #define SCSI$C_TIMEOUT 4 /* Target device timed out */ #define STMO$K_STO 1 /* subcode - chip detected */ #define STMO$K_INTTMO 2 /* subcode - host detected */ #define SCSI$C_PARITY 5 /* Parity error */ #define SCSI$C_PHASE_ERROR 6 /* SCSI bus phase error */ #define SCSI$C_BUS_RESET 7 /* SCSI bus reset detected */ #define SCSI$C_UNEXPECTED_INTERRUPT 8 /* Unexpected interrupt received */ #define SCSI$C_BUS_RESET_ISSUED 9 /* SCSI bus reset issued */ #define SCSI$C_RESEL_ERR 10 /* Error during reselection */ #define SCSI$C_CTL_ERR 11 /* Controller error */ #define SCTL$K_MAPOVRR 1 /* subcode - mapping overrun */ #define SCSI$C_BUS_ERR 12 /* Bus error */ #define SCSI$C_ILLEGAL_MSG 13 /* Illegal message received */ #define SCSI$C_ILLEGAL_RSP 14 /* Illegal response received from port */ #define SCSI$C_DEVICE_RESET 15 /* SCSI device has been reset */ #define SCSI$C_CMD_ABORT 16 /* Command aborted in progress */ #define SCSI$C_PORT_INIT 17 /* Port (re)initialized */ #pragma member_alignment restore /* Define codes which are stuffed into the first longword of * device dependent information in the port UCB to indicate * why the port failed to come online. */ #define PKQ$K_ERR_MAPREG 1 /* Err mapping ISP1020 registers */ #define PKQ$K_ERR_ALOQUE 2 /* Err allocating request/response rings */ #define PKQ$K_ERR_ALOKPB 3 /* Err allocating KPB */ #define PKQ$K_ERR_KPSTRT 4 /* Err starting kernel process */ #define PKQ$K_ERR_ALORSP 5 /* Err allocating rspid */ /* External routines. Note that most external routines are defined above * by including the following files: * exe_routines.h * ioc_routines.h * sch_routines.h * smp_routines.h */ extern void erl_std$deviceattn(); extern void exe_std$outcstring( char * ); /* ** TEMP m/b defined in exe_routines.h */ extern void exe_std$outcrlf(); /* ** TEMP m/b defined in exe_routines.h */ extern void ini$brk(); extern void sa$startio(); extern void sa$cancelio(); extern void sc$rl_create_port( UCB *, int, SPDT ** ); /* External global variables */ extern uint8 clu$gl_allocls; extern uint8 clu$gl_tape_allocls; extern DDT driver$ddt; extern DPT driver$dpt; extern FDT sa$functab; extern int exe$gl_abstim; extern int exe$gl_blakhole; extern PTE *exe$gl_eraseppt; extern HWRPB *exe$gpq_hwrpb; extern uint32 ioc$gl_naming; extern char sys$gq_version; extern const int mmg$gl_bwp_mask; /* Byte within page mask */ extern const int mmg$gl_page_size; extern int mmg$gl_vpn_to_va; extern uint16 risc_code_addr01; extern uint16 risc_code_length01; extern uint16 risc_code01[]; extern uint32 scs$gb_systemid; extern int sgn$gl_userd1; #pragma extern_model save #pragma extern_model globalvalue extern int PK$K_MAX_SCSI_ID = 7; extern int SA$K_KP_STKSIZ; extern int SA$K_KP_REGMSK; #pragma extern_model restore extern int not_valid_counter = 0; /* Forward routines -- Table of Contents */ int driver$init_tables (); int pk$abort_command ( PKQ_SPDT *spdt, SCDRP *scdrp, SCDT *scdt ); int pk$map_buffer ( PKQ_SPDT *spdt, SCDRP *scdrp, uint32 priority ); int pk$cmd_buffer_alloc ( PKQ_SPDT *spdt, SCDRP *scdrp, uint32 request_size, void **ret_addr ); int pk$cmd_buffer_dealloc ( PKQ_SPDT *spdt, SCDRP *scdrp ); void pk$interrupt ( IDB *idb ); int pk$negotiate_synch ( PKQ_SPDT *spdt, STDT *stdt ); int pk$reset_scsi_bus ( PKQ_SPDT *spdt, SCDRP *scdrp ); int pk$send_command ( PKQ_SPDT *spdt, SCDRP *scdrp, SCDT *scdt, STDT *stdt, UCB *ucb ); int pk$unmap_buffer ( PKQ_SPDT *spdt, SCDRP *scdrp ); int pk$wait_completion ( PKQ_SPDT *spdt, SCDRP *scdrp, SCDT *scdt, STDT *stdt ); int pkq_alloc_queues ( PKQ_SPDT *spdt ); RSPID pkq_alloc_rspid ( PKQ_SPDT *spdt,SCDRP *scdrp ); int pkq_buffer_map_restart ( CRCTX *crctx ); int pkq_bugcheck (); void pkq_convert_ver ( PKQ_SPDT *spdt, char *message ); int pkq_ctrl_init ( IDB *idb, DDB *ddb, CRB *crb ); void pkq_dealloc_rspid ( PKQ_SPDT *spdt, RSPID rspid ); int pkq_dump_firmware ( PKQ_SPDT *spdt, uint16 **console_fw, int *console_fw_length ); int pkq_get_firmware_ver ( uint16 *firmware ); void pkq_get_parameters ( PKQ_SPDT *spdt ); QBUF *pkq_get_qbuf ( SCDRP *scdrp ); STDT *pkq_get_stdt ( PKQ_SPDT *spdt, int scsi_id ); void pkq_init_rspid_table ( PKQ_SPDT *spdt, RTE *p_rspid_table ); void pkq_interrupt_fork ( IDB *idb, PKQ_SPDT *spdt, SCSI_UCB *ucb ); int pkq_load_firmware ( PKQ_SPDT *spdt); void pkq_log_error ( uint8 type, uint8 subtype, SCDT *scdt, PKQ_SPDT *spdt ); int pkq_mailbox_io ( PKQ_SPDT *spdt, uint32 rcount, uint16 command, uint16 r1, uint16 r2, uint16 r3, uint16 r4, uint16 r5 ); int pkq_map_registers ( PKQ_SPDT *spdt ); void pkq_nvram_command ( ADP *adp, uint64 *handle, uint16 cmd ); void pkq_nvram_delay ( int32 delta ); void pkq_nvram_off ( ADP *adp, uint64 *handle ); void pkq_nvram_on ( ADP *adp, uint64 *handle ); int pkq_nvram_read_array ( PKQ_SPDT *spdt, ISP_NVRAM *nvram ); uint16 pkq_nvram_read_word ( PKQ_SPDT *spdt, uint16 nvaddr ); int pkq_set_speed ( PKQ_SPDT *spdt); void pkq_nvram_toggle ( ADP *adp, uint64 *handle ); void pkq_nvram_write_word ( PKQ_SPDT *spdt, uint16 nvaddr, uint16 data ); void pkq_port_init ( KPB *kpb ); void pkq_print_message ( PKQ_SPDT *spdt, char *message, int severity); void pkq_print_firmware_ver ( PKQ_SPDT *spdt, int severity); void pkq_process_error ( PKQ_SPDT *spdt, QBUF *qbuf, ISP_ENTRY *isp_entry); void pkq_register_dump ( uint8 *buffer, PKQ_SPDT *spdt, SCSI_UCB *ucb ); int pkq_reset ( PKQ_SPDT *spdt ); int pkq_reset_scsi_bus ( PKQ_SPDT *spdt ); void pkq_ret_qbuf ( QBUF *qbuf ); SCDRP *pkq_rspid_to_scdrp ( PKQ_SPDT *spdt, RSPID rspid ); int pkq_select_firmware ( PKQ_SPDT *spdt ); int pkq_set_parameters ( PKQ_SPDT *spdt ); int pkq_send_marker ( PKQ_SPDT *spdt ); void pkq_struc_init ( CRB *crb_ptr, DDB *ddb_ptr, IDB *idb_ptr, ORB *orb_ptr, UCB *ucb_ptr ); void pkq_struc_reinit ( CRB *crb_ptr, DDB *ddb_ptr, IDB *idb_ptr, ORB *orb_ptr, UCB *ucb_ptr ); int pkq_unit_init ( IDB *idb, SCSI_UCB * ucb ); void pkq_unit_init_fork ( IDB *idb, SCSI_UCB *ucb, FKB *fkb ); void pkq_watchdog ( CRB * crb ); void stall_qman ( PKQ_SPDT *spdt ); void resume_qman ( PKQ_SPDT *spdt, uint32 uncond_flag); int dump_ram ( PKQ_SPDT *spdt, uint16 *buffer, uint32 risc_addr, uint32 size); uint32 backoff_delay ( uint32 delay, uint32 max); void init_target_reply (PKQ_SPDT *); int alloc_atio_buffer (PKQ_SPDT *, ISP_ENTRY *, ISP_ENTRY **); void dealloc_atio_buffer (ISP_ENTRY *); void build_ctio (PKQ_SPDT *, ISP_ENTRY *,ISP_ENTRY *); void set_reqq_entry (PKQ_SPDT *, int ); void ack_notify ( PKQ_SPDT *, ISP_ENTRY *); void reset_ack_notify ( PKQ_SPDT *); void clear_entry (ISP_ENTRY *entry); void return_atio (PKQ_SPDT *, ISP_ENTRY *); void send_enable_lun ( PKQ_SPDT *); void wait_enable_lun ( PKQ_SPDT *); uint32 pause_risc ( PKQ_SPDT *); uint32 release_risc ( PKQ_SPDT *); unsigned int target_mode ( PKQ_SPDT *); #if PKQ_DEBUG int pkq_alloc_trace ( PKQ_SPDT *spdt ); void pkq_trace_event (PKQ_SPDT *spdt, uint32 type, uint32 p1, uint32 p2, uint32 p3); void print64 ( char *, int64); void pkq_dump_nvram (PKQ_SPDT * ,ISP_NVRAM * ); void print_field (int , char *, int *); void out_value (int); char hex_digit (int); void pkq_trace_entry (PKQ_SPDT * ,ISP_ENTRY * ); #endif /* * Name: driver$init_tables - Initialize driver tables * * Abstract: Complete the initialization of the DPT, DDT, and FDT structures. * If a driver image contains a routine named DRIVER$INIT_TABLES then * this routine is called by the $LOAD_DRIVER service immediately * after the driver image is loaded and before any validity checks are * performed on the DPT, DDT, and FDT. A prototype version of these * structures is built into this image at link time from the * VMS$VOLATILE_PRIVATE_INTERFACES.OLB library. Note that the device * related data structures (e.g. DDB, UCB, etc.) have not yet been * created when this routine is called. Thus the actions of this * routine must be confined to the initialization of the DPT, DDT, * and FDT structures which are contained in the driver image. * * Inputs: None. * * Implicit * inputs: driver$dpt, driver$ddt, driver$fdt * These are the externally defined names for the prototype * DPT, DDT, and FDT structures that are linked into this driver. * * Outputs: None. * * Implicit * outputs: DPT, DDT and FDT fields are initialized to the desired values. * * Return * values: SS$_NORMAL * */ int driver$init_tables() { DPT *dpt = &driver$dpt; DDT *ddt = &driver$ddt; ini_dpt_name ( dpt, "PKQDRIVER" ); ini_dpt_flags ( dpt, DPT$M_SMPMOD | DPT$M_SCSI_PORT | DPT$M_NOUNLOAD | DPT$M_SNAPSHOT ); ini_dpt_adapt ( dpt, AT$_PCI ); ini_dpt_defunits ( dpt, 1 ); ini_dpt_ucbsize ( dpt, sizeof( SCSI_UCB )); ini_dpt_struc_init ( dpt, pkq_struc_init ); ini_dpt_struc_reinit ( dpt, pkq_struc_reinit ); ini_dpt_end ( dpt ); ini_ddt_start ( ddt, exe_std$kp_startio ); ini_ddt_kp_startio ( ddt, sa$startio ); ini_ddt_ctrlinit ( ddt, pkq_ctrl_init ); ini_ddt_unitinit ( ddt, pkq_unit_init ); /* ini_ddt_functab ( ddt, sa$functab ); */ ddt->ddt$ps_fdt_2 = &sa$functab; ini_ddt_cancel ( ddt, sa$cancelio ); ini_ddt_kp_stack_size ( ddt, SA$K_KP_STKSIZ ); ini_ddt_kp_reg_mask ( ddt, SA$K_KP_REGMSK ); ini_ddt_regdmp ( ddt, pkq_register_dump ); ini_ddt_diagbf ( ddt, 0 ); ini_ddt_erlgbf ( ddt, PKQ_ERL_BUFSIZE ); ini_ddt_end ( ddt ); return( SS$_NORMAL ); } /* * Name: pkq_struc_init - Driver database initialization * * Abstract: Called when the driver is loaded to initialize elements * of the driver database. * * Inputs: CRB address * DDB address * IDB address * ORB address * UCB address * * Outputs: None * * Implicit * outputs: UCB fields are initialized. * * Return * values: None */ void pkq_struc_init( CRB *crb, /* Channel request block */ DDB *ddb, /* Device data block */ IDB *idb, /* Interrupt dispatch block */ ORB *orb, /* Object rights block */ UCB *ucb ) /* Unit control block */ { HWRPB *hwrpb = exe$gpq_hwrpb; /* Hardware Restart Parameter Block */ /* Set up the device class and type. */ ucb->ucb$b_devclass = DC$_BUS; ucb->ucb$b_devtype = DT$_ISP1020; /* Set the device characteristics to indicate: */ ucb->ucb$l_devchar = DEV$M_AVL | DEV$M_ELG | DEV$M_IDV | DEV$M_ODV; ucb->ucb$l_devchar2 = DEV$M_NNM; /* Set the fork lock to IOLOCK8 and set the device IPL * based on platform. */ ucb->ucb$b_flck = SPL$C_IOLOCK8; ucb->ucb$b_dipl = 21; /* */ ucb->ucb$w_devbufsiz = 65535; ucb->ucb$l_devsts = UCB$M_NOCNVRT; } /* * Name: struc_reinit - Structure reinitialization routine * * Abstract: This routine is called every time the driver is loaded * or reloaded to initialize elements of the device database. * * Inputs: CRB address * DDB address * IDB address * ORB address * UCB address * * Outputs: None * * Implicit * outputs: The structure elements are initialized. * * Return * values: None * */ void pkq_struc_reinit (CRB *crb, /* Channel request block */ DDB *ddb, /* Device data block */ IDB *idb, /* Interrupt dispatch block */ ORB *orb, /* Object rights block */ UCB *ucb ) /* Unit control block */ { /* Store the addresses of the interrupt routines procedure * descriptor and entry point in the VEC portion of the CRB. */ ddb->ddb$ps_ddt = &driver$ddt; dpt_store_isr( crb, pk$interrupt ); } /* * * Name: pkq_ctrl_init - Controller initialization entry point * * Abstract: Currently, this routine does nothing except return SS$_NORMAL. * * * Inputs: IDB address * * Implicit * inputs: IPL 31 * * Outputs: None * * Return * Values: SS$_NORMAL * */ int pkq_ctrl_init( IDB *idb, /* Interrupt Data Block pointer */ DDB *ddb, /* Device Data Block pointer */ CRB *crb ) /* Channel Resource Block pointer */ { return( SS$_NORMAL ); } /* * * Name: pkq_unit_init - Unit initialization entry point * * Abstract: It schedules a routine to run at fork IPL to do * the real work of port initialization. * * Inputs: IDB address * UCB address * Implicit * inputs: IPL 31 * * Outputs: None * * Return * Values: SS$_NORMAL * */ int pkq_unit_init( IDB *idb, /* Interrupt Data Block pointer */ SCSI_UCB * ucb ) /* Unit Control Block pointer */ { FKB *fkb; /* Fork Block pointer */ /* Set up IDB fields and schedule the fork routine if this * hasn't already been done. */ idb->idb$ps_owner = ( UCB * )ucb; idb->idb$ps_auxstruc = ucb; if( !_BBSSI( 0, &ucb->ucb$il_pk_exflags )) { fkb = ( FKB * )&ucb->ucb$ib_pk_inifkblk; fkb->fkb$b_flck = SPL$C_IOLOCK8; fork( pkq_unit_init_fork, idb, ucb, fkb ); } return( SS$_NORMAL ); } /* * * Name: pkq_unit_init_fork - Build port structures and init port * * Abstract: This fork routine is scheduled from unit * initialization to allocate port structures * and to initialize the port hardware. * * Inputs: IDB address * UCB address * FKB address * * Implicit * inputs: Fork lock held * * Outputs: * * Return * Values: None * */ void pkq_unit_init_fork( IDB *idb, /* Interrupt Data Block pointer */ SCSI_UCB *ucb, /* Unit Control Block pointer */ FKB *fkb ) /* Fork Block pointer */ { HEADER *head; /* pointer to use for rspid header*/ int32 retlen; /* actual allocation length */ PKQ_SPDT *spdt; /* SCSI Port Descriptor Table */ KPB *kpb; /* Kernel Process Block */ CRB *crb; /* Channel Request Block */ RTE *p_rspid_table; /* pointer to rspid table */ /* Release the fork routine busy lock. If entry was due to power * failure recovery, get the address of the SPDT from the UCB. */ _BBCCI( 0, &ucb->ucb$il_pk_exflags ); ucb->ucb$r_erlucb.ucb$r_ucb.ucb$l_fpc = 0; if( ucb->ucb$r_scsi_ucb.ucb$l_sts & UCB$M_POWER ) { spdt = (PKQ_SPDT *)ucb->ucb$r_scsi_ucb.ucb$l_pdt; } else { /* ** allocate a rspid_table */ if(ERROR( exe_std$alononpaged( PKQ_NUM_RSPID *(sizeof(RTE))+sizeof(HEADER), &retlen, (void **)&p_rspid_table ))) { ucb->ucb$r_scsi_ucb.ucb$l_devdepend = PKQ$K_ERR_ALORSP ; return; } else { head = (HEADER *)p_rspid_table; head->size = retlen; head->flink = ucb; p_rspid_table = (RTE *)(++head); } /* Create and initialize the SPDT using sc$rl_create port(). */ sc$rl_create_port( ( UCB * )ucb, sizeof( PKQ_SPDT ), ( SPDT ** )&spdt ); spdt->basespdt.spdt$iw_max_bus_width = 16; /* RCL001 */ spdt->basespdt.spdt$iw_config_bus_width = 16; /* RCL001 */ /* Initialize port-specific data in the SPDT: * - rspid table address; * - port type; * - structure versions; * - port capability flags; * - extra mapping registers (2 required for PCI); * - scsi port ID; * - maximum byte count; * - mapping and unmapping routine names. */ spdt->spdt$ps_rspid_table = p_rspid_table; pkq_init_rspid_table( spdt, p_rspid_table ); spdt->basespdt.spdt$w_spdt_type = SPDT$C_TYPE_PKQ; spdt->basespdt.spdt$l_version_check = ( SCDRP$C_VERSION | ( SPDT$C_VERSION << 8 ) | ( SCDT$C_VERSION << 16 )); spdt->basespdt.spdt$l_port_flags = ( SPDT$M_PFLG_SYNCH | SPDT$M_PFLG_ASYNCH | SPDT$M_PFLG_MAPPING_REG | SPDT$M_PFLG_DIR_DMA | SPDT$M_PFLG_LUNS | SPDT$M_PFLG_CMDQ | SPDT$M_PFLG_PORT_AUTOSENSE | SPDT$M_PFLG_SMART_PORT ); spdt->basespdt.spdt$is_extmapreg = 2; { DDB *ddb = ucb->ucb$r_scsi_ucb.ucb$l_ddb; spdt->basespdt.spdt$l_scsi_port_id = ddb->ddb$t_name[ 3 ] - 'A'; } spdt->basespdt.spdt$l_maxbytecnt = PKQ_MAXBCNT; spdt->basespdt.spdt$ps_cd_buffer_map = pk$map_buffer; spdt->basespdt.spdt$ps_cd_buffer_unmap = pk$unmap_buffer; /* ** Initialize the inquiry and request sense reply's that will be returned ** in target mode for those two messages. ** Note. RJS 21-May-1997 the call to initialise this data in the spdt ** needs to be done here. If it is done later (after target mode has ** been enabled) there would be a window of time when all zeros would ** be sent as a reply to a target mode inquiry. At this time we do ** NOT yet know if the adapter is capable of supporting target mode and ** so we cannot make the call conditional on target mode - but it does ** no harm to have the correct inquiry response data in the ** spdt - even if we do not subsequently enable target mode. */ init_target_reply(spdt); /* Set up the watchdog timer routine. */ crb = ucb->ucb$r_scsi_ucb.ucb$l_crb; crb->crb$l_scs_struc = ( unsigned int )spdt; crb->crb$l_toutrout = pkq_watchdog; crb->crb$b_flck = ucb->ucb$r_scsi_ucb.ucb$b_flck; crb->crb$l_duetime = -1; /* Map the ISP1020 device registers. */ if( ERROR( pkq_map_registers( spdt ))) { ucb->ucb$r_scsi_ucb.ucb$l_devdepend = PKQ$K_ERR_MAPREG; return; } /* Allocate the ISP1020 request and response queues. */ if( ERROR( pkq_alloc_queues( spdt ))) { ucb->ucb$r_scsi_ucb.ucb$l_devdepend = PKQ$K_ERR_ALOQUE; return; } #if PKQ_DEBUG /* Allocate a trace buffer for command tracing */ if( ERROR( pkq_alloc_trace( spdt ))) { ucb->ucb$r_scsi_ucb.ucb$l_devdepend = PKQ$K_ERR_ALOQUE; return; } #endif /* Allocate a KPB to do port initialization. */ if( ERROR( exe$kp_allocate_kpb( &kpb, SA$K_KP_STKSIZ, ( KP$M_IO & ~KP$M_DEALLOC_AT_END ), 0 ))) { ucb->ucb$r_scsi_ucb.ucb$l_devdepend = PKQ$K_ERR_ALOKPB; return; } spdt->spdt$ps_kpb = kpb; kpb->kpb$ps_ucb = ( UCB * )ucb; kpb->kpb$ps_scsi_ptr1 = idb; } /* For both normal initialization and power fail recovery, start * a kernel process to do port hardware initialization. */ spdt->spdt$l_pkq_flags |= SPDT$M_PKQ_UNIT_INIT; if( ERROR( exe$kp_start( kpb, pkq_port_init, SA$K_KP_REGMSK ))) { ucb->ucb$r_scsi_ucb.ucb$l_devdepend = PKQ$K_ERR_KPSTRT; return; } /* ** Setup TQE for timeing out IO's if the fw hangs. */ spdt->basespdt.spdt$ps_rl_timeout_setup_tqe ( spdt ); spdt->spdt$l_pkq_flags &= ~SPDT$M_PKQ_UNIT_INIT; } /* * * Name: pkq_port_init * * Abstract: Perform ISP1020 port hardware initialization or * re-initialization. * * Inputs: kpb Kernel Process Block * kpb$ps_ucb Unit Control Block * kpb$ps_scsi_ptr1 Interrupt Dispatch Block * * Implicit * inputs: Fork lock held * Kernel process context * * Outputs: * * Return * Values: None * */ void pkq_port_init( KPB *kpb ) { DECLARE_ISP(); /* Declare ISP register file and temps */ SCSI_UCB *ucb; /* Unit Control Block w/SCSI extensions */ PKQ_SPDT *spdt; /* SCSI Port Descriptor Table */ CRB *crb; /* CRB */ int reset_stat, fw_stat, param_stat, bus_stat; /* misc. routine status */ /* * 1. Reset the ISP1020 port; * 2. Load and start the ISP firmware; * 3. Read parameters out of the NVRAM; * 4. Load parameters into the ISP and initialize * the ISP request and response queues; * 5. Reset the SCSI Bus; * 6. Mark UCB and SPDT online; * 7. Enable interrupts. */ ucb = ( SCSI_UCB * )kpb->kpb$ps_ucb; crb = ucb->ucb$r_scsi_ucb.ucb$l_crb; spdt = ( PKQ_SPDT *)ucb->ucb$r_scsi_ucb.ucb$l_pdt; reset_stat = pkq_reset (spdt); fw_stat = pkq_select_firmware (spdt); param_stat = pkq_set_parameters (spdt); bus_stat = pkq_reset_scsi_bus (spdt); if (SUCCESS (reset_stat && fw_stat && param_stat && bus_stat)) { ucb->ucb$r_scsi_ucb.ucb$l_sts |= UCB$M_ONLINE; spdt->basespdt.spdt$l_sts |= SPDT$M_STS_ONLINE; ioc$node_function( crb, IOC$K_ENABLE_INTR ); WRITE_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_hccr, ISP$K_HCCR_CLR_RISC_INT ); WRITE_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_bus_semaphore, 0 ); WRITE_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_bus_icr, ( ISP$M_ICR_ALL_INT_ENB | ISP$M_ICR_RISC_INT_ENB )); pkq_print_message( spdt, "initialization complete; port online", STS$K_INFO ); } else { pkq_print_message( spdt, "initialization error; port marked offline", STS$K_WARNING ); } } /* * * Name: pkq_map_registers - Map ISP1020 registers * * Abstract: Map the ISP1020 registers into PCI memory space and * associate them with a handle that can be used in * later I/O calls. * * Inputs: SPDT address * * Outputs: * * Implicit * outputs: spdt$q_mem_base * spdt$q_iohandle_reg * spdt$q_direct_dma_base * spdt$l_direct_dma_size * * Return * Values: SS$_NORMAL routine completed normally * Other errors returned from called routines * */ int pkq_map_registers( PKQ_SPDT *spdt ) /* SCSI port descriptor table */ { ADP *adp = spdt->basespdt.spdt$l_adp; SCSI_UCB *ucb = ( SCSI_UCB * )spdt->basespdt.spdt$l_port_ucb; CRB *crb = ucb->ucb$r_scsi_ucb.ucb$l_crb; int status; /* * - Get the PCI base address of memory space using the node * number in the CRB; * - 'AND' off the irrelevant low order bits; * - Map the ISP1020 registers, returning a handle that is stored * in the SPDT and can be used in subsequent ioc$read_io and * ioc$write_io function calls. * - Get the start and size of the direct DMA window (converting the * size from megabytes (?!) to bytes) and store them in the SPDT * for subsequent use in buffer mapping. */ if( ERROR( status = ioc$read_pci_config( adp, crb->crb$l_node, offsetof( PCI, pci$l_base_address_1 ), sizeof( uint32 ), (int *) &spdt->spdt$q_mem_base ))) return( status ); spdt->spdt$q_mem_base &= PCI$M_BASE_ADDRESS_BITS_31_4; if( ERROR( status = ioc$map_io( adp, crb->crb$l_node, &spdt->spdt$q_mem_base, sizeof( ISP_REGISTERS ), IOC$K_BUS_MEM_BYTE_GRAN, &spdt->spdt$q_iohandle_reg ))) return( status ); if( ERROR( status = ioc$node_data( crb, IOC$K_DIRECT_DMA_BASE, &spdt->spdt$q_direct_dma_base ))) return( status ); /* * Since this driver uses 32 bit addresses to access memory we need to * ensure that the direct dma mapped space is in the 1'st 4gig of physical * address space. */ if( (spdt->spdt$q_direct_dma_base & 0xffffffff00000000) != 0 ) pkq_bugcheck(); if( ERROR( status = ioc$node_data( crb, IOC$K_DIRECT_DMA_SIZE, &spdt->spdt$l_direct_dma_size ))) return( status ); spdt->spdt$l_direct_dma_size <<= 20; /* * Make sure that it the direct dma map starts in the 1'st gig then it ends * there as well. */ if( (( spdt->spdt$q_direct_dma_base + spdt->spdt$l_direct_dma_size ) & 0xffffffff00000000) != 0 ) pkq_bugcheck(); return( SS$_NORMAL ); } /* * * Name: pkq_alloc_queues - allocate ISP1020 queue space * * Abstract: Allocate ISP1020 request and response queue rings and initialize * the SPDT cells that manage them. * * Inputs: SPDT address * * Outputs: None * * Implicit * outputs: spdt$ps_requestq_va * spdt$ps_responseq_va * spdt$ps_requestq_pa * spdt$ps_responseq_pa * spdt$ps_requestq_in * spdt$ps_responseq_out * * Return * Values: SS$_NORMAL routine completed successfully * other errors returned from called routines * */ int pkq_alloc_queues( PKQ_SPDT *spdt ) { uint32 rqlen; /* Requested length in bytes */ uint32 rqlip; /* Requested length in pages */ uint32 alolen; /* Allocated length in bytes */ ISP_ENTRY *alobuf; /* Address of allocated buffer */ int status; /* VMS status */ #define REQUEST_QUEUE_SIZE 64 /* Size of ISP1020 request queue */ #define RESPONSE_QUEUE_SIZE 64 /* Size of ISP1020 response queue */ /* * 1. Calculate the total length required to contain the ISP request and * response queues and convert to pages; * 2. Allocate physically contiguous space for the queues, returning errors * to our caller; * 3. Store the base virtual addresses for the request and response queues; * 4. Calculate and store the base physical addresses for the request and * response queues. * 5. Initialize the request queue in and response queue out indices. */ rqlen = ( sizeof( ISP_ENTRY) * ( REQUEST_QUEUE_SIZE + RESPONSE_QUEUE_SIZE )) + mmg$gl_bwp_mask; rqlip = rqlen >> mmg$gl_vpn_to_va; if( ERROR( status = spdt->basespdt.spdt$ps_rl_pool_alloc_physical( spdt, rqlip, &alolen, &alobuf ))) return( status ); spdt->spdt$ps_requestq_va = alobuf; spdt->spdt$ps_responseq_va = alobuf + REQUEST_QUEUE_SIZE; spdt->spdt$ps_requestq_pa = ioc$sva_to_pa( spdt->spdt$ps_requestq_va, 0, 0, 0 ) + spdt->spdt$q_direct_dma_base; spdt->spdt$ps_responseq_pa = ioc$sva_to_pa( spdt->spdt$ps_responseq_va, 0, 0, 0 ) + spdt->spdt$q_direct_dma_base; spdt->spdt$l_requestq_in = 0; spdt->spdt$l_responseq_out = 0; return( SS$_NORMAL ); } /* * * Name: pkq_reset - Reset the ISP1020 * * Abstract: Reset the ISP1020 RISC processor to put it in a known * initial state. * * Inputs: SPDT address * * Outputs: None * * Return * Values: SS$_NORMAL reset succeeded * SS$_NOSUCHDEV reset failed * other errors from timed wait * (reset failed) * */ int pkq_reset( PKQ_SPDT *spdt ) { DECLARE_ISP(); ADP *adp = spdt->basespdt.spdt$l_adp; uint64 *handle = &spdt->spdt$q_iohandle_reg; int status; int64 interval; /* Interval in ns being timed */ int64 due_time; /* Time event becomes due */ /* Reset the RISC processor by performing the following steps: * 1. Soft reset the ISP (dunno why ); * 2. Hard reset the ISP which puts the ISP in reset mode, clearing * "all" control and status bits; * 3. Release the ISP from reset mode; * 4. Clear the BIOS enable bit to allow the address bits to access * external RAM; * 5. Clear the burst mode bits in the config 1 register which were * missed by the hard reset; * 6. Wait for mailbox register 0 to clear, indicating that the reset * is complete; * 7. Read mailbox registers 1 - 4 and compare them to the expected * reset values, returning success if they match or failure if not. */ WRITE_ISP( adp, handle, isp$w_bus_icr, ISP$M_ICR_SOFT_RESET ); WRITE_ISP( adp, handle, isp$w_hccr, ISP$K_HCCR_RESET ); WRITE_ISP( adp, handle, isp$w_hccr, ISP$K_HCCR_RELEASE ); WRITE_ISP( adp, handle, isp$w_hccr, ISP$K_HCCR_WRITE_BIOS ); WRITE_ISP( adp, handle, isp$w_bus_config_1, 0 ); interval = 10000; if( READ_ISP( adp, handle, isp$w_mailbox_0 ) != 0 ) { if( ERROR( status = exe$timedwait_setup( &interval, &due_time ))) return( status ); while(( status = exe$timedwait_complete( &due_time )) == SS$_CONTINUE ) { if( READ_ISP( adp, handle, isp$w_mailbox_0 ) == 0 ) { status == SS$_NORMAL; break; } } if( ERROR (status )) return( status ); } if(( READ_ISP( adp, handle, isp$w_mailbox_1 ) == 0x4953 ) && ( READ_ISP( adp, handle, isp$w_mailbox_2 ) == 0x5020 ) && ( READ_ISP( adp, handle, isp$w_mailbox_3 ) == 0x2020 ) && ( READ_ISP( adp, handle, isp$w_mailbox_4 ) == 0x0001 )) return( SS$_NORMAL ); else return( SS$_NOSUCHDEV ); } /* * * Name: pkq_select_firmware - select operational ISP1020 firmware * * Abstract: Select the version of firmware to be used in operation: * dump the ISP1020 firmware that was loaded by the console * into a page-aligned buffer. Extract the version numbers * from the dumped and linked copies of the firmware and * set up the SPDT to use the newer of the two unless the * PKQ_USE_LINKED_FIRMWARE symbol is set to 1. If the * linked version is used, deallocate the buffer that contains * the dumped firmware. Finally, load and start the copy of * the firmware whose address is in the SPDT. * * * Inputs: SPDT address * * Outputs: None * * Return * Values: SS$_NORMAL firmware dumped successfully * SS$_TIMEOUT mailbox command timed out (load failed) * */ int pkq_select_firmware( PKQ_SPDT *spdt ) { uint16 *console_fw; /* Console firmware pointer */ int console_fw_length; /* Console firmware length */ int console_fw_version; /* Console firmware version */ int severity; /* Error message severity */ /* If the firmware hasn't been set up, select and load it now. * - Point the firmware virtual address at the linked firmware; * - Get the version number of the linked firmware into the SPDT; * - Reset the "firmware-from-console" indicator; * - Dump the firmware loaded by the console into non-paged pool; * - Get the version number of the linked firmware; * - If the console firmware version is higher than the linked version: * o Point the firmware virtual address at the dumped firmware; * o Get the version number of the dumped firmware into the SPDT; * o Set the "firmware-from-console" indicator. * - Otherwise, deallocate the pool containing the dumped firmware; * - Print the firmware version number; * - Load the firmware pointed to by the firmware virtual address * in the SPDT. */ severity = STS$K_INFO; if( spdt->spdt$ps_firmware_va == 0 ) { spdt->spdt$ps_firmware_va = risc_code01; spdt->spdt$l_firmware_version = pkq_get_firmware_ver( risc_code01 ); spdt->spdt$l_pkq_flags &= ~SPDT$M_PKQ_CONSOLE_FW; if( PKQ_USE_LINKED_FIRMWARE == FALSE ) { if( SUCCESS( pkq_dump_firmware( spdt, &console_fw, &console_fw_length ))) { console_fw_version = pkq_get_firmware_ver( console_fw ); if( spdt->spdt$l_firmware_version < console_fw_version ) { spdt->spdt$ps_firmware_va = console_fw; spdt->spdt$l_firmware_version = console_fw_version; spdt->spdt$l_pkq_flags |= SPDT$M_PKQ_CONSOLE_FW; } else { spdt->basespdt.spdt$ps_rl_pool_dealloc( spdt, console_fw, console_fw_length ); } } else { severity = STS$K_WARNING; pkq_print_message( spdt, "console firmware checksum error", severity ); } } } pkq_print_firmware_ver( spdt, severity ); return( pkq_load_firmware( spdt )); } /* * * Name: pkq_dump_firmware - dump the current firmware into memory * * Abstract: Allocate a memory buffer and dump the contents of * the ISP1020 RAM into it. * * Inputs: SPDT address * * Outputs: None * * Return * Values: SS$_NORMAL firmware dumped successfully * SS$_TIMEOUT mailbox command timed out (load failed) * */ int pkq_dump_firmware( PKQ_SPDT *spdt, uint16 **console_fw, int *console_fw_length ) { uint8 *buf; /* SVA of the firmware buffer */ int len; /* Length of the firmware buffer (bytes) */ uint64 pa; /* Physical address of the buffer */ uint64 pcia; /* PCI address of the buffer */ int32 size_remaining; /* Size that remains to be loaded */ int32 size_in_segment; /* Part of buffer in current contiguous segment */ uint8 *cur_sva; /* Current buffer SVA during load */ uint16 fw_length; /* Firmware length in 16-bit words */ uint16 length_offset; /* Offset of fw length within firmware image */ uint16 cur_risc_addr; /* RISC-space address of current load */ uint16 checksum; /* Driver-computed checksum */ int retries; /* Number of times to try dump operation */ int status; /* VMS status */ /* Dump the contents of ISP1020 RAM into memory: * - Verify the checksum of the RAM load; * - Get the length of the firmware; * - Allocate a buffer; * - While data remains to be dumped: * o Convert the source virtual address to be a physical address and determine the * amount of data (bytes) contained in the current page; * o Convert the physical address to be a PCI address; * o Invoke a mailbox command to dump the portion of the firmware contained in the * current page; * o Update the amount remaining to be dumped, the source address and the * destination address; * - Return the firmware buffer address and length to the caller. */ length_offset = risc_code_addr01 + ( offsetof( ISP_FW_HDR, firmware_length ) / 2 ); *console_fw = 0; *console_fw_length = 0; if( ERROR( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_VERIFY_CHECKSUM, risc_code_addr01, 0, 0, 0, 0 ))) { return( status ); } if( ERROR( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_READ_RAM_WORD, length_offset, 0, 0, 0, 0 ))) { return( status ); } /* Allocate non-paged pool for the firmware. */ fw_length = spdt->spdt$w_mailbox_2; if( ERROR( status = spdt->basespdt.spdt$ps_rl_pool_alloc( spdt, ( fw_length * 2 ), &len, &buf ))) { return( status ); } /* Dump the firmware into the buffer just allocated, then checksum the result; * if the checksum fails, try again until the dump succeeds or until retries * are exhausted. */ retries = 5; do { cur_sva = buf; cur_risc_addr = risc_code_addr01; size_remaining = fw_length * 2; /* While there is data left to dump, calculate the physical address and size * of the data contained in the next physically contiguous segment, then dump it. */ while( size_remaining > 0 ) { pa = ioc$sva_to_pa( cur_sva, 0, size_remaining, &size_in_segment ); pcia = pa + spdt->spdt$q_direct_dma_base; /* If this segment of the dump succeeded, update the parameters to * get to the next segment. */ /* ** ** There has been some problems with this command so it's being replaced with ** a routine that reads ram a word at a time. ** ** if( SUCCESS( status = pkq_mailbox_io( spdt, 5, ** ISP$K_MB_DUMP_RAM, cur_risc_addr, ** (( pcia >> 16 ) & 0xffff ), ( pcia & 0xffff ), ** size_in_segment / 2, 0 ))) { ** */ if( SUCCESS( status = dump_ram( spdt, (uint16 *)cur_sva, cur_risc_addr, size_in_segment/2))) { cur_sva += size_in_segment; cur_risc_addr += ( size_in_segment / 2 ); size_remaining -= size_in_segment; /* If the DUMP_RAM command failed, don't attempt to dump additional * segments during this iteration of the do while loop. */ } else { break; } } /* If the dump command succeeded, compute the firmware checksum. */ if( SUCCESS( status )) { int i; /* Loop counter */ uint16 *fwp; /* Firmware pointer */ checksum = 0; fwp = ( uint16 * )buf; for( i = 0; i < fw_length; i++ ) { checksum += fwp[ i ]; } } } while( checksum && --retries ); /* If the checksum failed, set SS$_BADPARAM status. */ if( checksum ) { status = SS$_BADPARAM; } /* If either the dump or the checksum failed, deallocate the firmware buffer. */ if( ERROR( status )) { spdt->basespdt.spdt$ps_rl_pool_dealloc( spdt, buf, len ); /* Both the dump and checksum succeeded; set the address and length of * the firmware buffer. */ } else { *console_fw = ( uint16 *)buf; *console_fw_length = len; } return( status ); } /* * * Name: pkq_load_firmware - load the ISP1020 firmware * * Abstract: Load the ISP1020 firmware and start it up. * * Inputs: SPDT address * * Outputs: None * * Return * Values: SS$_NORMAL firmware loaded/started successfully * SS$_TIMEOUT mailbox command timed out (load failed) * */ int pkq_load_firmware( PKQ_SPDT *spdt) /* SCSI PDT with driver extensions */ { uint8 *sva; /* SVA of the firmware buffer */ uint64 pa; /* Physical address of the buffer */ uint64 pcia; /* PCI address of the buffer */ int32 size_remaining; /* Size that remains to be loaded */ int32 size_in_page; /* Part of buffer in current page */ uint16 cur_risc_addr; /* RISC-space address of current load */ int status; /* VMS status */ /* Load and start the firmware given its address in the SPDT. * - Initialize the source (system virtual) address, the destination (RISC) address, * and the total size in 16-bit words remaining to be loaded; * - While data remains to be loaded: * o Convert the source virtual address to be a physical address and determine the * amount of data (bytes) contained in the current page; * o Convert the physical address to be a PCI address; * o Invoke a mailbox command to load the portion of the firmware contained in the * current page; * o Update the amount remaining to be loaded, the source address and the * destination address; * - Verify the checksum of the firmware just loaded; * - Start the firmware. */ sva = ( uint8 * )spdt->spdt$ps_firmware_va; cur_risc_addr = risc_code_addr01; size_remaining = ( *( uint16 * )( sva + offsetof( ISP_FW_HDR, firmware_length ))) * 2; while( size_remaining > 0 ) { pa = ioc$sva_to_pa( sva, 0, size_remaining, &size_in_page ); pcia = pa + spdt->spdt$q_direct_dma_base; if( ERROR( status = pkq_mailbox_io( spdt, 5, ISP$K_MB_LOAD_RAM, cur_risc_addr, (( pcia >> 16 ) & 0xffff ), ( pcia & 0xffff ), size_in_page / 2, 0 ))) { return( status ); } sva += size_in_page; cur_risc_addr += ( size_in_page / 2 ); size_remaining -= size_in_page; } if( ERROR( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_VERIFY_CHECKSUM, risc_code_addr01, 0, 0, 0, 0 ))) { return( status ); } if( ERROR( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_EXEC_FIRMWARE, risc_code_addr01, 0, 0, 0, 0 ))) { return( status ); } return( SS$_NORMAL ); } /* * * Name: pkq_get_firmware_ver - get version from firmware image * * Abstract: Locate the copyright test imbedded within the firmware * image, then locate the version number at the end of the * copyright and convert it to binary. Return the binary * version number to the caller. * * Inputs: Address of the firmware buffer. * * Outputs: None * * Return * Values: Firmware version number * */ int pkq_get_firmware_ver( uint16 *firmware ) { ISP_FW_HDR *hdr; uint16 *copyright; int copyright_length; char string[ 128 ], *sp, *subp; char temp; int version; /* Locate the beginning of the copyright notice as the first byte * beyond the fixed firmware header. Then calculate the length of * the copyright notice in words as the difference between the target * of the jump instruction and the end of the fixed header. * Pretty funky. */ hdr = ( ISP_FW_HDR * )firmware; copyright = ( uint16 * )(( char * )firmware + sizeof( ISP_FW_HDR )); copyright_length = hdr->jmpaddr - risc_code_addr01 - ( sizeof( ISP_FW_HDR ) / 2 ); /* Copy the copyright notice into a local string buffer, byte-swapping, * down-casing and converting nulls to blanks as we go. Finally, null * terminate the string. */ sp = string; while( copyright_length ) { temp = (( *copyright >> 8 ) & 0xff ); *sp++ = temp ? ( isupper( temp ) ? temp | 0x20 : temp ) : ' '; temp = ( *copyright++ & 0xff ); *sp++ = temp ? ( isupper( temp ) ? temp | 0x20 : temp ) : ' '; --copyright_length; } *sp = 0; /* Scan the local string for the substring "version ", and bump by * it to find the version number string. Convert the decimal * version number to binary, ignoring the decimal point but * terminating on any other non-digit character. */ subp = strstr( string, "version " ); subp += sizeof( "version " ) - 1; version = 0; while( *subp ) { temp = *subp++; if( isdigit( temp )) { version = ( 10 * version ) + temp - '0'; } else if( temp != '.' ) { break; } } /* Return the binary version number. YOW! */ return( version ); } /* * * Name: pkq_set_parameters - set ISP1020 parameters * * Abstract: Set the ISP1020 operational parameters based on * values stored in the NVRAM or on supplied defaults. * * Inputs: SPDT address * * Outputs: None * * Implicit * outputs: ISP1020 parameters are set up in the firmware * * Return * Values: SS$_NORMAL parameters were set * SS$_TIMEOUT error occurred in a mailbox command * */ int pkq_set_parameters( PKQ_SPDT *spdt ) { DECLARE_ISP(); /* Declare ISP register file and temps */ ADP *adp; /* Adaptor control block */ uint64 *handle; /* ISP register I/O handle */ int status; /* VMS status */ int scsi_id; /* SCSI (target) ID used in loop */ /* Get the operational parameters from the NVRAM or defaults. */ pkq_get_parameters( spdt ); /* Set the initiator's SCSI ID mask and number based on the NVRAM value. */ spdt->basespdt.spdt$l_scsi_bus_id = ( 1 << spdt->spdt$w_initiator_scsi_id ); spdt->basespdt.spdt$is_scsi_id_num = spdt->spdt$w_initiator_scsi_id; /* Set up the ADP pointer and I/O handle. */ adp = spdt->basespdt.spdt$l_adp; handle = &spdt->spdt$q_iohandle_reg; /* Set the FIFO threshhold. */ WRITE_ISP( adp, handle, isp$w_bus_config_1, ( spdt->spdt$v_fifo_threshhold | ISP$M_CONFIG_1_BURST_ENABLE )); /* Set Data and CMD channel burst. Must precede queue setup. */ if( ERROR( status = pkq_mailbox_io( spdt, 3, ISP$K_MB_SET_PCI_CONTROL_PARAMS, ( spdt->spdt$v_data_dma_burst_enable << 1 ), ( spdt->spdt$v_command_dma_burst_enable << 1 ), 0, 0, 0 ))) { return( status ); } /* Initialize request queue */ if( ERROR( status = pkq_mailbox_io( spdt, 5, ISP$K_MB_INIT_REQUEST_Q, REQUEST_QUEUE_SIZE, ( spdt->spdt$ps_requestq_pa >> 16 ) & 0xFFFF, spdt->spdt$ps_requestq_pa & 0xFFFF, spdt->spdt$l_requestq_in, 0 ))) { return( status ); } /* Initialize response queue */ if( ERROR( status = pkq_mailbox_io( spdt, 6, ISP$K_MB_INIT_RESPONSE_Q, RESPONSE_QUEUE_SIZE, ( spdt->spdt$ps_responseq_pa >> 16 ) & 0xFFFF, spdt->spdt$ps_responseq_pa & 0xFFFF, 0, spdt->spdt$l_responseq_out ))) { return( status ); } /* Set the SCSI ID */ if( ERROR( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_SET_INITIATOR_SCSI_ID, spdt->spdt$w_initiator_scsi_id, 0, 0, 0, 0 ))) { return( status ); } /* Set the Selection Timeout */ if( ERROR( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_SET_SELECTION_TIMEOUT, spdt->spdt$w_selection_timeout, 0, 0, 0, 0 ))) { return( status ); } /* Set retry count and delay */ if( ERROR( status = pkq_mailbox_io( spdt, 3, ISP$K_MB_SET_RETRY_COUNT, spdt->spdt$w_retry_count, spdt->spdt$w_retry_delay, 0, 0, 0 ))) { return( status ); } /* Set active negation for REQ/ACK and data. */ if( ERROR( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_SET_ACTIVE_NEGATION, (( spdt->spdt$v_req_ack_active_negation << 5 ) | ( spdt->spdt$v_data_active_negation << 4 )), 0, 0, 0, 0 ))) { return( status ); } /* Set tag age limit */ if( ERROR( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_SET_TAG_AGE_LIMIT, spdt->spdt$w_tag_age_limit, 0, 0, 0, 0 ))) { return( status ); } /* Set clock speed (plus adjust spdt$v_asynch_setup_time if necessary) */ if( ERROR( status = pkq_set_speed( spdt ))) { return( status ); } /* Set asynchronous data setup time */ if( ERROR( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_SET_ASYNCH_SETUP_TIME, spdt->spdt$v_asynch_setup_time, 0, 0, 0, 0 ))) { return( status ); } /* Set target parameters */ for( scsi_id = 0; scsi_id < 16; scsi_id++ ) { if( ERROR( status = pkq_mailbox_io( spdt, 4, ISP$K_MB_SET_TARGET_PARAMETERS, ( scsi_id << 8 ), spdt->spdt$r_tparams[ scsi_id].spdt$w_capabilities, spdt->spdt$r_tparams[ scsi_id].spdt$w_synch_params, 0, 0 ))) return( status ); } /* Set overrun recovery mode */ if( ERROR( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_SET_OVERRUN_RECOVERY_MODE, ISP$K_MB1_INTERRUPT_WITHOUT_RESET, 0, 0, 0, 0 ))) { return( status ); } if( target_mode( spdt )) { /* Enable Target Mode */ if( ERROR( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_ENABLE_TARGET, ISP$K_MB1_ENABLE_TARGET, 0, 0, 0, 0 ))) { return( status ); } } return( SS$_NORMAL ); } /* * * Name: pkq_get_parameters - read ISP parameters from NVRAM * * Abstract: Read the parameters from the NVRAM and store them in * the driver-specific portion of the SPDT. If the * contents of the NVRAM are invalid, supply defaults. * * Inputs: SPDT address * * Outputs: None * * Implicit * outputs: ISP parameters are stored in the SPDT * * Return * Values: None * */ void pkq_get_parameters( PKQ_SPDT *spdt ) { DECLARE_ISP(); int scsi_id; ISP_NVRAM isp_nvram, *nvram; ADP *adp = spdt->basespdt.spdt$l_adp; uint64 *handle = &spdt->spdt$q_iohandle_reg; uint16 isp_hwrev; isp_hwrev = (READ_ISP (adp, handle, isp$w_bus_config_0)) & 0xF; /* If the parameters have already been read from the NVRAM, exit. */ if( spdt->spdt$l_pkq_flags & SPDT$M_PKQ_PARAMS_READ ) { return; } /* Read the entire NVRAM. If the NVRAM read succeeds (iff NVRAM contents * have checksummed correctly and passed other sanity checks), store the * NVRAM parameter values in the SPDT. */ nvram = &isp_nvram; if(( PKQ_USE_NVRAM_DEFAULTS == FALSE ) && SUCCESS( pkq_nvram_read_array( spdt, nvram ))) { #if PKQ_DEBUG /* pkq_dump_nvram(spdt, nvram); */ #endif /* Store the adaptor-wide parameters in the SPDT. ** ** Note that sync offset is minimized to 8 for data integrity reasons. ** */ spdt->spdt$w_initiator_scsi_id = nvram->isp$v_nvr_initiator_scsi_id; spdt->spdt$w_bus_reset_delay = nvram->isp$b_nvr_bus_reset_delay; spdt->spdt$w_retry_count = nvram->isp$b_nvr_retry_count; spdt->spdt$w_retry_delay = nvram->isp$b_nvr_retry_delay; spdt->spdt$w_tag_age_limit = nvram->isp$b_nvr_tag_age_limit; spdt->spdt$w_selection_timeout = nvram->isp$w_nvr_selection_timeout; spdt->spdt$w_max_queue_depth = nvram->isp$w_nvr_max_queue_depth; spdt->spdt$v_fifo_threshhold = nvram->isp$v_nvr_fifo_threshhold; spdt->spdt$v_adaptor_enable = nvram->isp$v_nvr_adaptor_enable; spdt->spdt$v_asynch_setup_time = nvram->isp$v_nvr_asynch_setup_time; spdt->spdt$v_req_ack_active_negation = nvram->isp$v_nvr_req_ack_active_negation; spdt->spdt$v_data_active_negation = nvram->isp$v_nvr_data_active_negation; spdt->spdt$v_data_dma_burst_enable = nvram->isp$v_nvr_data_dma_burst_enable; spdt->spdt$v_command_dma_burst_enable = nvram->isp$v_nvr_command_dma_burst_enable; spdt->spdt$v_termination_low_enable = nvram->isp$v_nvr_termination_low_enable; spdt->spdt$v_termination_high_enable = nvram->isp$v_nvr_termination_high_enable; spdt->spdt$v_pcmc_burst_enable = nvram->isp$v_nvr_pcmc_burst_enable; spdt->spdt$v_60mhz_enable = nvram->isp$v_nvr_60mhz_enable; /* Store the per-target parameters in the SPDT */ for( scsi_id = 0; scsi_id < 16; scsi_id++ ) { spdt->spdt$r_tparams[ scsi_id ].spdt$v_renegotiate_on_error = nvram->isp$r_nvr_target[ scsi_id ].isp$v_nvr_renegotiate_on_error; spdt->spdt$r_tparams[ scsi_id ].spdt$v_stop_queue_on_check = nvram->isp$r_nvr_target[ scsi_id ].isp$v_nvr_stop_queue_on_check; spdt->spdt$r_tparams[ scsi_id ].spdt$v_auto_request_sense = nvram->isp$r_nvr_target[ scsi_id ].isp$v_nvr_auto_request_sense; spdt->spdt$r_tparams[ scsi_id ].spdt$v_tagged_queuing = nvram->isp$r_nvr_target[ scsi_id ].isp$v_nvr_tagged_queuing; spdt->spdt$r_tparams[ scsi_id ].spdt$v_synch_data_transfers = nvram->isp$r_nvr_target[ scsi_id ].isp$v_nvr_synch_data_transfers; spdt->spdt$r_tparams[ scsi_id ].spdt$v_wide_data_transfers = nvram->isp$r_nvr_target[ scsi_id ].isp$v_nvr_wide_data_transfers; spdt->spdt$r_tparams[ scsi_id ].spdt$v_parity_checking = nvram->isp$r_nvr_target[ scsi_id ].isp$v_nvr_parity_checking; spdt->spdt$r_tparams[ scsi_id ].spdt$v_disconnect_allowed = nvram->isp$r_nvr_target[ scsi_id ].isp$v_nvr_disconnect_allowed; spdt->spdt$r_tparams[ scsi_id ].spdt$b_synch_period = nvram->isp$r_nvr_target[ scsi_id ].isp$b_nvr_synch_period; spdt->spdt$r_tparams[ scsi_id ].spdt$b_synch_offset = nvram->isp$r_nvr_target[ scsi_id ].isp$v_nvr_synch_offset; spdt->spdt$r_tparams[ scsi_id ].spdt$w_execution_throttle = nvram->isp$r_nvr_target[ scsi_id ].isp$b_nvr_execution_throttle; spdt->spdt$r_tparams[ scsi_id ].spdt$v_device_enable = nvram->isp$r_nvr_target[ scsi_id ].isp$v_nvr_device_enable; } /* The NVRAM read failed for some reason. Use the default parameter settings. */ } else { spdt->spdt$w_initiator_scsi_id = ISP$K_NVR_INITIATOR_SCSI_ID; spdt->spdt$w_bus_reset_delay = ISP$K_NVR_BUS_RESET_DELAY; spdt->spdt$w_retry_count = ISP$K_NVR_RETRY_COUNT; spdt->spdt$w_retry_delay = ISP$K_NVR_RETRY_DELAY; spdt->spdt$w_tag_age_limit = ISP$K_NVR_TAG_AGE_LIMIT; spdt->spdt$w_selection_timeout = ISP$K_NVR_SELECTION_TIMEOUT; spdt->spdt$w_max_queue_depth = ISP$K_NVR_MAX_QUEUE_DEPTH; spdt->spdt$v_adaptor_enable = ISP$K_NVR_ADAPTOR_ENABLE; spdt->spdt$v_asynch_setup_time = ISP$K_NVR_ASYNCH_SETUP_TIME; spdt->spdt$v_req_ack_active_negation = ISP$K_NVR_REQ_ACK_ACTIVE_NEGATION; spdt->spdt$v_data_active_negation = ISP$K_NVR_DATA_ACTIVE_NEGATION; spdt->spdt$v_data_dma_burst_enable = ISP$K_NVR_DATA_DMA_BURST_ENABLE; spdt->spdt$v_command_dma_burst_enable = ISP$K_NVR_COMMAND_DMA_BURST_ENABLE; spdt->spdt$v_termination_low_enable = ISP$K_NVR_TERMINATION_LOW_ENABLE; spdt->spdt$v_termination_high_enable = ISP$K_NVR_TERMINATION_HIGH_ENABLE; spdt->spdt$v_pcmc_burst_enable = ISP$K_NVR_PCMC_BURST_ENABLE; spdt->spdt$v_60mhz_enable = 0 ; /* * If the ISP is 1040B V1, then set fifo threshold to 0 (8 bytes) to * ensure data integrity. For all other ISP revisions, set it to 2 * (32 bytes). */ if (isp_hwrev == ISP$K_HWREV_1040B_V1) spdt->spdt$v_fifo_threshhold = 0; else spdt->spdt$v_fifo_threshhold = ISP$K_NVR_FIFO_THRESHHOLD; for( scsi_id = 0; scsi_id < 16; scsi_id++ ) { spdt->spdt$r_tparams[ scsi_id ].spdt$v_renegotiate_on_error = ISP$K_NVR_RENEGOTIATE_ON_ERROR; spdt->spdt$r_tparams[ scsi_id ].spdt$v_stop_queue_on_check = ISP$K_NVR_STOP_QUEUE_ON_CHECK; spdt->spdt$r_tparams[ scsi_id ].spdt$v_auto_request_sense = ISP$K_NVR_AUTO_REQUEST_SENSE; spdt->spdt$r_tparams[ scsi_id ].spdt$v_tagged_queuing = ISP$K_NVR_TAGGED_QUEUING; spdt->spdt$r_tparams[ scsi_id ].spdt$v_synch_data_transfers = ISP$K_NVR_SYNCH_DATA_TRANSFERS; spdt->spdt$r_tparams[ scsi_id ].spdt$v_wide_data_transfers = ISP$K_NVR_WIDE_DATA_TRANSFERS; spdt->spdt$r_tparams[ scsi_id ].spdt$v_parity_checking = ISP$K_NVR_PARITY_CHECKING; spdt->spdt$r_tparams[ scsi_id ].spdt$v_disconnect_allowed = ISP$K_NVR_DISCONNECT_ALLOWED; spdt->spdt$r_tparams[ scsi_id ].spdt$b_synch_period = ISP$K_NVR_SYNCH_PERIOD; spdt->spdt$r_tparams[ scsi_id ].spdt$b_synch_offset = min(8,ISP$K_NVR_SYNCH_OFFSET); spdt->spdt$r_tparams[ scsi_id ].spdt$w_execution_throttle = ISP$K_NVR_EXECUTION_THROTTLE; spdt->spdt$r_tparams[ scsi_id ].spdt$v_device_enable = ISP$K_NVR_DEVICE_ENABLE; } } } /* * * Name: pkq_reset_scsi_bus - reset the SCSI bus * * Abstract: Call the common SCSI bus reset code as if we were a class * driver in order to provide correct synchronization. * * Inputs: SPDT address * * Outputs: None * * Implicit * outputs: The bus is reset. * * Return * Values: SS$_NORMAL Bus reset succeeded * */ int pkq_reset_scsi_bus( PKQ_SPDT *spdt ) { SCDRP local_scdrp, *scdrp; /* Fabricate a SCDRP and a pointer thereto */ int status; /* VMS status */ /* Fabricate and initialize an SCDRP on the KP stack, then call * the class drivers' reset SCSI bus routine which will make sure * that the bus reset is synchronized with other activity. */ scdrp = &local_scdrp; memset( scdrp, 0, sizeof( SCDRP )); scdrp->scdrp$w_scdrpsize = SCDRP$K_LENGTH; scdrp->scdrp$b_cd_type = DYN$C_SCDRP; scdrp->scdrp$b_flck = spdt->basespdt.spdt$is_flck; scdrp->scdrp$ps_spdt = ( SPDT * )spdt; scdrp->scdrp$ps_kpb = spdt->spdt$ps_kpb; status = spdt->basespdt.spdt$ps_cd_reset_scsi_bus( spdt, scdrp ); return( status ); } /* * * Name: pk$reset_scsi_bus - reset the SCSI bus * * Abstract: Issue the mailbox command to do a SCSI bus * reset and set the wait period before the * firmware will put the next command on the bus. * * Inputs: SPDT address * SCDRP address * * Outputs: None * * Implicit * outputs: The bus is reset. * * Return * Values: SS$_NORMAL Bus reset mailbox command succeeded * SS$_TIMEOUT The mailbox command timed out * */ int pk$reset_scsi_bus( PKQ_SPDT *spdt, SCDRP *scdrp ) { DECLARE_ISP(); int status; /* Reset the SCSI bus using a mailbox command. */ if( SUCCESS( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_BUS_RESET, spdt->spdt$w_bus_reset_delay, 0, 0, 0, 0 ))) { /* The bus reset succeeded. If this routine was called during * port (re-)initialization, exit immediately with success. * Otherwise, call the reset-detected code to clean up outstanding * requests on the port. */ if( spdt->spdt$l_pkq_flags & SPDT$M_PKQ_UNIT_INIT ) { status = SS$_NORMAL; } else { status = spdt->basespdt.spdt$ps_rl_reset_detected_fork( spdt, scdrp ); } /* Set the send-marker-required flag */ if( target_mode( spdt ) ) { spdt->spdt$l_pkq_flags |= (SPDT$M_PKQ_SEND_MARKER | SPDT$M_PKQ_SEND_ENABLE_LUN); } else { spdt->spdt$l_pkq_flags |= (SPDT$M_PKQ_SEND_MARKER ); } } return( status ); } /* * * Name: pkq_mailbox_io - send a mailbox command * * Abstract: Send an ISP1020 mailbox command and wait for it * to complete. Then read the outgoing mailbox * registers to see if the command succeeded * * Inputs: SCSI Port Descriptor Table address * Register load count * Mailbox command and parameters 1 - 5 * * Outputs: Outgoing mailbox contents * * Return * Values: SS$_NORMAL mailbox command succeeded * SS$_TIMEOUT mailbox command timed out * SS$_ * */ int pkq_mailbox_io( PKQ_SPDT *spdt, /* SCSI Port Descriptor Table address */ uint32 rcount, /* Mailbox register load count */ uint16 command, /* Mailbox 0 (command) load value */ uint16 r1, /* Mailbox 1 - 5 load values */ uint16 r2, /* */ uint16 r3, /* */ uint16 r4, /* */ uint16 r5 ) /* */ { #define MBX_INTERVAL 100000000 /* 100 ms */ uint16 mb_status; /* Mailbox command status */ int status; /* Routine status */ DECLARE_ISP(); /* Temps for READ_ISP/WRITE_ISP */ ADP *adp; /* Adaptor block pointer */ uint64 *handle; /* Pointer to ISP register file handle */ int64 interval; /* Interval in ns being timed */ int64 due_time; /* Time event becomes due */ uint16 icr_image; /* Copy of Interrupt Control Register */ int saved_ipl; /* IPL saved/restored by lock/unlock */ uint32 delay; /* backoff delay value in nano seconds */ /* Get the ADP address and I/O handle from the SPDT. */ adp = spdt->basespdt.spdt$l_adp; handle = &spdt->spdt$q_iohandle_reg; /* Lock the device, save the interrupt control register, disable interrupts if * they were enabled, and unlock the device. */ device_lock( spdt->basespdt.spdt$l_dlck, RAISE_IPL, &saved_ipl ); if(( icr_image = READ_ISP( adp, handle, isp$w_bus_icr )) & ISP$M_ICR_ALL_INT_ENB ) { WRITE_ISP( adp, handle, isp$w_bus_icr, 0 ); READ_ISP(adp, handle, isp$w_bus_icr ); } device_unlock( spdt->basespdt.spdt$l_dlck, saved_ipl, SMP_RESTORE ); /* Wait for the mailbox registers to become available by spinning on the host interrupt * bit in the HCCR. If they don't become available within the specified timeout period, * return an error to the caller. */ interval = MBX_INTERVAL; if(( READ_ISP( adp, handle, isp$w_hccr ) & ISP$M_HCCR_HOST_INTERRUPT ) != 0 ) { if( SUCCESS( status = exe$timedwait_setup( &interval, &due_time ))) { delay = 1000; while(( status = exe$timedwait_complete( &due_time )) == SS$_CONTINUE ) { if(( READ_ISP( adp, handle, isp$w_hccr ) & ISP$M_HCCR_HOST_INTERRUPT ) == 0 ) { status == SS$_NORMAL; break; } else { delay = backoff_delay( delay , 20000); } } } if( ERROR ( status )) { WRITE_ISP( adp, handle, isp$w_bus_icr, icr_image ); return( status ); } } #if PKQ_DEBUG trace_event(spdt,'mio1',rcount,r1,r2); trace_event(spdt,'mio2',r3 ,r4,r5); #endif /* Load the mailbox registers specified in the rcount parameter. * An rcount value of "n" means load registers 0 through n-1. The single * exception is that the MB_INIT_RESPONSE_Q command doesn't load mailbox * register 4 which normally contains the command queue address. */ switch( rcount ) { case 6: WRITE_ISP( adp, handle, isp$w_mailbox_5, r5 ); case 5: if( command != ISP$K_MB_INIT_RESPONSE_Q ) WRITE_ISP( adp, handle, isp$w_mailbox_4, r4 ); case 4: WRITE_ISP( adp, handle, isp$w_mailbox_3, r3 ); case 3: WRITE_ISP( adp, handle, isp$w_mailbox_2, r2 ); case 2: WRITE_ISP( adp, handle, isp$w_mailbox_1, r1 ); case 1: WRITE_ISP( adp, handle, isp$w_mailbox_0, command ); break; /* Anything else represents an internal driver error. Bugcheck. */ default: pkq_bugcheck(); } /* Send an interrupt request to the ISP processor to get its attention. */ WRITE_ISP( adp, handle, isp$w_hccr, ISP$K_HCCR_SET_HOST_INT ); /* In order not to overwrite its outgoing mailbox registers, the RISC processor * synchronizes with the host by acquiring the semaphore lock, then fills in the * registers and requests a RISC interrupt. * This routine therefore waits until the semaphore lock and RISC interrupt bits * are set, clears the RISC interrupt bit, reads and stores the mailbox registers, * then clears the semaphore lock bit to free the mailbox registers. */ interval = MBX_INTERVAL; if( !( READ_ISP( adp, handle, isp$w_bus_semaphore ) & ISP$M_SEMAPHORE_LOCK ) || !( READ_ISP( adp, handle, isp$w_bus_isr ) & ISP$M_ISR_RISC_INT )) { if( SUCCESS( status = exe$timedwait_setup( &interval, &due_time ))) { delay = 1000; while(( status = exe$timedwait_complete( &due_time )) == SS$_CONTINUE ) { if(( READ_ISP( adp, handle, isp$w_bus_semaphore ) & ISP$M_SEMAPHORE_LOCK ) && ( READ_ISP( adp, handle, isp$w_bus_isr ) & ISP$M_ISR_RISC_INT )) { status == SS$_NORMAL; break; } else { delay = backoff_delay( delay , 20000); } } } if( ERROR( status )) { WRITE_ISP( adp, handle, isp$w_bus_icr, icr_image ); return( status ); } } WRITE_ISP( adp, handle, isp$w_hccr, ISP$K_HCCR_CLR_RISC_INT ); mb_status = READ_ISP( adp, handle, isp$w_mailbox_0 ); spdt->spdt$w_mailbox_0 = mb_status; spdt->spdt$w_mailbox_1 = READ_ISP( adp, handle, isp$w_mailbox_1 ); spdt->spdt$w_mailbox_2 = READ_ISP( adp, handle, isp$w_mailbox_2 ); spdt->spdt$w_mailbox_3 = READ_ISP( adp, handle, isp$w_mailbox_3 ); spdt->spdt$w_mailbox_4 = READ_ISP( adp, handle, isp$w_mailbox_4 ); spdt->spdt$w_mailbox_5 = READ_ISP( adp, handle, isp$w_mailbox_5 ); WRITE_ISP( adp, handle, isp$w_bus_semaphore, 0 ); /* Dispatch on the mailbox status code */ switch( mb_status ) { /* Normal command completion. */ case ISP$K_MB_STS_COMMAND_COMPLETE: status = SS$_NORMAL; break; /* Various mailbox error conditions. Return an error * to the caller and let him decided what to do about it. */ case ISP$K_MB_STS_INVALID_COMMAND: case ISP$K_MB_STS_HOST_INTFC_ERROR: case ISP$K_MB_STS_TEST_FAILED: case ISP$K_MB_STS_COMMAND_ERROR: case ISP$K_MB_STS_PARAMETER_ERROR: status = SS$_BADPARAM; break; case ISP$K_MB_STS_BUSY: status = SS$_TIMEOUT; break; default: pkq_bugcheck(); } WRITE_ISP( adp, handle, isp$w_bus_icr, icr_image ); return( status ); } /* * * Name: pkq_bugcheck - issue a bugcheck * * Abstract: Issue a bugcheck * * Inputs: None * * Outputs: None * * Return * Values: SS$_NORMAL bugcheck succeeded * */ int pkq_bugcheck() { bug_check( INCONSTATE, FATAL, COLD ); return(SS$_NORMAL); } /* * * Name: pk$cmd_buffer_alloc - allocate a command buffer * * Abstract: Allocate a command/autosense buffer on behalf of * the caller and store the class driver's idea of * its address in the cell provided. * * Inputs: SPDT address * SCDRP address * Requested command buffer size * Address of cell to receive QBUF address * * * Outputs: The address of the QBUF (kind of). * * Return * Values: SS$_NORMAL the allocation succeeded * SS$_BADPARAM requested size larger than QBUF * */ int pk$cmd_buffer_alloc( PKQ_SPDT *spdt, /* SCSI PDT address */ SCDRP *scdrp, /* SCSI CDRP address */ uint32 request_size, /* Requested command buffer size */ void **ret_addr ) /* Cell to receive QBUF address */ { /* Allocate buffer space for the CDB and autosense data. The CDB is preceded by * 8 bytes of overhead for use by the class driver. * 1. Get the KPB address out of the SCDRP; * 2. If the requested CDB size exceeds the maximum CDB size, * return SS$_BADPARAM; * 3. Allocate a QBUF, which includes both the CDB and autosense buffer; * 4. Fill in the SCDRP with buffer addresses and lengths; * 5. Allocate a RSPID; * 6. Return the address of the class driver overhead field to the caller. */ /* ** TODO ** the command buffer addressing crock needs to be documented * 1. This routine allocates enough space for both command and sense data * plus the command length and a status cell. * 2. This routine returns a pointer to the status cell which must precede * the command length which must precede the command. * 3. The class driver understands the relationship of these fields and autoincs * to pass the address of the cdb_size into the send command routine. */ KPB *kpb = scdrp->scdrp$ps_kpb; QBUF *qbuf; RSPID rspid; if( request_size > QBUF$K_CDB_SIZE ) return( SS$_BADPARAM ); qbuf = pkq_get_qbuf( scdrp ); if( qbuf == NULL ) return( SS$_BADPARAM ); qbuf->qbuf$ps_scdrp = scdrp; rspid = pkq_alloc_rspid( spdt, scdrp ); if( rspid.seq_no == 0 ) return( SS$_BADPARAM ); qbuf->qbuf$r_rspid = rspid; qbuf->qbuf$l_port_status = 0; *ret_addr = &qbuf->qbuf$l_class_driver; return( SS$_NORMAL ); } /* * * Name: pk$cmd_buffer_dealloc - deallocate a command buffer * * Abstract: Deallocates a command/autosense buffer (QBUF) on behalf * of the caller and zeros its pointer in the SCDRP. * * Inputs: SPDT address * SCDRP address * * Outputs: SCDRP * scdrp$l_cmd_ptr zeroed * * Return * Values: SS$_NORMAL * */ int pk$cmd_buffer_dealloc( PKQ_SPDT *spdt, /* SCSI PDT address */ SCDRP *scdrp ) /* SCSI CDRP address */ { QBUF *qbuf; /* Command/sense buffer */ /* Deallocate buffer space for the CDB and autosense data. * Deallocate the RSPID. */ /* If a command buffer was not successfully allocated in cmd_buffer_alloc, * scdrp->scdrp$l_cmd_ptr will be 0. Test for this rather than crashing try * to access the qbuf. */ if (scdrp->scdrp$l_cmd_ptr != 0) { qbuf = ( QBUF *)(( uint8 * )scdrp->scdrp$l_cmd_ptr - offsetof( QBUF, qbuf$l_scsi_cdb_size )); pkq_dealloc_rspid( spdt, qbuf->qbuf$r_rspid ); pkq_ret_qbuf( qbuf ); } return( SS$_NORMAL ); } /* * * Name: pkq_get_qbuf - allocate a QBUF * * Abstract: Allocate a QBUF structure out of non-paged pool and * return its address to the caller. * * Inputs: SCDRP address * * Outputs: None * * Return * Values: Address of allocated QBUF structure * */ QBUF *pkq_get_qbuf( SCDRP *scdrp ) /* Address of SCDRP for request */ { QBUF *qbuf; /* Address of allocated buffer */ int32 alo_size; /* Size of allocated buffer */ /* Get a queue buffer to hold the SCSI command data block and the returned autosense data: * - Attempt to allocate a queue buffer (QBUF), doing a fork and wait * until the allocation succeeds - if the fork and wait itself fails * return 0 to the caller; * - Zero the QBUF; * - Fill in the standard header portion of the QBUF; * - Return the QBUF address; */ while( ERROR( exe_std$alononpaged( sizeof( QBUF ), &alo_size, ( void ** )&qbuf ))) { if( ERROR( exe$kp_fork_wait( scdrp->scdrp$ps_kpb, ( FKB * )scdrp ))) return( 0 ); } memset( qbuf, 0, sizeof( QBUF )); qbuf->qbuf$w_size = alo_size; qbuf->qbuf$b_type = DYN$C_MISC; qbuf->qbuf$b_subtype = DYN$C_QBUF; return( qbuf ); } /* * * Name: pkq_ret_qbuf - return a QBUF * * Abstract: Deallocates a QBUF structure to non-paged pool. * * Inputs: QBUF address * * Outputs: None * * Return * Values: None * */ void pkq_ret_qbuf( QBUF *qbuf ) /* Buffer address */ { /* Return the queue buffer to the non-paged pool. */ exe_std$deanonpaged( qbuf ); return; } /* * * Name: pk$map_buffer - allocate mapping resources for transfer * * Abstract: Determine whether any of the pages invoved in the transfer * fall outside the system direct DMA window. If so, * allocate and map the transfer; otherwise, set up the SCDRP * so that the send command routine can tell that no mapping * is required. * * Inputs: SPDT address * SCDRP address * Mapping request priority * * Outputs: * * Implicit * outputs: SCDRP fields, initialized appropriately **TODO** more * * Return * Values: SS$_NORMAL mapping succeeded * */ int pk$map_buffer( PKQ_SPDT *spdt, /* SCSI PDT address */ SCDRP *scdrp, /* SCSI CDRP address */ uint32 priority ) /* Request priority */ { uint32 use_map_registers; /* Boolean: set if using map registers */ uint64 pa; uint32 page_count; /* Cell used for page or resource count */ PTE *svapte; /* System VA of PTE */ CRCTX *crctx; /* Counted resource context pointer */ KPB *kpb; /* Kernel process block pointer */ int status; /* VMS status */ /* Compute the number of pages required by the transfer. Assume * that mapping is NOT required. Check the physical address of each * of the pages to see if it falls outside the direct DMA window; * if so, force the use of map registers. */ svapte = scdrp->scdrp$l_svapte; page_count = ( scdrp->scdrp$l_boff + scdrp->scdrp$l_bcnt + mmg$gl_bwp_mask ) >> mmg$gl_vpn_to_va; for( use_map_registers = FALSE; page_count; --page_count, svapte++ ) { pa = svapte->pte$v_pfn; if( !svapte->pte$v_valid ) { pa = ioc_std$ptetopfn( svapte ); not_valid_counter++; } pa <<= mmg$gl_vpn_to_va; if( pa >= spdt->spdt$l_direct_dma_size ) { use_map_registers = TRUE; break; } } /* Initialize the Counted Resource Context block embedded * in the SCDRP: * - Standard structure header; * - CRAB pointer and forklock index from the SPDT; * - Priority from the input parameter; * - Auxiliary context from KPB pointer in the SCDRP; * - Item number, upper and lower bounds set to 0; * - Callback routine; * - Saved callback set to 0; * - Item count (number of mapping resources required); * computed as: * ( byte offset within mapping resource + * byte count + bytes per mapping resource - 1 ) / * bytes per mapping resource. * A shift is used in place of division since bytes * per mapping resource is constrained to be a power of 2. */ if( use_map_registers == TRUE ) { crctx = ( CRCTX * )&scdrp->scdrp$r_crctx_base; crctx->crctx$w_size = CRCTX$K_LENGTH; crctx->crctx$b_type = DYN$C_MISC; crctx->crctx$b_subtype = DYN$C_CRCTX; crctx->crctx$l_crab = spdt->basespdt.spdt$ps_crab; crctx->crctx$b_flck = spdt->basespdt.spdt$is_flck; crctx->crctx$l_flags = priority; crctx->crctx$l_aux_context = scdrp->scdrp$ps_kpb; crctx->crctx$l_item_num = 0; crctx->crctx$l_up_bound = 0; crctx->crctx$l_low_bound = 0; crctx->crctx$l_callback = pkq_buffer_map_restart; crctx->crctx$l_saved_callback = 0; crctx->crctx$l_item_cnt = (((( scdrp->scdrp$l_boff & spdt->basespdt.spdt$is_crctx_bwp_mask ) + scdrp->scdrp$l_bcnt + spdt->basespdt.spdt$is_crctx_bwp_mask ) >> spdt->basespdt.spdt$is_crctx_shift ) + spdt->basespdt.spdt$is_extmapreg ); /* Allocate the mapping resources. If the allocation fails and the * request was high priority, convert it to normal priority and try * the allocation again. If allocation fails again, stall the thread * until the map restart routine notifies us that the resources have * been allocated for us. */ if( ERROR( status = ioc$alloc_cnt_res( spdt->basespdt.spdt$ps_crab, crctx, 0, 0, 0 ))) { if( crctx->crctx$l_flags & CRCTX$M_HIGH_PRIO ) { crctx->crctx$l_flags &= ~CRCTX$M_HIGH_PRIO; status = ioc$alloc_cnt_res( spdt->basespdt.spdt$ps_crab, crctx, 0, 0, 0 ); } if( ERROR( status )) { kpb = scdrp->scdrp$ps_kpb; kpb->kpb$ps_sch_stall_rtn = ioc$return; kpb->kpb$ps_sch_restrt_rtn = 0; exe$kp_stall_general( kpb ); } } /* The mapping resources are in hand; load the map. */ scdrp->scdrp$l_port_boff = scdrp->scdrp$l_boff; scdrp->scdrp$l_port_svapte = scdrp->scdrp$l_svapte; scdrp->scdrp$l_sva_spte = 0; status = ioc$load_map( spdt->basespdt.spdt$l_adp, crctx, scdrp->scdrp$l_svapte, scdrp->scdrp$l_boff, &scdrp->scdrp$l_sva_dma ); /* The direct DMA window is being used. Set the SCDRP up to remember * that this request requires no mapping resource. */ } else { scdrp->scdrp$is_item_cnt = 0; scdrp->scdrp$is_item_num = 0; scdrp->scdrp$l_sva_dma = 0; scdrp->scdrp$l_sva_spte = 0; status = SS$_NORMAL; } return( status ); } /* * * Name: pkq_buffer_map_restart - restart the map routine * * Abstract: Wake the KP thread when mapping resources become * available. * * Inputs: CRCTX address * * Outputs: None * * Return * Values: SS$_NORMAL * */ int pkq_buffer_map_restart( CRCTX *crctx ) { exe$kp_restart( ( KPB * )crctx->crctx$l_aux_context, SS$_NORMAL ); return( SS$_NORMAL ); } /* * * Name: pk$unmap_buffer - deallocate mapping resources * * Abstract: If the request required mapping resources, deallocate * them and clear the appropriate SCDRP fields to indicate * that they are no longer held. * * Inputs: SPDT address * SCDRP address * * Outputs: scdrp$is_item_cnt * scdrp$is_item_num * scdrp$l_sva_dma * scdrp$l_svaspte all zeroed * * Return * Values: SS$_NORMAL deallocate succeeded * other error returned from deallocate request * */ int pk$unmap_buffer( PKQ_SPDT *spdt, /* SCSI Port Descriptor Table */ SCDRP *scdrp ) /* SCSI Class Driver Request Packet */ { CRCTX *crctx; /* Counted Resource Context Block */ uint32 status; /* VMS status code */ status = SS$_NORMAL; if( scdrp->scdrp$l_sva_dma ) { crctx = ( CRCTX * )&scdrp->scdrp$r_crctx_base; if( SUCCESS( status = ioc$dealloc_cnt_res( spdt->basespdt.spdt$ps_crab, crctx ))) { scdrp->scdrp$is_item_cnt = 0; scdrp->scdrp$is_item_num = 0; scdrp->scdrp$l_sva_dma = 0; scdrp->scdrp$l_sva_spte = 0; } } return( status ); } /* * * Name: pk$send_command - Queue a SCSI command to the adaptor * * Abstract: Set up the request queue entries required to describe * the SCSI command and notify the ISP1020 RISC firmware * that it has work to do. * * Inputs: SPDT address * SCDRP address * SCDT address * STDT address * UCB address * * Outputs: None * * Implicit * outputs: spdt$l_requestq_in updated * * Return * Values: SS$_NORMAL command has been queued * SS$_RETRY no queue space available * */ int pk$send_command( PKQ_SPDT *spdt, /* SCSI Port Descriptor Table */ SCDRP *scdrp, /* SCSI Class Driver Request Packet */ SCDT *scdt, /* SCSI Connection Descriptor Table */ STDT *stdt, /* SCSI Target Descriptor Table */ UCB *ucb ) /* Unit Control Block */ { #define MAX_ENTRIES_PER_REQUEST 3 DECLARE_ISP(); /* ISP register file and temps */ uint32 requestq_out; /* Index of ISP entry chip is processing */ uint32 requestq_in; /* Index of next available ISP entry */ int32 avail_entries; /* Number of available ISP entries */ ISP_ENTRY *isp_entry; /* Current ISP entry */ ISP_ENTRY *first_entry; /* Saved copy of first ISP entry */ QBUF *qbuf; /* QBUF pointer */ uint32 cdb_length; /* SCSI command data block length */ PTE *svapte; /* System VA of PTE */ int32 i; /* Data descriptor index */ int32 pa; /* Physical address */ int32 byte_count; /* Requested byte count */ int32 pad_count; /* request pad count */ int32 byte_offset; /* Offset w/in page of transfer start */ int32 status; /* VMS status */ /* Get the QBUF associated with this SCDRP */ qbuf = ( QBUF *)(( uint8 * )scdrp->scdrp$l_cmd_ptr - offsetof( QBUF, qbuf$l_scsi_cdb_size )); /* Clear this to force stall in pk$cmd_wait_completion */ qbuf->qbuf$l_port_status = 0; /* If this is the first command being sent after initialization * or bus reset, put a marker entry in the queue. * If this is the first marker after unit init we'll also send an enable_lun */ if( spdt->spdt$l_pkq_flags & (SPDT$M_PKQ_SEND_MARKER | SPDT$M_PKQ_SEND_ENABLE_LUN) ) { pkq_send_marker( spdt ); } /* Put a command entry in the ISP1020 request queue. Requestq_in is the * index of the next entry to be filled in by this routine. Requestq_out * is the index of the next entry the ISP1020 firmware is going to empty. */ /* Check to see if there is enough space available in the request queue * to accomodate a maximum size request (and still leave room for target * mode requests). If not, retry several times to give the firmware a * chance to empty the queue. * * NOTE: This code always leaves MIN_FREE_ENTRIES request queue entries * free in the request queue for use by the target mode code. * this saves the complication of having to stall waiting from the * interrupt fork. */ do { requestq_in = spdt->spdt$l_requestq_in; #if PKQ_DEBUG spdt->spdt$l_requestq_out = #endif requestq_out = ( uint32 )READ_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_mailbox_4 ); if(( avail_entries = requestq_out - requestq_in ) <= 0 ) { avail_entries += REQUEST_QUEUE_SIZE; } if( avail_entries <= MIN_FREE_ENTRIES ) { stall_qman( spdt ); } } while( avail_entries <= MIN_FREE_ENTRIES ); /* If space is available, calculate the address of the next * entry to be filled and save the entry pointer for later use. */ if( avail_entries > MAX_ENTRIES_PER_REQUEST ) { isp_entry = spdt->spdt$ps_requestq_va + requestq_in; first_entry = isp_entry; /* Fill in the header portion of the entry: * - Entry type is COMMAND; * - Entry count (number of entries that make up the command * including continuations) set to 1; * - Header Flags (flags set by the firmware when returning a * command in the response queue) set to 0; * - System (host) defined field, set to 0; * - Handle used to complete the request, set to the request's * QBUF address. */ isp_entry->isp$b_type = ISP$K_TY_COMMAND; isp_entry->isp$b_count = 1; isp_entry->isp$b_sysdef_1 = 0; isp_entry->isp$b_header_flags = 0; isp_entry->isp$l_handle = *( uint32 * )&qbuf->qbuf$r_rspid; /* Fill in the CDB portion of the entry: * - LUN from the SCDT; * - Target from the STDT; * - CDB length from the QBUF; * - Control flags: * o queuing characteristics based on SCDRP flags; * o No-disconnect flag based on the SCDT; * o Data direction (read/write/not-transfer) based * on the byte count and status fields in the SCDRP; * - Reserved field set to 0; * - Timeout set to the sum of the SCDRP DMA and disconnect * timeouts; * - Total count of data segments used by the command. * * Also get the byte count and starting byte offset from * the SCDRP. */ isp_entry->isp$b_lun = ( uint8 )scdt->scdt$l_scsi_lun; isp_entry->isp$b_target = stdt->stdt$is_scsi_id_num; cdb_length = qbuf->qbuf$l_scsi_cdb_size; isp_entry->isp$w_cdb_length = ( uint16 )cdb_length; isp_entry->isp$w_control_flags = 0; isp_entry->isp$v_cf_extend_autosens_en = 1; if( scdrp->scdrp$l_scsi_flags & SCDRP$M_FLAG_QUEUED_IO ) { if( scdrp->scdrp$is_queue_char == SCDRP$K_QCHAR_UNORDERED ) isp_entry->isp$v_cf_simple = 1; else if( scdrp->scdrp$is_queue_char == SCDRP$K_QCHAR_ORDERED ) isp_entry->isp$v_cf_ordered = 1; else if( scdrp->scdrp$is_queue_char == SCDRP$K_QCHAR_HEAD ) isp_entry->isp$v_cf_head = 1; } if(( scdt->scdt$is_con_flags & SCDT$M_CFLG_ENA_DISCON ) == 0 ) isp_entry->isp$v_cf_no_disconnect = 1; byte_count = scdrp->scdrp$l_bcnt; pad_count = scdrp->scdrp$l_pad_bcnt; byte_offset = scdrp->scdrp$l_boff; isp_entry->isp$v_cf_data_direction = ISP$K_DD_NOT_XFR; if( byte_count != 0 ) { if( scdrp->scdrp$is_sts & IRP$M_FUNC ) { isp_entry->isp$v_cf_data_direction = ISP$K_DD_READ; } else { isp_entry->isp$v_cf_data_direction = ISP$K_DD_WRITE; } } isp_entry->isp$w_reserved_0 = 0; isp_entry->isp$w_timeout = scdrp->scdrp$l_dma_timeout + scdrp->scdrp$l_discon_timeout; isp_entry->isp$w_data_seg_count = 0; /* ** Set up duetime for timeout code. */ scdrp->scdrp$l_duetime = exe$gl_abstim + scdrp->scdrp$l_dma_timeout + scdrp->scdrp$l_discon_timeout+1; scdrp->scdrp$v_cnx_dscn = 1; /* Fill in the data segment portion of the entry. First, get the * starting SVAPTE. */ svapte = scdrp->scdrp$l_svapte; /* If the CDB is of "normal" length, move it into the entry with * assignment statements. */ if( cdb_length <= 12 ) { *( uint64 *)&isp_entry->isp$b_cdb[ 0 ] = *( uint64 *)&qbuf->qbuf$b_scsi_cdb[ 0 ]; *( uint32 *)&isp_entry->isp$b_cdb[ 8 ] = *( uint32 *)&qbuf->qbuf$b_scsi_cdb[ 8 ]; /* Test the SCSI command in byte 0 of the CDB for an INQUIRE command */ if ( isp_entry->isp$b_cdb[0] == SCSI$K_INQUIRY ) { /* An INQUIRE command is to be sent. * Test for firmware version >= 2.00 * (Only firmware >= V2.0 can force wide/synch re-negotiation) */ if ( spdt->spdt$l_firmware_version >= 200) { /* Before sending the INQUIRE:- * - If we think the target is talking wide we must force re-negotiation of wide * - If we think the target is talking synch we must force re-negotiation of synch */ /* Make sure that we have the current NVRAM parameters in the spdt * ( They definitely SHOULD be there - test can't hurt - costs little ) */ pkq_get_parameters( spdt ); /* Test if we think we are talking synch - and if so force synch renegotiation * ( looking in the spdt seems the safest ) * ( because we KNOW they are there after the call to pkq_get_parameters ) */ if ( spdt->spdt$r_tparams[stdt->stdt$is_scsi_id_num].spdt$v_synch_data_transfers) { isp_entry->isp$v_cf_init_sdtr_negot = 1; } /* Test if we think we are talking wide - and if so force wide renegotiation */ if ( spdt->spdt$r_tparams[stdt->stdt$is_scsi_id_num].spdt$v_wide_data_transfers ) { isp_entry->isp$v_cf_init_wdtr_negot = 1; } } } /* A COMMAND entry has room for up to four data descriptors. If the transfer * requires the use of mapping registers (indicated by a nonzero scdrp$l_sva_dma), * a single data descriptor covers the entire transfer. Otherwise, we build * a descriptor for each page involved in the transfer. */ for( i = 0; ( i < 4 ) &&( ( byte_count > 0 ) || (pad_count > 0) ); i++ ) { if( byte_count>0 ) { if( scdrp->scdrp$l_sva_dma ) { isp_entry->isp$r_data_seg[ i ].isp$ps_base_address = scdrp->scdrp$l_sva_dma; isp_entry->isp$r_data_seg[ i ].isp$l_byte_count = byte_count; byte_count -= byte_count; } else { pa = svapte->pte$v_pfn; if( !svapte->pte$v_valid ) { pa = ioc_std$ptetopfn( svapte ); } pa <<= mmg$gl_vpn_to_va; isp_entry->isp$r_data_seg[ i ].isp$ps_base_address = ( void *)( spdt->spdt$q_direct_dma_base + pa + byte_offset ); isp_entry->isp$r_data_seg[ i ].isp$l_byte_count = (( byte_offset + byte_count ) > mmg$gl_page_size ) ? ( mmg$gl_page_size - byte_offset ) : byte_count; byte_offset = 0; byte_count -= isp_entry->isp$r_data_seg[ i ].isp$l_byte_count; svapte++; } isp_entry->isp$w_data_seg_count++; } else { /* here we must have more pad count to handle */ /* If we are doing a write, use the Erase Page. For a read, we must use a different page. Otherwise we corrupt the EXE$GL_ERASEPB buffer. In the case of a read, just use EXE$GL_BLAKHOLE since we aren't interested in the data that is written to the pad buffer anyways. MED 2/5/97 */ if (isp_entry->isp$v_cf_data_direction == ISP$K_DD_READ) pa = (exe$gl_blakhole) << mmg$gl_vpn_to_va; else pa = (exe$gl_eraseppt->pte$v_pfn) << mmg$gl_vpn_to_va; isp_entry->isp$r_data_seg[ i ].isp$ps_base_address = ( void *)( spdt->spdt$q_direct_dma_base + pa ); isp_entry->isp$r_data_seg[ i ].isp$l_byte_count = min( pad_count, mmg$gl_page_size); pad_count -= min( pad_count, mmg$gl_page_size); isp_entry->isp$w_data_seg_count++; } } /* The CDB is longer than 12 bytes, so it requires an EXTENDED command * entry. Change the entry type and copy the CDB into the entry. Note * that an EXTENDED command entry has no room for data segments, so at * least one continuation entry is required to perform a data transfer. */ } else { isp_entry->isp$b_type = ISP$K_TY_EXTENDED; memcpy( isp_entry->isp$b_cdb, qbuf->qbuf$b_scsi_cdb, cdb_length ); } /* Increment the request queue ring index and handle wrap-around. */ requestq_in++; if( requestq_in >= REQUEST_QUEUE_SIZE ) requestq_in = 0; /* Fill in a continuation entry if there is more data to transfer. * In the header: * - Entry type set to CONTINUATION; * - Entry count (number of entries that make up the command * including continuations) updated in the first entry and copied * to the current entry (which means the count is correct only * in the first and last entries if there are more than 2); * - Header Flags (flags set by the firmware when returning a * command in the response queue) set to 0; * - System (host) defined field, set to 0; */ while( (byte_count > 0) || (pad_count >0)) { isp_entry = spdt->spdt$ps_requestq_va + requestq_in; isp_entry->isp$b_type = ISP$K_TY_CONTINUATION; first_entry->isp$b_count++; isp_entry->isp$b_count = first_entry->isp$b_count; isp_entry->isp$b_sysdef_1 = 0; isp_entry->isp$b_header_flags = 0; /* A CONTINUATION entry has room for up to seven data descriptors. If the transfer * requires the use of mapping registers (indicated by a nonzero scdrp$l_sva_dma), * a single data descriptor covers the entire transfer. Otherwise, we build * a descriptor for each page involved in the transfer. */ for( i = 0; ( i < 7 ) && (( byte_count != 0 ) || (pad_count>0)); i++ ) { if( byte_count != 0) { if( scdrp->scdrp$l_sva_dma ) { isp_entry->isp$r_cont_seg[ i ].isp$ps_base_address = scdrp->scdrp$l_sva_dma; isp_entry->isp$r_cont_seg[ i ].isp$l_byte_count = byte_count; byte_count -= byte_count; } else { pa = svapte->pte$v_pfn; if( !svapte->pte$v_valid ) { pa = ioc_std$ptetopfn( svapte ); } pa <<= mmg$gl_vpn_to_va; isp_entry->isp$r_cont_seg[ i ].isp$ps_base_address = ( void *)( spdt->spdt$q_direct_dma_base + pa + byte_offset ); isp_entry->isp$r_cont_seg[ i ].isp$l_byte_count = (( byte_offset + byte_count ) > mmg$gl_page_size ) ? ( mmg$gl_page_size - byte_offset ) : byte_count; byte_offset = 0; byte_count -= isp_entry->isp$r_cont_seg[ i ].isp$l_byte_count; svapte++; } } else { /* here we must have more pad count to handle */ /* If we are doing a write, use the Erase Page. For a read, we must use a different page. Otherwise we corrupt the EXE$GL_ERASEPB buffer. In the case of a read, just use EXE$GL_BLAKHOLE since we aren't interested in the data that is written to the pad buffer anyways. MED 2/5/97 Since this is a CONTINUATION entry, we must check the data direction in the first_entry, not this isp_entry. */ if (first_entry->isp$v_cf_data_direction == ISP$K_DD_READ) pa = (exe$gl_blakhole) << mmg$gl_vpn_to_va; else pa = (exe$gl_eraseppt->pte$v_pfn) << mmg$gl_vpn_to_va; isp_entry->isp$r_cont_seg[ i ].isp$ps_base_address = ( void *)( spdt->spdt$q_direct_dma_base + pa ); isp_entry->isp$r_cont_seg[ i ].isp$l_byte_count = min( pad_count, mmg$gl_page_size); pad_count -= min( pad_count, mmg$gl_page_size); } first_entry->isp$w_data_seg_count++; } /* Increment the request queue ring index and handle wrap-around. */ requestq_in++; if( requestq_in >= REQUEST_QUEUE_SIZE ) requestq_in = 0; } /* Update the requestq_in index in the SPDT and let the firmware * know that it has more work by writing the updated index into * mailbox register 4. Exit with SS$_NORMAL. */ spdt->spdt$l_requestq_in = requestq_in; #if PKQ_DEBUG trace_event(spdt,'send', (((exe$gl_abstim&0xffff)<<16) + (requestq_in<<8) + requestq_out), (uint32)qbuf,(uint32)isp_entry); #endif WRITE_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_mailbox_4, requestq_in ); #if PKQ_DEBUG trace_event(spdt,'sdon', (((exe$gl_abstim&0xffff)<<16) + (requestq_in<<8) + requestq_out), (uint32)scdrp, ((qbuf->qbuf$r_rspid.index)+(qbuf->qbuf$r_rspid.seq_no<<16))); #endif return( SS$_NORMAL ); /* No room in the request queue after we waited for sufficient room. This * means either we're calulating the room wrong somewhere, we've misjudged * the maximum number of entries that can be used outside of send_command * or something else that we don't understand isn't working. Better crash. */ } else { pkq_bugcheck(); } } /* * * Name: pkq_send_marker - Queue a marker entry * * Abstract: Put a marker entry in the request queue. Any time the * bus is reset, all I/Os in progress or in queue will be * returned as incomplete, marked accordingly. The firmware * needs a way to tell where new activity begins. The marker * entry precedes new activity. The target mode firmware also * requires a notify acknowledge message to be sent to clear the * reset. This routine is also used to send the enable lun message * after unit init. * * Inputs: SPDT address * * Outputs: None * * Implicit * outputs: spdt$l_requestq_in updated * * Return * Values: SS$_NORMAL send marker succeeded * SS$_RETRY no queue space available * */ int pkq_send_marker( PKQ_SPDT *spdt ) /* SCSI Port Descriptor Table */ { DECLARE_ISP(); /* Isp register file and temps */ uint32 entries_needed; /* # entries neede by this routine */ uint32 requestq_out; /* Index of ISP entry chip is processing */ uint32 requestq_in; /* Index of next available ISP entry */ int32 avail_entries; /* Number of available ISP entries */ ISP_ENTRY *isp_entry; /* Current ISP entry */ int saved_ipl; /* IPL saved during device locking */ /* Put a marker entry in the ISP1020 request queue. Requestq_in is the * index of the next entry to be filled in by this routine. Requestq_out * is the index of the next entry the ISP1020 firmware is going to empty. * Send marker requires 2 entries. One for the marker entry and one for the * Notify acknowledge entry. */ entries_needed = 0; if((spdt->spdt$l_pkq_flags & SPDT$M_PKQ_SEND_MARKER)!= 0) entries_needed += 2; if( target_mode( spdt ) ) { if((spdt->spdt$l_pkq_flags & SPDT$M_PKQ_SEND_ENABLE_LUN)!= 0) entries_needed++; } requestq_in = spdt->spdt$l_requestq_in; do { #if PKQ_DEBUG spdt->spdt$l_requestq_out = #endif requestq_out = ( uint32 )READ_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_mailbox_4 ); if(( avail_entries = requestq_out - requestq_in ) <= 0 ) { avail_entries += REQUEST_QUEUE_SIZE; } if( avail_entries <= entries_needed ) { stall_qman( spdt ); } } while( avail_entries <= entries_needed ); /* If there is space in the request ring for the marker * entry, get the entry address and build a marker entry. */ if( ( avail_entries > 2 ) && ((spdt->spdt$l_pkq_flags & SPDT$M_PKQ_SEND_MARKER)!=0) ){ if( target_mode( spdt ) ) { reset_ack_notify(spdt); avail_entries--; requestq_in = spdt->spdt$l_requestq_in; } isp_entry = spdt->spdt$ps_requestq_va + requestq_in; isp_entry->isp$b_type = ISP$K_TY_MARKER; isp_entry->isp$b_count = 1; isp_entry->isp$b_sysdef_1 = 0; isp_entry->isp$b_header_flags = 0; isp_entry->isp$l_handle = 0; isp_entry->isp$b_lun = 0; isp_entry->isp$b_target = 0; isp_entry->isp$b_modifier = 2; /* Need symbols for this */ isp_entry->isp$b_marker_reserved_mbz = 0; #if PKQ_DEBUG trace_event(spdt,'mrkr',requestq_in,exe$gl_abstim,0); #endif /* Bump the request_in index modulo the queue size. */ requestq_in++; if( requestq_in >= REQUEST_QUEUE_SIZE ) requestq_in = 0; /* Update the requestq_in index in the SPDT and in mailbox * register 4. Then reset the send marker flag and return * success to the caller. */ spdt->spdt$l_requestq_in = requestq_in; #if PKQ_DEBUG trace_entry(spdt,isp_entry); #endif WRITE_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_mailbox_4, requestq_in ); spdt->spdt$l_pkq_flags &= ~SPDT$M_PKQ_SEND_MARKER; avail_entries--; } else { /* * No room in the ring. This shouldn't happen so crash. */ pkq_bugcheck(); } if( target_mode( spdt) && (spdt->spdt$l_pkq_flags & SPDT$M_PKQ_SEND_ENABLE_LUN) ) { if( avail_entries > 1 ) { send_enable_lun( spdt ); } else { /* * No room in the ring. This shouldn't happen so crash. */ pkq_bugcheck(); } } return(SS$_NORMAL); } /* * * Name: pk$cmd_wait_completion - wait for command completion * * Abstract: Stall the thread until the hardware completes the * command; when the thread is resumed, update the SCDRP * to reflect the completion and return to the caller of * pk$send_command. * * Inputs: SPDT address * SCDRP address * SCDT address * STDT address * * Outputs: TBS * * Return * Values: port status * */ int pk$cmd_wait_completion( PKQ_SPDT *spdt, SCDRP *scdrp, SCDT *scdt, STDT *stdt ) { KPB *kpb; /* Kernel Process Block pointer */ QBUF *qbuf; /* QBUF pointer */ uint32 status; /* VMS status */ /* Prepare for the stall: * - Get the KPB address; * - Set up the SPDT and SCDRP addresses in the KPB; * - Set up the restart and stall routine pointers * to do nothing; * - Stall until this thread is resumed. */ kpb = scdrp->scdrp$ps_kpb; kpb->kpb$ps_scsi_ptr1 = spdt; kpb->kpb$ps_scsi_scdrp = scdrp; kpb->kpb$ps_sch_restrt_rtn = NULL; kpb->kpb$ps_sch_stall_rtn = ioc$return; qbuf = ( QBUF *)(( uint8 * )scdrp->scdrp$l_cmd_ptr - offsetof( QBUF, qbuf$l_scsi_cdb_size )); #if PKQ_DEBUG trace_event(spdt,'wait', (uint32)kpb , (uint32)scdrp , (uint32)qbuf ); #endif if(qbuf->qbuf$l_port_status == 0) { status = exe$kp_stall_general( kpb ); } else { status = qbuf->qbuf$l_port_status; #if PKQ_DEBUG trace_event(spdt,'nwai', (uint32)kpb , (uint32)scdrp , exe$gl_abstim ); #endif } #if PKQ_DEBUG trace_event(spdt,'cont', (uint32)kpb , (uint32)scdrp , status); #endif /* Resume after the stall: * - Get the QBUF address; * - Store the SCSI status in the SCDRP; * - Compute the actual transfer count; * - If there is valid autosense data, store its length * and address in the SCDRP; * - Return the port status to the caller of pk$send_command. */ qbuf = ( QBUF *)(( uint8 * )scdrp->scdrp$l_cmd_ptr - offsetof( QBUF, qbuf$l_scsi_cdb_size )); *scdrp->scdrp$l_sts_ptr = qbuf->qbuf$b_scsi_status; scdrp->scdrp$l_trans_cnt = scdrp->scdrp$l_bcnt + scdrp->scdrp$l_pad_bcnt - qbuf->qbuf$l_bytes_not_xfrd; if( qbuf->qbuf$l_flags & QBUF$M_FL_AUTOSENSE_VALID ) { scdrp->scdrp$l_scsi_flags |= SCDRP$M_FLAG_ASENSE_VALID; scdrp->scdrp$is_sense_buffer_len = qbuf->qbuf$w_autosense_length; scdrp->scdrp$ps_sense_buffer = &qbuf->qbuf$b_autosense; } if( SUCCESS( status )) { status = qbuf->qbuf$l_port_status; } return( status ); } /* * * Name: pk$interrupt - field hardware interrupts * * Abstract: Process an interrupt from the ISP1020 RISC: * if the interrupt was legitimate, disable * diable interrupts and queue a fork process * to do the real work. * * Inputs: IDB address * * Implicit * inputs: Device IPL * * Outputs: None * * Implicit * outputs: Interrupt disabled * * Return * Values: None * */ void pk$interrupt( IDB *idb ) { SCSI_UCB *ucb; /* Unit Control Block pointer */ PKQ_SPDT *spdt; /* SCSI Port Descriptor Table pointer */ int saved_ipl; /* IPL saved/restored by device_lock/unlock */ DECLARE_ISP(); /* Define ISP_REGISTER structure and temps */ /* Process ISP interrupts: * 1. Take out the device lock for the port; * 2. If there was an interrupt for this device: * a. Disable port interrupts; * b. Restore the device lock; * c. Schedule a fork process to process the condition; * 3. Otherwise, the interrupt was spurious; ignore it by * simply restoring the lock. */ ucb = idb->idb$ps_auxstruc; spdt = ( PKQ_SPDT *)ucb->ucb$r_scsi_ucb.ucb$l_pdt; #if PKQ_DEBUG trace_event(spdt,'intr', (uint32)ucb ,(uint32)idb , exe$gl_abstim ); #endif device_lock( spdt->basespdt.spdt$l_dlck, RAISE_IPL, &saved_ipl ); if( READ_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_bus_isr) & ISP$M_ISR_RISC_INT ) { WRITE_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_bus_icr, 0 ); /* ** This was put in to ensure that the write to the icr gets done before ** we return from the interrupt. It was replaced with the fork block ** interlock code below. Now if we return with interrupts still enabled ** we'll get another interrupt which will see the ucb fkb in use and ** return after it reads the ICR register. ** ** READ_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, ** isp$w_bus_icr); ** */ if( ucb->ucb$r_erlucb.ucb$r_ucb.ucb$l_fpc != 0 ) { #if PKQ_DEBUG trace_event(spdt,'reen', 'ter ' , 'intr' , exe$gl_abstim ); #endif READ_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_bus_icr); } else { fork( pkq_interrupt_fork, idb, spdt, ucb ); } } device_unlock( spdt->basespdt.spdt$l_dlck, saved_ipl, SMP_RESTORE ); } /* * * Name: pkq_interrupt_fork - Process errors and response queue * * Abstract: Identify the cause the interrupt, process serious errors * and then loop through the response queue completing requests. * * Inputs: IDB address * SPDT address * UCB address * * Outputs: SPDT * spdt$l_responseq_out updated * * * Return * Values: None * */ void pkq_interrupt_fork( IDB *idb, /* Interrupt Data Block */ PKQ_SPDT *spdt, /* SCSI Port Descriptor Table */ SCSI_UCB *ucb ) /* Unit Control Block */ { int status; /* status value */ DECLARE_ISP(); /* Declare ISP register file and temps */ ADP *adp; /* Adaptor block pointer */ uint64 *handle; /* Pointer to ISP register file handle */ uint32 mb_status; /* Status returned in ISP Mailbox 0 */ uint32 responseq_in; /* Next entry for driver to handle */ uint32 responseq_out; /* Last entry that ISP chip filled in */ KPB *kpb; /* Kernel Process Block pointer */ SCDRP *scdrp; /* SCSI class driver request packet */ QBUF *qbuf; /* Queue buffer pointer */ ISP_ENTRY *isp_entry; /* Current ISP entry */ ISP_ENTRY *saved_atio; /* allocated atio block */ RSPID rspid; /* Response ID */ uint32 reset_flag; /* Bus reset occurred if TRUE */ int saved_ipl; /* IPL saved while acquiring device lock */ ISP_ENTRY *ctio_entry; /* pointer to ctio entry to avoid ugly casting */ uint32 requestq_in; /* request queue in index */ /* * Get the ADP and I/O handle addresses. Set the bus reset detected flag * to FALSE. * Also check to see if queue manager needs to be restarted by calling * resume_qman. */ #if PKQ_DEBUG trace_event(spdt,'ifrk', 0 , 0 , exe$gl_abstim ); #endif /* ** Reenable use of fork block by interrupt service routine. */ adp = spdt->basespdt.spdt$l_adp; handle = &spdt->spdt$q_iohandle_reg; reset_flag = FALSE; /* Main processing loop. Determine the cause of the interrupt and process * accordingly. */ do { /* The ISP1020 sets the bus semaphore lock to indicate the presence of * a serious error or the completion of a requested mailbox operation. * Read and dispatch on the status code presented in mailbox register 0. */ if( READ_ISP( adp, handle, isp$w_bus_semaphore ) & ISP$M_SEMAPHORE_LOCK ) { mb_status = ( uint32 )READ_ISP( adp, handle, isp$w_mailbox_0 ); #if PKQ_DEBUG { uint32 r1,r2,r3,r4,r5; trace_event(spdt,'ifrk', 'mbst' , mb_status , spdt->spdt$l_responseq_in ); r1 = ( uint32 )READ_ISP( adp, handle, isp$w_mailbox_1 ); r2 = ( uint32 )READ_ISP( adp, handle, isp$w_mailbox_2 ); r3 = ( uint32 )READ_ISP( adp, handle, isp$w_mailbox_3 ); r4 = ( uint32 )READ_ISP( adp, handle, isp$w_mailbox_4 ); r5 = ( uint32 )READ_ISP( adp, handle, isp$w_mailbox_5 ); trace_event(spdt,' if1',r1,r2,r3); trace_event(spdt,' if2',r4,r5,exe$gl_abstim); } #endif switch( mb_status ) { /* A SCSI bus reset has been detected. Acquire the device lock, call * the reset detected wait routine in scsi2common to prevent the queue * manager from passing through any new requests. Then restore the * device lock, and fall through to process any I/Os that may have * completed before the reset. */ case ISP$K_MB_STS_SCSI_BUS_RESET: case ISP$K_MB_STS_TIMEOUT_RESET: pkq_log_error( SCSI$C_BUS_RESET, 0, 0, spdt ); reset_flag = TRUE; device_lock( spdt->basespdt.spdt$l_dlck, RAISE_IPL, &saved_ipl ); spdt->basespdt.spdt$ps_rl_reset_detected_wait( spdt ); device_unlock( spdt->basespdt.spdt$l_dlck, saved_ipl, SMP_RESTORE ); resume_qman( spdt, TRUE ); break; /* The request queue has reached the threshhold specified in a * WAKEUP mailbox command. This driver doesn't currently issue * the WAKEUP command, so ignore this condition. */ case ISP$K_MB_STS_COMMAND_COMPLETE: case ISP$K_MB_STS_ALIVE: case ISP$K_MB_STS_REQUEST_Q_WAKEUP: break; /* * Here the isp1020 is notifying us that a data overrun has occured * on a data out command. This could be because a target has gone * wacky or because of a failure in the isp1020. Most likely it is * because of bad signal integrety on the bus causeing causing * reqs/acks to get lost (or counted twice). In any case it is * likely that data has been corrupted and we need to take serious * action. We will reset the bus. This will cause disk IO's to be * retried and tape IO's to go back to MTACP. */ case ISP$K_MB_STS_OVERRUN_NO_RESET: pkq_log_error( SCSI$C_CTL_ERR, SCTL$K_MAPOVRR , 0, spdt ); spdt->basespdt.spdt$ps_rl_queue_reset_fork( spdt , 0 ); resume_qman(spdt, TRUE ); break; /* One of the following has occurred: * - the ISP1020 firmware has detected an internal error; * - the firmware was unable to transfer a request queue entry * into its internal store; * - the firmware was unable to transfer a response from its * internal store to the response queue; * - the status code was unrecognized. * Log the error condition, then reinitialize the chip from the * ground up before restarting. */ case ISP$K_MB_STS_SYSTEM_ERROR: /* ** log an error and start kp to initialize the chip. */ pkq_log_error( SCSI$C_BUS_ERR, 0, 0, spdt ); ucb->ucb$r_scsi_ucb.ucb$l_sts &= ~UCB$M_ONLINE; spdt->basespdt.spdt$l_sts &= ~SPDT$M_STS_ONLINE; kpb = spdt->spdt$ps_kpb; if(( kpb->kpb$is_flags & KPB$M_VALID ) == 0 ) { exe$kp_start( kpb, pkq_port_init, SA$K_KP_REGMSK ); } break; /* ** the kp will reset the bus so don't ** fall through */ case ISP$K_MB_STS_REQUEST_XFER_ERROR: case ISP$K_MB_STS_RESPONSE_XFER_ERROR: case ISP$K_MB_STS_INVALID_COMMAND: case ISP$K_MB_STS_HOST_INTFC_ERROR: case ISP$K_MB_STS_TEST_FAILED: case ISP$K_MB_STS_COMMAND_ERROR: case ISP$K_MB_STS_PARAMETER_ERROR: case ISP$K_MB_STS_CHECKSUM_ERROR: case ISP$K_MB_STS_SHADOW_LOAD_ERROR: case ISP$K_MB_STS_BUSY: default: pkq_log_error( SCSI$C_BUS_RESET, 0, 0, spdt ); spdt->basespdt.spdt$ps_rl_queue_reset_fork( spdt , 0 ); resume_qman( spdt, TRUE ); break; } /* Clear the semaphore lock to allow the ISP1020 to update * outgoing mailbox registers 0 - 4. */ WRITE_ISP( adp, handle, isp$w_bus_semaphore, 0 ); } /* Process the response queue ring: * Responseq_in is the index of the next entry to be filled by * the ISP1020 firmware, obtained by reading outgoing mailbox * register 5. Responseq_out is the index of the next entry to * be emptied by this routine. Processing continues until the * responseq_out is equal to responseq_in. * Note that mailbox register 5 may be updated as soon as the * RISC interrupt is cleared. */ #if PKQ_DEBUG spdt->spdt$l_responseq_in = #endif responseq_in = ( uint32 )READ_ISP( adp, handle, isp$w_mailbox_5 ); WRITE_ISP( adp, handle, isp$w_hccr, ISP$K_HCCR_CLR_RISC_INT ); responseq_out = spdt->spdt$l_responseq_out; /* Start of code to process entries in response queue */ /* Process until response_que_in pointer equals response_que_out pointer */ /* ie until we have taken everything off of the response queue */ while( responseq_out != responseq_in ) { isp_entry = spdt->spdt$ps_responseq_va + responseq_out; /* Test if response is a status entry. */ if( isp_entry->isp$b_type == ISP$K_TY_STATUS ) { /* Response is a status entry */ /* Use the RSPID in the handle field of the response to locate the */ /* SCDRP and QBUF. It is possible that the request may have already */ /* been completed by a bus reset thread; if so, there will not be a */ /* valid request corresponding to this status response. In that case,*/ /* discard the status response. */ rspid = *( RSPID * )&isp_entry->isp$l_handle; if( scdrp = pkq_rspid_to_scdrp( spdt, rspid )) { /* There IS a valid request corresponding to this status response */ qbuf = ( QBUF *)(( uint8 * )scdrp->scdrp$l_cmd_ptr - offsetof( QBUF, qbuf$l_scsi_cdb_size )); qbuf->qbuf$l_bytes_not_xfrd = isp_entry->isp$l_bytes_not_xfrd; /* Note: Extended sense data is returned as follows: */ /* */ /* If length of the extended sense data <= 32 (QBUF$K_STAT_AUTOSENSE_SIZE) */ /* All the sense data is contained in the status entry */ /* */ /* If the length of the extended sense data > 32 (QBUF$K_STAT_AUTOSENSE_SIZE)*/ /* 1st 32 bytes of sense data is contained in status entry */ /* Remaining bytes of sense data are in */ /* continuation entries which contain 60 bytes of sense data */ /* (ie. QBUF$K_CON1_AUTOSENSE_SIZE,QBUF$K_CON2_AUTOSENSE_SIZE, */ /* QBUF$K_CON3_AUTOSENSE_SIZE, & QBUF$K_CON4_AUTOSENSE_SIZE respectively) */ /* There may be up to 4 such continuations as required */ /* */ /* In both cases isp_entry->isp$w_sense_data_length in the status entry is */ /* the overall length of the sense data */ /* */ /* In both cases isp_entry->isp$b_count is the total (expected) number of */ /* entries (status entry + continuation entries) that contain the sense data */ /* */ /* qbuf->qbuf$b_received_entry_count records the number of entries received */ /* qbuf->qbuf$b_expected_entry_count holds the total (expected) number of */ /* entrys */ /* Zeroise the status continuation entry information in the QBUF */ qbuf->qbuf$b_received_entry_count = 0; qbuf->qbuf$b_expected_entry_count = 0; #if PKQ_DEBUG trace_event(spdt,'iodn',rspid.index+(rspid.seq_no<<16) , (((exe$gl_abstim & 0xffff ) << 16) + (responseq_in<<8) + responseq_out ) , (uint32)qbuf ); #endif /* If the port completion status and the SCSI status are both */ /* normal, continue processing in line; otherwise, process the */ /* error before continuing. */ if(( isp_entry->isp$w_completion_status == ISP$K_CS_COMPLETE ) && ( isp_entry->isp$b_scsi_status == SCSI$C_GOOD )) { /* Port completion and SCSI status ARE both good */ qbuf->qbuf$b_scsi_status = SCSI$C_GOOD; qbuf->qbuf$l_port_status = SS$_NORMAL; } else { /* Port completion and SCSI status ARE NOT both good */ pkq_process_error( spdt, qbuf, isp_entry ); } /* If there are no associated status continuation entries for */ /* extended sense data the Kernel process is restarted. */ /* If there are status continuation entries for the extended sense*/ /* kernel process in NOT restarted until they have been processed.*/ if( qbuf->qbuf$b_received_entry_count == qbuf->qbuf$b_expected_entry_count){ exe$kp_restart( scdrp->scdrp$ps_kpb, SS$_NORMAL);} } else { /* There is No valid request corresponding to this status response */ /* We presume its stale because the request has already completed */ /* by a bus reset thread - just increment the stale count in the */ /* spdt and otherwise ignore the status entry. */ /* And indicate that we do not have a valid qbuf */ spdt->spdt$is_pkq_rspid_stale_count++; qbuf = NULL; } } /* Closes entry is a status response */ /* Test if entry is a status continuation entry */ else if ( isp_entry->isp$b_type == ISP$K_TY_STATUS_CONT ) { /* Response is a status continuation entry */ /* Test that we have a valid qbuf - ie. that we have already received */ /* a status entry and found a matching scdrp to match the handle */ /* If we do not have a qbuf there is no way to find it because there */ /* is no handle in a status continuation entry */ if( qbuf != NULL ) { /* We have a valid qbuf */ /* Test that we are expecting a status continuation entry and that it */ /* is in the correct sequence as indicated by isp_entry->isp$b_count */ /* /* NOTE: (this is NOT clearly documented in the QLOGIC literature) */ /* When there are status continuation entries then isp_entry->isp$b_count*/ /* "counts down" with each entry. So if there are (say) 3 continuations */ /* it would would be set thus: */ /* isp_entry->isp$b_count is set to 4 in status entry */ /* isp_entry->isp$b_count is set to 3 in 1st continuation entry */ /* isp_entry->isp$b_count is set to 2 in 2nd continuation entry */ /* isp_entry->isp$b_count is set to 1 in 3rd continuation entry */ if( (qbuf->qbuf$b_received_entry_count < qbuf->qbuf$b_expected_entry_count) && ( isp_entry->isp$b_count == qbuf->qbuf$b_expected_entry_count - qbuf->qbuf$b_received_entry_count)) { /* Status continuation entry was expected and is in sequence */ /* increment the count of entries received */ ++qbuf->qbuf$b_received_entry_count; /* Copy the extended sense data from the continuation entry */ /* into the appropriate part of the extended sense buffer */ switch (qbuf->qbuf$b_received_entry_count) { case 2: memcpy( qbuf->qbuf$b_con1_autosense, isp_entry->isp$b_sense_data_cont, QBUF$K_CON1_AUTOSENSE_SIZE ); break; case 3: memcpy( qbuf->qbuf$b_con2_autosense, isp_entry->isp$b_sense_data_cont, QBUF$K_CON2_AUTOSENSE_SIZE ); break; case 4: memcpy( qbuf->qbuf$b_con3_autosense, isp_entry->isp$b_sense_data_cont, QBUF$K_CON3_AUTOSENSE_SIZE ); break; case 5: memcpy( qbuf->qbuf$b_con4_autosense, isp_entry->isp$b_sense_data_cont, QBUF$K_CON4_AUTOSENSE_SIZE ); break; default: /* There should never be more than 4 status continuation */ /* entries - if there are treat it in the same way as */ /* an out of sequence entry - ie, assume its stale */ spdt->spdt$is_pkq_rspid_stale_count++; } /* Test whether we expect any more status continuation entries */ /* If no further status continuation entries are expected then */ /* set the flag that we have valid sense data and restart the kernel*/ /* process */ if (qbuf->qbuf$b_received_entry_count == qbuf->qbuf$b_expected_entry_count) { qbuf->qbuf$l_flags |= QBUF$M_FL_AUTOSENSE_VALID; exe$kp_restart( scdrp->scdrp$ps_kpb, SS$_NORMAL); } } else { /* We have a qbuf but - */ /* Status Continuation entry NOT expected or NOT in sequence */ /* Presume its stale because the associated request already completed*/ /* by a bus reset thread - just inc the stale count in the spdt */ /* and otherwise ignore it. */ /* Note this will cause all other status continuation entries before */ /* the next status entry to be similarly ignored */ spdt->spdt$is_pkq_rspid_stale_count++; } } else { /* Status continuation entry without a valid qbuf */ /* In this case there is no way that we can find the associated qbuf */ /* because there is no handle in a status continuation entry */ /* Again, presume its stale because the associated thread already */ /* completed by a bus reset thread - just inc the stale count in the */ /* spdt and otherwise ignore it. */ /* Note this will cause all other status continuation entries before */ /* the next status entry to be similarly ignored */ spdt->spdt$is_pkq_rspid_stale_count++; } } /* closes entry is status_continuation entry */ else { /* The entry is neither a status entry nor a status completion entry */ /* So something is wrong. Try and figure it out ! */ #if PKQ_DEBUG trace_entry(spdt,isp_entry); #endif /* If the entry has FULL set in the header flags, it is * a request that was rejected because the ISP1020 internal * queue is full. Complete the request with SCSI$C_QUEUE_FULL * and SS$_NORMAL. * * Note: fix the uninitialized qbuf problem here. */ if( isp_entry->isp$b_header_flags & ISP$M_HF_FULL ) { qbuf = NULL; rspid = *( RSPID * )&isp_entry->isp$l_handle; if( scdrp = pkq_rspid_to_scdrp( spdt, rspid )) { qbuf = ( QBUF *)(( uint8 * )scdrp->scdrp$l_cmd_ptr - offsetof( QBUF, qbuf$l_scsi_cdb_size )); } if( qbuf != NULL ) { qbuf->qbuf$b_scsi_status = SCSI$C_QUEUE_FULL; qbuf->qbuf$l_port_status = SS$_NORMAL; scdrp = ( SCDRP * )qbuf->qbuf$ps_scdrp; exe$kp_restart( scdrp->scdrp$ps_kpb, SS$_NORMAL ); #if PKQ_DEBUG trace_event(spdt,'qful', 'rply' , (uint32)qbuf , (uint32)scdrp ); #endif } /* * Note: Nothing below here is particularly performance * sensitive. */ /* * If this is an ATIO type then we need to start a target * mode operation. * * Build a ctio entry in the request queue. * */ } else if( target_mode( spdt ) && isp_entry->isp$b_type == ISP$K_TY_ATIO ) { status = alloc_atio_buffer( spdt, isp_entry, &saved_atio); if( status != SS$_NORMAL) pkq_bugcheck(); requestq_in = spdt->spdt$l_requestq_in; ctio_entry = spdt->spdt$ps_requestq_va + requestq_in; build_ctio( spdt, isp_entry, ctio_entry); set_reqq_entry(spdt, requestq_in); #if PKQ_DEBUG trace_entry(spdt,ctio_entry); #endif /* * It this is a CTIO type then we need to finish a target * mode operation, Return the ATIO to the target mode firmware. */ } else if( target_mode( spdt ) && isp_entry->isp$b_type == ISP$K_TY_CTIO ) { /* * Was the operation successful? */ if( isp_entry->isp$b_status == ISP$K_CTIO_COMPLETED_WO_ERROR) { return_atio(spdt, isp_entry); } else if( (isp_entry->isp$b_status == ISP$K_CTIO_ABORTED_BY_HOST )|| (isp_entry->isp$b_status == ISP$K_CTIO_UNACKED_EVENT_BY_HOST)) { /* ** here we should expect an immediate notify so don't ** send the atio back yet. */ } else if( (isp_entry->isp$b_status == ISP$K_CTIO_SCSI_BUS_RESET ) ) { return_atio(spdt, isp_entry); } else { pkq_log_error( SCSI$C_CMD_ABORT, 0 , 0, spdt ); return_atio(spdt, isp_entry); }; /* * It this is an immediate notify type then we need to handle * a target mode operation error */ } else if(target_mode( spdt) && ( isp_entry->isp$b_type == ISP$K_TY_NOTIFY) ) { ack_notify(spdt, isp_entry); return_atio(spdt,isp_entry); /* * All we need to do with enable lun is set the bit that says * that we got it. */ } else if( target_mode( spdt) && isp_entry->isp$b_type == ISP$K_TY_ENABLE_LUN ) { spdt->spdt$l_pkq_flags |= SPDT$M_PKQ_GOT_ENABLE_LUN; /* If the entry has BADHEADER or BADPAYLOAD set in the header * flags, the driver has an internal error. Bugcheck. */ } else if( isp_entry->isp$b_header_flags & ( ISP$M_HF_BADHEADER | ISP$M_HF_BADPAYLOAD )) { pkq_bugcheck(); } /* Otherwise, the entry is most likely a request being * returned after a bus reset. Ignore it and let the queue * manager deal with restarting the request. */ } /* Bump to the next response queue entry modulo queue size. */ responseq_out++; if( responseq_out >= RESPONSE_QUEUE_SIZE ) { responseq_out = 0; } /* ** conditionally resume the queue manager ( ie decrement count and ** restart if zero.) */ resume_qman( spdt, FALSE ); } /* closes while( responseq_out != responseq_in ) */ /* Emptied the response ring. Notify the ISP1020 by writing the * updated index into incoming mailbox 5 and store it in the SPDT. */ WRITE_ISP( adp, handle, isp$w_mailbox_5, responseq_out ); spdt->spdt$l_responseq_out = responseq_out; /* If the ISP1020 RISC has another interrupt pending, continue. */ } while( READ_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_bus_isr ) & ISP$M_ISR_RISC_INT ); /* If a bus reset was detected above, call reset_detected_fork to complete * the reset clean-up. The reset code is called directly rather than being * scheduled as a separate fork process as would happen if reset_detected_dipl * were being used. */ if( reset_flag ) { #if PKQ_DEBUG trace_event(spdt,'rst ', 'dete', 'cted' , exe$gl_abstim ); #endif spdt->spdt$l_pkq_flags |= (SPDT$M_PKQ_SEND_MARKER ); spdt->basespdt.spdt$ps_rl_reset_detected_fork( spdt ); } /* Enable port interrupts. */ ucb -> ucb$r_erlucb.ucb$r_ucb.ucb$l_fpc = 0; WRITE_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_bus_icr, ( ISP$M_ICR_ALL_INT_ENB | ISP$M_ICR_RISC_INT_ENB )); } /* * * Name: pkq_process_error - process completions with error * * Abstract: Process responses that indicate either a SCSI or * ISP1020 transport error. * * Inputs: QBUF address * ISP_ENTRY address * * Outputs: None * * Implicit * outputs: QBUF * qbuf$b_autosense_length * qbuf$b_autosense * qbuf$l_flags * qbuf$l_port_status * qbuf$b_scsi_status * * Return * Values: None * */ void pkq_process_error( PKQ_SPDT *spdt, QBUF *qbuf, ISP_ENTRY *isp_entry) { /* Process error conditions: this routine is entered when either * the SCSI status or the ISP response entry completion status * indicates an error. * * If the ISP response is COMPLETE, a SCSI error has occurred. * * If a check condition has occurred and autosense is enabled * * Move the total autosense data length into the QBUF. * * If all the sense data is contained in the status entry * move the sense data into the QBUF. * set expected number of entries in QBUF to 1 * set number of entries received in QBUF to 1 * * * Otherwise (only 1st 32 - QBUF$K_STAT_AUTOSENSE_SIZE - bytes of sense * data is in the status entry) and the additional sense data is contained * in status continuation entries. * move 32 ( QBUF$K_STAT_AUTOSENSE_SIZE) bytes of sense data into the QBUF * set expected number of entries in QBUF to continuation entries expected +1 * set number of entries received in QBUF to 1 * * If the ISP response is other than COMPLETE, a transport error * of some sort has occurred. * - Use the error status map to convert the ISP completion status * into a VMS status code. * - If required, use the ISP operation flags determine the VMS * status code. * - Log the error if appropriate. * - Return the VMS status code ... TODO * */ int isp_status; int vms_status; int copy_length; isp_status = isp_entry->isp$w_completion_status; if( isp_status == ISP$K_CS_COMPLETE ) { if( isp_entry->isp$b_scsi_status == SCSI$C_CHECK_CONDITION ) { if( isp_entry->isp$v_sf_got_sense ) { /* Got sense data */ /* Put the length of the entire autosense data into the QBUF */ qbuf->qbuf$w_autosense_length = isp_entry->isp$w_sense_data_length; /* Copy a maximum of QBUF$K_STAT_AUTOSENSE_SIZE bytes of */ /* sense data from the status entry */ copy_length = qbuf->qbuf$w_autosense_length; if( copy_length > QBUF$K_STAT_AUTOSENSE_SIZE) copy_length = QBUF$K_STAT_AUTOSENSE_SIZE; memcpy( qbuf->qbuf$b_stat_autosense, isp_entry->isp$b_sense_data, copy_length ); /* Put information on number of status entries received */ /* And total number (status + continuations) expected into QBUF */ qbuf->qbuf$b_received_entry_count = 1; qbuf->qbuf$b_expected_entry_count = isp_entry->isp$b_count; /* Set flag if we have all the sense data */ if (qbuf->qbuf$b_received_entry_count == qbuf->qbuf$b_expected_entry_count) qbuf->qbuf$l_flags |= QBUF$M_FL_AUTOSENSE_VALID; vms_status = SS$_NORMAL; } } else { /* No sense data */ vms_status = SS$_NORMAL; } } else { /* status is not CHECK CONDITION */ if( isp_status < err_status_map_size ) { if( isp_status == ISP$K_CS_RESET ) { spdt->spdt$l_pkq_flags |= SPDT$M_PKQ_SEND_MARKER; #if PKQ_DEBUG trace_event(spdt, 'rrtn', (uint32)isp_entry, (uint32)isp_entry->isp$l_handle, (uint32)isp_status); #endif } vms_status = err_status_map[ isp_status ].vms_status; if( err_status_map[ isp_status ].flags & PKQ_M_ESM_USEOF ) { if( isp_entry->isp$w_bus_operation & ISP$M_OF_TIMEOUT ) vms_status = SS$_TIMEOUT; else if( isp_entry->isp$v_of_aborted ) vms_status = SS$_ABORT; else if( isp_entry->isp$v_of_device_reset ) vms_status = SS$_DEVOFFLINE; else if( isp_entry->isp$v_of_bus_reset ) vms_status = SS$_MEDOFL; else if( isp_entry->isp$v_of_parity_error ) vms_status = SS$_PARITY; } if( err_status_map[ isp_status ].flags & PKQ_M_ESM_LOG ) { /* Log the error */ } else if( err_status_map[ isp_status ].flags & PKQ_M_ESM_LOGI ) { /* Log the error if we're done initialization */ } } else { vms_status = SS$_CTRLERR; /* Log the error */ } } qbuf->qbuf$b_scsi_status = isp_entry->isp$b_scsi_status; qbuf->qbuf$l_port_status = vms_status; } /* * * Name: pk$negotiate_synch - (Re-)negotiate synchronous transfers * * Abstract: This routine is intended to cause renegotiation of * synchronous transfers. It doesn't cause any direct * action at the port level. * * Inputs: SPDT address * STDT address * * Outputs: * * Implicit * outputs: STDT * stdt->stdt$is_dipl_flags renegotiate_synch set * * Return * Values: SS$_NORMAL * */ int pk$negotiate_synch( PKQ_SPDT *spdt, /* SCSI Port Descriptor Table */ STDT *stdt ) /* SCSI Target Descriptor Table */ { /* Turn on bit to cause synchronous negotiation. This currently * doesn't do anything to the target at all. */ stdt->stdt$is_dipl_flags |= STDT$M_DFLG_RENEGOTIATE_SYNC; return( SS$_NORMAL ); } /* * * Name: pk$abort_command - abort a single command * * Abstract: This routine is intended to abort a single command: * it is currently disabled. * * Inputs: SPDT address * SCDRP address * SCDT address * * Outputs: None * * Return * Values: SS$_NORMAL * */ int pk$abort_command( PKQ_SPDT *spdt, SCDRP *scdrp, SCDT *scdt ) { #if 0 STDT *stdt = scdt->scdt$ps_stdt; pkq_mailbox_io( spdt, 4, ISP$K_MB_ABORT, &spdt->spdt$w_mailbox_0, (( stdt->stdt$ib_target_id << 8 ) | ( scdt->scdt$l_scsi_lun & 0xff )), (( uint32 )scdrp >> 16 ), (( uint32 )scdrp & 0xffff )); #endif return( SS$_NORMAL ); } /* * * Name: pkq_log_error - Log an attention error * * Abstract: Log an attention error from the port. * * * * Inputs: error type * error subtype * SCDT address or 0 * SPDT address * * Outputs: None * * Implicit * outputs: TBS * * Return * Values: None * */ void pkq_log_error( uint8 type, uint8 subtype, SCDT *scdt, PKQ_SPDT *spdt ) { SCSI_UCB *ucb = ( SCSI_UCB * )spdt->basespdt.spdt$l_port_ucb; spdt->basespdt.spdt$iw_erl_type = ( int16 )( type + ( subtype << 8 )); spdt->basespdt.spdt$ps_erl_scdt = scdt; erl_std$deviceattn( spdt, ucb ); } /* * * Name: pkq_register_dump * * Abstract: Fill in the supplied error log buffer with device * registers and other SCSI port information. * * Inputs: error log buffer address * SPDT address * UCB address * * Outputs: None * * Implicit * outputs: Many error log buffer fields filled in. * * Return * Values: None * */ void pkq_register_dump( uint8 *buffer, PKQ_SPDT *spdt, SCSI_UCB *ucb ) { DECLARE_ISP(); uint8 *curbuf; SCDT *scdt; uint32 type; uint32 subtype; /* Routine-wide initialization: * - initialize the current buffer pointer * - get the SCDT address, if any * - get the errlog SCSI entry type and subtype */ curbuf = buffer; scdt = ( SCDT * )spdt->basespdt.spdt$ps_erl_scdt; type = spdt->basespdt.spdt$iw_erl_type & 0xff; subtype = ( spdt->basespdt.spdt$iw_erl_type >> 8 ) & 0xff; /* Fill in the header portion of the error log entry * except for the entry length in longwords which will * be filled in at the end of the routine. */ { PKQ_ERL_HDR *erl = ( PKQ_ERL_HDR * )curbuf; STDT *stdt; erl->pkq_erl$b_rev = PKQ_ERL_REV; erl->pkq_erl$w_type = spdt->basespdt.spdt$iw_erl_type; erl->pkq_erl$b_scsi_id = 0xFF; if( scdt && ( stdt = scdt->scdt$ps_stdt )) { erl->pkq_erl$b_scsi_id = stdt->stdt$is_scsi_id_num; } curbuf += sizeof( PKQ_ERL_HDR ); } /* Fill in the SCSI Command Data Block if it can * be located; otherwise, provide a null field. */ { PKQ_ERL_CMD *erl = ( PKQ_ERL_CMD * )curbuf; KPB *kpb; SCDRP *scdrp; char *cmdptr; uint8 cmdlth; uint32 i; /* The SCDT, KPB, SCDRP and its cmd_ptr field must all * be present in order to locate the SCSI command data block. */ if( scdt && ( kpb = spdt->basespdt.spdt$ps_chip_kpb ) && ( scdrp = kpb->kpb$ps_scsi_scdrp ) && ( cmdptr = scdrp->scdrp$l_cmd_ptr )) { cmdlth = *cmdptr++; erl->pkq_erl$b_scsi_cdb_size = cmdlth; memcpy( erl->pkq_erl$b_scsi_cdb, cmdptr, cmdlth ); curbuf += ( cmdlth + 1 ); } else { erl->pkq_erl$b_scsi_cdb_size = 0; curbuf++; } } /* Fill in the request/response ISP queue entries * for those error types in which they are relevant. */ { PKQ_ERL_MSG *erl = ( PKQ_ERL_MSG * )curbuf; ISP_ENTRY *isp_entry; /* ISP entry */ uint32 requestq_in; uint32 responseq_out; int32 i; /* On this adaptor, "bus hung" means "chip hung" and * "bus err" means "chip internal error". */ if(( type == SCSI$C_BUS_HUNG ) || ( type == SCSI$C_BUS_ERR )) { requestq_in = spdt->spdt$l_requestq_in; /* Look backward in the request ring for the most * recent command entry. If the message ring is * well-formed, it will be found within the last * MIN_FREE_ENTRIES entries; copy it to the * error log buffer. Otherwise, set the field to * NULLs. */ for( i = 0; i < MIN_FREE_ENTRIES; i++ ) { if( requestq_in == 0 ) { requestq_in = REQUEST_QUEUE_SIZE - 1; } --requestq_in; isp_entry = spdt->spdt$ps_requestq_va + requestq_in; if( isp_entry->isp$b_type == ISP$K_TY_COMMAND ) { break; } else { isp_entry = 0; } } if( isp_entry ) { memcpy( erl->pkq_erl$b_request, isp_entry, ISP$K_ENTRY_LTH ); } else { memset( erl->pkq_erl$b_request, 0, ISP$K_ENTRY_LTH ); } /* Copy the next response to be processed into the error log * entry, then update the current buffer pointer. */ responseq_out = spdt->spdt$l_responseq_out; isp_entry = spdt->spdt$ps_responseq_va + responseq_out; memcpy( erl->pkq_erl$b_response, isp_entry, ISP$K_ENTRY_LTH ); erl->pkq_erl$b_msg_lth = ( 2 * ISP$K_ENTRY_LTH ); curbuf += sizeof( PKQ_ERL_MSG ); /* Provide a null msg segment for other error types. */ } else { erl->pkq_erl$b_msg_lth = 0; curbuf++; } } /* Store the error status byte if the containing structures * are all present. */ { PKQ_ERL_STATUS *erl = ( PKQ_ERL_STATUS * )curbuf; KPB *kpb; SCDRP *scdrp; int *stsptr; /* Assume the status is not available and provide a * dummy status byte. */ erl->pkq_erl$b_status = 0xff; /* The error log SCDT, KPB, SCDRP and its sts_ptr field must all * be present in order to locate the SCSI status byte. */ if( scdt && ( kpb = spdt->basespdt.spdt$ps_chip_kpb ) && ( scdrp = kpb->kpb$ps_scsi_scdrp ) && ( stsptr = scdrp->scdrp$l_sts_ptr )) { erl->pkq_erl$b_status = *stsptr; } curbuf += sizeof( PKQ_ERL_STATUS ); } /* Pause the ISP RISC micro processor, so that RISC register * contents are frozen. */ { ADP *adp = spdt->basespdt.spdt$l_adp; uint64 *handle = &spdt->spdt$q_iohandle_reg; uint32 regnum; uint32 offset; uint32 temp; uint32 interval; /* Write a pause request into the HCCR and then wait * a while for it to happen. If we manage to exhaust * the interval count fall through anyway to take whatever * register values we can get. */ WRITE_ISP( adp, handle, isp$w_hccr, ISP$K_HCCR_PAUSE ); interval = 10000; while( --interval && !( READ_ISP( adp, handle, isp$w_hccr ) & ISP$M_HCCR_PAUSE_MODE )) {} } /* Dump the device registers if they are relevant. */ { PKQ_ERL_REGS *erl = ( PKQ_ERL_REGS * )curbuf; ADP *adp = spdt->basespdt.spdt$l_adp; uint64 *handle = &spdt->spdt$q_iohandle_reg; uint32 regnum; uint32 offset; uint32 temp; /* If the error being logged is a hung bus or bus error, dump * all of the PCI, RISC and DMA registers in the ISP1020. */ if(( type == SCSI$C_BUS_HUNG ) || ( type == SCSI$C_BUS_ERR )) { erl->pkq_erl$b_regs_lth = ISP$K_REG_FILE_SIZE; for( regnum = 0; regnum < ISP$K_REG_FILE_CNT; regnum++ ) { offset = 2 * regnum; if( SUCCESS( ioc$read_io( adp, handle, offset, sizeof( uint16 ), &temp ))) { if( offset & 2 ) { temp = (( temp >> 16 ) & 0xffff ); } else { temp &= 0xffff; } } else { temp = 0; } erl->pkq_erl$w_regs[ regnum ] = temp; } curbuf += sizeof( PKQ_ERL_REGS ); } else { erl->pkq_erl$b_regs_lth = 0; curbuf++; } } { PKQ_ERL_SXP_REGS *erl = ( PKQ_ERL_SXP_REGS * )curbuf; ADP *adp = spdt->basespdt.spdt$l_adp; uint64 *handle = &spdt->spdt$q_iohandle_reg; uint32 regnum; uint32 offset; uint32 temp; /* If the error being logged is a hung bus or bus error, dump * all of the SXP (SCSI processor) registers in the ISP1020. */ if(( type == SCSI$C_BUS_HUNG ) || ( type == SCSI$C_BUS_ERR )) { erl->pkq_erl$b_sxp_regs_lth = ISP$K_SXP_REG_SIZE; /* Select the SXP register set while preserving the FIFO threshhold * (and setting burst enable) */ WRITE_ISP( adp, handle, isp$w_bus_config_1, ( spdt->spdt$v_fifo_threshhold | ISP$M_CONFIG_1_BURST_ENABLE | ISP$M_CONFIG_1_SEL_SXP_REGS )); /* Dump the SXP registers into the error log buffer. */ for( regnum = 0; regnum < ISP$K_SXP_REG_CNT; regnum++ ) { offset = 0x80 + ( 2 * regnum ); /* TODO need symbolic offset when doc arrives */ if( SUCCESS( ioc$read_io( adp, handle, offset, sizeof( uint16 ), &temp ))) { if( offset & 2 ) { temp = (( temp >> 16 ) & 0xffff ); } else { temp &= 0xffff; } } else { temp = 0; } erl->pkq_erl$w_sxp_regs[ regnum ] = temp; } /* Bump the buffer pointer by the size of the SXP register file. */ curbuf += sizeof( PKQ_ERL_SXP_REGS ); /* Now DE-Select the SXP register set while preserving the * FIFO threshhold and setting burst enable. */ WRITE_ISP( adp, handle, isp$w_bus_config_1, ( spdt->spdt$v_fifo_threshhold | ISP$M_CONFIG_1_BURST_ENABLE )); /* Not logging registers. Just supply a zero length field. */ } else { curbuf++; erl->pkq_erl$b_sxp_regs_lth = 0; } } /* Release the ISP RISC micro processor to resume processing. */ { ADP *adp = spdt->basespdt.spdt$l_adp; uint64 *handle = &spdt->spdt$q_iohandle_reg; uint32 regnum; uint32 offset; uint16 temp; WRITE_ISP( adp, handle, isp$w_hccr, ISP$K_HCCR_RELEASE ); } /* Fill the entry length in longwords into the error log header. */ { PKQ_ERL_HDR *erl = ( PKQ_ERL_HDR * )buffer; erl->pkq_erl$l_lth_in_lw = ( curbuf - buffer + sizeof( long ) - 1 ) / sizeof( long ); } } /* * * Name: pkq_watchdog - detect hung port * * Abstract: This routine is called periodically to make sure * that the port is still alive. It issues the * GET FIRMWARE STATUS mailbox command and checks the * return status to ensure that the command completed: * if the command doesn't complete, the port is * reinitialized. * * Inputs: crb Channel request block address * * Outputs: None. * * Return * Values: None. * */ void pkq_watchdog( CRB * crb ) { PKQ_SPDT *spdt; /* SCSI Port Descriptor Table address */ SCSI_UCB *ucb; /* SCSI Unit Control Block address */ KPB *kpb; /* Kernel Process Block address */ int status; /* VMS status */ /* Issue the mailbox command. If it succeeds, start another timeout * and exit. */ spdt = ( PKQ_SPDT * )crb->crb$l_scs_struc; if( SUCCESS( status = pkq_mailbox_io( spdt, 2, ISP$K_MB_GET_FIRMWARE_STATUS, 0, 0, 0, 0, 0 ))) { crb->crb$l_duetime = exe$gl_abstim + PKQ_WATCHDOG_INTERVAL; /* The mailbox routine timed out. Reset timeouts, reset the port * and UCB online bits and attempt to restart the port. */ } else { ucb = ( SCSI_UCB * )spdt->basespdt.spdt$l_port_ucb; crb->crb$l_duetime = -1; ucb->ucb$r_scsi_ucb.ucb$l_sts &= ~UCB$M_ONLINE; spdt->basespdt.spdt$l_sts &= ~SPDT$M_STS_ONLINE; kpb = spdt->spdt$ps_kpb; if(( kpb->kpb$is_flags & KPB$M_VALID ) == 0 ) { if( ERROR( exe$kp_start( kpb, pkq_port_init, SA$K_KP_REGMSK ))) { ucb->ucb$r_scsi_ucb.ucb$l_devdepend = PKQ$K_ERR_KPSTRT; } } } return; } /* * * Name: * * Abstract: * * Inputs: * * Outputs: * * Return * Values: * */ STDT *pkq_get_stdt( PKQ_SPDT *spdt, int scsi_id ) { STDT *stdt; int hash_id; /* Convert the SCSI ID into a hash table index by using * its low order bits. */ hash_id = ( scsi_id & SPDT$K_STDT_HASH_BITMASK ) << SPDT$K_STDT_HASH_BITBASE; /* Get the address of the first STDT to which the SCSI ID * hashes. If the SCSI ID in the STDT isn't the one desired, * chain down the hash list until the correct STDT is found * or the list is exhausted. */ stdt = ( STDT * )(( STDT ** )spdt->basespdt.spdt$ps_stdt_hash_table + hash_id ); while( stdt && ( stdt->stdt$is_scsi_id_num != scsi_id )) { stdt = stdt->stdt$ps_hash_flink; } /* Return the address of the STDT corresponding to the given * SCSI ID or if not found, NULL. */ return( stdt ); } /* * * Name: pkq_set_speed * * Abstract: * * This routine looks at the nvram clock speed parameter and the psr_60mhz * bit in the psr to determine speed. If they are the same then the speed * is set as indicated (40 vs 60 mhz). IF they are different then the * speed is set based on the psr and a diagnostic message is output. * * Inputs: * spdt Pointer the the spdt for this port * * Outputs: * none * * Return * Values: * Status returned from mailbox_io routine; * */ int pkq_set_speed( PKQ_SPDT *spdt) { DECLARE_ISP(); ADP *adp = spdt->basespdt.spdt$l_adp; uint64 *handle = &spdt->spdt$q_iohandle_reg; uint16 data = 0; uint16 fast20 = 0; uint16 mask; uint16 clock_speed; /* * get the clock designator from the board- have to stop risc processor first. */ if ( ERROR ( pause_risc( spdt ) ) ) return SS$_CTRLERR; fast20=((READ_ISP(adp, handle, isp$w_risc_psr) & ISP$M_PSR_60MHZ_CLOCK)!=0); if ( ERROR ( release_risc( spdt ) ) ) return SS$_CTRLERR; if(fast20) { if (spdt->spdt$v_asynch_setup_time < 9 ) { spdt->spdt$v_asynch_setup_time = 9 ; /* ** pkq_print_message( spdt, ** " WARNING! Nvram asynchronous setup time value set too low for fast20.", STS$K_WARNING ); ** pkq_print_message( spdt, ** " Using larger value", ** STS$K_WARNING ); */ } clock_speed=60; return( pkq_mailbox_io( spdt, 2, ISP$K_MB_SET_CLOCK_RATE, clock_speed, 0, 0, 0, 0 ) ); } if( ( fast20 != spdt->spdt$v_60mhz_enable) ) { if( !fast20 ) { pkq_print_message( spdt, " WARNING! nvram speed doesn't match board clock speed.", STS$K_WARNING ); pkq_print_message( spdt, " nvram set to 60 MHZ. Board set to 40MHZ", STS$K_WARNING ); } } return( SS$_NORMAL ); } /* * * Name: * * Abstract: * * Inputs: * * Outputs: * * Return * Values: * */ int pkq_nvram_read_array( PKQ_SPDT *spdt, ISP_NVRAM *nvram ) { int i; uint16 *curbuf; uint8 *bytebuf; uint8 sum; int status; for( i = 0, curbuf = ( uint16 *)nvram; i < ( ISP$K_NVRAM_SIZE / 2 ); i++ ) { *curbuf++ = pkq_nvram_read_word( spdt, i ); } if(( nvram->isp$b_nvr_id[ 0 ] != 'I' ) || ( nvram->isp$b_nvr_id[ 1 ] != 'S' ) || ( nvram->isp$b_nvr_id[ 2 ] != 'P' ) || ( nvram->isp$b_nvr_id[ 3 ] != ' ' ) || ( nvram->isp$b_nvr_version < ISP$K_NVR_MIN_VERSION )) { return( SS$_BADPARAM ); } for( i = 0, bytebuf = ( uint8 * )nvram, sum = 0; i < ISP$K_NVRAM_SIZE; i++ ) { sum += *bytebuf++; } if( sum ) { return( SS$_BADPARAM ); } return( SS$_NORMAL ); } /* * * Name: * * Abstract: * * Inputs: * * Outputs: * * Return * Values: * */ void pkq_nvram_write_word( PKQ_SPDT *spdt, uint16 nvaddr, uint16 data ) { DECLARE_ISP(); ADP *adp = spdt->basespdt.spdt$l_adp; uint64 *handle = &spdt->spdt$q_iohandle_reg; uint16 mask; uint16 data_out_val; pkq_nvram_on( adp, handle ); pkq_nvram_command( adp, handle, ISP$K_NVRAM_WRITE_ENABLE ); pkq_nvram_toggle( adp, handle ); pkq_nvram_command( adp, handle, ( ISP$K_NVRAM_WRITE | nvaddr )); for( mask = ( 1 << 15 ); mask != 0; mask >>= 1 ) { data_out_val = ( data & mask ) ? ISP$M_NVRAM_DATA_OUT : 0; WRITE_ISP( adp, handle, isp$w_bus_nvram, ISP$M_NVRAM_SELECT | data_out_val ); pkq_nvram_delay( 2000 ); WRITE_ISP( adp, handle, isp$w_bus_nvram, ISP$M_NVRAM_SELECT | data_out_val | ISP$M_NVRAM_CLOCK ); pkq_nvram_delay( 2000 ); WRITE_ISP( adp, handle, isp$w_bus_nvram, ISP$M_NVRAM_SELECT | data_out_val ); pkq_nvram_delay( 2000 ); } pkq_nvram_toggle( adp, handle ); while( !( READ_ISP( adp, handle, isp$w_bus_nvram ) & ISP$M_NVRAM_DATA_IN )) { pkq_nvram_delay( 2000 ); } pkq_nvram_off( adp, handle ); } /* * * Name: * * Abstract: * * Inputs: * * Outputs: * * Return * Values: * */ uint16 pkq_nvram_read_word( PKQ_SPDT *spdt, uint16 nvaddr ) { DECLARE_ISP(); ADP *adp = spdt->basespdt.spdt$l_adp; uint64 *handle = &spdt->spdt$q_iohandle_reg; uint16 data = 0; uint16 mask; pkq_nvram_on( adp, handle ); pkq_nvram_command( adp, handle, ( ISP$K_NVRAM_READ | nvaddr )); for( mask = ( 1 << 15 ); mask != 0; mask >>= 1 ) { WRITE_ISP( adp, handle, isp$w_bus_nvram, ISP$M_NVRAM_SELECT | ISP$M_NVRAM_CLOCK ); pkq_nvram_delay( 2000 ); data |= ( READ_ISP( adp, handle, isp$w_bus_nvram ) & ISP$M_NVRAM_DATA_IN ) ? mask : 0; WRITE_ISP( adp, handle, isp$w_bus_nvram, ISP$M_NVRAM_SELECT ); pkq_nvram_delay( 2000 ); } pkq_nvram_off( adp, handle ); return( data ); } /* * * Name: * * Abstract: * * Inputs: * * Outputs: * * Return * Values: * */ void pkq_nvram_command( ADP *adp, uint64 *handle, uint16 cmd ) { DECLARE_ISP(); uint16 data_out_val; uint16 mask; for( mask = ( 1 << 8 ); mask != 0; mask >>= 1 ) { data_out_val = ( cmd & mask ) ? ISP$M_NVRAM_DATA_OUT : 0; WRITE_ISP( adp, handle, isp$w_bus_nvram, ISP$M_NVRAM_SELECT | data_out_val ); pkq_nvram_delay( 2000 ); WRITE_ISP( adp, handle, isp$w_bus_nvram, ISP$M_NVRAM_SELECT | data_out_val | ISP$M_NVRAM_CLOCK ); pkq_nvram_delay( 2000 ); WRITE_ISP( adp, handle, isp$w_bus_nvram, ISP$M_NVRAM_SELECT | data_out_val ); pkq_nvram_delay( 2000 ); } } /* * * Name: * * Abstract: * * Inputs: * * Outputs: * * Return * Values: * */ void pkq_nvram_on( ADP *adp, uint64 *handle ) { DECLARE_ISP(); WRITE_ISP( adp, handle, isp$w_bus_nvram, ISP$M_NVRAM_SELECT ); pkq_nvram_delay( 2000 ); WRITE_ISP( adp, handle, isp$w_bus_nvram, ISP$M_NVRAM_SELECT | ISP$M_NVRAM_CLOCK ); pkq_nvram_delay( 2000 ); } /* * * Name: * * Abstract: * * Inputs: * * Outputs: * * Return * Values: * */ void pkq_nvram_off( ADP *adp, uint64 *handle ) { DECLARE_ISP(); /* Writing zero deselects the NVRAM chip. */ WRITE_ISP( adp, handle, isp$w_bus_nvram, 0 ); pkq_nvram_delay( 2000 ); } /* * * Name: * * Abstract: * * Inputs: * * Outputs: * * Return * Values: * */ void pkq_nvram_toggle( ADP *adp, uint64 *handle ) { DECLARE_ISP(); pkq_nvram_off( adp, handle ); WRITE_ISP( adp, handle, isp$w_bus_nvram, ISP$M_NVRAM_CLOCK ); pkq_nvram_delay( 2000 ); pkq_nvram_on( adp, handle ); } /* * * Name: * * Abstract: * * Inputs: * * Outputs: * * Return * Values: * */ void pkq_nvram_delay( int32 delta ) { /* Delay 'delta' nanoseconds: * convert the 32-bit delta value to 64 bits * and pass it by reference to exe$delay() */ int64 big_delta = delta; exe$delay( &big_delta ); } /* * * Name: pkq_print_firmware_version * * Abstract: * * Inputs: * * Outputs: * * Return * Values: * */ void pkq_print_firmware_ver( PKQ_SPDT *spdt, int severity ) { char firmware_message[ 256 ]; /* Firmware version */ strcpy( firmware_message, "loading firmware version " ); pkq_convert_ver( spdt, firmware_message); strcat( firmware_message, " from " ); if( spdt->spdt$l_pkq_flags & SPDT$M_PKQ_CONSOLE_FW ) { strcat( firmware_message, "console" ); } else { strcat( firmware_message, "driver" ); } pkq_print_message( spdt, firmware_message, severity ); return; } /* * * Name: pkq_print_convert_ver * * Abstract: * * Inputs: * * Outputs: * * Return * Values: * */ void pkq_convert_ver( PKQ_SPDT *spdt, char *message ) { char version[ 8 ] = { 0 }; /* Firmware version number */ int units, tenths, hundredths; /* Digits of version */ hundredths = spdt->spdt$l_firmware_version % 10; tenths = ( spdt->spdt$l_firmware_version / 10 ) % 10; units = spdt->spdt$l_firmware_version / 100; version[ 0 ] = units + '0'; version[ 1 ] = '.'; version[ 2 ] = tenths + '0'; version[ 3 ] = hundredths + '0'; strcat( message, version ); return; } /* * * Name: pkq_print_message * * Abstract: Print message text provided by the caller. * * Inputs: spdt SPDT address * message pointer to message text * severity severity code * * Outputs: None * * Return * Values: None * */ void pkq_print_message( PKQ_SPDT *spdt, char *message, int severity ) { UCB *ucb; /* Unit Control Block pointer */ DDB *ddb; /* Device Data Block pointer */ ucb = spdt->basespdt.spdt$l_port_ucb; ddb = ucb->ucb$l_ddb; /* Ignore informational messages unless the SYSGEN UserD1 * parameter is set. */ if((( sgn$gl_userd1 & 1 ) == 0 ) && ( severity == STS$K_INFO )) { return; } /* Print out a message prefix using the severity code * and device name from the DDB. */ exe_std$outzstring( "\a\r\n%PKQDRIVER-" ); switch( severity ) { case STS$K_INFO: default: exe_std$outzstring( "I- " ); break; case STS$K_WARNING: exe_std$outzstring( "W- " ); break; case STS$K_ERROR: exe_std$outzstring( "E- " ); break; case STS$K_SEVERE: exe_std$outzstring( "F- " ); break; } exe_std$outcstring( ddb->ddb$t_name ); exe_std$outzstring( "0, " ); /* Print the user-provided portion of the message followed * by CR-LF. */ exe_std$outzstring( message ); exe_std$outcrlf(); return; } /* * * Name: pkq_init_rspid_table * * Abstract: Initialize the driver-wide RSPID table and * chain its entries together into an free list. * * Inputs: None * * Implicit * Inputs: rspid_listhead free list head * rspid_table the RSPID table itself * * Outputs: p_rspid_table pointer to the rspid table. * * Implicit * Outputs: rspid_listhead updated free list head * rspid_table initialized RSPID table * * Return * Values: None * */ void pkq_init_rspid_table( PKQ_SPDT *spdt, RTE *p_rspid_table ) { RTE *rtp; int i; rtp = spdt->spdt$ps_rspid_table; for( i = PKQ_NUM_RSPID - 1; i >= 0; --i ) { rtp[ i ].flink = spdt->spdt$ps_rspid_listhead; spdt->spdt$ps_rspid_listhead = &rtp[ i ] ; rtp[ i ].rspid.index = i; rtp[ i ].rspid.seq_no = 1; rtp[ i ].p1 = NULL; rtp[ i ].spare = NULL; } } /* * * Name: pkq_alloc_rspid * * Abstract: Allocate a RSPID and correlate it with an SCDRP * * Inputs: scdrp SCSI class driver request packet * * Outputs: None * * Return * Values: rtp->rspid Response ID * */ RSPID pkq_alloc_rspid( PKQ_SPDT *spdt, SCDRP *scdrp ) { RTE *rtp; RSPID null_rspid = { 0,0 }; /* If no RSPID is available, fork and wait until one frees up. * If the fork and wait operation fails, return a null RSPID * to the caller. */ while(( rtp = spdt->spdt$ps_rspid_listhead ) == NULL ) { if( ERROR( exe$kp_fork_wait( scdrp->scdrp$ps_kpb, ( FKB * )scdrp ))) return( null_rspid ); spdt->spdt$is_pkq_rspid_alloc_failures++; } /* Allocation succeeded. Update the RSPID free list, store the * SCDRP address in the RSPID table, and return the RSPID to the * caller. */ spdt->spdt$ps_rspid_listhead = rtp->flink; rtp->flink = NULL; rtp->p1 = ( void * )scdrp; return( rtp->rspid ); } /* * * Name: pkq_dealloc_rspid * * Abstract: Deallocate a RSPID and update its sequence number. * * Inputs: rspid Response Id * * Outputs: None * * Return * Values: None * */ void pkq_dealloc_rspid( PKQ_SPDT *spdt,RSPID rspid ) { RTE *rtp; RTE *rtb; /* Point at the RSPID table entry specified by the RSPID. If * the index and sequence numbers match, update the sequence * number and return the RSPID table entry to the free list. */ rtb = spdt->spdt$ps_rspid_table; rtp = &rtb[ rspid.index ]; if(( rtp->rspid.index == rspid.index ) && ( rtp->rspid.seq_no == rspid.seq_no )) { if( ++rtp->rspid.seq_no == 0 ) { ++rtp->rspid.seq_no; } rtp->flink = spdt->spdt$ps_rspid_listhead; spdt->spdt$ps_rspid_listhead = ( RTE * )&rtp->flink; rtp->p1 = NULL; } } /* * * Name: pkq_rspid_to_scdrp * * Abstract: Return the SCDRP pointer corresponding to the * given RSPID, if any. * * Inputs: rspid Response ID * * Outputs: None * * Return * Values: scdrp Corresponding SCDRP or NULL * */ SCDRP *pkq_rspid_to_scdrp( PKQ_SPDT *spdt, RSPID rspid ) { RTE *rtp; RTE *rtb; /* Point at the RSPID table entry specified by the RSPID. If * the index and sequence numbers match, return the SCDRP address * in the RTE. Otherwise, return a NULL pointer. */ rtb = spdt->spdt$ps_rspid_table; rtp = &rtb[ rspid.index ]; if(( rtp->rspid.index == rspid.index ) && ( rtp->rspid.seq_no == rspid.seq_no )) { return(( SCDRP * )rtp->p1 ); } else { return(( SCDRP * )NULL ); } } /* * * Name: stall_qman * * Abstract: * * This routine stalls the queue manager at least until 25% of current IO * completes. It is meant to be used as a flow control mechanism to * be used when the request queue is full. This routine meerly sets a * count to indicate that the queue manager is stalled and does a KP_STALL. * Any interrupt that sees the non zero count will decrement the count and * wake up the queue manager when the count becomes zero. * * Note: There is a more efficient way to allocate resources that * involves calling a common routine. This routine will stall * the entire io system while waiting for resources. Unfortunately * this mechanism cannot be used directly since it only allows * allocation of one resource at a time. This should be modified * at the next opportunity. * Inputs: * spdt Address of the SPDT * * Outputs: * none * * Return * Values: * none * */ void stall_qman(PKQ_SPDT *spdt) { KPB *kpb; spdt->spdt$l_qman_stalled = REQUEST_QUEUE_SIZE >> 2; kpb = spdt->basespdt.spdt$ps_qman_kpb; kpb->kpb$ps_sch_stall_rtn = ioc$return; kpb->kpb$ps_sch_restrt_rtn = 0; #if PKQ_DEBUG trace_event(spdt,'stal',(uint32)kpb ,(uint32)spdt, exe$gl_abstim ); #endif exe$kp_stall_general( kpb ); #if PKQ_DEBUG trace_event(spdt,'resm',(uint32)kpb ,(uint32)spdt, exe$gl_abstim ); #endif } /* * * Name: * resume_qman * * Abstract: * * This routine conditionally resumes the queue manager KPB if it is * waiting. This is indicated by the qkpb_stalled in the spdt. This routine * must be called holding the fork lock. * * Inputs: * spdt Address of the SPDT for this port. * uncond_flag TRUE means restart qman unconditioanlly. * FALSE means decrement count and restart qman if <= 0 * Outputs: * none * * Implicit Outputs: * spdt->spdt$l_restart_qman is decremented and set to zero if the * queue manager is restarted. * queue manager is conditioanlly restarted. * * Return * Values: * none * */ void resume_qman(PKQ_SPDT *spdt, uint32 uncond_flag) { KPB *kpb; if( spdt->spdt$l_qman_stalled ) { if( ( (--(spdt->spdt$l_qman_stalled) <= 0) ) || uncond_flag ) { kpb = spdt->basespdt.spdt$ps_qman_kpb; #if PKQ_DEBUG trace_event(spdt,'rest',' kp ' ,(uint32)kpb, exe$gl_abstim ); #endif spdt->spdt$l_qman_stalled = 0; exe$kp_restart( kpb, SS$_NORMAL); } } } /* ** ** Name: ** dump_ram ** ** Abstract: ** ** This routine loads the firmware (normally left there by the console) ** into a buffer supplied as an input. It does this 16 bits at a time ** since we've had problems with the mailbox 'dump ram' command. ** ** Inputs: ** spdt Address of the SPDT for this port. ** buffer pointer to the destination buffer ** risc_addr starting risc address (ie internal chip address) ** size The number of 16 bit values to read ** ** Outputs: ** buffer Buffer is filled in with data ** ** Implicit Outputs: ** ** Return ** Values: status of last mailbox command ** */ int dump_ram( PKQ_SPDT *spdt, uint16 *buffer, uint32 risc_addr, uint32 size) { int status ; int i; while(size) { status = pkq_mailbox_io( spdt, 2, ISP$K_MB_READ_RAM_WORD, risc_addr, 0,0,0,0); if(SUCCESS(status)) { *buffer++ = spdt->spdt$w_mailbox_2; risc_addr++; size--; } else { break; } } return (status); } /* ** ** Name: ** backoff_delay ** ** Abstract: ** ** This routine is used to generate a backoff delay loop. It will delay ** up to a 'max' value that is passed is an input argument. The actual ** delay argument is doubled until the max value is reached. ** ** Inputs: ** delay The address of an int32 # of nanoseconds to delay ** max The maximum value that delay may be set to. ** ** ** Outputs: ** ** Implicit Outputs: ** ** Return ** Values: delay*2 or max whichever is smaller. ** */ uint32 backoff_delay( uint32 delay, uint32 maximum) { pkq_nvram_delay( delay ); return (min( (delay*2) , maximum) ); } /* ** ** Name: init_target_reply ** ** Abstract: ** ** This routine does the unit init time initialization of the responses to ** target mode request sense and inquiry messages and the data structures ** used to handle the requests. ** ** Inputs: ** spdt address of the spdt for this device; ** ** Outputs: ** None ** ** Implicit Outputs: ** All of the fields in the two structures are inited. ** The ATIO buffers are initialized. ** ** Return ** SS$_NORMAL ** */ void init_target_reply(PKQ_SPDT *spdt) { int i; char *scan; INQ_RESP *inqr; REQ_SEN_RESP *reqsenser; inqr = &(spdt->spdt$s_inquiry_response); reqsenser = &(spdt->spdt$s_request_sense_response); inqr->scsi$inq$v_device_type = SCSI$C_CPU; inqr->scsi$inq$v_ansi_version = SCSI$C_ANSI_SCSI_2; inqr->scsi$inq$v_resp_data_format= SCSI$C_SCSI_2; inqr->scsi$inq$b_add_length= sizeof(INQ_RESP)-5; strcpy (inqr->scsi$inq$b_vendor_id, "DEC "); scan = inqr->scsi$inq$b_product_id; strcpy (scan, "OpenVMS "); scan = scan+strlen(scan); *scan++ = (char)(spdt->basespdt.spdt$l_scsi_port_id+'A'); *scan++ = (char)clu$gl_allocls; *scan++ = (char)clu$gl_tape_allocls; strncpy (scan, " ", strlen(" ")); strncpy (inqr->scsi$inq$b_product_revision, &sys$gq_version, 4); /* * If ioc$gl_nameing is set then we may need to overwrite the above data * to support new device naming. If the ddb ddb$v_pac is set then the * ddb contains a port allocation class. Use that instead of the port id, * allocation class etc. */ if( ioc$gl_naming & 1 ) { UCB *ucb; DDB *ddb; uint32 *buff; ucb = spdt->basespdt.spdt$l_port_ucb; ddb = ucb->ucb$l_ddb; if( ddb->ddb$v_pac ) { buff = (uint32 *)( inqr->scsi$inq$b_product_id +8 ); *buff++ = scs$gb_systemid; *buff = ddb->ddb$l_allocls; } } reqsenser->scsi$sns$v_error_code = SCSI$SC1$C_CURRENT; reqsenser->scsi$sns$v_sense_key = SCSI$C_ILLEGAL_REQUEST; reqsenser->scsi$sns$b_add_sense_len = sizeof(REQ_SEN_RESP)-8; /* 8 is length of standard part om message */ reqsenser->scsi$sns$b_add_sense_code = 0x25; /* Lun not supported */ for( i = 0; i<4; i++) { clear_entry( &spdt->spdt$s_atio_entries[i] ); } } /* ** ** Name: Alloc_atio_buffer ** ** Abstract: ** ** This routine allocates one of 4 buffers available for accept target ** IO processing. The ATIO is kept in the buffer until the IO is completed. ** ** Inputs: ** spdt address of the SPDT ** entry isp_entry to copy into the buffer. ** ** Outputs: ** buffer virtual address of the copied atio call ** ** Implicit Outputs: ** atio in *entry is copied into allocated block. ** ** Return ** SS$_NORMAL if the buffer is allocated and copied. ** 0 otherwise. */ int alloc_atio_buffer(PKQ_SPDT *spdt, ISP_ENTRY *entry, ISP_ENTRY **buffer) { int status = 0; int i; ISP_ENTRY *ent; /* ** First make sure that there isn't one already there. */ for(i = 0; ispdt$s_atio_entries[i]; if ( ( ent->isp$b_type == ISP$K_TY_ATIO ) && ( ent->isp$b_initiator_id == entry->isp$b_initiator_id) && ( ent->isp$b_tag_value == entry->isp$b_tag_value) ) return status; } /* ** Now allocate one of the blocks. */ for(i = 0; ispdt$s_atio_entries[i].isp$b_type == 0 ) { *buffer = &(spdt->spdt$s_atio_entries[i]); status = SS$_NORMAL; spdt->spdt$s_atio_entries[i] = *entry; break; } } return (status); } /* ** ** Name: dealloc_atio_buffer ** ** Abstract: ** ** This routine deallocates an atio buffer by clearing its type ** ** Inputs: ** entry address of the atio buffer ** Outputs: ** entry->isp$b_type is cleared ** Implicit Outputs: ** none ** Return ** SS$_NORMAL */ void dealloc_atio_buffer( ISP_ENTRY *entry) { entry->isp$b_type = 0; } /* ** ** Name: build_ctio ** ** Abstract: ** ** This routine builds a ctio entry dirrectly into the request queue from ** the information in an atio entry. ** ** Inputs: ** spdt pointer to the spdt ** isp_entry address of the atio entry ** ctio_entry address of buffer into which the ctio is to be built. ** ** Outputs: ** ** none ** ** Implicit Outputs: ** ** contents of buffer *ctio are modified ** ** Return ** none */ void build_ctio(PKQ_SPDT *spdt, ISP_ENTRY *isp_entry, ISP_ENTRY *ctio_entry) { int xfer_len; /* length of transfer */ uint32 buffer_pa; /* physical address or reply */ uint32 bytes_in_page; /* bytes of reply in first physical page*/ /* ** first make a copy of the atio. That helps since some fields are the same. */ *ctio_entry = *isp_entry; /* ** fill in the header section. */ ctio_entry->isp$l_handle = 0; ctio_entry->isp$b_cdb_len = 0; ctio_entry->isp$b_type = ISP$K_TY_CTIO; ctio_entry->isp$b_count = 1; ctio_entry->isp$v_data_direction = ISP$K_DD_READ; ctio_entry->isp$b_ctio_status = 0; ctio_entry->isp$w_ctio_timeout = 5; ctio_entry->isp$l_residual_xfer_len = 0; ctio_entry->isp$v_send_scsi_status = 1; ctio_entry->isp$b_scsi_status = 0; if( isp_entry->isp$b_cdb[0] == SCSI$K_REQUEST_SENSE ) { if( isp_entry->isp$b_cdb[ 4 ] < sizeof (REQ_SEN_RESP) ) { ctio_entry->isp$w_ctio_data_seg_count = 0; ctio_entry->isp$l_flags = 0; ctio_entry->isp$v_send_scsi_status = FALSE; } else { /* send request sense reply */ ctio_entry->isp$l_xfer_length = sizeof (REQ_SEN_RESP); /* ** This assumes that spdt$q_direct_dma_base has all 0's in the ms longword ** (this is enforced with a bugcheck in the unit init code) */ buffer_pa = ioc$sva_to_pa( &spdt->spdt$s_request_sense_response ,0,0,0)+ (uint32)spdt->spdt$q_direct_dma_base; bytes_in_page = (buffer_pa&mmg$gl_bwp_mask)+mmg$gl_page_size-buffer_pa; /* ** Assume the reply is all in one page for now. */ ctio_entry->isp$w_ctio_data_seg_count = 1; ctio_entry->isp$r_ctio_data_seg[0].isp$ps_base_address = (void *)buffer_pa; if (sizeof (REQ_SEN_RESP) <= bytes_in_page) /* does reply cross a */ /* page boundry ? */ { ctio_entry->isp$r_ctio_data_seg[0].isp$l_byte_count = sizeof (REQ_SEN_RESP); } else { ctio_entry->isp$r_ctio_data_seg[0].isp$l_byte_count = bytes_in_page; ctio_entry->isp$r_ctio_data_seg[1].isp$l_byte_count = sizeof (REQ_SEN_RESP)-bytes_in_page; ctio_entry->isp$r_ctio_data_seg[1].isp$ps_base_address = (void*)(ioc$sva_to_pa( ( char *)&spdt->spdt$s_request_sense_response + mmg$gl_page_size, 0, 0, 0) + (uint32)spdt->spdt$q_direct_dma_base); ctio_entry->isp$w_ctio_data_seg_count = 2; } } } else if ( isp_entry->isp$b_cdb[0] == SCSI$K_INQUIRY ) { if( isp_entry->isp$b_cdb[ 4 ] < sizeof (INQ_RESP) ) { if(isp_entry->isp$b_cdb[ 4 ] >0 ) { ctio_entry->isp$l_xfer_length = 1; spdt->spdt$b_short_inquiry_response = 0x7f; /* send lun not supported */ buffer_pa = ioc$sva_to_pa( &spdt->spdt$b_short_inquiry_response , 0, 0, 0) + (uint32)spdt->spdt$q_direct_dma_base; ctio_entry->isp$w_ctio_data_seg_count = 1; ctio_entry->isp$r_ctio_data_seg[0].isp$ps_base_address = (void *)buffer_pa; ctio_entry->isp$r_ctio_data_seg[0].isp$l_byte_count = 1; } else { ctio_entry->isp$w_ctio_data_seg_count = 0; ctio_entry->isp$l_flags = 0; ctio_entry->isp$v_send_scsi_status = FALSE; } } else { /* send inquiry reply */ ctio_entry->isp$l_xfer_length = sizeof (INQ_RESP); buffer_pa = ioc$sva_to_pa( &spdt->spdt$s_inquiry_response , 0, 0, 0) + (uint32)spdt->spdt$q_direct_dma_base; bytes_in_page = (buffer_pa&mmg$gl_bwp_mask)+mmg$gl_page_size-buffer_pa; ctio_entry->isp$w_ctio_data_seg_count = 1; ctio_entry->isp$r_ctio_data_seg[0].isp$ps_base_address = (void *)buffer_pa; if (sizeof ( INQ_RESP) <= bytes_in_page) { ctio_entry->isp$r_ctio_data_seg[0].isp$l_byte_count = sizeof (INQ_RESP); } else { ctio_entry->isp$r_ctio_data_seg[0].isp$l_byte_count = bytes_in_page; ctio_entry->isp$r_ctio_data_seg[1].isp$l_byte_count = sizeof (INQ_RESP)-bytes_in_page; ctio_entry->isp$r_ctio_data_seg[1].isp$ps_base_address = (void *)(ioc$sva_to_pa( ( char *)&spdt->spdt$s_inquiry_response+ mmg$gl_page_size, 0, 0, 0) + (uint32)spdt->spdt$q_direct_dma_base); ctio_entry->isp$w_ctio_data_seg_count = 2; } } } else { /* here return check condition */ ctio_entry->isp$w_ctio_data_seg_count = 0; ctio_entry->isp$b_scsi_status = SCSI$C_CHECK_CONDITION; ctio_entry->isp$l_flags = 0; ctio_entry->isp$v_send_scsi_status = TRUE; } } /* ** find_atio search for an atio in the spdt atio buffers given a ctio entry. ** ** Abstract: ** ** This routine takes as input a ctio entry received in the response queue ** and finds the atio that is associated with that ctio ( or 0 if not ** found ). The initiator id and tag must match in the two messages. ** ** input: ** spdt address of spdt ** ctio_entry address of the ctio entry for the requested atio ** Outputs: ** none ** ** return: ** ATIO_ENTRY address (or 0 if not found) ** ** ** */ ISP_ENTRY *find_atio(PKQ_SPDT *spdt, ISP_ENTRY *ctio_entry) { int i; ISP_ENTRY *atio_entry = 0, *entry; for (i = 0;i < MAX_ATIO_ENTRIES;i++) { entry = &spdt->spdt$s_atio_entries[i]; if ( ( entry->isp$b_type == ISP$K_TY_ATIO ) && ( entry->isp$b_initiator_id == ctio_entry->isp$b_initiator_id ) && ( entry->isp$b_tag_value == ctio_entry->isp$b_tag_value) ) { atio_entry=entry; break; } } return atio_entry; } /* ** ** Name: set_reqq_entry ** ** Abstract: ** This routine updates the request queue after an entry has been placed ** into it. it updates spdt$l_requestq_in and writes the new value to the ** device register to notify the isp_fw that there is a new entry in the queue. ** The mailbox (hardware) register is updated with the new value in effect ** sending the entry to the chip firmware. ** ** Inputs: ** spdt address of the spdt ** requestq_in current value of spdt$l_requestq_in ** ** Outputs: ** none ** ** Implicit Outputs: ** spdt$l_requestq_in is incremented and, if necessary wraps. ** that value is also written to the mailbox 4 register. ** ** Return ** none ** */ void set_reqq_entry(PKQ_SPDT *spdt, int requestq_in) { DECLARE_ISP(); requestq_in++; if( requestq_in >= REQUEST_QUEUE_SIZE ) requestq_in = 0; spdt->spdt$l_requestq_in = requestq_in; WRITE_ISP( spdt->basespdt.spdt$l_adp, &spdt->spdt$q_iohandle_reg, isp$w_mailbox_4, requestq_in ); } /* ** ** Name: ack_notify ** ** Abstract: ** ** This routine sends a notify_ack message to the isp_fw in response to ** an immediate notify. ** ** Inputs: ** spdt address of SPDT ** isp_entry address of the isp_entry for the immediate notify message ** ** Outputs: ** none ** ** Implicit Outputs: ** A Notify acknowledge message is put into the request queue. ** ** Return ** none ** */ void ack_notify(PKQ_SPDT *spdt, ISP_ENTRY *isp_entry) { int requestq_in; ISP_ENTRY *ack_entry; /* ** get a request queue entry. */ requestq_in = spdt->spdt$l_requestq_in; ack_entry = spdt->spdt$ps_requestq_va+requestq_in; /* ** copy the immediate notify message and turn it into an acknowledge */ *ack_entry = *isp_entry; ack_entry->isp$b_type = ISP$K_TY_NOTIFY_ACK; ack_entry->isp$b_event = 0; #if PKQ_DEBUG trace_entry(spdt,ack_entry); #endif set_reqq_entry(spdt, requestq_in); } /* ** reset_ack_notify send an Notify Acknowledge message to clear an interrupt ** ** Abstract: ** ** This routine sends an ack notify message to tell the pkqdriver target ** mode firmware that a reset has been cleared. This performs the same ** funtion as the marker entry for initiator mode. ** ** input: ** spdt address of the spdt ** ** Outputs: ** none ** ** return: ** none ** ** Implicit Outputs: ** A notify acknowlege message is put in the requestq woth the ** 'reset cleared' bit set. ** */ void reset_ack_notify( PKQ_SPDT *spdt) { ISP_ENTRY *ack; int requestq_in; requestq_in = spdt->spdt$l_requestq_in; ack = spdt->spdt$ps_requestq_va + requestq_in; clear_entry(ack); ack->isp$b_type = ISP$K_TY_NOTIFY_ACK ; ack->isp$b_count = 1 ; ack->isp$b_target_id = (char )spdt->basespdt.spdt$is_scsi_id_num ; ack->isp$v_reset_cleared = 1 ; set_reqq_entry(spdt, requestq_in); #if PKQ_DEBUG trace_entry(spdt,ack); #endif } /* ** clear_entry clear an isp_entry structure. ** ** input: ** entry address of isp entry to clear ** ** Outputs: ** none ** ** return: ** none ** Implicit Outputs: ** The buffer at entry address is cleared (8 quadwords) ** ** Note: there should be an assume in here that the length of an entry is 64. ** */ void clear_entry(ISP_ENTRY *entry) { uint64 *scan; #if PKQ_DEBUG if( sizeof(ISP_ENTRY) != 64) pkq_bugcheck(); #endif scan = (uint64 *)entry; *scan = 0; *(scan+1) = 0; *(scan+2) = 0; *(scan+3) = 0; *(scan+4) = 0; *(scan+5) = 0; *(scan+6) = 0; *(scan+7) = 0; } /* ** ** Name: return_atio ** ** Abstract: ** This routine returns and deallocates an atio that is being held in the ** spdt while a target message is happening. ** ** Inputs: ** spdt address of the spdt ** isp_entry address of the atio being held ** ** Outputs: ** None: ** ** Implicit Outputs: ** isp_entry is copied into the request queue ** spdt$l_requestq_in is updated ** Return ** None ** ** */ void return_atio(PKQ_SPDT *spdt,ISP_ENTRY *isp_entry) { int requestq_in; ISP_ENTRY *atio_entry, *requestq_entry; requestq_in = spdt->spdt$l_requestq_in; requestq_entry = spdt->spdt$ps_requestq_va + requestq_in; atio_entry = find_atio(spdt, isp_entry); if(atio_entry != NULL) { *requestq_entry = *atio_entry; dealloc_atio_buffer( atio_entry ); set_reqq_entry(spdt, requestq_in); } #if PKQ_DEBUG trace_entry(spdt,requestq_entry); #endif } /* ** ** Name: send_enable_lun ** ** Abstract: ** This routine puts an enable lun command in the request queue to enable ** target mode. ** ** Inputs: ** spdt address of the spdt ** ** Outputs: ** none ** ** Implicit Outputs: ** request queue is updated to send the enable lun command. ** ** ** Return ** none ** */ void send_enable_lun(PKQ_SPDT *spdt) { ISP_ENTRY *lun_entry; int i; lun_entry = spdt->spdt$ps_requestq_va + spdt->spdt$l_requestq_in; clear_entry(lun_entry); lun_entry->isp$b_type = ISP$K_TY_ENABLE_LUN; lun_entry->isp$b_count = 1; lun_entry->isp$b_lun = 0; lun_entry->isp$l_flags = 0; /* clear ISP$M_TAGGED_QUEUE_ENABLE */ lun_entry->isp$b_command_count = MAX_ATIO_ENTRIES; lun_entry->isp$b_notify_count = MAX_NOTIFY_ENTRIES; lun_entry->isp$b_group_6_count = 6; lun_entry->isp$b_group_7_count = 6; lun_entry->isp$w_timeout = 6; #if PKQ_DEBUG trace_entry(spdt,lun_entry); #endif set_reqq_entry(spdt, spdt->spdt$l_requestq_in ); spdt->spdt$l_pkq_flags &= ~SPDT$M_PKQ_SEND_ENABLE_LUN; wait_enable_lun(spdt); } void wait_enable_lun( PKQ_SPDT *spdt) { int64 delay = 10000; int count = 10000; while(((spdt->spdt$l_pkq_flags & SPDT$M_PKQ_GOT_ENABLE_LUN)==0) && (--count)>0 ) { exe$kp_tqe_wait( spdt->basespdt.spdt$ps_qman_kpb, &delay, spdt->basespdt.spdt$is_flck ); } } /* ** ** Name: pause_risc ** ** Abstract: ** This routine pauses risc processor and wait for the indication that it ** is paused. ** ** Inputs: ** spdt address of the spdt ** ** Outputs: ** none ** ** Implicit Outputs: ** The risc firmware will not be running upon return from this routine ** ** ** Return ** none ** */ uint32 pause_risc( PKQ_SPDT *spdt) { DECLARE_ISP(); ADP *adp = spdt->basespdt.spdt$l_adp; uint32 interval; uint64 *handle = &spdt->spdt$q_iohandle_reg; /* Write a pause request into the HCCR and then wait * a while for it to happen. If we manage to exhaust * the interval count fall through anyway to take whatever * register values we can get. */ WRITE_ISP( adp, handle, isp$w_hccr, ISP$K_HCCR_PAUSE ); interval = 100000; while( --interval && !( READ_ISP( adp, handle, isp$w_hccr ) & ISP$M_HCCR_PAUSE_MODE )) {} return(interval != 0); } /* ** ** Name: release_risc ** ** Abstract: ** This routine releases risc processor that presumably is paused. ** ** Inputs: ** spdt address of the spdt ** ** Outputs: ** none ** ** Implicit Outputs: ** The risc firmware will be running upon return from this routine ** ** ** Return ** none ** */ uint32 release_risc( PKQ_SPDT *spdt) { DECLARE_ISP(); ADP *adp = spdt->basespdt.spdt$l_adp; uint64 *handle = &spdt->spdt$q_iohandle_reg; uint32 interval; /* ** Reenable the risc processor */ WRITE_ISP( adp, handle, isp$w_hccr, ISP$K_HCCR_RELEASE ); interval = 100000; while( --interval && ( READ_ISP( adp, handle, isp$w_hccr ) & ISP$M_HCCR_PAUSE_MODE )) {} return(interval != 0); } /* ** ** Name: target_mode ** ** Abstract: ** This routine returnes a true value iff the loaded firmware supports ** target mode. Currently this routine only compares the ** spdt$l_firmware_version field to 300. If a more complex algoritm is ** required ( after 4.x or whatever ) or if the format of ** spdt$l_firmware_version changes this routine will need to change. ** ** qLogic may decide to use odd major revision numbers for target mode and ** even major revisions for non-target mode code. In that case this routine ** will look something like: ** ** return ( (spdt->spdt$l_firmware_version/100) & 1 ); ** ** Inputs: ** spdt address of the spdt ** ** Outputs: ** none ** ** Implicit Outputs: ** The risc firmware will be running upon return from this routine ** ** ** Return ** FALSE = firmware doesn't support target mode. ** TRUE = firmware does support target mode. ** */ unsigned int target_mode( PKQ_SPDT *spdt ) { return ( spdt->spdt$l_firmware_version >= 300 ); } /* ** ** Debug only code starts here. ** */ #if PKQ_DEBUG /* ** Allocate a trace buffer */ int pkq_alloc_trace( PKQ_SPDT *spdt ) { int status; int32 retlen; char *buffer; status = exe_std$alononpaged( TRACE_SIZE , &retlen, ( void ** )&buffer ); spdt->spdt$ps_next_log = spdt->spdt$ps_log_buffer = (struct pkq_trace_entry *)(buffer+sizeof(struct pkq_trace_entry)); spdt->spdt$l_log_size = TRACE_ENTRIES-1; return status; } /* ** trace an event. */ void pkq_trace_event(PKQ_SPDT *spdt, uint32 type, uint32 p1, uint32 p2, uint32 p3) { struct pkq_trace_entry *next; int saved_ipl; device_lock( spdt->basespdt.spdt$l_dlck, RAISE_IPL, &saved_ipl ); next = spdt->spdt$ps_next_log; next->trace_type = type; next->trace_p1 = p1; next->trace_p2 = p2; next->trace_p3 = p3; next = next+1; if ( ( next - spdt->spdt$ps_log_buffer ) >= (spdt->spdt$l_log_size) ) { next = spdt->spdt$ps_log_buffer; } spdt->spdt$ps_next_log = next; next->trace_type = '****'; next->trace_p1 = '****'; device_unlock( spdt->basespdt.spdt$l_dlck, saved_ipl, SMP_RESTORE ); } /* ** print64 Print a 64 bit value with a description. ** ** input: ** str pointer to an asciz descriptor string ** value a 64bit value ** Outputs: ** none ** ** return: ** none ** ** ** This routine writes out a string to the console of the form: ** ** descriptive string = 01234567 89ABCDEF(cr) ** ** The call to output that line would be... ** ** print64("descriptive string", 0x123456789abcdef); ** ** This routine is meant for debugging purposes only! ** */ void print64( char *str, int64 value) { char ostr[32],*scan; int i; scan = &ostr[16+2]; *scan-- = 0; exe_std$outzstring( str ); exe_std$outzstring( " =" ); for ( i = 0;i<16;i++) { *scan = value&0xf; if ((*scan) >9) { *scan += 'A'-10; } else { *scan += '0'; } scan--; if ( ( i == 7) || (i == 15)) { *scan-- = ' '; } value = value>>4; } exe_std$outzstring( ostr ); exe_std$outcrlf(); } /* ** trace_entry add a request-response queue entry to the trace buffer. ** ** input: ** spdt pointer to spdt ** entry pointer to isp entry in request queue or response queue ** Outputs: ** none ** ** Return: ** none ** ** Implicit outputs: ** ** Trace buffer is updated including spdt$ps_next_log ** ** Description: ** ** This routine adds 5 lines (0x50 bytes) to the trace buffer. ** The first line contains either 'sent' or 'got' for entries in the ** request and response queue respectivly, the in and out pointers for the ** queues the entry address and exe$gl_abstime. The next 4 lines are a copy ** of the actual entry from the buffer. ** ** Note that trace_event synchronizes access to the trace buffer at dipl so ** it is possible to be interrupted between lines of trace. ** ** */ void pkq_trace_entry(PKQ_SPDT *spdt,ISP_ENTRY *entry) { int i; uint32 *buff; buff = (uint32 *)entry; if(entry >= spdt->spdt$ps_responseq_va) { trace_event(spdt, 'got ', ((char)spdt->spdt$l_responseq_out<<24) + ((char)spdt->spdt$l_responseq_in<<16) + ((char)spdt->spdt$l_requestq_in<<8) + ((char)spdt->spdt$l_requestq_out), (uint32)entry, exe$gl_abstim); } else { trace_event(spdt, 'sent', ((char)spdt->spdt$l_responseq_out<<24) + ((char)spdt->spdt$l_responseq_in<<16) + ((char)spdt->spdt$l_requestq_in<<8) + ((char)spdt->spdt$l_requestq_out), (uint32)entry, exe$gl_abstim); } for( i = 0; i<16; i+=4) { trace_event(spdt, *(buff+(i+0)), *(buff+(i+1)), *(buff+(i+2)), *(buff+(i+3))); } } #define out_field( a ,b) print_field((int)nvram->a, b ,&line_count) void pkq_dump_nvram( PKQ_SPDT *spdt, ISP_NVRAM *nvram) { char buffer[80]; int line_count = 0; int i; print_field( ( spdt->basespdt.spdt$l_scsi_port_id + 0xa) , " port_id", &line_count); exe_std$outcrlf(); exe_std$outzstring( " NvRAM Parameters " ); exe_std$outzstring( " Header = " ); strncpy (buffer, (char *)&nvram->isp$b_nvr_id , 4); buffer[4] = 0; exe_std$outzstring( buffer ); exe_std$outcrlf(); line_count = 0; out_field( isp$v_nvr_fifo_threshhold, " fifo_threshhold" ); out_field( isp$v_nvr_adaptor_enable , " adaptor_enable "); out_field( isp$v_nvr_initiator_scsi_id , " initiator_scsi_id "); out_field( isp$b_nvr_bus_reset_delay , " bus_reset_delay "); out_field( isp$b_nvr_retry_count , " retry_count "); out_field( isp$b_nvr_retry_delay , " retry_delay "); out_field( isp$v_nvr_asynch_setup_time , " asynch_setup_time "); out_field( isp$v_nvr_req_ack_active_negation , " req_ack_active_negation "); out_field( isp$v_nvr_data_active_negation , " data_active_negation "); out_field( isp$v_nvr_data_dma_burst_enable , " data_dma_burst_enable "); out_field( isp$v_nvr_command_dma_burst_enable , " command_dma_burst_enable "); out_field( isp$b_nvr_tag_age_limit , " tag_age_limit "); out_field( isp$v_nvr_termination_low_enable , " termination_low_enable "); out_field( isp$v_nvr_termination_high_enable , " termination_high_enable "); out_field( isp$v_nvr_pcmc_burst_enable , " pcmc_burst_enable "); out_field( isp$v_nvr_60mhz_enable , " 60mhz_enable "); out_field( isp$w_nvr_max_queue_depth , " max_queue_depth "); exe_std$outcrlf(); exe_std$outcrlf(); line_count = 0; for( i = 0; i<16; i++) { print_field(i," id #",&line_count); exe_std$outcrlf(); line_count = 0; out_field( isp$r_nvr_target[i].isp$b_nvr_capabilites , " capabilites "); out_field( isp$r_nvr_target[i].isp$v_nvr_renegotiate_on_error , " renegotiate_on_error "); out_field( isp$r_nvr_target[i].isp$v_nvr_stop_queue_on_check , " stop_queue_on_check "); out_field( isp$r_nvr_target[i].isp$v_nvr_auto_request_sense , " auto_request_sense "); out_field( isp$r_nvr_target[i].isp$v_nvr_tagged_queuing , " tagged_queuing "); out_field( isp$r_nvr_target[i].isp$v_nvr_synch_data_transfers , " synch_data_transfers "); out_field( isp$r_nvr_target[i].isp$v_nvr_wide_data_transfers , " wide_data_transfers "); out_field( isp$r_nvr_target[i].isp$v_nvr_parity_checking , " parity_checking "); out_field( isp$r_nvr_target[i].isp$v_nvr_disconnect_allowed , " disconnect_allowed "); out_field( isp$r_nvr_target[i].isp$b_nvr_execution_throttle , " execution_throttle "); out_field( isp$r_nvr_target[i].isp$b_nvr_synch_period , " synch_period "); out_field( isp$r_nvr_target[i].isp$v_nvr_synch_offset , " reqack_offset "); exe_std$outcrlf(); exe_std$outcrlf(); line_count = 0; } } void print_field(int value, char *str, int *count) { char *equals = " = "; int count_needed; if(value > 255) count_needed = 4; else count_needed = 2; count_needed += strlen( str ) + strlen( equals )+1; if( (count_needed + *count) >80) { *count = count_needed; exe_std$outcrlf(); } else { *count += count_needed; } exe_std$outzstring( str ); exe_std$outzstring( equals ); out_value(value); exe_std$outzstring( " " ); } void out_value(int value) { char buff[8],*scan; scan = buff; if(value >255 ) { *scan++= hex_digit(value>>12); *scan++= hex_digit(value>>8); } *scan++ = hex_digit(value>>4); *scan++ = hex_digit(value>>0); *scan++ = 0; exe_std$outzstring( buff ); } char hex_digit(int value) { value = value&0x0f; if (value >9) return ( value +'A'-10); else return ( value +'0'); } #endif