1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-26 11:47:31 +00:00
freebsd/sys/scsi/scsiconf.c
Rodney W. Grimes a3e4335498 Revised drivers from Julian. Support for generic scsi. Fixed scsi tape (st.c)
problems with respect to multiple tar files on one tape.  Now uses malloc
of data structures to reduce static kernel size.
1993-08-28 03:08:59 +00:00

781 lines
18 KiB
C

/*
* Written by Julian Elischer (julian@tfs.com)
* for TRW Financial Systems for use under the MACH(2.5) operating system.
*
* TRW Financial Systems, in accordance with their agreement with Carnegie
* Mellon University, makes this software available to CMU to distribute
* or use in any manner that they see fit as long as this message is kept with
* the software. For this reason TFS also grants any other persons or
* organisations permission to use or modify this software.
*
* TFS supplies this software to be publicly redistributed
* on the understanding that TFS is not responsible for the correct
* functioning of this software in any circumstances.
*
* Ported to run under 386BSD by Julian Elischer (julian@tfs.com) Sept 1992
*
* $Id: scsiconf.c,v 1.6 93/08/26 21:09:39 julian Exp Locker: julian $
*/
#include <sys/types.h>
#include "st.h"
#include "sd.h"
#include "ch.h"
#include "cd.h"
#include "sg.h"
#ifdef MACH
#include <i386/machparam.h>
#endif MACH
#include <scsi/scsi_all.h>
#include <scsi/scsiconf.h>
#if !defined(OSF) && !defined(__386BSD__)
#include "bll.h"
#include "cals.h"
#include "kil.h"
#else
#define NBLL 0
#define NCALS 0
#define NKIL 0
#endif /* !defined(OSF) && !defined(__386BSD__) */
#if NSD > 0
extern sdattach();
#endif NSD
#if NST > 0
extern stattach();
#endif NST
#if NCH > 0
extern chattach();
#endif NCH
#if NCD > 0
extern cdattach();
#endif NCD
#if NBLL > 0
extern bllattach();
#endif NBLL
#if NCALS > 0
extern calsattach();
#endif NCALS
#if NKIL > 0
extern kil_attach();
#endif NKIL
/***************************************************************\
* The structure of pre-configured devices that might be turned *
* off and therefore may not show up *
\***************************************************************/
struct predefined
{
u_char scsibus;
u_char dev;
u_char lu;
int (*attach_rtn)();
char *devname;
char flags;
}
pd[] =
{
#ifdef EXAMPLE_PREDEFINE
#if NSD > 0
{0,0,0,sdattach,"sd",0},/* define a disk at scsibus=0 dev=0 lu=0 */
#endif NSD
#endif EXAMPLE_PREDEFINE
{0,9,9} /*illegal dummy end entry */
};
/***************************************************************\
* The structure of known drivers for autoconfiguration *
\***************************************************************/
static struct scsidevs
{
int type;
int removable;
char *manufacturer;
char *model;
char *version;
int (*attach_rtn)();
char *devname;
char flags; /* 1 show my comparisons during boot(debug) */
}
#define SC_SHOWME 0x01
#define SC_ONE_LU 0x00
#define SC_MORE_LUS 0x02
knowndevs[] = {
#if NSD > 0
{ T_DIRECT,T_FIXED,"standard","any"
,"any",sdattach,"sd",SC_ONE_LU },
{ T_DIRECT,T_FIXED,"MAXTOR ","XT-4170S "
,"B5A ",sdattach,"mx1",SC_ONE_LU },
#endif NSD
#if NST > 0
{ T_SEQUENTIAL,T_REMOV,"standard","any"
,"any",stattach,"st",SC_ONE_LU },
#endif NST
#if NCALS > 0
{ T_PROCESSOR,T_FIXED,"standard","any"
,"any",calsattach,"cals",SC_MORE_LUS },
#endif NCALS
#if NCH > 0
{ T_CHANGER,T_REMOV,"standard","any"
,"any",chattach,"ch",SC_ONE_LU },
#endif NCH
#if NCD > 0
{ T_READONLY,T_REMOV,"SONY ","CD-ROM CDU-8012 "
,"3.1a",cdattach,"cd",SC_ONE_LU },
{ T_READONLY,T_REMOV,"PIONEER ","CD-ROM DRM-600 "
,"any",cdattach,"cd",SC_MORE_LUS },
#endif NCD
#if NBLL > 0
{ T_PROCESSOR,T_FIXED,"AEG ","READER "
,"V1.0",bllattach,"bll",SC_MORE_LUS },
#endif NBLL
#if NKIL > 0
{ T_SCANNER,T_FIXED,"KODAK ","IL Scanner 900 "
,"any",kil_attach,"kil",SC_ONE_LU },
#endif NKIL
{0}
};
/***************************************************************\
* Declarations *
\***************************************************************/
struct predefined *scsi_get_predef();
struct scsidevs *scsi_probedev();
struct scsidevs *selectdev();
/* controls debug level within the scsi subsystem */
/* see scsiconf.h for values */
int scsi_debug = 0x0;
int scsibus = 0x0; /* This is the Nth scsibus */
/***************************************************************\
* The routine called by the adapter boards to get all their *
* devices configured in. *
\***************************************************************/
scsi_attachdevs( unit, scsi_addr, scsi_switch)
int unit,scsi_addr;
struct scsi_switch *scsi_switch;
{
int targ,lun;
struct scsidevs *bestmatch = (struct scsidevs *)0;
struct predefined *predef;
int maybe_more;
#ifdef SCSI_DELAY
#if SCSI_DELAY > 2
printf("waiting for scsi devices to settle\n");
#else SCSI_DELAY > 2
#define SCSI_DELAY 2
#endif SCSI_DELAY > 2
#else
#define SCSI_DELAY 2
#endif SCSI_DELAY
spinwait(1000 * SCSI_DELAY);
targ = 0;
while(targ < 8)
{
maybe_more = 0; /* by default only check 1 lun */
if (targ == scsi_addr)
{
targ++;
continue;
}
lun = 0;
while(lun < 8)
{
predef = scsi_get_predef(scsibus
,targ
,lun
,scsi_switch
,&maybe_more);
bestmatch = scsi_probedev(unit
,targ
,lun
,scsi_switch
,&maybe_more);
if((bestmatch) && (predef)) /* both exist */
{
if(bestmatch->attach_rtn
!= predef->attach_rtn)
{
printf("Clash in found/expected devices\n");
printf("will link in FOUND\n");
}
(*(bestmatch->attach_rtn))(unit,
targ,
lun,
scsi_switch);
}
if((bestmatch) && (!predef)) /* just FOUND */
{
(*(bestmatch->attach_rtn))(unit,
targ,
lun,
scsi_switch);
}
if((!bestmatch) && (predef)) /* just predef */
{
(*(predef->attach_rtn))(unit,
targ,
lun,
scsi_switch);
}
if(!(maybe_more)) /* nothing suggests we'll find more */
{
break; /* nothing here, skip to next targ */
}
/* otherwise something says we should look further*/
lun++;
}
targ++;
}
#if NSG > 0
/***************************************************************\
* If available hook up the generic scsi driver, letting it *
* know which target is US. (i.e. illegal or at least special) *
\***************************************************************/
sg_attach(unit,scsi_addr,scsi_switch);
#endif
scsibus++; /* next time we are on the NEXT scsi bus */
}
/***********************************************\
* given a target and lu, check if there is a *
* predefined device for that address *
\***********************************************/
struct predefined *scsi_get_predef(unit,target,lu,scsi_switch,maybe_more)
int unit,target,lu,*maybe_more;
struct scsi_switch *scsi_switch;
{
int upto,numents;
numents = (sizeof(pd)/sizeof(struct predefined)) - 1;
for(upto = 0;upto < numents;upto++)
{
if(pd[upto].scsibus != unit)
continue;
if(pd[upto].dev != target)
continue;
if(pd[upto].lu != lu)
continue;
printf("%s%d targ %d lun %d: <%s> - PRECONFIGURED -\n"
,scsi_switch->name
,unit
,target
,lu
,pd[upto].devname);
*maybe_more = pd[upto].flags & SC_MORE_LUS;
return(&(pd[upto]));
}
return((struct predefined *)0);
}
/***********************************************\
* given a target and lu, ask the device what *
* it is, and find the correct driver table *
* entry. *
\***********************************************/
struct scsidevs *scsi_probedev(unit,target,lu,scsi_switch, maybe_more)
struct scsi_switch *scsi_switch;
int unit,target,lu;
int *maybe_more;
{
struct scsidevs *bestmatch = (struct scsidevs *)0;
char *dtype=(char *)0,*desc;
char *qtype;
static struct scsi_inquiry_data inqbuf;
int len,qualifier,type,remov;
char manu[32];
char model[32];
char version[32];
bzero(&inqbuf,sizeof(inqbuf));
/***********************************************\
* Ask the device what it is *
\***********************************************/
#ifdef DEBUG
if((target == 0) && (lu == 0))
scsi_debug = 0xfff;
else
scsi_debug = 0;
#endif DEBUG
if(scsi_ready( unit,
target,
lu,
scsi_switch,
SCSI_NOSLEEP | SCSI_NOMASK) != COMPLETE)
{
return(struct scsidevs *)0;
}
if(scsi_inquire(unit,
target,
lu,
scsi_switch,
&inqbuf,
SCSI_NOSLEEP | SCSI_NOMASK) != COMPLETE)
{
return(struct scsidevs *)0;
}
/***********************************************\
* note what BASIC type of device it is *
\***********************************************/
if(scsi_debug & SHOWINQUIRY)
{
desc=(char *)&inqbuf;
printf("inq: %x %x %x %x %x %x %x %x %x %x %x %x %x\n",
desc[0], desc[1], desc[2], desc[3],
desc[4], desc[5], desc[6], desc[7],
desc[8], desc[9], desc[10], desc[11],
desc[12]);
}
type = inqbuf.device & SID_TYPE;
qualifier = inqbuf.device & SID_QUAL;
remov = inqbuf.dev_qual2 & SID_REMOVABLE;
/* Any device qualifier that has
* the top bit set (qualifier&4 != 0) is vendor specific and
* won't match in this switch.
*/
switch(qualifier)
{
case SID_QUAL_LU_OK:
qtype="";
break;
case SID_QUAL_LU_OFFLINE:
qtype=", Unit not Connected!";
break;
case SID_QUAL_RSVD:
qtype=", Reserved Peripheral Qualifier!";
*maybe_more = 1;
return (struct scsidevs *)0;
break;
case SID_QUAL_BAD_LU:
/*
* Check for a non-existent unit. If the device is returning
* this much, then we must set the flag that has
* the searcher keep looking on other luns.
*/
qtype=", The Target can't support this Unit!";
*maybe_more = 1;
return (struct scsidevs *)0;
default:
dtype="vendor specific";
qtype="";
*maybe_more = 1;
break;
}
if (dtype == 0)
{
switch(type)
{
case T_DIRECT:
dtype="direct";
break;
case T_SEQUENTIAL:
dtype="sequential";
break;
case T_PRINTER:
dtype="printer";
break;
case T_PROCESSOR:
dtype="processor";
break;
case T_READONLY:
dtype="readonly";
break;
case T_WORM:
dtype="worm";
break;
case T_SCANNER:
dtype="scanner";
break;
case T_OPTICAL:
dtype="optical";
break;
case T_CHANGER:
dtype="changer";
break;
case T_COMM:
dtype="communication";
break;
case T_NODEVICE:
*maybe_more = 1;
return (struct scsidevs *)0;
default:
dtype="unknown";
break;
}
}
/***********************************************\
* Then if it's advanced enough, more detailed *
* information *
\***********************************************/
if((inqbuf.version & SID_ANSII) > 0)
{
if ((len = inqbuf.additional_length
+ ( (char *)inqbuf.unused
- (char *)&inqbuf))
> (sizeof(struct scsi_inquiry_data) - 1))
len = sizeof(struct scsi_inquiry_data) - 1;
desc=inqbuf.vendor;
desc[len-(desc - (char *)&inqbuf)] = 0;
strncpy(manu,inqbuf.vendor,8);manu[8]=0;
strncpy(model,inqbuf.product,16);model[16]=0;
strncpy(version,inqbuf.revision,4);version[4]=0;
}
else
/***********************************************\
* If not advanced enough, use default values *
\***********************************************/
{
desc="early protocol device";
strncpy(manu,"unknown",8);
strncpy(model,"unknown",16);
strncpy(version,"????",4);
}
printf("%s%d targ %d lun %d: type %d(%s) %s SCSI%d\n"
,scsi_switch->name
,unit
,target
,lu
,type
,dtype
,remov?"removable":"fixed"
,inqbuf.version & SID_ANSII
);
printf("%s%d targ %d lun %d: <%s%s%s>\n"
,scsi_switch->name
,unit
,target
,lu
,manu
,model
,version
);
if(qtype[0])
{
printf("%s%d targ %d lun %d: qualifier %d(%s)\n"
,scsi_switch->name
,unit
,target
,lu
,qualifier
,qtype
);
}
/***********************************************\
* Try make as good a match as possible with *
* available sub drivers *
\***********************************************/
bestmatch = (selectdev(unit,target,lu,&scsi_switch,
qualifier,type,remov?T_REMOV:T_FIXED,manu,model,version));
if((bestmatch) && (bestmatch->flags & SC_MORE_LUS))
{
*maybe_more = 1;
}
return(bestmatch);
}
/***********************************************\
* Try make as good a match as possible with *
* available sub drivers *
\***********************************************/
struct scsidevs
*selectdev(unit,target,lu,dvr_switch,qualifier,type,remov,manu,model,rev)
int unit,target,lu;
struct scsi_switch *dvr_switch;
int qualifier,type,remov;
char *manu,*model,*rev;
{
int numents = (sizeof(knowndevs)/sizeof(struct scsidevs)) - 1;
int count = 0;
int bestmatches = 0;
struct scsidevs *bestmatch = (struct scsidevs *)0;
struct scsidevs *thisentry = knowndevs;
type |= qualifier; /* why? */
thisentry--;
while( count++ < numents)
{
thisentry++;
if(type != thisentry->type)
{
continue;
}
if(bestmatches < 1)
{
bestmatches = 1;
bestmatch = thisentry;
}
if(remov != thisentry->removable)
{
continue;
}
if(bestmatches < 2)
{
bestmatches = 2;
bestmatch = thisentry;
}
if(thisentry->flags & SC_SHOWME)
printf("\n%s-\n%s-",thisentry->manufacturer, manu);
if(strcmp(thisentry->manufacturer, manu))
{
continue;
}
if(bestmatches < 3)
{
bestmatches = 3;
bestmatch = thisentry;
}
if(thisentry->flags & SC_SHOWME)
printf("\n%s-\n%s-",thisentry->model, model);
if(strcmp(thisentry->model, model))
{
continue;
}
if(bestmatches < 4)
{
bestmatches = 4;
bestmatch = thisentry;
}
if(thisentry->flags & SC_SHOWME)
printf("\n%s-\n%s-",thisentry->version, rev);
if(strcmp(thisentry->version, rev))
{
continue;
}
if(bestmatches < 5)
{
bestmatches = 5;
bestmatch = thisentry;
break;
}
}
if (bestmatch == (struct scsidevs *)0)
printf(" No explicit device driver match for \"%s %s\".\n",
manu, model);
return(bestmatch);
}
static int recurse = 0;
/***********************************************\
* Do a scsi operation asking a device if it is *
* ready. Use the scsi_cmd routine in the switch *
* table. *
\***********************************************/
scsi_ready(unit,target,lu,scsi_switch, flags)
struct scsi_switch *scsi_switch;
{
struct scsi_test_unit_ready scsi_cmd;
struct scsi_xfer scsi_xfer;
volatile int rval;
int key;
bzero(&scsi_cmd, sizeof(scsi_cmd));
bzero(&scsi_xfer, sizeof(scsi_xfer));
scsi_cmd.op_code = TEST_UNIT_READY;
scsi_xfer.flags=flags | INUSE;
scsi_xfer.adapter=unit;
scsi_xfer.targ=target;
scsi_xfer.lu=lu;
scsi_xfer.cmd=(struct scsi_generic *)&scsi_cmd;
scsi_xfer.retries=8;
scsi_xfer.timeout=10000;
scsi_xfer.cmdlen=sizeof(scsi_cmd);
scsi_xfer.data=0;
scsi_xfer.datalen=0;
scsi_xfer.resid=0;
scsi_xfer.when_done=0;
scsi_xfer.done_arg=0;
retry: scsi_xfer.error=0;
/*******************************************************\
* do not use interrupts *
\*******************************************************/
rval = (*(scsi_switch->scsi_cmd))(&scsi_xfer);
if (rval != COMPLETE)
{
if(scsi_debug)
{
printf("scsi error, rval = 0x%x\n",rval);
printf("code from driver: 0x%x\n",scsi_xfer.error);
}
switch(scsi_xfer.error)
{
case XS_SENSE:
/*******************************************************\
* Any sense value is illegal except UNIT ATTENTION *
* In which case we need to check again to get the *
* correct response. *
*( especially exabytes) *
\*******************************************************/
if(((scsi_xfer.sense.error_code & SSD_ERRCODE) == 0x70 )
||((scsi_xfer.sense.error_code & SSD_ERRCODE) == 0x71 ))
{
key = scsi_xfer.sense.ext.extended.flags & SSD_KEY ;
switch(key)
{
case 2: /* not ready BUT PRESENT! */
return(COMPLETE);
case 6:
spinwait(1000);
if(scsi_xfer.retries--)
{
scsi_xfer.flags &= ~ITSDONE;
goto retry;
}
return(COMPLETE);
default:
if(scsi_debug)
printf("%d:%d,key=%x.",
target,lu,key);
}
}
return(HAD_ERROR);
case XS_BUSY:
spinwait(1000);
if(scsi_xfer.retries--)
{
scsi_xfer.flags &= ~ITSDONE;
goto retry;
}
return(COMPLETE); /* it's busy so it's there */
case XS_TIMEOUT:
default:
return(HAD_ERROR);
}
}
return(COMPLETE);
}
/***********************************************\
* Do a scsi operation asking a device what it is*
* Use the scsi_cmd routine in the switch table. *
\***********************************************/
scsi_inquire(unit,target,lu,scsi_switch,inqbuf, flags)
struct scsi_switch *scsi_switch;
u_char *inqbuf;
{
struct scsi_inquiry scsi_cmd;
struct scsi_xfer scsi_xfer;
volatile int rval;
bzero(&scsi_cmd, sizeof(scsi_cmd));
bzero(&scsi_xfer, sizeof(scsi_xfer));
scsi_cmd.op_code = INQUIRY;
scsi_cmd.length = sizeof(struct scsi_inquiry_data);
scsi_xfer.flags=flags | SCSI_DATA_IN | INUSE;
scsi_xfer.adapter=unit;
scsi_xfer.targ=target;
scsi_xfer.lu=lu;
scsi_xfer.retries=8;
scsi_xfer.timeout=10000;
scsi_xfer.cmd=(struct scsi_generic *)&scsi_cmd;
scsi_xfer.cmdlen= sizeof(struct scsi_inquiry);
scsi_xfer.data=inqbuf;
scsi_xfer.datalen=sizeof(struct scsi_inquiry_data);
scsi_xfer.resid=sizeof(struct scsi_inquiry_data);
scsi_xfer.when_done=0;
scsi_xfer.done_arg=0;
retry: scsi_xfer.error=0;
/*******************************************************\
* do not use interrupts *
\*******************************************************/
if ((*(scsi_switch->scsi_cmd))(&scsi_xfer) != COMPLETE)
{
if(scsi_debug) printf("inquiry had error(0x%x) ",scsi_xfer.error);
switch(scsi_xfer.error)
{
case XS_NOERROR:
break;
case XS_SENSE:
/*******************************************************\
* Any sense value is illegal except UNIT ATTENTION *
* In which case we need to check again to get the *
* correct response. *
*( especially exabytes) *
\*******************************************************/
if(((scsi_xfer.sense.error_code & SSD_ERRCODE) == 0x70 )
&& ((scsi_xfer.sense.ext.extended.flags & SSD_KEY) == 6))
{ /* it's changed so it's there */
spinwait(1000);
{
if(scsi_xfer.retries--)
{
scsi_xfer.flags &= ~ITSDONE;
goto retry;
}
}
return( COMPLETE);
}
return(HAD_ERROR);
case XS_BUSY:
spinwait(1000);
if(scsi_xfer.retries--)
{
scsi_xfer.flags &= ~ITSDONE;
goto retry;
}
case XS_TIMEOUT:
default:
return(HAD_ERROR);
}
}
return(COMPLETE);
}
/***********************************************\
* Utility routines often used in SCSI stuff *
\***********************************************/
/***********************************************\
* convert a physical address to 3 bytes, *
* MSB at the lowest address, *
* LSB at the highest. *
\***********************************************/
lto3b(val, bytes)
u_char *bytes;
{
*bytes++ = (val&0xff0000)>>16;
*bytes++ = (val&0xff00)>>8;
*bytes = val&0xff;
}
/***********************************************\
* The reverse of lto3b *
\***********************************************/
_3btol(bytes)
u_char *bytes;
{
int rc;
rc = (*bytes++ << 16);
rc += (*bytes++ << 8);
rc += *bytes;
return(rc);
}