版權(quán)聲明:本文為衛(wèi)偉學(xué)習(xí)總結(jié)文章堂飞,轉(zhuǎn)載請注明出處!
導(dǎo)讀:Linux輸入子系統(tǒng)由驅(qū)動層稼跳、輸入子系統(tǒng)核心層、事件處理層三部分組成。一個輸入事件碧聪,如鼠標(biāo)移動、鍵盤按下等通過Driver->Inputcore->Event handler->userspace的順序到達(dá)用戶控件的應(yīng)用程序液茎。
驅(qū)動層
將底層的硬件輸入轉(zhuǎn)化為統(tǒng)一事件形式逞姿,向輸入核心層(Input Core)匯報辞嗡。
輸入子系統(tǒng)核心層
- 為驅(qū)動層提供輸入設(shè)備注冊與操作接口,如:input_register_device;
- 通知事件處理層對事件進(jìn)行處理; 在/Proc下產(chǎn)生相應(yīng)的設(shè)備信息滞造。
事件處理層
主要是和用戶空間交互(Linux在用戶空間將所有的設(shè)備都當(dāng)作文件處理续室,由于在一般的驅(qū)動程序中都有提供fops接口,以及在/dev下生成相應(yīng)的設(shè)備文件nod,這些操作在輸入子系統(tǒng)中由事件處理層完成)谒养。
設(shè)備描述
input_dev結(jié)構(gòu)是實現(xiàn)設(shè)備驅(qū)動核心工作:向系統(tǒng)報告按鍵挺狰、觸摸屏等輸入事件(event,通過input_event結(jié)構(gòu)描述),不再需要關(guān)心文件操作接口买窟。驅(qū)動報告事件經(jīng)過inputCore和Eventhandler到達(dá)用戶空間丰泊。
注冊輸入設(shè)備函數(shù):
int input_register_device(struct input_dev *dev);
注銷輸入設(shè)備函數(shù):
void input_unregister_device(struct input_dev *dev)始绍;
驅(qū)動實現(xiàn)--初始化(事件支持)set_bit()告訴input輸入子系統(tǒng)支持那些事件瞳购,那些按鍵。例如:set_bit(EV_KEY,button_dev.evbit) (其中button_dev是struct input_dev類型)亏推。
struct input_dev中有兩個成員為
- evbit事件類型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)学赛。
- eybit按鍵類型(當(dāng)事件類型為EV_KEY時包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等)。
驅(qū)動實現(xiàn)——報告事件用于報告EV_KEY,EV_REL,EV_ABS事件的函數(shù)分別為:
void input_report_key(struct input_dev *dev,unsigned int code,int value)
void input_report_rel(struct input_dev *dev,unsigned int code,int value)
void input_report_abs(struct input_dev *dev,unsigned int code,int value)
驅(qū)動實現(xiàn)——報告結(jié)束input_sync()同步用于告訴input core子系統(tǒng)報告結(jié)束吞杭,觸摸屏設(shè)備驅(qū)動中盏浇,一次點擊的整個報告過程如下:
input_reprot_abs(input_dev,ABS_X,x); //x坐標(biāo)
input_reprot_abs(input_dev,ABS_Y,y); // y坐標(biāo)
input_reprot_abs(input_dev,ABS_PRESSURE,1);
input_sync(input_dev);//同步結(jié)束
input.c文件分析
drivers/input/input.c:
input_init > err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //入口函數(shù)
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
怎么讀按鍵?
input_open_file
struct input_handler *handler = input_table[iminor(inode) >> 5];
new_fops = fops_get(handler->fops) // =>&evdev_fops
file->f_op = new_fops;
err = new_fops->open(inode, file);
app: read > ... > file->f_op->read
input_table數(shù)組由誰構(gòu)造篇亭?
input_register_handler
注冊input_handler:
input_register_handler
// 放入數(shù)組
input_table[handler->minor >> 5] = handler;
// 放入鏈表
list_add_tail(&handler->node, &input_handler_list);
// 對于每個input_dev缠捌,調(diào)用input_attach_handler
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler); // 根據(jù)input_handler的id_table判斷能否支持這個input_dev
注冊輸入設(shè)備:
input_register_device
// 放入鏈表
list_add_tail(&dev->node, &input_dev_list);
// 對于每一個input_handler,都調(diào)用input_attach_handler
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); // 根據(jù)input_handler的id_table判斷能否支持這個input_dev
input_attach_handler
id = input_match_device(handler->id_table, dev);
error = handler->connect(handler, dev, id);
注冊input_dev或input_handler時译蒂,會兩兩比較左邊的input_dev和右邊的input_handler,
根據(jù)input_handler的id_table判斷這個input_handler能否支持這個input_dev曼月,
如果能支持,則調(diào)用input_handler的connect函數(shù)建立"連接"柔昼。
怎么建立連接哑芹?
- 分配一個input_handle結(jié)構(gòu)體;
- input_handle.dev = input_dev; // 指向左邊的input_dev
input_handle.handler = input_handler; // 指向右邊的input_handler - 注冊: input_handler->h_list = &input_handle;
inpu_dev->h_list = &input_handle;
evdev_connect
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 分配一個input_handle
// 設(shè)置
evdev->handle.dev = dev; // 指向左邊的input_dev
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; // 指向右邊的input_handler
evdev->handle.private = evdev;
// 注冊
error = input_register_handle(&evdev->handle);
怎么讀按鍵捕透?
evdev_read
// 無數(shù)據(jù)并且是非阻塞方式打開聪姿,則立刻返回
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);
誰來喚醒?
evdev_event
wake_up_interruptible(&evdev->wait);
**
evdev_event被誰調(diào)用乙嘀?**
猜:應(yīng)該是硬件相關(guān)的代碼末购,input_dev那層調(diào)用的
在設(shè)備的中斷服務(wù)程序里,確定事件是什么虎谢,然后調(diào)用相應(yīng)的input_handler的event處理函數(shù)
gpio_keys_isr
// 上報事件
input_event(input, type, button->code, !!state);
input_sync(input);
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)
if (handle->open)
handle->handler->event(handle, type, code, value);
怎么寫符合輸入子系統(tǒng)框架的驅(qū)動程序盟榴?
- 向內(nèi)核申請input_dev結(jié)構(gòu)體;
- 設(shè)置input_dev的成員婴噩;
- 注冊input_dev 驅(qū)動設(shè)備擎场;
- 初始化定時器和中斷羽德;
- 寫中斷服務(wù)函數(shù);
- 寫定時器超時函數(shù)迅办;
- 在出口函數(shù)中 釋放中斷函數(shù),刪除定時器,卸載釋放驅(qū)動宅静。
具體代碼如下(都加了注釋):
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/gpio_keys.h>
#include <asm/gpio.h>
struct input_dev *buttons_dev; // 定義一個input_dev結(jié)構(gòu)體
static struct ping_desc *buttons_id; //保存dev_id,在定時器中用
static struct timer_list buttons_timer; //定時器結(jié)構(gòu)體
struct ping_desc{
unsigned char *name; //中斷設(shè)備名稱
int pin_irq; //按鍵的外部中斷標(biāo)志位
unsigned int pin; //引腳
unsigned int irq_ctl; //觸發(fā)中斷狀態(tài): IRQ_TYPE_EDGE_BOTH
unsigned int button; //dev_id,對應(yīng)鍵盤的 L , S, 空格, enter
};
// KEY1 -> L
// KEY2 -> S
// KEY3 -> 空格
// KEY4 -> enter
static struct ping_desc buttons_desc[5]=
{
{"s1", IRQ_EINT0, S3C2410_GPF0, IRQ_TYPE_EDGE_BOTH,KEY_L},
{"s2", IRQ_EINT2, S3C2410_GPF2, IRQ_TYPE_EDGE_BOTH,KEY_S},
{"s3", IRQ_EINT11, S3C2410_GPG3 , IRQ_TYPE_EDGE_BOTH,KEY_SPACE},
{"s4", IRQ_EINT19, S3C2410_GPG11,IRQ_TYPE_EDGE_BOTH,KEY_ENTER},
};
/*5. 寫中斷服務(wù)函數(shù)*/
static irqreturn_t buttons_irq (int irq, void *dev_id) //中斷服務(wù)函數(shù)
{
buttons_id=(struct ping_desc *)dev_id; //保存當(dāng)前的dev_id
mod_timer(&buttons_timer, jiffies+HZ/100 ); //更新定時器值 10ms
return 0;
}
/*6.寫定時器超時函數(shù)*/
void buttons_timer_function(unsigned long i)
{
int val;
val=s3c2410_gpio_getpin(buttons_id->pin); //獲取是什么電平
if(val) //高電平,松開
{
/*上報事件*/
input_event(buttons_dev,EV_KEY,buttons_id->button, 0); //上報EV_KEY類型,button按鍵,0(沒按下)
input_sync(buttons_dev); // 上傳同步事件,告訴系統(tǒng)有事件出現(xiàn)
}
else //低電平,按下
{
/*上報事件*/
input_event(buttons_dev, EV_KEY, buttons_id->button, 1); //上報EV_KEY類型,button按鍵,1(按下)
input_sync(buttons_dev); // 上傳同步事件,告訴系統(tǒng)有事件出現(xiàn)
}
}
static int buttons_init(void) //入口函數(shù)
{
int i;
buttons_dev=input_allocate_device(); //1.向內(nèi)核 申請input_dev結(jié)構(gòu)體
/*2.設(shè)置input_dev , */
set_bit(EV_KEY,buttons_dev->evbit); //支持鍵盤事件
set_bit(EV_REP,buttons_dev->evbit); //支持鍵盤重復(fù)按事件
set_bit(KEY_L,buttons_dev->keybit); //支持按鍵 L
set_bit(KEY_S,buttons_dev->keybit); //支持按鍵 S
set_bit(KEY_SPACE,buttons_dev->keybit); //支持按鍵 空格
set_bit(KEY_ENTER,buttons_dev->keybit); //支持按鍵 enter
/*3.注冊input_dev */
input_register_device(buttons_dev);
/*4. 初始化硬件:初始化定時器和中斷*/
// KEY1 -> L
// KEY2 -> S
// KEY3 -> 空格
// KEY4 -> enter
init_timer(&buttons_timer);
buttons_timer.function=buttons_timer_function;
add_timer(&buttons_timer);
for(i=0;i<4;i++)
request_irq(buttons_desc[i].pin_irq, buttons_irq, buttons_desc[i].irq_ctl, buttons_desc[i].name, &buttons_desc[i]);
return 0;
}
static int buttons_exit(void) //出口函數(shù)
{
/*7.釋放中斷函數(shù),刪除定時器,卸載釋放驅(qū)動*/
int i;
for(i=0;i<4;i++)
free_irq(buttons_desc[i].pin_irq,&buttons_desc[i]); //釋放中斷函數(shù)
del_timer(&buttons_timer); //刪除定時器
input_unregister_device(buttons_dev); //卸載類下的驅(qū)動設(shè)備
input_free_device(buttons_dev); //釋放驅(qū)動結(jié)構(gòu)體
return 0;
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL v2");
測試運行
掛載鍵盤驅(qū)動后, 如下圖,可以通過 ls -l /dev/event* 命令查看已掛載的設(shè)備節(jié)點:
測試運行有兩種,一種是直接打開/dev/tyy1,第二種是使用exec命令
- cat /dev/tty1 //tty1:LCD終端,就會通過tty_io.c來訪問鍵盤驅(qū)動,然后打印在tty1終端上
- exec 0</dev/tty1 //將/dev/tty1掛載到-sh進(jìn)程描述符0下,此時的鍵盤驅(qū)動就會直接打印在tty1終端上