/*      $NetBSD: scsipi_base.c,v 1.193 2024/10/29 15:50:07 nat Exp $    */

/*-
* Copyright (c) 1998, 1999, 2000, 2002, 2003, 2004 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Charles M. Hannum; by Jason R. Thorpe of the Numerical Aerospace
* Simulation Facility, NASA Ames Research Center.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

#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 *"/*xs*/, "uint8_t"/*tag*/, "uint8_t"/*type*/);
SDT_PROBE_DEFINE3(scsi, base, tag, put,
   "struct scsipi_xfer *"/*xs*/, "uint8_t"/*tag*/, "uint8_t"/*type*/);

SDT_PROBE_DEFINE3(scsi, base, adapter, request__start,
   "struct scsipi_channel *"/*chan*/,
   "scsipi_adapter_req_t"/*req*/,
   "void *"/*arg*/);
SDT_PROBE_DEFINE3(scsi, base, adapter, request__done,
   "struct scsipi_channel *"/*chan*/,
   "scsipi_adapter_req_t"/*req*/,
   "void *"/*arg*/);

SDT_PROBE_DEFINE1(scsi, base, queue, batch__start,
   "struct scsipi_channel *"/*chan*/);
SDT_PROBE_DEFINE2(scsi, base, queue, run,
   "struct scsipi_channel *"/*chan*/,
   "struct scsipi_xfer *"/*xs*/);
SDT_PROBE_DEFINE1(scsi, base, queue, batch__done,
   "struct scsipi_channel *"/*chan*/);

SDT_PROBE_DEFINE1(scsi, base, xfer, execute,  "struct scsipi_xfer *"/*xs*/);
SDT_PROBE_DEFINE1(scsi, base, xfer, enqueue,  "struct scsipi_xfer *"/*xs*/);
SDT_PROBE_DEFINE1(scsi, base, xfer, done,  "struct scsipi_xfer *"/*xs*/);
SDT_PROBE_DEFINE1(scsi, base, xfer, redone,  "struct scsipi_xfer *"/*xs*/);
SDT_PROBE_DEFINE1(scsi, base, xfer, complete,  "struct scsipi_xfer *"/*xs*/);
SDT_PROBE_DEFINE1(scsi, base, xfer, restart,  "struct scsipi_xfer *"/*xs*/);
SDT_PROBE_DEFINE1(scsi, base, xfer, free,  "struct scsipi_xfer *"/*xs*/);

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;

/*
* scsipi_init:
*
*      Called when a scsibus or atapibus is attached to the system
*      to initialize shared data structures.
*/
void
scsipi_init(void)
{
       static int scsipi_init_done;

       if (scsipi_init_done)
               return;
       scsipi_init_done = 1;

       /* Initialize the scsipi_xfer pool. */
       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();
}

/*
* scsipi_channel_init:
*
*      Initialize a scsipi_channel when it is attached.
*/
int
scsipi_channel_init(struct scsipi_channel *chan)
{
       struct scsipi_adapter *adapt = chan->chan_adapter;
       int i;

       /* Initialize shared data. */
       scsipi_init();

       /* Initialize the queues. */
       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]);

       /*
        * Create the asynchronous completion thread.
        */
       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;
}

/*
* scsipi_channel_shutdown:
*
*      Shutdown a scsipi_channel.
*/
void
scsipi_channel_shutdown(struct scsipi_channel *chan)
{

       mutex_enter(chan_mtx(chan));
       /*
        * Shut down the completion thread.
        */
       chan->chan_tflags |= SCSIPI_CHANT_SHUTDOWN;
       cv_broadcast(chan_cv_complete(chan));

       /*
        * Now wait for the thread to exit.
        */
       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;
}

/*
* scsipi_insert_periph:
*
*      Insert a periph into the channel.
*/
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));
}

/*
* scsipi_remove_periph:
*
*      Remove a periph from the channel.
*/
void
scsipi_remove_periph(struct scsipi_channel *chan,
   struct scsipi_periph *periph)
{

