diff mbox series

[v2.5,2/3] firmware: Add support for loading compressed files

Message ID 20190611122626.28059-3-tiwai@suse.de (mailing list archive)
State Mainlined
Commit 82fd7a8142a10b8eb41313074b3859d82c0857dc
Headers show
Series firmware: Add support for loading compressed files | expand

Commit Message

Takashi Iwai June 11, 2019, 12:26 p.m. UTC
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>
---
v1->v2.5: Slight code refactoring, no functional changes

 drivers/base/firmware_loader/Kconfig    |  18 ++++
 drivers/base/firmware_loader/firmware.h |   8 +-
 drivers/base/firmware_loader/main.c     | 147 ++++++++++++++++++++++++++++++--
 3 files changed, 161 insertions(+), 12 deletions(-)

Comments

Luis Chamberlain June 19, 2019, 11:26 p.m. UTC | #1
Sorry for the late review... Ah!

On Tue, Jun 11, 2019 at 02:26:25PM +0200, Takashi Iwai wrote:
> @@ -354,7 +454,12 @@ 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,
> +			   const char *suffix,
> +			   int (*decompress)(struct device *dev,
> +					     struct fw_priv *fw_priv,
> +					     size_t in_size,
> +					     const void *in_buffer))

I *think* this could be cleaner, I'll elaborate below.

> @@ -645,7 +768,13 @@ _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, "", NULL);
> +#ifdef CONFIG_FW_LOADER_COMPRESS
> +	if (ret == -ENOENT)
> +		ret = fw_get_filesystem_firmware(device, fw->priv, ".xz",
> +						 fw_decompress_xz);
> +#endif

Hrm, and let more #ifdef'ery.

And so if someone wants to add bzip, we'd add yet-another if else on the
return value of this call... and yet more #ifdefs.

We already have a list of paths supported. It seems what we need instead
is a list of supported suffixes, and a respective structure which then
has its set of callbacks for posthandling.

This way, this could all be handled inside fw_get_filesystem_firmware()
neatly, and we can just strive towards avoiding #ifdef'ery.

Since I'm late to review, this could be done in the future, but I do
think something along these lines would make the code more maintainable
and extensible.

  Luis
Takashi Iwai June 20, 2019, 7:36 a.m. UTC | #2
On Thu, 20 Jun 2019 01:26:47 +0200,
Luis Chamberlain wrote:
> 
> Sorry for the late review... Ah!

No problem, thanks for review.

> On Tue, Jun 11, 2019 at 02:26:25PM +0200, Takashi Iwai wrote:
> > @@ -354,7 +454,12 @@ 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,
> > +			   const char *suffix,
> > +			   int (*decompress)(struct device *dev,
> > +					     struct fw_priv *fw_priv,
> > +					     size_t in_size,
> > +					     const void *in_buffer))
> 
> I *think* this could be cleaner, I'll elaborate below.
> 
> > @@ -645,7 +768,13 @@ _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, "", NULL);
> > +#ifdef CONFIG_FW_LOADER_COMPRESS
> > +	if (ret == -ENOENT)
> > +		ret = fw_get_filesystem_firmware(device, fw->priv, ".xz",
> > +						 fw_decompress_xz);
> > +#endif
> 
> Hrm, and let more #ifdef'ery.
> 
> And so if someone wants to add bzip, we'd add yet-another if else on the
> return value of this call... and yet more #ifdefs.
> 
> We already have a list of paths supported. It seems what we need instead
> is a list of supported suffixes, and a respective structure which then
> has its set of callbacks for posthandling.
> 
> This way, this could all be handled inside fw_get_filesystem_firmware()
> neatly, and we can just strive towards avoiding #ifdef'ery.

Yes, I had similar idea.  Actually my plan for multiple compression
formats was:

- Move the decompression part into another file, e.g. decompress_xz.c
  and change in Makefile:
  firmware_class-$(CONFIG_FW_LOADER_COMPRESS_XZ) += decompress_xz.o
  
