Skip to content

Commit

Permalink
[SCSI] scsi_dh: add infrastructure for SCSI Device Handlers
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Mike Anderson <[email protected]>
Signed-off-by: Mike Christie <[email protected]>
Signed-off-by: James Bottomley <[email protected]>
  • Loading branch information
chandra2 authored and James Bottomley committed Jun 5, 2008
1 parent 53c8ba9 commit a6a8d9f
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 0 deletions.
2 changes: 2 additions & 0 deletions drivers/scsi/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -1771,4 +1771,6 @@ endif # SCSI_LOWLEVEL

source "drivers/scsi/pcmcia/Kconfig"

source "drivers/scsi/device_handler/Kconfig"

endmenu
1 change: 1 addition & 0 deletions drivers/scsi/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions drivers/scsi/device_handler/Kconfig
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions drivers/scsi/device_handler/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#
# SCSI Device Handler
#
obj-$(CONFIG_SCSI_DH) += scsi_dh.o
162 changes: 162 additions & 0 deletions drivers/scsi/device_handler/scsi_dh.c
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
* Mike Anderson <[email protected]>
*/

#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 <[email protected]>");
MODULE_LICENSE("GPL");
11 changes: 11 additions & 0 deletions drivers/scsi/scsi_error.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions drivers/scsi/scsi_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
1 change: 1 addition & 0 deletions drivers/scsi/scsi_sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
1 change: 1 addition & 0 deletions include/scsi/scsi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
22 changes: 22 additions & 0 deletions include/scsi/scsi_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down Expand Up @@ -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 *);
Expand Down
59 changes: 59 additions & 0 deletions include/scsi/scsi_dh.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Header file for SCSI device handler infrastruture.
*
* Modified version of patches posted by Mike Christie <[email protected]>
*
* 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 <[email protected]>
* Mike Anderson <[email protected]>
*/

#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 *);

0 comments on commit a6a8d9f

Please sign in to comment.