mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v5 0/8] Arm Live Firmware Activation (LFA) support
@ 2026-10-16 18:27 Andre Przywara
  2026-10-16 18:27 ` [PATCH v5 2/8] firmware: hwrng: arm_smccc_trng: Register as an SMCCC device Andre Przywara
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Andre Przywara @ 2026-10-16 18:27 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,

an 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.

I added the two SMCCC bus prerequisite patches to this series, just to
trigger a Sashiko review, which was missing so far. I am sure it will be
a feast for Sashiko to tear this apart ;-) Those two patches have already
been queued in arm64/for-next, for the v7.4 merge window, but I will need
to send at least another revision after the merge window anyway, so I just
added them here meanwhile, for this one version. Please ignore them for
review purposes.

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

Based on v7.3-rc1, plus those first two patches from Aneesh's SMCCC bus[1]
series. 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 4/8.
Based on v7.3-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

Aneesh Kumar K.V (Arm) (2):
  firmware: smccc: Add an Arm SMCCC bus
  firmware: hwrng: arm_smccc_trng: Register as an SMCCC device

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 +
 MAINTAINERS                                   |    2 +
 arch/arm/include/asm/archrandom.h             |    2 +-
 arch/arm64/include/asm/archrandom.h           |    2 +-
 drivers/char/hw_random/arm_smccc_trng.c       |   32 +-
 drivers/firmware/smccc/Kconfig                |   10 +
 drivers/firmware/smccc/Makefile               |    3 +-
 drivers/firmware/smccc/bus.c                  |  158 +++
 drivers/firmware/smccc/lfa_fw.c               | 1057 +++++++++++++++++
 drivers/firmware/smccc/smccc.c                |   67 +-
 include/linux/arm-smccc-bus.h                 |   48 +
 include/linux/arm-smccc.h                     |   15 +
 include/linux/device-id/arm_smccc.h           |   19 +
 include/linux/mod_devicetable.h               |    1 +
 scripts/mod/devicetable-offsets.c             |    3 +
 scripts/mod/file2alias.c                      |    8 +
 17 files changed, 1560 insertions(+), 23 deletions(-)
 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/bus.c
 create mode 100644 drivers/firmware/smccc/lfa_fw.c
 create mode 100644 include/linux/arm-smccc-bus.h
 create mode 100644 include/linux/device-id/arm_smccc.h


base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
-- 
2.43.0


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

* [PATCH v5 2/8] firmware: hwrng: arm_smccc_trng: Register as an SMCCC device
  2026-10-16 18:27 [PATCH v5 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
@ 2026-10-16 18:27 ` Andre Przywara
  2026-10-16 18:27 ` [PATCH v5 3/8] dt-bindings: arm: Add Live Firmware Activation Andre Przywara
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Andre Przywara @ 2026-10-16 18:27 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: "Aneesh Kumar K.V (Arm)" <aneesh.kumar@kernel.org>

The SMCCC TRNG interface is a firmware-provided SMCCC service rather than a
standalone platform device. Now that the SMCCC core has an SMCCC bus,
create an arm-smccc-trng device for the discovered TRNG service and convert
the hwrng driver to an SMCCC driver.

The SMCCC id table preserves module autoloading for systems where the TRNG
driver is built as a module.

The sysfs device path changes from the old smccc_trng platform-device path
to an arm-smccc device path. No known userspace dependency on the old path
was found; a Debian Code Search lookup for the existing platform-device
name/path did not find any users.

Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Sudeep Holla <sudeep.holla@kernel.org>
Tested-by: Andre Przywara <andre.przywara@arm.com>
Cc: Russell King <linux@armlinux.org.uk>
Cc: Will Deacon <will@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Olivia Mackall <olivia@selenic.com>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Cc: Lorenzo Pieralisi <lpieralisi@kernel.org>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---
 arch/arm/include/asm/archrandom.h       |  2 +-
 arch/arm64/include/asm/archrandom.h     |  2 +-
 drivers/char/hw_random/arm_smccc_trng.c | 32 ++++++++-----
 drivers/firmware/smccc/smccc.c          | 62 +++++++++++++++++++++----
 4 files changed, 76 insertions(+), 22 deletions(-)

diff --git a/arch/arm/include/asm/archrandom.h b/arch/arm/include/asm/archrandom.h
index cc4714eb1a755..ee39a03ddf8ab 100644
--- a/arch/arm/include/asm/archrandom.h
+++ b/arch/arm/include/asm/archrandom.h
@@ -2,7 +2,7 @@
 #ifndef _ASM_ARCHRANDOM_H
 #define _ASM_ARCHRANDOM_H
 
-static inline bool __init smccc_probe_trng(void)
+static inline bool smccc_probe_trng(void)
 {
 	return false;
 }
diff --git a/arch/arm64/include/asm/archrandom.h b/arch/arm64/include/asm/archrandom.h
index 8babfbe31f954..7605dd81bd1e1 100644
--- a/arch/arm64/include/asm/archrandom.h
+++ b/arch/arm64/include/asm/archrandom.h
@@ -12,7 +12,7 @@
 
 extern bool smccc_trng_available;
 
-static inline bool __init smccc_probe_trng(void)
+static inline bool smccc_probe_trng(void)
 {
 	struct arm_smccc_res res;
 
diff --git a/drivers/char/hw_random/arm_smccc_trng.c b/drivers/char/hw_random/arm_smccc_trng.c
index dcb8e7f37f25c..01c1a30cbab8d 100644
--- a/drivers/char/hw_random/arm_smccc_trng.c
+++ b/drivers/char/hw_random/arm_smccc_trng.c
@@ -16,8 +16,10 @@
 #include <linux/device.h>
 #include <linux/hw_random.h>
 #include <linux/module.h>
-#include <linux/platform_device.h>
 #include <linux/arm-smccc.h>
+#include <linux/arm-smccc-bus.h>
+
+#include <asm/archrandom.h>
 
 #ifdef CONFIG_ARM64
 #define ARM_SMCCC_TRNG_RND	ARM_SMCCC_TRNG_RND64
@@ -94,29 +96,37 @@ static int smccc_trng_read(struct hwrng *rng, void *data, size_t max, bool wait)
 	return copied;
 }
 
-static int smccc_trng_probe(struct platform_device *pdev)
+static int smccc_trng_probe(struct arm_smccc_device *sdev)
 {
 	struct hwrng *trng;
 
-	trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL);
+	/* validate the minimum version requirement */
+	if (!smccc_probe_trng())
+		return -ENODEV;
+
+	trng = devm_kzalloc(&sdev->dev, sizeof(*trng), GFP_KERNEL);
 	if (!trng)
 		return -ENOMEM;
 
 	trng->name = "smccc_trng";
 	trng->read = smccc_trng_read;
 
-	return devm_hwrng_register(&pdev->dev, trng);
+	return devm_hwrng_register(&sdev->dev, trng);
 }
 
-static struct platform_driver smccc_trng_driver = {
-	.driver = {
-		.name		= "smccc_trng",
-	},
-	.probe		= smccc_trng_probe,
+static const struct arm_smccc_device_id smccc_trng_id_table[] = {
+	{ .func_id = ARM_SMCCC_TRNG_VERSION },
+	{}
+};
+MODULE_DEVICE_TABLE(arm_smccc, smccc_trng_id_table);
+
+static struct arm_smccc_driver smccc_trng_driver = {
+	.name	  = KBUILD_MODNAME,
+	.probe	  = smccc_trng_probe,
+	.id_table = smccc_trng_id_table,
 };
-module_platform_driver(smccc_trng_driver);
+module_arm_smccc_driver(smccc_trng_driver);
 
-MODULE_ALIAS("platform:smccc_trng");
 MODULE_AUTHOR("Andre Przywara");
 MODULE_DESCRIPTION("Arm SMCCC TRNG firmware interface support");
 MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/smccc/smccc.c b/drivers/firmware/smccc/smccc.c
index bdee057db2fd3..67e503dac409f 100644
--- a/drivers/firmware/smccc/smccc.c
+++ b/drivers/firmware/smccc/smccc.c
@@ -9,7 +9,8 @@
 #include <linux/init.h>
 #include <linux/arm-smccc.h>
 #include <linux/kernel.h>
-#include <linux/platform_device.h>
+#include <linux/arm-smccc-bus.h>
+
 #include <asm/archrandom.h>
 
 static u32 smccc_version = ARM_SMCCC_VERSION_1_0;
@@ -81,16 +82,59 @@ bool arm_smccc_hypervisor_has_uuid(const uuid_t *hyp_uuid)
 }
 EXPORT_SYMBOL_GPL(arm_smccc_hypervisor_has_uuid);
 
+struct smccc_device_info {
+	u32 func_id;
+	bool requires_smc;
+	const char *device_name;
+};
+
+static const struct smccc_device_info smccc_devices[] __initconst = {
+	{
+		.func_id        = ARM_SMCCC_TRNG_VERSION,
+		.requires_smc   = false,
+		.device_name    = "arm-smccc-trng",
+	},
+};
+
+static bool __init smccc_probe_smccc_device(const struct smccc_device_info *smccc_dev)
+{
+	int ret;
+	struct arm_smccc_res res = {};
+
+	if (smccc_conduit == SMCCC_CONDUIT_NONE)
+		return false;
+
+	if (smccc_dev->requires_smc && smccc_conduit != SMCCC_CONDUIT_SMC)
+		return false;
+
+	if (IS_ENABLED(CONFIG_ARM) && ARM_SMCCC_IS_64(smccc_dev->func_id))
+		return false;
+
+	arm_smccc_1_1_invoke(smccc_dev->func_id,
+			     0, 0, 0, 0, 0, 0, 0, &res);
+	ret = res.a0;
+
+	if (ret == SMCCC_RET_NOT_SUPPORTED)
+		return false;
+
+	return true;
+}
+
 static int __init smccc_devices_init(void)
 {
-	struct platform_device *pdev;
-
-	if (smccc_trng_available) {
-		pdev = platform_device_register_simple("smccc_trng", -1,
-						       NULL, 0);
-		if (IS_ERR(pdev))
-			pr_err("smccc_trng: could not register device: %ld\n",
-			       PTR_ERR(pdev));
+	struct arm_smccc_device *sdev;
+	const struct smccc_device_info *smccc_dev;
+
+	for (int i = 0; i < ARRAY_SIZE(smccc_devices); i++) {
+		smccc_dev = &smccc_devices[i];
+
+		if (!smccc_probe_smccc_device(smccc_dev))
+			continue;
+
+		sdev = arm_smccc_device_register(smccc_dev->device_name, smccc_dev->func_id);
+		if (IS_ERR(sdev))
+			pr_err("%s: could not register device: %pe\n",
+			       smccc_dev->device_name, sdev);
 	}
 
 	return 0;
-- 
2.43.0


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

* [PATCH v5 3/8] dt-bindings: arm: Add Live Firmware Activation
  2026-10-16 18:27 [PATCH v5 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
  2026-10-16 18:27 ` [PATCH v5 2/8] firmware: hwrng: arm_smccc_trng: Register as an SMCCC device Andre Przywara
