1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2e381322bSDavid Lechner /*
3e381322bSDavid Lechner * Userspace driver for the LED subsystem
4e381322bSDavid Lechner *
5e381322bSDavid Lechner * Copyright (C) 2016 David Lechner <david@lechnology.com>
6e381322bSDavid Lechner *
7e381322bSDavid Lechner * Based on uinput.c: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
8e381322bSDavid Lechner */
9e381322bSDavid Lechner #include <linux/fs.h>
10e381322bSDavid Lechner #include <linux/init.h>
11e381322bSDavid Lechner #include <linux/leds.h>
12e381322bSDavid Lechner #include <linux/miscdevice.h>
13e381322bSDavid Lechner #include <linux/module.h>
14e381322bSDavid Lechner #include <linux/poll.h>
15e381322bSDavid Lechner #include <linux/sched.h>
16e381322bSDavid Lechner #include <linux/slab.h>
17e381322bSDavid Lechner
18e381322bSDavid Lechner #include <uapi/linux/uleds.h>
19e381322bSDavid Lechner
20e381322bSDavid Lechner #define ULEDS_NAME "uleds"
21e381322bSDavid Lechner
22e381322bSDavid Lechner enum uleds_state {
23e381322bSDavid Lechner ULEDS_STATE_UNKNOWN,
24e381322bSDavid Lechner ULEDS_STATE_REGISTERED,
25e381322bSDavid Lechner };
26e381322bSDavid Lechner
27e381322bSDavid Lechner struct uleds_device {
28e381322bSDavid Lechner struct uleds_user_dev user_dev;
29e381322bSDavid Lechner struct led_classdev led_cdev;
30e381322bSDavid Lechner struct mutex mutex;
31e381322bSDavid Lechner enum uleds_state state;
32e381322bSDavid Lechner wait_queue_head_t waitq;
33e381322bSDavid Lechner int brightness;
34e381322bSDavid Lechner bool new_data;
35e381322bSDavid Lechner };
36e381322bSDavid Lechner
37e381322bSDavid Lechner static struct miscdevice uleds_misc;
38e381322bSDavid Lechner
uleds_brightness_set(struct led_classdev * led_cdev,enum led_brightness brightness)39e381322bSDavid Lechner static void uleds_brightness_set(struct led_classdev *led_cdev,
40e381322bSDavid Lechner enum led_brightness brightness)
41e381322bSDavid Lechner {
42e381322bSDavid Lechner struct uleds_device *udev = container_of(led_cdev, struct uleds_device,
43e381322bSDavid Lechner led_cdev);
44e381322bSDavid Lechner
45e381322bSDavid Lechner if (udev->brightness != brightness) {
46e381322bSDavid Lechner udev->brightness = brightness;
47e381322bSDavid Lechner udev->new_data = true;
48e381322bSDavid Lechner wake_up_interruptible(&udev->waitq);
49e381322bSDavid Lechner }
50e381322bSDavid Lechner }
51e381322bSDavid Lechner
uleds_open(struct inode * inode,struct file * file)52e381322bSDavid Lechner static int uleds_open(struct inode *inode, struct file *file)
53e381322bSDavid Lechner {
54e381322bSDavid Lechner struct uleds_device *udev;
55e381322bSDavid Lechner
56e381322bSDavid Lechner udev = kzalloc(sizeof(*udev), GFP_KERNEL);
57e381322bSDavid Lechner if (!udev)
58e381322bSDavid Lechner return -ENOMEM;
59e381322bSDavid Lechner
60e381322bSDavid Lechner udev->led_cdev.name = udev->user_dev.name;
61e381322bSDavid Lechner udev->led_cdev.brightness_set = uleds_brightness_set;
62e381322bSDavid Lechner
63e381322bSDavid Lechner mutex_init(&udev->mutex);
64e381322bSDavid Lechner init_waitqueue_head(&udev->waitq);
65e381322bSDavid Lechner udev->state = ULEDS_STATE_UNKNOWN;
66e381322bSDavid Lechner
67e381322bSDavid Lechner file->private_data = udev;
68c5bf68feSKirill Smelkov stream_open(inode, file);
69e381322bSDavid Lechner
70e381322bSDavid Lechner return 0;
71e381322bSDavid Lechner }
72e381322bSDavid Lechner
uleds_write(struct file * file,const char __user * buffer,size_t count,loff_t * ppos)73e381322bSDavid Lechner static ssize_t uleds_write(struct file *file, const char __user *buffer,
74e381322bSDavid Lechner size_t count, loff_t *ppos)
75e381322bSDavid Lechner {
76e381322bSDavid Lechner struct uleds_device *udev = file->private_data;
77e381322bSDavid Lechner const char *name;
78e381322bSDavid Lechner int ret;
79e381322bSDavid Lechner
80e381322bSDavid Lechner if (count == 0)
81e381322bSDavid Lechner return 0;
82e381322bSDavid Lechner
83e381322bSDavid Lechner ret = mutex_lock_interruptible(&udev->mutex);
84e381322bSDavid Lechner if (ret)
85e381322bSDavid Lechner return ret;
86e381322bSDavid Lechner
87e381322bSDavid Lechner if (udev->state == ULEDS_STATE_REGISTERED) {
88e381322bSDavid Lechner ret = -EBUSY;
89e381322bSDavid Lechner goto out;
90e381322bSDavid Lechner }
91e381322bSDavid Lechner
92e381322bSDavid Lechner if (count != sizeof(struct uleds_user_dev)) {
93e381322bSDavid Lechner ret = -EINVAL;
94e381322bSDavid Lechner goto out;
95e381322bSDavid Lechner }
96e381322bSDavid Lechner
97e381322bSDavid Lechner if (copy_from_user(&udev->user_dev, buffer,
98e381322bSDavid Lechner sizeof(struct uleds_user_dev))) {
99e381322bSDavid Lechner ret = -EFAULT;
100e381322bSDavid Lechner goto out;
101e381322bSDavid Lechner }
102e381322bSDavid Lechner
103e381322bSDavid Lechner name = udev->user_dev.name;
104e381322bSDavid Lechner if (!name[0] || !strcmp(name, ".") || !strcmp(name, "..") ||
105e381322bSDavid Lechner strchr(name, '/')) {
106e381322bSDavid Lechner ret = -EINVAL;
107e381322bSDavid Lechner goto out;
108e381322bSDavid Lechner }
109e381322bSDavid Lechner
110e381322bSDavid Lechner if (udev->user_dev.max_brightness <= 0) {
111e381322bSDavid Lechner ret = -EINVAL;
112e381322bSDavid Lechner goto out;
113e381322bSDavid Lechner }
114e381322bSDavid Lechner udev->led_cdev.max_brightness = udev->user_dev.max_brightness;
115e381322bSDavid Lechner
116e381322bSDavid Lechner ret = devm_led_classdev_register(uleds_misc.this_device,
117e381322bSDavid Lechner &udev->led_cdev);
118e381322bSDavid Lechner if (ret < 0)
119e381322bSDavid Lechner goto out;
120e381322bSDavid Lechner
121e381322bSDavid Lechner udev->new_data = true;
122e381322bSDavid Lechner udev->state = ULEDS_STATE_REGISTERED;
123e381322bSDavid Lechner ret = count;
124e381322bSDavid Lechner
125e381322bSDavid Lechner out:
126e381322bSDavid Lechner mutex_unlock(&udev->mutex);
127e381322bSDavid Lechner
128e381322bSDavid Lechner return ret;
129e381322bSDavid Lechner }
130e381322bSDavid Lechner
uleds_read(struct file * file,char __user * buffer,size_t count,loff_t * ppos)131e381322bSDavid Lechner static ssize_t uleds_read(struct file *file, char __user *buffer, size_t count,
132e381322bSDavid Lechner loff_t *ppos)
133e381322bSDavid Lechner {
134e381322bSDavid Lechner struct uleds_device *udev = file->private_data;
135e381322bSDavid Lechner ssize_t retval;
136e381322bSDavid Lechner
137e381322bSDavid Lechner if (count < sizeof(udev->brightness))
138e381322bSDavid Lechner return 0;
139e381322bSDavid Lechner
140e381322bSDavid Lechner do {
141e381322bSDavid Lechner retval = mutex_lock_interruptible(&udev->mutex);
142e381322bSDavid Lechner if (retval)
143e381322bSDavid Lechner return retval;
144e381322bSDavid Lechner
145e381322bSDavid Lechner if (udev->state != ULEDS_STATE_REGISTERED) {
146e381322bSDavid Lechner retval = -ENODEV;
147e381322bSDavid Lechner } else if (!udev->new_data && (file->f_flags & O_NONBLOCK)) {
148e381322bSDavid Lechner retval = -EAGAIN;
149e381322bSDavid Lechner } else if (udev->new_data) {
150e381322bSDavid Lechner retval = copy_to_user(buffer, &udev->brightness,
151e381322bSDavid Lechner sizeof(udev->brightness));
152e381322bSDavid Lechner udev->new_data = false;
153e381322bSDavid Lechner retval = sizeof(udev->brightness);
154e381322bSDavid Lechner }
155e381322bSDavid Lechner
156e381322bSDavid Lechner mutex_unlock(&udev->mutex);
157e381322bSDavid Lechner
158e381322bSDavid Lechner if (retval)
159e381322bSDavid Lechner break;
160e381322bSDavid Lechner
161e381322bSDavid Lechner if (!(file->f_flags & O_NONBLOCK))
162e381322bSDavid Lechner retval = wait_event_interruptible(udev->waitq,
163e381322bSDavid Lechner udev->new_data ||
164e381322bSDavid Lechner udev->state != ULEDS_STATE_REGISTERED);
165e381322bSDavid Lechner } while (retval == 0);
166e381322bSDavid Lechner
167e381322bSDavid Lechner return retval;
168e381322bSDavid Lechner }
169e381322bSDavid Lechner
uleds_poll(struct file * file,poll_table * wait)170afc9a42bSAl Viro static __poll_t uleds_poll(struct file *file, poll_table *wait)
171e381322bSDavid Lechner {
172e381322bSDavid Lechner struct uleds_device *udev = file->private_data;
173e381322bSDavid Lechner
174e381322bSDavid Lechner poll_wait(file, &udev->waitq, wait);
175e381322bSDavid Lechner
176e381322bSDavid Lechner if (udev->new_data)
177a9a08845SLinus Torvalds return EPOLLIN | EPOLLRDNORM;
178e381322bSDavid Lechner
179e381322bSDavid Lechner return 0;
180e381322bSDavid Lechner }
181e381322bSDavid Lechner
uleds_release(struct inode * inode,struct file * file)182e381322bSDavid Lechner static int uleds_release(struct inode *inode, struct file *file)
183e381322bSDavid Lechner {
184e381322bSDavid Lechner struct uleds_device *udev = file->private_data;
185e381322bSDavid Lechner
186e381322bSDavid Lechner if (udev->state == ULEDS_STATE_REGISTERED) {
187e381322bSDavid Lechner udev->state = ULEDS_STATE_UNKNOWN;
188e381322bSDavid Lechner devm_led_classdev_unregister(uleds_misc.this_device,
189e381322bSDavid Lechner &udev->led_cdev);
190e381322bSDavid Lechner }
191e381322bSDavid Lechner kfree(udev);
192e381322bSDavid Lechner
193e381322bSDavid Lechner return 0;
194e381322bSDavid Lechner }
195e381322bSDavid Lechner
196e381322bSDavid Lechner static const struct file_operations uleds_fops = {
197e381322bSDavid Lechner .owner = THIS_MODULE,
198e381322bSDavid Lechner .open = uleds_open,
199e381322bSDavid Lechner .release = uleds_release,
200e381322bSDavid Lechner .read = uleds_read,
201e381322bSDavid Lechner .write = uleds_write,
202e381322bSDavid Lechner .poll = uleds_poll,
203e381322bSDavid Lechner .llseek = no_llseek,
204e381322bSDavid Lechner };
205e381322bSDavid Lechner
206e381322bSDavid Lechner static struct miscdevice uleds_misc = {
207e381322bSDavid Lechner .fops = &uleds_fops,
208e381322bSDavid Lechner .minor = MISC_DYNAMIC_MINOR,
209e381322bSDavid Lechner .name = ULEDS_NAME,
210e381322bSDavid Lechner };
211e381322bSDavid Lechner
212*a916d720SLi Zetao module_misc_device(uleds_misc);
213e381322bSDavid Lechner
214e381322bSDavid Lechner MODULE_AUTHOR("David Lechner <david@lechnology.com>");
215e381322bSDavid Lechner MODULE_DESCRIPTION("Userspace driver for the LED subsystem");
216e381322bSDavid Lechner MODULE_LICENSE("GPL");
217