/*      $NetBSD: aic79xx_osm.c,v 1.36 2021/08/07 16:19:12 thorpej Exp $ */

/*
* Bus independent NetBSD shim for the aic7xxx based adaptec SCSI controllers
*
* Copyright (c) 1994-2002 Justin T. Gibbs.
* Copyright (c) 2001-2002 Adaptec Inc.
* All rights reserved.
*
* 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,
*    without modification.
* 2. The name of the author may not be used to endorse or promote products
*    derived from this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU Public License ("GPL").
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
*
* //depot/aic7xxx/freebsd/dev/aic7xxx/aic79xx_osm.c#26 $
*
* $FreeBSD: src/sys/dev/aic7xxx/aic79xx_osm.c,v 1.11 2003/05/04 00:20:07 gibbs Exp $
*/
/*
* Ported from FreeBSD by Pascal Renauld, Network Storage Solutions, Inc.
* - April 2003
*/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: aic79xx_osm.c,v 1.36 2021/08/07 16:19:12 thorpej Exp $");

#include <dev/ic/aic79xx_osm.h>
#include <dev/ic/aic79xx_inline.h>

#ifndef AHD_TMODE_ENABLE
#define AHD_TMODE_ENABLE 0
#endif

static int      ahd_ioctl(struct scsipi_channel *channel, u_long cmd,
                         void *addr, int flag, struct proc *p);
static void     ahd_action(struct scsipi_channel *chan,
                          scsipi_adapter_req_t req, void *arg);
static void     ahd_execute_scb(void *arg, bus_dma_segment_t *dm_segs,
                               int nsegments);
static int      ahd_poll(struct ahd_softc *ahd, int wait);
static void     ahd_setup_data(struct ahd_softc *ahd, struct scsipi_xfer *xs,
                              struct scb *scb);

#if NOT_YET
static void     ahd_set_recoveryscb(struct ahd_softc *ahd, struct scb *scb);
#endif

static bool     ahd_pmf_suspend(device_t, const pmf_qual_t *);
static bool     ahd_pmf_resume(device_t, const pmf_qual_t *);
static bool     ahd_pmf_shutdown(device_t, int);

/*
* Attach all the sub-devices we can find
*/
int
ahd_attach(struct ahd_softc *ahd)
{
       int     s;
       char    ahd_info[256];

       ahd_controller_info(ahd, ahd_info, sizeof(ahd_info));
       aprint_normal("%s: %s\n", ahd_name(ahd), ahd_info);

       ahd_lock(ahd, &s);

       ahd->sc_adapter.adapt_dev = ahd->sc_dev;
       ahd->sc_adapter.adapt_nchannels = 1;

       ahd->sc_adapter.adapt_openings = ahd->scb_data.numscbs - 1;
       ahd->sc_adapter.adapt_max_periph = 32;

       ahd->sc_adapter.adapt_ioctl = ahd_ioctl;
       ahd->sc_adapter.adapt_minphys = ahd_minphys;
       ahd->sc_adapter.adapt_request = ahd_action;

       ahd->sc_channel.chan_adapter = &ahd->sc_adapter;
       ahd->sc_channel.chan_bustype = &scsi_bustype;
       ahd->sc_channel.chan_channel = 0;
       ahd->sc_channel.chan_ntargets = AHD_NUM_TARGETS;
       ahd->sc_channel.chan_nluns = 8 /*AHD_NUM_LUNS*/;
       ahd->sc_channel.chan_id = ahd->our_id;
       ahd->sc_channel.chan_flags |= SCSIPI_CHAN_CANGROW;

       ahd->sc_child = config_found(ahd->sc_dev, &ahd->sc_channel, scsiprint,
           CFARGS_NONE);

       ahd_intr_enable(ahd, TRUE);

       if (ahd->flags & AHD_RESET_BUS_A)
               ahd_reset_channel(ahd, 'A', TRUE);

       if (!pmf_device_register1(ahd->sc_dev,
           ahd_pmf_suspend, ahd_pmf_resume, ahd_pmf_shutdown))
               aprint_error_dev(ahd->sc_dev,
                   "couldn't establish power handler\n");

       ahd_unlock(ahd, &s);

       return (1);
}

static bool
ahd_pmf_suspend(device_t dev, const pmf_qual_t *qual)
{
       struct ahd_softc *sc = device_private(dev);
#if 0
       return (ahd_suspend(sc) == 0);
#else
       ahd_shutdown(sc);
       return true;
#endif
}

