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 22 enum uleds_state { 23 ULEDS_STATE_UNKNOWN, 24 ULEDS_STATE_REGISTERED, 25 }; 26 27 struct 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 37 static struct miscdevice uleds_misc; 38 39 static 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 52 static int uleds_open(struct inode *inode, struct file *file) 53 { 54 struct uleds_device *udev; 55 56 udev = kzalloc_obj(*udev); 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 73 static 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 strnchr(name, sizeof(udev->user_dev.name), '/') || 106 !strnchr(name, sizeof(udev->user_dev.name), '\0')) { 107 ret = -EINVAL; 108 goto out; 109 } 110 111 if (udev->user_dev.max_brightness <= 0) { 112 ret = -EINVAL; 113 goto out; 114 } 115 udev->led_cdev.max_brightness = udev->user_dev.max_brightness; 116 117 ret = devm_led_classdev_register(uleds_misc.this_device, 118 &udev->led_cdev); 119 if (ret < 0) 120 goto out; 121 122 udev->new_data = true; 123 udev->state = ULEDS_STATE_REGISTERED; 124 ret = count; 125 126 out: 127 mutex_unlock(&udev->mutex); 128 129 return ret; 130 } 131 132 static ssize_t uleds_read(struct file *file, char __user *buffer, size_t count, 133 loff_t *ppos) 134 { 135 struct uleds_device *udev = file->private_data; 136 ssize_t retval; 137 138 if (count < sizeof(udev->brightness)) 139 return 0; 140 141 do { 142 retval = mutex_lock_interruptible(&udev->mutex); 143 if (retval) 144 return retval; 145 146 if (udev->state != ULEDS_STATE_REGISTERED) { 147 retval = -ENODEV; 148 } else if (!udev->new_data && (file->f_flags & O_NONBLOCK)) { 149 retval = -EAGAIN; 150 } else if (udev->new_data) { 151 if (copy_to_user(buffer, &udev->brightness, 152 sizeof(udev->brightness))) { 153 retval = -EFAULT; 154 } else { 155 udev->new_data = false; 156 retval = sizeof(udev->brightness); 157 } 158 } 159 160 mutex_unlock(&udev->mutex); 161 162 if (retval) 163 break; 164 165 if (!(file->f_flags & O_NONBLOCK)) 166 retval = wait_event_interruptible(udev->waitq, 167 udev->new_data || 168 udev->state != ULEDS_STATE_REGISTERED); 169 } while (retval == 0); 170 171 return retval; 172 } 173 174 static __poll_t uleds_poll(struct file *file, poll_table *wait) 175 { 176 struct uleds_device *udev = file->private_data; 177 178 poll_wait(file, &udev->waitq, wait); 179 180 if (udev->new_data) 181 return EPOLLIN | EPOLLRDNORM; 182 183 return 0; 184 } 185 186 static int uleds_release(struct inode *inode, struct file *file) 187 { 188 struct uleds_device *udev = file->private_data; 189 190 if (udev->state == ULEDS_STATE_REGISTERED) { 191 udev->state = ULEDS_STATE_UNKNOWN; 192 devm_led_classdev_unregister(uleds_misc.this_device, 193 &udev->led_cdev); 194 } 195 kfree(udev); 196 197 return 0; 198 } 199 200 static const struct file_operations uleds_fops = { 201 .owner = THIS_MODULE, 202 .open = uleds_open, 203 .release = uleds_release, 204 .read = uleds_read, 205 .write = uleds_write, 206 .poll = uleds_poll, 207 }; 208 209 static struct miscdevice uleds_misc = { 210 .fops = &uleds_fops, 211 .minor = MISC_DYNAMIC_MINOR, 212 .name = ULEDS_NAME, 213 }; 214 215 module_misc_device(uleds_misc); 216 217 MODULE_AUTHOR("David Lechner <david@lechnology.com>"); 218 MODULE_DESCRIPTION("Userspace driver for the LED subsystem"); 219 MODULE_LICENSE("GPL"); 220