base/firmware retry firmware load after rootfs mount 63/200163/2
authorChristoph Manszewski <c.manszewski@samsung.com>
Fri, 15 Feb 2019 14:50:00 +0000 (15:50 +0100)
committerSeung-Woo Kim <sw0312.kim@samsung.com>
Wed, 27 Feb 2019 00:27:40 +0000 (00:27 +0000)
Return -EPROBE_DEFER for request_firmware() until root filesystem is
mounted. In case of request_firmware_nowait() create a list of deferred
firmware load requests, and retry firmware load after root filesytem is
mounted.

This allows to have wifi drivers build into the kernel, but
the firmware files shipped on the root filesystem.

Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: Christoph Manszewski <c.manszewski@samsung.com>
Change-Id: I40c0a2a98ca9b1a95fb743a848d2366250cae839

drivers/base/dd.c
drivers/base/firmware_class.c
include/linux/device.h
include/linux/firmware.h
init/do_mounts.c

index d928cc6d06388c3541e48b9f104c1516ee5651de..ed4cb846d158c2830f4aad0bbf89ffbfc88303a9 100644 (file)
@@ -180,7 +180,7 @@ static bool driver_deferred_probe_enable = false;
  * changes in the midst of a probe, then deferred processing should be triggered
  * again.
  */
-static void driver_deferred_probe_trigger(void)
+void driver_deferred_probe_trigger(void)
 {
        if (!driver_deferred_probe_enable)
                return;
index 4b57cf5bc81d15f47069832a5a631a9f254e567a..5eaa0ffeb5f9f3d2dc2156dab88f27b7224c1308 100644 (file)
@@ -32,6 +32,7 @@
 #include <linux/syscore_ops.h>
 #include <linux/reboot.h>
 #include <linux/security.h>
+#include <linux/root_dev.h>
 
 #include <generated/utsrelease.h>
 
@@ -392,6 +393,9 @@ fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf)
        enum kernel_read_file_id id = READING_FIRMWARE;
        size_t msize = INT_MAX;
 
+       if (ROOT_DEV == 0)
+               return -EPROBE_DEFER;
+
        /* Already populated data member means we're loading into a buffer */
        if (buf->data) {
                id = READING_FIRMWARE_PREALLOC_BUFFER;
@@ -1351,17 +1355,28 @@ struct firmware_work {
        void *context;
        void (*cont)(const struct firmware *fw, void *context);
        unsigned int opt_flags;
+       struct list_head node;
 };
+static LIST_HEAD(firmware_work_list);
+static DEFINE_SPINLOCK(firmware_work_list_lock);
 
 static void request_firmware_work_func(struct work_struct *work)
 {
        struct firmware_work *fw_work;
        const struct firmware *fw;
+       int err = 0;
 
        fw_work = container_of(work, struct firmware_work, work);
 
-       _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0,
-                         fw_work->opt_flags);
+       err = _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0,
+                       fw_work->opt_flags);
+       if (err == -EPROBE_DEFER) {
+               spin_lock(&firmware_work_list_lock);
+               list_add_tail(&fw_work->node, &firmware_work_list);
+               spin_unlock(&firmware_work_list_lock);
+               return;
+       }
+
        fw_work->cont(fw, fw_work->context);
        put_device(fw_work->device); /* taken in request_firmware_nowait() */
 
@@ -1370,6 +1385,24 @@ static void request_firmware_work_func(struct work_struct *work)
        kfree(fw_work);
 }
 
+void retry_request_firmware(void)
+{
+       struct firmware_work *fw_work;
+
+       spin_lock(&firmware_work_list_lock);
+       while (!list_empty(&firmware_work_list)) {
+               fw_work = list_first_entry(&firmware_work_list,
+                                          struct firmware_work, node);
+               list_del(&fw_work->node);
+               spin_unlock(&firmware_work_list_lock);
+
+               request_firmware_work_func(&fw_work->work);
+
+               spin_lock(&firmware_work_list_lock);
+       }
+       spin_unlock(&firmware_work_list_lock);
+}
+
 /**
  * request_firmware_nowait - asynchronous version of request_firmware
  * @module: module requesting the firmware
@@ -1416,6 +1449,7 @@ request_firmware_nowait(
        fw_work->cont = cont;
        fw_work->opt_flags = FW_OPT_NOWAIT | FW_OPT_FALLBACK |
                (uevent ? FW_OPT_UEVENT : FW_OPT_USERHELPER);
+       INIT_LIST_HEAD(&fw_work->node);
 
        if (!try_module_get(module)) {
                kfree_const(fw_work->name);
index 66fe271c2544d5cf861e1e5c58e23a0793ba1ed6..79c377478d015cbf0d0100b80d8c017abe2ae6dd 100644 (file)
@@ -294,6 +294,7 @@ extern void driver_unregister(struct device_driver *drv);
 
 extern struct device_driver *driver_find(const char *name,
                                         struct bus_type *bus);
+extern void driver_deferred_probe_trigger(void);
 extern int driver_probe_done(void);
 extern void wait_for_device_probe(void);
 
index d4508080348d301444a50cfadf6947ac363147a5..740d77ed4a5e5eca4c629386229eceaa4d0993ab 100644 (file)
@@ -42,6 +42,7 @@ struct builtin_fw {
 #if defined(CONFIG_FW_LOADER) || (defined(CONFIG_FW_LOADER_MODULE) && defined(MODULE))
 int request_firmware(const struct firmware **fw, const char *name,
                     struct device *device);
+void retry_request_firmware(void);
 int request_firmware_nowait(
        struct module *module, bool uevent,
        const char *name, struct device *device, gfp_t gfp, void *context,
@@ -59,6 +60,11 @@ static inline int request_firmware(const struct firmware **fw,
 {
        return -EINVAL;
 }
+
+static inline void retry_request_firmware(void)
+{
+}
+
 static inline int request_firmware_nowait(
        struct module *module, bool uevent,
        const char *name, struct device *device, gfp_t gfp, void *context,
index 7cf4f6dafd5f32031db0a1d28072fc93c0474f5a..83644453ef1c6ad8ec32e812b907719dc177d51c 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/slab.h>
 #include <linux/ramfs.h>
 #include <linux/shmem_fs.h>
+#include <linux/firmware.h>
 
 #include <linux/nfs_fs.h>
 #include <linux/nfs_fs_sb.h>
@@ -601,6 +602,8 @@ out:
        devtmpfs_mount("dev");
        sys_mount(".", "/", NULL, MS_MOVE, NULL);
        sys_chroot(".");
+       driver_deferred_probe_trigger();
+       retry_request_firmware();
 }
 
 static bool is_tmpfs;