Input: evdev - implement proper locking
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
This commit is contained in:
@@ -30,6 +30,8 @@ struct evdev {
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -37,39 +39,53 @@ 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_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
|
||||
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. Note that we are
|
||||
* caleld under a spinlock with interrupts off so we don't need
|
||||
* to use rcu_read_lock() here. Writers will be using syncronize_sched()
|
||||
* instead of synchrnoize_rcu().
|
||||
*/
|
||||
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;
|
||||
|
||||
if (evdev->grab) {
|
||||
client = evdev->grab;
|
||||
do_gettimeofday(&event.time);
|
||||
event.type = type;
|
||||
event.code = code;
|
||||
event.value = value;
|
||||
|
||||
do_gettimeofday(&client->buffer[client->head].time);
|
||||
client->buffer[client->head].type = type;
|
||||
client->buffer[client->head].code = code;
|
||||
client->buffer[client->head].value = value;
|
||||
client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
|
||||
|
||||
kill_fasync(&client->fasync, SIGIO, POLL_IN);
|
||||
} else
|
||||
list_for_each_entry(client, &evdev->client_list, node) {
|
||||
|
||||
do_gettimeofday(&client->buffer[client->head].time);
|
||||
client->buffer[client->head].type = type;
|
||||
client->buffer[client->head].code = code;
|
||||
client->buffer[client->head].value = value;
|
||||
client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
|
||||
|
||||
kill_fasync(&client->fasync, SIGIO, POLL_IN);
|
||||
}
|
||||
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);
|
||||
|
||||
wake_up_interruptible(&evdev->wait);
|
||||
}
|
||||
@@ -88,38 +104,142 @@ 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)
|
||||
return -ENODEV;
|
||||
retval = -ENODEV;
|
||||
else
|
||||
retval = input_flush_device(&evdev->handle, file);
|
||||
|
||||
return 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);
|
||||
|
||||
evdev_table[evdev->minor] = NULL;
|
||||
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);
|
||||
/*
|
||||
* We don't use synchronize_rcu() here because read-side
|
||||
* critical section is protected by a spinlock instead
|
||||
* of rcu_read_lock().
|
||||
*/
|
||||
synchronize_sched();
|
||||
|
||||
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_sched();
|
||||
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_sched();
|
||||
}
|
||||
|
||||
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_sched();
|
||||
}
|
||||
|
||||
static int evdev_open_device(struct evdev *evdev)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = mutex_lock_interruptible(&evdev->mutex);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (!evdev->exist)
|
||||
retval = -ENODEV;
|
||||
else if (!evdev->open++)
|
||||
retval = input_open_device(&evdev->handle);
|
||||
|
||||
mutex_unlock(&evdev->mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void evdev_close_device(struct evdev *evdev)
|
||||
{
|
||||
mutex_lock(&evdev->mutex);
|
||||
|
||||
if (evdev->exist && !--evdev->open)
|
||||
input_close_device(&evdev->handle);
|
||||
|
||||
mutex_unlock(&evdev->mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Wake up users waiting for IO so they can disconnect from
|
||||
* dead device.
|
||||
*/
|
||||
static void evdev_hangup(struct evdev *evdev)
|
||||
{
|
||||
struct evdev_client *client;
|
||||
|
||||
spin_lock(&evdev->client_lock);
|
||||
list_for_each_entry(client, &evdev->client_list, node)
|
||||
kill_fasync(&client->fasync, SIGIO, POLL_HUP);
|
||||
spin_unlock(&evdev->client_lock);
|
||||
|
||||
wake_up_interruptible(&evdev->wait);
|
||||
}
|
||||
|
||||
static int evdev_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct evdev_client *client = file->private_data;
|
||||
struct evdev *evdev = client->evdev;
|
||||
|
||||
if (evdev->grab == client) {
|
||||
input_release_device(&evdev->handle);
|
||||
evdev->grab = NULL;
|
||||
}
|
||||
mutex_lock(&evdev->mutex);
|
||||
if (evdev->grab == client)
|
||||
evdev_ungrab(evdev, client);
|
||||
mutex_unlock(&evdev->mutex);
|
||||
|
||||
evdev_fasync(-1, file, 0);
|
||||
list_del(&client->node);
|
||||
evdev_detach_client(evdev, client);
|
||||
kfree(client);
|
||||
|
||||
if (!--evdev->open && evdev->exist)
|
||||
input_close_device(&evdev->handle);
|
||||
|
||||
evdev_close_device(evdev);
|
||||
put_device(&evdev->dev);
|
||||
|
||||
return 0;
|
||||
@@ -127,20 +247,24 @@ static int evdev_release(struct inode *inode, struct file *file)
|
||||
|
||||
static int evdev_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct evdev_client *client;
|
||||
struct evdev *evdev;
|
||||
struct evdev_client *client;
|
||||
int i = iminor(inode) - EVDEV_MINOR_BASE;
|
||||
int error;
|
||||
|
||||
if (i >= EVDEV_MINORS)
|
||||
return -ENODEV;
|
||||
|
||||
error = mutex_lock_interruptible(&evdev_table_mutex);
|
||||
if (error)
|
||||
return error;
|
||||
evdev = evdev_table[i];
|
||||
|
||||
if (!evdev || !evdev->exist)
|
||||
return -ENODEV;
|
||||
|
||||
if (evdev)
|
||||
get_device(&evdev->dev);
|
||||
mutex_unlock(&evdev_table_mutex);
|
||||
|
||||
if (!evdev)
|
||||
return -ENODEV;
|
||||
|
||||
client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
|
||||
if (!client) {
|
||||
@@ -148,20 +272,19 @@ static int evdev_open(struct inode *inode, struct file *file)
|
||||
goto err_put_evdev;
|
||||
}
|
||||
|
||||
spin_lock_init(&client->buffer_lock);
|
||||
client->evdev = evdev;
|
||||
list_add_tail(&client->node, &evdev->client_list);
|
||||
evdev_attach_client(evdev, client);
|
||||
|
||||
if (!evdev->open++ && evdev->exist) {
|
||||
error = input_open_device(&evdev->handle);
|
||||
error = evdev_open_device(evdev);
|
||||
if (error)
|
||||
goto err_free_client;
|
||||
}
|
||||
|
||||
file->private_data = client;
|
||||
return 0;
|
||||
|
||||
err_free_client:
|
||||
list_del(&client->node);
|
||||
evdev_detach_client(evdev, client);
|
||||
kfree(client);
|
||||
err_put_evdev:
|
||||
put_device(&evdev->dev);
|
||||
@@ -197,12 +320,14 @@ static inline size_t evdev_event_size(void)
|
||||
sizeof(struct input_event_compat) : sizeof(struct input_event);
|
||||
}
|
||||
|
||||
static int evdev_event_from_user(const char __user *buffer, struct input_event *event)
|
||||
static int evdev_event_from_user(const char __user *buffer,
|
||||
struct input_event *event)
|
||||
{
|
||||
if (COMPAT_TEST) {
|
||||
struct input_event_compat compat_event;
|
||||
|
||||
if (copy_from_user(&compat_event, buffer, sizeof(struct input_event_compat)))
|
||||
if (copy_from_user(&compat_event, buffer,
|
||||
sizeof(struct input_event_compat)))
|
||||
return -EFAULT;
|
||||
|
||||
event->time.tv_sec = compat_event.time.tv_sec;
|
||||
@@ -219,7 +344,8 @@ static int evdev_event_from_user(const char __user *buffer, struct input_event *
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int evdev_event_to_user(char __user *buffer, const struct input_event *event)
|
||||
static int evdev_event_to_user(char __user *buffer,
|
||||
const struct input_event *event)
|
||||
{
|
||||
if (COMPAT_TEST) {
|
||||
struct input_event_compat compat_event;
|
||||
@@ -230,7 +356,8 @@ static int evdev_event_to_user(char __user *buffer, const struct input_event *ev
|
||||
compat_event.code = event->code;
|
||||
compat_event.value = event->value;
|
||||
|
||||
if (copy_to_user(buffer, &compat_event, sizeof(struct input_event_compat)))
|
||||
if (copy_to_user(buffer, &compat_event,
|
||||
sizeof(struct input_event_compat)))
|
||||
return -EFAULT;
|
||||
|
||||
} else {
|
||||
@@ -248,7 +375,8 @@ static inline size_t evdev_event_size(void)
|
||||
return sizeof(struct input_event);
|
||||
}
|
||||
|
||||
static int evdev_event_from_user(const char __user *buffer, struct input_event *event)
|
||||
static int evdev_event_from_user(const char __user *buffer,
|
||||
struct input_event *event)
|
||||
{
|
||||
if (copy_from_user(event, buffer, sizeof(struct input_event)))
|
||||
return -EFAULT;
|
||||
@@ -256,7 +384,8 @@ static int evdev_event_from_user(const char __user *buffer, struct input_event *
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int evdev_event_to_user(char __user *buffer, const struct input_event *event)
|
||||
static int evdev_event_to_user(char __user *buffer,
|
||||
const struct input_event *event)
|
||||
{
|
||||
if (copy_to_user(buffer, event, sizeof(struct input_event)))
|
||||
return -EFAULT;
|
||||
@@ -266,37 +395,71 @@ static int evdev_event_to_user(char __user *buffer, const struct input_event *ev
|
||||
|
||||
#endif /* CONFIG_COMPAT */
|
||||
|
||||
static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
|
||||
static ssize_t evdev_write(struct file *file, const char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct evdev_client *client = file->private_data;
|
||||
struct evdev *evdev = client->evdev;
|
||||
struct input_event event;
|
||||
int retval = 0;
|
||||
int retval;
|
||||
|
||||
if (!evdev->exist)
|
||||
return -ENODEV;
|
||||
retval = mutex_lock_interruptible(&evdev->mutex);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
if (!evdev->exist) {
|
||||
retval = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (retval < count) {
|
||||
|
||||
if (evdev_event_from_user(buffer + retval, &event))
|
||||
return -EFAULT;
|
||||
input_inject_event(&evdev->handle, event.type, event.code, event.value);
|
||||
if (evdev_event_from_user(buffer + retval, &event)) {
|
||||
retval = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
input_inject_event(&evdev->handle,
|
||||
event.type, event.code, event.value);
|
||||
retval += evdev_event_size();
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&evdev->mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
|
||||
static int evdev_fetch_next_event(struct evdev_client *client,
|
||||
struct input_event *event)
|
||||
{
|
||||
int have_event;
|
||||
|
||||
spin_lock_irq(&client->buffer_lock);
|
||||
|
||||
have_event = client->head != client->tail;
|
||||
if (have_event) {
|
||||
*event = client->buffer[client->tail++];
|
||||
client->tail &= EVDEV_BUFFER_SIZE - 1;
|
||||
}
|
||||
|
||||
spin_unlock_irq(&client->buffer_lock);
|
||||
|
||||
return have_event;
|
||||
}
|
||||
|
||||
static ssize_t evdev_read(struct file *file, char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct evdev_client *client = file->private_data;
|
||||
struct evdev *evdev = client->evdev;
|
||||
struct input_event event;
|
||||
int retval;
|
||||
|
||||
if (count < evdev_event_size())
|
||||
return -EINVAL;
|
||||
|
||||
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
|
||||
if (client->head == client->tail && evdev->exist &&
|
||||
(file->f_flags & O_NONBLOCK))
|
||||
return -EAGAIN;
|
||||
|
||||
retval = wait_event_interruptible(evdev->wait,
|
||||
@@ -307,14 +470,12 @@ static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count,
|
||||
if (!evdev->exist)
|
||||
return -ENODEV;
|
||||
|
||||
while (client->head != client->tail && retval + evdev_event_size() <= count) {
|
||||
while (retval + evdev_event_size() <= count &&
|
||||
evdev_fetch_next_event(client, &event)) {
|
||||
|
||||
struct input_event *event = (struct input_event *) client->buffer + client->tail;
|
||||
|
||||
if (evdev_event_to_user(buffer + retval, event))
|
||||
if (evdev_event_to_user(buffer + retval, &event))
|
||||
return -EFAULT;
|
||||
|
||||
client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1);
|
||||
retval += evdev_event_size();
|
||||
}
|
||||
|
||||
@@ -409,7 +570,7 @@ static int str_to_user(const char *str, unsigned int maxlen, void __user *p)
|
||||
return copy_to_user(p, str, len) ? -EFAULT : len;
|
||||
}
|
||||
|
||||
static long evdev_ioctl_handler(struct file *file, unsigned int cmd,
|
||||
static long evdev_do_ioctl(struct file *file, unsigned int cmd,
|
||||
void __user *p, int compat_mode)
|
||||
{
|
||||
struct evdev_client *client = file->private_data;
|
||||
@@ -421,9 +582,6 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd,
|
||||
int i, t, u, v;
|
||||
int error;
|
||||
|
||||
if (!evdev->exist)
|
||||
return -ENODEV;
|
||||
|
||||
switch (cmd) {
|
||||
|
||||
case EVIOCGVERSION:
|
||||
@@ -490,26 +648,17 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd,
|
||||
return input_ff_erase(dev, (int)(unsigned long) p, file);
|
||||
|
||||
case EVIOCGEFFECTS:
|
||||
i = test_bit(EV_FF, dev->evbit) ? dev->ff->max_effects : 0;
|
||||
i = test_bit(EV_FF, dev->evbit) ?
|
||||
dev->ff->max_effects : 0;
|
||||
if (put_user(i, ip))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
|
||||
case EVIOCGRAB:
|
||||
if (p) {
|
||||
if (evdev->grab)
|
||||
return -EBUSY;
|
||||
if (input_grab_device(&evdev->handle))
|
||||
return -EBUSY;
|
||||
evdev->grab = client;
|
||||
return 0;
|
||||
} else {
|
||||
if (evdev->grab != client)
|
||||
return -EINVAL;
|
||||
input_release_device(&evdev->handle);
|
||||
evdev->grab = NULL;
|
||||
return 0;
|
||||
}
|
||||
if (p)
|
||||
return evdev_grab(evdev, client);
|
||||
else
|
||||
return evdev_ungrab(evdev, client);
|
||||
|
||||
default:
|
||||
|
||||
@@ -518,12 +667,13 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd,
|
||||
|
||||
if (_IOC_DIR(cmd) == _IOC_READ) {
|
||||
|
||||
if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0,0))) {
|
||||
if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0))) {
|
||||
|
||||
unsigned long *bits;
|
||||
int len;
|
||||
|
||||
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;
|
||||
@@ -587,15 +737,25 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd,
|
||||
|
||||
t = _IOC_NR(cmd) & ABS_MAX;
|
||||
|
||||
if (copy_from_user(&abs, p, sizeof(struct input_absinfo)))
|
||||
if (copy_from_user(&abs, p,
|
||||
sizeof(struct input_absinfo)))
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* Take event lock to ensure that we are not
|
||||
* changing device parameters in the middle
|
||||
* of event.
|
||||
*/
|
||||
spin_lock_irq(&dev->event_lock);
|
||||
|
||||
dev->abs[t] = abs.value;
|
||||
dev->absmin[t] = abs.minimum;
|
||||
dev->absmax[t] = abs.maximum;
|
||||
dev->absfuzz[t] = abs.fuzz;
|
||||
dev->absflat[t] = abs.flat;
|
||||
|
||||
spin_unlock_irq(&dev->event_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -603,13 +763,37 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static long evdev_ioctl_handler(struct file *file, unsigned int cmd,
|
||||
void __user *p, int compat_mode)
|
||||
{
|
||||
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;
|
||||
goto out;
|
||||
}
|
||||
|
||||
retval = evdev_do_ioctl(file, cmd, p, compat_mode);
|
||||
|
||||
out:
|
||||
mutex_unlock(&evdev->mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static long evdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
return evdev_ioctl_handler(file, cmd, (void __user *)arg, 0);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static long evdev_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
static long evdev_ioctl_compat(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
return evdev_ioctl_handler(file, cmd, compat_ptr(arg), 1);
|
||||
}
|
||||
@@ -630,6 +814,57 @@ static const struct file_operations evdev_fops = {
|
||||
.flush = evdev_flush
|
||||
};
|
||||
|
||||
static int evdev_install_chrdev(struct evdev *evdev)
|
||||
{
|
||||
/*
|
||||
* No need to do any locking here as calls to connect and
|
||||
* disconnect are serialized by the input core
|
||||
*/
|
||||
evdev_table[evdev->minor] = evdev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void evdev_remove_chrdev(struct evdev *evdev)
|
||||
{
|
||||
/*
|
||||
* Lock evdev table to prevent race with evdev_open()
|
||||
*/
|
||||
mutex_lock(&evdev_table_mutex);
|
||||
evdev_table[evdev->minor] = NULL;
|
||||
mutex_unlock(&evdev_table_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark device non-existent. This disables writes, ioctls and
|
||||
* prevents new users from opening the device. Already posted
|
||||
* blocking reads will stay, however new ones will fail.
|
||||
*/
|
||||
static void evdev_mark_dead(struct evdev *evdev)
|
||||
{
|
||||
mutex_lock(&evdev->mutex);
|
||||
evdev->exist = 0;
|
||||
mutex_unlock(&evdev->mutex);
|
||||
}
|
||||
|
||||
static void evdev_cleanup(struct evdev *evdev)
|
||||
{
|
||||
struct input_handle *handle = &evdev->handle;
|
||||
|
||||
evdev_mark_dead(evdev);
|
||||
evdev_hangup(evdev);
|
||||
evdev_remove_chrdev(evdev);
|
||||
|
||||
/* evdev is marked dead so no one else accesses evdev->open */
|
||||
if (evdev->open) {
|
||||
input_flush_device(handle, NULL);
|
||||
input_close_device(handle);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create new evdev device. Note that input core serializes calls
|
||||
* to connect and disconnect so we don't need to lock evdev_table here.
|
||||
*/
|
||||
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
|
||||
const struct input_device_id *id)
|
||||
{
|
||||
@@ -637,7 +872,10 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
|
||||
int minor;
|
||||
int error;
|
||||
|
||||
for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);
|
||||
for (minor = 0; minor < EVDEV_MINORS; minor++)
|
||||
if (!evdev_table[minor])
|
||||
break;
|
||||
|
||||
if (minor == EVDEV_MINORS) {
|
||||
printk(KERN_ERR "evdev: no more free evdev devices\n");
|
||||
return -ENFILE;
|
||||
@@ -648,38 +886,44 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&evdev->client_list);
|
||||
spin_lock_init(&evdev->client_lock);
|
||||
mutex_init(&evdev->mutex);
|
||||
init_waitqueue_head(&evdev->wait);
|
||||
|
||||
snprintf(evdev->name, sizeof(evdev->name), "event%d", minor);
|
||||
evdev->exist = 1;
|
||||
evdev->minor = minor;
|
||||
|
||||
evdev->handle.dev = dev;
|
||||
evdev->handle.name = evdev->name;
|
||||
evdev->handle.handler = handler;
|
||||
evdev->handle.private = evdev;
|
||||
snprintf(evdev->name, sizeof(evdev->name), "event%d", minor);
|
||||
|
||||
snprintf(evdev->dev.bus_id, sizeof(evdev->dev.bus_id),
|
||||
"event%d", minor);
|
||||
strlcpy(evdev->dev.bus_id, evdev->name, sizeof(evdev->dev.bus_id));
|
||||
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
|
||||
evdev->dev.class = &input_class;
|
||||
evdev->dev.parent = &dev->dev;
|
||||
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
|
||||
evdev->dev.release = evdev_free;
|
||||
device_initialize(&evdev->dev);
|
||||
|
||||
evdev_table[minor] = evdev;
|
||||
|
||||
error = device_add(&evdev->dev);
|
||||
error = input_register_handle(&evdev->handle);
|
||||
if (error)
|
||||
goto err_free_evdev;
|
||||
|
||||
error = input_register_handle(&evdev->handle);
|
||||
error = evdev_install_chrdev(evdev);
|
||||
if (error)
|
||||
goto err_delete_evdev;
|
||||
goto err_unregister_handle;
|
||||
|
||||
error = device_add(&evdev->dev);
|
||||
if (error)
|
||||
goto err_cleanup_evdev;
|
||||
|
||||
return 0;
|
||||
|
||||
err_delete_evdev:
|
||||
device_del(&evdev->dev);
|
||||
err_cleanup_evdev:
|
||||
evdev_cleanup(evdev);
|
||||
err_unregister_handle:
|
||||
input_unregister_handle(&evdev->handle);
|
||||
err_free_evdev:
|
||||
put_device(&evdev->dev);
|
||||
return error;
|
||||
@@ -688,21 +932,10 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
|
||||
static void evdev_disconnect(struct input_handle *handle)
|
||||
{
|
||||
struct evdev *evdev = handle->private;
|
||||
struct evdev_client *client;
|
||||
|
||||
input_unregister_handle(handle);
|
||||
device_del(&evdev->dev);
|
||||
|
||||
evdev->exist = 0;
|
||||
|
||||
if (evdev->open) {
|
||||
input_flush_device(handle, NULL);
|
||||
input_close_device(handle);
|
||||
list_for_each_entry(client, &evdev->client_list, node)
|
||||
kill_fasync(&client->fasync, SIGIO, POLL_HUP);
|
||||
wake_up_interruptible(&evdev->wait);
|
||||
}
|
||||
|
||||
evdev_cleanup(evdev);
|
||||
input_unregister_handle(handle);
|
||||
put_device(&evdev->dev);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user