summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/scsi/Kconfig2
-rw-r--r--drivers/scsi/Makefile1
-rw-r--r--drivers/scsi/device_handler/Kconfig12
-rw-r--r--drivers/scsi/device_handler/Makefile4
-rw-r--r--drivers/scsi/device_handler/scsi_dh.c162
-rw-r--r--drivers/scsi/scsi_error.c11
-rw-r--r--drivers/scsi/scsi_lib.c8
-rw-r--r--drivers/scsi/scsi_sysfs.c1
-rw-r--r--include/scsi/scsi.h1
-rw-r--r--include/scsi/scsi_device.h22
-rw-r--r--include/scsi/scsi_dh.h59
11 files changed, 283 insertions, 0 deletions
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
index 81ccbd7f9e34..7886ec6e5f87 100644
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -1771,4 +1771,6 @@ endif # SCSI_LOWLEVEL
source "drivers/scsi/pcmcia/Kconfig"
+source "drivers/scsi/device_handler/Kconfig"
+
endmenu
diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
index 6c775e350c98..aa8272e188ea 100644
--- a/drivers/scsi/Makefile
+++ b/drivers/scsi/Makefile
@@ -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
index 000000000000..228241d1d98c
--- /dev/null
+++ b/drivers/scsi/device_handler/Kconfig
@@ -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
index 000000000000..f306e44e3837
--- /dev/null
+++ b/drivers/scsi/device_handler/Makefile
@@ -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
index 000000000000..ab6c21cd9689
--- /dev/null
+++ b/drivers/scsi/device_handler/scsi_dh.c
@@ -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");
diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
index eaf5a8add1ba..006a95916f72 100644
--- a/drivers/scsi/scsi_error.c
+++ b/drivers/scsi/scsi_error.c
@@ -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.
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index a82d2fe80fb5..033c58a65f50 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -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.
*/
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index 93d2b6714453..b6e561059779 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -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)
{
diff --git a/include/scsi/scsi.h b/include/scsi/scsi.h
index 32742c4563de..2b5b9356c314 100644
--- a/include/scsi/scsi.h
+++ b/include/scsi/scsi.h
@@ -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.
diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h
index f6a9fe0ef09c..06b979f105b7 100644
--- a/include/scsi/scsi_device.h
+++ b/include/scsi/scsi_device.h
@@ -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
index 000000000000..04d0d8495c83
--- /dev/null
+++ b/include/scsi/scsi_dh.h
@@ -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 *);