| /* | 
 |  * userio kernel serio device emulation module | 
 |  * Copyright (C) 2015 Red Hat | 
 |  * Copyright (C) 2015 Stephen Chandler Paul <thatslyude@gmail.com> | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify it | 
 |  * under the terms of the GNU Lesser General Public License as published by | 
 |  * the Free Software Foundation; either version 2 of the License, or (at | 
 |  * your option) any later version. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, but | 
 |  * WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser | 
 |  * General Public License for more details. | 
 |  */ | 
 |  | 
 | #include <linux/circ_buf.h> | 
 | #include <linux/mutex.h> | 
 | #include <linux/module.h> | 
 | #include <linux/init.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/serio.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/fs.h> | 
 | #include <linux/miscdevice.h> | 
 | #include <linux/sched.h> | 
 | #include <linux/poll.h> | 
 | #include <uapi/linux/userio.h> | 
 |  | 
 | #define USERIO_NAME		"userio" | 
 | #define USERIO_BUFSIZE		16 | 
 |  | 
 | static struct miscdevice userio_misc; | 
 |  | 
 | struct userio_device { | 
 | 	struct serio *serio; | 
 | 	struct mutex mutex; | 
 |  | 
 | 	bool running; | 
 |  | 
 | 	u8 head; | 
 | 	u8 tail; | 
 |  | 
 | 	spinlock_t buf_lock; | 
 | 	unsigned char buf[USERIO_BUFSIZE]; | 
 |  | 
 | 	wait_queue_head_t waitq; | 
 | }; | 
 |  | 
 | /** | 
 |  * userio_device_write - Write data from serio to a userio device in userspace | 
 |  * @id: The serio port for the userio device | 
 |  * @val: The data to write to the device | 
 |  */ | 
 | static int userio_device_write(struct serio *id, unsigned char val) | 
 | { | 
 | 	struct userio_device *userio = id->port_data; | 
 | 	unsigned long flags; | 
 |  | 
 | 	spin_lock_irqsave(&userio->buf_lock, flags); | 
 |  | 
 | 	userio->buf[userio->head] = val; | 
 | 	userio->head = (userio->head + 1) % USERIO_BUFSIZE; | 
 |  | 
 | 	if (userio->head == userio->tail) | 
 | 		dev_warn(userio_misc.this_device, | 
 | 			 "Buffer overflowed, userio client isn't keeping up"); | 
 |  | 
 | 	spin_unlock_irqrestore(&userio->buf_lock, flags); | 
 |  | 
 | 	wake_up_interruptible(&userio->waitq); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int userio_char_open(struct inode *inode, struct file *file) | 
 | { | 
 | 	struct userio_device *userio; | 
 |  | 
 | 	userio = kzalloc(sizeof(struct userio_device), GFP_KERNEL); | 
 | 	if (!userio) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	mutex_init(&userio->mutex); | 
 | 	spin_lock_init(&userio->buf_lock); | 
 | 	init_waitqueue_head(&userio->waitq); | 
 |  | 
 | 	userio->serio = kzalloc(sizeof(struct serio), GFP_KERNEL); | 
 | 	if (!userio->serio) { | 
 | 		kfree(userio); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	userio->serio->write = userio_device_write; | 
 | 	userio->serio->port_data = userio; | 
 |  | 
 | 	file->private_data = userio; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int userio_char_release(struct inode *inode, struct file *file) | 
 | { | 
 | 	struct userio_device *userio = file->private_data; | 
 |  | 
 | 	if (userio->running) { | 
 | 		/* | 
 | 		 * Don't free the serio port here, serio_unregister_port() | 
 | 		 * does it for us. | 
 | 		 */ | 
 | 		serio_unregister_port(userio->serio); | 
 | 	} else { | 
 | 		kfree(userio->serio); | 
 | 	} | 
 |  | 
 | 	kfree(userio); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static ssize_t userio_char_read(struct file *file, char __user *user_buffer, | 
 | 				size_t count, loff_t *ppos) | 
 | { | 
 | 	struct userio_device *userio = file->private_data; | 
 | 	int error; | 
 | 	size_t nonwrap_len, copylen; | 
 | 	unsigned char buf[USERIO_BUFSIZE]; | 
 | 	unsigned long flags; | 
 |  | 
 | 	/* | 
 | 	 * By the time we get here, the data that was waiting might have | 
 | 	 * been taken by another thread. Grab the buffer lock and check if | 
 | 	 * there's still any data waiting, otherwise repeat this process | 
 | 	 * until we have data (unless the file descriptor is non-blocking | 
 | 	 * of course). | 
 | 	 */ | 
 | 	for (;;) { | 
 | 		spin_lock_irqsave(&userio->buf_lock, flags); | 
 |  | 
 | 		nonwrap_len = CIRC_CNT_TO_END(userio->head, | 
 | 					      userio->tail, | 
 | 					      USERIO_BUFSIZE); | 
 | 		copylen = min(nonwrap_len, count); | 
 | 		if (copylen) { | 
 | 			memcpy(buf, &userio->buf[userio->tail], copylen); | 
 | 			userio->tail = (userio->tail + copylen) % | 
 | 							USERIO_BUFSIZE; | 
 | 		} | 
 |  | 
 | 		spin_unlock_irqrestore(&userio->buf_lock, flags); | 
 |  | 
 | 		if (nonwrap_len) | 
 | 			break; | 
 |  | 
 | 		/* buffer was/is empty */ | 
 | 		if (file->f_flags & O_NONBLOCK) | 
 | 			return -EAGAIN; | 
 |  | 
 | 		/* | 
 | 		 * count == 0 is special - no IO is done but we check | 
 | 		 * for error conditions (see above). | 
 | 		 */ | 
 | 		if (count == 0) | 
 | 			return 0; | 
 |  | 
 | 		error = wait_event_interruptible(userio->waitq, | 
 | 						 userio->head != userio->tail); | 
 | 		if (error) | 
 | 			return error; | 
 | 	} | 
 |  | 
 | 	if (copylen) | 
 | 		if (copy_to_user(user_buffer, buf, copylen)) | 
 | 			return -EFAULT; | 
 |  | 
 | 	return copylen; | 
 | } | 
 |  | 
 | static ssize_t userio_char_write(struct file *file, const char __user *buffer, | 
 | 				 size_t count, loff_t *ppos) | 
 | { | 
 | 	struct userio_device *userio = file->private_data; | 
 | 	struct userio_cmd cmd; | 
 | 	int error; | 
 |  | 
 | 	if (count != sizeof(cmd)) { | 
 | 		dev_warn(userio_misc.this_device, "Invalid payload size\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	if (copy_from_user(&cmd, buffer, sizeof(cmd))) | 
 | 		return -EFAULT; | 
 |  | 
 | 	error = mutex_lock_interruptible(&userio->mutex); | 
 | 	if (error) | 
 | 		return error; | 
 |  | 
 | 	switch (cmd.type) { | 
 | 	case USERIO_CMD_REGISTER: | 
 | 		if (!userio->serio->id.type) { | 
 | 			dev_warn(userio_misc.this_device, | 
 | 				 "No port type given on /dev/userio\n"); | 
 |  | 
 | 			error = -EINVAL; | 
 | 			goto out; | 
 | 		} | 
 |  | 
 | 		if (userio->running) { | 
 | 			dev_warn(userio_misc.this_device, | 
 | 				 "Begin command sent, but we're already running\n"); | 
 | 			error = -EBUSY; | 
 | 			goto out; | 
 | 		} | 
 |  | 
 | 		userio->running = true; | 
 | 		serio_register_port(userio->serio); | 
 | 		break; | 
 |  | 
 | 	case USERIO_CMD_SET_PORT_TYPE: | 
 | 		if (userio->running) { | 
 | 			dev_warn(userio_misc.this_device, | 
 | 				 "Can't change port type on an already running userio instance\n"); | 
 | 			error = -EBUSY; | 
 | 			goto out; | 
 | 		} | 
 |  | 
 | 		userio->serio->id.type = cmd.data; | 
 | 		break; | 
 |  | 
 | 	case USERIO_CMD_SEND_INTERRUPT: | 
 | 		if (!userio->running) { | 
 | 			dev_warn(userio_misc.this_device, | 
 | 				 "The device must be registered before sending interrupts\n"); | 
 | 			error = -ENODEV; | 
 | 			goto out; | 
 | 		} | 
 |  | 
 | 		serio_interrupt(userio->serio, cmd.data, 0); | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		error = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | out: | 
 | 	mutex_unlock(&userio->mutex); | 
 | 	return error ?: count; | 
 | } | 
 |  | 
 | static __poll_t userio_char_poll(struct file *file, poll_table *wait) | 
 | { | 
 | 	struct userio_device *userio = file->private_data; | 
 |  | 
 | 	poll_wait(file, &userio->waitq, wait); | 
 |  | 
 | 	if (userio->head != userio->tail) | 
 | 		return EPOLLIN | EPOLLRDNORM; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct file_operations userio_fops = { | 
 | 	.owner		= THIS_MODULE, | 
 | 	.open		= userio_char_open, | 
 | 	.release	= userio_char_release, | 
 | 	.read		= userio_char_read, | 
 | 	.write		= userio_char_write, | 
 | 	.poll		= userio_char_poll, | 
 | 	.llseek		= no_llseek, | 
 | }; | 
 |  | 
 | static struct miscdevice userio_misc = { | 
 | 	.fops	= &userio_fops, | 
 | 	.minor	= USERIO_MINOR, | 
 | 	.name	= USERIO_NAME, | 
 | }; | 
 | module_driver(userio_misc, misc_register, misc_deregister); | 
 |  | 
 | MODULE_ALIAS_MISCDEV(USERIO_MINOR); | 
 | MODULE_ALIAS("devname:" USERIO_NAME); | 
 |  | 
 | MODULE_AUTHOR("Stephen Chandler Paul <thatslyude@gmail.com>"); | 
 | MODULE_DESCRIPTION("Virtual Serio Device Support"); | 
 | MODULE_LICENSE("GPL"); |