]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/base/firmware_class.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[karo-tx-linux.git] / drivers / base / firmware_class.c
index a2be09dd7771455c64493fe8cee44fdba1433f22..4b1f9265887f1048ff5a64cc14fa3d04964b519b 100644 (file)
@@ -88,11 +88,6 @@ enum {
        FW_STATUS_ABORT,
 };
 
-enum fw_buf_fmt {
-       VMALLOC_BUF,    /* used in direct loading */
-       PAGE_BUF,       /* used in loading via userspace */
-};
-
 static int loading_timeout = 60;       /* In seconds */
 
 static inline long firmware_loading_timeout(void)
@@ -128,12 +123,14 @@ struct firmware_buf {
        struct completion completion;
        struct firmware_cache *fwc;
        unsigned long status;
-       enum fw_buf_fmt fmt;
        void *data;
        size_t size;
+#ifdef CONFIG_FW_LOADER_USER_HELPER
+       bool is_paged_buf;
        struct page **pages;
        int nr_pages;
        int page_array_size;
+#endif
        char fw_id[];
 };
 
@@ -142,14 +139,6 @@ struct fw_cache_entry {
        char name[];
 };
 
-struct firmware_priv {
-       struct delayed_work timeout_work;
-       bool nowait;
-       struct device dev;
-       struct firmware_buf *buf;
-       struct firmware *fw;
-};
-
 struct fw_name_devm {
        unsigned long magic;
        char name[];
@@ -182,7 +171,6 @@ static struct firmware_buf *__allocate_fw_buf(const char *fw_name,
        strcpy(buf->fw_id, fw_name);
        buf->fwc = fwc;
        init_completion(&buf->completion);
-       buf->fmt = VMALLOC_BUF;
 
        pr_debug("%s: fw-%s buf=%p\n", __func__, fw_name, buf);
 
@@ -240,7 +228,6 @@ static void __fw_free_buf(struct kref *ref)
 {
        struct firmware_buf *buf = to_fwbuf(ref);
        struct firmware_cache *fwc = buf->fwc;
-       int i;
 
        pr_debug("%s: fw-%s buf=%p data=%p size=%u\n",
                 __func__, buf->fw_id, buf, buf->data,
@@ -249,13 +236,15 @@ static void __fw_free_buf(struct kref *ref)
        list_del(&buf->list);
        spin_unlock(&fwc->lock);
 
-
-       if (buf->fmt == PAGE_BUF) {
+#ifdef CONFIG_FW_LOADER_USER_HELPER
+       if (buf->is_paged_buf) {
+               int i;
                vunmap(buf->data);
                for (i = 0; i < buf->nr_pages; i++)
                        __free_page(buf->pages[i]);
                kfree(buf->pages);
        } else
+#endif
                vfree(buf->data);
        kfree(buf);
 }
@@ -319,7 +308,8 @@ static bool fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf
        return true;
 }
 
-static bool fw_get_filesystem_firmware(struct firmware_buf *buf)
+static bool fw_get_filesystem_firmware(struct device *device,
+                                      struct firmware_buf *buf)
 {
        int i;
        bool success = false;
@@ -343,9 +333,114 @@ static bool fw_get_filesystem_firmware(struct firmware_buf *buf)
                        break;
        }
        __putname(path);
+
+       if (success) {
+               dev_dbg(device, "firmware: direct-loading firmware %s\n",
+                       buf->fw_id);
+               mutex_lock(&fw_lock);
+               set_bit(FW_STATUS_DONE, &buf->status);
+               complete_all(&buf->completion);
+               mutex_unlock(&fw_lock);
+       }
+
        return success;
 }
 
+/* firmware holds the ownership of pages */
+static void firmware_free_data(const struct firmware *fw)
+{
+       /* Loaded directly? */
+       if (!fw->priv) {
+               vfree(fw->data);
+               return;
+       }
+       fw_free_buf(fw->priv);
+}
+
+/* store the pages buffer info firmware from buf */
+static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw)
+{
+       fw->priv = buf;
+#ifdef CONFIG_FW_LOADER_USER_HELPER
+       fw->pages = buf->pages;
+#endif
+       fw->size = buf->size;
+       fw->data = buf->data;
+
+       pr_debug("%s: fw-%s buf=%p data=%p size=%u\n",
+                __func__, buf->fw_id, buf, buf->data,
+                (unsigned int)buf->size);
+}
+
+#ifdef CONFIG_PM_SLEEP
+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;
+}
+#else
+static int fw_add_devm_name(struct device *dev, const char *name)
+{
+       return 0;
+}
+#endif
+
+
+/*
+ * user-mode helper code
+ */
+#ifdef CONFIG_FW_LOADER_USER_HELPER
+struct firmware_priv {
+       struct delayed_work timeout_work;
+       bool nowait;
+       struct device dev;
+       struct firmware_buf *buf;
+       struct firmware *fw;
+};
+
 static struct firmware_priv *to_firmware_priv(struct device *dev)
 {
        return container_of(dev, struct firmware_priv, dev);
@@ -359,6 +454,9 @@ static void fw_load_abort(struct firmware_priv *fw_priv)
        complete_all(&buf->completion);
 }
 
