root/sys/dev/ic/aic79xx_osm.c
/*      $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;
        }
}