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 = ; + }; + }; +... diff --git a/drivers/firmware/smccc/Kconfig b/drivers/firmware/smccc/Kconfig index ff7ca49486b05..7fd646d515f86 100644 --- a/drivers/firmware/smccc/Kconfig +++ b/drivers/firmware/smccc/Kconfig @@ -26,8 +26,10 @@ config ARM_SMCCC_SOC_ID config ARM_LFA tristate "Arm Live Firmware activation support" - depends on HAVE_ARM_SMCCC_DISCOVERY + depends on HAVE_ARM_SMCCC_DISCOVERY && ARM64 default y help - Include support for triggering Live Firmware Activation, which - allows to upgrade certain firmware components without a reboot. + 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/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c index 90727a66e49a5..e8e7073af91b2 100644 --- a/drivers/firmware/smccc/lfa_fw.c +++ b/drivers/firmware/smccc/lfa_fw.c @@ -3,25 +3,31 @@ * Copyright (C) 2025 Arm Limited */ +#include +#include +#include +#include +#include #include #include +#include #include +#include +#include #include +#include +#include +#include +#include +#include #include #include #include -#include -#include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include #define DRIVER_NAME "ARM_LFA" #undef pr_fmt @@ -43,13 +49,10 @@ #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 */ +#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 @@ -65,6 +68,13 @@ #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 @@ -94,11 +104,12 @@ enum image_attr_names { LFA_ATTR_FORCE_CPU_RENDEZVOUS, LFA_ATTR_ACTIVATE, LFA_ATTR_CANCEL, + LFA_ATTR_AUTO_ACTIVATE, LFA_ATTR_NR_IMAGES }; -struct image_props { - struct list_head image_node; +struct fw_image { + struct kobject kobj; const char *image_name; int fw_seq_id; u64 current_version; @@ -108,10 +119,14 @@ struct image_props { bool may_reset_cpu; bool cpu_rendezvous; bool cpu_rendezvous_forced; - struct kobject *image_dir; + bool auto_activate; struct kobj_attribute image_attrs[LFA_ATTR_NR_IMAGES]; }; -static LIST_HEAD(lfa_fw_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 { @@ -119,6 +134,7 @@ struct uuid_regs { 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; @@ -137,55 +153,124 @@ static const struct fw_image_uuid { }, }; -static struct kobject *lfa_dir; -static DEFINE_MUTEX(lfa_lock); +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]; + +/* + * 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, +}; + +static const struct attribute_group *image_default_groups[] = { + &image_attr_group, + NULL +}; static int update_fw_images_tree(void); -static void delete_fw_image_node(struct image_props *attrs) +static const char *lfa_error_string(int error) { - int i; + if (error > 0) + return lfa_error_strings[LFA_SUCCESS]; - for (i = 0; i < LFA_ATTR_NR_IMAGES; i++) - sysfs_remove_file(attrs->image_dir, &attrs->image_attrs[i].attr); + error = -error; + if (error < ARRAY_SIZE(lfa_error_strings)) + return lfa_error_strings[error]; + if (error == -LFA_TIMED_OUT) + return "timed out"; - kobject_put(attrs->image_dir); - list_del(&attrs->image_node); - kfree(attrs); + return lfa_error_strings[LFA_DEVICE_ERROR]; } -static void remove_invalid_fw_images(struct work_struct *work) +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) { - struct image_props *attrs, *tmp; + kobject_del(&image->kobj); + kobject_put(&image->kobj); +} - mutex_lock(&lfa_lock); +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. */ - list_for_each_entry_safe(attrs, tmp, &lfa_fw_images, image_node) { - if (attrs->fw_seq_id == -1) - delete_fw_image_node(attrs); + 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); - mutex_unlock(&lfa_lock); + 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 image_props *attrs, int seq_id, +static void set_image_flags(struct fw_image *image, 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)); + 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 */ - attrs->cpu_rendezvous = !(image_flags & BIT(3)); + image->cpu_rendezvous = !(image_flags & BIT(3)); } static unsigned long get_nr_lfa_components(void) @@ -195,6 +280,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; @@ -204,12 +290,14 @@ static unsigned long get_nr_lfa_components(void) static int lfa_cancel(void *data) { - struct image_props *attrs = 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 = attrs->fw_seq_id; + 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", @@ -218,211 +306,251 @@ static int lfa_cancel(void *data) */ if (reg.a0 == LFA_SUCCESS) { pr_info("Activation cancelled for image %s\n", - attrs->image_name); + image->image_name); } else { - pr_err("Firmware activation could not be cancelled: %s\n", - lfa_error_strings[-reg.a0]); + 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 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 fw_image *image = data; + struct arm_smccc_1_2_regs reg = { 0 }, res; - args.a0 = LFA_1_0_FN_ACTIVATE; - args.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */ + touch_nmi_watchdog(); + 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. + * 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. */ - args.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); - - if ((long)res.a0 < 0) { - pr_err("ACTIVATE for image %s failed: %s\n", - attrs->image_name, lfa_error_strings[-res.a0]); - return res.a0; - } - if (!(res.a1 & LFA_ACTIVATE_CALL_AGAIN)) - break; /* ACTIVATE successful */ + reg.a2 = !(image->cpu_rendezvous_forced || image->cpu_rendezvous); + arm_smccc_1_2_invoke(®, &res); - /* SMC returned with call_again flag set */ - if (ktime_before(ktime_get(), end)) { - udelay(LFA_ACTIVATE_POLL_DELAY_US); - continue; - } + if ((long)res.a0 < 0) + return (long)res.a0; - pr_err("ACTIVATE for image %s timed out", attrs->image_name); - return -ETIMEDOUT; - } + if (res.a1 & LFA_ACTIVATE_CALL_AGAIN) + return -LFA_CALL_AGAIN; - return res.a0; + return 0; } -static int activate_fw_image(struct image_props *attrs) +static int activate_fw_image(struct fw_image *image) { + ktime_t end = ktime_add_ms(ktime_get(), LFA_ACTIVATE_BUDGET_MS); int ret; - mutex_lock(&lfa_lock); - if (attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous) - ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask); +retry: + down_write(&smc_lock); + if (image->cpu_rendezvous_forced || image->cpu_rendezvous) + ret = stop_machine(call_lfa_activate, image, cpu_online_mask); else - ret = call_lfa_activate(attrs); + ret = call_lfa_activate(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)) { + msleep_interruptible(LFA_ACTIVATE_DELAY_MS); + goto retry; + } + + ret = -LFA_TIMED_OUT; + } - if (ret != 0) { - mutex_unlock(&lfa_lock); - return lfa_cancel(attrs); + /* + * 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; } - update_fw_images_tree(); - mutex_unlock(&lfa_lock); + 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 image_props *attrs) +static int prime_fw_image(struct fw_image *image) { - 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 }, res; + ktime_t end = ktime_add_ms(ktime_get(), LFA_PRIME_BUDGET_MS); 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) { + if (image->may_reset_cpu) { pr_err("CPU reset not supported by kernel driver\n"); - mutex_unlock(&lfa_lock); return -EINVAL; } + touch_nmi_watchdog(); + + reg.a0 = LFA_1_0_FN_PRIME; +retry: /* - * 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. + * 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. */ - args.a0 = LFA_1_0_FN_PRIME; - args.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); - - if ((long)res.a0 < 0) { - pr_err("LFA_PRIME for image %s failed: %s\n", - attrs->image_name, lfa_error_strings[-res.a0]); - mutex_unlock(&lfa_lock); - - return res.a0; - } - if (!(res.a1 & LFA_PRIME_CALL_AGAIN)) - break; /* PRIME successful */ + down_read(&smc_lock); + reg.a1 = image->fw_seq_id; + 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), + lfa_error_string(res.a0)); + + return res.a0; + } + + if (res.a1 & LFA_PRIME_CALL_AGAIN) { /* SMC returned with call_again flag set */ if (ktime_before(ktime_get(), end)) { - udelay(LFA_PRIME_POLL_DELAY_US); - continue; + msleep_interruptible(LFA_PRIME_DELAY_MS); + goto retry; } - pr_err("LFA_PRIME for image %s timed out", attrs->image_name); - mutex_unlock(&lfa_lock); + pr_err("LFA_PRIME for image %s timed out", + get_image_name(image)); - ret = lfa_cancel(attrs); + ret = lfa_cancel(image); if (ret != 0) return ret; + return -ETIMEDOUT; } - mutex_unlock(&lfa_lock); - return ret; + return 0; } 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]); + struct fw_image *image = kobj_to_fw_image(kobj); - return sysfs_emit(buf, "%s\n", attrs->image_name); + 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 image_props *attrs = container_of(attr, struct image_props, - image_attrs[LFA_ATTR_ACT_CAPABLE]); + 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(®, ®); - return sysfs_emit(buf, "%d\n", attrs->activation_capable); + if (reg.a0 == LFA_SUCCESS) + 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) { - struct image_props *attrs = container_of(attr, struct image_props, - image_attrs[LFA_ATTR_ACT_PENDING]); - struct arm_smccc_1_2_regs reg = { 0 }; + 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 */ - 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)); + update_fw_image_pending(image); - return sysfs_emit(buf, "%d\n", attrs->activation_pending); + 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 image_props *attrs = container_of(attr, struct image_props, - image_attrs[LFA_ATTR_MAY_RESET_CPU]); + struct fw_image *image = kobj_to_fw_image(kobj); - return sysfs_emit(buf, "%d\n", attrs->may_reset_cpu); + 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 image_props *attrs = container_of(attr, struct image_props, - image_attrs[LFA_ATTR_CPU_RENDEZVOUS]); + struct fw_image *image = kobj_to_fw_image(kobj); - return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous); + 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 image_props *attrs = container_of(attr, struct image_props, - image_attrs[LFA_ATTR_FORCE_CPU_RENDEZVOUS]); + struct fw_image *image = kobj_to_fw_image(kobj); int ret; - ret = kstrtobool(buf, &attrs->cpu_rendezvous_forced); + ret = kstrtobool(buf, &image->cpu_rendezvous_forced); if (ret) return ret; @@ -432,73 +560,68 @@ static ssize_t force_cpu_rendezvous_store(struct kobject *kobj, 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]); + struct fw_image *image = kobj_to_fw_image(kobj); - return sysfs_emit(buf, "%d\n", attrs->cpu_rendezvous_forced); + 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 image_props *attrs = container_of(attr, struct image_props, - image_attrs[LFA_ATTR_CURRENT_VERSION]); + struct fw_image *image = kobj_to_fw_image(kobj); u32 maj, min; - maj = attrs->current_version >> 32; - min = attrs->current_version & 0xffffffff; + 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 image_props *attrs = container_of(attr, struct image_props, - image_attrs[LFA_ATTR_ACT_PENDING]); + struct fw_image *image = kobj_to_fw_image(kobj); 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. */ + down_read(&smc_lock); reg.a0 = LFA_1_0_FN_GET_INVENTORY; - reg.a1 = attrs->fw_seq_id; + reg.a1 = image->fw_seq_id; arm_smccc_1_2_invoke(®, ®); + up_read(&smc_lock); if (reg.a0 == LFA_SUCCESS) { - if (reg.a5 != 0 && attrs->activation_pending) - { - attrs->pending_version = reg.a5; + 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, "%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 image_props *attrs = container_of(attr, struct image_props, - image_attrs[LFA_ATTR_ACTIVATE]); + struct fw_image *image = kobj_to_fw_image(kobj); int ret; - ret = prime_fw_image(attrs); - if (ret) { - pr_err("Firmware prime failed: %s\n", - lfa_error_strings[-ret]); + ret = prime_fw_image(image); + if (ret) return -ECANCELED; - } - ret = activate_fw_image(attrs); - if (ret) { - pr_err("Firmware activation failed: %s\n", - lfa_error_strings[-ret]); + ret = activate_fw_image(image); + if (ret) return -ECANCELED; - } - pr_info("Firmware activation succeeded\n"); + pr_info("%s: successfully activated\n", get_image_name(image)); return count; } @@ -506,17 +629,38 @@ static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr, 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 fw_image *image = kobj_to_fw_image(kobj); int ret; - ret = lfa_cancel(attrs); + ret = lfa_cancel(image); if (ret != 0) return ret; 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), @@ -527,91 +671,94 @@ 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) +{ + 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 image_props *attrs, *tmp; + 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(attrs, tmp, &lfa_fw_images, image_node) - delete_fw_image_node(attrs); + 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) + u32 image_flags, u64 reg_current_ver, + u64 reg_pending_ver) { - const char *image_name = "(unknown)"; - struct image_props *attrs; - int ret; + 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. */ - 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, + 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); - attrs = kzalloc(sizeof(*attrs), GFP_KERNEL); - if (!attrs) + image = kzalloc_obj(*image); + if (!image) return -ENOMEM; - for (int i = 0; i < ARRAY_SIZE(fw_images_uuids); i++) { + 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; - 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, + 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, + "%s", fw_uuid)) { + kobject_put(&image->kobj); - /* - * 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; - } + return -ENOMEM; } - list_add(&attrs->image_node, &lfa_fw_images); - return ret; + return 0; } static int update_fw_images_tree(void) { - struct arm_smccc_1_2_regs reg = { 0 }; + struct arm_smccc_1_2_regs reg = { 0 }, res; struct uuid_regs image_uuid; - struct image_props *attrs; + struct kobject *kobj; char image_id_str[40]; int ret, num_of_components; @@ -626,22 +773,26 @@ static int update_fw_images_tree(void) * 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); + 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.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; + 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, - reg.a3, reg.a4, reg.a5); + ret = update_fw_image_node(image_id_str, i, res.a3, + res.a4, res.a5); if (ret) return ret; } @@ -653,115 +804,190 @@ static int update_fw_images_tree(void) * _store() handler, so have to postpone the list removal to a * workqueue. */ - INIT_WORK(&fw_images_update_work, remove_invalid_fw_images); queue_work(fw_images_update_wq, &fw_images_update_work); return 0; } -#if defined(CONFIG_ACPI) -static void lfa_notify_handler(acpi_handle handle, u32 event, void *data) +/* + * Go through all FW images in a loop and trigger activation + * 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. + */ +static int activate_pending_image(void) { - struct image_props *attrs = NULL; + struct kobject *kobj; + bool found_pending = false; + struct fw_image *image; 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; + 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); + + if (image->fw_seq_id == -1) + continue; /* Invalid FW component */ + + _update_fw_image_pending(image); + if (image->activation_capable && image->activation_pending && + image->auto_activate) { + found_pending = true; break; } + } + spin_unlock(&lfa_kset->list_lock); + up_read(&smc_lock); - 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); + 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())) + ; - return; + if (ret != -ENOENT) + pr_warn("notified image activation failed: %d\n", ret); } -static int lfa_probe(struct platform_device *pdev) +static int lfa_register_acpi(struct device *dev) { + struct acpi_device *acpi_dev; + acpi_handle handle; acpi_status status; - acpi_handle handle = ACPI_HANDLE(&pdev->dev); - if (!handle) + + 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 if LFA updates are available */ - status = acpi_install_notify_handler(handle, - ACPI_DEVICE_NOTIFY, lfa_notify_handler, pdev); - if (ACPI_FAILURE(status)) + /* 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(struct platform_device *pdev) { - acpi_handle handle = ACPI_HANDLE(&pdev->dev); +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_notify_handler); + 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 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), - }, -}; +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; + + if (!acpi_disabled) { + ret = lfa_register_acpi(&fdev->dev); + if (ret != -ENODEV) { + if (!ret) + pr_info("registered LFA ACPI notification\n"); + return ret; + } + } + + ret = lfa_register_dt(&fdev->dev); + if (!ret) + pr_info("registered LFA DT notification interrupt\n"); + if (ret != -ENODEV) + return ret; + + return 0; +} + +static void lfa_faux_remove(struct faux_device *fdev) +{ + lfa_remove_acpi(&fdev->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 }; @@ -774,33 +1000,39 @@ static int __init lfa_init(void) 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); + 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); - pr_info("Live Firmware Activation: detected v%ld.%ld\n", - reg.a0 >> 16, reg.a0 & 0xffff); + init_image_default_attrs(); + lfa_kset = kset_create_and_add("lfa", NULL, firmware_kobj); + if (!lfa_kset) + return -ENOMEM; -#if defined(CONFIG_ACPI) - err = platform_driver_register(&lfa_driver); - if (err < 0) - pr_err("Platform driver register failed"); -#endif + /* + * 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); - lfa_dir = kobject_create_and_add("lfa", firmware_kobj); - if (!lfa_dir) - return -ENOMEM; + init_rwsem(&smc_lock); - mutex_lock(&lfa_lock); err = update_fw_images_tree(); - if (err != 0) - kobject_put(lfa_dir); + if (err != 0) { + kset_unregister(lfa_kset); + destroy_workqueue(fw_images_update_wq); + } - mutex_unlock(&lfa_lock); return err; } module_init(lfa_init); @@ -809,15 +1041,9 @@ 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); -#if defined(CONFIG_ACPI) - platform_driver_unregister(&lfa_driver); -#endif + kset_unregister(lfa_kset); + faux_device_destroy(lfa_dev); } module_exit(lfa_exit);