| /* |
| * 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 <linux/string.h> |
| #include <linux/slab.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"); |