diff options
Diffstat (limited to 'drivers/scsi/scsi_devinfo.c')
-rw-r--r-- | drivers/scsi/scsi_devinfo.c | 247 |
1 files changed, 225 insertions, 22 deletions
diff --git a/drivers/scsi/scsi_devinfo.c b/drivers/scsi/scsi_devinfo.c index 8821df9a277b..93c2622cb969 100644 --- a/drivers/scsi/scsi_devinfo.c +++ b/drivers/scsi/scsi_devinfo.c | |||
@@ -24,6 +24,13 @@ struct scsi_dev_info_list { | |||
24 | unsigned compatible; /* for use with scsi_static_device_list entries */ | 24 | unsigned compatible; /* for use with scsi_static_device_list entries */ |
25 | }; | 25 | }; |
26 | 26 | ||
27 | struct scsi_dev_info_list_table { | ||
28 | struct list_head node; /* our node for being on the master list */ | ||
29 | struct list_head scsi_dev_info_list; /* head of dev info list */ | ||
30 | const char *name; /* name of list for /proc (NULL for global) */ | ||
31 | int key; /* unique numeric identifier */ | ||
32 | }; | ||
33 | |||
27 | 34 | ||
28 | static const char spaces[] = " "; /* 16 of them */ | 35 | static const char spaces[] = " "; /* 16 of them */ |
29 | static unsigned scsi_default_dev_flags; | 36 | static unsigned scsi_default_dev_flags; |
@@ -247,6 +254,22 @@ static struct { | |||
247 | { NULL, NULL, NULL, 0 }, | 254 | { NULL, NULL, NULL, 0 }, |
248 | }; | 255 | }; |
249 | 256 | ||
257 | static struct scsi_dev_info_list_table *scsi_devinfo_lookup_by_key(int key) | ||
258 | { | ||
259 | struct scsi_dev_info_list_table *devinfo_table; | ||
260 | int found = 0; | ||
261 | |||
262 | list_for_each_entry(devinfo_table, &scsi_dev_info_list, node) | ||
263 | if (devinfo_table->key == key) { | ||
264 | found = 1; | ||
265 | break; | ||
266 | } | ||
267 | if (!found) | ||
268 | return ERR_PTR(-EINVAL); | ||
269 | |||
270 | return devinfo_table; | ||
271 | } | ||
272 | |||
250 | /* | 273 | /* |
251 | * scsi_strcpy_devinfo: called from scsi_dev_info_list_add to copy into | 274 | * scsi_strcpy_devinfo: called from scsi_dev_info_list_add to copy into |
252 | * devinfo vendor and model strings. | 275 | * devinfo vendor and model strings. |
@@ -296,7 +319,38 @@ static void scsi_strcpy_devinfo(char *name, char *to, size_t to_length, | |||
296 | static int scsi_dev_info_list_add(int compatible, char *vendor, char *model, | 319 | static int scsi_dev_info_list_add(int compatible, char *vendor, char *model, |
297 | char *strflags, int flags) | 320 | char *strflags, int flags) |
298 | { | 321 | { |
322 | return scsi_dev_info_list_add_keyed(compatible, vendor, model, | ||
323 | strflags, flags, | ||
324 | SCSI_DEVINFO_GLOBAL); | ||
325 | } | ||
326 | |||
327 | /** | ||
328 | * scsi_dev_info_list_add_keyed - add one dev_info list entry. | ||
329 | * @compatible: if true, null terminate short strings. Otherwise space pad. | ||
330 | * @vendor: vendor string | ||
331 | * @model: model (product) string | ||
332 | * @strflags: integer string | ||
333 | * @flags: if strflags NULL, use this flag value | ||
334 | * @key: specify list to use | ||
335 | * | ||
336 | * Description: | ||
337 | * Create and add one dev_info entry for @vendor, @model, | ||
338 | * @strflags or @flag in list specified by @key. If @compatible, | ||
339 | * add to the tail of the list, do not space pad, and set | ||
340 | * devinfo->compatible. The scsi_static_device_list entries are | ||
341 | * added with @compatible 1 and @clfags NULL. | ||
342 | * | ||
343 | * Returns: 0 OK, -error on failure. | ||
344 | **/ | ||
345 | int scsi_dev_info_list_add_keyed(int compatible, char *vendor, char *model, | ||
346 | char *strflags, int flags, int key) | ||
347 | { | ||
299 | struct scsi_dev_info_list *devinfo; | 348 | struct scsi_dev_info_list *devinfo; |
349 | struct scsi_dev_info_list_table *devinfo_table = | ||
350 | scsi_devinfo_lookup_by_key(key); | ||
351 | |||
352 | if (IS_ERR(devinfo_table)) | ||
353 | return PTR_ERR(devinfo_table); | ||
300 | 354 | ||
301 | devinfo = kmalloc(sizeof(*devinfo), GFP_KERNEL); | 355 | devinfo = kmalloc(sizeof(*devinfo), GFP_KERNEL); |
302 | if (!devinfo) { | 356 | if (!devinfo) { |
@@ -317,12 +371,15 @@ static int scsi_dev_info_list_add(int compatible, char *vendor, char *model, | |||
317 | devinfo->compatible = compatible; | 371 | devinfo->compatible = compatible; |
318 | 372 | ||
319 | if (compatible) | 373 | if (compatible) |
320 | list_add_tail(&devinfo->dev_info_list, &scsi_dev_info_list); | 374 | list_add_tail(&devinfo->dev_info_list, |
375 | &devinfo_table->scsi_dev_info_list); | ||
321 | else | 376 | else |
322 | list_add(&devinfo->dev_info_list, &scsi_dev_info_list); | 377 | list_add(&devinfo->dev_info_list, |
378 | &devinfo_table->scsi_dev_info_list); | ||
323 | 379 | ||
324 | return 0; | 380 | return 0; |
325 | } | 381 | } |
382 | EXPORT_SYMBOL(scsi_dev_info_list_add_keyed); | ||
326 | 383 | ||
327 | /** | 384 | /** |
328 | * scsi_dev_info_list_add_str - parse dev_list and add to the scsi_dev_info_list. | 385 | * scsi_dev_info_list_add_str - parse dev_list and add to the scsi_dev_info_list. |
@@ -382,22 +439,48 @@ static int scsi_dev_info_list_add_str(char *dev_list) | |||
382 | * @model: model name | 439 | * @model: model name |
383 | * | 440 | * |
384 | * Description: | 441 | * Description: |
385 | * Search the scsi_dev_info_list for an entry matching @vendor and | 442 | * Search the global scsi_dev_info_list (specified by list zero) |
386 | * @model, if found, return the matching flags value, else return | 443 | * for an entry matching @vendor and @model, if found, return the |
387 | * the host or global default settings. Called during scan time. | 444 | * matching flags value, else return the host or global default |
445 | * settings. Called during scan time. | ||
388 | **/ | 446 | **/ |
389 | int scsi_get_device_flags(struct scsi_device *sdev, | 447 | int scsi_get_device_flags(struct scsi_device *sdev, |
390 | const unsigned char *vendor, | 448 | const unsigned char *vendor, |
391 | const unsigned char *model) | 449 | const unsigned char *model) |
392 | { | 450 | { |
451 | return scsi_get_device_flags_keyed(sdev, vendor, model, | ||
452 | SCSI_DEVINFO_GLOBAL); | ||
453 | } | ||
454 | |||
455 | |||
456 | /** | ||
457 | * get_device_flags_keyed - get device specific flags from the dynamic device list. | ||
458 | * @sdev: &scsi_device to get flags for | ||
459 | * @vendor: vendor name | ||
460 | * @model: model name | ||
461 | * @key: list to look up | ||
462 | * | ||
463 | * Description: | ||
464 | * Search the scsi_dev_info_list specified by @key for an entry | ||
465 | * matching @vendor and @model, if found, return the matching | ||
466 | * flags value, else return the host or global default settings. | ||
467 | * Called during scan time. | ||
468 | **/ | ||
469 | int scsi_get_device_flags_keyed(struct scsi_device *sdev, | ||
470 | const unsigned char *vendor, | ||
471 | const unsigned char *model, | ||
472 | int key) | ||
473 | { | ||
393 | struct scsi_dev_info_list *devinfo; | 474 | struct scsi_dev_info_list *devinfo; |
394 | unsigned int bflags; | 475 | struct scsi_dev_info_list_table *devinfo_table; |
476 | |||
477 | devinfo_table = scsi_devinfo_lookup_by_key(key); | ||
395 | 478 | ||
396 | bflags = sdev->sdev_bflags; | 479 | if (IS_ERR(devinfo_table)) |
397 | if (!bflags) | 480 | return PTR_ERR(devinfo_table); |
398 | bflags = scsi_default_dev_flags; | ||
399 | 481 | ||
400 | list_for_each_entry(devinfo, &scsi_dev_info_list, dev_info_list) { | 482 | list_for_each_entry(devinfo, &devinfo_table->scsi_dev_info_list, |
483 | dev_info_list) { | ||
401 | if (devinfo->compatible) { | 484 | if (devinfo->compatible) { |
402 | /* | 485 | /* |
403 | * Behave like the older version of get_device_flags. | 486 | * Behave like the older version of get_device_flags. |
@@ -447,32 +530,89 @@ int scsi_get_device_flags(struct scsi_device *sdev, | |||
447 | return devinfo->flags; | 530 | return devinfo->flags; |
448 | } | 531 | } |
449 | } | 532 | } |
450 | return bflags; | 533 | /* nothing found, return nothing */ |
534 | if (key != SCSI_DEVINFO_GLOBAL) | ||
535 | return 0; | ||
536 | |||
537 | /* except for the global list, where we have an exception */ | ||
538 | if (sdev->sdev_bflags) | ||
539 | return sdev->sdev_bflags; | ||
540 | |||
541 | return scsi_default_dev_flags; | ||
451 | } | 542 | } |
543 | EXPORT_SYMBOL(scsi_get_device_flags_keyed); | ||
452 | 544 | ||
453 | #ifdef CONFIG_SCSI_PROC_FS | 545 | #ifdef CONFIG_SCSI_PROC_FS |
546 | struct double_list { | ||
547 | struct list_head *top; | ||
548 | struct list_head *bottom; | ||
549 | }; | ||
550 | |||
454 | static int devinfo_seq_show(struct seq_file *m, void *v) | 551 | static int devinfo_seq_show(struct seq_file *m, void *v) |
455 | { | 552 | { |
553 | struct double_list *dl = v; | ||
554 | struct scsi_dev_info_list_table *devinfo_table = | ||
555 | list_entry(dl->top, struct scsi_dev_info_list_table, node); | ||
456 | struct scsi_dev_info_list *devinfo = | 556 | struct scsi_dev_info_list *devinfo = |
457 | list_entry(v, struct scsi_dev_info_list, dev_info_list); | 557 | list_entry(dl->bottom, struct scsi_dev_info_list, |
558 | dev_info_list); | ||
559 | |||
560 | if (devinfo_table->scsi_dev_info_list.next == dl->bottom && | ||
561 | devinfo_table->name) | ||
562 | seq_printf(m, "[%s]:\n", devinfo_table->name); | ||
458 | 563 | ||
459 | seq_printf(m, "'%.8s' '%.16s' 0x%x\n", | 564 | seq_printf(m, "'%.8s' '%.16s' 0x%x\n", |
460 | devinfo->vendor, devinfo->model, devinfo->flags); | 565 | devinfo->vendor, devinfo->model, devinfo->flags); |
461 | return 0; | 566 | return 0; |
462 | } | 567 | } |
463 | 568 | ||
464 | static void * devinfo_seq_start(struct seq_file *m, loff_t *pos) | 569 | static void *devinfo_seq_start(struct seq_file *m, loff_t *ppos) |
465 | { | 570 | { |
466 | return seq_list_start(&scsi_dev_info_list, *pos); | 571 | struct double_list *dl = kmalloc(sizeof(*dl), GFP_KERNEL); |
572 | loff_t pos = *ppos; | ||
573 | |||
574 | if (!dl) | ||
575 | return NULL; | ||
576 | |||
577 | list_for_each(dl->top, &scsi_dev_info_list) { | ||
578 | struct scsi_dev_info_list_table *devinfo_table = | ||
579 | list_entry(dl->top, struct scsi_dev_info_list_table, | ||
580 | node); | ||
581 | list_for_each(dl->bottom, &devinfo_table->scsi_dev_info_list) | ||
582 | if (pos-- == 0) | ||
583 | return dl; | ||
584 | } | ||
585 | |||
586 | kfree(dl); | ||
587 | return NULL; | ||
467 | } | 588 | } |
468 | 589 | ||
469 | static void * devinfo_seq_next(struct seq_file *m, void *v, loff_t *pos) | 590 | static void *devinfo_seq_next(struct seq_file *m, void *v, loff_t *ppos) |
470 | { | 591 | { |
471 | return seq_list_next(v, &scsi_dev_info_list, pos); | 592 | struct double_list *dl = v; |
593 | struct scsi_dev_info_list_table *devinfo_table = | ||
594 | list_entry(dl->top, struct scsi_dev_info_list_table, node); | ||
595 | |||
596 | ++*ppos; | ||
597 | dl->bottom = dl->bottom->next; | ||
598 | while (&devinfo_table->scsi_dev_info_list == dl->bottom) { | ||
599 | dl->top = dl->top->next; | ||
600 | if (dl->top == &scsi_dev_info_list) { | ||
601 | kfree(dl); | ||
602 | return NULL; | ||
603 | } | ||
604 | devinfo_table = list_entry(dl->top, | ||
605 | struct scsi_dev_info_list_table, | ||
606 | node); | ||
607 | dl->bottom = devinfo_table->scsi_dev_info_list.next; | ||
608 | } | ||
609 | |||
610 | return dl; | ||
472 | } | 611 | } |
473 | 612 | ||
474 | static void devinfo_seq_stop(struct seq_file *m, void *v) | 613 | static void devinfo_seq_stop(struct seq_file *m, void *v) |
475 | { | 614 | { |
615 | kfree(v); | ||
476 | } | 616 | } |
477 | 617 | ||
478 | static const struct seq_operations scsi_devinfo_seq_ops = { | 618 | static const struct seq_operations scsi_devinfo_seq_ops = { |
@@ -549,19 +689,78 @@ MODULE_PARM_DESC(default_dev_flags, | |||
549 | **/ | 689 | **/ |
550 | void scsi_exit_devinfo(void) | 690 | void scsi_exit_devinfo(void) |
551 | { | 691 | { |
552 | struct list_head *lh, *lh_next; | ||
553 | struct scsi_dev_info_list *devinfo; | ||
554 | |||
555 | #ifdef CONFIG_SCSI_PROC_FS | 692 | #ifdef CONFIG_SCSI_PROC_FS |
556 | remove_proc_entry("scsi/device_info", NULL); | 693 | remove_proc_entry("scsi/device_info", NULL); |
557 | #endif | 694 | #endif |
558 | 695 | ||
559 | list_for_each_safe(lh, lh_next, &scsi_dev_info_list) { | 696 | scsi_dev_info_remove_list(SCSI_DEVINFO_GLOBAL); |
697 | } | ||
698 | |||
699 | /** | ||
700 | * scsi_dev_info_add_list - add a new devinfo list | ||
701 | * @key: key of the list to add | ||
702 | * @name: Name of the list to add (for /proc/scsi/device_info) | ||
703 | * | ||
704 | * Adds the requested list, returns zero on success, -EEXIST if the | ||
705 | * key is already registered to a list, or other error on failure. | ||
706 | */ | ||
707 | int scsi_dev_info_add_list(int key, const char *name) | ||
708 | { | ||
709 | struct scsi_dev_info_list_table *devinfo_table = | ||
710 | scsi_devinfo_lookup_by_key(key); | ||
711 | |||
712 | if (!IS_ERR(devinfo_table)) | ||
713 | /* list already exists */ | ||
714 | return -EEXIST; | ||
715 | |||
716 | devinfo_table = kmalloc(sizeof(*devinfo_table), GFP_KERNEL); | ||
717 | |||
718 | if (!devinfo_table) | ||
719 | return -ENOMEM; | ||
720 | |||
721 | INIT_LIST_HEAD(&devinfo_table->node); | ||
722 | INIT_LIST_HEAD(&devinfo_table->scsi_dev_info_list); | ||
723 | devinfo_table->name = name; | ||
724 | devinfo_table->key = key; | ||
725 | list_add_tail(&devinfo_table->node, &scsi_dev_info_list); | ||
726 | |||
727 | return 0; | ||
728 | } | ||
729 | EXPORT_SYMBOL(scsi_dev_info_add_list); | ||
730 | |||
731 | /** | ||
732 | * scsi_dev_info_remove_list - destroy an added devinfo list | ||
733 | * @key: key of the list to destroy | ||
734 | * | ||
735 | * Iterates over the entire list first, freeing all the values, then | ||
736 | * frees the list itself. Returns 0 on success or -EINVAL if the key | ||
737 | * can't be found. | ||
738 | */ | ||
739 | int scsi_dev_info_remove_list(int key) | ||
740 | { | ||
741 | struct list_head *lh, *lh_next; | ||
742 | struct scsi_dev_info_list_table *devinfo_table = | ||
743 | scsi_devinfo_lookup_by_key(key); | ||
744 | |||
745 | if (IS_ERR(devinfo_table)) | ||
746 | /* no such list */ | ||
747 | return -EINVAL; | ||
748 | |||
749 | /* remove from the master list */ | ||
750 | list_del(&devinfo_table->node); | ||
751 | |||
752 | list_for_each_safe(lh, lh_next, &devinfo_table->scsi_dev_info_list) { | ||
753 | struct scsi_dev_info_list *devinfo; | ||
754 | |||
560 | devinfo = list_entry(lh, struct scsi_dev_info_list, | 755 | devinfo = list_entry(lh, struct scsi_dev_info_list, |
561 | dev_info_list); | 756 | dev_info_list); |
562 | kfree(devinfo); | 757 | kfree(devinfo); |
563 | } | 758 | } |
759 | kfree(devinfo_table); | ||
760 | |||
761 | return 0; | ||
564 | } | 762 | } |
763 | EXPORT_SYMBOL(scsi_dev_info_remove_list); | ||
565 | 764 | ||
566 | /** | 765 | /** |
567 | * scsi_init_devinfo - set up the dynamic device list. | 766 | * scsi_init_devinfo - set up the dynamic device list. |
@@ -577,10 +776,14 @@ int __init scsi_init_devinfo(void) | |||
577 | #endif | 776 | #endif |
578 | int error, i; | 777 | int error, i; |
579 | 778 | ||
580 | error = scsi_dev_info_list_add_str(scsi_dev_flags); | 779 | error = scsi_dev_info_add_list(SCSI_DEVINFO_GLOBAL, NULL); |
581 | if (error) | 780 | if (error) |
582 | return error; | 781 | return error; |
583 | 782 | ||
783 | error = scsi_dev_info_list_add_str(scsi_dev_flags); | ||
784 | if (error) | ||
785 | goto out; | ||
786 | |||
584 | for (i = 0; scsi_static_device_list[i].vendor; i++) { | 787 | for (i = 0; scsi_static_device_list[i].vendor; i++) { |
585 | error = scsi_dev_info_list_add(1 /* compatibile */, | 788 | error = scsi_dev_info_list_add(1 /* compatibile */, |
586 | scsi_static_device_list[i].vendor, | 789 | scsi_static_device_list[i].vendor, |