Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/8] Arm Live Firmware Activation (LFA) support
@ 2026-07-06 13:44 Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 1/8] dt-bindings: arm: Add Live Firmware Activation Andre Przywara
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Andre Przywara @ 2026-07-06 13:44 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Conor Dooley, vsethi, Salman Nabi, Rob Herring, linux-kernel,
	Varun Wadekar, Trilok Soni, devicetree, Nirmoy Das,
	Krzysztof Kozlowski, linux-arm-kernel

Hi all,

this is version 3 of the Live Firmware Activation kernel support. The
device part of the code now relies on Aneesh's SMCCC bus patches[1],
which solves some problems rather nicely. I also added the formerly
missing sysfs ABI documentation. Based on v7.2-rc1, plus the first
two patches from Aneesh's series. More detailed changelog below.
==============================

This series implements the kernel side support of the Arm Live
Firmware Activation (LFA) specification [2]. LFA enables the activation
of updated firmware components without requiring a system reboot,
reducing downtime and allowing quicker deployment of critical bug fixes
in environments such as data centers and hyperscale systems.
It requires explicit firmware support, both via an agent running in EL3
(for instance in TF-A, already merged), but also in the firmware
component to be activated. TF-RMM recently merged support for this.

Unlike the usual firmware update process (which may use tools like
fwupd), LFA focuses solely on the activation of an already updated
firmware component, called "pending activation" in LFA lingo. This works
by signalling the LFA agent (part of the EL3 runtime firmware) via an
SMC call, which then does the heavy lifting of the live update, in
cooperation with the to-be-updated firmware component.

Key features of the driver:
* Detects LFA support in system firmware (EL3).
* Lists all firmware components that support live activation, identified
  by their GUID.
* Exposes component attributes (e.g., activation capability, and
  activation pending) via sysfs under /sys/firmware/lfa/<GUID>/.
* Provides interfaces to:
  - Trigger activation of an updated firmware component.
  - Cancel an ongoing activation if required.
A more detailed list of features can be found in patch 2/8.
Based on v7.2-rc1.

This work is conceptually similar to Intel’s Platform Firmware Runtime
Update and telemetry (PFRUT) [3] and TDX module updates [4], but
targets Arm platforms. The driver has been used to successfully activate
a Realm Management Monitor (RMM) firmware image in a controlled test
environment. RMM is analogous to Intel’s TDX module.

There is effort on similar work from the OCP [5]. Future work may
include integration with utilities like fwupd to automatically select
the appropriate driver, based on platform architecture, for Live/Runtime
firmware updates.

Please have a look, test and comment!

Best regards,
Salman and Andre

Changes in v3:
- change node name in DT binding example
- move SMCCC function identifiers into generic SMCCC header
- rework to use the new SMCCC bus instead of a using a faux device
- add missing workqueue cleanup in probe() routine
- squash former patch v2 3/8 into base patch
- use get_image_name() more consistently
- also consider -LFA_BUSY return in activate call

Changes in v2:
- restrict build to arm64 (the LFA spec only supports AArch64)
- rename and extend central data structure to fw_image
- use separate GPR register sets for some SMC calls
- provide wrapper for error messages to prevent out-of-bound access
- return GUID in the "name" sysfs file when image is unknown
- fix wrong attribute in pending version number show function
- add missing include files and order them properly
- fix memory leaks in error cleanup paths
- handle lifetime using embedded kobjects and a kset
- drop global lfa_lock, use kset list lock and kobject refcount instead
- add DT binding documentation
- add timeout and watchdog re-arming (contributed by Veda)
- relax timeout period and do not block while waiting
- register ACPI notification (contributed by Veda) and DT interrupt 
- refactor ACPI notification code to allow sharing with DT code
- use faux device instead of platform driver
- add auto_activate file to control automatic activation
- introduce rwsem mutex to prevent using stale sequence ID
- use labels and goto instead of infinite loop when retrying activation
- initialise workqueue only once (thanks to Nirmoy)
- various cleanups on reported messages and code formatting
- rebase on top of v7.0-rc1

Changes in v1:
- Updated SMCCC version 1.1 to 1.2 per the LFA specification requirement.
- Changed "image_props" array to a linked list to support the dynamic
  removal and addition of firmware images.
- Added code to refresh firmware images following a successful activation.
- Added a work_queue to handle the removal of firmware image attribute
  from it's respective kobject "_store" handle.
- Refactored prime and activate into separate functions.
- Kernel config for LFA now defaults to "y" i.e. included by default.
- Added individual kernel attribute files removal when removing the
  respective kobjects using kobject_put().
- mutex_lock added to activate_fw_image() and prime_fw_image() calls.
- Renamed create_fw_inventory to update_fw_image_node.
- Renamed create_fw_images_tree to update_fw_images_tree.
- Added two more attributes due to specs update from bet0 to bet1:
  current_version: For retrieval of the current firmware's version info.
  pending_version: For retrieval of the pending firmware's version info.
- Minor changes such as, improved firmware image names, and code comments.
- do...while loops refactored to for(;;) loops.

[1] https://lore.kernel.org/linux-arm-kernel/20260611130429.295516-1-aneesh.kumar@kernel.org/
[2] https://developer.arm.com/documentation/den0147/latest/
[3] https://lore.kernel.org/all/cover.1631025237.git.yu.c.chen@intel.com/
[4] https://lore.kernel.org/all/20250523095322.88774-1-chao.gao@intel.com/
[5] https://www.opencompute.org/documents/hyperscale-cpu-impactless-firmware-updates-requirements-specification-v0-7-9-29-2025-pdf

Andre Przywara (5):
  dt-bindings: arm: Add Live Firmware Activation
  firmware: smccc: lfa: Add auto_activate sysfs file
  firmware: smccc: lfa: Register DT interrupt
  firmware: smccc: lfa: introduce SMC access lock
  firmware: smccc: lfa: add sysfs ABI documentation

Salman Nabi (1):
  firmware: smccc: Add support for Live Firmware Activation (LFA)

Vedashree Vidwans (2):
  firmware: smccc: lfa: Add timeout and trigger watchdog
  firmware: smccc: lfa: Register ACPI notification

 Documentation/ABI/testing/sysfs-firmware-lfa  | 106 ++
 .../devicetree/bindings/arm/arm,lfa.yaml      |  45 +
 drivers/firmware/smccc/Kconfig                |  10 +
 drivers/firmware/smccc/Makefile               |   1 +
 drivers/firmware/smccc/lfa_fw.c               | 988 ++++++++++++++++++
 drivers/firmware/smccc/smccc.c                |   5 +
 include/linux/arm-smccc.h                     |  15 +
 7 files changed, 1170 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-firmware-lfa
 create mode 100644 Documentation/devicetree/bindings/arm/arm,lfa.yaml
 create mode 100644 drivers/firmware/smccc/lfa_fw.c


base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
prerequisite-patch-id: 64fa2209c9eee2084861ba13f0bdfdac312b19b0
prerequisite-patch-id: fe1ca1a9aeb94623c79bf66366c76bfb8645b581
-- 
2.43.0



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

* [PATCH v3 1/8] dt-bindings: arm: Add Live Firmware Activation
  2026-07-06 13:44 [PATCH v3 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
@ 2026-07-06 13:44 ` Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 2/8] firmware: smccc: Add support for Live Firmware Activation (LFA) Andre Przywara
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Andre Przywara @ 2026-07-06 13:44 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Conor Dooley, vsethi, Salman Nabi, Rob Herring, linux-kernel,
	Varun Wadekar, Trilok Soni, devicetree, Nirmoy Das,
	Krzysztof Kozlowski, linux-arm-kernel

The Arm Live Firmware Activation spec [1] describes updating firmware
images during runtime, without requiring a reboot. Update images might
be deployed out-of-band, for instance via a BMC, in this case the OS
needs to be notified about the availability of a new image.

Describe an interrupt that could be triggered by the platform, to notify
about any changes.

[1] https://developer.arm.com/documentation/den0147/latest/

Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
 .../devicetree/bindings/arm/arm,lfa.yaml      | 45 +++++++++++++++++++
 1 file changed, 45 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/arm,lfa.yaml

diff --git a/Documentation/devicetree/bindings/arm/arm,lfa.yaml b/Documentation/devicetree/bindings/arm/arm,lfa.yaml
new file mode 100644
index 000000000000..ef8e250f2da7
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/arm,lfa.yaml
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/arm,lfa.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Arm Live Firmware Activation (LFA)
+
+maintainers:
+  - Andre Przywara <andre.przywara@arm.com>
+  - Sudeep Holla <sudeep.holla@arm.com>
+
+description:
+  The Arm Live Firmware Activation (LFA) specification [1] describes a
+  firmware interface to activate an updated firmware at runtime, without
+  requiring a reboot. Updates might be supplied out-of-band, for instance
+  via a BMC, in which case the platform needs to notify an OS about pending
+  image updates.
+  [1] https://developer.arm.com/documentation/den0147/latest/
+
+properties:
+  compatible:
+    const: arm,lfa
+
+  interrupts:
+    maxItems: 1
+    description: notification interrupt for changed firmware image status
+
+required:
+  - compatible
+  - interrupts
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    firmware {
+        firmware-update {
+            compatible = "arm,lfa";
+            interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
+        };
+    };
+...
-- 
2.43.0



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

* [PATCH v3 2/8] firmware: smccc: Add support for Live Firmware Activation (LFA)
  2026-07-06 13:44 [PATCH v3 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 1/8] dt-bindings: arm: Add Live Firmware Activation Andre Przywara
@ 2026-07-06 13:44 ` Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 3/8] firmware: smccc: lfa: Add timeout and trigger watchdog Andre Przywara
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Andre Przywara @ 2026-07-06 13:44 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Conor Dooley, vsethi, Salman Nabi, Rob Herring, linux-kernel,
	Varun Wadekar, Trilok Soni, devicetree, Nirmoy Das,
	Krzysztof Kozlowski, linux-arm-kernel

