1e381322bSDavid Lechner /* 2e381322bSDavid Lechner * Userspace driver for the LED subsystem 3e381322bSDavid Lechner * 4e381322bSDavid Lechner * Copyright (C) 2016 David Lechner <david@lechnology.com> 5e381322bSDavid Lechner * 6e381322bSDavid Lechner * Based on uinput.c: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org> 7e381322bSDavid Lechner * 8e381322bSDavid Lechner * This program is free software; you can redistribute it and/or modify 9e381322bSDavid Lechner * it under the terms of the GNU General Public License as published by 10e381322bSDavid Lechner * the Free Software Foundation; either version 2 of the License, or 11e381322bSDavid Lechner * (at your option) any later version. 12e381322bSDavid Lechner * 13e381322bSDavid Lechner * This program is distributed in the hope that it will be useful, 14e381322bSDavid Lechner * but WITHOUT ANY WARRANTY; without even the implied warranty of 15e381322bSDavid Lechner * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16e381322bSDavid Lechner * GNU General Public License for more details. 17e381322bSDavid Lechner */ 18e381322bSDavid Lechner #include <linux/fs.h> 19e381322bSDavid Lechner #include <linux/init.h> 20e381322bSDavid Lechner #include <linux/leds.h> 21e381322bSDavid Lechner #include <linux/miscdevice.h> 22e381322bSDavid Lechner #include <linux/module.h> 23e381322bSDavid Lechner #include <linux/poll.h> 24e381322bSDavid Lechner #include <linux/sched.h> 25e381322bSDavid Lechner #include <linux/slab.h> 26e381322bSDavid Lechner 27e381322bSDavid Lechner #include <uapi/linux/uleds.h> 28e381322bSDavid Lechner 29e381322bSDavid Lechner #define ULEDS_NAME "uleds" 30e381322bSDavid Lechner 31e381322bSDavid Lechner enum uleds_state { 32e381322bSDavid Lechner ULEDS_STATE_UNKNOWN, 33e381322bSDavid Lechner ULEDS_STATE_REGISTERED, 34e381322bSDavid Lechner }; 35e381322bSDavid Lechner 36e381322bSDavid Lechner struct uleds_device { 37e381322bSDavid Lechner struct uleds_user_dev user_dev; 38e381322bSDavid Lechner struct led_classdev led_cdev; 39e381322bSDavid Lechner struct mutex mutex; 40e381322bSDavid Lechner enum uleds_state state; 41e381322bSDavid Lechner wait_queue_head_t waitq; 42e381322bSDavid Lechner int brightness; 43e381322bSDavid Lechner bool new_data; 44e381322bSDavid Lechner }; 45e381322bSDavid Lechner 46e381322bSDavid Lechner static struct miscdevice uleds_misc; 47e381322bSDavid Lechner 48e381322bSDavid Lechner static void uleds_brightness_set(struct led_classdev *led_cdev, 49e381322bSDavid Lechner enum led_brightness brightness) 50e381322bSDavid Lechner { 51e381322bSDavid Lechner struct uleds_device *udev = container_of(led_cdev, struct uleds_device, 52e381322bSDavid Lechner led_cdev); 53e381322bSDavid Lechner 54e381322bSDavid Lechner if (udev->brightness != brightness) { 55e381322bSDavid Lechner udev->brightness = brightness; 56e381322bSDavid Lechner udev->new_data = true; 57e381322bSDavid Lechner wake_up_interruptible(&udev->waitq); 58e381322bSDavid Lechner } 59e381322bSDavid Lechner } 60e381322bSDavid Lechner 61e381322bSDavid Lechner static int uleds_open(struct inode *inode, struct file *file) 62e381322bSDavid Lechner { 63e381322bSDavid Lechner struct uleds_device *udev; 64e381322bSDavid Lechner 65e381322bSDavid Lechner udev = kzalloc(sizeof(*udev), GFP_KERNEL); 66e381322bSDavid Lechner if (!udev) 67e381322bSDavid Lechner return -ENOMEM; 68e381322bSDavid Lechner 69e381322bSDavid Lechner udev->led_cdev.name = udev->user_dev.name; 70e381322bSDavid Lechner udev->led_cdev.brightness_set = uleds_brightness_set; 71e381322bSDavid Lechner 72e381322bSDavid Lechner mutex_init(&udev->mutex); 73e381322bSDavid Lechner init_waitqueue_head(&udev->waitq); 74e381322bSDavid Lechner udev->state = ULEDS_STATE_UNKNOWN; 75e381322bSDavid Lechner 76e381322bSDavid Lechner file->private_data = udev; 77e381322bSDavid Lechner nonseekable_open(inode, file); 78e381322bSDavid Lechner 79e381322bSDavid Lechner return 0; 80e381322bSDavid Lechner } 81e381322bSDavid Lechner 82e381322bSDavid Lechner static ssize_t uleds_write(struct file *file, const char __user *buffer, 83e381322bSDavid Lechner size_t count, loff_t *ppos) 84e381322bSDavid Lechner { 85e381322bSDavid Lechner struct uleds_device *udev = file->private_data; 86e381322bSDavid Lechner const char *name; 87e381322bSDavid Lechner int ret; 88e381322bSDavid Lechner 89e381322bSDavid Lechner if (count == 0) 90e381322bSDavid Lechner return 0; 91e381322bSDavid Lechner 92e381322bSDavid Lechner ret = mutex_lock_interruptible(&udev->mutex); 93e381322bSDavid Lechner if (ret) 94e381322bSDavid Lechner return ret; 95e381322bSDavid Lechner 96e381322bSDavid Lechner if (udev->state == ULEDS_STATE_REGISTERED) { 97e381322bSDavid Lechner ret = -EBUSY; 98e381322bSDavid Lechner goto out; 99e381322bSDavid Lechner } 100e381322bSDavid Lechner 101e381322bSDavid Lechner if (count != sizeof(struct uleds_user_dev)) { 102e381322bSDavid Lechner ret = -EINVAL; 103e381322bSDavid Lechner goto out; 104e381322bSDavid Lechner } 105e381322bSDavid Lechner 106e381322bSDavid Lechner if (copy_from_user(&udev->user_dev, buffer, 107e381322bSDavid Lechner sizeof(struct uleds_user_dev))) { 108e381322bSDavid Lechner ret = -EFAULT; 109e381322bSDavid Lechner goto out; 110e381322bSDavid Lechner } 111e381322bSDavid Lechner 112e381322bSDavid Lechner name = udev->user_dev.name; 113e381322bSDavid Lechner if (!name[0] || !strcmp(name, ".") || !strcmp(name, "..") || 114e381322bSDavid Lechner strchr(name, '/')) { 115e381322bSDavid Lechner ret = -EINVAL; 116e381322bSDavid Lechner goto out; 117e381322bSDavid Lechner } 118e381322bSDavid Lechner 119e381322bSDavid Lechner if (udev->user_dev.max_brightness <= 0) { 120e381322bSDavid Lechner ret = -EINVAL; 121e381322bSDavid Lechner goto out; 122e381322bSDavid Lechner } 123e381322bSDavid Lechner udev->led_cdev.max_brightness = udev->user_dev.max_brightness; 124e381322bSDavid Lechner 125e381322bSDavid Lechner ret = devm_led_classdev_register(uleds_misc.this_device, 126e381322bSDavid Lechner &udev->led_cdev); 127e381322bSDavid Lechner if (ret < 0) 128e381322bSDavid Lechner goto out; 129e381322bSDavid Lechner 130e381322bSDavid Lechner udev->new_data = true; 131e381322bSDavid Lechner udev->state = ULEDS_STATE_REGISTERED; 132e381322bSDavid Lechner ret = count; 133e381322bSDavid Lechner 134e381322bSDavid Lechner out: 135e381322bSDavid Lechner mutex_unlock(&udev->mutex); 136e381322bSDavid Lechner 137e381322bSDavid Lechner return ret; 138e381322bSDavid Lechner } 139e381322bSDavid Lechner 140e381322bSDavid Lechner static ssize_t uleds_read(struct file *file, char __user *buffer, size_t count, 141e381322bSDavid Lechner loff_t *ppos) 142e381322bSDavid Lechner { 143e381322bSDavid Lechner struct uleds_device *udev = file->private_data; 144e381322bSDavid Lechner ssize_t retval; 145e381322bSDavid Lechner 146e381322bSDavid Lechner if (count < sizeof(udev->brightness)) 147e381322bSDavid Lechner return 0; 148e381322bSDavid Lechner 149e381322bSDavid Lechner do { 150e381322bSDavid Lechner retval = mutex_lock_interruptible(&udev->mutex); 151e381322bSDavid Lechner if (retval) 152e381322bSDavid Lechner return retval; 153e381322bSDavid Lechner 154e381322bSDavid Lechner if (udev->state != ULEDS_STATE_REGISTERED) { 155e381322bSDavid Lechner retval = -ENODEV; 156e381322bSDavid Lechner } else if (!udev->new_data && (file->f_flags & O_NONBLOCK)) { 157e381322bSDavid Lechner retval = -EAGAIN; 158e381322bSDavid Lechner } else if (udev->new_data) { 159e381322bSDavid Lechner retval = copy_to_user(buffer, &udev->brightness, 160e381322bSDavid Lechner sizeof(udev->brightness)); 161e381322bSDavid Lechner udev->new_data = false; 162e381322bSDavid Lechner retval = sizeof(udev->brightness); 163e381322bSDavid Lechner } 164e381322bSDavid Lechner 165e381322bSDavid Lechner mutex_unlock(&udev->mutex); 166e381322bSDavid Lechner 167e381322bSDavid Lechner if (retval) 168e381322bSDavid Lechner break; 169e381322bSDavid Lechner 170e381322bSDavid Lechner if (!(file->f_flags & O_NONBLOCK)) 171e381322bSDavid Lechner retval = wait_event_interruptible(udev->waitq, 172e381322bSDavid Lechner udev->new_data || 173e381322bSDavid Lechner udev->state != ULEDS_STATE_REGISTERED); 174e381322bSDavid Lechner } while (retval == 0); 175e381322bSDavid Lechner 176e381322bSDavid Lechner return retval; 177e381322bSDavid Lechner } 178e381322bSDavid Lechner 179afc9a42bSAl Viro static __poll_t uleds_poll(struct file *file, poll_table *wait) 180e381322bSDavid Lechner { 181e381322bSDavid Lechner struct uleds_device *udev = file->private_data; 182e381322bSDavid Lechner 183e381322bSDavid Lechner poll_wait(file, &udev->waitq, wait); 184e381322bSDavid Lechner 185e381322bSDavid Lechner if (udev->new_data) 186e381322bSDavid Lechner return POLLIN | POLLRDNORM; 187e381322bSDavid Lechner 188e381322bSDavid Lechner return 0; 189e381322bSDavid Lechner } 190e381322bSDavid Lechner 191e381322bSDavid Lechner static int uleds_release(struct inode *inode, struct file *file) 192e381322bSDavid Lechner { 193e381322bSDavid Lechner struct uleds_device *udev = file->private_data; 194e381322bSDavid Lechner 195e381322bSDavid Lechner if (udev->state == ULEDS_STATE_REGISTERED) { 196e381322bSDavid Lechner udev->state = ULEDS_STATE_UNKNOWN; 197e381322bSDavid Lechner devm_led_classdev_unregister(uleds_misc.this_device, 198e381322bSDavid Lechner &udev->led_cdev); 199e381322bSDavid Lechner } 200e381322bSDavid Lechner kfree(udev); 201e381322bSDavid Lechner 202e381322bSDavid Lechner return 0; 203e381322bSDavid Lechner } 204e381322bSDavid Lechner 205e381322bSDavid Lechner static const struct file_operations uleds_fops = { 206e381322bSDavid Lechner .owner = THIS_MODULE, 207e381322bSDavid Lechner .open = uleds_open, 208e381322bSDavid Lechner .release = uleds_release, 209e381322bSDavid Lechner .read = uleds_read, 210e381322bSDavid Lechner .write = uleds_write, 211e381322bSDavid Lechner .poll = uleds_poll, 212e381322bSDavid Lechner .llseek = no_llseek, 213e381322bSDavid Lechner }; 214e381322bSDavid Lechner 215e381322bSDavid Lechner static struct miscdevice uleds_misc = { 216e381322bSDavid Lechner .fops = &uleds_fops, 217e381322bSDavid Lechner .minor = MISC_DYNAMIC_MINOR, 218e381322bSDavid Lechner .name = ULEDS_NAME, 219e381322bSDavid Lechner }; 220e381322bSDavid Lechner 221e381322bSDavid Lechner static int __init uleds_init(void) 222e381322bSDavid Lechner { 223e381322bSDavid Lechner return misc_register(&uleds_misc); 224e381322bSDavid Lechner } 225e381322bSDavid Lechner module_init(uleds_init); 226e381322bSDavid Lechner 227e381322bSDavid Lechner static void __exit uleds_exit(void) 228e381322bSDavid Lechner { 229e381322bSDavid Lechner misc_deregister(&uleds_misc); 230e381322bSDavid Lechner } 231e381322bSDavid Lechner module_exit(uleds_exit); 232e381322bSDavid Lechner 233e381322bSDavid Lechner MODULE_AUTHOR("David Lechner <david@lechnology.com>"); 234e381322bSDavid Lechner MODULE_DESCRIPTION("Userspace driver for the LED subsystem"); 235e381322bSDavid Lechner MODULE_LICENSE("GPL"); 236