https://www.cnblogs.com/lifexy/p/7542989.html
輸入子系統概念介紹
在以前的按鍵程式上,有個缺點,沒辦法用在被人的現成的應用程式上,比如QT等應用程式。别的應用程式不會打開/dev/buttons,有可能打開現成的裝置/dev/tty。也有可能直接調用scanf(),就可以擷取按鍵的輸入。
以前寫的應用程式,隻有你知道怎麼用,但是其他人不知道怎麼用。
寫出一個通用的驅動程式,讓已經現成的應用程式無縫的移植到你的單闆上。(無縫就是不需要修改别人的應用程式)
使用現成的驅動。在核心裡面現成的驅動程式,把自己需要的東西融合進去。(現成的驅動程式就是輸入input子系統)
以前的字元裝置驅動程式步驟:
1、确定主裝置号major;
2、構造一個file_operations結構體(open函數、read函數、write函數);
3、告訴核心,register_chrdev,注冊字元裝置驅動程式;
4、入口函數調用注冊函數;
5、有入口函數,必然有出口函數;
一、輸入(input)子系統架構:(現成的架構,系統已做好,現成的架構也有上面的幾個步驟)
打開核心層input.c(中轉作用),位于drivers\input目錄下
subsys_initcall(input_init);
module_exit(input_exit);
入口函數input_init,出口函數input_exit。
二、顯然輸入子系統是作為一個子產品存在,我們先來分析一下input_init()入口函數
static int __init input_init(void)
{
int err;
err = class_register(&input_class);
if (err) {
printk(KERN_ERR "input: unable to register input_dev class\n");
return err;
}
err = input_proc_init();
if (err)
goto fail1;
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
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)上面第15行,register_chrdev(INPUT_MAJOR, "input", &input_fops);,以前注冊裝置需要自己寫的,現在核心代碼裡面有了。注冊了主裝置号為13。看一下input_fops(file_operantions)結構體:
為什麼隻有一個open函數,不是要讀按鍵嗎?
顯然open函數做了某些工作。
三、然後進入input_open_file函數裡面:
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;
}
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
(1)第三行中, iminor(inode)調用了MINOR(inode->i_rdev)來擷取次裝置号,将次裝置号處理32。input_handler輸入處理器(處理句柄),handler=input_table。input_table數組,根據次裝置号所打開的檔案指派給handler。
(2)第七行中,input_handler有個file_operations結構體,fops_get那個file_operations結構體。
(3)第十五行中,現在所打開的檔案f_op 等于 新的f_op(file_operantions結構體)。
(4)第十七行中,調用新的f_op的open函數。
以後應用程式app讀read時,最終會調用到file->f_op->read。
input_table數組由誰來構造?
四、input_register_handler會構造input_table數組:
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
INIT_LIST_HEAD(&handler->h_list);
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5])
return -EBUSY;
input_table[handler->minor >> 5] = handler; //(1)
}
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
return 0;
}
五、搜尋一下input_register_handler函數會被誰調用:
我們可以從下圖可以知道,evdev.c(事件裝置),joydev.c(遊戲句柄裝置),keyboard.c(鍵盤裝置),mousedev.c(滑鼠裝置),tsdev.c(觸摸屏裝置)。
以evdev.c為例子,它在evdev_exit()注冊:
static void __exit evdev_exit(void)
{
input_unregister_handler(&evdev_handler); //注冊
}
六、再來看一下edev_handler結構體:(純軟體)
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
(1)第五中.fops:file_oprations結構體。
(2)第六行中.minor:其中,EVDEV_MINOR_BASE=64,次裝置是64。然後調用input_register_handler注冊,相當于EVDEV_MINOR_BASE/32=2,是以放在數組的第二項EVDEV_MINOR_BASE[2]。
是以open時,就會調用input_open_file,執行evdev_handler->edev_fops->.open
(3)第八行中.id_table:表示handler能夠支援哪一些輸入裝置。hadler和devices進行比較,handler處理器(軟體)能否支援device(裝置),如果能夠支援裝置,則調用.connect函數。
(4)第三行中.connect:連接配接函數,将input_device和input_handler建立連接配接。
七、再來看一下input_register_device函數:
int input_register_device(struct input_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); //(2)
... ...
return 0;
}
(1)第4行中,将input_devid結構體,放入input_dev_list連結清單裡面。
(2)第6行中,對于每一個input_handler,都調用input_attach_handler,input_attach_handler根據input_handler的id_table判斷能否支援那個input_dev(輸入裝置)。
八、然後再回過頭看input_handler的input_register_handler()函數:
int input_register_handler(struct input_handler *handler)
{
... ...
input_table[handler->minor >> 5] = handler; //(1)
... ...
list_add_tail(&handler->node, &input_handler_list); //(2)
list_for_each_entry(dev, &input_dev_list, node) //(3)
input_attach_handler(dev, handler);
... ...
return 0;
}
(1)第4行中,首先将input_device放入一個數組裡面。
(2)第6行中,再講input_device放到一個連結清單input_handler_list裡面。
(3)第8行中,對于每一個input_device,都調用input_attach_handler,input_attach_handler根據input_handler的id_table判斷能否支援那個input_dev(輸入裝置)。
是以,不管是先添加input_handler還是input_dev,調用input_attach_handler(),判斷兩者是否比對、支援。
九、我們來看一下input_attach_handler()函數:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
... ...
id = input_match_device(handler->id_table, dev); //根據handler的id_table和輸入裝置dev進行比較
if (!id) //比對失敗
return -ENODEV;
error = handler->connect(handler, dev, id); //如果比對調用handler的connect函數
... ...
return error;
}
根據handler的id_table和輸入裝置dev進行比較,是否比對。如果比對,調用hadler裡面的connect函數建立連接配接。
注冊input_dev或者input_handler時,會兩兩比較左邊的input_dev和右邊的input_handler,根據input_handler的id_table判斷這個input_handler能否支援這個input_dev,如果能支援,則調用input_handler的connect函數建立“連接配接”。
十、舉個列子(evdev.c事件驅動):evdev_handler->connect函數
10.1 來分析怎麼樣建立連接配接:
10.2 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) {
printk(KERN_ERR "evdev: no more free evdev devices\n");
return -ENFILE;
}
... ...
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //(1)配置設定一個input_handle結構體(沒有r)
... ...
evdev->handle.dev = dev; //(2)設定input_handle,指向左邊的input_dev結構體
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; //(2)指向右邊的input_handler結構體
evdev->handle.private = evdev;
sprintf(evdev->name, "event%d", minor);
... ...
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name);
... ...
error = input_register_handle(&evdev->handle); //(3)注冊handle
... ...
}
(1)第10行中,配置設定一個input_handle結構體(沒有r)。
(2)第12行中,設定input_handle,dev指向左邊的input_dev結構體,.handler指向右邊的input_handler結構體。
(3)第21行中,注冊handle。
10.3 最終進入input_register_handle()函數注冊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); //(1)
list_add_tail(&handle->h_node, &handler->h_list); //(2)
if (handler->start)
handler->start(handle);
return 0;
}
(1)第五行中,把傳進來的handle放入一個輸入裝置dev的連結清單h_list裡面。
(2)第六行中,把handle放入右邊handler的連結清單h_list裡面。
連接配接的時候構造一個input_handle,裡面.dev指向input_device,裡面.handler指向input_handler。
input_device的.h_list指向input_handle,input_handler的h_.list指向input_handle。
可以從input_device輸入裝置,通過.h_list找到input_handle,從裡面的.handler找到右邊的handler處理者。
也可從input_handler,通過.h_list找到input_handle,從裡面的.dev找到左邊的能支援的裝置dev。
總結一下:
1、配置設定一個input_handle結構體
2、input_handle.dev = input_dev //指向左邊的input_dev
input_handle.handler = input_handler //指向右邊的 input_handler
3、注冊:
input_handler->h_list = &input_handle;
input_dev->h_list = &input_handle;
十一、怎麼讀取按鍵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;
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);
... ...
}
十二、read函數進入休眠,被誰喚醒呢?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被誰調用?gpio_keys_isr(在drivers\input\keyboard\gpio_keys.c檔案)
在裝置的中斷服務程式裡,确定事件是什麼,然後調用相應的input_handler的event處理函數。
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
... ...
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;
list_for_each_entry(handle, &dev->h_list, d_node)//對連結清單的每一項handle
if (handle->open)//如果handle已經打開
handle->handler->event(handle, type, code, value);
}