diff options
author | Ming Lei <ming.lei@canonical.com> | 2012-08-04 00:01:21 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-08-16 16:22:45 -0400 |
commit | 1f2b79599ee8f5fc82cc73c6c090eb6cdff881d6 (patch) | |
tree | 6b3fed8d355f708cc6a001e772f07783a300d2cd /drivers/base/firmware_class.c | |
parent | 1244691c73b250be522e77ac1a00ad53b601b4c4 (diff) |
firmware loader: always let firmware_buf own the pages buffer
This patch always let firmware_buf own the pages buffer allocated
inside firmware_data_write, and add all instances of firmware_buf
into the firmware cache global list. Also introduce one private field
in 'struct firmware', so release_firmware will see the instance of
firmware_buf associated with the current firmware instance, then just
'free' the instance of firmware_buf.
The firmware_buf instance represents one pages buffer for one
firmware image, so lots of firmware loading requests can share
the same firmware_buf instance if they request the same firmware
image file.
This patch will make implementation of the following cache_firmware/
uncache_firmware very easy and simple.
In fact, the patch improves request_formware/release_firmware:
- only request userspace to write firmware image once if
several devices share one same firmware image and its drivers
call request_firmware concurrently.
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>
Diffstat (limited to 'drivers/base/firmware_class.c')
-rw-r--r-- | drivers/base/firmware_class.c | 240 |
1 files changed, 171 insertions, 69 deletions
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 5f2076e5d5b1..848ad97e8d79 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c | |||
@@ -21,6 +21,7 @@ | |||
21 | #include <linux/firmware.h> | 21 | #include <linux/firmware.h> |
22 | #include <linux/slab.h> | 22 | #include <linux/slab.h> |
23 | #include <linux/sched.h> | 23 | #include <linux/sched.h> |
24 | #include <linux/list.h> | ||
24 | 25 | ||
25 | MODULE_AUTHOR("Manuel Estrada Sainz"); | 26 | MODULE_AUTHOR("Manuel Estrada Sainz"); |
26 | MODULE_DESCRIPTION("Multi purpose firmware loading support"); | 27 | MODULE_DESCRIPTION("Multi purpose firmware loading support"); |
@@ -85,13 +86,17 @@ static inline long firmware_loading_timeout(void) | |||
85 | return loading_timeout > 0 ? loading_timeout * HZ : MAX_SCHEDULE_TIMEOUT; | 86 | return loading_timeout > 0 ? loading_timeout * HZ : MAX_SCHEDULE_TIMEOUT; |
86 | } | 87 | } |
87 | 88 | ||
88 | /* fw_lock could be moved to 'struct firmware_priv' but since it is just | 89 | struct firmware_cache { |
89 | * guarding for corner cases a global lock should be OK */ | 90 | /* firmware_buf instance will be added into the below list */ |
90 | static DEFINE_MUTEX(fw_lock); | 91 | spinlock_t lock; |
92 | struct list_head head; | ||
93 | }; | ||
91 | 94 | ||
92 | struct firmware_buf { | 95 | struct firmware_buf { |
96 | struct kref ref; | ||
97 | struct list_head list; | ||
93 | struct completion completion; | 98 | struct completion completion; |
94 | struct firmware *fw; | 99 | struct firmware_cache *fwc; |
95 | unsigned long status; | 100 | unsigned long status; |
96 | void *data; | 101 | void *data; |
97 | size_t size; | 102 | size_t size; |
@@ -106,8 +111,94 @@ struct firmware_priv { | |||
106 | bool nowait; | 111 | bool nowait; |
107 | struct device dev; | 112 | struct device dev; |
108 | struct firmware_buf *buf; | 113 | struct firmware_buf *buf; |
114 | struct firmware *fw; | ||
109 | }; | 115 | }; |
110 | 116 | ||
117 | #define to_fwbuf(d) container_of(d, struct firmware_buf, ref) | ||
118 | |||
119 | /* fw_lock could be moved to 'struct firmware_priv' but since it is just | ||
120 | * guarding for corner cases a global lock should be OK */ | ||
121 | static DEFINE_MUTEX(fw_lock); | ||
122 | |||
123 | static struct firmware_cache fw_cache; | ||
124 | |||
125 | static struct firmware_buf *__allocate_fw_buf(const char *fw_name, | ||
126 | struct firmware_cache *fwc) | ||
127 | { | ||
128 | struct firmware_buf *buf; | ||
129 | |||
130 | buf = kzalloc(sizeof(*buf) + strlen(fw_name) + 1 , GFP_ATOMIC); | ||
131 | |||
132 | if (!buf) | ||
133 | return buf; | ||
134 | |||
135 | kref_init(&buf->ref); | ||
136 | strcpy(buf->fw_id, fw_name); | ||
137 | buf->fwc = fwc; | ||
138 | init_completion(&buf->completion); | ||
139 | |||
140 | pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf); | ||
141 | |||
142 | return buf; | ||
143 | } | ||
144 | |||
145 | static int fw_lookup_and_allocate_buf(const char *fw_name, | ||
146 | struct firmware_cache *fwc, | ||
147 | struct firmware_buf **buf) | ||
148 | { | ||
149 | struct firmware_buf *tmp; | ||
150 | |||
151 | spin_lock(&fwc->lock); | ||
152 | list_for_each_entry(tmp, &fwc->head, list) | ||
153 | if (!strcmp(tmp->fw_id, fw_name)) { | ||
154 | kref_get(&tmp->ref); | ||
155 | spin_unlock(&fwc->lock); | ||
156 | *buf = tmp; | ||
157 | return 1; | ||
158 | } | ||
159 | |||
160 | tmp = __allocate_fw_buf(fw_name, fwc); | ||
161 | if (tmp) | ||
162 | list_add(&tmp->list, &fwc->head); | ||
163 | spin_unlock(&fwc->lock); | ||
164 | |||
165 | *buf = tmp; | ||
166 | |||
167 | return tmp ? 0 : -ENOMEM; | ||
168 | } | ||
169 | |||
170 | static void __fw_free_buf(struct kref *ref) | ||
171 | { | ||
172 | struct firmware_buf *buf = to_fwbuf(ref); | ||
173 | struct firmware_cache *fwc = buf->fwc; | ||
174 | int i; | ||
175 | |||
176 | pr_debug("%s: fw-%s buf=%p data=%p size=%u\n", | ||
177 | __func__, buf->fw_id, buf, buf->data, | ||
178 | (unsigned int)buf->size); | ||
179 | |||
180 | spin_lock(&fwc->lock); | ||
181 | list_del(&buf->list); | ||
182 | spin_unlock(&fwc->lock); | ||
183 | |||
184 | vunmap(buf->data); | ||
185 | for (i = 0; i < buf->nr_pages; i++) | ||
186 | __free_page(buf->pages[i]); | ||
187 | kfree(buf->pages); | ||
188 | kfree(buf); | ||
189 | } | ||
190 | |||
191 | static void fw_free_buf(struct firmware_buf *buf) | ||
192 | { | ||
193 | kref_put(&buf->ref, __fw_free_buf); | ||
194 | } | ||
195 | |||
196 | static void __init fw_cache_init(void) | ||
197 | { | ||
198 | spin_lock_init(&fw_cache.lock); | ||
199 | INIT_LIST_HEAD(&fw_cache.head); | ||
200 | } | ||
201 | |||
111 | static struct firmware_priv *to_firmware_priv(struct device *dev) | 202 | static struct firmware_priv *to_firmware_priv(struct device *dev) |
112 | { | 203 | { |
113 | return container_of(dev, struct firmware_priv, dev); | 204 | return container_of(dev, struct firmware_priv, dev); |
@@ -118,7 +209,7 @@ static void fw_load_abort(struct firmware_priv *fw_priv) | |||
118 | struct firmware_buf *buf = fw_priv->buf; | 209 | struct firmware_buf *buf = fw_priv->buf; |
119 | 210 | ||
120 | set_bit(FW_STATUS_ABORT, &buf->status); | 211 | set_bit(FW_STATUS_ABORT, &buf->status); |
121 | complete(&buf->completion); | 212 | complete_all(&buf->completion); |
122 | } | 213 | } |
123 | 214 | ||
124 | static ssize_t firmware_timeout_show(struct class *class, | 215 | static ssize_t firmware_timeout_show(struct class *class, |
@@ -158,18 +249,6 @@ static struct class_attribute firmware_class_attrs[] = { | |||
158 | __ATTR_NULL | 249 | __ATTR_NULL |
159 | }; | 250 | }; |
160 | 251 | ||
161 | static void fw_free_buf(struct firmware_buf *buf) | ||
162 | { | ||
163 | int i; | ||
164 | |||
165 | if (!buf) | ||
166 | return; | ||
167 | |||
168 | for (i = 0; i < buf->nr_pages; i++) | ||
169 | __free_page(buf->pages[i]); | ||
170 | kfree(buf->pages); | ||
171 | } | ||
172 | |||
173 | static void fw_dev_release(struct device *dev) | 252 | static void fw_dev_release(struct device *dev) |
174 | { | 253 | { |
175 | struct firmware_priv *fw_priv = to_firmware_priv(dev); | 254 | struct firmware_priv *fw_priv = to_firmware_priv(dev); |
@@ -212,13 +291,8 @@ static ssize_t firmware_loading_show(struct device *dev, | |||
212 | /* firmware holds the ownership of pages */ | 291 | /* firmware holds the ownership of pages */ |
213 | static void firmware_free_data(const struct firmware *fw) | 292 | static void firmware_free_data(const struct firmware *fw) |
214 | { | 293 | { |
215 | int i; | 294 | WARN_ON(!fw->priv); |
216 | vunmap(fw->data); | 295 | fw_free_buf(fw->priv); |
217 | if (fw->pages) { | ||
218 | for (i = 0; i < PFN_UP(fw->size); i++) | ||
219 | __free_page(fw->pages[i]); | ||
220 | kfree(fw->pages); | ||
221 | } | ||
222 | } | 296 | } |
223 | 297 | ||
224 | /* Some architectures don't have PAGE_KERNEL_RO */ | 298 | /* Some architectures don't have PAGE_KERNEL_RO */ |
@@ -269,7 +343,7 @@ static ssize_t firmware_loading_store(struct device *dev, | |||
269 | if (test_bit(FW_STATUS_LOADING, &fw_buf->status)) { | 343 | if (test_bit(FW_STATUS_LOADING, &fw_buf->status)) { |
270 | set_bit(FW_STATUS_DONE, &fw_buf->status); | 344 | set_bit(FW_STATUS_DONE, &fw_buf->status); |
271 | clear_bit(FW_STATUS_LOADING, &fw_buf->status); | 345 | clear_bit(FW_STATUS_LOADING, &fw_buf->status); |
272 | complete(&fw_buf->completion); | 346 | complete_all(&fw_buf->completion); |
273 | break; | 347 | break; |
274 | } | 348 | } |
275 | /* fallthrough */ | 349 | /* fallthrough */ |
@@ -448,7 +522,6 @@ fw_create_instance(struct firmware *firmware, const char *fw_name, | |||
448 | struct device *device, bool uevent, bool nowait) | 522 | struct device *device, bool uevent, bool nowait) |
449 | { | 523 | { |
450 | struct firmware_priv *fw_priv; | 524 | struct firmware_priv *fw_priv; |
451 | struct firmware_buf *buf; | ||
452 | struct device *f_dev; | 525 | struct device *f_dev; |
453 | 526 | ||
454 | fw_priv = kzalloc(sizeof(*fw_priv), GFP_KERNEL); | 527 | fw_priv = kzalloc(sizeof(*fw_priv), GFP_KERNEL); |
@@ -458,21 +531,10 @@ fw_create_instance(struct firmware *firmware, const char *fw_name, | |||
458 | goto exit; | 531 | goto exit; |
459 | } | 532 | } |
460 | 533 | ||
461 | buf = kzalloc(sizeof(*buf) + strlen(fw_name) + 1, GFP_KERNEL); | ||
462 | if (!buf) { | ||
463 | dev_err(device, "%s: kmalloc failed\n", __func__); | ||
464 | kfree(fw_priv); | ||
465 | fw_priv = ERR_PTR(-ENOMEM); | ||
466 | goto exit; | ||
467 | } | ||
468 | |||
469 | buf->fw = firmware; | ||
470 | fw_priv->buf = buf; | ||
471 | fw_priv->nowait = nowait; | 534 | fw_priv->nowait = nowait; |
535 | fw_priv->fw = firmware; | ||
472 | setup_timer(&fw_priv->timeout, | 536 | setup_timer(&fw_priv->timeout, |
473 | firmware_class_timeout, (u_long) fw_priv); | 537 | firmware_class_timeout, (u_long) fw_priv); |
474 | strcpy(buf->fw_id, fw_name); | ||
475 | init_completion(&buf->completion); | ||
476 | 538 | ||
477 | f_dev = &fw_priv->dev; | 539 | f_dev = &fw_priv->dev; |
478 | 540 | ||
@@ -484,12 +546,42 @@ exit: | |||
484 | return fw_priv; | 546 | return fw_priv; |
485 | } | 547 | } |
486 | 548 | ||
549 | /* one pages buffer is mapped/unmapped only once */ | ||
550 | static int fw_map_pages_buf(struct firmware_buf *buf) | ||
551 | { | ||
552 | buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO); | ||
553 | if (!buf->data) | ||
554 | return -ENOMEM; | ||
555 | return 0; | ||
556 | } | ||
557 | |||
558 | /* store the pages buffer info firmware from buf */ | ||
559 | static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw) | ||
560 | { | ||
561 | fw->priv = buf; | ||
562 | fw->pages = buf->pages; | ||
563 | fw->size = buf->size; | ||
564 | fw->data = buf->data; | ||
565 | |||
566 | pr_debug("%s: fw-%s buf=%p data=%p size=%u\n", | ||
567 | __func__, buf->fw_id, buf, buf->data, | ||
568 | (unsigned int)buf->size); | ||
569 | } | ||
570 | |||
571 | static void _request_firmware_cleanup(const struct firmware **firmware_p) | ||
572 | { | ||
573 | release_firmware(*firmware_p); | ||
574 | *firmware_p = NULL; | ||
575 | } | ||
576 | |||
487 | static struct firmware_priv * | 577 | static struct firmware_priv * |
488 | _request_firmware_prepare(const struct firmware **firmware_p, const char *name, | 578 | _request_firmware_prepare(const struct firmware **firmware_p, const char *name, |
489 | struct device *device, bool uevent, bool nowait) | 579 | struct device *device, bool uevent, bool nowait) |
490 | { | 580 | { |
491 | struct firmware *firmware; | 581 | struct firmware *firmware; |
492 | struct firmware_priv *fw_priv; | 582 | struct firmware_priv *fw_priv = NULL; |
583 | struct firmware_buf *buf; | ||
584 | int ret; | ||
493 | 585 | ||
494 | if (!firmware_p) | 586 | if (!firmware_p) |
495 | return ERR_PTR(-EINVAL); | 587 | return ERR_PTR(-EINVAL); |
@@ -506,35 +598,45 @@ _request_firmware_prepare(const struct firmware **firmware_p, const char *name, | |||
506 | return NULL; | 598 | return NULL; |
507 | } | 599 | } |
508 | 600 | ||
509 | fw_priv = fw_create_instance(firmware, name, device, uevent, nowait); | 601 | ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf); |
510 | if (IS_ERR(fw_priv)) { | 602 | if (!ret) |
511 | release_firmware(firmware); | 603 | fw_priv = fw_create_instance(firmware, name, device, |
604 | uevent, nowait); | ||
605 | |||
606 | if (IS_ERR(fw_priv) || ret < 0) { | ||
607 | kfree(firmware); | ||
512 | *firmware_p = NULL; | 608 | *firmware_p = NULL; |
609 | return ERR_PTR(-ENOMEM); | ||
610 | } else if (fw_priv) { | ||
611 | fw_priv->buf = buf; | ||
612 | |||
613 | /* | ||
614 | * bind with 'buf' now to avoid warning in failure path | ||
615 | * of requesting firmware. | ||
616 | */ | ||
617 | firmware->priv = buf; | ||
618 | return fw_priv; | ||
513 | } | 619 | } |
514 | return fw_priv; | ||
515 | } | ||
516 | |||
517 | static void _request_firmware_cleanup(const struct firmware **firmware_p) | ||
518 | { | ||
519 | release_firmware(*firmware_p); | ||
520 | *firmware_p = NULL; | ||
521 | } | ||
522 | 620 | ||
523 | /* transfer the ownership of pages to firmware */ | 621 | /* share the cached buf, which is inprogessing or completed */ |
524 | static int fw_set_page_data(struct firmware_buf *buf) | 622 | check_status: |
525 | { | 623 | mutex_lock(&fw_lock); |
526 | struct firmware *fw = buf->fw; | 624 | if (test_bit(FW_STATUS_ABORT, &buf->status)) { |
527 | 625 | fw_priv = ERR_PTR(-ENOENT); | |
528 | buf->data = vmap(buf->pages, buf->nr_pages, 0, PAGE_KERNEL_RO); | 626 | _request_firmware_cleanup(firmware_p); |
529 | if (!buf->data) | 627 | goto exit; |
530 | return -ENOMEM; | 628 | } else if (test_bit(FW_STATUS_DONE, &buf->status)) { |
531 | 629 | fw_priv = NULL; | |
532 | fw->data = buf->data; | 630 | fw_set_page_data(buf, firmware); |
533 | fw->pages = buf->pages; | 631 | goto exit; |
534 | fw->size = buf->size; | 632 | } |
535 | WARN_ON(PFN_UP(fw->size) != buf->nr_pages); | 633 | mutex_unlock(&fw_lock); |
634 | wait_for_completion(&buf->completion); | ||
635 | goto check_status; | ||
536 | 636 | ||
537 | return 0; | 637 | exit: |
638 | mutex_unlock(&fw_lock); | ||
639 | return fw_priv; | ||
538 | } | 640 | } |
539 | 641 | ||
540 | static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, | 642 | static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, |
@@ -585,13 +687,12 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, | |||
585 | if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status)) | 687 | if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status)) |
586 | retval = -ENOENT; | 688 | retval = -ENOENT; |
587 | 689 | ||
588 | /* transfer pages ownership at the last minute */ | ||
589 | if (!retval) | 690 | if (!retval) |
590 | retval = fw_set_page_data(buf); | 691 | retval = fw_map_pages_buf(buf); |
591 | if (retval) | 692 | |
592 | fw_free_buf(buf); /* free untransfered pages buffer */ | 693 | /* pass the pages buffer to driver at the last minute */ |
694 | fw_set_page_data(buf, fw_priv->fw); | ||
593 | 695 | ||
594 | kfree(buf); | ||
595 | fw_priv->buf = NULL; | 696 | fw_priv->buf = NULL; |
596 | mutex_unlock(&fw_lock); | 697 | mutex_unlock(&fw_lock); |
597 | 698 | ||
@@ -753,6 +854,7 @@ request_firmware_nowait( | |||
753 | 854 | ||
754 | static int __init firmware_class_init(void) | 855 | static int __init firmware_class_init(void) |
755 | { | 856 | { |
857 | fw_cache_init(); | ||
756 | return class_register(&firmware_class); | 858 | return class_register(&firmware_class); |
757 | } | 859 | } |
758 | 860 | ||