       LIST_REMOVE(periph, periph_hash);
}

/*
* scsipi_lookup_periph:
*
*      Lookup a periph on the specified channel.
*/
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);
}

/*
* scsipi_get_resource:
*
*      Allocate a single xfer `resource' from the channel.
*
*      NOTE: Must be called with channel lock held
*/
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;
}

/*
* scsipi_grow_resources:
*
*      Attempt to grow resources for a channel.  If this succeeds,
*      we allocate one for our caller.
*
*      NOTE: Must be called with channel lock held
*/
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);
               }
               /*
                * ask the channel thread to do it. It'll have to thaw the
                * queue
                */
               scsipi_channel_freeze_locked(chan, 1);
               chan->chan_tflags |= SCSIPI_CHANT_GROWRES;
               cv_broadcast(chan_cv_complete(chan));
               return 0;
       }

       return 0;
}

/*
* scsipi_put_resource:
*
*      Free a single xfer `resource' to the channel.
*
*      NOTE: Must be called with channel lock held
*/
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++;
}

/*
* scsipi_get_tag:
*
*      Get a tag ID for the specified xfer.
*
*      NOTE: Must be called with channel lock held
*/
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;        /* XXX gcc */
       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;

       /* XXX Should eventually disallow this completely. */
       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);
}

/*
* scsipi_put_tag:
*
*      Put the tag ID for the specified xfer back into the pool.
*
*      NOTE: Must be called with channel lock held
*/
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);
}

/*
* scsipi_get_xs:
*
*      Allocate an xfer descriptor and associate it with the
*      specified peripheral.  If the peripheral has no more
*      available command openings, we either block waiting for
*      one to become available, or fail.
*
*      When this routine is called with the channel lock held
*      the flags must include XS_CTL_NOSLEEP.
*/
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
       /*
        * URGENT commands can never be ASYNC.
        */
       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

       /*
        * Wait for a command opening to become available.  Rules:
        *
        *      - All xfers must wait for an available opening.
        *        Exception: URGENT xfers can proceed when
        *        active == openings, because we use the opening
        *        of the command we're recovering for.
        *      - if the periph has sense pending, only URGENT & REQSENSE
        *        xfers may proceed.
        *
        *      - If the periph is recovering, only URGENT xfers may
        *        proceed.
        *
        *      - If the periph is currently executing a recovery
        *        command, URGENT commands must block, because only
        *        one recovery command can execute at a time.
        */
       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;
}

/*
* scsipi_put_xs:
*
*      Release an xfer descriptor, decreasing the outstanding command
*      count for the peripheral.  If there is a thread waiting for
*      an opening, wake it up.  If not, kick any queued I/O the
*      peripheral may have.
*
*      NOTE: Must be called with channel lock held
*/
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);
               }
       }
}

/*
* scsipi_channel_freeze:
*
*      Freeze a channel's xfer queue.
*/
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;
}

/*
* scsipi_channel_thaw:
*
*      Thaw a channel's xfer queue.
*/
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;
       /*
        * Don't let the freeze count go negative.
        *
        * Presumably the adapter driver could keep track of this,
        * but it might just be easier to do this here so as to allow
        * multiple callers, including those outside the adapter driver.
        */
       if (chan->chan_qfreeze < 0) {
               chan->chan_qfreeze = 0;
       }
       if (lock)
               mutex_exit(chan_mtx(chan));

       /*
        * until the channel is running
        */
       if (!lock)
               return;

       /*
        * Kick the channel's queue here.  Note, we may be running in
        * interrupt context (softclock or HBA's interrupt), so the adapter
        * driver had better not sleep.
        */
       if (chan->chan_qfreeze == 0)
               scsipi_run_queue(chan);
}

/*
* scsipi_channel_timed_thaw:
*
*      Thaw a channel after some time has expired. This will also
*      run the channel's queue if the freeze count has reached 0.
*/
void
scsipi_channel_timed_thaw(void *arg)
{
       struct scsipi_channel *chan = arg;

       scsipi_channel_thaw(chan, 1);
}

