Devicetree
 help / color / mirror / Atom feed
* [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support
@ 2026-11-02 16:54 Andre Przywara
  2026-11-02 16:54 ` [PATCH v5 RESEND 1/6] dt-bindings: arm: Add Live Firmware Activation Andre Przywara
                   ` (6 more replies)
  0 siblings, 7 replies; 19+ messages in thread
From: Andre Przywara @ 2026-11-02 16:54 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Salman Nabi, Vedashree Vidwans, Trilok Soni, Nirmoy Das, vsethi,
	Varun Wadekar, linux-arm-kernel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree

Hi all,

-------------------------
a resend of the v5 series posted two weeks ago, where our mailserver
swallowed one email. Just used the opportunity to rebase on v7.4-rc1,
so dropping the two extra patches I prepended to the post back then
(already merged). No changes to the patches themselves compared to the
other v5 post.
-------------------------

v5 is a decent overhaul of the series for the "Live Firmware Activation"
kernel support, based on feedback from Mark and Sudeep. Two patches have
been squashed into the main feature patch, the name attribute has been
removed, and documentation (hopefully) improved. Other than that quite
some smaller changes, renames and reworks, hopefully for the better.

For a high level overview of LFA see the rest of this commit message and
the commit message of the main feature patch (2/6). Some simplified
overview of the spec is in the top comment in patch 2/6. Also the sysfs
ABI documentation in the last patch contains some more information.

Based on v7.4-rc1. 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 UUID.
* Exposes component attributes (e.g., activation capability, and
  activation pending) via sysfs under /sys/firmware/lfa/<UUID>/.
* 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/6.
Based on v7.4-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 v5:
- merge the timeout and the lock patch into original feature patch
- drop "name" attribute altogether
- rename firmware "image" to "component" everywhere
- add explanation of LFA concepts in top comment
- use switch/case for error strings and error mapping
- define error codes with negative numbers
- rename SMCCC register variable to from "reg" to "regs"
- don't open code bit masking
- use only one SMCCC register set where possible
- rewrite retry:/goto into do/while() loop
- make example IRQ to be edge triggered
- update activation_pending in pending_version_show()
- check SMCCC version to be v1.2 or higher
- check all SMCCC functions are implemented
- remove blank lines before return statements
- use UUID instead of GUID everywhere
- improve comments
- filter for ACPI notification 0x80 only
- update ABI doc to use "component", not "image"
- add activation_pending attribute
- fix IRQ/ACPI registration error handling
- add comments documenting lock requirements
- print LFA error number when printing error strings
- explicitly free IRQ in remove() routine
- reorder functions to avoid early empty prototype
- adjust SMCCC function ID names to match spec
- copy list of activatable images to avoid infinite loop
- update pending status when showing pending_version
- cast error returns to s64
- replace "N/A" error return with empty string

Changes in v4:
- streamline threaded IRQ handler (drop empty hard IRQ part)
- add lfa_to_linux_error() conversion function
- use Linux errno codes in more functions
- drop bogus LFA error numbers for -EAGAIN and -EBUSY
- clarify interrupt purpose and edge trigger requirement in binding
- honour signal related return from msleep_interruptible()
- call clean_fw_images_tree() first in driver teardown
- streamline ACPI and DT registration
- cache rendezvous status to avoid race during activation
- only trigger activation when writing 1/Y/true to sysfs file
- adapt to new version of Aneesh's SMCCC bus patches ([1])

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/20260914060511.277948-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 (4):
  dt-bindings: arm: Add Live Firmware Activation
  firmware: smccc: lfa: Add auto_activate sysfs file
  firmware: smccc: lfa: Register DT interrupt
  firmware: smccc: lfa: add sysfs ABI documentation

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

Vedashree Vidwans (1):
  firmware: smccc: lfa: Register ACPI notification

 Documentation/ABI/testing/sysfs-firmware-lfa  |  106 ++
 .../devicetree/bindings/arm/arm,lfa.yaml      |   50 +
 drivers/firmware/smccc/Kconfig                |   10 +
 drivers/firmware/smccc/Makefile               |    1 +
 drivers/firmware/smccc/lfa_fw.c               | 1057 +++++++++++++++++
 drivers/firmware/smccc/smccc.c                |    5 +
 include/linux/arm-smccc.h                     |   15 +
 7 files changed, 1244 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

-- 
2.43.0


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

* [PATCH v5 RESEND 1/6] dt-bindings: arm: Add Live Firmware Activation
  2026-11-02 16:54 [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support Andre Przywara
@ 2026-11-02 16:54 ` Andre Przywara
  2026-11-02 16:54 ` [PATCH v5 RESEND 2/6] firmware: smccc: Add support for Live Firmware Activation (LFA) Andre Przywara
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 19+ messages in thread
From: Andre Przywara @ 2026-11-02 16:54 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Salman Nabi, Vedashree Vidwans, Trilok Soni, Nirmoy Das, vsethi,
	Varun Wadekar, linux-arm-kernel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree

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>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
 .../devicetree/bindings/arm/arm,lfa.yaml      | 50 +++++++++++++++++++
 1 file changed, 50 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 0000000000000..de97dc88d0b15
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/arm,lfa.yaml
@@ -0,0 +1,50 @@
+# 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:
+      The notification interrupt for changed firmware image status. For
+      an out-of-band firmware update, some system entity would signal
+      the availability of a firmware update to the host OS via this interrupt.
+
+      This must be an edge-triggered IRQ.
+
+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_EDGE_RISING>;
+        };
+    };
+...
-- 
2.43.0


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

