Raw access to serio ports (1/2)
Hi,
Below is an implementation of rawdev driver. The driver will bind to serio ports with type SERIO_8042_RAW and provides raw byte-oriented access to the underlying serio device. I tried to keep behavior as close to 2.4 as possible so multiple readers/writers are allowed and they will fight with each other over access.
The driver will attempt to register char device 10,1 (/dev/psaux) and if that fails will request dynamic minor. So if mousedev psaux support is turned off and I expect only onle device requiring "special" handling to be present at a time everything should work pretty much the same as in 2.4
The driver will happily co-exist with psmouse and atkbd loaded as they ignore SERIO_8042_RAW ports, so it is possible to have one AUX port in raw mode and other in standard 2.6 mode.
The patch to mark some of i8042 AUX ports as raw is in the next message.
Comments?
Signature · 460 lines
Dmitry P.S. The patch is against Vojtech's tree so there is some nice if appllied to Linus' tree but there should be no rejects. =================================================================== [email protected], 2004-06-02 01:57:12-05:00, [email protected] Input: Add rawdev driver that binds to serio ports marked as 'raw' and provides unobstructed access to the underlying serio port via a char device. The driver tries to register char device 10,1 (/dev/psaux) first and if it fails goes for dynamically allocated minor. Signed-off-by: Dmitry Torokhov <[email protected]> drivers/input/misc/Kconfig | 12 + drivers/input/misc/Makefile | 1 drivers/input/misc/rawdev.c | 388 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/serio.h | 1 4 files changed, 402 insertions(+) =================================================================== diff -Nru a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig --- a/drivers/input/misc/Kconfig 2004-06-02 02:15:20 -05:00 +++ b/drivers/input/misc/Kconfig 2004-06-02 02:15:20 -05:00 @@ -54,3 +54,15 @@ To compile this driver as a module, choose M here: the module will be called uinput. +config INPUT_RAWDEV + tristate "Raw access to serio ports" + depends on INPUT && INPUT_MISC + help + Say Y here if you want to have raw access to serio ports, such as + AUX ports on i8042 keyboard controller. Each serio port that is + marked as providing raw access will be accessible via a char device + with major 10 and dynamically allocated minor (it will first try + allocating minor 1 historically corrsponding to /dev/psaux). + + To compile this driver as a module, choose M here: the + module will be called rawdev. diff -Nru a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile --- a/drivers/input/misc/Makefile 2004-06-02 02:15:20 -05:00 +++ b/drivers/input/misc/Makefile 2004-06-02 02:15:20 -05:00 @@ -9,3 +9,4 @@ obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o obj-$(CONFIG_INPUT_98SPKR) += 98spkr.o obj-$(CONFIG_INPUT_UINPUT) += uinput.o +obj-$(CONFIG_INPUT_RAWDEV) += rawdev.o diff -Nru a/drivers/input/misc/rawdev.c b/drivers/input/misc/rawdev.c --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/input/misc/rawdev.c 2004-06-02 02:15:20 -05:00 @@ -0,0 +1,388 @@ +/* + * Raw serio device providing access to a raw byte stream from underlying + * serio port. Closely emulates behavior of pre-2.6 /dev/psaux device + * + * Copyright (c) 2004 Dmitry Torokhov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/major.h> +#include <linux/device.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/miscdevice.h> +#include <linux/wait.h> + +MODULE_AUTHOR("Dmitry Torokhov <[email protected]>"); +MODULE_DESCRIPTION("Raw serio driver"); +MODULE_LICENSE("GPL"); + +#define RAWDEV_QUEUE_LEN 64 +struct rawdev { + unsigned char queue[RAWDEV_QUEUE_LEN]; + unsigned int tail, head; + spinlock_t lock; + + char name[16]; + unsigned int refcnt; + struct serio *serio; + struct miscdevice dev; + wait_queue_head_t wait; + struct list_head list; + struct list_head node; +}; + +struct rawdev_list { + struct fasync_struct *fasync; + struct rawdev *rawdev; + struct list_head node; +}; + +static DECLARE_MUTEX(rawdev_sem); +static LIST_HEAD(rawdev_list); +static unsigned int rawdev_no; + +/********************************************************************* + * Interface with userspace (file operations) * + *********************************************************************/ + +static int rawdev_fasync(int fd, struct file *file, int on) +{ + struct rawdev_list *list = file->private_data; + int retval; + + retval = fasync_helper(fd, file, on, &list->fasync); + return retval < 0 ? retval : 0; +} + +static struct rawdev *rawdev_locate(int minor) +{ + struct rawdev *rawdev; + + list_for_each_entry(rawdev, &rawdev_list, node) { + if (rawdev->dev.minor == minor) + return rawdev; + } + + return NULL; +} + +static int rawdev_open(struct inode *inode, struct file *file) +{ + struct rawdev *rawdev; + struct rawdev_list *list; + int retval = 0; + + retval = down_interruptible(&rawdev_sem); + if (retval) + return retval; + + if (!(rawdev = rawdev_locate(iminor(inode)))) { + retval = -ENODEV; + goto out; + } + + if (!rawdev->serio) { + retval = -ENODEV; + goto out; + } + + if (!(list = kmalloc(sizeof(struct rawdev_list), GFP_KERNEL))) { + retval = -ENOMEM; + goto out; + } + + memset(list, 0, sizeof(struct rawdev_list)); + list->rawdev = rawdev; + file->private_data = list; + + rawdev->refcnt++; + list_add_tail(&list->node, &rawdev->list); + +out: + up(&rawdev_sem); + return retval; +} + +static int rawdev_cleanup(struct rawdev *rawdev) +{ + if (--rawdev->refcnt == 0) { + misc_deregister(&rawdev->dev); + list_del_init(&rawdev->node); + kfree(rawdev); + + return 1; + } + + return 0; +} + +static int rawdev_release(struct inode *inode, struct file *file) +{ + struct rawdev_list *list = file->private_data; + struct rawdev *rawdev = list->rawdev; + + down(&rawdev_sem); + + rawdev_fasync(-1, file, 0); + rawdev_cleanup(rawdev); + + up(&rawdev_sem); + return 0; +} + +static int rawdev_fetch_byte(struct rawdev *rawdev, char *c) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&rawdev->lock, flags); + + empty = rawdev->head == rawdev->tail; + if (!empty) { + *c = rawdev->queue[rawdev->tail]; + rawdev->tail = (rawdev->tail + 1) % RAWDEV_QUEUE_LEN; + } + + spin_unlock_irqrestore(&rawdev->lock, flags); + + return !empty; +} + +static ssize_t rawdev_read(struct file *file, char *buffer, size_t count, loff_t *ppos) +{ + struct rawdev_list *list = file->private_data; + struct rawdev *rawdev = list->rawdev; + char c; + ssize_t retval = 0; + + if (!rawdev->serio) + return -ENODEV; + + if (rawdev->head == rawdev->tail && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(list->rawdev->wait, + rawdev->head != rawdev->tail || !rawdev->serio); + if (retval) + return retval; + + if (!rawdev->serio) + return -ENODEV; + + while (retval < count && rawdev_fetch_byte(rawdev, &c)) { + if (put_user(c, buffer++)) + return -EFAULT; + retval++; + } + + return retval; +} + +static ssize_t rawdev_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) +{ + struct rawdev_list *list = file->private_data; + ssize_t written = 0; + int retval; + unsigned char c; + + retval = down_interruptible(&rawdev_sem); + if (retval) + return retval; + + if (!list->rawdev->serio) { + retval = -ENODEV; + goto out; + } + + if (count > 32) + count = 32; + + while (count--) { + if (get_user(c, buffer++)) { + retval = -EFAULT; + goto out; + } + if (serio_write(list->rawdev->serio, c)) { + retval = -EIO; + goto out; + } + written++; + }; + +out: + up(&rawdev_sem); + return written; +} + +static unsigned int rawdev_poll(struct file *file, poll_table *wait) +{ + struct rawdev_list *list = file->private_data; + + poll_wait(file, &list->rawdev->wait, wait); + + if (list->rawdev->head != list->rawdev->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +struct file_operations rawdev_fops = { + .owner = THIS_MODULE, + .open = rawdev_open, + .release = rawdev_release, + .read = rawdev_read, + .write = rawdev_write, + .poll = rawdev_poll, + .fasync = rawdev_fasync, +}; + + +/********************************************************************* + * Interface with serio port * + *********************************************************************/ + +static irqreturn_t rawdev_interrupt(struct serio *serio, unsigned char data, + unsigned int dfl, struct pt_regs *regs) +{ + struct rawdev *rawdev = serio->private; + struct rawdev_list *list; + unsigned int head = rawdev->head; + unsigned long flags; + + spin_lock_irqsave(&rawdev->lock, flags); + + rawdev->queue[head] = data; + head = (head + 1) % RAWDEV_QUEUE_LEN; + if (likely(head != rawdev->tail)) { + rawdev->head = head; + list_for_each_entry(list, &rawdev->list, node) + kill_fasync(&list->fasync, SIGIO, POLL_IN); + wake_up_interruptible(&rawdev->wait); + } + + spin_unlock_irqrestore(&rawdev->lock, flags); + + return IRQ_HANDLED; +} + +static void rawdev_connect(struct serio *serio, struct serio_dev *dev) +{ + struct rawdev *rawdev; + int err; + + if ((serio->type & SERIO_TYPE) != SERIO_8042_RAW) + return; + + if (!(rawdev = kmalloc(sizeof(struct rawdev), GFP_KERNEL))) { + printk(KERN_ERR "rawdev.c: can't allocate memory for a device\n"); + return; + } + + down(&rawdev_sem); + + memset(rawdev, 0, sizeof(struct rawdev)); + snprintf(rawdev->name, sizeof(rawdev->name), "serio_raw%d", rawdev_no++); + rawdev->refcnt = 1; + rawdev->serio = serio; + INIT_LIST_HEAD(&rawdev->list); + init_waitqueue_head(&rawdev->wait); + + serio->private = rawdev; + if (serio_open(serio, dev)) + goto out_free; + + list_add_tail(&rawdev->node, &rawdev_list); + + rawdev->dev.minor = PSMOUSE_MINOR; + rawdev->dev.name = rawdev->name; + rawdev->dev.fops = &rawdev_fops; + + err = misc_register(&rawdev->dev); + if (err) { + rawdev->dev.minor = MISC_DYNAMIC_MINOR; + err = misc_register(&rawdev->dev); + } + + if (err) { + printk(KERN_INFO "rawdev: failed to register raw access device for %s\n", + serio->phys); + goto out_close; + } + + printk(KERN_INFO "rawdev: raw access enabled on %s (%s, minor %d)\n", + serio->phys, rawdev->name, rawdev->dev.minor); + goto out; + +out_close: + serio_close(serio); + list_del_init(&rawdev->node); +out_free: + serio->private = NULL; + kfree(rawdev); +out: + up(&rawdev_sem); +} + +static int rawdev_reconnect(struct serio *serio) +{ + struct rawdev *rawdev = serio->private; + struct serio_dev *dev = serio->dev; + + if (!dev || !rawdev) { + printk(KERN_DEBUG "rawdev: reconnect request, but serio is disconnected, ignoring...\n"); + return -1; + } + + /* + * Nothing needs to be done here, we just need this method to + * keep the same device. + */ + return 0; +} + +static void rawdev_disconnect(struct serio *serio) +{ + struct rawdev *rawdev; + + down(&rawdev_sem); + + rawdev = serio->private; + + serio_close(serio); + serio->private = NULL; + + rawdev->serio = NULL; + if (!rawdev_cleanup(rawdev)) + wake_up_interruptible(&rawdev->wait); + + up(&rawdev_sem); +} + +static struct serio_dev rawdev_dev = { + .interrupt = rawdev_interrupt, + .connect = rawdev_connect, + .reconnect = rawdev_reconnect, + .disconnect = rawdev_disconnect, +}; + +int __init rawdev_init(void) +{ + serio_register_device(&rawdev_dev); + return 0; +} + +void __exit rawdev_exit(void) +{ + serio_unregister_device(&rawdev_dev); +} + +module_init(rawdev_init); +module_exit(rawdev_exit); diff -Nru a/include/linux/serio.h b/include/linux/serio.h --- a/include/linux/serio.h 2004-06-02 02:15:20 -05:00 +++ b/include/linux/serio.h 2004-06-02 02:15:20 -05:00 @@ -108,6 +108,7 @@ #define SERIO_PC9800 0x04000000UL #define SERIO_PS_PSTHRU 0x05000000UL #define SERIO_8042_XL 0x06000000UL +#define SERIO_8042_RAW 0x07000000UL #define SERIO_PROTO 0xFFUL #define SERIO_MSC 0x01
Quoted 4 lines · Dmitry Torokhov
Hi,
Below is an implementation of rawdev driver.
Yes, it does appear that we need the feature, thanks.
Quoted 1 line
Comments?
They're the thingies inside /* and */. Your patch is refreshingly free of them ;)
Quoted 5 lines
(quoted diff) +5
-0+static ssize_t rawdev_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) +{ + struct rawdev_list *list = file->private_data; + ssize_t written = 0; + int retval;
Review
Quoted 10 lines
(quoted diff) +10
-0+ unsigned char c; + + retval = down_interruptible(&rawdev_sem); + if (retval) + return retval; + + if (!list->rawdev->serio) { + retval = -ENODEV; + goto out; + }
The return values here are mucked up - this function returns `written'.
Review
Quoted 2 lines
(quoted diff) +2
-0+ if (count > 32) + count = 32;
Why? (Don't tell me - add a comment!)
Quoted 22 lines · Andrew Morton
Quoted 4 lines · Dmitry Torokhov
Hi,
Below is an implementation of rawdev driver.
Yes, it does appear that we need the feature, thanks.
Quoted 1 line
Comments?
They're the thingies inside /* and */. Your patch is refreshingly free of them ;)
Quoted 9 lines
(quoted diff) +9
-0+static ssize_t rawdev_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) +{ + struct rawdev_list *list = file->private_data; + ssize_t written = 0; + int retval; + unsigned char c; + + retval = down_interruptible(&rawdev_sem); + if (retval)
Quoted 8 lines
Review
Quoted 6 lines
(quoted diff) +6
-0+ return retval; + + if (!list->rawdev->serio) { + retval = -ENODEV; + goto out; + }The return values here are mucked up - this function returns `written'.
Or an error code in negative notation - it's the standard practice for read/write. Am I missing something?
Quoted 5 lines
Review
Quoted 3 lines
(quoted diff) +2
-0+ if (count > 32) + count = 32;Why? (Don't tell me - add a comment!)
I have no idea - taken from 2.4... My best guess would be it's an attempt not to hog the device... I'll sprinkle some comments ;)
Signature · 1 line
Dmitry
Quoted 5 lines · Dmitry Torokhov
Quoted 2 lines
The return values here are mucked up - this function returns `written'.
Or an error code in negative notation - it's the standard practice for read/write. Am I missing something?
You want the function to return -ENODEV if !list->rawdev->serio
if (!list->rawdev->serio) {
retval = -ENODEV;
goto out;
}
...
out:
up(&rawdev_sem);
return written;
}But it won't.
Quoted 32 lines · Dmitry Torokhov
Quoted 22 lines · Andrew Morton
Quoted 4 lines · Dmitry Torokhov
Hi,
Below is an implementation of rawdev driver.
Yes, it does appear that we need the feature, thanks.
Quoted 1 line
Comments?
They're the thingies inside /* and */. Your patch is refreshingly free of them ;)
Quoted 9 lines
(quoted diff) +9
-0+static ssize_t rawdev_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) +{ + struct rawdev_list *list = file->private_data; + ssize_t written = 0; + int retval; + unsigned char c; + + retval = down_interruptible(&rawdev_sem); + if (retval)Quoted 8 lines
Review
Quoted 6 lines
(quoted diff) +6
-0+ return retval; + + if (!list->rawdev->serio) { + retval = -ENODEV; + goto out; + }The return values here are mucked up - this function returns `written'.
Quoted 3 lines
Or an error code in negative notation - it's the standard practice for read/write. Am I missing something?
Oh, yes, I am... Sorry, will fix tomorrow.
Signature · 1 line
Dmitry
Ok, new version:
- fixed return value logic in rawdev_write,
- rawdev_read and rawdev_write arguments marked with __user,
- some comments sprinkled around ;)
===================================================================
[email protected], 2004-06-02 22:31:17-05:00, [email protected]
Input: Add rawdev driver that binds to serio ports marked as 'raw'
and provides unobstructed access to the underlying serio port via a char device. The driver tries to register char device 10,1 (/dev/psaux) first and if it fails goes for dynamically allocated minor.
drivers/input/misc/Kconfig | 12 + drivers/input/misc/Makefile | 1 drivers/input/misc/rawdev.c | 422 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/serio.h | 1 4 files changed, 436 insertions(+)
===================================================================
drivers/input/misc/Kconfig +12 -0
@@ -54,3 +54,15 @@ To compile this driver as a module, choose M here: the module will be called uinput. +config INPUT_RAWDEV + tristate "Raw access to serio ports" + depends on INPUT && INPUT_MISC + help + Say Y here if you want to have raw access to serio ports, such as + AUX ports on i8042 keyboard controller. Each serio port that is + marked as providing raw access will be accessible via a char device + with major 10 and dynamically allocated minor (it will first try + allocating minor 1 historically corresponding to /dev/psaux). + + To compile this driver as a module, choose M here: the + module will be called rawdev.
drivers/input/misc/Makefile +1 -0
@@ -9,3 +9,4 @@ obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o obj-$(CONFIG_INPUT_98SPKR) += 98spkr.o obj-$(CONFIG_INPUT_UINPUT) += uinput.o +obj-$(CONFIG_INPUT_RAWDEV) += rawdev.o
drivers/input/misc/rawdev.c new file +422 -0
422 lines not on this page · raw message
include/linux/serio.h +1 -0
@@ -108,6 +108,7 @@#define SERIO_PC9800 0x04000000UL#define SERIO_PS_PSTHRU 0x05000000UL#define SERIO_8042_XL 0x06000000UL +#define SERIO_8042_RAW 0x07000000UL#define SERIO_PROTO 0xFFUL#define SERIO_MSC 0x01
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
"Dmitry" == Dmitry Torokhov <[email protected]> writes:
Dmitry> +config INPUT_RAWDEV
Dmitry> + tristate "Raw access to serio ports"
Dmitry> + depends on INPUT && INPUT_MISC
Dmitry> + help
Since this provides raw access to serio ports ONLY, shouldn't it also depend on INPUT_SERIO? Shall we better call it SERIO_RAW?
Signature · 4 lines
Sau Dan LEE 李守敦(Big5) ~{@nJX6X~}(HZ)
E-mail: [email protected]
Home page: http://www.informatik.uni-freiburg.de/~danleeQuoted 9 lines · Sau Dan Lee
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
"Dmitry" == Dmitry Torokhov <[email protected]> writes:
Dmitry> +config
INPUT_RAWDEV
Dmitry> + tristate "Raw access to serio ports"
Dmitry> + depends on INPUT &&INPUT_MISC
Dmitry> + helpSince this provides raw access to serio ports ONLY, shouldn't it also depend on
INPUT_SERIO?
Yes, you are right, will change.
Quoted 2 lines
Shall we better call it
SERIO_RAW?
I could probably rename it to serio_rawdev... It's close to 2AM here so the next version is due tomorrow night ;)
Signature · 1 line
Dmitry
Re: [RFC/RFT] Raw access to serio ports (1/2) - take 3
In reply to Sau Dan LeeOk, 3rd attempt... As Sau Dan Lee suggested renamed form rawdev to serio_raw and adjsuted dependencies. Since it does not really need input subsystem, just serio, moved to drivers/input/serio.
Signature · 459 lines
Dmitry =================================================================== [email protected], 2004-06-05 00:47:44-05:00, [email protected] Input: Add serio_raw driver that binds to serio ports marked as 'raw' and provides unobstructed access to the underlying serio port via a char device. The driver tries to register char device 10,1 (/dev/psaux) first and if it fails goes for dynamically allocated minor. Signed-off-by: Dmitry Torokhov <[email protected]> drivers/input/serio/Kconfig | 14 + drivers/input/serio/Makefile | 1 drivers/input/serio/serio_raw.c | 388 ++++++++++++++++++++++++++++++++++++++++ include/linux/serio.h | 1 4 files changed, 404 insertions(+) =================================================================== diff -Nru a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig --- a/drivers/input/serio/Kconfig 2004-06-05 02:05:24 -05:00 +++ b/drivers/input/serio/Kconfig 2004-06-05 02:05:24 -05:00 @@ -140,3 +140,17 @@ To compile this driver as a module, choose M here: the module will be called maceps2. + +config SERIO_RAW + tristate "Raw access to serio ports" + depends on SERIO + help + Say Y here if you want to have raw access to serio ports, such as + AUX ports on i8042 keyboard controller. Each serio port that is + marked as providing raw access will be accessible via a char device + with major 10 and dynamically allocated minor. The driver will try + allocating minor 1 (that historically corresponds to /dev/psaux) + first. + + To compile this driver as a module, choose M here: the + module will be called serio_raw. diff -Nru a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile --- a/drivers/input/serio/Makefile 2004-06-05 02:05:24 -05:00 +++ b/drivers/input/serio/Makefile 2004-06-05 02:05:24 -05:00 @@ -17,3 +17,4 @@ obj-$(CONFIG_SERIO_GSCPS2) += gscps2.o obj-$(CONFIG_SERIO_PCIPS2) += pcips2.o obj-$(CONFIG_SERIO_MACEPS2) += maceps2.o +obj-$(CONFIG_SERIO_RAW) += serio_raw.o diff -Nru a/drivers/input/serio/serio_raw.c b/drivers/input/serio/serio_raw.c --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/input/serio/serio_raw.c 2004-06-05 02:05:24 -05:00 @@ -0,0 +1,388 @@ +/* + * Raw serio device providing access to a raw byte stream from underlying + * serio port. Closely emulates behavior of pre-2.6 /dev/psaux device + * + * Copyright (c) 2004 Dmitry Torokhov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/major.h> +#include <linux/device.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/miscdevice.h> +#include <linux/wait.h> + +MODULE_AUTHOR("Dmitry Torokhov <[email protected]>"); +MODULE_DESCRIPTION("Raw serio driver"); +MODULE_LICENSE("GPL"); + +#define SERIO_RAW_QUEUE_LEN 64 +struct serio_raw { + unsigned char queue[SERIO_RAW_QUEUE_LEN]; + unsigned int tail, head; + spinlock_t lock; + + char name[16]; + unsigned int refcnt; + struct serio *serio; + struct miscdevice dev; + wait_queue_head_t wait; + struct list_head list; + struct list_head node; +}; + +struct serio_raw_list { + struct fasync_struct *fasync; + struct serio_raw *serio_raw; + struct list_head node; +}; + +static DECLARE_MUTEX(serio_raw_sem); +static LIST_HEAD(serio_raw_list); +static unsigned int serio_raw_no; + +/********************************************************************* + * Interface with userspace (file operations) * + *********************************************************************/ + +static int serio_raw_fasync(int fd, struct file *file, int on) +{ + struct serio_raw_list *list = file->private_data; + int retval; + + retval = fasync_helper(fd, file, on, &list->fasync); + return retval < 0 ? retval : 0; +} + +static struct serio_raw *serio_raw_locate(int minor) +{ + struct serio_raw *serio_raw; + + list_for_each_entry(serio_raw, &serio_raw_list, node) { + if (serio_raw->dev.minor == minor) + return serio_raw; + } + + return NULL; +} + +static int serio_raw_open(struct inode *inode, struct file *file) +{ + struct serio_raw *serio_raw; + struct serio_raw_list *list; + int retval = 0; + + retval = down_interruptible(&serio_raw_sem); + if (retval) + return retval; + + if (!(serio_raw = serio_raw_locate(iminor(inode)))) { + retval = -ENODEV; + goto out; + } + + if (!serio_raw->serio) { + retval = -ENODEV; + goto out; + } + + if (!(list = kmalloc(sizeof(struct serio_raw_list), GFP_KERNEL))) { + retval = -ENOMEM; + goto out; + } + + memset(list, 0, sizeof(struct serio_raw_list)); + list->serio_raw = serio_raw; + file->private_data = list; + + serio_raw->refcnt++; + list_add_tail(&list->node, &serio_raw->list); + +out: + up(&serio_raw_sem); + return retval; +} + +static int serio_raw_cleanup(struct serio_raw *serio_raw) +{ + if (--serio_raw->refcnt == 0) { + misc_deregister(&serio_raw->dev); + list_del_init(&serio_raw->node); + kfree(serio_raw); + + return 1; + } + + return 0; +} + +static int serio_raw_release(struct inode *inode, struct file *file) +{ + struct serio_raw_list *list = file->private_data; + struct serio_raw *serio_raw = list->serio_raw; + + down(&serio_raw_sem); + + serio_raw_fasync(-1, file, 0); + serio_raw_cleanup(serio_raw); + + up(&serio_raw_sem); + return 0; +} + +static int serio_raw_fetch_byte(struct serio_raw *serio_raw, char *c) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&serio_raw->lock, flags); + + empty = serio_raw->head == serio_raw->tail; + if (!empty) { + *c = serio_raw->queue[serio_raw->tail]; + serio_raw->tail = (serio_raw->tail + 1) % SERIO_RAW_QUEUE_LEN; + } + + spin_unlock_irqrestore(&serio_raw->lock, flags); + + return !empty; +} + +static ssize_t serio_raw_read(struct file *file, char *buffer, size_t count, loff_t *ppos) +{ + struct serio_raw_list *list = file->private_data; + struct serio_raw *serio_raw = list->serio_raw; + char c; + ssize_t retval = 0; + + if (!serio_raw->serio) + return -ENODEV; + + if (serio_raw->head == serio_raw->tail && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(list->serio_raw->wait, + serio_raw->head != serio_raw->tail || !serio_raw->serio); + if (retval) + return retval; + + if (!serio_raw->serio) + return -ENODEV; + + while (retval < count && serio_raw_fetch_byte(serio_raw, &c)) { + if (put_user(c, buffer++)) + return -EFAULT; + retval++; + } + + return retval; +} + +static ssize_t serio_raw_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) +{ + struct serio_raw_list *list = file->private_data; + ssize_t written = 0; + int retval; + unsigned char c; + + retval = down_interruptible(&serio_raw_sem); + if (retval) + return retval; + + if (!list->serio_raw->serio) { + retval = -ENODEV; + goto out; + } + + if (count > 32) + count = 32; + + while (count--) { + if (get_user(c, buffer++)) { + retval = -EFAULT; + goto out; + } + if (serio_write(list->serio_raw->serio, c)) { + retval = -EIO; + goto out; + } + written++; + }; + +out: + up(&serio_raw_sem); + return written; +} + +static unsigned int serio_raw_poll(struct file *file, poll_table *wait) +{ + struct serio_raw_list *list = file->private_data; + + poll_wait(file, &list->serio_raw->wait, wait); + + if (list->serio_raw->head != list->serio_raw->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +struct file_operations serio_raw_fops = { + .owner = THIS_MODULE, + .open = serio_raw_open, + .release = serio_raw_release, + .read = serio_raw_read, + .write = serio_raw_write, + .poll = serio_raw_poll, + .fasync = serio_raw_fasync, +}; + + +/********************************************************************* + * Interface with serio port * + *********************************************************************/ + +static irqreturn_t serio_raw_interrupt(struct serio *serio, unsigned char data, + unsigned int dfl, struct pt_regs *regs) +{ + struct serio_raw *serio_raw = serio->private; + struct serio_raw_list *list; + unsigned int head = serio_raw->head; + unsigned long flags; + + spin_lock_irqsave(&serio_raw->lock, flags); + + serio_raw->queue[head] = data; + head = (head + 1) % SERIO_RAW_QUEUE_LEN; + if (likely(head != serio_raw->tail)) { + serio_raw->head = head; + list_for_each_entry(list, &serio_raw->list, node) + kill_fasync(&list->fasync, SIGIO, POLL_IN); + wake_up_interruptible(&serio_raw->wait); + } + + spin_unlock_irqrestore(&serio_raw->lock, flags); + + return IRQ_HANDLED; +} + +static void serio_raw_connect(struct serio *serio, struct serio_dev *dev) +{ + struct serio_raw *serio_raw; + int err; + + if ((serio->type & SERIO_TYPE) != SERIO_8042_RAW) + return; + + if (!(serio_raw = kmalloc(sizeof(struct serio_raw), GFP_KERNEL))) { + printk(KERN_ERR "serio_raw.c: can't allocate memory for a device\n"); + return; + } + + down(&serio_raw_sem); + + memset(serio_raw, 0, sizeof(struct serio_raw)); + snprintf(serio_raw->name, sizeof(serio_raw->name), "serio_raw%d", serio_raw_no++); + serio_raw->refcnt = 1; + serio_raw->serio = serio; + INIT_LIST_HEAD(&serio_raw->list); + init_waitqueue_head(&serio_raw->wait); + + serio->private = serio_raw; + if (serio_open(serio, dev)) + goto out_free; + + list_add_tail(&serio_raw->node, &serio_raw_list); + + serio_raw->dev.minor = PSMOUSE_MINOR; + serio_raw->dev.name = serio_raw->name; + serio_raw->dev.fops = &serio_raw_fops; + + err = misc_register(&serio_raw->dev); + if (err) { + serio_raw->dev.minor = MISC_DYNAMIC_MINOR; + err = misc_register(&serio_raw->dev); + } + + if (err) { + printk(KERN_INFO "serio_raw: failed to register raw access device for %s\n", + serio->phys); + goto out_close; + } + + printk(KERN_INFO "serio_raw: raw access enabled on %s (%s, minor %d)\n", + serio->phys, serio_raw->name, serio_raw->dev.minor); + goto out; + +out_close: + serio_close(serio); + list_del_init(&serio_raw->node); +out_free: + serio->private = NULL; + kfree(serio_raw); +out: + up(&serio_raw_sem); +} + +static int serio_raw_reconnect(struct serio *serio) +{ + struct serio_raw *serio_raw = serio->private; + struct serio_dev *dev = serio->dev; + + if (!dev || !serio_raw) { + printk(KERN_DEBUG "serio_raw: reconnect request, but serio is disconnected, ignoring...\n"); + return -1; + } + + /* + * Nothing needs to be done here, we just need this method to + * keep the same device. + */ + return 0; +} + +static void serio_raw_disconnect(struct serio *serio) +{ + struct serio_raw *serio_raw; + + down(&serio_raw_sem); + + serio_raw = serio->private; + + serio_close(serio); + serio->private = NULL; + + serio_raw->serio = NULL; + if (!serio_raw_cleanup(serio_raw)) + wake_up_interruptible(&serio_raw->wait); + + up(&serio_raw_sem); +} + +static struct serio_dev serio_raw_dev = { + .interrupt = serio_raw_interrupt, + .connect = serio_raw_connect, + .reconnect = serio_raw_reconnect, + .disconnect = serio_raw_disconnect, +}; + +int __init serio_raw_init(void) +{ + serio_register_device(&serio_raw_dev); + return 0; +} + +void __exit serio_raw_exit(void) +{ + serio_unregister_device(&serio_raw_dev); +} + +module_init(serio_raw_init); +module_exit(serio_raw_exit); diff -Nru a/include/linux/serio.h b/include/linux/serio.h --- a/include/linux/serio.h 2004-06-05 02:05:24 -05:00 +++ b/include/linux/serio.h 2004-06-05 02:05:24 -05:00 @@ -111,6 +111,7 @@ #define SERIO_PC9800 0x04000000UL #define SERIO_PS_PSTHRU 0x05000000UL #define SERIO_8042_XL 0x06000000UL +#define SERIO_8042_RAW 0x07000000UL #define SERIO_PROTO 0xFFUL #define SERIO_MSC 0x01