linux驅動input子系統學習四(輸入事件驅動層)
阿新 • • 發佈:2021-01-20
輸入事件層目前系統幫我們區分了三種,
通用各種型別輸入類裝置的evdev,主流,也是將來大方向
mousedev,joydev。
我們學習就以evdev來學習,將來使用通用性也更高。
一、首先是框架性的呼叫輸入核心層實現的input_register_handler和input_unregister_handler來註冊evdev層。
static struct input_handler evdev_handler = { //事件驅動核心結構體 .event = evdev_event, //打包資料,並上報事件(傳送一個事件) .events = evdev_events, //傳送多個事件 .connect = evdev_connect, //找到匹配 dev 後進行連線 .disconnect = evdev_disconnect, //斷開連線時使用 .legacy_minors = true, .minor = EVDEV_MINOR_BASE, //次裝置號基數 64 .name = "evdev", //處理程式的名稱,在/proc/bus/input/handlers 中顯示 .id_table = evdev_ids, //匹配規則,evdev是所有都可以匹配 }; static int __init evdev_init(void) //註冊事件驅動 { return input_register_handler(&evdev_handler); } static void __exit evdev_exit(void) //解除安裝事件驅動 { input_unregister_handler(&evdev_handler); } module_init(evdev_init); module_exit(evdev_exit);
evdev有個重要的資料結構,當輸入裝置和事件驅動匹配的時候,就會在connect函式建立dvdev裝置,最後註冊成字元裝置開放到使用者空間。
struct evdev { // evdev 相當於一個橋樑,連結 input_dev 和 handler int open; //開啟引用計數 struct input_handle handle; //關聯的input_handle wait_queue_head_t wait; //等待佇列 struct evdev_client __rcu *grab; struct list_head client_list; //evdev_client 連結串列,說明一個evdev裝置可以處理多個evdev_client,可以有多個程序訪問 spinlock_t client_lock; /* protects client_list */ struct mutex mutex; struct device dev; //dev 是 evdev 的父裝置 struct cdev cdev; //cdev字元裝置 bool exist; //判斷此 evdev 釋放存在 };
二、介面實現
1.首先我們看一下connect函式,它在註冊handler和dev時,如果匹配上了就會呼叫。(具體可以檢視我的輸入子系統學習三)
/* * Create new evdev device. Note that input core serializes calls * to connect and disconnect. */ static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) //建立新的 evdev 裝置。輸入核心有序連線和斷開 { struct evdev *evdev; int minor; int dev_no; int error; minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true); //申請未使用的minor if (minor < 0) { error = minor; pr_err("failed to reserve new minor: %d\n", error); return error; } evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //申請 sizeof(struct evdev) 的空間 if (!evdev) { error = -ENOMEM; goto err_free_minor; } INIT_LIST_HEAD(&evdev->client_list); //初始化 client_list 連結串列 spin_lock_init(&evdev->client_lock); //初始化 client_lock 自旋鎖 mutex_init(&evdev->mutex); //初始化 evdev 的互斥量 init_waitqueue_head(&evdev->wait); //初始化 evdev 的等待佇列 evdev->exist = true; //此 evdev 為存在 dev_no = minor; //申請到的次裝置號 /* Normalize device number if it falls into legacy range */ if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS) dev_no -= EVDEV_MINOR_BASE; //判斷此 evdev 裝置申請的是 32 箇中的哪一個 dev_set_name(&evdev->dev, "event%d", dev_no); //設定此 evdev->dev 的名字 evdev->handle.dev = input_get_device(dev); //增加 evdev->dev 的引用計數 evdev->handle.name = dev_name(&evdev->dev); //設定名字 event0,1,2,3.... evdev->handle.handler = handler; //指向匹配到的 handler evdev->handle.private = evdev; //指向自己 evdev evdev->dev.devt = MKDEV(INPUT_MAJOR, minor); //由輸入裝置的主裝置號13和此裝置號合成一個裝置號 evdev->dev.class = &input_class; //設定 evdev 的類為 input_class evdev->dev.parent = &dev->dev; //輸入裝置 input_dev 是 evdev 是的父裝置 evdev->dev.release = evdev_free; //釋放 evdev 資源 device_initialize(&evdev->dev); //內部 device 初始化 error = input_register_handle(&evdev->handle); //註冊 handle if (error) goto err_free_evdev; cdev_init(&evdev->cdev, &evdev_fops); //用 evdev_fops 初始化一個 cdev 字元裝置 evdev->cdev.kobj.parent = &evdev->dev.kobj; error = cdev_add(&evdev->cdev, evdev->dev.devt, 1); //新增一個字元裝置到系統中 if (error) goto err_unregister_handle; error = device_add(&evdev->dev); //真正的建立裝置層次和attribute在sysfs中 if (error) goto err_cleanup_evdev; return 0; err_cleanup_evdev: evdev_cleanup(evdev); err_unregister_handle: input_unregister_handle(&evdev->handle); err_free_evdev: put_device(&evdev->dev); err_free_minor: input_free_minor(minor); return error; }
2.disconnect,和connect相反
static void evdev_disconnect(struct input_handle *handle)
{
struct evdev *evdev = handle->private; //handle 的私有資料就是 handle 本身 evdev
device_del(&evdev->dev);
evdev_cleanup(evdev);
input_free_minor(MINOR(evdev->dev.devt));
input_unregister_handle(handle);
put_device(&evdev->dev);
}
3.對一個裝置驅動層傳送過來的事件打包
/*
* Pass incoming events to all connected clients. 傳遞來到的事件給所有連線的客戶端
*/
static void evdev_events(struct input_handle *handle,
const struct input_value *vals, unsigned int count)
{
struct evdev *evdev = handle->private; //此處的私有資料就是 evdev 本身
struct evdev_client *client;
ktime_t ev_time[EV_CLK_MAX];
ev_time[EV_CLK_MONO] = ktime_get(); //設定對應的時間
ev_time[EV_CLK_REAL] = ktime_mono_to_real(ev_time[EV_CLK_MONO]);
ev_time[EV_CLK_BOOT] = ktime_mono_to_any(ev_time[EV_CLK_MONO],
TK_OFFS_BOOT);
rcu_read_lock();
client = rcu_dereference(evdev->grab);
if (client) //如果該evdev有個專用的client,那麼就將事件發給它如果該evdev
evdev_pass_values(client, vals, count, ev_time);
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_values(client, vals, count, ev_time); //否則,發給 evdev 客戶連結串列上的所有客戶
rcu_read_unlock();
}
/*
* Pass incoming event to all connected clients.
*/
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value) //傳遞輸入進來的事件給所有的客戶
{
struct input_value vals[] = { { type, code, value } };
evdev_events(handle, vals, 1);
}
傳送資料給client函式
static void __pass_event(struct evdev_client *client,
const struct input_event *event) //使用非同步通知函式通知上層
{
client->buffer[client->head++] = *event; //把 *event 放在 struct evdev_client 的緩衝區中
client->head &= client->bufsize - 1;
if (unlikely(client->head == client->tail)) {
/*
* This effectively "drops" all unconsumed events, leaving
* EV_SYN/SYN_DROPPED plus the newest event in the queue.
*/
client->tail = (client->head - 2) & (client->bufsize - 1);
client->buffer[client->tail].time = event->time;
client->buffer[client->tail].type = EV_SYN;
client->buffer[client->tail].code = SYN_DROPPED;
client->buffer[client->tail].value = 0;
client->packet_head = client->tail;
}
if (event->type == EV_SYN && event->code == SYN_REPORT) { //事件的型別是同步型別,事件的編碼是隻上報一次
client->packet_head = client->head; //下一次從緩衝區的第一個位置取資料
kill_fasync(&client->fasync, SIGIO, POLL_IN); //傳送一個非同步通知,通知該開啟該client的應用程式,執行訊號處理函式
}
}
static void evdev_pass_values(struct evdev_client *client,
const struct input_value *vals, unsigned int count,
ktime_t *ev_time) //把事件發給 evdev_client,喚醒等待佇列
{
struct evdev *evdev = client->evdev; //獲取此客戶端要訪問的 evdev 裝置
const struct input_value *v;
struct input_event event;
bool wakeup = false; //預設不喚醒
if (client->revoked) //如果保留了,直接返回
return;
event.time = ktime_to_timeval(ev_time[client->clk_type]); //根據客戶端的時間型別,獲取對應的時間
/* Interrupts are disabled, just acquire the lock. */ //中斷被關閉,獲取鎖
spin_lock(&client->buffer_lock);
for (v = vals; v != vals + count; v++) { //迴圈傳送
event.type = v->type;
event.code = v->code;
event.value = v->value;
__pass_event(client, &event); //使用非同步通知函式通知上層
if (v->type == EV_SYN && v->code == SYN_REPORT) //事件的型別是同步型別,事件的編碼是隻上報一次,此時要喚醒等待佇列
wakeup = true;
}
spin_unlock(&client->buffer_lock);
if (wakeup)
wake_up_interruptible(&evdev->wait); //喚醒 evdev 的等待佇列
}
4.應用層的呼叫介面file_operation
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,
};
操作函式比較多,我們主要分析連個最常用的,open,和read
當我們使用open開啟時,會建立evdev_client 和 ecdev字元裝置建立聯絡,通過evdev的handle找到對應的device,開啟此裝置。
static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev); //由 inode->i_cdev 成員獲取 evdev 裝置
unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
unsigned int size = sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event);
struct evdev_client *client;
int error;
client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN); //申請 size 大小的記憶體
if (!client)
client = vzalloc(size);
if (!client)
return -ENOMEM;
client->bufsize = bufsize; //設定事件緩衝區的大小
spin_lock_init(&client->buffer_lock);
client->evdev = evdev; //此次開啟字元裝置所使用的 evdev
evdev_attach_client(evdev, client); //把此 dvdev 附著在 evdev_client
error = evdev_open_device(evdev); //開啟此 evdev 字元裝置
if (error)
goto err_free_client;
file->private_data = client; //file 的私有資料設定為 evdev_client
nonseekable_open(inode, file);
return 0;
err_free_client:
evdev_detach_client(evdev, client);
kvfree(client);
return error;
}
int input_open_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
int retval;
retval = mutex_lock_interruptible(&dev->mutex);
if (retval)
return retval;
if (dev->going_away) {
retval = -ENODEV;
goto out;
}
handle->open++; //增加引用計數
if (!dev->users++ && dev->open)
retval = dev->open(dev); //.open = input_proc_devices_open, 開啟輸入裝置
if (retval) {
dev->users--;
if (!--handle->open) {
/*
* Make sure we are not delivering any more events
* through this handle
*/
synchronize_rcu();
}
}
out:
mutex_unlock(&dev->mutex);
return retval;
}
對應應用層的read函式
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;
size_t read = 0;
int error;
if (count != 0 && count < input_event_size())
return -EINVAL;
for (;;) {
if (!evdev->exist || client->revoked)
return -ENODEV;
/* 當client緩衝區無資料;evdev不存在;檔案非阻塞開啟,那個read直接返回錯誤 */
if (client->packet_head == client->tail &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
/*
* count == 0 is special - no IO is done but we check
* for error conditions (see above).
*/
if (count == 0)
break;
/* 當要讀取的資料大於struct input_event且client裡面的buffer有資料,則把資料拷貝到使用者空間*/
while (read + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
if (input_event_to_user(buffer + read, &event)) /* 對copy_to_user的封裝 */
return -EFAULT;
read += input_event_size();
}
if (read)
break;
if (!(file->f_flags & O_NONBLOCK)) { //以阻塞的方式等待事件的發生
error = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail ||
!evdev->exist || client->revoked);
if (error)
return error;
}
}
return read;
}