@ 2026-10-16 18:27 ` Andre Przywara
  2026-10-16 18:27 ` [PATCH v5 4/8] firmware: smccc: Add support for Live Firmware Activation (LFA) Andre Przywara
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Andre Przywara @ 2026-10-16 18:27 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	[flat|nested] 8+ messages in thread

* [PATCH v5 4/8] firmware: smccc: Add support for Live Firmware Activation (LFA)
  2026-10-16 18:27 [PATCH v5 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
  2026-10-16 18:27 ` [PATCH v5 2/8] firmware: hwrng: arm_smccc_trng: Register as an SMCCC device Andre Przywara
  2026-10-16 18:27 ` [PATCH v5 3/8] dt-bindings: arm: Add Live Firmware Activation Andre Przywara
@ 2026-10-16 18:27 ` Andre Przywara
  2026-10-16 18:27 ` [PATCH v5 5/8] firmware: smccc: lfa: Register ACPI notification Andre Przywara
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Andre Przywara @ 2026-10-16 18:27 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 67e503dac409f..8c8074d3f3f2b 100644
--- a/drivers/firmware/smccc/smccc.c
+++ b/drivers/firmware/smccc/smccc.c
@@ -94,6 +94,11 @@ static const struct smccc_device_info smccc_devices[] __initconst = {
 		.requires_smc   = false,
 		.device_name    = "arm-smccc-trng",
 	},
+	{
+		.func_id        = ARM_SMCCC_LFA_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	[flat|nested] 8+ messages in thread

* [PATCH v5 5/8] firmware: smccc: lfa: Register ACPI notification
  2026-10-16 18:27 [PATCH v5 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (2 preceding siblings ...)
  2026-10-16 18:27 ` [PATCH v5 4/8] firmware: smccc: Add support for Live Firmware Activation (LFA) Andre Przywara
@ 2026-10-16 18:27 ` Andre Przywara
  2026-10-16 18:27 ` [PATCH v5 6/8] firmware: smccc: lfa: Add auto_activate sysfs file Andre Przywara
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Andre Przywara @ 2026-10-16 18:27 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	[flat|nested] 8+ messages in thread

* [PATCH v5 6/8] firmware: smccc: lfa: Add auto_activate sysfs file
  2026-10-16 18:27 [PATCH v5 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (3 preceding siblings ...)
  2026-10-16 18:27 ` [PATCH v5 5/8] firmware: smccc: lfa: Register ACPI notification Andre Przywara
@ 2026-10-16 18:27 ` Andre Przywara
  2026-10-16 18:27 ` [PATCH v5 7/8] firmware: smccc: lfa: Register DT interrupt Andre Przywara
  2026-10-16 18:27 ` [PATCH v5 8/8] firmware: smccc: lfa: add sysfs ABI documentation Andre Przywara
  6 siblings, 0 replies; 8+ messages in thread
From: Andre Przywara @ 2026-10-16 18:27 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	[flat|nested] 8+ messages in thread

* [PATCH v5 7/8] firmware: smccc: lfa: Register DT interrupt
  2026-10-16 18:27 [PATCH v5 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (4 preceding siblings ...)
  2026-10-16 18:27 ` [PATCH v5 6/8] firmware: smccc: lfa: Add auto_activate sysfs file Andre Przywara
@ 2026-10-16 18:27 ` Andre Przywara
  2026-10-16 18:27 ` [PATCH v5 8/8] firmware: smccc: lfa: add sysfs ABI documentation Andre Przywara
  6 siblings, 0 replies; 8+ messages in thread
From: Andre Przywara @ 2026-10-16 18:27 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	[flat|nested] 8+ messages in thread

* [PATCH v5 8/8] firmware: smccc: lfa: add sysfs ABI documentation
  2026-10-16 18:27 [PATCH v5 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
                   ` (5 preceding siblings ...)
  2026-10-16 18:27 ` [PATCH v5 7/8] firmware: smccc: lfa: Register DT interrupt Andre Przywara
@ 2026-10-16 18:27 ` Andre Przywara
  6 siblings, 0 replies; 8+ messages in thread
From: Andre Przywara @ 2026-10-16 18:27 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	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-10-16 18:27 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-16 18:27 [PATCH v5 0/8] Arm Live Firmware Activation (LFA) support Andre Przywara
2026-10-16 18:27 ` [PATCH v5 2/8] firmware: hwrng: arm_smccc_trng: Register as an SMCCC device Andre Przywara
2026-10-16 18:27 ` [PATCH v5 3/8] dt-bindings: arm: Add Live Firmware Activation Andre Przywara
2026-10-16 18:27 ` [PATCH v5 4/8] firmware: smccc: Add support for Live Firmware Activation (LFA) Andre Przywara
2026-10-16 18:27 ` [PATCH v5 5/8] firmware: smccc: lfa: Register ACPI notification Andre Przywara
2026-10-16 18:27 ` [PATCH v5 6/8] firmware: smccc: lfa: Add auto_activate sysfs file Andre Przywara
2026-10-16 18:27 ` [PATCH v5 7/8] firmware: smccc: lfa: Register DT interrupt Andre Przywara
2026-10-16 18:27 ` [PATCH v5 8/8] firmware: smccc: lfa: add sysfs ABI documentation Andre Przywara

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

all inboxes | Powered by JetHome®