summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2019-06-11 08:26:25 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2019-06-18 03:11:22 -0400
commit82fd7a8142a10b8eb41313074b3859d82c0857dc (patch)
treec98ad021f2d262809d9155ad443c2ea009b78a4e
parent5342e7093ff298d9cbd40f9342b607adb02b2dd0 (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/Kconfig18
-rw-r--r--drivers/base/firmware_loader/firmware.h8
-rw-r--r--drivers/base/firmware_loader/main.c147
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
27if FW_LOADER 27if FW_LOADER
28 28
29config FW_LOADER_PAGED_BUF
30 bool
31
29config EXTRA_FIRMWARE 32config 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
68config FW_LOADER_USER_HELPER 71config 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
158config 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
154endif # FW_LOADER 172endif # FW_LOADER
155endmenu 173endmenu
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)
133int assign_fw(struct firmware *fw, struct device *device, 135int 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
137void fw_free_paged_buf(struct fw_priv *fw_priv); 139void fw_free_paged_buf(struct fw_priv *fw_priv);
138int fw_grow_paged_buf(struct fw_priv *fw_priv, int pages_needed); 140int fw_grow_paged_buf(struct fw_priv *fw_priv, int pages_needed);
139int fw_map_paged_buf(struct fw_priv *fw_priv); 141int 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
270void fw_free_paged_buf(struct fw_priv *fw_priv) 271void 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 */
344static 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 */
354static 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 */
380static 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
427static 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 */
339static char fw_path_para[256]; 439static char fw_path_para[256];
340static const char * const fw_path[] = { 440static const char * const fw_path[] = {
@@ -354,7 +454,12 @@ module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644);
354MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path"); 454MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path");
355 455
356static int 456static int
357fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) 457fw_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,