[PATCH RFC v2 8/8] tee: optee: support RPMI asynchronous notification doorbells

From: Amirreza Zarrabi

Date: Mon Oct 05 2026 - 20:44:28 EST


OP-TEE uses asynchronous notifications to request bottom-half
processing in normal world without waiting for a client call.

Allocate a TEE-to-REE RPMI signal and pass its ID to OP-TEE when
enabling asynchronous notifications. Queue bottom-half processing on
a private workqueue.

Initialize notification delivery before publishing the TEE devices.
Allow probe to continue if setup fails, keeping synchronous RPC
notifications available.

On cleanup, request that OP-TEE stop asynchronous notifications,
relinquish the signal and drain the bottom-half workqueue. Transport
relinquishment does not synchronize an already-selected callback.

Signed-off-by: Amirreza Zarrabi <amirreza.zarrabi@xxxxxxxxxxxxxxxx>
---
drivers/tee/optee/optee_private.h | 6 +++
drivers/tee/optee/rpmi_abi.c | 87 +++++++++++++++++++++++++++++++++++++++
2 files changed, 93 insertions(+)

diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h
index 07c27e322a71..e5c6381bb872 100644
--- a/drivers/tee/optee/optee_private.h
+++ b/drivers/tee/optee/optee_private.h
@@ -190,6 +190,9 @@ struct rpmi_tee_device;
* @shm_rht: lookup by the host-endian parcel ID and nonce pair
* @sec_caps: negotiated optional OPTEE_RPMI_CAP_* features
* @notification_count: negotiated nonzero number of logical notification keys
+ * @signal: allocated TEE-to-REE doorbell, independent of logical keys
+ * @notif_wq: private bottom-half workqueue, NULL when notifications are inactive
+ * @notif_work: processes bottom halves requested by the RPMI signal
*
* Callers keep their tee_shm alive while using its registration. Lookup
* returns a raw pointer; the mutex does not protect its lifetime after
@@ -203,6 +206,9 @@ struct optee_rpmi {
struct rhashtable shm_rht;
u32 sec_caps;
u32 notification_count;
+ u32 signal;
+ struct workqueue_struct *notif_wq;
+ struct work_struct notif_work;
};
#endif

diff --git a/drivers/tee/optee/rpmi_abi.c b/drivers/tee/optee/rpmi_abi.c
index db541f3de425..ffe44d81e857 100644
--- a/drivers/tee/optee/rpmi_abi.c
+++ b/drivers/tee/optee/rpmi_abi.c
@@ -685,6 +685,88 @@ static int optee_rpmi_do_call_with_arg(struct tee_context *ctx,
return optee_rpmi_yielding_call(ctx, &req, rpc_arg, system_thread);
}

+/* Yielding bottom halves run here, not in the transport retrieval worker. */
+static void optee_rpmi_notif_work(struct work_struct *work)
+{
+ struct optee_rpmi *rpmi = container_of(work, struct optee_rpmi, notif_work);
+ struct optee *optee = container_of(rpmi, struct optee, rpmi);
+
+ optee_do_bottom_half(optee->ctx);
+}
+
+static void optee_rpmi_notif_callback(struct rpmi_tee_device *rdev, u32 signal,
+ void *cb_data)
+{
+ struct optee *optee = cb_data;
+
+ queue_work(optee->rpmi.notif_wq, &optee->rpmi.notif_work);
+}
+
+/* Relinquish is not a barrier for an already-selected transport callback. */
+static void optee_rpmi_async_notif_uninit(struct optee *optee)
+{
+ struct optee_rpmi *rpmi = &optee->rpmi;
+ struct rpmi_tee_device *rdev = rpmi->rdev;
+ int ret;
+
+ if (!rpmi->notif_wq)
+ return;
+
+ ret = optee_stop_async_notif(optee->ctx);
+ if (ret)
+ dev_warn(&rdev->dev, "stop notifications failed: %d\n", ret);
+
+ ret = rdev->ops->notifier_ops->notify_relinquish(rdev, rpmi->signal);
+ if (ret && ret != -EOPNOTSUPP)
+ dev_warn(&rdev->dev,
+ "relinquish notification failed: %d\n", ret);
+
+ destroy_workqueue(rpmi->notif_wq);
+
+ rpmi->notif_wq = NULL;
+}
+
+/* Enable OP-TEE's bottom-half doorbell using the reserved RPMI signal. */
+static int optee_rpmi_enable_async_notif(struct optee *optee)
+{
+ struct optee_rpmi_enable_notif_req req = {
+ .op = cpu_to_le32(OPTEE_RPMI_ENABLE_ASYNC_NOTIF),
+ .signal_id = cpu_to_le32(optee->rpmi.signal),
+ };
+ struct optee_rpmi_status_resp resp;
+
+ return optee_rpmi_call(optee, &req, sizeof(req), &resp, sizeof(resp));
+}
+
+static int optee_rpmi_async_notif_init(struct optee *optee)
+{
+ struct optee_rpmi *rpmi = &optee->rpmi;
+ struct rpmi_tee_device *rdev = rpmi->rdev;
+ int ret;
+
+ INIT_WORK(&rpmi->notif_work, optee_rpmi_notif_work);
+ rpmi->notif_wq = alloc_workqueue("optee_rpmi_notif", WQ_UNBOUND, 1);
+ if (!rpmi->notif_wq)
+ return -ENOMEM;
+
+ ret = rdev->ops->notifier_ops->notify_request(rdev,
+ optee_rpmi_notif_callback,
+ optee, &rpmi->signal);
+ if (ret) {
+ destroy_workqueue(rpmi->notif_wq);
+ /* Checked in optee_rpmi_async_notif_uninit(). */
+ rpmi->notif_wq = NULL;
+
+ return ret;
+ }
+
+ ret = optee_rpmi_enable_async_notif(optee);
+ if (ret)
+ optee_rpmi_async_notif_uninit(optee);
+
+ return ret;
+}
+
/* Query and store the trusted OS revision. */
static int optee_rpmi_get_os_version(struct optee *optee)
{
@@ -823,6 +905,7 @@ static void optee_rpmi_remove(struct rpmi_tee_device *rdev)
{
struct optee *optee = dev_get_drvdata(&rdev->dev);

+ optee_rpmi_async_notif_uninit(optee);
optee_remove_common(optee);
optee_rpmi_shm_rht_uninit(optee);
kfree(optee);
@@ -901,6 +984,10 @@ static int optee_rpmi_probe(struct rpmi_tee_device *rdev)

optee->ctx = ctx;
dev_set_drvdata(&rdev->dev, optee);
+ ret = optee_rpmi_async_notif_init(optee);
+ if (ret)
+ dev_warn(&rdev->dev, "async notifications unavailable: %d\n", ret);
+
if (optee->in_kernel_rpmb_routing)
blocking_notifier_chain_register(&optee_rpmb_intf_added,
&optee->rpmb_intf);

--
2.34.1