Skip to content
Snippets Groups Projects
firmware_class.c 37 KiB
Newer Older
  • Learn to ignore specific revisions
  • 
    	if (ret > 0) {
    		ret = sync_cached_firmware_buf(buf);
    		if (!ret) {
    			fw_set_page_data(buf, firmware);
    			return 0; /* assigned */
    		}
    
    	if (ret < 0)
    		return ret;
    	return 1; /* need to load */
    }
    
    
    static int assign_firmware_buf(struct firmware *fw, struct device *device,
    				bool skip_cache)
    
    {
    	struct firmware_buf *buf = fw->priv;
    
    
    	if (!buf->size || is_fw_load_aborted(buf)) {
    
    		mutex_unlock(&fw_lock);
    		return -ENOENT;
    
    	/*
    	 * add firmware name into devres list so that we can auto cache
    	 * and uncache firmware for device.
    	 *
    	 * device may has been deleted already, but the problem
    	 * should be fixed in devres or driver core.
    	 */
    
    		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 (fw_cache_piggyback_on_request(buf->fw_id))
    			kref_get(&buf->ref);
    	}
    
    	/* pass the pages buffer to driver at the last minute */
    	fw_set_page_data(buf, fw);
    
    /* called from request_firmware() and request_firmware_work_func() */
    static int
    _request_firmware(const struct firmware **firmware_p, const char *name,
    		  struct device *device, bool uevent, bool nowait)
    {
    	struct firmware *fw;
    	long timeout;
    	int ret;
    
    	if (!firmware_p)
    		return -EINVAL;
    
    	ret = _request_firmware_prepare(&fw, name, device);
    	if (ret <= 0) /* error or already assigned */
    		goto out;
    
    	ret = 0;
    	timeout = firmware_loading_timeout();
    	if (nowait) {
    		timeout = usermodehelper_read_lock_wait(timeout);
    		if (!timeout) {
    			dev_dbg(device, "firmware: %s loading timed out\n",
    				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);
    			goto out;
    		}
    	}
    
    	if (!fw_get_filesystem_firmware(device, fw->priv))
    		ret = fw_load_from_user_helper(fw, name, device,
    					       uevent, nowait, timeout);
    
    
    	/* don't cache firmware handled without uevent */
    
    		ret = assign_firmware_buf(fw, device, !uevent);
    
    
    	usermodehelper_read_unlock();
    
     out:
    	if (ret < 0) {
    		release_firmware(fw);
    		fw = NULL;
    	}
    
    	*firmware_p = fw;
    	return ret;
    }
    
    
     * request_firmware: - send firmware request and wait for it
    
     * @firmware_p: pointer to firmware image
     * @name: name of firmware file
     * @device: device for which firmware is being loaded
     *
     *      @firmware_p will be used to return a firmware image by the name
    
     *      of @name for device @device.
     *
     *      Should be called from user context where sleeping is allowed.
     *
    
     *      @name will be used as $FIRMWARE in the uevent environment and
    
     *      should be distinctive enough not to be confused with any other
     *      firmware image for this or any other device.
    
     *
     *	Caller must hold the reference count of @device.
    
     *
     *	The function can be called safely inside device's suspend and
     *	resume callback.
    
     **/
    int
    request_firmware(const struct firmware **firmware_p, const char *name,
                     struct device *device)
    {
    
    	return _request_firmware(firmware_p, name, device, true, false);
    
    EXPORT_SYMBOL(request_firmware);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /**
     * release_firmware: - release the resource associated with a firmware image
    
     * @fw: firmware resource to release
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     **/
    
    void release_firmware(const struct firmware *fw)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	if (fw) {
    
    		if (!fw_is_builtin_firmware(fw))
    			firmware_free_data(fw);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		kfree(fw);
    	}
    }
    
    EXPORT_SYMBOL(release_firmware);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    /* 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);
    
    static void request_firmware_work_func(struct work_struct *work)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct firmware_work *fw_work;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	const struct firmware *fw;
    
    	fw_work = container_of(work, struct firmware_work, work);
    
    	_request_firmware(&fw, fw_work->name, fw_work->device,
    			  fw_work->uevent, true);
    
    	fw_work->cont(fw, fw_work->context);
    
    	put_device(fw_work->device); /* taken in request_firmware_nowait() */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	module_put(fw_work->module);
    	kfree(fw_work);
    }
    
    /**
    
     * request_firmware_nowait - asynchronous version of request_firmware
    
     * @module: module requesting the firmware
    
     * @uevent: sends uevent to copy the firmware image if this flag
    
     *	is non-zero else the firmware copy must be done manually.
     * @name: name of firmware file
     * @device: device for which firmware is being loaded
    
     * @context: will be passed over to @cont, and
     *	@fw may be %NULL if firmware request fails.
     * @cont: function will be called asynchronously when the firmware
     *	request is over.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
    
     *	Caller must hold the reference count of @device.
     *
    
     *	Asynchronous variant of request_firmware() for user contexts:
     *		- sleep for as small periods as possible since it may
     *		increase kernel boot time of built-in device drivers
     *		requesting firmware in their ->probe() methods, if
     *		@gfp is GFP_KERNEL.
     *
     *		- can't sleep at all if @gfp is GFP_ATOMIC.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     **/
    int
    request_firmware_nowait(
    
    	struct module *module, bool uevent,
    
    	const char *name, struct device *device, gfp_t gfp, void *context,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	void (*cont)(const struct firmware *fw, void *context))
    {
    
    	struct firmware_work *fw_work;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	fw_work = kzalloc(sizeof (struct firmware_work), gfp);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!fw_work)
    		return -ENOMEM;
    
    
    	fw_work->module = module;
    	fw_work->name = name;
    	fw_work->device = device;
    	fw_work->context = context;
    	fw_work->cont = cont;
    	fw_work->uevent = uevent;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!try_module_get(module)) {
    		kfree(fw_work);
    		return -EFAULT;
    	}
    
    
    	get_device(fw_work->device);
    
    	INIT_WORK(&fw_work->work, request_firmware_work_func);
    	schedule_work(&fw_work->work);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    }
    
    EXPORT_SYMBOL(request_firmware_nowait);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    /**
     * cache_firmware - cache one firmware image in kernel memory space
     * @fw_name: the firmware image name
     *
     * Cache firmware in kernel memory so that drivers can use it when
     * system isn't ready for them to request firmware image from userspace.
     * Once it returns successfully, driver can use request_firmware or its
     * nowait version to get the cached firmware without any interacting
     * with userspace
     *
     * Return 0 if the firmware image has been cached successfully
     * Return !0 otherwise
     *
     */
    int cache_firmware(const char *fw_name)
    {
    	int ret;
    	const struct firmware *fw;
    
    	pr_debug("%s: %s\n", __func__, fw_name);
    
    	ret = request_firmware(&fw, fw_name, NULL);
    	if (!ret)
    		kfree(fw);
    
    	pr_debug("%s: %s ret=%d\n", __func__, fw_name, ret);
    
    	return ret;
    }
    
    EXPORT_SYMBOL_GPL(cache_firmware);
    
    
    /**
     * uncache_firmware - remove one cached firmware image
     * @fw_name: the firmware image name
     *
     * Uncache one firmware image which has been cached successfully
     * before.
     *
     * Return 0 if the firmware cache has been removed successfully
     * Return !0 otherwise
     *
     */
    int uncache_firmware(const char *fw_name)
    {
    	struct firmware_buf *buf;
    	struct firmware fw;
    
    	pr_debug("%s: %s\n", __func__, fw_name);
    
    	if (fw_get_builtin_firmware(&fw, fw_name))
    		return 0;
    
    	buf = fw_lookup_buf(fw_name);
    	if (buf) {
    		fw_free_buf(buf);
    		return 0;
    	}
    
    	return -EINVAL;
    }
    
    EXPORT_SYMBOL_GPL(uncache_firmware);
    
    #ifdef CONFIG_PM_SLEEP
    
    static ASYNC_DOMAIN_EXCLUSIVE(fw_cache_domain);
    
    
    static struct fw_cache_entry *alloc_fw_cache_entry(const char *name)
    {
    	struct fw_cache_entry *fce;
    
    	fce = kzalloc(sizeof(*fce) + strlen(name) + 1, GFP_ATOMIC);
    	if (!fce)
    		goto exit;
    
    	strcpy(fce->name, name);
    exit:
    	return fce;
    }
    
    
    static int __fw_entry_found(const char *name)
    
    {
    	struct firmware_cache *fwc = &fw_cache;
    	struct fw_cache_entry *fce;
    
    	list_for_each_entry(fce, &fwc->fw_names, list) {
    		if (!strcmp(fce->name, name))
    
    	return 0;
    }
    
    static int fw_cache_piggyback_on_request(const char *name)
    {
    	struct firmware_cache *fwc = &fw_cache;
    	struct fw_cache_entry *fce;
    	int ret = 0;
    
    	spin_lock(&fwc->name_lock);
    	if (__fw_entry_found(name))
    		goto found;
    
    
    	fce = alloc_fw_cache_entry(name);
    	if (fce) {
    		ret = 1;
    		list_add(&fce->list, &fwc->fw_names);
    		pr_debug("%s: fw: %s\n", __func__, name);
    	}
    found:
    	spin_unlock(&fwc->name_lock);
    	return ret;
    }
    
    
    static void free_fw_cache_entry(struct fw_cache_entry *fce)
    {
    	kfree(fce);
    }
    
    static void __async_dev_cache_fw_image(void *fw_entry,
    				       async_cookie_t cookie)
    {
    	struct fw_cache_entry *fce = fw_entry;
    	struct firmware_cache *fwc = &fw_cache;
    	int ret;
    
    	ret = cache_firmware(fce->name);
    
    	if (ret) {
    		spin_lock(&fwc->name_lock);
    		list_del(&fce->list);
    		spin_unlock(&fwc->name_lock);
    
    }
    
    /* called with dev->devres_lock held */
    static void dev_create_fw_entry(struct device *dev, void *res,
    				void *data)
    {
    	struct fw_name_devm *fwn = res;
    	const char *fw_name = fwn->name;
    	struct list_head *head = data;
    	struct fw_cache_entry *fce;
    
    	fce = alloc_fw_cache_entry(fw_name);
    	if (fce)
    		list_add(&fce->list, head);
    }
    
    static int devm_name_match(struct device *dev, void *res,
    			   void *match_data)
    {
    	struct fw_name_devm *fwn = res;
    	return (fwn->magic == (unsigned long)match_data);
    }
    
    
    static void dev_cache_fw_image(struct device *dev, void *data)
    
    {
    	LIST_HEAD(todo);
    	struct fw_cache_entry *fce;
    	struct fw_cache_entry *fce_next;
    	struct firmware_cache *fwc = &fw_cache;
    
    	devres_for_each_res(dev, fw_name_devm_release,
    			    devm_name_match, &fw_cache,
    			    dev_create_fw_entry, &todo);
    
    	list_for_each_entry_safe(fce, fce_next, &todo, list) {
    		list_del(&fce->list);
    
    		spin_lock(&fwc->name_lock);
    
    		/* only one cache entry for one firmware */
    		if (!__fw_entry_found(fce->name)) {
    			list_add(&fce->list, &fwc->fw_names);
    		} else {
    			free_fw_cache_entry(fce);
    			fce = NULL;
    		}
    
    		spin_unlock(&fwc->name_lock);
    
    
    			async_schedule_domain(__async_dev_cache_fw_image,
    					      (void *)fce,
    					      &fw_cache_domain);
    
    	}
    }
    
    static void __device_uncache_fw_images(void)
    {
    	struct firmware_cache *fwc = &fw_cache;
    	struct fw_cache_entry *fce;
    
    	spin_lock(&fwc->name_lock);
    	while (!list_empty(&fwc->fw_names)) {
    		fce = list_entry(fwc->fw_names.next,
    				struct fw_cache_entry, list);
    		list_del(&fce->list);
    		spin_unlock(&fwc->name_lock);
    
    		uncache_firmware(fce->name);
    		free_fw_cache_entry(fce);
    
    		spin_lock(&fwc->name_lock);
    	}
    	spin_unlock(&fwc->name_lock);
    }
    
    /**
     * device_cache_fw_images - cache devices' firmware
     *
     * If one device called request_firmware or its nowait version
     * successfully before, the firmware names are recored into the
     * device's devres link list, so device_cache_fw_images can call
     * cache_firmware() to cache these firmwares for the device,
     * then the device driver can load its firmwares easily at
     * time when system is not ready to complete loading firmware.
     */
    static void device_cache_fw_images(void)
    {
    	struct firmware_cache *fwc = &fw_cache;
    
    	DEFINE_WAIT(wait);
    
    	pr_debug("%s\n", __func__);
    
    
    	/* cancel uncache work */
    	cancel_delayed_work_sync(&fwc->work);
    
    
    	/*
    	 * use small loading timeout for caching devices' firmware
    	 * because all these firmware images have been loaded
    	 * successfully at lease once, also system is ready for
    	 * completing firmware loading now. The maximum size of
    	 * firmware in current distributions is about 2M bytes,
    	 * so 10 secs should be enough.
    	 */
    	old_timeout = loading_timeout;
    	loading_timeout = 10;
    
    
    	mutex_lock(&fw_lock);
    	fwc->state = FW_LOADER_START_CACHE;
    
    	dpm_for_each_dev(NULL, dev_cache_fw_image);
    
    
    	/* wait for completion of caching firmware for all devices */
    
    	async_synchronize_full_domain(&fw_cache_domain);
    
    
    	loading_timeout = old_timeout;
    
    }
    
    /**
     * device_uncache_fw_images - uncache devices' firmware
     *
     * uncache all firmwares which have been cached successfully
     * by device_uncache_fw_images earlier
     */
    static void device_uncache_fw_images(void)
    {
    	pr_debug("%s\n", __func__);
    	__device_uncache_fw_images();
    }
    
    static void device_uncache_fw_images_work(struct work_struct *work)
    {
    	device_uncache_fw_images();
    }
    
    /**
     * device_uncache_fw_images_delay - uncache devices firmwares
     * @delay: number of milliseconds to delay uncache device firmwares
     *
     * uncache all devices's firmwares which has been cached successfully
     * by device_cache_fw_images after @delay milliseconds.
     */
    static void device_uncache_fw_images_delay(unsigned long delay)
    {
    	schedule_delayed_work(&fw_cache.work,
    			msecs_to_jiffies(delay));
    }
    
    
    static int fw_pm_notify(struct notifier_block *notify_block,
    			unsigned long mode, void *unused)
    {
    	switch (mode) {
    	case PM_HIBERNATION_PREPARE:
    	case PM_SUSPEND_PREPARE:
    
    		device_cache_fw_images();
    		break;
    
    	case PM_POST_SUSPEND:
    	case PM_POST_HIBERNATION:
    	case PM_POST_RESTORE:
    
    		/*
    		 * In case that system sleep failed and syscore_suspend is
    		 * not called.
    		 */
    		mutex_lock(&fw_lock);
    		fw_cache.state = FW_LOADER_NO_CACHE;
    		mutex_unlock(&fw_lock);
    
    
    		device_uncache_fw_images_delay(10 * MSEC_PER_SEC);
    		break;
    	}
    
    	return 0;
    }
    
    
    /* stop caching firmware once syscore_suspend is reached */
    static int fw_suspend(void)
    {
    	fw_cache.state = FW_LOADER_NO_CACHE;
    	return 0;
    }
    
    static struct syscore_ops fw_syscore_ops = {
    	.suspend = fw_suspend,
    };
    
    #else
    static int fw_cache_piggyback_on_request(const char *name)
    {
    	return 0;
    }
    #endif
    
    static void __init fw_cache_init(void)
    {
    	spin_lock_init(&fw_cache.lock);
    	INIT_LIST_HEAD(&fw_cache.head);
    
    	fw_cache.state = FW_LOADER_NO_CACHE;
    
    #ifdef CONFIG_PM_SLEEP
    
    	spin_lock_init(&fw_cache.name_lock);
    	INIT_LIST_HEAD(&fw_cache.fw_names);
    
    	INIT_DELAYED_WORK(&fw_cache.work,
    			  device_uncache_fw_images_work);
    
    
    	fw_cache.pm_notify.notifier_call = fw_pm_notify;
    	register_pm_notifier(&fw_cache.pm_notify);
    
    
    	register_syscore_ops(&fw_syscore_ops);
    
    static int __init firmware_class_init(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    #ifdef CONFIG_FW_LOADER_USER_HELPER
    
    	register_reboot_notifier(&fw_shutdown_nb);
    
    	return class_register(&firmware_class);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    
    static void __exit firmware_class_exit(void)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    #ifdef CONFIG_PM_SLEEP
    
    	unregister_syscore_ops(&fw_syscore_ops);
    
    	unregister_pm_notifier(&fw_cache.pm_notify);
    
    #ifdef CONFIG_FW_LOADER_USER_HELPER
    
    	unregister_reboot_notifier(&fw_shutdown_nb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	class_unregister(&firmware_class);
    
    fs_initcall(firmware_class_init);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    module_exit(firmware_class_exit);