At the moment, the remoteproc core supports only one auto boot "strategy": A remoteproc that is already running during boot ("detached") is attached, a remoteproc that is offline is started after loading the firmware. This works if the firmware loaded during boot is the same that we would start later, but it could also be outdated or a reduced size version that is missing some functionality. In this case, the best option is to try restarting it with new firmware - assuming that it is available while booting. Add support for this alternative behavior by replacing the "auto_boot" bool with a more explicit enum rproc_auto_boot. For RPROC_AUTO_BOOT_RESTART_IF_FW_AVAILABLE, try requesting the firmware early and - if successful - perform a clean stop of the remoteproc so that it can be restarted with the new firmware afterwards. A remoteproc driver making use of this functionality must handle the stop() callback being called during the initial detached state. Signed-off-by: Stephan Gerhold Signed-off-by: Abel Vesa --- drivers/remoteproc/imx_rproc.c | 8 +++-- drivers/remoteproc/ingenic_rproc.c | 5 ++- drivers/remoteproc/pru_rproc.c | 2 +- drivers/remoteproc/qcom_q6v5_adsp.c | 5 ++- drivers/remoteproc/qcom_q6v5_mss.c | 2 +- drivers/remoteproc/qcom_q6v5_pas.c | 5 ++- drivers/remoteproc/rcar_rproc.c | 2 +- drivers/remoteproc/remoteproc_core.c | 47 ++++++++++++++++++------- drivers/remoteproc/stm32_rproc.c | 8 +++-- drivers/remoteproc/wkup_m3_rproc.c | 2 +- drivers/remoteproc/xlnx_r5_remoteproc.c | 2 +- include/linux/remoteproc.h | 23 +++++++++++- 12 files changed, 85 insertions(+), 26 deletions(-) diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c index 0dd80e688b0ea3..d5758207d155b6 100644 --- a/drivers/remoteproc/imx_rproc.c +++ b/drivers/remoteproc/imx_rproc.c @@ -1288,8 +1288,12 @@ static int imx_rproc_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to enable clock\n"); } - if (rproc->state != RPROC_DETACHED) - rproc->auto_boot = of_property_read_bool(np, "fsl,auto-boot"); + if (rproc->state != RPROC_DETACHED) { + if (of_property_read_bool(np, "fsl,auto-boot")) + rproc->auto_boot = RPROC_AUTO_BOOT_ATTACH_OR_START; + else + rproc->auto_boot = RPROC_AUTO_BOOT_DISABLED; + } if (dcfg->flags & IMX_RPROC_NEED_SYSTEM_OFF) { /* diff --git a/drivers/remoteproc/ingenic_rproc.c b/drivers/remoteproc/ingenic_rproc.c index 1b78d8ddeacfd5..351b9529713790 100644 --- a/drivers/remoteproc/ingenic_rproc.c +++ b/drivers/remoteproc/ingenic_rproc.c @@ -177,7 +177,10 @@ static int ingenic_rproc_probe(struct platform_device *pdev) if (!rproc) return -ENOMEM; - rproc->auto_boot = auto_boot; + if (auto_boot) + rproc->auto_boot = RPROC_AUTO_BOOT_ATTACH_OR_START; + else + rproc->auto_boot = RPROC_AUTO_BOOT_DISABLED; vpu = rproc->priv; vpu->dev = &pdev->dev; diff --git a/drivers/remoteproc/pru_rproc.c b/drivers/remoteproc/pru_rproc.c index a4636c7bc6b7be..0928a75916f352 100644 --- a/drivers/remoteproc/pru_rproc.c +++ b/drivers/remoteproc/pru_rproc.c @@ -1029,7 +1029,7 @@ static int pru_rproc_probe(struct platform_device *pdev) * remote-processor as part of its state machine either through the * remoteproc sysfs interface or through the equivalent kernel API. */ - rproc->auto_boot = false; + rproc->auto_boot = RPROC_AUTO_BOOT_DISABLED; pru = rproc->priv; pru->dev = dev; diff --git a/drivers/remoteproc/qcom_q6v5_adsp.c b/drivers/remoteproc/qcom_q6v5_adsp.c index b5c8d6d38c9cbc..7184f73be69105 100644 --- a/drivers/remoteproc/qcom_q6v5_adsp.c +++ b/drivers/remoteproc/qcom_q6v5_adsp.c @@ -673,7 +673,10 @@ static int adsp_probe(struct platform_device *pdev) return -ENOMEM; } - rproc->auto_boot = desc->auto_boot; + if (desc->auto_boot) + rproc->auto_boot = RPROC_AUTO_BOOT_ATTACH_OR_START; + else + rproc->auto_boot = RPROC_AUTO_BOOT_DISABLED; rproc->has_iommu = desc->has_iommu; rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE); diff --git a/drivers/remoteproc/qcom_q6v5_mss.c b/drivers/remoteproc/qcom_q6v5_mss.c index ae78f5c7c1b69e..606896500ec310 100644 --- a/drivers/remoteproc/qcom_q6v5_mss.c +++ b/drivers/remoteproc/qcom_q6v5_mss.c @@ -2095,7 +2095,7 @@ static int q6v5_probe(struct platform_device *pdev) return -ENOMEM; } - rproc->auto_boot = false; + rproc->auto_boot = RPROC_AUTO_BOOT_DISABLED; rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE); qproc = rproc->priv; diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index da27d1d3c9da64..1327e187514821 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -774,7 +774,10 @@ static int qcom_pas_probe(struct platform_device *pdev) } rproc->has_iommu = of_property_present(pdev->dev.of_node, "iommus"); - rproc->auto_boot = desc->auto_boot; + if (desc->auto_boot) + rproc->auto_boot = RPROC_AUTO_BOOT_ATTACH_OR_START; + else + rproc->auto_boot = RPROC_AUTO_BOOT_DISABLED; rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE); pas = rproc->priv; diff --git a/drivers/remoteproc/rcar_rproc.c b/drivers/remoteproc/rcar_rproc.c index 3c25625f966dcb..99a26a05165883 100644 --- a/drivers/remoteproc/rcar_rproc.c +++ b/drivers/remoteproc/rcar_rproc.c @@ -172,7 +172,7 @@ static int rcar_rproc_probe(struct platform_device *pdev) dev_set_drvdata(dev, rproc); /* Manually start the rproc */ - rproc->auto_boot = false; + rproc->auto_boot = RPROC_AUTO_BOOT_DISABLED; ret = devm_rproc_add(dev, rproc); if (ret) { diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index b087ed21858a8c..ec75659957033b 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -1683,7 +1683,8 @@ static int rproc_trigger_auto_boot(struct rproc *rproc) { int ret; - if (rproc->state == RPROC_DETACHED) { + if (rproc->state == RPROC_DETACHED && + rproc->auto_boot != RPROC_AUTO_BOOT_RESTART_IF_FW_AVAILABLE) { schedule_work(&rproc->attach_work); return 0; } @@ -1708,8 +1709,9 @@ static int rproc_stop(struct rproc *rproc, bool crashed) if (!rproc->ops->stop) return -EINVAL; - /* Stop any subdevices for the remote processor */ - rproc_stop_subdevices(rproc, crashed); + /* Stop any subdevices for the remote processor if it was attached */ + if (rproc->state != RPROC_DETACHED) + rproc_stop_subdevices(rproc, crashed); /* the installed resource table is no longer accessible */ ret = rproc_reset_rsc_table_on_stop(rproc); @@ -1726,7 +1728,8 @@ static int rproc_stop(struct rproc *rproc, bool crashed) return ret; } - rproc_unprepare_subdevices(rproc); + if (rproc->state != RPROC_DETACHED) + rproc_unprepare_subdevices(rproc); rproc->state = RPROC_OFFLINE; @@ -1903,9 +1906,9 @@ static void rproc_crash_handler_work(struct work_struct *work) */ int rproc_boot(struct rproc *rproc) { - const struct firmware *firmware_p; + const struct firmware *firmware_p = NULL; struct device *dev; - int ret; + int ret, fw_ret = 1; if (!rproc) { pr_err("invalid rproc handle\n"); @@ -1932,6 +1935,19 @@ int rproc_boot(struct rproc *rproc) goto unlock_mutex; } + /* Check early if we have firmware avilable if needed */ + if (rproc->auto_boot == RPROC_AUTO_BOOT_RESTART_IF_FW_AVAILABLE && + rproc->state == RPROC_DETACHED) { + fw_ret = request_firmware(&firmware_p, rproc->firmware, dev); + if (fw_ret == 0) { + dev_info(dev, "restarting %s with new firmware\n", rproc->name); + + ret = rproc_stop(rproc, false); + if (ret) + goto downref_rproc; + } + } + if (rproc->state == RPROC_DETACHED) { dev_info(dev, "attaching to %s\n", rproc->name); @@ -1939,19 +1955,20 @@ int rproc_boot(struct rproc *rproc) } else { dev_info(dev, "powering up %s\n", rproc->name); - /* load firmware */ - ret = request_firmware(&firmware_p, rproc->firmware, dev); - if (ret < 0) { - dev_err(dev, "request_firmware failed: %d\n", ret); + /* load firmware (if not already happened above) */ + if (fw_ret == 1) + fw_ret = request_firmware(&firmware_p, rproc->firmware, dev); + if (fw_ret < 0) { + dev_err(dev, "request_firmware failed: %d\n", fw_ret); + ret = fw_ret; goto downref_rproc; } ret = rproc_fw_boot(rproc, firmware_p); - - release_firmware(firmware_p); } downref_rproc: + release_firmware(firmware_p); if (ret) atomic_dec(&rproc->power); unlock_mutex: @@ -2259,6 +2276,10 @@ static int rproc_validate(struct rproc *rproc) return -EINVAL; } + if (rproc->auto_boot == RPROC_AUTO_BOOT_RESTART_IF_FW_AVAILABLE && + (!rproc->ops->stop || !rproc->ops->start || !rproc->ops->attach)) + return -EINVAL; + return 0; } @@ -2470,7 +2491,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, return NULL; rproc->priv = &rproc[1]; - rproc->auto_boot = true; + rproc->auto_boot = RPROC_AUTO_BOOT_ATTACH_OR_START; rproc->elf_class = ELFCLASSNONE; rproc->elf_machine = EM_NONE; diff --git a/drivers/remoteproc/stm32_rproc.c b/drivers/remoteproc/stm32_rproc.c index 632614013dc652..891fc17f8a63eb 100644 --- a/drivers/remoteproc/stm32_rproc.c +++ b/drivers/remoteproc/stm32_rproc.c @@ -696,7 +696,8 @@ static int stm32_rproc_get_syscon(struct device_node *np, const char *prop, } static int stm32_rproc_parse_dt(struct platform_device *pdev, - struct stm32_rproc *ddata, bool *auto_boot) + struct stm32_rproc *ddata, + enum rproc_auto_boot *auto_boot) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; @@ -777,7 +778,10 @@ static int stm32_rproc_parse_dt(struct platform_device *pdev, if (err) dev_info(dev, "failed to get pdds\n"); - *auto_boot = of_property_read_bool(np, "st,auto-boot"); + if (of_property_read_bool(np, "st,auto-boot")) + *auto_boot = RPROC_AUTO_BOOT_ATTACH_OR_START; + else + *auto_boot = RPROC_AUTO_BOOT_DISABLED; /* * See if we can check the M4 status, i.e if it was started diff --git a/drivers/remoteproc/wkup_m3_rproc.c b/drivers/remoteproc/wkup_m3_rproc.c index 2d5bfbefcacc5b..8815648295c892 100644 --- a/drivers/remoteproc/wkup_m3_rproc.c +++ b/drivers/remoteproc/wkup_m3_rproc.c @@ -170,7 +170,7 @@ static int wkup_m3_rproc_probe(struct platform_device *pdev) if (!rproc) return -ENOMEM; - rproc->auto_boot = false; + rproc->auto_boot = RPROC_AUTO_BOOT_DISABLED; rproc->sysfs_read_only = true; wkupm3 = rproc->priv; diff --git a/drivers/remoteproc/xlnx_r5_remoteproc.c b/drivers/remoteproc/xlnx_r5_remoteproc.c index 50a9974f3202e6..4e230838a24abd 100644 --- a/drivers/remoteproc/xlnx_r5_remoteproc.c +++ b/drivers/remoteproc/xlnx_r5_remoteproc.c @@ -931,7 +931,7 @@ static struct zynqmp_r5_core *zynqmp_r5_add_rproc_core(struct device *cdev) r5_rproc->recovery_disabled = true; r5_rproc->has_iommu = false; - r5_rproc->auto_boot = false; + r5_rproc->auto_boot = RPROC_AUTO_BOOT_DISABLED; /* attempt to boot automatically if the firmware-name is provided */ if (fw_name) diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index b4795698d8c2a4..c40b18d120ab46 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -503,6 +503,27 @@ enum rproc_features { RPROC_MAX_FEATURES, }; +/** + * enum rproc_auto_boot - auto boot strategy for remoteproc during initial boot + * + * @RPROC_AUTO_BOOT_DISABLED: The remoteproc will be left offline (or detached). + * @RPROC_AUTO_BOOT_ATTACH_OR_START: The remoteproc will be attached (if it is + * already running). Otherwise, it will be + * started with new loaded firmware. + * @RPROC_FEAT_REBOOT_IF_FW_AVAILABLE: The remoteproc will be restarted if + * requesting new firmware succeeds. If + * the firmware is missing and the + * remoteproc is already running, it will + * be attached instead. A remoteproc + * implementing this must handle stop() + * being called in detached state. + */ +enum rproc_auto_boot { + RPROC_AUTO_BOOT_DISABLED, + RPROC_AUTO_BOOT_ATTACH_OR_START, + RPROC_AUTO_BOOT_RESTART_IF_FW_AVAILABLE, +}; + /** * struct rproc - represents a physical remote processor device * @node: list node of this rproc object @@ -577,7 +598,7 @@ struct rproc { struct resource_table *cached_table; size_t table_sz; bool has_iommu; - bool auto_boot; + enum rproc_auto_boot auto_boot; bool sysfs_read_only; struct list_head dump_segments; int nb_vdev;