[RFC PATCH 6/9] leds: Add trigger_may_offload attribute

From: Rong Zhang

Date: Fri Feb 27 2026 - 14:08:31 EST


Currently, we have multiple triggers implementing hw control. Only
"netdev" provides a custom attribute to determine if it's offloaded to
hardware (i.e., in hw control mode). For other triggers, there is no
obvious way userspace can determine the hw control state
programmatically. Moreover, userspace can't even query if an LED device
supports hw control and the name of hw control trigger.

Add a new attribute "trigger_may_offload" to the LED core, so that
userspace can determine:

- if the LED device supports hw control (supported => visible)
- which trigger is the hw control trigger
- if the hw control trigger is selected
- if the hw control trigger is in hw control

Signed-off-by: Rong Zhang <i@xxxxxxxx>
---
.../obsolete/sysfs-class-led-trigger-netdev | 15 +++++++
Documentation/ABI/testing/sysfs-class-led | 22 ++++++++++
.../testing/sysfs-class-led-trigger-netdev | 13 ------
Documentation/leds/leds-class.rst | 4 +-
drivers/leds/led-class.c | 22 ++++++++++
drivers/leds/led-triggers.c | 42 +++++++++++++++++++
drivers/leds/leds.h | 2 +
drivers/leds/trigger/ledtrig-netdev.c | 2 +
8 files changed, 108 insertions(+), 14 deletions(-)
create mode 100644 Documentation/ABI/obsolete/sysfs-class-led-trigger-netdev

diff --git a/Documentation/ABI/obsolete/sysfs-class-led-trigger-netdev b/Documentation/ABI/obsolete/sysfs-class-led-trigger-netdev
new file mode 100644
index 0000000000000..d1a7713d45700
--- /dev/null
+++ b/Documentation/ABI/obsolete/sysfs-class-led-trigger-netdev
@@ -0,0 +1,15 @@
+What: /sys/class/leds/<led>/offloaded
+Date: March 2026
+Contact: linux-leds@xxxxxxxxxxxxxxx
+Description:
+ Communicate whether the LED trigger modes are offloaded to
+ hardware or whether software fallback is used.
+
+ If 0, the LED is using software fallback to blink.
+
+ If 1, the LED blinking in requested mode is offloaded to
+ hardware.
+
+ Since 7.1, /sys/class/leds/<led>/trigger_may_offload provides
+ a generic method to query the offloaded state of supported
+ trigger, so this has been deprecated.
diff --git a/Documentation/ABI/testing/sysfs-class-led b/Documentation/ABI/testing/sysfs-class-led
index 0313b82644f24..403f411b09036 100644
--- a/Documentation/ABI/testing/sysfs-class-led
+++ b/Documentation/ABI/testing/sysfs-class-led
@@ -78,6 +78,28 @@ Description:
(which would often be configured in the device tree for the
hardware).

+What: /sys/class/leds/<led>/trigger_may_offload
+Date: March 2026
+KernelVersion: 7.1
+Contact: linux-leds@xxxxxxxxxxxxxxx
+Description:
+ Names and states of triggers that may be offloaded to hardware.
+ Such triggers are also called "hw control trigger" in some
+ context.
+
+ Only exists when the LED supports trigger offload.
+
+ Returns a list of hw control triggers on read. The optional
+ brackets around the trigger name indicate the state of the
+ current trigger:
+
+ - `foo_trigger`: the trigger is not selected.
+ - `<foo_trigger>`: the trigger is selected, but falls back to
+ software blink for some reason (e.g., incompatible trigger
+ parameters)
+ - `[foo_trigger]`: the trigger is selected and offloaded to
+ hardware.
+
What: /sys/class/leds/<led>/inverted
Date: January 2011
KernelVersion: 2.6.38
diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-netdev b/Documentation/ABI/testing/sysfs-class-led-trigger-netdev
index ed46b37ab8a28..396d37a4b820d 100644
--- a/Documentation/ABI/testing/sysfs-class-led-trigger-netdev
+++ b/Documentation/ABI/testing/sysfs-class-led-trigger-netdev
@@ -62,19 +62,6 @@ Description:
When offloaded is true, the blink interval is controlled by
hardware and won't reflect the value set in interval.

