1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Target driver for EMC CLARiiON AX/CX-series hardware. |
4 | * Based on code from Lars Marowsky-Bree <lmb@suse.de> |
5 | * and Ed Goggin <egoggin@emc.com>. |
6 | * |
7 | * Copyright (C) 2006 Red Hat, Inc. All rights reserved. |
8 | * Copyright (C) 2006 Mike Christie |
9 | */ |
10 | #include <linux/slab.h> |
11 | #include <linux/module.h> |
12 | #include <scsi/scsi.h> |
13 | #include <scsi/scsi_eh.h> |
14 | #include <scsi/scsi_dh.h> |
15 | #include <scsi/scsi_device.h> |
16 | |
17 | #define CLARIION_NAME "emc" |
18 | |
19 | #define CLARIION_TRESPASS_PAGE 0x22 |
20 | #define CLARIION_BUFFER_SIZE 0xFC |
21 | #define CLARIION_TIMEOUT (60 * HZ) |
22 | #define CLARIION_RETRIES 3 |
23 | #define CLARIION_UNBOUND_LU -1 |
24 | #define CLARIION_SP_A 0 |
25 | #define CLARIION_SP_B 1 |
26 | |
27 | /* Flags */ |
28 | #define CLARIION_SHORT_TRESPASS 1 |
29 | #define CLARIION_HONOR_RESERVATIONS 2 |
30 | |
31 | /* LUN states */ |
32 | #define CLARIION_LUN_UNINITIALIZED -1 |
33 | #define CLARIION_LUN_UNBOUND 0 |
34 | #define CLARIION_LUN_BOUND 1 |
35 | #define CLARIION_LUN_OWNED 2 |
36 | |
37 | static unsigned char long_trespass[] = { |
38 | 0, 0, 0, 0, 0, 0, 0, 0, |
39 | CLARIION_TRESPASS_PAGE, /* Page code */ |
40 | 0x09, /* Page length - 2 */ |
41 | 0x01, /* Trespass code */ |
42 | 0xff, 0xff, /* Trespass target */ |
43 | 0, 0, 0, 0, 0, 0 /* Reserved bytes / unknown */ |
44 | }; |
45 | |
46 | static unsigned char short_trespass[] = { |
47 | 0, 0, 0, 0, |
48 | CLARIION_TRESPASS_PAGE, /* Page code */ |
49 | 0x02, /* Page length - 2 */ |
50 | 0x01, /* Trespass code */ |
51 | 0xff, /* Trespass target */ |
52 | }; |
53 | |
54 | static const char * lun_state[] = |
55 | { |
56 | "not bound" , |
57 | "bound" , |
58 | "owned" , |
59 | }; |
60 | |
61 | struct clariion_dh_data { |
62 | /* |
63 | * Flags: |
64 | * CLARIION_SHORT_TRESPASS |
65 | * Use short trespass command (FC-series) or the long version |
66 | * (default for AX/CX CLARiiON arrays). |
67 | * |
68 | * CLARIION_HONOR_RESERVATIONS |
69 | * Whether or not (default) to honor SCSI reservations when |
70 | * initiating a switch-over. |
71 | */ |
72 | unsigned flags; |
73 | /* |
74 | * I/O buffer for both MODE_SELECT and INQUIRY commands. |
75 | */ |
76 | unsigned char buffer[CLARIION_BUFFER_SIZE]; |
77 | /* |
78 | * LUN state |
79 | */ |
80 | int lun_state; |
81 | /* |
82 | * SP Port number |
83 | */ |
84 | int port; |
85 | /* |
86 | * which SP (A=0,B=1,UNBOUND=-1) is the default SP for this |
87 | * path's mapped LUN |
88 | */ |
89 | int default_sp; |
90 | /* |
91 | * which SP (A=0,B=1,UNBOUND=-1) is the active SP for this |
92 | * path's mapped LUN |
93 | */ |
94 | int current_sp; |
95 | }; |
96 | |
97 | /* |
98 | * Parse MODE_SELECT cmd reply. |
99 | */ |
100 | static int trespass_endio(struct scsi_device *sdev, |
101 | struct scsi_sense_hdr *sshdr) |
102 | { |
103 | int err = SCSI_DH_IO; |
104 | |
105 | sdev_printk(KERN_ERR, sdev, "%s: Found valid sense data 0x%2x, " |
106 | "0x%2x, 0x%2x while sending CLARiiON trespass " |
107 | "command.\n" , CLARIION_NAME, sshdr->sense_key, |
108 | sshdr->asc, sshdr->ascq); |
109 | |
110 | if (sshdr->sense_key == 0x05 && sshdr->asc == 0x04 && |
111 | sshdr->ascq == 0x00) { |
112 | /* |
113 | * Array based copy in progress -- do not send |
114 | * mode_select or copy will be aborted mid-stream. |
115 | */ |
116 | sdev_printk(KERN_INFO, sdev, "%s: Array Based Copy in " |
117 | "progress while sending CLARiiON trespass " |
118 | "command.\n" , CLARIION_NAME); |
119 | err = SCSI_DH_DEV_TEMP_BUSY; |
120 | } else if (sshdr->sense_key == 0x02 && sshdr->asc == 0x04 && |
121 | sshdr->ascq == 0x03) { |
122 | /* |
123 | * LUN Not Ready - Manual Intervention Required |
124 | * indicates in-progress ucode upgrade (NDU). |
125 | */ |
126 | sdev_printk(KERN_INFO, sdev, "%s: Detected in-progress " |
127 | "ucode upgrade NDU operation while sending " |
128 | "CLARiiON trespass command.\n" , CLARIION_NAME); |
129 | err = SCSI_DH_DEV_TEMP_BUSY; |
130 | } else |
131 | err = SCSI_DH_DEV_FAILED; |
132 | return err; |
133 | } |
134 | |
135 | static int parse_sp_info_reply(struct scsi_device *sdev, |
136 | struct clariion_dh_data *csdev) |
137 | { |
138 | int err = SCSI_DH_OK; |
139 | |
140 | /* check for in-progress ucode upgrade (NDU) */ |
141 | if (csdev->buffer[48] != 0) { |
142 | sdev_printk(KERN_NOTICE, sdev, "%s: Detected in-progress " |
143 | "ucode upgrade NDU operation while finding " |
144 | "current active SP." , CLARIION_NAME); |
145 | err = SCSI_DH_DEV_TEMP_BUSY; |
146 | goto out; |
147 | } |
148 | if (csdev->buffer[4] > 2) { |
149 | /* Invalid buffer format */ |
150 | sdev_printk(KERN_NOTICE, sdev, |
151 | "%s: invalid VPD page 0xC0 format\n" , |
152 | CLARIION_NAME); |
153 | err = SCSI_DH_NOSYS; |
154 | goto out; |
155 | } |
156 | switch (csdev->buffer[28] & 0x0f) { |
157 | case 6: |
158 | sdev_printk(KERN_NOTICE, sdev, |
159 | "%s: ALUA failover mode detected\n" , |
160 | CLARIION_NAME); |
161 | break; |
162 | case 4: |
163 | /* Linux failover */ |
164 | break; |
165 | default: |
166 | sdev_printk(KERN_WARNING, sdev, |
167 | "%s: Invalid failover mode %d\n" , |
168 | CLARIION_NAME, csdev->buffer[28] & 0x0f); |
169 | err = SCSI_DH_NOSYS; |
170 | goto out; |
171 | } |
172 | |
173 | csdev->default_sp = csdev->buffer[5]; |
174 | csdev->lun_state = csdev->buffer[4]; |
175 | csdev->current_sp = csdev->buffer[8]; |
176 | csdev->port = csdev->buffer[7]; |
177 | if (csdev->lun_state == CLARIION_LUN_OWNED) |
178 | sdev->access_state = SCSI_ACCESS_STATE_OPTIMAL; |
179 | else |
180 | sdev->access_state = SCSI_ACCESS_STATE_STANDBY; |
181 | if (csdev->default_sp == csdev->current_sp) |
182 | sdev->access_state |= SCSI_ACCESS_STATE_PREFERRED; |
183 | out: |
184 | return err; |
185 | } |
186 | |
187 | #define emc_default_str "FC (Legacy)" |
188 | |
189 | static char * parse_sp_model(struct scsi_device *sdev, unsigned char *buffer) |
190 | { |
191 | unsigned char len = buffer[4] + 5; |
192 | char *sp_model = NULL; |
193 | unsigned char sp_len, serial_len; |
194 | |
195 | if (len < 160) { |
196 | sdev_printk(KERN_WARNING, sdev, |
197 | "%s: Invalid information section length %d\n" , |
198 | CLARIION_NAME, len); |
199 | /* Check for old FC arrays */ |
200 | if (!strncmp(buffer + 8, "DGC" , 3)) { |
201 | /* Old FC array, not supporting extended information */ |
202 | sp_model = emc_default_str; |
203 | } |
204 | goto out; |
205 | } |
206 | |
207 | /* |
208 | * Parse extended information for SP model number |
209 | */ |
210 | serial_len = buffer[160]; |
211 | if (serial_len == 0 || serial_len + 161 > len) { |
212 | sdev_printk(KERN_WARNING, sdev, |
213 | "%s: Invalid array serial number length %d\n" , |
214 | CLARIION_NAME, serial_len); |
215 | goto out; |
216 | } |
217 | sp_len = buffer[99]; |
218 | if (sp_len == 0 || serial_len + sp_len + 161 > len) { |
219 | sdev_printk(KERN_WARNING, sdev, |
220 | "%s: Invalid model number length %d\n" , |
221 | CLARIION_NAME, sp_len); |
222 | goto out; |
223 | } |
224 | sp_model = &buffer[serial_len + 161]; |
225 | /* Strip whitespace at the end */ |
226 | while (sp_len > 1 && sp_model[sp_len - 1] == ' ') |
227 | sp_len--; |
228 | |
229 | sp_model[sp_len] = '\0'; |
230 | |
231 | out: |
232 | return sp_model; |
233 | } |
234 | |
235 | static int send_trespass_cmd(struct scsi_device *sdev, |
236 | struct clariion_dh_data *csdev) |
237 | { |
238 | unsigned char *page22; |
239 | unsigned char cdb[MAX_COMMAND_SIZE]; |
240 | int err, res = SCSI_DH_OK, len; |
241 | struct scsi_sense_hdr sshdr; |
242 | blk_opf_t opf = REQ_OP_DRV_OUT | REQ_FAILFAST_DEV | |
243 | REQ_FAILFAST_TRANSPORT | REQ_FAILFAST_DRIVER; |
244 | const struct scsi_exec_args exec_args = { |
245 | .sshdr = &sshdr, |
246 | }; |
247 | |
248 | if (csdev->flags & CLARIION_SHORT_TRESPASS) { |
249 | page22 = short_trespass; |
250 | if (!(csdev->flags & CLARIION_HONOR_RESERVATIONS)) |
251 | /* Set Honor Reservations bit */ |
252 | page22[6] |= 0x80; |
253 | len = sizeof(short_trespass); |
254 | cdb[0] = MODE_SELECT; |
255 | cdb[1] = 0x10; |
256 | cdb[4] = len; |
257 | } else { |
258 | page22 = long_trespass; |
259 | if (!(csdev->flags & CLARIION_HONOR_RESERVATIONS)) |
260 | /* Set Honor Reservations bit */ |
261 | page22[10] |= 0x80; |
262 | len = sizeof(long_trespass); |
263 | cdb[0] = MODE_SELECT_10; |
264 | cdb[8] = len; |
265 | } |
266 | BUG_ON((len > CLARIION_BUFFER_SIZE)); |
267 | memcpy(csdev->buffer, page22, len); |
268 | |
269 | err = scsi_execute_cmd(sdev, cmd: cdb, opf, buffer: csdev->buffer, bufflen: len, |
270 | CLARIION_TIMEOUT * HZ, CLARIION_RETRIES, |
271 | args: &exec_args); |
272 | if (err) { |
273 | if (scsi_sense_valid(sshdr: &sshdr)) |
274 | res = trespass_endio(sdev, sshdr: &sshdr); |
275 | else { |
276 | sdev_printk(KERN_INFO, sdev, |
277 | "%s: failed to send MODE SELECT: %x\n" , |
278 | CLARIION_NAME, err); |
279 | res = SCSI_DH_IO; |
280 | } |
281 | } |
282 | |
283 | return res; |
284 | } |
285 | |
286 | static enum scsi_disposition clariion_check_sense(struct scsi_device *sdev, |
287 | struct scsi_sense_hdr *sense_hdr) |
288 | { |
289 | switch (sense_hdr->sense_key) { |
290 | case NOT_READY: |
291 | if (sense_hdr->asc == 0x04 && sense_hdr->ascq == 0x03) |
292 | /* |
293 | * LUN Not Ready - Manual Intervention Required |
294 | * indicates this is a passive path. |
295 | * |
296 | * FIXME: However, if this is seen and EVPD C0 |
297 | * indicates that this is due to a NDU in |
298 | * progress, we should set FAIL_PATH too. |
299 | * This indicates we might have to do a SCSI |
300 | * inquiry in the end_io path. Ugh. |
301 | * |
302 | * Can return FAILED only when we want the error |
303 | * recovery process to kick in. |
304 | */ |
305 | return SUCCESS; |
306 | break; |
307 | case ILLEGAL_REQUEST: |
308 | if (sense_hdr->asc == 0x25 && sense_hdr->ascq == 0x01) |
309 | /* |
310 | * An array based copy is in progress. Do not |
311 | * fail the path, do not bypass to another PG, |
312 | * do not retry. Fail the IO immediately. |
313 | * (Actually this is the same conclusion as in |
314 | * the default handler, but lets make sure.) |
315 | * |
316 | * Can return FAILED only when we want the error |
317 | * recovery process to kick in. |
318 | */ |
319 | return SUCCESS; |
320 | break; |
321 | case UNIT_ATTENTION: |
322 | if (sense_hdr->asc == 0x29 && sense_hdr->ascq == 0x00) |
323 | /* |
324 | * Unit Attention Code. This is the first IO |
325 | * to the new path, so just retry. |
326 | */ |
327 | return ADD_TO_MLQUEUE; |
328 | break; |
329 | } |
330 | |
331 | return SCSI_RETURN_NOT_HANDLED; |
332 | } |
333 | |
334 | static blk_status_t clariion_prep_fn(struct scsi_device *sdev, |
335 | struct request *req) |
336 | { |
337 | struct clariion_dh_data *h = sdev->handler_data; |
338 | |
339 | if (h->lun_state != CLARIION_LUN_OWNED) { |
340 | req->rq_flags |= RQF_QUIET; |
341 | return BLK_STS_IOERR; |
342 | } |
343 | |
344 | return BLK_STS_OK; |
345 | } |
346 | |
347 | static int clariion_std_inquiry(struct scsi_device *sdev, |
348 | struct clariion_dh_data *csdev) |
349 | { |
350 | int err = SCSI_DH_OK; |
351 | char *sp_model; |
352 | |
353 | sp_model = parse_sp_model(sdev, buffer: sdev->inquiry); |
354 | if (!sp_model) { |
355 | err = SCSI_DH_DEV_UNSUPP; |
356 | goto out; |
357 | } |
358 | |
359 | /* |
360 | * FC Series arrays do not support long trespass |
361 | */ |
362 | if (!strlen(sp_model) || !strncmp(sp_model, "FC" ,2)) |
363 | csdev->flags |= CLARIION_SHORT_TRESPASS; |
364 | |
365 | sdev_printk(KERN_INFO, sdev, |
366 | "%s: detected Clariion %s, flags %x\n" , |
367 | CLARIION_NAME, sp_model, csdev->flags); |
368 | out: |
369 | return err; |
370 | } |
371 | |
372 | static int clariion_send_inquiry(struct scsi_device *sdev, |
373 | struct clariion_dh_data *csdev) |
374 | { |
375 | int err = SCSI_DH_IO; |
376 | |
377 | if (!scsi_get_vpd_page(sdev, page: 0xC0, buf: csdev->buffer, |
378 | CLARIION_BUFFER_SIZE)) |
379 | err = parse_sp_info_reply(sdev, csdev); |
380 | |
381 | return err; |
382 | } |
383 | |
384 | static int clariion_activate(struct scsi_device *sdev, |
385 | activate_complete fn, void *data) |
386 | { |
387 | struct clariion_dh_data *csdev = sdev->handler_data; |
388 | int result; |
389 | |
390 | result = clariion_send_inquiry(sdev, csdev); |
391 | if (result != SCSI_DH_OK) |
392 | goto done; |
393 | |
394 | if (csdev->lun_state == CLARIION_LUN_OWNED) |
395 | goto done; |
396 | |
397 | result = send_trespass_cmd(sdev, csdev); |
398 | if (result != SCSI_DH_OK) |
399 | goto done; |
400 | sdev_printk(KERN_INFO, sdev,"%s: %s trespass command sent\n" , |
401 | CLARIION_NAME, |
402 | csdev->flags&CLARIION_SHORT_TRESPASS?"short" :"long" ); |
403 | |
404 | /* Update status */ |
405 | result = clariion_send_inquiry(sdev, csdev); |
406 | if (result != SCSI_DH_OK) |
407 | goto done; |
408 | |
409 | done: |
410 | sdev_printk(KERN_INFO, sdev, |
411 | "%s: at SP %c Port %d (%s, default SP %c)\n" , |
412 | CLARIION_NAME, csdev->current_sp + 'A', |
413 | csdev->port, lun_state[csdev->lun_state], |
414 | csdev->default_sp + 'A'); |
415 | |
416 | if (fn) |
417 | fn(data, result); |
418 | return 0; |
419 | } |
420 | /* |
421 | * params - parameters in the following format |
422 | * "no_of_params\0param1\0param2\0param3\0...\0" |
423 | * for example, string for 2 parameters with value 10 and 21 |
424 | * is specified as "2\010\021\0". |
425 | */ |
426 | static int clariion_set_params(struct scsi_device *sdev, const char *params) |
427 | { |
428 | struct clariion_dh_data *csdev = sdev->handler_data; |
429 | unsigned int hr = 0, st = 0, argc; |
430 | const char *p = params; |
431 | int result = SCSI_DH_OK; |
432 | |
433 | if ((sscanf(params, "%u" , &argc) != 1) || (argc != 2)) |
434 | return -EINVAL; |
435 | |
436 | while (*p++) |
437 | ; |
438 | if ((sscanf(p, "%u" , &st) != 1) || (st > 1)) |
439 | return -EINVAL; |
440 | |
441 | while (*p++) |
442 | ; |
443 | if ((sscanf(p, "%u" , &hr) != 1) || (hr > 1)) |
444 | return -EINVAL; |
445 | |
446 | if (st) |
447 | csdev->flags |= CLARIION_SHORT_TRESPASS; |
448 | else |
449 | csdev->flags &= ~CLARIION_SHORT_TRESPASS; |
450 | |
451 | if (hr) |
452 | csdev->flags |= CLARIION_HONOR_RESERVATIONS; |
453 | else |
454 | csdev->flags &= ~CLARIION_HONOR_RESERVATIONS; |
455 | |
456 | /* |
457 | * If this path is owned, we have to send a trespass command |
458 | * with the new parameters. If not, simply return. Next trespass |
459 | * command would use the parameters. |
460 | */ |
461 | if (csdev->lun_state != CLARIION_LUN_OWNED) |
462 | goto done; |
463 | |
464 | csdev->lun_state = CLARIION_LUN_UNINITIALIZED; |
465 | result = send_trespass_cmd(sdev, csdev); |
466 | if (result != SCSI_DH_OK) |
467 | goto done; |
468 | |
469 | /* Update status */ |
470 | result = clariion_send_inquiry(sdev, csdev); |
471 | |
472 | done: |
473 | return result; |
474 | } |
475 | |
476 | static int clariion_bus_attach(struct scsi_device *sdev) |
477 | { |
478 | struct clariion_dh_data *h; |
479 | int err; |
480 | |
481 | h = kzalloc(size: sizeof(*h) , GFP_KERNEL); |
482 | if (!h) |
483 | return SCSI_DH_NOMEM; |
484 | h->lun_state = CLARIION_LUN_UNINITIALIZED; |
485 | h->default_sp = CLARIION_UNBOUND_LU; |
486 | h->current_sp = CLARIION_UNBOUND_LU; |
487 | |
488 | err = clariion_std_inquiry(sdev, csdev: h); |
489 | if (err != SCSI_DH_OK) |
490 | goto failed; |
491 | |
492 | err = clariion_send_inquiry(sdev, csdev: h); |
493 | if (err != SCSI_DH_OK) |
494 | goto failed; |
495 | |
496 | sdev_printk(KERN_INFO, sdev, |
497 | "%s: connected to SP %c Port %d (%s, default SP %c)\n" , |
498 | CLARIION_NAME, h->current_sp + 'A', |
499 | h->port, lun_state[h->lun_state], |
500 | h->default_sp + 'A'); |
501 | |
502 | sdev->handler_data = h; |
503 | return SCSI_DH_OK; |
504 | |
505 | failed: |
506 | kfree(objp: h); |
507 | return err; |
508 | } |
509 | |
510 | static void clariion_bus_detach(struct scsi_device *sdev) |
511 | { |
512 | kfree(objp: sdev->handler_data); |
513 | sdev->handler_data = NULL; |
514 | } |
515 | |
516 | static struct scsi_device_handler clariion_dh = { |
517 | .name = CLARIION_NAME, |
518 | .module = THIS_MODULE, |
519 | .attach = clariion_bus_attach, |
520 | .detach = clariion_bus_detach, |
521 | .check_sense = clariion_check_sense, |
522 | .activate = clariion_activate, |
523 | .prep_fn = clariion_prep_fn, |
524 | .set_params = clariion_set_params, |
525 | }; |
526 | |
527 | static int __init clariion_init(void) |
528 | { |
529 | int r; |
530 | |
531 | r = scsi_register_device_handler(scsi_dh: &clariion_dh); |
532 | if (r != 0) |
533 | printk(KERN_ERR "%s: Failed to register scsi device handler." , |
534 | CLARIION_NAME); |
535 | return r; |
536 | } |
537 | |
538 | static void __exit clariion_exit(void) |
539 | { |
540 | scsi_unregister_device_handler(scsi_dh: &clariion_dh); |
541 | } |
542 | |
543 | module_init(clariion_init); |
544 | module_exit(clariion_exit); |
545 | |
546 | MODULE_DESCRIPTION("EMC CX/AX/FC-family driver" ); |
547 | MODULE_AUTHOR("Mike Christie <michaelc@cs.wisc.edu>, Chandra Seetharaman <sekharan@us.ibm.com>" ); |
548 | MODULE_LICENSE("GPL" ); |
549 | |