/*
* scsipi_periph_freeze:
*
*      Freeze a device's xfer queue.
*/
void
scsipi_periph_freeze_locked(struct scsipi_periph *periph, int count)
{

       periph->periph_qfreeze += count;
}

/*
* scsipi_periph_thaw:
*
*      Thaw a device's xfer queue.
*/
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));
}

/*
* scsipi_periph_timed_thaw:
*
*      Thaw a device after some time has expired.
*/
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) {
               /*
                * Kick the channel's queue here.  Note, we're running in
                * interrupt context (softclock), so the adapter driver
                * had better not sleep.
                */
               mutex_exit(chan_mtx(chan));
               scsipi_run_queue(periph->periph_channel);
       } else {
               /*
                * Tell the completion thread to kick the channel's queue here.
                */
               periph->periph_channel->chan_tflags |= SCSIPI_CHANT_KICK;
               cv_broadcast(chan_cv_complete(chan));
               mutex_exit(chan_mtx(chan));
       }
}

/*
* scsipi_wait_drain:
*
*      Wait for a periph's pending xfers to drain.
*/
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));
}

/*
* scsipi_kill_pending:
*
*      Kill off all pending xfers for a periph.
*
*      NOTE: Must be called with channel lock held
*/
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));
       }
}

/*
* scsipi_print_cdb:
* prints a command descriptor block (for debug purpose, error messages,
* SCSIVERBOSE, ...)
*/
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++) /* already done the opcode */
               printf(" %02x", cmd->bytes[i]);
}

/*
* scsipi_interpret_sense:
*
*      Look at the returned sense and act on the error, determining
*      the unix error number to pass back.  (0 = report no error)
*
*      NOTE: If we return ERESTART, we are expected to have
*      thawed the device!
*
*      THIS IS THE DEFAULT ERROR HANDLER FOR SCSI DEVICES.
*/
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 the periph has its 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 (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;
       }
       /* otherwise use the default */
       switch (SSD_RCODE(sense->response_code)) {

               /*
                * Old SCSI-1 and SASI devices respond with
                * codes other than 70.
                */
       case 0x00:              /* no error (command completed OK) */
               return 0;
       case 0x04:              /* drive not ready after it was selected */
               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;
               /* XXX - display some sort of error here? */
               return EIO;
       case 0x20:              /* invalid command */
               if ((xs->xs_control &
                    XS_CTL_IGNORE_ILLEGAL_REQUEST) != 0)
                       return 0;
               return EINVAL;
       case 0x25:              /* invalid LUN (Adaptec ACB-4000) */
               return EACCES;

               /*
                * If it's code 70, use the extended stuff and
                * interpret the key
                */
       case 0x71:              /* delayed error */
               scsipi_printaddr(periph);
               key = SSD_SENSE_KEY(sense->flags);
               printf(" DEFERRED ERROR, key = 0x%x\n", key);
               /* FALLTHROUGH */
       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) {
                               /*
                                * Why is this here?
                                */
                               xs->resid = 0;  /* not short read */
                       }
                       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; /* Medium not present */
                               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;
                       /*
                        * Handle the case where a device reports
                        * Logical Unit Not Supported during discovery.
                        */
                       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) {
                               /* device or bus reset */
                               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 ||
                               /* XXX Should reupload any transient state. */
                               (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;
               }

               /* Print verbose decode if appropriate and possible */
               if ((key == 0) ||
                   ((xs->xs_control & XS_CTL_SILENT) != 0) ||
                   (scsipi_print_sense(xs, 0) != 0))
                       return error;

               /* Print brief(er) sense information */
               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;

       /*
        * Some other code, just report it
        */
       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;
       }
}

/*
* scsipi_test_unit_ready:
*
*      Issue a `test unit ready' request.
*/
int
scsipi_test_unit_ready(struct scsipi_periph *periph, int flags)
{
       struct scsi_test_unit_ready cmd;
       int retries;

       /* some ATAPI drives don't support TEST UNIT READY. Sigh */
       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;
}

