Linux驅(qū)動-輸入子系統(tǒng)框架
部分內(nèi)容參考Linux學習之路,表示感謝.
① 在 input_init 中注冊了字符設備驅(qū)動
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
② 在注冊的fops中,僅僅只有1個open函數(shù),下面我們來看這個open函數(shù)input_open_file
static int input_open_file(struct inode *inode, struct file *file)
{
// 定義一個input_handler指針,根據(jù)次設備號,從 input_table 數(shù)組中取出對應的 handler
struct input_handler *handler = input_table[iminor(inode) >> 5];
const struct file_operations *old_fops, *new_fops = NULL;
// 將 handler 的fops賦值給file->f_op,并用調(diào)用新的open函數(shù)
old_fops = file->f_op;
file->f_op = fops_get(handler->fops);
err = file->f_op->open(inode, file);
那么,必定有個地方創(chuàng)建了handler并對它進行一定的設置,并提供fops函數(shù),將它放入input_table。
二、事件處理層,注冊input_handler ① 放入鏈表、數(shù)組(input_register_handler)就這樣,Input.c 實現(xiàn)了一個通用對外接口。
input.c/input_register_handler 函數(shù)中 創(chuàng)建了handler并對它進行一定的設置,提供fops函數(shù),將它放入input_table
int input_register_handler(struct input_handler *handler)
{
// 將 handler 放入 input_table
input_table[handler->minor >> 5] = handler;
// 將 handler 放入 input_handler_list 鏈表
list_add_tail(&handler->node, &input_handler_list);
// 取出 input_dev_list 鏈表中的每一個 dev 與 該 handler 進行 比對
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
}
以 Evdev.c 為例來更好的進行說明
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,
};
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
我們與核心層提供的接口對應一下,假設APP需要讀按鍵:
app: read > ... > file->f_op->read == handler->fops->read == evdev_handler->evdev_fops->evdev_read
/* 讀函數(shù)中 如果沒有事件上報休眠,等待上報事件 喚醒休眠,將事件傳送到用戶空間 */
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
//如果無數(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);
//將內(nèi)核空間數(shù)據(jù)拷貝到用戶空間,略
return retval;
}
② 匹配 (input_attach_handler)
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
// 看 dev.id 是否存在于 handler->id_table 中
id = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV;
// 在的話,調(diào)用 handler->connect
error = handler->connect(handler, dev, id);
}
③ 建立連接
我們以 Evdev.c 為例,看一下connect函數(shù)
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
// 不要關(guān)心 evdev ,只看 evdev->handle 即可,這里構(gòu)建了一個 handle ,注意不是handler
// handle 就是個 中間件,可以理解成膠帶,它把 hander 與 dev 連在一起
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
// 第一次建立聯(lián)系,在 handle 中記錄 dev 與 handle 的信息,這樣通過handle就可以找到dev與handler
// 即是 實現(xiàn) handle -> dev handle -> hander 的聯(lián)系
evdev->handle.dev = dev;
evdev->handle.handler = handler;
// 申請設備號,創(chuàng)建設備節(jié)點
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
dev_set_name(&evdev->dev, "event%d", minor);
// 在input 類下面創(chuàng)建設備,文件夾的名字是 evdev->name ->inputn ,設備名是 dev->cdev.dev.name -> eventn
cdev = class_device_create(&input_class, &dev->cdev, devt,
dev->cdev.dev, evdev->name);
// 注冊 handle
error = input_register_handle(&evdev->handle);
}
④ 注冊handle,第二次建立聯(lián)系
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
// 將handle 記錄在 dev->h_list 中
list_add_tail(&handle->d_node, &handle->dev->h_list);
// 將handle 記錄在 handler->h_list 中
list_add_tail(&handle->h_node, &handler->h_list);
// 至此,dev 與 hander 也可以找到handle了,dev <-> handle <-> handler 之間暢通無阻
}
小結(jié):
事件處理層,構(gòu)建 handler , 通過 input_register_handler 進行注冊,注冊時
1、將 handler 放入 input_handler_list 鏈表
2、將 handler 放入 input_table
3、取出 input_dev_list鏈表中的每一個dev 調(diào)用 input_attach_handler 進行id匹配
4、如果匹配成功,則調(diào)用 handler->connect 第一次建立連接
5、創(chuàng)建 handle 來進行第二次建立連接,在 handle 中記錄 dev 與 handler 的信息,這樣通過handle就可以找到dev與handler
6、在dev hander 中記錄 handle的信息,實現(xiàn) dev <-> handle <-> handler
三、設備層,注冊input_dev
int input_register_device(struct input_dev *dev)
{
// 將 dev 放入 input_dev_list
list_add_tail(&dev->node, &input_dev_list);
// 匹配 handler ,參考 ①、②
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
}
建立設備層與設備處理層聯(lián)系作用
以上面Evdev.c讀按鍵為例:假設無按鍵按下進如休眠模式
wait_event_interruptible(evdev->wait,
client->head != client->tail || !evdev->exist);
那么誰來喚醒休眠呢?
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
wake_up_interruptible(&evdev->wait);
}
這樣我們似乎明白了,在設備層,我們寫驅(qū)動的時候,比如按鍵按了一下,我們要上報event 到Handler層進行處理,然后提交給用戶程序。
例如:Gpio_keys.c 中斷處理函數(shù)中
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
// 上報事件
input_event(input, type, button->code, !!state);
input_sync(input);
return IRQ_HANDLED;
}
回看input.c/input_event函數(shù)
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);
最終調(diào)用 handler->event(handle, type, code, value);
剛好與我們的例子對應:evdev_handler->evdev_event >> wake_up_interruptible(&evdev->wait);
四、寫一個基于Input子系統(tǒng)的設備驅(qū)動
事件處理層不用我們管了,- -是暫時能力有限管不了。寫寫設備層的程序就好了。
軟件設計流程:
/* 1. 分配一個Input_dev結(jié)構(gòu)體 */
/* 2. 設置 支持哪一類事件,該類事件里的那些事件*/
/* 3.注冊 */
/* 4.硬件相關(guān)操作 */
流程圖:
設置事件的類型:
/*事件類型:*/
struct input_dev {
void *private; //輸入設備私有指針,一般指向用于描述設備驅(qū)動層的設備結(jié)構(gòu)
const char *name; //提供給用戶的輸入設備的名稱
const char *phys; //提供給編程者的設備節(jié)點的名稱
const char *uniq; //指定唯一的ID號,就像MAC地址一樣
struct input_id id; //輸入設備標識ID,用于和事件處理層進行匹配
unsigned long evbit[NBITS(EV_MAX)]; //位圖,記錄設備支持的事件類型
/*
* #define EV_SYN 0x00 //同步事件
* #define EV_KEY 0x01 //按鍵事件
* #define EV_REL 0x02 //相對坐標
* #define EV_ABS 0x03 //絕對坐標
* #define EV_MSC 0x04 //其它
* #define EV_SW 0x05 //開關(guān)事件
* #define EV_LED 0x11 //LED事件
* #define EV_SND 0x12
* #define EV_REP 0x14 //重復上報
* #define EV_FF 0x15
* #define EV_PWR 0x16
* #define EV_FF_STATUS 0x17
* #define EV_MAX 0x1f
*/
unsigned long keybit[NBITS(KEY_MAX)]; //位圖,記錄設備支持的按鍵類型
unsigned long relbit[NBITS(REL_MAX)]; //位圖,記錄設備支持的相對坐標
unsigned long absbit[NBITS(ABS_MAX)]; //位圖,記錄設備支持的絕對坐標
unsigned long mscbit[NBITS(MSC_MAX)]; //位圖,記錄設備支持的其他功能
unsigned long ledbit[NBITS(LED_MAX)]; //位圖,記錄設備支持的指示燈
unsigned long sndbit[NBITS(SND_MAX)]; //位圖,記錄設備支持的聲音或警報
unsigned long ffbit[NBITS(FF_MAX)]; //位圖,記錄設備支持的作用力功能
unsigned long swbit[NBITS(SW_MAX)]; //位圖,記錄設備支持的開關(guān)功能
unsigned int keycodemax; //設備支持的最大按鍵值個數(shù)
unsigned int keycodesize; //每個按鍵的字節(jié)大小
void *keycode; //指向按鍵池,即指向按鍵值數(shù)組首地址
int (*setkeycode)(struct input_dev *dev, int scancode, int keycode); //修改按鍵值
int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode); //獲取按鍵值
struct ff_device *ff;
unsigned int repeat_key; //支持重復按鍵
struct timer_list timer; //設置當有連擊時的延時定時器
int state;
int sync; //同步事件完成標識,為1說明事件同步完成
int abs[ABS_MAX + 1]; //記錄坐標的值
int rep[REP_MAX + 1]; //記錄重復按鍵的參數(shù)值
unsigned long key[NBITS(KEY_MAX)]; //位圖,按鍵的狀態(tài)
unsigned long led[NBITS(LED_MAX)]; //位圖,led的狀態(tài)
unsigned long snd[NBITS(SND_MAX)]; //位圖,聲音的狀態(tài)
unsigned long sw[NBITS(SW_MAX)]; //位圖,開關(guān)的狀態(tài)
int absmax[ABS_MAX + 1]; //位圖,記錄坐標的最大值
int absmin[ABS_MAX + 1]; //位圖,記錄坐標的最小值
int absfuzz[ABS_MAX + 1]; //位圖,記錄坐標的分辨率
int absflat[ABS_MAX + 1]; //位圖,記錄坐標的基準值
int (*open)(struct input_dev *dev); //輸入設備打開函數(shù)
void (*close)(struct input_dev *dev); //輸入設備關(guān)閉函數(shù)
int (*flush)(struct input_dev *dev, struct file *file); //輸入設備斷開后刷新函數(shù)
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); //事件處理
struct input_handle *grab;
struct mutex mutex; //用于open、close函數(shù)的連續(xù)訪問互斥
unsigned int users;
struct class_device cdev; //輸入設備的類信息
union { //設備結(jié)構(gòu)體
struct device *parent;
} dev;
struct list_head h_list; //handle鏈表
struct list_head node; //input_dev鏈表
};
驅(qū)動函數(shù):buttons_drv.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct pin_desc {
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[3] = {
{IRQ_EINT0, "s2",S3C2410_GPF0,KEY_L},
{IRQ_EINT2, "s3",S3C2410_GPF2,KEY_S},
{IRQ_EINT11,"s4",S3C2410_GPG3,KEY_ENTER},
};
static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 設置定時器,發(fā)生中斷后10ms后再去讀電平值 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer,jiffies+HZ/100);
return IRQ_RETVAL(IRQ_HANDLED);
}
static void buttons_timer_function(unsigned long data)
{
struct pin_desc *pindesc = irq_pd;
unsigned int pinval;
if(!pindesc)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if(pinval)
{
/* 松開 : 最后一個參數(shù): 0-松開,1-按下 */
input_event(buttons_dev,EV_KEY,pindesc->key_val,0);
}
else
{
/* 按下 : 最后一個參數(shù): 0-松開,1-按下 */
input_event(buttons_dev,EV_KEY,pindesc->key_val,1);
}
}
static int buttons_init(void)
{
int i;
/* 1. 分配一個input_dev結(jié)構(gòu)體 */
buttons_dev = input_allocate_device();
/* 2. 設置 */
/* 2.1 能產(chǎn)生哪類事件 */
set_bit(EV_KEY,buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);//用于重復事件,按下按鍵不松開可一直輸入
/* 2.2 能產(chǎn)生這類事件里的哪些操作:L,S,ENTER */
set_bit(KEY_L,buttons_dev->keybit);
set_bit(KEY_S,buttons_dev->keybit);
set_bit(KEY_ENTER,buttons_dev->keybit);
/* 3. 注冊 */
input_register_device(buttons_dev);
/* 4. 硬件相關(guān)的操作 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
for(i = 0 ;i < 3;i++)
{
request_irq(pins_desc[i].irq, buttons_irq,IRQT_BOTHEDGE,pins_desc[i].name,&pins_desc[i]);
}
return 0;
}
static void buttons_exit(void)
{
int i;
for(i = 0;i < 3;i++)
{
free_irq(pins_desc[i].irq,&pins_desc[i]);
}
del_timer(&buttons_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
測試方法:
① 通過控制臺查看cat /dev/tty1
這里有個問題就是必須按下回車才會出現(xiàn)回顯,因為我們默認的輸出設備為串口,現(xiàn)在將他改為tty1,使用
exec 0
hexdump /dev/event1
查看用戶得到的數(shù)據(jù)和驅(qū)動中的數(shù)據(jù)比對
硬件有數(shù)據(jù)產(chǎn)生時,調(diào)用input_event 上報時間(上報事件核心)
--》handle->handler->event(handle, type, code, value);//從輸入設備的h_list里面找出handle,從handle得到handler,調(diào)用它的event函數(shù)
--》evdev_event//記錄按鍵值-->發(fā)信號--》喚醒程序,因為之前read時沒數(shù)據(jù)會休眠;
那么我們這里:
open(/dev/event1)
–》read > ... >
–》evdev_handler->evdev_fops->evdev_read
–》evdev_event_to_user
–》讀到input_event -->
struct input_event {
struct timeval time; 時間
__u16 type; 類別(按鍵類、相對位移、絕對位移)
__u16 code;那個位置位置
__s32 value;
};//這個結(jié)構(gòu)體可以支持所有的輸入事件。
剛好一一對應測試成功。