From 3b2a980404505e9c2460532e23958b15cbf345bd Mon Sep 17 00:00:00 2001 From: Salman Nabi Date: Fri, 26 Sep 2025 12:31:45 +0000 Subject: [PATCH 1/7] NVIDIA: VR: SAUCE: firmware: smccc: Add support for Live Firmware Activation (LFA) 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. The other files reflect the status of the firmware image, as reported by the LFA agent, and described in the LFA spec. "force_cpu_rendezvous" is a boolean flag that controls the CPU rendezvous behaviour. Normally when activating a firmware the kernel will gather all CPUs to avoid them calling firmware services during the activation. As this is disruptive and might not be necessary, depending on the nature of the firmware, setting this flag to 0 will let the kernel skip the stop_machine() call for the activation. ========================================== /sys/firmware/lfa # ls -l . 6c* .: total 0 drwxr-xr-x 2 0 0 0 May 30 13:31 47d4086d-4cfe-9846-9b95-2950cbbd5a00 drwxr-xr-x 2 0 0 0 May 30 13:31 6c0762a6-12f2-4b56-92cb-ba8f633606d9 drwxr-xr-x 2 0 0 0 May 30 13:31 d6d0eea7-fcea-d54b-9782-9934f234b6e4 6c0762a6-12f2-4b56-92cb-ba8f633606d9: total 0 --w------- 1 0 0 4096 May 30 13:31 activate -r--r--r-- 1 0 0 4096 May 30 13:31 activation_capable -r--r--r-- 1 0 0 4096 May 30 13:31 activation_pending --w------- 1 0 0 4096 May 30 13:31 cancel -r--r--r-- 1 0 0 4096 May 30 13:31 cpu_rendezvous -rw-r--r-- 1 0 0 4096 May 30 13:31 force_cpu_rendezvous -r--r--r-- 1 0 0 4096 May 30 13:31 may_reset_cpu -r--r--r-- 1 0 0 4096 May 30 13:31 name /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 force_cpu_rendezvous:1 may_reset_cpu:0 name:rmm /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 [Andre: add actual activation routine, cleanups] Signed-off-by: Andre Przywara (backported from https://lore.kernel.org/lkml/20250926123145.268728-1-salman.nabi@arm.com/) Signed-off-by: Jamie Nguyen --- drivers/firmware/smccc/Kconfig | 7 + drivers/firmware/smccc/Makefile | 1 + drivers/firmware/smccc/lfa_fw.c | 455 ++++++++++++++++++++++++++++++++ 3 files changed, 463 insertions(+) create mode 100644 drivers/firmware/smccc/lfa_fw.c diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig index 15e7466179a62..48b98c14f7700 100644 --- a/drivers/firmware/smccc/Kconfig +++ b/drivers/firmware/smccc/Kconfig @@ -23,3 +23,10 @@ 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 + 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 40d19144a8607..a6dd01558a94a 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 0000000000000..1f333237271d8 --- /dev/null +++ b/drivers/firmware/smccc/lfa_fw.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Arm Limited + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LFA_ERROR_STRING(name) \ + [name] = #name +#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) + +/* 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 + +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_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; + 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 = "BL31 runtime", + .uuid = "47d4086d-4cfe-9846-9b95-2950cbbd5a00", + }, + { + .name = "BL33 non-secure payload", + .uuid = "d6d0eea7-fcea-d54b-9782-9934f234b6e4", + }, + { + .name = "RMM", + .uuid = "6c0762a6-12f2-4b56-92cb-ba8f633606d9", + }, +}; + +static struct kobject *lfa_dir; + +static int get_nr_lfa_components(void) +{ + struct arm_smccc_res res = { 0 }; + + arm_smccc_1_1_invoke(LFA_1_0_FN_GET_INFO, 0x0, &res); + if (res.a0 != LFA_SUCCESS) + return res.a0; + + return res.a1; +} + +static int call_lfa_activate(void *data) +{ + struct image_props *attrs = data; + struct arm_smccc_res res = { 0 }; + + do { + /* + * As we do not support updates requiring a CPU reset (yet), + * we pass 0 in x3 and x4, holding the entry point and context + * ID respectively. + * We want to force CPU rendezvous if either cpu_rendezvous or + * cpu_rendezvous_forced is set. The flag value is flipped as + * it is called skip_cpu_rendezvous in the spec. + */ + arm_smccc_1_1_invoke(LFA_1_0_FN_ACTIVATE, attrs->fw_seq_id, + !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous), + 0, 0, &res); + } while (res.a0 == 0 && res.a1 == 1); + + return res.a0; +} + +static int activate_fw_image(struct image_props *attrs) +{ + struct arm_smccc_res res = { 0 }; + int ret; + + /* + * LFA_PRIME/ACTIVATE will return 1 in res.a1 if the firmware + * priming/activation is still in progress. In that case + * LFA_PRIME/ACTIVATE will need to be called again. + * res.a1 will become 0 once the prime/activate process completes. + */ + do { + arm_smccc_1_1_invoke(LFA_1_0_FN_PRIME, attrs->fw_seq_id, &res); + if (res.a0 != LFA_SUCCESS) { + pr_err("LFA_PRIME failed: %s\n", + lfa_error_strings[-res.a0]); + + return res.a0; + } + } while (res.a1 == 1); + + if (attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous) + ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask); + else + ret = call_lfa_activate(attrs); + + return ret; +} + +static void set_image_flags(struct image_props *attrs, int seq_id, + u32 image_flags) +{ + attrs->fw_seq_id = seq_id; + 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 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_res res = { 0 }; + + /* + * Activation pending status can change anytime thus we need to update + * and return its current value + */ + arm_smccc_1_1_invoke(LFA_1_0_FN_GET_INVENTORY, attrs->fw_seq_id, &res); + if (res.a0 == LFA_SUCCESS) + attrs->activation_pending = !!(res.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 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; + + if (attrs->may_reset_cpu) { + pr_err("Firmware component requires unsupported CPU reset\n"); + + return -EINVAL; + } + + 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"); + + /* TODO: refresh image flags here*/ + 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]); + struct arm_smccc_res res = { 0 }; + + arm_smccc_1_1_invoke(LFA_1_0_FN_CANCEL, attrs->fw_seq_id, &res); + + /* + * 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 (res.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[-res.a0]); + return -EINVAL; + } + + return count; +} + +static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = { + [LFA_ATTR_NAME] = __ATTR_RO(name), + [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) { + kobject_put(attrs->image_dir); + list_del(&attrs->image_node); + kfree(attrs); + } +} + +static int create_fw_inventory(char *fw_uuid, int seq_id, u32 image_flags) +{ + const char *image_name = "(unknown)"; + struct image_props *attrs; + int ret; + + 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); + + /* + * 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 create_fw_images_tree(void) +{ + struct arm_smccc_res res = { 0 }; + struct uuid_regs image_uuid; + char image_id_str[40]; + int ret, num_of_components; + + num_of_components = get_nr_lfa_components(); + for (int i = 0; i < num_of_components; i++) { + arm_smccc_1_1_invoke(LFA_1_0_FN_GET_INVENTORY, i, &res); + if (res.a0 == LFA_SUCCESS) { + image_uuid.uuid_lo = res.a1; + image_uuid.uuid_hi = res.a2; + + snprintf(image_id_str, sizeof(image_id_str), "%pUb", + &image_uuid); + ret = create_fw_inventory(image_id_str, i, res.a3); + if (ret) + return ret; + } + } + + return 0; +} + +static int __init lfa_init(void) +{ + struct arm_smccc_res res = { 0 }; + int err; + + arm_smccc_1_1_invoke(LFA_1_0_FN_GET_VERSION, &res); + if (res.a0 == -LFA_NOT_SUPPORTED) { + pr_err("Arm Live Firmware activation(LFA): no firmware agent found\n"); + return -ENODEV; + } + + pr_info("Arm Live Firmware Activation (LFA): detected v%ld.%ld\n", + res.a0 >> 16, res.a0 & 0xffff); + + lfa_dir = kobject_create_and_add("lfa", firmware_kobj); + if (!lfa_dir) + return -ENOMEM; + + err = create_fw_images_tree(); + if (err != 0) + kobject_put(lfa_dir); + + return err; +} +module_init(lfa_init); + +static void __exit lfa_exit(void) +{ + clean_fw_images_tree(); + kobject_put(lfa_dir); +} +module_exit(lfa_exit); + +MODULE_DESCRIPTION("ARM Live Firmware Activation (LFA)"); +MODULE_LICENSE("GPL"); From 28034bf0858691332ff37bfd91fe03f78bec6293 Mon Sep 17 00:00:00 2001 From: Vedashree Vidwans Date: Wed, 8 Oct 2025 17:19:45 +0000 Subject: [PATCH 2/7] NVIDIA: VR: SAUCE: firmware: smccc: LFA: use smcc 1.2 Update driver to use SMCCC 1.2+ version as mentioned in the LFA spec. Signed-off-by: Vedashree Vidwans (backported from https://lore.kernel.org/linux-arm-kernel/20251208221319.1524888-1-vvidwans@nvidia.com/) Signed-off-by: Jamie Nguyen --- drivers/firmware/smccc/lfa_fw.c | 102 ++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index 1f333237271d8..bdde14b666063 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -117,32 +117,38 @@ static struct kobject *lfa_dir; static int get_nr_lfa_components(void) { - struct arm_smccc_res res = { 0 }; + struct arm_smccc_1_2_regs reg = { 0 }; - arm_smccc_1_1_invoke(LFA_1_0_FN_GET_INFO, 0x0, &res); - if (res.a0 != LFA_SUCCESS) - return res.a0; + reg.a0 = LFA_1_0_FN_GET_INFO; + reg.a1 = 0; /* lfa_info_selector = 0 */ - return res.a1; + arm_smccc_1_2_invoke(®, ®); + if (reg.a0 != LFA_SUCCESS) + return reg.a0; + + return reg.a1; } static int call_lfa_activate(void *data) { struct image_props *attrs = data; - struct arm_smccc_res res = { 0 }; + struct arm_smccc_1_2_regs args = { 0 }; + struct arm_smccc_1_2_regs res = { 0 }; + + args.a0 = LFA_1_0_FN_ACTIVATE; + args.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 args.a3 and args.a4, holding the entry point and context + * ID respectively. + * We want to force CPU rendezvous if either cpu_rendezvous or + * cpu_rendezvous_forced is set. The flag value is flipped as + * it is called skip_cpu_rendezvous in the spec. + */ + args.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous); do { - /* - * As we do not support updates requiring a CPU reset (yet), - * we pass 0 in x3 and x4, holding the entry point and context - * ID respectively. - * We want to force CPU rendezvous if either cpu_rendezvous or - * cpu_rendezvous_forced is set. The flag value is flipped as - * it is called skip_cpu_rendezvous in the spec. - */ - arm_smccc_1_1_invoke(LFA_1_0_FN_ACTIVATE, attrs->fw_seq_id, - !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous), - 0, 0, &res); + arm_smccc_1_2_invoke(&args, &res); } while (res.a0 == 0 && res.a1 == 1); return res.a0; @@ -150,7 +156,8 @@ static int call_lfa_activate(void *data) static int activate_fw_image(struct image_props *attrs) { - struct arm_smccc_res res = { 0 }; + struct arm_smccc_1_2_regs args = { 0 }; + struct arm_smccc_1_2_regs res = { 0 }; int ret; /* @@ -159,8 +166,10 @@ static int activate_fw_image(struct image_props *attrs) * LFA_PRIME/ACTIVATE will need to be called again. * res.a1 will become 0 once the prime/activate process completes. */ + args.a0 = LFA_1_0_FN_PRIME; + args.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */ do { - arm_smccc_1_1_invoke(LFA_1_0_FN_PRIME, attrs->fw_seq_id, &res); + arm_smccc_1_2_invoke(&args, &res); if (res.a0 != LFA_SUCCESS) { pr_err("LFA_PRIME failed: %s\n", lfa_error_strings[-res.a0]); @@ -211,15 +220,17 @@ static ssize_t activation_pending_show(struct kobject *kobj, { struct image_props *attrs = container_of(attr, struct image_props, image_attrs[LFA_ATTR_ACT_PENDING]); - struct arm_smccc_res res = { 0 }; + struct arm_smccc_1_2_regs reg = { 0 }; /* * Activation pending status can change anytime thus we need to update * and return its current value */ - arm_smccc_1_1_invoke(LFA_1_0_FN_GET_INVENTORY, attrs->fw_seq_id, &res); - if (res.a0 == LFA_SUCCESS) - attrs->activation_pending = !!(res.a3 & BIT(1)); + 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); } @@ -298,21 +309,23 @@ static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr, { struct image_props *attrs = container_of(attr, struct image_props, image_attrs[LFA_ATTR_CANCEL]); - struct arm_smccc_res res = { 0 }; + struct arm_smccc_1_2_regs reg = { 0 }; - arm_smccc_1_1_invoke(LFA_1_0_FN_CANCEL, attrs->fw_seq_id, &res); + 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 (res.a0 == LFA_SUCCESS) { + 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[-res.a0]); + lfa_error_strings[-reg.a0]); return -EINVAL; } @@ -395,21 +408,24 @@ static int create_fw_inventory(char *fw_uuid, int seq_id, u32 image_flags) static int create_fw_images_tree(void) { - struct arm_smccc_res res = { 0 }; + 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(); + for (int i = 0; i < num_of_components; i++) { - arm_smccc_1_1_invoke(LFA_1_0_FN_GET_INVENTORY, i, &res); - if (res.a0 == LFA_SUCCESS) { - image_uuid.uuid_lo = res.a1; - image_uuid.uuid_hi = res.a2; + 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 = create_fw_inventory(image_id_str, i, res.a3); + ret = create_fw_inventory(image_id_str, i, reg.a3); if (ret) return ret; } @@ -420,17 +436,29 @@ static int create_fw_images_tree(void) static int __init lfa_init(void) { - struct arm_smccc_res res = { 0 }; + struct arm_smccc_1_2_regs reg = { 0 }; int err; - arm_smccc_1_1_invoke(LFA_1_0_FN_GET_VERSION, &res); - if (res.a0 == -LFA_NOT_SUPPORTED) { + /* LFA requires SMCCC version >= 1.2 */ + if (arm_smccc_get_version() < ARM_SMCCC_VERSION_1_2) { + pr_err("Not supported with SMCCC version %u", arm_smccc_get_version()); + return -ENODEV; + } + + if (arm_smccc_1_1_get_conduit() == SMCCC_CONDUIT_NONE) { + pr_err("Invalid SMCCC conduit"); + return -ENODEV; + } + + reg.a0 = LFA_1_0_FN_GET_VERSION; + arm_smccc_1_2_invoke(®, ®); + if (reg.a0 == -LFA_NOT_SUPPORTED) { pr_err("Arm Live Firmware activation(LFA): no firmware agent found\n"); return -ENODEV; } pr_info("Arm Live Firmware Activation (LFA): detected v%ld.%ld\n", - res.a0 >> 16, res.a0 & 0xffff); + reg.a0 >> 16, reg.a0 & 0xffff); lfa_dir = kobject_create_and_add("lfa", firmware_kobj); if (!lfa_dir) From ffae5ec4731593b6c22f7047440b53fbcb48e488 Mon Sep 17 00:00:00 2001 From: Vedashree Vidwans Date: Tue, 11 Nov 2025 00:29:09 +0000 Subject: [PATCH 3/7] NVIDIA: VR: SAUCE: firmware: smccc: LFA: refactor - Refactor LFA CANCEL logic into independent lfa_cancel() function. - Use FW UUID as image_name for images not known by the driver. - Move may_reset_cpu check to activate_fw_image(). This keeps all the functionality within a function. Signed-off-by: Vedashree Vidwans (backported from https://lore.kernel.org/linux-arm-kernel/20251208221319.1524888-1-vvidwans@nvidia.com/) Signed-off-by: Jamie Nguyen --- drivers/firmware/smccc/lfa_fw.c | 64 ++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index bdde14b666063..df8b65324413e 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -129,6 +129,31 @@ static int get_nr_lfa_components(void) return reg.a1; } +static int lfa_cancel(struct image_props *attrs) +{ + 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; @@ -160,6 +185,11 @@ static int activate_fw_image(struct image_props *attrs) struct arm_smccc_1_2_regs res = { 0 }; int ret; + if (attrs->may_reset_cpu) { + pr_err("CPU reset not supported by kernel driver\n"); + return -EINVAL; + } + /* * LFA_PRIME/ACTIVATE will return 1 in res.a1 if the firmware * priming/activation is still in progress. In that case @@ -284,12 +314,6 @@ static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr, image_attrs[LFA_ATTR_ACTIVATE]); int ret; - if (attrs->may_reset_cpu) { - pr_err("Firmware component requires unsupported CPU reset\n"); - - return -EINVAL; - } - ret = activate_fw_image(attrs); if (ret) { pr_err("Firmware activation failed: %s\n", @@ -309,25 +333,11 @@ static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr, { struct image_props *attrs = container_of(attr, struct image_props, image_attrs[LFA_ATTR_CANCEL]); - 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(®, ®); + int ret; - /* - * 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; - } + ret = lfa_cancel(attrs); + if (ret != 0) + return ret; return count; } @@ -367,6 +377,8 @@ static int create_fw_inventory(char *fw_uuid, int seq_id, u32 image_flags) 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; + else + image_name = fw_uuid; } attrs->image_dir = kobject_create_and_add(fw_uuid, lfa_dir); @@ -414,6 +426,10 @@ static int create_fw_images_tree(void) 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"); + return -ENODEV; + } for (int i = 0; i < num_of_components; i++) { reg.a0 = LFA_1_0_FN_GET_INVENTORY; From 32a9263f414291e75db35e667801f8704931ed35 Mon Sep 17 00:00:00 2001 From: Vedashree Vidwans Date: Thu, 13 Nov 2025 08:17:35 +0000 Subject: [PATCH 4/7] NVIDIA: VR: SAUCE: firmware: smccc: add timeout, touch wdt Enhance PRIME/ACTIVATION functions to touch watchdog and implement timeout mechanism. This update ensures that any potential hangs are detected promptly and that the LFA process is allocated sufficient execution time before the watchdog timer expires. These changes improve overall system reliability by reducing the risk of undetected process stalls and unexpected watchdog resets. Signed-off-by: Vedashree Vidwans (backported from https://lore.kernel.org/linux-arm-kernel/20251208221319.1524888-1-vvidwans@nvidia.com/) Signed-off-by: Jamie Nguyen --- drivers/firmware/smccc/lfa_fw.c | 74 +++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index df8b65324413e..0e420cefa260e 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -16,6 +16,9 @@ #include #include #include +#include +#include +#include #define LFA_ERROR_STRING(name) \ [name] = #name @@ -34,6 +37,18 @@ #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) + +/* Prime loop limits, TODO: tune after testing */ +#define LFA_PRIME_BUDGET_US 30000000 /* 30s cap */ +#define LFA_PRIME_POLL_DELAY_US 10 /* 10us between polls */ + +/* Activation loop limits, TODO: tune after testing */ +#define LFA_ACTIVATE_BUDGET_US 20000000 /* 20s cap */ +#define LFA_ACTIVATE_POLL_DELAY_US 10 /* 10us between polls */ + /* LFA return values */ #define LFA_SUCCESS 0 #define LFA_NOT_SUPPORTED 1 @@ -159,6 +174,8 @@ static int call_lfa_activate(void *data) struct image_props *attrs = data; struct arm_smccc_1_2_regs args = { 0 }; struct arm_smccc_1_2_regs res = { 0 }; + ktime_t end = ktime_add_us(ktime_get(), LFA_ACTIVATE_BUDGET_US); + int ret; args.a0 = LFA_1_0_FN_ACTIVATE; args.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */ @@ -172,9 +189,34 @@ static int call_lfa_activate(void *data) */ args.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous); - do { + for (;;) { + /* Touch watchdog, ACTIVATE shouldn't take longer than watchdog_thresh */ + touch_nmi_watchdog(); arm_smccc_1_2_invoke(&args, &res); - } while (res.a0 == 0 && res.a1 == 1); + + if ((long)res.a0 < 0) { + pr_err("ACTIVATE for image %s failed: %s", + attrs->image_name, lfa_error_strings[-res.a0]); + return res.a0; + } + + /* SMC returned with success */ + if (!(res.a1 & LFA_ACTIVATE_CALL_AGAIN)) + break; /* ACTIVATE successful */ + + /* SMC returned with call_again flag set */ + if (ktime_before(ktime_get(), end)) { + udelay(LFA_ACTIVATE_POLL_DELAY_US); + continue; + } + + pr_err("ACTIVATE timed out for image %s", attrs->image_name); + ret = lfa_cancel(attrs); + if (ret == 0) + return -ETIMEDOUT; + else + return ret; + } return res.a0; } @@ -183,6 +225,7 @@ static int activate_fw_image(struct image_props *attrs) { struct arm_smccc_1_2_regs args = { 0 }; struct arm_smccc_1_2_regs res = { 0 }; + ktime_t end = ktime_add_us(ktime_get(), LFA_PRIME_BUDGET_US); int ret; if (attrs->may_reset_cpu) { @@ -198,15 +241,32 @@ static int activate_fw_image(struct image_props *attrs) */ args.a0 = LFA_1_0_FN_PRIME; args.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */ - do { + for (;;) { + /* Touch watchdog, PRIME shouldn't take longer than watchdog_thresh */ + touch_nmi_watchdog(); arm_smccc_1_2_invoke(&args, &res); - if (res.a0 != LFA_SUCCESS) { - pr_err("LFA_PRIME failed: %s\n", - lfa_error_strings[-res.a0]); + if ((long)res.a0 < 0) { + pr_err("LFA_PRIME for image %s failed: %s\n", + attrs->image_name, lfa_error_strings[-res.a0]); return res.a0; } - } while (res.a1 == 1); + if (!(res.a1 & LFA_PRIME_CALL_AGAIN)) + break; /* PRIME successful */ + + /* SMC returned with call_again flag set */ + if (ktime_before(ktime_get(), end)) { + udelay(LFA_PRIME_POLL_DELAY_US); + continue; + } + + pr_err("PRIME timed out for image %s", attrs->image_name); + ret = lfa_cancel(attrs); + if (ret == 0) + return -ETIMEDOUT; + else + return ret; + } if (attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous) ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask); From 2d330dc3ef5f3b325837e5c577c0aa9158590a18 Mon Sep 17 00:00:00 2001 From: Vedashree Vidwans Date: Thu, 13 Nov 2025 08:52:13 +0000 Subject: [PATCH 5/7] NVIDIA: VR: SAUCE: firmware: smccc: register as platform driver - Update driver to be in-built kernel module. This will ensure driver is installed in kernel and would not require any user intervention. - Register the LFA driver as a platform driver corresponding to 'armhf000' device. The driver will be invoked when the device is detected on a platform. - Add functionality to register LFA interrupt in the driver probe(). This LFA IRQ number will be retrived from the LFA device node. - On IRQ, driver will query FW component details and trigger activation of capable and pending FW component. The driver will loop to update FW component details after every successful FW component activation. - Mutex synchronization is implemented to avoid concurrent LFA updates through interrupt and sysfs interfaces. Device node snippet from LFA spec[1]: fwu0 { compatible = "arm,armhf000"; memory-region = <&fwu_payload>; interrupt-parent = <&ic>; interrupts = <0 100 1>; // SPI, Interrupt #100, Edge Rising }; [1] https://developer.arm.com/documentation/den0147/latest/ Signed-off-by: Vedashree Vidwans (backported from https://lore.kernel.org/linux-arm-kernel/20251208221319.1524888-1-vvidwans@nvidia.com/) [jamien: remove unnecessary __init from lfa_probe to avoid warning] Signed-off-by: Jamie Nguyen --- drivers/firmware/smccc/Kconfig | 3 +- drivers/firmware/smccc/lfa_fw.c | 124 +++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig index 48b98c14f7700..c21be43fbfed4 100644 --- a/drivers/firmware/smccc/Kconfig +++ b/drivers/firmware/smccc/Kconfig @@ -25,8 +25,9 @@ config ARM_SMCCC_SOC_ID platforms providing some sysfs information about the SoC variant. config ARM_LFA - tristate "Arm Live Firmware activation support" + bool "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/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index 0e420cefa260e..c0c6f0538a325 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -19,7 +19,12 @@ #include #include #include +#include +#include +#include +#include +#define DRIVER_NAME "ARM_LFA" #define LFA_ERROR_STRING(name) \ [name] = #name #undef pr_fmt @@ -129,6 +134,7 @@ static const struct fw_image_uuid { }; static struct kobject *lfa_dir; +static DEFINE_MUTEX(lfa_lock); static int get_nr_lfa_components(void) { @@ -374,17 +380,23 @@ static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr, image_attrs[LFA_ATTR_ACTIVATE]); int ret; + if (!mutex_trylock(&lfa_lock)) { + pr_err("Mutex locked, try again"); + return -EAGAIN; + } + ret = activate_fw_image(attrs); if (ret) { pr_err("Firmware activation failed: %s\n", lfa_error_strings[-ret]); - + mutex_unlock(&lfa_lock); return -ECANCELED; } pr_info("Firmware activation succeeded\n"); /* TODO: refresh image flags here*/ + mutex_unlock(&lfa_lock); return count; } @@ -510,6 +522,106 @@ static int create_fw_images_tree(void) return 0; } +static irqreturn_t lfa_irq_thread(int irq, void *data) +{ + struct image_props *attrs = NULL; + int ret; + int num_of_components, curr_component; + + mutex_lock(&lfa_lock); + + /* + * As per LFA spec, after activation of a component, the caller + * is expected to re-enumerate the component states (using + * LFA_GET_INFO then LFA_GET_INVENTORY). + * Hence we need an unconditional loop. + */ + + do { + /* TODO: refresh image flags here */ + /* If refresh fails goto exit_unlock */ + + /* Initialize counters to track list traversal */ + num_of_components = get_nr_lfa_components(); + curr_component = 0; + + /* Execute PRIME and ACTIVATE for activable FW component */ + list_for_each_entry(attrs, &lfa_fw_images, image_node) { + curr_component++; + if ((!attrs->activation_capable) || (!attrs->activation_pending)) { + /* LFA not applicable for this FW component */ + continue; + } + + ret = activate_fw_image(attrs); + if (ret) { + pr_err("Firmware %s activation failed: %s\n", + attrs->image_name, lfa_error_strings[-ret]); + goto exit_unlock; + } + + pr_info("Firmware %s activation succeeded", attrs->image_name); + /* Refresh FW component details */ + break; + } + } while (curr_component < num_of_components); + + /* TODO: refresh image flags here */ + /* If refresh fails goto exit_unlock */ + +exit_unlock: + mutex_unlock(&lfa_lock); + return IRQ_HANDLED; +} + +static int lfa_probe(struct platform_device *pdev) +{ + int err; + unsigned int irq; + + err = platform_get_irq_byname_optional(pdev, "fw-store-updated-interrupt"); + if (err < 0) + err = platform_get_irq(pdev, 0); + if (err < 0) { + pr_err("Interrupt not found, functionality will be unavailable."); + + /* Bail out without failing the driver. */ + return 0; + } + irq = err; + + err = request_threaded_irq(irq, NULL, lfa_irq_thread, IRQF_ONESHOT, DRIVER_NAME, NULL); + if (err != 0) { + pr_err("Interrupt setup failed, functionality will be unavailable."); + + /* Bail out without failing the driver. */ + return 0; + } + + return 0; +} + +static const struct of_device_id lfa_of_ids[] = { + { .compatible = "arm,armhf000", }, + { }, +}; +MODULE_DEVICE_TABLE(of, lfa_of_ids); + +static const struct acpi_device_id lfa_acpi_ids[] = { + {"ARMHF000"}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, lfa_acpi_ids); + +static struct platform_driver lfa_driver = { + .probe = lfa_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = lfa_of_ids, + .acpi_match_table = ACPI_PTR(lfa_acpi_ids), + }, +}; + static int __init lfa_init(void) { struct arm_smccc_1_2_regs reg = { 0 }; @@ -536,22 +648,32 @@ static int __init lfa_init(void) pr_info("Arm Live Firmware Activation (LFA): detected v%ld.%ld\n", reg.a0 >> 16, reg.a0 & 0xffff); + err = platform_driver_register(&lfa_driver); + if (err < 0) + pr_err("Platform driver register failed"); + lfa_dir = kobject_create_and_add("lfa", firmware_kobj); if (!lfa_dir) return -ENOMEM; + mutex_lock(&lfa_lock); err = create_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) { + mutex_lock(&lfa_lock); clean_fw_images_tree(); + mutex_unlock(&lfa_lock); + kobject_put(lfa_dir); + platform_driver_unregister(&lfa_driver); } module_exit(lfa_exit); From 6a27418a36fbd3ad4d15008dbdad29d48712f088 Mon Sep 17 00:00:00 2001 From: Vedashree Vidwans Date: Tue, 2 Dec 2025 16:24:32 +0000 Subject: [PATCH 6/7] NVIDIA: VR: SAUCE: firmware: smccc: lfa: refresh fw details FW image details are querried with a SMC call. Currently, these FW details are added as nodes in a linked list. This patch updates the FW node creation and update functions. Now the linked list is updated based on the current value of num_lfa_components. As per spec [1], FW inventory is updated after each activation. [1] https://developer.arm.com/documentation/den0147/latest/ Signed-off-by: Vedashree Vidwans (backported from https://lore.kernel.org/linux-arm-kernel/20251208221319.1524888-1-vvidwans@nvidia.com/) [jamien: remove unnecessary loop initializer in update_fw_images_tree() to avoid warning] Signed-off-by: Jamie Nguyen --- drivers/firmware/smccc/lfa_fw.c | 148 +++++++++++++++++++++----------- 1 file changed, 100 insertions(+), 48 deletions(-) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index c0c6f0538a325..1561a5ac2fc4e 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -133,6 +133,7 @@ static const struct fw_image_uuid { }, }; +static int update_fw_images_tree(void); static struct kobject *lfa_dir; static DEFINE_MUTEX(lfa_lock); @@ -282,17 +283,6 @@ static int activate_fw_image(struct image_props *attrs) return ret; } -static void set_image_flags(struct image_props *attrs, int seq_id, - u32 image_flags) -{ - attrs->fw_seq_id = seq_id; - 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 ssize_t name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { @@ -395,7 +385,9 @@ static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr, pr_info("Firmware activation succeeded\n"); - /* TODO: refresh image flags here*/ + ret = update_fw_images_tree(); + if (ret) + pr_err("Failed to update FW images tree"); mutex_unlock(&lfa_lock); return count; } @@ -425,20 +417,41 @@ static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = { [LFA_ATTR_CANCEL] = __ATTR_WO(cancel) }; +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); + attrs->image_dir = NULL; + list_del(&attrs->image_node); + kfree(attrs); +} + static void clean_fw_images_tree(void) { struct image_props *attrs, *tmp; - list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node) { - kobject_put(attrs->image_dir); - list_del(&attrs->image_node); - kfree(attrs); - } + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node) + delete_fw_image_node(attrs); +} + +static void update_fw_image_node(struct image_props *attrs, int seq_id, + char *fw_uuid, u32 image_flags) +{ + attrs->fw_seq_id = seq_id; + attrs->image_name = fw_uuid; + 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 int create_fw_inventory(char *fw_uuid, int seq_id, u32 image_flags) +static int create_fw_image_node(int seq_id, char *fw_uuid, u32 image_flags) { - const char *image_name = "(unknown)"; struct image_props *attrs; int ret; @@ -446,21 +459,12 @@ static int create_fw_inventory(char *fw_uuid, int seq_id, u32 image_flags) 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; - else - image_name = fw_uuid; - } - 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); + /* Update FW attributes */ + update_fw_image_node(attrs, seq_id, fw_uuid, image_flags); /* * The attributes for each sysfs file are constant (handler functions, @@ -485,17 +489,19 @@ static int create_fw_inventory(char *fw_uuid, int seq_id, u32 image_flags) return ret; } } - list_add(&attrs->image_node, &lfa_fw_images); + list_add_tail(&attrs->image_node, &lfa_fw_images); return ret; } -static int create_fw_images_tree(void) +static int update_fw_images_tree(void) { struct arm_smccc_1_2_regs reg = { 0 }; struct uuid_regs image_uuid; + struct image_props *attrs, *tmp; char image_id_str[40]; int ret, num_of_components; + int node_idx = 0; num_of_components = get_nr_lfa_components(); if (num_of_components <= 0) { @@ -503,22 +509,67 @@ static int create_fw_images_tree(void) 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) { + /* + * Pass 1: + * For nodes < num_of_components, update fw_image_node + * For nodes >= num_of_components, delete + */ + list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node) { + if (attrs->fw_seq_id < num_of_components) { + /* Update this FW image node */ + + /* Get FW details */ + reg.a0 = LFA_1_0_FN_GET_INVENTORY; + reg.a1 = attrs->fw_seq_id; + arm_smccc_1_2_invoke(®, ®); + + if (reg.a0 != LFA_SUCCESS) + return -EINVAL; + + /* Build image name with UUID */ image_uuid.uuid_lo = reg.a1; image_uuid.uuid_hi = reg.a2; + snprintf(image_id_str, sizeof(image_id_str), "%pUb", &image_uuid); - snprintf(image_id_str, sizeof(image_id_str), "%pUb", - &image_uuid); - ret = create_fw_inventory(image_id_str, i, reg.a3); - if (ret) - return ret; + if (strcmp(attrs->image_name, image_id_str)) { + /* UUID doesn't match previous UUID for given FW, not expected */ + pr_err("FW seq id %u: Previous UUID %s doesn't match current %s", + attrs->fw_seq_id, attrs->image_name, image_id_str); + return -EINVAL; + } + + /* Update FW attributes */ + update_fw_image_node(attrs, attrs->fw_seq_id, image_id_str, reg.a3); + node_idx++; + } else { + /* This node is beyond valid FW components list */ + delete_fw_image_node(attrs); } } + /* + * Pass 2: + * If current FW components number is more than previous list, add new component nodes. + */ + for (; node_idx < num_of_components; node_idx++) { + /* Get FW details */ + reg.a0 = LFA_1_0_FN_GET_INVENTORY; + reg.a1 = node_idx; + arm_smccc_1_2_invoke(®, ®); + + if (reg.a0 != LFA_SUCCESS) + return -EINVAL; + + /* Build image name with UUID */ + image_uuid.uuid_lo = reg.a1; + image_uuid.uuid_hi = reg.a2; + snprintf(image_id_str, sizeof(image_id_str), "%pUb", &image_uuid); + + ret = create_fw_image_node(node_idx, image_id_str, reg.a3); + if (ret) + return ret; + } + return 0; } @@ -538,8 +589,9 @@ static irqreturn_t lfa_irq_thread(int irq, void *data) */ do { - /* TODO: refresh image flags here */ - /* If refresh fails goto exit_unlock */ + ret = update_fw_images_tree(); + if (ret) + goto exit_unlock; /* Initialize counters to track list traversal */ num_of_components = get_nr_lfa_components(); @@ -561,13 +613,13 @@ static irqreturn_t lfa_irq_thread(int irq, void *data) } pr_info("Firmware %s activation succeeded", attrs->image_name); - /* Refresh FW component details */ break; } } while (curr_component < num_of_components); - /* TODO: refresh image flags here */ - /* If refresh fails goto exit_unlock */ + ret = update_fw_images_tree(); + if (ret) + goto exit_unlock; exit_unlock: mutex_unlock(&lfa_lock); @@ -657,7 +709,7 @@ static int __init lfa_init(void) return -ENOMEM; mutex_lock(&lfa_lock); - err = create_fw_images_tree(); + err = update_fw_images_tree(); if (err != 0) kobject_put(lfa_dir); From ca18484459a01c5d08c1d6496eee4cb71ab7c2fa Mon Sep 17 00:00:00 2001 From: Jamie Nguyen Date: Tue, 13 Jan 2026 13:34:51 -0800 Subject: [PATCH 7/7] NVIDIA: VR: SAUCE: [Config] Enable ARM LFA support Enable Arm Live Firmware Activation support by setting CONFIG_ARM_LFA=y. Signed-off-by: Jamie Nguyen --- debian.nvidia-6.17/config/annotations | 1 + 1 file changed, 1 insertion(+) diff --git a/debian.nvidia-6.17/config/annotations b/debian.nvidia-6.17/config/annotations index 3624c02cf2034..26770c0f0a577 100644 --- a/debian.nvidia-6.17/config/annotations +++ b/debian.nvidia-6.17/config/annotations @@ -203,6 +203,7 @@ CONFIG_ARM64_MPAM_DRIVER_DEBUG policy<{'amd64': '-', 'arm64': ' CONFIG_ARM64_MPAM_RESCTRL_FS policy<{'arm64': 'y'}> CONFIG_ARM_CPU_RESCTRL policy<{'amd64': '-', 'arm64': '-'}> CONFIG_ARM_FFA_TRANSPORT policy<{'arm64': 'y'}> +CONFIG_ARM_LFA policy<{'arm64': 'y'}> CONFIG_ARM_SMMU_V3_IOMMUFD policy<{'arm64': 'y'}> CONFIG_AS_VERSION policy<{'amd64': '24200', 'arm64': '24200'}> CONFIG_AX88796B_RUST_PHY policy<{'amd64': '-', 'arm64': '-'}>