]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ACPI: scan: Use async schedule function in acpi_scan_clear_dep_fn()
authorYicong Yang <yang.yicong@picoheart.com>
Wed, 28 Jan 2026 13:28:47 +0000 (21:28 +0800)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Wed, 28 Jan 2026 21:03:24 +0000 (22:03 +0100)
The device object rescan in acpi_scan_clear_dep_fn() is scheduled on a
system workqueue which is not guaranteed to be finished before entering
userspace. This may cause some key devices to be missing when userspace
init task tries to find them. Two issues observed on RISCV platforms:

 - Kernel panic due to userspace init cannot have an opened
   console.

   The console device scanning is queued by acpi_scan_clear_dep_queue()
   and not finished by the time userspace init process running, thus by
   the time userspace init runs, no console is present.

 - Entering rescue shell due to the lack of root devices (PCIe nvme in
   our case).

   Same reason as above, the PCIe host bridge scanning is queued on
   a system workqueue and finished after init process runs.

The reason is because both devices (console, PCIe host bridge) depend on
riscv-aplic irqchip to serve their interrupts (console's wired interrupt
and PCI's INTx interrupts). In order to keep the dependency, these
devices are scanned and created after initializing riscv-aplic. The
riscv-aplic is initialized in device_initcall() and a device scan work
is queued via acpi_scan_clear_dep_queue(), which is close to the time
userspace init process is run. Since system_dfl_wq is used in
acpi_scan_clear_dep_queue() with no synchronization, the issues will
happen if userspace init runs before these devices are ready.

The solution is to wait for the queued work to complete before entering
userspace init. One possible way would be to use a dedicated workqueue
instead of system_dfl_wq, and explicitly flush it somewhere in the
initcall stage before entering userspace. Another way is to use
async_schedule_dev_nocall() for scanning these devices. It's designed
for asynchronous initialization and will work in the same way as before
because it's using a dedicated unbound workqueue as well, but the kernel
init code calls async_synchronize_full() right before entering userspace
init which will wait for the work to complete.

Compared to a dedicated workqueue, the second approach is simpler
because the async schedule framework takes care of all of the details.
The ACPI code only needs to focus on its job. A dedicated workqueue for
this could also be redundant because some platforms don't need
acpi_scan_clear_dep_queue() for their device scanning.

Signed-off-by: Yicong Yang <yang.yicong@picoheart.com>
[ rjw: Subject adjustment, changelog edits ]
Link: https://patch.msgid.link/20260128132848.93638-1-yang.yicong@picoheart.com
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/acpi/scan.c

index 26ea73abe39ff3435ee444a6500a02f7853809c7..fc40dcfa5ffca0639bca0980936499ba39b536e4 100644 (file)
@@ -5,6 +5,7 @@
 
 #define pr_fmt(fmt) "ACPI: " fmt
 
+#include <linux/async.h>
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/slab.h>
@@ -2388,46 +2389,34 @@ static int acpi_dev_get_next_consumer_dev_cb(struct acpi_dep_data *dep, void *da
        return 0;
 }
 
-struct acpi_scan_clear_dep_work {
-       struct work_struct work;
-       struct acpi_device *adev;
-};
-
-static void acpi_scan_clear_dep_fn(struct work_struct *work)
+static void acpi_scan_clear_dep_fn(void *dev, async_cookie_t cookie)
 {
-       struct acpi_scan_clear_dep_work *cdw;
-
-       cdw = container_of(work, struct acpi_scan_clear_dep_work, work);
+       struct acpi_device *adev = to_acpi_device(dev);
 
        acpi_scan_lock_acquire();
-       acpi_bus_attach(cdw->adev, (void *)true);
+       acpi_bus_attach(adev, (void *)true);
        acpi_scan_lock_release();
 
-       acpi_dev_put(cdw->adev);
-       kfree(cdw);
+       acpi_dev_put(adev);
 }
 
 static bool acpi_scan_clear_dep_queue(struct acpi_device *adev)
 {
-       struct acpi_scan_clear_dep_work *cdw;
-
        if (adev->dep_unmet)
                return false;
 
-       cdw = kmalloc(sizeof(*cdw), GFP_KERNEL);
-       if (!cdw)
-               return false;
-
-       cdw->adev = adev;
-       INIT_WORK(&cdw->work, acpi_scan_clear_dep_fn);
        /*
-        * Since the work function may block on the lock until the entire
-        * initial enumeration of devices is complete, put it into the unbound
-        * workqueue.
+        * Async schedule the deferred acpi_scan_clear_dep_fn() since:
+        * - acpi_bus_attach() needs to hold acpi_scan_lock which cannot
+        *   be acquired under acpi_dep_list_lock (held here)
+        * - the deferred work at boot stage is ensured to be finished
+        *   before userspace init task by the async_synchronize_full()
+        *   barrier
+        *
+        * Use _nocall variant since it'll return on failure instead of
+        * run the function synchronously.
         */
-       queue_work(system_dfl_wq, &cdw->work);
-
-       return true;
+       return async_schedule_dev_nocall(acpi_scan_clear_dep_fn, &adev->dev);
 }
 
 static void acpi_scan_delete_dep_data(struct acpi_dep_data *dep)