* [PATCH v5 RESEND 2/6] firmware: smccc: Add support for Live Firmware Activation (LFA)
  2026-11-02 16:54 [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support Andre Przywara
  2026-11-02 16:54 ` [PATCH v5 RESEND 1/6] dt-bindings: arm: Add Live Firmware Activation Andre Przywara
@ 2026-11-02 16:54 ` Andre Przywara
  2026-11-04 22:50   ` Vedashree Vidwans
  2026-11-05 15:56   ` sashiko-bot
  2026-11-02 16:54 ` [PATCH v5 RESEND 3/6] firmware: smccc: lfa: Register ACPI notification Andre Przywara
                   ` (4 subsequent siblings)
  6 siblings, 2 replies; 19+ messages in thread
From: Andre Przywara @ 2026-11-02 16:54 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Salman Nabi, Vedashree Vidwans, Trilok Soni, Nirmoy Das, vsethi,
	Varun Wadekar, linux-arm-kernel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree

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 UUID. 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 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.7
force_cpu_rendezvous:1
may_reset_cpu:0
pending_version:0.8
/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/

Co-developed-by: Vedashree Vidwans <vvidwans@nvidia.com>
Signed-off-by: Vedashree Vidwans <vvidwans@nvidia.com>
Signed-off-by: Salman Nabi <salman.nabi@arm.com>
Co-developed-by: Andre Przywara <andre.przywara@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 | 845 ++++++++++++++++++++++++++++++++
 drivers/firmware/smccc/smccc.c  |   5 +
 include/linux/arm-smccc.h       |  15 +
 5 files changed, 876 insertions(+)
 create mode 100644 drivers/firmware/smccc/lfa_fw.c

diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig
index 15e7466179a62..7fd646d515f86 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 68bbff1407b8b..cddf2c460ab9d 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 0000000000000..db2a7fe344d5d
--- /dev/null
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -0,0 +1,845 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025-2026 Arm Limited
+ *
+ * Driver for accessing the Arm "Live Firmware Activation" (LFA) services
+ * provided via SMCCC by platform firmware. This allows to activate certain
+ * updated firmware components at runtime, without a reboot, as described in
+ * Arm's DEN0147 specification:
+ * https://developer.arm.com/documentation/den0147/latest/
+ *
+ * An "LFA agent" is running in EL3, providing a list for activate-able
+ * firmware components via repeated LFA_GET_INVENTORY SMCCC calls. Those
+ * components are identified by a UUID, which this driver uses to name
+ * subdirectories under /sys/firmware/lfa. The properties and controls of each
+ * firmware component are exposed as sysfs files in this directory, for a list
+ * of them see: Documentation/ABI/testing/sysfs-firmware-lfa.
+ *
+ * Each firmware component sets the "activation pending" bit, if an update
+ * image for that component becomes available, either through some host
+ * initiated update process (UEFI capsule update), or via some out-of-band,
+ * BMC controlled update. The latter can be signalled via an interrupt or
+ * an ACPI notification.
+ * The actual activation process is split in two phases: LFA_PRIME prepares
+ * the activation, it can take longer and might require multiple calls to it.
+ * The real handover happens with LFA_ACTIVATE then. Some firmware components'
+ * update procedure might require to gather all CPUs in EL3 during the
+ * LFA_ACTIVATE call, to prevent them from doing any service calls to the
+ * firmware component being updated, but also to allow the updating firmware
+ * component to initialise itself on each available CPU. This is called
+ * "CPU rendezvous", some firmware components require that for their
+ * activation. This driver chooses to do so regardless, to prevent stray
+ * firmware accesses from the kernel during the update phase. This can be
+ * skipped by setting the force_cpu_rendezvous sysfs file to false.
+ */
+
+#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/rwsem.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)
+
+#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
+#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
+
+#define CASE_LFA_ERROR(name) 		case name: return #name
+
+static const char *lfa_error_string(s64 error)
+{
+	switch (error) {
+	CASE_LFA_ERROR(LFA_SUCCESS);
+	CASE_LFA_ERROR(LFA_NOT_SUPPORTED);
+	CASE_LFA_ERROR(LFA_BUSY);
+	CASE_LFA_ERROR(LFA_AUTH_ERROR);
+	CASE_LFA_ERROR(LFA_NO_MEMORY);
+	CASE_LFA_ERROR(LFA_CRITICAL_ERROR);
+	CASE_LFA_ERROR(LFA_DEVICE_ERROR);
+	CASE_LFA_ERROR(LFA_WRONG_STATE);
+	CASE_LFA_ERROR(LFA_INVALID_PARAMETERS);
+	CASE_LFA_ERROR(LFA_COMPONENT_WRONG_STATE);
+	CASE_LFA_ERROR(LFA_INVALID_ADDRESS);
+	CASE_LFA_ERROR(LFA_ACTIVATION_FAILED);
+	default:
+		return "UNKNOWN";
+	}
+}
+
+static int lfa_to_linux_errno(s64 lfa_error)
+{
+	switch (lfa_error) {
+	case LFA_SUCCESS:
+		return 0;
+	case LFA_NOT_SUPPORTED:
+		return -EOPNOTSUPP;
+	case LFA_BUSY:
+		return -EBUSY;
+	case LFA_AUTH_ERROR:
+		return -EPERM;
+	case LFA_NO_MEMORY:
+		return -ENOMEM;
+	case LFA_CRITICAL_ERROR:
+		return -EACCES;
+	case LFA_DEVICE_ERROR:
+		return -EIO;
+	case LFA_WRONG_STATE:
+		return -EPROTO;
+	case LFA_INVALID_PARAMETERS:
+		return -EINVAL;
+	case LFA_COMPONENT_WRONG_STATE:
+		return -EPROTO;
+	case LFA_INVALID_ADDRESS:
+		return -ENXIO;
+	case LFA_ACTIVATION_FAILED:
+		return -EIO;
+	default:
+		return -EINVAL;
+	}
+}
+
+enum component_attr_names {
+	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_component {
+	struct kobject kobj;
+	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;
+	bool use_cpu_rendezvous;
+	struct kobj_attribute comp_attrs[LFA_ATTR_NR_IMAGES];
+};
+
+static struct fw_component *kobj_to_fw_component(struct kobject *kobj)
+{
+	return container_of(kobj, struct fw_component, kobj);
+}
+
+/* A UUID split over two 64-bit registers */
+struct uuid_regs {
+	u64 uuid_lo;
+	u64 uuid_hi;
+};
+
+static struct kset *lfa_kset;
+static struct workqueue_struct *fw_comps_update_wq;
+static struct work_struct fw_comps_update_work;
+static struct attribute *component_default_attrs[LFA_ATTR_NR_IMAGES + 1];
+
+/*
+ * A successful component activation might change the number of available
+ * components, leading to a re-order and thus re-assignment of the sequence IDs.
+ * The lock protects the connection between a firmware component (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 comp_attr_group = {
+	.attrs = component_default_attrs,
+};
+
+static const struct attribute_group *comp_default_groups[] = {
+	&comp_attr_group,
+	NULL
+};
+
+static void component_release(struct kobject *kobj)
+{
+	struct fw_component *comp = kobj_to_fw_component(kobj);
+
+	kfree(comp);
+}
+
+static const struct kobj_type component_ktype = {
+	.release = component_release,
+	.sysfs_ops = &kobj_sysfs_ops,
+	.default_groups = comp_default_groups,
+};
+
+static void delete_fw_component_node(struct fw_component *comp)
+{
+	kobject_del(&comp->kobj);
+	kobject_put(&comp->kobj);
+}
+
+static void remove_invalid_fw_components(struct work_struct *work)
+{
+	struct kobject *kobj, *tmp;
+	struct list_head comps_to_delete = LIST_HEAD_INIT(comps_to_delete);
+
+	/*
+	 * After an activation, firmware components might have been removed
+	 * from the inventory. Move those components into a private temporary
+	 * list, where they can be safely destroyed separately, as kobject_del()
+	 * would be trying to take the same kset->list_lock.
+	 */
+	spin_lock(&lfa_kset->list_lock);
+	list_for_each_entry_safe(kobj, tmp, &lfa_kset->list, entry) {
+		struct fw_component *comp = kobj_to_fw_component(kobj);
+
+		if (comp->fw_seq_id == -1)
+			list_move_tail(&kobj->entry, &comps_to_delete);
+	}
+	spin_unlock(&lfa_kset->list_lock);
+
+	list_for_each_entry_safe(kobj, tmp, &comps_to_delete, entry) {
+		struct fw_component *comp = kobj_to_fw_component(kobj);
+
+		delete_fw_component_node(comp);
+	}
+}
+
+static void set_component_flags(struct fw_component *comp, int seq_id,
+				u32 comp_flags, u64 reg_current_ver,
+				u64 reg_pending_ver)
+{
+	comp->fw_seq_id = seq_id;
+	comp->current_version = reg_current_ver;
+	comp->pending_version = reg_pending_ver;
+	comp->activation_capable = !!(comp_flags & BIT(0));
+	comp->activation_pending = !!(comp_flags & BIT(1));
+	comp->may_reset_cpu = !!(comp_flags & BIT(2));
+	/* cpu_rendezvous_optional bit has inverse logic in the spec */
+	comp->cpu_rendezvous = !(comp_flags & BIT(3));
+}
+
+static int get_nr_lfa_components(void)
+{
+	struct arm_smccc_1_2_regs regs = {
+		.a0 = ARM_SMCCC_LFA_GET_INFO,
+		.a1 = 0,		 /* lfa_info_selector = 0 */
+	};
+
+	/* No need for the smc_lock, since no sequence IDs are involved. */
+	arm_smccc_1_2_invoke(&regs, &regs);
+	if (regs.a0 != LFA_SUCCESS)
+		return (s64)regs.a0;
+
+	return regs.a1;
+}
+
+static int update_fw_components_node(char *fw_uuid, int seq_id,
+				     u32 comp_flags, u64 reg_current_ver,
+				     u64 reg_pending_ver)
+{
+	struct fw_component *comp;
+	struct kobject *kobj;
+
+	/*
+	 * If a component is already in the components 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_component *comp = kobj_to_fw_component(kobj);
+
+			set_component_flags(comp, seq_id, comp_flags,
+					reg_current_ver, reg_pending_ver);
+			spin_unlock(&lfa_kset->list_lock);
+
+			return 0;
+		}
+	}
+	spin_unlock(&lfa_kset->list_lock);
+
+	comp = kzalloc_obj(*comp);
+	if (!comp)
+		return -ENOMEM;
+
+	comp->kobj.kset = lfa_kset;
+	comp->cpu_rendezvous_forced = true;
+	set_component_flags(comp, seq_id, comp_flags, reg_current_ver,
+			    reg_pending_ver);
+	if (kobject_init_and_add(&comp->kobj, &component_ktype, NULL,
+				 "%s", fw_uuid)) {
+		kobject_put(&comp->kobj);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/*
+ * Creates/updates the list of components provided by the firmware.
+ * Must be called with the smc_lock held if called outside of initialisation.
+ */
+static int update_fw_components_tree(void)
+{
+	struct arm_smccc_1_2_regs regs = { 0 }, res;
+	struct uuid_regs comp_uuid;
+	struct kobject *kobj;
+	char comp_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 components as the seq_ids and the
+	 * number of firmware components in the LFA agent may change after a
+	 * successful activation attempt. Negate all components flags as well.
+	 */
+	spin_lock(&lfa_kset->list_lock);
+	list_for_each_entry(kobj, &lfa_kset->list, entry) {
+		struct fw_component *comp = kobj_to_fw_component(kobj);
+
+		set_component_flags(comp, -1, 0b1000, 0, 0);
+	}
+	spin_unlock(&lfa_kset->list_lock);
+
+	regs.a0 = ARM_SMCCC_LFA_GET_INVENTORY;
+	for (int i = 0; i < num_of_components; i++) {
+		regs.a1 = i; /* fw_seq_id to be queried */
+		arm_smccc_1_2_invoke(&regs, &res);
+		if (res.a0 == LFA_SUCCESS) {
+			comp_uuid.uuid_lo = res.a1;
+			comp_uuid.uuid_hi = res.a2;
+
+			snprintf(comp_id_str, sizeof(comp_id_str), "%pUb",
+				 &comp_uuid);
+			ret = update_fw_components_node(comp_id_str, i, res.a3,
+							res.a4, res.a5);
+			if (ret)
+				return ret;
+		}
+	}
+
+	/*
+	 * Removing non-valid component 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_comps_update_wq, &fw_comps_update_work);
+
+	return 0;
+}
+
+static int lfa_cancel(void *data)
+{
+	struct fw_component *comp = data;
+	struct arm_smccc_1_2_regs regs = { 0 };
+
+	down_read(&smc_lock);
+	regs.a0 = ARM_SMCCC_LFA_CANCEL;
+	regs.a1 = comp->fw_seq_id;
+	arm_smccc_1_2_invoke(&regs, &regs);
+	up_read(&smc_lock);
+
+	/*
+	 * 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 (regs.a0 == LFA_SUCCESS) {
+		pr_info("Activation cancelled for component %s\n",
+			kobject_name(&comp->kobj));
+	} else {
+		pr_err("Activation not cancelled for component %s: %s (%ld)\n",
+		       kobject_name(&comp->kobj), lfa_error_string(regs.a0),
+		       regs.a0);
+		return -EINVAL;
+	}
+
+	return regs.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.
+ * Returns a Linux error code, not an LFA one.
+ */
+static int call_lfa_activate(void *data)
+{
+	/*
+	 * Use the cached value of the rendezvous status, to be consistent
+	 * with how we were called (via stop_machine() or not).
+	 * As we do not support updates requiring a CPU reset (yet),
+	 * we can skip the entry point and context ID parameters.
+	 */
+	struct fw_component *comp = data;
+	struct arm_smccc_1_2_regs regs = {
+		.a0 = ARM_SMCCC_LFA_ACTIVATE,
+		.a1 = comp->fw_seq_id,
+		.a2 = !comp->use_cpu_rendezvous,
+		.a3 = 0,	/* unused entry point */
+		.a4 = 0,	/* unused context ID */
+	};
+
+	touch_nmi_watchdog();
+	arm_smccc_1_2_invoke(&regs, &regs);
+	if ((s64)regs.a0 < 0)
+		return lfa_to_linux_errno((s64)regs.a0);
+
+	if (regs.a1 & LFA_ACTIVATE_CALL_AGAIN)
+		return -EAGAIN;
+
+	return 0;
+}
+
+static int activate_fw_component(struct fw_component *comp)
+{
+	ktime_t end = ktime_add_ms(ktime_get(), LFA_ACTIVATE_BUDGET_MS);
+	int ret;
+
+	do {
+		down_write(&smc_lock);
+		/*
+		 * cpu_rendezvous_forced is set by the administrator, via sysfs,
+		 * cpu_rendezvous is dictated by each firmware component.
+		 * All cores call into the firmware with the same arguments.
+		 */
+		comp->use_cpu_rendezvous = comp->cpu_rendezvous_forced ||
+					   comp->cpu_rendezvous;
+		if (comp->use_cpu_rendezvous) {
+			cpus_read_lock();
+			ret = stop_machine(call_lfa_activate, comp,
+					   cpu_online_mask);
+			cpus_read_unlock();
+		} else {
+			ret = call_lfa_activate(comp);
+		}
+		if (!ret)
+			update_fw_components_tree();
+		up_write(&smc_lock);
+
+		if (!ret)
+			return 0;
+
+		if (ret == -EAGAIN || ret == -EBUSY) {
+			if (ktime_before(ktime_get(), end)) {
+				if (msleep_interruptible(LFA_ACTIVATE_DELAY_MS))
+					ret = -EINTR;
+			} else {
+				ret = -ETIMEDOUT;
+			}
+		}
+	} while (ret == -EAGAIN || ret == -EBUSY);
+
+	lfa_cancel(comp);
+
+	pr_err("LFA_ACTIVATE for component %s failed\n",
+	       kobject_name(&comp->kobj));
+
+	return ret;
+}
+
+static int prime_fw_component(struct fw_component *comp)
+{
+	ktime_t end = ktime_add_ms(ktime_get(), LFA_PRIME_BUDGET_MS);
+	bool retry = false;
+	int ret = 0;
+
+	if (comp->may_reset_cpu) {
+		pr_err("CPU reset not supported by kernel driver\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * LFA_PRIME will return 1 in regs.a1 if the firmware priming
+	 * is still in progress. In that case LFA_PRIME will need to
+	 * be called again.
+	 * regs.a1 will become 0 once the prime process completes.
+	 */
+	do {
+		struct arm_smccc_1_2_regs regs = {
+			.a0 = ARM_SMCCC_LFA_PRIME,
+		};
+
+		touch_nmi_watchdog();
+		down_read(&smc_lock);
+		regs.a1 = comp->fw_seq_id;
+		arm_smccc_1_2_invoke(&regs, &regs);
+		up_read(&smc_lock);
+
+		if ((s64)regs.a0 < 0) {
+			pr_err("LFA_PRIME for component %s failed: %s (%ld)\n",
+			       kobject_name(&comp->kobj),
+			       lfa_error_string((s64)regs.a0), regs.a0);
+			return lfa_to_linux_errno((s64)regs.a0);
+		}
+
+		if ((regs.a1 & LFA_PRIME_CALL_AGAIN)) {
+			if (ktime_before(ktime_get(), end)) {
+				if (msleep_interruptible(LFA_PRIME_DELAY_MS)) {
+					ret = -EINTR;
+					break;
+				}
+			} else {
+				pr_err("LFA_PRIME for component %s timed out",
+				       kobject_name(&comp->kobj));
+				ret = -ETIMEDOUT;
+				break;
+			}
+			retry = true;
+		}
+	} while (retry);
+
+	if (ret)
+		lfa_cancel(comp);
+
+	return ret;
+}
+
+static ssize_t activation_capable_show(struct kobject *kobj,
+				       struct kobj_attribute *attr, char *buf)
+{
+	struct fw_component *comp = kobj_to_fw_component(kobj);
+
+	return sysfs_emit(buf, "%d\n", comp->activation_capable);
+}
+
+/* called with smc_lock held */
+static void _update_fw_component_pending(struct fw_component *comp)
+{
+	struct arm_smccc_1_2_regs regs = {
+		.a0 = ARM_SMCCC_LFA_GET_INVENTORY,
+		.a1 = comp->fw_seq_id,
+	};
+
+	arm_smccc_1_2_invoke(&regs, &regs);
+
+	if (regs.a0 == LFA_SUCCESS)
+		comp->activation_pending = !!(regs.a3 & BIT(1));
+}
+
+static void update_fw_component_pending(struct fw_component *comp)
+{
+	down_read(&smc_lock);
+	_update_fw_component_pending(comp);
+	up_read(&smc_lock);
+}
+
+static ssize_t activation_pending_show(struct kobject *kobj,
+				       struct kobj_attribute *attr, char *buf)
+{
+	struct fw_component *comp = kobj_to_fw_component(kobj);
+
+	/*
+	 * Activation pending status can change anytime thus we need to update
+	 * and return its current value
+	 */
+	update_fw_component_pending(comp);
+
+	return sysfs_emit(buf, "%d\n", comp->activation_pending);
+}
+
+static ssize_t may_reset_cpu_show(struct kobject *kobj,
+				  struct kobj_attribute *attr, char *buf)
+{
+	struct fw_component *comp = kobj_to_fw_component(kobj);
+
+	return sysfs_emit(buf, "%d\n", comp->may_reset_cpu);
+}
+
+static ssize_t cpu_rendezvous_show(struct kobject *kobj,
+				   struct kobj_attribute *attr, char *buf)
+{
+	struct fw_component *comp = kobj_to_fw_component(kobj);
+
+	return sysfs_emit(buf, "%d\n", comp->cpu_rendezvous);
+}
+
+static ssize_t force_cpu_rendezvous_store(struct kobject *kobj,
+					  struct kobj_attribute *attr,
+					  const char *buf, size_t count)
+{
+	struct fw_component *comp = kobj_to_fw_component(kobj);
+	int ret;
+
+	ret = kstrtobool(buf, &comp->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_component *comp = kobj_to_fw_component(kobj);
+
+	return sysfs_emit(buf, "%d\n", comp->cpu_rendezvous_forced);
+}
+
+static ssize_t current_version_show(struct kobject *kobj,
+				    struct kobj_attribute *attr, char *buf)
+{
+	struct fw_component *comp = kobj_to_fw_component(kobj);
+	u32 maj, min;
+
+	maj = upper_32_bits(comp->current_version);
+	min = lower_32_bits(comp->current_version);
+
+	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_component *comp = kobj_to_fw_component(kobj);
+	struct arm_smccc_1_2_regs regs = { 0 };
+
+	/*
+	 * Similar to activation pending, this value can change following an
+	 * update, we need to retrieve fresh info instead of stale information.
+	 * Update the activation_pending status on the way.
+	 */
+	down_read(&smc_lock);
+	regs.a0 = ARM_SMCCC_LFA_GET_INVENTORY;
+	regs.a1 = comp->fw_seq_id;
+	arm_smccc_1_2_invoke(&regs, &regs);
+	up_read(&smc_lock);
+	if (regs.a0 == LFA_SUCCESS) {
+		comp->activation_pending = !!(regs.a3 & BIT(1));
+		if (regs.a5 != 0 && comp->activation_pending) {
+			u32 maj, min;
+
+			comp->pending_version = regs.a5;
+			maj = upper_32_bits(regs.a5);
+			min = lower_32_bits(regs.a5);
+
+			return sysfs_emit(buf, "%u.%u\n", maj, min);
+		}
+
+		return 0;
+	}
+
+	return lfa_to_linux_errno((s64)regs.a0);
+}
+
+static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct fw_component *comp = kobj_to_fw_component(kobj);
+	bool activate;
+	int ret;
+
+	ret = kstrtobool(buf, &activate);
+	if (ret)
+		return ret;
+
+	if (!activate)
+		return count;
+
+	ret = prime_fw_component(comp);
+	if (ret)
+		return ret;
+
+	ret = activate_fw_component(comp);
+	if (ret)
+		return ret;
+
+	pr_info("%s: successfully activated\n", kobject_name(&comp->kobj));
+
+	return count;
+}
+
+static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr,
+			    const char *buf, size_t count)
+{
+	struct fw_component *comp = kobj_to_fw_component(kobj);
+	bool cancel;
+	int ret;
+
+	ret = kstrtobool(buf, &cancel);
+	if (ret)
+		return ret;
+
+	if (!cancel)
+		return count;
+
+	ret = lfa_cancel(comp);
+	if (ret != 0)
+		return ret;
+
+	return count;
+}
+
+static struct kobj_attribute comp_attrs_group[LFA_ATTR_NR_IMAGES] = {
+	[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_component_default_attrs(void)
+{
+	for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++)
+		component_default_attrs[i] = &comp_attrs_group[i].attr;
+	component_default_attrs[LFA_ATTR_NR_IMAGES] = NULL;
+}
+
+static void clean_fw_components_tree(void)
+{
+	struct kobject *kobj, *tmp;
+	struct list_head comp_to_delete;
+
+	INIT_LIST_HEAD(&comp_to_delete);
+
+	spin_lock(&lfa_kset->list_lock);
+	list_for_each_entry_safe(kobj, tmp, &lfa_kset->list, entry) {
+		list_move_tail(&kobj->entry, &comp_to_delete);
+	}
+	spin_unlock(&lfa_kset->list_lock);
+
+	list_for_each_entry_safe(kobj, tmp, &comp_to_delete, entry) {
+		struct fw_component *comp = kobj_to_fw_component(kobj);
+
+		delete_fw_component_node(comp);
+	}
+}
+
+static bool lfa_smccc_has_all_functions(void)
+{
+	struct arm_smccc_1_2_regs regs = { 0 }, res;
+	u32 fn_id;
+
+	regs.a0 = ARM_SMCCC_LFA_FEATURES;
+
+	for (fn_id = ARM_SMCCC_LFA_GET_INFO;
+	     fn_id <= ARM_SMCCC_LFA_CANCEL;
+	     fn_id++) {
+		regs.a1 = fn_id;
+		arm_smccc_1_2_invoke(&regs, &res);
+		if (res.a0)
+			return false;
+	}
+
+	return true;
+}
+
+static int lfa_smccc_probe(struct arm_smccc_device *sdev)
+{
+	struct arm_smccc_1_2_regs regs = { 0 };
+	int err;
+
+	/* The LFA spec requires SMCCC v1.2 or higher. */
+	if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2)
+		return -EOPNOTSUPP;
+
+	regs.a0 = ARM_SMCCC_LFA_VERSION;
+	arm_smccc_1_2_invoke(&regs, &regs);
+	if ((s32)regs.a0 == LFA_NOT_SUPPORTED)
+		return -ENODEV;
+
+	if (!lfa_smccc_has_all_functions())
+		return -EOPNOTSUPP;
+
+	pr_info("Live Firmware Activation: detected v%d.%d\n",
+		upper_16_bits(regs.a0), lower_16_bits(regs.a0));
+
+	fw_comps_update_wq = alloc_workqueue("fw_comps_update_wq",
+					     WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
+	if (!fw_comps_update_wq) {
+		pr_err("Live Firmware Activation: Failed to allocate workqueue.\n");
+		return -ENOMEM;
+	}
+	INIT_WORK(&fw_comps_update_work, remove_invalid_fw_components);
+
+	init_component_default_attrs();
+	lfa_kset = kset_create_and_add("lfa", NULL, firmware_kobj);
+	if (!lfa_kset) {
+		destroy_workqueue(fw_comps_update_wq);
+		return -ENOMEM;
+	}
+
+	init_rwsem(&smc_lock);
+
+	err = update_fw_components_tree();
+	if (err != 0) {
+		kset_unregister(lfa_kset);
+		destroy_workqueue(fw_comps_update_wq);
+	}
+
+	return err;
+}
+
+static void lfa_smccc_remove(struct arm_smccc_device *sdev)
+{
+	clean_fw_components_tree();
+	flush_workqueue(fw_comps_update_wq);
+	destroy_workqueue(fw_comps_update_wq);
+	kset_unregister(lfa_kset);
+}
+
+static const struct arm_smccc_device_id lfa_smccc_id_table[] = {
+	{ .func_id = ARM_SMCCC_LFA_VERSION },
+	{}
+};
+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 5dc768ac4143a..848d992141568 100644
--- a/drivers/firmware/smccc/smccc.c
+++ b/drivers/firmware/smccc/smccc.c
@@ -100,6 +100,11 @@ static const struct smccc_device_info smccc_devices[] __initconst = {
 		.requires_smc   = true,
 		.device_name    = "arm-rsi",
 	},
+	{
+		.func_id        = ARM_SMCCC_LFA_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 4de81848fe2ee..381d527c16a4c 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_VERSION		ARM_SMCCC_LFA_FN(0)
+#define ARM_SMCCC_LFA_FEATURES		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] 19+ messages in thread

* [PATCH v5 RESEND 3/6] firmware: smccc: lfa: Register ACPI notification
  2026-11-02 16:54 [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support Andre Przywara
  2026-11-02 16:54 ` [PATCH v5 RESEND 1/6] dt-bindings: arm: Add Live Firmware Activation Andre Przywara
  2026-11-02 16:54 ` [PATCH v5 RESEND 2/6] firmware: smccc: Add support for Live Firmware Activation (LFA) Andre Przywara
@ 2026-11-02 16:54 ` Andre Przywara
  2026-11-04 20:03   ` Nirmoy Das
  2026-11-05 16:14   ` sashiko-bot
  2026-11-02 16:54 ` [PATCH v5 RESEND 4/6] firmware: smccc: lfa: Add auto_activate sysfs file Andre Przywara
                   ` (3 subsequent siblings)
  6 siblings, 2 replies; 19+ messages in thread
From: Andre Przywara @ 2026-11-02 16:54 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Salman Nabi, Vedashree Vidwans, Trilok Soni, Nirmoy Das, vsethi,
	Varun Wadekar, linux-arm-kernel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree

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 | 154 +++++++++++++++++++++++++++++++-
 1 file changed, 153 insertions(+), 1 deletion(-)

diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
index db2a7fe344d5d..4b4daf79611bb 100644
--- a/drivers/firmware/smccc/lfa_fw.c
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -33,12 +33,14 @@
  * skipped by setting the force_cpu_rendezvous sysfs file to false.
  */
 
+#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>
@@ -49,11 +51,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
 
@@ -753,6 +757,135 @@ static void clean_fw_components_tree(void)
 	}
 }
 
+/*
+ * Iterate over all FW components and trigger activation of all activatible and
+ * pending components.
+ * Since we would need to re-enumerate the list of all components after a
+ * successful activation, and this could trigger infinite loops if a firmware
+ * component still appears as pending, we create an array of update candidates
+ * first, then activate each one of them.
+ */
+static void activate_pending_components(void)
+{
+	struct fw_component **all_comps;
+	struct fw_component *comp;
+	struct kobject *kobj;
+	int nr_comps = 0;
+	int ret, i;
+
+	down_read(&smc_lock);
+	spin_lock(&lfa_kset->list_lock);
+	all_comps = kmalloc_array(list_count_nodes(&lfa_kset->list),
+				  sizeof(*all_comps), GFP_NOWAIT);
+	if (!all_comps) {
+		spin_unlock(&lfa_kset->list_lock);
+		up_read(&smc_lock);
+		pr_warn("LFA: automatic activation failed: -ENOMEM\n");
+		return;
+	}
+
+	list_for_each_entry(kobj, &lfa_kset->list, entry) {
+		comp = kobj_to_fw_component(kobj);
+
+		if (comp->fw_seq_id == -1)
+			continue; /* Invalid FW component */
+
+		_update_fw_component_pending(comp);
+		if (comp->activation_capable && comp->activation_pending) {
+			kobject_get(&comp->kobj);
+			all_comps[nr_comps++] = comp;
+		}
+	}
+	spin_unlock(&lfa_kset->list_lock);
+	up_read(&smc_lock);
+
+	for (i = 0; i < nr_comps; i++) {
+		comp = all_comps[i];
+
+		ret = prime_fw_component(comp);
+		if (ret) {
+			pr_warn("LFA: LFA_PRIME failed for component %s\n",
+				kobject_name(&comp->kobj));
+			kobject_put(&comp->kobj);
+			continue;
+		}
+
+		ret = activate_fw_component(comp);
+		if (ret) {
+			pr_warn("LFA: LFA_ACTIVATE failed for component %s\n",
+				kobject_name(&comp->kobj));
+			kobject_put(&comp->kobj);
+			continue;
+		}
+
+		pr_info("%s: automatic activation succeeded\n",
+			kobject_name(&comp->kobj));
+		kobject_put(&comp->kobj);
+	}
+
+	kfree(all_comps);
+}
+
+#ifdef CONFIG_ACPI
+static void lfa_acpi_notify_handler(acpi_handle handle, u32 event, void *data)
+{
+	/* The spec explicitly mentions 0x80 only for the update event. */
+	if (event != 0x80)
+		return;
+
+	activate_pending_components();
+}
+
+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 bool lfa_smccc_has_all_functions(void)
 {
 	struct arm_smccc_1_2_regs regs = { 0 }, res;
@@ -813,13 +946,32 @@ static int lfa_smccc_probe(struct arm_smccc_device *sdev)
 	if (err != 0) {
 		kset_unregister(lfa_kset);
 		destroy_workqueue(fw_comps_update_wq);
+		return err;
+	}
+
+	if (!acpi_disabled) {
+		err = lfa_register_acpi(&sdev->dev);
+		if (!err)
+			pr_info("registered LFA ACPI notification\n");
+	}
+
+	/*
+	 * An ACPI notification or an interrupt are optional, but we must
+	 * handle errors that occurred during their setup.
+	 */
+	if (err != -ENODEV) {
+		kset_unregister(lfa_kset);
+		destroy_workqueue(fw_comps_update_wq);
+		return err;
 	}
 
-	return err;
+	return 0;
 }
 
 static void lfa_smccc_remove(struct arm_smccc_device *sdev)
 {
+	if (!acpi_disabled)
+		lfa_remove_acpi(&sdev->dev);
 	clean_fw_components_tree();
 	flush_workqueue(fw_comps_update_wq);
 	destroy_workqueue(fw_comps_update_wq);
-- 
2.43.0


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

* [PATCH v5 RESEND 4/6] firmware: smccc: lfa: Add auto_activate sysfs file
  2026-11-02 16:54 [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (2 preceding siblings ...)
  2026-11-02 16:54 ` [PATCH v5 RESEND 3/6] firmware: smccc: lfa: Register ACPI notification Andre Przywara
@ 2026-11-02 16:54 ` Andre Przywara
  2026-11-05 16:25   ` sashiko-bot
  2026-11-02 16:54 ` [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt Andre Przywara
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 19+ messages in thread
From: Andre Przywara @ 2026-11-02 16:54 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Salman Nabi, Vedashree Vidwans, Trilok Soni, Nirmoy Das, vsethi,
	Varun Wadekar, linux-arm-kernel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree

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 | 41 +++++++++++++++++++++++++++------
 1 file changed, 34 insertions(+), 7 deletions(-)

diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
index 4b4daf79611bb..65f28ffc9d108 100644
--- a/drivers/firmware/smccc/lfa_fw.c
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -148,6 +148,7 @@ enum component_attr_names {
 	LFA_ATTR_FORCE_CPU_RENDEZVOUS,
 	LFA_ATTR_ACTIVATE,
 	LFA_ATTR_CANCEL,
+	LFA_ATTR_AUTO_ACTIVATE,
 	LFA_ATTR_NR_IMAGES
 };
 
@@ -162,6 +163,7 @@ struct fw_component {
 	bool cpu_rendezvous;
 	bool cpu_rendezvous_forced;
 	bool use_cpu_rendezvous;
+	bool auto_activate;
 	struct kobj_attribute comp_attrs[LFA_ATTR_NR_IMAGES];
 };
 
@@ -306,6 +308,7 @@ static int update_fw_components_node(char *fw_uuid, int seq_id,
 
 	comp->kobj.kset = lfa_kset;
 	comp->cpu_rendezvous_forced = true;
+	comp->auto_activate = false;
 	set_component_flags(comp, seq_id, comp_flags, reg_current_ver,
 			    reg_pending_ver);
 	if (kobject_init_and_add(&comp->kobj, &component_ktype, NULL,
@@ -718,6 +721,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_component *comp = kobj_to_fw_component(kobj);
+	int ret;
+
+	ret = kstrtobool(buf, &comp->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_component *comp = kobj_to_fw_component(kobj);
+
+	return sysfs_emit(buf, "%d\n", comp->auto_activate);
+}
+
 static struct kobj_attribute comp_attrs_group[LFA_ATTR_NR_IMAGES] = {
 	[LFA_ATTR_CURRENT_VERSION]	= __ATTR_RO(current_version),
 	[LFA_ATTR_PENDING_VERSION]	= __ATTR_RO(pending_version),
@@ -727,7 +752,8 @@ static struct kobj_attribute comp_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_component_default_attrs(void)
@@ -759,11 +785,11 @@ static void clean_fw_components_tree(void)
 
 /*
  * Iterate over all FW components and trigger activation of all activatible and
- * pending components.
- * Since we would need to re-enumerate the list of all components after a
- * successful activation, and this could trigger infinite loops if a firmware
- * component still appears as pending, we create an array of update candidates
- * first, then activate each one of them.
+ * pending components, but only if automatic activation for that component is
+ * allowed. Since we would need to re-enumerate the list of all components
+ * after a successful activation, and this could trigger infinite loops if a
+ * firmware component still appears as pending, we create an array of update
+ * candidates first, then activate each one of them.
  */
 static void activate_pending_components(void)
 {
@@ -791,7 +817,8 @@ static void activate_pending_components(void)
 			continue; /* Invalid FW component */
 
 		_update_fw_component_pending(comp);
-		if (comp->activation_capable && comp->activation_pending) {
+		if (comp->activation_capable && comp->activation_pending &&
+		    comp->auto_activate) {
 			kobject_get(&comp->kobj);
 			all_comps[nr_comps++] = comp;
 		}
-- 
2.43.0


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

* [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt
  2026-11-02 16:54 [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (3 preceding siblings ...)
  2026-11-02 16:54 ` [PATCH v5 RESEND 4/6] firmware: smccc: lfa: Add auto_activate sysfs file Andre Przywara
@ 2026-11-02 16:54 ` Andre Przywara
  2026-11-03  4:20   ` kernel test robot
                     ` (3 more replies)
  2026-11-02 16:54 ` [PATCH v5 RESEND 6/6] firmware: smccc: lfa: add sysfs ABI documentation Andre Przywara
  2026-11-05  9:17 ` [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support Nirmoy Das
  6 siblings, 4 replies; 19+ messages in thread
From: Andre Przywara @ 2026-11-02 16:54 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Salman Nabi, Vedashree Vidwans, Trilok Soni, Nirmoy Das, vsethi,
	Varun Wadekar, linux-arm-kernel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree

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 | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
index 65f28ffc9d108..3ff368dca9497 100644
--- a/drivers/firmware/smccc/lfa_fw.c
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -46,6 +46,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/rwsem.h>
 #include <linux/stop_machine.h>
@@ -182,6 +184,7 @@ static struct kset *lfa_kset;
 static struct workqueue_struct *fw_comps_update_wq;
 static struct work_struct fw_comps_update_work;
 static struct attribute *component_default_attrs[LFA_ATTR_NR_IMAGES + 1];
+static unsigned int lfa_irq;
 
 /*
  * A successful component activation might change the number of available
@@ -913,6 +916,31 @@ static void lfa_remove_acpi(struct device *dev)
 }
 #endif
 
+static irqreturn_t lfa_irq_handler_thread(int irq, void *dev_id)
+{
+	activate_pending_components();
+
+	return IRQ_HANDLED;
+}
+
+static int lfa_register_dt(struct device *dev)
+{
+	struct device_node *np;
+
+	np = of_find_compatible_node(NULL, NULL, "arm,lfa");
+	if (!np)
+		return -ENODEV;
+
+	lfa_irq = irq_of_parse_and_map(np, 0);
+	of_node_put(np);
+	if (!lfa_irq)
+		return -ENODEV;
+
+	return devm_request_threaded_irq(dev, lfa_irq, NULL,
+					 lfa_irq_handler_thread, IRQF_COND_ONESHOT,
+					 NULL, NULL);
+}
+
 static bool lfa_smccc_has_all_functions(void)
 {
 	struct arm_smccc_1_2_regs regs = { 0 }, res;
@@ -980,6 +1008,10 @@ static int lfa_smccc_probe(struct arm_smccc_device *sdev)
 		err = lfa_register_acpi(&sdev->dev);
 		if (!err)
 			pr_info("registered LFA ACPI notification\n");
+	} else {
+		err = lfa_register_dt(&sdev->dev);
+		if (!err)
+			pr_info("registered LFA DT notification interrupt\n");
 	}
 
 	/*
@@ -999,6 +1031,7 @@ static void lfa_smccc_remove(struct arm_smccc_device *sdev)
 {
 	if (!acpi_disabled)
 		lfa_remove_acpi(&sdev->dev);
+	devm_free_irq(&sdev->dev, lfa_irq, NULL);
 	clean_fw_components_tree();
 	flush_workqueue(fw_comps_update_wq);
 	destroy_workqueue(fw_comps_update_wq);
-- 
2.43.0


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

* [PATCH v5 RESEND 6/6] firmware: smccc: lfa: add sysfs ABI documentation
  2026-11-02 16:54 [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (4 preceding siblings ...)
  2026-11-02 16:54 ` [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt Andre Przywara
@ 2026-11-02 16:54 ` Andre Przywara
  2026-11-05 16:53   ` sashiko-bot
  2026-11-05  9:17 ` [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support Nirmoy Das
  6 siblings, 1 reply; 19+ messages in thread
From: Andre Przywara @ 2026-11-02 16:54 UTC (permalink / raw)
  To: Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Salman Nabi, Vedashree Vidwans, Trilok Soni, Nirmoy Das, vsethi,
	Varun Wadekar, linux-arm-kernel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree

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 0000000000000..4e93a1aea91f7
--- /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 mechanism), then,
+		if supported, can be activated immediately, without rebooting
+		the system.
+		This directory contains one subdirectory for each of the LFA
+		capable firmware components supported by the agent, identified
+		by its UUID. Within each directory, the same set of sysfs files
+		provides access to the properties and actions for this
+		particular firmware component.
+
+What:		/sys/firmware/lfa/<UUID>/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/<UUID>/activation_capable
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RO) A boolean value to show whether this firmware component is
+		capable of being activated.
+
+What:		/sys/firmware/lfa/<UUID>/activation_pending
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RO) A boolean value to show whether this firmware component
+		has an updated image ready to be activated.
+
+What:		/sys/firmware/lfa/<UUID>/auto_activate
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RW) Write a boolean 'true' value to allow this firmware
+		component 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/<UUID>/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/<UUID>/cpu_rendezvous
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RO) A boolean value to show whether this component 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/<UUID>/current_version
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RO) Shows the currently running version of that firmware
+		component, as reported by the agent (if supported).
+
+What:		/sys/firmware/lfa/<UUID>/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 components 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/<UUID>/may_reset_cpu
+Date:		May 2026
+Contact:	Andre Przywara <andre.przywara@arm.com>
+Description:
+		(RO) A boolean value to show whether this firmware component
+		requires a CPU reset during the activation.
+		Currently this type of firmware component is not supported by
+		the kernel driver, as we cannot handle a CPU reset.
+
+What:		/sys/firmware/lfa/<UUID>/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 LFA agent (if supported).
+		If not update image is provided, this returns zero bytes.
-- 
2.43.0


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

* Re: [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt
  2026-11-02 16:54 ` [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt Andre Przywara
@ 2026-11-03  4:20   ` kernel test robot
  2026-11-03  7:50   ` kernel test robot
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 19+ messages in thread
From: kernel test robot @ 2026-11-03  4:20 UTC (permalink / raw)
  To: Andre Przywara, Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: oe-kbuild-all, Salman Nabi, Vedashree Vidwans, Trilok Soni,
	Nirmoy Das, vsethi, Varun Wadekar, linux-arm-kernel, linux-kernel,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree

Hi Andre,

kernel test robot noticed the following build warnings:

[auto build test WARNING on robh/for-next]
[also build test WARNING on arm64/for-next/core linus/master next-20261030]
[cannot apply to arm/for-next arm/fixes clk/clk-next kvmarm/next rockchip/for-next soc/for-next nferre-at91/at91-next shawnguo/for-next v7.3]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Andre-Przywara/dt-bindings-arm-Add-Live-Firmware-Activation/20261102-175415
base:   https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
patch link:    https://lore.kernel.org/r/20261102165421.3651202-6-andre.przywara%40arm.com
patch subject: [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt
config: arm64-randconfig-1000-20261103 (https://download.01.org/0day-ci/archive/20261103/202611031233.vQxtNp7l-lkp@intel.com/config)
compiler: aarch64-linux-gcc (GCC) 13.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261103/202611031233.vQxtNp7l-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202611031233.vQxtNp7l-lkp@intel.com/

All warnings (new ones prefixed by >>):

   drivers/firmware/smccc/lfa_fw.c: In function 'lfa_register_dt':
   drivers/firmware/smccc/lfa_fw.c:939:16: error: implicit declaration of function 'devm_request_threaded_irq'; did you mean 'devm_request_region'? [-Werror=implicit-function-declaration]
     939 |         return devm_request_threaded_irq(dev, lfa_irq, NULL,
         |                ^~~~~~~~~~~~~~~~~~~~~~~~~
         |                devm_request_region
   drivers/firmware/smccc/lfa_fw.c:940:66: error: 'IRQF_COND_ONESHOT' undeclared (first use in this function)
     940 |                                          lfa_irq_handler_thread, IRQF_COND_ONESHOT,
         |                                                                  ^~~~~~~~~~~~~~~~~
   drivers/firmware/smccc/lfa_fw.c:940:66: note: each undeclared identifier is reported only once for each function it appears in
   drivers/firmware/smccc/lfa_fw.c: In function 'lfa_smccc_remove':
   drivers/firmware/smccc/lfa_fw.c:1034:9: error: implicit declaration of function 'devm_free_irq'; did you mean 'devm_free_pages'? [-Werror=implicit-function-declaration]
    1034 |         devm_free_irq(&sdev->dev, lfa_irq, NULL);
         |         ^~~~~~~~~~~~~
         |         devm_free_pages
   drivers/firmware/smccc/lfa_fw.c: In function 'lfa_register_dt':
>> drivers/firmware/smccc/lfa_fw.c:942:1: warning: control reaches end of non-void function [-Wreturn-type]
     942 | }
         | ^
   cc1: some warnings being treated as errors


vim +942 drivers/firmware/smccc/lfa_fw.c

   925	
   926	static int lfa_register_dt(struct device *dev)
   927	{
   928		struct device_node *np;
   929	
   930		np = of_find_compatible_node(NULL, NULL, "arm,lfa");
   931		if (!np)
   932			return -ENODEV;
   933	
   934		lfa_irq = irq_of_parse_and_map(np, 0);
   935		of_node_put(np);
   936		if (!lfa_irq)
   937			return -ENODEV;
   938	
   939		return devm_request_threaded_irq(dev, lfa_irq, NULL,
   940						 lfa_irq_handler_thread, IRQF_COND_ONESHOT,
   941						 NULL, NULL);
 > 942	}
   943	

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt
  2026-11-02 16:54 ` [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt Andre Przywara
  2026-11-03  4:20   ` kernel test robot
@ 2026-11-03  7:50   ` kernel test robot
  2026-11-03  9:25   ` kernel test robot
  2026-11-05 16:45   ` sashiko-bot
  3 siblings, 0 replies; 19+ messages in thread
From: kernel test robot @ 2026-11-03  7:50 UTC (permalink / raw)
  To: Andre Przywara, Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: oe-kbuild-all, Salman Nabi, Vedashree Vidwans, Trilok Soni,
	Nirmoy Das, vsethi, Varun Wadekar, linux-arm-kernel, linux-kernel,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree

Hi Andre,

kernel test robot noticed the following build errors:

[auto build test ERROR on robh/for-next]
[also build test ERROR on arm64/for-next/core linus/master next-20261030]
[cannot apply to arm/for-next arm/fixes clk/clk-next kvmarm/next rockchip/for-next soc/for-next nferre-at91/at91-next shawnguo/for-next v7.3]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Andre-Przywara/dt-bindings-arm-Add-Live-Firmware-Activation/20261102-175415
base:   https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
patch link:    https://lore.kernel.org/r/20261102165421.3651202-6-andre.przywara%40arm.com
patch subject: [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt
config: arm64-randconfig-1000-20261103 (https://download.01.org/0day-ci/archive/20261103/202611031517.aikxJm6o-lkp@intel.com/config)
compiler: aarch64-linux-gcc (GCC) 13.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261103/202611031517.aikxJm6o-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202611031517.aikxJm6o-lkp@intel.com/

All errors (new ones prefixed by >>):

   drivers/firmware/smccc/lfa_fw.c: In function 'lfa_register_dt':
>> drivers/firmware/smccc/lfa_fw.c:939:16: error: implicit declaration of function 'devm_request_threaded_irq'; did you mean 'devm_request_region'? [-Werror=implicit-function-declaration]
     939 |         return devm_request_threaded_irq(dev, lfa_irq, NULL,
         |                ^~~~~~~~~~~~~~~~~~~~~~~~~
         |                devm_request_region
>> drivers/firmware/smccc/lfa_fw.c:940:66: error: 'IRQF_COND_ONESHOT' undeclared (first use in this function)
     940 |                                          lfa_irq_handler_thread, IRQF_COND_ONESHOT,
         |                                                                  ^~~~~~~~~~~~~~~~~
   drivers/firmware/smccc/lfa_fw.c:940:66: note: each undeclared identifier is reported only once for each function it appears in
   drivers/firmware/smccc/lfa_fw.c: In function 'lfa_smccc_remove':
>> drivers/firmware/smccc/lfa_fw.c:1034:9: error: implicit declaration of function 'devm_free_irq'; did you mean 'devm_free_pages'? [-Werror=implicit-function-declaration]
    1034 |         devm_free_irq(&sdev->dev, lfa_irq, NULL);
         |         ^~~~~~~~~~~~~
         |         devm_free_pages
   drivers/firmware/smccc/lfa_fw.c: In function 'lfa_register_dt':
   drivers/firmware/smccc/lfa_fw.c:942:1: warning: control reaches end of non-void function [-Wreturn-type]
     942 | }
         | ^
   cc1: some warnings being treated as errors


vim +939 drivers/firmware/smccc/lfa_fw.c

   925	
   926	static int lfa_register_dt(struct device *dev)
   927	{
   928		struct device_node *np;
   929	
   930		np = of_find_compatible_node(NULL, NULL, "arm,lfa");
   931		if (!np)
   932			return -ENODEV;
   933	
   934		lfa_irq = irq_of_parse_and_map(np, 0);
   935		of_node_put(np);
   936		if (!lfa_irq)
   937			return -ENODEV;
   938	
 > 939		return devm_request_threaded_irq(dev, lfa_irq, NULL,
 > 940						 lfa_irq_handler_thread, IRQF_COND_ONESHOT,
   941						 NULL, NULL);
   942	}
   943	
   944	static bool lfa_smccc_has_all_functions(void)
   945	{
   946		struct arm_smccc_1_2_regs regs = { 0 }, res;
   947		u32 fn_id;
   948	
   949		regs.a0 = ARM_SMCCC_LFA_FEATURES;
   950	
   951		for (fn_id = ARM_SMCCC_LFA_GET_INFO;
   952		     fn_id <= ARM_SMCCC_LFA_CANCEL;
   953		     fn_id++) {
   954			regs.a1 = fn_id;
   955			arm_smccc_1_2_invoke(&regs, &res);
   956			if (res.a0)
   957				return false;
   958		}
   959	
   960		return true;
   961	}
   962	
   963	static int lfa_smccc_probe(struct arm_smccc_device *sdev)
   964	{
   965		struct arm_smccc_1_2_regs regs = { 0 };
   966		int err;
   967	
   968		/* The LFA spec requires SMCCC v1.2 or higher. */
   969		if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2)
   970			return -EOPNOTSUPP;
   971	
   972		regs.a0 = ARM_SMCCC_LFA_VERSION;
   973		arm_smccc_1_2_invoke(&regs, &regs);
   974		if ((s32)regs.a0 == LFA_NOT_SUPPORTED)
   975			return -ENODEV;
   976	
   977		if (!lfa_smccc_has_all_functions())
   978			return -EOPNOTSUPP;
   979	
   980		pr_info("Live Firmware Activation: detected v%d.%d\n",
   981			upper_16_bits(regs.a0), lower_16_bits(regs.a0));
   982	
   983		fw_comps_update_wq = alloc_workqueue("fw_comps_update_wq",
   984						     WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
   985		if (!fw_comps_update_wq) {
   986			pr_err("Live Firmware Activation: Failed to allocate workqueue.\n");
   987			return -ENOMEM;
   988		}
   989		INIT_WORK(&fw_comps_update_work, remove_invalid_fw_components);
   990	
   991		init_component_default_attrs();
   992		lfa_kset = kset_create_and_add("lfa", NULL, firmware_kobj);
   993		if (!lfa_kset) {
   994			destroy_workqueue(fw_comps_update_wq);
   995			return -ENOMEM;
   996		}
   997	
   998		init_rwsem(&smc_lock);
   999	
  1000		err = update_fw_components_tree();
  1001		if (err != 0) {
  1002			kset_unregister(lfa_kset);
  1003			destroy_workqueue(fw_comps_update_wq);
  1004			return err;
  1005		}
  1006	
  1007		if (!acpi_disabled) {
  1008			err = lfa_register_acpi(&sdev->dev);
  1009			if (!err)
  1010				pr_info("registered LFA ACPI notification\n");
  1011		} else {
  1012			err = lfa_register_dt(&sdev->dev);
  1013			if (!err)
  1014				pr_info("registered LFA DT notification interrupt\n");
  1015		}
  1016	
  1017		/*
  1018		 * An ACPI notification or an interrupt are optional, but we must
  1019		 * handle errors that occurred during their setup.
  1020		 */
  1021		if (err != -ENODEV) {
  1022			kset_unregister(lfa_kset);
  1023			destroy_workqueue(fw_comps_update_wq);
  1024			return err;
  1025		}
  1026	
  1027		return 0;
  1028	}
  1029	
  1030	static void lfa_smccc_remove(struct arm_smccc_device *sdev)
  1031	{
  1032		if (!acpi_disabled)
  1033			lfa_remove_acpi(&sdev->dev);
> 1034		devm_free_irq(&sdev->dev, lfa_irq, NULL);
  1035		clean_fw_components_tree();
  1036		flush_workqueue(fw_comps_update_wq);
  1037		destroy_workqueue(fw_comps_update_wq);
  1038		kset_unregister(lfa_kset);
  1039	}
  1040	

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt
  2026-11-02 16:54 ` [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt Andre Przywara
  2026-11-03  4:20   ` kernel test robot
  2026-11-03  7:50   ` kernel test robot
@ 2026-11-03  9:25   ` kernel test robot
  2026-11-05 16:45   ` sashiko-bot
  3 siblings, 0 replies; 19+ messages in thread
From: kernel test robot @ 2026-11-03  9:25 UTC (permalink / raw)
  To: Andre Przywara, Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: llvm, oe-kbuild-all, Salman Nabi, Vedashree Vidwans, Trilok Soni,
	Nirmoy Das, vsethi, Varun Wadekar, linux-arm-kernel, linux-kernel,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree

Hi Andre,

kernel test robot noticed the following build errors:

[auto build test ERROR on robh/for-next]
[also build test ERROR on arm64/for-next/core linus/master next-20261030]
[cannot apply to arm/for-next arm/fixes clk/clk-next kvmarm/next rockchip/for-next soc/for-next v7.3]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Andre-Przywara/dt-bindings-arm-Add-Live-Firmware-Activation/20261102-175415
base:   https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
patch link:    https://lore.kernel.org/r/20261102165421.3651202-6-andre.przywara%40arm.com
patch subject: [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt
config: arm64-randconfig-1001-20261103 (https://download.01.org/0day-ci/archive/20261103/202611031726.J5nXvG8V-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261103/202611031726.J5nXvG8V-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202611031726.J5nXvG8V-lkp@intel.com/

All errors (new ones prefixed by >>):

>> drivers/firmware/smccc/lfa_fw.c:939:9: error: call to undeclared function 'devm_request_threaded_irq'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     939 |         return devm_request_threaded_irq(dev, lfa_irq, NULL,
         |                ^
>> drivers/firmware/smccc/lfa_fw.c:940:31: error: use of undeclared identifier 'IRQF_COND_ONESHOT'
     940 |                                          lfa_irq_handler_thread, IRQF_COND_ONESHOT,
         |                                                                  ^
>> drivers/firmware/smccc/lfa_fw.c:1034:2: error: call to undeclared function 'devm_free_irq'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
    1034 |         devm_free_irq(&sdev->dev, lfa_irq, NULL);
         |         ^
   3 errors generated.


vim +/devm_request_threaded_irq +939 drivers/firmware/smccc/lfa_fw.c

   925	
   926	static int lfa_register_dt(struct device *dev)
   927	{
   928		struct device_node *np;
   929	
   930		np = of_find_compatible_node(NULL, NULL, "arm,lfa");
   931		if (!np)
   932			return -ENODEV;
   933	
   934		lfa_irq = irq_of_parse_and_map(np, 0);
   935		of_node_put(np);
   936		if (!lfa_irq)
   937			return -ENODEV;
   938	
 > 939		return devm_request_threaded_irq(dev, lfa_irq, NULL,
 > 940						 lfa_irq_handler_thread, IRQF_COND_ONESHOT,
   941						 NULL, NULL);
   942	}
   943	
   944	static bool lfa_smccc_has_all_functions(void)
   945	{
   946		struct arm_smccc_1_2_regs regs = { 0 }, res;
   947		u32 fn_id;
   948	
   949		regs.a0 = ARM_SMCCC_LFA_FEATURES;
   950	
   951		for (fn_id = ARM_SMCCC_LFA_GET_INFO;
   952		     fn_id <= ARM_SMCCC_LFA_CANCEL;
   953		     fn_id++) {
   954			regs.a1 = fn_id;
   955			arm_smccc_1_2_invoke(&regs, &res);
   956			if (res.a0)
   957				return false;
   958		}
   959	
   960		return true;
   961	}
   962	
   963	static int lfa_smccc_probe(struct arm_smccc_device *sdev)
   964	{
   965		struct arm_smccc_1_2_regs regs = { 0 };
   966		int err;
   967	
   968		/* The LFA spec requires SMCCC v1.2 or higher. */
   969		if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2)
   970			return -EOPNOTSUPP;
   971	
   972		regs.a0 = ARM_SMCCC_LFA_VERSION;
   973		arm_smccc_1_2_invoke(&regs, &regs);
   974		if ((s32)regs.a0 == LFA_NOT_SUPPORTED)
   975			return -ENODEV;
   976	
   977		if (!lfa_smccc_has_all_functions())
   978			return -EOPNOTSUPP;
   979	
   980		pr_info("Live Firmware Activation: detected v%d.%d\n",
   981			upper_16_bits(regs.a0), lower_16_bits(regs.a0));
   982	
   983		fw_comps_update_wq = alloc_workqueue("fw_comps_update_wq",
   984						     WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
   985		if (!fw_comps_update_wq) {
   986			pr_err("Live Firmware Activation: Failed to allocate workqueue.\n");
   987			return -ENOMEM;
   988		}
   989		INIT_WORK(&fw_comps_update_work, remove_invalid_fw_components);
   990	
   991		init_component_default_attrs();
   992		lfa_kset = kset_create_and_add("lfa", NULL, firmware_kobj);
   993		if (!lfa_kset) {
   994			destroy_workqueue(fw_comps_update_wq);
   995			return -ENOMEM;
   996		}
   997	
   998		init_rwsem(&smc_lock);
   999	
  1000		err = update_fw_components_tree();
  1001		if (err != 0) {
  1002			kset_unregister(lfa_kset);
  1003			destroy_workqueue(fw_comps_update_wq);
  1004			return err;
  1005		}
  1006	
  1007		if (!acpi_disabled) {
  1008			err = lfa_register_acpi(&sdev->dev);
  1009			if (!err)
  1010				pr_info("registered LFA ACPI notification\n");
  1011		} else {
  1012			err = lfa_register_dt(&sdev->dev);
  1013			if (!err)
  1014				pr_info("registered LFA DT notification interrupt\n");
  1015		}
  1016	
  1017		/*
  1018		 * An ACPI notification or an interrupt are optional, but we must
  1019		 * handle errors that occurred during their setup.
  1020		 */
  1021		if (err != -ENODEV) {
  1022			kset_unregister(lfa_kset);
  1023			destroy_workqueue(fw_comps_update_wq);
  1024			return err;
  1025		}
  1026	
  1027		return 0;
  1028	}
  1029	
  1030	static void lfa_smccc_remove(struct arm_smccc_device *sdev)
  1031	{
  1032		if (!acpi_disabled)
  1033			lfa_remove_acpi(&sdev->dev);
> 1034		devm_free_irq(&sdev->dev, lfa_irq, NULL);
  1035		clean_fw_components_tree();
  1036		flush_workqueue(fw_comps_update_wq);
  1037		destroy_workqueue(fw_comps_update_wq);
  1038		kset_unregister(lfa_kset);
  1039	}
  1040	

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH v5 RESEND 3/6] firmware: smccc: lfa: Register ACPI notification
  2026-11-02 16:54 ` [PATCH v5 RESEND 3/6] firmware: smccc: lfa: Register ACPI notification Andre Przywara
@ 2026-11-04 20:03   ` Nirmoy Das
  2026-11-04 23:02     ` Vedashree Vidwans
  2026-11-05 16:14   ` sashiko-bot
  1 sibling, 1 reply; 19+ messages in thread
From: Nirmoy Das @ 2026-11-04 20:03 UTC (permalink / raw)
  To: Andre Przywara, Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Salman Nabi, Vedashree Vidwans, Trilok Soni, vsethi,
	Varun Wadekar, linux-arm-kernel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree

Hi Andre

On 02.11.26 17:54, Andre Przywara wrote:
> 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 | 154 +++++++++++++++++++++++++++++++-
>   1 file changed, 153 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
> index db2a7fe344d5d..4b4daf79611bb 100644
> --- a/drivers/firmware/smccc/lfa_fw.c
> +++ b/drivers/firmware/smccc/lfa_fw.c
> @@ -33,12 +33,14 @@
>    * skipped by setting the force_cpu_rendezvous sysfs file to false.
>    */
>   
> +#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>
> @@ -49,11 +51,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
>   
> @@ -753,6 +757,135 @@ static void clean_fw_components_tree(void)
>   	}
>   }
>   
> +/*
> + * Iterate over all FW components and trigger activation of all activatible and
> + * pending components.
> + * Since we would need to re-enumerate the list of all components after a
> + * successful activation, and this could trigger infinite loops if a firmware
> + * component still appears as pending, we create an array of update candidates
> + * first, then activate each one of them.
> + */
> +static void activate_pending_components(void)
> +{
> +	struct fw_component **all_comps;
> +	struct fw_component *comp;
> +	struct kobject *kobj;
> +	int nr_comps = 0;
> +	int ret, i;
> +
> +	down_read(&smc_lock);
> +	spin_lock(&lfa_kset->list_lock);
> +	all_comps = kmalloc_array(list_count_nodes(&lfa_kset->list),
> +				  sizeof(*all_comps), GFP_NOWAIT);
> +	if (!all_comps) {
> +		spin_unlock(&lfa_kset->list_lock);
> +		up_read(&smc_lock);
> +		pr_warn("LFA: automatic activation failed: -ENOMEM\n");
> +		return;
> +	}
> +
> +	list_for_each_entry(kobj, &lfa_kset->list, entry) {
> +		comp = kobj_to_fw_component(kobj);
> +
> +		if (comp->fw_seq_id == -1)
> +			continue; /* Invalid FW component */
> +
> +		_update_fw_component_pending(comp);
> +		if (comp->activation_capable && comp->activation_pending) {
> +			kobject_get(&comp->kobj);
> +			all_comps[nr_comps++] = comp;
> +		}
> +	}
> +	spin_unlock(&lfa_kset->list_lock);
> +	up_read(&smc_lock);
> +
> +	for (i = 0; i < nr_comps; i++) {
> +		comp = all_comps[i];
> +
> +		ret = prime_fw_component(comp);
> +		if (ret) {
> +			pr_warn("LFA: LFA_PRIME failed for component %s\n",
> +				kobject_name(&comp->kobj));
> +			kobject_put(&comp->kobj);
> +			continue;
> +		}
> +
> +		ret = activate_fw_component(comp);
> +		if (ret) {
> +			pr_warn("LFA: LFA_ACTIVATE failed for component %s\n",
> +				kobject_name(&comp->kobj));
> +			kobject_put(&comp->kobj);
> +			continue;
> +		}
> +
> +		pr_info("%s: automatic activation succeeded\n",
> +			kobject_name(&comp->kobj));
> +		kobject_put(&comp->kobj);
> +	}
> +
> +	kfree(all_comps);
> +}
> +
> +#ifdef CONFIG_ACPI
> +static void lfa_acpi_notify_handler(acpi_handle handle, u32 event, void *data)
> +{
> +	/* The spec explicitly mentions 0x80 only for the update event. */
> +	if (event != 0x80)
> +		return;
> +
> +	activate_pending_components();
> +}
> +
> +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 bool lfa_smccc_has_all_functions(void)
>   {
>   	struct arm_smccc_1_2_regs regs = { 0 }, res;
> @@ -813,13 +946,32 @@ static int lfa_smccc_probe(struct arm_smccc_device *sdev)
>   	if (err != 0) {
>   		kset_unregister(lfa_kset);
>   		destroy_workqueue(fw_comps_update_wq);
> +		return err;
> +	}
> +
> +	if (!acpi_disabled) {
> +		err = lfa_register_acpi(&sdev->dev);
> +		if (!err)
> +			pr_info("registered LFA ACPI notification\n");
> +	}
> +
> +	/*
> +	 * An ACPI notification or an interrupt are optional, but we must
> +	 * handle errors that occurred during their setup.
> +	 */
> +	if (err != -ENODEV) {

On successful registration, err is zero, so this condition still 
unregisters the inventory and destroys the workqueue before returning 
success. Should it be:

if (err && err != -ENODEV) {


Regards,

Nirmoy

> +		kset_unregister(lfa_kset);
> +		destroy_workqueue(fw_comps_update_wq);
> +		return err;
>   	}
>   
> -	return err;
> +	return 0;
>   }
>   
>   static void lfa_smccc_remove(struct arm_smccc_device *sdev)
>   {
> +	if (!acpi_disabled)
> +		lfa_remove_acpi(&sdev->dev);
>   	clean_fw_components_tree();
>   	flush_workqueue(fw_comps_update_wq);
>   	destroy_workqueue(fw_comps_update_wq);

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

* Re: [PATCH v5 RESEND 2/6] firmware: smccc: Add support for Live Firmware Activation (LFA)
  2026-11-02 16:54 ` [PATCH v5 RESEND 2/6] firmware: smccc: Add support for Live Firmware Activation (LFA) Andre Przywara
@ 2026-11-04 22:50   ` Vedashree Vidwans
  2026-11-05 15:56   ` sashiko-bot
  1 sibling, 0 replies; 19+ messages in thread
From: Vedashree Vidwans @ 2026-11-04 22:50 UTC (permalink / raw)
  To: Andre Przywara, Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Salman Nabi, Trilok Soni, Nirmoy Das, vsethi, Varun Wadekar,
	linux-arm-kernel, linux-kernel, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, devicetree

Hi Andre,

I have a few comments about retry handling and inventory lifetime.

On 11/2/26 08:54, Andre Przywara wrote:
> 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 UUID. 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 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.7
> force_cpu_rendezvous:1
> may_reset_cpu:0
> pending_version:0.8
> /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/
>
> Co-developed-by: Vedashree Vidwans <vvidwans@nvidia.com>
> Signed-off-by: Vedashree Vidwans <vvidwans@nvidia.com>
> Signed-off-by: Salman Nabi <salman.nabi@arm.com>
> Co-developed-by: Andre Przywara <andre.przywara@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 | 845 ++++++++++++++++++++++++++++++++
>   drivers/firmware/smccc/smccc.c  |   5 +
>   include/linux/arm-smccc.h       |  15 +
>   5 files changed, 876 insertions(+)
>   create mode 100644 drivers/firmware/smccc/lfa_fw.c
>
> diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig
> index 15e7466179a62..7fd646d515f86 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 68bbff1407b8b..cddf2c460ab9d 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 0000000000000..db2a7fe344d5d
> --- /dev/null
> +++ b/drivers/firmware/smccc/lfa_fw.c
> @@ -0,0 +1,845 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025-2026 Arm Limited
> + *
> + * Driver for accessing the Arm "Live Firmware Activation" (LFA) services
> + * provided via SMCCC by platform firmware. This allows to activate certain
> + * updated firmware components at runtime, without a reboot, as described in
> + * Arm's DEN0147 specification:
> + * https://developer.arm.com/documentation/den0147/latest/
> + *
> + * An "LFA agent" is running in EL3, providing a list for activate-able
> + * firmware components via repeated LFA_GET_INVENTORY SMCCC calls. Those
> + * components are identified by a UUID, which this driver uses to name
> + * subdirectories under /sys/firmware/lfa. The properties and controls of each
> + * firmware component are exposed as sysfs files in this directory, for a list
> + * of them see: Documentation/ABI/testing/sysfs-firmware-lfa.
> + *
> + * Each firmware component sets the "activation pending" bit, if an update
> + * image for that component becomes available, either through some host
> + * initiated update process (UEFI capsule update), or via some out-of-band,
> + * BMC controlled update. The latter can be signalled via an interrupt or
> + * an ACPI notification.
> + * The actual activation process is split in two phases: LFA_PRIME prepares
> + * the activation, it can take longer and might require multiple calls to it.
> + * The real handover happens with LFA_ACTIVATE then. Some firmware components'
> + * update procedure might require to gather all CPUs in EL3 during the
> + * LFA_ACTIVATE call, to prevent them from doing any service calls to the
> + * firmware component being updated, but also to allow the updating firmware
> + * component to initialise itself on each available CPU. This is called
> + * "CPU rendezvous", some firmware components require that for their
> + * activation. This driver chooses to do so regardless, to prevent stray
> + * firmware accesses from the kernel during the update phase. This can be
> + * skipped by setting the force_cpu_rendezvous sysfs file to false.
> + */
> +
> +#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/rwsem.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)
> +
> +#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
> +#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
> +
> +#define CASE_LFA_ERROR(name) 		case name: return #name
> +
> +static const char *lfa_error_string(s64 error)
> +{
> +	switch (error) {
> +	CASE_LFA_ERROR(LFA_SUCCESS);
> +	CASE_LFA_ERROR(LFA_NOT_SUPPORTED);
> +	CASE_LFA_ERROR(LFA_BUSY);
> +	CASE_LFA_ERROR(LFA_AUTH_ERROR);
> +	CASE_LFA_ERROR(LFA_NO_MEMORY);
> +	CASE_LFA_ERROR(LFA_CRITICAL_ERROR);
> +	CASE_LFA_ERROR(LFA_DEVICE_ERROR);
> +	CASE_LFA_ERROR(LFA_WRONG_STATE);
> +	CASE_LFA_ERROR(LFA_INVALID_PARAMETERS);
> +	CASE_LFA_ERROR(LFA_COMPONENT_WRONG_STATE);
> +	CASE_LFA_ERROR(LFA_INVALID_ADDRESS);
> +	CASE_LFA_ERROR(LFA_ACTIVATION_FAILED);
> +	default:
> +		return "UNKNOWN";
> +	}
> +}
> +
> +static int lfa_to_linux_errno(s64 lfa_error)
> +{
> +	switch (lfa_error) {
> +	case LFA_SUCCESS:
> +		return 0;
> +	case LFA_NOT_SUPPORTED:
> +		return -EOPNOTSUPP;
> +	case LFA_BUSY:
> +		return -EBUSY;
> +	case LFA_AUTH_ERROR:
> +		return -EPERM;
> +	case LFA_NO_MEMORY:
> +		return -ENOMEM;
> +	case LFA_CRITICAL_ERROR:
> +		return -EACCES;
> +	case LFA_DEVICE_ERROR:
> +		return -EIO;
> +	case LFA_WRONG_STATE:
> +		return -EPROTO;
> +	case LFA_INVALID_PARAMETERS:
> +		return -EINVAL;
> +	case LFA_COMPONENT_WRONG_STATE:
> +		return -EPROTO;
> +	case LFA_INVALID_ADDRESS:
> +		return -ENXIO;
> +	case LFA_ACTIVATION_FAILED:
> +		return -EIO;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +enum component_attr_names {
> +	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_component {
> +	struct kobject kobj;
> +	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;
> +	bool use_cpu_rendezvous;
> +	struct kobj_attribute comp_attrs[LFA_ATTR_NR_IMAGES];
> +};
> +
> +static struct fw_component *kobj_to_fw_component(struct kobject *kobj)
> +{
> +	return container_of(kobj, struct fw_component, kobj);
> +}
> +
> +/* A UUID split over two 64-bit registers */
> +struct uuid_regs {
> +	u64 uuid_lo;
> +	u64 uuid_hi;
> +};
> +
> +static struct kset *lfa_kset;
> +static struct workqueue_struct *fw_comps_update_wq;
> +static struct work_struct fw_comps_update_work;
> +static struct attribute *component_default_attrs[LFA_ATTR_NR_IMAGES + 1];
> +
> +/*
> + * A successful component activation might change the number of available
> + * components, leading to a re-order and thus re-assignment of the sequence IDs.
> + * The lock protects the connection between a firmware component (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 comp_attr_group = {
> +	.attrs = component_default_attrs,
> +};
> +
> +static const struct attribute_group *comp_default_groups[] = {
> +	&comp_attr_group,
> +	NULL
> +};
> +
> +static void component_release(struct kobject *kobj)
> +{
> +	struct fw_component *comp = kobj_to_fw_component(kobj);
> +
> +	kfree(comp);
> +}
> +
> +static const struct kobj_type component_ktype = {
> +	.release = component_release,
> +	.sysfs_ops = &kobj_sysfs_ops,
> +	.default_groups = comp_default_groups,
> +};
> +
> +static void delete_fw_component_node(struct fw_component *comp)
> +{
> +	kobject_del(&comp->kobj);
> +	kobject_put(&comp->kobj);
> +}
> +
> +static void remove_invalid_fw_components(struct work_struct *work)
> +{
> +	struct kobject *kobj, *tmp;
> +	struct list_head comps_to_delete = LIST_HEAD_INIT(comps_to_delete);
> +
> +	/*
> +	 * After an activation, firmware components might have been removed
> +	 * from the inventory. Move those components into a private temporary
> +	 * list, where they can be safely destroyed separately, as kobject_del()
> +	 * would be trying to take the same kset->list_lock.
> +	 */
> +	spin_lock(&lfa_kset->list_lock);
> +	list_for_each_entry_safe(kobj, tmp, &lfa_kset->list, entry) {
> +		struct fw_component *comp = kobj_to_fw_component(kobj);
> +
> +		if (comp->fw_seq_id == -1)
> +			list_move_tail(&kobj->entry, &comps_to_delete);
`update_fw_components_tree()` invalidates all sequence IDs, releases the 
list lock, and restores the components individually during enumeration. 
A deletion worker queued by an earlier refresh can run in that interval. 
It only takes the list lock, so it can see the temporary `fw_seq_id == 
-1` values and detach components that are still present in the new 
inventory.

Could the worker's selection and detachment of invalid entries be 
synchronized against enumeration?
> +	}
> +	spin_unlock(&lfa_kset->list_lock);
> +
> +	list_for_each_entry_safe(kobj, tmp, &comps_to_delete, entry) {
> +		struct fw_component *comp = kobj_to_fw_component(kobj);
> +
> +		delete_fw_component_node(comp);
> +	}
> +}
> +
> +static void set_component_flags(struct fw_component *comp, int seq_id,
> +				u32 comp_flags, u64 reg_current_ver,
> +				u64 reg_pending_ver)
> +{
> +	comp->fw_seq_id = seq_id;
> +	comp->current_version = reg_current_ver;
> +	comp->pending_version = reg_pending_ver;
> +	comp->activation_capable = !!(comp_flags & BIT(0));
> +	comp->activation_pending = !!(comp_flags & BIT(1));
> +	comp->may_reset_cpu = !!(comp_flags & BIT(2));
> +	/* cpu_rendezvous_optional bit has inverse logic in the spec */
> +	comp->cpu_rendezvous = !(comp_flags & BIT(3));
> +}
> +
> +static int get_nr_lfa_components(void)
> +{
> +	struct arm_smccc_1_2_regs regs = {
> +		.a0 = ARM_SMCCC_LFA_GET_INFO,
> +		.a1 = 0,		 /* lfa_info_selector = 0 */
> +	};
> +
> +	/* No need for the smc_lock, since no sequence IDs are involved. */
> +	arm_smccc_1_2_invoke(&regs, &regs);
> +	if (regs.a0 != LFA_SUCCESS)
> +		return (s64)regs.a0;
> +
> +	return regs.a1;
> +}
> +
> +static int update_fw_components_node(char *fw_uuid, int seq_id,
> +				     u32 comp_flags, u64 reg_current_ver,
> +				     u64 reg_pending_ver)
> +{
> +	struct fw_component *comp;
> +	struct kobject *kobj;
> +
> +	/*
> +	 * If a component is already in the components 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_component *comp = kobj_to_fw_component(kobj);
> +
> +			set_component_flags(comp, seq_id, comp_flags,
> +					reg_current_ver, reg_pending_ver);
> +			spin_unlock(&lfa_kset->list_lock);
> +
> +			return 0;
> +		}
> +	}
> +	spin_unlock(&lfa_kset->list_lock);
> +
> +	comp = kzalloc_obj(*comp);
> +	if (!comp)
> +		return -ENOMEM;
> +
> +	comp->kobj.kset = lfa_kset;
> +	comp->cpu_rendezvous_forced = true;
> +	set_component_flags(comp, seq_id, comp_flags, reg_current_ver,
> +			    reg_pending_ver);
> +	if (kobject_init_and_add(&comp->kobj, &component_ktype, NULL,
> +				 "%s", fw_uuid)) {
> +		kobject_put(&comp->kobj);
> +		return -ENOMEM;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * Creates/updates the list of components provided by the firmware.
> + * Must be called with the smc_lock held if called outside of initialisation.
> + */
> +static int update_fw_components_tree(void)
> +{
> +	struct arm_smccc_1_2_regs regs = { 0 }, res;
> +	struct uuid_regs comp_uuid;
> +	struct kobject *kobj;
> +	char comp_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 components as the seq_ids and the
> +	 * number of firmware components in the LFA agent may change after a
> +	 * successful activation attempt. Negate all components flags as well.
> +	 */
> +	spin_lock(&lfa_kset->list_lock);
> +	list_for_each_entry(kobj, &lfa_kset->list, entry) {
> +		struct fw_component *comp = kobj_to_fw_component(kobj);
> +
> +		set_component_flags(comp, -1, 0b1000, 0, 0);
> +	}
> +	spin_unlock(&lfa_kset->list_lock);
> +
> +	regs.a0 = ARM_SMCCC_LFA_GET_INVENTORY;
> +	for (int i = 0; i < num_of_components; i++) {
> +		regs.a1 = i; /* fw_seq_id to be queried */
> +		arm_smccc_1_2_invoke(&regs, &res);
> +		if (res.a0 == LFA_SUCCESS) {
> +			comp_uuid.uuid_lo = res.a1;
> +			comp_uuid.uuid_hi = res.a2;
> +
> +			snprintf(comp_id_str, sizeof(comp_id_str), "%pUb",
> +				 &comp_uuid);
> +			ret = update_fw_components_node(comp_id_str, i, res.a3,
> +							res.a4, res.a5);
> +			if (ret)
> +				return ret;
> +		}
> +	}
> +
> +	/*
> +	 * Removing non-valid component 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_comps_update_wq, &fw_comps_update_work);
> +
> +	return 0;
> +}
> +
> +static int lfa_cancel(void *data)
> +{
> +	struct fw_component *comp = data;
> +	struct arm_smccc_1_2_regs regs = { 0 };
> +
> +	down_read(&smc_lock);
> +	regs.a0 = ARM_SMCCC_LFA_CANCEL;
> +	regs.a1 = comp->fw_seq_id;
> +	arm_smccc_1_2_invoke(&regs, &regs);
> +	up_read(&smc_lock);
> +
> +	/*
> +	 * 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 (regs.a0 == LFA_SUCCESS) {
> +		pr_info("Activation cancelled for component %s\n",
> +			kobject_name(&comp->kobj));
> +	} else {
> +		pr_err("Activation not cancelled for component %s: %s (%ld)\n",
> +		       kobject_name(&comp->kobj), lfa_error_string(regs.a0),
> +		       regs.a0);
> +		return -EINVAL;
> +	}
> +
> +	return regs.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.
> + * Returns a Linux error code, not an LFA one.
> + */
> +static int call_lfa_activate(void *data)
> +{
> +	/*
> +	 * Use the cached value of the rendezvous status, to be consistent
> +	 * with how we were called (via stop_machine() or not).
> +	 * As we do not support updates requiring a CPU reset (yet),
> +	 * we can skip the entry point and context ID parameters.
> +	 */
> +	struct fw_component *comp = data;
> +	struct arm_smccc_1_2_regs regs = {
> +		.a0 = ARM_SMCCC_LFA_ACTIVATE,
> +		.a1 = comp->fw_seq_id,
> +		.a2 = !comp->use_cpu_rendezvous,
> +		.a3 = 0,	/* unused entry point */
> +		.a4 = 0,	/* unused context ID */
> +	};
> +
> +	touch_nmi_watchdog();
> +	arm_smccc_1_2_invoke(&regs, &regs);
> +	if ((s64)regs.a0 < 0)
> +		return lfa_to_linux_errno((s64)regs.a0);
> +
> +	if (regs.a1 & LFA_ACTIVATE_CALL_AGAIN)
> +		return -EAGAIN;
> +
> +	return 0;
> +}
> +
> +static int activate_fw_component(struct fw_component *comp)
> +{
> +	ktime_t end = ktime_add_ms(ktime_get(), LFA_ACTIVATE_BUDGET_MS);
> +	int ret;
> +
> +	do {
> +		down_write(&smc_lock);
> +		/*
> +		 * cpu_rendezvous_forced is set by the administrator, via sysfs,
> +		 * cpu_rendezvous is dictated by each firmware component.
> +		 * All cores call into the firmware with the same arguments.
> +		 */
> +		comp->use_cpu_rendezvous = comp->cpu_rendezvous_forced ||
> +					   comp->cpu_rendezvous;
> +		if (comp->use_cpu_rendezvous) {
> +			cpus_read_lock();
> +			ret = stop_machine(call_lfa_activate, comp,
> +					   cpu_online_mask);
stop_machine() takes cpus_read_lock internally. Recursive reader 
acquisition can deadlock if hotplug writer intervenes between the 
acquisitions. Could we drop the outer lock/unlock or use 
stop_machine_cpuslocked() ?
> +			cpus_read_unlock();
> +		} else {
> +			ret = call_lfa_activate(comp);
> +		}
> +		if (!ret)
> +			update_fw_components_tree();

The return value is ignored here. Since activation can reorder sequence 
IDs, a failed refresh can leave stale or partially rebuilt mappings 
available to subsequent operations.

Could refresh failures be handled explicitly, with further component 
operations prevented from using unverified mappings? Activation may 
already have succeeded, so that result should be distinguished from 
failure to refresh the inventory.

> +		up_write(&smc_lock);
> +
> +		if (!ret)
> +			return 0;
> +
> +		if (ret == -EAGAIN || ret == -EBUSY) {
> +			if (ktime_before(ktime_get(), end)) {
> +				if (msleep_interruptible(LFA_ACTIVATE_DELAY_MS))
> +					ret = -EINTR;
> +			} else {
> +				ret = -ETIMEDOUT;
> +			}
> +		}
> +	} while (ret == -EAGAIN || ret == -EBUSY);
> +
> +	lfa_cancel(comp);
> +
> +	pr_err("LFA_ACTIVATE for component %s failed\n",
> +	       kobject_name(&comp->kobj));
> +
> +	return ret;
> +}
> +
> +static int prime_fw_component(struct fw_component *comp)
> +{
> +	ktime_t end = ktime_add_ms(ktime_get(), LFA_PRIME_BUDGET_MS);
> +	bool retry = false;
> +	int ret = 0;
> +
> +	if (comp->may_reset_cpu) {
> +		pr_err("CPU reset not supported by kernel driver\n");
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * LFA_PRIME will return 1 in regs.a1 if the firmware priming
> +	 * is still in progress. In that case LFA_PRIME will need to
> +	 * be called again.
> +	 * regs.a1 will become 0 once the prime process completes.
> +	 */
> +	do {
> +		struct arm_smccc_1_2_regs regs = {
> +			.a0 = ARM_SMCCC_LFA_PRIME,
> +		};
> +
> +		touch_nmi_watchdog();
> +		down_read(&smc_lock);
> +		regs.a1 = comp->fw_seq_id;
> +		arm_smccc_1_2_invoke(&regs, &regs);
> +		up_read(&smc_lock);
> +
> +		if ((s64)regs.a0 < 0) {
> +			pr_err("LFA_PRIME for component %s failed: %s (%ld)\n",
> +			       kobject_name(&comp->kobj),
> +			       lfa_error_string((s64)regs.a0), regs.a0);
> +			return lfa_to_linux_errno((s64)regs.a0);
> +		}
> +
> +		if ((regs.a1 & LFA_PRIME_CALL_AGAIN)) {
> +			if (ktime_before(ktime_get(), end)) {
> +				if (msleep_interruptible(LFA_PRIME_DELAY_MS)) {
> +					ret = -EINTR;
> +					break;
> +				}
> +			} else {
> +				pr_err("LFA_PRIME for component %s timed out",
> +				       kobject_name(&comp->kobj));
> +				ret = -ETIMEDOUT;
> +				break;
> +			}
> +			retry = true;
> +		}
> +	} while (retry);

'retry' is initialized to false before the loop, but once CALL_AGAIN 
sets it to true, it is not cleared before subsequent PRIME calls. If 
CALL_AGAIN is followed by successful completion, the loop will continue 
instead of proceeding to ACTIVATE.

If subsequent calls continue reporting completion, it also bypasses the 
sleep and timeou checks inside the CALL_AGAIN branch.

Could we derive 'retry' from response of PRIME SMC call?

> +
> +	if (ret)
> +		lfa_cancel(comp);
> +
> +	return ret;
> +}
> +
> +static ssize_t activation_capable_show(struct kobject *kobj,
> +				       struct kobj_attribute *attr, char *buf)
> +{
> +	struct fw_component *comp = kobj_to_fw_component(kobj);
> +
> +	return sysfs_emit(buf, "%d\n", comp->activation_capable);
> +}
> +
> +/* called with smc_lock held */
> +static void _update_fw_component_pending(struct fw_component *comp)
> +{
> +	struct arm_smccc_1_2_regs regs = {
> +		.a0 = ARM_SMCCC_LFA_GET_INVENTORY,
> +		.a1 = comp->fw_seq_id,
> +	};
> +
> +	arm_smccc_1_2_invoke(&regs, &regs);
> +
> +	if (regs.a0 == LFA_SUCCESS)
> +		comp->activation_pending = !!(regs.a3 & BIT(1));
> +}
> +
> +static void update_fw_component_pending(struct fw_component *comp)
> +{
> +	down_read(&smc_lock);
> +	_update_fw_component_pending(comp);
> +	up_read(&smc_lock);
> +}
> +
> +static ssize_t activation_pending_show(struct kobject *kobj,
> +				       struct kobj_attribute *attr, char *buf)
> +{
> +	struct fw_component *comp = kobj_to_fw_component(kobj);
> +
> +	/*
> +	 * Activation pending status can change anytime thus we need to update
> +	 * and return its current value
> +	 */
> +	update_fw_component_pending(comp);
> +
> +	return sysfs_emit(buf, "%d\n", comp->activation_pending);
> +}
> +
> +static ssize_t may_reset_cpu_show(struct kobject *kobj,
> +				  struct kobj_attribute *attr, char *buf)
> +{
> +	struct fw_component *comp = kobj_to_fw_component(kobj);
> +
> +	return sysfs_emit(buf, "%d\n", comp->may_reset_cpu);
> +}
> +
> +static ssize_t cpu_rendezvous_show(struct kobject *kobj,
> +				   struct kobj_attribute *attr, char *buf)
> +{
> +	struct fw_component *comp = kobj_to_fw_component(kobj);
> +
> +	return sysfs_emit(buf, "%d\n", comp->cpu_rendezvous);
> +}
> +
> +static ssize_t force_cpu_rendezvous_store(struct kobject *kobj,
> +					  struct kobj_attribute *attr,
> +					  const char *buf, size_t count)
> +{
> +	struct fw_component *comp = kobj_to_fw_component(kobj);
> +	int ret;
> +
> +	ret = kstrtobool(buf, &comp->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_component *comp = kobj_to_fw_component(kobj);
> +
> +	return sysfs_emit(buf, "%d\n", comp->cpu_rendezvous_forced);
> +}
> +
> +static ssize_t current_version_show(struct kobject *kobj,
> +				    struct kobj_attribute *attr, char *buf)
> +{
> +	struct fw_component *comp = kobj_to_fw_component(kobj);
> +	u32 maj, min;
> +
> +	maj = upper_32_bits(comp->current_version);
> +	min = lower_32_bits(comp->current_version);
> +
> +	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_component *comp = kobj_to_fw_component(kobj);
> +	struct arm_smccc_1_2_regs regs = { 0 };
> +
> +	/*
> +	 * Similar to activation pending, this value can change following an
> +	 * update, we need to retrieve fresh info instead of stale information.
> +	 * Update the activation_pending status on the way.
> +	 */
> +	down_read(&smc_lock);
> +	regs.a0 = ARM_SMCCC_LFA_GET_INVENTORY;
> +	regs.a1 = comp->fw_seq_id;
> +	arm_smccc_1_2_invoke(&regs, &regs);
> +	up_read(&smc_lock);
> +	if (regs.a0 == LFA_SUCCESS) {
> +		comp->activation_pending = !!(regs.a3 & BIT(1));
> +		if (regs.a5 != 0 && comp->activation_pending) {
> +			u32 maj, min;
> +
> +			comp->pending_version = regs.a5;
> +			maj = upper_32_bits(regs.a5);
> +			min = lower_32_bits(regs.a5);
> +
> +			return sysfs_emit(buf, "%u.%u\n", maj, min);
> +		}
> +
> +		return 0;
> +	}
> +
> +	return lfa_to_linux_errno((s64)regs.a0);
> +}
> +
> +static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr,
> +			      const char *buf, size_t count)
> +{
> +	struct fw_component *comp = kobj_to_fw_component(kobj);
> +	bool activate;
> +	int ret;
> +
> +	ret = kstrtobool(buf, &activate);
> +	if (ret)
> +		return ret;
> +
> +	if (!activate)
> +		return count;
> +
> +	ret = prime_fw_component(comp);
> +	if (ret)
> +		return ret;
> +
> +	ret = activate_fw_component(comp);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("%s: successfully activated\n", kobject_name(&comp->kobj));
> +
> +	return count;
> +}
> +
> +static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr,
> +			    const char *buf, size_t count)
> +{
> +	struct fw_component *comp = kobj_to_fw_component(kobj);
> +	bool cancel;
> +	int ret;
> +
> +	ret = kstrtobool(buf, &cancel);
> +	if (ret)
> +		return ret;
> +
> +	if (!cancel)
> +		return count;
> +
> +	ret = lfa_cancel(comp);
> +	if (ret != 0)
> +		return ret;
> +
> +	return count;
> +}
> +
> +static struct kobj_attribute comp_attrs_group[LFA_ATTR_NR_IMAGES] = {
> +	[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_component_default_attrs(void)
> +{
> +	for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++)
> +		component_default_attrs[i] = &comp_attrs_group[i].attr;
> +	component_default_attrs[LFA_ATTR_NR_IMAGES] = NULL;
> +}
> +
> +static void clean_fw_components_tree(void)
> +{
> +	struct kobject *kobj, *tmp;
> +	struct list_head comp_to_delete;
> +
> +	INIT_LIST_HEAD(&comp_to_delete);
> +
> +	spin_lock(&lfa_kset->list_lock);
> +	list_for_each_entry_safe(kobj, tmp, &lfa_kset->list, entry) {
> +		list_move_tail(&kobj->entry, &comp_to_delete);
> +	}
> +	spin_unlock(&lfa_kset->list_lock);
> +
> +	list_for_each_entry_safe(kobj, tmp, &comp_to_delete, entry) {
> +		struct fw_component *comp = kobj_to_fw_component(kobj);
> +
> +		delete_fw_component_node(comp);
> +	}
> +}
> +
> +static bool lfa_smccc_has_all_functions(void)
> +{
> +	struct arm_smccc_1_2_regs regs = { 0 }, res;
> +	u32 fn_id;
> +
> +	regs.a0 = ARM_SMCCC_LFA_FEATURES;
> +
> +	for (fn_id = ARM_SMCCC_LFA_GET_INFO;
> +	     fn_id <= ARM_SMCCC_LFA_CANCEL;
> +	     fn_id++) {
> +		regs.a1 = fn_id;
> +		arm_smccc_1_2_invoke(&regs, &res);
> +		if (res.a0)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
> +static int lfa_smccc_probe(struct arm_smccc_device *sdev)
> +{
> +	struct arm_smccc_1_2_regs regs = { 0 };
> +	int err;
> +
> +	/* The LFA spec requires SMCCC v1.2 or higher. */
> +	if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2)
> +		return -EOPNOTSUPP;
> +
> +	regs.a0 = ARM_SMCCC_LFA_VERSION;
> +	arm_smccc_1_2_invoke(&regs, &regs);
> +	if ((s32)regs.a0 == LFA_NOT_SUPPORTED)
> +		return -ENODEV;
> +
> +	if (!lfa_smccc_has_all_functions())
> +		return -EOPNOTSUPP;
> +
> +	pr_info("Live Firmware Activation: detected v%d.%d\n",
> +		upper_16_bits(regs.a0), lower_16_bits(regs.a0));
> +
> +	fw_comps_update_wq = alloc_workqueue("fw_comps_update_wq",
> +					     WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
> +	if (!fw_comps_update_wq) {
> +		pr_err("Live Firmware Activation: Failed to allocate workqueue.\n");
> +		return -ENOMEM;
> +	}
> +	INIT_WORK(&fw_comps_update_work, remove_invalid_fw_components);
> +
> +	init_component_default_attrs();
> +	lfa_kset = kset_create_and_add("lfa", NULL, firmware_kobj);
> +	if (!lfa_kset) {
> +		destroy_workqueue(fw_comps_update_wq);
> +		return -ENOMEM;
> +	}
> +
> +	init_rwsem(&smc_lock);
> +
> +	err = update_fw_components_tree();
> +	if (err != 0) {
> +		kset_unregister(lfa_kset);
> +		destroy_workqueue(fw_comps_update_wq);
> +	}
If fw_components enumeration creates few component kobjects and then 
fails, the failure case here only unregisters kset and destroys 
workqueue. Could we call clean_fw_components_tree() in the failure path 
before unregistering lfa_kset ?
> +
> +	return err;
> +}
> +
> +static void lfa_smccc_remove(struct arm_smccc_device *sdev)
> +{
> +	clean_fw_components_tree();
> +	flush_workqueue(fw_comps_update_wq);
> +	destroy_workqueue(fw_comps_update_wq);
> +	kset_unregister(lfa_kset);
> +}
> +
> +static const struct arm_smccc_device_id lfa_smccc_id_table[] = {
> +	{ .func_id = ARM_SMCCC_LFA_VERSION },
> +	{}
> +};
> +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 5dc768ac4143a..848d992141568 100644
> --- a/drivers/firmware/smccc/smccc.c
> +++ b/drivers/firmware/smccc/smccc.c
> @@ -100,6 +100,11 @@ static const struct smccc_device_info smccc_devices[] __initconst = {
>   		.requires_smc   = true,
>   		.device_name    = "arm-rsi",
>   	},
> +	{
> +		.func_id        = ARM_SMCCC_LFA_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 4de81848fe2ee..381d527c16a4c 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_VERSION		ARM_SMCCC_LFA_FN(0)
> +#define ARM_SMCCC_LFA_FEATURES		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

Regards,

Veda


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

* Re: [PATCH v5 RESEND 3/6] firmware: smccc: lfa: Register ACPI notification
  2026-11-04 20:03   ` Nirmoy Das
@ 2026-11-04 23:02     ` Vedashree Vidwans
  0 siblings, 0 replies; 19+ messages in thread
From: Vedashree Vidwans @ 2026-11-04 23:02 UTC (permalink / raw)
  To: Nirmoy Das, Andre Przywara, Mark Rutland, Lorenzo Pieralisi,
	Sudeep Holla
  Cc: Salman Nabi, Trilok Soni, vsethi, Varun Wadekar, linux-arm-kernel,
	linux-kernel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	devicetree

Hi Andre

On 11/4/26 12:03, Nirmoy Das wrote:
> Hi Andre
>
> On 02.11.26 17:54, Andre Przywara wrote:
>> 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 | 154 +++++++++++++++++++++++++++++++-
>>   1 file changed, 153 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/firmware/smccc/lfa_fw.c 
>> b/drivers/firmware/smccc/lfa_fw.c
>> index db2a7fe344d5d..4b4daf79611bb 100644
>> --- a/drivers/firmware/smccc/lfa_fw.c
>> +++ b/drivers/firmware/smccc/lfa_fw.c
>> @@ -33,12 +33,14 @@
>>    * skipped by setting the force_cpu_rendezvous sysfs file to false.
>>    */
>>   +#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>
>> @@ -49,11 +51,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
>>   @@ -753,6 +757,135 @@ static void clean_fw_components_tree(void)
>>       }
>>   }
>>   +/*
>> + * Iterate over all FW components and trigger activation of all 
>> activatible and
>> + * pending components.
>> + * Since we would need to re-enumerate the list of all components 
>> after a
>> + * successful activation, and this could trigger infinite loops if a 
>> firmware
>> + * component still appears as pending, we create an array of update 
>> candidates
>> + * first, then activate each one of them.
>> + */
>> +static void activate_pending_components(void)
>> +{
>> +    struct fw_component **all_comps;
>> +    struct fw_component *comp;
>> +    struct kobject *kobj;
>> +    int nr_comps = 0;
>> +    int ret, i;
>> +
>> +    down_read(&smc_lock);
>> +    spin_lock(&lfa_kset->list_lock);
>> +    all_comps = kmalloc_array(list_count_nodes(&lfa_kset->list),
>> +                  sizeof(*all_comps), GFP_NOWAIT);
>> +    if (!all_comps) {
>> +        spin_unlock(&lfa_kset->list_lock);
>> +        up_read(&smc_lock);
>> +        pr_warn("LFA: automatic activation failed: -ENOMEM\n");
>> +        return;
>> +    }
>> +
>> +    list_for_each_entry(kobj, &lfa_kset->list, entry) {
>> +        comp = kobj_to_fw_component(kobj);
>> +
>> +        if (comp->fw_seq_id == -1)
>> +            continue; /* Invalid FW component */
>> +
>> +        _update_fw_component_pending(comp);
>> +        if (comp->activation_capable && comp->activation_pending) {
>> +            kobject_get(&comp->kobj);
>> +            all_comps[nr_comps++] = comp;
>> +        }
>> +    }
>> +    spin_unlock(&lfa_kset->list_lock);
>> +    up_read(&smc_lock);
>> +
>> +    for (i = 0; i < nr_comps; i++) {
>> +        comp = all_comps[i];
>> +
>> +        ret = prime_fw_component(comp);
The kobject references protect the snapshot objects from being freed, 
but do not guarantee that the components remain in the firmware inventory.

For example, the snapshot can contain A and B, and activating A can 
remove B from the refreshed inventory. The reference keeps B allocated, 
but its sequence ID becomes `-1`. The subsequent PRIME call does not 
reject that invalid ID before issuing the SMC.

Could candidates be revalidated under `smc_lock` before issuing 
component operations? The check needs to cover activation retries too, 
since inventory can change between calls.
>> +        if (ret) {
>> +            pr_warn("LFA: LFA_PRIME failed for component %s\n",
>> +                kobject_name(&comp->kobj));
>> +            kobject_put(&comp->kobj);
>> +            continue;
>> +        }
>> +
>> +        ret = activate_fw_component(comp);
>> +        if (ret) {
>> +            pr_warn("LFA: LFA_ACTIVATE failed for component %s\n",
>> +                kobject_name(&comp->kobj));
>> +            kobject_put(&comp->kobj);
>> +            continue;
>> +        }
>> +
>> +        pr_info("%s: automatic activation succeeded\n",
>> +            kobject_name(&comp->kobj));
>> +        kobject_put(&comp->kobj);
>> +    }
>> +
>> +    kfree(all_comps);
>> +}
>> +
>> +#ifdef CONFIG_ACPI
>> +static void lfa_acpi_notify_handler(acpi_handle handle, u32 event, 
>> void *data)
>> +{
>> +    /* The spec explicitly mentions 0x80 only for the update event. */
>> +    if (event != 0x80)
>> +        return;
>> +
>> +    activate_pending_components();
>> +}
>> +
>> +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 bool lfa_smccc_has_all_functions(void)
>>   {
>>       struct arm_smccc_1_2_regs regs = { 0 }, res;
>> @@ -813,13 +946,32 @@ static int lfa_smccc_probe(struct 
>> arm_smccc_device *sdev)
>>       if (err != 0) {
>>           kset_unregister(lfa_kset);
>>           destroy_workqueue(fw_comps_update_wq);
>> +        return err;
>> +    }
>> +
>> +    if (!acpi_disabled) {
>> +        err = lfa_register_acpi(&sdev->dev);
>> +        if (!err)
>> +            pr_info("registered LFA ACPI notification\n");
>> +    }
>> +
>> +    /*
>> +     * An ACPI notification or an interrupt are optional, but we must
>> +     * handle errors that occurred during their setup.
>> +     */
>> +    if (err != -ENODEV) {
>
> On successful registration, err is zero, so this condition still 
> unregisters the inventory and destroys the workqueue before returning 
> success. Should it be:
>
> if (err && err != -ENODEV) {
>
>
> Regards,
>
> Nirmoy
>
>> +        kset_unregister(lfa_kset);
>> +        destroy_workqueue(fw_comps_update_wq);
>> +        return err;
>>       }
>>   -    return err;
>> +    return 0;
>>   }
>>     static void lfa_smccc_remove(struct arm_smccc_device *sdev)
>>   {
>> +    if (!acpi_disabled)
>> +        lfa_remove_acpi(&sdev->dev);
>>       clean_fw_components_tree();
>>       flush_workqueue(fw_comps_update_wq);
>>       destroy_workqueue(fw_comps_update_wq);

Regards,

Veda


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

* Re: [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support
  2026-11-02 16:54 [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (5 preceding siblings ...)
  2026-11-02 16:54 ` [PATCH v5 RESEND 6/6] firmware: smccc: lfa: add sysfs ABI documentation Andre Przywara
@ 2026-11-05  9:17 ` Nirmoy Das
  6 siblings, 0 replies; 19+ messages in thread
From: Nirmoy Das @ 2026-11-05  9:17 UTC (permalink / raw)
  To: Andre Przywara, Mark Rutland, Lorenzo Pieralisi, Sudeep Holla
  Cc: Salman Nabi, Vedashree Vidwans, Trilok Soni, vsethi,
	Varun Wadekar, linux-arm-kernel, linux-kernel, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree


On 02.11.26 17:54, Andre Przywara wrote:
> Hi all,
>
> -------------------------
> a resend of the v5 series posted two weeks ago, where our mailserver
> swallowed one email. Just used the opportunity to rebase on v7.4-rc1,
> so dropping the two extra patches I prepended to the post back then
> (already merged). No changes to the patches themselves compared to the
> other v5 post.
> -------------------------
>
> v5 is a decent overhaul of the series for the "Live Firmware Activation"
> kernel support, based on feedback from Mark and Sudeep. Two patches have
> been squashed into the main feature patch, the name attribute has been
> removed, and documentation (hopefully) improved. Other than that quite
> some smaller changes, renames and reworks, hopefully for the better.
>
> For a high level overview of LFA see the rest of this commit message and
> the commit message of the main feature patch (2/6). Some simplified
> overview of the spec is in the top comment in patch 2/6. Also the sysfs
> ABI documentation in the last patch contains some more information.
>
> Based on v7.4-rc1. 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 UUID.
> * Exposes component attributes (e.g., activation capability, and
>    activation pending) via sysfs under /sys/firmware/lfa/<UUID>/.
> * 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/6.
> Based on v7.4-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!

Hi Andre,

Tested on our hardware with my 3/6 probe fix applied. Automatic 
activation via the ACPI notification worked.

Our internal AI reviewer flagged a number of issues, which Sashiko will 
probably flag as well. Sashiko has shown this series as
"In Review" since Nov 2, while series posted around it were reviewed. Do 
you know whether it is still working on it, or does the series
need a resend to get reviewed?

https://sashiko.dev/#/patchset/20261102165421.3651202-1-andre.przywara%40arm.com 



Regards,

Nirmoy

>
> Best regards,
> Salman and Andre
>
> Changes in v5:
> - merge the timeout and the lock patch into original feature patch
> - drop "name" attribute altogether
> - rename firmware "image" to "component" everywhere
> - add explanation of LFA concepts in top comment
> - use switch/case for error strings and error mapping
> - define error codes with negative numbers
> - rename SMCCC register variable to from "reg" to "regs"
> - don't open code bit masking
> - use only one SMCCC register set where possible
> - rewrite retry:/goto into do/while() loop
> - make example IRQ to be edge triggered
> - update activation_pending in pending_version_show()
> - check SMCCC version to be v1.2 or higher
> - check all SMCCC functions are implemented
> - remove blank lines before return statements
> - use UUID instead of GUID everywhere
> - improve comments
> - filter for ACPI notification 0x80 only
> - update ABI doc to use "component", not "image"
> - add activation_pending attribute
> - fix IRQ/ACPI registration error handling
> - add comments documenting lock requirements
> - print LFA error number when printing error strings
> - explicitly free IRQ in remove() routine
> - reorder functions to avoid early empty prototype
> - adjust SMCCC function ID names to match spec
> - copy list of activatable images to avoid infinite loop
> - update pending status when showing pending_version
> - cast error returns to s64
> - replace "N/A" error return with empty string
>
> Changes in v4:
> - streamline threaded IRQ handler (drop empty hard IRQ part)
> - add lfa_to_linux_error() conversion function
> - use Linux errno codes in more functions
> - drop bogus LFA error numbers for -EAGAIN and -EBUSY
> - clarify interrupt purpose and edge trigger requirement in binding
> - honour signal related return from msleep_interruptible()
> - call clean_fw_images_tree() first in driver teardown
> - streamline ACPI and DT registration
> - cache rendezvous status to avoid race during activation
> - only trigger activation when writing 1/Y/true to sysfs file
> - adapt to new version of Aneesh's SMCCC bus patches ([1])
>
> 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/20260914060511.277948-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 (4):
>    dt-bindings: arm: Add Live Firmware Activation
>    firmware: smccc: lfa: Add auto_activate sysfs file
>    firmware: smccc: lfa: Register DT interrupt
>    firmware: smccc: lfa: add sysfs ABI documentation
>
> Salman Nabi (1):
>    firmware: smccc: Add support for Live Firmware Activation (LFA)
>
> Vedashree Vidwans (1):
>    firmware: smccc: lfa: Register ACPI notification
>
>   Documentation/ABI/testing/sysfs-firmware-lfa  |  106 ++
>   .../devicetree/bindings/arm/arm,lfa.yaml      |   50 +
>   drivers/firmware/smccc/Kconfig                |   10 +
>   drivers/firmware/smccc/Makefile               |    1 +
>   drivers/firmware/smccc/lfa_fw.c               | 1057 +++++++++++++++++
>   drivers/firmware/smccc/smccc.c                |    5 +
>   include/linux/arm-smccc.h                     |   15 +
>   7 files changed, 1244 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
>

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

* Re: [PATCH v5 RESEND 2/6] firmware: smccc: Add support for Live Firmware Activation (LFA)
  2026-11-02 16:54 ` [PATCH v5 RESEND 2/6] firmware: smccc: Add support for Live Firmware Activation (LFA) Andre Przywara
  2026-11-04 22:50   ` Vedashree Vidwans
@ 2026-11-05 15:56   ` sashiko-bot
  1 sibling, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-11-05 15:56 UTC (permalink / raw)
  To: Andre Przywara; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 14 potential issue(s) to consider:
- [High] firmware: smccc: lfa: infinite loop in prime_fw_component() on retry
- [High] firmware: smccc: lfa: nested cpus_read_lock() in activate_fw_component()
- [High] firmware: smccc: lfa: unlocked fw_seq_id race in remove_invalid_fw_components()
- [Medium] firmware: smccc: lfa: kobject leak in lfa_smccc_probe() error path
- [Medium] firmware: smccc: lfa: unvalidated fw_seq_id (-1) passed to SMCCC calls
- [Medium] firmware: smccc: lfa: unserialized concurrent activation and remove teardown
- [Medium] firmware: smccc: lfa: ignored update_fw_components_tree() error on activate
- [Medium] firmware: smccc: lfa: missing smc_lock in lfa_smccc_probe()
- [Medium] firmware: smccc: unaddressed objection to force_cpu_rendezvous_store() ABI
- [Low] firmware: smccc: lfa: non-static smc_lock and unused comp_attrs member
- [Low] firmware: smccc: lfa: broken UUID endianness in update_fw_components_tree()
- [Low] firmware: smccc: lfa: hardcoded -EINVAL return on failure in lfa_cancel()
- [Low] firmware: smccc: lfa: missing newline in prime_fw_component() timeout log
- [Low] firmware: smccc: driver-private LFA function ID macros left in arm-smccc.h
--

commit 5cf9560b4beb0919c0dfd145e7357461f787a9cc
Author: Salman Nabi <salman.nabi@arm.com>

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

This commit adds an SMCCC driver for Arm Live Firmware Activation (LFA),
exposing firmware components and activation controls under /sys/firmware/lfa.

> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
> new file mode 100644
> index 0000000000000..db2a7fe344d5d
> --- /dev/null
> +++ b/drivers/firmware/smccc/lfa_fw.c

[ ... ]

> +struct fw_component {
> +	struct kobject kobj;
> +	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;
> +	bool use_cpu_rendezvous;
> +	struct kobj_attribute comp_attrs[LFA_ATTR_NR_IMAGES];
> +};
> +
> +static struct fw_component *kobj_to_fw_component(struct kobject *kobj)
> +{
> +	return container_of(kobj, struct fw_component, kobj);
> +}
> +
> +/* A UUID split over two 64-bit registers */
> +struct uuid_regs {
> +	u64 uuid_lo;
> +	u64 uuid_hi;
> +};
> +
> +static struct kset *lfa_kset;
> +static struct workqueue_struct *fw_comps_update_wq;
> +static struct work_struct fw_comps_update_work;
> +static struct attribute *component_default_attrs[LFA_ATTR_NR_IMAGES + 1];
> +
> +/*
> + * A successful component activation might change the number of available
> + * components, leading to a re-order and thus re-assignment of the sequence IDs.
> + * The lock protects the connection between a firmware component (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;

[Severity: Low]
This isn't a bug, but smc_lock is only used within drivers/firmware/smccc/lfa_fw.c
and is defined without the static keyword, which exports smc_lock into the
global symbol namespace and triggers a -Wmissing-declarations warning.

Also, struct fw_component embeds comp_attrs[LFA_ATTR_NR_IMAGES], which is
never referenced because the driver registers the static comp_attrs_group
array via component_default_attrs. Could smc_lock be made static and
comp_attrs removed from struct fw_component?

[ ... ]

> +static void remove_invalid_fw_components(struct work_struct *work)
> +{
> +	struct kobject *kobj, *tmp;
> +	struct list_head comps_to_delete = LIST_HEAD_INIT(comps_to_delete);
> +
> +	/*
> +	 * After an activation, firmware components might have been removed
> +	 * from the inventory. Move those components into a private temporary
> +	 * list, where they can be safely destroyed separately, as kobject_del()
> +	 * would be trying to take the same kset->list_lock.
> +	 */
> +	spin_lock(&lfa_kset->list_lock);
> +	list_for_each_entry_safe(kobj, tmp, &lfa_kset->list, entry) {
> +		struct fw_component *comp = kobj_to_fw_component(kobj);
> +
> +		if (comp->fw_seq_id == -1)
> +			list_move_tail(&kobj->entry, &comps_to_delete);
> +	}
> +	spin_unlock(&lfa_kset->list_lock);
> +
> +	list_for_each_entry_safe(kobj, tmp, &comps_to_delete, entry) {
> +		struct fw_component *comp = kobj_to_fw_component(kobj);
> +
> +		delete_fw_component_node(comp);
> +	}
> +}

[Severity: High]
As reported by Sashiko on v2 [1], can remove_invalid_fw_components() race
with update_fw_components_tree() and delete valid firmware components or
cause -EEXIST failures in kobject_init_and_add()?

In update_fw_components_tree(), the driver resets all entries in
lfa_kset->list to fw_seq_id = -1 (and zeroes their flags, including setting
may_reset_cpu to false) under lfa_kset->list_lock, then drops list_lock while
looping over ARM_SMCCC_LFA_GET_INVENTORY to repopulate each component:

    spin_lock(&lfa_kset->list_lock);
    list_for_each_entry(kobj, &lfa_kset->list, entry) {
        struct fw_component *comp = kobj_to_fw_component(kobj);

        set_component_flags(comp, -1, 0b1000, 0, 0);
    }
    spin_unlock(&lfa_kset->list_lock);

Because remove_invalid_fw_components() runs asynchronously on
fw_comps_update_wq without holding smc_lock, if it executes while another
update_fw_components_tree() call is in the middle of its inventory loop, it
will observe fw_seq_id == -1 on valid components, move them off
lfa_kset->list onto comps_to_delete, and delete their kobjects.

Furthermore, once an entry is moved to comps_to_delete,
update_fw_components_node() will no longer find it in lfa_kset->list, so if
a component with the same UUID is added before kobject_del() finishes
removing the old sysfs directory, kobject_init_and_add() will fail with
-EEXIST (returned as -ENOMEM). During the same window between invalidation
and repopulation, prime_fw_component() also reads comp->may_reset_cpu without
synchronization and can observe the transient false value.

[ ... ]

> +static int update_fw_components_tree(void)
> +{
> +	struct arm_smccc_1_2_regs regs = { 0 }, res;
> +	struct uuid_regs comp_uuid;
> +	struct kobject *kobj;
> +	char comp_id_str[40];
> +	int ret, num_of_components;

[ ... ]

> +	regs.a0 = ARM_SMCCC_LFA_GET_INVENTORY;
> +	for (int i = 0; i < num_of_components; i++) {
> +		regs.a1 = i; /* fw_seq_id to be queried */
> +		arm_smccc_1_2_invoke(&regs, &res);
> +		if (res.a0 == LFA_SUCCESS) {
> +			comp_uuid.uuid_lo = res.a1;
> +			comp_uuid.uuid_hi = res.a2;
> +
> +			snprintf(comp_id_str, sizeof(comp_id_str), "%pUb",
> +				 &comp_uuid);
> +			ret = update_fw_components_node(comp_id_str, i, res.a3,
> +							res.a4, res.a5);
> +			if (ret)
> +				return ret;
> +		}
> +	}

[Severity: Low]
As reported by Sashiko on v2 [2], does formatting comp_uuid with %pUb
produce byte-reversed UUID strings on big-endian kernels?

Per the Arm LFA specification (DEN0147), ARM_SMCCC_LFA_GET_INVENTORY returns
the component UUID across two 64-bit registers in little-endian byte order.
In update_fw_components_tree(), res.a1 and res.a2 are stored directly into
host-endian u64 fields in struct uuid_regs before passing &comp_uuid to %pUb,
which reads the 16-byte buffer in memory address order. On a big-endian
kernel, each 8-byte half of the UUID will have its byte order reversed.
Should uuid_lo and uuid_hi be __le64 populated with cpu_to_le64()?

> +
> +	/*
> +	 * Removing non-valid component 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_comps_update_wq, &fw_comps_update_work);
> +
> +	return 0;
> +}
> +
> +static int lfa_cancel(void *data)
> +{
> +	struct fw_component *comp = data;
> +	struct arm_smccc_1_2_regs regs = { 0 };
> +
> +	down_read(&smc_lock);
> +	regs.a0 = ARM_SMCCC_LFA_CANCEL;
> +	regs.a1 = comp->fw_seq_id;
> +	arm_smccc_1_2_invoke(&regs, &regs);
> +	up_read(&smc_lock);

[Severity: Medium]
As reported by Sashiko on v2 [3], can comp->fw_seq_id be -1 when passed to
the firmware in SMCCC calls?

When update_fw_components_tree() invalidates components by setting
comp->fw_seq_id = -1 (either because a component was removed from the
inventory after activation or because update_fw_components_tree() returned
early on error), the component's sysfs files remain accessible until
remove_invalid_fw_components() runs on fw_comps_update_wq and deletes the
kobject. Any thread waiting on smc_lock or sleeping between retry iterations
can also resume after comp->fw_seq_id has been set to -1.

None of lfa_cancel(), call_lfa_activate(), prime_fw_component(),
_update_fw_component_pending(), or pending_version_show() check whether
comp->fw_seq_id < 0 after acquiring smc_lock, allowing -1
(0xffffffffffffffff) to be passed in regs.a1 to ARM_SMCCC_LFA_CANCEL,
ARM_SMCCC_LFA_ACTIVATE, ARM_SMCCC_LFA_PRIME, and ARM_SMCCC_LFA_GET_INVENTORY.
Should these callers validate comp->fw_seq_id >= 0 while holding smc_lock?

> +
> +	/*
> +	 * 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 (regs.a0 == LFA_SUCCESS) {
> +		pr_info("Activation cancelled for component %s\n",
> +			kobject_name(&comp->kobj));
> +	} else {
> +		pr_err("Activation not cancelled for component %s: %s (%ld)\n",
> +		       kobject_name(&comp->kobj), lfa_error_string(regs.a0),
> +		       regs.a0);
> +		return -EINVAL;
> +	}
> +
> +	return regs.a0;
> +}

[Severity: Low]
In lfa_cancel(), when ARM_SMCCC_LFA_CANCEL fails, the error branch
unconditionally returns -EINVAL even though the comment notes that LFA_CANCEL
can fail with LFA_BUSY. Should this return lfa_to_linux_errno((s64)regs.a0)
instead of -EINVAL so userspace receives the mapped error code via
cancel_store()?

[ ... ]

> +static int activate_fw_component(struct fw_component *comp)
> +{
> +	ktime_t end = ktime_add_ms(ktime_get(), LFA_ACTIVATE_BUDGET_MS);
> +	int ret;
> +
> +	do {
> +		down_write(&smc_lock);
> +		/*
> +		 * cpu_rendezvous_forced is set by the administrator, via sysfs,
> +		 * cpu_rendezvous is dictated by each firmware component.
> +		 * All cores call into the firmware with the same arguments.
> +		 */
> +		comp->use_cpu_rendezvous = comp->cpu_rendezvous_forced ||
> +					   comp->cpu_rendezvous;
> +		if (comp->use_cpu_rendezvous) {
> +			cpus_read_lock();
> +			ret = stop_machine(call_lfa_activate, comp,
> +					   cpu_online_mask);
> +			cpus_read_unlock();
> +		} else {
> +			ret = call_lfa_activate(comp);
> +		}

[Severity: High]
Doesn't calling stop_machine() while holding cpus_read_lock() in
activate_fw_component() acquire cpus_read_lock() recursively?

In kernel/stop_machine.c, stop_machine() already takes cpus_read_lock()
before calling stop_machine_cpuslocked():

    cpus_read_lock();
    ret = stop_machine_cpuslocked(fn, data, cpus);
    cpus_read_unlock();

Because cpus_read_lock() acquires cpu_hotplug_lock via percpu_down_read(),
taking it twice on the same task when comp->use_cpu_rendezvous is true
triggers a lockdep recursive read-lock warning under CONFIG_PROVE_LOCKING
and can deadlock if a CPU hotplug writer waits on cpu_hotplug_lock between
the outer and inner acquisitions. Should activate_fw_component() call
stop_machine_cpuslocked() or drop the outer cpus_read_lock() and
cpus_read_unlock()?

> +		if (!ret)
> +			update_fw_components_tree();
> +		up_write(&smc_lock);

[Severity: Medium]
As reported by Sashiko on v2 [4], what happens if update_fw_components_tree()
fails here in activate_fw_component()?

In update_fw_components_tree(), once get_nr_lfa_components() returns a
positive count, all existing components in lfa_kset->list are invalidated to
fw_seq_id = -1 with zeroed flags and versions before the loop queries
ARM_SMCCC_LFA_GET_INVENTORY and calls update_fw_components_node(). If
update_fw_components_node() fails with -ENOMEM partway through the loop,
update_fw_components_tree() returns early, leaving the remaining sysfs
components in a corrupted state with fw_seq_id == -1 while
activate_fw_component() ignores the return value and reports success. If
ARM_SMCCC_LFA_GET_INVENTORY fails for an entry, that component also remains at
fw_seq_id == -1 and gets removed by remove_invalid_fw_components().

[ ... ]

> +static int prime_fw_component(struct fw_component *comp)
> +{
> +	ktime_t end = ktime_add_ms(ktime_get(), LFA_PRIME_BUDGET_MS);
> +	bool retry = false;
> +	int ret = 0;
> +
> +	if (comp->may_reset_cpu) {
> +		pr_err("CPU reset not supported by kernel driver\n");
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * LFA_PRIME will return 1 in regs.a1 if the firmware priming
> +	 * is still in progress. In that case LFA_PRIME will need to
> +	 * be called again.
> +	 * regs.a1 will become 0 once the prime process completes.
> +	 */
> +	do {
> +		struct arm_smccc_1_2_regs regs = {
> +			.a0 = ARM_SMCCC_LFA_PRIME,
> +		};
> +
> +		touch_nmi_watchdog();
> +		down_read(&smc_lock);
> +		regs.a1 = comp->fw_seq_id;
> +		arm_smccc_1_2_invoke(&regs, &regs);
> +		up_read(&smc_lock);
> +
> +		if ((s64)regs.a0 < 0) {
> +			pr_err("LFA_PRIME for component %s failed: %s (%ld)\n",
> +			       kobject_name(&comp->kobj),
> +			       lfa_error_string((s64)regs.a0), regs.a0);
> +			return lfa_to_linux_errno((s64)regs.a0);
> +		}
> +
> +		if ((regs.a1 & LFA_PRIME_CALL_AGAIN)) {
> +			if (ktime_before(ktime_get(), end)) {
> +				if (msleep_interruptible(LFA_PRIME_DELAY_MS)) {
> +					ret = -EINTR;
> +					break;
> +				}
> +			} else {
> +				pr_err("LFA_PRIME for component %s timed out",
> +				       kobject_name(&comp->kobj));
> +				ret = -ETIMEDOUT;
> +				break;
> +			}
> +			retry = true;
> +		}
> +	} while (retry);

[Severity: High]
In prime_fw_component(), retry is initialized to false once before the
do-while loop rather than being reset on each iteration.

If the first ARM_SMCCC_LFA_PRIME call returns LFA_SUCCESS with
LFA_PRIME_CALL_AGAIN set in regs.a1, retry is set to true. When a subsequent
iteration completes priming and returns LFA_SUCCESS with LFA_PRIME_CALL_AGAIN
cleared (regs.a1 == 0), the if ((regs.a1 & LFA_PRIME_CALL_AGAIN)) block is
skipped and retry remains true. Because both the msleep_interruptible() call
and the timeout check ktime_before(ktime_get(), end) are inside that if
block while touch_nmi_watchdog() is called on every iteration, won't
prime_fw_component() spin in an infinite loop calling ARM_SMCCC_LFA_PRIME
until the firmware returns an error?

[Severity: Low]
As reported by Sashiko on v2 [5], the pr_err("LFA_PRIME for component %s
timed out", ...) format string in prime_fw_component() is missing a trailing
newline character.

[ ... ]

> +static ssize_t force_cpu_rendezvous_store(struct kobject *kobj,
> +					  struct kobj_attribute *attr,
> +					  const char *buf, size_t count)
> +{
> +	struct fw_component *comp = kobj_to_fw_component(kobj);
> +	int ret;
> +
> +	ret = kstrtobool(buf, &comp->cpu_rendezvous_forced);
> +	if (ret)
> +		return ret;
> +
> +	return count;
> +}

[Severity: Medium]
As reported by Mark Rutland on v4 [6], should the driver expose
force_cpu_rendezvous_store() to allow the administrator to disable CPU
rendezvous via sysfs?

When a user writes 0 to force_cpu_rendezvous on a component where
comp->cpu_rendezvous is false, activate_fw_component() invokes
call_lfa_activate() directly without stop_machine(), which can break the
kernel if other CPUs invoke firmware services concurrently during activation
and the administrator has no way of knowing what firmware services the kernel
depends upon. In the same v4 discussion, it was also asked whether the LFA
specification guarantees that LFA_GET_INVENTORY cannot return multiple entries
with the same UUID, since update_fw_components_node() matches existing nodes
by UUID string and overwrites the existing entry if a duplicate UUID is
returned.

[ ... ]

> +static int lfa_smccc_probe(struct arm_smccc_device *sdev)
> +{

[ ... ]

> +	init_rwsem(&smc_lock);
> +
> +	err = update_fw_components_tree();
> +	if (err != 0) {
> +		kset_unregister(lfa_kset);
> +		destroy_workqueue(fw_comps_update_wq);
> +	}
> +
> +	return err;
> +}

[Severity: Medium]
As reported by Sashiko on v2 [7], in lfa_smccc_probe(),
update_fw_components_tree() is called without holding smc_lock. Because each
iteration of update_fw_components_tree() calls update_fw_components_node() ->
kobject_init_and_add(), component directories and their activate sysfs
attributes are immediately published to userspace before probe finishes
enumerating the remaining components. If userspace writes to activate on an
already-published component while lfa_smccc_probe() is still running, can
activate_store() run update_fw_components_tree() concurrently with the
unlocked update_fw_components_tree() in lfa_smccc_probe()?

[Severity: Medium]
As reported by Sashiko on v2 [8], if update_fw_components_node() fails
mid-loop in update_fw_components_tree() after earlier iterations have already
registered child comp->kobj entries under lfa_kset, the error path in
lfa_smccc_probe() calls kset_unregister(lfa_kset) without calling
clean_fw_components_tree(). Because kset_unregister() only drops the kset's
own reference and does not delete or put child kobjects in lfa_kset->list,
won't the previously added fw_component kobjects and lfa_kset be leaked?

> +
> +static void lfa_smccc_remove(struct arm_smccc_device *sdev)
> +{
> +	clean_fw_components_tree();
> +	flush_workqueue(fw_comps_update_wq);
> +	destroy_workqueue(fw_comps_update_wq);
> +	kset_unregister(lfa_kset);
> +}

[Severity: Medium]
In activate_store(), prime_fw_component() and activate_fw_component() only
hold smc_lock around individual SMCCC calls and drop it across
msleep_interruptible() polling delays and between priming and activation.

If two processes write to activate concurrently, their LFA_PRIME,
LFA_ACTIVATE, and LFA_CANCEL calls can interleave. In addition, if
lfa_smccc_remove() runs while an activate_store() write is in flight,
clean_fw_components_tree() moves all current entries off lfa_kset->list and
blocks in kobject_del() waiting for the active sysfs write to finish. While
activate_store() completes, activate_fw_component() can call
update_fw_components_tree(), which sees lfa_kset->list as empty and adds
fresh fw_component kobjects with fw_seq_id >= 0 to lfa_kset->list. When
kobject_del() returns, flush_workqueue() runs remove_invalid_fw_components()
(which ignores entries with fw_seq_id >= 0), and kset_unregister(lfa_kset) is
called with those newly created child kobjects still registered. Should
activations be serialized and prevented before clean_fw_components_tree() in
lfa_smccc_remove()?

[ ... ]

> diff --git a/include/linux/arm-smccc.h b/include/linux/arm-smccc.h
> index 4de81848fe2ee..381d527c16a4c 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_VERSION		ARM_SMCCC_LFA_FN(0)
> +#define ARM_SMCCC_LFA_FEATURES		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)

[Severity: Low]
As reported by Sudeep Holla on v3 [9], only ARM_SMCCC_LFA_VERSION is used
outside drivers/firmware/smccc/lfa_fw.c (for device discovery in
drivers/firmware/smccc/smccc.c). Should ARM_SMCCC_LFA_FEATURES,
ARM_SMCCC_LFA_GET_INFO, ARM_SMCCC_LFA_GET_INVENTORY, ARM_SMCCC_LFA_PRIME,
ARM_SMCCC_LFA_ACTIVATE, and ARM_SMCCC_LFA_CANCEL be moved into
drivers/firmware/smccc/lfa_fw.c rather than exposed in
include/linux/arm-smccc.h?

[1] https://lore.kernel.org/r/20260317103336.1273582-3-andre.przywara@arm.com
[2] https://lore.kernel.org/r/20260317103336.1273582-3-andre.przywara@arm.com
[3] https://lore.kernel.org/r/20260317103336.1273582-9-andre.przywara@arm.com
[4] https://lore.kernel.org/r/20260317103336.1273582-3-andre.przywara@arm.com
[5] https://lore.kernel.org/r/20260317103336.1273582-5-andre.przywara@arm.com
[6] https://lore.kernel.org/r/aq1XmaBemuGybxp2@J2N7QTR9R3
[7] https://lore.kernel.org/r/20260317103336.1273582-9-andre.przywara@arm.com
[8] https://lore.kernel.org/r/20260317103336.1273582-3-andre.przywara@arm.com
[9] https://lore.kernel.org/r/20260717-bipedal-courageous-alpaca-4fb54d@sudeepholla

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102165421.3651202-1-andre.przywara@arm.com?part=2

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

* Re: [PATCH v5 RESEND 3/6] firmware: smccc: lfa: Register ACPI notification
  2026-11-02 16:54 ` [PATCH v5 RESEND 3/6] firmware: smccc: lfa: Register ACPI notification Andre Przywara
  2026-11-04 20:03   ` Nirmoy Das
@ 2026-11-05 16:14   ` sashiko-bot
  1 sibling, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-11-05 16:14 UTC (permalink / raw)
  To: Andre Przywara; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] firmware: smccc: lfa: inverted error check in lfa_smccc_probe() tears down on 0
- [High] firmware: smccc: lfa: missing component cleanup on error in lfa_smccc_probe()
- [High] firmware: smccc: lfa: stale fw_seq_id used in activate_pending_components()
- [High] firmware: smccc: lfa: infinite loop in prime_fw_component() when retry is set
- [High] firmware: smccc: lfa: race with remove_invalid_fw_components() on activation
- [Medium] firmware: smccc: lfa: dangling ACPI companion pointer in lfa_remove_acpi()
- [Low] firmware: smccc: lfa: unused DRIVER_NAME macro and stale commit description
--

commit 9b467f592a89b6ca0bddc96f9df536713b9c357c
Author: Vedashree Vidwans <vvidwans@nvidia.com>

firmware: smccc: lfa: Register ACPI notification

This commit adds ACPI notification support for Arm Live Firmware Activation
(LFA) so the platform can notify the kernel when pending firmware component
updates are ready to be primed and activated.

>     firmware: smccc: lfa: Register ACPI notification
>     
>     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>
> 
> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
> index db2a7fe344d5d..4b4daf79611bb 100644
> --- a/drivers/firmware/smccc/lfa_fw.c
> +++ b/drivers/firmware/smccc/lfa_fw.c
[ ... ]
> @@ -49,11 +51,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"

[Severity: Low]
This isn't a bug, but DRIVER_NAME does not appear to be used anywhere in
drivers/firmware/smccc/lfa_fw.c.

Also, the commit message still describes adding a faux device and
re-enumerating images in a loop on activation, whereas the patch uses the
existing arm_smccc_device on the SMCCC bus and snapshots the candidate array
once in activate_pending_components(). Could the unused macro be dropped and
the commit description updated to match the implementation?

>  #undef pr_fmt
>  #define pr_fmt(fmt) "Arm LFA: " fmt
>  
> @@ -753,6 +757,135 @@ static void clean_fw_components_tree(void)
>  	}
>  }
>  
> +/*
> + * Iterate over all FW components and trigger activation of all activatible and
> + * pending components.
> + * Since we would need to re-enumerate the list of all components after a
> + * successful activation, and this could trigger infinite loops if a firmware
> + * component still appears as pending, we create an array of update candidates
> + * first, then activate each one of them.
> + */
> +static void activate_pending_components(void)
> +{
> +	struct fw_component **all_comps;
> +	struct fw_component *comp;
> +	struct kobject *kobj;
> +	int nr_comps = 0;
> +	int ret, i;
> +
> +	down_read(&smc_lock);
> +	spin_lock(&lfa_kset->list_lock);
> +	all_comps = kmalloc_array(list_count_nodes(&lfa_kset->list),
> +				  sizeof(*all_comps), GFP_NOWAIT);
> +	if (!all_comps) {
> +		spin_unlock(&lfa_kset->list_lock);
> +		up_read(&smc_lock);
> +		pr_warn("LFA: automatic activation failed: -ENOMEM\n");
> +		return;
> +	}
> +
> +	list_for_each_entry(kobj, &lfa_kset->list, entry) {
> +		comp = kobj_to_fw_component(kobj);
> +
> +		if (comp->fw_seq_id == -1)
> +			continue; /* Invalid FW component */
> +
> +		_update_fw_component_pending(comp);
> +		if (comp->activation_capable && comp->activation_pending) {
> +			kobject_get(&comp->kobj);
> +			all_comps[nr_comps++] = comp;
> +		}
> +	}
> +	spin_unlock(&lfa_kset->list_lock);
> +	up_read(&smc_lock);
> +
> +	for (i = 0; i < nr_comps; i++) {
> +		comp = all_comps[i];
> +
> +		ret = prime_fw_component(comp);

[Severity: High]
As reported by Sashiko on v2 [1], can activate_pending_components() end up
calling prime_fw_component() and activate_fw_component() with an invalidated
comp->fw_seq_id of -1 when multiple components are in all_comps[] or when a
concurrent activation runs?

In activate_pending_components(), candidate pointers are collected into
all_comps[] under smc_lock and lfa_kset->list_lock, and then both locks are
released before the activation loop. When all_comps[0] is activated,
activate_fw_component() calls update_fw_components_tree() under
down_write(&smc_lock):

drivers/firmware/smccc/lfa_fw.c:update_fw_components_tree() {
	...
	spin_lock(&lfa_kset->list_lock);
	list_for_each_entry(kobj, &lfa_kset->list, entry) {
		struct fw_component *comp = kobj_to_fw_component(kobj);

		set_component_flags(comp, -1, 0b1000, 0, 0);
	}
	spin_unlock(&lfa_kset->list_lock);
	...
}

If a subsequent entry all_comps[i] is no longer present in the firmware
inventory after the earlier activation, had its activation_pending or
activation_capable bit cleared, or if update_fw_components_tree() failed
partway through, its comp->fw_seq_id remains -1.

Because prime_fw_component(), activate_fw_component(), and lfa_cancel() read
comp->fw_seq_id under smc_lock without checking whether comp->fw_seq_id == -1
or whether the component is still pending and activation-capable:

drivers/firmware/smccc/lfa_fw.c:prime_fw_component() {
	...
		touch_nmi_watchdog();
		down_read(&smc_lock);
		regs.a1 = comp->fw_seq_id;
		arm_smccc_1_2_invoke(&regs, &regs);
		up_read(&smc_lock);
	...
}

won't the driver invoke ARM_SMCCC_LFA_PRIME, ARM_SMCCC_LFA_ACTIVATE, and
ARM_SMCCC_LFA_CANCEL with regs.a1 = -1? Also, during the initial scan,
_update_fw_component_pending() only refreshes activation_pending without
updating activation_capable, may_reset_cpu, or cpu_rendezvous.

[Severity: High]
What happens in prime_fw_component() when the first ARM_SMCCC_LFA_PRIME call
returns LFA_PRIME_CALL_AGAIN in regs.a1 and a subsequent call completes with
regs.a1 == 0?

Looking at prime_fw_component() in drivers/firmware/smccc/lfa_fw.c:

drivers/firmware/smccc/lfa_fw.c:prime_fw_component() {
	...
	bool retry = false;
	...
	do {
		...
		if ((regs.a1 & LFA_PRIME_CALL_AGAIN)) {
			if (ktime_before(ktime_get(), end)) {
				if (msleep_interruptible(LFA_PRIME_DELAY_MS)) {
					ret = -EINTR;
					break;
				}
			} else {
				pr_err("LFA_PRIME for component %s timed out",
				       kobject_name(&comp->kobj));
				ret = -ETIMEDOUT;
				break;
			}
			retry = true;
		}
	} while (retry);
	...
}

Once retry is set to true on an iteration where LFA_PRIME_CALL_AGAIN is set,
nothing resets retry to false when a later call succeeds with
LFA_PRIME_CALL_AGAIN cleared. Because the sleep and timeout checks are both
inside the if ((regs.a1 & LFA_PRIME_CALL_AGAIN)) block and
touch_nmi_watchdog() is called on each pass, wouldn't this spin in an
unthrottled infinite loop issuing ARM_SMCCC_LFA_PRIME calls?

> +		if (ret) {
> +			pr_warn("LFA: LFA_PRIME failed for component %s\n",
> +				kobject_name(&comp->kobj));
> +			kobject_put(&comp->kobj);
> +			continue;
> +		}
> +
> +		ret = activate_fw_component(comp);

[Severity: High]
Could activating multiple components in activate_pending_components() race
with the asynchronous remove_invalid_fw_components() work item?

When the first component in all_comps[] is activated,
activate_fw_component() -> update_fw_components_tree() queues
fw_comps_update_work (remove_invalid_fw_components()) onto the unbound
workqueue fw_comps_update_wq and then drops smc_lock.

When the next component in all_comps[] is activated,
update_fw_components_tree() acquires lfa_kset->list_lock, resets
comp->fw_seq_id = -1 on every component in lfa_kset->list, and drops
lfa_kset->list_lock while querying ARM_SMCCC_LFA_GET_INVENTORY over SMC:

drivers/firmware/smccc/lfa_fw.c:update_fw_components_tree() {
	...
	spin_lock(&lfa_kset->list_lock);
	list_for_each_entry(kobj, &lfa_kset->list, entry) {
		struct fw_component *comp = kobj_to_fw_component(kobj);

		set_component_flags(comp, -1, 0b1000, 0, 0);
	}
	spin_unlock(&lfa_kset->list_lock);

	regs.a0 = ARM_SMCCC_LFA_GET_INVENTORY;
	for (int i = 0; i < num_of_components; i++) {
		...
}

Meanwhile, remove_invalid_fw_components() only takes lfa_kset->list_lock and
does not take smc_lock:

drivers/firmware/smccc/lfa_fw.c:remove_invalid_fw_components() {
	...
	spin_lock(&lfa_kset->list_lock);
	list_for_each_entry_safe(kobj, tmp, &lfa_kset->list, entry) {
		struct fw_component *comp = kobj_to_fw_component(kobj);

		if (comp->fw_seq_id == -1)
			list_move_tail(&kobj->entry, &comps_to_delete);
	}
	spin_unlock(&lfa_kset->list_lock);
	...
}

If remove_invalid_fw_components() runs during that window, won't it see
comp->fw_seq_id == -1 on valid components that have not yet been refreshed by
the inventory loop, remove them from lfa_kset->list, and delete their sysfs
kobjects?

[ ... ]
> +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);
> +}

[Severity: Medium]
In lfa_register_acpi(), ACPI_COMPANION_SET(dev, acpi_dev) attaches acpi_dev
as the fwnode companion on sdev->dev. When lfa_remove_acpi() later drops the
reference with acpi_dev_put(acpi_dev) on driver unbind or module unload,
should it also clear the companion pointer via ACPI_COMPANION_SET(dev, NULL)
so the persistent arm_smccc_device is not left with a dangling unreferenced
fwnode pointer?

[ ... ]
> @@ -813,13 +946,32 @@ static int lfa_smccc_probe(struct arm_smccc_device *sdev)
>  	if (err != 0) {
>  		kset_unregister(lfa_kset);
>  		destroy_workqueue(fw_comps_update_wq);
> +		return err;
> +	}
> +
> +	if (!acpi_disabled) {
> +		err = lfa_register_acpi(&sdev->dev);
> +		if (!err)
> +			pr_info("registered LFA ACPI notification\n");
> +	}
> +
> +	/*
> +	 * An ACPI notification or an interrupt are optional, but we must
> +	 * handle errors that occurred during their setup.
> +	 */
> +	if (err != -ENODEV) {
> +		kset_unregister(lfa_kset);
> +		destroy_workqueue(fw_comps_update_wq);
> +		return err;
>  	}

[Severity: Critical]
As reported by Sudeep Holla on v4 [2], when lfa_register_acpi() succeeds (or
when acpi_disabled is true after update_fw_components_tree() succeeded), err
is 0.

Because 0 != -ENODEV is true, won't lfa_smccc_probe() enter this error block,
unregister lfa_kset, destroy fw_comps_update_wq, and then return 0 (success)
with the ACPI notify handler still registered? Any subsequent 0x80 ACPI
notification or driver removal in lfa_smccc_remove() will then access the
destroyed lfa_kset and fw_comps_update_wq. Should this check be:

	if (err && err != -ENODEV)

instead?

[Severity: High]
On the same error path in lfa_smccc_probe(), if lfa_register_acpi() returns a
real error (such as -EIO) after update_fw_components_tree() has already
populated lfa_kset->list and queued fw_comps_update_work onto
fw_comps_update_wq, doesn't this unwind path leak all child struct
fw_component kobjects and race with the queued work item?

In lfa_smccc_remove(), teardown cleans the component tree and flushes/destroys
the workqueue before unregistering lfa_kset:

drivers/firmware/smccc/lfa_fw.c:lfa_smccc_remove() {
	if (!acpi_disabled)
		lfa_remove_acpi(&sdev->dev);
	clean_fw_components_tree();
	flush_workqueue(fw_comps_update_wq);
	destroy_workqueue(fw_comps_update_wq);
	kset_unregister(lfa_kset);
}

Should the probe failure path after update_fw_components_tree() follow the
same cleanup sequence?

[1] https://lore.kernel.org/r/20260317103336.1273582-9-andre.przywara@arm.com
[2] https://lore.kernel.org/r/20260921-smiling-rare-salamander-6c442d@sudeepholla

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102165421.3651202-1-andre.przywara@arm.com?part=3

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

* Re: [PATCH v5 RESEND 4/6] firmware: smccc: lfa: Add auto_activate sysfs file
  2026-11-02 16:54 ` [PATCH v5 RESEND 4/6] firmware: smccc: lfa: Add auto_activate sysfs file Andre Przywara
@ 2026-11-05 16:25   ` sashiko-bot
  0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-11-05 16:25 UTC (permalink / raw)
  To: Andre Przywara; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] firmware: smccc: lfa: teardown on success in lfa_smccc_probe()
- [High] firmware: smccc: lfa: infinite loop in prime_fw_component() on retry
- [Medium] firmware: smccc: lfa: kobject leak in update_fw_components_tree()
--

commit 9e007983d149d16e1c362d811e6caba071d543eb
Author: Andre Przywara <andre.przywara@arm.com>

firmware: smccc: lfa: Add auto_activate sysfs file

This commit adds a per-component auto_activate sysfs attribute to control
whether platform-initiated notifications automatically trigger firmware
activation in activate_pending_components().

> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
> index 4b4daf79611bb..65f28ffc9d108 100644
> --- a/drivers/firmware/smccc/lfa_fw.c
> +++ b/drivers/firmware/smccc/lfa_fw.c

[ ... ]

> @@ -306,6 +308,7 @@ static int update_fw_components_node(char *fw_uuid, int seq_id,
>  
>  	comp->kobj.kset = lfa_kset;
>  	comp->cpu_rendezvous_forced = true;
> +	comp->auto_activate = false;
>  	set_component_flags(comp, seq_id, comp_flags, reg_current_ver,
>  			    reg_pending_ver);
>  	if (kobject_init_and_add(&comp->kobj, &component_ktype, NULL,

[Severity: Medium]
As reported by Sashiko on v2 [1], if update_fw_components_node() fails after
earlier iterations of the loop in update_fw_components_tree() have already
registered fw_component kobjects:

drivers/firmware/smccc/lfa_fw.c:update_fw_components_tree() {
	...
	for (int i = 0; i < num_of_components; i++) {
		...
		if (res.a0 == LFA_SUCCESS) {
			...
			ret = update_fw_components_node(comp_id_str, i, res.a3,
							res.a4, res.a5);
			if (ret)
				return ret;
		}
	}
	...
}

and lfa_smccc_probe() then unwinds on that error:

drivers/firmware/smccc/lfa_fw.c:lfa_smccc_probe() {
	...
	err = update_fw_components_tree();
	if (err != 0) {
		kset_unregister(lfa_kset);
		destroy_workqueue(fw_comps_update_wq);
		return err;
	}
	...
}

does this leak the fw_component kobjects (and their references on lfa_kset)
created in prior iterations because clean_fw_components_tree() is never
called before kset_unregister(lfa_kset)? Also, if this fails during runtime
activation, returning before queue_work() skips removing components whose
fw_seq_id was invalidated to -1 at the start of update_fw_components_tree().

[ ... ]

> @@ -791,7 +817,8 @@ static void activate_pending_components(void)
>  			continue; /* Invalid FW component */
>  
>  		_update_fw_component_pending(comp);
> -		if (comp->activation_capable && comp->activation_pending) {
> +		if (comp->activation_capable && comp->activation_pending &&
> +		    comp->auto_activate) {
>  			kobject_get(&comp->kobj);
>  			all_comps[nr_comps++] = comp;
>  		}

[Severity: High]
When an ACPI notification triggers lfa_acpi_notify() ->
activate_pending_components(), can lfa_kset already be unregistered and
freed after a successful probe?

In lfa_smccc_probe(), after update_fw_components_tree() succeeds with
err == 0, either acpi_disabled is true (leaving err == 0) or
lfa_register_acpi(&sdev->dev) succeeds and returns err == 0:

drivers/firmware/smccc/lfa_fw.c:lfa_smccc_probe() {
	...
	if (!acpi_disabled) {
		err = lfa_register_acpi(&sdev->dev);
		if (!err)
			pr_info("registered LFA ACPI notification\n");
	}

	/*
	 * An ACPI notification or an interrupt are optional, but we must
	 * handle errors that occurred during their setup.
	 */
	if (err != -ENODEV) {
		kset_unregister(lfa_kset);
		destroy_workqueue(fw_comps_update_wq);
		return err;
	}

	return 0;
}

Because 0 != -ENODEV is true, doesn't lfa_smccc_probe() tear down lfa_kset
and fw_comps_update_wq whenever probe succeeds, while returning 0 with the
driver still bound and the ACPI notify handler still installed (as well as
leaking the child fw_component kobjects without calling
clean_fw_components_tree())? Note that this remains present at the end of the
series when IRQ setup is added in commit 93a930701ee9.

[Severity: High]
When activate_pending_components() (or activate_store()) calls
prime_fw_component(comp), what happens if ARM_SMCCC_LFA_PRIME returns
LFA_PRIME_CALL_AGAIN on the first call and then succeeds with regs.a1 == 0 on
a subsequent iteration?

drivers/firmware/smccc/lfa_fw.c:prime_fw_component() {
	ktime_t end = ktime_add_ms(ktime_get(), LFA_PRIME_BUDGET_MS);
	bool retry = false;
	...
	do {
		...
		if ((regs.a1 & LFA_PRIME_CALL_AGAIN)) {
			if (ktime_before(ktime_get(), end)) {
				if (msleep_interruptible(LFA_PRIME_DELAY_MS)) {
					ret = -EINTR;
					break;
				}
			} else {
				pr_err("LFA_PRIME for component %s timed out",
				       kobject_name(&comp->kobj));
				ret = -ETIMEDOUT;
				break;
			}
			retry = true;
		}
	} while (retry);
	...
}

Since retry is initialized before the do-while loop and never reset to false
when LFA_PRIME_CALL_AGAIN is cleared, won't the loop spin indefinitely
calling ARM_SMCCC_LFA_PRIME without sleeping once priming finishes?

[1] https://lore.kernel.org/r/20260317103336.1273582-7-andre.przywara@arm.com

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102165421.3651202-1-andre.przywara@arm.com?part=4

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

* Re: [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt
  2026-11-02 16:54 ` [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt Andre Przywara
                     ` (2 preceding siblings ...)
  2026-11-03  9:25   ` kernel test robot
@ 2026-11-05 16:45   ` sashiko-bot
  3 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-11-05 16:45 UTC (permalink / raw)
  To: Andre Przywara; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] firmware: smccc: lfa: probe success treated as error in lfa_smccc_probe()
- [High] firmware: smccc: lfa: invalid IRQF_COND_ONESHOT in lfa_register_dt()
- [High] firmware: smccc: lfa: infinite retry loop in prime_fw_component()
- [Medium] firmware: smccc: lfa: unconditional devm_free_irq() in lfa_smccc_remove()
- [Medium] firmware: smccc: lfa: missing kobject cleanup on lfa_smccc_probe() error
- [Low] firmware: smccc: lfa: missing linux/interrupt.h include in lfa_fw.c
--

commit 336251e18192897c278c68cc88128e6b0243d876
Author: Andre Przywara <andre.przywara@arm.com>

firmware: smccc: lfa: Register DT interrupt

This commit adds support for finding an arm,lfa devicetree node and
registering its threaded interrupt handler to trigger pending Live
Firmware Activation updates when ACPI is disabled.

> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
> index 65f28ffc9d108..3ff368dca9497 100644
> --- a/drivers/firmware/smccc/lfa_fw.c
> +++ b/drivers/firmware/smccc/lfa_fw.c
> @@ -46,6 +46,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/rwsem.h>
>  #include <linux/stop_machine.h>

[Severity: Low]
As reported by kernel test robot on v2 [1], does drivers/firmware/smccc/lfa_fw.c
fail to build when CONFIG_ACPI is disabled?

The new code uses irqreturn_t, IRQ_HANDLED, IRQF_COND_ONESHOT,
devm_request_threaded_irq(), and devm_free_irq(), which are declared in
linux/interrupt.h. When CONFIG_ACPI=y, linux/acpi.h pulls in
linux/interrupt.h transitively, but with CONFIG_ACPI=n neither linux/of.h
nor linux/of_irq.h includes it, resulting in implicit function declaration
and undeclared identifier errors. Should linux/interrupt.h be included
explicitly here?

[ ... ]

> @@ -913,6 +916,31 @@ static void lfa_remove_acpi(struct device *dev)
>  }
>  #endif
>  
> +static irqreturn_t lfa_irq_handler_thread(int irq, void *dev_id)
> +{
> +	activate_pending_components();
> +
> +	return IRQ_HANDLED;
> +}

[Severity: High]
When lfa_irq_handler_thread() invokes activate_pending_components() ->
prime_fw_component(), can prime_fw_component() get stuck in an infinite
loop if priming requires at least one retry?

In drivers/firmware/smccc/lfa_fw.c:prime_fw_component():

	bool retry = false;
	...
	do {
		...
		if ((regs.a1 & LFA_PRIME_CALL_AGAIN)) {
			if (ktime_before(ktime_get(), end)) {
				if (msleep_interruptible(LFA_PRIME_DELAY_MS)) {
					ret = -EINTR;
					break;
				}
			} else {
				pr_err("LFA_PRIME for component %s timed out",
				       kobject_name(&comp->kobj));
				ret = -ETIMEDOUT;
				break;
			}
			retry = true;
		}
	} while (retry);

Once LFA_PRIME_CALL_AGAIN is set on an initial ARM_SMCCC_LFA_PRIME call,
retry is set to true. On a subsequent iteration where firmware finishes
priming and returns regs.a0 == 0 with LFA_PRIME_CALL_AGAIN cleared in
regs.a1, the if ((regs.a1 & LFA_PRIME_CALL_AGAIN)) branch is not entered
and retry is never reset to false, causing while (retry) to spin
indefinitely issuing SMC calls without sleeping or checking the timeout.

> +
> +static int lfa_register_dt(struct device *dev)
> +{
> +	struct device_node *np;
> +
> +	np = of_find_compatible_node(NULL, NULL, "arm,lfa");
> +	if (!np)
> +		return -ENODEV;
> +
> +	lfa_irq = irq_of_parse_and_map(np, 0);
> +	of_node_put(np);
> +	if (!lfa_irq)
> +		return -ENODEV;
> +
> +	return devm_request_threaded_irq(dev, lfa_irq, NULL,
> +					 lfa_irq_handler_thread, IRQF_COND_ONESHOT,
> +					 NULL, NULL);
> +}

[Severity: High]
As reported by Sashiko on v2 [2], will devm_request_threaded_irq() in
lfa_register_dt() always fail with -EINVAL on standard Arm GIC interrupt
controllers?

Passing NULL as the primary handler causes request_threaded_irq() to assign
irq_default_primary_handler. In __setup_irq(), IRQF_COND_ONESHOT only sets
IRQF_ONESHOT when joining an already-shared interrupt line whose existing
action has IRQF_ONESHOT set. Because this interrupt is not shared,
IRQF_ONESHOT remains unset, and __setup_irq() rejects the request:

	} else if (new->handler == irq_default_primary_handler &&
		   !(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {
		pr_err("Threaded irq requested with handler=NULL and !ONESHOT for %s (irq %d)\n",
		       new->name, irq);
		ret = -EINVAL;
		goto out_unlock;
	}

Since standard Arm GIC irqchips do not set IRQCHIP_ONESHOT_SAFE, this
returns -EINVAL and aborts lfa_smccc_probe(). Should this pass IRQF_ONESHOT
instead of IRQF_COND_ONESHOT?

> +
>  static bool lfa_smccc_has_all_functions(void)
>  {
>  	struct arm_smccc_1_2_regs regs = { 0 }, res;
> @@ -980,6 +1008,10 @@ static int lfa_smccc_probe(struct arm_smccc_device *sdev)
>  		err = lfa_register_acpi(&sdev->dev);
>  		if (!err)
>  			pr_info("registered LFA ACPI notification\n");
> +	} else {
> +		err = lfa_register_dt(&sdev->dev);
> +		if (!err)
> +			pr_info("registered LFA DT notification interrupt\n");
>  	}
>  
>  	/*

[Severity: Critical]
When lfa_register_dt() or lfa_register_acpi() succeeds and returns 0 in
lfa_smccc_probe(), doesn't the immediately following error check tear down
lfa_kset and fw_comps_update_wq while still returning 0 from probe?

In drivers/firmware/smccc/lfa_fw.c:lfa_smccc_probe():

	/*
	 * An ACPI notification or an interrupt are optional, but we must
	 * handle errors that occurred during their setup.
	 */
	if (err != -ENODEV) {
		kset_unregister(lfa_kset);
		destroy_workqueue(fw_comps_update_wq);
		return err;
	}

When err is 0, (err != -ENODEV) is true, so lfa_smccc_probe() unregisters
lfa_kset, destroys fw_comps_update_wq, and returns 0. Because probe returns
0, the device remains bound with the DT interrupt or ACPI notify handler
registered, leading to a use-after-free on lfa_kset and fw_comps_update_wq
when a notification fires or when lfa_smccc_remove() runs. Should the
condition be if (err && err != -ENODEV)?

[Severity: Medium]
As reported by Sudeep Holla on v4 [3], when lfa_smccc_probe() fails after
update_fw_components_tree() (for example when lfa_register_dt() or
lfa_register_acpi() returns a real error, or if update_fw_components_tree()
fails after adding components), does the error path leak the allocated
fw_component kobjects and race with fw_comps_update_work?

Unlike lfa_smccc_remove(), the error cleanup in lfa_smccc_probe():

	if (err != -ENODEV) {
		kset_unregister(lfa_kset);
		destroy_workqueue(fw_comps_update_wq);
		return err;
	}

does not call clean_fw_components_tree() to drop the child fw_component
kobjects added under lfa_kset, and it calls kset_unregister(lfa_kset)
before flushing and destroying fw_comps_update_wq while
remove_invalid_fw_components() may still be accessing lfa_kset.

> @@ -999,6 +1031,7 @@ static void lfa_smccc_remove(struct arm_smccc_device *sdev)
>  {
>  	if (!acpi_disabled)
>  		lfa_remove_acpi(&sdev->dev);
> +	devm_free_irq(&sdev->dev, lfa_irq, NULL);
>  	clean_fw_components_tree();
>  	flush_workqueue(fw_comps_update_wq);
>  	destroy_workqueue(fw_comps_update_wq);

[Severity: Medium]
In lfa_smccc_remove(), won't calling devm_free_irq() unconditionally
trigger a WARN_ON() when ACPI is used or when the optional DT interrupt
was not registered?

On ACPI systems (!acpi_disabled) or DT systems where the arm,lfa node or
its interrupt is absent, devm_request_threaded_irq() is never called and
lfa_irq remains 0. Calling devm_free_irq(&sdev->dev, lfa_irq, NULL) in
lfa_smccc_remove() causes devres_release() in kernel/irq/devres.c to fail
to find a matching devres entry and trigger WARN_ON(). Should
devm_free_irq() only be called when lfa_irq is non-zero (or omitted since
the IRQ is managed via devm on &sdev->dev)?

[1] https://lore.kernel.org/r/202603181913.NFRwBsN4-lkp@intel.com
[2] https://lore.kernel.org/r/20260317103336.1273582-8-andre.przywara@arm.com
[3] https://lore.kernel.org/r/20260921-aloof-sympathetic-python-54aa7d@sudeepholla

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102165421.3651202-1-andre.przywara@arm.com?part=5

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

* Re: [PATCH v5 RESEND 6/6] firmware: smccc: lfa: add sysfs ABI documentation
  2026-11-02 16:54 ` [PATCH v5 RESEND 6/6] firmware: smccc: lfa: add sysfs ABI documentation Andre Przywara
@ 2026-11-05 16:53   ` sashiko-bot
  0 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-11-05 16:53 UTC (permalink / raw)
  To: Andre Przywara; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] firmware: smccc: lfa: premature teardown on success in lfa_smccc_probe()
- [High] firmware: smccc: lfa: infinite loop in prime_fw_component() on retry
- [Medium] firmware: smccc: lfa: unconditional devm_free_irq() in lfa_smccc_remove()
- [Low] firmware: smccc: lfa: make file-local smc_lock static
--

commit 93a930701ee9d0e354315cd9d9c704901ce6e5c2
Author: Andre Przywara <andre.przywara@arm.com>

firmware: smccc: lfa: add sysfs ABI documentation

This series adds support for the Arm Live Firmware Activation (LFA)
specification over SMCCC, including sysfs attributes, ACPI notifications,
devicetree interrupts, and ABI documentation.

> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
> new file mode 100644
> index 0000000000000..3ff368dca9497
> --- /dev/null
> +++ b/drivers/firmware/smccc/lfa_fw.c

[ ... ]

> +static struct kset *lfa_kset;
> +static struct workqueue_struct *fw_comps_update_wq;
> +static struct work_struct fw_comps_update_work;
> +static struct attribute *component_default_attrs[LFA_ATTR_NR_IMAGES + 1];
> +static unsigned int lfa_irq;
> +
> +/*
> + * A successful component activation might change the number of available
> + * components, leading to a re-order and thus re-assignment of the sequence IDs.
> + * The lock protects the connection between a firmware component (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;

[Severity: Low]
This isn't a bug, but since smc_lock is only used within
drivers/firmware/smccc/lfa_fw.c, should it be declared static like the
other file-scope variables above?

[ ... ]

> +static int prime_fw_component(struct fw_component *comp)
> +{
> +	ktime_t end = ktime_add_ms(ktime_get(), LFA_PRIME_BUDGET_MS);
> +	bool retry = false;
> +	int ret = 0;
> +
> +	if (comp->may_reset_cpu) {
> +		pr_err("CPU reset not supported by kernel driver\n");
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * LFA_PRIME will return 1 in regs.a1 if the firmware priming
> +	 * is still in progress. In that case LFA_PRIME will need to
> +	 * be called again.
> +	 * regs.a1 will become 0 once the prime process completes.
> +	 */
> +	do {
> +		struct arm_smccc_1_2_regs regs = {
> +			.a0 = ARM_SMCCC_LFA_PRIME,
> +		};
> +
> +		touch_nmi_watchdog();
> +		down_read(&smc_lock);
> +		regs.a1 = comp->fw_seq_id;
> +		arm_smccc_1_2_invoke(&regs, &regs);
> +		up_read(&smc_lock);
> +
> +		if ((s64)regs.a0 < 0) {
> +			pr_err("LFA_PRIME for component %s failed: %s (%ld)\n",
> +			       kobject_name(&comp->kobj),
> +			       lfa_error_string((s64)regs.a0), regs.a0);
> +			return lfa_to_linux_errno((s64)regs.a0);
> +		}
> +
> +		if ((regs.a1 & LFA_PRIME_CALL_AGAIN)) {
> +			if (ktime_before(ktime_get(), end)) {
> +				if (msleep_interruptible(LFA_PRIME_DELAY_MS)) {
> +					ret = -EINTR;
> +					break;
> +				}
> +			} else {
> +				pr_err("LFA_PRIME for component %s timed out",
> +				       kobject_name(&comp->kobj));
> +				ret = -ETIMEDOUT;
> +				break;
> +			}
> +			retry = true;
> +		}
> +	} while (retry);

[Severity: High]
Can prime_fw_component() enter an infinite loop when firmware priming
takes more than one call?

In prime_fw_component(), retry is initialized to false before the do-while
loop rather than being reset on each iteration. If the first
ARM_SMCCC_LFA_PRIME call returns LFA_SUCCESS with LFA_PRIME_CALL_AGAIN set
in regs.a1, retry is set to true.

When a subsequent ARM_SMCCC_LFA_PRIME call completes priming and returns
LFA_SUCCESS with LFA_PRIME_CALL_AGAIN cleared, the if block checking
LFA_PRIME_CALL_AGAIN is skipped while retry remains true. Because the
timeout check and msleep_interruptible() are inside that conditional block,
will the loop spin indefinitely calling ARM_SMCCC_LFA_PRIME?

[ ... ]

> +static int lfa_smccc_probe(struct arm_smccc_device *sdev)
> +{

[ ... ]

> +	err = update_fw_components_tree();
> +	if (err != 0) {
> +		kset_unregister(lfa_kset);
> +		destroy_workqueue(fw_comps_update_wq);
> +		return err;
> +	}
> +
> +	if (!acpi_disabled) {
> +		err = lfa_register_acpi(&sdev->dev);
> +		if (!err)
> +			pr_info("registered LFA ACPI notification\n");
> +	} else {
> +		err = lfa_register_dt(&sdev->dev);
> +		if (!err)
> +			pr_info("registered LFA DT notification interrupt\n");
> +	}
> +
> +	/*
> +	 * An ACPI notification or an interrupt are optional, but we must
> +	 * handle errors that occurred during their setup.
> +	 */
> +	if (err != -ENODEV) {
> +		kset_unregister(lfa_kset);
> +		destroy_workqueue(fw_comps_update_wq);
> +		return err;
> +	}
> +
> +	return 0;
> +}

[Severity: High]
When lfa_register_acpi() or lfa_register_dt() succeeds in
lfa_smccc_probe(), err is 0. Since 0 != -ENODEV evaluates to true, doesn't
this error path tear down lfa_kset and fw_comps_update_wq on a successful
registration while still returning 0 from probe, leaving child component
kobjects and the notification handler or interrupt active with freed
resources?

Also, if notification setup fails with a real error after
update_fw_components_tree() has populated component kobjects, should
clean_fw_components_tree() and flush_workqueue() be called before
destroying fw_comps_update_wq and unregistering lfa_kset?

> +
> +static void lfa_smccc_remove(struct arm_smccc_device *sdev)
> +{
> +	if (!acpi_disabled)
> +		lfa_remove_acpi(&sdev->dev);
> +	devm_free_irq(&sdev->dev, lfa_irq, NULL);
> +	clean_fw_components_tree();
> +	flush_workqueue(fw_comps_update_wq);
> +	destroy_workqueue(fw_comps_update_wq);
> +	kset_unregister(lfa_kset);
> +}

[Severity: Medium]
In lfa_smccc_remove(), devm_free_irq() is called unconditionally even when
running with ACPI enabled or when no arm,lfa DT interrupt was registered
during lfa_smccc_probe(). Will this trigger the WARN_ON in devm_free_irq()
when unbinding the driver or unloading the module on systems where no DT
interrupt was requested?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102165421.3651202-1-andre.przywara@arm.com?part=6

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

end of thread, other threads:[~2026-11-05 16:53 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02 16:54 [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support Andre Przywara
2026-11-02 16:54 ` [PATCH v5 RESEND 1/6] dt-bindings: arm: Add Live Firmware Activation Andre Przywara
2026-11-02 16:54 ` [PATCH v5 RESEND 2/6] firmware: smccc: Add support for Live Firmware Activation (LFA) Andre Przywara
2026-11-04 22:50   ` Vedashree Vidwans
2026-11-05 15:56   ` sashiko-bot
2026-11-02 16:54 ` [PATCH v5 RESEND 3/6] firmware: smccc: lfa: Register ACPI notification Andre Przywara
2026-11-04 20:03   ` Nirmoy Das
2026-11-04 23:02     ` Vedashree Vidwans
2026-11-05 16:14   ` sashiko-bot
2026-11-02 16:54 ` [PATCH v5 RESEND 4/6] firmware: smccc: lfa: Add auto_activate sysfs file Andre Przywara
2026-11-05 16:25   ` sashiko-bot
2026-11-02 16:54 ` [PATCH v5 RESEND 5/6] firmware: smccc: lfa: Register DT interrupt Andre Przywara
2026-11-03  4:20   ` kernel test robot
2026-11-03  7:50   ` kernel test robot
2026-11-03  9:25   ` kernel test robot
2026-11-05 16:45   ` sashiko-bot
2026-11-02 16:54 ` [PATCH v5 RESEND 6/6] firmware: smccc: lfa: add sysfs ABI documentation Andre Przywara
2026-11-05 16:53   ` sashiko-bot
2026-11-05  9:17 ` [PATCH v5 RESEND 0/6] Arm Live Firmware Activation (LFA) support Nirmoy Das

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