meta-st-stm32mp/recipes-kernel/linux/linux-stm32mp/5.10/5.10.10/0009-ARM-5.10.10-stm32mp1-r...

3131 lines
84 KiB
Diff

From 4ceafa69af22aeb262fb8d2e0e3cd845773ce0ae Mon Sep 17 00:00:00 2001
From: Romuald JEANNE <romuald.jeanne@st.com>
Date: Tue, 16 Mar 2021 09:07:25 +0100
Subject: [PATCH 09/22] ARM 5.10.10-stm32mp1-r1 MAILBOX-REMOTEPROC-RPMSG
Signed-off-by: Romuald JEANNE <romuald.jeanne@st.com>
---
Documentation/staging/remoteproc.rst | 22 +
drivers/mailbox/Kconfig | 7 +
drivers/mailbox/Makefile | 2 +
drivers/mailbox/arm-smc-mailbox.c | 166 ++++++
drivers/remoteproc/Kconfig | 28 +
drivers/remoteproc/Makefile | 3 +
drivers/remoteproc/remoteproc_core.c | 19 +-
drivers/remoteproc/rproc_srm_core.c | 303 ++++++++++
drivers/remoteproc/rproc_srm_core.h | 98 ++++
drivers/remoteproc/rproc_srm_dev.c | 744 +++++++++++++++++++++++++
drivers/remoteproc/stm32_rproc.c | 364 ++++++++----
drivers/remoteproc/tee_remoteproc.c | 380 +++++++++++++
drivers/rpmsg/Kconfig | 9 +
drivers/rpmsg/Makefile | 1 +
drivers/rpmsg/rpmsg_core.c | 19 +
drivers/rpmsg/rpmsg_internal.h | 2 +
drivers/rpmsg/rpmsg_tty.c | 342 ++++++++++++
drivers/rpmsg/virtio_rpmsg_bus.c | 11 +
include/linux/mailbox/arm-smccc-mbox.h | 20 +
include/linux/rpmsg.h | 9 +
include/linux/tee_remoteproc.h | 106 ++++
21 files changed, 2540 insertions(+), 115 deletions(-)
create mode 100644 drivers/mailbox/arm-smc-mailbox.c
create mode 100644 drivers/remoteproc/rproc_srm_core.c
create mode 100644 drivers/remoteproc/rproc_srm_core.h
create mode 100644 drivers/remoteproc/rproc_srm_dev.c
create mode 100644 drivers/remoteproc/tee_remoteproc.c
create mode 100644 drivers/rpmsg/rpmsg_tty.c
create mode 100644 include/linux/mailbox/arm-smccc-mbox.h
create mode 100644 include/linux/tee_remoteproc.h
diff --git a/Documentation/staging/remoteproc.rst b/Documentation/staging/remoteproc.rst
index 9cccd3dd6a4b..c2367e3c0b19 100644
--- a/Documentation/staging/remoteproc.rst
+++ b/Documentation/staging/remoteproc.rst
@@ -357,3 +357,25 @@ Of course, RSC_VDEV resource entries are only good enough for static
allocation of virtio devices. Dynamic allocations will also be made possible
using the rpmsg bus (similar to how we already do dynamic allocations of
rpmsg channels; read more about it in rpmsg.txt).
+
+8. System Resource Manager (SRM)
+
+Since some resources are shared (directly or not) between the processors, a
+processor cannot manage such resources without potentially impacting the other
+processors : as an example, if a processor changes the frequency of a clock, the
+frequency of another clock managed by another processor may be updated too.
+
+The System Resource Manager prevents such resource conflicts between the
+processors : it reserves and initializes the system resources of the peripherals
+assigned to a remote processor.
+
+As of today the following resources are controlled by the SRM:
+- clocks
+- regulators (power supplies)
+
+The SRM is implemented as an 'rproc_subdev' and registered to remoteproc_core.
+Unlike the virtio device (vdev), the SRM subdev is probed *before* the rproc
+boots, ensuring the availability of the resources before the remoteproc starts.
+
+The resources handled by the SRM are defined in the DeviceTree: please read
+Documentation/devicetree/bindings/remoteproc/rproc-srm.txt for details.
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index 05b1009e2820..3d388bf2d6f6 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -16,6 +16,13 @@ config ARM_MHU
The controller has 3 mailbox channels, the last of which can be
used in Secure mode only.
+config ARM_SMC_MBOX
+ tristate "Generic ARM smc mailbox"
+ depends on OF && HAVE_ARM_SMCCC
+ help
+ Generic mailbox driver which uses ARM smc calls to call into
+ firmware for triggering mailboxes.
+
config IMX_MBOX
tristate "i.MX Mailbox"
depends on ARCH_MXC || COMPILE_TEST
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index 2e06e02b2e03..24841a298f4a 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -7,6 +7,8 @@ obj-$(CONFIG_MAILBOX_TEST) += mailbox-test.o
obj-$(CONFIG_ARM_MHU) += arm_mhu.o arm_mhu_db.o
+obj-$(CONFIG_ARM_SMC_MBOX) += arm-smc-mailbox.o
+
obj-$(CONFIG_IMX_MBOX) += imx-mailbox.o
obj-$(CONFIG_ARMADA_37XX_RWTM_MBOX) += armada-37xx-rwtm-mailbox.o
diff --git a/drivers/mailbox/arm-smc-mailbox.c b/drivers/mailbox/arm-smc-mailbox.c
new file mode 100644
index 000000000000..a6ec56f41f7f
--- /dev/null
+++ b/drivers/mailbox/arm-smc-mailbox.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2016,2017 ARM Ltd.
+ * Copyright 2019 NXP
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/mailbox_controller.h>
+#include <linux/mailbox/arm-smccc-mbox.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+typedef unsigned long (smc_mbox_fn)(unsigned int, unsigned long,
+ unsigned long, unsigned long,
+ unsigned long, unsigned long,
+ unsigned long);
+
+struct arm_smc_chan_data {
+ unsigned int function_id;
+ smc_mbox_fn *invoke_smc_mbox_fn;
+};
+
+static int arm_smc_send_data(struct mbox_chan *link, void *data)
+{
+ struct arm_smc_chan_data *chan_data = link->con_priv;
+ struct arm_smccc_mbox_cmd *cmd = data;
+ unsigned long ret;
+
+ if (ARM_SMCCC_IS_64(chan_data->function_id)) {
+ ret = chan_data->invoke_smc_mbox_fn(chan_data->function_id,
+ cmd->args_smccc64[0],
+ cmd->args_smccc64[1],
+ cmd->args_smccc64[2],
+ cmd->args_smccc64[3],
+ cmd->args_smccc64[4],
+ cmd->args_smccc64[5]);
+ } else {
+ ret = chan_data->invoke_smc_mbox_fn(chan_data->function_id,
+ cmd->args_smccc32[0],
+ cmd->args_smccc32[1],
+ cmd->args_smccc32[2],
+ cmd->args_smccc32[3],
+ cmd->args_smccc32[4],
+ cmd->args_smccc32[5]);
+ }
+
+ mbox_chan_received_data(link, (void *)ret);
+
+ return 0;
+}
+
+static unsigned long __invoke_fn_hvc(unsigned int function_id,
+ unsigned long arg0, unsigned long arg1,
+ unsigned long arg2, unsigned long arg3,
+ unsigned long arg4, unsigned long arg5)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_hvc(function_id, arg0, arg1, arg2, arg3, arg4,
+ arg5, 0, &res);
+ return res.a0;
+}
+
+static unsigned long __invoke_fn_smc(unsigned int function_id,
+ unsigned long arg0, unsigned long arg1,
+ unsigned long arg2, unsigned long arg3,
+ unsigned long arg4, unsigned long arg5)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(function_id, arg0, arg1, arg2, arg3, arg4,
+ arg5, 0, &res);
+ return res.a0;
+}
+
+static const struct mbox_chan_ops arm_smc_mbox_chan_ops = {
+ .send_data = arm_smc_send_data,
+};
+
+static struct mbox_chan *
+arm_smc_mbox_of_xlate(struct mbox_controller *mbox,
+ const struct of_phandle_args *sp)
+{
+ return mbox->chans;
+}
+
+static int arm_smc_mbox_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mbox_controller *mbox;
+ struct arm_smc_chan_data *chan_data;
+ int ret;
+
+ mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
+ if (!mbox)
+ return -ENOMEM;
+
+ mbox->of_xlate = arm_smc_mbox_of_xlate;
+ mbox->num_chans = 1;
+ mbox->chans = devm_kzalloc(dev, sizeof(*mbox->chans), GFP_KERNEL);
+ if (!mbox->chans)
+ return -ENOMEM;
+
+ chan_data = devm_kzalloc(dev, sizeof(*chan_data), GFP_KERNEL);
+ if (!chan_data)
+ return -ENOMEM;
+
+ ret = of_property_read_u32(dev->of_node, "arm,func-id",
+ &chan_data->function_id);
+ if (ret)
+ return ret;
+
+ if (of_device_is_compatible(dev->of_node, "arm,smc-mbox"))
+ chan_data->invoke_smc_mbox_fn = __invoke_fn_smc;
+ else
+ chan_data->invoke_smc_mbox_fn = __invoke_fn_hvc;
+
+
+ mbox->chans->con_priv = chan_data;
+
+ mbox->txdone_poll = false;
+ mbox->txdone_irq = false;
+ mbox->ops = &arm_smc_mbox_chan_ops;
+ mbox->dev = dev;
+
+ platform_set_drvdata(pdev, mbox);
+
+ ret = devm_mbox_controller_register(dev, mbox);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "ARM SMC mailbox enabled.\n");
+
+ return ret;
+}
+
+static int arm_smc_mbox_remove(struct platform_device *pdev)
+{
+ struct mbox_controller *mbox = platform_get_drvdata(pdev);
+
+ mbox_controller_unregister(mbox);
+ return 0;
+}
+
+static const struct of_device_id arm_smc_mbox_of_match[] = {
+ { .compatible = "arm,smc-mbox", },
+ { .compatible = "arm,hvc-mbox", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, arm_smc_mbox_of_match);
+
+static struct platform_driver arm_smc_mbox_driver = {
+ .driver = {
+ .name = "arm-smc-mbox",
+ .of_match_table = arm_smc_mbox_of_match,
+ },
+ .probe = arm_smc_mbox_probe,
+ .remove = arm_smc_mbox_remove,
+};
+module_platform_driver(arm_smc_mbox_driver);
+
+MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>");
+MODULE_DESCRIPTION("Generic ARM smc mailbox driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index d99548fb5dde..cffac49af045 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -23,6 +23,25 @@ config REMOTEPROC_CDEV
It's safe to say N if you don't want to use this interface.
+config REMOTEPROC_SRM_CORE
+ tristate "Remoteproc System Resource Manager core"
+ depends on RPMSG
+ help
+ Say y here to enable the core driver of the remoteproc System Resource
+ Manager (SRM).
+ The SRM handles resources allocated to remote processors.
+ The core part is in charge of controlling the device children.
+
+config REMOTEPROC_SRM_DEV
+ tristate "Remoteproc System Resource Manager device"
+ depends on REMOTEPROC_SRM_CORE
+ help
+ Say y here to enable the device driver of the remoteproc System
+ Resource Manager (SRM).
+ The SRM handles resources allocated to remote processors.
+ The device part is in charge of reserving and initializing resources
+ for a peripheral assigned to a coprocessor.
+
config IMX_REMOTEPROC
tristate "IMX6/7 remoteproc support"
depends on ARCH_MXC
@@ -288,6 +307,15 @@ config TI_K3_R5_REMOTEPROC
It's safe to say N here if you're not interested in utilizing
a slave processor.
+
+config TEE_REMOTEPROC
+ tristate "trusted firmware Support by a Trusted Application"
+ depends on OPTEE
+ help
+ Support for trusted remote processors firmware. The firmware
+ authentication and/or decryption are managed by a trusted application.
+ This can be either built-in or a loadable module.
+
endif # REMOTEPROC
endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index da2ace4ec86c..9f2eb094c509 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -11,6 +11,9 @@ remoteproc-y += remoteproc_sysfs.o
remoteproc-y += remoteproc_virtio.o
remoteproc-y += remoteproc_elf_loader.o
obj-$(CONFIG_REMOTEPROC_CDEV) += remoteproc_cdev.o
+obj-$(CONFIG_REMOTEPROC_SRM_CORE) += rproc_srm_core.o
+obj-$(CONFIG_REMOTEPROC_SRM_DEV) += rproc_srm_dev.o
+obj-$(CONFIG_TEE_REMOTEPROC) += tee_remoteproc.o
obj-$(CONFIG_IMX_REMOTEPROC) += imx_rproc.o
obj-$(CONFIG_INGENIC_VPU_RPROC) += ingenic_rproc.o
obj-$(CONFIG_MTK_SCP) += mtk_scp.o mtk_scp_ipi.o
diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c
index dab2c0f5caf0..316acd156b0b 100644
--- a/drivers/remoteproc/remoteproc_core.c
+++ b/drivers/remoteproc/remoteproc_core.c
@@ -37,6 +37,7 @@
#include <linux/of_reserved_mem.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_ring.h>
+#include <linux/of_platform.h>
#include <asm/byteorder.h>
#include <linux/platform_device.h>
@@ -1687,20 +1688,29 @@ int rproc_trigger_recovery(struct rproc *rproc)
{
const struct firmware *firmware_p;
struct device *dev = &rproc->dev;
+ bool detached;
int ret;
ret = mutex_lock_interruptible(&rproc->lock);
if (ret)
return ret;
+ detached = rproc->autonomous && !atomic_read(&rproc->power);
+
/* State could have changed before we got the mutex */
if (rproc->state != RPROC_CRASHED)
goto unlock_mutex;
dev_err(dev, "recovering %s\n", rproc->name);
+ if (rproc->autonomous && !detached) {
+ mutex_unlock(&rproc->lock);
+ rproc_shutdown(rproc);
+ return 0;
+ }
+
ret = rproc_stop(rproc, true);
- if (ret)
+ if (ret || detached)
goto unlock_mutex;
/* generate coredump */
@@ -2014,6 +2024,11 @@ int rproc_add(struct rproc *rproc)
if (ret < 0)
return ret;
+ /* add resource manager device */
+ ret = devm_of_platform_populate(dev->parent);
+ if (ret < 0)
+ return ret;
+
/*
* Remind ourselves the remote processor has been attached to rather
* than booted by the remoteproc core. This is important because the
@@ -2297,6 +2312,8 @@ int rproc_del(struct rproc *rproc)
list_del_rcu(&rproc->node);
mutex_unlock(&rproc_list_mutex);
+ of_platform_depopulate(rproc->dev.parent);
+
/* Ensure that no readers of rproc_list are still active */
synchronize_rcu();
diff --git a/drivers/remoteproc/rproc_srm_core.c b/drivers/remoteproc/rproc_srm_core.c
new file mode 100644
index 000000000000..fc61e8b35686
--- /dev/null
+++ b/drivers/remoteproc/rproc_srm_core.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
+ */
+
+#include <linux/component.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/remoteproc.h>
+#include <linux/rpmsg.h>
+
+#include "rproc_srm_core.h"
+
+#define BIND_TIMEOUT 10000
+
+struct rproc_srm_core {
+ struct device *dev;
+ struct completion all_bound;
+ int bind_status;
+ atomic_t prepared;
+ struct rproc_subdev subdev;
+ struct rpmsg_driver rpdrv;
+ struct blocking_notifier_head notifier;
+};
+
+#define to_rproc_srm_core(s) container_of(s, struct rproc_srm_core, subdev)
+
+static struct rproc_srm_core *rpmsg_srm_to_core(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_driver *rpdrv;
+ struct rproc_srm_core *core;
+
+ rpdrv = container_of(rpdev->dev.driver, struct rpmsg_driver, drv);
+ core = container_of(rpdrv, struct rproc_srm_core, rpdrv);
+
+ return core;
+}
+
+int rpmsg_srm_send(struct rpmsg_endpoint *ept, struct rpmsg_srm_msg *msg)
+{
+ int ret;
+
+ ret = rpmsg_send(ept, (void *)msg, sizeof(*msg));
+ if (ret)
+ dev_err(&ept->rpdev->dev, "rpmsg_send failed: %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL(rpmsg_srm_send);
+
+static int rpmsg_srm_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ struct rproc_srm_core *core = rpmsg_srm_to_core(rpdev);
+ struct rpmsg_srm_msg_desc desc;
+ int ret;
+
+ desc.ept = rpdev->ept;
+ desc.msg = data;
+
+ ret = blocking_notifier_call_chain(&core->notifier, 0, &desc);
+
+ if (!(ret & NOTIFY_STOP_MASK)) {
+ dev_warn(&rpdev->dev, "unknown device\n");
+ desc.msg->message_type = RPROC_SRM_MSG_ERROR;
+ rpmsg_srm_send(desc.ept, desc.msg);
+ }
+
+ return 0;
+}
+
+static int rpmsg_srm_probe(struct rpmsg_device *rpdev)
+{
+ int ret;
+
+ dev_dbg(&rpdev->dev, "%s\n", __func__);
+
+ /* Send an empty message to complete the initialization */
+ ret = rpmsg_send(rpdev->ept, NULL, 0);
+ if (ret)
+ dev_err(&rpdev->dev, "failed to send init message\n");
+
+ return ret;
+}
+
+static void rpmsg_srm_remove(struct rpmsg_device *rpdev)
+{
+ /* Note : the remove ops is mandatory */
+ dev_dbg(&rpdev->dev, "%s\n", __func__);
+}
+
+static struct rpmsg_device_id rpmsg_srm_id_table[] = {
+ { .name = "rproc-srm" },
+ { },
+};
+MODULE_DEVICE_TABLE(rpmsg, rpmsg_srm_id_table);
+
+static struct rpmsg_driver rpmsg_srm_drv = {
+ .drv.name = "rpmsg_srm",
+ .id_table = rpmsg_srm_id_table,
+ .probe = rpmsg_srm_probe,
+ .callback = rpmsg_srm_cb,
+ .remove = rpmsg_srm_remove,
+};
+
+int rproc_srm_core_register_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&core->notifier, nb);
+}
+EXPORT_SYMBOL(rproc_srm_core_register_notifier);
+
+int rproc_srm_core_unregister_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&core->notifier, nb);
+}
+EXPORT_SYMBOL(rproc_srm_core_unregister_notifier);
+
+static int compare_of(struct device *dev, void *data)
+{
+ return dev->of_node == data;
+}
+
+static void release_of(struct device *dev, void *data)
+{
+ of_node_put(data);
+}
+
+static void rproc_srm_core_unbind(struct device *dev)
+{
+ component_unbind_all(dev, NULL);
+}
+
+static int rproc_srm_core_bind(struct device *dev)
+{
+ struct rproc_srm_core *rproc_srm_core = dev_get_drvdata(dev);
+
+ rproc_srm_core->bind_status = component_bind_all(dev, NULL);
+ complete(&rproc_srm_core->all_bound);
+
+ return rproc_srm_core->bind_status;
+}
+
+static const struct component_master_ops srm_comp_ops = {
+ .bind = rproc_srm_core_bind,
+ .unbind = rproc_srm_core_unbind,
+};
+
+static int rproc_srm_core_prepare(struct rproc_subdev *subdev)
+{
+ struct rproc_srm_core *rproc_srm_core = to_rproc_srm_core(subdev);
+ struct device *dev = rproc_srm_core->dev;
+ struct device_node *node = dev->of_node;
+ struct device_node *child_np;
+ struct component_match *match = NULL;
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ init_completion(&rproc_srm_core->all_bound);
+
+ ret = devm_of_platform_populate(dev);
+ if (ret) {
+ dev_err(dev, "cannot populate node (%d)\n", ret);
+ return ret;
+ }
+
+ child_np = of_get_next_available_child(node, NULL);
+
+ while (child_np) {
+ of_node_get(child_np);
+ component_match_add_release(dev, &match, release_of, compare_of,
+ child_np);
+ child_np = of_get_next_available_child(node, child_np);
+ }
+
+ if (!match) {
+ dev_dbg(dev, "No available child\n");
+ goto done;
+ }
+
+ ret = component_master_add_with_match(dev, &srm_comp_ops, match);
+ if (ret)
+ goto depopulate;
+
+ /* Wait for every child to be bound */
+ if (!wait_for_completion_timeout(&rproc_srm_core->all_bound,
+ msecs_to_jiffies(BIND_TIMEOUT))) {
+ dev_err(dev, "failed to bind one or more system resource device(s)\n");
+ ret = -ETIMEDOUT;
+ goto master;
+ }
+
+ ret = rproc_srm_core->bind_status;
+ if (ret) {
+ dev_err(dev, "failed to bind\n");
+ goto master;
+ }
+
+ /* Register rpmsg driver for dynamic management */
+ rproc_srm_core->rpdrv = rpmsg_srm_drv;
+ ret = register_rpmsg_driver(&rproc_srm_core->rpdrv);
+ if (ret) {
+ dev_err(dev, "failed to register rpmsg drv\n");
+ goto master;
+ }
+
+done:
+ atomic_inc(&rproc_srm_core->prepared);
+
+ return 0;
+
+master:
+ component_master_del(dev, &srm_comp_ops);
+depopulate:
+ devm_of_platform_depopulate(dev);
+ return ret;
+}
+
+static void rproc_srm_core_unprepare(struct rproc_subdev *subdev)
+{
+ struct rproc_srm_core *rproc_srm_core = to_rproc_srm_core(subdev);
+ struct device *dev = rproc_srm_core->dev;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (!atomic_read(&rproc_srm_core->prepared))
+ return;
+
+ atomic_dec(&rproc_srm_core->prepared);
+
+ unregister_rpmsg_driver(&rproc_srm_core->rpdrv);
+
+ component_master_del(dev, &srm_comp_ops);
+ devm_of_platform_depopulate(dev);
+}
+
+static int rproc_srm_core_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc *rproc = dev_get_drvdata(dev->parent);
+ struct rproc_srm_core *rproc_srm_core;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ rproc_srm_core = devm_kzalloc(dev, sizeof(struct rproc_srm_core),
+ GFP_KERNEL);
+ if (!rproc_srm_core)
+ return -ENOMEM;
+
+ rproc_srm_core->dev = dev;
+ BLOCKING_INIT_NOTIFIER_HEAD(&rproc_srm_core->notifier);
+
+ /* Register rproc subdevice with (un)prepare ops */
+ rproc_srm_core->subdev.prepare = rproc_srm_core_prepare;
+ rproc_srm_core->subdev.unprepare = rproc_srm_core_unprepare;
+ rproc_add_subdev(rproc, &rproc_srm_core->subdev);
+
+ dev_set_drvdata(dev, rproc_srm_core);
+
+ return 0;
+}
+
+static int rproc_srm_core_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc_srm_core *rproc_srm_core = dev_get_drvdata(dev);
+ struct rproc *rproc = dev_get_drvdata(dev->parent);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (atomic_read(&rproc->power) > 0)
+ dev_warn(dev, "Releasing resources while firmware running!\n");
+
+ rproc_srm_core_unprepare(&rproc_srm_core->subdev);
+
+ return 0;
+}
+
+static const struct of_device_id rproc_srm_core_match[] = {
+ { .compatible = "rproc-srm-core", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, rproc_srm_core_match);
+
+static struct platform_driver rproc_srm_core_driver = {
+ .probe = rproc_srm_core_probe,
+ .remove = rproc_srm_core_remove,
+ .driver = {
+ .name = "rproc-srm-core",
+ .of_match_table = of_match_ptr(rproc_srm_core_match),
+ },
+};
+
+module_platform_driver(rproc_srm_core_driver);
+
+MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
+MODULE_DESCRIPTION("Remoteproc System Resource Manager driver - core");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/remoteproc/rproc_srm_core.h b/drivers/remoteproc/rproc_srm_core.h
new file mode 100644
index 000000000000..7dffdb38f4d4
--- /dev/null
+++ b/drivers/remoteproc/rproc_srm_core.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
+ */
+
+#ifndef _RPROC_SRM_CORE_H_
+#define _RPROC_SRM_CORE_H_
+
+/**
+ * Message type used in resource manager rpmsg:
+ * RPROC_SRM_MSG_GETCONFIG: Request to get the configuration of a resource
+ * RPROC_SRM_MSG_SETCONFIG: Request to set the configuration of a resource
+ * RPROC_SRM_MSG_ERROR: Error when processing a request
+ */
+#define RPROC_SRM_MSG_GETCONFIG 0x00
+#define RPROC_SRM_MSG_SETCONFIG 0x01
+#define RPROC_SRM_MSG_ERROR 0xFF
+
+/**
+ * Resource type used in resource manager rpmsg:
+ * RPROC_SRM_RSC_CLOCK: clock resource
+ * RPROC_SRM_RSC_REGU: regulator resource
+ */
+#define RPROC_SRM_RSC_CLOCK 0x00
+#define RPROC_SRM_RSC_REGU 0x01
+
+/**
+ * struct clock_cfg - clock configuration used in resource manager rpmsg
+ * @index: clock index
+ * @name: clock name
+ * @rate: clock rate request (in SetConfig message) or current status (in
+ * GetConfig message)
+ */
+struct clock_cfg {
+ u32 index;
+ u8 name[16];
+ u32 rate;
+};
+
+/**
+ * struct regu_cfg - regu configuration used in resource manager rpmsg
+ * @index: regulator index
+ * @name: regulator name
+ * @enable: regulator enable/disable request (in SetConfig message) or
+ * current status (in GetConfig message)
+ * @curr_voltage_mv: current regulator voltage in mV (meaningful in
+ * SetConfig message)
+ * @min_voltage_mv: regulator min voltage request in mV (meaningful in
+ * SetConfig message)
+ * @max_voltage_mv: regulator max voltage request in mV (meaningful in
+ * SetConfig message)
+ */
+struct regu_cfg {
+ u32 index;
+ u8 name[16];
+ u32 enable;
+ u32 curr_voltage_mv;
+ u32 min_voltage_mv;
+ u32 max_voltage_mv;
+};
+
+/**
+ * struct rpmsg_srm_msg - message structure used between processors to
+ * dynamically update resources configuration
+ * @message_type: type of the message: see RPROC_SRM_MSG*
+ * @device_id: an identifier specifying the device owning the resources.
+ * This is implementation dependent. As example it may be the
+ * device name or the device address.
+ * @rsc_type: the type of the resource for which the configuration applies:
+ * see RPROC_SRM_RSC*
+ * @clock_cfg: clock config - relevant if &rsc_type is RPROC_SRM_RSC_CLOCK
+ * @regu_cfg: regulator config - relevant if &rsc_type is RPROC_SRM_RSC_REGU
+ */
+struct rpmsg_srm_msg {
+ u32 message_type;
+ u8 device_id[32];
+ u32 rsc_type;
+ union {
+ struct clock_cfg clock_cfg;
+ struct regu_cfg regu_cfg;
+ };
+};
+
+struct rpmsg_srm_msg_desc {
+ struct rpmsg_endpoint *ept;
+ struct rpmsg_srm_msg *msg;
+};
+
+struct rproc_srm_core;
+
+int rproc_srm_core_register_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb);
+int rproc_srm_core_unregister_notifier(struct rproc_srm_core *core,
+ struct notifier_block *nb);
+int rpmsg_srm_send(struct rpmsg_endpoint *ept, struct rpmsg_srm_msg *msg);
+
+#endif
diff --git a/drivers/remoteproc/rproc_srm_dev.c b/drivers/remoteproc/rproc_srm_dev.c
new file mode 100644
index 000000000000..9dad0820f263
--- /dev/null
+++ b/drivers/remoteproc/rproc_srm_dev.c
@@ -0,0 +1,744 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/component.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/remoteproc.h>
+
+#include "rproc_srm_core.h"
+
+struct rproc_srm_clk_info {
+ struct list_head list;
+ unsigned int index;
+ struct clk *clk;
+ const char *name;
+ bool parent_enabled;
+};
+
+struct rproc_srm_regu_info {
+ struct list_head list;
+ unsigned int index;
+ struct regulator *regu;
+ const char *name;
+ bool enabled;
+};
+
+struct rproc_srm_irq_info {
+ struct list_head list;
+ unsigned int index;
+ char *name;
+ int irq;
+ bool enabled;
+};
+
+struct rproc_srm_dev {
+ struct device *dev;
+ struct rproc_srm_core *core;
+ struct notifier_block nb;
+ bool early_boot;
+
+ struct list_head clk_list_head;
+ struct list_head regu_list_head;
+ struct list_head irq_list_head;
+};
+
+/* Irqs */
+static void rproc_srm_dev_irqs_put(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_irq_info *i, *tmp;
+
+ list_for_each_entry_safe(i, tmp, &rproc_srm_dev->irq_list_head, list) {
+ devm_free_irq(dev, i->irq, NULL);
+ dev_dbg(dev, "Put irq %d (%s)\n", i->irq, i->name);
+ list_del(&i->list);
+ }
+}
+
+static irqreturn_t rproc_srm_dev_irq_handler(int irq, void *dev)
+{
+ dev_warn(dev, "Spurious interrupt\n");
+ return IRQ_HANDLED;
+}
+
+static int rproc_srm_dev_irqs_get(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct device_node *np = dev->of_node;
+ struct rproc_srm_irq_info *info;
+ const char *name;
+ int nr, ret, irq;
+ unsigned int i;
+
+ if (!np)
+ return 0;
+
+ nr = platform_irq_count(pdev);
+ if (!nr)
+ return 0;
+
+ if (rproc_srm_dev->early_boot)
+ /*
+ * Do not overwrite the irq configuration.
+ * No need to parse irq from DT since the resource manager does
+ * not offer any service to update the irq config.
+ */
+ return 0;
+
+ for (i = 0; i < nr; i++) {
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ irq = platform_get_irq(pdev, i);
+ if (irq <= 0) {
+ ret = irq;
+ dev_err(dev, "Failed to get irq (%d)\n", ret);
+ goto err;
+ }
+
+ info->irq = irq;
+
+ /* Register a dummy irq handleras not used by Linux */
+ ret = devm_request_irq(dev, info->irq,
+ rproc_srm_dev_irq_handler, 0,
+ dev_name(dev), NULL);
+ if (ret) {
+ dev_err(dev, "Failed to request irq (%d)\n", ret);
+ goto err;
+ }
+
+ /*
+ * Disable IRQ. Since it is used by the remote processor we
+ * must not use the 'irq lazy disable' optimization
+ */
+ irq_set_status_flags(info->irq, IRQ_DISABLE_UNLAZY);
+ disable_irq(info->irq);
+
+ /* Note: "interrupt-names" is optional */
+ if (!of_property_read_string_index(np, "interrupt-names", i,
+ &name))
+ info->name = devm_kstrdup(dev, name, GFP_KERNEL);
+ else
+ info->name = devm_kstrdup(dev, "", GFP_KERNEL);
+
+ info->index = i;
+
+ list_add_tail(&info->list, &rproc_srm_dev->irq_list_head);
+ dev_dbg(dev, "Got irq %d (%s)\n", info->irq, info->name);
+ }
+
+ return 0;
+
+err:
+ rproc_srm_dev_irqs_put(rproc_srm_dev);
+
+ return ret;
+}
+
+/* Clocks */
+static void rproc_srm_dev_clocks_unsetup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_clk_info *c;
+
+ list_for_each_entry(c, &rproc_srm_dev->clk_list_head, list) {
+ if (!c->parent_enabled)
+ continue;
+
+ clk_disable_unprepare(clk_get_parent(c->clk));
+ c->parent_enabled = false;
+ dev_dbg(rproc_srm_dev->dev, "clk %d (%s) unsetup\n",
+ c->index, c->name);
+ }
+}
+
+static int rproc_srm_dev_clocks_setup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_clk_info *c;
+ int ret;
+
+ /*
+ * Prepare and enable the parent clocks.
+ * Since the clock tree is under the exclusive control of the master
+ * processor, we need to configure the clock tree of the targeted clock.
+ * We do not want to enable the clock itself, which is under the
+ * responsibility of the remote processor.
+ * Hence we prepare and enable the parent clock.
+ */
+
+ list_for_each_entry(c, &rproc_srm_dev->clk_list_head, list) {
+ if (c->parent_enabled)
+ continue;
+
+ ret = clk_prepare_enable(clk_get_parent(c->clk));
+ if (ret) {
+ dev_err(rproc_srm_dev->dev,
+ "clk %d (%s) parent enable failed\n",
+ c->index, c->name);
+ rproc_srm_dev_clocks_unsetup(rproc_srm_dev);
+ return ret;
+ }
+ c->parent_enabled = true;
+ dev_dbg(rproc_srm_dev->dev, "clk %d (%s) parent enabled\n",
+ c->index, c->name);
+ }
+
+ return 0;
+}
+
+static struct rproc_srm_clk_info
+ *rproc_srm_dev_clock_find(struct rproc_srm_dev *rproc_srm_dev,
+ struct clock_cfg *cfg)
+{
+ struct rproc_srm_clk_info *ci;
+
+ /* Search by index (if valid value) otherwise search by name */
+ list_for_each_entry(ci, &rproc_srm_dev->clk_list_head, list) {
+ if (cfg->index != U32_MAX) {
+ if (ci->index == cfg->index)
+ return ci;
+ } else {
+ if (!strcmp(ci->name, cfg->name))
+ return ci;
+ }
+ }
+
+ return NULL;
+}
+
+static int rproc_srm_dev_clock_set_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct clock_cfg *cfg)
+{
+ struct rproc_srm_clk_info *c;
+ struct device *dev = rproc_srm_dev->dev;
+ int ret;
+
+ c = rproc_srm_dev_clock_find(rproc_srm_dev, cfg);
+
+ if (!c) {
+ dev_err(dev, "unknown clock (id %d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ if (cfg->rate && clk_get_rate(c->clk) != cfg->rate) {
+ ret = clk_set_rate(c->clk, cfg->rate);
+ if (ret) {
+ dev_err(dev, "clk set rate failed\n");
+ return ret;
+ }
+
+ dev_dbg(dev, "clk %d (%s) rate = %d\n", c->index, c->name,
+ cfg->rate);
+ }
+
+ return 0;
+}
+
+static int rproc_srm_dev_clock_get_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct clock_cfg *cfg)
+{
+ struct rproc_srm_clk_info *c;
+
+ c = rproc_srm_dev_clock_find(rproc_srm_dev, cfg);
+ if (!c) {
+ dev_err(rproc_srm_dev->dev, "unknown clock (%d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ strlcpy(cfg->name, c->name, sizeof(cfg->name));
+ cfg->index = c->index;
+ cfg->rate = (u32)clk_get_rate(c->clk);
+
+ return 0;
+}
+
+static void rproc_srm_dev_clocks_put(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_clk_info *c, *tmp;
+
+ list_for_each_entry_safe(c, tmp, &rproc_srm_dev->clk_list_head, list) {
+ clk_put(c->clk);
+ dev_dbg(dev, "put clock %d (%s)\n", c->index, c->name);
+ list_del(&c->list);
+ }
+}
+
+static int rproc_srm_dev_clocks_get(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct device_node *np = dev->of_node;
+ struct rproc_srm_clk_info *c;
+ const char *name;
+ int nb_c, ret;
+ unsigned int i;
+
+ if (!np)
+ return 0;
+
+ nb_c = of_clk_get_parent_count(np);
+ if (!nb_c)
+ return 0;
+
+ for (i = 0; i < nb_c; i++) {
+ c = devm_kzalloc(dev, sizeof(*c), GFP_KERNEL);
+ if (!c) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ c->clk = of_clk_get(np, i);
+ if (IS_ERR(c->clk)) {
+ dev_err(dev, "clock %d KO (%ld)\n", i,
+ PTR_ERR(c->clk));
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* Note: "clock-names" is optional */
+ if (!of_property_read_string_index(np, "clock-names", i,
+ &name))
+ c->name = devm_kstrdup(dev, name, GFP_KERNEL);
+ else
+ c->name = devm_kstrdup(dev, "", GFP_KERNEL);
+
+ c->index = i;
+
+ list_add_tail(&c->list, &rproc_srm_dev->clk_list_head);
+ dev_dbg(dev, "got clock %d (%s)\n", c->index, c->name);
+ }
+
+ return 0;
+
+err:
+ rproc_srm_dev_clocks_put(rproc_srm_dev);
+ return ret;
+}
+
+/* Regulators */
+static void rproc_srm_dev_regus_unsetup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_regu_info *r;
+ struct device *dev = rproc_srm_dev->dev;
+
+ list_for_each_entry(r, &rproc_srm_dev->regu_list_head, list) {
+ if (!r->enabled)
+ continue;
+
+ if (regulator_disable(r->regu)) {
+ dev_warn(dev, "regu %d disabled failed\n", r->index);
+ continue;
+ }
+
+ r->enabled = false;
+ dev_dbg(dev, "regu %d (%s) disabled\n", r->index, r->name);
+ }
+}
+
+static int rproc_srm_dev_regus_setup(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct rproc_srm_regu_info *r;
+ int ret;
+
+ /* Enable all the regulators */
+ list_for_each_entry(r, &rproc_srm_dev->regu_list_head, list) {
+ if (r->enabled)
+ continue;
+
+ /* in early_boot mode sync on hw */
+ if (rproc_srm_dev->early_boot && !regulator_is_enabled(r->regu))
+ continue;
+
+ ret = regulator_enable(r->regu);
+ if (ret) {
+ dev_err(rproc_srm_dev->dev, "regu %d (%s) failed\n",
+ r->index, r->name);
+ rproc_srm_dev_regus_unsetup(rproc_srm_dev);
+ return ret;
+ }
+ r->enabled = true;
+ dev_dbg(rproc_srm_dev->dev, "regu %d (%s) enabled\n",
+ r->index, r->name);
+ }
+
+ return 0;
+}
+
+static struct rproc_srm_regu_info
+ *rproc_srm_dev_regu_find(struct rproc_srm_dev *rproc_srm_dev,
+ struct regu_cfg *cfg)
+{
+ struct rproc_srm_regu_info *ri;
+
+ list_for_each_entry(ri, &rproc_srm_dev->regu_list_head, list) {
+ if (cfg->index != U32_MAX) {
+ if (ri->index == cfg->index)
+ return ri;
+ } else {
+ if (!strcmp(ri->name, cfg->name))
+ return ri;
+ }
+ }
+
+ return NULL;
+}
+
+static int rproc_srm_dev_regu_set_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct regu_cfg *cfg)
+{
+ struct rproc_srm_regu_info *r;
+ struct device *dev = rproc_srm_dev->dev;
+ int ret;
+
+ r = rproc_srm_dev_regu_find(rproc_srm_dev, cfg);
+ if (!r) {
+ dev_err(dev, "unknown regu (%d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ if (!r->enabled && cfg->enable) {
+ ret = regulator_enable(r->regu);
+ if (ret) {
+ dev_err(dev, "regu %d enable failed\n", r->index);
+ return ret;
+ }
+ r->enabled = true;
+ dev_dbg(dev, "regu %d (%s) enabled\n", r->index, r->name);
+ } else if (r->enabled && !cfg->enable) {
+ ret = regulator_disable(r->regu);
+ if (ret) {
+ dev_err(dev, "regu %d disable failed\n", r->index);
+ return ret;
+ }
+ r->enabled = false;
+ dev_dbg(dev, "regu %d (%s) disabled\n", r->index, r->name);
+ }
+
+ if (cfg->min_voltage_mv || cfg->max_voltage_mv) {
+ ret = regulator_set_voltage(r->regu, cfg->min_voltage_mv * 1000,
+ cfg->max_voltage_mv * 1000);
+ if (ret) {
+ dev_err(dev, "regu %d set voltage failed\n", r->index);
+ return ret;
+ }
+
+ dev_dbg(dev, "regu %d (%s) voltage = [%d - %d] mv\n", r->index,
+ r->name, cfg->min_voltage_mv, cfg->max_voltage_mv);
+ }
+
+ return 0;
+}
+
+static int rproc_srm_dev_regu_get_cfg(struct rproc_srm_dev *rproc_srm_dev,
+ struct regu_cfg *cfg)
+{
+ struct rproc_srm_regu_info *r;
+ struct device *dev = rproc_srm_dev->dev;
+ int v;
+
+ r = rproc_srm_dev_regu_find(rproc_srm_dev, cfg);
+ if (!r) {
+ dev_err(dev, "unknown regu (%d)\n", cfg->index);
+ return -EINVAL;
+ }
+
+ strlcpy(cfg->name, r->name, sizeof(cfg->name));
+ cfg->index = r->index;
+ cfg->enable = r->enabled;
+ cfg->min_voltage_mv = 0;
+ cfg->max_voltage_mv = 0;
+
+ v = regulator_get_voltage(r->regu);
+ if (v < 0) {
+ dev_warn(dev, "cannot get %s voltage\n", r->name);
+ cfg->curr_voltage_mv = 0;
+ } else {
+ cfg->curr_voltage_mv = v / 1000;
+ }
+
+ return 0;
+}
+
+static void rproc_srm_dev_regus_put(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct rproc_srm_regu_info *r, *tmp;
+
+ list_for_each_entry_safe(r, tmp, &rproc_srm_dev->regu_list_head, list) {
+ devm_regulator_put(r->regu);
+ dev_dbg(dev, "put regu %d (%s)\n", r->index, r->name);
+ list_del(&r->list);
+ }
+}
+
+static int rproc_srm_dev_regus_get(struct rproc_srm_dev *rproc_srm_dev)
+{
+ struct device *dev = rproc_srm_dev->dev;
+ struct device_node *np = dev->of_node;
+ struct property *p;
+ const char *n;
+ char *name;
+ struct rproc_srm_regu_info *r;
+ int ret, nb_s = 0;
+
+ if (!np)
+ return 0;
+
+ for_each_property_of_node(np, p) {
+ n = strstr(p->name, "-supply");
+ if (!n || n == p->name)
+ continue;
+
+ r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL);
+ if (!r) {
+ ret = -ENOMEM;
+ goto err_list;
+ }
+
+ name = devm_kstrdup(dev, p->name, GFP_KERNEL);
+ name[strlen(p->name) - strlen("-supply")] = '\0';
+ r->name = name;
+
+ r->regu = devm_regulator_get(dev, r->name);
+ if (IS_ERR(r->regu)) {
+ dev_err(dev, "cannot get regu %s\n", r->name);
+ ret = -EINVAL;
+ goto err_list;
+ }
+
+ r->index = nb_s++;
+
+ list_add_tail(&r->list, &rproc_srm_dev->regu_list_head);
+ dev_dbg(dev, "got regu %d (%s)\n", r->index, r->name);
+ }
+
+ return 0;
+
+err_list:
+ rproc_srm_dev_regus_put(rproc_srm_dev);
+ return ret;
+}
+
+/* Core */
+static int rproc_srm_dev_notify_cb(struct notifier_block *nb, unsigned long evt,
+ void *data)
+{
+ struct rproc_srm_dev *rproc_srm_dev =
+ container_of(nb, struct rproc_srm_dev, nb);
+ struct device *dev = rproc_srm_dev->dev;
+ struct rpmsg_srm_msg_desc *desc;
+ struct rpmsg_srm_msg *i, o;
+ int ret = 0;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ desc = (struct rpmsg_srm_msg_desc *)data;
+ i = desc->msg;
+ o = *i;
+
+ /* Check if 'device_id' (name / addr ) matches this device */
+ if (!strstr(dev_name(dev), i->device_id))
+ return NOTIFY_DONE;
+
+ switch (i->message_type) {
+ case RPROC_SRM_MSG_SETCONFIG:
+ switch (i->rsc_type) {
+ case RPROC_SRM_RSC_CLOCK:
+ ret = rproc_srm_dev_clock_set_cfg(rproc_srm_dev,
+ &i->clock_cfg);
+ if (!ret)
+ ret = rproc_srm_dev_clock_get_cfg(rproc_srm_dev,
+ &o.clock_cfg);
+ break;
+ case RPROC_SRM_RSC_REGU:
+ ret = rproc_srm_dev_regu_set_cfg(rproc_srm_dev,
+ &i->regu_cfg);
+ if (!ret)
+ ret = rproc_srm_dev_regu_get_cfg(rproc_srm_dev,
+ &o.regu_cfg);
+ break;
+ default:
+ dev_warn(dev, "bad rsc type (%d)\n", i->rsc_type);
+ ret = -EINVAL;
+ break;
+ }
+ break;
+ case RPROC_SRM_MSG_GETCONFIG:
+ switch (i->rsc_type) {
+ case RPROC_SRM_RSC_CLOCK:
+ ret = rproc_srm_dev_clock_get_cfg(rproc_srm_dev,
+ &o.clock_cfg);
+ break;
+ case RPROC_SRM_RSC_REGU:
+ ret = rproc_srm_dev_regu_get_cfg(rproc_srm_dev,
+ &o.regu_cfg);
+ break;
+ default:
+ dev_warn(dev, "bad rsc type (%d)\n", i->rsc_type);
+ ret = -EINVAL;
+ break;
+ }
+ break;
+ default:
+ dev_warn(dev, "bad msg type (%d)\n", i->message_type);
+ ret = -EINVAL;
+ break;
+ }
+
+ /* Send return msg */
+ if (ret)
+ o.message_type = RPROC_SRM_MSG_ERROR;
+
+ ret = rpmsg_srm_send(desc->ept, &o);
+
+ return ret ? NOTIFY_BAD : NOTIFY_STOP;
+}
+
+static void
+rproc_srm_dev_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ rproc_srm_dev_regus_unsetup(rproc_srm_dev);
+ rproc_srm_dev_clocks_unsetup(rproc_srm_dev);
+
+ /* For IRQs: nothing to unsetup */
+}
+
+static int
+rproc_srm_dev_bind(struct device *dev, struct device *master, void *data)
+{
+ struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ ret = rproc_srm_dev_clocks_setup(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ ret = rproc_srm_dev_regus_setup(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ /* For IRQs: nothing to setup */
+ return 0;
+}
+
+static const struct component_ops rproc_srm_dev_ops = {
+ .bind = rproc_srm_dev_bind,
+ .unbind = rproc_srm_dev_unbind,
+};
+
+static int rproc_srm_dev_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc_srm_dev *rproc_srm_dev;
+ struct rproc *rproc;
+ int ret;
+
+ dev_dbg(dev, "%s for node %s\n", __func__, dev->of_node->name);
+
+ rproc_srm_dev = devm_kzalloc(dev, sizeof(struct rproc_srm_dev),
+ GFP_KERNEL);
+ if (!rproc_srm_dev)
+ return -ENOMEM;
+
+ rproc_srm_dev->dev = dev;
+ rproc = (struct rproc *)dev_get_drvdata(dev->parent->parent);
+ rproc_srm_dev->early_boot = (rproc->state == RPROC_DETACHED);
+ rproc_srm_dev->core = dev_get_drvdata(dev->parent);
+
+ INIT_LIST_HEAD(&rproc_srm_dev->clk_list_head);
+ INIT_LIST_HEAD(&rproc_srm_dev->regu_list_head);
+ INIT_LIST_HEAD(&rproc_srm_dev->irq_list_head);
+
+ /* Get clocks, regu and irqs */
+ ret = rproc_srm_dev_clocks_get(rproc_srm_dev);
+ if (ret)
+ return ret;
+
+ ret = rproc_srm_dev_regus_get(rproc_srm_dev);
+ if (ret)
+ goto err_get;
+
+ ret = rproc_srm_dev_irqs_get(rproc_srm_dev);
+ if (ret)
+ goto err_get;
+
+ rproc_srm_dev->nb.notifier_call = rproc_srm_dev_notify_cb;
+ ret = rproc_srm_core_register_notifier(rproc_srm_dev->core,
+ &rproc_srm_dev->nb);
+ if (ret)
+ goto err_register;
+
+ dev_set_drvdata(dev, rproc_srm_dev);
+
+ return component_add(dev, &rproc_srm_dev_ops);
+
+err_register:
+ rproc_srm_core_unregister_notifier(rproc_srm_dev->core,
+ &rproc_srm_dev->nb);
+err_get:
+ rproc_srm_dev_irqs_put(rproc_srm_dev);
+ rproc_srm_dev_regus_put(rproc_srm_dev);
+ rproc_srm_dev_clocks_put(rproc_srm_dev);
+ return ret;
+}
+
+static int rproc_srm_dev_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc_srm_dev *rproc_srm_dev = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ component_del(dev, &rproc_srm_dev_ops);
+
+ rproc_srm_core_unregister_notifier(rproc_srm_dev->core,
+ &rproc_srm_dev->nb);
+
+ rproc_srm_dev_irqs_put(rproc_srm_dev);
+ rproc_srm_dev_regus_put(rproc_srm_dev);
+ rproc_srm_dev_clocks_put(rproc_srm_dev);
+
+ return 0;
+}
+
+static const struct of_device_id rproc_srm_dev_match[] = {
+ { .compatible = "rproc-srm-dev", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, rproc_srm_dev_match);
+
+static struct platform_driver rproc_srm_dev_driver = {
+ .probe = rproc_srm_dev_probe,
+ .remove = rproc_srm_dev_remove,
+ .driver = {
+ .name = "rproc-srm-dev",
+ .of_match_table = of_match_ptr(rproc_srm_dev_match),
+ },
+};
+
+module_platform_driver(rproc_srm_dev_driver);
+
+MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
+MODULE_DESCRIPTION("Remoteproc System Resource Manager driver - dev");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/remoteproc/stm32_rproc.c b/drivers/remoteproc/stm32_rproc.c
index d2414cc1d90d..f5a65eea9cdc 100644
--- a/drivers/remoteproc/stm32_rproc.c
+++ b/drivers/remoteproc/stm32_rproc.c
@@ -20,13 +20,11 @@
#include <linux/remoteproc.h>
#include <linux/reset.h>
#include <linux/slab.h>
+#include <linux/tee_remoteproc.h>
#include <linux/workqueue.h>
#include "remoteproc_internal.h"
-#define HOLD_BOOT 0
-#define RELEASE_BOOT 1
-
#define MBOX_NB_VQ 2
#define MBOX_NB_MBX 3
@@ -48,6 +46,13 @@
#define M4_STATE_STANDBY 4
#define M4_STATE_CRASH 5
+/*
+ * Define a default index in future may come a global list of
+ * firmwares which list platforms and associated firmware(s)
+ */
+
+#define STM32_MP1_FW_ID 0
+
struct stm32_syscon {
struct regmap *map;
u32 reg;
@@ -78,7 +83,7 @@ struct stm32_mbox {
struct stm32_rproc {
struct reset_control *rst;
- struct stm32_syscon hold_boot;
+ struct reset_control *hold_boot;
struct stm32_syscon pdds;
struct stm32_syscon m4_state;
struct stm32_syscon rsctbl;
@@ -87,10 +92,17 @@ struct stm32_rproc {
struct stm32_rproc_mem *rmems;
struct stm32_mbox mb[MBOX_NB_MBX];
struct workqueue_struct *workqueue;
- bool secured_soc;
+ bool secured_fw;
+ bool fw_loaded;
+ struct tee_rproc *trproc;
void __iomem *rsc_va;
};
+struct stm32_rproc_conf {
+ bool secured_fw;
+ struct rproc_ops *ops;
+};
+
static int stm32_rproc_pa_to_da(struct rproc *rproc, phys_addr_t pa, u64 *da)
{
unsigned int i;
@@ -207,15 +219,139 @@ static int stm32_rproc_mbox_idx(struct rproc *rproc, const unsigned char *name)
return -EINVAL;
}
-static int stm32_rproc_elf_load_rsc_table(struct rproc *rproc,
+static void stm32_rproc_request_shutdown(struct rproc *rproc)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ int err, dummy_data, idx;
+
+ /* Request shutdown of the remote processor */
+ if (rproc->state != RPROC_OFFLINE) {
+ idx = stm32_rproc_mbox_idx(rproc, STM32_MBX_SHUTDOWN);
+ if (idx >= 0 && ddata->mb[idx].chan) {
+ /* A dummy data is sent to allow to block on transmit. */
+ err = mbox_send_message(ddata->mb[idx].chan,
+ &dummy_data);
+ if (err < 0)
+ dev_warn(&rproc->dev, "warning: remote FW shutdown without ack\n");
+ }
+ }
+}
+
+static int stm32_rproc_release(struct rproc *rproc)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ unsigned int err = 0;
+
+ /* To allow platform Standby power mode, set remote proc Deep Sleep. */
+ if (ddata->pdds.map) {
+ err = regmap_update_bits(ddata->pdds.map, ddata->pdds.reg,
+ ddata->pdds.mask, 1);
+ if (err) {
+ dev_err(&rproc->dev, "failed to set pdds\n");
+ return err;
+ }
+ }
+
+ /* Update coprocessor state to OFF if available. */
+ if (ddata->m4_state.map) {
+ err = regmap_update_bits(ddata->m4_state.map,
+ ddata->m4_state.reg,
+ ddata->m4_state.mask,
+ M4_STATE_OFF);
+ if (err) {
+ dev_err(&rproc->dev, "failed to set copro state\n");
+ return err;
+ }
+ }
+
+ return err;
+}
+
+static int stm32_rproc_tee_elf_sanity_check(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ unsigned int ret = 0;
+
+ if (rproc->state == RPROC_DETACHED)
+ return 0;
+
+ ret = tee_rproc_load_fw(ddata->trproc, fw);
+ if (!ret)
+ ddata->fw_loaded = true;
+
+ return ret;
+}
+
+static int stm32_rproc_tee_elf_load(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ unsigned int ret;
+
+ /*
+ * This function can be called by remote proc for recovery
+ * without the sanity check. In this case we need to load the firmware
+ * else nothing done here as the firmware has been preloaded for the
+ * sanity check to be able to parse it for the resource table
+ */
+ if (ddata->fw_loaded)
+ return 0;
+
+ ret = tee_rproc_load_fw(ddata->trproc, fw);
+ if (ret)
+ return ret;
+ ddata->fw_loaded = true;
+
+ /* update the resource table parameters */
+ if (rproc_tee_get_rsc_table(ddata->trproc)) {
+ /* no resource table: reset the related fields */
+ rproc->cached_table = NULL;
+ rproc->table_ptr = NULL;
+ rproc->table_sz = 0;
+ }
+
+ return 0;
+}
+
+static struct resource_table *
+stm32_rproc_tee_elf_find_loaded_rsc_table(struct rproc *rproc,
const struct firmware *fw)
{
- if (rproc_elf_load_rsc_table(rproc, fw))
- dev_warn(&rproc->dev, "no resource table found for this firmware\n");
+ struct stm32_rproc *ddata = rproc->priv;
+
+ return tee_rproc_get_loaded_rsc_table(ddata->trproc);
+}
+
+static int stm32_rproc_tee_start(struct rproc *rproc)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+
+ return tee_rproc_start(ddata->trproc);
+}
+static int stm32_rproc_tee_attach(struct rproc *rproc)
+{
+ /* Nothing to do, remote proc already started by the secured context */
return 0;
}
+static int stm32_rproc_tee_stop(struct rproc *rproc)
+{
+ struct stm32_rproc *ddata = rproc->priv;
+ int err;
+
+ stm32_rproc_request_shutdown(rproc);
+
+ err = tee_rproc_stop(ddata->trproc);
+ if (err)
+ return err;
+
+ ddata->fw_loaded = false;
+
+ return stm32_rproc_release(rproc);
+}
+
static int stm32_rproc_parse_memory_regions(struct rproc *rproc)
{
struct device *dev = rproc->dev.parent;
@@ -274,12 +410,21 @@ static int stm32_rproc_parse_memory_regions(struct rproc *rproc)
static int stm32_rproc_parse_fw(struct rproc *rproc, const struct firmware *fw)
{
- int ret = stm32_rproc_parse_memory_regions(rproc);
+ struct stm32_rproc *ddata = rproc->priv;
+ int ret;
+ ret = stm32_rproc_parse_memory_regions(rproc);
if (ret)
return ret;
- return stm32_rproc_elf_load_rsc_table(rproc, fw);
+ if (ddata->trproc)
+ ret = rproc_tee_get_rsc_table(ddata->trproc);
+ else
+ ret = rproc_elf_load_rsc_table(rproc, fw);
+ if (ret)
+ dev_warn(&rproc->dev, "no resource table found for this firmware\n");
+
+ return 0;
}
static irqreturn_t stm32_rproc_wdg(int irq, void *data)
@@ -370,8 +515,13 @@ static int stm32_rproc_request_mbox(struct rproc *rproc)
ddata->mb[i].chan = mbox_request_channel_byname(cl, name);
if (IS_ERR(ddata->mb[i].chan)) {
- if (PTR_ERR(ddata->mb[i].chan) == -EPROBE_DEFER)
+ if (PTR_ERR(ddata->mb[i].chan) == -EPROBE_DEFER) {
+ dev_err_probe(dev->parent,
+ PTR_ERR(ddata->mb[i].chan),
+ "failed to request mailbox %s\n",
+ name);
goto err_probe;
+ }
dev_warn(dev, "cannot get %s mbox\n", name);
ddata->mb[i].chan = NULL;
}
@@ -390,30 +540,6 @@ static int stm32_rproc_request_mbox(struct rproc *rproc)
return -EPROBE_DEFER;
}
-static int stm32_rproc_set_hold_boot(struct rproc *rproc, bool hold)
-{
- struct stm32_rproc *ddata = rproc->priv;
- struct stm32_syscon hold_boot = ddata->hold_boot;
- struct arm_smccc_res smc_res;
- int val, err;
-
- val = hold ? HOLD_BOOT : RELEASE_BOOT;
-
- if (IS_ENABLED(CONFIG_HAVE_ARM_SMCCC) && ddata->secured_soc) {
- arm_smccc_smc(STM32_SMC_RCC, STM32_SMC_REG_WRITE,
- hold_boot.reg, val, 0, 0, 0, 0, &smc_res);
- err = smc_res.a0;
- } else {
- err = regmap_update_bits(hold_boot.map, hold_boot.reg,
- hold_boot.mask, val);
- }
-
- if (err)
- dev_err(&rproc->dev, "failed to set hold boot\n");
-
- return err;
-}
-
static void stm32_rproc_add_coredump_trace(struct rproc *rproc)
{
struct rproc_debug_trace *trace;
@@ -453,40 +579,34 @@ static int stm32_rproc_start(struct rproc *rproc)
}
}
- err = stm32_rproc_set_hold_boot(rproc, false);
+ err = reset_control_deassert(ddata->hold_boot);
if (err)
return err;
- return stm32_rproc_set_hold_boot(rproc, true);
+ return reset_control_assert(ddata->hold_boot);
}
static int stm32_rproc_attach(struct rproc *rproc)
{
+ struct stm32_rproc *ddata = rproc->priv;
+
stm32_rproc_add_coredump_trace(rproc);
- return stm32_rproc_set_hold_boot(rproc, true);
+ return reset_control_assert(ddata->hold_boot);
}
static int stm32_rproc_stop(struct rproc *rproc)
{
struct stm32_rproc *ddata = rproc->priv;
- int err, dummy_data, idx;
+ int err;
- /* request shutdown of the remote processor */
- if (rproc->state != RPROC_OFFLINE) {
- idx = stm32_rproc_mbox_idx(rproc, STM32_MBX_SHUTDOWN);
- if (idx >= 0 && ddata->mb[idx].chan) {
- /* a dummy data is sent to allow to block on transmit */
- err = mbox_send_message(ddata->mb[idx].chan,
- &dummy_data);
- if (err < 0)
- dev_warn(&rproc->dev, "warning: remote FW shutdown without ack\n");
- }
- }
+ stm32_rproc_request_shutdown(rproc);
- err = stm32_rproc_set_hold_boot(rproc, true);
- if (err)
+ err = reset_control_assert(ddata->hold_boot);
+ if (err) {
+ dev_err(&rproc->dev, "failed to assert the hold boot\n");
return err;
+ }
err = reset_control_assert(ddata->rst);
if (err) {
@@ -494,29 +614,8 @@ static int stm32_rproc_stop(struct rproc *rproc)
return err;
}
- /* to allow platform Standby power mode, set remote proc Deep Sleep */
- if (ddata->pdds.map) {
- err = regmap_update_bits(ddata->pdds.map, ddata->pdds.reg,
- ddata->pdds.mask, 1);
- if (err) {
- dev_err(&rproc->dev, "failed to set pdds\n");
- return err;
- }
- }
- /* update coprocessor state to OFF if available */
- if (ddata->m4_state.map) {
- err = regmap_update_bits(ddata->m4_state.map,
- ddata->m4_state.reg,
- ddata->m4_state.mask,
- M4_STATE_OFF);
- if (err) {
- dev_err(&rproc->dev, "failed to set copro state\n");
- return err;
- }
- }
-
- return 0;
+ return stm32_rproc_release(rproc);
}
static void stm32_rproc_kick(struct rproc *rproc, int vqid)
@@ -553,8 +652,36 @@ static struct rproc_ops st_rproc_ops = {
.get_boot_addr = rproc_elf_get_boot_addr,
};
+static struct rproc_ops st_rproc_tee_ops = {
+ .start = stm32_rproc_tee_start,
+ .stop = stm32_rproc_tee_stop,
+ .attach = stm32_rproc_tee_attach,
+ .kick = stm32_rproc_kick,
+ .parse_fw = stm32_rproc_parse_fw,
+ .find_loaded_rsc_table = stm32_rproc_tee_elf_find_loaded_rsc_table,
+ .sanity_check = stm32_rproc_tee_elf_sanity_check,
+ .load = stm32_rproc_tee_elf_load,
+};
+
+static const struct stm32_rproc_conf stm32_rproc_default_conf = {
+ .secured_fw = false,
+ .ops = &st_rproc_ops,
+};
+
+static const struct stm32_rproc_conf stm32_rproc_tee_conf = {
+ .secured_fw = true,
+ .ops = &st_rproc_tee_ops,
+};
+
static const struct of_device_id stm32_rproc_match[] = {
- { .compatible = "st,stm32mp1-m4" },
+ {
+ .compatible = "st,stm32mp1-m4",
+ .data = &stm32_rproc_default_conf,
+ },
+ {
+ .compatible = "st,stm32mp1-m4_optee",
+ .data = &stm32_rproc_tee_conf,
+ },
{},
};
MODULE_DEVICE_TABLE(of, stm32_rproc_match);
@@ -586,21 +713,18 @@ static int stm32_rproc_parse_dt(struct platform_device *pdev,
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
- struct stm32_syscon tz;
- unsigned int tzen;
int err, irq;
irq = platform_get_irq(pdev, 0);
if (irq == -EPROBE_DEFER)
- return -EPROBE_DEFER;
+ return dev_err_probe(dev, irq, "failed to get interrupt\n");
if (irq > 0) {
err = devm_request_irq(dev, irq, stm32_rproc_wdg, 0,
dev_name(dev), pdev);
- if (err) {
- dev_err(dev, "failed to request wdg irq\n");
- return err;
- }
+ if (err)
+ return dev_err_probe(dev, err,
+ "failed to request wdg irq\n");
ddata->wdg_irq = irq;
@@ -612,36 +736,15 @@ static int stm32_rproc_parse_dt(struct platform_device *pdev,
dev_info(dev, "wdg irq registered\n");
}
- ddata->rst = devm_reset_control_get_by_index(dev, 0);
- if (IS_ERR(ddata->rst)) {
- dev_err(dev, "failed to get mcu reset\n");
- return PTR_ERR(ddata->rst);
- }
-
- /*
- * if platform is secured the hold boot bit must be written by
- * smc call and read normally.
- * if not secure the hold boot bit could be read/write normally
- */
- err = stm32_rproc_get_syscon(np, "st,syscfg-tz", &tz);
- if (err) {
- dev_err(dev, "failed to get tz syscfg\n");
- return err;
- }
-
- err = regmap_read(tz.map, tz.reg, &tzen);
- if (err) {
- dev_err(dev, "failed to read tzen\n");
- return err;
- }
- ddata->secured_soc = tzen & tz.mask;
+ ddata->rst = devm_reset_control_get(dev, "mcu_rst");
+ if (IS_ERR(ddata->rst))
+ return dev_err_probe(dev, PTR_ERR(ddata->rst),
+ "failed to get mcu_reset\n");
- 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;
- }
+ ddata->hold_boot = devm_reset_control_get(dev, "hold_boot");
+ if (IS_ERR(ddata->hold_boot))
+ return dev_err_probe(dev, PTR_ERR(ddata->hold_boot),
+ "failed to get mcu reset\n");
err = stm32_rproc_get_syscon(np, "st,syscfg-pdds", &ddata->pdds);
if (err)
@@ -766,6 +869,8 @@ static int stm32_rproc_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct stm32_rproc *ddata;
struct device_node *np = dev->of_node;
+ const struct of_device_id *of_id;
+ const struct stm32_rproc_conf *conf;
struct rproc *rproc;
unsigned int state;
int ret;
@@ -774,12 +879,18 @@ static int stm32_rproc_probe(struct platform_device *pdev)
if (ret)
return ret;
- rproc = rproc_alloc(dev, np->name, &st_rproc_ops, NULL, sizeof(*ddata));
+ of_id = of_match_device(stm32_rproc_match, &pdev->dev);
+ if (of_id)
+ conf = (const struct stm32_rproc_conf *)of_id->data;
+ else
+ return -EINVAL;
+
+ rproc = rproc_alloc(dev, np->name, conf->ops, NULL, sizeof(*ddata));
if (!rproc)
return -ENOMEM;
ddata = rproc->priv;
-
+ ddata->secured_fw = conf->secured_fw;
rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE);
ret = stm32_rproc_parse_dt(pdev, ddata, &rproc->auto_boot);
@@ -820,12 +931,25 @@ static int stm32_rproc_probe(struct platform_device *pdev)
if (ret)
goto free_wkq;
+ if (ddata->secured_fw) {
+ ddata->trproc = tee_rproc_register(dev, rproc,
+ STM32_MP1_FW_ID);
+ if (IS_ERR(ddata->trproc)) {
+ ret = PTR_ERR(ddata->trproc);
+ dev_err_probe(dev, ret, "TEE rproc device not found\n");
+ goto free_mb;
+ }
+ }
+
ret = rproc_add(rproc);
if (ret)
- goto free_mb;
+ goto free_tee;
return 0;
+free_tee:
+ if (ddata->trproc)
+ tee_rproc_unregister(ddata->trproc);
free_mb:
stm32_rproc_free_mbox(rproc);
free_wkq:
@@ -851,6 +975,8 @@ static int stm32_rproc_remove(struct platform_device *pdev)
rproc_shutdown(rproc);
rproc_del(rproc);
+ if (ddata->trproc)
+ tee_rproc_unregister(ddata->trproc);
stm32_rproc_free_mbox(rproc);
destroy_workqueue(ddata->workqueue);
@@ -863,6 +989,15 @@ static int stm32_rproc_remove(struct platform_device *pdev)
return 0;
}
+static void stm32_rproc_shutdown(struct platform_device *pdev)
+{
+ struct rproc *rproc = platform_get_drvdata(pdev);
+
+ if (atomic_read(&rproc->power) > 0)
+ dev_warn(&pdev->dev,
+ "Warning: remote fw is still running with possible side effect!!!\n");
+}
+
static int __maybe_unused stm32_rproc_suspend(struct device *dev)
{
struct rproc *rproc = dev_get_drvdata(dev);
@@ -891,6 +1026,7 @@ static SIMPLE_DEV_PM_OPS(stm32_rproc_pm_ops,
static struct platform_driver stm32_rproc_driver = {
.probe = stm32_rproc_probe,
.remove = stm32_rproc_remove,
+ .shutdown = stm32_rproc_shutdown,
.driver = {
.name = "stm32-rproc",
.pm = &stm32_rproc_pm_ops,
diff --git a/drivers/remoteproc/tee_remoteproc.c b/drivers/remoteproc/tee_remoteproc.c
new file mode 100644
index 000000000000..67d924c95871
--- /dev/null
+++ b/drivers/remoteproc/tee_remoteproc.c
@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) STMicroelectronics 2020 - All Rights Reserved
+ * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com>
+ */
+
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/remoteproc.h>
+#include <linux/slab.h>
+#include <linux/tee_drv.h>
+#include <linux/tee_remoteproc.h>
+
+#include "remoteproc_internal.h"
+
+#define MAX_TEE_PARAM_ARRY_MEMBER 4
+
+/*
+ * Authentication of the firmware and load in the remote processor memory
+ *
+ * [in] params[0].value.a: unique 32bit identifier of the firmware
+ * [in] params[1].memref: buffer containing the image of the buffer
+ */
+#define TA_RPROC_FW_CMD_LOAD_FW 1
+
+/*
+ * start the remote processor
+ *
+ * [in] params[0].value.a: unique 32bit identifier of the firmware
+ */
+#define TA_RPROC_FW_CMD_START_FW 2
+
+/*
+ * stop the remote processor
+ *
+ * [in] params[0].value.a: unique 32bit identifier of the firmware
+ */
+#define TA_RPROC_FW_CMD_STOP_FW 3
+
+/*
+ * return the address of the resource table, or 0 if not found
+ * No chech is done to verify that the address returned is accessible by
+ * the non secure context. If the resource table is loaded in a protected
+ * memory the acces by the non secure context will lead to a data abort.
+ *
+ * [in] params[0].value.a: unique 32bit identifier of the firmware
+ * [out] params[1].value.a: 32bit LSB resource table memory address
+ * [out] params[1].value.b: 32bit MSB resource table memory address
+ * [out] params[2].value.a: 32bit LSB resource table memory size
+ * [out] params[2].value.b: 32bit MSB resource table memory size
+ */
+#define TA_RPROC_FW_CMD_GET_RSC_TABLE 4
+
+/*
+ * return the address of the core dump
+ *
+ * [in] params[0].value.a: unique 32bit identifier of the firmware
+ * [out] params[1].memref: address of the core dump image if exist,
+ * else return Null
+ */
+#define TA_RPROC_FW_CMD_GET_COREDUMP 5
+
+struct tee_rproc_mem {
+ char name[20];
+ void __iomem *cpu_addr;
+ phys_addr_t bus_addr;
+ u32 dev_addr;
+ size_t size;
+};
+
+struct tee_rproc_context {
+ struct list_head sessions;
+ struct tee_context *ctx;
+ struct device *dev;
+};
+
+struct tee_rproc_context pvt_data;
+
+static void prepare_args(struct tee_rproc *trproc, int cmd,
+ struct tee_ioctl_invoke_arg *arg,
+ struct tee_param *param, unsigned int num_params)
+{
+ memset(arg, 0, sizeof(*arg));
+ memset(param, 0, MAX_TEE_PARAM_ARRY_MEMBER * sizeof(*param));
+
+ arg->func = cmd;
+ arg->session = trproc->session_id;
+ arg->num_params = num_params + 1;
+
+ param[0] = (struct tee_param) {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
+ .u.value.a = trproc->fw_id,
+ };
+}
+
+int tee_rproc_load_fw(struct tee_rproc *trproc, const struct firmware *fw)
+{
+ struct tee_ioctl_invoke_arg arg;
+ struct tee_param param[MAX_TEE_PARAM_ARRY_MEMBER];
+ struct tee_shm *fw_shm;
+ int ret;
+
+ /*
+ * useless copy waiting that tee_shm_register and tee well support
+ * kernel buffers registration
+ */
+
+ fw_shm = tee_shm_alloc(pvt_data.ctx, fw->size,
+ TEE_SHM_MAPPED | TEE_SHM_DMA_BUF);
+ if (IS_ERR(fw_shm))
+ return PTR_ERR(fw_shm);
+
+ memcpy(tee_shm_get_va(fw_shm, 0), fw->data, fw->size);
+
+ prepare_args(trproc, TA_RPROC_FW_CMD_LOAD_FW, &arg, param, 1);
+
+ /* provide the address of the firmware image */
+ param[1] = (struct tee_param) {
+ .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
+ .u.memref = {
+ .shm = fw_shm,
+ .size = fw->size,
+ .shm_offs = 0,
+ },
+ };
+
+ ret = tee_client_invoke_func(pvt_data.ctx, &arg, param);
+ if (ret < 0 || arg.ret != 0) {
+ dev_err(pvt_data.dev,
+ "TA_RPROC_FW_CMD_LOAD_FW invoke failed TEE err: %x, ret:%x\n",
+ arg.ret, ret);
+ if (!ret)
+ ret = -EIO;
+ }
+
+ tee_shm_free(fw_shm);
+
+ return ret;
+}
+EXPORT_SYMBOL(tee_rproc_load_fw);
+
+int rproc_tee_get_rsc_table(struct tee_rproc *trproc)
+{
+ struct tee_ioctl_invoke_arg arg;
+ struct tee_param param[MAX_TEE_PARAM_ARRY_MEMBER];
+ struct rproc *rproc = trproc->rproc;
+ size_t rsc_size;
+ int ret;
+
+ prepare_args(trproc, TA_RPROC_FW_CMD_GET_RSC_TABLE, &arg, param, 2);
+
+ param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT;
+ param[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT;
+
+ ret = tee_client_invoke_func(pvt_data.ctx, &arg, param);
+ if (ret < 0 || arg.ret != 0) {
+ dev_err(pvt_data.dev,
+ "TA_RPROC_FW_CMD_GET_RSC_TABLE invoke failed TEE err: %x, ret:%x\n",
+ arg.ret, ret);
+ return -EIO;
+ }
+
+ rsc_size = param[2].u.value.a;
+
+ /*
+ * Store the resource table address that would be updated by the remote
+ * core and the virtio.
+ */
+ trproc->rsc_va = ioremap_wc(param[1].u.value.a, rsc_size);
+ if (IS_ERR_OR_NULL(trproc->rsc_va)) {
+ dev_err(pvt_data.dev, "Unable to map memory region: %lld+%zx\n",
+ param[1].u.value.a, rsc_size);
+ trproc->rsc_va = NULL;
+ return -ENOMEM;
+ }
+
+ /*
+ * A cached table is requested as the physical address is not mapped yet
+ * but remoteproc need to parse the table for resources.
+ */
+ rproc->cached_table = kmemdup(trproc->rsc_va, rsc_size, GFP_KERNEL);
+ if (!rproc->cached_table)
+ return -ENOMEM;
+
+ rproc->table_ptr = rproc->cached_table;
+ rproc->table_sz = rsc_size;
+
+ return 0;
+}
+EXPORT_SYMBOL(rproc_tee_get_rsc_table);
+
+struct resource_table *tee_rproc_get_loaded_rsc_table(struct tee_rproc *trproc)
+{
+ return (struct resource_table *)trproc->rsc_va;
+}
+EXPORT_SYMBOL(tee_rproc_get_loaded_rsc_table);
+
+int tee_rproc_start(struct tee_rproc *trproc)
+{
+ struct tee_ioctl_invoke_arg arg;
+ struct tee_param param[MAX_TEE_PARAM_ARRY_MEMBER];
+ int ret;
+
+ prepare_args(trproc, TA_RPROC_FW_CMD_START_FW, &arg, param, 0);
+
+ ret = tee_client_invoke_func(pvt_data.ctx, &arg, param);
+ if (ret < 0 || arg.ret != 0) {
+ dev_err(pvt_data.dev,
+ "TA_RPROC_FW_CMD_START_FW invoke failed TEE err: %x, ret:%x\n",
+ arg.ret, ret);
+ if (!ret)
+ ret = -EIO;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(tee_rproc_start);
+
+int tee_rproc_stop(struct tee_rproc *trproc)
+{
+ struct tee_ioctl_invoke_arg arg;
+ struct tee_param param[MAX_TEE_PARAM_ARRY_MEMBER];
+ int ret;
+
+ prepare_args(trproc, TA_RPROC_FW_CMD_STOP_FW, &arg, param, 0);
+
+ ret = tee_client_invoke_func(pvt_data.ctx, &arg, param);
+ if (ret < 0 || arg.ret != 0) {
+ dev_err(pvt_data.dev,
+ "TA_RPROC_FW_CMD_STOP_FW invoke failed TEE err: %x, ret:%x\n",
+ arg.ret, ret);
+ if (!ret)
+ ret = -EIO;
+ }
+ if (trproc->rsc_va)
+ iounmap(trproc->rsc_va);
+ trproc->rsc_va = NULL;
+
+ return ret;
+}
+EXPORT_SYMBOL(tee_rproc_stop);
+
+static const struct tee_client_device_id stm32_tee_fw_id_table[] = {
+ {UUID_INIT(0x80a4c275, 0x0a47, 0x4905,
+ 0x82, 0x85, 0x14, 0x86, 0xa9, 0x77, 0x1a, 0x08)},
+ {}
+};
+
+struct tee_rproc *tee_rproc_register(struct device *dev, struct rproc *rproc,
+ unsigned int fw_id)
+{
+ struct tee_client_device *rproc_tee_device;
+ struct tee_ioctl_open_session_arg sess_arg;
+ struct tee_rproc *trproc;
+ int ret;
+
+ if (!pvt_data.ctx)
+ return ERR_PTR(-ENODEV);
+
+ trproc = devm_kzalloc(dev, sizeof(*trproc), GFP_KERNEL);
+ if (!trproc)
+ return ERR_PTR(-ENOMEM);
+
+ rproc_tee_device = to_tee_client_device(pvt_data.dev);
+ memset(&sess_arg, 0, sizeof(sess_arg));
+
+ /* Open session with rproc_tee load Trusted App */
+ memcpy(sess_arg.uuid, rproc_tee_device->id.uuid.b, TEE_IOCTL_UUID_LEN);
+
+ /*
+ * TODO: should we replace TEE_IOCTL_LOGIN_PUBLIC by
+ * TEE_IOCTL_LOGIN_REE_KERNEL?
+ */
+ sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC;
+ sess_arg.num_params = 0;
+
+ ret = tee_client_open_session(pvt_data.ctx, &sess_arg, NULL);
+ if (ret < 0 || sess_arg.ret != 0) {
+ dev_err(dev, "tee_client_open_session failed, err: %x\n",
+ sess_arg.ret);
+ return ERR_PTR(ret);
+ }
+
+ trproc->rproc = rproc;
+ trproc->parent = dev;
+ trproc->fw_id = fw_id;
+ trproc->session_id = sess_arg.session;
+
+ list_add_tail(&trproc->node, &pvt_data.sessions);
+
+ return trproc;
+}
+EXPORT_SYMBOL(tee_rproc_register);
+
+int tee_rproc_unregister(struct tee_rproc *trproc)
+{
+ int ret;
+
+ if (!pvt_data.ctx)
+ return -ENODEV;
+
+ ret = tee_client_close_session(pvt_data.ctx, trproc->session_id);
+ if (ret < 0) {
+ dev_err(trproc->parent,
+ "tee_client_close_session failed, err: %x\n", ret);
+ }
+
+ list_del(&trproc->node);
+
+ return ret;
+}
+EXPORT_SYMBOL(tee_rproc_unregister);
+
+static int tee_ctx_match(struct tee_ioctl_version_data *ver, const void *data)
+{
+ /* Today we support only the OP-TEE, could be extend to other tees */
+ return (ver->impl_id == TEE_IMPL_ID_OPTEE);
+}
+
+static int tee_rproc_probe(struct device *dev)
+{
+ /* Open context with TEE driver */
+ pvt_data.ctx = tee_client_open_context(NULL, tee_ctx_match, NULL,
+ NULL);
+ if (IS_ERR(pvt_data.ctx))
+ return -ENODEV;
+
+ pvt_data.dev = dev;
+ INIT_LIST_HEAD(&pvt_data.sessions);
+
+ return 0;
+}
+
+static int tee_rproc_remove(struct device *dev)
+{
+ struct tee_rproc *entry, *tmp;
+
+ list_for_each_entry_safe(entry, tmp, &pvt_data.sessions, node) {
+ tee_client_close_session(pvt_data.ctx, entry->session_id);
+ list_del(&entry->node);
+ kfree(entry);
+ }
+
+ tee_client_close_context(pvt_data.ctx);
+ return 0;
+}
+
+MODULE_DEVICE_TABLE(tee, stm32_tee_fw_id_table);
+
+static struct tee_client_driver tee_rproc_fw_driver = {
+ .id_table = stm32_tee_fw_id_table,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .bus = &tee_bus_type,
+ .probe = tee_rproc_probe,
+ .remove = tee_rproc_remove,
+ },
+};
+
+static int __init tee_rproc_fw_mod_init(void)
+{
+ return driver_register(&tee_rproc_fw_driver.driver);
+}
+
+static void __exit tee_rproc_fw_mod_exit(void)
+{
+ driver_unregister(&tee_rproc_fw_driver.driver);
+}
+
+module_init(tee_rproc_fw_mod_init);
+module_exit(tee_rproc_fw_mod_exit);
+
+MODULE_DESCRIPTION("secure remote processor control driver");
+MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig
index f96716893c2a..7c8053aa968e 100644
--- a/drivers/rpmsg/Kconfig
+++ b/drivers/rpmsg/Kconfig
@@ -64,4 +64,13 @@ config RPMSG_VIRTIO
select RPMSG
select VIRTIO
+config RPMSG_TTY
+ tristate "RPMSG tty driver"
+ depends on RPMSG
+ help
+ Say y here to export rpmsg endpoints as tty console, usually found
+ in /dev/ttyRPMSG.
+ This makes it possible for user-space programs to send and receive
+ rpmsg messages as a standard tty protocol.
+
endmenu
diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile
index ffe932ef6050..26a197365679 100644
--- a/drivers/rpmsg/Makefile
+++ b/drivers/rpmsg/Makefile
@@ -7,4 +7,5 @@ obj-$(CONFIG_RPMSG_QCOM_GLINK) += qcom_glink.o
obj-$(CONFIG_RPMSG_QCOM_GLINK_RPM) += qcom_glink_rpm.o
obj-$(CONFIG_RPMSG_QCOM_GLINK_SMEM) += qcom_glink_smem.o
obj-$(CONFIG_RPMSG_QCOM_SMD) += qcom_smd.o
+obj-$(CONFIG_RPMSG_TTY) += rpmsg_tty.o
obj-$(CONFIG_RPMSG_VIRTIO) += virtio_rpmsg_bus.o
diff --git a/drivers/rpmsg/rpmsg_core.c b/drivers/rpmsg/rpmsg_core.c
index 91de940896e3..380500872352 100644
--- a/drivers/rpmsg/rpmsg_core.c
+++ b/drivers/rpmsg/rpmsg_core.c
@@ -283,6 +283,25 @@ int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
}
EXPORT_SYMBOL(rpmsg_trysend_offchannel);
+/**
+ * rpmsg_get_buffer_size()
+ * This function returns buffer size available for sending messages.
+ *
+ * @ept: the rpmsg endpoint
+ *
+ * Returns buffer size on success and an appropriate error value on failure.
+ */
+int rpmsg_get_buffer_size(struct rpmsg_endpoint *ept)
+{
+ if (WARN_ON(!ept))
+ return -EINVAL;
+ if (!ept->ops->get_buffer_size)
+ return -ENXIO;
+
+ return ept->ops->get_buffer_size(ept);
+}
+EXPORT_SYMBOL(rpmsg_get_buffer_size);
+
/*
* match a rpmsg channel with a channel info struct.
* this is used to make sure we're not creating rpmsg devices for channels
diff --git a/drivers/rpmsg/rpmsg_internal.h b/drivers/rpmsg/rpmsg_internal.h
index 3fc83cd50e98..244292540e58 100644
--- a/drivers/rpmsg/rpmsg_internal.h
+++ b/drivers/rpmsg/rpmsg_internal.h
@@ -47,6 +47,7 @@ struct rpmsg_device_ops {
* @trysendto: see @rpmsg_trysendto(), optional
* @trysend_offchannel: see @rpmsg_trysend_offchannel(), optional
* @poll: see @rpmsg_poll(), optional
+ * @get_buffer_size: see @rpmsg_get_buffer_size(), optional
*
* Indirection table for the operations that a rpmsg backend should implement.
* In addition to @destroy_ept, the backend must at least implement @send and
@@ -66,6 +67,7 @@ struct rpmsg_endpoint_ops {
void *data, int len);
__poll_t (*poll)(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait);
+ int (*get_buffer_size)(struct rpmsg_endpoint *ept);
};
int rpmsg_register_device(struct rpmsg_device *rpdev);
diff --git a/drivers/rpmsg/rpmsg_tty.c b/drivers/rpmsg/rpmsg_tty.c
new file mode 100644
index 000000000000..b7bd1196630d
--- /dev/null
+++ b/drivers/rpmsg/rpmsg_tty.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Arnaud Pouliquen <arnaud.pouliquen@st.com> for STMicroelectronics.
+ * Derived from the imx-rmpsg and omap-rpmsg implementations.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/virtio.h>
+
+#define MAX_TTY_RPMSG_INDEX 32 /* Should be enough for a while */
+
+static LIST_HEAD(rpmsg_tty_list); /* tty instances list */
+static DEFINE_MUTEX(rpmsg_tty_lock); /* protect tty list */
+
+static struct tty_driver *rpmsg_tty_driver;
+static struct tty_port_operations rpmsg_tty_port_ops = { };
+
+struct rpmsg_tty_port {
+ struct tty_port *port; /* TTY port data */
+ struct list_head list; /* TTY device list */
+ u32 id; /* tty rpmsg index */
+ spinlock_t rx_lock; /* message reception lock */
+ struct rpmsg_device *rpdev; /* handle rpmsg device */
+};
+
+static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ int space;
+ unsigned char *cbuf;
+ struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
+
+ /* Flush the recv-ed none-zero data to tty node */
+ if (len == 0)
+ return -EINVAL;
+
+ dev_dbg(&rpdev->dev, "msg(<- src 0x%x) len %d\n", src, len);
+
+ print_hex_dump_debug(__func__, DUMP_PREFIX_NONE, 16, 1, data, len,
+ true);
+
+ spin_lock(&cport->rx_lock);
+ space = tty_prepare_flip_string(cport->port, &cbuf, len);
+ if (space <= 0) {
+ dev_err(&rpdev->dev, "No memory for tty_prepare_flip_string\n");
+ spin_unlock(&cport->rx_lock);
+ return -ENOMEM;
+ }
+
+ if (space != len)
+ dev_warn(&rpdev->dev, "Trunc buffer from %d to %d\n",
+ len, space);
+
+ memcpy(cbuf, data, space);
+ tty_flip_buffer_push(cport->port);
+ spin_unlock(&cport->rx_lock);
+
+ return 0;
+}
+
+static struct rpmsg_tty_port *rpmsg_tty_get(unsigned int index)
+{
+ struct rpmsg_tty_port *cport;
+
+ mutex_lock(&rpmsg_tty_lock);
+ list_for_each_entry(cport, &rpmsg_tty_list, list) {
+ if (index == cport->id) {
+ mutex_unlock(&rpmsg_tty_lock);
+ return cport;
+ }
+ }
+ mutex_unlock(&rpmsg_tty_lock);
+
+ return NULL;
+}
+
+static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+ struct rpmsg_tty_port *cport = rpmsg_tty_get(tty->index);
+
+ if (!cport)
+ return -ENODEV;
+
+ return tty_port_install(cport->port, driver, tty);
+}
+
+static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ struct rpmsg_tty_port *cport = rpmsg_tty_get(tty->index);
+
+ if (!cport)
+ return -ENODEV;
+
+ return tty_port_open(tty->port, tty, filp);
+}
+
+static void rpmsg_tty_close(struct tty_struct *tty, struct file *filp)
+{
+ tty_port_close(tty->port, tty, filp);
+}
+
+static int rpmsg_tty_write(struct tty_struct *tty, const unsigned char *buf,
+ int total)
+{
+ int count, ret = 0;
+ const unsigned char *tbuf;
+ struct rpmsg_tty_port *cport = rpmsg_tty_get(tty->index);
+
+ struct rpmsg_device *rpdev = cport->rpdev;
+ int msg_size;
+
+ dev_dbg(&rpdev->dev, "%s: send message from tty->index = %d\n",
+ __func__, tty->index);
+
+ if (!buf) {
+ dev_err(&rpdev->dev, "buf shouldn't be null.\n");
+ return -ENOMEM;
+ }
+
+ msg_size = rpmsg_get_buffer_size(rpdev->ept);
+ if (msg_size < 0)
+ return msg_size;
+
+ count = total;
+ tbuf = buf;
+ do {
+ /* send a message to our remote processor */
+ ret = rpmsg_trysend(rpdev->ept, (void *)tbuf,
+ count > msg_size ? msg_size : count);
+ if (ret) {
+ dev_dbg(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
+ return 0;
+ }
+
+ if (count > msg_size) {
+ count -= msg_size;
+ tbuf += msg_size;
+ } else {
+ count = 0;
+ }
+ } while (count > 0);
+
+ tty_port_tty_wakeup(cport->port);
+
+ return total;
+}
+
+static int rpmsg_tty_write_room(struct tty_struct *tty)
+{
+ struct rpmsg_tty_port *cport = rpmsg_tty_get(tty->index);
+ struct rpmsg_device *rpdev = cport->rpdev;
+
+ /* report the space in the rpmsg buffer */
+ return rpmsg_get_buffer_size(rpdev->ept);
+}
+
+static const struct tty_operations rpmsg_tty_ops = {
+ .install = rpmsg_tty_install,
+ .open = rpmsg_tty_open,
+ .close = rpmsg_tty_close,
+ .write = rpmsg_tty_write,
+ .write_room = rpmsg_tty_write_room,
+};
+
+static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_tty_port *cport, *tmp;
+ unsigned int index;
+ struct device *tty_dev;
+ int ret = 0;
+
+ cport = devm_kzalloc(&rpdev->dev, sizeof(*cport), GFP_KERNEL);
+ if (!cport)
+ return -ENOMEM;
+
+ mutex_lock(&rpmsg_tty_lock);
+
+ /* Get free index */
+ for (index = 0; index < MAX_TTY_RPMSG_INDEX; index++) {
+ bool id_found = false;
+
+ list_for_each_entry(tmp, &rpmsg_tty_list, list) {
+ if (index == tmp->id) {
+ id_found = true;
+ break;
+ }
+ }
+ if (!id_found)
+ break;
+ }
+
+ if (index >= MAX_TTY_RPMSG_INDEX) {
+ ret = -ENOSPC;
+ goto end_probe;
+ }
+
+ /*
+ * the tty port allocation has to be independent from the tty device
+ * The reason is that it can be use after device removing, for instance
+ * if a user has opened it.
+ * So it is not possible to release the port on device remove.
+ * A solution is to store the port in the driver structure. The port
+ * structure is reused on next probe to save memory.
+ */
+ if (!rpmsg_tty_driver->ports[index]) {
+ /* first allocation of the port associated to the index */
+ rpmsg_tty_driver->ports[index] = kzalloc(sizeof(*cport->port),
+ GFP_KERNEL);
+ if (!rpmsg_tty_driver->ports[index]) {
+ ret = -ENOMEM;
+ goto end_probe;
+ }
+ }
+
+ cport->port = rpmsg_tty_driver->ports[index];
+ tty_port_init(cport->port);
+ cport->port->ops = &rpmsg_tty_port_ops;
+
+ spin_lock_init(&cport->rx_lock);
+ cport->port->low_latency = cport->port->flags | ASYNC_LOW_LATENCY;
+ cport->rpdev = rpdev;
+
+ tty_dev = tty_port_register_device(cport->port, rpmsg_tty_driver,
+ index, &rpdev->dev);
+ if (IS_ERR(tty_dev)) {
+ dev_err(&rpdev->dev, "failed to register tty port\n");
+ tty_port_destroy(cport->port);
+ ret = PTR_ERR(tty_dev);
+ goto end_probe;
+ }
+
+ cport->id = index;
+ list_add_tail(&cport->list, &rpmsg_tty_list);
+ dev_set_drvdata(&rpdev->dev, cport);
+
+ dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x : ttyRPMSG%d\n",
+ rpdev->src, rpdev->dst, index);
+end_probe:
+ mutex_unlock(&rpmsg_tty_lock);
+
+ return ret;
+}
+
+static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
+{
+ struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
+ struct tty_struct *tty;
+
+ tty_unregister_device(rpmsg_tty_driver, cport->id);
+ tty = tty_port_tty_get(cport->port);
+ if (tty) {
+ tty_vhangup(cport->port->tty);
+ tty_kref_put(tty);
+ }
+ list_del(&cport->list);
+
+ dev_info(&rpdev->dev, "rpmsg tty device %d is removed\n", cport->id);
+}
+
+static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
+ { .name = "rpmsg-tty-channel" },
+ { },
+};
+MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_tty_id_table);
+
+static struct rpmsg_driver rpmsg_tty_rmpsg_drv = {
+ .drv.name = KBUILD_MODNAME,
+ .drv.owner = THIS_MODULE,
+ .id_table = rpmsg_driver_tty_id_table,
+ .probe = rpmsg_tty_probe,
+ .callback = rpmsg_tty_cb,
+ .remove = rpmsg_tty_remove,
+};
+
+static int __init rpmsg_tty_init(void)
+{
+ int err;
+
+ rpmsg_tty_driver = tty_alloc_driver(MAX_TTY_RPMSG_INDEX, 0);
+ if (IS_ERR(rpmsg_tty_driver))
+ return PTR_ERR(rpmsg_tty_driver);
+
+ rpmsg_tty_driver->driver_name = "rpmsg_tty";
+ rpmsg_tty_driver->name = "ttyRPMSG";
+ rpmsg_tty_driver->major = TTYAUX_MAJOR;
+ rpmsg_tty_driver->minor_start = 3;
+ rpmsg_tty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
+ rpmsg_tty_driver->init_termios = tty_std_termios;
+ rpmsg_tty_driver->flags = TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_DYNAMIC_DEV;
+
+ tty_set_operations(rpmsg_tty_driver, &rpmsg_tty_ops);
+
+ /* Disable unused mode by default */
+ rpmsg_tty_driver->init_termios = tty_std_termios;
+ rpmsg_tty_driver->init_termios.c_lflag &= ~(ECHO | ICANON);
+ rpmsg_tty_driver->init_termios.c_oflag &= ~(OPOST | ONLCR);
+
+ err = tty_register_driver(rpmsg_tty_driver);
+ if (err < 0) {
+ pr_err("Couldn't install rpmsg tty driver: err %d\n", err);
+ goto tty_error;
+ }
+ err = register_rpmsg_driver(&rpmsg_tty_rmpsg_drv);
+
+ if (!err)
+ return 0;
+
+ tty_unregister_driver(rpmsg_tty_driver);
+
+tty_error:
+ put_tty_driver(rpmsg_tty_driver);
+
+ return err;
+}
+
+static void __exit rpmsg_tty_exit(void)
+{
+ unsigned int index;
+
+ unregister_rpmsg_driver(&rpmsg_tty_rmpsg_drv);
+ tty_unregister_driver(rpmsg_tty_driver);
+
+ /* release port allocations */
+ for (index = 0; index < MAX_TTY_RPMSG_INDEX; index++)
+ kfree(rpmsg_tty_driver->ports[index]);
+ put_tty_driver(rpmsg_tty_driver);
+}
+
+module_init(rpmsg_tty_init);
+module_exit(rpmsg_tty_exit);
+
+MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
+MODULE_DESCRIPTION("virtio remote processor messaging tty driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c
index 7d7ed4e5cce7..b8e60b34c258 100644
--- a/drivers/rpmsg/virtio_rpmsg_bus.c
+++ b/drivers/rpmsg/virtio_rpmsg_bus.c
@@ -181,6 +181,7 @@ static int virtio_rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data,
int len, u32 dst);
static int virtio_rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src,
u32 dst, void *data, int len);
+static int virtio_get_buffer_size(struct rpmsg_endpoint *ept);
static const struct rpmsg_endpoint_ops virtio_endpoint_ops = {
.destroy_ept = virtio_rpmsg_destroy_ept,
@@ -190,6 +191,7 @@ static const struct rpmsg_endpoint_ops virtio_endpoint_ops = {
.trysend = virtio_rpmsg_trysend,
.trysendto = virtio_rpmsg_trysendto,
.trysend_offchannel = virtio_rpmsg_trysend_offchannel,
+ .get_buffer_size = virtio_get_buffer_size,
};
/**
@@ -705,6 +707,15 @@ static int virtio_rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src,
return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, false);
}
+static int virtio_get_buffer_size(struct rpmsg_endpoint *ept)
+{
+ struct rpmsg_device *rpdev = ept->rpdev;
+ struct virtio_rpmsg_channel *vch = to_virtio_rpmsg_channel(rpdev);
+ struct virtproc_info *vrp = vch->vrp;
+
+ return vrp->buf_size - sizeof(struct rpmsg_hdr);
+}
+
static int rpmsg_recv_single(struct virtproc_info *vrp, struct device *dev,
struct rpmsg_hdr *msg, unsigned int len)
{
diff --git a/include/linux/mailbox/arm-smccc-mbox.h b/include/linux/mailbox/arm-smccc-mbox.h
new file mode 100644
index 000000000000..d35fb89a77f5
--- /dev/null
+++ b/include/linux/mailbox/arm-smccc-mbox.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _LINUX_ARM_SMCCC_MBOX_H_
+#define _LINUX_ARM_SMCCC_MBOX_H_
+
+#include <linux/types.h>
+
+/**
+ * struct arm_smccc_mbox_cmd - ARM SMCCC message structure
+ * @args_smccc32/64: actual usage of registers is up to the protocol
+ * (within the SMCCC limits)
+ */
+struct arm_smccc_mbox_cmd {
+ union {
+ u32 args_smccc32[6];
+ u64 args_smccc64[6];
+ };
+};
+
+#endif /* _LINUX_ARM_SMCCC_MBOX_H_ */
diff --git a/include/linux/rpmsg.h b/include/linux/rpmsg.h
index 9fe156d1c018..2af7674035aa 100644
--- a/include/linux/rpmsg.h
+++ b/include/linux/rpmsg.h
@@ -135,6 +135,7 @@ int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
__poll_t rpmsg_poll(struct rpmsg_endpoint *ept, struct file *filp,
poll_table *wait);
+int rpmsg_get_buffer_size(struct rpmsg_endpoint *ept);
#else
static inline int register_rpmsg_device(struct rpmsg_device *dev)
@@ -242,6 +243,14 @@ static inline __poll_t rpmsg_poll(struct rpmsg_endpoint *ept,
return 0;
}
+static int rpmsg_get_buffer_size(struct rpmsg_endpoint *ept)
+{
+ /* This shouldn't be possible */
+ WARN_ON(1);
+
+ return -ENXIO;
+}
+
#endif /* IS_ENABLED(CONFIG_RPMSG) */
/* use a macro to avoid include chaining to get THIS_MODULE */
diff --git a/include/linux/tee_remoteproc.h b/include/linux/tee_remoteproc.h
new file mode 100644
index 000000000000..5d2d6ae492d0
--- /dev/null
+++ b/include/linux/tee_remoteproc.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright(c) 2020 STMicroelectronics 2020
+ */
+
+#ifndef TEE_REMOTEPROC_H
+#define TEE_REMOTEPROC_H
+
+#include <linux/remoteproc.h>
+#include <linux/tee_drv.h>
+
+/**
+ * struct tee_rproc - TEE remoteproc structure
+ * @node: Reference in list
+ * @rproc: Remoteproc reference
+ * @parent: Parent device
+ * @fw_id: Identifier of the target firmware
+ * @session_id: TEE session identifier
+ * @rsc_va: Resource table virtual address.
+ */
+struct tee_rproc {
+ struct list_head node;
+
+ struct rproc *rproc;
+ struct device *parent;
+ u32 fw_id;
+ u32 session_id;
+ void *rsc_va;
+};
+
+#if IS_ENABLED(CONFIG_TEE_REMOTEPROC)
+
+struct tee_rproc *tee_rproc_register(struct device *dev, struct rproc *rproc,
+ unsigned int fw_id);
+int tee_rproc_unregister(struct tee_rproc *trproc);
+
+int tee_rproc_load_fw(struct tee_rproc *trproc, const struct firmware *fw);
+int rproc_tee_get_rsc_table(struct tee_rproc *trproc);
+struct resource_table *tee_rproc_get_loaded_rsc_table(struct tee_rproc *trproc);
+int tee_rproc_start(struct tee_rproc *trproc);
+int tee_rproc_stop(struct tee_rproc *trproc);
+
+#else
+
+static inline struct tee_rproc *tee_rproc_register(struct device *dev,
+ struct rproc *rproc,
+ unsigned int fw_id)
+{
+ /* This shouldn't be possible */
+ WARN_ON(1);
+
+ return NULL;
+}
+
+static inline int tee_rproc_unregister(struct tee_rproc *trproc)
+{
+ /* This shouldn't be possible */
+ WARN_ON(1);
+
+ return 0;
+}
+
+static inline int tee_rproc_load_fw(struct tee_rproc *trproc,
+ const struct firmware *fw)
+{
+ /* This shouldn't be possible */
+ WARN_ON(1);
+
+ return 0;
+}
+
+static inline int tee_rproc_start(struct tee_rproc *trproc)
+{
+ /* This shouldn't be possible */
+ WARN_ON(1);
+
+ return 0;
+}
+
+static inline int tee_rproc_stop(struct tee_rproc *trproc)
+{
+ /* This shouldn't be possible */
+ WARN_ON(1);
+
+ return 0;
+}
+
+static inline int rproc_tee_get_rsc_table(struct tee_rproc *trproc)
+{
+ /* This shouldn't be possible */
+ WARN_ON(1);
+
+ return 0;
+}
+
+static inline struct resource_table *
+ tee_rproc_get_loaded_rsc_table(struct tee_rproc *trproc)
+{
+ /* This shouldn't be possible */
+ WARN_ON(1);
+
+ return NULL;
+}
+
+#endif /* CONFIG_TEE_REMOTEPROC */
+#endif /* TEE_REMOTEPROC_H */
--
2.17.1