[PATCH v22 7/7] remoteproc: stm32: add OP-TEE backend support

Arnaud Pouliquen
2026-04-14T15:29:43Z

Integrate OP-TEE-controlled firmware loading into stm32_rproc
adding support for the st,rproc-tee devicetree property.

When st,rproc-tee is present, the driver:
- parses the backend phandle + ID from DT and validates it
- uses the remoteproc_tee operation set and registration path
- keeps reset/holdboot handling only for the non-TEE case
- uses a TEE-specific flow.

Notice that the attach/detach is not yet supported and should be part
of a separate patchset.

Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>
---
V21 updates:
- reapply the V19 patch as the stm32_rproc-tee driver can no more
be used.
- update v19 patch to support the st,rproc-tee phandle.
- rework the commit message.
---
drivers/remoteproc/stm32_rproc.c | 167 +++++++++++++++++++++++--------
1 file changed, 123 insertions(+), 44 deletions(-)

diff --git a/drivers/remoteproc/stm32_rproc.c b/drivers/remoteproc/stm32_rproc.c
index 4bcd6a784935..61d89f3a78b1 100644
--- a/drivers/remoteproc/stm32_rproc.c
+++ b/drivers/remoteproc/stm32_rproc.c
@@ -18,6 +18,7 @@
#include <linux/pm_wakeirq.h>
#include <linux/regmap.h>
#include <linux/remoteproc.h>
+#include <linux/remoteproc_tee.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
@@ -89,6 +90,7 @@ struct stm32_rproc {
struct stm32_rproc_mem *rmems;
struct stm32_mbox mb[MBOX_NB_MBX];
struct workqueue_struct *workqueue;
+ struct of_phandle_args tee_phandle;
bool hold_boot_smc;
void __iomem *rsc_va;
};
@@ -255,6 +257,19 @@ static int stm32_rproc_release(struct rproc *rproc)
return 0;
}

