diff options
author | Takashi Iwai <tiwai@suse.de> | 2019-06-11 08:26:25 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2019-06-18 03:11:22 -0400 |
commit | 82fd7a8142a10b8eb41313074b3859d82c0857dc (patch) | |
tree | c98ad021f2d262809d9155ad443c2ea009b78a4e | |
parent | 5342e7093ff298d9cbd40f9342b607adb02b2dd0 (diff) |
firmware: Add support for loading compressed files
This patch adds the support for loading compressed firmware files.
The primary motivation is to reduce the storage size; e.g. currently
the files in /lib/firmware on my machine counts up to 419MB, while
they can be reduced to 130MB by file compression.
The patch introduces a new kconfig option CONFIG_FW_LOADER_COMPRESS.
Even with this option set, the firmware loader still tries to load the
original firmware file as-is at first, but then falls back to the file
with ".xz" extension when it's not found, and the decompressed file
content is returned to the caller of request_firmware(). So, no
change is needed for the rest.
Currently only XZ format is supported. A caveat is that the kernel XZ
helper code supports only CRC32 (or none) integrity check type, so
you'll have to compress the files via xz -C crc32 option.
Since we can't determine the expanded size immediately from an XZ
file, the patch re-uses the paged buffer that was used for the
user-mode fallback; it puts the decompressed content page, which are
vmapped at the end. The paged buffer code is conditionally built with
a new Kconfig that is selected automatically.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/base/firmware_loader/Kconfig | 18 | ||||
-rw-r--r-- | drivers/base/firmware_loader/firmware.h | 8 | ||||
-rw-r--r-- | drivers/base/firmware_loader/main.c | 147 |
3 files changed, 161 insertions, 12 deletions
diff --git a/drivers/base/firmware_loader/Kconfig b/drivers/base/firmware_loader/Kconfig index 38f2da6f5c2b..3f9e274e2ed3 100644 --- a/drivers/base/firmware_loader/Kconfig +++ b/drivers/base/firmware_loader/Kconfig | |||
@@ -26,6 +26,9 @@ config FW_LOADER | |||
26 | 26 | ||
27 | if FW_LOADER | 27 | if FW_LOADER |
28 | 28 | ||
29 | config FW_LOADER_PAGED_BUF | ||
30 | bool | ||
31 | |||
29 | config EXTRA_FIRMWARE | 32 | config EXTRA_FIRMWARE |
30 | string "Build named firmware blobs into the kernel binary" | 33 | string "Build named firmware blobs into the kernel binary" |
31 | help | 34 | help |
@@ -67,6 +70,7 @@ config EXTRA_FIRMWARE_DIR | |||
67 | 70 | ||
68 | config FW_LOADER_USER_HELPER | 71 | config FW_LOADER_USER_HELPER |
69 | bool "Enable the firmware sysfs fallback mechanism" | 72 | bool "Enable the firmware sysfs fallback mechanism" |
73 | select FW_LOADER_PAGED_BUF | ||
70 | help | 74 | help |
71 | This option enables a sysfs loading facility to enable firmware | 75 | This option enables a sysfs loading facility to enable firmware |
72 | loading to the kernel through userspace as a fallback mechanism | 76 | loading to the kernel through userspace as a fallback mechanism |
@@ -151,5 +155,19 @@ config FW_LOADER_USER_HELPER_FALLBACK | |||
151 | 155 | ||
152 | If you are unsure about this, say N here. | 156 | If you are unsure about this, say N here. |
153 | 157 | ||
158 | config FW_LOADER_COMPRESS | ||
159 | bool "Enable compressed firmware support" | ||
160 | select FW_LOADER_PAGED_BUF | ||
161 | select XZ_DEC | ||
162 | help | ||
163 | This option enables the support for loading compressed firmware | ||
164 | files. The caller of firmware API receives the decompressed file | ||
165 | content. The compressed file is loaded as a fallback, only after | ||
166 | loading the raw file failed at first. | ||
167 | |||
168 | Currently only XZ-compressed files are supported, and they have to | ||
169 | be compressed with either none or crc32 integrity check type (pass | ||
170 | "-C crc32" option to xz command). | ||
171 | |||
154 | endif # FW_LOADER | 172 | endif # FW_LOADER |
155 | endmenu | 173 | endmenu |
diff --git a/drivers/base/firmware_loader/firmware.h b/drivers/base/firmware_loader/firmware.h index 35f4e58b2d98..7048a41973ed 100644 --- a/drivers/base/firmware_loader/firmware.h +++ b/drivers/base/firmware_loader/firmware.h | |||
@@ -64,12 +64,14 @@ struct fw_priv { | |||
64 | void *data; | 64 | void *data; |
65 | size_t size; | 65 | size_t size; |
66 | size_t allocated_size; | 66 | size_t allocated_size; |
67 | #ifdef CONFIG_FW_LOADER_USER_HELPER | 67 | #ifdef CONFIG_FW_LOADER_PAGED_BUF |
68 | bool is_paged_buf; | 68 | bool is_paged_buf; |
69 | bool need_uevent; | ||
70 | struct page **pages; | 69 | struct page **pages; |
71 | int nr_pages; | 70 | int nr_pages; |
72 | int page_array_size; | 71 | int page_array_size; |
72 | #endif | ||
73 | #ifdef CONFIG_FW_LOADER_USER_HELPER | ||
74 | bool need_uevent; | ||
73 | struct list_head pending_list; | 75 | struct list_head pending_list; |
74 | #endif | 76 | #endif |
75 | const char *fw_name; | 77 | const char *fw_name; |
@@ -133,7 +135,7 @@ static inline void fw_state_done(struct fw_priv *fw_priv) | |||
133 | int assign_fw(struct firmware *fw, struct device *device, | 135 | int assign_fw(struct firmware *fw, struct device *device, |
134 | enum fw_opt opt_flags); | 136 | enum fw_opt opt_flags); |
135 | 137 | ||
136 | #ifdef CONFIG_FW_LOADER_USER_HELPER | 138 | #ifdef CONFIG_FW_LOADER_PAGED_BUF |
137 | void fw_free_paged_buf(struct fw_priv *fw_priv); | 139 | void fw_free_paged_buf(struct fw_priv *fw_priv); |
138 | int fw_grow_paged_buf(struct fw_priv *fw_priv, int pages_needed); | 140 | int fw_grow_paged_buf(struct fw_priv *fw_priv, int pages_needed); |
139 | int fw_map_paged_buf(struct fw_priv *fw_priv); | 141 | int fw_map_paged_buf(struct fw_priv *fw_priv); |
diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index 7e12732f4705..bf44c79beae9 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c | |||
@@ -33,6 +33,7 @@ | |||
33 | #include <linux/syscore_ops.h> | 33 | #include <linux/syscore_ops.h> |
34 | #include <linux/reboot.h> | 34 | #include <linux/reboot.h> |
35 | #include <linux/security.h> | 35 | #include <linux/security.h> |
36 | #include <linux/xz.h> | ||
36 | 37 | ||
37 | #include <generated/utsrelease.h> | 38 | #include <generated/utsrelease.h> |
38 | 39 | ||
@@ -266,7 +267,7 @@ static void free_fw_priv(struct fw_priv *fw_priv) | |||
266 | spin_unlock(&fwc->lock); | 267 | spin_unlock(&fwc->lock); |
267 | } | 268 | } |
268 | 269 | ||
269 | #ifdef CONFIG_FW_LOADER_USER_HELPER | 270 | #ifdef CONFIG_FW_LOADER_PAGED_BUF |
270 | void fw_free_paged_buf(struct fw_priv *fw_priv) | 271 | void fw_free_paged_buf(struct fw_priv *fw_priv) |
271 | { | 272 | { |
272 | int i; | 273 | int i; |
@@ -335,6 +336,105 @@ int fw_map_paged_buf(struct fw_priv *fw_priv) | |||
335 | } | 336 | } |
336 | #endif | 337 | #endif |
337 | 338 | ||
339 | /* | ||
340 | * XZ-compressed firmware support | ||
341 | */ | ||
342 | #ifdef CONFIG_FW_LOADER_COMPRESS | ||
343 | /* show an error and return the standard error code */ | ||
344 | static int fw_decompress_xz_error(struct device *dev, enum xz_ret xz_ret) | ||
345 | { | ||
346 | if (xz_ret != XZ_STREAM_END) { | ||
347 | dev_warn(dev, "xz decompression failed (xz_ret=%d)\n", xz_ret); | ||
348 | return xz_ret == XZ_MEM_ERROR ? -ENOMEM : -EINVAL; | ||
349 | } | ||
350 | return 0; | ||
351 | } | ||
352 | |||
353 | /* single-shot decompression onto the pre-allocated buffer */ | ||
354 | static int fw_decompress_xz_single(struct device *dev, struct fw_priv *fw_priv, | ||
355 | size_t in_size, const void *in_buffer) | ||
356 | { | ||
357 | struct xz_dec *xz_dec; | ||
358 | struct xz_buf xz_buf; | ||
359 | enum xz_ret xz_ret; | ||
360 | |||
361 | xz_dec = xz_dec_init(XZ_SINGLE, (u32)-1); | ||
362 | if (!xz_dec) | ||
363 | return -ENOMEM; | ||
364 | |||
365 | xz_buf.in_size = in_size; | ||
366 | xz_buf.in = in_buffer; | ||
367 | xz_buf.in_pos = 0; | ||
368 | xz_buf.out_size = fw_priv->allocated_size; | ||
369 | xz_buf.out = fw_priv->data; | ||
370 | xz_buf.out_pos = 0; | ||
371 | |||
372 | xz_ret = xz_dec_run(xz_dec, &xz_buf); | ||
373 | xz_dec_end(xz_dec); | ||
374 | |||
375 | fw_priv->size = xz_buf.out_pos; | ||
376 | return fw_decompress_xz_error(dev, xz_ret); | ||
377 | } | ||
378 | |||
379 | /* decompression on paged buffer and map it */ | ||
380 | static int fw_decompress_xz_pages(struct device *dev, struct fw_priv *fw_priv, | ||
381 | size_t in_size, const void *in_buffer) | ||
382 | { | ||
383 | struct xz_dec *xz_dec; | ||
384 | struct xz_buf xz_buf; | ||
385 | enum xz_ret xz_ret; | ||
386 | struct page *page; | ||
387 | int err = 0; | ||
388 | |||
389 | xz_dec = xz_dec_init(XZ_DYNALLOC, (u32)-1); | ||
390 | if (!xz_dec) | ||
391 | return -ENOMEM; | ||
392 | |||
393 | xz_buf.in_size = in_size; | ||
394 | xz_buf.in = in_buffer; | ||
395 | xz_buf.in_pos = 0; | ||
396 | |||
397 | fw_priv->is_paged_buf = true; | ||
398 | fw_priv->size = 0; | ||
399 | do { | ||
400 | if (fw_grow_paged_buf(fw_priv, fw_priv->nr_pages + 1)) { | ||
401 | err = -ENOMEM; | ||
402 | goto out; | ||
403 | } | ||
404 | |||
405 | /* decompress onto the new allocated page */ | ||
406 | page = fw_priv->pages[fw_priv->nr_pages - 1]; | ||
407 | xz_buf.out = kmap(page); | ||
408 | xz_buf.out_pos = 0; | ||
409 | xz_buf.out_size = PAGE_SIZE; | ||
410 | xz_ret = xz_dec_run(xz_dec, &xz_buf); | ||
411 | kunmap(page); | ||
412 | fw_priv->size += xz_buf.out_pos; | ||
413 | /* partial decompression means either end or error */ | ||
414 | if (xz_buf.out_pos != PAGE_SIZE) | ||
415 | break; | ||
416 | } while (xz_ret == XZ_OK); | ||
417 | |||
418 | err = fw_decompress_xz_error(dev, xz_ret); | ||
419 | if (!err) | ||
420 | err = fw_map_paged_buf(fw_priv); | ||
421 | |||
422 | out: | ||
423 | xz_dec_end(xz_dec); | ||
424 | return err; | ||
425 | } | ||
426 | |||
427 | static int fw_decompress_xz(struct device *dev, struct fw_priv *fw_priv, | ||
428 | size_t in_size, const void *in_buffer) | ||
429 | { | ||
430 | /* if the buffer is pre-allocated, we can perform in single-shot mode */ | ||
431 | if (fw_priv->data) | ||
432 | return fw_decompress_xz_single(dev, fw_priv, in_size, in_buffer); | ||
433 | else | ||
434 | return fw_decompress_xz_pages(dev, fw_priv, in_size, in_buffer); | ||
435 | } | ||
436 | #endif /* CONFIG_FW_LOADER_COMPRESS */ | ||
437 | |||
338 | /* direct firmware loading support */ | 438 | /* direct firmware loading support */ |
339 | static char fw_path_para[256]; | 439 | static char fw_path_para[256]; |
340 | static const char * const fw_path[] = { | 440 | static const char * const fw_path[] = { |
@@ -354,7 +454,12 @@ module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644); | |||
354 | MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path"); | 454 | MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path"); |
355 | 455 | ||
356 | static int | 456 | static int |
357 | fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) | 457 | fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, |
458 | const char *suffix, | ||
459 | int (*decompress)(struct device *dev, | ||
460 | struct fw_priv *fw_priv, | ||
461 | size_t in_size, | ||
462 | const void *in_buffer)) | ||
358 | { | 463 | { |
359 | loff_t size; | 464 | loff_t size; |
360 | int i, len; | 465 | int i, len; |
@@ -362,9 +467,11 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) | |||
362 | char *path; | 467 | char *path; |
363 | enum kernel_read_file_id id = READING_FIRMWARE; | 468 | enum kernel_read_file_id id = READING_FIRMWARE; |
364 | size_t msize = INT_MAX; | 469 | size_t msize = INT_MAX; |
470 | void *buffer = NULL; | ||
365 | 471 | ||
366 | /* Already populated data member means we're loading into a buffer */ | 472 | /* Already populated data member means we're loading into a buffer */ |
367 | if (fw_priv->data) { | 473 | if (!decompress && fw_priv->data) { |
474 | buffer = fw_priv->data; | ||
368 | id = READING_FIRMWARE_PREALLOC_BUFFER; | 475 | id = READING_FIRMWARE_PREALLOC_BUFFER; |
369 | msize = fw_priv->allocated_size; | 476 | msize = fw_priv->allocated_size; |
370 | } | 477 | } |
@@ -378,15 +485,15 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) | |||
378 | if (!fw_path[i][0]) | 485 | if (!fw_path[i][0]) |
379 | continue; | 486 | continue; |
380 | 487 | ||
381 | len = snprintf(path, PATH_MAX, "%s/%s", | 488 | len = snprintf(path, PATH_MAX, "%s/%s%s", |
382 | fw_path[i], fw_priv->fw_name); | 489 | fw_path[i], fw_priv->fw_name, suffix); |
383 | if (len >= PATH_MAX) { | 490 | if (len >= PATH_MAX) { |
384 | rc = -ENAMETOOLONG; | 491 | rc = -ENAMETOOLONG; |
385 | break; | 492 | break; |
386 | } | 493 | } |
387 | 494 | ||
388 | fw_priv->size = 0; | 495 | fw_priv->size = 0; |
389 | rc = kernel_read_file_from_path(path, &fw_priv->data, &size, | 496 | rc = kernel_read_file_from_path(path, &buffer, &size, |
390 | msize, id); | 497 | msize, id); |
391 | if (rc) { | 498 | if (rc) { |
392 | if (rc != -ENOENT) | 499 | if (rc != -ENOENT) |
@@ -397,8 +504,24 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) | |||
397 | path); | 504 | path); |
398 | continue; | 505 | continue; |
399 | } | 506 | } |
400 | dev_dbg(device, "direct-loading %s\n", fw_priv->fw_name); | 507 | if (decompress) { |
401 | fw_priv->size = size; | 508 | dev_dbg(device, "f/w decompressing %s\n", |
509 | fw_priv->fw_name); | ||
510 | rc = decompress(device, fw_priv, size, buffer); | ||
511 | /* discard the superfluous original content */ | ||
512 | vfree(buffer); | ||
513 | buffer = NULL; | ||
514 | if (rc) { | ||
515 | fw_free_paged_buf(fw_priv); | ||
516 | continue; | ||
517 | } | ||
518 | } else { | ||
519 | dev_dbg(device, "direct-loading %s\n", | ||
520 | fw_priv->fw_name); | ||
521 | if (!fw_priv->data) | ||
522 | fw_priv->data = buffer; | ||
523 | fw_priv->size = size; | ||
524 | } | ||
402 | fw_state_done(fw_priv); | 525 | fw_state_done(fw_priv); |
403 | break; | 526 | break; |
404 | } | 527 | } |
@@ -645,7 +768,13 @@ _request_firmware(const struct firmware **firmware_p, const char *name, | |||
645 | if (ret <= 0) /* error or already assigned */ | 768 | if (ret <= 0) /* error or already assigned */ |
646 | goto out; | 769 | goto out; |
647 | 770 | ||
648 | ret = fw_get_filesystem_firmware(device, fw->priv); | 771 | ret = fw_get_filesystem_firmware(device, fw->priv, "", NULL); |
772 | #ifdef CONFIG_FW_LOADER_COMPRESS | ||
773 | if (ret == -ENOENT) | ||
774 | ret = fw_get_filesystem_firmware(device, fw->priv, ".xz", | ||
775 | fw_decompress_xz); | ||
776 | #endif | ||
777 | |||
649 | if (ret) { | 778 | if (ret) { |
650 | if (!(opt_flags & FW_OPT_NO_WARN)) | 779 | if (!(opt_flags & FW_OPT_NO_WARN)) |
651 | dev_warn(device, | 780 | dev_warn(device, |