FF-A 1.2 extends the endpoint memory access descriptor (EMAD) from
16 to 32 bytes, adding implementation-defined (IMPDEF) fields and
reserved space. The MEM_SHARE path currently assumes the 1.1 EMAD
size and rejects the 1.2 layout.
Add FF-A 1.2 EMAD support to MEM_SHARE:
- define ffa_mem_access_1_2 and store IMPDEF payload in ffa_shm_mem
- emit 1.2 EMADs to the SPMC for FF-A 1.2 guests, forwarding IMPDEF
- refactor header parsing into read_mem_transaction() for 1.0/1.1+
- detect EMAD format by mem_access_size to allow 1.1 on 1.2 guests
Functional impact: MEM_SHARE supports FF-A 1.2 EMADs.
Signed-off-by: Bertrand Marquis <bertrand.marquis@arm.com>
---
xen/arch/arm/tee/ffa_shm.c | 108 +++++++++++++++++++++++++++++--------
1 file changed, 86 insertions(+), 22 deletions(-)
diff --git a/xen/arch/arm/tee/ffa_shm.c b/xen/arch/arm/tee/ffa_shm.c
index 4c0b45cde6ee..905a64e3db01 100644
--- a/xen/arch/arm/tee/ffa_shm.c
+++ b/xen/arch/arm/tee/ffa_shm.c
@@ -30,6 +30,14 @@ struct ffa_mem_access {
uint64_t reserved;
};
+/* Endpoint memory access descriptor (FF-A 1.2) */
+struct ffa_mem_access_1_2 {
+ struct ffa_mem_access_perm access_perm;
+ uint32_t region_offs;
+ uint8_t impdef[16];
+ uint8_t reserved[8];
+};
+
/* Lend, donate or share memory transaction descriptor */
struct ffa_mem_transaction_1_0 {
uint16_t sender_id;
@@ -73,7 +81,7 @@ struct ffa_mem_transaction_1_1 {
/*
* The parts needed from struct ffa_mem_transaction_1_0 or struct
* ffa_mem_transaction_1_1, used to provide an abstraction of difference in
- * data structures between version 1.0 and 1.1. This is just an internal
+ * data structures between version 1.0 and 1.2. This is just an internal
* interface and can be changed without changing any ABI.
*/
struct ffa_mem_transaction_int {
@@ -92,6 +100,8 @@ struct ffa_shm_mem {
uint16_t sender_id;
uint16_t ep_id; /* endpoint, the one lending */
uint64_t handle; /* FFA_HANDLE_INVALID if not set yet */
+ /* Endpoint memory access descriptor IMPDEF value (FF-A 1.2). */
+ uint64_t impdef[2];
unsigned int page_count;
struct page_info *pages[];
};
@@ -297,17 +307,21 @@ static void init_range(struct ffa_address_range *addr_range,
* This function uses the ffa_spmc tx buffer to transmit the memory transaction
* descriptor.
*/
-static int share_shm(struct ffa_shm_mem *shm)
+static int share_shm(struct ffa_shm_mem *shm, uint32_t ffa_vers)
{
const uint32_t max_frag_len = FFA_RXTX_PAGE_COUNT * FFA_PAGE_SIZE;
struct ffa_mem_access *mem_access_array;
+ struct ffa_mem_access_1_2 *mem_access_array_1_2;
struct ffa_mem_transaction_1_1 *descr;
struct ffa_address_range *addr_range;
struct ffa_mem_region *region_descr;
- const unsigned int region_count = 1;
uint32_t frag_len;
uint32_t tot_len;
+ uint32_t mem_access_size;
+ uint32_t mem_access_offs;
+ uint32_t region_offs;
paddr_t last_pa;
+ uint32_t range_count;
unsigned int n;
paddr_t pa;
int32_t ret;
@@ -326,16 +340,35 @@ static int share_shm(struct ffa_shm_mem *shm)
descr->handle = shm->handle;
descr->mem_reg_attr = FFA_NORMAL_MEM_REG_ATTR;
descr->mem_access_count = 1;
- descr->mem_access_size = sizeof(*mem_access_array);
- descr->mem_access_offs = MEM_ACCESS_OFFSET(0);
+ if ( ffa_vers >= FFA_VERSION_1_2 )
+ mem_access_size = sizeof(struct ffa_mem_access_1_2);
+ else
+ mem_access_size = sizeof(struct ffa_mem_access);
+ mem_access_offs = sizeof(struct ffa_mem_transaction_1_1);
+ region_offs = mem_access_offs + mem_access_size;
+ descr->mem_access_size = mem_access_size;
+ descr->mem_access_offs = mem_access_offs;
- mem_access_array = buf + descr->mem_access_offs;
- memset(mem_access_array, 0, sizeof(*mem_access_array));
- mem_access_array[0].access_perm.endpoint_id = shm->ep_id;
- mem_access_array[0].access_perm.perm = FFA_MEM_ACC_RW;
- mem_access_array[0].region_offs = REGION_OFFSET(descr->mem_access_count, 0);
+ if ( ffa_vers >= FFA_VERSION_1_2 )
+ {
+ mem_access_array_1_2 = buf + mem_access_offs;
+ memset(mem_access_array_1_2, 0, sizeof(*mem_access_array_1_2));
+ mem_access_array_1_2[0].access_perm.endpoint_id = shm->ep_id;
+ mem_access_array_1_2[0].access_perm.perm = FFA_MEM_ACC_RW;
+ mem_access_array_1_2[0].region_offs = region_offs;
+ memcpy(mem_access_array_1_2[0].impdef, shm->impdef,
+ sizeof(mem_access_array_1_2[0].impdef));
+ }
+ else
+ {
+ mem_access_array = buf + mem_access_offs;
+ memset(mem_access_array, 0, sizeof(*mem_access_array));
+ mem_access_array[0].access_perm.endpoint_id = shm->ep_id;
+ mem_access_array[0].access_perm.perm = FFA_MEM_ACC_RW;
+ mem_access_array[0].region_offs = region_offs;
+ }
- region_descr = buf + mem_access_array[0].region_offs;
+ region_descr = buf + region_offs;
memset(region_descr, 0, sizeof(*region_descr));
region_descr->total_page_count = shm->page_count;
@@ -349,8 +382,9 @@ static int share_shm(struct ffa_shm_mem *shm)
region_descr->address_range_count++;
}
- tot_len = ADDR_RANGE_OFFSET(descr->mem_access_count, region_count,
- region_descr->address_range_count);
+ range_count = region_descr->address_range_count;
+ tot_len = region_offs + sizeof(*region_descr) +
+ range_count * sizeof(struct ffa_address_range);
if ( tot_len > max_frag_len )
{
ret = FFA_RET_NOT_SUPPORTED;
@@ -358,7 +392,7 @@ static int share_shm(struct ffa_shm_mem *shm)
}
addr_range = region_descr->address_range_array;
- frag_len = ADDR_RANGE_OFFSET(descr->mem_access_count, region_count, 1);
+ frag_len = region_offs + sizeof(*region_descr) + sizeof(*addr_range);
last_pa = page_to_maddr(shm->pages[0]);
init_range(addr_range, last_pa);
for ( n = 1; n < shm->page_count; last_pa = pa, n++ )
@@ -448,6 +482,12 @@ static int read_mem_transaction(uint32_t ffa_vers, const void *buf, size_t blen,
if ( size * count + offs > blen )
return FFA_RET_INVALID_PARAMETERS;
+ if ( size < sizeof(struct ffa_mem_access) )
+ return FFA_RET_INVALID_PARAMETERS;
+
+ if ( offs & 0xF )
+ return FFA_RET_INVALID_PARAMETERS;
+
trans->mem_reg_attr = mem_reg_attr;
trans->flags = flags;
trans->mem_access_size = size;
@@ -464,7 +504,7 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
uint64_t addr = get_user_reg(regs, 3);
uint32_t page_count = get_user_reg(regs, 4);
const struct ffa_mem_region *region_descr;
- const struct ffa_mem_access *mem_access;
+ const struct ffa_mem_access_1_2 *mem_access;
struct ffa_mem_transaction_int trans;
struct domain *d = current->domain;
struct ffa_ctx *ctx = d->arch.tee;
@@ -474,9 +514,12 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
register_t handle_hi = 0;
register_t handle_lo = 0;
int ret = FFA_RET_DENIED;
+ uint32_t ffa_vers;
uint32_t range_count;
uint32_t region_offs;
uint16_t dst_id;
+ uint8_t perm;
+ uint64_t impdef[2];
if ( !ffa_fw_supports_fid(FFA_MEM_SHARE_64) )
{
@@ -515,8 +558,8 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
if ( frag_len > tx_size )
goto out_unlock;
- ret = read_mem_transaction(ACCESS_ONCE(ctx->guest_vers), tx_buf,
- frag_len, &trans);
+ ffa_vers = ACCESS_ONCE(ctx->guest_vers);
+ ret = read_mem_transaction(ffa_vers, tx_buf, frag_len, &trans);
if ( ret )
goto out_unlock;
@@ -545,13 +588,35 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
goto out_unlock;
}
+ if ( trans.mem_access_size < sizeof(struct ffa_mem_access) )
+ {
+ ret = FFA_RET_INVALID_PARAMETERS;
+ goto out_unlock;
+ }
+
/* Check that it fits in the supplied data */
if ( trans.mem_access_offs + trans.mem_access_size > frag_len )
goto out_unlock;
mem_access = tx_buf + trans.mem_access_offs;
-
dst_id = ACCESS_ONCE(mem_access->access_perm.endpoint_id);
+ perm = ACCESS_ONCE(mem_access->access_perm.perm);
+ region_offs = ACCESS_ONCE(mem_access->region_offs);
+
+ /*
+ * FF-A 1.2 introduced an extended mem_access descriptor with impdef
+ * fields, but guests can still use the 1.1 format if they don't need
+ * implementation-defined data. Detect which format is used based on
+ * the mem_access_size field rather than the negotiated FF-A version.
+ */
+ if ( trans.mem_access_size >= sizeof(struct ffa_mem_access_1_2) )
+ memcpy(impdef, mem_access->impdef, sizeof(impdef));
+ else
+ {
+ impdef[0] = 0;
+ impdef[1] = 0;
+ }
+
if ( !FFA_ID_IS_SECURE(dst_id) )
{
/* we do not support sharing with VMs */
@@ -559,13 +624,11 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
goto out_unlock;
}
- if ( ACCESS_ONCE(mem_access->access_perm.perm) != FFA_MEM_ACC_RW )
+ if ( perm != FFA_MEM_ACC_RW )
{
ret = FFA_RET_NOT_SUPPORTED;
goto out_unlock;
}
-
- region_offs = ACCESS_ONCE(mem_access->region_offs);
if ( sizeof(*region_descr) + region_offs > frag_len )
{
ret = FFA_RET_NOT_SUPPORTED;
@@ -590,6 +653,7 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
}
shm->sender_id = trans.sender_id;
shm->ep_id = dst_id;
+ memcpy(shm->impdef, impdef, sizeof(shm->impdef));
/*
* Check that the Composite memory region descriptor fits.
@@ -605,7 +669,7 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
if ( ret )
goto out;
- ret = share_shm(shm);
+ ret = share_shm(shm, ffa_vers);
if ( ret )
goto out;
--
2.50.1 (Apple Git-155)
Hi Bertrand,
On Tue, Feb 3, 2026 at 6:38 PM Bertrand Marquis
<bertrand.marquis@arm.com> wrote:
>
> FF-A 1.2 extends the endpoint memory access descriptor (EMAD) from
> 16 to 32 bytes, adding implementation-defined (IMPDEF) fields and
> reserved space. The MEM_SHARE path currently assumes the 1.1 EMAD
> size and rejects the 1.2 layout.
>
> Add FF-A 1.2 EMAD support to MEM_SHARE:
> - define ffa_mem_access_1_2 and store IMPDEF payload in ffa_shm_mem
> - emit 1.2 EMADs to the SPMC for FF-A 1.2 guests, forwarding IMPDEF
> - refactor header parsing into read_mem_transaction() for 1.0/1.1+
> - detect EMAD format by mem_access_size to allow 1.1 on 1.2 guests
>
> Functional impact: MEM_SHARE supports FF-A 1.2 EMADs.
>
> Signed-off-by: Bertrand Marquis <bertrand.marquis@arm.com>
> ---
> xen/arch/arm/tee/ffa_shm.c | 108 +++++++++++++++++++++++++++++--------
> 1 file changed, 86 insertions(+), 22 deletions(-)
>
> diff --git a/xen/arch/arm/tee/ffa_shm.c b/xen/arch/arm/tee/ffa_shm.c
> index 4c0b45cde6ee..905a64e3db01 100644
> --- a/xen/arch/arm/tee/ffa_shm.c
> +++ b/xen/arch/arm/tee/ffa_shm.c
> @@ -30,6 +30,14 @@ struct ffa_mem_access {
> uint64_t reserved;
> };
>
> +/* Endpoint memory access descriptor (FF-A 1.2) */
> +struct ffa_mem_access_1_2 {
> + struct ffa_mem_access_perm access_perm;
> + uint32_t region_offs;
> + uint8_t impdef[16];
> + uint8_t reserved[8];
> +};
> +
> /* Lend, donate or share memory transaction descriptor */
> struct ffa_mem_transaction_1_0 {
> uint16_t sender_id;
> @@ -73,7 +81,7 @@ struct ffa_mem_transaction_1_1 {
> /*
> * The parts needed from struct ffa_mem_transaction_1_0 or struct
> * ffa_mem_transaction_1_1, used to provide an abstraction of difference in
> - * data structures between version 1.0 and 1.1. This is just an internal
> + * data structures between version 1.0 and 1.2. This is just an internal
> * interface and can be changed without changing any ABI.
> */
> struct ffa_mem_transaction_int {
> @@ -92,6 +100,8 @@ struct ffa_shm_mem {
> uint16_t sender_id;
> uint16_t ep_id; /* endpoint, the one lending */
> uint64_t handle; /* FFA_HANDLE_INVALID if not set yet */
> + /* Endpoint memory access descriptor IMPDEF value (FF-A 1.2). */
> + uint64_t impdef[2];
> unsigned int page_count;
> struct page_info *pages[];
> };
> @@ -297,17 +307,21 @@ static void init_range(struct ffa_address_range *addr_range,
> * This function uses the ffa_spmc tx buffer to transmit the memory transaction
> * descriptor.
> */
> -static int share_shm(struct ffa_shm_mem *shm)
> +static int share_shm(struct ffa_shm_mem *shm, uint32_t ffa_vers)
> {
> const uint32_t max_frag_len = FFA_RXTX_PAGE_COUNT * FFA_PAGE_SIZE;
> struct ffa_mem_access *mem_access_array;
> + struct ffa_mem_access_1_2 *mem_access_array_1_2;
> struct ffa_mem_transaction_1_1 *descr;
> struct ffa_address_range *addr_range;
> struct ffa_mem_region *region_descr;
> - const unsigned int region_count = 1;
> uint32_t frag_len;
> uint32_t tot_len;
> + uint32_t mem_access_size;
> + uint32_t mem_access_offs;
> + uint32_t region_offs;
> paddr_t last_pa;
> + uint32_t range_count;
> unsigned int n;
> paddr_t pa;
> int32_t ret;
> @@ -326,16 +340,35 @@ static int share_shm(struct ffa_shm_mem *shm)
> descr->handle = shm->handle;
> descr->mem_reg_attr = FFA_NORMAL_MEM_REG_ATTR;
> descr->mem_access_count = 1;
> - descr->mem_access_size = sizeof(*mem_access_array);
> - descr->mem_access_offs = MEM_ACCESS_OFFSET(0);
> + if ( ffa_vers >= FFA_VERSION_1_2 )
> + mem_access_size = sizeof(struct ffa_mem_access_1_2);
> + else
> + mem_access_size = sizeof(struct ffa_mem_access);
> + mem_access_offs = sizeof(struct ffa_mem_transaction_1_1);
> + region_offs = mem_access_offs + mem_access_size;
> + descr->mem_access_size = mem_access_size;
> + descr->mem_access_offs = mem_access_offs;
>
> - mem_access_array = buf + descr->mem_access_offs;
> - memset(mem_access_array, 0, sizeof(*mem_access_array));
> - mem_access_array[0].access_perm.endpoint_id = shm->ep_id;
> - mem_access_array[0].access_perm.perm = FFA_MEM_ACC_RW;
> - mem_access_array[0].region_offs = REGION_OFFSET(descr->mem_access_count, 0);
> + if ( ffa_vers >= FFA_VERSION_1_2 )
> + {
> + mem_access_array_1_2 = buf + mem_access_offs;
> + memset(mem_access_array_1_2, 0, sizeof(*mem_access_array_1_2));
> + mem_access_array_1_2[0].access_perm.endpoint_id = shm->ep_id;
> + mem_access_array_1_2[0].access_perm.perm = FFA_MEM_ACC_RW;
> + mem_access_array_1_2[0].region_offs = region_offs;
> + memcpy(mem_access_array_1_2[0].impdef, shm->impdef,
> + sizeof(mem_access_array_1_2[0].impdef));
> + }
> + else
> + {
> + mem_access_array = buf + mem_access_offs;
> + memset(mem_access_array, 0, sizeof(*mem_access_array));
> + mem_access_array[0].access_perm.endpoint_id = shm->ep_id;
> + mem_access_array[0].access_perm.perm = FFA_MEM_ACC_RW;
> + mem_access_array[0].region_offs = region_offs;
> + }
>
> - region_descr = buf + mem_access_array[0].region_offs;
> + region_descr = buf + region_offs;
> memset(region_descr, 0, sizeof(*region_descr));
> region_descr->total_page_count = shm->page_count;
>
> @@ -349,8 +382,9 @@ static int share_shm(struct ffa_shm_mem *shm)
> region_descr->address_range_count++;
> }
>
> - tot_len = ADDR_RANGE_OFFSET(descr->mem_access_count, region_count,
> - region_descr->address_range_count);
Please remove the unused ADDR_RANGE_OFFSET() macro and friends, as
they're no longer accurate.
> + range_count = region_descr->address_range_count;
> + tot_len = region_offs + sizeof(*region_descr) +
> + range_count * sizeof(struct ffa_address_range);
> if ( tot_len > max_frag_len )
> {
> ret = FFA_RET_NOT_SUPPORTED;
> @@ -358,7 +392,7 @@ static int share_shm(struct ffa_shm_mem *shm)
> }
>
> addr_range = region_descr->address_range_array;
> - frag_len = ADDR_RANGE_OFFSET(descr->mem_access_count, region_count, 1);
> + frag_len = region_offs + sizeof(*region_descr) + sizeof(*addr_range);
> last_pa = page_to_maddr(shm->pages[0]);
> init_range(addr_range, last_pa);
> for ( n = 1; n < shm->page_count; last_pa = pa, n++ )
> @@ -448,6 +482,12 @@ static int read_mem_transaction(uint32_t ffa_vers, const void *buf, size_t blen,
> if ( size * count + offs > blen )
> return FFA_RET_INVALID_PARAMETERS;
>
> + if ( size < sizeof(struct ffa_mem_access) )
> + return FFA_RET_INVALID_PARAMETERS;
Implicitly, size should also be a multiple of 16. Don't you agree?
> +
> + if ( offs & 0xF )
> + return FFA_RET_INVALID_PARAMETERS;
> +
> trans->mem_reg_attr = mem_reg_attr;
> trans->flags = flags;
> trans->mem_access_size = size;
> @@ -464,7 +504,7 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
> uint64_t addr = get_user_reg(regs, 3);
> uint32_t page_count = get_user_reg(regs, 4);
> const struct ffa_mem_region *region_descr;
> - const struct ffa_mem_access *mem_access;
> + const struct ffa_mem_access_1_2 *mem_access;
> struct ffa_mem_transaction_int trans;
> struct domain *d = current->domain;
> struct ffa_ctx *ctx = d->arch.tee;
> @@ -474,9 +514,12 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
> register_t handle_hi = 0;
> register_t handle_lo = 0;
> int ret = FFA_RET_DENIED;
> + uint32_t ffa_vers;
> uint32_t range_count;
> uint32_t region_offs;
> uint16_t dst_id;
> + uint8_t perm;
> + uint64_t impdef[2];
>
> if ( !ffa_fw_supports_fid(FFA_MEM_SHARE_64) )
> {
> @@ -515,8 +558,8 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
> if ( frag_len > tx_size )
> goto out_unlock;
>
> - ret = read_mem_transaction(ACCESS_ONCE(ctx->guest_vers), tx_buf,
> - frag_len, &trans);
> + ffa_vers = ACCESS_ONCE(ctx->guest_vers);
> + ret = read_mem_transaction(ffa_vers, tx_buf, frag_len, &trans);
> if ( ret )
> goto out_unlock;
>
> @@ -545,13 +588,35 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
> goto out_unlock;
> }
>
> + if ( trans.mem_access_size < sizeof(struct ffa_mem_access) )
> + {
> + ret = FFA_RET_INVALID_PARAMETERS;
> + goto out_unlock;
> + }
> +
> /* Check that it fits in the supplied data */
> if ( trans.mem_access_offs + trans.mem_access_size > frag_len )
> goto out_unlock;
>
> mem_access = tx_buf + trans.mem_access_offs;
> -
> dst_id = ACCESS_ONCE(mem_access->access_perm.endpoint_id);
> + perm = ACCESS_ONCE(mem_access->access_perm.perm);
> + region_offs = ACCESS_ONCE(mem_access->region_offs);
> +
> + /*
> + * FF-A 1.2 introduced an extended mem_access descriptor with impdef
> + * fields, but guests can still use the 1.1 format if they don't need
> + * implementation-defined data. Detect which format is used based on
> + * the mem_access_size field rather than the negotiated FF-A version.
> + */
> + if ( trans.mem_access_size >= sizeof(struct ffa_mem_access_1_2) )
> + memcpy(impdef, mem_access->impdef, sizeof(impdef));
> + else
> + {
> + impdef[0] = 0;
> + impdef[1] = 0;
> + }
> +
> if ( !FFA_ID_IS_SECURE(dst_id) )
> {
> /* we do not support sharing with VMs */
> @@ -559,13 +624,11 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
> goto out_unlock;
> }
>
> - if ( ACCESS_ONCE(mem_access->access_perm.perm) != FFA_MEM_ACC_RW )
> + if ( perm != FFA_MEM_ACC_RW )
> {
> ret = FFA_RET_NOT_SUPPORTED;
> goto out_unlock;
> }
> -
> - region_offs = ACCESS_ONCE(mem_access->region_offs);
> if ( sizeof(*region_descr) + region_offs > frag_len )
> {
> ret = FFA_RET_NOT_SUPPORTED;
> @@ -590,6 +653,7 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
> }
> shm->sender_id = trans.sender_id;
> shm->ep_id = dst_id;
> + memcpy(shm->impdef, impdef, sizeof(shm->impdef));
>
> /*
> * Check that the Composite memory region descriptor fits.
> @@ -605,7 +669,7 @@ void ffa_handle_mem_share(struct cpu_user_regs *regs)
> if ( ret )
> goto out;
>
> - ret = share_shm(shm);
> + ret = share_shm(shm, ffa_vers);
Shouldn't we rather use ffa_fw_version?
Cheers,
Jens
> if ( ret )
> goto out;
>
> --
> 2.50.1 (Apple Git-155)
>
© 2016 - 2026 Red Hat, Inc.