static bool
ahd_pmf_resume(device_t dev, const pmf_qual_t *qual)
{
#if 0
       struct ahd_softc *sc = device_private(dev);

       return (ahd_resume(sc) == 0);
#else
       return true;
#endif
}

static bool
ahd_pmf_shutdown(device_t dev, int howto)
{
       struct ahd_softc *sc = device_private(dev);

       /* Disable all interrupt sources by resetting the controller */
       ahd_shutdown(sc);

       return true;
}

static int
ahd_ioctl(struct scsipi_channel *channel, u_long cmd,
         void *addr, int flag, struct proc *p)
{
       struct ahd_softc *ahd;
       int s, ret = ENOTTY;

       ahd = device_private(channel->chan_adapter->adapt_dev);

       switch (cmd) {
       case SCBUSIORESET:
               s = splbio();
               ahd_reset_channel(ahd, channel->chan_channel == 1 ? 'B' : 'A', TRUE);
               splx(s);
               ret = 0;
               break;
       default:
               break;
       }

       return ret;
}

/*
* Catch an interrupt from the adapter
*/
void
ahd_platform_intr(void *arg)
{
       struct  ahd_softc *ahd;

       ahd = arg;

       printf("%s; ahd_platform_intr\n", ahd_name(ahd));

       ahd_intr(ahd);
}

/*
* We have an scb which has been processed by the
* adaptor, now we look to see how the operation * went.
*/
void
ahd_done(struct ahd_softc *ahd, struct scb *scb)
{
       struct scsipi_xfer      *xs;
       struct scsipi_periph    *periph;
       int                     s;

       LIST_REMOVE(scb, pending_links);

       xs = scb->xs;
       periph = xs->xs_periph;

       callout_stop(&scb->xs->xs_callout);

       if (xs->datalen) {
               int op;

               if (xs->xs_control & XS_CTL_DATA_IN)
                       op = BUS_DMASYNC_POSTREAD;
               else
                       op = BUS_DMASYNC_POSTWRITE;

               bus_dmamap_sync(ahd->parent_dmat, scb->dmamap, 0,
                               scb->dmamap->dm_mapsize, op);
               bus_dmamap_unload(ahd->parent_dmat, scb->dmamap);
       }

       /*
        * If the recovery SCB completes, we have to be
        * out of our timeout.
        */
       if ((scb->flags & SCB_RECOVERY_SCB) != 0) {
               struct  scb *list_scb;

               /*
                * We were able to complete the command successfully,
                * so reinstate the timeouts for all other pending
                * commands.
                */
               LIST_FOREACH(list_scb, &ahd->pending_scbs, pending_links) {
                       struct scsipi_xfer      *txs = list_scb->xs;

                       if (!(txs->xs_control & XS_CTL_POLL)) {
                               callout_reset(&txs->xs_callout,
                                   (txs->timeout > 1000000) ?
                                   (txs->timeout / 1000) * hz :
                                   (txs->timeout * hz) / 1000,
                                   ahd_timeout, list_scb);
                       }
               }

               if (ahd_get_transaction_status(scb) != XS_NOERROR)
                       ahd_set_transaction_status(scb, XS_TIMEOUT);
               scsipi_printaddr(xs->xs_periph);
               printf("%s: no longer in timeout, status = %x\n",
                      ahd_name(ahd), xs->status);
       }

       if (xs->error != XS_NOERROR) {
               /* Don't clobber any existing error state */
       } else if ((xs->status == SCSI_STATUS_BUSY) ||
                  (xs->status == SCSI_STATUS_QUEUE_FULL)) {
               ahd_set_transaction_status(scb, XS_BUSY);
               printf("%s: drive (ID %d, LUN %d) queue full (SCB 0x%x)\n",
                      ahd_name(ahd), SCB_GET_TARGET(ahd,scb), SCB_GET_LUN(scb), SCB_GET_TAG(scb));
       } else if ((scb->flags & SCB_SENSE) != 0) {
               /*
                * We performed autosense retrieval.
                *
                * zero the sense data before having
                * the drive fill it.  The SCSI spec mandates
                * that any untransferred data should be
                * assumed to be zero.  Complete the 'bounce'
                * of sense information through buffers accessible
                * via bus-space by copying it into the clients
                * csio.
                */
               memset(&xs->sense.scsi_sense, 0, sizeof(xs->sense.scsi_sense));
               memcpy(&xs->sense.scsi_sense, ahd_get_sense_buf(ahd, scb),
                      sizeof(struct scsi_sense_data));

               ahd_set_transaction_status(scb, XS_SENSE);
       } else if ((scb->flags & SCB_PKT_SENSE) != 0) {
               struct scsi_status_iu_header *siu;
               u_int sense_len;
#ifdef AHD_DEBUG
               int i;
#endif
               /*
                * Copy only the sense data into the provided buffer.
                */
               siu = (struct scsi_status_iu_header *)scb->sense_data;
               sense_len = MIN(scsi_4btoul(siu->sense_length),
                               sizeof(xs->sense.scsi_sense));
               memset(&xs->sense.scsi_sense, 0, sizeof(xs->sense.scsi_sense));
               memcpy(&xs->sense.scsi_sense,
                      scb->sense_data + SIU_SENSE_OFFSET(siu), sense_len);
#ifdef AHD_DEBUG
               printf("Copied %d bytes of sense data offset %d:", sense_len,
                      SIU_SENSE_OFFSET(siu));
               for (i = 0; i < sense_len; i++)
                       printf(" 0x%x", ((uint8_t *)&xs->sense.scsi_sense)[i]);
               printf("\n");
#endif
               ahd_set_transaction_status(scb, XS_SENSE);
       }

       if (scb->flags & SCB_FREEZE_QUEUE) {
               scsipi_periph_thaw(periph, 1);
               scb->flags &= ~SCB_FREEZE_QUEUE;
       }

       if (scb->flags & SCB_REQUEUE)
               ahd_set_transaction_status(scb, XS_REQUEUE);

       ahd_lock(ahd, &s);
       ahd_free_scb(ahd, scb);
       ahd_unlock(ahd, &s);

       scsipi_done(xs);
}