From: Salman Nabi <salman.nabi@arm.com>

The Arm Live Firmware Activation (LFA) is a specification [1] to describe
activating firmware components without a reboot. Those components
(like TF-A's BL31, EDK-II, TF-RMM, secure paylods) would be updated the
usual way: via fwupd, FF-A or other secure storage methods, or via some
IMPDEF Out-Of-Bound method. The user can then activate this new firmware,
at system runtime, without requiring a reboot.
The specification covers the SMCCC interface to list and query available
components and eventually trigger the activation.

Add a new directory under /sys/firmware to present firmware components
capable of live activation. Each of them is a directory under lfa/,
and is identified via its GUID. The activation will be triggered by echoing
"1" into the "activate" file:
==========================================
/sys/firmware/lfa # ls -l . 6c*
.:
total 0
drwxr-xr-x    2 0 0         0 Jan 19 11:33 47d4086d-4cfe-9846-9b95-2950cbbd5a00
drwxr-xr-x    2 0 0         0 Jan 19 11:33 6c0762a6-12f2-4b56-92cb-ba8f633606d9
drwxr-xr-x    2 0 0         0 Jan 19 11:33 d6d0eea7-fcea-d54b-9782-9934f234b6e4

6c0762a6-12f2-4b56-92cb-ba8f633606d9:
total 0
--w-------    1 0        0             4096 Jan 19 11:33 activate
-r--r--r--    1 0        0             4096 Jan 19 11:33 activation_capable
-r--r--r--    1 0        0             4096 Jan 19 11:33 activation_pending
--w-------    1 0        0             4096 Jan 19 11:33 cancel
-r--r--r--    1 0        0             4096 Jan 19 11:33 cpu_rendezvous
-r--r--r--    1 0        0             4096 Jan 19 11:33 current_version
-rw-r--r--    1 0        0             4096 Jan 19 11:33 force_cpu_rendezvous
-r--r--r--    1 0        0             4096 Jan 19 11:33 may_reset_cpu
-r--r--r--    1 0        0             4096 Jan 19 11:33 name
-r--r--r--    1 0        0             4096 Jan 19 11:33 pending_version
/sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # grep . *
grep: activate: Permission denied
activation_capable:1
activation_pending:1
grep: cancel: Permission denied
cpu_rendezvous:1
current_version:0.0
force_cpu_rendezvous:1
may_reset_cpu:0
name:TF-RMM
pending_version:0.0
/sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # echo 1 > activate
[ 2825.797871] Arm LFA: firmware activation succeeded.
/sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 #
==========================================

[1] https://developer.arm.com/documentation/den0147/latest/

Signed-off-by: Salman Nabi <salman.nabi@arm.com>
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
 drivers/firmware/smccc/Kconfig  |  10 +
 drivers/firmware/smccc/Makefile |   1 +
 drivers/firmware/smccc/lfa_fw.c | 725 ++++++++++++++++++++++++++++++++
 drivers/firmware/smccc/smccc.c  |   5 +
 include/linux/arm-smccc.h       |  15 +
 5 files changed, 756 insertions(+)
 create mode 100644 drivers/firmware/smccc/lfa_fw.c

diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig
index 15e7466179a6..7fd646d515f8 100644
--- a/drivers/firmware/smccc/Kconfig
+++ b/drivers/firmware/smccc/Kconfig
@@ -23,3 +23,13 @@ config ARM_SMCCC_SOC_ID
 	help
 	  Include support for the SoC bus on the ARM SMCCC firmware based
 	  platforms providing some sysfs information about the SoC variant.
+
+config ARM_LFA
+	tristate "Arm Live Firmware activation support"
+	depends on HAVE_ARM_SMCCC_DISCOVERY && ARM64
+	default y
+	help
+	  Include support for triggering a Live Firmware Activation (LFA),
+	  which allows to upgrade certain firmware components without a reboot.
+	  This is described in the Arm DEN0147 specification, and relies on
+	  a firmware agent running in EL3.
diff --git a/drivers/firmware/smccc/Makefile b/drivers/firmware/smccc/Makefile
index 68bbff1407b8..cddf2c460ab9 100644
--- a/drivers/firmware/smccc/Makefile
+++ b/drivers/firmware/smccc/Makefile
@@ -2,3 +2,4 @@
 #
 obj-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY)	+= bus.o smccc.o kvm_guest.o
 obj-$(CONFIG_ARM_SMCCC_SOC_ID)	+= soc_id.o
