aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin T. Gibbs <gibbs@FreeBSD.org>1997-01-29 05:27:03 +0000
committerJustin T. Gibbs <gibbs@FreeBSD.org>1997-01-29 05:27:03 +0000
commit54dd351d9311088bf3e0853a0db40be6da6d1d24 (patch)
tree00b24e476a7c8d8e8dca5195efd4a0d4f26dd1ff
parente0604514d8bbb6c160b8ac9bc16b1579b0a02108 (diff)
Notes
-rw-r--r--sys/i386/scsi/aic7xxx.c498
-rw-r--r--sys/i386/scsi/aic7xxx.h15
2 files changed, 377 insertions, 136 deletions
diff --git a/sys/i386/scsi/aic7xxx.c b/sys/i386/scsi/aic7xxx.c
index e638d26bd024..fd00b040c660 100644
--- a/sys/i386/scsi/aic7xxx.c
+++ b/sys/i386/scsi/aic7xxx.c
@@ -5,7 +5,7 @@
* pci/aic7870.c 3940, 2940, aic7880, aic7870, aic7860,
* and aic7850 controllers
*
- * Copyright (c) 1994, 1995, 1996 Justin T. Gibbs.
+ * Copyright (c) 1994, 1995, 1996, 1997 Justin T. Gibbs.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -134,8 +134,10 @@
#include <sys/kernel.h>
+#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#define ALL_TARGETS -1
+#define ALL_CHANNELS '\0'
#if defined(__FreeBSD__)
u_long ahc_unit = 0;
@@ -173,6 +175,19 @@ static inline void unpause_sequencer __P((struct ahc_softc *ahc,
int unpause_always));
static inline void restart_sequencer __P((struct ahc_softc *ahc));
+static int timeouts_work; /*
+ * Boolean that lets the driver know
+ * that it can safely use timeouts
+ * for event scheduling. Timeouts
+ * don't work early in the boot
+ * cycle.
+ */
+
+#define AHC_BUSRESET_DELAY 1000 /* Reset delay in us */
+#define AHC_BUSSETTLE_DELAY (100 * 1000)/* Delay before taking commands
+ * after a reset in us
+ */
+
static struct scsi_adapter ahc_switch =
{
ahc_scsi_cmd,
@@ -248,7 +263,6 @@ restart_sequencer(ahc)
static u_int8_t ahc_abort_wscb __P((struct ahc_softc *ahc, struct scb *scbp,
u_int8_t scbpos, u_int8_t prev,
- struct scb *timedout_scb,
u_int32_t xs_error));
static void ahc_done __P((struct ahc_softc *ahc, struct scb *scbp));
static void ahc_handle_seqint __P((struct ahc_softc *ahc, u_int8_t intstat));
@@ -257,18 +271,17 @@ static void ahc_handle_scsiint __P((struct ahc_softc *ahc,
static void ahc_handle_devreset __P((struct ahc_softc *ahc,
struct scb *scb));
static void ahc_loadseq __P((struct ahc_softc *ahc));
-static int ahc_match_scb __P((struct scb *scb, int target, char channel));
+static int ahc_match_scb __P((struct scb *scb, int target, char channel,
+ u_int8_t tag));
static int ahc_poll __P((struct ahc_softc *ahc, int wait));
#ifdef AHC_DEBUG
static void ahc_print_scb __P((struct scb *scb));
#endif
static u_int8_t find_scb __P((struct ahc_softc *ahc, struct scb *scb));
static int ahc_reset_channel __P((struct ahc_softc *ahc, char channel,
- struct scb *timedout_scb,
- u_int32_t xs_error,
- int initiate_reset));
+ u_int32_t xs_error, int initiate_reset));
static int ahc_reset_device __P((struct ahc_softc *ahc, int target,
- char channel, struct scb *timedout_scb,
+ char channel, u_int8_t tag,
u_int32_t xs_error));
static void ahc_reset_current_bus __P((struct ahc_softc *ahc));
static void ahc_run_done_queue __P((struct ahc_softc *ahc));
@@ -282,6 +295,15 @@ static timeout_t
static void ahc_timeout __P((void *));
#endif
+static timeout_t
+ ahc_first_timeout;
+
+static timeout_t
+ ahc_busreset_complete;
+
+static timeout_t
+ ahc_busreset_settle_complete;
+
static u_int8_t ahc_unbusy_target __P((struct ahc_softc *ahc,
int target, char channel));
@@ -441,6 +463,11 @@ ahc_construct(ahc, bc, ioh, maddr, type, flags)
ahc->unpause = (ahc_inb(ahc, HCNTRL) & IRQMS) | INTEN;
ahc->pause = ahc->unpause | PAUSE;
+ ahc->busreset_args.ahc = ahc;
+ ahc->busreset_args.bus = 'A';
+ ahc->busreset_args.ahc = ahc;
+ ahc->busreset_args.bus = 'B';
+
#if defined(__FreeBSD__)
return (ahc);
#endif
@@ -729,10 +756,13 @@ ahc_intr(arg)
num_errors = sizeof(hard_error)/sizeof(hard_error[0]);
for (i = 0; error != 1 && i < num_errors; i++)
error >>= 1;
- panic("%s: brkadrint, %s at seqaddr = 0x%x\n",
- ahc_name(ahc), hard_error[i].errmesg,
- (ahc_inb(ahc, SEQADDR1) << 8) |
- ahc_inb(ahc, SEQADDR0));
+ printf("%s: brkadrint, %s at seqaddr = 0x%x\n",
+ ahc_name(ahc), hard_error[i].errmesg,
+ (ahc_inb(ahc, SEQADDR1) << 8) |
+ ahc_inb(ahc, SEQADDR0));
+
+ ahc_reset_device(ahc, ALL_TARGETS, ALL_CHANNELS, SCB_LIST_NULL,
+ XS_DRIVER_STUFFUP);
}
if (intstat & SEQINT)
ahc_handle_seqint(ahc, intstat);
@@ -823,6 +853,9 @@ ahc_handle_seqint(ahc, intstat)
if (scb->hscb->control & ABORT_SCB) {
sc_print_addr(scb->xs->sc_link);
printf(" - SCB abort successfull\n");
+ ahc_reset_device(ahc, target, channel,
+ scb->hscb->tag, XS_TIMEOUT);
+ ahc_run_done_queue(ahc);
break;
}
}
@@ -835,39 +868,20 @@ ahc_handle_seqint(ahc, intstat)
}
case NO_MATCH_BUSY:
{
- /* The SCB that wanted to link in is in CUR_SCBID */
+ /*
+ * XXX Leave this as a panic for the time being since
+ * it indicates a bug in the timeout code for this
+ * to happen.
+ */
u_int8_t scb_index;
u_int8_t busy_scbindex;
- struct scb *busy_scb = NULL;
scb_index = ahc_inb(ahc, CUR_SCBID);
scb = ahc->scb_data->scbarray[scb_index];
- /* Find the busy SCB and unbusy this target */
- busy_scbindex = ahc_unbusy_target(ahc, scb->xs->sc_link->target,
- channel);
- if (busy_scbindex == SCB_LIST_NULL)
- panic("%s:%c:%d: Target busy link failure, but "
- "the target is not busy!\n",
- ahc_name(ahc), channel, target);
-
- busy_scb = ahc->scb_data->scbarray[busy_scbindex];
- /* Busy SCB should be aborted */
- if ((busy_scb != NULL)
- && (busy_scb->hscb->control & ABORT_SCB) == 0
- && (busy_scb->hscb->control & SCB_ACTIVE) != 0) {
- panic("%s:%c:%d: Target busy link failure, but "
- "busy SCB exists!\n",
- ahc_name(ahc), channel, target);
- }
-
- if ((scb->hscb->control & ABORT_SCB) == 0) {
- /* We didn't want to abort this one too */
- ahc_outb(ahc, QINFIFO, scb_index);
- } else
- /* It's been aborted */
- ahc_done(ahc, scb);
- restart_sequencer(ahc);
+ panic("%s:%c:%d: Target busy link failure.\n",
+ ahc_name(ahc), (scb->hscb->tcl & SELBUSB) ? 'B' : 'A',
+ scb->xs->sc_link->target);
}
case SEND_REJECT:
{
@@ -878,6 +892,12 @@ ahc_handle_seqint(ahc, intstat)
break;
}
case NO_IDENT:
+ /*
+ * XXX - We probably need to do something very carefully to
+ * make sure we don't lock up the bus or transfer data to a
+ * bogus area of memory. Perhaps a bus reset is the best
+ * alternative???
+ */
panic("%s:%c:%d: Target did not send an IDENTIFY message. "
"SAVED_TCL == 0x%x\n",
ahc_name(ahc), channel, target,
@@ -1245,27 +1265,27 @@ ahc_handle_seqint(ahc, intstat)
case AWAITING_MSG:
{
int scb_index;
+ u_int8_t message_offset;
scb_index = ahc_inb(ahc, SCB_TAG);
scb = ahc->scb_data->scbarray[scb_index];
+ message_offset = ahc_inb(ahc, MSG_LEN);
/*
* This SCB had MK_MESSAGE set in its control byte,
* informing the sequencer that we wanted to send a
* special message to this target.
*/
if (scb->flags & SCB_DEVICE_RESET) {
- ahc_outb(ahc, MSG0,
- MSG_BUS_DEV_RESET);
+ ahc_outb(ahc, MSG0, MSG_BUS_DEV_RESET);
ahc_outb(ahc, MSG_LEN, 1);
printf("Bus Device Reset Message Sent\n");
} else if (scb->flags & SCB_MSGOUT_WDTR) {
- ahc_construct_wdtr(ahc, ahc_inb(ahc, MSG_LEN),
- BUS_16_BIT);
+ ahc_construct_wdtr(ahc, message_offset, BUS_16_BIT);
} else if (scb->flags & SCB_MSGOUT_SDTR) {
- u_int8_t target_scratch;
- u_int16_t ultraenable;
int sxfr;
int i;
+ u_int16_t ultraenable;
+ u_int8_t target_scratch;
/* Pull the user defined setting */
target_scratch = ahc_inb(ahc, TARG_SCRATCH
@@ -1283,10 +1303,18 @@ ahc_handle_seqint(ahc, intstat)
if (sxfr == ahc_syncrates[i].sxfr)
break;
- ahc_construct_sdtr(ahc, ahc_inb(ahc, MSG_LEN),
+ ahc_construct_sdtr(ahc, message_offset,
ahc_syncrates[i].period,
(target_scratch & WIDEXFER) ?
MAX_OFFSET_16BIT : MAX_OFFSET_8BIT);
+ } else if (scb->flags & SCB_ABORT) {
+ if ((scb->hscb->control & TAG_ENB) != 0)
+ ahc_outb(ahc, MSG0 + message_offset,
+ MSG_ABORT_TAG);
+ else
+ ahc_outb(ahc, MSG0 + message_offset, MSG_ABORT);
+ ahc_outb(ahc, MSG_LEN, message_offset + 1);
+ printf("Abort Message Sent\n");
} else
panic("ahc_intr: AWAITING_MSG for an SCB that "
"does not have a waiting message");
@@ -1412,7 +1440,6 @@ ahc_handle_scsiint(ahc, intstat)
ahc_name(ahc), channel);
ahc_reset_channel(ahc,
channel,
- NULL,
XS_BUSY,
/* Initiate Reset */FALSE);
scb = NULL;
@@ -1448,7 +1475,8 @@ ahc_handle_scsiint(ahc, intstat)
hscb = &ahc->scb_data->hscbs[next];
ahc_outb(ahc, SCBCNT, SCBAUTO);
ahc_outsb(ahc, SCBARRAY,
- (u_int8_t *)hscb, 28);
+ (u_int8_t *)hscb,
+ SCB_PIO_TRANSFER_SIZE);
ahc_outb(ahc, SCBCNT, 0);
} else
/* Just switch to the SCB */
@@ -1456,24 +1484,30 @@ ahc_handle_scsiint(ahc, intstat)
/* Add this SCB as the next to run */
ahc_outb(ahc, SCB_NEXT,
- ahc_inb(ahc, WAITING_SCBH));
+ ahc_inb(ahc, WAITING_SCBH));
ahc_outb(ahc, WAITING_SCBH,
ahc_inb(ahc, SCBPTR));
} else {
/* Add us to the Free list */
ahc_outb(ahc, SCB_NEXT,
- ahc_inb(ahc, FREE_SCBH));
+ ahc_inb(ahc, FREE_SCBH));
ahc_outb(ahc, FREE_SCBH,
- ahc_inb(ahc, SCBPTR));
+ ahc_inb(ahc, SCBPTR));
}
/* Did we ask for this?? */
if (lastphase == P_MESGOUT && scb != NULL) {
if (scb->flags & SCB_DEVICE_RESET) {
ahc_handle_devreset(ahc, scb);
+ scb = NULL;
printerror = 0;
- } else if (scb->flags & SCB_ABORTED) {
- ahc_done(ahc, scb);
+ } else if (scb->flags & SCB_ABORT) {
+ ahc_reset_device(ahc,
+ target,
+ channel,
+ scb->hscb->tag,
+ XS_TIMEOUT);
+ ahc_run_done_queue(ahc);
scb = NULL;
printerror = 0;
}
@@ -1553,12 +1587,20 @@ ahc_handle_scsiint(ahc, intstat)
if (mesg_out != MSG_NOOP) {
ahc_outb(ahc, MSG0, mesg_out);
ahc_outb(ahc, MSG_LEN, 1);
+ scb = NULL;
} else
/*
* Should we allow the target to make
- * this decision for us?
+ * this decision for us? If we get a
+ * sense request from the drive, we will
+ * not fetch it since xs->error != XS_NOERROR.
+ * perhaps we need two error fields in the
+ * xs structure?
*/
xs->error = XS_DRIVER_STUFFUP;
+ ahc_outb(ahc, CLRSINT1, CLRSCSIPERR);
+ unpause_sequencer(ahc, /*unpause_always*/TRUE);
+ ahc_outb(ahc, CLRINT, CLRSCSIINT);
} else if ((status & SELTO) != 0) {
struct scsi_xfer *xs;
u_int8_t scbptr;
@@ -1637,7 +1679,8 @@ ahc_handle_devreset(ahc, scb)
targ_scratch = ahc_inb(ahc, TARG_SCRATCH + scratch_offset);
targ_scratch &= SXFR;
ahc_outb(ahc, TARG_SCRATCH + scratch_offset, targ_scratch);
- found = ahc_reset_device(ahc, target, channel, NULL, XS_NOERROR);
+ found = ahc_reset_device(ahc, target, channel, SCB_LIST_NULL,
+ XS_NOERROR);
sc_print_addr(scb->xs->sc_link);
printf("Bus Device Reset delivered. %d SCBs aborted\n", found);
ahc->in_timeout = FALSE;
@@ -1683,8 +1726,8 @@ ahc_done(ahc, scb)
xs->flags |= ITSDONE;
#ifdef AHC_TAGENABLE
/*
- * This functionality is provided by the generic SCSI layer
- * in FreeBSD 2.2.
+ * This functionality will be provided by the generic SCSI layer
+ * in FreeBSD 3.0.
*/
if (xs->cmd->opcode == INQUIRY && xs->error == XS_NOERROR) {
struct scsi_inquiry_data *inq_data;
@@ -1891,10 +1934,7 @@ ahc_init(ahc)
if (bootverbose)
printf("%s: Reseting Channel B\n",
ahc_name(ahc));
- ahc_outb(ahc, SCSISEQ, SCSIRSTO);
- DELAY(1000);
- ahc_outb(ahc, SCSISEQ, 0);
-
+ ahc_reset_current_bus(ahc);
/* Ensure we don't get a RSTI interrupt from this */
ahc_outb(ahc, CLRSINT1, CLRSCSIRSTI);
ahc_outb(ahc, CLRINT, CLRSCSIINT);
@@ -1920,9 +1960,7 @@ ahc_init(ahc)
if (bootverbose)
printf("%s: Reseting Channel A\n", ahc_name(ahc));
- ahc_outb(ahc, SCSISEQ, SCSIRSTO);
- DELAY(1000);
- ahc_outb(ahc, SCSISEQ, 0);
+ ahc_reset_current_bus(ahc);
/* Ensure we don't get a RSTI interrupt from this */
ahc_outb(ahc, CLRSINT1, CLRSCSIRSTI);
@@ -1956,9 +1994,11 @@ ahc_init(ahc)
for (i = 0; i <= max_targ; i++) {
u_int8_t target_settings;
if (ahc->flags & AHC_USEDEFAULTS) {
- target_settings = 0; /* 10MHz */
+ target_settings = 0; /* 10MHz/20MHz */
ahc->needsdtr_orig |= (0x01 << i);
ahc->needwdtr_orig |= (0x01 << i);
+ if (ahc->type & AHC_ULTRA)
+ ultraenable |= (0x01 << i);
} else {
/* Take the settings leftover in scratch RAM. */
target_settings = ahc_inb(ahc, TARG_SCRATCH + i);
@@ -2048,8 +2088,8 @@ ahc_init(ahc)
if (array_size > PAGE_SIZE) {
ahc->scb_data->hscbs = (struct hardware_scb *)
contigmalloc(array_size, M_DEVBUF,
- M_NOWAIT, 0ul, 0xffffffff, PAGE_SIZE,
- 0x10000);
+ M_NOWAIT, 0ul, 0xffffffff,
+ PAGE_SIZE, 0x10000);
} else {
ahc->scb_data->hscbs = (struct hardware_scb *)
malloc(array_size, M_DEVBUF, M_NOWAIT);
@@ -2134,6 +2174,9 @@ ahc_init(ahc)
unpause_sequencer(ahc, /*unpause_always*/TRUE);
+ /* Notify us when the system can handle timeouts */
+ timeout(ahc_first_timeout, NULL, 0);
+
/*
* Note that we are going and return (to probe)
*/
@@ -2181,6 +2224,24 @@ ahc_scsi_cmd(xs)
| (IS_SCSIBUS_B(ahc, xs->sc_link) ? SELBUSB : 0)));
SC_DEBUG(xs->sc_link, SDEV_DB2, ("ahc_scsi_cmd\n"));
flags = xs->flags;
+
+ if (ahc->in_reset != 0) {
+ /*
+ * If we are still in a reset, send the transaction
+ * back indicating that it was aborted due to a
+ * reset.
+ */
+ if ((IS_SCSIBUS_B(ahc, xs->sc_link)
+ && (ahc->in_reset & CHANNEL_B_RESET) != 0)
+ || (!IS_SCSIBUS_B(ahc, xs->sc_link)
+ && (ahc->in_reset & CHANNEL_A_RESET) != 0)) {
+ /* Ick, but I don't want it to abort this */
+ xs->retries++;
+ xs->error = XS_BUSY;
+ return(COMPLETE);
+ }
+ }
+
/*
* get an scb to use. If the transfer
* is from a buf (possibly from interrupt time)
@@ -2530,8 +2591,8 @@ static void
ahc_loadseq(ahc)
struct ahc_softc *ahc;
{
- static u_char seqprog[] = {
-# include "aic7xxx_seq.h"
+ static u_char seqprog[] = {
+# include "aic7xxx_seq.h"
};
ahc_outb(ahc, SEQCTL, PERRORDIS|LOADRAM);
@@ -2566,25 +2627,50 @@ ahc_poll(ahc, wait)
return (0);
}
+/*
+ * Handler to register that the system is capable of
+ * delivering timeouts. We register this timeout in
+ * ahc_init and it should go off as soon as we're through
+ * the boot process.
+ */
+static void
+ahc_first_timeout(arg)
+ void *arg;
+{
+ timeouts_work = 1;
+}
+
static void
ahc_timeout(arg)
void *arg;
{
- struct scb *scb = (struct scb *)arg;
- struct ahc_softc *ahc;
- int s, found;
- u_char bus_state;
+ struct scb *scb = (struct scb *)arg;
+ struct ahc_softc *ahc;
+ int s, found;
+ u_int8_t bus_state;
- s = splbio();
+ ahc = (struct ahc_softc *)scb->xs->sc_link->adapter_softc;
+
+ s = splbio();
+
+ /*
+ * Ensure that the card doesn't do anything
+ * behind our back. Also make sure that we
+ * didn't "just" miss an interrupt that would
+ * affect this timeout.
+ */
+ do {
+ ahc_intr(ahc);
+ pause_sequencer(ahc);
+ } while (ahc_inb(ahc, INTSTAT) & INT_PEND);
if (!(scb->flags & SCB_ACTIVE)) {
/* Previous timeout took care of me already */
+ unpause_sequencer(ahc, /*unpause_always*/TRUE);
splx(s);
return;
}
- ahc = (struct ahc_softc *)scb->xs->sc_link->adapter_softc;
-
if (ahc->in_timeout) {
/*
* Some other SCB has started a recovery operation
@@ -2609,18 +2695,13 @@ ahc_timeout(arg)
scb->flags |= SCB_TIMEDOUT;
timeout(ahc_timeout, (caddr_t)scb,
(scb->xs->timeout * hz) / 1000);
+ unpause_sequencer(ahc, /*unpause_always*/TRUE);
splx(s);
return;
}
}
ahc->in_timeout = TRUE;
- /*
- * Ensure that the card doesn't do anything
- * behind our back.
- */
- pause_sequencer(ahc);
-
sc_print_addr(scb->xs->sc_link);
printf("timed out ");
/*
@@ -2677,8 +2758,8 @@ ahc_timeout(arg)
*/
char channel = (scb->hscb->tcl & SELBUSB)
? 'B': 'A';
- found = ahc_reset_channel(ahc, channel, scb,
- XS_TIMEOUT, /*Initiate Reset*/TRUE);
+ found = ahc_reset_channel(ahc, channel, XS_TIMEOUT,
+ /*Initiate Reset*/TRUE);
printf("%s: Issued Channel %c Bus Reset. "
"%d SCBs aborted\n", ahc_name(ahc), channel, found);
ahc->in_timeout = FALSE;
@@ -2692,7 +2773,7 @@ ahc_timeout(arg)
scb->flags |= SCB_SENTORDEREDTAG;
ahc->orderedtag |= 0xFF;
timeout(ahc_timeout, (caddr_t)scb, (5 * hz));
- unpause_sequencer(ahc, /*unpause_always*/FALSE);
+ unpause_sequencer(ahc, /*unpause_always*/TRUE);
printf("Ordered Tag queued\n");
} else {
/*
@@ -2734,8 +2815,9 @@ ahc_timeout(arg)
timeout(ahc_timeout, (caddr_t)scb,
(scb->xs->timeout * hz) / 1000);
}
- timeout(ahc_timeout, (caddr_t)active_scb, (2 * hz));
- unpause_sequencer(ahc, /*unpause_always*/FALSE);
+ timeout(ahc_timeout, (caddr_t)active_scb,
+ (100 * hz) / 1000);
+ unpause_sequencer(ahc, /*unpause_always*/TRUE);
} else {
u_int8_t hscb_index;
int disconnected;
@@ -2753,16 +2835,31 @@ ahc_timeout(arg)
scb->flags |= SCB_ABORTED;
if (disconnected) {
/* Simply set the ABORT_SCB control bit */
- scb->hscb->control |= ABORT_SCB;
- if (hscb_index != SCB_LIST_NULL)
- ahc_outb(ahc, SCB_CONTROL, ABORT_SCB);
- timeout(ahc_timeout, (caddr_t)scb, (2 * hz));
+ scb->hscb->control |= ABORT_SCB|MK_MESSAGE;
+ scb->hscb->control &= DISCONNECTED;;
+ scb->flags |= SCB_ABORT;
+ if (hscb_index != SCB_LIST_NULL) {
+ u_int8_t scb_control;
+
+ scb_control = ahc_inb(ahc, SCB_CONTROL);
+ ahc_outb(ahc, SCB_CONTROL,
+ scb_control | ABORT_SCB);
+ }
+ /*
+ * Actually re-queue this SCB in case we can
+ * select the device before it reconnects.
+ */
+ STAILQ_INSERT_HEAD(&ahc->waiting_scbs, scb,
+ links);
+ timeout(ahc_timeout, (caddr_t)scb,
+ (100 * hz) / 1000);
+ ahc_run_waiting_queue(ahc);
}
ahc_outb(ahc, SCBPTR, saved_scbptr);
- unpause_sequencer(ahc, /*unpause_always*/FALSE);
+ unpause_sequencer(ahc, /*unpause_always*/TRUE);
if (!disconnected)
/* Go "immediatly" to the bus reset */
- timeout(ahc_timeout, (caddr_t)scb, hz / 2);
+ timeout(ahc_timeout, (caddr_t)scb, 0);
}
}
splx(s);
@@ -2801,11 +2898,11 @@ find_scb(ahc, scb)
* all active and queued scbs for that target/channel.
*/
static int
-ahc_reset_device(ahc, target, channel, timedout_scb, xs_error)
- struct ahc_softc *ahc;
- int target;
- char channel;
- struct scb *timedout_scb;
+ahc_reset_device(ahc, target, channel, tag, xs_error)
+ struct ahc_softc *ahc;
+ int target;
+ char channel;
+ u_int8_t tag;
u_int32_t xs_error;
{
struct scb *scbp;
@@ -2826,14 +2923,13 @@ ahc_reset_device(ahc, target, channel, timedout_scb, xs_error)
for (i = 0; i < (queued - found); i++) {
saved_queue[i] = ahc_inb(ahc, QINFIFO);
scbp = ahc->scb_data->scbarray[saved_queue[i]];
- if (ahc_match_scb (scbp, target, channel)) {
+ if (ahc_match_scb (scbp, target, channel, tag)) {
/*
* We found an scb that needs to be aborted.
*/
scbp->flags = SCB_ABORTED|SCB_QUEUED_FOR_DONE;
- scbp->xs->error |= xs_error;
- if(scbp != timedout_scb)
- untimeout(ahc_timeout, (caddr_t)scbp);
+ scbp->xs->error = xs_error;
+ untimeout(ahc_timeout, (caddr_t)scbp);
i--;
found++;
}
@@ -2856,9 +2952,9 @@ ahc_reset_device(ahc, target, channel, timedout_scb, xs_error)
while (next != SCB_LIST_NULL) {
ahc_outb(ahc, SCBPTR, next);
scbp = ahc->scb_data->scbarray[ahc_inb(ahc, SCB_TAG)];
- if (ahc_match_scb(scbp, target, channel)) {
+ if (ahc_match_scb(scbp, target, channel, tag)) {
next = ahc_abort_wscb(ahc, scbp, next, prev,
- timedout_scb, xs_error);
+ xs_error);
found++;
} else {
prev = next;
@@ -2875,16 +2971,59 @@ ahc_reset_device(ahc, target, channel, timedout_scb, xs_error)
for (i = 0; i < ahc->scb_data->numscbs; i++) {
scbp = ahc->scb_data->scbarray[i];
if ((scbp->flags & SCB_ACTIVE)
- && ahc_match_scb(scbp, target, channel)) {
+ && ahc_match_scb(scbp, target, channel, tag)) {
/* Ensure the target is "free" */
- ahc_unbusy_target(ahc, target, channel);
+ ahc_unbusy_target(ahc, target,
+ (scbp->hscb->tcl & SELBUSB) ?
+ 'B' : 'A');
scbp->flags = SCB_ABORTED|SCB_QUEUED_FOR_DONE;
scbp->xs->error |= xs_error;
- if (scbp != timedout_scb)
- untimeout(ahc_timeout, (caddr_t)scbp);
+ untimeout(ahc_timeout, (caddr_t)scbp);
found++;
}
- }
+ }
+
+ /*
+ * Go through the disconnected list and remove any entries we
+ * have queued for completion, 0'ing their control byte too.
+ */
+ {
+ u_int8_t next, prev;
+
+ next = ahc_inb(ahc, DISCONNECTED_SCBH);
+ prev = SCB_LIST_NULL;
+
+ while (next != SCB_LIST_NULL) {
+ ahc_outb(ahc, SCBPTR, next);
+ scbp = ahc->scb_data->scbarray[ahc_inb(ahc, SCB_TAG)];
+ if (ahc_match_scb(scbp, target, channel, tag)) {
+ u_int8_t curpos;
+
+ curpos = next;
+ next = ahc_inb(ahc, SCB_NEXT);
+ ahc_outb(ahc, SCB_CONTROL, 0);
+
+ /* Add this SCB to the free list */
+ ahc_outb(ahc, SCB_NEXT,
+ ahc_inb(ahc, FREE_SCBH));
+ ahc_outb(ahc, FREE_SCBH, curpos);
+
+ if (prev != NULL) {
+ ahc_outb(ahc, SCBPTR, prev);
+ ahc_outb(ahc, SCB_NEXT, next);
+ } else
+ ahc_outb(ahc, DISCONNECTED_SCBH, next);
+
+ if (next != NULL) {
+ ahc_outb(ahc, SCBPTR, next);
+ ahc_outb(ahc, SCB_NEXT, prev);
+ }
+ } else {
+ prev = next;
+ next = ahc_inb(ahc, SCB_NEXT);
+ }
+ }
+ }
ahc_outb(ahc, SCBPTR, active_scb);
return found;
}
@@ -2894,12 +3033,11 @@ ahc_reset_device(ahc, target, channel, timedout_scb, xs_error)
* scb that follows the one that we remove.
*/
static u_char
-ahc_abort_wscb (ahc, scbp, scbpos, prev, timedout_scb, xs_error)
+ahc_abort_wscb (ahc, scbp, scbpos, prev, xs_error)
struct ahc_softc *ahc;
struct scb *scbp;
u_int8_t scbpos;
u_int8_t prev;
- struct scb *timedout_scb;
u_int32_t xs_error;
{
u_int8_t curscb, next;
@@ -2918,6 +3056,10 @@ ahc_abort_wscb (ahc, scbp, scbpos, prev, timedout_scb, xs_error)
ahc_outb(ahc, SCB_NEXT, SCB_LIST_NULL);
ahc_unbusy_target(ahc, target, channel);
+ /* Add this SCB to the free list */
+ ahc_outb(ahc, SCB_NEXT, ahc_inb(ahc, FREE_SCBH));
+ ahc_outb(ahc, FREE_SCBH, scbpos);
+
/* update the waiting list */
if (prev == SCB_LIST_NULL)
/* First in the list */
@@ -2930,6 +3072,7 @@ ahc_abort_wscb (ahc, scbp, scbpos, prev, timedout_scb, xs_error)
ahc_outb(ahc, SCBPTR, prev);
ahc_outb(ahc, SCB_NEXT, next);
}
+
/*
* Point us back at the original scb position
* and inform the SCSI system that the command
@@ -2938,8 +3081,7 @@ ahc_abort_wscb (ahc, scbp, scbpos, prev, timedout_scb, xs_error)
ahc_outb(ahc, SCBPTR, curscb);
scbp->flags = SCB_ABORTED|SCB_QUEUED_FOR_DONE;
scbp->xs->error |= xs_error;
- if (scbp != timedout_scb)
- untimeout(ahc_timeout, (caddr_t)scbp);
+ untimeout(ahc_timeout, (caddr_t)scbp);
return next;
}
@@ -2970,33 +3112,114 @@ static void
ahc_reset_current_bus(ahc)
struct ahc_softc *ahc;
{
- ahc_outb(ahc, SCSISEQ, SCSIRSTO);
- DELAY(1000);
- ahc_outb(ahc, SCSISEQ, 0);
+ u_int8_t scsiseq;
+ u_int8_t sblkctl;
+
+ scsiseq = ahc_inb(ahc, SCSISEQ);
+ ahc_outb(ahc, SCSISEQ, scsiseq | SCSIRSTO);
+ if (timeouts_work != 0) {
+ struct ahc_busreset_args *args;
+
+ sblkctl = ahc_inb(ahc, SBLKCTL);
+ args = (sblkctl & SELBUSB) ? &ahc->busreset_args_b
+ : &ahc->busreset_args;
+ ahc->in_reset |= (args->bus == 'A' ? CHANNEL_A_RESET
+ : CHANNEL_B_RESET);
+ timeout(ahc_busreset_complete, args,
+ MAX((AHC_BUSRESET_DELAY * hz) / 1000000, 1));
+ } else {
+ DELAY(AHC_BUSRESET_DELAY);
+ /* Turn off the bus reset */
+ ahc_outb(ahc, SCSISEQ, scsiseq & ~SCSIRSTO);
+ /* Wait a minimal bus settle delay */
+ DELAY(AHC_BUSSETTLE_DELAY);
+ }
+}
+
+static void
+ahc_busreset_complete(arg)
+ void *arg;
+{
+ struct ahc_busreset_args *args;
+ struct ahc_softc *ahc;
+ int s;
+ u_int8_t scsiseq;
+ u_int8_t sblkctl;
+
+ args = (struct ahc_busreset_args *)arg;
+ ahc = (struct ahc_softc *)args->ahc;
+
+ s = splbio();
+ pause_sequencer(ahc);
+
+ /* Save the current block control state */
+ sblkctl = ahc_inb(ahc, SBLKCTL);
+
+ /* Switch to the right bus */
+ if (args->bus == 'A')
+ ahc_outb(ahc, SBLKCTL, sblkctl & ~SELBUSB);
+ else
+ ahc_outb(ahc, SBLKCTL, sblkctl | SELBUSB);
+
+ /* Turn off the bus reset */
+ printf("Clearing bus reset\n");
+ scsiseq = ahc_inb(ahc, SCSISEQ);
+ ahc_outb(ahc, SCSISEQ, scsiseq & ~SCSIRSTO);
+
+ /*
+ * Set a 100ms timeout to clear our in_reset flag.
+ * It will be *at-least* that long before any targets
+ * respond to real commands on the bus.
+ */
+ timeout(ahc_busreset_settle_complete, args,
+ (AHC_BUSSETTLE_DELAY * hz) / 1000000);
+
+ /* Restore the original channel */
+ ahc_outb(ahc, SBLKCTL, sblkctl);
+ unpause_sequencer(ahc, /*unpause_always*/FALSE);
+ splx(s);
+}
+
+static void
+ahc_busreset_settle_complete(arg)
+ void *arg;
+{
+ struct ahc_busreset_args *args;
+ struct ahc_softc *ahc;
+ int s;
+
+ args = (struct ahc_busreset_args *)arg;
+ ahc = (struct ahc_softc *)args->ahc;
+ /* Clear the reset flag */
+ s = splbio();
+ printf("Clearing 'in-reset' flag\n");
+ ahc->in_reset &= (args->bus == 'A' ? ~CHANNEL_A_RESET
+ : ~CHANNEL_B_RESET);
+ splx(s);
}
static int
-ahc_reset_channel(ahc, channel, timedout_scb, xs_error, initiate_reset)
+ahc_reset_channel(ahc, channel, xs_error, initiate_reset)
struct ahc_softc *ahc;
char channel;
- struct scb *timedout_scb;
u_int32_t xs_error;
int initiate_reset;
{
- u_int8_t sblkctl;
- char cur_channel;
u_int32_t offset, offset_max;
- int found;
- int target;
- int maxtarget;
+ int found;
+ int target;
+ int maxtarget;
+ u_int8_t sblkctl;
+ char cur_channel;
+ pause_sequencer(ahc);
maxtarget = 8;
/*
* Clean up all the state information for the
* pending transactions on this bus.
*/
- found = ahc_reset_device(ahc, ALL_TARGETS, channel,
- timedout_scb, xs_error);
+ found = ahc_reset_device(ahc, ALL_TARGETS, channel, SCB_LIST_NULL,
+ xs_error);
if (channel == 'B') {
ahc->needsdtr |= (ahc->needsdtr_orig & 0xff00);
ahc->sdtrpending &= 0x00ff;
@@ -3044,6 +3267,7 @@ ahc_reset_channel(ahc, channel, timedout_scb, xs_error, initiate_reset)
* upsetting the current bus.
*/
ahc_outb(ahc, SBLKCTL, sblkctl ^ SELBUSB);
+ ahc_outb(ahc, SIMODE1, ahc_inb(ahc, SIMODE1) & ~ENBUSFREE);
if (initiate_reset)
ahc_reset_current_bus(ahc);
ahc_outb(ahc, CLRSINT1, CLRSCSIRSTI|CLRSELTIMEO|CLRBUSFREE);
@@ -3052,6 +3276,7 @@ ahc_reset_channel(ahc, channel, timedout_scb, xs_error, initiate_reset)
unpause_sequencer(ahc, /*unpause_always*/TRUE);
} else {
/* Case 2: A command from this bus is active or we're idle */
+ ahc_outb(ahc, SIMODE1, ahc_inb(ahc, SIMODE1) & ~ENBUSFREE);
if (initiate_reset)
ahc_reset_current_bus(ahc);
ahc_outb(ahc, CLRSINT1, CLRSCSIRSTI|CLRSELTIMEO|CLRBUSFREE);
@@ -3077,18 +3302,23 @@ ahc_run_done_queue(ahc)
}
static int
-ahc_match_scb (scb, target, channel)
- struct scb *scb;
- int target;
- char channel;
+ahc_match_scb (scb, target, channel, tag)
+ struct scb *scb;
+ int target;
+ char channel;
+ u_int8_t tag;
{
- int targ = (scb->hscb->tcl >> 4) & 0x0f;
- char chan = (scb->hscb->tcl & SELBUSB) ? 'B' : 'A';
+ int targ = (scb->hscb->tcl >> 4) & 0x0f;
+ char chan = (scb->hscb->tcl & SELBUSB) ? 'B' : 'A';
+ int match;
- if (target == ALL_TARGETS)
- return (chan == channel);
- else
- return ((chan == channel) && (targ == target));
+ match = ((chan == channel) || (channel == ALL_CHANNELS));
+ if (match != 0)
+ match = ((targ == target) || (target == ALL_TARGETS));
+ if (match != 0)
+ match = ((tag == scb->hscb->tag) || (tag == SCB_LIST_NULL));
+
+ return match;
}
static void
diff --git a/sys/i386/scsi/aic7xxx.h b/sys/i386/scsi/aic7xxx.h
index 3510dee1798e..e68ed23c104f 100644
--- a/sys/i386/scsi/aic7xxx.h
+++ b/sys/i386/scsi/aic7xxx.h
@@ -3,7 +3,7 @@
* SCSI controllers. This is used to implement product specific
* probe and attach routines.
*
- * Copyright (c) 1994, 1995, 1996 Justin T. Gibbs.
+ * Copyright (c) 1994, 1995, 1996, 1997 Justin T. Gibbs.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -144,7 +144,8 @@ typedef enum {
SCB_ASSIGNEDQ = 0x0200,
SCB_SENTORDEREDTAG = 0x0400,
SCB_MSGOUT_SDTR = 0x0800,
- SCB_MSGOUT_WDTR = 0x1000
+ SCB_MSGOUT_WDTR = 0x1000,
+ SCB_ABORT = 0x2000
} scb_flag;
/*
@@ -214,6 +215,11 @@ struct scb_data {
*/
};
+struct ahc_busreset_args {
+ struct ahc_softc *ahc;
+ char bus;
+};
+
struct ahc_softc {
#if defined(__FreeBSD__)
int unit;
@@ -230,6 +236,8 @@ struct ahc_softc {
#endif
volatile u_int8_t *maddr;
struct scb_data *scb_data;
+ struct ahc_busreset_args busreset_args;
+ struct ahc_busreset_args busreset_args_b;
struct scsi_link sc_link;
struct scsi_link sc_link_b; /* Second bus for Twin channel cards */
STAILQ_HEAD(, scb) waiting_scbs;/*
@@ -268,6 +276,9 @@ struct ahc_softc {
u_int8_t unpause;
u_int8_t pause;
u_int8_t in_timeout;
+ u_int8_t in_reset;
+#define CHANNEL_A_RESET 0x01
+#define CHANNEL_B_RESET 0x02
};
struct full_ahc_softc {