brintos

brintos / linux-shallow public Read only

0
0
Text · 4.5 KiB · 374a841 Raw
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