/*
* 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
*/
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");
/* 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;
/*
* 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 (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);
}
/*
* 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 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);
}
}
/*
* 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;
}
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);
}
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);