+obj-$(CONFIG_ARM_LFA) += lfa_fw.o
diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
new file mode 100644
index 000000000000..b333b1e28c0d
--- /dev/null
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -0,0 +1,725 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Arm Limited
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/arm-smccc-bus.h>
+#include <linux/array_size.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kobject.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/psci.h>
+#include <linux/stop_machine.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/uuid.h>
+#include <linux/workqueue.h>
+
+#include <uapi/linux/psci.h>
+
+#undef pr_fmt
+#define pr_fmt(fmt) "Arm LFA: " fmt
+
+/* CALL_AGAIN flags (returned by SMC) */
+#define LFA_PRIME_CALL_AGAIN		BIT(0)
+#define LFA_ACTIVATE_CALL_AGAIN		BIT(0)
+
+/* LFA return values */
+#define LFA_SUCCESS			0
+#define LFA_NOT_SUPPORTED		1
+#define LFA_BUSY			2
+#define LFA_AUTH_ERROR			3
+#define LFA_NO_MEMORY			4
+#define LFA_CRITICAL_ERROR		5
+#define LFA_DEVICE_ERROR		6
+#define LFA_WRONG_STATE			7
+#define LFA_INVALID_PARAMETERS		8
+#define LFA_COMPONENT_WRONG_STATE	9
+#define LFA_INVALID_ADDRESS		10
+#define LFA_ACTIVATION_FAILED		11
+
+/*
+ * Not error codes described by the spec, but used internally when
+ * PRIME/ACTIVATE calls return with the CALL_AGAIN bit set.
+ */
+#define LFA_TIMED_OUT			32
+#define LFA_CALL_AGAIN			33
+
+#define LFA_ERROR_STRING(name) \
+	[name] = #name
+
+static const char * const lfa_error_strings[] = {
+	LFA_ERROR_STRING(LFA_SUCCESS),
+	LFA_ERROR_STRING(LFA_NOT_SUPPORTED),
+	LFA_ERROR_STRING(LFA_BUSY),
+	LFA_ERROR_STRING(LFA_AUTH_ERROR),
+	LFA_ERROR_STRING(LFA_NO_MEMORY),
+	LFA_ERROR_STRING(LFA_CRITICAL_ERROR),
+	LFA_ERROR_STRING(LFA_DEVICE_ERROR),
+	LFA_ERROR_STRING(LFA_WRONG_STATE),
+	LFA_ERROR_STRING(LFA_INVALID_PARAMETERS),
+	LFA_ERROR_STRING(LFA_COMPONENT_WRONG_STATE),
+	LFA_ERROR_STRING(LFA_INVALID_ADDRESS),
+	LFA_ERROR_STRING(LFA_ACTIVATION_FAILED)
+};
+
+enum image_attr_names {
+	LFA_ATTR_NAME,
+	LFA_ATTR_CURRENT_VERSION,
+	LFA_ATTR_PENDING_VERSION,
+	LFA_ATTR_ACT_CAPABLE,
+	LFA_ATTR_ACT_PENDING,
+	LFA_ATTR_MAY_RESET_CPU,
+	LFA_ATTR_CPU_RENDEZVOUS,
+	LFA_ATTR_FORCE_CPU_RENDEZVOUS,
+	LFA_ATTR_ACTIVATE,
+	LFA_ATTR_CANCEL,
+	LFA_ATTR_NR_IMAGES
+};
+
+struct fw_image {
+	struct kobject kobj;
+	const char *image_name;
+	int fw_seq_id;
+	u64 current_version;
+	u64 pending_version;
+	bool activation_capable;
+	bool activation_pending;
+	bool may_reset_cpu;
+	bool cpu_rendezvous;
+	bool cpu_rendezvous_forced;
+	struct kobj_attribute image_attrs[LFA_ATTR_NR_IMAGES];
+};
+
+static struct fw_image *kobj_to_fw_image(struct kobject *kobj)
+{
+	return container_of(kobj, struct fw_image, kobj);
+}
+
+/* A UUID split over two 64-bit registers */
+struct uuid_regs {
+	u64 uuid_lo;
+	u64 uuid_hi;
+};
+
+/* A list of known GUIDs, to be shown in the "name" sysfs file. */
+static const struct fw_image_uuid {
+	const char *name;
+	const char *uuid;
+} fw_images_uuids[] = {
+	{
+		.name = "TF-A BL31 runtime",
+		.uuid = "47d4086d-4cfe-9846-9b95-2950cbbd5a00",
+	},
+	{
+		.name = "BL33 non-secure payload",
+		.uuid = "d6d0eea7-fcea-d54b-9782-9934f234b6e4",
+	},
+	{
+		.name = "TF-RMM",
+		.uuid = "6c0762a6-12f2-4b56-92cb-ba8f633606d9",
+	},
+};
+
+static struct kset *lfa_kset;
+static struct workqueue_struct *fw_images_update_wq;
+static struct work_struct fw_images_update_work;
+static struct attribute *image_default_attrs[LFA_ATTR_NR_IMAGES + 1];
+
+static const struct attribute_group image_attr_group = {
+	.attrs = image_default_attrs,
+};
+
+static const struct attribute_group *image_default_groups[] = {
+	&image_attr_group,
+	NULL
+};
+
+static int update_fw_images_tree(void);
+
+static const char *lfa_error_string(int error)
+{
+	if (error > 0)
+		return lfa_error_strings[LFA_SUCCESS];
+
+	error = -error;
+	if (error < ARRAY_SIZE(lfa_error_strings))
+		return lfa_error_strings[error];
+	if (error == -LFA_TIMED_OUT)
+		return "timed out";
+
+	return lfa_error_strings[LFA_DEVICE_ERROR];
+}
+
+static void image_release(struct kobject *kobj)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+
+	kfree(image);
+}
+
+static const struct kobj_type image_ktype = {
+	.release = image_release,
+	.sysfs_ops = &kobj_sysfs_ops,
+	.default_groups = image_default_groups,
+};
+
+static void delete_fw_image_node(struct fw_image *image)
+{
+	kobject_del(&image->kobj);
+	kobject_put(&image->kobj);
+}
+
+static void remove_invalid_fw_images(struct work_struct *work)
+{
+	struct kobject *kobj, *tmp;
+	struct list_head images_to_delete = LIST_HEAD_INIT(images_to_delete);
+
+	/*
+	 * Remove firmware images including directories that are no longer
+	 * present in the LFA agent after updating the existing ones.
+	 * Delete list images before calling kobject_del() and kobject_put() on
+	 * them. Kobject_del() uses kset->list_lock itself which can cause lock
+	 * recursion, and kobject_put() may sleep.
+	 */
+	spin_lock(&lfa_kset->list_lock);
+	list_for_each_entry_safe(kobj, tmp, &lfa_kset->list, entry) {
+		struct fw_image *image = kobj_to_fw_image(kobj);
+
+		if (image->fw_seq_id == -1)
+			list_move_tail(&kobj->entry, &images_to_delete);
+	}
+	spin_unlock(&lfa_kset->list_lock);
+
+	/*
+	 * Now safely remove the sysfs kobjects for the deleted list items
+	 */
+	list_for_each_entry_safe(kobj, tmp, &images_to_delete, entry) {
+		struct fw_image *image = kobj_to_fw_image(kobj);
+
+		delete_fw_image_node(image);
+	}
+}
+
+static void set_image_flags(struct fw_image *image, int seq_id,
+			    u32 image_flags, u64 reg_current_ver,
+			    u64 reg_pending_ver)
+{
+	image->fw_seq_id = seq_id;
+	image->current_version = reg_current_ver;
+	image->pending_version = reg_pending_ver;
+	image->activation_capable = !!(image_flags & BIT(0));
+	image->activation_pending = !!(image_flags & BIT(1));
+	image->may_reset_cpu = !!(image_flags & BIT(2));
+	/* cpu_rendezvous_optional bit has inverse logic in the spec */
+	image->cpu_rendezvous = !(image_flags & BIT(3));
+}
+
+static unsigned long get_nr_lfa_components(void)
+{
+	struct arm_smccc_1_2_regs reg = { 0 };
+
+	reg.a0 = ARM_SMCCC_LFA_GET_INFO;
+	reg.a1 = 0; /* lfa_info_selector = 0 */
+
+	arm_smccc_1_2_invoke(&reg, &reg);
+	if (reg.a0 != LFA_SUCCESS)
+		return reg.a0;
+
+	return reg.a1;
+}
+
+static const char *get_image_name(const struct fw_image *image)
+{
+	if (image->image_name && image->image_name[0] != '\0')
+		return image->image_name;
+
+	return kobject_name(&image->kobj);
+}
+
+static int lfa_cancel(void *data)
+{
+	struct fw_image *image = data;
+	struct arm_smccc_1_2_regs reg = { 0 };
+
+	reg.a0 = ARM_SMCCC_LFA_CANCEL;
+	reg.a1 = image->fw_seq_id;
+	arm_smccc_1_2_invoke(&reg, &reg);
+
+	/*
+	 * When firmware activation is called with "skip_cpu_rendezvous=1",
+	 * LFA_CANCEL can fail with LFA_BUSY if the activation could not be
+	 * cancelled.
+	 */
+	if (reg.a0 == LFA_SUCCESS) {
+		pr_info("Activation cancelled for image %s\n",
+			get_image_name(image));
+	} else {
+		pr_err("Activation not cancelled for image %s: %s\n",
+		       get_image_name(image), lfa_error_string(reg.a0));
+		return -EINVAL;
+	}
+
+	return reg.a0;
+}
+
+/*
+ * Try a single activation call. The smc_lock writer lock must be held,
+ * and it must be called from inside stop_machine() when CPU rendezvous is
+ * required.
+ */
+static int call_lfa_activate(void *data)
+{
+	struct fw_image *image = data;
+	struct arm_smccc_1_2_regs reg = { 0 }, res;
+
+	reg.a0 = ARM_SMCCC_LFA_ACTIVATE;
+	reg.a1 = image->fw_seq_id;
+	/*
+	 * As we do not support updates requiring a CPU reset (yet),
+	 * we pass 0 in reg.a3 and reg.a4, holding the entry point and
+	 * context ID respectively.
+	 * cpu_rendezvous_forced is set by the administrator, via sysfs,
+	 * cpu_rendezvous is dictated by each firmware component.
+	 */
+	reg.a2 = !(image->cpu_rendezvous_forced || image->cpu_rendezvous);
+	arm_smccc_1_2_invoke(&reg, &res);
+
+	if ((long)res.a0 < 0)
+		return (long)res.a0;
+
+	if (res.a1 & LFA_ACTIVATE_CALL_AGAIN)
+		return -LFA_CALL_AGAIN;
+
+	return 0;
+}
+
+static int activate_fw_image(struct fw_image *image)
+{
+	int ret;
+
+retry:
+	if (image->cpu_rendezvous_forced || image->cpu_rendezvous)
+		ret = stop_machine(call_lfa_activate, image, cpu_online_mask);
+	else
+		ret = call_lfa_activate(image);
+
+	if (!ret) {
+		update_fw_images_tree();
+
+		return 0;
+	}
+
+	/* SMC returned with call_again flag set, or with LFA_BUSY */
+	if (ret == -LFA_CALL_AGAIN || ret == -LFA_BUSY)
+		goto retry;
+
+	lfa_cancel(image);
+
+	pr_err("LFA_ACTIVATE for image %s failed: %s\n",
+	       get_image_name(image), lfa_error_string(ret));
+
+	return ret;
+}
+
+static int prime_fw_image(struct fw_image *image)
+{
+	struct arm_smccc_1_2_regs reg = { 0 }, res;
+
+	if (image->may_reset_cpu) {
+		pr_err("CPU reset not supported by kernel driver\n");
+
+		return -EINVAL;
+	}
+
+	reg.a0 = ARM_SMCCC_LFA_PRIME;
+retry:
+	/*
+	 * LFA_PRIME will return 1 in reg.a1 if the firmware priming
+	 * is still in progress. In that case LFA_PRIME will need to
+	 * be called again.
+	 * reg.a1 will become 0 once the prime process completes.
+	 */
+	reg.a1 = image->fw_seq_id;
+	arm_smccc_1_2_invoke(&reg, &res);
+	if ((long)res.a0 < 0) {
+		pr_err("LFA_PRIME for image %s failed: %s\n",
+		       get_image_name(image),
+		       lfa_error_string(res.a0));
+
+		return res.a0;
+	}
+
+	if (res.a1 & LFA_PRIME_CALL_AGAIN)
+		goto retry;
+
+	return 0;
+}
+
+static ssize_t name_show(struct kobject *kobj, struct kobj_attribute *attr,
+			 char *buf)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+
+	return sysfs_emit(buf, "%s\n", image->image_name);
+}
+
+static ssize_t activation_capable_show(struct kobject *kobj,
+				       struct kobj_attribute *attr, char *buf)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+
+	return sysfs_emit(buf, "%d\n", image->activation_capable);
+}
+
+static void update_fw_image_pending(struct fw_image *image)
+{
+	struct arm_smccc_1_2_regs reg = { 0 };
+
+	reg.a0 = ARM_SMCCC_LFA_GET_INVENTORY;
+	reg.a1 = image->fw_seq_id;
+	arm_smccc_1_2_invoke(&reg, &reg);
+
+	if (reg.a0 == LFA_SUCCESS)
+		image->activation_pending = !!(reg.a3 & BIT(1));
+}
+
+static ssize_t activation_pending_show(struct kobject *kobj,
+				       struct kobj_attribute *attr, char *buf)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+
+	/*
+	 * Activation pending status can change anytime thus we need to update
+	 * and return its current value
+	 */
+	update_fw_image_pending(image);
+
+	return sysfs_emit(buf, "%d\n", image->activation_pending);
+}
+
+static ssize_t may_reset_cpu_show(struct kobject *kobj,
+				  struct kobj_attribute *attr, char *buf)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+
+	return sysfs_emit(buf, "%d\n", image->may_reset_cpu);
+}
+
+static ssize_t cpu_rendezvous_show(struct kobject *kobj,
+				   struct kobj_attribute *attr, char *buf)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+
+	return sysfs_emit(buf, "%d\n", image->cpu_rendezvous);
+}
+
+static ssize_t force_cpu_rendezvous_store(struct kobject *kobj,
+					  struct kobj_attribute *attr,
+					  const char *buf, size_t count)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+	int ret;
+
+	ret = kstrtobool(buf, &image->cpu_rendezvous_forced);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t force_cpu_rendezvous_show(struct kobject *kobj,
+					 struct kobj_attribute *attr, char *buf)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+
+	return sysfs_emit(buf, "%d\n", image->cpu_rendezvous_forced);
+}
+
+static ssize_t current_version_show(struct kobject *kobj,
+				    struct kobj_attribute *attr, char *buf)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+	u32 maj, min;
+
+	maj = image->current_version >> 32;
+	min = image->current_version & 0xffffffff;
+
+	return sysfs_emit(buf, "%u.%u\n", maj, min);
+}
+
+static ssize_t pending_version_show(struct kobject *kobj,
+				    struct kobj_attribute *attr, char *buf)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+	struct arm_smccc_1_2_regs reg = { 0 };
+
+	/*
+	 * Similar to activation pending, this value can change following an
+	 * update, we need to retrieve fresh info instead of stale information.
+	 */
+	reg.a0 = ARM_SMCCC_LFA_GET_INVENTORY;
+	reg.a1 = image->fw_seq_id;
+	arm_smccc_1_2_invoke(&reg, &reg);
+	if (reg.a0 == LFA_SUCCESS) {
+		if (reg.a5 != 0 && image->activation_pending) {
+			u32 maj, min;
+
+			image->pending_version = reg.a5;
+			maj = reg.a5 >> 32;
+			min = reg.a5 & 0xffffffff;
+
+			return sysfs_emit(buf, "%u.%u\n", maj, min);
+		}
+	}
+
+	return sysfs_emit(buf, "N/A\n");
+}
+
+static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+	int ret;
+
+	ret = prime_fw_image(image);
+	if (ret)
+		return -ECANCELED;
+
+	ret = activate_fw_image(image);
+	if (ret)
+		return -ECANCELED;
+
+	pr_info("%s: successfully activated\n", get_image_name(image));
+
+	return count;
+}
+
+static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr,
+			    const char *buf, size_t count)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+	int ret;
+
+	ret = lfa_cancel(image);
+	if (ret != 0)
+		return ret;
+
+	return count;
+}
+
+static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = {
+	[LFA_ATTR_NAME]			= __ATTR_RO(name),
+	[LFA_ATTR_CURRENT_VERSION]	= __ATTR_RO(current_version),
+	[LFA_ATTR_PENDING_VERSION]	= __ATTR_RO(pending_version),
+	[LFA_ATTR_ACT_CAPABLE]		= __ATTR_RO(activation_capable),
+	[LFA_ATTR_ACT_PENDING]		= __ATTR_RO(activation_pending),
+	[LFA_ATTR_MAY_RESET_CPU]	= __ATTR_RO(may_reset_cpu),
+	[LFA_ATTR_CPU_RENDEZVOUS]	= __ATTR_RO(cpu_rendezvous),
+	[LFA_ATTR_FORCE_CPU_RENDEZVOUS]	= __ATTR_RW(force_cpu_rendezvous),
+	[LFA_ATTR_ACTIVATE]		= __ATTR_WO(activate),
+	[LFA_ATTR_CANCEL]		= __ATTR_WO(cancel)
+};
+
+static void init_image_default_attrs(void)
+{
+	for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++)
+		image_default_attrs[i] = &image_attrs_group[i].attr;
+	image_default_attrs[LFA_ATTR_NR_IMAGES] = NULL;
+}
+
+static void clean_fw_images_tree(void)
+{
+	struct kobject *kobj, *tmp;
+	struct list_head images_to_delete;
+
+	INIT_LIST_HEAD(&images_to_delete);
+
+	spin_lock(&lfa_kset->list_lock);
+	list_for_each_entry_safe(kobj, tmp, &lfa_kset->list, entry) {
+		list_move_tail(&kobj->entry, &images_to_delete);
+	}
+	spin_unlock(&lfa_kset->list_lock);
+
+	list_for_each_entry_safe(kobj, tmp, &images_to_delete, entry) {
+		struct fw_image *image = kobj_to_fw_image(kobj);
+
+		delete_fw_image_node(image);
+	}
+}
+
+static int update_fw_image_node(char *fw_uuid, int seq_id,
+				u32 image_flags, u64 reg_current_ver,
+				u64 reg_pending_ver)
+{
+	const char *image_name = "";
+	struct fw_image *image;
+	struct kobject *kobj;
+	int i;
+
+	/*
+	 * If a fw_image is already in the images list then we just update
+	 * its flags and seq_id instead of trying to recreate it.
+	 */
+	spin_lock(&lfa_kset->list_lock);
+	list_for_each_entry(kobj, &lfa_kset->list, entry) {
+		if (!strcmp(kobject_name(kobj), fw_uuid)) {
+			struct fw_image *image = kobj_to_fw_image(kobj);
+
+			set_image_flags(image, seq_id, image_flags,
+					reg_current_ver, reg_pending_ver);
+			spin_unlock(&lfa_kset->list_lock);
+
+			return 0;
+		}
+	}
+	spin_unlock(&lfa_kset->list_lock);
+
+	image = kzalloc_obj(*image);
+	if (!image)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(fw_images_uuids); i++) {
+		if (!strcmp(fw_images_uuids[i].uuid, fw_uuid)) {
+			image_name = fw_images_uuids[i].name;
+			break;
+		}
+	}
+
+	image->kobj.kset = lfa_kset;
+	image->image_name = image_name;
+	image->cpu_rendezvous_forced = true;
+	set_image_flags(image, seq_id, image_flags, reg_current_ver,
+			reg_pending_ver);
+	if (kobject_init_and_add(&image->kobj, &image_ktype, NULL,
+				 "%s", fw_uuid)) {
+		kobject_put(&image->kobj);
+
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int update_fw_images_tree(void)
+{
+	struct arm_smccc_1_2_regs reg = { 0 }, res;
+	struct uuid_regs image_uuid;
+	struct kobject *kobj;
+	char image_id_str[40];
+	int ret, num_of_components;
+
+	num_of_components = get_nr_lfa_components();
+	if (num_of_components <= 0) {
+		pr_err("Error getting number of LFA components\n");
+		return -ENODEV;
+	}
+
+	/*
+	 * Invalidate fw_seq_ids (-1) for all images as the seq_ids and the
+	 * number of firmware images in the LFA agent may change after a
+	 * successful activation attempt. Negate all image flags as well.
+	 */
+	spin_lock(&lfa_kset->list_lock);
+	list_for_each_entry(kobj, &lfa_kset->list, entry) {
+		struct fw_image *image = kobj_to_fw_image(kobj);
+
+		set_image_flags(image, -1, 0b1000, 0, 0);
+	}
+	spin_unlock(&lfa_kset->list_lock);
+
+	reg.a0 = ARM_SMCCC_LFA_GET_INVENTORY;
+	for (int i = 0; i < num_of_components; i++) {
+		reg.a1 = i; /* fw_seq_id to be queried */
+		arm_smccc_1_2_invoke(&reg, &res);
+		if (res.a0 == LFA_SUCCESS) {
+			image_uuid.uuid_lo = res.a1;
+			image_uuid.uuid_hi = res.a2;
+
+			snprintf(image_id_str, sizeof(image_id_str), "%pUb",
+				 &image_uuid);
+			ret = update_fw_image_node(image_id_str, i, res.a3,
+						   res.a4, res.a5);
+			if (ret)
+				return ret;
+		}
+	}
+
+	/*
+	 * Removing non-valid image directories at the end of an activation.
+	 * We can't remove the sysfs attributes while in the respective
+	 * _store() handler, so have to postpone the list removal to a
+	 * workqueue.
+	 */
+	queue_work(fw_images_update_wq, &fw_images_update_work);
+
+	return 0;
+}
+
+static int lfa_smccc_probe(struct arm_smccc_device *sdev)
+{
+	struct arm_smccc_1_2_regs reg = { 0 };
+	int err;
+
+	reg.a0 = ARM_SMCCC_LFA_GET_VERSION;
+	arm_smccc_1_2_invoke(&reg, &reg);
+	if ((s32)reg.a0 == -LFA_NOT_SUPPORTED)
+		return -ENODEV;
+
+	pr_info("Live Firmware Activation: detected v%ld.%ld\n",
+		reg.a0 >> 16, reg.a0 & 0xffff);
+
+	fw_images_update_wq = alloc_workqueue("fw_images_update_wq",
+					      WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
+	if (!fw_images_update_wq) {
+		pr_err("Live Firmware Activation: Failed to allocate workqueue.\n");
+
+		return -ENOMEM;
+	}
+	INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
+
+	init_image_default_attrs();
+	lfa_kset = kset_create_and_add("lfa", NULL, firmware_kobj);
+	if (!lfa_kset) {
+		destroy_workqueue(fw_images_update_wq);
+
+		return -ENOMEM;
+	}
+
+	err = update_fw_images_tree();
+	if (err != 0) {
+		kset_unregister(lfa_kset);
+		destroy_workqueue(fw_images_update_wq);
+	}
+
+	return err;
+}
+
+static void lfa_smccc_remove(struct arm_smccc_device *sdev)
+{
+	flush_workqueue(fw_images_update_wq);
+	destroy_workqueue(fw_images_update_wq);
+	clean_fw_images_tree();
+	kset_unregister(lfa_kset);
+}
+
+static const struct arm_smccc_device_id lfa_smccc_id_table[] = {
+	{ .name = "arm-smccc-lfa" },
+	{}
+};
+MODULE_DEVICE_TABLE(arm_smccc, lfa_smccc_id_table);
+
+static struct arm_smccc_driver smccc_lfa_driver = {
+	.name		= KBUILD_MODNAME,
+	.probe		= lfa_smccc_probe,
+	.remove		= lfa_smccc_remove,
+	.id_table	= lfa_smccc_id_table,
+};
+
+module_arm_smccc_driver(smccc_lfa_driver);
+
+MODULE_DESCRIPTION("ARM Live Firmware Activation (LFA)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/smccc/smccc.c b/drivers/firmware/smccc/smccc.c
index a47696f3a5de..6ce2d984ce9f 100644
--- a/drivers/firmware/smccc/smccc.c
+++ b/drivers/firmware/smccc/smccc.c
@@ -94,6 +94,11 @@ static const struct smccc_device_info smccc_devices[] __initconst = {
 		.requires_smc   = false,
 		.device_name    = "arm-smccc-trng",
 	},
+	{
+		.func_id        = ARM_SMCCC_LFA_GET_VERSION,
+		.requires_smc   = false,
+		.device_name    = "arm-smccc-lfa",
+	},
 };
 
 static bool __init smccc_probe_smccc_device(const struct smccc_device_info *smccc_dev)
diff --git a/include/linux/arm-smccc.h b/include/linux/arm-smccc.h
index 4de81848fe2e..956a5af96c70 100644
--- a/include/linux/arm-smccc.h
+++ b/include/linux/arm-smccc.h
@@ -304,6 +304,21 @@
 			   ARM_SMCCC_OWNER_STANDARD,		\
 			   0x53)
 
+/* Live Firmware Activation (LFA) calls (defined by ARM DEN0147) */
+#define ARM_SMCCC_LFA_FN_BASE					\
+	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL,			\
+			   ARM_SMCCC_SMC_64,			\
+			   ARM_SMCCC_OWNER_STANDARD,		\
+			   0x2e0)
+#define ARM_SMCCC_LFA_FN(n)		(ARM_SMCCC_LFA_FN_BASE + (n))
+#define ARM_SMCCC_LFA_GET_VERSION	ARM_SMCCC_LFA_FN(0)
+#define ARM_SMCCC_LFA_CHECK_FEATURE	ARM_SMCCC_LFA_FN(1)
+#define ARM_SMCCC_LFA_GET_INFO		ARM_SMCCC_LFA_FN(2)
+#define ARM_SMCCC_LFA_GET_INVENTORY	ARM_SMCCC_LFA_FN(3)
+#define ARM_SMCCC_LFA_PRIME		ARM_SMCCC_LFA_FN(4)
+#define ARM_SMCCC_LFA_ACTIVATE		ARM_SMCCC_LFA_FN(5)
+#define ARM_SMCCC_LFA_CANCEL		ARM_SMCCC_LFA_FN(6)
+
 /*
  * Return codes defined in ARM DEN 0070A
  * ARM DEN 0070A is now merged/consolidated into ARM DEN 0028 C
-- 
2.43.0



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

* [PATCH v3 3/8] firmware: smccc: lfa: Add timeout and trigger watchdog
  2026-07-06 13:44 [PATCH v3 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 1/8] dt-bindings: arm: Add Live Firmware Activation Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 2/8] firmware: smccc: Add support for Live Firmware Activation (LFA) Andre Przywara
@ 2026-07-06 13:44 ` Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 4/8] firmware: smccc: lfa: Register ACPI notification Andre Przywara
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Andre Przywara @ 2026-07-06 13:44 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Conor Dooley, vsethi, Salman Nabi, Rob Herring, linux-kernel,
	Varun Wadekar, Trilok Soni, devicetree, Nirmoy Das,
	Krzysztof Kozlowski, linux-arm-kernel

From: Vedashree Vidwans <vvidwans@nvidia.com>

Enhance PRIME/ACTIVATION functions to touch watchdog and implement
timeout mechanism. This update ensures that any potential hangs are
detected promptly and that the LFA process is allocated sufficient
execution time before the watchdog timer expires. These changes improve
overall system reliability by reducing the risk of undetected process
stalls and unexpected watchdog resets.

Signed-off-by: Vedashree Vidwans <vvidwans@nvidia.com>
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
 drivers/firmware/smccc/lfa_fw.c | 43 ++++++++++++++++++++++++++++++---
 1 file changed, 39 insertions(+), 4 deletions(-)

diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
index b333b1e28c0d..357e41f95206 100644
--- a/drivers/firmware/smccc/lfa_fw.c
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -6,11 +6,14 @@
 #include <linux/arm-smccc.h>
 #include <linux/arm-smccc-bus.h>
 #include <linux/array_size.h>
+#include <linux/delay.h>
 #include <linux/fs.h>
 #include <linux/init.h>
 #include <linux/kobject.h>
+#include <linux/ktime.h>
 #include <linux/list.h>
 #include <linux/module.h>
+#include <linux/nmi.h>
 #include <linux/psci.h>
 #include <linux/stop_machine.h>
 #include <linux/string.h>
@@ -27,6 +30,11 @@
 #define LFA_PRIME_CALL_AGAIN		BIT(0)
 #define LFA_ACTIVATE_CALL_AGAIN		BIT(0)
 
+#define LFA_PRIME_BUDGET_MS		30000		/* 30s cap */
+#define LFA_PRIME_DELAY_MS		10		/* 10ms between polls */
+#define LFA_ACTIVATE_BUDGET_MS		10000		/* 10s cap */
+#define LFA_ACTIVATE_DELAY_MS		10		/* 10ms between polls */
+
 /* LFA return values */
 #define LFA_SUCCESS			0
 #define LFA_NOT_SUPPORTED		1
