OP-TEE FF-A memory objects use 4 KiB pages, while the kernel page size may be larger. Consequently, tee_shm->offset can be greater than or equal to FFA_PAGE_SIZE, but OP-TEE rejects such a value in internal_offs.
Do not encode the excess page offset in offs_low/offs_high. Those fields describe the logical memref offset and are copied back into tee_param->shm_offs on return. Folding the page offset into them breaks parameter round trips when a memref is reused. They are also ignored by the OPTEE_RPC_CMD_SHM_ALLOC response path, which uses only global_id and internal_offs to construct the shared-memory mobj.
Instead, start the FF-A descriptor at the 4 KiB page containing the shared buffer, the same approach as optee_fill_pages_list() in the SMC ABI. Store the remaining in-page offset in internal_offs and preserve shm_offs in offs_low/offs_high. This keeps internal_offs within the FF-A page size, maps RPC allocations at the correct address, and preserves normal memref offsets across repeated invocations.
Tested on ARMv8-A with 64 KiB PAGE_SIZE. OP-TEE OS ran as a secure partition under Hafnium (SPMC) over FF-A. Verified registered shared memory with tee_shm->offset >= 4 KiB, memref reuse on the same TEEC_Operation, and RPC OPTEE_RPC_CMD_SHM_ALLOC (xtest regression 6007-6009). optee_hello_world, optee_aes, and xtest regression 1005, 1007, 1008, 4001-4003 and 6001-6003 also passed.
Fixes: 4615e5a34b95 ("optee: add FF-A support") Acked-by: Liming Sun limings@nvidia.com Acked-by: James Hurley jahurley@nvidia.com Acked-by: Dave Thompson davthompson@nvidia.com Signed-off-by: Mahantesh Salimath mahantesh@nvidia.com --- drivers/tee/optee/ffa_abi.c | 66 +++++++++++++++++++++++++++++------ drivers/tee/optee/optee_msg.h | 4 +-- 2 files changed, 57 insertions(+), 13 deletions(-)
diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c index 633715b98625..2a37e4899dc6 100644 --- a/drivers/tee/optee/ffa_abi.c +++ b/drivers/tee/optee/ffa_abi.c @@ -187,6 +187,40 @@ static int optee_ffa_from_msg_param(struct optee *optee, return 0; }
+/* + * OP-TEE FF-A memory objects use 4 KiB pages while the kernel page size may + * be larger, for example 64 KiB on arm64. The FF-A descriptor is registered + * from the 4 KiB page containing the start of the shared buffer, so + * internal_offs is the offset into that page. + */ +static void optee_ffa_set_internal_offs(struct optee_msg_param_fmem *fmem, + struct tee_shm *shm) +{ + size_t page_offs = tee_shm_get_page_offset(shm); + + BUILD_BUG_ON(PAGE_SIZE < FFA_PAGE_SIZE); + + fmem->internal_offs = page_offs & (FFA_PAGE_SIZE - 1); +} + +/* + * Keep shm_offs unchanged in offs_low/offs_high: it is returned to callers + * and may be reused for a subsequent invocation. + */ +static int optee_ffa_set_fmem_offsets(struct optee_msg_param_fmem *fmem, + struct tee_shm *shm, u64 shm_offs) +{ + optee_ffa_set_internal_offs(fmem, shm); + + fmem->offs_low = shm_offs; + fmem->offs_high = shm_offs >> 32; + /* Check that the entire offset could be stored. */ + if (fmem->offs_high != shm_offs >> 32) + return -EINVAL; + + return 0; +} + static int to_msg_param_ffa_mem(struct optee_msg_param *mp, const struct tee_param *p) { @@ -196,14 +230,8 @@ static int to_msg_param_ffa_mem(struct optee_msg_param *mp, 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) + if (optee_ffa_set_fmem_offsets(&mp->u.fmem, shm, + p->u.memref.shm_offs)) return -EINVAL;
mp->u.fmem.global_id = shm->sec_world_id; @@ -284,14 +312,30 @@ static int optee_ffa_shm_register(struct tee_context *ctx, struct tee_shm *shm, .nattrs = 1, }; struct sg_table sgt; + size_t page_offs; + size_t ffa_offs; + size_t ffa_size; int rc;
+ if (!num_pages) + return -EINVAL; + 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); + page_offs = tee_shm_get_page_offset(shm); + ffa_offs = round_down(page_offs, FFA_PAGE_SIZE); + ffa_size = num_pages * PAGE_SIZE - ffa_offs; + + /* + * Start the FF-A descriptor at the 4 KiB page containing the shared + * buffer, skipping unused leading 4 KiB pages when PAGE_SIZE is + * larger. Same approach as optee_fill_pages_list() in the SMC ABI. + * This leaves only page_offs & (FFA_PAGE_SIZE - 1) for internal_offs. + */ + rc = sg_alloc_table_from_pages(&sgt, pages, num_pages, ffa_offs, + ffa_size, GFP_KERNEL); if (rc) return rc; args.sg = sgt.sgl; @@ -458,8 +502,8 @@ static void handle_ffa_rpc_func_cmd_shm_alloc(struct tee_context *ctx, .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, }; + optee_ffa_set_internal_offs(&arg->params[0].u.fmem, shm);
arg->ret = TEEC_SUCCESS; } diff --git a/drivers/tee/optee/optee_msg.h b/drivers/tee/optee/optee_msg.h index 7d9b12e71c03..6c3043f8da33 100644 --- a/drivers/tee/optee/optee_msg.h +++ b/drivers/tee/optee/optee_msg.h @@ -136,8 +136,8 @@ struct optee_msg_param_rmem { * struct optee_msg_param_fmem - FF-A memory reference parameter * @offs_low: lower bits of offset into shared memory reference * @offs_high: higher bits of offset into shared memory reference - * @internal_offs: internal offset into the first page of shared memory - * reference + * @internal_offs: offset into the first 4 KiB page of the FF-A shared + * memory region * @size: size of the buffer * @global_id: global identifier of the shared memory */
On Fri, 04 Sep 2026 at 13:47:32 +0000, Mahantesh Salimath wrote:
OP-TEE FF-A memory objects use 4 KiB pages, while the kernel page size may be larger. Consequently, tee_shm->offset can be greater than or equal to FFA_PAGE_SIZE, but OP-TEE rejects such a value in internal_offs.
Do not encode the excess page offset in offs_low/offs_high. Those fields describe the logical memref offset and are copied back into tee_param->shm_offs on return. Folding the page offset into them breaks parameter round trips when a memref is reused. They are also ignored by the OPTEE_RPC_CMD_SHM_ALLOC response path, which uses only global_id and internal_offs to construct the shared-memory mobj.
Instead, start the FF-A descriptor at the 4 KiB page containing the shared buffer, the same approach as optee_fill_pages_list() in the SMC ABI. Store the remaining in-page offset in internal_offs and preserve shm_offs in offs_low/offs_high. This keeps internal_offs within the FF-A page size, maps RPC allocations at the correct address, and preserves normal memref offsets across repeated invocations.
Tested on ARMv8-A with 64 KiB PAGE_SIZE. OP-TEE OS ran as a secure partition under Hafnium (SPMC) over FF-A. Verified registered shared memory with tee_shm->offset >= 4 KiB, memref reuse on the same TEEC_Operation, and RPC OPTEE_RPC_CMD_SHM_ALLOC (xtest regression 6007-6009). optee_hello_world, optee_aes, and xtest regression 1005, 1007, 1008, 4001-4003 and 6001-6003 also passed.
Fixes: 4615e5a34b95 ("optee: add FF-A support") Acked-by: Liming Sun limings@nvidia.com Acked-by: James Hurley jahurley@nvidia.com Acked-by: Dave Thompson davthompson@nvidia.com Signed-off-by: Mahantesh Salimath mahantesh@nvidia.com
drivers/tee/optee/ffa_abi.c | 66 +++++++++++++++++++++++++++++------ drivers/tee/optee/optee_msg.h | 4 +-- 2 files changed, 57 insertions(+), 13 deletions(-)
diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c index 633715b98625..2a37e4899dc6 100644 --- a/drivers/tee/optee/ffa_abi.c +++ b/drivers/tee/optee/ffa_abi.c @@ -187,6 +187,40 @@ static int optee_ffa_from_msg_param(struct optee *optee, return 0; }
+/*
- OP-TEE FF-A memory objects use 4 KiB pages while the kernel page size may
- be larger, for example 64 KiB on arm64. The FF-A descriptor is registered
- from the 4 KiB page containing the start of the shared buffer, so
- internal_offs is the offset into that page.
- */
+static void optee_ffa_set_internal_offs(struct optee_msg_param_fmem *fmem,
struct tee_shm *shm)+{
- size_t page_offs = tee_shm_get_page_offset(shm);
- BUILD_BUG_ON(PAGE_SIZE < FFA_PAGE_SIZE);
- fmem->internal_offs = page_offs & (FFA_PAGE_SIZE - 1);
+}
+/*
- Keep shm_offs unchanged in offs_low/offs_high: it is returned to callers
- and may be reused for a subsequent invocation.
- */
+static int optee_ffa_set_fmem_offsets(struct optee_msg_param_fmem *fmem,
struct tee_shm *shm, u64 shm_offs)+{
- optee_ffa_set_internal_offs(fmem, shm);
- fmem->offs_low = shm_offs;
- fmem->offs_high = shm_offs >> 32;
- /* Check that the entire offset could be stored. */
- if (fmem->offs_high != shm_offs >> 32)
return -EINVAL;- return 0;
+}
static int to_msg_param_ffa_mem(struct optee_msg_param *mp, const struct tee_param *p) { @@ -196,14 +230,8 @@ static int to_msg_param_ffa_mem(struct optee_msg_param *mp, 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)
if (optee_ffa_set_fmem_offsets(&mp->u.fmem, shm,p->u.memref.shm_offs)) return -EINVAL;
This all can rather be simplified below, lots of indirection here.
diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c index 633715b98625..b9dcd2e2e28b 100644 --- a/drivers/tee/optee/ffa_abi.c +++ b/drivers/tee/optee/ffa_abi.c @@ -198,8 +198,8 @@ static int to_msg_param_ffa_mem(struct optee_msg_param *mp, if (shm) { u64 shm_offs = p->u.memref.shm_offs;
- mp->u.fmem.internal_offs = shm->offset; - + mp->u.fmem.internal_offs = tee_shm_get_page_offset(shm) & + (FFA_PAGE_SIZE - 1); mp->u.fmem.offs_low = shm_offs; mp->u.fmem.offs_high = shm_offs >> 32; /* Check that the entire offset could be stored. */
mp->u.fmem.global_id = shm->sec_world_id;@@ -284,14 +312,30 @@ static int optee_ffa_shm_register(struct tee_context *ctx, struct tee_shm *shm, .nattrs = 1, }; struct sg_table sgt;
size_t page_offs;
size_t ffa_offs;
size_t ffa_size; int rc;
if (!num_pages)
return -EINVAL;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);
- page_offs = tee_shm_get_page_offset(shm);
- ffa_offs = round_down(page_offs, FFA_PAGE_SIZE);
- ffa_size = num_pages * PAGE_SIZE - ffa_offs;
Avoid redundant variables which are used only once.
- /*
* Start the FF-A descriptor at the 4 KiB page containing the shared* buffer, skipping unused leading 4 KiB pages when PAGE_SIZE is* larger. Same approach as optee_fill_pages_list() in the SMC ABI.* This leaves only page_offs & (FFA_PAGE_SIZE - 1) for internal_offs.*/- rc = sg_alloc_table_from_pages(&sgt, pages, num_pages, ffa_offs,
if (rc) return rc; args.sg = sgt.sgl;ffa_size, GFP_KERNEL);@@ -458,8 +502,8 @@ static void handle_ffa_rpc_func_cmd_shm_alloc(struct tee_context *ctx, .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,
- optee_ffa_set_internal_offs(&arg->params[0].u.fmem, shm);
Ditto, directly inline the masking here.
-Sumit
arg->ret = TEEC_SUCCESS; } diff --git a/drivers/tee/optee/optee_msg.h b/drivers/tee/optee/optee_msg.h index 7d9b12e71c03..6c3043f8da33 100644 --- a/drivers/tee/optee/optee_msg.h +++ b/drivers/tee/optee/optee_msg.h @@ -136,8 +136,8 @@ struct optee_msg_param_rmem {
- struct optee_msg_param_fmem - FF-A memory reference parameter
- @offs_low: lower bits of offset into shared memory reference
- @offs_high: higher bits of offset into shared memory reference
- @internal_offs: internal offset into the first page of shared memory
reference
- @internal_offs: offset into the first 4 KiB page of the FF-A shared
memory region
- @size: size of the buffer
- @global_id: global identifier of the shared memory
*/
2.43.0
Thanks for the review. I'll send a v2 shortly with the helpers inlined and the extra locals dropped, as you suggested.
Thanks, Mahantesh
OP-TEE FF-A memory objects use 4 KiB pages, while the kernel page size may be larger. Consequently, tee_shm->offset can be greater than or equal to FFA_PAGE_SIZE, but OP-TEE rejects such a value in internal_offs.
Do not encode the excess page offset in offs_low/offs_high. Those fields describe the logical memref offset and are copied back into tee_param->shm_offs on return. Folding the page offset into them breaks parameter round trips when a memref is reused. They are also ignored by the OPTEE_RPC_CMD_SHM_ALLOC response path, which uses only global_id and internal_offs to construct the shared-memory mobj.
Instead, start the FF-A descriptor at the 4 KiB page containing the shared buffer, the same approach as optee_fill_pages_list() in the SMC ABI. Store the remaining in-page offset in internal_offs and preserve shm_offs in offs_low/offs_high. This keeps internal_offs within the FF-A page size, maps RPC allocations at the correct address, and preserves normal memref offsets across repeated invocations.
Tested on ARMv8-A with 64 KiB PAGE_SIZE. OP-TEE OS ran as a secure partition under Hafnium (SPMC) over FF-A. Verified registered shared memory with tee_shm->offset >= 4 KiB, memref reuse on the same TEEC_Operation, and RPC OPTEE_RPC_CMD_SHM_ALLOC (xtest regression 6007-6009). optee_hello_world, optee_aes, and xtest regression 1005, 1007, 1008, 4001-4003 and 6001-6003 also passed.
Fixes: 4615e5a34b95 ("optee: add FF-A support") Acked-by: Liming Sun limings@nvidia.com Acked-by: James Hurley jahurley@nvidia.com Acked-by: Dave Thompson davthompson@nvidia.com Signed-off-by: Mahantesh Salimath mahantesh@nvidia.com --- v2: - Drop helper indirection; mask internal_offs inline (Sumit Garg) - Keep a single ffa_offs local in optee_ffa_shm_register()
Link: https://lore.kernel.org/lkml/20260904134732.1072541-1-mahantesh@nvidia.com/
drivers/tee/optee/ffa_abi.c | 22 ++++++++++++++++++---- drivers/tee/optee/optee_msg.h | 4 ++-- 2 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c index 633715b98625..fdedcab50f23 100644 --- a/drivers/tee/optee/ffa_abi.c +++ b/drivers/tee/optee/ffa_abi.c @@ -198,7 +198,8 @@ static int to_msg_param_ffa_mem(struct optee_msg_param *mp, if (shm) { u64 shm_offs = p->u.memref.shm_offs;
- mp->u.fmem.internal_offs = shm->offset; + mp->u.fmem.internal_offs = tee_shm_get_page_offset(shm) & + (FFA_PAGE_SIZE - 1);
mp->u.fmem.offs_low = shm_offs; mp->u.fmem.offs_high = shm_offs >> 32; @@ -284,14 +285,26 @@ static int optee_ffa_shm_register(struct tee_context *ctx, struct tee_shm *shm, .nattrs = 1, }; struct sg_table sgt; + size_t ffa_offs; int rc;
+ if (!num_pages) + return -EINVAL; + 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); + /* + * Start the FF-A descriptor at the 4 KiB page containing the shared + * buffer, skipping unused leading 4 KiB pages when PAGE_SIZE is + * larger. Same approach as optee_fill_pages_list() in the SMC ABI. + * This leaves only the offset into that 4 KiB page for internal_offs. + */ + ffa_offs = round_down(tee_shm_get_page_offset(shm), FFA_PAGE_SIZE); + rc = sg_alloc_table_from_pages(&sgt, pages, num_pages, ffa_offs, + num_pages * PAGE_SIZE - ffa_offs, + GFP_KERNEL); if (rc) return rc; args.sg = sgt.sgl; @@ -458,7 +471,8 @@ static void handle_ffa_rpc_func_cmd_shm_alloc(struct tee_context *ctx, .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, + .u.fmem.internal_offs = tee_shm_get_page_offset(shm) & + (FFA_PAGE_SIZE - 1), };
arg->ret = TEEC_SUCCESS; diff --git a/drivers/tee/optee/optee_msg.h b/drivers/tee/optee/optee_msg.h index 7d9b12e71c03..6c3043f8da33 100644 --- a/drivers/tee/optee/optee_msg.h +++ b/drivers/tee/optee/optee_msg.h @@ -136,8 +136,8 @@ struct optee_msg_param_rmem { * struct optee_msg_param_fmem - FF-A memory reference parameter * @offs_low: lower bits of offset into shared memory reference * @offs_high: higher bits of offset into shared memory reference - * @internal_offs: internal offset into the first page of shared memory - * reference + * @internal_offs: offset into the first 4 KiB page of the FF-A shared + * memory region * @size: size of the buffer * @global_id: global identifier of the shared memory */
op-tee@lists.trustedfirmware.org