/*
* scsipi_inquire:
*
*      Ask the device about itself.
*/
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;

       /*
        * If we request more data than the device can provide, it SHOULD just
        * return a short response.  However, some devices error with an
        * ILLEGAL REQUEST sense code, and yet others have even more special
        * failure modes (such as the GL641USB flash adapter, which goes loony
        * and sends corrupted CRCs).  To work around this, and to bring our
        * behavior more in line with other OSes, we do a shorter inquiry,
        * covering all the SCSI-2 information, first, and then request more
        * data iff the "additional length" field indicates there is more.
        * - mycroft, 2003/10/16
        */
       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
       /*
        * Kludge for the Adaptec ACB-4000 SCSI->MFM translator.
        * This board doesn't support the INQUIRY command at all.
        */
       if (error == EINVAL || error == EACCES) {
               /*
                * Conjure up an INQUIRY response.
                */
               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;
       }

       /*
        * Kludge for the Emulex MT-02 SCSI->QIC translator.
        * This board gives an empty response to an INQUIRY command.
        */
       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) {
               /*
                * Fill out the INQUIRY response.
                */
               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 /* SCSI_OLD_NOINQUIRY */

       return error;
}

/*
* scsipi_prevent:
*
*      Prevent or allow the user to remove the media
*/
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));
}

/*
* scsipi_start:
*
*      Send a START UNIT.
*/
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);
}

/*
* scsipi_mode_sense, scsipi_mode_sense_big:
*      get a sense page from a device
*/

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);
}

/*
* scsipi_get_opcodeinfo:
*
* query the device for supported commands and their timeout
* building a timeout lookup table if timeout information is available.
*/
void
scsipi_get_opcodeinfo(struct scsipi_periph *periph)
{
       u_int8_t *data;
       int len = 16*1024;
       int rc;
       int retries;
       struct scsi_repsuppopcode cmd;

       /* refrain from asking for supported opcodes */
       if (periph->periph_quirks & PQUIRK_NOREPSUPPOPC ||
           periph->periph_type == T_PROCESSOR || /* spec. */
           periph->periph_type == T_CDROM) /* spec. */
               return;

       scsipi_free_opcodeinfo(periph);

       /*
        * query REPORT SUPPORTED OPERATION CODES
        * if OK
        *   enumerate all codes
        *     if timeout exists insert maximum into opcode table
        */
       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);

       /* loop to skip any UNIT ATTENTIONS at this point */
       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);
}

/*
* scsipi_update_timeouts:
*      Override timeout value if device/config provided
*      timeouts are available.
*/
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) {
               /*
                * pick up device configured timeouts if they
                * are longer than the requested ones but less
                * than a day
                */
#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;
       }
}

/*
* scsipi_free_opcodeinfo:
*
* free the opcode information table
*/
void
scsipi_free_opcodeinfo(struct scsipi_periph *periph)
{
       if (periph->periph_opcs != NULL) {
               free(periph->periph_opcs, M_DEVBUF);
       }

       periph->periph_opcs = NULL;
}