+#define is_fw_load_aborted(buf)        \
+       test_bit(FW_STATUS_ABORT, &(buf)->status)
+
 static ssize_t firmware_timeout_show(struct class *class,
                                     struct class_attribute *attr,
                                     char *buf)
@@ -435,17 +533,6 @@ static ssize_t firmware_loading_show(struct device *dev,
        return sprintf(buf, "%d\n", loading);
 }
 
-/* firmware holds the ownership of pages */
-static void firmware_free_data(const struct firmware *fw)
-{
-       /* Loaded directly? */
-       if (!fw->priv) {
-               vfree(fw->data);
-               return;
-       }
-       fw_free_buf(fw->priv);
-}
-
 /* Some architectures don't have PAGE_KERNEL_RO */
 #ifndef PAGE_KERNEL_RO
 #define PAGE_KERNEL_RO PAGE_KERNEL
@@ -454,7 +541,7 @@ static void firmware_free_data(const struct firmware *fw)
 /* one pages buffer should be mapped/unmapped only once */
 static int fw_map_pages_buf(struct firmware_buf *buf)
 {
-       if (buf->fmt != PAGE_BUF)
+       if (!buf->is_paged_buf)
                return 0;
 
        if (buf->data)
@@ -727,171 +814,16 @@ exit:
        return fw_priv;
 }
 
