|
@@ -30,7 +30,7 @@
|
|
|
#include <scsi/scsi_transport_iscsi.h>
|
|
|
#include <scsi/iscsi_if.h>
|
|
|
|
|
|
-#define ISCSI_SESSION_ATTRS 18
|
|
|
+#define ISCSI_SESSION_ATTRS 19
|
|
|
#define ISCSI_CONN_ATTRS 11
|
|
|
#define ISCSI_HOST_ATTRS 4
|
|
|
#define ISCSI_TRANSPORT_VERSION "2.0-867"
|
|
@@ -221,6 +221,54 @@ static struct iscsi_cls_conn *iscsi_conn_lookup(uint32_t sid, uint32_t cid)
|
|
|
* The following functions can be used by LLDs that allocate
|
|
|
* their own scsi_hosts or by software iscsi LLDs
|
|
|
*/
|
|
|
+static struct {
|
|
|
+ int value;
|
|
|
+ char *name;
|
|
|
+} iscsi_session_state_names[] = {
|
|
|
+ { ISCSI_SESSION_LOGGED_IN, "LOGGED_IN" },
|
|
|
+ { ISCSI_SESSION_FAILED, "FAILED" },
|
|
|
+ { ISCSI_SESSION_FREE, "FREE" },
|
|
|
+};
|
|
|
+
|
|
|
+const char *iscsi_session_state_name(int state)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ char *name = NULL;
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(iscsi_session_state_names); i++) {
|
|
|
+ if (iscsi_session_state_names[i].value == state) {
|
|
|
+ name = iscsi_session_state_names[i].name;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return name;
|
|
|
+}
|
|
|
+
|
|
|
+int iscsi_session_chkready(struct iscsi_cls_session *session)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&session->lock, flags);
|
|
|
+ switch (session->state) {
|
|
|
+ case ISCSI_SESSION_LOGGED_IN:
|
|
|
+ err = 0;
|
|
|
+ break;
|
|
|
+ case ISCSI_SESSION_FAILED:
|
|
|
+ err = DID_IMM_RETRY << 16;
|
|
|
+ break;
|
|
|
+ case ISCSI_SESSION_FREE:
|
|
|
+ err = DID_NO_CONNECT << 16;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ err = DID_NO_CONNECT << 16;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ spin_unlock_irqrestore(&session->lock, flags);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(iscsi_session_chkready);
|
|
|
+
|
|
|
static void iscsi_session_release(struct device *dev)
|
|
|
{
|
|
|
struct iscsi_cls_session *session = iscsi_dev_to_session(dev);
|
|
@@ -259,26 +307,57 @@ static void session_recovery_timedout(struct work_struct *work)
|
|
|
struct iscsi_cls_session *session =
|
|
|
container_of(work, struct iscsi_cls_session,
|
|
|
recovery_work.work);
|
|
|
+ unsigned long flags;
|
|
|
|
|
|
dev_printk(KERN_INFO, &session->dev, "iscsi: session recovery timed "
|
|
|
"out after %d secs\n", session->recovery_tmo);
|
|
|
|
|
|
+ spin_lock_irqsave(&session->lock, flags);
|
|
|
+ switch (session->state) {
|
|
|
+ case ISCSI_SESSION_FAILED:
|
|
|
+ session->state = ISCSI_SESSION_FREE;
|
|
|
+ break;
|
|
|
+ case ISCSI_SESSION_LOGGED_IN:
|
|
|
+ case ISCSI_SESSION_FREE:
|
|
|
+ /* we raced with the unblock's flush */
|
|
|
+ spin_unlock_irqrestore(&session->lock, flags);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ spin_unlock_irqrestore(&session->lock, flags);
|
|
|
+
|
|
|
if (session->transport->session_recovery_timedout)
|
|
|
session->transport->session_recovery_timedout(session);
|
|
|
|
|
|
scsi_target_unblock(&session->dev);
|
|
|
}
|
|
|
|
|
|
-void iscsi_unblock_session(struct iscsi_cls_session *session)
|
|
|
+void __iscsi_unblock_session(struct iscsi_cls_session *session)
|
|
|
{
|
|
|
if (!cancel_delayed_work(&session->recovery_work))
|
|
|
flush_workqueue(iscsi_eh_timer_workq);
|
|
|
scsi_target_unblock(&session->dev);
|
|
|
}
|
|
|
+
|
|
|
+void iscsi_unblock_session(struct iscsi_cls_session *session)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&session->lock, flags);
|
|
|
+ session->state = ISCSI_SESSION_LOGGED_IN;
|
|
|
+ spin_unlock_irqrestore(&session->lock, flags);
|
|
|
+
|
|
|
+ __iscsi_unblock_session(session);
|
|
|
+}
|
|
|
EXPORT_SYMBOL_GPL(iscsi_unblock_session);
|
|
|
|
|
|
void iscsi_block_session(struct iscsi_cls_session *session)
|
|
|
{
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&session->lock, flags);
|
|
|
+ session->state = ISCSI_SESSION_FAILED;
|
|
|
+ spin_unlock_irqrestore(&session->lock, flags);
|
|
|
+
|
|
|
scsi_target_block(&session->dev);
|
|
|
queue_delayed_work(iscsi_eh_timer_workq, &session->recovery_work,
|
|
|
session->recovery_tmo * HZ);
|
|
@@ -327,10 +406,12 @@ iscsi_alloc_session(struct Scsi_Host *shost,
|
|
|
|
|
|
session->transport = transport;
|
|
|
session->recovery_tmo = 120;
|
|
|
+ session->state = ISCSI_SESSION_FREE;
|
|
|
INIT_DELAYED_WORK(&session->recovery_work, session_recovery_timedout);
|
|
|
INIT_LIST_HEAD(&session->host_list);
|
|
|
INIT_LIST_HEAD(&session->sess_list);
|
|
|
INIT_WORK(&session->unbind_work, __iscsi_unbind_session);
|
|
|
+ spin_lock_init(&session->lock);
|
|
|
|
|
|
/* this is released in the dev's release function */
|
|
|
scsi_host_get(shost);
|
|
@@ -444,7 +525,10 @@ void iscsi_remove_session(struct iscsi_cls_session *session)
|
|
|
* If we are blocked let commands flow again. The lld or iscsi
|
|
|
* layer should set up the queuecommand to fail commands.
|
|
|
*/
|
|
|
- iscsi_unblock_session(session);
|
|
|
+ spin_lock_irqsave(&session->lock, flags);
|
|
|
+ session->state = ISCSI_SESSION_FREE;
|
|
|
+ spin_unlock_irqrestore(&session->lock, flags);
|
|
|
+ __iscsi_unblock_session(session);
|
|
|
iscsi_unbind_session(session);
|
|
|
/*
|
|
|
* If the session dropped while removing devices then we need to make
|
|
@@ -661,16 +745,23 @@ EXPORT_SYMBOL_GPL(iscsi_recv_pdu);
|
|
|
|
|
|
void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error)
|
|
|
{
|
|
|
+ struct iscsi_cls_session *session = iscsi_conn_to_session(conn);
|
|
|
struct nlmsghdr *nlh;
|
|
|
struct sk_buff *skb;
|
|
|
struct iscsi_uevent *ev;
|
|
|
struct iscsi_internal *priv;
|
|
|
int len = NLMSG_SPACE(sizeof(*ev));
|
|
|
+ unsigned long flags;
|
|
|
|
|
|
priv = iscsi_if_transport_lookup(conn->transport);
|
|
|
if (!priv)
|
|
|
return;
|
|
|
|
|
|
+ spin_lock_irqsave(&session->lock, flags);
|
|
|
+ if (session->state == ISCSI_SESSION_LOGGED_IN)
|
|
|
+ session->state = ISCSI_SESSION_FAILED;
|
|
|
+ spin_unlock_irqrestore(&session->lock, flags);
|
|
|
+
|
|
|
skb = alloc_skb(len, GFP_ATOMIC);
|
|
|
if (!skb) {
|
|
|
dev_printk(KERN_ERR, &conn->dev, "iscsi: gracefully ignored "
|
|
@@ -1246,6 +1337,15 @@ iscsi_session_attr(fast_abort, ISCSI_PARAM_FAST_ABORT, 0);
|
|
|
iscsi_session_attr(abort_tmo, ISCSI_PARAM_ABORT_TMO, 0);
|
|
|
iscsi_session_attr(lu_reset_tmo, ISCSI_PARAM_LU_RESET_TMO, 0);
|
|
|
|
|
|
+static ssize_t
|
|
|
+show_priv_session_state(struct class_device *cdev, char *buf)
|
|
|
+{
|
|
|
+ struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev);
|
|
|
+ return sprintf(buf, "%s\n", iscsi_session_state_name(session->state));
|
|
|
+}
|
|
|
+static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
|
|
|
+ NULL);
|
|
|
+
|
|
|
#define iscsi_priv_session_attr_show(field, format) \
|
|
|
static ssize_t \
|
|
|
show_priv_session_##field(struct class_device *cdev, char *buf) \
|
|
@@ -1472,6 +1572,7 @@ iscsi_register_transport(struct iscsi_transport *tt)
|
|
|
SETUP_SESSION_RD_ATTR(abort_tmo, ISCSI_ABORT_TMO);
|
|
|
SETUP_SESSION_RD_ATTR(lu_reset_tmo,ISCSI_LU_RESET_TMO);
|
|
|
SETUP_PRIV_SESSION_RD_ATTR(recovery_tmo);
|
|
|
+ SETUP_PRIV_SESSION_RD_ATTR(state);
|
|
|
|
|
|
BUG_ON(count > ISCSI_SESSION_ATTRS);
|
|
|
priv->session_attrs[count] = NULL;
|