static void
ahd_action(struct scsipi_channel *chan, scsipi_adapter_req_t req, void *arg)
{
       struct ahd_softc *ahd;
       struct ahd_initiator_tinfo *tinfo;
       struct ahd_tmode_tstate *tstate;

       ahd = device_private(chan->chan_adapter->adapt_dev);

       switch(req) {

       case ADAPTER_REQ_RUN_XFER:
         {
               struct scsipi_xfer *xs;
               struct scsipi_periph *periph;
               struct scb *scb;
               struct hardware_scb *hscb;
               u_int target_id;
               u_int our_id;
               u_int col_idx;
               char channel;
               int s;

               xs = arg;
               periph = xs->xs_periph;

               SC_DEBUG(periph, SCSIPI_DB3, ("ahd_action\n"));

               target_id = periph->periph_target;
               our_id = ahd->our_id;
               channel = (chan->chan_channel == 1) ? 'B' : 'A';

               /*
                * get an scb to use.
                */
               ahd_lock(ahd, &s);
               tinfo = ahd_fetch_transinfo(ahd, channel, our_id,
                                           target_id, &tstate);

               if (xs->xs_tag_type != 0 ||
                   (tinfo->curr.ppr_options & MSG_EXT_PPR_IU_REQ) != 0)
                       col_idx = AHD_NEVER_COL_IDX;
               else
                       col_idx = AHD_BUILD_COL_IDX(target_id,
                           periph->periph_lun);

               if ((scb = ahd_get_scb(ahd, col_idx)) == NULL) {
                       xs->error = XS_RESOURCE_SHORTAGE;
                       ahd_unlock(ahd, &s);
                       scsipi_done(xs);
                       return;
               }
               ahd_unlock(ahd, &s);

               hscb = scb->hscb;

               SC_DEBUG(periph, SCSIPI_DB3, ("start scb(%p)\n", scb));
               scb->xs = xs;

               /*
                * Put all the arguments for the xfer in the scb
                */
               hscb->control = 0;
               hscb->scsiid = BUILD_SCSIID(ahd, sim, target_id, our_id);
               hscb->lun = periph->periph_lun;
               if (xs->xs_control & XS_CTL_RESET) {
                       hscb->cdb_len = 0;
                       scb->flags |= SCB_DEVICE_RESET;
                       hscb->control |= MK_MESSAGE;
                       hscb->task_management = SIU_TASKMGMT_LUN_RESET;
                       ahd_execute_scb(scb, NULL, 0);
               } else {
                       hscb->task_management = 0;
               }

               ahd_setup_data(ahd, xs, scb);
               break;
         }

       case ADAPTER_REQ_GROW_RESOURCES:
#ifdef AHC_DEBUG
               printf("%s: ADAPTER_REQ_GROW_RESOURCES\n", ahd_name(ahd));
#endif
               chan->chan_adapter->adapt_openings += ahd_alloc_scbs(ahd);
               if (ahd->scb_data.numscbs >= AHD_SCB_MAX_ALLOC)
                       chan->chan_flags &= ~SCSIPI_CHAN_CANGROW;
               break;

       case ADAPTER_REQ_SET_XFER_MODE:
           {
               struct scsipi_xfer_mode *xm = arg;
               struct ahd_devinfo devinfo;
               int target_id, our_id, first;
               u_int width;
               int s;
               char channel;
               u_int ppr_options = 0, period, offset;
               uint16_t old_autoneg;

               target_id = xm->xm_target;
               our_id = chan->chan_id;
               channel = 'A';
               s = splbio();
               tinfo = ahd_fetch_transinfo(ahd, channel, our_id, target_id,
                   &tstate);
               ahd_compile_devinfo(&devinfo, our_id, target_id,
                   0, channel, ROLE_INITIATOR);

               old_autoneg = tstate->auto_negotiate;

               /*
                * XXX since the period and offset are not provided here,
                * fake things by forcing a renegotiation using the user
                * settings if this is called for the first time (i.e.
                * during probe). Also, cap various values at the user
                * values, assuming that the user set it up that way.
                */
               if (ahd->inited_target[target_id] == 0) {
                       period = tinfo->user.period;
                       offset = tinfo->user.offset;
                       ppr_options = tinfo->user.ppr_options;
                       width = tinfo->user.width;
                       tstate->tagenable |=
                           (ahd->user_tagenable & devinfo.target_mask);
                       tstate->discenable |=
                           (ahd->user_discenable & devinfo.target_mask);
                       ahd->inited_target[target_id] = 1;
                       first = 1;
               } else
                       first = 0;

               if (xm->xm_mode & (PERIPH_CAP_WIDE16 | PERIPH_CAP_DT))
                       width = MSG_EXT_WDTR_BUS_16_BIT;
               else
                       width = MSG_EXT_WDTR_BUS_8_BIT;

               ahd_validate_width(ahd, NULL, &width, ROLE_UNKNOWN);
               if (width > tinfo->user.width)
                       width = tinfo->user.width;
               ahd_set_width(ahd, &devinfo, width, AHD_TRANS_GOAL, FALSE);

               if (!(xm->xm_mode & (PERIPH_CAP_SYNC | PERIPH_CAP_DT))) {
                       period = 0;
                       offset = 0;
                       ppr_options = 0;
               }

               if ((xm->xm_mode & PERIPH_CAP_DT) &&
                   (tinfo->user.ppr_options & MSG_EXT_PPR_DT_REQ))
                       ppr_options |= MSG_EXT_PPR_DT_REQ;
               else
                       ppr_options &= ~MSG_EXT_PPR_DT_REQ;

               if ((tstate->discenable & devinfo.target_mask) == 0 ||
                   (tstate->tagenable & devinfo.target_mask) == 0)
                       ppr_options &= ~MSG_EXT_PPR_IU_REQ;

               if ((xm->xm_mode & PERIPH_CAP_TQING) &&
                   (ahd->user_tagenable & devinfo.target_mask))
                       tstate->tagenable |= devinfo.target_mask;
               else
                       tstate->tagenable &= ~devinfo.target_mask;

               ahd_find_syncrate(ahd, &period, &ppr_options, AHD_SYNCRATE_MAX);
               ahd_validate_offset(ahd, NULL, period, &offset,
                   MSG_EXT_WDTR_BUS_8_BIT, ROLE_UNKNOWN);
               if (offset == 0) {
                       period = 0;
                       ppr_options = 0;
               }
               if (ppr_options != 0
                   && tinfo->user.transport_version >= 3) {
                       tinfo->goal.transport_version =
                           tinfo->user.transport_version;
                       tinfo->curr.transport_version =
                           tinfo->user.transport_version;
               }

               ahd_set_syncrate(ahd, &devinfo, period, offset,
                   ppr_options, AHD_TRANS_GOAL, FALSE);

               /*
                * If this is the first request, and no negotiation is
                * needed, just confirm the state to the scsipi layer,
                * so that it can print a message.
                */
               if (old_autoneg == tstate->auto_negotiate && first) {
                       xm->xm_mode = 0;
                       xm->xm_period = tinfo->curr.period;
                       xm->xm_offset = tinfo->curr.offset;
                       if (tinfo->curr.width == MSG_EXT_WDTR_BUS_16_BIT)
                               xm->xm_mode |= PERIPH_CAP_WIDE16;
                       if (tinfo->curr.period)
                               xm->xm_mode |= PERIPH_CAP_SYNC;
                       if (tstate->tagenable & devinfo.target_mask)
                               xm->xm_mode |= PERIPH_CAP_TQING;
                       if (tinfo->curr.ppr_options & MSG_EXT_PPR_DT_REQ)
                               xm->xm_mode |= PERIPH_CAP_DT;
                       scsipi_async_event(chan, ASYNC_EVENT_XFER_MODE, xm);
               }
               splx(s);
           }
       }

       return;
}

