[SCSI] aic94xx: fix TMF ascb handling to prevent sequencer panic

This is a particularly nasty bug.  The problem is that if any internal
ascb times out, currently we free it even though it's pending at the
sequencer.  This results in the sequencer getting terminally confused
and the error message:

BUG:sequencer:dl:no ascb

Being returned when it comes back.  The way to fix this is to manage
freeing the ascb from the tasklet completion routine, so that we only
free it when the sequencer actually returns it.  The code is also
altered to use on stack completions and transfer variables.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
diff --git a/drivers/scsi/aic94xx/aic94xx_tmf.c b/drivers/scsi/aic94xx/aic94xx_tmf.c
index d684c74..b9ac8f7 100644
--- a/drivers/scsi/aic94xx/aic94xx_tmf.c
+++ b/drivers/scsi/aic94xx/aic94xx_tmf.c
@@ -53,50 +53,64 @@
 	return res;
 }
 
-static inline void asd_timedout_common(unsigned long data)
-{
-	struct asd_ascb *ascb = (void *) data;
-	struct asd_seq_data *seq = &ascb->ha->seq;
-        unsigned long flags;
-
-	spin_lock_irqsave(&seq->pend_q_lock, flags);
-        seq->pending--;
-        list_del_init(&ascb->list);
-        spin_unlock_irqrestore(&seq->pend_q_lock, flags);
-}
-
 /* ---------- CLEAR NEXUS ---------- */
 
+struct tasklet_completion_status {
+	int	dl_opcode;
+	int	tmf_state;
+	u8	tag_valid:1;
+	__be16	tag;
+};
+
+#define DECLARE_TCS(tcs) \
+	struct tasklet_completion_status tcs = { \
+		.dl_opcode = 0, \
+		.tmf_state = 0, \
+		.tag_valid = 0, \
+		.tag = 0, \
+	}
+
+
 static void asd_clear_nexus_tasklet_complete(struct asd_ascb *ascb,
 					     struct done_list_struct *dl)
 {
+	struct tasklet_completion_status *tcs = ascb->uldd_task;
 	ASD_DPRINTK("%s: here\n", __FUNCTION__);
 	if (!del_timer(&ascb->timer)) {
 		ASD_DPRINTK("%s: couldn't delete timer\n", __FUNCTION__);
 		return;
 	}
 	ASD_DPRINTK("%s: opcode: 0x%x\n", __FUNCTION__, dl->opcode);
-	ascb->uldd_task = (void *) (unsigned long) dl->opcode;
-	complete(&ascb->completion);
+	tcs->dl_opcode = dl->opcode;
+	complete(ascb->completion);
+	asd_ascb_free(ascb);
 }
 
 static void asd_clear_nexus_timedout(unsigned long data)
 {
-	struct asd_ascb *ascb = (void *) data;
+	struct asd_ascb *ascb = (void *)data;
+	struct tasklet_completion_status *tcs = ascb->uldd_task;
 
 	ASD_DPRINTK("%s: here\n", __FUNCTION__);
-	asd_timedout_common(data);
-	ascb->uldd_task = (void *) TMF_RESP_FUNC_FAILED;
-	complete(&ascb->completion);
+	tcs->dl_opcode = TMF_RESP_FUNC_FAILED;
+	complete(ascb->completion);
 }
 
 #define CLEAR_NEXUS_PRE         \
+	struct asd_ascb *ascb; \
+	struct scb *scb; \
+	int res; \
+	DECLARE_COMPLETION_ONSTACK(completion); \
+	DECLARE_TCS(tcs); \
+		\
 	ASD_DPRINTK("%s: PRE\n", __FUNCTION__); \
         res = 1;                \
 	ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL); \
 	if (!ascb)              \
 		return -ENOMEM; \
                                 \
+	ascb->completion = &completion; \
+	ascb->uldd_task = &tcs; \
 	scb = ascb->scb;        \
 	scb->header.opcode = CLEAR_NEXUS
 
@@ -107,10 +121,11 @@
 	if (res)                \
 		goto out_err;   \
 	ASD_DPRINTK("%s: clear nexus posted, waiting...\n", __FUNCTION__); \
-	wait_for_completion(&ascb->completion); \
-	res = (int) (unsigned long) ascb->uldd_task; \
+	wait_for_completion(&completion); \
+	res = tcs.dl_opcode; \
 	if (res == TC_NO_ERROR) \
 		res = TMF_RESP_FUNC_COMPLETE;   \
+	return res; \
 out_err:                        \
 	asd_ascb_free(ascb);    \
 	return res
