firmware: Add the support for ZSTD-compressed firmware files
authorTakashi Iwai <tiwai@suse.de>
Thu, 21 Apr 2022 15:29:04 +0000 (17:29 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 22 Apr 2022 06:51:16 +0000 (08:51 +0200)
As the growing demand on ZSTD compressions, there have been requests
for the support of ZSTD-compressed firmware files, so here it is:
this patch extends the firmware loader code to allow loading ZSTD
files.  The implementation is fairly straightforward, it just adds a
ZSTD decompression routine for the file expander.  (And the code is
even simpler than XZ thanks to the ZSTD API that gives the original
decompressed size from the header.)

Link: https://lore.kernel.org/all/20210127154939.13288-1-tiwai@suse.de/
Tested-by: Piotr Gorski <lucjan.lucjanov@gmail.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Link: https://lore.kernel.org/r/20220421152908.4718-2-tiwai@suse.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/base/firmware_loader/Kconfig
drivers/base/firmware_loader/main.c

index 38f3b66bf52b27457b0e571c286bae0565efec66..08bb50451a969f1af1cc2904976fd1ef739f603a 100644 (file)
@@ -159,21 +159,33 @@ config FW_LOADER_USER_HELPER_FALLBACK
 
 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
          loading the raw file failed at first.
 
-         Currently only XZ-compressed files are supported, and they have to
-         be compressed with either none or crc32 integrity check type (pass
-         "-C crc32" option to xz command).
-
          Compressed firmware support does not apply to firmware images
          that are built into the kernel image (CONFIG_EXTRA_FIRMWARE).
 
+if FW_LOADER_COMPRESS
+config FW_LOADER_COMPRESS_XZ
+       bool "Enable XZ-compressed firmware support"
+       select FW_LOADER_PAGED_BUF
+       select XZ_DEC
+       help
+         This option adds the support for XZ-compressed files.
+         The files have to be compressed with either none or crc32
+         integrity check type (pass "-C crc32" option to xz command).
+
+config FW_LOADER_COMPRESS_ZSTD
+       bool "Enable ZSTD-compressed firmware support"
+       select ZSTD_DECOMPRESS
+       help
+         This option adds the support for ZSTD-compressed files.
+
+endif # FW_LOADER_COMPRESS
+
 config FW_CACHE
        bool "Enable firmware caching during suspend"
        depends on PM_SLEEP
index 94d1789a233e0f436e51cc9d214c139bb8dceb80..74830aeec7f6562f7e3b592b9a21f800170e2e0f 100644 (file)
@@ -35,6 +35,7 @@
 #include <linux/syscore_ops.h>
 #include <linux/reboot.h>
 #include <linux/security.h>
+#include <linux/zstd.h>
 #include <linux/xz.h>
 
 #include <generated/utsrelease.h>
@@ -304,10 +305,74 @@ int fw_map_paged_buf(struct fw_priv *fw_priv)
 }
 #endif
 
+/*
+ * ZSTD-compressed firmware support
+ */
+#ifdef CONFIG_FW_LOADER_COMPRESS_ZSTD
+static int fw_decompress_zstd(struct device *dev, struct fw_priv *fw_priv,
+                             size_t in_size, const void *in_buffer)
+{
+       size_t len, out_size, workspace_size;
+       void *workspace, *out_buf;
+       zstd_dctx *ctx;
+       int err;
+
+       if (fw_priv->allocated_size) {
+               out_size = fw_priv->allocated_size;
+               out_buf = fw_priv->data;
+       } else {
+               zstd_frame_header params;
+
+               if (zstd_get_frame_header(&params, in_buffer, in_size) ||
+                   params.frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN) {
+                       dev_dbg(dev, "%s: invalid zstd header\n", __func__);
+                       return -EINVAL;
+               }
+               out_size = params.frameContentSize;
+               out_buf = vzalloc(out_size);
+               if (!out_buf)
+                       return -ENOMEM;
+       }
+
+       workspace_size = zstd_dctx_workspace_bound();
+       workspace = kvzalloc(workspace_size, GFP_KERNEL);
+       if (!workspace) {
+               err = -ENOMEM;
+               goto error;
+       }
+
+       ctx = zstd_init_dctx(workspace, workspace_size);
+       if (!ctx) {
+               dev_dbg(dev, "%s: failed to initialize context\n", __func__);
+               err = -EINVAL;
+               goto error;
+       }
+
+       len = zstd_decompress_dctx(ctx, out_buf, out_size, in_buffer, in_size);
+       if (zstd_is_error(len)) {
+               dev_dbg(dev, "%s: failed to decompress: %d\n", __func__,
+                       zstd_get_error_code(len));
+               err = -EINVAL;
+               goto error;
+       }
+
+       if (!fw_priv->allocated_size)
+               fw_priv->data = out_buf;
+       fw_priv->size = len;
+       err = 0;
+
+ error:
+       kvfree(workspace);
+       if (err && !fw_priv->allocated_size)
+               vfree(out_buf);
+       return err;
+}
+#endif /* CONFIG_FW_LOADER_COMPRESS_ZSTD */
+
 /*
  * XZ-compressed firmware support
  */
-#ifdef CONFIG_FW_LOADER_COMPRESS
+#ifdef CONFIG_FW_LOADER_COMPRESS_XZ
 /* show an error and return the standard error code */
 static int fw_decompress_xz_error(struct device *dev, enum xz_ret xz_ret)
 {
@@ -401,7 +466,7 @@ static int fw_decompress_xz(struct device *dev, struct fw_priv *fw_priv,
        else
                return fw_decompress_xz_pages(dev, fw_priv, in_size, in_buffer);
 }
-#endif /* CONFIG_FW_LOADER_COMPRESS */
+#endif /* CONFIG_FW_LOADER_COMPRESS_XZ */
 
 /* direct firmware loading support */
 static char fw_path_para[256];
@@ -757,7 +822,12 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
        if (!(opt_flags & FW_OPT_PARTIAL))
                nondirect = true;
 
-#ifdef CONFIG_FW_LOADER_COMPRESS
+#ifdef CONFIG_FW_LOADER_COMPRESS_ZSTD
+       if (ret == -ENOENT && nondirect)
+               ret = fw_get_filesystem_firmware(device, fw->priv, ".zst",
+                                                fw_decompress_zstd);
+#endif
+#ifdef CONFIG_FW_LOADER_COMPRESS_XZ
        if (ret == -ENOENT && nondirect)
                ret = fw_get_filesystem_firmware(device, fw->priv, ".xz",
                                                 fw_decompress_xz);