static void
ahd_execute_scb(void *arg, bus_dma_segment_t *dm_segs, int nsegments)
{
       struct scb *scb;
       struct scsipi_xfer *xs;
       struct ahd_softc *ahd;
       struct ahd_initiator_tinfo *tinfo;
       struct ahd_tmode_tstate *tstate;
       u_int  mask;
       int    s;

       scb = arg;
       xs = scb->xs;
       xs->error = 0;
       xs->status = 0;
       xs->xs_status = 0;
       ahd = device_private(
           xs->xs_periph->periph_channel->chan_adapter->adapt_dev);

       scb->sg_count = 0;
       if (nsegments != 0) {
               void *sg;
               int op;
               u_int i;

               ahd_setup_data_scb(ahd, scb);

               /* Copy the segments into our SG list */
               for (i = nsegments, sg = scb->sg_list; i > 0; i--) {

                       sg = ahd_sg_setup(ahd, scb, sg, dm_segs->ds_addr,
                                         dm_segs->ds_len,
                                         /*last*/i == 1);
                       dm_segs++;
               }

               if (xs->xs_control & XS_CTL_DATA_IN)
                       op = BUS_DMASYNC_PREREAD;
               else
                       op = BUS_DMASYNC_PREWRITE;

               bus_dmamap_sync(ahd->parent_dmat, scb->dmamap, 0,
                               scb->dmamap->dm_mapsize, op);
       }

       ahd_lock(ahd, &s);

       /*
        * Last time we need to check if this SCB needs to
        * be aborted.
        */
       if (ahd_get_scsi_status(scb) == XS_STS_DONE) {
               if (nsegments != 0)
                       bus_dmamap_unload(ahd->parent_dmat,
                                         scb->dmamap);
               ahd_free_scb(ahd, scb);
               ahd_unlock(ahd, &s);
               return;
       }

       tinfo = ahd_fetch_transinfo(ahd, SCSIID_CHANNEL(ahd, scb->hscb->scsiid),
                                   SCSIID_OUR_ID(scb->hscb->scsiid),
                                   SCSIID_TARGET(ahd, scb->hscb->scsiid),
                                   &tstate);

       mask = SCB_GET_TARGET_MASK(ahd, scb);

       if ((tstate->discenable & mask) != 0)
               scb->hscb->control |= DISCENB;

       if ((tstate->tagenable & mask) != 0)
               scb->hscb->control |= xs->xs_tag_type|TAG_ENB;

       if ((tinfo->curr.ppr_options & MSG_EXT_PPR_IU) != 0) {
               scb->flags |= SCB_PACKETIZED;
               if (scb->hscb->task_management != 0)
                       scb->hscb->control &= ~MK_MESSAGE;
       }

#if 0   /* This looks like it makes sense at first, but it can loop */
       if ((xs->xs_control & XS_CTL_DISCOVERY) &&
           (tinfo->goal.width != 0
            || tinfo->goal.period != 0
            || tinfo->goal.ppr_options != 0)) {
               scb->flags |= SCB_NEGOTIATE;
               scb->hscb->control |= MK_MESSAGE;
       } else
#endif
       if ((tstate->auto_negotiate & mask) != 0) {
               scb->flags |= SCB_AUTO_NEGOTIATE;
               scb->hscb->control |= MK_MESSAGE;
       }

       LIST_INSERT_HEAD(&ahd->pending_scbs, scb, pending_links);

       scb->flags |= SCB_ACTIVE;

       if (!(xs->xs_control & XS_CTL_POLL)) {
               callout_reset(&scb->xs->xs_callout, xs->timeout > 1000000 ?
                             (xs->timeout / 1000) * hz : (xs->timeout * hz) / 1000,
                             ahd_timeout, scb);
       }

       if ((scb->flags & SCB_TARGET_IMMEDIATE) != 0) {
               /* Define a mapping from our tag to the SCB. */
               ahd->scb_data.scbindex[SCB_GET_TAG(scb)] = scb;
               ahd_pause(ahd);
               ahd_set_scbptr(ahd, SCB_GET_TAG(scb));
               ahd_outb(ahd, RETURN_1, CONT_MSG_LOOP_TARG);
               ahd_unpause(ahd);
       } else {
               ahd_queue_scb(ahd, scb);
       }

       if (!(xs->xs_control & XS_CTL_POLL)) {
               ahd_unlock(ahd, &s);
               return;
       }
       /*
        * If we can't use interrupts, poll for completion
        */
       SC_DEBUG(xs->xs_periph, SCSIPI_DB3, ("cmd_poll\n"));
       do {
               if (ahd_poll(ahd, xs->timeout)) {
                       if (!(xs->xs_control & XS_CTL_SILENT))
                               printf("cmd fail\n");
                       ahd_timeout(scb);
                       break;
               }
       } while (!(xs->xs_status & XS_STS_DONE));

       ahd_unlock(ahd, &s);
}

