Please don't apply this patch. I realised that the Documentation/leds/index.rst needs to add the new uledtriggers.rst. I will make a patch v2 in due course. But I'd like to wait for any other feedback first. ---- On Thu, 06 Mar 2025 22:46:39 +1100 Craig McQueen wrote --- > This driver creates a userspace LED triggers driver similar to > uleds and uinput. > > New LED triggers s are created by opening /dev/uledtriggers and writing > a uledtriggers_user_dev struct. A new LED trigger is registered with the > name given in the struct. > > After the initial setup, writing an int value will set the trigger's > brightness, equivalent to calling led_trigger_event(). > > Alternatively, there are ioctls for setup, changing trigger brightness, > or doing blinking. > > Closing the file handle to /dev/uledtriggers will remove the LED > trigger. > --- > Documentation/leds/uledtriggers.rst | 36 +++ > drivers/leds/Kconfig | 9 + > drivers/leds/Makefile | 1 + > drivers/leds/uledtriggers.c | 384 ++++++++++++++++++++++++++++ > include/uapi/linux/uledtriggers.h | 123 +++++++++ > 5 files changed, 553 insertions(+) > create mode 100644 Documentation/leds/uledtriggers.rst > create mode 100644 drivers/leds/uledtriggers.c > create mode 100644 include/uapi/linux/uledtriggers.h > > diff --git a/Documentation/leds/uledtriggers.rst b/Documentation/leds/uledtriggers.rst > new file mode 100644 > index 000000000000..6ec5cbf8f13e > --- /dev/null > +++ b/Documentation/leds/uledtriggers.rst > @@ -0,0 +1,36 @@ > +====================== > +Userspace LED Triggers > +====================== > + > +The uledtriggers driver supports userspace LED triggers. This can be useful > +to create a more flexible architecture for applications to control LEDs. > + > + > +Usage > +===== > + > +When the driver is loaded, a character device is created at /dev/uledtriggers. > +To create a new LED trigger, open /dev/uledtriggers and write a > +uledtriggers_user_dev structure to it (found in kernel public header file > +linux/uledtriggers.h):: > + > + #define LED_TRIGGER_MAX_NAME_SIZE 50 > + > + struct uledtriggers_user_dev { > + char name[LED_TRIGGER_MAX_NAME_SIZE]; > + }; > + > +A new LED trigger will be created with the name given. The name can consist of > +alphanumeric, hyphen and underscore characters. > + > +After the initial setup, writing an int value will set the trigger's > +brightness, equivalent to calling led_trigger_event(). > + > +Alternatively, there are ioctls (defined in the public header file) for setup, > +changing trigger brightness, or doing blinking. > + > +The LED trigger will be removed when the open file handle to /dev/uledtriggers > +is closed. > + > +Multiple LED triggers are created by opening additional file handles to > +/dev/uledtriggers. > diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig > index 2b27d043921c..7cd2fbcb1aa5 100644 > --- a/drivers/leds/Kconfig > +++ b/drivers/leds/Kconfig > @@ -921,6 +921,15 @@ config LEDS_USER > support in kernel. To compile this driver as a module, choose 'm' here: > the module will be called uleds. > > +config LED_TRIGGERS_USER > + tristate "Userspace LED triggers support" > + depends on LEDS_CLASS > + select LEDS_TRIGGERS > + help > + This option enables support for userspace LED triggers. Say 'y' to enable > + this support in kernel. To compile this driver as a module, choose 'm' > + here: the module will be called uledtriggers. > + > config LEDS_NIC78BX > tristate "LED support for NI PXI NIC78bx devices" > depends on LEDS_CLASS > diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile > index 6ad52e219ec6..c71569a59b15 100644 > --- a/drivers/leds/Makefile > +++ b/drivers/leds/Makefile > @@ -108,6 +108,7 @@ obj-$(CONFIG_LEDS_SPI_BYTE) += leds-spi-byte.o > > # LED Userspace Drivers > obj-$(CONFIG_LEDS_USER) += uleds.o > +obj-$(CONFIG_LED_TRIGGERS_USER) += uledtriggers.o > > # Flash and Torch LED Drivers > obj-$(CONFIG_LEDS_CLASS_FLASH) += flash/ > diff --git a/drivers/leds/uledtriggers.c b/drivers/leds/uledtriggers.c > new file mode 100644 > index 000000000000..4fdb8cf4c82d > --- /dev/null > +++ b/drivers/leds/uledtriggers.c > @@ -0,0 +1,384 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Userspace LED triggers driver > + * > + * Copyright (C) 2025 Craig McQueen craig@xxxxxxxxxx> > + */ > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > + > +#define ULEDTRIGGERS_NAME "uledtriggers" > + > +enum uledtriggers_state { > + ULEDTRIGGERS_STATE_UNKNOWN, > + ULEDTRIGGERS_STATE_REGISTERED, > +}; > + > +enum uledtriggers_trig_state { > + TRIG_STATE_EVENT, > + TRIG_STATE_BLINK, > +}; > + > +struct uledtriggers_device { > + struct uledtriggers_user_dev user_dev; > + struct led_trigger led_trigger; > + struct mutex mutex; > + enum uledtriggers_state state; > + enum uledtriggers_trig_state trig_state; > + int brightness; > + unsigned long trig_delay_on; > + unsigned long trig_delay_off; > +}; > + > +static struct miscdevice uledtriggers_misc; > + > +static int set_led_trigger(struct uledtriggers_device *udev) > +{ > + int retval = 0; > + enum uledtriggers_trig_state trig_state; > + > + retval = mutex_lock_interruptible(&udev->mutex); > + if (retval) > + return retval; > + > + trig_state = udev->trig_state; > + switch (trig_state) { > + default: > + case TRIG_STATE_EVENT: > + led_trigger_event(&udev->led_trigger, udev->brightness); > + break; > + case TRIG_STATE_BLINK: > + led_trigger_blink(&udev->led_trigger, udev->trig_delay_on, udev->trig_delay_off); > + break; > + } > + mutex_unlock(&udev->mutex); > + > + return retval; > +} > + > +/* > + * When an LED is connected to the trigger, this 'activate' function runs and > + * sets the initial state of the LED. > + */ > +static int uledtriggers_trig_activate(struct led_classdev *led_cdev) > +{ > + struct led_trigger *trig; > + struct uledtriggers_device *udev; > + > + trig = led_cdev->trigger; > + udev = container_of(trig, struct uledtriggers_device, led_trigger); > + return set_led_trigger(udev); > +} > + > +static int uledtriggers_open(struct inode *inode, struct file *file) > +{ > + struct uledtriggers_device *udev; > + > + udev = kzalloc(sizeof(*udev), GFP_KERNEL); > + if (!udev) > + return -ENOMEM; > + > + mutex_init(&udev->mutex); > + udev->state = ULEDTRIGGERS_STATE_UNKNOWN; > + > + file->private_data = udev; > + stream_open(inode, file); > + > + return 0; > +} > + > +/* > + * Name validation: Allow only alphanumeric, hyphen or underscore. > + */ > +static bool is_trigger_name_valid(const char * name) > +{ > + size_t i; > + > + if (name[0] == '\0') > + return false; > + > + for (i = 0; i < TRIG_NAME_MAX; i++) { > + if (name[i] == '\0') > + break; > + if (!isalnum(name[i]) && name[i] != '-' && name[i] != '_') > + return false; > + } > + /* Length check. */ > + return (i < TRIG_NAME_MAX); > +} > + > +static int dev_setup(struct uledtriggers_device *udev, const char __user *buffer) > +{ > + const char *name; > + int retval; > + > + retval = mutex_lock_interruptible(&udev->mutex); > + if (retval) > + return retval; > + > + if (udev->state == ULEDTRIGGERS_STATE_REGISTERED) { > + retval = -EBUSY; > + goto out; > + } > + > + if (copy_from_user(&udev->user_dev, buffer, > + sizeof(struct uledtriggers_user_dev))) { > + retval = -EFAULT; > + goto out; > + } > + > + name = udev->user_dev.name; > + if (!is_trigger_name_valid(name)) { > + retval = -EINVAL; > + goto out; > + } > + > + udev->led_trigger.name = udev->user_dev.name; > + udev->led_trigger.activate = uledtriggers_trig_activate; > + retval = led_trigger_register(&udev->led_trigger); > + if (retval < 0) { > + udev->led_trigger.name = NULL; > + goto out; > + } > + > + udev->state = ULEDTRIGGERS_STATE_REGISTERED; > + > +out: > + mutex_unlock(&udev->mutex); > + > + return retval; > +} > + > +static int write_brightness(struct uledtriggers_device *udev, const char __user *buffer) > +{ > + int retval; > + int brightness; > + > + retval = mutex_lock_interruptible(&udev->mutex); > + if (retval) > + return retval; > + > + if (udev->state != ULEDTRIGGERS_STATE_REGISTERED) { > + retval = -EBUSY; > + goto out; > + } > + > + if (copy_from_user(&brightness, buffer, > + sizeof(brightness))) { > + retval = -EFAULT; > + goto out; > + } > + > + udev->trig_delay_on = 0u; > + udev->trig_delay_off = 0u; > + udev->brightness = brightness; > + udev->trig_state = TRIG_STATE_EVENT; > + led_trigger_event(&udev->led_trigger, brightness); > + > +out: > + mutex_unlock(&udev->mutex); > + > + return retval; > +} > + > +static ssize_t uledtriggers_write(struct file *file, const char __user *buffer, > + size_t count, loff_t *ppos) > +{ > + struct uledtriggers_device *udev = file->private_data; > + int retval; > + > + if (count == 0) > + return 0; > + > + switch (udev->state) { > + case ULEDTRIGGERS_STATE_UNKNOWN: > + if (count != sizeof(struct uledtriggers_user_dev)) { > + return -EINVAL; > + } > + retval = dev_setup(udev, buffer); > + if (retval < 0) > + return retval; > + return count; > + case ULEDTRIGGERS_STATE_REGISTERED: > + if (count != sizeof(int)) { > + return -EINVAL; > + } > + retval = write_brightness(udev, buffer); > + if (retval < 0) > + return retval; > + return count; > + default: > + return -EBADFD; > + } > +} > + > +static long uledtriggers_ioctl(struct file *file, unsigned int cmd, unsigned long arg) > +{ > + struct uledtriggers_device *udev = file->private_data; > + struct uledtriggers_blink blink; > + struct uledtriggers_blink_oneshot blink_oneshot; > + int brightness; > + int retval = 0; > + > + /* > + * the direction is a bitmask, and VERIFY_WRITE catches R/W > + * transfers. `Direction' is user-oriented, while > + * access_ok is kernel-oriented, so the concept of "read" and > + * "write" is reversed > + */ > + retval = 0; > + if (_IOC_DIR(cmd) & _IOC_READ) > + retval = !access_ok((void __user *)arg, _IOC_SIZE(cmd)); > + else if (_IOC_DIR(cmd) & _IOC_WRITE) > + retval = !access_ok((void __user *)arg, _IOC_SIZE(cmd)); > + if (retval) > + return -EFAULT; > + > + switch (cmd) { > + case ULEDTRIGGERS_IOC_DEV_SETUP: > + retval = dev_setup(udev, (const char __user *)arg); > + break; > + > + case ULEDTRIGGERS_IOC_OFF: > + retval = mutex_lock_interruptible(&udev->mutex); > + if (retval) > + return retval; > + if (udev->state != ULEDTRIGGERS_STATE_REGISTERED) { > + mutex_unlock(&udev->mutex); > + return -EINVAL; > + } > + udev->trig_delay_on = 0u; > + udev->trig_delay_off = 0u; > + udev->brightness = 0; > + udev->trig_state = TRIG_STATE_EVENT; > + led_trigger_event(&udev->led_trigger, LED_OFF); > + mutex_unlock(&udev->mutex); > + break; > + > + case ULEDTRIGGERS_IOC_ON: > + retval = mutex_lock_interruptible(&udev->mutex); > + if (retval) > + return retval; > + if (udev->state != ULEDTRIGGERS_STATE_REGISTERED) { > + mutex_unlock(&udev->mutex); > + return -EINVAL; > + } > + udev->trig_delay_on = 0u; > + udev->trig_delay_off = 0u; > + udev->brightness = LED_FULL; > + udev->trig_state = TRIG_STATE_EVENT; > + led_trigger_event(&udev->led_trigger, LED_FULL); > + mutex_unlock(&udev->mutex); > + break; > + > + case ULEDTRIGGERS_IOC_EVENT: > + retval = copy_from_user(&brightness, > + (int __user *)arg, > + sizeof(brightness)); > + if (retval) > + return retval; > + retval = mutex_lock_interruptible(&udev->mutex); > + if (retval) > + return retval; > + if (udev->state != ULEDTRIGGERS_STATE_REGISTERED) { > + mutex_unlock(&udev->mutex); > + return -EINVAL; > + } > + udev->trig_delay_on = 0u; > + udev->trig_delay_off = 0u; > + udev->brightness = brightness; > + udev->trig_state = TRIG_STATE_EVENT; > + led_trigger_event(&udev->led_trigger, brightness); > + mutex_unlock(&udev->mutex); > + break; > + > + case ULEDTRIGGERS_IOC_BLINK: > + retval = copy_from_user(&blink, > + (struct uledtriggers_blink __user *)arg, > + sizeof(blink)); > + if (retval) > + return retval; > + retval = mutex_lock_interruptible(&udev->mutex); > + if (retval) > + return retval; > + if (udev->state != ULEDTRIGGERS_STATE_REGISTERED) { > + mutex_unlock(&udev->mutex); > + return -EINVAL; > + } > + udev->trig_delay_on = blink.delay_on; > + udev->trig_delay_off = blink.delay_off; > + udev->brightness = LED_FULL; > + udev->trig_state = TRIG_STATE_BLINK; > + led_trigger_blink(&udev->led_trigger, blink.delay_on, blink.delay_off); > + mutex_unlock(&udev->mutex); > + break; > + > + case ULEDTRIGGERS_IOC_BLINK_ONESHOT: > + retval = copy_from_user(&blink_oneshot, > + (struct uledtriggers_blink_oneshot __user *)arg, > + sizeof(blink_oneshot)); > + if (retval) > + return retval; > + if (blink_oneshot.__unused) > + return -EINVAL; > + retval = mutex_lock_interruptible(&udev->mutex); > + if (retval) > + return retval; > + if (udev->state != ULEDTRIGGERS_STATE_REGISTERED) { > + mutex_unlock(&udev->mutex); > + return -EINVAL; > + } > + udev->trig_delay_on = 0u; > + udev->trig_delay_off = 0u; > + udev->brightness = blink_oneshot.invert ? LED_FULL : LED_OFF; > + udev->trig_state = TRIG_STATE_EVENT; > + led_trigger_blink_oneshot(&udev->led_trigger, blink_oneshot.delay_on, blink_oneshot.delay_off, blink_oneshot.invert); > + mutex_unlock(&udev->mutex); > + break; > + > + default: > + retval = -ENOIOCTLCMD; > + break; > + } > + > + return retval; > +} > + > +static int uledtriggers_release(struct inode *inode, struct file *file) > +{ > + struct uledtriggers_device *udev = file->private_data; > + > + if (udev->state == ULEDTRIGGERS_STATE_REGISTERED) { > + udev->state = ULEDTRIGGERS_STATE_UNKNOWN; > + led_trigger_unregister(&udev->led_trigger); > + } > + kfree(udev); > + > + return 0; > +} > + > +static const struct file_operations uledtriggers_fops = { > + .owner = THIS_MODULE, > + .open = uledtriggers_open, > + .release = uledtriggers_release, > + .write = uledtriggers_write, > + .unlocked_ioctl = uledtriggers_ioctl, > +}; > + > +static struct miscdevice uledtriggers_misc = { > + .fops = &uledtriggers_fops, > + .minor = MISC_DYNAMIC_MINOR, > + .name = ULEDTRIGGERS_NAME, > +}; > + > +module_misc_device(uledtriggers_misc); > + > +MODULE_AUTHOR("Craig McQueen craig@xxxxxxxxxx>"); > +MODULE_DESCRIPTION("Userspace LED triggers driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/uapi/linux/uledtriggers.h b/include/uapi/linux/uledtriggers.h > new file mode 100644 > index 000000000000..251fa0a31861 > --- /dev/null > +++ b/include/uapi/linux/uledtriggers.h > @@ -0,0 +1,123 @@ > +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ > +/* > + * Userspace LED triggers driver support > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + */ > +#ifndef _UAPI__ULEDTRIGGERS_H_ > +#define _UAPI__ULEDTRIGGERS_H_ > + > +/* See TRIG_NAME_MAX in linux/leds.h */ > +#define LED_TRIGGER_MAX_NAME_SIZE 50 > + > +/* > + * Struct for initial write to setup, or ioctl ULEDTREGGERS_IOC_DEV_SETUP. > + */ > +struct uledtriggers_user_dev { > + char name[LED_TRIGGER_MAX_NAME_SIZE]; > +}; > + > +/* > + * Brightness levels for writes of int values, or for use with ULEDTRIGGERS_IOC_EVENT. > + * These correspond to Linux kernel internal enum led_brightness in linux/leds.h. > + */ > +enum uledtriggers_brightness { > + ULEDTRIGGERS_OFF = 0, > + ULEDTRIGGERS_ON = 1, > + ULEDTRIGGERS_HALF = 127, > + ULEDTRIGGERS_FULL = 255, > +}; > + > +/* > + * Struct for ioctl ULEDTRIGGERS_IOC_BLINK. > + */ > +struct uledtriggers_blink { > + unsigned long delay_on; > + unsigned long delay_off; > +}; > + > +/* > + * Struct for ioctl ULEDTRIGGERS_IOC_BLINK_ONESHOT. > + * Note padding at the end due to alignment (for 64-bit kernels). Ensure it's set to 0. > + */ > +struct uledtriggers_blink_oneshot { > + unsigned long delay_on; > + unsigned long delay_off; > + int invert; > + int __unused; > +}; > + > + > +/* ioctl commands */ > + > +#define ULEDTRIGGERS_IOC_MAGIC 't' > + > +/* > + * Initial setup. > + * E.g.: > + * int retval; > + * struct uledtriggers_user_dev dev_setup = { "transmogrifier" }; > + * retval = ioctl(fd, ULEDTRIGGERS_IOC_DEV_SETUP, &dev_setup); > + */ > +#define ULEDTRIGGERS_IOC_DEV_SETUP _IOW(ULEDTRIGGERS_IOC_MAGIC, 0x01, struct uledtriggers_user_dev) > + > +/* > + * Turn the trigger off. > + * E.g.: > + * int retval; > + * retval = ioctl(fd, ULEDTRIGGERS_IOC_OFF); > + */ > +#define ULEDTRIGGERS_IOC_OFF _IO(ULEDTRIGGERS_IOC_MAGIC, 0x10) > + > +/* > + * Turn the trigger on. > + * E.g.: > + * int retval; > + * retval = ioctl(fd, ULEDTRIGGERS_IOC_ON); > + */ > +#define ULEDTRIGGERS_IOC_ON _IO(ULEDTRIGGERS_IOC_MAGIC, 0x11) > + > +/* > + * Set the LED trigger to a specified brightness. > + * Refer to enum uledtriggers_brightness. > + * E.g.: > + * int retval; > + * int brightness = ULEDTRIGGERS_FULL; > + * retval = ioctl(fd, ULEDTRIGGERS_IOC_EVENT, &brightness); > + */ > +#define ULEDTRIGGERS_IOC_EVENT _IOW(ULEDTRIGGERS_IOC_MAGIC, 0x12, int) > + > +/* > + * Set the LED trigger to blink continuously. > + * E.g.: > + * int retval; > + * struct uledtriggers_blink blink; > + * blink.delay_on = 100; > + * blink.delay_off = 400; > + * retval = ioctl(fd, ULEDTRIGGERS_IOC_BLINK, &blink); > + */ > +#define ULEDTRIGGERS_IOC_BLINK _IOW(ULEDTRIGGERS_IOC_MAGIC, 0x20, struct uledtriggers_blink) > + > +/* > + * Set the LED trigger to blink once. > + * E.g.: > + * int retval; > + * struct uledtriggers_blink_oneshot blink_oneshot; > + * blink_oneshot.delay_on = 100; > + * blink_oneshot.delay_off = 400; > + * blink_oneshot.invert = false; > + * blink_oneshot.__unused = 0; > + * retval = ioctl(fd, ULEDTRIGGERS_IOC_BLINK_ONESHOT, &blink_oneshot); > + */ > +#define ULEDTRIGGERS_IOC_BLINK_ONESHOT _IOW(ULEDTRIGGERS_IOC_MAGIC, 0x21, struct uledtriggers_blink_oneshot) > + > + > +#endif /* _UAPI__ULEDTRIGGERS_H_ */ > -- > 2.48.1 > > >