Linux之輸入子系統分析
在此節之前,我們學的都是簡單的字元驅動,涉及的內容有字元驅動的框架、自動建立裝置節點、linux中斷、poll機制、非同步通知、同步互斥/非阻塞、定時器去抖動。
其中驅動框架如下:
1)寫file_operations結構體的成員函式: .open()、.read()、.write()
2)在入口函式裡通過register_chrdev()建立驅動名,生成主裝置號,賦入file_operations結構體
3)在出口函式裡通過unregister_chrdev() 解除安裝驅動
若有多個不同的驅動程式時,應用程式就要開啟多個不同的驅動裝置,由於是自己寫肯定會很清楚,如果給別人來使用時是不是很麻煩?
所以需要使用輸入子系統, 使應用程式無需開啟多個不同的驅動裝置便能實現
1.輸入子系統簡介
同樣的輸入子系統也需要輸入驅動的框架,好來辨認應用程式要開啟的是哪個輸入驅動
比如: 滑鼠、鍵盤、遊戲手柄等等這些都屬於輸入裝置;這些輸入裝置的驅動都是通過輸入子系統來實現的(當然,這些裝置也依賴於usb子系統)
這些輸入裝置都各有不同,那麼輸入子系統也就只能實現他們的共性,差異性則由裝置驅動來實現。差異性又體現在哪裡?
最直觀的就表現在這些裝置功能上的不同了。對於我們寫驅動的人來說在裝置驅動中就只要使用輸入子系統提供的工具(也就是函式)來完成這些“差異”就行了,其他的則是輸入子系統的工作。這個思想不僅存在於輸入子系統,其他子系統也是一樣(比如:usb子系統、video子系統等)
所以我們先來分析下輸入子系統input.c的程式碼,然後怎麼來使用輸入子系統(在核心中以input來形容輸入子系統)
2.開啟input.c,位於核心deivers/input
有以下這麼兩段:
subsys_initcall(input_init); //修飾入口函式 module_exit(input_exit); //修飾出口函式
顯然輸入子系統是作為一個模組存在,我們先來分析下input_int()入口函式
static int __init input_init(void)
{
int err;
err = class_register(&input_class); //(1)註冊類,放在/sys/class
if (err) {
printk(KERN_ERR "input: unable to register input_dev class\n");
return err;
}
err = input_proc_init(); //在/proc下面建立相關的檔案
if (err)
goto fail1;err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //(2)註冊驅動
if (err) {
printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
(1)上面第4行”err = class_register(&input_class);”是在/sys/class 裡建立一個 input類, input_class變數如下圖:
如下圖,我們啟動核心,再啟動一個input子系統的驅動後,也可以看到建立了個"input"類 :
為什麼這裡程式碼只建立類,沒有使用class_device_create()函式在類下面建立驅動裝置?
在下面第8小結會詳細講到,這裡簡單描述:當註冊input子系統的驅動後,才會有驅動裝置,此時這裡的程式碼是沒有驅動的
(2)上面第14行通過register_chrdev建立驅動裝置,其中變數INPUT_MAJOR =13,所以建立了一個主裝置為13的"input"裝置。
然後我們來看看它的操作結構體input_fops,如下圖:
只有一個.open函式,比如當我們掛載一個新的input驅動,則核心便會呼叫該.open函式,接下來分析該.open函式
3 然後進入input_open_file函式(drivers/input/input.c)
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5]; // (1)
const struct file_operations *old_fops, *new_fops = NULL;
int err;if (!handler || !(new_fops = fops_get(handler->fops))) //(2)
return -ENODEV;if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
}old_fops = file->f_op;
file->f_op = new_fops; //(3)err = new_fops->open(inode, file); //(4)
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}fops_put(old_fops);
return err;
}
(1)第3行中,其中iminor (inode)函式呼叫了MINOR(inode->i_rdev);讀取子裝置號,然後將子裝置除以32,找到新掛載的input驅動的陣列號,然後放在input_handler 驅動處理函式handler中
(2)第7行中,若handler有值,說明掛載有這個驅動,就將handler結構體裡的成員file_operations * fops賦到新的file_operations *old_fops裡面
(3)第16行中, 再將新的file_operations *old_fops賦到file-> file_operations *f_op裡, 此時input子系統的file_operations就等於新掛載的input驅動的file_operations結構體,實現一個偷天換日的效果.
(4)第18行中,然後呼叫新掛載的input驅動的*old_fops裡面的成員.open函式
4.上面程式碼的input_table[]陣列在初始時是沒有值的,
所以我們來看看input_table數組裡面的資料又是在哪個函式裡被賦值
在input.c函式(drivers/input/input.c)中搜索input_table,找到它在input_register_handler()函式中被賦值,程式碼如下:
int input_register_handler(struct input_handler *handler)
{
... ...
input_table[handler->minor >> 5] = handler; //input_table[]被賦值
... ...
list_add_tail(&handler->node, &input_handler_list); //然後將這個input_handler放到input_handler_list連結串列中
... ...
}
就是將驅動處理程式input_handler註冊到input_table[]中,然後放在input_handler_list連結串列中,後面會講這個連結串列
5.繼續來搜尋input_register_handler,看看這個函式被誰來呼叫
如下圖所示,有evdev.c(事件裝置),tsdev.c(觸控式螢幕裝置),joydev.c(joystick操作杆裝置),keyboard.c(鍵盤裝置),mousedev.c(滑鼠裝置) 這5個核心自帶的裝置處理函式註冊到input子系統中
我們以evdev.c為例,它在evdev_ini()函式中註冊:
static int __init evdev_init(void) { return input_register_handler(&evdev_handler); //註冊 }
6我們來看看這個evdev_handler變數是什麼結構體
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect, //(4)
.disconnect = evdev_disconnect,
.fops = &evdev_fops, //(1)
.minor = EVDEV_MINOR_BASE, //(2)
.name = "evdev",
.id_table = evdev_ids, //(3)
};
(1) 第5行中.fops:檔案操作結構體,其中evdev_fops函式就是自己的寫的操作函式,然後賦到.fops中
(2)第6行中 .minor:用來存放次裝置號
其中EVDEV_MINOR_BASE=64, 然後呼叫input_register_handler(&evdev_handler)後,由於EVDEV_MINOR_BASE/32=2,所以存到input_table[2]中
所以當open開啟這個input裝置,就會進入 input_open_file()函式,執行evdev_handler-> evdev_fops -> .open函式,如下圖所示:
(3)第8行中.id_table : 表示能支援哪些輸入裝置,比如某個驅動裝置的input_dev->的id和某個input_handler的id_table相匹配,就會呼叫.connect連線函式,如下圖
(4)第3行中.connect:連線函式,將裝置input_dev和某個input_handler建立連線,如下圖
7.我們先來看看上圖的input_register_device()函式,如何建立驅動裝置的
搜尋input_register_device,發現核心自己就已經註冊了很多驅動裝置
7.1然後進入input_register_device()函式,程式碼如下:
int input_register_device(struct input_dev *dev) //*dev:要註冊的驅動裝置
{
... ...
list_add_tail(&dev->node, &input_dev_list); //(1)放入連結串列中
... ...
list_for_each_entry(handler, &input_handler_list, node) //(2)
input_attach_handler(dev, handler);
... ...
}
(1)第4行中,將要註冊的input_dev驅動裝置放在input_dev_list連結串列中
(2)第6行中,其中input_handler_list在前面講過,就是存放每個input_handle驅動處理結構體,
然後list_for_each_entry()函式會將每個input_handle從連結串列中取出,放到handler中
最後會呼叫input_attach_handler()函式,將每個input_handle的id_table進行判斷,若兩者支援便進行連線。
7.2然後我們在回過頭來看註冊input_handler的input_register_handler()函式,如下圖所示
所以,不管新新增input_dev還是input_handler,都會進入input_attach_handler()判斷兩者id是否有支援, 若兩者支援便進行連線。
7.3我們來看看input_attach_handler()如何實現匹配兩者id的:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
... ...
id = input_match_device(handler->id_table, dev); //匹配兩者if (!id) //若不匹配,return退出
return -ENODEV;error = handler->connect(handler, dev, id); //呼叫input_handler ->connect函式建立連線
... ...}
若兩者匹配成功,就會自動進入input_handler 的connect函式建立連線
8我們還是以evdev.c(事件驅動) 的evdev_handler->connect函式
來分析是怎樣建立連線的,如下圖:
8.1 evdev_handler的.connect函式是evdev_connect(),程式碼如下:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
{
... ...
for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); //查詢驅動裝置的子裝置號
if (minor == EVDEV_MINORS) { // EVDEV_MINORS=32,所以該事件下的驅動裝置最多存32個,
printk(KERN_ERR "evdev: no more free evdev devices\n");
return -ENFILE; //沒找到驅動裝置
}
... ...
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //分配一個input_handle全域性結構體(沒有r)
... ...
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); //(1)儲存驅動裝置名字, event%d
... ...
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), //(2) 將主裝置號和次裝置號轉換成dev_t型別
cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name);
// (3)在input類下建立驅動裝置... ...
error = input_register_handle(&evdev->handle); //(4)註冊這個input_handle結構體... ...
}
(1) 第16行中,是在儲存驅動裝置名字,名為event%d, 比如下圖(鍵盤驅動)event1: 因為沒有設定子裝置號,預設從小到大排列,其中event0是表示這個input子系統,所以這個鍵盤驅動名字就是event1
(2)第18行中,是在儲存驅動裝置的主次裝置號,其中主裝置號INPUT_MAJOR=13,因為EVDEV_MINOR_BASE=64,所以此裝置號=64+驅動程式本事子裝置號, 比如下圖(鍵盤驅動)event1: 主次裝置號就是13,65
(3)在之前在2小結裡就分析了input_class類結構,所以第19行中,會在/sys/class/input類下建立驅動裝置event%d,比如下圖(鍵盤驅動)event1:
(4)最終會進入input_register_handle()函式來註冊,程式碼在下面
8.2 input_register_handle()函式如下:
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler; //handler= input_handler驅動處理結構體list_add_tail(&handle->d_node, &handle->dev->h_list); //(1)
list_add_tail(&handle->h_node, &handler->h_list); // (2)
if (handler->start)
handler->start(handle);
return 0;
}
(1)在第5行中, 因為handle->dev指向input_dev驅動裝置,所以就是將handle->d_node放入到input_dev驅動裝置的h_list連結串列中,
即input_dev驅動裝置的h_list連結串列就指向handle->d_node
(2) 在第6行中, 同樣, input_handler驅動處理結構體的h_list也指向了handle->h_node
最終如下圖所示:
兩者的.h_list都指向了同一個handle結構體,然後通過.h_list 來找到handle的成員.dev和handler,便能找到對方,便建立了連線
9.建立了連線後,又如何讀取evdev.c(事件驅動) 的evdev_handler->.fops->.read函式?
事件驅動的.read函式是evdev_read()函式,我們來分析下:
static ssize_t evdev_read(struct file *file, char __user * buffer, size_t count, loff_t *ppos)
{
... ...
/*判斷應用層要讀取的資料是否正確*/
if (count < evdev_event_size())
return -EINVAL;/*在非阻塞操作情況下,若client->head == client->tail|| evdev->exist時(沒有資料),則return返回*/
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
/*若client->head == client->tail|| evdev->exist時(沒有資料),等待中斷進入睡眠狀態 */
retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);... ... //上傳資料
}
10若read函式進入了休眠狀態,又是誰來喚醒?
我們搜尋這個evdev->wait這個等待佇列變數,找到evdev_event函式裡喚醒:
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { ... ... wake_up_interruptible(&evdev->wait); //有事件觸發,便喚醒等待中斷 }
其中evdev_event()是evdev.c(事件驅動) 的evdev_handler->.event成員,如下圖所示:
當有事件發生了,比如對於按鍵驅動,當有按鍵按下時,就會進入.event函式中處理事件
11分析下,是誰呼叫evdev_event()這個.event事件驅動函式
應該就是之前分析的input_dev那層呼叫的
我們來看看核心 gpio_keys_isr()函式程式碼例子就知道了 (driver/input/keyboard/gpio_key.c)
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
/*獲取按鍵值,賦到state裡*/
... .../*上報事件*/
input_event(input, type, button->code, !!state);
input_sync(input); //同步訊號通知,表示事件傳送完畢
}
顯然就是通過input_event()來呼叫.event事件函式,我們來看看:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
... .../* 通過input_dev ->h_list連結串列找到input_handle驅動處理結構體*/
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open) //如果input_handle之前open 過,那麼這個就是我們的驅動處理結構體
handle->handler->event(handle, type, code, value); //呼叫evdev_event()的.event事件函式}
若之前驅動input_dev和處理input_handler已經通過input_handler 的.connect函式建立起了連線,那麼就呼叫evdev_event()的.event事件函式,如下圖所示:
12本節總結分析:
1.註冊輸入子系統,進入put_init():
1)建立主裝置號為13的"input"字元裝置
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
2.open開啟驅動,進入input_open_file():
1)更新裝置的file_oprations
file->f_op=fops_get(handler->fops);
2)執行file_oprations->open函式
err = new_fops->open(inode, file);
3.註冊input_handler,進入input_register_handler():
1)新增到input_table[]處理陣列中
input_table[handler->minor >> 5] = handler;
2)新增到input_handler_list連結串列中
list_add_tail(&handler->node, &input_handler_list);
3)判斷input_dev的id,是否有支援這個驅動的裝置
list_for_each_entry(dev, &input_dev_list, node) //遍歷查詢input_dev_list連結串列裡所有input_dev input_attach_handler(dev, handler); //判斷兩者id,若兩者支援便進行連線。
4.註冊input_dev,進入input_register_device():
1)放在input_dev_list連結串列中
list_add_tail(&dev->node, &input_dev_list);
2)判斷input_handler的id,是否有支援這個裝置的驅動
list_for_each_entry(handler, &input_handler_list, node) //遍歷查詢input_handler_list連結串列裡所有input_handler input_attach_handler(dev, handler); //判斷兩者id,若兩者支援便進行連線
5.判斷input_handler和input_dev的id,進入input_attach_handler():
1)匹配兩者id,
input_match_device(handler->id_table, dev); //匹配input_handler和dev的id,不成功退出函式
2)匹配成功呼叫input_handler ->connect
handler->connect(handler, dev, id); //建立連線
6.建立input_handler和input_dev的連線,進入input_handler->connect():
1)建立全域性結構體,通過input_handle結構體連線雙方
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //建立兩者連線的input_handle全域性結構體 list_add_tail(&handle->d_node, &handle->dev->h_list); //連線input_dev->h_list list_add_tail(&handle->h_node, &handler->h_list); // 連線input_handle->h_list
7.有事件發生時,比如按鍵中斷,在中斷函式中需要進入input_event()上報事件:
1)找到驅動處理結構體,然後執行input_handler->event()
list_for_each_entry(handle, &dev->h_list, d_node) // 通過input_dev ->h_list連結串列找到input_handle驅動處理結構體 if (handle->open) //如果input_handle之前open 過,那麼這個就是我們的驅動處理結構體(有可能一個驅動裝置在不同情況下有不同的驅動處理方式) handle->handler->event(handle, type, code, value); //呼叫evdev_event()的.event事件函式