static int
ahd_poll(struct ahd_softc *ahd, int wait)
{

       while (--wait) {
               DELAY(1000);
               if (ahd_inb(ahd, INTSTAT) & INT_PEND)
                       break;
       }

       if (wait == 0) {
               printf("%s: board is not responding\n", ahd_name(ahd));
               return (EIO);
       }

       ahd_intr(ahd);
       return (0);
}


static void
ahd_setup_data(struct ahd_softc *ahd, struct scsipi_xfer *xs,
              struct scb *scb)
{
       struct hardware_scb *hscb;

       hscb = scb->hscb;
       xs->resid = xs->status = 0;

       hscb->cdb_len = xs->cmdlen;
       if (hscb->cdb_len > MAX_CDB_LEN) {
               int s;
               /*
                * Should CAM start to support CDB sizes
                * greater than 16 bytes, we could use
                * the sense buffer to store the CDB.
                */
               ahd_set_transaction_status(scb,
                                          XS_DRIVER_STUFFUP);

               ahd_lock(ahd, &s);
               ahd_free_scb(ahd, scb);
               ahd_unlock(ahd, &s);
               scsipi_done(xs);
       }
       memcpy(hscb->shared_data.idata.cdb, xs->cmd, hscb->cdb_len);

       /* Only use S/G if there is a transfer */
       if (xs->datalen) {
               int error;

               error = bus_dmamap_load(ahd->parent_dmat,
                                       scb->dmamap, xs->data,
                                       xs->datalen, NULL,
                                       ((xs->xs_control & XS_CTL_NOSLEEP) ?
                                        BUS_DMA_NOWAIT : BUS_DMA_WAITOK) |
                                       BUS_DMA_STREAMING |
                                       ((xs->xs_control & XS_CTL_DATA_IN) ?
                                        BUS_DMA_READ : BUS_DMA_WRITE));
               if (error) {
#ifdef AHD_DEBUG
                       printf("%s: in ahd_setup_data(): bus_dmamap_load() "
                              "= %d\n",
                              ahd_name(ahd), error);
#endif
                       xs->error = XS_RESOURCE_SHORTAGE;
                       scsipi_done(xs);
                       return;
               }
               ahd_execute_scb(scb,
                               scb->dmamap->dm_segs,
                               scb->dmamap->dm_nsegs);
       } else {
               ahd_execute_scb(scb, NULL, 0);
       }
}

