From a35a50125d09b0c3c1e834cc511cb4d9e6a60723 Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Mon, 20 Apr 2026 23:29:30 -0700 Subject: [PATCH 01/13] Revert "NVIDIA: VR: SAUCE: firmware: smccc: register as platform driver" This reverts commit f1ad1da2ead9f317331195fdba00aebc14408b76. Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/lfa_fw.c | 153 ++++---------------------------- 1 file changed, 19 insertions(+), 134 deletions(-) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index 90727a66e49a5..ed439daf49319 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -20,10 +20,7 @@ #include #include #include -#include -#include -#define DRIVER_NAME "ARM_LFA" #undef pr_fmt #define pr_fmt(fmt) "Arm LFA: " fmt @@ -287,7 +284,26 @@ static int activate_fw_image(struct image_props *attrs) 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; @@ -611,7 +627,6 @@ static int update_fw_images_tree(void) { struct arm_smccc_1_2_regs reg = { 0 }; struct uuid_regs image_uuid; - struct image_props *attrs; char image_id_str[40]; int ret, num_of_components; @@ -621,15 +636,6 @@ static int update_fw_images_tree(void) return -ENODEV; } - /* - * Invalidate fw_seq_ids (-1) for all images as the seq_ids and the - * number of firmware images in the LFA agent may change after a - * successful activation attempt. Negate all image flags as well. - */ - list_for_each_entry(attrs, &lfa_fw_images, image_node) { - set_image_flags(attrs, -1, 0b1000, 0, 0); - } - 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 */ @@ -647,121 +653,9 @@ static int update_fw_images_tree(void) } } - /* - * 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); - - return 0; -} - -#if defined(CONFIG_ACPI) -static void lfa_notify_handler(acpi_handle handle, u32 event, void *data) -{ - struct image_props *attrs = NULL; - int ret; - bool found_activable_image = false; - - /* Get latest FW inventory */ - mutex_lock(&lfa_lock); - ret = update_fw_images_tree(); - mutex_unlock(&lfa_lock); - if (ret != 0) { - pr_err("FW images tree update failed"); - return; - } - - /* - * Go through all FW images in a loop and trigger activation - * of all activable and pending images. - */ - do { - /* Reset activable image flag */ - found_activable_image = false; - list_for_each_entry(attrs, &lfa_fw_images, image_node) { - if (attrs->fw_seq_id == -1) - continue; /* Invalid FW component */ - - if ((!attrs->activation_capable) || (!attrs->activation_pending)) - continue; /* FW component is not activable */ - - /* - * Found an image that is activable. - * As the FW images tree is revised after activation, it is - * not ideal to invoke activation from inside - * list_for_each_entry() loop. - * So, set the flasg and exit loop. - */ - found_activable_image = true; - break; - } - - if (found_activable_image) { - ret = prime_fw_image(attrs); - if (ret) { - pr_err("Firmware prime failed: %s\n", - lfa_error_strings[-ret]); - return; - } - - ret = activate_fw_image(attrs); - if (ret) { - pr_err("Firmware activation failed: %s\n", - lfa_error_strings[-ret]); - return; - } - - pr_info("Firmware %s activation succeeded", attrs->image_name); - } - } while(found_activable_image); - - return; -} - -static int lfa_probe(struct platform_device *pdev) -{ - acpi_status status; - acpi_handle handle = ACPI_HANDLE(&pdev->dev); - if (!handle) - return -ENODEV; - - /* Register notify handler that indicates if LFA updates are available */ - status = acpi_install_notify_handler(handle, - ACPI_DEVICE_NOTIFY, lfa_notify_handler, pdev); - if (ACPI_FAILURE(status)) - return -EIO; - return 0; } -static void lfa_remove(struct platform_device *pdev) { - acpi_handle handle = ACPI_HANDLE(&pdev->dev); - - if (handle) - acpi_remove_notify_handler(handle, - ACPI_DEVICE_NOTIFY, lfa_notify_handler); -} - -static const struct acpi_device_id lfa_acpi_ids[] = { - {"ARML0003"}, - {}, -}; -MODULE_DEVICE_TABLE(acpi, lfa_acpi_ids); - -static struct platform_driver lfa_driver = { - .probe = lfa_probe, - .remove = lfa_remove, - .driver = { - .name = DRIVER_NAME, - .acpi_match_table = ACPI_PTR(lfa_acpi_ids), - }, -}; -#endif - static int __init lfa_init(void) { struct arm_smccc_1_2_regs reg = { 0 }; @@ -785,12 +679,6 @@ static int __init lfa_init(void) pr_info("Live Firmware Activation: detected v%ld.%ld\n", reg.a0 >> 16, reg.a0 & 0xffff); -#if defined(CONFIG_ACPI) - err = platform_driver_register(&lfa_driver); - if (err < 0) - pr_err("Platform driver register failed"); -#endif - lfa_dir = kobject_create_and_add("lfa", firmware_kobj); if (!lfa_dir) return -ENOMEM; @@ -815,9 +703,6 @@ static void __exit lfa_exit(void) mutex_unlock(&lfa_lock); kobject_put(lfa_dir); -#if defined(CONFIG_ACPI) - platform_driver_unregister(&lfa_driver); -#endif } module_exit(lfa_exit); From db87af12201842360a6cfd984d52bd58f2ad754c Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Mon, 20 Apr 2026 23:29:30 -0700 Subject: [PATCH 02/13] Revert "NVIDIA: VR: SAUCE: firmware: smccc: add timeout, touch wdt" This reverts commit e7616bcae74b5c2282379e77b56fc93265d10a9d. Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/lfa_fw.c | 82 +++++++++------------------------ 1 file changed, 21 insertions(+), 61 deletions(-) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index ed439daf49319..adf4171066437 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -17,9 +17,6 @@ #include #include #include -#include -#include -#include #undef pr_fmt #define pr_fmt(fmt) "Arm LFA: " fmt @@ -40,14 +37,6 @@ #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 @@ -228,12 +217,10 @@ static int lfa_cancel(void *data) 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); + struct arm_smccc_1_2_regs reg = { 0 }; - args.a0 = LFA_1_0_FN_ACTIVATE; - args.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */ + 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 @@ -241,32 +228,21 @@ static int call_lfa_activate(void *data) * cpu_rendezvous_forced is set by the administrator, via sysfs, * cpu_rendezvous is dictated by each firmware component. */ - args.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous); + reg.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous); for (;;) { - /* Touch watchdog, ACTIVATE shouldn't take longer than watchdog_thresh */ - touch_nmi_watchdog(); - arm_smccc_1_2_invoke(&args, &res); + arm_smccc_1_2_invoke(®, ®); - if ((long)res.a0 < 0) { + if ((long)reg.a0 < 0) { pr_err("ACTIVATE for image %s failed: %s\n", - attrs->image_name, lfa_error_strings[-res.a0]); - return res.a0; + attrs->image_name, lfa_error_strings[-reg.a0]); + return reg.a0; } - if (!(res.a1 & LFA_ACTIVATE_CALL_AGAIN)) + if (!(reg.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 for image %s timed out", attrs->image_name); - return -ETIMEDOUT; } - return res.a0; + return reg.a0; } static int activate_fw_image(struct image_props *attrs) @@ -311,9 +287,7 @@ static int activate_fw_image(struct image_props *attrs) static int prime_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); + struct arm_smccc_1_2_regs reg = { 0 }; int ret; mutex_lock(&lfa_lock); @@ -333,41 +307,27 @@ static int prime_fw_image(struct image_props *attrs) } /* - * LFA_PRIME/ACTIVATE will return 1 in res.a1 if the firmware + * 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. - * res.a1 will become 0 once the prime/activate process completes. + * reg.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 */ + reg.a0 = LFA_1_0_FN_PRIME; + reg.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */ for (;;) { - /* Touch watchdog, PRIME shouldn't take longer than watchdog_thresh */ - touch_nmi_watchdog(); - arm_smccc_1_2_invoke(&args, &res); + arm_smccc_1_2_invoke(®, ®); - if ((long)res.a0 < 0) { + if ((long)reg.a0 < 0) { pr_err("LFA_PRIME for image %s failed: %s\n", - attrs->image_name, lfa_error_strings[-res.a0]); + attrs->image_name, lfa_error_strings[-reg.a0]); mutex_unlock(&lfa_lock); - return res.a0; + return reg.a0; } - if (!(res.a1 & LFA_PRIME_CALL_AGAIN)) + if (!(reg.a1 & LFA_PRIME_CALL_AGAIN)) { + ret = 0; 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("LFA_PRIME for image %s timed out", attrs->image_name); - mutex_unlock(&lfa_lock); - - ret = lfa_cancel(attrs); - if (ret != 0) - return ret; - return -ETIMEDOUT; } mutex_unlock(&lfa_lock); From b02fd80fb04f893e3949e9215b66c799f7ab7cc9 Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Mon, 20 Apr 2026 23:29:30 -0700 Subject: [PATCH 03/13] Revert "NVIDIA: VR: SAUCE: firmware: smccc: add support for Live Firmware Activation (LFA)" This reverts commit dc371707478752e2627fe104a1424685fc1cb36f. Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/Kconfig | 8 - drivers/firmware/smccc/Makefile | 1 - drivers/firmware/smccc/lfa_fw.c | 670 -------------------------------- 3 files changed, 679 deletions(-) delete mode 100644 drivers/firmware/smccc/lfa_fw.c diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig index ff7ca49486b05..15e7466179a62 100644 --- a/drivers/firmware/smccc/Kconfig +++ b/drivers/firmware/smccc/Kconfig @@ -23,11 +23,3 @@ 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 a6dd01558a94a..40d19144a8607 100644 --- a/drivers/firmware/smccc/Makefile +++ b/drivers/firmware/smccc/Makefile @@ -2,4 +2,3 @@ # 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 deleted file mode 100644 index adf4171066437..0000000000000 --- a/drivers/firmware/smccc/lfa_fw.c +++ /dev/null @@ -1,670 +0,0 @@ -// 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 -#include - -#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; - 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, 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"); From 4ff148a79d1641af94c8e5a8ef787f6e7e3af631 Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Tue, 17 Mar 2026 11:33:27 +0100 Subject: [PATCH 04/13] NVIDIA: VR: SAUCE: dt-bindings: arm: Add Live Firmware Activation binding The Arm Live Firmware Activation spec [1] describes updating firmware images during runtime, without requiring a reboot. Update images might be deployed out-of-band, for instance via a BMC, in this case the OS needs to be notified about the availability of a new image. This binding describes an interrupt that could be triggered by the platform, to notify about any changes. [1] https://developer.arm.com/documentation/den0147/latest/ Signed-off-by: Andre Przywara (backported from https://lore.kernel.org/all/20260317103336.1273582-1-andre.przywara@arm.com/) Signed-off-by: Nirmoy Das --- .../devicetree/bindings/arm/arm,lfa.yaml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Documentation/devicetree/bindings/arm/arm,lfa.yaml diff --git a/Documentation/devicetree/bindings/arm/arm,lfa.yaml b/Documentation/devicetree/bindings/arm/arm,lfa.yaml new file mode 100644 index 0000000000000..92f0564fd672e --- /dev/null +++ b/Documentation/devicetree/bindings/arm/arm,lfa.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/arm/arm,lfa.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Arm Live Firmware Activation (LFA) + +maintainers: + - Andre Przywara + - Sudeep Holla + +description: + The Arm Live Firmware Activation (LFA) specification [1] describes a + firmware interface to activate an updated firmware at runtime, without + requiring a reboot. Updates might be supplied out-of-band, for instance + via a BMC, in which case the platform needs to notify an OS about pending + image updates. + [1] https://developer.arm.com/documentation/den0147/latest/ + +properties: + compatible: + const: arm,lfa + + interrupts: + maxItems: 1 + description: notification interrupt for changed firmware image status + +required: + - compatible + - interrupts + +additionalProperties: false + +examples: + - | + #include + + firmware { + arm-lfa { + compatible = "arm,lfa"; + interrupts = ; + }; + }; +... From 4c1ae5f56ba52e90384dd40501b9c93b1e1d1df2 Mon Sep 17 00:00:00 2001 From: Salman Nabi Date: Tue, 17 Mar 2026 11:33:28 +0100 Subject: [PATCH 05/13] 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: ========================================== /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 Signed-off-by: Andre Przywara (backported from https://lore.kernel.org/all/20260317103336.1273582-1-andre.przywara@arm.com/) Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/Kconfig | 10 + drivers/firmware/smccc/Makefile | 1 + drivers/firmware/smccc/lfa_fw.c | 721 ++++++++++++++++++++++++++++++++ 3 files changed, 732 insertions(+) create mode 100644 drivers/firmware/smccc/lfa_fw.c diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig index 15e7466179a62..7fd646d515f86 100644 --- a/drivers/firmware/smccc/Kconfig +++ b/drivers/firmware/smccc/Kconfig @@ -23,3 +23,13 @@ config ARM_SMCCC_SOC_ID help Include support for the SoC bus on the ARM SMCCC firmware based platforms providing some sysfs information about the SoC variant. + +config ARM_LFA + tristate "Arm Live Firmware activation support" + depends on HAVE_ARM_SMCCC_DISCOVERY && ARM64 + default y + help + Include support for triggering a Live Firmware Activation (LFA), + which allows to upgrade certain firmware components without a reboot. + This is described in the Arm DEN0147 specification, and relies on + a firmware agent running in EL3. diff --git a/drivers/firmware/smccc/Makefile b/drivers/firmware/smccc/Makefile index 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..284b7c18d3d09 --- /dev/null +++ b/drivers/firmware/smccc/lfa_fw.c @@ -0,0 +1,721 @@ +// 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 + +#include + +#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 + +/* + * Not error codes described by the spec, but used internally when + * PRIME/ACTIVATE calls return with the CALL_AGAIN bit set. + */ +#define LFA_TIMED_OUT 32 +#define LFA_CALL_AGAIN 33 + +#define LFA_ERROR_STRING(name) \ + [name] = #name + +static const char * const lfa_error_strings[] = { + LFA_ERROR_STRING(LFA_SUCCESS), + LFA_ERROR_STRING(LFA_NOT_SUPPORTED), + LFA_ERROR_STRING(LFA_BUSY), + LFA_ERROR_STRING(LFA_AUTH_ERROR), + LFA_ERROR_STRING(LFA_NO_MEMORY), + LFA_ERROR_STRING(LFA_CRITICAL_ERROR), + LFA_ERROR_STRING(LFA_DEVICE_ERROR), + LFA_ERROR_STRING(LFA_WRONG_STATE), + LFA_ERROR_STRING(LFA_INVALID_PARAMETERS), + LFA_ERROR_STRING(LFA_COMPONENT_WRONG_STATE), + LFA_ERROR_STRING(LFA_INVALID_ADDRESS), + LFA_ERROR_STRING(LFA_ACTIVATION_FAILED) +}; + +enum image_attr_names { + LFA_ATTR_NAME, + LFA_ATTR_CURRENT_VERSION, + LFA_ATTR_PENDING_VERSION, + LFA_ATTR_ACT_CAPABLE, + LFA_ATTR_ACT_PENDING, + LFA_ATTR_MAY_RESET_CPU, + LFA_ATTR_CPU_RENDEZVOUS, + LFA_ATTR_FORCE_CPU_RENDEZVOUS, + LFA_ATTR_ACTIVATE, + LFA_ATTR_CANCEL, + LFA_ATTR_NR_IMAGES +}; + +struct fw_image { + struct kobject kobj; + const char *image_name; + int fw_seq_id; + u64 current_version; + u64 pending_version; + bool activation_capable; + bool activation_pending; + bool may_reset_cpu; + bool cpu_rendezvous; + bool cpu_rendezvous_forced; + struct kobj_attribute image_attrs[LFA_ATTR_NR_IMAGES]; +}; + +static struct fw_image *kobj_to_fw_image(struct kobject *kobj) +{ + return container_of(kobj, struct fw_image, kobj); +} + +/* A UUID split over two 64-bit registers */ +struct uuid_regs { + u64 uuid_lo; + u64 uuid_hi; +}; + +/* A list of known GUIDs, to be shown in the "name" sysfs file. */ +static const struct fw_image_uuid { + const char *name; + const char *uuid; +} fw_images_uuids[] = { + { + .name = "TF-A BL31 runtime", + .uuid = "47d4086d-4cfe-9846-9b95-2950cbbd5a00", + }, + { + .name = "BL33 non-secure payload", + .uuid = "d6d0eea7-fcea-d54b-9782-9934f234b6e4", + }, + { + .name = "TF-RMM", + .uuid = "6c0762a6-12f2-4b56-92cb-ba8f633606d9", + }, +}; + +static struct kset *lfa_kset; +static struct workqueue_struct *fw_images_update_wq; +static struct work_struct fw_images_update_work; +static struct attribute *image_default_attrs[LFA_ATTR_NR_IMAGES + 1]; + +static const struct attribute_group image_attr_group = { + .attrs = image_default_attrs, +}; + +static const struct attribute_group *image_default_groups[] = { + &image_attr_group, + NULL +}; + +static int update_fw_images_tree(void); + +static const char *lfa_error_string(int error) +{ + if (error > 0) + return lfa_error_strings[LFA_SUCCESS]; + + error = -error; + if (error < ARRAY_SIZE(lfa_error_strings)) + return lfa_error_strings[error]; + if (error == -LFA_TIMED_OUT) + return "timed out"; + + return lfa_error_strings[LFA_DEVICE_ERROR]; +} + +static void image_release(struct kobject *kobj) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + + kfree(image); +} + +static const struct kobj_type image_ktype = { + .release = image_release, + .sysfs_ops = &kobj_sysfs_ops, + .default_groups = image_default_groups, +}; + +static void delete_fw_image_node(struct fw_image *image) +{ + kobject_del(&image->kobj); + kobject_put(&image->kobj); +} + +static void remove_invalid_fw_images(struct work_struct *work) +{ + struct kobject *kobj, *tmp; + struct list_head images_to_delete = LIST_HEAD_INIT(images_to_delete); + + /* + * Remove firmware images including directories that are no longer + * present in the LFA agent after updating the existing ones. + * Delete list images before calling kobject_del() and kobject_put() on + * them. Kobject_del() uses kset->list_lock itself which can cause lock + * recursion, and kobject_put() may sleep. + */ + spin_lock(&lfa_kset->list_lock); + list_for_each_entry_safe(kobj, tmp, &lfa_kset->list, entry) { + struct fw_image *image = kobj_to_fw_image(kobj); + + if (image->fw_seq_id == -1) + list_move_tail(&kobj->entry, &images_to_delete); + } + spin_unlock(&lfa_kset->list_lock); + + /* + * Now safely remove the sysfs kobjects for the deleted list items + */ + list_for_each_entry_safe(kobj, tmp, &images_to_delete, entry) { + struct fw_image *image = kobj_to_fw_image(kobj); + + delete_fw_image_node(image); + } +} + +static void set_image_flags(struct fw_image *image, int seq_id, + u32 image_flags, u64 reg_current_ver, + u64 reg_pending_ver) +{ + image->fw_seq_id = seq_id; + image->current_version = reg_current_ver; + image->pending_version = reg_pending_ver; + image->activation_capable = !!(image_flags & BIT(0)); + image->activation_pending = !!(image_flags & BIT(1)); + image->may_reset_cpu = !!(image_flags & BIT(2)); + /* cpu_rendezvous_optional bit has inverse logic in the spec */ + image->cpu_rendezvous = !(image_flags & BIT(3)); +} + +static unsigned long get_nr_lfa_components(void) +{ + struct arm_smccc_1_2_regs reg = { 0 }; + + reg.a0 = 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 fw_image *image = data; + struct arm_smccc_1_2_regs reg = { 0 }; + + reg.a0 = LFA_1_0_FN_CANCEL; + reg.a1 = image->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", + image->image_name); + } else { + pr_err("Activation not cancelled for image %s: %s\n", + image->image_name, lfa_error_string(reg.a0)); + return -EINVAL; + } + + return reg.a0; +} + +static const char *get_image_name(const struct fw_image *image) +{ + if (image->image_name && image->image_name[0] != '\0') + return image->image_name; + + return kobject_name(&image->kobj); +} + +/* + * Try a single activation call. The smc_lock writer lock must be held, + * and it must be called from inside stop_machine() when CPU rendezvous is + * required. + */ +static int call_lfa_activate(void *data) +{ + struct fw_image *image = data; + struct arm_smccc_1_2_regs reg = { 0 }, res; + + reg.a0 = LFA_1_0_FN_ACTIVATE; + reg.a1 = image->fw_seq_id; + /* + * As we do not support updates requiring a CPU reset (yet), + * we pass 0 in reg.a3 and reg.a4, holding the entry point and + * context ID respectively. + * cpu_rendezvous_forced is set by the administrator, via sysfs, + * cpu_rendezvous is dictated by each firmware component. + */ + reg.a2 = !(image->cpu_rendezvous_forced || image->cpu_rendezvous); + arm_smccc_1_2_invoke(®, &res); + + if ((long)res.a0 < 0) + return (long)res.a0; + + if (res.a1 & LFA_ACTIVATE_CALL_AGAIN) + return -LFA_CALL_AGAIN; + + return 0; +} + +static int activate_fw_image(struct fw_image *image) +{ + struct kobject *kobj; + int ret; + +retry: + if (image->cpu_rendezvous_forced || image->cpu_rendezvous) + ret = stop_machine(call_lfa_activate, image, cpu_online_mask); + else + ret = call_lfa_activate(image); + + if (!ret) { + /* + * Invalidate fw_seq_ids (-1) for all images as the seq_ids + * and the number of firmware images in the LFA agent may + * change after a successful activation attempt. + * Negate all image flags as well. + */ + spin_lock(&lfa_kset->list_lock); + list_for_each_entry(kobj, &lfa_kset->list, entry) { + struct fw_image *image = kobj_to_fw_image(kobj); + + set_image_flags(image, -1, 0b1000, 0, 0); + } + spin_unlock(&lfa_kset->list_lock); + + 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. + */ + queue_work(fw_images_update_wq, &fw_images_update_work); + + return 0; + } + + if (ret == -LFA_CALL_AGAIN) + goto retry; + + lfa_cancel(image); + + pr_err("LFA_ACTIVATE for image %s failed: %s\n", + get_image_name(image), lfa_error_string(ret)); + + return ret; +} + +static int prime_fw_image(struct fw_image *image) +{ + struct arm_smccc_1_2_regs reg = { 0 }, res; + + if (image->may_reset_cpu) { + pr_err("CPU reset not supported by kernel driver\n"); + + return -EINVAL; + } + + reg.a0 = LFA_1_0_FN_PRIME; +retry: + /* + * LFA_PRIME will return 1 in reg.a1 if the firmware priming + * is still in progress. In that case LFA_PRIME will need to + * be called again. + * reg.a1 will become 0 once the prime process completes. + */ + reg.a1 = image->fw_seq_id; + arm_smccc_1_2_invoke(®, &res); + if ((long)res.a0 < 0) { + pr_err("LFA_PRIME for image %s failed: %s\n", + get_image_name(image), + lfa_error_string(res.a0)); + + return res.a0; + } + + if (res.a1 & LFA_PRIME_CALL_AGAIN) + goto retry; + + return 0; +} + +static ssize_t name_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + + return sysfs_emit(buf, "%s\n", image->image_name); +} + +static ssize_t activation_capable_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + + return sysfs_emit(buf, "%d\n", image->activation_capable); +} + +static void update_fw_image_pending(struct fw_image *image) +{ + struct arm_smccc_1_2_regs reg = { 0 }; + + reg.a0 = LFA_1_0_FN_GET_INVENTORY; + reg.a1 = image->fw_seq_id; + arm_smccc_1_2_invoke(®, ®); + + if (reg.a0 == LFA_SUCCESS) + image->activation_pending = !!(reg.a3 & BIT(1)); +} + +static ssize_t activation_pending_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + + /* + * Activation pending status can change anytime thus we need to update + * and return its current value + */ + update_fw_image_pending(image); + + return sysfs_emit(buf, "%d\n", image->activation_pending); +} + +static ssize_t may_reset_cpu_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + + return sysfs_emit(buf, "%d\n", image->may_reset_cpu); +} + +static ssize_t cpu_rendezvous_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + + return sysfs_emit(buf, "%d\n", image->cpu_rendezvous); +} + +static ssize_t force_cpu_rendezvous_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + int ret; + + ret = kstrtobool(buf, &image->cpu_rendezvous_forced); + if (ret) + return ret; + + return count; +} + +static ssize_t force_cpu_rendezvous_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + + return sysfs_emit(buf, "%d\n", image->cpu_rendezvous_forced); +} + +static ssize_t current_version_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + u32 maj, min; + + maj = image->current_version >> 32; + min = image->current_version & 0xffffffff; + + return sysfs_emit(buf, "%u.%u\n", maj, min); +} + +static ssize_t pending_version_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + struct arm_smccc_1_2_regs reg = { 0 }; + + /* + * Similar to activation pending, this value can change following an + * update, we need to retrieve fresh info instead of stale information. + */ + reg.a0 = LFA_1_0_FN_GET_INVENTORY; + reg.a1 = image->fw_seq_id; + arm_smccc_1_2_invoke(®, ®); + if (reg.a0 == LFA_SUCCESS) { + if (reg.a5 != 0 && image->activation_pending) { + u32 maj, min; + + image->pending_version = reg.a5; + maj = reg.a5 >> 32; + min = reg.a5 & 0xffffffff; + + return sysfs_emit(buf, "%u.%u\n", maj, min); + } + } + + return sysfs_emit(buf, "N/A\n"); +} + +static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + int ret; + + ret = prime_fw_image(image); + if (ret) + return -ECANCELED; + + ret = activate_fw_image(image); + if (ret) + return -ECANCELED; + + pr_info("%s: successfully activated\n", get_image_name(image)); + + return count; +} + +static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + int ret; + + ret = lfa_cancel(image); + if (ret != 0) + return ret; + + return count; +} + +static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = { + [LFA_ATTR_NAME] = __ATTR_RO(name), + [LFA_ATTR_CURRENT_VERSION] = __ATTR_RO(current_version), + [LFA_ATTR_PENDING_VERSION] = __ATTR_RO(pending_version), + [LFA_ATTR_ACT_CAPABLE] = __ATTR_RO(activation_capable), + [LFA_ATTR_ACT_PENDING] = __ATTR_RO(activation_pending), + [LFA_ATTR_MAY_RESET_CPU] = __ATTR_RO(may_reset_cpu), + [LFA_ATTR_CPU_RENDEZVOUS] = __ATTR_RO(cpu_rendezvous), + [LFA_ATTR_FORCE_CPU_RENDEZVOUS] = __ATTR_RW(force_cpu_rendezvous), + [LFA_ATTR_ACTIVATE] = __ATTR_WO(activate), + [LFA_ATTR_CANCEL] = __ATTR_WO(cancel) +}; + +static void init_image_default_attrs(void) +{ + for (int i = 0; i < LFA_ATTR_NR_IMAGES; i++) + image_default_attrs[i] = &image_attrs_group[i].attr; + image_default_attrs[LFA_ATTR_NR_IMAGES] = NULL; +} + +static void clean_fw_images_tree(void) +{ + struct kobject *kobj, *tmp; + struct list_head images_to_delete; + + INIT_LIST_HEAD(&images_to_delete); + + spin_lock(&lfa_kset->list_lock); + list_for_each_entry_safe(kobj, tmp, &lfa_kset->list, entry) { + list_move_tail(&kobj->entry, &images_to_delete); + } + spin_unlock(&lfa_kset->list_lock); + + list_for_each_entry_safe(kobj, tmp, &images_to_delete, entry) { + struct fw_image *image = kobj_to_fw_image(kobj); + + delete_fw_image_node(image); + } +} + +static int update_fw_image_node(char *fw_uuid, int seq_id, + u32 image_flags, u64 reg_current_ver, + u64 reg_pending_ver) +{ + const char *image_name = ""; + struct fw_image *image; + struct kobject *kobj; + int i; + + /* + * If a fw_image is already in the images list then we just update + * its flags and seq_id instead of trying to recreate it. + */ + spin_lock(&lfa_kset->list_lock); + list_for_each_entry(kobj, &lfa_kset->list, entry) { + if (!strcmp(kobject_name(kobj), fw_uuid)) { + struct fw_image *image = kobj_to_fw_image(kobj); + + set_image_flags(image, seq_id, image_flags, + reg_current_ver, reg_pending_ver); + spin_unlock(&lfa_kset->list_lock); + + return 0; + } + } + spin_unlock(&lfa_kset->list_lock); + + image = kzalloc_obj(*image); + if (!image) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(fw_images_uuids); i++) { + if (!strcmp(fw_images_uuids[i].uuid, fw_uuid)) + image_name = fw_images_uuids[i].name; + } + + image->kobj.kset = lfa_kset; + image->image_name = image_name; + image->cpu_rendezvous_forced = true; + set_image_flags(image, seq_id, image_flags, reg_current_ver, + reg_pending_ver); + if (kobject_init_and_add(&image->kobj, &image_ktype, NULL, + "%s", fw_uuid)) { + kobject_put(&image->kobj); + + return -ENOMEM; + } + + return 0; +} + +static int update_fw_images_tree(void) +{ + struct arm_smccc_1_2_regs reg = { 0 }, res; + struct uuid_regs image_uuid; + 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; + } + + reg.a0 = LFA_1_0_FN_GET_INVENTORY; + for (int i = 0; i < num_of_components; i++) { + reg.a1 = i; /* fw_seq_id to be queried */ + arm_smccc_1_2_invoke(®, &res); + if (res.a0 == LFA_SUCCESS) { + image_uuid.uuid_lo = res.a1; + image_uuid.uuid_hi = res.a2; + + snprintf(image_id_str, sizeof(image_id_str), "%pUb", + &image_uuid); + ret = update_fw_image_node(image_id_str, i, res.a3, + res.a4, res.a5); + if (ret) + return ret; + } + } + + 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; + } + + pr_info("Live Firmware Activation: detected v%ld.%ld\n", + reg.a0 >> 16, reg.a0 & 0xffff); + + fw_images_update_wq = alloc_workqueue("fw_images_update_wq", + WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!fw_images_update_wq) { + pr_err("Live Firmware Activation: Failed to allocate workqueue.\n"); + + return -ENOMEM; + } + INIT_WORK(&fw_images_update_work, remove_invalid_fw_images); + + init_image_default_attrs(); + lfa_kset = kset_create_and_add("lfa", NULL, firmware_kobj); + if (!lfa_kset) + return -ENOMEM; + + err = update_fw_images_tree(); + if (err != 0) { + kset_unregister(lfa_kset); + destroy_workqueue(fw_images_update_wq); + } + + return err; +} +module_init(lfa_init); + +static void __exit lfa_exit(void) +{ + flush_workqueue(fw_images_update_wq); + destroy_workqueue(fw_images_update_wq); + clean_fw_images_tree(); + kset_unregister(lfa_kset); +} +module_exit(lfa_exit); + +MODULE_DESCRIPTION("ARM Live Firmware Activation (LFA)"); +MODULE_LICENSE("GPL"); From c2dcbefe0a78bbf68fd35a25d4330df4d1decac6 Mon Sep 17 00:00:00 2001 From: Vedashree Vidwans Date: Tue, 17 Mar 2026 11:33:29 +0100 Subject: [PATCH 06/13] NVIDIA: VR: SAUCE: firmware: smccc: lfa: Move image rescanning After an image activation, the list of firmware images might change, so we have to re-iterate them through the SMC interface. Move the corresponding code from the activate_fw_image() function into update_fw_images_tree(), where it could be reused more easily, for instance when triggered by an interrupt. Signed-off-by: Vedashree Vidwans [Andre: split off from another patch, rebased] Signed-off-by: Andre Przywara (backported from https://lore.kernel.org/all/20260317103336.1273582-1-andre.przywara@arm.com/) Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/lfa_fw.c | 46 ++++++++++++++++----------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index 284b7c18d3d09..4831abf2b60ee 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -310,7 +310,6 @@ static int call_lfa_activate(void *data) static int activate_fw_image(struct fw_image *image) { - struct kobject *kobj; int ret; retry: @@ -320,31 +319,8 @@ static int activate_fw_image(struct fw_image *image) ret = call_lfa_activate(image); if (!ret) { - /* - * Invalidate fw_seq_ids (-1) for all images as the seq_ids - * and the number of firmware images in the LFA agent may - * change after a successful activation attempt. - * Negate all image flags as well. - */ - spin_lock(&lfa_kset->list_lock); - list_for_each_entry(kobj, &lfa_kset->list, entry) { - struct fw_image *image = kobj_to_fw_image(kobj); - - set_image_flags(image, -1, 0b1000, 0, 0); - } - spin_unlock(&lfa_kset->list_lock); - 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. - */ - queue_work(fw_images_update_wq, &fw_images_update_work); - return 0; } @@ -640,6 +616,7 @@ static int update_fw_images_tree(void) { struct arm_smccc_1_2_regs reg = { 0 }, res; struct uuid_regs image_uuid; + struct kobject *kobj; char image_id_str[40]; int ret, num_of_components; @@ -649,6 +626,19 @@ static int update_fw_images_tree(void) return -ENODEV; } + /* + * Invalidate fw_seq_ids (-1) for all images as the seq_ids and the + * number of firmware images in the LFA agent may change after a + * successful activation attempt. Negate all image flags as well. + */ + spin_lock(&lfa_kset->list_lock); + list_for_each_entry(kobj, &lfa_kset->list, entry) { + struct fw_image *image = kobj_to_fw_image(kobj); + + set_image_flags(image, -1, 0b1000, 0, 0); + } + spin_unlock(&lfa_kset->list_lock); + reg.a0 = LFA_1_0_FN_GET_INVENTORY; for (int i = 0; i < num_of_components; i++) { reg.a1 = i; /* fw_seq_id to be queried */ @@ -666,6 +656,14 @@ static int update_fw_images_tree(void) } } + /* + * Removing non-valid image directories at the end of an activation. + * We can't remove the sysfs attributes while in the respective + * _store() handler, so have to postpone the list removal to a + * workqueue. + */ + queue_work(fw_images_update_wq, &fw_images_update_work); + return 0; } From 0e06ba25fad2c3f5364f21ee439c83d19679914f Mon Sep 17 00:00:00 2001 From: Vedashree Vidwans Date: Tue, 17 Mar 2026 11:33:30 +0100 Subject: [PATCH 07/13] NVIDIA: VR: SAUCE: firmware: smccc: lfa: Add timeout and trigger watchdog 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 Signed-off-by: Andre Przywara (backported from https://lore.kernel.org/all/20260317103336.1273582-1-andre.przywara@arm.com/) Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/lfa_fw.c | 43 ++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index 4831abf2b60ee..d1b5cd29b8a09 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -5,11 +5,14 @@ #include #include +#include #include #include #include +#include #include #include +#include #include #include #include @@ -38,6 +41,11 @@ #define LFA_PRIME_CALL_AGAIN BIT(0) #define LFA_ACTIVATE_CALL_AGAIN BIT(0) +#define LFA_PRIME_BUDGET_MS 30000 /* 30s cap */ +#define LFA_PRIME_DELAY_MS 10 /* 10ms between polls */ +#define LFA_ACTIVATE_BUDGET_MS 10000 /* 10s cap */ +#define LFA_ACTIVATE_DELAY_MS 10 /* 10ms between polls */ + /* LFA return values */ #define LFA_SUCCESS 0 #define LFA_NOT_SUPPORTED 1 @@ -287,6 +295,7 @@ static int call_lfa_activate(void *data) struct fw_image *image = data; struct arm_smccc_1_2_regs reg = { 0 }, res; + touch_nmi_watchdog(); reg.a0 = LFA_1_0_FN_ACTIVATE; reg.a1 = image->fw_seq_id; /* @@ -310,6 +319,7 @@ static int call_lfa_activate(void *data) static int activate_fw_image(struct fw_image *image) { + ktime_t end = ktime_add_ms(ktime_get(), LFA_ACTIVATE_BUDGET_MS); int ret; retry: @@ -324,8 +334,15 @@ static int activate_fw_image(struct fw_image *image) return 0; } - if (ret == -LFA_CALL_AGAIN) - goto retry; + if (ret == -LFA_CALL_AGAIN) { + /* SMC returned with call_again flag set */ + if (ktime_before(ktime_get(), end)) { + msleep_interruptible(LFA_ACTIVATE_DELAY_MS); + goto retry; + } + + ret = -LFA_TIMED_OUT; + } lfa_cancel(image); @@ -338,6 +355,8 @@ static int activate_fw_image(struct fw_image *image) static int prime_fw_image(struct fw_image *image) { struct arm_smccc_1_2_regs reg = { 0 }, res; + ktime_t end = ktime_add_ms(ktime_get(), LFA_PRIME_BUDGET_MS); + int ret; if (image->may_reset_cpu) { pr_err("CPU reset not supported by kernel driver\n"); @@ -345,6 +364,8 @@ static int prime_fw_image(struct fw_image *image) return -EINVAL; } + touch_nmi_watchdog(); + reg.a0 = LFA_1_0_FN_PRIME; retry: /* @@ -363,8 +384,22 @@ static int prime_fw_image(struct fw_image *image) return res.a0; } - if (res.a1 & LFA_PRIME_CALL_AGAIN) - goto retry; + if (res.a1 & LFA_PRIME_CALL_AGAIN) { + /* SMC returned with call_again flag set */ + if (ktime_before(ktime_get(), end)) { + msleep_interruptible(LFA_PRIME_DELAY_MS); + goto retry; + } + + pr_err("LFA_PRIME for image %s timed out", + get_image_name(image)); + + ret = lfa_cancel(image); + if (ret != 0) + return ret; + + return -ETIMEDOUT; + } return 0; } From 252ab06874f9fd144804d910c263ab805778deeb Mon Sep 17 00:00:00 2001 From: Vedashree Vidwans Date: Tue, 17 Mar 2026 11:33:31 +0100 Subject: [PATCH 08/13] NVIDIA: VR: SAUCE: firmware: smccc: lfa: Register ACPI notification The Arm LFA spec describes an ACPI notification mechanism, where the platform (firmware) can notify an LFA client about newly available firmware imag updates ("pending images" in LFA terms). Add a faux device after discovering the existence of an LFA agent via the SMCCC discovery mechnism, and use that device to check for the ACPI notification description. Register this when one is provided. The notification just conveys the fact that at least one firmware image has now a pending update, it doesn't say which, also there could be more than one pending. Loop through all images to find every which needs to be activated, and trigger the activation. We need to do this is a loop, since an activation might change the number and the status of available images. Signed-off-by: Vedashree Vidwans [Andre: convert from platform driver to faux device] Signed-off-by: Andre Przywara (backported from https://lore.kernel.org/all/20260317103336.1273582-1-andre.przywara@arm.com/) Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/lfa_fw.c | 147 ++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index d1b5cd29b8a09..f20ea45cdbd93 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -3,11 +3,14 @@ * Copyright (C) 2025 Arm Limited */ +#include #include #include #include +#include #include #include +#include #include #include #include @@ -17,11 +20,13 @@ #include #include #include +#include #include #include #include +#define DRIVER_NAME "ARM_LFA" #undef pr_fmt #define pr_fmt(fmt) "Arm LFA: " fmt @@ -702,6 +707,139 @@ static int update_fw_images_tree(void) return 0; } +/* + * Go through all FW images in a loop and trigger activation + * of all activatible and pending images. + * We have to restart enumeration after every triggered activation, + * since the firmware images might have changed during the activation. + */ +static int activate_pending_image(void) +{ + struct kobject *kobj; + bool found_pending = false; + struct fw_image *image; + int ret; + + spin_lock(&lfa_kset->list_lock); + list_for_each_entry(kobj, &lfa_kset->list, entry) { + image = kobj_to_fw_image(kobj); + + if (image->fw_seq_id == -1) + continue; /* Invalid FW component */ + + update_fw_image_pending(image); + if (image->activation_capable && image->activation_pending) { + found_pending = true; + break; + } + } + spin_unlock(&lfa_kset->list_lock); + + if (!found_pending) + return -ENOENT; + + ret = prime_fw_image(image); + if (ret) + return ret; + + ret = activate_fw_image(image); + if (ret) + return ret; + + pr_info("%s: automatic activation succeeded\n", get_image_name(image)); + + return 0; +} + +#ifdef CONFIG_ACPI +static void lfa_acpi_notify_handler(acpi_handle handle, u32 event, void *data) +{ + int ret; + + while (!(ret = activate_pending_image())) + ; + + if (ret != -ENOENT) + pr_warn("notified image activation failed: %d\n", ret); +} + +static int lfa_register_acpi(struct device *dev) +{ + struct acpi_device *acpi_dev; + acpi_handle handle; + acpi_status status; + + acpi_dev = acpi_dev_get_first_match_dev("ARML0003", NULL, -1); + if (!acpi_dev) + return -ENODEV; + handle = acpi_device_handle(acpi_dev); + if (!handle) { + acpi_dev_put(acpi_dev); + return -ENODEV; + } + + /* Register notify handler that indicates LFA updates are available */ + status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY, + lfa_acpi_notify_handler, NULL); + if (ACPI_FAILURE(status)) { + acpi_dev_put(acpi_dev); + return -EIO; + } + + ACPI_COMPANION_SET(dev, acpi_dev); + + return 0; +} + +static void lfa_remove_acpi(struct device *dev) +{ + struct acpi_device *acpi_dev = ACPI_COMPANION(dev); + acpi_handle handle = acpi_device_handle(acpi_dev); + + if (handle) + acpi_remove_notify_handler(handle, + ACPI_DEVICE_NOTIFY, + lfa_acpi_notify_handler); + acpi_dev_put(acpi_dev); +} +#else /* !CONFIG_ACPI */ +static int lfa_register_acpi(struct device *dev) +{ + return -ENODEV; +} + +static void lfa_remove_acpi(struct device *dev) +{ +} +#endif + +static int lfa_faux_probe(struct faux_device *fdev) +{ + int ret; + + if (!acpi_disabled) { + ret = lfa_register_acpi(&fdev->dev); + if (ret != -ENODEV) { + if (!ret) + pr_info("registered LFA ACPI notification\n"); + return ret; + } + } + + return 0; +} + +static void lfa_faux_remove(struct faux_device *fdev) +{ + lfa_remove_acpi(&fdev->dev); +} + +static struct faux_device *lfa_dev; +static struct faux_device_ops lfa_device_ops = { + .probe = lfa_faux_probe, + .remove = lfa_faux_remove, +}; + static int __init lfa_init(void) { struct arm_smccc_1_2_regs reg = { 0 }; @@ -731,6 +869,14 @@ static int __init lfa_init(void) if (!lfa_kset) return -ENOMEM; + /* + * This faux device is just used for the optional notification + * mechanism, to register the ACPI notification or interrupt. + * If the firmware tables do not contain this information, the + * driver will still work. + */ + lfa_dev = faux_device_create("arm-lfa", NULL, &lfa_device_ops); + err = update_fw_images_tree(); if (err != 0) { kset_unregister(lfa_kset); @@ -747,6 +893,7 @@ static void __exit lfa_exit(void) destroy_workqueue(fw_images_update_wq); clean_fw_images_tree(); kset_unregister(lfa_kset); + faux_device_destroy(lfa_dev); } module_exit(lfa_exit); From c31a18579709a0f705a0efad95c2a2439c790add Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Tue, 17 Mar 2026 11:33:32 +0100 Subject: [PATCH 09/13] NVIDIA: VR: SAUCE: firmware: smccc: lfa: Add auto_activate sysfs file The Arm LFA spec places control over the actual activation process in the hands of the non-secure host OS. An platform initiated interrupt or notification signals the availability of an updateable firmware image, but does not necessarily need to trigger it automatically. Add a sysfs control file that guards such automatic activation. If an administrator wants to allow automatic platform initiated updates, they can activate that by echoing a "1" into the auto_activate file in the respective sysfs directory. Any incoming notification would then result in the activation triggered. Signed-off-by: Andre Przywara (backported from https://lore.kernel.org/all/20260317103336.1273582-1-andre.przywara@arm.com/) Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/lfa_fw.c | 34 ++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index f20ea45cdbd93..5dc531e462ebc 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -101,6 +101,7 @@ enum image_attr_names { LFA_ATTR_FORCE_CPU_RENDEZVOUS, LFA_ATTR_ACTIVATE, LFA_ATTR_CANCEL, + LFA_ATTR_AUTO_ACTIVATE, LFA_ATTR_NR_IMAGES }; @@ -115,6 +116,7 @@ struct fw_image { bool may_reset_cpu; bool cpu_rendezvous; bool cpu_rendezvous_forced; + bool auto_activate; struct kobj_attribute image_attrs[LFA_ATTR_NR_IMAGES]; }; @@ -561,6 +563,28 @@ static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr, return count; } +static ssize_t auto_activate_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + int ret; + + ret = kstrtobool(buf, &image->auto_activate); + if (ret) + return ret; + + return count; +} + +static ssize_t auto_activate_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct fw_image *image = kobj_to_fw_image(kobj); + + return sysfs_emit(buf, "%d\n", image->auto_activate); +} + static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = { [LFA_ATTR_NAME] = __ATTR_RO(name), [LFA_ATTR_CURRENT_VERSION] = __ATTR_RO(current_version), @@ -571,7 +595,8 @@ static struct kobj_attribute image_attrs_group[LFA_ATTR_NR_IMAGES] = { [LFA_ATTR_CPU_RENDEZVOUS] = __ATTR_RO(cpu_rendezvous), [LFA_ATTR_FORCE_CPU_RENDEZVOUS] = __ATTR_RW(force_cpu_rendezvous), [LFA_ATTR_ACTIVATE] = __ATTR_WO(activate), - [LFA_ATTR_CANCEL] = __ATTR_WO(cancel) + [LFA_ATTR_CANCEL] = __ATTR_WO(cancel), + [LFA_ATTR_AUTO_ACTIVATE] = __ATTR_RW(auto_activate), }; static void init_image_default_attrs(void) @@ -640,6 +665,7 @@ static int update_fw_image_node(char *fw_uuid, int seq_id, image->kobj.kset = lfa_kset; image->image_name = image_name; image->cpu_rendezvous_forced = true; + image->auto_activate = false; set_image_flags(image, seq_id, image_flags, reg_current_ver, reg_pending_ver); if (kobject_init_and_add(&image->kobj, &image_ktype, NULL, @@ -709,7 +735,8 @@ static int update_fw_images_tree(void) /* * Go through all FW images in a loop and trigger activation - * of all activatible and pending images. + * of all activatible and pending images, but only if automatic + * activation for that image is allowed. * We have to restart enumeration after every triggered activation, * since the firmware images might have changed during the activation. */ @@ -728,7 +755,8 @@ static int activate_pending_image(void) continue; /* Invalid FW component */ update_fw_image_pending(image); - if (image->activation_capable && image->activation_pending) { + if (image->activation_capable && image->activation_pending && + image->auto_activate) { found_pending = true; break; } From 584d5308e16bae7a497f9f62dd2a977a3622151c Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Tue, 17 Mar 2026 11:33:33 +0100 Subject: [PATCH 10/13] NVIDIA: VR: SAUCE: firmware: smccc: lfa: Register DT interrupt The Arm Live Firmware Activation spec describes an asynchronous notification mechanism, where the platform can notify the host OS about newly pending image updates. In the absence of the ACPI notification mechanism also a simple devicetree node can describe an interrupt. Add code to find the respective DT node and register the specified interrupt, to trigger the activation if needed. Signed-off-by: Andre Przywara (backported from https://lore.kernel.org/all/20260317103336.1273582-1-andre.przywara@arm.com/) Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/lfa_fw.c | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index 5dc531e462ebc..ecd056901b8d8 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -841,6 +843,43 @@ static void lfa_remove_acpi(struct device *dev) } #endif +static irqreturn_t lfa_irq_handler(int irq, void *dev_id) +{ + return IRQ_WAKE_THREAD; +} + +static irqreturn_t lfa_irq_handler_thread(int irq, void *dev_id) +{ + int ret; + + while (!(ret = activate_pending_image())) + ; + + if (ret != -ENOENT) + pr_warn("notified image activation failed: %d\n", ret); + + return IRQ_HANDLED; +} + +static int lfa_register_dt(struct device *dev) +{ + struct device_node *np; + unsigned int irq; + + np = of_find_compatible_node(NULL, NULL, "arm,lfa"); + if (!np) + return -ENODEV; + + irq = irq_of_parse_and_map(np, 0); + of_node_put(np); + if (!irq) + return -ENODEV; + + return devm_request_threaded_irq(dev, irq, lfa_irq_handler, + lfa_irq_handler_thread, + IRQF_COND_ONESHOT, NULL, NULL); +} + static int lfa_faux_probe(struct faux_device *fdev) { int ret; @@ -854,6 +893,12 @@ static int lfa_faux_probe(struct faux_device *fdev) } } + ret = lfa_register_dt(&fdev->dev); + if (!ret) + pr_info("registered LFA DT notification interrupt\n"); + if (ret != -ENODEV) + return ret; + return 0; } From 1ca69bf550209334a0b27547b85c617903f0710a Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Tue, 17 Mar 2026 11:33:34 +0100 Subject: [PATCH 11/13] NVIDIA: VR: SAUCE: firmware: smccc: lfa: introduce SMC access lock After a successful live activation, the list of firmware images might change, which also affects the sequence IDs. We store the sequence ID in a data structure and connect it to its GUID, which is the identifier used to access certain image properties from userland. When an activation is happening, the sequence ID associations might change at any point, so we must be sure to not use any previously learned sequence ID during this time. Protect the association between a sequence ID and a firmware image (its GUID, really) by a reader/writer lock. In this case it's a R/W semaphore, so it can sleep and we can hold it for longer, also concurrent SMC calls are not blocked on each other, it's just an activation that blocks calls. Signed-off-by: Andre Przywara (backported from https://lore.kernel.org/all/20260317103336.1273582-1-andre.przywara@arm.com/) Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/lfa_fw.c | 38 +++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index ecd056901b8d8..663ba79f07135 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -157,6 +158,16 @@ static struct workqueue_struct *fw_images_update_wq; static struct work_struct fw_images_update_work; static struct attribute *image_default_attrs[LFA_ATTR_NR_IMAGES + 1]; +/* + * A successful image activation might change the number of available images, + * leading to a re-order and thus re-assignment of the sequence IDs. + * The lock protects the connection between a firmware image (through its + * user visible UUID) and the sequence IDs. Anyone doing an SMC call with + * a sequence ID needs to take the readers lock. Doing an activation requires + * the writer lock, as that process might change the assocications. + */ +struct rw_semaphore smc_lock; + static const struct attribute_group image_attr_group = { .attrs = image_default_attrs, }; @@ -253,6 +264,7 @@ static unsigned long get_nr_lfa_components(void) reg.a0 = LFA_1_0_FN_GET_INFO; reg.a1 = 0; /* lfa_info_selector = 0 */ + /* No need for the smc_lock, since no sequence IDs are involved. */ arm_smccc_1_2_invoke(®, ®); if (reg.a0 != LFA_SUCCESS) return reg.a0; @@ -265,9 +277,11 @@ static int lfa_cancel(void *data) struct fw_image *image = data; struct arm_smccc_1_2_regs reg = { 0 }; + down_read(&smc_lock); reg.a0 = LFA_1_0_FN_CANCEL; reg.a1 = image->fw_seq_id; arm_smccc_1_2_invoke(®, ®); + up_read(&smc_lock); /* * When firmware activation is called with "skip_cpu_rendezvous=1", @@ -332,6 +346,7 @@ static int activate_fw_image(struct fw_image *image) int ret; retry: + down_write(&smc_lock); if (image->cpu_rendezvous_forced || image->cpu_rendezvous) ret = stop_machine(call_lfa_activate, image, cpu_online_mask); else @@ -339,10 +354,13 @@ static int activate_fw_image(struct fw_image *image) if (!ret) { update_fw_images_tree(); + up_write(&smc_lock); return 0; } + up_write(&smc_lock); + if (ret == -LFA_CALL_AGAIN) { /* SMC returned with call_again flag set */ if (ktime_before(ktime_get(), end)) { @@ -383,8 +401,11 @@ static int prime_fw_image(struct fw_image *image) * be called again. * reg.a1 will become 0 once the prime process completes. */ + down_read(&smc_lock); reg.a1 = image->fw_seq_id; arm_smccc_1_2_invoke(®, &res); + up_read(&smc_lock); + if ((long)res.a0 < 0) { pr_err("LFA_PRIME for image %s failed: %s\n", get_image_name(image), @@ -429,7 +450,7 @@ static ssize_t activation_capable_show(struct kobject *kobj, return sysfs_emit(buf, "%d\n", image->activation_capable); } -static void update_fw_image_pending(struct fw_image *image) +static void _update_fw_image_pending(struct fw_image *image) { struct arm_smccc_1_2_regs reg = { 0 }; @@ -441,6 +462,13 @@ static void update_fw_image_pending(struct fw_image *image) image->activation_pending = !!(reg.a3 & BIT(1)); } +static void update_fw_image_pending(struct fw_image *image) +{ + down_read(&smc_lock); + _update_fw_image_pending(image); + up_read(&smc_lock); +} + static ssize_t activation_pending_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { @@ -515,9 +543,11 @@ static ssize_t pending_version_show(struct kobject *kobj, * Similar to activation pending, this value can change following an * update, we need to retrieve fresh info instead of stale information. */ + down_read(&smc_lock); reg.a0 = LFA_1_0_FN_GET_INVENTORY; reg.a1 = image->fw_seq_id; arm_smccc_1_2_invoke(®, ®); + up_read(&smc_lock); if (reg.a0 == LFA_SUCCESS) { if (reg.a5 != 0 && image->activation_pending) { u32 maj, min; @@ -749,6 +779,7 @@ static int activate_pending_image(void) struct fw_image *image; int ret; + down_read(&smc_lock); spin_lock(&lfa_kset->list_lock); list_for_each_entry(kobj, &lfa_kset->list, entry) { image = kobj_to_fw_image(kobj); @@ -756,7 +787,7 @@ static int activate_pending_image(void) if (image->fw_seq_id == -1) continue; /* Invalid FW component */ - update_fw_image_pending(image); + _update_fw_image_pending(image); if (image->activation_capable && image->activation_pending && image->auto_activate) { found_pending = true; @@ -764,6 +795,7 @@ static int activate_pending_image(void) } } spin_unlock(&lfa_kset->list_lock); + up_read(&smc_lock); if (!found_pending) return -ENOENT; @@ -950,6 +982,8 @@ static int __init lfa_init(void) */ lfa_dev = faux_device_create("arm-lfa", NULL, &lfa_device_ops); + init_rwsem(&smc_lock); + err = update_fw_images_tree(); if (err != 0) { kset_unregister(lfa_kset); From 01cd0f8e97bf40be6ca45449e5b6ee09221e4f54 Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Thu, 23 Apr 2026 04:39:29 -0700 Subject: [PATCH 12/13] NVIDIA: VR: SAUCE: firmware: smccc: lfa: handle LFA_BUSY in PRIME and ACTIVATE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DEN0147 §2.6: LFA_ACTIVATE can return LFA_BUSY when the firmware postpones the activation. Although the rwsem in this driver prevents concurrent ACTIVATE calls from kernel space, an external agent or internal firmware state may still produce LFA_BUSY. Add an explicit retry loop (same budget and delay as CALL_AGAIN) so the code does not silently treat a retriable condition as a terminal failure. Catching LFA_BUSY explicitly also surfaces potential firmware or driver bugs. DEN0147 §2.5: LFA_PRIME returning LFA_BUSY means another CPU is running LFA_PRIME concurrently. This driver never issues parallel PRIME, so this is unexpected; log pr_warn and return so the caller can surface the anomaly rather than swallowing it in the generic error path. Signed-off-by: Vedashree Vidwans Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/lfa_fw.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index 663ba79f07135..3c6a59c74bc96 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -371,6 +371,23 @@ static int activate_fw_image(struct fw_image *image) ret = -LFA_TIMED_OUT; } + /* + * DEN0147 §2.6: LFA_BUSY means activation was postponed by firmware + * and must be retried. Although the rwsem prevents concurrent ACTIVATE + * from this driver, an external agent or firmware-internal state may + * still return LFA_BUSY. Handle it explicitly so it is not silently + * treated as a terminal failure — catching it here may also indicate a + * bug in the driver or firmware. + */ + if (ret == -LFA_BUSY) { + if (ktime_before(ktime_get(), end)) { + msleep_interruptible(LFA_ACTIVATE_DELAY_MS); + goto retry; + } + + ret = -LFA_TIMED_OUT; + } + lfa_cancel(image); pr_err("LFA_ACTIVATE for image %s failed: %s\n", @@ -406,6 +423,17 @@ static int prime_fw_image(struct fw_image *image) arm_smccc_1_2_invoke(®, &res); up_read(&smc_lock); + /* + * DEN0147 §2.5: LFA_BUSY from PRIME means another CPU is concurrently + * running LFA_PRIME. This driver never issues parallel PRIME, so this + * is unexpected and likely indicates a firmware or driver bug. + */ + if ((long)res.a0 == -LFA_BUSY) { + pr_warn("LFA_PRIME for image %s returned LFA_BUSY (concurrent PRIME unexpected; possible firmware or driver bug)\n", + get_image_name(image)); + return res.a0; + } + if ((long)res.a0 < 0) { pr_err("LFA_PRIME for image %s failed: %s\n", get_image_name(image), From 772ce13332c644c76b102db45a03546450190b2c Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Mon, 4 May 2026 04:26:06 -0700 Subject: [PATCH 13/13] NVIDIA: VR: SAUCE: firmware: smccc: lfa: Emit a uevent on inventory updates Firmware image directories are plain kobjects under /sys/firmware. udev coldplug does not enumerate them as devices, so rules matching the per-image LFA kobjects do not run reliably at boot. LFA already creates the arm-lfa faux device. Emit KOBJ_CHANGE from that device after the firmware image tree is refreshed, so user space can use the existing driver-core device as the notification anchor for runtime inventory updates. The same udev rule then also covers coldplug via the device add event, e.g.: ACTION=="add|change", SUBSYSTEM=="faux", KERNEL=="arm-lfa", \ RUN+="/usr/local/sbin/lfa-auto-activate" Signed-off-by: Nirmoy Das --- drivers/firmware/smccc/lfa_fw.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index 3c6a59c74bc96..e8e7073af91b2 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -154,6 +154,7 @@ static const struct fw_image_uuid { }; static struct kset *lfa_kset; +static struct faux_device *lfa_dev; static struct workqueue_struct *fw_images_update_wq; static struct work_struct fw_images_update_work; static struct attribute *image_default_attrs[LFA_ATTR_NR_IMAGES + 1]; @@ -241,6 +242,21 @@ static void remove_invalid_fw_images(struct work_struct *work) delete_fw_image_node(image); } + + /* + * Notify user space only after the firmware image tree has been + * fully reconciled, so that consumers reading /sys/firmware/lfa/ + * see a settled inventory. Anchor the event on the arm-lfa faux + * device because the per-image kobjects under /sys/firmware/ are + * not enumerated by udev coldplug. + */ + if (lfa_dev) { + int ret = kobject_uevent(&lfa_dev->dev.kobj, KOBJ_CHANGE); + + if (ret) + pr_warn("failed to send firmware inventory change uevent: %d\n", + ret); + } } static void set_image_flags(struct fw_image *image, int seq_id, @@ -967,7 +983,6 @@ static void lfa_faux_remove(struct faux_device *fdev) lfa_remove_acpi(&fdev->dev); } -static struct faux_device *lfa_dev; static struct faux_device_ops lfa_device_ops = { .probe = lfa_faux_probe, .remove = lfa_faux_remove,