diff options
Diffstat (limited to 'drivers/base/firmware_class.c')
-rw-r--r-- | drivers/base/firmware_class.c | 478 |
1 files changed, 379 insertions, 99 deletions
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 8524450e75bd..152c81ca50ea 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -29,6 +29,7 @@ #include <linux/syscore_ops.h> #include <linux/reboot.h> #include <linux/security.h> +#include <linux/io.h> #include <generated/utsrelease.h> @@ -111,6 +112,7 @@ static inline long firmware_loading_timeout(void) #define FW_OPT_FALLBACK 0 #endif #define FW_OPT_NO_WARN (1U << 3) +#define FW_OPT_NOCACHE (1U << 4) struct firmware_cache { /* firmware_buf instance will be added into the below list */ @@ -142,6 +144,11 @@ struct firmware_buf { unsigned long status; void *data; size_t size; + phys_addr_t dest_addr; + size_t dest_size; + void * (*map_fw_mem)(phys_addr_t phys, size_t size, void *data); + void (*unmap_fw_mem)(void *virt, size_t size, void *data); + void *map_data; #ifdef CONFIG_FW_LOADER_USER_HELPER bool is_paged_buf; bool need_uevent; @@ -163,6 +170,22 @@ struct fw_name_devm { const char *name; }; +struct fw_desc { + struct work_struct work; + const struct firmware **firmware_p; + const char *name; + struct device *device; + unsigned int opt_flags; + phys_addr_t dest_addr; + size_t dest_size; + void * (*map_fw_mem)(phys_addr_t phys, size_t size, void *data); + void (*unmap_fw_mem)(void *virt, size_t size, void *data); + void *map_data; + struct module *module; + void *context; + void (*cont)(const struct firmware *fw, void *context); +}; + #define to_fwbuf(d) container_of(d, struct firmware_buf, ref) #define FW_LOADER_NO_CACHE 0 @@ -268,6 +291,10 @@ static void __fw_free_buf(struct kref *ref) static void fw_free_buf(struct firmware_buf *buf) { struct firmware_cache *fwc = buf->fwc; + if (!fwc) { + kfree(buf); + return; + } spin_lock(&fwc->lock); if (!kref_put(&buf->ref, __fw_free_buf)) spin_unlock(&fwc->lock); @@ -280,7 +307,8 @@ static const char * const fw_path[] = { "/lib/firmware/updates/" UTS_RELEASE, "/lib/firmware/updates", "/lib/firmware/" UTS_RELEASE, - "/lib/firmware" + "/lib/firmware", + "/firmware/image" }; /* @@ -302,7 +330,14 @@ static int fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf) size = i_size_read(file_inode(file)); if (size <= 0) return -EINVAL; - buf = vmalloc(size); + if (fw_buf->dest_size > 0 && fw_buf->dest_size < size) + return -EINVAL; + + if (fw_buf->dest_addr) + buf = fw_buf->map_fw_mem(fw_buf->dest_addr, + fw_buf->dest_size, fw_buf->map_data); + else + buf = vmalloc(size); if (!buf) return -ENOMEM; rc = kernel_read(file, 0, buf, size); @@ -316,14 +351,20 @@ static int fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf) goto fail; fw_buf->data = buf; fw_buf->size = size; + if (fw_buf->dest_addr) + fw_buf->unmap_fw_mem(buf, fw_buf->size, fw_buf->map_data); return 0; fail: - vfree(buf); + if (fw_buf->dest_addr) + fw_buf->unmap_fw_mem(buf, fw_buf->size, fw_buf->map_data); + else + vfree(buf); return rc; } static int fw_get_filesystem_firmware(struct device *device, - struct firmware_buf *buf) + struct firmware_buf *buf, + phys_addr_t dest_addr, size_t dest_size) { int i, len; int rc = -ENOENT; @@ -658,6 +699,10 @@ static ssize_t firmware_loading_store(struct device *dev, case 1: /* discarding any previous partial load */ if (!test_bit(FW_STATUS_DONE, &fw_buf->status)) { + if (fw_buf->dest_addr) { + set_bit(FW_STATUS_LOADING, &fw_buf->status); + break; + } for (i = 0; i < fw_buf->nr_pages; i++) __free_page(fw_buf->pages[i]); kfree(fw_buf->pages); @@ -715,6 +760,101 @@ out: static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store); +static int __firmware_data_rw(struct firmware_priv *fw_priv, char *buffer, + loff_t *offset, size_t count, int read) +{ + u8 __iomem *fw_buf; + struct firmware_buf *buf = fw_priv->buf; + int retval = count; + + if ((*offset + count) > buf->dest_size) { + pr_debug("%s: Failed size check.\n", __func__); + retval = -EINVAL; + goto out; + } + + fw_buf = buf->map_fw_mem(buf->dest_addr + *offset, count, + buf->map_data); + if (!fw_buf) { + pr_debug("%s: Failed ioremap.\n", __func__); + retval = -ENOMEM; + goto out; + } + + if (read) + memcpy(buffer, fw_buf, count); + else + memcpy(fw_buf, buffer, count); + + *offset += count; + buf->unmap_fw_mem(fw_buf, count, buf->map_data); + +out: + return retval; +} + +static ssize_t firmware_direct_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t offset, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct firmware_priv *fw_priv = to_firmware_priv(dev); + struct firmware *fw; + ssize_t ret_count; + + if (!fw_priv->fw) + return -ENODEV; + + mutex_lock(&fw_lock); + fw = fw_priv->fw; + + if (offset > fw->size) { + ret_count = 0; + goto out; + } + if (count > fw->size - offset) + count = fw->size - offset; + + if (test_bit(FW_STATUS_DONE, &fw_priv->buf->status)) { + ret_count = -ENODEV; + goto out; + } + + ret_count = __firmware_data_rw(fw_priv, buffer, &offset, count, 1); +out: + mutex_unlock(&fw_lock); + return ret_count; +} + +static ssize_t firmware_direct_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t offset, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct firmware_priv *fw_priv = to_firmware_priv(dev); + struct firmware *fw; + ssize_t retval; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + mutex_lock(&fw_lock); + fw = fw_priv->fw; + if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->buf->status)) { + retval = -ENODEV; + goto out; + } + + retval = __firmware_data_rw(fw_priv, buffer, &offset, count, 0); + if (retval < 0) + goto out; + + fw_priv->buf->size = max_t(size_t, offset, fw_priv->buf->size); +out: + mutex_unlock(&fw_lock); + return retval; +} + static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, char *buffer, loff_t offset, size_t count) @@ -864,6 +1004,13 @@ static struct bin_attribute firmware_attr_data = { .write = firmware_data_write, }; +static struct bin_attribute firmware_direct_attr_data = { + .attr = { .name = "data", .mode = 0644 }, + .size = 0, + .read = firmware_direct_read, + .write = firmware_direct_write, +}; + static struct attribute *fw_dev_attrs[] = { &dev_attr_loading.attr, NULL @@ -879,31 +1026,47 @@ static const struct attribute_group fw_dev_attr_group = { .bin_attrs = fw_dev_bin_attrs, }; +static struct bin_attribute *fw_dev_direct_bin_attrs[] = { + &firmware_direct_attr_data, + NULL +}; + +static const struct attribute_group fw_dev_direct_attr_group = { + .attrs = fw_dev_attrs, + .bin_attrs = fw_dev_direct_bin_attrs, +}; + static const struct attribute_group *fw_dev_attr_groups[] = { &fw_dev_attr_group, NULL }; +static const struct attribute_group *fw_dev_direct_attr_groups[] = { + &fw_dev_direct_attr_group, + NULL +}; + static struct firmware_priv * -fw_create_instance(struct firmware *firmware, const char *fw_name, - struct device *device, unsigned int opt_flags) +fw_create_instance(struct firmware *firmware, struct fw_desc *desc) { struct firmware_priv *fw_priv; struct device *f_dev; fw_priv = kzalloc(sizeof(*fw_priv), GFP_KERNEL); if (!fw_priv) { + dev_err(desc->device, "%s: kmalloc failed\n", __func__); fw_priv = ERR_PTR(-ENOMEM); goto exit; } - fw_priv->nowait = !!(opt_flags & FW_OPT_NOWAIT); + fw_priv->nowait = !!(desc->opt_flags & FW_OPT_NOWAIT); fw_priv->fw = firmware; + f_dev = &fw_priv->dev; device_initialize(f_dev); - dev_set_name(f_dev, "%s", fw_name); - f_dev->parent = device; + dev_set_name(f_dev, "%s", desc->name); + f_dev->parent = desc->device; f_dev->class = &firmware_class; f_dev->groups = fw_dev_attr_groups; exit: @@ -919,7 +1082,10 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, struct firmware_buf *buf = fw_priv->buf; /* fall back on userspace loading */ - buf->is_paged_buf = true; + buf->is_paged_buf = buf->dest_addr ? false : true; + + if (buf->dest_addr) + f_dev->groups = fw_dev_direct_attr_groups; dev_set_uevent_suppress(f_dev, true); @@ -954,7 +1120,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, if (is_fw_load_aborted(buf)) retval = -EAGAIN; - else if (!buf->data) + else if (!buf->data && buf->is_paged_buf) retval = -ENOMEM; device_del(f_dev); @@ -964,17 +1130,16 @@ err_put_dev: } static int fw_load_from_user_helper(struct firmware *firmware, - const char *name, struct device *device, - unsigned int opt_flags, long timeout) + struct fw_desc *desc, long timeout) { struct firmware_priv *fw_priv; - fw_priv = fw_create_instance(firmware, name, device, opt_flags); + fw_priv = fw_create_instance(firmware, desc); if (IS_ERR(fw_priv)) return PTR_ERR(fw_priv); fw_priv->buf = firmware->priv; - return _request_firmware_load(fw_priv, opt_flags, timeout); + return _request_firmware_load(fw_priv, desc->opt_flags, timeout); } #ifdef CONFIG_PM_SLEEP @@ -995,9 +1160,8 @@ static void kill_requests_without_uevent(void) #else /* CONFIG_FW_LOADER_USER_HELPER */ static inline int -fw_load_from_user_helper(struct firmware *firmware, const char *name, - struct device *device, unsigned int opt_flags, - long timeout) +fw_load_from_user_helper(struct firmware *firmware, + struct fw_desc *desc, long timeout) { return -ENOENT; } @@ -1036,8 +1200,7 @@ static int sync_cached_firmware_buf(struct firmware_buf *buf) * or a negative error code */ static int -_request_firmware_prepare(struct firmware **firmware_p, const char *name, - struct device *device) +_request_firmware_prepare(struct firmware **firmware_p, struct fw_desc *desc) { struct firmware *firmware; struct firmware_buf *buf; @@ -1045,17 +1208,31 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name, *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); if (!firmware) { - dev_err(device, "%s: kmalloc(struct firmware) failed\n", + dev_err(desc->device, "%s: kmalloc(struct firmware) failed\n", __func__); return -ENOMEM; } - if (fw_get_builtin_firmware(firmware, name)) { - dev_dbg(device, "firmware: using built-in firmware %s\n", name); + if (fw_get_builtin_firmware(firmware, desc->name)) { + dev_dbg(desc->device, "firmware: using built-in firmware %s\n", + desc->name); return 0; /* assigned */ } - ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf); + if (desc->opt_flags & FW_OPT_NOCACHE) { + buf = __allocate_fw_buf(desc->name, NULL); + if (!buf) + return -ENOMEM; + buf->dest_addr = desc->dest_addr; + buf->dest_size = desc->dest_size; + buf->map_fw_mem = desc->map_fw_mem; + buf->unmap_fw_mem = desc->unmap_fw_mem; + buf->map_data = desc->map_data; + firmware->priv = buf; + return 1; + } + + ret = fw_lookup_and_allocate_buf(desc->name, &fw_cache, &buf); /* * bind with 'buf' now to avoid warning in failure path @@ -1094,15 +1271,19 @@ static int assign_firmware_buf(struct firmware *fw, struct device *device, * device may has been deleted already, but the problem * should be fixed in devres or driver core. */ - /* don't cache firmware handled without uevent */ - if (device && (opt_flags & FW_OPT_UEVENT)) + /* don't cache firmware handled without uevent, or when explicitly + * disabled + */ + if (device && (opt_flags & FW_OPT_UEVENT) + && !(opt_flags & FW_OPT_NOCACHE)) fw_add_devm_name(device, buf->fw_id); /* * After caching firmware image is started, let it piggyback * on request firmware. */ - if (buf->fwc->state == FW_LOADER_START_CACHE) { + if (!(opt_flags & FW_OPT_NOCACHE) + && (buf->fwc->state == FW_LOADER_START_CACHE)) { if (fw_cache_piggyback_on_request(buf->fw_id)) kref_get(&buf->ref); } @@ -1114,58 +1295,56 @@ static int assign_firmware_buf(struct firmware *fw, struct device *device, } /* called from request_firmware() and request_firmware_work_func() */ -static int -_request_firmware(const struct firmware **firmware_p, const char *name, - struct device *device, unsigned int opt_flags) +static int _request_firmware(struct fw_desc *desc) { struct firmware *fw; long timeout; int ret; - if (!firmware_p) + if (!desc->firmware_p) return -EINVAL; - if (!name || name[0] == '\0') + if (!desc->name || desc->name[0] == '\0') return -EINVAL; - ret = _request_firmware_prepare(&fw, name, device); + ret = _request_firmware_prepare(&fw, desc); if (ret <= 0) /* error or already assigned */ goto out; ret = 0; timeout = firmware_loading_timeout(); - if (opt_flags & FW_OPT_NOWAIT) { + if (desc->opt_flags & FW_OPT_NOWAIT) { timeout = usermodehelper_read_lock_wait(timeout); if (!timeout) { - dev_dbg(device, "firmware: %s loading timed out\n", - name); + dev_dbg(desc->device, "firmware: %s loading timed out\n", + desc->name); ret = -EBUSY; goto out; } } else { ret = usermodehelper_read_trylock(); if (WARN_ON(ret)) { - dev_err(device, "firmware: %s will not be loaded\n", - name); + dev_err(desc->device, "firmware: %s will not be loaded\n", + desc->name); goto out; } } - ret = fw_get_filesystem_firmware(device, fw->priv); + ret = fw_get_filesystem_firmware(desc->device, fw->priv, + desc->dest_addr, desc->dest_size); if (ret) { - if (!(opt_flags & FW_OPT_NO_WARN)) - dev_warn(device, + if (!(desc->opt_flags & FW_OPT_NO_WARN)) + dev_dbg(desc->device, "Direct firmware load for %s failed with error %d\n", - name, ret); - if (opt_flags & FW_OPT_USERHELPER) { - dev_warn(device, "Falling back to user helper\n"); - ret = fw_load_from_user_helper(fw, name, device, - opt_flags, timeout); + desc->name, ret); + if (desc->opt_flags & FW_OPT_USERHELPER) { + dev_dbg(desc->device, "Falling back to user helper\n"); + ret = fw_load_from_user_helper(fw, desc, timeout); } } if (!ret) - ret = assign_firmware_buf(fw, device, opt_flags); + ret = assign_firmware_buf(fw, desc->device, desc->opt_flags); usermodehelper_read_unlock(); @@ -1175,7 +1354,7 @@ _request_firmware(const struct firmware **firmware_p, const char *name, fw = NULL; } - *firmware_p = fw; + *desc->firmware_p = fw; return ret; } @@ -1203,13 +1382,21 @@ int request_firmware(const struct firmware **firmware_p, const char *name, struct device *device) { + struct fw_desc desc; int ret; + desc.firmware_p = firmware_p; + desc.name = name; + desc.device = device; + desc.dest_addr = 0; + desc.dest_size = 0; + desc.opt_flags = FW_OPT_UEVENT | FW_OPT_FALLBACK; + /* Need to pin this module until return */ __module_get(THIS_MODULE); - ret = _request_firmware(firmware_p, name, device, - FW_OPT_UEVENT | FW_OPT_FALLBACK); + ret = _request_firmware(&desc); module_put(THIS_MODULE); + return ret; } EXPORT_SYMBOL(request_firmware); @@ -1228,17 +1415,70 @@ EXPORT_SYMBOL(request_firmware); int request_firmware_direct(const struct firmware **firmware_p, const char *name, struct device *device) { + struct fw_desc desc; int ret; + desc.firmware_p = firmware_p; + desc.name = name; + desc.device = device; + desc.opt_flags = FW_OPT_UEVENT | FW_OPT_NO_WARN; + + /* Need to pin this module until return */ __module_get(THIS_MODULE); - ret = _request_firmware(firmware_p, name, device, - FW_OPT_UEVENT | FW_OPT_NO_WARN); + ret = _request_firmware(&desc); module_put(THIS_MODULE); + return ret; } EXPORT_SYMBOL_GPL(request_firmware_direct); /** + * request_firmware_into_buf: - send firmware request and wait for it + * @dest_addr: Destination address for the firmware + * @dest_size: Size of destination buffer + * + * Similar to request_firmware, except takes in a buffer address and + * copies firmware data directly to that buffer. Returns the size of + * the firmware that was loaded at dest_addr. This API prevents the + * caching of images. +*/ +int +request_firmware_into_buf(const char *name, struct device *device, + phys_addr_t dest_addr, size_t dest_size, + void * (*map_fw_mem)(phys_addr_t phys, size_t size, + void *data), + void (*unmap_fw_mem)(void *virt, size_t sz, void *data), + void *map_data) +{ + struct fw_desc desc; + const struct firmware *fp = NULL; + int ret; + + if (dest_addr && !map_fw_mem) + return -EINVAL; + if (dest_addr && dest_size <= 0) + return -EINVAL; + + desc.firmware_p = &fp; + desc.name = name; + desc.device = device; + desc.opt_flags = FW_OPT_FALLBACK | FW_OPT_UEVENT | FW_OPT_NOCACHE; + desc.dest_addr = dest_addr; + desc.dest_size = dest_size; + desc.map_fw_mem = map_fw_mem; + desc.unmap_fw_mem = unmap_fw_mem; + desc.map_data = map_data; + + ret = _request_firmware(&desc); + if (ret) + return ret; + ret = fp->size; + release_firmware(fp); + return ret; +} +EXPORT_SYMBOL_GPL(request_firmware_into_buf); + +/** * release_firmware: - release the resource associated with a firmware image * @fw: firmware resource to release **/ @@ -1253,31 +1493,70 @@ void release_firmware(const struct firmware *fw) EXPORT_SYMBOL(release_firmware); /* Async support */ -struct firmware_work { - struct work_struct work; - struct module *module; - const char *name; - struct device *device; - void *context; - void (*cont)(const struct firmware *fw, void *context); - unsigned int opt_flags; -}; - static void request_firmware_work_func(struct work_struct *work) { - struct firmware_work *fw_work; const struct firmware *fw; + struct fw_desc *desc; + + desc = container_of(work, struct fw_desc, work); + desc->firmware_p = &fw; + _request_firmware(desc); + desc->cont(fw, desc->context); + put_device(desc->device); /* taken in request_firmware_nowait() */ + + module_put(desc->module); + kfree(desc); +} + +int +_request_firmware_nowait( + struct module *module, bool uevent, + const char *name, struct device *device, gfp_t gfp, void *context, + void (*cont)(const struct firmware *fw, void *context), + bool nocache, phys_addr_t dest_addr, size_t dest_size, + void * (*map_fw_mem)(phys_addr_t phys, size_t size, void *data), + void (*unmap_fw_mem)(void *virt, size_t size, void *data), + void *map_data) +{ + struct fw_desc *desc; + + if (dest_addr && !map_fw_mem) + return -EINVAL; + if (dest_addr && dest_size <= 0) + return -EINVAL; - fw_work = container_of(work, struct firmware_work, work); + desc = kzalloc(sizeof(struct fw_desc), gfp); + if (!desc) + return -ENOMEM; + + desc->module = module; + desc->name = name; + desc->device = device; + desc->context = context; + desc->cont = cont; + desc->dest_addr = dest_addr; + desc->dest_size = dest_size; + desc->map_fw_mem = map_fw_mem; + desc->unmap_fw_mem = unmap_fw_mem; + desc->map_data = map_data; + desc->opt_flags = FW_OPT_FALLBACK | FW_OPT_NOWAIT; + + if (uevent) + desc->opt_flags |= FW_OPT_UEVENT; + else + desc->opt_flags |= FW_OPT_USERHELPER; + if (nocache) + desc->opt_flags |= FW_OPT_NOCACHE; - _request_firmware(&fw, fw_work->name, fw_work->device, - fw_work->opt_flags); - fw_work->cont(fw, fw_work->context); - put_device(fw_work->device); /* taken in request_firmware_nowait() */ + if (!try_module_get(module)) { + kfree(desc); + return -EFAULT; + } - module_put(fw_work->module); - kfree_const(fw_work->name); - kfree(fw_work); + get_device(desc->device); + INIT_WORK(&desc->work, request_firmware_work_func); + schedule_work(&desc->work); + return 0; } /** @@ -1309,37 +1588,38 @@ request_firmware_nowait( const char *name, struct device *device, gfp_t gfp, void *context, void (*cont)(const struct firmware *fw, void *context)) { - struct firmware_work *fw_work; - - fw_work = kzalloc(sizeof(struct firmware_work), gfp); - if (!fw_work) - return -ENOMEM; - - fw_work->module = module; - fw_work->name = kstrdup_const(name, gfp); - if (!fw_work->name) { - kfree(fw_work); - return -ENOMEM; - } - fw_work->device = device; - fw_work->context = context; - fw_work->cont = cont; - fw_work->opt_flags = FW_OPT_NOWAIT | FW_OPT_FALLBACK | - (uevent ? FW_OPT_UEVENT : FW_OPT_USERHELPER); - - if (!try_module_get(module)) { - kfree_const(fw_work->name); - kfree(fw_work); - return -EFAULT; - } - - get_device(fw_work->device); - INIT_WORK(&fw_work->work, request_firmware_work_func); - schedule_work(&fw_work->work); - return 0; + return _request_firmware_nowait(module, uevent, name, device, gfp, + context, cont, false, 0, 0, NULL, NULL, NULL); } EXPORT_SYMBOL(request_firmware_nowait); +/** + * request_firmware_nowait_into_buf - asynchronous version of request_firmware + * @dest_addr: Destination address for the firmware + * @dest_size: Size of destination buffer + * + * Similar to request_firmware_nowait, except loads the firmware + * directly to a destination address without using an intermediate + * buffer. + * + **/ +int +request_firmware_nowait_into_buf( + struct module *module, bool uevent, + const char *name, struct device *device, gfp_t gfp, void *context, + void (*cont)(const struct firmware *fw, void *context), + phys_addr_t dest_addr, size_t dest_size, + void * (*map_fw_mem)(phys_addr_t phys, size_t size, void *data), + void (*unmap_fw_mem)(void *virt, size_t size, void *data), + void *map_data) +{ + return _request_firmware_nowait(module, uevent, name, device, gfp, + context, cont, true, dest_addr, + dest_size, map_fw_mem, unmap_fw_mem, + map_data); +} +EXPORT_SYMBOL_GPL(request_firmware_nowait_into_buf); + #ifdef CONFIG_PM_SLEEP static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain); |