1745 lines · 43.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * main.c - Multi purpose firmware loading support * * Copyright (c) 2003 Manuel Estrada Sainz * * Please see Documentation/driver-api/firmware/ for more information. * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/capability.h>#include <linux/device.h>#include <linux/kernel_read_file.h>#include <linux/module.h>#include <linux/init.h>#include <linux/initrd.h>#include <linux/timer.h>#include <linux/vmalloc.h>#include <linux/interrupt.h>#include <linux/bitops.h>#include <linux/mutex.h>#include <linux/workqueue.h>#include <linux/highmem.h>#include <linux/firmware.h>#include <linux/slab.h>#include <linux/sched.h>#include <linux/file.h>#include <linux/list.h>#include <linux/fs.h>#include <linux/async.h>#include <linux/pm.h>#include <linux/suspend.h>#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> #include "../base.h"#include "firmware.h"#include "fallback.h" MODULE_AUTHOR("Manuel Estrada Sainz");MODULE_DESCRIPTION("Multi purpose firmware loading support");MODULE_LICENSE("GPL"); struct firmware_cache {	/* firmware_buf instance will be added into the below list */	spinlock_t lock;	struct list_head head;	int state; #ifdef CONFIG_FW_CACHE	/*	 * Names of firmware images which have been cached successfully	 * will be added into the below list so that device uncache	 * helper can trace which firmware images have been cached	 * before.	 */	spinlock_t name_lock;	struct list_head fw_names; 	struct delayed_work work; 	struct notifier_block   pm_notify;#endif}; struct fw_cache_entry {	struct list_head list;	const char *name;}; struct fw_name_devm {	unsigned long magic;	const char *name;}; static inline struct fw_priv *to_fw_priv(struct kref *ref){	return container_of(ref, struct fw_priv, ref);} #define	FW_LOADER_NO_CACHE	0#define	FW_LOADER_START_CACHE	1 /* fw_lock could be moved to 'struct fw_sysfs' but since it is just * guarding for corner cases a global lock should be OK */DEFINE_MUTEX(fw_lock); struct firmware_cache fw_cache;bool fw_load_abort_all; void fw_state_init(struct fw_priv *fw_priv){	struct fw_state *fw_st = &fw_priv->fw_st; 	init_completion(&fw_st->completion);	fw_st->status = FW_STATUS_UNKNOWN;} static inline int fw_state_wait(struct fw_priv *fw_priv){	return __fw_state_wait_common(fw_priv, MAX_SCHEDULE_TIMEOUT);} static void fw_cache_piggyback_on_request(struct fw_priv *fw_priv); static struct fw_priv *__allocate_fw_priv(const char *fw_name,					  struct firmware_cache *fwc,					  void *dbuf,					  size_t size,					  size_t offset,					  u32 opt_flags){	struct fw_priv *fw_priv; 	/* For a partial read, the buffer must be preallocated. */	if ((opt_flags & FW_OPT_PARTIAL) && !dbuf)		return NULL; 	/* Only partial reads are allowed to use an offset. */	if (offset != 0 && !(opt_flags & FW_OPT_PARTIAL))		return NULL; 	fw_priv = kzalloc_obj(*fw_priv, GFP_ATOMIC);	if (!fw_priv)		return NULL; 	fw_priv->fw_name = kstrdup_const(fw_name, GFP_ATOMIC);	if (!fw_priv->fw_name) {		kfree(fw_priv);		return NULL;	} 	kref_init(&fw_priv->ref);	fw_priv->fwc = fwc;	fw_priv->data = dbuf;	fw_priv->allocated_size = size;	fw_priv->offset = offset;	fw_priv->opt_flags = opt_flags;	fw_state_init(fw_priv);#ifdef CONFIG_FW_LOADER_USER_HELPER	INIT_LIST_HEAD(&fw_priv->pending_list);#endif 	pr_debug("%s: fw-%s fw_priv=%p\n", __func__, fw_name, fw_priv); 	return fw_priv;} static struct fw_priv *__lookup_fw_priv(const char *fw_name){	struct fw_priv *tmp;	struct firmware_cache *fwc = &fw_cache; 	list_for_each_entry(tmp, &fwc->head, list)		if (!strcmp(tmp->fw_name, fw_name))			return tmp;	return NULL;} /* Returns 1 for batching firmware requests with the same name */int alloc_lookup_fw_priv(const char *fw_name, struct firmware_cache *fwc,			 struct fw_priv **fw_priv, void *dbuf, size_t size,			 size_t offset, u32 opt_flags){	struct fw_priv *tmp; 	spin_lock(&fwc->lock);	/*	 * Do not merge requests that are marked to be non-cached or	 * are performing partial reads.	 */	if (!(opt_flags & (FW_OPT_NOCACHE | FW_OPT_PARTIAL))) {		tmp = __lookup_fw_priv(fw_name);		if (tmp) {			kref_get(&tmp->ref);			spin_unlock(&fwc->lock);			*fw_priv = tmp;			pr_debug("batched request - sharing the same struct fw_priv and lookup for multiple requests\n");			return 1;		}	} 	tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size, offset, opt_flags);	if (tmp) {		INIT_LIST_HEAD(&tmp->list);		if (!(opt_flags & FW_OPT_NOCACHE))			list_add(&tmp->list, &fwc->head);	}	spin_unlock(&fwc->lock); 	*fw_priv = tmp; 	return tmp ? 0 : -ENOMEM;} static void __free_fw_priv(struct kref *ref)	__releases(&fwc->lock){	struct fw_priv *fw_priv = to_fw_priv(ref);	struct firmware_cache *fwc = fw_priv->fwc; 	pr_debug("%s: fw-%s fw_priv=%p data=%p size=%u\n",		 __func__, fw_priv->fw_name, fw_priv, fw_priv->data,		 (unsigned int)fw_priv->size); 	list_del(&fw_priv->list);	spin_unlock(&fwc->lock); 	if (fw_is_paged_buf(fw_priv))		fw_free_paged_buf(fw_priv);	else if (!fw_priv->allocated_size)		vfree(fw_priv->data); 	kfree_const(fw_priv->fw_name);	kfree(fw_priv);} void free_fw_priv(struct fw_priv *fw_priv){	struct firmware_cache *fwc = fw_priv->fwc;	spin_lock(&fwc->lock);	if (!kref_put(&fw_priv->ref, __free_fw_priv))		spin_unlock(&fwc->lock);} #ifdef CONFIG_FW_LOADER_PAGED_BUFbool fw_is_paged_buf(struct fw_priv *fw_priv){	return fw_priv->is_paged_buf;} void fw_free_paged_buf(struct fw_priv *fw_priv){	int i; 	if (!fw_priv->pages)		return; 	vunmap(fw_priv->data); 	for (i = 0; i < fw_priv->nr_pages; i++)		__free_page(fw_priv->pages[i]);	kvfree(fw_priv->pages);	fw_priv->pages = NULL;	fw_priv->page_array_size = 0;	fw_priv->nr_pages = 0;	fw_priv->data = NULL;	fw_priv->size = 0;} int fw_grow_paged_buf(struct fw_priv *fw_priv, int pages_needed){	/* If the array of pages is too small, grow it */	if (fw_priv->page_array_size < pages_needed) {		int new_array_size = max(pages_needed,					 fw_priv->page_array_size * 2);		struct page **new_pages; 		new_pages = kvmalloc_array(new_array_size, sizeof(void *),					   GFP_KERNEL);		if (!new_pages)			return -ENOMEM;		memcpy(new_pages, fw_priv->pages,		       fw_priv->page_array_size * sizeof(void *));		memset(&new_pages[fw_priv->page_array_size], 0, sizeof(void *) *		       (new_array_size - fw_priv->page_array_size));		kvfree(fw_priv->pages);		fw_priv->pages = new_pages;		fw_priv->page_array_size = new_array_size;	} 	while (fw_priv->nr_pages < pages_needed) {		fw_priv->pages[fw_priv->nr_pages] =			alloc_page(GFP_KERNEL | __GFP_HIGHMEM); 		if (!fw_priv->pages[fw_priv->nr_pages])			return -ENOMEM;		fw_priv->nr_pages++;	} 	return 0;} int fw_map_paged_buf(struct fw_priv *fw_priv){	/* one pages buffer should be mapped/unmapped only once */	if (!fw_priv->pages)		return 0; 	vunmap(fw_priv->data);	fw_priv->data = vmap(fw_priv->pages, fw_priv->nr_pages, 0,			     PAGE_KERNEL_RO);	if (!fw_priv->data)		return -ENOMEM; 	return 0;}#endif /* * ZSTD-compressed firmware support */#ifdef CONFIG_FW_LOADER_COMPRESS_ZSTDstatic 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_XZ/* 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_local_page(page);		xz_buf.out_pos = 0;		xz_buf.out_size = PAGE_SIZE;		xz_ret = xz_dec_run(xz_dec, &xz_buf);		kunmap_local(xz_buf.out);		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_XZ */ /* direct firmware loading support */static char fw_path_para[256];static const char * const fw_path[] = {	fw_path_para,	"/lib/firmware/updates/" UTS_RELEASE,	"/lib/firmware/updates",	"/lib/firmware/" UTS_RELEASE,	"/lib/firmware"}; /* * Typical usage is that passing 'firmware_class.path=$CUSTOMIZED_PATH' * from kernel command line because firmware_class is generally built in * kernel instead of module. */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 intfw_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)){	size_t size;	int i, len, maxlen = 0;	int rc = -ENOENT;	char *path, *nt = NULL;	size_t msize = INT_MAX;

Lines 1–500 of 1745.