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