diff options
Diffstat (limited to 'drivers/usb/evdev.c')
-rw-r--r-- | drivers/usb/evdev.c | 122 |
1 files changed, 89 insertions, 33 deletions
diff --git a/drivers/usb/evdev.c b/drivers/usb/evdev.c index 9cca9fdf6..1d42f7df0 100644 --- a/drivers/usb/evdev.c +++ b/drivers/usb/evdev.c @@ -29,9 +29,9 @@ */ #define EVDEV_MINOR_BASE 64 +#define EVDEV_MINORS 32 #define EVDEV_BUFFER_SIZE 64 -#include <linux/miscdevice.h> #include <linux/poll.h> #include <linux/malloc.h> #include <linux/module.h> @@ -39,11 +39,12 @@ #include <linux/input.h> struct evdev { - char name[32]; int used; + int open; + int minor; struct input_handle handle; - struct miscdevice misc; wait_queue_head_t wait; + devfs_handle_t devfs; struct evdev_list *list; }; @@ -56,8 +57,7 @@ struct evdev_list { struct evdev_list *next; }; -static unsigned long evdev_miscbits = 0; -static struct evdev *evdev_base[BITS_PER_LONG]; +static struct evdev *evdev_table[EVDEV_MINORS] = { NULL, /* ... */ }; static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { @@ -99,10 +99,13 @@ static int evdev_release(struct inode * inode, struct file * file) while (*listptr && (*listptr != list)) listptr = &((*listptr)->next); *listptr = (*listptr)->next; + + if (!--list->evdev->open) + input_close_device(&list->evdev->handle); if (!--list->evdev->used) { - clear_bit(list->evdev->misc.minor - EVDEV_MINOR_BASE, &evdev_miscbits); - misc_deregister(&list->evdev->misc); + input_unregister_minor(list->evdev->devfs); + evdev_table[list->evdev->minor] = NULL; kfree(list->evdev); } @@ -117,23 +120,28 @@ static int evdev_open(struct inode * inode, struct file * file) struct evdev_list *list; int i = MINOR(inode->i_rdev) - EVDEV_MINOR_BASE; - if (i > BITS_PER_LONG || !test_bit(i, &evdev_miscbits)) + if (i > EVDEV_MINORS || !evdev_table[i]) return -ENODEV; - if (!(list = kmalloc(sizeof(struct evdev_list), GFP_KERNEL))) - return -ENOMEM; + MOD_INC_USE_COUNT; + if (!(list = kmalloc(sizeof(struct evdev_list), GFP_KERNEL))) { + MOD_DEC_USE_COUNT; + return -ENOMEM; + } memset(list, 0, sizeof(struct evdev_list)); - list->evdev = evdev_base[i]; - list->next = evdev_base[i]->list; - evdev_base[i]->list = list; + list->evdev = evdev_table[i]; + list->next = evdev_table[i]->list; + evdev_table[i]->list = list; file->private_data = list; list->evdev->used++; - MOD_INC_USE_COUNT; + if (!list->evdev->open++) + input_open_device(&list->evdev->handle); + return 0; } @@ -193,34 +201,81 @@ static unsigned int evdev_poll(struct file *file, poll_table *wait) return 0; } +static int evdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct evdev_list *list = file->private_data; + struct evdev *evdev = list->evdev; + struct input_dev *dev = evdev->handle.dev; + + switch (cmd) { + + case EVIOCGVERSION: + return put_user(EV_VERSION, (__u32 *) arg); + case EVIOCGID: + return copy_to_user(&dev->id, (void *) arg, + sizeof(struct input_id)) ? -EFAULT : 0; + default: + + if (_IOC_TYPE(cmd) != 'E' || _IOC_DIR(cmd) != _IOC_READ) + return -EINVAL; + + if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0,0))) { + + long *bits = NULL; + int len = 0; + + switch (_IOC_NR(cmd) & EV_MAX) { + case 0: bits = dev->evbit; len = EV_MAX; break; + case EV_KEY: bits = dev->keybit; len = KEY_MAX; break; + case EV_REL: bits = dev->relbit; len = REL_MAX; break; + case EV_ABS: bits = dev->absbit; len = ABS_MAX; break; + case EV_LED: bits = dev->ledbit; len = LED_MAX; break; + case EV_SND: bits = dev->sndbit; len = SND_MAX; break; + default: return -EINVAL; + } + len = NBITS(len) * sizeof(long); + if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); + return copy_to_user((void *) arg, bits, len) ? -EFAULT : len; + } + + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGNAME(0))) { + int len = strlen(dev->name) + 1; + if (len > _IOC_SIZE(cmd)) len = _IOC_SIZE(cmd); + return copy_to_user((char *) arg, dev->name, len) ? -EFAULT : len; + } + } + return -EINVAL; +} + static struct file_operations evdev_fops = { read: evdev_read, write: evdev_write, poll: evdev_poll, open: evdev_open, release: evdev_release, + ioctl: evdev_ioctl, fasync: evdev_fasync, }; -static int evdev_connect(struct input_handler *handler, struct input_dev *dev) +static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev) { struct evdev *evdev; + int minor; - if (!(evdev = kmalloc(sizeof(struct evdev), GFP_KERNEL))) - return -1; + for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); + if (evdev_table[minor]) { + printk(KERN_ERR "evdev: no more free evdev devices\n"); + return NULL; + } + if (!(evdev = kmalloc(sizeof(struct evdev), GFP_KERNEL))) + return NULL; memset(evdev, 0, sizeof(struct evdev)); init_waitqueue_head(&evdev->wait); - evdev->misc.minor = ffz(evdev_miscbits); - set_bit(evdev->misc.minor, &evdev_miscbits); - evdev_base[evdev->misc.minor] = evdev; - - sprintf(evdev->name, "evdev%d", evdev->misc.minor); - evdev->misc.name = evdev->name; - evdev->misc.minor += EVDEV_MINOR_BASE; - evdev->misc.fops = &evdev_fops; + evdev->minor = minor; + evdev_table[minor] = evdev; evdev->handle.dev = dev; evdev->handle.handler = handler; @@ -228,24 +283,23 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev) evdev->used = 1; - misc_register(&evdev->misc); - input_open_device(&evdev->handle); + evdev->devfs = input_register_minor("event%d", minor, EVDEV_MINOR_BASE); - printk("%s: Event device for input%d on misc%d - /dev/input%d\n", - evdev->name, dev->number, evdev->misc.minor, evdev->misc.minor - EVDEV_MINOR_BASE); + printk("event%d: Event device for input%d\n", minor, dev->number); - return 0; + return &evdev->handle; } static void evdev_disconnect(struct input_handle *handle) { struct evdev *evdev = handle->private; - input_close_device(handle); + if (evdev->open) + input_close_device(handle); if (!--evdev->used) { - clear_bit(evdev->misc.minor - EVDEV_MINOR_BASE, &evdev_miscbits); - misc_deregister(&evdev->misc); + input_unregister_minor(evdev->devfs); + evdev_table[evdev->minor] = NULL; kfree(evdev); } } @@ -254,6 +308,8 @@ static struct input_handler evdev_handler = { event: evdev_event, connect: evdev_connect, disconnect: evdev_disconnect, + fops: &evdev_fops, + minor: EVDEV_MINOR_BASE, }; static int __init evdev_init(void) |