-/* store the pages buffer info firmware from buf */
-static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw)
-{
-       fw->priv = buf;
-       fw->pages = buf->pages;
-       fw->size = buf->size;
-       fw->data = buf->data;
-
-       pr_debug("%s: fw-%s buf=%p data=%p size=%u\n",
-                __func__, buf->fw_id, buf, buf->data,
-                (unsigned int)buf->size);
-}
-
-#ifdef CONFIG_PM_SLEEP
-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;
-}
-#else
-static int fw_add_devm_name(struct device *dev, const char *name)
-{
-       return 0;
-}
-#endif
-
-static void _request_firmware_cleanup(const struct firmware **firmware_p)
-{
-       release_firmware(*firmware_p);
-       *firmware_p = NULL;
-}
-
-static struct firmware_priv *
-_request_firmware_prepare(const struct firmware **firmware_p, const char *name,
-                         struct device *device, bool uevent, bool nowait)
-{
-       struct firmware *firmware;
-       struct firmware_priv *fw_priv = NULL;
-       struct firmware_buf *buf;
-       int ret;
-
-       if (!firmware_p)
-               return ERR_PTR(-EINVAL);
-
-       *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL);
-       if (!firmware) {
-               dev_err(device, "%s: kmalloc(struct firmware) failed\n",
-                       __func__);
-               return ERR_PTR(-ENOMEM);
-       }
-
-       if (fw_get_builtin_firmware(firmware, name)) {
-               dev_dbg(device, "firmware: using built-in firmware %s\n", name);
-               return NULL;
-       }
-
-       ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf);
-       if (!ret)
-               fw_priv = fw_create_instance(firmware, name, device,
-                               uevent, nowait);
-
-       if (IS_ERR(fw_priv) || ret < 0) {
-               kfree(firmware);
-               *firmware_p = NULL;
-               return ERR_PTR(-ENOMEM);
-       } else if (fw_priv) {
-               fw_priv->buf = buf;
-
-               /*
-                * bind with 'buf' now to avoid warning in failure path
-                * of requesting firmware.
-                */
-               firmware->priv = buf;
-               return fw_priv;
-       }
-
-       /* share the cached buf, which is inprogessing or completed */
- check_status:
-       mutex_lock(&fw_lock);
-       if (test_bit(FW_STATUS_ABORT, &buf->status)) {
-               fw_priv = ERR_PTR(-ENOENT);
-               firmware->priv = buf;
-               _request_firmware_cleanup(firmware_p);
-               goto exit;
-       } else if (test_bit(FW_STATUS_DONE, &buf->status)) {
-               fw_priv = NULL;
-               fw_set_page_data(buf, firmware);
-               goto exit;
-       }
-       mutex_unlock(&fw_lock);
-       wait_for_completion(&buf->completion);
-       goto check_status;
-
-exit:
-       mutex_unlock(&fw_lock);
-       return fw_priv;
-}
-
+/* load a firmware via user helper */
 static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
                                  long timeout)
 {
        int retval = 0;
        struct device *f_dev = &fw_priv->dev;
        struct firmware_buf *buf = fw_priv->buf;
-       struct firmware_cache *fwc = &fw_cache;
-       int direct_load = 0;
-
-       /* try direct loading from fs first */
-       if (fw_get_filesystem_firmware(buf)) {
-               dev_dbg(f_dev->parent, "firmware: direct-loading"
-                       " firmware %s\n", buf->fw_id);
-
-               mutex_lock(&fw_lock);
-               set_bit(FW_STATUS_DONE, &buf->status);
-               mutex_unlock(&fw_lock);
-               complete_all(&buf->completion);
-               direct_load = 1;
-               goto handle_fw;
-       }
 
        /* fall back on userspace loading */
-       buf->fmt = PAGE_BUF;
+       buf->is_paged_buf = true;
 
        dev_set_uevent_suppress(f_dev, true);
 
@@ -929,47 +861,196 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, bool uevent,
 
        cancel_delayed_work_sync(&fw_priv->timeout_work);
 
-handle_fw:
+       fw_priv->buf = NULL;
+
+       device_remove_file(f_dev, &dev_attr_loading);
+err_del_bin_attr:
+       device_remove_bin_file(f_dev, &firmware_attr_data);
+err_del_dev:
+       device_del(f_dev);
+err_put_dev:
+       put_device(f_dev);
+       return retval;
+}
+
+static int fw_load_from_user_helper(struct firmware *firmware,
+                                   const char *name, struct device *device,
+                                   bool uevent, bool nowait, long timeout)
+{
+       struct firmware_priv *fw_priv;
+
+       fw_priv = fw_create_instance(firmware, name, device, uevent, nowait);
+       if (IS_ERR(fw_priv))
+               return PTR_ERR(fw_priv);
+
+       fw_priv->buf = firmware->priv;
+       return _request_firmware_load(fw_priv, uevent, timeout);
+}
+#else /* CONFIG_FW_LOADER_USER_HELPER */
+static inline int
+fw_load_from_user_helper(struct firmware *firmware, const char *name,
+                        struct device *device, bool uevent, bool nowait,
+                        long timeout)
+{
+       return -ENOENT;
+}
+
+/* No abort during direct loading */
+#define is_fw_load_aborted(buf) false
+
+#endif /* CONFIG_FW_LOADER_USER_HELPER */
+
+
+/* wait until the shared firmware_buf becomes ready (or error) */
+static int sync_cached_firmware_buf(struct firmware_buf *buf)
+{
+       int ret = 0;
+
+       mutex_lock(&fw_lock);
+       while (!test_bit(FW_STATUS_DONE, &buf->status)) {
+               if (is_fw_load_aborted(buf)) {
+                       ret = -ENOENT;
+                       break;
+               }
+               mutex_unlock(&fw_lock);
+               wait_for_completion(&buf->completion);
+               mutex_lock(&fw_lock);
+       }
+       mutex_unlock(&fw_lock);
+       return ret;
+}
+
+/* prepare firmware and firmware_buf structs;
+ * return 0 if a firmware is already assigned, 1 if need to load one,
+ * or a negative error code
+ */
+static int
+_request_firmware_prepare(struct firmware **firmware_p, const char *name,
+                         struct device *device)
+{
+       struct firmware *firmware;
+       struct firmware_buf *buf;
+       int ret;
+
+       *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL);
+       if (!firmware) {
+               dev_err(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);
+               return 0; /* assigned */
+       }
+
+       ret = fw_lookup_and_allocate_buf(name, &fw_cache, &buf);
+
+       /*
+        * bind with 'buf' now to avoid warning in failure path
+        * of requesting firmware.
+        */
+       firmware->priv = buf;
+
+       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)
+{
+       struct firmware_buf *buf = fw->priv;
+
        mutex_lock(&fw_lock);
-       if (!buf->size || test_bit(FW_STATUS_ABORT, &buf->status))
-               retval = -ENOENT;
+       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.
         *
-        * f_dev->parent may has been deleted already, but the problem
+        * device 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 (device)
+               fw_add_devm_name(device, buf->fw_id);
 
        /*
         * After caching firmware image is started, let it piggyback
         * on request firmware.
         */
-       if (!retval && fwc->state == FW_LOADER_START_CACHE) {
+       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_priv->fw);
-
-       fw_priv->buf = NULL;
+       fw_set_page_data(buf, fw);
        mutex_unlock(&fw_lock);
+       return 0;
+}
 
