Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/3] ACPI: arm64: FFH Operation Region support for FF-A (offset 2)
@ 2026-07-29 17:56 Jamie Nguyen
  2026-07-29 17:56 ` [RFC PATCH 1/3] firmware: arm_ffa: Split the response out of ffa_msg_send_direct_req2() Jamie Nguyen
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Jamie Nguyen @ 2026-07-29 17:56 UTC (permalink / raw)
  To: Lorenzo Pieralisi, Hanjun Guo, Sudeep Holla, Catalin Marinas,
	Will Deacon, Rafael J . Wysocki
  Cc: Len Brown, Dat Mach, linux-acpi, linux-arm-kernel, linux-kernel,
	Jamie Nguyen

Arm DEN0048D (Functional Fixed Hardware Specification v1.3), published in
March 2026, adds a third FFH Operation Region flavour:

  https://developer.arm.com/documentation/den0048/latest/

An Operation Region declared with an Offset of 0x2 triggers an
FFA_MSG_SEND_DIRECT_REQ2 call instead of a bare SMC or HVC:

  OperationRegion (AFFH, FFixedHW, 2, 40)
  Field (AFFH, BufferAcc, NoLock, Preserve) { FFAD, 320 }

Each 64-bit field is one register, ordered from X0. X0 carries the call
status on return, X1[15:0] the receiver endpoint ID (or zero, which asks
OSPM to resolve it from the UUID), X2-X3 the service UUID written with
ToUUID(), and X4-X17 the payload. The region Length is 32 + 8 * N bytes
with 1 <= N <= 14, so X0-X4 at minimum and X0-X17 at most.

The spec recommends offset 0x2 for new platforms on the grounds that not
every OSPM implements offsets 0x0 and 0x1. Linux has had both since v6.2
but nothing for 0x2, so AML using the recommended encoding currently gets
AE_ERROR back.

These patches implement it. I could not find any prior posting of this on
linux-acpi or linux-arm-kernel, so apologies if I have missed one and
duplicated someone's work.

All three patches are co-developed with Dat Mach.

Design
------

drivers/acpi/arm64/ffh.c holds the DEN0048D side: region length
validation, the X0-X17 layout, the ToUUID() to FF-A UUID byte order
conversion, and the table 3 status codes. drivers/firmware/arm_ffa/ holds
the FF-A side: resolving a service UUID to an endpoint, and the call
itself, including the FFA_YIELD and FFA_INTERRUPT re-invocation DEN0048D
asks for.

The two talk through an ops structure the FF-A driver registers rather
than a direct call, because ffh.c is built in under a bool Kconfig symbol
while CONFIG_ARM_FFA_TRANSPORT is a tristate. Unregistration takes the
rwsem for writing, so it cannot race with an access already in flight.

None of what the handler needs was reachable through the existing
ffa_device interface. ffa_sync_send_receive2() always addresses
dev->vm_id, so a receiver endpoint ID supplied by AML cannot be honoured.
UUID to endpoint resolution had no in-kernel user at all. And
ffa_msg_send_direct_req2() throws away the response registers DEN0048D
wants copied back to AML. Patch 1 splits those out, leaving what existing
callers see unchanged.

Testing
-------

Built on arm64 with CONFIG_ACPI_FFH=y and CONFIG_ARM_FFA_TRANSPORT both =y
and =m, and with CONFIG_ACPI_FFH=n, W=1 clean. Every patch builds on its
own.

Runtime tested on an Arm server whose firmware reports FF-A 1.3 and picks
offset 2 in its TPM Physical Presence Interface method. Reading
/sys/class/tpm/tpm0/ppi/response drives it; kprobes show the call reaching
ffa_acpi_ffh_direct_req2() with the endpoint and the fourteen payload
registers the region implies, succeeding twenty times over. That method
only returns a response when the status field reads back zero, and it
does, so the status, the copied back registers and the payload are all
landing where the firmware expects them.

That firmware always names the receiver endpoint and always sends a well
formed request, so the rest was driven from test SSDTs loaded at runtime
through CONFIG_ACPI_CONFIGFS, against the same partition. Top level AML in
a dynamically loaded table runs at load, so each table is one invocation
with the register contents under test:

  X1 zero, UUID set      endpoint resolved from the UUID, call succeeds
  X1 zero, UUID nil      rejected before any FF-A call
  X1 an unknown endpoint call attempted rather than refused, and the
                         callee's FFA_ERROR reported as FFH_FFA_CALL_FAILED
                         with the FF-A error code in X2 per table 3
  length 0x28, the min   one payload register, call succeeds
  length 0x24, invalid   rejected before any FF-A call

A second server covers FFH_FFA_NOT_SUPPORTED, since its FF-A 1.1 firmware
cannot do FFA_MSG_SEND_DIRECT_REQ2 at all. It doubles as a regression
platform: it declares no FFH Operation Regions, and an offset 2 access
there returns AE_ERROR on a distro kernel, which is what this series
removes. It also has one service UUID per endpoint, where the first
machine has six sharing one, so both partition topologies are covered.

Registration and teardown were exercised with CONFIG_ARM_FFA_TRANSPORT=m.
Nothing autoloads it, so an access before the module is loaded correctly
reports FFH_FFA_NOT_SUPPORTED; loading makes the same access reach the
FF-A driver, unloading returns it to NOT_SUPPORTED, and reloading reaches
it again.

One path is unexercised: nothing I have provokes a response that is
neither FFA_ERROR nor FFA_MSG_SEND_DIRECT_RESP2, so the -EPROTO mapping is
untested. Neither platform has a UUID that resolves to more than one
endpoint either, so the loop comparing endpoint IDs in
ffa_acpi_ffh_partition_id() was exercised instead with a throwaway stub
that makes a UUID filtered probe report two differing IDs. It returns
-ENOTUNIQ, no FF-A call is attempted, and AML reads back
FFH_FFA_INVALID_PARAMETERS. That case does look reachable rather than
theoretical, since ffa_device_match_uuid() already walks the descriptors a
UUID query returns looking for a matching endpoint ID.

Open question
-------------

1. ffa_msg_send_wait_for_completion() loops on FFA_YIELD and FFA_INTERRUPT
   with no bound, sleeping 1 ms per FFA_YIELD round. The loop predates this
   series and DEN0048D does require the reinvocation, but until now it was
   only reachable from in-kernel FF-A drivers. This series opens it up to
   any control method that writes the Operation Region, including at
   enumeration time, so a partition that keeps yielding would stall an AML
   thread indefinitely.

   Bounding it would affect the existing FF-A callers too, and table 3 has
   no status code for a call that was abandoned, so it may be out of scope
   for this series unless you disagree.

Jamie Nguyen (3):
  firmware: arm_ffa: Split the response out of
    ffa_msg_send_direct_req2()
  ACPI: arm64: Add support for the FF-A FFH Operation Region (offset 2)
  firmware: arm_ffa: Back the ACPI FF-A FFH Operation Region

 drivers/acpi/arm64/ffh.c          | 185 ++++++++++++++++++++++++++++++
 drivers/firmware/arm_ffa/driver.c | 169 +++++++++++++++++++++++++--
 include/linux/acpi.h              |  41 +++++++
 3 files changed, 386 insertions(+), 9 deletions(-)


base-commit: 3652b49adac266a3d27cb41cdfdb7d8790fc3633
-- 
2.43.0



^ permalink raw reply	[flat|nested] 4+ messages in thread

* [RFC PATCH 1/3] firmware: arm_ffa: Split the response out of ffa_msg_send_direct_req2()
  2026-07-29 17:56 [RFC PATCH 0/3] ACPI: arm64: FFH Operation Region support for FF-A (offset 2) Jamie Nguyen
@ 2026-07-29 17:56 ` Jamie Nguyen
  2026-07-29 17:56 ` [RFC PATCH 2/3] ACPI: arm64: Add support for the FF-A FFH Operation Region (offset 2) Jamie Nguyen
  2026-07-29 17:56 ` [RFC PATCH 3/3] firmware: arm_ffa: Back the ACPI FF-A FFH Operation Region Jamie Nguyen
  2 siblings, 0 replies; 4+ messages in thread
