1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Userspace driver for the LED subsystem
5 * Copyright (C) 2016 David Lechner <david@lechnology.com>
7 * Based on uinput.c: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
10 #include <linux/init.h>
11 #include <linux/leds.h>
12 #include <linux/miscdevice.h>
13 #include <linux/module.h>
14 #include <linux/poll.h>
15 #include <linux/sched.h>
16 #include <linux/slab.h>
18 #include <uapi/linux/uleds.h>
20 #define ULEDS_NAME "uleds"
24 ULEDS_STATE_REGISTERED
,
28 struct uleds_user_dev user_dev
;
29 struct led_classdev led_cdev
;
31 enum uleds_state state
;
32 wait_queue_head_t waitq
;
37 static struct miscdevice uleds_misc
;
39 static void uleds_brightness_set(struct led_classdev
*led_cdev
,
40 enum led_brightness brightness
)
42 struct uleds_device
*udev
= container_of(led_cdev
, struct uleds_device
,
45 if (udev
->brightness
!= brightness
) {
46 udev
->brightness
= brightness
;
47 udev
->new_data
= true;
48 wake_up_interruptible(&udev
->waitq
);
52 static int uleds_open(struct inode
*inode
, struct file
*file
)
54 struct uleds_device
*udev
;
56 udev
= kzalloc(sizeof(*udev
), GFP_KERNEL
);
60 udev
->led_cdev
.name
= udev
->user_dev
.name
;
61 udev
->led_cdev
.brightness_set
= uleds_brightness_set
;
63 mutex_init(&udev
->mutex
);
64 init_waitqueue_head(&udev
->waitq
);
65 udev
->state
= ULEDS_STATE_UNKNOWN
;
67 file
->private_data
= udev
;
68 stream_open(inode
, file
);
73 static ssize_t
uleds_write(struct file
*file
, const char __user
*buffer
,
74 size_t count
, loff_t
*ppos
)
76 struct uleds_device
*udev
= file
->private_data
;
83 ret
= mutex_lock_interruptible(&udev
->mutex
);
87 if (udev
->state
== ULEDS_STATE_REGISTERED
) {
92 if (count
!= sizeof(struct uleds_user_dev
)) {
97 if (copy_from_user(&udev
->user_dev
, buffer
,
98 sizeof(struct uleds_user_dev
))) {
103 name
= udev
->user_dev
.name
;
104 if (!name
[0] || !strcmp(name
, ".") || !strcmp(name
, "..") ||
110 if (udev
->user_dev
.max_brightness
<= 0) {
114 udev
->led_cdev
.max_brightness
= udev
->user_dev
.max_brightness
;
116 ret
= devm_led_classdev_register(uleds_misc
.this_device
,
121 udev
->new_data
= true;
122 udev
->state
= ULEDS_STATE_REGISTERED
;
126 mutex_unlock(&udev
->mutex
);
131 static ssize_t
uleds_read(struct file
*file
, char __user
*buffer
, size_t count
,
134 struct uleds_device
*udev
= file
->private_data
;
137 if (count
< sizeof(udev
->brightness
))
141 retval
= mutex_lock_interruptible(&udev
->mutex
);
145 if (udev
->state
!= ULEDS_STATE_REGISTERED
) {
147 } else if (!udev
->new_data
&& (file
->f_flags
& O_NONBLOCK
)) {
149 } else if (udev
->new_data
) {
150 retval
= copy_to_user(buffer
, &udev
->brightness
,
151 sizeof(udev
->brightness
));
152 udev
->new_data
= false;
153 retval
= sizeof(udev
->brightness
);
156 mutex_unlock(&udev
->mutex
);
161 if (!(file
->f_flags
& O_NONBLOCK
))
162 retval
= wait_event_interruptible(udev
->waitq
,
164 udev
->state
!= ULEDS_STATE_REGISTERED
);
165 } while (retval
== 0);
170 static __poll_t
uleds_poll(struct file
*file
, poll_table
*wait
)
172 struct uleds_device
*udev
= file
->private_data
;
174 poll_wait(file
, &udev
->waitq
, wait
);
177 return EPOLLIN
| EPOLLRDNORM
;
182 static int uleds_release(struct inode
*inode
, struct file
*file
)
184 struct uleds_device
*udev
= file
->private_data
;
186 if (udev
->state
== ULEDS_STATE_REGISTERED
) {
187 udev
->state
= ULEDS_STATE_UNKNOWN
;
188 devm_led_classdev_unregister(uleds_misc
.this_device
,
196 static const struct file_operations uleds_fops
= {
197 .owner
= THIS_MODULE
,
199 .release
= uleds_release
,
201 .write
= uleds_write
,
206 static struct miscdevice uleds_misc
= {
208 .minor
= MISC_DYNAMIC_MINOR
,
212 static int __init
uleds_init(void)
214 return misc_register(&uleds_misc
);
216 module_init(uleds_init
);
218 static void __exit
uleds_exit(void)
220 misc_deregister(&uleds_misc
);
222 module_exit(uleds_exit
);
224 MODULE_AUTHOR("David Lechner <david@lechnology.com>");
225 MODULE_DESCRIPTION("Userspace driver for the LED subsystem");
226 MODULE_LICENSE("GPL");