-       if (direct_load)
-               goto err_put_dev;
+/* 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;
 
-       device_remove_file(f_dev, &dev_attr_loading);
-err_del_bin_attr:
-       device_remove_bin_file(f_dev, &firmware_attr_data);
-err_del_dev:
-       device_del(f_dev);
-err_put_dev:
-       put_device(f_dev);
-       return retval;
+       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);
+       if (!ret)
+               ret = assign_firmware_buf(fw, device);
+
+       usermodehelper_read_unlock();
+
+ out:
+       if (ret < 0) {
+               release_firmware(fw);
+               fw = NULL;
+       }
+
+       *firmware_p = fw;
+       return ret;
 }
 
 /**
@@ -996,26 +1077,7 @@ int
 request_firmware(const struct firmware **firmware_p, const char *name,
                  struct device *device)
 {
-       struct firmware_priv *fw_priv;
-       int ret;
-
-       fw_priv = _request_firmware_prepare(firmware_p, name, device, true,
-                                           false);
-       if (IS_ERR_OR_NULL(fw_priv))
-               return PTR_RET(fw_priv);
-
-       ret = usermodehelper_read_trylock();
-       if (WARN_ON(ret)) {
-               dev_err(device, "firmware: %s will not be loaded\n", name);
-       } else {
-               ret = _request_firmware_load(fw_priv, true,
-                                       firmware_loading_timeout());
-               usermodehelper_read_unlock();
-       }
-       if (ret)
-               _request_firmware_cleanup(firmware_p);
-
-       return ret;
+       return _request_firmware(firmware_p, name, device, true, false);
 }
 
 /**
@@ -1046,33 +1108,13 @@ static void request_firmware_work_func(struct work_struct *work)
 {
        struct firmware_work *fw_work;
        const struct firmware *fw;
-       struct firmware_priv *fw_priv;
-       long timeout;
-       int ret;
 
        fw_work = container_of(work, struct firmware_work, work);
-       fw_priv = _request_firmware_prepare(&fw, fw_work->name, fw_work->device,
-                       fw_work->uevent, true);
-       if (IS_ERR_OR_NULL(fw_priv)) {
-               ret = PTR_RET(fw_priv);
-               goto out;
-       }
-
-       timeout = usermodehelper_read_lock_wait(firmware_loading_timeout());
-       if (timeout) {
-               ret = _request_firmware_load(fw_priv, fw_work->uevent, timeout);
-               usermodehelper_read_unlock();
-       } else {
-               dev_dbg(fw_work->device, "firmware: %s loading timed out\n",
-                       fw_work->name);
-               ret = -EAGAIN;
-       }
-       if (ret)
-               _request_firmware_cleanup(&fw);
 
- out:
+       _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);
+       put_device(fw_work->device); /* taken in request_firmware_nowait() */
 
        module_put(fw_work->module);
        kfree(fw_work);
@@ -1474,7 +1516,11 @@ static void __init fw_cache_init(void)
 static int __init firmware_class_init(void)
 {
        fw_cache_init();
+#ifdef CONFIG_FW_LOADER_USER_HELPER
        return class_register(&firmware_class);
+#else
+       return 0;
+#endif
 }
 
 static void __exit firmware_class_exit(void)
@@ -1483,7 +1529,9 @@ static void __exit firmware_class_exit(void)
        unregister_syscore_ops(&fw_syscore_ops);
        unregister_pm_notifier(&fw_cache.pm_notify);
 #endif
+#ifdef CONFIG_FW_LOADER_USER_HELPER
        class_unregister(&firmware_class);
+#endif
 }
 
 fs_initcall(firmware_class_init);