/*
* Event char devices, giving access to raw input device events.
*
* Copyright (c) 1999-2002 Vojtech Pavlik
*
* 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.
*/
#define EVDEV_MINOR_BASE 64
#define EVDEV_MINORS 32
#define EVDEV_BUFFER_SIZE 64
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/major.h>
#include <linux/device.h>
#include "input-compat.h"
struct evdev {
int exist;
int open;
int minor;
char name[16];
struct input_handle handle;
wait_queue_head_t wait;
struct evdev_client *grab;
struct list_head client_list;
spinlock_t client_lock; /* protects client_list */
struct mutex mutex;
struct device dev;
};
struct evdev_client {
struct input_event buffer[EVDEV_BUFFER_SIZE];
int head;
int tail;
spinlock_t buffer_lock; /* protects access to buffer, head and tail */
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
};
static struct evdev *evdev_table[EVDEV_MINORS];
static DEFINE_MUTEX(evdev_table_mutex);
static void evdev_pass_event(struct evdev_client *client,
struct input_event *event)
{
/*
* Interrupts are disabled, just acquire the lock
*/
spin_lock(&client->buffer_lock);
client->buffer[client->head++] = *event;
client->head &= EVDEV_BUFFER_SIZE - 1;
spin_unlock(&client->buffer_lock);
kill_fasync(&client->fasync, SIGIO, POLL_IN);
}
/*
* Pass incoming event to all connected clients.
*/
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
struct input_event event;
do_gettimeofday(&event.time);
event.type = type;
event.code = code;
event.value = value;
rcu_read_lock();
client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_event(client, &event);
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_event(client, &event);
rcu_read_unlock();
wake_up_interruptible(&evdev->wait);
}
static int evdev_fasync(int fd, struct file *file, int on)
{
struct evdev_client *client = file->private_data;
int retval;
retval = fasync_helper(fd, file, on, &client->fasync);
return retval < 0 ? retval : 0;
}
static int evdev_flush(struct file *file, fl_owner_t id)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
int retval;
retval = mutex_lock_interruptible(&evdev->mutex);
if (retval)
return retval;
if (!evdev->exist)
retval = -ENODEV;
else
retval = input_flush_device(&evdev->handle, file);
mutex_unlock(&evdev->mutex);
return retval;
}
static void evdev_free(struct device *dev)
{
struct evdev *evdev = container_of(dev, struct evdev, dev);
input_put_device(evdev->handle.dev);
kfree(evdev);
}
/*
* Grabs an event device (along with underlying input device).
* This function is called with evdev->mutex taken.
*/
static int evdev_grab(struct evdev *evdev, struct evdev_client *client)
{
int error;
if (evdev->grab)
return -EBUSY;
error = input_grab_device(&evdev->handle);
if (error)
return error;
rcu_assign_pointer(evdev->grab, client);
synchronize_rcu();
return 0;
}
static int evdev_ungrab(struct evdev *evdev, struct evdev_client *client)
{
if (evdev->grab != client)
return -EINVAL;
rcu_assign_pointer(evdev->grab, NULL);
synchronize_rcu();
input_release_device(&evdev->handle);
return 0;
}
static void evdev_attach_client(struct evdev *evdev,
struct evdev_client *client)
{
spin_lock(&evdev->client_lock);
list_add_tail_rcu(&client->node, &evdev->client_list);
spin_unlock(&evdev->client_lock);
synchronize_rcu();
}
static void evdev_detach_client(struct evdev *evdev,
struct evdev_client *client)
{
spin_lock(&evdev->client_lock);
list_del_rcu(&client->node);
spin_unlock(&evdev->client_lock);
synchronize_rcu();
}
static int evdev_open_device(struct evdev *evdev)
{