[PATCH RFC v2 2/8] tee: optee: define the RPMI control and parcel-reference ABI
Amirreza Zarrabi
amirreza.zarrabi at oss.qualcomm.com
Thu Oct 8 15:02:54 PDT 2026
Hi Jens,
On 10/8/2026 5:51 PM, Jens Wiklander wrote:
> Hi Amir,
>
> On Tue, Oct 6, 2026 at 2:40 AM Amirreza Zarrabi
> <amirreza.zarrabi at oss.qualcomm.com> wrote:
>>
>> Define the OP-TEE service protocol carried by RPMI TEE_CALL payloads.
>> Add operations for version and capability queries, shared-memory
>> unregistration, asynchronous notification enablement, and yielding-call
>> start and resume. Use fixed-width little-endian control fields and RPMI
>> error codes for control responses.
>>
>> Add a parcel memory-reference layout to the common message parameter
>> union. Identify shared memory by its parcel ID and nonce, with a 64-bit
>> byte offset and size, without changing the message parameter size.
>> Encode NULL references with a zero parcel ID and nonce, leaving parcel
>> ID zero usable with a nonzero nonce.
>>
>> Document the wire layouts and ownership rules for matching Linux and
>> OP-TEE implementations.
>>
>> Signed-off-by: Amirreza Zarrabi <amirreza.zarrabi at oss.qualcomm.com>
>> ---
>> drivers/tee/optee/optee_msg.h | 38 +++++--
>> drivers/tee/optee/optee_rpmi.h | 234 +++++++++++++++++++++++++++++++++++++++++
>> 2 files changed, 264 insertions(+), 8 deletions(-)
>>
>> diff --git a/drivers/tee/optee/optee_msg.h b/drivers/tee/optee/optee_msg.h
>> index 6c3043f8da33..028f8cd95fdd 100644
>> --- a/drivers/tee/optee/optee_msg.h
>> +++ b/drivers/tee/optee/optee_msg.h
>> @@ -31,6 +31,9 @@
>> #define OPTEE_MSG_ATTR_TYPE_FMEM_INPUT OPTEE_MSG_ATTR_TYPE_RMEM_INPUT
>> #define OPTEE_MSG_ATTR_TYPE_FMEM_OUTPUT OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT
>> #define OPTEE_MSG_ATTR_TYPE_FMEM_INOUT OPTEE_MSG_ATTR_TYPE_RMEM_INOUT
>> +#define OPTEE_MSG_ATTR_TYPE_PMEM_INPUT OPTEE_MSG_ATTR_TYPE_RMEM_INPUT
>> +#define OPTEE_MSG_ATTR_TYPE_PMEM_OUTPUT OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT
>> +#define OPTEE_MSG_ATTR_TYPE_PMEM_INOUT OPTEE_MSG_ATTR_TYPE_RMEM_INOUT
>> #define OPTEE_MSG_ATTR_TYPE_TMEM_INPUT 0x9
>> #define OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT 0xa
>> #define OPTEE_MSG_ATTR_TYPE_TMEM_INOUT 0xb
>> @@ -149,6 +152,23 @@ struct optee_msg_param_fmem {
>> u64 global_id;
>> };
>>
>> +/**
>> + * struct optee_msg_param_pmem - RPMI parcel memory reference
>> + * @offs: full-width byte offset from the parcel's first byte
>> + * @size: logical reference size, or required size for a short-buffer response
>> + * @parcel_id: firmware-assigned parcel identifier
>> + * @nonce: nonzero REE nonce supplied when sharing the parcel
>> + *
>> + * A zero parcel ID and nonce encode a NULL reference. Its offset must be zero;
>> + * its size is preserved. Parcel ID zero remains valid with a nonzero nonce.
>> + */
>> +struct optee_msg_param_pmem {
>
> Without an internal offset like in struct optee_msg_param_fmem, an
> explicit register SHM call into OP-TEE is needed before it can be
> used. You'd still need the TEE_MEMORY_PARCEL_CREATE, but we can save
> one round-trip into the secure world.
>
The original intenation was to use parcel-relative offsets, with the
secure-side memory object covers the entire parcel. OP-TEE can retrieve it
lazily and apply the supplied offset directly, so this design does not require
an explicit SHM registration call or an additional round trip.
Since we are implementing the RPMI parcel memory-object cache independently of
FF-A's cache in OP-TEE, this seems simpler: it avoids a separate
initial-offset property, preserves a full-width offset reference, while
keeping OP-TEE ignorant from the SHM concept which is a Linux side concept.
That said, I can separate the offsets to matching FF-A's memory-object
model. But I am not sure what we achive?
>> + u64 offs;
>> + u64 size;
>> + u32 parcel_id;
>> + u32 nonce;
>
> I wonder if parcel_id and nonce wouldn't be better combined into a
> single field. They need to be separate when preparing arguments for
> the TEE_MEMORY_* calls. Everywhere else, it's only an opaque memory
> handle, and one handle is easier to keep track of than two.
I thought about that before. This would push the conversion to the
firmware boundary. I was not sure if it is acceptable. I'll do that :).
>
>> +};
>> +
>> /**
>> * struct optee_msg_param_value - opaque value parameter
>> * @a: first opaque value
>> @@ -166,18 +186,19 @@ struct optee_msg_param_value {
>> /**
>> * struct optee_msg_param - parameter used together with struct optee_msg_arg
>> * @attr: attributes
>> - * @tmem: parameter by temporary memory reference
>> - * @rmem: parameter by registered memory reference
>> - * @fmem: parameter by FF-A registered memory reference
>> - * @value: parameter by opaque value
>> - * @octets: parameter by octet string
>> + * @u.tmem: parameter by temporary memory reference
>> + * @u.rmem: parameter by registered memory reference
>> + * @u.fmem: parameter by FF-A registered memory reference
>> + * @u.pmem: parameter by RPMI parcel memory reference
>> + * @u.value: parameter by opaque value
>> + * @u.octets: parameter by octet string
>> * @u: union holding OP-TEE msg parameter
>> *
>> * @attr & OPTEE_MSG_ATTR_TYPE_MASK indicates if tmem, rmem or value is used in
>> * the union. OPTEE_MSG_ATTR_TYPE_VALUE_* indicates value or octets,
>> - * OPTEE_MSG_ATTR_TYPE_TMEM_* indicates @tmem and
>> - * OPTEE_MSG_ATTR_TYPE_RMEM_* or the alias PTEE_MSG_ATTR_TYPE_FMEM_* indicates
>> - * @rmem or @fmem depending on the conduit.
>> + * OPTEE_MSG_ATTR_TYPE_TMEM_* indicates @u.tmem. OPTEE_MSG_ATTR_TYPE_RMEM_*
>> + * and its FMEM/PMEM aliases indicate @u.rmem, @u.fmem or @u.pmem depending
>> + * on the conduit.
>> * OPTEE_MSG_ATTR_TYPE_NONE indicates that none of the members are used.
>> */
>> struct optee_msg_param {
>> @@ -186,6 +207,7 @@ struct optee_msg_param {
>> struct optee_msg_param_tmem tmem;
>> struct optee_msg_param_rmem rmem;
>> struct optee_msg_param_fmem fmem;
>> + struct optee_msg_param_pmem pmem;
>> struct optee_msg_param_value value;
>> u8 octets[24];
>> } u;
>> diff --git a/drivers/tee/optee/optee_rpmi.h b/drivers/tee/optee/optee_rpmi.h
>> new file mode 100644
>> index 000000000000..252216aad09b
>> --- /dev/null
>> +++ b/drivers/tee/optee/optee_rpmi.h
>> @@ -0,0 +1,234 @@
>> +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */
>> +/*
>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>> + */
>> +#ifndef OPTEE_RPMI_H
>> +#define OPTEE_RPMI_H
>> +
>> +#include <linux/bitops.h>
>> +#include <linux/types.h>
>> +#include <linux/uuid.h>
>> +
>> +/*
>> + * OP-TEE service ABI over RPMI TEE_CALL.
>> + *
>> + * Requests and responses are carried in the TEE_CALL service payload.
>> + * Control fields are little-endian. Response status fields contain signed
>> + * RPMI error codes, distinct from the outer TEE_CALL status and the GP
>> + * command result in optee_msg_arg.ret.
>> + */
>> +#define OPTEE_RPMI_SERVICE_UUID \
>> + UUID_INIT(0x486178e0, 0xe7f8, 0x11e3, \
>> + 0xbc, 0x5e, 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b)
>> +
>> +#define OPTEE_RPMI_VERSION_MAJOR 1
>> +#define OPTEE_RPMI_VERSION_MINOR 0
>> +
>> +/**
>> + * struct optee_rpmi_probe_req - request without operation-specific arguments
>> + * @op: GET_API_VERSION, GET_OS_VERSION or EXCHANGE_CAPABILITIES
>> + */
>> +struct optee_rpmi_probe_req {
>> + __le32 op;
>> +} __packed;
>> +
>> +/**
>> + * struct optee_rpmi_status_resp - response carrying only an RPMI status
>> + * @status: signed RPMI error code
>> + */
>> +struct optee_rpmi_status_resp {
>> + __le32 status;
>> +} __packed;
>> +
>> +/*
>> + * Return the service API version.
>> + *
>> + * Request: struct optee_rpmi_probe_req
>> + * Response: struct optee_rpmi_api_resp
>> + */
>> +#define OPTEE_RPMI_GET_API_VERSION 0
>> +
>> +/**
>> + * struct optee_rpmi_api_resp - GET_API_VERSION response
>> + * @status: signed RPMI error code
>> + * @major: incompatible protocol revision
>> + * @minor: compatible protocol revision
>> + */
>> +struct optee_rpmi_api_resp {
>> + __le32 status;
>> + __le32 major;
>> + __le32 minor;
>> +} __packed;
>
> Why do these communication structs have to be packed? With careful
> design of the layout, padding, alignment, etc, it shouldn't be an
> issue.
Agreed. The structures already have naturally aligned fields and
explicit reserved fields where needed, so `__packed` is unnecessary for
their current layouts. I'll remove it and keep the wire layout explicitly
defined, retaining unaligned access helpers where transport-buffer alignment
is not guaranteed.
>
>> +
>> +/*
>> + * Return the trusted OS revision, not the service API revision.
>> + *
>> + * Request: struct optee_rpmi_probe_req
>> + * Response: struct optee_rpmi_os_resp
>> + */
>> +#define OPTEE_RPMI_GET_OS_VERSION 1
>> +
>> +/**
>> + * struct optee_rpmi_os_resp - GET_OS_VERSION response
>> + * @status: signed RPMI error code
>> + * @major: trusted OS major revision
>> + * @minor: trusted OS minor revision
>> + * @reserved: must be zero
>> + * @build_id: trusted OS build identifier, zero if unspecified
>> + */
>> +struct optee_rpmi_os_resp {
>> + __le32 status;
>> + __le32 major;
>> + __le32 minor;
>> + __le32 reserved;
>> + __le64 build_id;
>> +} __packed;
>> +
>> +/*
>> + * Query secure-world capabilities and limits.
>> + *
>> + * Request: struct optee_rpmi_probe_req
>> + * Response: struct optee_rpmi_caps_resp
>> + */
>> +#define OPTEE_RPMI_EXCHANGE_CAPABILITIES 2
>> +
>> +/**
>> + * struct optee_rpmi_caps_resp - EXCHANGE_CAPABILITIES response
>> + * @status: signed RPMI error code
>> + * @secure_caps: reserved for future optional features; zero in version 1
>> + * @rpc_param_count: nonzero parameter capacity of each RPC argument buffer
>> + * @notification_count: nonzero logical key count, including synchronous keys
>> + * Keys range from zero through notification_count - 1.
>> + *
>> + * Version 1 defines no capability bits. Unknown bits are ignored by Linux
>> + * for compatibility with future extensions.
>
> Note that we expect the ABI version to stay at 1.0 for a foreseeable
> future. Extensions to the ABI are primarily negotiated using
> capabilities.
>
Ack.
Thanks Jens,
Best regards,
Amir
> Cheers,
> Jens
>
>> + */
>> +struct optee_rpmi_caps_resp {
>> + __le32 status;
>> + __le32 secure_caps;
>> + __le32 rpc_param_count;
>> + __le32 notification_count;
>> +} __packed;
>> +
>> +/*
>> + * Unregister a shared parcel from OP-TEE.
>> + *
>> + * Request: struct optee_rpmi_unregister_req
>> + * Response: struct optee_rpmi_status_resp
>> + */
>> +#define OPTEE_RPMI_UNREGISTER_SHM 3
>> +
>> +/**
>> + * struct optee_rpmi_unregister_req - retire a shared parcel in OP-TEE
>> + * @op: OPTEE_RPMI_UNREGISTER_SHM
>> + * @parcel_id: parcel to retire; zero is valid with a nonzero nonce
>> + * @nonce: nonzero nonce associated with the parcel
>> + *
>> + * Success means OP-TEE stopped using the mapping and released its receiver
>> + * interest.
>> + */
>> +struct optee_rpmi_unregister_req {
>> + __le32 op;
>> + __le32 parcel_id;
>> + __le32 nonce;
>> +} __packed;
>> +
>> +/*
>> + * Enable asynchronous notification delivery.
>> + *
>> + * Request: struct optee_rpmi_enable_notif_req
>> + * Response: struct optee_rpmi_status_resp
>> + */
>> +#define OPTEE_RPMI_ENABLE_ASYNC_NOTIF 4
>> +
>> +/**
>> + * struct optee_rpmi_enable_notif_req - bind an incoming RPMI doorbell
>> + * @op: OPTEE_RPMI_ENABLE_ASYNC_NOTIF
>> + * @signal_id: allocated TEE-to-REE signal, not a logical notification key
>> + *
>> + * Success activates delivery and raises the doorbell for pending work.
>> + * Raising the signal requests OPTEE_MSG_CMD_DO_BOTTOM_HALF.
>> + * OPTEE_MSG_CMD_STOP_ASYNC_NOTIF stops future doorbell generation, but does
>> + * not drain already-raised signals or release the signal ID.
>> + */
>> +struct optee_rpmi_enable_notif_req {
>> + __le32 op;
>> + __le32 signal_id;
>> +} __packed;
>> +
>> +/*
>> + * Start a yielding command using shared command and RPC arguments.
>> + *
>> + * Request: struct optee_rpmi_call_req
>> + * Response: struct optee_rpmi_call_resp
>> + */
>> +#define OPTEE_RPMI_YIELDING_CALL_WITH_ARG 5
>> +
>> +#define OPTEE_RPMI_YIELDING_CALL_RETURN_DONE 0
>> +#define OPTEE_RPMI_YIELDING_CALL_RETURN_RPC_CMD 1
>> +#define OPTEE_RPMI_YIELDING_CALL_RETURN_INTERRUPT 2
>> +
>> +/**
>> + * struct optee_rpmi_call_req - start a yielding command
>> + * @op: OPTEE_RPMI_YIELDING_CALL_WITH_ARG
>> + * @parcel_id: argument parcel identity
>> + * @nonce: nonce associated with the parcel
>> + * @flags: zero in version 1
>> + * @arg_offset: command argument byte offset from the parcel's first byte
>> + * @rpc_offset: RPC argument byte offset from the parcel's first byte
>> + * @arg_size: command argument extent in bytes
>> + * @rpc_size: RPC argument capacity in bytes
>> + *
>> + * Firmware validates ownership, RW access, alignment and disjoint ranges.
>> + * RPMI_ERR_BUSY rejects an initial request without acquiring call ownership.
>> + */
>> +struct optee_rpmi_call_req {
>> + __le32 op;
>> + __le32 parcel_id;
>> + __le32 nonce;
>> + __le32 flags;
>> + __le64 arg_offset;
>> + __le64 rpc_offset;
>> + __le32 arg_size;
>> + __le32 rpc_size;
>> +} __packed;
>> +
>> +/**
>> + * struct optee_rpmi_call_resp - yielding command response
>> + * @status: signed RPMI error code, distinct from the command's GP result
>> + * @result: OPTEE_RPMI_YIELDING_CALL_RETURN_* value when status is success
>> + * @resume_token: zero for DONE, nonzero opaque token for a suspended command
>> + *
>> + * DONE releases all access to the call's argument and RPC ranges. RPC_CMD
>> + * requests RPC command handling; INTERRUPT requests resumption without an
>> + * RPC command. Tokens belong to one accepted call, service and caller.
>> + */
>> +struct optee_rpmi_call_resp {
>> + __le32 status;
>> + __le32 result;
>> + __le64 resume_token;
>> +} __packed;
>> +
>> +/*
>> + * Resume a suspended yielding command.
>> + *
>> + * Request: struct optee_rpmi_resume_req
>> + * Response: struct optee_rpmi_call_resp
>> + */
>> +#define OPTEE_RPMI_YIELDING_CALL_RESUME 6
>> +
>> +/**
>> + * struct optee_rpmi_resume_req - resume a suspended command
>> + * @op: OPTEE_RPMI_YIELDING_CALL_RESUME
>> + * @reserved: must be zero
>> + * @resume_token: token from the preceding response for this call
>> + *
>> + * Resume must not return RPMI_ERR_BUSY.
>> + */
>> +struct optee_rpmi_resume_req {
>> + __le32 op;
>> + __le32 reserved;
>> + __le64 resume_token;
>> +} __packed;
>> +
>> +#endif /* OPTEE_RPMI_H */
>>
>> --
>> 2.34.1
>>
More information about the linux-riscv
mailing list