diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index a47266ccfc60..65c60666685b 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -114,6 +114,11 @@ struct firmware_priv { struct firmware *fw; }; +struct fw_name_devm { + unsigned long magic; + char name[]; +}; + #define to_fwbuf(d) container_of(d, struct firmware_buf, ref) /* fw_lock could be moved to 'struct firmware_priv' but since it is just @@ -590,6 +595,55 @@ static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw) (unsigned int)buf->size); } +static void fw_name_devm_release(struct device *dev, void *res) +{ + struct fw_name_devm *fwn = res; + + if (fwn->magic == (unsigned long)&fw_cache) + pr_debug("%s: fw_name-%s devm-%p released\n", + __func__, fwn->name, res); +} + +static int fw_devm_match(struct device *dev, void *res, + void *match_data) +{ + struct fw_name_devm *fwn = res; + + return (fwn->magic == (unsigned long)&fw_cache) && + !strcmp(fwn->name, match_data); +} + +static struct fw_name_devm *fw_find_devm_name(struct device *dev, + const char *name) +{ + struct fw_name_devm *fwn; + + fwn = devres_find(dev, fw_name_devm_release, + fw_devm_match, (void *)name); + return fwn; +} + +/* add firmware name into devres list */ +static int fw_add_devm_name(struct device *dev, const char *name) +{ + struct fw_name_devm *fwn; + + fwn = fw_find_devm_name(dev, name); + if (fwn) + return 1; + + fwn = devres_alloc(fw_name_devm_release, sizeof(struct fw_name_devm) + + strlen(name) + 1, GFP_KERNEL); + if (!fwn) + return -ENOMEM; + + fwn->magic = (unsigned long)&fw_cache; + strcpy(fwn->name, name); + devres_add(dev, fwn); + + return 0; +} + static void _request_firmware_cleanup(const struct firmware **firmware_p) { release_firmware(*firmware_p); @@ -709,6 +763,16 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent, if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status)) retval = -ENOENT; + /* + * add firmware name into devres list so that we can auto cache + * and uncache firmware for device. + * + * f_dev->parent may has been deleted already, but the problem + * should be fixed in devres or driver core. + */ + if (!retval && f_dev->parent) + fw_add_devm_name(f_dev->parent, buf->fw_id); + if (!retval) retval = fw_map_pages_buf(buf);