[PATCH] firmware: Avoid caching firmware when FW_OPT_NOCACHE is set

From: Rishabh Bhatnagar
Date: Thu Jul 19 2018 - 17:25:33 EST


When calling request_firmware_into_buf(), we pass the FW_OPT_NOCACHE
flag with the intent of skipping the caching mechanism of the
firmware loader. Unfortunately, that doesn't work, because
alloc_lookup_fw_priv() isn't told to _not_ add the struct
firmware_buf to the firmware cache (fwc) list. So when we call
request_firmware_into_buf() the second time, we find the buffer in
the cache and return it immediately without reloading.

This may break users of request_firmware_into_buf that are expecting
a fresh copy of the firmware to be reloaded into memory. The existing
copy may either be modified by drivers, remote processors or even
freed. Fix fw_lookup_and_allocate_buf to not add to the fwc list
if FW_OPT_NOCACHE is set, and also don't do the lookup in the list.

Fixes: 0e742e9271 ("firmware: provide infrastructure to make fw caching optional")

Signed-off-by: Vikram Mulukutla <markivx@xxxxxxxxxxxxxx>
Signed-off-by: Rishabh Bhatnagar <rishabhb@xxxxxxxxxxxxxx>
---
drivers/base/firmware_loader/main.c | 30 ++++++++++++++++++------------
1 file changed, 18 insertions(+), 12 deletions(-)

diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c
index 2e0c37a..db9038c0 100644
--- a/drivers/base/firmware_loader/main.c
+++ b/drivers/base/firmware_loader/main.c
@@ -210,21 +210,24 @@ static struct fw_priv *__lookup_fw_priv(const char *fw_name)
static 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 size, enum fw_opt opt_flags)
{
struct fw_priv *tmp;

spin_lock(&fwc->lock);
- 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;
+ if (!(opt_flags & FW_OPT_NOCACHE)) {
+ 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);
- if (tmp)
+ if (tmp && !(opt_flags & FW_OPT_NOCACHE))
list_add(&tmp->list, &fwc->head);
spin_unlock(&fwc->lock);

@@ -500,7 +503,8 @@ 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)
+ struct device *device, void *dbuf, size_t size,
+ enum fw_opt opt_flags)
{
struct firmware *firmware;
struct fw_priv *fw_priv;
@@ -518,7 +522,8 @@ int assign_fw(struct firmware *fw, struct device *device,
return 0; /* assigned */
}

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

/*
* bind with 'priv' now to avoid warning in failure path
@@ -578,7 +583,8 @@ static void fw_abort_batch_reqs(struct firmware *fw)
goto out;
}

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

--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project