/*
* scsipi_done:
*
*      This routine is called by an adapter's interrupt handler when
*      an xfer is completed.
*/
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);
       /*
        * The resource this command was using is now free.
        */
       if (xs->xs_status & XS_STS_DONE) {
               /* XXX in certain circumstances, such as a device
                * being detached, a xs that has already been
                * scsipi_done()'d by the main thread will be done'd
                * again by scsibusdetach(). Putting the xs on the
                * chan_complete queue causes list corruption and
                * everyone dies. This prevents that, but perhaps
                * there should be better coordination somewhere such
                * that this won't ever happen (and can be turned into
                * a KASSERT().
                */
               SDT_PROBE1(scsi, base, xfer, redone,  xs);
               mutex_exit(chan_mtx(chan));
               goto out;
       }
       scsipi_put_resource(chan);
       xs->xs_periph->periph_sent--;

       /*
        * If the command was tagged, free the tag.
        */
       if (XS_CTL_TAGTYPE(xs) != 0)
               scsipi_put_tag(xs);
       else
               periph->periph_flags &= ~PERIPH_UNTAG;

       /* Mark the command as `done'. */
       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

       /*
        * If the xfer had an error of any sort, freeze the
        * periph's queue.  Freeze it again if we were requested
        * to do so in the xfer.
        */
       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);

       /*
        * record the xfer with a pending sense, in case a SCSI reset is
        * received before the thread is waked up.
        */
       if (xs->error == XS_BUSY && xs->status == SCSI_CHECK) {
               periph->periph_flags |= PERIPH_SENSE;
               periph->periph_xscheck = xs;
       }

       /*
        * If this was an xfer that was not to complete asynchronously,
        * let the requesting thread perform error checking/handling
        * in its context.
        */
       if ((xs->xs_control & XS_CTL_ASYNC) == 0) {
               /*
                * If it's a polling job, just return, to unwind the
                * call graph.  We don't need to restart the queue,
                * because polling jobs are treated specially, and
                * are really only used during crash dumps anyway
                * (XXX or during boot-time autoconfiguration of
                * ATAPI devices).
                */
               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;
       }

       /*
        * Catch the extremely common case of I/O completing
        * without error; no use in taking a context switch
        * if we can handle it in interrupt context.
        */
       if (xs->error == XS_NOERROR) {
               mutex_exit(chan_mtx(chan));
               (void) scsipi_complete(xs);
               goto out;
       }

       /*
        * There is an error on this xfer.  Put it on the channel's
        * completion queue, and wake up the completion thread.
        */
       TAILQ_INSERT_TAIL(&chan->chan_complete, xs, channel_q);
       cv_broadcast(chan_cv_complete(chan));
       mutex_exit(chan_mtx(chan));

out:
       /*
        * If there are more xfers on the channel's queue, attempt to
        * run them.
        */
       scsipi_run_queue(chan);
}

/*
* scsipi_complete:
*
*      Completion of a scsipi_xfer.  This is the guts of scsipi_done().
*
*      NOTE: This routine MUST be called with valid thread context
*      except for the case where the following two conditions are
*      true:
*
*              xs->error == XS_NOERROR
*              XS_CTL_ASYNC is set in xs->xs_control
*
*      The semantics of this routine can be tricky, so here is an
*      explanation:
*
*              0               Xfer completed successfully.
*
*              ERESTART        Xfer had an error, but was restarted.
*
*              anything else   Xfer had an error, return value is Unix
*                              errno.
*
*      If the return value is anything but ERESTART:
*
*              - If XS_CTL_ASYNC is set, `xs' has been freed back to
*                the pool.
*              - If there is a buf associated with the xfer,
*                it has been biodone()'d.
*/
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
       /*
        * If command terminated with a CHECK CONDITION, we need to issue a
        * REQUEST_SENSE command. Once the REQUEST_SENSE has been processed
        * we'll have the real status.
        * Must be processed with channel lock held to avoid missing
        * a SCSI bus reset for this command.
        */
       mutex_enter(chan_mtx(chan));
       if (xs->error == XS_BUSY && xs->status == SCSI_CHECK) {
               /* request sense for a request sense ? */
               if (xs->xs_control & XS_CTL_REQSENSE) {
                       scsipi_printaddr(periph);
                       printf("request sense for a request sense ?\n");
                       /* XXX maybe we should reset the device ? */
                       /* we've been frozen because xs->error != XS_NOERROR */
                       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)); // XXX allows other commands to queue or run
               scsipi_request_sense(xs);
       } else
               mutex_exit(chan_mtx(chan));

       /*
        * If it's a user level request, bypass all usual completion
        * processing, let the user work it out..
        */
       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:
               /*
                * XXX Should freeze channel's queue.
                */
               scsipi_printaddr(periph);
               printf("adapter resource shortage\n");
               /* FALLTHROUGH */

       case XS_BUSY:
               if (xs->error == XS_BUSY && xs->status == SCSI_QUEUE_FULL) {
                       struct scsipi_max_openings mo;

                       /*
                        * We set the openings to active - 1, assuming that
                        * the command that got us here is the first one that
                        * can't fit into the device's queue.  If that's not
                        * the case, I guess we'll find out soon enough.
                        */
                       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--;
                       /*
                        * Wait one second, and try again.
                        */
                       mutex_enter(chan_mtx(chan));
                       if ((xs->xs_control & XS_CTL_POLL) ||
                           (chan->chan_flags & SCSIPI_CHAN_TACTIVE) == 0) {
                               /* XXX: quite extreme */
                               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 the device hasn't gone away, honor retry counts.
                *
                * Note that if we're in the middle of probing it,
                * it won't be found because it isn't here yet so
                * we won't honor the retry count in that case.
                */
               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) {
                       /*
                        * request sense interrupted by reset: signal it
                        * with EINTR return code.
                        */
                       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);
               /*
                * If we get here, the periph has been thawed and frozen
                * again if we had to issue recovery commands.  Alternatively,
                * it may have been frozen again and in a timed thaw.  In
                * any case, we thaw the periph once we re-enqueue the
                * command.  Once the periph is fully thawed, it will begin
                * operation again.
                */
               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;
               }
       }

       /*
        * scsipi_done() freezes the queue if not XS_NOERROR.
        * Thaw it here.
        */
       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;
}

