diff options
author | David Lechner <david@lechnology.com> | 2016-09-16 15:16:48 -0400 |
---|---|---|
committer | Jacek Anaszewski <j.anaszewski@samsung.com> | 2016-11-22 06:07:02 -0500 |
commit | e381322b0190c1253d347de3f28b5c37756fb651 (patch) | |
tree | 792a2bc1695cae581e152565e95127405424f5c6 /drivers/leds | |
parent | 1001354ca34179f3db924eb66672442a173147dc (diff) |
leds: Introduce userspace LED class driver
This driver creates a userspace leds driver similar to uinput.
New LEDs are created by opening /dev/uleds and writing a uleds_user_dev
struct. A new LED class device is registered with the name given in the
struct. Reading will return a single byte that is the current brightness.
The poll() syscall is also supported. It will be triggered whenever the
brightness changes. Closing the file handle to /dev/uleds will remove
the leds class device.
Signed-off-by: David Lechner <david@lechnology.com>
Signed-off-by: Jacek Anaszewski <j.anaszewski@samsung.com>
Diffstat (limited to 'drivers/leds')
-rw-r--r-- | drivers/leds/Kconfig | 8 | ||||
-rw-r--r-- | drivers/leds/Makefile | 3 | ||||
-rw-r--r-- | drivers/leds/uleds.c | 235 |
3 files changed, 246 insertions, 0 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 7a628c6516f6..5fd3f4ca0c3c 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig | |||
@@ -659,6 +659,14 @@ config LEDS_MLXCPLD | |||
659 | This option enabled support for the LEDs on the Mellanox | 659 | This option enabled support for the LEDs on the Mellanox |
660 | boards. Say Y to enabled these. | 660 | boards. Say Y to enabled these. |
661 | 661 | ||
662 | config LEDS_USER | ||
663 | tristate "Userspace LED support" | ||
664 | depends on LEDS_CLASS | ||
665 | help | ||
666 | This option enables support for userspace LEDs. Say 'y' to enable this | ||
667 | support in kernel. To compile this driver as a module, choose 'm' here: | ||
668 | the module will be called uleds. | ||
669 | |||
662 | comment "LED Triggers" | 670 | comment "LED Triggers" |
663 | source "drivers/leds/trigger/Kconfig" | 671 | source "drivers/leds/trigger/Kconfig" |
664 | 672 | ||
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 3965070190f5..d5331ff5b563 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile | |||
@@ -75,5 +75,8 @@ obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o | |||
75 | # LED SPI Drivers | 75 | # LED SPI Drivers |
76 | obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o | 76 | obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o |
77 | 77 | ||
78 | # LED Userspace Drivers | ||
79 | obj-$(CONFIG_LEDS_USER) += uleds.o | ||
80 | |||
78 | # LED Triggers | 81 | # LED Triggers |
79 | obj-$(CONFIG_LEDS_TRIGGERS) += trigger/ | 82 | obj-$(CONFIG_LEDS_TRIGGERS) += trigger/ |
diff --git a/drivers/leds/uleds.c b/drivers/leds/uleds.c new file mode 100644 index 000000000000..5e9e8a1fdefb --- /dev/null +++ b/drivers/leds/uleds.c | |||
@@ -0,0 +1,235 @@ | |||
1 | /* | ||
2 | * Userspace driver for the LED subsystem | ||
3 | * | ||
4 | * Copyright (C) 2016 David Lechner <david@lechnology.com> | ||
5 | * | ||
6 | * Based on uinput.c: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | */ | ||
18 | #include <linux/fs.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/leds.h> | ||
21 | #include <linux/miscdevice.h> | ||
22 | #include <linux/module.h> | ||
23 | #include <linux/poll.h> | ||
24 | #include <linux/sched.h> | ||
25 | #include <linux/slab.h> | ||
26 | |||
27 | #include <uapi/linux/uleds.h> | ||
28 | |||
29 | #define ULEDS_NAME "uleds" | ||
30 | |||
31 | enum uleds_state { | ||
32 | ULEDS_STATE_UNKNOWN, | ||
33 | ULEDS_STATE_REGISTERED, | ||
34 | }; | ||
35 | |||
36 | struct uleds_device { | ||
37 | struct uleds_user_dev user_dev; | ||
38 | struct led_classdev led_cdev; | ||
39 | struct mutex mutex; | ||
40 | enum uleds_state state; | ||
41 | wait_queue_head_t waitq; | ||
42 | int brightness; | ||
43 | bool new_data; | ||
44 | }; | ||
45 | |||
46 | static struct miscdevice uleds_misc; | ||
47 | |||
48 | static void uleds_brightness_set(struct led_classdev *led_cdev, | ||
49 | enum led_brightness brightness) | ||
50 | { | ||
51 | struct uleds_device *udev = container_of(led_cdev, struct uleds_device, | ||
52 | led_cdev); | ||
53 | |||
54 | if (udev->brightness != brightness) { | ||
55 | udev->brightness = brightness; | ||
56 | udev->new_data = true; | ||
57 | wake_up_interruptible(&udev->waitq); | ||
58 | } | ||
59 | } | ||
60 | |||
61 | static int uleds_open(struct inode *inode, struct file *file) | ||
62 | { | ||
63 | struct uleds_device *udev; | ||
64 | |||
65 | udev = kzalloc(sizeof(*udev), GFP_KERNEL); | ||
66 | if (!udev) | ||
67 | return -ENOMEM; | ||
68 | |||
69 | udev->led_cdev.name = udev->user_dev.name; | ||
70 | udev->led_cdev.brightness_set = uleds_brightness_set; | ||
71 | |||
72 | mutex_init(&udev->mutex); | ||
73 | init_waitqueue_head(&udev->waitq); | ||
74 | udev->state = ULEDS_STATE_UNKNOWN; | ||
75 | |||
76 | file->private_data = udev; | ||
77 | nonseekable_open(inode, file); | ||
78 | |||
79 | return 0; | ||
80 | } | ||
81 | |||
82 | static ssize_t uleds_write(struct file *file, const char __user *buffer, | ||
83 | size_t count, loff_t *ppos) | ||
84 | { | ||
85 | struct uleds_device *udev = file->private_data; | ||
86 | const char *name; | ||
87 | int ret; | ||
88 | |||
89 | if (count == 0) | ||
90 | return 0; | ||
91 | |||
92 | ret = mutex_lock_interruptible(&udev->mutex); | ||
93 | if (ret) | ||
94 | return ret; | ||
95 | |||
96 | if (udev->state == ULEDS_STATE_REGISTERED) { | ||
97 | ret = -EBUSY; | ||
98 | goto out; | ||
99 | } | ||
100 | |||
101 | if (count != sizeof(struct uleds_user_dev)) { | ||
102 | ret = -EINVAL; | ||
103 | goto out; | ||
104 | } | ||
105 | |||
106 | if (copy_from_user(&udev->user_dev, buffer, | ||
107 | sizeof(struct uleds_user_dev))) { | ||
108 | ret = -EFAULT; | ||
109 | goto out; | ||
110 | } | ||
111 | |||
112 | name = udev->user_dev.name; | ||
113 | if (!name[0] || !strcmp(name, ".") || !strcmp(name, "..") || | ||
114 | strchr(name, '/')) { | ||
115 | ret = -EINVAL; | ||
116 | goto out; | ||
117 | } | ||
118 | |||
119 | if (udev->user_dev.max_brightness <= 0) { | ||
120 | ret = -EINVAL; | ||
121 | goto out; | ||
122 | } | ||
123 | udev->led_cdev.max_brightness = udev->user_dev.max_brightness; | ||
124 | |||
125 | ret = devm_led_classdev_register(uleds_misc.this_device, | ||
126 | &udev->led_cdev); | ||
127 | if (ret < 0) | ||
128 | goto out; | ||
129 | |||
130 | udev->new_data = true; | ||
131 | udev->state = ULEDS_STATE_REGISTERED; | ||
132 | ret = count; | ||
133 | |||
134 | out: | ||
135 | mutex_unlock(&udev->mutex); | ||
136 | |||
137 | return ret; | ||
138 | } | ||
139 | |||
140 | static ssize_t uleds_read(struct file *file, char __user *buffer, size_t count, | ||
141 | loff_t *ppos) | ||
142 | { | ||
143 | struct uleds_device *udev = file->private_data; | ||
144 | ssize_t retval; | ||
145 | |||
146 | if (count < sizeof(udev->brightness)) | ||
147 | return 0; | ||
148 | |||
149 | do { | ||
150 | retval = mutex_lock_interruptible(&udev->mutex); | ||
151 | if (retval) | ||
152 | return retval; | ||
153 | |||
154 | if (udev->state != ULEDS_STATE_REGISTERED) { | ||
155 | retval = -ENODEV; | ||
156 | } else if (!udev->new_data && (file->f_flags & O_NONBLOCK)) { | ||
157 | retval = -EAGAIN; | ||
158 | } else if (udev->new_data) { | ||
159 | retval = copy_to_user(buffer, &udev->brightness, | ||
160 | sizeof(udev->brightness)); | ||
161 | udev->new_data = false; | ||
162 | retval = sizeof(udev->brightness); | ||
163 | } | ||
164 | |||
165 | mutex_unlock(&udev->mutex); | ||
166 | |||
167 | if (retval) | ||
168 | break; | ||
169 | |||
170 | if (!(file->f_flags & O_NONBLOCK)) | ||
171 | retval = wait_event_interruptible(udev->waitq, | ||
172 | udev->new_data || | ||
173 | udev->state != ULEDS_STATE_REGISTERED); | ||
174 | } while (retval == 0); | ||
175 | |||
176 | return retval; | ||
177 | } | ||
178 | |||
179 | static unsigned int uleds_poll(struct file *file, poll_table *wait) | ||
180 | { | ||
181 | struct uleds_device *udev = file->private_data; | ||
182 | |||
183 | poll_wait(file, &udev->waitq, wait); | ||
184 | |||
185 | if (udev->new_data) | ||
186 | return POLLIN | POLLRDNORM; | ||
187 | |||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | static int uleds_release(struct inode *inode, struct file *file) | ||
192 | { | ||
193 | struct uleds_device *udev = file->private_data; | ||
194 | |||
195 | if (udev->state == ULEDS_STATE_REGISTERED) { | ||
196 | udev->state = ULEDS_STATE_UNKNOWN; | ||
197 | devm_led_classdev_unregister(uleds_misc.this_device, | ||
198 | &udev->led_cdev); | ||
199 | } | ||
200 | kfree(udev); | ||
201 | |||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | static const struct file_operations uleds_fops = { | ||
206 | .owner = THIS_MODULE, | ||
207 | .open = uleds_open, | ||
208 | .release = uleds_release, | ||
209 | .read = uleds_read, | ||
210 | .write = uleds_write, | ||
211 | .poll = uleds_poll, | ||
212 | .llseek = no_llseek, | ||
213 | }; | ||
214 | |||
215 | static struct miscdevice uleds_misc = { | ||
216 | .fops = &uleds_fops, | ||
217 | .minor = MISC_DYNAMIC_MINOR, | ||
218 | .name = ULEDS_NAME, | ||
219 | }; | ||
220 | |||
221 | static int __init uleds_init(void) | ||
222 | { | ||
223 | return misc_register(&uleds_misc); | ||
224 | } | ||
225 | module_init(uleds_init); | ||
226 | |||
227 | static void __exit uleds_exit(void) | ||
228 | { | ||
229 | misc_deregister(&uleds_misc); | ||
230 | } | ||
231 | module_exit(uleds_exit); | ||
232 | |||
233 | MODULE_AUTHOR("David Lechner <david@lechnology.com>"); | ||
234 | MODULE_DESCRIPTION("Userspace driver for the LED subsystem"); | ||
235 | MODULE_LICENSE("GPL"); | ||