[PATCH RFC 4/5] optee: riscv: add asynchronous notifications over the signal bus

Amirreza Zarrabi amirreza.zarrabi at oss.qualcomm.com
Sat Sep 12 03:15:15 PDT 2026


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 at 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




More information about the linux-riscv mailing list