/*
* Issue a request sense for the given scsipi_xfer. Called when the xfer
* returns with a CHECK_CONDITION status. Must be called in valid thread
* context.
*/

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;

       /* if command was polling, request sense will too */
       flags = xs->xs_control & XS_CTL_POLL;
       /* Polling commands can't sleep */
       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:
               /* we have a valid sense */
               xs->error = XS_SENSE;
               return;
       case EINTR:
               /* REQUEST_SENSE interrupted by bus reset. */
               xs->error = XS_RESET;
               return;
       case EIO:
                /* request sense couldn't be performed */
               /*
                * XXX this isn't quite right but we don't have anything
                * better for now
                */
               xs->error = XS_DRIVER_STUFFUP;
               return;
       default:
                /* Notify that request sense failed. */
               xs->error = XS_DRIVER_STUFFUP;
               scsipi_printaddr(periph);
               printf("request sense failed with error %d\n", error);
               return;
       }
}

/*
* scsipi_enqueue:
*
*      Enqueue an xfer on a channel.
*/
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);

       /*
        * If the xfer is to be polled, and there are already jobs on
        * the queue, we can't proceed.
        */
       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 we have an URGENT xfer, it's an error recovery command
        * and it should just go on the head of the channel's queue.
        */
       if (xs->xs_control & XS_CTL_URGENT) {
               TAILQ_INSERT_HEAD(&chan->chan_queue, xs, channel_q);
               goto out;
       }

       /*
        * If this xfer has already been on the queue before, we
        * need to reinsert it in the correct order.  That order is:
        *
        *      Immediately before the first xfer for this periph
        *      with a requeuecnt less than xs->xs_requeuecnt.
        *
        * Failing that, at the end of the queue.  (We'll end up
        * there naturally.)
        */
       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;
}

/*
* scsipi_run_queue:
*
*      Start as many xfers as possible running on the channel.
*/
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 the channel is frozen, we can't do any work right
                * now.
                */
               if (chan->chan_qfreeze != 0) {
                       mutex_exit(chan_mtx(chan));
                       break;
               }

               /*
                * Look for work to do, and make sure we can do it.
                */
               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;

                       /*
                        * We can issue this xfer!
                        */
                       goto got_one;
               }

               /*
                * Can't find any work to do right now.
                */
               mutex_exit(chan_mtx(chan));
               break;