@@ -276,6 +284,7 @@ static int call_lfa_activate(void *data)
 	struct fw_image *image = data;
 	struct arm_smccc_1_2_regs reg = { 0 }, res;
 
+	touch_nmi_watchdog();
 	reg.a0 = ARM_SMCCC_LFA_ACTIVATE;
 	reg.a1 = image->fw_seq_id;
 	/*
@@ -299,6 +308,7 @@ static int call_lfa_activate(void *data)
 
 static int activate_fw_image(struct fw_image *image)
 {
+	ktime_t end = ktime_add_ms(ktime_get(), LFA_ACTIVATE_BUDGET_MS);
 	int ret;
 
 retry:
@@ -314,8 +324,14 @@ static int activate_fw_image(struct fw_image *image)
 	}
 
 	/* SMC returned with call_again flag set, or with LFA_BUSY */
-	if (ret == -LFA_CALL_AGAIN || ret == -LFA_BUSY)
-		goto retry;
+	if (ret == -LFA_CALL_AGAIN || ret == -LFA_BUSY) {
+		if (ktime_before(ktime_get(), end)) {
+			msleep_interruptible(LFA_ACTIVATE_DELAY_MS);
+			goto retry;
+		}
+
+		ret = -LFA_TIMED_OUT;
+	}
 
 	lfa_cancel(image);
 
