OP-TEE commands and RPCs need shared memory that secure world can identify through RPMI parcels.
Register normal-world pages as read-write parcels shared with the OP-TEE endpoint. Track each parcel ID and nonce with the common shared-memory map. Store the combined identity in tee_shm.sec_world_id and allocate nonzero nonces to distinguish registered memory from NULL references. Reuse nonces after successful parcel reclamation.
Add conversions between TEE parameters and parcel memory references.
On client memory unregistration, ask OP-TEE to release its mapping before reclaiming the parcel. Supplicant memory has already been released by OP-TEE through its SHM_FREE RPC and only needs reclaiming.
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- drivers/tee/optee/Makefile | 1 + drivers/tee/optee/optee_private.h | 19 +++ drivers/tee/optee/rpmi_abi.c | 319 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+)
diff --git a/drivers/tee/optee/Makefile b/drivers/tee/optee/Makefile index ec2efc27ef05..4794c7c0ef91 100644 --- a/drivers/tee/optee/Makefile +++ b/drivers/tee/optee/Makefile @@ -10,6 +10,7 @@ optee-objs += device.o optee-objs += shm_handle_map.o optee-$(CONFIG_HAVE_ARM_SMCCC) += smc_abi.o optee-$(CONFIG_ARM_FFA_TRANSPORT) += ffa_abi.o +optee-$(CONFIG_RISCV_RPMI_TEE_TRANSPORT) += rpmi_abi.o
# for tracing framework to find optee_trace.h CFLAGS_smc_abi.o := -I$(src) diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h index 92428945ee33..efc1e37fe1bf 100644 --- a/drivers/tee/optee/optee_private.h +++ b/drivers/tee/optee/optee_private.h @@ -6,6 +6,7 @@ #ifndef OPTEE_PRIVATE_H #define OPTEE_PRIVATE_H
+#include <linux/idr.h> #include <linux/notifier.h> #include <linux/rhashtable.h> #include <linux/rpmb.h> @@ -200,6 +201,20 @@ struct optee_ffa { }; #endif
+#if IS_REACHABLE(CONFIG_RISCV_RPMI_TEE_TRANSPORT) +/** + * struct optee_rpmi - RPMI transport-specific OP-TEE state + * @rdev: owning RPMI service device + * @shm_map: lookup by the host-endian parcel ID and nonce pair + * @nonce_ida: nonzero SHM nonces, reusable after successful parcel reclamation + */ +struct optee_rpmi { + struct rpmi_tee_device *rdev; + struct optee_shm_handle_map shm_map; + struct ida nonce_ida; +}; +#endif + struct optee;
/** @@ -260,6 +275,7 @@ struct optee_ops { * @ctx: driver internal TEE context * @smc: specific to SMC ABI * @ffa: specific to FF-A ABI + * @rpmi: specific to RPMI ABI * @shm_arg_cache: shared memory cache argument * @call_queue: queue of threads waiting to call @invoke_fn * @notif: notification synchronization struct @@ -291,6 +307,9 @@ struct optee { #endif #if IS_REACHABLE(CONFIG_ARM_FFA_TRANSPORT) struct optee_ffa ffa; +#endif +#if IS_REACHABLE(CONFIG_RISCV_RPMI_TEE_TRANSPORT) + struct optee_rpmi rpmi; #endif }; struct optee_shm_arg_cache shm_arg_cache; diff --git a/drivers/tee/optee/rpmi_abi.c b/drivers/tee/optee/rpmi_abi.c new file mode 100644 index 000000000000..940676794280 --- /dev/null +++ b/drivers/tee/optee/rpmi_abi.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/mailbox/riscv-rpmi-message.h> +#include <linux/overflow.h> +#include <linux/rpmi_tee.h> +#include <linux/slab.h> +#include <linux/unaligned.h> +#include "optee_private.h" +#include "optee_rpmi.h" + +/* Keep transport errors separate from the control status in a response. */ +static int optee_rpmi_call_with_status(struct optee *optee, + const void *req, size_t req_len, + void *resp, size_t resp_size, + u32 *status) +{ + struct rpmi_tee_device *rdev = optee->rpmi.rdev; + size_t received = resp_size; + int rc; + + rc = rdev->ops->msg_ops->call(rdev, req, req_len, resp, &received); + if (rc) + return rc; + + if (received != resp_size) + return -EPROTO; + + *status = get_unaligned_le32(resp); + + return 0; +} + +/** + * optee_rpmi_call - Send a control request and check its TEE result + * @optee: OP-TEE instance. + * @req: Control request, including the operation number. + * @req_len: Request size in bytes. + * @resp: Response buffer, beginning with a little-endian TEE result. + * @resp_size: Exact expected response size, including the status field. + * + * Return: 0 on success, a transport error, -EPROTO for an unexpected response + * size, or -EIO for an unsuccessful control response. + */ +static int optee_rpmi_call(struct optee *optee, const void *req, size_t req_len, + void *resp, size_t resp_size) +{ + u32 status; + int rc; + + rc = optee_rpmi_call_with_status(optee, req, req_len, resp, resp_size, + &status); + if (rc) + return rc; + else if (status != TEEC_SUCCESS) + return -EIO; + + return 0; +} + +static int optee_rpmi_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); + struct rpmi_tee_device *rdev = optee->rpmi.rdev; + struct rpmi_tee_mem_receiver receiver = { + .endpoint_id = rdev->endpoint_id, + .access = RPMI_TEE_MEM_ACCESS_READ | RPMI_TEE_MEM_ACCESS_WRITE, + }; + struct rpmi_tee_mem_args args = { + .receivers = &receiver, + .receiver_count = 1, + .creator_access = RPMI_TEE_MEM_ACCESS_READ | + RPMI_TEE_MEM_ACCESS_WRITE, + }; + struct sg_table sgt; + u64 global_id; + int rc; + + rc = optee_check_mem_type(start, num_pages); + if (rc) + return rc; + + rc = sg_alloc_table_from_pages(&sgt, pages, num_pages, 0, + num_pages * PAGE_SIZE, GFP_KERNEL); + if (rc) + return rc; + + rc = ida_alloc_min(&optee->rpmi.nonce_ida, 1, GFP_KERNEL); + if (rc < 0) { + sg_free_table(&sgt); + return rc; + } + + args.nonce = rc; + args.sg = sgt.sgl; + rc = rdev->ops->mem_ops->memory_share(rdev, &args); + sg_free_table(&sgt); + /* A failed exchange may leave a parcel using this nonce. */ + if (rc) + return rc; + + global_id = optee_rpmi_global_id(args.parcel_id, args.nonce); + + rc = optee_shm_handle_map_add(&optee->rpmi.shm_map, shm, global_id); + if (rc) { + int rrc; + + rrc = rdev->ops->mem_ops->memory_reclaim(rdev, args.parcel_id); + /* Keep the nonce if firmware may still hold the parcel. */ + if (rrc) + dev_err(&rdev->dev, "reclaim parcel %#x failed: %d\n", + args.parcel_id, rrc); + else + ida_free(&optee->rpmi.nonce_ida, args.nonce); + + return rc; + } + + shm->sec_world_id = global_id; + + return 0; +} + +static int optee_rpmi_shm_unregister(struct tee_context *ctx, + struct tee_shm *shm) +{ + struct optee *optee = tee_get_drvdata(ctx->teedev); + struct rpmi_tee_device *rdev = optee->rpmi.rdev; + struct optee_rpmi_unregister_req req = { + .op = cpu_to_le32(OPTEE_RPMI_UNREGISTER_SHM), + .global_id = cpu_to_le64(shm->sec_world_id), + }; + struct optee_rpmi_status_resp resp; + u32 parcel_id = optee_rpmi_parcel_id(shm->sec_world_id); + u32 nonce = optee_rpmi_nonce(shm->sec_world_id); + int rc; + + optee_shm_handle_map_remove(&optee->rpmi.shm_map, shm->sec_world_id); + shm->sec_world_id = 0; + + rc = optee_rpmi_call(optee, &req, sizeof(req), &resp, sizeof(resp)); + if (rc) + dev_err(&rdev->dev, "unregister parcel %#x failed: %d\n", + parcel_id, rc); + + rc = rdev->ops->mem_ops->memory_reclaim(rdev, parcel_id); + /* Keep the nonce if firmware may still hold the parcel. */ + if (rc) + dev_err(&rdev->dev, "reclaim parcel %#x failed: %d\n", + parcel_id, rc); + else + ida_free(&optee->rpmi.nonce_ida, nonce); + + return rc; +} + +static int optee_rpmi_shm_unregister_supp(struct tee_context *ctx, + struct tee_shm *shm) +{ + struct optee *optee = tee_get_drvdata(ctx->teedev); + struct rpmi_tee_device *rdev = optee->rpmi.rdev; + u32 parcel_id = optee_rpmi_parcel_id(shm->sec_world_id); + u32 nonce = optee_rpmi_nonce(shm->sec_world_id); + int rc; + + optee_shm_handle_map_remove(&optee->rpmi.shm_map, shm->sec_world_id); + shm->sec_world_id = 0; + /* OP-TEE has already retired the parcel through SHM_FREE RPC. */ + rc = rdev->ops->mem_ops->memory_reclaim(rdev, parcel_id); + /* Keep the nonce if firmware may still hold the parcel. */ + if (rc) + dev_err(&rdev->dev, "reclaim parcel %#x failed: %d\n", + parcel_id, rc); + else + ida_free(&optee->rpmi.nonce_ida, nonce); + + return rc; +} + +/* Convert a memory reference to an OP-TEE RPMI parcel reference. */ +static int to_msg_param_rpmi_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_PMEM_INPUT + p->attr - + TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; + memset(&mp->u, 0, sizeof(mp->u)); + /* For !shm, a zero global_id encodes a NULL reference. */ + if (shm) { + if (check_add_overflow((u64)shm->offset, + (u64)p->u.memref.shm_offs, + &mp->u.pmem.offs)) + return -EINVAL; + + mp->u.pmem.global_id = shm->sec_world_id; + } + + mp->u.pmem.size = p->u.memref.size; + + return 0; +} + +static int optee_rpmi_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 = OPTEE_MSG_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_rpmi_mem(mp, p)) + return -EINVAL; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +/* Convert an RPMI parcel reference to a memref; callers own SHM lifetime. */ +static int from_msg_param_rpmi_mem(struct optee *optee, struct tee_param *p, + u32 attr, const struct optee_msg_param *mp) +{ + struct tee_shm *shm; + u64 offset; + + p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT + attr - + OPTEE_MSG_ATTR_TYPE_PMEM_INPUT; + + if (mp->u.pmem.size > SIZE_MAX) + return -EOVERFLOW; + p->u.memref.size = mp->u.pmem.size; + + if (!optee_rpmi_nonce(mp->u.pmem.global_id)) { + /* Return NULL shm. */ + if (mp->u.pmem.offs) + return -EINVAL; + p->u.memref.shm = NULL; + p->u.memref.shm_offs = 0; + return 0; + } + + shm = optee_shm_handle_map_lookup(&optee->rpmi.shm_map, + mp->u.pmem.global_id); + if (!shm || mp->u.pmem.offs < shm->offset) + return -EINVAL; + + offset = mp->u.pmem.offs - shm->offset; + if (offset > SIZE_MAX) + return -EOVERFLOW; + + p->u.memref.shm = shm; + p->u.memref.shm_offs = offset; + + return 0; +} + +static int optee_rpmi_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++) { + const struct optee_msg_param *mp = msg_params + n; + struct tee_param *p = params + n; + u32 attr = mp->attr & OPTEE_MSG_ATTR_TYPE_MASK; + int rc; + + 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_PMEM_INPUT: + case OPTEE_MSG_ATTR_TYPE_PMEM_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_PMEM_INOUT: + rc = from_msg_param_rpmi_mem(optee, p, attr, mp); + if (rc) + return rc; + break; + default: + return -EINVAL; + } + } + + return 0; +}