Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/nab/target...
[pandora-kernel.git] / drivers / scsi / scsi_transport_iscsi.c
index a8dd85d..96029e6 100644 (file)
@@ -23,6 +23,8 @@
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/slab.h>
+#include <linux/bsg-lib.h>
+#include <linux/idr.h>
 #include <net/tcp.h>
 #include <scsi/scsi.h>
 #include <scsi/scsi_host.h>
@@ -31,8 +33,7 @@
 #include <scsi/scsi_transport_iscsi.h>
 #include <scsi/iscsi_if.h>
 #include <scsi/scsi_cmnd.h>
-
-#define ISCSI_HOST_ATTRS 4
+#include <scsi/scsi_bsg_iscsi.h>
 
 #define ISCSI_TRANSPORT_VERSION "2.0-870"
 
@@ -74,7 +75,6 @@ struct iscsi_internal {
        struct list_head list;
        struct device dev;
 
-       struct device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
        struct transport_container conn_cont;
        struct transport_container session_cont;
 };
@@ -82,6 +82,7 @@ struct iscsi_internal {
 static atomic_t iscsi_session_nr; /* sysfs session id for next new session */
 static struct workqueue_struct *iscsi_eh_timer_workq;
 
+static DEFINE_IDA(iscsi_sess_ida);
 /*
  * list of registered transports and lock that must
  * be held while accessing list. The iscsi_transport_lock must
@@ -316,12 +317,16 @@ iscsi_iface_net_attr(ipv6_iface, link_local_addr, ISCSI_NET_PARAM_IPV6_LINKLOCAL
 iscsi_iface_net_attr(ipv6_iface, router_addr, ISCSI_NET_PARAM_IPV6_ROUTER);
 iscsi_iface_net_attr(ipv6_iface, ipaddr_autocfg,
                     ISCSI_NET_PARAM_IPV6_ADDR_AUTOCFG);
-iscsi_iface_net_attr(ipv6_iface, linklocal_autocfg,
+iscsi_iface_net_attr(ipv6_iface, link_local_autocfg,
                     ISCSI_NET_PARAM_IPV6_LINKLOCAL_AUTOCFG);
 
 /* common read only iface attribute */
 iscsi_iface_net_attr(iface, enabled, ISCSI_NET_PARAM_IFACE_ENABLE);
-iscsi_iface_net_attr(iface, vlan, ISCSI_NET_PARAM_VLAN_ID);
+iscsi_iface_net_attr(iface, vlan_id, ISCSI_NET_PARAM_VLAN_ID);
+iscsi_iface_net_attr(iface, vlan_priority, ISCSI_NET_PARAM_VLAN_PRIORITY);
+iscsi_iface_net_attr(iface, vlan_enabled, ISCSI_NET_PARAM_VLAN_ENABLED);
+iscsi_iface_net_attr(iface, mtu, ISCSI_NET_PARAM_MTU);
+iscsi_iface_net_attr(iface, port, ISCSI_NET_PARAM_PORT);
 
 static mode_t iscsi_iface_attr_is_visible(struct kobject *kobj,
                                          struct attribute *attr, int i)
@@ -333,8 +338,16 @@ static mode_t iscsi_iface_attr_is_visible(struct kobject *kobj,
 
        if (attr == &dev_attr_iface_enabled.attr)
                param = ISCSI_NET_PARAM_IFACE_ENABLE;
-       else if (attr == &dev_attr_iface_vlan.attr)
+       else if (attr == &dev_attr_iface_vlan_id.attr)
                param = ISCSI_NET_PARAM_VLAN_ID;
+       else if (attr == &dev_attr_iface_vlan_priority.attr)
+               param = ISCSI_NET_PARAM_VLAN_PRIORITY;
+       else if (attr == &dev_attr_iface_vlan_enabled.attr)
+               param = ISCSI_NET_PARAM_VLAN_ENABLED;
+       else if (attr == &dev_attr_iface_mtu.attr)
+               param = ISCSI_NET_PARAM_MTU;
+       else if (attr == &dev_attr_iface_port.attr)
+               param = ISCSI_NET_PARAM_PORT;
        else if (iface->iface_type == ISCSI_IFACE_TYPE_IPV4) {
                if (attr == &dev_attr_ipv4_iface_ipaddress.attr)
                        param = ISCSI_NET_PARAM_IPV4_ADDR;
@@ -355,7 +368,7 @@ static mode_t iscsi_iface_attr_is_visible(struct kobject *kobj,
                        param = ISCSI_NET_PARAM_IPV6_ROUTER;
                else if (attr == &dev_attr_ipv6_iface_ipaddr_autocfg.attr)
                        param = ISCSI_NET_PARAM_IPV6_ADDR_AUTOCFG;
-               else if (attr == &dev_attr_ipv6_iface_linklocal_autocfg.attr)
+               else if (attr == &dev_attr_ipv6_iface_link_local_autocfg.attr)
                        param = ISCSI_NET_PARAM_IPV6_LINKLOCAL_AUTOCFG;
                else
                        return 0;
@@ -369,7 +382,9 @@ static mode_t iscsi_iface_attr_is_visible(struct kobject *kobj,
 
 static struct attribute *iscsi_iface_attrs[] = {
        &dev_attr_iface_enabled.attr,
-       &dev_attr_iface_vlan.attr,
+       &dev_attr_iface_vlan_id.attr,
+       &dev_attr_iface_vlan_priority.attr,
+       &dev_attr_iface_vlan_enabled.attr,
        &dev_attr_ipv4_iface_ipaddress.attr,
        &dev_attr_ipv4_iface_gateway.attr,
        &dev_attr_ipv4_iface_subnet.attr,
@@ -378,7 +393,9 @@ static struct attribute *iscsi_iface_attrs[] = {
        &dev_attr_ipv6_iface_link_local_addr.attr,
        &dev_attr_ipv6_iface_router_addr.attr,
        &dev_attr_ipv6_iface_ipaddr_autocfg.attr,
-       &dev_attr_ipv6_iface_linklocal_autocfg.attr,
+       &dev_attr_ipv6_iface_link_local_autocfg.attr,
+       &dev_attr_iface_mtu.attr,
+       &dev_attr_iface_port.attr,
        NULL,
 };
 
@@ -442,6 +459,99 @@ void iscsi_destroy_iface(struct iscsi_iface *iface)
 }
 EXPORT_SYMBOL_GPL(iscsi_destroy_iface);
 
+/*
+ * BSG support
+ */
+/**
+ * iscsi_bsg_host_dispatch - Dispatch command to LLD.
+ * @job: bsg job to be processed
+ */
+static int iscsi_bsg_host_dispatch(struct bsg_job *job)
+{
+       struct Scsi_Host *shost = iscsi_job_to_shost(job);
+       struct iscsi_bsg_request *req = job->request;
+       struct iscsi_bsg_reply *reply = job->reply;
+       struct iscsi_internal *i = to_iscsi_internal(shost->transportt);
+       int cmdlen = sizeof(uint32_t);  /* start with length of msgcode */
+       int ret;
+
+       /* check if we have the msgcode value at least */
+       if (job->request_len < sizeof(uint32_t)) {
+               ret = -ENOMSG;
+               goto fail_host_msg;
+       }
+
+       /* Validate the host command */
+       switch (req->msgcode) {
+       case ISCSI_BSG_HST_VENDOR:
+               cmdlen += sizeof(struct iscsi_bsg_host_vendor);
+               if ((shost->hostt->vendor_id == 0L) ||
+                   (req->rqst_data.h_vendor.vendor_id !=
+                       shost->hostt->vendor_id)) {
+                       ret = -ESRCH;
+                       goto fail_host_msg;
+               }
+               break;
+       default:
+               ret = -EBADR;
+               goto fail_host_msg;
+       }
+
+       /* check if we really have all the request data needed */
+       if (job->request_len < cmdlen) {
+               ret = -ENOMSG;
+               goto fail_host_msg;
+       }
+
+       ret = i->iscsi_transport->bsg_request(job);
+       if (!ret)
+               return 0;
+
+fail_host_msg:
+       /* return the errno failure code as the only status */
+       BUG_ON(job->reply_len < sizeof(uint32_t));
+       reply->reply_payload_rcv_len = 0;
+       reply->result = ret;
+       job->reply_len = sizeof(uint32_t);
+       bsg_job_done(job, ret, 0);
+       return 0;
+}
+
+/**
+ * iscsi_bsg_host_add - Create and add the bsg hooks to receive requests
+ * @shost: shost for iscsi_host
+ * @ihost: iscsi_cls_host adding the structures to
+ */
+static int
+iscsi_bsg_host_add(struct Scsi_Host *shost, struct iscsi_cls_host *ihost)
+{
+       struct device *dev = &shost->shost_gendev;
+       struct iscsi_internal *i = to_iscsi_internal(shost->transportt);
+       struct request_queue *q;
+       char bsg_name[20];
+       int ret;
+
+       if (!i->iscsi_transport->bsg_request)
+               return -ENOTSUPP;
+
+       snprintf(bsg_name, sizeof(bsg_name), "iscsi_host%d", shost->host_no);
+
+       q = __scsi_alloc_queue(shost, bsg_request_fn);
+       if (!q)
+               return -ENOMEM;
+
+       ret = bsg_setup_queue(dev, q, bsg_name, iscsi_bsg_host_dispatch, 0);
+       if (ret) {
+               shost_printk(KERN_ERR, shost, "bsg interface failed to "
+                            "initialize - no request queue\n");
+               blk_cleanup_queue(q);
+               return ret;
+       }
+
+       ihost->bsg_q = q;
+       return 0;
+}
+
 static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
                            struct device *cdev)
 {
@@ -451,13 +561,30 @@ static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
        memset(ihost, 0, sizeof(*ihost));
        atomic_set(&ihost->nr_scans, 0);
        mutex_init(&ihost->mutex);
+
+       iscsi_bsg_host_add(shost, ihost);
+       /* ignore any bsg add error - we just can't do sgio */
+
+       return 0;
+}
+
+static int iscsi_remove_host(struct transport_container *tc,
+                            struct device *dev, struct device *cdev)
+{
+       struct Scsi_Host *shost = dev_to_shost(dev);
+       struct iscsi_cls_host *ihost = shost->shost_data;
+
+       if (ihost->bsg_q) {
+               bsg_remove_queue(ihost->bsg_q);
+               blk_cleanup_queue(ihost->bsg_q);
+       }
        return 0;
 }
 
 static DECLARE_TRANSPORT_CLASS(iscsi_host_class,
                               "iscsi_host",
                               iscsi_setup_host,
-                              NULL,
+                              iscsi_remove_host,
                               NULL);
 
 static DECLARE_TRANSPORT_CLASS(iscsi_session_class,
@@ -576,6 +703,19 @@ int iscsi_session_chkready(struct iscsi_cls_session *session)
 }
 EXPORT_SYMBOL_GPL(iscsi_session_chkready);
 
+int iscsi_is_session_online(struct iscsi_cls_session *session)
+{
+       unsigned long flags;
+       int ret = 0;
+
+       spin_lock_irqsave(&session->lock, flags);
+       if (session->state == ISCSI_SESSION_LOGGED_IN)
+               ret = 1;
+       spin_unlock_irqrestore(&session->lock, flags);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(iscsi_is_session_online);
+
 static void iscsi_session_release(struct device *dev)
 {
        struct iscsi_cls_session *session = iscsi_dev_to_session(dev);
@@ -852,6 +992,7 @@ static void __iscsi_unbind_session(struct work_struct *work)
        struct Scsi_Host *shost = iscsi_session_to_shost(session);
        struct iscsi_cls_host *ihost = shost->shost_data;
        unsigned long flags;
+       unsigned int target_id;
 
        ISCSI_DBG_TRANS_SESSION(session, "Unbinding session\n");
 
@@ -863,10 +1004,15 @@ static void __iscsi_unbind_session(struct work_struct *work)
                mutex_unlock(&ihost->mutex);
                return;
        }
+
+       target_id = session->target_id;
        session->target_id = ISCSI_MAX_TARGET;
        spin_unlock_irqrestore(&session->lock, flags);
        mutex_unlock(&ihost->mutex);
 
+       if (session->ida_used)
+               ida_simple_remove(&iscsi_sess_ida, target_id);
+
        scsi_remove_target(&session->dev);
        iscsi_session_event(session, ISCSI_KEVENT_UNBIND_SESSION);
        ISCSI_DBG_TRANS_SESSION(session, "Completed target removal\n");
@@ -907,59 +1053,36 @@ iscsi_alloc_session(struct Scsi_Host *shost, struct iscsi_transport *transport,
 }
 EXPORT_SYMBOL_GPL(iscsi_alloc_session);
 
-static int iscsi_get_next_target_id(struct device *dev, void *data)
-{
-       struct iscsi_cls_session *session;
-       unsigned long flags;
-       int err = 0;
-
-       if (!iscsi_is_session_dev(dev))
-               return 0;
-
-       session = iscsi_dev_to_session(dev);
-       spin_lock_irqsave(&session->lock, flags);
-       if (*((unsigned int *) data) == session->target_id)
-               err = -EEXIST;
-       spin_unlock_irqrestore(&session->lock, flags);
-       return err;
-}
-
 int iscsi_add_session(struct iscsi_cls_session *session, unsigned int target_id)
 {
        struct Scsi_Host *shost = iscsi_session_to_shost(session);
        struct iscsi_cls_host *ihost;
        unsigned long flags;
-       unsigned int id = target_id;
+       int id = 0;
        int err;
 
        ihost = shost->shost_data;
        session->sid = atomic_add_return(1, &iscsi_session_nr);
 
-       if (id == ISCSI_MAX_TARGET) {
-               for (id = 0; id < ISCSI_MAX_TARGET; id++) {
-                       err = device_for_each_child(&shost->shost_gendev, &id,
-                                                   iscsi_get_next_target_id);
-                       if (!err)
-                               break;
-               }
+       if (target_id == ISCSI_MAX_TARGET) {
+               id = ida_simple_get(&iscsi_sess_ida, 0, 0, GFP_KERNEL);
 
-               if (id == ISCSI_MAX_TARGET) {
+               if (id < 0) {
                        iscsi_cls_session_printk(KERN_ERR, session,
-                                                "Too many iscsi targets. Max "
-                                                "number of targets is %d.\n",
-                                                ISCSI_MAX_TARGET - 1);
-                       err = -EOVERFLOW;
-                       goto release_host;
+                                       "Failure in Target ID Allocation\n");
+                       return id;
                }
-       }
-       session->target_id = id;
+               session->target_id = (unsigned int)id;
+               session->ida_used = true;
+       } else
+               session->target_id = target_id;
 
        dev_set_name(&session->dev, "session%u", session->sid);
        err = device_add(&session->dev);
        if (err) {
                iscsi_cls_session_printk(KERN_ERR, session,
                                         "could not register session's dev\n");
-               goto release_host;
+               goto release_ida;
        }
        transport_register_device(&session->dev);
 
@@ -971,8 +1094,10 @@ int iscsi_add_session(struct iscsi_cls_session *session, unsigned int target_id)
        ISCSI_DBG_TRANS_SESSION(session, "Completed session adding\n");
        return 0;
 
-release_host:
-       scsi_host_put(shost);
+release_ida:
+       if (session->ida_used)
+               ida_simple_remove(&iscsi_sess_ida, session->target_id);
+
        return err;
 }
 EXPORT_SYMBOL_GPL(iscsi_add_session);
@@ -1316,6 +1441,40 @@ void iscsi_conn_error_event(struct iscsi_cls_conn *conn, enum iscsi_err error)
 }
 EXPORT_SYMBOL_GPL(iscsi_conn_error_event);
 
+void iscsi_conn_login_event(struct iscsi_cls_conn *conn,
+                           enum iscsi_conn_state state)
+{
+       struct nlmsghdr *nlh;
+       struct sk_buff  *skb;
+       struct iscsi_uevent *ev;
+       struct iscsi_internal *priv;
+       int len = NLMSG_SPACE(sizeof(*ev));
+
+       priv = iscsi_if_transport_lookup(conn->transport);
+       if (!priv)
+               return;
+
+       skb = alloc_skb(len, GFP_ATOMIC);
+       if (!skb) {
+               iscsi_cls_conn_printk(KERN_ERR, conn, "gracefully ignored "
+                                     "conn login (%d)\n", state);
+               return;
+       }
+
+       nlh = __nlmsg_put(skb, 0, 0, 0, (len - sizeof(*nlh)), 0);
+       ev = NLMSG_DATA(nlh);
+       ev->transport_handle = iscsi_handle(conn->transport);
+       ev->type = ISCSI_KEVENT_CONN_LOGIN_STATE;
+       ev->r.conn_login.state = state;
+       ev->r.conn_login.cid = conn->cid;
+       ev->r.conn_login.sid = iscsi_conn_get_sid(conn);
+       iscsi_multicast_skb(skb, ISCSI_NL_GRP_ISCSID, GFP_ATOMIC);
+
+       iscsi_cls_conn_printk(KERN_INFO, conn, "detected conn login (%d)\n",
+                             state);
+}
+EXPORT_SYMBOL_GPL(iscsi_conn_login_event);
+
 static int
 iscsi_if_send_reply(uint32_t group, int seq, int type, int done, int multi,
                    void *payload, int size)
@@ -1731,7 +1890,7 @@ iscsi_set_path(struct iscsi_transport *transport, struct iscsi_uevent *ev)
 
 static int
 iscsi_set_iface_params(struct iscsi_transport *transport,
-                      struct iscsi_uevent *ev)
+                      struct iscsi_uevent *ev, uint32_t len)
 {
        char *data = (char *)ev + sizeof(*ev);
        struct Scsi_Host *shost;
@@ -1747,8 +1906,7 @@ iscsi_set_iface_params(struct iscsi_transport *transport,
                return -ENODEV;
        }
 
-       err = transport->set_iface_param(shost, data,
-                                        ev->u.set_iface_params.count);
+       err = transport->set_iface_param(shost, data, len);
        scsi_host_put(shost);
        return err;
 }
@@ -1893,7 +2051,8 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, uint32_t *group)
                err = iscsi_set_path(transport, ev);
                break;
        case ISCSI_UEVENT_SET_IFACE_PARAMS:
-               err = iscsi_set_iface_params(transport, ev);
+               err = iscsi_set_iface_params(transport, ev,
+                                            nlmsg_attrlen(nlh, sizeof(*ev)));
                break;
        default:
                err = -ENOSYS;
@@ -2301,13 +2460,42 @@ iscsi_host_attr(hwaddress, ISCSI_HOST_PARAM_HWADDRESS);
 iscsi_host_attr(ipaddress, ISCSI_HOST_PARAM_IPADDRESS);
 iscsi_host_attr(initiatorname, ISCSI_HOST_PARAM_INITIATOR_NAME);
 
-#define SETUP_HOST_RD_ATTR(field, param_flag)                          \
-do {                                                                   \
-       if (tt->host_param_mask & param_flag) {                         \
-               priv->host_attrs[count] = &dev_attr_host_##field; \
-               count++;                                                \
-       }                                                               \
-} while (0)
+static struct attribute *iscsi_host_attrs[] = {
+       &dev_attr_host_netdev.attr,
+       &dev_attr_host_hwaddress.attr,
+       &dev_attr_host_ipaddress.attr,
+       &dev_attr_host_initiatorname.attr,
+       NULL,
+};
+
+static mode_t iscsi_host_attr_is_visible(struct kobject *kobj,
+                                        struct attribute *attr, int i)
+{
+       struct device *cdev = container_of(kobj, struct device, kobj);
+       struct Scsi_Host *shost = transport_class_to_shost(cdev);
+       struct iscsi_internal *priv = to_iscsi_internal(shost->transportt);
+       int param;
+
+       if (attr == &dev_attr_host_netdev.attr)
+               param = ISCSI_HOST_PARAM_NETDEV_NAME;
+       else if (attr == &dev_attr_host_hwaddress.attr)
+               param = ISCSI_HOST_PARAM_HWADDRESS;
+       else if (attr == &dev_attr_host_ipaddress.attr)
+               param = ISCSI_HOST_PARAM_IPADDRESS;
+       else if (attr == &dev_attr_host_initiatorname.attr)
+               param = ISCSI_HOST_PARAM_INITIATOR_NAME;
+       else {
+               WARN_ONCE(1, "Invalid host attr");
+               return 0;
+       }
+
+       return priv->iscsi_transport->attr_is_visible(ISCSI_HOST_PARAM, param);
+}
+
+static struct attribute_group iscsi_host_group = {
+       .attrs = iscsi_host_attrs,
+       .is_visible = iscsi_host_attr_is_visible,
+};
 
 static int iscsi_session_match(struct attribute_container *cont,
                           struct device *dev)
@@ -2379,7 +2567,7 @@ iscsi_register_transport(struct iscsi_transport *tt)
 {
        struct iscsi_internal *priv;
        unsigned long flags;
-       int count = 0, err;
+       int err;
 
        BUG_ON(!tt);
 
@@ -2406,20 +2594,12 @@ iscsi_register_transport(struct iscsi_transport *tt)
                goto unregister_dev;
 
        /* host parameters */
-       priv->t.host_attrs.ac.attrs = &priv->host_attrs[0];
        priv->t.host_attrs.ac.class = &iscsi_host_class.class;
        priv->t.host_attrs.ac.match = iscsi_host_match;
+       priv->t.host_attrs.ac.grp = &iscsi_host_group;
        priv->t.host_size = sizeof(struct iscsi_cls_host);
        transport_container_register(&priv->t.host_attrs);
 
-       SETUP_HOST_RD_ATTR(netdev, ISCSI_HOST_NETDEV_NAME);
-       SETUP_HOST_RD_ATTR(ipaddress, ISCSI_HOST_IPADDRESS);
-       SETUP_HOST_RD_ATTR(hwaddress, ISCSI_HOST_HWADDRESS);
-       SETUP_HOST_RD_ATTR(initiatorname, ISCSI_HOST_INITIATOR_NAME);
-       BUG_ON(count > ISCSI_HOST_ATTRS);
-       priv->host_attrs[count] = NULL;
-       count = 0;
-
        /* connection parameters */
        priv->conn_cont.ac.class = &iscsi_connection_class.class;
        priv->conn_cont.ac.match = iscsi_conn_match;