@@ -328,6 +344,7 @@ static int activate_fw_image(struct fw_image *image)
 static int prime_fw_image(struct fw_image *image)
 {
 	struct arm_smccc_1_2_regs reg = { 0 }, res;
+	ktime_t end = ktime_add_ms(ktime_get(), LFA_PRIME_BUDGET_MS);
 
 	if (image->may_reset_cpu) {
 		pr_err("CPU reset not supported by kernel driver\n");
@@ -335,6 +352,8 @@ static int prime_fw_image(struct fw_image *image)
 		return -EINVAL;
 	}
 
+	touch_nmi_watchdog();
+
 	reg.a0 = ARM_SMCCC_LFA_PRIME;
 retry:
 	/*
@@ -353,8 +372,24 @@ static int prime_fw_image(struct fw_image *image)
 		return res.a0;
 	}
 
-	if (res.a1 & LFA_PRIME_CALL_AGAIN)
-		goto retry;
+	if (res.a1 & LFA_PRIME_CALL_AGAIN) {
+		int ret;
+
+		/* SMC returned with call_again flag set */
+		if (ktime_before(ktime_get(), end)) {
+			msleep_interruptible(LFA_PRIME_DELAY_MS);
+			goto retry;
+		}
+
+		pr_err("LFA_PRIME for image %s timed out",
+		       get_image_name(image));
+
+		ret = lfa_cancel(image);
+		if (ret != 0)
+			return ret;
+
+		return -ETIMEDOUT;
+	}
 
 	return 0;
 }
-- 
2.43.0



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

