]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/scsi/scsi_transport_fc.c
[SCSI] SCSI and FC Transport: add netlink support for posting of transport events
[linux-2.6-omap-h63xx.git] / drivers / scsi / scsi_transport_fc.c
index f2db7a41cf1d4dbbb973eb1ad033dbcaa44183e9..05989f13055437fa6cc5a481869cf2abad264360 100644 (file)
@@ -32,6 +32,9 @@
 #include <scsi/scsi_transport.h>
 #include <scsi/scsi_transport_fc.h>
 #include <scsi/scsi_cmnd.h>
+#include <linux/netlink.h>
+#include <net/netlink.h>
+#include <scsi/scsi_netlink_fc.h>
 #include "scsi_priv.h"
 
 static int fc_queue_work(struct Scsi_Host *, struct work_struct *);
@@ -93,6 +96,29 @@ fc_enum_name_search(port_type, fc_port_type, fc_port_type_names)
 #define FC_PORTTYPE_MAX_NAMELEN                50
 
 
+/* Convert fc_host_event_code values to ascii string name */
+static const struct {
+       enum fc_host_event_code         value;
+       char                            *name;
+} fc_host_event_code_names[] = {
+       { FCH_EVT_LIP,                  "lip" },
+       { FCH_EVT_LINKUP,               "link_up" },
+       { FCH_EVT_LINKDOWN,             "link_down" },
+       { FCH_EVT_LIPRESET,             "lip_reset" },
+       { FCH_EVT_RSCN,                 "rscn" },
+       { FCH_EVT_ADAPTER_CHANGE,       "adapter_chg" },
+       { FCH_EVT_PORT_UNKNOWN,         "port_unknown" },
+       { FCH_EVT_PORT_ONLINE,          "port_online" },
+       { FCH_EVT_PORT_OFFLINE,         "port_offline" },
+       { FCH_EVT_PORT_FABRIC,          "port_fabric" },
+       { FCH_EVT_LINK_UNKNOWN,         "link_unknown" },
+       { FCH_EVT_VENDOR_UNIQUE,        "vendor_unique" },
+};
+fc_enum_name_search(host_event_code, fc_host_event_code,
+               fc_host_event_code_names)
+#define FC_HOST_EVENT_CODE_MAX_NAMELEN 30
+
+
 /* Convert fc_port_state values to ascii string name */
 static struct {
        enum fc_port_state      value;
@@ -301,8 +327,6 @@ static int fc_host_setup(struct transport_container *tc, struct device *dev,
        fc_host->supported_classes = FC_COS_UNSPECIFIED;
        memset(fc_host->supported_fc4s, 0,
                sizeof(fc_host->supported_fc4s));
-       memset(fc_host->symbolic_name, 0,
-               sizeof(fc_host->symbolic_name));
        fc_host->supported_speeds = FC_PORTSPEED_UNKNOWN;
        fc_host->maxframe_size = -1;
        memset(fc_host->serial_number, 0,
@@ -315,6 +339,8 @@ static int fc_host_setup(struct transport_container *tc, struct device *dev,
                sizeof(fc_host->active_fc4s));
        fc_host->speed = FC_PORTSPEED_UNKNOWN;
        fc_host->fabric_name = -1;
+       memset(fc_host->symbolic_name, 0, sizeof(fc_host->symbolic_name));
+       memset(fc_host->system_hostname, 0, sizeof(fc_host->system_hostname));
 
        fc_host->tgtid_bind_type = FC_TGTID_BIND_BY_WWPN;
 
@@ -368,7 +394,7 @@ static DECLARE_TRANSPORT_CLASS(fc_rport_class,
  *   should insulate the loss of a remote port.
  *   The maximum will be capped by the value of SCSI_DEVICE_BLOCK_MAX_TIMEOUT.
  */
-static unsigned int fc_dev_loss_tmo = SCSI_DEVICE_BLOCK_MAX_TIMEOUT;
+static unsigned int fc_dev_loss_tmo = 60;              /* seconds */
 
 module_param_named(dev_loss_tmo, fc_dev_loss_tmo, int, S_IRUGO|S_IWUSR);
 MODULE_PARM_DESC(dev_loss_tmo,
@@ -377,10 +403,182 @@ MODULE_PARM_DESC(dev_loss_tmo,
                 " exceeded, the scsi target is removed. Value should be"
                 " between 1 and SCSI_DEVICE_BLOCK_MAX_TIMEOUT.");
 
+/**
+ * Netlink Infrastructure
+ **/
+
+static atomic_t fc_event_seq;
+
+/**
+ * fc_get_event_number - Obtain the next sequential FC event number
+ *
+ * Notes:
+ *   We could have inline'd this, but it would have required fc_event_seq to
+ *   be exposed. For now, live with the subroutine call.
+ *   Atomic used to avoid lock/unlock...
+ **/
+u32
+fc_get_event_number(void)
+{
+       return atomic_add_return(1, &fc_event_seq);
+}
+EXPORT_SYMBOL(fc_get_event_number);
+
+
+/**
+ * fc_host_post_event - called to post an even on an fc_host.
+ *
+ * @shost:             host the event occurred on
+ * @event_number:      fc event number obtained from get_fc_event_number()
+ * @event_code:                fc_host event being posted
+ * @event_data:                32bits of data for the event being posted
+ *
+ * Notes:
+ *     This routine assumes no locks are held on entry.
+ **/
+void
+fc_host_post_event(struct Scsi_Host *shost, u32 event_number,
+               enum fc_host_event_code event_code, u32 event_data)
+{
+       struct sk_buff *skb;
+       struct nlmsghdr *nlh;
+       struct fc_nl_event *event;
+       const char *name;
+       u32 len, skblen;
+       int err;
+
+       if (!scsi_nl_sock) {
+               err = -ENOENT;
+               goto send_fail;
+       }
+
+       len = FC_NL_MSGALIGN(sizeof(*event));
+       skblen = NLMSG_SPACE(len);
+
+       skb = alloc_skb(skblen, GFP_KERNEL);
+       if (!skb) {
+               err = -ENOBUFS;
+               goto send_fail;
+       }
+
+       nlh = nlmsg_put(skb, 0, 0, SCSI_TRANSPORT_MSG,
+                               skblen - sizeof(*nlh), 0);
+       if (!nlh) {
+               err = -ENOBUFS;
+               goto send_fail_skb;
+       }
+       event = NLMSG_DATA(nlh);
+
+       INIT_SCSI_NL_HDR(&event->snlh, SCSI_NL_TRANSPORT_FC,
+                               FC_NL_ASYNC_EVENT, len);
+       event->seconds = get_seconds();
+       event->vendor_id = 0;
+       event->host_no = shost->host_no;
+       event->event_datalen = sizeof(u32);     /* bytes */
+       event->event_num = event_number;
+       event->event_code = event_code;
+       event->event_data = event_data;
+
+       err = nlmsg_multicast(scsi_nl_sock, skb, 0, SCSI_NL_GRP_FC_EVENTS);
+       if (err && (err != -ESRCH))     /* filter no recipient errors */
+               /* nlmsg_multicast already kfree_skb'd */
+               goto send_fail;
+
+       return;
+
+send_fail_skb:
+       kfree_skb(skb);
+send_fail:
+       name = get_fc_host_event_code_name(event_code);
+       printk(KERN_WARNING
+               "%s: Dropped Event : host %d %s data 0x%08x - err %d\n",
+               __FUNCTION__, shost->host_no,
+               (name) ? name : "<unknown>", event_data, err);
+       return;
+}
+EXPORT_SYMBOL(fc_host_post_event);
+
+
+/**
+ * fc_host_post_vendor_event - called to post a vendor unique event on
+ *                             a fc_host
+ *
+ * @shost:             host the event occurred on
+ * @event_number:      fc event number obtained from get_fc_event_number()
+ * @data_len:          amount, in bytes, of vendor unique data
+ * @data_buf:          pointer to vendor unique data
+ *
+ * Notes:
+ *     This routine assumes no locks are held on entry.
+ **/
+void
+fc_host_post_vendor_event(struct Scsi_Host *shost, u32 event_number,
+               u32 data_len, char * data_buf, u32 vendor_id)
+{
+       struct sk_buff *skb;
+       struct nlmsghdr *nlh;
+       struct fc_nl_event *event;
+       u32 len, skblen;
+       int err;
+
+       if (!scsi_nl_sock) {
+               err = -ENOENT;
+               goto send_vendor_fail;
+       }
+
+       len = FC_NL_MSGALIGN(sizeof(*event) + data_len);
+       skblen = NLMSG_SPACE(len);
+
+       skb = alloc_skb(skblen, GFP_KERNEL);
+       if (!skb) {
+               err = -ENOBUFS;
+               goto send_vendor_fail;
+       }
+
+       nlh = nlmsg_put(skb, 0, 0, SCSI_TRANSPORT_MSG,
+                               skblen - sizeof(*nlh), 0);
+       if (!nlh) {
+               err = -ENOBUFS;
+               goto send_vendor_fail_skb;
+       }
+       event = NLMSG_DATA(nlh);
+
+       INIT_SCSI_NL_HDR(&event->snlh, SCSI_NL_TRANSPORT_FC,
+                               FC_NL_ASYNC_EVENT, len);
+       event->seconds = get_seconds();
+       event->vendor_id = vendor_id;
+       event->host_no = shost->host_no;
+       event->event_datalen = data_len;        /* bytes */
+       event->event_num = event_number;
+       event->event_code = FCH_EVT_VENDOR_UNIQUE;
+       memcpy(&event->event_data, data_buf, data_len);
+
+       err = nlmsg_multicast(scsi_nl_sock, skb, 0, SCSI_NL_GRP_FC_EVENTS);
+       if (err && (err != -ESRCH))     /* filter no recipient errors */
+               /* nlmsg_multicast already kfree_skb'd */
+               goto send_vendor_fail;
+
+       return;
+
+send_vendor_fail_skb:
+       kfree_skb(skb);
+send_vendor_fail:
+       printk(KERN_WARNING
+               "%s: Dropped Event : host %d vendor_unique - err %d\n",
+               __FUNCTION__, shost->host_no, err);
+       return;
+}
+EXPORT_SYMBOL(fc_host_post_vendor_event);
+
+
 
 static __init int fc_transport_init(void)
 {
-       int error = transport_class_register(&fc_host_class);
+       int error;
+
+       atomic_set(&fc_event_seq, 0);
+
+       error = transport_class_register(&fc_host_class);
        if (error)
                return error;
        error = transport_class_register(&fc_rport_class);
@@ -688,6 +886,25 @@ store_fc_host_##field(struct class_device *cdev, const char *buf,  \
        return count;                                                   \
 }
 
+#define fc_host_store_str_function(field, slen)                                \
+static ssize_t                                                         \
+store_fc_host_##field(struct class_device *cdev, const char *buf,      \
+                          size_t count)                                \
+{                                                                      \
+       struct Scsi_Host *shost = transport_class_to_shost(cdev);       \
+       struct fc_internal *i = to_fc_internal(shost->transportt);      \
+       unsigned int cnt=count;                                         \
+                                                                       \
+       /* count may include a LF at end of string */                   \
+       if (buf[cnt-1] == '\n')                                         \
+               cnt--;                                                  \
+       if (cnt > ((slen) - 1))                                         \
+               return -EINVAL;                                         \
+       memcpy(fc_host_##field(shost), buf, cnt);                       \
+       i->f->set_host_##field(shost);                                  \
+       return count;                                                   \
+}
+
 #define fc_host_rd_attr(field, format_string, sz)                      \
        fc_host_show_function(field, format_string, sz, )               \
 static FC_CLASS_DEVICE_ATTR(host, field, S_IRUGO,                      \
@@ -815,7 +1032,6 @@ fc_private_host_rd_attr_cast(node_name, "0x%llx\n", 20, unsigned long long);
 fc_private_host_rd_attr_cast(port_name, "0x%llx\n", 20, unsigned long long);
 fc_private_host_rd_attr_cast(permanent_port_name, "0x%llx\n", 20,
                             unsigned long long);
-fc_private_host_rd_attr(symbolic_name, "%s\n", (FC_SYMBOLIC_NAME_SIZE +1));
 fc_private_host_rd_attr(maxframe_size, "%u bytes\n", 20);
 fc_private_host_rd_attr(serial_number, "%s\n", (FC_SERIAL_NUMBER_SIZE +1));
 
@@ -858,6 +1074,13 @@ fc_host_rd_attr(port_id, "0x%06x\n", 20);
 fc_host_rd_enum_attr(port_type, FC_PORTTYPE_MAX_NAMELEN);
 fc_host_rd_enum_attr(port_state, FC_PORTSTATE_MAX_NAMELEN);
 fc_host_rd_attr_cast(fabric_name, "0x%llx\n", 20, unsigned long long);
+fc_host_rd_attr(symbolic_name, "%s\n", FC_SYMBOLIC_NAME_SIZE + 1);
+
+fc_private_host_show_function(system_hostname, "%s\n",
+               FC_SYMBOLIC_NAME_SIZE + 1, )
+fc_host_store_str_function(system_hostname, FC_SYMBOLIC_NAME_SIZE)
+static FC_CLASS_DEVICE_ATTR(host, system_hostname, S_IRUGO | S_IWUSR,
+               show_fc_host_system_hostname, store_fc_host_system_hostname);
 
 
 /* Private Host Attributes */
@@ -1223,7 +1446,6 @@ fc_attach_transport(struct fc_function_template *ft)
        SETUP_HOST_ATTRIBUTE_RD(permanent_port_name);
        SETUP_HOST_ATTRIBUTE_RD(supported_classes);
        SETUP_HOST_ATTRIBUTE_RD(supported_fc4s);
-       SETUP_HOST_ATTRIBUTE_RD(symbolic_name);
        SETUP_HOST_ATTRIBUTE_RD(supported_speeds);
        SETUP_HOST_ATTRIBUTE_RD(maxframe_size);
        SETUP_HOST_ATTRIBUTE_RD(serial_number);
@@ -1234,6 +1456,8 @@ fc_attach_transport(struct fc_function_template *ft)
        SETUP_HOST_ATTRIBUTE_RD(active_fc4s);
        SETUP_HOST_ATTRIBUTE_RD(speed);
        SETUP_HOST_ATTRIBUTE_RD(fabric_name);
+       SETUP_HOST_ATTRIBUTE_RD(symbolic_name);
+       SETUP_HOST_ATTRIBUTE_RW(system_hostname);
 
        /* Transport-managed attributes */
        SETUP_PRIVATE_HOST_ATTRIBUTE_RW(tgtid_bind_type);
@@ -1284,7 +1508,9 @@ EXPORT_SYMBOL(fc_release_transport);
  * @work:      Work to queue for execution.
  *
  * Return value:
- *     0 on success / != 0 for error
+ *     1 - work queued for execution
+ *     0 - work is already queued
+ *     -EINVAL - work queue doesn't exist
  **/
 static int
 fc_queue_work(struct Scsi_Host *shost, struct work_struct *work)
@@ -1434,8 +1660,6 @@ fc_starget_delete(void *data)
        struct Scsi_Host *shost = rport_to_shost(rport);
        unsigned long flags;
 
-       scsi_target_unblock(&rport->dev);
-
        spin_lock_irqsave(shost->host_lock, flags);
        if (rport->flags & FC_RPORT_DEVLOSS_PENDING) {
                spin_unlock_irqrestore(shost->host_lock, flags);
@@ -1476,7 +1700,8 @@ fc_rport_final_delete(void *data)
        transport_remove_device(dev);
        device_del(dev);
        transport_destroy_device(dev);
-       put_device(&shost->shost_gendev);
+       put_device(&shost->shost_gendev);       /* for fc_host->rport list */
+       put_device(dev);                        /* for self-reference */
 }
 
 
@@ -1537,13 +1762,13 @@ fc_rport_create(struct Scsi_Host *shost, int channel,
        else
                rport->scsi_target_id = -1;
        list_add_tail(&rport->peers, &fc_host->rports);
-       get_device(&shost->shost_gendev);
+       get_device(&shost->shost_gendev);       /* for fc_host->rport list */
 
        spin_unlock_irqrestore(shost->host_lock, flags);
 
        dev = &rport->dev;
-       device_initialize(dev);
-       dev->parent = get_device(&shost->shost_gendev);
+       device_initialize(dev);                 /* takes self reference */
+       dev->parent = get_device(&shost->shost_gendev); /* parent reference */
        dev->release = fc_rport_dev_release;
        sprintf(dev->bus_id, "rport-%d:%d-%d",
                shost->host_no, channel, rport->number);
@@ -1567,10 +1792,9 @@ fc_rport_create(struct Scsi_Host *shost, int channel,
 
 delete_rport:
        transport_destroy_device(dev);
-       put_device(dev->parent);
        spin_lock_irqsave(shost->host_lock, flags);
        list_del(&rport->peers);
-       put_device(&shost->shost_gendev);
+       put_device(&shost->shost_gendev);       /* for fc_host->rport list */
        spin_unlock_irqrestore(shost->host_lock, flags);
        put_device(dev->parent);
        kfree(rport);
@@ -1707,6 +1931,8 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel,
 
                                spin_unlock_irqrestore(shost->host_lock, flags);
 
+                               scsi_target_unblock(&rport->dev);
+
                                return rport;
                        }
                }
@@ -1762,9 +1988,10 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel,
                                /* initiate a scan of the target */
                                rport->flags |= FC_RPORT_SCAN_PENDING;
                                scsi_queue_work(shost, &rport->scan_work);
-                       }
-
-                       spin_unlock_irqrestore(shost->host_lock, flags);
+                               spin_unlock_irqrestore(shost->host_lock, flags);
+                               scsi_target_unblock(&rport->dev);
+                       } else
+                               spin_unlock_irqrestore(shost->host_lock, flags);
 
                        return rport;
                }
@@ -1938,6 +2165,7 @@ fc_remote_port_rolechg(struct fc_rport  *rport, u32 roles)
                rport->flags |= FC_RPORT_SCAN_PENDING;
                scsi_queue_work(shost, &rport->scan_work);
                spin_unlock_irqrestore(shost->host_lock, flags);
+               scsi_target_unblock(&rport->dev);
        }
 }
 EXPORT_SYMBOL(fc_remote_port_rolechg);
@@ -1970,8 +2198,9 @@ fc_timeout_deleted_rport(void  *data)
                dev_printk(KERN_ERR, &rport->dev,
                        "blocked FC remote port time out: no longer"
                        " a FCP target, removing starget\n");
-               fc_queue_work(shost, &rport->stgt_delete_work);
                spin_unlock_irqrestore(shost->host_lock, flags);
+               scsi_target_unblock(&rport->dev);
+               fc_queue_work(shost, &rport->stgt_delete_work);
                return;
        }
 
@@ -2035,17 +2264,15 @@ fc_timeout_deleted_rport(void  *data)
         * went away and didn't come back - we'll remove
         * all attached scsi devices.
         */
-       fc_queue_work(shost, &rport->stgt_delete_work);
-
        spin_unlock_irqrestore(shost->host_lock, flags);
+
+       scsi_target_unblock(&rport->dev);
+       fc_queue_work(shost, &rport->stgt_delete_work);
 }
 
 /**
  * fc_scsi_scan_rport - called to perform a scsi scan on a remote port.
  *
- * Will unblock the target (in case it went away and has now come back),
- * then invoke a scan.
- *
  * @data:      remote port to be scanned.
  **/
 static void
@@ -2057,7 +2284,6 @@ fc_scsi_scan_rport(void *data)
 
        if ((rport->port_state == FC_PORTSTATE_ONLINE) &&
            (rport->roles & FC_RPORT_ROLE_FCP_TARGET)) {
-               scsi_target_unblock(&rport->dev);
                scsi_scan_target(&rport->dev, rport->channel,
                        rport->scsi_target_id, SCAN_WILD_CARD, 1);
        }