Re: [RFC 1/1 v2] Input: Add ps2emu module

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Stephen,

On Tue, Jul 21, 2015 at 03:47:17PM -0400, Stephen Chandler Paul wrote:
> Debugging input devices, specifically laptop touchpads, can be tricky
> without having the physical device handy. Here we try to remedy that
> with ps2emu. This module allows an application to connect to a character
> device provided by the kernel, and simulate any PS/2 device. In
> combination with userspace programs that can record PS/2 devices and
> replay them through the /dev/ps2emu device, this allows developers to
> debug driver issues on the PS/2 level with devices simply by requesting
> a recording from the user experiencing the issue without having to have
> the physical hardware in front of them.

This does not seem to be limited to PS/2, why not userio to keep it in
the vein of uinput, uhid, etc?

> 
> Signed-off-by: Stephen Chandler Paul <cpaul@xxxxxxxxxx>
> Reviewed-by: Benjamin Tissoires <benjamin.tissoires@xxxxxxxxxx>
> ---
> 				    Changes
> * Remove PS2EMU_MINOR, use MISC_DYNAMIC_MINOR
> * Remove ps2emu_warn(), just use dev_warn()
> * Don't return value from copy_to_user(), return -EFAULT
> * Remove usages of unlikely()
> * Remove call to nonseekable_open()
> 
> 			     Things I didn't change
> * Didn't rename this_device, I might have misinterpreted what you were saying
>   but this_device is a member of a struct that isn't defined in any of my own
>   patches. I could have renamed ps2emu_misc and ps2emu_fops to misc and fops,
>   but I'm guessing that's the wrong thing to do if I go off the style of the
>   other driver files in the kernel tree (in drivers/input, anyway).
> 
>  Documentation/input/ps2emu.txt |  72 ++++++++++++
>  MAINTAINERS                    |   6 +
>  drivers/input/serio/Kconfig    |  10 ++
>  drivers/input/serio/Makefile   |   1 +
>  drivers/input/serio/ps2emu.c   | 250 +++++++++++++++++++++++++++++++++++++++++
>  include/uapi/linux/ps2emu.h    |  42 +++++++
>  6 files changed, 381 insertions(+)
>  create mode 100644 Documentation/input/ps2emu.txt
>  create mode 100644 drivers/input/serio/ps2emu.c
>  create mode 100644 include/uapi/linux/ps2emu.h
> 
> diff --git a/Documentation/input/ps2emu.txt b/Documentation/input/ps2emu.txt
> new file mode 100644
> index 0000000..560298c
> --- /dev/null
> +++ b/Documentation/input/ps2emu.txt
> @@ -0,0 +1,72 @@
> +			      The ps2emu Protocol
> +	     (c) 2015 Stephen Chandler Paul <thatslyude@xxxxxxxxx>
> +			      Sponsored by Red Hat
> +--------------------------------------------------------------------------------
> +
> +1. Introduction
> +~~~~~~~~~~~~~~~
> +  This module is intended to try to make the lives of input driver developers
> +easier by allowing them to test various PS/2 devices (mainly the various
> +touchpads found on laptops) without having to have the physical device in front
> +of them. ps2emu accomplishes this by allowing any privileged userspace program
> +to directly interact with the kernel's serio driver and pretend to be a PS/2
> +device.
> +
> +2. Usage overview
> +~~~~~~~~~~~~~~~~~
> +  In order to interact with the ps2emu kernel module, one simply opens the
> +/dev/ps2emu character device in their applications. Commands are sent to the
> +kernel module by writing to the device, and any data received from the serio
> +driver is read as-is from the /dev/ps2emu device. All of the structures and
> +macros you need to interact with the device are defined in <linux/ps2emu.h>.
> +
> +3. Command Structure
> +~~~~~~~~~~~~~~~~~~~~
> +  The struct used for sending commands to /dev/ps2emu is as follows:
> +
> +	struct ps2emu_cmd {
> +		__u8 type;
> +		__u8 data;
> +	};
> +
> +  "type" describes the type of command that is being sent. This can be any one
> +of the PS2EMU_CMD macros defined in <linux/ps2emu.h>. "data" is the argument
> +that goes along with the command. In the event that the command doesn't have an
> +argument, this field can be left untouched and will be ignored by the kernel.
> +Each command should be sent by writing the struct directly to the character
> +device. In the event that the command you send is invalid, an error will be
> +returned by the character device and a more descriptive error will be printed
> +to the kernel log. Only one command can be sent at a time, any additional data
> +written to the character device after the initial command will be ignored.
> +  To close the virtual PS/2 port, just close /dev/ps2emu.
> +
> +4. Commands
> +~~~~~~~~~~~
> +
> +4.1 PS2EMU_CMD_REGISTER
> +~~~~~~~~~~~~~~~~~~~~~~~
> +  Registers the port with the serio driver and begins transmitting data back and
> +forth. Registration can only be performed once a port type is set with
> +PS2EMU_CMD_SET_PORT_TYPE. Has no argument.
> +
> +4.2 PS2EMU_CMD_SET_PORT_TYPE
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +  Sets the type of port we're emulating, where "data" is the port type being
> +set. Can be any of the following macros from <linux/serio.h>:
> +
> +	SERIO_8042
> +	SERIO_8042_XL
> +	SERIO_PS_PSTHRU

