1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-18 10:35:55 +00:00
freebsd/sys/pci/tek390.c
Stefan Eßer 5bec615793 Completely replace the PCI bus driver code to make it better reflect
reality. There will be a new call interface, but for now the file
pci_compat.c (which is to be deleted, after all drivers are converted)
provides an emulation of the old PCI bus driver functions. The only
change that might be visible to drivers is, that the type pcici_t
(which had been meant to be just a handle, whose exact definition
should not be relied on), has been converted into a pcicfgregs* .

The Tekram AMD SCSI driver bogusly relied on the definition of pcici_t
and has been converted to just call the PCI drivers functions to access
configuration space register, instead of inventing its own ...

This code is by no means complete, but assumed to be fully operational,
and brings the official code base more in line with my development code.

A new generic device descriptor data type has to be agreed on. The PCI
code will then use that data type to provide new functionality:

1) userconfig support
2) "wired" PCI devices
3) conflicts checking against ISA/EISA
4) maps will depend on the command register enable bits
5) PCI to Anything bridges can be defined as devices,
   and are probed like any "standard" PCI device.

The following features are currently missing, but will be added back,
soon:

1) unknown device probe message
2) suppression of "mirrored" devices caused by ancient, broken chip-sets

This code relies on generic shared interrupt support just commited to
kern_intr.c (plus the modifications of isa.c and isa_device.h).
1997-05-26 15:08:43 +00:00

1713 lines
36 KiB
C

