lkml.org 
[lkml]   [2020]   [Jul]   [28]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    /
    Date
    From
    SubjectRe: [PATCH v3 18/19] firmware: Add request_partial_firmware_into_buf()
    Long ago Takashi had some points about this strategy breaking
    compressed file use. Was that considered?

    Luis

    On Fri, Jul 24, 2020 at 02:36:39PM -0700, Kees Cook wrote:
    > From: Scott Branden <scott.branden@broadcom.com>
    >
    > 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>
    > ---
    > drivers/base/firmware_loader/firmware.h | 4 +
    > drivers/base/firmware_loader/main.c | 109 +++++++++++++++++++-----
    > include/linux/firmware.h | 12 +++
    > 3 files changed, 105 insertions(+), 20 deletions(-)
    >
    > diff --git a/drivers/base/firmware_loader/firmware.h b/drivers/base/firmware_loader/firmware.h
    > index 7ad5fe52bc72..3f6eda46b3a2 100644
    > --- a/drivers/base/firmware_loader/firmware.h
    > +++ b/drivers/base/firmware_loader/firmware.h
    > @@ -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;
    > diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c
    > index 814a18cc51bd..7aa22bdc2f60 100644
    > --- a/drivers/base/firmware_loader/main.c
    > +++ b/drivers/base/firmware_loader/main.c
    > @@ -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))
    > @@ -439,6 +454,12 @@ static int fw_decompress_xz(struct device *dev, struct fw_priv *fw_priv,
    > else
    > return fw_decompress_xz_pages(dev, fw_priv, in_size, in_buffer);
    > }
    > +#else
    > +static inline int fw_decompress_xz(struct device *dev, struct fw_priv *fw_priv,
    > + size_t in_size, const void *in_buffer)
    > +{
    > + return -ENOENT;
    > +}
    > #endif /* CONFIG_FW_LOADER_COMPRESS */
    >
    > /* direct firmware loading support */
    > @@ -485,6 +506,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;
    > @@ -498,9 +522,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)
    > @@ -691,7 +724,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;
    > @@ -710,7 +743,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
    > @@ -757,9 +790,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)
    > @@ -771,18 +805,20 @@ _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);
    > -#ifdef CONFIG_FW_LOADER_COMPRESS
    > - if (ret == -ENOENT)
    > +
    > + /* Only full reads can support decompression, platform, and sysfs. */
    > + if (!(opt_flags & FW_OPT_PARTIAL))
    > + nondirect = true;
    > +
    > + 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) {
    > @@ -790,7 +826,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);
    >
    > @@ -833,7 +871,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;
    > @@ -860,7 +898,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;
    > @@ -884,7 +922,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);
    > @@ -909,7 +947,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;
    > @@ -965,13 +1003,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
    > @@ -1004,7 +1073,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() */
    > diff --git a/include/linux/firmware.h b/include/linux/firmware.h
    > index cb3e2c06ed8a..c15acadc6cf4 100644
    > --- a/include/linux/firmware.h
    > +++ b/include/linux/firmware.h
    > @@ -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);
    > --
    > 2.25.1
    >

    \
     
     \ /
      Last update: 2020-07-29 03:18    [W:4.399 / U:0.180 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site