[SCSI] scsi_dh: add infrastructure for SCSI Device Handlers
authorChandra Seetharaman <sekharan@us.ibm.com>
Thu, 1 May 2008 21:49:46 +0000 (14:49 -0700)
committerJames Bottomley <James.Bottomley@HansenPartnership.com>
Thu, 5 Jun 2008 14:23:40 +0000 (09:23 -0500)
Some of the storage devices (that can be accessed through multiple paths),
do need some special handling for
1. Activating the passive path of the storage access.
2. Decode and handle the special sense codes returned by the devices.
3. Handle the I/Os being sent to the passive path, especially
           during the device probe time.
when accessed through multiple paths.

As of today this special device handling is done at the dm-multipath
layer using dm-handlers. That works well for (1); for (2) to be handled
at dm layer, scsi sense information need to be exported from SCSI to dm-layer,
which is not very attractive; (3) cannot be done at all at the dm layer.

Device handler has been moved to SCSI mainly to handle (2) and (3) properly.

Signed-off-by: Chandra Seetharaman <sekharan@us.ibm.com>
Signed-off-by: Mike Anderson <andmike@linux.vnet.ibm.com>
Signed-off-by: Mike Christie <michaelc@cs.wisc.edu>
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
drivers/scsi/Kconfig
drivers/scsi/Makefile
drivers/scsi/device_handler/Kconfig [new file with mode: 0644]
drivers/scsi/device_handler/Makefile [new file with mode: 0644]
drivers/scsi/device_handler/scsi_dh.c [new file with mode: 0644]
drivers/scsi/scsi_error.c
drivers/scsi/scsi_lib.c
drivers/scsi/scsi_sysfs.c
include/scsi/scsi.h
include/scsi/scsi_device.h
include/scsi/scsi_dh.h [new file with mode: 0644]

index 81ccbd7f9e34d4d4405ea3982952a608b09f8aa9..7886ec6e5f873425c5bb98567fea1769ddfd7108 100644 (file)
@@ -1771,4 +1771,6 @@ endif # SCSI_LOWLEVEL
 
 source "drivers/scsi/pcmcia/Kconfig"
 
+source "drivers/scsi/device_handler/Kconfig"
+
 endmenu
index 6c775e350c9842718283cbd9a6340c139e3e4914..aa8272e188eab0613bd0c4e07a0736c967a012e0 100644 (file)
@@ -34,6 +34,7 @@ obj-$(CONFIG_SCSI_ISCSI_ATTRS)        += scsi_transport_iscsi.o
 obj-$(CONFIG_SCSI_SAS_ATTRS)   += scsi_transport_sas.o
 obj-$(CONFIG_SCSI_SAS_LIBSAS)  += libsas/
 obj-$(CONFIG_SCSI_SRP_ATTRS)   += scsi_transport_srp.o
+obj-$(CONFIG_SCSI_DH)          += device_handler/
 
 obj-$(CONFIG_ISCSI_TCP)        += libiscsi.o   iscsi_tcp.o
 obj-$(CONFIG_INFINIBAND_ISER)  += libiscsi.o
