Commit 23cfbc6e authored by Takashi Iwai's avatar Takashi Iwai Committed by Greg Kroah-Hartman
Browse files

firmware: Add the support for ZSTD-compressed firmware files

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: default avatarPiotr Gorski <lucjan.lucjanov@gmail.com>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
Link: https://lore.kernel.org/r/20220421152908.4718-2-tiwai@suse.de


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent ce522ba9
Loading
Loading
Loading
Loading
+18 −6
Original line number Diff line number Diff line
@@ -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
+73 −3
Original line number Diff line number Diff line
@@ -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);