evdev.c
来自「linux 内核源代码」· C语言 代码 · 共 974 行 · 第 1/2 页
C
974 行
/* * 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 <linux/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); 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){ 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); if (retval) evdev->open--; } 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; mutex_lock(&evdev->mutex); if (evdev->grab == client) evdev_ungrab(evdev, client); mutex_unlock(&evdev->mutex); evdev_fasync(-1, file, 0); evdev_detach_client(evdev, client); kfree(client); evdev_close_device(evdev); put_device(&evdev->dev); return 0;}static int evdev_open(struct inode *inode, struct file *file){ 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) get_device(&evdev->dev); mutex_unlock(&evdev_table_mutex); if (!evdev) return -ENODEV; client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL); if (!client) { error = -ENOMEM; goto err_put_evdev; } spin_lock_init(&client->buffer_lock); client->evdev = evdev; evdev_attach_client(evdev, client); error = evdev_open_device(evdev); if (error) goto err_free_client; file->private_data = client; return 0; err_free_client: evdev_detach_client(evdev, client); kfree(client); err_put_evdev: put_device(&evdev->dev); return error;}#ifdef CONFIG_COMPATstruct input_event_compat { struct compat_timeval time; __u16 type; __u16 code; __s32 value;};/* Note to the author of this code: did it ever occur to you why the ifdefs are needed? Think about it again. -AK */#ifdef CONFIG_X86_64# define COMPAT_TEST is_compat_task()#elif defined(CONFIG_IA64)# define COMPAT_TEST IS_IA32_PROCESS(task_pt_regs(current))#elif defined(CONFIG_S390)# define COMPAT_TEST test_thread_flag(TIF_31BIT)#elif defined(CONFIG_MIPS)# define COMPAT_TEST test_thread_flag(TIF_32BIT_ADDR)#else# define COMPAT_TEST test_thread_flag(TIF_32BIT)#endifstatic inline size_t evdev_event_size(void){ return COMPAT_TEST ? sizeof(struct input_event_compat) : sizeof(struct input_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))) return -EFAULT; event->time.tv_sec = compat_event.time.tv_sec; event->time.tv_usec = compat_event.time.tv_usec; event->type = compat_event.type; event->code = compat_event.code; event->value = compat_event.value; } else { if (copy_from_user(event, buffer, sizeof(struct input_event))) return -EFAULT; } return 0;}static int evdev_event_to_user(char __user *buffer, const struct input_event *event){ if (COMPAT_TEST) { struct input_event_compat compat_event; compat_event.time.tv_sec = event->time.tv_sec; compat_event.time.tv_usec = event->time.tv_usec; compat_event.type = event->type; compat_event.code = event->code; compat_event.value = event->value; if (copy_to_user(buffer, &compat_event, sizeof(struct input_event_compat))) return -EFAULT; } else { if (copy_to_user(buffer, event, sizeof(struct input_event))) return -EFAULT; } return 0;}#elsestatic 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){ if (copy_from_user(event, buffer, sizeof(struct input_event))) return -EFAULT; return 0;}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; return 0;}#endif /* CONFIG_COMPAT */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; 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)) { 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 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)) return -EAGAIN; retval = wait_event_interruptible(evdev->wait, client->head != client->tail || !evdev->exist); if (retval) return retval; if (!evdev->exist) return -ENODEV; while (retval + evdev_event_size() <= count && evdev_fetch_next_event(client, &event)) { if (evdev_event_to_user(buffer + retval, &event)) return -EFAULT; retval += evdev_event_size(); } return retval;}/* No kernel lock - fine */static unsigned int evdev_poll(struct file *file, poll_table *wait){ struct evdev_client *client = file->private_data;
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?