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); + + +/* 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; + +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) { + dbg("a hw_port has already been opened...\n"); + return ret; + } + + ret = serio_open(port, drv); + + if (!ret) { + poskbd_status->hw_port = port; + } + + 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 + */ +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); + data->scancode = scancode; + list_add_tail(&(data->list), &poskbd_status->data->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) +{ + 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)); + 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); + 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; + } + cnt++; + + /* remove the scancode from the buffer */ + list_del(i); + 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) +{ + size_t i; + unsigned char c; + int ret = 0; + + if (!poskbd_status->hw_port) { + warning("no device connected\n"); + return -ENODEV; + } + + for (i = 0; i < len; i++) { + if (copy_from_user(&c, data + i, 1)) { + warning("could not get data from userspace\n"); + return -EACCES; + } + + 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; + } + 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