void
ahd_timeout(void *arg)
{
       struct  scb       *scb;
       struct  ahd_softc *ahd;
       int                s;

       scb = arg;
       ahd = scb->ahd_softc;

       printf("%s: ahd_timeout\n", ahd_name(ahd));

       ahd_lock(ahd, &s);

       ahd_pause_and_flushwork(ahd);
       (void)ahd_save_modes(ahd);
#if 0
       ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
       ahd_outb(ahd, SCSISIGO, ACKO);
       printf("set ACK\n");
       ahd_outb(ahd, SCSISIGO, 0);
       printf("clearing Ack\n");
       ahd_restore_modes(ahd, saved_modes);
#endif
       if ((scb->flags & SCB_ACTIVE) == 0) {
               /* Previous timeout took care of me already */
               printf("%s: Timedout SCB already complete. "
                      "Interrupts may not be functioning.\n", ahd_name(ahd));
               ahd_unpause(ahd);
               ahd_unlock(ahd, &s);
               return;
       }

       ahd_print_path(ahd, scb);
       printf("SCB 0x%x - timed out\n", SCB_GET_TAG(scb));
       ahd_dump_card_state(ahd);
       ahd_reset_channel(ahd, SIM_CHANNEL(ahd, sim),
                         /*initiate reset*/TRUE);
       ahd_unlock(ahd, &s);
       return;
}