* [PATCH v3 4/8] firmware: smccc: lfa: Register ACPI notification
  2026-07-06 13:44 [PATCH v3 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (2 preceding siblings ...)
  2026-07-06 13:44 ` [PATCH v3 3/8] firmware: smccc: lfa: Add timeout and trigger watchdog Andre Przywara
@ 2026-07-06 13:44 ` Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 5/8] firmware: smccc: lfa: Add auto_activate sysfs file Andre Przywara
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Andre Przywara @ 2026-07-06 13:44 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Conor Dooley, vsethi, Salman Nabi, Rob Herring, linux-kernel,
	Varun Wadekar, Trilok Soni, devicetree, Nirmoy Das,
	Krzysztof Kozlowski, linux-arm-kernel

From: Vedashree Vidwans <vvidwans@nvidia.com>

The Arm LFA spec describes an ACPI notification mechanism, where the
platform (firmware) can notify an LFA client about newly available
firmware imag updates ("pending images" in LFA terms).

Add a faux device after discovering the existence of an LFA agent via
the SMCCC discovery mechnism, and use that device to check for the ACPI
notification description. Register this when one is provided.

The notification just conveys the fact that at least one firmware image
has now a pending update, it doesn't say which, also there could be more
than one pending. Loop through all images to find every which needs to
be activated, and trigger the activation. We need to do this is a loop,
since an activation might change the number and the status of available
images.

Signed-off-by: Vedashree Vidwans <vvidwans@nvidia.com>
[Andre: convert from platform driver to smccc bus]
Signed-off-by: Andre Przywara <andre.przywar@arm.com>
---
 drivers/firmware/smccc/lfa_fw.c | 123 +++++++++++++++++++++++++++++++-
 1 file changed, 122 insertions(+), 1 deletion(-)

diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
index 357e41f95206..5b7f9b07f6c8 100644
--- a/drivers/firmware/smccc/lfa_fw.c
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -3,12 +3,14 @@
  * Copyright (C) 2025 Arm Limited
  */
 
+#include <linux/acpi.h>
 #include <linux/arm-smccc.h>
 #include <linux/arm-smccc-bus.h>
 #include <linux/array_size.h>
 #include <linux/delay.h>
 #include <linux/fs.h>
 #include <linux/init.h>
+#include <linux/kernel.h>
 #include <linux/kobject.h>
 #include <linux/ktime.h>
 #include <linux/list.h>
@@ -18,11 +20,13 @@
 #include <linux/stop_machine.h>
 #include <linux/string.h>
 #include <linux/sysfs.h>
+#include <linux/types.h>
 #include <linux/uuid.h>
 #include <linux/workqueue.h>
 
 #include <uapi/linux/psci.h>
 
+#define DRIVER_NAME	"ARM_LFA"
 #undef pr_fmt
 #define pr_fmt(fmt) "Arm LFA: " fmt
 
@@ -694,6 +698,112 @@ static int update_fw_images_tree(void)
 	return 0;
 }
 
+/*
+ * Go through all FW images in a loop and trigger activation
+ * of all activatible and pending images.
+ * We have to restart enumeration after every triggered activation,
+ * since the firmware images might have changed during the activation.
+ */
+static int activate_pending_image(void)
+{
+	struct kobject *kobj;
+	bool found_pending = false;
+	struct fw_image *image;
+	int ret;
+
+	spin_lock(&lfa_kset->list_lock);
+	list_for_each_entry(kobj, &lfa_kset->list, entry) {
+		image = kobj_to_fw_image(kobj);
+
+		if (image->fw_seq_id == -1)
+			continue; /* Invalid FW component */
+
+		update_fw_image_pending(image);
+		if (image->activation_capable && image->activation_pending) {
+			found_pending = true;
+			break;
+		}
+	}
+	spin_unlock(&lfa_kset->list_lock);
+
+	if (!found_pending)
+		return -ENOENT;
+
+	ret = prime_fw_image(image);
+	if (ret)
+		return ret;
+
+	ret = activate_fw_image(image);
+	if (ret)
+		return ret;
+
+	pr_info("%s: automatic activation succeeded\n", get_image_name(image));
+
+	return 0;
+}
+
+#ifdef CONFIG_ACPI
+static void lfa_acpi_notify_handler(acpi_handle handle, u32 event, void *data)
+{
+	int ret;
+
+	while (!(ret = activate_pending_image()))
+		;
+
+	if (ret != -ENOENT)
+		pr_warn("notified image activation failed: %d\n", ret);
+}
+
+static int lfa_register_acpi(struct device *dev)
+{
+	struct acpi_device *acpi_dev;
+	acpi_handle handle;
+	acpi_status status;
+
+	acpi_dev = acpi_dev_get_first_match_dev("ARML0003", NULL, -1);
+	if (!acpi_dev)
+		return -ENODEV;
+	handle = acpi_device_handle(acpi_dev);
+	if (!handle) {
+		acpi_dev_put(acpi_dev);
+		return -ENODEV;
+	}
+
+	/* Register notify handler that indicates LFA updates are available */
+	status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY,
+					     lfa_acpi_notify_handler, NULL);
+	if (ACPI_FAILURE(status)) {
+		acpi_dev_put(acpi_dev);
+		return -EIO;
+	}
+
+	ACPI_COMPANION_SET(dev, acpi_dev);
+
+	return 0;
+}
+
+static void lfa_remove_acpi(struct device *dev)
+{
+	struct acpi_device *acpi_dev = ACPI_COMPANION(dev);
+	acpi_handle handle = acpi_device_handle(acpi_dev);
+
+	if (handle)
+		acpi_remove_notify_handler(handle,
+					   ACPI_DEVICE_NOTIFY,
+					   lfa_acpi_notify_handler);
+	acpi_dev_put(acpi_dev);
+}
+#else	/* !CONFIG_ACPI */
+static int lfa_register_acpi(struct device *dev)
+{
+	return -ENODEV;
+}
+
+static void lfa_remove_acpi(struct device *dev)
+{
+}
+#endif
+
 static int lfa_smccc_probe(struct arm_smccc_device *sdev)
 {
 	struct arm_smccc_1_2_regs reg = { 0 };
@@ -730,11 +840,22 @@ static int lfa_smccc_probe(struct arm_smccc_device *sdev)
 		destroy_workqueue(fw_images_update_wq);
 	}
 
-	return err;
+	if (!acpi_disabled) {
+		err = lfa_register_acpi(&sdev->dev);
+		if (err != -ENODEV) {
+			if (!err)
+				pr_info("registered LFA ACPI notification\n");
+			return err;
+		}
+	}
+
+	return 0;
 }
 
 static void lfa_smccc_remove(struct arm_smccc_device *sdev)
 {
+	if (!acpi_disabled)
+		lfa_remove_acpi(&sdev->dev);
 	flush_workqueue(fw_images_update_wq);
 	destroy_workqueue(fw_images_update_wq);
 	clean_fw_images_tree();
-- 
2.43.0



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

* [PATCH v3 5/8] firmware: smccc: lfa: Add auto_activate sysfs file
  2026-07-06 13:44 [PATCH v3 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (3 preceding siblings ...)
  2026-07-06 13:44 ` [PATCH v3 4/8] firmware: smccc: lfa: Register ACPI notification Andre Przywara
@ 2026-07-06 13:44 ` Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 6/8] firmware: smccc: lfa: Register DT interrupt Andre Przywara
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Andre Przywara @ 2026-07-06 13:44 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Conor Dooley, vsethi, Salman Nabi, Rob Herring, linux-kernel,
	Varun Wadekar, Trilok Soni, devicetree, Nirmoy Das,
	Krzysztof Kozlowski, linux-arm-kernel

The Arm LFA spec places control over the actual activation process in
the hands of the non-secure host OS. An platform initiated interrupt or
notification signals the availability of an updateable firmware image,
but does not necessarily need to trigger it automatically.

Add a sysfs control file that guards such automatic activation. If an
administrator wants to allow automatic platform initiated updates, they
can activate that by echoing a "1" into the auto_activate file in the
respective sysfs directory. Any incoming notification would then result
in the activation triggered.

Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
 drivers/firmware/smccc/lfa_fw.c | 34 ++++++++++++++++++++++++++++++---
 1 file changed, 31 insertions(+), 3 deletions(-)

diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
index 5b7f9b07f6c8..ce4f966db475 100644
--- a/drivers/firmware/smccc/lfa_fw.c
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -89,6 +89,7 @@ enum image_attr_names {
 	LFA_ATTR_FORCE_CPU_RENDEZVOUS,
 	LFA_ATTR_ACTIVATE,
 	LFA_ATTR_CANCEL,
+	LFA_ATTR_AUTO_ACTIVATE,
 	LFA_ATTR_NR_IMAGES
 };
 
@@ -103,6 +104,7 @@ struct fw_image {
 	bool may_reset_cpu;
 	bool cpu_rendezvous;
 	bool cpu_rendezvous_forced;
+	bool auto_activate;
 	struct kobj_attribute image_attrs[LFA_ATTR_NR_IMAGES];
 };
 
@@ -550,6 +552,28 @@ static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr,
 	return count;
 }
 
