diff options
author | Ming Lei <ming.lei@canonical.com> | 2012-08-04 00:01:16 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-08-16 16:13:18 -0400 |
commit | 65710cb6ea315b3ef76a8a3da7be99afcf58d2bb (patch) | |
tree | 40043563aca5fa81fbc1f04f16ea887b6b372212 | |
parent | 3cd52ab68b7f17eddbff46c1f8e5a105cd901f8e (diff) |
firmware loader: simplify pages ownership transfer
This patch doesn't transfer ownership of pages' buffer to the
instance of firmware until the firmware loading is completed,
which will simplify firmware_loading_store a lot, so help
to introduce the following cache_firmware and uncache_firmware
mechanism during system suspend-resume cycle.
In fact, this patch fixes one bug: if writing data into
firmware loader device is bypassed between writting 1 and 0 to
'loading', OOPS will be triggered without the patch.
Also handle the vmap failure case, and add some comments to make
code more readable.
Signed-off-by: Ming Lei <ming.lei@canonical.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/base/firmware_class.c | 62 |
1 files changed, 39 insertions, 23 deletions
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 803cfc1597a9..1cbefcfd15f7 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c | |||
@@ -93,6 +93,8 @@ struct firmware_priv { | |||
93 | struct completion completion; | 93 | struct completion completion; |
94 | struct firmware *fw; | 94 | struct firmware *fw; |
95 | unsigned long status; | 95 | unsigned long status; |
96 | void *data; | ||
97 | size_t size; | ||
96 | struct page **pages; | 98 | struct page **pages; |
97 | int nr_pages; | 99 | int nr_pages; |
98 | int page_array_size; | 100 | int page_array_size; |
@@ -156,9 +158,11 @@ static void fw_dev_release(struct device *dev) | |||
156 | struct firmware_priv *fw_priv = to_firmware_priv(dev); | 158 | struct firmware_priv *fw_priv = to_firmware_priv(dev); |
157 | int i; | 159 | int i; |
158 | 160 | ||
161 | /* free untransfered pages buffer */ | ||
159 | for (i = 0; i < fw_priv->nr_pages; i++) | 162 | for (i = 0; i < fw_priv->nr_pages; i++) |
160 | __free_page(fw_priv->pages[i]); | 163 | __free_page(fw_priv->pages[i]); |
161 | kfree(fw_priv->pages); | 164 | kfree(fw_priv->pages); |
165 | |||
162 | kfree(fw_priv); | 166 | kfree(fw_priv); |
163 | 167 | ||
164 | module_put(THIS_MODULE); | 168 | module_put(THIS_MODULE); |
@@ -194,6 +198,7 @@ static ssize_t firmware_loading_show(struct device *dev, | |||
194 | return sprintf(buf, "%d\n", loading); | 198 | return sprintf(buf, "%d\n", loading); |
195 | } | 199 | } |
196 | 200 | ||
201 | /* firmware holds the ownership of pages */ | ||
197 | static void firmware_free_data(const struct firmware *fw) | 202 | static void firmware_free_data(const struct firmware *fw) |
198 | { | 203 | { |
199 | int i; | 204 | int i; |
@@ -237,9 +242,7 @@ static ssize_t firmware_loading_store(struct device *dev, | |||
237 | 242 | ||
238 | switch (loading) { | 243 | switch (loading) { |
239 | case 1: | 244 | case 1: |
240 | firmware_free_data(fw_priv->fw); | 245 | /* discarding any previous partial load */ |
241 | memset(fw_priv->fw, 0, sizeof(struct firmware)); | ||
242 | /* If the pages are not owned by 'struct firmware' */ | ||
243 | for (i = 0; i < fw_priv->nr_pages; i++) | 246 | for (i = 0; i < fw_priv->nr_pages; i++) |
244 | __free_page(fw_priv->pages[i]); | 247 | __free_page(fw_priv->pages[i]); |
245 | kfree(fw_priv->pages); | 248 | kfree(fw_priv->pages); |
@@ -250,20 +253,6 @@ static ssize_t firmware_loading_store(struct device *dev, | |||
250 | break; | 253 | break; |
251 | case 0: | 254 | case 0: |
252 | if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) { | 255 | if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) { |
253 | vunmap(fw_priv->fw->data); | ||
254 | fw_priv->fw->data = vmap(fw_priv->pages, | ||
255 | fw_priv->nr_pages, | ||
256 | 0, PAGE_KERNEL_RO); | ||
257 | if (!fw_priv->fw->data) { | ||
258 | dev_err(dev, "%s: vmap() failed\n", __func__); | ||
259 | goto err; | ||
260 | } | ||
261 | /* Pages are now owned by 'struct firmware' */ | ||
262 | fw_priv->fw->pages = fw_priv->pages; | ||
263 | fw_priv->pages = NULL; | ||
264 | |||
265 | fw_priv->page_array_size = 0; | ||
266 | fw_priv->nr_pages = 0; | ||
267 | complete(&fw_priv->completion); | 256 | complete(&fw_priv->completion); |
268 | clear_bit(FW_STATUS_LOADING, &fw_priv->status); | 257 | clear_bit(FW_STATUS_LOADING, &fw_priv->status); |
269 | break; | 258 | break; |
@@ -273,7 +262,6 @@ static ssize_t firmware_loading_store(struct device *dev, | |||
273 | dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); | 262 | dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); |
274 | /* fallthrough */ | 263 | /* fallthrough */ |
275 | case -1: | 264 | case -1: |
276 | err: | ||
277 | fw_load_abort(fw_priv); | 265 | fw_load_abort(fw_priv); |
278 | break; | 266 | break; |
279 | } | 267 | } |
@@ -299,12 +287,12 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, | |||
299 | ret_count = -ENODEV; | 287 | ret_count = -ENODEV; |
300 | goto out; | 288 | goto out; |
301 | } | 289 | } |
302 | if (offset > fw->size) { | 290 | if (offset > fw_priv->size) { |
303 | ret_count = 0; | 291 | ret_count = 0; |
304 | goto out; | 292 | goto out; |
305 | } | 293 | } |
306 | if (count > fw->size - offset) | 294 | if (count > fw_priv->size - offset) |
307 | count = fw->size - offset; | 295 | count = fw_priv->size - offset; |
308 | 296 | ||
309 | ret_count = count; | 297 | ret_count = count; |
310 | 298 | ||
@@ -396,6 +384,7 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, | |||
396 | retval = -ENODEV; | 384 | retval = -ENODEV; |
397 | goto out; | 385 | goto out; |
398 | } | 386 | } |
387 | |||
399 | retval = fw_realloc_buffer(fw_priv, offset + count); | 388 | retval = fw_realloc_buffer(fw_priv, offset + count); |
400 | if (retval) | 389 | if (retval) |
401 | goto out; | 390 | goto out; |
@@ -418,7 +407,7 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, | |||
418 | count -= page_cnt; | 407 | count -= page_cnt; |
419 | } | 408 | } |
420 | 409 | ||
421 | fw->size = max_t(size_t, offset, fw->size); | 410 | fw_priv->size = max_t(size_t, offset, fw_priv->size); |
422 | out: | 411 | out: |
423 | mutex_unlock(&fw_lock); | 412 | mutex_unlock(&fw_lock); |
424 | return retval; | 413 | return retval; |
@@ -504,6 +493,29 @@ static void _request_firmware_cleanup(const struct firmware **firmware_p) | |||
504 | *firmware_p = NULL; | 493 | *firmware_p = NULL; |
505 | } | 494 | } |
506 | 495 | ||
496 | /* transfer the ownership of pages to firmware */ | ||
497 | static int fw_set_page_data(struct firmware_priv *fw_priv) | ||
498 | { | ||
499 | struct firmware *fw = fw_priv->fw; | ||
500 | |||
501 | fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, | ||
502 | 0, PAGE_KERNEL_RO); | ||
503 | if (!fw_priv->data) | ||
504 | return -ENOMEM; | ||
505 | |||
506 | fw->data = fw_priv->data; | ||
507 | fw->pages = fw_priv->pages; | ||
508 | fw->size = fw_priv->size; | ||
509 | |||
510 | WARN_ON(PFN_UP(fw->size) != fw_priv->nr_pages); | ||
511 | |||
512 | fw_priv->nr_pages = 0; | ||
513 | fw_priv->pages = NULL; | ||
514 | fw_priv->data = NULL; | ||
515 | |||
516 | return 0; | ||
517 | } | ||
518 | |||
507 | static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, | 519 | static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, |
508 | long timeout) | 520 | long timeout) |
509 | { | 521 | { |
@@ -549,8 +561,12 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, | |||
549 | del_timer_sync(&fw_priv->timeout); | 561 | del_timer_sync(&fw_priv->timeout); |
550 | 562 | ||
551 | mutex_lock(&fw_lock); | 563 | mutex_lock(&fw_lock); |
552 | if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) | 564 | if (!fw_priv->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) |
553 | retval = -ENOENT; | 565 | retval = -ENOENT; |
566 | |||
567 | /* transfer pages ownership at the last minute */ | ||
568 | if (!retval) | ||
569 | retval = fw_set_page_data(fw_priv); | ||
554 | fw_priv->fw = NULL; | 570 | fw_priv->fw = NULL; |
555 | mutex_unlock(&fw_lock); | 571 | mutex_unlock(&fw_lock); |
556 | 572 | ||