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 __poll_t 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 EPOLLIN | EPOLLRDNORM; 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