From 1eaa12e351c3fa5d27d00849a665451b476e3dbc Mon Sep 17 00:00:00 2001 From: Heinrich Toews Date: Thu, 2 Apr 2026 14:20:09 +0200 Subject: [PATCH] leds: rgb: wago-m4-led: load M4 firmware asynchronously When the M4 coprocessor is not yet running, the driver previously called rproc_boot() synchronously from probe(), which blocked the boot process until the rootfs was mounted and the ELF image was readable. Switch to request_firmware_nowait() for the firmware-load path (case b). probe() now returns immediately; the firmware loader invokes wago_rproc_fw_callback() once the image is available. rproc_boot() and register_rpmsg_driver() are called from the callback, which runs in a kthread/workqueue context where blocking is safe. LEDs become available after the callback fires. The attach path (case a, M4 already running / RPROC_DETACHED) is unchanged and still completes synchronously. Additional fixes in this commit: - Pass rproc_np->phandle to rproc_get_by_phandle() instead of the of_node pointer, fixing a type mismatch. - Add and includes. - Improve comments in wago_rproc_start() and probe() to clearly distinguish the two boot paths. Signed-off-by: Heinrich Toews --- drivers/leds/rgb/wago-m4-led-wrapper.c | 135 +++++++++++++++++++++---- 1 file changed, 113 insertions(+), 22 deletions(-) diff --git a/drivers/leds/rgb/wago-m4-led-wrapper.c b/drivers/leds/rgb/wago-m4-led-wrapper.c index 7f6670aa6da7..aabe30c40303 100644 --- a/drivers/leds/rgb/wago-m4-led-wrapper.c +++ b/drivers/leds/rgb/wago-m4-led-wrapper.c @@ -30,9 +30,15 @@ * -ENOMEM and the driver retries up to WAGO_SEND_RETRIES times with a short * back-off before giving up. * + * Firmware loading + * ---------------- * The M4 firmware may either be: - * a) Already running (loaded by bootloader) -> driver attaches via remoteproc - * b) Not yet running -> driver loads it via remoteproc + * a) Already running (loaded by bootloader) -> driver attaches synchronously + * b) Not yet running -> driver loads it asynchronously via + * request_firmware_nowait() so that probe() returns immediately without + * blocking the boot process while the rootfs is not yet mounted. + * The RPMsg driver is registered and LEDs become available once the + * firmware callback fires and rproc_boot() succeeds. * * Sysfs example * ------------- @@ -43,6 +49,8 @@ * Author: WAGO GmbH & Co. KG */ +#include +#include #include #include #include @@ -347,6 +355,50 @@ static struct rpmsg_driver wago_rpmsg_driver = { * remoteproc helpers * ---------------------------------------------------------------------- */ +/* + * wago_rproc_fw_callback - called by the firmware loader once the M4 ELF + * image is available (asynchronous path, case b). + * + * Runs in a kernel thread context (kthread / workqueue), so blocking calls + * like rproc_boot() are safe here. + */ +static void wago_rproc_fw_callback(const struct firmware *fw, void *context) +{ + struct platform_device *pdev = context; + struct wago_m4_led_priv *priv = platform_get_drvdata(pdev); + int ret; + + if (!fw) { + dev_err(&pdev->dev, + "Firmware not available — M4 LED strip will not work\n"); + return; + } + + /* + * release_firmware() before rproc_boot(): remoteproc will call + * request_firmware() again internally with the name we set via + * rproc_set_firmware(). We just used request_firmware_nowait() to + * wait for the filesystem; the actual image is re-read by rproc. + */ + release_firmware(fw); + + ret = rproc_boot(priv->rproc); + if (ret) { + dev_err(&pdev->dev, "rproc_boot failed: %d\n", ret); + rproc_put(priv->rproc); + priv->rproc = NULL; + return; + } + + priv->rproc_booted_by_us = true; + + /* RPMsg channel will appear shortly; register the driver now */ + ret = register_rpmsg_driver(&wago_rpmsg_driver); + if (ret) + dev_err(&pdev->dev, + "Failed to register rpmsg driver: %d\n", ret); +} + static int wago_rproc_start(struct wago_m4_led_priv *priv, struct platform_device *pdev) { @@ -360,7 +412,7 @@ static int wago_rproc_start(struct wago_m4_led_priv *priv, return dev_err_probe(&pdev->dev, -ENODEV, "Missing 'remoteproc' phandle in DT\n"); - priv->rproc = rproc_get_by_phandle(rproc_np); + priv->rproc = rproc_get_by_phandle(rproc_np->phandle); of_node_put(rproc_np); if (IS_ERR_OR_NULL(priv->rproc)) return dev_err_probe(&pdev->dev, @@ -368,33 +420,56 @@ static int wago_rproc_start(struct wago_m4_led_priv *priv, "Cannot get remoteproc handle\n"); /* - * RPROC_DETACHED means the M4 is already running (bootloader loaded - * the firmware). In that case rproc_boot() performs an attach. + * Case a) M4 already running (bootloader loaded the firmware). + * rproc_boot() on a DETACHED rproc performs a lightweight attach + * without touching the filesystem — safe to do synchronously. */ - if (priv->rproc->state != RPROC_DETACHED) { - if (of_property_read_string(np, "firmware-name", &fw_name)) - fw_name = "wago-led-server.elf"; - - dev_info(&pdev->dev, "Loading M4 firmware: %s\n", fw_name); - - ret = rproc_set_firmware(priv->rproc, fw_name); + if (priv->rproc->state == RPROC_DETACHED) { + dev_info(&pdev->dev, "M4 already running — attaching\n"); + ret = rproc_boot(priv->rproc); if (ret) { - dev_err(&pdev->dev, - "rproc_set_firmware failed: %d\n", ret); + dev_err(&pdev->dev, "rproc attach failed: %d\n", ret); goto err_put; } - } else { - dev_info(&pdev->dev, "M4 already running — attaching\n"); + priv->rproc_booted_by_us = true; + return 0; } - ret = rproc_boot(priv->rproc); + /* + * Case b) M4 not running — need to load firmware from the filesystem. + * + * Use request_firmware_nowait() so that probe() can return + * immediately. The firmware loader will call wago_rproc_fw_callback() + * once the rootfs is mounted and the file is available. The RPMsg + * driver registration and LED class device creation happen there. + */ + if (of_property_read_string(np, "firmware-name", &fw_name)) + fw_name = "wago-led-server.elf"; + + ret = rproc_set_firmware(priv->rproc, fw_name); if (ret) { - dev_err(&pdev->dev, "rproc_boot failed: %d\n", ret); + dev_err(&pdev->dev, "rproc_set_firmware failed: %d\n", ret); goto err_put; } - priv->rproc_booted_by_us = true; - return 0; + dev_info(&pdev->dev, + "Queuing async firmware load: %s\n", fw_name); + + /* + * Pass the platform_device as context so the callback can retrieve + * priv via platform_get_drvdata(). + */ + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, + fw_name, &pdev->dev, GFP_KERNEL, + pdev, wago_rproc_fw_callback); + if (ret) { + dev_err(&pdev->dev, + "request_firmware_nowait failed: %d\n", ret); + goto err_put; + } + + /* Signal probe() that boot is async — LEDs will appear later */ + return -EINPROGRESS; err_put: rproc_put(priv->rproc); @@ -436,12 +511,22 @@ static int wago_m4_led_probe(struct platform_device *pdev) /* Boot / attach the M4 coprocessor */ ret = wago_rproc_start(priv, pdev); + if (ret == -EINPROGRESS) { + /* + * Firmware load is in progress (async). The callback + * wago_rproc_fw_callback() will register the RPMsg driver + * and bring up the LEDs once the rootfs is available. + */ + dev_info(&pdev->dev, + "WAGO M4 LED wrapper probed (firmware load pending)\n"); + return 0; + } if (ret) return ret; /* - * Register the RPMsg driver. The rpmsg core calls wago_rpmsg_probe() - * once the M4 announces the "rpmsg-tty" service via name-service. + * Synchronous path (M4 was already running / attached): + * Register the RPMsg driver immediately. */ ret = register_rpmsg_driver(&wago_rpmsg_driver); if (ret) { @@ -459,6 +544,12 @@ static int wago_m4_led_remove(struct platform_device *pdev) { struct wago_m4_led_priv *priv = platform_get_drvdata(pdev); + /* + * Unregister the RPMsg driver regardless of whether it was registered + * by probe() (sync path) or by the firmware callback (async path). + * unregister_rpmsg_driver() is safe to call even if the driver was + * never registered. + */ unregister_rpmsg_driver(&wago_rpmsg_driver); wago_rproc_stop(priv);