* [PATCH v2 1/3] firmware: arm_ffa: Share FF-A register field definitions
2026-11-12 11:14 [PATCH v2 0/3] ACPI: Arm FFH: Add support for OpRegion Offset 2 (FF-A DIRECT_REQ2) Sudeep Holla
@ 2026-11-12 11:14 ` Sudeep Holla
2026-11-12 11:14 ` [PATCH v2 2/3] ACPI: FFH: Support FF-A direct request 2 calls Sudeep Holla
2026-11-12 11:14 ` [PATCH v2 3/3] firmware: arm_ffa: Share FF-A version negotiation Sudeep Holla
2 siblings, 0 replies; 5+ messages in thread
From: Sudeep Holla @ 2026-11-12 11:14 UTC (permalink / raw)
To: linux-acpi, linux-arm-kernel
Cc: Sudeep Holla, Jamie Nguyen, Lorenzo Pieralisi, Hanjun Guo,
Catalin Marinas, Will Deacon, Dat Mach
ACPI FFH and the FF-A transport driver interpret the same
PARTITION_INFO_GET_REGS metadata and pack endpoint IDs using the same
bit fields. Define these fields once in arm_ffa.h instead of keeping
private copies.
Use the shared masks in the transport driver and check descriptor
lengths against the same minimum used by ACPI FFH. This keeps the ABI
encoding consistent between the two users.
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
drivers/firmware/arm_ffa/driver.c | 48 ++++++++++++++-------------------------
include/linux/arm_ffa.h | 19 ++++++++++++++++
2 files changed, 36 insertions(+), 31 deletions(-)
diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c
index 28abc808fd32..babc5d62bdd2 100644
--- a/drivers/firmware/arm_ffa/driver.c
+++ b/drivers/firmware/arm_ffa/driver.c
@@ -52,13 +52,6 @@
#define FFA_MIN_VERSION FFA_VERSION_1_0
#define FFA_PLATFORM_NAME "arm-ffa"
-#define SENDER_ID_MASK GENMASK(31, 16)
-#define RECEIVER_ID_MASK GENMASK(15, 0)
-#define SENDER_ID(x) ((u16)(FIELD_GET(SENDER_ID_MASK, (x))))
-#define RECEIVER_ID(x) ((u16)(FIELD_GET(RECEIVER_ID_MASK, (x))))
-#define PACK_TARGET_INFO(s, r) \
- (FIELD_PREP(SENDER_ID_MASK, (s)) | FIELD_PREP(RECEIVER_ID_MASK, (r)))
-
#define RXTX_MAP_MIN_BUFSZ_MASK GENMASK(1, 0)
#define RXTX_MAP_MAX_BUFSZ_MASK GENMASK(31, 16)
#define RXTX_MAP_MIN_BUFSZ(x) (FIELD_GET(RXTX_MAP_MIN_BUFSZ_MASK, (x)))
@@ -320,24 +313,16 @@ __ffa_partition_info_get(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
return count;
}
-#define LAST_INDEX_MASK GENMASK(15, 0)
-#define CURRENT_INDEX_MASK GENMASK(31, 16)
-#define UUID_INFO_TAG_MASK GENMASK(47, 32)
-#define PARTITION_INFO_SZ_MASK GENMASK(63, 48)
-#define PARTITION_COUNT(x) ((u16)(FIELD_GET(LAST_INDEX_MASK, (x))) + 1)
-#define CURRENT_INDEX(x) ((u16)(FIELD_GET(CURRENT_INDEX_MASK, (x))))
-#define UUID_INFO_TAG(x) ((u16)(FIELD_GET(UUID_INFO_TAG_MASK, (x))))
-#define PARTITION_INFO_SZ(x) ((u16)(FIELD_GET(PARTITION_INFO_SZ_MASK, (x))))
-#define PART_INFO_ID_MASK GENMASK(15, 0)
-#define PART_INFO_EXEC_CXT_MASK GENMASK(31, 16)
-#define PART_INFO_PROPS_MASK GENMASK(63, 32)
+#define PARTITION_COUNT(x) ((u16)(FIELD_GET(FFA_PART_INFO_LAST_IDX_MASK, (x))) + 1)
+#define CURRENT_INDEX(x) ((u16)(FIELD_GET(FFA_PART_INFO_CUR_IDX_MASK, (x))))
+#define UUID_INFO_TAG(x) ((u16)(FIELD_GET(FFA_PART_INFO_UUID_TAG_MASK, (x))))
+#define PARTITION_INFO_SZ(x) ((u16)(FIELD_GET(FFA_PART_INFO_DESC_SIZE_MASK, (x))))
#define FFA_PART_INFO_GET_REGS_FIRST_REG 3
-#define FFA_PART_INFO_GET_REGS_MIN_REGS_PER_DESC 3
#define FFA_PART_INFO_GET_REGS_NUM_REGS \
(sizeof(ffa_value_t) / sizeof_field(ffa_value_t, a0))
-#define PART_INFO_ID(x) ((u16)(FIELD_GET(PART_INFO_ID_MASK, (x))))
-#define PART_INFO_EXEC_CXT(x) ((u16)(FIELD_GET(PART_INFO_EXEC_CXT_MASK, (x))))
-#define PART_INFO_PROPERTIES(x) ((u32)(FIELD_GET(PART_INFO_PROPS_MASK, (x))))
+#define PART_INFO_ID(x) ((u16)(FIELD_GET(FFA_PART_INFO_ID_MASK, (x))))
+#define PART_INFO_EXEC_CXT(x) ((u16)(FIELD_GET(FFA_PART_INFO_EXEC_CXT_MASK, (x))))
+#define PART_INFO_PROPERTIES(x) ((u32)(FIELD_GET(FFA_PART_INFO_PROPS_MASK, (x))))
static int
__ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
struct ffa_partition_info *buffer, int num_parts)
@@ -375,9 +360,9 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
if (buf_sz % sizeof(*regs))
return -EINVAL;
- regs_per_desc = buf_sz / sizeof(*regs);
- if (regs_per_desc < FFA_PART_INFO_GET_REGS_MIN_REGS_PER_DESC)
+ if (buf_sz < FFA_PART_INFO_MIN_DESC_SIZE)
return -EINVAL;
+ regs_per_desc = buf_sz / sizeof(*regs);
nr_desc = cur_idx - start_idx + 1;
max_desc = (FFA_PART_INFO_GET_REGS_NUM_REGS -
@@ -493,7 +478,7 @@ static inline void ffa_msg_send_wait_for_completion(ffa_value_t *ret)
static int ffa_msg_send_direct_req(u16 src_id, u16 dst_id, bool mode_32bit,
struct ffa_send_direct_data *data)
{
- u32 req_id, resp_id, src_dst_ids = PACK_TARGET_INFO(src_id, dst_id);
+ u32 req_id, resp_id, src_dst_ids = ffa_pack_target_info(src_id, dst_id);
ffa_value_t ret;
if (mode_32bit) {
@@ -529,7 +514,7 @@ static int ffa_msg_send_direct_req(u16 src_id, u16 dst_id, bool mode_32bit,
static int ffa_msg_send2(struct ffa_device *dev, u16 src_id, void *buf, size_t sz)
{
- u32 src_dst_ids = PACK_TARGET_INFO(src_id, dev->vm_id);
+ u32 src_dst_ids = ffa_pack_target_info(src_id, dev->vm_id);
struct ffa_indirect_msg_hdr *msg;
ffa_value_t ret;
int retval = 0;
@@ -563,7 +548,7 @@ static int ffa_msg_send2(struct ffa_device *dev, u16 src_id, void *buf, size_t s
static int ffa_msg_send_direct_req2(u16 src_id, u16 dst_id, const uuid_t *uuid,
struct ffa_send_direct_data2 *data)
{
- u32 src_dst_ids = PACK_TARGET_INFO(src_id, dst_id);
+ u32 src_dst_ids = ffa_pack_target_info(src_id, dst_id);
union {
uuid_t uuid;
__le64 regs[2];
@@ -889,7 +874,7 @@ enum notify_type {
#define RECEIVER_VCPU_MASK GENMASK(31, 16)
#define PACK_NOTIFICATION_GET_RECEIVER_INFO(vcpu_r, r) \
(FIELD_PREP(RECEIVER_VCPU_MASK, (vcpu_r)) | \
- FIELD_PREP(RECEIVER_ID_MASK, (r)))
+ FIELD_PREP(FFA_TARGET_INFO_RECEIVER_MASK, (r)))
#define NOTIFICATION_INFO_GET_MORE_PEND_MASK BIT(0)
#define NOTIFICATION_INFO_GET_ID_COUNT GENMASK(11, 7)
@@ -920,7 +905,7 @@ static int ffa_notification_bind_common(u16 dst_id, u64 bitmap,
u32 flags, bool is_bind)
{
ffa_value_t ret;
- u32 func, src_dst_ids = PACK_TARGET_INFO(dst_id, drv_info->vm_id);
+ u32 func, src_dst_ids = ffa_pack_target_info(dst_id, drv_info->vm_id);
func = is_bind ? FFA_NOTIFICATION_BIND : FFA_NOTIFICATION_UNBIND;
@@ -942,7 +927,7 @@ static
int ffa_notification_set(u16 src_id, u16 dst_id, u32 flags, u64 bitmap)
{
ffa_value_t ret;
- u32 src_dst_ids = PACK_TARGET_INFO(dst_id, src_id);
+ u32 src_dst_ids = ffa_pack_target_info(dst_id, src_id);
invoke_ffa_fn((ffa_value_t) {
.a0 = FFA_NOTIFICATION_SET, .a1 = src_dst_ids, .a2 = flags,
@@ -1542,7 +1527,8 @@ static void handle_fwk_notif_callbacks(u32 bitmap)
return;
}
- target = SENDER_ID(msg->send_recv_id);
+ target = FIELD_GET(FFA_TARGET_INFO_SENDER_MASK,
+ msg->send_recv_id);
if (offset >= sizeof(*msg))
uuid_copy(&uuid, &msg->uuid);
else
diff --git a/include/linux/arm_ffa.h b/include/linux/arm_ffa.h
index e71d83ee0aef..3831fbe244c1 100644
--- a/include/linux/arm_ffa.h
+++ b/include/linux/arm_ffa.h
@@ -130,6 +130,15 @@
#define FFA_FEAT_RXTX_MIN_SZ_16K 2
#define FFA_FEAT_RXTX_MIN_SZ_MASK GENMASK(1, 0)
+#define FFA_TARGET_INFO_SENDER_MASK GENMASK(31, 16)
+#define FFA_TARGET_INFO_RECEIVER_MASK GENMASK(15, 0)
+
+static inline u32 ffa_pack_target_info(u16 sender, u16 receiver)
+{
+ return FIELD_PREP(FFA_TARGET_INFO_SENDER_MASK, sender) |
+ FIELD_PREP(FFA_TARGET_INFO_RECEIVER_MASK, receiver);
+}
+
/* FFA Bus/Device/Driver related */
struct ffa_device {
u32 id;
@@ -228,6 +237,16 @@ extern const struct bus_type ffa_bus_type;
/* The FF-A 1.0 partition structure lacks the uuid[4] */
#define FFA_1_0_PARTITON_INFO_SZ (8)
+/* FFA_PARTITION_INFO_GET_REGS response fields */
+#define FFA_PART_INFO_LAST_IDX_MASK GENMASK_ULL(15, 0)
+#define FFA_PART_INFO_CUR_IDX_MASK GENMASK_ULL(31, 16)
+#define FFA_PART_INFO_UUID_TAG_MASK GENMASK_ULL(47, 32)
+#define FFA_PART_INFO_DESC_SIZE_MASK GENMASK_ULL(63, 48)
+#define FFA_PART_INFO_MIN_DESC_SIZE 24
+#define FFA_PART_INFO_ID_MASK GENMASK_ULL(15, 0)
+#define FFA_PART_INFO_EXEC_CXT_MASK GENMASK_ULL(31, 16)
+#define FFA_PART_INFO_PROPS_MASK GENMASK_ULL(63, 32)
+
/* FFA transport related */
struct ffa_partition_info {
u16 id;
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v2 2/3] ACPI: FFH: Support FF-A direct request 2 calls
2026-11-12 11:14 [PATCH v2 0/3] ACPI: Arm FFH: Add support for OpRegion Offset 2 (FF-A DIRECT_REQ2) Sudeep Holla
2026-11-12 11:14 ` [PATCH v2 1/3] firmware: arm_ffa: Share FF-A register field definitions Sudeep Holla
@ 2026-11-12 11:14 ` Sudeep Holla
2026-11-12 11:14 ` [PATCH v2 3/3] firmware: arm_ffa: Share FF-A version negotiation Sudeep Holla
2 siblings, 0 replies; 5+ messages in thread
From: Sudeep Holla @ 2026-11-12 11:14 UTC (permalink / raw)
To: linux-acpi, linux-arm-kernel
Cc: Sudeep Holla, Jamie Nguyen, Lorenzo Pieralisi, Hanjun Guo,
Catalin Marinas, Will Deacon, Dat Mach
Arm Functional Fixed Hardware specification v1.3 defines FFH
OperationRegion offset 2 for issuing FFA_MSG_SEND_DIRECT_REQ2 calls.
The arm64 FFH handler currently supports only generic 32-bit and 64-bit
SMCCC calls, so it cannot implement this standardized interface.
Add a standalone FF-A path using the existing SMCCC conduit. Negotiate
FF-A v1.2, discover the sender endpoint with FFA_ID_GET, and use either
the receiver supplied by firmware or resolve a unique receiver from the
service UUID with FFA_PARTITION_INFO_GET_REGS.
Convert the ACPI ToUUID representation to the FF-A register encoding,
construct the direct request, resume interrupted or yielded calls with
FFA_RUN, and return the defined FFH status codes to AML.
Register-based partition discovery avoids RX/TX buffer ownership and
keeps the FFH implementation independent of the Arm FF-A transport
driver.
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
drivers/acpi/arm64/ffh.c | 223 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 223 insertions(+)
diff --git a/drivers/acpi/arm64/ffh.c b/drivers/acpi/arm64/ffh.c
index 04380bab193d..7f13a6d2473a 100644
--- a/drivers/acpi/arm64/ffh.c
+++ b/drivers/acpi/arm64/ffh.c
@@ -1,7 +1,23 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/acpi.h>
+#include <linux/arm_ffa.h>
#include <linux/arm-smccc.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+#define FFH_FFA_CALL_FAILED 1
+#define FFH_FFA_SUCCESS 0
+#define FFH_FFA_NOT_SUPPORTED -1
+#define FFH_FFA_INVALID_PARAMETERS -2
+#define FFH_FFA_OUT_OF_MEMORY -3
+#define FFH_FFA_UNSPECIFIED_ERROR -4
+
+#define FFH_FFA_MIN_LEN (5 * sizeof(u64))
+#define FFH_FFA_MAX_LEN (18 * sizeof(u64))
+#define FFH_FFA_UUID_OFFSET (2 * sizeof(u64))
/*
* Implements ARM64 specific callbacks to support ACPI FFH Operation Region as
@@ -19,6 +35,211 @@ struct acpi_ffh_data {
struct arm_smccc_1_2_regs *res);
};
+static DEFINE_MUTEX(acpi_ffh_ffa_lock);
+static bool acpi_ffh_ffa_initialized, acpi_ffh_ffa_partition_info_regs;
+static u16 acpi_ffh_ffa_sender_id;
+
+static void acpi_ffh_ffa_invoke(struct acpi_ffh_data *ffh_ctxt,
+ const struct arm_smccc_1_2_regs *args,
+ struct arm_smccc_1_2_regs *res)
+{
+ ffh_ctxt->invoke_ffh64_fn(args, res);
+}
+
+static int acpi_ffh_ffa_features(struct acpi_ffh_data *ffh_ctxt, u32 fid)
+{
+ struct arm_smccc_1_2_regs args = {
+ .a0 = FFA_FEATURES,
+ .a1 = fid,
+ };
+ struct arm_smccc_1_2_regs res;
+
+ acpi_ffh_ffa_invoke(ffh_ctxt, &args, &res);
+ if (res.a0 == FFA_ERROR) {
+ if ((s32)res.a2 == FFA_RET_NOT_SUPPORTED)
+ return -EOPNOTSUPP;
+ return -EINVAL;
+ }
+
+ return res.a0 == FFA_SUCCESS ? 0 : -EIO;
+}
+
+static int acpi_ffh_ffa_init(struct acpi_ffh_data *ffh_ctxt)
+{
+ struct arm_smccc_1_2_regs args = { }, res;
+ u32 version;
+ int ret = 0;
+
+ guard(mutex)(&acpi_ffh_ffa_lock);
+ if (acpi_ffh_ffa_initialized)
+ return 0;
+
+ args.a0 = FFA_VERSION;
+ args.a1 = FFA_VERSION_1_2;
+ acpi_ffh_ffa_invoke(ffh_ctxt, &args, &res);
+ version = res.a0;
+ if ((s32)version == SMCCC_RET_NOT_SUPPORTED ||
+ FFA_MAJOR_VERSION(version) != 1 || version < FFA_VERSION_1_2) {
+ ret = -EOPNOTSUPP;
+ return ret;
+ }
+
+ ret = acpi_ffh_ffa_features(ffh_ctxt, FFA_MSG_SEND_DIRECT_REQ2);
+ if (ret)
+ return ret;
+
+ ret = acpi_ffh_ffa_features(ffh_ctxt, FFA_MSG_SEND_DIRECT_RESP2);
+ if (ret)
+ return ret;
+
+ acpi_ffh_ffa_partition_info_regs =
+ !acpi_ffh_ffa_features(ffh_ctxt, FFA_PARTITION_INFO_GET_REGS);
+
+ args = (struct arm_smccc_1_2_regs) { .a0 = FFA_ID_GET };
+ acpi_ffh_ffa_invoke(ffh_ctxt, &args, &res);
+ if (res.a0 == FFA_ERROR) {
+ ret = (s32)res.a2 == FFA_RET_NOT_SUPPORTED ? -EOPNOTSUPP : -EIO;
+ return ret;
+ }
+ if (res.a0 != FFA_SUCCESS) {
+ ret = -EIO;
+ return ret;
+ }
+
+ acpi_ffh_ffa_sender_id = (u16)res.a2;
+ acpi_ffh_ffa_initialized = true;
+ return ret;
+}
+
+static void acpi_ffh_ffa_uuid_convert(const u8 *guid, u8 *uuid)
+{
+ /* ACPI ToUUID uses GUID byte order while FF-A uses UUID byte order. */
+ uuid[0] = guid[3];
+ uuid[1] = guid[2];
+ uuid[2] = guid[1];
+ uuid[3] = guid[0];
+ uuid[4] = guid[5];
+ uuid[5] = guid[4];
+ uuid[6] = guid[7];
+ uuid[7] = guid[6];
+ memcpy(uuid + 8, guid + 8, UUID_SIZE - 8);
+}
+
+static int acpi_ffh_ffa_receiver_id(struct acpi_ffh_data *ffh_ctxt,
+ u64 uuid_lo, u64 uuid_hi, u16 *receiver_id)
+{
+ struct arm_smccc_1_2_regs args = {
+ .a0 = FFA_PARTITION_INFO_GET_REGS,
+ .a1 = uuid_lo,
+ .a2 = uuid_hi,
+ };
+ struct arm_smccc_1_2_regs res;
+ u32 properties;
+
+ if (!acpi_ffh_ffa_partition_info_regs)
+ return -EOPNOTSUPP;
+
+ acpi_ffh_ffa_invoke(ffh_ctxt, &args, &res);
+ if (res.a0 != FFA_FN64_SUCCESS)
+ return -EINVAL;
+
+ if (FIELD_GET(FFA_PART_INFO_LAST_IDX_MASK, res.a2) ||
+ FIELD_GET(FFA_PART_INFO_CUR_IDX_MASK, res.a2) ||
+ FIELD_GET(FFA_PART_INFO_DESC_SIZE_MASK, res.a2) <
+ FFA_PART_INFO_MIN_DESC_SIZE)
+ return -EINVAL;
+
+ properties = FIELD_GET(FFA_PART_INFO_PROPS_MASK, res.a3);
+ if (!(properties & FFA_PARTITION_DIRECT_REQ2_RECV))
+ return -EINVAL;
+
+ *receiver_id = FIELD_GET(FFA_PART_INFO_ID_MASK, res.a3);
+ return 0;
+}
+
+static int acpi_ffh_ffa_status(int error)
+{
+ switch (error) {
+ case -EOPNOTSUPP:
+ return FFH_FFA_NOT_SUPPORTED;
+ case -EINVAL:
+ return FFH_FFA_INVALID_PARAMETERS;
+ case -ENOMEM:
+ return FFH_FFA_OUT_OF_MEMORY;
+ default:
+ return FFH_FFA_UNSPECIFIED_ERROR;
+ }
+}
+
+static int acpi_ffh_ffa_direct_req2(acpi_integer *value,
+ struct acpi_ffh_data *ffh_ctxt)
+{
+ struct arm_smccc_1_2_regs args = { }, res;
+ u64 *buf = (u64 *)value;
+ u64 uuid_lo, uuid_hi;
+ u16 receiver_id = (u16)buf[1];
+ uuid_t uuid;
+ size_t len = ffh_ctxt->info.length;
+ int ret;
+
+ if (len < FFH_FFA_MIN_LEN || len > FFH_FFA_MAX_LEN ||
+ !IS_ALIGNED(len - 4 * sizeof(u64), sizeof(u64))) {
+ buf[0] = FFH_FFA_INVALID_PARAMETERS;
+ return AE_OK;
+ }
+
+ acpi_ffh_ffa_uuid_convert((u8 *)value + FFH_FFA_UUID_OFFSET, uuid.b);
+ if (uuid_is_null(&uuid)) {
+ buf[0] = FFH_FFA_INVALID_PARAMETERS;
+ return AE_OK;
+ }
+
+ ret = acpi_ffh_ffa_init(ffh_ctxt);
+ if (ret) {
+ buf[0] = acpi_ffh_ffa_status(ret);
+ return AE_OK;
+ }
+
+ uuid_lo = get_unaligned_le64(uuid.b);
+ uuid_hi = get_unaligned_le64(uuid.b + sizeof(uuid_lo));
+ if (!receiver_id) {
+ ret = acpi_ffh_ffa_receiver_id(ffh_ctxt, uuid_lo, uuid_hi,
+ &receiver_id);
+ if (ret) {
+ buf[0] = FFH_FFA_INVALID_PARAMETERS;
+ return AE_OK;
+ }
+ }
+
+ args.a0 = FFA_MSG_SEND_DIRECT_REQ2;
+ args.a1 = ffa_pack_target_info(acpi_ffh_ffa_sender_id, receiver_id);
+ args.a2 = uuid_lo;
+ args.a3 = uuid_hi;
+ memcpy((void *)&args + offsetof(struct arm_smccc_1_2_regs, a4),
+ &buf[4], len - 4 * sizeof(u64));
+
+ acpi_ffh_ffa_invoke(ffh_ctxt, &args, &res);
+ while (res.a0 == FFA_INTERRUPT || res.a0 == FFA_YIELD) {
+ if (res.a0 == FFA_YIELD)
+ fsleep(1000);
+
+ args = (struct arm_smccc_1_2_regs) {
+ .a0 = FFA_RUN,
+ .a1 = res.a1,
+ };
+ acpi_ffh_ffa_invoke(ffh_ctxt, &args, &res);
+ }
+
+ memcpy(buf, &res, len);
+ if (res.a0 == FFA_MSG_SEND_DIRECT_RESP2 || res.a0 == FFA_SUCCESS ||
+ res.a0 == FFA_FN64_SUCCESS)
+ buf[0] = FFH_FFA_SUCCESS;
+ else
+ buf[0] = FFH_FFA_CALL_FAILED;
+
+ return AE_OK;
+}
+
int acpi_ffh_address_space_arch_setup(void *handler_ctxt, void **region_ctxt)
{
enum arm_smccc_conduit conduit;
@@ -99,6 +320,8 @@ int acpi_ffh_address_space_arch_handler(acpi_integer *value, void *region_contex
ffh_ctxt->invoke_ffh64_fn(r, r);
memcpy(value, r, ffh_ctxt->info.length);
}
+ } else if (ffh_ctxt->info.offset == 2) {
+ ret = acpi_ffh_ffa_direct_req2(value, ffh_ctxt);
} else {
ret = AE_ERROR;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH v2 3/3] firmware: arm_ffa: Share FF-A version negotiation
2026-11-12 11:14 [PATCH v2 0/3] ACPI: Arm FFH: Add support for OpRegion Offset 2 (FF-A DIRECT_REQ2) Sudeep Holla
2026-11-12 11:14 ` [PATCH v2 1/3] firmware: arm_ffa: Share FF-A register field definitions Sudeep Holla
2026-11-12 11:14 ` [PATCH v2 2/3] ACPI: FFH: Support FF-A direct request 2 calls Sudeep Holla
@ 2026-11-12 11:14 ` Sudeep Holla
2026-11-12 16:30 ` Jamie Nguyen
2 siblings, 1 reply; 5+ messages in thread
From: Sudeep Holla @ 2026-11-12 11:14 UTC (permalink / raw)
To: linux-acpi, linux-arm-kernel
Cc: Sudeep Holla, Jamie Nguyen, Lorenzo Pieralisi, Hanjun Guo,
Catalin Marinas, Will Deacon, Dat Mach
ACPI FFH and the FF-A transport driver use the same Normal-world
endpoint. Independent FFA_VERSION calls can conflict with endpoint-wide
version negotiation and make initialization order matter.
Add a built-in helper to serialize and cache the FFA_VERSION result,
and use it from both clients. The transport retains its existing version
compatibility checks, while FFH continues to require v1.2.
Build the helper when either client is enabled so the transport
driver can remain modular.
Signed-off-by: Sudeep Holla <sudeep.holla@kernel.org>
---
drivers/acpi/arm64/ffh.c | 17 ++++++-----------
drivers/firmware/arm_ffa/Kconfig | 5 +++++
drivers/firmware/arm_ffa/Makefile | 3 ++-
drivers/firmware/arm_ffa/driver.c | 31 ++++++++++++++++--------------
drivers/firmware/arm_ffa/version.c | 39 ++++++++++++++++++++++++++++++++++++++
include/linux/arm_ffa.h | 12 ++++++++++++
6 files changed, 81 insertions(+), 26 deletions(-)
diff --git a/drivers/acpi/arm64/ffh.c b/drivers/acpi/arm64/ffh.c
index 7f13a6d2473a..2b6489d9800e 100644
--- a/drivers/acpi/arm64/ffh.c
+++ b/drivers/acpi/arm64/ffh.c
@@ -66,23 +66,18 @@ static int acpi_ffh_ffa_features(struct acpi_ffh_data *ffh_ctxt, u32 fid)
static int acpi_ffh_ffa_init(struct acpi_ffh_data *ffh_ctxt)
{
- struct arm_smccc_1_2_regs args = { }, res;
+ struct arm_smccc_1_2_regs args, res;
u32 version;
- int ret = 0;
+ int ret;
guard(mutex)(&acpi_ffh_ffa_lock);
if (acpi_ffh_ffa_initialized)
return 0;
- args.a0 = FFA_VERSION;
- args.a1 = FFA_VERSION_1_2;
- acpi_ffh_ffa_invoke(ffh_ctxt, &args, &res);
- version = res.a0;
- if ((s32)version == SMCCC_RET_NOT_SUPPORTED ||
- FFA_MAJOR_VERSION(version) != 1 || version < FFA_VERSION_1_2) {
- ret = -EOPNOTSUPP;
- return ret;
- }
+ ret = arm_ffa_version_get(ffh_ctxt->invoke_ffh64_fn, FFA_VERSION_1_2,
+ &version);
+ if (ret || version < FFA_VERSION_1_2)
+ return -EOPNOTSUPP;
ret = acpi_ffh_ffa_features(ffh_ctxt, FFA_MSG_SEND_DIRECT_REQ2);
if (ret)
diff --git a/drivers/firmware/arm_ffa/Kconfig b/drivers/firmware/arm_ffa/Kconfig
index 5e3ae5cf82e8..81be3d6dd7da 100644
--- a/drivers/firmware/arm_ffa/Kconfig
+++ b/drivers/firmware/arm_ffa/Kconfig
@@ -15,6 +15,11 @@ config ARM_FFA_TRANSPORT
This driver provides interface for all the client drivers making
use of the features offered by ARM FF-A.
+config ARM_FFA_VERSION_NEGOTIATION
+ bool
+ depends on ARM64
+ default y if ACPI_FFH || ARM_FFA_TRANSPORT
+
config ARM_FFA_SMCCC
bool
default ARM_FFA_TRANSPORT
diff --git a/drivers/firmware/arm_ffa/Makefile b/drivers/firmware/arm_ffa/Makefile
index 168990a7e792..6bd09b81e15f 100644
--- a/drivers/firmware/arm_ffa/Makefile
+++ b/drivers/firmware/arm_ffa/Makefile
@@ -4,5 +4,6 @@ ffa-driver-y = driver.o
ffa-transport-$(CONFIG_ARM_FFA_SMCCC) += smccc.o
ffa-core-objs := $(ffa-bus-y)
ffa-module-objs := $(ffa-driver-y) $(ffa-transport-y)
-obj-$(CONFIG_ARM_FFA_TRANSPORT) = ffa-core.o
+obj-$(CONFIG_ARM_FFA_VERSION_NEGOTIATION) += version.o
+obj-$(CONFIG_ARM_FFA_TRANSPORT) += ffa-core.o
obj-$(CONFIG_ARM_FFA_TRANSPORT) += ffa-module.o
diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c
index babc5d62bdd2..b9b0f88e8f24 100644
--- a/drivers/firmware/arm_ffa/driver.c
+++ b/drivers/firmware/arm_ffa/driver.c
@@ -138,30 +138,33 @@ static u32 ffa_compatible_version_find(u32 version)
return FFA_DRIVER_VERSION;
}
-static int ffa_version_check(u32 *version)
+static void ffa_version_invoke(const ffa_value_t *args, ffa_value_t *res)
{
- ffa_value_t ver;
+ invoke_ffa_fn(*args, res);
+}
- invoke_ffa_fn((ffa_value_t){
- .a0 = FFA_VERSION, .a1 = FFA_DRIVER_VERSION,
- }, &ver);
+static int ffa_version_check(u32 *version)
+{
+ int ret;
+ u32 ver;
- if ((s32)ver.a0 == FFA_RET_NOT_SUPPORTED) {
+ ret = arm_ffa_version_get(ffa_version_invoke, FFA_DRIVER_VERSION, &ver);
+ if (ret) {
pr_info("FFA_VERSION returned not supported\n");
- return -EOPNOTSUPP;
+ return ret;
}
- if (FFA_MAJOR_VERSION(ver.a0) > FFA_MAJOR_VERSION(FFA_DRIVER_VERSION)) {
+ if (FFA_MAJOR_VERSION(ver) > FFA_MAJOR_VERSION(FFA_DRIVER_VERSION)) {
pr_err("Incompatible v%d.%d! Latest supported v%d.%d\n",
- FFA_MAJOR_VERSION(ver.a0), FFA_MINOR_VERSION(ver.a0),
+ FFA_MAJOR_VERSION(ver), FFA_MINOR_VERSION(ver),
FFA_MAJOR_VERSION(FFA_DRIVER_VERSION),
FFA_MINOR_VERSION(FFA_DRIVER_VERSION));
return -EINVAL;
}
- if (ver.a0 < FFA_MIN_VERSION) {
+ if (ver < FFA_MIN_VERSION) {
pr_err("Incompatible v%d.%d! Earliest supported v%d.%d\n",
- FFA_MAJOR_VERSION(ver.a0), FFA_MINOR_VERSION(ver.a0),
+ FFA_MAJOR_VERSION(ver), FFA_MINOR_VERSION(ver),
FFA_MAJOR_VERSION(FFA_MIN_VERSION),
FFA_MINOR_VERSION(FFA_MIN_VERSION));
return -EINVAL;
@@ -169,9 +172,9 @@ static int ffa_version_check(u32 *version)
pr_info("Driver version %d.%d\n", FFA_MAJOR_VERSION(FFA_DRIVER_VERSION),
FFA_MINOR_VERSION(FFA_DRIVER_VERSION));
- pr_info("Firmware version %d.%d found\n", FFA_MAJOR_VERSION(ver.a0),
- FFA_MINOR_VERSION(ver.a0));
- *version = ffa_compatible_version_find(ver.a0);
+ pr_info("Firmware version %d.%d found\n", FFA_MAJOR_VERSION(ver),
+ FFA_MINOR_VERSION(ver));
+ *version = ffa_compatible_version_find(ver);
return 0;
}
diff --git a/drivers/firmware/arm_ffa/version.c b/drivers/firmware/arm_ffa/version.c
new file mode 100644
index 000000000000..f5c973710dff
--- /dev/null
+++ b/drivers/firmware/arm_ffa/version.c
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#define pr_fmt(fmt) "ARM FF-A: " fmt
+
+#include <linux/arm-smccc.h>
+#include <linux/arm_ffa.h>
+#include <linux/cleanup.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+static DEFINE_MUTEX(ffa_version_lock);
+static int ffa_version;
+
+int arm_ffa_version_get(void (*invoke)(const struct arm_smccc_1_2_regs *args,
+ struct arm_smccc_1_2_regs *res),
+ u32 input_version, u32 *version)
+{
+ struct arm_smccc_1_2_regs args = {
+ .a0 = FFA_VERSION,
+ .a1 = input_version,
+ }, res;
+
+ guard(mutex)(&ffa_version_lock);
+ if (!ffa_version) {
+ invoke(&args, &res);
+
+ if ((s32)res.a0 == FFA_RET_NOT_SUPPORTED)
+ ffa_version = -EOPNOTSUPP;
+ else
+ ffa_version = res.a0;
+ }
+
+ if (ffa_version < 0)
+ return ffa_version;
+
+ *version = ffa_version;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(arm_ffa_version_get);
diff --git a/include/linux/arm_ffa.h b/include/linux/arm_ffa.h
index 3831fbe244c1..039308a41cdb 100644
--- a/include/linux/arm_ffa.h
+++ b/include/linux/arm_ffa.h
@@ -6,6 +6,7 @@
#ifndef _LINUX_ARM_FFA_H
#define _LINUX_ARM_FFA_H
+#include <linux/arm-smccc.h>
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/module.h>
@@ -179,6 +180,17 @@ static inline void *ffa_dev_get_drvdata(struct ffa_device *fdev)
struct ffa_partition_info;
+#if IS_BUILTIN(CONFIG_ARM_FFA_VERSION_NEGOTIATION)
+int arm_ffa_version_get(void (*invoke)(const struct arm_smccc_1_2_regs *args,
+ struct arm_smccc_1_2_regs *res),
+ u32 input_version, u32 *version);
+#else
+static inline int arm_ffa_version_get(
+ void (*invoke)(const struct arm_smccc_1_2_regs *args,
+ struct arm_smccc_1_2_regs *res),
+ u32 input_version, u32 *version) { return -EOPNOTSUPP; }
+#endif /* CONFIG_ARM_FFA_VERSION_NEGOTIATION */
+
#if IS_REACHABLE(CONFIG_ARM_FFA_TRANSPORT)
struct ffa_device *
ffa_device_register(const struct ffa_partition_info *part_info,
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH v2 3/3] firmware: arm_ffa: Share FF-A version negotiation
2026-11-12 11:14 ` [PATCH v2 3/3] firmware: arm_ffa: Share FF-A version negotiation Sudeep Holla
@ 2026-11-12 16:30 ` Jamie Nguyen
0 siblings, 0 replies; 5+ messages in thread
From: Jamie Nguyen @ 2026-11-12 16:30 UTC (permalink / raw)
To: Sudeep Holla
Cc: Jamie Nguyen, linux-acpi, linux-arm-kernel, Lorenzo Pieralisi,
Hanjun Guo, Catalin Marinas, Will Deacon, Dat Mach
On Thu, Nov 12, 2026 at 11:14:08AM +0000, Sudeep Holla wrote:
[...]
> + guard(mutex)(&ffa_version_lock);
> + if (!ffa_version) {
> + invoke(&args, &res);
Can this scenario happen?
1) pKVM is enabled but not yet initialised, and offset-2 AML runs
(e.g. during the initial ACPI scan), so ffh.c gets here first and
negotiates directly with EL3.
2) The result is cached.
3) Once pKVM is up, ffa_version_check() gets the cached version and
never sends FFA_VERSION through the proxy.
If so, then I think kvm_host_ffa_handler() would reject the transport's
calls, since has_version_negotiated is never set.
Should we decline here instead, without caching, while
is_protected_kvm_enabled() && !is_pkvm_initialized(), like ffa_probe()
already does?
^ permalink raw reply [flat|nested] 5+ messages in thread