+static ssize_t auto_activate_store(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+	int ret;
+
+	ret = kstrtobool(buf, &image->auto_activate);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t auto_activate_show(struct kobject *kobj,
+				  struct kobj_attribute *attr, char *buf)
+{
+	struct fw_image *image = kobj_to_fw_image(kobj);
+
+	return sysfs_emit(buf, "%d\n", image->auto_activate);
+}
+
 static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = {
 	[LFA_ATTR_NAME]			= __ATTR_RO(name),
 	[LFA_ATTR_CURRENT_VERSION]	= __ATTR_RO(current_version),
@@ -560,7 +584,8 @@ static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = {
 	[LFA_ATTR_CPU_RENDEZVOUS]	= __ATTR_RO(cpu_rendezvous),
 	[LFA_ATTR_FORCE_CPU_RENDEZVOUS]	= __ATTR_RW(force_cpu_rendezvous),
 	[LFA_ATTR_ACTIVATE]		= __ATTR_WO(activate),
-	[LFA_ATTR_CANCEL]		= __ATTR_WO(cancel)
+	[LFA_ATTR_CANCEL]		= __ATTR_WO(cancel),
+	[LFA_ATTR_AUTO_ACTIVATE]	= __ATTR_RW(auto_activate),
 };
 
 static void init_image_default_attrs(void)
@@ -631,6 +656,7 @@ static int update_fw_image_node(char *fw_uuid, int seq_id,
 	image->kobj.kset = lfa_kset;
 	image->image_name = image_name;
 	image->cpu_rendezvous_forced = true;
+	image->auto_activate = false;
 	set_image_flags(image, seq_id, image_flags, reg_current_ver,
 			reg_pending_ver);
 	if (kobject_init_and_add(&image->kobj, &image_ktype, NULL,
@@ -700,7 +726,8 @@ static int update_fw_images_tree(void)
 
 /*
  * Go through all FW images in a loop and trigger activation
- * of all activatible and pending images.
+ * of all activatible and pending images, but only if automatic
+ * activation for that image is allowed.
  * We have to restart enumeration after every triggered activation,
  * since the firmware images might have changed during the activation.
  */
@@ -719,7 +746,8 @@ static int activate_pending_image(void)
 			continue; /* Invalid FW component */
 
 		update_fw_image_pending(image);
-		if (image->activation_capable && image->activation_pending) {
+		if (image->activation_capable && image->activation_pending &&
+		    image->auto_activate) {
 			found_pending = true;
 			break;
 		}
-- 
2.43.0



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

* [PATCH v3 6/8] firmware: smccc: lfa: Register DT interrupt
  2026-07-06 13:44 [PATCH v3 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (4 preceding siblings ...)
  2026-07-06 13:44 ` [PATCH v3 5/8] firmware: smccc: lfa: Add auto_activate sysfs file Andre Przywara
@ 2026-07-06 13:44 ` Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 7/8] firmware: smccc: lfa: introduce SMC access lock Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 8/8] firmware: smccc: lfa: add sysfs ABI documentation Andre Przywara
  7 siblings, 0 replies; 9+ messages in thread
From: Andre Przywara @ 2026-07-06 13:44 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Conor Dooley, vsethi, Salman Nabi, Rob Herring, linux-kernel,
	Varun Wadekar, Trilok Soni, devicetree, Nirmoy Das,
	Krzysztof Kozlowski, linux-arm-kernel

The Arm Live Firmware Activation spec describes an asynchronous
notification mechanism, where the platform can notify the host OS about
newly pending image updates.
In the absence of the ACPI notification mechanism also a simple
devicetree node can describe an interrupt.

Add code to find the respective DT node and register the specified
interrupt, to trigger the activation if needed.

Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
 drivers/firmware/smccc/lfa_fw.c | 45 +++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
index ce4f966db475..2a9c2a90b1bb 100644
--- a/drivers/firmware/smccc/lfa_fw.c
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -16,6 +16,8 @@
 #include <linux/list.h>
 #include <linux/module.h>
 #include <linux/nmi.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
 #include <linux/psci.h>
 #include <linux/stop_machine.h>
 #include <linux/string.h>
@@ -832,6 +834,43 @@ static void lfa_remove_acpi(struct device *dev)
 }
 #endif
 
+static irqreturn_t lfa_irq_handler(int irq, void *dev_id)
+{
+	return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t lfa_irq_handler_thread(int irq, void *dev_id)
+{
+	int ret;
+
+	while (!(ret = activate_pending_image()))
+		;
+
+	if (ret != -ENOENT)
+		pr_warn("notified image activation failed: %d\n", ret);
+
+	return IRQ_HANDLED;
+}
+
+static int lfa_register_dt(struct device *dev)
+{
+	struct device_node *np;
+	unsigned int irq;
+
+	np = of_find_compatible_node(NULL, NULL, "arm,lfa");
+	if (!np)
+		return -ENODEV;
+
+	irq = irq_of_parse_and_map(np, 0);
+	of_node_put(np);
+	if (!irq)
+		return -ENODEV;
+
+	return devm_request_threaded_irq(dev, irq, lfa_irq_handler,
+					 lfa_irq_handler_thread,
+					 IRQF_COND_ONESHOT, NULL, NULL);
+}
+
 static int lfa_smccc_probe(struct arm_smccc_device *sdev)
 {
 	struct arm_smccc_1_2_regs reg = { 0 };
@@ -877,6 +916,12 @@ static int lfa_smccc_probe(struct arm_smccc_device *sdev)
 		}
 	}
 
+	err = lfa_register_dt(&sdev->dev);
+	if (!err)
+		pr_info("registered LFA DT notification interrupt\n");
+	if (err != -ENODEV)
+		return err;
+
 	return 0;
 }
 
-- 
2.43.0



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

* [PATCH v3 7/8] firmware: smccc: lfa: introduce SMC access lock
  2026-07-06 13:44 [PATCH v3 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (5 preceding siblings ...)
  2026-07-06 13:44 ` [PATCH v3 6/8] firmware: smccc: lfa: Register DT interrupt Andre Przywara
@ 2026-07-06 13:44 ` Andre Przywara
  2026-07-06 13:44 ` [PATCH v3 8/8] firmware: smccc: lfa: add sysfs ABI documentation Andre Przywara
  7 siblings, 0 replies; 9+ messages in thread
From: Andre Przywara @ 2026-07-06 13:44 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Conor Dooley, vsethi, Salman Nabi, Rob Herring, linux-kernel,
	Varun Wadekar, Trilok Soni, devicetree, Nirmoy Das,
	Krzysztof Kozlowski, linux-arm-kernel

After a successful live activation, the list of firmware images might
change, which also affects the sequence IDs. We store the sequence
ID in a data structure and connect it to its GUID, which is the
identifier used to access certain image properties from userland.
When an activation is happening, the sequence ID associations might
change at any point, so we must be sure to not use any previously
learned sequence ID during this time.

Protect the association between a sequence ID and a firmware image
(its GUID, really) by a reader/writer lock. In this case it's a R/W
semaphore, so it can sleep and we can hold it for longer, also
concurrent SMC calls are not blocked on each other, it's just an
activation that blocks calls.

Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
 drivers/firmware/smccc/lfa_fw.c | 38 +++++++++++++++++++++++++++++++--
 1 file changed, 36 insertions(+), 2 deletions(-)

diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
index 2a9c2a90b1bb..44f8baec4c92 100644
--- a/drivers/firmware/smccc/lfa_fw.c
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -19,6 +19,7 @@
 #include <linux/of.h>
 #include <linux/of_irq.h>
 #include <linux/psci.h>
+#include <linux/rwsem.h>
 #include <linux/stop_machine.h>
 #include <linux/string.h>
 #include <linux/sysfs.h>
@@ -145,6 +146,16 @@ static struct workqueue_struct *fw_images_update_wq;
 static struct work_struct fw_images_update_work;
 static struct attribute *image_default_attrs[LFA_ATTR_NR_IMAGES + 1];
 
+/*
+ * A successful image activation might change the number of available images,
+ * leading to a re-order and thus re-assignment of the sequence IDs.
+ * The lock protects the connection between a firmware image (through its
+ * user visible UUID) and the sequence IDs. Anyone doing an SMC call with
+ * a sequence ID needs to take the readers lock. Doing an activation requires
+ * the writer lock, as that process might change the assocications.
+ */
+struct rw_semaphore smc_lock;
+
 static const struct attribute_group image_attr_group = {
 	.attrs = image_default_attrs,
 };
@@ -241,6 +252,7 @@ static unsigned long get_nr_lfa_components(void)
 	reg.a0 = ARM_SMCCC_LFA_GET_INFO;
 	reg.a1 = 0; /* lfa_info_selector = 0 */
 
+	/* No need for the smc_lock, since no sequence IDs are involved. */
 	arm_smccc_1_2_invoke(&reg, &reg);
 	if (reg.a0 != LFA_SUCCESS)
 		return reg.a0;
@@ -261,9 +273,11 @@ static int lfa_cancel(void *data)
 	struct fw_image *image = data;
 	struct arm_smccc_1_2_regs reg = { 0 };
 
+	down_read(&smc_lock);
 	reg.a0 = ARM_SMCCC_LFA_CANCEL;
 	reg.a1 = image->fw_seq_id;
 	arm_smccc_1_2_invoke(&reg, &reg);
+	up_read(&smc_lock);
 
 	/*
 	 * When firmware activation is called with "skip_cpu_rendezvous=1",
@@ -320,6 +334,7 @@ static int activate_fw_image(struct fw_image *image)
 	int ret;
 
 retry:
+	down_write(&smc_lock);
 	if (image->cpu_rendezvous_forced || image->cpu_rendezvous)
 		ret = stop_machine(call_lfa_activate, image, cpu_online_mask);
 	else
@@ -327,10 +342,13 @@ static int activate_fw_image(struct fw_image *image)
 
 	if (!ret) {
 		update_fw_images_tree();
+		up_write(&smc_lock);
 
 		return 0;
 	}
 
+	up_write(&smc_lock);
+
 	/* SMC returned with call_again flag set, or with LFA_BUSY */
 	if (ret == -LFA_CALL_AGAIN || ret == -LFA_BUSY) {
 		if (ktime_before(ktime_get(), end)) {
@@ -370,8 +388,11 @@ static int prime_fw_image(struct fw_image *image)
 	 * be called again.
 	 * reg.a1 will become 0 once the prime process completes.
 	 */
+	down_read(&smc_lock);
 	reg.a1 = image->fw_seq_id;
 	arm_smccc_1_2_invoke(&reg, &res);
+	up_read(&smc_lock);
+
 	if ((long)res.a0 < 0) {
 		pr_err("LFA_PRIME for image %s failed: %s\n",
 		       get_image_name(image),
@@ -418,7 +439,7 @@ static ssize_t activation_capable_show(struct kobject *kobj,
 	return sysfs_emit(buf, "%d\n", image->activation_capable);
 }
 
-static void update_fw_image_pending(struct fw_image *image)
+static void _update_fw_image_pending(struct fw_image *image)
 {
 	struct arm_smccc_1_2_regs reg = { 0 };
 
@@ -430,6 +451,13 @@ static void update_fw_image_pending(struct fw_image *image)
 		image->activation_pending = !!(reg.a3 & BIT(1));
 }
 
+static void update_fw_image_pending(struct fw_image *image)
+{
+	down_read(&smc_lock);
+	_update_fw_image_pending(image);
+	up_read(&smc_lock);
+}
+
 static ssize_t activation_pending_show(struct kobject *kobj,
 				       struct kobj_attribute *attr, char *buf)
 {
@@ -504,9 +532,11 @@ static ssize_t pending_version_show(struct kobject *kobj,
 	 * Similar to activation pending, this value can change following an
 	 * update, we need to retrieve fresh info instead of stale information.
 	 */
+	down_read(&smc_lock);
 	reg.a0 = ARM_SMCCC_LFA_GET_INVENTORY;
 	reg.a1 = image->fw_seq_id;
 	arm_smccc_1_2_invoke(&reg, &reg);
+	up_read(&smc_lock);
 	if (reg.a0 == LFA_SUCCESS) {
 		if (reg.a5 != 0 && image->activation_pending) {
 			u32 maj, min;
@@ -740,6 +770,7 @@ static int activate_pending_image(void)
 	struct fw_image *image;
 	int ret;
 
+	down_read(&smc_lock);
 	spin_lock(&lfa_kset->list_lock);
 	list_for_each_entry(kobj, &lfa_kset->list, entry) {
 		image = kobj_to_fw_image(kobj);
@@ -747,7 +778,7 @@ static int activate_pending_image(void)
 		if (image->fw_seq_id == -1)
 			continue; /* Invalid FW component */
 
-		update_fw_image_pending(image);
+		_update_fw_image_pending(image);
 		if (image->activation_capable && image->activation_pending &&
 		    image->auto_activate) {
 			found_pending = true;
@@ -755,6 +786,7 @@ static int activate_pending_image(void)
 		}
 	}
 	spin_unlock(&lfa_kset->list_lock);
+	up_read(&smc_lock);
 
 	if (!found_pending)
 		return -ENOENT;
@@ -901,6 +933,8 @@ static int lfa_smccc_probe(struct arm_smccc_device *sdev)
 		return -ENOMEM;
 	}
 
+	init_rwsem(&smc_lock);
+
 	err = update_fw_images_tree();
 	if (err != 0) {
 		kset_unregister(lfa_kset);
-- 
2.43.0



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

* [PATCH v3 8/8] firmware: smccc: lfa: add sysfs ABI documentation
  2026-07-06 13:44 [PATCH v3 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (6 preceding siblings ...)
  2026-07-06 13:44 ` [PATCH v3 7/8] firmware: smccc: lfa: introduce SMC access lock Andre Przywara
@ 2026-07-06 13:44 ` Andre Przywara
  7 siblings, 0 replies; 9+ messages in thread
From: Andre Przywara @ 2026-07-06 13:44 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Conor Dooley, vsethi, Salman Nabi, Rob Herring, linux-kernel,
	Varun Wadekar, Trilok Soni, devicetree, Nirmoy Das,
	Krzysztof Kozlowski, linux-arm-kernel

Document the sysfs attribute files as exported by the LFA driver.

Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
 Documentation/ABI/testing/sysfs-firmware-lfa | 106 +++++++++++++++++++
 1 file changed, 106 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-firmware-lfa

diff --git a/Documentation/ABI/testing/sysfs-firmware-lfa b/Documentation/ABI/testing/sysfs-firmware-lfa
new file mode 100644
index 000000000000..afd349af9cf1
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-firmware-lfa
@@ -0,0 +1,106 @@
+What:		/sys/firmware/lfa
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		The Arm Live Firmware Activation (LFA) spec describes an
+		interface between platform firmware (an agent) and any user
+		on the non-secure side (like the Linux kernel) to activate
+		pending firmware updates at runtime. The actual firmware
+		update would be provided independently (out-of-band, or
+		via any existing explicit firmware update mechanisms), then,
+		if supported, can be activated immediately, without rebooting
+		the system.
+		This directory contains one directory for each of the LFA
+		capable firmware images supported by the agent, identified by
+		its GUID. Within each directory, the same set of sysfs files
+		provides access to the properties and actions for this
+		particular firmware image.
+
+What:		/sys/firmware/lfa/<GUID>/activate
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(WO) Write a boolean 'true' value to trigger the
+		activation of that firmware component. An update must be
+		pending for this to be successful.
+
+What:		/sys/firmware/lfa/<GUID>/activation_capable
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RO) A boolean value to show whether this firmware image is
+		capable of being activated.
+
+What:		/sys/firmware/lfa/<GUID>/auto_activate
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RW) Write a boolean 'true' value to allow a firmware image
+		to be automatically activated once a new firmware update
+		becomes available ("pending"). This depends on a working
+		notification scheme, for instance via an ACPI notification
+		or an interrupt to notify the kernel.
+
+What:		/sys/firmware/lfa/<GUID>/cancel
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(WO) Write a boolean 'true' value to cancel a recently
+		triggered activation, if that has not finished yet.
+
+What:		/sys/firmware/lfa/<GUID>/cpu_rendezvous
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RO) A boolean value to show whether this image requires
+		a CPU rendezvous in firmware to be activated. If true, this
+		means all CPUs will enter the firmware during the activation
+		process, so will not be able to execute kernel code or
+		handle interrupts for a brief moment.
+
+What:		/sys/firmware/lfa/<GUID>/current_version
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RO) Shows the currently running version of that firmware
+		image, as reported by the agent (if supported).
+
+What:		/sys/firmware/lfa/<GUID>/force_cpu_rendezvous
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RW) Write a boolean 'true' value to force whether the kernel
+		will perform a CPU rendezvous during activation. This has no
+		effect if the firmware already *requires* a CPU rendezvous.
+		Firmware images might provide services to the kernel, at which
+		point is might be unsafe to continue running the kernel while
+		the activation is performed. Sending all CPUs into firmware
+		provides the safest option for the kernel. If you know for
+		certain that there are no services provides by that component
+		or the firmware component can handle requests during the
+		activation, you can write a 0 in here to avoid the brief
+		kernel downtime.
+
+What:		/sys/firmware/lfa/<GUID>/may_reset_cpu
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RO) A boolean value to show whether this firmware image
+		requires a CPU reset during the activation.
+		Currently this type of firmware image is not supported by
+		the kernel driver, as we cannot handle a CPU reset yet.
+
+What:		/sys/firmware/lfa/<GUID>/name
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RO) Shows a human readable name for that firmware image, if
+		known. Depends on the kernel driver knowing this particular
+		GUID, via a built-in list.
+
+What:		/sys/firmware/lfa/<GUID>/pending_version
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RO) Shows the version number of a pending firmware update
+		image, as reported by the agent (if supported).
-- 
2.43.0



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

end of thread, other threads:[~2026-07-06 13:45 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-06 13:44 [PATCH v3 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
2026-07-06 13:44 ` [PATCH v3 1/8] dt-bindings: arm: Add Live Firmware Activation Andre Przywara
2026-07-06 13:44 ` [PATCH v3 2/8] firmware: smccc: Add support for Live Firmware Activation (LFA) Andre Przywara
2026-07-06 13:44 ` [PATCH v3 3/8] firmware: smccc: lfa: Add timeout and trigger watchdog Andre Przywara
2026-07-06 13:44 ` [PATCH v3 4/8] firmware: smccc: lfa: Register ACPI notification Andre Przywara
2026-07-06 13:44 ` [PATCH v3 5/8] firmware: smccc: lfa: Add auto_activate sysfs file Andre Przywara
2026-07-06 13:44 ` [PATCH v3 6/8] firmware: smccc: lfa: Register DT interrupt Andre Przywara
2026-07-06 13:44 ` [PATCH v3 7/8] firmware: smccc: lfa: introduce SMC access lock Andre Przywara
2026-07-06 13:44 ` [PATCH v3 8/8] firmware: smccc: lfa: add sysfs ABI documentation Andre Przywara

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