/***********************************************************************
* FILE NAME : TEK390.C *
* BY : C.L. Huang (ching@tekram.com.tw) *
* Description: Device Driver for Tekram DC-390(T) PCI SCSI *
* Bus Master Host Adapter *
* (C)Copyright 1995-1996 Tekram Technology Co., Ltd. *
***********************************************************************/
/***********************************************************************
* HISTORY: *
* *
* REV# DATE NAME DESCRIPTION *
* 1.00 07/02/96 CLH First release for RELEASE-2.1.0 *
* 1.01 08/20/96 CLH Update for RELEASE-2.1.5 *
* *
***********************************************************************/
/**************************************************************************
*
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************/
/**************************************************************************/
/* Imported into FreeBSD source repository, and updated to compile under */
/* FreeBSD-3.0-DEVELOPMENT, by Stefan Esser <se@FreeBSD.Org>, 1996-12-17 */
/**************************************************************************/
/* #define REL_2_1_0 */
#define REL_2_1_5
#define DC390_DEBUG
#include <sys/param.h>
/* XXX this doesn't actually compile unless KERNEL is defined. */
#ifdef KERNEL
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/buf.h>
#include <sys/kernel.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#endif /* KERNEL */
#include <pci/pcivar.h>
#include <pci/pcireg.h>
#include <scsi/scsiconf.h>
#include <machine/clock.h>
#include <pci/tek390.h>
#define INT32 int32
#define U_INT32 u_int32
#define OutB(val, port) outb(port, val)
#define OutW(val, port) outw(port, val)
#define OutL(val, port) outl(port, val)
#define PCI_DEVICE_ID_AMD53C974 0x20201022ul
#define PCI_BASE_ADDR0 0x10
#ifdef REL_2_1_0
static int DC390_Interrupt (PACB pACB);
#endif
#ifdef REL_2_1_5
static void DC390_Interrupt (PACB pACB);
#endif
static USHORT DC390_StartSCSI( PACB pACB, PDCB pDCB, PSRB pSRB );
static void DC390_DataOut_0( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_DataIn_0( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_Command_0( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_Status_0( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_MsgOut_0( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_MsgIn_0( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_DataOutPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_DataInPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_CommandPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_StatusPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_MsgOutPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_MsgInPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_Nop_0( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void DC390_Nop_1( PACB pACB, PSRB pSRB, PUCHAR psstatus);
static void SetXferRate( PACB pACB, PDCB pDCB );
static void DC390_Disconnect( PACB pACB );
static void DC390_Reselect( PACB pACB );
static void SRBdone( PACB pACB, PDCB pDCB, PSRB pSRB );
static void DoingSRB_Done( PACB pACB );
static void DC390_ScsiRstDetect( PACB pACB );
static void DC390_ResetSCSIBus( PACB pACB );
static void RequestSense( PACB pACB, PDCB pDCB, PSRB pSRB );
static void EnableMsgOut2( PACB pACB, PSRB pSRB );
static void EnableMsgOut( PACB pACB, PSRB pSRB );
static void DC390_InvalidCmd( PACB pACB );
static void DC390_timeout( void *arg1);
static void DC390_reset (PACB pACB);
static PUCHAR phystovirt( PSRB pSRB, ULONG xferCnt );
void DC390_initDCB( PACB pACB, PDCB pDCB, PSCSICMD cmd );
void DC390_initSRB( PSRB psrb );
void DC390_linkSRB( PACB pACB );
void DC390_initACB( PACB pACB, ULONG io_port, UCHAR Irq, USHORT index );
int DC390_initAdapter( PACB pACB, ULONG io_port, UCHAR Irq, USHORT index,
pcici_t config_id );
void DC390_EnableCfg( USHORT mechnum, UCHAR regval );
void DC390_DisableCfg( USHORT mechnum );
UCHAR DC390_inByte( USHORT mechnum, UCHAR regval );
USHORT DC390_inWord( USHORT mechnum, UCHAR regval );
ULONG DC390_inDword(USHORT mechnum, UCHAR regval );
void DC390_OutB(USHORT mechnum, UCHAR regval, UCHAR bval );
void DC390_EnDisableCE( UCHAR mode, USHORT mechnum, PUCHAR regval );
void DC390_EEpromOutDI( USHORT mechnum, PUCHAR regval, USHORT Carry );
UCHAR DC390_EEpromInDO( USHORT mechnum );
USHORT EEpromGetData1( USHORT mechnum );
void DC390_Prepare( USHORT mechnum, PUCHAR regval, UCHAR EEpromCmd );
void DC390_ReadEEprom( USHORT mechnum, USHORT index );
USHORT DC390_DefaultEEprom( USHORT mechnum, USHORT index );
USHORT DC390_CheckEEpromCheckSum( USHORT MechNum, USHORT index );
USHORT DC390_ToMech( USHORT Mechnum, pcici_t config_id );
#ifdef KERNEL
static char* trmamd_probe( pcici_t tag, pcidi_t type);
static void trmamd_attach( pcici_t tag, int unit);
#ifdef REL_2_1_0
static int32 trmamd_scsi_cmd( struct scsi_xfer *sx);
#endif
#ifdef REL_2_1_5
static int32_t trmamd_scsi_cmd( struct scsi_xfer *sx);
#endif
static void trmamd_min_phys( struct buf *pbuf);
#ifdef REL_2_1_0
static u_int32 trmamd_info( int unit );
#endif
#endif /* KERNEL */
static u_long trmamd_count;
struct pci_device trmamd_device = {
"amd",
trmamd_probe,
trmamd_attach,
&trmamd_count,
NULL
};
DATA_SET (pcidevice_set, trmamd_device);
struct scsi_adapter trmamd_switch =
{
trmamd_scsi_cmd,
trmamd_min_phys,
0,
0,
#ifdef REL_2_1_0
trmamd_info,
#endif
#ifdef REL_2_1_5
0,
#endif
"amd",
};
struct scsi_device trmamd_dev =
{
NULL, /* Use default error handler */
NULL, /* have a queue, served by this */
NULL, /* have no async handler */
NULL, /* Use default 'done' routine */
"amd",
};
static PACB pACB0[MAX_ADAPTER_NUM]={0};
static PACB pACB_start= NULL;
static PACB pACB_current = NULL;
static PDCB pPrevDCB = NULL;
static USHORT adapterCnt = 0;
static USHORT CurrSyncOffset = 0;
static USHORT mech2Agent;
static ULONG mech1addr;
static UCHAR mech2bus, mech2CfgSPenR, CurrentID, CurrentLUN;
static PVOID DC390_phase0[]={
DC390_DataOut_0,
DC390_DataIn_0,
DC390_Command_0,
DC390_Status_0,
DC390_Nop_0,
DC390_Nop_0,
DC390_MsgOut_0,
DC390_MsgIn_0,
DC390_Nop_1
};
static PVOID DC390_phase1[]={
DC390_DataOutPhase,
DC390_DataInPhase,
DC390_CommandPhase,
DC390_StatusPhase,
DC390_Nop_0,
DC390_Nop_0,
DC390_MsgOutPhase,
DC390_MsgInPhase,
DC390_Nop_1,
};
UCHAR eepromBuf[MAX_ADAPTER_NUM][128];
UCHAR clock_period1[] = {4, 5, 6, 7 ,8, 10, 13, 20};
UCHAR baddevname1[2][28] ={
"SEAGATE ST3390N ??? 9546",
"HP C3323-300 4269"};
#define BADDEVCNT 2
/***********************************************************************
*
*
*
**********************************************************************/
static PSRB
GetSRB( PACB pACB )
{
int flags;
PSRB pSRB;
flags = splbio();
pSRB = pACB->pFreeSRB;
if( pSRB )
{
pACB->pFreeSRB = pSRB->pNextSRB;
pSRB->pNextSRB = NULL;
}
splx(flags);
return( pSRB );
}
static void
RewaitSRB0( PDCB pDCB, PSRB pSRB )
{
PSRB psrb1;
int flags;
flags = splbio();
if( (psrb1 = pDCB->pWaitingSRB) )
{
pSRB->pNextSRB = psrb1;
pDCB->pWaitingSRB = pSRB;
}
else
{
pSRB->pNextSRB = NULL;
pDCB->pWaitingSRB = pSRB;
pDCB->pWaitLast = pSRB;
}
splx(flags);
}
static void
RewaitSRB( PDCB pDCB, PSRB pSRB )
{
PSRB psrb1;
int flags;
UCHAR bval;
flags = splbio();
pDCB->GoingSRBCnt--;
psrb1 = pDCB->pGoingSRB;
if( pSRB == psrb1 )
{
pDCB->pGoingSRB = psrb1->pNextSRB;
}
else
{
while( pSRB != psrb1->pNextSRB )
psrb1 = psrb1->pNextSRB;
psrb1->pNextSRB = pSRB->pNextSRB;
if( pSRB == pDCB->pGoingLast )
pDCB->pGoingLast = psrb1;
}
if( (psrb1 = pDCB->pWaitingSRB) )
{
pSRB->pNextSRB = psrb1;
pDCB->pWaitingSRB = pSRB;
}
else
{
pSRB->pNextSRB = NULL;
pDCB->pWaitingSRB = pSRB;
pDCB->pWaitLast = pSRB;
}
bval = pSRB->TagNumber;
pDCB->TagMask &= (~(1 << bval)); /* Free TAG number */
splx(flags);
}
static void
DoWaitingSRB( PACB pACB )
{
int flags;
PDCB ptr, ptr1;
PSRB pSRB;
flags = splbio();
if( !(pACB->pActiveDCB) && !(pACB->ACBFlag & (RESET_DETECT+RESET_DONE+RESET_DEV) ) )
{
ptr = pACB->pDCBRunRobin;
if( !ptr )
{
ptr = pACB->pLinkDCB;
pACB->pDCBRunRobin = ptr;
}
ptr1 = ptr;
for( ;ptr1; )
{
pACB->pDCBRunRobin = ptr1->pNextDCB;
if( !( ptr1->MaxCommand > ptr1->GoingSRBCnt ) ||
!( pSRB = ptr1->pWaitingSRB ) )
{
if(pACB->pDCBRunRobin == ptr)
break;
ptr1 = ptr1->pNextDCB;
}
else
{
if( !DC390_StartSCSI(pACB, ptr1, pSRB) )
{
ptr1->GoingSRBCnt++;
if( ptr1->pWaitLast == pSRB )
{
ptr1->pWaitingSRB = NULL;
ptr1->pWaitLast = NULL;
}
else
{
ptr1->pWaitingSRB = pSRB->pNextSRB;
}
pSRB->pNextSRB = NULL;
if( ptr1->pGoingSRB )
ptr1->pGoingLast->pNextSRB = pSRB;
else
ptr1->pGoingSRB = pSRB;
ptr1->pGoingLast = pSRB;
}
break;
}
}
}
splx(flags);
return;
}
static void
SRBwaiting( PDCB pDCB, PSRB pSRB)
{
if( pDCB->pWaitingSRB )
{
pDCB->pWaitLast->pNextSRB = pSRB;
pDCB->pWaitLast = pSRB;
pSRB->pNextSRB = NULL;
}
else
{
pDCB->pWaitingSRB = pSRB;
pDCB->pWaitLast = pSRB;
}
}
static void
SendSRB( PSCSICMD pcmd, PACB pACB, PSRB pSRB )
{
int flags;
PDCB pDCB;
flags = splbio();
pDCB = pSRB->pSRBDCB;
if( !(pDCB->MaxCommand > pDCB->GoingSRBCnt) || (pACB->pActiveDCB) ||
(pACB->ACBFlag & (RESET_DETECT+RESET_DONE+RESET_DEV)) )
{
SRBwaiting(pDCB, pSRB);
goto SND_EXIT;
}
if( pDCB->pWaitingSRB )
{
SRBwaiting(pDCB, pSRB);
/* pSRB = GetWaitingSRB(pDCB); */
pSRB = pDCB->pWaitingSRB;
pDCB->pWaitingSRB = pSRB->pNextSRB;
pSRB->pNextSRB = NULL;
}
if( !DC390_StartSCSI(pACB, pDCB, pSRB) )
{
pDCB->GoingSRBCnt++;
if( pDCB->pGoingSRB )
{
pDCB->pGoingLast->pNextSRB = pSRB;
pDCB->pGoingLast = pSRB;
}
else
{
pDCB->pGoingSRB = pSRB;
pDCB->pGoingLast = pSRB;
}
}
else
RewaitSRB0( pDCB, pSRB );
SND_EXIT:
splx(flags);
return;
}
/***********************************************************************
* Function : static int32 dc390_scsi_cmd (struct scsi_xfer *cmd)
* Purpose : enqueues a SCSI command
***********************************************************************/
#ifdef REL_2_1_0
int32
#endif
#ifdef REL_2_1_5
int32_t
#endif
trmamd_scsi_cmd ( PSCSICMD cmd )
{
USHORT ioport, i;
PSCSICMD pcmd;
PSCLINK plink;
PACB pACB;
PDCB pDCB;
PSRB pSRB;
int flags, cflags, unit, CurrPgVaddr;
ULONG sglen, pglen, datalen, CurrPgPaddr, NextPgPaddr;
PUCHAR ptr,ptr1;
PSEG psg;
UCHAR sgc, sstatus;
plink = cmd->sc_link;
unit = plink->adapter_unit;
pACB = pACB0[unit];
ioport = pACB->IOPortBase;
#ifdef DC390_DEBUG0
printf("Cmd=%2x,ID=%d,LUN=%d,",cmd->cmd->opcode,
plink->target, plink->lun);
#endif
if( pACB->scan_devices )
{
if( (plink->target > CurrentID) ||
(plink->target == CurrentID) && (plink->lun >= CurrentLUN) )
{
CurrentID = plink->target;
CurrentLUN = plink->lun;
}
else
{
pACB->scan_devices = 0;
pPrevDCB->pNextDCB = pACB->pLinkDCB;
}
}
if ( ( plink->target > pACB->max_id ) || ( plink->lun > pACB->max_lun ) )
{
#ifdef DC390_DEBUG0
printf("DC390: Ignore target %d lun %d\n",
plink->target, plink->lun);
#endif
cmd->error = XS_DRIVER_STUFFUP;
return( COMPLETE );
}
if( (pACB->scan_devices) && !(pACB->DCBmap[plink->target] & (1 << plink->lun)) )
{
if( pACB->DeviceCnt < MAX_DEVICES )
{
pACB->DCBmap[plink->target] |= (1 << plink->lun);
pDCB = pACB->pDCB_free;
#ifdef DC390_DEBUG0
printf("pDCB=%8x,ID=%2x,", (UINT) pDCB, plink->target);
#endif
DC390_initDCB( pACB, pDCB, cmd );
}
else /* ???? */
{
#ifdef DC390_DEBUG0
printf("DC390: Ignore target %d lun %d\n",
plink->target, plink->lun);
#endif
cmd->error = XS_DRIVER_STUFFUP;
return( COMPLETE );
}
}
else if( !(pACB->scan_devices) && !(pACB->DCBmap[plink->target] & (1 << plink->lun)) )
{
#ifdef DC390_DEBUG0
printf("DC390: Ignore target %d lun %d\n",
plink->target, plink->lun);
#endif
cmd->error = XS_DRIVER_STUFFUP;
return( COMPLETE );
}
else
{
pDCB = pACB->pLinkDCB;
while( (pDCB->UnitSCSIID != plink->target) ||
(pDCB->UnitSCSILUN != plink->lun) )
{
pDCB = pDCB->pNextDCB;
}
#ifdef DC390_DEBUG0
printf("pDCB=%8x,ID=%2x,", (UINT) pDCB, plink->target);
#endif
}
cflags = cmd->flags;
if(cflags & SCSI_RESET)
{
DC390_reset (pACB);
cmd->error = XS_NOERROR;
return(COMPLETE);
}
if( cflags & ITSDONE )
{
printf("DC390: Is it done?\n");
cmd->flags &= ~ITSDONE;
}
if( !(cflags & INUSE) )
{
printf("DC390: In Use?\n");
cmd->flags |= INUSE;
}
cmd->error = 0;
cmd->resid = 0;
flags = splbio();
pcmd = cmd;
pSRB = GetSRB( pACB );
if( !pSRB )
{
pcmd->error = XS_DRIVER_STUFFUP;
splx(flags);
return( TRY_AGAIN_LATER);
}
/* BuildSRB(pSRB); */
pSRB->pSRBDCB = pDCB;
pSRB->pcmd = pcmd;
ptr = (PUCHAR) pSRB->CmdBlock;
ptr1 = (PUCHAR) pcmd->cmd;
pSRB->ScsiCmdLen = pcmd->cmdlen;
for(i=0; i< pcmd->cmdlen; i++)
{
*ptr = *ptr1;
ptr++;
ptr1++;
}
if( pcmd->datalen )
{
psg = (PSEG) &pSRB->SGsegment[0];
pSRB->pSegmentList = psg;
sgc = 0;
/* Set up the scatter gather list */
datalen = pcmd->datalen;
CurrPgVaddr = (int) pcmd->data;
CurrPgPaddr = vtophys(CurrPgVaddr);
while ((datalen) && (sgc < MAX_SG_ENTRY))
{
sglen = 0;
psg->SGXPtr = CurrPgPaddr;
NextPgPaddr = CurrPgPaddr;
while ((datalen) && (CurrPgPaddr == NextPgPaddr))
{
/*
* This page is contiguous (physically) with the the last,
* just extend the length
*/
NextPgPaddr = (CurrPgPaddr & (~(PAGELEN - 1))) + PAGELEN;
pglen = NextPgPaddr - CurrPgPaddr;
if( datalen < pglen )
pglen = datalen;
sglen += pglen;
datalen -= pglen;
CurrPgVaddr = (CurrPgVaddr & (~(PAGELEN - 1))) + PAGELEN;
if( datalen )
CurrPgPaddr = vtophys(CurrPgVaddr);
}
/*
next page isn't contiguous, finish this segment
*/
psg->SGXLen = sglen;
psg++;
sgc++;
}
pSRB->SGcount = sgc;
if (datalen)
{
printf("DC390: Out Of Segment Buffer!\n");
pSRB->pNextSRB = pACB->pFreeSRB;
pACB->pFreeSRB = pSRB;
pcmd->error = XS_DRIVER_STUFFUP;
splx(flags);
return (HAD_ERROR);
}
}
else
pSRB->SGcount = 0;
pSRB->SGIndex = 0;
pSRB->AdaptStatus = 0;
pSRB->TargetStatus = 0;
pSRB->MsgCnt = 0;
if( pDCB->DevType != SCSI_SEQACESS )
pSRB->RetryCnt = 1;
else
pSRB->RetryCnt = 0;
pSRB->SRBStatus = 0;
pSRB->SRBFlag = 0;
pSRB->SRBState = 0;
pSRB->TotalXferredLen = 0;
pSRB->SGPhysAddr = 0;
pSRB->SGToBeXferLen = 0;
pSRB->ScsiPhase = 0;
pSRB->EndMessage = 0;
splx(flags);
if( !(cflags & SCSI_NOMASK) )
{
flags = splbio();
SendSRB( pcmd, pACB, pSRB );
timeout(DC390_timeout, (caddr_t)pSRB, (pcmd->timeout * hz)/1000);
splx(flags);
return( SUCCESSFULLY_QUEUED);
}
else
{
SendSRB( pcmd, pACB, pSRB );
do
{
while(--pcmd->timeout)
{
DELAY(1000);
sstatus = inb( ioport+Scsi_Status );
if( sstatus & INTERRUPT )
break;
}
if( pcmd->timeout == 0 )
{
return(HAD_ERROR);
}
else
{
DC390_Interrupt( pACB );
}
}
while( !(pcmd->flags & ITSDONE) );
if( pcmd->error == XS_TIMEOUT)
return(HAD_ERROR);
else
return(COMPLETE);
}
}
void
trmamd_min_phys( struct buf *bp )
{
if (bp->b_bcount > ((MAX_SG_ENTRY - 1) * PAGELEN))
bp->b_bcount = ((MAX_SG_ENTRY - 1) * PAGELEN);
}
#ifdef REL_2_1_0
u_int32
trmamd_info( int unit )
{
return (MAX_CMD_PER_LUN); /* outstanding requests at a time per device */
}
#endif
static PUCHAR phystovirt( PSRB pSRB, ULONG xferCnt )
{
int dataPtr;
PSCSICMD pcmd;
UCHAR i;
PSEG pseg;
pcmd = pSRB->pcmd;
dataPtr = (int) pcmd->data;
pseg = pSRB->SGsegment;
for(i=0; i < pSRB->SGIndex; i++)
{
dataPtr += (int) pseg->SGXLen;
pseg++;
}
dataPtr += (int) xferCnt;
return( (PUCHAR) dataPtr);
}
/***********************************************************************
* Function : int DC390_abort (SCSICMD *cmd)
*
* Purpose : Abort an errant SCSI command
*
* Inputs : cmd - command to abort
*
* Returns : 0 on success, -1 on failure.
***********************************************************************/
/*
int
DC390_abort (SCSICMD *cmd)
{
int flags;
PACB pACB;
PDCB pDCB, pdcb;
PSRB pSRB, psrb;
USHORT count, i;
PSCSICMD pcmd, pcmd1;
int status;
#ifdef DC390_DEBUG0
printf("DC390 : Abort Cmd.");
#endif
flags = splbio();
pACB = (PACB) cmd->host->hostdata;
pDCB = pACB->pLinkDCB;
pdcb = pDCB;
while( (pDCB->UnitSCSIID != cmd->sc_link->target) ||
(pDCB->UnitSCSILUN != cmd->sc_link->lun) )
{
pDCB = pDCB->pNextDCB;
if( pDCB == pdcb )
goto NOT_RUN;
}
pSRB = pDCB->pWaitingSRB;
if( !pSRB )
goto ON_GOING;
if( pSRB->pcmd == cmd )
{
pDCB->pWaitingSRB = pSRB->pNextSRB;
goto IN_WAIT;
}
else
{
psrb = pSRB;
while( psrb->pNextSRB->pcmd != cmd )
{
psrb = psrb->pNextSRB;
if( !psrb )
goto ON_GOING;
}
pSRB = psrb->pNextSRB;
psrb->pNextSRB = pSRB->pNextSRB;
if( pSRB == pDCB->pWaitLast )
pDCB->pWaitLast = psrb;
IN_WAIT:
pSRB->pNextSRB = pACB->pFreeSRB;
pACB->pFreeSRB = pSRB;
cmd->next = NULL;
status = SCSI_ABORT_SUCCESS;
goto ABO_X;
}
ON_GOING:
pSRB = pDCB->pGoingSRB;
for( count = pDCB->GoingSRBCnt, i=0; i<count; i++)
{
if( pSRB->pcmd != cmd )
pSRB = pSRB->pNextSRB;
else
{
if( (pACB->pActiveDCB == pDCB) && (pDCB->pActiveSRB == pSRB) )
{
status = SCSI_ABORT_BUSY;
goto ABO_X;
}
else
{
status = SCSI_ABORT_SNOOZE;
goto ABO_X;
}
}
}
NOT_RUN:
status = SCSI_ABORT_NOT_RUNNING;
ABO_X:
cmd->error = XS_NOERROR;
scsi_done(cmd);
splx(flags);
return( status );
}
*/
static void
ResetDevParam( PACB pACB )
{
PDCB pDCB, pdcb;
pDCB = pACB->pLinkDCB;
if( pDCB == NULL )
return;
pdcb = pDCB;
do
{
pDCB->SyncMode &= ~SYNC_NEGO_DONE;
pDCB->SyncPeriod = 0;
pDCB->SyncOffset = 0;
pDCB->CtrlR3 = FAST_CLK;
pDCB->CtrlR4 &= NEGATE_REQACKDATA;
pDCB->CtrlR4 |= EATER_25NS;
pDCB = pDCB->pNextDCB;
}
while( pdcb != pDCB );
}
static void
RecoverSRB( PACB pACB )
{
PDCB pDCB, pdcb;
PSRB psrb, psrb2;
USHORT cnt, i;
pDCB = pACB->pLinkDCB;
if( pDCB == NULL )
return;
pdcb = pDCB;
do
{
cnt = pdcb->GoingSRBCnt;
psrb = pdcb->pGoingSRB;
for (i=0; i<cnt; i++)
{
psrb2 = psrb;
psrb = psrb->pNextSRB;
/* RewaitSRB( pDCB, psrb ); */
if( pdcb->pWaitingSRB )
{
psrb2->pNextSRB = pdcb->pWaitingSRB;
pdcb->pWaitingSRB = psrb2;
}
else
{
pdcb->pWaitingSRB = psrb2;
pdcb->pWaitLast = psrb2;
psrb2->pNextSRB = NULL;
}
}
pdcb->GoingSRBCnt = 0;
pdcb->pGoingSRB = NULL;
pdcb->TagMask = 0;
pdcb = pdcb->pNextDCB;
}
while( pdcb != pDCB );
}
/***********************************************************************
* Function : DC390_reset (PACB pACB)
*
* Purpose : perform a hard reset on the SCSI bus( and AMD chip).
*
* Inputs : cmd - command which caused the SCSI RESET
*
***********************************************************************/
static void
DC390_reset (PACB pACB)
{
USHORT ioport;
int flags;
UCHAR bval;
USHORT i;
#ifdef DC390_DEBUG0
printf("DC390: RESET,");
#endif
flags = splbio();
ioport = pACB->IOPortBase;
bval = inb(ioport+CtrlReg1);
bval |= DIS_INT_ON_SCSI_RST;
OutB(bval,ioport+CtrlReg1); /* disable interrupt */
DC390_ResetSCSIBus( pACB );
for( i=0; i<500; i++ )
DELAY(1000);
bval = inb(ioport+CtrlReg1);
bval &= ~DIS_INT_ON_SCSI_RST;
OutB(bval,ioport+CtrlReg1); /* re-enable interrupt */
bval = DMA_IDLE_CMD;
OutB(bval,ioport+DMA_Cmd);
bval = CLEAR_FIFO_CMD;
OutB(bval,ioport+ScsiCmd);
ResetDevParam( pACB );
DoingSRB_Done( pACB );
pACB->pActiveDCB = NULL;
pACB->ACBFlag = 0;
DoWaitingSRB( pACB );
splx(flags);
return;
}
void
DC390_timeout( void *arg1)
{
PSRB pSRB;
pSRB = (PSRB) arg1;
}
#include <pci/scsiiom.c>
/***********************************************************************
* Function : static void DC390_initDCB
*
* Purpose : initialize the internal structures for a given DCB
*
* Inputs : cmd - pointer to this scsi cmd request block structure
*
***********************************************************************/
void DC390_initDCB( PACB pACB, PDCB pDCB, PSCSICMD cmd )
{
PEEprom prom;
UCHAR bval;
USHORT index;
PSCLINK plink;
if( pACB->DeviceCnt == 0 )
{
pACB->pLinkDCB = pDCB;
pACB->pDCBRunRobin = pDCB;
pDCB->pNextDCB = pDCB;
pPrevDCB = pDCB;
}
else
pPrevDCB->pNextDCB = pDCB;
plink = cmd->sc_link;
pDCB->pDCBACB = pACB;
pDCB->UnitSCSIID = plink->target;
pDCB->UnitSCSILUN = plink->lun;
pDCB->pWaitingSRB = NULL;
pDCB->pGoingSRB = NULL;
pDCB->GoingSRBCnt = 0;
pDCB->pActiveSRB = NULL;
pDCB->TagMask = 0;
pDCB->MaxCommand = 1;
pDCB->AdaptIndex = pACB->AdapterIndex;
index = pACB->AdapterIndex;
pDCB->DCBFlag = 0;
prom = (PEEprom) &eepromBuf[index][plink->target << 2];
pDCB->DevMode = prom->EE_MODE1;
pDCB->AdpMode = eepromBuf[index][EE_MODE2];
if( pDCB->DevMode & EN_DISCONNECT_ )
bval = 0xC0;
else
bval = 0x80;
bval |= plink->lun;
pDCB->IdentifyMsg = bval;
pDCB->SyncMode = 0;
if( pDCB->DevMode & SYNC_NEGO_ )
{
if( !(plink->lun) || CurrSyncOffset )
pDCB->SyncMode = SYNC_ENABLE;
}
pDCB->SyncPeriod = 0;
pDCB->SyncOffset = 0;
pDCB->NegoPeriod = (clock_period1[prom->EE_SPEED] * 25) >> 2;
pDCB->CtrlR1 = pACB->AdaptSCSIID;
if( pDCB->DevMode & PARITY_CHK_ )
pDCB->CtrlR1 |= PARITY_ERR_REPO;
pDCB->CtrlR3 = FAST_CLK;
pDCB->CtrlR4 = EATER_25NS;
if( pDCB->AdpMode & ACTIVE_NEGATION)
pDCB->CtrlR4 |= NEGATE_REQACKDATA;
}
/***********************************************************************
* Function : static void DC390_initSRB
*
* Purpose : initialize the internal structures for a given SRB
*
* Inputs : psrb - pointer to this scsi request block structure
*
***********************************************************************/
void DC390_initSRB( PSRB psrb )
{
psrb->PhysSRB = vtophys( psrb );
}
void DC390_linkSRB( PACB pACB )
{
USHORT count, i;
PSRB psrb;
count = pACB->SRBCount;
for( i=0; i< count; i++)
{
if( i != count - 1)
pACB->SRB_array[i].pNextSRB = &pACB->SRB_array[i+1];
else
pACB->SRB_array[i].pNextSRB = NULL;
psrb = (PSRB) &pACB->SRB_array[i];
DC390_initSRB( psrb );
}
}
/***********************************************************************
* Function : static void DC390_initACB
*
* Purpose : initialize the internal structures for a given SCSI host
*
* Inputs : psh - pointer to this host adapter's structure
*
***********************************************************************/
void DC390_initACB( PACB pACB, ULONG io_port, UCHAR Irq, USHORT index )
{
USHORT i;
pACB->max_id = 7;
if( pACB->max_id == eepromBuf[index][EE_ADAPT_SCSI_ID] )
pACB->max_id--;
if( eepromBuf[index][EE_MODE2] & LUN_CHECK )
pACB->max_lun = 7;
else
pACB->max_lun = 0;
pACB->IOPortBase = (USHORT) io_port;
pACB->pLinkDCB = NULL;
pACB->pDCBRunRobin = NULL;
pACB->pActiveDCB = NULL;
pACB->pFreeSRB = pACB->SRB_array;
pACB->SRBCount = MAX_SRB_CNT;
pACB->AdapterIndex = index;
pACB->status = 0;
pACB->AdaptSCSIID = eepromBuf[index][EE_ADAPT_SCSI_ID];
pACB->HostID_Bit = (1 << pACB->AdaptSCSIID);
pACB->AdaptSCSILUN = 0;
pACB->DeviceCnt = 0;
pACB->IRQLevel = Irq;
pACB->TagMaxNum = (eepromBuf[index][EE_TAG_CMD_NUM]) << 2;
pACB->ACBFlag = 0;
pACB->scan_devices = 1;
pACB->Gmode2 = eepromBuf[index][EE_MODE2];
if( eepromBuf[index][EE_MODE2] & LUN_CHECK )
pACB->LUNchk = 1;
pACB->pDCB_free = &pACB->DCB_array[0];
DC390_linkSRB( pACB );
pACB->pTmpSRB = &pACB->TmpSRB;
DC390_initSRB( pACB->pTmpSRB );
for(i=0; i<MAX_SCSI_ID; i++)
pACB->DCBmap[i] = 0;
pACB->ScsiLink.adapter_unit = index;
pACB->ScsiLink.adapter_targ = pACB->AdaptSCSIID;
pACB->ScsiLink.fordriver = 0;
pACB->ScsiLink.opennings = 2;
pACB->ScsiLink.adapter = &trmamd_switch;
pACB->ScsiLink.device = &trmamd_dev;
pACB->ScsiLink.flags = 0;
}
/***********************************************************************
* Function : static int DC390_initAdapter
*
* Purpose : initialize the SCSI chip ctrl registers
*
* Inputs : psh - pointer to this host adapter's structure
*
***********************************************************************/
int DC390_initAdapter( PACB pACB, ULONG io_port, UCHAR Irq, USHORT index,
pcici_t config_id )
{
USHORT ioport;
UCHAR bval;
#ifdef CHECK_SHARE_INT
PACB pacb;
USHORT used_irq = 0;
pacb = pACB_start;
if( pacb != NULL )
{
for ( ; (pacb != (PACB) -1) ; )
{
if( pacb->IRQLevel == Irq )
{
used_irq = 1;
break;
}
else
pacb = pacb->pNextACB;
}
}
if( !used_irq )
{
#endif
if( !pci_map_int (config_id, (PVOID)DC390_Interrupt, pACB, &bio_imask) )
{
if(bootverbose)
printf("DC390: register Interrupt handler error!\n");
return( -1 );
}
#ifdef CHECK_SHARE_INT
}
#endif
ioport = (USHORT) io_port;
bval = 153; /* 250ms selection timeout */
OutB(bval,ioport+Scsi_TimeOut);
bval = CLK_FREQ_40MHZ; /* Conversion factor = 0 , 40MHz clock */
OutB(bval,ioport+Clk_Factor);
bval = NOP_CMD; /* NOP cmd - clear command register */
OutB(bval,ioport+ScsiCmd);
bval = EN_FEATURE+EN_SCSI2_CMD; /* Enable Feature and SCSI-2 */
OutB(bval,ioport+CtrlReg2);
bval = FAST_CLK; /* fast clock */
OutB(bval,ioport+CtrlReg3);
bval = EATER_25NS;
if( eepromBuf[index][EE_MODE2] & ACTIVE_NEGATION )
bval |= NEGATE_REQACKDATA;
OutB(bval,ioport+CtrlReg4);
bval = DIS_INT_ON_SCSI_RST; /* Disable SCSI bus reset interrupt */
OutB(bval,ioport+CtrlReg1);
return(0);
}
#ifdef PCI_COMPAT
static pcicfgregs *cfg;
#define DC390_EnableCfg(a,b)
#define DC390_DisableCfg(a)
#define DC390_inByte(a,reg) pci_cfgread(cfg,reg,1)
#define DC390_inWord(a,reg) pci_cfgread(cfg,reg,2)
#define DC390_inDword(a,reg) pci_cfgread(cfg,reg,4)
#define DC390_OutB(a,reg,val) pci_cfgwrite(cfg,reg,val,1)
#else
void
DC390_EnableCfg( USHORT mechnum, UCHAR regval )
{
ULONG wlval;
if(mechnum == 2)
{
OutB(mech2bus, PCI_CFG2_FORWARD_REG);
OutB(mech2CfgSPenR, PCI_CFG2_ENABLE_REG);
}
else
{
regval &= 0xFC;
wlval = mech1addr;
wlval |= (((ULONG)regval) & 0xff);
OutL(wlval, PCI_CFG1_ADDRESS_REG);
}
}
void
DC390_DisableCfg( USHORT mechnum )
{
if(mechnum == 2)
OutB(0, PCI_CFG2_ENABLE_REG);
else
OutL(0, PCI_CFG1_ADDRESS_REG);
}
UCHAR
DC390_inByte( USHORT mechnum, UCHAR regval )
{
UCHAR bval;
USHORT wval;
int flags;
flags = splbio();
DC390_EnableCfg( mechnum, regval );
if(mechnum == 2)
{
wval = mech2Agent;
wval <<= 8;
wval |= ((USHORT) regval) & 0xff;
bval = inb(wval);
}
else
{
regval &= 3;
bval = inb(PCI_CFG1_DATA_REG | regval);
}
DC390_DisableCfg(mechnum);
splx(flags);
return(bval);
}
USHORT
DC390_inWord( USHORT mechnum, UCHAR regval )
{
USHORT wval;
int flags;
flags = splbio();
DC390_EnableCfg(mechnum,regval);
if(mechnum == 2)
{
wval = mech2Agent;
wval <<= 8;
wval |= regval;
wval = inw(wval);
}
else
{
regval &= 3;
wval = inw(PCI_CFG1_DATA_REG | regval);
}
DC390_DisableCfg(mechnum);
splx(flags);
return(wval);
}
ULONG
DC390_inDword(USHORT mechnum, UCHAR regval )
{
ULONG wlval;
int flags;
USHORT wval;
flags = splbio();
DC390_EnableCfg(mechnum,regval);
if(mechnum == 2)
{
wval = mech2Agent;
wval <<= 8;
wval |= regval;
wlval = inl(wval);
}
else
{
wlval = inl(PCI_CFG1_DATA_REG);
}
DC390_DisableCfg(mechnum);
splx(flags);
return(wlval);
}
void
DC390_OutB(USHORT mechnum, UCHAR regval, UCHAR bval )
{
USHORT wval;
int flags;
flags = splbio();
DC390_EnableCfg(mechnum,regval);
if(mechnum == 2)
{
wval = mech2Agent;
wval <<= 8;
wval |= regval;
OutB(bval, wval);
}
else
{
regval &= 3;
OutB(bval, PCI_CFG1_DATA_REG | regval);
}
DC390_DisableCfg(mechnum);
splx(flags);
}
#endif /PCI_COMPAT */
void
DC390_EnDisableCE( UCHAR mode, USHORT mechnum, PUCHAR regval )
{
UCHAR bval;
bval = 0;
if(mode == ENABLE_CE)
*regval = 0xc0;
else
*regval = 0x80;
DC390_OutB(mechnum,*regval,bval);
if(mode == DISABLE_CE)
DC390_OutB(mechnum,*regval,bval);
DELAY(160);
}
void
DC390_EEpromOutDI( USHORT mechnum, PUCHAR regval, USHORT Carry )
{
UCHAR bval;
bval = 0;
if(Carry)
{
bval = 0x40;
*regval = 0x80;
DC390_OutB(mechnum,*regval,bval);
}
DELAY(160);
bval |= 0x80;
DC390_OutB(mechnum,*regval,bval);
DELAY(160);
bval = 0;
DC390_OutB(mechnum,*regval,bval);
DELAY(160);
}
UCHAR
DC390_EEpromInDO( USHORT mechnum )
{
UCHAR bval,regval;
regval = 0x80;
bval = 0x80;
DC390_OutB(mechnum,regval,bval);
DELAY(160);
bval = 0x40;
DC390_OutB(mechnum,regval,bval);
DELAY(160);
regval = 0x0;
bval = DC390_inByte(mechnum,regval);
if(bval == 0x22)
return(1);
else
return(0);
}
USHORT
EEpromGetData1( USHORT mechnum )
{
UCHAR i;
UCHAR carryFlag;
USHORT wval;
wval = 0;
for(i=0; i<16; i++)
{
wval <<= 1;
carryFlag = DC390_EEpromInDO(mechnum);
wval |= carryFlag;
}
return(wval);
}
void
DC390_Prepare( USHORT mechnum, PUCHAR regval, UCHAR EEpromCmd )
{
UCHAR i,j;
USHORT carryFlag;
carryFlag = 1;
j = 0x80;
for(i=0; i<9; i++)
{
DC390_EEpromOutDI(mechnum,regval,carryFlag);
carryFlag = (EEpromCmd & j) ? 1 : 0;
j >>= 1;
}
}
void
DC390_ReadEEprom( USHORT mechnum, USHORT index )
{
UCHAR regval,cmd;
PUSHORT ptr;
USHORT i;
ptr = (PUSHORT) &eepromBuf[index][0];
cmd = EEPROM_READ;
for(i=0; i<0x40; i++)
{
DC390_EnDisableCE(ENABLE_CE, mechnum, &regval);
DC390_Prepare(mechnum, &regval, cmd);
*ptr = EEpromGetData1(mechnum);
ptr++;
cmd++;
DC390_EnDisableCE(DISABLE_CE,mechnum,&regval);
}
}
USHORT
DC390_DefaultEEprom( USHORT mechnum, USHORT index )
{
PUCHAR ptr;
USHORT i;
ptr = (PUCHAR) &eepromBuf[index][0];
bzero (ptr, sizeof eepromBuf[index]);
for(i=0; i<0x40; i++)
{
*ptr = (TAG_QUEUING_|EN_DISCONNECT_|SYNC_NEGO_|PARITY_CHK_);
ptr += 4;
}
ptr[EE_ADAPT_SCSI_ID] = 7;
ptr[EE_MODE2] = (LUN_CHECK|ACTIVE_NEGATION);
ptr[EE_TAG_CMD_NUM] = 4;
return 0;
}
USHORT
DC390_CheckEEpromCheckSum( USHORT MechNum, USHORT index )
{
USHORT wval, rc, *ptr;
UCHAR i;
DC390_ReadEEprom( MechNum, index );
wval = 0;
ptr = (PUSHORT) &eepromBuf[index][0];
for(i=0; i<128 ;i+=2, ptr++)
wval += *ptr;
if( wval == 0x1234 )
rc = 0;
else
rc = DC390_DefaultEEprom( MechNum, index);
return( rc );
}
USHORT
DC390_ToMech( USHORT Mechnum, pcici_t config_id )
{
#ifdef PCI_COMPAT
cfg = config_id;
#else
if(Mechnum == 2)
{
mech2bus = config_id.cfg2.forward; /* Bus num */
mech2Agent = config_id.cfg2.port >> 8; /* Dev num */
mech2CfgSPenR = config_id.cfg2.enable; /* Fun num */
}
else /* use mech #1 method */
{
mech1addr = config_id.cfg1;
}
#endif /* PCI_COMPAT */
return(0);
}
/***********************************************************************
* Function : static int DC390_init (struct Scsi_Host *host)
*
* Purpose : initialize the internal structures for a given SCSI host
*
* Inputs : host - pointer to this host adapter's structure/
*
* Preconditions : when this function is called, the chip_type
* field of the pACB structure MUST have been set.
***********************************************************************/
static int
DC390_init( ULONG io_port, UCHAR Irq, USHORT index, USHORT MechNum,
pcici_t config_id)
{
PACB pACB;
if( !DC390_CheckEEpromCheckSum( MechNum, index) )
{
pACB = (PACB) malloc (sizeof (struct _ACB), M_DEVBUF, M_WAITOK);
if( !pACB )
{
printf("DC390%d: cannot allocate ACB !\n", index);
return( -1 );
}
bzero (pACB, sizeof (struct _ACB));
DC390_initACB( pACB, io_port, Irq, index );
if( !DC390_initAdapter( pACB, io_port, Irq, index, config_id) )
{
if( !pACB_start )
{
pACB_start = pACB;
pACB_current = pACB;
pACB->pNextACB = (PACB) -1;
}
else
{
pACB_current->pNextACB = pACB;
pACB_current = pACB;
pACB->pNextACB = (PACB) -1;
}
pACB0[index] = pACB;
#ifdef DC390_DEBUG0
printf("DC390: pACB = %8x, pDCB_array = %8x, pSRB_array = %8x\n",
(UINT) pACB, (UINT) pACB->DCB_array, (UINT) pACB->SRB_array);
printf("DC390: ACB size= %4x, DCB size= %4x, SRB size= %4x\n",
sizeof(DC390_ACB), sizeof(DC390_DCB), sizeof(DC390_SRB) );
#endif
return( 0 );
}
else
{
free( pACB, M_DEVBUF);
return( -1 );
}
}
else
{
printf("DC390_init: EEPROM reading error!\n");
return( -1 );
}
}
void
trmamd_attach (pcici_t config_id, int unit)
{
struct scsibus_data *scbus;
UCHAR irq;
USHORT MechNum;
ULONG io_port, wlval;
PACB pACB = 0;
int flags;
if( unit >= MAX_ADAPTER_NUM )
return;
if( pACB0[unit] )
return;
CurrentID = 0;
CurrentLUN = 0;
MechNum = pci_mechanism;
#ifdef DC390_DEBUG0
if(bootverbose)
printf("DC390: Mech=%2x,\n",(UCHAR) MechNum);
#endif
if( !DC390_ToMech( MechNum, config_id ) )
{
wlval = DC390_inDword( MechNum, PCI_ID_REG);
if(wlval == PCI_DEVICE_ID_AMD53C974 )
{
io_port =DC390_inDword(MechNum,PCI_BASE_ADDR0) & 0xFFFE;
irq = DC390_inByte( MechNum, PCI_INTERRUPT_REG);
#ifdef DC390_DEBUG0
if(bootverbose)
printf("DC390: IO_PORT=%4x,IRQ=%x,\n",(UINT) io_port, irq);
#endif
if( !DC390_init( io_port, irq, (USHORT) unit, MechNum, config_id) )
{
adapterCnt++;
}
else
return;
}
}
pACB = pACB0[unit];
/*
Now let the generic SCSI driver look for the SCSI devices on the bus
*/
flags = splbio();
scbus = scsi_alloc_bus();
if(!scbus)
{
splx(flags);
return;
}
scbus->adapter_link = &pACB->ScsiLink;
scbus->maxtarg = pACB->max_id;
#ifdef DC390_DEBUG
if(bootverbose)
printf("\nDC390: scanning for devices ...\n\n");
#endif
scsi_attachdevs (scbus);
scbus = NULL; /* Upper-level SCSI code owns this now */
#ifdef DC390_DEBUG
if(bootverbose)
printf("\n\nDC390: Attach devices return\n");
#endif
splx(flags);
}
static char*
trmamd_probe (pcici_t tag, pcidi_t type)
{
if( type == PCI_DEVICE_ID_AMD53C974 )
return ("amd 53c974 scsi");
else
return (NULL);
}