[PATCH RFC v2 7/8] tee: optee: bind RPMI services and negotiate backend capabilities

From: Amirreza Zarrabi

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


Register an RPMI service driver matching the OP-TEE service UUID and
integrate it with OP-TEE module initialization and removal.

Check the service API version, query the trusted OS revision, and
obtain the RPC parameter and logical notification counts. Initialize
shared-memory tracking, the call queue, supplicant state and internal
context before publishing the client and supplicant TEE devices.

Connect the RPMI backend to the common OP-TEE operations and enumerate
trusted application devices. Enable in-kernel RPMB routing when the
RPMB subsystem is reachable.

Add removal and probe failure cleanup for the backend resources.

Signed-off-by: Amirreza Zarrabi <amirreza.zarrabi@xxxxxxxxxxxxxxxx>
---
drivers/tee/optee/core.c | 10 +-
drivers/tee/optee/optee_private.h | 21 ++-
drivers/tee/optee/rpmi_abi.c | 286 ++++++++++++++++++++++++++++++++++++++
3 files changed, 312 insertions(+), 5 deletions(-)

diff --git a/drivers/tee/optee/core.c b/drivers/tee/optee/core.c
index a52c1f498b99..8a44a25ebc66 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 rpmi_abi_rc;
static bool intf_is_regged;

static int __init optee_core_init(void)
@@ -245,14 +246,15 @@ static int __init optee_core_init(void)

smc_abi_rc = optee_smc_abi_register();
ffa_abi_rc = optee_ffa_abi_register();
+ rpmi_abi_rc = optee_rpmi_abi_register();

- /* If both failed there's no point with this module */
- if (smc_abi_rc && ffa_abi_rc) {
+ /* Keep the module if any supported transport registered successfully. */
+ if (smc_abi_rc && ffa_abi_rc && rpmi_abi_rc) {
if (IS_REACHABLE(CONFIG_RPMB)) {
rpmb_interface_unregister(&rpmb_class_intf);
intf_is_regged = false;
}
- return smc_abi_rc;
+ return -EOPNOTSUPP;
}

return 0;
@@ -270,6 +272,8 @@ static void __exit optee_core_exit(void)
optee_smc_abi_unregister();
if (!ffa_abi_rc)
optee_ffa_abi_unregister();
+ if (!rpmi_abi_rc)
+ optee_rpmi_abi_unregister();
}
module_exit(optee_core_exit);

diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h
index 2422caf3c883..07c27e322a71 100644
--- a/drivers/tee/optee/optee_private.h
+++ b/drivers/tee/optee/optee_private.h
@@ -188,6 +188,8 @@ struct rpmi_tee_device;
* @rdev: owning RPMI service device
* @shm_rht_lock: protects parcel lookup, insertion, removal and publication
* @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
*
* Callers keep their tee_shm alive while using its registration. Lookup
* returns a raw pointer; the mutex does not protect its lifetime after
@@ -199,6 +201,8 @@ struct optee_rpmi {
/* Protects parcel lookup, insertion, removal and publication. */
struct mutex shm_rht_lock;
struct rhashtable shm_rht;
+ u32 sec_caps;
+ u32 notification_count;
};
#endif

@@ -211,8 +215,8 @@ struct optee;
* @os_build_id: OP-TEE OS build identifier (0 if unspecified)
*
* Values come from OPTEE_SMC_CALL_GET_OS_REVISION (SMC ABI) or
- * OPTEE_FFA_GET_OS_VERSION (FF-A ABI); this is the trusted OS revision, not an
- * FF-A ABI version.
+ * OPTEE_FFA_GET_OS_VERSION (FF-A ABI) or OPTEE_RPMI_GET_OS_VERSION (RPMI ABI).
+ * This is the trusted OS revision, not a transport ABI version.
*/
struct optee_revision {
u32 os_major;
@@ -490,5 +494,18 @@ static inline void optee_ffa_abi_unregister(void)
}
#endif