diff --git a/drivers/scsi/device_handler/Kconfig b/drivers/scsi/device_handler/Kconfig
new file mode 100644 (file)
index 0000000..228241d
--- /dev/null
@@ -0,0 +1,12 @@
+#
+# SCSI Device Handler configuration
+#
+
+menuconfig SCSI_DH
+       tristate "SCSI Device Handlers"
+       depends on SCSI
+       default n
+       help
+         SCSI Device Handlers provide device specific support for
+         devices utilized in multipath configurations. Say Y here to
+         select support for specific hardware.
diff --git a/drivers/scsi/device_handler/Makefile b/drivers/scsi/device_handler/Makefile
new file mode 100644 (file)
index 0000000..f306e44
--- /dev/null
@@ -0,0 +1,4 @@
+#
+# SCSI Device Handler
+#
+obj-$(CONFIG_SCSI_DH)          += scsi_dh.o
diff --git a/drivers/scsi/device_handler/scsi_dh.c b/drivers/scsi/device_handler/scsi_dh.c
new file mode 100644 (file)
index 0000000..ab6c21c
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * SCSI device handler infrastruture.
+ *
+ * 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.
+ *
+ * Copyright IBM Corporation, 2007
+ *      Authors:
+ *               Chandra Seetharaman <sekharan@us.ibm.com>
+ *               Mike Anderson <andmike@linux.vnet.ibm.com>
+ */
+
+#include <scsi/scsi_dh.h>
+#include "../scsi_priv.h"
+
+static DEFINE_SPINLOCK(list_lock);
+static LIST_HEAD(scsi_dh_list);
+
+static struct scsi_device_handler *get_device_handler(const char *name)
+{
+       struct scsi_device_handler *tmp, *found = NULL;
+
+       spin_lock(&list_lock);
+       list_for_each_entry(tmp, &scsi_dh_list, list) {
+               if (!strcmp(tmp->name, name)) {
+                       found = tmp;
+                       break;
+               }
+       }
+       spin_unlock(&list_lock);
+       return found;
+}
+
+static int scsi_dh_notifier_add(struct device *dev, void *data)
+{
+       struct scsi_device_handler *scsi_dh = data;
+
+       scsi_dh->nb.notifier_call(&scsi_dh->nb, BUS_NOTIFY_ADD_DEVICE, dev);
+       return 0;
+}
+
+/*
+ * scsi_register_device_handler - register a device handler personality
+ *      module.
+ * @scsi_dh - device handler to be registered.
+ *
+ * Returns 0 on success, -EBUSY if handler already registered.
+ */
+int scsi_register_device_handler(struct scsi_device_handler *scsi_dh)
+{
+       int ret = -EBUSY;
+       struct scsi_device_handler *tmp;
+
+       tmp = get_device_handler(scsi_dh->name);
+       if (tmp)
+               goto done;
+
+       ret = bus_register_notifier(&scsi_bus_type, &scsi_dh->nb);
+
+       bus_for_each_dev(&scsi_bus_type, NULL, scsi_dh, scsi_dh_notifier_add);
+       spin_lock(&list_lock);
+       list_add(&scsi_dh->list, &scsi_dh_list);
+       spin_unlock(&list_lock);
+
+done:
+       return ret;
+}
+EXPORT_SYMBOL_GPL(scsi_register_device_handler);
+
+static int scsi_dh_notifier_remove(struct device *dev, void *data)
+{
+       struct scsi_device_handler *scsi_dh = data;
+
+       scsi_dh->nb.notifier_call(&scsi_dh->nb, BUS_NOTIFY_DEL_DEVICE, dev);
+       return 0;
+}
+
+/*
+ * scsi_unregister_device_handler - register a device handler personality
+ *      module.
+ * @scsi_dh - device handler to be unregistered.
+ *
+ * Returns 0 on success, -ENODEV if handler not registered.
+ */
+int scsi_unregister_device_handler(struct scsi_device_handler *scsi_dh)
+{
+       int ret = -ENODEV;
+       struct scsi_device_handler *tmp;
+
+       tmp = get_device_handler(scsi_dh->name);
+       if (!tmp)
+               goto done;
+
+       ret = bus_unregister_notifier(&scsi_bus_type, &scsi_dh->nb);
+
+       bus_for_each_dev(&scsi_bus_type, NULL, scsi_dh,
+                                       scsi_dh_notifier_remove);
+       spin_lock(&list_lock);
+       list_del(&scsi_dh->list);
+       spin_unlock(&list_lock);
+
+done:
+       return ret;
+}
+EXPORT_SYMBOL_GPL(scsi_unregister_device_handler);
+
+/*
+ * scsi_dh_activate - activate the path associated with the scsi_device
+ *      corresponding to the given request queue.
+ * @q - Request queue that is associated with the scsi_device to be
+ *      activated.
+ */
+int scsi_dh_activate(struct request_queue *q)
+{
+       int err = 0;
+       unsigned long flags;
+       struct scsi_device *sdev;
+       struct scsi_device_handler *scsi_dh = NULL;
+
+       spin_lock_irqsave(q->queue_lock, flags);
+       sdev = q->queuedata;
+       if (sdev && sdev->scsi_dh_data)
+               scsi_dh = sdev->scsi_dh_data->scsi_dh;
+       if (!scsi_dh || !get_device(&sdev->sdev_gendev))
+               err = SCSI_DH_NOSYS;
+       spin_unlock_irqrestore(q->queue_lock, flags);
+
+       if (err)
+               return err;
+
+       if (scsi_dh->activate)
+               err = scsi_dh->activate(sdev);
+       put_device(&sdev->sdev_gendev);
+       return err;
+}
+EXPORT_SYMBOL_GPL(scsi_dh_activate);
+
+/*
+ * scsi_dh_handler_exist - Return TRUE(1) if a device handler exists for
+ *     the given name. FALSE(0) otherwise.
+ * @name - name of the device handler.
+ */
+int scsi_dh_handler_exist(const char *name)
+{
+       return (get_device_handler(name) != NULL);
+}
+EXPORT_SYMBOL_GPL(scsi_dh_handler_exist);
+
+MODULE_DESCRIPTION("SCSI device handler");
+MODULE_AUTHOR("Chandra Seetharaman <sekharan@us.ibm.com>");
+MODULE_LICENSE("GPL");
index eaf5a8add1ba1a9ba9e03d8aaa0969ffd41d2c58..006a95916f72ae4aff8cae52db9122a9d5e4bf9c 100644 (file)
@@ -298,6 +298,7 @@ static inline void scsi_eh_prt_fail_stats(struct Scsi_Host *shost,
  */
 static int scsi_check_sense(struct scsi_cmnd *scmd)
 {
+       struct scsi_device *sdev = scmd->device;
        struct scsi_sense_hdr sshdr;
 
        if (! scsi_command_normalize_sense(scmd, &sshdr))
@@ -306,6 +307,16 @@ static int scsi_check_sense(struct scsi_cmnd *scmd)
        if (scsi_sense_is_deferred(&sshdr))
                return NEEDS_RETRY;
 
+       if (sdev->scsi_dh_data && sdev->scsi_dh_data->scsi_dh &&
+                       sdev->scsi_dh_data->scsi_dh->check_sense) {
+               int rc;
+
+               rc = sdev->scsi_dh_data->scsi_dh->check_sense(sdev, &sshdr);
+               if (rc != SCSI_RETURN_NOT_HANDLED)
+                       return rc;
+               /* handler does not care. Drop down to default handling */
+       }
+
        /*
         * Previous logic looked for FILEMARK, EOM or ILI which are
         * mainly associated with tapes and returned SUCCESS.
index a82d2fe80fb544da5afb86d835e6fbafb3a74533..033c58a65f50d6c4b07ce94278178b316a9ac7f0 100644 (file)
@@ -1160,6 +1160,14 @@ int scsi_setup_fs_cmnd(struct scsi_device *sdev, struct request *req)
 
        if (ret != BLKPREP_OK)
                return ret;
+
+       if (unlikely(sdev->scsi_dh_data && sdev->scsi_dh_data->scsi_dh
+                        && sdev->scsi_dh_data->scsi_dh->prep_fn)) {
+               ret = sdev->scsi_dh_data->scsi_dh->prep_fn(sdev, req);
+               if (ret != BLKPREP_OK)
+                       return ret;
+       }
+
        /*
         * Filesystem requests must transfer data.
         */
index 93d2b6714453e6ae7e11f1cd2e3b864182f7d66d..b6e5610597794c553a011231f9a097785e529bf9 100644 (file)
@@ -439,6 +439,7 @@ struct bus_type scsi_bus_type = {
        .resume         = scsi_bus_resume,
        .remove         = scsi_bus_remove,
 };
+EXPORT_SYMBOL_GPL(scsi_bus_type);
 
 int scsi_sysfs_register(void)
 {
index 32742c4563de22131a6abce886ff2d8ff5629724..2b5b9356c314eee75f84b6c9cf00c2431dfc4717 100644 (file)
@@ -400,6 +400,7 @@ struct scsi_lun {
 #define SOFT_ERROR      0x2005
 #define ADD_TO_MLQUEUE  0x2006
 #define TIMEOUT_ERROR   0x2007
+#define SCSI_RETURN_NOT_HANDLED   0x2008
 
 /*
  * Midlevel queue return values.
index f6a9fe0ef09c62cf38b28f6cdd9bfd9bad06121a..06b979f105b790e7988ed99b54583ef575db32ed 100644 (file)
@@ -161,9 +161,29 @@ struct scsi_device {
 
        struct execute_work     ew; /* used to get process context on put */
 
+       struct scsi_dh_data     *scsi_dh_data;
        enum scsi_device_state sdev_state;
        unsigned long           sdev_data[0];
 } __attribute__((aligned(sizeof(unsigned long))));
+
+struct scsi_device_handler {
+       /* Used by the infrastructure */
+       struct list_head list; /* list of scsi_device_handlers */
+       struct notifier_block nb;
+
+       /* Filled by the hardware handler */
+       struct module *module;
+       const char *name;
+       int (*check_sense)(struct scsi_device *, struct scsi_sense_hdr *);
+       int (*activate)(struct scsi_device *);
+       int (*prep_fn)(struct scsi_device *, struct request *);
+};
+
+struct scsi_dh_data {
+       struct scsi_device_handler *scsi_dh;
+       char buf[0];
+};
+
 #define        to_scsi_device(d)       \
        container_of(d, struct scsi_device, sdev_gendev)
 #define        class_to_sdev(d)        \
@@ -230,7 +250,9 @@ extern struct scsi_device *__scsi_add_device(struct Scsi_Host *,
                uint, uint, uint, void *hostdata);
 extern int scsi_add_device(struct Scsi_Host *host, uint channel,
                           uint target, uint lun);
+extern int scsi_register_device_handler(struct scsi_device_handler *scsi_dh);
 extern void scsi_remove_device(struct scsi_device *);
+extern int scsi_unregister_device_handler(struct scsi_device_handler *scsi_dh);
 
 extern int scsi_device_get(struct scsi_device *);
 extern void scsi_device_put(struct scsi_device *);
diff --git a/include/scsi/scsi_dh.h b/include/scsi/scsi_dh.h
new file mode 100644 (file)
index 0000000..04d0d84
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Header file for SCSI device handler infrastruture.
+ *
+ * Modified version of patches posted by Mike Christie <michaelc@cs.wisc.edu>
+ *
+ * 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.
+ *
+ * Copyright IBM Corporation, 2007
+ *      Authors:
+ *               Chandra Seetharaman <sekharan@us.ibm.com>
+ *               Mike Anderson <andmike@linux.vnet.ibm.com>
+ */
+
+#include <scsi/scsi_device.h>
+
+enum {
+       SCSI_DH_OK = 0,
+       /*
+        * device errors
+        */
+       SCSI_DH_DEV_FAILED,     /* generic device error */
+       SCSI_DH_DEV_TEMP_BUSY,
+       SCSI_DH_DEVICE_MAX,     /* max device blkerr definition */
+
+       /*
+        * transport errors
+        */
+       SCSI_DH_NOTCONN = SCSI_DH_DEVICE_MAX + 1,
+       SCSI_DH_CONN_FAILURE,
+       SCSI_DH_TRANSPORT_MAX,  /* max transport blkerr definition */
+
+       /*
+        * driver and generic errors
+        */
+       SCSI_DH_IO = SCSI_DH_TRANSPORT_MAX + 1, /* generic error */
+       SCSI_DH_INVALID_IO,
+       SCSI_DH_RETRY,          /* retry the req, but not immediately */
+       SCSI_DH_IMM_RETRY,      /* immediately retry the req */
+       SCSI_DH_TIMED_OUT,
+       SCSI_DH_RES_TEMP_UNAVAIL,
+       SCSI_DH_DEV_OFFLINED,
+       SCSI_DH_NOSYS,
+       SCSI_DH_DRIVER_MAX,
+};
+
+extern int scsi_dh_activate(struct request_queue *);
+extern int scsi_dh_handler_exist(const char *);