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 <linux/delay.h> and <linux/firmware.h> includes.
  - Improve comments in wago_rproc_start() and probe() to clearly
    distinguish the two boot paths.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
This commit is contained in:
Heinrich Toews
2026-04-02 14:20:09 +02:00
parent 8349670eff
commit 1eaa12e351
+113 -22
View File
@@ -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 <linux/delay.h>
#include <linux/firmware.h>
#include <linux/led-class-multicolor.h>
#include <linux/leds.h>
#include <linux/module.h>
@@ -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);