diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 0ac883777318..e9aca86d429b 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1820,6 +1820,18 @@
initcall functions. Useful for debugging built-in
modules and initcalls.
+ initramfs_async= [KNL] Normally, the initramfs image is
+ unpacked synchronously, before most devices
+ are initialized. When the initramfs is huge,
+ or on slow CPUs, this can take a significant
+ amount of time. Setting this option means the
+ unpacking is instead done in a background
+ thread, allowing the main init process to
+ begin calling device_initcall()s while the
+ initramfs is being unpacked.
+ Format: <bool>
+ Default set by CONFIG_INITRAMFS_ASYNC.
+
initrd= [BOOT] Specify the location of the initial ramdisk
initrdmem= [KNL] Specify a physical address and size from which to
diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c
index 78355095e00d..4fdb8219cd08 100644
--- a/drivers/base/firmware_loader/main.c
+++ b/drivers/base/firmware_loader/main.c
@@ -15,6 +15,7 @@
#include <linux/kernel_read_file.h>
#include <linux/module.h>
#include <linux/init.h>
+#include <linux/initrd.h>
#include <linux/timer.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
@@ -504,6 +505,7 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv,
if (!path)
return -ENOMEM;
+ wait_for_initramfs();
for (i = 0; i < ARRAY_SIZE(fw_path); i++) {
size_t file_size = 0;
size_t *file_size_ptr = NULL;diff --git a/include/linux/initrd.h b/include/linux/initrd.h
index 8db6f8c8030b..7c915a7b7e26 100644
--- a/include/linux/initrd.h
+++ b/include/linux/initrd.h
@@ -24,3 +24,10 @@ extern char __initramfs_start[];
extern unsigned long __initramfs_size;
void console_on_rootfs(void);
+
+#ifdef CONFIG_BLK_DEV_INITRD
+extern void _wait_for_initramfs(const char *caller);
+#define wait_for_initramfs() _wait_for_initramfs(__func__)
+#else
+static inline void wait_for_initramfs(void) { }
+#endifdiff --git a/init/initramfs.c b/init/initramfs.c
index 55b74d7e5260..3a59fd323314 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -530,6 +530,43 @@ static int __init keepinitrd_setup(char *__unused)
__setup("keepinitrd", keepinitrd_setup);
#endif
+static bool __initdata initramfs_async = CONFIG_INITRAMFS_ASYNC;
+static int __init initramfs_async_setup(char *str)
+{
+ strtobool(str, &initramfs_async);
+ return 1;
+}
+__setup("initramfs_async=", initramfs_async_setup);
+
+static __initdata struct work_struct initramfs_wrk;
+static DECLARE_COMPLETION(initramfs_done);
+static bool initramfs_unpack_started;
+
+void _wait_for_initramfs(const char *caller)
+{
+ unsigned long start;
+
+ if (try_wait_for_completion(&initramfs_done))
+ return;
+ if (!initramfs_unpack_started) {
+ /*
+ * Something before rootfs_initcall wants to access
+ * the filesystem/initramfs. Probably a bug. Make a
+ * note, avoid deadlocking the machine, and let the
+ * caller's access fail as it used to.
+ */
+ pr_warn_once("wait_for_initramfs() called by %s"
+ " before rootfs_initcalls\n", caller);
+ return;
+ }
+
+ start = jiffies;
+ wait_for_completion(&initramfs_done);
+ pr_info("%s() waited %lu jiffies for initramfs_done\n",
+ caller, jiffies - start);
+}
+EXPORT_SYMBOL_GPL(_wait_for_initramfs);
+
extern char __initramfs_start[];
extern unsigned long __initramfs_size;
#include <linux/initrd.h>@@ -602,7 +639,7 @@ static void __init populate_initrd_image(char *err)
}
#endif /* CONFIG_BLK_DEV_RAM */
-static int __init populate_rootfs(void)
+static void __init do_populate_rootfs(struct work_struct *w)
{
/* Load the built in initramfs */
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);@@ -637,6 +674,18 @@ static int __init populate_rootfs(void)
initrd_end = 0;
flush_delayed_fput();
+ complete_all(&initramfs_done);
+}
+
+static int __init populate_rootfs(void)
+{
+ initramfs_unpack_started = true;
+ if (initramfs_async) {
+ INIT_WORK(&initramfs_wrk, do_populate_rootfs);
+ schedule_work(&initramfs_wrk);
+ } else {
+ do_populate_rootfs(NULL);
+ }
return 0;
}
rootfs_initcall(populate_rootfs);diff --git a/init/main.c b/init/main.c
index a626e78dbf06..fecdede7b85c 100644
--- a/init/main.c
+++ b/init/main.c
@@ -1534,6 +1534,7 @@ static noinline void __init kernel_init_freeable(void)
kunit_run_all_tests();
+ wait_for_initramfs();
console_on_rootfs();
/*
diff --git a/kernel/umh.c b/kernel/umh.c
index 3f646613a9d3..61f6b82c354b 100644
--- a/kernel/umh.c
+++ b/kernel/umh.c
@@ -27,6 +27,7 @@
#include <linux/ptrace.h>
#include <linux/async.h>
#include <linux/uaccess.h>
+#include <linux/initrd.h>
#include <trace/events/module.h>
@@ -107,6 +108,7 @@ static int call_usermodehelper_exec_async(void *data)
commit_creds(new);
+ wait_for_initramfs();
retval = kernel_execve(sub_info->path,
(const char *const *)sub_info->argv,
(const char *const *)sub_info->envp);
diff --git a/usr/Kconfig b/usr/Kconfig
index 2599bc21c1b2..56bb250458e4 100644
--- a/usr/Kconfig
+++ b/usr/Kconfig
@@ -32,6 +32,16 @@ config INITRAMFS_FORCE
and is useful if you cannot or don't want to change the image
your bootloader passes to the kernel.
+config INITRAMFS_ASYNC
+ bool "Unpack initramfs asynchronously"
+ help
+ This option sets the default value of the initramfs_async=
+ command line parameter. If that parameter is set, unpacking
+ of initramfs (both the builtin and one passed from a
+ bootloader) is done asynchronously. See
+ <file:Documentation/admin-guide/kernel-parameters.txt> for
+ details.
+
config INITRAMFS_ROOT_UID
int "User ID to map to 0 (user root)"
depends on INITRAMFS_SOURCE!=""
--
2.29.2