* [PATCH 0/1] Arm Live Firmware Activation (LFA) support
@ 2026-01-19 12:27 Salman Nabi
2026-01-19 12:27 ` [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA) Salman Nabi
0 siblings, 1 reply; 18+ messages in thread
From: Salman Nabi @ 2026-01-19 12:27 UTC (permalink / raw)
To: vvidwans, andre.przywara, sudeep.holla, mark.rutland, lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
Hi reviewers,
(This is a follow-up to the Live Firmware Activation work that was
submitted for RFC [1]).
This patch introduces a Linux kernel driver implementing the Arm Live
Firmware Activation (LFA) specification [2]. LFA enables the activation
of updated firmware components without requiring a system reboot,
reducing downtime in environments such as data centers and hyperscale
systems.
Unlike firmware update process (which may use tools like fwupd), LFA
focuses solely on the activation of an already updated firmware
component, that is pending activation, without a system reboot. This
capability helps maintain service availability and minimize operational
disruption.
Key features of the driver:
* Detects LFA support in system firmware (EL3).
* Lists all firmware components that support live activation.
* Exposes component attributes (e.g., activation capability, and
activation pending) via sysfs under /sys/firmware/lfa/.
* Provides interfaces to:
- Trigger activation of an updated firmware component.
- Cancel an ongoing activation if required.
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.
Note: The ACPI tables are described in the spec. The Device Tree
bindings are currently work-in-progress, and a follow up patch will soon
be submitted that will add the DT bindings to the driver.
Summary of changes since rfc:
- 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.
Best regards,
Salman Nabi
[1] https://lore.kernel.org/all/20250625142722.1911172-2-andre.przywara@arm.com/
[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
Salman Nabi (1):
firmware: smccc: add support for Live Firmware Activation (LFA)
drivers/firmware/smccc/Kconfig | 8 +
drivers/firmware/smccc/Makefile | 1 +
drivers/firmware/smccc/lfa_fw.c | 668 ++++++++++++++++++++++++++++++++
3 files changed, 677 insertions(+)
create mode 100644 drivers/firmware/smccc/lfa_fw.c
--
2.25.1
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-19 12:27 [PATCH 0/1] Arm Live Firmware Activation (LFA) support Salman Nabi
@ 2026-01-19 12:27 ` Salman Nabi
2026-01-19 16:29 ` kernel test robot
` (7 more replies)
0 siblings, 8 replies; 18+ messages in thread
From: Salman Nabi @ 2026-01-19 12:27 UTC (permalink / raw)
To: vvidwans, andre.przywara, sudeep.holla, mark.rutland, lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
The Arm Live Firmware Activation (LFA) is a specification [1] to describe
activating firmware components without a reboot. Those components
(like TF-A's BL31, EDK-II, TF-RMM, secure paylods) would be updated the
usual way: via fwupd, FF-A or other secure storage methods, or via some
IMPDEF Out-Of-Bound method. The user can then activate this new firmware,
at system runtime, without requiring a reboot.
The specification covers the SMCCC interface to list and query available
components and eventually trigger the activation.
Add a new directory under /sys/firmware to present firmware components
capable of live activation. Each of them is a directory under lfa/,
and is identified via its GUID. The activation will be triggered by echoing
"1" into the "activate" file:
==========================================
/sys/firmware/lfa # ls -l . 6c*
.:
total 0
drwxr-xr-x 2 0 0 0 Jan 19 11:33 47d4086d-4cfe-9846-9b95-2950cbbd5a00
drwxr-xr-x 2 0 0 0 Jan 19 11:33 6c0762a6-12f2-4b56-92cb-ba8f633606d9
drwxr-xr-x 2 0 0 0 Jan 19 11:33 d6d0eea7-fcea-d54b-9782-9934f234b6e4
6c0762a6-12f2-4b56-92cb-ba8f633606d9:
total 0
--w------- 1 0 0 4096 Jan 19 11:33 activate
-r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_capable
-r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_pending
--w------- 1 0 0 4096 Jan 19 11:33 cancel
-r--r--r-- 1 0 0 4096 Jan 19 11:33 cpu_rendezvous
-r--r--r-- 1 0 0 4096 Jan 19 11:33 current_version
-rw-r--r-- 1 0 0 4096 Jan 19 11:33 force_cpu_rendezvous
-r--r--r-- 1 0 0 4096 Jan 19 11:33 may_reset_cpu
-r--r--r-- 1 0 0 4096 Jan 19 11:33 name
-r--r--r-- 1 0 0 4096 Jan 19 11:33 pending_version
/sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # grep . *
grep: activate: Permission denied
activation_capable:1
activation_pending:1
grep: cancel: Permission denied
cpu_rendezvous:1
current_version:0.0
force_cpu_rendezvous:1
may_reset_cpu:0
name:TF-RMM
pending_version:0.0
/sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # echo 1 > activate
[ 2825.797871] Arm LFA: firmware activation succeeded.
/sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 #
==========================================
[1] https://developer.arm.com/documentation/den0147/latest/
Signed-off-by: Salman Nabi <salman.nabi@arm.com>
---
drivers/firmware/smccc/Kconfig | 8 +
drivers/firmware/smccc/Makefile | 1 +
drivers/firmware/smccc/lfa_fw.c | 668 ++++++++++++++++++++++++++++++++
3 files changed, 677 insertions(+)
create mode 100644 drivers/firmware/smccc/lfa_fw.c
diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig
index 15e7466179a6..ff7ca49486b0 100644
--- a/drivers/firmware/smccc/Kconfig
+++ b/drivers/firmware/smccc/Kconfig
@@ -23,3 +23,11 @@ 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
+ default y
+ help
+ Include support for triggering Live Firmware Activation, which
+ allows to upgrade certain firmware components without a reboot.
diff --git a/drivers/firmware/smccc/Makefile b/drivers/firmware/smccc/Makefile
index 40d19144a860..a6dd01558a94 100644
--- a/drivers/firmware/smccc/Makefile
+++ b/drivers/firmware/smccc/Makefile
@@ -2,3 +2,4 @@
#
obj-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += smccc.o kvm_guest.o
obj-$(CONFIG_ARM_SMCCC_SOC_ID) += soc_id.o
+obj-$(CONFIG_ARM_LFA) += lfa_fw.o
diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
new file mode 100644
index 000000000000..ce54049b7190
--- /dev/null
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -0,0 +1,668 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Arm Limited
+ */
+
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <linux/stop_machine.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/arm-smccc.h>
+#include <linux/psci.h>
+#include <uapi/linux/psci.h>
+#include <linux/uuid.h>
+#include <linux/array_size.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+
+#undef pr_fmt
+#define pr_fmt(fmt) "Arm LFA: " fmt
+
+/* LFA v1.0b0 specification */
+#define LFA_1_0_FN_BASE 0xc40002e0
+#define LFA_1_0_FN(n) (LFA_1_0_FN_BASE + (n))
+
+#define LFA_1_0_FN_GET_VERSION LFA_1_0_FN(0)
+#define LFA_1_0_FN_CHECK_FEATURE LFA_1_0_FN(1)
+#define LFA_1_0_FN_GET_INFO LFA_1_0_FN(2)
+#define LFA_1_0_FN_GET_INVENTORY LFA_1_0_FN(3)
+#define LFA_1_0_FN_PRIME LFA_1_0_FN(4)
+#define LFA_1_0_FN_ACTIVATE LFA_1_0_FN(5)
+#define LFA_1_0_FN_CANCEL LFA_1_0_FN(6)
+
+/* CALL_AGAIN flags (returned by SMC) */
+#define LFA_PRIME_CALL_AGAIN BIT(0)
+#define LFA_ACTIVATE_CALL_AGAIN BIT(0)
+
+/* LFA return values */
+#define LFA_SUCCESS 0
+#define LFA_NOT_SUPPORTED 1
+#define LFA_BUSY 2
+#define LFA_AUTH_ERROR 3
+#define LFA_NO_MEMORY 4
+#define LFA_CRITICAL_ERROR 5
+#define LFA_DEVICE_ERROR 6
+#define LFA_WRONG_STATE 7
+#define LFA_INVALID_PARAMETERS 8
+#define LFA_COMPONENT_WRONG_STATE 9
+#define LFA_INVALID_ADDRESS 10
+#define LFA_ACTIVATION_FAILED 11
+
+#define LFA_ERROR_STRING(name) \
+ [name] = #name
+
+static const char * const lfa_error_strings[] = {
+ LFA_ERROR_STRING(LFA_SUCCESS),
+ LFA_ERROR_STRING(LFA_NOT_SUPPORTED),
+ LFA_ERROR_STRING(LFA_BUSY),
+ LFA_ERROR_STRING(LFA_AUTH_ERROR),
+ LFA_ERROR_STRING(LFA_NO_MEMORY),
+ LFA_ERROR_STRING(LFA_CRITICAL_ERROR),
+ LFA_ERROR_STRING(LFA_DEVICE_ERROR),
+ LFA_ERROR_STRING(LFA_WRONG_STATE),
+ LFA_ERROR_STRING(LFA_INVALID_PARAMETERS),
+ LFA_ERROR_STRING(LFA_COMPONENT_WRONG_STATE),
+ LFA_ERROR_STRING(LFA_INVALID_ADDRESS),
+ LFA_ERROR_STRING(LFA_ACTIVATION_FAILED)
+};
+
+enum image_attr_names {
+ LFA_ATTR_NAME,
+ LFA_ATTR_CURRENT_VERSION,
+ LFA_ATTR_PENDING_VERSION,
+ LFA_ATTR_ACT_CAPABLE,
+ LFA_ATTR_ACT_PENDING,
+ LFA_ATTR_MAY_RESET_CPU,
+ LFA_ATTR_CPU_RENDEZVOUS,
+ LFA_ATTR_FORCE_CPU_RENDEZVOUS,
+ LFA_ATTR_ACTIVATE,
+ LFA_ATTR_CANCEL,
+ LFA_ATTR_NR_IMAGES
+};
+
+struct image_props {
+ struct list_head image_node;
+ const char *image_name;
+ int fw_seq_id;
+ u64 current_version;
+ u64 pending_version;
+ bool activation_capable;
+ bool activation_pending;
+ bool may_reset_cpu;
+ bool cpu_rendezvous;
+ bool cpu_rendezvous_forced;
+ struct kobject *image_dir;
+ struct kobj_attribute image_attrs[LFA_ATTR_NR_IMAGES];
+};
+static LIST_HEAD(lfa_fw_images);
+
+/* A UUID split over two 64-bit registers */
+struct uuid_regs {
+ u64 uuid_lo;
+ u64 uuid_hi;
+};
+
+static const struct fw_image_uuid {
+ const char *name;
+ const char *uuid;
+} fw_images_uuids[] = {
+ {
+ .name = "TF-A BL31 runtime",
+ .uuid = "47d4086d-4cfe-9846-9b95-2950cbbd5a00",
+ },
+ {
+ .name = "BL33 non-secure payload",
+ .uuid = "d6d0eea7-fcea-d54b-9782-9934f234b6e4",
+ },
+ {
+ .name = "TF-RMM",
+ .uuid = "6c0762a6-12f2-4b56-92cb-ba8f633606d9",
+ },
+};
+
+static struct kobject *lfa_dir;
+static DEFINE_MUTEX(lfa_lock);
+static struct workqueue_struct *fw_images_update_wq;
+static struct work_struct fw_images_update_work;
+
+static int update_fw_images_tree(void);
+
+static void delete_fw_image_node(struct image_props *attrs)
+{
+ int i;
+
+ for (i = 0; i < LFA_ATTR_NR_IMAGES; i++)
+ sysfs_remove_file(attrs->image_dir, &attrs->image_attrs[i].attr);
+
+ kobject_put(attrs->image_dir);
+ list_del(&attrs->image_node);
+ kfree(attrs);
+}
+
+static void remove_invalid_fw_images(struct work_struct *work)
+{
+ struct image_props *attrs, *tmp;
+
+ mutex_lock(&lfa_lock);
+
+ /*
+ * Remove firmware images including directories that are no longer
+ * present in the LFA agent after updating the existing ones.
+ */
+ list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node) {
+ if (attrs->fw_seq_id == -1)
+ delete_fw_image_node(attrs);
+ }
+
+ mutex_unlock(&lfa_lock);
+}
+
+static void set_image_flags(struct image_props *attrs, int seq_id,
+ u32 image_flags, u64 reg_current_ver,
+ u64 reg_pending_ver)
+{
+ attrs->fw_seq_id = seq_id;
+ attrs->current_version = reg_current_ver;
+ attrs->pending_version = reg_pending_ver;
+ attrs->activation_capable = !!(image_flags & BIT(0));
+ attrs->activation_pending = !!(image_flags & BIT(1));
+ attrs->may_reset_cpu = !!(image_flags & BIT(2));
+ /* cpu_rendezvous_optional bit has inverse logic in the spec */
+ attrs->cpu_rendezvous = !(image_flags & BIT(3));
+}
+
+static unsigned long get_nr_lfa_components(void)
+{
+ struct arm_smccc_1_2_regs reg = { 0 };
+
+ reg.a0 = LFA_1_0_FN_GET_INFO;
+ reg.a1 = 0; /* lfa_info_selector = 0 */
+
+ arm_smccc_1_2_invoke(®, ®);
+ if (reg.a0 != LFA_SUCCESS)
+ return reg.a0;
+
+ return reg.a1;
+}
+
+static int lfa_cancel(void *data)
+{
+ struct image_props *attrs = data;
+ struct arm_smccc_1_2_regs reg = { 0 };
+
+ reg.a0 = LFA_1_0_FN_CANCEL;
+ reg.a1 = attrs->fw_seq_id;
+ arm_smccc_1_2_invoke(®, ®);
+
+ /*
+ * When firmware activation is called with "skip_cpu_rendezvous=1",
+ * LFA_CANCEL can fail with LFA_BUSY if the activation could not be
+ * cancelled.
+ */
+ if (reg.a0 == LFA_SUCCESS) {
+ pr_info("Activation cancelled for image %s\n",
+ attrs->image_name);
+ } else {
+ pr_err("Firmware activation could not be cancelled: %s\n",
+ lfa_error_strings[-reg.a0]);
+ return -EINVAL;
+ }
+
+ return reg.a0;
+}
+
+static int call_lfa_activate(void *data)
+{
+ struct image_props *attrs = data;
+ struct arm_smccc_1_2_regs reg = { 0 };
+
+ reg.a0 = LFA_1_0_FN_ACTIVATE;
+ reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
+ /*
+ * As we do not support updates requiring a CPU reset (yet),
+ * we pass 0 in reg.a3 and reg.a4, holding the entry point and context
+ * ID respectively.
+ * cpu_rendezvous_forced is set by the administrator, via sysfs,
+ * cpu_rendezvous is dictated by each firmware component.
+ */
+ reg.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous);
+
+ for (;;) {
+ arm_smccc_1_2_invoke(®, ®);
+
+ if ((long)reg.a0 < 0) {
+ pr_err("ACTIVATE for image %s failed: %s\n",
+ attrs->image_name, lfa_error_strings[-reg.a0]);
+ return reg.a0;
+ }
+ if (!(reg.a1 & LFA_ACTIVATE_CALL_AGAIN))
+ break; /* ACTIVATE successful */
+ }
+
+ return reg.a0;
+}
+
+static int activate_fw_image(struct image_props *attrs)
+{
+ int ret;
+
+ mutex_lock(&lfa_lock);
+ if (attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous)
+ ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask);
+ else
+ ret = call_lfa_activate(attrs);
+
+ if (ret != 0) {
+ mutex_unlock(&lfa_lock);
+ return lfa_cancel(attrs);
+ }
+
+ /*
+ * Invalidate fw_seq_ids (-1) for all images as the seq_ids and the
+ * number of firmware images in the LFA agent may change after a
+ * successful activation attempt. Negate all image flags as well.
+ */
+ attrs = NULL;
+ list_for_each_entry(attrs, &lfa_fw_images, image_node) {
+ set_image_flags(attrs, -1, 0b1000, 0, 0);
+ }
+
+ update_fw_images_tree();
+
+ /*
+ * Removing non-valid image directories at the end of an activation.
+ * We can't remove the sysfs attributes while in the respective
+ * _store() handler, so have to postpone the list removal to a
+ * workqueue.
+ */
+ INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
+ queue_work(fw_images_update_wq, &fw_images_update_work);
+ mutex_unlock(&lfa_lock);
+
+ return ret;
+}
+
+static int prime_fw_image(struct image_props *attrs)
+{
+ struct arm_smccc_1_2_regs reg = { 0 };
+ int ret;
+
+ mutex_lock(&lfa_lock);
+ /* Avoid SMC calls on invalid firmware images */
+ if (attrs->fw_seq_id == -1) {
+ pr_err("Arm LFA: Invalid firmware sequence id\n");
+ mutex_unlock(&lfa_lock);
+
+ return -ENODEV;
+ }
+
+ if (attrs->may_reset_cpu) {
+ pr_err("CPU reset not supported by kernel driver\n");
+ mutex_unlock(&lfa_lock);
+
+ return -EINVAL;
+ }
+
+ /*
+ * LFA_PRIME/ACTIVATE will return 1 in reg.a1 if the firmware
+ * priming/activation is still in progress. In that case
+ * LFA_PRIME/ACTIVATE will need to be called again.
+ * reg.a1 will become 0 once the prime/activate process completes.
+ */
+ reg.a0 = LFA_1_0_FN_PRIME;
+ reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
+ for (;;) {
+ arm_smccc_1_2_invoke(®, ®);
+
+ if ((long)reg.a0 < 0) {
+ pr_err("LFA_PRIME for image %s failed: %s\n",
+ attrs->image_name, lfa_error_strings[-reg.a0]);
+ mutex_unlock(&lfa_lock);
+
+ return reg.a0;
+ }
+ if (!(reg.a1 & LFA_PRIME_CALL_AGAIN)) {
+ ret = 0;
+ break; /* PRIME successful */
+ }
+ }
+
+ mutex_unlock(&lfa_lock);
+ return ret;
+}
+
+static ssize_t name_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ struct image_props *attrs = container_of(attr, struct image_props,
+ image_attrs[LFA_ATTR_NAME]);
+
+ return sysfs_emit(buf, "%s\n", attrs->image_name);
+}
+
+static ssize_t activation_capable_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct image_props *attrs = container_of(attr, struct image_props,
+ image_attrs[LFA_ATTR_ACT_CAPABLE]);
+
+ return sysfs_emit(buf, "%d\n", attrs->activation_capable);
+}
+
+static ssize_t activation_pending_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct image_props *attrs = container_of(attr, struct image_props,
+ image_attrs[LFA_ATTR_ACT_PENDING]);
+ struct arm_smccc_1_2_regs reg = { 0 };
+
+ /*
+ * Activation pending status can change anytime thus we need to update
+ * and return its current value
+ */
+ reg.a0 = LFA_1_0_FN_GET_INVENTORY;
+ reg.a1 = attrs->fw_seq_id;
+ arm_smccc_1_2_invoke(®, ®);
+ if (reg.a0 == LFA_SUCCESS)
+ attrs->activation_pending = !!(reg.a3 & BIT(1));
+
+ return sysfs_emit(buf, "%d\n", attrs->activation_pending);
+}
+
+static ssize_t may_reset_cpu_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct image_props *attrs = container_of(attr, struct image_props,
+ image_attrs[LFA_ATTR_MAY_RESET_CPU]);
+
+ return sysfs_emit(buf, "%d\n", attrs->may_reset_cpu);
+}
+
+static ssize_t cpu_rendezvous_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct image_props *attrs = container_of(attr, struct image_props,
+ image_attrs[LFA_ATTR_CPU_RENDEZVOUS]);
+
+ return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous);
+}
+
+static ssize_t force_cpu_rendezvous_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct image_props *attrs = container_of(attr, struct image_props,
+ image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
+ int ret;
+
+ ret = kstrtobool(buf, &attrs->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 image_props *attrs = container_of(attr, struct image_props,
+ image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
+
+ return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous_forced);
+}
+
+static ssize_t current_version_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct image_props *attrs = container_of(attr, struct image_props,
+ image_attrs[LFA_ATTR_CURRENT_VERSION]);
+ u32 maj, min;
+
+ maj = attrs->current_version >> 32;
+ min = attrs->current_version & 0xffffffff;
+ return sysfs_emit(buf, "%u.%u\n", maj, min);
+}
+
+static ssize_t pending_version_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct image_props *attrs = container_of(attr, struct image_props,
+ image_attrs[LFA_ATTR_ACT_PENDING]);
+ struct arm_smccc_1_2_regs reg = { 0 };
+ u32 maj, min;
+
+ /*
+ * Similar to activation pending, this value can change following an
+ * update, we need to retrieve fresh info instead of stale information.
+ */
+ reg.a0 = LFA_1_0_FN_GET_INVENTORY;
+ reg.a1 = attrs->fw_seq_id;
+ arm_smccc_1_2_invoke(®, ®);
+ if (reg.a0 == LFA_SUCCESS) {
+ if (reg.a5 != 0 && attrs->activation_pending)
+ {
+ attrs->pending_version = reg.a5;
+ maj = reg.a5 >> 32;
+ min = reg.a5 & 0xffffffff;
+ }
+ }
+
+ return sysfs_emit(buf, "%u.%u\n", maj, min);
+}
+
+static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct image_props *attrs = container_of(attr, struct image_props,
+ image_attrs[LFA_ATTR_ACTIVATE]);
+ int ret;
+
+ ret = prime_fw_image(attrs);
+ if (ret) {
+ pr_err("Firmware prime failed: %s\n",
+ lfa_error_strings[-ret]);
+ return -ECANCELED;
+ }
+
+ ret = activate_fw_image(attrs);
+ if (ret) {
+ pr_err("Firmware activation failed: %s\n",
+ lfa_error_strings[-ret]);
+ return -ECANCELED;
+ }
+
+ pr_info("Firmware activation succeeded\n");
+
+ return count;
+}
+
+static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct image_props *attrs = container_of(attr, struct image_props,
+ image_attrs[LFA_ATTR_CANCEL]);
+ int ret;
+
+ ret = lfa_cancel(attrs);
+ if (ret != 0)
+ return ret;
+
+ return count;
+}
+
+static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = {
+ [LFA_ATTR_NAME] = __ATTR_RO(name),
+ [LFA_ATTR_CURRENT_VERSION] = __ATTR_RO(current_version),
+ [LFA_ATTR_PENDING_VERSION] = __ATTR_RO(pending_version),
+ [LFA_ATTR_ACT_CAPABLE] = __ATTR_RO(activation_capable),
+ [LFA_ATTR_ACT_PENDING] = __ATTR_RO(activation_pending),
+ [LFA_ATTR_MAY_RESET_CPU] = __ATTR_RO(may_reset_cpu),
+ [LFA_ATTR_CPU_RENDEZVOUS] = __ATTR_RO(cpu_rendezvous),
+ [LFA_ATTR_FORCE_CPU_RENDEZVOUS] = __ATTR_RW(force_cpu_rendezvous),
+ [LFA_ATTR_ACTIVATE] = __ATTR_WO(activate),
+ [LFA_ATTR_CANCEL] = __ATTR_WO(cancel)
+};
+
+static void clean_fw_images_tree(void)
+{
+ struct image_props *attrs, *tmp;
+
+ list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node)
+ delete_fw_image_node(attrs);
+}
+
+static int update_fw_image_node(char *fw_uuid, int seq_id,
+ u32 image_flags, u64 reg_current_ver,
+ u64 reg_pending_ver)
+{
+ const char *image_name = "(unknown)";
+ struct image_props *attrs;
+ int ret;
+
+ /*
+ * If a fw_image is already in the images list then we just update
+ * its flags and seq_id instead of trying to recreate it.
+ */
+ list_for_each_entry(attrs, &lfa_fw_images, image_node) {
+ if (!strcmp(attrs->image_dir->name, fw_uuid)) {
+ set_image_flags(attrs, seq_id, image_flags,
+ reg_current_ver, reg_pending_ver);
+ return 0;
+ }
+ }
+
+ attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);
+ if (!attrs)
+ return -ENOMEM;
+
+ for (int i = 0; i < ARRAY_SIZE(fw_images_uuids); i++) {
+ if (!strcmp(fw_images_uuids[i].uuid, fw_uuid))
+ image_name = fw_images_uuids[i].name;
+ }
+
+ attrs->image_dir = kobject_create_and_add(fw_uuid, lfa_dir);
+ if (!attrs->image_dir)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&attrs->image_node);
+ attrs->image_name = image_name;
+ attrs->cpu_rendezvous_forced = 1;
+ set_image_flags(attrs, seq_id, image_flags, reg_current_ver,
+ reg_pending_ver);
+
+ /*
+ * The attributes for each sysfs file are constant (handler functions,
+ * name and permissions are the same within each directory), but we
+ * need a per-directory copy regardless, to get a unique handle
+ * for each directory, so that container_of can do its magic.
+ * Also this requires an explicit sysfs_attr_init(), since it's a new
+ * copy, to make LOCKDEP happy.
+ */
+ memcpy(attrs->image_attrs, image_attrs_group,
+ sizeof(attrs->image_attrs));
+ for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++) {
+ struct attribute *attr = &attrs->image_attrs[i].attr;
+
+ sysfs_attr_init(attr);
+ ret = sysfs_create_file(attrs->image_dir, attr);
+ if (ret) {
+ pr_err("creating sysfs file for uuid %s: %d\n",
+ fw_uuid, ret);
+ clean_fw_images_tree();
+
+ return ret;
+ }
+ }
+ list_add(&attrs->image_node, &lfa_fw_images);
+
+ return ret;
+}
+
+static int update_fw_images_tree(void)
+{
+ struct arm_smccc_1_2_regs reg = { 0 };
+ struct uuid_regs image_uuid;
+ char image_id_str[40];
+ int ret, num_of_components;
+
+ num_of_components = get_nr_lfa_components();
+ if (num_of_components <= 0) {
+ pr_err("Error getting number of LFA components\n");
+ return -ENODEV;
+ }
+
+ for (int i = 0; i < num_of_components; i++) {
+ reg.a0 = LFA_1_0_FN_GET_INVENTORY;
+ reg.a1 = i; /* fw_seq_id under consideration */
+ arm_smccc_1_2_invoke(®, ®);
+ if (reg.a0 == LFA_SUCCESS) {
+ image_uuid.uuid_lo = reg.a1;
+ image_uuid.uuid_hi = reg.a2;
+
+ snprintf(image_id_str, sizeof(image_id_str), "%pUb",
+ &image_uuid);
+ ret = update_fw_image_node(image_id_str, i,
+ reg.a3, reg.a4, reg.a5);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int __init lfa_init(void)
+{
+ struct arm_smccc_1_2_regs reg = { 0 };
+ int err;
+
+ reg.a0 = LFA_1_0_FN_GET_VERSION;
+ arm_smccc_1_2_invoke(®, ®);
+ if (reg.a0 == -LFA_NOT_SUPPORTED) {
+ pr_info("Live Firmware activation: no firmware agent found\n");
+ return -ENODEV;
+ }
+
+ fw_images_update_wq = alloc_workqueue("fw_images_update_wq",
+ WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
+ if (!fw_images_update_wq) {
+ pr_err("Live Firmware Activation: Failed to allocate workqueue.\n");
+
+ return -ENOMEM;
+ }
+
+ pr_info("Live Firmware Activation: detected v%ld.%ld\n",
+ reg.a0 >> 16, reg.a0 & 0xffff);
+
+ lfa_dir = kobject_create_and_add("lfa", firmware_kobj);
+ if (!lfa_dir)
+ return -ENOMEM;
+
+ mutex_lock(&lfa_lock);
+ err = update_fw_images_tree();
+ if (err != 0)
+ kobject_put(lfa_dir);
+
+ mutex_unlock(&lfa_lock);
+ return err;
+}
+module_init(lfa_init);
+
+static void __exit lfa_exit(void)
+{
+ flush_workqueue(fw_images_update_wq);
+ destroy_workqueue(fw_images_update_wq);
+
+ mutex_lock(&lfa_lock);
+ clean_fw_images_tree();
+ mutex_unlock(&lfa_lock);
+
+ kobject_put(lfa_dir);
+}
+module_exit(lfa_exit);
+
+MODULE_DESCRIPTION("ARM Live Firmware Activation (LFA)");
+MODULE_LICENSE("GPL");
--
2.25.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-19 12:27 ` [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA) Salman Nabi
@ 2026-01-19 16:29 ` kernel test robot
2026-01-19 16:41 ` Andre Przywara
2026-01-19 18:41 ` kernel test robot
` (6 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: kernel test robot @ 2026-01-19 16:29 UTC (permalink / raw)
To: Salman Nabi, vvidwans, andre.przywara, sudeep.holla, mark.rutland,
lpieralisi
Cc: oe-kbuild-all, ardb, chao.gao, linux-arm-kernel, linux-coco,
linux-kernel, sdonthineni, vsethi, vwadekar
Hi Salman,
kernel test robot noticed the following build warnings:
[auto build test WARNING on soc/for-next]
[also build test WARNING on linus/master v6.19-rc6 next-20260116]
[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/Salman-Nabi/firmware-smccc-add-support-for-Live-Firmware-Activation-LFA/20260119-203221
base: https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git for-next
patch link: https://lore.kernel.org/r/20260119122729.287522-2-salman.nabi%40arm.com
patch subject: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
config: arm-sp7021_defconfig (https://download.01.org/0day-ci/archive/20260120/202601200007.GVZIjoCx-lkp@intel.com/config)
compiler: arm-linux-gnueabi-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260120/202601200007.GVZIjoCx-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/202601200007.GVZIjoCx-lkp@intel.com/
All warnings (new ones prefixed by >>):
drivers/firmware/smccc/lfa_fw.c: In function 'get_nr_lfa_components':
drivers/firmware/smccc/lfa_fw.c:179:16: error: variable 'reg' has initializer but incomplete type
179 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
>> drivers/firmware/smccc/lfa_fw.c:179:43: warning: excess elements in struct initializer
179 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:179:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:179:35: error: storage size of 'reg' isn't known
179 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:184:9: error: implicit declaration of function 'arm_smccc_1_2_invoke'; did you mean 'arm_smccc_1_1_invoke'? [-Wimplicit-function-declaration]
184 | arm_smccc_1_2_invoke(®, ®);
| ^~~~~~~~~~~~~~~~~~~~
| arm_smccc_1_1_invoke
>> drivers/firmware/smccc/lfa_fw.c:179:35: warning: unused variable 'reg' [-Wunused-variable]
179 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c: In function 'lfa_cancel':
drivers/firmware/smccc/lfa_fw.c:194:16: error: variable 'reg' has initializer but incomplete type
194 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:194:43: warning: excess elements in struct initializer
194 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:194:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:194:35: error: storage size of 'reg' isn't known
194 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:194:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'call_lfa_activate':
drivers/firmware/smccc/lfa_fw.c:220:16: error: variable 'reg' has initializer but incomplete type
220 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:220:43: warning: excess elements in struct initializer
220 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:220:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:220:35: error: storage size of 'reg' isn't known
220 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:220:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'prime_fw_image':
drivers/firmware/smccc/lfa_fw.c:290:16: error: variable 'reg' has initializer but incomplete type
290 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:290:43: warning: excess elements in struct initializer
290 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:290:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:290:35: error: storage size of 'reg' isn't known
290 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:290:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'activation_pending_show':
drivers/firmware/smccc/lfa_fw.c:360:16: error: variable 'reg' has initializer but incomplete type
360 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:360:43: warning: excess elements in struct initializer
360 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:360:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:360:35: error: storage size of 'reg' isn't known
360 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:360:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'pending_version_show':
drivers/firmware/smccc/lfa_fw.c:434:16: error: variable 'reg' has initializer but incomplete type
434 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:434:43: warning: excess elements in struct initializer
434 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:434:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:434:35: error: storage size of 'reg' isn't known
434 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:434:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'update_fw_images_tree':
drivers/firmware/smccc/lfa_fw.c:586:16: error: variable 'reg' has initializer but incomplete type
586 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:586:43: warning: excess elements in struct initializer
586 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:586:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:586:35: error: storage size of 'reg' isn't known
586 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:586:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'lfa_init':
drivers/firmware/smccc/lfa_fw.c:619:16: error: variable 'reg' has initializer but incomplete type
619 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:619:43: warning: excess elements in struct initializer
619 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:619:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:619:35: error: storage size of 'reg' isn't known
619 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:619:35: warning: unused variable 'reg' [-Wunused-variable]
vim +179 drivers/firmware/smccc/lfa_fw.c
176
177 static unsigned long get_nr_lfa_components(void)
178 {
> 179 struct arm_smccc_1_2_regs reg = { 0 };
180
181 reg.a0 = LFA_1_0_FN_GET_INFO;
182 reg.a1 = 0; /* lfa_info_selector = 0 */
183
184 arm_smccc_1_2_invoke(®, ®);
185 if (reg.a0 != LFA_SUCCESS)
186 return reg.a0;
187
188 return reg.a1;
189 }
190
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-19 16:29 ` kernel test robot
@ 2026-01-19 16:41 ` Andre Przywara
0 siblings, 0 replies; 18+ messages in thread
From: Andre Przywara @ 2026-01-19 16:41 UTC (permalink / raw)
To: kernel test robot, Salman Nabi, vvidwans, sudeep.holla,
mark.rutland, lpieralisi
Cc: oe-kbuild-all, ardb, chao.gao, linux-arm-kernel, linux-coco,
linux-kernel, sdonthineni, vsethi, vwadekar
Hi,
On 19/01/2026 16:29, kernel test robot wrote:
> Hi Salman,
>
> kernel test robot noticed the following build warnings:
>
> [auto build test WARNING on soc/for-next]
> [also build test WARNING on linus/master v6.19-rc6 next-20260116]
> [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/Salman-Nabi/firmware-smccc-add-support-for-Live-Firmware-Activation-LFA/20260119-203221
> base: https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git for-next
> patch link: https://lore.kernel.org/r/20260119122729.287522-2-salman.nabi%40arm.com
> patch subject: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
> config: arm-sp7021_defconfig (https://download.01.org/0day-ci/archive/20260120/202601200007.GVZIjoCx-lkp@intel.com/config)
> compiler: arm-linux-gnueabi-gcc (GCC) 15.2.0
> reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260120/202601200007.GVZIjoCx-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/202601200007.GVZIjoCx-lkp@intel.com/
>
> All warnings (new ones prefixed by >>):
>
> drivers/firmware/smccc/lfa_fw.c: In function 'get_nr_lfa_components':
> drivers/firmware/smccc/lfa_fw.c:179:16: error: variable 'reg' has initializer but incomplete type
> 179 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~~~~~~~~~~~~~~~~
>>> drivers/firmware/smccc/lfa_fw.c:179:43: warning: excess elements in struct initializer
Ah, yes, arm_smccc_1_2_regs is only defined for arm64. We rely on v1.2,
and the LFA spec actually means that this means AArch64 only right at
the beginning (chapter 2).
So we need an additional dependency on ARM64 in the Kconfig entry.
Cheers,
Andre
> 179 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^
> drivers/firmware/smccc/lfa_fw.c:179:43: note: (near initialization for 'reg')
> drivers/firmware/smccc/lfa_fw.c:179:35: error: storage size of 'reg' isn't known
> 179 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~
> drivers/firmware/smccc/lfa_fw.c:184:9: error: implicit declaration of function 'arm_smccc_1_2_invoke'; did you mean 'arm_smccc_1_1_invoke'? [-Wimplicit-function-declaration]
> 184 | arm_smccc_1_2_invoke(®, ®);
> | ^~~~~~~~~~~~~~~~~~~~
> | arm_smccc_1_1_invoke
>>> drivers/firmware/smccc/lfa_fw.c:179:35: warning: unused variable 'reg' [-Wunused-variable]
> 179 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~
> drivers/firmware/smccc/lfa_fw.c: In function 'lfa_cancel':
> drivers/firmware/smccc/lfa_fw.c:194:16: error: variable 'reg' has initializer but incomplete type
> 194 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~~~~~~~~~~~~~~~~
> drivers/firmware/smccc/lfa_fw.c:194:43: warning: excess elements in struct initializer
> 194 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^
> drivers/firmware/smccc/lfa_fw.c:194:43: note: (near initialization for 'reg')
> drivers/firmware/smccc/lfa_fw.c:194:35: error: storage size of 'reg' isn't known
> 194 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~
> drivers/firmware/smccc/lfa_fw.c:194:35: warning: unused variable 'reg' [-Wunused-variable]
> drivers/firmware/smccc/lfa_fw.c: In function 'call_lfa_activate':
> drivers/firmware/smccc/lfa_fw.c:220:16: error: variable 'reg' has initializer but incomplete type
> 220 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~~~~~~~~~~~~~~~~
> drivers/firmware/smccc/lfa_fw.c:220:43: warning: excess elements in struct initializer
> 220 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^
> drivers/firmware/smccc/lfa_fw.c:220:43: note: (near initialization for 'reg')
> drivers/firmware/smccc/lfa_fw.c:220:35: error: storage size of 'reg' isn't known
> 220 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~
> drivers/firmware/smccc/lfa_fw.c:220:35: warning: unused variable 'reg' [-Wunused-variable]
> drivers/firmware/smccc/lfa_fw.c: In function 'prime_fw_image':
> drivers/firmware/smccc/lfa_fw.c:290:16: error: variable 'reg' has initializer but incomplete type
> 290 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~~~~~~~~~~~~~~~~
> drivers/firmware/smccc/lfa_fw.c:290:43: warning: excess elements in struct initializer
> 290 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^
> drivers/firmware/smccc/lfa_fw.c:290:43: note: (near initialization for 'reg')
> drivers/firmware/smccc/lfa_fw.c:290:35: error: storage size of 'reg' isn't known
> 290 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~
> drivers/firmware/smccc/lfa_fw.c:290:35: warning: unused variable 'reg' [-Wunused-variable]
> drivers/firmware/smccc/lfa_fw.c: In function 'activation_pending_show':
> drivers/firmware/smccc/lfa_fw.c:360:16: error: variable 'reg' has initializer but incomplete type
> 360 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~~~~~~~~~~~~~~~~
> drivers/firmware/smccc/lfa_fw.c:360:43: warning: excess elements in struct initializer
> 360 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^
> drivers/firmware/smccc/lfa_fw.c:360:43: note: (near initialization for 'reg')
> drivers/firmware/smccc/lfa_fw.c:360:35: error: storage size of 'reg' isn't known
> 360 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~
> drivers/firmware/smccc/lfa_fw.c:360:35: warning: unused variable 'reg' [-Wunused-variable]
> drivers/firmware/smccc/lfa_fw.c: In function 'pending_version_show':
> drivers/firmware/smccc/lfa_fw.c:434:16: error: variable 'reg' has initializer but incomplete type
> 434 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~~~~~~~~~~~~~~~~
> drivers/firmware/smccc/lfa_fw.c:434:43: warning: excess elements in struct initializer
> 434 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^
> drivers/firmware/smccc/lfa_fw.c:434:43: note: (near initialization for 'reg')
> drivers/firmware/smccc/lfa_fw.c:434:35: error: storage size of 'reg' isn't known
> 434 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~
> drivers/firmware/smccc/lfa_fw.c:434:35: warning: unused variable 'reg' [-Wunused-variable]
> drivers/firmware/smccc/lfa_fw.c: In function 'update_fw_images_tree':
> drivers/firmware/smccc/lfa_fw.c:586:16: error: variable 'reg' has initializer but incomplete type
> 586 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~~~~~~~~~~~~~~~~
> drivers/firmware/smccc/lfa_fw.c:586:43: warning: excess elements in struct initializer
> 586 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^
> drivers/firmware/smccc/lfa_fw.c:586:43: note: (near initialization for 'reg')
> drivers/firmware/smccc/lfa_fw.c:586:35: error: storage size of 'reg' isn't known
> 586 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~
> drivers/firmware/smccc/lfa_fw.c:586:35: warning: unused variable 'reg' [-Wunused-variable]
> drivers/firmware/smccc/lfa_fw.c: In function 'lfa_init':
> drivers/firmware/smccc/lfa_fw.c:619:16: error: variable 'reg' has initializer but incomplete type
> 619 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~~~~~~~~~~~~~~~~
> drivers/firmware/smccc/lfa_fw.c:619:43: warning: excess elements in struct initializer
> 619 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^
> drivers/firmware/smccc/lfa_fw.c:619:43: note: (near initialization for 'reg')
> drivers/firmware/smccc/lfa_fw.c:619:35: error: storage size of 'reg' isn't known
> 619 | struct arm_smccc_1_2_regs reg = { 0 };
> | ^~~
> drivers/firmware/smccc/lfa_fw.c:619:35: warning: unused variable 'reg' [-Wunused-variable]
>
>
> vim +179 drivers/firmware/smccc/lfa_fw.c
>
> 176
> 177 static unsigned long get_nr_lfa_components(void)
> 178 {
> > 179 struct arm_smccc_1_2_regs reg = { 0 };
> 180
> 181 reg.a0 = LFA_1_0_FN_GET_INFO;
> 182 reg.a1 = 0; /* lfa_info_selector = 0 */
> 183
> 184 arm_smccc_1_2_invoke(®, ®);
> 185 if (reg.a0 != LFA_SUCCESS)
> 186 return reg.a0;
> 187
> 188 return reg.a1;
> 189 }
> 190
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-19 12:27 ` [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA) Salman Nabi
2026-01-19 16:29 ` kernel test robot
@ 2026-01-19 18:41 ` kernel test robot
2026-01-19 21:35 ` kernel test robot
` (5 subsequent siblings)
7 siblings, 0 replies; 18+ messages in thread
From: kernel test robot @ 2026-01-19 18:41 UTC (permalink / raw)
To: Salman Nabi, vvidwans, andre.przywara, sudeep.holla, mark.rutland,
lpieralisi
Cc: oe-kbuild-all, ardb, chao.gao, linux-arm-kernel, linux-coco,
linux-kernel, sdonthineni, vsethi, vwadekar
Hi Salman,
kernel test robot noticed the following build errors:
[auto build test ERROR on soc/for-next]
[also build test ERROR on linus/master v6.19-rc6 next-20260116]
[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/Salman-Nabi/firmware-smccc-add-support-for-Live-Firmware-Activation-LFA/20260119-203221
base: https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git for-next
patch link: https://lore.kernel.org/r/20260119122729.287522-2-salman.nabi%40arm.com
patch subject: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
config: arm-sp7021_defconfig (https://download.01.org/0day-ci/archive/20260120/202601200225.cTYuYwRI-lkp@intel.com/config)
compiler: arm-linux-gnueabi-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260120/202601200225.cTYuYwRI-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/202601200225.cTYuYwRI-lkp@intel.com/
All errors (new ones prefixed by >>):
drivers/firmware/smccc/lfa_fw.c: In function 'get_nr_lfa_components':
>> drivers/firmware/smccc/lfa_fw.c:179:16: error: variable 'reg' has initializer but incomplete type
179 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:179:43: warning: excess elements in struct initializer
179 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:179:43: note: (near initialization for 'reg')
>> drivers/firmware/smccc/lfa_fw.c:179:35: error: storage size of 'reg' isn't known
179 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
>> drivers/firmware/smccc/lfa_fw.c:184:9: error: implicit declaration of function 'arm_smccc_1_2_invoke'; did you mean 'arm_smccc_1_1_invoke'? [-Wimplicit-function-declaration]
184 | arm_smccc_1_2_invoke(®, ®);
| ^~~~~~~~~~~~~~~~~~~~
| arm_smccc_1_1_invoke
drivers/firmware/smccc/lfa_fw.c:179:35: warning: unused variable 'reg' [-Wunused-variable]
179 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c: In function 'lfa_cancel':
drivers/firmware/smccc/lfa_fw.c:194:16: error: variable 'reg' has initializer but incomplete type
194 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:194:43: warning: excess elements in struct initializer
194 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:194:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:194:35: error: storage size of 'reg' isn't known
194 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:194:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'call_lfa_activate':
drivers/firmware/smccc/lfa_fw.c:220:16: error: variable 'reg' has initializer but incomplete type
220 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:220:43: warning: excess elements in struct initializer
220 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:220:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:220:35: error: storage size of 'reg' isn't known
220 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:220:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'prime_fw_image':
drivers/firmware/smccc/lfa_fw.c:290:16: error: variable 'reg' has initializer but incomplete type
290 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:290:43: warning: excess elements in struct initializer
290 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:290:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:290:35: error: storage size of 'reg' isn't known
290 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:290:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'activation_pending_show':
drivers/firmware/smccc/lfa_fw.c:360:16: error: variable 'reg' has initializer but incomplete type
360 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:360:43: warning: excess elements in struct initializer
360 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:360:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:360:35: error: storage size of 'reg' isn't known
360 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:360:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'pending_version_show':
drivers/firmware/smccc/lfa_fw.c:434:16: error: variable 'reg' has initializer but incomplete type
434 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:434:43: warning: excess elements in struct initializer
434 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:434:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:434:35: error: storage size of 'reg' isn't known
434 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:434:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'update_fw_images_tree':
drivers/firmware/smccc/lfa_fw.c:586:16: error: variable 'reg' has initializer but incomplete type
586 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:586:43: warning: excess elements in struct initializer
586 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:586:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:586:35: error: storage size of 'reg' isn't known
586 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:586:35: warning: unused variable 'reg' [-Wunused-variable]
drivers/firmware/smccc/lfa_fw.c: In function 'lfa_init':
drivers/firmware/smccc/lfa_fw.c:619:16: error: variable 'reg' has initializer but incomplete type
619 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~~~~~~~~~~~~~~~~
drivers/firmware/smccc/lfa_fw.c:619:43: warning: excess elements in struct initializer
619 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:619:43: note: (near initialization for 'reg')
drivers/firmware/smccc/lfa_fw.c:619:35: error: storage size of 'reg' isn't known
619 | struct arm_smccc_1_2_regs reg = { 0 };
| ^~~
drivers/firmware/smccc/lfa_fw.c:619:35: warning: unused variable 'reg' [-Wunused-variable]
vim +/reg +179 drivers/firmware/smccc/lfa_fw.c
176
177 static unsigned long get_nr_lfa_components(void)
178 {
> 179 struct arm_smccc_1_2_regs reg = { 0 };
180
181 reg.a0 = LFA_1_0_FN_GET_INFO;
182 reg.a1 = 0; /* lfa_info_selector = 0 */
183
> 184 arm_smccc_1_2_invoke(®, ®);
185 if (reg.a0 != LFA_SUCCESS)
186 return reg.a0;
187
188 return reg.a1;
189 }
190
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-19 12:27 ` [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA) Salman Nabi
2026-01-19 16:29 ` kernel test robot
2026-01-19 18:41 ` kernel test robot
@ 2026-01-19 21:35 ` kernel test robot
2026-01-27 23:01 ` Vedashree Vidwans
` (4 subsequent siblings)
7 siblings, 0 replies; 18+ messages in thread
From: kernel test robot @ 2026-01-19 21:35 UTC (permalink / raw)
To: Salman Nabi, vvidwans, andre.przywara, sudeep.holla, mark.rutland,
lpieralisi
Cc: llvm, oe-kbuild-all, ardb, chao.gao, linux-arm-kernel, linux-coco,
linux-kernel, sdonthineni, vsethi, vwadekar
Hi Salman,
kernel test robot noticed the following build errors:
[auto build test ERROR on soc/for-next]
[also build test ERROR on linus/master v6.19-rc6 next-20260116]
[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/Salman-Nabi/firmware-smccc-add-support-for-Live-Firmware-Activation-LFA/20260119-203221
base: https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git for-next
patch link: https://lore.kernel.org/r/20260119122729.287522-2-salman.nabi%40arm.com
patch subject: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
config: arm-defconfig (https://download.01.org/0day-ci/archive/20260120/202601200543.EKFOBfnW-lkp@intel.com/config)
compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 9b8addffa70cee5b2acc5454712d9cf78ce45710)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260120/202601200543.EKFOBfnW-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/202601200543.EKFOBfnW-lkp@intel.com/
All errors (new ones prefixed by >>):
>> drivers/firmware/smccc/lfa_fw.c:179:28: error: variable has incomplete type 'struct arm_smccc_1_2_regs'
179 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:179:9: note: forward declaration of 'struct arm_smccc_1_2_regs'
179 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
>> drivers/firmware/smccc/lfa_fw.c:184:2: error: call to undeclared function 'arm_smccc_1_2_invoke'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
184 | arm_smccc_1_2_invoke(®, ®);
| ^
drivers/firmware/smccc/lfa_fw.c:194:28: error: variable has incomplete type 'struct arm_smccc_1_2_regs'
194 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:194:9: note: forward declaration of 'struct arm_smccc_1_2_regs'
194 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:198:2: error: call to undeclared function 'arm_smccc_1_2_invoke'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
198 | arm_smccc_1_2_invoke(®, ®);
| ^
drivers/firmware/smccc/lfa_fw.c:220:28: error: variable has incomplete type 'struct arm_smccc_1_2_regs'
220 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:220:9: note: forward declaration of 'struct arm_smccc_1_2_regs'
220 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:234:3: error: call to undeclared function 'arm_smccc_1_2_invoke'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
234 | arm_smccc_1_2_invoke(®, ®);
| ^
drivers/firmware/smccc/lfa_fw.c:290:28: error: variable has incomplete type 'struct arm_smccc_1_2_regs'
290 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:290:9: note: forward declaration of 'struct arm_smccc_1_2_regs'
290 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:318:3: error: call to undeclared function 'arm_smccc_1_2_invoke'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
318 | arm_smccc_1_2_invoke(®, ®);
| ^
drivers/firmware/smccc/lfa_fw.c:360:28: error: variable has incomplete type 'struct arm_smccc_1_2_regs'
360 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:360:9: note: forward declaration of 'struct arm_smccc_1_2_regs'
360 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:368:2: error: call to undeclared function 'arm_smccc_1_2_invoke'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
368 | arm_smccc_1_2_invoke(®, ®);
| ^
drivers/firmware/smccc/lfa_fw.c:434:28: error: variable has incomplete type 'struct arm_smccc_1_2_regs'
434 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:434:9: note: forward declaration of 'struct arm_smccc_1_2_regs'
434 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:443:2: error: call to undeclared function 'arm_smccc_1_2_invoke'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
443 | arm_smccc_1_2_invoke(®, ®);
| ^
drivers/firmware/smccc/lfa_fw.c:586:28: error: variable has incomplete type 'struct arm_smccc_1_2_regs'
586 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:586:9: note: forward declaration of 'struct arm_smccc_1_2_regs'
586 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:600:3: error: call to undeclared function 'arm_smccc_1_2_invoke'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
600 | arm_smccc_1_2_invoke(®, ®);
| ^
drivers/firmware/smccc/lfa_fw.c:619:28: error: variable has incomplete type 'struct arm_smccc_1_2_regs'
619 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:619:9: note: forward declaration of 'struct arm_smccc_1_2_regs'
619 | struct arm_smccc_1_2_regs reg = { 0 };
| ^
drivers/firmware/smccc/lfa_fw.c:623:2: error: call to undeclared function 'arm_smccc_1_2_invoke'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
623 | arm_smccc_1_2_invoke(®, ®);
| ^
16 errors generated.
vim +179 drivers/firmware/smccc/lfa_fw.c
176
177 static unsigned long get_nr_lfa_components(void)
178 {
> 179 struct arm_smccc_1_2_regs reg = { 0 };
180
181 reg.a0 = LFA_1_0_FN_GET_INFO;
182 reg.a1 = 0; /* lfa_info_selector = 0 */
183
> 184 arm_smccc_1_2_invoke(®, ®);
185 if (reg.a0 != LFA_SUCCESS)
186 return reg.a0;
187
188 return reg.a1;
189 }
190
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-19 12:27 ` [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA) Salman Nabi
` (2 preceding siblings ...)
2026-01-19 21:35 ` kernel test robot
@ 2026-01-27 23:01 ` Vedashree Vidwans
2026-01-29 17:26 ` Andre Przywara
2026-01-31 1:35 ` Trilok Soni
` (3 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: Vedashree Vidwans @ 2026-01-27 23:01 UTC (permalink / raw)
To: Salman Nabi, andre.przywara, sudeep.holla, mark.rutland,
lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
Hello,
On 1/19/26 04:27, Salman Nabi wrote:
> The Arm Live Firmware Activation (LFA) is a specification [1] to describe
> activating firmware components without a reboot. Those components
> (like TF-A's BL31, EDK-II, TF-RMM, secure paylods) would be updated the
> usual way: via fwupd, FF-A or other secure storage methods, or via some
> IMPDEF Out-Of-Bound method. The user can then activate this new firmware,
> at system runtime, without requiring a reboot.
> The specification covers the SMCCC interface to list and query available
> components and eventually trigger the activation.
>
> Add a new directory under /sys/firmware to present firmware components
> capable of live activation. Each of them is a directory under lfa/,
> and is identified via its GUID. The activation will be triggered by echoing
> "1" into the "activate" file:
> ==========================================
> /sys/firmware/lfa # ls -l . 6c*
> .:
> total 0
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 47d4086d-4cfe-9846-9b95-2950cbbd5a00
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 6c0762a6-12f2-4b56-92cb-ba8f633606d9
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 d6d0eea7-fcea-d54b-9782-9934f234b6e4
>
> 6c0762a6-12f2-4b56-92cb-ba8f633606d9:
> total 0
> --w------- 1 0 0 4096 Jan 19 11:33 activate
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_capable
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_pending
> --w------- 1 0 0 4096 Jan 19 11:33 cancel
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 cpu_rendezvous
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 current_version
> -rw-r--r-- 1 0 0 4096 Jan 19 11:33 force_cpu_rendezvous
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 may_reset_cpu
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 name
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 pending_version
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # grep . *
> grep: activate: Permission denied
> activation_capable:1
> activation_pending:1
> grep: cancel: Permission denied
> cpu_rendezvous:1
> current_version:0.0
> force_cpu_rendezvous:1
> may_reset_cpu:0
> name:TF-RMM
> pending_version:0.0
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # echo 1 > activate
> [ 2825.797871] Arm LFA: firmware activation succeeded.
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 #
> ==========================================
>
> [1] https://developer.arm.com/documentation/den0147/latest/
>
> Signed-off-by: Salman Nabi <salman.nabi@arm.com>
> ---
> drivers/firmware/smccc/Kconfig | 8 +
> drivers/firmware/smccc/Makefile | 1 +
> drivers/firmware/smccc/lfa_fw.c | 668 ++++++++++++++++++++++++++++++++
> 3 files changed, 677 insertions(+)
> create mode 100644 drivers/firmware/smccc/lfa_fw.c
>
> diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig
> index 15e7466179a6..ff7ca49486b0 100644
> --- a/drivers/firmware/smccc/Kconfig
> +++ b/drivers/firmware/smccc/Kconfig
> @@ -23,3 +23,11 @@ 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
> + default y
> + help
> + Include support for triggering Live Firmware Activation, which
> + allows to upgrade certain firmware components without a reboot.
> diff --git a/drivers/firmware/smccc/Makefile b/drivers/firmware/smccc/Makefile
> index 40d19144a860..a6dd01558a94 100644
> --- a/drivers/firmware/smccc/Makefile
> +++ b/drivers/firmware/smccc/Makefile
> @@ -2,3 +2,4 @@
> #
> obj-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += smccc.o kvm_guest.o
> obj-$(CONFIG_ARM_SMCCC_SOC_ID) += soc_id.o
> +obj-$(CONFIG_ARM_LFA) += lfa_fw.o
> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
> new file mode 100644
> index 000000000000..ce54049b7190
> --- /dev/null
> +++ b/drivers/firmware/smccc/lfa_fw.c
> @@ -0,0 +1,668 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Arm Limited
> + */
> +
> +#include <linux/fs.h>
> +#include <linux/init.h>
> +#include <linux/kobject.h>
> +#include <linux/module.h>
> +#include <linux/stop_machine.h>
> +#include <linux/string.h>
> +#include <linux/sysfs.h>
> +#include <linux/arm-smccc.h>
> +#include <linux/psci.h>
> +#include <uapi/linux/psci.h>
> +#include <linux/uuid.h>
> +#include <linux/array_size.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +
> +#undef pr_fmt
> +#define pr_fmt(fmt) "Arm LFA: " fmt
> +
> +/* LFA v1.0b0 specification */
> +#define LFA_1_0_FN_BASE 0xc40002e0
> +#define LFA_1_0_FN(n) (LFA_1_0_FN_BASE + (n))
> +
> +#define LFA_1_0_FN_GET_VERSION LFA_1_0_FN(0)
> +#define LFA_1_0_FN_CHECK_FEATURE LFA_1_0_FN(1)
> +#define LFA_1_0_FN_GET_INFO LFA_1_0_FN(2)
> +#define LFA_1_0_FN_GET_INVENTORY LFA_1_0_FN(3)
> +#define LFA_1_0_FN_PRIME LFA_1_0_FN(4)
> +#define LFA_1_0_FN_ACTIVATE LFA_1_0_FN(5)
> +#define LFA_1_0_FN_CANCEL LFA_1_0_FN(6)
> +
> +/* CALL_AGAIN flags (returned by SMC) */
> +#define LFA_PRIME_CALL_AGAIN BIT(0)
> +#define LFA_ACTIVATE_CALL_AGAIN BIT(0)
> +
> +/* LFA return values */
> +#define LFA_SUCCESS 0
> +#define LFA_NOT_SUPPORTED 1
> +#define LFA_BUSY 2
> +#define LFA_AUTH_ERROR 3
> +#define LFA_NO_MEMORY 4
> +#define LFA_CRITICAL_ERROR 5
> +#define LFA_DEVICE_ERROR 6
> +#define LFA_WRONG_STATE 7
> +#define LFA_INVALID_PARAMETERS 8
> +#define LFA_COMPONENT_WRONG_STATE 9
> +#define LFA_INVALID_ADDRESS 10
> +#define LFA_ACTIVATION_FAILED 11
> +
> +#define LFA_ERROR_STRING(name) \
> + [name] = #name
> +
> +static const char * const lfa_error_strings[] = {
> + LFA_ERROR_STRING(LFA_SUCCESS),
> + LFA_ERROR_STRING(LFA_NOT_SUPPORTED),
> + LFA_ERROR_STRING(LFA_BUSY),
> + LFA_ERROR_STRING(LFA_AUTH_ERROR),
> + LFA_ERROR_STRING(LFA_NO_MEMORY),
> + LFA_ERROR_STRING(LFA_CRITICAL_ERROR),
> + LFA_ERROR_STRING(LFA_DEVICE_ERROR),
> + LFA_ERROR_STRING(LFA_WRONG_STATE),
> + LFA_ERROR_STRING(LFA_INVALID_PARAMETERS),
> + LFA_ERROR_STRING(LFA_COMPONENT_WRONG_STATE),
> + LFA_ERROR_STRING(LFA_INVALID_ADDRESS),
> + LFA_ERROR_STRING(LFA_ACTIVATION_FAILED)
> +};
> +
> +enum image_attr_names {
> + LFA_ATTR_NAME,
> + LFA_ATTR_CURRENT_VERSION,
> + LFA_ATTR_PENDING_VERSION,
> + LFA_ATTR_ACT_CAPABLE,
> + LFA_ATTR_ACT_PENDING,
> + LFA_ATTR_MAY_RESET_CPU,
> + LFA_ATTR_CPU_RENDEZVOUS,
> + LFA_ATTR_FORCE_CPU_RENDEZVOUS,
> + LFA_ATTR_ACTIVATE,
> + LFA_ATTR_CANCEL,
> + LFA_ATTR_NR_IMAGES
> +};
> +
> +struct image_props {
> + struct list_head image_node;
> + const char *image_name;
> + int fw_seq_id;
> + u64 current_version;
> + u64 pending_version;
> + bool activation_capable;
> + bool activation_pending;
> + bool may_reset_cpu;
> + bool cpu_rendezvous;
> + bool cpu_rendezvous_forced;
> + struct kobject *image_dir;
> + struct kobj_attribute image_attrs[LFA_ATTR_NR_IMAGES];
> +};
> +static LIST_HEAD(lfa_fw_images);
> +
> +/* A UUID split over two 64-bit registers */
> +struct uuid_regs {
> + u64 uuid_lo;
> + u64 uuid_hi;
> +};
> +
> +static const struct fw_image_uuid {
> + const char *name;
> + const char *uuid;
> +} fw_images_uuids[] = {
> + {
> + .name = "TF-A BL31 runtime",
> + .uuid = "47d4086d-4cfe-9846-9b95-2950cbbd5a00",
> + },
> + {
> + .name = "BL33 non-secure payload",
> + .uuid = "d6d0eea7-fcea-d54b-9782-9934f234b6e4",
> + },
> + {
> + .name = "TF-RMM",
> + .uuid = "6c0762a6-12f2-4b56-92cb-ba8f633606d9",
> + },
> +};
> +
> +static struct kobject *lfa_dir;
> +static DEFINE_MUTEX(lfa_lock);
> +static struct workqueue_struct *fw_images_update_wq;
> +static struct work_struct fw_images_update_work;
> +
> +static int update_fw_images_tree(void);
> +
> +static void delete_fw_image_node(struct image_props *attrs)
> +{
> + int i;
> +
> + for (i = 0; i < LFA_ATTR_NR_IMAGES; i++)
> + sysfs_remove_file(attrs->image_dir, &attrs->image_attrs[i].attr);
> +
> + kobject_put(attrs->image_dir);
> + list_del(&attrs->image_node);
> + kfree(attrs);
> +}
> +
> +static void remove_invalid_fw_images(struct work_struct *work)
> +{
> + struct image_props *attrs, *tmp;
> +
> + mutex_lock(&lfa_lock);
> +
> + /*
> + * Remove firmware images including directories that are no longer
> + * present in the LFA agent after updating the existing ones.
> + */
> + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node) {
> + if (attrs->fw_seq_id == -1)
> + delete_fw_image_node(attrs);
> + }
> +
> + mutex_unlock(&lfa_lock);
> +}
> +
> +static void set_image_flags(struct image_props *attrs, int seq_id,
> + u32 image_flags, u64 reg_current_ver,
> + u64 reg_pending_ver)
> +{
> + attrs->fw_seq_id = seq_id;
> + attrs->current_version = reg_current_ver;
> + attrs->pending_version = reg_pending_ver;
> + attrs->activation_capable = !!(image_flags & BIT(0));
> + attrs->activation_pending = !!(image_flags & BIT(1));
> + attrs->may_reset_cpu = !!(image_flags & BIT(2));
> + /* cpu_rendezvous_optional bit has inverse logic in the spec */
> + attrs->cpu_rendezvous = !(image_flags & BIT(3));
> +}
> +
> +static unsigned long get_nr_lfa_components(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_GET_INFO;
> + reg.a1 = 0; /* lfa_info_selector = 0 */
> +
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 != LFA_SUCCESS)
> + return reg.a0;
> +
> + return reg.a1;
> +}
> +
> +static int lfa_cancel(void *data)
> +{
> + struct image_props *attrs = data;
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_CANCEL;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> +
> + /*
> + * When firmware activation is called with "skip_cpu_rendezvous=1",
> + * LFA_CANCEL can fail with LFA_BUSY if the activation could not be
> + * cancelled.
> + */
> + if (reg.a0 == LFA_SUCCESS) {
> + pr_info("Activation cancelled for image %s\n",
> + attrs->image_name);
> + } else {
> + pr_err("Firmware activation could not be cancelled: %s\n",
> + lfa_error_strings[-reg.a0]);
> + return -EINVAL;
> + }
> +
> + return reg.a0;
> +}
> +
> +static int call_lfa_activate(void *data)
> +{
> + struct image_props *attrs = data;
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_ACTIVATE;
> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
> + /*
> + * As we do not support updates requiring a CPU reset (yet),
> + * we pass 0 in reg.a3 and reg.a4, holding the entry point and context
> + * ID respectively.
> + * cpu_rendezvous_forced is set by the administrator, via sysfs,
> + * cpu_rendezvous is dictated by each firmware component.
> + */
> + reg.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous);
> +
> + for (;;) {
> + arm_smccc_1_2_invoke(®, ®);
> +
> + if ((long)reg.a0 < 0) {
> + pr_err("ACTIVATE for image %s failed: %s\n",
> + attrs->image_name, lfa_error_strings[-reg.a0]);
> + return reg.a0;
> + }
> + if (!(reg.a1 & LFA_ACTIVATE_CALL_AGAIN))
> + break; /* ACTIVATE successful */
> + }
The implementation uses same 'struct arm_smccc_1_2_regs reg' as
input and output for arm_smccc_1_2_invoke(). Here, reg.a0 (function ID),
reg.a1 (fw_seq_id) and reg.a2 (cpu rendezvous) are initialized once
before the loop and arm_smccc_1_2_invoke() overwrites the whole register
set on every iteration. That means inputs (a0, a1, a2) can be clobbered
between loop iterations unless reassigned each time.
Suggestion: Re-initialize input members of reg on each loop iteration or
use a separate 'struct arm_smccc_1_2_regs' for output to avoid input
corruption.
> +
> + return reg.a0;
> +}
> +
> +static int activate_fw_image(struct image_props *attrs)
> +{
> + int ret;
> +
> + mutex_lock(&lfa_lock);
> + if (attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous)
> + ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask);
> + else
> + ret = call_lfa_activate(attrs);
> +
> + if (ret != 0) {
> + mutex_unlock(&lfa_lock);
> + return lfa_cancel(attrs);
> + }
> +
> + /*
> + * Invalidate fw_seq_ids (-1) for all images as the seq_ids and the
> + * number of firmware images in the LFA agent may change after a
> + * successful activation attempt. Negate all image flags as well.
> + */
> + attrs = NULL;
> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
> + set_image_flags(attrs, -1, 0b1000, 0, 0);
> + }
> +
> + update_fw_images_tree();
> +
> + /*
> + * Removing non-valid image directories at the end of an activation.
> + * We can't remove the sysfs attributes while in the respective
> + * _store() handler, so have to postpone the list removal to a
> + * workqueue.
> + */
> + INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
> + queue_work(fw_images_update_wq, &fw_images_update_work);
> + mutex_unlock(&lfa_lock);
> +
> + return ret;
> +}
> +
> +static int prime_fw_image(struct image_props *attrs)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + int ret;
> +
> + mutex_lock(&lfa_lock);
> + /* Avoid SMC calls on invalid firmware images */
> + if (attrs->fw_seq_id == -1) {
> + pr_err("Arm LFA: Invalid firmware sequence id\n");
> + mutex_unlock(&lfa_lock);
> +
> + return -ENODEV;
> + }
> +
> + if (attrs->may_reset_cpu) {
> + pr_err("CPU reset not supported by kernel driver\n");
> + mutex_unlock(&lfa_lock);
> +
> + return -EINVAL;
> + }
> +
> + /*
> + * LFA_PRIME/ACTIVATE will return 1 in reg.a1 if the firmware
> + * priming/activation is still in progress. In that case
> + * LFA_PRIME/ACTIVATE will need to be called again.
> + * reg.a1 will become 0 once the prime/activate process completes.
> + */
> + reg.a0 = LFA_1_0_FN_PRIME;
> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
> + for (;;) {
> + arm_smccc_1_2_invoke(®, ®);
> +
> + if ((long)reg.a0 < 0) {
> + pr_err("LFA_PRIME for image %s failed: %s\n",
> + attrs->image_name, lfa_error_strings[-reg.a0]);
> + mutex_unlock(&lfa_lock);
> +
> + return reg.a0;
> + }
> + if (!(reg.a1 & LFA_PRIME_CALL_AGAIN)) {
> + ret = 0;
> + break; /* PRIME successful */
> + }
> + }
Similar comment to call_lfa_activate(). Suggestion to either re-assign
'struct arm_smccc_1_2_regs' input values on each loop iteration or use a
separate 'struct arm_smccc_1_2_regs' for output to avoid input
corruption between loop iterations. This matches the intended
'CALL_AGAIN' protocal while keeping the inputs stable across retries.
> +
> + mutex_unlock(&lfa_lock);
> + return ret;
The introduction of separate 'ret' cariable does not appear necessary
for functional correctness. The SMCCC status is conveyed via reg.a0 on
each iteration, so returning reg.a0 should preserve existing behavior.
If 'ret' must be kept, consider initializing it to 0 at declaration
time. That avoids setting ret = 0 inside 'PRIME successful' path and
leads to simpler control flow.
> +}
> +
> +static ssize_t name_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_NAME]);
> +
> + return sysfs_emit(buf, "%s\n", attrs->image_name);
> +}
> +
> +static ssize_t activation_capable_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_CAPABLE]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->activation_capable);
> +}
> +
> +static ssize_t activation_pending_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_PENDING]);
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + /*
> + * Activation pending status can change anytime thus we need to update
> + * and return its current value
> + */
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS)
> + attrs->activation_pending = !!(reg.a3 & BIT(1));
> +
> + return sysfs_emit(buf, "%d\n", attrs->activation_pending);
> +}
> +
> +static ssize_t may_reset_cpu_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_MAY_RESET_CPU]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->may_reset_cpu);
> +}
> +
> +static ssize_t cpu_rendezvous_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CPU_RENDEZVOUS]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous);
> +}
> +
> +static ssize_t force_cpu_rendezvous_store(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
> + int ret;
> +
> + ret = kstrtobool(buf, &attrs->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 image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous_forced);
> +}
> +
> +static ssize_t current_version_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CURRENT_VERSION]);
> + u32 maj, min;
> +
> + maj = attrs->current_version >> 32;
> + min = attrs->current_version & 0xffffffff;
> + return sysfs_emit(buf, "%u.%u\n", maj, min);
> +}
> +
> +static ssize_t pending_version_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_PENDING]);
> + struct arm_smccc_1_2_regs reg = { 0 };
> + u32 maj, min;
> +
> + /*
> + * Similar to activation pending, this value can change following an
> + * update, we need to retrieve fresh info instead of stale information.
> + */
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS) {
> + if (reg.a5 != 0 && attrs->activation_pending)
> + {
> + attrs->pending_version = reg.a5;
> + maj = reg.a5 >> 32;
> + min = reg.a5 & 0xffffffff;
> + }
> + }
> +
> + return sysfs_emit(buf, "%u.%u\n", maj, min);
> +}
> +
> +static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACTIVATE]);
> + int ret;
> +
> + ret = prime_fw_image(attrs);
> + if (ret) {
> + pr_err("Firmware prime failed: %s\n",
> + lfa_error_strings[-ret]);
> + return -ECANCELED;
> + }
> +
> + ret = activate_fw_image(attrs);
> + if (ret) {
> + pr_err("Firmware activation failed: %s\n",
> + lfa_error_strings[-ret]);
> + return -ECANCELED;
> + }
> +
> + pr_info("Firmware activation succeeded\n");
> +
> + return count;
> +}
> +
> +static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CANCEL]);
> + int ret;
> +
> + ret = lfa_cancel(attrs);
> + if (ret != 0)
> + return ret;
> +
> + return count;
> +}
> +
> +static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = {
> + [LFA_ATTR_NAME] = __ATTR_RO(name),
> + [LFA_ATTR_CURRENT_VERSION] = __ATTR_RO(current_version),
> + [LFA_ATTR_PENDING_VERSION] = __ATTR_RO(pending_version),
> + [LFA_ATTR_ACT_CAPABLE] = __ATTR_RO(activation_capable),
> + [LFA_ATTR_ACT_PENDING] = __ATTR_RO(activation_pending),
> + [LFA_ATTR_MAY_RESET_CPU] = __ATTR_RO(may_reset_cpu),
> + [LFA_ATTR_CPU_RENDEZVOUS] = __ATTR_RO(cpu_rendezvous),
> + [LFA_ATTR_FORCE_CPU_RENDEZVOUS] = __ATTR_RW(force_cpu_rendezvous),
> + [LFA_ATTR_ACTIVATE] = __ATTR_WO(activate),
> + [LFA_ATTR_CANCEL] = __ATTR_WO(cancel)
> +};
> +
> +static void clean_fw_images_tree(void)
> +{
> + struct image_props *attrs, *tmp;
> +
> + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node)
> + delete_fw_image_node(attrs);
> +}
> +
> +static int update_fw_image_node(char *fw_uuid, int seq_id,
> + u32 image_flags, u64 reg_current_ver,
> + u64 reg_pending_ver)
> +{
> + const char *image_name = "(unknown)";
> + struct image_props *attrs;
> + int ret;
> +
> + /*
> + * If a fw_image is already in the images list then we just update
> + * its flags and seq_id instead of trying to recreate it.
> + */
> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
> + if (!strcmp(attrs->image_dir->name, fw_uuid)) {
> + set_image_flags(attrs, seq_id, image_flags,
> + reg_current_ver, reg_pending_ver);
> + return 0;
> + }
> + }
> +
> + attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);
> + if (!attrs)
> + return -ENOMEM;
> +
> + for (int i = 0; i < ARRAY_SIZE(fw_images_uuids); i++) {
> + if (!strcmp(fw_images_uuids[i].uuid, fw_uuid))
> + image_name = fw_images_uuids[i].name;
> + }
I would recommend using fw_uuid as the image_name when UUID is not
found in fw_images_uuids[], currently the driver assigns 'unknown'
in such case.
There is a valid possibility that platform-specific FW images, not
listed in fw_images_uuids[], are used by LFA agent for live FW
activation. In such scenarios, falling back to 'unknown' would lose
important information especially when errors surface in
call_lfa_activate(). Using UUID directly would indicate which
image failed or behaved unexpectedly.
Thank you,
Veda
> +
> + attrs->image_dir = kobject_create_and_add(fw_uuid, lfa_dir);
> + if (!attrs->image_dir)
> + return -ENOMEM;
> +
> + INIT_LIST_HEAD(&attrs->image_node);
> + attrs->image_name = image_name;
> + attrs->cpu_rendezvous_forced = 1;
> + set_image_flags(attrs, seq_id, image_flags, reg_current_ver,
> + reg_pending_ver);
> +
> + /*
> + * The attributes for each sysfs file are constant (handler functions,
> + * name and permissions are the same within each directory), but we
> + * need a per-directory copy regardless, to get a unique handle
> + * for each directory, so that container_of can do its magic.
> + * Also this requires an explicit sysfs_attr_init(), since it's a new
> + * copy, to make LOCKDEP happy.
> + */
> + memcpy(attrs->image_attrs, image_attrs_group,
> + sizeof(attrs->image_attrs));
> + for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++) {
> + struct attribute *attr = &attrs->image_attrs[i].attr;
> +
> + sysfs_attr_init(attr);
> + ret = sysfs_create_file(attrs->image_dir, attr);
> + if (ret) {
> + pr_err("creating sysfs file for uuid %s: %d\n",
> + fw_uuid, ret);
> + clean_fw_images_tree();
> +
> + return ret;
> + }
> + }
> + list_add(&attrs->image_node, &lfa_fw_images);
> +
> + return ret;
> +}
> +
> +static int update_fw_images_tree(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + struct uuid_regs image_uuid;
> + char image_id_str[40];
> + int ret, num_of_components;
> +
> + num_of_components = get_nr_lfa_components();
> + if (num_of_components <= 0) {
> + pr_err("Error getting number of LFA components\n");
> + return -ENODEV;
> + }
> +
> + for (int i = 0; i < num_of_components; i++) {
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = i; /* fw_seq_id under consideration */
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS) {
> + image_uuid.uuid_lo = reg.a1;
> + image_uuid.uuid_hi = reg.a2;
> +
> + snprintf(image_id_str, sizeof(image_id_str), "%pUb",
> + &image_uuid);
> + ret = update_fw_image_node(image_id_str, i,
> + reg.a3, reg.a4, reg.a5);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int __init lfa_init(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + int err;
> +
> + reg.a0 = LFA_1_0_FN_GET_VERSION;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == -LFA_NOT_SUPPORTED) {
> + pr_info("Live Firmware activation: no firmware agent found\n");
> + return -ENODEV;
> + }
> +
> + fw_images_update_wq = alloc_workqueue("fw_images_update_wq",
> + WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
> + if (!fw_images_update_wq) {
> + pr_err("Live Firmware Activation: Failed to allocate workqueue.\n");
> +
> + return -ENOMEM;
> + }
> +
> + pr_info("Live Firmware Activation: detected v%ld.%ld\n",
> + reg.a0 >> 16, reg.a0 & 0xffff);
> +
> + lfa_dir = kobject_create_and_add("lfa", firmware_kobj);
> + if (!lfa_dir)
> + return -ENOMEM;
> +
> + mutex_lock(&lfa_lock);
> + err = update_fw_images_tree();
> + if (err != 0)
> + kobject_put(lfa_dir);
> +
> + mutex_unlock(&lfa_lock);
> + return err;
> +}
> +module_init(lfa_init);
> +
> +static void __exit lfa_exit(void)
> +{
> + flush_workqueue(fw_images_update_wq);
> + destroy_workqueue(fw_images_update_wq);
> +
> + mutex_lock(&lfa_lock);
> + clean_fw_images_tree();
> + mutex_unlock(&lfa_lock);
> +
> + kobject_put(lfa_dir);
> +}
> +module_exit(lfa_exit);
> +
> +MODULE_DESCRIPTION("ARM Live Firmware Activation (LFA)");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-27 23:01 ` Vedashree Vidwans
@ 2026-01-29 17:26 ` Andre Przywara
2026-01-30 19:29 ` Vedashree Vidwans
0 siblings, 1 reply; 18+ messages in thread
From: Andre Przywara @ 2026-01-29 17:26 UTC (permalink / raw)
To: Vedashree Vidwans, Salman Nabi, sudeep.holla, mark.rutland,
lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
Hi Vedashree,
many thanks for having a look!
On 1/28/26 00:01, Vedashree Vidwans wrote:
> Hello,
>
> On 1/19/26 04:27, Salman Nabi wrote:
>> 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
[ ... ]
>> drivers/firmware/smccc/Kconfig | 8 +
>> drivers/firmware/smccc/Makefile | 1 +
>> drivers/firmware/smccc/lfa_fw.c | 668 ++++++++++++++++++++++++++++++++
>> 3 files changed, 677 insertions(+)
>> create mode 100644 drivers/firmware/smccc/lfa_fw.c
>>
[ ... ]
>> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/
>> lfa_fw.c
>> new file mode 100644
>> index 000000000000..ce54049b7190
>> --- /dev/null
>> +++ b/drivers/firmware/smccc/lfa_fw.c
>> @@ -0,0 +1,668 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2025 Arm Limited
>> + */
[ ... ]
>> +
>> +static int call_lfa_activate(void *data)
>> +{
>> + struct image_props *attrs = data;
>> + struct arm_smccc_1_2_regs reg = { 0 };
>> +
>> + reg.a0 = LFA_1_0_FN_ACTIVATE;
>> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
>> + /*
>> + * As we do not support updates requiring a CPU reset (yet),
>> + * we pass 0 in reg.a3 and reg.a4, holding the entry point and
>> context
>> + * ID respectively.
>> + * cpu_rendezvous_forced is set by the administrator, via sysfs,
>> + * cpu_rendezvous is dictated by each firmware component.
>> + */
>> + reg.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous);
>> +
>> + for (;;) {
>> + arm_smccc_1_2_invoke(®, ®);
>> +
>> + if ((long)reg.a0 < 0) {
>> + pr_err("ACTIVATE for image %s failed: %s\n",
>> + attrs->image_name, lfa_error_strings[-reg.a0]);
>> + return reg.a0;
>> + }
>> + if (!(reg.a1 & LFA_ACTIVATE_CALL_AGAIN))
>> + break; /* ACTIVATE successful */
>> + }
> The implementation uses same 'struct arm_smccc_1_2_regs reg' as
> input and output for arm_smccc_1_2_invoke(). Here, reg.a0 (function ID),
> reg.a1 (fw_seq_id) and reg.a2 (cpu rendezvous) are initialized once
> before the loop and arm_smccc_1_2_invoke() overwrites the whole register
> set on every iteration. That means inputs (a0, a1, a2) can be clobbered
> between loop iterations unless reassigned each time.
> Suggestion: Re-initialize input members of reg on each loop iteration or
> use a separate 'struct arm_smccc_1_2_regs' for output to avoid input
> corruption.
Ah, that's a good point and indeed a bug, thanks for spotting this! Will
fix it.
>> +
>> + return reg.a0;
>> +}
>> +
>> +static int activate_fw_image(struct image_props *attrs)
>> +{
>> + int ret;
>> +
>> + mutex_lock(&lfa_lock);
>> + if (attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous)
>> + ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask);
>> + else
>> + ret = call_lfa_activate(attrs);
>> +
>> + if (ret != 0) {
>> + mutex_unlock(&lfa_lock);
>> + return lfa_cancel(attrs);
>> + }
>> +
>> + /*
>> + * Invalidate fw_seq_ids (-1) for all images as the seq_ids and the
>> + * number of firmware images in the LFA agent may change after a
>> + * successful activation attempt. Negate all image flags as well.
>> + */
>> + attrs = NULL;
>> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
>> + set_image_flags(attrs, -1, 0b1000, 0, 0);
>> + }
>> +
>> + update_fw_images_tree();
>> +
>> + /*
>> + * Removing non-valid image directories at the end of an activation.
>> + * We can't remove the sysfs attributes while in the respective
>> + * _store() handler, so have to postpone the list removal to a
>> + * workqueue.
>> + */
>> + INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
>> + queue_work(fw_images_update_wq, &fw_images_update_work);
>> + mutex_unlock(&lfa_lock);
>> +
>> + return ret;
>> +}
>> +
>> +static int prime_fw_image(struct image_props *attrs)
>> +{
>> + struct arm_smccc_1_2_regs reg = { 0 };
>> + int ret;
>> +
>> + mutex_lock(&lfa_lock);
>> + /* Avoid SMC calls on invalid firmware images */
>> + if (attrs->fw_seq_id == -1) {
>> + pr_err("Arm LFA: Invalid firmware sequence id\n");
>> + mutex_unlock(&lfa_lock);
>> +
>> + return -ENODEV;
>> + }
>> +
>> + if (attrs->may_reset_cpu) {
>> + pr_err("CPU reset not supported by kernel driver\n");
>> + mutex_unlock(&lfa_lock);
>> +
>> + return -EINVAL;
>> + }
>> +
>> + /*
>> + * LFA_PRIME/ACTIVATE will return 1 in reg.a1 if the firmware
>> + * priming/activation is still in progress. In that case
>> + * LFA_PRIME/ACTIVATE will need to be called again.
>> + * reg.a1 will become 0 once the prime/activate process completes.
>> + */
>> + reg.a0 = LFA_1_0_FN_PRIME;
>> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
>> + for (;;) {
>> + arm_smccc_1_2_invoke(®, ®);
>> +
>> + if ((long)reg.a0 < 0) {
>> + pr_err("LFA_PRIME for image %s failed: %s\n",
>> + attrs->image_name, lfa_error_strings[-reg.a0]);
>> + mutex_unlock(&lfa_lock);
>> +
>> + return reg.a0;
>> + }
>> + if (!(reg.a1 & LFA_PRIME_CALL_AGAIN)) {
>> + ret = 0;
>> + break; /* PRIME successful */
>> + }
>> + }
> Similar comment to call_lfa_activate(). Suggestion to either re-assign
> 'struct arm_smccc_1_2_regs' input values on each loop iteration or use a
> separate 'struct arm_smccc_1_2_regs' for output to avoid input
> corruption between loop iterations. This matches the intended
> 'CALL_AGAIN' protocal while keeping the inputs stable across retries.
Indeed, good catch.
>> +
>> + mutex_unlock(&lfa_lock);
>> + return ret;
> The introduction of separate 'ret' cariable does not appear necessary
> for functional correctness. The SMCCC status is conveyed via reg.a0 on
> each iteration, so returning reg.a0 should preserve existing behavior.
> If 'ret' must be kept, consider initializing it to 0 at declaration
> time. That avoids setting ret = 0 inside 'PRIME successful' path and
> leads to simpler control flow.
Right, looks like a leftover from a previous version, it's indeed not
needed.
>> +}
>> +
>> +static ssize_t name_show(struct kobject *kobj, struct kobj_attribute
>> *attr,
>> + char *buf)
>> +{
>> + struct image_props *attrs = container_of(attr, struct image_props,
>> + image_attrs[LFA_ATTR_NAME]);
>> +
>> + return sysfs_emit(buf, "%s\n", attrs->image_name);
>> +}
>> +
>> +static ssize_t activation_capable_show(struct kobject *kobj,
>> + struct kobj_attribute *attr, char *buf)
>> +{
>> + struct image_props *attrs = container_of(attr, struct image_props,
>> + image_attrs[LFA_ATTR_ACT_CAPABLE]);
>> +
>> + return sysfs_emit(buf, "%d\n", attrs->activation_capable);
>> +}
>> +
>> +static ssize_t activation_pending_show(struct kobject *kobj,
>> + struct kobj_attribute *attr, char *buf)
>> +{
>> + struct image_props *attrs = container_of(attr, struct image_props,
>> + image_attrs[LFA_ATTR_ACT_PENDING]);
>> + struct arm_smccc_1_2_regs reg = { 0 };
>> +
>> + /*
>> + * Activation pending status can change anytime thus we need to
>> update
>> + * and return its current value
>> + */
>> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
>> + reg.a1 = attrs->fw_seq_id;
>> + arm_smccc_1_2_invoke(®, ®);
>> + if (reg.a0 == LFA_SUCCESS)
>> + attrs->activation_pending = !!(reg.a3 & BIT(1));
>> +
>> + return sysfs_emit(buf, "%d\n", attrs->activation_pending);
>> +}
>> +
>> +static ssize_t may_reset_cpu_show(struct kobject *kobj,
>> + struct kobj_attribute *attr, char *buf)
>> +{
>> + struct image_props *attrs = container_of(attr, struct image_props,
>> + image_attrs[LFA_ATTR_MAY_RESET_CPU]);
>> +
>> + return sysfs_emit(buf, "%d\n", attrs->may_reset_cpu);
>> +}
>> +
>> +static ssize_t cpu_rendezvous_show(struct kobject *kobj,
>> + struct kobj_attribute *attr, char *buf)
>> +{
>> + struct image_props *attrs = container_of(attr, struct image_props,
>> + image_attrs[LFA_ATTR_CPU_RENDEZVOUS]);
>> +
>> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous);
>> +}
>> +
>> +static ssize_t force_cpu_rendezvous_store(struct kobject *kobj,
>> + struct kobj_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + struct image_props *attrs = container_of(attr, struct image_props,
>> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
>> + int ret;
>> +
>> + ret = kstrtobool(buf, &attrs->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 image_props *attrs = container_of(attr, struct image_props,
>> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
>> +
>> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous_forced);
>> +}
>> +
>> +static ssize_t current_version_show(struct kobject *kobj,
>> + struct kobj_attribute *attr, char *buf)
>> +{
>> + struct image_props *attrs = container_of(attr, struct image_props,
>> + image_attrs[LFA_ATTR_CURRENT_VERSION]);
>> + u32 maj, min;
>> +
>> + maj = attrs->current_version >> 32;
>> + min = attrs->current_version & 0xffffffff;
>> + return sysfs_emit(buf, "%u.%u\n", maj, min);
>> +}
>> +
>> +static ssize_t pending_version_show(struct kobject *kobj,
>> + struct kobj_attribute *attr, char *buf)
>> +{
>> + struct image_props *attrs = container_of(attr, struct image_props,
>> + image_attrs[LFA_ATTR_ACT_PENDING]);
>> + struct arm_smccc_1_2_regs reg = { 0 };
>> + u32 maj, min;
>> +
>> + /*
>> + * Similar to activation pending, this value can change following an
>> + * update, we need to retrieve fresh info instead of stale
>> information.
>> + */
>> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
>> + reg.a1 = attrs->fw_seq_id;
>> + arm_smccc_1_2_invoke(®, ®);
>> + if (reg.a0 == LFA_SUCCESS) {
>> + if (reg.a5 != 0 && attrs->activation_pending)
>> + {
>> + attrs->pending_version = reg.a5;
>> + maj = reg.a5 >> 32;
>> + min = reg.a5 & 0xffffffff;
>> + }
>> + }
>> +
>> + return sysfs_emit(buf, "%u.%u\n", maj, min);
>> +}
>> +
>> +static ssize_t activate_store(struct kobject *kobj, struct
>> kobj_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + struct image_props *attrs = container_of(attr, struct image_props,
>> + image_attrs[LFA_ATTR_ACTIVATE]);
>> + int ret;
>> +
>> + ret = prime_fw_image(attrs);
>> + if (ret) {
>> + pr_err("Firmware prime failed: %s\n",
>> + lfa_error_strings[-ret]);
>> + return -ECANCELED;
>> + }
>> +
>> + ret = activate_fw_image(attrs);
>> + if (ret) {
>> + pr_err("Firmware activation failed: %s\n",
>> + lfa_error_strings[-ret]);
>> + return -ECANCELED;
>> + }
>> +
>> + pr_info("Firmware activation succeeded\n");
>> +
>> + return count;
>> +}
>> +
>> +static ssize_t cancel_store(struct kobject *kobj, struct
>> kobj_attribute *attr,
>> + const char *buf, size_t count)
>> +{
>> + struct image_props *attrs = container_of(attr, struct image_props,
>> + image_attrs[LFA_ATTR_CANCEL]);
>> + int ret;
>> +
>> + ret = lfa_cancel(attrs);
>> + if (ret != 0)
>> + return ret;
>> +
>> + return count;
>> +}
>> +
>> +static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = {
>> + [LFA_ATTR_NAME] = __ATTR_RO(name),
>> + [LFA_ATTR_CURRENT_VERSION] = __ATTR_RO(current_version),
>> + [LFA_ATTR_PENDING_VERSION] = __ATTR_RO(pending_version),
>> + [LFA_ATTR_ACT_CAPABLE] = __ATTR_RO(activation_capable),
>> + [LFA_ATTR_ACT_PENDING] = __ATTR_RO(activation_pending),
>> + [LFA_ATTR_MAY_RESET_CPU] = __ATTR_RO(may_reset_cpu),
>> + [LFA_ATTR_CPU_RENDEZVOUS] = __ATTR_RO(cpu_rendezvous),
>> + [LFA_ATTR_FORCE_CPU_RENDEZVOUS] =
>> __ATTR_RW(force_cpu_rendezvous),
>> + [LFA_ATTR_ACTIVATE] = __ATTR_WO(activate),
>> + [LFA_ATTR_CANCEL] = __ATTR_WO(cancel)
>> +};
>> +
>> +static void clean_fw_images_tree(void)
>> +{
>> + struct image_props *attrs, *tmp;
>> +
>> + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node)
>> + delete_fw_image_node(attrs);
>> +}
>> +
>> +static int update_fw_image_node(char *fw_uuid, int seq_id,
>> + u32 image_flags, u64 reg_current_ver,
>> + u64 reg_pending_ver)
>> +{
>> + const char *image_name = "(unknown)";
>> + struct image_props *attrs;
>> + int ret;
>> +
>> + /*
>> + * If a fw_image is already in the images list then we just update
>> + * its flags and seq_id instead of trying to recreate it.
>> + */
>> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
>> + if (!strcmp(attrs->image_dir->name, fw_uuid)) {
>> + set_image_flags(attrs, seq_id, image_flags,
>> + reg_current_ver, reg_pending_ver);
>> + return 0;
>> + }
>> + }
>> +
>> + attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);
>> + if (!attrs)
>> + return -ENOMEM;
>> +
>> + for (int i = 0; i < ARRAY_SIZE(fw_images_uuids); i++) {
>> + if (!strcmp(fw_images_uuids[i].uuid, fw_uuid))
>> + image_name = fw_images_uuids[i].name;
>> + }
> I would recommend using fw_uuid as the image_name when UUID is not
> found in fw_images_uuids[], currently the driver assigns 'unknown'
> in such case.
> There is a valid possibility that platform-specific FW images, not
> listed in fw_images_uuids[], are used by LFA agent for live FW
> activation. In such scenarios, falling back to 'unknown' would lose
> important information especially when errors surface in
> call_lfa_activate(). Using UUID directly would indicate which
> image failed or behaved unexpectedly.
Well, I think if you want to identify an image clearly, you always have
to use the UUID, as shown by the directory name. The "name" sysfs file
is there just for convenience, to make this easier for *users* when
dealing with well-known firmware image. As you rightly said, we can
never guarantee that the kernel knows a certain UUID, and it wouldn't be
necessary for proper operation at all.
So I was expecting this name to be only used by reporting scripts or
such. But indeed some "unknown" string is a bit fragile as a placeholder
name, I was wondering if we should just provide an empty string in this
case? This would allow scripts to detect this special case reliably and
provide their own rendering then.
Does this make sense?
Cheers,
Andre
> Thank you,
> Veda
>> +
>> + attrs->image_dir = kobject_create_and_add(fw_uuid, lfa_dir);
>> + if (!attrs->image_dir)
>> + return -ENOMEM;
>> +
>> + INIT_LIST_HEAD(&attrs->image_node);
>> + attrs->image_name = image_name;
>> + attrs->cpu_rendezvous_forced = 1;
>> + set_image_flags(attrs, seq_id, image_flags, reg_current_ver,
>> + reg_pending_ver);
>> +
>> + /*
>> + * The attributes for each sysfs file are constant (handler
>> functions,
>> + * name and permissions are the same within each directory), but we
>> + * need a per-directory copy regardless, to get a unique handle
>> + * for each directory, so that container_of can do its magic.
>> + * Also this requires an explicit sysfs_attr_init(), since it's a
>> new
>> + * copy, to make LOCKDEP happy.
>> + */
>> + memcpy(attrs->image_attrs, image_attrs_group,
>> + sizeof(attrs->image_attrs));
>> + for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++) {
>> + struct attribute *attr = &attrs->image_attrs[i].attr;
>> +
>> + sysfs_attr_init(attr);
>> + ret = sysfs_create_file(attrs->image_dir, attr);
>> + if (ret) {
>> + pr_err("creating sysfs file for uuid %s: %d\n",
>> + fw_uuid, ret);
>> + clean_fw_images_tree();
>> +
>> + return ret;
>> + }
>> + }
>> + list_add(&attrs->image_node, &lfa_fw_images);
>> +
>> + return ret;
>> +}
>> +
>> +static int update_fw_images_tree(void)
>> +{
>> + struct arm_smccc_1_2_regs reg = { 0 };
>> + struct uuid_regs image_uuid;
>> + char image_id_str[40];
>> + int ret, num_of_components;
>> +
>> + num_of_components = get_nr_lfa_components();
>> + if (num_of_components <= 0) {
>> + pr_err("Error getting number of LFA components\n");
>> + return -ENODEV;
>> + }
>> +
>> + for (int i = 0; i < num_of_components; i++) {
>> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
>> + reg.a1 = i; /* fw_seq_id under consideration */
>> + arm_smccc_1_2_invoke(®, ®);
>> + if (reg.a0 == LFA_SUCCESS) {
>> + image_uuid.uuid_lo = reg.a1;
>> + image_uuid.uuid_hi = reg.a2;
>> +
>> + snprintf(image_id_str, sizeof(image_id_str), "%pUb",
>> + &image_uuid);
>> + ret = update_fw_image_node(image_id_str, i,
>> + reg.a3, reg.a4, reg.a5);
>> + if (ret)
>> + return ret;
>> + }
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int __init lfa_init(void)
>> +{
>> + struct arm_smccc_1_2_regs reg = { 0 };
>> + int err;
>> +
>> + reg.a0 = LFA_1_0_FN_GET_VERSION;
>> + arm_smccc_1_2_invoke(®, ®);
>> + if (reg.a0 == -LFA_NOT_SUPPORTED) {
>> + pr_info("Live Firmware activation: no firmware agent found\n");
>> + return -ENODEV;
>> + }
>> +
>> + fw_images_update_wq = alloc_workqueue("fw_images_update_wq",
>> + WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
>> + if (!fw_images_update_wq) {
>> + pr_err("Live Firmware Activation: Failed to allocate
>> workqueue.\n");
>> +
>> + return -ENOMEM;
>> + }
>> +
>> + pr_info("Live Firmware Activation: detected v%ld.%ld\n",
>> + reg.a0 >> 16, reg.a0 & 0xffff);
>> +
>> + lfa_dir = kobject_create_and_add("lfa", firmware_kobj);
>> + if (!lfa_dir)
>> + return -ENOMEM;
>> +
>> + mutex_lock(&lfa_lock);
>> + err = update_fw_images_tree();
>> + if (err != 0)
>> + kobject_put(lfa_dir);
>> +
>> + mutex_unlock(&lfa_lock);
>> + return err;
>> +}
>> +module_init(lfa_init);
>> +
>> +static void __exit lfa_exit(void)
>> +{
>> + flush_workqueue(fw_images_update_wq);
>> + destroy_workqueue(fw_images_update_wq);
>> +
>> + mutex_lock(&lfa_lock);
>> + clean_fw_images_tree();
>> + mutex_unlock(&lfa_lock);
>> +
>> + kobject_put(lfa_dir);
>> +}
>> +module_exit(lfa_exit);
>> +
>> +MODULE_DESCRIPTION("ARM Live Firmware Activation (LFA)");
>> +MODULE_LICENSE("GPL");
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-29 17:26 ` Andre Przywara
@ 2026-01-30 19:29 ` Vedashree Vidwans
2026-01-30 20:31 ` Andre Przywara
0 siblings, 1 reply; 18+ messages in thread
From: Vedashree Vidwans @ 2026-01-30 19:29 UTC (permalink / raw)
To: Andre Przywara, Salman Nabi, sudeep.holla, mark.rutland,
lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
Hi Andre,
On 1/29/26 09:26, Andre Przywara wrote:
> Hi Vedashree,
>
> many thanks for having a look!
>
> On 1/28/26 00:01, Vedashree Vidwans wrote:
>> Hello,
>>
>> On 1/19/26 04:27, Salman Nabi wrote:
>>> 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
>
> [ ... ]
>
>>> drivers/firmware/smccc/Kconfig | 8 +
>>> drivers/firmware/smccc/Makefile | 1 +
>>> drivers/firmware/smccc/lfa_fw.c | 668 ++++++++++++++++++++++++++++++++
>>> 3 files changed, 677 insertions(+)
>>> create mode 100644 drivers/firmware/smccc/lfa_fw.c
>>>
> [ ... ]
>>> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/
>>> smccc/ lfa_fw.c
>>> new file mode 100644
>>> index 000000000000..ce54049b7190
>>> --- /dev/null
>>> +++ b/drivers/firmware/smccc/lfa_fw.c
>>> @@ -0,0 +1,668 @@
>>> +// SPDX-License-Identifier: GPL-2.0-only
>>> +/*
>>> + * Copyright (C) 2025 Arm Limited
>>> + */
>
> [ ... ]
>
>>> +
>>> +static int call_lfa_activate(void *data)
>>> +{
>>> + struct image_props *attrs = data;
>>> + struct arm_smccc_1_2_regs reg = { 0 };
>>> +
>>> + reg.a0 = LFA_1_0_FN_ACTIVATE;
>>> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
>>> + /*
>>> + * As we do not support updates requiring a CPU reset (yet),
>>> + * we pass 0 in reg.a3 and reg.a4, holding the entry point and
>>> context
>>> + * ID respectively.
>>> + * cpu_rendezvous_forced is set by the administrator, via sysfs,
>>> + * cpu_rendezvous is dictated by each firmware component.
>>> + */
>>> + reg.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous);
>>> +
>>> + for (;;) {
>>> + arm_smccc_1_2_invoke(®, ®);
>>> +
>>> + if ((long)reg.a0 < 0) {
>>> + pr_err("ACTIVATE for image %s failed: %s\n",
>>> + attrs->image_name, lfa_error_strings[-reg.a0]);
>>> + return reg.a0;
>>> + }
>>> + if (!(reg.a1 & LFA_ACTIVATE_CALL_AGAIN))
>>> + break; /* ACTIVATE successful */
>>> + }
>> The implementation uses same 'struct arm_smccc_1_2_regs reg' as
>> input and output for arm_smccc_1_2_invoke(). Here, reg.a0 (function
>> ID), reg.a1 (fw_seq_id) and reg.a2 (cpu rendezvous) are initialized
>> once before the loop and arm_smccc_1_2_invoke() overwrites the whole
>> register set on every iteration. That means inputs (a0, a1, a2) can be
>> clobbered between loop iterations unless reassigned each time.
>> Suggestion: Re-initialize input members of reg on each loop iteration
>> or use a separate 'struct arm_smccc_1_2_regs' for output to avoid
>> input corruption.
>
> Ah, that's a good point and indeed a bug, thanks for spotting this! Will
> fix it.
>
>>> +
>>> + return reg.a0;
>>> +}
>>> +
>>> +static int activate_fw_image(struct image_props *attrs)
>>> +{
>>> + int ret;
>>> +
>>> + mutex_lock(&lfa_lock);
>>> + if (attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous)
>>> + ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask);
>>> + else
>>> + ret = call_lfa_activate(attrs);
>>> +
>>> + if (ret != 0) {
>>> + mutex_unlock(&lfa_lock);
>>> + return lfa_cancel(attrs);
>>> + }
>>> +
>>> + /*
>>> + * Invalidate fw_seq_ids (-1) for all images as the seq_ids and the
>>> + * number of firmware images in the LFA agent may change after a
>>> + * successful activation attempt. Negate all image flags as well.
>>> + */
>>> + attrs = NULL;
>>> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
>>> + set_image_flags(attrs, -1, 0b1000, 0, 0);
>>> + }
>>> +
>>> + update_fw_images_tree();
>>> +
>>> + /*
>>> + * Removing non-valid image directories at the end of an
>>> activation.
>>> + * We can't remove the sysfs attributes while in the respective
>>> + * _store() handler, so have to postpone the list removal to a
>>> + * workqueue.
>>> + */
>>> + INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
>>> + queue_work(fw_images_update_wq, &fw_images_update_work);
>>> + mutex_unlock(&lfa_lock);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int prime_fw_image(struct image_props *attrs)
>>> +{
>>> + struct arm_smccc_1_2_regs reg = { 0 };
>>> + int ret;
>>> +
>>> + mutex_lock(&lfa_lock);
>>> + /* Avoid SMC calls on invalid firmware images */
>>> + if (attrs->fw_seq_id == -1) {
>>> + pr_err("Arm LFA: Invalid firmware sequence id\n");
>>> + mutex_unlock(&lfa_lock);
>>> +
>>> + return -ENODEV;
>>> + }
>>> +
>>> + if (attrs->may_reset_cpu) {
>>> + pr_err("CPU reset not supported by kernel driver\n");
>>> + mutex_unlock(&lfa_lock);
>>> +
>>> + return -EINVAL;
>>> + }
>>> +
>>> + /*
>>> + * LFA_PRIME/ACTIVATE will return 1 in reg.a1 if the firmware
>>> + * priming/activation is still in progress. In that case
>>> + * LFA_PRIME/ACTIVATE will need to be called again.
>>> + * reg.a1 will become 0 once the prime/activate process completes.
>>> + */
>>> + reg.a0 = LFA_1_0_FN_PRIME;
>>> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
>>> + for (;;) {
>>> + arm_smccc_1_2_invoke(®, ®);
>>> +
>>> + if ((long)reg.a0 < 0) {
>>> + pr_err("LFA_PRIME for image %s failed: %s\n",
>>> + attrs->image_name, lfa_error_strings[-reg.a0]);
>>> + mutex_unlock(&lfa_lock);
>>> +
>>> + return reg.a0;
>>> + }
>>> + if (!(reg.a1 & LFA_PRIME_CALL_AGAIN)) {
>>> + ret = 0;
>>> + break; /* PRIME successful */
>>> + }
>>> + }
>> Similar comment to call_lfa_activate(). Suggestion to either re-assign
>> 'struct arm_smccc_1_2_regs' input values on each loop iteration or use
>> a separate 'struct arm_smccc_1_2_regs' for output to avoid input
>> corruption between loop iterations. This matches the intended
>> 'CALL_AGAIN' protocal while keeping the inputs stable across retries.
>
> Indeed, good catch.
>
>>> +
>>> + mutex_unlock(&lfa_lock);
>>> + return ret;
>> The introduction of separate 'ret' cariable does not appear necessary
>> for functional correctness. The SMCCC status is conveyed via reg.a0 on
>> each iteration, so returning reg.a0 should preserve existing behavior.
>> If 'ret' must be kept, consider initializing it to 0 at declaration
>> time. That avoids setting ret = 0 inside 'PRIME successful' path and
>> leads to simpler control flow.
>
> Right, looks like a leftover from a previous version, it's indeed not
> needed.
>
>>> +}
>>> +
>>> +static ssize_t name_show(struct kobject *kobj, struct kobj_attribute
>>> *attr,
>>> + char *buf)
>>> +{
>>> + struct image_props *attrs = container_of(attr, struct image_props,
>>> + image_attrs[LFA_ATTR_NAME]);
>>> +
>>> + return sysfs_emit(buf, "%s\n", attrs->image_name);
>>> +}
>>> +
>>> +static ssize_t activation_capable_show(struct kobject *kobj,
>>> + struct kobj_attribute *attr, char *buf)
>>> +{
>>> + struct image_props *attrs = container_of(attr, struct image_props,
>>> + image_attrs[LFA_ATTR_ACT_CAPABLE]);
>>> +
>>> + return sysfs_emit(buf, "%d\n", attrs->activation_capable);
>>> +}
>>> +
>>> +static ssize_t activation_pending_show(struct kobject *kobj,
>>> + struct kobj_attribute *attr, char *buf)
>>> +{
>>> + struct image_props *attrs = container_of(attr, struct image_props,
>>> + image_attrs[LFA_ATTR_ACT_PENDING]);
>>> + struct arm_smccc_1_2_regs reg = { 0 };
>>> +
>>> + /*
>>> + * Activation pending status can change anytime thus we need to
>>> update
>>> + * and return its current value
>>> + */
>>> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
>>> + reg.a1 = attrs->fw_seq_id;
>>> + arm_smccc_1_2_invoke(®, ®);
>>> + if (reg.a0 == LFA_SUCCESS)
>>> + attrs->activation_pending = !!(reg.a3 & BIT(1));
>>> +
>>> + return sysfs_emit(buf, "%d\n", attrs->activation_pending);
>>> +}
>>> +
>>> +static ssize_t may_reset_cpu_show(struct kobject *kobj,
>>> + struct kobj_attribute *attr, char *buf)
>>> +{
>>> + struct image_props *attrs = container_of(attr, struct image_props,
>>> + image_attrs[LFA_ATTR_MAY_RESET_CPU]);
>>> +
>>> + return sysfs_emit(buf, "%d\n", attrs->may_reset_cpu);
>>> +}
>>> +
>>> +static ssize_t cpu_rendezvous_show(struct kobject *kobj,
>>> + struct kobj_attribute *attr, char *buf)
>>> +{
>>> + struct image_props *attrs = container_of(attr, struct image_props,
>>> + image_attrs[LFA_ATTR_CPU_RENDEZVOUS]);
>>> +
>>> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous);
>>> +}
>>> +
>>> +static ssize_t force_cpu_rendezvous_store(struct kobject *kobj,
>>> + struct kobj_attribute *attr,
>>> + const char *buf, size_t count)
>>> +{
>>> + struct image_props *attrs = container_of(attr, struct image_props,
>>> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
>>> + int ret;
>>> +
>>> + ret = kstrtobool(buf, &attrs->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 image_props *attrs = container_of(attr, struct image_props,
>>> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
>>> +
>>> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous_forced);
>>> +}
>>> +
>>> +static ssize_t current_version_show(struct kobject *kobj,
>>> + struct kobj_attribute *attr, char *buf)
>>> +{
>>> + struct image_props *attrs = container_of(attr, struct image_props,
>>> + image_attrs[LFA_ATTR_CURRENT_VERSION]);
>>> + u32 maj, min;
>>> +
>>> + maj = attrs->current_version >> 32;
>>> + min = attrs->current_version & 0xffffffff;
>>> + return sysfs_emit(buf, "%u.%u\n", maj, min);
>>> +}
>>> +
>>> +static ssize_t pending_version_show(struct kobject *kobj,
>>> + struct kobj_attribute *attr, char *buf)
>>> +{
>>> + struct image_props *attrs = container_of(attr, struct image_props,
>>> + image_attrs[LFA_ATTR_ACT_PENDING]);
>>> + struct arm_smccc_1_2_regs reg = { 0 };
>>> + u32 maj, min;
>>> +
>>> + /*
>>> + * Similar to activation pending, this value can change
>>> following an
>>> + * update, we need to retrieve fresh info instead of stale
>>> information.
>>> + */
>>> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
>>> + reg.a1 = attrs->fw_seq_id;
>>> + arm_smccc_1_2_invoke(®, ®);
>>> + if (reg.a0 == LFA_SUCCESS) {
>>> + if (reg.a5 != 0 && attrs->activation_pending)
>>> + {
>>> + attrs->pending_version = reg.a5;
>>> + maj = reg.a5 >> 32;
>>> + min = reg.a5 & 0xffffffff;
>>> + }
>>> + }
>>> +
>>> + return sysfs_emit(buf, "%u.%u\n", maj, min);
>>> +}
>>> +
>>> +static ssize_t activate_store(struct kobject *kobj, struct
>>> kobj_attribute *attr,
>>> + const char *buf, size_t count)
>>> +{
>>> + struct image_props *attrs = container_of(attr, struct image_props,
>>> + image_attrs[LFA_ATTR_ACTIVATE]);
>>> + int ret;
>>> +
>>> + ret = prime_fw_image(attrs);
>>> + if (ret) {
>>> + pr_err("Firmware prime failed: %s\n",
>>> + lfa_error_strings[-ret]);
>>> + return -ECANCELED;
>>> + }
>>> +
>>> + ret = activate_fw_image(attrs);
>>> + if (ret) {
>>> + pr_err("Firmware activation failed: %s\n",
>>> + lfa_error_strings[-ret]);
>>> + return -ECANCELED;
>>> + }
>>> +
>>> + pr_info("Firmware activation succeeded\n");
>>> +
>>> + return count;
>>> +}
>>> +
>>> +static ssize_t cancel_store(struct kobject *kobj, struct
>>> kobj_attribute *attr,
>>> + const char *buf, size_t count)
>>> +{
>>> + struct image_props *attrs = container_of(attr, struct image_props,
>>> + image_attrs[LFA_ATTR_CANCEL]);
>>> + int ret;
>>> +
>>> + ret = lfa_cancel(attrs);
>>> + if (ret != 0)
>>> + return ret;
>>> +
>>> + return count;
>>> +}
>>> +
>>> +static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = {
>>> + [LFA_ATTR_NAME] = __ATTR_RO(name),
>>> + [LFA_ATTR_CURRENT_VERSION] = __ATTR_RO(current_version),
>>> + [LFA_ATTR_PENDING_VERSION] = __ATTR_RO(pending_version),
>>> + [LFA_ATTR_ACT_CAPABLE] = __ATTR_RO(activation_capable),
>>> + [LFA_ATTR_ACT_PENDING] = __ATTR_RO(activation_pending),
>>> + [LFA_ATTR_MAY_RESET_CPU] = __ATTR_RO(may_reset_cpu),
>>> + [LFA_ATTR_CPU_RENDEZVOUS] = __ATTR_RO(cpu_rendezvous),
>>> + [LFA_ATTR_FORCE_CPU_RENDEZVOUS] =
>>> __ATTR_RW(force_cpu_rendezvous),
>>> + [LFA_ATTR_ACTIVATE] = __ATTR_WO(activate),
>>> + [LFA_ATTR_CANCEL] = __ATTR_WO(cancel)
>>> +};
>>> +
>>> +static void clean_fw_images_tree(void)
>>> +{
>>> + struct image_props *attrs, *tmp;
>>> +
>>> + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node)
>>> + delete_fw_image_node(attrs);
>>> +}
>>> +
>>> +static int update_fw_image_node(char *fw_uuid, int seq_id,
>>> + u32 image_flags, u64 reg_current_ver,
>>> + u64 reg_pending_ver)
>>> +{
>>> + const char *image_name = "(unknown)";
>>> + struct image_props *attrs;
>>> + int ret;
>>> +
>>> + /*
>>> + * If a fw_image is already in the images list then we just update
>>> + * its flags and seq_id instead of trying to recreate it.
>>> + */
>>> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
>>> + if (!strcmp(attrs->image_dir->name, fw_uuid)) {
>>> + set_image_flags(attrs, seq_id, image_flags,
>>> + reg_current_ver, reg_pending_ver);
>>> + return 0;
>>> + }
>>> + }
>>> +
>>> + attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);
>>> + if (!attrs)
>>> + return -ENOMEM;
>>> +
>>> + for (int i = 0; i < ARRAY_SIZE(fw_images_uuids); i++) {
>>> + if (!strcmp(fw_images_uuids[i].uuid, fw_uuid))
>>> + image_name = fw_images_uuids[i].name;
>>> + }
>> I would recommend using fw_uuid as the image_name when UUID is not
>> found in fw_images_uuids[], currently the driver assigns 'unknown'
>> in such case.
>> There is a valid possibility that platform-specific FW images, not
>> listed in fw_images_uuids[], are used by LFA agent for live FW
>> activation. In such scenarios, falling back to 'unknown' would lose
>> important information especially when errors surface in
>> call_lfa_activate(). Using UUID directly would indicate which
>> image failed or behaved unexpectedly.
>
> Well, I think if you want to identify an image clearly, you always have
> to use the UUID, as shown by the directory name. The "name" sysfs file
> is there just for convenience, to make this easier for *users* when
> dealing with well-known firmware image. As you rightly said, we can
> never guarantee that the kernel knows a certain UUID, and it wouldn't be
> necessary for proper operation at all.
> So I was expecting this name to be only used by reporting scripts or
> such. But indeed some "unknown" string is a bit fragile as a placeholder
> name, I was wondering if we should just provide an empty string in this
> case? This would allow scripts to detect this special case reliably and
> provide their own rendering then.
> Does this make sense?
Thank you for teh clarification, this helps. I agree that UUID is the
canonical identifier and that 'name' sysfs file is mainly for user
convenience.
One concern I have is around error reporting. For example, if
call_lfa_activate() fails, the kernel prints:
ACTIVATE for image <name_string> failed: LFA_BUSY
If the 'name_string' is "unknown" or an empty string, the error message
doesn't indicate which firmware component failed activation, especially
on system with multiple LFA-capable FW images.
In such cases, having meaningful fallback identifier is helpful for
debugging. Using UUID in the error log would help identify which FW
image encountered the failure. This avoids ambiguity and makes error
triage much easier, especially when platform-specific firmware images
are not listed in fw_images_uuids[] but are still valid LFA components.
So eitehr of the following would solve the issue:
1. Use fw_uuid as the fallback image_name
2. Update error print in call_lfa_activate() and prime_fw_image() to use
UUID instead of image_name.
Best,
Veda
>
> Cheers,
> Andre
>
>> Thank you,
>> Veda
>>> +
>>> + attrs->image_dir = kobject_create_and_add(fw_uuid, lfa_dir);
>>> + if (!attrs->image_dir)
>>> + return -ENOMEM;
>>> +
>>> + INIT_LIST_HEAD(&attrs->image_node);
>>> + attrs->image_name = image_name;
>>> + attrs->cpu_rendezvous_forced = 1;
>>> + set_image_flags(attrs, seq_id, image_flags, reg_current_ver,
>>> + reg_pending_ver);
>>> +
>>> + /*
>>> + * The attributes for each sysfs file are constant (handler
>>> functions,
>>> + * name and permissions are the same within each directory), but we
>>> + * need a per-directory copy regardless, to get a unique handle
>>> + * for each directory, so that container_of can do its magic.
>>> + * Also this requires an explicit sysfs_attr_init(), since it's
>>> a new
>>> + * copy, to make LOCKDEP happy.
>>> + */
>>> + memcpy(attrs->image_attrs, image_attrs_group,
>>> + sizeof(attrs->image_attrs));
>>> + for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++) {
>>> + struct attribute *attr = &attrs->image_attrs[i].attr;
>>> +
>>> + sysfs_attr_init(attr);
>>> + ret = sysfs_create_file(attrs->image_dir, attr);
>>> + if (ret) {
>>> + pr_err("creating sysfs file for uuid %s: %d\n",
>>> + fw_uuid, ret);
>>> + clean_fw_images_tree();
>>> +
>>> + return ret;
>>> + }
>>> + }
>>> + list_add(&attrs->image_node, &lfa_fw_images);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int update_fw_images_tree(void)
>>> +{
>>> + struct arm_smccc_1_2_regs reg = { 0 };
>>> + struct uuid_regs image_uuid;
>>> + char image_id_str[40];
>>> + int ret, num_of_components;
>>> +
>>> + num_of_components = get_nr_lfa_components();
>>> + if (num_of_components <= 0) {
>>> + pr_err("Error getting number of LFA components\n");
>>> + return -ENODEV;
>>> + }
>>> +
>>> + for (int i = 0; i < num_of_components; i++) {
>>> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
>>> + reg.a1 = i; /* fw_seq_id under consideration */
>>> + arm_smccc_1_2_invoke(®, ®);
>>> + if (reg.a0 == LFA_SUCCESS) {
>>> + image_uuid.uuid_lo = reg.a1;
>>> + image_uuid.uuid_hi = reg.a2;
>>> +
>>> + snprintf(image_id_str, sizeof(image_id_str), "%pUb",
>>> + &image_uuid);
>>> + ret = update_fw_image_node(image_id_str, i,
>>> + reg.a3, reg.a4, reg.a5);
>>> + if (ret)
>>> + return ret;
>>> + }
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int __init lfa_init(void)
>>> +{
>>> + struct arm_smccc_1_2_regs reg = { 0 };
>>> + int err;
>>> +
>>> + reg.a0 = LFA_1_0_FN_GET_VERSION;
>>> + arm_smccc_1_2_invoke(®, ®);
>>> + if (reg.a0 == -LFA_NOT_SUPPORTED) {
>>> + pr_info("Live Firmware activation: no firmware agent found\n");
>>> + return -ENODEV;
>>> + }
>>> +
>>> + fw_images_update_wq = alloc_workqueue("fw_images_update_wq",
>>> + WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
>>> + if (!fw_images_update_wq) {
>>> + pr_err("Live Firmware Activation: Failed to allocate
>>> workqueue.\n");
>>> +
>>> + return -ENOMEM;
>>> + }
>>> +
>>> + pr_info("Live Firmware Activation: detected v%ld.%ld\n",
>>> + reg.a0 >> 16, reg.a0 & 0xffff);
>>> +
>>> + lfa_dir = kobject_create_and_add("lfa", firmware_kobj);
>>> + if (!lfa_dir)
>>> + return -ENOMEM;
>>> +
>>> + mutex_lock(&lfa_lock);
>>> + err = update_fw_images_tree();
>>> + if (err != 0)
>>> + kobject_put(lfa_dir);
>>> +
>>> + mutex_unlock(&lfa_lock);
>>> + return err;
>>> +}
>>> +module_init(lfa_init);
>>> +
>>> +static void __exit lfa_exit(void)
>>> +{
>>> + flush_workqueue(fw_images_update_wq);
>>> + destroy_workqueue(fw_images_update_wq);
>>> +
>>> + mutex_lock(&lfa_lock);
>>> + clean_fw_images_tree();
>>> + mutex_unlock(&lfa_lock);
>>> +
>>> + kobject_put(lfa_dir);
>>> +}
>>> +module_exit(lfa_exit);
>>> +
>>> +MODULE_DESCRIPTION("ARM Live Firmware Activation (LFA)");
>>> +MODULE_LICENSE("GPL");
>>
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-30 19:29 ` Vedashree Vidwans
@ 2026-01-30 20:31 ` Andre Przywara
0 siblings, 0 replies; 18+ messages in thread
From: Andre Przywara @ 2026-01-30 20:31 UTC (permalink / raw)
To: Vedashree Vidwans, Salman Nabi, sudeep.holla, mark.rutland,
lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
Hi,
On 1/30/26 20:29, Vedashree Vidwans wrote:
> Hi Andre,
>
> On 1/29/26 09:26, Andre Przywara wrote:
>> Hi Vedashree,
>>
>> many thanks for having a look!
>>
>> On 1/28/26 00:01, Vedashree Vidwans wrote:
>>> Hello,
>>>
>>> On 1/19/26 04:27, Salman Nabi wrote:
>>>> 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
>>
[ .... ]
>>>> +
>>>> + for (int i = 0; i < ARRAY_SIZE(fw_images_uuids); i++) {
>>>> + if (!strcmp(fw_images_uuids[i].uuid, fw_uuid))
>>>> + image_name = fw_images_uuids[i].name;
>>>> + }
>>> I would recommend using fw_uuid as the image_name when UUID is not
>>> found in fw_images_uuids[], currently the driver assigns 'unknown'
>>> in such case.
>>> There is a valid possibility that platform-specific FW images, not
>>> listed in fw_images_uuids[], are used by LFA agent for live FW
>>> activation. In such scenarios, falling back to 'unknown' would lose
>>> important information especially when errors surface in
>>> call_lfa_activate(). Using UUID directly would indicate which
>>> image failed or behaved unexpectedly.
>>
>> Well, I think if you want to identify an image clearly, you always
>> have to use the UUID, as shown by the directory name. The "name" sysfs
>> file is there just for convenience, to make this easier for *users*
>> when dealing with well-known firmware image. As you rightly said, we
>> can never guarantee that the kernel knows a certain UUID, and it
>> wouldn't be necessary for proper operation at all.
>> So I was expecting this name to be only used by reporting scripts or
>> such. But indeed some "unknown" string is a bit fragile as a
>> placeholder name, I was wondering if we should just provide an empty
>> string in this case? This would allow scripts to detect this special
>> case reliably and provide their own rendering then.
>> Does this make sense?
> Thank you for teh clarification, this helps. I agree that UUID is the
> canonical identifier and that 'name' sysfs file is mainly for user
> convenience.
> One concern I have is around error reporting. For example, if
> call_lfa_activate() fails, the kernel prints:
> ACTIVATE for image <name_string> failed: LFA_BUSY
>
> If the 'name_string' is "unknown" or an empty string, the error message
> doesn't indicate which firmware component failed activation, especially
> on system with multiple LFA-capable FW images.
Yes, that's a good point.
> In such cases, having meaningful fallback identifier is helpful for
> debugging. Using UUID in the error log would help identify which FW
> image encountered the failure. This avoids ambiguity and makes error
> triage much easier, especially when platform-specific firmware images
> are not listed in fw_images_uuids[] but are still valid LFA components.
> So eitehr of the following would solve the issue:
> 1. Use fw_uuid as the fallback image_name
> 2. Update error print in call_lfa_activate() and prime_fw_image() to use
> UUID instead of image_name.
I'd say number 2 is better. And if we leave the name char* as NULL or an
empty string, then this is also easy to detect in the code, and we can
print the UUID instead. If we do this more than once, then this could be
nicely hidden in a function.
Thanks,
Andre
> Best,
> Veda
>>
>> Cheers,
>> Andre
>>
>>> Thank you,
>>> Veda
>>>> +
>>>> + attrs->image_dir = kobject_create_and_add(fw_uuid, lfa_dir);
>>>> + if (!attrs->image_dir)
>>>> + return -ENOMEM;
>>>> +
>>>> + INIT_LIST_HEAD(&attrs->image_node);
>>>> + attrs->image_name = image_name;
>>>> + attrs->cpu_rendezvous_forced = 1;
>>>> + set_image_flags(attrs, seq_id, image_flags, reg_current_ver,
>>>> + reg_pending_ver);
>>>> +
>>>> + /*
>>>> + * The attributes for each sysfs file are constant (handler
>>>> functions,
>>>> + * name and permissions are the same within each directory),
>>>> but we
>>>> + * need a per-directory copy regardless, to get a unique handle
>>>> + * for each directory, so that container_of can do its magic.
>>>> + * Also this requires an explicit sysfs_attr_init(), since it's
>>>> a new
>>>> + * copy, to make LOCKDEP happy.
>>>> + */
>>>> + memcpy(attrs->image_attrs, image_attrs_group,
>>>> + sizeof(attrs->image_attrs));
>>>> + for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++) {
>>>> + struct attribute *attr = &attrs->image_attrs[i].attr;
>>>> +
>>>> + sysfs_attr_init(attr);
>>>> + ret = sysfs_create_file(attrs->image_dir, attr);
>>>> + if (ret) {
>>>> + pr_err("creating sysfs file for uuid %s: %d\n",
>>>> + fw_uuid, ret);
>>>> + clean_fw_images_tree();
>>>> +
>>>> + return ret;
>>>> + }
>>>> + }
>>>> + list_add(&attrs->image_node, &lfa_fw_images);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int update_fw_images_tree(void)
>>>> +{
>>>> + struct arm_smccc_1_2_regs reg = { 0 };
>>>> + struct uuid_regs image_uuid;
>>>> + char image_id_str[40];
>>>> + int ret, num_of_components;
>>>> +
>>>> + num_of_components = get_nr_lfa_components();
>>>> + if (num_of_components <= 0) {
>>>> + pr_err("Error getting number of LFA components\n");
>>>> + return -ENODEV;
>>>> + }
>>>> +
>>>> + for (int i = 0; i < num_of_components; i++) {
>>>> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
>>>> + reg.a1 = i; /* fw_seq_id under consideration */
>>>> + arm_smccc_1_2_invoke(®, ®);
>>>> + if (reg.a0 == LFA_SUCCESS) {
>>>> + image_uuid.uuid_lo = reg.a1;
>>>> + image_uuid.uuid_hi = reg.a2;
>>>> +
>>>> + snprintf(image_id_str, sizeof(image_id_str), "%pUb",
>>>> + &image_uuid);
>>>> + ret = update_fw_image_node(image_id_str, i,
>>>> + reg.a3, reg.a4, reg.a5);
>>>> + if (ret)
>>>> + return ret;
>>>> + }
>>>> + }
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int __init lfa_init(void)
>>>> +{
>>>> + struct arm_smccc_1_2_regs reg = { 0 };
>>>> + int err;
>>>> +
>>>> + reg.a0 = LFA_1_0_FN_GET_VERSION;
>>>> + arm_smccc_1_2_invoke(®, ®);
>>>> + if (reg.a0 == -LFA_NOT_SUPPORTED) {
>>>> + pr_info("Live Firmware activation: no firmware agent
>>>> found\n");
>>>> + return -ENODEV;
>>>> + }
>>>> +
>>>> + fw_images_update_wq = alloc_workqueue("fw_images_update_wq",
>>>> + WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
>>>> + if (!fw_images_update_wq) {
>>>> + pr_err("Live Firmware Activation: Failed to allocate
>>>> workqueue.\n");
>>>> +
>>>> + return -ENOMEM;
>>>> + }
>>>> +
>>>> + pr_info("Live Firmware Activation: detected v%ld.%ld\n",
>>>> + reg.a0 >> 16, reg.a0 & 0xffff);
>>>> +
>>>> + lfa_dir = kobject_create_and_add("lfa", firmware_kobj);
>>>> + if (!lfa_dir)
>>>> + return -ENOMEM;
>>>> +
>>>> + mutex_lock(&lfa_lock);
>>>> + err = update_fw_images_tree();
>>>> + if (err != 0)
>>>> + kobject_put(lfa_dir);
>>>> +
>>>> + mutex_unlock(&lfa_lock);
>>>> + return err;
>>>> +}
>>>> +module_init(lfa_init);
>>>> +
>>>> +static void __exit lfa_exit(void)
>>>> +{
>>>> + flush_workqueue(fw_images_update_wq);
>>>> + destroy_workqueue(fw_images_update_wq);
>>>> +
>>>> + mutex_lock(&lfa_lock);
>>>> + clean_fw_images_tree();
>>>> + mutex_unlock(&lfa_lock);
>>>> +
>>>> + kobject_put(lfa_dir);
>>>> +}
>>>> +module_exit(lfa_exit);
>>>> +
>>>> +MODULE_DESCRIPTION("ARM Live Firmware Activation (LFA)");
>>>> +MODULE_LICENSE("GPL");
>>>
>>
>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-19 12:27 ` [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA) Salman Nabi
` (3 preceding siblings ...)
2026-01-27 23:01 ` Vedashree Vidwans
@ 2026-01-31 1:35 ` Trilok Soni
2026-02-02 15:52 ` Salman Nabi
2026-02-10 22:20 ` Vedashree Vidwans
` (2 subsequent siblings)
7 siblings, 1 reply; 18+ messages in thread
From: Trilok Soni @ 2026-01-31 1:35 UTC (permalink / raw)
To: Salman Nabi, vvidwans, andre.przywara, sudeep.holla, mark.rutland,
lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
On 1/19/2026 4:27 AM, Salman Nabi wrote:
> The Arm Live Firmware Activation (LFA) is a specification [1] to describe
> activating firmware components without a reboot. Those components
> (like TF-A's BL31, EDK-II, TF-RMM, secure paylods) would be updated the
> usual way: via fwupd, FF-A or other secure storage methods, or via some
> IMPDEF Out-Of-Bound method. The user can then activate this new firmware,
> at system runtime, without requiring a reboot.
> The specification covers the SMCCC interface to list and query available
> components and eventually trigger the activation.
>
> Add a new directory under /sys/firmware to present firmware components
> capable of live activation. Each of them is a directory under lfa/,
> and is identified via its GUID. The activation will be triggered by echoing
> "1" into the "activate" file:
> ==========================================
> /sys/firmware/lfa # ls -l . 6c*
> .:
> total 0
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 47d4086d-4cfe-9846-9b95-2950cbbd5a00
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 6c0762a6-12f2-4b56-92cb-ba8f633606d9
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 d6d0eea7-fcea-d54b-9782-9934f234b6e4
Can you please explain or add a note on why we don't have name of the firmware
as the directory name and why you have selected GUID as top-level
directory name?
---Trilok Soni
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-31 1:35 ` Trilok Soni
@ 2026-02-02 15:52 ` Salman Nabi
2026-02-06 6:37 ` Trilok Soni
0 siblings, 1 reply; 18+ messages in thread
From: Salman Nabi @ 2026-02-02 15:52 UTC (permalink / raw)
To: Trilok Soni, vvidwans, andre.przywara, sudeep.holla, mark.rutland,
lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
Hi Trilok,
On 1/31/26 01:35, Trilok Soni wrote:
> On 1/19/2026 4:27 AM, Salman Nabi wrote:
>> The Arm Live Firmware Activation (LFA) is a specification [1] to describe
>> activating firmware components without a reboot. Those components
>> (like TF-A's BL31, EDK-II, TF-RMM, secure paylods) would be updated the
>> usual way: via fwupd, FF-A or other secure storage methods, or via some
>> IMPDEF Out-Of-Bound method. The user can then activate this new firmware,
>> at system runtime, without requiring a reboot.
>> The specification covers the SMCCC interface to list and query available
>> components and eventually trigger the activation.
>>
>> Add a new directory under /sys/firmware to present firmware components
>> capable of live activation. Each of them is a directory under lfa/,
>> and is identified via its GUID. The activation will be triggered by echoing
>> "1" into the "activate" file:
>> ==========================================
>> /sys/firmware/lfa # ls -l . 6c*
>> .:
>> total 0
>> drwxr-xr-x 2 0 0 0 Jan 19 11:33 47d4086d-4cfe-9846-9b95-2950cbbd5a00
>> drwxr-xr-x 2 0 0 0 Jan 19 11:33 6c0762a6-12f2-4b56-92cb-ba8f633606d9
>> drwxr-xr-x 2 0 0 0 Jan 19 11:33 d6d0eea7-fcea-d54b-9782-9934f234b6e4
> Can you please explain or add a note on why we don't have name of the firmware
> as the directory name and why you have selected GUID as top-level
> directory name?
We obtain the GUIDs of firmware components from the LFA agent in TF-A, which does not provide their names. For convenience, we have added a C structure in the driver to associate each firmware GUID with its corresponding name. Because new firmware components may be supported in the LFA agent before their GUID-to-name mapping is added to the driver, we avoid using the firmware name as the directory (kobject) name.
Additionally, sysfs requires directory names to be unique among siblings. Since GUIDs are inherently unique, they provide a convenient, collision-free choice for directory names.
>
> ---Trilok Soni
Many thanks,
Salman
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-02-02 15:52 ` Salman Nabi
@ 2026-02-06 6:37 ` Trilok Soni
0 siblings, 0 replies; 18+ messages in thread
From: Trilok Soni @ 2026-02-06 6:37 UTC (permalink / raw)
To: Salman Nabi, vvidwans, andre.przywara, sudeep.holla, mark.rutland,
lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
On 2/2/2026 7:52 AM, Salman Nabi wrote:
> Hi Trilok,
>
> On 1/31/26 01:35, Trilok Soni wrote:
>> On 1/19/2026 4:27 AM, Salman Nabi wrote:
>>> 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
s/paylods/payloads
>>> usual way: via fwupd, FF-A or other secure storage methods, or via some
>>> IMPDEF Out-Of-Bound method. The user can then activate this new firmware,
>>> at system runtime, without requiring a reboot.
>>> The specification covers the SMCCC interface to list and query available
>>> components and eventually trigger the activation.
>>>
>>> Add a new directory under /sys/firmware to present firmware components
>>> capable of live activation. Each of them is a directory under lfa/,
>>> and is identified via its GUID. The activation will be triggered by echoing
>>> "1" into the "activate" file:
>>> ==========================================
>>> /sys/firmware/lfa # ls -l . 6c*
>>> .:
>>> total 0
>>> drwxr-xr-x 2 0 0 0 Jan 19 11:33 47d4086d-4cfe-9846-9b95-2950cbbd5a00
>>> drwxr-xr-x 2 0 0 0 Jan 19 11:33 6c0762a6-12f2-4b56-92cb-ba8f633606d9
>>> drwxr-xr-x 2 0 0 0 Jan 19 11:33 d6d0eea7-fcea-d54b-9782-9934f234b6e4
>> Can you please explain or add a note on why we don't have name of the firmware
>> as the directory name and why you have selected GUID as top-level
>> directory name?
>
>
> We obtain the GUIDs of firmware components from the LFA agent in TF-A, which does not provide their names. For convenience, we have added a C structure in the driver to associate each firmware GUID with its corresponding name. Because new firmware components may be supported in the LFA agent before their GUID-to-name mapping is added to the driver, we avoid using the firmware name as the directory (kobject) name.
>
> Additionally, sysfs requires directory names to be unique among siblings. Since GUIDs are inherently unique, they provide a convenient, collision-free choice for directory names.
Thank you for the explanation. Please add these details in the commit text.
---Trilok Soni
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-19 12:27 ` [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA) Salman Nabi
` (4 preceding siblings ...)
2026-01-31 1:35 ` Trilok Soni
@ 2026-02-10 22:20 ` Vedashree Vidwans
2026-02-20 17:39 ` Andre Przywara
2026-03-13 9:46 ` Nirmoy Das
7 siblings, 0 replies; 18+ messages in thread
From: Vedashree Vidwans @ 2026-02-10 22:20 UTC (permalink / raw)
To: Salman Nabi, andre.przywara, sudeep.holla, mark.rutland,
lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
Hello,
I have tested this change on Nvidia server platform using Linux kernel
6.16. My testing was done on the final integrated version, which
includes a platform driver interface built on top of this patch. I will
be posting the platform driver patch to LKML separately.
The final driver has been validated on target platform, except for the
sysfs interface which was not excercised during testing.
Tested-by: Vedashree Vidwans <vvidwans@nvidia.com>
Best,
Veda
On 1/19/26 04:27, Salman Nabi wrote:
> The Arm Live Firmware Activation (LFA) is a specification [1] to describe
> activating firmware components without a reboot. Those components
> (like TF-A's BL31, EDK-II, TF-RMM, secure paylods) would be updated the
> usual way: via fwupd, FF-A or other secure storage methods, or via some
> IMPDEF Out-Of-Bound method. The user can then activate this new firmware,
> at system runtime, without requiring a reboot.
> The specification covers the SMCCC interface to list and query available
> components and eventually trigger the activation.
>
> Add a new directory under /sys/firmware to present firmware components
> capable of live activation. Each of them is a directory under lfa/,
> and is identified via its GUID. The activation will be triggered by echoing
> "1" into the "activate" file:
> ==========================================
> /sys/firmware/lfa # ls -l . 6c*
> .:
> total 0
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 47d4086d-4cfe-9846-9b95-2950cbbd5a00
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 6c0762a6-12f2-4b56-92cb-ba8f633606d9
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 d6d0eea7-fcea-d54b-9782-9934f234b6e4
>
> 6c0762a6-12f2-4b56-92cb-ba8f633606d9:
> total 0
> --w------- 1 0 0 4096 Jan 19 11:33 activate
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_capable
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_pending
> --w------- 1 0 0 4096 Jan 19 11:33 cancel
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 cpu_rendezvous
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 current_version
> -rw-r--r-- 1 0 0 4096 Jan 19 11:33 force_cpu_rendezvous
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 may_reset_cpu
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 name
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 pending_version
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # grep . *
> grep: activate: Permission denied
> activation_capable:1
> activation_pending:1
> grep: cancel: Permission denied
> cpu_rendezvous:1
> current_version:0.0
> force_cpu_rendezvous:1
> may_reset_cpu:0
> name:TF-RMM
> pending_version:0.0
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # echo 1 > activate
> [ 2825.797871] Arm LFA: firmware activation succeeded.
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 #
> ==========================================
>
> [1] https://developer.arm.com/documentation/den0147/latest/
>
> Signed-off-by: Salman Nabi <salman.nabi@arm.com>
> ---
> drivers/firmware/smccc/Kconfig | 8 +
> drivers/firmware/smccc/Makefile | 1 +
> drivers/firmware/smccc/lfa_fw.c | 668 ++++++++++++++++++++++++++++++++
> 3 files changed, 677 insertions(+)
> create mode 100644 drivers/firmware/smccc/lfa_fw.c
>
> diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig
> index 15e7466179a6..ff7ca49486b0 100644
> --- a/drivers/firmware/smccc/Kconfig
> +++ b/drivers/firmware/smccc/Kconfig
> @@ -23,3 +23,11 @@ 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
> + default y
> + help
> + Include support for triggering Live Firmware Activation, which
> + allows to upgrade certain firmware components without a reboot.
> diff --git a/drivers/firmware/smccc/Makefile b/drivers/firmware/smccc/Makefile
> index 40d19144a860..a6dd01558a94 100644
> --- a/drivers/firmware/smccc/Makefile
> +++ b/drivers/firmware/smccc/Makefile
> @@ -2,3 +2,4 @@
> #
> obj-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += smccc.o kvm_guest.o
> obj-$(CONFIG_ARM_SMCCC_SOC_ID) += soc_id.o
> +obj-$(CONFIG_ARM_LFA) += lfa_fw.o
> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
> new file mode 100644
> index 000000000000..ce54049b7190
> --- /dev/null
> +++ b/drivers/firmware/smccc/lfa_fw.c
> @@ -0,0 +1,668 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Arm Limited
> + */
> +
> +#include <linux/fs.h>
> +#include <linux/init.h>
> +#include <linux/kobject.h>
> +#include <linux/module.h>
> +#include <linux/stop_machine.h>
> +#include <linux/string.h>
> +#include <linux/sysfs.h>
> +#include <linux/arm-smccc.h>
> +#include <linux/psci.h>
> +#include <uapi/linux/psci.h>
> +#include <linux/uuid.h>
> +#include <linux/array_size.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +
> +#undef pr_fmt
> +#define pr_fmt(fmt) "Arm LFA: " fmt
> +
> +/* LFA v1.0b0 specification */
> +#define LFA_1_0_FN_BASE 0xc40002e0
> +#define LFA_1_0_FN(n) (LFA_1_0_FN_BASE + (n))
> +
> +#define LFA_1_0_FN_GET_VERSION LFA_1_0_FN(0)
> +#define LFA_1_0_FN_CHECK_FEATURE LFA_1_0_FN(1)
> +#define LFA_1_0_FN_GET_INFO LFA_1_0_FN(2)
> +#define LFA_1_0_FN_GET_INVENTORY LFA_1_0_FN(3)
> +#define LFA_1_0_FN_PRIME LFA_1_0_FN(4)
> +#define LFA_1_0_FN_ACTIVATE LFA_1_0_FN(5)
> +#define LFA_1_0_FN_CANCEL LFA_1_0_FN(6)
> +
> +/* CALL_AGAIN flags (returned by SMC) */
> +#define LFA_PRIME_CALL_AGAIN BIT(0)
> +#define LFA_ACTIVATE_CALL_AGAIN BIT(0)
> +
> +/* LFA return values */
> +#define LFA_SUCCESS 0
> +#define LFA_NOT_SUPPORTED 1
> +#define LFA_BUSY 2
> +#define LFA_AUTH_ERROR 3
> +#define LFA_NO_MEMORY 4
> +#define LFA_CRITICAL_ERROR 5
> +#define LFA_DEVICE_ERROR 6
> +#define LFA_WRONG_STATE 7
> +#define LFA_INVALID_PARAMETERS 8
> +#define LFA_COMPONENT_WRONG_STATE 9
> +#define LFA_INVALID_ADDRESS 10
> +#define LFA_ACTIVATION_FAILED 11
> +
> +#define LFA_ERROR_STRING(name) \
> + [name] = #name
> +
> +static const char * const lfa_error_strings[] = {
> + LFA_ERROR_STRING(LFA_SUCCESS),
> + LFA_ERROR_STRING(LFA_NOT_SUPPORTED),
> + LFA_ERROR_STRING(LFA_BUSY),
> + LFA_ERROR_STRING(LFA_AUTH_ERROR),
> + LFA_ERROR_STRING(LFA_NO_MEMORY),
> + LFA_ERROR_STRING(LFA_CRITICAL_ERROR),
> + LFA_ERROR_STRING(LFA_DEVICE_ERROR),
> + LFA_ERROR_STRING(LFA_WRONG_STATE),
> + LFA_ERROR_STRING(LFA_INVALID_PARAMETERS),
> + LFA_ERROR_STRING(LFA_COMPONENT_WRONG_STATE),
> + LFA_ERROR_STRING(LFA_INVALID_ADDRESS),
> + LFA_ERROR_STRING(LFA_ACTIVATION_FAILED)
> +};
> +
> +enum image_attr_names {
> + LFA_ATTR_NAME,
> + LFA_ATTR_CURRENT_VERSION,
> + LFA_ATTR_PENDING_VERSION,
> + LFA_ATTR_ACT_CAPABLE,
> + LFA_ATTR_ACT_PENDING,
> + LFA_ATTR_MAY_RESET_CPU,
> + LFA_ATTR_CPU_RENDEZVOUS,
> + LFA_ATTR_FORCE_CPU_RENDEZVOUS,
> + LFA_ATTR_ACTIVATE,
> + LFA_ATTR_CANCEL,
> + LFA_ATTR_NR_IMAGES
> +};
> +
> +struct image_props {
> + struct list_head image_node;
> + const char *image_name;
> + int fw_seq_id;
> + u64 current_version;
> + u64 pending_version;
> + bool activation_capable;
> + bool activation_pending;
> + bool may_reset_cpu;
> + bool cpu_rendezvous;
> + bool cpu_rendezvous_forced;
> + struct kobject *image_dir;
> + struct kobj_attribute image_attrs[LFA_ATTR_NR_IMAGES];
> +};
> +static LIST_HEAD(lfa_fw_images);
> +
> +/* A UUID split over two 64-bit registers */
> +struct uuid_regs {
> + u64 uuid_lo;
> + u64 uuid_hi;
> +};
> +
> +static const struct fw_image_uuid {
> + const char *name;
> + const char *uuid;
> +} fw_images_uuids[] = {
> + {
> + .name = "TF-A BL31 runtime",
> + .uuid = "47d4086d-4cfe-9846-9b95-2950cbbd5a00",
> + },
> + {
> + .name = "BL33 non-secure payload",
> + .uuid = "d6d0eea7-fcea-d54b-9782-9934f234b6e4",
> + },
> + {
> + .name = "TF-RMM",
> + .uuid = "6c0762a6-12f2-4b56-92cb-ba8f633606d9",
> + },
> +};
> +
> +static struct kobject *lfa_dir;
> +static DEFINE_MUTEX(lfa_lock);
> +static struct workqueue_struct *fw_images_update_wq;
> +static struct work_struct fw_images_update_work;
> +
> +static int update_fw_images_tree(void);
> +
> +static void delete_fw_image_node(struct image_props *attrs)
> +{
> + int i;
> +
> + for (i = 0; i < LFA_ATTR_NR_IMAGES; i++)
> + sysfs_remove_file(attrs->image_dir, &attrs->image_attrs[i].attr);
> +
> + kobject_put(attrs->image_dir);
> + list_del(&attrs->image_node);
> + kfree(attrs);
> +}
> +
> +static void remove_invalid_fw_images(struct work_struct *work)
> +{
> + struct image_props *attrs, *tmp;
> +
> + mutex_lock(&lfa_lock);
> +
> + /*
> + * Remove firmware images including directories that are no longer
> + * present in the LFA agent after updating the existing ones.
> + */
> + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node) {
> + if (attrs->fw_seq_id == -1)
> + delete_fw_image_node(attrs);
> + }
> +
> + mutex_unlock(&lfa_lock);
> +}
> +
> +static void set_image_flags(struct image_props *attrs, int seq_id,
> + u32 image_flags, u64 reg_current_ver,
> + u64 reg_pending_ver)
> +{
> + attrs->fw_seq_id = seq_id;
> + attrs->current_version = reg_current_ver;
> + attrs->pending_version = reg_pending_ver;
> + attrs->activation_capable = !!(image_flags & BIT(0));
> + attrs->activation_pending = !!(image_flags & BIT(1));
> + attrs->may_reset_cpu = !!(image_flags & BIT(2));
> + /* cpu_rendezvous_optional bit has inverse logic in the spec */
> + attrs->cpu_rendezvous = !(image_flags & BIT(3));
> +}
> +
> +static unsigned long get_nr_lfa_components(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_GET_INFO;
> + reg.a1 = 0; /* lfa_info_selector = 0 */
> +
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 != LFA_SUCCESS)
> + return reg.a0;
> +
> + return reg.a1;
> +}
> +
> +static int lfa_cancel(void *data)
> +{
> + struct image_props *attrs = data;
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_CANCEL;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> +
> + /*
> + * When firmware activation is called with "skip_cpu_rendezvous=1",
> + * LFA_CANCEL can fail with LFA_BUSY if the activation could not be
> + * cancelled.
> + */
> + if (reg.a0 == LFA_SUCCESS) {
> + pr_info("Activation cancelled for image %s\n",
> + attrs->image_name);
> + } else {
> + pr_err("Firmware activation could not be cancelled: %s\n",
> + lfa_error_strings[-reg.a0]);
> + return -EINVAL;
> + }
> +
> + return reg.a0;
> +}
> +
> +static int call_lfa_activate(void *data)
> +{
> + struct image_props *attrs = data;
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_ACTIVATE;
> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
> + /*
> + * As we do not support updates requiring a CPU reset (yet),
> + * we pass 0 in reg.a3 and reg.a4, holding the entry point and context
> + * ID respectively.
> + * cpu_rendezvous_forced is set by the administrator, via sysfs,
> + * cpu_rendezvous is dictated by each firmware component.
> + */
> + reg.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous);
> +
> + for (;;) {
> + arm_smccc_1_2_invoke(®, ®);
> +
> + if ((long)reg.a0 < 0) {
> + pr_err("ACTIVATE for image %s failed: %s\n",
> + attrs->image_name, lfa_error_strings[-reg.a0]);
> + return reg.a0;
> + }
> + if (!(reg.a1 & LFA_ACTIVATE_CALL_AGAIN))
> + break; /* ACTIVATE successful */
> + }
> +
> + return reg.a0;
> +}
> +
> +static int activate_fw_image(struct image_props *attrs)
> +{
> + int ret;
> +
> + mutex_lock(&lfa_lock);
> + if (attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous)
> + ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask);
> + else
> + ret = call_lfa_activate(attrs);
> +
> + if (ret != 0) {
> + mutex_unlock(&lfa_lock);
> + return lfa_cancel(attrs);
> + }
> +
> + /*
> + * Invalidate fw_seq_ids (-1) for all images as the seq_ids and the
> + * number of firmware images in the LFA agent may change after a
> + * successful activation attempt. Negate all image flags as well.
> + */
> + attrs = NULL;
> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
> + set_image_flags(attrs, -1, 0b1000, 0, 0);
> + }
> +
> + update_fw_images_tree();
> +
> + /*
> + * Removing non-valid image directories at the end of an activation.
> + * We can't remove the sysfs attributes while in the respective
> + * _store() handler, so have to postpone the list removal to a
> + * workqueue.
> + */
> + INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
> + queue_work(fw_images_update_wq, &fw_images_update_work);
> + mutex_unlock(&lfa_lock);
> +
> + return ret;
> +}
> +
> +static int prime_fw_image(struct image_props *attrs)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + int ret;
> +
> + mutex_lock(&lfa_lock);
> + /* Avoid SMC calls on invalid firmware images */
> + if (attrs->fw_seq_id == -1) {
> + pr_err("Arm LFA: Invalid firmware sequence id\n");
> + mutex_unlock(&lfa_lock);
> +
> + return -ENODEV;
> + }
> +
> + if (attrs->may_reset_cpu) {
> + pr_err("CPU reset not supported by kernel driver\n");
> + mutex_unlock(&lfa_lock);
> +
> + return -EINVAL;
> + }
> +
> + /*
> + * LFA_PRIME/ACTIVATE will return 1 in reg.a1 if the firmware
> + * priming/activation is still in progress. In that case
> + * LFA_PRIME/ACTIVATE will need to be called again.
> + * reg.a1 will become 0 once the prime/activate process completes.
> + */
> + reg.a0 = LFA_1_0_FN_PRIME;
> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
> + for (;;) {
> + arm_smccc_1_2_invoke(®, ®);
> +
> + if ((long)reg.a0 < 0) {
> + pr_err("LFA_PRIME for image %s failed: %s\n",
> + attrs->image_name, lfa_error_strings[-reg.a0]);
> + mutex_unlock(&lfa_lock);
> +
> + return reg.a0;
> + }
> + if (!(reg.a1 & LFA_PRIME_CALL_AGAIN)) {
> + ret = 0;
> + break; /* PRIME successful */
> + }
> + }
> +
> + mutex_unlock(&lfa_lock);
> + return ret;
> +}
> +
> +static ssize_t name_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_NAME]);
> +
> + return sysfs_emit(buf, "%s\n", attrs->image_name);
> +}
> +
> +static ssize_t activation_capable_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_CAPABLE]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->activation_capable);
> +}
> +
> +static ssize_t activation_pending_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_PENDING]);
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + /*
> + * Activation pending status can change anytime thus we need to update
> + * and return its current value
> + */
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS)
> + attrs->activation_pending = !!(reg.a3 & BIT(1));
> +
> + return sysfs_emit(buf, "%d\n", attrs->activation_pending);
> +}
> +
> +static ssize_t may_reset_cpu_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_MAY_RESET_CPU]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->may_reset_cpu);
> +}
> +
> +static ssize_t cpu_rendezvous_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CPU_RENDEZVOUS]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous);
> +}
> +
> +static ssize_t force_cpu_rendezvous_store(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
> + int ret;
> +
> + ret = kstrtobool(buf, &attrs->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 image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous_forced);
> +}
> +
> +static ssize_t current_version_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CURRENT_VERSION]);
> + u32 maj, min;
> +
> + maj = attrs->current_version >> 32;
> + min = attrs->current_version & 0xffffffff;
> + return sysfs_emit(buf, "%u.%u\n", maj, min);
> +}
> +
> +static ssize_t pending_version_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_PENDING]);
> + struct arm_smccc_1_2_regs reg = { 0 };
> + u32 maj, min;
> +
> + /*
> + * Similar to activation pending, this value can change following an
> + * update, we need to retrieve fresh info instead of stale information.
> + */
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS) {
> + if (reg.a5 != 0 && attrs->activation_pending)
> + {
> + attrs->pending_version = reg.a5;
> + maj = reg.a5 >> 32;
> + min = reg.a5 & 0xffffffff;
> + }
> + }
> +
> + return sysfs_emit(buf, "%u.%u\n", maj, min);
> +}
> +
> +static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACTIVATE]);
> + int ret;
> +
> + ret = prime_fw_image(attrs);
> + if (ret) {
> + pr_err("Firmware prime failed: %s\n",
> + lfa_error_strings[-ret]);
> + return -ECANCELED;
> + }
> +
> + ret = activate_fw_image(attrs);
> + if (ret) {
> + pr_err("Firmware activation failed: %s\n",
> + lfa_error_strings[-ret]);
> + return -ECANCELED;
> + }
> +
> + pr_info("Firmware activation succeeded\n");
> +
> + return count;
> +}
> +
> +static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CANCEL]);
> + int ret;
> +
> + ret = lfa_cancel(attrs);
> + if (ret != 0)
> + return ret;
> +
> + return count;
> +}
> +
> +static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = {
> + [LFA_ATTR_NAME] = __ATTR_RO(name),
> + [LFA_ATTR_CURRENT_VERSION] = __ATTR_RO(current_version),
> + [LFA_ATTR_PENDING_VERSION] = __ATTR_RO(pending_version),
> + [LFA_ATTR_ACT_CAPABLE] = __ATTR_RO(activation_capable),
> + [LFA_ATTR_ACT_PENDING] = __ATTR_RO(activation_pending),
> + [LFA_ATTR_MAY_RESET_CPU] = __ATTR_RO(may_reset_cpu),
> + [LFA_ATTR_CPU_RENDEZVOUS] = __ATTR_RO(cpu_rendezvous),
> + [LFA_ATTR_FORCE_CPU_RENDEZVOUS] = __ATTR_RW(force_cpu_rendezvous),
> + [LFA_ATTR_ACTIVATE] = __ATTR_WO(activate),
> + [LFA_ATTR_CANCEL] = __ATTR_WO(cancel)
> +};
> +
> +static void clean_fw_images_tree(void)
> +{
> + struct image_props *attrs, *tmp;
> +
> + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node)
> + delete_fw_image_node(attrs);
> +}
> +
> +static int update_fw_image_node(char *fw_uuid, int seq_id,
> + u32 image_flags, u64 reg_current_ver,
> + u64 reg_pending_ver)
> +{
> + const char *image_name = "(unknown)";
> + struct image_props *attrs;
> + int ret;
> +
> + /*
> + * If a fw_image is already in the images list then we just update
> + * its flags and seq_id instead of trying to recreate it.
> + */
> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
> + if (!strcmp(attrs->image_dir->name, fw_uuid)) {
> + set_image_flags(attrs, seq_id, image_flags,
> + reg_current_ver, reg_pending_ver);
> + return 0;
> + }
> + }
> +
> + attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);
> + if (!attrs)
> + return -ENOMEM;
> +
> + for (int i = 0; i < ARRAY_SIZE(fw_images_uuids); i++) {
> + if (!strcmp(fw_images_uuids[i].uuid, fw_uuid))
> + image_name = fw_images_uuids[i].name;
> + }
> +
> + attrs->image_dir = kobject_create_and_add(fw_uuid, lfa_dir);
> + if (!attrs->image_dir)
> + return -ENOMEM;
> +
> + INIT_LIST_HEAD(&attrs->image_node);
> + attrs->image_name = image_name;
> + attrs->cpu_rendezvous_forced = 1;
> + set_image_flags(attrs, seq_id, image_flags, reg_current_ver,
> + reg_pending_ver);
> +
> + /*
> + * The attributes for each sysfs file are constant (handler functions,
> + * name and permissions are the same within each directory), but we
> + * need a per-directory copy regardless, to get a unique handle
> + * for each directory, so that container_of can do its magic.
> + * Also this requires an explicit sysfs_attr_init(), since it's a new
> + * copy, to make LOCKDEP happy.
> + */
> + memcpy(attrs->image_attrs, image_attrs_group,
> + sizeof(attrs->image_attrs));
> + for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++) {
> + struct attribute *attr = &attrs->image_attrs[i].attr;
> +
> + sysfs_attr_init(attr);
> + ret = sysfs_create_file(attrs->image_dir, attr);
> + if (ret) {
> + pr_err("creating sysfs file for uuid %s: %d\n",
> + fw_uuid, ret);
> + clean_fw_images_tree();
> +
> + return ret;
> + }
> + }
> + list_add(&attrs->image_node, &lfa_fw_images);
> +
> + return ret;
> +}
> +
> +static int update_fw_images_tree(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + struct uuid_regs image_uuid;
> + char image_id_str[40];
> + int ret, num_of_components;
> +
> + num_of_components = get_nr_lfa_components();
> + if (num_of_components <= 0) {
> + pr_err("Error getting number of LFA components\n");
> + return -ENODEV;
> + }
> +
> + for (int i = 0; i < num_of_components; i++) {
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = i; /* fw_seq_id under consideration */
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS) {
> + image_uuid.uuid_lo = reg.a1;
> + image_uuid.uuid_hi = reg.a2;
> +
> + snprintf(image_id_str, sizeof(image_id_str), "%pUb",
> + &image_uuid);
> + ret = update_fw_image_node(image_id_str, i,
> + reg.a3, reg.a4, reg.a5);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int __init lfa_init(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + int err;
> +
> + reg.a0 = LFA_1_0_FN_GET_VERSION;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == -LFA_NOT_SUPPORTED) {
> + pr_info("Live Firmware activation: no firmware agent found\n");
> + return -ENODEV;
> + }
> +
> + fw_images_update_wq = alloc_workqueue("fw_images_update_wq",
> + WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
> + if (!fw_images_update_wq) {
> + pr_err("Live Firmware Activation: Failed to allocate workqueue.\n");
> +
> + return -ENOMEM;
> + }
> +
> + pr_info("Live Firmware Activation: detected v%ld.%ld\n",
> + reg.a0 >> 16, reg.a0 & 0xffff);
> +
> + lfa_dir = kobject_create_and_add("lfa", firmware_kobj);
> + if (!lfa_dir)
> + return -ENOMEM;
> +
> + mutex_lock(&lfa_lock);
> + err = update_fw_images_tree();
> + if (err != 0)
> + kobject_put(lfa_dir);
> +
> + mutex_unlock(&lfa_lock);
> + return err;
> +}
> +module_init(lfa_init);
> +
> +static void __exit lfa_exit(void)
> +{
> + flush_workqueue(fw_images_update_wq);
> + destroy_workqueue(fw_images_update_wq);
> +
> + mutex_lock(&lfa_lock);
> + clean_fw_images_tree();
> + mutex_unlock(&lfa_lock);
> +
> + kobject_put(lfa_dir);
> +}
> +module_exit(lfa_exit);
> +
> +MODULE_DESCRIPTION("ARM Live Firmware Activation (LFA)");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-19 12:27 ` [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA) Salman Nabi
` (5 preceding siblings ...)
2026-02-10 22:20 ` Vedashree Vidwans
@ 2026-02-20 17:39 ` Andre Przywara
2026-02-20 21:48 ` Trilok Soni
2026-03-13 9:46 ` Nirmoy Das
7 siblings, 1 reply; 18+ messages in thread
From: Andre Przywara @ 2026-02-20 17:39 UTC (permalink / raw)
To: Salman Nabi
Cc: vvidwans, sudeep.holla, mark.rutland, lpieralisi, ardb, chao.gao,
linux-arm-kernel, linux-coco, linux-kernel, sdonthineni, vsethi,
vwadekar
On Mon, 19 Jan 2026 12:27:29 +0000
Salman Nabi <salman.nabi@arm.com> wrote:
Hey,
for the records: while working on improving the patch and during internal
review, we found some bugs and issues in there. I will mark them below for
the benefit of others. I will send a v2 after -rc1, with those things
fixed and some improvements, and will include Vedashree's patches, so
that we have everything in one series.
> The Arm Live Firmware Activation (LFA) is a specification [1] to describe
> activating firmware components without a reboot. Those components
> (like TF-A's BL31, EDK-II, TF-RMM, secure paylods) would be updated the
> usual way: via fwupd, FF-A or other secure storage methods, or via some
> IMPDEF Out-Of-Bound method. The user can then activate this new firmware,
> at system runtime, without requiring a reboot.
> The specification covers the SMCCC interface to list and query available
> components and eventually trigger the activation.
>
> Add a new directory under /sys/firmware to present firmware components
> capable of live activation. Each of them is a directory under lfa/,
> and is identified via its GUID. The activation will be triggered by echoing
> "1" into the "activate" file:
> ==========================================
> /sys/firmware/lfa # ls -l . 6c*
> .:
> total 0
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 47d4086d-4cfe-9846-9b95-2950cbbd5a00
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 6c0762a6-12f2-4b56-92cb-ba8f633606d9
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 d6d0eea7-fcea-d54b-9782-9934f234b6e4
>
> 6c0762a6-12f2-4b56-92cb-ba8f633606d9:
> total 0
> --w------- 1 0 0 4096 Jan 19 11:33 activate
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_capable
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_pending
> --w------- 1 0 0 4096 Jan 19 11:33 cancel
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 cpu_rendezvous
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 current_version
> -rw-r--r-- 1 0 0 4096 Jan 19 11:33 force_cpu_rendezvous
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 may_reset_cpu
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 name
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 pending_version
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # grep . *
> grep: activate: Permission denied
> activation_capable:1
> activation_pending:1
> grep: cancel: Permission denied
> cpu_rendezvous:1
> current_version:0.0
> force_cpu_rendezvous:1
> may_reset_cpu:0
> name:TF-RMM
> pending_version:0.0
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # echo 1 > activate
> [ 2825.797871] Arm LFA: firmware activation succeeded.
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 #
> ==========================================
>
> [1] https://developer.arm.com/documentation/den0147/latest/
>
> Signed-off-by: Salman Nabi <salman.nabi@arm.com>
> ---
> drivers/firmware/smccc/Kconfig | 8 +
> drivers/firmware/smccc/Makefile | 1 +
> drivers/firmware/smccc/lfa_fw.c | 668 ++++++++++++++++++++++++++++++++
> 3 files changed, 677 insertions(+)
> create mode 100644 drivers/firmware/smccc/lfa_fw.c
>
> diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig
> index 15e7466179a6..ff7ca49486b0 100644
> --- a/drivers/firmware/smccc/Kconfig
> +++ b/drivers/firmware/smccc/Kconfig
> @@ -23,3 +23,11 @@ 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
As the kernel test robot correctly pointed out, this only works on ARM64.
SMCCC v1.2 is only defined for AArch64, and the LFA spec documents
actually explicitly mentions AArch64-only at the beginning.
> + default y
> + help
> + Include support for triggering Live Firmware Activation, which
> + allows to upgrade certain firmware components without a reboot.
> diff --git a/drivers/firmware/smccc/Makefile b/drivers/firmware/smccc/Makefile
> index 40d19144a860..a6dd01558a94 100644
> --- a/drivers/firmware/smccc/Makefile
> +++ b/drivers/firmware/smccc/Makefile
> @@ -2,3 +2,4 @@
> #
> obj-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += smccc.o kvm_guest.o
> obj-$(CONFIG_ARM_SMCCC_SOC_ID) += soc_id.o
> +obj-$(CONFIG_ARM_LFA) += lfa_fw.o
> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
> new file mode 100644
> index 000000000000..ce54049b7190
> --- /dev/null
> +++ b/drivers/firmware/smccc/lfa_fw.c
> @@ -0,0 +1,668 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Arm Limited
> + */
> +
> +#include <linux/fs.h>
> +#include <linux/init.h>
> +#include <linux/kobject.h>
> +#include <linux/module.h>
> +#include <linux/stop_machine.h>
> +#include <linux/string.h>
> +#include <linux/sysfs.h>
> +#include <linux/arm-smccc.h>
> +#include <linux/psci.h>
> +#include <uapi/linux/psci.h>
> +#include <linux/uuid.h>
> +#include <linux/array_size.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
This misses <linux/workqueue.h>. We get it via other includes, but better
list it here explicitly.
> +
> +#undef pr_fmt
> +#define pr_fmt(fmt) "Arm LFA: " fmt
> +
> +/* LFA v1.0b0 specification */
> +#define LFA_1_0_FN_BASE 0xc40002e0
> +#define LFA_1_0_FN(n) (LFA_1_0_FN_BASE + (n))
> +
> +#define LFA_1_0_FN_GET_VERSION LFA_1_0_FN(0)
> +#define LFA_1_0_FN_CHECK_FEATURE LFA_1_0_FN(1)
> +#define LFA_1_0_FN_GET_INFO LFA_1_0_FN(2)
> +#define LFA_1_0_FN_GET_INVENTORY LFA_1_0_FN(3)
> +#define LFA_1_0_FN_PRIME LFA_1_0_FN(4)
> +#define LFA_1_0_FN_ACTIVATE LFA_1_0_FN(5)
> +#define LFA_1_0_FN_CANCEL LFA_1_0_FN(6)
> +
> +/* CALL_AGAIN flags (returned by SMC) */
> +#define LFA_PRIME_CALL_AGAIN BIT(0)
> +#define LFA_ACTIVATE_CALL_AGAIN BIT(0)
> +
> +/* LFA return values */
> +#define LFA_SUCCESS 0
> +#define LFA_NOT_SUPPORTED 1
> +#define LFA_BUSY 2
> +#define LFA_AUTH_ERROR 3
> +#define LFA_NO_MEMORY 4
> +#define LFA_CRITICAL_ERROR 5
> +#define LFA_DEVICE_ERROR 6
> +#define LFA_WRONG_STATE 7
> +#define LFA_INVALID_PARAMETERS 8
> +#define LFA_COMPONENT_WRONG_STATE 9
> +#define LFA_INVALID_ADDRESS 10
> +#define LFA_ACTIVATION_FAILED 11
> +
> +#define LFA_ERROR_STRING(name) \
> + [name] = #name
> +
> +static const char * const lfa_error_strings[] = {
> + LFA_ERROR_STRING(LFA_SUCCESS),
> + LFA_ERROR_STRING(LFA_NOT_SUPPORTED),
> + LFA_ERROR_STRING(LFA_BUSY),
> + LFA_ERROR_STRING(LFA_AUTH_ERROR),
> + LFA_ERROR_STRING(LFA_NO_MEMORY),
> + LFA_ERROR_STRING(LFA_CRITICAL_ERROR),
> + LFA_ERROR_STRING(LFA_DEVICE_ERROR),
> + LFA_ERROR_STRING(LFA_WRONG_STATE),
> + LFA_ERROR_STRING(LFA_INVALID_PARAMETERS),
> + LFA_ERROR_STRING(LFA_COMPONENT_WRONG_STATE),
> + LFA_ERROR_STRING(LFA_INVALID_ADDRESS),
> + LFA_ERROR_STRING(LFA_ACTIVATION_FAILED)
> +};
> +
> +enum image_attr_names {
> + LFA_ATTR_NAME,
> + LFA_ATTR_CURRENT_VERSION,
> + LFA_ATTR_PENDING_VERSION,
> + LFA_ATTR_ACT_CAPABLE,
> + LFA_ATTR_ACT_PENDING,
> + LFA_ATTR_MAY_RESET_CPU,
> + LFA_ATTR_CPU_RENDEZVOUS,
> + LFA_ATTR_FORCE_CPU_RENDEZVOUS,
> + LFA_ATTR_ACTIVATE,
> + LFA_ATTR_CANCEL,
> + LFA_ATTR_NR_IMAGES
> +};
> +
> +struct image_props {
> + struct list_head image_node;
> + const char *image_name;
> + int fw_seq_id;
> + u64 current_version;
> + u64 pending_version;
> + bool activation_capable;
> + bool activation_pending;
> + bool may_reset_cpu;
> + bool cpu_rendezvous;
> + bool cpu_rendezvous_forced;
> + struct kobject *image_dir;
> + struct kobj_attribute image_attrs[LFA_ATTR_NR_IMAGES];
> +};
> +static LIST_HEAD(lfa_fw_images);
> +
> +/* A UUID split over two 64-bit registers */
> +struct uuid_regs {
> + u64 uuid_lo;
> + u64 uuid_hi;
> +};
> +
> +static const struct fw_image_uuid {
> + const char *name;
> + const char *uuid;
> +} fw_images_uuids[] = {
> + {
> + .name = "TF-A BL31 runtime",
> + .uuid = "47d4086d-4cfe-9846-9b95-2950cbbd5a00",
> + },
> + {
> + .name = "BL33 non-secure payload",
> + .uuid = "d6d0eea7-fcea-d54b-9782-9934f234b6e4",
> + },
> + {
> + .name = "TF-RMM",
> + .uuid = "6c0762a6-12f2-4b56-92cb-ba8f633606d9",
> + },
> +};
> +
> +static struct kobject *lfa_dir;
> +static DEFINE_MUTEX(lfa_lock);
> +static struct workqueue_struct *fw_images_update_wq;
> +static struct work_struct fw_images_update_work;
> +
> +static int update_fw_images_tree(void);
> +
> +static void delete_fw_image_node(struct image_props *attrs)
> +{
> + int i;
> +
> + for (i = 0; i < LFA_ATTR_NR_IMAGES; i++)
> + sysfs_remove_file(attrs->image_dir, &attrs->image_attrs[i].attr);
> +
> + kobject_put(attrs->image_dir);
> + list_del(&attrs->image_node);
> + kfree(attrs);
> +}
> +
> +static void remove_invalid_fw_images(struct work_struct *work)
> +{
> + struct image_props *attrs, *tmp;
> +
> + mutex_lock(&lfa_lock);
> +
> + /*
> + * Remove firmware images including directories that are no longer
> + * present in the LFA agent after updating the existing ones.
> + */
> + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node) {
> + if (attrs->fw_seq_id == -1)
> + delete_fw_image_node(attrs);
> + }
> +
> + mutex_unlock(&lfa_lock);
> +}
> +
> +static void set_image_flags(struct image_props *attrs, int seq_id,
> + u32 image_flags, u64 reg_current_ver,
> + u64 reg_pending_ver)
> +{
> + attrs->fw_seq_id = seq_id;
> + attrs->current_version = reg_current_ver;
> + attrs->pending_version = reg_pending_ver;
> + attrs->activation_capable = !!(image_flags & BIT(0));
> + attrs->activation_pending = !!(image_flags & BIT(1));
> + attrs->may_reset_cpu = !!(image_flags & BIT(2));
> + /* cpu_rendezvous_optional bit has inverse logic in the spec */
> + attrs->cpu_rendezvous = !(image_flags & BIT(3));
> +}
> +
> +static unsigned long get_nr_lfa_components(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_GET_INFO;
> + reg.a1 = 0; /* lfa_info_selector = 0 */
> +
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 != LFA_SUCCESS)
> + return reg.a0;
> +
> + return reg.a1;
> +}
> +
> +static int lfa_cancel(void *data)
> +{
> + struct image_props *attrs = data;
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_CANCEL;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> +
> + /*
> + * When firmware activation is called with "skip_cpu_rendezvous=1",
> + * LFA_CANCEL can fail with LFA_BUSY if the activation could not be
> + * cancelled.
> + */
> + if (reg.a0 == LFA_SUCCESS) {
> + pr_info("Activation cancelled for image %s\n",
> + attrs->image_name);
> + } else {
> + pr_err("Firmware activation could not be cancelled: %s\n",
> + lfa_error_strings[-reg.a0]);
> + return -EINVAL;
> + }
> +
> + return reg.a0;
> +}
> +
> +static int call_lfa_activate(void *data)
> +{
> + struct image_props *attrs = data;
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_ACTIVATE;
> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
> + /*
> + * As we do not support updates requiring a CPU reset (yet),
> + * we pass 0 in reg.a3 and reg.a4, holding the entry point and context
> + * ID respectively.
> + * cpu_rendezvous_forced is set by the administrator, via sysfs,
> + * cpu_rendezvous is dictated by each firmware component.
> + */
> + reg.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous);
> +
> + for (;;) {
> + arm_smccc_1_2_invoke(®, ®);
> +
> + if ((long)reg.a0 < 0) {
> + pr_err("ACTIVATE for image %s failed: %s\n",
> + attrs->image_name, lfa_error_strings[-reg.a0]);
We should not let an externally provided value (the return value in a0) to
index an array. Future (or rogue?) versions of an LFA agent could use
higher error numbers, so the value must be checked before being used as an
index.
> + return reg.a0;
> + }
> + if (!(reg.a1 & LFA_ACTIVATE_CALL_AGAIN))
> + break; /* ACTIVATE successful */
> + }
> +
> + return reg.a0;
> +}
> +
> +static int activate_fw_image(struct image_props *attrs)
> +{
> + int ret;
> +
> + mutex_lock(&lfa_lock);
> + if (attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous)
> + ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask);
> + else
> + ret = call_lfa_activate(attrs);
> +
> + if (ret != 0) {
> + mutex_unlock(&lfa_lock);
> + return lfa_cancel(attrs);
> + }
> +
> + /*
> + * Invalidate fw_seq_ids (-1) for all images as the seq_ids and the
> + * number of firmware images in the LFA agent may change after a
> + * successful activation attempt. Negate all image flags as well.
> + */
> + attrs = NULL;
> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
> + set_image_flags(attrs, -1, 0b1000, 0, 0);
> + }
> +
> + update_fw_images_tree();
> +
> + /*
> + * Removing non-valid image directories at the end of an activation.
> + * We can't remove the sysfs attributes while in the respective
> + * _store() handler, so have to postpone the list removal to a
> + * workqueue.
> + */
> + INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
> + queue_work(fw_images_update_wq, &fw_images_update_work);
> + mutex_unlock(&lfa_lock);
> +
> + return ret;
> +}
> +
> +static int prime_fw_image(struct image_props *attrs)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + int ret;
> +
> + mutex_lock(&lfa_lock);
> + /* Avoid SMC calls on invalid firmware images */
> + if (attrs->fw_seq_id == -1) {
> + pr_err("Arm LFA: Invalid firmware sequence id\n");
> + mutex_unlock(&lfa_lock);
> +
> + return -ENODEV;
> + }
> +
> + if (attrs->may_reset_cpu) {
> + pr_err("CPU reset not supported by kernel driver\n");
> + mutex_unlock(&lfa_lock);
> +
> + return -EINVAL;
> + }
> +
> + /*
> + * LFA_PRIME/ACTIVATE will return 1 in reg.a1 if the firmware
> + * priming/activation is still in progress. In that case
> + * LFA_PRIME/ACTIVATE will need to be called again.
> + * reg.a1 will become 0 once the prime/activate process completes.
> + */
> + reg.a0 = LFA_1_0_FN_PRIME;
> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
> + for (;;) {
> + arm_smccc_1_2_invoke(®, ®);
> +
> + if ((long)reg.a0 < 0) {
> + pr_err("LFA_PRIME for image %s failed: %s\n",
> + attrs->image_name, lfa_error_strings[-reg.a0]);
> + mutex_unlock(&lfa_lock);
> +
> + return reg.a0;
> + }
> + if (!(reg.a1 & LFA_PRIME_CALL_AGAIN)) {
> + ret = 0;
> + break; /* PRIME successful */
> + }
> + }
> +
> + mutex_unlock(&lfa_lock);
> + return ret;
> +}
> +
> +static ssize_t name_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_NAME]);
> +
> + return sysfs_emit(buf, "%s\n", attrs->image_name);
> +}
> +
> +static ssize_t activation_capable_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_CAPABLE]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->activation_capable);
> +}
> +
> +static ssize_t activation_pending_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_PENDING]);
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + /*
> + * Activation pending status can change anytime thus we need to update
> + * and return its current value
> + */
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS)
> + attrs->activation_pending = !!(reg.a3 & BIT(1));
> +
> + return sysfs_emit(buf, "%d\n", attrs->activation_pending);
> +}
> +
> +static ssize_t may_reset_cpu_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_MAY_RESET_CPU]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->may_reset_cpu);
> +}
> +
> +static ssize_t cpu_rendezvous_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CPU_RENDEZVOUS]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous);
> +}
> +
> +static ssize_t force_cpu_rendezvous_store(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
> + int ret;
> +
> + ret = kstrtobool(buf, &attrs->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 image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous_forced);
> +}
> +
> +static ssize_t current_version_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CURRENT_VERSION]);
> + u32 maj, min;
> +
> + maj = attrs->current_version >> 32;
> + min = attrs->current_version & 0xffffffff;
> + return sysfs_emit(buf, "%u.%u\n", maj, min);
> +}
> +
> +static ssize_t pending_version_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_PENDING]);
This should be LFA_ATTR_PENDING_VERSION as the index.
> + struct arm_smccc_1_2_regs reg = { 0 };
> + u32 maj, min;
> +
> + /*
> + * Similar to activation pending, this value can change following an
> + * update, we need to retrieve fresh info instead of stale information.
> + */
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS) {
> + if (reg.a5 != 0 && attrs->activation_pending)
> + {
> + attrs->pending_version = reg.a5;
> + maj = reg.a5 >> 32;
> + min = reg.a5 & 0xffffffff;
> + }
> + }
This leaves maj and min uninitialised, if either the call failed or the
image is not pending or doesn't provide a new version.
Could be fixed by calling sysfs_emit() in the "if" clause, then returning,
and outputting some fallback version number otherwise.
> +
> + return sysfs_emit(buf, "%u.%u\n", maj, min);
> +}
> +
> +static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACTIVATE]);
> + int ret;
> +
> + ret = prime_fw_image(attrs);
> + if (ret) {
> + pr_err("Firmware prime failed: %s\n",
> + lfa_error_strings[-ret]);
> + return -ECANCELED;
> + }
> +
> + ret = activate_fw_image(attrs);
> + if (ret) {
> + pr_err("Firmware activation failed: %s\n",
> + lfa_error_strings[-ret]);
> + return -ECANCELED;
> + }
> +
> + pr_info("Firmware activation succeeded\n");
> +
> + return count;
> +}
> +
> +static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CANCEL]);
> + int ret;
> +
> + ret = lfa_cancel(attrs);
> + if (ret != 0)
> + return ret;
> +
> + return count;
> +}
> +
> +static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = {
> + [LFA_ATTR_NAME] = __ATTR_RO(name),
> + [LFA_ATTR_CURRENT_VERSION] = __ATTR_RO(current_version),
> + [LFA_ATTR_PENDING_VERSION] = __ATTR_RO(pending_version),
> + [LFA_ATTR_ACT_CAPABLE] = __ATTR_RO(activation_capable),
> + [LFA_ATTR_ACT_PENDING] = __ATTR_RO(activation_pending),
> + [LFA_ATTR_MAY_RESET_CPU] = __ATTR_RO(may_reset_cpu),
> + [LFA_ATTR_CPU_RENDEZVOUS] = __ATTR_RO(cpu_rendezvous),
> + [LFA_ATTR_FORCE_CPU_RENDEZVOUS] = __ATTR_RW(force_cpu_rendezvous),
> + [LFA_ATTR_ACTIVATE] = __ATTR_WO(activate),
> + [LFA_ATTR_CANCEL] = __ATTR_WO(cancel)
> +};
> +
> +static void clean_fw_images_tree(void)
> +{
> + struct image_props *attrs, *tmp;
> +
> + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node)
> + delete_fw_image_node(attrs);
> +}
> +
> +static int update_fw_image_node(char *fw_uuid, int seq_id,
> + u32 image_flags, u64 reg_current_ver,
> + u64 reg_pending_ver)
> +{
> + const char *image_name = "(unknown)";
> + struct image_props *attrs;
> + int ret;
> +
> + /*
> + * If a fw_image is already in the images list then we just update
> + * its flags and seq_id instead of trying to recreate it.
> + */
> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
> + if (!strcmp(attrs->image_dir->name, fw_uuid)) {
> + set_image_flags(attrs, seq_id, image_flags,
> + reg_current_ver, reg_pending_ver);
> + return 0;
> + }
> + }
> +
> + attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);
> + if (!attrs)
> + return -ENOMEM;
> +
> + for (int i = 0; i < ARRAY_SIZE(fw_images_uuids); i++) {
> + if (!strcmp(fw_images_uuids[i].uuid, fw_uuid))
> + image_name = fw_images_uuids[i].name;
> + }
> +
> + attrs->image_dir = kobject_create_and_add(fw_uuid, lfa_dir);
> + if (!attrs->image_dir)
> + return -ENOMEM;
that leaks attrs, allocated above
> +
> + INIT_LIST_HEAD(&attrs->image_node);
> + attrs->image_name = image_name;
> + attrs->cpu_rendezvous_forced = 1;
> + set_image_flags(attrs, seq_id, image_flags, reg_current_ver,
> + reg_pending_ver);
> +
> + /*
> + * The attributes for each sysfs file are constant (handler functions,
> + * name and permissions are the same within each directory), but we
> + * need a per-directory copy regardless, to get a unique handle
> + * for each directory, so that container_of can do its magic.
> + * Also this requires an explicit sysfs_attr_init(), since it's a new
> + * copy, to make LOCKDEP happy.
> + */
> + memcpy(attrs->image_attrs, image_attrs_group,
> + sizeof(attrs->image_attrs));
> + for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++) {
> + struct attribute *attr = &attrs->image_attrs[i].attr;
> +
> + sysfs_attr_init(attr);
> + ret = sysfs_create_file(attrs->image_dir, attr);
> + if (ret) {
> + pr_err("creating sysfs file for uuid %s: %d\n",
> + fw_uuid, ret);
> + clean_fw_images_tree();
> +
> + return ret;
> + }
> + }
> + list_add(&attrs->image_node, &lfa_fw_images);
> +
> + return ret;
> +}
> +
> +static int update_fw_images_tree(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + struct uuid_regs image_uuid;
> + char image_id_str[40];
> + int ret, num_of_components;
> +
> + num_of_components = get_nr_lfa_components();
> + if (num_of_components <= 0) {
> + pr_err("Error getting number of LFA components\n");
> + return -ENODEV;
> + }
> +
> + for (int i = 0; i < num_of_components; i++) {
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = i; /* fw_seq_id under consideration */
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS) {
> + image_uuid.uuid_lo = reg.a1;
> + image_uuid.uuid_hi = reg.a2;
> +
> + snprintf(image_id_str, sizeof(image_id_str), "%pUb",
> + &image_uuid);
> + ret = update_fw_image_node(image_id_str, i,
> + reg.a3, reg.a4, reg.a5);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int __init lfa_init(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + int err;
> +
> + reg.a0 = LFA_1_0_FN_GET_VERSION;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == -LFA_NOT_SUPPORTED) {
> + pr_info("Live Firmware activation: no firmware agent found\n");
> + return -ENODEV;
> + }
> +
> + fw_images_update_wq = alloc_workqueue("fw_images_update_wq",
> + WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
> + if (!fw_images_update_wq) {
> + pr_err("Live Firmware Activation: Failed to allocate workqueue.\n");
> +
> + return -ENOMEM;
> + }
> +
> + pr_info("Live Firmware Activation: detected v%ld.%ld\n",
> + reg.a0 >> 16, reg.a0 & 0xffff);
> +
> + lfa_dir = kobject_create_and_add("lfa", firmware_kobj);
> + if (!lfa_dir)
> + return -ENOMEM;
That leaks the workqueue created above.
> +
> + mutex_lock(&lfa_lock);
> + err = update_fw_images_tree();
> + if (err != 0)
> + kobject_put(lfa_dir);
... and also here.
Cheers,
Andre
> +
> + mutex_unlock(&lfa_lock);
> + return err;
> +}
> +module_init(lfa_init);
> +
> +static void __exit lfa_exit(void)
> +{
> + flush_workqueue(fw_images_update_wq);
> + destroy_workqueue(fw_images_update_wq);
> +
> + mutex_lock(&lfa_lock);
> + clean_fw_images_tree();
> + mutex_unlock(&lfa_lock);
> +
> + kobject_put(lfa_dir);
> +}
> +module_exit(lfa_exit);
> +
> +MODULE_DESCRIPTION("ARM Live Firmware Activation (LFA)");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-02-20 17:39 ` Andre Przywara
@ 2026-02-20 21:48 ` Trilok Soni
0 siblings, 0 replies; 18+ messages in thread
From: Trilok Soni @ 2026-02-20 21:48 UTC (permalink / raw)
To: Andre Przywara, Salman Nabi
Cc: mark.rutland, lpieralisi, linux-kernel, vwadekar, sdonthineni,
vsethi, linux-coco, sudeep.holla, ardb, linux-arm-kernel,
chao.gao
On 2/20/2026 9:39 AM, Andre Przywara wrote:
> On Mon, 19 Jan 2026 12:27:29 +0000
> Salman Nabi <salman.nabi@arm.com> wrote:
>
> Hey,
>
> for the records: while working on improving the patch and during internal
> review, we found some bugs and issues in there. I will mark them below for
> the benefit of others. I will send a v2 after -rc1, with those things
> fixed and some improvements, and will include Vedashree's patches, so
> that we have everything in one series.
>
>> The Arm Live Firmware Activation (LFA) is a specification [1] to describe
>> activating firmware components without a reboot. Those components
>> (like TF-A's BL31, EDK-II, TF-RMM, secure paylods) would be updated the
>> usual way: via fwupd, FF-A or other secure storage methods, or via some
>> IMPDEF Out-Of-Bound method. The user can then activate this new firmware,
>> at system runtime, without requiring a reboot.
>> The specification covers the SMCCC interface to list and query available
>> components and eventually trigger the activation.
>>
>> Add a new directory under /sys/firmware to present firmware components
>> capable of live activation. Each of them is a directory under lfa/,
>> and is identified via its GUID. The activation will be triggered by echoing
>> "1" into the "activate" file:
>> ==========================================
>> /sys/firmware/lfa # ls -l . 6c*
>> .:
>> total 0
>> drwxr-xr-x 2 0 0 0 Jan 19 11:33 47d4086d-4cfe-9846-9b95-2950cbbd5a00
>> drwxr-xr-x 2 0 0 0 Jan 19 11:33 6c0762a6-12f2-4b56-92cb-ba8f633606d9
>> drwxr-xr-x 2 0 0 0 Jan 19 11:33 d6d0eea7-fcea-d54b-9782-9934f234b6e4
>>
>> 6c0762a6-12f2-4b56-92cb-ba8f633606d9:
>> total 0
>> --w------- 1 0 0 4096 Jan 19 11:33 activate
>> -r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_capable
>> -r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_pending
>> --w------- 1 0 0 4096 Jan 19 11:33 cancel
>> -r--r--r-- 1 0 0 4096 Jan 19 11:33 cpu_rendezvous
>> -r--r--r-- 1 0 0 4096 Jan 19 11:33 current_version
>> -rw-r--r-- 1 0 0 4096 Jan 19 11:33 force_cpu_rendezvous
>> -r--r--r-- 1 0 0 4096 Jan 19 11:33 may_reset_cpu
>> -r--r--r-- 1 0 0 4096 Jan 19 11:33 name
>> -r--r--r-- 1 0 0 4096 Jan 19 11:33 pending_version
>> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # grep . *
>> grep: activate: Permission denied
>> activation_capable:1
>> activation_pending:1
>> grep: cancel: Permission denied
>> cpu_rendezvous:1
>> current_version:0.0
>> force_cpu_rendezvous:1
>> may_reset_cpu:0
>> name:TF-RMM
>> pending_version:0.0
>> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # echo 1 > activate
>> [ 2825.797871] Arm LFA: firmware activation succeeded.
>> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 #
>> ==========================================
>>
>> [1] https://developer.arm.com/documentation/den0147/latest/
>>
>> Signed-off-by: Salman Nabi <salman.nabi@arm.com>
>> ---
>> drivers/firmware/smccc/Kconfig | 8 +
>> drivers/firmware/smccc/Makefile | 1 +
>> drivers/firmware/smccc/lfa_fw.c | 668 ++++++++++++++++++++++++++++++++
>> 3 files changed, 677 insertions(+)
>> create mode 100644 drivers/firmware/smccc/lfa_fw.c
>>
>> diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig
>> index 15e7466179a6..ff7ca49486b0 100644
>> --- a/drivers/firmware/smccc/Kconfig
>> +++ b/drivers/firmware/smccc/Kconfig
>> @@ -23,3 +23,11 @@ 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
>
> As the kernel test robot correctly pointed out, this only works on ARM64.
> SMCCC v1.2 is only defined for AArch64, and the LFA spec documents
> actually explicitly mentions AArch64-only at the beginning.
Thank you for the update. How to test these patches outside of NVIDIA devices?
Is it possible to test them on the QEMU or FVP? Any instructions on doing
these tests will be helpful w/ the virtual platforms.
---Trilok Soni
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-01-19 12:27 ` [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA) Salman Nabi
` (6 preceding siblings ...)
2026-02-20 17:39 ` Andre Przywara
@ 2026-03-13 9:46 ` Nirmoy Das
2026-03-13 14:39 ` Andre Przywara
7 siblings, 1 reply; 18+ messages in thread
From: Nirmoy Das @ 2026-03-13 9:46 UTC (permalink / raw)
To: Salman Nabi, vvidwans, andre.przywara, sudeep.holla, mark.rutland,
lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
Hi Salman and Andre,
We found an bug while testing LFA. See below:
On 19.01.26 14:27, Salman Nabi wrote:
> The Arm Live Firmware Activation (LFA) is a specification [1] to describe
> activating firmware components without a reboot. Those components
> (like TF-A's BL31, EDK-II, TF-RMM, secure paylods) would be updated the
> usual way: via fwupd, FF-A or other secure storage methods, or via some
> IMPDEF Out-Of-Bound method. The user can then activate this new firmware,
> at system runtime, without requiring a reboot.
> The specification covers the SMCCC interface to list and query available
> components and eventually trigger the activation.
>
> Add a new directory under /sys/firmware to present firmware components
> capable of live activation. Each of them is a directory under lfa/,
> and is identified via its GUID. The activation will be triggered by echoing
> "1" into the "activate" file:
> ==========================================
> /sys/firmware/lfa # ls -l . 6c*
> .:
> total 0
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 47d4086d-4cfe-9846-9b95-2950cbbd5a00
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 6c0762a6-12f2-4b56-92cb-ba8f633606d9
> drwxr-xr-x 2 0 0 0 Jan 19 11:33 d6d0eea7-fcea-d54b-9782-9934f234b6e4
>
> 6c0762a6-12f2-4b56-92cb-ba8f633606d9:
> total 0
> --w------- 1 0 0 4096 Jan 19 11:33 activate
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_capable
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 activation_pending
> --w------- 1 0 0 4096 Jan 19 11:33 cancel
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 cpu_rendezvous
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 current_version
> -rw-r--r-- 1 0 0 4096 Jan 19 11:33 force_cpu_rendezvous
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 may_reset_cpu
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 name
> -r--r--r-- 1 0 0 4096 Jan 19 11:33 pending_version
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # grep . *
> grep: activate: Permission denied
> activation_capable:1
> activation_pending:1
> grep: cancel: Permission denied
> cpu_rendezvous:1
> current_version:0.0
> force_cpu_rendezvous:1
> may_reset_cpu:0
> name:TF-RMM
> pending_version:0.0
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 # echo 1 > activate
> [ 2825.797871] Arm LFA: firmware activation succeeded.
> /sys/firmware/lfa/6c0762a6-12f2-4b56-92cb-ba8f633606d9 #
> ==========================================
>
> [1] https://developer.arm.com/documentation/den0147/latest/
>
> Signed-off-by: Salman Nabi <salman.nabi@arm.com>
> ---
> drivers/firmware/smccc/Kconfig | 8 +
> drivers/firmware/smccc/Makefile | 1 +
> drivers/firmware/smccc/lfa_fw.c | 668 ++++++++++++++++++++++++++++++++
> 3 files changed, 677 insertions(+)
> create mode 100644 drivers/firmware/smccc/lfa_fw.c
>
> diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig
> index 15e7466179a6..ff7ca49486b0 100644
> --- a/drivers/firmware/smccc/Kconfig
> +++ b/drivers/firmware/smccc/Kconfig
> @@ -23,3 +23,11 @@ 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
> + default y
> + help
> + Include support for triggering Live Firmware Activation, which
> + allows to upgrade certain firmware components without a reboot.
> diff --git a/drivers/firmware/smccc/Makefile b/drivers/firmware/smccc/Makefile
> index 40d19144a860..a6dd01558a94 100644
> --- a/drivers/firmware/smccc/Makefile
> +++ b/drivers/firmware/smccc/Makefile
> @@ -2,3 +2,4 @@
> #
> obj-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += smccc.o kvm_guest.o
> obj-$(CONFIG_ARM_SMCCC_SOC_ID) += soc_id.o
> +obj-$(CONFIG_ARM_LFA) += lfa_fw.o
> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
> new file mode 100644
> index 000000000000..ce54049b7190
> --- /dev/null
> +++ b/drivers/firmware/smccc/lfa_fw.c
> @@ -0,0 +1,668 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Arm Limited
> + */
> +
> +#include <linux/fs.h>
> +#include <linux/init.h>
> +#include <linux/kobject.h>
> +#include <linux/module.h>
> +#include <linux/stop_machine.h>
> +#include <linux/string.h>
> +#include <linux/sysfs.h>
> +#include <linux/arm-smccc.h>
> +#include <linux/psci.h>
> +#include <uapi/linux/psci.h>
> +#include <linux/uuid.h>
> +#include <linux/array_size.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +
> +#undef pr_fmt
> +#define pr_fmt(fmt) "Arm LFA: " fmt
> +
> +/* LFA v1.0b0 specification */
> +#define LFA_1_0_FN_BASE 0xc40002e0
> +#define LFA_1_0_FN(n) (LFA_1_0_FN_BASE + (n))
> +
> +#define LFA_1_0_FN_GET_VERSION LFA_1_0_FN(0)
> +#define LFA_1_0_FN_CHECK_FEATURE LFA_1_0_FN(1)
> +#define LFA_1_0_FN_GET_INFO LFA_1_0_FN(2)
> +#define LFA_1_0_FN_GET_INVENTORY LFA_1_0_FN(3)
> +#define LFA_1_0_FN_PRIME LFA_1_0_FN(4)
> +#define LFA_1_0_FN_ACTIVATE LFA_1_0_FN(5)
> +#define LFA_1_0_FN_CANCEL LFA_1_0_FN(6)
> +
> +/* CALL_AGAIN flags (returned by SMC) */
> +#define LFA_PRIME_CALL_AGAIN BIT(0)
> +#define LFA_ACTIVATE_CALL_AGAIN BIT(0)
> +
> +/* LFA return values */
> +#define LFA_SUCCESS 0
> +#define LFA_NOT_SUPPORTED 1
> +#define LFA_BUSY 2
> +#define LFA_AUTH_ERROR 3
> +#define LFA_NO_MEMORY 4
> +#define LFA_CRITICAL_ERROR 5
> +#define LFA_DEVICE_ERROR 6
> +#define LFA_WRONG_STATE 7
> +#define LFA_INVALID_PARAMETERS 8
> +#define LFA_COMPONENT_WRONG_STATE 9
> +#define LFA_INVALID_ADDRESS 10
> +#define LFA_ACTIVATION_FAILED 11
> +
> +#define LFA_ERROR_STRING(name) \
> + [name] = #name
> +
> +static const char * const lfa_error_strings[] = {
> + LFA_ERROR_STRING(LFA_SUCCESS),
> + LFA_ERROR_STRING(LFA_NOT_SUPPORTED),
> + LFA_ERROR_STRING(LFA_BUSY),
> + LFA_ERROR_STRING(LFA_AUTH_ERROR),
> + LFA_ERROR_STRING(LFA_NO_MEMORY),
> + LFA_ERROR_STRING(LFA_CRITICAL_ERROR),
> + LFA_ERROR_STRING(LFA_DEVICE_ERROR),
> + LFA_ERROR_STRING(LFA_WRONG_STATE),
> + LFA_ERROR_STRING(LFA_INVALID_PARAMETERS),
> + LFA_ERROR_STRING(LFA_COMPONENT_WRONG_STATE),
> + LFA_ERROR_STRING(LFA_INVALID_ADDRESS),
> + LFA_ERROR_STRING(LFA_ACTIVATION_FAILED)
> +};
> +
> +enum image_attr_names {
> + LFA_ATTR_NAME,
> + LFA_ATTR_CURRENT_VERSION,
> + LFA_ATTR_PENDING_VERSION,
> + LFA_ATTR_ACT_CAPABLE,
> + LFA_ATTR_ACT_PENDING,
> + LFA_ATTR_MAY_RESET_CPU,
> + LFA_ATTR_CPU_RENDEZVOUS,
> + LFA_ATTR_FORCE_CPU_RENDEZVOUS,
> + LFA_ATTR_ACTIVATE,
> + LFA_ATTR_CANCEL,
> + LFA_ATTR_NR_IMAGES
> +};
> +
> +struct image_props {
> + struct list_head image_node;
> + const char *image_name;
> + int fw_seq_id;
> + u64 current_version;
> + u64 pending_version;
> + bool activation_capable;
> + bool activation_pending;
> + bool may_reset_cpu;
> + bool cpu_rendezvous;
> + bool cpu_rendezvous_forced;
> + struct kobject *image_dir;
> + struct kobj_attribute image_attrs[LFA_ATTR_NR_IMAGES];
> +};
> +static LIST_HEAD(lfa_fw_images);
> +
> +/* A UUID split over two 64-bit registers */
> +struct uuid_regs {
> + u64 uuid_lo;
> + u64 uuid_hi;
> +};
> +
> +static const struct fw_image_uuid {
> + const char *name;
> + const char *uuid;
> +} fw_images_uuids[] = {
> + {
> + .name = "TF-A BL31 runtime",
> + .uuid = "47d4086d-4cfe-9846-9b95-2950cbbd5a00",
> + },
> + {
> + .name = "BL33 non-secure payload",
> + .uuid = "d6d0eea7-fcea-d54b-9782-9934f234b6e4",
> + },
> + {
> + .name = "TF-RMM",
> + .uuid = "6c0762a6-12f2-4b56-92cb-ba8f633606d9",
> + },
> +};
> +
> +static struct kobject *lfa_dir;
> +static DEFINE_MUTEX(lfa_lock);
> +static struct workqueue_struct *fw_images_update_wq;
> +static struct work_struct fw_images_update_work;
> +
> +static int update_fw_images_tree(void);
> +
> +static void delete_fw_image_node(struct image_props *attrs)
> +{
> + int i;
> +
> + for (i = 0; i < LFA_ATTR_NR_IMAGES; i++)
> + sysfs_remove_file(attrs->image_dir, &attrs->image_attrs[i].attr);
> +
> + kobject_put(attrs->image_dir);
> + list_del(&attrs->image_node);
> + kfree(attrs);
> +}
> +
> +static void remove_invalid_fw_images(struct work_struct *work)
> +{
> + struct image_props *attrs, *tmp;
> +
> + mutex_lock(&lfa_lock);
> +
> + /*
> + * Remove firmware images including directories that are no longer
> + * present in the LFA agent after updating the existing ones.
> + */
> + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node) {
> + if (attrs->fw_seq_id == -1)
> + delete_fw_image_node(attrs);
> + }
> +
> + mutex_unlock(&lfa_lock);
> +}
> +
> +static void set_image_flags(struct image_props *attrs, int seq_id,
> + u32 image_flags, u64 reg_current_ver,
> + u64 reg_pending_ver)
> +{
> + attrs->fw_seq_id = seq_id;
> + attrs->current_version = reg_current_ver;
> + attrs->pending_version = reg_pending_ver;
> + attrs->activation_capable = !!(image_flags & BIT(0));
> + attrs->activation_pending = !!(image_flags & BIT(1));
> + attrs->may_reset_cpu = !!(image_flags & BIT(2));
> + /* cpu_rendezvous_optional bit has inverse logic in the spec */
> + attrs->cpu_rendezvous = !(image_flags & BIT(3));
> +}
> +
> +static unsigned long get_nr_lfa_components(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_GET_INFO;
> + reg.a1 = 0; /* lfa_info_selector = 0 */
> +
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 != LFA_SUCCESS)
> + return reg.a0;
> +
> + return reg.a1;
> +}
> +
> +static int lfa_cancel(void *data)
> +{
> + struct image_props *attrs = data;
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_CANCEL;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> +
> + /*
> + * When firmware activation is called with "skip_cpu_rendezvous=1",
> + * LFA_CANCEL can fail with LFA_BUSY if the activation could not be
> + * cancelled.
> + */
> + if (reg.a0 == LFA_SUCCESS) {
> + pr_info("Activation cancelled for image %s\n",
> + attrs->image_name);
> + } else {
> + pr_err("Firmware activation could not be cancelled: %s\n",
> + lfa_error_strings[-reg.a0]);
> + return -EINVAL;
> + }
> +
> + return reg.a0;
> +}
> +
> +static int call_lfa_activate(void *data)
> +{
> + struct image_props *attrs = data;
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + reg.a0 = LFA_1_0_FN_ACTIVATE;
> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
> + /*
> + * As we do not support updates requiring a CPU reset (yet),
> + * we pass 0 in reg.a3 and reg.a4, holding the entry point and context
> + * ID respectively.
> + * cpu_rendezvous_forced is set by the administrator, via sysfs,
> + * cpu_rendezvous is dictated by each firmware component.
> + */
> + reg.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous);
> +
> + for (;;) {
> + arm_smccc_1_2_invoke(®, ®);
> +
> + if ((long)reg.a0 < 0) {
> + pr_err("ACTIVATE for image %s failed: %s\n",
> + attrs->image_name, lfa_error_strings[-reg.a0]);
> + return reg.a0;
> + }
> + if (!(reg.a1 & LFA_ACTIVATE_CALL_AGAIN))
> + break; /* ACTIVATE successful */
> + }
> +
> + return reg.a0;
> +}
> +
> +static int activate_fw_image(struct image_props *attrs)
> +{
> + int ret;
> +
> + mutex_lock(&lfa_lock);
> + if (attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous)
> + ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask);
> + else
> + ret = call_lfa_activate(attrs);
> +
> + if (ret != 0) {
> + mutex_unlock(&lfa_lock);
> + return lfa_cancel(attrs);
> + }
> +
> + /*
> + * Invalidate fw_seq_ids (-1) for all images as the seq_ids and the
> + * number of firmware images in the LFA agent may change after a
> + * successful activation attempt. Negate all image flags as well.
> + */
> + attrs = NULL;
> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
> + set_image_flags(attrs, -1, 0b1000, 0, 0);
> + }
> +
> + update_fw_images_tree();
> +
> + /*
> + * Removing non-valid image directories at the end of an activation.
> + * We can't remove the sysfs attributes while in the respective
> + * _store() handler, so have to postpone the list removal to a
> + * workqueue.
> + */
> + INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
This can get invoke multiple times so re-initializing a work item that
may already be queued or running
is unsafe. This should be moved to lfa_init() so it is only called once.
I suggest:
diff --git a/drivers/firmware/smccc/lfa_fw.c
b/drivers/firmware/smccc/lfa_fw.c
index 90727a66e49a5..135358113104c 100644
--- a/drivers/firmware/smccc/lfa_fw.c
+++ b/drivers/firmware/smccc/lfa_fw.c
@@ -653,7 +653,6 @@ static int update_fw_images_tree(void)
* _store() handler, so have to postpone the list removal to a
* workqueue.
*/
- INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
queue_work(fw_images_update_wq, &fw_images_update_work);
return 0;
@@ -680,7 +679,7 @@ static void lfa_notify_handler(acpi_handle handle,
u32 event, void *data)
* of all activable and pending images.
*/
do {
- /* Reset activable image flag */
+ flush_workqueue(fw_images_update_wq);
found_activable_image = false;
list_for_each_entry(attrs, &lfa_fw_images, image_node) {
if (attrs->fw_seq_id == -1)
@@ -782,6 +781,8 @@ static int __init lfa_init(void)
return -ENOMEM;
}
+ INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
+
pr_info("Live Firmware Activation: detected v%ld.%ld\n",
reg.a0 >> 16, reg.a0 & 0xffff);
Regards,
Nirmoy
> + queue_work(fw_images_update_wq, &fw_images_update_work);
> + mutex_unlock(&lfa_lock);
> +
> + return ret;
> +}
> +
> +static int prime_fw_image(struct image_props *attrs)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + int ret;
> +
> + mutex_lock(&lfa_lock);
> + /* Avoid SMC calls on invalid firmware images */
> + if (attrs->fw_seq_id == -1) {
> + pr_err("Arm LFA: Invalid firmware sequence id\n");
> + mutex_unlock(&lfa_lock);
> +
> + return -ENODEV;
> + }
> +
> + if (attrs->may_reset_cpu) {
> + pr_err("CPU reset not supported by kernel driver\n");
> + mutex_unlock(&lfa_lock);
> +
> + return -EINVAL;
> + }
> +
> + /*
> + * LFA_PRIME/ACTIVATE will return 1 in reg.a1 if the firmware
> + * priming/activation is still in progress. In that case
> + * LFA_PRIME/ACTIVATE will need to be called again.
> + * reg.a1 will become 0 once the prime/activate process completes.
> + */
> + reg.a0 = LFA_1_0_FN_PRIME;
> + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
> + for (;;) {
> + arm_smccc_1_2_invoke(®, ®);
> +
> + if ((long)reg.a0 < 0) {
> + pr_err("LFA_PRIME for image %s failed: %s\n",
> + attrs->image_name, lfa_error_strings[-reg.a0]);
> + mutex_unlock(&lfa_lock);
> +
> + return reg.a0;
> + }
> + if (!(reg.a1 & LFA_PRIME_CALL_AGAIN)) {
> + ret = 0;
> + break; /* PRIME successful */
> + }
> + }
> +
> + mutex_unlock(&lfa_lock);
> + return ret;
> +}
> +
> +static ssize_t name_show(struct kobject *kobj, struct kobj_attribute *attr,
> + char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_NAME]);
> +
> + return sysfs_emit(buf, "%s\n", attrs->image_name);
> +}
> +
> +static ssize_t activation_capable_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_CAPABLE]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->activation_capable);
> +}
> +
> +static ssize_t activation_pending_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_PENDING]);
> + struct arm_smccc_1_2_regs reg = { 0 };
> +
> + /*
> + * Activation pending status can change anytime thus we need to update
> + * and return its current value
> + */
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS)
> + attrs->activation_pending = !!(reg.a3 & BIT(1));
> +
> + return sysfs_emit(buf, "%d\n", attrs->activation_pending);
> +}
> +
> +static ssize_t may_reset_cpu_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_MAY_RESET_CPU]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->may_reset_cpu);
> +}
> +
> +static ssize_t cpu_rendezvous_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CPU_RENDEZVOUS]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous);
> +}
> +
> +static ssize_t force_cpu_rendezvous_store(struct kobject *kobj,
> + struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
> + int ret;
> +
> + ret = kstrtobool(buf, &attrs->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 image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]);
> +
> + return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous_forced);
> +}
> +
> +static ssize_t current_version_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CURRENT_VERSION]);
> + u32 maj, min;
> +
> + maj = attrs->current_version >> 32;
> + min = attrs->current_version & 0xffffffff;
> + return sysfs_emit(buf, "%u.%u\n", maj, min);
> +}
> +
> +static ssize_t pending_version_show(struct kobject *kobj,
> + struct kobj_attribute *attr, char *buf)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACT_PENDING]);
> + struct arm_smccc_1_2_regs reg = { 0 };
> + u32 maj, min;
> +
> + /*
> + * Similar to activation pending, this value can change following an
> + * update, we need to retrieve fresh info instead of stale information.
> + */
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = attrs->fw_seq_id;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS) {
> + if (reg.a5 != 0 && attrs->activation_pending)
> + {
> + attrs->pending_version = reg.a5;
> + maj = reg.a5 >> 32;
> + min = reg.a5 & 0xffffffff;
> + }
> + }
> +
> + return sysfs_emit(buf, "%u.%u\n", maj, min);
> +}
> +
> +static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_ACTIVATE]);
> + int ret;
> +
> + ret = prime_fw_image(attrs);
> + if (ret) {
> + pr_err("Firmware prime failed: %s\n",
> + lfa_error_strings[-ret]);
> + return -ECANCELED;
> + }
> +
> + ret = activate_fw_image(attrs);
> + if (ret) {
> + pr_err("Firmware activation failed: %s\n",
> + lfa_error_strings[-ret]);
> + return -ECANCELED;
> + }
> +
> + pr_info("Firmware activation succeeded\n");
> +
> + return count;
> +}
> +
> +static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct image_props *attrs = container_of(attr, struct image_props,
> + image_attrs[LFA_ATTR_CANCEL]);
> + int ret;
> +
> + ret = lfa_cancel(attrs);
> + if (ret != 0)
> + return ret;
> +
> + return count;
> +}
> +
> +static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = {
> + [LFA_ATTR_NAME] = __ATTR_RO(name),
> + [LFA_ATTR_CURRENT_VERSION] = __ATTR_RO(current_version),
> + [LFA_ATTR_PENDING_VERSION] = __ATTR_RO(pending_version),
> + [LFA_ATTR_ACT_CAPABLE] = __ATTR_RO(activation_capable),
> + [LFA_ATTR_ACT_PENDING] = __ATTR_RO(activation_pending),
> + [LFA_ATTR_MAY_RESET_CPU] = __ATTR_RO(may_reset_cpu),
> + [LFA_ATTR_CPU_RENDEZVOUS] = __ATTR_RO(cpu_rendezvous),
> + [LFA_ATTR_FORCE_CPU_RENDEZVOUS] = __ATTR_RW(force_cpu_rendezvous),
> + [LFA_ATTR_ACTIVATE] = __ATTR_WO(activate),
> + [LFA_ATTR_CANCEL] = __ATTR_WO(cancel)
> +};
> +
> +static void clean_fw_images_tree(void)
> +{
> + struct image_props *attrs, *tmp;
> +
> + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node)
> + delete_fw_image_node(attrs);
> +}
> +
> +static int update_fw_image_node(char *fw_uuid, int seq_id,
> + u32 image_flags, u64 reg_current_ver,
> + u64 reg_pending_ver)
> +{
> + const char *image_name = "(unknown)";
> + struct image_props *attrs;
> + int ret;
> +
> + /*
> + * If a fw_image is already in the images list then we just update
> + * its flags and seq_id instead of trying to recreate it.
> + */
> + list_for_each_entry(attrs, &lfa_fw_images, image_node) {
> + if (!strcmp(attrs->image_dir->name, fw_uuid)) {
> + set_image_flags(attrs, seq_id, image_flags,
> + reg_current_ver, reg_pending_ver);
> + return 0;
> + }
> + }
> +
> + attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);
> + if (!attrs)
> + return -ENOMEM;
> +
> + for (int i = 0; i < ARRAY_SIZE(fw_images_uuids); i++) {
> + if (!strcmp(fw_images_uuids[i].uuid, fw_uuid))
> + image_name = fw_images_uuids[i].name;
> + }
> +
> + attrs->image_dir = kobject_create_and_add(fw_uuid, lfa_dir);
> + if (!attrs->image_dir)
> + return -ENOMEM;
> +
> + INIT_LIST_HEAD(&attrs->image_node);
> + attrs->image_name = image_name;
> + attrs->cpu_rendezvous_forced = 1;
> + set_image_flags(attrs, seq_id, image_flags, reg_current_ver,
> + reg_pending_ver);
> +
> + /*
> + * The attributes for each sysfs file are constant (handler functions,
> + * name and permissions are the same within each directory), but we
> + * need a per-directory copy regardless, to get a unique handle
> + * for each directory, so that container_of can do its magic.
> + * Also this requires an explicit sysfs_attr_init(), since it's a new
> + * copy, to make LOCKDEP happy.
> + */
> + memcpy(attrs->image_attrs, image_attrs_group,
> + sizeof(attrs->image_attrs));
> + for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++) {
> + struct attribute *attr = &attrs->image_attrs[i].attr;
> +
> + sysfs_attr_init(attr);
> + ret = sysfs_create_file(attrs->image_dir, attr);
> + if (ret) {
> + pr_err("creating sysfs file for uuid %s: %d\n",
> + fw_uuid, ret);
> + clean_fw_images_tree();
> +
> + return ret;
> + }
> + }
> + list_add(&attrs->image_node, &lfa_fw_images);
> +
> + return ret;
> +}
> +
> +static int update_fw_images_tree(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + struct uuid_regs image_uuid;
> + char image_id_str[40];
> + int ret, num_of_components;
> +
> + num_of_components = get_nr_lfa_components();
> + if (num_of_components <= 0) {
> + pr_err("Error getting number of LFA components\n");
> + return -ENODEV;
> + }
> +
> + for (int i = 0; i < num_of_components; i++) {
> + reg.a0 = LFA_1_0_FN_GET_INVENTORY;
> + reg.a1 = i; /* fw_seq_id under consideration */
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == LFA_SUCCESS) {
> + image_uuid.uuid_lo = reg.a1;
> + image_uuid.uuid_hi = reg.a2;
> +
> + snprintf(image_id_str, sizeof(image_id_str), "%pUb",
> + &image_uuid);
> + ret = update_fw_image_node(image_id_str, i,
> + reg.a3, reg.a4, reg.a5);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int __init lfa_init(void)
> +{
> + struct arm_smccc_1_2_regs reg = { 0 };
> + int err;
> +
> + reg.a0 = LFA_1_0_FN_GET_VERSION;
> + arm_smccc_1_2_invoke(®, ®);
> + if (reg.a0 == -LFA_NOT_SUPPORTED) {
> + pr_info("Live Firmware activation: no firmware agent found\n");
> + return -ENODEV;
> + }
> +
> + fw_images_update_wq = alloc_workqueue("fw_images_update_wq",
> + WQ_UNBOUND | WQ_MEM_RECLAIM, 1);
> + if (!fw_images_update_wq) {
> + pr_err("Live Firmware Activation: Failed to allocate workqueue.\n");
> +
> + return -ENOMEM;
> + }
> +
> + pr_info("Live Firmware Activation: detected v%ld.%ld\n",
> + reg.a0 >> 16, reg.a0 & 0xffff);
> +
> + lfa_dir = kobject_create_and_add("lfa", firmware_kobj);
> + if (!lfa_dir)
> + return -ENOMEM;
> +
> + mutex_lock(&lfa_lock);
> + err = update_fw_images_tree();
> + if (err != 0)
> + kobject_put(lfa_dir);
> +
> + mutex_unlock(&lfa_lock);
> + return err;
> +}
> +module_init(lfa_init);
> +
> +static void __exit lfa_exit(void)
> +{
> + flush_workqueue(fw_images_update_wq);
> + destroy_workqueue(fw_images_update_wq);
> +
> + mutex_lock(&lfa_lock);
> + clean_fw_images_tree();
> + mutex_unlock(&lfa_lock);
> +
> + kobject_put(lfa_dir);
> +}
> +module_exit(lfa_exit);
> +
> +MODULE_DESCRIPTION("ARM Live Firmware Activation (LFA)");
> +MODULE_LICENSE("GPL");
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA)
2026-03-13 9:46 ` Nirmoy Das
@ 2026-03-13 14:39 ` Andre Przywara
0 siblings, 0 replies; 18+ messages in thread
From: Andre Przywara @ 2026-03-13 14:39 UTC (permalink / raw)
To: Nirmoy Das, Salman Nabi, vvidwans, sudeep.holla, mark.rutland,
lpieralisi
Cc: ardb, chao.gao, linux-arm-kernel, linux-coco, linux-kernel,
sdonthineni, vsethi, vwadekar
Hi Nirmoy,
On 3/13/26 10:46, Nirmoy Das wrote:
> Hi Salman and Andre,
>
>
> We found an bug while testing LFA. See below:
>
> On 19.01.26 14:27, Salman Nabi wrote:
>> 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.
[ .... ]
>> +
>> + update_fw_images_tree();
>> +
>> + /*
>> + * Removing non-valid image directories at the end of an activation.
>> + * We can't remove the sysfs attributes while in the respective
>> + * _store() handler, so have to postpone the list removal to a
>> + * workqueue.
>> + */
>> + INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
>
>
> This can get invoke multiple times so re-initializing a work item that
> may already be queued or running
>
> is unsafe. This should be moved to lfa_init() so it is only called once.
> I suggest:
Ah, good point, thanks for spotting and reporting. Will fold this into
the next post!
Cheers,
Andre
>
> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/
> lfa_fw.c
> index 90727a66e49a5..135358113104c 100644
> --- a/drivers/firmware/smccc/lfa_fw.c
> +++ b/drivers/firmware/smccc/lfa_fw.c
> @@ -653,7 +653,6 @@ static int update_fw_images_tree(void)
> * _store() handler, so have to postpone the list removal to a
> * workqueue.
> */
> - INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
> queue_work(fw_images_update_wq, &fw_images_update_work);
>
> return 0;
> @@ -680,7 +679,7 @@ static void lfa_notify_handler(acpi_handle handle,
> u32 event, void *data)
> * of all activable and pending images.
> */
> do {
> - /* Reset activable image flag */
> + flush_workqueue(fw_images_update_wq);
> found_activable_image = false;
> list_for_each_entry(attrs, &lfa_fw_images, image_node) {
> if (attrs->fw_seq_id == -1)
> @@ -782,6 +781,8 @@ static int __init lfa_init(void)
> return -ENOMEM;
> }
>
> + INIT_WORK(&fw_images_update_work, remove_invalid_fw_images);
> +
> pr_info("Live Firmware Activation: detected v%ld.%ld\n",
> reg.a0 >> 16, reg.a0 & 0xffff);
>
>
> Regards,
>
> Nirmoy
>
>> + queue_work(fw_images_update_wq, &fw_images_update_work);
>> + mutex_unlock(&lfa_lock);
>> +
>> + return ret;
>> +}
>> +
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-03-13 14:39 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-19 12:27 [PATCH 0/1] Arm Live Firmware Activation (LFA) support Salman Nabi
2026-01-19 12:27 ` [PATCH 1/1] firmware: smccc: add support for Live Firmware Activation (LFA) Salman Nabi
2026-01-19 16:29 ` kernel test robot
2026-01-19 16:41 ` Andre Przywara
2026-01-19 18:41 ` kernel test robot
2026-01-19 21:35 ` kernel test robot
2026-01-27 23:01 ` Vedashree Vidwans
2026-01-29 17:26 ` Andre Przywara
2026-01-30 19:29 ` Vedashree Vidwans
2026-01-30 20:31 ` Andre Przywara
2026-01-31 1:35 ` Trilok Soni
2026-02-02 15:52 ` Salman Nabi
2026-02-06 6:37 ` Trilok Soni
2026-02-10 22:20 ` Vedashree Vidwans
2026-02-20 17:39 ` Andre Przywara
2026-02-20 21:48 ` Trilok Soni
2026-03-13 9:46 ` Nirmoy Das
2026-03-13 14:39 ` Andre Przywara
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox