/*
* Copyright (c) 2002 Manuel Bouyer.
*
* 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 ``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.
*
*/
/*
* get space for the CMD done slot. For this we use a tag table entry.
* It's the same size and allows us to not waste 3/4 of a page
*/
#ifdef DIAGNOSTIC
if (ESIOP_NTAG != A_ndone_slots) {
aprint_error_dev(sc->sc_c.sc_dev,
"size of tag DSA table different from the done ring\n");
return;
}
#endif
esiop_moretagtbl(sc);
tagtbl_donering = TAILQ_FIRST(&sc->free_tagtbl);
if (tagtbl_donering == NULL) {
aprint_error_dev(sc->sc_c.sc_dev,
"no memory for command done ring\n");
return;
}
TAILQ_REMOVE(&sc->free_tagtbl, tagtbl_donering, next);
sc->sc_done_map = tagtbl_donering->tblblk->blkmap;
sc->sc_done_offset = tagtbl_donering->tbl_offset;
sc->sc_done_slot = &tagtbl_donering->tbl[0];
/* Do a bus reset, so that devices fall back to narrow/async */
siop_resetbus(&sc->sc_c);
/*
* siop_reset() will reset the chip, thus clearing pending interrupts
*/
esiop_reset(sc);
#ifdef SIOP_DUMP_SCRIPT
esiop_dump_script(sc);
#endif
if (dstat & ~(DSTAT_SIR | DSTAT_DFE | DSTAT_SSI)) {
printf("%s: DMA IRQ:", device_xname(sc->sc_c.sc_dev));
if (dstat & DSTAT_IID)
printf(" Illegal instruction");
if (dstat & DSTAT_BF)
printf(" bus fault");
if (dstat & DSTAT_MDPE)
printf(" parity");
if (dstat & DSTAT_DFE)
printf(" DMA fifo empty");
else
siop_clearfifo(&sc->sc_c);
printf(", DSP=0x%x DSA=0x%x: ",
(int)(bus_space_read_4(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_DSP) - sc->sc_c.sc_scriptaddr),
bus_space_read_4(sc->sc_c.sc_rt, sc->sc_c.sc_rh, SIOP_DSA));
if (esiop_cmd)
printf("T/L/Q=%d/%d/%d last msg_in=0x%x status=0x%x\n",
target, lun, tag, esiop_cmd->cmd_tables->msg_in[0],
le32toh(esiop_cmd->cmd_tables->status));
else
printf(" current T/L/Q invalid\n");
need_reset = 1;
}
}
if (istat & ISTAT_SIP) {
if (istat & ISTAT_DIP)
delay(10);
/*
* Can't read sist0 & sist1 independently, or we have to
* insert delay
*/
sist = bus_space_read_2(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_SIST0);
sstat1 = bus_space_read_1(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_SSTAT1);
#ifdef SIOP_DEBUG_INTR
printf("scsi interrupt, sist=0x%x sstat1=0x%x "
"DSA=0x%x DSP=0x%lx\n", sist, sstat1,
bus_space_read_4(sc->sc_c.sc_rt, sc->sc_c.sc_rh, SIOP_DSA),
(u_long)(bus_space_read_4(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_DSP) -
sc->sc_c.sc_scriptaddr));
#endif
if (sist & SIST0_RST) {
esiop_handle_reset(sc);
/* no table to flush here */
return 1;
}
if (sist & SIST0_SGE) {
if (esiop_cmd)
scsipi_printaddr(xs->xs_periph);
else
printf("%s:", device_xname(sc->sc_c.sc_dev));
printf("scsi gross error\n");
if (esiop_target)
esiop_target->target_c.flags &= ~TARF_DT;
#ifdef SIOP_DEBUG
printf("DSA=0x%x DSP=0x%lx\n",
bus_space_read_4(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_DSA),
(u_long)(bus_space_read_4(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_DSP) -
sc->sc_c.sc_scriptaddr));
printf("SDID 0x%x SCNTL3 0x%x SXFER 0x%x SCNTL4 0x%x\n",
bus_space_read_1(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_SDID),
bus_space_read_1(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_SCNTL3),
bus_space_read_1(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_SXFER),
bus_space_read_1(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_SCNTL4));
#endif
goto reset;
}
if ((sist & SIST0_MA) && need_reset == 0) {
if (esiop_cmd) {
int scratchc0;
dstat = bus_space_read_1(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_DSTAT);
/*
* first restore DSA, in case we were in a S/G
* operation.
*/
bus_space_write_4(sc->sc_c.sc_rt,
sc->sc_c.sc_rh,
SIOP_DSA, esiop_cmd->cmd_c.dsa);
scratchc0 = bus_space_read_1(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_SCRATCHC);
switch (sstat1 & SSTAT1_PHASE_MASK) {
case SSTAT1_PHASE_STATUS:
/*
* previous phase may be aborted for any reason
* ( for example, the target has less data to
* transfer than requested). Compute resid and
* just go to status, the command should
* terminate.
*/
INCSTAT(esiop_stat_intr_shortxfer);
if (scratchc0 & A_f_c_data)
siop_ma(&esiop_cmd->cmd_c);
else if ((dstat & DSTAT_DFE) == 0)
siop_clearfifo(&sc->sc_c);
CALL_SCRIPT(Ent_status);
return 1;
case SSTAT1_PHASE_MSGIN:
/*
* target may be ready to disconnect
* Compute resid which would be used later
* if a save data pointer is needed.
*/
INCSTAT(esiop_stat_intr_xferdisc);
if (scratchc0 & A_f_c_data)
siop_ma(&esiop_cmd->cmd_c);
else if ((dstat & DSTAT_DFE) == 0)
siop_clearfifo(&sc->sc_c);
bus_space_write_1(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_SCRATCHC,
scratchc0 & ~A_f_c_data);
CALL_SCRIPT(Ent_msgin);
return 1;
}
aprint_error_dev(sc->sc_c.sc_dev,
"unexpected phase mismatch %d\n",
sstat1 & SSTAT1_PHASE_MASK);
} else {
aprint_error_dev(sc->sc_c.sc_dev,
"phase mismatch without command\n");
}
need_reset = 1;
}
if (sist & SIST0_PAR) {
/* parity error, reset */
if (esiop_cmd)
scsipi_printaddr(xs->xs_periph);
else
printf("%s:", device_xname(sc->sc_c.sc_dev));
printf("parity error\n");
if (esiop_target)
esiop_target->target_c.flags &= ~TARF_DT;
goto reset;
}
if ((sist & (SIST1_STO << 8)) && need_reset == 0) {
/*
* selection time out, assume there's no device here
* We also have to update the ring pointer ourselves
*/
slot = bus_space_read_1(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_SCRATCHE);
esiop_script_sync(sc,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
#ifdef SIOP_DEBUG_SCHED
printf("sel timeout target %d, slot %d\n",
target, slot);
#endif
/*
* mark this slot as free, and advance to next slot
*/
esiop_script_write(sc,
sc->sc_shedoffset + slot * CMD_SLOTSIZE,
A_f_cmd_free);
addr = bus_space_read_4(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_SCRATCHD);
if (slot < (A_ncmd_slots - 1)) {
bus_space_write_1(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_SCRATCHE, slot + 1);
addr = addr + sizeof(struct esiop_slot);
} else {
bus_space_write_1(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_SCRATCHE, 0);
addr = sc->sc_c.sc_scriptaddr +
sc->sc_shedoffset * sizeof(uint32_t);
}
bus_space_write_4(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_SCRATCHD, addr);
esiop_script_sync(sc,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
if (esiop_cmd) {
esiop_cmd->cmd_c.status = CMDST_DONE;
xs->error = XS_SELTIMEOUT;
freetarget = 1;
goto end;
} else {
printf("%s: selection timeout without "
"command, target %d (sdid 0x%x), "
"slot %d\n",
device_xname(sc->sc_c.sc_dev), target,
bus_space_read_1(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_SDID), slot);
need_reset = 1;
}
}
if (sist & SIST0_UDC) {
/*
* unexpected disconnect. Usually the target signals
* a fatal condition this way. Attempt to get sense.
*/
if (esiop_cmd) {
esiop_cmd->cmd_tables->status =
htole32(SCSI_CHECK);
goto end;
}
aprint_error_dev(sc->sc_c.sc_dev,
"unexpected disconnect without command\n");
goto reset;
}
if (sist & (SIST1_SBMC << 8)) {
/* SCSI bus mode change */
if (siop_modechange(&sc->sc_c) == 0 || need_reset == 1)
goto reset;
if ((istat & ISTAT_DIP) && (dstat & DSTAT_SIR)) {
/*
* we have a script interrupt, it will
* restart the script.
*/
goto scintr;
}
/*
* else we have to restart it ourselves, at the
* interrupted instruction.
*/
bus_space_write_4(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_DSP,
bus_space_read_4(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_DSP) - 8);
return 1;
}
/* Else it's an unhandled exception (for now). */
aprint_error_dev(sc->sc_c.sc_dev,
"unhandled scsi interrupt, sist=0x%x sstat1=0x%x "
"DSA=0x%x DSP=0x%x\n", sist,
bus_space_read_1(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_SSTAT1),
bus_space_read_4(sc->sc_c.sc_rt, sc->sc_c.sc_rh, SIOP_DSA),
(int)(bus_space_read_4(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_DSP) - sc->sc_c.sc_scriptaddr));
if (esiop_cmd) {
esiop_cmd->cmd_c.status = CMDST_DONE;
xs->error = XS_SELTIMEOUT;
goto end;
}
need_reset = 1;
}
if (need_reset) {
reset:
/* fatal error, reset the bus */
siop_resetbus(&sc->sc_c);
/* no table to flush here */
return 1;
}
scintr:
if ((istat & ISTAT_DIP) && (dstat & DSTAT_SIR)) { /* script interrupt */
irqcode = bus_space_read_4(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_DSPS);
#ifdef SIOP_DEBUG_INTR
printf("script interrupt 0x%x\n", irqcode);
#endif
/*
* no command, or an inactive command is only valid for a
* reselect interrupt
*/
if ((irqcode & 0x80) == 0) {
if (esiop_cmd == NULL) {
aprint_error_dev(sc->sc_c.sc_dev,
"script interrupt (0x%x) with invalid DSA !!!\n",
irqcode);
goto reset;
}
if (esiop_cmd->cmd_c.status != CMDST_ACTIVE) {
aprint_error_dev(sc->sc_c.sc_dev,
"command with invalid status "
"(IRQ code 0x%x current status %d) !\n",
irqcode, esiop_cmd->cmd_c.status);
xs = NULL;
}
}
switch(irqcode) {
case A_int_err:
printf("error, DSP=0x%x\n",
(int)(bus_space_read_4(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_DSP) -
sc->sc_c.sc_scriptaddr));
if (xs) {
xs->error = XS_SELTIMEOUT;
goto end;
} else {
goto reset;
}
case A_int_msgin:
{
int msgin = bus_space_read_1(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_SFBR);
if (msgin == MSG_MESSAGE_REJECT) {
int msg, extmsg;
if (esiop_cmd->cmd_tables->msg_out[0] & 0x80) {
/*
* message was part of a identify +
* something else. Identify shouldn't
* have been rejected.
*/
msg =
esiop_cmd->cmd_tables->msg_out[1];
extmsg =
esiop_cmd->cmd_tables->msg_out[3];
} else {
msg =
esiop_cmd->cmd_tables->msg_out[0];
extmsg =
esiop_cmd->cmd_tables->msg_out[2];
}
if (msg == MSG_MESSAGE_REJECT) {
/* MSG_REJECT for a MSG_REJECT !*/
if (xs)
scsipi_printaddr(xs->xs_periph);
else
printf("%s: ", device_xname(
sc->sc_c.sc_dev));
printf("our reject message was "
"rejected\n");
goto reset;
}
if (msg == MSG_EXTENDED &&
extmsg == MSG_EXT_WDTR) {
/* WDTR rejected, initiate sync */
if ((esiop_target->target_c.flags &
TARF_SYNC) == 0) {
esiop_target->target_c.status =
TARST_OK;
siop_update_xfer_mode(&sc->sc_c,
target);
/* no table to flush here */
CALL_SCRIPT(Ent_msgin_ack);
return 1;
}
esiop_target->target_c.status =
TARST_SYNC_NEG;
siop_sdtr_msg(&esiop_cmd->cmd_c, 0,
sc->sc_c.st_minsync,
sc->sc_c.maxoff);
esiop_table_sync(esiop_cmd,
BUS_DMASYNC_PREREAD |
BUS_DMASYNC_PREWRITE);
CALL_SCRIPT(Ent_send_msgout);
return 1;
} else if (msg == MSG_EXTENDED &&
extmsg == MSG_EXT_SDTR) {
/* sync rejected */
esiop_target->target_c.offset = 0;
esiop_target->target_c.period = 0;
esiop_target->target_c.status =
TARST_OK;
siop_update_xfer_mode(&sc->sc_c,
target);
/* no table to flush here */
CALL_SCRIPT(Ent_msgin_ack);
return 1;
} else if (msg == MSG_EXTENDED &&
extmsg == MSG_EXT_PPR) {
/* PPR rejected */
esiop_target->target_c.offset = 0;
esiop_target->target_c.period = 0;
esiop_target->target_c.status =
TARST_OK;
siop_update_xfer_mode(&sc->sc_c,
target);
/* no table to flush here */
CALL_SCRIPT(Ent_msgin_ack);
return 1;
} else if (msg == MSG_SIMPLE_Q_TAG ||
msg == MSG_HEAD_OF_Q_TAG ||
msg == MSG_ORDERED_Q_TAG) {
if (esiop_handle_qtag_reject(
esiop_cmd) == -1)
goto reset;
CALL_SCRIPT(Ent_msgin_ack);
return 1;
}
if (xs)
scsipi_printaddr(xs->xs_periph);
else
printf("%s: ",
device_xname(sc->sc_c.sc_dev));
if (msg == MSG_EXTENDED) {
printf("scsi message reject, extended "
"message sent was 0x%x\n", extmsg);
} else {
printf("scsi message reject, message "
"sent was 0x%x\n", msg);
}
/* no table to flush here */
CALL_SCRIPT(Ent_msgin_ack);
return 1;
}
if (msgin == MSG_IGN_WIDE_RESIDUE) {
/* use the extmsgdata table to get the second byte */
esiop_cmd->cmd_tables->t_extmsgdata.count =
htole32(1);
esiop_table_sync(esiop_cmd,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
CALL_SCRIPT(Ent_get_extmsgdata);
return 1;
}
if (xs)
scsipi_printaddr(xs->xs_periph);
else
printf("%s: ", device_xname(sc->sc_c.sc_dev));
printf("unhandled message 0x%x\n", msgin);
esiop_cmd->cmd_tables->msg_out[0] = MSG_MESSAGE_REJECT;
esiop_cmd->cmd_tables->t_msgout.count= htole32(1);
esiop_table_sync(esiop_cmd,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
CALL_SCRIPT(Ent_send_msgout);
return 1;
}
case A_int_extmsgin:
#ifdef SIOP_DEBUG_INTR
printf("extended message: msg 0x%x len %d\n",
esiop_cmd->cmd_tables->msg_in[2],
esiop_cmd->cmd_tables->msg_in[1]);
#endif
if (esiop_cmd->cmd_tables->msg_in[1] >
sizeof(esiop_cmd->cmd_tables->msg_in) - 2)
aprint_error_dev(sc->sc_c.sc_dev,
"extended message too big (%d)\n",
esiop_cmd->cmd_tables->msg_in[1]);
esiop_cmd->cmd_tables->t_extmsgdata.count =
htole32(esiop_cmd->cmd_tables->msg_in[1] - 1);
esiop_table_sync(esiop_cmd,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
CALL_SCRIPT(Ent_get_extmsgdata);
return 1;
case A_int_extmsgdata:
#ifdef SIOP_DEBUG_INTR
{
int i;
printf("extended message: 0x%x, data:",
esiop_cmd->cmd_tables->msg_in[2]);
for (i = 3; i < 2 + esiop_cmd->cmd_tables->msg_in[1];
i++)
printf(" 0x%x",
esiop_cmd->cmd_tables->msg_in[i]);
printf("\n");
}
#endif
if (esiop_cmd->cmd_tables->msg_in[0] ==
MSG_IGN_WIDE_RESIDUE) {
/* we got the second byte of MSG_IGN_WIDE_RESIDUE */
if (esiop_cmd->cmd_tables->msg_in[3] != 1)
printf("MSG_IGN_WIDE_RESIDUE: "
"bad len %d\n",
esiop_cmd->cmd_tables->msg_in[3]);
switch (siop_iwr(&esiop_cmd->cmd_c)) {
case SIOP_NEG_MSGOUT:
esiop_table_sync(esiop_cmd,
BUS_DMASYNC_PREREAD |
BUS_DMASYNC_PREWRITE);
CALL_SCRIPT(Ent_send_msgout);
return 1;
case SIOP_NEG_ACK:
CALL_SCRIPT(Ent_msgin_ack);
return 1;
default:
panic("invalid retval from "
"siop_iwr()");
}
return 1;
}
if (esiop_cmd->cmd_tables->msg_in[2] == MSG_EXT_PPR) {
switch (siop_ppr_neg(&esiop_cmd->cmd_c)) {
case SIOP_NEG_MSGOUT:
esiop_update_scntl3(sc,
esiop_cmd->cmd_c.siop_target);
esiop_table_sync(esiop_cmd,
BUS_DMASYNC_PREREAD |
BUS_DMASYNC_PREWRITE);
CALL_SCRIPT(Ent_send_msgout);
return 1;
case SIOP_NEG_ACK:
esiop_update_scntl3(sc,
esiop_cmd->cmd_c.siop_target);
CALL_SCRIPT(Ent_msgin_ack);
return 1;
default:
panic("invalid retval from "
"siop_ppr_neg()");
}
return 1;
}
if (esiop_cmd->cmd_tables->msg_in[2] == MSG_EXT_WDTR) {
switch (siop_wdtr_neg(&esiop_cmd->cmd_c)) {
case SIOP_NEG_MSGOUT:
esiop_update_scntl3(sc,
esiop_cmd->cmd_c.siop_target);
esiop_table_sync(esiop_cmd,
BUS_DMASYNC_PREREAD |
BUS_DMASYNC_PREWRITE);
CALL_SCRIPT(Ent_send_msgout);
return 1;
case SIOP_NEG_ACK:
esiop_update_scntl3(sc,
esiop_cmd->cmd_c.siop_target);
CALL_SCRIPT(Ent_msgin_ack);
return 1;
default:
panic("invalid retval from "
"siop_wdtr_neg()");
}
return 1;
}
if (esiop_cmd->cmd_tables->msg_in[2] == MSG_EXT_SDTR) {
switch (siop_sdtr_neg(&esiop_cmd->cmd_c)) {
case SIOP_NEG_MSGOUT:
esiop_update_scntl3(sc,
esiop_cmd->cmd_c.siop_target);
esiop_table_sync(esiop_cmd,
BUS_DMASYNC_PREREAD |
BUS_DMASYNC_PREWRITE);
CALL_SCRIPT(Ent_send_msgout);
return 1;
case SIOP_NEG_ACK:
esiop_update_scntl3(sc,
esiop_cmd->cmd_c.siop_target);
CALL_SCRIPT(Ent_msgin_ack);
return 1;
default:
panic("invalid retval from "
"siop_sdtr_neg()");
}
return 1;
}
/* send a message reject */
esiop_cmd->cmd_tables->msg_out[0] = MSG_MESSAGE_REJECT;
esiop_cmd->cmd_tables->t_msgout.count = htole32(1);
esiop_table_sync(esiop_cmd,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
CALL_SCRIPT(Ent_send_msgout);
return 1;
case A_int_disc:
INCSTAT(esiop_stat_intr_sdp);
offset = bus_space_read_1(sc->sc_c.sc_rt,
sc->sc_c.sc_rh, SIOP_SCRATCHA + 1);
#ifdef SIOP_DEBUG_DR
printf("disconnect offset %d\n", offset);
#endif
siop_sdp(&esiop_cmd->cmd_c, offset);
/* we start again with no offset */
ESIOP_XFER(esiop_cmd, saved_offset) =
htole32(SIOP_NOOFFSET);
esiop_table_sync(esiop_cmd,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
CALL_SCRIPT(Ent_script_sched);
return 1;
case A_int_resfail:
printf("reselect failed\n");
CALL_SCRIPT(Ent_script_sched);
return 1;
case A_int_done:
if (xs == NULL) {
printf("%s: done without command\n",
device_xname(sc->sc_c.sc_dev));
CALL_SCRIPT(Ent_script_sched);
return 1;
}
#ifdef SIOP_DEBUG_INTR
printf("done, DSA=0x%lx target id 0x%x last msg "
"in=0x%x status=0x%x\n",
(u_long)esiop_cmd->cmd_c.dsa,
le32toh(esiop_cmd->cmd_tables->id),
esiop_cmd->cmd_tables->msg_in[0],
le32toh(esiop_cmd->cmd_tables->status));
#endif
INCSTAT(esiop_stat_intr_done);
esiop_cmd->cmd_c.status = CMDST_DONE;
goto end;
default:
printf("unknown irqcode %x\n", irqcode);
if (xs) {
xs->error = XS_SELTIMEOUT;
goto end;
}
goto reset;
}
return 1;
}
/*
* We just shouldn't get there, but on some KVM virtual hosts,
* we do - see PR 48277.
*/
printf("esiop_intr: I shouldn't be there !\n");
return 1;
end:
/*
* restart the script now if command completed properly
* Otherwise wait for siop_scsicmd_end(), we may need to cleanup the
* queue
*/
xs->status = le32toh(esiop_cmd->cmd_tables->status);
#ifdef SIOP_DEBUG_INTR
printf("esiop_intr end: status %d\n", xs->status);
#endif
if (tag >= 0)
esiop_lun->tactive[tag] = NULL;
else
esiop_lun->active = NULL;
offset = bus_space_read_1(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_SCRATCHA + 1);
/*
* if we got a disconnect between the last data phase
* and the status phase, offset will be 0. In this
* case, cmd_tables->saved_offset will have the proper value
* if it got updated by the controller
*/
if (offset == 0 &&
ESIOP_XFER(esiop_cmd, saved_offset) != htole32(SIOP_NOOFFSET))
offset =
(le32toh(ESIOP_XFER(esiop_cmd, saved_offset)) >> 8) & 0xff;
switch(xs->status) {
case SCSI_OK:
xs->error = XS_NOERROR;
break;
case SCSI_BUSY:
xs->error = XS_BUSY;
break;
case SCSI_CHECK:
xs->error = XS_BUSY;
/* remove commands in the queue and scheduler */
esiop_unqueue(sc, xs->xs_periph->periph_target,
xs->xs_periph->periph_lun);
break;
case SCSI_QUEUE_FULL:
INCSTAT(esiop_stat_intr_qfull);
#ifdef SIOP_DEBUG
printf("%s:%d:%d: queue full (tag %d)\n",
device_xname(sc->sc_c.sc_dev),
xs->xs_periph->periph_target,
xs->xs_periph->periph_lun, esiop_cmd->cmd_c.tag);
#endif
xs->error = XS_BUSY;
break;
case SCSI_SIOP_NOCHECK:
/*
* don't check status, xs->error is already valid
*/
break;
case SCSI_SIOP_NOSTATUS:
/*
* the status byte was not updated, cmd was
* aborted
*/
xs->error = XS_SELTIMEOUT;
break;
default:
scsipi_printaddr(xs->xs_periph);
printf("invalid status code %d\n", xs->status);
xs->error = XS_DRIVER_STUFFUP;
}
if (xs->xs_control & (XS_CTL_DATA_IN | XS_CTL_DATA_OUT)) {
bus_dmamap_sync(sc->sc_c.sc_dmat,
esiop_cmd->cmd_c.dmamap_data, 0,
esiop_cmd->cmd_c.dmamap_data->dm_mapsize,
(xs->xs_control & XS_CTL_DATA_IN) ?
BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->sc_c.sc_dmat,
esiop_cmd->cmd_c.dmamap_data);
}
bus_dmamap_unload(sc->sc_c.sc_dmat, esiop_cmd->cmd_c.dmamap_cmd);
if ((xs->xs_control & XS_CTL_POLL) == 0)
callout_stop(&xs->xs_callout);
esiop_cmd->cmd_c.status = CMDST_FREE;
TAILQ_INSERT_TAIL(&sc->free_list, esiop_cmd, next);
#if 0
if (xs->resid != 0)
printf("resid %d datalen %d\n", xs->resid, xs->datalen);
#endif
scsipi_done (xs);
}
void
esiop_checkdone(struct esiop_softc *sc)
{
int target, lun, tag;
struct esiop_target *esiop_target;
struct esiop_lun *esiop_lun;
struct esiop_cmd *esiop_cmd;
uint32_t slot;
int needsync = 0;
int status;
uint32_t sem, offset;
esiop_script_sync(sc, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
sem = esiop_script_read(sc, sc->sc_semoffset);
esiop_script_write(sc, sc->sc_semoffset, sem & ~A_sem_done);
if ((sc->sc_flags & SCF_CHAN_NOSLOT) && (sem & A_sem_start)) {
/*
* at last one command have been started,
* so we should have free slots now
*/
sc->sc_flags &= ~SCF_CHAN_NOSLOT;
scsipi_channel_thaw(&sc->sc_c.sc_chan, 1);
}
esiop_script_sync(sc, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
if ((sem & A_sem_done) == 0) {
/* no pending done command */
return;
}
esiop_table_sync(esiop_cmd,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
status = le32toh(esiop_cmd->cmd_tables->status);
#ifdef DIAGNOSTIC
if (status != SCSI_OK) {
printf("command for T/L/Q %d/%d/%d status %d\n",
target, lun, tag, status);
goto next;
}
#endif
/* Ok, this command has been handled */
esiop_cmd->cmd_c.xs->status = status;
if (tag >= 0)
esiop_lun->tactive[tag] = NULL;
else
esiop_lun->active = NULL;
/*
* scratcha was eventually saved in saved_offset by script.
* fetch offset from it
*/
offset = 0;
if (ESIOP_XFER(esiop_cmd, saved_offset) != htole32(SIOP_NOOFFSET))
offset =
(le32toh(ESIOP_XFER(esiop_cmd, saved_offset)) >> 8) & 0xff;
esiop_scsicmd_end(esiop_cmd, offset);
goto next;
}
void
esiop_unqueue(struct esiop_softc *sc, int target, int lun)
{
int slot, tag;
uint32_t slotdsa;
struct esiop_cmd *esiop_cmd;
struct esiop_lun *esiop_lun =
((struct esiop_target *)sc->sc_c.targets[target])->esiop_lun[lun];
/* first make sure to read valid data */
esiop_script_sync(sc, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
for (tag = 0; tag < ESIOP_NTAG; tag++) {
/* look for commands in the scheduler, not yet started */
if (esiop_lun->tactive[tag] == NULL)
continue;
esiop_cmd = esiop_lun->tactive[tag];
for (slot = 0; slot < A_ncmd_slots; slot++) {
slotdsa = esiop_script_read(sc,
sc->sc_shedoffset + slot * CMD_SLOTSIZE);
/* if the slot has any flag, it won't match the DSA */
if (slotdsa == esiop_cmd->cmd_c.dsa) { /* found it */
/* Mark this slot as ignore */
esiop_script_write(sc,
sc->sc_shedoffset + slot * CMD_SLOTSIZE,
esiop_cmd->cmd_c.dsa | A_f_cmd_ignore);
/* ask to requeue */
esiop_cmd->cmd_c.xs->error = XS_REQUEUE;
esiop_cmd->cmd_c.xs->status = SCSI_SIOP_NOCHECK;
esiop_lun->tactive[tag] = NULL;
esiop_scsicmd_end(esiop_cmd, 0);
break;
}
}
}
esiop_script_sync(sc, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
}
/*
* handle a rejected queue tag message: the command will run untagged,
* has to adjust the reselect script.
*/
int
esiop_handle_qtag_reject(struct esiop_cmd *esiop_cmd)
{
struct esiop_softc *sc = (struct esiop_softc *)esiop_cmd->cmd_c.siop_sc;
int target = esiop_cmd->cmd_c.xs->xs_periph->periph_target;
int lun = esiop_cmd->cmd_c.xs->xs_periph->periph_lun;
int tag = esiop_cmd->cmd_tables->msg_out[2];
struct esiop_target *esiop_target =
(struct esiop_target*)sc->sc_c.targets[target];
struct esiop_lun *esiop_lun = esiop_target->esiop_lun[lun];
/*
* handle a bus reset: reset chip, unqueue all active commands, free all
* target struct and report lossage to upper layer.
* As the upper layer may requeue immediately we have to first store
* all active commands in a temporary queue.
*/
void
esiop_handle_reset(struct esiop_softc *sc)
{
struct esiop_cmd *esiop_cmd;
struct esiop_lun *esiop_lun;
int target, lun, tag;
/*
* scsi bus reset. reset the chip and restart
* the queue. Need to clean up all active commands
*/
printf("%s: scsi bus reset\n", device_xname(sc->sc_c.sc_dev));
/* stop, reset and restart the chip */
esiop_reset(sc);
if (sc->sc_flags & SCF_CHAN_NOSLOT) {
/* chip has been reset, all slots are free now */
sc->sc_flags &= ~SCF_CHAN_NOSLOT;
scsipi_channel_thaw(&sc->sc_c.sc_chan, 1);
}
/*
* Process all commands: first commands completes, then commands
* being executed
*/
esiop_checkdone(sc);
for (target = 0; target < sc->sc_c.sc_chan.chan_ntargets; target++) {
struct esiop_target *esiop_target =
(struct esiop_target *)sc->sc_c.targets[target];
if (esiop_target == NULL)
continue;
for (lun = 0; lun < 8; lun++) {
esiop_lun = esiop_target->esiop_lun[lun];
if (esiop_lun == NULL)
continue;
for (tag = -1; tag <
((sc->sc_c.targets[target]->flags & TARF_TAG) ?
ESIOP_NTAG : 0);
tag++) {
if (tag >= 0)
esiop_cmd = esiop_lun->tactive[tag];
else
esiop_cmd = esiop_lun->active;
if (esiop_cmd == NULL)
continue;
scsipi_printaddr(
esiop_cmd->cmd_c.xs->xs_periph);
printf("command with tag id %d reset\n", tag);
esiop_cmd->cmd_c.xs->error =
(esiop_cmd->cmd_c.flags & CMDFL_TIMEOUT) ?
XS_TIMEOUT : XS_RESET;
esiop_cmd->cmd_c.xs->status = SCSI_SIOP_NOCHECK;
if (tag >= 0)
esiop_lun->tactive[tag] = NULL;
else
esiop_lun->active = NULL;
esiop_cmd->cmd_c.status = CMDST_DONE;
esiop_scsicmd_end(esiop_cmd, 0);
}
}
sc->sc_c.targets[target]->status = TARST_ASYNC;
sc->sc_c.targets[target]->flags &= ~(TARF_ISWIDE | TARF_ISDT);
sc->sc_c.targets[target]->period =
sc->sc_c.targets[target]->offset = 0;
siop_update_xfer_mode(&sc->sc_c, target);
}
/*
* first make sure to read valid data
*/
esiop_script_sync(sc, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
/*
* We use a circular queue here. sc->sc_currschedslot points to a
* free slot, unless we have filled the queue. Check this.
*/
slot = sc->sc_currschedslot;
if ((esiop_script_read(sc, sc->sc_shedoffset + slot * CMD_SLOTSIZE) &
A_f_cmd_free) == 0) {
/*
* no more free slot, no need to continue. freeze the queue
* and requeue this command.
*/
scsipi_channel_freeze(&sc->sc_c.sc_chan, 1);
sc->sc_flags |= SCF_CHAN_NOSLOT;
esiop_script_write(sc, sc->sc_semoffset,
esiop_script_read(sc, sc->sc_semoffset) & ~A_sem_start);
esiop_script_sync(sc,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
esiop_cmd->cmd_c.xs->error = XS_REQUEUE;
esiop_cmd->cmd_c.xs->status = SCSI_SIOP_NOCHECK;
esiop_scsicmd_end(esiop_cmd, 0);
return;
}
/* OK, we can use this slot */
target = esiop_cmd->cmd_c.xs->xs_periph->periph_target;
lun = esiop_cmd->cmd_c.xs->xs_periph->periph_lun;
esiop_target = (struct esiop_target*)sc->sc_c.targets[target];
esiop_lun = esiop_target->esiop_lun[lun];
/* if non-tagged command active, panic: this shouldn't happen */
if (esiop_lun->active != NULL) {
panic("esiop_start: tagged cmd while untagged running");
}
#ifdef DIAGNOSTIC
/* sanity check the tag if needed */
if (esiop_cmd->cmd_c.flags & CMDFL_TAG) {
if (esiop_cmd->cmd_c.tag >= ESIOP_NTAG ||
esiop_cmd->cmd_c.tag < 0) {
scsipi_printaddr(esiop_cmd->cmd_c.xs->xs_periph);
printf(": tag id %d\n", esiop_cmd->cmd_c.tag);
panic("esiop_start: invalid tag id");
}
if (esiop_lun->tactive[esiop_cmd->cmd_c.tag] != NULL)
panic("esiop_start: tag not free");
}
#endif
#ifdef SIOP_DEBUG_SCHED
printf("using slot %d for DSA 0x%lx\n", slot,
(u_long)esiop_cmd->cmd_c.dsa);
#endif
/* mark command as active */
if (esiop_cmd->cmd_c.status == CMDST_READY)
esiop_cmd->cmd_c.status = CMDST_ACTIVE;
else
panic("esiop_start: bad status");
/* DSA table for reselect */
if (esiop_cmd->cmd_c.flags & CMDFL_TAG) {
esiop_lun->tactive[esiop_cmd->cmd_c.tag] = esiop_cmd;
/* DSA table for reselect */
esiop_lun->lun_tagtbl->tbl[esiop_cmd->cmd_c.tag] =
htole32(esiop_cmd->cmd_c.dsa);
bus_dmamap_sync(sc->sc_c.sc_dmat,
esiop_lun->lun_tagtbl->tblblk->blkmap,
esiop_lun->lun_tagtbl->tbl_offset,
sizeof(uint32_t) * ESIOP_NTAG, BUS_DMASYNC_PREWRITE);
} else {
esiop_lun->active = esiop_cmd;
esiop_script_write(sc,
esiop_target->lun_table_offset +
lun * 2 + A_target_luntbl / sizeof(uint32_t),
esiop_cmd->cmd_c.dsa);
}
/* scheduler slot: DSA */
esiop_script_write(sc, sc->sc_shedoffset + slot * CMD_SLOTSIZE,
esiop_cmd->cmd_c.dsa);
/* make sure SCRIPT processor will read valid data */
esiop_script_sync(sc, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
/* handle timeout */
if ((esiop_cmd->cmd_c.xs->xs_control & XS_CTL_POLL) == 0) {
/* start exire timer */
timeout = mstohz(esiop_cmd->cmd_c.xs->timeout);
if (timeout == 0)
timeout = 1;
callout_reset( &esiop_cmd->cmd_c.xs->xs_callout,
timeout, esiop_timeout, esiop_cmd);
}
/* Signal script it has some work to do */
bus_space_write_1(sc->sc_c.sc_rt, sc->sc_c.sc_rh,
SIOP_ISTAT, ISTAT_SIGP);
/* update the current slot, and wait for IRQ */
sc->sc_currschedslot++;
if (sc->sc_currschedslot >= A_ncmd_slots)
sc->sc_currschedslot = 0;
}
/* deactivate callout */
callout_stop(&esiop_cmd->cmd_c.xs->xs_callout);
/*
* mark command has being timed out and just return;
* the bus reset will generate an interrupt,
* it will be handled in siop_intr()
*/
esiop_cmd->cmd_c.flags |= CMDFL_TIMEOUT;
splx(s);
}
void
esiop_dump_script(struct esiop_softc *sc)
{
int i;
for (i = 0; i < PAGE_SIZE / 4; i += 2) {
printf("0x%04x: 0x%08x 0x%08x", i * 4,
esiop_script_read(sc, i),
esiop_script_read(sc, i + 1));
if ((esiop_script_read(sc, i) & 0xe0000000) == 0xc0000000) {
i++;
printf(" 0x%08x", esiop_script_read(sc, i + 1));
}
printf("\n");
}
}
void
esiop_morecbd(struct esiop_softc *sc)
{
int error, i, s;
bus_dma_segment_t seg;
int rseg;
struct esiop_cbd *newcbd;
struct esiop_xfer *xfer;
bus_addr_t dsa;
/* allocate a new list head */
newcbd = malloc(sizeof(struct esiop_cbd), M_DEVBUF, M_NOWAIT|M_ZERO);
if (newcbd == NULL) {
aprint_error_dev(sc->sc_c.sc_dev,
"can't allocate memory for command descriptors "
"head\n");
return;
}
void
esiop_moretagtbl(struct esiop_softc *sc)
{
int error, i, j, s;
bus_dma_segment_t seg;
int rseg;
struct esiop_dsatblblk *newtblblk;
struct esiop_dsatbl *newtbls;
uint32_t *tbls;
/* allocate a new list head */
newtblblk = malloc(sizeof(struct esiop_dsatblblk),
M_DEVBUF, M_NOWAIT|M_ZERO);
if (newtblblk == NULL) {
aprint_error_dev(sc->sc_c.sc_dev,
"can't allocate memory for tag DSA table block\n");
return;
}