Commit 5e7dccad3621f6e2b572f309cf830a2c902cae80

Authored by Chandra Seetharaman
Committed by James Bottomley
1 parent f6dd337ee4

[SCSI] scsi_dh: add EMC Clariion device handler

This adds support for EMC Clariions. This patch has the features that
currently exists in mainline and advanced features from Ed's patches.

Signed-off-by: Chandra Seetharaman <sekharan@us.ibm.com>
Signed-off-by: Mike Christie <michaelc@cs.wisc.edu>
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>

Showing 3 changed files with 506 additions and 0 deletions Side-by-side Diff

drivers/scsi/device_handler/Kconfig
... ... @@ -24,4 +24,10 @@
24 24 If you have a HP/COMPAQ MSA device that requires START_STOP to
25 25 be sent to start it and cannot upgrade the firmware then select y.
26 26 Otherwise, say N.
  27 +
  28 +config SCSI_DH_EMC
  29 + tristate "EMC CLARiiON Device Handler"
  30 + depends on SCSI_DH
  31 + help
  32 + If you have a EMC CLARiiON select y. Otherwise, say N.
drivers/scsi/device_handler/Makefile
... ... @@ -4,4 +4,5 @@
4 4 obj-$(CONFIG_SCSI_DH) += scsi_dh.o
5 5 obj-$(CONFIG_SCSI_DH_RDAC) += scsi_dh_rdac.o
6 6 obj-$(CONFIG_SCSI_DH_HP_SW) += scsi_dh_hp_sw.o
  7 +obj-$(CONFIG_SCSI_DH_EMC) += scsi_dh_emc.o
