forked from Minki/linux
389 lines
12 KiB
C
389 lines
12 KiB
C
|
/*
|
||
|
* 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.ac.class
|
||
|
!= &iscsi_host_class.class)
|
||
|
return 0;
|
||
|
|
||
|
i = to_iscsi_internal(shost->transportt);
|
||
|
|
||
|
return &i->t.host_attrs.ac == 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.ac.class
|
||
|
!= &iscsi_host_class.class)
|
||
|
return 0;
|
||
|
|
||
|
i = to_iscsi_internal(shost->transportt);
|
||
|
|
||
|
return &i->t.target_attrs.ac == 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.ac.attrs = &i->session_attrs[0];
|
||
|
i->t.target_attrs.ac.class = &iscsi_transport_class.class;
|
||
|
i->t.target_attrs.ac.match = iscsi_target_match;
|
||
|
transport_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.ac.attrs = &i->host_attrs[0];
|
||
|
i->t.host_attrs.ac.class = &iscsi_host_class.class;
|
||
|
i->t.host_attrs.ac.match = iscsi_host_match;
|
||
|
transport_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);
|
||
|
|
||
|
transport_container_unregister(&i->t.target_attrs);
|
||
|
transport_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");
|