Messages in this thread Patch in this message | | | From | Takashi Iwai <> | Subject | [PATCH 5/5] firmware: Add support for loading compressed files | Date | Mon, 20 May 2019 11:26:47 +0200 |
| |
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, the patch re-uses the paged buffer that was used for the user-mode fallback, and puts the decompressed content page by page as well as a new kconfig for enabling the paged buffer support.
Signed-off-by: Takashi Iwai <tiwai@suse.de> --- drivers/base/firmware_loader/Kconfig | 18 +++++ drivers/base/firmware_loader/firmware.h | 8 +- drivers/base/firmware_loader/main.c | 135 +++++++++++++++++++++++++++++--- 3 files changed, 149 insertions(+), 12 deletions(-)
diff --git a/drivers/base/firmware_loader/Kconfig b/drivers/base/firmware_loader/Kconfig index 38f2da6f5c2b..cc86d51d2999 100644 --- a/drivers/base/firmware_loader/Kconfig +++ b/drivers/base/firmware_loader/Kconfig @@ -26,6 +26,9 @@ config FW_LOADER if FW_LOADER +config FW_LOADER_PAGED_BUF + bool + config EXTRA_FIRMWARE string "Build named firmware blobs into the kernel binary" help @@ -67,6 +70,7 @@ config EXTRA_FIRMWARE_DIR config FW_LOADER_USER_HELPER bool "Enable the firmware sysfs fallback mechanism" + select FW_LOADER_PAGED_BUF help This option enables a sysfs loading facility to enable firmware loading to the kernel through userspace as a fallback mechanism @@ -151,5 +155,19 @@ config FW_LOADER_USER_HELPER_FALLBACK If you are unsure about this, say N here. +config FW_LOADER_COMPRESS + bool "Enable compressed firmware support" + select FW_LOADER_PAGED_BUF + select XZ_DEC + help + This option enables the support for loading compressed firmware + files. The caller of firmware API receives the decompressed file + content. The compressed file is loaded as a fallback, only after + trying to load the raw file at first. + + Currently only XZ-compressed files are supported, and they have to + be compressed with either none or crc32 integrity check type (the + -C crc32 option of xz command). + endif # FW_LOADER 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 { void *data; size_t size; size_t allocated_size; -#ifdef CONFIG_FW_LOADER_USER_HELPER +#ifdef CONFIG_FW_LOADER_PAGED_BUF bool is_paged_buf; - bool need_uevent; struct page **pages; int nr_pages; int page_array_size; +#endif +#ifdef CONFIG_FW_LOADER_USER_HELPER + bool need_uevent; struct list_head pending_list; #endif const char *fw_name; @@ -133,7 +135,7 @@ static inline void fw_state_done(struct fw_priv *fw_priv) int assign_fw(struct firmware *fw, struct device *device, enum fw_opt opt_flags); -#ifdef CONFIG_FW_LOADER_USER_HELPER +#ifdef CONFIG_FW_LOADER_PAGED_BUF void fw_free_paged_buf(struct fw_priv *fw_priv); int fw_grow_paged_buf(struct fw_priv *fw_priv, int pages_needed); 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..b070d6b4f4f1 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -33,6 +33,7 @@ #include <linux/syscore_ops.h> #include <linux/reboot.h> #include <linux/security.h> +#include <linux/xz.h> #include <generated/utsrelease.h> @@ -266,7 +267,7 @@ static void free_fw_priv(struct fw_priv *fw_priv) spin_unlock(&fwc->lock); } -#ifdef CONFIG_FW_LOADER_USER_HELPER +#ifdef CONFIG_FW_LOADER_PAGED_BUF void fw_free_paged_buf(struct fw_priv *fw_priv) { int i; @@ -335,6 +336,98 @@ int fw_map_paged_buf(struct fw_priv *fw_priv) } #endif +/* + * compressed firmware support + */ +#ifdef CONFIG_FW_LOADER_COMPRESS +/* single-shot decompression onto the pre-allocated buffer */ +static enum xz_ret fw_decompress_single(struct fw_priv *fw_priv, + struct xz_dec *xz_dec, + struct xz_buf *xz_buf) +{ + enum xz_ret xz_ret; + + xz_buf->out_pos = 0; + xz_buf->out = fw_priv->data; + xz_buf->out_size = fw_priv->allocated_size; + xz_ret = xz_dec_run(xz_dec, xz_buf); + fw_priv->size = xz_buf->out_pos; + return xz_ret; +} + +/* decompression on paged buffer and map it */ +static enum xz_ret fw_decompress_pages(struct fw_priv *fw_priv, + struct xz_dec *xz_dec, + struct xz_buf *xz_buf) +{ + struct page *page; + enum xz_ret xz_ret; + + fw_priv->is_paged_buf = true; + fw_priv->size = 0; + do { + if (fw_grow_paged_buf(fw_priv, fw_priv->nr_pages + 1)) + return XZ_MEM_ERROR; + + page = fw_priv->pages[fw_priv->nr_pages - 1]; + xz_buf->out = kmap(page); + xz_buf->out_pos = 0; + xz_buf->out_size = PAGE_SIZE; + xz_ret = xz_dec_run(xz_dec, xz_buf); + kunmap(page); + fw_priv->size += xz_buf->out_pos; + } while (xz_ret == XZ_OK); + + if (xz_ret == XZ_STREAM_END) { + if (fw_map_paged_buf(fw_priv)) + return XZ_MEM_ERROR; + } + + return xz_ret; +} + +static int fw_decompress_buffer(struct device *dev, struct fw_priv *fw_priv, + size_t in_size, const void *in_buffer) +{ + struct xz_dec *xz_dec; + struct xz_buf xz_buf; + enum xz_ret xz_ret; + bool singleshot = false; + + /* if the buffer is pre-allocaed, we can perform in single-shot mode */ + if (fw_priv->data) + singleshot = true; + + xz_dec = xz_dec_init(singleshot ? XZ_SINGLE : XZ_DYNALLOC, (u32)-1); + if (!xz_dec) { + dev_warn(dev, "f/w decompression init failed\n"); + return -ENOMEM; + } + + xz_buf.in_size = in_size; + xz_buf.in = in_buffer; + xz_buf.in_pos = 0; + if (singleshot) + xz_ret = fw_decompress_single(fw_priv, xz_dec, &xz_buf); + else + xz_ret = fw_decompress_pages(fw_priv, xz_dec, &xz_buf); + xz_dec_end(xz_dec); + + if (xz_ret != XZ_STREAM_END) { + dev_warn(dev, "f/w decompression failed (xz_ret=%d)\n", xz_ret); + return xz_ret == XZ_MEM_ERROR ? -ENOMEM : -EINVAL; + } + + return 0; +} +#else /* CONFIG_FW_LOADER_COMPRESS */ +static inline int fw_decompress_buffer(struct device *dev, struct fw_priv *fw, + size_t in_size, const void *in_buffer) +{ + return -ENXIO; +} +#endif /* CONFIG_FW_LOADER_COMPRESS */ + /* direct firmware loading support */ static char fw_path_para[256]; static const char * const fw_path[] = { @@ -354,7 +447,8 @@ module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644); MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path"); static int -fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) +fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, + bool decompress) { loff_t size; int i, len; @@ -362,9 +456,12 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) char *path; enum kernel_read_file_id id = READING_FIRMWARE; size_t msize = INT_MAX; + void *buffer = NULL; + const char *suffix = decompress ? ".xz" : ""; /* Already populated data member means we're loading into a buffer */ - if (fw_priv->data) { + if (!decompress && fw_priv->data) { + buffer = fw_priv->data; id = READING_FIRMWARE_PREALLOC_BUFFER; msize = fw_priv->allocated_size; } @@ -378,15 +475,15 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) if (!fw_path[i][0]) continue; - len = snprintf(path, PATH_MAX, "%s/%s", - fw_path[i], fw_priv->fw_name); + len = snprintf(path, PATH_MAX, "%s/%s%s", + fw_path[i], fw_priv->fw_name, suffix); if (len >= PATH_MAX) { rc = -ENAMETOOLONG; break; } fw_priv->size = 0; - rc = kernel_read_file_from_path(path, &fw_priv->data, &size, + rc = kernel_read_file_from_path(path, &buffer, &size, msize, id); if (rc) { if (rc != -ENOENT) @@ -397,8 +494,25 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv) path); continue; } - dev_dbg(device, "direct-loading %s\n", fw_priv->fw_name); - fw_priv->size = size; + if (decompress) { + dev_dbg(device, "f/w decompressing %s\n", + fw_priv->fw_name); + rc = fw_decompress_buffer(device, fw_priv, size, + buffer); + /* discard the superfluous original content */ + vfree(buffer); + buffer = NULL; + if (rc) { + fw_free_paged_buf(fw_priv); + continue; + } + } else { + dev_dbg(device, "direct-loading %s\n", + fw_priv->fw_name); + if (!fw_priv->data) + fw_priv->data = buffer; + fw_priv->size = size; + } fw_state_done(fw_priv); break; } @@ -645,7 +759,10 @@ _request_firmware(const struct firmware **firmware_p, const char *name, if (ret <= 0) /* error or already assigned */ goto out; - ret = fw_get_filesystem_firmware(device, fw->priv); + ret = fw_get_filesystem_firmware(device, fw->priv, false); + if (IS_ENABLED(CONFIG_FW_LOADER_COMPRESS) && ret == -ENOENT) + ret = fw_get_filesystem_firmware(device, fw->priv, true); + if (ret) { if (!(opt_flags & FW_OPT_NO_WARN)) dev_warn(device, -- 2.16.4
| |