[SCSI] aic79xx: Fixup external device reset
Hannes Reinecke [Mon, 23 Oct 2006 13:22:37 +0000 (15:22 +0200)]
Whenever an external device is resetted we really have to take
care to keep the channel in sync. Just notifying SCSI-ML and retry
is not enough as we have to make sure the SCSI bus is not getting
confused, either.
So whenever we detect an external reset we rewrite the command to
TUR, disable packetized command and notify the internal engine
that an abort has happened. This way we trigger a proper bus
reset sequence and all devices will be renegotiated properly.
Kudos to Justin Gibbs and Luben Tuikov for this idea.

Signed-off-by: Hannes Reinecke <hare@suse.de>
Signed-off-by: James Bottomley <James.Bottomley@SteelEye.com>

drivers/scsi/aic7xxx/aic79xx_core.c

index 4c2b5a8..07a86a3 100644 (file)
@@ -1154,10 +1154,12 @@ ahd_handle_seqint(struct ahd_softc *ahd, u_int intstat)
                         * If a target takes us into the command phase
                         * assume that it has been externally reset and
                         * has thus lost our previous packetized negotiation
-                        * agreement.
-                        * Revert to async/narrow transfers until we
-                        * can renegotiate with the device and notify
-                        * the OSM about the reset.
+                        * agreement.  Since we have not sent an identify
+                        * message and may not have fully qualified the
+                        * connection, we change our command to TUR, assert
+                        * ATN and ABORT the task when we go to message in
+                        * phase.  The OSM will see the REQUEUE_REQUEST
+                        * status and retry the command.
                         */
                        scbid = ahd_get_scbptr(ahd);
                        scb = ahd_lookup_scb(ahd, scbid);
@@ -1184,7 +1186,28 @@ ahd_handle_seqint(struct ahd_softc *ahd, u_int intstat)
                        ahd_set_syncrate(ahd, &devinfo, /*period*/0,
                                         /*offset*/0, /*ppr_options*/0,
                                         AHD_TRANS_ACTIVE, /*paused*/TRUE);
-                       scb->flags |= SCB_EXTERNAL_RESET;
+                       /* Hand-craft TUR command */
+                       ahd_outb(ahd, SCB_CDB_STORE, 0);
+                       ahd_outb(ahd, SCB_CDB_STORE+1, 0);
+                       ahd_outb(ahd, SCB_CDB_STORE+2, 0);
+                       ahd_outb(ahd, SCB_CDB_STORE+3, 0);
+                       ahd_outb(ahd, SCB_CDB_STORE+4, 0);
+                       ahd_outb(ahd, SCB_CDB_STORE+5, 0);
+                       ahd_outb(ahd, SCB_CDB_LEN, 6);
+                       scb->hscb->control &= ~(TAG_ENB|SCB_TAG_TYPE);
+                       scb->hscb->control |= MK_MESSAGE;
+                       ahd_outb(ahd, SCB_CONTROL, scb->hscb->control);
+                       ahd_outb(ahd, MSG_OUT, HOST_MSG);
+                       ahd_outb(ahd, SAVED_SCSIID, scb->hscb->scsiid);
+                       /*
+                        * The lun is 0, regardless of the SCB's lun
+                        * as we have not sent an identify message.
+                        */
+                       ahd_outb(ahd, SAVED_LUN, 0);
+                       ahd_outb(ahd, SEQ_FLAGS, 0);
+                       ahd_assert_atn(ahd);
+                       scb->flags &= ~SCB_PACKETIZED;
+                       scb->flags |= SCB_ABORT|SCB_EXTERNAL_RESET;
                        ahd_freeze_devq(ahd, scb);
                        ahd_set_transaction_status(scb, CAM_REQUEUE_REQ);
                        ahd_freeze_scb(scb);
@@ -1620,8 +1643,10 @@ ahd_handle_scsiint(struct ahd_softc *ahd, u_int intstat)
        /*
         * Ignore external resets after a bus reset.
         */
-       if (((status & SCSIRSTI) != 0) && (ahd->flags & AHD_BUS_RESET_ACTIVE))
+       if (((status & SCSIRSTI) != 0) && (ahd->flags & AHD_BUS_RESET_ACTIVE)) {
+               ahd_outb(ahd, CLRSINT1, CLRSCSIRSTI);
                return;
+       }
 
        /*
         * Clear bus reset flag
@@ -2301,6 +2326,22 @@ ahd_handle_nonpkt_busfree(struct ahd_softc *ahd)
                        if (sent_msg == MSG_ABORT_TAG)
                                tag = SCB_GET_TAG(scb);
 
+                       if ((scb->flags & SCB_EXTERNAL_RESET) != 0) {
+                               /*
+                                * This abort is in response to an
+                                * unexpected switch to command phase
+                                * for a packetized connection.  Since
+                                * the identify message was never sent,
+                                * "saved lun" is 0.  We really want to
+                                * abort only the SCB that encountered
+                                * this error, which could have a different
+                                * lun.  The SCB will be retried so the OS
+                                * will see the UA after renegotiating to
+                                * packetized.
+                                */
+                               tag = SCB_GET_TAG(scb);
+                               saved_lun = scb->hscb->lun;
+                       }
                        found = ahd_abort_scbs(ahd, target, 'A', saved_lun,
                                               tag, ROLE_INITIATOR,
                                               CAM_REQ_ABORTED);
@@ -7985,6 +8026,11 @@ ahd_reset_channel(struct ahd_softc *ahd, char channel, int initiate_reset)
        ahd_clear_fifo(ahd, 1);
 
        /*
+        * Clear SCSI interrupt status
+        */
+       ahd_outb(ahd, CLRSINT1, CLRSCSIRSTI);
+
+       /*
         * Reenable selections
         */
        ahd_outb(ahd, SIMODE1, ahd_inb(ahd, SIMODE1) | ENSCSIRST);
@@ -8017,10 +8063,6 @@ ahd_reset_channel(struct ahd_softc *ahd, char channel, int initiate_reset)
                }
        }
 #endif
-       /* Notify the XPT that a bus reset occurred */
-       ahd_send_async(ahd, devinfo.channel, CAM_TARGET_WILDCARD,
-                      CAM_LUN_WILDCARD, AC_BUS_RESET);
-
        /*
         * Revert to async/narrow transfers until we renegotiate.
         */
@@ -8042,6 +8084,10 @@ ahd_reset_channel(struct ahd_softc *ahd, char channel, int initiate_reset)
                }
        }
 
+       /* Notify the XPT that a bus reset occurred */
+       ahd_send_async(ahd, devinfo.channel, CAM_TARGET_WILDCARD,
+                      CAM_LUN_WILDCARD, AC_BUS_RESET);
+
        ahd_restart(ahd);
 
        return (found);