freebsd-skq/sys/i386/isa/bs/bs.c
2002-03-20 07:51:46 +00:00

1674 lines
36 KiB
C

/* $NecBSD: bs.c,v 1.1 1997/07/18 09:18:59 kmatsuda Exp $ */
/* $NetBSD$ */
/* $FreeBSD$ */
/*
* [NetBSD for NEC PC98 series]
* Copyright (c) 1994, 1995, 1996 NetBSD/pc98 porting staff.
* 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.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
*/
/*
* Copyright (c) 1994, 1995, 1996 Naofumi HONDA. All rights reserved.
*/
#ifdef __NetBSD__
#include <i386/Cbus/dev/bs/bsif.h>
#endif
#ifdef __FreeBSD__
#include <i386/isa/bs/bsif.h>
#endif
#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_all.h>
#include <cam/scsi/scsi_message.h>
/*****************************************************************
* Inline phase funcs
*****************************************************************/
/* static inline declare */
static BS_INLINE struct targ_info *bs_reselect(struct bs_softc *);
static BS_INLINE void bs_sat_continue(struct bs_softc *, struct targ_info *, struct bsccb *);
static BS_INLINE struct targ_info *bs_selected(struct bs_softc *, struct targ_info *, struct bsccb *);
static BS_INLINE u_int8_t bs_read_1byte(struct bs_softc *);
static BS_INLINE void bs_write_1byte(struct bs_softc *, u_int8_t);
static BS_INLINE void bs_commandout(struct bs_softc *, struct targ_info *, struct bsccb *);
static BS_INLINE void bs_status_check(struct bs_softc *, struct targ_info *);
static BS_INLINE void bs_msgin(struct bs_softc *, struct targ_info *);
static BS_INLINE void bs_msgout(struct bs_softc *, struct targ_info *, struct bsccb *);
static BS_INLINE void bs_disconnect_phase(struct bs_softc *, struct targ_info *, struct bsccb *);
static void bs_phase_error(struct targ_info *, struct bsccb *);
static int bs_scsi_cmd_poll_internal(struct targ_info *);
static int bs_xfer(struct bs_softc *, char *, int);
static void bs_io_xfer(struct targ_info *);
static void bs_quick_abort(struct targ_info *, u_int);
static void bs_msgin_error(struct targ_info *, u_int);
static void bs_msgin_ext(struct targ_info *);
static void bs_msg_reject(struct targ_info *);
static void bshoststart(struct bs_softc *, struct targ_info *);
/*****************************************************************
* SIM interface
*****************************************************************/
void
bs_scsi_cmd(struct cam_sim *sim, union ccb *ccb)
{
struct bs_softc *bsc = (struct bs_softc *) cam_sim_softc(sim);
int s, target = (u_int) (ccb->ccb_h.target_id);
struct targ_info *ti;
struct bsccb *cb;
switch (ccb->ccb_h.func_code) {
case XPT_SCSI_IO: /* Execute the requested I/O operation */
ti = bsc->sc_ti[target];
if ((cb = bs_get_ccb()) == NULL) {
ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
xpt_done(ccb);
return;
}
/* make up ccb! */
cb->ccb = ccb;
cb->lun = ccb->ccb_h.target_lun;
cb->cmd = ccb->csio.cdb_io.cdb_bytes;
cb->cmdlen = (int) ccb->csio.cdb_len;
cb->data = ccb->csio.data_ptr;
cb->datalen = (int) ccb->csio.dxfer_len;
cb->rcnt = 0;
cb->msgoutlen = 0;
cb->bsccb_flags = 0;
bs_targ_flags(ti, cb);
cb->tcmax = 0;/*(xs->timeout >> 10); default HN2*/
if (cb->tcmax < BS_DEFAULT_TIMEOUT_SECOND)
cb->tcmax = BS_DEFAULT_TIMEOUT_SECOND;
s = splcam();
TAILQ_INSERT_TAIL(&ti->ti_ctab, cb, ccb_chain);
if (ti->ti_phase == FREE) {
if (ti->ti_state == BS_TARG_START)
bs_start_syncmsg(ti, NULL, BS_SYNCMSG_ASSERT);
bscmdstart(ti, BSCMDSTART);
}
splx(s);
break;
case XPT_RESET_DEV: /* Bus Device Reset the specified SCSI device */
case XPT_EN_LUN: /* Enable LUN as a target */
case XPT_TARGET_IO: /* Execute target I/O request */
case XPT_ACCEPT_TARGET_IO: /* Accept Host Target Mode CDB */
case XPT_CONT_TARGET_IO: /* Continue Host Target I/O Connection*/
case XPT_ABORT: /* Abort the specified CCB */
/* XXX Implement */
ccb->ccb_h.status = CAM_REQ_INVALID;
xpt_done(ccb);
break;
case XPT_SET_TRAN_SETTINGS:
/* XXX Implement */
ccb->ccb_h.status = CAM_FUNC_NOTAVAIL;
xpt_done(ccb);
break;
case XPT_GET_TRAN_SETTINGS: {
struct ccb_trans_settings *cts;
struct targ_info *ti;
/*int s;*/
cts = &ccb->cts;
ti = bsc->sc_ti[ccb->ccb_h.target_id];
/*s = splcam();*/
if ((cts->flags & CCB_TRANS_USER_SETTINGS) != 0) {
if (ti->ti_cfgflags & BS_SCSI_DISC)
cts->flags = CCB_TRANS_DISC_ENB;
else
cts->flags = 0;
if (ti->ti_cfgflags & BS_SCSI_QTAG)
cts->flags |= CCB_TRANS_TAG_ENB;
cts->sync_period = ti->ti_syncnow.period;
cts->sync_offset = ti->ti_syncnow.offset;
cts->bus_width = 0;/*HN2*/
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;
} else
ccb->ccb_h.status = CAM_FUNC_NOTAVAIL;
/*splx(s);*/
xpt_done(ccb);
break;
}
case XPT_CALC_GEOMETRY: { /* not yet HN2 */
struct ccb_calc_geometry *ccg;
u_int32_t size_mb;
u_int32_t secs_per_cylinder;
ccg = &ccb->ccg;
size_mb = ccg->volume_size
/ ((1024L * 1024L) / ccg->block_size);
ccg->heads = 8;
ccg->secs_per_track = 34;
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 */
bshw_chip_reset(bsc); /* XXX need perfect RESET? */
ccb->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
case XPT_TERM_IO: /* Terminate the I/O process */
/* XXX Implement */
ccb->ccb_h.status = CAM_REQ_INVALID;
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;
cpi->target_sprt = 0;
cpi->hba_misc = 0;
cpi->hba_eng_cnt = 0;
cpi->max_target = NTARGETS - 1;
cpi->max_lun = 7;
cpi->initiator_id = bsc->sc_hostid;
cpi->bus_id = cam_sim_bus(sim);
cpi->base_transfer_speed = 3300;
strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
strncpy(cpi->hba_vid, "NEC", 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:
/*printf("bs: non support func_code = %d ", ccb->ccb_h.func_code);*/
ccb->ccb_h.status = CAM_REQ_INVALID;
xpt_done(ccb);
break;
}
}
/**************************************************
* ### NEXUS START and TERMINATE ###
**************************************************/
/*
* FLAGS : BSCMDRESTART restart in case of error.
*/
int
bscmdstart(ti, flags)
struct targ_info *ti;
int flags;
{
struct bsccb *cb;
struct bs_softc *bsc = ti->ti_bsc;
if ((cb = TAILQ_FIRST(&ti->ti_ctab)) == NULL)
{
if (bsc->sc_nexus == NULL)
bshoststart(bsc, NULL);
return 0;
}
ti->ti_lun = cb->lun;
ti->ti_error = 0;
ti->ti_scsp.data = cb->data;
ti->ti_scsp.datalen = cb->datalen;
ti->ti_scsp.seglen = 0;
if (cb->rcnt)
cb->bsccb_flags &= ~(BSSAT | BSLINK);
ti->ti_flags &= ~BSCFLAGSMASK;
ti->ti_flags |= cb->bsccb_flags & BSCFLAGSMASK;
cb->tc = cb->tcmax;
/* GO GO */
if (ti->ti_phase == FREE)
{
if (bsc->sc_nexus == NULL)
bshoststart(bsc, ti);
else
{
if (flags & BSCMDRESTART)
bs_hostque_head(bsc, ti);
else
bs_hostque_tail(bsc, ti);
BS_SETUP_PHASE(HOSTQUEUE)
}
}
else if (bsc->sc_nexus == NULL)
bshoststart(bsc, NULL);
return 1;
}
struct bsccb *
bscmddone(ti)
struct targ_info *ti;
{
struct bs_softc *bsc = ti->ti_bsc;
struct bsccb *cb = TAILQ_FIRST(&ti->ti_ctab);
union ccb *ccb;
int error;
if (ti->ti_state == BS_TARG_SYNCH)
{
if (bs_analyze_syncmsg(ti, cb))
return cb;
}
if (bsc->sc_p.datalen != 0)
ti->ti_error |= BSDMAABNORMAL;
cb->error = ti->ti_error;
do
{
ccb = cb->ccb;
error = CAM_REQ_CMP;
if (cb->bsccb_flags & (BSITSDONE | BSSENSECCB | BSCASTAT))
{
if (cb->bsccb_flags & BSSENSECCB)
{
cb->error &= ~BSDMAABNORMAL;
if (cb->error == 0)
ti->ti_flags |= BSCASTAT;
ti->ti_flags |= BSERROROK;
}
else if (cb->bsccb_flags & BSCASTAT)
{
if (ti->ti_flags & BSCASTAT)
{
ti->ti_flags &= ~BSCASTAT;
error = CAM_AUTOSNS_VALID|CAM_SCSI_STATUS_ERROR;
if (ccb)
ccb->csio.sense_data = ti->sense;/* XXX may not be csio.... */
}
else
error = CAM_AUTOSENSE_FAIL;
ti->ti_flags |= BSERROROK;
} else
bs_panic(bsc, "internal error");
}
while (cb->error)
{
if (ti->ti_flags & BSERROROK)
break;
if (cb->rcnt >= bsc->sc_retry || (cb->error & BSFATALIO))
{
if (cb->error & (BSSELTIMEOUT | BSTIMEOUT))
error = CAM_CMD_TIMEOUT;
else if (cb->error & BSTARGETBUSY)
error = CAM_SCSI_STATUS_ERROR;
else
error = CAM_REQ_CMP_ERR;
break;
}
if (cb->error & BSREQSENSE)
{
/* must clear the target's sense state */
cb->rcnt++;
cb->bsccb_flags |= (BSITSDONE | BSCASTAT);
cb->error &= ~BSREQSENSE;
return bs_request_sense(ti);
}
/* XXX: compat with upper driver */
if ((cb->error & BSDMAABNORMAL) &&
BSHW_CMD_CHECK(cb, BSERROROK))
{
cb->error &= ~BSDMAABNORMAL;
continue;
}
if (/*(xs && xs->bp) || can't know whether bufferd i/o or not */
(cb->error & BSSELTIMEOUT) == 0)
bs_debug_print(bsc, ti);
cb->rcnt++;
return cb;
}
#ifdef BS_DIAG
cb->bsccb_flags |= BSITSDONE;
#endif /* BS_DIAG */
if (bsc->sc_poll)
{
bsc->sc_flags |= BSJOBDONE;
if (bsc->sc_outccb == cb)
bsc->sc_flags |= BSPOLLDONE;
}
TAILQ_REMOVE(&ti->ti_ctab, cb, ccb_chain);
if (ccb)
{
ccb->ccb_h.status = error;
ccb->csio.scsi_status = ti->ti_status;/*XXX*/
xpt_done(ccb);
}
bs_free_ccb(cb);
cb = TAILQ_FIRST(&ti->ti_ctab);
}
while (cb != NULL && (cb->bsccb_flags & BSITSDONE) != 0);
/* complete */
return NULL;
}
/**************************************************
* ### PHASE FUNCTIONS ###
**************************************************/
/**************************************************
* <SELECTION PHASE>
**************************************************/
static void
bshoststart(bsc, ti)
struct bs_softc *bsc;
struct targ_info *ti;
{
struct bsccb *cb;
int s;
if (bsc->sc_flags & BSINACTIVE)
return;
again:
if (ti == NULL)
{
if ((ti = TAILQ_FIRST(&bsc->sc_sttab)) == NULL)
return;
bs_hostque_delete(bsc, ti);
}
if ((cb = TAILQ_FIRST(&ti->ti_ctab)) == NULL)
{
bs_printf(ti, "bshoststart", "Warning: No ccb");
BS_SETUP_PHASE(FREE);
ti = NULL;
goto again;
}
#ifdef BS_DIAG
if (cb->bsccb_flags & BSITSDONE)
bs_panic(bsc, "bshoststart: already done");
if (bsc->sc_nexus || (ti->ti_flags & BSNEXUS))
{
char *s = ((ti->ti_flags & BSNEXUS) ?
"nexus already established" : "scsi board busy");
bs_debug_print(bsc, ti);
bs_printf(ti, "bshoststart", s);
}
#endif /* BS_DIAG */
#ifdef BS_STATICS
bs_statics[ti->ti_id].select++;
#endif /* BS_STATICS */
if (ti->ti_cfgflags & BS_SCSI_WAIT)
{
struct targ_info *tmpti;
TAILQ_FOREACH(tmpti, &bsc->sc_titab, ti_tchain)
if (tmpti->ti_phase >= DISCONNECTED)
goto retry;
}
/* start selection */
ti->ti_status = ST_UNK;
if (bs_check_sat(ti))
{
if ((bshw_get_auxstat(bsc) & STR_BUSY) == 0)
{
BS_LOAD_SDP
bshw_set_dst_id(bsc, ti->ti_id, ti->ti_lun);
bshw_setup_ctrl_reg(bsc, ti->ti_cfgflags);
bshw_cmd_pass(bsc, 0);
bshw_set_sync_reg(bsc, ti->ti_sync);
bshw_issue_satcmd(bsc, cb, bs_check_link(ti, cb));
if (bs_check_smit(ti) || bsc->sc_p.datalen <= 0)
bshw_set_count(bsc, 0);
else
bs_dma_xfer(ti, BSHW_CMD_CHECK(cb, BSREAD));
s = splhigh();
if ((bshw_get_auxstat(bsc) & STR_BUSY) == 0)
{
/* XXX:
* Reload a lun again here.
*/
bshw_set_lun(bsc, ti->ti_lun);
bshw_start_sat(bsc, bs_check_disc(ti));
if ((bshw_get_auxstat(bsc) & STR_LCI) == 0)
{
splx(s);
BS_HOST_START
BS_SELECTION_START
BS_SETUP_PHASE(SATSEL);
ti->ti_omsgoutlen = 0;
ti->ti_msgout = bs_identify_msg(ti);
#ifdef BS_DIAG
ti->ti_flags |= BSNEXUS;
#endif /* BS_DIAG */
#ifdef BS_STATICS
bs_statics[ti->ti_id].select_win++;
#endif /* BS_STATICS */
return;
}
}
splx(s);
if (bs_check_smit(ti) == 0)
bshw_dmaabort(bsc, ti);
#ifdef BS_STATICS
bs_statics[ti->ti_id].select_miss_in_assert++;
#endif /* BS_STATICS */
}
}
else
{
s = splhigh();
if ((bshw_get_auxstat(bsc) & STR_BUSY) == 0)
{
bshw_set_dst_id(bsc, ti->ti_id, ti->ti_lun);
bshw_setup_ctrl_reg(bsc, ti->ti_cfgflags);
bshw_set_sync_reg(bsc, ti->ti_sync);
bshw_assert_select(bsc);
if ((bshw_get_auxstat(bsc) & STR_LCI) == 0)
{
splx(s);
BS_HOST_START
BS_SELECTION_START
BS_SETUP_PHASE(SELECTASSERT);
#ifdef BS_STATICS
bs_statics[ti->ti_id].select_win++;
#endif /* BS_STATICS */
return;
}
#ifdef BS_STATICS
bs_statics[ti->ti_id].select_miss_in_assert++;
#endif /* BS_STATICS */
}
splx(s);
}
/* RETRY LATER */
retry:
#ifdef BS_STATICS
bs_statics[ti->ti_id].select_miss++;
#endif /* BS_STATICS */
bs_hostque_head(bsc, ti);
BS_SETUP_PHASE(HOSTQUEUE)
}
static BS_INLINE struct targ_info *
bs_selected(bsc, ti, cb)
struct bs_softc *bsc;
struct targ_info *ti;
struct bsccb *cb;
{
if (bsc->sc_busstat != BSR_SELECTED)
{
bs_phase_error(ti, cb);
return NULL;
}
#ifdef BS_DIAG
if (bsc->sc_selwait != ti)
panic("%s selection internal error\n", bsc->sc_dvname);
ti->ti_flags |= BSNEXUS;
#endif /* BS_DIAG */
/* clear select wait state */
BS_SETUP_PHASE(SELECTED);
BS_SELECTION_TERMINATE;
BS_LOAD_SDP
return ti;
}
/**************************************************
* <RESELECTION>
**************************************************/
static BS_INLINE struct targ_info *
bs_reselect(bsc)
struct bs_softc *bsc;
{
u_int target;
struct targ_info *ti;
/* check collision */
if ((ti = bsc->sc_selwait) != NULL)
{
if (ti->ti_phase == SATSEL)
{
#ifdef BS_DIAG
ti->ti_flags &= ~BSNEXUS;
#endif /* BS_DIAG */
ti->ti_msgout = 0;
if (bs_check_smit(ti) == 0)
bshw_dmaabort(bsc, ti);
}
bs_hostque_head(bsc, ti);
BS_SELECTION_TERMINATE
BS_SETUP_PHASE(HOSTQUEUE)
#ifdef BS_STATICS
bs_statics[ti->ti_id].select_miss_by_reselect++;
bs_statics[ti->ti_id].select_miss++;
#endif /* BS_STATICS */
}
/* who are you ? */
target = bshw_get_src_id(bsc);
if ((ti = bsc->sc_ti[target]) == NULL)
{
bs_debug_print_all(bsc);
printf("reselect: miss reselect. target(%d)\n", target);
bs_reset_nexus(bsc);
return NULL;
}
/* confirm nexus */
BS_HOST_START
bshw_setup_ctrl_reg(bsc, ti->ti_cfgflags);
if (TAILQ_FIRST(&ti->ti_ctab) == NULL || ti->ti_phase != DISCONNECTED)
{
bs_printf(ti, "reselect", "phase mismatch");
BS_SETUP_PHASE(UNDEF)
bs_force_abort(ti);
bs_hostque_delete(bsc, ti);
}
else
bsc->sc_dtgnum --;
/* recover host */
bshw_set_dst_id(bsc, ti->ti_id, ti->ti_lun);
bshw_set_sync_reg(bsc, ti->ti_sync);
BS_RESTORE_SDP
BS_SETUP_PHASE(RESELECTED)
#ifdef BS_STATICS
bs_statics[ti->ti_id].reselect++;
#endif /* BS_STATICS */
return ti;
}
static BS_INLINE void
bs_sat_continue(bsc, ti, cb)
struct bs_softc *bsc;
struct targ_info *ti;
struct bsccb *cb;
{
BS_SETUP_PHASE(SATRESEL);
bshw_set_dst_id(bsc, ti->ti_id, ti->ti_lun);
bshw_cmd_pass(bsc, 0x44);
bshw_set_sync_reg(bsc, ti->ti_sync);
bshw_issue_satcmd(bsc, cb, 0);
if (bs_check_smit(ti) || bsc->sc_p.datalen <= 0)
bshw_set_count(bsc, 0);
else
bs_dma_xfer(ti, BSHW_CMD_CHECK(cb, BSREAD));
bshw_set_lun(bsc, ti->ti_lun); /* XXX */
bshw_start_sat(bsc, 0);
}
/*************************************************
* <DATA PHASE>
*************************************************/
#define DR (STR_BSY | STR_DBR)
void
bs_poll_timeout(bsc, s)
struct bs_softc *bsc;
char *s;
{
struct targ_info *ti;
bs_printf(NULL, s, "timeout");
bsc->sc_flags |= BSRESET;
if ((ti = bsc->sc_nexus) && TAILQ_FIRST(&ti->ti_ctab))
ti->ti_error |= BSTIMEOUT;
}
static BS_INLINE u_int8_t
bs_read_1byte(bsc)
struct bs_softc *bsc;
{
register u_int wc;
bshw_start_sxfer(bsc);
for (wc = bsc->sc_wc; (bshw_get_auxstat(bsc) & DR) != DR && --wc; );
if (wc)
return bshw_read_data(bsc);
else
bs_poll_timeout(bsc, "read_1byte");
return 0;
}
static BS_INLINE void
bs_write_1byte(bsc, data)
struct bs_softc *bsc;
u_int8_t data;
{
register u_int wc;
bshw_start_sxfer(bsc);
for (wc = bsc->sc_wc; (bshw_get_auxstat(bsc) & DR) != DR && --wc; );
if (wc)
bshw_write_data(bsc, data);
else
bs_poll_timeout(bsc, "write_1byte");
}
static int
bs_xfer(bsc, data, len)
struct bs_softc *bsc;
char *data;
int len;
{
u_int8_t aux;
u_int count, wc;
bshw_set_count(bsc, len);
bshw_start_xfer(bsc);
for (count = 0, wc = bsc->sc_wc; count < len && --wc; )
{
if (((aux = bshw_get_auxstat(bsc)) & DR) == DR)
{
if (bsc->sc_busstat & BSHW_READ)
*(data++) = bshw_read_data(bsc);
else
bshw_write_data(bsc, *(data++));
count++;
wc = bsc->sc_wc;
}
if (aux & STR_INT)
break;
}
if (wc == 0)
bs_poll_timeout(bsc, "bs_xfer");
return count;
}
#undef DR
static void
bs_io_xfer(ti)
struct targ_info *ti;
{
struct bs_softc *bsc = ti->ti_bsc;
struct sc_p *sp = &bsc->sc_p;
u_int count, dummy;
/* switch dma trasnfr mode */
bshw_set_poll_trans(bsc, ti->ti_cfgflags);
sp->seglen = 0;
sp->bufp = NULL;
if (sp->datalen <= 0)
{
ti->ti_error |= BSDMAABNORMAL;
dummy = 0;
count = bs_xfer(bsc, (u_int8_t *) &dummy, 1);
}
else
count = bs_xfer(bsc, sp->data, sp->datalen);
sp->data += count;
sp->datalen -= count;
}
/************************************************
* <COMMAND PHASE>
************************************************/
static BS_INLINE void
bs_commandout(bsc, ti, cb)
struct bs_softc *bsc;
struct targ_info *ti;
struct bsccb *cb;
{
u_int8_t scsi_cmd[16];
int len;
BS_SETUP_PHASE(CMDPHASE);
if (bs_check_link(ti, cb))
{
bcopy(cb->cmd, scsi_cmd, cb->cmdlen);
scsi_cmd[cb->cmdlen - 1] |= 0x01;
len = bs_xfer(bsc, scsi_cmd, cb->cmdlen);
}
else
len = bs_xfer(bsc, cb->cmd, cb->cmdlen);
if (len != cb->cmdlen)
ti->ti_error |= BSCMDABNORMAL;
}
/************************************************
* <STATUS IN>
************************************************/
static BS_INLINE void
bs_status_check(bsc, ti)
struct bs_softc *bsc;
struct targ_info *ti;
{
if (ti->ti_status == ST_GOOD || ti->ti_status == ST_INTERGOOD)
return;
switch (ti->ti_status)
{
case ST_MET:
case ST_INTERMET:
case ST_CHKCOND:
ti->ti_error |= BSREQSENSE;
break;
case ST_BUSY:
ti->ti_error |= BSTARGETBUSY;
break;
default:
ti->ti_error |= BSSTATUSERROR;
break;
}
}
/************************************************
* <MSG IN>
************************************************/
#define MSGWAIT(cnt) { if (ti->ti_msginptr < (cnt)) return; }
static void
bs_quick_abort(ti, msg)
struct targ_info *ti;
u_int msg;
{
struct bsccb *cb;
if ((cb = TAILQ_FIRST(&ti->ti_ctab)) == NULL)
return;
cb->msgoutlen = 1;
cb->msgout[0] = msg;
cb->rcnt++;
ti->ti_error |= BSMSGERROR;
}
static void
bs_msgin_error(ti, count)
struct targ_info *ti;
u_int count;
{
int n;
MSGWAIT(count);
bs_printf(ti, "msgin", "illegal msg");
for (n = 0; n < ti->ti_msginptr; n ++)
printf("[0x%x] ", (u_int) ti->ti_msgin[n]);
printf("\n");
bs_quick_abort(ti, MSG_REJECT);
ti->ti_msginptr = 0;
}
static void
bs_msgin_ext(ti)
struct targ_info *ti;
{
struct bs_softc *bsc = ti->ti_bsc;
struct bsccb *cb = TAILQ_FIRST(&ti->ti_ctab);
int count;
u_int reqlen;
u_int32_t *ptr;
struct msgbase msg;
MSGWAIT(2);
reqlen = ti->ti_msgin[1];
if (reqlen == 0)
reqlen = 256;
if (ti->ti_msginptr >= MAXMSGLEN)
ti->ti_msginptr = 3; /* XXX */
MSGWAIT(reqlen + 2);
switch (MKMSG_EXTEND(ti->ti_msgin[1], ti->ti_msgin[2]))
{
case MKMSG_EXTEND(MSG_EXTEND_MDPLEN, MSG_EXTEND_MDPCODE):
ptr = (u_int32_t *)(&ti->ti_msgin[3]);
count = (int) htonl((long) (*ptr));
bsc->sc_p.seglen = ti->ti_scsp.seglen = 0;
if (bsc->sc_p.datalen - count >= 0 &&
bsc->sc_p.datalen - count <= cb->datalen)
{
bsc->sc_p.datalen -= count;
bsc->sc_p.data += count;
}
else
bs_msgin_error(ti, 7);
break;
case MKMSG_EXTEND(MSG_EXTEND_SYNCHLEN, MSG_EXTEND_SYNCHCODE):
ti->ti_syncnow.period = ti->ti_msgin[3];
ti->ti_syncnow.offset = ti->ti_msgin[4];
if (ti->ti_syncnow.offset == 0)
ti->ti_syncnow.period = 0;
if (ti->ti_syncnow.state != BS_SYNCMSG_ASSERT)
{
bs_start_syncmsg(ti, NULL, BS_SYNCMSG_REQUESTED);
bscmdstart(ti, BSCMDSTART);
}
else
BS_SETUP_SYNCSTATE(BS_SYNCMSG_ACCEPT)
break;
case MKMSG_EXTEND(MSG_EXTEND_WIDELEN, MSG_EXTEND_WIDECODE):
msg.msglen = MSG_EXTEND_WIDELEN + 2;
msg.msg[0] = MSG_EXTEND;
msg.msg[1] = MSG_EXTEND_WIDELEN;
msg.msg[2] = MSG_EXTEND_WIDECODE;
msg.msg[3] = 0;
msg.flag = 0;
bs_make_msg_ccb(ti, cb->lun, cb, &msg, 0);
break;
default:
bs_msgin_error(ti, 0);
return;
}
ti->ti_msginptr = 0;
return;
}
static void
bs_msg_reject(ti)
struct targ_info *ti;
{
struct bs_softc *bsc = ti->ti_bsc;
struct bsccb *cb = TAILQ_FIRST(&ti->ti_ctab);
char *s = "unexpected msg reject";
switch (ti->ti_ophase)
{
case CMDPHASE:
s = "cmd rejected";
cb->bsccb_flags &= ~BSLINK;
BS_SETUP_MSGPHASE(IOCOMPLETED);
break;
case MSGOUT:
if (ti->ti_msgout & 0x80)
{
s = "identify msg rejected";
cb->bsccb_flags &= ~BSDISC;
BS_SETUP_MSGPHASE(IOCOMPLETED);
}
else if (ti->ti_msgout == MSG_EXTEND)
{
switch (ti->ti_emsgout)
{
case MSG_EXTEND_SYNCHCODE:
BS_SETUP_SYNCSTATE(BS_SYNCMSG_REJECT);
return;
default:
break;
}
}
break;
default:
break;
}
bs_debug_print(bsc, ti);
bs_printf(ti, "msgin", s);
ti->ti_error |= BSMSGERROR;
}
static BS_INLINE void
bs_msgin(bsc, ti)
struct bs_softc *bsc;
struct targ_info *ti;
{
BS_SETUP_PHASE(MSGIN);
switch (ti->ti_msgin[0])
{
case MSG_SAVESP:
BS_SAVE_SDP
break;
case MSG_RESTORESP:
BS_RESTORE_SDP
bs_printf(ti, "msgin", "restore scsi pointer");
break;
case MSG_REJECT:
bs_msg_reject(ti);
break;
case 0xf:
break;
case MSG_I_ERROR:/* all I -> T : nothing to do*/
case MSG_ABORT:
case MSG_PARITY:
case MSG_RESET:
case 0xe:
bs_msgin_error(ti, 1);
goto resume;
case MSG_NOOP:
break;
case MSG_EXTEND:
bs_msgin_ext(ti);
goto resume;
case 0xd:
bs_msgin_error(ti, 2);
goto resume;
case MSG_DISCON:
BS_SETUP_MSGPHASE(DISCONNECTASSERT);
break;
case MSG_COMP:
BS_SETUP_MSGPHASE(IOCOMPLETED);
break;
case MSG_LCOMP:
case MSG_LCOMP_F:
bs_status_check(bsc, ti);
if (bscmddone(ti) == NULL)
{
if (bscmdstart(ti, BSCMDSTART) == 0)
{
bs_printf(ti, "msgin", "cmd line miss");
bs_force_abort(ti);
}
}
else
bscmdstart(ti, BSCMDRESTART);
#ifdef BS_STATICS
bs_linkcmd_count[ti->ti_id]++;
#endif /* BS_STATICS */
BS_LOAD_SDP
ti->ti_status = ST_UNK;
break;
default:
if (ti->ti_msgin[0] & 0x80)
{
if ((ti->ti_msgin[0] & 0x07) != ti->ti_lun)
{
ti->ti_lun = (ti->ti_msgin[0] & 0x07);
bshw_set_dst_id(bsc, ti->ti_id, ti->ti_lun);
bshw_set_sync_reg(bsc, ti->ti_sync);
bs_printf(ti, "msgin", "lun error");
bs_quick_abort(ti, MSG_ABORT);
}
break;
}
else if (ti->ti_msgin[0] < 0x20)
bs_msgin_error(ti, 1);
else if (ti->ti_msgin[0] < 0x30)
bs_msgin_error(ti, 2);
else
bs_msgin_error(ti, 1);
goto resume;
}
ti->ti_msginptr = 0;
resume:
return;
}
/************************************************
* <MSG OUT>
************************************************/
static BS_INLINE void
bs_msgout(bsc, ti, cb)
struct bs_softc *bsc;
struct targ_info *ti;
struct bsccb *cb;
{
u_int8_t msg[MAXMSGLEN + 1];
if (ti->ti_phase == MSGOUT)
{
if (cb->rcnt ++ < bsc->sc_retry)
cb->msgoutlen = ti->ti_omsgoutlen;
}
else
BS_SETUP_PHASE(MSGOUT);
if (ti->ti_ophase == SELECTED)
{
identify:
if (cb->msgoutlen == 0)
{
ti->ti_msgout = bs_identify_msg(ti);
ti->ti_omsgoutlen = 0;
bs_write_1byte(bsc, ti->ti_msgout);
}
else
{
if (cb->msgout[0] != MSG_RESET &&
cb->msgout[0] != MSG_ABORT)
{
msg[0] = bs_identify_msg(ti);
bcopy(cb->msgout, &msg[1], cb->msgoutlen);
bs_xfer(bsc, msg, cb->msgoutlen + 1);
}
else
bs_xfer(bsc, cb->msgout, cb->msgoutlen);
ti->ti_msgout = cb->msgout[0];
ti->ti_emsgout = cb->msgout[2];
ti->ti_omsgoutlen = cb->msgoutlen;
cb->msgoutlen = 0;
}
return;
}
if (ti->ti_ophase == SATSEL)
{
/* XXX:
* Maybe identify msg rejected due to
* a parity error in target side.
*/
bs_printf(ti, "msgout", "msg identify retry (SAT)");
goto identify;
}
if (cb->msgoutlen == 0)
{
ti->ti_msgout = MSG_REJECT;
ti->ti_omsgoutlen = 0;
bs_write_1byte(bsc, ti->ti_msgout);
}
else
{
ti->ti_msgout = cb->msgout[0];
ti->ti_emsgout = cb->msgout[2];
ti->ti_omsgoutlen = cb->msgoutlen;
bs_xfer(bsc, cb->msgout, cb->msgoutlen);
cb->msgoutlen = 0;
}
}
/************************************************
* <DISCONNECT>
************************************************/
static BS_INLINE void
bs_disconnect_phase(bsc, ti, cb)
struct bs_softc *bsc;
struct targ_info *ti;
struct bsccb *cb;
{
switch (bsc->sc_msgphase)
{
default:
panic("%s unknown msg phase\n", bsc->sc_dvname);
break;
case DISCONNECTASSERT:
case FREE:
#ifdef BS_STATICS
bs_statics[ti->ti_id].disconnected++;
#endif /* BS_STATICS */
if (ti->ti_cfgflags & BS_SCSI_SAVESP)
BS_SAVE_SDP;
BS_HOST_TERMINATE;
BS_SETUP_PHASE(DISCONNECTED);
bsc->sc_dtgnum ++;
bshoststart(bsc, NULL);
break;
case IOCOMPLETED:
bs_status_check(bsc, ti);
cb = bscmddone(ti);
#ifdef BS_DIAG
ti->ti_flags &= ~BSNEXUS;
#endif /* BS_DIAG */
BS_SETUP_PHASE(FREE);
if (cb || TAILQ_FIRST(&bsc->sc_sttab) == NULL)
{
BS_HOST_TERMINATE;
bscmdstart(ti, BSCMDSTART);
}
else
{
/* give a chance to other target */
bscmdstart(ti, BSCMDSTART);
BS_HOST_TERMINATE;
bshoststart(bsc, NULL);
}
break;
}
BS_SETUP_MSGPHASE(FREE);
}
/**************************************************
* <PHASE ERROR>
**************************************************/
#define scsi_status (bsc->sc_busstat)
struct bs_err {
u_char *pe_msg;
u_int pe_err;
u_int pe_ph;
};
struct bs_err bs_cmderr[] = {
/*0*/ { "illegal cmd", BSABNORMAL, UNDEF },
/*1*/ { "unexpected bus free", BSABNORMAL, FREE },
/*2*/ { NULL, BSSELTIMEOUT, FREE},
/*3*/ { "scsi bus parity error", BSPARITY, UNDEF },
/*4*/ { "scsi bus parity error", BSPARITY, UNDEF },
/*5*/ { "unknown" , BSFATALIO, UNDEF },
/*6*/ { "miss reselection (target mode)", BSFATALIO, UNDEF },
/*7*/ { "wrong status byte", BSPARITY, STATUSIN },
};
static void
bs_phase_error(ti, cb)
struct targ_info *ti;
struct bsccb *cb;
{
struct bs_softc *bsc = ti->ti_bsc;
struct bs_err *pep;
if ((scsi_status & BSR_CM) == BSR_CMDERR &&
(scsi_status & BSR_PHVALID) == 0)
{
pep = &bs_cmderr[scsi_status & BSR_PM];
ti->ti_error |= pep->pe_err;
if (pep->pe_msg)
{
bs_debug_print(bsc, ti);
bs_printf(ti, "bsintr", pep->pe_msg);
}
BS_SETUP_PHASE(pep->pe_ph);
}
else
{
ti->ti_error |= BSABNORMAL;
bs_debug_print(bsc, ti);
bs_printf(ti, "bsintr", "phase error");
BS_SETUP_PHASE(UNDEF);
}
BS_SETUP_MSGPHASE(FREE);
switch (ti->ti_phase)
{
case FREE:
BS_SETUP_PHASE(UNDEF);
cb = bscmddone(ti);
#ifdef BS_DIAG
ti->ti_flags &= ~BSNEXUS;
#endif /* BS_DIAG */
BS_HOST_TERMINATE;
BS_SETUP_PHASE(FREE);
bscmdstart(ti, ((cb == NULL) ? BSCMDSTART : BSCMDRESTART));
break;
case STATUSIN:
ti->ti_error |= BSSTATUSERROR;
ti->ti_status = bshw_get_status_insat(bsc); /* XXX SAT */
bs_debug_print(bsc, ti);
break;
case UNDEF:
default:
ti->ti_error |= BSABNORMAL;
bs_reset_nexus(bsc);
break;
}
}
/**************************************************
* ### SCSI PHASE SEQUENCER ###
**************************************************/
static BS_INLINE void bs_ack_wait(struct bs_softc *, struct targ_info *, struct bsccb *);
static BS_INLINE void
bs_ack_wait(bsc, ti, cb)
struct bs_softc *bsc;
struct targ_info *ti;
struct bsccb *cb;
{
int wc = bsc->sc_wc;
for (wc = bsc->sc_wc; bshw_get_busstat(bsc) != BSR_ACKREQ && wc > 0; )
wc --;
if (wc <= 0)
{
bs_printf(ti, "bs_ack_wait", "timeout I");
return;
}
bshw_get_auxstat(bsc);
scsi_status = bshw_get_busstat(bsc);
if (cb->msgoutlen > 0)
{
bshw_assert_atn(bsc);
delay(800);
BS_SETUP_PHASE(ATTENTIONASSERT);
}
bshw_negate_ack(bsc);
#ifdef WAITNEXTP
for (wc = bsc->sc_wc; bshw_get_busstat(bsc) == BSR_ACKREQ && wc > 0; )
wc --;
if (wc <= 0)
bs_printf(ti, "bs_ack_wait", "timeout II");
#endif /* WAITNEXTP */
}
int
bs_sequencer(bsc)
struct bs_softc *bsc;
{
register struct targ_info *ti;
struct bsccb *cb;
/**************************************************
* Check reset
**************************************************/
if (bsc->sc_flags & (BSRESET | BSINACTIVE))
{
if (bsc->sc_flags & BSRESET)
bs_reset_nexus(bsc);
return 1;
}
/**************************************************
* Get status & bus phase
**************************************************/
if ((bshw_get_auxstat(bsc) & STR_INT) == 0)
return 0;
scsi_status = bshw_get_busstat(bsc);
if (scsi_status == ((u_int8_t) -1))
{
bs_debug_print_all(bsc);
return 1;
}
/**************************************************
* Check reselection, or nexus
**************************************************/
if (scsi_status == BSR_RESEL)
{
bs_reselect(bsc);
return 1;
}
ti = bsc->sc_nexus;
if (ti == NULL || (cb = TAILQ_FIRST(&ti->ti_ctab)) == NULL)
{
bs_debug_print_all(bsc);
bs_printf(ti, "bsintr", "no nexus");
bs_reset_nexus(bsc);
return 1;
}
/**************************************************
* Debug section
**************************************************/
#ifdef BS_DEBUG
if (bs_debug_flag)
{
bs_debug_print(bsc, ti);
if (bs_debug_flag > 1)
Debugger();
}
#endif /* BS_DEBUG */
/**************************************************
* internal scsi phase
**************************************************/
switch (ti->ti_phase)
{
case SELECTASSERT:
bs_selected(bsc, ti, cb);
return 1;
case SATSEL:
BS_SELECTION_TERMINATE;
case SATRESEL:
if (bsc->sc_flags & (BSDMASTART | BSSMITSTART))
{
if (bsc->sc_flags & BSSMITSTART)
{
bs_debug_print_all(bsc);
bs_reset_nexus(bsc);
bs_printf(ti, "bsintr", "smit transfer");
return 1;
}
BS_SETUP_PHASE(DATAPHASE); /* XXX */
bs_dma_xfer_end(ti);
ti->ti_phase = ti->ti_ophase; /* XXX */
}
break;
default:
/* XXX:
* check check check for safety !!
*/
if (bsc->sc_selwait)
{
/* Ghaaa! phase error! retry! */
bs_phase_error(ti, cb);
return 1;
}
if (bsc->sc_flags & (BSDMASTART | BSSMITSTART))
{
if (bsc->sc_flags & BSDMASTART)
bs_dma_xfer_end(ti);
else
bs_smit_xfer_end(ti);
}
break;
}
/**************************************************
* hw scsi phase
**************************************************/
if (scsi_status & BSR_PHVALID)
{
/**************************************************
* Normal SCSI phase.
**************************************************/
if ((scsi_status & BSR_CM) == BSR_CMDABT)
{
bs_phase_error(ti, cb);
return 1;
}
switch (scsi_status & BSR_PM)
{
case BSR_DATAOUT:
case BSR_DATAIN:
BS_SETUP_PHASE(DATAPHASE);
if (bsc->sc_p.datalen <= 0 ||
(ti->ti_flags & BSFORCEIOPOLL))
{
bs_io_xfer(ti);
return 1;
}
if (bs_check_smit(ti) &&
(bsc->sc_p.datalen % sizeof(u_int32_t)) == 0)
{
bs_lc_smit_xfer(ti, scsi_status & BSR_IOR);
return 1;
}
bs_dma_xfer(ti, scsi_status & BSR_IOR);
bshw_start_xfer(bsc);
return 1;
case BSR_CMDOUT:
bs_commandout(bsc, ti, cb);
return 1;
case BSR_STATIN:
if (bs_check_sat(ti))
{
BS_SETUP_PHASE(SATCOMPSEQ);
bshw_set_count(bsc, 0);
bshw_cmd_pass(bsc, 0x41);
bshw_start_sat(bsc, 0);
}
else
{
BS_SETUP_PHASE(STATUSIN);
ti->ti_status = bs_read_1byte(bsc);
}
return 1;
case BSR_UNSPINFO0:
case BSR_UNSPINFO1:
bs_debug_print(bsc, ti);
bs_printf(ti, "bsintr", "illegal bus phase");
return 1;
case BSR_MSGOUT:
bs_msgout(bsc, ti, cb);
return 1;
case BSR_MSGIN:/* msg in */
if (bs_check_sat(ti))
{
if (ti->ti_phase == RESELECTED)
{
bs_sat_continue(bsc, ti, cb);
return 1;
}
/* XXX */
if (ti->ti_status == ST_UNK)
ti->ti_status = bshw_get_status_insat(bsc);
}
ti->ti_msgin[ti->ti_msginptr ++] = bs_read_1byte(bsc);
bs_msgin(bsc, ti);
if (bsc->sc_cfgflags & BSC_FASTACK)
bs_ack_wait(bsc, ti, cb);
return 1;
}
}
else
{
/**************************************************
* Special SCSI phase
**************************************************/
switch (scsi_status)
{
case BSR_SATSDP:/* SAT with save data pointer */
BS_SAVE_SDP
bshw_cmd_pass(bsc, 0x41);
bshw_start_sat(bsc, 0);
BS_SETUP_PHASE(SATSDP)
return 1;
case BSR_SATFIN:/* SAT COMPLETE */
ti->ti_status = bshw_get_status_insat(bsc);
BS_SETUP_MSGPHASE(IOCOMPLETED);
bs_disconnect_phase(bsc, ti, cb);
return 1;
case BSR_ACKREQ:/* negate ACK */
if (cb->msgoutlen > 0)
{
bshw_assert_atn(bsc);
delay(800);
BS_SETUP_PHASE(ATTENTIONASSERT);
}
bshw_negate_ack(bsc);
return 1;
case BSR_DISC:/* disconnect */
bs_disconnect_phase(bsc, ti, cb);
return 1;
default:
break;
}
}
bs_phase_error(ti, cb);
return 1;
}
/*****************************************************************
* INTERNAL POLLING FUNCTIONS
*****************************************************************/
static int
bs_scsi_cmd_poll_internal(cti)
struct targ_info *cti;
{
struct bs_softc *bsc = cti->ti_bsc;
struct targ_info *ti;
struct bsccb *cb;
int i, waits, delay_count;
bsc->sc_poll++;
/* setup timeout count */
if ((ti = bsc->sc_nexus) == NULL ||
(cb = TAILQ_FIRST(&ti->ti_ctab)) == NULL)
waits = BS_DEFAULT_TIMEOUT_SECOND * 1000000;
else
waits = cb->tcmax * 1000000;
/* force all current jobs into the polling state. */
for (i = 0; i < NTARGETS; i++)
{
if ((ti = bsc->sc_ti[i]) != NULL)
{
ti->ti_flags |= BSFORCEIOPOLL;
if ((cb = TAILQ_FIRST(&ti->ti_ctab)) != NULL)
cb->bsccb_flags |= BSFORCEIOPOLL;
}
}
/* do io */
bsc->sc_flags &= ~BSJOBDONE;
do
{
delay_count = ((bsc->sc_flags & BSDMASTART) ? 1000000 : 100);
delay(delay_count);
waits -= delay_count;
bs_sequencer(bsc);
}
while (waits >= 0 && (bsc->sc_flags & (BSUNDERRESET | BSJOBDONE)) == 0);
/* done */
bsc->sc_poll--;
if (waits < 0 || (bsc->sc_flags & BSUNDERRESET))
{
bs_printf(NULL, "cmd_poll", "timeout or fatal");
return HASERROR;
}
return COMPLETE;
}
int
bs_scsi_cmd_poll(cti, targetcb)
struct targ_info *cti;
struct bsccb *targetcb;
{
struct bs_softc *bsc = cti->ti_bsc;
struct targ_info *ti;
int s, error = COMPLETE;
s = splcam();
bs_terminate_timeout(bsc);
if (bsc->sc_hstate == BSC_TARG_CHECK)
{
if ((error = bs_scsi_cmd_poll_internal(cti)) != COMPLETE)
bs_reset_nexus(bsc);
}
else
{
if (bsc->sc_outccb)
bs_panic(bsc, "bs_cmd_poll: internal error");
bsc->sc_flags &= ~BSPOLLDONE;
bsc->sc_outccb = targetcb;
while ((bsc->sc_flags & BSPOLLDONE) == 0)
{
if (bs_scsi_cmd_poll_internal(cti) != COMPLETE)
{
if ((ti = bsc->sc_nexus) && TAILQ_FIRST(&ti->ti_ctab))
ti->ti_error |= (BSTIMEOUT | BSABNORMAL);
bs_reset_nexus(bsc);
}
}
bsc->sc_outccb = NULL;
}
bs_start_timeout(bsc);
softintr(bsc->sc_irq);
splx(s);
return error;
}