-What: /sys/class/leds/<led>/offloaded
-Date: Jun 2023
-KernelVersion: 6.5
-Contact: linux-leds@xxxxxxxxxxxxxxx
-Description:
- Communicate whether the LED trigger modes are offloaded to
- hardware or whether software fallback is used.
-
- If 0, the LED is using software fallback to blink.
-
- If 1, the LED blinking in requested mode is offloaded to
- hardware.
-
What: /sys/class/leds/<led>/link_10
Date: Jun 2023
KernelVersion: 6.5
diff --git a/Documentation/leds/leds-class.rst b/Documentation/leds/leds-class.rst
index 84665200a88dc..cf7733e30bace 100644
--- a/Documentation/leds/leds-class.rst
+++ b/Documentation/leds/leds-class.rst
@@ -177,7 +177,9 @@ limited to blink but also to turn off or on autonomously.
To support this feature, a LED needs to implement various additional
ops and needs to declare specific support for the supported triggers.

-With hw control we refer to the LED driven by hardware.
+With hw control we refer to the LED driven by hardware. In hw control mode,
+the current trigger is offloaded to hardware. The `trigger_may_offload`
+attribute can be used to query supported triggers and their states.

LED driver must define the following value to support hw control:

diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index 0fa45f22246e3..8a661e2616f06 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -90,8 +90,30 @@ static const struct bin_attribute *const led_trigger_bin_attrs[] = {
&bin_attr_trigger,
NULL,
};
+
+static DEVICE_ATTR(trigger_may_offload, 0644, led_trigger_may_offload_show, NULL);
+static struct attribute *led_trigger_attrs[] = {
+ &dev_attr_trigger_may_offload.attr,
+ NULL,
+};
+static umode_t led_trigger_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int idx)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+ if (attr == &dev_attr_trigger_may_offload.attr &&
+ !led_cdev->hw_control_trigger)
+ return 0;
+
+ return attr->mode;
+}
+
static const struct attribute_group led_trigger_group = {
.bin_attrs = led_trigger_bin_attrs,
+ .attrs = led_trigger_attrs,
+ .is_visible = led_trigger_is_visible,
};
#endif

diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index 3066bc91a5f94..f8100381fc684 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -346,6 +346,48 @@ void led_load_hw_control_trigger(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_load_hw_control_trigger);

+/*
+ * Caller must ensure led_cdev->trigger_lock held,
+ * and led_cdev->trigger->name must match led_cdev->hw_control_trigger.
+ */
+static bool led_trigger_get_offloaded(struct led_classdev *led_cdev)
+{
+ if (likely(led_cdev->trigger->offloaded))
+ return led_cdev->trigger->offloaded(led_cdev);
+
+ dev_err_once(led_cdev->dev,
+ "hw control trigger %s doesn't implement offloaded(), this is a bug\n",
+ led_cdev->trigger->name);
+ return false;
+}
+
+ssize_t led_trigger_may_offload_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ bool hit, offloaded = false;
+ int len;
+
+ mutex_lock(&led_cdev->led_access);
+ down_read(&led_cdev->trigger_lock);
+
+ hit = led_cdev->trigger && led_match_hw_control_trigger(led_cdev, led_cdev->trigger);
+ if (hit)
+ offloaded = led_trigger_get_offloaded(led_cdev);
+
+ /* [offloaded] <active_but_not_offloaded> inactive */
+ len = led_trigger_snprintf(buf, PAGE_SIZE,
+ hit ? (offloaded ? "[%s]\n" : "<%s>\n")
+ : "%s\n",
+ led_cdev->hw_control_trigger);
+
+ up_read(&led_cdev->trigger_lock);
+ mutex_unlock(&led_cdev->led_access);
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(led_trigger_may_offload_show);
+
/* LED Trigger Interface */

int led_trigger_register(struct led_trigger *trig)
diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
index e85afd4d04fd0..ee0dbddd411ec 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -28,6 +28,8 @@ ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count);
+ssize_t led_trigger_may_offload_show(struct device *dev,
+ struct device_attribute *attr, char *buf);

extern struct rw_semaphore leds_list_lock;
extern struct list_head leds_list;
diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
index edde167b60a54..0a4f4469ff9b0 100644
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -482,6 +482,8 @@ static ssize_t offloaded_show(struct device *dev,
{
struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);

+ dev_warn_once(dev, "offloaded attribute has been deprecated, see trigger_may_offload.\n");
+
return sprintf(buf, "%d\n", trigger_data->hw_control);
}

--
2.51.0