- Create a table of the extension and the decompression,

  static struct fw_decompression_table fw_decompressions[] = {
	{ "", NULL },
#ifdef CONFIG_FW_LOADER_COMPRESS_XZ
	{ ".xz", fw_decompress_xz },
#endif
#ifdef CONFIG_FW_LOADER_COMPRESS_BZIP2
	{ ".bz2", fw_decompress_bzip2 },
#endif
	.....
  };

and call it

	for (i = 0; i < ARRAY_SIZE(fw_decompressions); i++) {
		ret = fw_get_filesystem_firmware(device, fw->priv,
						 fw_decompressions[i].extesnion,
						 fw_decompressions[i].func);
		if (ret != -ENOENT)
			break;
	}


The patch was submitted in the current form just because it's simpler
for a single compression case.  But as you can see in the v2 patchset
change, it has already the decompression function pointer, so that the
code can be cleaned up / extended easily later if multiple formats are
supported like the above.


BTW, I took a look at other compression methods, and found that LZ4 is
a bit tough with the current API.  ZSTD should be relatively easy, as
it can get the whole decompressed size at first, so we'll be able to
do vmalloc().  For others, we may need a streaming decompression and
growing buffer per page.  But LZ4 decompression API doesn't seem
allowing the partial decompression-and-continue as I used for XZ, so
it'll be tricky.
GZIP and BZIP2 should work in a streaming mode like XZ, I suppose.


thanks,

Takashi
Greg KH June 20, 2019, 8:10 a.m. UTC | #3
On Thu, Jun 20, 2019 at 09:36:03AM +0200, Takashi Iwai wrote:
> On Thu, 20 Jun 2019 01:26:47 +0200,
> Luis Chamberlain wrote:
> > 
> > Sorry for the late review... Ah!
> 
> No problem, thanks for review.
> 
> > On Tue, Jun 11, 2019 at 02:26:25PM +0200, Takashi Iwai wrote:
> > > @@ -354,7 +454,12 @@ 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,
> > > +			   const char *suffix,
> > > +			   int (*decompress)(struct device *dev,
> > > +					     struct fw_priv *fw_priv,
> > > +					     size_t in_size,
> > > +					     const void *in_buffer))
> > 
> > I *think* this could be cleaner, I'll elaborate below.
> > 
> > > @@ -645,7 +768,13 @@ _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, "", NULL);
> > > +#ifdef CONFIG_FW_LOADER_COMPRESS
> > > +	if (ret == -ENOENT)
> > > +		ret = fw_get_filesystem_firmware(device, fw->priv, ".xz",
> > > +						 fw_decompress_xz);
> > > +#endif
> > 
> > Hrm, and let more #ifdef'ery.
> > 
> > And so if someone wants to add bzip, we'd add yet-another if else on the
> > return value of this call... and yet more #ifdefs.
> > 
> > We already have a list of paths supported. It seems what we need instead
> > is a list of supported suffixes, and a respective structure which then
> > has its set of callbacks for posthandling.
> > 
> > This way, this could all be handled inside fw_get_filesystem_firmware()
> > neatly, and we can just strive towards avoiding #ifdef'ery.
> 
> Yes, I had similar idea.  Actually my plan for multiple compression
> formats was:
> 
> - Move the decompression part into another file, e.g. decompress_xz.c
>   and change in Makefile:
>   firmware_class-$(CONFIG_FW_LOADER_COMPRESS_XZ) += decompress_xz.o
>   
> - Create a table of the extension and the decompression,
> 
>   static struct fw_decompression_table fw_decompressions[] = {
> 	{ "", NULL },
> #ifdef CONFIG_FW_LOADER_COMPRESS_XZ
> 	{ ".xz", fw_decompress_xz },
> #endif
> #ifdef CONFIG_FW_LOADER_COMPRESS_BZIP2
> 	{ ".bz2", fw_decompress_bzip2 },
> #endif
> 	.....
>   };

But why?  Why not just stick with one for now, we don't need a zillion
different formats to start with.  Let's just stick with .xz and that's
it.  There is no need to do anything else for the foreseeable future.

thanks,

greg k-h
Takashi Iwai June 20, 2019, 8:19 a.m. UTC | #4
On Thu, 20 Jun 2019 10:10:03 +0200,
Greg Kroah-Hartman wrote:
> 
> On Thu, Jun 20, 2019 at 09:36:03AM +0200, Takashi Iwai wrote:
> > On Thu, 20 Jun 2019 01:26:47 +0200,
> > Luis Chamberlain wrote:
> > > 
> > > Sorry for the late review... Ah!
> > 
> > No problem, thanks for review.
> > 
> > > On Tue, Jun 11, 2019 at 02:26:25PM +0200, Takashi Iwai wrote:
> > > > @@ -354,7 +454,12 @@ 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,
> > > > +			   const char *suffix,
> > > > +			   int (*decompress)(struct device *dev,
> > > > +					     struct fw_priv *fw_priv,
> > > > +					     size_t in_size,
> > > > +					     const void *in_buffer))
> > > 
> > > I *think* this could be cleaner, I'll elaborate below.
> > > 
> > > > @@ -645,7 +768,13 @@ _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, "", NULL);
> > > > +#ifdef CONFIG_FW_LOADER_COMPRESS
> > > > +	if (ret == -ENOENT)
> > > > +		ret = fw_get_filesystem_firmware(device, fw->priv, ".xz",
> > > > +						 fw_decompress_xz);
> > > > +#endif
> > > 
> > > Hrm, and let more #ifdef'ery.
> > > 
> > > And so if someone wants to add bzip, we'd add yet-another if else on the
> > > return value of this call... and yet more #ifdefs.
> > > 
> > > We already have a list of paths supported. It seems what we need instead
> > > is a list of supported suffixes, and a respective structure which then
> > > has its set of callbacks for posthandling.
> > > 
> > > This way, this could all be handled inside fw_get_filesystem_firmware()
> > > neatly, and we can just strive towards avoiding #ifdef'ery.
> > 
> > Yes, I had similar idea.  Actually my plan for multiple compression
> > formats was:
> > 
> > - Move the decompression part into another file, e.g. decompress_xz.c
> >   and change in Makefile:
> >   firmware_class-$(CONFIG_FW_LOADER_COMPRESS_XZ) += decompress_xz.o
> >   
> > - Create a table of the extension and the decompression,
> > 
> >   static struct fw_decompression_table fw_decompressions[] = {
> > 	{ "", NULL },
> > #ifdef CONFIG_FW_LOADER_COMPRESS_XZ
> > 	{ ".xz", fw_decompress_xz },
> > #endif
> > #ifdef CONFIG_FW_LOADER_COMPRESS_BZIP2
> > 	{ ".bz2", fw_decompress_bzip2 },
> > #endif
> > 	.....
> >   };
> 
> But why?  Why not just stick with one for now, we don't need a zillion
> different formats to start with.  Let's just stick with .xz and that's
> it.  There is no need to do anything else for the foreseeable future.

Yeah, that's the reason I submitted the patch in the current form;
XZ format should be good enough and it's simpler for a single format,
after all.  The suggestion above is only for the case we need to
support multiple formats.

Maybe we may want to support ZSTD in future, as Fedora is moving
toward to that format.  Once when the time comes, we can revisit how
the things can be cleaned up.
(And, I heard Ubuntu switching to LZ4, but LZ4 is difficult, as
mentioned in the previous mail...)


thanks,

Takashi
diff mbox series

Patch

diff --git a/drivers/base/firmware_loader/Kconfig b/drivers/base/firmware_loader/Kconfig
index 38f2da6f5c2b..3f9e274e2ed3 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
+	  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).
+
 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..bf44c79beae9 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,105 @@  int fw_map_paged_buf(struct fw_priv *fw_priv)
 }
 #endif
 