From: Jamie Nguyen @ 2026-07-29 17:56 UTC (permalink / raw)
  To: Lorenzo Pieralisi, Hanjun Guo, Sudeep Holla, Catalin Marinas,
	Will Deacon, Rafael J . Wysocki
  Cc: Len Brown, Dat Mach, linux-acpi, linux-arm-kernel, linux-kernel,
	Jamie Nguyen

ffa_msg_send_direct_req2() uses one buffer for both the request and the
response, and it only ever reports a callee side FFA_ERROR as a translated
errno. The ACPI FFH Operation Region handler added later in this series
can work with neither. It has to hand every response register back to AML,
including on the paths where the call failed.

Move the body into __ffa_msg_send_direct_req2(). It writes the response to
a buffer of its own, returns X1-X3 through @resp_regs, and separates three
outcomes: success, a callee that returned FFA_ERROR (-EIO, with the FF-A
error code left in @resp_regs[1]), and anything else (-EPROTO).

ffa_msg_send_direct_req2() sits on top of that as a wrapper. It copies to
the caller's buffer only on success and returns the same errnos it did
before, so ffa_sync_send_receive2() and its users are untouched.

No functional change.

Co-developed-by: Dat Mach <dmach@nvidia.com>
Signed-off-by: Dat Mach <dmach@nvidia.com>
Signed-off-by: Jamie Nguyen <jamien@nvidia.com>
---
 drivers/firmware/arm_ffa/driver.c | 51 ++++++++++++++++++++++++++-----
 1 file changed, 44 insertions(+), 7 deletions(-)

diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c
index 8654b3365c9b6..3236cd0a731ab 100644
--- a/drivers/firmware/arm_ffa/driver.c
+++ b/drivers/firmware/arm_ffa/driver.c
@@ -560,8 +560,20 @@ static int ffa_msg_send2(struct ffa_device *dev, u16 src_id, void *buf, size_t s
 	return retval;
 }
 
-static int ffa_msg_send_direct_req2(u16 src_id, u16 dst_id, const uuid_t *uuid,
-				    struct ffa_send_direct_data2 *data)
+/*
+ * Sends @req and reports the callee's X1-X3 through @resp_regs and its
+ * X4-X17 through @resp, both unconditionally, so that a caller bound by
+ * DEN0048D section 2.3.1.2 can copy every register back to AML even when the
+ * callee returned FFA_ERROR. Keeping the response out of @req leaves the
+ * caller free to decide when, if ever, to overwrite its own buffer. Returns
+ * -EIO for FFA_ERROR (the FF-A error code is left in @resp_regs[1] as X2) and
+ * -EPROTO for an unexpected response; both mean the call was made. Callers
+ * that only need an errno should use ffa_msg_send_direct_req2().
+ */
+static int __ffa_msg_send_direct_req2(u16 src_id, u16 dst_id, const uuid_t *uuid,
+				      const struct ffa_send_direct_data2 *req,
+				      struct ffa_send_direct_data2 *resp,
+				      u64 resp_regs[3])
 {
 	u32 src_dst_ids = PACK_TARGET_INFO(src_id, dst_id);
 	union {
@@ -574,21 +586,46 @@ static int ffa_msg_send_direct_req2(u16 src_id, u16 dst_id, const uuid_t *uuid,
 		.a2 = le64_to_cpu(uuid_regs.regs[0]),
 		.a3 = le64_to_cpu(uuid_regs.regs[1]),
 	};
-	memcpy((void *)&args + offsetof(ffa_value_t, a4), data, sizeof(*data));
+	memcpy((void *)&args + offsetof(ffa_value_t, a4), req, sizeof(*req));
 
 	invoke_ffa_fn(args, &ret);
 
 	ffa_msg_send_wait_for_completion(&ret);
 
+	resp_regs[0] = ret.a1;
+	resp_regs[1] = ret.a2;
+	resp_regs[2] = ret.a3;
+	memcpy(resp, (void *)&ret + offsetof(ffa_value_t, a4), sizeof(*resp));
+
 	if (ret.a0 == FFA_ERROR)
-		return ffa_to_linux_errno((int)ret.a2);
+		return -EIO;
+
+	if (ret.a0 == FFA_MSG_SEND_DIRECT_RESP2)
+		return 0;
+
+	return -EPROTO;
+}
 
-	if (ret.a0 == FFA_MSG_SEND_DIRECT_RESP2) {
-		memcpy(data, (void *)&ret + offsetof(ffa_value_t, a4), sizeof(*data));
+static int ffa_msg_send_direct_req2(u16 src_id, u16 dst_id, const uuid_t *uuid,
+				    struct ffa_send_direct_data2 *data)
+{
+	struct ffa_send_direct_data2 resp;
+	u64 resp_regs[3];
+	int ret;
+
+	ret = __ffa_msg_send_direct_req2(src_id, dst_id, uuid, data, &resp,
+					 resp_regs);
+	if (!ret) {
+		*data = resp;
 		return 0;
 	}
 
-	return -EINVAL;
+	if (ret == -EIO)
+		return ffa_to_linux_errno((int)resp_regs[1]);
+	if (ret == -EPROTO)
+		return -EINVAL;
+
+	return ret;
 }
 
 static int ffa_mem_first_frag(u32 func_id, phys_addr_t buf, u32 buf_sz,
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [RFC PATCH 2/3] ACPI: arm64: Add support for the FF-A FFH Operation Region (offset 2)
  2026-07-29 17:56 [RFC PATCH 0/3] ACPI: arm64: FFH Operation Region support for FF-A (offset 2) Jamie Nguyen
  2026-07-29 17:56 ` [RFC PATCH 1/3] firmware: arm_ffa: Split the response out of ffa_msg_send_direct_req2() Jamie Nguyen
@ 2026-07-29 17:56 ` Jamie Nguyen
  2026-07-29 17:56 ` [RFC PATCH 3/3] firmware: arm_ffa: Back the ACPI FF-A FFH Operation Region Jamie Nguyen
  2 siblings, 0 replies; 4+ messages in thread
From: Jamie Nguyen @ 2026-07-29 17:56 UTC (permalink / raw)
  To: Lorenzo Pieralisi, Hanjun Guo, Sudeep Holla, Catalin Marinas,
	Will Deacon, Rafael J . Wysocki
  Cc: Len Brown, Dat Mach, linux-acpi, linux-arm-kernel, linux-kernel,
	Jamie Nguyen

Arm DEN0048D (Functional Fixed Hardware Specification v1.3, March 2026)
added a third FFH Operation Region flavour. An Operation Region declared
with an Offset of 0x2 triggers an FFA_MSG_SEND_DIRECT_REQ2 call instead of
a bare SMC or HVC, and each 64-bit field of the region is one register,
starting at X0:

  X0		call status, populated by OSPM on return
  X1		Bits[15:0] receiver endpoint ID, or zero to have OSPM
		resolve it from the service UUID
  X2-X3		service UUID of the callee partition, written by ACPI
		platform firmware using ToUUID()
  X4-X17	message payload, X4 is always present

DEN0048D recommends Offset 0x2 for new platforms, since not every OSPM
implements Offsets 0x0 and 0x1. Linux implements both and rejects anything
else, so AML using the recommended encoding simply fails today.

Add the Operation Region side of it. Region length validation, the X0-X17
layout, the ToUUID() to FF-A UUID byte order conversion and the DEN0048D
table 3 status codes all live here; they are ACPI semantics, not FF-A
ones. The call itself goes out through a small set of ops that the FF-A
driver registers. That indirection is needed because this file is built
in, while CONFIG_ARM_FFA_TRANSPORT is a tristate.

Anything that stops the call being made comes back to AML in the X0 status
field: a malformed region length, or a null service UUID when AML has
asked OSPM to find the endpoint itself. Once the call has gone out every
register is copied back, whatever the outcome, which is what DEN0048D
requires. That also takes care of table 3, because FFA_ERROR reports the
FF-A error code in X2 and FFH_FFA_CALL_FAILED wants it in the same place.
A response that is neither FFA_ERROR nor FFA_MSG_SEND_DIRECT_RESP2 is
handled the same way; the call did complete.

Co-developed-by: Dat Mach <dmach@nvidia.com>
Signed-off-by: Dat Mach <dmach@nvidia.com>
Signed-off-by: Jamie Nguyen <jamien@nvidia.com>
---
 drivers/acpi/arm64/ffh.c | 185 +++++++++++++++++++++++++++++++++++++++
 include/linux/acpi.h     |  41 +++++++++
 2 files changed, 226 insertions(+)

diff --git a/drivers/acpi/arm64/ffh.c b/drivers/acpi/arm64/ffh.c
index 04380bab193df..9f8a508cf2d4a 100644
--- a/drivers/acpi/arm64/ffh.c
+++ b/drivers/acpi/arm64/ffh.c
@@ -1,7 +1,9 @@
 // SPDX-License-Identifier: GPL-2.0-only
 #include <linux/acpi.h>
 #include <linux/arm-smccc.h>
+#include <linux/rwsem.h>
 #include <linux/slab.h>
+#include <linux/uuid.h>
 
 /*
  * Implements ARM64 specific callbacks to support ACPI FFH Operation Region as
@@ -62,6 +64,183 @@ static bool acpi_ffh_smccc_owner_allowed(u32 fid)
 	return false;
 }
 
+/*
+ * FFH Operation Regions declared with an Offset of 0x2 trigger an
+ * FFA_MSG_SEND_DIRECT_REQ2 call, as described in Arm DEN0048D (Functional
+ * Fixed Hardware Specification v1.3) section 2.3.1.2. Every 64-bit field of
+ * the region maps to one register, ordered from X0:
+ *
+ *   X0		status, one of the ACPI_FFH_FFA_* codes below, populated by
+ *		OSPM on return
+ *   X1		Bits[15:0] hold the receiver endpoint ID, or zero to have OSPM
+ *		resolve it from the service UUID
+ *   X2-X3	service UUID of the callee partition, written by ACPI platform
+ *		firmware with the ToUUID() ASL operator
+ *   X4-X17	message payload, X4 is always present
+ *
+ * The region Length is "32 + 8 * N" bytes with 1 <= N <= 14, which is X0-X4
+ * at minimum and X0-X17 at most.
+ */
+#define ACPI_FFH_FFA_HDR_REGS		4	/* X0 - X3 */
+#define ACPI_FFH_FFA_MAX_PAYLOAD_REGS	14	/* X4 - X17 */
+#define ACPI_FFH_FFA_UUID_OFFSET	(2 * sizeof(u64))
+#define ACPI_FFH_FFA_MIN_LENGTH		((ACPI_FFH_FFA_HDR_REGS + 1) * sizeof(u64))
+#define ACPI_FFH_FFA_MAX_LENGTH		\
+	((ACPI_FFH_FFA_HDR_REGS + ACPI_FFH_FFA_MAX_PAYLOAD_REGS) * sizeof(u64))
+
+/* DEN0048D table 3, FFH Operation Region status codes for FFA calls */
+#define ACPI_FFH_FFA_CALL_FAILED		1
+#define ACPI_FFH_FFA_SUCCESS			0
+#define ACPI_FFH_FFA_NOT_SUPPORTED		(-1)
+#define ACPI_FFH_FFA_INVALID_PARAMETERS		(-2)
+#define ACPI_FFH_FFA_OUT_OF_MEMORY		(-3)
+#define ACPI_FFH_FFA_UNSPECIFIED_ERROR		(-4)
+
+static const struct acpi_ffh_ffa_ops *ffa_ops;
+static DECLARE_RWSEM(ffa_ops_sem);
+
+int acpi_ffh_ffa_register(const struct acpi_ffh_ffa_ops *ops)
+{
+	int ret = 0;
+
+	if (!ops || !ops->partition_id || !ops->direct_req2)
+		return -EINVAL;
+
+	down_write(&ffa_ops_sem);
+	if (ffa_ops)
+		ret = -EBUSY;
+	else
+		ffa_ops = ops;
+	up_write(&ffa_ops_sem);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(acpi_ffh_ffa_register);
+
+void acpi_ffh_ffa_unregister(const struct acpi_ffh_ffa_ops *ops)
+{
+	down_write(&ffa_ops_sem);
+	if (ffa_ops == ops)
+		ffa_ops = NULL;
+	up_write(&ffa_ops_sem);
+}
+EXPORT_SYMBOL_GPL(acpi_ffh_ffa_unregister);
+
+/*
+ * ToUUID() emits the UUID in mixed-endian (EFI GUID) byte order whereas FF-A
+ * expects the RFC4122 layout, which is exactly a guid_t to uuid_t conversion.
+ */
+static void acpi_ffh_ffa_uuid(uuid_t *uuid, const u8 *aml_buf)
+{
+	int i;
+
+	for (i = 0; i < UUID_SIZE; i++)
+		uuid->b[i] = aml_buf[guid_index[i]];
+}
+
+static int acpi_ffh_ffa_status(int err)
+{
+	switch (err) {
+	case 0:
+		return ACPI_FFH_FFA_SUCCESS;
+	case -EIO:
+	case -EPROTO:
+		return ACPI_FFH_FFA_CALL_FAILED;
+	case -EOPNOTSUPP:
+		return ACPI_FFH_FFA_NOT_SUPPORTED;
+	case -EINVAL:
+	case -ENOENT:
+	case -ENODEV:
+	case -ENOTUNIQ:
+		return ACPI_FFH_FFA_INVALID_PARAMETERS;
+	case -ENOMEM:
+		return ACPI_FFH_FFA_OUT_OF_MEMORY;
+	default:
+		return ACPI_FFH_FFA_UNSPECIFIED_ERROR;
+	}
+}
+
+static bool acpi_ffh_ffa_length_valid(u64 length)
+{
+	return length >= ACPI_FFH_FFA_MIN_LENGTH &&
+	       length <= ACPI_FFH_FFA_MAX_LENGTH &&
+	       !(length % sizeof(u64));
+}
+
+static void acpi_ffh_ffa_handler(struct acpi_ffh_info *info, void *value)
+{
+	int status = ACPI_FFH_FFA_INVALID_PARAMETERS;
+	u64 resp_regs[3] = {};
+	unsigned int nr_payload;
+	u64 *regs = value;
+	uuid_t uuid;
+	u16 dst_id;
+	int ret;
+
+	if (!acpi_ffh_ffa_length_valid(info->length))
+		goto out;
+
+	nr_payload = info->length / sizeof(u64) - ACPI_FFH_FFA_HDR_REGS;
+
+	acpi_ffh_ffa_uuid(&uuid, (u8 *)value + ACPI_FFH_FFA_UUID_OFFSET);
+
+	down_read(&ffa_ops_sem);
+	if (!ffa_ops) {
+		status = ACPI_FFH_FFA_NOT_SUPPORTED;
+		goto out_unlock;
+	}
+
+	/*
+	 * A zero receiver endpoint ID means ACPI platform firmware expects
+	 * OSPM to derive it from the service UUID.
+	 */
+	dst_id = regs[1] & GENMASK(15, 0);
+	if (!dst_id) {
+		/*
+		 * A null UUID means "every partition" to
+		 * FFA_PARTITION_INFO_GET, so reject it here rather than let a
+		 * bare read of the Operation Region, which arrives as a zeroed
+		 * buffer, resolve to an arbitrary endpoint.
+		 */
+		if (uuid_is_null(&uuid))
+			goto out_unlock;
+
+		ret = ffa_ops->partition_id(&uuid, &dst_id);
+		if (ret) {
+			status = acpi_ffh_ffa_status(ret);
+			goto out_unlock;
+		}
+	}
+
+	ret = ffa_ops->direct_req2(dst_id, &uuid, regs + ACPI_FFH_FFA_HDR_REGS,
+				   nr_payload, resp_regs);
+	status = acpi_ffh_ffa_status(ret);
+
+	/*
+	 * DEN0048D asks for the response registers to be copied back. That also
+	 * takes care of table 3: FFA_ERROR reports the FF-A error code in X2,
+	 * which is exactly where FFH_FFA_CALL_FAILED wants it. -EIO and -EPROTO
+	 * both mean the call completed, so the registers hold the callee's
+	 * response and not AML's own request.
+	 */
+	if (!ret || ret == -EIO || ret == -EPROTO) {
+		regs[1] = resp_regs[0];
+		regs[2] = resp_regs[1];
+		regs[3] = resp_regs[2];
+	}
+
+out_unlock:
+	up_read(&ffa_ops_sem);
+out:
+	/*
+	 * DEN0048D describes this field as 64 bits wide and gives the table 3
+	 * codes as signed values, so sign extend rather than write a narrower
+	 * quantity. 0xfffe or 0xfffffffe would read back as a positive number
+	 * in a 64-bit AML comparison.
+	 */
+	regs[0] = (u64)(s64)status;
+}
+
 int acpi_ffh_address_space_arch_handler(acpi_integer *value, void *region_context)
 {
 	int ret = 0;
@@ -99,6 +278,12 @@ 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) {
+		/* FFA_MSG_SEND_DIRECT_REQ2 call */
+		if (ffh_ctxt->info.length < sizeof(u64))
+			ret = AE_ERROR;
+		else
+			acpi_ffh_ffa_handler(&ffh_ctxt->info, value);
 	} else {
 		ret = AE_ERROR;
 	}
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index ddacac8120947..9f219a75cffe6 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -1649,6 +1649,35 @@ void acpi_init_pcc(void);
 static inline void acpi_init_pcc(void) { }
 #endif
 
+/**
+ * struct acpi_ffh_ffa_ops - FF-A backend for the FFH Operation Region
+ * @partition_id: Resolve @uuid to a unique endpoint ID. Must fail with a
+ *		  negative errno if the UUID resolves to no endpoint or to
+ *		  more than one distinct endpoint.
+ * @direct_req2:  Issue FFA_MSG_SEND_DIRECT_REQ2 towards @dst_id using @uuid
+ *		  as the service UUID. @payload holds the X4-X17 registers
+ *		  taken from the Operation Region and is updated in place
+ *		  with the response payload; @nr_payload is the number of
+ *		  registers in use (1 to 14). @resp_regs is filled with the
+ *		  X1-X3 registers returned by the callee, and @payload with
+ *		  the response, whenever the call was actually made. Returns
+ *		  0 on success, -EIO if the callee returned FFA_ERROR,
+ *		  -EPROTO if it returned something unexpected, or another
+ *		  negative errno if the call could not be made at all. -EIO
+ *		  and -EPROTO both mean the registers hold the callee's
+ *		  response and must be copied back to AML.
+ *
+ * Backend for FFH Operation Regions declared with an Offset of 0x2, as
+ * described in Arm DEN0048D (Functional Fixed Hardware Specification v1.3)
+ * section 2.3.1.2. Registered by the FF-A driver, which may be a module,
+ * while the Operation Region handler itself is built in.
+ */
+struct acpi_ffh_ffa_ops {
+	int (*partition_id)(const uuid_t *uuid, u16 *dst_id);
+	int (*direct_req2)(u16 dst_id, const uuid_t *uuid, u64 *payload,
+			   unsigned int nr_payload, u64 resp_regs[3]);
+};
+
 #ifdef CONFIG_ACPI_FFH
 void acpi_init_ffh(void);
 extern int acpi_ffh_address_space_arch_setup(void *handler_ctxt,
@@ -1659,6 +1688,18 @@ extern int acpi_ffh_address_space_arch_handler(acpi_integer *value,
 static inline void acpi_init_ffh(void) { }
 #endif
 
+#if defined(CONFIG_ACPI_FFH) && defined(CONFIG_ARM64)
+int acpi_ffh_ffa_register(const struct acpi_ffh_ffa_ops *ops);
+void acpi_ffh_ffa_unregister(const struct acpi_ffh_ffa_ops *ops);
+#else
+static inline int acpi_ffh_ffa_register(const struct acpi_ffh_ffa_ops *ops)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void acpi_ffh_ffa_unregister(const struct acpi_ffh_ffa_ops *ops) { }
+#endif
+
 #ifdef CONFIG_ACPI
 extern void acpi_device_notify(struct device *dev);
 extern void acpi_device_notify_remove(struct device *dev);
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [RFC PATCH 3/3] firmware: arm_ffa: Back the ACPI FF-A FFH Operation Region
  2026-07-29 17:56 [RFC PATCH 0/3] ACPI: arm64: FFH Operation Region support for FF-A (offset 2) Jamie Nguyen
  2026-07-29 17:56 ` [RFC PATCH 1/3] firmware: arm_ffa: Split the response out of ffa_msg_send_direct_req2() Jamie Nguyen
  2026-07-29 17:56 ` [RFC PATCH 2/3] ACPI: arm64: Add support for the FF-A FFH Operation Region (offset 2) Jamie Nguyen
@ 2026-07-29 17:56 ` Jamie Nguyen
  2 siblings, 0 replies; 4+ messages in thread
From: Jamie Nguyen @ 2026-07-29 17:56 UTC (permalink / raw)
  To: Lorenzo Pieralisi, Hanjun Guo, Sudeep Holla, Catalin Marinas,
	Will Deacon, Rafael J . Wysocki
  Cc: Len Brown, Dat Mach, linux-acpi, linux-arm-kernel, linux-kernel,
	Jamie Nguyen

Add the FFA_MSG_SEND_DIRECT_REQ2 backend behind FFH Operation Regions
declared with an Offset of 0x2, as described in Arm DEN0048D section
2.3.1.2, and register it once partition setup has completed.

None of what the Operation Region handler needs was reachable through the
existing ffa_device based interface. Resolving a service UUID to an
endpoint ID had no user yet. ffa_sync_send_receive2() always addresses
dev->vm_id, so a receiver endpoint ID that AML supplied in X1 cannot be
honoured. And the response registers DEN0048D wants copied back to AML are
discarded by ffa_msg_send_direct_req2(), which folds the FF-A error code
into an errno instead; patch 1 dealt with that one.

DEN0048D only requires the resolved endpoint to be unique, and a single
partition may be described by more than one entry, so compare endpoint IDs
instead of insisting on exactly one descriptor.

Where the endpoint was enumerated at probe time, check first that it can
receive direct request2 messages. AML then gets FFH_FFA_NOT_SUPPORTED
instead of a call that was never going to work. An endpoint with no
matching device is passed through and left for the callee to reject.

Co-developed-by: Dat Mach <dmach@nvidia.com>
Signed-off-by: Dat Mach <dmach@nvidia.com>
Signed-off-by: Jamie Nguyen <jamien@nvidia.com>
---
 drivers/firmware/arm_ffa/driver.c | 118 +++++++++++++++++++++++++++++-
 1 file changed, 116 insertions(+), 2 deletions(-)

diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c
index 3236cd0a731ab..0577c7d5685ee 100644
--- a/drivers/firmware/arm_ffa/driver.c
+++ b/drivers/firmware/arm_ffa/driver.c
@@ -2146,6 +2146,114 @@ static void ffa_notifications_setup(void)
 	ffa_notifications_cleanup();
 }
 
+/*
+ * Backend for FFH Operation Regions declared with an Offset of 0x2, see Arm
+ * DEN0048D (Functional Fixed Hardware Specification v1.3) section 2.3.1.2.
+ * The Operation Region handler itself lives in drivers/acpi/arm64/ffh.c and
+ * is built in, so it reaches this driver, which may be a module, through the
+ * ops registered below.
+ */
+static int ffa_acpi_ffh_partition_id(const uuid_t *uuid, u16 *dst_id)
+{
+	struct ffa_partition_info *pbuf;
+	int count, idx, ret = 0;
+	u16 id;
+
+	count = ffa_partition_probe(uuid, &pbuf);
+	if (count <= 0)
+		return count ? : -ENOENT;
+
+	/*
+	 * DEN0048D only requires the endpoint ID to be unique. A partition may
+	 * be described by more than one entry, so compare IDs rather than
+	 * insisting on a single descriptor.
+	 */
+	id = pbuf[0].id;
+	for (idx = 1; idx < count; idx++) {
+		if (pbuf[idx].id != id) {
+			ret = -ENOTUNIQ;
+			goto out;
+		}
+	}
+
+	*dst_id = id;
+out:
+	kfree(pbuf);
+	return ret;
+}
+
+static int ffa_acpi_ffh_match_id(struct device *dev, const void *data)
+{
+	const u16 *dst_id = data;
+
+	return to_ffa_dev(dev)->vm_id == *dst_id;
+}
+
+static int ffa_acpi_ffh_check_partition(u16 dst_id)
+{
+	struct device *dev;
+	bool supported;
+
+	dev = bus_find_device(&ffa_bus_type, NULL, &dst_id,
+			      ffa_acpi_ffh_match_id);
+	/*
+	 * Only partitions reported by FFA_PARTITION_INFO_GET at probe time have
+	 * a device here. AML may name an endpoint that was never enumerated, so
+	 * an unknown one is left to the callee to reject rather than refused
+	 * outright.
+	 */
+	if (!dev)
+		return 0;
+
+	supported = ffa_partition_supports_direct_req2_recv(to_ffa_dev(dev));
+	put_device(dev);
+
+	return supported ? 0 : -EOPNOTSUPP;
+}
+
+static int ffa_acpi_ffh_direct_req2(u16 dst_id, const uuid_t *uuid,
+				    u64 *payload, unsigned int nr_payload,
+				    u64 resp_regs[3])
+{
+	struct ffa_send_direct_data2 req = {}, resp;
+	int ret;
+
+	BUILD_BUG_ON(sizeof(req.data[0]) != sizeof(*payload));
+
+	if (!drv_info->msg_direct_req2_supp)
+		return -EOPNOTSUPP;
+
+	if (!nr_payload || nr_payload > ARRAY_SIZE(req.data))
+		return -EINVAL;
+
+	ret = ffa_acpi_ffh_check_partition(dst_id);
+	if (ret)
+		return ret;
+
+	/*
+	 * Registers not represented in the Operation Region stay zero, as
+	 * required by DEN0048D.
+	 */
+	memcpy(req.data, payload, nr_payload * sizeof(*payload));
+
+	ret = __ffa_msg_send_direct_req2(drv_info->vm_id, dst_id, uuid, &req,
+					 &resp, resp_regs);
+
+	/*
+	 * DEN0048D asks for the registers to be copied back once the call has
+	 * completed, whatever the outcome, so this runs on the FFA_ERROR path
+	 * too rather than leaving AML looking at its own request.
+	 */
+	memcpy(payload, resp.data, nr_payload * sizeof(*payload));
+
+	return ret;
+}
+
+static const struct acpi_ffh_ffa_ops ffa_acpi_ffh_ops = {
+	.partition_id	= ffa_acpi_ffh_partition_id,
+	.direct_req2	= ffa_acpi_ffh_direct_req2,
+};
+
 static int ffa_probe(struct platform_device *pdev)
 {
 	int ret;
@@ -2228,8 +2336,13 @@ static int ffa_probe(struct platform_device *pdev)
 	ffa_notifications_setup();
 
 	ret = ffa_setup_partitions();
-	if (!ret)
-		return ret;
+	if (!ret) {
+		ret = acpi_ffh_ffa_register(&ffa_acpi_ffh_ops);
+		if (ret && ret != -EOPNOTSUPP)
+			pr_warn("failed to register ACPI FFH backend (%d)\n",
+				ret);
+		return 0;
+	}
 
 	pr_err("failed to setup partitions\n");
 	ffa_notifications_cleanup();
@@ -2249,6 +2362,7 @@ static void ffa_remove(struct platform_device *pdev)
 {
 	struct ffa_drv_info *info = platform_get_drvdata(pdev);
 
+	acpi_ffh_ffa_unregister(&ffa_acpi_ffh_ops);
 	ffa_notifications_cleanup();
 	ffa_partitions_cleanup();
 	ffa_rxtx_unmap();
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-07-29 17:58 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29 17:56 [RFC PATCH 0/3] ACPI: arm64: FFH Operation Region support for FF-A (offset 2) Jamie Nguyen
2026-07-29 17:56 ` [RFC PATCH 1/3] firmware: arm_ffa: Split the response out of ffa_msg_send_direct_req2() Jamie Nguyen
2026-07-29 17:56 ` [RFC PATCH 2/3] ACPI: arm64: Add support for the FF-A FFH Operation Region (offset 2) Jamie Nguyen
2026-07-29 17:56 ` [RFC PATCH 3/3] firmware: arm_ffa: Back the ACPI FF-A FFH Operation Region Jamie Nguyen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox