summaryrefslogtreecommitdiff
path: root/sys/dev/ic/aic79xx.c
diff options
context:
space:
mode:
authorKenneth R Westerback <krw@cvs.openbsd.org>2004-12-11 00:12:26 +0000
committerKenneth R Westerback <krw@cvs.openbsd.org>2004-12-11 00:12:26 +0000
commitd7ee871bf30b47a2d27c2f47163fcfd8c4a5d99d (patch)
tree62a2cd8509c1f23441b2cd673720ccf0511d1b65 /sys/dev/ic/aic79xx.c
parent295f712ab4695a2319a5a1dd477b932d9e6a63ff (diff)
Correct a very rare case where command ordering could be compromised
by a transaction performing a driver handled message sequence (an scb with the MK_MESSAGE flag set). Full details on changes in FreeBSD commit message. From FreeBSD aic79xx.c r1.30, using microcode generated from just committed new source. Tested in August by marco@.
Diffstat (limited to 'sys/dev/ic/aic79xx.c')
-rw-r--r--sys/dev/ic/aic79xx.c229
1 files changed, 174 insertions, 55 deletions
diff --git a/sys/dev/ic/aic79xx.c b/sys/dev/ic/aic79xx.c
index 4747318707d..195b4082195 100644
--- a/sys/dev/ic/aic79xx.c
+++ b/sys/dev/ic/aic79xx.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: aic79xx.c,v 1.18 2004/12/10 17:00:36 krw Exp $ */
+/* $OpenBSD: aic79xx.c,v 1.19 2004/12/11 00:12:25 krw Exp $ */
/*
* Copyright (c) 2004 Milos Urbanek, Kenneth R. Westerback & Marco Peereboom
@@ -30,7 +30,7 @@
/*
* Core routines and tables shareable across OS platforms.
*
- * Copyright (c) 1994-2002 Justin T. Gibbs.
+ * Copyright (c) 1994-2002, 2004 Justin T. Gibbs.
* Copyright (c) 2000-2003 Adaptec Inc.
* All rights reserved.
*
@@ -226,7 +226,8 @@ int ahd_search_scb_list(struct ahd_softc *ahd, int target,
char channel, int lun, u_int tag,
role_t role, uint32_t status,
ahd_search_action action,
- u_int *list_head, u_int tid);
+ u_int *list_head, u_int *list_tail,
+ u_int tid);
void ahd_stitch_tid_list(struct ahd_softc *ahd,
u_int tid_prev, u_int tid_cur,
u_int tid_next);
@@ -1700,7 +1701,8 @@ ahd_handle_scsiint(struct ahd_softc *ahd, u_int intstat)
* so just clear the error.
*/
ahd_outb(ahd, CLRLQIINT1, CLRLQICRCI_NLQ);
- } else if ((status & BUSFREE) != 0) {
+ } else if ((status & BUSFREE) != 0
+ || (lqistat1 & LQOBUSFREE) != 0) {
u_int lqostat1;
int restart;
int clear_fifo;
@@ -2065,10 +2067,6 @@ ahd_handle_pkt_busfree(struct ahd_softc *ahd, u_int busfreetime)
u_int waiting_t;
u_int next;
- if ((busfreetime & BUSFREE_LQO) == 0)
- printf("%s: Warning, BUSFREE time is 0x%x. "
- "Expected BUSFREE_LQO.\n",
- ahd_name(ahd), busfreetime);
/*
* The LQO manager detected an unexpected busfree
* either:
@@ -2291,8 +2289,14 @@ ahd_handle_nonpkt_busfree(struct ahd_softc *ahd)
struct ahd_tmode_tstate *tstate;
/*
- * PPR Rejected. Try non-ppr negotiation
- * and retry command.
+ * PPR Rejected.
+ *
+ * If the previous negotiation was packetized,
+ * this could be because the device has been
+ * reset without our knowledge. Force our
+ * current negotiation to async and retry the
+ * negotiation. Otherwise retry the command
+ * with non-ppr negotiation.
*/
#ifdef AHD_DEBUG
if ((ahd_debug & AHD_SHOW_MESSAGES) != 0)
@@ -2301,11 +2305,34 @@ ahd_handle_nonpkt_busfree(struct ahd_softc *ahd)
tinfo = ahd_fetch_transinfo(ahd, devinfo.channel,
devinfo.our_scsiid,
devinfo.target, &tstate);
- tinfo->curr.transport_version = 2;
- tinfo->goal.transport_version = 2;
- tinfo->goal.ppr_options = 0;
- ahd_qinfifo_requeue_tail(ahd, scb);
- printerror = 0;
+ if ((tinfo->curr.ppr_options & MSG_EXT_PPR_IU_REQ)!=0) {
+ ahd_set_width(ahd, &devinfo,
+ MSG_EXT_WDTR_BUS_8_BIT,
+ AHD_TRANS_CUR,
+ /*paused*/TRUE);
+ ahd_set_syncrate(ahd, &devinfo,
+ /*period*/0, /*offset*/0,
+ /*ppr_options*/0,
+ AHD_TRANS_CUR,
+ /*paused*/TRUE);
+ /*
+ * The expect PPR busfree handler below
+ * will effect the retry and necessary
+ * abort.
+ */
+ } else {
+ tinfo->curr.transport_version = 2;
+ tinfo->goal.transport_version = 2;
+ tinfo->goal.ppr_options = 0;
+ /*
+ * Remove any SCBs in the waiting for selection
+ * queue that may also be for this target so
+ * that command ordering is preserved.
+ */
+ ahd_freeze_devq(ahd, scb);
+ ahd_qinfifo_requeue_tail(ahd, scb);
+ printerror = 0;
+ }
} else if (ahd_sent_msg(ahd, AHDMSG_EXT, MSG_EXT_WDTR, FALSE)
&& ppr_busfree == 0) {
/*
@@ -2320,6 +2347,12 @@ ahd_handle_nonpkt_busfree(struct ahd_softc *ahd)
MSG_EXT_WDTR_BUS_8_BIT,
AHD_TRANS_CUR|AHD_TRANS_GOAL,
/*paused*/TRUE);
+ /*
+ * Remove any SCBs in the waiting for selection
+ * queue that may also be for this target so that
+ * command ordering is preserved.
+ */
+ ahd_freeze_devq(ahd, scb);
ahd_qinfifo_requeue_tail(ahd, scb);
printerror = 0;
} else if (ahd_sent_msg(ahd, AHDMSG_EXT, MSG_EXT_SDTR, FALSE)
@@ -2337,6 +2370,12 @@ ahd_handle_nonpkt_busfree(struct ahd_softc *ahd)
/*ppr_options*/0,
AHD_TRANS_CUR|AHD_TRANS_GOAL,
/*paused*/TRUE);
+ /*
+ * Remove any SCBs in the waiting for selection
+ * queue that may also be for this target so that
+ * command ordering is preserved.
+ */
+ ahd_freeze_devq(ahd, scb);
ahd_qinfifo_requeue_tail(ahd, scb);
printerror = 0;
} else if ((ahd->msg_flags & MSG_FLAG_EXPECT_IDE_BUSFREE) != 0
@@ -2409,14 +2448,14 @@ ahd_handle_nonpkt_busfree(struct ahd_softc *ahd)
*/
printf("%s: ", ahd_name(ahd));
}
- if (lastphase != P_BUSFREE)
- ahd_force_renegotiation(ahd, &devinfo);
printf("Unexpected busfree %s, %d SCBs aborted, "
"PRGMCNT == 0x%x\n",
ahd_lookup_phase_entry(lastphase)->phasemsg,
aborted,
ahd_inw(ahd, PRGMCNT));
ahd_dump_card_state(ahd);
+ if (lastphase != P_BUSFREE)
+ ahd_force_renegotiation(ahd, &devinfo);
}
/* Always restart the sequencer. */
return (1);
@@ -3360,7 +3399,6 @@ ahd_update_pending_scbs(struct ahd_softc *ahd)
{
struct scb *pending_scb;
int pending_scb_count;
- u_int scb_tag;
int paused;
u_int saved_scbptr;
ahd_mode_state saved_modes;
@@ -3378,7 +3416,6 @@ ahd_update_pending_scbs(struct ahd_softc *ahd)
pending_scb_count = 0;
LIST_FOREACH(pending_scb, &ahd->pending_scbs, pending_links) {
struct ahd_devinfo devinfo;
- struct hardware_scb *pending_hscb;
struct ahd_initiator_tinfo *tinfo;
struct ahd_tmode_tstate *tstate;
@@ -3386,11 +3423,10 @@ ahd_update_pending_scbs(struct ahd_softc *ahd)
tinfo = ahd_fetch_transinfo(ahd, devinfo.channel,
devinfo.our_scsiid,
devinfo.target, &tstate);
- pending_hscb = pending_scb->hscb;
if ((tstate->auto_negotiate & devinfo.target_mask) == 0
&& (pending_scb->flags & SCB_AUTO_NEGOTIATE) != 0) {
pending_scb->flags &= ~SCB_AUTO_NEGOTIATE;
- pending_hscb->control &= ~MK_MESSAGE;
+ pending_scb->hscb->control &= ~MK_MESSAGE;
}
ahd_sync_scb(ahd, pending_scb,
BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
@@ -3422,18 +3458,15 @@ ahd_update_pending_scbs(struct ahd_softc *ahd)
ahd_outb(ahd, SCSISEQ0, ahd_inb(ahd, SCSISEQ0) & ~ENSELO);
saved_scbptr = ahd_get_scbptr(ahd);
/* Ensure that the hscbs down on the card match the new information */
- for (scb_tag = 0; scb_tag < ahd->scb_data.maxhscbs; scb_tag++) {
- struct hardware_scb *pending_hscb;
+ LIST_FOREACH(pending_scb, &ahd->pending_scbs, pending_links) {
+ u_int scb_tag;
u_int control;
- pending_scb = ahd_lookup_scb(ahd, scb_tag);
- if (pending_scb == NULL)
- continue;
+ scb_tag = SCB_GET_TAG(pending_scb);
ahd_set_scbptr(ahd, scb_tag);
- pending_hscb = pending_scb->hscb;
control = ahd_inb_scbram(ahd, SCB_CONTROL);
control &= ~MK_MESSAGE;
- control |= pending_hscb->control & MK_MESSAGE;
+ control |= pending_scb->hscb->control & MK_MESSAGE;
ahd_outb(ahd, SCB_CONTROL, control);
}
ahd_set_scbptr(ahd, saved_scbptr);
@@ -4911,7 +4944,7 @@ ahd_handle_ign_wide_residue(struct ahd_softc *ahd, struct ahd_devinfo *devinfo)
*/
sg--;
sglen = aic_le32toh(sg->len) & AHD_SG_LEN_MASK;
- if (sg != (struct ahd_dma64_seg *)scb->sg_list
+ if (sg != scb->sg_list
&& sglen < (data_cnt & AHD_SG_LEN_MASK)) {
sg--;
@@ -6409,13 +6442,14 @@ ahd_chip_init(struct ahd_softc *ahd)
| ENLQIOVERI_LQ|ENLQIOVERI_NLQ);
ahd_outb(ahd, LQOMODE0, ENLQOATNLQ|ENLQOATNPKT|ENLQOTCRC);
/*
- * An interrupt from LQOBUSFREE is made redundant by the
- * BUSFREE interrupt. We choose to have the sequencer catch
- * LQOPHCHGINPKT errors manually for the command phase at the
- * start of a packetized selection case.
- ahd_outb(ahd, LQOMODE1, ENLQOBUSFREE|ENLQOPHACHGINPKT);
+ * We choose to have the sequencer catch LQOPHCHGINPKT errors
+ * manually for the command phase at the start of a packetized
+ * selection case. ENLQOBUSFREE should be made redundant by
+ * the BUSFREE interrupt, but it seems that some LQOBUSFREE
+ * events fail to assert the BUSFREE interrupt so we must
+ * also enable LQOBUSFREE interrupts.
*/
- ahd_outb(ahd, LQOMODE1, 0);
+ ahd_outb(ahd, LQOMODE1, ENLQOBUSFREE);
/*
* Setup sequencer interrupt handlers.
@@ -6526,6 +6560,8 @@ ahd_chip_init(struct ahd_softc *ahd)
/* We don't have any waiting selections */
ahd_outw(ahd, WAITING_TID_HEAD, SCB_LIST_NULL);
ahd_outw(ahd, WAITING_TID_TAIL, SCB_LIST_NULL);
+ ahd_outw(ahd, MK_MESSAGE_SCB, SCB_LIST_NULL);
+ ahd_outw(ahd, MK_MESSAGE_SCSIID, 0xFF);
for (i = 0; i < AHD_NUM_TARGETS; i++)
ahd_outw(ahd, WAITING_SCB_TAILS + (2 * i), SCB_LIST_NULL);
@@ -7191,6 +7227,7 @@ ahd_search_qinfifo(struct ahd_softc *ahd, int target, char channel,
ahd_search_action action)
{
struct scb *scb;
+ struct scb *mk_msg_scb;
struct scb *prev_scb;
ahd_mode_state saved_modes;
u_int qinstart;
@@ -7199,6 +7236,7 @@ ahd_search_qinfifo(struct ahd_softc *ahd, int target, char channel,
u_int tid_next;
u_int tid_prev;
u_int scbid;
+ u_int seq_flags2;
u_int savedscbptr;
uint32_t busaddr;
int found;
@@ -7287,21 +7325,24 @@ ahd_search_qinfifo(struct ahd_softc *ahd, int target, char channel,
* looking for matches.
*/
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
+ seq_flags2 = ahd_inb(ahd, SEQ_FLAGS2);
+ if ((seq_flags2 & PENDING_MK_MESSAGE) != 0) {
+ scbid = ahd_inw(ahd, MK_MESSAGE_SCB);
+ mk_msg_scb = ahd_lookup_scb(ahd, scbid);
+ } else
+ mk_msg_scb = NULL;
savedscbptr = ahd_get_scbptr(ahd);
tid_next = ahd_inw(ahd, WAITING_TID_HEAD);
tid_prev = SCB_LIST_NULL;
targets = 0;
for (scbid = tid_next; !SCBID_IS_NULL(scbid); scbid = tid_next) {
u_int tid_head;
+ u_int tid_tail;
- /*
- * We limit based on the number of SCBs since
- * MK_MESSAGE SCBs are not in the per-tid lists.
- */
targets++;
- if (targets > AHD_SCB_MAX) {
+ if (targets > AHD_NUM_TARGETS)
panic("TID LIST LOOP");
- }
+
if (scbid >= ahd->scb_data.numscbs) {
printf("%s: Waiting TID List inconsistency. "
"SCB index == 0x%x, yet numscbs == 0x%x.",
@@ -7331,8 +7372,71 @@ ahd_search_qinfifo(struct ahd_softc *ahd, int target, char channel,
tid_head = scbid;
found += ahd_search_scb_list(ahd, target, channel,
lun, tag, role, status,
- action, &tid_head,
+ action, &tid_head, &tid_tail,
SCB_GET_TARGET(ahd, scb));
+ /*
+ * Check any MK_MESSAGE SCB that is still waiting to
+ * enter this target's waiting for selection queue.
+ */
+ if (mk_msg_scb != NULL
+ && ahd_match_scb(ahd, mk_msg_scb, target, channel,
+ lun, tag, role)) {
+
+ /*
+ * We found an scb that needs to be acted on.
+ */
+ found++;
+ switch (action) {
+ case SEARCH_COMPLETE:
+ if ((mk_msg_scb->flags & SCB_ACTIVE) == 0)
+ printf("Inactive SCB pending MK_MSG\n");
+ ahd_done_with_status(ahd, mk_msg_scb, status);
+ /* FALLTHROUGH */
+ case SEARCH_REMOVE:
+ {
+ u_int tail_offset;
+
+ printf("Removing MK_MSG scb\n");
+
+ /*
+ * Reset our tail to the tail of the
+ * main per-target list.
+ */
+ tail_offset = WAITING_SCB_TAILS
+ + (2 * SCB_GET_TARGET(ahd, mk_msg_scb));
+ ahd_outw(ahd, tail_offset, tid_tail);
+
+ seq_flags2 &= ~PENDING_MK_MESSAGE;
+ ahd_outb(ahd, SEQ_FLAGS2, seq_flags2);
+ ahd_outw(ahd, CMDS_PENDING,
+ ahd_inw(ahd, CMDS_PENDING)-1);
+ mk_msg_scb = NULL;
+ break;
+ }
+ case SEARCH_PRINT:
+ printf(" 0x%x", SCB_GET_TAG(scb));
+ /* FALLTHROUGH */
+ case SEARCH_COUNT:
+ break;
+ }
+ }
+
+ if (mk_msg_scb != NULL
+ && SCBID_IS_NULL(tid_head)
+ && ahd_match_scb(ahd, scb, target, channel, CAM_LUN_WILDCARD,
+ SCB_LIST_NULL, ROLE_UNKNOWN)) {
+
+ /*
+ * When removing the last SCB for a target
+ * queue with a pending MK_MESSAGE scb, we
+ * must queue the MK_MESSAGE scb.
+ */
+ printf("Queueing mk_msg_scb\n");
+ tid_head = ahd_inw(ahd, MK_MESSAGE_SCB);
+ seq_flags2 &= ~PENDING_MK_MESSAGE;
+ ahd_outb(ahd, SEQ_FLAGS2, seq_flags2);
+ mk_msg_scb = NULL;
+ }
if (tid_head != scbid)
ahd_stitch_tid_list(ahd, tid_prev, tid_head, tid_next);
if (!SCBID_IS_NULL(tid_head))
@@ -7350,7 +7454,8 @@ ahd_search_qinfifo(struct ahd_softc *ahd, int target, char channel,
int
ahd_search_scb_list(struct ahd_softc *ahd, int target, char channel,
int lun, u_int tag, role_t role, uint32_t status,
- ahd_search_action action, u_int *list_head, u_int tid)
+ ahd_search_action action, u_int *list_head,
+ u_int *list_tail, u_int tid)
{
struct scb *scb;
u_int scbid;
@@ -7362,6 +7467,7 @@ ahd_search_scb_list(struct ahd_softc *ahd, int target, char channel,
found = 0;
prev = SCB_LIST_NULL;
next = *list_head;
+ *list_tail = SCB_LIST_NULL;
for (scbid = next; !SCBID_IS_NULL(scbid); scbid = next) {
if (scbid >= ahd->scb_data.numscbs) {
printf("%s:SCB List inconsistency. "
@@ -7377,6 +7483,7 @@ ahd_search_scb_list(struct ahd_softc *ahd, int target, char channel,
panic("Waiting List traversal\n");
}
ahd_set_scbptr(ahd, scbid);
+ *list_tail = scbid;
next = ahd_inw_scbram(ahd, SCB_NEXT);
if (ahd_match_scb(ahd, scb, target, channel,
lun, SCB_LIST_NULL, role) == 0) {
@@ -7461,9 +7568,11 @@ ahd_rem_wscb(struct ahd_softc *ahd, u_int scbid,
}
/*
- * SCBs that had MK_MESSAGE set in them will not
- * be queued to the per-target lists, so don't
- * blindly clear the tail pointer.
+ * SCBs that have MK_MESSAGE set in them may
+ * cause the tail pointer to be updated without
+ * setting the next pointer of the previous tail.
+ * Only clear the tail if the removed SCB was
+ * the tail.
*/
tail_offset = WAITING_SCB_TAILS + (2 * tid);
if (SCBID_IS_NULL(next)
@@ -8730,6 +8839,9 @@ ahd_dump_card_state(struct ahd_softc *ahd)
* Mode independent registers.
*/
cur_col = 0;
+ ahd_intstat_print(ahd_inb(ahd, INTSTAT), &cur_col, 50);
+ ahd_seloid_print(ahd_inb(ahd, SELOID), &cur_col, 50);
+ ahd_selid_print(ahd_inb(ahd, SELID), &cur_col, 50);
ahd_hs_mailbox_print(ahd_inb(ahd, LOCAL_HS_MAILBOX), &cur_col, 50);
ahd_intctl_print(ahd_inb(ahd, INTCTL), &cur_col, 50);
ahd_seqintstat_print(ahd_inb(ahd, SEQINTSTAT), &cur_col, 50);
@@ -8745,6 +8857,12 @@ ahd_dump_card_state(struct ahd_softc *ahd)
ahd_seqintctl_print(ahd_inb(ahd, SEQINTCTL), &cur_col, 50);
ahd_seq_flags_print(ahd_inb(ahd, SEQ_FLAGS), &cur_col, 50);
ahd_seq_flags2_print(ahd_inb(ahd, SEQ_FLAGS2), &cur_col, 50);
+ ahd_qfreeze_count_print(ahd_inw(ahd, QFREEZE_COUNT), &cur_col, 50);
+ ahd_kernel_qfreeze_count_print(ahd_inw(ahd, KERNEL_QFREEZE_COUNT),
+ &cur_col, 50);
+ ahd_mk_message_scb_print(ahd_inw(ahd, MK_MESSAGE_SCB), &cur_col, 50);
+ ahd_mk_message_scsiid_print(ahd_inb(ahd, MK_MESSAGE_SCSIID),
+ &cur_col, 50);
ahd_sstat0_print(ahd_inb(ahd, SSTAT0), &cur_col, 50);
ahd_sstat1_print(ahd_inb(ahd, SSTAT1), &cur_col, 50);
ahd_sstat2_print(ahd_inb(ahd, SSTAT2), &cur_col, 50);
@@ -8852,7 +8970,7 @@ ahd_dump_card_state(struct ahd_softc *ahd)
ahd_set_modes(ahd, AHD_MODE_DFF0 + i, AHD_MODE_DFF0 + i);
fifo_scbptr = ahd_get_scbptr(ahd);
- printf("\n%s: FIFO%d %s, LONGJMP == 0x%x, SCB 0x%x\n",
+ printf("\n\n%s: FIFO%d %s, LONGJMP == 0x%x, SCB 0x%x\n",
ahd_name(ahd), i,
(dffstat & (FIFO0FREE << i)) ? "Free" : "Active",
ahd_inw(ahd, LONGJMP_ADDR), fifo_scbptr);
@@ -8907,6 +9025,9 @@ ahd_dump_card_state(struct ahd_softc *ahd)
printf("%s: OS_SPACE_CNT = 0x%x MAXCMDCNT = 0x%x\n",
ahd_name(ahd), ahd_inb(ahd, OS_SPACE_CNT),
ahd_inb(ahd, MAXCMDCNT));
+ printf("%s: SAVED_SCSIID = 0x%x SAVED_LUN = 0x%x\n",
+ ahd_name(ahd), ahd_inb(ahd, SAVED_SCSIID),
+ ahd_inb(ahd, SAVED_LUN));
ahd_simode0_print(ahd_inb(ahd, SIMODE0), &cur_col, 50);
printf("\n");
ahd_set_modes(ahd, AHD_MODE_CCHAN, AHD_MODE_CCHAN);
@@ -9000,6 +9121,11 @@ ahd_timeout(void *arg)
* interrupt handler has yet to see.
*/
was_paused = ahd_is_paused(ahd);
+
+ printf("%s: Recovery Initiated - Card was %spaused\n", ahd_name(ahd),
+ was_paused ? "" : "not ");
+ ahd_dump_card_state(ahd);
+
ahd_pause_and_flushwork(ahd);
if ((scb->flags & SCB_ACTIVE) == 0) {
@@ -9018,12 +9144,6 @@ ahd_timeout(void *arg)
return;
}
- printf("%s: Recovery Initiated - Card was %spaused\n", ahd_name(ahd),
- was_paused ? "" : "not ");
- ahd_dump_card_state(ahd);
- ahd_reset_channel(ahd, SCSI_CHANNEL(ahd, sim), /*initiate reset*/TRUE);
- ahd_unlock(ahd, &s);
- return;
#if 0
/*
* Determine identity of SCB acting on the bus.
@@ -9218,10 +9338,9 @@ bus_reset:
LIST_REMOVE(scb, timedout_links);
scb->flags &= ~SCB_TIMEDOUT;
}
-
+#endif
ahd_unpause(ahd);
ahd_unlock(ahd, &s);
-#endif
}
#if 0