freebsd-nq/sys/dev/aic/aic.c
Steven Hartland 85c9dd9d89 Prevent overflow issues in timeout processing
Previously, any timeout value for which (timeout * hz) will overflow the
signed integer, will give weird results, since callout(9) routines will
convert negative values of ticks to '1'. For unsigned integer overflow we
will get sufficiently smaller timeout values than expected.

Switch from callout_reset, which requires conversion to int based ticks
to callout_reset_sbt to avoid this.

Also correct isci to correctly resolve ccb timeout.

This was based on the original work done by Eygene Ryabinkin
<rea@freebsd.org> back in 5 Aug 2011 which used a macro to help avoid
the overlow.

Differential Revision:	https://reviews.freebsd.org/D1157
Reviewed by:	mav, davide
MFC after:	1 month
Sponsored by:	Multiplay
2014-11-21 21:01:24 +00:00

1599 lines
40 KiB
C

/*-
* Copyright (c) 1999 Luoqi Chen.
* 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.
* 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 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.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <cam/cam.h>
#include <cam/cam_ccb.h>
#include <cam/cam_sim.h>
#include <cam/cam_xpt_sim.h>
#include <cam/cam_debug.h>
#include <cam/scsi/scsi_message.h>
#include <dev/aic/aic6360reg.h>
#include <dev/aic/aicvar.h>
static void aic_action(struct cam_sim *sim, union ccb *ccb);
static void aic_execute_scb(void *arg, bus_dma_segment_t *dm_segs,
int nseg, int error);
static void aic_intr_locked(struct aic_softc *aic);
static void aic_start(struct aic_softc *aic);
static void aic_select(struct aic_softc *aic);
static void aic_selected(struct aic_softc *aic);
static void aic_reselected(struct aic_softc *aic);
static void aic_reconnect(struct aic_softc *aic, int tag);
static void aic_cmd(struct aic_softc *aic);
static void aic_msgin(struct aic_softc *aic);
static void aic_handle_msgin(struct aic_softc *aic);
static void aic_msgout(struct aic_softc *aic);
static void aic_datain(struct aic_softc *aic);
static void aic_dataout(struct aic_softc *aic);
static void aic_done(struct aic_softc *aic, struct aic_scb *scb);
static void aic_poll(struct cam_sim *sim);
static void aic_timeout(void *arg);
static void aic_scsi_reset(struct aic_softc *aic);
static void aic_chip_reset(struct aic_softc *aic);
static void aic_reset(struct aic_softc *aic, int initiate_reset);
devclass_t aic_devclass;
static struct aic_scb *
aic_get_scb(struct aic_softc *aic)
{
struct aic_scb *scb;
if (!dumping)
mtx_assert(&aic->lock, MA_OWNED);
if ((scb = SLIST_FIRST(&aic->free_scbs)) != NULL)
SLIST_REMOVE_HEAD(&aic->free_scbs, link);
return (scb);
}
static void
aic_free_scb(struct aic_softc *aic, struct aic_scb *scb)
{
if (!dumping)
mtx_assert(&aic->lock, MA_OWNED);
if ((aic->flags & AIC_RESOURCE_SHORTAGE) != 0 &&
(scb->ccb->ccb_h.status & CAM_RELEASE_SIMQ) == 0) {
scb->ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
aic->flags &= ~AIC_RESOURCE_SHORTAGE;
}
scb->flags = 0;
SLIST_INSERT_HEAD(&aic->free_scbs, scb, link);
}
static void
aic_action(struct cam_sim *sim, union ccb *ccb)
{
struct aic_softc *aic;
CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("aic_action\n"));
aic = (struct aic_softc *)cam_sim_softc(sim);
mtx_assert(&aic->lock, MA_OWNED);
switch (ccb->ccb_h.func_code) {
case XPT_SCSI_IO: /* Execute the requested I/O operation */
case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */
{
struct aic_scb *scb;
if ((scb = aic_get_scb(aic)) == NULL) {
aic->flags |= AIC_RESOURCE_SHORTAGE;
xpt_freeze_simq(aic->sim, /*count*/1);
ccb->ccb_h.status = CAM_REQUEUE_REQ;
xpt_done(ccb);
return;
}
scb->ccb = ccb;
ccb->ccb_h.ccb_scb_ptr = scb;
ccb->ccb_h.ccb_aic_ptr = aic;
scb->target = ccb->ccb_h.target_id;
scb->lun = ccb->ccb_h.target_lun;
if (ccb->ccb_h.func_code == XPT_SCSI_IO) {
scb->cmd_len = ccb->csio.cdb_len;
if (ccb->ccb_h.flags & CAM_CDB_POINTER) {
if (ccb->ccb_h.flags & CAM_CDB_PHYS) {
ccb->ccb_h.status = CAM_REQ_INVALID;
aic_free_scb(aic, scb);
xpt_done(ccb);
return;
}
scb->cmd_ptr = ccb->csio.cdb_io.cdb_ptr;
} else {
scb->cmd_ptr = ccb->csio.cdb_io.cdb_bytes;
}
if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) {
if ((ccb->ccb_h.flags & CAM_DATA_MASK) !=
CAM_DATA_VADDR) {
ccb->ccb_h.status = CAM_REQ_INVALID;
aic_free_scb(aic, scb);
xpt_done(ccb);
return;
}
scb->data_ptr = ccb->csio.data_ptr;
scb->data_len = ccb->csio.dxfer_len;
} else {
scb->data_ptr = NULL;
scb->data_len = 0;
}
aic_execute_scb(scb, NULL, 0, 0);
} else {
scb->flags |= SCB_DEVICE_RESET;
aic_execute_scb(scb, NULL, 0, 0);
}
break;
}
case XPT_SET_TRAN_SETTINGS:
{
struct ccb_trans_settings *cts = &ccb->cts;
struct aic_tinfo *ti = &aic->tinfo[ccb->ccb_h.target_id];
struct ccb_trans_settings_scsi *scsi =
&cts->proto_specific.scsi;
struct ccb_trans_settings_spi *spi =
&cts->xport_specific.spi;
if ((spi->valid & CTS_SPI_VALID_DISC) != 0 &&
(aic->flags & AIC_DISC_ENABLE) != 0) {
if ((spi->flags & CTS_SPI_FLAGS_DISC_ENB) != 0)
ti->flags |= TINFO_DISC_ENB;
else
ti->flags &= ~TINFO_DISC_ENB;
}
if ((scsi->valid & CTS_SCSI_VALID_TQ) != 0) {
if ((scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) != 0)
ti->flags |= TINFO_TAG_ENB;
else
ti->flags &= ~TINFO_TAG_ENB;
}
if ((spi->valid & CTS_SPI_VALID_SYNC_RATE) != 0) {
ti->goal.period = spi->sync_period;
if (ti->goal.period > aic->min_period) {
ti->goal.period = 0;
ti->goal.offset = 0;
} else if (ti->goal.period < aic->max_period)
ti->goal.period = aic->max_period;
}
if ((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) != 0) {
ti->goal.offset = spi->sync_offset;
if (ti->goal.offset == 0)
ti->goal.period = 0;
else if (ti->goal.offset > AIC_SYNC_OFFSET)
ti->goal.offset = AIC_SYNC_OFFSET;
}
if ((ti->goal.period != ti->current.period)
|| (ti->goal.offset != ti->current.offset))
ti->flags |= TINFO_SDTR_NEGO;
ccb->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
}
case XPT_GET_TRAN_SETTINGS:
{
struct ccb_trans_settings *cts = &ccb->cts;
struct aic_tinfo *ti = &aic->tinfo[ccb->ccb_h.target_id];
struct ccb_trans_settings_scsi *scsi =
&cts->proto_specific.scsi;
struct ccb_trans_settings_spi *spi =
&cts->xport_specific.spi;
cts->protocol = PROTO_SCSI;
cts->protocol_version = SCSI_REV_2;
cts->transport = XPORT_SPI;
cts->transport_version = 2;
scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB;
spi->flags &= ~CTS_SPI_FLAGS_DISC_ENB;
if ((ti->flags & TINFO_DISC_ENB) != 0)
spi->flags |= CTS_SPI_FLAGS_DISC_ENB;
if ((ti->flags & TINFO_TAG_ENB) != 0)
scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB;
if (cts->type == CTS_TYPE_CURRENT_SETTINGS) {
spi->sync_period = ti->current.period;
spi->sync_offset = ti->current.offset;
} else {
spi->sync_period = ti->user.period;
spi->sync_offset = ti->user.offset;
}
spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT;
spi->valid = CTS_SPI_VALID_SYNC_RATE
| CTS_SPI_VALID_SYNC_OFFSET
| CTS_SPI_VALID_BUS_WIDTH
| CTS_SPI_VALID_DISC;
scsi->valid = CTS_SCSI_VALID_TQ;
ccb->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
}
case XPT_CALC_GEOMETRY:
{
cam_calc_geometry(&ccb->ccg, /*extended*/1);
xpt_done(ccb);
break;
}
case XPT_RESET_BUS: /* Reset the specified SCSI bus */
aic_reset(aic, /*initiate_reset*/TRUE);
ccb->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
case XPT_PATH_INQ: /* Path routing inquiry */
{
struct ccb_pathinq *cpi = &ccb->cpi;
cpi->version_num = 1; /* XXX??? */
cpi->hba_inquiry = PI_SDTR_ABLE | PI_TAG_ABLE;
cpi->target_sprt = 0;
cpi->hba_misc = 0;
cpi->hba_eng_cnt = 0;
cpi->max_target = 7;
cpi->max_lun = 7;
cpi->initiator_id = aic->initiator;
cpi->bus_id = cam_sim_bus(sim);
cpi->base_transfer_speed = 3300;
strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
strncpy(cpi->hba_vid, "Adaptec", HBA_IDLEN);
strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
cpi->unit_number = cam_sim_unit(sim);
cpi->transport = XPORT_SPI;
cpi->transport_version = 2;
cpi->protocol = PROTO_SCSI;
cpi->protocol_version = SCSI_REV_2;
cpi->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
}
default:
ccb->ccb_h.status = CAM_REQ_INVALID;
xpt_done(ccb);
break;
}
}
static void
aic_execute_scb(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error)
{
struct aic_scb *scb = (struct aic_scb *)arg;
union ccb *ccb = scb->ccb;
struct aic_softc *aic = (struct aic_softc *)ccb->ccb_h.ccb_aic_ptr;
if (!dumping)
mtx_assert(&aic->lock, MA_OWNED);
if (ccb->ccb_h.status != CAM_REQ_INPROG) {
aic_free_scb(aic, scb);
xpt_done(ccb);
return;
}
scb->flags |= SCB_ACTIVE;
ccb->ccb_h.status |= CAM_SIM_QUEUED;
TAILQ_INSERT_TAIL(&aic->pending_ccbs, &ccb->ccb_h, sim_links.tqe);
callout_reset_sbt(&scb->timer, SBT_1MS * ccb->ccb_h.timeout, 0,
aic_timeout, scb, 0);
aic_start(aic);
}
/*
* Start another command if the controller is not busy.
*/
static void
aic_start(struct aic_softc *aic)
{
struct ccb_hdr *ccb_h;
struct aic_tinfo *ti;
if (aic->state != AIC_IDLE)
return;
TAILQ_FOREACH(ccb_h, &aic->pending_ccbs, sim_links.tqe) {
ti = &aic->tinfo[ccb_h->target_id];
if ((ti->lubusy & (1 << ccb_h->target_lun)) == 0) {
TAILQ_REMOVE(&aic->pending_ccbs, ccb_h, sim_links.tqe);
aic->nexus = (struct aic_scb *)ccb_h->ccb_scb_ptr;
aic_select(aic);
return;
}
}
CAM_DEBUG_PRINT(CAM_DEBUG_TRACE, ("aic_start: idle\n"));
aic_outb(aic, SIMODE0, ENSELDI);
aic_outb(aic, SIMODE1, ENSCSIRST);
aic_outb(aic, SCSISEQ, ENRESELI);
}
/*
* Start a selection.
*/
static void
aic_select(struct aic_softc *aic)
{
struct aic_scb *scb = aic->nexus;
CAM_DEBUG(scb->ccb->ccb_h.path, CAM_DEBUG_TRACE,
("aic_select - ccb %p\n", scb->ccb));
aic->state = AIC_SELECTING;
aic_outb(aic, DMACNTRL1, 0);
aic_outb(aic, SCSIID, aic->initiator << OID_S | scb->target);
aic_outb(aic, SXFRCTL1, STIMO_256ms | ENSTIMER |
(aic->flags & AIC_PARITY_ENABLE ? ENSPCHK : 0));
aic_outb(aic, SIMODE0, ENSELDI|ENSELDO);
aic_outb(aic, SIMODE1, ENSCSIRST|ENSELTIMO);
aic_outb(aic, SCSISEQ, ENRESELI|ENSELO|ENAUTOATNO);
}
/*
* We have successfully selected a target, prepare for the information
* transfer phases.
*/
static void
aic_selected(struct aic_softc *aic)
{
struct aic_scb *scb = aic->nexus;
union ccb *ccb = scb->ccb;
struct aic_tinfo *ti = &aic->tinfo[scb->target];
CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE,
("aic_selected - ccb %p\n", ccb));
aic->state = AIC_HASNEXUS;
if (scb->flags & SCB_DEVICE_RESET) {
aic->msg_buf[0] = MSG_BUS_DEV_RESET;
aic->msg_len = 1;
aic->msg_outq = AIC_MSG_MSGBUF;
} else {
aic->msg_outq = AIC_MSG_IDENTIFY;
if ((ti->flags & TINFO_TAG_ENB) != 0 &&
(ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) != 0)
aic->msg_outq |= AIC_MSG_TAG_Q;
else
ti->lubusy |= 1 << scb->lun;
if ((ti->flags & TINFO_SDTR_NEGO) != 0)
aic->msg_outq |= AIC_MSG_SDTR;
}
aic_outb(aic, CLRSINT0, CLRSELDO);
aic_outb(aic, CLRSINT1, CLRBUSFREE);
aic_outb(aic, SCSISEQ, ENAUTOATNP);
aic_outb(aic, SIMODE0, 0);
aic_outb(aic, SIMODE1, ENSCSIRST|ENBUSFREE|ENREQINIT);
aic_outb(aic, SCSIRATE, ti->scsirate);
}
/*
* We are re-selected by a target, save the target id and wait for the
* target to further identify itself.
*/
static void
aic_reselected(struct aic_softc *aic)
{
u_int8_t selid;
CAM_DEBUG_PRINT(CAM_DEBUG_TRACE, ("aic_reselected\n"));
/*
* If we have started a selection, it must have lost out in
* the arbitration, put the command back to the pending queue.
*/
if (aic->nexus) {
TAILQ_INSERT_HEAD(&aic->pending_ccbs,
&aic->nexus->ccb->ccb_h, sim_links.tqe);
aic->nexus = NULL;
}
selid = aic_inb(aic, SELID) & ~(1 << aic->initiator);
if (selid & (selid - 1)) {
/* this should never have happened */
printf("aic_reselected: invalid selid %x\n", selid);
aic_reset(aic, /*initiate_reset*/TRUE);
return;
}
aic->state = AIC_RESELECTED;
aic->target = ffs(selid) - 1;
aic->lun = -1;
aic_outb(aic, CLRSINT0, CLRSELDI);
aic_outb(aic, CLRSINT1, CLRBUSFREE);
aic_outb(aic, SIMODE0, 0);
aic_outb(aic, SIMODE1, ENSCSIRST|ENBUSFREE|ENREQINIT);
aic_outb(aic, SCSISEQ, ENAUTOATNP);
aic_outb(aic, SCSIRATE, aic->tinfo[aic->target].scsirate);
}
/*
* Raise ATNO to signal the target that we have a message for it.
*/
static __inline void
aic_sched_msgout(struct aic_softc *aic, u_int8_t msg)
{
if (msg) {
aic->msg_buf[0] = msg;
aic->msg_len = 1;
}
aic->msg_outq |= AIC_MSG_MSGBUF;
aic_outb(aic, SCSISIGO, aic_inb(aic, SCSISIGI) | ATNO);
}
/*
* Wait for SPIORDY (SCSI PIO ready) flag, or a phase change.
*/
static __inline int
aic_spiordy(struct aic_softc *aic)
{
while (!(aic_inb(aic, DMASTAT) & INTSTAT) &&
!(aic_inb(aic, SSTAT0) & SPIORDY))
;
return !(aic_inb(aic, DMASTAT) & INTSTAT);
}
/*
* Reestablish a disconnected nexus.
*/
static void
aic_reconnect(struct aic_softc *aic, int tag)
{
struct aic_scb *scb;
struct ccb_hdr *ccb_h;
CAM_DEBUG_PRINT(CAM_DEBUG_TRACE, ("aic_reconnect\n"));
/* Find the nexus */
scb = NULL;
TAILQ_FOREACH(ccb_h, &aic->nexus_ccbs, sim_links.tqe) {
scb = (struct aic_scb *)ccb_h->ccb_scb_ptr;
if (scb->target == aic->target && scb->lun == aic->lun &&
(tag == -1 || scb->tag == tag))
break;
}
/* ABORT if nothing is found */
if (!ccb_h) {
if (tag == -1)
aic_sched_msgout(aic, MSG_ABORT);
else
aic_sched_msgout(aic, MSG_ABORT_TAG);
xpt_async(AC_UNSOL_RESEL, aic->path, NULL);
return;
}
/* Reestablish the nexus */
TAILQ_REMOVE(&aic->nexus_ccbs, ccb_h, sim_links.tqe);
aic->nexus = scb;
scb->flags &= ~SCB_DISCONNECTED;
aic->state = AIC_HASNEXUS;
}
/*
* Read messages.
*/
static void
aic_msgin(struct aic_softc *aic)
{
int msglen;
CAM_DEBUG_PRINT(CAM_DEBUG_TRACE, ("aic_msgin\n"));
aic_outb(aic, SIMODE1, ENSCSIRST|ENPHASEMIS|ENBUSFREE);
aic_outb(aic, SXFRCTL0, CHEN|SPIOEN);
aic->flags &= ~AIC_DROP_MSGIN;
aic->msg_len = 0;
do {
/*
* If a parity error is detected, drop the remaining
* bytes and inform the target so it could resend
* the messages.
*/
if (aic_inb(aic, SSTAT1) & SCSIPERR) {
aic_outb(aic, CLRSINT1, CLRSCSIPERR);
aic->flags |= AIC_DROP_MSGIN;
aic_sched_msgout(aic, MSG_PARITY_ERROR);
}
if ((aic->flags & AIC_DROP_MSGIN)) {
aic_inb(aic, SCSIDAT);
continue;
}
/* read the message byte without ACKing on it */
aic->msg_buf[aic->msg_len++] = aic_inb(aic, SCSIBUS);
if (aic->msg_buf[0] == MSG_EXTENDED) {
if (aic->msg_len < 2) {
(void) aic_inb(aic, SCSIDAT);
continue;
}
switch (aic->msg_buf[2]) {
case MSG_EXT_SDTR:
msglen = MSG_EXT_SDTR_LEN;
break;
case MSG_EXT_WDTR:
msglen = MSG_EXT_WDTR_LEN;
break;
default:
msglen = 0;
break;
}
if (aic->msg_buf[1] != msglen) {
aic->flags |= AIC_DROP_MSGIN;
aic_sched_msgout(aic, MSG_MESSAGE_REJECT);
}
msglen += 2;
} else if (aic->msg_buf[0] >= 0x20 && aic->msg_buf[0] <= 0x2f)
msglen = 2;
else
msglen = 1;
/*
* If we have a complete message, handle it before the final
* ACK (in case we decide to reject the message).
*/
if (aic->msg_len == msglen) {
aic_handle_msgin(aic);
aic->msg_len = 0;
}
/* ACK on the message byte */
(void) aic_inb(aic, SCSIDAT);
} while (aic_spiordy(aic));
aic_outb(aic, SXFRCTL0, CHEN);
aic_outb(aic, SIMODE1, ENSCSIRST|ENBUSFREE|ENREQINIT);
}
/*
* Handle a message.
*/
static void
aic_handle_msgin(struct aic_softc *aic)
{
struct aic_scb *scb;
struct ccb_hdr *ccb_h;
struct aic_tinfo *ti;
struct ccb_trans_settings neg;
struct ccb_trans_settings_spi *spi = &neg.xport_specific.spi;
if (aic->state == AIC_RESELECTED) {
if (!MSG_ISIDENTIFY(aic->msg_buf[0])) {
aic_sched_msgout(aic, MSG_MESSAGE_REJECT);
return;
}
aic->lun = aic->msg_buf[0] & MSG_IDENTIFY_LUNMASK;
if (aic->tinfo[aic->target].lubusy & (1 << aic->lun))
aic_reconnect(aic, -1);
else
aic->state = AIC_RECONNECTING;
return;
}
if (aic->state == AIC_RECONNECTING) {
if (aic->msg_buf[0] != MSG_SIMPLE_Q_TAG) {
aic_sched_msgout(aic, MSG_MESSAGE_REJECT);
return;
}
aic_reconnect(aic, aic->msg_buf[1]);
return;
}
switch (aic->msg_buf[0]) {
case MSG_CMDCOMPLETE: {
struct ccb_scsiio *csio;
scb = aic->nexus;
ccb_h = &scb->ccb->ccb_h;
csio = &scb->ccb->csio;
if ((scb->flags & SCB_SENSE) != 0) {
/* auto REQUEST SENSE command */
scb->flags &= ~SCB_SENSE;
csio->sense_resid = scb->data_len;
if (scb->status == SCSI_STATUS_OK) {
ccb_h->status |=
CAM_SCSI_STATUS_ERROR|CAM_AUTOSNS_VALID;
/*scsi_sense_print(csio);*/
} else {
ccb_h->status |= CAM_AUTOSENSE_FAIL;
printf("ccb %p sense failed %x\n",
ccb_h, scb->status);
}
} else {
csio->scsi_status = scb->status;
csio->resid = scb->data_len;
if (scb->status == SCSI_STATUS_OK) {
/* everything goes well */
ccb_h->status |= CAM_REQ_CMP;
} else if ((ccb_h->flags & CAM_DIS_AUTOSENSE) == 0 &&
(csio->scsi_status == SCSI_STATUS_CHECK_COND ||
csio->scsi_status == SCSI_STATUS_CMD_TERMINATED)) {
/* try to retrieve sense information */
scb->flags |= SCB_SENSE;
aic->flags |= AIC_BUSFREE_OK;
return;
} else
ccb_h->status |= CAM_SCSI_STATUS_ERROR;
}
aic_done(aic, scb);
aic->flags |= AIC_BUSFREE_OK;
break;
}
case MSG_EXTENDED:
switch (aic->msg_buf[2]) {
case MSG_EXT_SDTR:
scb = aic->nexus;
ti = &aic->tinfo[scb->target];
if (ti->flags & TINFO_SDTR_SENT) {
ti->current.period = aic->msg_buf[3];
ti->current.offset = aic->msg_buf[4];
} else {
ti->current.period = aic->msg_buf[3] =
max(ti->goal.period, aic->msg_buf[3]);
ti->current.offset = aic->msg_buf[4] =
min(ti->goal.offset, aic->msg_buf[4]);
/*
* The target initiated the negotiation,
* send back a response.
*/
aic_sched_msgout(aic, 0);
}
ti->flags &= ~(TINFO_SDTR_SENT|TINFO_SDTR_NEGO);
ti->scsirate = ti->current.offset ? ti->current.offset |
((ti->current.period * 4 + 49) / 50 - 2) << 4 : 0;
aic_outb(aic, SCSIRATE, ti->scsirate);
memset(&neg, 0, sizeof (neg));
neg.protocol = PROTO_SCSI;
neg.protocol_version = SCSI_REV_2;
neg.transport = XPORT_SPI;
neg.transport_version = 2;
spi->sync_period = ti->goal.period = ti->current.period;
spi->sync_offset = ti->goal.offset = ti->current.offset;
spi->valid = CTS_SPI_VALID_SYNC_RATE
| CTS_SPI_VALID_SYNC_OFFSET;
ccb_h = &scb->ccb->ccb_h;
xpt_setup_ccb(&neg.ccb_h, ccb_h->path, 1);
xpt_async(AC_TRANSFER_NEG, ccb_h->path, &neg);
break;
case MSG_EXT_WDTR:
default:
aic_sched_msgout(aic, MSG_MESSAGE_REJECT);
break;
}
break;
case MSG_DISCONNECT:
scb = aic->nexus;
ccb_h = &scb->ccb->ccb_h;
TAILQ_INSERT_TAIL(&aic->nexus_ccbs, ccb_h, sim_links.tqe);
scb->flags |= SCB_DISCONNECTED;
aic->flags |= AIC_BUSFREE_OK;
aic->nexus = NULL;
CAM_DEBUG(ccb_h->path, CAM_DEBUG_TRACE, ("disconnected\n"));
break;
case MSG_MESSAGE_REJECT:
switch (aic->msg_outq & -aic->msg_outq) {
case AIC_MSG_TAG_Q:
scb = aic->nexus;
ti = &aic->tinfo[scb->target];
ti->flags &= ~TINFO_TAG_ENB;
ti->lubusy |= 1 << scb->lun;
break;
case AIC_MSG_SDTR:
scb = aic->nexus;
ti = &aic->tinfo[scb->target];
ti->current.period = ti->goal.period = 0;
ti->current.offset = ti->goal.offset = 0;
ti->flags &= ~(TINFO_SDTR_SENT|TINFO_SDTR_NEGO);
ti->scsirate = 0;
aic_outb(aic, SCSIRATE, ti->scsirate);
memset(&neg, 0, sizeof (neg));
neg.protocol = PROTO_SCSI;
neg.protocol_version = SCSI_REV_2;
neg.transport = XPORT_SPI;
neg.transport_version = 2;
spi->sync_period = ti->current.period;
spi->sync_offset = ti->current.offset;
spi->valid = CTS_SPI_VALID_SYNC_RATE
| CTS_SPI_VALID_SYNC_OFFSET;
ccb_h = &scb->ccb->ccb_h;
xpt_setup_ccb(&neg.ccb_h, ccb_h->path, 1);
xpt_async(AC_TRANSFER_NEG, ccb_h->path, &neg);
break;
default:
break;
}
break;
case MSG_SAVEDATAPOINTER:
break;
case MSG_RESTOREPOINTERS:
break;
case MSG_NOOP:
break;
default:
aic_sched_msgout(aic, MSG_MESSAGE_REJECT);
break;
}
}
/*
* Send messages.
*/
static void
aic_msgout(struct aic_softc *aic)
{
struct aic_scb *scb;
union ccb *ccb;
struct aic_tinfo *ti;
int msgidx = 0;
CAM_DEBUG_PRINT(CAM_DEBUG_TRACE, ("aic_msgout\n"));
aic_outb(aic, SIMODE1, ENSCSIRST|ENPHASEMIS|ENBUSFREE);
aic_outb(aic, SXFRCTL0, CHEN|SPIOEN);
/*
* If the previous phase is also the message out phase,
* we need to retransmit all the messages, probably
* because the target has detected a parity error during
* the past transmission.
*/
if (aic->prev_phase == PH_MSGOUT)
aic->msg_outq = aic->msg_sent;
do {
int q = aic->msg_outq;
if (msgidx > 0 && msgidx == aic->msg_len) {
/* complete message sent, start the next one */
q &= -q;
aic->msg_sent |= q;
aic->msg_outq ^= q;
q = aic->msg_outq;
msgidx = 0;
}
if (msgidx == 0) {
/* setup the message */
switch (q & -q) {
case AIC_MSG_IDENTIFY:
scb = aic->nexus;
ccb = scb->ccb;
ti = &aic->tinfo[scb->target];
aic->msg_buf[0] = MSG_IDENTIFY(scb->lun,
(ti->flags & TINFO_DISC_ENB) &&
!(ccb->ccb_h.flags & CAM_DIS_DISCONNECT));
aic->msg_len = 1;
break;
case AIC_MSG_TAG_Q:
scb = aic->nexus;
ccb = scb->ccb;
aic->msg_buf[0] = ccb->csio.tag_action;
aic->msg_buf[1] = scb->tag;
aic->msg_len = 2;
break;
case AIC_MSG_SDTR:
scb = aic->nexus;
ti = &aic->tinfo[scb->target];
aic->msg_buf[0] = MSG_EXTENDED;
aic->msg_buf[1] = MSG_EXT_SDTR_LEN;
aic->msg_buf[2] = MSG_EXT_SDTR;
aic->msg_buf[3] = ti->goal.period;
aic->msg_buf[4] = ti->goal.offset;
aic->msg_len = MSG_EXT_SDTR_LEN + 2;
ti->flags |= TINFO_SDTR_SENT;
break;
case AIC_MSG_MSGBUF:
/* a single message already in the buffer */
if (aic->msg_buf[0] == MSG_BUS_DEV_RESET ||
aic->msg_buf[0] == MSG_ABORT ||
aic->msg_buf[0] == MSG_ABORT_TAG)
aic->flags |= AIC_BUSFREE_OK;
break;
}
}
/*
* If this is the last message byte of all messages,
* clear ATNO to signal transmission complete.
*/
if ((q & (q - 1)) == 0 && msgidx == aic->msg_len - 1)
aic_outb(aic, CLRSINT1, CLRATNO);
/* transmit the message byte */
aic_outb(aic, SCSIDAT, aic->msg_buf[msgidx++]);
} while (aic_spiordy(aic));
aic_outb(aic, SXFRCTL0, CHEN);
aic_outb(aic, SIMODE1, ENSCSIRST|ENBUSFREE|ENREQINIT);
}
/*
* Read data bytes.
*/
static void
aic_datain(struct aic_softc *aic)
{
struct aic_scb *scb = aic->nexus;
u_int8_t dmastat, dmacntrl0;
int n;
CAM_DEBUG_PRINT(CAM_DEBUG_TRACE, ("aic_datain\n"));
aic_outb(aic, SIMODE1, ENSCSIRST|ENPHASEMIS|ENBUSFREE);
aic_outb(aic, SXFRCTL0, SCSIEN|DMAEN|CHEN);
dmacntrl0 = ENDMA;
if (aic->flags & AIC_DWIO_ENABLE)
dmacntrl0 |= DWORDPIO;
aic_outb(aic, DMACNTRL0, dmacntrl0);
while (scb->data_len > 0) {
for (;;) {
/* wait for the fifo to fill up or a phase change */
dmastat = aic_inb(aic, DMASTAT);
if (dmastat & (INTSTAT|DFIFOFULL))
break;
}
if (dmastat & DFIFOFULL) {
n = FIFOSIZE;
} else {
/*
* No more data, wait for the remaining bytes in
* the scsi fifo to be transfer to the host fifo.
*/
while (!(aic_inb(aic, SSTAT2) & SEMPTY))
;
n = aic_inb(aic, FIFOSTAT);
}
n = imin(scb->data_len, n);
if (aic->flags & AIC_DWIO_ENABLE) {
if (n >= 12) {
aic_insl(aic, DMADATALONG, scb->data_ptr, n>>2);
scb->data_ptr += n & ~3;
scb->data_len -= n & ~3;
n &= 3;
}
} else {
if (n >= 8) {
aic_insw(aic, DMADATA, scb->data_ptr, n >> 1);
scb->data_ptr += n & ~1;
scb->data_len -= n & ~1;
n &= 1;
}
}
if (n) {
aic_outb(aic, DMACNTRL0, ENDMA|B8MODE);
aic_insb(aic, DMADATA, scb->data_ptr, n);
scb->data_ptr += n;
scb->data_len -= n;
aic_outb(aic, DMACNTRL0, dmacntrl0);
}
if (dmastat & INTSTAT)
break;
}
aic_outb(aic, SXFRCTL0, CHEN);
aic_outb(aic, SIMODE1, ENSCSIRST|ENBUSFREE|ENREQINIT);
}
/*
* Send data bytes.
*/
static void
aic_dataout(struct aic_softc *aic)
{
struct aic_scb *scb = aic->nexus;
u_int8_t dmastat, dmacntrl0, sstat2;
int n;
CAM_DEBUG_PRINT(CAM_DEBUG_TRACE, ("aic_dataout\n"));
aic_outb(aic, SIMODE1, ENSCSIRST|ENPHASEMIS|ENBUSFREE);
aic_outb(aic, SXFRCTL0, SCSIEN|DMAEN|CHEN);
dmacntrl0 = ENDMA|WRITE;
if (aic->flags & AIC_DWIO_ENABLE)
dmacntrl0 |= DWORDPIO;
aic_outb(aic, DMACNTRL0, dmacntrl0);
while (scb->data_len > 0) {
for (;;) {
/* wait for the fifo to clear up or a phase change */
dmastat = aic_inb(aic, DMASTAT);
if (dmastat & (INTSTAT|DFIFOEMP))
break;
}
if (dmastat & INTSTAT)
break;
n = imin(scb->data_len, FIFOSIZE);
if (aic->flags & AIC_DWIO_ENABLE) {
if (n >= 12) {
aic_outsl(aic, DMADATALONG, scb->data_ptr,n>>2);
scb->data_ptr += n & ~3;
scb->data_len -= n & ~3;
n &= 3;
}
} else {
if (n >= 8) {
aic_outsw(aic, DMADATA, scb->data_ptr, n >> 1);
scb->data_ptr += n & ~1;
scb->data_len -= n & ~1;
n &= 1;
}
}
if (n) {
aic_outb(aic, DMACNTRL0, ENDMA|WRITE|B8MODE);
aic_outsb(aic, DMADATA, scb->data_ptr, n);
scb->data_ptr += n;
scb->data_len -= n;
aic_outb(aic, DMACNTRL0, dmacntrl0);
}
}
for (;;) {
/* wait until all bytes in the fifos are transmitted */
dmastat = aic_inb(aic, DMASTAT);
sstat2 = aic_inb(aic, SSTAT2);
if ((dmastat & DFIFOEMP) && (sstat2 & SEMPTY))
break;
if (dmastat & INTSTAT) {
/* adjust for untransmitted bytes */
n = aic_inb(aic, FIFOSTAT) + (sstat2 & 0xf);
scb->data_ptr -= n;
scb->data_len += n;
/* clear the fifo */
aic_outb(aic, SXFRCTL0, CHEN|CLRCH);
aic_outb(aic, DMACNTRL0, RSTFIFO);
break;
}
}
aic_outb(aic, SXFRCTL0, CHEN);
aic_outb(aic, SIMODE1, ENSCSIRST|ENBUSFREE|ENREQINIT);
}
/*
* Send the scsi command.
*/
static void
aic_cmd(struct aic_softc *aic)
{
struct aic_scb *scb = aic->nexus;
struct scsi_request_sense sense_cmd;
CAM_DEBUG_PRINT(CAM_DEBUG_TRACE, ("aic_cmd\n"));
if (scb->flags & SCB_SENSE) {
/* autosense request */
sense_cmd.opcode = REQUEST_SENSE;
sense_cmd.byte2 = scb->lun << 5;
sense_cmd.length = scb->ccb->csio.sense_len;
sense_cmd.control = 0;
sense_cmd.unused[0] = 0;
sense_cmd.unused[1] = 0;
scb->cmd_ptr = (u_int8_t *)&sense_cmd;
scb->cmd_len = sizeof(sense_cmd);
scb->data_ptr = (u_int8_t *)&scb->ccb->csio.sense_data;
scb->data_len = scb->ccb->csio.sense_len;
}
aic_outb(aic, SIMODE1, ENSCSIRST|ENPHASEMIS|ENBUSFREE);
aic_outb(aic, DMACNTRL0, ENDMA|WRITE);
aic_outb(aic, SXFRCTL0, SCSIEN|DMAEN|CHEN);
aic_outsw(aic, DMADATA, (u_int16_t *)scb->cmd_ptr, scb->cmd_len >> 1);
while ((aic_inb(aic, SSTAT2) & SEMPTY) == 0 &&
(aic_inb(aic, DMASTAT) & INTSTAT) == 0)
;
aic_outb(aic, SXFRCTL0, CHEN);
aic_outb(aic, SIMODE1, ENSCSIRST|ENBUSFREE|ENREQINIT);
}
/*
* Finish off a command. The caller is responsible to remove the ccb
* from any queue.
*/
static void
aic_done(struct aic_softc *aic, struct aic_scb *scb)
{
union ccb *ccb = scb->ccb;
CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE,
("aic_done - ccb %p status %x resid %d\n",
ccb, ccb->ccb_h.status, ccb->csio.resid));
callout_stop(&scb->timer);
if ((scb->flags & SCB_DEVICE_RESET) != 0 &&
ccb->ccb_h.func_code != XPT_RESET_DEV) {
struct cam_path *path;
struct ccb_hdr *ccb_h;
cam_status error;
error = xpt_create_path(&path, /*periph*/NULL,
cam_sim_path(aic->sim),
scb->target,
CAM_LUN_WILDCARD);
if (error == CAM_REQ_CMP) {
xpt_async(AC_SENT_BDR, path, NULL);
xpt_free_path(path);
}
ccb_h = TAILQ_FIRST(&aic->pending_ccbs);
while (ccb_h != NULL) {
struct aic_scb *pending_scb;
pending_scb = (struct aic_scb *)ccb_h->ccb_scb_ptr;
if (ccb_h->target_id == scb->target) {
ccb_h->status |= CAM_BDR_SENT;
ccb_h = TAILQ_NEXT(ccb_h, sim_links.tqe);
TAILQ_REMOVE(&aic->pending_ccbs,
&pending_scb->ccb->ccb_h, sim_links.tqe);
aic_done(aic, pending_scb);
} else {
callout_reset_sbt(&pending_scb->timer,
SBT_1MS * ccb_h->timeout, 0, aic_timeout,
pending_scb, 0);
ccb_h = TAILQ_NEXT(ccb_h, sim_links.tqe);
}
}
ccb_h = TAILQ_FIRST(&aic->nexus_ccbs);
while (ccb_h != NULL) {
struct aic_scb *nexus_scb;
nexus_scb = (struct aic_scb *)ccb_h->ccb_scb_ptr;
if (ccb_h->target_id == scb->target) {
ccb_h->status |= CAM_BDR_SENT;
ccb_h = TAILQ_NEXT(ccb_h, sim_links.tqe);
TAILQ_REMOVE(&aic->nexus_ccbs,
&nexus_scb->ccb->ccb_h, sim_links.tqe);
aic_done(aic, nexus_scb);
} else {
callout_reset_sbt(&nexus_scb->timer,
SBT_1MS * ccb_h->timeout, 0, aic_timeout,
nexus_scb, 0);
ccb_h = TAILQ_NEXT(ccb_h, sim_links.tqe);
}
}
}
if (aic->nexus == scb || scb->flags & SCB_DISCONNECTED)
aic->tinfo[scb->target].lubusy &= ~(1 << scb->lun);
if (aic->nexus == scb) {
aic->nexus = NULL;
}
aic_free_scb(aic, scb);
xpt_done(ccb);
}
static void
aic_poll(struct cam_sim *sim)
{
aic_intr_locked(cam_sim_softc(sim));
}
static void
aic_timeout(void *arg)
{
struct aic_scb *scb = (struct aic_scb *)arg;
union ccb *ccb = scb->ccb;
struct aic_softc *aic = (struct aic_softc *)ccb->ccb_h.ccb_aic_ptr;
mtx_assert(&aic->lock, MA_OWNED);
xpt_print_path(ccb->ccb_h.path);
printf("ccb %p - timed out", ccb);
if (aic->nexus && aic->nexus != scb)
printf(", nexus %p", aic->nexus->ccb);
printf(", phase 0x%x, state %d\n", aic_inb(aic, SCSISIGI), aic->state);
if ((scb->flags & SCB_ACTIVE) == 0) {
xpt_print_path(ccb->ccb_h.path);
printf("ccb %p - timed out already completed\n", ccb);
return;
}
if ((scb->flags & SCB_DEVICE_RESET) == 0 && aic->nexus == scb) {
struct ccb_hdr *ccb_h = &scb->ccb->ccb_h;
struct aic_scb *pending_scb;
if ((ccb_h->status & CAM_RELEASE_SIMQ) == 0) {
xpt_freeze_simq(aic->sim, /*count*/1);
ccb_h->status |= CAM_RELEASE_SIMQ;
}
TAILQ_FOREACH(ccb_h, &aic->pending_ccbs, sim_links.tqe) {
pending_scb = ccb_h->ccb_scb_ptr;
callout_stop(&pending_scb->timer);
}
TAILQ_FOREACH(ccb_h, &aic->nexus_ccbs, sim_links.tqe) {
pending_scb = ccb_h->ccb_scb_ptr;
callout_stop(&pending_scb->timer);
}
scb->flags |= SCB_DEVICE_RESET;
callout_reset(&scb->timer, 5 * hz, aic_timeout, scb);
aic_sched_msgout(aic, MSG_BUS_DEV_RESET);
} else {
if (aic->nexus == scb) {
ccb->ccb_h.status |= CAM_CMD_TIMEOUT;
aic_done(aic, scb);
}
aic_reset(aic, /*initiate_reset*/TRUE);
}
}
void
aic_intr(void *arg)
{
struct aic_softc *aic = (struct aic_softc *)arg;
mtx_lock(&aic->lock);
aic_intr_locked(aic);
mtx_unlock(&aic->lock);
}
void
aic_intr_locked(struct aic_softc *aic)
{
u_int8_t sstat0, sstat1;
union ccb *ccb;
struct aic_scb *scb;
if (!(aic_inb(aic, DMASTAT) & INTSTAT))
return;
aic_outb(aic, DMACNTRL0, 0);
sstat0 = aic_inb(aic, SSTAT0);
sstat1 = aic_inb(aic, SSTAT1);
if ((sstat1 & SCSIRSTI) != 0) {
/* a device-initiated bus reset */
aic_outb(aic, CLRSINT1, CLRSCSIRSTI);
aic_reset(aic, /*initiate_reset*/FALSE);
return;
}
if ((sstat1 & SCSIPERR) != 0) {
aic_outb(aic, CLRSINT1, CLRSCSIPERR);
aic_sched_msgout(aic, MSG_PARITY_ERROR);
aic_outb(aic, DMACNTRL0, INTEN);
return;
}
if (aic_inb(aic, SSTAT4)) {
aic_outb(aic, CLRSERR, CLRSYNCERR|CLRFWERR|CLRFRERR);
aic_reset(aic, /*initiate_reset*/TRUE);
return;
}
if (aic->state <= AIC_SELECTING) {
if ((sstat0 & SELDI) != 0) {
aic_reselected(aic);
aic_outb(aic, DMACNTRL0, INTEN);
return;
}
if ((sstat0 & SELDO) != 0) {
aic_selected(aic);
aic_outb(aic, DMACNTRL0, INTEN);
return;
}
if ((sstat1 & SELTO) != 0) {
scb = aic->nexus;
ccb = scb->ccb;
ccb->ccb_h.status = CAM_SEL_TIMEOUT;
aic_done(aic, scb);
while ((sstat1 & BUSFREE) == 0)
sstat1 = aic_inb(aic, SSTAT1);
aic->flags |= AIC_BUSFREE_OK;
}
}
if ((sstat1 & BUSFREE) != 0) {
aic_outb(aic, SCSISEQ, 0);
aic_outb(aic, CLRSINT0, sstat0);
aic_outb(aic, CLRSINT1, sstat1);
if ((scb = aic->nexus)) {
if ((aic->flags & AIC_BUSFREE_OK) == 0) {
ccb = scb->ccb;
ccb->ccb_h.status = CAM_UNEXP_BUSFREE;
aic_done(aic, scb);
} else if (scb->flags & SCB_DEVICE_RESET) {
ccb = scb->ccb;
if (ccb->ccb_h.func_code == XPT_RESET_DEV) {
xpt_async(AC_SENT_BDR,
ccb->ccb_h.path, NULL);
ccb->ccb_h.status |= CAM_REQ_CMP;
} else
ccb->ccb_h.status |= CAM_CMD_TIMEOUT;
aic_done(aic, scb);
} else if (scb->flags & SCB_SENSE) {
/* autosense request */
aic->flags &= ~AIC_BUSFREE_OK;
aic->tinfo[scb->target].lubusy &=
~(1 << scb->lun);
aic_select(aic);
aic_outb(aic, DMACNTRL0, INTEN);
return;
}
}
aic->flags &= ~AIC_BUSFREE_OK;
aic->state = AIC_IDLE;
aic_start(aic);
aic_outb(aic, DMACNTRL0, INTEN);
return;
}
if ((sstat1 & REQINIT) != 0) {
u_int8_t phase = aic_inb(aic, SCSISIGI) & PH_MASK;
aic_outb(aic, SCSISIGO, phase);
aic_outb(aic, CLRSINT1, CLRPHASECHG);
switch (phase) {
case PH_MSGOUT:
aic_msgout(aic);
break;
case PH_MSGIN:
aic_msgin(aic);
break;
case PH_STAT:
scb = aic->nexus;
ccb = scb->ccb;
aic_outb(aic, DMACNTRL0, 0);
aic_outb(aic, SXFRCTL0, CHEN|SPIOEN);
scb->status = aic_inb(aic, SCSIDAT);
aic_outb(aic, SXFRCTL0, CHEN);
break;
case PH_CMD:
aic_cmd(aic);
break;
case PH_DATAIN:
aic_datain(aic);
break;
case PH_DATAOUT:
aic_dataout(aic);
break;
}
aic->prev_phase = phase;
aic_outb(aic, DMACNTRL0, INTEN);
return;
}
printf("aic_intr: unexpected intr sstat0 %x sstat1 %x\n",
sstat0, sstat1);
aic_outb(aic, DMACNTRL0, INTEN);
}
/*
* Reset ourselves.
*/
static void
aic_chip_reset(struct aic_softc *aic)
{
/*
* Doc. recommends to clear these two registers before
* operations commence
*/
aic_outb(aic, SCSITEST, 0);
aic_outb(aic, TEST, 0);
/* Reset SCSI-FIFO and abort any transfers */
aic_outb(aic, SXFRCTL0, CHEN|CLRCH|CLRSTCNT);
/* Reset HOST-FIFO */
aic_outb(aic, DMACNTRL0, RSTFIFO);
aic_outb(aic, DMACNTRL1, 0);
/* Disable all selection features */
aic_outb(aic, SCSISEQ, 0);
aic_outb(aic, SXFRCTL1, 0);
/* Disable interrupts */
aic_outb(aic, SIMODE0, 0);
aic_outb(aic, SIMODE1, 0);
/* Clear interrupts */
aic_outb(aic, CLRSINT0, 0x7f);
aic_outb(aic, CLRSINT1, 0xef);
/* Disable synchronous transfers */
aic_outb(aic, SCSIRATE, 0);
/* Haven't seen ant errors (yet) */
aic_outb(aic, CLRSERR, 0x07);
/* Set our SCSI-ID */
aic_outb(aic, SCSIID, aic->initiator << OID_S);
aic_outb(aic, BRSTCNTRL, EISA_BRST_TIM);
}
/*
* Reset the SCSI bus
*/
static void
aic_scsi_reset(struct aic_softc *aic)
{
aic_outb(aic, SCSISEQ, SCSIRSTO);
DELAY(500);
aic_outb(aic, SCSISEQ, 0);
DELAY(50);
}
/*
* Reset. Abort all pending commands.
*/
static void
aic_reset(struct aic_softc *aic, int initiate_reset)
{
struct ccb_hdr *ccb_h;
CAM_DEBUG_PRINT(CAM_DEBUG_TRACE, ("aic_reset\n"));
if (initiate_reset)
aic_scsi_reset(aic);
aic_chip_reset(aic);
xpt_async(AC_BUS_RESET, aic->path, NULL);
while ((ccb_h = TAILQ_FIRST(&aic->pending_ccbs)) != NULL) {
TAILQ_REMOVE(&aic->pending_ccbs, ccb_h, sim_links.tqe);
ccb_h->status |= CAM_SCSI_BUS_RESET;
aic_done(aic, (struct aic_scb *)ccb_h->ccb_scb_ptr);
}
while ((ccb_h = TAILQ_FIRST(&aic->nexus_ccbs)) != NULL) {
TAILQ_REMOVE(&aic->nexus_ccbs, ccb_h, sim_links.tqe);
ccb_h->status |= CAM_SCSI_BUS_RESET;
aic_done(aic, (struct aic_scb *)ccb_h->ccb_scb_ptr);
}
if (aic->nexus) {
ccb_h = &aic->nexus->ccb->ccb_h;
ccb_h->status |= CAM_SCSI_BUS_RESET;
aic_done(aic, aic->nexus);
}
aic->state = AIC_IDLE;
aic_outb(aic, DMACNTRL0, INTEN);
}
static char *aic_chip_names[] = {
"AIC6260", "AIC6360", "AIC6370", "GM82C700",
};
static struct {
int type;
char *idstring;
} aic_chip_ids[] = {
{ AIC6360, IDSTRING_AIC6360 },
{ AIC6370, IDSTRING_AIC6370 },
{ GM82C700, IDSTRING_GM82C700 },
};
static void
aic_init(struct aic_softc *aic)
{
struct aic_scb *scb;
struct aic_tinfo *ti;
u_int8_t porta, portb;
char chip_id[33];
int i;
TAILQ_INIT(&aic->pending_ccbs);
TAILQ_INIT(&aic->nexus_ccbs);
SLIST_INIT(&aic->free_scbs);
aic->nexus = NULL;
aic->state = AIC_IDLE;
aic->prev_phase = -1;
aic->flags = 0;
aic_chip_reset(aic);
aic_scsi_reset(aic);
/* determine the chip type from its ID string */
aic->chip_type = AIC6260;
aic_insb(aic, ID, chip_id, sizeof(chip_id) - 1);
chip_id[sizeof(chip_id) - 1] = '\0';
for (i = 0; i < sizeof(aic_chip_ids) / sizeof(aic_chip_ids[0]); i++) {
if (!strcmp(chip_id, aic_chip_ids[i].idstring)) {
aic->chip_type = aic_chip_ids[i].type;
break;
}
}
porta = aic_inb(aic, PORTA);
portb = aic_inb(aic, PORTB);
aic->initiator = PORTA_ID(porta);
if (PORTA_PARITY(porta))
aic->flags |= AIC_PARITY_ENABLE;
if (PORTB_DISC(portb))
aic->flags |= AIC_DISC_ENABLE;
if (PORTB_DMA(portb))
aic->flags |= AIC_DMA_ENABLE;
/*
* We can do fast SCSI (10MHz clock rate) if bit 4 of portb
* is set and we've got a 6360. The 6260 can only do standard
* 5MHz SCSI.
*/
if (aic->chip_type > AIC6260 || aic_inb(aic, REV)) {
if (PORTB_FSYNC(portb))
aic->flags |= AIC_FAST_ENABLE;
aic->flags |= AIC_DWIO_ENABLE;
}
if (aic->flags & AIC_FAST_ENABLE)
aic->max_period = AIC_FAST_SYNC_PERIOD;
else
aic->max_period = AIC_SYNC_PERIOD;
aic->min_period = AIC_MIN_SYNC_PERIOD;
for (i = 255; i >= 0; i--) {
scb = &aic->scbs[i];
scb->tag = i;
callout_init_mtx(&scb->timer, &aic->lock, 0);
aic_free_scb(aic, scb);
}
for (i = 0; i < 8; i++) {
if (i == aic->initiator)
continue;
ti = &aic->tinfo[i];
bzero(ti, sizeof(*ti));
ti->flags = TINFO_TAG_ENB;
if (aic->flags & AIC_DISC_ENABLE)
ti->flags |= TINFO_DISC_ENB;
ti->user.period = aic->max_period;
ti->user.offset = AIC_SYNC_OFFSET;
ti->scsirate = 0;
}
aic_outb(aic, DMACNTRL0, INTEN);
}
int
aic_probe(struct aic_softc *aic)
{
int i;
/* Remove aic6360 from possible powerdown mode */
aic_outb(aic, DMACNTRL0, 0);
#define STSIZE 16
aic_outb(aic, DMACNTRL1, 0); /* Reset stack pointer */
for (i = 0; i < STSIZE; i++)
aic_outb(aic, STACK, i);
/* See if we can pull out the same sequence */
aic_outb(aic, DMACNTRL1, 0);
for (i = 0; i < STSIZE && aic_inb(aic, STACK) == i; i++)
;
if (i != STSIZE)
return (ENXIO);
#undef STSIZE
return (0);
}
int
aic_attach(struct aic_softc *aic)
{
struct cam_devq *devq;
/*
* Create the device queue for our SIM.
*/
devq = cam_simq_alloc(256);
if (devq == NULL)
return (ENOMEM);
/*
* Construct our SIM entry
*/
aic->sim = cam_sim_alloc(aic_action, aic_poll, "aic", aic,
device_get_unit(aic->dev), &aic->lock, 2, 256, devq);
if (aic->sim == NULL) {
cam_simq_free(devq);
return (ENOMEM);
}
mtx_lock(&aic->lock);
if (xpt_bus_register(aic->sim, aic->dev, 0) != CAM_SUCCESS) {
cam_sim_free(aic->sim, /*free_devq*/TRUE);
mtx_unlock(&aic->lock);
return (ENXIO);
}
if (xpt_create_path(&aic->path, /*periph*/NULL,
cam_sim_path(aic->sim), CAM_TARGET_WILDCARD,
CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
xpt_bus_deregister(cam_sim_path(aic->sim));
cam_sim_free(aic->sim, /*free_devq*/TRUE);
mtx_unlock(&aic->lock);
return (ENXIO);
}
aic_init(aic);
device_printf(aic->dev, "%s", aic_chip_names[aic->chip_type]);
if (aic->flags & AIC_DMA_ENABLE)
printf(", dma");
if (aic->flags & AIC_DISC_ENABLE)
printf(", disconnection");
if (aic->flags & AIC_PARITY_ENABLE)
printf(", parity check");
if (aic->flags & AIC_FAST_ENABLE)
printf(", fast SCSI");
printf("\n");
mtx_unlock(&aic->lock);
return (0);
}
int
aic_detach(struct aic_softc *aic)
{
struct aic_scb *scb;
int i;
mtx_lock(&aic->lock);
xpt_async(AC_LOST_DEVICE, aic->path, NULL);
xpt_free_path(aic->path);
xpt_bus_deregister(cam_sim_path(aic->sim));
cam_sim_free(aic->sim, /*free_devq*/TRUE);
mtx_unlock(&aic->lock);
for (i = 255; i >= 0; i--) {
scb = &aic->scbs[i];
callout_drain(&scb->timer);
}
return (0);
}