a7a2c30d57cc6e4616785e20bcc5fd6a0df7a5d5
* Copyright (c) 1992 The Regents of the University of California.
* This software was developed by the Computer Systems Engineering group
* at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
* contributed to Berkeley.
* All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Lawrence Berkeley Laboratories.
* %sccs.include.redist.c%
* @(#)scsi_subr.c 5.5 (Berkeley) %G%
* from: $Header: scsi_subr.c,v 1.10 93/02/01 19:21:58 torek Exp $ (LBL)
* Generic SCSI host adapter driver.
* Does almost nothing (most work is relegated to per-hba drivers).
#include <dev/scsi/scsi.h>
#include <dev/scsi/scsivar.h>
* General subroutines, and scsi data.
/* table of lengths of scsi commands */
const char scsicmdlen
[8] = { 6, 10, 0, 0, 0, 12, 0, 0 };
/* table of lengths of scsi messages */
const signed char scsimsglen
[0x24] = {
SMLEN_DONE
, /* MSG_CMD_COMPLETE */
SMLEN_EXTENDED
, /* MSG_EXT_MESSAGE */
1, /* MSG_SAVE_DATA_PTR */
1, /* MSG_INIT_DETECT_ERROR */
1, /* MSG_PARITY_ERROR */
1, /* MSG_BUS_DEVICE_RESET */
1, /* MSG_INITIATE_RECOVERY */
1, /* MSG_RELEASE_RECOVERY */
1, /* MSG_TERMINATE_PROCESS */
2, /* MSG_ORDERED_QTAG */
2, /* MSG_IGNORE_WIDE_RESID */
/* definition of `tg' target driver for autoconfig */
static int scsi_targmatch
__P((struct device
*, struct cfdata
*, void *));
static void scsi_targattach
__P((struct device
*, struct device
*, void *));
{ NULL
, "tg", scsi_targmatch
, scsi_targattach
,
DV_DULL
, sizeof(struct targ
) };
void scsi_targstart
__P((struct device
*, struct sq
*, struct buf
*,
scdgo_fn
, struct device
*));
int scsi_targgo
__P((struct device
*, int targ
,
scintr_fn
, struct device
*, struct buf
*, int));
void scsi_targintr
__P((struct device
*, int, int));
void scsi_targrel
__P((struct device
*));
#define NOBUF ((caddr_t)0)
* Perform a TEST UNIT READY immediate (polled) command
* on the given <target,unit> pair. Return the status byte
* returned, or -1 for none.
scsi_test_unit_ready(hba
, targ
, unit
)
CDB6(&cdb
)->cdb_cmd
= CMD_TEST_UNIT_READY
;
CDB6(&cdb
)->cdb_lun_lbah
= unit
<< 5;
*(short *)&CDB6(&cdb
)->cdb_lbam
= 0;
*(short *)&CDB6(&cdb
)->cdb_len
= 0;
return (hba
->hba_driver
->hd_icmd(hba
, targ
, &cdb
, NOBUF
, 0, 0));
* Request sense. The sense is to be written into the given buffer.
* The given length must be < 256.
scsi_request_sense(hba
, targ
, unit
, buf
, len
)
CDB6(&cdb
)->cdb_cmd
= CMD_REQUEST_SENSE
;
CDB6(&cdb
)->cdb_lun_lbah
= unit
<< 5;
*(short *)&CDB6(&cdb
)->cdb_lbam
= 0;
CDB6(&cdb
)->cdb_len
= len
;
CDB6(&cdb
)->cdb_ctrl
= 0;
return (hba
->hba_driver
->hd_icmd(hba
, targ
, &cdb
, buf
, len
, B_READ
));
* Called (indirectly, via config_found) from scsi_hbaattach.
* Print target number, and if no device was configured there,
printf("target %d on %s", *(int *)aux
, hba
);
printf(" target %d", *(int *)aux
);
* Print information about a unit found on some target.
* If the unit was not configured, `targ' is the name of the target
* on which the unit was found. If it was, targ is NULL and we
* let the unit's attach routine print the INQUIRE result if
scsi_unitprint(aux
, targ
)
register struct scsi_attach_args
*sa
= aux
;
printf("unit %d at %s", sa
->sa_unit
, targ
);
if ((sa
->sa_inq_status
& STS_MASK
) == STS_GOOD
) {
scsi_printinq(&sa
->sa_si
);
printf(" unit %d", sa
->sa_unit
);
scsi_targmatch(parent
, cf
, aux
)
register struct cfdata
*cf
;
return (cf
->cf_loc
[0] == targ
|| cf
->cf_loc
[0] == -1);
* And now, a generic `target attach' routine.
* We assume that INQUIRY works.
scsi_targattach(parent
, self
, aux
)
struct device
*parent
, *self
;
register struct targ
*t
= (struct targ
*)self
;
register struct hba_softc
*hba
;
register struct hbadriver
*hd
;
struct scsi_attach_args sa
;
t
->t_targ
= targ
= *(int *)aux
;
hba
= (struct hba_softc
*)parent
;
hba
->hba_targets
[targ
] = t
;
* Probe each of the 8 units using the sequence
* The first should not be necessary, but some SCSI devices
* refuse to speak until it is done. The second is only necessary
* if the first returns a CHECK CONDITION status, but we do it
CDB6(&si
)->cdb_cmd
= CMD_INQUIRY
;
*(short *)&CDB6(&si
)->cdb_lbam
= 0;
CDB6(&si
)->cdb_len
= sizeof sa
.sa_si
;
for (unit
= 0; unit
< 8; unit
++) {
if (scsi_test_unit_ready(hba
, targ
, unit
) == -1)
sa
.sa_req_status
= scsi_request_sense(hba
, targ
, unit
,
(caddr_t
)&sa
.sa_sn
, sizeof sa
.sa_sn
);
CDB6(&si
)->cdb_lun_lbah
= unit
<< 5;
sa
.sa_inq_status
= (*hd
->hd_icmd
)(hba
, targ
, &si
,
(caddr_t
)&sa
.sa_si
, sizeof sa
.sa_si
, B_READ
);
if ((sa
.sa_inq_status
& STS_MASK
) == STS_GOOD
&&
#ifdef notdef /* XXX don't know if this is a reasonable test */
(sa
.sa_si
.si_type
& TYPE_QUAL_MASK
) == TYPE_QUAL_NOTCONN
&&
(sa
.sa_si
.si_type
& TYPE_TYPE_MASK
) == TYPE_NP
) {
config_found(&t
->t_dev
, (void *)&sa
, scsi_unitprint
);
* Each unit calls scsi_establish to tell the hba and target of
scsi_establish(u
, dev
, unit
)
register struct hba_softc
*hba
;
register struct hbadriver
*hbd
;
t
= (struct targ
*)dev
->dv_parent
;
hba
= (struct hba_softc
*)t
->t_dev
.dv_parent
;
* This is the first unit on the target. We can
* probably just call the hba start code, avoiding
* one level of calls and queueing. If we attach
* another target we will fix this in the code below.
u
->u_start
= hbd
->hd_start
;
u
->u_updev
= &hba
->hba_dev
;
* This is not the only unit on the target, so we
* must call the target start code rather than the
* hba start code. Fix the linkage on the first
* target too (possibly for the 2nd, 3rd, ..., time).
t
->t_units
[t
->t_firstunit
]->u_start
= scsi_targstart
;
t
->t_units
[t
->t_firstunit
]->u_go
= scsi_targgo
;
t
->t_units
[t
->t_firstunit
]->u_rel
= scsi_targrel
;
t
->t_units
[t
->t_firstunit
]->u_updev
= &t
->t_dev
;
u
->u_start
= scsi_targstart
;
t
->t_nunits
++; /* another unit is alive */
u
->u_targ
= t
->t_targ
; /* record target number, */
u
->u_hba
= hba
; /* hba ... */
u
->u_hbd
= hbd
; /* and driver */
/* NO DOUBT SOME OF THE STUFF PRINTED HERE IS USELESS */
register struct scsi_inquiry
*inq
;
register int iso
, ecma
, ansi
, t
;
static char *types
[] = { "disk", "tape", "printer", "processor",
"WORM", "ROM disk", "scanner", "magneto-optical",
if ((t
= (inq
->si_type
& TYPE_QUAL_MASK
)) != 0)
printf("type-qual=0x%x ", t
);
t
= inq
->si_type
& TYPE_TYPE_MASK
;
if (t
< sizeof types
/ sizeof *types
)
if (inq
->si_qual
& QUAL_RMB
)
printf(" qual=0x%x", inq
->si_qual
& QUAL_MASK
);
iso
= (inq
->si_qual
>> VER_ISO_SHIFT
) & VER_ISO_MASK
;
ecma
= (inq
->si_qual
>> VER_ECMA_SHIFT
) & VER_ECMA_MASK
;
ansi
= (inq
->si_qual
>> VER_ANSI_SHIFT
) & VER_ANSI_MASK
;
printf(" version=<iso %d, ecma %d, ansi %d>", iso
, ecma
, ansi
);
if (ansi
== 1 || ansi
== 2) {
scsi_inq_ansi((struct scsi_inq_ansi
*)inq
, v
, p
, r
);
printf(" vendor %s, product %s, rev %s", v
, p
, r
);
/* copy a counted string but trim trailing blanks; make the dest a C string */
register char *src
, *dst
;
while (src
[len
- 1] == ' ') {
scsi_inq_ansi(si
, vendor
, product
, rev
)
register struct scsi_inq_ansi
*si
;
char *vendor
, *product
, *rev
;
/* if too short, extend with blanks */
len
= si
->si_len
+ 5; /* 5 fixed; len is `additional' */
for (i
= len
; i
< sizeof *si
; i
++)
scsi_str(si
->si_vendor
, vendor
, sizeof si
->si_vendor
);
scsi_str(si
->si_product
, product
, sizeof si
->si_product
);
scsi_str(si
->si_rev
, rev
, sizeof si
->si_rev
);
* Tell all the devices on the given hba that it has been reset.
* SHOULD PROBABLY DO MORE HERE
register struct hba_softc
*hba
;
for (targ
= 0; targ
< 8; targ
++) {
if ((t
= hba
->hba_targets
[targ
]) == NULL
)
for (unit
= 0; unit
< 8; unit
++)
if ((u
= t
->t_units
[unit
]) != NULL
)
(*u
->u_driver
->ud_reset
)(u
);
* Start a unit on a target.
* If the target is busy, just enqueue the unit;
* once the target becomes free, we will call the hba start routine.
* Otherwise, call the hba start routine now, and then when the hba
* becomes free it will call the unit's dgo routine.
scsi_targstart(self
, sq
, bp
, dgo
, dev
)
register struct targ
*t
= (struct targ
*)self
;
register struct hba_softc
*hba
;
hba
= (struct hba_softc
*)t
->t_dev
.dv_parent
;
(*hba
->hba_driver
->hd_start
)(&hba
->hba_dev
, &t
->t_forw
, bp
,
* The unit got the bus, and wants the hba to go.
* Remember its interrupt handler; substitute ours instead.
scsi_targgo(self
, targ
, intr
, dev
, bp
, pad
)
register struct targ
*t
= (struct targ
*)self
;
register struct hba_softc
*hba
;
hba
= (struct hba_softc
*)t
->t_dev
.dv_parent
;
return ((*hba
->hba_driver
->hd_go
)(&hba
->hba_dev
, targ
,
scsi_targintr
, &t
->t_dev
, bp
, pad
));
* The hba got an interrupt. Dequeue the unit from the target
* (the target is already off the hba queue) and then call the
* underlying interrupt handler.
scsi_targintr(self
, stat
, resid
)
register struct targ
*t
= (struct targ
*)self
;
register struct hba_softc
*hba
;
if (sq
== NULL
) panic("scsi_targintr");
t
->t_head
= sq
= sq
->sq_forw
;
(*t
->t_intr
)(t
->t_intrdev
, stat
, resid
);
hba
= (struct hba_softc
*)t
->t_dev
.dv_parent
;
(*hba
->hba_driver
->hd_start
)(&hba
->hba_dev
, &t
->t_forw
,
sq
->sq_bp
, sq
->sq_dgo
, sq
->sq_dev
);
* The unit decided that it needed to `give up' its hold on the bus early.
register struct targ
*t
= (struct targ
*)self
;
register struct hba_softc
*hba
;
hba
= (struct hba_softc
*)t
->t_dev
.dv_parent
;
if (sq
== NULL
) panic("scsi_targrel");
* This target is at the head of the hba queue.
* Remove it by calling hba bus release. Then, if the
* target queue is not empty, put it back on the hba queue.
* (This produces round robin service.)
(*hba
->hba_driver
->hd_rel
)(&hba
->hba_dev
);
if ((t
->t_head
= sq
) != NULL
)
(*hba
->hba_driver
->hd_start
)(&hba
->hba_dev
, &t
->t_forw
,
sq
->sq_bp
, sq
->sq_dgo
, sq
->sq_dev
);