got_one:
               /*
                * Have an xfer to run.  Allocate a resource from
                * the adapter to run it.  If we can't allocate that
                * resource, we don't dequeue the xfer.
                */
               if (scsipi_get_resource(chan) == 0) {
                       /*
                        * Adapter is out of resources.  If the adapter
                        * supports it, attempt to grow them.
                        */
                       if (scsipi_grow_resources(chan) == 0) {
                               /*
                                * Wasn't able to grow resources,
                                * nothing more we can do.
                                */
                               if (xs->xs_control & XS_CTL_POLL) {
                                       scsipi_printaddr(xs->xs_periph);
                                       printf("polling command but no "
                                           "adapter resources");
                                       /* We'll panic shortly... */
                               }
                               mutex_exit(chan_mtx(chan));

                               /*
                                * XXX: We should be able to note that
                                * XXX: that resources are needed here!
                                */
                               break;
                       }
                       /*
                        * scsipi_grow_resources() allocated the resource
                        * for us.
                        */
               }

               /*
                * We have a resource to run this xfer, do it!
                */
               TAILQ_REMOVE(&chan->chan_queue, xs, channel_q);

               /*
                * If the command is to be tagged, allocate a tag ID
                * for it.
                */
               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);
}

/*
* scsipi_execute_xs:
*
*      Begin execution of an xfer, waiting for it to complete, if necessary.
*/
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

       /*
        * Deal with command tagging:
        *
        *      - If the device's current operating mode doesn't
        *        include tagged queueing, clear the tag mask.
        *
        *      - If the device's current operating mode *does*
        *        include tagged queueing, set the tag_type in
        *        the xfer to the appropriate byte for the tag
        *        message.
        */
       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 the request doesn't specify a tag, give Head
                * tags to URGENT operations and Simple tags to
                * everything 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 the adapter wants us to poll, poll. */
       if (chan->chan_adapter->adapt_flags & SCSIPI_ADAPT_POLL_ONLY)
               xs->xs_control |= XS_CTL_POLL;

       /*
        * If we don't yet have a completion thread, or we are to poll for
        * completion, clear the ASYNC flag.
        */
       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

       /*
        * Enqueue the transfer.  If we're not polling for completion, this
        * should ALWAYS return `no error'.
        */
       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));

       /*
        * The xfer is enqueued, and possibly running.  If it's to be
        * completed asynchronously, just return now.
        */
       if (async)
               return 0;

       /*
        * Not an asynchronous command; wait for it to complete.
        */
       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));
       }

       /*
        * Command is complete.  scsipi_done() has awakened us to perform
        * the error handling.
        */
       mutex_exit(chan_mtx(chan));
       error = scsipi_complete(xs);
       if (error == ERESTART)
               goto restarted;

       /*
        * If it was meant to run async and we cleared async ourselves,
        * don't return an error here. It has already been handled
        */
       if (oasync)
               error = 0;
       /*
        * Command completed successfully or fatal error occurred.  Fall
        * into....
        */
       mutex_enter(chan_mtx(chan));
free_xs:
       scsipi_put_xs(xs);
       mutex_exit(chan_mtx(chan));

       /*
        * Kick the queue, keep it running in case it stopped for some
        * reason.
        */
       scsipi_run_queue(chan);

       mutex_enter(chan_mtx(chan));
       return error;
}

/*
* scsipi_completion_thread:
*
*      This is the completion thread.  We wait for errors on
*      asynchronous xfers, and perform the error handling
*      function, restarting the command, if necessary.
*/
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) {
                       /* nothing to do; wait */
                       cv_wait(chan_cv_complete(chan), chan_mtx(chan));
                       continue;
               }
               if (chan->chan_tflags & SCSIPI_CHANT_CALLBACK) {
                       /* call chan_callback from thread context */
                       chan->chan_tflags &= ~SCSIPI_CHANT_CALLBACK;
                       chan->chan_callback(chan, chan->chan_callback_arg);
                       continue;
               }
               if (chan->chan_tflags & SCSIPI_CHANT_GROWRES) {
                       /* attempt to get more openings for this channel */
                       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) {
                       /* explicitly run the queues for this channel */
                       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));

                       /*
                        * Have an xfer with an error; process it.
                        */
                       (void) scsipi_complete(xs);

                       /*
                        * Kick the queue; keep it running if it was stopped
                        * for some reason.
                        */
                       scsipi_run_queue(chan);
                       mutex_enter(chan_mtx(chan));
               }
       }

       chan->chan_thread = NULL;

       /* In case parent is waiting for us to exit. */
       cv_broadcast(chan_cv_thread(chan));
       mutex_exit(chan_mtx(chan));

       kthread_exit(0);
}
/*
* scsipi_thread_call_callback:
*
*      request to call a callback from the completion thread
*/
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) {
               /* kernel thread doesn't exist yet */
               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;
}