+/*
+ * XZ-compressed firmware support
+ */
+#ifdef CONFIG_FW_LOADER_COMPRESS
+/* show an error and return the standard error code */
+static int fw_decompress_xz_error(struct device *dev, enum xz_ret xz_ret)
+{
+	if (xz_ret != XZ_STREAM_END) {
+		dev_warn(dev, "xz decompression failed (xz_ret=%d)\n", xz_ret);
+		return xz_ret == XZ_MEM_ERROR ? -ENOMEM : -EINVAL;
+	}
+	return 0;
+}
+
+/* single-shot decompression onto the pre-allocated buffer */
+static int fw_decompress_xz_single(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;
+
+	xz_dec = xz_dec_init(XZ_SINGLE, (u32)-1);
+	if (!xz_dec)
+		return -ENOMEM;
+
+	xz_buf.in_size = in_size;
+	xz_buf.in = in_buffer;
+	xz_buf.in_pos = 0;
+	xz_buf.out_size = fw_priv->allocated_size;
+	xz_buf.out = fw_priv->data;
+	xz_buf.out_pos = 0;
+
+	xz_ret = xz_dec_run(xz_dec, &xz_buf);
+	xz_dec_end(xz_dec);
+
+	fw_priv->size = xz_buf.out_pos;
+	return fw_decompress_xz_error(dev, xz_ret);
+}
+
+/* decompression on paged buffer and map it */
+static int fw_decompress_xz_pages(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;
+	struct page *page;
+	int err = 0;
+
+	xz_dec = xz_dec_init(XZ_DYNALLOC, (u32)-1);
+	if (!xz_dec)
+		return -ENOMEM;
+
+	xz_buf.in_size = in_size;
+	xz_buf.in = in_buffer;
+	xz_buf.in_pos = 0;
+
+	fw_priv->is_paged_buf = true;
+	fw_priv->size = 0;
+	do {
+		if (fw_grow_paged_buf(fw_priv, fw_priv->nr_pages + 1)) {
+			err = -ENOMEM;
+			goto out;
+		}
+
+		/* decompress onto the new allocated page */
+		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;
+		/* partial decompression means either end or error */
+		if (xz_buf.out_pos != PAGE_SIZE)
+			break;
+	} while (xz_ret == XZ_OK);
+
+	err = fw_decompress_xz_error(dev, xz_ret);
+	if (!err)
+		err = fw_map_paged_buf(fw_priv);
+
+ out:
+	xz_dec_end(xz_dec);
+	return err;
+}
+
+static int fw_decompress_xz(struct device *dev, struct fw_priv *fw_priv,
+			    size_t in_size, const void *in_buffer)
+{
+	/* if the buffer is pre-allocated, we can perform in single-shot mode */
+	if (fw_priv->data)
+		return fw_decompress_xz_single(dev, fw_priv, in_size, in_buffer);
+	else
+		return fw_decompress_xz_pages(dev, fw_priv, in_size, in_buffer);
+}
+#endif /* CONFIG_FW_LOADER_COMPRESS */
+
 /* direct firmware loading support */
 static char fw_path_para[256];
 static const char * const fw_path[] = {
@@ -354,7 +454,12 @@  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,
+			   const char *suffix,
+			   int (*decompress)(struct device *dev,
+					     struct fw_priv *fw_priv,
+					     size_t in_size,
+					     const void *in_buffer))
 {
 	loff_t size;
 	int i, len;
@@ -362,9 +467,11 @@  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;
 
 	/* 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 +485,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 +504,24 @@  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 = decompress(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 +768,13 @@  _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, "", NULL);
+#ifdef CONFIG_FW_LOADER_COMPRESS
+	if (ret == -ENOENT)
+		ret = fw_get_filesystem_firmware(device, fw->priv, ".xz",
+						 fw_decompress_xz);
+#endif
+
 	if (ret) {
 		if (!(opt_flags & FW_OPT_NO_WARN))
 			dev_warn(device,