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:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user