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 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 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 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 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 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 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 212e381322bSDavid Lechner static int __init uleds_init(void) 213e381322bSDavid Lechner { 214e381322bSDavid Lechner return misc_register(&uleds_misc); 215e381322bSDavid Lechner } 216e381322bSDavid Lechner module_init(uleds_init); 217e381322bSDavid Lechner 218e381322bSDavid Lechner static void __exit uleds_exit(void) 219e381322bSDavid Lechner { 220e381322bSDavid Lechner misc_deregister(&uleds_misc); 221e381322bSDavid Lechner } 222e381322bSDavid Lechner module_exit(uleds_exit); 223e381322bSDavid Lechner 224e381322bSDavid Lechner MODULE_AUTHOR("David Lechner <david@lechnology.com>"); 225e381322bSDavid Lechner MODULE_DESCRIPTION("Userspace driver for the LED subsystem"); 226e381322bSDavid Lechner MODULE_LICENSE("GPL"); 227