diff mbox series

[1/2] lib: add support for ZSTD-compressed kernel

Message ID 20200316135025.7579-1-oss@malat.biz (mailing list archive)
State New, archived
Headers show
Series [1/2] lib: add support for ZSTD-compressed kernel | expand

Commit Message

Petr Malat March 16, 2020, 1:50 p.m. UTC
Add support for extracting ZSTD-compressed kernel images, as well as
ZSTD-compressed initramfs.

Signed-off-by: Petr Malat <oss@malat.biz>
---
 include/linux/decompress/unzstd.h |  12 +++
 init/Kconfig                      |  15 ++-
 lib/Kconfig                       |   4 +
 lib/Makefile                      |   1 +
 lib/decompress.c                  |   5 +
 lib/decompress_unzstd.c           | 159 ++++++++++++++++++++++++++++++
 lib/zstd/decompress.c             |   2 +
 lib/zstd/fse_decompress.c         |   4 +-
 scripts/Makefile.lib              |   3 +
 usr/Kconfig                       |  24 +++++
 10 files changed, 227 insertions(+), 2 deletions(-)
 create mode 100644 include/linux/decompress/unzstd.h
 create mode 100644 lib/decompress_unzstd.c

Comments

Greg Kroah-Hartman March 16, 2020, 2:07 p.m. UTC | #1
On Mon, Mar 16, 2020 at 02:50:24PM +0100, Petr Malat wrote:
> Add support for extracting ZSTD-compressed kernel images, as well as
> ZSTD-compressed initramfs.
> 
> Signed-off-by: Petr Malat <oss@malat.biz>

That says _what_ you did here, but not _why_ you did this, or why anyone
would even want this.

thanks,

greg k-h
Petr Malat March 16, 2020, 2:35 p.m. UTC | #2
I have extended the description, thx.
  Petr

On Mon, Mar 16, 2020 at 03:07:26PM +0100, Greg KH wrote:
> On Mon, Mar 16, 2020 at 02:50:24PM +0100, Petr Malat wrote:
> > Add support for extracting ZSTD-compressed kernel images, as well as
> > ZSTD-compressed initramfs.
> > 
> > Signed-off-by: Petr Malat <oss@malat.biz>
> 
> That says _what_ you did here, but not _why_ you did this, or why anyone
> would even want this.
> 
> thanks,
> 
> greg k-h
Nick Terrell March 17, 2020, 9 p.m. UTC | #3
> On Mar 16, 2020, at 6:50 AM, Petr Malat <oss@malat.biz> wrote:
> 
> Add support for extracting ZSTD-compressed kernel images, as well as
> ZSTD-compressed initramfs.

Hi Petr,

Thanks for putting up this patch!

In the last month I rebased, retested, and refactored my patches [0][1], because
we had a new use case for a zstd compressed initramfs. We have been using
the patches I put up for quite some time internally, so we know they work well.

I will re-submit my patches today, once I write all the commit summaries and a
new cover letter. Thanks for the kick in the butt to resubmit it.

Your patches are missing several things that I have included in my patches [0][1]:
* Invocations of EXPORT_SYMBOL() and MODULE_LICENSE() in the pre-boot
  either emitted warnings or didn’t work in the pre-boot environment, unless that
  has changed.
* The memcpy() inside of ZSTD_copy8() which is the core of zstd’s hot loop gets
  outlined in the x86 preboot environment unless you use __builtin_memcpy().
  This destroys decompression speed.
* unzstd() can use less memory when fill & flush are both NULL by calling
  ZSTD_decompressDCtx().
* ZO_z_extra_bytes needs to be bumped because zstd can overlap more than
  Other compressors. If it isn’t you could corrupt the kernel.

I think it would be better to go with my patches. They have already been tested
in production for both correctness and performance, and I have boot tested on
x86, arm, and aarch64. I will make my case fully when I resubmit my patches.

Thanks again for submitting this, because I have been dawdling!
Nick

[0] https://lore.kernel.org/patchwork/patch/839674/
[1] https://lore.kernel.org/patchwork/patch/839675/

