Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Takashi Iwai and committed by
Greg Kroah-Hartman
82fd7a81 5342e709

+161 -12
+18
drivers/base/firmware_loader/Kconfig
··· 26 26 27 27 if FW_LOADER 28 28 29 + config FW_LOADER_PAGED_BUF 30 + bool 31 + 29 32 config EXTRA_FIRMWARE 30 33 string "Build named firmware blobs into the kernel binary" 31 34 help ··· 70 67 71 68 config FW_LOADER_USER_HELPER 72 69 bool "Enable the firmware sysfs fallback mechanism" 70 + select FW_LOADER_PAGED_BUF 73 71 help 74 72 This option enables a sysfs loading facility to enable firmware 75 73 loading to the kernel through userspace as a fallback mechanism ··· 154 150 /proc/sys/kernel/firmware_config/force_sysfs_fallback 155 151 156 152 If you are unsure about this, say N here. 153 + 154 + config FW_LOADER_COMPRESS 155 + bool "Enable compressed firmware support" 156 + select FW_LOADER_PAGED_BUF 157 + select XZ_DEC 158 + help 159 + This option enables the support for loading compressed firmware 160 + files. The caller of firmware API receives the decompressed file 161 + content. The compressed file is loaded as a fallback, only after 162 + loading the raw file failed at first. 163 + 164 + Currently only XZ-compressed files are supported, and they have to 165 + be compressed with either none or crc32 integrity check type (pass 166 + "-C crc32" option to xz command). 157 167 158 168 endif # FW_LOADER 159 169 endmenu
+5 -3
drivers/base/firmware_loader/firmware.h
··· 64 64 void *data; 65 65 size_t size; 66 66 size_t allocated_size; 67 - #ifdef CONFIG_FW_LOADER_USER_HELPER 67 + #ifdef CONFIG_FW_LOADER_PAGED_BUF 68 68 bool is_paged_buf; 69 - bool need_uevent; 70 69 struct page **pages; 71 70 int nr_pages; 72 71 int page_array_size; 72 + #endif 73 + #ifdef CONFIG_FW_LOADER_USER_HELPER 74 + bool need_uevent; 73 75 struct list_head pending_list; 74 76 #endif 75 77 const char *fw_name; ··· 135 133 int assign_fw(struct firmware *fw, struct device *device, 136 134 enum fw_opt opt_flags); 137 135 138 - #ifdef CONFIG_FW_LOADER_USER_HELPER 136 + #ifdef CONFIG_FW_LOADER_PAGED_BUF 139 137 void fw_free_paged_buf(struct fw_priv *fw_priv); 140 138 int fw_grow_paged_buf(struct fw_priv *fw_priv, int pages_needed); 141 139 int fw_map_paged_buf(struct fw_priv *fw_priv);
+138 -9
drivers/base/firmware_loader/main.c
··· 33 33 #include <linux/syscore_ops.h> 34 34 #include <linux/reboot.h> 35 35 #include <linux/security.h> 36 + #include <linux/xz.h> 36 37 37 38 #include <generated/utsrelease.h> 38 39 ··· 267 266 spin_unlock(&fwc->lock); 268 267 } 269 268 270 - #ifdef CONFIG_FW_LOADER_USER_HELPER 269 + #ifdef CONFIG_FW_LOADER_PAGED_BUF 271 270 void fw_free_paged_buf(struct fw_priv *fw_priv) 272 271 { 273 272 int i; ··· 336 335 } 337 336 #endif 338 337 338 + /* 339 + * XZ-compressed firmware support 340 + */ 341 + #ifdef CONFIG_FW_LOADER_COMPRESS 342 + /* show an error and return the standard error code */ 343 + static int fw_decompress_xz_error(struct device *dev, enum xz_ret xz_ret) 344 + { 345 + if (xz_ret != XZ_STREAM_END) { 346 + dev_warn(dev, "xz decompression failed (xz_ret=%d)\n", xz_ret); 347 + return xz_ret == XZ_MEM_ERROR ? -ENOMEM : -EINVAL; 348 + } 349 + return 0; 350 + } 351 + 352 + /* single-shot decompression onto the pre-allocated buffer */ 353 + static int fw_decompress_xz_single(struct device *dev, struct fw_priv *fw_priv, 354 + size_t in_size, const void *in_buffer) 355 + { 356 + struct xz_dec *xz_dec; 357 + struct xz_buf xz_buf; 358 + enum xz_ret xz_ret; 359 + 360 + xz_dec = xz_dec_init(XZ_SINGLE, (u32)-1); 361 + if (!xz_dec) 362 + return -ENOMEM; 363 + 364 + xz_buf.in_size = in_size; 365 + xz_buf.in = in_buffer; 366 + xz_buf.in_pos = 0; 367 + xz_buf.out_size = fw_priv->allocated_size; 368 + xz_buf.out = fw_priv->data; 369 + xz_buf.out_pos = 0; 370 + 371 + xz_ret = xz_dec_run(xz_dec, &xz_buf); 372 + xz_dec_end(xz_dec); 373 + 374 + fw_priv->size = xz_buf.out_pos; 375 + return fw_decompress_xz_error(dev, xz_ret); 376 + } 377 + 378 + /* decompression on paged buffer and map it */ 379 + static int fw_decompress_xz_pages(struct device *dev, struct fw_priv *fw_priv, 380 + size_t in_size, const void *in_buffer) 381 + { 382 + struct xz_dec *xz_dec; 383 + struct xz_buf xz_buf; 384 + enum xz_ret xz_ret; 385 + struct page *page; 386 + int err = 0; 387 + 388 + xz_dec = xz_dec_init(XZ_DYNALLOC, (u32)-1); 389 + if (!xz_dec) 390 + return -ENOMEM; 391 + 392 + xz_buf.in_size = in_size; 393 + xz_buf.in = in_buffer; 394 + xz_buf.in_pos = 0; 395 + 396 + fw_priv->is_paged_buf = true; 397 + fw_priv->size = 0; 398 + do { 399 + if (fw_grow_paged_buf(fw_priv, fw_priv->nr_pages + 1)) { 400 + err = -ENOMEM; 401 + goto out; 402 + } 403 + 404 + /* decompress onto the new allocated page */ 405 + page = fw_priv->pages[fw_priv->nr_pages - 1]; 406 + xz_buf.out = kmap(page); 407 + xz_buf.out_pos = 0; 408 + xz_buf.out_size = PAGE_SIZE; 409 + xz_ret = xz_dec_run(xz_dec, &xz_buf); 410 + kunmap(page); 411 + fw_priv->size += xz_buf.out_pos; 412 + /* partial decompression means either end or error */ 413 + if (xz_buf.out_pos != PAGE_SIZE) 414 + break; 415 + } while (xz_ret == XZ_OK); 416 + 417 + err = fw_decompress_xz_error(dev, xz_ret); 418 + if (!err) 419 + err = fw_map_paged_buf(fw_priv); 420 + 421 + out: 422 + xz_dec_end(xz_dec); 423 + return err; 424 + } 425 + 426 + static int fw_decompress_xz(struct device *dev, struct fw_priv *fw_priv, 427 + size_t in_size, const void *in_buffer) 428 + { 429 + /* if the buffer is pre-allocated, we can perform in single-shot mode */ 430 + if (fw_priv->data) 431 + return fw_decompress_xz_single(dev, fw_priv, in_size, in_buffer); 432 + else 433 + return fw_decompress_xz_pages(dev, fw_priv, in_size, in_buffer); 434 + } 435 + #endif /* CONFIG_FW_LOADER_COMPRESS */ 436 + 339 437 /* direct firmware loading support */ 340 438 static char fw_path_para[256]; 341 439 static const char * const fw_path[] = { ··· 454 354 MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path"); 455 355 456 356 static int 457 - fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) 357 + fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, 358 + const char *suffix, 359 + int (*decompress)(struct device *dev, 360 + struct fw_priv *fw_priv, 361 + size_t in_size, 362 + const void *in_buffer)) 458 363 { 459 364 loff_t size; 460 365 int i, len; ··· 467 362 char *path; 468 363 enum kernel_read_file_id id = READING_FIRMWARE; 469 364 size_t msize = INT_MAX; 365 + void *buffer = NULL; 470 366 471 367 /* Already populated data member means we're loading into a buffer */ 472 - if (fw_priv->data) { 368 + if (!decompress && fw_priv->data) { 369 + buffer = fw_priv->data; 473 370 id = READING_FIRMWARE_PREALLOC_BUFFER; 474 371 msize = fw_priv->allocated_size; 475 372 } ··· 485 378 if (!fw_path[i][0]) 486 379 continue; 487 380 488 - len = snprintf(path, PATH_MAX, "%s/%s", 489 - fw_path[i], fw_priv->fw_name); 381 + len = snprintf(path, PATH_MAX, "%s/%s%s", 382 + fw_path[i], fw_priv->fw_name, suffix); 490 383 if (len >= PATH_MAX) { 491 384 rc = -ENAMETOOLONG; 492 385 break; 493 386 } 494 387 495 388 fw_priv->size = 0; 496 - rc = kernel_read_file_from_path(path, &fw_priv->data, &size, 389 + rc = kernel_read_file_from_path(path, &buffer, &size, 497 390 msize, id); 498 391 if (rc) { 499 392 if (rc != -ENOENT) ··· 504 397 path); 505 398 continue; 506 399 } 507 - dev_dbg(device, "direct-loading %s\n", fw_priv->fw_name); 508 - fw_priv->size = size; 400 + if (decompress) { 401 + dev_dbg(device, "f/w decompressing %s\n", 402 + fw_priv->fw_name); 403 + rc = decompress(device, fw_priv, size, buffer); 404 + /* discard the superfluous original content */ 405 + vfree(buffer); 406 + buffer = NULL; 407 + if (rc) { 408 + fw_free_paged_buf(fw_priv); 409 + continue; 410 + } 411 + } else { 412 + dev_dbg(device, "direct-loading %s\n", 413 + fw_priv->fw_name); 414 + if (!fw_priv->data) 415 + fw_priv->data = buffer; 416 + fw_priv->size = size; 417 + } 509 418 fw_state_done(fw_priv); 510 419 break; 511 420 } ··· 768 645 if (ret <= 0) /* error or already assigned */ 769 646 goto out; 770 647 771 - ret = fw_get_filesystem_firmware(device, fw->priv); 648 + ret = fw_get_filesystem_firmware(device, fw->priv, "", NULL); 649 + #ifdef CONFIG_FW_LOADER_COMPRESS 650 + if (ret == -ENOENT) 651 + ret = fw_get_filesystem_firmware(device, fw->priv, ".xz", 652 + fw_decompress_xz); 653 + #endif 654 + 772 655 if (ret) { 773 656 if (!(opt_flags & FW_OPT_NO_WARN)) 774 657 dev_warn(device,