int
ahd_platform_alloc(struct ahd_softc *ahd, void *platform_arg)
{
       ahd->platform_data = malloc(sizeof(struct ahd_platform_data), M_DEVBUF,
                                   M_WAITOK | M_ZERO);
       return (0);
}

void
ahd_platform_free(struct ahd_softc *ahd)
{
       free(ahd->platform_data, M_DEVBUF);
}

int
ahd_softc_comp(struct ahd_softc *lahd, struct ahd_softc *rahd)
{
       /* We don't sort softcs under NetBSD so report equal always */
       return (0);
}

int
ahd_detach(struct ahd_softc *ahd, int flags)
{
       int rv = 0;

       if (ahd->sc_child != NULL)
               rv = config_detach(ahd->sc_child, flags);

       pmf_device_deregister(ahd->sc_dev);

       ahd_free(ahd);

       return rv;
}

void
ahd_platform_set_tags(struct ahd_softc *ahd,
                     struct ahd_devinfo *devinfo, ahd_queue_alg alg)
{
       struct ahd_tmode_tstate *tstate;

       ahd_fetch_transinfo(ahd, devinfo->channel, devinfo->our_scsiid,
                           devinfo->target, &tstate);

       if (alg != AHD_QUEUE_NONE)
               tstate->tagenable |= devinfo->target_mask;
       else
               tstate->tagenable &= ~devinfo->target_mask;
}

void
ahd_send_async(struct ahd_softc *ahd, char channel, u_int target, u_int lun,
              ac_code code, void *opt_arg)
{
       struct ahd_tmode_tstate *tstate;
       struct ahd_initiator_tinfo *tinfo;
       struct ahd_devinfo devinfo;
       struct scsipi_channel *chan;
       struct scsipi_xfer_mode xm;

#ifdef DIAGNOSTIC
       if (channel != 'A')
               panic("ahd_send_async: not channel A");
#endif
       chan = &ahd->sc_channel;
       switch (code) {
       case AC_TRANSFER_NEG:
               tinfo = ahd_fetch_transinfo(ahd, channel, ahd->our_id, target,
                           &tstate);
               ahd_compile_devinfo(&devinfo, ahd->our_id, target, lun,
                   channel, ROLE_UNKNOWN);
               /*
                * Don't bother if negotiating. XXX?
                */
               if (tinfo->curr.period != tinfo->goal.period
                   || tinfo->curr.width != tinfo->goal.width
                   || tinfo->curr.offset != tinfo->goal.offset
                   || tinfo->curr.ppr_options != tinfo->goal.ppr_options)
                       break;
               xm.xm_target = target;
               xm.xm_mode = 0;
               xm.xm_period = tinfo->curr.period;
               xm.xm_offset = tinfo->curr.offset;
               if (tinfo->goal.ppr_options & MSG_EXT_PPR_DT_REQ)
                       xm.xm_mode |= PERIPH_CAP_DT;
               if (tinfo->curr.width == MSG_EXT_WDTR_BUS_16_BIT)
                       xm.xm_mode |= PERIPH_CAP_WIDE16;
               if (tinfo->curr.period)
                       xm.xm_mode |= PERIPH_CAP_SYNC;
               if (tstate->tagenable & devinfo.target_mask)
                       xm.xm_mode |= PERIPH_CAP_TQING;
               scsipi_async_event(chan, ASYNC_EVENT_XFER_MODE, &xm);
               break;
       case AC_BUS_RESET:
               scsipi_async_event(chan, ASYNC_EVENT_RESET, NULL);
       case AC_SENT_BDR:
       default:
               break;
       }
}