lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <a58eeebf-7678-4003-9c30-e87d2540d121@nvidia.com>
Date: Mon, 13 Oct 2025 14:09:28 -0700
From: Vedashree Vidwans <vvidwans@...dia.com>
To: Andre Przywara <andre.przywara@....com>
Cc: salman.nabi@....com, lpieralisi@...nel.org, mark.rutland@....com,
 sudeep.holla@....com, ardb@...nel.org, chao.gao@...el.com,
 linux-arm-kernel@...ts.infradead.org, linux-coco@...ts.linux.dev,
 linux-kernel@...r.kernel.org, sdonthineni@...dia.com, vsethi@...dia.com,
 vwadekar@...dia.com
Subject: Re: [RFC PATCH 2/3] firmware: smccc: LFA: refactor, add device node
 support



On 10/10/25 17:03, Andre Przywara wrote:
> On Wed, 8 Oct 2025 19:09:06 +0000
> Vedashree Vidwans <vvidwans@...dia.com> wrote:
> 
> Hi Vedashree,
> 
> thanks for sharing this code, that's much appreciated! I wonder whether
> it's possible to split this patch up, as it's doing multiple things at
> once, and it's harder to follow, review and comment on.
> The bullet points below basically give away how to split this: first do
> the refactors and fixes, then add each feature, in a separate patch.
> 
I understand, let me split the patches in next iteration.> Just one 
general comment for now ...
> 
>> - Add support for LFA device node in the kernel driver. Implement
> 
> That "device node" put me off a bit, do you mean you register a platform
> device, to connect this to that ACPI node?
> I wonder if we can use this new faux device instead of a platform
> device, since it's not a real device? Or maybe even query the ACPI or
> DT nodes without a device at all, like using of_find_compatible_node()
> or something?
> 
> Cheers,
> Andre
> 
I haven't completely understood your suggestion/question about using a 
faux device. But here is some context.
This patch registers the driver as a platform driver corresponding to 
device "arm,armhf000".
As the spec recommends, we would have a "arm,armhf000" node in 
devicetree with appropriate interrupt and payload information.
The OS will invoke this driver when it finds corresponding device.
Could you please elaborate/add details for your question?

