This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / drivers / scsi / scsi_transport_iscsi.c
diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c
new file mode 100644 (file)
index 0000000..b0aff94
--- /dev/null
@@ -0,0 +1,388 @@
+/* 
+ * iSCSI transport class definitions
+ *
+ * Copyright (C) IBM Corporation, 2004
+ * Copyright (C) Mike Christie, 2004
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#include <linux/module.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_transport.h>
+#include <scsi/scsi_transport_iscsi.h>
+
+#define ISCSI_SESSION_ATTRS 20
+#define ISCSI_HOST_ATTRS 2
+
+struct iscsi_internal {
+       struct scsi_transport_template t;
+       struct iscsi_function_template *fnt;
+       /*
+        * We do not have any private or other attrs.
+        */
+       struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1];
+       struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1];
+};
+
+#define to_iscsi_internal(tmpl) container_of(tmpl, struct iscsi_internal, t)
+
+static DECLARE_TRANSPORT_CLASS(iscsi_transport_class,
+                              "iscsi_transport",
+                              NULL,
+                              NULL,
+                              NULL);
+
+static DECLARE_TRANSPORT_CLASS(iscsi_host_class,
+                              "iscsi_host",
+                              NULL,
+                              NULL,
+                              NULL);
+/*
+ * iSCSI target and session attrs
+ */
+#define iscsi_session_show_fn(field, format)                           \
+                                                                       \
+static ssize_t                                                         \
+show_session_##field(struct class_device *cdev, char *buf)             \
+{                                                                      \
+       struct scsi_target *starget = transport_class_to_starget(cdev); \
+       struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);    \
+       struct iscsi_internal *i = to_iscsi_internal(shost->transportt); \
+                                                                       \
+       if (i->fnt->get_##field)                                        \
+               i->fnt->get_##field(starget);                           \
+       return snprintf(buf, 20, format"\n", iscsi_##field(starget));   \
+}
+
+#define iscsi_session_rd_attr(field, format)                           \
+       iscsi_session_show_fn(field, format)                            \
+static CLASS_DEVICE_ATTR(field, S_IRUGO, show_session_##field, NULL);
+
+iscsi_session_rd_attr(tpgt, "%hu");
+iscsi_session_rd_attr(tsih, "%2x");
+iscsi_session_rd_attr(max_recv_data_segment_len, "%u");
+iscsi_session_rd_attr(max_burst_len, "%u");
+iscsi_session_rd_attr(first_burst_len, "%u");
+iscsi_session_rd_attr(def_time2wait, "%hu");
+iscsi_session_rd_attr(def_time2retain, "%hu");
+iscsi_session_rd_attr(max_outstanding_r2t, "%hu");
+iscsi_session_rd_attr(erl, "%d");
+
+
+#define iscsi_session_show_bool_fn(field)                              \
+                                                                       \
+static ssize_t                                                         \
+show_session_bool_##field(struct class_device *cdev, char *buf)                \
+{                                                                      \
+       struct scsi_target *starget = transport_class_to_starget(cdev); \
+       struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);    \
+       struct iscsi_internal *i = to_iscsi_internal(shost->transportt); \
+                                                                       \
+       if (i->fnt->get_##field)                                        \
+               i->fnt->get_##field(starget);                           \
+                                                                       \
+       if (iscsi_##field(starget))                                     \
+               return sprintf(buf, "Yes\n");                           \
+       return sprintf(buf, "No\n");                                    \
+}
+
+#define iscsi_session_rd_bool_attr(field)                              \
+       iscsi_session_show_bool_fn(field)                               \
+static CLASS_DEVICE_ATTR(field, S_IRUGO, show_session_bool_##field, NULL);
+
+iscsi_session_rd_bool_attr(initial_r2t);
+iscsi_session_rd_bool_attr(immediate_data);
+iscsi_session_rd_bool_attr(data_pdu_in_order);
+iscsi_session_rd_bool_attr(data_sequence_in_order);
+
+#define iscsi_session_show_digest_fn(field)                            \
+                                                                       \
+static ssize_t                                                         \
+show_##field(struct class_device *cdev, char *buf)                     \
+{                                                                      \
+       struct scsi_target *starget = transport_class_to_starget(cdev); \
+       struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);    \
+       struct iscsi_internal *i = to_iscsi_internal(shost->transportt); \
+                                                                       \
+       if (i->fnt->get_##field)                                        \
+               i->fnt->get_##field(starget);                           \
+                                                                       \
+       if (iscsi_##field(starget))                                     \
+               return sprintf(buf, "CRC32C\n");                        \
+       return sprintf(buf, "None\n");                                  \
+}
+
+#define iscsi_session_rd_digest_attr(field)                            \
+       iscsi_session_show_digest_fn(field)                             \
+static CLASS_DEVICE_ATTR(field, S_IRUGO, show_##field, NULL);
+
+iscsi_session_rd_digest_attr(header_digest);
+iscsi_session_rd_digest_attr(data_digest);
+
+static ssize_t
+show_port(struct class_device *cdev, char *buf)
+{
+       struct scsi_target *starget = transport_class_to_starget(cdev);
+       struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+       struct iscsi_internal *i = to_iscsi_internal(shost->transportt);
+
+       if (i->fnt->get_port)
+               i->fnt->get_port(starget);
+
+       return snprintf(buf, 20, "%hu\n", ntohs(iscsi_port(starget)));
+}
+static CLASS_DEVICE_ATTR(port, S_IRUGO, show_port, NULL);
+
+static ssize_t
+show_ip_address(struct class_device *cdev, char *buf)
+{
+       struct scsi_target *starget = transport_class_to_starget(cdev);
+       struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+       struct iscsi_internal *i = to_iscsi_internal(shost->transportt);
+
+       if (i->fnt->get_ip_address)
+               i->fnt->get_ip_address(starget);
+
+       if (iscsi_addr_type(starget) == AF_INET)
+               return sprintf(buf, "%u.%u.%u.%u\n",
+                              NIPQUAD(iscsi_sin_addr(starget)));
+       else if(iscsi_addr_type(starget) == AF_INET6)
+               return sprintf(buf, "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+                              NIP6(iscsi_sin6_addr(starget)));
+       return -EINVAL;
+}
+static CLASS_DEVICE_ATTR(ip_address, S_IRUGO, show_ip_address, NULL);
+
+static ssize_t
+show_isid(struct class_device *cdev, char *buf)
+{
+       struct scsi_target *starget = transport_class_to_starget(cdev);
+       struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
+       struct iscsi_internal *i = to_iscsi_internal(shost->transportt);
+
+       if (i->fnt->get_isid)
+               i->fnt->get_isid(starget);
+
+       return sprintf(buf, "%02x%02x%02x%02x%02x%02x\n",
+                      iscsi_isid(starget)[0], iscsi_isid(starget)[1],
+                      iscsi_isid(starget)[2], iscsi_isid(starget)[3],
+                      iscsi_isid(starget)[4], iscsi_isid(starget)[5]);
+}
+static CLASS_DEVICE_ATTR(isid, S_IRUGO, show_isid, NULL);
+
+/*
+ * This is used for iSCSI names. Normally, we follow
+ * the transport class convention of having the lld
+ * set the field, but in these cases the value is
+ * too large.
+ */
+#define iscsi_session_show_str_fn(field)                               \
+                                                                       \
+static ssize_t                                                         \
+show_session_str_##field(struct class_device *cdev, char *buf)         \
+{                                                                      \
+       ssize_t ret = 0;                                                \
+       struct scsi_target *starget = transport_class_to_starget(cdev); \
+       struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);    \
+       struct iscsi_internal *i = to_iscsi_internal(shost->transportt); \
+                                                                       \
+       if (i->fnt->get_##field)                                        \
+               ret = i->fnt->get_##field(starget, buf, PAGE_SIZE);     \
+       return ret;                                                     \
+}
+
+#define iscsi_session_rd_str_attr(field)                               \
+       iscsi_session_show_str_fn(field)                                \
+static CLASS_DEVICE_ATTR(field, S_IRUGO, show_session_str_##field, NULL);
+
+iscsi_session_rd_str_attr(target_name);
+iscsi_session_rd_str_attr(target_alias);
+
+/*
+ * iSCSI host attrs
+ */
+
+/*
+ * Again, this is used for iSCSI names. Normally, we follow
+ * the transport class convention of having the lld set
+ * the field, but in these cases the value is too large.
+ */
+#define iscsi_host_show_str_fn(field)                                  \
+                                                                       \
+static ssize_t                                                         \
+show_host_str_##field(struct class_device *cdev, char *buf)            \
+{                                                                      \
+       int ret = 0;                                                    \
+       struct Scsi_Host *shost = transport_class_to_shost(cdev);       \
+       struct iscsi_internal *i = to_iscsi_internal(shost->transportt); \
+                                                                       \
+       if (i->fnt->get_##field)                                        \
+               ret = i->fnt->get_##field(shost, buf, PAGE_SIZE);       \
+       return ret;                                                     \
+}
+
+#define iscsi_host_rd_str_attr(field)                                  \
+       iscsi_host_show_str_fn(field)                                   \
+static CLASS_DEVICE_ATTR(field, S_IRUGO, show_host_str_##field, NULL);
+
+iscsi_host_rd_str_attr(initiator_name);
+iscsi_host_rd_str_attr(initiator_alias);
+
+#define SETUP_SESSION_RD_ATTR(field)                                   \
+       if (i->fnt->show_##field) {                                     \
+               i->session_attrs[count] = &class_device_attr_##field;   \
+               count++;                                                \
+       }
+
+#define SETUP_HOST_RD_ATTR(field)                                      \
+       if (i->fnt->show_##field) {                                     \
+               i->host_attrs[count] = &class_device_attr_##field;      \
+               count++;                                                \
+       }
+
+static int iscsi_host_match(struct attribute_container *cont,
+                         struct device *dev)
+{
+       struct Scsi_Host *shost;
+       struct iscsi_internal *i;
+
+       if (!scsi_is_host_device(dev))
+               return 0;
+
+       shost = dev_to_shost(dev);
+       if (!shost->transportt  || shost->transportt->host_attrs.class
+           != &iscsi_host_class.class)
+               return 0;
+
+       i = to_iscsi_internal(shost->transportt);
+       
+       return &i->t.host_attrs == cont;
+}
+
+static int iscsi_target_match(struct attribute_container *cont,
+                           struct device *dev)
+{
+       struct Scsi_Host *shost;
+       struct iscsi_internal *i;
+
+       if (!scsi_is_target_device(dev))
+               return 0;
+
+       shost = dev_to_shost(dev->parent);
+       if (!shost->transportt  || shost->transportt->host_attrs.class
+           != &iscsi_host_class.class)
+               return 0;
+
+       i = to_iscsi_internal(shost->transportt);
+       
+       return &i->t.target_attrs == cont;
+}
+
+struct scsi_transport_template *
+iscsi_attach_transport(struct iscsi_function_template *fnt)
+{
+       struct iscsi_internal *i = kmalloc(sizeof(struct iscsi_internal),
+                                          GFP_KERNEL);
+       int count = 0;
+
+       if (unlikely(!i))
+               return NULL;
+
+       memset(i, 0, sizeof(struct iscsi_internal));
+       i->fnt = fnt;
+
+       i->t.target_attrs.attrs = &i->session_attrs[0];
+       i->t.target_attrs.class = &iscsi_transport_class.class;
+       i->t.target_attrs.match = iscsi_target_match;
+       attribute_container_register(&i->t.target_attrs);
+       i->t.target_size = sizeof(struct iscsi_class_session);
+
+       SETUP_SESSION_RD_ATTR(tsih);
+       SETUP_SESSION_RD_ATTR(isid);
+       SETUP_SESSION_RD_ATTR(header_digest);
+       SETUP_SESSION_RD_ATTR(data_digest);
+       SETUP_SESSION_RD_ATTR(target_name);
+       SETUP_SESSION_RD_ATTR(target_alias);
+       SETUP_SESSION_RD_ATTR(port);
+       SETUP_SESSION_RD_ATTR(tpgt);
+       SETUP_SESSION_RD_ATTR(ip_address);
+       SETUP_SESSION_RD_ATTR(initial_r2t);
+       SETUP_SESSION_RD_ATTR(immediate_data);
+       SETUP_SESSION_RD_ATTR(max_recv_data_segment_len);
+       SETUP_SESSION_RD_ATTR(max_burst_len);
+       SETUP_SESSION_RD_ATTR(first_burst_len);
+       SETUP_SESSION_RD_ATTR(def_time2wait);
+       SETUP_SESSION_RD_ATTR(def_time2retain);
+       SETUP_SESSION_RD_ATTR(max_outstanding_r2t);
+       SETUP_SESSION_RD_ATTR(data_pdu_in_order);
+       SETUP_SESSION_RD_ATTR(data_sequence_in_order);
+       SETUP_SESSION_RD_ATTR(erl);
+
+       BUG_ON(count > ISCSI_SESSION_ATTRS);
+       i->session_attrs[count] = NULL;
+
+       i->t.host_attrs.attrs = &i->host_attrs[0];
+       i->t.host_attrs.class = &iscsi_host_class.class;
+       i->t.host_attrs.match = iscsi_host_match;
+       attribute_container_register(&i->t.host_attrs);
+       i->t.host_size = 0;
+
+       count = 0;
+       SETUP_HOST_RD_ATTR(initiator_name);
+       SETUP_HOST_RD_ATTR(initiator_alias);
+
+       BUG_ON(count > ISCSI_HOST_ATTRS);
+       i->host_attrs[count] = NULL;
+
+       return &i->t;
+}
+
+EXPORT_SYMBOL(iscsi_attach_transport);
+
+void iscsi_release_transport(struct scsi_transport_template *t)
+{
+       struct iscsi_internal *i = to_iscsi_internal(t);
+
+       attribute_container_unregister(&i->t.target_attrs);
+       attribute_container_unregister(&i->t.host_attrs);
+
+       kfree(i);
+}
+
+EXPORT_SYMBOL(iscsi_release_transport);
+
+static __init int iscsi_transport_init(void)
+{
+       int err = transport_class_register(&iscsi_transport_class);
+
+       if (err)
+               return err;
+       return transport_class_register(&iscsi_host_class);
+}
+
+static void __exit iscsi_transport_exit(void)
+{
+       transport_class_unregister(&iscsi_host_class);
+       transport_class_unregister(&iscsi_transport_class);
+}
+
+module_init(iscsi_transport_init);
+module_exit(iscsi_transport_exit);
+
+MODULE_AUTHOR("Mike Christie");
+MODULE_DESCRIPTION("iSCSI Transport Attributes");
+MODULE_LICENSE("GPL");