/*
* scsipi_async_event:
*
*      Handle an asynchronous event from an adapter.
*/
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));
}

/*
* scsipi_async_event_max_openings:
*
*      Update the maximum number of outstanding commands a
*      device may have.
*/
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) {
               /*
                * Wildcarded; apply it to all LUNs.
                */
               minlun = 0;
               maxlun = chan->chan_nluns - 1;
       } else
               minlun = maxlun = mo->mo_lun;

       /* XXX This could really suck with a large LUN space. */
       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;
       }
}

/*
* scsipi_set_xfer_mode:
*
*      Set the xfer mode for the specified I_T Nexus.
*/
void
scsipi_set_xfer_mode(struct scsipi_channel *chan, int target, int immed)
{
       struct scsipi_xfer_mode xm;
       struct scsipi_periph *itperiph;
       int lun;

       /*
        * Go to the minimal xfer mode.
        */
       xm.xm_target = target;
       xm.xm_mode = 0;
       xm.xm_period = 0;                       /* ignored */
       xm.xm_offset = 0;                       /* ignored */

       /*
        * Find the first LUN we know about on this I_T Nexus.
        */
       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;
               /*
                * Now issue the request to the adapter.
                */
               scsipi_adapter_request(chan, ADAPTER_REQ_SET_XFER_MODE, &xm);
               /*
                * If we want this to happen immediately, issue a dummy
                * command, since most adapters can't really negotiate unless
                * they're executing a job.
                */
               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);
               }
       }
}

/*
* scsipi_channel_reset:
*
*      handle scsi bus reset
* called with channel lock held
*/
static void
scsipi_async_event_channel_reset(struct scsipi_channel *chan)
{
       struct scsipi_xfer *xs, *xs_next;
       struct scsipi_periph *periph;
       int target, lun;

       /*
        * Channel has been reset. Also mark as reset pending REQUEST_SENSE
        * commands; as the sense is not available any more.
        * can't call scsipi_done() from here, as the command has not been
        * sent to the adapter yet (this would corrupt accounting).
        */

       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));
       /* Catch xs with pending sense which may not have a REQSENSE xs yet */
       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;
                       }
               }
       }
}

/*
* scsipi_target_detach:
*
*      detach all periph associated with a I_T
*      must be called from valid thread context
*/
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;
       }

       /* for config_detach */
       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;
}

/*
* scsipi_adapter_addref:
*
*      Add a reference to the adapter pointed to by the provided
*      link, enabling the adapter if necessary.
*/
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;
}

/*
* scsipi_adapter_delref:
*
*      Delete a reference to the adapter pointed to by the provided
*      link, disabling the adapter if possible.
*/
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;      /* ns * 100 */
} scsipi_syncparams[] = {
       { 0x08,          625 }, /* FAST-160 (Ultra320) */
       { 0x09,         1250 }, /* FAST-80 (Ultra160) */
       { 0x0a,         2500 }, /* FAST-40 40MHz (Ultra2) */
       { 0x0b,         3030 }, /* FAST-40 33MHz (Ultra2) */
       { 0x0c,         5000 }, /* FAST-20 (Ultra) */
};
static const int scsipi_nsyncparams =
   sizeof(scsipi_syncparams) / sizeof(scsipi_syncparams[0]);

int
scsipi_sync_period_to_factor(int period /* ns * 100 */)
{
       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
/*
* Given a scsipi_xfer, dump the request, in all its glory
*/
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 /* SCSIPI_DEBUG */