Regards,
Veda>> probe() to register LFA interrupt and threaded interrupt service
>> function.
>> - CPUs will be rendezvoused during activation.
>> - On IRQ, driver will query FW components then triggers activation of
>> capable and pending components.
>> - Mutex synchronization is implemented to avoid concurrent LFA updates
>> through interrupt and sysfs interfaces.
>> - Refactor LFA CANCEL logic into independent lfa_cancel() function.
>> - Enhance PRIME/ACTIVATION functions to touch watchdog and implement
>> timeouts.
>>
>> Signed-off-by: Vedashree Vidwans <vvidwans@...dia.com>
>> ---
>>   drivers/firmware/smccc/lfa_fw.c | 299 ++++++++++++++++++++++++++++----
>>   1 file changed, 262 insertions(+), 37 deletions(-)
>>
>> diff --git a/drivers/firmware/smccc/lfa_fw.c b/drivers/firmware/smccc/lfa_fw.c
>> index 49f7feb6a211b..b36b8d7457c30 100644
>> --- a/drivers/firmware/smccc/lfa_fw.c
>> +++ b/drivers/firmware/smccc/lfa_fw.c
>> @@ -16,7 +16,15 @@
>>   #include <linux/uuid.h>
>>   #include <linux/array_size.h>
>>   #include <linux/list.h>
>> -
>> +#include <linux/interrupt.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/acpi.h>
>> +#include <linux/nmi.h>
>> +#include <linux/ktime.h>
>> +#include <linux/delay.h>
>> +#include <linux/mutex.h>
>> +
>> +#define DRIVER_NAME	"ARM_LFA"
>>   #define LFA_ERROR_STRING(name) \
>>   	[name] = #name
>>   #undef pr_fmt
>> @@ -34,6 +42,18 @@
>>   #define LFA_1_0_FN_ACTIVATE		LFA_1_0_FN(5)
>>   #define LFA_1_0_FN_CANCEL		LFA_1_0_FN(6)
>>   
>> +/* CALL_AGAIN flags (returned in res.a1[0]) */
>> +#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
>> @@ -114,8 +134,9 @@ static const struct fw_image_uuid {
>>   };
>>   
>>   static struct kobject *lfa_dir;
>> +static DEFINE_MUTEX(lfa_lock);
>>   
>> -static int get_nr_lfa_components(void)
>> +static unsigned long get_nr_lfa_components(void)
>>   {
>>   	struct arm_smccc_1_2_regs args = { 0 };
>>   	struct arm_smccc_1_2_regs res = { 0 };
>> @@ -130,11 +151,40 @@ static int get_nr_lfa_components(void)
>>   	return res.a1;
>>   }
>>   
>> +static int lfa_cancel(void *data)
>> +{
>> +	struct image_props *attrs = data;
>> +	struct arm_smccc_1_2_regs args = { 0 };
>> +	struct arm_smccc_1_2_regs res = { 0 };
>> +
>> +	args.a0 = LFA_1_0_FN_CANCEL;
>> +	args.a1 = attrs->fw_seq_id;
>> +	arm_smccc_1_2_invoke(&args, &res);
>> +
>> +	/*
>> +	 * When firmware activation is called with "skip_cpu_rendezvous=1",
>> +	 * LFA_CANCEL can fail with LFA_BUSY if the activation could not be
>> +	 * cancelled.
>> +	 */
>> +	if (res.a0 == LFA_SUCCESS) {
>> +		pr_info("Activation cancelled for image %s",
>> +			attrs->image_name);
>> +	} else {
>> +		pr_err("Firmware activation could not be cancelled: %ld",
>> +		       (long)res.a0);
>> +		return -EIO;
>> +	}
>> +
>> +	return res.a0;
>> +}
>> +
>>   static int call_lfa_activate(void *data)
>>   {
>>   	struct image_props *attrs = data;
>>   	struct arm_smccc_1_2_regs args = { 0 };
>>   	struct arm_smccc_1_2_regs res = { 0 };
>> +	ktime_t end = ktime_add_us(ktime_get(), LFA_ACTIVATE_BUDGET_US);
>> +	int ret;
>>   
>>   	args.a0 = LFA_1_0_FN_ACTIVATE;
>>   	args.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
>> @@ -148,9 +198,32 @@ static int call_lfa_activate(void *data)
>>   	 */
>>   	args.a2 = !(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous);
>>   
>> -	do {
>> +	for (;;) {
>> +		/* Touch watchdog, ACTIVATE shouldn't take longer than watchdog_thresh */
>> +		touch_nmi_watchdog();
>>   		arm_smccc_1_2_invoke(&args, &res);
>> -	} while (res.a0 == 0 && res.a1 == 1);
>> +
>> +		if ((long)res.a0 < 0) {
>> +			pr_err("ACTIVATE for image %s failed: %s",
>> +				attrs->image_name, lfa_error_strings[-res.a0]);
>> +			return res.a0;
>> +		}
>> +		if (!(res.a1 & LFA_ACTIVATE_CALL_AGAIN))
>> +			break; /* ACTIVATE successful */
>> +
>> +		/* SMC returned with call_again flag set */
>> +		if (ktime_before(ktime_get(), end)) {
>> +			udelay(LFA_ACTIVATE_POLL_DELAY_US);
>> +			continue;
>> +		}
>> +
>> +		pr_err("ACTIVATE timed out for image %s", attrs->image_name);
>> +		ret = lfa_cancel(attrs);
>> +		if (ret == 0)
>> +			return -ETIMEDOUT;
>> +		else
>> +			return ret;
>> +	}
>>   
>>   	return res.a0;
>>   }
>> @@ -159,8 +232,24 @@ static int activate_fw_image(struct image_props *attrs)
>>   {
>>   	struct arm_smccc_1_2_regs args = { 0 };
>>   	struct arm_smccc_1_2_regs res = { 0 };
>> +	ktime_t end = ktime_add_us(ktime_get(), LFA_PRIME_BUDGET_US);
>>   	int ret;
>>   
>> +	if (attrs->may_reset_cpu) {
>> +		pr_err("Firmware component requires unsupported CPU reset");
>> +		return -EINVAL;
>> +	}
>> +
>> +	/*
>> +	 * We want to force CPU rendezvous if either cpu_rendezvous or
>> +	 * cpu_rendezvous_forced is set. The flag value is flipped as
>> +	 * it is called skip_cpu_rendezvous in the spec.
>> +	 */
>> +	if (!(attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous)) {
>> +		pr_warn("CPU rendezvous is expected to be selected.");
>> +		return -EAGAIN;
>> +	}
>> +
>>   	/*
>>   	 * LFA_PRIME/ACTIVATE will return 1 in res.a1 if the firmware
>>   	 * priming/activation is still in progress. In that case
>> @@ -169,20 +258,36 @@ static int activate_fw_image(struct image_props *attrs)
>>   	 */
>>   	args.a0 = LFA_1_0_FN_PRIME;
>>   	args.a1 = attrs->fw_seq_id; /* fw_seq_id under consideration */
>> -	do {
>> +	for (;;) {
>> +		/* Touch watchdog, PRIME shouldn't take longer than watchdog_thresh */
>> +		touch_nmi_watchdog();
>>   		arm_smccc_1_2_invoke(&args, &res);
>> -		if (res.a0 != LFA_SUCCESS) {
>> -			pr_err("LFA_PRIME failed: %s\n",
>> -				lfa_error_strings[-res.a0]);
>>   
>> +		if ((long)res.a0 < 0) {
>> +			pr_err("LFA_PRIME for image %s failed: %s\n",
>> +				attrs->image_name, lfa_error_strings[-res.a0]);
>>   			return res.a0;
>>   		}
>> -	} while (res.a1 == 1);
>> +		if (!(res.a1 & LFA_PRIME_CALL_AGAIN))
>> +			break; /* PRIME successful */
>>   
>> -	if (attrs->cpu_rendezvous_forced || attrs->cpu_rendezvous)
>> -		ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask);
>> -	else
>> -		ret = call_lfa_activate(attrs);
>> +		/* SMC returned with call_again flag set */
>> +		if (ktime_before(ktime_get(), end)) {
>> +			udelay(LFA_PRIME_POLL_DELAY_US);
>> +			continue;
>> +		}
>> +
>> +		pr_err("PRIME timed out for image %s", attrs->image_name);
>> +		ret = lfa_cancel(attrs);
>> +		if (ret == 0)
>> +			return -ETIMEDOUT;
>> +		else
>> +			return ret;
>> +	}
>> +
>> +	ret = stop_machine(call_lfa_activate, attrs, cpu_online_mask);
>> +	if (ret != 0)
>> +		return lfa_cancel(attrs);
>>   
>>   	return ret;
>>   }
>> @@ -286,23 +391,23 @@ static ssize_t activate_store(struct kobject *kobj, struct kobj_attribute *attr,
>>   					 image_attrs[LFA_ATTR_ACTIVATE]);
>>   	int ret;
>>   
>> -	if (attrs->may_reset_cpu) {
>> -		pr_err("Firmware component requires unsupported CPU reset\n");
>> -
>> -		return -EINVAL;
>> +	if (!mutex_trylock(&lfa_lock)) {
>> +		pr_err("Mutex locked, try again");
>> +		return -EAGAIN;
>>   	}
>>   
>>   	ret = activate_fw_image(attrs);
>>   	if (ret) {
>>   		pr_err("Firmware activation failed: %s\n",
>>   			lfa_error_strings[-ret]);
>> -
>> +		mutex_unlock(&lfa_lock);
>>   		return -ECANCELED;
>>   	}
>>   
>>   	pr_info("Firmware activation succeeded\n");
>>   
>>   	/* TODO: refresh image flags here*/
>> +	mutex_unlock(&lfa_lock);
>>   	return count;
>>   }
>>   
>> @@ -311,26 +416,11 @@ static ssize_t cancel_store(struct kobject *kobj, struct kobj_attribute *attr,
>>   {
>>   	struct image_props *attrs = container_of(attr, struct image_props,
>>   						 image_attrs[LFA_ATTR_CANCEL]);
>> -	struct arm_smccc_1_2_regs args = { 0 };
>> -	struct arm_smccc_1_2_regs res = { 0 };
>> -
>> -	args.a0 = LFA_1_0_FN_CANCEL;
>> -	args.a1 = attrs->fw_seq_id;
>> -	arm_smccc_1_2_invoke(&args, &res);
>> +	int ret;
>>   
>> -	/*
>> -	 * When firmware activation is called with "skip_cpu_rendezvous=1",
>> -	 * LFA_CANCEL can fail with LFA_BUSY if the activation could not be
>> -	 * cancelled.
>> -	 */
>> -	if (res.a0 == LFA_SUCCESS) {
>> -		pr_info("Activation cancelled for image %s\n",
>> -			attrs->image_name);
>> -	} else {
>> -		pr_err("Firmware activation could not be cancelled: %s\n",
>> -		       lfa_error_strings[-res.a0]);
>> -		return -EINVAL;
>> -	}
>> +	ret = lfa_cancel(attrs);
>> +	if (ret != 0)
>> +		return ret;
>>   
>>   	return count;
>>   }
>> @@ -418,6 +508,11 @@ static int create_fw_images_tree(void)
>>   	int ret, num_of_components;
>>   
>>   	num_of_components = get_nr_lfa_components();
>> +	if (num_of_components <= 0) {
>> +		pr_err("Error getting number of LFA components");
>> +		return -ENODEV;
>> +	}
>> +
>>   	args.a0 = LFA_1_0_FN_GET_INVENTORY;
>>   	for (int i = 0; i < num_of_components; i++) {
>>   		args.a1 = i; /* fw_seq_id under consideration */
>> @@ -437,6 +532,125 @@ static int create_fw_images_tree(void)
>>   	return 0;
>>   }
>>   
>> +static int refresh_fw_images_tree(void)
>> +{
>> +	int ret;
>> +	/*
>> +	 * Ideally, this function should invoke the GET_INVENTORY SMC
>> +	 * for each firmware image and update the corresponding details
>> +	 * in the firmware image tree node.
>> +	 * There are several edge cases to consider:
>> +	 *    - The number of firmware components may change.
>> +	 *    - The mapping between firmware sequence IDs and
>> +	 *      firmware image UUIDs may be modified.
>> +	 * As a result, it is possible that the firmware image tree nodes
>> +	 * will require updates. Additionally, GET_INVENTORY SMC provides
>> +	 * all current and revised information. Therefore, retaining the
>> +	 * existing fw_images_tree data is not justified. Reconstructing
>> +	 * the firmware images tree will simplify the code and keep data
>> +	 * up-to-date.
>> +	 */
>> +	// Clean current inventory details
>> +	clean_fw_images_tree();
>> +
>> +	// Update new inventory details
>> +	ret = create_fw_images_tree();
>> +	if (ret != 0)
>> +		kobject_put(lfa_dir);
>> +
>> +	return ret;
>> +}
>> +
>> +static irqreturn_t lfa_irq_thread(int irq, void *data)
>> +{
>> +	struct image_props *attrs = NULL;
>> +	int ret;
>> +
>> +	mutex_lock(&lfa_lock);
>> +
>> +	// Update new inventory details
>> +	ret = refresh_fw_images_tree();
>> +	if (ret != 0)
>> +		goto exit_unlock;
>> +
>> +	/*
>> +	 * Execute PRIME and ACTIVATE for each FW component
>> +	 * Start from first FW component
>> +	 */
>> +	list_for_each_entry(attrs, &lfa_fw_images, image_node) {
>> +		if ((!attrs->activation_capable) || (!attrs->activation_pending)) {
>> +			/* LFA not applicable for this FW component, continue to next component */
>> +			continue;
>> +		}
>> +
>> +		ret = activate_fw_image(attrs);
>> +		if (ret) {
>> +			pr_err("Firmware %s activation failed: %s\n",
>> +				attrs->image_name, lfa_error_strings[-ret]);
>> +			goto exit_unlock;
>> +		}
>> +
>> +		pr_info("Firmware %s activation succeeded", attrs->image_name);
>> +	}
>> +
>> +	// Update new inventory details
>> +	ret = refresh_fw_images_tree();
>> +	if (ret != 0)
>> +		goto exit_unlock;
>> +
>> +exit_unlock:
>> +	mutex_unlock(&lfa_lock);
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int __init lfa_probe(struct platform_device *pdev)
>> +{
>> +	int err;
>> +	unsigned int irq;
>> +
>> +	err = platform_get_irq_byname_optional(pdev, "fw-store-updated-interrupt");
>> +	if (err < 0)
>> +		err = platform_get_irq(pdev, 0);
>> +	if (err < 0) {
>> +		pr_err("Interrupt not found, functionality will be unavailable.");
>> +
>> +		/* Bail out without failing the driver. */
>> +		return 0;
>> +	}
>> +	irq = err;
>> +
>> +	err = request_threaded_irq(irq, NULL, lfa_irq_thread, IRQF_ONESHOT, DRIVER_NAME, NULL);
>> +	if (err != 0) {
>> +		pr_err("Interrupt setup failed, functionality will be unavailable.");
>> +
>> +		/* Bail out without failing the driver. */
>> +		return 0;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id lfa_of_ids[] = {
>> +	{ .compatible = "arm,armhf000", },
>> +	{ },
>> +};
>> +MODULE_DEVICE_TABLE(of, lfa_of_ids);
>> +
>> +static const struct acpi_device_id lfa_acpi_ids[] = {
>> +	{"ARMHF000"},
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(acpi, lfa_acpi_ids);
>> +
>> +static struct platform_driver lfa_driver = {
>> +	.probe = lfa_probe,
>> +	.driver = {
>> +		.name = DRIVER_NAME,
>> +		.of_match_table = lfa_of_ids,
>> +		.acpi_match_table = ACPI_PTR(lfa_acpi_ids),
>> +	},
>> +};
>> +
>>   static int __init lfa_init(void)
>>   {
>>   	struct arm_smccc_1_2_regs args = { 0 };
>> @@ -464,22 +678,33 @@ static int __init lfa_init(void)
>>   	pr_info("Arm Live Firmware Activation (LFA): detected v%ld.%ld\n",
>>   		res.a0 >> 16, res.a0 & 0xffff);
>>   
>> +	err = platform_driver_register(&lfa_driver);
>> +	if (err < 0)
>> +		pr_err("Platform driver register failed");
>> +
>> +
>>   	lfa_dir = kobject_create_and_add("lfa", firmware_kobj);
>>   	if (!lfa_dir)
>>   		return -ENOMEM;
>>   
>> +	mutex_lock(&lfa_lock);
>>   	err = create_fw_images_tree();
>>   	if (err != 0)
>>   		kobject_put(lfa_dir);
>>   
>> +	mutex_unlock(&lfa_lock);
>>   	return err;
>>   }
>>   module_init(lfa_init);
>>   
>>   static void __exit lfa_exit(void)
>>   {
>> +	mutex_lock(&lfa_lock);
>>   	clean_fw_images_tree();
>> +	mutex_unlock(&lfa_lock);
>> +
>>   	kobject_put(lfa_dir);
>> +	platform_driver_unregister(&lfa_driver);
>>   }
>>   module_exit(lfa_exit);
>>   
> 


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