summaryrefslogtreecommitdiffstats
path: root/sys
diff options
context:
space:
mode:
authorgibbs <gibbs@FreeBSD.org>1995-07-31 08:25:36 +0000
committergibbs <gibbs@FreeBSD.org>1995-07-31 08:25:36 +0000
commit1214917908a1e5a327d11ab75cf2bf387d14abb9 (patch)
treeaf649b1ceed8f17df419550f1563104e3d4edd40 /sys
parent8c18a11f08a7dd6d1c7c168f948e2ad4f2b6f085 (diff)
downloadFreeBSD-src-1214917908a1e5a327d11ab75cf2bf387d14abb9.zip
FreeBSD-src-1214917908a1e5a327d11ab75cf2bf387d14abb9.tar.gz
Long overdue, more complete, reset code. These changes implement a
BUS DEVICE RESET followed by BUS RESET failure recovery strategy including the necesary renegotiation of sync/wide transfers after recovery completes. Clean up debugging code to make it more finely selectable. Reset code debugging is enabled for now so I can get more feedback on how this code behaves in real life.
Diffstat (limited to 'sys')
-rw-r--r--sys/i386/scsi/aic7xxx.c772
1 files changed, 592 insertions, 180 deletions
diff --git a/sys/i386/scsi/aic7xxx.c b/sys/i386/scsi/aic7xxx.c
index ebbd7dd..ec4e987 100644
--- a/sys/i386/scsi/aic7xxx.c
+++ b/sys/i386/scsi/aic7xxx.c
@@ -24,17 +24,12 @@
*
* commenced: Sun Sep 27 18:14:01 PDT 1992
*
- * $Id: aic7xxx.c,v 1.31 1995/07/08 22:09:11 ats Exp $
+ * $Id: aic7xxx.c,v 1.32 1995/07/17 23:35:16 gibbs Exp $
*/
/*
* TODO:
- * Add target reset capabilities
* Implement Target Mode
*
- * This driver is very stable, and seems to offer performance
- * comprable to the 1742 FreeBSD driver. I have not experienced
- * any timeouts since the timeout code was written, so in that
- * sense, it is untested.
*/
#include <sys/param.h>
@@ -58,6 +53,7 @@
#define KVTOPHYS(x) vtophys(x)
#define MIN(a,b) ((a < b) ? a : b)
+#define ALL_TARGETS -1
struct ahc_data *ahcdata[NAHC];
@@ -68,8 +64,17 @@ timeout_t ahc_timeout;
void ahc_done();
struct scb *ahc_get_scb __P((int unit, int flags));
void ahc_free_scb();
-void ahc_abort_scb __P((int unit, struct ahc_data *ahc, struct scb *scb));
+void ahc_scb_timeout __P((int unit, struct ahc_data *ahc, struct scb *scb));
+u_char ahc_abort_wscb __P((int unit, struct scb *scbp, u_char prev,
+ u_long iobase, u_char timedout_scb, u_int32 xs_error));
+int ahc_match_scb __P((struct scb *scb, int target, char channel));
+int ahc_reset_device __P((int unit, struct ahc_data *ahc, int target,
+ char channel, u_char timedout_scb, u_int32 xs_error));
+void ahc_reset_current_bus __P((u_long iobase));
+int ahc_reset_channel __P((int unit, struct ahc_data *ahc, char channel,
+ u_char timedout_scb, u_int32 xs_error));
void ahcminphys();
+void ahc_unbusy_target __P((int target, char channel, u_long iobase));
struct scb *ahc_scb_phys_kv();
int ahc_poll __P((int unit, int wait));
u_int32 ahc_adapter_info();
@@ -77,16 +82,24 @@ u_int32 ahc_adapter_info();
int ahc_unit = 0;
/* Different debugging levels */
-#define AHC_SHOWMISC 0x0001
-#define AHC_SHOWCMDS 0x0002
-#define AHC_SHOWSCBS 0x0004
-/* #define AHC_DEBUG */
-int ahc_debug = AHC_SHOWMISC;
+#define AHC_SHOWMISC 0x0001
+#define AHC_SHOWCMDS 0x0002
+#define AHC_SHOWSCBS 0x0004
+#define AHC_SHOWABORTS 0x0008
+#define AHC_SHOWSENSE 0x0010
+#define AHC_DEBUG
+int ahc_debug = AHC_SHOWABORTS;
/**** bit definitions for SCSIDEF ****/
#define HSCSIID 0x07 /* our SCSI ID */
#define HWSCSIID 0x0f /* our SCSI ID if Wide Bus */
+typedef enum {
+ list_head,
+ list_second,
+ list_tail
+}insert_t;
+
struct scsi_adapter ahc_switch =
{
ahc_scsi_cmd,
@@ -409,6 +422,7 @@ struct scsi_device ahc_dev =
#define RESIDUAL 0x80
#define ABORT_TAG 0x90
#define AWAITING_MSG 0xa0
+#define IMMEDDONE 0xb0
#define BRKADRINT 0x08
#define SCSIINT 0x04
#define CMDCMPLT 0x02
@@ -583,8 +597,8 @@ struct scsi_device ahc_dev =
#define BIOSMODE 0x30
#define BIOSDISABLED 0x30
-#define MSG_ABORT 0x06
-#define MSG_BUS_DEVICE_RESET 0x0c
+#define MSG_ABORT 0x06
+#define MSG_BUS_DEVICE_RESET 0x0c
#define BUS_8_BIT 0x00
#define BUS_16_BIT 0x01
#define BUS_32_BIT 0x02
@@ -833,6 +847,7 @@ void ahc_scsirate(scsirate, period, offset, unit, target )
printf("ahc%d: target %d synchronous at %sMB/s, "
"offset = 0x%x\n", unit, target,
ahc_syncrates[i].rate, offset );
+/* XXX Conditional on bootverbose??? */
#ifdef AHC_DEBUG
#endif /* AHC_DEBUG */
return;
@@ -910,6 +925,57 @@ void ahc_getscb(iobase, scb)
}
/*
+ * Add this SCB to the "waiting for selection" list.
+ */
+static
+void ahc_add_waiting_scb (iobase, scb, where)
+ u_long iobase;
+ struct scb *scb;
+ insert_t where;
+{
+ u_char head, tail;
+ u_char curscb;
+
+ curscb = inb(SCBPTR + iobase);
+ head = inb(WAITING_SCBH + iobase);
+ tail = inb(WAITING_SCBT + iobase);
+ if(head == SCB_LIST_NULL) {
+ /* List was empty */
+ head = scb->position;
+ tail = SCB_LIST_NULL;
+ }
+ else if (where == list_head) {
+ outb(SCBPTR+iobase, scb->position);
+ outb(SCBARRAY+iobase+30, head);
+ head = scb->position;
+ }
+ else if(tail == SCB_LIST_NULL) {
+ /* List had one element */
+ tail = scb->position;
+ outb(SCBPTR+iobase,head);
+ outb(SCBARRAY+iobase+30,
+ tail);
+ }
+ else if(where == list_second) {
+ u_char third_scb;
+ outb(SCBPTR+iobase, head);
+ third_scb = inb(SCBARRAY+iobase+30);
+ outb(SCBARRAY+iobase+30,scb->position);
+ outb(SCBPTR+iobase, scb->position);
+ outb(SCBARRAY+iobase+30,third_scb);
+ }
+ else {
+ outb(SCBPTR+iobase,tail);
+ tail = scb->position;
+ outb(SCBARRAY+iobase+30,
+ tail);
+ }
+ outb(WAITING_SCBH + iobase, head);
+ outb(WAITING_SCBT + iobase, tail);
+ outb(SCBPTR + iobase, curscb);
+}
+
+/*
* Catch an interrupt from the adaptor
*/
int
@@ -985,26 +1051,14 @@ ahcintr(unit)
break;
case NO_MATCH:
{
- u_char active;
- int active_port = HA_ACTIVE0 + iobase;
printf("ahc%d:%c:%d: no active SCB for "
"reconnecting target - "
"issuing ABORT\n", unit, channel,
target);
printf("SAVED_TCL == 0x%x\n",
inb(SAVED_TCL + iobase));
- if(targ_mask & 0xff00) {
- /*
- * targets on the Second channel or
- * above id 7 store info in byte two
- * of HA_ACTIVE
- */
- active_port++;
- }
- active = inb(active_port);
- active &= ~(0x01 << (target & 0x07));
+ ahc_unbusy_target(target, channel, iobase);
outb(SCBARRAY + iobase, SCB_NEEDDMA);
- outb(active_port, active);
outb(CLRSINT1 + iobase, CLRSELTIMEO);
RESTART_SEQUENCER(ahc);
break;
@@ -1155,7 +1209,7 @@ ahcintr(unit)
targ_scratch &= 0x7f;
ahc->needwdtr &= ~targ_mask;
ahc->wdtrpending &= ~targ_mask;
- printf("ahc%d:%c:%d: refusing "
+ printf("ahc%d:%c:%d: refuses "
"WIDE negotiation. Using "
"8bit transfers\n",
unit, channel, target);
@@ -1165,7 +1219,7 @@ ahcintr(unit)
targ_scratch &= 0xf0;
ahc->needsdtr &= ~targ_mask;
ahc->sdtrpending &= ~targ_mask;
- printf("ahc%d:%c:%d: refusing "
+ printf("ahc%d:%c:%d: refuses "
"syncronous negotiation. Using "
"asyncronous transfers\n",
unit, channel, target);
@@ -1220,7 +1274,8 @@ ahcintr(unit)
ahc_getscb(iobase, scb);
#ifdef AHC_DEBUG
- if(xs->sc_link->target == DEBUGTARG)
+ if((ahc_debug & AHC_SHOWSCBS)
+ && xs->sc_link->target == DEBUGTARG)
ahc_print_scb(scb);
#endif
xs->status = scb->target_status;
@@ -1231,22 +1286,26 @@ ahcintr(unit)
break;
case SCSI_CHECK:
#ifdef AHC_DEBUG
- sc_print_addr(xs->sc_link);
- printf("requests Check Status\n");
+ if(ahc_debug & AHC_SHOWSENSE)
+ {
+ sc_print_addr(xs->sc_link);
+ printf("requests Check Status\n");
+ }
#endif
if((xs->error == XS_NOERROR) &&
!(scb->flags & SCB_SENSE)) {
u_char control = scb->control;
- u_char head;
- u_char tail;
u_short active;
struct ahc_dma_seg *sg = scb->ahc_dma;
struct scsi_sense *sc = &(scb->sense_cmd);
u_char tcl = scb->target_channel_lun;
#ifdef AHC_DEBUG
- sc_print_addr(xs->sc_link);
- printf("Sending Sense\n");
+ if(ahc_debug & AHC_SHOWSENSE)
+ {
+ sc_print_addr(xs->sc_link);
+ printf("Sending Sense\n");
+ }
#endif
bzero(scb, SCB_DOWN_SIZE);
#ifdef NOT_YET
@@ -1284,38 +1343,14 @@ ahcintr(unit)
* commands if we happen to be doing
* tagged I/O.
*/
- active = inb(HA_ACTIVE0 + iobase)
+ active = inb(HA_ACTIVE0 + iobase)
| (inb(HA_ACTIVE1 + iobase) << 8);
active |= targ_mask;
outb(HA_ACTIVE0 + iobase,active & 0xff);
outb(HA_ACTIVE1 + iobase,
(active >> 8) & 0xff);
- /*
- * Add this SCB to the "waiting for
- * selection" list.
- */
- head = inb(WAITING_SCBH + iobase);
- tail = inb(WAITING_SCBT + iobase);
- if(head == SCB_LIST_NULL) {
- /* List was empty */
- head = scb->position;
- tail = SCB_LIST_NULL;
- }
- else if(tail == SCB_LIST_NULL) {
- /* List had one element */
- tail = scb->position;
- outb(SCBPTR+iobase,head);
- outb(SCBARRAY+iobase+30,
- tail);
- }
- else {
- outb(SCBPTR+iobase,tail);
- tail = scb->position;
- outb(SCBARRAY+iobase+30,
- tail);
- }
- outb(WAITING_SCBH + iobase, head);
- outb(WAITING_SCBT + iobase, tail);
+ ahc_add_waiting_scb(iobase, scb,
+ list_head);
outb(HA_RETURN_1 + iobase, SEND_SENSE);
break;
}
@@ -1370,11 +1405,13 @@ ahcintr(unit)
inb(iobase+SCBARRAY+15);
xs->flags |= SCSI_RESID_VALID;
#ifdef AHC_DEBUG
- sc_print_addr(xs->sc_link);
- printf("Handled Residual of %d bytes\n"
- "SG_COUNT == %d\n",
- scb->xs->resid,
- inb(SCBARRAY+18 + iobase));
+ if(ahc_debug & AHC_SHOWMISC) {
+ sc_print_addr(xs->sc_link);
+ printf("Handled Residual of %d bytes\n"
+ "SG_COUNT == %d\n",
+ scb->xs->resid,
+ inb(SCBARRAY+18 + iobase));
+ }
#endif
}
break;
@@ -1418,6 +1455,46 @@ ahcintr(unit)
"does not have a waiting message");
break;
}
+ case IMMEDDONE:
+ {
+ /*
+ * Take care of device reset messages
+ */
+ u_char scbindex = inb(SCBPTR + iobase);
+ scb = ahc->scbarray[scbindex];
+ if(scb->flags & SCB_DEVICE_RESET) {
+ u_char targ_scratch;
+ int found;
+ /*
+ * Go back to async/narrow transfers and
+ * renegotiate.
+ */
+ ahc_unbusy_target(target, channel, iobase);
+ ahc->needsdtr |= ahc->needsdtr_orig & targ_mask;
+ ahc->needwdtr |= ahc->needwdtr_orig & targ_mask;
+ ahc->sdtrpending &= ~targ_mask;
+ ahc->wdtrpending &= ~targ_mask;
+ targ_scratch = inb(HA_TARG_SCRATCH + iobase
+ + scratch_offset);
+ targ_scratch &= SXFR;
+ outb(HA_TARG_SCRATCH + iobase + scratch_offset,
+ targ_scratch);
+ found = ahc_reset_device(unit, ahc, target,
+ channel, SCB_LIST_NULL,
+ XS_NOERROR);
+#ifdef AHC_DEBUG
+ if(ahc_debug & AHC_SHOWABORTS) {
+ sc_print_addr(scb->xs->sc_link);
+ printf("Bus Device Reset delivered. "
+ "%d SCBs aborted\n", found);
+ }
+#endif
+ }
+ else
+ panic("ahcintr: Immediate complete for "
+ "unknown operation.");
+ break;
+ }
default:
printf("ahc: seqint, "
"intstat == 0x%x, scsisigi = 0x%x\n",
@@ -1459,10 +1536,8 @@ clear:
xs = scb->xs;
if (status & SELTO) {
- u_char active;
u_char waiting;
u_char flags;
- u_long active_port = HA_ACTIVE0 + iobase;
outb(SCSISEQ + iobase, ENRSELI);
xs->error = XS_TIMEOUT;
/*
@@ -1472,13 +1547,10 @@ clear:
flags = inb( HA_FLAGS + iobase );
outb(HA_FLAGS + iobase,
flags & ~ACTIVE_MSG);
-
- if (scb->target_channel_lun & 0x88)
- active_port++;
-
- active = inb(active_port) &
- ~(0x01 << (xs->sc_link->target & 0x07));
- outb(active_port, active);
+ ahc_unbusy_target(xs->sc_link->target,
+ ((long)xs->sc_link->fordriver & SELBUSB)
+ ? 'B' : 'A',
+ iobase);
outb(SCBARRAY + iobase, SCB_NEEDDMA);
@@ -1664,9 +1736,9 @@ ahc_init(unit)
*/
#ifdef AHC_DEBUG
- printf("ahc%d: scb %d bytes; SCB_SIZE %d bytes, ahc_dma %d bytes\n",
- unit, sizeof(struct scb), SCB_DOWN_SIZE,
- sizeof(struct ahc_dma_seg));
+ if(ahc_debug & AHC_SHOWMISC)
+ printf("ahc%d: scb %d bytes; ahc_dma %d bytes\n",
+ unit, sizeof(struct scb), sizeof(struct ahc_dma_seg));
#endif /* AHC_DEBUG */
printf("ahc%d: reading board settings\n", unit);
@@ -1893,6 +1965,12 @@ ahc_init(unit)
scsi_conf = inb(HA_SCSICONF + 1 + iobase) & (ENSPCHK|STIMESEL);
outb(SXFRCTL1 + iobase, scsi_conf|ENSTIMER|ACTNEGEN|STPWEN);
outb(SIMODE1 + iobase, ENSELTIMO|ENSCSIPERR);
+
+ /* Reset the bus */
+ outb(SCSISEQ + iobase, SCSIRSTO);
+ DELAY(10000);
+ outb(SCSISEQ + iobase, 0);
+
/* Select Channel A */
outb(SBLKCTL + iobase, 0);
}
@@ -1901,6 +1979,11 @@ ahc_init(unit)
outb(SXFRCTL1 + iobase, scsi_conf|ENSTIMER|ACTNEGEN|STPWEN);
outb(SIMODE1 + iobase, ENSELTIMO|ENSCSIPERR);
+ /* Reset the bus */
+ outb(SCSISEQ + iobase, SCSIRSTO);
+ DELAY(10000);
+ outb(SCSISEQ + iobase, 0);
+
/*
* Look at the information that board initialization or
* the board bios has left us. In the lower four bits of each
@@ -1989,8 +2072,10 @@ ahc_init(unit)
}
#ifdef AHC_DEBUG
- printf("NEEDSDTR == 0x%x\nNEEDWDTR == 0x%x\nDISCENABLE == 0x%x\n",
- ahc->needsdtr, ahc->needwdtr, ahc->discenable);
+ if(ahc_debug & AHC_SHOWMISC)
+ printf("NEEDSDTR == 0x%x\nNEEDWDTR == 0x%x\n"
+ "DISCENABLE == 0x%x\n", ahc->needsdtr,
+ ahc->needwdtr, ahc->discenable);
#endif
/*
* Set the number of availible SCBs
@@ -2018,11 +2103,6 @@ ahc_init(unit)
if (!(ahc->type & AHC_AIC78X0))
outb(BCTL + iobase, ENABLE);
- /* Reset the bus */
- outb(SCSISEQ + iobase, SCSIRSTO);
- DELAY(10000);
- outb(SCSISEQ + iobase, 0);
-
UNPAUSE_SEQUENCER(ahc);
/*
@@ -2209,7 +2289,7 @@ ahc_scsi_cmd(xs)
* Usually return SUCCESSFULLY QUEUED
*/
#ifdef AHC_DEBUG
- if(xs->sc_link->target == DEBUGTARG)
+ if((ahc_debug & AHC_SHOWSCBS) && (xs->sc_link->target == DEBUGTARG))
ahc_print_scb(scb);
#endif
if (!(flags & SCSI_NOMASK)) {
@@ -2230,7 +2310,7 @@ ahc_scsi_cmd(xs)
if (!(xs->flags & SCSI_SILENT))
printf("cmd fail\n");
printf("cmd fail\n");
- ahc_abort_scb(unit,ahc,scb);
+ ahc_scb_timeout(unit,ahc,scb);
return (HAD_ERROR);
}
} while (!(xs->flags & ITSDONE)); /* a non command complete intr */
@@ -2363,7 +2443,8 @@ ahc_get_scb(unit, flags)
scbp->flags = SCB_ACTIVE;
#ifdef AHC_DEBUG
ahc->activescbs++;
- if( ahc->activescbs == ahc->maxscbs )
+ if((ahc_debug & AHC_SHOWMISC)
+ && (ahc->activescbs == ahc->maxscbs))
printf("ahc%d: Max SCBs active\n", unit);
#endif
}
@@ -2416,115 +2497,156 @@ ahc_poll(int unit, int wait)
}
void
-ahc_abort_scb( unit, ahc, scb )
+ahc_scb_timeout(unit, ahc, scb)
int unit;
struct ahc_data *ahc;
struct scb *scb;
{
u_long iobase = ahc->baseport;
int found = 0;
- int active_scb;
- u_char flags;
u_char scb_control;
+ char channel = scb->target_channel_lun & SELBUSB ? 'B': 'A';
- PAUSE_SEQUENCER(ahc);
/*
- * Case 1: In the QINFIFO
+ * Ensure that the card doesn't do anything
+ * behind our back.
*/
- {
- int saved_queue[AHC_SCB_MAX];
- int i;
- int queued = inb(QINCNT + iobase);
-
- for( i = 0; i < (queued - found); i++){
- saved_queue[i] = inb(QINFIFO + iobase);
- if( saved_queue[i] == scb->position ){
- i--;
- found = 1;
- }
- }
- /* Re-insert entries back into the queue */
- for( queued = 0; queued < i; queued++ )
- outb(QINFIFO + iobase, saved_queue[queued]);
-
- if( found ){
- goto done;
- }
- }
+ PAUSE_SEQUENCER(ahc);
- active_scb = inb(SCBPTR + iobase);
/*
- * Case 2: Not the active command
+ * First, determine if we want to do a bus
+ * reset or simply a bus device reset.
+ * If this is the first time that a transaction
+ * has timed out, just schedule a bus device
+ * reset. Otherwise, we reset the bus and
+ * abort all pending I/Os on that bus.
*/
- if( active_scb != scb->position ){
+ if(scb->flags & SCB_ABORTED)
+ {
/*
- * Select the SCB we want to abort
- * and turn off the disconnected bit.
- * the driver will then abort the command
- * and notify us of the abort.
+ * Been down this road before.
+ * Do a full bus reset.
*/
- outb(SCBPTR + iobase, scb->position);
- scb_control = inb(SCBARRAY + iobase);
- scb_control &= ~SCB_DIS;
- outb(SCBARRAY + iobase, scb_control);
- outb(SCBPTR + iobase, active_scb);
- goto done;
- }
- scb_control = inb(SCBARRAY + iobase);
- if( scb_control & SCB_DIS ) {
- scb_control &= ~SCB_DIS;
- outb(SCBARRAY + iobase, scb_control);
- goto done;
+ found = ahc_reset_channel(unit, ahc, channel, scb->position,
+ XS_TIMEOUT);
+#ifdef AHC_DEBUG
+ if(ahc_debug & AHC_SHOWABORTS)
+ printf("ahc%d: Issued Channel %c Bus Reset #1. "
+ "%d SCBs aborted\n", unit, channel, found);
+#endif
}
- /*
- * Case 3: Currently active command
- */
- if ( (flags = inb(HA_FLAGS + iobase)) & ACTIVE_MSG) {
+ else {
/*
- * If there's a message in progress,
- * reset the bus and have all devices renegotiate.
+ * Send a Bus Device Reset Message:
+ * The target we select to send the message to may
+ * be entirely different than the target pointed to
+ * by the scb that timed out. If the command is
+ * in the QINFIFO or the waiting for selection list,
+ * its not tying up the bus and isn't responsible
+ * for the delay so we pick off the active command
+ * which should be the SCB selected by SCBPTR. If
+ * its disconnected or active, we device reset the
+ * target scbp points to. Although it may be that
+ * this target is not responsible for the delay, it
+ * may also be that we're timing out on a command that
+ * just takes too much time, so we try the bus device
+ * reset there first.
*/
- if(scb->target_channel_lun & 0x08){
- ahc->needsdtr |= (ahc->needsdtr_orig & 0xff00);
- ahc->sdtrpending &= 0x00ff;
- outb(HA_ACTIVE1 + iobase, 0);
+ u_char active_scb, control;
+ struct scb *active_scbp;
+ active_scb = inb(SCBPTR + iobase);
+ active_scbp = ahc->scbarray[active_scb];
+ control = inb(SCBARRAY + iobase);
+
+ /* Test to see if scbp is disconnected */
+ outb(SCBPTR + iobase, scb->position);
+ if(inb(SCBARRAY + iobase) & SCB_DIS) {
+ scb->flags |= SCB_DEVICE_RESET|SCB_ABORTED;
+ scb->SG_segment_count = 0;
+ scb->SG_list_pointer = 0;
+ scb->data = 0;
+ scb->datalen[0] = 0;
+ scb->datalen[1] = 0;
+ scb->datalen[2] = 0;
+ outb(SCBCNT + iobase, 0x80);
+ outsb(SCBARRAY+iobase,scb,SCB_DOWN_SIZE);
+ outb(SCBCNT + iobase, 0);
+ ahc_add_waiting_scb(iobase, scb, list_second);
+ timeout(ahc_timeout, (caddr_t)active_scbp,
+ (2 * hz) / 1000);
+#ifdef AHC_DEBUG
+ if(ahc_debug & AHC_SHOWABORTS) {
+ sc_print_addr(scb->xs->sc_link);
+ printf("BUS DEVICE RESET message queued.\n");
+ }
+#endif
+ UNPAUSE_SEQUENCER(ahc);
}
- else if (ahc->type & AHC_WIDE){
- ahc->needsdtr = ahc->needsdtr_orig;
- ahc->needwdtr = ahc->needwdtr_orig;
- ahc->sdtrpending = 0;
- ahc->wdtrpending = 0;
- outb(HA_ACTIVE0 + iobase, 0);
- outb(HA_ACTIVE1 + iobase, 0);
+ /* Is the active SCB really active? */
+ else if((active_scbp->flags & SCB_ACTIVE)
+ && (control & SCB_NEEDDMA) == SCB_NEEDDMA) {
+
+ u_char flags = inb(HA_FLAGS + iobase);
+ if(flags & ACTIVE_MSG) {
+ /*
+ * If we're in a message phase, tacking on
+ * another message may confuse the target totally.
+ * The bus is probably wedged, so reset the
+ * channel.
+ */
+ channel = (active_scbp->target_channel_lun & SELBUSB)
+ ? 'B': 'A';
+ ahc_reset_channel(unit, ahc, channel, scb->position,
+ XS_TIMEOUT);
+#ifdef AHC_DEBUG
+ if(ahc_debug & AHC_SHOWABORTS)
+ printf("ahc%d: Issued Channel %c Bus Reset #2. "
+ "%d SCBs aborted\n", unit, channel,
+ found);
+#endif
+ }
+ else {
+ /*
+ * Load the message buffer and assert attention.
+ */
+ active_scbp->flags |= SCB_DEVICE_RESET|SCB_ABORTED;
+ if(active_scbp != scb)
+ untimeout(ahc_timeout, (caddr_t)active_scbp);
+ timeout(ahc_timeout, (caddr_t)active_scbp,
+ (2 * hz) / 1000);
+ outb(HA_FLAGS + iobase, flags | ACTIVE_MSG);
+ outb(HA_MSG_LEN + iobase, 1);
+ outb(HA_MSG_START + iobase, MSG_BUS_DEVICE_RESET);
+ if(active_scbp->target_channel_lun
+ != scb->target_channel_lun) {
+ /* Give scb a new lease on life */
+ timeout(ahc_timeout, (caddr_t)scb,
+ (scb->xs->timeout * hz) / 1000);
+ }
+#ifdef AHC_DEBUG
+ if(ahc_debug & AHC_SHOWABORTS) {
+ sc_print_addr(active_scbp->xs->sc_link);
+ printf("BUS DEVICE RESET message queued.\n");
+ }
+#endif
+ UNPAUSE_SEQUENCER(ahc);
+ }
}
- else{
- ahc->needsdtr |= (ahc->needsdtr_orig & 0x00ff);
- ahc->sdtrpending &= 0xff00;
- outb(HA_ACTIVE0 + iobase, 0);
+ else {
+ /*
+ * No active command to single out, so reset
+ * the bus for the timed out target.
+ */
+ ahc_reset_channel(unit, ahc, channel, scb->position,
+ XS_TIMEOUT);
+#ifdef AHC_DEBUG
+ if(ahc_debug & AHC_SHOWABORTS)
+ printf("ahc%d: Issued Channel %c Bus Reset #3. "
+ "%d SCBs aborted\n", unit, channel,
+ found);
+#endif
}
-
- /* Reset the bus */
- outb(SCSISEQ + iobase, SCSIRSTO);
- DELAY(1000);
- outb(SCSISEQ + iobase, 0);
- goto done;
- }
-
- /*
- * Otherwise, set up an abort message and have the sequencer
- * clean up
- */
- outb(HA_FLAGS + iobase, flags | ACTIVE_MSG);
- outb(HA_MSG_LEN + iobase, 1);
- outb(HA_MSG_START + iobase, MSG_ABORT);
-
- outb(SCSISIGO + iobase, inb(HA_SIGSTATE + iobase) | 0x10);
-
-done:
- scb->flags |= SCB_ABORTED;
- UNPAUSE_SEQUENCER(ahc);
- ahc_done(unit, scb);
- return;
+ }
}
void
@@ -2560,8 +2682,298 @@ ahc_timeout(void *arg1)
return;
}
/* abort the operation that has timed out */
- printf("\n");
- ahc_abort_scb( unit, ahc, scb );
+ ahc_scb_timeout( unit, ahc, scb );
splx(s);
}
+
+/*
+ * The device at the given target/channel has been reset. Abort
+ * all active and queued scbs for that target/channel.
+ */
+int
+ahc_reset_device(unit, ahc, target, channel, timedout_scb, xs_error)
+ int unit;
+ struct ahc_data *ahc;
+ int target;
+ char channel;
+ u_char timedout_scb;
+ u_int32 xs_error;
+{
+ u_long iobase = ahc->baseport;
+ struct scb *scbp;
+ u_char active_scb;
+ int i = 0;
+ int found = 0;
+
+ /* restore this when we're done */
+ active_scb = inb(SCBPTR + iobase);
+
+ /*
+ * Search the QINFIFO.
+ */
+ {
+ int saved_queue[AHC_SCB_MAX];
+ int queued = inb(QINCNT + iobase);
+
+ for (i = 0; i < (queued - found); i++) {
+ saved_queue[i] = inb(QINFIFO + iobase);
+ scbp = ahc->scbarray[saved_queue[i]];
+ if (ahc_match_scb (scbp, target, channel)){
+ /*
+ * We found an scb that needs to be aborted.
+ */
+ scbp->flags |= SCB_ABORTED;
+ scbp->xs->error |= xs_error;
+ if(scbp->position != timedout_scb)
+ untimeout(ahc_timeout, (caddr_t)scbp);
+ ahc_done (scbp);
+ outb(SCBPTR + iobase, scbp->position);
+ outb(SCBARRAY + iobase, SCB_NEEDDMA);
+ i--;
+ found++;
+ }
+ }
+ /* Now put the saved scbs back. */
+ for (queued = 0; queued < i; queued++) {
+ outb (QINFIFO + iobase, saved_queue[queued]);
+ }
+ }
+
+ /*
+ * Search waiting for selection list.
+ */
+ {
+ u_char next, prev;
+
+ next = inb(WAITING_SCBH + iobase); /* Start at head of list. */
+ prev = SCB_LIST_NULL;
+
+ while (next != SCB_LIST_NULL) {
+ scbp = ahc->scbarray[next];
+ /*
+ * Select the SCB.
+ */
+ if (ahc_match_scb(scbp, target, channel)) {
+ next = ahc_abort_wscb(unit, scbp, prev,
+ iobase, timedout_scb, xs_error);
+ found++;
+ }
+ else {
+ outb(SCBPTR + iobase, scbp->position);
+ prev = next;
+ next = inb(SCBARRAY + iobase + 30);
+ }
+ }
+ }
+ /*
+ * Go through the entire SCB array now and look for
+ * commands for this target that are active. These
+ * are other (most likely tagged) commands that
+ * were disconnected when the reset occured.
+ */
+ for(i = 0; i < ahc->numscbs; i++) {
+ scbp = ahc->scbarray[i];
+ if((scbp->flags & SCB_ACTIVE)
+ && ahc_match_scb(scbp, target, channel)) {
+ /* Ensure the target is "free" */
+ ahc_unbusy_target(target, channel, iobase);
+ outb(SCBPTR + iobase, scbp->position);
+ outb(SCBARRAY + iobase, SCB_NEEDDMA);
+ scbp->flags |= SCB_ABORTED;
+ scbp->xs->error |= xs_error;
+ if(scbp->position != timedout_scb)
+ untimeout(ahc_timeout, (caddr_t)scbp);
+ ahc_done (unit, scbp);
+ found++;
+ }
+ }
+ outb(SCBPTR + iobase, active_scb);
+ return found;
+}
+
+/*
+ * Manipulate the waiting for selection list and return the
+ * scb that follows the one that we remove.
+ */
+u_char
+ahc_abort_wscb (unit, scbp, prev, iobase, timedout_scb, xs_error)
+ int unit;
+ struct scb *scbp;
+ u_char prev;
+ u_long iobase;
+ u_char timedout_scb;
+ u_int32 xs_error;
+{
+ u_char curscbp, next;
+ int target = ((scbp->target_channel_lun >> 4) & 0x0f);
+ char channel = (scbp->target_channel_lun & SELBUSB) ? 'B' : 'A';
+ /*
+ * Select the SCB we want to abort and
+ * pull the next pointer out of it.
+ */
+ curscbp = inb(SCBPTR + iobase);
+ outb(SCBPTR + iobase, scbp->position);
+ next = inb(SCBARRAY + iobase + 30);
+
+ /* Clear the necessary fields */
+ outb(SCBARRAY + iobase, SCB_NEEDDMA);
+ outb(SCBARRAY + iobase + 30, SCB_LIST_NULL);
+ ahc_unbusy_target(target, channel, iobase);
+
+ /* update the waiting list */
+ if( prev == SCB_LIST_NULL )
+ /* First in the list */
+ outb(WAITING_SCBH + iobase, next);
+ else {
+ /*
+ * Select the scb that pointed to us
+ * and update its next pointer.
+ */
+ outb(SCBPTR + iobase, prev);
+ outb(SCBARRAY + iobase + 30, next);
+ }
+ /* Update the tale pointer */
+ if(inb(WAITING_SCBT + iobase) == scbp->position)
+ outb(WAITING_SCBT + iobase, prev);
+
+ /*
+ * Point us back at the original scb position
+ * and inform the SCSI system that the command
+ * has been aborted.
+ */
+ outb(SCBPTR + iobase, curscbp);
+ scbp->flags |= SCB_ABORTED;
+ scbp->xs->error |= xs_error;
+ if(scbp->position != timedout_scb)
+ untimeout(ahc_timeout, (caddr_t)scbp);
+ ahc_done (unit, scbp);
+ return next;
+}
+
+void
+ahc_unbusy_target(target, channel, iobase)
+ u_char target;
+ char channel;
+ u_long iobase;
+{
+ u_char active;
+ u_long active_port = HA_ACTIVE0 + iobase;
+ if(target > 0x07 || channel == 'B') {
+ /*
+ * targets on the Second channel or
+ * above id 7 store info in byte two
+ * of HA_ACTIVE
+ */
+ active_port++;
+ }
+ active = inb(active_port);
+ active &= ~(0x01 << (target & 0x07));
+ outb(active_port, active);
+}
+
+void
+ahc_reset_current_bus(iobase)
+ u_long iobase;
+{
+ outb(SCSISEQ + iobase, SCSIRSTO);
+ DELAY(1000);
+ outb(SCSISEQ + iobase, 0);
+}
+
+int
+ahc_reset_channel(unit, ahc, channel, timedout_scb, xs_error)
+ int unit;
+ struct ahc_data *ahc;
+ char channel;
+ u_char timedout_scb;
+ u_int32 xs_error;
+{
+ u_long iobase = ahc->baseport;
+ u_char sblkctl;
+ char cur_channel;
+ u_long offset, offset_max;
+ int found;
+
+ /*
+ * Clean up all the state information for the
+ * pending transactions on this bus.
+ */
+ found = ahc_reset_device(unit, ahc, ALL_TARGETS, channel,
+ timedout_scb, xs_error);
+ if(channel == 'B'){
+ ahc->needsdtr |= (ahc->needsdtr_orig & 0xff00);
+ ahc->sdtrpending &= 0x00ff;
+ outb(HA_ACTIVE1 + iobase, 0);
+ offset = HA_TARG_SCRATCH + iobase + 8;
+ offset_max = HA_TARG_SCRATCH + iobase + 16;
+ }
+ else if (ahc->type & AHC_WIDE){
+ ahc->needsdtr = ahc->needsdtr_orig;
+ ahc->needwdtr = ahc->needwdtr_orig;
+ ahc->sdtrpending = 0;
+ ahc->wdtrpending = 0;
+ outb(HA_ACTIVE0 + iobase, 0);
+ outb(HA_ACTIVE1 + iobase, 0);
+ offset = HA_TARG_SCRATCH + iobase;
+ offset_max = HA_TARG_SCRATCH + iobase + 16;
+ }
+ else{
+ ahc->needsdtr |= (ahc->needsdtr_orig & 0x00ff);
+ ahc->sdtrpending &= 0xff00;
+ outb(HA_ACTIVE0 + iobase, 0);
+ offset = HA_TARG_SCRATCH + iobase;
+ offset_max = HA_TARG_SCRATCH + iobase + 8;
+ }
+ for(;offset < offset_max;offset++) {
+ /*
+ * Revert to async/narrow transfers
+ * until we renegotiate.
+ */
+ u_char targ_scratch;
+ targ_scratch = inb(offset);
+ targ_scratch &= SXFR;
+ outb(offset, targ_scratch);
+ }
+
+ /*
+ * Reset the bus and unpause/restart the controller
+ */
+
+ /* Case 1: Command for another bus is active */
+ sblkctl = inb(SBLKCTL + iobase);
+ cur_channel = (sblkctl & SELBUSB) ? 'B' : 'A';
+ if(cur_channel != channel)
+ {
+ /*
+ * Stealthily reset the other bus
+ * without upsetting the current bus
+ */
+ outb(SBLKCTL + iobase, sblkctl ^ SELBUSB);
+ ahc_reset_current_bus(iobase);
+ outb(SBLKCTL + iobase, sblkctl);
+ UNPAUSE_SEQUENCER(ahc);
+ }
+ /* Case 2: A command from this bus is active or we're idle */
+ else {
+ ahc_reset_current_bus(iobase);
+ RESTART_SEQUENCER(ahc);
+ }
+ return found;
+}
+
+int
+ahc_match_scb (scb, target, channel)
+ struct scb *scb;
+ int target;
+ char channel;
+{
+ int targ = (scb->target_channel_lun >> 4) & 0x0f;
+ char chan = (scb->target_channel_lun & SELBUSB) ? 'B' : 'A';
+
+ if (target == ALL_TARGETS)
+ return (chan == channel);
+ else
+ return ((chan == channel) && (targ == target));
+}
+
OpenPOWER on IntegriCloud