[SCSI] libiscsi: fix nop handling
During root boot and shutdown the target could send us nops.
At this time iscsid cannot be running, so the target will drop
the session and the boot or shutdown will hang.
To handle this and allow us to better control when to check the network
this patch moves the nop handling to the kernel.
Signed-off-by: Mike Christie <michaelc@cs.wisc.edu>
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
index 5205ef2..9688361 100644
--- a/drivers/scsi/libiscsi.c
+++ b/drivers/scsi/libiscsi.c
@@ -313,11 +313,70 @@
list_del_init(&mtask->running);
if (conn->login_mtask == mtask)
return;
+
+ if (conn->ping_mtask == mtask)
+ conn->ping_mtask = NULL;
__kfifo_put(conn->session->mgmtpool.queue,
(void*)&mtask, sizeof(void*));
}
EXPORT_SYMBOL_GPL(iscsi_free_mgmt_task);
+static struct iscsi_mgmt_task *
+__iscsi_conn_send_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
+ char *data, uint32_t data_size)
+{
+ struct iscsi_session *session = conn->session;
+ struct iscsi_mgmt_task *mtask;
+
+ if (session->state == ISCSI_STATE_TERMINATE)
+ return NULL;
+
+ if (hdr->opcode == (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) ||
+ hdr->opcode == (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE))
+ /*
+ * Login and Text are sent serially, in
+ * request-followed-by-response sequence.
+ * Same mtask can be used. Same ITT must be used.
+ * Note that login_mtask is preallocated at conn_create().
+ */
+ mtask = conn->login_mtask;
+ else {
+ BUG_ON(conn->c_stage == ISCSI_CONN_INITIAL_STAGE);
+ BUG_ON(conn->c_stage == ISCSI_CONN_STOPPED);
+
+ if (!__kfifo_get(session->mgmtpool.queue,
+ (void*)&mtask, sizeof(void*)))
+ return NULL;
+ }
+
+ if (data_size) {
+ memcpy(mtask->data, data, data_size);
+ mtask->data_count = data_size;
+ } else
+ mtask->data_count = 0;
+
+ memcpy(mtask->hdr, hdr, sizeof(struct iscsi_hdr));
+ INIT_LIST_HEAD(&mtask->running);
+ list_add_tail(&mtask->running, &conn->mgmtqueue);
+ return mtask;
+}
+
+int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr,
+ char *data, uint32_t data_size)
+{
+ struct iscsi_conn *conn = cls_conn->dd_data;
+ struct iscsi_session *session = conn->session;
+ int err = 0;
+
+ spin_lock_bh(&session->lock);
+ if (!__iscsi_conn_send_pdu(conn, hdr, data, data_size))
+ err = -EPERM;
+ spin_unlock_bh(&session->lock);
+ scsi_queue_work(session->host, &conn->xmitwork);
+ return err;
+}
+EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu);
+
/**
* iscsi_cmd_rsp - SCSI Command Response processing
* @conn: iscsi connection
@@ -409,6 +468,39 @@
wake_up(&conn->ehwait);
}
+static void iscsi_send_nopout(struct iscsi_conn *conn, struct iscsi_nopin *rhdr)
+{
+ struct iscsi_nopout hdr;
+ struct iscsi_mgmt_task *mtask;
+
+ if (!rhdr && conn->ping_mtask)
+ return;
+
+ memset(&hdr, 0, sizeof(struct iscsi_nopout));
+ hdr.opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE;
+ hdr.flags = ISCSI_FLAG_CMD_FINAL;
+
+ if (rhdr) {
+ memcpy(hdr.lun, rhdr->lun, 8);
+ hdr.ttt = rhdr->ttt;
+ hdr.itt = RESERVED_ITT;
+ } else
+ hdr.ttt = RESERVED_ITT;
+
+ mtask = __iscsi_conn_send_pdu(conn, (struct iscsi_hdr *)&hdr, NULL, 0);
+ if (!mtask) {
+ printk(KERN_ERR "Could not send nopout\n");
+ return;
+ }
+
+ /* only track our nops */
+ if (!rhdr) {
+ conn->ping_mtask = mtask;
+ conn->last_ping = jiffies;
+ }
+ scsi_queue_work(conn->session->host, &conn->xmitwork);
+}
+
static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
char *data, int datalen)
{
@@ -453,6 +545,7 @@
struct iscsi_mgmt_task *mtask;
uint32_t itt;
+ conn->last_recv = jiffies;
if (hdr->itt != RESERVED_ITT)
itt = get_itt(hdr->itt);
else
@@ -520,14 +613,22 @@
iscsi_free_mgmt_task(conn, mtask);
break;
case ISCSI_OP_NOOP_IN:
- if (hdr->ttt != cpu_to_be32(ISCSI_RESERVED_TAG) || datalen) {
+ if (hdr->ttt != cpu_to_be32(ISCSI_RESERVED_TAG) ||
+ datalen) {
rc = ISCSI_ERR_PROTO;
break;
}
conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1;
- if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen))
- rc = ISCSI_ERR_CONN_FAILED;
+ if (conn->ping_mtask != mtask) {
+ /*
+ * If this is not in response to one of our
+ * nops then it must be from userspace.
+ */
+ if (iscsi_recv_pdu(conn->cls_conn, hdr, data,
+ datalen))
+ rc = ISCSI_ERR_CONN_FAILED;
+ }
iscsi_free_mgmt_task(conn, mtask);
break;
default:
@@ -547,8 +648,7 @@
if (hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG))
break;
- if (iscsi_recv_pdu(conn->cls_conn, hdr, NULL, 0))
- rc = ISCSI_ERR_CONN_FAILED;
+ iscsi_send_nopout(conn, (struct iscsi_nopin*)hdr);
break;
case ISCSI_OP_REJECT:
rc = iscsi_handle_reject(conn, hdr, data, datalen);
@@ -1003,62 +1103,6 @@
}
EXPORT_SYMBOL_GPL(iscsi_change_queue_depth);
-static struct iscsi_mgmt_task *
-__iscsi_conn_send_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr,
- char *data, uint32_t data_size)
-{
- struct iscsi_session *session = conn->session;
- struct iscsi_mgmt_task *mtask;
-
- if (session->state == ISCSI_STATE_TERMINATE)
- return NULL;
-
- if (hdr->opcode == (ISCSI_OP_LOGIN | ISCSI_OP_IMMEDIATE) ||
- hdr->opcode == (ISCSI_OP_TEXT | ISCSI_OP_IMMEDIATE))
- /*
- * Login and Text are sent serially, in
- * request-followed-by-response sequence.
- * Same mtask can be used. Same ITT must be used.
- * Note that login_mtask is preallocated at conn_create().
- */
- mtask = conn->login_mtask;
- else {
- BUG_ON(conn->c_stage == ISCSI_CONN_INITIAL_STAGE);
- BUG_ON(conn->c_stage == ISCSI_CONN_STOPPED);
-
- if (!__kfifo_get(session->mgmtpool.queue,
- (void*)&mtask, sizeof(void*)))
- return NULL;
- }
-
- if (data_size) {
- memcpy(mtask->data, data, data_size);
- mtask->data_count = data_size;
- } else
- mtask->data_count = 0;
-
- memcpy(mtask->hdr, hdr, sizeof(struct iscsi_hdr));
- INIT_LIST_HEAD(&mtask->running);
- list_add_tail(&mtask->running, &conn->mgmtqueue);
- return mtask;
-}
-
-int iscsi_conn_send_pdu(struct iscsi_cls_conn *cls_conn, struct iscsi_hdr *hdr,
- char *data, uint32_t data_size)
-{
- struct iscsi_conn *conn = cls_conn->dd_data;
- struct iscsi_session *session = conn->session;
- int err = 0;
-
- spin_lock_bh(&session->lock);
- if (!__iscsi_conn_send_pdu(conn, hdr, data, data_size))
- err = -EPERM;
- spin_unlock_bh(&session->lock);
- scsi_queue_work(session->host, &conn->xmitwork);
- return err;
-}
-EXPORT_SYMBOL_GPL(iscsi_conn_send_pdu);
-
void iscsi_session_recovery_timedout(struct iscsi_cls_session *cls_session)
{
struct iscsi_session *session = class_to_transport_session(cls_session);
@@ -1134,7 +1178,8 @@
}
static int iscsi_exec_task_mgmt_fn(struct iscsi_conn *conn,
- struct iscsi_tm *hdr, int age)
+ struct iscsi_tm *hdr, int age,
+ int timeout)
{
struct iscsi_session *session = conn->session;
struct iscsi_mgmt_task *mtask;
@@ -1149,7 +1194,7 @@
return -EPERM;
}
conn->tmfcmd_pdus_cnt++;
- conn->tmf_timer.expires = 30 * HZ + jiffies;
+ conn->tmf_timer.expires = timeout * HZ + jiffies;
conn->tmf_timer.function = iscsi_tmf_timedout;
conn->tmf_timer.data = (unsigned long)conn;
add_timer(&conn->tmf_timer);
@@ -1233,6 +1278,106 @@
scsi_queue_work(conn->session->host, &conn->xmitwork);
}
+static enum scsi_eh_timer_return iscsi_eh_cmd_timed_out(struct scsi_cmnd *scmd)
+{
+ struct iscsi_cls_session *cls_session;
+ struct iscsi_session *session;
+ struct iscsi_conn *conn;
+ enum scsi_eh_timer_return rc = EH_NOT_HANDLED;
+
+ cls_session = starget_to_session(scsi_target(scmd->device));
+ session = class_to_transport_session(cls_session);
+
+ debug_scsi("scsi cmd %p timedout\n", scmd);
+
+ spin_lock(&session->lock);
+ if (session->state != ISCSI_STATE_LOGGED_IN) {
+ /*
+ * We are probably in the middle of iscsi recovery so let
+ * that complete and handle the error.
+ */
+ rc = EH_RESET_TIMER;
+ goto done;
+ }
+
+ conn = session->leadconn;
+ if (!conn) {
+ /* In the middle of shuting down */
+ rc = EH_RESET_TIMER;
+ goto done;
+ }
+
+ if (!conn->recv_timeout && !conn->ping_timeout)
+ goto done;
+ /*
+ * if the ping timedout then we are in the middle of cleaning up
+ * and can let the iscsi eh handle it
+ */
+ if (time_before_eq(conn->last_recv + (conn->recv_timeout * HZ) +
+ (conn->ping_timeout * HZ), jiffies))
+ rc = EH_RESET_TIMER;
+ /*
+ * if we are about to check the transport then give the command
+ * more time
+ */
+ if (time_before_eq(conn->last_recv + (conn->recv_timeout * HZ),
+ jiffies))
+ rc = EH_RESET_TIMER;
+ /* if in the middle of checking the transport then give us more time */
+ if (conn->ping_mtask)
+ rc = EH_RESET_TIMER;
+done:
+ spin_unlock(&session->lock);
+ debug_scsi("return %s\n", rc == EH_RESET_TIMER ? "timer reset" : "nh");
+ return rc;
+}
+
+static void iscsi_check_transport_timeouts(unsigned long data)
+{
+ struct iscsi_conn *conn = (struct iscsi_conn *)data;
+ struct iscsi_session *session = conn->session;
+ unsigned long timeout, next_timeout = 0, last_recv;
+
+ spin_lock(&session->lock);
+ if (session->state != ISCSI_STATE_LOGGED_IN)
+ goto done;
+
+ timeout = conn->recv_timeout;
+ if (!timeout)
+ goto done;
+
+ timeout *= HZ;
+ last_recv = conn->last_recv;
+ if (time_before_eq(last_recv + timeout + (conn->ping_timeout * HZ),
+ jiffies)) {
+ printk(KERN_ERR "ping timeout of %d secs expired, "
+ "last rx %lu, last ping %lu, now %lu\n",
+ conn->ping_timeout, last_recv,
+ conn->last_ping, jiffies);
+ spin_unlock(&session->lock);
+ iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
+ return;
+ }
+
+ if (time_before_eq(last_recv + timeout, jiffies)) {
+ if (time_before_eq(conn->last_ping, last_recv)) {
+ /* send a ping to try to provoke some traffic */
+ debug_scsi("Sending nopout as ping on conn %p\n", conn);
+ iscsi_send_nopout(conn, NULL);
+ }
+ next_timeout = last_recv + timeout + (conn->ping_timeout * HZ);
+ } else {
+ next_timeout = last_recv + timeout;
+ }
+
+ if (next_timeout) {
+ debug_scsi("Setting next tmo %lu\n", next_timeout);
+ mod_timer(&conn->transport_timer, next_timeout);
+ }
+done:
+ spin_unlock(&session->lock);
+}
+
static void iscsi_prep_abort_task_pdu(struct iscsi_cmd_task *ctask,
struct iscsi_tm *hdr)
{
@@ -1304,7 +1449,7 @@
hdr = &conn->tmhdr;
iscsi_prep_abort_task_pdu(ctask, hdr);
- if (iscsi_exec_task_mgmt_fn(conn, hdr, age)) {
+ if (iscsi_exec_task_mgmt_fn(conn, hdr, age, session->abort_timeout)) {
rc = FAILED;
goto failed;
}
@@ -1365,7 +1510,7 @@
hdr->flags = ISCSI_TM_FUNC_LOGICAL_UNIT_RESET & ISCSI_FLAG_TM_FUNC_MASK;
hdr->flags |= ISCSI_FLAG_CMD_FINAL;
int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun);
- hdr->rtt = ISCSI_RESERVED_TAG;
+ hdr->rtt = RESERVED_ITT;
}
int iscsi_eh_device_reset(struct scsi_cmnd *sc)
@@ -1396,7 +1541,8 @@
hdr = &conn->tmhdr;
iscsi_prep_lun_reset_pdu(sc, hdr);
- if (iscsi_exec_task_mgmt_fn(conn, hdr, session->age)) {
+ if (iscsi_exec_task_mgmt_fn(conn, hdr, session->age,
+ session->lu_reset_timeout)) {
rc = FAILED;
goto unlock;
}
@@ -1572,12 +1718,14 @@
shost->max_cmd_len = iscsit->max_cmd_len;
shost->transportt = scsit;
shost->transportt->create_work_queue = 1;
+ shost->transportt->eh_timed_out = iscsi_eh_cmd_timed_out;
*hostno = shost->host_no;
session = iscsi_hostdata(shost->hostdata);
memset(session, 0, sizeof(struct iscsi_session));
session->host = shost;
session->state = ISCSI_STATE_FREE;
+ session->fast_abort = 1;
session->mgmtpool_max = ISCSI_MGMT_CMDS_MAX;
session->cmds_max = cmds_max;
session->queued_cmdsn = session->cmdsn = initial_cmdsn;
@@ -1708,6 +1856,11 @@
conn->id = conn_idx;
conn->exp_statsn = 0;
conn->tmf_state = TMF_INITIAL;
+
+ init_timer(&conn->transport_timer);
+ conn->transport_timer.data = (unsigned long)conn;
+ conn->transport_timer.function = iscsi_check_transport_timeouts;
+
INIT_LIST_HEAD(&conn->run_list);
INIT_LIST_HEAD(&conn->mgmt_run_list);
INIT_LIST_HEAD(&conn->mgmtqueue);
@@ -1757,6 +1910,8 @@
struct iscsi_session *session = conn->session;
unsigned long flags;
+ del_timer_sync(&conn->transport_timer);
+
spin_lock_bh(&session->lock);
conn->c_stage = ISCSI_CONN_CLEANUP_WAIT;
if (session->leadconn == conn) {
@@ -1823,11 +1978,29 @@
return -EINVAL;
}
+ if (conn->ping_timeout && !conn->recv_timeout) {
+ printk(KERN_ERR "iscsi: invalid recv timeout of zero "
+ "Using 5 seconds\n.");
+ conn->recv_timeout = 5;
+ }
+
+ if (conn->recv_timeout && !conn->ping_timeout) {
+ printk(KERN_ERR "iscsi: invalid ping timeout of zero "
+ "Using 5 seconds.\n");
+ conn->ping_timeout = 5;
+ }
+
spin_lock_bh(&session->lock);
conn->c_stage = ISCSI_CONN_STARTED;
session->state = ISCSI_STATE_LOGGED_IN;
session->queued_cmdsn = session->cmdsn;
+ conn->last_recv = jiffies;
+ conn->last_ping = jiffies;
+ if (conn->recv_timeout && conn->ping_timeout)
+ mod_timer(&conn->transport_timer,
+ jiffies + (conn->recv_timeout * HZ));
+
switch(conn->stop_stage) {
case STOP_CONN_RECOVER:
/*
@@ -1879,6 +2052,8 @@
{
int old_stop_stage;
+ del_timer_sync(&conn->transport_timer);
+
mutex_lock(&session->eh_mutex);
spin_lock_bh(&session->lock);
if (conn->stop_stage == STOP_CONN_TERM) {
@@ -1993,6 +2168,18 @@
case ISCSI_PARAM_FAST_ABORT:
sscanf(buf, "%d", &session->fast_abort);
break;
+ case ISCSI_PARAM_ABORT_TMO:
+ sscanf(buf, "%d", &session->abort_timeout);
+ break;
+ case ISCSI_PARAM_LU_RESET_TMO:
+ sscanf(buf, "%d", &session->lu_reset_timeout);
+ break;
+ case ISCSI_PARAM_PING_TMO:
+ sscanf(buf, "%d", &conn->ping_timeout);
+ break;
+ case ISCSI_PARAM_RECV_TMO:
+ sscanf(buf, "%d", &conn->recv_timeout);
+ break;
case ISCSI_PARAM_MAX_RECV_DLENGTH:
sscanf(buf, "%d", &conn->max_recv_dlength);
break;
@@ -2110,6 +2297,12 @@
case ISCSI_PARAM_FAST_ABORT:
len = sprintf(buf, "%d\n", session->fast_abort);
break;
+ case ISCSI_PARAM_ABORT_TMO:
+ len = sprintf(buf, "%d\n", session->abort_timeout);
+ break;
+ case ISCSI_PARAM_LU_RESET_TMO:
+ len = sprintf(buf, "%d\n", session->lu_reset_timeout);
+ break;
case ISCSI_PARAM_INITIAL_R2T_EN:
len = sprintf(buf, "%d\n", session->initial_r2t_en);
break;
@@ -2167,6 +2360,12 @@
int len;
switch(param) {
+ case ISCSI_PARAM_PING_TMO:
+ len = sprintf(buf, "%u\n", conn->ping_timeout);
+ break;
+ case ISCSI_PARAM_RECV_TMO:
+ len = sprintf(buf, "%u\n", conn->recv_timeout);
+ break;
case ISCSI_PARAM_MAX_RECV_DLENGTH:
len = sprintf(buf, "%u\n", conn->max_recv_dlength);
break;