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 <linux/slab.h>
24#include <linux/module.h>
25#include <scsi/scsi.h>
26#include <scsi/scsi_eh.h>
27#include <scsi/scsi_dh.h>
28#include <scsi/scsi_device.h>
29
30#define CLARIION_NAME			"emc"
31
32#define CLARIION_TRESPASS_PAGE		0x22
33#define CLARIION_BUFFER_SIZE		0xFC
34#define CLARIION_TIMEOUT		(60 * HZ)
35#define CLARIION_RETRIES		3
36#define CLARIION_UNBOUND_LU		-1
37#define CLARIION_SP_A			0
38#define CLARIION_SP_B			1
39
40/* Flags */
41#define CLARIION_SHORT_TRESPASS		1
42#define CLARIION_HONOR_RESERVATIONS	2
43
44/* LUN states */
45#define CLARIION_LUN_UNINITIALIZED	-1
46#define CLARIION_LUN_UNBOUND		0
47#define CLARIION_LUN_BOUND		1
48#define CLARIION_LUN_OWNED		2
49
50static unsigned char long_trespass[] = {
51	0, 0, 0, 0, 0, 0, 0, 0,
52	CLARIION_TRESPASS_PAGE,	/* Page code */
53	0x09,			/* Page length - 2 */
54	0x01,			/* Trespass code */
55	0xff, 0xff,		/* Trespass target */
56	0, 0, 0, 0, 0, 0	/* Reserved bytes / unknown */
57};
58
59static unsigned char short_trespass[] = {
60	0, 0, 0, 0,
61	CLARIION_TRESPASS_PAGE,	/* Page code */
62	0x02,			/* Page length - 2 */
63	0x01,			/* Trespass code */
64	0xff,			/* Trespass target */
65};
66
67static const char * lun_state[] =
68{
69    "not bound",
70    "bound",
71    "owned",
72};
73
74struct clariion_dh_data {
75	struct scsi_dh_data dh_data;
76	/*
77	 * Flags:
78	 *  CLARIION_SHORT_TRESPASS
79	 * Use short trespass command (FC-series) or the long version
80	 * (default for AX/CX CLARiiON arrays).
81	 *
82	 *  CLARIION_HONOR_RESERVATIONS
83	 * Whether or not (default) to honor SCSI reservations when
84	 * initiating a switch-over.
85	 */
86	unsigned flags;
87	/*
88	 * I/O buffer for both MODE_SELECT and INQUIRY commands.
89	 */
90	unsigned char buffer[CLARIION_BUFFER_SIZE];
91	/*
92	 * SCSI sense buffer for commands -- assumes serial issuance
93	 * and completion sequence of all commands for same multipath.
94	 */
95	unsigned char sense[SCSI_SENSE_BUFFERSIZE];
96	unsigned int senselen;
97	/*
98	 * LUN state
99	 */
100	int lun_state;
101	/*
102	 * SP Port number
103	 */
104	int port;
105	/*
106	 * which SP (A=0,B=1,UNBOUND=-1) is the default SP for this
107	 * path's mapped LUN
108	 */
109	int default_sp;
110	/*
111	 * which SP (A=0,B=1,UNBOUND=-1) is the active SP for this
112	 * path's mapped LUN
113	 */
114	int current_sp;
115};
116
117static inline struct clariion_dh_data
118			*get_clariion_data(struct scsi_device *sdev)
119{
120	return container_of(sdev->scsi_dh_data, struct clariion_dh_data,
121			dh_data);
122}
123
124/*
125 * Parse MODE_SELECT cmd reply.
126 */
127static int trespass_endio(struct scsi_device *sdev, char *sense)
128{
129	int err = SCSI_DH_IO;
130	struct scsi_sense_hdr sshdr;
131
132	if (!scsi_normalize_sense(sense, SCSI_SENSE_BUFFERSIZE, &sshdr)) {
133		sdev_printk(KERN_ERR, sdev, "%s: Found valid sense data 0x%2x, "
134			    "0x%2x, 0x%2x while sending CLARiiON trespass "
135			    "command.\n", CLARIION_NAME, sshdr.sense_key,
136			    sshdr.asc, sshdr.ascq);
137
138		if ((sshdr.sense_key == 0x05) && (sshdr.asc == 0x04) &&
139		     (sshdr.ascq == 0x00)) {
140			/*
141			 * Array based copy in progress -- do not send
142			 * mode_select or copy will be aborted mid-stream.
143			 */
144			sdev_printk(KERN_INFO, sdev, "%s: Array Based Copy in "
145				    "progress while sending CLARiiON trespass "
146				    "command.\n", CLARIION_NAME);
147			err = SCSI_DH_DEV_TEMP_BUSY;
148		} else if ((sshdr.sense_key == 0x02) && (sshdr.asc == 0x04) &&
149			    (sshdr.ascq == 0x03)) {
150			/*
151			 * LUN Not Ready - Manual Intervention Required
152			 * indicates in-progress ucode upgrade (NDU).
153			 */
154			sdev_printk(KERN_INFO, sdev, "%s: Detected in-progress "
155				    "ucode upgrade NDU operation while sending "
156				    "CLARiiON trespass command.\n", CLARIION_NAME);
157			err = SCSI_DH_DEV_TEMP_BUSY;
158		} else
159			err = SCSI_DH_DEV_FAILED;
160	} else {
161		sdev_printk(KERN_INFO, sdev,
162			    "%s: failed to send MODE SELECT, no sense available\n",
163			    CLARIION_NAME);
164	}
165	return err;
166}
167
168static int parse_sp_info_reply(struct scsi_device *sdev,
169			       struct clariion_dh_data *csdev)
170{
171	int err = SCSI_DH_OK;
172
173	/* check for in-progress ucode upgrade (NDU) */
174	if (csdev->buffer[48] != 0) {
175		sdev_printk(KERN_NOTICE, sdev, "%s: Detected in-progress "
176			    "ucode upgrade NDU operation while finding "
177			    "current active SP.", CLARIION_NAME);
178		err = SCSI_DH_DEV_TEMP_BUSY;
179		goto out;
180	}
181	if (csdev->buffer[4] > 2) {
182		/* Invalid buffer format */
183		sdev_printk(KERN_NOTICE, sdev,
184			    "%s: invalid VPD page 0xC0 format\n",
185			    CLARIION_NAME);
186		err = SCSI_DH_NOSYS;
187		goto out;
188	}
189	switch (csdev->buffer[28] & 0x0f) {
190	case 6:
191		sdev_printk(KERN_NOTICE, sdev,
192			    "%s: ALUA failover mode detected\n",
193			    CLARIION_NAME);
194		break;
195	case 4:
196		/* Linux failover */
197		break;
198	default:
199		sdev_printk(KERN_WARNING, sdev,
200			    "%s: Invalid failover mode %d\n",
201			    CLARIION_NAME, csdev->buffer[28] & 0x0f);
202		err = SCSI_DH_NOSYS;
203		goto out;
204	}
205
206	csdev->default_sp = csdev->buffer[5];
207	csdev->lun_state = csdev->buffer[4];
208	csdev->current_sp = csdev->buffer[8];
209	csdev->port = csdev->buffer[7];
210
211out:
212	return err;
213}
214
215#define emc_default_str "FC (Legacy)"
216
217static char * parse_sp_model(struct scsi_device *sdev, unsigned char *buffer)
218{
219	unsigned char len = buffer[4] + 5;
220	char *sp_model = NULL;
221	unsigned char sp_len, serial_len;
222
223	if (len < 160) {
224		sdev_printk(KERN_WARNING, sdev,
225			    "%s: Invalid information section length %d\n",
226			    CLARIION_NAME, len);
227		/* Check for old FC arrays */
228		if (!strncmp(buffer + 8, "DGC", 3)) {
229			/* Old FC array, not supporting extended information */
230			sp_model = emc_default_str;
231		}
232		goto out;
233	}
234
235	/*
236	 * Parse extended information for SP model number
237	 */
238	serial_len = buffer[160];
239	if (serial_len == 0 || serial_len + 161 > len) {
240		sdev_printk(KERN_WARNING, sdev,
241			    "%s: Invalid array serial number length %d\n",
242			    CLARIION_NAME, serial_len);
243		goto out;
244	}
245	sp_len = buffer[99];
246	if (sp_len == 0 || serial_len + sp_len + 161 > len) {
247		sdev_printk(KERN_WARNING, sdev,
248			    "%s: Invalid model number length %d\n",
249			    CLARIION_NAME, sp_len);
250		goto out;
251	}
252	sp_model = &buffer[serial_len + 161];
253	/* Strip whitespace at the end */
254	while (sp_len > 1 && sp_model[sp_len - 1] == ' ')
255		sp_len--;
256
257	sp_model[sp_len] = '\0';
258
259out:
260	return sp_model;
261}
262
263/*
264 * Get block request for REQ_BLOCK_PC command issued to path.  Currently
265 * limited to MODE_SELECT (trespass) and INQUIRY (VPD page 0xC0) commands.
266 *
267 * Uses data and sense buffers in hardware handler context structure and
268 * assumes serial servicing of commands, both issuance and completion.
269 */
270static struct request *get_req(struct scsi_device *sdev, int cmd,
271				unsigned char *buffer)
272{
273	struct request *rq;
274	int len = 0;
275
276	rq = blk_get_request(sdev->request_queue,
277			(cmd != INQUIRY) ? WRITE : READ, GFP_NOIO);
278	if (IS_ERR(rq)) {
279		sdev_printk(KERN_INFO, sdev, "get_req: blk_get_request failed");
280		return NULL;
281	}
282
283	blk_rq_set_block_pc(rq);
284	rq->cmd_len = COMMAND_SIZE(cmd);
285	rq->cmd[0] = cmd;
286
287	switch (cmd) {
288	case MODE_SELECT:
289		len = sizeof(short_trespass);
290		rq->cmd[1] = 0x10;
291		rq->cmd[4] = len;
292		break;
293	case MODE_SELECT_10:
294		len = sizeof(long_trespass);
295		rq->cmd[1] = 0x10;
296		rq->cmd[8] = len;
297		break;
298	case INQUIRY:
299		len = CLARIION_BUFFER_SIZE;
300		rq->cmd[4] = len;
301		memset(buffer, 0, len);
302		break;
303	default:
304		BUG_ON(1);
305		break;
306	}
307
308	rq->cmd_flags |= REQ_FAILFAST_DEV | REQ_FAILFAST_TRANSPORT |
309			 REQ_FAILFAST_DRIVER;
310	rq->timeout = CLARIION_TIMEOUT;
311	rq->retries = CLARIION_RETRIES;
312
313	if (blk_rq_map_kern(rq->q, rq, buffer, len, GFP_NOIO)) {
314		blk_put_request(rq);
315		return NULL;
316	}
317
318	return rq;
319}
320
321static int send_inquiry_cmd(struct scsi_device *sdev, int page,
322			    struct clariion_dh_data *csdev)
323{
324	struct request *rq = get_req(sdev, INQUIRY, csdev->buffer);
325	int err;
326
327	if (!rq)
328		return SCSI_DH_RES_TEMP_UNAVAIL;
329
330	rq->sense = csdev->sense;
331	memset(rq->sense, 0, SCSI_SENSE_BUFFERSIZE);
332	rq->sense_len = csdev->senselen = 0;
333
334	rq->cmd[0] = INQUIRY;
335	if (page != 0) {
336		rq->cmd[1] = 1;
337		rq->cmd[2] = page;
338	}
339	err = blk_execute_rq(sdev->request_queue, NULL, rq, 1);
340	if (err == -EIO) {
341		sdev_printk(KERN_INFO, sdev,
342			    "%s: failed to send %s INQUIRY: %x\n",
343			    CLARIION_NAME, page?"EVPD":"standard",
344			    rq->errors);
345		csdev->senselen = rq->sense_len;
346		err = SCSI_DH_IO;
347	}
348
349	blk_put_request(rq);
350
351	return err;
352}
353
354static int send_trespass_cmd(struct scsi_device *sdev,
355			    struct clariion_dh_data *csdev)
356{
357	struct request *rq;
358	unsigned char *page22;
359	int err, len, cmd;
360
361	if (csdev->flags & CLARIION_SHORT_TRESPASS) {
362		page22 = short_trespass;
363		if (!(csdev->flags & CLARIION_HONOR_RESERVATIONS))
364			/* Set Honor Reservations bit */
365			page22[6] |= 0x80;
366		len = sizeof(short_trespass);
367		cmd = MODE_SELECT;
368	} else {
369		page22 = long_trespass;
370		if (!(csdev->flags & CLARIION_HONOR_RESERVATIONS))
371			/* Set Honor Reservations bit */
372			page22[10] |= 0x80;
373		len = sizeof(long_trespass);
374		cmd = MODE_SELECT_10;
375	}
376	BUG_ON((len > CLARIION_BUFFER_SIZE));
377	memcpy(csdev->buffer, page22, len);
378
379	rq = get_req(sdev, cmd, csdev->buffer);
380	if (!rq)
381		return SCSI_DH_RES_TEMP_UNAVAIL;
382
383	rq->sense = csdev->sense;
384	memset(rq->sense, 0, SCSI_SENSE_BUFFERSIZE);
385	rq->sense_len = csdev->senselen = 0;
386
387	err = blk_execute_rq(sdev->request_queue, NULL, rq, 1);
388	if (err == -EIO) {
389		if (rq->sense_len) {
390			err = trespass_endio(sdev, csdev->sense);
391		} else {
392			sdev_printk(KERN_INFO, sdev,
393				    "%s: failed to send MODE SELECT: %x\n",
394				    CLARIION_NAME, rq->errors);
395		}
396	}
397
398	blk_put_request(rq);
399
400	return err;
401}
402
403static int clariion_check_sense(struct scsi_device *sdev,
404				struct scsi_sense_hdr *sense_hdr)
405{
406	switch (sense_hdr->sense_key) {
407	case NOT_READY:
408		if (sense_hdr->asc == 0x04 && sense_hdr->ascq == 0x03)
409			/*
410			 * LUN Not Ready - Manual Intervention Required
411			 * indicates this is a passive path.
412			 *
413			 * FIXME: However, if this is seen and EVPD C0
414			 * indicates that this is due to a NDU in
415			 * progress, we should set FAIL_PATH too.
416			 * This indicates we might have to do a SCSI
417			 * inquiry in the end_io path. Ugh.
418			 *
419			 * Can return FAILED only when we want the error
420			 * recovery process to kick in.
421			 */
422			return SUCCESS;
423		break;
424	case ILLEGAL_REQUEST:
425		if (sense_hdr->asc == 0x25 && sense_hdr->ascq == 0x01)
426			/*
427			 * An array based copy is in progress. Do not
428			 * fail the path, do not bypass to another PG,
429			 * do not retry. Fail the IO immediately.
430			 * (Actually this is the same conclusion as in
431			 * the default handler, but lets make sure.)
432			 *
433			 * Can return FAILED only when we want the error
434			 * recovery process to kick in.
435			 */
436			return SUCCESS;
437		break;
438	case UNIT_ATTENTION:
439		if (sense_hdr->asc == 0x29 && sense_hdr->ascq == 0x00)
440			/*
441			 * Unit Attention Code. This is the first IO
442			 * to the new path, so just retry.
443			 */
444			return ADD_TO_MLQUEUE;
445		break;
446	}
447
448	return SCSI_RETURN_NOT_HANDLED;
449}
450
451static int clariion_prep_fn(struct scsi_device *sdev, struct request *req)
452{
453	struct clariion_dh_data *h = get_clariion_data(sdev);
454	int ret = BLKPREP_OK;
455
456	if (h->lun_state != CLARIION_LUN_OWNED) {
457		ret = BLKPREP_KILL;
458		req->cmd_flags |= REQ_QUIET;
459	}
460	return ret;
461
462}
463
464static int clariion_std_inquiry(struct scsi_device *sdev,
465				struct clariion_dh_data *csdev)
466{
467	int err;
468	char *sp_model;
469
470	err = send_inquiry_cmd(sdev, 0, csdev);
471	if (err != SCSI_DH_OK && csdev->senselen) {
472		struct scsi_sense_hdr sshdr;
473
474		if (scsi_normalize_sense(csdev->sense, SCSI_SENSE_BUFFERSIZE,
475					 &sshdr)) {
476			sdev_printk(KERN_ERR, sdev, "%s: INQUIRY sense code "
477				    "%02x/%02x/%02x\n", CLARIION_NAME,
478				    sshdr.sense_key, sshdr.asc, sshdr.ascq);
479		}
480		err = SCSI_DH_IO;
481		goto out;
482	}
483
484	sp_model = parse_sp_model(sdev, csdev->buffer);
485	if (!sp_model) {
486		err = SCSI_DH_DEV_UNSUPP;
487		goto out;
488	}
489
490	/*
491	 * FC Series arrays do not support long trespass
492	 */
493	if (!strlen(sp_model) || !strncmp(sp_model, "FC",2))
494		csdev->flags |= CLARIION_SHORT_TRESPASS;
495
496	sdev_printk(KERN_INFO, sdev,
497		    "%s: detected Clariion %s, flags %x\n",
498		    CLARIION_NAME, sp_model, csdev->flags);
499out:
500	return err;
501}
502
503static int clariion_send_inquiry(struct scsi_device *sdev,
504				 struct clariion_dh_data *csdev)
505{
506	int err, retry = CLARIION_RETRIES;
507
508retry:
509	err = send_inquiry_cmd(sdev, 0xC0, csdev);
510	if (err != SCSI_DH_OK && csdev->senselen) {
511		struct scsi_sense_hdr sshdr;
512
513		err = scsi_normalize_sense(csdev->sense, SCSI_SENSE_BUFFERSIZE,
514					   &sshdr);
515		if (!err)
516			return SCSI_DH_IO;
517
518		err = clariion_check_sense(sdev, &sshdr);
519		if (retry > 0 && err == ADD_TO_MLQUEUE) {
520			retry--;
521			goto retry;
522		}
523		sdev_printk(KERN_ERR, sdev, "%s: INQUIRY sense code "
524			    "%02x/%02x/%02x\n", CLARIION_NAME,
525			      sshdr.sense_key, sshdr.asc, sshdr.ascq);
526		err = SCSI_DH_IO;
527	} else {
528		err = parse_sp_info_reply(sdev, csdev);
529	}
530	return err;
531}
532
533static int clariion_activate(struct scsi_device *sdev,
534				activate_complete fn, void *data)
535{
536	struct clariion_dh_data *csdev = get_clariion_data(sdev);
537	int result;
538
539	result = clariion_send_inquiry(sdev, csdev);
540	if (result != SCSI_DH_OK)
541		goto done;
542
543	if (csdev->lun_state == CLARIION_LUN_OWNED)
544		goto done;
545
546	result = send_trespass_cmd(sdev, csdev);
547	if (result != SCSI_DH_OK)
548		goto done;
549	sdev_printk(KERN_INFO, sdev,"%s: %s trespass command sent\n",
550		    CLARIION_NAME,
551		    csdev->flags&CLARIION_SHORT_TRESPASS?"short":"long" );
552
553	/* Update status */
554	result = clariion_send_inquiry(sdev, csdev);
555	if (result != SCSI_DH_OK)
556		goto done;
557
558done:
559	sdev_printk(KERN_INFO, sdev,
560		    "%s: at SP %c Port %d (%s, default SP %c)\n",
561		    CLARIION_NAME, csdev->current_sp + 'A',
562		    csdev->port, lun_state[csdev->lun_state],
563		    csdev->default_sp + 'A');
564
565	if (fn)
566		fn(data, result);
567	return 0;
568}
569/*
570 * params - parameters in the following format
571 *      "no_of_params\0param1\0param2\0param3\0...\0"
572 *      for example, string for 2 parameters with value 10 and 21
573 *      is specified as "2\010\021\0".
574 */
575static int clariion_set_params(struct scsi_device *sdev, const char *params)
576{
577	struct clariion_dh_data *csdev = get_clariion_data(sdev);
578	unsigned int hr = 0, st = 0, argc;
579	const char *p = params;
580	int result = SCSI_DH_OK;
581
582	if ((sscanf(params, "%u", &argc) != 1) || (argc != 2))
583		return -EINVAL;
584
585	while (*p++)
586		;
587	if ((sscanf(p, "%u", &st) != 1) || (st > 1))
588		return -EINVAL;
589
590	while (*p++)
591		;
592	if ((sscanf(p, "%u", &hr) != 1) || (hr > 1))
593		return -EINVAL;
594
595	if (st)
596		csdev->flags |= CLARIION_SHORT_TRESPASS;
597	else
598		csdev->flags &= ~CLARIION_SHORT_TRESPASS;
599
600	if (hr)
601		csdev->flags |= CLARIION_HONOR_RESERVATIONS;
602	else
603		csdev->flags &= ~CLARIION_HONOR_RESERVATIONS;
604
605	/*
606	 * If this path is owned, we have to send a trespass command
607	 * with the new parameters. If not, simply return. Next trespass
608	 * command would use the parameters.
609	 */
610	if (csdev->lun_state != CLARIION_LUN_OWNED)
611		goto done;
612
613	csdev->lun_state = CLARIION_LUN_UNINITIALIZED;
614	result = send_trespass_cmd(sdev, csdev);
615	if (result != SCSI_DH_OK)
616		goto done;
617
618	/* Update status */
619	result = clariion_send_inquiry(sdev, csdev);
620
621done:
622	return result;
623}
624
625static const struct {
626	char *vendor;
627	char *model;
628} clariion_dev_list[] = {
629	{"DGC", "RAID"},
630	{"DGC", "DISK"},
631	{"DGC", "VRAID"},
632	{NULL, NULL},
633};
634
635static bool clariion_match(struct scsi_device *sdev)
636{
637	int i;
638
639	if (scsi_device_tpgs(sdev))
640		return false;
641
642	for (i = 0; clariion_dev_list[i].vendor; i++) {
643		if (!strncmp(sdev->vendor, clariion_dev_list[i].vendor,
644			strlen(clariion_dev_list[i].vendor)) &&
645		    !strncmp(sdev->model, clariion_dev_list[i].model,
646			strlen(clariion_dev_list[i].model))) {
647			return true;
648		}
649	}
650	return false;
651}
652
653static struct scsi_dh_data *clariion_bus_attach(struct scsi_device *sdev)
654{
655	struct clariion_dh_data *h;
656	int err;
657
658	h = kzalloc(sizeof(*h) , GFP_KERNEL);
659	if (!h)
660		return ERR_PTR(-ENOMEM);
661	h->lun_state = CLARIION_LUN_UNINITIALIZED;
662	h->default_sp = CLARIION_UNBOUND_LU;
663	h->current_sp = CLARIION_UNBOUND_LU;
664
665	err = clariion_std_inquiry(sdev, h);
666	if (err != SCSI_DH_OK)
667		goto failed;
668
669	err = clariion_send_inquiry(sdev, h);
670	if (err != SCSI_DH_OK)
671		goto failed;
672
673	sdev_printk(KERN_INFO, sdev,
674		    "%s: connected to SP %c Port %d (%s, default SP %c)\n",
675		    CLARIION_NAME, h->current_sp + 'A',
676		    h->port, lun_state[h->lun_state],
677		    h->default_sp + 'A');
678	return &h->dh_data;
679
680failed:
681	kfree(h);
682	return ERR_PTR(-EINVAL);
683}
684
685static void clariion_bus_detach(struct scsi_device *sdev)
686{
687	struct clariion_dh_data *h = get_clariion_data(sdev);
688
689	kfree(h);
690}
691
692static struct scsi_device_handler clariion_dh = {
693	.name		= CLARIION_NAME,
694	.module		= THIS_MODULE,
695	.attach		= clariion_bus_attach,
696	.detach		= clariion_bus_detach,
697	.check_sense	= clariion_check_sense,
698	.activate	= clariion_activate,
699	.prep_fn	= clariion_prep_fn,
700	.set_params	= clariion_set_params,
701	.match		= clariion_match,
702};
703
704static int __init clariion_init(void)
705{
706	int r;
707
708	r = scsi_register_device_handler(&clariion_dh);
709	if (r != 0)
710		printk(KERN_ERR "%s: Failed to register scsi device handler.",
711			CLARIION_NAME);
712	return r;
713}
714
715static void __exit clariion_exit(void)
716{
717	scsi_unregister_device_handler(&clariion_dh);
718}
719
720module_init(clariion_init);
721module_exit(clariion_exit);
722
723MODULE_DESCRIPTION("EMC CX/AX/FC-family driver");
724MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu>, Chandra Seetharaman <sekharan@us.ibm.com>");
725MODULE_LICENSE("GPL");
726