]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/scsi/scsi_netlink.c
[SCSI] fix netlink kernel-doc
[linux-2.6-omap-h63xx.git] / drivers / scsi / scsi_netlink.c
index ae7ed9a226623db41e1354a12c6f97d5d83bc324..723fdecd91bdd4ba027ae4edd20470c6d84bc520 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/time.h>
 #include <linux/jiffies.h>
 #include <linux/security.h>
+#include <linux/delay.h>
 #include <net/sock.h>
 #include <net/netlink.h>
 
 struct sock *scsi_nl_sock = NULL;
 EXPORT_SYMBOL_GPL(scsi_nl_sock);
 
+static DEFINE_SPINLOCK(scsi_nl_lock);
+static struct list_head scsi_nl_drivers;
+
+static u32     scsi_nl_state;
+#define STATE_EHANDLER_BSY             0x00000001
+
+struct scsi_nl_transport {
+       int (*msg_handler)(struct sk_buff *);
+       void (*event_handler)(struct notifier_block *, unsigned long, void *);
+       unsigned int refcnt;
+       int flags;
+};
+
+/* flags values (bit flags) */
+#define HANDLER_DELETING               0x1
+
+static struct scsi_nl_transport transports[SCSI_NL_MAX_TRANSPORTS] =
+       { {NULL, }, };
+
+
+struct scsi_nl_drvr {
+       struct list_head next;
+       int (*dmsg_handler)(struct Scsi_Host *shost, void *payload,
+                                u32 len, u32 pid);
+       void (*devt_handler)(struct notifier_block *nb,
+                                unsigned long event, void *notify_ptr);
+       struct scsi_host_template *hostt;
+       u64 vendor_id;
+       unsigned int refcnt;
+       int flags;
+};
+
+
 
 /**
  * scsi_nl_rcv_msg - Receive message handler.
@@ -45,8 +79,9 @@ scsi_nl_rcv_msg(struct sk_buff *skb)
 {
        struct nlmsghdr *nlh;
        struct scsi_nl_hdr *hdr;
-       uint32_t rlen;
-       int err;
+       unsigned long flags;
+       u32 rlen;
+       int err, tport;
 
        while (skb->len >= NLMSG_SPACE(0)) {
                err = 0;
@@ -65,7 +100,7 @@ scsi_nl_rcv_msg(struct sk_buff *skb)
 
                if (nlh->nlmsg_type != SCSI_TRANSPORT_MSG) {
                        err = -EBADMSG;
-                       return;
+                       goto next_msg;
                }
 
                hdr = NLMSG_DATA(nlh);
@@ -83,12 +118,27 @@ scsi_nl_rcv_msg(struct sk_buff *skb)
                if (nlh->nlmsg_len < (sizeof(*nlh) + hdr->msglen)) {
                        printk(KERN_WARNING "%s: discarding partial message\n",
                                 __func__);
-                       return;
+                       goto next_msg;
                }
 
                /*
-                * We currently don't support anyone sending us a message
+                * Deliver message to the appropriate transport
                 */
+               spin_lock_irqsave(&scsi_nl_lock, flags);
+
+               tport = hdr->transport;
+               if ((tport < SCSI_NL_MAX_TRANSPORTS) &&
+                   !(transports[tport].flags & HANDLER_DELETING) &&
+                   (transports[tport].msg_handler)) {
+                       transports[tport].refcnt++;
+                       spin_unlock_irqrestore(&scsi_nl_lock, flags);
+                       err = transports[tport].msg_handler(skb);
+                       spin_lock_irqsave(&scsi_nl_lock, flags);
+                       transports[tport].refcnt--;
+               } else
+                       err = -ENOENT;
+
+               spin_unlock_irqrestore(&scsi_nl_lock, flags);
 
 next_msg:
                if ((err) || (nlh->nlmsg_flags & NLM_F_ACK))
