輸入子系統——框架分析
- 硬件平臺:韋東山嵌入式Linxu開發板(S3C2440.v3)
- 軟件平臺:運行於VMware Workstation 12 Player下UbuntuLTS16.04_x64 系統
- 參考資料:《嵌入式Linux應用開發手冊》、《嵌入式Linux應用開發手冊第2版》
- 開發環境:Linux 2.6.22.6 內核、arm-linux-gcc-3.4.5-glibc-2.3.6工具鏈
目錄
輸入子系統——框架分析 - 一、前言
- 二、框架分析
- 1、入口函數`input_init()`
- 2、`file_operation`結構體
- 3、`input_open_file`函數
- 4、誰負責存儲input_table[]這個數組的項——`input_register_handler()`函數
- 5、誰使用這個`input_register_handler()`函數——`evdev.c`
- 6、大致框架
- 7、`input_register_device()`函數
- 8、誰使用這個`input_register_device()`函數——具體的設備文件
- 9、大致框架2
- 10、`input_attach_handler()`函數
- 11、如何建立連接——分析驅動文件`evdev.c`
- 12、大致框架3
- 三、實際APP的read函數
一、前言
對於之前寫驅動的時候,我們都是在APP文件中打開了一個特定的設備文件如/dev/buttons
,但是一般在實際應用過程中不會打開/dev/buttons
。一般是直接scanf()
就去獲得了按鍵的輸入。
以前寫的那些驅動程序有一下缺點:
- 不通用,只能自已使用而非通用。
- 耦合性不高,無法讓其他應用程序“無縫”的使用。
解決這個問題的答案就需要:使用現成的驅動——輸入子系統(input 子系統),把自已的設備相關的驅動放到內核中這種驅動架構中去。
在使用之前,我們需要分析輸入子系統(input 子系統)的大致框架。
二、框架分析
對於輸入子系統,其主要文件在/driver/input.c
1、入口函數input_init()
分析:在這個入口函數中,通過register_chrdev(INPUT_MAJOR, "input", &input_fops);
註冊了一個設備。
/* 入口函數
3. 註冊了一個設備
*/
static int __init input_init(void)
{
/*........*/
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
/*........*/
}
2、file_operation
結構體
分析:只有open函數,沒有其他的函數,肯定在open函數中進行了其他的操作。
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
3、input_open_file
函數
分析:在input_open_file()
函數中
- 根據
inode
的此設備號找到對應的句柄hander
- 把該
hander->fops
賦值給新建的file_operations
結構體new_fops
- 保存
file->f_op
,並將其賦值爲new_fops
- 調用
new_fops->open()
函數
可以看出這個函數屬於被調用函數,而且是通過一個設備的次設備號作爲索引找到對應的句柄。
static int input_open_file(struct inode *inode, struct file *file)
{
/* 根據此設備號,在input_table[]數組裏面找到對應的input_handler句柄 */
struct input_handler *handler = input_table[iminor(inode) >> 5];
const struct file_operations *old_fops, *new_fops = NULL;
int err;
/* 通過上面得到的input_handler句柄找到對應的file_operation的fops結構體,並賦值給新建的new_fops */
if (!handler || !(new_fops = fops_get(handler->fops)))
return -ENODEV;
/* 判斷new_fops結構體的open函數是否爲空 */
if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
}
/* 把打開文件的file_operation結構體賦值給old_fops */
old_fops = file->f_op;
/* 把新得到的new_fops賦值給打開文件的fops: 起中轉作用 */
file->f_op = new_fops;
/* 根據這個new_fops結構體調用open函數 */
err = new_fops->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
return err;
}
4、誰負責存儲input_table[]這個數組的項——input_register_handler()
函數
input_table[]
原型
static struct input_handler *input_table[8];
input_register_handler()
函數
分析:
- 句柄的的次設備號作爲索引,存儲在對應的
input_table[ ]
項中 - 把該句柄的
node
放入鏈表 - 對於每一個
input_dev
,都調用input_attach_handler()
,根據input_handler的id_table
判斷能否支持這個input_dev
/* 註冊handle函數 */
int input_register_handler(struct input_handler *handler)
{
/*.....*/
/* 判斷是否爲空,則進行放入數組 */
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5])
return -EBUSY;
input_table[handler->minor >> 5] = handler;
}
// 放入鏈表
list_add_tail(&handler->node, &input_handler_list);
// 對於每個input_dev,調用input_attach_handler
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler); // 根據input_handler的id_table判斷能否支持這個input_dev
/*.....*/
return 0;
}
5、誰使用這個input_register_handler()
函數——evdev.c
通過搜索可以查詢到如下文件會調用input_register_handler()
,拿去其中的evdev.c
驅動文件進行說明。
5.1 入口函數evdev_init()
在這個入口函數只調用了input_register_handler()
,並且看見有evdev_handler
這個變量。
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
5.2 input_handler
類型的結構體evdev_handler
對於這個結構體中的函數,我們下面會進行分析。
static struct input_handler evdev_handler = {
.event = evdev_event, //事件
.connect = evdev_connect, //聯繫
.disconnect = evdev_disconnect, //不聯繫
.fops = &evdev_fops, //file_operation結構體
.minor = EVDEV_MINOR_BASE, //次設備號
.name = "evdev", //設備名字
.id_table = evdev_ids, //用來綁定設備和驅動
};
6、大致框架
到目前爲止,我們可以分析出大致的框架,目前分析的是在純軟件中,那麼對於實際的硬件呢?下面就來分析下在input.c文件中的input_register_device()
函數。
7、input_register_device()
函數
分析:
- 把該設備的
node
放入鏈表 - 對於每一個
input_handler
,都調用input_attach_handler()
,根據input_handler的id_table
判斷能否支持這個input_dev
int input_register_device(struct input_dev *dev)
{
/*......*/
// 放入鏈表
list_add_tail(&dev->node, &input_dev_list);
// 對於每一個input_handler,都調用input_attach_handler
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); // 根據input_handler的id_table判斷能否支持這個input_dev
input_wakeup_procfs_readers();
return 0;
}
8、誰使用這個input_register_device()
函數——具體的設備文件
通過搜索可以查詢到如下文件會調用input_register_device()
,對於一個驅動文件可以支持多個設備文件,而input_register_device()
函數就是把這些設備註冊進驅動文件中。
9、大致框架2
10、input_attach_handler()
函數
分析:可以發現到input_register_device()
和input_register_handle()
的處理過程很相似,而且都調用了input_attach_handler()
函數。
input_attach_handler()
函數
分析:
- 進行匹配: 註冊
input_dev
或input_handler
時,會兩兩比較左邊的input_dev和右邊的input_handler - 根據
input_handler
的id_table判斷這個input_handler能否支持這個input_dev - 如果能支持,則調用
input_handler的connect函數
建立"連接"
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
/* 進行匹配:註冊input_dev或input_handler時,會兩兩比較左邊的input_dev和右邊的input_handler */
if (handler->blacklist && input_match_device(handler->blacklist, dev))
return -ENODEV;
/* 根據input_handler的id_table判斷這個input_handler能否支持這個input_dev */
id = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV;
/* 如果能支持,則調用input_handler的connect函數建立"連接" */
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
printk(KERN_ERR
"input: failed to attach handler %s to device %s, "
"error: %d\n",
handler->name, kobject_name(&dev->cdev.kobj), error);
return error;
}
11、如何建立連接——分析驅動文件evdev.c
對於不同的驅動,建立連接的方式都不一樣,這裏分析驅動文件evdev.c
的evdev_connect()
函數
分析:
- 分配一個
input_handle結構體
- 設置
input_handle.dev = input_dev; // 指向左邊的input_dev
input_handle.handler = input_handler; // 指向右邊的input_handler - 註冊:調用
input_register_handle()
函數
input_handler->h_list = &input_handle;
inpu_dev->h_list = &input_handle;
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
//放入鏈表尾部
list_add_tail(&handle->d_node, &handle->dev->h_list); //dev->h_list
list_add_tail(&handle->h_node, &handler->h_list); //handler->h_list
if (handler->start)
handler->start(handle);
return 0;
}
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
/*...........*/
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一個input_handle
/*...........*/
// 設置
evdev->handle.dev = dev; // 指向左邊的input_dev
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; // 指向右邊的input_handler
evdev->handle.private = evdev;
sprintf(evdev->name, "event%d", minor);
//放入到構建的數組中
evdev_table[minor] = evdev;
/*......*/
// 註冊
error = input_register_handle(&evdev->handle);
/*......*/
}(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
/*...........*/
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一個input_handle
/*...........*/
// 設置
evdev->handle.dev = dev; // 指向左邊的input_dev
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; // 指向右邊的input_handler
evdev->handle.private = evdev;
sprintf(evdev->name, "event%d", minor);
//放入到構建的數組中
evdev_table[minor] = evdev;
/*......*/
// 註冊
error = input_register_handle(&evdev->handle);
/*......*/
}
12、大致框架3
此時對於整個輸入子系統,它的大致框架如下:
- 分上下兩層:對於核心層:將純軟件(input_handler)部分和硬件部分( input_dev)聯繫起來。
- 在下層中,純軟部分通過
input_register_handler()
向上註冊處理方式——驅動文件, 硬件部分是input_register_device()
向上註冊硬件層——即具體的設備文件。 - 二者註冊之後,會兩兩進行比較,查看其中的
handler
是否支持dev
- 若支持,則調用
input_handler()
結構體中的.connect
函數進行連接。 - 連接的方式:{
①、分配一個input_handle結構體
②、設置
input_handle.dev = input_dev; // 指向左邊的input_dev
input_handle.handler = input_handler; // 指向右邊的input_handler
③、註冊:調用input_register_handle()
函數
input_handler->h_list = &input_handle;
inpu_dev->h_list = &input_handle;
} - 連接之後,可用通過兩邊的結構體中的
.h_list
鏈表中的任一一邊找到對方。
三、實際APP的read函數
也是引用evdec.c
文件來說明
對於APP中調用read()
函數,最終會調用到設備的evdev_read()
函數
1、evdev_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;
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);
/*........*/
return retval;
}
2、evdev_event()
事件函數
在這個函數進行對休眠函數進行激活
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
/*.............*/
wake_up_interruptible(&evdev->wait); //激活
}
3、誰調用evdev_event()
事件函數
猜:應該是硬件相關的代碼,input_dev那層調用的
在設備的中斷服務程序裏,確定事件是什麼,然後調用相應的input_handler的event處理函數
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
/*.............*/
if (irq == gpio_to_irq(gpio)) {
unsigned int type = button->type ?: EV_KEY;
int state = (gpio_get_value(gpio) ? 1 : 0) ^ button->active_low;
// 上報事件
input_event(input, type, button->code, !!state);
input_sync(input);
}
}
return IRQ_HANDLED;
}
4、input_event()
函數
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
/*.....*/
if (dev->grab)
dev->grab->handler->event(dev->grab, type, code, value);
else
list_for_each_entry(handle, &dev->h_list, d_node)
/* 如果handle打開,則調用該handle->handler->event */
if (handle->open)
handle->handler->event(handle, type, code, value);
}
5、過程總結
- 應用程序讀,最終會導致 handler 中的
read
函數。 - 沒有數據可讀時就休眠,有休眠就會喚醒,搜索的結果是
even
函數來喚醒。 - 猜測是
input_dev
層的設備中斷服務程序調用了event
函數。 - 通過這個
event函數
可以最終追蹤到純軟件部分的input_handler結構體
中的.event
成員。