forked from luck/tmp_suning_uos_patched
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: Scott Branden <scott.branden@broadcom.com> Co-developed-by: Kees Cook <keescook@chromium.org> Signed-off-by: Kees Cook <keescook@chromium.org> Link: https://lore.kernel.org/r/20201002173828.2099543-16-keescook@chromium.org Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
89287c169f
commit
59cdb23ca2
|
@ -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;
|
||||
|
|
|
@ -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() */
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue
Block a user