Linux LED subsystem development
 help / color / mirror / Atom feed
From: Armin Wolf <W_Armin@gmx.de>
To: sashiko-reviews@lists.linux.dev
Cc: linux-leds@vger.kernel.org, lee@kernel.org
Subject: Re: [PATCH 08/10] leds: class: Split led_classdev_register_ext()
Date: Sun, 18 Oct 2026 19:14:06 +0200	[thread overview]
Message-ID: <bc1371ba-1355-4e45-be2c-d3e806c485a9@gmx.de> (raw)
In-Reply-To: <sashiko-outbox-176189@kernel.org>

Am 18.10.26 um 16:15 schrieb sashiko-bot@kernel.org:

> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] leds: dangling led_cdev->dev pointer on error in led_classdev_init()
> - [High] leds: premature device_set_node() in led_classdev_init() breaks fw_devlink
> --
>
> commit 7efb333e9420ea13e705d9a114384e841e92d2b2
> Author: Armin Wolf <W_Armin@gmx.de>
>
> leds: class: Split led_classdev_register_ext()
>
> This commit splits led_classdev_register_ext() into led_classdev_init() and
> led_classdev_add() so that LED class devices can be initialized before
> being added to the subsystem.
>
>> diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
>> index 77a0fce039528..ace417a2388e0 100644
>> --- a/drivers/leds/led-class.c
>> +++ b/drivers/leds/led-class.c
> [ ... ]
>
>> @@ -539,23 +550,54 @@ int led_classdev_register_ext(struct device *parent,
>>   	if (!led_cdev->max_brightness)
>>   		led_cdev->max_brightness = LED_FULL;
>>   
>> -	mutex_lock(&led_cdev->led_access);
>> -	led_cdev->dev = device_create_with_groups(&leds_class, parent, 0, led_cdev,
>> -						  led_cdev->groups, "%s", final_name);
>> -	if (IS_ERR(led_cdev->dev)) {
>> -		mutex_unlock(&led_cdev->led_access);
>> -		return PTR_ERR(led_cdev->dev);
>> +	led_cdev->dev = kzalloc_obj(*led_cdev->dev);
>> +	if (!led_cdev->dev)
>> +		return -ENOMEM;
>> +
>> +	device_initialize(led_cdev->dev);
>> +	led_cdev->dev->class = &leds_class;
>> +	led_cdev->dev->parent = parent;
>> +	led_cdev->dev->groups = led_cdev->groups;
>> +	dev_set_drvdata(led_cdev->dev, led_cdev);
>> +	ret = kobject_set_name(&led_cdev->dev->kobj, "%s", final_name);
>> +	if (ret < 0)
>> +	{
>> +		put_device(led_cdev->dev);
>> +		return ret;
>>   	}
> [Severity: High]
> Does this leave led_cdev->dev as a dangling pointer when kobject_set_name()
> fails here, or when led_classdev_add() fails in led_classdev_register_ext()
> below?
>
> Previously, led_cdev->dev was assigned the return value of
> device_create_with_groups(), so on any failure it remained either NULL or an
> ERR_PTR(). Commit 1dbb9fb4082ce2 ("leds: Allow to call
> led_classdev_unregister() unconditionally") added an IS_ERR_OR_NULL() check
> to led_classdev_unregister() so drivers can call it unconditionally in their
> error and removal paths:
>
> drivers/leds/led-class.c:led_classdev_unregister() {
> 	if (IS_ERR_OR_NULL(led_cdev->dev))
> 		return;
> ...
> 	led_stop_software_blink(led_cdev);
> ...
> 	flush_work(&led_cdev->set_brightness_work);
>
> 	device_unregister(led_cdev->dev);
>
> 	down_write(&leds_list_lock);
> 	list_del(&led_cdev->node);
> 	up_write(&leds_list_lock);
> ...
> }
>
> Many drivers rely on this guarantee during error unwind or removal, such as
> asus_wmi_led_init() -> asus_wmi_led_exit() in drivers/platform/x86/asus-wmi.c,
> asus_led_init() -> asus_led_exit() in drivers/platform/x86/asus-laptop.c, and
> samsung_leds_init() -> samsung_leds_exit() in
> drivers/platform/x86/samsung-laptop.c.
>
> With this change, put_device(led_cdev->dev) frees the device via
> led_classdev_release(), but led_cdev->dev is not reset to NULL. A subsequent
> call to led_classdev_unregister() will bypass the IS_ERR_OR_NULL(led_cdev->dev)
> check, operate on uninitialized blink_timer and set_brightness_work structures
> if kobject_set_name() failed before led_init_core(), trigger a use-after-free
> and double-free in device_unregister(led_cdev->dev), and crash in
> list_del(&led_cdev->node).
>
>>   
>>   	if (init_data && init_data->fwnode)
>>   		device_set_node(led_cdev->dev, init_data->fwnode);
> [Severity: High]
> Could calling device_set_node() in led_classdev_init() before device_add() is
> called in led_classdev_add() break fw_devlink for child LED nodes?
>
> Previously, device_create_with_groups() called device_add() while
> led_cdev->dev->fwnode was still NULL, and device_set_node() was only called
> after device_add() returned. When the parent LED controller finished probing,
> device_links_driver_bound() -> fw_devlink_pickup_dangling_consumers() marked
> the child LED fwnodes with FWNODE_FLAG_NOT_DEVICE and attached consumer links
> to the parent LED controller device.
>
> Now that led_cdev->dev->fwnode is populated before led_classdev_add() calls
> device_add(led_cdev->dev), this block in device_add() will trigger during the
> parent driver's probe:
>
> drivers/base/core.c:device_add() {
> ...
> 	if (dev->fwnode && !dev->fwnode->dev) {
> 		dev->fwnode->dev = dev;
> 		fw_devlink_link_device(dev);
> 	}
> ...
> }
>
> Because led_cdev->dev is a class device that never binds to a driver:
>
> 1. __fw_devlink_link_to_consumers(led_cdev->dev) converts consumer fwnode_links
>     on init_data->fwnode into managed device links with led_cdev->dev as the
>     supplier instead of the parent LED controller. During boot (while
>     fw_devlink_drv_reg_done is false), consumers remain blocked with
>     -EPROBE_DEFER until fw_devlink_drivers_done() runs, and are never linked to
>     the parent controller for runtime PM, suspend/resume, or unbind ordering.
>
> 2. __fw_devlink_link_to_suppliers(led_cdev->dev, fwnode) creates managed device
>     links with led_cdev->dev as the consumer for any supplier properties on the
>     child LED node (such as gpios in gpio-leds or pwms in pwm-leds). Since
>     led_cdev->dev never probes a driver, those links stay in DL_STATE_AVAILABLE
>     and prevent the GPIO or PWM suppliers from reaching sync_state().

I have no experience with devicetree, so i cannot verify if this is true, why do we call
device_set_node() in the first place after having already registered the device?

Thanks,
Armin Wolf

>> -
>> -	led_update_brightness(led_cdev);
>> -
>>   	led_cdev->wq = leds_wq;
>>   
>>   	led_init_core(led_cdev);
>>   
>> +	return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(led_classdev_init);
>> +
>> +/**
>> + * led_classdev_add - Add a new LED class device to the subsystem.
>> + * @led_cdev: the led_classdev structure for this device.
>> + *
>> + * Adds a new LED class device to the subsystem, allowing userspace applications
>> + * and/or kernel consumers to access it.
>> + *
>> + * Returns: Zero on success or a negative error code on failure.
>> + */
>> +int led_classdev_add(struct led_classdev *led_cdev)
>> +{
>> +	int ret;
>> +
>> +	guard(mutex)(&led_cdev->led_access);
>> +
>> +	ret = device_add(led_cdev->dev);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	led_update_brightness(led_cdev);
>> +
>>   	/* add to the list of leds */
>>   	down_write(&leds_list_lock);
>>   	list_add_tail(&led_cdev->node, &leds_list);
>> @@ -565,13 +607,36 @@ int led_classdev_register_ext(struct device *parent,
>>   	led_trigger_set_default(led_cdev);
>>   #endif
>>   
>> -	mutex_unlock(&led_cdev->led_access);
>> -
>> -	dev_dbg(parent, "Registered led device: %s\n",
>> -			led_cdev->name);
>> +	dev_dbg(led_cdev->dev->parent, "Registered led device: %s\n", led_cdev->name);
>>   
>>   	return 0;
>>   }
>> +EXPORT_SYMBOL_GPL(led_classdev_add);
>> +
>> +/**
>> + * led_classdev_register_ext - register a new object of led_classdev class
>> + *                             with init data.
>> + *
>> + * @parent: parent of LED device.
>> + * @led_cdev: the led_classdev structure for this device.
>> + * @init_data: LED class device initialization data.
>> + */
>> +int led_classdev_register_ext(struct device *parent,
>> +                              struct led_classdev *led_cdev,
>> +                              struct led_init_data *init_data)
>> +{
>> +	int ret;
>> +
>> +	ret = led_classdev_init(parent, led_cdev, init_data);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	ret = led_classdev_add(led_cdev);
>> +	if (ret < 0)
>> +		put_device(led_cdev->dev);
>> +
>> +	return ret;
>> +}
>>   EXPORT_SYMBOL_GPL(led_classdev_register_ext);
> [ ... ]
>
>> -- 
>> 2.51.2
>>

  reply	other threads:[~2026-10-18 17:14 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-18 13:46 [PATCH 00/10] leds: class: Support custom sysfs groups on multicolor and flash LEDs Armin Wolf
2026-10-18 13:46 ` [PATCH 01/10] leds: class: Constify sysfs attributes Armin Wolf
2026-10-18 13:57   ` sashiko-bot
2026-10-18 13:46 ` [PATCH 02/10] leds: multicolor: " Armin Wolf
2026-10-18 13:57   ` sashiko-bot
2026-10-18 13:46 ` [PATCH 03/10] leds: flash: " Armin Wolf
2026-10-18 13:56   ` sashiko-bot
2026-10-18 13:46 ` [PATCH 04/10] leds: class: Create "brightness_hw_changed" attribute atomically Armin Wolf
2026-10-18 14:11   ` sashiko-bot
2026-10-19 11:51   ` Rong Zhang
2026-10-20 20:55     ` Armin Wolf
2026-10-18 13:46 ` [PATCH 05/10] leds: class: Fix race condition when creating the device Armin Wolf
2026-10-18 13:58   ` sashiko-bot
2026-10-18 13:46 ` [PATCH 06/10] leds: class: Take the LED access lock when reading brightness_hw_changed Armin Wolf
2026-10-18 14:03   ` sashiko-bot
2026-10-18 17:08     ` Armin Wolf
2026-10-19 11:53       ` Rong Zhang
2026-10-20 21:01         ` Armin Wolf
2026-10-18 13:46 ` [PATCH 07/10] leds: class: Use correct type for brightness_hw_changed field Armin Wolf
2026-10-18 14:11   ` sashiko-bot
2026-10-18 13:46 ` [PATCH 08/10] leds: class: Split led_classdev_register_ext() Armin Wolf
2026-10-18 14:15   ` sashiko-bot
2026-10-18 17:14     ` Armin Wolf [this message]
2026-10-18 13:46 ` [PATCH 09/10] leds: Support custom sysfs groups on multicolor and flash LEDs Armin Wolf
2026-10-18 14:09   ` sashiko-bot
2026-10-19 11:53   ` Rong Zhang
2026-10-20 20:57     ` Armin Wolf
2026-10-18 13:46 ` [PATCH 10/10] leds: multicolor: Adjust maximum subled intensity during initialization Armin Wolf
2026-10-18 13:58   ` sashiko-bot
2026-10-19 11:54   ` Rong Zhang
2026-10-20 20:59     ` Armin Wolf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=bc1371ba-1355-4e45-be2c-d3e806c485a9@gmx.de \
    --to=w_armin@gmx.de \
    --cc=lee@kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox