xref: /linux/drivers/leds/uleds.c (revision 74ce1896c6c65b2f8cccbf59162d542988835835)
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");
236