diff options
Diffstat (limited to 'drivers/md/dm-emc.c')
-rw-r--r-- | drivers/md/dm-emc.c | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/drivers/md/dm-emc.c b/drivers/md/dm-emc.c new file mode 100644 index 0000000..7006586 --- /dev/null +++ b/drivers/md/dm-emc.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2004 SUSE LINUX Products GmbH. All rights reserved. + * Copyright (C) 2004 Red Hat, Inc. All rights reserved. + * + * This file is released under the GPL. + * + * Multipath support for EMC CLARiiON AX/CX-series hardware. + */ + +#include "dm.h" +#include "dm-hw-handler.h" +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> + +struct emc_handler { + spinlock_t lock; + + /* Whether we should send the short trespass command (FC-series) + * or the long version (default for AX/CX CLARiiON arrays). */ + unsigned short_trespass; + /* Whether or not to honor SCSI reservations when initiating a + * switch-over. Default: Don't. */ + unsigned hr; + + unsigned char sense[SCSI_SENSE_BUFFERSIZE]; +}; + +#define TRESPASS_PAGE 0x22 +#define EMC_FAILOVER_TIMEOUT (60 * HZ) + +/* Code borrowed from dm-lsi-rdac by Mike Christie */ + +static inline void free_bio(struct bio *bio) +{ + __free_page(bio->bi_io_vec[0].bv_page); + bio_put(bio); +} + +static int emc_endio(struct bio *bio, unsigned int bytes_done, int error) +{ + struct path *path = bio->bi_private; + + if (bio->bi_size) + return 1; + + /* We also need to look at the sense keys here whether or not to + * switch to the next PG etc. + * + * For now simple logic: either it works or it doesn't. + */ + if (error) + dm_pg_init_complete(path, MP_FAIL_PATH); + else + dm_pg_init_complete(path, 0); + + /* request is freed in block layer */ + free_bio(bio); + + return 0; +} + +static struct bio *get_failover_bio(struct path *path, unsigned data_size) +{ + struct bio *bio; + struct page *page; + + bio = bio_alloc(GFP_ATOMIC, 1); + if (!bio) { + DMERR("dm-emc: get_failover_bio: bio_alloc() failed."); + return NULL; + } + + bio->bi_rw |= (1 << BIO_RW); + bio->bi_bdev = path->dev->bdev; + bio->bi_sector = 0; + bio->bi_private = path; + bio->bi_end_io = emc_endio; + + page = alloc_page(GFP_ATOMIC); + if (!page) { + DMERR("dm-emc: get_failover_bio: alloc_page() failed."); + bio_put(bio); + return NULL; + } + + if (bio_add_page(bio, page, data_size, 0) != data_size) { + DMERR("dm-emc: get_failover_bio: alloc_page() failed."); + __free_page(page); + bio_put(bio); + return NULL; + } + + return bio; +} + +static struct request *get_failover_req(struct emc_handler *h, + struct bio *bio, struct path *path) +{ + struct request *rq; + struct block_device *bdev = bio->bi_bdev; + struct request_queue *q = bdev_get_queue(bdev); + + /* FIXME: Figure out why it fails with GFP_ATOMIC. */ + rq = blk_get_request(q, WRITE, __GFP_WAIT); + if (!rq) { + DMERR("dm-emc: get_failover_req: blk_get_request failed"); + return NULL; + } + + rq->bio = rq->biotail = bio; + blk_rq_bio_prep(q, rq, bio); + + rq->rq_disk = bdev->bd_contains->bd_disk; + + /* bio backed don't set data */ + rq->buffer = rq->data = NULL; + /* rq data_len used for pc cmd's request_bufflen */ + rq->data_len = bio->bi_size; + + rq->sense = h->sense; + memset(rq->sense, 0, SCSI_SENSE_BUFFERSIZE); + rq->sense_len = 0; + + memset(&rq->cmd, 0, BLK_MAX_CDB); + + rq->timeout = EMC_FAILOVER_TIMEOUT; + rq->flags |= (REQ_BLOCK_PC | REQ_FAILFAST | REQ_NOMERGE); + + return rq; +} + +static struct request *emc_trespass_get(struct emc_handler *h, + struct path *path) +{ + struct bio *bio; + struct request *rq; + unsigned char *page22; + unsigned char long_trespass_pg[] = { + 0, 0, 0, 0, + TRESPASS_PAGE, /* Page code */ + 0x09, /* Page length - 2 */ + h->hr ? 0x01 : 0x81, /* Trespass code + Honor reservation bit */ + 0xff, 0xff, /* Trespass target */ + 0, 0, 0, 0, 0, 0 /* Reserved bytes / unknown */ + }; + unsigned char short_trespass_pg[] = { + 0, 0, 0, 0, + TRESPASS_PAGE, /* Page code */ + 0x02, /* Page length - 2 */ + h->hr ? 0x01 : 0x81, /* Trespass code + Honor reservation bit */ + 0xff, /* Trespass target */ + }; + unsigned data_size = h->short_trespass ? sizeof(short_trespass_pg) : + sizeof(long_trespass_pg); + + /* get bio backing */ + if (data_size > PAGE_SIZE) + /* this should never happen */ + return NULL; + + bio = get_failover_bio(path, data_size); + if (!bio) { + DMERR("dm-emc: emc_trespass_get: no bio"); + return NULL; + } + + page22 = (unsigned char *)bio_data(bio); + memset(page22, 0, data_size); + + memcpy(page22, h->short_trespass ? + short_trespass_pg : long_trespass_pg, data_size); + + /* get request for block layer packet command */ + rq = get_failover_req(h, bio, path); + if (!rq) { + DMERR("dm-emc: emc_trespass_get: no rq"); + free_bio(bio); + return NULL; + } + + /* Prepare the command. */ + rq->cmd[0] = MODE_SELECT; + rq->cmd[1] = 0x10; + rq->cmd[4] = data_size; + rq->cmd_len = COMMAND_SIZE(rq->cmd[0]); + + return rq; +} + +static void emc_pg_init(struct hw_handler *hwh, unsigned bypassed, + struct path *path) +{ + struct request *rq; + struct request_queue *q = bdev_get_queue(path->dev->bdev); + + /* + * We can either blindly init the pg (then look at the sense), + * or we can send some commands to get the state here (then + * possibly send the fo cmnd), or we can also have the + * initial state passed into us and then get an update here. + */ + if (!q) { + DMINFO("dm-emc: emc_pg_init: no queue"); + goto fail_path; + } + + /* FIXME: The request should be pre-allocated. */ + rq = emc_trespass_get(hwh->context, path); + if (!rq) { + DMERR("dm-emc: emc_pg_init: no rq"); + goto fail_path; + } + + DMINFO("dm-emc: emc_pg_init: sending switch-over command"); + elv_add_request(q, rq, ELEVATOR_INSERT_FRONT, 1); + return; + +fail_path: + dm_pg_init_complete(path, MP_FAIL_PATH); +} + +static struct emc_handler *alloc_emc_handler(void) +{ + struct emc_handler *h = kmalloc(sizeof(*h), GFP_KERNEL); + + if (h) + spin_lock_init(&h->lock); + + return h; +} + +static int emc_create(struct hw_handler *hwh, unsigned argc, char **argv) +{ + struct emc_handler *h; + unsigned hr, short_trespass; + + if (argc == 0) { + /* No arguments: use defaults */ + hr = 0; + short_trespass = 0; + } else if (argc != 2) { + DMWARN("dm-emc hwhandler: incorrect number of arguments"); + return -EINVAL; + } else { + if ((sscanf(argv[0], "%u", &short_trespass) != 1) + || (short_trespass > 1)) { + DMWARN("dm-emc: invalid trespass mode selected"); + return -EINVAL; + } + + if ((sscanf(argv[1], "%u", &hr) != 1) + || (hr > 1)) { + DMWARN("dm-emc: invalid honor reservation flag selected"); + return -EINVAL; + } + } + + h = alloc_emc_handler(); + if (!h) + return -ENOMEM; + + memset(h, 0, sizeof(*h)); + + hwh->context = h; + + if ((h->short_trespass = short_trespass)) + DMWARN("dm-emc: short trespass command will be send"); + else + DMWARN("dm-emc: long trespass command will be send"); + + if ((h->hr = hr)) + DMWARN("dm-emc: honor reservation bit will be set"); + else + DMWARN("dm-emc: honor reservation bit will not be set (default)"); + + return 0; +} + +static void emc_destroy(struct hw_handler *hwh) +{ + struct emc_handler *h = (struct emc_handler *) hwh->context; + + kfree(h); + hwh->context = NULL; +} + +static unsigned emc_error(struct hw_handler *hwh, struct bio *bio) +{ + /* FIXME: Patch from axboe still missing */ +#if 0 + int sense; + + if (bio->bi_error & BIO_SENSE) { + sense = bio->bi_error & 0xffffff; /* sense key / asc / ascq */ + + if (sense == 0x020403) { + /* LUN Not Ready - Manual Intervention Required + * indicates this is a passive path. + * + * FIXME: However, if this is seen and EVPD C0 + * indicates that this is due to a NDU in + * progress, we should set FAIL_PATH too. + * This indicates we might have to do a SCSI + * inquiry in the end_io path. Ugh. */ + return MP_BYPASS_PG | MP_RETRY_IO; + } else if (sense == 0x052501) { + /* An array based copy is in progress. Do not + * fail the path, do not bypass to another PG, + * do not retry. Fail the IO immediately. + * (Actually this is the same conclusion as in + * the default handler, but lets make sure.) */ + return 0; + } else if (sense == 0x062900) { + /* Unit Attention Code. This is the first IO + * to the new path, so just retry. */ + return MP_RETRY_IO; + } + } +#endif + + /* Try default handler */ + return dm_scsi_err_handler(hwh, bio); +} + +static struct hw_handler_type emc_hwh = { + .name = "emc", + .module = THIS_MODULE, + .create = emc_create, + .destroy = emc_destroy, + .pg_init = emc_pg_init, + .error = emc_error, +}; + +static int __init dm_emc_init(void) +{ + int r = dm_register_hw_handler(&emc_hwh); + + if (r < 0) + DMERR("emc: register failed %d", r); + + DMINFO("dm-emc version 0.0.3 loaded"); + + return r; +} + +static void __exit dm_emc_exit(void) +{ + int r = dm_unregister_hw_handler(&emc_hwh); + + if (r < 0) + DMERR("emc: unregister failed %d", r); +} + +module_init(dm_emc_init); +module_exit(dm_emc_exit); + +MODULE_DESCRIPTION(DM_NAME " EMC CX/AX/FC-family multipath"); +MODULE_AUTHOR("Lars Marowsky-Bree <lmb@suse.de>"); +MODULE_LICENSE("GPL"); |