diff options
author | Ming Lei <ming.lei@canonical.com> | 2012-08-20 07:04:16 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-09-06 17:46:16 -0400 |
commit | ac39b3ea73aacde876d1d5ee1ca3e2719f771482 (patch) | |
tree | 58b9ee67654ea41ddba770f827d7f949ee1d40f2 /drivers/base/firmware_class.c | |
parent | ef40bb1bd01738670bd567e3dce8e862f2b91bf3 (diff) |
firmware loader: let caching firmware piggyback on loading firmware
After starting caching firmware, there is still some time left
before devices are suspended, during the period, request_firmware
or its nowait version may still be triggered by the below situations
to load firmware images which can't be cached during suspend/resume
cycle.
- new devices added
- driver bind
- or device open kind of things
This patch utilizes the piggyback trick to cache firmware for
this kind of situation: just increase the firmware buf's reference
count and add the fw name entry into cache entry list after starting
caching firmware and before syscore_suspend() is called.
Signed-off-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 | 83 |
1 files changed, 74 insertions, 9 deletions
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index edc88bc68b3d..95f6851b3010 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c | |||
@@ -25,6 +25,7 @@ | |||
25 | #include <linux/async.h> | 25 | #include <linux/async.h> |
26 | #include <linux/pm.h> | 26 | #include <linux/pm.h> |
27 | #include <linux/suspend.h> | 27 | #include <linux/suspend.h> |
28 | #include <linux/syscore_ops.h> | ||
28 | 29 | ||
29 | #include "base.h" | 30 | #include "base.h" |
30 | 31 | ||
@@ -105,6 +106,8 @@ struct firmware_cache { | |||
105 | spinlock_t name_lock; | 106 | spinlock_t name_lock; |
106 | struct list_head fw_names; | 107 | struct list_head fw_names; |
107 | 108 | ||
109 | int state; | ||
110 | |||
108 | wait_queue_head_t wait_queue; | 111 | wait_queue_head_t wait_queue; |
109 | int cnt; | 112 | int cnt; |
110 | struct delayed_work work; | 113 | struct delayed_work work; |
@@ -146,6 +149,11 @@ struct fw_name_devm { | |||
146 | 149 | ||
147 | #define to_fwbuf(d) container_of(d, struct firmware_buf, ref) | 150 | #define to_fwbuf(d) container_of(d, struct firmware_buf, ref) |
148 | 151 | ||
152 | #define FW_LOADER_NO_CACHE 0 | ||
153 | #define FW_LOADER_START_CACHE 1 | ||
154 | |||
155 | static int fw_cache_piggyback_on_request(const char *name); | ||
156 | |||
149 | /* fw_lock could be moved to 'struct firmware_priv' but since it is just | 157 | /* fw_lock could be moved to 'struct firmware_priv' but since it is just |
150 | * guarding for corner cases a global lock should be OK */ | 158 | * guarding for corner cases a global lock should be OK */ |
151 | static DEFINE_MUTEX(fw_lock); | 159 | static DEFINE_MUTEX(fw_lock); |
@@ -741,6 +749,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, | |||
741 | int retval = 0; | 749 | int retval = 0; |
742 | struct device *f_dev = &fw_priv->dev; | 750 | struct device *f_dev = &fw_priv->dev; |
743 | struct firmware_buf *buf = fw_priv->buf; | 751 | struct firmware_buf *buf = fw_priv->buf; |
752 | struct firmware_cache *fwc = &fw_cache; | ||
744 | 753 | ||
745 | dev_set_uevent_suppress(f_dev, true); | 754 | dev_set_uevent_suppress(f_dev, true); |
746 | 755 | ||
@@ -796,6 +805,15 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, | |||
796 | if (!retval) | 805 | if (!retval) |
797 | retval = fw_map_pages_buf(buf); | 806 | retval = fw_map_pages_buf(buf); |
798 | 807 | ||
808 | /* | ||
809 | * After caching firmware image is started, let it piggyback | ||
810 | * on request firmware. | ||
811 | */ | ||
812 | if (!retval && fwc->state == FW_LOADER_START_CACHE) { | ||
813 | if (fw_cache_piggyback_on_request(buf->fw_id)) | ||
814 | kref_get(&buf->ref); | ||
815 | } | ||
816 | |||
799 | /* pass the pages buffer to driver at the last minute */ | 817 | /* pass the pages buffer to driver at the last minute */ |
800 | fw_set_page_data(buf, fw_priv->fw); | 818 | fw_set_page_data(buf, fw_priv->fw); |
801 | 819 | ||
@@ -1041,6 +1059,29 @@ exit: | |||
1041 | return fce; | 1059 | return fce; |
1042 | } | 1060 | } |
1043 | 1061 | ||
1062 | static int fw_cache_piggyback_on_request(const char *name) | ||
1063 | { | ||
1064 | struct firmware_cache *fwc = &fw_cache; | ||
1065 | struct fw_cache_entry *fce; | ||
1066 | int ret = 0; | ||
1067 | |||
1068 | spin_lock(&fwc->name_lock); | ||
1069 | list_for_each_entry(fce, &fwc->fw_names, list) { | ||
1070 | if (!strcmp(fce->name, name)) | ||
1071 | goto found; | ||
1072 | } | ||
1073 | |||
1074 | fce = alloc_fw_cache_entry(name); | ||
1075 | if (fce) { | ||
1076 | ret = 1; | ||
1077 | list_add(&fce->list, &fwc->fw_names); | ||
1078 | pr_debug("%s: fw: %s\n", __func__, name); | ||
1079 | } | ||
1080 | found: | ||
1081 | spin_unlock(&fwc->name_lock); | ||
1082 | return ret; | ||
1083 | } | ||
1084 | |||
1044 | static void free_fw_cache_entry(struct fw_cache_entry *fce) | 1085 | static void free_fw_cache_entry(struct fw_cache_entry *fce) |
1045 | { | 1086 | { |
1046 | kfree(fce); | 1087 | kfree(fce); |
@@ -1054,17 +1095,14 @@ static void __async_dev_cache_fw_image(void *fw_entry, | |||
1054 | int ret; | 1095 | int ret; |
1055 | 1096 | ||
1056 | ret = cache_firmware(fce->name); | 1097 | ret = cache_firmware(fce->name); |
1057 | if (ret) | 1098 | if (ret) { |
1058 | goto free; | 1099 | spin_lock(&fwc->name_lock); |
1100 | list_del(&fce->list); | ||
1101 | spin_unlock(&fwc->name_lock); | ||
1059 | 1102 | ||
1060 | spin_lock(&fwc->name_lock); | 1103 | free_fw_cache_entry(fce); |
1061 | list_add(&fce->list, &fwc->fw_names); | 1104 | } |
1062 | spin_unlock(&fwc->name_lock); | ||
1063 | goto drop_ref; | ||
1064 | 1105 | ||
1065 | free: | ||
1066 | free_fw_cache_entry(fce); | ||
1067 | drop_ref: | ||
1068 | spin_lock(&fwc->name_lock); | 1106 | spin_lock(&fwc->name_lock); |
1069 | fwc->cnt--; | 1107 | fwc->cnt--; |
1070 | spin_unlock(&fwc->name_lock); | 1108 | spin_unlock(&fwc->name_lock); |
@@ -1109,6 +1147,7 @@ static void dev_cache_fw_image(struct device *dev, void *data) | |||
1109 | 1147 | ||
1110 | spin_lock(&fwc->name_lock); | 1148 | spin_lock(&fwc->name_lock); |
1111 | fwc->cnt++; | 1149 | fwc->cnt++; |
1150 | list_add(&fce->list, &fwc->fw_names); | ||
1112 | spin_unlock(&fwc->name_lock); | 1151 | spin_unlock(&fwc->name_lock); |
1113 | 1152 | ||
1114 | async_schedule(__async_dev_cache_fw_image, (void *)fce); | 1153 | async_schedule(__async_dev_cache_fw_image, (void *)fce); |
@@ -1164,7 +1203,10 @@ static void device_cache_fw_images(void) | |||
1164 | old_timeout = loading_timeout; | 1203 | old_timeout = loading_timeout; |
1165 | loading_timeout = 10; | 1204 | loading_timeout = 10; |
1166 | 1205 | ||
1206 | mutex_lock(&fw_lock); | ||
1207 | fwc->state = FW_LOADER_START_CACHE; | ||
1167 | dpm_for_each_dev(NULL, dev_cache_fw_image); | 1208 | dpm_for_each_dev(NULL, dev_cache_fw_image); |
1209 | mutex_unlock(&fw_lock); | ||
1168 | 1210 | ||
1169 | /* wait for completion of caching firmware for all devices */ | 1211 | /* wait for completion of caching firmware for all devices */ |
1170 | spin_lock(&fwc->name_lock); | 1212 | spin_lock(&fwc->name_lock); |
@@ -1229,6 +1271,14 @@ static int fw_pm_notify(struct notifier_block *notify_block, | |||
1229 | case PM_POST_SUSPEND: | 1271 | case PM_POST_SUSPEND: |
1230 | case PM_POST_HIBERNATION: | 1272 | case PM_POST_HIBERNATION: |
1231 | case PM_POST_RESTORE: | 1273 | case PM_POST_RESTORE: |
1274 | /* | ||
1275 | * In case that system sleep failed and syscore_suspend is | ||
1276 | * not called. | ||
1277 | */ | ||
1278 | mutex_lock(&fw_lock); | ||
1279 | fw_cache.state = FW_LOADER_NO_CACHE; | ||
1280 | mutex_unlock(&fw_lock); | ||
1281 | |||
1232 | device_uncache_fw_images_delay(10 * MSEC_PER_SEC); | 1282 | device_uncache_fw_images_delay(10 * MSEC_PER_SEC); |
1233 | break; | 1283 | break; |
1234 | } | 1284 | } |
@@ -1243,6 +1293,17 @@ static int fw_pm_notify(struct notifier_block *notify_block, | |||
1243 | } | 1293 | } |
1244 | #endif | 1294 | #endif |
1245 | 1295 | ||
1296 | /* stop caching firmware once syscore_suspend is reached */ | ||
1297 | static int fw_suspend(void) | ||
1298 | { | ||
1299 | fw_cache.state = FW_LOADER_NO_CACHE; | ||
1300 | return 0; | ||
1301 | } | ||
1302 | |||
1303 | static struct syscore_ops fw_syscore_ops = { | ||
1304 | .suspend = fw_suspend, | ||
1305 | }; | ||
1306 | |||
1246 | static void __init fw_cache_init(void) | 1307 | static void __init fw_cache_init(void) |
1247 | { | 1308 | { |
1248 | spin_lock_init(&fw_cache.lock); | 1309 | spin_lock_init(&fw_cache.lock); |
@@ -1251,6 +1312,7 @@ static void __init fw_cache_init(void) | |||
1251 | spin_lock_init(&fw_cache.name_lock); | 1312 | spin_lock_init(&fw_cache.name_lock); |
1252 | INIT_LIST_HEAD(&fw_cache.fw_names); | 1313 | INIT_LIST_HEAD(&fw_cache.fw_names); |
1253 | fw_cache.cnt = 0; | 1314 | fw_cache.cnt = 0; |
1315 | fw_cache.state = FW_LOADER_NO_CACHE; | ||
1254 | 1316 | ||
1255 | init_waitqueue_head(&fw_cache.wait_queue); | 1317 | init_waitqueue_head(&fw_cache.wait_queue); |
1256 | INIT_DELAYED_WORK(&fw_cache.work, | 1318 | INIT_DELAYED_WORK(&fw_cache.work, |
@@ -1258,6 +1320,8 @@ static void __init fw_cache_init(void) | |||
1258 | 1320 | ||
1259 | fw_cache.pm_notify.notifier_call = fw_pm_notify; | 1321 | fw_cache.pm_notify.notifier_call = fw_pm_notify; |
1260 | register_pm_notifier(&fw_cache.pm_notify); | 1322 | register_pm_notifier(&fw_cache.pm_notify); |
1323 | |||
1324 | register_syscore_ops(&fw_syscore_ops); | ||
1261 | } | 1325 | } |
1262 | 1326 | ||
1263 | static int __init firmware_class_init(void) | 1327 | static int __init firmware_class_init(void) |
@@ -1268,6 +1332,7 @@ static int __init firmware_class_init(void) | |||
1268 | 1332 | ||
1269 | static void __exit firmware_class_exit(void) | 1333 | static void __exit firmware_class_exit(void) |
1270 | { | 1334 | { |
1335 | unregister_syscore_ops(&fw_syscore_ops); | ||
1271 | unregister_pm_notifier(&fw_cache.pm_notify); | 1336 | unregister_pm_notifier(&fw_cache.pm_notify); |
1272 | class_unregister(&firmware_class); | 1337 | class_unregister(&firmware_class); |
1273 | } | 1338 | } |