@@ -110,14 +160,42 @@ static int
 scsi_nl_rcv_event(struct notifier_block *this, unsigned long event, void *ptr)
 {
        struct netlink_notify *n = ptr;
+       struct scsi_nl_drvr *driver;
+       unsigned long flags;
+       int tport;
 
        if (n->protocol != NETLINK_SCSITRANSPORT)
                return NOTIFY_DONE;
 
+       spin_lock_irqsave(&scsi_nl_lock, flags);
+       scsi_nl_state |= STATE_EHANDLER_BSY;
+
+       /*
+        * Pass event on to any transports that may be listening
+        */
+       for (tport = 0; tport < SCSI_NL_MAX_TRANSPORTS; tport++) {
+               if (!(transports[tport].flags & HANDLER_DELETING) &&
+                   (transports[tport].event_handler)) {
+                       spin_unlock_irqrestore(&scsi_nl_lock, flags);
+                       transports[tport].event_handler(this, event, ptr);
+                       spin_lock_irqsave(&scsi_nl_lock, flags);
+               }
+       }
+
        /*
-        * Currently, we are not tracking PID's, etc. There is nothing
-        * to handle.
+        * Pass event on to any drivers that may be listening
         */
+       list_for_each_entry(driver, &scsi_nl_drivers, next) {
+               if (!(driver->flags & HANDLER_DELETING) &&
+                   (driver->devt_handler)) {
+                       spin_unlock_irqrestore(&scsi_nl_lock, flags);
+                       driver->devt_handler(this, event, ptr);
+                       spin_lock_irqsave(&scsi_nl_lock, flags);
+               }
+       }
+
+       scsi_nl_state &= ~STATE_EHANDLER_BSY;
+       spin_unlock_irqrestore(&scsi_nl_lock, flags);
 
        return NOTIFY_DONE;
 }
@@ -127,8 +205,279 @@ static struct notifier_block scsi_netlink_notifier = {
 };
 
 
