Merge branch 'pci/artpec6'
[linux-2.6-microblaze.git] / drivers / scsi / libiscsi_tcp.c
index 83f14b2..2e9ffe3 100644 (file)
@@ -524,48 +524,79 @@ static int iscsi_tcp_data_in(struct iscsi_conn *conn, struct iscsi_task *task)
 /**
  * iscsi_tcp_r2t_rsp - iSCSI R2T Response processing
  * @conn: iscsi connection
- * @task: scsi command task
+ * @hdr: PDU header
  */
-static int iscsi_tcp_r2t_rsp(struct iscsi_conn *conn, struct iscsi_task *task)
+static int iscsi_tcp_r2t_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
 {
        struct iscsi_session *session = conn->session;
-       struct iscsi_tcp_task *tcp_task = task->dd_data;
-       struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
-       struct iscsi_r2t_rsp *rhdr = (struct iscsi_r2t_rsp *)tcp_conn->in.hdr;
+       struct iscsi_tcp_task *tcp_task;
+       struct iscsi_tcp_conn *tcp_conn;
+       struct iscsi_r2t_rsp *rhdr;
        struct iscsi_r2t_info *r2t;
-       int r2tsn = be32_to_cpu(rhdr->r2tsn);
+       struct iscsi_task *task;
        u32 data_length;
        u32 data_offset;
+       int r2tsn;
        int rc;
 
+       spin_lock(&session->back_lock);
+       task = iscsi_itt_to_ctask(conn, hdr->itt);
+       if (!task) {
+               spin_unlock(&session->back_lock);
+               return ISCSI_ERR_BAD_ITT;
+       } else if (task->sc->sc_data_direction != DMA_TO_DEVICE) {
+               spin_unlock(&session->back_lock);
+               return ISCSI_ERR_PROTO;
+       }
+       /*
+        * A bad target might complete the cmd before we have handled R2Ts
+        * so get a ref to the task that will be dropped in the xmit path.
+        */
+       if (task->state != ISCSI_TASK_RUNNING) {
+               spin_unlock(&session->back_lock);
+               /* Let the path that got the early rsp complete it */
+               return 0;
+       }
+       task->last_xfer = jiffies;
+       __iscsi_get_task(task);
+
+       tcp_conn = conn->dd_data;
+       rhdr = (struct iscsi_r2t_rsp *)tcp_conn->in.hdr;
+       /* fill-in new R2T associated with the task */
+       iscsi_update_cmdsn(session, (struct iscsi_nopin *)rhdr);
+       spin_unlock(&session->back_lock);
+
        if (tcp_conn->in.datalen) {
                iscsi_conn_printk(KERN_ERR, conn,
                                  "invalid R2t with datalen %d\n",
                                  tcp_conn->in.datalen);
-               return ISCSI_ERR_DATALEN;
+               rc = ISCSI_ERR_DATALEN;
+               goto put_task;
        }
 
+       tcp_task = task->dd_data;
+       r2tsn = be32_to_cpu(rhdr->r2tsn);
        if (tcp_task->exp_datasn != r2tsn){
                ISCSI_DBG_TCP(conn, "task->exp_datasn(%d) != rhdr->r2tsn(%d)\n",
                              tcp_task->exp_datasn, r2tsn);
-               return ISCSI_ERR_R2TSN;
+               rc = ISCSI_ERR_R2TSN;
+               goto put_task;
        }
 
-       /* fill-in new R2T associated with the task */
-       iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr);
-
-       if (!task->sc || session->state != ISCSI_STATE_LOGGED_IN) {
+       if (session->state != ISCSI_STATE_LOGGED_IN) {
                iscsi_conn_printk(KERN_INFO, conn,
                                  "dropping R2T itt %d in recovery.\n",
                                  task->itt);
-               return 0;
+               rc = 0;
+               goto put_task;
        }
 
        data_length = be32_to_cpu(rhdr->data_length);
        if (data_length == 0) {
                iscsi_conn_printk(KERN_ERR, conn,
                                  "invalid R2T with zero data len\n");
-               return ISCSI_ERR_DATALEN;
+               rc = ISCSI_ERR_DATALEN;
+               goto put_task;
        }
 
        if (data_length > session->max_burst)
@@ -579,7 +610,8 @@ static int iscsi_tcp_r2t_rsp(struct iscsi_conn *conn, struct iscsi_task *task)
                                  "invalid R2T with data len %u at offset %u "
                                  "and total length %d\n", data_length,
                                  data_offset, task->sc->sdb.length);
-               return ISCSI_ERR_DATALEN;
+               rc = ISCSI_ERR_DATALEN;
+               goto put_task;
        }
 
        spin_lock(&tcp_task->pool2queue);
@@ -589,7 +621,8 @@ static int iscsi_tcp_r2t_rsp(struct iscsi_conn *conn, struct iscsi_task *task)
                                  "Target has sent more R2Ts than it "
                                  "negotiated for or driver has leaked.\n");
                spin_unlock(&tcp_task->pool2queue);
-               return ISCSI_ERR_PROTO;
+               rc = ISCSI_ERR_PROTO;
+               goto put_task;
        }
 
        r2t->exp_statsn = rhdr->statsn;
@@ -607,6 +640,10 @@ static int iscsi_tcp_r2t_rsp(struct iscsi_conn *conn, struct iscsi_task *task)
 
        iscsi_requeue_task(task);
        return 0;
+
+put_task:
+       iscsi_put_task(task);
+       return rc;
 }
 
 /*
@@ -730,20 +767,11 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr)
                rc = iscsi_complete_pdu(conn, hdr, NULL, 0);
                break;
        case ISCSI_OP_R2T:
-               spin_lock(&conn->session->back_lock);
-               task = iscsi_itt_to_ctask(conn, hdr->itt);
-               spin_unlock(&conn->session->back_lock);
-               if (!task)
-                       rc = ISCSI_ERR_BAD_ITT;
-               else if (ahslen)
+               if (ahslen) {
                        rc = ISCSI_ERR_AHSLEN;
-               else if (task->sc->sc_data_direction == DMA_TO_DEVICE) {
-                       task->last_xfer = jiffies;
-                       spin_lock(&conn->session->frwd_lock);
-                       rc = iscsi_tcp_r2t_rsp(conn, task);
-                       spin_unlock(&conn->session->frwd_lock);
-               } else
-                       rc = ISCSI_ERR_PROTO;
+                       break;
+               }
+               rc = iscsi_tcp_r2t_rsp(conn, hdr);
                break;
        case ISCSI_OP_LOGIN_RSP:
        case ISCSI_OP_TEXT_RSP: