[PATCH RFC 2/5] optee: riscv: add shared memory and scheduled calls

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


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




More information about the linux-riscv mailing list