[PATCH RFC v2 07/10] firmware: riscv: rpmi-tee: register TEE services and support calls
From: Amirreza Zarrabi
Date: Wed Oct 07 2026 - 23:35:04 EST
Register discovered TEE services as RPMI TEE bus devices so service
drivers can bind to them.
Provide TEE_CALL operations for exchanging service-defined requests
and responses, and expose the transport's payload limits to clients.
Signed-off-by: Amirreza Zarrabi <amirreza.zarrabi@xxxxxxxxxxxxxxxx>
---
drivers/firmware/riscv_rpmi_tee/driver.c | 278 ++++++++++++++++++++-
drivers/firmware/riscv_rpmi_tee/rpmi_tee_private.h | 13 +
include/linux/rpmi_tee.h | 35 +++
3 files changed, 325 insertions(+), 1 deletion(-)
diff --git a/drivers/firmware/riscv_rpmi_tee/driver.c b/drivers/firmware/riscv_rpmi_tee/driver.c
index a70b8be41bed..a683f7d28c25 100644
--- a/drivers/firmware/riscv_rpmi_tee/driver.c
+++ b/drivers/firmware/riscv_rpmi_tee/driver.c
@@ -5,16 +5,52 @@
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
-#include <linux/unaligned.h>
#include <linux/mailbox_client.h>
#include <linux/mailbox/riscv-rpmi-message.h>
+#include <linux/cleanup.h>
+#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/rpmi_tee.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
#include "rpmi_tee_private.h"
+/**
+ * struct rpmi_tee_call_req - TEE_CALL request prefix
+ * @sender_id: Calling REE endpoint identifier.
+ * @target_id: Destination TEE endpoint identifier.
+ * @service: UUID of the target service.
+ * @service_data_len: Length of @service_data in bytes.
+ * @service_data: Service-defined request data.
+ */
+struct rpmi_tee_call_req {
+ __le32 sender_id;
+ __le32 target_id;
+ u8 service[UUID_SIZE];
+ __le32 service_data_len;
+ u8 service_data[];
+} __packed;
+
+/**
+ * struct rpmi_tee_call_resp - TEE_CALL response prefix
+ * @status: RPMI completion status.
+ * @service_data_len: Length of @service_data in bytes.
+ * @service_data: Service-defined response data.
+ */
+struct rpmi_tee_call_resp {
+ __le32 status;
+ __le32 service_data_len;
+ u8 service_data[];
+} __packed;
+
+struct rpmi_tee_child {
+ struct list_head node;
+ struct rpmi_tee_device *rdev;
+};
+
/* rpmi_tee_send_with_status() - Send an RPMI TEE service request. */
int rpmi_tee_send_with_status(struct rpmi_tee_transport *priv, u32 service_id,
const void *req, size_t req_len, void *resp,
@@ -43,6 +79,47 @@ int rpmi_tee_send_with_status(struct rpmi_tee_transport *priv, u32 service_id,
return 0;
}
+/**
+ * rpmi_tee_send() - Send an RPMI TEE service request
+ * @priv: RPMI TEE transport
+ * @service_id: RPMI TEE service identifier
+ * @req: Request data
+ * @req_len: Request data length
+ * @resp: Response data buffer, or %NULL for a status-only response
+ * @resp_len: On entry, response buffer capacity; on success, response length
+ *
+ * Pass both @resp and @resp_len as %NULL when the service has no response
+ * payload beyond the mandatory RPMI status word.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+static int rpmi_tee_send(struct rpmi_tee_transport *priv, u32 service_id,
+ const void *req, size_t req_len, void *resp,
+ size_t *resp_len)
+{
+ __le32 status_resp;
+ size_t status_resp_len = sizeof(status_resp);
+ s32 status;
+ int ret;
+
+ if (!resp && !resp_len) {
+ resp = &status_resp;
+ resp_len = &status_resp_len;
+ } else if (!resp || !resp_len) {
+ return -EINVAL;
+ }
+
+ ret = rpmi_tee_send_with_status(priv, service_id, req, req_len, resp,
+ resp_len, &status);
+ if (ret)
+ return ret;
+
+ if (status == RPMI_ERR_NO_DATA)
+ return -ENODATA;
+
+ return rpmi_to_linux_error(status);
+}
+
/**
* rpmi_tee_get_attr() - Get an RPMI mailbox attribute
* @priv: RPMI TEE transport
@@ -95,12 +172,202 @@ static int rpmi_tee_check_transport(struct rpmi_tee_transport *priv)
ret = rpmi_tee_get_attr(priv, RPMI_MBOX_ATTR_MAX_MSG_DATA_SIZE, &value);
if (ret)
return ret;
+ /* The mandatory TEE_CALL request and response must fit the mailbox. */
+ if (value < sizeof(struct rpmi_tee_call_req) ||
+ value < sizeof(struct rpmi_tee_call_resp))
+ return -EMSGSIZE;
priv->mbox.max_msg_data_size = value;
+ priv->max_call_req_size = value - sizeof(struct rpmi_tee_call_req);
+ priv->max_call_resp_size = value - sizeof(struct rpmi_tee_call_resp);
+
+ return 0;
+}
+
+/* RPMI TEE SERVICE GRP API. */
+
+/* Return the transport that owns @rdev. */
+static struct rpmi_tee_transport *
+rpmi_tee_device_to_transport(struct rpmi_tee_device *rdev)
+{
+ return dev_get_drvdata(rdev->dev.parent);
+}
+
+static int rpmi_tee_op_msg_limits_get(struct rpmi_tee_device *rdev,
+ struct rpmi_tee_msg_limits *limits)
+{
+ struct rpmi_tee_transport *priv = rpmi_tee_device_to_transport(rdev);
+
+ if (!limits)
+ return -EINVAL;
+
+ limits->max_req_size = priv->max_call_req_size;
+ limits->max_resp_size = priv->max_call_resp_size;
+
+ return 0;
+}
+
+/**
+ * rpmi_tee_op_call - Invoke a service offered by a TEE endpoint
+ * @rdev: TEE service device.
+ * @req: Service-defined request data.
+ * @req_len: Length of @req in bytes.
+ * @resp: Buffer for service-defined response data.
+ * @resp_len: On entry, capacity of @resp; on success, response length.
+ *
+ * MPXY can return -ENOSPC after the TEE has processed the request when the
+ * response exceeds the supplied buffer. Callers must not blindly retry a
+ * non-idempotent request in that case.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+static int rpmi_tee_op_call(struct rpmi_tee_device *rdev, const void *req,
+ size_t req_len, void *resp, size_t *resp_len)
+{
+ struct rpmi_tee_transport *priv = rpmi_tee_device_to_transport(rdev);
+ size_t call_req_len, call_resp_len;
+ u32 service_data_len;
+ int ret;
+
+ if (!resp_len || (!req && req_len) || (!resp && *resp_len))
+ return -EINVAL;
+
+ /* TEE_CALL payload must fit the mailbox. */
+ if (req_len > priv->max_call_req_size ||
+ *resp_len > priv->max_call_resp_size)
+ return -EMSGSIZE;
+
+ call_req_len = sizeof(struct rpmi_tee_call_req) + req_len;
+ call_resp_len = sizeof(struct rpmi_tee_call_resp) + *resp_len;
+
+ struct rpmi_tee_call_req *call_req __free(kfree) =
+ kzalloc(call_req_len, GFP_KERNEL);
+ if (!call_req)
+ return -ENOMEM;
+
+ struct rpmi_tee_call_resp *call_resp __free(kfree) =
+ kzalloc(call_resp_len, GFP_KERNEL);
+ if (!call_resp)
+ return -ENOMEM;
+
+ call_req->sender_id = cpu_to_le32(priv->self_id);
+ call_req->target_id = cpu_to_le32(rdev->endpoint_id);
+ export_uuid(call_req->service, &rdev->uuid);
+ call_req->service_data_len = cpu_to_le32(req_len);
+ if (req_len)
+ memcpy(call_req->service_data, req, req_len);
+ /* Make TEE CALL. */
+ ret = rpmi_tee_send(priv, RPMI_TEE_SRV_CALL, call_req, call_req_len,
+ call_resp, &call_resp_len);
+ if (ret)
+ return ret;
+
+ if (call_resp_len < sizeof(*call_resp))
+ return -EPROTO;
+ service_data_len = get_unaligned_le32(&call_resp->service_data_len);
+ /* Verify the firmware-reported length fits the actual response. */
+ if (service_data_len != call_resp_len - sizeof(*call_resp))
+ return -EPROTO;
+
+ if (service_data_len)
+ memcpy(resp, call_resp->service_data, service_data_len);
+ *resp_len = service_data_len;
+
+ return 0;
+}
+
+static const struct rpmi_tee_info_ops rpmi_tee_info_ops = {
+ .msg_limits_get = rpmi_tee_op_msg_limits_get,
+};
+
+static const struct rpmi_tee_msg_ops rpmi_tee_msg_ops = {
+ .call = rpmi_tee_op_call,
+};
+
+static const struct rpmi_tee_ops rpmi_tee_ops = {
+ .info_ops = &rpmi_tee_info_ops,
+ .msg_ops = &rpmi_tee_msg_ops,
+};
+
+static void rpmi_tee_unregister_devices(struct rpmi_tee_transport *priv)
+{
+ struct rpmi_tee_child *child, *tmp;
+
+ list_for_each_entry_safe(child, tmp, &priv->devices, node) {
+ list_del(&child->node);
+ rpmi_tee_device_unregister(child->rdev);
+ kfree(child);
+ }
+}
+
+static struct rpmi_tee_device *
+rpmi_tee_find_device(struct rpmi_tee_transport *priv, const uuid_t *uuid,
+ u32 endpoint_id)
+{
+ struct rpmi_tee_child *child;
+
+ list_for_each_entry(child, &priv->devices, node) {
+ if (child->rdev->endpoint_id == endpoint_id &&
+ uuid_equal(&child->rdev->uuid, uuid))
+ return child->rdev;
+ }
+
+ return NULL;
+}
+
+static int
+rpmi_tee_register_devices(struct rpmi_tee_transport *priv,
+ const struct rpmi_tee_discovered_endpoint *ep)
+{
+ u32 i;
+
+ for (i = 0; i < ep->service_count; i++) {
+ const uuid_t *uuid = &ep->services[i];
+
+ /* Discard duplicate devices in the same endpoint. */
+ if (rpmi_tee_find_device(priv, uuid, ep->ep_id))
+ continue;
+
+ struct rpmi_tee_child *child __free(kfree) =
+ kzalloc_obj(*child, GFP_KERNEL);
+ if (!child)
+ return -ENOMEM;
+
+ child->rdev =
+ rpmi_tee_device_register(uuid, ep->ep_id,
+ &rpmi_tee_ops, priv->dev);
+ if (IS_ERR(child->rdev))
+ return PTR_ERR(child->rdev);
+
+ list_add_tail(&no_free_ptr(child)->node, &priv->devices);
+ }
return 0;
}
+static int rpmi_tee_setup_endpoints(struct rpmi_tee_transport *priv)
+{
+ struct rpmi_tee_discovered_endpoint *ep;
+ struct rpmi_tee_discovery system;
+ int ret;
+
+ ret = rpmi_tee_discover_endpoints(priv, &system);
+ if (ret)
+ return ret;
+
+ list_for_each_entry(ep, &system.eps, node) {
+ ret = rpmi_tee_register_devices(priv, ep);
+ if (ret) {
+ rpmi_tee_unregister_devices(priv);
+ break;
+ }
+ }
+
+ rpmi_tee_free_discovery(&system);
+
+ return ret;
+}
+
static int rpmi_tee_transport_probe(struct platform_device *pdev)
{
struct rpmi_tee_transport *priv;
@@ -112,6 +379,7 @@ static int rpmi_tee_transport_probe(struct platform_device *pdev)
priv->dev = &pdev->dev;
platform_set_drvdata(pdev, priv);
+ INIT_LIST_HEAD(&priv->devices);
priv->mbox.client.dev = &pdev->dev;
priv->mbox.client.tx_sync = true;
priv->mbox.chan = mbox_request_channel(&priv->mbox.client, 0);
@@ -126,6 +394,13 @@ static int rpmi_tee_transport_probe(struct platform_device *pdev)
goto out_failed;
}
+ ret = rpmi_tee_setup_endpoints(priv);
+ if (ret) {
+ dev_err_probe(&pdev->dev, ret,
+ "failed to discover RPMI TEE services\n");
+ goto out_failed;
+ }
+
return 0;
out_failed:
@@ -138,6 +413,7 @@ static void rpmi_tee_transport_remove(struct platform_device *pdev)
{
struct rpmi_tee_transport *priv = platform_get_drvdata(pdev);
+ rpmi_tee_unregister_devices(priv);
mbox_free_channel(priv->mbox.chan);
}
diff --git a/drivers/firmware/riscv_rpmi_tee/rpmi_tee_private.h b/drivers/firmware/riscv_rpmi_tee/rpmi_tee_private.h
index ba0e7cb59b6d..07e6b9913cb7 100644
--- a/drivers/firmware/riscv_rpmi_tee/rpmi_tee_private.h
+++ b/drivers/firmware/riscv_rpmi_tee/rpmi_tee_private.h
@@ -17,6 +17,7 @@
#define RPMI_TEE_SRV_PROBE_SYSTEM 0x03
#define RPMI_TEE_SRV_PROBE_DOMAIN 0x04
#define RPMI_TEE_SRV_PROBE_ENDPOINT 0x05
+#define RPMI_TEE_SRV_CALL 0x18
struct rpmi_tee_mbox {
struct mbox_client client;
@@ -24,10 +25,22 @@ struct rpmi_tee_mbox {
u32 max_msg_data_size;
};
+/**
+ * struct rpmi_tee_transport - State for one RPMI TEE transport instance
+ * @dev: Parent platform device.
+ * @mbox: RPMI mailbox transport state.
+ * @max_call_req_size: Maximum TEE_CALL request payload size in bytes.
+ * @max_call_resp_size: Maximum TEE_CALL response payload size in bytes.
+ * @self_id: Local REE physical endpoint identifier.
+ * @devices: List of registered TEE service devices.
+ */
struct rpmi_tee_transport {
struct device *dev;
struct rpmi_tee_mbox mbox;
+ size_t max_call_req_size;
+ size_t max_call_resp_size;
u32 self_id;
+ struct list_head devices;
};
/* Report local transport errors separately from the returned RPMI status. */
diff --git a/include/linux/rpmi_tee.h b/include/linux/rpmi_tee.h
index c499f0427833..a76055149996 100644
--- a/include/linux/rpmi_tee.h
+++ b/include/linux/rpmi_tee.h
@@ -41,6 +41,41 @@ struct rpmi_tee_driver {
#define to_rpmi_tee_drv(d) \
container_of_const(d, struct rpmi_tee_driver, driver)
+/**
+ * struct rpmi_tee_msg_limits - TEE_CALL service payload limits
+ * @max_req_size: Maximum request payload size in bytes.
+ * @max_resp_size: Maximum response payload size in bytes.
+ *
+ * Limits exclude the RPMI TEE_CALL request and response prefixes, but include
+ * any service-specific headers supplied by the caller.
+ */
+struct rpmi_tee_msg_limits {
+ size_t max_req_size;
+ size_t max_resp_size;
+};
+
+/**
+ * struct rpmi_tee_info_ops - RPMI TEE transport information operations
+ * @msg_limits_get: Return cached TEE_CALL payload limits for the transport
+ * serving @rdev in @limits. Limits remain fixed for the transport lifetime.
+ * Return 0 on success, or a negative error code on failure.
+ */
+struct rpmi_tee_info_ops {
+ int (*msg_limits_get)(struct rpmi_tee_device *rdev,
+ struct rpmi_tee_msg_limits *limits);
+};
+
+struct rpmi_tee_msg_ops {
+ int (*call)(struct rpmi_tee_device *rdev, const void *req,
+ size_t req_len, void *resp, size_t *resp_len);
+};
+
+/* RPMI TEE transport operation groups. */
+struct rpmi_tee_ops {
+ const struct rpmi_tee_info_ops *info_ops;
+ const struct rpmi_tee_msg_ops *msg_ops;
+};
+
extern const struct bus_type rpmi_tee_bus_type;
#if IS_REACHABLE(CONFIG_RISCV_RPMI_TEE_TRANSPORT)
--
2.34.1