diff options
author | luoqi <luoqi@FreeBSD.org> | 1999-10-21 08:56:53 +0000 |
---|---|---|
committer | luoqi <luoqi@FreeBSD.org> | 1999-10-21 08:56:53 +0000 |
commit | 5dab878b88e91782e452b9ce86c4fde1506ae95e (patch) | |
tree | 9ffd6f41f5fa9cac5a2e4b5072cc82c92688700a /sys/dev/aic/aic.c | |
parent | 37b81ac9d3bca142c50c370b5f86752e070dc23f (diff) | |
download | FreeBSD-src-5dab878b88e91782e452b9ce86c4fde1506ae95e.zip FreeBSD-src-5dab878b88e91782e452b9ce86c4fde1506ae95e.tar.gz |
Adaptec 6260/6360 CAM driver.
Diffstat (limited to 'sys/dev/aic/aic.c')
-rw-r--r-- | sys/dev/aic/aic.c | 1373 |
1 files changed, 1373 insertions, 0 deletions
diff --git a/sys/dev/aic/aic.c b/sys/dev/aic/aic.c new file mode 100644 index 0000000..f99e216 --- /dev/null +++ b/sys/dev/aic/aic.c @@ -0,0 +1,1373 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/kernel.h> +#include <sys/sysctl.h> +#include <sys/bus.h> + +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <machine/clock.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 __P((struct cam_sim *sim, union ccb *ccb)); +static void aic_execute_scb __P((void *arg, bus_dma_segment_t *dm_segs, + int nseg, int error)); +static void aic_start __P((struct aic_softc *aic)); +static void aic_select __P((struct aic_softc *aic)); +static void aic_selected __P((struct aic_softc *aic)); +static void aic_reselected __P((struct aic_softc *aic)); +static void aic_cmd __P((struct aic_softc *aic)); +static void aic_msgin __P((struct aic_softc *aic)); +static void aic_handle_msgin __P((struct aic_softc *aic)); +static void aic_sched_msgout __P((struct aic_softc *aic, u_int8_t msg)); +static void aic_msgout __P((struct aic_softc *aic)); +static void aic_datain __P((struct aic_softc *aic)); +static void aic_dataout __P((struct aic_softc *aic)); +static void aic_done __P((struct aic_softc *aic, struct aic_scb *scb)); +static void aic_poll __P((struct cam_sim *sim)); +static void aic_timeout __P((void *arg)); +static void aic_scsi_reset __P((struct aic_softc *aic)); +static void aic_chip_reset __P((struct aic_softc *aic)); +static void aic_reset __P((struct aic_softc *aic, int initiate_reset)); + +static struct aic_scb *free_scbs; + +static struct aic_scb * +aic_get_scb(struct aic_softc *aic) +{ + struct aic_scb *scb; + int s = splcam(); + if ((scb = free_scbs) != NULL) + free_scbs = (struct aic_scb *)free_scbs->ccb; + splx(s); + return (scb); +} + +static void +aic_free_scb(struct aic_softc *aic, struct aic_scb *scb) +{ + int s = splcam(); + 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; + scb->ccb = (union ccb *)free_scbs; + free_scbs = scb; + splx(s); +} + +static void +aic_action(struct cam_sim *sim, union ccb *ccb) +{ + struct aic_softc *aic; + int s; + + CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("aic_action\n")); + + aic = (struct aic_softc *)cam_sim_softc(sim); + + 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) { + s = splcam(); + aic->flags |= AIC_RESOURCE_SHORTAGE; + splx(s); + 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_SCATTER_VALID) || + (ccb->ccb_h.flags & CAM_DATA_PHYS)) { + 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; + struct aic_tinfo *ti; + + cts = &ccb->cts; + ti = &aic->tinfo[ccb->ccb_h.target_id]; + + s = splcam(); + + if ((cts->valid & CCB_TRANS_DISC_VALID) != 0 && + (aic->flags & AIC_DISC_ENABLE) != 0) { + if ((cts->flags & CCB_TRANS_DISC_ENB) != 0) + ti->flags |= TINFO_DISC_ENB; + else + ti->flags &= ~TINFO_DISC_ENB; + } + + if ((cts->valid & CCB_TRANS_TQ_VALID) != 0) { + if ((cts->flags & CCB_TRANS_TAG_ENB) != 0) + ti->flags |= TINFO_TAG_ENB; + else + ti->flags &= ~TINFO_TAG_ENB; + } + + if ((cts->valid & CCB_TRANS_SYNC_RATE_VALID) != 0) { + ti->goal.period = cts->sync_period; + if (ti->goal.period != ti->current.period) + ti->flags |= TINFO_SDTR_NEGO; + } + + if ((cts->valid & CCB_TRANS_SYNC_OFFSET_VALID) != 0) { + ti->goal.offset = cts->sync_offset; + if (ti->goal.offset != ti->current.offset) + ti->flags |= TINFO_SDTR_NEGO; + } + + splx(s); + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_GET_TRAN_SETTINGS: + { + struct ccb_trans_settings *cts; + struct aic_tinfo *ti; + + cts = &ccb->cts; + ti = &aic->tinfo[ccb->ccb_h.target_id]; + + s = splcam(); + + cts->flags &= ~(CCB_TRANS_DISC_ENB|CCB_TRANS_TAG_ENB); + if ((ti->flags & TINFO_DISC_ENB) != 0) + cts->flags |= CCB_TRANS_DISC_ENB; + if ((ti->flags & TINFO_TAG_ENB) != 0) + cts->flags |= CCB_TRANS_TAG_ENB; + + if ((cts->flags & CCB_TRANS_CURRENT_SETTINGS) != 0) { + cts->sync_period = ti->current.period; + cts->sync_offset = ti->current.offset; + } else { + cts->sync_period = ti->user.period; + cts->sync_offset = ti->user.offset; + } + cts->bus_width = MSG_EXT_WDTR_BUS_8_BIT; + + splx(s); + + cts->valid = CCB_TRANS_SYNC_RATE_VALID + | CCB_TRANS_SYNC_OFFSET_VALID + | CCB_TRANS_BUS_WIDTH_VALID + | CCB_TRANS_DISC_VALID + | CCB_TRANS_TQ_VALID; + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_CALC_GEOMETRY: + { + struct ccb_calc_geometry *ccg; + u_int32_t size_mb; + u_int32_t secs_per_cylinder; + int extended = 0; + + ccg = &ccb->ccg; + size_mb = ccg->volume_size + / ((1024L * 1024L) / ccg->block_size); + + if (size_mb >= 1024 && extended) { + ccg->heads = 255; + ccg->secs_per_track = 63; + } else { + ccg->heads = 64; + ccg->secs_per_track = 32; + } + secs_per_cylinder = ccg->heads * ccg->secs_per_track; + ccg->cylinders = ccg->volume_size / secs_per_cylinder; + ccb->ccb_h.status = CAM_REQ_CMP; + 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->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; + int s; + + s = splcam(); + + if (ccb->ccb_h.status != CAM_REQ_INPROG) { + splx(s); + 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); + + ccb->ccb_h.timeout_ch = timeout(aic_timeout, (caddr_t)scb, + (ccb->ccb_h.timeout * hz) / 1000); + + aic_start(aic); + splx(s); +} + +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; + } + } + + aic_outb(aic, SIMODE0, ENSELDI); + aic_outb(aic, SIMODE1, ENSCSIRST); + aic_outb(aic, SCSISEQ, ENRESELI); +} + +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); +} + +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; + if ((ti->flags & TINFO_SDTR_NEGO) != 0) + aic->msg_outq |= AIC_MSG_SDTR; + } + if ((aic->msg_outq & AIC_MSG_TAG_Q) == 0) + ti->lubusy |= 1 << scb->lun; + + 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); +} + +static void +aic_reselected(struct aic_softc *aic) +{ + u_int8_t selid; + struct aic_tinfo *ti; + + CAM_DEBUG_PRINT(CAM_DEBUG_TRACE, ("aic_reselected\n")); + + 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) & SCSI_ID_MASK; + if (selid & (selid - 1)) { + ; + } + + aic->state = AIC_RESELECTED; + aic->target = ffs(selid) - 1; + aic->lun = -1; + ti = &aic->tinfo[aic->target]; + + 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, ti->scsirate); +} + +static __inline int +aic_spiordy(struct aic_softc *aic) +{ + int spincount = 100; + u_int8_t spiordy = 0; + + while (spincount-- && !(aic_inb(aic, DMASTAT) & INTSTAT) && + !(spiordy = aic_inb(aic, SSTAT0) & SPIORDY)) + ; + return (spiordy); +} + +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 (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; + } + 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) + msglen = 2; + else + msglen = 1; + if (aic->msg_len == msglen) { + aic_handle_msgin(aic); + aic->msg_len = 0; + } + (void) aic_inb(aic, SCSIDAT); + } while (aic_spiordy(aic)); + + aic_outb(aic, SXFRCTL0, CHEN); + aic_outb(aic, SIMODE1, ENSCSIRST|ENBUSFREE|ENREQINIT); +} + +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; + + if (aic->state == AIC_RESELECTED) { + int tag; + + ti = &aic->tinfo[aic->target]; + + if (MSG_ISIDENTIFY(aic->msg_buf[0])) { + aic->lun = aic->msg_buf[0] & MSG_IDENTIFY_LUNMASK; + if (ti->flags & TINFO_TAG_ENB) + return; + else + tag = -1; + } else if (aic->msg_buf[0] >= MSG_SIMPLE_Q_TAG && + aic->msg_buf[0] <= MSG_ORDERED_Q_TAG) { + tag = aic->msg_buf[1]; + } else { + aic_sched_msgout(aic, MSG_MESSAGE_REJECT); + return; + } + + TAILQ_FOREACH(ccb_h, &aic->nexus_ccbs, sim_links.tqe) { + scb = (struct aic_scb *)ccb_h->ccb_scb_ptr; + if (ccb_h->target_id == aic->target && + ccb_h->target_lun == aic->lun && + (tag == -1 || scb->tag == tag)) + break; + } + + if (!ccb_h) { + aic_sched_msgout(aic, MSG_ABORT); /* MSG_ABORT_TAG?*/ + return; + } + + TAILQ_REMOVE(&aic->nexus_ccbs, ccb_h, sim_links.tqe); + aic->nexus = scb; + scb->flags &= ~SCB_DISCONNECTED; + aic->state = AIC_HASNEXUS; + 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; + ccb_h->status &= ~CAM_STATUS_MASK; + if ((scb->flags & SCB_SENSE) != 0) { + 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) { + 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)) { + 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]); + 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); + neg.sync_period = ti->goal.period = ti->current.period; + neg.sync_offset = ti->goal.offset = ti->current.offset; + neg.valid = CCB_TRANS_SYNC_RATE_VALID + | CCB_TRANS_SYNC_OFFSET_VALID; + 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_HEAD(&aic->nexus_ccbs, ccb_h, sim_links.tqe); + scb->flags |= SCB_DISCONNECTED; + ti = &aic->tinfo[scb->target]; + aic->flags |= AIC_BUSFREE_OK; + 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; + 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); + neg.sync_period = ti->current.period; + neg.sync_offset = ti->current.offset; + neg.valid = CCB_TRANS_SYNC_RATE_VALID + | CCB_TRANS_SYNC_OFFSET_VALID; + 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; + } +} + +static 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); +} + +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 (aic->prev_phase == PH_MSGOUT) + aic->msg_outq = aic->msg_sent; + + do { + int q = aic->msg_outq; + if (msgidx > 0 && msgidx == aic->msg_len) { + q &= -q; + aic->msg_sent |= q; + aic->msg_outq ^= q; + q = aic->msg_outq; + msgidx = 0; + } + if (msgidx == 0) { + 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: + 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 ((q & (q - 1)) == 0 && msgidx == aic->msg_len - 1) + aic_outb(aic, CLRSINT1, CLRATNO); + aic_outb(aic, SCSIDAT, aic->msg_buf[msgidx++]); + } while (aic_spiordy(aic)); + + aic_outb(aic, SXFRCTL0, CHEN); + aic_outb(aic, SIMODE1, ENSCSIRST|ENBUSFREE|ENREQINIT); +} + +static void +aic_datain(struct aic_softc *aic) +{ + struct aic_scb *scb = aic->nexus; + u_int8_t dmastat; + 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); + + while (scb->data_len > 0) { + for (;;) { + dmastat = aic_inb(aic, DMASTAT); + if (dmastat & (INTSTAT|DFIFOFULL)) + break; + } + if (dmastat & DFIFOFULL) { + n = FIFOSIZE; + } else { + while (!(aic_inb(aic, SSTAT2) & SEMPTY)) + ; + n = aic_inb(aic, FIFOSTAT); + } + n = imin(scb->data_len, n); + if (n >= 12 && (aic->flags & AIC_DWIO_ENABLE)) { + aic_outb(aic, DMACNTRL0, ENDMA|DWORDPIO); + aic_insl(aic, DMADATALONG, scb->data_ptr, n >> 2); + scb->data_ptr += n & ~3; + scb->data_len -= n & ~3; + n &= 3; + } + if (n >= 8) { + aic_outb(aic, DMACNTRL0, ENDMA); + 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; + } + + if (dmastat & INTSTAT) + break; + } + + aic_outb(aic, SXFRCTL0, CHEN); + aic_outb(aic, SIMODE1, ENSCSIRST|ENBUSFREE|ENREQINIT); +} + +static void +aic_dataout(struct aic_softc *aic) +{ + struct aic_scb *scb = aic->nexus; + u_int8_t dmastat; + 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); + + while (scb->data_len > 0) { + for (;;) { + dmastat = aic_inb(aic, DMASTAT); + if (dmastat & (INTSTAT|DFIFOEMP)) + break; + } + if (dmastat & INTSTAT) + break; + n = imin(scb->data_len, FIFOSIZE); + if (n >= 12 && (aic->flags & AIC_DWIO_ENABLE)) { + aic_outb(aic, DMACNTRL0, ENDMA|WRITE|DWORDPIO); + aic_outsl(aic, DMADATALONG, scb->data_ptr, n >> 2); + scb->data_ptr += n & ~3; + scb->data_len -= n & ~3; + n &= 3; + } + if (n >= 8) { + aic_outb(aic, DMACNTRL0, ENDMA|WRITE); + aic_outsl(aic, DMADATA, scb->data_ptr, n >> 2); + 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; + } + } + + for (;;) { + dmastat = aic_inb(aic, DMASTAT); + if (dmastat & INTSTAT) + break; + if ((dmastat & DFIFOEMP) && (aic_inb(aic, SSTAT2) & SEMPTY)) + break; + } + + aic_outb(aic, SXFRCTL0, CHEN); + aic_outb(aic, SIMODE1, ENSCSIRST|ENBUSFREE|ENREQINIT); +} + +static void +aic_cmd(struct aic_softc *aic) +{ + struct aic_scb *scb = aic->nexus; + struct scsi_request_sense sense_cmd; + + if (scb->flags & SCB_SENSE) { + sense_cmd.opcode = REQUEST_SENSE; + sense_cmd.byte2 = scb->lun << 2; + 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, 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); +} + +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)); + + untimeout(aic_timeout, (caddr_t)scb, ccb->ccb_h.timeout_ch); + + 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); + + 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 { + ccb_h->timeout_ch = + timeout(aic_timeout, (caddr_t)pending_scb, + (ccb_h->timeout * hz) / 1000); + 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 { + ccb_h->timeout_ch = + timeout(aic_timeout, (caddr_t)nexus_scb, + (ccb_h->timeout * hz) / 1000); + 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->state = AIC_IDLE; + } + aic_free_scb(aic, scb); + xpt_done(ccb); +} + +static void +aic_poll(struct cam_sim *sim) +{ + aic_intr(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; + int s; + + 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("\n"); + + s = splcam(); + + if ((scb->flags & SCB_ACTIVE) == 0) { + splx(s); + 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; + + 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) { + untimeout(aic_timeout, (caddr_t)ccb_h->ccb_scb_ptr, + ccb_h->timeout_ch); + } + + TAILQ_FOREACH(ccb_h, &aic->nexus_ccbs, sim_links.tqe) { + untimeout(aic_timeout, (caddr_t)ccb_h->ccb_scb_ptr, + ccb_h->timeout_ch); + } + + scb->flags |= SCB_DEVICE_RESET; + ccb->ccb_h.timeout_ch = + timeout(aic_timeout, (caddr_t)scb, 5 * hz); + aic_sched_msgout(aic, MSG_BUS_DEV_RESET); + } else { + if (aic->nexus == scb) { + ccb->ccb_h.status &= ~CAM_STATUS_MASK; + ccb->ccb_h.status |= CAM_CMD_TIMEOUT; + aic_done(aic, scb); + } + aic_reset(aic, /*initiate_reset*/TRUE); + } + + splx(s); +} + +void +aic_intr(void *arg) +{ + struct aic_softc *aic = (struct aic_softc *)arg; + 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) { + 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_HASNEXUS) { + 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 & 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; + } + + if ((sstat1 & BUSFREE) != 0) { + aic_outb(aic, SCSISEQ, 0); + aic_outb(aic, CLRSINT0, sstat0); + aic_outb(aic, CLRSINT1, sstat1); + aic_outb(aic, SIMODE0, ENSELDI); + aic_outb(aic, SIMODE1, ENSCSIRST); + aic_outb(aic, SCSISEQ, ENRESELI); + 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_SENSE) { + aic->flags &= ~AIC_BUSFREE_OK; + 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; + } + + printf("aic_intr: unexpected intr sstat0 %x sstat1 %x\n", + sstat0, sstat1); + aic_outb(aic, DMACNTRL0, INTEN); +} + +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); +} + +static void +aic_scsi_reset(struct aic_softc *aic) +{ + aic_outb(aic, SCSISEQ, SCSIRSTO); + DELAY(500); + aic_outb(aic, SCSISEQ, 0); + DELAY(50); +} + +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_STATUS_MASK; + 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_STATUS_MASK; + 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_STATUS_MASK; + ccb_h->status |= CAM_SCSI_BUS_RESET; + aic_done(aic, aic->nexus); + } + + aic->state = AIC_IDLE; + + aic_outb(aic, DMACNTRL0, INTEN); +} + +static void +aic_init(struct aic_softc *aic) +{ + struct aic_scb *scb; + struct aic_tinfo *ti; + u_int8_t porta, portb; + int i; + + TAILQ_INIT(&aic->pending_ccbs); + TAILQ_INIT(&aic->nexus_ccbs); + aic->nexus = NULL; + aic->state = AIC_IDLE; + aic->prev_phase = -1; + aic->flags = 0; + + aic_chip_reset(aic); + aic_scsi_reset(aic); + + 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; + if (aic_inb(aic, REV)) + aic->flags |= AIC_DWIO_ENABLE; + + free_scbs = NULL; + for (i = 255; i >= 0; i--) { + scb = &aic->scbs[i]; + scb->tag = i; + 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 = 0; + ti->user.period = AIC_SYNC_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, + aic->unit, 2, 256, devq); + if (aic->sim == NULL) { + cam_simq_free(devq); + return (ENOMEM); + } + + if (xpt_bus_register(aic->sim, 0) != CAM_SUCCESS) { + cam_sim_free(aic->sim, /*free_devq*/TRUE); + 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); + return (ENXIO); + } + + aic_init(aic); + return (0); +} + +int +aic_detach(struct aic_softc *aic) +{ + 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); + return (0); +} |