diff options
author | Takashi Iwai <tiwai@suse.de> | 2013-05-22 12:28:38 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-06-03 16:57:29 -0400 |
commit | fe304143b0c3d43fbf13773bcd4a7989a1fb4e1c (patch) | |
tree | 882efb8d1ce0c0ed0140e5c88d89677ce9037b2b /drivers/base/firmware_class.c | |
parent | d05c39ea67f5786a549ac9d672d2951992b658c6 (diff) |
firmware: Avoid deadlock of usermodehelper lock at shutdown
When a system goes to reboot/shutdown, it tries to disable the
usermode helper via usermodehelper_disable(). This might be blocked
when a driver tries to load a firmware beforehand and it's stuck by
some reason. For example, dell_rbu driver loads the firmware in
non-hotplug mode and waits for user-space clearing the loading sysfs
flag. If user-space doesn't clear the flag, it waits forever, thus
blocks the reboot, too.
As a workaround, in this patch, the firmware class driver registers a
reboot notifier so that it can abort all pending f/w bufs before
issuing usermodehelper_disable().
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Acked-by: Ming Lei <ming.lei@canonical.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/base/firmware_class.c')
-rw-r--r-- | drivers/base/firmware_class.c | 44 |
1 files changed, 37 insertions, 7 deletions
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 4b1f9265887f..e650c25360d1 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c | |||
@@ -27,6 +27,7 @@ | |||
27 | #include <linux/pm.h> | 27 | #include <linux/pm.h> |
28 | #include <linux/suspend.h> | 28 | #include <linux/suspend.h> |
29 | #include <linux/syscore_ops.h> | 29 | #include <linux/syscore_ops.h> |
30 | #include <linux/reboot.h> | ||
30 | 31 | ||
31 | #include <generated/utsrelease.h> | 32 | #include <generated/utsrelease.h> |
32 | 33 | ||
@@ -130,6 +131,7 @@ struct firmware_buf { | |||
130 | struct page **pages; | 131 | struct page **pages; |
131 | int nr_pages; | 132 | int nr_pages; |
132 | int page_array_size; | 133 | int page_array_size; |
134 | struct list_head pending_list; | ||
133 | #endif | 135 | #endif |
134 | char fw_id[]; | 136 | char fw_id[]; |
135 | }; | 137 | }; |
@@ -171,6 +173,9 @@ static struct firmware_buf *__allocate_fw_buf(const char *fw_name, | |||
171 | strcpy(buf->fw_id, fw_name); | 173 | strcpy(buf->fw_id, fw_name); |
172 | buf->fwc = fwc; | 174 | buf->fwc = fwc; |
173 | init_completion(&buf->completion); | 175 | init_completion(&buf->completion); |
176 | #ifdef CONFIG_FW_LOADER_USER_HELPER | ||
177 | INIT_LIST_HEAD(&buf->pending_list); | ||
178 | #endif | ||
174 | 179 | ||
175 | pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf); | 180 | pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf); |
176 | 181 | ||
@@ -446,10 +451,9 @@ static struct firmware_priv *to_firmware_priv(struct device *dev) | |||
446 | return container_of(dev, struct firmware_priv, dev); | 451 | return container_of(dev, struct firmware_priv, dev); |
447 | } | 452 | } |
448 | 453 | ||
449 | static void fw_load_abort(struct firmware_priv *fw_priv) | 454 | static void fw_load_abort(struct firmware_buf *buf) |
450 | { | 455 | { |
451 | struct firmware_buf *buf = fw_priv->buf; | 456 | list_del_init(&buf->pending_list); |
452 | |||
453 | set_bit(FW_STATUS_ABORT, &buf->status); | 457 | set_bit(FW_STATUS_ABORT, &buf->status); |
454 | complete_all(&buf->completion); | 458 | complete_all(&buf->completion); |
455 | } | 459 | } |
@@ -457,6 +461,25 @@ static void fw_load_abort(struct firmware_priv *fw_priv) | |||
457 | #define is_fw_load_aborted(buf) \ | 461 | #define is_fw_load_aborted(buf) \ |
458 | test_bit(FW_STATUS_ABORT, &(buf)->status) | 462 | test_bit(FW_STATUS_ABORT, &(buf)->status) |
459 | 463 | ||
464 | static LIST_HEAD(pending_fw_head); | ||
465 | |||
466 | /* reboot notifier for avoid deadlock with usermode_lock */ | ||
467 | static int fw_shutdown_notify(struct notifier_block *unused1, | ||
468 | unsigned long unused2, void *unused3) | ||
469 | { | ||
470 | mutex_lock(&fw_lock); | ||
471 | while (!list_empty(&pending_fw_head)) | ||
472 | fw_load_abort(list_first_entry(&pending_fw_head, | ||
473 | struct firmware_buf, | ||
474 | pending_list)); | ||
475 | mutex_unlock(&fw_lock); | ||
476 | return NOTIFY_DONE; | ||
477 | } | ||
478 | |||
479 | static struct notifier_block fw_shutdown_nb = { | ||
480 | .notifier_call = fw_shutdown_notify, | ||
481 | }; | ||
482 | |||
460 | static ssize_t firmware_timeout_show(struct class *class, | 483 | static ssize_t firmware_timeout_show(struct class *class, |
461 | struct class_attribute *attr, | 484 | struct class_attribute *attr, |
462 | char *buf) | 485 | char *buf) |
@@ -604,6 +627,7 @@ static ssize_t firmware_loading_store(struct device *dev, | |||
604 | * is completed. | 627 | * is completed. |
605 | * */ | 628 | * */ |
606 | fw_map_pages_buf(fw_buf); | 629 | fw_map_pages_buf(fw_buf); |
630 | list_del_init(&fw_buf->pending_list); | ||
607 | complete_all(&fw_buf->completion); | 631 | complete_all(&fw_buf->completion); |
608 | break; | 632 | break; |
609 | } | 633 | } |
@@ -612,7 +636,7 @@ static ssize_t firmware_loading_store(struct device *dev, | |||
612 | dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); | 636 | dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); |
613 | /* fallthrough */ | 637 | /* fallthrough */ |
614 | case -1: | 638 | case -1: |
615 | fw_load_abort(fw_priv); | 639 | fw_load_abort(fw_buf); |
616 | break; | 640 | break; |
617 | } | 641 | } |
618 | out: | 642 | out: |
@@ -680,7 +704,7 @@ static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) | |||
680 | new_pages = kmalloc(new_array_size * sizeof(void *), | 704 | new_pages = kmalloc(new_array_size * sizeof(void *), |
681 | GFP_KERNEL); | 705 | GFP_KERNEL); |
682 | if (!new_pages) { | 706 | if (!new_pages) { |
683 | fw_load_abort(fw_priv); | 707 | fw_load_abort(buf); |
684 | return -ENOMEM; | 708 | return -ENOMEM; |
685 | } | 709 | } |
686 | memcpy(new_pages, buf->pages, | 710 | memcpy(new_pages, buf->pages, |
@@ -697,7 +721,7 @@ static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) | |||
697 | alloc_page(GFP_KERNEL | __GFP_HIGHMEM); | 721 | alloc_page(GFP_KERNEL | __GFP_HIGHMEM); |
698 | 722 | ||
699 | if (!buf->pages[buf->nr_pages]) { | 723 | if (!buf->pages[buf->nr_pages]) { |
700 | fw_load_abort(fw_priv); | 724 | fw_load_abort(buf); |
701 | return -ENOMEM; | 725 | return -ENOMEM; |
702 | } | 726 | } |
703 | buf->nr_pages++; | 727 | buf->nr_pages++; |
@@ -781,7 +805,7 @@ static void firmware_class_timeout_work(struct work_struct *work) | |||
781 | mutex_unlock(&fw_lock); | 805 | mutex_unlock(&fw_lock); |
782 | return; | 806 | return; |
783 | } | 807 | } |
784 | fw_load_abort(fw_priv); | 808 | fw_load_abort(fw_priv->buf); |
785 | mutex_unlock(&fw_lock); | 809 | mutex_unlock(&fw_lock); |
786 | } | 810 | } |
787 | 811 | ||
@@ -857,6 +881,10 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, | |||
857 | kobject_uevent(&fw_priv->dev.kobj, KOBJ_ADD); | 881 | kobject_uevent(&fw_priv->dev.kobj, KOBJ_ADD); |
858 | } | 882 | } |
859 | 883 | ||
884 | mutex_lock(&fw_lock); | ||
885 | list_add(&buf->pending_list, &pending_fw_head); | ||
886 | mutex_unlock(&fw_lock); | ||
887 | |||
860 | wait_for_completion(&buf->completion); | 888 | wait_for_completion(&buf->completion); |
861 | 889 | ||
862 | cancel_delayed_work_sync(&fw_priv->timeout_work); | 890 | cancel_delayed_work_sync(&fw_priv->timeout_work); |
@@ -1517,6 +1545,7 @@ static int __init firmware_class_init(void) | |||
1517 | { | 1545 | { |
1518 | fw_cache_init(); | 1546 | fw_cache_init(); |
1519 | #ifdef CONFIG_FW_LOADER_USER_HELPER | 1547 | #ifdef CONFIG_FW_LOADER_USER_HELPER |
1548 | register_reboot_notifier(&fw_shutdown_nb); | ||
1520 | return class_register(&firmware_class); | 1549 | return class_register(&firmware_class); |
1521 | #else | 1550 | #else |
1522 | return 0; | 1551 | return 0; |
@@ -1530,6 +1559,7 @@ static void __exit firmware_class_exit(void) | |||
1530 | unregister_pm_notifier(&fw_cache.pm_notify); | 1559 | unregister_pm_notifier(&fw_cache.pm_notify); |
1531 | #endif | 1560 | #endif |
1532 | #ifdef CONFIG_FW_LOADER_USER_HELPER | 1561 | #ifdef CONFIG_FW_LOADER_USER_HELPER |
1562 | unregister_reboot_notifier(&fw_shutdown_nb); | ||
1533 | class_unregister(&firmware_class); | 1563 | class_unregister(&firmware_class); |
1534 | #endif | 1564 | #endif |
1535 | } | 1565 | } |