* [PATCH RFC 0/5] tee: optee: add RISC-V RPMI TEE transport
@ 2026-09-12 10:15 Amirreza Zarrabi
2026-09-12 10:15 ` [PATCH RFC 1/5] optee: riscv: add RPMI TEE service group transport Amirreza Zarrabi
` (4 more replies)
0 siblings, 5 replies; 10+ messages in thread
From: Amirreza Zarrabi @ 2026-09-12 10:15 UTC (permalink / raw)
To: Jens Wiklander, Sumit Garg, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Rahul Pathak, Anup Patel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-kernel, op-tee, linux-riscv, devicetree,
Amirreza Zarrabi
This RFC adds initial support for communicating with OP-TEE on RISC-V
using the RPMI TEE service group [1] over the SBI MPXY [2]
mailbox framework.
The intention is to provide a transport for RISC-V systems similar in
purpose to the FF-A transport currently used by OP-TEE on Arm systems.
Linux and OP-TEE act as endpoints of the RPMI TEE service group, while
the RPMI framework in machine-mode firmware mediates communication
between them.
The implementation closely follows the existing OP-TEE FF-A backend in
drivers/tee/optee/ffa_abi.c. Where possible, the same overall design and
lifetime rules are preserved, with FF-A operations mapped to equivalent
RPMI TEE services.
For shared memory, FF-A memory handles are replaced by RPMI memory parcel
identifiers. Linux creates an RPMI memory parcel for shared memory and
makes OP-TEE a receiver of that parcel. The parcel identifier is then
used in OP-TEE message parameters in a similar way to the FF-A global
memory handle.
RPMI TEE_CALL is used as the entry mechanism into OP-TEE, serving a role
similar to the FF-A direct-message path. The request and response contain
a small register-like payload carrying the OP-TEE call information and
state required to resume yielding calls. The existing OP-TEE call queue,
RPC handling and shared-memory infrastructure are reused as much as
possible.
The transport uses one SBI MPXY mailbox channel per hart. A TEE call is
issued using the channel corresponding to the CPU on which the call is
running. This preserves the execution model where entering OP-TEE on a
hart results in secure-world execution associated with that hart.
Shared memory is implemented using RPMI TEE memory parcels. The driver
maintains a mapping between parcel identifiers and struct tee_shm objects
and uses parcel create and reclaim operations to manage the lifetime of
memory shared with OP-TEE.
Asynchronous notifications are implemented using the RPMI TEE signal
bus. The RPMI TEE service group does not provide framework notification
events corresponding directly to the OP-TEE asynchronous notification
mechanism, so the signal bus is used to carry notification values from
OP-TEE to Linux.
This is an early RFC and the implementation is not complete. There may
still be functional bugs, incorrect assumptions, missing pieces, or
interfaces that need to change.
The series is being posted at this stage primarily to get feedback on the
overall architecture and on how the OP-TEE ABI is mapped onto the RPMI
TEE service group.
The implementation deliberately follows the FF-A backend and ffa_abi.c
closely. This allows the existing OP-TEE driver design to be reused and
keeps the RISC-V and FF-A transports as consistent as possible. Some of
these abstractions may need to be adjusted where the RPMI execution,
notification, or memory model differs from FF-A, while others may
eventually be shared between the transports.
[1] https://github.com/riscv-non-isa/riscv-rpmi/commits/main/src/srvgrp-tee.adoc
[2] https://github.com/riscv-non-isa/riscv-sbi-doc/releases
Signed-off-by: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
---
Amirreza Zarrabi (5):
optee: riscv: add RPMI TEE service group transport
optee: riscv: add shared memory and scheduled calls
optee: riscv: enable persistent shared argument cache
optee: riscv: add asynchronous notifications over the signal bus
dt-bindings: tee: add RISC-V RPMI TEE transport
.../bindings/tee/riscv,rpmi-mpxy-tee.yaml | 65 +
MAINTAINERS | 1 +
drivers/tee/optee/Makefile | 1 +
drivers/tee/optee/core.c | 8 +-
drivers/tee/optee/optee_private.h | 47 +
drivers/tee/optee/optee_riscv.c | 1621 ++++++++++++++++++++
drivers/tee/optee/optee_riscv.h | 334 ++++
include/linux/mailbox/riscv-rpmi-message.h | 1 +
8 files changed, 2076 insertions(+), 2 deletions(-)
---
base-commit: 68142f986ff04b2b70b31db00f719bf690f64a9a
change-id: 20260912-rpmi-tee-service-grp-dev-b2ce2f63e0df
Best regards,
--
Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH RFC 1/5] optee: riscv: add RPMI TEE service group transport
2026-09-12 10:15 [PATCH RFC 0/5] tee: optee: add RISC-V RPMI TEE transport Amirreza Zarrabi
@ 2026-09-12 10:15 ` Amirreza Zarrabi
2026-09-12 10:27 ` sashiko-bot
2026-09-12 10:15 ` [PATCH RFC 2/5] optee: riscv: add shared memory and scheduled calls Amirreza Zarrabi
` (3 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Amirreza Zarrabi @ 2026-09-12 10:15 UTC (permalink / raw)
To: Jens Wiklander, Sumit Garg, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Rahul Pathak, Anup Patel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-kernel, op-tee, linux-riscv, devicetree,
Amirreza Zarrabi
Add an OP-TEE transport for RISC-V using the RPMI TEE service group over
the SBI MPXY mailbox framework.
Request one mailbox channel per hart and use the channel corresponding to
the current CPU when issuing a TEE request. Probe the RPMI TEE service
group and required memory-sharing capabilities before registering the
transport.
This provides the basic transport and discovery support needed by the
following patches.
Signed-off-by: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
---
drivers/tee/optee/Makefile | 1 +
drivers/tee/optee/core.c | 8 +-
drivers/tee/optee/optee_private.h | 34 ++++
drivers/tee/optee/optee_riscv.c | 312 +++++++++++++++++++++++++++++
drivers/tee/optee/optee_riscv.h | 141 +++++++++++++
include/linux/mailbox/riscv-rpmi-message.h | 1 +
6 files changed, 495 insertions(+), 2 deletions(-)
diff --git a/drivers/tee/optee/Makefile b/drivers/tee/optee/Makefile
index ad7049c1c107..925b8ec7ef68 100644
--- a/drivers/tee/optee/Makefile
+++ b/drivers/tee/optee/Makefile
@@ -9,6 +9,7 @@ optee-objs += supp.o
optee-objs += device.o
optee-objs += smc_abi.o
optee-objs += ffa_abi.o
+optee-$(CONFIG_RISCV_SBI_MPXY_MBOX) += optee_riscv.o
# for tracing framework to find optee_trace.h
CFLAGS_smc_abi.o := -I$(src)
diff --git a/drivers/tee/optee/core.c b/drivers/tee/optee/core.c
index a52c1f498b99..63f1725e646e 100644
--- a/drivers/tee/optee/core.c
+++ b/drivers/tee/optee/core.c
@@ -220,6 +220,7 @@ void optee_remove_common(struct optee *optee)
static int smc_abi_rc;
static int ffa_abi_rc;
+static int riscv_abi_rc;
static bool intf_is_regged;
static int __init optee_core_init(void)
@@ -245,9 +246,10 @@ static int __init optee_core_init(void)
smc_abi_rc = optee_smc_abi_register();
ffa_abi_rc = optee_ffa_abi_register();
+ riscv_abi_rc = optee_riscv_abi_register();
- /* If both failed there's no point with this module */
- if (smc_abi_rc && ffa_abi_rc) {
+ /* If all failed there's no point with this module */
+ if (smc_abi_rc && ffa_abi_rc && riscv_abi_rc) {
if (IS_REACHABLE(CONFIG_RPMB)) {
rpmb_interface_unregister(&rpmb_class_intf);
intf_is_regged = false;
@@ -270,6 +272,8 @@ static void __exit optee_core_exit(void)
optee_smc_abi_unregister();
if (!ffa_abi_rc)
optee_ffa_abi_unregister();
+ if (!riscv_abi_rc)
+ optee_riscv_abi_unregister();
}
module_exit(optee_core_exit);
diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h
index aefe1e6f5689..8d22d65e087b 100644
--- a/drivers/tee/optee/optee_private.h
+++ b/drivers/tee/optee/optee_private.h
@@ -171,6 +171,31 @@ struct optee_ffa {
struct work_struct notif_work;
};
+/**
+ * struct optee_riscv - RPMI TEE communication struct
+ * @chan: per-hart RPMI TEE service group mailbox channels
+ * @client: RPMI mailbox client used to request @chan
+ * @dev: device backing the RPMI TEE mailbox client
+ * @nr_chan: number of entries in @chan
+ * @max_msg_data_size: maximum RPMI message data size of the TEE channel
+ * @mutex: serializes access to @global_ids
+ * @global_ids: memory parcel id to tee_shm translation table
+ *
+ * This is the RISC-V analog of struct optee_ffa: communication with secure
+ * world OP-TEE OS rides the RPMI TEE service group (RPMI spec section 4.16)
+ * over the SBI MPXY mailbox instead of Arm FF-A.
+ */
+struct optee_riscv {
+ struct mbox_chan **chan;
+ struct mbox_client *client;
+ struct device *dev;
+ unsigned int nr_chan;
+ u32 max_msg_data_size;
+ /* Serializes access to @global_ids */
+ struct mutex mutex;
+ struct rhashtable global_ids;
+};
+
struct optee;
/**
@@ -231,6 +256,7 @@ struct optee_ops {
* @ctx: driver internal TEE context
* @smc: specific to SMC ABI
* @ffa: specific to FF-A ABI
+ * @riscv: specific to RPMI TEE ABI
* @shm_arg_cache: shared memory cache argument
* @call_queue: queue of threads waiting to call @invoke_fn
* @notif: notification synchronization struct
@@ -259,6 +285,7 @@ struct optee {
union {
struct optee_smc smc;
struct optee_ffa ffa;
+ struct optee_riscv riscv;
};
struct optee_shm_arg_cache shm_arg_cache;
struct optee_call_queue call_queue;
@@ -426,5 +453,12 @@ int optee_smc_abi_register(void);
void optee_smc_abi_unregister(void);
int optee_ffa_abi_register(void);
void optee_ffa_abi_unregister(void);
+#ifdef CONFIG_RISCV_SBI_MPXY_MBOX
+int optee_riscv_abi_register(void);
+void optee_riscv_abi_unregister(void);
+#else
+static inline int optee_riscv_abi_register(void) { return -EOPNOTSUPP; }
+static inline void optee_riscv_abi_unregister(void) { }
+#endif
#endif /*OPTEE_PRIVATE_H*/
diff --git a/drivers/tee/optee/optee_riscv.c b/drivers/tee/optee/optee_riscv.c
new file mode 100644
index 000000000000..0fe4edf92fc9
--- /dev/null
+++ b/drivers/tee/optee/optee_riscv.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * This file implements the ABI used when communicating with secure world
+ * OP-TEE OS over the RPMI TEE service group (RPMI spec section 4.16). It is
+ * the RISC-V analog of ffa_abi.c: OP-TEE and Linux are peer endpoints of the
+ * RPMI framework (OpenSBI), and shared memory follows the FF-A memory-donation
+ * model through the RPMI memory parcel services.
+ *
+ * This file is divided into the following sections:
+ * 1. Low level RPMI TEE service group transport over the SBI MPXY mailbox
+ * 2. Feature discovery and notification handshake
+ * 3. Driver initialization
+ *
+ * The remaining FF-A-equivalent sections (parcel id hash table, tee_param
+ * marshalling, dynamic shared memory pool and the scheduled call into secure
+ * world) are added on top of this transport layer.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/errno.h>
+#include <linux/mailbox_client.h>
+#include <linux/mailbox/riscv-rpmi-message.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/smp.h>
+#include <linux/tee_core.h>
+#include <linux/types.h>
+
+#include "optee_private.h"
+#include "optee_riscv.h"
+
+/*
+ * 1. Low level RPMI TEE service group transport over the SBI MPXY mailbox
+ *
+ * The RPMI TEE service group is reached through the SBI MPXY mailbox. Each
+ * hart owns a dedicated MPXY channel so that a call issued on a given hart is
+ * serviced by the OP-TEE context bound to it; optee_riscv_send() therefore
+ * selects the channel of the running hart. All RPMI messages are exchanged
+ * synchronously with rpmi_mbox_send_message().
+ */
+
+static int optee_riscv_send(struct optee *optee, struct rpmi_mbox_message *msg)
+{
+ int cpu, ret;
+
+ cpu = get_cpu();
+ if (cpu >= optee->riscv.nr_chan || !optee->riscv.chan[cpu]) {
+ put_cpu();
+ return -ENODEV;
+ }
+ ret = rpmi_mbox_send_message(optee->riscv.chan[cpu], msg);
+ put_cpu();
+
+ return ret;
+}
+
+/*
+ * 2. Feature discovery and notification handshake
+ *
+ * TEE_PROBE_FEATURES (0x02) reports which framework features are available;
+ * TEE_ENABLE_NOTIFICATION (0x01) subscribes to TEE service group events. Both
+ * are mandatory services (RPMI spec section 4.16), so probing them also
+ * confirms that the framework speaks the TEE service group on this channel.
+ */
+
+static int optee_riscv_probe_feature(struct optee *optee, u32 feature_id,
+ u32 *value)
+{
+ struct rpmi_tee_probe_features_req tx = {
+ .feature_id = cpu_to_le32(feature_id),
+ };
+ struct rpmi_tee_probe_features_resp rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_PROBE_FEATURES,
+ &tx, sizeof(tx), &rx, sizeof(rx));
+ ret = optee_riscv_send(optee, &msg);
+ if (ret)
+ return ret;
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ if (value)
+ *value = le32_to_cpu(rx.value);
+
+ return 0;
+}
+
+static int optee_riscv_features(struct optee *optee)
+{
+ u32 share = RPMI_TEE_MEMORY_SHARE_NONE;
+ int ret;
+
+ /*
+ * Memory parcels carry normal-world shared memory to OP-TEE, so the
+ * framework must support sharing memory between the REE and a TEE.
+ */
+ ret = optee_riscv_probe_feature(optee, RPMI_TEE_FEAT_MEMORY_SHARE,
+ &share);
+ if (ret) {
+ pr_err("Failed to probe MEMORY_SHARE feature: %d\n", ret);
+ return ret;
+ }
+ if (share != RPMI_TEE_MEMORY_SHARE_FULL) {
+ pr_err("Framework cannot share memory between REE and TEE (%u)\n",
+ share);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int optee_riscv_enable_notif(struct optee *optee)
+{
+ struct rpmi_tee_probe_features_resp rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_ENABLE_NOTIFICATION,
+ NULL, 0, &rx, sizeof(rx));
+ ret = optee_riscv_send(optee, &msg);
+ if (ret)
+ return ret;
+
+ /*
+ * The TEE service group defines no notification events on this
+ * platform, so RPMI_ERR_NOTSUPP is expected and not fatal.
+ */
+ if (rx.status && le32_to_cpu(rx.status) != (u32)RPMI_ERR_NOTSUPP)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ return 0;
+}
+
+/*
+ * 3. Driver initialization
+ *
+ * The RPMI TEE service group is described in the device tree by a single
+ * node whose "mboxes" property lists one SBI MPXY channel per hart, in hart
+ * order. The driver requests each list entry by index and validates the
+ * transport before building the OP-TEE device.
+ */
+
+static int optee_riscv_request_channels(struct optee *optee)
+{
+ struct device *dev = optee->riscv.dev;
+ int nr_mboxes;
+ unsigned int cpuid;
+
+ nr_mboxes = of_count_phandle_with_args(dev->of_node, "mboxes",
+ "#mbox-cells");
+ if (nr_mboxes != optee->riscv.nr_chan)
+ return dev_err_probe(dev, -EINVAL,
+ "Expected %u mailbox channels, got %d\n",
+ optee->riscv.nr_chan, nr_mboxes);
+
+ for (cpuid = 0; cpuid < optee->riscv.nr_chan; cpuid++) {
+ optee->riscv.chan[cpuid] =
+ mbox_request_channel(optee->riscv.client, cpuid);
+ if (IS_ERR(optee->riscv.chan[cpuid])) {
+ int ret = PTR_ERR(optee->riscv.chan[cpuid]);
+
+ optee->riscv.chan[cpuid] = NULL;
+ return dev_err_probe(dev, ret,
+ "Failed to request channel %u\n",
+ cpuid);
+ }
+ }
+
+ return 0;
+}
+
+static void optee_riscv_free_channels(struct optee *optee)
+{
+ unsigned int i;
+
+ for (i = 0; i < optee->riscv.nr_chan; i++) {
+ if (optee->riscv.chan[i])
+ mbox_free_channel(optee->riscv.chan[i]);
+ }
+}
+
+static int optee_riscv_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rpmi_mbox_message msg;
+ struct mbox_client *client;
+ struct optee *optee;
+ u32 servicegroup_id;
+ unsigned int nr_cpus;
+ int ret;
+
+ nr_cpus = num_possible_cpus();
+ if (!nr_cpus)
+ return dev_err_probe(dev, -ENODEV, "No harts found\n");
+
+ optee = kzalloc_obj(*optee);
+ if (!optee)
+ return -ENOMEM;
+
+ client = devm_kzalloc(dev, sizeof(*client), GFP_KERNEL);
+ if (!client) {
+ ret = -ENOMEM;
+ goto err_free_optee;
+ }
+ client->dev = dev;
+ client->rx_callback = NULL;
+ client->tx_block = false;
+ client->knows_txdone = true;
+ client->tx_tout = 0;
+
+ optee->riscv.dev = dev;
+ optee->riscv.client = client;
+ optee->riscv.nr_chan = nr_cpus;
+ optee->riscv.chan = kcalloc(nr_cpus, sizeof(*optee->riscv.chan),
+ GFP_KERNEL);
+ if (!optee->riscv.chan) {
+ ret = -ENOMEM;
+ goto err_free_optee;
+ }
+
+ ret = optee_riscv_request_channels(optee);
+ if (ret)
+ goto err_free_channels;
+
+ /* Confirm the channel really speaks the TEE service group. */
+ rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_SERVICEGROUP_ID);
+ ret = optee_riscv_send(optee, &msg);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to get service group id\n");
+ goto err_free_channels;
+ }
+ servicegroup_id = msg.attr.value;
+ if (servicegroup_id != RPMI_SRVGRP_TEE) {
+ ret = -ENODEV;
+ dev_err_probe(dev, ret, "Not a TEE service group channel (0x%x)\n",
+ servicegroup_id);
+ goto err_free_channels;
+ }
+
+ rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_MAX_MSG_DATA_SIZE);
+ ret = optee_riscv_send(optee, &msg);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to get max msg data size\n");
+ goto err_free_channels;
+ }
+ optee->riscv.max_msg_data_size = msg.attr.value;
+
+ ret = optee_riscv_features(optee);
+ if (ret) {
+ dev_err_probe(dev, ret, "Missing required TEE features\n");
+ goto err_free_channels;
+ }
+
+ ret = optee_riscv_enable_notif(optee);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to enable notifications\n");
+ goto err_free_channels;
+ }
+
+ platform_set_drvdata(pdev, optee);
+ dev_info(dev, "initialized driver\n");
+
+ return 0;
+
+err_free_channels:
+ optee_riscv_free_channels(optee);
+ kfree(optee->riscv.chan);
+err_free_optee:
+ kfree(optee);
+ return ret;
+}
+
+static void optee_riscv_remove(struct platform_device *pdev)
+{
+ struct optee *optee = platform_get_drvdata(pdev);
+
+ optee_riscv_free_channels(optee);
+ kfree(optee->riscv.chan);
+ kfree(optee);
+}
+
+static const struct of_device_id optee_riscv_match[] = {
+ { .compatible = "riscv,rpmi-mpxy-tee" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, optee_riscv_match);
+
+static struct platform_driver optee_riscv_driver = {
+ .driver = {
+ .name = DRIVER_NAME "-riscv",
+ .of_match_table = optee_riscv_match,
+ },
+ .probe = optee_riscv_probe,
+ .remove = optee_riscv_remove,
+};
+
+int optee_riscv_abi_register(void)
+{
+ return platform_driver_register(&optee_riscv_driver);
+}
+
+void optee_riscv_abi_unregister(void)
+{
+ platform_driver_unregister(&optee_riscv_driver);
+}
diff --git a/drivers/tee/optee/optee_riscv.h b/drivers/tee/optee/optee_riscv.h
new file mode 100644
index 000000000000..d87298faa6a2
--- /dev/null
+++ b/drivers/tee/optee/optee_riscv.h
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+/*
+ * This file is exported by OP-TEE and is kept in sync between secure world
+ * and normal world drivers. It describes the wire contract used when
+ * communicating with secure world OP-TEE OS over the RPMI TEE service group
+ * (RPMI specification section 4.16, SERVICEGROUP_ID 0x0010).
+ *
+ * The RPMI TEE service group is the RISC-V analog of Arm FF-A: OP-TEE and the
+ * rich execution environment (REE, i.e. Linux) are peer endpoints, and the
+ * RPMI framework (OpenSBI in M-mode) mediates every message. Memory sharing
+ * follows the FF-A memory-donation model through the memory parcel services:
+ * the REE creates a parcel describing its pages, OP-TEE accepts it lazily by
+ * parcel id, and teardown is two phased (OP-TEE releases, the REE reclaims).
+ *
+ * All request and response payloads are little-endian uint32 words as defined
+ * by the RPMI specification. These definitions MUST byte-match the OpenSBI
+ * framework definitions in <sbi_utils/mailbox/rpmi_msgprot.h>.
+ */
+
+#ifndef __OPTEE_RISCV_H
+#define __OPTEE_RISCV_H
+
+#include <linux/mailbox/riscv-rpmi-message.h>
+#include <linux/types.h>
+
+/*
+ * RPMI TEE service ids (RPMI spec section 4.16, Table 181).
+ *
+ * Only TEE_ENABLE_NOTIFICATION, TEE_PROBE_FEATURES and TEE_CALL are mandated;
+ * the remaining services are optional and may return RPMI_ERR_NOTSUPP.
+ */
+enum rpmi_tee_service_id {
+ RPMI_TEE_SRV_ENABLE_NOTIFICATION = 0x01,
+ RPMI_TEE_SRV_PROBE_FEATURES = 0x02,
+ RPMI_TEE_SRV_PROBE_SYSTEM = 0x03,
+ RPMI_TEE_SRV_EXIT = 0x04,
+ RPMI_TEE_SRV_SIGNAL_BUS_SETUP = 0x05,
+ RPMI_TEE_SRV_SIGNAL_BUS_TEARDOWN = 0x06,
+ RPMI_TEE_SRV_SIGNAL_RAISE = 0x07,
+ RPMI_TEE_SRV_SIGNAL_RETRIEVE = 0x08,
+ RPMI_TEE_SRV_MEM_PARCEL_CREATE = 0x09,
+ RPMI_TEE_SRV_MEM_PARCEL_ACCEPT = 0x0a,
+ RPMI_TEE_SRV_MEM_PARCEL_RELEASE = 0x0b,
+ RPMI_TEE_SRV_MEM_PARCEL_RECLAIM = 0x0c,
+ RPMI_TEE_SRV_MEM_PARCEL_SEGMENT_SEND = 0x0d,
+ RPMI_TEE_SRV_MEM_PARCEL_SEGMENT_RECEIVE = 0x0e,
+ RPMI_TEE_SRV_CALL = 0x13,
+ RPMI_TEE_SRV_MAX_COUNT,
+};
+
+/*
+ * RPMI TEE endpoint identities.
+ *
+ * The RPMI specification does not fix numeric endpoint ids; they are assigned
+ * by the framework at runtime. These values match the OpenSBI framework
+ * assignment used on this platform: the REE is endpoint 0 and OP-TEE is
+ * endpoint 1.
+ */
+#define RPMI_TEE_ENDPOINT_REE 0
+#define RPMI_TEE_ENDPOINT_OPTEE 1
+
+/*
+ * RPMI TEE feature ids for TEE_PROBE_FEATURES (RPMI spec section 4.16.4,
+ * Table 182).
+ */
+enum rpmi_tee_feature_id {
+ RPMI_TEE_FEAT_MEMORY_DONATE = 1,
+ RPMI_TEE_FEAT_MEMORY_LEND = 2,
+ RPMI_TEE_FEAT_MEMORY_SHARE = 3,
+ RPMI_TEE_FEAT_SIGNAL_BUS = 4,
+ RPMI_TEE_FEAT_MULTISEGMENT_OPS = 5,
+ RPMI_TEE_FEAT_SYSINFO_FORMAT = 6,
+};
+
+/* MEMORY_SHARE feature values (RPMI spec Table 182). */
+#define RPMI_TEE_MEMORY_SHARE_NONE 0
+#define RPMI_TEE_MEMORY_SHARE_TEE_ONLY 1
+#define RPMI_TEE_MEMORY_SHARE_FULL 2
+
+/* TEE_PROBE_FEATURES request (Table 183) / response (Table 184). */
+struct rpmi_tee_probe_features_req {
+ __le32 feature_id;
+};
+
+struct rpmi_tee_probe_features_resp {
+ __le32 status;
+ __le32 value;
+};
+
+/*
+ * TEE_CALL wire encoding (RPMI spec section 4.16.21, Tables 218 and 219).
+ *
+ * TEE_CALL is the mandatory doorbell service used to enter OP-TEE. The
+ * request carries a fixed REE->OP-TEE identity, the well-known OP-TEE service
+ * UUID and a SERVICE_DATA payload; the response carries a STATUS word, a
+ * SERVICE_RSP_LEN word and the SERVICE_RSP payload.
+ *
+ * The SERVICE_DATA/SERVICE_RSP registers are XLEN-sized little-endian values.
+ * The structures are __packed so the 16-byte UUID does not force padding
+ * before the length word.
+ */
+#define RPMI_TEE_UUID_LEN 16
+
+/* OP-TEE communicate service UUID: 5be1b1a0-7e11-4e7a-9b10-0010c0ffee00 */
+#define RPMI_TEE_OPTEE_UUID \
+ { 0x5b, 0xe1, 0xb1, 0xa0, 0x7e, 0x11, 0x4e, 0x7a, \
+ 0x9b, 0x10, 0x00, 0x10, 0xc0, 0xff, 0xee, 0x00 }
+
+/* OP-TEE SMC-style call convention carried inside SERVICE_DATA. */
+#define RPMI_TEE_OPTEE_CALL_REGS 8 /* a0-a7 */
+#define RPMI_TEE_OPTEE_RESP_REGS 4 /* a0-a3 */
+
+#if __riscv_xlen == 64
+typedef __le64 rpmi_xlen_t;
+#define cpu_to_rpmi_xlen(x) cpu_to_le64(x)
+#define rpmi_xlen_to_cpu(x) le64_to_cpu(x)
+#else
+typedef __le32 rpmi_xlen_t;
+#define cpu_to_rpmi_xlen(x) cpu_to_le32(x)
+#define rpmi_xlen_to_cpu(x) le32_to_cpu(x)
+#endif
+
+struct rpmi_tee_call_req {
+ __le32 sender_id;
+ __le32 target_id;
+ u8 service[RPMI_TEE_UUID_LEN];
+ __le32 service_data_len;
+ rpmi_xlen_t reg[RPMI_TEE_OPTEE_CALL_REGS];
+} __packed;
+
+struct rpmi_tee_call_resp {
+ __le32 status;
+ __le32 service_rsp_len;
+ rpmi_xlen_t reg[RPMI_TEE_OPTEE_RESP_REGS];
+} __packed;
+
+#endif /* __OPTEE_RISCV_H */
diff --git a/include/linux/mailbox/riscv-rpmi-message.h b/include/linux/mailbox/riscv-rpmi-message.h
index e135c6564d0c..47a540bd81c9 100644
--- a/include/linux/mailbox/riscv-rpmi-message.h
+++ b/include/linux/mailbox/riscv-rpmi-message.h
@@ -93,6 +93,7 @@ static inline int rpmi_to_linux_error(int rpmi_error)
/* RPMI service group IDs */
#define RPMI_SRVGRP_SYSTEM_MSI 0x00002
#define RPMI_SRVGRP_CLOCK 0x00008
+#define RPMI_SRVGRP_TEE 0x00010
/* RPMI clock service IDs */
enum rpmi_clock_service_id {
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC 2/5] optee: riscv: add shared memory and scheduled calls
2026-09-12 10:15 [PATCH RFC 0/5] tee: optee: add RISC-V RPMI TEE transport Amirreza Zarrabi
2026-09-12 10:15 ` [PATCH RFC 1/5] optee: riscv: add RPMI TEE service group transport Amirreza Zarrabi
@ 2026-09-12 10:15 ` Amirreza Zarrabi
2026-09-12 10:31 ` sashiko-bot
2026-09-12 10:15 ` [PATCH RFC 3/5] optee: riscv: enable persistent shared argument cache Amirreza Zarrabi
` (2 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Amirreza Zarrabi @ 2026-09-12 10:15 UTC (permalink / raw)
To: Jens Wiklander, Sumit Garg, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Rahul Pathak, Anup Patel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-kernel, op-tee, linux-riscv, devicetree,
Amirreza Zarrabi
Add the shared-memory and call support required by the RISC-V RPMI
transport.
Use RPMI memory parcels to share memory between Linux and OP-TEE and keep
a mapping between parcel identifiers and struct tee_shm. Pass the parcel
identifier and offset when referencing shared memory from OP-TEE message
parameters.
Implement scheduled calls using RPMI TEE_CALL, including yielding calls,
RPC handling and shared-memory allocation. Also add OP-TEE version and
capability negotiation and complete registration of the OP-TEE device.
Signed-off-by: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
---
drivers/tee/optee/optee_private.h | 2 +
drivers/tee/optee/optee_riscv.c | 1198 ++++++++++++++++++++++++++++++++++---
drivers/tee/optee/optee_riscv.h | 135 ++++-
3 files changed, 1257 insertions(+), 78 deletions(-)
diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h
index 8d22d65e087b..cf878b8178f9 100644
--- a/drivers/tee/optee/optee_private.h
+++ b/drivers/tee/optee/optee_private.h
@@ -178,6 +178,7 @@ struct optee_ffa {
* @dev: device backing the RPMI TEE mailbox client
* @nr_chan: number of entries in @chan
* @max_msg_data_size: maximum RPMI message data size of the TEE channel
+ * @next_nonce: monotonic nonce source for memory parcel creation
* @mutex: serializes access to @global_ids
* @global_ids: memory parcel id to tee_shm translation table
*
@@ -191,6 +192,7 @@ struct optee_riscv {
struct device *dev;
unsigned int nr_chan;
u32 max_msg_data_size;
+ atomic_t next_nonce;
/* Serializes access to @global_ids */
struct mutex mutex;
struct rhashtable global_ids;
diff --git a/drivers/tee/optee/optee_riscv.c b/drivers/tee/optee/optee_riscv.c
index 0fe4edf92fc9..36115326486d 100644
--- a/drivers/tee/optee/optee_riscv.c
+++ b/drivers/tee/optee/optee_riscv.c
@@ -8,34 +8,46 @@
* RPMI framework (OpenSBI), and shared memory follows the FF-A memory-donation
* model through the RPMI memory parcel services.
*
- * This file is divided into the following sections:
- * 1. Low level RPMI TEE service group transport over the SBI MPXY mailbox
- * 2. Feature discovery and notification handshake
- * 3. Driver initialization
+ * This file is structured exactly like ffa_abi.c:
+ * 1. Maintain a hash table for lookup of a memory parcel id
+ * 2. Convert between struct tee_param and struct optee_msg_param
+ * 3. Low level support functions to register shared memory in secure world
+ * 4. Dynamic shared memory pool based on alloc_pages()
+ * 5. Do a normal scheduled call into secure world
+ * 6. Driver initialization
*
- * The remaining FF-A-equivalent sections (parcel id hash table, tee_param
- * marshalling, dynamic shared memory pool and the scheduled call into secure
- * world) are added on top of this transport layer.
+ * Every FF-A memory operation has a direct RPMI TEE service group analog:
+ * FFA_MEM_SHARE -> MEM_PARCEL_CREATE (0x09), issued by the REE
+ * FFA_MEM_RECLAIM -> MEM_PARCEL_RECLAIM (0x0c), issued by the REE
+ * direct message -> TEE_CALL (0x13), the call doorbell
+ * and the FF-A g_handle is replaced by a memory parcel id folded together
+ * with a caller-supplied nonce.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/atomic.h>
#include <linux/errno.h>
#include <linux/mailbox_client.h>
#include <linux/mailbox/riscv-rpmi-message.h>
+#include <linux/mm.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/rhashtable.h>
+#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/smp.h>
+#include <linux/string.h>
#include <linux/tee_core.h>
#include <linux/types.h>
#include "optee_private.h"
#include "optee_riscv.h"
+#include "optee_rpc_cmd.h"
/*
- * 1. Low level RPMI TEE service group transport over the SBI MPXY mailbox
+ * Low level RPMI TEE service group transport over the SBI MPXY mailbox.
*
* The RPMI TEE service group is reached through the SBI MPXY mailbox. Each
* hart owns a dedicated MPXY channel so that a call issued on a given hart is
@@ -60,25 +72,43 @@ static int optee_riscv_send(struct optee *optee, struct rpmi_mbox_message *msg)
}
/*
- * 2. Feature discovery and notification handshake
+ * optee_riscv_tee_call() - issue a TEE_CALL (RPMI service 0x13)
+ * @optee: main service struct
+ * @in: the command words carried in SERVICE_DATA, the RISC-V analog
+ * of struct ffa_send_direct_data's data0-data4 (w3-w7)
+ * @out: the response words returned in SERVICE_RSP, the RISC-V analog
+ * of the same data0-data4 set on the return path
*
- * TEE_PROBE_FEATURES (0x02) reports which framework features are available;
- * TEE_ENABLE_NOTIFICATION (0x01) subscribes to TEE service group events. Both
- * are mandatory services (RPMI spec section 4.16), so probing them also
- * confirms that the framework speaks the TEE service group on this channel.
+ * TEE_CALL is the RISC-V analog of the FF-A direct message: it is the single
+ * doorbell used both for the blocking (fast) calls of section 6 and for the
+ * yielding call of section 5. The struct optee_msg_arg itself is never
+ * carried here, only its parcel handle and offset, exactly as FF-A carries
+ * only w4-w6.
+ *
+ * Returns 0 on success or <0 on failure.
*/
-
-static int optee_riscv_probe_feature(struct optee *optee, u32 feature_id,
- u32 *value)
+static int optee_riscv_tee_call(struct optee *optee,
+ const u64 in[RPMI_TEE_OPTEE_CALL_REGS],
+ u64 out[RPMI_TEE_OPTEE_RESP_REGS])
{
- struct rpmi_tee_probe_features_req tx = {
- .feature_id = cpu_to_le32(feature_id),
+ static const u8 optee_uuid[RPMI_TEE_UUID_LEN] = RPMI_TEE_OPTEE_UUID;
+ struct rpmi_tee_call_req tx = {
+ .sender_id = cpu_to_le32(RPMI_TEE_ENDPOINT_REE),
+ .target_id = cpu_to_le32(RPMI_TEE_ENDPOINT_OPTEE),
+ .service_data_len =
+ cpu_to_le32(RPMI_TEE_OPTEE_CALL_REGS *
+ sizeof(rpmi_xlen_t)),
};
- struct rpmi_tee_probe_features_resp rx = { };
+ struct rpmi_tee_call_resp rx = { };
struct rpmi_mbox_message msg;
+ unsigned int i;
int ret;
- rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_PROBE_FEATURES,
+ memcpy(tx.service, optee_uuid, sizeof(tx.service));
+ for (i = 0; i < RPMI_TEE_OPTEE_CALL_REGS; i++)
+ tx.reg[i] = cpu_to_rpmi_xlen(in[i]);
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_CALL,
&tx, sizeof(tx), &rx, sizeof(rx));
ret = optee_riscv_send(optee, &msg);
if (ret)
@@ -86,61 +116,887 @@ static int optee_riscv_probe_feature(struct optee *optee, u32 feature_id,
if (rx.status)
return rpmi_to_linux_error(le32_to_cpu(rx.status));
- if (value)
- *value = le32_to_cpu(rx.value);
+ for (i = 0; i < RPMI_TEE_OPTEE_RESP_REGS; i++)
+ out[i] = rpmi_xlen_to_cpu(rx.reg[i]);
return 0;
}
-static int optee_riscv_features(struct optee *optee)
+/*
+ * 1. Maintain a hash table for lookup of a memory parcel id
+ *
+ * The RPMI framework assigns a memory parcel id for each piece of shared
+ * memory. Together with a caller-supplied nonce it forms the wire identity
+ * used when communicating with secure world, playing the exact role of the
+ * FF-A global memory handle.
+ *
+ * Main functions are optee_shm_add_riscv_handle() and
+ * optee_shm_rem_riscv_handle().
+ */
+struct shm_rhash {
+ struct tee_shm *shm;
+ u64 global_id;
+ struct rhash_head linkage;
+};
+
+static void rh_free_fn(void *ptr, void *arg)
{
- u32 share = RPMI_TEE_MEMORY_SHARE_NONE;
+ kfree(ptr);
+}
+
+static const struct rhashtable_params shm_rhash_params = {
+ .head_offset = offsetof(struct shm_rhash, linkage),
+ .key_len = sizeof(u64),
+ .key_offset = offsetof(struct shm_rhash, global_id),
+ .automatic_shrinking = true,
+};
+
+static struct tee_shm *optee_shm_from_riscv_handle(struct optee *optee,
+ u64 global_id)
+{
+ struct tee_shm *shm = NULL;
+ struct shm_rhash *r;
+
+ mutex_lock(&optee->riscv.mutex);
+ r = rhashtable_lookup_fast(&optee->riscv.global_ids, &global_id,
+ shm_rhash_params);
+ if (r)
+ shm = r->shm;
+ mutex_unlock(&optee->riscv.mutex);
+
+ return shm;
+}
+
+static int optee_shm_add_riscv_handle(struct optee *optee, struct tee_shm *shm,
+ u64 global_id)
+{
+ struct shm_rhash *r;
+ int rc;
+
+ r = kmalloc_obj(*r);
+ if (!r)
+ return -ENOMEM;
+ r->shm = shm;
+ r->global_id = global_id;
+
+ mutex_lock(&optee->riscv.mutex);
+ rc = rhashtable_lookup_insert_fast(&optee->riscv.global_ids,
+ &r->linkage, shm_rhash_params);
+ mutex_unlock(&optee->riscv.mutex);
+
+ if (rc)
+ kfree(r);
+
+ return rc;
+}
+
+static int optee_shm_rem_riscv_handle(struct optee *optee, u64 global_id)
+{
+ struct shm_rhash *r;
+ int rc = -ENOENT;
+
+ mutex_lock(&optee->riscv.mutex);
+ r = rhashtable_lookup_fast(&optee->riscv.global_ids, &global_id,
+ shm_rhash_params);
+ if (r)
+ rc = rhashtable_remove_fast(&optee->riscv.global_ids,
+ &r->linkage, shm_rhash_params);
+ mutex_unlock(&optee->riscv.mutex);
+
+ if (!rc)
+ kfree(r);
+
+ return rc;
+}
+
+/*
+ * 2. Convert between struct tee_param and struct optee_msg_param
+ *
+ * optee_riscv_from_msg_param() and optee_riscv_to_msg_param() are the main
+ * functions. They are identical to their FF-A counterparts: the memref
+ * carries only the parcel handle (stored in fmem.global_id, the same slot
+ * FF-A uses for its g_handle), an offset and a size, never a page list.
+ */
+
+static void from_msg_param_riscv_mem(struct optee *optee, struct tee_param *p,
+ u32 attr, const struct optee_msg_param *mp)
+{
+ struct tee_shm *shm = NULL;
+ u64 offs_high = 0;
+ u64 offs_low = 0;
+
+ p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT +
+ attr - OPTEE_MSG_ATTR_TYPE_FMEM_INPUT;
+ p->u.memref.size = mp->u.fmem.size;
+
+ if (mp->u.fmem.global_id != OPTEE_MSG_FMEM_INVALID_GLOBAL_ID)
+ shm = optee_shm_from_riscv_handle(optee, mp->u.fmem.global_id);
+ p->u.memref.shm = shm;
+
+ if (shm) {
+ offs_low = mp->u.fmem.offs_low;
+ offs_high = mp->u.fmem.offs_high;
+ }
+ p->u.memref.shm_offs = offs_low | offs_high << 32;
+}
+
+/**
+ * optee_riscv_from_msg_param() - convert from OPTEE_MSG parameters to
+ * struct tee_param
+ * @optee: main service struct
+ * @params: subsystem internal parameter representation
+ * @num_params: number of elements in the parameter arrays
+ * @msg_params: OPTEE_MSG parameters
+ *
+ * Returns 0 on success or <0 on failure
+ */
+static int optee_riscv_from_msg_param(struct optee *optee,
+ struct tee_param *params,
+ size_t num_params,
+ const struct optee_msg_param *msg_params)
+{
+ size_t n;
+
+ for (n = 0; n < num_params; n++) {
+ struct tee_param *p = params + n;
+ const struct optee_msg_param *mp = msg_params + n;
+ u32 attr = mp->attr & OPTEE_MSG_ATTR_TYPE_MASK;
+
+ switch (attr) {
+ case OPTEE_MSG_ATTR_TYPE_NONE:
+ p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE;
+ memset(&p->u, 0, sizeof(p->u));
+ break;
+ case OPTEE_MSG_ATTR_TYPE_VALUE_INPUT:
+ case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT:
+ case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT:
+ optee_from_msg_param_value(p, attr, mp);
+ break;
+ case OPTEE_MSG_ATTR_TYPE_FMEM_INPUT:
+ case OPTEE_MSG_ATTR_TYPE_FMEM_OUTPUT:
+ case OPTEE_MSG_ATTR_TYPE_FMEM_INOUT:
+ from_msg_param_riscv_mem(optee, p, attr, mp);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int to_msg_param_riscv_mem(struct optee_msg_param *mp,
+ const struct tee_param *p)
+{
+ struct tee_shm *shm = p->u.memref.shm;
+
+ mp->attr = OPTEE_MSG_ATTR_TYPE_FMEM_INPUT + p->attr -
+ TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT;
+
+ if (shm) {
+ u64 shm_offs = p->u.memref.shm_offs;
+
+ mp->u.fmem.internal_offs = shm->offset;
+
+ mp->u.fmem.offs_low = shm_offs;
+ mp->u.fmem.offs_high = shm_offs >> 32;
+ /* Check that the entire offset could be stored. */
+ if (mp->u.fmem.offs_high != shm_offs >> 32)
+ return -EINVAL;
+
+ mp->u.fmem.global_id = shm->sec_world_id;
+ } else {
+ memset(&mp->u, 0, sizeof(mp->u));
+ mp->u.fmem.global_id = OPTEE_MSG_FMEM_INVALID_GLOBAL_ID;
+ }
+ mp->u.fmem.size = p->u.memref.size;
+
+ return 0;
+}
+
+/**
+ * optee_riscv_to_msg_param() - convert from struct tee_params to OPTEE_MSG
+ * parameters
+ * @optee: main service struct
+ * @msg_params: OPTEE_MSG parameters
+ * @num_params: number of elements in the parameter arrays
+ * @params: subsystem internal parameter representation
+ *
+ * Returns 0 on success or <0 on failure
+ */
+static int optee_riscv_to_msg_param(struct optee *optee,
+ struct optee_msg_param *msg_params,
+ size_t num_params,
+ const struct tee_param *params)
+{
+ size_t n;
+
+ for (n = 0; n < num_params; n++) {
+ const struct tee_param *p = params + n;
+ struct optee_msg_param *mp = msg_params + n;
+
+ switch (p->attr) {
+ case TEE_IOCTL_PARAM_ATTR_TYPE_NONE:
+ mp->attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE;
+ memset(&mp->u, 0, sizeof(mp->u));
+ break;
+ case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT:
+ case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT:
+ case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT:
+ optee_to_msg_param_value(mp, p);
+ break;
+ case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT:
+ case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT:
+ case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT:
+ if (to_msg_param_riscv_mem(mp, p))
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * 3. Low level support functions to register shared memory in secure world
+ *
+ * Functions to register and unregister shared memory both for normal
+ * clients and for tee-supplicant. Registration creates an RPMI memory
+ * parcel (MEM_PARCEL_CREATE), which is the analog of FFA_MEM_SHARE;
+ * unregistration reclaims it (MEM_PARCEL_RECLAIM), the analog of
+ * FFA_MEM_RECLAIM, after a synchronous handshake with OP-TEE.
+ */
+
+/*
+ * Coalesce a page array into RPMI block-list entries (Table 198). Each entry
+ * spans a run of physically contiguous pages, up to RPMI_TEE_PARCEL_BLOCK_MAX_
+ * PAGES. When @block_high / @block_low are NULL only the entry count is
+ * computed, so the caller can size the request buffer first.
+ */
+static u32 optee_riscv_build_blocks(struct page **pages, size_t num_pages,
+ __le32 *block_high, __le32 *block_low)
+{
+ u32 nblocks = 0;
+ size_t i = 0;
+
+ while (i < num_pages) {
+ u64 pfn = page_to_pfn(pages[i]);
+ u32 run = 1;
+
+ while (i + run < num_pages &&
+ run < RPMI_TEE_PARCEL_BLOCK_MAX_PAGES &&
+ page_to_pfn(pages[i + run]) == pfn + run)
+ run++;
+
+ if (block_high && block_low) {
+ block_high[nblocks] = rpmi_tee_block_high(pfn);
+ block_low[nblocks] = rpmi_tee_block_low(pfn, run);
+ }
+ nblocks++;
+ i += run;
+ }
+
+ return nblocks;
+}
+
+/*
+ * Issue MEM_PARCEL_CREATE (RPMI service 0x09) for @pages with the REE as the
+ * creator and OP-TEE as the sole read/write receiver. Returns the framework
+ * assigned parcel id (>= 0) or a negative errno.
+ */
+static int optee_riscv_parcel_create(struct optee *optee, struct page **pages,
+ size_t num_pages, u32 nonce)
+{
+ struct rpmi_tee_mem_parcel_create_req *req;
+ struct rpmi_tee_mem_parcel_create_resp rx = { };
+ struct rpmi_mbox_message msg;
+ __le32 *block_high, *block_low;
+ size_t req_len;
+ u32 block_cnt;
+ __le32 *data;
int ret;
+ block_cnt = optee_riscv_build_blocks(pages, num_pages, NULL, NULL);
+
/*
- * Memory parcels carry normal-world shared memory to OP-TEE, so the
- * framework must support sharing memory between the REE and a TEE.
+ * Layout of the trailing data[] array (Table 200): one receiver_id and
+ * one access word (receiver_cnt == 1), then block_high[block_cnt] and
+ * block_low[block_cnt].
*/
- ret = optee_riscv_probe_feature(optee, RPMI_TEE_FEAT_MEMORY_SHARE,
- &share);
- if (ret) {
- pr_err("Failed to probe MEMORY_SHARE feature: %d\n", ret);
+ req_len = struct_size(req, data, 2 + 2 * block_cnt);
+ if (optee->riscv.max_msg_data_size &&
+ req_len > optee->riscv.max_msg_data_size)
+ return -E2BIG;
+
+ req = kzalloc(req_len, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req->creator_id = cpu_to_le32(RPMI_TEE_ENDPOINT_REE);
+ req->creator_access = cpu_to_le32(RPMI_TEE_PARCEL_ACCESS_R |
+ RPMI_TEE_PARCEL_ACCESS_W);
+ req->receiver_cnt = cpu_to_le32(1);
+ req->flags = 0;
+ req->nonce = cpu_to_le32(nonce);
+ req->block_cnt = cpu_to_le32(block_cnt);
+
+ data = req->data;
+ data[0] = cpu_to_le32(RPMI_TEE_ENDPOINT_OPTEE);
+ data[1] = cpu_to_le32(RPMI_TEE_PARCEL_ACCESS_R |
+ RPMI_TEE_PARCEL_ACCESS_W);
+ block_high = &data[2];
+ block_low = &data[2 + block_cnt];
+ optee_riscv_build_blocks(pages, num_pages, block_high, block_low);
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_MEM_PARCEL_CREATE,
+ req, req_len, &rx, sizeof(rx));
+ ret = optee_riscv_send(optee, &msg);
+ kfree(req);
+ if (ret)
return ret;
- }
- if (share != RPMI_TEE_MEMORY_SHARE_FULL) {
- pr_err("Framework cannot share memory between REE and TEE (%u)\n",
- share);
- return -EOPNOTSUPP;
- }
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
- return 0;
+ return le32_to_cpu(rx.mem_parcel_id);
}
-static int optee_riscv_enable_notif(struct optee *optee)
+/*
+ * Issue MEM_PARCEL_RECLAIM (RPMI service 0x0c). OpenSBI fails the reclaim
+ * while any receiver still holds the parcel, so this is only called after the
+ * OPTEE_ABI_UNREGISTER_SHM handshake below has confirmed OP-TEE released it.
+ */
+static int optee_riscv_parcel_reclaim(struct optee *optee, u32 parcel_id)
{
- struct rpmi_tee_probe_features_resp rx = { };
+ struct rpmi_tee_mem_parcel_reclaim_req tx = {
+ .mem_parcel_id = cpu_to_le32(parcel_id),
+ };
+ struct rpmi_tee_mem_parcel_reclaim_resp rx = { };
struct rpmi_mbox_message msg;
int ret;
- rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_ENABLE_NOTIFICATION,
- NULL, 0, &rx, sizeof(rx));
+ rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_MEM_PARCEL_RECLAIM,
+ &tx, sizeof(tx), &rx, sizeof(rx));
ret = optee_riscv_send(optee, &msg);
if (ret)
return ret;
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ return 0;
+}
+
+static int optee_riscv_shm_register(struct tee_context *ctx,
+ struct tee_shm *shm, struct page **pages,
+ size_t num_pages, unsigned long start)
+{
+ struct optee *optee = tee_get_drvdata(ctx->teedev);
+ u64 global_id;
+ u32 nonce;
+ int rc;
+
+ rc = optee_check_mem_type(start, num_pages);
+ if (rc)
+ return rc;
/*
- * The TEE service group defines no notification events on this
- * platform, so RPMI_ERR_NOTSUPP is expected and not fatal.
+ * MEM_PARCEL_CREATE returns only a parcel id; the nonce is
+ * caller-supplied. Fold them into the FF-A style 64-bit handle:
+ * parcel id in the low word, nonce in the high word.
*/
- if (rx.status && le32_to_cpu(rx.status) != (u32)RPMI_ERR_NOTSUPP)
- return rpmi_to_linux_error(le32_to_cpu(rx.status));
+ nonce = (u32)atomic_inc_return(&optee->riscv.next_nonce);
+ rc = optee_riscv_parcel_create(optee, pages, num_pages, nonce);
+ if (rc < 0)
+ return rc;
+ global_id = (u32)rc | ((u64)nonce << 32);
+
+ rc = optee_shm_add_riscv_handle(optee, shm, global_id);
+ if (rc) {
+ optee_riscv_parcel_reclaim(optee, (u32)global_id);
+ return rc;
+ }
+
+ shm->sec_world_id = global_id;
return 0;
}
+static int optee_riscv_shm_unregister(struct tee_context *ctx,
+ struct tee_shm *shm)
+{
+ struct optee *optee = tee_get_drvdata(ctx->teedev);
+ u64 global_id = shm->sec_world_id;
+ u64 in[RPMI_TEE_OPTEE_CALL_REGS] = {
+ OPTEE_ABI_UNREGISTER_SHM,
+ (u32)global_id,
+ global_id >> 32,
+ 0,
+ };
+ u64 out[RPMI_TEE_OPTEE_RESP_REGS] = { };
+ int rc;
+
+ optee_shm_rem_riscv_handle(optee, global_id);
+ shm->sec_world_id = 0;
+
+ /*
+ * Synchronous teardown handshake, the analog of the FF-A
+ * OPTEE_FFA_UNREGISTER_SHM blocking call: OP-TEE releases the parcel on
+ * its own TEE channel before we reclaim it. Only reclaim once OP-TEE
+ * has acknowledged, so we never race the release.
+ */
+ rc = optee_riscv_tee_call(optee, in, out);
+ if (rc)
+ pr_err("Unregister SHM id 0x%llx rc %d\n", global_id, rc);
+
+ rc = optee_riscv_parcel_reclaim(optee, (u32)global_id);
+ if (rc)
+ pr_err("parcel_reclaim: 0x%llx %d\n", global_id, rc);
+
+ return rc;
+}
+
+static int optee_riscv_shm_unregister_supp(struct tee_context *ctx,
+ struct tee_shm *shm)
+{
+ struct optee *optee = tee_get_drvdata(ctx->teedev);
+ u64 global_id = shm->sec_world_id;
+ int rc;
+
+ /*
+ * We're skipping the OPTEE_ABI_UNREGISTER_SHM handshake since this is
+ * OP-TEE freeing via RPC, so it has already retired this parcel.
+ */
+ optee_shm_rem_riscv_handle(optee, global_id);
+ shm->sec_world_id = 0;
+
+ rc = optee_riscv_parcel_reclaim(optee, (u32)global_id);
+ if (rc)
+ pr_err("parcel_reclaim: 0x%llx %d\n", global_id, rc);
+
+ return rc;
+}
+
+/*
+ * 4. Dynamic shared memory pool based on alloc_pages()
+ *
+ * Implements an OP-TEE specific shared memory pool.
+ * The main function is optee_riscv_shm_pool_alloc_pages().
+ */
+
+static int pool_riscv_op_alloc(struct tee_shm_pool *pool,
+ struct tee_shm *shm, size_t size, size_t align)
+{
+ return tee_dyn_shm_alloc_helper(shm, size, align,
+ optee_riscv_shm_register);
+}
+
+static void pool_riscv_op_free(struct tee_shm_pool *pool, struct tee_shm *shm)
+{
+ tee_dyn_shm_free_helper(shm, optee_riscv_shm_unregister);
+}
+
+static void pool_riscv_op_destroy_pool(struct tee_shm_pool *pool)
+{
+ kfree(pool);
+}
+
+static const struct tee_shm_pool_ops pool_riscv_ops = {
+ .alloc = pool_riscv_op_alloc,
+ .free = pool_riscv_op_free,
+ .destroy_pool = pool_riscv_op_destroy_pool,
+};
+
+/**
+ * optee_riscv_shm_pool_alloc_pages() - create page-based allocator pool
+ *
+ * This pool is used with OP-TEE over the RPMI TEE service group. In this case
+ * command buffers and such are allocated from kernel's own memory.
+ */
+static struct tee_shm_pool *optee_riscv_shm_pool_alloc_pages(void)
+{
+ struct tee_shm_pool *pool = kzalloc_obj(*pool);
+
+ if (!pool)
+ return ERR_PTR(-ENOMEM);
+
+ pool->ops = &pool_riscv_ops;
+
+ return pool;
+}
+
/*
- * 3. Driver initialization
+ * 5. Do a normal scheduled call into secure world
*
+ * The function optee_riscv_do_call_with_arg() performs a normal scheduled
+ * call into secure world. During this call secure world may request help
+ * from normal world using RPCs, Remote Procedure Calls. This includes
+ * delivery of non-secure interrupts to for instance allow rescheduling of
+ * the current task.
+ */
+
+static void handle_riscv_rpc_func_cmd_shm_alloc(struct tee_context *ctx,
+ struct optee *optee,
+ struct optee_msg_arg *arg)
+{
+ struct tee_shm *shm;
+
+ if (arg->num_params != 1 ||
+ arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_VALUE_INPUT) {
+ arg->ret = TEEC_ERROR_BAD_PARAMETERS;
+ return;
+ }
+
+ switch (arg->params[0].u.value.a) {
+ case OPTEE_RPC_SHM_TYPE_APPL:
+ shm = optee_rpc_cmd_alloc_suppl(ctx, arg->params[0].u.value.b);
+ break;
+ case OPTEE_RPC_SHM_TYPE_KERNEL:
+ shm = tee_shm_alloc_priv_buf(optee->ctx,
+ arg->params[0].u.value.b);
+ break;
+ default:
+ arg->ret = TEEC_ERROR_BAD_PARAMETERS;
+ return;
+ }
+
+ if (IS_ERR(shm)) {
+ arg->ret = TEEC_ERROR_OUT_OF_MEMORY;
+ return;
+ }
+
+ arg->params[0] = (struct optee_msg_param){
+ .attr = OPTEE_MSG_ATTR_TYPE_FMEM_OUTPUT,
+ .u.fmem.size = tee_shm_get_size(shm),
+ .u.fmem.global_id = shm->sec_world_id,
+ .u.fmem.internal_offs = shm->offset,
+ };
+
+ arg->ret = TEEC_SUCCESS;
+}
+
+static void handle_riscv_rpc_func_cmd_shm_free(struct tee_context *ctx,
+ struct optee *optee,
+ struct optee_msg_arg *arg)
+{
+ struct tee_shm *shm;
+
+ if (arg->num_params != 1 ||
+ arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_VALUE_INPUT)
+ goto err_bad_param;
+
+ shm = optee_shm_from_riscv_handle(optee, arg->params[0].u.value.b);
+ if (!shm)
+ goto err_bad_param;
+ switch (arg->params[0].u.value.a) {
+ case OPTEE_RPC_SHM_TYPE_APPL:
+ optee_rpc_cmd_free_suppl(ctx, shm);
+ break;
+ case OPTEE_RPC_SHM_TYPE_KERNEL:
+ tee_shm_free(shm);
+ break;
+ default:
+ goto err_bad_param;
+ }
+ arg->ret = TEEC_SUCCESS;
+ return;
+
+err_bad_param:
+ arg->ret = TEEC_ERROR_BAD_PARAMETERS;
+}
+
+static void handle_riscv_rpc_func_cmd(struct tee_context *ctx,
+ struct optee *optee,
+ struct optee_msg_arg *arg)
+{
+ arg->ret_origin = TEEC_ORIGIN_COMMS;
+ switch (arg->cmd) {
+ case OPTEE_RPC_CMD_SHM_ALLOC:
+ handle_riscv_rpc_func_cmd_shm_alloc(ctx, optee, arg);
+ break;
+ case OPTEE_RPC_CMD_SHM_FREE:
+ handle_riscv_rpc_func_cmd_shm_free(ctx, optee, arg);
+ break;
+ default:
+ optee_rpc_cmd(ctx, optee, arg);
+ }
+}
+
+static void optee_handle_riscv_rpc(struct tee_context *ctx,
+ struct optee *optee, u32 cmd,
+ struct optee_msg_arg *arg)
+{
+ switch (cmd) {
+ case OPTEE_ABI_YIELDING_CALL_RETURN_RPC_CMD:
+ handle_riscv_rpc_func_cmd(ctx, optee, arg);
+ break;
+ case OPTEE_ABI_YIELDING_CALL_RETURN_INTERRUPT:
+ /* Interrupt delivered by now */
+ break;
+ default:
+ pr_warn("Unknown RPC func 0x%x\n", cmd);
+ break;
+ }
+}
+
+static int optee_riscv_yielding_call(struct tee_context *ctx,
+ u64 in[RPMI_TEE_OPTEE_CALL_REGS],
+ struct optee_msg_arg *rpc_arg,
+ bool system_thread)
+{
+ struct optee *optee = tee_get_drvdata(ctx->teedev);
+ struct optee_call_waiter w;
+ u64 out[RPMI_TEE_OPTEE_RESP_REGS] = { };
+ int rc;
+
+ /* Initialize waiter */
+ optee_cq_wait_init(&optee->call_queue, &w, system_thread);
+ while (true) {
+ rc = optee_riscv_tee_call(optee, in, out);
+ if (rc)
+ goto done;
+
+ switch ((int)out[0]) {
+ case TEEC_SUCCESS:
+ break;
+ case TEEC_ERROR_BUSY:
+ if (in[0] == OPTEE_ABI_YIELDING_CALL_RESUME) {
+ rc = -EIO;
+ goto done;
+ }
+
+ /*
+ * Out of threads in secure world, wait for a thread
+ * to become available.
+ */
+ optee_cq_wait_for_completion(&optee->call_queue, &w);
+ continue;
+ default:
+ rc = -EIO;
+ goto done;
+ }
+
+ if (out[1] == OPTEE_ABI_YIELDING_CALL_RETURN_DONE)
+ goto done;
+
+ /*
+ * OP-TEE has returned with an RPC request.
+ *
+ * Note that out[4] (returned in reg[4]) is already filled in
+ * by optee_riscv_tee_call() returning above.
+ */
+ cond_resched();
+ optee_handle_riscv_rpc(ctx, optee, out[1], rpc_arg);
+ in[0] = OPTEE_ABI_YIELDING_CALL_RESUME;
+ in[1] = 0;
+ in[2] = 0;
+ in[3] = 0;
+ in[4] = out[4]; /* resume info */
+ }
+done:
+ /*
+ * We're done with our thread in secure world, if there are any
+ * thread waiters wake up one.
+ */
+ optee_cq_wait_final(&optee->call_queue, &w);
+
+ return rc;
+}
+
+/**
+ * optee_riscv_do_call_with_arg() - enter OP-TEE in secure world
+ * @ctx: calling context
+ * @shm: shared memory holding the message to pass to secure world
+ * @offs: offset of the message in @shm
+ * @system_thread: true if caller requests TEE system thread support
+ *
+ * Does a TEE_CALL to OP-TEE in secure world and handles the resulting
+ * Remote Procedure Calls (RPC) from OP-TEE. The struct optee_msg_arg is
+ * passed by its parcel handle plus @offs, exactly as FF-A passes it by
+ * shared memory handle.
+ *
+ * Returns return code from OP-TEE, 0 is OK
+ */
+static int optee_riscv_do_call_with_arg(struct tee_context *ctx,
+ struct tee_shm *shm, u_int offs,
+ bool system_thread)
+{
+ u64 in[RPMI_TEE_OPTEE_CALL_REGS] = {
+ OPTEE_ABI_YIELDING_CALL_WITH_ARG,
+ (u32)shm->sec_world_id,
+ shm->sec_world_id >> 32,
+ offs,
+ };
+ struct optee_msg_arg *arg;
+ unsigned int rpc_arg_offs;
+ struct optee_msg_arg *rpc_arg;
+
+ /*
+ * The shared memory object has to start on a page when passed as
+ * an argument struct. This is also what the shm pool allocator
+ * returns, but check this before calling secure world to catch
+ * eventual errors early in case something changes.
+ */
+ if (shm->offset)
+ return -EINVAL;
+
+ arg = tee_shm_get_va(shm, offs);
+ if (IS_ERR(arg))
+ return PTR_ERR(arg);
+
+ rpc_arg_offs = OPTEE_MSG_GET_ARG_SIZE(arg->num_params);
+ rpc_arg = tee_shm_get_va(shm, offs + rpc_arg_offs);
+ if (IS_ERR(rpc_arg))
+ return PTR_ERR(rpc_arg);
+
+ return optee_riscv_yielding_call(ctx, in, rpc_arg, system_thread);
+}
+
+/*
+ * 6. Driver initialization
+ *
+ * During driver initialization the OP-TEE Trusted OS is probed over TEE_CALL
+ * to find out which features it supports so the driver can be initialized
+ * with a matching configuration. These blocking calls mirror the FF-A
+ * OPTEE_FFA_GET_API_VERSION / GET_OS_VERSION / EXCHANGE_CAPABILITIES probes.
+ */
+
+static bool optee_riscv_api_is_compatible(struct optee *optee)
+{
+ u64 in[RPMI_TEE_OPTEE_CALL_REGS] = { OPTEE_ABI_GET_API_VERSION };
+ u64 out[RPMI_TEE_OPTEE_RESP_REGS] = { };
+ int rc;
+
+ rc = optee_riscv_tee_call(optee, in, out);
+ if (rc) {
+ pr_err("Unexpected error %d\n", rc);
+ return false;
+ }
+ if (out[0] != OPTEE_ABI_VERSION_MAJOR ||
+ out[1] < OPTEE_ABI_VERSION_MINOR) {
+ pr_err("Incompatible OP-TEE API version %llu.%llu\n",
+ out[0], out[1]);
+ return false;
+ }
+
+ return true;
+}
+
+static bool optee_riscv_get_os_revision(struct optee *optee)
+{
+ u64 in[RPMI_TEE_OPTEE_CALL_REGS] = { OPTEE_ABI_GET_OS_VERSION };
+ u64 out[RPMI_TEE_OPTEE_RESP_REGS] = { };
+ int rc;
+
+ rc = optee_riscv_tee_call(optee, in, out);
+ if (rc) {
+ pr_err("Unexpected error %d\n", rc);
+ return false;
+ }
+
+ optee->revision.os_major = out[0];
+ optee->revision.os_minor = out[1];
+ optee->revision.os_build_id = out[2];
+
+ if (out[2])
+ pr_info("revision %llu.%llu (%08llx)\n", out[0], out[1],
+ out[2]);
+ else
+ pr_info("revision %llu.%llu\n", out[0], out[1]);
+
+ return true;
+}
+
+static bool optee_riscv_exchange_caps(struct optee *optee, u32 *sec_caps,
+ unsigned int *rpc_param_count,
+ unsigned int *max_notif_value)
+{
+ u64 in[RPMI_TEE_OPTEE_CALL_REGS] = { OPTEE_ABI_EXCHANGE_CAPABILITIES };
+ u64 out[RPMI_TEE_OPTEE_RESP_REGS] = { };
+ int rc;
+
+ rc = optee_riscv_tee_call(optee, in, out);
+ if (rc) {
+ pr_err("Unexpected error %d\n", rc);
+ return false;
+ }
+ if (out[0]) {
+ pr_err("Unexpected exchange error %llu\n", out[0]);
+ return false;
+ }
+
+ *rpc_param_count = (u8)out[1];
+ *sec_caps = out[2];
+ if (out[3])
+ *max_notif_value = out[3];
+ else
+ *max_notif_value = OPTEE_DEFAULT_MAX_NOTIF_VALUE;
+
+ return true;
+}
+
+static void optee_riscv_get_version(struct tee_device *teedev,
+ struct tee_ioctl_version_data *vers)
+{
+ struct tee_ioctl_version_data v = {
+ .impl_id = TEE_IMPL_ID_OPTEE,
+ .impl_caps = TEE_OPTEE_CAP_TZ,
+ .gen_caps = TEE_GEN_CAP_GP | TEE_GEN_CAP_REG_MEM |
+ TEE_GEN_CAP_MEMREF_NULL,
+ };
+
+ *vers = v;
+}
+
+static int optee_riscv_open(struct tee_context *ctx)
+{
+ return optee_open(ctx, true);
+}
+
+static const struct tee_driver_ops optee_riscv_clnt_ops = {
+ .get_version = optee_riscv_get_version,
+ .get_tee_revision = optee_get_revision,
+ .open = optee_riscv_open,
+ .release = optee_release,
+ .open_session = optee_open_session,
+ .close_session = optee_close_session,
+ .invoke_func = optee_invoke_func,
+ .cancel_req = optee_cancel_req,
+ .shm_register = optee_riscv_shm_register,
+ .shm_unregister = optee_riscv_shm_unregister,
+};
+
+static const struct tee_desc optee_riscv_clnt_desc = {
+ .name = DRIVER_NAME "-riscv-clnt",
+ .ops = &optee_riscv_clnt_ops,
+ .owner = THIS_MODULE,
+};
+
+static const struct tee_driver_ops optee_riscv_supp_ops = {
+ .get_version = optee_riscv_get_version,
+ .get_tee_revision = optee_get_revision,
+ .open = optee_riscv_open,
+ .release = optee_release_supp,
+ .supp_recv = optee_supp_recv,
+ .supp_send = optee_supp_send,
+ .shm_register = optee_riscv_shm_register, /* same as for clnt ops */
+ .shm_unregister = optee_riscv_shm_unregister_supp,
+};
+
+static const struct tee_desc optee_riscv_supp_desc = {
+ .name = DRIVER_NAME "-riscv-supp",
+ .ops = &optee_riscv_supp_ops,
+ .owner = THIS_MODULE,
+ .flags = TEE_DESC_PRIVILEGED,
+};
+
+static const struct optee_ops optee_riscv_ops = {
+ .do_call_with_arg = optee_riscv_do_call_with_arg,
+ .to_msg_param = optee_riscv_to_msg_param,
+ .from_msg_param = optee_riscv_from_msg_param,
+};
+
+/*
* The RPMI TEE service group is described in the device tree by a single
* node whose "mboxes" property lists one SBI MPXY channel per hart, in hart
* order. The driver requests each list entry by index and validates the
@@ -186,15 +1042,117 @@ static void optee_riscv_free_channels(struct optee *optee)
}
}
+/* Confirm the TEE service group and read its transport attributes. */
+static int optee_riscv_check_transport(struct optee *optee)
+{
+ struct device *dev = optee->riscv.dev;
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_SERVICEGROUP_ID);
+ ret = optee_riscv_send(optee, &msg);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get service group id\n");
+ if (msg.attr.value != RPMI_SRVGRP_TEE)
+ return dev_err_probe(dev, -ENODEV,
+ "Not a TEE service group channel (0x%x)\n",
+ msg.attr.value);
+
+ rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_MAX_MSG_DATA_SIZE);
+ ret = optee_riscv_send(optee, &msg);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get max msg data size\n");
+ optee->riscv.max_msg_data_size = msg.attr.value;
+
+ return 0;
+}
+
+/*
+ * TEE_PROBE_FEATURES (0x02) reports which framework features are available;
+ * memory parcels carry normal-world shared memory to OP-TEE, so the framework
+ * must support sharing memory between the REE and a TEE.
+ */
+static int optee_riscv_probe_feature(struct optee *optee, u32 feature_id,
+ u32 *value)
+{
+ struct rpmi_tee_probe_features_req tx = {
+ .feature_id = cpu_to_le32(feature_id),
+ };
+ struct rpmi_tee_probe_features_resp rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_PROBE_FEATURES,
+ &tx, sizeof(tx), &rx, sizeof(rx));
+ ret = optee_riscv_send(optee, &msg);
+ if (ret)
+ return ret;
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ if (value)
+ *value = le32_to_cpu(rx.value);
+
+ return 0;
+}
+
+static int optee_riscv_features(struct optee *optee)
+{
+ u32 share = RPMI_TEE_MEMORY_SHARE_NONE;
+ int ret;
+
+ ret = optee_riscv_probe_feature(optee, RPMI_TEE_FEAT_MEMORY_SHARE,
+ &share);
+ if (ret) {
+ pr_err("Failed to probe MEMORY_SHARE feature: %d\n", ret);
+ return ret;
+ }
+ if (share != RPMI_TEE_MEMORY_SHARE_FULL) {
+ pr_err("Framework cannot share memory between REE and TEE (%u)\n",
+ share);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int optee_riscv_enable_notif(struct optee *optee)
+{
+ struct rpmi_tee_probe_features_resp rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_ENABLE_NOTIFICATION,
+ NULL, 0, &rx, sizeof(rx));
+ ret = optee_riscv_send(optee, &msg);
+ if (ret)
+ return ret;
+
+ /*
+ * The TEE service group defines no notification events on this
+ * platform, so RPMI_ERR_NOTSUPP is expected and not fatal.
+ */
+ if (rx.status && le32_to_cpu(rx.status) != (u32)RPMI_ERR_NOTSUPP)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ return 0;
+}
+
static int optee_riscv_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct rpmi_mbox_message msg;
+ unsigned int rpc_param_count;
+ unsigned int max_notif_value;
+ struct tee_shm_pool *pool;
+ struct tee_device *teedev;
+ struct tee_context *ctx;
struct mbox_client *client;
struct optee *optee;
- u32 servicegroup_id;
+ u32 sec_caps;
unsigned int nr_cpus;
- int ret;
+ int rc;
nr_cpus = num_possible_cpus();
if (!nr_cpus)
@@ -206,7 +1164,7 @@ static int optee_riscv_probe(struct platform_device *pdev)
client = devm_kzalloc(dev, sizeof(*client), GFP_KERNEL);
if (!client) {
- ret = -ENOMEM;
+ rc = -ENOMEM;
goto err_free_optee;
}
client->dev = dev;
@@ -221,66 +1179,156 @@ static int optee_riscv_probe(struct platform_device *pdev)
optee->riscv.chan = kcalloc(nr_cpus, sizeof(*optee->riscv.chan),
GFP_KERNEL);
if (!optee->riscv.chan) {
- ret = -ENOMEM;
+ rc = -ENOMEM;
goto err_free_optee;
}
- ret = optee_riscv_request_channels(optee);
- if (ret)
+ rc = optee_riscv_request_channels(optee);
+ if (rc)
goto err_free_channels;
- /* Confirm the channel really speaks the TEE service group. */
- rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_SERVICEGROUP_ID);
- ret = optee_riscv_send(optee, &msg);
- if (ret) {
- dev_err_probe(dev, ret, "Failed to get service group id\n");
+ rc = optee_riscv_check_transport(optee);
+ if (rc)
+ goto err_free_channels;
+
+ rc = optee_riscv_features(optee);
+ if (rc) {
+ dev_err_probe(dev, rc, "Missing required TEE features\n");
goto err_free_channels;
}
- servicegroup_id = msg.attr.value;
- if (servicegroup_id != RPMI_SRVGRP_TEE) {
- ret = -ENODEV;
- dev_err_probe(dev, ret, "Not a TEE service group channel (0x%x)\n",
- servicegroup_id);
+
+ rc = optee_riscv_enable_notif(optee);
+ if (rc) {
+ dev_err_probe(dev, rc, "Failed to enable notifications\n");
goto err_free_channels;
}
- rpmi_mbox_init_get_attribute(&msg, RPMI_MBOX_ATTR_MAX_MSG_DATA_SIZE);
- ret = optee_riscv_send(optee, &msg);
- if (ret) {
- dev_err_probe(dev, ret, "Failed to get max msg data size\n");
+ if (!optee_riscv_api_is_compatible(optee)) {
+ rc = -EINVAL;
goto err_free_channels;
}
- optee->riscv.max_msg_data_size = msg.attr.value;
- ret = optee_riscv_features(optee);
- if (ret) {
- dev_err_probe(dev, ret, "Missing required TEE features\n");
+ if (!optee_riscv_get_os_revision(optee)) {
+ rc = -EINVAL;
goto err_free_channels;
}
- ret = optee_riscv_enable_notif(optee);
- if (ret) {
- dev_err_probe(dev, ret, "Failed to enable notifications\n");
+ if (!optee_riscv_exchange_caps(optee, &sec_caps, &rpc_param_count,
+ &max_notif_value)) {
+ rc = -EINVAL;
+ goto err_free_channels;
+ }
+
+ pool = optee_riscv_shm_pool_alloc_pages();
+ if (IS_ERR(pool)) {
+ rc = PTR_ERR(pool);
goto err_free_channels;
}
+ optee->pool = pool;
+
+ optee->ops = &optee_riscv_ops;
+ optee->rpc_param_count = rpc_param_count;
+
+ if (IS_REACHABLE(CONFIG_RPMB) &&
+ (sec_caps & OPTEE_ABI_SEC_CAP_RPMB_PROBE))
+ optee->in_kernel_rpmb_routing = true;
+
+ teedev = tee_device_alloc(&optee_riscv_clnt_desc, NULL, optee->pool,
+ optee);
+ if (IS_ERR(teedev)) {
+ rc = PTR_ERR(teedev);
+ goto err_free_shm_pool;
+ }
+ optee->teedev = teedev;
+
+ teedev = tee_device_alloc(&optee_riscv_supp_desc, NULL, optee->pool,
+ optee);
+ if (IS_ERR(teedev)) {
+ rc = PTR_ERR(teedev);
+ goto err_unreg_teedev;
+ }
+ optee->supp_teedev = teedev;
+ optee_set_dev_group(optee);
+
+ rc = tee_device_register(optee->teedev);
+ if (rc)
+ goto err_unreg_supp_teedev;
+
+ rc = tee_device_register(optee->supp_teedev);
+ if (rc)
+ goto err_unreg_supp_teedev;
+
+ rc = rhashtable_init(&optee->riscv.global_ids, &shm_rhash_params);
+ if (rc)
+ goto err_unreg_supp_teedev;
+ mutex_init(&optee->riscv.mutex);
+ atomic_set(&optee->riscv.next_nonce, 0);
+ optee_cq_init(&optee->call_queue, 0);
+ optee_supp_init(&optee->supp);
+ optee_shm_arg_cache_init(optee, 0);
+ mutex_init(&optee->rpmb_dev_mutex);
platform_set_drvdata(pdev, optee);
+
+ ctx = teedev_open(optee->teedev);
+ if (IS_ERR(ctx)) {
+ rc = PTR_ERR(ctx);
+ goto err_rhashtable_free;
+ }
+ optee->ctx = ctx;
+
+ rc = optee_notif_init(optee, max_notif_value);
+ if (rc)
+ goto err_close_ctx;
+
+ rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES);
+ if (rc)
+ goto err_unregister_devices;
+
+ INIT_WORK(&optee->rpmb_scan_bus_work, optee_bus_scan_rpmb);
+ optee->rpmb_intf.notifier_call = optee_rpmb_intf_rdev;
+ blocking_notifier_chain_register(&optee_rpmb_intf_added,
+ &optee->rpmb_intf);
+
dev_info(dev, "initialized driver\n");
return 0;
+err_unregister_devices:
+ optee_unregister_devices();
+ optee_notif_uninit(optee);
+err_close_ctx:
+ teedev_close_context(ctx);
+err_rhashtable_free:
+ rhashtable_free_and_destroy(&optee->riscv.global_ids, rh_free_fn, NULL);
+ rpmb_dev_put(optee->rpmb_dev);
+ mutex_destroy(&optee->rpmb_dev_mutex);
+ optee_supp_uninit(&optee->supp);
+ mutex_destroy(&optee->call_queue.mutex);
+ mutex_destroy(&optee->riscv.mutex);
+err_unreg_supp_teedev:
+ tee_device_unregister(optee->supp_teedev);
+err_unreg_teedev:
+ tee_device_unregister(optee->teedev);
+err_free_shm_pool:
+ tee_shm_pool_free(pool);
err_free_channels:
optee_riscv_free_channels(optee);
kfree(optee->riscv.chan);
err_free_optee:
kfree(optee);
- return ret;
+ return rc;
}
static void optee_riscv_remove(struct platform_device *pdev)
{
struct optee *optee = platform_get_drvdata(pdev);
+ optee_remove_common(optee);
+
+ mutex_destroy(&optee->riscv.mutex);
+ rhashtable_free_and_destroy(&optee->riscv.global_ids, rh_free_fn, NULL);
+
optee_riscv_free_channels(optee);
kfree(optee->riscv.chan);
kfree(optee);
diff --git a/drivers/tee/optee/optee_riscv.h b/drivers/tee/optee/optee_riscv.h
index d87298faa6a2..2cdbb1fb4eab 100644
--- a/drivers/tee/optee/optee_riscv.h
+++ b/drivers/tee/optee/optee_riscv.h
@@ -24,6 +24,7 @@
#ifndef __OPTEE_RISCV_H
#define __OPTEE_RISCV_H
+#include <linux/bits.h>
#include <linux/mailbox/riscv-rpmi-message.h>
#include <linux/types.h>
@@ -110,9 +111,13 @@ struct rpmi_tee_probe_features_resp {
{ 0x5b, 0xe1, 0xb1, 0xa0, 0x7e, 0x11, 0x4e, 0x7a, \
0x9b, 0x10, 0x00, 0x10, 0xc0, 0xff, 0xee, 0x00 }
-/* OP-TEE SMC-style call convention carried inside SERVICE_DATA. */
-#define RPMI_TEE_OPTEE_CALL_REGS 8 /* a0-a7 */
-#define RPMI_TEE_OPTEE_RESP_REGS 4 /* a0-a3 */
+/*
+ * OP-TEE FF-A direct message convention carried inside SERVICE_DATA:
+ * five command words each way, the RISC-V analog of the FF-A data0-data4
+ * (w3-w7) set of struct ffa_send_direct_data.
+ */
+#define RPMI_TEE_OPTEE_CALL_REGS 5
+#define RPMI_TEE_OPTEE_RESP_REGS 5
#if __riscv_xlen == 64
typedef __le64 rpmi_xlen_t;
@@ -138,4 +143,128 @@ struct rpmi_tee_call_resp {
rpmi_xlen_t reg[RPMI_TEE_OPTEE_RESP_REGS];
} __packed;
+/*
+ * OP-TEE message ABI carried inside the TEE_CALL SERVICE_DATA words.
+ *
+ * This mirrors the FF-A message ABI in <optee_ffa.h>: OP-TEE and the REE are
+ * peer endpoints and the argument struct optee_msg_arg is passed by shared
+ * memory handle (a parcel id) plus an offset, never by a register block. The
+ * SERVICE_DATA registers carry a small command word set that is the RISC-V
+ * analog of the FF-A w3-w7 register usage:
+ *
+ * reg[0]: command / service id (OPTEE_ABI_YIELDING_CALL_* below)
+ * reg[1]: shared memory handle, lower 32 bits (parcel id)
+ * reg[2]: shared memory handle, upper 32 bits (parcel nonce)
+ * reg[3]: offset into the shared memory to the struct optee_msg_arg
+ * reg[4]: not used on this call, resume info on OPTEE_ABI_YIELDING_CALL_RESUME
+ *
+ * On return the SERVICE_RSP registers carry:
+ * reg[0]: error code, 0 on success
+ * reg[1]: return code (OPTEE_ABI_YIELDING_CALL_RETURN_* below)
+ * reg[2..3]: not used
+ * reg[4]: RPC resume info
+ *
+ * These MUST byte-match the secure world OP-TEE header.
+ */
+#define OPTEE_ABI_BLOCKING_CALL(id) (id)
+#define OPTEE_ABI_YIELDING_CALL_BIT 31
+#define OPTEE_ABI_YIELDING_CALL(id) ((id) | BIT(OPTEE_ABI_YIELDING_CALL_BIT))
+
+/* Blocking (fast) calls, mirror of OPTEE_FFA_BLOCKING_CALL ids. */
+#define OPTEE_ABI_GET_API_VERSION OPTEE_ABI_BLOCKING_CALL(0)
+#define OPTEE_ABI_GET_OS_VERSION OPTEE_ABI_BLOCKING_CALL(1)
+#define OPTEE_ABI_EXCHANGE_CAPABILITIES OPTEE_ABI_BLOCKING_CALL(2)
+#define OPTEE_ABI_UNREGISTER_SHM OPTEE_ABI_BLOCKING_CALL(3)
+#define OPTEE_ABI_ENABLE_ASYNC_NOTIF OPTEE_ABI_BLOCKING_CALL(5)
+
+/* OP-TEE ABI version, mirror of OPTEE_FFA_VERSION_*. */
+#define OPTEE_ABI_VERSION_MAJOR 1
+#define OPTEE_ABI_VERSION_MINOR 0
+
+/* Capabilities returned by EXCHANGE_CAPABILITIES (OPTEE_FFA_SEC_CAP_* analog). */
+#define OPTEE_ABI_SEC_CAP_ARG_OFFSET BIT(0)
+#define OPTEE_ABI_SEC_CAP_ASYNC_NOTIF BIT(1)
+#define OPTEE_ABI_SEC_CAP_RPMB_PROBE BIT(2)
+
+#define OPTEE_ABI_MAX_ASYNC_NOTIF_VALUE 64
+
+/* Yielding calls, mirror of OPTEE_FFA_YIELDING_CALL_*. */
+#define OPTEE_ABI_YIELDING_CALL_WITH_ARG OPTEE_ABI_YIELDING_CALL(0)
+#define OPTEE_ABI_YIELDING_CALL_RESUME OPTEE_ABI_YIELDING_CALL(1)
+
+#define OPTEE_ABI_YIELDING_CALL_RETURN_DONE 0
+#define OPTEE_ABI_YIELDING_CALL_RETURN_RPC_CMD 1
+#define OPTEE_ABI_YIELDING_CALL_RETURN_INTERRUPT 2
+
+/*
+ * Memory parcel wire encodings (RPMI spec section 4.16, Tables 198-207).
+ *
+ * A memory parcel is the RISC-V analog of an FF-A memory-share handle: the REE
+ * creates a parcel describing its pages and OP-TEE accepts it lazily by parcel
+ * id. All fields are little-endian uint32 words; block-list addresses are
+ * expressed in units of 4kB pages.
+ */
+
+/* Memory access encoding (Table 199). */
+#define RPMI_TEE_PARCEL_ACCESS_R BIT(29)
+#define RPMI_TEE_PARCEL_ACCESS_W BIT(30)
+#define RPMI_TEE_PARCEL_ACCESS_X BIT(31)
+
+/* MEM_PARCEL_CREATE flags (Table 200). */
+#define RPMI_TEE_PARCEL_CREATE_FLAG_MULTI_SEGMENT BIT(31)
+#define RPMI_TEE_PARCEL_CREATE_FLAG_OWNER_XFER BIT(30)
+
+/* Length of the parcel LABEL field (Table 200). */
+#define RPMI_TEE_PARCEL_LABEL_LEN 16
+
+/*
+ * A block list entry covers a run of physically contiguous 4kB pages
+ * (Table 198):
+ * BLOCK_HIGH = page-frame number [51:20]
+ * BLOCK_LOW = (page-frame number [19:0] << 12) | (page count - 1)
+ * so a single block spans at most 4096 pages (16MB).
+ */
+#define RPMI_TEE_PARCEL_BLOCK_MAX_PAGES 4096
+
+static inline __le32 rpmi_tee_block_high(u64 pfn)
+{
+ return cpu_to_le32((u32)(pfn >> 20));
+}
+
+static inline __le32 rpmi_tee_block_low(u64 pfn, u32 npages)
+{
+ return cpu_to_le32(((u32)(pfn & 0xfffff) << 12) | (npages - 1));
+}
+
+/*
+ * MEM_PARCEL_CREATE request (Table 200): a fixed header followed by
+ * receiver_id[receiver_cnt], access[receiver_cnt], block_high[block_cnt] and
+ * block_low[block_cnt].
+ */
+struct rpmi_tee_mem_parcel_create_req {
+ __le32 creator_id;
+ __le32 creator_access;
+ __le32 receiver_cnt;
+ __le32 flags;
+ __le32 nonce;
+ __le32 block_cnt;
+ u8 label[RPMI_TEE_PARCEL_LABEL_LEN];
+ __le32 data[];
+};
+
+struct rpmi_tee_mem_parcel_create_resp {
+ __le32 status;
+ __le32 mem_parcel_id;
+};
+
+/* MEM_PARCEL_RECLAIM request (Table 206) / response (Table 207). */
+struct rpmi_tee_mem_parcel_reclaim_req {
+ __le32 mem_parcel_id;
+};
+
+struct rpmi_tee_mem_parcel_reclaim_resp {
+ __le32 status;
+ __le32 flags;
+};
+
#endif /* __OPTEE_RISCV_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC 3/5] optee: riscv: enable persistent shared argument cache
2026-09-12 10:15 [PATCH RFC 0/5] tee: optee: add RISC-V RPMI TEE transport Amirreza Zarrabi
2026-09-12 10:15 ` [PATCH RFC 1/5] optee: riscv: add RPMI TEE service group transport Amirreza Zarrabi
2026-09-12 10:15 ` [PATCH RFC 2/5] optee: riscv: add shared memory and scheduled calls Amirreza Zarrabi
@ 2026-09-12 10:15 ` Amirreza Zarrabi
2026-09-12 10:30 ` sashiko-bot
2026-09-12 10:15 ` [PATCH RFC 4/5] optee: riscv: add asynchronous notifications over the signal bus Amirreza Zarrabi
2026-09-12 10:15 ` [PATCH RFC 5/5] dt-bindings: tee: add RISC-V RPMI TEE transport Amirreza Zarrabi
4 siblings, 1 reply; 10+ messages in thread
From: Amirreza Zarrabi @ 2026-09-12 10:15 UTC (permalink / raw)
To: Jens Wiklander, Sumit Garg, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Rahul Pathak, Anup Patel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-kernel, op-tee, linux-riscv, devicetree,
Amirreza Zarrabi
Use the OP-TEE shared argument cache when secure world advertises
OPTEE_ABI_SEC_CAP_ARG_OFFSET.
In this mode the argument structure can reside at an offset within an
already registered parcel-backed shared-memory buffer, allowing the same
buffer to be reused across calls instead of creating and reclaiming a
parcel for each call.
Signed-off-by: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
---
drivers/tee/optee/optee_riscv.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/tee/optee/optee_riscv.c b/drivers/tee/optee/optee_riscv.c
index 36115326486d..72c9eb85f4fe 100644
--- a/drivers/tee/optee/optee_riscv.c
+++ b/drivers/tee/optee/optee_riscv.c
@@ -1149,6 +1149,7 @@ static int optee_riscv_probe(struct platform_device *pdev)
struct tee_device *teedev;
struct tee_context *ctx;
struct mbox_client *client;
+ u32 arg_cache_flags = 0;
struct optee *optee;
u32 sec_caps;
unsigned int nr_cpus;
@@ -1219,6 +1220,15 @@ static int optee_riscv_probe(struct platform_device *pdev)
goto err_free_channels;
}
+ /*
+ * If OP-TEE can read the argument struct from an offset into a shared
+ * memory buffer, cache and reuse one buffer across calls instead of
+ * creating a fresh parcel per call. This is the persistent pool that
+ * matches the FF-A OPTEE_FFA_SEC_CAP_ARG_OFFSET path.
+ */
+ if (sec_caps & OPTEE_ABI_SEC_CAP_ARG_OFFSET)
+ arg_cache_flags |= OPTEE_SHM_ARG_SHARED;
+
pool = optee_riscv_shm_pool_alloc_pages();
if (IS_ERR(pool)) {
rc = PTR_ERR(pool);
@@ -1266,7 +1276,7 @@ static int optee_riscv_probe(struct platform_device *pdev)
atomic_set(&optee->riscv.next_nonce, 0);
optee_cq_init(&optee->call_queue, 0);
optee_supp_init(&optee->supp);
- optee_shm_arg_cache_init(optee, 0);
+ optee_shm_arg_cache_init(optee, arg_cache_flags);
mutex_init(&optee->rpmb_dev_mutex);
platform_set_drvdata(pdev, optee);
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC 4/5] optee: riscv: add asynchronous notifications over the signal bus
2026-09-12 10:15 [PATCH RFC 0/5] tee: optee: add RISC-V RPMI TEE transport Amirreza Zarrabi
` (2 preceding siblings ...)
2026-09-12 10:15 ` [PATCH RFC 3/5] optee: riscv: enable persistent shared argument cache Amirreza Zarrabi
@ 2026-09-12 10:15 ` Amirreza Zarrabi
2026-09-12 10:26 ` sashiko-bot
2026-09-12 10:15 ` [PATCH RFC 5/5] dt-bindings: tee: add RISC-V RPMI TEE transport Amirreza Zarrabi
4 siblings, 1 reply; 10+ messages in thread
From: Amirreza Zarrabi @ 2026-09-12 10:15 UTC (permalink / raw)
To: Jens Wiklander, Sumit Garg, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Rahul Pathak, Anup Patel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-kernel, op-tee, linux-riscv, devicetree,
Amirreza Zarrabi
Add asynchronous notification support using the RPMI TEE signal bus.
Set up a signal bus between OP-TEE and Linux and use the platform
interrupt as the availability doorbell. When signalled, retrieve pending
signals and pass normal notification values to optee_notif_send(). Reserve
one signal value for requesting the OP-TEE bottom-half handler.
Enable this only when secure world advertises asynchronous notification
support.
Signed-off-by: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
---
drivers/tee/optee/optee_private.h | 11 ++
drivers/tee/optee/optee_riscv.c | 251 ++++++++++++++++++++++++++++++++++++++
drivers/tee/optee/optee_riscv.h | 64 ++++++++++
3 files changed, 326 insertions(+)
diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h
index cf878b8178f9..2ec53bd330a1 100644
--- a/drivers/tee/optee/optee_private.h
+++ b/drivers/tee/optee/optee_private.h
@@ -181,6 +181,12 @@ struct optee_ffa {
* @next_nonce: monotonic nonce source for memory parcel creation
* @mutex: serializes access to @global_ids
* @global_ids: memory parcel id to tee_shm translation table
+ * @notif_wq: workqueue for signal-bus asynchronous notification
+ * @notif_work: work for signal-bus asynchronous notification
+ * @signal_irq: availability doorbell IRQ, or 0 if async notif unused
+ * @sender_signals: number of signals OP-TEE may raise to the REE
+ * @bottom_half_value: signal value that requests an RPC bottom half, or
+ * U32_MAX if async notif is unused
*
* This is the RISC-V analog of struct optee_ffa: communication with secure
* world OP-TEE OS rides the RPMI TEE service group (RPMI spec section 4.16)
@@ -196,6 +202,11 @@ struct optee_riscv {
/* Serializes access to @global_ids */
struct mutex mutex;
struct rhashtable global_ids;
+ struct workqueue_struct *notif_wq;
+ struct work_struct notif_work;
+ unsigned int signal_irq;
+ u32 sender_signals;
+ u32 bottom_half_value;
};
struct optee;
diff --git a/drivers/tee/optee/optee_riscv.c b/drivers/tee/optee/optee_riscv.c
index 72c9eb85f4fe..b9bb813adef5 100644
--- a/drivers/tee/optee/optee_riscv.c
+++ b/drivers/tee/optee/optee_riscv.c
@@ -28,6 +28,7 @@
#include <linux/atomic.h>
#include <linux/errno.h>
+#include <linux/interrupt.h>
#include <linux/mailbox_client.h>
#include <linux/mailbox/riscv-rpmi-message.h>
#include <linux/mm.h>
@@ -46,6 +47,9 @@
#include "optee_riscv.h"
#include "optee_rpc_cmd.h"
+static int optee_riscv_probe_feature(struct optee *optee, u32 feature_id,
+ u32 *value);
+
/*
* Low level RPMI TEE service group transport over the SBI MPXY mailbox.
*
@@ -852,6 +856,242 @@ static int optee_riscv_do_call_with_arg(struct tee_context *ctx,
return optee_riscv_yielding_call(ctx, in, rpc_arg, system_thread);
}
+/*
+ * 5b. Asynchronous notification over the signal bus
+ *
+ * The TEE service group defines no framework notification events (RPMI spec
+ * section 4.16.2), so OP-TEE signals the REE asynchronously over the signal
+ * bus (services 0x05-0x08). This is the RISC-V analog of the FF-A
+ * notification path (optee_ffa_async_notif_init / notif_callback): OP-TEE
+ * raises a signal, the framework rings an availability doorbell delivered as
+ * a System MSI or System IRQ, and the REE retrieves the pending signals with
+ * TEE_SIGNAL_RETRIEVE. A retrieved signal value is the OP-TEE async
+ * notification key; the reserved top value requests an RPC bottom half.
+ */
+
+static void notif_work_fn(struct work_struct *work)
+{
+ struct optee_riscv *optee_riscv = container_of(work, struct optee_riscv,
+ notif_work);
+ struct optee *optee = container_of(optee_riscv, struct optee, riscv);
+
+ optee_do_bottom_half(optee->ctx);
+}
+
+/*
+ * Drain all pending signals from the framework and dispatch them. Returns
+ * true if an RPC bottom half was requested by OP-TEE. TEE_SIGNAL_RETRIEVE
+ * returns the signals of one bus per call and sets MORE_AVAILABLE while other
+ * buses still have pending signals, so loop until it is clear.
+ */
+static bool optee_riscv_retrieve_signals(struct optee *optee)
+{
+ bool do_bottom_half = false;
+ size_t max_signals = optee->riscv.sender_signals;
+ struct rpmi_tee_signal_retrieve_resp *rx;
+ struct rpmi_mbox_message msg;
+ size_t rx_len;
+ u32 flags;
+
+ rx_len = struct_size(rx, signal, max_signals);
+ rx = kzalloc(rx_len, GFP_KERNEL);
+ if (!rx)
+ return false;
+
+ do {
+ u32 status, n, i;
+
+ rpmi_mbox_init_send_with_response(&msg,
+ RPMI_TEE_SRV_SIGNAL_RETRIEVE,
+ NULL, 0, rx, rx_len);
+ if (optee_riscv_send(optee, &msg))
+ break;
+
+ status = le32_to_cpu(rx->status);
+ if (status == (u32)RPMI_ERR_NO_DATA)
+ break;
+ if (status)
+ break;
+
+ n = min_t(u32, le32_to_cpu(rx->signal_len), max_signals);
+ for (i = 0; i < n; i++) {
+ u32 value = le32_to_cpu(rx->signal[i]);
+
+ if (value == OPTEE_ABI_ASYNC_NOTIF_BOTTOM_HALF)
+ do_bottom_half = true;
+ else
+ optee_notif_send(optee, value);
+ }
+
+ flags = le32_to_cpu(rx->flags);
+ } while (flags & RPMI_TEE_SIGNAL_RETRIEVE_MORE_AVAILABLE);
+
+ kfree(rx);
+
+ return do_bottom_half;
+}
+
+static irqreturn_t notif_irq_handler(int irq, void *dev_id)
+{
+ struct optee *optee = dev_id;
+
+ if (optee_riscv_retrieve_signals(optee))
+ queue_work(optee->riscv.notif_wq, &optee->riscv.notif_work);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Arm the OP-TEE asynchronous notification subsystem (OPTEE_ABI_ENABLE_ASYNC_NOTIF
+ * blocking call, the mirror of FF-A's OPTEE_FFA_ENABLE_ASYNC_NOTIF). The reserved
+ * bottom-half signal value is handed to OP-TEE so that a raise of that value is
+ * understood as a request to run the driver bottom half rather than as a plain
+ * notification key.
+ */
+static int optee_riscv_enable_async_notif(struct optee *optee)
+{
+ u64 in[RPMI_TEE_OPTEE_CALL_REGS] = { OPTEE_ABI_ENABLE_ASYNC_NOTIF,
+ optee->riscv.bottom_half_value };
+ u64 out[RPMI_TEE_OPTEE_RESP_REGS] = { };
+ int rc;
+
+ rc = optee_riscv_tee_call(optee, in, out);
+ if (rc)
+ return rc;
+ if (out[0])
+ return -EINVAL;
+
+ return 0;
+}
+
+/*
+ * Set up the signal bus with OP-TEE (TEE_SIGNAL_BUS_SETUP, service 0x05) and
+ * request the availability doorbell IRQ. The bus must be set up by the REE
+ * (RPMI spec section 4.16.7) and is sized so every OP-TEE async notification
+ * value, plus the reserved bottom-half value, maps to a distinct signal that
+ * OP-TEE may raise.
+ */
+static int optee_riscv_setup_signal_bus(struct optee *optee)
+{
+ struct rpmi_tee_signal_bus_setup_req tx = {
+ .target_id = cpu_to_le32(RPMI_TEE_ENDPOINT_OPTEE),
+ .bus_width = cpu_to_le32(OPTEE_ABI_ASYNC_NOTIF_BUS_WIDTH),
+ /*
+ * SENDER_SIGNALS (RPMI spec Table 190) is the number of signals
+ * reserved for us, the sender, to receive: signals 0 <= x < N
+ * are raised by the target (OP-TEE) and read by us. We only
+ * ever receive notifications from OP-TEE and never raise any, so
+ * reserve the whole bus for OP-TEE to raise.
+ */
+ .sender_signals = cpu_to_le32(OPTEE_ABI_ASYNC_NOTIF_BUS_WIDTH),
+ };
+ struct rpmi_tee_signal_bus_setup_resp rx = { };
+ struct rpmi_mbox_message msg;
+ int ret;
+
+ rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_SIGNAL_BUS_SETUP,
+ &tx, sizeof(tx), &rx, sizeof(rx));
+ ret = optee_riscv_send(optee, &msg);
+ if (ret)
+ return ret;
+ if (rx.status)
+ return rpmi_to_linux_error(le32_to_cpu(rx.status));
+
+ return 0;
+}
+
+static void optee_riscv_teardown_signal_bus(struct optee *optee)
+{
+ struct rpmi_tee_signal_bus_teardown_req tx = {
+ .target_id = cpu_to_le32(RPMI_TEE_ENDPOINT_OPTEE),
+ };
+ struct rpmi_tee_signal_bus_teardown_resp rx = { };
+ struct rpmi_mbox_message msg;
+
+ rpmi_mbox_init_send_with_response(&msg,
+ RPMI_TEE_SRV_SIGNAL_BUS_TEARDOWN,
+ &tx, sizeof(tx), &rx, sizeof(rx));
+ optee_riscv_send(optee, &msg);
+}
+
+/*
+ * Discover and enable asynchronous notification. Probe the SIGNAL_BUS
+ * feature word: bits [1:0] give the doorbell transport (System MSI or System
+ * IRQ), [11:2] the maximum bus width and [31:12] the doorbell index. On this
+ * platform the doorbell is wired to the platform device as its interrupt, so
+ * the index is resolved through the DT and requested with platform_get_irq().
+ */
+static int optee_riscv_async_notif_init(struct platform_device *pdev,
+ struct optee *optee)
+{
+ u32 feat = 0;
+ int irq, rc;
+
+ rc = optee_riscv_probe_feature(optee, RPMI_TEE_FEAT_SIGNAL_BUS, &feat);
+ if (rc)
+ return rc;
+
+ if (RPMI_TEE_SIGNAL_BUS_TRANSPORT(feat) == RPMI_TEE_SIGNAL_BUS_NONE)
+ return -EOPNOTSUPP;
+ if (RPMI_TEE_SIGNAL_BUS_WIDTH(feat) < OPTEE_ABI_ASYNC_NOTIF_BUS_WIDTH)
+ return -EOPNOTSUPP;
+
+ irq = platform_get_irq_optional(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ INIT_WORK(&optee->riscv.notif_work, notif_work_fn);
+ optee->riscv.notif_wq = create_workqueue("optee_notification");
+ if (!optee->riscv.notif_wq) {
+ rc = -ENOMEM;
+ goto err;
+ }
+
+ optee->riscv.sender_signals = OPTEE_ABI_ASYNC_NOTIF_BUS_WIDTH;
+
+ rc = optee_riscv_setup_signal_bus(optee);
+ if (rc)
+ goto err_wq;
+
+ rc = request_threaded_irq(irq, NULL, notif_irq_handler, IRQF_ONESHOT,
+ "optee_notification", optee);
+ if (rc)
+ goto err_bus;
+ optee->riscv.signal_irq = irq;
+ optee->riscv.bottom_half_value = OPTEE_ABI_ASYNC_NOTIF_BOTTOM_HALF;
+
+ rc = optee_riscv_enable_async_notif(optee);
+ if (rc)
+ goto err_irq;
+
+ return 0;
+
+err_irq:
+ free_irq(irq, optee);
+ optee->riscv.signal_irq = 0;
+err_bus:
+ optee_riscv_teardown_signal_bus(optee);
+err_wq:
+ destroy_workqueue(optee->riscv.notif_wq);
+ optee->riscv.notif_wq = NULL;
+err:
+ optee->riscv.sender_signals = 0;
+ optee->riscv.bottom_half_value = U32_MAX;
+
+ return rc;
+}
+
+static void optee_riscv_async_notif_uninit(struct optee *optee)
+{
+ if (optee->riscv.bottom_half_value == U32_MAX)
+ return;
+
+ free_irq(optee->riscv.signal_irq, optee);
+ optee_riscv_teardown_signal_bus(optee);
+ destroy_workqueue(optee->riscv.notif_wq);
+ optee->riscv.notif_wq = NULL;
+}
+
/*
* 6. Driver initialization
*
@@ -1238,6 +1478,7 @@ static int optee_riscv_probe(struct platform_device *pdev)
optee->ops = &optee_riscv_ops;
optee->rpc_param_count = rpc_param_count;
+ optee->riscv.bottom_half_value = U32_MAX;
if (IS_REACHABLE(CONFIG_RPMB) &&
(sec_caps & OPTEE_ABI_SEC_CAP_RPMB_PROBE))
@@ -1291,6 +1532,13 @@ static int optee_riscv_probe(struct platform_device *pdev)
if (rc)
goto err_close_ctx;
+ if (sec_caps & OPTEE_ABI_SEC_CAP_ASYNC_NOTIF) {
+ rc = optee_riscv_async_notif_init(pdev, optee);
+ if (rc)
+ dev_warn(dev, "Failed to initialize async notifications: %d\n",
+ rc);
+ }
+
rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES);
if (rc)
goto err_unregister_devices;
@@ -1306,6 +1554,7 @@ static int optee_riscv_probe(struct platform_device *pdev)
err_unregister_devices:
optee_unregister_devices();
+ optee_riscv_async_notif_uninit(optee);
optee_notif_uninit(optee);
err_close_ctx:
teedev_close_context(ctx);
@@ -1334,6 +1583,8 @@ static void optee_riscv_remove(struct platform_device *pdev)
{
struct optee *optee = platform_get_drvdata(pdev);
+ optee_riscv_async_notif_uninit(optee);
+
optee_remove_common(optee);
mutex_destroy(&optee->riscv.mutex);
diff --git a/drivers/tee/optee/optee_riscv.h b/drivers/tee/optee/optee_riscv.h
index 2cdbb1fb4eab..33a714b79f98 100644
--- a/drivers/tee/optee/optee_riscv.h
+++ b/drivers/tee/optee/optee_riscv.h
@@ -82,6 +82,70 @@ enum rpmi_tee_feature_id {
#define RPMI_TEE_MEMORY_SHARE_TEE_ONLY 1
#define RPMI_TEE_MEMORY_SHARE_FULL 2
+/*
+ * SIGNAL_BUS feature word encoding (RPMI spec Table 182, feature id 4).
+ *
+ * The TEE service group defines no framework notification events
+ * (RPMI spec section 4.16.2), so asynchronous notification from OP-TEE rides
+ * the signal bus (services 0x05-0x08) instead. The SIGNAL_BUS feature word
+ * describes both how the availability doorbell is delivered and the shape of
+ * the bus:
+ * [1:0] transport: 0 unsupported, 1 System MSI, 2 System IRQ
+ * [11:2] maximum bus width (number of signals per endpoint pair)
+ * [31:12] System MSI index or System IRQ index of the availability doorbell
+ */
+#define RPMI_TEE_SIGNAL_BUS_TRANSPORT(v) ((v) & GENMASK(1, 0))
+#define RPMI_TEE_SIGNAL_BUS_WIDTH(v) (((v) & GENMASK(11, 2)) >> 2)
+#define RPMI_TEE_SIGNAL_BUS_INDEX(v) (((v) & GENMASK(31, 12)) >> 12)
+
+#define RPMI_TEE_SIGNAL_BUS_NONE 0
+#define RPMI_TEE_SIGNAL_BUS_MSI 1
+#define RPMI_TEE_SIGNAL_BUS_SYSIRQ 2
+
+/*
+ * Signal bus wire encodings (RPMI spec section 4.16.7-4.16.10,
+ * Tables 190-197). The bus is always set up by the REE; signals
+ * 0 <= x < sender_signals are raised by the target (OP-TEE) and read by us.
+ */
+struct rpmi_tee_signal_bus_setup_req {
+ __le32 target_id;
+ __le32 bus_width;
+ __le32 sender_signals;
+};
+
+struct rpmi_tee_signal_bus_setup_resp {
+ __le32 status;
+};
+
+struct rpmi_tee_signal_bus_teardown_req {
+ __le32 target_id;
+};
+
+struct rpmi_tee_signal_bus_teardown_resp {
+ __le32 status;
+};
+
+/* TEE_SIGNAL_RETRIEVE response (Table 197); request carries no data. */
+#define RPMI_TEE_SIGNAL_RETRIEVE_MORE_AVAILABLE BIT(31)
+
+struct rpmi_tee_signal_retrieve_resp {
+ __le32 status;
+ __le32 flags;
+ __le32 target_id;
+ __le32 signal_len;
+ __le32 signal[];
+};
+
+/*
+ * Asynchronous notification signal assignment (frozen contract with secure
+ * world OP-TEE). A raised signal value is the OP-TEE async notification key
+ * verbatim: values 0 <= x < OPTEE_ABI_MAX_ASYNC_NOTIF_VALUE are delivered to
+ * optee_notif_send(), and the reserved top value requests an RPC bottom half.
+ * The bus is therefore sized one wider than the maximum notification value.
+ */
+#define OPTEE_ABI_ASYNC_NOTIF_BOTTOM_HALF OPTEE_ABI_MAX_ASYNC_NOTIF_VALUE
+#define OPTEE_ABI_ASYNC_NOTIF_BUS_WIDTH (OPTEE_ABI_MAX_ASYNC_NOTIF_VALUE + 1)
+
/* TEE_PROBE_FEATURES request (Table 183) / response (Table 184). */
struct rpmi_tee_probe_features_req {
__le32 feature_id;
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC 5/5] dt-bindings: tee: add RISC-V RPMI TEE transport
2026-09-12 10:15 [PATCH RFC 0/5] tee: optee: add RISC-V RPMI TEE transport Amirreza Zarrabi
` (3 preceding siblings ...)
2026-09-12 10:15 ` [PATCH RFC 4/5] optee: riscv: add asynchronous notifications over the signal bus Amirreza Zarrabi
@ 2026-09-12 10:15 ` Amirreza Zarrabi
4 siblings, 0 replies; 10+ messages in thread
From: Amirreza Zarrabi @ 2026-09-12 10:15 UTC (permalink / raw)
To: Jens Wiklander, Sumit Garg, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Rahul Pathak, Anup Patel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-kernel, op-tee, linux-riscv, devicetree,
Amirreza Zarrabi
Add a device-tree binding for the OP-TEE RISC-V transport using the RPMI
TEE service group over SBI MPXY.
Describe one mailbox channel per hart and an optional interrupt used as
the availability doorbell for asynchronous notifications.
Add the binding to the existing OP-TEE MAINTAINERS entry.
Signed-off-by: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
---
.../bindings/tee/riscv,rpmi-mpxy-tee.yaml | 65 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 66 insertions(+)
diff --git a/Documentation/devicetree/bindings/tee/riscv,rpmi-mpxy-tee.yaml b/Documentation/devicetree/bindings/tee/riscv,rpmi-mpxy-tee.yaml
new file mode 100644
index 000000000000..8f6ff313fd42
--- /dev/null
+++ b/Documentation/devicetree/bindings/tee/riscv,rpmi-mpxy-tee.yaml
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/tee/riscv,rpmi-mpxy-tee.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: RISC-V RPMI TEE service group based message proxy
+
+maintainers:
+ - Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
+
+description: |
+ The RISC-V Platform Management Interface (RPMI) [1] defines a messaging
+ protocol which is modular and extensible. The supervisor software can
+ send/receive RPMI messages via the SBI MPXY extension [2] or some dedicated
+ supervisor-mode RPMI transport.
+
+ The RPMI specification [1] defines a TEE service group which is the RISC-V
+ analog of Arm FF-A: OP-TEE and the rich execution environment (REE, i.e.
+ Linux) are peer endpoints and the RPMI framework (machine mode firmware)
+ mediates every message. Entering OP-TEE on a hart runs it on that hart until
+ it responds, so the SBI implementation provides one SBI MPXY channel per
+ hart; all of them are listed, in hart order, on a single node.
+
+ ===========================================
+ References
+ ===========================================
+
+ [1] RISC-V Platform Management Interface (RPMI) v1.0 (or higher)
+ https://github.com/riscv-non-isa/riscv-rpmi/releases
+
+ [2] RISC-V Supervisor Binary Interface (SBI) v3.0 (or higher)
+ https://github.com/riscv-non-isa/riscv-sbi-doc/releases
+
+properties:
+ compatible:
+ const: riscv,rpmi-mpxy-tee
+
+ mboxes:
+ minItems: 1
+ description:
+ One SBI MPXY channel implementing the RPMI TEE service group per hart,
+ listed in the same order as the CPU nodes.
+
+ interrupts:
+ maxItems: 1
+ description:
+ Availability doorbell raised by OP-TEE to signal asynchronous
+ notifications over the RPMI TEE signal bus. Optional; when absent
+ asynchronous notification is disabled.
+
+required:
+ - compatible
+ - mboxes
+
+additionalProperties: false
+
+examples:
+ - |
+ tee {
+ compatible = "riscv,rpmi-mpxy-tee";
+ mboxes = <&mpxy_mbox 0x10 0x0>, <&mpxy_mbox 0x11 0x0>;
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 207a6e2db70c..7d0e550085b2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20577,6 +20577,7 @@ M: Jens Wiklander <jenswi@kernel.org>
L: op-tee@lists.trustedfirmware.org (moderated for non-subscribers)
S: Maintained
F: Documentation/ABI/testing/sysfs-bus-optee-devices
+F: Documentation/devicetree/bindings/tee/riscv,rpmi-mpxy-tee.yaml
F: drivers/tee/optee/
OP-TEE RANDOM NUMBER GENERATOR (RNG) DRIVER
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH RFC 4/5] optee: riscv: add asynchronous notifications over the signal bus
2026-09-12 10:15 ` [PATCH RFC 4/5] optee: riscv: add asynchronous notifications over the signal bus Amirreza Zarrabi
@ 2026-09-12 10:26 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-09-12 10:26 UTC (permalink / raw)
To: Amirreza Zarrabi; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Reusing the `rx` buffer without validating `msg.data.out_response_len` allows stale data to trigger an infinite loop in the threaded IRQ handler.
- [High] Failing to drain pending signals on error paths (such as dynamic memory allocation failure) leads to an interrupt storm.
- [Medium] Swallowing `-EPROBE_DEFER` from `platform_get_irq_optional` permanently disables async notifications on unfavorable driver probe orders.
--
commit 0300738ec4be7419d301effc706a11f0f885fbcb
Author: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
optee: riscv: add asynchronous notifications over the signal bus
This commit adds asynchronous notification support using the RPMI TEE signal
bus. It sets up a signal bus between OP-TEE and Linux, using the platform
interrupt as the availability doorbell.
> diff --git a/drivers/tee/optee/optee_riscv.c b/drivers/tee/optee/optee_riscv.c
> index 72c9eb85f4fed..b9bb813adef50 100644
> --- a/drivers/tee/optee/optee_riscv.c
> +++ b/drivers/tee/optee/optee_riscv.c
[ ... ]
> @@ -852,6 +856,242 @@ static int optee_riscv_do_call_with_arg(struct tee_context *ctx,
> return optee_riscv_yielding_call(ctx, in, rpc_arg, system_thread);
> }
>
> +/*
> + * 5b. Asynchronous notification over the signal bus
[ ... ]
> +static bool optee_riscv_retrieve_signals(struct optee *optee)
> +{
> + bool do_bottom_half = false;
> + size_t max_signals = optee->riscv.sender_signals;
> + struct rpmi_tee_signal_retrieve_resp *rx;
> + struct rpmi_mbox_message msg;
> + size_t rx_len;
> + u32 flags;
> +
> + rx_len = struct_size(rx, signal, max_signals);
> + rx = kzalloc(rx_len, GFP_KERNEL);
> + if (!rx)
> + return false;
[Severity: High]
What happens to the level-triggered hardware interrupt if this memory
allocation fails?
If kzalloc() fails, we abort without draining the pending signals from the
firmware. However, looking at notif_irq_handler() below, it will
unconditionally return IRQ_HANDLED. Will this unmask an uncleared
level-triggered interrupt, causing an immediate interrupt storm?
> +
> + do {
> + u32 status, n, i;
> +
> + rpmi_mbox_init_send_with_response(&msg,
> + RPMI_TEE_SRV_SIGNAL_RETRIEVE,
> + NULL, 0, rx, rx_len);
> + if (optee_riscv_send(optee, &msg))
> + break;
> +
> + status = le32_to_cpu(rx->status);
[Severity: High]
Does this loop properly validate the actual payload length of the response?
Since the rx buffer is reused in this loop, if the RPMI firmware returns a
truncated or empty response, it seems we might read stale fields (like
status, signal_len, and flags) left over from the previous iteration.
Could this lead to an infinite loop if the stale flags still contain the
RPMI_TEE_SIGNAL_RETRIEVE_MORE_AVAILABLE bit?
> + if (status == (u32)RPMI_ERR_NO_DATA)
> + break;
> + if (status)
> + break;
> +
> + n = min_t(u32, le32_to_cpu(rx->signal_len), max_signals);
> + for (i = 0; i < n; i++) {
> + u32 value = le32_to_cpu(rx->signal[i]);
> +
> + if (value == OPTEE_ABI_ASYNC_NOTIF_BOTTOM_HALF)
> + do_bottom_half = true;
> + else
> + optee_notif_send(optee, value);
> + }
> +
> + flags = le32_to_cpu(rx->flags);
> + } while (flags & RPMI_TEE_SIGNAL_RETRIEVE_MORE_AVAILABLE);
> +
> + kfree(rx);
> +
> + return do_bottom_half;
> +}
> +
> +static irqreturn_t notif_irq_handler(int irq, void *dev_id)
> +{
> + struct optee *optee = dev_id;
> +
> + if (optee_riscv_retrieve_signals(optee))
> + queue_work(optee->riscv.notif_wq, &optee->riscv.notif_work);
> +
> + return IRQ_HANDLED;
> +}
[ ... ]
> @@ -1291,6 +1532,13 @@ static int optee_riscv_probe(struct platform_device *pdev)
> if (rc)
> goto err_close_ctx;
>
> + if (sec_caps & OPTEE_ABI_SEC_CAP_ASYNC_NOTIF) {
> + rc = optee_riscv_async_notif_init(pdev, optee);
> + if (rc)
> + dev_warn(dev, "Failed to initialize async notifications: %d\n",
> + rc);
> + }
[Severity: Medium]
Is it intentional to swallow errors from optee_riscv_async_notif_init() here?
If the interrupt controller driver is probed after OP-TEE,
platform_get_irq_optional() inside optee_riscv_async_notif_init() will return
-EPROBE_DEFER.
By swallowing this error code instead of returning it to the driver core,
won't we permanently disable async notifications for this boot session on
unfavorable probe orders?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260912-rpmi-tee-service-grp-dev-v1-0-1d1d35c2a859@oss.qualcomm.com?part=4
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH RFC 1/5] optee: riscv: add RPMI TEE service group transport
2026-09-12 10:15 ` [PATCH RFC 1/5] optee: riscv: add RPMI TEE service group transport Amirreza Zarrabi
@ 2026-09-12 10:27 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-09-12 10:27 UTC (permalink / raw)
To: Amirreza Zarrabi; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The driver incorrectly maps Linux logical CPU IDs to DT mailbox channels and incorrectly sizes the channel array using `num_possible_cpus()`, breaking on sparse CPU masks and differing logical/physical mappings.
- [Medium] Tristate configuration bug causes `optee_riscv.o` to be built as a separate, useless module and silently disables the feature when `CONFIG_RISCV_SBI_MPXY_MBOX` is built as a module (`=m`).
--
commit 24671dee2dd22fa1cba0bfbc89feb8174b438236
Author: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
optee: riscv: add RPMI TEE service group transport
Add an OP-TEE transport for RISC-V using the RPMI TEE service group over
the SBI MPXY mailbox framework.
> diff --git a/drivers/tee/optee/Makefile b/drivers/tee/optee/Makefile
> index ad7049c1c1072..925b8ec7ef688 100644
> --- a/drivers/tee/optee/Makefile
> +++ b/drivers/tee/optee/Makefile
> @@ -9,6 +9,7 @@ optee-objs += supp.o
> optee-objs += device.o
> optee-objs += smc_abi.o
> optee-objs += ffa_abi.o
> +optee-$(CONFIG_RISCV_SBI_MPXY_MBOX) += optee_riscv.o
[Severity: Medium]
Will this fail to link the object into the main optee module when built as
a tristate module?
If CONFIG_RISCV_SBI_MPXY_MBOX is built as a module (=m), this adds
optee_riscv.o to optee-m rather than optee-objs, creating a separate
optee_riscv.ko module instead of properly linking it into optee.ko.
[ ... ]
> diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h
> index aefe1e6f56891..8d22d65e087be 100644
> --- a/drivers/tee/optee/optee_private.h
> +++ b/drivers/tee/optee/optee_private.h
[ ... ]
> @@ -426,5 +453,12 @@ int optee_smc_abi_register(void);
> void optee_smc_abi_unregister(void);
> int optee_ffa_abi_register(void);
> void optee_ffa_abi_unregister(void);
> +#ifdef CONFIG_RISCV_SBI_MPXY_MBOX
> +int optee_riscv_abi_register(void);
> +void optee_riscv_abi_unregister(void);
> +#else
> +static inline int optee_riscv_abi_register(void) { return -EOPNOTSUPP; }
> +static inline void optee_riscv_abi_unregister(void) { }
> +#endif
[Severity: Medium]
Does this silently disable ABI registration when built as a module?
If CONFIG_RISCV_SBI_MPXY_MBOX is =m, Kbuild defines
CONFIG_RISCV_SBI_MPXY_MBOX_MODULE instead. This causes the macro to evaluate
to false, compiling optee.ko against the dummy ABI stub and silently
dropping the feature.
[ ... ]
> diff --git a/drivers/tee/optee/optee_riscv.c b/drivers/tee/optee/optee_riscv.c
> new file mode 100644
> index 0000000000000..0fe4edf92fc9b
> --- /dev/null
> +++ b/drivers/tee/optee/optee_riscv.c
[ ... ]
> +static int optee_riscv_send(struct optee *optee, struct rpmi_mbox_message *msg)
> +{
> + int cpu, ret;
> +
> + cpu = get_cpu();
> + if (cpu >= optee->riscv.nr_chan || !optee->riscv.chan[cpu]) {
[Severity: High]
Can this index out of bounds on systems with sparse CPU topologies?
Because cpu is a logical ID from get_cpu(), it can be greater than or equal
to num_possible_cpus() on architectures with non-contiguous CPU IDs. This
would cause optee_riscv_send() to reject valid harts and route messages to
the wrong physical channel.
> + put_cpu();
> + return -ENODEV;
> + }
> + ret = rpmi_mbox_send_message(optee->riscv.chan[cpu], msg);
> + put_cpu();
> +
> + return ret;
> +}
[ ... ]
> +static int optee_riscv_request_channels(struct optee *optee)
> +{
> + struct device *dev = optee->riscv.dev;
> + int nr_mboxes;
> + unsigned int cpuid;
> +
> + nr_mboxes = of_count_phandle_with_args(dev->of_node, "mboxes",
> + "#mbox-cells");
> + if (nr_mboxes != optee->riscv.nr_chan)
> + return dev_err_probe(dev, -EINVAL,
> + "Expected %u mailbox channels, got %d\n",
> + optee->riscv.nr_chan, nr_mboxes);
> +
> + for (cpuid = 0; cpuid < optee->riscv.nr_chan; cpuid++) {
> + optee->riscv.chan[cpuid] =
> + mbox_request_channel(optee->riscv.client, cpuid);
[Severity: High]
Does this incorrectly assume a 1:1 identity mapping between the Linux logical
CPU ID sequence and the hardware hart order?
Using the loop counter cpuid as the DT property index maps logical IDs
directly to hardware channels, which will route messages incorrectly if the
topology is sparse or harts are disabled.
> + if (IS_ERR(optee->riscv.chan[cpuid])) {
> + int ret = PTR_ERR(optee->riscv.chan[cpuid]);
> +
> + optee->riscv.chan[cpuid] = NULL;
> + return dev_err_probe(dev, ret,
> + "Failed to request channel %u\n",
> + cpuid);
> + }
> + }
> +
> + return 0;
> +}
[ ... ]
> +static int optee_riscv_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct rpmi_mbox_message msg;
> + struct mbox_client *client;
> + struct optee *optee;
> + u32 servicegroup_id;
> + unsigned int nr_cpus;
> + int ret;
> +
> + nr_cpus = num_possible_cpus();
> + if (!nr_cpus)
> + return dev_err_probe(dev, -ENODEV, "No harts found\n");
> +
[ ... ]
> + optee->riscv.dev = dev;
> + optee->riscv.client = client;
> + optee->riscv.nr_chan = nr_cpus;
> + optee->riscv.chan = kcalloc(nr_cpus, sizeof(*optee->riscv.chan),
> + GFP_KERNEL);
[Severity: High]
Is num_possible_cpus() the right size for this array?
Since the array is later indexed by the logical CPU ID in
optee_riscv_send(), it should likely be sized by nr_cpu_ids instead to
prevent out-of-bounds access vulnerabilities.
> + if (!optee->riscv.chan) {
> + ret = -ENOMEM;
> + goto err_free_optee;
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260912-rpmi-tee-service-grp-dev-v1-0-1d1d35c2a859@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH RFC 3/5] optee: riscv: enable persistent shared argument cache
2026-09-12 10:15 ` [PATCH RFC 3/5] optee: riscv: enable persistent shared argument cache Amirreza Zarrabi
@ 2026-09-12 10:30 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-09-12 10:30 UTC (permalink / raw)
To: Amirreza Zarrabi; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Missing .system_session callback in optee_riscv_clnt_ops leads to -EINVAL and potential deadlocks in SCMI.
Pre-existing issues:
- [High] Preexisting heap out-of-bounds write and newly reachable memory exhaustion in optee_get_msg_arg() when Secure World provides a large rpc_param_count.
--
commit 4888de794792f9ffe4984b86932a02db3b6e5b62
Author: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
optee: riscv: enable persistent shared argument cache
This commit uses the OP-TEE shared argument cache when the secure world
advertises OPTEE_ABI_SEC_CAP_ARG_OFFSET. It allows the argument structure
to reside at an offset within a registered shared-memory buffer to reuse
buffers across calls.
> diff --git a/drivers/tee/optee/optee_riscv.c b/drivers/tee/optee/optee_riscv.c
> index 36115326486d6..72c9eb85f4fed 100644
> --- a/drivers/tee/optee/optee_riscv.c
> +++ b/drivers/tee/optee/optee_riscv.c
[ ... ]
> @@ -1219,6 +1220,15 @@ static int optee_riscv_probe(struct platform_device *pdev)
> goto err_free_channels;
> }
>
> + /*
> + * If OP-TEE can read the argument struct from an offset into a shared
> + * memory buffer, cache and reuse one buffer across calls instead of
> + * creating a fresh parcel per call. This is the persistent pool that
> + * matches the FF-A OPTEE_FFA_SEC_CAP_ARG_OFFSET path.
> + */
> + if (sec_caps & OPTEE_ABI_SEC_CAP_ARG_OFFSET)
> + arg_cache_flags |= OPTEE_SHM_ARG_SHARED;
> +
[Severity: High]
This is a pre-existing issue, but there appears to be a heap out-of-bounds
write and memory exhaustion in optee_get_msg_arg() when the Secure World
provides a large rpc_param_count. This becomes actively reachable memory
exhaustion when OPTEE_SHM_ARG_SHARED is enabled here.
During probe, optee_riscv_exchange_caps() reads out[1] into rpc_param_count,
which can be up to 255. optee_msg_arg_size() then calculates a required size
sz that can exceed SHM_ENTRY_SIZE (which is hardcoded to PAGE_SIZE).
In optee_get_msg_arg() in drivers/tee/optee/call.c:
res = tee_shm_alloc_kernel_buf(ctx, SHM_ENTRY_SIZE);
...
memset(ma, 0, sz);
Does this blindly allocate exactly SHM_ENTRY_SIZE bytes but then
unconditionally zero-fill sz bytes, leading to an out-of-bounds write?
Furthermore, because this patch explicitly enables OPTEE_SHM_ARG_SHARED, the
cache logic in optee_get_msg_arg() computes:
args_per_entry = SHM_ENTRY_SIZE / sz;
If sz > SHM_ENTRY_SIZE, args_per_entry evaluates to 0.
This makes the cache reuse condition bit < args_per_entry (evaluated as
bit < 0) always false. Does this silently drop the allocation reference into
the cache list and allocate a fresh page on every TEE call without ever
reusing them, leading to memory exhaustion?
[ ... ]
> @@ -1266,7 +1276,7 @@ static int optee_riscv_probe(struct platform_device *pdev)
> atomic_set(&optee->riscv.next_nonce, 0);
> optee_cq_init(&optee->call_queue, 0);
> optee_supp_init(&optee->supp);
> - optee_shm_arg_cache_init(optee, 0);
> + optee_shm_arg_cache_init(optee, arg_cache_flags);
> mutex_init(&optee->rpmb_dev_mutex);
> platform_set_drvdata(pdev, optee);
[Severity: High]
While enabling the persistent shared argument cache here, does the
optee_riscv_clnt_ops structure in drivers/tee/optee/optee_riscv.c lack the
.system_session callback?
The SCMI OP-TEE transport relies on tee_client_system_session() to mark its
sessions as system threads, which prevents thread exhaustion and deadlocks
under heavy load.
tee_client_system_session() unconditionally depends on the
tee_driver_ops->system_session callback. Without this callback assigned
(e.g. .system_session = optee_system_session), calls from the SCMI driver
fail with -EINVAL.
Could this force it to fall back to a non-system session and lead to deadlocks
if SCMI is needed for power management during normal OP-TEE tasks?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260912-rpmi-tee-service-grp-dev-v1-0-1d1d35c2a859@oss.qualcomm.com?part=3
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH RFC 2/5] optee: riscv: add shared memory and scheduled calls
2026-09-12 10:15 ` [PATCH RFC 2/5] optee: riscv: add shared memory and scheduled calls Amirreza Zarrabi
@ 2026-09-12 10:31 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-09-12 10:31 UTC (permalink / raw)
To: Amirreza Zarrabi; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Valid memory parcel IDs >= 0x80000000 are incorrectly treated as negative error codes due to sign extension and int truncation.
- [High] Array sizing and indexing use num_possible_cpus() instead of nr_cpu_ids, causing out-of-bounds rejections on systems with sparse logical CPU IDs.
- [High] The rhashtable element `struct shm_rhash` is freed synchronously with `kfree()` upon removal, causing a use-after-free race with the background rhashtable resizing worker.
- [Medium] Missing call to optee_shm_arg_cache_uninit() on the probe error path causing a resource leak.
--
commit 7782cc7b47fb82ba73be35240ca31c1527d2ca1e
Author: Amirreza Zarrabi <amirreza.zarrabi@oss.qualcomm.com>
Date: Sat Sep 12 10:15:13 2026 +0000
optee: riscv: add shared memory and scheduled calls
Add the shared-memory and call support required by the RISC-V RPMI
transport.
> diff --git a/drivers/tee/optee/optee_riscv.c b/drivers/tee/optee/optee_riscv.c
> index 0fe4edf92fc9b..36115326486d6 100644
> --- a/drivers/tee/optee/optee_riscv.c
> +++ b/drivers/tee/optee/optee_riscv.c
[ ... ]
> @@ -86,61 +116,887 @@ static int optee_riscv_probe_feature(struct optee *optee, u32 feature_id,
> if (rx.status)
> return rpmi_to_linux_error(le32_to_cpu(rx.status));
>
> - if (value)
> - *value = le32_to_cpu(rx.value);
> + for (i = 0; i < RPMI_TEE_OPTEE_RESP_REGS; i++)
> + out[i] = rpmi_xlen_to_cpu(rx.reg[i]);
>
> return 0;
> }
>
> -static int optee_riscv_features(struct optee *optee)
> +/*
> + * 1. Maintain a hash table for lookup of a memory parcel id
> + *
> + * The RPMI framework assigns a memory parcel id for each piece of shared
> + * memory. Together with a caller-supplied nonce it forms the wire identity
> + * used when communicating with secure world, playing the exact role of the
> + * FF-A global memory handle.
> + *
> + * Main functions are optee_shm_add_riscv_handle() and
> + * optee_shm_rem_riscv_handle().
> + */
> +struct shm_rhash {
> + struct tee_shm *shm;
> + u64 global_id;
> + struct rhash_head linkage;
> +};
[Severity: High]
Does this struct need an rcu_head field for safe RCU-deferred freeing?
Since the hash table is initialized with automatic_shrinking = true, the
RCU-based background resizing worker (rht_deferred_worker) can traverse the
hash table concurrently.
> +
> +static void rh_free_fn(void *ptr, void *arg)
> {
> - u32 share = RPMI_TEE_MEMORY_SHARE_NONE;
> + kfree(ptr);
> +}
> +
> +static const struct rhashtable_params shm_rhash_params = {
> + .head_offset = offsetof(struct shm_rhash, linkage),
> + .key_len = sizeof(u64),
> + .key_offset = offsetof(struct shm_rhash, global_id),
> + .automatic_shrinking = true,
> +};
[ ... ]
> +static int optee_shm_rem_riscv_handle(struct optee *optee, u64 global_id)
> +{
> + struct shm_rhash *r;
> + int rc = -ENOENT;
> +
> + mutex_lock(&optee->riscv.mutex);
> + r = rhashtable_lookup_fast(&optee->riscv.global_ids, &global_id,
> + shm_rhash_params);
> + if (r)
> + rc = rhashtable_remove_fast(&optee->riscv.global_ids,
> + &r->linkage, shm_rhash_params);
> + mutex_unlock(&optee->riscv.mutex);
> +
> + if (!rc)
> + kfree(r);
[Severity: High]
Can freeing this element synchronously with kfree() cause a use-after-free
with the background rhashtable resizing worker?
Without an RCU grace period (using kfree_rcu()), the background worker might
access the freed linkage pointers.
> +
> + return rc;
> +}
[ ... ]
> +static int optee_riscv_parcel_create(struct optee *optee, struct page **pages,
> + size_t num_pages, u32 nonce)
> +{
[ ... ]
> + rpmi_mbox_init_send_with_response(&msg, RPMI_TEE_SRV_MEM_PARCEL_CREATE,
> + req, req_len, &rx, sizeof(rx));
> + ret = optee_riscv_send(optee, &msg);
> + kfree(req);
> + if (ret)
> + return ret;
> + if (rx.status)
> + return rpmi_to_linux_error(le32_to_cpu(rx.status));
>
> - return 0;
> + return le32_to_cpu(rx.mem_parcel_id);
> }
[Severity: High]
Since this function returns an int, what happens if rx.mem_parcel_id is a
valid 32-bit unsigned ID with the most significant bit set (>= 0x80000000)?
The unsigned value would be sign-extended to a negative integer, causing the
caller to misinterpret it as an error code.
> +
> +static int optee_riscv_shm_register(struct tee_context *ctx,
> + struct tee_shm *shm, struct page **pages,
> + size_t num_pages, unsigned long start)
> +{
[ ... ]
> + nonce = (u32)atomic_inc_return(&optee->riscv.next_nonce);
> + rc = optee_riscv_parcel_create(optee, pages, num_pages, nonce);
> + if (rc < 0)
> + return rc;
[Severity: High]
Following up on the previous question, if a valid memory parcel ID >=
0x80000000 is returned, will this error check mistakenly trigger?
This would result in returning early without reclaiming the parcel,
permanently leaking it in the firmware and causing the allocation to fail.
> + global_id = (u32)rc | ((u64)nonce << 32);
> +
> + rc = optee_shm_add_riscv_handle(optee, shm, global_id);
> + if (rc) {
> + optee_riscv_parcel_reclaim(optee, (u32)global_id);
> + return rc;
> + }
[ ... ]
> static int optee_riscv_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> - struct rpmi_mbox_message msg;
> + unsigned int rpc_param_count;
> + unsigned int max_notif_value;
> + struct tee_shm_pool *pool;
> + struct tee_device *teedev;
> + struct tee_context *ctx;
> struct mbox_client *client;
> struct optee *optee;
> - u32 servicegroup_id;
> + u32 sec_caps;
> unsigned int nr_cpus;
> - int ret;
> + int rc;
>
> nr_cpus = num_possible_cpus();
> if (!nr_cpus)
> @@ -206,7 +1164,7 @@ static int optee_riscv_probe(struct platform_device *pdev)
>
> client = devm_kzalloc(dev, sizeof(*client), GFP_KERNEL);
> if (!client) {
> - ret = -ENOMEM;
> + rc = -ENOMEM;
> goto err_free_optee;
> }
> client->dev = dev;
> @@ -221,66 +1179,156 @@ static int optee_riscv_probe(struct platform_device *pdev)
> optee->riscv.chan = kcalloc(nr_cpus, sizeof(*optee->riscv.chan),
> GFP_KERNEL);
[Severity: High]
Does allocating this array using num_possible_cpus() risk being too small
on systems with sparse logical CPU IDs?
If a sparse logical CPU ID is retrieved via get_cpu() in optee_riscv_send(),
it might exceed the population count used to size this array.
For example, if get_cpu() returns a sparse ID that is numerically larger
than the population count, optee_riscv_send() checks:
cpu = get_cpu();
if (cpu >= optee->riscv.nr_chan || !optee->riscv.chan[cpu]) {
put_cpu();
return -ENODEV;
}
This could lead to valid CPUs being incorrectly rejected because their ID is
>= nr_cpus, even though they are valid logical CPUs. Should nr_cpu_ids be
used instead?
> if (!optee->riscv.chan) {
> - ret = -ENOMEM;
> + rc = -ENOMEM;
> goto err_free_optee;
> }
[ ... ]
> + rc = rhashtable_init(&optee->riscv.global_ids, &shm_rhash_params);
> + if (rc)
> + goto err_unreg_supp_teedev;
> + mutex_init(&optee->riscv.mutex);
> + atomic_set(&optee->riscv.next_nonce, 0);
> + optee_cq_init(&optee->call_queue, 0);
> + optee_supp_init(&optee->supp);
> + optee_shm_arg_cache_init(optee, 0);
> + mutex_init(&optee->rpmb_dev_mutex);
> platform_set_drvdata(pdev, optee);
> +
> + ctx = teedev_open(optee->teedev);
> + if (IS_ERR(ctx)) {
> + rc = PTR_ERR(ctx);
> + goto err_rhashtable_free;
> + }
> + optee->ctx = ctx;
[ ... ]
> +err_rhashtable_free:
> + rhashtable_free_and_destroy(&optee->riscv.global_ids, rh_free_fn, NULL);
> + rpmb_dev_put(optee->rpmb_dev);
> + mutex_destroy(&optee->rpmb_dev_mutex);
> + optee_supp_uninit(&optee->supp);
> + mutex_destroy(&optee->call_queue.mutex);
> + mutex_destroy(&optee->riscv.mutex);
[Severity: Medium]
Should there be a call to optee_shm_arg_cache_uninit() in this error path?
It looks like the resources allocated by optee_shm_arg_cache_init() might
leak if probe fails here.
> +err_unreg_supp_teedev:
> + tee_device_unregister(optee->supp_teedev);
> +err_unreg_teedev:
> + tee_device_unregister(optee->teedev);
> +err_free_shm_pool:
> + tee_shm_pool_free(pool);
> err_free_channels:
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260912-rpmi-tee-service-grp-dev-v1-0-1d1d35c2a859@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-09-12 10:31 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-12 10:15 [PATCH RFC 0/5] tee: optee: add RISC-V RPMI TEE transport Amirreza Zarrabi
2026-09-12 10:15 ` [PATCH RFC 1/5] optee: riscv: add RPMI TEE service group transport Amirreza Zarrabi
2026-09-12 10:27 ` sashiko-bot
2026-09-12 10:15 ` [PATCH RFC 2/5] optee: riscv: add shared memory and scheduled calls Amirreza Zarrabi
2026-09-12 10:31 ` sashiko-bot
2026-09-12 10:15 ` [PATCH RFC 3/5] optee: riscv: enable persistent shared argument cache Amirreza Zarrabi
2026-09-12 10:30 ` sashiko-bot
2026-09-12 10:15 ` [PATCH RFC 4/5] optee: riscv: add asynchronous notifications over the signal bus Amirreza Zarrabi
2026-09-12 10:26 ` sashiko-bot
2026-09-12 10:15 ` [PATCH RFC 5/5] dt-bindings: tee: add RISC-V RPMI TEE transport Amirreza Zarrabi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox