* Written By Julian ELischer
* Copyright julian Elischer 1993.
* Permission is granted to use or redistribute this file in any way as long
* as this notice remains. Julian Elischer does not guarantee that this file
* is totally correct for any given task and users of this file must
* accept responsibility for any damage that occurs from the application of this
* Written by Julian Elischer (julian@dialix.oz.au)
* $Id: scsi_base.c,v 1.6 1994/02/07 02:15:01 rgrimes Exp $
#include <machine/param.h>
#include <vm/vm_statistics.h>
#include <machine/pmap.h>
#include <machine/vmparam.h>
#include <scsi/scsi_all.h>
#include <scsi/scsi_disk.h>
#include <scsi/scsiconf.h>
static errval
sc_err1(struct scsi_xfer
*);
static errval
scsi_interpret_sense(struct scsi_xfer
*);
void sc_print_addr
__P((struct scsi_link
*sc_link
));
struct scsi_xfer
*next_free_xs
;
* Get a scsi transfer structure for the caller. Charge the structure
* to the device that is referenced by the sc_link structure. If the
* sc_link structure has no 'credits' then the device already has the
* maximum number or outstanding operations under way. In this stage,
* wait on the structure so that when one is freed, we are awoken again
* If the SCSI_NOSLEEP flag is set, then do not wait, but rather, return
* a NULL pointer, signifying that no slots were available
* Note in the link structure, that we are waiting on it.
struct scsi_link
*sc_link
; /* who to charge the xs to */
u_int32 flags
; /* if this call can sleep */
SC_DEBUG(sc_link
, SDEV_DB3
, ("get_xs\n"));
while (!sc_link
->opennings
) {
SC_DEBUG(sc_link
, SDEV_DB3
, ("sleeping\n"));
if (flags
& SCSI_NOSLEEP
) {
sc_link
->flags
|= SDEV_WAITING
;
tsleep((caddr_t
)sc_link
, PRIBIO
, "scsiget", 0);
SC_DEBUG(sc_link
, SDEV_DB3
, ("making\n"));
xs
= malloc(sizeof(*xs
), M_TEMP
,
((flags
& SCSI_NOSLEEP
) ? M_NOWAIT
: M_WAITOK
));
printf("cannot allocate scsi xs\n");
SC_DEBUG(sc_link
, SDEV_DB3
, ("returning\n"));
* Given a scsi_xfer struct, and a device (referenced through sc_link)
* return the struct to the free pool and credit the device with it
* If another process is waiting for an xs, do a wakeup, let it proceed
free_xs(xs
, sc_link
, flags
)
struct scsi_link
*sc_link
; /* who to credit for returning it */
SC_DEBUG(sc_link
, SDEV_DB3
, ("free_xs\n"));
/* if was 0 and someone waits, wake them up */
if ((!sc_link
->opennings
++) && (sc_link
->flags
& SDEV_WAITING
)) {
sc_link
->flags
&= ~SDEV_WAITING
;
wakeup((caddr_t
)sc_link
); /* remember, it wakes them ALL up */
if (sc_link
->device
->start
) {
SC_DEBUG(sc_link
, SDEV_DB2
, ("calling private start()\n"));
(*(sc_link
->device
->start
)) (sc_link
->dev_unit
);
* Find out from the device what its capacity is.
scsi_size(sc_link
, flags
)
struct scsi_link
*sc_link
;
struct scsi_read_cap_data rdcap
;
struct scsi_read_capacity scsi_cmd
;
* make up a scsi command and ask the scsi driver to do
bzero(&scsi_cmd
, sizeof(scsi_cmd
));
scsi_cmd
.op_code
= READ_CAPACITY
;
* If the command works, interpret the result as a 4 byte
if (scsi_scsi_cmd(sc_link
,
(struct scsi_generic
*) &scsi_cmd
,
flags
| SCSI_DATA_IN
) != 0) {
printf("could not get size\n");
size
+= rdcap
.addr_1
<< 8;
size
+= rdcap
.addr_2
<< 16;
size
+= rdcap
.addr_3
<< 24;
* Get scsi driver to send a "are you ready?" command
scsi_test_unit_ready(sc_link
, flags
)
struct scsi_link
*sc_link
;
struct scsi_test_unit_ready scsi_cmd
;
bzero(&scsi_cmd
, sizeof(scsi_cmd
));
scsi_cmd
.op_code
= TEST_UNIT_READY
;
return (scsi_scsi_cmd(sc_link
,
(struct scsi_generic
*) &scsi_cmd
,
* Do a scsi operation, asking a device to run as SCSI-II if it can.
scsi_change_def(sc_link
, flags
)
struct scsi_link
*sc_link
;
struct scsi_changedef scsi_cmd
;
bzero(&scsi_cmd
, sizeof(scsi_cmd
));
scsi_cmd
.op_code
= CHANGE_DEFINITION
;
scsi_cmd
.how
= SC_SCSI_2
;
return (scsi_scsi_cmd(sc_link
,
(struct scsi_generic
*) &scsi_cmd
,
* Do a scsi operation asking a device what it is
* Use the scsi_cmd routine in the switch table.
scsi_inquire(sc_link
, inqbuf
, flags
)
struct scsi_link
*sc_link
;
struct scsi_inquiry_data
*inqbuf
;
struct scsi_inquiry scsi_cmd
;
bzero(&scsi_cmd
, sizeof(scsi_cmd
));
scsi_cmd
.op_code
= INQUIRY
;
scsi_cmd
.length
= sizeof(struct scsi_inquiry_data
);
return (scsi_scsi_cmd(sc_link
,
(struct scsi_generic
*) &scsi_cmd
,
sizeof(struct scsi_inquiry_data
),
* Prevent or allow the user to remove the media
scsi_prevent(sc_link
, type
, flags
)
struct scsi_link
*sc_link
;
struct scsi_prevent scsi_cmd
;
bzero(&scsi_cmd
, sizeof(scsi_cmd
));
scsi_cmd
.op_code
= PREVENT_ALLOW
;
return (scsi_scsi_cmd(sc_link
,
(struct scsi_generic
*) &scsi_cmd
,
* Get scsi driver to send a "start up" command
scsi_start_unit(sc_link
, flags
)
struct scsi_link
*sc_link
;
struct scsi_start_stop scsi_cmd
;
bzero(&scsi_cmd
, sizeof(scsi_cmd
));
scsi_cmd
.op_code
= START_STOP
;
scsi_cmd
.how
= SSS_START
;
return (scsi_scsi_cmd(sc_link
,
(struct scsi_generic
*) &scsi_cmd
,
* Get scsi driver to send a "stop" command
scsi_stop_unit(sc_link
, eject
, flags
)
struct scsi_link
*sc_link
;
struct scsi_start_stop scsi_cmd
;
bzero(&scsi_cmd
, sizeof(scsi_cmd
));
scsi_cmd
.op_code
= START_STOP
;
return (scsi_scsi_cmd(sc_link
,
(struct scsi_generic
*) &scsi_cmd
,
* This routine is called by the scsi interrupt when the transfer is complete.
struct scsi_link
*sc_link
= xs
->sc_link
;
SC_DEBUG(sc_link
, SDEV_DB2
, ("scsi_done\n"));
if (sc_link
->flags
& SDEV_DB1
)
* If it's a user level request, bypass all usual completion processing,
* let the user work it out.. We take reponsibility for freeing the
* xs when the user returns. (and restarting the device's queue).
if (xs
->flags
& SCSI_USER
) {
SC_DEBUG(sc_link
, SDEV_DB3
, ("calling user done()\n"));
scsi_user_done(xs
); /* to take a copy of the sense etc. */
SC_DEBUG(sc_link
, SDEV_DB3
, ("returned from user done()\n "));
free_xs(xs
, sc_link
, SCSI_NOSLEEP
); /* restarts queue too */
SC_DEBUG(sc_link
, SDEV_DB3
, ("returning to adapter\n"));
* If the device has it's own done routine, call it first.
* If it returns a legit error value, return that, otherwise
* it wants us to continue with normal processing.
if (sc_link
->device
->done
) {
SC_DEBUG(sc_link
, SDEV_DB2
, ("calling private done()\n"));
retval
= (*sc_link
->device
->done
) (xs
);
free_xs(xs
, sc_link
, SCSI_NOSLEEP
); /*XXX */
return; /* it did it all, finish up */
return; /* it did it all, finish up */
SC_DEBUG(sc_link
, SDEV_DB3
, ("continuing with generic done()\n"));
if ((bp
= xs
->bp
) == NULL
) {
* if it's a normal upper level request, then ask
* the upper level code to handle error checking
* rather than doing it here at interrupt time
* Go and handle errors now.
* If it returns -1 then we should RETRY
if ((retval
= sc_err1(xs
)) == -1) {
if ((*(sc_link
->adapter
->scsi_cmd
)) (xs
)
== SUCCESSFULLY_QUEUED
) { /* don't wake the job, ok? */
free_xs(xs
, sc_link
, SCSI_NOSLEEP
); /* does a start if needed */
* ask the scsi driver to perform a command for us.
* tell it where to read/write the data, and how
* long the data is supposed to be. If we have a buf
* to associate with the transfer, we need that too.
scsi_scsi_cmd(sc_link
, scsi_cmd
, cmdlen
, data_addr
, datalen
,
retries
, timeout
, bp
, flags
)
struct scsi_link
*sc_link
;
struct scsi_generic
*scsi_cmd
;
if (bp
) flags
|= SCSI_NOSLEEP
;
SC_DEBUG(sc_link
, SDEV_DB2
, ("scsi_cmd\n"));
xs
= get_xs(sc_link
, flags
); /* should wait unless booting */
if (!xs
) return (ENOMEM
);
* Fill out the scsi_xfer structure. We don't know whose context
* the cmd is in, so copy it.
bcopy(scsi_cmd
, &(xs
->cmdstore
), cmdlen
);
xs
->flags
= INUSE
| flags
;
/*XXX*/ /*use constant not magic number */
if (datalen
&& ((caddr_t
) data_addr
< (caddr_t
) KERNBASE
)) {
printf("Data buffered space not in kernel context\n");
xs
->data
= malloc(datalen
, M_TEMP
, M_WAITOK
);
xs
->data
= (caddr_t
) vm_bounce_kva_alloc( (datalen
+ PAGE_SIZE
- 1)/PAGE_SIZE
);
/* I think waiting is ok *//*XXX */
switch ((int)(flags
& (SCSI_DATA_IN
| SCSI_DATA_OUT
))) {
printf("No direction flags, assuming both\n");
case SCSI_DATA_IN
| SCSI_DATA_OUT
: /* weird */
bcopy(data_addr
, xs
->data
, datalen
);
bzero(xs
->data
, datalen
);
if (datalen
&& ((caddr_t
) xs
->data
< (caddr_t
) KERNBASE
)) {
printf("It's still wrong!\n");
if (sc_link
->flags
& SDEV_DB3
) show_scsi_xs(xs
);
* Do the transfer. If we are polling we will return:
* COMPLETE, Was poll, and scsi_done has been called
* TRY_AGAIN_LATER, Adapter short resources, try again
* if under full steam (interrupts) it will return:
* SUCCESSFULLY_QUEUED, will do a wakeup when complete
* TRY_AGAIN_LATER, (as for polling)
* After the wakeup, we must still check if it succeeded
* If we have a bp however, all the error proccessing
* and the buffer code both expect us to return straight
* to them, so as soon as the command is queued, return
retval
= (*(sc_link
->adapter
->scsi_cmd
)) (xs
);
case SUCCESSFULLY_QUEUED
:
return retval
; /* will sleep (or not) elsewhere */
while (!(xs
->flags
& ITSDONE
)) {
tsleep((caddr_t
)xs
, PRIBIO
+ 1, "scsicmd", 0);
/* fall through to check success of completed command */
case COMPLETE
: /* Polling command completed ok */
/*XXX*/ case HAD_ERROR
: /* Polling command completed with error */
SC_DEBUG(sc_link
, SDEV_DB3
, ("back in cmd()\n"));
if ((retval
= sc_err1(xs
)) == -1)
case TRY_AGAIN_LATER
: /* adapter resource shortage */
SC_DEBUG(sc_link
, SDEV_DB3
, ("will try again \n"));
/* should sleep 1 sec here */
* If we had to copy the data out of the user's context,
* then do the other half (copy it back or whatever)
* and free the memory buffer
if (datalen
&& (xs
->data
!= data_addr
)) {
switch ((int)(flags
& (SCSI_DATA_IN
| SCSI_DATA_OUT
))) {
case SCSI_DATA_IN
| SCSI_DATA_OUT
: /* weird */
bcopy(xs
->data
, data_addr
, datalen
);
vm_bounce_kva_alloc_free(xs
->data
, (datalen
+ PAGE_SIZE
- 1)/PAGE_SIZE
, 0);
* we have finished with the xfer stuct, free it and
* check if anyone else needs to be started up.
free_xs(xs
, sc_link
, flags
); /* includes the 'start' op */
SC_DEBUG(xs
->sc_link
, SDEV_DB3
, ("sc_err1,err = 0x%x \n", xs
->error
));
* If it has a buf, we might be working with
* a request from the buffer cache or some other
* piece of code that requires us to process
* errors at inetrrupt time. We have probably
* been called by scsi_done()
switch ((int)xs
->error
) {
case XS_NOERROR
: /* nearly always hit this one */
if (retval
= (scsi_interpret_sense(xs
))) {
bp
->b_resid
= bp
->b_bcount
;
SC_DEBUG(xs
->sc_link
, SDEV_DB3
,
("scsi_interpret_sense (bp) returned %d\n", retval
));
retval
= (scsi_interpret_sense(xs
));
SC_DEBUG(xs
->sc_link
, SDEV_DB3
,
("scsi_interpret_sense (no bp) returned %d\n", retval
));
/*should somehow arange for a 1 sec delay here (how?) */
/* XXX tsleep(&localvar, priority, "foo", hz);
* If we can, resubmit it to the adapter.
sc_print_addr(xs
->sc_link
);
printf("unknown error category from scsi driver\n");
* Look at the returned sense and act on the error, determining
* the unix error number to pass back. (0 = report no error)
* THIS IS THE DEFAULT ERROR HANDLER
struct scsi_sense_data
*sense
;
struct scsi_link
*sc_link
= xs
->sc_link
;
static char *error_mes
[] =
{"soft error (corrected)",
"not ready", "medium error",
"non-media hardware failure", "illegal request",
"unit attention", "readonly device",
"no data found", "vendor unique",
"copy aborted", "command aborted",
"search returned equal", "volume overflow",
"verify miscompare", "unknown error key"
* If the flags say errs are ok, then always return ok.
if (xs
->flags
& SCSI_ERR_OK
)
if (sc_link
->flags
& SDEV_DB1
) {
printf("code%x valid%x ",
sense
->error_code
& SSD_ERRCODE
,
sense
->error_code
& SSD_ERRCODE_VALID
? 1 : 0);
printf("seg%x key%x ili%x eom%x fmark%x\n",
sense
->ext
.extended
.segment
,
sense
->ext
.extended
.flags
& SSD_KEY
,
sense
->ext
.extended
.flags
& SSD_ILI
? 1 : 0,
sense
->ext
.extended
.flags
& SSD_EOM
? 1 : 0,
sense
->ext
.extended
.flags
& SSD_FILEMARK
? 1 : 0);
printf("info: %x %x %x %x followed by %d extra bytes\n",
sense
->ext
.extended
.info
[0],
sense
->ext
.extended
.info
[1],
sense
->ext
.extended
.info
[2],
sense
->ext
.extended
.info
[3],
sense
->ext
.extended
.extra_len
);
while (count
< sense
->ext
.extended
.extra_len
) {
printf("%x ", sense
->ext
.extended
.extra_bytes
[count
++]);
* If the device has it's own error handler, call it first.
* If it returns a legit error value, return that, otherwise
* it wants us to continue with normal error processing.
if (sc_link
->device
->err_handler
) {
SC_DEBUG(sc_link
, SDEV_DB2
, ("calling private err_handler()\n"));
errcode
= (*sc_link
->device
->err_handler
) (xs
);
return errcode
; /* errcode >= 0 better ? */
/* otherwise use the default */
silent
= (xs
->flags
& SCSI_SILENT
);
switch (sense
->error_code
& SSD_ERRCODE
) {
* If it's code 70, use the extended stuff and interpret the key
case 0x71: /* delayed error */
key
= sense
->ext
.extended
.flags
& SSD_KEY
;
printf(" DELAYED ERROR, key = 0x%x\n", key
);
if (sense
->error_code
& SSD_ERRCODE_VALID
) {
info
= ntohl(*((long *) sense
->ext
.extended
.info
));
key
= sense
->ext
.extended
.flags
& SSD_KEY
;
printf("%s", error_mes
[key
- 1]);
if (sense
->error_code
& SSD_ERRCODE_VALID
) {
case 0x2: /* NOT READY */
case 0x5: /* ILLEGAL REQUEST */
case 0x6: /* UNIT ATTENTION */
case 0x7: /* DATA PROTECT */
case 0x8: /* BLANK CHECK */
printf(", requested size: %d (decimal)",
printf(", info = %d (decimal)", info
);
case 0x1: /* RECOVERED ERROR */
if (xs
->resid
== xs
->datalen
)
xs
->resid
= 0; /* not short read */
case 0x2: /* NOT READY */
sc_link
->flags
&= ~SDEV_MEDIA_LOADED
;
case 0x5: /* ILLEGAL REQUEST */
case 0x6: /* UNIT ATTENTION */
sc_link
->flags
&= ~SDEV_MEDIA_LOADED
;
if (sc_link
->flags
& SDEV_OPEN
) {
case 0x7: /* DATA PROTECT */
case 0xd: /* VOLUME OVERFLOW */
case 0x8: /* BLANK CHECK */
* Not code 70, just report it
sense
->error_code
& SSD_ERRCODE
);
if (sense
->error_code
& SSD_ERRCODE_VALID
) {
printf(" at block no. %d (decimal)",
(sense
->ext
.unextended
.blockhi
<< 16) +
(sense
->ext
.unextended
.blockmed
<< 8) +
(sense
->ext
.unextended
.blocklow
));
* Utility routines often used in SCSI stuff
* convert a physical address to 3 bytes,
* MSB at the lowest address,
*bytes
++ = (val
& 0xff0000) >> 16;
*bytes
++ = (val
& 0xff00) >> 8;
* Print out the scsi_link structure's address info.
struct scsi_link
*sc_link
;
printf("%s%d(%s%d:%d:%d): ", sc_link
->device
->name
, sc_link
->dev_unit
,
sc_link
->adapter
->name
, sc_link
->adapter_unit
,
sc_link
->target
, sc_link
->lun
);
* Given a scsi_xfer, dump the request, in all it's glory
printf("xs(0x%x): ", xs
);
printf("flg(0x%x)", xs
->flags
);
printf("sc_link(0x%x)", xs
->sc_link
);
printf("retr(0x%x)", xs
->retries
);
printf("timo(0x%x)", xs
->timeout
);
printf("cmd(0x%x)", xs
->cmd
);
printf("len(0x%x)", xs
->cmdlen
);
printf("data(0x%x)", xs
->data
);
printf("len(0x%x)", xs
->datalen
);
printf("res(0x%x)", xs
->resid
);
printf("err(0x%x)", xs
->error
);
printf("bp(0x%x)", xs
->bp
);
show_scsi_cmd(struct scsi_xfer
*xs
)
u_char
*b
= (u_char
*) xs
->cmd
;
sc_print_addr(xs
->sc_link
);
if (!(xs
->flags
& SCSI_RESET
)) {
printf("-[%d bytes]\n", xs
->datalen
);
show_mem(xs
->data
, min(64, xs
->datalen
));
printf("------------------------------");
for (y
= 0; y
< num
; y
+= 1) {
printf("%02x ", *address
++);
printf("\n------------------------------\n");