Commit 59cdb23c authored by Scott Branden's avatar Scott Branden Committed by Greg Kroah-Hartman
Browse files

firmware: Add request_partial_firmware_into_buf()



Add request_partial_firmware_into_buf() to allow for portions of a
firmware file to be read into a buffer. This is needed when large firmware
must be loaded in portions from a file on memory constrained systems.

Signed-off-by: default avatarScott Branden <scott.branden@broadcom.com>
Co-developed-by: default avatarKees Cook <keescook@chromium.org>
Signed-off-by: default avatarKees Cook <keescook@chromium.org>
Link: https://lore.kernel.org/r/20201002173828.2099543-16-keescook@chromium.org


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 89287c16
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@
 * @FW_OPT_FALLBACK_PLATFORM: Enable fallback to device fw copy embedded in
 *	the platform's main firmware. If both this fallback and the sysfs
 *      fallback are enabled, then this fallback will be tried first.
 * @FW_OPT_PARTIAL: Allow partial read of firmware instead of needing to read
 *	entire file.
 */
enum fw_opt {
	FW_OPT_UEVENT			= BIT(0),
@@ -41,6 +43,7 @@ enum fw_opt {
	FW_OPT_NOCACHE			= BIT(4),
	FW_OPT_NOFALLBACK_SYSFS		= BIT(5),
	FW_OPT_FALLBACK_PLATFORM	= BIT(6),
	FW_OPT_PARTIAL			= BIT(7),
};

enum fw_status {
@@ -68,6 +71,7 @@ struct fw_priv {
	void *data;
	size_t size;
	size_t allocated_size;
	size_t offset;
	u32 opt_flags;
#ifdef CONFIG_FW_LOADER_PAGED_BUF
	bool is_paged_buf;
+83 −18
Original line number Diff line number Diff line
@@ -170,10 +170,19 @@ 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(sizeof(*fw_priv), GFP_ATOMIC);
	if (!fw_priv)
		return NULL;
@@ -188,6 +197,7 @@ static struct fw_priv *__allocate_fw_priv(const char *fw_name,
	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
@@ -216,12 +226,17 @@ static int alloc_lookup_fw_priv(const char *fw_name,
				struct fw_priv **fw_priv,
				void *dbuf,
				size_t size,
				size_t offset,
				u32 opt_flags)
{
	struct fw_priv *tmp;

	spin_lock(&fwc->lock);
	if (!(opt_flags & FW_OPT_NOCACHE)) {
	/*
	 * 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);
@@ -232,7 +247,7 @@ static int alloc_lookup_fw_priv(const char *fw_name,
		}
	}

	tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size, opt_flags);
	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))
@@ -490,6 +505,9 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv,
		return -ENOMEM;

	for (i = 0; i < ARRAY_SIZE(fw_path); i++) {
		size_t file_size = 0;
		size_t *file_size_ptr = NULL;

		/* skip the unset customized path */
		if (!fw_path[i][0])
			continue;
@@ -503,9 +521,18 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv,

		fw_priv->size = 0;

		/*
		 * The total file size is only examined when doing a partial
		 * read; the "full read" case needs to fail if the whole
		 * firmware was not completely loaded.
		 */
		if ((fw_priv->opt_flags & FW_OPT_PARTIAL) && buffer)
			file_size_ptr = &file_size;

		/* load firmware files from the mount namespace of init */
		rc = kernel_read_file_from_path_initns(path, 0, &buffer, msize,
						       NULL,
		rc = kernel_read_file_from_path_initns(path, fw_priv->offset,
						       &buffer, msize,
						       file_size_ptr,
						       READING_FIRMWARE);
		if (rc < 0) {
			if (rc != -ENOENT)
@@ -696,7 +723,7 @@ int assign_fw(struct firmware *fw, struct device *device)
static int
_request_firmware_prepare(struct firmware **firmware_p, const char *name,
			  struct device *device, void *dbuf, size_t size,
			  u32 opt_flags)
			  size_t offset, u32 opt_flags)
{
	struct firmware *firmware;
	struct fw_priv *fw_priv;
@@ -715,7 +742,7 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
	}

	ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size,
				  opt_flags);
				   offset, opt_flags);

	/*
	 * bind with 'priv' now to avoid warning in failure path
@@ -762,9 +789,10 @@ static void fw_abort_batch_reqs(struct firmware *fw)
static int
_request_firmware(const struct firmware **firmware_p, const char *name,
		  struct device *device, void *buf, size_t size,
		  u32 opt_flags)
		  size_t offset, u32 opt_flags)
{
	struct firmware *fw = NULL;
	bool nondirect = false;
	int ret;

	if (!firmware_p)
@@ -776,18 +804,22 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
	}

	ret = _request_firmware_prepare(&fw, name, device, buf, size,
					opt_flags);
					offset, opt_flags);
	if (ret <= 0) /* error or already assigned */
		goto out;

	ret = fw_get_filesystem_firmware(device, fw->priv, "", NULL);

	/* Only full reads can support decompression, platform, and sysfs. */
	if (!(opt_flags & FW_OPT_PARTIAL))
		nondirect = true;

#ifdef CONFIG_FW_LOADER_COMPRESS
	if (ret == -ENOENT)
	if (ret == -ENOENT && nondirect)
		ret = fw_get_filesystem_firmware(device, fw->priv, ".xz",
						 fw_decompress_xz);
#endif

	if (ret == -ENOENT)
	if (ret == -ENOENT && nondirect)
		ret = firmware_fallback_platform(fw->priv);

	if (ret) {
@@ -795,7 +827,9 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
			dev_warn(device,
				 "Direct firmware load for %s failed with error %d\n",
				 name, ret);
		ret = firmware_fallback_sysfs(fw, name, device, opt_flags, ret);
		if (nondirect)
			ret = firmware_fallback_sysfs(fw, name, device,
						      opt_flags, ret);
	} else
		ret = assign_fw(fw, device);

@@ -838,7 +872,7 @@ request_firmware(const struct firmware **firmware_p, const char *name,

	/* Need to pin this module until return */
	__module_get(THIS_MODULE);
	ret = _request_firmware(firmware_p, name, device, NULL, 0,
	ret = _request_firmware(firmware_p, name, device, NULL, 0, 0,
				FW_OPT_UEVENT);
	module_put(THIS_MODULE);
	return ret;
@@ -865,7 +899,7 @@ int firmware_request_nowarn(const struct firmware **firmware, const char *name,

	/* Need to pin this module until return */
	__module_get(THIS_MODULE);
	ret = _request_firmware(firmware, name, device, NULL, 0,
	ret = _request_firmware(firmware, name, device, NULL, 0, 0,
				FW_OPT_UEVENT | FW_OPT_NO_WARN);
	module_put(THIS_MODULE);
	return ret;
@@ -889,7 +923,7 @@ int request_firmware_direct(const struct firmware **firmware_p,
	int ret;

	__module_get(THIS_MODULE);
	ret = _request_firmware(firmware_p, name, device, NULL, 0,
	ret = _request_firmware(firmware_p, name, device, NULL, 0, 0,
				FW_OPT_UEVENT | FW_OPT_NO_WARN |
				FW_OPT_NOFALLBACK_SYSFS);
	module_put(THIS_MODULE);
@@ -914,7 +948,7 @@ int firmware_request_platform(const struct firmware **firmware,

	/* Need to pin this module until return */
	__module_get(THIS_MODULE);
	ret = _request_firmware(firmware, name, device, NULL, 0,
	ret = _request_firmware(firmware, name, device, NULL, 0, 0,
				FW_OPT_UEVENT | FW_OPT_FALLBACK_PLATFORM);
	module_put(THIS_MODULE);
	return ret;
@@ -970,13 +1004,44 @@ request_firmware_into_buf(const struct firmware **firmware_p, const char *name,
		return -EOPNOTSUPP;

	__module_get(THIS_MODULE);
	ret = _request_firmware(firmware_p, name, device, buf, size,
	ret = _request_firmware(firmware_p, name, device, buf, size, 0,
				FW_OPT_UEVENT | FW_OPT_NOCACHE);
	module_put(THIS_MODULE);
	return ret;
}
EXPORT_SYMBOL(request_firmware_into_buf);

/**
 * request_partial_firmware_into_buf() - load partial firmware into a previously allocated buffer
 * @firmware_p: pointer to firmware image
 * @name: name of firmware file
 * @device: device for which firmware is being loaded and DMA region allocated
 * @buf: address of buffer to load firmware into
 * @size: size of buffer
 * @offset: offset into file to read
 *
 * This function works pretty much like request_firmware_into_buf except
 * it allows a partial read of the file.
 */
int
request_partial_firmware_into_buf(const struct firmware **firmware_p,
				  const char *name, struct device *device,
				  void *buf, size_t size, size_t offset)
{
	int ret;

	if (fw_cache_is_setup(device, name))
		return -EOPNOTSUPP;

	__module_get(THIS_MODULE);
	ret = _request_firmware(firmware_p, name, device, buf, size, offset,
				FW_OPT_UEVENT | FW_OPT_NOCACHE |
				FW_OPT_PARTIAL);
	module_put(THIS_MODULE);
	return ret;
}
EXPORT_SYMBOL(request_partial_firmware_into_buf);

/**
 * release_firmware() - release the resource associated with a firmware image
 * @fw: firmware resource to release
@@ -1009,7 +1074,7 @@ static void request_firmware_work_func(struct work_struct *work)

	fw_work = container_of(work, struct firmware_work, work);

	_request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0,
	_request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0, 0,
			  fw_work->opt_flags);
	fw_work->cont(fw, fw_work->context);
	put_device(fw_work->device); /* taken in request_firmware_nowait() */
+12 −0
Original line number Diff line number Diff line
@@ -53,6 +53,9 @@ int request_firmware_direct(const struct firmware **fw, const char *name,
			    struct device *device);
int request_firmware_into_buf(const struct firmware **firmware_p,
	const char *name, struct device *device, void *buf, size_t size);
int request_partial_firmware_into_buf(const struct firmware **firmware_p,
				      const char *name, struct device *device,
				      void *buf, size_t size, size_t offset);

void release_firmware(const struct firmware *fw);
#else
@@ -102,6 +105,15 @@ static inline int request_firmware_into_buf(const struct firmware **firmware_p,
	return -EINVAL;
}

static inline int request_partial_firmware_into_buf
					(const struct firmware **firmware_p,
					 const char *name,
					 struct device *device,
					 void *buf, size_t size, size_t offset)
{
	return -EINVAL;
}

#endif

int firmware_request_cache(struct device *device, const char *name);