Why not any others?

> +
> +4.3 PS2EMU_CMD_SEND_INTERRUPT
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +  Sends an interrupt through the virtual PS/2 port to the serio driver, where
> +"data" is the interrupt data being sent.

Might want to also allow sending "flags".

> +
> +5. Userspace tools
> +~~~~~~~~~~~~~~~~~~
> +  The ps2emu userspace tools are able to record PS/2 devices using some of the
> +debugging information from i8042, and play back the devices on /dev/ps2emu. The
> +latest version of these tools can be found at:
> +
> +	https://github.com/Lyude/ps2emu
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a226416..68a0977 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -10877,6 +10877,12 @@ S:	Maintained
>  F:	drivers/media/v4l2-core/videobuf2-*
>  F:	include/media/videobuf2-*
>  
> +VIRTUAL PS/2 DEVICE DRIVER
> +M:	Stephen Chandler Paul <thatslyude@xxxxxxxxx>
> +S:	Maintained
> +F:	drivers/input/serio/ps2emu.c
> +F:	include/uapi/linux/ps2emu.h
> +
>  VIRTIO CONSOLE DRIVER
>  M:	Amit Shah <amit.shah@xxxxxxxxxx>
>  L:	virtualization@xxxxxxxxxxxxxxxxxxxxxxxxxx
> diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
> index 200841b..cc3563f 100644
> --- a/drivers/input/serio/Kconfig
> +++ b/drivers/input/serio/Kconfig
> @@ -292,4 +292,14 @@ config SERIO_SUN4I_PS2
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called sun4i-ps2.
>  
> +config PS2EMU
> +	tristate "Virtual PS/2 device support"
> +	help
> +	  Say Y here if you want to emulate PS/2 devices using the ps2emu tools.
> +
> +	  To compile this driver as a module, choose M here: the module will be
> +	  called ps2emu.
> +
> +	  If you are unsure, say N.
> +
>  endif
> diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
> index c600089..7b20936 100644
> --- a/drivers/input/serio/Makefile
> +++ b/drivers/input/serio/Makefile
> @@ -30,3 +30,4 @@ obj-$(CONFIG_SERIO_APBPS2)	+= apbps2.o
>  obj-$(CONFIG_SERIO_OLPC_APSP)	+= olpc_apsp.o
>  obj-$(CONFIG_HYPERV_KEYBOARD)	+= hyperv-keyboard.o
>  obj-$(CONFIG_SERIO_SUN4I_PS2)	+= sun4i-ps2.o
> +obj-$(CONFIG_PS2EMU)		+= ps2emu.o
> diff --git a/drivers/input/serio/ps2emu.c b/drivers/input/serio/ps2emu.c
> new file mode 100644
> index 0000000..73bf389
> --- /dev/null
> +++ b/drivers/input/serio/ps2emu.c
> @@ -0,0 +1,250 @@
> +/*
> + * ps2emu kernel PS/2 device emulation module
> + * Copyright (C) 2015 Red Hat
> + * Copyright (C) 2015 Stephen Chandler Paul <thatslyude@xxxxxxxxx>
> + *
> + * 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/libps2.h>
> +#include <linux/slab.h>
> +#include <linux/fs.h>
> +#include <linux/miscdevice.h>
> +#include <linux/sched.h>
> +#include <linux/poll.h>
> +#include <uapi/linux/ps2emu.h>
> +
> +#define PS2EMU_NAME "ps2emu"
> +#define PS2EMU_BUFSIZE 16
> +
> +static const struct file_operations ps2emu_fops;
> +static struct miscdevice ps2emu_misc;
> +
> +struct ps2emu_device {
> +	struct serio serio;

Do not embed serio into your structire but allocate separately as serio
is refcounted and you do not have exact control over when it will be
released.

> +
> +	bool running;
> +
> +	u8 head;
> +	u8 tail;
> +	unsigned char buf[PS2EMU_BUFSIZE];
> +
> +	wait_queue_head_t waitq;
> +};
> +
> +/**
> + * ps2emu_device_write - Write data from serio to a ps2emu device in userspace
> + * @id: The serio port for the ps2emu device
> + * @val: The data to write to the device
> + */
> +static int ps2emu_device_write(struct serio *id, unsigned char val)
> +{
> +	struct ps2emu_device *ps2emu = id->port_data;
> +	u8 newhead;
> +
> +	ps2emu->buf[ps2emu->head] = val;
> +
> +	newhead = ps2emu->head + 1;
> +
> +	if (newhead < PS2EMU_BUFSIZE)
> +		ps2emu->head = newhead;
> +	else
> +		ps2emu->head = 0;

Why not

	ps2emu->head = (ps2emu->head + 1) % PS2EMU_BUFSIZE;

given your chosen bufsize it shoudl be optimized to increment and
bitwise and.

You need locking though.

> +
> +	if (newhead == ps2emu->tail)
> +		dev_warn(ps2emu_misc.this_device,
> +			 "Buffer overflowed, ps2emu client isn't keeping up");
> +
> +	wake_up_interruptible(&ps2emu->waitq);
> +
> +	return 0;
> +}
> +
> +static int ps2emu_char_open(struct inode *inode, struct file *file)
> +{
> +	struct ps2emu_device *ps2emu = NULL;
> +
> +	ps2emu = kzalloc(sizeof(struct ps2emu_device), GFP_KERNEL);
> +	if (!ps2emu)
> +		return -ENOMEM;
> +
> +	init_waitqueue_head(&ps2emu->waitq);
> +
> +	ps2emu->serio.write = ps2emu_device_write;
> +	ps2emu->serio.port_data = ps2emu;
> +
> +	file->private_data = ps2emu;
> +
> +	return 0;
> +}
> +
> +static int ps2emu_char_release(struct inode *inode, struct file *file)
> +{
> +	struct ps2emu_device *ps2emu = file->private_data;
> +
> +	/*
> +	 * We can rely on serio_unregister_port() to free the ps2emu struct on
> +	 * it's own
> +	 */
> +	if (ps2emu->running)
> +		serio_unregister_port(&ps2emu->serio);
> +	else
> +		kfree(ps2emu);
> +
> +	return 0;
> +}
> +
> +static ssize_t ps2emu_char_read(struct file *file, char __user *buffer,
> +				size_t count, loff_t *ppos)
> +{
> +	struct ps2emu_device *ps2emu = file->private_data;
> +	int ret;
> +	size_t nonwrap_len, copylen;
> +	u8 head; /* So we only access ps2emu->head once */

Why is it important?

> +
> +	if (file->f_flags & O_NONBLOCK) {
> +		head = ps2emu->head;
> +
> +		if (head == ps2emu->tail)
> +			return -EAGAIN;
> +	} else {
> +		ret = wait_event_interruptible(
> +		       ps2emu->waitq, (head = ps2emu->head) != ps2emu->tail);

If I understand it correctly you need to treat blocking read with 0
length properly: it should not wait.

> +
> +		if (ret)
> +			return ret;
> +	}
> +
> +	nonwrap_len = CIRC_CNT_TO_END(head, ps2emu->tail, PS2EMU_BUFSIZE);
> +	copylen = min(nonwrap_len, count);
> +
> +	if (copy_to_user(buffer, &ps2emu->buf[ps2emu->tail], copylen))
> +		return -EFAULT;
> +
> +	ps2emu->tail += copylen;
> +	if (ps2emu->tail == PS2EMU_BUFSIZE)
> +		ps2emu->tail = 0;

Locking needed gain - you can have several readers.

> +
> +	return copylen;
> +}
> +
> +static ssize_t ps2emu_char_write(struct file *file, const char __user *buffer,
> +				 size_t count, loff_t *ppos)
> +{
> +	struct ps2emu_device *ps2emu = file->private_data;
> +	struct ps2emu_cmd cmd;
> +
> +	if (count < sizeof(cmd))
> +		return -EINVAL;
> +
> +	if (copy_from_user(&cmd, buffer, sizeof(cmd)))
> +		return -EFAULT;
> +
> +	switch (cmd.type) {
> +	case PS2EMU_CMD_REGISTER:
> +		if (!ps2emu->serio.id.type) {
> +			dev_warn(ps2emu_misc.this_device,
> +				 "No port type given on /dev/ps2emu\n");
> +
> +			return -EINVAL;
> +		}
> +		if (ps2emu->running) {
> +			dev_warn(ps2emu_misc.this_device,
> +				 "Begin command sent, but we're already running\n");
> +
> +			return -EINVAL;
> +		}
> +
> +		ps2emu->running = true;
> +		serio_register_port(&ps2emu->serio);
> +		break;
> +
> +	case PS2EMU_CMD_SET_PORT_TYPE:
> +		if (ps2emu->running) {
> +			dev_warn(ps2emu_misc.this_device,
> +				 "Can't change port type on an already running ps2emu instance\n");
> +
> +			return -EINVAL;
> +		}
> +
> +		switch (cmd.data) {
> +		case SERIO_8042:
> +		case SERIO_8042_XL:
> +		case SERIO_PS_PSTHRU:
> +			ps2emu->serio.id.type = cmd.data;
> +			break;
> +
> +		default:
> +			dev_warn(ps2emu_misc.this_device,
> +				 "Invalid port type 0x%hhx\n", cmd.data);

Why not allow others?

> +
> +			return -EINVAL;
> +		}
> +
> +		break;
> +
> +	case PS2EMU_CMD_SEND_INTERRUPT:
> +		if (!ps2emu->running) {
> +			dev_warn(ps2emu_misc.this_device,
> +				 "The device must be registered before sending interrupts\n");
> +
> +			return -EINVAL;
> +		}
> +
> +		serio_interrupt(&ps2emu->serio, cmd.data, 0);
> +
> +		break;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return sizeof(cmd);
> +}
> +
> +static unsigned int ps2emu_char_poll(struct file *file, poll_table *wait)
> +{
> +	struct ps2emu_device *ps2emu = file->private_data;
> +
> +	poll_wait(file, &ps2emu->waitq, wait);
> +
> +	if (ps2emu->head != ps2emu->tail)
> +		return POLLIN | POLLRDNORM;
> +
> +	return 0;
> +}
> +
> +static const struct file_operations ps2emu_fops = {
> +	.owner		= THIS_MODULE,
> +	.open		= ps2emu_char_open,
> +	.release	= ps2emu_char_release,
> +	.read		= ps2emu_char_read,
> +	.write		= ps2emu_char_write,
> +	.poll		= ps2emu_char_poll,
> +	.llseek		= no_llseek,
> +};
> +
> +static struct miscdevice ps2emu_misc = {
> +	.fops	= &ps2emu_fops,
> +	.minor	= MISC_DYNAMIC_MINOR,
> +	.name	= PS2EMU_NAME,
> +};
> +
> +MODULE_AUTHOR("Stephen Chandler Paul <thatslyude@xxxxxxxxx>");
> +MODULE_DESCRIPTION("ps2emu");
> +MODULE_LICENSE("GPL");
> +
> +module_driver(ps2emu_misc, misc_register, misc_deregister);
> diff --git a/include/uapi/linux/ps2emu.h b/include/uapi/linux/ps2emu.h
> new file mode 100644
> index 0000000..63f5cc9
> --- /dev/null
> +++ b/include/uapi/linux/ps2emu.h
> @@ -0,0 +1,42 @@
> +/*
> + * ps2emu.h
> + * Copyright (C) 2015 Red Hat
> + * Copyright (C) 2015 Lyude (Stephen Chandler Paul) <cpaul@xxxxxxxxxx>
> + *
> + * 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.
> + *
> + * This is the public header used for user-space communication with the ps2emu
> + * driver. __attribute__((__packed__)) is used  for all structs to keep ABI
> + * compatibility between all architectures.
> + */
> +
> +#ifndef _PS2EMU_H
> +#define _PS2EMU_H
> +
> +#include <linux/types.h>
> +
> +#define PS2EMU_CMD_REGISTER		0
> +#define PS2EMU_CMD_SET_PORT_TYPE	1
> +#define PS2EMU_CMD_SEND_INTERRUPT	2
> +
> +/*
> + * ps2emu Commands
> + * All commands sent to /dev/ps2emu are encoded using this structure. The type
> + * field should contain a PS2EMU_CMD* value that indicates what kind of command
> + * is being sent to ps2emu. The data field should contain the accompanying
> + * argument for the command, if there is one.
> + */
> +struct ps2emu_cmd {
> +	__u8 type;
> +	__u8 data;
> +} __attribute__((__packed__));
> +
> +#endif /* !_PS2EMU_H */
> -- 
> 2.4.3
> 

Thanks.

-- 
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-api" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux