Re: [RFC][PATCH] POSKeyboard driver for exclusive keyboard access

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

 



On Fri, 22 Aug 2008 18:17:28 +0200
"Niels de Vos" <niels.devos@xxxxxxxxxxxxxxxxxx> wrote:

> This new driver makes it possible for middleware like JavaPOS to use
> a POSKeyboard (connected to PS/2) with exclusive access. This is
> required by the UnifiedPOS Specification which is available from
> http://www.nrf-arts.org/UnifiedPOS. Any middleware using this driver
> should implement the full exception-handling in user-space. Therefor
> it is possible to use specific POS-extensions of POSKeyboards, without
> abusing other keyboard-drivers.
> 
> Opening /dev/poskeyboard will route all scancodes to this device. The
> scancodes will not be processes by the input-subsystem anymore. Reading
> /dev/poskeyboard results in receiving the scancodes as raw data for
> further processing by the reader. Sending commands to the hardware can
> be done by writing to /dev/poskeyboard.
> 
> If the driver is loaded and /dev/poskeyboard is not opened, all
> scancodes are given to the input-subsystem. This allows 'normal' use of
> the keyboard.
> 
> Making the driver active involves some commands like the following:
>    echo -n serio1 > /sys/bus/serio/drivers/atkbd/unbind
>    echo -n serio1 > /sys/bus/serio/drivers/poskbd/bind
> 
> Open questions:
> - How to achieve the same with USB-HID complaint keyboards?
> - Does serio_unregister_port() a kfree() on the port?
> 
> CC: Jiri Kosina <jkosina@xxxxxxx>
> Signed-off-by: Niels de Vos <niels.devos@xxxxxxxxxxxxxxxxxx>
> 
> ---
>  drivers/input/keyboard/Kconfig  |   12 +
>  drivers/input/keyboard/Makefile |    1 +
>  drivers/input/keyboard/poskbd.c |  445 +++++++++++++++++++++++++++++++++++++++
>  3 files changed, 458 insertions(+), 0 deletions(-)
> ---
> diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> index efd70a9..0ed311f 100644
> --- a/drivers/input/keyboard/Kconfig
> +++ b/drivers/input/keyboard/Kconfig
> @@ -323,4 +323,16 @@ config KEYBOARD_SH_KEYSC
> 
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called sh_keysc.
> +
> +config KEYBOARD_POSKBD
> +	tristate "UnifiedPOS complaint POS Keyboard access"
> +	default n
> +	select SERIO
> +	help
> +	  Say Y here if you need a driver which allows exclusive-use
> +	  to keyboard devices. This is required by the UnifiedPOS
> +	  Specification for the POS Keyboard device.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called poskbd.
>  endif
> diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> index 0edc8f2..aa4ad17 100644
> --- a/drivers/input/keyboard/Makefile
> +++ b/drivers/input/keyboard/Makefile
> @@ -27,3 +27,4 @@ obj-$(CONFIG_KEYBOARD_HP7XX)		+= jornada720_kbd.o
>  obj-$(CONFIG_KEYBOARD_MAPLE)		+= maple_keyb.o
>  obj-$(CONFIG_KEYBOARD_BFIN)		+= bf54x-keys.o
>  obj-$(CONFIG_KEYBOARD_SH_KEYSC)		+= sh_keysc.o
> +obj-$(CONFIG_KEYBOARD_POSKBD)		+= poskbd.o
> diff --git a/drivers/input/keyboard/poskbd.c b/drivers/input/keyboard/poskbd.c
> new file mode 100644
> index 0000000..7d7e1d5
> --- /dev/null
> +++ b/drivers/input/keyboard/poskbd.c
> @@ -0,0 +1,445 @@
> +/**
> + * Serio driver for exclusive-use keyboard access
> + *
> + * The UnifiedPOS Specification requires the POS keyboard to be an
> + * exclusive-use device. This means that the device is not allowed to
> + * send and/or receive events from processes other than the process
> + * which 'claimed' the device. This driver uses the serio-subsystem
> + * to get access to connected keyboard. After opening the associated
> + * device-node, the scancodes will only be available to the reader of
> + * the device-node. Therefore the reader must understand the protocol
> + * of the keyboard and implement the handling of exceptions and
> + * extensions by itself. Sending commands to the hardware is possible
> + * by writing the required bytes to the open file-descriptor.
> + *
> + * UnifiedPOS Sepecification: http://www.nrf-arts.org/UnifiedPOS
> + *
> + * This driver can be used by unbinding i.e. atkbd and binding poskbd:
> + * # echo -n serio1 > /sys/bus/serio/drivers/atkbd/unbind
> + * # echo -n serio1 > /sys/bus/serio/drivers/poskbd/bind
> + *
> + * Copyright (C) 2008 Wincor Nixdorf International
> + *
> + * Idea by and with help from SUSE Labs, Jiri Kosina <jkosina@xxxxxxx>.
> + *
> + * Author:
> + *   Niels de Vos <niels.devos@xxxxxxxxxxxxxxxxxx>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2.  See
> + * the COPYING file in the top-level directory.
> + *
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +
> +#include <linux/serio.h>
> +#include <linux/interrupt.h>
> +#include <linux/irqreturn.h>
> +
> +#include <linux/types.h>
> +#include <linux/wait.h>
> +#include <linux/slab.h>
> +#include <asm/uaccess.h>
> +#include <linux/errno.h>
> +#include <linux/list.h>
> +
> +#include <linux/fs.h>
> +#include <linux/miscdevice.h>
> +
> +/* driver info */
> +#define DRIVER_NAME	"poskbd"
> +#define DRIVER_DESC	"POSKeyboard driver for exclusive keyboard access"
> +#define DRIVER_AUTHOR	"Niels de Vos, Wincor Nixdorf International"
> +#define POSKBD_DEV	"poskeyboard"
> +
> +
> +/* enable debugging over a module-parameter */
> +#define dbg(fmt, args...) \
> +	if (debug >= 2) { printk(KERN_DEBUG "%s: " fmt, __func__, ##args); }
> +#define verbose(fmt, args...) \
> +	if (debug) { printk(KERN_INFO "%s: " fmt, __func__, ##args); }
> +#define warning(fmt, args...) \
> +	printk(KERN_WARNING "%s(%s): " fmt, DRIVER_NAME, __func__, ##args);

I think the new dynamic-debugging infrastructure is in Greg's tree (and
hence linux-next) now.  If not, it soon will be.

> +
> +/* for struct serio_driver*/
> +static int poskbd_connect(struct serio *serio, struct serio_driver *drv);
> +static void poskbd_disconnect(struct serio *serio);
> +static irqreturn_t poskbd_interrupt(struct serio *serio,
> +	unsigned char scancode, unsigned int flags);
> +
> +
> +/* for struct serio */
> +static int poskbd_port_open(struct serio *port);
> +static void poskbd_port_close(struct serio *port);
> +
> +
> +/* for struct file_operations */
> +static int poskbd_fs_open(struct inode *, struct file *);
> +static int poskbd_fs_release(struct inode *, struct file *);
> +static ssize_t poskbd_fs_read(struct file *, char __user *, size_t, loff_t *);
> +static ssize_t poskbd_fs_write(struct file *, const char __user *,
> size_t, loff_t *);
> +
> +
> +static struct serio_device_id poskbd_ids[] = {
> +	{
> +		.type   = SERIO_8042_XL,
> +		.proto  = SERIO_ANY,
> +		.id     = SERIO_ANY,
> +		.extra  = SERIO_ANY,
> +	},
> +	{ 0 }
> +};
> +
> +
> +static struct serio_driver poskbd_drv = {
> +	.driver         = {
> +		.name   = DRIVER_NAME,
> +	},
> +	.description    = DRIVER_DESC,
> +	.id_table       = poskbd_ids,
> +	.interrupt      = poskbd_interrupt,
> +	.connect        = poskbd_connect,
> +	.disconnect     = poskbd_disconnect,
> +};
> +
> +static struct file_operations poskbd_fs = {
> +	.owner		= THIS_MODULE,
> +	.read		= poskbd_fs_read,
> +	.write		= poskbd_fs_write,
> +	.open		= poskbd_fs_open,
> +	.release	= poskbd_fs_release,
> +	.llseek		= no_llseek,
> +};
> +
> +
> +static struct miscdevice poskbd_md = {
> +        .minor	= MISC_DYNAMIC_MINOR,
> +        .name	= POSKBD_DEV,
> +        .fops	= &poskbd_fs,
> +};
> +
> +
> +/**
> + * struct scancode_buf: list of buffered scancodes
> + *
> + * @list: for a dynamical list
> + * @scancode: the scancode
> + */
> +struct scancode_buf {
> +	struct list_head list;
> +	unsigned char scancode;
> +};
> +
> +
> +/**
> + * struct poskbd_status: driver internal status
> + *
> + * @hw_port: the real port to write the commands to (struct serio from atkbd)
> + * @kbd_port: the port for the POSKeyboard
> + * @wq: blocked on while waiting for more data
> + * @data: the list of buffered scancodes
> + * @opened: counter for of opened device-nodes
> + */
> +static struct poskbd_status {
> +	struct serio *hw_port;
> +	struct serio *kbd_port;
> +	wait_queue_head_t wq;
> +	struct scancode_buf *data;
> +	short opened;
> +} *poskbd_status;
> +
> +
> +/* parameters for the driver */
> +static int debug = 0;

unneeded initialisation.

Please run scripts/checkpatch.pl across the diff.  It finds rather a
lot of things.

> +static int poskbd_port_open(struct serio *port)
> +{
> +	dbg("nothing to do for %s\n", port->name);
> +	return 0;
> +}
> +
> +static void poskbd_port_close(struct serio *port)
> +{
> +	dbg("nothing to do for %s\n", port->name);
> +}
> +
> +
> +/**
> + * called on binding a serio* to the module
> + *    echo -n serio1 > /sys/bus/serio/drivers/wn_kbd/bind
> + */
> +static int poskbd_connect(struct serio *port, struct serio_driver *drv)
> +{
> +	int ret = 0;
> +	if (poskbd_status->hw_port) {

Please prefer to leave a blank line between end-of-local and start-of-code.

> +		dbg("a hw_port has already been opened...\n");
> +		return ret;
> +	}
> +
> +	ret = serio_open(port, drv);
> +
> +	if (!ret) {
> +		poskbd_status->hw_port = port;
> +	}

unneeded braces

> +	dbg("serio_open() on %s was %s\n", port->name, (ret ? "failed" :
> "was successful"));
> +	return ret;
> +}
> +
> +
> +/**
> + * called on binding a serio* to the module
> + *    echo -n serio1 > /sys/bus/serio/drivers/wn_kbd/unbind
> + */
> +static void poskbd_disconnect(struct serio *port)
> +{
> +	dbg("going to do serio_close(%s)\n", port->name);
> +	serio_close(port);
> +	if (port == poskbd_status->hw_port)
> +		poskbd_status->hw_port = NULL;
> +}
> +
> +
> +/**
> + * called on every keypress, keylock-rotate and card-swipe
> + */

The /** pattern is specificially used to flag a kerneldoc-style
comment.  This isn't a kerneldoc comment, so please just use /*.  (many
instances)

> +static irqreturn_t poskbd_interrupt(struct serio *port,
> +	unsigned char scancode, unsigned int flags)
> +{
> +	struct scancode_buf *data;
> +
> +	if (poskbd_status->opened) {
> +		/* add new scancode to the buffer */
> +		data = kzalloc(sizeof(struct scancode_buf), GFP_KERNEL);

Is this really an interrupt handler?  Looks like it.  If so, use of
GFP_KERNEL uis a big bug and suggests that the code wasn't tested with
suitable kerenl debugging options enabled (see
Documentation/SubmitChecklist).

> +		data->scancode = scancode;

kzalloc() can fail, and this will crash the machine.

> +		list_add_tail(&(data->list), &poskbd_status->data->list);

locking needed for that list?

> +		dbg("added scancode (0x%.2x) to the buffer\n", scancode);
> +
> +		/* wake_up() the read() */
> +		wake_up(&(poskbd_status->wq));
> +		return IRQ_HANDLED;
> +	}
> +
> +	if (!poskbd_status->kbd_port) {
> +		dbg("BUG: no port has been assigned to poskbd_status!\n");
> +		return IRQ_NONE;
> +	}
> +	
> +	dbg("passing scancode (0x%.2x) to serio_interrupt()\n", scancode);
> +	return serio_interrupt(poskbd_status->kbd_port, scancode, flags);
> +}
> +
> +
> +/**
> + * poskbd_fs_open: exclusive open on PS/2 devices
> + *
> + * Only one open allowed, make it really exclusive.
> + */
> +static int poskbd_fs_open(struct inode *inode, struct file *file)
> +{
> +	int ret = 0;
> +
> +	if (poskbd_status->opened) {
> +		verbose("device already opened\n");
> +		ret = -EBUSY;
> +	} else {
> +		poskbd_status->opened = 1;
> +	}
> +	return ret;
> +}
> +
> +
> +/**
> + * poskbd_fs_release: close()
> + */
> +static int poskbd_fs_release(struct inode *inode, struct file *file)
> +{
> +	poskbd_status->opened = 0;
> +	return 0;
> +}
> +
> +
> +/**
> + * poskbd_fs_read: block until data arrived
> + */
> +static ssize_t poskbd_fs_read(struct file *file, char __user *data,
> size_t size, loff_t *pos)

Indent the continuation line a bit.

> +{
> +	struct scancode_buf *buf;
> +	struct list_head *i, *q;
> +	size_t cnt = 0;
> +	int ret;
> +
> +	/* wait until data is available or read() is aborted */
> +	verbose("userspace wants %d/%d of data\n", (int) (size - cnt), (int) size);
> +	if (list_empty(&poskbd_status->data->list)) {
> +		ret = wait_event_interruptible(poskbd_status->wq,
> !list_empty(&poskbd_status->data->list));

ditto

> +		if (ret) {
> +			verbose("read() interrupted: %d\n", ret);
> +			return ret;
> +		}
> +		dbg("I'm awake!\n");
> +	}
> +
> +	/* loop through all buffered scancodes and send them to userspace */
> +	do {
> +		list_for_each_safe(i, q, &poskbd_status->data->list) {
> +			buf = list_entry(i, struct scancode_buf, list);

list_for_each_entry_safe()

> +			dbg("sending scancode (0x%.2x) to userspace\n", buf->scancode);
> +			if (copy_to_user((data + cnt), &(buf->scancode), sizeof(unsigned
> char)) != 0) {
> +				warning("could not copy data to userspace\n");
> +				return -EACCES;

-EFAULT

> +			}
> +			cnt++;
> +
> +			/* remove the scancode from the buffer */
> +			list_del(i);

locking?

> +			kfree(buf);
> +		}
> +	} while (!list_empty(&poskbd_status->data->list) || cnt == size);
> +
> +	verbose("sent %d bytes to userspace\n", (int) cnt);
> +	return cnt;
> +}
> +
> +
> +/**
> + * poskbd_fs_write: send commands to the keyboard
> + */
> +static ssize_t poskbd_fs_write(struct file *file, const char __user
> *data, size_t len, loff_t *pos)

indent

> +{
> +	size_t i;
> +	unsigned char c;
> +	int ret = 0;
> +
> +	if (!poskbd_status->hw_port) {
> +		warning("no device connected\n");
> +		return -ENODEV;

can this happen?

> +	}
> +
> +	for (i = 0; i < len; i++) {
> +		if (copy_from_user(&c, data + i, 1)) {
> +			warning("could not get data from userspace\n");
> +			return -EACCES;

-EFAULT

> +		}
> +		
> +		dbg("going to serio_write(%s, 0x%.2x)\n", poskbd_status->hw_port->name, c);
> +		ret = serio_write(poskbd_status->hw_port, c);
> +		if (ret < 0)
> +			return ret;

Normally a write() will return the number of bytes written even if it
encountered an error.  It will only return -EFOO if the error occurred
and zero bytes were written.

This policy is perhaps not appropriate in a driver however.

> +	}
> +	return i;
> +}
> +
> +
> +/**
> + * called on module loading
> + */
> +static int __init poskbd_init(void)
> +{
> +	int err = 0;
> +
> +	poskbd_status = kzalloc(sizeof(struct poskbd_status), GFP_KERNEL);
> +	if (!poskbd_status) {
> +		warning("could not allocate enough memory for driver status\n");
> +		return -ENOMEM;
> +	}
> +
> +	poskbd_status->data = kzalloc(sizeof(struct scancode_buf), GFP_KERNEL);
> +	if (!poskbd_status->data) {
> +		warning("could not allocate enough memory for scancode buffer\n");
> +		err = -ENOMEM;
> +		goto free1_exit;
> +	}
> +	
> +	INIT_LIST_HEAD(&(poskbd_status->data->list));
> +	init_waitqueue_head(&(poskbd_status->wq));
> +
> +	dbg("going to register_port()\n");
> +	poskbd_status->kbd_port = kzalloc(sizeof(struct serio), GFP_KERNEL);
> +	if (!poskbd_status->kbd_port) {
> +		warning("could not allocate enough memory for port\n");
> +		err = -ENOMEM;
> +		goto free2_exit;
> +	}
> +	/* fill the struct serio with valid data */
> +	poskbd_status->kbd_port->id.type 	= SERIO_8042_XL;
> +	poskbd_status->kbd_port->open		= poskbd_port_open;
> +	poskbd_status->kbd_port->close		= poskbd_port_close;
> +	snprintf(poskbd_status->kbd_port->name,
> sizeof(poskbd_status->kbd_port->name), "POSKeyboard");
> +	snprintf(poskbd_status->kbd_port->phys,
> sizeof(poskbd_status->kbd_port->name), "POSKeyboard");
> +
> +	serio_register_port(poskbd_status->kbd_port);
> +
> +	dbg("going to register_driver()\n");
> +	err = serio_register_driver(&poskbd_drv);
> +	if (err) {
> +		warning("could not register driver (%d)\n", err);
> +		goto free3_exit;
> +	}
> +
> +	dbg("registering character device\n");
> +	err = misc_register(&poskbd_md);
> +	if (err) {
> +		warning("could not register device (%d)\n", err);
> +		goto free4_exit;
> +	}
> +	return 0;
> +
> +free4_exit:
> +	serio_unregister_driver(&poskbd_drv);
> +free3_exit:
> +	serio_unregister_port(poskbd_status->kbd_port);
> +	kfree(poskbd_status->kbd_port);
> +free2_exit:
> +	kfree(poskbd_status->data);
> +free1_exit:
> +	kfree(poskbd_status);
> +	return err;
> +}
> +
> +
> +/**
> + * called on module unloading
> + */
> +static void __exit poskbd_exit(void)
> +{
> +	int err = 0;
> +	struct scancode_buf *data;
> +	struct list_head *pos, *q;
> +	
> +	dbg("unregistering character device\n");
> +	err = misc_deregister(&poskbd_md);
> +
> +	dbg("going to unregister_port %s\n", poskbd_status->kbd_port->name);
> +	serio_unregister_port(poskbd_status->kbd_port);
> +#if 0 /* TODO: does serio_unregister_port() a kfree() on the port? */
> +	kfree(poskbd_status->kbd_port);
> +#endif
> +
> +	dbg("going to unregister_driver()\n");
> +	serio_unregister_driver(&poskbd_drv);
> +
> +	if (!list_empty(&poskbd_status->data->list)) {
> +		dbg("freeing buffer:\n");
> +		list_for_each_safe(pos, q, &poskbd_status->data->list) {
> +			data = list_entry(pos, struct scancode_buf, list);
> +			list_del(pos);
> +			kfree(data);
> +		}
> +	}
> +	kfree(poskbd_status->data);
> +	kfree(poskbd_status);
> +}
> +
> +MODULE_AUTHOR(DRIVER_AUTHOR);
> +MODULE_LICENSE("GPL");
> +MODULE_DEVICE_TABLE(serio, poskbd_ids);
> +MODULE_PARM_DESC(debug, "show additional debug information (default: 0)");
> +
> +module_param(debug, int, 0);
> +
> +module_init(poskbd_init);
> +module_exit(poskbd_exit);

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

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

  Powered by Linux