> Signed-off-by: Petr Malat <oss@malat.biz>
> ---
> include/linux/decompress/unzstd.h |  12 +++
> init/Kconfig                      |  15 ++-
> lib/Kconfig                       |   4 +
> lib/Makefile                      |   1 +
> lib/decompress.c                  |   5 +
> lib/decompress_unzstd.c           | 159 ++++++++++++++++++++++++++++++
> lib/zstd/decompress.c             |   2 +
> lib/zstd/fse_decompress.c         |   4 +-
> scripts/Makefile.lib              |   3 +
> usr/Kconfig                       |  24 +++++
> 10 files changed, 227 insertions(+), 2 deletions(-)
> create mode 100644 include/linux/decompress/unzstd.h
> create mode 100644 lib/decompress_unzstd.c
> 
> diff --git a/include/linux/decompress/unzstd.h b/include/linux/decompress/unzstd.h
> new file mode 100644
> index 000000000000..dd2c49d47456
> --- /dev/null
> +++ b/include/linux/decompress/unzstd.h
> @@ -0,0 +1,12 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef DECOMPRESS_UNZSTD_H
> +#define DECOMPRESS_UNZSTD_H
> +
> +int unzstd(unsigned char *inbuf, long len,
> +	long (*fill)(void*, unsigned long),
> +	long (*flush)(void*, unsigned long),
> +	unsigned char *output,
> +	long *pos,
> +	void (*error)(char *x));
> +#endif
> +
> diff --git a/init/Kconfig b/init/Kconfig
> index a34064a031a5..628eb3c290a2 100644
> --- a/init/Kconfig
> +++ b/init/Kconfig
> @@ -172,13 +172,16 @@ config HAVE_KERNEL_LZO
> config HAVE_KERNEL_LZ4
> 	bool
> 
> +config HAVE_KERNEL_ZSTD
> +	bool
> +
> config HAVE_KERNEL_UNCOMPRESSED
> 	bool
> 
> choice
> 	prompt "Kernel compression mode"
> 	default KERNEL_GZIP
> -	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_UNCOMPRESSED
> +	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_ZSTD || HAVE_KERNEL_UNCOMPRESSED
> 	help
> 	  The linux kernel is a kind of self-extracting executable.
> 	  Several compression algorithms are available, which differ
> @@ -257,6 +260,16 @@ config KERNEL_LZ4
> 	  is about 8% bigger than LZO. But the decompression speed is
> 	  faster than LZO.
> 
> +config KERNEL_ZSTD
> +	bool "ZSTD"
> +	depends on HAVE_KERNEL_ZSTD
> +	help
> +	  Its compression ratio is roughly 10% worst than xz, but the
> +	  decompression is 10x faster. Currently, this is one of the optimal
> +	  algorithms available in the kernel, as there isn't an algorithm,
> +	  which would provide a better compression ratio and a shorter
> +	  decompression time.
> +
> config KERNEL_UNCOMPRESSED
> 	bool "None"
> 	depends on HAVE_KERNEL_UNCOMPRESSED
> diff --git a/lib/Kconfig b/lib/Kconfig
> index 6e790dc55c5b..df301bd888d7 100644
> --- a/lib/Kconfig
> +++ b/lib/Kconfig
> @@ -329,6 +329,10 @@ config DECOMPRESS_LZ4
> 	select LZ4_DECOMPRESS
> 	tristate
> 
> +config DECOMPRESS_ZSTD
> +	select ZSTD_DECOMPRESS
> +	tristate
> +
> #
> # Generic allocator support is selected if needed
> #
> diff --git a/lib/Makefile b/lib/Makefile
> index 93217d44237f..3ab9f4c31f8b 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -158,6 +158,7 @@ lib-$(CONFIG_DECOMPRESS_LZMA) += decompress_unlzma.o
> lib-$(CONFIG_DECOMPRESS_XZ) += decompress_unxz.o
> lib-$(CONFIG_DECOMPRESS_LZO) += decompress_unlzo.o
> lib-$(CONFIG_DECOMPRESS_LZ4) += decompress_unlz4.o
> +lib-$(CONFIG_DECOMPRESS_ZSTD) += decompress_unzstd.o
> 
> obj-$(CONFIG_TEXTSEARCH) += textsearch.o
> obj-$(CONFIG_TEXTSEARCH_KMP) += ts_kmp.o
> diff --git a/lib/decompress.c b/lib/decompress.c
> index 857ab1af1ef3..ab3fc90ffc64 100644
> --- a/lib/decompress.c
> +++ b/lib/decompress.c
> @@ -13,6 +13,7 @@
> #include <linux/decompress/inflate.h>
> #include <linux/decompress/unlzo.h>
> #include <linux/decompress/unlz4.h>
> +#include <linux/decompress/unzstd.h>
> 
> #include <linux/types.h>
> #include <linux/string.h>
> @@ -37,6 +38,9 @@
> #ifndef CONFIG_DECOMPRESS_LZ4
> # define unlz4 NULL
> #endif
> +#ifndef CONFIG_DECOMPRESS_ZSTD
> +# define unzstd NULL
> +#endif
> 
> struct compress_format {
> 	unsigned char magic[2];
> @@ -52,6 +56,7 @@ static const struct compress_format compressed_formats[] __initconst = {
> 	{ {0xfd, 0x37}, "xz", unxz },
> 	{ {0x89, 0x4c}, "lzo", unlzo },
> 	{ {0x02, 0x21}, "lz4", unlz4 },
> +	{ {0x28, 0xb5}, "zstd", unzstd },
> 	{ {0, 0}, NULL, NULL }
> };
> 
> diff --git a/lib/decompress_unzstd.c b/lib/decompress_unzstd.c
> new file mode 100644
> index 000000000000..5af647a49885
> --- /dev/null
> +++ b/lib/decompress_unzstd.c
> @@ -0,0 +1,159 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Wrapper for decompressing ZSTD-compressed kernel, initramfs, and initrd
> + * Based on decompress_unlz4.c
> + *
> + * Copyright (C) 2020, Petr Malat <oss@malat.biz>
> + */
> +
> +#ifdef STATIC
> +#define PREBOOT
> +#include "zstd/zstd_internal.h"
> +#include "zstd/huf_decompress.c"
> +#include "zstd/entropy_common.c"
> +#include "zstd/fse_decompress.c"
> +#include "zstd/zstd_common.c"
> +#include "zstd/decompress.c"
> +#include "xxhash.c"
> +#else
> +#include <linux/decompress/unzstd.h>
> +#include <linux/zstd.h>
> +#endif
> +#include <linux/types.h>
> +#include <linux/decompress/mm.h>
> +#include <linux/compiler.h>
> +
> +STATIC inline int INIT unzstd(u8 *input, long in_len,
> +				long (*fill)(void *, unsigned long),
> +				long (*flush)(void *, unsigned long),
> +				u8 *output, long *posp,
> +				void (*error)(char *x))
> +{
> +	int ret = -1, ws = 1 << ZSTD_WINDOWLOG_MAX;
> +	u8 *inp, *outp;
> +	ZSTD_DStream *zstd;
> +	void *workspace;
> +	size_t workspace_size;
> +	ZSTD_outBuffer out;
> +	ZSTD_inBuffer in;
> +	unsigned long out_len;
> +	unsigned long pos;
> +
> +	if (output) {
> +		out_len = ULONG_MAX; // Caller knows data will fit
> +		outp = output;
> +	} else if (!flush) {
> +		error("NULL output pointer and no flush function provided");
> +		goto exit_0;
> +	} else {
> +		out_len = ZSTD_DStreamOutSize();
> +		outp = large_malloc(out_len);
> +		if (!outp) {
> +			error("Could not allocate output buffer");
> +			goto exit_0;
> +		}
> +	}
> +
> +	if (input && fill) {
> +		error("Both input pointer and fill function provided,");
> +		goto exit_1;
> +	} else if (input) {
> +		ZSTD_frameParams p;
> +
> +		inp = input;
> +		if (!ZSTD_getFrameParams(&p, input, in_len))
> +			ws = p.windowSize;
> +	} else if (!fill) {
> +		error("NULL input pointer and missing fill function");
> +		goto exit_1;
> +	} else {
> +		in_len = ZSTD_DStreamInSize();
> +		inp = large_malloc(in_len);
> +		if (!inp) {
> +			error("Could not allocate input buffer");
> +			goto exit_1;
> +		}
> +	}
> +
> +	workspace_size = ZSTD_DStreamWorkspaceBound(ws);
> +	workspace = large_malloc(workspace_size);
> +	if (!workspace) {
> +		error("Could not allocate workspace");
> +		goto exit_2;
> +	}
> +
> +	zstd = ZSTD_initDStream(ws, workspace, workspace_size);
> +	if (!zstd) {
> +		error("Could not initialize ZSTD");
> +		goto exit_3;
> +	}
> +
> +	in.src = inp;
> +	in.size = in_len;
> +	in.pos = 0;
> +	if (posp)
> +		*posp = 0;
> +
> +	for (;;) {
> +		if (fill) {
> +			in.size = fill(inp, in_len);
> +			if (in.size == 0)
> +				break;
> +		} else if (in.size == in.pos) {
> +			break;
> +		}
> +init:		out.dst = outp;
> +		out.size = out_len;
> +		out.pos = 0;
> +		pos = in.pos;
> +
> +		ret = ZSTD_decompressStream(zstd, &out, &in);
> +		if (posp)
> +			*posp += in.pos - pos;
> +		if (ZSTD_isError(ret)) {
> +			error("Decompression failed");
> +			ret = -EIO;
> +			goto exit_3;
> +		}
> +
> +		if (flush && out.pos) {
> +			if (flush(out.dst, out.pos) != out.pos) {
> +				ret = -EIO;
> +				goto exit_3;
> +			}
> +			goto init;
> +		}
> +
> +		if (ret == 0) {
> +			ret = ZSTD_resetDStream(zstd);
> +			if (ZSTD_isError(ret)) {
> +				ret = -EIO;
> +				goto exit_3;
> +			}
> +		}
> +		if (in.pos < in.size)
> +			goto init_out;
> +	}
> +
> +	ret = 0;
> +
> +exit_3:	large_free(workspace);
> +exit_2:	if (!input)
> +		large_free(inp);
> +exit_1:	if (!output)
> +		large_free(outp);
> +exit_0:	return ret;
> +}
> +
> +#ifdef PREBOOT
> +STATIC int INIT __decompress(unsigned char *buf, long in_len,
> +			      long (*fill)(void*, unsigned long),
> +			      long (*flush)(void*, unsigned long),
> +			      unsigned char *output, long out_len,
> +			      long *posp,
> +			      void (*error)(char *x)
> +	)
> +{
> +	return unzstd(buf, in_len, fill, flush, output, posp, error);
> +}
> +#endif
> diff --git a/lib/zstd/decompress.c b/lib/zstd/decompress.c
> index 269ee9a796c1..6a5e1ce22719 100644
> --- a/lib/zstd/decompress.c
> +++ b/lib/zstd/decompress.c
> @@ -42,9 +42,11 @@
> /*-*************************************
> *  Macros
> ***************************************/
> +#ifndef PREBOOT
> #define ZSTD_isError ERR_isError /* for inlining */
> #define FSE_isError ERR_isError
> #define HUF_isError ERR_isError
> +#endif
> 
> /*_*******************************************************
> *  Memory operations
> diff --git a/lib/zstd/fse_decompress.c b/lib/zstd/fse_decompress.c
> index a84300e5a013..bd4e9c891d96 100644
> --- a/lib/zstd/fse_decompress.c
> +++ b/lib/zstd/fse_decompress.c
> @@ -54,12 +54,13 @@
> /* **************************************************************
> *  Error Management
> ****************************************************************/
> -#define FSE_isError ERR_isError
> #define FSE_STATIC_ASSERT(c)                                   \
> 	{                                                      \
> 		enum { FSE_static_assert = 1 / (int)(!!(c)) }; \
> 	} /* use only *after* variable declarations */
> 
> +#ifndef PREBOOT
> +#define FSE_isError ERR_isError
> /* check and forward error code */
> #define CHECK_F(f)                  \
> 	{                           \
> @@ -67,6 +68,7 @@
> 		if (FSE_isError(e)) \
> 			return e;   \
> 	}
> +#endif
> 
> /* **************************************************************
> *  Templates
> diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
> index 3fa32f83b2d7..1c2f2dc528dc 100644
> --- a/scripts/Makefile.lib
> +++ b/scripts/Makefile.lib
> @@ -337,6 +337,9 @@ quiet_cmd_lz4 = LZ4     $@
>       cmd_lz4 = { cat $(real-prereqs) | lz4c -l -c1 stdin stdout; \
>                   $(size_append); } > $@
> 
> +quiet_cmd_zstd = ZSTD    $@
> +      cmd_zstd = { cat $(real-prereqs) | zstd -19 --zstd=wlog=21; $(size_append); } > $@
> +
> # U-Boot mkimage
> # ---------------------------------------------------------------------------
> 
> diff --git a/usr/Kconfig b/usr/Kconfig
> index a6b68503d177..892eb15957db 100644
> --- a/usr/Kconfig
> +++ b/usr/Kconfig
> @@ -106,6 +106,15 @@ config RD_LZ4
> 	  Support loading of a LZ4 encoded initial ramdisk or cpio buffer
> 	  If unsure, say N.
> 
> +config RD_ZSTD
> +	bool "Support initial ramdisk/ramfs compressed using ZSTD"
> +	default y
> +	depends on BLK_DEV_INITRD
> +	select DECOMPRESS_ZSTD
> +	help
> +	  Support loading of a ZSTD encoded initial ramdisk or cpio buffer
> +	  If unsure, say N.
> +
> choice
> 	prompt "Built-in initramfs compression mode"
> 	depends on INITRAMFS_SOURCE!=""
> @@ -214,6 +223,19 @@ config INITRAMFS_COMPRESSION_LZ4
> 	  If you choose this, keep in mind that most distros don't provide lz4
> 	  by default which could cause a build failure.
> 
> +config INITRAMFS_COMPRESSION_ZSTD
> +	bool "ZSTD"
> +	depends on RD_ZSTD
> +	help
> +	  Its compression ratio is roughly 10% worst than xz, but the
> +	  decompression is 10x faster. Currently, this is one of the optimal
> +	  algorithms available in the kernel, as there isn't an algorithm,
> +	  which would provide a better compression ratio and a shorter
> +	  decompression time.
> +
> +	  If you choose this, keep in mind that you may need to install the zstd
> +	  tool to be able to compress the initram.
> +
> endchoice
> 
> config INITRAMFS_COMPRESSION
> @@ -226,10 +248,12 @@ config INITRAMFS_COMPRESSION
> 	default ".xz"   if INITRAMFS_COMPRESSION_XZ
> 	default ".lzo"  if INITRAMFS_COMPRESSION_LZO
> 	default ".lz4"  if INITRAMFS_COMPRESSION_LZ4
> +	default ".zst"  if INITRAMFS_COMPRESSION_ZSTD
> 	default ".gz"   if RD_GZIP
> 	default ".lz4"  if RD_LZ4
> 	default ".lzo"  if RD_LZO
> 	default ".xz"   if RD_XZ
> 	default ".lzma" if RD_LZMA
> 	default ".bz2"  if RD_BZIP2
> +	default ".zst"  if RD_ZSTD
> 	default ""
> -- 
> 2.20.1
>
diff mbox series

Patch

diff --git a/include/linux/decompress/unzstd.h b/include/linux/decompress/unzstd.h
new file mode 100644
index 000000000000..dd2c49d47456
--- /dev/null
+++ b/include/linux/decompress/unzstd.h
@@ -0,0 +1,12 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef DECOMPRESS_UNZSTD_H
+#define DECOMPRESS_UNZSTD_H
+
+int unzstd(unsigned char *inbuf, long len,
+	long (*fill)(void*, unsigned long),
+	long (*flush)(void*, unsigned long),
+	unsigned char *output,
+	long *pos,
+	void (*error)(char *x));
+#endif
+
diff --git a/init/Kconfig b/init/Kconfig
index a34064a031a5..628eb3c290a2 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -172,13 +172,16 @@  config HAVE_KERNEL_LZO
 config HAVE_KERNEL_LZ4
 	bool
 
+config HAVE_KERNEL_ZSTD
+	bool
+
 config HAVE_KERNEL_UNCOMPRESSED
 	bool
 
 choice
 	prompt "Kernel compression mode"
 	default KERNEL_GZIP
-	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_UNCOMPRESSED
+	depends on HAVE_KERNEL_GZIP || HAVE_KERNEL_BZIP2 || HAVE_KERNEL_LZMA || HAVE_KERNEL_XZ || HAVE_KERNEL_LZO || HAVE_KERNEL_LZ4 || HAVE_KERNEL_ZSTD || HAVE_KERNEL_UNCOMPRESSED
 	help
 	  The linux kernel is a kind of self-extracting executable.
 	  Several compression algorithms are available, which differ
@@ -257,6 +260,16 @@  config KERNEL_LZ4
 	  is about 8% bigger than LZO. But the decompression speed is
 	  faster than LZO.
 
+config KERNEL_ZSTD
+	bool "ZSTD"
+	depends on HAVE_KERNEL_ZSTD
+	help
+	  Its compression ratio is roughly 10% worst than xz, but the
+	  decompression is 10x faster. Currently, this is one of the optimal
+	  algorithms available in the kernel, as there isn't an algorithm,
+	  which would provide a better compression ratio and a shorter
+	  decompression time.
+
 config KERNEL_UNCOMPRESSED
 	bool "None"
 	depends on HAVE_KERNEL_UNCOMPRESSED
diff --git a/lib/Kconfig b/lib/Kconfig
index 6e790dc55c5b..df301bd888d7 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -329,6 +329,10 @@  config DECOMPRESS_LZ4
 	select LZ4_DECOMPRESS
 	tristate
 
+config DECOMPRESS_ZSTD
+	select ZSTD_DECOMPRESS
+	tristate
+
 #
 # Generic allocator support is selected if needed
 #
diff --git a/lib/Makefile b/lib/Makefile
index 93217d44237f..3ab9f4c31f8b 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -158,6 +158,7 @@  lib-$(CONFIG_DECOMPRESS_LZMA) += decompress_unlzma.o
 lib-$(CONFIG_DECOMPRESS_XZ) += decompress_unxz.o
 lib-$(CONFIG_DECOMPRESS_LZO) += decompress_unlzo.o
 lib-$(CONFIG_DECOMPRESS_LZ4) += decompress_unlz4.o
+lib-$(CONFIG_DECOMPRESS_ZSTD) += decompress_unzstd.o
 
 obj-$(CONFIG_TEXTSEARCH) += textsearch.o
 obj-$(CONFIG_TEXTSEARCH_KMP) += ts_kmp.o
diff --git a/lib/decompress.c b/lib/decompress.c
index 857ab1af1ef3..ab3fc90ffc64 100644
--- a/lib/decompress.c
+++ b/lib/decompress.c
@@ -13,6 +13,7 @@ 
 #include <linux/decompress/inflate.h>
 #include <linux/decompress/unlzo.h>
 #include <linux/decompress/unlz4.h>
+#include <linux/decompress/unzstd.h>
 
 #include <linux/types.h>
 #include <linux/string.h>
@@ -37,6 +38,9 @@ 
 #ifndef CONFIG_DECOMPRESS_LZ4
 # define unlz4 NULL
 #endif
+#ifndef CONFIG_DECOMPRESS_ZSTD
+# define unzstd NULL
+#endif
 
 struct compress_format {
 	unsigned char magic[2];
@@ -52,6 +56,7 @@  static const struct compress_format compressed_formats[] __initconst = {
 	{ {0xfd, 0x37}, "xz", unxz },
 	{ {0x89, 0x4c}, "lzo", unlzo },
 	{ {0x02, 0x21}, "lz4", unlz4 },
+	{ {0x28, 0xb5}, "zstd", unzstd },
 	{ {0, 0}, NULL, NULL }
 };
 
diff --git a/lib/decompress_unzstd.c b/lib/decompress_unzstd.c
new file mode 100644
index 000000000000..5af647a49885
--- /dev/null
+++ b/lib/decompress_unzstd.c
@@ -0,0 +1,159 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Wrapper for decompressing ZSTD-compressed kernel, initramfs, and initrd
+ * Based on decompress_unlz4.c
+ *
+ * Copyright (C) 2020, Petr Malat <oss@malat.biz>
+ */
+
+#ifdef STATIC
+#define PREBOOT
+#include "zstd/zstd_internal.h"
+#include "zstd/huf_decompress.c"
+#include "zstd/entropy_common.c"
+#include "zstd/fse_decompress.c"
+#include "zstd/zstd_common.c"
+#include "zstd/decompress.c"
+#include "xxhash.c"
+#else
+#include <linux/decompress/unzstd.h>
+#include <linux/zstd.h>
+#endif
+#include <linux/types.h>
+#include <linux/decompress/mm.h>
+#include <linux/compiler.h>
+
+STATIC inline int INIT unzstd(u8 *input, long in_len,
+				long (*fill)(void *, unsigned long),
+				long (*flush)(void *, unsigned long),
+				u8 *output, long *posp,
+				void (*error)(char *x))
+{
+	int ret = -1, ws = 1 << ZSTD_WINDOWLOG_MAX;
+	u8 *inp, *outp;
+	ZSTD_DStream *zstd;
+	void *workspace;
+	size_t workspace_size;
+	ZSTD_outBuffer out;
+	ZSTD_inBuffer in;
+	unsigned long out_len;
+	unsigned long pos;
+
+	if (output) {
+		out_len = ULONG_MAX; // Caller knows data will fit
+		outp = output;
+	} else if (!flush) {
+		error("NULL output pointer and no flush function provided");
+		goto exit_0;
+	} else {
+		out_len = ZSTD_DStreamOutSize();
+		outp = large_malloc(out_len);
+		if (!outp) {
+			error("Could not allocate output buffer");
+			goto exit_0;
+		}
+	}
+
+	if (input && fill) {
+		error("Both input pointer and fill function provided,");
+		goto exit_1;
+	} else if (input) {
+		ZSTD_frameParams p;
+
+		inp = input;
+		if (!ZSTD_getFrameParams(&p, input, in_len))
+			ws = p.windowSize;
+	} else if (!fill) {
+		error("NULL input pointer and missing fill function");
+		goto exit_1;
+	} else {
+		in_len = ZSTD_DStreamInSize();
+		inp = large_malloc(in_len);
+		if (!inp) {
+			error("Could not allocate input buffer");
+			goto exit_1;
+		}
+	}
+
+	workspace_size = ZSTD_DStreamWorkspaceBound(ws);
+	workspace = large_malloc(workspace_size);
+	if (!workspace) {
+		error("Could not allocate workspace");
+		goto exit_2;
+	}
+
+	zstd = ZSTD_initDStream(ws, workspace, workspace_size);
+	if (!zstd) {
+		error("Could not initialize ZSTD");
+		goto exit_3;
+	}
+
+	in.src = inp;
+	in.size = in_len;
+	in.pos = 0;
+	if (posp)
+		*posp = 0;
+
+	for (;;) {
+		if (fill) {
+			in.size = fill(inp, in_len);
+			if (in.size == 0)
+				break;
+		} else if (in.size == in.pos) {
+			break;
+		}
+init:		out.dst = outp;
+		out.size = out_len;
+		out.pos = 0;
+		pos = in.pos;
+
+		ret = ZSTD_decompressStream(zstd, &out, &in);
+		if (posp)
+			*posp += in.pos - pos;
+		if (ZSTD_isError(ret)) {
+			error("Decompression failed");
+			ret = -EIO;
+			goto exit_3;
+		}
+
+		if (flush && out.pos) {
+			if (flush(out.dst, out.pos) != out.pos) {
+				ret = -EIO;
+				goto exit_3;
+			}
+			goto init;
+		}
+
+		if (ret == 0) {
+			ret = ZSTD_resetDStream(zstd);
+			if (ZSTD_isError(ret)) {
+				ret = -EIO;
+				goto exit_3;
+			}
+		}
+		if (in.pos < in.size)
+			goto init_out;
+	}
+
+	ret = 0;
+
+exit_3:	large_free(workspace);
+exit_2:	if (!input)
+		large_free(inp);
+exit_1:	if (!output)
+		large_free(outp);
+exit_0:	return ret;
+}
+
+#ifdef PREBOOT
+STATIC int INIT __decompress(unsigned char *buf, long in_len,
+			      long (*fill)(void*, unsigned long),
+			      long (*flush)(void*, unsigned long),
+			      unsigned char *output, long out_len,
+			      long *posp,
+			      void (*error)(char *x)
+	)
+{
+	return unzstd(buf, in_len, fill, flush, output, posp, error);
+}
+#endif
diff --git a/lib/zstd/decompress.c b/lib/zstd/decompress.c
index 269ee9a796c1..6a5e1ce22719 100644
--- a/lib/zstd/decompress.c
+++ b/lib/zstd/decompress.c
@@ -42,9 +42,11 @@ 
 /*-*************************************
 *  Macros
 ***************************************/
+#ifndef PREBOOT
 #define ZSTD_isError ERR_isError /* for inlining */
 #define FSE_isError ERR_isError
 #define HUF_isError ERR_isError
+#endif
 
 /*_*******************************************************
 *  Memory operations
diff --git a/lib/zstd/fse_decompress.c b/lib/zstd/fse_decompress.c
index a84300e5a013..bd4e9c891d96 100644
--- a/lib/zstd/fse_decompress.c
+++ b/lib/zstd/fse_decompress.c
@@ -54,12 +54,13 @@ 
 /* **************************************************************
 *  Error Management
 ****************************************************************/
-#define FSE_isError ERR_isError
 #define FSE_STATIC_ASSERT(c)                                   \
 	{                                                      \
 		enum { FSE_static_assert = 1 / (int)(!!(c)) }; \
 	} /* use only *after* variable declarations */
 
+#ifndef PREBOOT
+#define FSE_isError ERR_isError
 /* check and forward error code */
 #define CHECK_F(f)                  \
 	{                           \
@@ -67,6 +68,7 @@ 
 		if (FSE_isError(e)) \
 			return e;   \
 	}
+#endif
 
 /* **************************************************************
 *  Templates
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 3fa32f83b2d7..1c2f2dc528dc 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -337,6 +337,9 @@  quiet_cmd_lz4 = LZ4     $@
       cmd_lz4 = { cat $(real-prereqs) | lz4c -l -c1 stdin stdout; \
                   $(size_append); } > $@
 
+quiet_cmd_zstd = ZSTD    $@
+      cmd_zstd = { cat $(real-prereqs) | zstd -19 --zstd=wlog=21; $(size_append); } > $@
+
 # U-Boot mkimage
 # ---------------------------------------------------------------------------
 
diff --git a/usr/Kconfig b/usr/Kconfig
index a6b68503d177..892eb15957db 100644
--- a/usr/Kconfig
+++ b/usr/Kconfig
@@ -106,6 +106,15 @@  config RD_LZ4
 	  Support loading of a LZ4 encoded initial ramdisk or cpio buffer
 	  If unsure, say N.
 
+config RD_ZSTD
+	bool "Support initial ramdisk/ramfs compressed using ZSTD"
+	default y
+	depends on BLK_DEV_INITRD
+	select DECOMPRESS_ZSTD
+	help
+	  Support loading of a ZSTD encoded initial ramdisk or cpio buffer
+	  If unsure, say N.
+
 choice
 	prompt "Built-in initramfs compression mode"
 	depends on INITRAMFS_SOURCE!=""
@@ -214,6 +223,19 @@  config INITRAMFS_COMPRESSION_LZ4
 	  If you choose this, keep in mind that most distros don't provide lz4
 	  by default which could cause a build failure.
 
+config INITRAMFS_COMPRESSION_ZSTD
+	bool "ZSTD"
+	depends on RD_ZSTD
+	help
+	  Its compression ratio is roughly 10% worst than xz, but the
+	  decompression is 10x faster. Currently, this is one of the optimal
+	  algorithms available in the kernel, as there isn't an algorithm,
+	  which would provide a better compression ratio and a shorter
+	  decompression time.
+
+	  If you choose this, keep in mind that you may need to install the zstd
+	  tool to be able to compress the initram.
+
 endchoice
 
 config INITRAMFS_COMPRESSION
@@ -226,10 +248,12 @@  config INITRAMFS_COMPRESSION
 	default ".xz"   if INITRAMFS_COMPRESSION_XZ
 	default ".lzo"  if INITRAMFS_COMPRESSION_LZO
 	default ".lz4"  if INITRAMFS_COMPRESSION_LZ4
+	default ".zst"  if INITRAMFS_COMPRESSION_ZSTD
 	default ".gz"   if RD_GZIP
 	default ".lz4"  if RD_LZ4
 	default ".lzo"  if RD_LZO
 	default ".xz"   if RD_XZ
 	default ".lzma" if RD_LZMA
 	default ".bz2"  if RD_BZIP2
+	default ".zst"  if RD_ZSTD
 	default ""