#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: scsipi_base.c,v 1.193 2024/10/29 15:50:07 nat Exp $");
#ifdef _KERNEL_OPT
#include "opt_scsi.h"
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/pool.h>
#include <sys/errno.h>
#include <sys/device.h>
#include <sys/proc.h>
#include <sys/kthread.h>
#include <sys/hash.h>
#include <sys/atomic.h>
#include <dev/scsipi/scsi_sdt.h>
#include <dev/scsipi/scsi_spc.h>
#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsipi_disk.h>
#include <dev/scsipi/scsipiconf.h>
#include <dev/scsipi/scsipi_base.h>
#include <dev/scsipi/scsi_all.h>
#include <dev/scsipi/scsi_message.h>
#include <machine/param.h>
SDT_PROVIDER_DEFINE(scsi);
SDT_PROBE_DEFINE3(scsi, base, tag, get,
"struct scsipi_xfer *", "uint8_t", "uint8_t");
SDT_PROBE_DEFINE3(scsi, base, tag, put,
"struct scsipi_xfer *", "uint8_t", "uint8_t");
SDT_PROBE_DEFINE3(scsi, base, adapter, request__start,
"struct scsipi_channel *",
"scsipi_adapter_req_t",
"void *");
SDT_PROBE_DEFINE3(scsi, base, adapter, request__done,
"struct scsipi_channel *",
"scsipi_adapter_req_t",
"void *");
SDT_PROBE_DEFINE1(scsi, base, queue, batch__start,
"struct scsipi_channel *");
SDT_PROBE_DEFINE2(scsi, base, queue, run,
"struct scsipi_channel *",
"struct scsipi_xfer *");
SDT_PROBE_DEFINE1(scsi, base, queue, batch__done,
"struct scsipi_channel *");
SDT_PROBE_DEFINE1(scsi, base, xfer, execute, "struct scsipi_xfer *");
SDT_PROBE_DEFINE1(scsi, base, xfer, enqueue, "struct scsipi_xfer *");
SDT_PROBE_DEFINE1(scsi, base, xfer, done, "struct scsipi_xfer *");
SDT_PROBE_DEFINE1(scsi, base, xfer, redone, "struct scsipi_xfer *");
SDT_PROBE_DEFINE1(scsi, base, xfer, complete, "struct scsipi_xfer *");
SDT_PROBE_DEFINE1(scsi, base, xfer, restart, "struct scsipi_xfer *");
SDT_PROBE_DEFINE1(scsi, base, xfer, free, "struct scsipi_xfer *");
static int scsipi_complete(struct scsipi_xfer *);
static void scsipi_request_sense(struct scsipi_xfer *);
static int scsipi_enqueue(struct scsipi_xfer *);
static void scsipi_run_queue(struct scsipi_channel *chan);
static void scsipi_completion_thread(void *);
static void scsipi_get_tag(struct scsipi_xfer *);
static void scsipi_put_tag(struct scsipi_xfer *);
static int scsipi_get_resource(struct scsipi_channel *);
static void scsipi_put_resource(struct scsipi_channel *);
static void scsipi_async_event_max_openings(struct scsipi_channel *,
struct scsipi_max_openings *);
static void scsipi_async_event_channel_reset(struct scsipi_channel *);
static void scsipi_channel_freeze_locked(struct scsipi_channel *, int);
static void scsipi_adapter_lock(struct scsipi_adapter *adapt);
static void scsipi_adapter_unlock(struct scsipi_adapter *adapt);
static void scsipi_update_timeouts(struct scsipi_xfer *xs);
static struct pool scsipi_xfer_pool;
int scsipi_xs_count = 0;
void
scsipi_init(void)
{
static int scsipi_init_done;
if (scsipi_init_done)
return;
scsipi_init_done = 1;
pool_init(&scsipi_xfer_pool, sizeof(struct scsipi_xfer), 0,
0, 0, "scxspl", NULL, IPL_BIO);
pool_prime(&scsipi_xfer_pool, 1);
scsipi_ioctl_init();
}
int
scsipi_channel_init(struct scsipi_channel *chan)
{
struct scsipi_adapter *adapt = chan->chan_adapter;
int i;
scsipi_init();
TAILQ_INIT(&chan->chan_queue);
TAILQ_INIT(&chan->chan_complete);
for (i = 0; i < SCSIPI_CHAN_PERIPH_BUCKETS; i++)
LIST_INIT(&chan->chan_periphtab[i]);
if (kthread_create(PRI_NONE, 0, NULL, scsipi_completion_thread, chan,
&chan->chan_thread, "%s", chan->chan_name)) {
aprint_error_dev(adapt->adapt_dev, "unable to create completion thread for "
"channel %d\n", chan->chan_channel);
panic("scsipi_channel_init");
}
return 0;
}
void
scsipi_channel_shutdown(struct scsipi_channel *chan)
{
mutex_enter(chan_mtx(chan));
chan->chan_tflags |= SCSIPI_CHANT_SHUTDOWN;
cv_broadcast(chan_cv_complete(chan));
while (chan->chan_thread != NULL)
cv_wait(chan_cv_thread(chan), chan_mtx(chan));
mutex_exit(chan_mtx(chan));
}
static uint32_t
scsipi_chan_periph_hash(uint64_t t, uint64_t l)
{
uint32_t hash;
hash = hash32_buf(&t, sizeof(t), HASH32_BUF_INIT);
hash = hash32_buf(&l, sizeof(l), hash);
return hash & SCSIPI_CHAN_PERIPH_HASHMASK;
}
void
scsipi_insert_periph(struct scsipi_channel *chan, struct scsipi_periph *periph)
{
uint32_t hash;
hash = scsipi_chan_periph_hash(periph->periph_target,
periph->periph_lun);
mutex_enter(chan_mtx(chan));
LIST_INSERT_HEAD(&chan->chan_periphtab[hash], periph, periph_hash);
mutex_exit(chan_mtx(chan));
}
void
scsipi_remove_periph(struct scsipi_channel *chan,
struct scsipi_periph *periph)
{
LIST_REMOVE(periph, periph_hash);
}
static struct scsipi_periph *
scsipi_lookup_periph_internal(struct scsipi_channel *chan, int target, int lun, bool lock)
{
struct scsipi_periph *periph;
uint32_t hash;
if (target >= chan->chan_ntargets ||
lun >= chan->chan_nluns)
return NULL;
hash = scsipi_chan_periph_hash(target, lun);
if (lock)
mutex_enter(chan_mtx(chan));
LIST_FOREACH(periph, &chan->chan_periphtab[hash], periph_hash) {
if (periph->periph_target == target &&
periph->periph_lun == lun)
break;
}
if (lock)
mutex_exit(chan_mtx(chan));
return periph;
}
struct scsipi_periph *
scsipi_lookup_periph_locked(struct scsipi_channel *chan, int target, int lun)
{
return scsipi_lookup_periph_internal(chan, target, lun, false);
}
struct scsipi_periph *
scsipi_lookup_periph(struct scsipi_channel *chan, int target, int lun)
{
return scsipi_lookup_periph_internal(chan, target, lun, true);
}
static int
scsipi_get_resource(struct scsipi_channel *chan)
{
struct scsipi_adapter *adapt = chan->chan_adapter;
if (chan->chan_flags & SCSIPI_CHAN_OPENINGS) {
if (chan->chan_openings > 0) {
chan->chan_openings--;
return 1;
}
return 0;
}
if (adapt->adapt_openings > 0) {
adapt->adapt_openings--;
return 1;
}
return 0;
}
static inline int
scsipi_grow_resources(struct scsipi_channel *chan)
{
if (chan->chan_flags & SCSIPI_CHAN_CANGROW) {
if ((chan->chan_flags & SCSIPI_CHAN_TACTIVE) == 0) {
mutex_exit(chan_mtx(chan));
scsipi_adapter_request(chan,
ADAPTER_REQ_GROW_RESOURCES, NULL);
mutex_enter(chan_mtx(chan));
return scsipi_get_resource(chan);
}
scsipi_channel_freeze_locked(chan, 1);
chan->chan_tflags |= SCSIPI_CHANT_GROWRES;
cv_broadcast(chan_cv_complete(chan));
return 0;
}
return 0;
}
static void
scsipi_put_resource(struct scsipi_channel *chan)
{
struct scsipi_adapter *adapt = chan->chan_adapter;
if (chan->chan_flags & SCSIPI_CHAN_OPENINGS)
chan->chan_openings++;
else
adapt->adapt_openings++;
}
static void
scsipi_get_tag(struct scsipi_xfer *xs)
{
struct scsipi_periph *periph = xs->xs_periph;
int bit, tag;
u_int word;
KASSERT(mutex_owned(chan_mtx(periph->periph_channel)));
bit = 0;
for (word = 0; word < PERIPH_NTAGWORDS; word++) {
bit = ffs(periph->periph_freetags[word]);
if (bit != 0)
break;
}
#ifdef DIAGNOSTIC
if (word == PERIPH_NTAGWORDS) {
scsipi_printaddr(periph);
printf("no free tags\n");
panic("scsipi_get_tag");
}
#endif
bit -= 1;
periph->periph_freetags[word] &= ~(1U << bit);
tag = (word << 5) | bit;
if (tag >= periph->periph_openings) {
scsipi_printaddr(periph);
printf("WARNING: tag %d greater than available openings %d\n",
tag, periph->periph_openings);
}
xs->xs_tag_id = tag;
SDT_PROBE3(scsi, base, tag, get,
xs, xs->xs_tag_id, xs->xs_tag_type);
}
static void
scsipi_put_tag(struct scsipi_xfer *xs)
{
struct scsipi_periph *periph = xs->xs_periph;
int word, bit;
KASSERT(mutex_owned(chan_mtx(periph->periph_channel)));
SDT_PROBE3(scsi, base, tag, put,
xs, xs->xs_tag_id, xs->xs_tag_type);
word = xs->xs_tag_id >> 5;
bit = xs->xs_tag_id & 0x1f;
periph->periph_freetags[word] |= (1U << bit);
}
struct scsipi_xfer *
scsipi_get_xs(struct scsipi_periph *periph, int flags)
{
struct scsipi_xfer *xs;
bool lock = (flags & XS_CTL_NOSLEEP) == 0;
SC_DEBUG(periph, SCSIPI_DB3, ("scsipi_get_xs\n"));
KASSERT(!cold);
#ifdef DIAGNOSTIC
if ((flags & (XS_CTL_URGENT|XS_CTL_ASYNC)) ==
(XS_CTL_URGENT|XS_CTL_ASYNC)) {
scsipi_printaddr(periph);
printf("URGENT and ASYNC\n");
panic("scsipi_get_xs");
}
#endif
if (lock)
mutex_enter(chan_mtx(periph->periph_channel));
for (;;) {
if (flags & XS_CTL_URGENT) {
if (periph->periph_active > periph->periph_openings)
goto wait_for_opening;
if (periph->periph_flags & PERIPH_SENSE) {
if ((flags & XS_CTL_REQSENSE) == 0)
goto wait_for_opening;
} else {
if ((periph->periph_flags &
PERIPH_RECOVERY_ACTIVE) != 0)
goto wait_for_opening;
periph->periph_flags |= PERIPH_RECOVERY_ACTIVE;
}
break;
}
if (periph->periph_active >= periph->periph_openings ||
(periph->periph_flags & PERIPH_RECOVERING) != 0)
goto wait_for_opening;
periph->periph_active++;
KASSERT(mutex_owned(chan_mtx(periph->periph_channel)));
break;
wait_for_opening:
if (flags & XS_CTL_NOSLEEP) {
KASSERT(!lock);
return NULL;
}
KASSERT(lock);
SC_DEBUG(periph, SCSIPI_DB3, ("sleeping\n"));
periph->periph_flags |= PERIPH_WAITING;
cv_wait(periph_cv_periph(periph),
chan_mtx(periph->periph_channel));
}
if (lock)
mutex_exit(chan_mtx(periph->periph_channel));
SC_DEBUG(periph, SCSIPI_DB3, ("calling pool_get\n"));
xs = pool_get(&scsipi_xfer_pool,
((flags & XS_CTL_NOSLEEP) != 0 ? PR_NOWAIT : PR_WAITOK));
if (xs == NULL) {
if (lock)
mutex_enter(chan_mtx(periph->periph_channel));
if (flags & XS_CTL_URGENT) {
if ((flags & XS_CTL_REQSENSE) == 0)
periph->periph_flags &= ~PERIPH_RECOVERY_ACTIVE;
} else
periph->periph_active--;
if (lock)
mutex_exit(chan_mtx(periph->periph_channel));
scsipi_printaddr(periph);
printf("unable to allocate %sscsipi_xfer\n",
(flags & XS_CTL_URGENT) ? "URGENT " : "");
}
SC_DEBUG(periph, SCSIPI_DB3, ("returning\n"));
if (xs != NULL) {
memset(xs, 0, sizeof(*xs));
callout_init(&xs->xs_callout, 0);
xs->xs_periph = periph;
xs->xs_control = flags;
xs->xs_status = 0;
if ((flags & XS_CTL_NOSLEEP) == 0)
mutex_enter(chan_mtx(periph->periph_channel));
TAILQ_INSERT_TAIL(&periph->periph_xferq, xs, device_q);
KASSERT(mutex_owned(chan_mtx(periph->periph_channel)));
if ((flags & XS_CTL_NOSLEEP) == 0)
mutex_exit(chan_mtx(periph->periph_channel));
}
return xs;
}
void
scsipi_put_xs(struct scsipi_xfer *xs)
{
struct scsipi_periph *periph = xs->xs_periph;
int flags = xs->xs_control;
SDT_PROBE1(scsi, base, xfer, free, xs);
SC_DEBUG(periph, SCSIPI_DB3, ("scsipi_free_xs\n"));
KASSERT(mutex_owned(chan_mtx(periph->periph_channel)));
TAILQ_REMOVE(&periph->periph_xferq, xs, device_q);
callout_destroy(&xs->xs_callout);
pool_put(&scsipi_xfer_pool, xs);
#ifdef DIAGNOSTIC
if ((periph->periph_flags & PERIPH_RECOVERY_ACTIVE) != 0 &&
periph->periph_active == 0) {
scsipi_printaddr(periph);
printf("recovery without a command to recovery for\n");
panic("scsipi_put_xs");
}
#endif
if (flags & XS_CTL_URGENT) {
if ((flags & XS_CTL_REQSENSE) == 0)
periph->periph_flags &= ~PERIPH_RECOVERY_ACTIVE;
} else
periph->periph_active--;
if (periph->periph_active == 0 &&
(periph->periph_flags & PERIPH_WAITDRAIN) != 0) {
periph->periph_flags &= ~PERIPH_WAITDRAIN;
cv_broadcast(periph_cv_active(periph));
}
if (periph->periph_flags & PERIPH_WAITING) {
periph->periph_flags &= ~PERIPH_WAITING;
cv_broadcast(periph_cv_periph(periph));
} else {
if (periph->periph_switch->psw_start != NULL &&
device_is_active(periph->periph_dev)) {
SC_DEBUG(periph, SCSIPI_DB2,
("calling private start()\n"));
(*periph->periph_switch->psw_start)(periph);
}
}
}
void
scsipi_channel_freeze(struct scsipi_channel *chan, int count)
{
bool lock = chan_running(chan) > 0;
if (lock)
mutex_enter(chan_mtx(chan));
chan->chan_qfreeze += count;
if (lock)
mutex_exit(chan_mtx(chan));
}
static void
scsipi_channel_freeze_locked(struct scsipi_channel *chan, int count)
{
chan->chan_qfreeze += count;
}
void
scsipi_channel_thaw(struct scsipi_channel *chan, int count)
{
bool lock = chan_running(chan) > 0;
if (lock)
mutex_enter(chan_mtx(chan));
chan->chan_qfreeze -= count;
if (chan->chan_qfreeze < 0) {
chan->chan_qfreeze = 0;
}
if (lock)
mutex_exit(chan_mtx(chan));
if (!lock)
return;
if (chan->chan_qfreeze == 0)
scsipi_run_queue(chan);
}
void
scsipi_channel_timed_thaw(void *arg)
{
struct scsipi_channel *chan = arg;
scsipi_channel_thaw(chan, 1);
}
void
scsipi_periph_freeze_locked(struct scsipi_periph *periph, int count)
{
periph->periph_qfreeze += count;
}
void
scsipi_periph_thaw_locked(struct scsipi_periph *periph, int count)
{
periph->periph_qfreeze -= count;
#ifdef DIAGNOSTIC
if (periph->periph_qfreeze < 0) {
static const char pc[] = "periph freeze count < 0";
scsipi_printaddr(periph);
printf("%s\n", pc);
panic(pc);
}
#endif
if (periph->periph_qfreeze == 0 &&
(periph->periph_flags & PERIPH_WAITING) != 0)
cv_broadcast(periph_cv_periph(periph));
}
void
scsipi_periph_freeze(struct scsipi_periph *periph, int count)
{
mutex_enter(chan_mtx(periph->periph_channel));
scsipi_periph_freeze_locked(periph, count);
mutex_exit(chan_mtx(periph->periph_channel));
}
void
scsipi_periph_thaw(struct scsipi_periph *periph, int count)
{
mutex_enter(chan_mtx(periph->periph_channel));
scsipi_periph_thaw_locked(periph, count);
mutex_exit(chan_mtx(periph->periph_channel));
}
void
scsipi_periph_timed_thaw(void *arg)
{
struct scsipi_periph *periph = arg;
struct scsipi_channel *chan = periph->periph_channel;
callout_stop(&periph->periph_callout);
mutex_enter(chan_mtx(chan));
scsipi_periph_thaw_locked(periph, 1);
if ((periph->periph_channel->chan_flags & SCSIPI_CHAN_TACTIVE) == 0) {
mutex_exit(chan_mtx(chan));
scsipi_run_queue(periph->periph_channel);
} else {
periph->periph_channel->chan_tflags |= SCSIPI_CHANT_KICK;
cv_broadcast(chan_cv_complete(chan));
mutex_exit(chan_mtx(chan));
}
}
void
scsipi_wait_drain(struct scsipi_periph *periph)
{
struct scsipi_channel *chan = periph->periph_channel;
mutex_enter(chan_mtx(chan));
while (periph->periph_active != 0) {
periph->periph_flags |= PERIPH_WAITDRAIN;
cv_wait(periph_cv_active(periph), chan_mtx(chan));
}
mutex_exit(chan_mtx(chan));
}
void
scsipi_kill_pending(struct scsipi_periph *periph)
{
struct scsipi_channel *chan = periph->periph_channel;
(*chan->chan_bustype->bustype_kill_pending)(periph);
while (periph->periph_active != 0) {
periph->periph_flags |= PERIPH_WAITDRAIN;
cv_wait(periph_cv_active(periph), chan_mtx(chan));
}
}
void
scsipi_print_cdb(struct scsipi_generic *cmd)
{
int i, j;
printf("0x%02x", cmd->opcode);
switch (CDB_GROUPID(cmd->opcode)) {
case CDB_GROUPID_0:
j = CDB_GROUP0;
break;
case CDB_GROUPID_1:
j = CDB_GROUP1;
break;
case CDB_GROUPID_2:
j = CDB_GROUP2;
break;
case CDB_GROUPID_3:
j = CDB_GROUP3;
break;
case CDB_GROUPID_4:
j = CDB_GROUP4;
break;
case CDB_GROUPID_5:
j = CDB_GROUP5;
break;
case CDB_GROUPID_6:
j = CDB_GROUP6;
break;
case CDB_GROUPID_7:
j = CDB_GROUP7;
break;
default:
j = 0;
}
if (j == 0)
j = sizeof (cmd->bytes);
for (i = 0; i < j-1; i++)
printf(" %02x", cmd->bytes[i]);
}
int
scsipi_interpret_sense(struct scsipi_xfer *xs)
{
struct scsi_sense_data *sense;
struct scsipi_periph *periph = xs->xs_periph;
u_int8_t key;
int error;
u_int32_t info;
static const 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"
};
sense = &xs->sense.scsi_sense;
#ifdef SCSIPI_DEBUG
if (periph->periph_flags & SCSIPI_DB1) {
int count, len;
scsipi_printaddr(periph);
printf(" sense debug information:\n");
printf("\tcode 0x%x valid %d\n",
SSD_RCODE(sense->response_code),
sense->response_code & SSD_RCODE_VALID ? 1 : 0);
printf("\tseg 0x%x key 0x%x ili 0x%x eom 0x%x fmark 0x%x\n",
sense->segment,
SSD_SENSE_KEY(sense->flags),
sense->flags & SSD_ILI ? 1 : 0,
sense->flags & SSD_EOM ? 1 : 0,
sense->flags & SSD_FILEMARK ? 1 : 0);
printf("\ninfo: 0x%x 0x%x 0x%x 0x%x followed by %d "
"extra bytes\n",
sense->info[0],
sense->info[1],
sense->info[2],
sense->info[3],
sense->extra_len);
len = SSD_ADD_BYTES_LIM(sense);
printf("\textra (up to %d bytes): ", len);
for (count = 0; count < len; count++)
printf("0x%x ", sense->csi[count]);
printf("\n");
}
#endif
if (periph->periph_switch->psw_error != NULL) {
SC_DEBUG(periph, SCSIPI_DB2,
("calling private err_handler()\n"));
error = (*periph->periph_switch->psw_error)(xs);
if (error != EJUSTRETURN)
return error;
}
switch (SSD_RCODE(sense->response_code)) {
case 0x00:
return 0;
case 0x04:
if ((periph->periph_flags & PERIPH_REMOVABLE) != 0)
periph->periph_flags &= ~PERIPH_MEDIA_LOADED;
if ((xs->xs_control & XS_CTL_IGNORE_NOT_READY) != 0)
return 0;
return EIO;
case 0x20:
if ((xs->xs_control &
XS_CTL_IGNORE_ILLEGAL_REQUEST) != 0)
return 0;
return EINVAL;
case 0x25:
return EACCES;
case 0x71:
scsipi_printaddr(periph);
key = SSD_SENSE_KEY(sense->flags);
printf(" DEFERRED ERROR, key = 0x%x\n", key);
case 0x70:
if ((sense->response_code & SSD_RCODE_VALID) != 0)
info = _4btol(sense->info);
else
info = 0;
key = SSD_SENSE_KEY(sense->flags);
switch (key) {
case SKEY_NO_SENSE:
case SKEY_RECOVERED_ERROR:
if (xs->resid == xs->datalen && xs->datalen) {
xs->resid = 0;
}
error = 0;
break;
case SKEY_EQUAL:
error = 0;
break;
case SKEY_NOT_READY:
if ((periph->periph_flags & PERIPH_REMOVABLE) != 0)
periph->periph_flags &= ~PERIPH_MEDIA_LOADED;
if ((xs->xs_control & XS_CTL_IGNORE_NOT_READY) != 0)
return 0;
if (sense->asc == 0x3A) {
error = ENODEV;
if (xs->xs_control & XS_CTL_SILENT_NODEV)
return error;
} else
error = EIO;
if ((xs->xs_control & XS_CTL_SILENT) != 0)
return error;
break;
case SKEY_ILLEGAL_REQUEST:
if ((xs->xs_control &
XS_CTL_IGNORE_ILLEGAL_REQUEST) != 0)
return 0;
if ((xs->xs_control & XS_CTL_DISCOVERY) != 0 &&
sense->asc == 0x25 &&
sense->ascq == 0x00)
return EINVAL;
if ((xs->xs_control & XS_CTL_SILENT) != 0)
return EIO;
error = EINVAL;
break;
case SKEY_UNIT_ATTENTION:
if (sense->asc == 0x29 &&
sense->ascq == 0x00) {
return ERESTART;
}
if ((periph->periph_flags & PERIPH_REMOVABLE) != 0)
periph->periph_flags &= ~PERIPH_MEDIA_LOADED;
if ((xs->xs_control &
XS_CTL_IGNORE_MEDIA_CHANGE) != 0 ||
(periph->periph_flags &
PERIPH_REMOVABLE) == 0) {
return ERESTART;
}
if ((xs->xs_control & XS_CTL_SILENT) != 0)
return EIO;
error = EIO;
break;
case SKEY_DATA_PROTECT:
error = EROFS;
break;
case SKEY_BLANK_CHECK:
error = 0;
break;
case SKEY_ABORTED_COMMAND:
if (xs->xs_retries != 0) {
xs->xs_retries--;
error = ERESTART;
} else
error = EIO;
break;
case SKEY_VOLUME_OVERFLOW:
error = ENOSPC;
break;
case SKEY_MEDIUM_ERROR:
if (xs->xs_retries != 0) {
xs->xs_retries--;
error = ERESTART;
} else
error = EIO;
break;
default:
error = EIO;
break;
}
if ((key == 0) ||
((xs->xs_control & XS_CTL_SILENT) != 0) ||
(scsipi_print_sense(xs, 0) != 0))
return error;
scsipi_printaddr(periph);
printf("%s", error_mes[key - 1]);
if ((sense->response_code & SSD_RCODE_VALID) != 0) {
switch (key) {
case SKEY_NOT_READY:
case SKEY_ILLEGAL_REQUEST:
case SKEY_UNIT_ATTENTION:
case SKEY_DATA_PROTECT:
break;
case SKEY_BLANK_CHECK:
printf(", requested size: %d (decimal)",
info);
break;
case SKEY_ABORTED_COMMAND:
if (xs->xs_retries)
printf(", retrying");
printf(", cmd 0x%x, info 0x%x",
xs->cmd->opcode, info);
break;
default:
printf(", info = %d (decimal)", info);
}
}
if (sense->extra_len != 0) {
int n;
printf(", data =");
for (n = 0; n < sense->extra_len; n++)
printf(" %02x",
sense->csi[n]);
}
printf("\n");
return error;
default:
#if defined(SCSIDEBUG) || defined(DEBUG)
{
static const char *uc = "undecodable sense error";
int i;
u_int8_t *cptr = (u_int8_t *) sense;
scsipi_printaddr(periph);
if (xs->cmd == &xs->cmdstore) {
printf("%s for opcode 0x%x, data=",
uc, xs->cmdstore.opcode);
} else {
printf("%s, data=", uc);
}
for (i = 0; i < sizeof (sense); i++)
printf(" 0x%02x", *(cptr++) & 0xff);
printf("\n");
}
#else
scsipi_printaddr(periph);
printf("Sense Error Code 0x%x",
SSD_RCODE(sense->response_code));
if ((sense->response_code & SSD_RCODE_VALID) != 0) {
struct scsi_sense_data_unextended *usense =
(struct scsi_sense_data_unextended *)sense;
printf(" at block no. %d (decimal)",
_3btol(usense->block));
}
printf("\n");
#endif
return EIO;
}
}
int
scsipi_test_unit_ready(struct scsipi_periph *periph, int flags)
{
struct scsi_test_unit_ready cmd;
int retries;
if (periph->periph_quirks & PQUIRK_NOTUR)
return 0;
if (flags & XS_CTL_DISCOVERY)
retries = 0;
else
retries = SCSIPIRETRIES;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_TEST_UNIT_READY;
return scsipi_command(periph, (void *)&cmd, sizeof(cmd), 0, 0,
retries, 10000, NULL, flags);
}
static const struct scsipi_inquiry3_pattern {
const char vendor[8];
const char product[16];
const char revision[4];
} scsipi_inquiry3_quirk[] = {
{ "ES-6600 ", "", "" },
};
static int
scsipi_inquiry3_ok(const struct scsipi_inquiry_data *ib)
{
for (size_t i = 0; i < __arraycount(scsipi_inquiry3_quirk); i++) {
const struct scsipi_inquiry3_pattern *q =
&scsipi_inquiry3_quirk[i];
#define MATCH(field) \
(q->field[0] ? memcmp(ib->field, q->field, sizeof(ib->field)) == 0 : 1)
if (MATCH(vendor) && MATCH(product) && MATCH(revision))
return 0;
}
return 1;
}
int
scsipi_inquire(struct scsipi_periph *periph, struct scsipi_inquiry_data *inqbuf,
int flags)
{
struct scsipi_inquiry cmd;
int error;
int retries;
if (flags & XS_CTL_DISCOVERY)
retries = 0;
else
retries = SCSIPIRETRIES;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = INQUIRY;
cmd.length = SCSIPI_INQUIRY_LENGTH_SCSI2;
error = scsipi_command(periph, (void *)&cmd, sizeof(cmd),
(void *)inqbuf, SCSIPI_INQUIRY_LENGTH_SCSI2, retries,
10000, NULL, flags | XS_CTL_DATA_IN);
if (!error &&
inqbuf->additional_length > SCSIPI_INQUIRY_LENGTH_SCSI2 - 4) {
if (scsipi_inquiry3_ok(inqbuf)) {
#if 0
printf("inquire: addlen=%d, retrying\n", inqbuf->additional_length);
#endif
cmd.length = SCSIPI_INQUIRY_LENGTH_SCSI3;
error = scsipi_command(periph, (void *)&cmd, sizeof(cmd),
(void *)inqbuf, SCSIPI_INQUIRY_LENGTH_SCSI3, retries,
10000, NULL, flags | XS_CTL_DATA_IN);
#if 0
printf("inquire: error=%d\n", error);
#endif
}
}
#ifdef SCSI_OLD_NOINQUIRY
if (error == EINVAL || error == EACCES) {
inqbuf->device = (error == EINVAL ?
SID_QUAL_LU_PRESENT :
SID_QUAL_LU_NOTPRESENT) | T_DIRECT;
inqbuf->dev_qual2 = 0;
inqbuf->version = 0;
inqbuf->response_format = SID_FORMAT_SCSI1;
inqbuf->additional_length = SCSIPI_INQUIRY_LENGTH_SCSI2 - 4;
inqbuf->flags1 = inqbuf->flags2 = inqbuf->flags3 = 0;
memcpy(inqbuf->vendor, "ADAPTEC ACB-4000 ", 28);
error = 0;
}
else if (error == 0 &&
inqbuf->device == (SID_QUAL_LU_PRESENT | T_DIRECT) &&
inqbuf->dev_qual2 == 0 &&
inqbuf->version == 0 &&
inqbuf->response_format == SID_FORMAT_SCSI1) {
inqbuf->device = (SID_QUAL_LU_PRESENT | T_SEQUENTIAL);
inqbuf->dev_qual2 = SID_REMOVABLE;
inqbuf->additional_length = SCSIPI_INQUIRY_LENGTH_SCSI2 - 4;
inqbuf->flags1 = inqbuf->flags2 = inqbuf->flags3 = 0;
memcpy(inqbuf->vendor, "EMULEX MT-02 QIC ", 28);
}
#endif
return error;
}
int
scsipi_prevent(struct scsipi_periph *periph, int type, int flags)
{
struct scsi_prevent_allow_medium_removal cmd;
if (periph->periph_quirks & PQUIRK_NODOORLOCK)
return 0;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL;
cmd.how = type;
return (scsipi_command(periph, (void *)&cmd, sizeof(cmd), 0, 0,
SCSIPIRETRIES, 5000, NULL, flags));
}
int
scsipi_start(struct scsipi_periph *periph, int type, int flags)
{
struct scsipi_start_stop cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = START_STOP;
cmd.byte2 = 0x00;
cmd.how = type;
return scsipi_command(periph, (void *)&cmd, sizeof(cmd), 0, 0,
SCSIPIRETRIES, (type & SSS_START) ? 60000 : 10000, NULL, flags);
}
int
scsipi_mode_sense(struct scsipi_periph *periph, int byte2, int page,
struct scsi_mode_parameter_header_6 *data, int len, int flags, int retries,
int timeout)
{
struct scsi_mode_sense_6 cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_MODE_SENSE_6;
cmd.byte2 = byte2;
cmd.page = page;
cmd.length = len & 0xff;
return scsipi_command(periph, (void *)&cmd, sizeof(cmd),
(void *)data, len, retries, timeout, NULL, flags | XS_CTL_DATA_IN);
}
int
scsipi_mode_sense_big(struct scsipi_periph *periph, int byte2, int page,
struct scsi_mode_parameter_header_10 *data, int len, int flags, int retries,
int timeout)
{
struct scsi_mode_sense_10 cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_MODE_SENSE_10;
cmd.byte2 = byte2;
cmd.page = page;
_lto2b(len, cmd.length);
return scsipi_command(periph, (void *)&cmd, sizeof(cmd),
(void *)data, len, retries, timeout, NULL, flags | XS_CTL_DATA_IN);
}
int
scsipi_mode_select(struct scsipi_periph *periph, int byte2,
struct scsi_mode_parameter_header_6 *data, int len, int flags, int retries,
int timeout)
{
struct scsi_mode_select_6 cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_MODE_SELECT_6;
cmd.byte2 = byte2;
cmd.length = len & 0xff;
return scsipi_command(periph, (void *)&cmd, sizeof(cmd),
(void *)data, len, retries, timeout, NULL, flags | XS_CTL_DATA_OUT);
}
int
scsipi_mode_select_big(struct scsipi_periph *periph, int byte2,
struct scsi_mode_parameter_header_10 *data, int len, int flags, int retries,
int timeout)
{
struct scsi_mode_select_10 cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_MODE_SELECT_10;
cmd.byte2 = byte2;
_lto2b(len, cmd.length);
return scsipi_command(periph, (void *)&cmd, sizeof(cmd),
(void *)data, len, retries, timeout, NULL, flags | XS_CTL_DATA_OUT);
}
void
scsipi_get_opcodeinfo(struct scsipi_periph *periph)
{
u_int8_t *data;
int len = 16*1024;
int rc;
int retries;
struct scsi_repsuppopcode cmd;
if (periph->periph_quirks & PQUIRK_NOREPSUPPOPC ||
periph->periph_type == T_PROCESSOR ||
periph->periph_type == T_CDROM)
return;
scsipi_free_opcodeinfo(periph);
data = malloc(len, M_DEVBUF, M_WAITOK|M_ZERO);
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_MAINTENANCE_IN;
cmd.svcaction = RSOC_REPORT_SUPPORTED_OPCODES;
cmd.repoption = RSOC_RCTD|RSOC_ALL;
_lto4b(len, cmd.alloclen);
retries = 3;
do {
rc = scsipi_command(periph, (void *)&cmd, sizeof(cmd),
(void *)data, len, 0, 60000, NULL,
XS_CTL_DATA_IN|XS_CTL_SILENT);
#ifdef SCSIPI_DEBUG
if (rc != 0) {
SC_DEBUG(periph, SCSIPI_DB3,
("SCSI_MAINTENANCE_IN"
"[RSOC_REPORT_SUPPORTED_OPCODES] command"
" failed: rc=%d, retries=%d\n",
rc, retries));
}
#endif
} while (rc == EIO && retries-- > 0);
if (rc == 0) {
int count;
int dlen = _4btol(data);
u_int8_t *c = data + 4;
SC_DEBUG(periph, SCSIPI_DB3,
("supported opcode timeout-values loaded\n"));
SC_DEBUG(periph, SCSIPI_DB3,
("CMD LEN SA spec nom. time cmd timeout\n"));
struct scsipi_opcodes *tot = malloc(sizeof(struct scsipi_opcodes),
M_DEVBUF, M_WAITOK|M_ZERO);
count = 0;
while (tot != NULL &&
dlen >= (int)sizeof(struct scsi_repsupopcode_all_commands_descriptor)) {
struct scsi_repsupopcode_all_commands_descriptor *acd
= (struct scsi_repsupopcode_all_commands_descriptor *)c;
#ifdef SCSIPI_DEBUG
int cdblen = _2btol((const u_int8_t *)&acd->cdblen);
#endif
dlen -= sizeof(struct scsi_repsupopcode_all_commands_descriptor);
c += sizeof(struct scsi_repsupopcode_all_commands_descriptor);
SC_DEBUG(periph, SCSIPI_DB3,
("0x%02x(%2d) ", acd->opcode, cdblen));
tot->opcode_info[acd->opcode].ti_flags = SCSIPI_TI_VALID;
if (acd->flags & RSOC_ACD_SERVACTV) {
SC_DEBUGN(periph, SCSIPI_DB3,
("0x%02x%02x ",
acd->serviceaction[0],
acd->serviceaction[1]));
} else {
SC_DEBUGN(periph, SCSIPI_DB3, (" "));
}
if (acd->flags & RSOC_ACD_CTDP
&& dlen >= (int)sizeof(struct scsi_repsupopcode_timeouts_descriptor)) {
struct scsi_repsupopcode_timeouts_descriptor *td
= (struct scsi_repsupopcode_timeouts_descriptor *)c;
long nomto = _4btol(td->nom_process_timeout);
long cmdto = _4btol(td->cmd_process_timeout);
long t = (cmdto > nomto) ? cmdto : nomto;
dlen -= sizeof(struct scsi_repsupopcode_timeouts_descriptor);
c += sizeof(struct scsi_repsupopcode_timeouts_descriptor);
SC_DEBUGN(periph, SCSIPI_DB3,
("0x%02x %10ld %10ld",
td->cmd_specific,
nomto, cmdto));
if (t > tot->opcode_info[acd->opcode].ti_timeout) {
tot->opcode_info[acd->opcode].ti_timeout = t;
++count;
}
}
SC_DEBUGN(periph, SCSIPI_DB3,("\n"));
}
if (count > 0) {
periph->periph_opcs = tot;
} else {
free(tot, M_DEVBUF);
SC_DEBUG(periph, SCSIPI_DB3,
("no usable timeout values available\n"));
}
} else {
SC_DEBUG(periph, SCSIPI_DB3,
("SCSI_MAINTENANCE_IN"
"[RSOC_REPORT_SUPPORTED_OPCODES] failed error=%d"
" - no device provided timeout "
"values available\n", rc));
}
free(data, M_DEVBUF);
}
static void
scsipi_update_timeouts(struct scsipi_xfer *xs)
{
struct scsipi_opcodes *opcs;
u_int8_t cmd;
int timeout;
struct scsipi_opinfo *oi;
if (xs->timeout <= 0) {
return;
}
opcs = xs->xs_periph->periph_opcs;
if (opcs == NULL) {
return;
}
cmd = xs->cmd->opcode;
oi = &opcs->opcode_info[cmd];
timeout = 1000 * (int)oi->ti_timeout;
if (timeout > xs->timeout && timeout < 86400000) {
#ifdef SCSIPI_DEBUG
if ((oi->ti_flags & SCSIPI_TI_LOGGED) == 0) {
SC_DEBUG(xs->xs_periph, SCSIPI_DB3,
("Overriding command 0x%02x "
"timeout of %d with %d ms\n",
cmd, xs->timeout, timeout));
oi->ti_flags |= SCSIPI_TI_LOGGED;
}
#endif
xs->timeout = timeout;
}
}
void
scsipi_free_opcodeinfo(struct scsipi_periph *periph)
{
if (periph->periph_opcs != NULL) {
free(periph->periph_opcs, M_DEVBUF);
}
periph->periph_opcs = NULL;
}
void
scsipi_done(struct scsipi_xfer *xs)
{
struct scsipi_periph *periph = xs->xs_periph;
struct scsipi_channel *chan = periph->periph_channel;
int freezecnt;
SC_DEBUG(periph, SCSIPI_DB2, ("scsipi_done\n"));
#ifdef SCSIPI_DEBUG
if (periph->periph_dbflags & SCSIPI_DB1)
show_scsipi_cmd(xs);
#endif
mutex_enter(chan_mtx(chan));
SDT_PROBE1(scsi, base, xfer, done, xs);
if (xs->xs_status & XS_STS_DONE) {
SDT_PROBE1(scsi, base, xfer, redone, xs);
mutex_exit(chan_mtx(chan));
goto out;
}
scsipi_put_resource(chan);
xs->xs_periph->periph_sent--;
if (XS_CTL_TAGTYPE(xs) != 0)
scsipi_put_tag(xs);
else
periph->periph_flags &= ~PERIPH_UNTAG;
xs->xs_status |= XS_STS_DONE;
#ifdef DIAGNOSTIC
if ((xs->xs_control & (XS_CTL_ASYNC|XS_CTL_POLL)) ==
(XS_CTL_ASYNC|XS_CTL_POLL))
panic("scsipi_done: ASYNC and POLL");
#endif
freezecnt = 0;
if (xs->error != XS_NOERROR)
freezecnt++;
if (xs->xs_control & XS_CTL_FREEZE_PERIPH)
freezecnt++;
if (freezecnt != 0)
scsipi_periph_freeze_locked(periph, freezecnt);
if (xs->error == XS_BUSY && xs->status == SCSI_CHECK) {
periph->periph_flags |= PERIPH_SENSE;
periph->periph_xscheck = xs;
}
if ((xs->xs_control & XS_CTL_ASYNC) == 0) {
if (xs->xs_control & XS_CTL_POLL) {
mutex_exit(chan_mtx(chan));
return;
}
cv_broadcast(xs_cv(xs));
mutex_exit(chan_mtx(chan));
goto out;
}
if (xs->error == XS_NOERROR) {
mutex_exit(chan_mtx(chan));
(void) scsipi_complete(xs);
goto out;
}
TAILQ_INSERT_TAIL(&chan->chan_complete, xs, channel_q);
cv_broadcast(chan_cv_complete(chan));
mutex_exit(chan_mtx(chan));
out:
scsipi_run_queue(chan);
}
static int
scsipi_complete(struct scsipi_xfer *xs)
{
struct scsipi_periph *periph = xs->xs_periph;
struct scsipi_channel *chan = periph->periph_channel;
int error;
SDT_PROBE1(scsi, base, xfer, complete, xs);
#ifdef DIAGNOSTIC
if ((xs->xs_control & XS_CTL_ASYNC) != 0 && xs->bp == NULL)
panic("scsipi_complete: XS_CTL_ASYNC but no buf");
#endif
mutex_enter(chan_mtx(chan));
if (xs->error == XS_BUSY && xs->status == SCSI_CHECK) {
if (xs->xs_control & XS_CTL_REQSENSE) {
scsipi_printaddr(periph);
printf("request sense for a request sense ?\n");
scsipi_periph_thaw_locked(periph, 1);
mutex_exit(chan_mtx(chan));
if (xs->resid < xs->datalen) {
printf("we read %d bytes of sense anyway:\n",
xs->datalen - xs->resid);
scsipi_print_sense_data((void *)xs->data, 0);
}
return EINVAL;
}
mutex_exit(chan_mtx(chan));
scsipi_request_sense(xs);
} else
mutex_exit(chan_mtx(chan));
if ((xs->xs_control & XS_CTL_USERCMD) != 0) {
SC_DEBUG(periph, SCSIPI_DB3, ("calling user done()\n"));
mutex_enter(chan_mtx(chan));
if (xs->error != XS_NOERROR)
scsipi_periph_thaw_locked(periph, 1);
mutex_exit(chan_mtx(chan));
scsipi_user_done(xs);
SC_DEBUG(periph, SCSIPI_DB3, ("returned from user done()\n "));
return 0;
}
switch (xs->error) {
case XS_NOERROR:
error = 0;
break;
case XS_SENSE:
case XS_SHORTSENSE:
error = (*chan->chan_bustype->bustype_interpret_sense)(xs);
break;
case XS_RESOURCE_SHORTAGE:
scsipi_printaddr(periph);
printf("adapter resource shortage\n");
case XS_BUSY:
if (xs->error == XS_BUSY && xs->status == SCSI_QUEUE_FULL) {
struct scsipi_max_openings mo;
mo.mo_target = periph->periph_target;
mo.mo_lun = periph->periph_lun;
if (periph->periph_active < periph->periph_openings)
mo.mo_openings = periph->periph_active - 1;
else
mo.mo_openings = periph->periph_openings - 1;
#ifdef DIAGNOSTIC
if (mo.mo_openings < 0) {
scsipi_printaddr(periph);
printf("QUEUE FULL resulted in < 0 openings\n");
panic("scsipi_done");
}
#endif
if (mo.mo_openings == 0) {
scsipi_printaddr(periph);
printf("QUEUE FULL resulted in 0 openings\n");
mo.mo_openings = 1;
}
scsipi_async_event(chan, ASYNC_EVENT_MAX_OPENINGS, &mo);
error = ERESTART;
} else if (xs->xs_retries != 0) {
xs->xs_retries--;
mutex_enter(chan_mtx(chan));
if ((xs->xs_control & XS_CTL_POLL) ||
(chan->chan_flags & SCSIPI_CHAN_TACTIVE) == 0) {
kpause("xsbusy", false, hz, chan_mtx(chan));
} else if (!callout_pending(&periph->periph_callout)) {
scsipi_periph_freeze_locked(periph, 1);
callout_reset(&periph->periph_callout,
hz, scsipi_periph_timed_thaw, periph);
}
mutex_exit(chan_mtx(chan));
error = ERESTART;
} else
error = EBUSY;
break;
case XS_REQUEUE:
error = ERESTART;
break;
case XS_SELTIMEOUT:
case XS_TIMEOUT:
if (scsipi_lookup_periph(chan, periph->periph_target,
periph->periph_lun) && xs->xs_retries != 0) {
xs->xs_retries--;
error = ERESTART;
} else
error = EIO;
break;
case XS_RESET:
if (xs->xs_control & XS_CTL_REQSENSE) {
error = EINTR;
} else {
if (xs->xs_retries != 0) {
xs->xs_retries--;
error = ERESTART;
} else
error = EIO;
}
break;
case XS_DRIVER_STUFFUP:
scsipi_printaddr(periph);
printf("generic HBA error\n");
error = EIO;
break;
default:
scsipi_printaddr(periph);
printf("invalid return code from adapter: %d\n", xs->error);
error = EIO;
break;
}
mutex_enter(chan_mtx(chan));
if (error == ERESTART) {
SDT_PROBE1(scsi, base, xfer, restart, xs);
xs->error = XS_NOERROR;
xs->status = SCSI_OK;
xs->xs_status &= ~XS_STS_DONE;
xs->xs_requeuecnt++;
error = scsipi_enqueue(xs);
if (error == 0) {
scsipi_periph_thaw_locked(periph, 1);
mutex_exit(chan_mtx(chan));
return ERESTART;
}
}
if (xs->error != XS_NOERROR)
scsipi_periph_thaw_locked(periph, 1);
mutex_exit(chan_mtx(chan));
if (periph->periph_switch->psw_done)
periph->periph_switch->psw_done(xs, error);
mutex_enter(chan_mtx(chan));
if (xs->xs_control & XS_CTL_ASYNC)
scsipi_put_xs(xs);
mutex_exit(chan_mtx(chan));
return error;
}
static void
scsipi_request_sense(struct scsipi_xfer *xs)
{
struct scsipi_periph *periph = xs->xs_periph;
int flags, error;
struct scsi_request_sense cmd;
periph->periph_flags |= PERIPH_SENSE;
flags = xs->xs_control & XS_CTL_POLL;
if (flags)
flags |= XS_CTL_NOSLEEP;
flags |= XS_CTL_REQSENSE | XS_CTL_URGENT | XS_CTL_DATA_IN |
XS_CTL_THAW_PERIPH | XS_CTL_FREEZE_PERIPH;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_REQUEST_SENSE;
cmd.length = sizeof(struct scsi_sense_data);
error = scsipi_command(periph, (void *)&cmd, sizeof(cmd),
(void *)&xs->sense.scsi_sense, sizeof(struct scsi_sense_data),
0, 1000, NULL, flags);
periph->periph_flags &= ~PERIPH_SENSE;
periph->periph_xscheck = NULL;
switch (error) {
case 0:
xs->error = XS_SENSE;
return;
case EINTR:
xs->error = XS_RESET;
return;
case EIO:
xs->error = XS_DRIVER_STUFFUP;
return;
default:
xs->error = XS_DRIVER_STUFFUP;
scsipi_printaddr(periph);
printf("request sense failed with error %d\n", error);
return;
}
}
static int
scsipi_enqueue(struct scsipi_xfer *xs)
{
struct scsipi_channel *chan = xs->xs_periph->periph_channel;
struct scsipi_xfer *qxs;
SDT_PROBE1(scsi, base, xfer, enqueue, xs);
KASSERT(mutex_owned(chan_mtx(chan)));
if ((xs->xs_control & XS_CTL_POLL) != 0 &&
TAILQ_FIRST(&chan->chan_queue) != NULL) {
xs->error = XS_DRIVER_STUFFUP;
return EAGAIN;
}
if (xs->xs_control & XS_CTL_URGENT) {
TAILQ_INSERT_HEAD(&chan->chan_queue, xs, channel_q);
goto out;
}
if (xs->xs_requeuecnt != 0) {
for (qxs = TAILQ_FIRST(&chan->chan_queue); qxs != NULL;
qxs = TAILQ_NEXT(qxs, channel_q)) {
if (qxs->xs_periph == xs->xs_periph &&
qxs->xs_requeuecnt < xs->xs_requeuecnt)
break;
}
if (qxs != NULL) {
TAILQ_INSERT_AFTER(&chan->chan_queue, qxs, xs,
channel_q);
goto out;
}
}
TAILQ_INSERT_TAIL(&chan->chan_queue, xs, channel_q);
out:
if (xs->xs_control & XS_CTL_THAW_PERIPH)
scsipi_periph_thaw_locked(xs->xs_periph, 1);
return 0;
}
static void
scsipi_run_queue(struct scsipi_channel *chan)
{
struct scsipi_xfer *xs;
struct scsipi_periph *periph;
SDT_PROBE1(scsi, base, queue, batch__start, chan);
for (;;) {
mutex_enter(chan_mtx(chan));
if (chan->chan_qfreeze != 0) {
mutex_exit(chan_mtx(chan));
break;
}
for (xs = TAILQ_FIRST(&chan->chan_queue); xs != NULL;
xs = TAILQ_NEXT(xs, channel_q)) {
periph = xs->xs_periph;
if ((periph->periph_sent >= periph->periph_openings) ||
periph->periph_qfreeze != 0 ||
(periph->periph_flags & PERIPH_UNTAG) != 0)
continue;
if ((periph->periph_flags &
(PERIPH_RECOVERING | PERIPH_SENSE)) != 0 &&
(xs->xs_control & XS_CTL_URGENT) == 0)
continue;
goto got_one;
}
mutex_exit(chan_mtx(chan));
break;
got_one:
if (scsipi_get_resource(chan) == 0) {
if (scsipi_grow_resources(chan) == 0) {
if (xs->xs_control & XS_CTL_POLL) {
scsipi_printaddr(xs->xs_periph);
printf("polling command but no "
"adapter resources");
}
mutex_exit(chan_mtx(chan));
break;
}
}
TAILQ_REMOVE(&chan->chan_queue, xs, channel_q);
if (XS_CTL_TAGTYPE(xs) != 0)
scsipi_get_tag(xs);
else
periph->periph_flags |= PERIPH_UNTAG;
periph->periph_sent++;
mutex_exit(chan_mtx(chan));
SDT_PROBE2(scsi, base, queue, run, chan, xs);
scsipi_adapter_request(chan, ADAPTER_REQ_RUN_XFER, xs);
}
SDT_PROBE1(scsi, base, queue, batch__done, chan);
}
int
scsipi_execute_xs(struct scsipi_xfer *xs)
{
struct scsipi_periph *periph = xs->xs_periph;
struct scsipi_channel *chan = periph->periph_channel;
int oasync, async, poll, error;
KASSERT(!cold);
scsipi_update_timeouts(xs);
(chan->chan_bustype->bustype_cmd)(xs);
xs->xs_status &= ~XS_STS_DONE;
xs->error = XS_NOERROR;
xs->resid = xs->datalen;
xs->status = SCSI_OK;
SDT_PROBE1(scsi, base, xfer, execute, xs);
#ifdef SCSIPI_DEBUG
if (xs->xs_periph->periph_dbflags & SCSIPI_DB3) {
printf("scsipi_execute_xs: ");
show_scsipi_xs(xs);
printf("\n");
}
#endif
if ((PERIPH_XFER_MODE(periph) & PERIPH_CAP_TQING) == 0 ||
(xs->xs_control & XS_CTL_REQSENSE)) {
xs->xs_control &= ~XS_CTL_TAGMASK;
xs->xs_tag_type = 0;
} else {
if (XS_CTL_TAGTYPE(xs) == 0) {
if (xs->xs_control & XS_CTL_URGENT)
xs->xs_control |= XS_CTL_HEAD_TAG;
else
xs->xs_control |= XS_CTL_SIMPLE_TAG;
}
switch (XS_CTL_TAGTYPE(xs)) {
case XS_CTL_ORDERED_TAG:
xs->xs_tag_type = MSG_ORDERED_Q_TAG;
break;
case XS_CTL_SIMPLE_TAG:
xs->xs_tag_type = MSG_SIMPLE_Q_TAG;
break;
case XS_CTL_HEAD_TAG:
xs->xs_tag_type = MSG_HEAD_OF_Q_TAG;
break;
default:
scsipi_printaddr(periph);
printf("invalid tag mask 0x%08x\n",
XS_CTL_TAGTYPE(xs));
panic("scsipi_execute_xs");
}
}
if (chan->chan_adapter->adapt_flags & SCSIPI_ADAPT_POLL_ONLY)
xs->xs_control |= XS_CTL_POLL;
oasync = (xs->xs_control & XS_CTL_ASYNC);
if (chan->chan_thread == NULL || (xs->xs_control & XS_CTL_POLL) != 0)
xs->xs_control &= ~XS_CTL_ASYNC;
async = (xs->xs_control & XS_CTL_ASYNC);
poll = (xs->xs_control & XS_CTL_POLL);
#ifdef DIAGNOSTIC
if (oasync != 0 && xs->bp == NULL)
panic("scsipi_execute_xs: XS_CTL_ASYNC but no buf");
#endif
error = scsipi_enqueue(xs);
if (error) {
if (poll == 0) {
scsipi_printaddr(periph);
printf("not polling, but enqueue failed with %d\n",
error);
panic("scsipi_execute_xs");
}
scsipi_printaddr(periph);
printf("should have flushed queue?\n");
goto free_xs;
}
mutex_exit(chan_mtx(chan));
restarted:
scsipi_run_queue(chan);
mutex_enter(chan_mtx(chan));
if (async)
return 0;
while ((xs->xs_status & XS_STS_DONE) == 0) {
if (poll) {
scsipi_printaddr(periph);
printf("polling command not done\n");
panic("scsipi_execute_xs");
}
cv_wait(xs_cv(xs), chan_mtx(chan));
}
mutex_exit(chan_mtx(chan));
error = scsipi_complete(xs);
if (error == ERESTART)
goto restarted;
if (oasync)
error = 0;
mutex_enter(chan_mtx(chan));
free_xs:
scsipi_put_xs(xs);
mutex_exit(chan_mtx(chan));
scsipi_run_queue(chan);
mutex_enter(chan_mtx(chan));
return error;
}
static void
scsipi_completion_thread(void *arg)
{
struct scsipi_channel *chan = arg;
struct scsipi_xfer *xs;
if (chan->chan_init_cb)
(*chan->chan_init_cb)(chan, chan->chan_init_cb_arg);
mutex_enter(chan_mtx(chan));
chan->chan_flags |= SCSIPI_CHAN_TACTIVE;
for (;;) {
xs = TAILQ_FIRST(&chan->chan_complete);
if (xs == NULL && chan->chan_tflags == 0) {
cv_wait(chan_cv_complete(chan), chan_mtx(chan));
continue;
}
if (chan->chan_tflags & SCSIPI_CHANT_CALLBACK) {
chan->chan_tflags &= ~SCSIPI_CHANT_CALLBACK;
chan->chan_callback(chan, chan->chan_callback_arg);
continue;
}
if (chan->chan_tflags & SCSIPI_CHANT_GROWRES) {
chan->chan_tflags &= ~SCSIPI_CHANT_GROWRES;
mutex_exit(chan_mtx(chan));
scsipi_adapter_request(chan,
ADAPTER_REQ_GROW_RESOURCES, NULL);
scsipi_channel_thaw(chan, 1);
if (chan->chan_tflags & SCSIPI_CHANT_GROWRES)
kpause("scsizzz", FALSE, hz/10, NULL);
mutex_enter(chan_mtx(chan));
continue;
}
if (chan->chan_tflags & SCSIPI_CHANT_KICK) {
chan->chan_tflags &= ~SCSIPI_CHANT_KICK;
mutex_exit(chan_mtx(chan));
scsipi_run_queue(chan);
mutex_enter(chan_mtx(chan));
continue;
}
if (chan->chan_tflags & SCSIPI_CHANT_SHUTDOWN) {
break;
}
if (xs) {
TAILQ_REMOVE(&chan->chan_complete, xs, channel_q);
mutex_exit(chan_mtx(chan));
(void) scsipi_complete(xs);
scsipi_run_queue(chan);
mutex_enter(chan_mtx(chan));
}
}
chan->chan_thread = NULL;
cv_broadcast(chan_cv_thread(chan));
mutex_exit(chan_mtx(chan));
kthread_exit(0);
}
int
scsipi_thread_call_callback(struct scsipi_channel *chan,
void (*callback)(struct scsipi_channel *, void *), void *arg)
{
mutex_enter(chan_mtx(chan));
if ((chan->chan_flags & SCSIPI_CHAN_TACTIVE) == 0) {
mutex_exit(chan_mtx(chan));
return ESRCH;
}
if (chan->chan_tflags & SCSIPI_CHANT_CALLBACK) {
mutex_exit(chan_mtx(chan));
return EBUSY;
}
scsipi_channel_freeze(chan, 1);
chan->chan_callback = callback;
chan->chan_callback_arg = arg;
chan->chan_tflags |= SCSIPI_CHANT_CALLBACK;
cv_broadcast(chan_cv_complete(chan));
mutex_exit(chan_mtx(chan));
return 0;
}
void
scsipi_async_event(struct scsipi_channel *chan, scsipi_async_event_t event,
void *arg)
{
bool lock = chan_running(chan) > 0;
if (lock)
mutex_enter(chan_mtx(chan));
switch (event) {
case ASYNC_EVENT_MAX_OPENINGS:
scsipi_async_event_max_openings(chan,
(struct scsipi_max_openings *)arg);
break;
case ASYNC_EVENT_XFER_MODE:
if (chan->chan_bustype->bustype_async_event_xfer_mode) {
chan->chan_bustype->bustype_async_event_xfer_mode(
chan, arg);
}
break;
case ASYNC_EVENT_RESET:
scsipi_async_event_channel_reset(chan);
break;
}
if (lock)
mutex_exit(chan_mtx(chan));
}
static void
scsipi_async_event_max_openings(struct scsipi_channel *chan,
struct scsipi_max_openings *mo)
{
struct scsipi_periph *periph;
int minlun, maxlun;
if (mo->mo_lun == -1) {
minlun = 0;
maxlun = chan->chan_nluns - 1;
} else
minlun = maxlun = mo->mo_lun;
for (; minlun <= maxlun; minlun++) {
periph = scsipi_lookup_periph_locked(chan, mo->mo_target, minlun);
if (periph == NULL)
continue;
if (mo->mo_openings < periph->periph_openings)
periph->periph_openings = mo->mo_openings;
else if (mo->mo_openings > periph->periph_openings &&
(periph->periph_flags & PERIPH_GROW_OPENINGS) != 0)
periph->periph_openings = mo->mo_openings;
}
}
void
scsipi_set_xfer_mode(struct scsipi_channel *chan, int target, int immed)
{
struct scsipi_xfer_mode xm;
struct scsipi_periph *itperiph;
int lun;
xm.xm_target = target;
xm.xm_mode = 0;
xm.xm_period = 0;
xm.xm_offset = 0;
for (itperiph = NULL, lun = 0; lun < chan->chan_nluns; lun++) {
itperiph = scsipi_lookup_periph(chan, target, lun);
if (itperiph != NULL)
break;
}
if (itperiph != NULL) {
xm.xm_mode = itperiph->periph_cap;
scsipi_adapter_request(chan, ADAPTER_REQ_SET_XFER_MODE, &xm);
if (immed != 0) {
(void) scsipi_test_unit_ready(itperiph,
XS_CTL_DISCOVERY | XS_CTL_IGNORE_ILLEGAL_REQUEST |
XS_CTL_IGNORE_NOT_READY |
XS_CTL_IGNORE_MEDIA_CHANGE);
}
}
}
static void
scsipi_async_event_channel_reset(struct scsipi_channel *chan)
{
struct scsipi_xfer *xs, *xs_next;
struct scsipi_periph *periph;
int target, lun;
for (xs = TAILQ_FIRST(&chan->chan_queue); xs != NULL; xs = xs_next) {
xs_next = TAILQ_NEXT(xs, channel_q);
if (xs->xs_control & XS_CTL_REQSENSE) {
TAILQ_REMOVE(&chan->chan_queue, xs, channel_q);
xs->error = XS_RESET;
if ((xs->xs_control & XS_CTL_ASYNC) != 0)
TAILQ_INSERT_TAIL(&chan->chan_complete, xs,
channel_q);
}
}
cv_broadcast(chan_cv_complete(chan));
for (target = 0; target < chan->chan_ntargets; target++) {
if (target == chan->chan_id)
continue;
for (lun = 0; lun < chan->chan_nluns; lun++) {
periph = scsipi_lookup_periph_locked(chan, target, lun);
if (periph) {
xs = periph->periph_xscheck;
if (xs)
xs->error = XS_RESET;
}
}
}
}
int
scsipi_target_detach(struct scsipi_channel *chan, int target, int lun,
int flags)
{
struct scsipi_periph *periph;
device_t tdev;
int ctarget, mintarget, maxtarget;
int clun, minlun, maxlun;
int error = 0;
if (target == -1) {
mintarget = 0;
maxtarget = chan->chan_ntargets;
} else {
if (target == chan->chan_id)
return EINVAL;
if (target < 0 || target >= chan->chan_ntargets)
return EINVAL;
mintarget = target;
maxtarget = target + 1;
}
if (lun == -1) {
minlun = 0;
maxlun = chan->chan_nluns;
} else {
if (lun < 0 || lun >= chan->chan_nluns)
return EINVAL;
minlun = lun;
maxlun = lun + 1;
}
KERNEL_LOCK(1, curlwp);
mutex_enter(chan_mtx(chan));
for (ctarget = mintarget; ctarget < maxtarget; ctarget++) {
if (ctarget == chan->chan_id)
continue;
for (clun = minlun; clun < maxlun; clun++) {
periph = scsipi_lookup_periph_locked(chan, ctarget, clun);
if (periph == NULL)
continue;
tdev = periph->periph_dev;
mutex_exit(chan_mtx(chan));
error = config_detach(tdev, flags);
if (error)
goto out;
mutex_enter(chan_mtx(chan));
KASSERT(scsipi_lookup_periph_locked(chan, ctarget, clun) == NULL);
}
}
mutex_exit(chan_mtx(chan));
out:
KERNEL_UNLOCK_ONE(curlwp);
return error;
}
int
scsipi_adapter_addref(struct scsipi_adapter *adapt)
{
int error = 0;
if (atomic_inc_uint_nv(&adapt->adapt_refcnt) == 1
&& adapt->adapt_enable != NULL) {
scsipi_adapter_lock(adapt);
error = scsipi_adapter_enable(adapt, 1);
scsipi_adapter_unlock(adapt);
if (error)
atomic_dec_uint(&adapt->adapt_refcnt);
}
return error;
}
void
scsipi_adapter_delref(struct scsipi_adapter *adapt)
{
membar_release();
if (atomic_dec_uint_nv(&adapt->adapt_refcnt) == 0
&& adapt->adapt_enable != NULL) {
membar_acquire();
scsipi_adapter_lock(adapt);
(void) scsipi_adapter_enable(adapt, 0);
scsipi_adapter_unlock(adapt);
}
}
static struct scsipi_syncparam {
int ss_factor;
int ss_period;
} scsipi_syncparams[] = {
{ 0x08, 625 },
{ 0x09, 1250 },
{ 0x0a, 2500 },
{ 0x0b, 3030 },
{ 0x0c, 5000 },
};
static const int scsipi_nsyncparams =
sizeof(scsipi_syncparams) / sizeof(scsipi_syncparams[0]);
int
scsipi_sync_period_to_factor(int period )
{
int i;
for (i = 0; i < scsipi_nsyncparams; i++) {
if (period <= scsipi_syncparams[i].ss_period)
return scsipi_syncparams[i].ss_factor;
}
return (period / 100) / 4;
}
int
scsipi_sync_factor_to_period(int factor)
{
int i;
for (i = 0; i < scsipi_nsyncparams; i++) {
if (factor == scsipi_syncparams[i].ss_factor)
return scsipi_syncparams[i].ss_period;
}
return (factor * 4) * 100;
}
int
scsipi_sync_factor_to_freq(int factor)
{
int i;
for (i = 0; i < scsipi_nsyncparams; i++) {
if (factor == scsipi_syncparams[i].ss_factor)
return 100000000 / scsipi_syncparams[i].ss_period;
}
return 10000000 / ((factor * 4) * 10);
}
static inline void
scsipi_adapter_lock(struct scsipi_adapter *adapt)
{
if ((adapt->adapt_flags & SCSIPI_ADAPT_MPSAFE) == 0)
KERNEL_LOCK(1, NULL);
}
static inline void
scsipi_adapter_unlock(struct scsipi_adapter *adapt)
{
if ((adapt->adapt_flags & SCSIPI_ADAPT_MPSAFE) == 0)
KERNEL_UNLOCK_ONE(NULL);
}
void
scsipi_adapter_minphys(struct scsipi_channel *chan, struct buf *bp)
{
struct scsipi_adapter *adapt = chan->chan_adapter;
scsipi_adapter_lock(adapt);
(adapt->adapt_minphys)(bp);
scsipi_adapter_unlock(chan->chan_adapter);
}
void
scsipi_adapter_request(struct scsipi_channel *chan,
scsipi_adapter_req_t req, void *arg)
{
struct scsipi_adapter *adapt = chan->chan_adapter;
scsipi_adapter_lock(adapt);
SDT_PROBE3(scsi, base, adapter, request__start, chan, req, arg);
(adapt->adapt_request)(chan, req, arg);
SDT_PROBE3(scsi, base, adapter, request__done, chan, req, arg);
scsipi_adapter_unlock(adapt);
}
int
scsipi_adapter_ioctl(struct scsipi_channel *chan, u_long cmd,
void *data, int flag, struct proc *p)
{
struct scsipi_adapter *adapt = chan->chan_adapter;
int error;
if (adapt->adapt_ioctl == NULL)
return ENOTTY;
scsipi_adapter_lock(adapt);
error = (adapt->adapt_ioctl)(chan, cmd, data, flag, p);
scsipi_adapter_unlock(adapt);
return error;
}
int
scsipi_adapter_enable(struct scsipi_adapter *adapt, int enable)
{
int error;
scsipi_adapter_lock(adapt);
error = (adapt->adapt_enable)(adapt->adapt_dev, enable);
scsipi_adapter_unlock(adapt);
return error;
}
#ifdef SCSIPI_DEBUG
void
show_scsipi_xs(struct scsipi_xfer *xs)
{
printf("xs(%p): ", xs);
printf("xs_control(0x%08x)", xs->xs_control);
printf("xs_status(0x%08x)", xs->xs_status);
printf("periph(%p)", xs->xs_periph);
printf("retr(0x%x)", xs->xs_retries);
printf("timo(0x%x)", xs->timeout);
printf("cmd(%p)", xs->cmd);
printf("len(0x%x)", xs->cmdlen);
printf("data(%p)", xs->data);
printf("len(0x%x)", xs->datalen);
printf("res(0x%x)", xs->resid);
printf("err(0x%x)", xs->error);
printf("bp(%p)", xs->bp);
show_scsipi_cmd(xs);
}
void
show_scsipi_cmd(struct scsipi_xfer *xs)
{
u_char *b = (u_char *) xs->cmd;
int i = 0;
scsipi_printaddr(xs->xs_periph);
printf(" command: ");
if ((xs->xs_control & XS_CTL_RESET) == 0) {
while (i < xs->cmdlen) {
if (i)
printf(",");
printf("0x%x", b[i++]);
}
printf("-[%d bytes]\n", xs->datalen);
if (xs->datalen)
show_mem(xs->data, uimin(64, xs->datalen));
} else
printf("-RESET-\n");
}
void
show_mem(u_char *address, int num)
{
int x;
printf("------------------------------");
for (x = 0; x < num; x++) {
if ((x % 16) == 0)
printf("\n%03d: ", x);
printf("%02x ", *address++);
}
printf("\n------------------------------\n");
}
#endif