freebsd-skq/sys/scsi/scsiconf.c
Justin T. Gibbs 07b1c6b3c9 Remove hard coded assumption that SCSI busses have 7 targets.
This change forces the controller drivers to allocate a scsibus_data struct
via a call to scsi_alloc_bus(), fill in the adapter_link field, and optionally
modify any other fields of the struct.  Scsi_alloc_bus() initializes all fields
to the default, so the changes in most drivers are very minimal.  For drivers
that support Wide controllers, the maxtarg field will have to be updated to
allow probing of all targets (for an example, look at the aic7xxx driver).

Scsi_attachdevs() now takes a scsibus_data* as its argument instead of an
sc_link*.  This allows us to expand the role of the scsibus_data struct for
other bus level configuration setings (max number of transactions, current
transaction opennings, etc for better tagged queuing support).

Reviewed by: Rodney Grimes <rgrimes>, Peter Dufault <dufault>, Julian Elischer <julian>
1995-08-23 23:03:34 +00:00

1365 lines
31 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
*
* New configuration setup: dufault@hda.com
*
* $Id: scsiconf.c,v 1.33 1995/08/16 16:13:53 bde Exp $
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/stat.h>
#include <sys/malloc.h>
#include <sys/devconf.h>
#include <sys/conf.h>
#include <machine/clock.h>
#include <machine/cpu.h> /* XXX For bootverbose (funny place) */
#include "scbus.h"
#include "sd.h"
#include "st.h"
#include "cd.h"
#include "ch.h"
#include "su.h"
#include "sctarg.h"
#include <scsi/scsi_all.h>
#include <scsi/scsiconf.h>
/*
* XXX SCSI_DEVICE_ENTRIES() generates extern switches but it should
* generate static switches except for this. Separate macros are
* probably required for the extern and static parts.
*/
extern struct scsi_device uk_switch;
/* Extensible arrays: Use a realloc like implementation to permit
* the arrays to be extend. These are set up to be moved out
* of this file if needed elsewhere.
*/
struct extend_array
{
int nelem;
void **ps;
};
static errval scsi_attach_sctarg __P((void));
static void *extend_alloc(size_t s)
{
void *p = malloc(s, M_DEVBUF, M_NOWAIT);
if (!p)
panic("extend_alloc: malloc failed.");
return p;
}
static void extend_free(void *p) { free(p, M_DEVBUF); }
/* EXTEND_CHUNK: Number of extend slots to allocate whenever we need a new
* one.
*/
#ifndef EXTEND_CHUNK
#define EXTEND_CHUNK 8
#endif
struct extend_array *extend_new(void)
{
struct extend_array *p = extend_alloc(sizeof(*p));
if (p) {
p->nelem = 0;
p->ps = 0;
}
return p;
}
void *extend_set(struct extend_array *ea, int index, void *value)
{
if (index >= ea->nelem) {
void **space;
space = extend_alloc(sizeof(void *) * (index + EXTEND_CHUNK));
bzero(space, sizeof(void *) * (index + EXTEND_CHUNK));
/* Make sure we have something to copy before we copy it */
if (ea->nelem) {
bcopy(ea->ps, space, sizeof(void *) * ea->nelem);
extend_free(ea->ps);
}
ea->ps = space;
ea->nelem = index + EXTEND_CHUNK;
}
if (ea->ps[index]) {
printf("extend_set: entry %d already has storage.\n", index);
return 0;
}
else
ea->ps[index] = value;
return value;
}
void *extend_get(struct extend_array *ea, int index)
{
if (ea == NULL || index >= ea->nelem || index < 0)
return NULL;
return ea->ps[index];
}
void extend_release(struct extend_array *ea, int index)
{
void *p = extend_get(ea, index);
if (p) {
ea->ps[index] = 0;
}
}
/*
* This extend_array holds an array of "scsibus_data" pointers.
* One of these is allocated and filled in for each scsi bus.
* it holds pointers to allow the scsi bus to get to the driver
* that is running each LUN on the bus
* it also has a template entry which is the prototype struct
* supplied by the adapter driver, this is used to initialise
* the others, before they have the rest of the fields filled in
*/
struct extend_array *scbusses;
/*
* The structure of known drivers for autoconfiguration
*/
struct scsidevs {
u_int32 type;
boolean removable;
char *manufacturer;
char *model;
char *version;
char *devname;
char flags; /* 1 show my comparisons during boot(debug) */
#ifdef NEW_SCSICONF
u_int16 quirks;
void *devmodes;
#endif
};
#define SC_SHOWME 0x01
#define SC_ONE_LU 0x00
#define SC_MORE_LUS 0x02
static struct scsidevs unknowndev =
{
T_UNKNOWN, 0, "*", "*", "*",
"uk", SC_MORE_LUS
};
#ifdef NEW_SCSICONF
static st_modes mode_tandberg3600 =
{
{0, 0, 0}, /* minor 0,1,2,3 */
{0, ST_Q_FORCE_VAR_MODE, QIC_525}, /* minor 4,5,6,7 */
{0, 0, QIC_150}, /* minor 8,9,10,11 */
{0, 0, QIC_120} /* minor 12,13,14,15 */
};
static st_modes mode_archive2525 =
{
{0, ST_Q_SNS_HLP, 0}, /* minor 0,1,2,3 */
{0, ST_Q_SNS_HLP, QIC_525}, /* minor 4,5,6,7 */
{0, 0, QIC_150}, /* minor 8,9,10,11 */
{0, 0, QIC_120} /* minor 12,13,14,15 */
};
static st_modes mode_archive150 =
{
{0, 0, 0}, /* minor 0,1,2,3 */
{0, 0, QIC_150}, /* minor 4,5,6,7 */
{0, 0, QIC_120}, /* minor 8,9,10,11 */
{0, 0, QIC_24} /* minor 12,13,14,15 */
};
static st_modes mode_wangtek5525 =
{
{0, 0, 0}, /* minor 0,1,2,3 */
{0, ST_Q_BLKSIZ, QIC_525}, /* minor 4,5,6,7 */
{0, 0, QIC_150}, /* minor 8,9,10,11 */
{0, 0, QIC_120} /* minor 12,13,14,15 */
};
static st_modes mode_wangdat1300 =
{
{0, 0, 0}, /* minor 0,1,2,3 */
{512, ST_Q_FORCE_FIXED_MODE, DDS}, /* minor 4,5,6,7 */
{1024, ST_Q_FORCE_FIXED_MODE, DDS}, /* minor 8,9,10,11 */
{0, ST_Q_FORCE_VAR_MODE, DDS} /* minor 12,13,14,15 */
};
static st_modes mode_unktape =
{
{512, ST_Q_FORCE_FIXED_MODE, 0}, /* minor 0,1,2,3 */
{512, ST_Q_FORCE_FIXED_MODE, QIC_24}, /* minor 4,5,6,7 */
{0, ST_Q_FORCE_VAR_MODE, HALFINCH_1600}, /* minor 8,9,10,11 */
{0, ST_Q_FORCE_VAR_MODE, HALFINCH_6250} /* minor 12,13,14,15 */
};
#endif /* NEW_SCSICONF */
static struct scsidevs knowndevs[] =
#ifdef NEW_SCSICONF
{
#if NSD > 0
{
T_DIRECT, T_FIXED, "MAXTOR", "XT-4170S", "B5A",
"mx1", SC_ONE_LU
},
{
T_DIRECT, T_FIXED, "*", "*", "*",
"sd", SC_ONE_LU
},
#endif /* NSD */
#if NST > 0
{
T_SEQUENTIAL, T_REMOV, "TANDBERG", " TDC 3600", "*",
"st", SC_ONE_LU, ST_Q_NEEDS_PAGE_0, mode_tandberg3600
},
{
T_SEQUENTIAL, T_REMOV, "ARCHIVE", "VIPER 2525*", "-005",
"st", SC_ONE_LU, 0, mode_archive2525
},
{
T_SEQUENTIAL, T_REMOV, "ARCHIVE", "VIPER 150", "*",
"st", SC_ONE_LU, ST_Q_NEEDS_PAGE_0, mode_archive150
},
{
T_SEQUENTIAL, T_REMOV, "WANGTEK", "5525ES*", "*",
"st", SC_ONE_LU, 0, mode_wangtek5525
},
{
T_SEQUENTIAL, T_REMOV, "WangDAT", "Model 1300", "*",
"st", SC_ONE_LU, 0, mode_wangdat1300
},
{
T_SEQUENTIAL, T_REMOV, "*", "*", "*",
"st", SC_ONE_LU, 0, mode_unktape
},
#endif /* NST */
#if NCH > 0
{
T_CHANGER, T_REMOV, "*", "*", "*",
"ch", SC_ONE_LU
},
#endif /* NCH */
#if NCD > 0
#ifndef UKTEST /* make cdroms unrecognised to test the uk driver */
{
T_READONLY, T_REMOV, "SONY", "CD-ROM CDU-8012", "3.1a",
"cd", SC_ONE_LU
},
{
T_READONLY, T_REMOV, "PIONEER", "CD-ROM DRM-600", "*",
"cd", SC_MORE_LUS
},
{
T_READONLY, T_REMOV, "PIONEER", "CD-ROM DRM-602X" ,"*",
"cd", SC_MORE_LUS
},
{
T_READONLY, T_REMOV, "CHINON", "CD-ROM CDS-535","*",
"cd", SC_ONE_LU
},
#endif
#endif /* NCD */
{
0
}
};
#else
{
#if NSD > 0
{
T_DIRECT, T_FIXED, "standard", "any"
,"any", "sd", SC_ONE_LU
},
{
T_DIRECT, T_FIXED, "MAXTOR ", "XT-4170S "
,"B5A ", "mx1", SC_ONE_LU
},
#endif /* NSD */
#if NST > 0
{
T_SEQUENTIAL, T_REMOV, "standard", "any"
,"any", "st", SC_ONE_LU
},
#endif /* NST */
#if NCH > 0
{
T_CHANGER, T_REMOV, "standard", "any"
,"any", "ch", SC_ONE_LU
},
#endif /* NCH */
#if NCD > 0
#ifndef UKTEST /* make cdroms unrecognised to test the uk driver */
{
T_READONLY, T_REMOV, "SONY", "CD-ROM CDU-8012"
,"3.1a", "cd", SC_ONE_LU
},
{
T_READONLY, T_REMOV, "PIONEER", "CD-ROM DRM-600"
,"any", "cd", SC_MORE_LUS
},
{
T_READONLY, T_REMOV, "PIONEER", "CD-ROM DRM-602X"
,"any", "cd", SC_MORE_LUS
},
{
T_READONLY, T_REMOV, "CHINON", "CD-ROM CDS-535"
,"any", "cd", SC_ONE_LU
},
#endif
#endif /* NCD */
{
0
}
};
#endif /* NEW_SCSICONF */
/*
* Declarations
*/
struct scsidevs *scsi_probedev();
struct scsidevs *scsi_selectdev();
/* XXX dufault@hda.com
* This scsi_device doesn't have the scsi_data_size.
* This is used during probe.
*/
struct scsi_device probe_switch =
{
NULL,
NULL,
NULL,
NULL,
"probe",
0,
{0, 0},
NULL,
0
};
/*
* XXX
* This is BOGUS.
* We do this because it was easier than adding the requisite information
* to the scsi_link structure and modifying everything to use that.
* Someday, we will do just that, and users will be able to nail down their
* preferred SCSI ids.
*
*/
struct kern_devconf kdc_scbus0 = {
0, 0, 0, /* filled in by dev_attach */
"scbus", 0, MDDC_SCBUS,
0, 0, 0, 0, /* no external data */
0, /* no parent */
0, /* no parentdata */
DC_BUSY, /* busses are always busy */
"SCSI subsystem"
};
static int free_bus; /* First bus not wired down */
extern void ukinit();
static struct scsi_device *device_list;
static int next_free_type = T_NTYPES;
/* Register new functions at the head of the list. That allows
* you to replace a standard driver with a new one.
*
* You can't register the exact device (the same in memory structure)
* more than once - the list links are part of the structure. That is
* prevented.
*
* Custom devices should always be registered as type "-1". Then
* the next available type number will be allocated for it.
*
* Be careful not to register a type as 0 unless you really mean to
* replace the disk driver.
*
* This is usually called only by the "device_init" function generated
* automatically in the SCSI_DEVICE_ENTRIES macro.
*/
void
scsi_device_register(struct scsi_device *sd)
{
/* Not only is it pointless to add the same device more than once
* but it will also screw up the list.
*/
struct scsi_device *is_there;
for (is_there = device_list; is_there; is_there = is_there->next)
if (is_there == sd)
return;
if (sd->type == -1)
sd->type = next_free_type++;
sd->next = device_list;
device_list = sd;
if (sd->links == 0)
sd->links = extend_new();
}
static struct scsi_device *
scsi_device_lookup(int type)
{
struct scsi_device *sd;
for (sd = device_list; sd; sd = sd->next)
if (sd->type == type)
return sd;
return &uk_switch;
}
static struct scsi_device *
scsi_device_lookup_by_name(char *name)
{
struct scsi_device *sd;
for (sd = device_list; sd; sd = sd->next)
if (strcmp(sd->name, name) == 0)
return sd;
return &uk_switch;
}
/* Macro that lets us know something is specified.
*/
#define IS_SPECIFIED(ARG) (ARG != SCCONF_UNSPEC && ARG != SCCONF_ANY)
/* scsi_init: Do all the one time processing. This initializes the
* type drivers and initializes the configuration.
*/
static void
scsi_init(void)
{
static int done = 0;
if(!done) {
int i;
done = 1;
scbusses = extend_new();
dev_attach(&kdc_scbus0);
/* First call all type initialization functions.
*/
ukinit(); /* We always have the unknown device. */
for (i = 0; scsi_tinit[i]; i++)
(*scsi_tinit[i])();
/* Lowest free bus for auto-configure is one
* more than the first one not
* specified in config:
*/
for (i = 0; scsi_cinit[i].driver; i++)
if (IS_SPECIFIED(scsi_cinit[i].scbus) &&
free_bus <= scsi_cinit[i].scbus)
free_bus = scsi_cinit[i].scbus + 1;
/* Lowest free unit for each type for auto-configure is one
* more than the first one not specified in the config file:
*/
for (i = 0; scsi_dinit[i].name; i++) {
struct scsi_device_config *sdc = scsi_dinit + i;
struct scsi_device *sd =
scsi_device_lookup_by_name(sdc->name);
/* This is a little tricky: We don't want "sd 4" to match as
* a wired down device, but we do want "sd 4 target 5" or
* even "sd 4 scbus 1" to match.
*/
if (IS_SPECIFIED(sdc->unit) &&
(IS_SPECIFIED(sdc->target) || IS_SPECIFIED(sdc->cunit)) &&
sd->free_unit <= sdc->unit)
sd->free_unit = sdc->unit + 1;
}
}
}
/* scsi_bus_conf: Figure out which bus this is. If it is wired in config
* use that. Otherwise use the next free one.
*/
static int
scsi_bus_conf(sc_link_proto)
struct scsi_link *sc_link_proto;
{
int i;
int bus;
/* Which bus is this? Try to find a match in the "scsi_cinit"
* table. If it isn't wired down auto-configure it at the
* next available bus.
*/
bus = SCCONF_UNSPEC;
for (i = 0; scsi_cinit[i].driver; i++) {
if (IS_SPECIFIED(scsi_cinit[i].scbus))
{
if (!strcmp(sc_link_proto->adapter->name, scsi_cinit[i].driver)
&&(sc_link_proto->adapter_unit == scsi_cinit[i].unit))
{
if (IS_SPECIFIED(scsi_cinit[i].bus)) {
if (sc_link_proto->adapter_bus==scsi_cinit[i].bus){
bus = scsi_cinit[i].scbus;
break;
}
}
else if (sc_link_proto->adapter_bus == 0) {
/* Backwards compatibility for single bus cards */
bus = scsi_cinit[i].scbus;
break;
}
else {
printf("Ambiguous scbus configuration for %s%d "
"bus %d, cannot wire down. The kernel "
"config entry for scbus%d should specify "
"a controller bus.\n"
"Scbus will be assigned dynamically.\n",
sc_link_proto->adapter->name,
sc_link_proto->adapter_unit,
sc_link_proto->adapter_bus);
break;
}
}
}
}
if (bus == SCCONF_UNSPEC)
bus = free_bus++;
else if (bootverbose)
printf("Choosing drivers for scbus configured at %d\n", bus);
return bus;
}
/* scsi_assign_unit: Look through the structure generated by config.
* See if there is a fixed assignment for this unit. If there isn't,
* assign the next free unit.
*/
static int
scsi_assign_unit(struct scsi_link *sc_link)
{
int i;
int found;
found = 0;
for (i = 0; scsi_dinit[i].name; i++) {
if ((strcmp(sc_link->device->name, scsi_dinit[i].name) == 0) &&
sc_link->target == scsi_dinit[i].target &&
(
(sc_link->lun == scsi_dinit[i].lun) ||
(sc_link->lun == 0 && scsi_dinit[i].lun == SCCONF_UNSPEC)
) &&
sc_link->scsibus == scsi_dinit[i].cunit) {
sc_link->dev_unit = scsi_dinit[i].unit;
found = 1;
if (bootverbose)
printf("%s is configured at %d\n",
sc_link->device->name, sc_link->dev_unit);
break;
}
}
if (!found)
sc_link->dev_unit = sc_link->device->free_unit++;
return sc_link->dev_unit;
}
#if NSCTARG > 0
/* The SCSI target configuration is simpler. If an entry is present
* we just return the bus, target and lun for that unit.
*/
static void
scsi_sctarg_lookup(char *name, int unit, int *target, int *lun, int *bus)
{
int i;
*bus = SCCONF_UNSPEC;
*target = SCCONF_UNSPEC;
*lun = SCCONF_UNSPEC;
for (i = 0; scsi_dinit[i].name; i++) {
if ((strcmp(name, scsi_dinit[i].name) == 0) &&
unit == scsi_dinit[i].unit)
{
*bus = scsi_dinit[i].cunit;
*target = scsi_dinit[i].target;
*lun = scsi_dinit[i].lun;
}
}
}
#endif /* NSCTARG > 0 */
void scsi_configure_start(void)
{
scsi_init();
}
void scsi_configure_finish(void)
{
#if NSCTARG > 0
scsi_attach_sctarg();
#endif
}
/*
* scsi_attachdevs is the routine called by the adapter boards
* to get all their devices configured in.
*/
void
scsi_attachdevs(scbus)
struct scsibus_data *scbus;
{
int scsibus;
struct scsi_link *sc_link_proto = scbus->adapter_link;
if ( (scsibus = scsi_bus_conf(sc_link_proto)) == -1) {
return;
}
/*
* if the adapter didn't give us this, set a default
* (compatibility with old adapter drivers)
*/
if(!(sc_link_proto->opennings)) {
sc_link_proto->opennings = 1;
}
sc_link_proto->scsibus = scsibus;
/*
* Allocate our target-lun space.
*/
scbus->sc_link = (struct scsi_link *(*)[][8])malloc(
sizeof(struct scsi_link *[scbus->maxtarg + 1][8]),
M_TEMP, M_NOWAIT);
if(scbus == 0 || scbus->sc_link == 0
|| extend_set(scbusses, scsibus, scbus) == 0) {
panic("scsi_attachdevs: malloc");
}
bzero(scbus->sc_link, sizeof(struct scsi_link*[scbus->maxtarg + 1][8]));
#if defined(SCSI_DELAY) && SCSI_DELAY > 2
printf("%s%d waiting for scsi devices to settle\n",
sc_link_proto->adapter->name, sc_link_proto->adapter_unit);
#else /* SCSI_DELAY > 2 */
#undef SCSI_DELAY
#define SCSI_DELAY 2
#endif /* SCSI_DELAY */
DELAY(1000000 * SCSI_DELAY);
scsi_probe_bus(scsibus,-1,-1);
}
/*
* Probe the requested scsi bus. It must be already set up.
* -1 requests all set up scsi busses.
* targ and lun optionally narrow the search if not -1
*/
errval
scsi_probe_busses(int bus, int targ, int lun)
{
if (bus == -1) {
for(bus = 0; bus < scbusses->nelem; bus++) {
scsi_probe_bus(bus, targ, lun);
}
return 0;
} else {
return scsi_probe_bus(bus, targ, lun);
}
}
/* scsi_alloc_unit: Register a scsi_data pointer for a given
* unit in a given scsi_device structure.
*
* XXX dufault@hda.com: I still don't like the way this reallocs stuff -
* but at least now it is collected in one place instead of existing
* in multiple type drivers. I'd like it better if we had it do a
* second pass after it knew the sizes of everything and set up everything
* at once.
*/
static int
scsi_alloc_unit(struct scsi_link *sc_link)
{
u_int32 unit;
struct scsi_data *sd;
struct scsi_device *dsw;
unit = sc_link->dev_unit;
dsw = sc_link->device;
/*
* allocate the per unit data area
*/
if (dsw->sizeof_scsi_data)
{
sd = malloc(dsw->sizeof_scsi_data, M_DEVBUF, M_NOWAIT);
if (!sd) {
printf("%s%ld: malloc failed for scsi_data\n",
sc_link->device->name, unit);
return 0;
}
bzero(sd, dsw->sizeof_scsi_data);
}
else
sd = 0;
sc_link->sd = sd;
if (extend_set(dsw->links, unit, (void *)sc_link) == 0) {
printf("%s%ld: Can't store link pointer.\n",
sc_link->device->name, unit);
free(sd, M_DEVBUF);
return 0;
}
return 1;
}
static void
scsi_free_unit(struct scsi_link *sc_link)
{
if (sc_link->sd)
{
free(sc_link->sd, M_DEVBUF);
sc_link->sd = 0;
}
extend_release(sc_link->device->links, sc_link->dev_unit);
}
#if NSCTARG > 0
/* XXX: It is a bug that the sc_link has this information
* about the adapter in it. The sc_link should refer to
* a structure that is host adpater specific. That will also
* pull all knowledge of an sc_link out of the adapter drivers.
*/
errval
scsi_set_bus(int bus, struct scsi_link *sc_link)
{
struct scsi_link *ad_link;
struct scsibus_data *scsibus_data;
if (bus < 0 || bus > scbusses->nelem) {
return ENXIO;
}
scsibus_data = (struct scsibus_data *)extend_get(scbusses, bus);
if(!scsibus_data) {
return ENXIO;
}
ad_link = scsibus_data->adapter_link;
sc_link->adapter_unit = ad_link->adapter_unit;
sc_link->adapter_targ = ad_link->adapter_targ;
sc_link->adapter = ad_link->adapter;
sc_link->device = ad_link->device;
sc_link->flags = ad_link->flags;
return 0;
}
/*
* Allocate and attach as many SCSI target devices as configured.
* There are two ways that you can configure the target device:
* 1. In the configuration file. That is handled here.
* 2. Via the minor number. That takes precedence over the config file.
*/
static errval
scsi_attach_sctarg()
{
struct scsi_link *sc_link = NULL;
int dev_unit;
struct scsi_device *sctarg = scsi_device_lookup(T_TARGET);
if (sctarg == 0) {
return ENXIO;
}
for (dev_unit = 0; dev_unit < NSCTARG; dev_unit++) {
int target, lun, bus;
/* If we don't have a link block allocate one.
*/
if (!sc_link) {
sc_link = malloc(sizeof(*sc_link), M_TEMP, M_NOWAIT);
}
scsi_sctarg_lookup(sctarg->name, dev_unit, &target, &lun, &bus);
if (IS_SPECIFIED(bus)) {
struct scsibus_data *scsibus_data;
if (bus < 0 || bus > scbusses->nelem) {
printf("%s%d: configured on illegal bus %d.\n",
sctarg->name, dev_unit, bus);
continue;
}
scsibus_data = (struct scsibus_data *)extend_get(scbusses, bus);
if(!scsibus_data) {
printf("%s%d: no bus %d.\n", sctarg->name, dev_unit, bus);
continue;
}
*sc_link = *scsibus_data->adapter_link; /* struct copy */
sc_link->target = target;
sc_link->lun = lun;
}
else {
/* This will be configured in the open routine.
*/
sc_link->scsibus = SCCONF_UNSPEC;
sc_link->target = SCCONF_UNSPEC;
sc_link->lun = SCCONF_UNSPEC;
}
sc_link->quirks = 0;
sc_link->device = sctarg;
sc_link->dev_unit = dev_unit;
if (scsi_alloc_unit(sc_link)) {
if (scsi_device_attach(sc_link) == 0) {
sc_link = NULL; /* it's been used */
}
else
scsi_free_unit(sc_link);
}
}
if (sc_link) {
free(sc_link, M_TEMP);
}
return 0;
}
#endif /* NSCTARG > 0 */
/*
* Allocate a scsibus_data structure
* The target/lun area is dynamically allocated in scsi_attachdevs after
* the controller driver has a chance to update the maxtarg field.
*/
struct scsibus_data*
scsi_alloc_bus()
{
struct scsibus_data *scbus;
/*
* Prepare the scsibus_data area for the upperlevel
* scsi code.
*/
scbus = malloc(sizeof(struct scsibus_data), M_TEMP, M_NOWAIT);
if(!scbus) {
printf("scsi_alloc_bus: - cannot malloc!\n");
return NULL;
}
bzero(scbus, sizeof(struct scsibus_data));
/* Setup the defaults */
scbus->maxtarg = 7;
scbus->maxlun = 7;
return scbus;
}
/*
* Probe the requested scsi bus. It must be already set up.
* targ and lun optionally narrow the search if not -1
*/
errval
scsi_probe_bus(int bus, int targ, int lun)
{
struct scsibus_data *scsibus_data ;
int maxtarg,mintarg,maxlun,minlun;
struct scsi_link *sc_link_proto;
u_int8 scsi_addr ;
struct scsidevs *bestmatch = NULL;
struct scsi_link *sc_link = NULL;
boolean maybe_more;
int type;
if ((bus < 0 ) || ( bus >= scbusses->nelem)) {
return ENXIO;
}
scsibus_data = (struct scsibus_data *)extend_get(scbusses, bus);
if(!scsibus_data) return ENXIO;
sc_link_proto = scsibus_data->adapter_link;
scsi_addr = sc_link_proto->adapter_targ;
if(targ == -1){
maxtarg = scsibus_data->maxtarg;
mintarg = 0;
} else {
if((targ < 0 ) || (targ > scsibus_data->maxtarg)) return EINVAL;
maxtarg = mintarg = targ;
}
if(lun == -1){
maxlun = scsibus_data->maxlun;
minlun = 0;
} else {
if((lun < 0 ) || (lun > scsibus_data->maxlun)) return EINVAL;
maxlun = minlun = lun;
}
for ( targ = mintarg;targ <= maxtarg; targ++) {
maybe_more = 0; /* by default only check 1 lun */
if (targ == scsi_addr) {
continue;
}
for ( lun = minlun; lun <= maxlun ;lun++) {
/*
* The spot appears to already have something
* linked in, skip past it. Must be doing a 'reprobe'
*/
if((*scsibus_data->sc_link)[targ][lun])
{/* don't do this one, but check other luns */
maybe_more = 1;
continue;
}
/*
* If we presently don't have a link block
* then allocate one to use while probing
*/
if (!sc_link) {
sc_link = malloc(sizeof(*sc_link), M_TEMP, M_NOWAIT);
}
*sc_link = *sc_link_proto; /* struct copy */
sc_link->device = &probe_switch;
sc_link->target = targ;
sc_link->lun = lun;
sc_link->quirks = 0;
bestmatch = scsi_probedev(sc_link, &maybe_more, &type);
#ifdef NEW_SCSICONF
if (bestmatch) {
sc_link->quirks = bestmatch->quirks;
sc_link->devmodes = bestmatch->devmodes;
} else {
sc_link->quirks = 0;
sc_link->devmodes = NULL;
}
#endif
if (bestmatch) { /* FOUND */
sc_link->device = scsi_device_lookup(type);
(void)scsi_assign_unit(sc_link);
if (scsi_alloc_unit(sc_link)) {
if (scsi_device_attach(sc_link) == 0) {
(*scsibus_data->sc_link)[targ][lun] = sc_link;
sc_link = NULL; /* it's been used */
}
else
scsi_free_unit(sc_link);
}
}
if (!(maybe_more)) { /* nothing suggests we'll find more */
break; /* nothing here, skip to next targ */
}
/* otherwise something says we should look further */
}
}
if (sc_link) {
free(sc_link, M_TEMP);
}
return 0;
}
/* Return the scsi_link for this device, if any.
*/
struct scsi_link *
scsi_link_get(bus, targ, lun)
int bus;
int targ;
int lun;
{
struct scsibus_data *scsibus_data =
(struct scsibus_data *)extend_get(scbusses, bus);
return (scsibus_data) ? (*scsibus_data->sc_link)[targ][lun] : 0;
}
/* make_readable: Make the inquiry data readable. Anything less than a ' '
* is made a '?' and trailing spaces are removed.
*/
static void
make_readable(to, from, n)
char *to;
char *from;
size_t n;
{
int i;
for (i = 0; from[i] && i < n - 1; i++) {
if (from[i] < ' ')
to[i]='?';
else
to[i] = from[i];
}
while (i && to[i - 1] == ' ')
i--;
to[i] = 0;
}
/*
* given a target and lu, ask the device what
* it is, and find the correct driver table
* entry.
*/
struct scsidevs *
scsi_probedev(sc_link, maybe_more, type_p)
boolean *maybe_more;
struct scsi_link *sc_link;
int *type_p;
{
u_int8 target = sc_link->target;
u_int8 lu = sc_link->lun;
struct scsidevs *bestmatch = (struct scsidevs *) 0;
int dtype = 0;
char *desc;
char *qtype;
struct scsi_inquiry_data *inqbuf;
u_int32 len, qualifier, type;
boolean remov;
char manu[8 + 1];
char model[16 + 1];
char version[4 + 1];
inqbuf = &sc_link->inqbuf;
bzero(inqbuf, sizeof(*inqbuf));
/*
* Ask the device what it is
*/
#ifdef SCSIDEBUG
if ((target == DEBUGTARG) && (lu == DEBUGLUN))
sc_link->flags |= (DEBUGLEVEL);
else
sc_link->flags &= ~(SDEV_DB1 | SDEV_DB2 | SDEV_DB3 | SDEV_DB4);
#endif /* SCSIDEBUG */
/* catch unit attn */
scsi_test_unit_ready(sc_link, SCSI_NOSLEEP | SCSI_NOMASK | SCSI_SILENT);
#ifdef DOUBTFULL
switch (scsi_test_unit_ready(sc_link, SCSI_NOSLEEP | SCSI_NOMASK | SCSI_SILENT)) {
case 0: /* said it WAS ready */
case EBUSY: /* replied 'NOT READY' but WAS present, continue */
case ENXIO:
break;
case EIO: /* device timed out */
case EINVAL: /* Lun not supported */
default:
return (struct scsidevs *) 0;
}
#endif /*DOUBTFULL*/
#ifdef SCSI_2_DEF
/* some devices need to be told to go to SCSI2 */
/* However some just explode if you tell them this.. leave it out */
scsi_change_def(sc_link, SCSI_NOSLEEP | SCSI_NOMASK | SCSI_SILENT);
#endif /*SCSI_2_DEF */
/* Now go ask the device all about itself */
if (scsi_inquire(sc_link, inqbuf, SCSI_NOSLEEP | SCSI_NOMASK) != 0) {
return (struct scsidevs *) 0;
}
/*
* note what BASIC type of device it is
*/
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 will match in the default of this switch.
*/
switch ((int)qualifier) {
case SID_QUAL_LU_OK:
qtype = "";
break;
case SID_QUAL_LU_OFFLINE:
qtype = "Supported device currently not connected";
break;
case SID_QUAL_RSVD: /* Peripheral qualifier reserved in SCSI-2 spec */
*maybe_more = 1;
return (struct scsidevs *) 0;
case SID_QUAL_BAD_LU: /* Target can not support a device on this unit */
/*
* Check for a non-existent unit. If the device is returning
* this much, then we must set the flag that has
* the searchers keep looking on other luns.
*/
*maybe_more = 1;
return (struct scsidevs *) 0;
default:
dtype = 1;
qtype = "Vendor specific peripheral qualifier";
*maybe_more = 1;
break;
}
if (dtype == 0) {
if (type == T_NODEVICE) {
*maybe_more = 1;
return (struct scsidevs *) 0;
}
dtype = 1;
}
/*
* 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;
make_readable(manu, inqbuf->vendor, sizeof(manu));
make_readable(model, inqbuf->product, sizeof(model));
make_readable(version, inqbuf->revision, sizeof(version));
} else {
/*
* If not advanced enough, use default values
*/
desc = "early protocol device";
make_readable(manu, "unknown", sizeof(manu));
make_readable(model, "unknown", sizeof(model));
make_readable(version, "????", sizeof(version));
}
sc_print_start(sc_link);
printf("\"%s %s %s\" ", manu, model, version );
printf("type %ld %sSCSI %d"
,type
,remov ? "removable " : "fixed "
,inqbuf->version & SID_ANSII
);
if (qtype[0]) {
sc_print_addr(sc_link);
printf(" qualifier %ld: %s" ,qualifier ,qtype);
}
printf("\n");
sc_print_finish();
/*
* Try make as good a match as possible with
* available sub drivers
*/
bestmatch = (scsi_selectdev(
qualifier, type, remov ? T_REMOV : T_FIXED, manu, model, version));
if ((bestmatch) && (bestmatch->flags & SC_MORE_LUS)) {
*maybe_more = 1;
}
/* If the device is unknown then we should be trying to look up a
* type driver based on the inquiry type.
*/
if (bestmatch == &unknowndev)
*type_p = type;
else
*type_p = bestmatch->type;
return bestmatch;
}
/* Try to find the major number for a device during attach.
*/
dev_t
scsi_dev_lookup(d_open)
int (*d_open)();
{
int i;
dev_t d = NODEV;
for (i = 0; i < nchrdev; i++)
if (cdevsw[i].d_open == d_open)
{
d = makedev(i, 0);
break;
}
return d;
}
#ifdef NEW_SCSICONF
/*
* Compare name with pattern, return 0 on match.
* Short pattern matches trailing blanks in name,
* wildcard '*' in pattern matches rest of name
*/
int
match(pattern, name)
char *pattern;
char *name;
{
char c;
while (c = *pattern++)
{
if (c == '*') return 0;
if (c != *name++) return 1;
}
while (c = *name++)
{
if (c != ' ') return 1;
}
return 0;
}
#endif
/*
* Try make as good a match as possible with
* available sub drivers
*/
struct scsidevs *
scsi_selectdev(qualifier, type, remov, manu, model, rev)
u_int32 qualifier, type;
boolean remov;
char *manu, *model, *rev;
{
#ifdef NEW_SCSICONF
struct scsidevs *bestmatch = NULL;
struct scsidevs *thisentry;
type |= qualifier; /* why? */
for ( thisentry = knowndevs; thisentry->manufacturer; thisentry++ )
{
if (type != thisentry->type) {
continue;
}
if (remov != thisentry->removable) {
continue;
}
if (thisentry->flags & SC_SHOWME)
printf("\n%s-\n%s-", thisentry->manufacturer, manu);
if (match(thisentry->manufacturer, manu)) {
continue;
}
if (thisentry->flags & SC_SHOWME)
printf("\n%s-\n%s-", thisentry->model, model);
if (match(thisentry->model, model)) {
continue;
}
if (thisentry->flags & SC_SHOWME)
printf("\n%s-\n%s-", thisentry->version, rev);
if (match(thisentry->version, rev)) {
continue;
}
bestmatch = thisentry;
break;
}
#else
u_int32 numents = (sizeof(knowndevs) / sizeof(struct scsidevs)) - 1;
u_int32 count = 0;
u_int32 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("'%s'-'%s'\n", thisentry->manufacturer, manu);
if (strcmp(thisentry->manufacturer, manu)) {
continue;
}
if (bestmatches < 3) {
bestmatches = 3;
bestmatch = thisentry;
}
if (thisentry->flags & SC_SHOWME)
printf("'%s'-'%s'\n", thisentry->model, model);
if (strcmp(thisentry->model, model)) {
continue;
}
if (bestmatches < 4) {
bestmatches = 4;
bestmatch = thisentry;
}
if (thisentry->flags & SC_SHOWME)
printf("'%s'-'%s'\n", thisentry->version, rev);
if (strcmp(thisentry->version, rev)) {
continue;
}
if (bestmatches < 5) {
bestmatches = 5;
bestmatch = thisentry;
break;
}
}
#endif /* NEW_SCSICONF */
if (bestmatch == (struct scsidevs *) 0) {
bestmatch = &unknowndev;
}
return (bestmatch);
}
int
scsi_externalize(struct scsi_link *sl, void *userp, size_t *lenp)
{
if(*lenp < sizeof *sl)
return ENOMEM;
*lenp -= sizeof *sl;
return copyout(sl, userp, sizeof *sl);
}