Sync up drivers/scsi/aic7xxx
PATCH 3/28 Sync up drivers/scsi/aic7xxx
Sync up drivers/scsi/aic7xxx/. (2239-2241)
==== //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic79xx_osm.c#171 - /home/luben/projects/linux/2.6/linux-2.5/drivers/scsi/aic7xxx/aic79xx_osm.c ====
home/luben/projects/linux/2.6/linux-2.5/drivers/scsi/aic7xxx/aic79xx_osm.c renamed from tmp/tmp.26128.0 +0 -0
@@ -1,7 +1,7 @@
/*
* Adaptec AIC79xx device driver for Linux.
*
- * $Id: //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic79xx_osm.c#171 $
+ * $Id: //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic79xx_osm.c#172 $ *
* --------------------------------------------------------------------------
* Copyright (c) 1994-2000 Justin T. Gibbs.
@@ -473,6 +473,7 @@
static void ahd_linux_filter_inquiry(struct ahd_softc *ahd,
struct ahd_devinfo *devinfo);
static void ahd_linux_dev_timed_unfreeze(u_long arg); +static void ahd_release_simq_locked(struct ahd_softc *ahd); static void ahd_linux_sem_timeout(u_long arg);
static void ahd_linux_initialize_scsi_bus(struct ahd_softc *ahd); static void ahd_linux_size_nseg(void);
@@ -848,6 +849,156 @@
#endif
}
+/************************** Error Recovery ************************************/ +static int ahd_linux_recovery_thread(void *arg);
+
+static int
+ahd_linux_recovery_thread(void *arg)
+{
+ struct ahd_softc *ahd;
+ u_long s;
+
+ ahd = (struct ahd_softc *)arg;
+
+ /*
+ * Complete thread creation.
+ */
+ lock_kernel();
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,60)
+ /*
+ * Don't care about any signals.
+ */
+ siginitsetinv(¤t->blocked, 0);
+
+ daemonize();
+ sprintf(current->comm, "ahd_dv_%d", ahd->unit);
+#else
+ daemonize("ahd_recovery_%d", ahd->unit);
+#endif
+ unlock_kernel();
+
+ while (1) {
+
+ /*
+ * Use down_interruptible() rather than down() to
+ * avoid inclusion in the load average.
+ */
+ down_interruptible(&ahd->platform_data->recovery_sem);
+
+ ahd_lock(ahd, &s);
+ if ((ahd->flags & AHD_SHUTDOWN_RECOVERY) != 0) {
+ ahd_unlock(ahd, &s);
+ break;
+ }
+ ahd_unlock(ahd, &s);
+ ahd_recover_commands(ahd);
+ }
+ up(&ahd->platform_data->recovery_ending_sem);
+ return(0);
+}
+
+int
+ahd_spawn_recovery_thread(struct ahd_softc *ahd)
+{
+ ahd->platform_data->recovery_pid =
+ kernel_thread(ahd_linux_recovery_thread, ahd, 0);
+ return (0);
+}
+
+void
+ahd_terminate_recovery_thread(struct ahd_softc *ahd)
+{
+ u_long s;
+
+ ahd_lock(ahd, &s);
+ if (ahd->platform_data->recovery_pid != 0) {
+ ahd->flags |= AHD_SHUTDOWN_RECOVERY;
+ ahd_unlock(ahd, &s);
+ up(&ahd->platform_data->recovery_sem);
+
+ /*
+ * Use the recovery_ending_sem as an indicator that
+ * the dv thread is exiting. Note that the dv
+ * thread must still return after performing
+ * the up on our semaphore before it has
+ * completely exited this module. Unfortunately,
+ * there seems to be no easy way to wait for the
+ * exit of a thread for which you are not the
+ * parent (dv threads are parented by init).
+ * Cross your fingers...
+ */
+ down(&ahd->platform_data->recovery_ending_sem);
+
+ /*
+ * Mark the recovery thread as already dead. This
+ * avoids attempting to kill it a second time.
+ * This is necessary because we must kill the
+ * our threads before calling ahd_free() in the
+ * module shutdown case to avoid bogus locking
+ * in the SCSI mid-layer, but when ahd_free() is
+ * called without killing the DV thread in the
+ * instance detach case, so ahd_platform_free()
+ * calls us again to verify that the DV thread
+ * is dead.
+ */
+ ahd->platform_data->recovery_pid = 0;
+ } else {
+ ahd_unlock(ahd, &s);
+ }
+}
+
+void
+ahd_set_recoveryscb(struct ahd_softc *ahd, struct scb *scb)
+{
+ if ((scb->flags & SCB_RECOVERY_SCB) == 0) {
+ struct scb *list_scb;
+
+ scb->flags |= SCB_RECOVERY_SCB;
+
+ /*
+ * Take all queued, but not sent SCBs out of the equation.
+ * Also ensure that no new commands are queued to us while we
+ * try to fix this problem.
+ */
+ if ((scb->platform_data->flags & AHD_RELEASE_SIMQ) == 0) {
+ ahd_freeze_simq(ahd);
+ scb->platform_data->flags |= AHD_RELEASE_SIMQ;
+ }
+
+ /*
+ * Go through all of our pending SCBs and remove
+ * any scheduled timeouts for them. We will reschedule
+ * them after we've successfully fixed this problem.
+ */
+ LIST_FOREACH(list_scb, &ahd->pending_scbs, pending_links) {
+
+ scsi_delete_timer(list_scb->io_ctx);
+ scb->platform_data->flags &= ~AHD_TIMEOUT_ACTIVE;
+ }
+ }
+}
+
+void
+ahd_platform_timeout(struct scsi_cmnd *cmd)
+{
+
+ if (AHD_DV_CMD(cmd) == 0) {
+
+ ahd_linux_dv_timeout(cmd);
+ } else {
+ struct scb *scb;
+
+ scb = (struct scb *)cmd->host_scribble;
+ scb->platform_data->flags &= ~AHD_TIMEOUT_ACTIVE;
+ ahd_timeout(scb);
+ }
+}
+
+void
+ahd_linux_midlayer_timeout(struct scsi_cmnd *cmd)
+{
+}
+/********************** Host Template Entry Points ****************************/ /*
* Try to detect an Adaptec 79XX controller.
*/
@@ -1027,6 +1178,7 @@
cmd->device->id, cmd->device->lun,
/*alloc*/TRUE);
if (dev == NULL) {
+
ahd_cmd_set_transaction_status(cmd, CAM_RESRC_UNAVAIL);
ahd_linux_queue_cmd_complete(ahd, cmd);
ahd_schedule_completeq(ahd);
@@ -1035,8 +1187,23 @@
ahd_name(ahd));
return (0);
}- if (cmd->cmd_len > MAX_CDB_LEN)
- return (-EINVAL);
+
+ if (cmd->cmd_len > MAX_CDB_LEN) {
+
+ ahd_cmd_set_transaction_status(cmd, CAM_REQ_INVALID);
+ ahd_linux_queue_cmd_complete(ahd, cmd);
+ ahd_schedule_completeq(ahd);
+ ahd_midlayer_entrypoint_unlock(ahd, &flags);
+ printf("%s: aic79xx_linux_queue -"
+ "CDB length of %d exceeds max!\n",
+ ahd_name(ahd), cmd->cmd_len);
+ }
+
+ /*
+ * We perform our own timeout handling.
+ */
+ scsi_delete_timer(cmd);
+
cmd->result = CAM_REQ_INPROG << 16;
TAILQ_INSERT_TAIL(&dev->busyq, (struct ahd_cmd *)cmd, acmd_links.tqe);
if ((dev->flags & AHD_DEV_ON_RUN_LIST) == 0) {
@@ -1341,6 +1508,17 @@
cmd->device->lun);
TAILQ_REMOVE(&dev->busyq, list_acmd, acmd_links.tqe);
cmd->result = DID_ABORT << 16;+ /*
+ * The completion handler believes that
+ * commands without active timers running
+ * have lost the race of completing before
+ * their timer expires. Since commands in
+ * our busy queues do not have timers running,
+ * appease the mid-layer by adding a timer
+ * now. This timer will be immediately
+ * canceled by the midlayer.
+ */
+ scsi_add_timer(cmd, 60*HZ, ahd_linux_midlayer_timeout); ahd_linux_queue_cmd_complete(ahd, cmd); retval = SUCCESS; goto done; @@ -1592,6 +1770,7 @@ recovery_cmd->host_scribble = (char *)scb; scb->io_ctx = recovery_cmd; scb->platform_data->dev = dev; + scb->platform_data->flags = 0; scb->sg_count = 0; ahd_set_residual(scb, 0); ahd_set_sense_residual(scb, 0);
@@ -2160,7 +2339,7 @@
* It is expected that either an external application
* or a modified kernel will be used to probe this
* ID if it is appropriate. To accommodate these
- * installations, ahc_linux_alloc_target() will allocate
+ * installations, ahd_linux_alloc_target() will allocate
* for our ID if asked to do so. */ if (target == ahd->our_id) @@ -2283,10 +2462,14 @@ init_MUTEX_LOCKED(&ahd->platform_data->eh_sem); init_MUTEX_LOCKED(&ahd->platform_data->dv_sem); init_MUTEX_LOCKED(&ahd->platform_data->dv_cmd_sem); + init_MUTEX_LOCKED(&ahd->platform_data->recovery_sem); + init_MUTEX_LOCKED(&ahd->platform_data->recovery_ending_sem); #else ahd->platform_data->eh_sem = MUTEX_LOCKED; ahd->platform_data->dv_sem = MUTEX_LOCKED; ahd->platform_data->dv_cmd_sem = MUTEX_LOCKED; + ahd->platform_data->recovery_sem = MUTEX_LOCKED; + ahd->platform_data->recovery_ending_sem = MUTEX_LOCKED; #endif ahd_setup_runq_tasklet(ahd); ahd->seltime = (aic79xx_seltime & 0x3) << 4; @@ -2538,6 +2721,18 @@ acmd_links.tqe); count++; cmd->result = status << 16;
+ /*
+ * The completion handler believes that
+ * commands without active timers running
+ * have lost the race of completing before
+ * their timer expires. Since commands in
+ * our busy queues do not have timers running,
+ * appease the mid-layer by adding a timer
+ * now. This timer will be immediately
+ * canceled by the midlayer.
+ */
+ scsi_add_timer(cmd, 60*HZ,
+ ahd_linux_midlayer_timeout); ahd_linux_queue_cmd_complete(ahd, cmd); } }
@@ -2573,7 +2768,16 @@
#endif
ahd->platform_data->flags |= AHD_DV_ACTIVE;
+
+ /*
+ * Prevent upper layer from sending any
+ * commands to us.
+ */
ahd_freeze_simq(ahd);
+ scsi_block_requests(ahd->platform_data->host);
+ ahd_platform_abort_scbs(ahd, CAM_TARGET_WILDCARD, ALL_CHANNELS,
+ CAM_LUN_WILDCARD, SCB_LIST_NULL,
+ ROLE_INITIATOR, CAM_REQUEUE_REQ);
/* Wake up the DV kthread */
up(&ahd->platform_data->dv_sem);
@@ -2612,6 +2816,7 @@
unlock_kernel();
while (1) {+
/*
* Use down_interruptible() rather than down() to
* avoid inclusion in the load average.
@@ -2660,13 +2865,16 @@
ahd_lock(ahd, &s); ahd->platform_data->flags &= ~AHD_DV_ACTIVE;
- ahd_unlock(ahd, &s);
/* * Release the SIMQ so that normal commands are * allowed to continue on the bus. */
- ahd_release_simq(ahd);
+ ahd_release_simq_locked(ahd);
+
+ ahd_unlock(ahd, &s);
+
+ scsi_unblock_requests(ahd->platform_data->host); } up(&ahd->platform_data->eh_sem); return (0);
@@ -2699,10 +2907,10 @@
/*
* Mark the dv thread as already dead. This
* avoids attempting to kill it a second time.
- * This is necessary because we must kill the
- * DV thread before calling ahd_free() in the
+ * This is necessary because we must kill our
+ * threads before calling ahd_free() in the
* module shutdown case to avoid bogus locking
- * in the SCSI mid-layer, but we ahd_free() is
+ * in the SCSI mid-layer, but when ahd_free() is
* called without killing the DV thread in the
* instance detach case, so ahd_platform_free()
* calls us again to verify that the DV thread
@@ -2842,8 +3050,6 @@
} /* Queue the command and wait for it to complete */ - /* Abuse eh_timeout in the scsi_cmnd struct for our purposes */ - init_timer(&cmd->eh_timeout); #ifdef AHD_DEBUG if ((ahd_debug & AHD_SHOW_MESSAGES) != 0) /* @@ -2853,7 +3059,9 @@ */ timeout += HZ;
#endif
- scsi_add_timer(cmd, timeout, ahd_linux_dv_timeout);
+ init_timer(&cmd->eh_timeout);
+ cmd->timeout_per_command = timeout;
+
/*
* In 2.5.X, it is assumed that all calls from the
* "midlayer" (which we are emulating) will have the
@@ -4183,6 +4391,7 @@
if ((dev->flags & AHD_DEV_PERIODIC_OTAG) != 0)
dev->commands_since_idle_or_otag++;
scb->flags |= SCB_ACTIVE;
+ ahd_scb_timer_start(scb);
ahd_queue_scb(ahd, scb);
}
}
@@ -4545,9 +4754,39 @@
if ((scb->platform_data->flags & AHD_SCB_UP_EH_SEM) != 0) {
scb->platform_data->flags &= ~AHD_SCB_UP_EH_SEM;
up(&ahd->platform_data->eh_sem);+ } else {
+ struct scb *list_scb;
+
+ /*
+ * We were able to complete the command successfully,
+ * so reinstate the timeouts for all other pending
+ * commands.
+ */
+ LIST_FOREACH(list_scb,
+ &ahd->pending_scbs, pending_links) {
+
+ ahd_scb_timer_start(list_scb);
+ }
} }
+ if ((scb->platform_data->flags & AHD_TIMEOUT_ACTIVE) == 0) {
+ /*
+ * The completion handler believes that
+ * commands without active timers running
+ * have lost the race of completing before
+ * their timer expires. Since commands in
+ * our busy queues do not have timers running,
+ * appease the mid-layer by adding a timer
+ * now. This timer will be immediately
+ * canceled by the midlayer.
+ */
+ scsi_add_timer(cmd, 60*HZ, ahd_linux_midlayer_timeout);
+ }
+
+ if ((scb->platform_data->flags & AHD_RELEASE_SIMQ) != 0)
+ ahd_release_simq_locked(ahd);
+
ahd_free_scb(ahd, scb); ahd_linux_queue_cmd_complete(ahd, cmd);
@@ -4982,42 +5221,31 @@ahd_freeze_simq(struct ahd_softc *ahd)
{
ahd->platform_data->qfrozen++;- if (ahd->platform_data->qfrozen == 1) {
- scsi_block_requests(ahd->platform_data->host);
- ahd_platform_abort_scbs(ahd, CAM_TARGET_WILDCARD, ALL_CHANNELS,
- CAM_LUN_WILDCARD, SCB_LIST_NULL,
- ROLE_INITIATOR, CAM_REQUEUE_REQ);
- }
}
voidahd_release_simq(struct ahd_softc *ahd)
{
u_long s;- int unblock_reqs;
- unblock_reqs = 0; ahd_lock(ahd, &s);
+ ahd_release_simq_locked(ahd);
+ ahd_unlock(ahd, &s);
+}
+
+static void
+ahd_release_simq_locked(struct ahd_softc *ahd)
+{
+
if (ahd->platform_data->qfrozen > 0)
ahd->platform_data->qfrozen--;
- if (ahd->platform_data->qfrozen == 0) {
- unblock_reqs = 1;
- }
if (AHD_DV_SIMQ_FROZEN(ahd)
&& ((ahd->platform_data->flags & AHD_DV_WAIT_SIMQ_RELEASE) != 0)) {
ahd->platform_data->flags &= ~AHD_DV_WAIT_SIMQ_RELEASE;
up(&ahd->platform_data->dv_sem);
}- ahd_schedule_runq(ahd);
- ahd_unlock(ahd, &s);
- /*
- * There is still a race here. The mid-layer
- * should keep its own freeze count and use
- * a bottom half handler to run the queues
- * so we can unblock with our own lock held.
- */
- if (unblock_reqs)
- scsi_unblock_requests(ahd->platform_data->host);
+ if (ahd->platform_data->qfrozen == 0)
+ ahd_schedule_runq(ahd);
}
static void
@@ -5111,20 +5339,18 @@ahd_linux_exit(void)
{
struct ahd_softc *ahd;- u_long l;
/*
- * Shutdown DV threads before going into the SCSI mid-layer.
+ * Shutdown our threads before going into the SCSI mid-layer.
* This avoids situations where the mid-layer locks the entire
* kernel so that waiting for our DV threads to exit leads
* to deadlock.
*/
- ahd_list_lock(&l);
TAILQ_FOREACH(ahd, &ahd_tailq, links) {
ahd_linux_kill_dv_thread(ahd);
+ ahd_terminate_recovery_thread(ahd);
}
- ahd_list_unlock(&l);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
/*
* In 2.4 we have to unregister from the PCI core _after_==== //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic79xx_osm.h#137 - /home/luben/projects/linux/2.6/linux-2.5/drivers/scsi/aic7xxx/aic79xx_osm.h ====
home/luben/projects/linux/2.6/linux-2.5/drivers/scsi/aic7xxx/aic79xx_osm.h renamed from tmp/tmp.26128.1 +1 -1
@@ -36,7 +36,7 @@* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * -* $Id: //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic79xx_osm.h#137 $ +* $Id: //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic79xx_osm.h#138 $ * */
#ifndef _AIC79XX_LINUX_H_
@@ -253,33 +253,6 @@
#endif
#include "aic79xx.h"
-/***************************** Timer Facilities *******************************/ -#define ahd_timer_init init_timer
-#define ahd_timer_stop del_timer_sync
-typedef void ahd_linux_callback_t (u_long);
-static __inline void ahd_timer_reset(ahd_timer_t *timer, u_int usec,
- ahd_callback_t *func, void *arg);
-static __inline void ahd_scb_timer_reset(struct scb *scb, u_int usec); -
-static __inline void
-ahd_timer_reset(ahd_timer_t *timer, u_int usec, ahd_callback_t *func, void *arg) -{
- struct ahd_softc *ahd;
-
- ahd = (struct ahd_softc *)arg;
- del_timer(timer);
- timer->data = (u_long)arg;
- timer->expires = jiffies + (usec * HZ)/1000000;
- timer->function = (ahd_linux_callback_t*)func;
- add_timer(timer);
-}
-
-static __inline void
-ahd_scb_timer_reset(struct scb *scb, u_int usec)
-{
- mod_timer(&scb->io_ctx->eh_timeout, jiffies + (usec * HZ)/1000000);
-}
-
/***************************** SMP support ************************************/ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,17)
#include <linux/spinlock.h>
@@ -505,7 +478,9 @@
* Per-SCB OSM storage.
*/
typedef enum {
- AHD_SCB_UP_EH_SEM = 0x1
+ AHD_SCB_UP_EH_SEM = 0x1,
+ AHD_TIMEOUT_ACTIVE = 0x2,
+ AHD_RELEASE_SIMQ = 0x4
} ahd_linux_scb_flags;
struct scb_platform_data {
@@ -549,14 +524,15 @@
#endif u_int qfrozen; pid_t dv_pid; + pid_t recovery_pid; struct timer_list completeq_timer; struct timer_list reset_timer; struct timer_list stats_timer; struct semaphore eh_sem; struct semaphore dv_sem;
- struct semaphore dv_cmd_sem; /* XXX This needs to be in
- * the target struct
- */
+ struct semaphore dv_cmd_sem;
+ struct semaphore recovery_sem;
+ struct semaphore recovery_ending_sem; struct scsi_device *dv_scsi_dev; struct Scsi_Host *host; /* pointer to scsi host */ #define AHD_LINUX_NOIRQ ((uint32_t)~0) @@ -567,6 +543,72 @@ ahd_linux_softc_flags flags; };
+/***************************** Timer Facilities *******************************/
+void ahd_platform_timeout(struct scsi_cmnd *);
+void ahd_linux_midlayer_timeout(struct scsi_cmnd *);
+
+#define ahd_timer_init init_timer
+#define ahd_timer_stop del_timer_sync
+typedef void ahd_linux_callback_t (u_long);
+static __inline void ahd_timer_reset(ahd_timer_t *timer, uint32_t usec,
+ ahd_callback_t *func, void *arg);
+static __inline uint32_t ahd_get_timeout(struct scb *);
+static __inline void ahd_scb_timer_start(struct scb *scb);
+static __inline void ahd_scb_timer_reset(struct scb *scb, uint32_t usec);
+
+static __inline void
+ahd_timer_reset(ahd_timer_t *timer, uint32_t usec,
+ ahd_callback_t *func, void *arg)
+{
+ struct ahd_softc *ahd;
+
+ ahd = (struct ahd_softc *)arg;
+ del_timer(timer);
+ timer->data = (u_long)arg;
+ timer->expires = jiffies + (usec * HZ)/1000000;
+ timer->function = (ahd_linux_callback_t*)func;
+ add_timer(timer);
+}
+
+static __inline uint32_t
+ahd_get_timeout(struct scb *scb)
+{
+
+ /*
+ * Convert from jiffies to usec.
+ */
+ return (scb->io_ctx->timeout_per_command * (1000000/HZ));
+}
+
+static __inline void
+ahd_scb_timer_start(struct scb *scb)
+{
+ scb->platform_data->flags |= AHD_TIMEOUT_ACTIVE;
+ scsi_add_timer(scb->io_ctx, scb->io_ctx->timeout_per_command,
+ ahd_platform_timeout);
+}
+
+static __inline void
+ahd_scb_timer_reset(struct scb *scb, uint32_t usec)
+{
+ scb->platform_data->flags |= AHD_TIMEOUT_ACTIVE;
+ mod_timer(&scb->io_ctx->eh_timeout, jiffies + (usec * HZ)/1000000);
+}
+
+/************************** Error Recovery ************************************/
+static __inline void ahd_wakeup_recovery_thread(struct ahd_softc *ahd);
+
+static __inline void
+ahd_wakeup_recovery_thread(struct ahd_softc *ahd)
+{
+ up(&ahd->platform_data->recovery_sem);
+}
+
+int ahd_spawn_recovery_thread(struct ahd_softc *ahd);
+void ahd_terminate_recovery_thread(struct ahd_softc *ahd);
+void ahd_set_recoveryscb(struct ahd_softc *ahd,
+ struct scb *scb);
+
/************************** OS Utility Wrappers *******************************/
#define printf printk
#define M_NOWAIT GFP_ATOMIC
==== //depot/aic7xxx/aic7xxx/aic7xxx.h#81 - /home/luben/projects/linux/2.6/linux-2.5/drivers/scsi/aic7xxx/aic7xxx.h ====
home/luben/projects/linux/2.6/linux-2.5/drivers/scsi/aic7xxx/aic7xxx.h renamed from tmp/tmp.26128.2 +1 -1
@@ -37,7 +37,7 @@* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * -* $Id: //depot/aic7xxx/aic7xxx/aic7xxx.h#81 $ +* $Id: //depot/aic7xxx/aic7xxx/aic7xxx.h#82 $ * * $FreeBSD$ */
@@ -412,6 +412,7 @@
uint8_t initiator_tag; /* Initiator's transaction tag */
};
+#define MAX_CDB_LEN 16
struct hardware_scb {
/*0*/ union {
/*==== //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic7xxx_osm.c#235 - /home/luben/projects/linux/2.6/linux-2.5/drivers/scsi/aic7xxx/aic7xxx_osm.c ====
home/luben/projects/linux/2.6/linux-2.5/drivers/scsi/aic7xxx/aic7xxx_osm.c renamed from tmp/tmp.26128.3 +0 -0
@@ -1,7 +1,7 @@
/*
* Adaptec AIC7xxx device driver for Linux.
*
- * $Id: //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic7xxx_osm.c#235 $
+ * $Id: //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic7xxx_osm.c#236 $ *
* Copyright (c) 1994 John Aycock
* The University of Calgary Department of Computer Science.
@@ -490,6 +490,7 @@
static void ahc_linux_sem_timeout(u_long arg);
static void ahc_linux_freeze_simq(struct ahc_softc *ahc); static void ahc_linux_release_simq(u_long arg);
+static void ahc_linux_release_simq_locked(struct ahc_softc *ahc); static void ahc_linux_dev_timed_unfreeze(u_long arg);
static int ahc_linux_queue_recovery_cmd(Scsi_Cmnd *cmd, scb_flag flag); static void ahc_linux_initialize_scsi_bus(struct ahc_softc *ahc); @@ -836,6 +837,156 @@
#endif
}
+/************************** Error Recovery ************************************/ +static int ahc_linux_recovery_thread(void *arg);
+
+static int
+ahc_linux_recovery_thread(void *arg)
+{
+ struct ahc_softc *ahc;
+ u_long s;
+
+ ahc = (struct ahc_softc *)arg;
+
+ /*
+ * Complete thread creation.
+ */
+ lock_kernel();
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,60)
+ /*
+ * Don't care about any signals.
+ */
+ siginitsetinv(¤t->blocked, 0);
+
+ daemonize();
+ sprintf(current->comm, "ahc_dv_%d", ahc->unit);
+#else
+ daemonize("ahc_recovery_%d", ahc->unit);
+#endif
+ unlock_kernel();
+
+ while (1) {
+
+ /*
+ * Use down_interruptible() rather than down() to
+ * avoid inclusion in the load average.
+ */
+ down_interruptible(&ahc->platform_data->recovery_sem);
+
+ ahc_lock(ahc, &s);
+ if ((ahc->flags & AHC_SHUTDOWN_RECOVERY) != 0) {
+ ahc_unlock(ahc, &s);
+ break;
+ }
+ ahc_unlock(ahc, &s);
+ ahc_recover_commands(ahc);
+ }
+ up(&ahc->platform_data->recovery_ending_sem);
+ return(0);
+}
+
+int
+ahc_spawn_recovery_thread(struct ahc_softc *ahc)
+{
+ ahc->platform_data->recovery_pid =
+ kernel_thread(ahc_linux_recovery_thread, ahc, 0);
+ return (0);
+}
+
+void
+ahc_terminate_recovery_thread(struct ahc_softc *ahc)
+{
+ u_long s;
+
+ ahc_lock(ahc, &s);
+ if (ahc->platform_data->recovery_pid != 0) {
+ ahc->flags |= AHC_SHUTDOWN_RECOVERY;
+ ahc_unlock(ahc, &s);
+ up(&ahc->platform_data->recovery_sem);
+
+ /*
+ * Use the recovery_ending_sem as an indicator that
+ * the dv thread is exiting. Note that the dv
+ * thread must still return after performing
+ * the up on our semaphore before it has
+ * completely exited this module. Unfortunately,
+ * there seems to be no easy way to wait for the
+ * exit of a thread for which you are not the
+ * parent (dv threads are parented by init).
+ * Cross your fingers...
+ */
+ down(&ahc->platform_data->recovery_ending_sem);
+
+ /*
+ * Mark the recovery thread as already dead. This
+ * avoids attempting to kill it a second time.
+ * This is necessary because we must kill the
+ * our threads before calling ahc_free() in the
+ * module shutdown case to avoid bogus locking
+ * in the SCSI mid-layer, but when ahc_free() is
+ * called without killing the DV thread in the
+ * instance detach case, so ahc_platform_free()
+ * calls us again to verify that the DV thread
+ * is dead.
+ */
+ ahc->platform_data->recovery_pid = 0;
+ } else {
+ ahc_unlock(ahc, &s);
+ }
+}
+
+void
+ahc_set_recoveryscb(struct ahc_softc *ahc, struct scb *scb)
+{
+ if ((scb->flags & SCB_RECOVERY_SCB) == 0) {
+ struct scb *list_scb;
+
+ scb->flags |= SCB_RECOVERY_SCB;
+
+ /*
+ * Take all queued, but not sent SCBs out of the equation.
+ * Also ensure that no new commands are queued to us while we
+ * try to fix this problem.
+ */
+ if ((scb->platform_data->flags & AHC_RELEASE_SIMQ) == 0) {
+ ahc_linux_freeze_simq(ahc);
+ scb->platform_data->flags |= AHC_RELEASE_SIMQ;
+ }
+
+ /*
+ * Go through all of our pending SCBs and remove
+ * any scheduled timeouts for them. We will reschedule
+ * them after we've successfully fixed this problem.
+ */
+ LIST_FOREACH(list_scb, &ahc->pending_scbs, pending_links) {
+
+ scsi_delete_timer(list_scb->io_ctx);
+ scb->platform_data->flags &= ~AHC_TIMEOUT_ACTIVE;
+ }
+ }
+}
+
+void
+ahc_platform_timeout(struct scsi_cmnd *cmd)
+{
+
+ if (AHC_DV_CMD(cmd) == 0) {
+
+ ahc_linux_dv_timeout(cmd);
+ } else {
+ struct scb *scb;
+
+ scb = (struct scb *)cmd->host_scribble;
+ scb->platform_data->flags &= ~AHC_TIMEOUT_ACTIVE;
+ ahc_timeout(scb);
+ }
+}
+
+void
+ahc_linux_midlayer_timeout(struct scsi_cmnd *cmd)
+{
+}
+/************************ Linux Entry Points **********************************/ /*
* Try to detect an Adaptec 7XXX controller.
*/
@@ -1015,6 +1166,7 @@
dev = ahc_linux_get_device(ahc, cmd->device->channel, cmd->device->id,
cmd->device->lun, /*alloc*/TRUE);
if (dev == NULL) {
+
ahc_cmd_set_transaction_status(cmd, CAM_RESRC_UNAVAIL);
ahc_linux_queue_cmd_complete(ahc, cmd);
ahc_schedule_completeq(ahc);
@@ -1023,6 +1175,23 @@
ahc_name(ahc));
return (0);
}+
+ if (cmd->cmd_len > MAX_CDB_LEN) {
+
+ ahc_cmd_set_transaction_status(cmd, CAM_REQ_INVALID);
+ ahc_linux_queue_cmd_complete(ahc, cmd);
+ ahc_schedule_completeq(ahc);
+ ahc_midlayer_entrypoint_unlock(ahc, &flags);
+ printf("%s: aic7xxx_linux_queue -"
+ "CDB length of %d exceeds max!\n",
+ ahc_name(ahc), cmd->cmd_len);
+ }
+
+ /*
+ * We perform our own timeout handling.
+ */
+ scsi_delete_timer(cmd);
+
cmd->result = CAM_REQ_INPROG << 16;
TAILQ_INSERT_TAIL(&dev->busyq, (struct ahc_cmd *)cmd, acmd_links.tqe);
if ((dev->flags & AHC_DEV_ON_RUN_LIST) == 0) {
@@ -1947,10 +2116,14 @@
init_MUTEX_LOCKED(&ahc->platform_data->eh_sem);
init_MUTEX_LOCKED(&ahc->platform_data->dv_sem);
init_MUTEX_LOCKED(&ahc->platform_data->dv_cmd_sem);
+ init_MUTEX_LOCKED(&ahc->platform_data->recovery_sem);
+ init_MUTEX_LOCKED(&ahc->platform_data->recovery_ending_sem);
#else
ahc->platform_data->eh_sem = MUTEX_LOCKED;
ahc->platform_data->dv_sem = MUTEX_LOCKED;
ahc->platform_data->dv_cmd_sem = MUTEX_LOCKED;
+ ahc->platform_data->recovery_sem = MUTEX_LOCKED;
+ ahc->platform_data->recovery_ending_sem = MUTEX_LOCKED;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
tasklet_init(&ahc->platform_data->runq_tasklet, ahc_runq_tasklet,
@@ -2200,6 +2373,20 @@
acmd_links.tqe);
count++;
cmd->result = status << 16;+ /*
+ * The completion handler believes that
+ * commands without active timers
+ * running have lost the race of
+ * completing before their timer
+ * expires. Since commands in our
+ * busy queues do not have timers
+ * running, appease the mid-layer by
+ * adding a timer now. This timer will
+ * be immediately canceled by the
+ * midlayer.
+ */
+ scsi_add_timer(cmd, 60*HZ,
+ ahc_linux_midlayer_timeout); ahc_linux_queue_cmd_complete(ahc, cmd); } }
@@ -2236,7 +2423,16 @@
#endif
ahc->platform_data->flags |= AHC_DV_ACTIVE;
+
+ /*
+ * Prevent upper layer from sending any
+ * commands to us.
+ */
ahc_linux_freeze_simq(ahc);
+ scsi_block_requests(ahc->platform_data->host);
+ ahc_platform_abort_scbs(ahc, CAM_TARGET_WILDCARD, ALL_CHANNELS,
+ CAM_LUN_WILDCARD, SCB_LIST_NULL,
+ ROLE_INITIATOR, CAM_REQUEUE_REQ);
/* Wake up the DV kthread */ up(&ahc->platform_data->dv_sem);
@@ -2365,13 +2561,15 @@
ahc_lock(ahc, &s); ahc->platform_data->flags &= ~AHC_DV_ACTIVE;
- ahc_unlock(ahc, &s);
/* * Release the SIMQ so that normal commands are * allowed to continue on the bus. */
- ahc_linux_release_simq((u_long)ahc);
+ ahc_linux_release_simq_locked(ahc);
+ ahc_unlock(ahc, &s);
+
+ scsi_unblock_requests(ahc->platform_data->host); } up(&ahc->platform_data->eh_sem); return (0); @@ -2507,8 +2705,6 @@ } /* Queue the command and wait for it to complete */ - /* Abuse eh_timeout in the scsi_cmnd struct for our purposes */ - init_timer(&cmd->eh_timeout); #ifdef AHC_DEBUG if ((ahc_debug & AHC_SHOW_MESSAGES) != 0) /* @@ -2518,7 +2714,9 @@ */ timeout += HZ;
#endif
- scsi_add_timer(cmd, timeout, ahc_linux_dv_timeout);
+ init_timer(&cmd->eh_timeout);
+ cmd->timeout_per_command = timeout;
+
/*
* In 2.5.X, it is assumed that all calls from the
* "midlayer" (which we are emulating) will have the
@@ -3689,6 +3887,7 @@
cmd = &acmd_scsi_cmd(acmd); scb->io_ctx = cmd; scb->platform_data->dev = dev; + scb->platform_data->flags = 0; hscb = scb->hscb; cmd->host_scribble = (char *)scb;
@@ -3856,6 +4055,7 @@
continue;
}
scb->flags |= SCB_ACTIVE;
+ ahc_scb_timer_start(scb);
ahc_queue_scb(ahc, scb);
}
}
@@ -4243,9 +4443,39 @@
if ((ahc->platform_data->flags & AHC_UP_EH_SEMAPHORE) != 0) {
ahc->platform_data->flags &= ~AHC_UP_EH_SEMAPHORE;
up(&ahc->platform_data->eh_sem);+ } else {
+ struct scb *list_scb;
+
+ /*
+ * We were able to complete the command successfully,
+ * so reinstate the timeouts for all other pending
+ * commands.
+ */
+ LIST_FOREACH(list_scb,
+ &ahc->pending_scbs, pending_links) {
+
+ ahc_scb_timer_start(list_scb);
+ }
} }
+ if ((scb->platform_data->flags & AHC_TIMEOUT_ACTIVE) == 0) {
+ /*
+ * The completion handler believes that
+ * commands without active timers running
+ * have lost the race of completing before
+ * their timer expires. Since commands in
+ * our busy queues do not have timers running,
+ * appease the mid-layer by adding a timer
+ * now. This timer will be immediately
+ * canceled by the midlayer.
+ */
+ scsi_add_timer(cmd, 60*HZ, ahc_linux_midlayer_timeout);
+ }
+
+ if ((scb->platform_data->flags & AHC_RELEASE_SIMQ) != 0)
+ ahc_linux_release_simq_locked(ahc);
+
ahc_free_scb(ahc, scb); ahc_linux_queue_cmd_complete(ahc, cmd);
@@ -4650,14 +4880,6 @@ahc_linux_freeze_simq(struct ahc_softc *ahc)
{
ahc->platform_data->qfrozen++;- if (ahc->platform_data->qfrozen == 1) {
- scsi_block_requests(ahc->platform_data->host);
-
- /* XXX What about Twin channels? */
- ahc_platform_abort_scbs(ahc, CAM_TARGET_WILDCARD, ALL_CHANNELS,
- CAM_LUN_WILDCARD, SCB_LIST_NULL,
- ROLE_INITIATOR, CAM_REQUEUE_REQ);
- }
}
static void
@@ -4665,31 +4887,27 @@
{
struct ahc_softc *ahc;
u_long s;- int unblock_reqs;
ahc = (struct ahc_softc *)arg;
- unblock_reqs = 0; ahc_lock(ahc, &s);
+ ahc_linux_release_simq_locked(ahc);
+ ahc_unlock(ahc, &s);
+}
+
+static void +ahc_linux_release_simq_locked(struct ahc_softc *ahc) +{
+
if (ahc->platform_data->qfrozen > 0)
ahc->platform_data->qfrozen--;
- if (ahc->platform_data->qfrozen == 0)
- unblock_reqs = 1;
if (AHC_DV_SIMQ_FROZEN(ahc)
&& ((ahc->platform_data->flags & AHC_DV_WAIT_SIMQ_RELEASE) != 0)) {
ahc->platform_data->flags &= ~AHC_DV_WAIT_SIMQ_RELEASE;
up(&ahc->platform_data->dv_sem);
}- ahc_schedule_runq(ahc);
- ahc_unlock(ahc, &s);
- /*
- * There is still a race here. The mid-layer
- * should keep its own freeze count and use
- * a bottom half handler to run the queues
- * so we can unblock with our own lock held.
- */
- if (unblock_reqs)
- scsi_unblock_requests(ahc->platform_data->host);
+ if (ahc->platform_data->qfrozen == 0)
+ ahc_schedule_runq(ahc);
}
static void
@@ -4800,6 +5018,17 @@
if (flag == SCB_ABORT) {
TAILQ_REMOVE(&dev->busyq, list_acmd, acmd_links.tqe);
cmd->result = DID_ABORT << 16;+ /*
+ * The completion handler believes that
+ * commands without active timers running
+ * have lost the race of completing before
+ * their timer expires. Since commands in our
+ * busy queues do not have timers running,
+ * appease the mid-layer by adding a timer
+ * now. This timer will be immediately
+ * canceled by the midlayer.
+ */
+ scsi_add_timer(cmd, 60*HZ, ahc_linux_midlayer_timeout);
ahc_linux_queue_cmd_complete(ahc, cmd);
retval = SUCCESS;
goto done;
@@ -5105,20 +5334,18 @@
ahc_linux_exit(void)
{
struct ahc_softc *ahc;- u_long l;
/*
- * Shutdown DV threads before going into the SCSI mid-layer.
+ * Shutdown our threads before going into the SCSI mid-layer.
* This avoids situations where the mid-layer locks the entire
* kernel so that waiting for our DV threads to exit leads
* to deadlock.
*/
- ahc_list_lock(&l);
TAILQ_FOREACH(ahc, &ahc_tailq, links) {
ahc_linux_kill_dv_thread(ahc);
+ ahc_terminate_recovery_thread(ahc);
}- ahc_list_unlock(&l);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) /*
==== //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic7xxx_osm.h#151 - /home/luben/projects/linux/2.6/linux-2.5/drivers/scsi/aic7xxx/aic7xxx_osm.h ====
home/luben/projects/linux/2.6/linux-2.5/drivers/scsi/aic7xxx/aic7xxx_osm.h renamed from tmp/tmp.26128.4 +1 -1
@@ -53,7 +53,7 @@* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * -* $Id: //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic7xxx_osm.h#151 $ +* $Id: //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic7xxx_osm.h#152 $ * */
#ifndef _AIC7XXX_LINUX_H_
@@ -265,33 +265,6 @@
#endif
#include "aic7xxx.h"
-/***************************** Timer Facilities *******************************/ -#define ahc_timer_init init_timer
-#define ahc_timer_stop del_timer_sync
-typedef void ahc_linux_callback_t (u_long);
-static __inline void ahc_timer_reset(ahc_timer_t *timer, int usec,
- ahc_callback_t *func, void *arg);
-static __inline void ahc_scb_timer_reset(struct scb *scb, u_int usec); -
-static __inline void
-ahc_timer_reset(ahc_timer_t *timer, int usec, ahc_callback_t *func, void *arg) -{
- struct ahc_softc *ahc;
-
- ahc = (struct ahc_softc *)arg;
- del_timer(timer);
- timer->data = (u_long)arg;
- timer->expires = jiffies + (usec * HZ)/1000000;
- timer->function = (ahc_linux_callback_t*)func;
- add_timer(timer);
-}
-
-static __inline void
-ahc_scb_timer_reset(struct scb *scb, u_int usec)
-{
- mod_timer(&scb->io_ctx->eh_timeout, jiffies + (usec * HZ)/1000000);
-}
-
/***************************** SMP support ************************************/ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,17)
#include <linux/spinlock.h>
@@ -511,7 +484,9 @@
* Per-SCB OSM storage.
*/
typedef enum {
- AHC_UP_EH_SEMAPHORE = 0x1
+ AHC_UP_EH_SEMAPHORE = 0x1,
+ AHC_TIMEOUT_ACTIVE = 0x2,
+ AHC_RELEASE_SIMQ = 0x4
} ahc_linux_scb_flags;
struct scb_platform_data {
@@ -555,13 +530,14 @@
#endif u_int qfrozen; pid_t dv_pid; + pid_t recovery_pid; struct timer_list completeq_timer; struct timer_list reset_timer; struct semaphore eh_sem; struct semaphore dv_sem;
- struct semaphore dv_cmd_sem; /* XXX This needs to be in
- * the target struct
- */
+ struct semaphore dv_cmd_sem;
+ struct semaphore recovery_sem;
+ struct semaphore recovery_ending_sem; struct scsi_device *dv_scsi_dev; struct Scsi_Host *host; /* pointer to scsi host */ #define AHC_LINUX_NOIRQ ((uint32_t)~0) @@ -572,6 +548,73 @@ ahc_linux_softc_flags flags; };
+/***************************** Timer Facilities *******************************/ +void ahc_platform_timeout(struct scsi_cmnd *);
+void ahc_linux_midlayer_timeout(struct scsi_cmnd *);
+
+#define ahc_timer_init init_timer
+#define ahc_timer_stop del_timer_sync
+typedef void ahc_linux_callback_t (u_long);
+static __inline void ahc_timer_reset(ahc_timer_t *timer, uint32_t usec,
+ ahc_callback_t *func, void *arg);
+static __inline uint32_t ahc_get_timeout(struct scb *);
+static __inline void ahc_scb_timer_start(struct scb *scb);
+static __inline void ahc_scb_timer_reset(struct scb *scb, uint32_t usec); +
+static __inline void
+ahc_timer_reset(ahc_timer_t *timer, uint32_t usec,
+ ahc_callback_t *func, void *arg)
+{
+ struct ahc_softc *ahc;
+
+ ahc = (struct ahc_softc *)arg;
+ del_timer(timer);
+ timer->data = (u_long)arg;
+ timer->expires = jiffies + (usec * HZ)/1000000;
+ timer->function = (ahc_linux_callback_t*)func;
+ add_timer(timer);
+}
+
+static __inline uint32_t
+ahc_get_timeout(struct scb *scb)
+{
+
+ /*
+ * Convert from jiffies to usec avoiding
+ * overflow and truncation.
+ */
+ return (scb->io_ctx->timeout_per_command * (1000000/HZ));
+}
+
+static __inline void
+ahc_scb_timer_start(struct scb *scb)
+{
+ scb->platform_data->flags |= AHC_TIMEOUT_ACTIVE;
+ scsi_add_timer(scb->io_ctx, scb->io_ctx->timeout_per_command,
+ ahc_platform_timeout);
+}
+
+static __inline void
+ahc_scb_timer_reset(struct scb *scb, uint32_t usec)
+{
+ scb->platform_data->flags |= AHC_TIMEOUT_ACTIVE;
+ mod_timer(&scb->io_ctx->eh_timeout, jiffies + (usec * HZ)/1000000);
+}
+
+/************************** Error Recovery ************************************/
+static __inline void ahc_wakeup_recovery_thread(struct ahc_softc *ahc);
+
+static __inline void
+ahc_wakeup_recovery_thread(struct ahc_softc *ahc)
+{
+ up(&ahc->platform_data->recovery_sem);
+}
+
+int ahc_spawn_recovery_thread(struct ahc_softc *ahc);
+void ahc_terminate_recovery_thread(struct ahc_softc *ahc);
+void ahc_set_recoveryscb(struct ahc_softc *ahc,
+ struct scb *scb);
+
/************************** OS Utility Wrappers *******************************/ #define printf printk
#define M_NOWAIT GFP_ATOMIC
Review
Quoted 4 lines · Luben Tuikov
(quoted diff) +4
-0+ /* + * We perform our own timeout handling. + */ + scsi_delete_timer(cmd);
this surely looks unacceptable as well.