drivers/scsi/device_handler/scsi_dh_emc.c
  1 +/*
  2 + * Target driver for EMC CLARiiON AX/CX-series hardware.
  3 + * Based on code from Lars Marowsky-Bree <lmb@suse.de>
  4 + * and Ed Goggin <egoggin@emc.com>.
  5 + *
  6 + * Copyright (C) 2006 Red Hat, Inc. All rights reserved.
  7 + * Copyright (C) 2006 Mike Christie
  8 + *
  9 + * This program is free software; you can redistribute it and/or modify
  10 + * it under the terms of the GNU General Public License as published by
  11 + * the Free Software Foundation; either version 2, or (at your option)
  12 + * any later version.
  13 + *
  14 + * This program is distributed in the hope that it will be useful,
  15 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17 + * GNU General Public License for more details.
  18 + *
  19 + * You should have received a copy of the GNU General Public License
  20 + * along with this program; see the file COPYING. If not, write to
  21 + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  22 + */
  23 +#include <scsi/scsi.h>
  24 +#include <scsi/scsi_eh.h>
  25 +#include <scsi/scsi_dh.h>
  26 +#include <scsi/scsi_device.h>
  27 +
  28 +#define CLARIION_NAME "emc_clariion"
  29 +
  30 +#define CLARIION_TRESPASS_PAGE 0x22
  31 +#define CLARIION_BUFFER_SIZE 0x80
  32 +#define CLARIION_TIMEOUT (60 * HZ)
  33 +#define CLARIION_RETRIES 3
  34 +#define CLARIION_UNBOUND_LU -1
  35 +
  36 +static unsigned char long_trespass[] = {
  37 + 0, 0, 0, 0,
  38 + CLARIION_TRESPASS_PAGE, /* Page code */
  39 + 0x09, /* Page length - 2 */
  40 + 0x81, /* Trespass code + Honor reservation bit */
  41 + 0xff, 0xff, /* Trespass target */
  42 + 0, 0, 0, 0, 0, 0 /* Reserved bytes / unknown */
  43 +};
  44 +
  45 +static unsigned char long_trespass_hr[] = {
  46 + 0, 0, 0, 0,
  47 + CLARIION_TRESPASS_PAGE, /* Page code */
  48 + 0x09, /* Page length - 2 */
  49 + 0x01, /* Trespass code + Honor reservation bit */
  50 + 0xff, 0xff, /* Trespass target */
  51 + 0, 0, 0, 0, 0, 0 /* Reserved bytes / unknown */
  52 +};
  53 +
  54 +static unsigned char short_trespass[] = {
  55 + 0, 0, 0, 0,
  56 + CLARIION_TRESPASS_PAGE, /* Page code */
  57 + 0x02, /* Page length - 2 */
  58 + 0x81, /* Trespass code + Honor reservation bit */
  59 + 0xff, /* Trespass target */
  60 +};
  61 +
  62 +static unsigned char short_trespass_hr[] = {
  63 + 0, 0, 0, 0,
  64 + CLARIION_TRESPASS_PAGE, /* Page code */
  65 + 0x02, /* Page length - 2 */
  66 + 0x01, /* Trespass code + Honor reservation bit */
  67 + 0xff, /* Trespass target */
  68 +};
  69 +
  70 +struct clariion_dh_data {
  71 + /*
  72 + * Use short trespass command (FC-series) or the long version
  73 + * (default for AX/CX CLARiiON arrays).
  74 + */
  75 + unsigned short_trespass;
  76 + /*
  77 + * Whether or not (default) to honor SCSI reservations when
  78 + * initiating a switch-over.
  79 + */
  80 + unsigned hr;
  81 + /* I/O buffer for both MODE_SELECT and INQUIRY commands. */
  82 + char buffer[CLARIION_BUFFER_SIZE];
  83 + /*
  84 + * SCSI sense buffer for commands -- assumes serial issuance
  85 + * and completion sequence of all commands for same multipath.
  86 + */
  87 + unsigned char sense[SCSI_SENSE_BUFFERSIZE];
  88 + /* which SP (A=0,B=1,UNBOUND=-1) is dflt SP for path's mapped dev */
  89 + int default_sp;
  90 + /* which SP (A=0,B=1,UNBOUND=-1) is active for path's mapped dev */
  91 + int current_sp;
  92 +};
  93 +
  94 +static inline struct clariion_dh_data
  95 + *get_clariion_data(struct scsi_device *sdev)
  96 +{
  97 + struct scsi_dh_data *scsi_dh_data = sdev->scsi_dh_data;
  98 + BUG_ON(scsi_dh_data == NULL);
  99 + return ((struct clariion_dh_data *) scsi_dh_data->buf);
  100 +}
  101 +
  102 +/*
  103 + * Parse MODE_SELECT cmd reply.
  104 + */
  105 +static int trespass_endio(struct scsi_device *sdev, int result)
  106 +{
  107 + int err = SCSI_DH_OK;
  108 + struct scsi_sense_hdr sshdr;
  109 + struct clariion_dh_data *csdev = get_clariion_data(sdev);
  110 + char *sense = csdev->sense;
  111 +
  112 + if (status_byte(result) == CHECK_CONDITION &&
  113 + scsi_normalize_sense(sense, SCSI_SENSE_BUFFERSIZE, &sshdr)) {
  114 + sdev_printk(KERN_ERR, sdev, "Found valid sense data 0x%2x, "
  115 + "0x%2x, 0x%2x while sending CLARiiON trespass "
  116 + "command.\n", sshdr.sense_key, sshdr.asc,
  117 + sshdr.ascq);
  118 +
  119 + if ((sshdr.sense_key == 0x05) && (sshdr.asc == 0x04) &&
  120 + (sshdr.ascq == 0x00)) {
  121 + /*
  122 + * Array based copy in progress -- do not send
  123 + * mode_select or copy will be aborted mid-stream.
  124 + */
  125 + sdev_printk(KERN_INFO, sdev, "Array Based Copy in "
  126 + "progress while sending CLARiiON trespass "
  127 + "command.\n");
  128 + err = SCSI_DH_DEV_TEMP_BUSY;
  129 + } else if ((sshdr.sense_key == 0x02) && (sshdr.asc == 0x04) &&
  130 + (sshdr.ascq == 0x03)) {
  131 + /*
  132 + * LUN Not Ready - Manual Intervention Required
  133 + * indicates in-progress ucode upgrade (NDU).
  134 + */
  135 + sdev_printk(KERN_INFO, sdev, "Detected in-progress "
  136 + "ucode upgrade NDU operation while sending "
  137 + "CLARiiON trespass command.\n");
  138 + err = SCSI_DH_DEV_TEMP_BUSY;
  139 + } else
  140 + err = SCSI_DH_DEV_FAILED;
  141 + } else if (result) {
  142 + sdev_printk(KERN_ERR, sdev, "Error 0x%x while sending "
  143 + "CLARiiON trespass command.\n", result);
  144 + err = SCSI_DH_IO;
  145 + }
  146 +
  147 + return err;
  148 +}
  149 +
  150 +static int parse_sp_info_reply(struct scsi_device *sdev, int result,
  151 + int *default_sp, int *current_sp, int *new_current_sp)
  152 +{
  153 + int err = SCSI_DH_OK;
  154 + struct clariion_dh_data *csdev = get_clariion_data(sdev);
  155 +
  156 + if (result == 0) {
  157 + /* check for in-progress ucode upgrade (NDU) */
  158 + if (csdev->buffer[48] != 0) {
  159 + sdev_printk(KERN_NOTICE, sdev, "Detected in-progress "
  160 + "ucode upgrade NDU operation while finding "
  161 + "current active SP.");
  162 + err = SCSI_DH_DEV_TEMP_BUSY;
  163 + } else {
  164 + *default_sp = csdev->buffer[5];
  165 +
  166 + if (csdev->buffer[4] == 2)
  167 + /* SP for path is current */
  168 + *current_sp = csdev->buffer[8];
  169 + else {
  170 + if (csdev->buffer[4] == 1)
  171 + /* SP for this path is NOT current */
  172 + if (csdev->buffer[8] == 0)
  173 + *current_sp = 1;
  174 + else
  175 + *current_sp = 0;
  176 + else
  177 + /* unbound LU or LUNZ */
  178 + *current_sp = CLARIION_UNBOUND_LU;
  179 + }
  180 + *new_current_sp = csdev->buffer[8];
  181 + }
  182 + } else {
  183 + struct scsi_sense_hdr sshdr;
  184 +
  185 + err = SCSI_DH_IO;
  186 +
  187 + if (scsi_normalize_sense(csdev->sense, SCSI_SENSE_BUFFERSIZE,
  188 + &sshdr))
  189 + sdev_printk(KERN_ERR, sdev, "Found valid sense data "
  190 + "0x%2x, 0x%2x, 0x%2x while finding current "
  191 + "active SP.", sshdr.sense_key, sshdr.asc,
  192 + sshdr.ascq);
  193 + else
  194 + sdev_printk(KERN_ERR, sdev, "Error 0x%x finding "
  195 + "current active SP.", result);
  196 + }
  197 +
  198 + return err;
  199 +}
  200 +
  201 +static int sp_info_endio(struct scsi_device *sdev, int result,
  202 + int mode_select_sent, int *done)
  203 +{
  204 + struct clariion_dh_data *csdev = get_clariion_data(sdev);
  205 + int err_flags, default_sp, current_sp, new_current_sp;
  206 +
  207 + err_flags = parse_sp_info_reply(sdev, result, &default_sp,
  208 + &current_sp, &new_current_sp);
  209 +
  210 + if (err_flags != SCSI_DH_OK)
  211 + goto done;
  212 +
  213 + if (mode_select_sent) {
  214 + csdev->default_sp = default_sp;
  215 + csdev->current_sp = current_sp;
  216 + } else {
  217 + /*
  218 + * Issue the actual module_selec request IFF either
  219 + * (1) we do not know the identity of the current SP OR
  220 + * (2) what we think we know is actually correct.
  221 + */
  222 + if ((current_sp != CLARIION_UNBOUND_LU) &&
  223 + (new_current_sp != current_sp)) {
  224 +
  225 + csdev->default_sp = default_sp;
  226 + csdev->current_sp = current_sp;
  227 +
  228 + sdev_printk(KERN_INFO, sdev, "Ignoring path group "
  229 + "switch-over command for CLARiiON SP%s since "
  230 + " mapped device is already initialized.",
  231 + current_sp ? "B" : "A");
  232 + if (done)
  233 + *done = 1; /* as good as doing it */
  234 + }
  235 + }
  236 +done:
  237 + return err_flags;
  238 +}
  239 +
  240 +/*
  241 +* Get block request for REQ_BLOCK_PC command issued to path. Currently
  242 +* limited to MODE_SELECT (trespass) and INQUIRY (VPD page 0xC0) commands.
  243 +*
  244 +* Uses data and sense buffers in hardware handler context structure and
  245 +* assumes serial servicing of commands, both issuance and completion.
  246 +*/
  247 +static struct request *get_req(struct scsi_device *sdev, int cmd)
  248 +{
  249 + struct clariion_dh_data *csdev = get_clariion_data(sdev);
  250 + struct request *rq;
  251 + unsigned char *page22;
  252 + int len = 0;
  253 +
  254 + rq = blk_get_request(sdev->request_queue,
  255 + (cmd == MODE_SELECT) ? WRITE : READ, GFP_ATOMIC);
  256 + if (!rq) {
  257 + sdev_printk(KERN_INFO, sdev, "get_req: blk_get_request failed");
  258 + return NULL;
  259 + }
  260 +
  261 + memset(&rq->cmd, 0, BLK_MAX_CDB);
  262 + rq->cmd[0] = cmd;
  263 + rq->cmd_len = COMMAND_SIZE(rq->cmd[0]);
  264 +
  265 + switch (cmd) {
  266 + case MODE_SELECT:
  267 + if (csdev->short_trespass) {
  268 + page22 = csdev->hr ? short_trespass_hr : short_trespass;
  269 + len = sizeof(short_trespass);
  270 + } else {
  271 + page22 = csdev->hr ? long_trespass_hr : long_trespass;
  272 + len = sizeof(long_trespass);
  273 + }
  274 + /*
  275 + * Can't DMA from kernel BSS -- must copy selected trespass
  276 + * command mode page contents to context buffer which is
  277 + * allocated by kmalloc.
  278 + */
  279 + BUG_ON((len > CLARIION_BUFFER_SIZE));
  280 + memcpy(csdev->buffer, page22, len);
  281 + rq->cmd_flags |= REQ_RW;
  282 + rq->cmd[1] = 0x10;
  283 + break;
  284 + case INQUIRY:
  285 + rq->cmd[1] = 0x1;
  286 + rq->cmd[2] = 0xC0;
  287 + len = CLARIION_BUFFER_SIZE;
  288 + memset(csdev->buffer, 0, CLARIION_BUFFER_SIZE);
  289 + break;
  290 + default:
  291 + BUG_ON(1);
  292 + break;
  293 + }
  294 +
  295 + rq->cmd[4] = len;
  296 + rq->cmd_type = REQ_TYPE_BLOCK_PC;
  297 + rq->cmd_flags |= REQ_FAILFAST;
  298 + rq->timeout = CLARIION_TIMEOUT;
  299 + rq->retries = CLARIION_RETRIES;
  300 +
  301 + rq->sense = csdev->sense;
  302 + memset(rq->sense, 0, SCSI_SENSE_BUFFERSIZE);
  303 + rq->sense_len = 0;
  304 +
  305 + if (blk_rq_map_kern(sdev->request_queue, rq, csdev->buffer,
  306 + len, GFP_ATOMIC)) {
  307 + __blk_put_request(rq->q, rq);
  308 + return NULL;
  309 + }
  310 +
  311 + return rq;
  312 +}
  313 +
  314 +static int send_cmd(struct scsi_device *sdev, int cmd)
  315 +{
  316 + struct request *rq = get_req(sdev, cmd);
  317 +
  318 + if (!rq)
  319 + return SCSI_DH_RES_TEMP_UNAVAIL;
  320 +
  321 + return blk_execute_rq(sdev->request_queue, NULL, rq, 1);
  322 +}
  323 +
  324 +static int clariion_activate(struct scsi_device *sdev)
  325 +{
  326 + int result, done = 0;
  327 +
  328 + result = send_cmd(sdev, INQUIRY);
  329 + result = sp_info_endio(sdev, result, 0, &done);
  330 + if (result || done)
  331 + goto done;
  332 +
  333 + result = send_cmd(sdev, MODE_SELECT);
  334 + result = trespass_endio(sdev, result);
  335 + if (result)
  336 + goto done;
  337 +
  338 + result = send_cmd(sdev, INQUIRY);
  339 + result = sp_info_endio(sdev, result, 1, NULL);
  340 +done:
  341 + return result;
  342 +}
  343 +
  344 +static int clariion_check_sense(struct scsi_device *sdev,
  345 + struct scsi_sense_hdr *sense_hdr)
  346 +{
  347 + switch (sense_hdr->sense_key) {
  348 + case NOT_READY:
  349 + if (sense_hdr->asc == 0x04 && sense_hdr->ascq == 0x03)
  350 + /*
  351 + * LUN Not Ready - Manual Intervention Required
  352 + * indicates this is a passive path.
  353 + *
  354 + * FIXME: However, if this is seen and EVPD C0
  355 + * indicates that this is due to a NDU in
  356 + * progress, we should set FAIL_PATH too.
  357 + * This indicates we might have to do a SCSI
  358 + * inquiry in the end_io path. Ugh.
  359 + *
  360 + * Can return FAILED only when we want the error
  361 + * recovery process to kick in.
  362 + */
  363 + return SUCCESS;
  364 + break;
  365 + case ILLEGAL_REQUEST:
  366 + if (sense_hdr->asc == 0x25 && sense_hdr->ascq == 0x01)
  367 + /*
  368 + * An array based copy is in progress. Do not
  369 + * fail the path, do not bypass to another PG,
  370 + * do not retry. Fail the IO immediately.
  371 + * (Actually this is the same conclusion as in
  372 + * the default handler, but lets make sure.)
  373 + *
  374 + * Can return FAILED only when we want the error
  375 + * recovery process to kick in.
  376 + */
  377 + return SUCCESS;
  378 + break;
  379 + case UNIT_ATTENTION:
  380 + if (sense_hdr->asc == 0x29 && sense_hdr->ascq == 0x00)
  381 + /*
  382 + * Unit Attention Code. This is the first IO
  383 + * to the new path, so just retry.
  384 + */
  385 + return NEEDS_RETRY;
  386 + break;
  387 + }
  388 +
  389 + /* success just means we do not care what scsi-ml does */
  390 + return SUCCESS;
  391 +}
  392 +
  393 +static const struct {
  394 + char *vendor;
  395 + char *model;
  396 +} clariion_dev_list[] = {
  397 + {"DGC", "RAID"},
  398 + {"DGC", "DISK"},
  399 + {NULL, NULL},
  400 +};
  401 +
  402 +static int clariion_bus_notify(struct notifier_block *, unsigned long, void *);
  403 +
  404 +static struct scsi_device_handler clariion_dh = {
  405 + .name = CLARIION_NAME,
  406 + .module = THIS_MODULE,
  407 + .nb.notifier_call = clariion_bus_notify,
  408 + .check_sense = clariion_check_sense,
  409 + .activate = clariion_activate,
  410 +};
  411 +
  412 +/*
  413 + * TODO: need some interface so we can set trespass values
  414 + */
  415 +static int clariion_bus_notify(struct notifier_block *nb,
  416 + unsigned long action, void *data)
  417 +{
  418 + struct device *dev = data;
  419 + struct scsi_device *sdev = to_scsi_device(dev);
  420 + struct scsi_dh_data *scsi_dh_data;
  421 + struct clariion_dh_data *h;
  422 + int i, found = 0;
  423 + unsigned long flags;
  424 +
  425 + if (action == BUS_NOTIFY_ADD_DEVICE) {
  426 + for (i = 0; clariion_dev_list[i].vendor; i++) {
  427 + if (!strncmp(sdev->vendor, clariion_dev_list[i].vendor,
  428 + strlen(clariion_dev_list[i].vendor)) &&
  429 + !strncmp(sdev->model, clariion_dev_list[i].model,
  430 + strlen(clariion_dev_list[i].model))) {
  431 + found = 1;
  432 + break;
  433 + }
  434 + }
  435 + if (!found)
  436 + goto out;
  437 +
  438 + scsi_dh_data = kzalloc(sizeof(struct scsi_device_handler *)
  439 + + sizeof(*h) , GFP_KERNEL);
  440 + if (!scsi_dh_data) {
  441 + sdev_printk(KERN_ERR, sdev, "Attach failed %s.\n",
  442 + CLARIION_NAME);
  443 + goto out;
  444 + }
  445 +
  446 + scsi_dh_data->scsi_dh = &clariion_dh;
  447 + h = (struct clariion_dh_data *) scsi_dh_data->buf;
  448 + h->default_sp = CLARIION_UNBOUND_LU;
  449 + h->current_sp = CLARIION_UNBOUND_LU;
  450 +
  451 + spin_lock_irqsave(sdev->request_queue->queue_lock, flags);
  452 + sdev->scsi_dh_data = scsi_dh_data;
  453 + spin_unlock_irqrestore(sdev->request_queue->queue_lock, flags);
  454 +
  455 + sdev_printk(KERN_NOTICE, sdev, "Attached %s.\n", CLARIION_NAME);
  456 + try_module_get(THIS_MODULE);
  457 +
  458 + } else if (action == BUS_NOTIFY_DEL_DEVICE) {
  459 + if (sdev->scsi_dh_data == NULL ||
  460 + sdev->scsi_dh_data->scsi_dh != &clariion_dh)
  461 + goto out;
  462 +
  463 + spin_lock_irqsave(sdev->request_queue->queue_lock, flags);
  464 + scsi_dh_data = sdev->scsi_dh_data;
  465 + sdev->scsi_dh_data = NULL;
  466 + spin_unlock_irqrestore(sdev->request_queue->queue_lock, flags);
  467 +
  468 + sdev_printk(KERN_NOTICE, sdev, "Dettached %s.\n",
  469 + CLARIION_NAME);
  470 +
  471 + kfree(scsi_dh_data);
  472 + module_put(THIS_MODULE);
  473 + }
  474 +
  475 +out:
  476 + return 0;
  477 +}
  478 +
  479 +static int __init clariion_init(void)
  480 +{
  481 + int r;
  482 +
  483 + r = scsi_register_device_handler(&clariion_dh);
  484 + if (r != 0)
  485 + printk(KERN_ERR "Failed to register scsi device handler.");
  486 + return r;
  487 +}
  488 +
  489 +static void __exit clariion_exit(void)
  490 +{
  491 + scsi_unregister_device_handler(&clariion_dh);
  492 +}
  493 +
  494 +module_init(clariion_init);
  495 +module_exit(clariion_exit);
  496 +
  497 +MODULE_DESCRIPTION("EMC CX/AX/FC-family driver");
  498 +MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu>, Chandra Seetharaman <sekharan@us.ibm.com>");
  499 +MODULE_LICENSE("GPL");