+static int stm32_rproc_tee_stop(struct rproc *rproc)
+{
+ int err;
+
+ stm32_rproc_request_shutdown(rproc);
+
+ err = rproc_tee_stop(rproc);
+ if (err)
+ return err;
+
+ return stm32_rproc_release(rproc);
+}
+
static int stm32_rproc_prepare(struct rproc *rproc)
{
struct device *dev = rproc->dev.parent;
@@ -683,6 +698,17 @@ static const struct rproc_ops st_rproc_ops = {
.get_boot_addr = rproc_elf_get_boot_addr,
};

+static const struct rproc_ops st_rproc_tee_ops = {
+ .prepare = stm32_rproc_prepare,
+ .start = rproc_tee_start,
+ .stop = stm32_rproc_tee_stop,
+ .kick = stm32_rproc_kick,
+ .load = rproc_tee_load_fw,
+ .parse_fw = rproc_tee_parse_fw,
+ .find_loaded_rsc_table = rproc_tee_find_loaded_rsc_table,
+ .release_fw = rproc_tee_release_fw,
+};
+
static const struct of_device_id stm32_rproc_match[] = {
{ .compatible = "st,stm32mp1-m4" },
{},
@@ -716,6 +742,7 @@ static int stm32_rproc_parse_dt(struct platform_device *pdev,
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
+ struct of_phandle_args tee_phandle;
struct stm32_syscon tz;
unsigned int tzen;
int err, irq;
@@ -741,51 +768,69 @@ static int stm32_rproc_parse_dt(struct platform_device *pdev,
dev_info(dev, "wdg irq registered\n");
}

- ddata->rst = devm_reset_control_get_optional(dev, "mcu_rst");
- if (!ddata->rst) {
- /* Try legacy fallback method: get it by index */
- ddata->rst = devm_reset_control_get_by_index(dev, 0);
+ if (of_find_property(np, "st,rproc-tee", NULL)) {
+ err = of_parse_phandle_with_fixed_args(np, "st,rproc-tee",
+ 1, 0, &tee_phandle);
+ if (err)
+ return dev_err_probe(dev, err,
+ "failed to parse st,rproc-tee\n");
+
+ if (!IS_ENABLED(CONFIG_REMOTEPROC_TEE)) {
+ of_node_put(tee_phandle.np);
+ return dev_err_probe(dev, -ENODEV,
+ "st,rproc-tee requires REMOTEPROC_TEE support\n");
+ }
+
+ ddata->tee_phandle = tee_phandle;
}
- if (IS_ERR(ddata->rst))
- return dev_err_probe(dev, PTR_ERR(ddata->rst),
- "failed to get mcu_reset\n");

- /*
- * Three ways to manage the hold boot
- * - using SCMI: the hold boot is managed as a reset
- * The DT "reset-mames" property should be defined with 2 items:
- * reset-names = "mcu_rst", "hold_boot";
- * - using SMC call (deprecated): use SMC reset interface
- * The DT "reset-mames" property is optional, "st,syscfg-tz" is required
- * - default(no SCMI, no SMC): the hold boot is managed as a syscon register
- * The DT "reset-mames" property is optional, "st,syscfg-holdboot" is required
- */
+ if (!ddata->tee_phandle.np) {
+ ddata->rst = devm_reset_control_get_optional(dev, "mcu_rst");
+ if (!ddata->rst) {
+ /* Try legacy fallback method: get it by index */
+ ddata->rst = devm_reset_control_get_by_index(dev, 0);
+ }
+ if (IS_ERR(ddata->rst))
+ return dev_err_probe(dev, PTR_ERR(ddata->rst),
+ "failed to get mcu_reset\n");

- ddata->hold_boot_rst = devm_reset_control_get_optional(dev, "hold_boot");
- if (IS_ERR(ddata->hold_boot_rst))
- return dev_err_probe(dev, PTR_ERR(ddata->hold_boot_rst),
- "failed to get hold_boot reset\n");
+ /*
+ * Three ways to manage the hold boot
+ * - using SCMI: the hold boot is managed as a reset
+ * The DT "reset-mames" property should be defined with 2 items:
+ * reset-names = "mcu_rst", "hold_boot";
+ * - using SMC call (deprecated): use SMC reset interface
+ * The DT "reset-mames" property is optional, "st,syscfg-tz" is required
+ * - default(no SCMI, no SMC): the hold boot is managed as a syscon register
+ * The DT "reset-mames" property is optional, "st,syscfg-holdboot" is required
+ */

- if (!ddata->hold_boot_rst && IS_ENABLED(CONFIG_HAVE_ARM_SMCCC)) {
- /* Manage the MCU_BOOT using SMC call */
- err = stm32_rproc_get_syscon(np, "st,syscfg-tz", &tz);
- if (!err) {
- err = regmap_read(tz.map, tz.reg, &tzen);
- if (err) {
- dev_err(dev, "failed to read tzen\n");
- return err;
+ ddata->hold_boot_rst = devm_reset_control_get_optional(dev, "hold_boot");
+ if (IS_ERR(ddata->hold_boot_rst))
+ return dev_err_probe(dev, PTR_ERR(ddata->hold_boot_rst),
+ "failed to get hold_boot reset\n");
+
+ if (!ddata->hold_boot_rst && IS_ENABLED(CONFIG_HAVE_ARM_SMCCC)) {
+ /* Manage the MCU_BOOT using SMC call */
+ err = stm32_rproc_get_syscon(np, "st,syscfg-tz", &tz);
+ if (!err) {
+ err = regmap_read(tz.map, tz.reg, &tzen);
+ if (err) {
+ dev_err(dev, "failed to read tzen\n");
+ return err;
+ }
+ ddata->hold_boot_smc = tzen & tz.mask;
}
- ddata->hold_boot_smc = tzen & tz.mask;
}
- }

- if (!ddata->hold_boot_rst && !ddata->hold_boot_smc) {
- /* Default: hold boot manage it through the syscon controller */
- err = stm32_rproc_get_syscon(np, "st,syscfg-holdboot",
- &ddata->hold_boot);
- if (err) {
- dev_err(dev, "failed to get hold boot\n");
- return err;
+ if (!ddata->hold_boot_rst && !ddata->hold_boot_smc) {
+ /* Default: hold boot manage it through the syscon controller */
+ err = stm32_rproc_get_syscon(np, "st,syscfg-holdboot",
+ &ddata->hold_boot);
+ if (err) {
+ dev_err(dev, "failed to get hold boot\n");
+ return err;
+ }
}
}

@@ -857,18 +902,31 @@ static int stm32_rproc_probe(struct platform_device *pdev)
if (ret < 0 && ret != -EINVAL)
return ret;

- rproc = devm_rproc_alloc(dev, np->name, &st_rproc_ops, fw_name, sizeof(*ddata));
+ if (of_find_property(np, "st,rproc-tee", NULL))
+ rproc = devm_rproc_alloc(dev, np->name, &st_rproc_tee_ops, fw_name,
+ sizeof(*ddata));
+ else
+ rproc = devm_rproc_alloc(dev, np->name, &st_rproc_ops, fw_name,
+ sizeof(*ddata));
+
if (!rproc)
return -ENOMEM;

ddata = rproc->priv;
-
rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE);

ret = stm32_rproc_parse_dt(pdev, ddata, &rproc->auto_boot);
if (ret)
goto free_rproc;

+ if (ddata->tee_phandle.np &&
+ !of_device_is_available(ddata->tee_phandle.np)) {
+ of_node_put(ddata->tee_phandle.np);
+ ddata->tee_phandle.np = NULL;
+ ret = -EPROBE_DEFER;
+ goto free_rproc;
+ }
+
ret = stm32_rproc_of_memory_translations(pdev, ddata);
if (ret)
goto free_rproc;
@@ -877,8 +935,14 @@ static int stm32_rproc_probe(struct platform_device *pdev)
if (ret)
goto free_rproc;

- if (state == M4_STATE_CRUN)
+ if (state == M4_STATE_CRUN) {
+ if (ddata->tee_phandle.np) {
+ dev_err(dev, "TEE support not yet compatible with attached state\n");
+ ret = -EINVAL;
+ goto free_rproc;
+ }
rproc->state = RPROC_DETACHED;
+ }

rproc->has_iommu = false;
ddata->workqueue = create_workqueue(dev_name(dev));
@@ -894,10 +958,12 @@ static int stm32_rproc_probe(struct platform_device *pdev)
if (ret)
goto free_wkq;

- ret = rproc_add(rproc);
+ if (ddata->tee_phandle.np)
+ ret = rproc_tee_register(dev, rproc, &ddata->tee_phandle);
+ else
+ ret = rproc_add(rproc);
if (ret)
goto free_mb;
-
return 0;

free_mb:
@@ -911,6 +977,11 @@ static int stm32_rproc_probe(struct platform_device *pdev)
dev_pm_clear_wake_irq(dev);
device_init_wakeup(dev, false);
}
+ if (ddata->tee_phandle.np) {
+ of_node_put(ddata->tee_phandle.np);
+ ddata->tee_phandle.np = NULL;
+ }
+
return ret;
}

@@ -919,11 +990,11 @@ static void stm32_rproc_remove(struct platform_device *pdev)
struct rproc *rproc = platform_get_drvdata(pdev);
struct stm32_rproc *ddata = rproc->priv;
struct device *dev = &pdev->dev;
+ struct device_node *tee_np = ddata->tee_phandle.np;

if (atomic_read(&rproc->power) > 0)
rproc_shutdown(rproc);

- rproc_del(rproc);
stm32_rproc_free_mbox(rproc);
destroy_workqueue(ddata->workqueue);

@@ -931,6 +1002,14 @@ static void stm32_rproc_remove(struct platform_device *pdev)
dev_pm_clear_wake_irq(dev);
device_init_wakeup(dev, false);
}
+
+ if (tee_np) {
+ ddata->tee_phandle.np = NULL;
+ rproc_tee_unregister(dev, rproc);
+ of_node_put(tee_np);
+ } else {
+ rproc_del(rproc);
+ }
}

static int stm32_rproc_suspend(struct device *dev)
--
2.43.0