+/*
+ * GENERIC SCSI transport receive and event handlers
+ */
+
+/**
+ * scsi_generic_msg_handler - receive message handler for GENERIC transport messages
+ * @skb:               socket receive buffer
+ **/
+static int
+scsi_generic_msg_handler(struct sk_buff *skb)
+{
+       struct nlmsghdr *nlh = nlmsg_hdr(skb);
+       struct scsi_nl_hdr *snlh = NLMSG_DATA(nlh);
+       struct scsi_nl_drvr *driver;
+       struct Scsi_Host *shost;
+       unsigned long flags;
+       int err = 0, match, pid;
+
+       pid = NETLINK_CREDS(skb)->pid;
+
+       switch (snlh->msgtype) {
+       case SCSI_NL_SHOST_VENDOR:
+               {
+               struct scsi_nl_host_vendor_msg *msg = NLMSG_DATA(nlh);
+
+               /* Locate the driver that corresponds to the message */
+               spin_lock_irqsave(&scsi_nl_lock, flags);
+               match = 0;
+               list_for_each_entry(driver, &scsi_nl_drivers, next) {
+                       if (driver->vendor_id == msg->vendor_id) {
+                               match = 1;
+                               break;
+                       }
+               }
+
+               if ((!match) || (!driver->dmsg_handler)) {
+                       spin_unlock_irqrestore(&scsi_nl_lock, flags);
+                       err = -ESRCH;
+                       goto rcv_exit;
+               }
+
+               if (driver->flags & HANDLER_DELETING) {
+                       spin_unlock_irqrestore(&scsi_nl_lock, flags);
+                       err = -ESHUTDOWN;
+                       goto rcv_exit;
+               }
+
+               driver->refcnt++;
+               spin_unlock_irqrestore(&scsi_nl_lock, flags);
+
+
+               /* if successful, scsi_host_lookup takes a shost reference */
+               shost = scsi_host_lookup(msg->host_no);
+               if (!shost) {
+                       err = -ENODEV;
+                       goto driver_exit;
+               }
+
+               /* is this host owned by the vendor ? */
+               if (shost->hostt != driver->hostt) {
+                       err = -EINVAL;
+                       goto vendormsg_put;
+               }
+
+               /* pass message on to the driver */
+               err = driver->dmsg_handler(shost, (void *)&msg[1],
+                                        msg->vmsg_datalen, pid);
+
+vendormsg_put:
+               /* release reference by scsi_host_lookup */
+               scsi_host_put(shost);
+
+driver_exit:
+               /* release our own reference on the registration object */
+               spin_lock_irqsave(&scsi_nl_lock, flags);
+               driver->refcnt--;
+               spin_unlock_irqrestore(&scsi_nl_lock, flags);
+               break;
+               }
+
+       default:
+               err = -EBADR;
+               break;
+       }
+
+rcv_exit:
+       if (err)
+               printk(KERN_WARNING "%s: Msgtype %d failed - err %d\n",
+                        __func__, snlh->msgtype, err);
+       return err;
+}
+
+
+/**
+ * scsi_nl_add_transport -
+ *    Registers message and event handlers for a transport. Enables
+ *    receipt of netlink messages and events to a transport.
+ *
+ * @tport:             transport registering handlers
+ * @msg_handler:       receive message handler callback
+ * @event_handler:     receive event handler callback
+ **/
+int
+scsi_nl_add_transport(u8 tport,
+       int (*msg_handler)(struct sk_buff *),
+       void (*event_handler)(struct notifier_block *, unsigned long, void *))
+{
+       unsigned long flags;
+       int err = 0;
+
+       if (tport >= SCSI_NL_MAX_TRANSPORTS)
+               return -EINVAL;
+
+       spin_lock_irqsave(&scsi_nl_lock, flags);
+
+       if (scsi_nl_state & STATE_EHANDLER_BSY) {
+               spin_unlock_irqrestore(&scsi_nl_lock, flags);
+               msleep(1);
+               spin_lock_irqsave(&scsi_nl_lock, flags);
+       }
+
+       if (transports[tport].msg_handler || transports[tport].event_handler) {
+               err = -EALREADY;
+               goto register_out;
+       }
+
+       transports[tport].msg_handler = msg_handler;
+       transports[tport].event_handler = event_handler;
+       transports[tport].flags = 0;
+       transports[tport].refcnt = 0;
+
+register_out:
+       spin_unlock_irqrestore(&scsi_nl_lock, flags);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(scsi_nl_add_transport);
+
+
 /**
- * scsi_netlink_init - Called by SCSI subsystem to intialize the SCSI transport netlink interface
+ * scsi_nl_remove_transport -
+ *    Disable transport receiption of messages and events
+ *
+ * @tport:             transport deregistering handlers
+ *
+ **/
+void
+scsi_nl_remove_transport(u8 tport)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&scsi_nl_lock, flags);
+       if (scsi_nl_state & STATE_EHANDLER_BSY) {
+               spin_unlock_irqrestore(&scsi_nl_lock, flags);
+               msleep(1);
+               spin_lock_irqsave(&scsi_nl_lock, flags);
+       }
+
+       if (tport < SCSI_NL_MAX_TRANSPORTS) {
+               transports[tport].flags |= HANDLER_DELETING;
+
+               while (transports[tport].refcnt != 0) {
+                       spin_unlock_irqrestore(&scsi_nl_lock, flags);
+                       schedule_timeout_uninterruptible(HZ/4);
+                       spin_lock_irqsave(&scsi_nl_lock, flags);
+               }
+               transports[tport].msg_handler = NULL;
+               transports[tport].event_handler = NULL;
+               transports[tport].flags = 0;
+       }
+
+       spin_unlock_irqrestore(&scsi_nl_lock, flags);
+
+       return;
+}
+EXPORT_SYMBOL_GPL(scsi_nl_remove_transport);
+
+
+/**
+ * scsi_nl_add_driver -
+ *    A driver is registering its interfaces for SCSI netlink messages
+ *
+ * @vendor_id:          A unique identification value for the driver.
+ * @hostt:             address of the driver's host template. Used
+ *                     to verify an shost is bound to the driver
+ * @nlmsg_handler:     receive message handler callback
+ * @nlevt_handler:     receive event handler callback
+ *
+ * Returns:
+ *   0 on Success
+ *   error result otherwise
+ **/
+int
+scsi_nl_add_driver(u64 vendor_id, struct scsi_host_template *hostt,
+       int (*nlmsg_handler)(struct Scsi_Host *shost, void *payload,
+                                u32 len, u32 pid),
+       void (*nlevt_handler)(struct notifier_block *nb,
+                                unsigned long event, void *notify_ptr))
+{
+       struct scsi_nl_drvr *driver;
+       unsigned long flags;
+
+       driver = kzalloc(sizeof(*driver), GFP_KERNEL);
+       if (unlikely(!driver)) {
+               printk(KERN_ERR "%s: allocation failure\n", __func__);
+               return -ENOMEM;
+       }
+
+       driver->dmsg_handler = nlmsg_handler;
+       driver->devt_handler = nlevt_handler;
+       driver->hostt = hostt;
+       driver->vendor_id = vendor_id;
+
+       spin_lock_irqsave(&scsi_nl_lock, flags);
+       if (scsi_nl_state & STATE_EHANDLER_BSY) {
+               spin_unlock_irqrestore(&scsi_nl_lock, flags);
+               msleep(1);
+               spin_lock_irqsave(&scsi_nl_lock, flags);
+       }
+       list_add_tail(&driver->next, &scsi_nl_drivers);
+       spin_unlock_irqrestore(&scsi_nl_lock, flags);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(scsi_nl_add_driver);
+
+
+/**
+ * scsi_nl_remove_driver -
+ *    An driver is unregistering with the SCSI netlink messages
+ *
+ * @vendor_id:          The unique identification value for the driver.
+ **/
+void
+scsi_nl_remove_driver(u64 vendor_id)
+{
+       struct scsi_nl_drvr *driver;
+       unsigned long flags;
+
+       spin_lock_irqsave(&scsi_nl_lock, flags);
+       if (scsi_nl_state & STATE_EHANDLER_BSY) {
+               spin_unlock_irqrestore(&scsi_nl_lock, flags);
+               msleep(1);
+               spin_lock_irqsave(&scsi_nl_lock, flags);
+       }
+
+       list_for_each_entry(driver, &scsi_nl_drivers, next) {
+               if (driver->vendor_id == vendor_id) {
+                       driver->flags |= HANDLER_DELETING;
+                       while (driver->refcnt != 0) {
+                               spin_unlock_irqrestore(&scsi_nl_lock, flags);
+                               schedule_timeout_uninterruptible(HZ/4);
+                               spin_lock_irqsave(&scsi_nl_lock, flags);
+                       }
+                       list_del(&driver->next);
+                       kfree(driver);
+                       spin_unlock_irqrestore(&scsi_nl_lock, flags);
+                       return;
+               }
+       }
+
+       spin_unlock_irqrestore(&scsi_nl_lock, flags);
+
+       printk(KERN_ERR "%s: removal of driver failed - vendor_id 0x%llx\n",
+              __func__, (unsigned long long)vendor_id);
+       return;
+}
+EXPORT_SYMBOL_GPL(scsi_nl_remove_driver);
+
+
+/**
+ * scsi_netlink_init - Called by SCSI subsystem to intialize
+ *     the SCSI transport netlink interface
  *
  **/
 void
@@ -136,6 +485,8 @@ scsi_netlink_init(void)
 {
        int error;
 
+       INIT_LIST_HEAD(&scsi_nl_drivers);
+
        error = netlink_register_notifier(&scsi_netlink_notifier);
        if (error) {
                printk(KERN_ERR "%s: register of event handler failed - %d\n",
@@ -150,8 +501,15 @@ scsi_netlink_init(void)
                printk(KERN_ERR "%s: register of recieve handler failed\n",
                                __func__);
                netlink_unregister_notifier(&scsi_netlink_notifier);
+               return;
        }
 
+       /* Register the entry points for the generic SCSI transport */
+       error = scsi_nl_add_transport(SCSI_NL_TRANSPORT,
+                               scsi_generic_msg_handler, NULL);
+       if (error)
+               printk(KERN_ERR "%s: register of GENERIC transport handler"
+                               "  failed - %d\n", __func__, error);
        return;
 }
 
@@ -163,6 +521,8 @@ scsi_netlink_init(void)
 void
 scsi_netlink_exit(void)
 {
+       scsi_nl_remove_transport(SCSI_NL_TRANSPORT);
+
        if (scsi_nl_sock) {
                netlink_kernel_release(scsi_nl_sock);
                netlink_unregister_notifier(&scsi_netlink_notifier);
@@ -172,3 +532,147 @@ scsi_netlink_exit(void)
 }
 
 
+/*
+ * Exported Interfaces
+ */
+
+/**
+ * scsi_nl_send_transport_msg -
+ *    Generic function to send a single message from a SCSI transport to
+ *    a single process
+ *
+ * @pid:               receiving pid
+ * @hdr:               message payload
+ *
+ **/
+void
+scsi_nl_send_transport_msg(u32 pid, struct scsi_nl_hdr *hdr)
+{
+       struct sk_buff *skb;
+       struct nlmsghdr *nlh;
+       const char *fn;
+       char *datab;
+       u32 len, skblen;
+       int err;
+
+       if (!scsi_nl_sock) {
+               err = -ENOENT;
+               fn = "netlink socket";
+               goto msg_fail;
+       }
+
+       len = NLMSG_SPACE(hdr->msglen);
+       skblen = NLMSG_SPACE(len);
+
+       skb = alloc_skb(skblen, GFP_KERNEL);
+       if (!skb) {
+               err = -ENOBUFS;
+               fn = "alloc_skb";
+               goto msg_fail;
+       }
+
+       nlh = nlmsg_put(skb, pid, 0, SCSI_TRANSPORT_MSG, len - sizeof(*nlh), 0);
+       if (!nlh) {
+               err = -ENOBUFS;
+               fn = "nlmsg_put";
+               goto msg_fail_skb;
+       }
+       datab = NLMSG_DATA(nlh);
+       memcpy(datab, hdr, hdr->msglen);
+
+       err = nlmsg_unicast(scsi_nl_sock, skb, pid);
+       if (err < 0) {
+               fn = "nlmsg_unicast";
+               /* nlmsg_unicast already kfree_skb'd */
+               goto msg_fail;
+       }
+
+       return;
+
+msg_fail_skb:
+       kfree_skb(skb);
+msg_fail:
+       printk(KERN_WARNING
+               "%s: Dropped Message : pid %d Transport %d, msgtype x%x, "
+               "msglen %d: %s : err %d\n",
+               __func__, pid, hdr->transport, hdr->msgtype, hdr->msglen,
+               fn, err);
+       return;
+}
+EXPORT_SYMBOL_GPL(scsi_nl_send_transport_msg);
+
+
+/**
+ * scsi_nl_send_vendor_msg - called to send a shost vendor unique message
+ *                      to a specific process id.
+ *
+ * @pid:               process id of the receiver
+ * @host_no:           host # sending the message
+ * @vendor_id:         unique identifier for the driver's vendor
+ * @data_len:          amount, in bytes, of vendor unique payload data
+ * @data_buf:          pointer to vendor unique data buffer
+ *
+ * Returns:
+ *   0 on succesful return
+ *   otherwise, failing error code
+ *
+ * Notes:
+ *     This routine assumes no locks are held on entry.
+ */
+int
+scsi_nl_send_vendor_msg(u32 pid, unsigned short host_no, u64 vendor_id,
+                        char *data_buf, u32 data_len)
+{
+       struct sk_buff *skb;
+       struct nlmsghdr *nlh;
+       struct scsi_nl_host_vendor_msg *msg;
+       u32 len, skblen;
+       int err;
+
+       if (!scsi_nl_sock) {
+               err = -ENOENT;
+               goto send_vendor_fail;
+       }
+
+       len = SCSI_NL_MSGALIGN(sizeof(*msg) + 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;
+       }
+       msg = NLMSG_DATA(nlh);
+
+       INIT_SCSI_NL_HDR(&msg->snlh, SCSI_NL_TRANSPORT,
+                               SCSI_NL_SHOST_VENDOR, len);
+       msg->vendor_id = vendor_id;
+       msg->host_no = host_no;
+       msg->vmsg_datalen = data_len;   /* bytes */
+       memcpy(&msg[1], data_buf, data_len);
+
+       err = nlmsg_unicast(scsi_nl_sock, skb, pid);
+       if (err)
+               /* nlmsg_multicast already kfree_skb'd */
+               goto send_vendor_fail;
+
+       return 0;
+
+send_vendor_fail_skb:
+       kfree_skb(skb);
+send_vendor_fail:
+       printk(KERN_WARNING
+               "%s: Dropped SCSI Msg : host %d vendor_unique - err %d\n",
+               __func__, host_no, err);
+       return err;
+}
+EXPORT_SYMBOL(scsi_nl_send_vendor_msg);
+
+