@@ -118,9 +133,6 @@
 int asd_clear_nexus_ha(struct sas_ha_struct *sas_ha)
 {
 	struct asd_ha_struct *asd_ha = sas_ha->lldd_ha;
-	struct asd_ascb *ascb;
-	struct scb *scb;
-	int res;
 
 	CLEAR_NEXUS_PRE;
 	scb->clear_nexus.nexus = NEXUS_ADAPTER;
@@ -130,9 +142,6 @@
 int asd_clear_nexus_port(struct asd_sas_port *port)
 {
 	struct asd_ha_struct *asd_ha = port->ha->lldd_ha;
-	struct asd_ascb *ascb;
-	struct scb *scb;
-	int res;
 
 	CLEAR_NEXUS_PRE;
 	scb->clear_nexus.nexus = NEXUS_PORT;
@@ -150,9 +159,6 @@
 			       enum clear_nexus_phase phase)
 {
 	struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
-	struct asd_ascb *ascb;
-	struct scb *scb;
-	int res;
 
 	CLEAR_NEXUS_PRE;
 	scb->clear_nexus.nexus = NEXUS_I_T;
@@ -210,9 +216,6 @@
 static int asd_clear_nexus_I_T_L(struct domain_device *dev, u8 *lun)
 {
 	struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
-	struct asd_ascb *ascb;
-	struct scb *scb;
-	int res;
 
 	CLEAR_NEXUS_PRE;
 	scb->clear_nexus.nexus = NEXUS_I_T_L;
@@ -227,9 +230,6 @@
 {
 	struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha;
 	struct asd_ascb *tascb = task->lldd_task;
-	struct asd_ascb *ascb;
-	struct scb *scb;
-	int res;
 
 	CLEAR_NEXUS_PRE;
 	scb->clear_nexus.nexus = NEXUS_TAG;
@@ -245,9 +245,6 @@
 {
 	struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha;
 	struct asd_ascb *tascb = task->lldd_task;
-	struct asd_ascb *ascb;
-	struct scb *scb;
-	int res;
 
 	CLEAR_NEXUS_PRE;
 	scb->clear_nexus.nexus = NEXUS_TRANS_CX;
@@ -263,11 +260,11 @@
 static void asd_tmf_timedout(unsigned long data)
 {
 	struct asd_ascb *ascb = (void *) data;
+	struct tasklet_completion_status *tcs = ascb->uldd_task;
 
 	ASD_DPRINTK("tmf timed out\n");
-	asd_timedout_common(data);
-	ascb->uldd_task = (void *) TMF_RESP_FUNC_FAILED;
-	complete(&ascb->completion);
+	tcs->tmf_state = TMF_RESP_FUNC_FAILED;
+	complete(ascb->completion);
 }
 
 static int asd_get_tmf_resp_tasklet(struct asd_ascb *ascb,
@@ -319,18 +316,24 @@
 static void asd_tmf_tasklet_complete(struct asd_ascb *ascb,
 				     struct done_list_struct *dl)
 {
+	struct tasklet_completion_status *tcs;
+
 	if (!del_timer(&ascb->timer))
 		return;
 
+	tcs = ascb->uldd_task;
 	ASD_DPRINTK("tmf tasklet complete\n");
 
-	if (dl->opcode == TC_SSP_RESP)
-		ascb->uldd_task = (void *) (unsigned long)
-			asd_get_tmf_resp_tasklet(ascb, dl);
-	else
-		ascb->uldd_task = (void *) 0xFF00 + (unsigned long) dl->opcode;
+	tcs->dl_opcode = dl->opcode;
 
-	complete(&ascb->completion);
+	if (dl->opcode == TC_SSP_RESP) {
+		tcs->tmf_state = asd_get_tmf_resp_tasklet(ascb, dl);
+		tcs->tag_valid = ascb->tag_valid;
+		tcs->tag = ascb->tag;
+	}
+
+	complete(ascb->completion);
+	asd_ascb_free(ascb);
 }
 
 static inline int asd_clear_nexus(struct sas_task *task)
@@ -338,15 +341,19 @@
 	int res = TMF_RESP_FUNC_FAILED;
 	int leftover;
 	struct asd_ascb *tascb = task->lldd_task;
+	DECLARE_COMPLETION_ONSTACK(completion);
 	unsigned long flags;
 
+	tascb->completion = &completion;
+
 	ASD_DPRINTK("task not done, clearing nexus\n");
 	if (tascb->tag_valid)
 		res = asd_clear_nexus_tag(task);
 	else
 		res = asd_clear_nexus_index(task);
-	leftover = wait_for_completion_timeout(&tascb->completion,
+	leftover = wait_for_completion_timeout(&completion,
 					       AIC94XX_SCB_TIMEOUT);
+	tascb->completion = NULL;
 	ASD_DPRINTK("came back from clear nexus\n");
 	spin_lock_irqsave(&task->task_state_lock, flags);
 	if (leftover < 1)
@@ -400,6 +407,11 @@
 	struct asd_ascb *ascb = NULL;
 	struct scb *scb;
 	int leftover;
+	DECLARE_TCS(tcs);
+	DECLARE_COMPLETION_ONSTACK(completion);
+	DECLARE_COMPLETION_ONSTACK(tascb_completion);
+
+	tascb->completion = &tascb_completion;
 
 	spin_lock_irqsave(&task->task_state_lock, flags);
 	if (task->task_state_flags & SAS_TASK_STATE_DONE) {
@@ -413,8 +425,10 @@
 	ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL);
 	if (!ascb)
 		return -ENOMEM;
-	scb = ascb->scb;
 
+	ascb->uldd_task = &tcs;
+	ascb->completion = &completion;
+	scb = ascb->scb;
 	scb->header.opcode = SCB_ABORT_TASK;
 
 	switch (task->task_proto) {
@@ -456,13 +470,12 @@
 	res = asd_enqueue_internal(ascb, asd_tmf_tasklet_complete,
 				   asd_tmf_timedout);
 	if (res)
-		goto out;
-	wait_for_completion(&ascb->completion);
+		goto out_free;
+	wait_for_completion(&completion);
 	ASD_DPRINTK("tmf came back\n");
 
-	res = (int) (unsigned long) ascb->uldd_task;
-	tascb->tag = ascb->tag;
-	tascb->tag_valid = ascb->tag_valid;
+	tascb->tag = tcs.tag;
+	tascb->tag_valid = tcs.tag_valid;
 
 	spin_lock_irqsave(&task->task_state_lock, flags);
 	if (task->task_state_flags & SAS_TASK_STATE_DONE) {
@@ -473,63 +486,68 @@
 	}
 	spin_unlock_irqrestore(&task->task_state_lock, flags);
 
-	switch (res) {
-	/* The task to be aborted has been sent to the device.
-	 * We got a Response IU for the ABORT TASK TMF. */
-	case TC_NO_ERROR + 0xFF00:
-	case TMF_RESP_FUNC_COMPLETE:
-	case TMF_RESP_FUNC_FAILED:
-		res = asd_clear_nexus(task);
-		break;
-	case TMF_RESP_INVALID_FRAME:
-	case TMF_RESP_OVERLAPPED_TAG:
-	case TMF_RESP_FUNC_ESUPP:
-	case TMF_RESP_NO_LUN:
-		goto out_done; break;
-	}
-	/* In the following we assume that the managing layer
-	 * will _never_ make a mistake, when issuing ABORT TASK.
-	 */
-	switch (res) {
-	default:
-		res = asd_clear_nexus(task);
-		/* fallthrough */
-	case TC_NO_ERROR + 0xFF00:
-	case TMF_RESP_FUNC_COMPLETE:
-		break;
-	/* The task hasn't been sent to the device xor we never got
-	 * a (sane) Response IU for the ABORT TASK TMF.
-	 */
-	case TF_NAK_RECV + 0xFF00:
-		res = TMF_RESP_INVALID_FRAME;
-		break;
-	case TF_TMF_TASK_DONE + 0xFF00:	/* done but not reported yet */
+	if (tcs.dl_opcode == TC_SSP_RESP) {
+		/* The task to be aborted has been sent to the device.
+		 * We got a Response IU for the ABORT TASK TMF. */
+		if (tcs.tmf_state == TMF_RESP_FUNC_COMPLETE)
+			res = asd_clear_nexus(task);
+		else
+			res = tcs.tmf_state;
+	} else if (tcs.dl_opcode == TC_NO_ERROR &&
+		   tcs.tmf_state == TMF_RESP_FUNC_FAILED) {
+		/* timeout */
 		res = TMF_RESP_FUNC_FAILED;
-		leftover = wait_for_completion_timeout(&tascb->completion,
-						       AIC94XX_SCB_TIMEOUT);
-		spin_lock_irqsave(&task->task_state_lock, flags);
-		if (leftover < 1)
+	} else {
+		/* In the following we assume that the managing layer
+		 * will _never_ make a mistake, when issuing ABORT
+		 * TASK.
+		 */
+		switch (tcs.dl_opcode) {
+		default:
+			res = asd_clear_nexus(task);
+			/* fallthrough */
+		case TC_NO_ERROR:
+			break;
+			/* The task hasn't been sent to the device xor
+			 * we never got a (sane) Response IU for the
+			 * ABORT TASK TMF.
+			 */
+		case TF_NAK_RECV:
+			res = TMF_RESP_INVALID_FRAME;
+			break;
+		case TF_TMF_TASK_DONE:	/* done but not reported yet */
 			res = TMF_RESP_FUNC_FAILED;
-		if (task->task_state_flags & SAS_TASK_STATE_DONE)
+			leftover =
+				wait_for_completion_timeout(&tascb_completion,
+							  AIC94XX_SCB_TIMEOUT);
+			spin_lock_irqsave(&task->task_state_lock, flags);
+			if (leftover < 1)
+				res = TMF_RESP_FUNC_FAILED;
+			if (task->task_state_flags & SAS_TASK_STATE_DONE)
+				res = TMF_RESP_FUNC_COMPLETE;
+			spin_unlock_irqrestore(&task->task_state_lock, flags);
+			break;
+		case TF_TMF_NO_TAG:
+		case TF_TMF_TAG_FREE: /* the tag is in the free list */
+		case TF_TMF_NO_CONN_HANDLE: /* no such device */
 			res = TMF_RESP_FUNC_COMPLETE;
-		spin_unlock_irqrestore(&task->task_state_lock, flags);
-		goto out_done;
-	case TF_TMF_NO_TAG + 0xFF00:
-	case TF_TMF_TAG_FREE + 0xFF00: /* the tag is in the free list */
-	case TF_TMF_NO_CONN_HANDLE + 0xFF00: /* no such device */
-		res = TMF_RESP_FUNC_COMPLETE;
-		goto out_done;
-	case TF_TMF_NO_CTX + 0xFF00: /* not in seq, or proto != SSP */
-		res = TMF_RESP_FUNC_ESUPP;
-		goto out;
+			break;
+		case TF_TMF_NO_CTX: /* not in seq, or proto != SSP */
+			res = TMF_RESP_FUNC_ESUPP;
+			break;
+		}
 	}
-out_done:
+ out_done:
+	tascb->completion = NULL;
 	if (res == TMF_RESP_FUNC_COMPLETE) {
 		task->lldd_task = NULL;
 		mb();
 		asd_ascb_free(tascb);
 	}
-out:
+	ASD_DPRINTK("task 0x%p aborted, res: 0x%x\n", task, res);
+	return res;
+
+ out_free:
 	asd_ascb_free(ascb);
 	ASD_DPRINTK("task 0x%p aborted, res: 0x%x\n", task, res);
 	return res;
@@ -557,6 +575,8 @@
 	struct asd_ascb *ascb;
 	int res = 1;
 	struct scb *scb;
+	DECLARE_COMPLETION_ONSTACK(completion);
+	DECLARE_TCS(tcs);
 
 	if (!(dev->tproto & SAS_PROTOCOL_SSP))
 		return TMF_RESP_FUNC_ESUPP;
@@ -564,6 +584,9 @@
 	ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL);
 	if (!ascb)
 		return -ENOMEM;
+
+	ascb->completion = &completion;
+	ascb->uldd_task = &tcs;
 	scb = ascb->scb;
 
 	if (tmf == TMF_QUERY_TASK)
@@ -596,31 +619,32 @@
 				   asd_tmf_timedout);
 	if (res)
 		goto out_err;
-	wait_for_completion(&ascb->completion);
-	res = (int) (unsigned long) ascb->uldd_task;
+	wait_for_completion(&completion);
 
-	switch (res) {
-	case TC_NO_ERROR + 0xFF00:
+	switch (tcs.dl_opcode) {
+	case TC_NO_ERROR:
 		res = TMF_RESP_FUNC_COMPLETE;
 		break;
-	case TF_NAK_RECV + 0xFF00:
+	case TF_NAK_RECV:
 		res = TMF_RESP_INVALID_FRAME;
 		break;
-	case TF_TMF_TASK_DONE + 0xFF00:
+	case TF_TMF_TASK_DONE:
 		res = TMF_RESP_FUNC_FAILED;
 		break;
-	case TF_TMF_NO_TAG + 0xFF00:
-	case TF_TMF_TAG_FREE + 0xFF00: /* the tag is in the free list */
-	case TF_TMF_NO_CONN_HANDLE + 0xFF00: /* no such device */
+	case TF_TMF_NO_TAG:
+	case TF_TMF_TAG_FREE: /* the tag is in the free list */
+	case TF_TMF_NO_CONN_HANDLE: /* no such device */
 		res = TMF_RESP_FUNC_COMPLETE;
 		break;
-	case TF_TMF_NO_CTX + 0xFF00: /* not in seq, or proto != SSP */
+	case TF_TMF_NO_CTX: /* not in seq, or proto != SSP */
 		res = TMF_RESP_FUNC_ESUPP;
 		break;
 	default:
 		/* Allow TMF response codes to propagate upwards */
+		res = tcs.dl_opcode;
 		break;
 	}
+	return res;
 out_err:
 	asd_ascb_free(ascb);
 	return res;