+#if IS_REACHABLE(CONFIG_RISCV_RPMI_TEE_TRANSPORT)
+int optee_rpmi_abi_register(void);
+void optee_rpmi_abi_unregister(void);
+#else
+static inline int optee_rpmi_abi_register(void)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void optee_rpmi_abi_unregister(void)
+{
+}
+#endif

#endif /*OPTEE_PRIVATE_H*/
diff --git a/drivers/tee/optee/rpmi_abi.c b/drivers/tee/optee/rpmi_abi.c
index 6e76316794c1..db541f3de425 100644
--- a/drivers/tee/optee/rpmi_abi.c
+++ b/drivers/tee/optee/rpmi_abi.c
@@ -684,3 +684,289 @@ static int optee_rpmi_do_call_with_arg(struct tee_context *ctx,

return optee_rpmi_yielding_call(ctx, &req, rpc_arg, system_thread);
}
+
+/* Query and store the trusted OS revision. */
+static int optee_rpmi_get_os_version(struct optee *optee)
+{
+ struct optee_rpmi_probe_req req = {
+ .op = cpu_to_le32(OPTEE_RPMI_GET_OS_VERSION),
+ };
+ struct optee_rpmi_os_resp os;
+ int ret;
+
+ ret = optee_rpmi_call(optee, &req, sizeof(req), &os, sizeof(os));
+ if (ret)
+ return ret;
+
+ optee->revision.os_major = get_unaligned_le32(&os.major);
+ optee->revision.os_minor = get_unaligned_le32(&os.minor);
+ optee->revision.os_build_id = get_unaligned_le64(&os.build_id);
+
+ if (optee->revision.os_build_id)
+ pr_info("revision %u.%u (%016llx)\n",
+ optee->revision.os_major, optee->revision.os_minor,
+ optee->revision.os_build_id);
+ else
+ pr_info("revision %u.%u\n", optee->revision.os_major,
+ optee->revision.os_minor);
+
+ return 0;
+}
+
+/* Query and store secure-world capabilities and buffer limits. */
+static int optee_rpmi_exchange_caps(struct optee *optee)
+{
+ struct optee_rpmi_probe_req req = {
+ .op = cpu_to_le32(OPTEE_RPMI_EXCHANGE_CAPABILITIES),
+ };
+ struct optee_rpmi_caps_resp caps;
+ u32 rpc_count, sec_caps, notif_count;
+ int ret;
+
+ ret = optee_rpmi_call(optee, &req, sizeof(req), &caps, sizeof(caps));
+ if (ret)
+ return ret;
+
+ sec_caps = get_unaligned_le32(&caps.secure_caps);
+ rpc_count = get_unaligned_le32(&caps.rpc_param_count);
+ notif_count = get_unaligned_le32(&caps.notification_count);
+ if (!notif_count || !rpc_count)
+ return -EPROTO;
+
+ optee->rpc_param_count = rpc_count;
+ optee->rpmi.sec_caps = sec_caps;
+ optee->rpmi.notification_count = notif_count;
+ optee->in_kernel_rpmb_routing = IS_REACHABLE(CONFIG_RPMB);
+
+ return 0;
+}
+
+static int optee_rpmi_api_is_compatible(struct optee *optee)
+{
+ struct optee_rpmi_probe_req req = {
+ .op = cpu_to_le32(OPTEE_RPMI_GET_API_VERSION),
+ };
+ struct optee_rpmi_api_resp api;
+ int ret;
+
+ ret = optee_rpmi_call(optee, &req, sizeof(req), &api, sizeof(api));
+ if (ret)
+ return ret;
+
+ if (get_unaligned_le32(&api.major) != OPTEE_RPMI_VERSION_MAJOR)
+ return -EPROTONOSUPPORT;
+
+ /* Version 1.0 has no minimum minor revision beyond zero. */
+ return 0;
+}
+
+static void optee_rpmi_get_version(struct tee_device *teedev,
+ struct tee_ioctl_version_data *vers)
+{
+ *vers = (struct tee_ioctl_version_data) {
+ .impl_id = TEE_IMPL_ID_OPTEE,
+ .gen_caps = TEE_GEN_CAP_GP | TEE_GEN_CAP_REG_MEM |
+ TEE_GEN_CAP_MEMREF_NULL,
+ };
+}
+
+static int optee_rpmi_open(struct tee_context *ctx)
+{
+ return optee_open(ctx, true);
+}
+
+static const struct tee_driver_ops optee_rpmi_clnt_ops = {
+ .get_version = optee_rpmi_get_version,
+ .get_tee_revision = optee_get_revision,
+ .open = optee_rpmi_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_rpmi_shm_register,
+ .shm_unregister = optee_rpmi_shm_unregister,
+};
+
+static const struct tee_driver_ops optee_rpmi_supp_ops = {
+ .get_version = optee_rpmi_get_version,
+ .get_tee_revision = optee_get_revision,
+ .open = optee_rpmi_open,
+ .release = optee_release_supp,
+ .supp_recv = optee_supp_recv,
+ .supp_send = optee_supp_send,
+ .shm_register = optee_rpmi_shm_register,
+ .shm_unregister = optee_rpmi_shm_unregister_supp,
+};
+
+static const struct tee_desc optee_rpmi_clnt_desc = {
+ .name = DRIVER_NAME "-rpmi-clnt",
+ .ops = &optee_rpmi_clnt_ops,
+ .owner = THIS_MODULE,
+};
+
+static const struct tee_desc optee_rpmi_supp_desc = {
+ .name = DRIVER_NAME "-rpmi-supp",
+ .ops = &optee_rpmi_supp_ops,
+ .owner = THIS_MODULE,
+ .flags = TEE_DESC_PRIVILEGED,
+};
+
+static const struct optee_ops optee_rpmi_ops = {
+ .do_call_with_arg = optee_rpmi_do_call_with_arg,
+ .to_msg_param = optee_rpmi_to_msg_param,
+ .from_msg_param = optee_rpmi_from_msg_param,
+};
+
+/* Keep callback state and memory tables alive until all TEE users release. */
+static void optee_rpmi_remove(struct rpmi_tee_device *rdev)
+{
+ struct optee *optee = dev_get_drvdata(&rdev->dev);
+
+ optee_remove_common(optee);
+ optee_rpmi_shm_rht_uninit(optee);
+ kfree(optee);
+}
+
+static int optee_rpmi_probe(struct rpmi_tee_device *rdev)
+{
+ struct tee_device *teedev;
+ struct tee_context *ctx;
+ int ret;
+
+ struct optee *optee __free(kfree) = kzalloc_obj(*optee);
+ if (!optee)
+ return -ENOMEM;
+
+ optee->rpmi.rdev = rdev;
+ optee->ops = &optee_rpmi_ops;
+
+ ret = optee_rpmi_api_is_compatible(optee);
+ if (ret)
+ return ret;
+
+ ret = optee_rpmi_get_os_version(optee);
+ if (ret)
+ return ret;
+
+ ret = optee_rpmi_exchange_caps(optee);
+ if (ret)
+ return ret;
+
+ optee->pool = optee_rpmi_shm_pool_alloc();
+ if (IS_ERR(optee->pool))
+ return PTR_ERR(optee->pool);
+
+ ret = optee_rpmi_shm_rht_init(optee);
+ if (ret)
+ goto err_pool;
+
+ optee_cq_init(&optee->call_queue, 0);
+ optee_supp_init(&optee->supp);
+ optee_shm_arg_cache_init(optee, OPTEE_SHM_ARG_SHARED);
+ mutex_init(&optee->rpmb_dev_mutex);
+ INIT_WORK(&optee->rpmb_scan_bus_work, optee_bus_scan_rpmb);
+ optee->rpmb_intf.notifier_call = optee_rpmb_intf_rdev;
+ ret = optee_notif_init(optee, optee->rpmi.notification_count);
+ if (ret)
+ goto err_common;
+
+ /* Allocate all keys, then restrict the inclusive bound to the last key. */
+ optee->notif.max_key = optee->rpmi.notification_count - 1;
+
+ teedev = tee_device_alloc(&optee_rpmi_clnt_desc, &rdev->dev,
+ optee->pool, optee);
+ if (IS_ERR(teedev)) {
+ ret = PTR_ERR(teedev);
+ goto err_notif;
+ }
+ optee->teedev = teedev;
+
+ teedev = tee_device_alloc(&optee_rpmi_supp_desc, &rdev->dev,
+ optee->pool, optee);
+ if (IS_ERR(teedev)) {
+ ret = PTR_ERR(teedev);
+ goto err_devices;
+ }
+ optee->supp_teedev = teedev;
+
+ optee_set_dev_group(optee);
+
+ /* Internal RPC allocation must be ready before userspace can enter. */
+ ctx = teedev_open(optee->teedev);
+ if (IS_ERR(ctx)) {
+ ret = PTR_ERR(ctx);
+ goto err_devices;
+ }
+
+ optee->ctx = ctx;
+ dev_set_drvdata(&rdev->dev, optee);
+ if (optee->in_kernel_rpmb_routing)
+ blocking_notifier_chain_register(&optee_rpmb_intf_added,
+ &optee->rpmb_intf);
+
+ ret = tee_device_register(optee->teedev);
+ if (ret)
+ goto err_initialized;
+
+ ret = tee_device_register(optee->supp_teedev);
+ if (ret)
+ goto err_initialized;
+
+ ret = optee_enumerate_devices(PTA_CMD_GET_DEVICES);
+ if (ret)
+ goto err_initialized;
+
+ dev_info(&rdev->dev, "OP-TEE RPMI %u.%u initialized\n",
+ optee->revision.os_major, optee->revision.os_minor);
+ retain_and_null_ptr(optee);
+
+ return 0;
+
+err_initialized:
+ /* The remove path owns and frees the published backend state. */
+ retain_and_null_ptr(optee);
+ optee_rpmi_remove(rdev);
+
+ return ret;
+err_devices:
+ tee_device_unregister(optee->supp_teedev);
+ tee_device_unregister(optee->teedev);
+ optee_shm_arg_cache_uninit(optee);
+err_notif:
+ optee_notif_uninit(optee);
+err_common:
+ optee_supp_uninit(&optee->supp);
+ mutex_destroy(&optee->call_queue.mutex);
+ rpmb_dev_put(optee->rpmb_dev);
+ mutex_destroy(&optee->rpmb_dev_mutex);
+ optee_rpmi_shm_rht_uninit(optee);
+err_pool:
+ tee_shm_pool_free(optee->pool);
+
+ return ret;
+}
+
+static const struct rpmi_tee_device_id optee_rpmi_device_ids[] = {
+ { OPTEE_RPMI_SERVICE_UUID },
+ {}
+};
+
+static struct rpmi_tee_driver optee_rpmi_driver = {
+ .name = DRIVER_NAME "-rpmi",
+ .probe = optee_rpmi_probe,
+ .remove = optee_rpmi_remove,
+ .id_table = optee_rpmi_device_ids,
+};
+
+int optee_rpmi_abi_register(void)
+{
+ return rpmi_tee_register(&optee_rpmi_driver);
+}
+
+void optee_rpmi_abi_unregister(void)
+{
+ rpmi_tee_unregister(&optee_rpmi_driver);
+}
+
+MODULE_ALIAS("rpmi_tee:486178e0-e7f8-11e3-bc5e-0002a5d5c51b");

--
2.34.1