diff options
Diffstat (limited to 'drivers/base')
| -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 | ||
