diff options
Diffstat (limited to 'drivers/base/firmware_class.c')
-rw-r--r-- | drivers/base/firmware_class.c | 129 |
1 files changed, 103 insertions, 26 deletions
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index d3a59c688fe4..8a267c427629 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c | |||
@@ -17,7 +17,7 @@ | |||
17 | #include <linux/bitops.h> | 17 | #include <linux/bitops.h> |
18 | #include <linux/mutex.h> | 18 | #include <linux/mutex.h> |
19 | #include <linux/kthread.h> | 19 | #include <linux/kthread.h> |
20 | 20 | #include <linux/highmem.h> | |
21 | #include <linux/firmware.h> | 21 | #include <linux/firmware.h> |
22 | #include "base.h" | 22 | #include "base.h" |
23 | 23 | ||
@@ -45,7 +45,10 @@ struct firmware_priv { | |||
45 | struct bin_attribute attr_data; | 45 | struct bin_attribute attr_data; |
46 | struct firmware *fw; | 46 | struct firmware *fw; |
47 | unsigned long status; | 47 | unsigned long status; |
48 | int alloc_size; | 48 | struct page **pages; |
49 | int nr_pages; | ||
50 | int page_array_size; | ||
51 | const char *vdata; | ||
49 | struct timer_list timeout; | 52 | struct timer_list timeout; |
50 | }; | 53 | }; |
51 | 54 | ||
@@ -122,6 +125,10 @@ static ssize_t firmware_loading_show(struct device *dev, | |||
122 | return sprintf(buf, "%d\n", loading); | 125 | return sprintf(buf, "%d\n", loading); |
123 | } | 126 | } |
124 | 127 | ||
128 | /* Some architectures don't have PAGE_KERNEL_RO */ | ||
129 | #ifndef PAGE_KERNEL_RO | ||
130 | #define PAGE_KERNEL_RO PAGE_KERNEL | ||
131 | #endif | ||
125 | /** | 132 | /** |
126 | * firmware_loading_store - set value in the 'loading' control file | 133 | * firmware_loading_store - set value in the 'loading' control file |
127 | * @dev: device pointer | 134 | * @dev: device pointer |
@@ -141,6 +148,7 @@ static ssize_t firmware_loading_store(struct device *dev, | |||
141 | { | 148 | { |
142 | struct firmware_priv *fw_priv = dev_get_drvdata(dev); | 149 | struct firmware_priv *fw_priv = dev_get_drvdata(dev); |
143 | int loading = simple_strtol(buf, NULL, 10); | 150 | int loading = simple_strtol(buf, NULL, 10); |
151 | int i; | ||
144 | 152 | ||
145 | switch (loading) { | 153 | switch (loading) { |
146 | case 1: | 154 | case 1: |
@@ -151,13 +159,30 @@ static ssize_t firmware_loading_store(struct device *dev, | |||
151 | } | 159 | } |
152 | vfree(fw_priv->fw->data); | 160 | vfree(fw_priv->fw->data); |
153 | fw_priv->fw->data = NULL; | 161 | fw_priv->fw->data = NULL; |
162 | for (i = 0; i < fw_priv->nr_pages; i++) | ||
163 | __free_page(fw_priv->pages[i]); | ||
164 | kfree(fw_priv->pages); | ||
165 | fw_priv->pages = NULL; | ||
166 | fw_priv->page_array_size = 0; | ||
167 | fw_priv->nr_pages = 0; | ||
154 | fw_priv->fw->size = 0; | 168 | fw_priv->fw->size = 0; |
155 | fw_priv->alloc_size = 0; | ||
156 | set_bit(FW_STATUS_LOADING, &fw_priv->status); | 169 | set_bit(FW_STATUS_LOADING, &fw_priv->status); |
157 | mutex_unlock(&fw_lock); | 170 | mutex_unlock(&fw_lock); |
158 | break; | 171 | break; |
159 | case 0: | 172 | case 0: |
160 | if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) { | 173 | if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) { |
174 | vfree(fw_priv->fw->data); | ||
175 | fw_priv->fw->data = vmap(fw_priv->pages, | ||
176 | fw_priv->nr_pages, | ||
177 | 0, PAGE_KERNEL_RO); | ||
178 | if (!fw_priv->fw->data) { | ||
179 | dev_err(dev, "%s: vmap() failed\n", __func__); | ||
180 | goto err; | ||
181 | } | ||
182 | /* Pages will be freed by vfree() */ | ||
183 | fw_priv->pages = NULL; | ||
184 | fw_priv->page_array_size = 0; | ||
185 | fw_priv->nr_pages = 0; | ||
161 | complete(&fw_priv->completion); | 186 | complete(&fw_priv->completion); |
162 | clear_bit(FW_STATUS_LOADING, &fw_priv->status); | 187 | clear_bit(FW_STATUS_LOADING, &fw_priv->status); |
163 | break; | 188 | break; |
@@ -167,6 +192,7 @@ static ssize_t firmware_loading_store(struct device *dev, | |||
167 | dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); | 192 | dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); |
168 | /* fallthrough */ | 193 | /* fallthrough */ |
169 | case -1: | 194 | case -1: |
195 | err: | ||
170 | fw_load_abort(fw_priv); | 196 | fw_load_abort(fw_priv); |
171 | break; | 197 | break; |
172 | } | 198 | } |
@@ -191,8 +217,28 @@ firmware_data_read(struct kobject *kobj, struct bin_attribute *bin_attr, | |||
191 | ret_count = -ENODEV; | 217 | ret_count = -ENODEV; |
192 | goto out; | 218 | goto out; |
193 | } | 219 | } |
194 | ret_count = memory_read_from_buffer(buffer, count, &offset, | 220 | if (offset > fw->size) |
195 | fw->data, fw->size); | 221 | return 0; |
222 | if (count > fw->size - offset) | ||
223 | count = fw->size - offset; | ||
224 | |||
225 | ret_count = count; | ||
226 | |||
227 | while (count) { | ||
228 | void *page_data; | ||
229 | int page_nr = offset >> PAGE_SHIFT; | ||
230 | int page_ofs = offset & (PAGE_SIZE-1); | ||
231 | int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); | ||
232 | |||
233 | page_data = kmap(fw_priv->pages[page_nr]); | ||
234 | |||
235 | memcpy(buffer, page_data + page_ofs, page_cnt); | ||
236 | |||
237 | kunmap(fw_priv->pages[page_nr]); | ||
238 | buffer += page_cnt; | ||
239 | offset += page_cnt; | ||
240 | count -= page_cnt; | ||
241 | } | ||
196 | out: | 242 | out: |
197 | mutex_unlock(&fw_lock); | 243 | mutex_unlock(&fw_lock); |
198 | return ret_count; | 244 | return ret_count; |
@@ -201,27 +247,39 @@ out: | |||
201 | static int | 247 | static int |
202 | fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) | 248 | fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) |
203 | { | 249 | { |
204 | u8 *new_data; | 250 | int pages_needed = ALIGN(min_size, PAGE_SIZE) >> PAGE_SHIFT; |
205 | int new_size = fw_priv->alloc_size; | 251 | |
252 | /* If the array of pages is too small, grow it... */ | ||
253 | if (fw_priv->page_array_size < pages_needed) { | ||
254 | int new_array_size = max(pages_needed, | ||
255 | fw_priv->page_array_size * 2); | ||
256 | struct page **new_pages; | ||
257 | |||
258 | new_pages = kmalloc(new_array_size * sizeof(void *), | ||
259 | GFP_KERNEL); | ||
260 | if (!new_pages) { | ||
261 | fw_load_abort(fw_priv); | ||
262 | return -ENOMEM; | ||
263 | } | ||
264 | memcpy(new_pages, fw_priv->pages, | ||
265 | fw_priv->page_array_size * sizeof(void *)); | ||
266 | memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) * | ||
267 | (new_array_size - fw_priv->page_array_size)); | ||
268 | kfree(fw_priv->pages); | ||
269 | fw_priv->pages = new_pages; | ||
270 | fw_priv->page_array_size = new_array_size; | ||
271 | } | ||
206 | 272 | ||
207 | if (min_size <= fw_priv->alloc_size) | 273 | while (fw_priv->nr_pages < pages_needed) { |
208 | return 0; | 274 | fw_priv->pages[fw_priv->nr_pages] = |
275 | alloc_page(GFP_KERNEL | __GFP_HIGHMEM); | ||
209 | 276 | ||
210 | new_size = ALIGN(min_size, PAGE_SIZE); | 277 | if (!fw_priv->pages[fw_priv->nr_pages]) { |
211 | new_data = vmalloc(new_size); | 278 | fw_load_abort(fw_priv); |
212 | if (!new_data) { | 279 | return -ENOMEM; |
213 | printk(KERN_ERR "%s: unable to alloc buffer\n", __func__); | 280 | } |
214 | /* Make sure that we don't keep incomplete data */ | 281 | fw_priv->nr_pages++; |
215 | fw_load_abort(fw_priv); | ||
216 | return -ENOMEM; | ||
217 | } | ||
218 | fw_priv->alloc_size = new_size; | ||
219 | if (fw_priv->fw->data) { | ||
220 | memcpy(new_data, fw_priv->fw->data, fw_priv->fw->size); | ||
221 | vfree(fw_priv->fw->data); | ||
222 | } | 282 | } |
223 | fw_priv->fw->data = new_data; | ||
224 | BUG_ON(min_size > fw_priv->alloc_size); | ||
225 | return 0; | 283 | return 0; |
226 | } | 284 | } |
227 | 285 | ||
@@ -258,10 +316,25 @@ firmware_data_write(struct kobject *kobj, struct bin_attribute *bin_attr, | |||
258 | if (retval) | 316 | if (retval) |
259 | goto out; | 317 | goto out; |
260 | 318 | ||
261 | memcpy((u8 *)fw->data + offset, buffer, count); | ||
262 | |||
263 | fw->size = max_t(size_t, offset + count, fw->size); | ||
264 | retval = count; | 319 | retval = count; |
320 | |||
321 | while (count) { | ||
322 | void *page_data; | ||
323 | int page_nr = offset >> PAGE_SHIFT; | ||
324 | int page_ofs = offset & (PAGE_SIZE - 1); | ||
325 | int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count); | ||
326 | |||
327 | page_data = kmap(fw_priv->pages[page_nr]); | ||
328 | |||
329 | memcpy(page_data + page_ofs, buffer, page_cnt); | ||
330 | |||
331 | kunmap(fw_priv->pages[page_nr]); | ||
332 | buffer += page_cnt; | ||
333 | offset += page_cnt; | ||
334 | count -= page_cnt; | ||
335 | } | ||
336 | |||
337 | fw->size = max_t(size_t, offset, fw->size); | ||
265 | out: | 338 | out: |
266 | mutex_unlock(&fw_lock); | 339 | mutex_unlock(&fw_lock); |
267 | return retval; | 340 | return retval; |
@@ -277,7 +350,11 @@ static struct bin_attribute firmware_attr_data_tmpl = { | |||
277 | static void fw_dev_release(struct device *dev) | 350 | static void fw_dev_release(struct device *dev) |
278 | { | 351 | { |
279 | struct firmware_priv *fw_priv = dev_get_drvdata(dev); | 352 | struct firmware_priv *fw_priv = dev_get_drvdata(dev); |
353 | int i; | ||
280 | 354 | ||
355 | for (i = 0; i < fw_priv->nr_pages; i++) | ||
356 | __free_page(fw_priv->pages[i]); | ||
357 | kfree(fw_priv->pages); | ||
281 | kfree(fw_priv); | 358 | kfree(fw_priv); |
282 | kfree(dev); | 359 | kfree(dev); |
283 | 360 | ||