1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * SCSI Media Changer device driver for Linux 2.6 |
4 | * |
5 | * (c) 1996-2003 Gerd Knorr <kraxel@bytesex.org> |
6 | * |
7 | */ |
8 | |
9 | #define VERSION "0.25" |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/init.h> |
13 | #include <linux/fs.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/mm.h> |
16 | #include <linux/major.h> |
17 | #include <linux/string.h> |
18 | #include <linux/errno.h> |
19 | #include <linux/interrupt.h> |
20 | #include <linux/blkdev.h> |
21 | #include <linux/completion.h> |
22 | #include <linux/compat.h> |
23 | #include <linux/chio.h> /* here are all the ioctls */ |
24 | #include <linux/mutex.h> |
25 | #include <linux/idr.h> |
26 | #include <linux/slab.h> |
27 | |
28 | #include <scsi/scsi.h> |
29 | #include <scsi/scsi_cmnd.h> |
30 | #include <scsi/scsi_driver.h> |
31 | #include <scsi/scsi_ioctl.h> |
32 | #include <scsi/scsi_host.h> |
33 | #include <scsi/scsi_device.h> |
34 | #include <scsi/scsi_eh.h> |
35 | #include <scsi/scsi_dbg.h> |
36 | |
37 | #define CH_DT_MAX 16 |
38 | #define CH_TYPES 8 |
39 | #define CH_MAX_DEVS 128 |
40 | |
41 | MODULE_DESCRIPTION("device driver for scsi media changer devices" ); |
42 | MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org>" ); |
43 | MODULE_LICENSE("GPL" ); |
44 | MODULE_ALIAS_CHARDEV_MAJOR(SCSI_CHANGER_MAJOR); |
45 | MODULE_ALIAS_SCSI_DEVICE(TYPE_MEDIUM_CHANGER); |
46 | |
47 | static int init = 1; |
48 | module_param(init, int, 0444); |
49 | MODULE_PARM_DESC(init, \ |
50 | "initialize element status on driver load (default: on)" ); |
51 | |
52 | static int timeout_move = 300; |
53 | module_param(timeout_move, int, 0644); |
54 | MODULE_PARM_DESC(timeout_move,"timeout for move commands " |
55 | "(default: 300 seconds)" ); |
56 | |
57 | static int timeout_init = 3600; |
58 | module_param(timeout_init, int, 0644); |
59 | MODULE_PARM_DESC(timeout_init,"timeout for INITIALIZE ELEMENT STATUS " |
60 | "(default: 3600 seconds)" ); |
61 | |
62 | static int verbose = 1; |
63 | module_param(verbose, int, 0644); |
64 | MODULE_PARM_DESC(verbose,"be verbose (default: on)" ); |
65 | |
66 | static int debug; |
67 | module_param(debug, int, 0644); |
68 | MODULE_PARM_DESC(debug,"enable/disable debug messages, also prints more " |
69 | "detailed sense codes on scsi errors (default: off)" ); |
70 | |
71 | static int dt_id[CH_DT_MAX] = { [ 0 ... (CH_DT_MAX-1) ] = -1 }; |
72 | static int dt_lun[CH_DT_MAX]; |
73 | module_param_array(dt_id, int, NULL, 0444); |
74 | module_param_array(dt_lun, int, NULL, 0444); |
75 | |
76 | /* tell the driver about vendor-specific slots */ |
77 | static int vendor_firsts[CH_TYPES-4]; |
78 | static int vendor_counts[CH_TYPES-4]; |
79 | module_param_array(vendor_firsts, int, NULL, 0444); |
80 | module_param_array(vendor_counts, int, NULL, 0444); |
81 | |
82 | static const char * vendor_labels[CH_TYPES-4] = { |
83 | "v0" , "v1" , "v2" , "v3" |
84 | }; |
85 | // module_param_string_array(vendor_labels, NULL, 0444); |
86 | |
87 | #define ch_printk(prefix, ch, fmt, a...) \ |
88 | sdev_prefix_printk(prefix, (ch)->device, (ch)->name, fmt, ##a) |
89 | |
90 | #define DPRINTK(fmt, arg...) \ |
91 | do { \ |
92 | if (debug) \ |
93 | ch_printk(KERN_DEBUG, ch, fmt, ##arg); \ |
94 | } while (0) |
95 | #define VPRINTK(level, fmt, arg...) \ |
96 | do { \ |
97 | if (verbose) \ |
98 | ch_printk(level, ch, fmt, ##arg); \ |
99 | } while (0) |
100 | |
101 | /* ------------------------------------------------------------------- */ |
102 | |
103 | #define MAX_RETRIES 1 |
104 | |
105 | static const struct class ch_sysfs_class = { |
106 | .name = "scsi_changer" , |
107 | }; |
108 | |
109 | typedef struct { |
110 | struct kref ref; |
111 | struct list_head list; |
112 | int minor; |
113 | char name[8]; |
114 | struct scsi_device *device; |
115 | struct scsi_device **dt; /* ptrs to data transfer elements */ |
116 | u_int firsts[CH_TYPES]; |
117 | u_int counts[CH_TYPES]; |
118 | u_int voltags; |
119 | struct mutex lock; |
120 | } scsi_changer; |
121 | |
122 | static DEFINE_IDR(ch_index_idr); |
123 | static DEFINE_SPINLOCK(ch_index_lock); |
124 | |
125 | static const struct { |
126 | unsigned char sense; |
127 | unsigned char asc; |
128 | unsigned char ascq; |
129 | int errno; |
130 | } ch_err[] = { |
131 | /* Just filled in what looks right. Hav'nt checked any standard paper for |
132 | these errno assignments, so they may be wrong... */ |
133 | { |
134 | .sense = ILLEGAL_REQUEST, |
135 | .asc = 0x21, |
136 | .ascq = 0x01, |
137 | .errno = EBADSLT, /* Invalid element address */ |
138 | },{ |
139 | .sense = ILLEGAL_REQUEST, |
140 | .asc = 0x28, |
141 | .ascq = 0x01, |
142 | .errno = EBADE, /* Import or export element accessed */ |
143 | },{ |
144 | .sense = ILLEGAL_REQUEST, |
145 | .asc = 0x3B, |
146 | .ascq = 0x0D, |
147 | .errno = EXFULL, /* Medium destination element full */ |
148 | },{ |
149 | .sense = ILLEGAL_REQUEST, |
150 | .asc = 0x3B, |
151 | .ascq = 0x0E, |
152 | .errno = EBADE, /* Medium source element empty */ |
153 | },{ |
154 | .sense = ILLEGAL_REQUEST, |
155 | .asc = 0x20, |
156 | .ascq = 0x00, |
157 | .errno = EBADRQC, /* Invalid command operation code */ |
158 | },{ |
159 | /* end of list */ |
160 | } |
161 | }; |
162 | |
163 | /* ------------------------------------------------------------------- */ |
164 | |
165 | static int ch_find_errno(struct scsi_sense_hdr *sshdr) |
166 | { |
167 | int i,errno = 0; |
168 | |
169 | /* Check to see if additional sense information is available */ |
170 | if (scsi_sense_valid(sshdr) && |
171 | sshdr->asc != 0) { |
172 | for (i = 0; ch_err[i].errno != 0; i++) { |
173 | if (ch_err[i].sense == sshdr->sense_key && |
174 | ch_err[i].asc == sshdr->asc && |
175 | ch_err[i].ascq == sshdr->ascq) { |
176 | errno = -ch_err[i].errno; |
177 | break; |
178 | } |
179 | } |
180 | } |
181 | if (errno == 0) |
182 | errno = -EIO; |
183 | return errno; |
184 | } |
185 | |
186 | static int |
187 | ch_do_scsi(scsi_changer *ch, unsigned char *cmd, int cmd_len, |
188 | void *buffer, unsigned int buflength, enum req_op op) |
189 | { |
190 | int errno = 0, timeout, result; |
191 | struct scsi_sense_hdr sshdr; |
192 | struct scsi_failure failure_defs[] = { |
193 | { |
194 | .sense = UNIT_ATTENTION, |
195 | .asc = SCMD_FAILURE_ASC_ANY, |
196 | .ascq = SCMD_FAILURE_ASCQ_ANY, |
197 | .allowed = 3, |
198 | .result = SAM_STAT_CHECK_CONDITION, |
199 | }, |
200 | {} |
201 | }; |
202 | struct scsi_failures failures = { |
203 | .failure_definitions = failure_defs, |
204 | }; |
205 | const struct scsi_exec_args exec_args = { |
206 | .sshdr = &sshdr, |
207 | .failures = &failures, |
208 | }; |
209 | |
210 | timeout = (cmd[0] == INITIALIZE_ELEMENT_STATUS) |
211 | ? timeout_init : timeout_move; |
212 | |
213 | result = scsi_execute_cmd(sdev: ch->device, cmd, opf: op, buffer, bufflen: buflength, |
214 | timeout: timeout * HZ, MAX_RETRIES, args: &exec_args); |
215 | if (result < 0) |
216 | return result; |
217 | if (scsi_sense_valid(sshdr: &sshdr)) { |
218 | if (debug) |
219 | scsi_print_sense_hdr(ch->device, ch->name, &sshdr); |
220 | errno = ch_find_errno(sshdr: &sshdr); |
221 | } |
222 | return errno; |
223 | } |
224 | |
225 | /* ------------------------------------------------------------------------ */ |
226 | |
227 | static int |
228 | ch_elem_to_typecode(scsi_changer *ch, u_int elem) |
229 | { |
230 | int i; |
231 | |
232 | for (i = 0; i < CH_TYPES; i++) { |
233 | if (elem >= ch->firsts[i] && |
234 | elem < ch->firsts[i] + |
235 | ch->counts[i]) |
236 | return i+1; |
237 | } |
238 | return 0; |
239 | } |
240 | |
241 | static int |
242 | ch_read_element_status(scsi_changer *ch, u_int elem, char *data) |
243 | { |
244 | u_char cmd[12]; |
245 | u_char *buffer; |
246 | int result; |
247 | |
248 | buffer = kmalloc(size: 512, GFP_KERNEL); |
249 | if(!buffer) |
250 | return -ENOMEM; |
251 | |
252 | retry: |
253 | memset(cmd,0,sizeof(cmd)); |
254 | cmd[0] = READ_ELEMENT_STATUS; |
255 | cmd[1] = ((ch->device->lun & 0x7) << 5) | |
256 | (ch->voltags ? 0x10 : 0) | |
257 | ch_elem_to_typecode(ch,elem); |
258 | cmd[2] = (elem >> 8) & 0xff; |
259 | cmd[3] = elem & 0xff; |
260 | cmd[5] = 1; |
261 | cmd[9] = 255; |
262 | if (0 == (result = ch_do_scsi(ch, cmd, cmd_len: 12, |
263 | buffer, buflength: 256, op: REQ_OP_DRV_IN))) { |
264 | if (((buffer[16] << 8) | buffer[17]) != elem) { |
265 | DPRINTK("asked for element 0x%02x, got 0x%02x\n" , |
266 | elem,(buffer[16] << 8) | buffer[17]); |
267 | kfree(objp: buffer); |
268 | return -EIO; |
269 | } |
270 | memcpy(data,buffer+16,16); |
271 | } else { |
272 | if (ch->voltags) { |
273 | ch->voltags = 0; |
274 | VPRINTK(KERN_INFO, "device has no volume tag support\n" ); |
275 | goto retry; |
276 | } |
277 | DPRINTK("READ ELEMENT STATUS for element 0x%x failed\n" ,elem); |
278 | } |
279 | kfree(objp: buffer); |
280 | return result; |
281 | } |
282 | |
283 | static int |
284 | ch_init_elem(scsi_changer *ch) |
285 | { |
286 | int err; |
287 | u_char cmd[6]; |
288 | |
289 | VPRINTK(KERN_INFO, "INITIALIZE ELEMENT STATUS, may take some time ...\n" ); |
290 | memset(cmd,0,sizeof(cmd)); |
291 | cmd[0] = INITIALIZE_ELEMENT_STATUS; |
292 | cmd[1] = (ch->device->lun & 0x7) << 5; |
293 | err = ch_do_scsi(ch, cmd, cmd_len: 6, NULL, buflength: 0, op: REQ_OP_DRV_IN); |
294 | VPRINTK(KERN_INFO, "... finished\n" ); |
295 | return err; |
296 | } |
297 | |
298 | static int |
299 | ch_readconfig(scsi_changer *ch) |
300 | { |
301 | u_char cmd[10], data[16]; |
302 | u_char *buffer; |
303 | int result,id,lun,i; |
304 | u_int elem; |
305 | |
306 | buffer = kzalloc(size: 512, GFP_KERNEL); |
307 | if (!buffer) |
308 | return -ENOMEM; |
309 | |
310 | memset(cmd,0,sizeof(cmd)); |
311 | cmd[0] = MODE_SENSE; |
312 | cmd[1] = (ch->device->lun & 0x7) << 5; |
313 | cmd[2] = 0x1d; |
314 | cmd[4] = 255; |
315 | result = ch_do_scsi(ch, cmd, cmd_len: 10, buffer, buflength: 255, op: REQ_OP_DRV_IN); |
316 | if (0 != result) { |
317 | cmd[1] |= (1<<3); |
318 | result = ch_do_scsi(ch, cmd, cmd_len: 10, buffer, buflength: 255, op: REQ_OP_DRV_IN); |
319 | } |
320 | if (0 == result) { |
321 | ch->firsts[CHET_MT] = |
322 | (buffer[buffer[3]+ 6] << 8) | buffer[buffer[3]+ 7]; |
323 | ch->counts[CHET_MT] = |
324 | (buffer[buffer[3]+ 8] << 8) | buffer[buffer[3]+ 9]; |
325 | ch->firsts[CHET_ST] = |
326 | (buffer[buffer[3]+10] << 8) | buffer[buffer[3]+11]; |
327 | ch->counts[CHET_ST] = |
328 | (buffer[buffer[3]+12] << 8) | buffer[buffer[3]+13]; |
329 | ch->firsts[CHET_IE] = |
330 | (buffer[buffer[3]+14] << 8) | buffer[buffer[3]+15]; |
331 | ch->counts[CHET_IE] = |
332 | (buffer[buffer[3]+16] << 8) | buffer[buffer[3]+17]; |
333 | ch->firsts[CHET_DT] = |
334 | (buffer[buffer[3]+18] << 8) | buffer[buffer[3]+19]; |
335 | ch->counts[CHET_DT] = |
336 | (buffer[buffer[3]+20] << 8) | buffer[buffer[3]+21]; |
337 | VPRINTK(KERN_INFO, "type #1 (mt): 0x%x+%d [medium transport]\n" , |
338 | ch->firsts[CHET_MT], |
339 | ch->counts[CHET_MT]); |
340 | VPRINTK(KERN_INFO, "type #2 (st): 0x%x+%d [storage]\n" , |
341 | ch->firsts[CHET_ST], |
342 | ch->counts[CHET_ST]); |
343 | VPRINTK(KERN_INFO, "type #3 (ie): 0x%x+%d [import/export]\n" , |
344 | ch->firsts[CHET_IE], |
345 | ch->counts[CHET_IE]); |
346 | VPRINTK(KERN_INFO, "type #4 (dt): 0x%x+%d [data transfer]\n" , |
347 | ch->firsts[CHET_DT], |
348 | ch->counts[CHET_DT]); |
349 | } else { |
350 | VPRINTK(KERN_INFO, "reading element address assignment page failed!\n" ); |
351 | } |
352 | |
353 | /* vendor specific element types */ |
354 | for (i = 0; i < 4; i++) { |
355 | if (0 == vendor_counts[i]) |
356 | continue; |
357 | if (NULL == vendor_labels[i]) |
358 | continue; |
359 | ch->firsts[CHET_V1+i] = vendor_firsts[i]; |
360 | ch->counts[CHET_V1+i] = vendor_counts[i]; |
361 | VPRINTK(KERN_INFO, "type #%d (v%d): 0x%x+%d [%s, vendor specific]\n" , |
362 | i+5,i+1,vendor_firsts[i],vendor_counts[i], |
363 | vendor_labels[i]); |
364 | } |
365 | |
366 | /* look up the devices of the data transfer elements */ |
367 | ch->dt = kcalloc(n: ch->counts[CHET_DT], size: sizeof(*ch->dt), |
368 | GFP_KERNEL); |
369 | |
370 | if (!ch->dt) { |
371 | kfree(objp: buffer); |
372 | return -ENOMEM; |
373 | } |
374 | |
375 | for (elem = 0; elem < ch->counts[CHET_DT]; elem++) { |
376 | id = -1; |
377 | lun = 0; |
378 | if (elem < CH_DT_MAX && -1 != dt_id[elem]) { |
379 | id = dt_id[elem]; |
380 | lun = dt_lun[elem]; |
381 | VPRINTK(KERN_INFO, "dt 0x%x: [insmod option] " , |
382 | elem+ch->firsts[CHET_DT]); |
383 | } else if (0 != ch_read_element_status |
384 | (ch,elem: elem+ch->firsts[CHET_DT],data)) { |
385 | VPRINTK(KERN_INFO, "dt 0x%x: READ ELEMENT STATUS failed\n" , |
386 | elem+ch->firsts[CHET_DT]); |
387 | } else { |
388 | VPRINTK(KERN_INFO, "dt 0x%x: " ,elem+ch->firsts[CHET_DT]); |
389 | if (data[6] & 0x80) { |
390 | VPRINTK(KERN_CONT, "not this SCSI bus\n" ); |
391 | ch->dt[elem] = NULL; |
392 | } else if (0 == (data[6] & 0x30)) { |
393 | VPRINTK(KERN_CONT, "ID/LUN unknown\n" ); |
394 | ch->dt[elem] = NULL; |
395 | } else { |
396 | id = ch->device->id; |
397 | lun = 0; |
398 | if (data[6] & 0x20) id = data[7]; |
399 | if (data[6] & 0x10) lun = data[6] & 7; |
400 | } |
401 | } |
402 | if (-1 != id) { |
403 | VPRINTK(KERN_CONT, "ID %i, LUN %i, " ,id,lun); |
404 | ch->dt[elem] = |
405 | scsi_device_lookup(ch->device->host, |
406 | ch->device->channel, |
407 | id,lun); |
408 | if (!ch->dt[elem]) { |
409 | /* should not happen */ |
410 | VPRINTK(KERN_CONT, "Huh? device not found!\n" ); |
411 | } else { |
412 | VPRINTK(KERN_CONT, "name: %8.8s %16.16s %4.4s\n" , |
413 | ch->dt[elem]->vendor, |
414 | ch->dt[elem]->model, |
415 | ch->dt[elem]->rev); |
416 | } |
417 | } |
418 | } |
419 | ch->voltags = 1; |
420 | kfree(objp: buffer); |
421 | |
422 | return 0; |
423 | } |
424 | |
425 | /* ------------------------------------------------------------------------ */ |
426 | |
427 | static int |
428 | ch_position(scsi_changer *ch, u_int trans, u_int elem, int rotate) |
429 | { |
430 | u_char cmd[10]; |
431 | |
432 | DPRINTK("position: 0x%x\n" ,elem); |
433 | if (0 == trans) |
434 | trans = ch->firsts[CHET_MT]; |
435 | memset(cmd,0,sizeof(cmd)); |
436 | cmd[0] = POSITION_TO_ELEMENT; |
437 | cmd[1] = (ch->device->lun & 0x7) << 5; |
438 | cmd[2] = (trans >> 8) & 0xff; |
439 | cmd[3] = trans & 0xff; |
440 | cmd[4] = (elem >> 8) & 0xff; |
441 | cmd[5] = elem & 0xff; |
442 | cmd[8] = rotate ? 1 : 0; |
443 | return ch_do_scsi(ch, cmd, cmd_len: 10, NULL, buflength: 0, op: REQ_OP_DRV_IN); |
444 | } |
445 | |
446 | static int |
447 | ch_move(scsi_changer *ch, u_int trans, u_int src, u_int dest, int rotate) |
448 | { |
449 | u_char cmd[12]; |
450 | |
451 | DPRINTK("move: 0x%x => 0x%x\n" ,src,dest); |
452 | if (0 == trans) |
453 | trans = ch->firsts[CHET_MT]; |
454 | memset(cmd,0,sizeof(cmd)); |
455 | cmd[0] = MOVE_MEDIUM; |
456 | cmd[1] = (ch->device->lun & 0x7) << 5; |
457 | cmd[2] = (trans >> 8) & 0xff; |
458 | cmd[3] = trans & 0xff; |
459 | cmd[4] = (src >> 8) & 0xff; |
460 | cmd[5] = src & 0xff; |
461 | cmd[6] = (dest >> 8) & 0xff; |
462 | cmd[7] = dest & 0xff; |
463 | cmd[10] = rotate ? 1 : 0; |
464 | return ch_do_scsi(ch, cmd, cmd_len: 12, NULL, buflength: 0, op: REQ_OP_DRV_IN); |
465 | } |
466 | |
467 | static int |
468 | ch_exchange(scsi_changer *ch, u_int trans, u_int src, |
469 | u_int dest1, u_int dest2, int rotate1, int rotate2) |
470 | { |
471 | u_char cmd[12]; |
472 | |
473 | DPRINTK("exchange: 0x%x => 0x%x => 0x%x\n" , |
474 | src,dest1,dest2); |
475 | if (0 == trans) |
476 | trans = ch->firsts[CHET_MT]; |
477 | memset(cmd,0,sizeof(cmd)); |
478 | cmd[0] = EXCHANGE_MEDIUM; |
479 | cmd[1] = (ch->device->lun & 0x7) << 5; |
480 | cmd[2] = (trans >> 8) & 0xff; |
481 | cmd[3] = trans & 0xff; |
482 | cmd[4] = (src >> 8) & 0xff; |
483 | cmd[5] = src & 0xff; |
484 | cmd[6] = (dest1 >> 8) & 0xff; |
485 | cmd[7] = dest1 & 0xff; |
486 | cmd[8] = (dest2 >> 8) & 0xff; |
487 | cmd[9] = dest2 & 0xff; |
488 | cmd[10] = (rotate1 ? 1 : 0) | (rotate2 ? 2 : 0); |
489 | |
490 | return ch_do_scsi(ch, cmd, cmd_len: 12, NULL, buflength: 0, op: REQ_OP_DRV_IN); |
491 | } |
492 | |
493 | static void |
494 | ch_check_voltag(char *tag) |
495 | { |
496 | int i; |
497 | |
498 | for (i = 0; i < 32; i++) { |
499 | /* restrict to ascii */ |
500 | if (tag[i] >= 0x7f || tag[i] < 0x20) |
501 | tag[i] = ' '; |
502 | /* don't allow search wildcards */ |
503 | if (tag[i] == '?' || |
504 | tag[i] == '*') |
505 | tag[i] = ' '; |
506 | } |
507 | } |
508 | |
509 | static int |
510 | ch_set_voltag(scsi_changer *ch, u_int elem, |
511 | int alternate, int clear, u_char *tag) |
512 | { |
513 | u_char cmd[12]; |
514 | u_char *buffer; |
515 | int result; |
516 | |
517 | buffer = kzalloc(size: 512, GFP_KERNEL); |
518 | if (!buffer) |
519 | return -ENOMEM; |
520 | |
521 | DPRINTK("%s %s voltag: 0x%x => \"%s\"\n" , |
522 | clear ? "clear" : "set" , |
523 | alternate ? "alternate" : "primary" , |
524 | elem, tag); |
525 | memset(cmd,0,sizeof(cmd)); |
526 | cmd[0] = SEND_VOLUME_TAG; |
527 | cmd[1] = ((ch->device->lun & 0x7) << 5) | |
528 | ch_elem_to_typecode(ch,elem); |
529 | cmd[2] = (elem >> 8) & 0xff; |
530 | cmd[3] = elem & 0xff; |
531 | cmd[5] = clear |
532 | ? (alternate ? 0x0d : 0x0c) |
533 | : (alternate ? 0x0b : 0x0a); |
534 | |
535 | cmd[9] = 255; |
536 | |
537 | memcpy(buffer,tag,32); |
538 | ch_check_voltag(tag: buffer); |
539 | |
540 | result = ch_do_scsi(ch, cmd, cmd_len: 12, buffer, buflength: 256, op: REQ_OP_DRV_OUT); |
541 | kfree(objp: buffer); |
542 | return result; |
543 | } |
544 | |
545 | static int ch_gstatus(scsi_changer *ch, int type, unsigned char __user *dest) |
546 | { |
547 | int retval = 0; |
548 | u_char data[16]; |
549 | unsigned int i; |
550 | |
551 | mutex_lock(&ch->lock); |
552 | for (i = 0; i < ch->counts[type]; i++) { |
553 | if (0 != ch_read_element_status |
554 | (ch, elem: ch->firsts[type]+i,data)) { |
555 | retval = -EIO; |
556 | break; |
557 | } |
558 | put_user(data[2], dest+i); |
559 | if (data[2] & CESTATUS_EXCEPT) |
560 | VPRINTK(KERN_INFO, "element 0x%x: asc=0x%x, ascq=0x%x\n" , |
561 | ch->firsts[type]+i, |
562 | (int)data[4],(int)data[5]); |
563 | retval = ch_read_element_status |
564 | (ch, elem: ch->firsts[type]+i,data); |
565 | if (0 != retval) |
566 | break; |
567 | } |
568 | mutex_unlock(lock: &ch->lock); |
569 | return retval; |
570 | } |
571 | |
572 | /* ------------------------------------------------------------------------ */ |
573 | |
574 | static void ch_destroy(struct kref *ref) |
575 | { |
576 | scsi_changer *ch = container_of(ref, scsi_changer, ref); |
577 | |
578 | ch->device = NULL; |
579 | kfree(objp: ch->dt); |
580 | kfree(objp: ch); |
581 | } |
582 | |
583 | static int |
584 | ch_release(struct inode *inode, struct file *file) |
585 | { |
586 | scsi_changer *ch = file->private_data; |
587 | |
588 | scsi_device_put(ch->device); |
589 | file->private_data = NULL; |
590 | kref_put(kref: &ch->ref, release: ch_destroy); |
591 | return 0; |
592 | } |
593 | |
594 | static int |
595 | ch_open(struct inode *inode, struct file *file) |
596 | { |
597 | scsi_changer *ch; |
598 | int minor = iminor(inode); |
599 | |
600 | spin_lock(lock: &ch_index_lock); |
601 | ch = idr_find(&ch_index_idr, id: minor); |
602 | |
603 | if (ch == NULL || !kref_get_unless_zero(kref: &ch->ref)) { |
604 | spin_unlock(lock: &ch_index_lock); |
605 | return -ENXIO; |
606 | } |
607 | spin_unlock(lock: &ch_index_lock); |
608 | if (scsi_device_get(ch->device)) { |
609 | kref_put(kref: &ch->ref, release: ch_destroy); |
610 | return -ENXIO; |
611 | } |
612 | /* Synchronize with ch_probe() */ |
613 | mutex_lock(&ch->lock); |
614 | file->private_data = ch; |
615 | mutex_unlock(lock: &ch->lock); |
616 | return 0; |
617 | } |
618 | |
619 | static int |
620 | ch_checkrange(scsi_changer *ch, unsigned int type, unsigned int unit) |
621 | { |
622 | if (type >= CH_TYPES || unit >= ch->counts[type]) |
623 | return -1; |
624 | return 0; |
625 | } |
626 | |
627 | struct changer_element_status32 { |
628 | int ces_type; |
629 | compat_uptr_t ces_data; |
630 | }; |
631 | #define CHIOGSTATUS32 _IOW('c', 8, struct changer_element_status32) |
632 | |
633 | static long ch_ioctl(struct file *file, |
634 | unsigned int cmd, unsigned long arg) |
635 | { |
636 | scsi_changer *ch = file->private_data; |
637 | int retval; |
638 | void __user *argp = (void __user *)arg; |
639 | |
640 | retval = scsi_ioctl_block_when_processing_errors(sdev: ch->device, cmd, |
641 | ndelay: file->f_flags & O_NDELAY); |
642 | if (retval) |
643 | return retval; |
644 | |
645 | switch (cmd) { |
646 | case CHIOGPARAMS: |
647 | { |
648 | struct changer_params params; |
649 | |
650 | params.cp_curpicker = 0; |
651 | params.cp_npickers = ch->counts[CHET_MT]; |
652 | params.cp_nslots = ch->counts[CHET_ST]; |
653 | params.cp_nportals = ch->counts[CHET_IE]; |
654 | params.cp_ndrives = ch->counts[CHET_DT]; |
655 | |
656 | if (copy_to_user(to: argp, from: ¶ms, n: sizeof(params))) |
657 | return -EFAULT; |
658 | return 0; |
659 | } |
660 | case CHIOGVPARAMS: |
661 | { |
662 | struct changer_vendor_params vparams; |
663 | |
664 | memset(&vparams,0,sizeof(vparams)); |
665 | if (ch->counts[CHET_V1]) { |
666 | vparams.cvp_n1 = ch->counts[CHET_V1]; |
667 | strscpy(vparams.cvp_label1, vendor_labels[0], |
668 | sizeof(vparams.cvp_label1)); |
669 | } |
670 | if (ch->counts[CHET_V2]) { |
671 | vparams.cvp_n2 = ch->counts[CHET_V2]; |
672 | strscpy(vparams.cvp_label2, vendor_labels[1], |
673 | sizeof(vparams.cvp_label2)); |
674 | } |
675 | if (ch->counts[CHET_V3]) { |
676 | vparams.cvp_n3 = ch->counts[CHET_V3]; |
677 | strscpy(vparams.cvp_label3, vendor_labels[2], |
678 | sizeof(vparams.cvp_label3)); |
679 | } |
680 | if (ch->counts[CHET_V4]) { |
681 | vparams.cvp_n4 = ch->counts[CHET_V4]; |
682 | strscpy(vparams.cvp_label4, vendor_labels[3], |
683 | sizeof(vparams.cvp_label4)); |
684 | } |
685 | if (copy_to_user(to: argp, from: &vparams, n: sizeof(vparams))) |
686 | return -EFAULT; |
687 | return 0; |
688 | } |
689 | |
690 | case CHIOPOSITION: |
691 | { |
692 | struct changer_position pos; |
693 | |
694 | if (copy_from_user(to: &pos, from: argp, n: sizeof (pos))) |
695 | return -EFAULT; |
696 | |
697 | if (0 != ch_checkrange(ch, type: pos.cp_type, unit: pos.cp_unit)) { |
698 | DPRINTK("CHIOPOSITION: invalid parameter\n" ); |
699 | return -EBADSLT; |
700 | } |
701 | mutex_lock(&ch->lock); |
702 | retval = ch_position(ch,trans: 0, |
703 | elem: ch->firsts[pos.cp_type] + pos.cp_unit, |
704 | rotate: pos.cp_flags & CP_INVERT); |
705 | mutex_unlock(lock: &ch->lock); |
706 | return retval; |
707 | } |
708 | |
709 | case CHIOMOVE: |
710 | { |
711 | struct changer_move mv; |
712 | |
713 | if (copy_from_user(to: &mv, from: argp, n: sizeof (mv))) |
714 | return -EFAULT; |
715 | |
716 | if (0 != ch_checkrange(ch, type: mv.cm_fromtype, unit: mv.cm_fromunit) || |
717 | 0 != ch_checkrange(ch, type: mv.cm_totype, unit: mv.cm_tounit )) { |
718 | DPRINTK("CHIOMOVE: invalid parameter\n" ); |
719 | return -EBADSLT; |
720 | } |
721 | |
722 | mutex_lock(&ch->lock); |
723 | retval = ch_move(ch,trans: 0, |
724 | src: ch->firsts[mv.cm_fromtype] + mv.cm_fromunit, |
725 | dest: ch->firsts[mv.cm_totype] + mv.cm_tounit, |
726 | rotate: mv.cm_flags & CM_INVERT); |
727 | mutex_unlock(lock: &ch->lock); |
728 | return retval; |
729 | } |
730 | |
731 | case CHIOEXCHANGE: |
732 | { |
733 | struct changer_exchange mv; |
734 | |
735 | if (copy_from_user(to: &mv, from: argp, n: sizeof (mv))) |
736 | return -EFAULT; |
737 | |
738 | if (0 != ch_checkrange(ch, type: mv.ce_srctype, unit: mv.ce_srcunit ) || |
739 | 0 != ch_checkrange(ch, type: mv.ce_fdsttype, unit: mv.ce_fdstunit) || |
740 | 0 != ch_checkrange(ch, type: mv.ce_sdsttype, unit: mv.ce_sdstunit)) { |
741 | DPRINTK("CHIOEXCHANGE: invalid parameter\n" ); |
742 | return -EBADSLT; |
743 | } |
744 | |
745 | mutex_lock(&ch->lock); |
746 | retval = ch_exchange |
747 | (ch,trans: 0, |
748 | src: ch->firsts[mv.ce_srctype] + mv.ce_srcunit, |
749 | dest1: ch->firsts[mv.ce_fdsttype] + mv.ce_fdstunit, |
750 | dest2: ch->firsts[mv.ce_sdsttype] + mv.ce_sdstunit, |
751 | rotate1: mv.ce_flags & CE_INVERT1, rotate2: mv.ce_flags & CE_INVERT2); |
752 | mutex_unlock(lock: &ch->lock); |
753 | return retval; |
754 | } |
755 | |
756 | case CHIOGSTATUS: |
757 | { |
758 | struct changer_element_status ces; |
759 | |
760 | if (copy_from_user(to: &ces, from: argp, n: sizeof (ces))) |
761 | return -EFAULT; |
762 | if (ces.ces_type < 0 || ces.ces_type >= CH_TYPES) |
763 | return -EINVAL; |
764 | |
765 | return ch_gstatus(ch, type: ces.ces_type, dest: ces.ces_data); |
766 | } |
767 | #ifdef CONFIG_COMPAT |
768 | case CHIOGSTATUS32: |
769 | { |
770 | struct changer_element_status32 ces32; |
771 | |
772 | if (copy_from_user(to: &ces32, from: argp, n: sizeof(ces32))) |
773 | return -EFAULT; |
774 | if (ces32.ces_type < 0 || ces32.ces_type >= CH_TYPES) |
775 | return -EINVAL; |
776 | |
777 | return ch_gstatus(ch, type: ces32.ces_type, |
778 | dest: compat_ptr(uptr: ces32.ces_data)); |
779 | } |
780 | #endif |
781 | case CHIOGELEM: |
782 | { |
783 | struct changer_get_element cge; |
784 | u_char ch_cmd[12]; |
785 | u_char *buffer; |
786 | unsigned int elem; |
787 | int result,i; |
788 | |
789 | if (copy_from_user(to: &cge, from: argp, n: sizeof (cge))) |
790 | return -EFAULT; |
791 | |
792 | if (0 != ch_checkrange(ch, type: cge.cge_type, unit: cge.cge_unit)) |
793 | return -EINVAL; |
794 | elem = ch->firsts[cge.cge_type] + cge.cge_unit; |
795 | |
796 | buffer = kmalloc(size: 512, GFP_KERNEL); |
797 | if (!buffer) |
798 | return -ENOMEM; |
799 | mutex_lock(&ch->lock); |
800 | |
801 | voltag_retry: |
802 | memset(ch_cmd, 0, sizeof(ch_cmd)); |
803 | ch_cmd[0] = READ_ELEMENT_STATUS; |
804 | ch_cmd[1] = ((ch->device->lun & 0x7) << 5) | |
805 | (ch->voltags ? 0x10 : 0) | |
806 | ch_elem_to_typecode(ch,elem); |
807 | ch_cmd[2] = (elem >> 8) & 0xff; |
808 | ch_cmd[3] = elem & 0xff; |
809 | ch_cmd[5] = 1; |
810 | ch_cmd[9] = 255; |
811 | |
812 | result = ch_do_scsi(ch, cmd: ch_cmd, cmd_len: 12, buffer, buflength: 256, op: REQ_OP_DRV_IN); |
813 | if (!result) { |
814 | cge.cge_status = buffer[18]; |
815 | cge.cge_flags = 0; |
816 | if (buffer[18] & CESTATUS_EXCEPT) { |
817 | cge.cge_errno = EIO; |
818 | } |
819 | if (buffer[25] & 0x80) { |
820 | cge.cge_flags |= CGE_SRC; |
821 | if (buffer[25] & 0x40) |
822 | cge.cge_flags |= CGE_INVERT; |
823 | elem = (buffer[26]<<8) | buffer[27]; |
824 | for (i = 0; i < 4; i++) { |
825 | if (elem >= ch->firsts[i] && |
826 | elem < ch->firsts[i] + ch->counts[i]) { |
827 | cge.cge_srctype = i; |
828 | cge.cge_srcunit = elem-ch->firsts[i]; |
829 | } |
830 | } |
831 | } |
832 | if ((buffer[22] & 0x30) == 0x30) { |
833 | cge.cge_flags |= CGE_IDLUN; |
834 | cge.cge_id = buffer[23]; |
835 | cge.cge_lun = buffer[22] & 7; |
836 | } |
837 | if (buffer[9] & 0x80) { |
838 | cge.cge_flags |= CGE_PVOLTAG; |
839 | memcpy(cge.cge_pvoltag,buffer+28,36); |
840 | } |
841 | if (buffer[9] & 0x40) { |
842 | cge.cge_flags |= CGE_AVOLTAG; |
843 | memcpy(cge.cge_avoltag,buffer+64,36); |
844 | } |
845 | } else if (ch->voltags) { |
846 | ch->voltags = 0; |
847 | VPRINTK(KERN_INFO, "device has no volume tag support\n" ); |
848 | goto voltag_retry; |
849 | } |
850 | kfree(objp: buffer); |
851 | mutex_unlock(lock: &ch->lock); |
852 | |
853 | if (copy_to_user(to: argp, from: &cge, n: sizeof (cge))) |
854 | return -EFAULT; |
855 | return result; |
856 | } |
857 | |
858 | case CHIOINITELEM: |
859 | { |
860 | mutex_lock(&ch->lock); |
861 | retval = ch_init_elem(ch); |
862 | mutex_unlock(lock: &ch->lock); |
863 | return retval; |
864 | } |
865 | |
866 | case CHIOSVOLTAG: |
867 | { |
868 | struct changer_set_voltag csv; |
869 | int elem; |
870 | |
871 | if (copy_from_user(to: &csv, from: argp, n: sizeof(csv))) |
872 | return -EFAULT; |
873 | |
874 | if (0 != ch_checkrange(ch, type: csv.csv_type, unit: csv.csv_unit)) { |
875 | DPRINTK("CHIOSVOLTAG: invalid parameter\n" ); |
876 | return -EBADSLT; |
877 | } |
878 | elem = ch->firsts[csv.csv_type] + csv.csv_unit; |
879 | mutex_lock(&ch->lock); |
880 | retval = ch_set_voltag(ch, elem, |
881 | alternate: csv.csv_flags & CSV_AVOLTAG, |
882 | clear: csv.csv_flags & CSV_CLEARTAG, |
883 | tag: csv.csv_voltag); |
884 | mutex_unlock(lock: &ch->lock); |
885 | return retval; |
886 | } |
887 | |
888 | default: |
889 | return scsi_ioctl(sdev: ch->device, open_for_write: file->f_mode & FMODE_WRITE, cmd, |
890 | arg: argp); |
891 | |
892 | } |
893 | } |
894 | |
895 | /* ------------------------------------------------------------------------ */ |
896 | |
897 | static int ch_probe(struct device *dev) |
898 | { |
899 | struct scsi_device *sd = to_scsi_device(dev); |
900 | struct device *class_dev; |
901 | int ret; |
902 | scsi_changer *ch; |
903 | |
904 | if (sd->type != TYPE_MEDIUM_CHANGER) |
905 | return -ENODEV; |
906 | |
907 | ch = kzalloc(size: sizeof(*ch), GFP_KERNEL); |
908 | if (NULL == ch) |
909 | return -ENOMEM; |
910 | |
911 | idr_preload(GFP_KERNEL); |
912 | spin_lock(lock: &ch_index_lock); |
913 | ret = idr_alloc(&ch_index_idr, ptr: ch, start: 0, CH_MAX_DEVS + 1, GFP_NOWAIT); |
914 | spin_unlock(lock: &ch_index_lock); |
915 | idr_preload_end(); |
916 | |
917 | if (ret < 0) { |
918 | if (ret == -ENOSPC) |
919 | ret = -ENODEV; |
920 | goto free_ch; |
921 | } |
922 | |
923 | ch->minor = ret; |
924 | sprintf(buf: ch->name,fmt: "ch%d" ,ch->minor); |
925 | ret = scsi_device_get(sd); |
926 | if (ret) { |
927 | sdev_printk(KERN_WARNING, sd, "ch%d: failed to get device\n" , |
928 | ch->minor); |
929 | goto remove_idr; |
930 | } |
931 | |
932 | mutex_init(&ch->lock); |
933 | kref_init(kref: &ch->ref); |
934 | ch->device = sd; |
935 | class_dev = device_create(cls: &ch_sysfs_class, parent: dev, |
936 | MKDEV(SCSI_CHANGER_MAJOR, ch->minor), drvdata: ch, |
937 | fmt: "s%s" , ch->name); |
938 | if (IS_ERR(ptr: class_dev)) { |
939 | sdev_printk(KERN_WARNING, sd, "ch%d: device_create failed\n" , |
940 | ch->minor); |
941 | ret = PTR_ERR(ptr: class_dev); |
942 | goto put_device; |
943 | } |
944 | |
945 | mutex_lock(&ch->lock); |
946 | ret = ch_readconfig(ch); |
947 | if (ret) { |
948 | mutex_unlock(lock: &ch->lock); |
949 | goto destroy_dev; |
950 | } |
951 | if (init) |
952 | ch_init_elem(ch); |
953 | |
954 | mutex_unlock(lock: &ch->lock); |
955 | dev_set_drvdata(dev, data: ch); |
956 | sdev_printk(KERN_INFO, sd, "Attached scsi changer %s\n" , ch->name); |
957 | |
958 | return 0; |
959 | destroy_dev: |
960 | device_destroy(cls: &ch_sysfs_class, MKDEV(SCSI_CHANGER_MAJOR, ch->minor)); |
961 | put_device: |
962 | scsi_device_put(sd); |
963 | remove_idr: |
964 | idr_remove(&ch_index_idr, id: ch->minor); |
965 | free_ch: |
966 | kfree(objp: ch); |
967 | return ret; |
968 | } |
969 | |
970 | static int ch_remove(struct device *dev) |
971 | { |
972 | scsi_changer *ch = dev_get_drvdata(dev); |
973 | |
974 | spin_lock(lock: &ch_index_lock); |
975 | idr_remove(&ch_index_idr, id: ch->minor); |
976 | dev_set_drvdata(dev, NULL); |
977 | spin_unlock(lock: &ch_index_lock); |
978 | |
979 | device_destroy(cls: &ch_sysfs_class, MKDEV(SCSI_CHANGER_MAJOR, ch->minor)); |
980 | scsi_device_put(ch->device); |
981 | kref_put(kref: &ch->ref, release: ch_destroy); |
982 | return 0; |
983 | } |
984 | |
985 | static struct scsi_driver ch_template = { |
986 | .gendrv = { |
987 | .name = "ch" , |
988 | .owner = THIS_MODULE, |
989 | .probe = ch_probe, |
990 | .remove = ch_remove, |
991 | }, |
992 | }; |
993 | |
994 | static const struct file_operations changer_fops = { |
995 | .owner = THIS_MODULE, |
996 | .open = ch_open, |
997 | .release = ch_release, |
998 | .unlocked_ioctl = ch_ioctl, |
999 | .compat_ioctl = compat_ptr_ioctl, |
1000 | .llseek = noop_llseek, |
1001 | }; |
1002 | |
1003 | static int __init init_ch_module(void) |
1004 | { |
1005 | int rc; |
1006 | |
1007 | printk(KERN_INFO "SCSI Media Changer driver v" VERSION " \n" ); |
1008 | rc = class_register(class: &ch_sysfs_class); |
1009 | if (rc) |
1010 | return rc; |
1011 | rc = register_chrdev(SCSI_CHANGER_MAJOR,name: "ch" ,fops: &changer_fops); |
1012 | if (rc < 0) { |
1013 | printk("Unable to get major %d for SCSI-Changer\n" , |
1014 | SCSI_CHANGER_MAJOR); |
1015 | goto fail1; |
1016 | } |
1017 | rc = scsi_register_driver(&ch_template.gendrv); |
1018 | if (rc < 0) |
1019 | goto fail2; |
1020 | return 0; |
1021 | |
1022 | fail2: |
1023 | unregister_chrdev(SCSI_CHANGER_MAJOR, name: "ch" ); |
1024 | fail1: |
1025 | class_unregister(class: &ch_sysfs_class); |
1026 | return rc; |
1027 | } |
1028 | |
1029 | static void __exit exit_ch_module(void) |
1030 | { |
1031 | scsi_unregister_driver(&ch_template.gendrv); |
1032 | unregister_chrdev(SCSI_CHANGER_MAJOR, name: "ch" ); |
1033 | class_unregister(class: &ch_sysfs_class); |
1034 | idr_destroy(&ch_index_idr); |
1035 | } |
1036 | |
1037 | module_init(init_ch_module); |
1038 | module_exit(exit_ch_module); |
1039 | |