216 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Userspace driver for the LED subsystem4 *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};204 205static struct miscdevice uleds_misc = {206 .fops = &uleds_fops,207 .minor = MISC_DYNAMIC_MINOR,208 .name = ULEDS_NAME,209};210 211module_misc_device(uleds_misc);212 213MODULE_AUTHOR("David Lechner <david@lechnology.com>");214MODULE_DESCRIPTION("Userspace driver for the LED subsystem");215MODULE_LICENSE("GPL");216