前言:上一篇中,我們掌握一些必要的基礎(chǔ)知識(shí),這一篇就來分析當(dāng)我們點(diǎn)擊一個(gè)按鍵時(shí),按鍵上報(bào)的具體流程。
一、按鍵按下 涉及到的層級(jí)
按鍵按下 會(huì)通過以下系統(tǒng)層級(jí):
Driver(設(shè)備驅(qū)動(dòng)層)->Inputcore(輸入子系統(tǒng)核心層)
->Event handler(事件處理層)->userspace(用戶空間)

整個(gè)過程涉及到內(nèi)核層、Framework層以及應(yīng)用層.
本文主要分析輸入事件內(nèi)核層的流程
二、鍵值上報(bào)具體流程
1.中斷注冊(cè) kpd_pdrv_probe()
路徑:kernel-3.18/drivers/input/keyboard/mediatek/kpd.c
static int kpd_pdrv_probe(struct platform_device *pdev){
//省略代碼...
/* initialize and register input device (/dev/input/eventX) */
kpd_input_dev = input_allocate_device();//申請(qǐng)input設(shè)備
if (!kpd_input_dev) {
kpd_print("input allocate device fail.\n");
return -ENOMEM;
}
//input設(shè)備初始化 填充kpd_input_dev 設(shè)備驅(qū)動(dòng)結(jié)構(gòu)體*
kpd_input_dev->name = KPD_NAME;
kpd_input_dev->id.bustype = BUS_HOST;
kpd_input_dev->id.vendor = 0x2454;
kpd_input_dev->id.product = 0x6500;
kpd_input_dev->id.version = 0x0010;
kpd_input_dev->open = kpd_open;
//解析dts中keypad節(jié)點(diǎn)的信息,賦值給kpd_dts_data結(jié)構(gòu)體
kpd_get_dts_info(pdev->dev.of_node);
//input設(shè)備支持EV_KEY事件
__set_bit(EV_KEY, kpd_input_dev->evbit);
//注冊(cè)input設(shè)備
r = input_register_device(kpd_input_dev);
//設(shè)置按鍵消抖
kpd_set_debounce(kpd_dts_data.kpd_key_debounce);
//申請(qǐng)中斷處理
r = request_irq(kp_irqnr, kpd_irq_handler,
IRQF_TRIGGER_NONE, KPD_NAME, NULL);
}
分析:可以看到,在probe()函數(shù)中,調(diào)用
request_irq();注冊(cè)了中斷處理函數(shù)kpd_irq_handle()
2.中斷處理函數(shù) kpd_irq_handler()
當(dāng)我們按鍵按下了,就會(huì)觸發(fā)中斷,進(jìn)入中斷服務(wù)子程序,系統(tǒng)會(huì)調(diào)用相應(yīng)的中斷處理函數(shù)kpd_irq_handler()去上報(bào)事件。
路徑:kernel-3.18/drivers/input/keyboard/mediatek/kpd.c
//中斷處理函數(shù)
static irqreturn_t kpd_irq_handler(int irq, void *dev_id)
{
/* use _nosync to avoid deadlock */
disable_irq_nosync(kp_irqnr);//禁止中斷,無需進(jìn)行同步,防止死鎖
tasklet_schedule(&kpd_keymap_tasklet);//調(diào)度tasklet
return IRQ_HANDLED;
}
分析:先disable中斷(防止死鎖),在調(diào)度tasklet。
補(bǔ)充知識(shí):
######中斷處理的 tasklet 機(jī)制
中斷服務(wù)程序一般都是在中斷請(qǐng)求關(guān)閉的條件下執(zhí)行的,
以避免嵌套而使中斷控制復(fù)雜化。(具體機(jī)制就不是本文重點(diǎn)啦)
######tasklet 使用方法
/*用靜態(tài)方式聲明并定義一個(gè)tasklet,動(dòng)態(tài)方式一樣*/
DECLARE_TASKLET(my_tasklet,&tasklet_func,0);
/*直接調(diào)度我們的函數(shù)*/
tasklet_schedule(&my_tasklet);
因此,跟蹤DECLARE_TASKLET()方法
/* for keymap handling */
static void kpd_keymap_handler(unsigned long data);
static DECLARE_TASKLET(kpd_keymap_tasklet, kpd_keymap_handler, 0);
分析:可以看到,中斷服務(wù)程序里面執(zhí)行tasklet_schedule(&kpd_keymap_tasklet);實(shí)際上會(huì)去調(diào)用這個(gè)函數(shù)kpd_keymap_handler()
static void kpd_keymap_handler(unsigned long data)
{
int i, j;
bool pressed;
u16 new_state[KPD_NUM_MEMS], change, mask;
u16 hw_keycode, linux_keycode;
//mtk通過5組寄存器來保存按鍵的狀態(tài),這里回讀寄存器并保存為new_state
kpd_get_keymap_state(new_state);
//激活鎖喚醒系統(tǒng),500ms后就釋放掉
wake_lock_timeout(&kpd_suspend_lock, HZ / 2);
for (i = 0; i < KPD_NUM_MEMS; i++) {
//每組中按鍵狀態(tài)未改變則對(duì)比下一組,按位處理
change = new_state[i] ^ kpd_keymap_state[i];
if (!change)
continue;
for (j = 0; j < 16; j++) {
//每組(16位)中對(duì)比按位查看是否狀態(tài)發(fā)生改變
mask = 1U << j;
if (!(change & mask))
continue;
hw_keycode = (i << 4) + j;
/* bit is 1: not pressed, 0: pressed */
//按鍵是否按下,寄存器中0表示按鍵處于按下狀態(tài)
pressed = !(new_state[i] & mask);
if (kpd_show_hw_keycode)
kpd_print("(%s) HW keycode = %u\n", pressed ?
"pressed" : "released", hw_keycode);
BUG_ON(hw_keycode >= KPD_NUM_KEYS);
linux_keycode = kpd_keymap[hw_keycode];
if (unlikely(linux_keycode == 0)) {
kpd_print("Linux keycode = 0\n");
continue;
}
kpd_aee_handler(linux_keycode, pressed);
//上報(bào)鍵值
input_report_key(kpd_input_dev, linux_keycode, pressed);
//同步用于告訴input core子系統(tǒng)報(bào)告結(jié)束
input_sync(kpd_input_dev);
kpd_print("report Linux keycode = %u\n", linux_keycode);
}
}
//kpd_keymap_state保存new_state,用于下輪對(duì)比
memcpy(kpd_keymap_state, new_state, sizeof(new_state));
kpd_print("save new keymap state\n");
//按鍵處理完畢,打開中斷
enable_irq(kp_irqnr);
}
分析:該函數(shù)中,最重要的是
//向INPUT子系統(tǒng)上報(bào)鍵值
input_report_key(kpd_input_dev, linux_keycode, pressed);
//同步用于告訴input core子系統(tǒng)報(bào)告結(jié)束
input_sync(kpd_input_dev);
也就是上篇文章中抓取到的adb信息,因?yàn)榘存I按下到松開,會(huì)產(chǎn)生2次中斷,因此會(huì)調(diào)用2次
按鍵按下
0001 0072 00000001//input_report_key();
0000 0000 00000000 // input_sync(kpd_input_dev);
按鍵松開
0001 0072 00000000//input_report_key();
0000 0000 00000000// input_sync(kpd_input_dev);
接下來,就通過input子系統(tǒng)一層層調(diào)用,最終調(diào)用input_event_to_user()上報(bào)到用戶空間。
我們來跟蹤以下具體流程
路徑:kernel-3.18/include/linux/input.h
static inline void input_report_key(struct input_dev *dev,
unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
可以看到,無論是input_report_key還是input_sync,都會(huì)調(diào)用input_event繼續(xù)上報(bào)
路徑:kernel-3.18/drivers/input/input.c
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
//判斷是否支持此種事件類型和事件類型中的編碼類型
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);//內(nèi)核鎖
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);//內(nèi)核鎖
}
}
EXPORT_SYMBOL(input_event);
分析,可以看到,代碼又繼續(xù)調(diào)用了input_handle_event()進(jìn)一步上報(bào)數(shù)值,
路徑:kernel-3.18/drivers/input/input.c
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition;
//獲得事件處理者身份
disposition = input_get_disposition(dev, type, code, &value);
//如果事件處理者身份為INPUT_PASS_TO_HANDLERS表示交給input hardler處理
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (!dev->vals)
return;
//如果事件處理者身份為INPUT_PASS_TO_DEVICE表示交給input device處理
if (disposition & INPUT_PASS_TO_HANDLERS) {
struct input_value *v;
if (disposition & INPUT_SLOT) {
v = &dev->vals[dev->num_vals++];
v->type = EV_ABS;
v->code = ABS_MT_SLOT;
v->value = dev->mt->slot;
}
v = &dev->vals[dev->num_vals++];
v->type = type;
v->code = code;
v->value = value;
}
//如果事件處理者身份為INPUT_FLUSH表示需要handler立即處理
if (disposition & INPUT_FLUSH) {
if (dev->num_vals >= 2)
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
} else if (dev->num_vals >= dev->max_vals - 2) {
dev->vals[dev->num_vals++] = input_value_sync;
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
}
}
分析:input_get_disposition()獲得事件處理者身份。
input_handle_event函數(shù)向輸入子系統(tǒng)傳送事件信息,
參數(shù)1是輸入設(shè)備input_dev,參數(shù)2是事件類型,
參數(shù)3是鍵碼,參數(shù)4是鍵值。
input_get_disposition()確定事件的處理方式,返回值主要有以下幾種:
INPUT_IGNORE_EVENT:表示忽略事件,不進(jìn)行處理。
INPUT_PASS_TO_HANDLERS:表示事件交給handler處理。
INPUT_PASS_TO_DEVICE:表示將事件交給input_dev處理。
INPUT_PASS_TO_ALL:表示將事件交給handler和 input_dev共同處理。
那到底返回的disposition是啥的?那就的繼續(xù)跟一下
input_get_disposition()函數(shù)去看
路徑:kernel-3.18/drivers/input/input.c
static int input_get_disposition(struct input_dev *dev,
unsigned int type, unsigned int code, int *pval)
{
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break;
//傳進(jìn)來的參數(shù)是:EV_SYN, SYN_REPORT,因此 代碼會(huì)走這里
case SYN_REPORT:
disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH;
break;
case SYN_MT_REPORT:
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
break;
case EV_KEY:
if (is_event_supported(code, dev->keybit, KEY_MAX)) {
/* auto-repeat bypasses state updates */
if (value == 2) {
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
if (!!test_bit(code, dev->key) != !!value) {
__change_bit(code, dev->key);
disposition = INPUT_PASS_TO_HANDLERS;
}
}
break;
case EV_SW :
//..........省略..................
分析:從前面的分析,每上報(bào)一個(gè)事件,就會(huì)調(diào)用input_sync,告訴Input子系統(tǒng)事件上報(bào)完畢,
可以看到,傳的參數(shù)是EV_SYN, SYN_REPORT,0
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
所以 最后的 disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH;
從input_handle_event(),可以看出,會(huì)接著去調(diào)用
input_pass_values()繼續(xù)上報(bào)
路徑:kernel-3.18/drivers/input/input.c
static void input_pass_values(struct input_dev *dev,
struct input_value *vals, unsigned int count)
{
struct input_handle *handle;
struct input_value *v;
if (!count)
return;
rcu_read_lock();
handle = rcu_dereference(dev->grab);
if (handle) {//如果是綁定的handle,則調(diào)用綁定的handler->event函數(shù)
// 調(diào)用input_to_handler,進(jìn)行事件的處理
count = input_to_handler(handle, vals, count);
} else {
//如果沒有綁定,則遍歷dev的h_list鏈表,尋找handle,
//如果handle已經(jīng)打開,說明有進(jìn)程讀取設(shè)備關(guān)聯(lián)的evdev。
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open)
// 調(diào)用input_to_handler,進(jìn)行事件的處理
count = input_to_handler(handle, vals, count);
}
rcu_read_unlock();
//.......................省略...................................
}
分析:input_pass_values()函數(shù)用于確定input_dev的handler,并通過input_to_handler()調(diào)用handler的event函數(shù)。在上述代碼中,rcu_dereference(),該接口用來獲取RCU protected pointer。reader要訪問RCU保護(hù)的共享數(shù)據(jù),當(dāng)然要獲取RCU protected pointer,然后通過該指針進(jìn)行dereference的操作。dev->grab是強(qiáng)制為input device的handler,如果rcu_dereference()函數(shù)返回值不為空,說明有為input_device強(qiáng)制指定handler,就直接調(diào)用handler的event函數(shù)。如果為NULL,表示沒有為input_device強(qiáng)制指定handler,就會(huì)通過遍歷input device->h_list上的handle成員。如果該handle被打開,表示該設(shè)備已經(jīng)被一個(gè)用戶進(jìn)程使用,就會(huì)調(diào)用與輸入設(shè)備對(duì)應(yīng)的handler的event函數(shù)。
注:只有在handle被打開的情況下才會(huì)接收到事件,這就是說,只有設(shè)備被用戶程序使用時(shí),才有必要向用戶空間導(dǎo)出信息。
接下來會(huì)去調(diào)用input_to_handler()繼續(xù)上報(bào)
路徑:kernel-3.18/drivers/input/input.c
static unsigned int input_to_handler(struct input_handle *handle,
struct input_value *vals, unsigned int count)
{
struct input_handler *handler = handle->handler;
struct input_value *end = vals;
struct input_value *v;
//通過過濾器進(jìn)行事件過濾
for (v = vals; v != vals + count; v++) {
if (handler->filter &&
handler->filter(handle, v->type, v->code, v->value))
continue;
if (end != v)
*end = *v;
end++;
}
count = end - vals;
if (!count)
return 0;
if (handler->events)
//繼續(xù)上報(bào)數(shù)據(jù)
handler->events(handle, vals, count);
else if (handler->event)
for (v = vals; v != end; v++)
handler->event(handle, v->type, v->code, v->value);
return count;
}
分析:首先會(huì)通過handler->filter去過濾事件,接著調(diào)用 handler->event()【handle->handler->event()】或者h(yuǎn)andler->events繼續(xù)上報(bào),那這個(gè)event()或者events()函數(shù)在哪定義呢
那就得看input_handler 結(jié)構(gòu)體
路徑:kernel-3.18/drivers/input/evdev.c

路徑:kernel-3.18/drivers/input/evdev.c
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct input_value vals[] = { { type, code, value } };
//可以看到 evdev_event調(diào)用了evdev_events
evdev_events(handle, vals, 1);
}
static void evdev_events(struct input_handle *handle,
const struct input_value *vals, unsigned int count)
{
//......省略代碼
rcu_read_lock();
client = rcu_dereference(evdev->grab);
if (client)//如果evdev綁定了client那么,處理這個(gè)客戶端
evdev_pass_values(client, vals, count, time_mono, time_real);
else
//遍歷client鏈表,調(diào)用evdev_pass_values函數(shù)
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_values(client, vals, count,
time_mono, time_real);
rcu_read_unlock();
//...........省略代碼.................
}
分析:從代碼中可以看到evdev_event()去調(diào)用了evdev_events(),
所以最終都走evdev_events()方法,該方法里面又去evdev_pass_values()進(jìn)一步上報(bào)數(shù)據(jù)
路徑:kernel-3.18/drivers/input/evdev.c
static void evdev_pass_values(struct evdev_client *client,
const struct input_value *vals, unsigned int count,
ktime_t mono, ktime_t real)
{
//.....................省略........................
/* Interrupts are disabled, just acquire the lock. */
spin_lock(&client->buffer_lock);
for (v = vals; v != vals + count; v++) {
event.type = v->type;
event.code = v->code;
event.value = v->value;
__pass_event(client, &event);
if (v->type == EV_SYN && v->code == SYN_REPORT)
wakeup = true;
}
spin_unlock(&client->buffer_lock);
if (wakeup)
wake_up_interruptible(&evdev->wait);
}
分析:先調(diào)用__pass_event()進(jìn)一步上報(bào)數(shù)據(jù),然后調(diào)用wake_up_interruptible喚醒中斷
路徑:kernel-3.18/drivers/input/evdev.c
static void __pass_event(struct evdev_client *client,
const struct input_event *event)
{
client->buffer[client->head++] = *event;
client->head &= client->bufsize - 1;
if (unlikely(client->head == client->tail)) {
/*
* This effectively "drops" all unconsumed events, leaving
* EV_SYN/SYN_DROPPED plus the newest event in the queue.
*/
client->tail = (client->head - 2) & (client->bufsize - 1);
/*將event裝入client的buffer中,buffer是一個(gè)環(huán)形緩存區(qū)*/
client->buffer[client->tail].time = event->time;
client->buffer[client->tail].type = EV_SYN;
client->buffer[client->tail].code = SYN_DROPPED;
client->buffer[client->tail].value = 0;
client->packet_head = client->tail;
if (client->use_wake_lock)
wake_unlock(&client->wake_lock);
}
}
分析:到這里,最終將事件傳遞給了用戶端的client結(jié)構(gòu)中buffer中,buffer是一個(gè)環(huán)形緩存區(qū),等待用戶空間來讀取
讀取大致過程:先調(diào)用evdev_open_device打開設(shè)備,然后調(diào)用evdev_read去讀取
路徑:kernel-3.18/drivers/input/evdev.c
static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
/*這個(gè)就是剛才在open函數(shù)中*/
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
int retval;
if (count < input_event_size())
return -EINVAL;
/*如果client的環(huán)形緩沖區(qū)中沒有數(shù)據(jù)并且是非阻塞的,那么返回-EAGAIN,
也就是try again*/
if (client->head == client->tail && evdev->exist &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
/*如果沒有數(shù)據(jù),并且是阻塞的,則在等待隊(duì)列上等待吧*/
retval = wait_event_interruptible(evdev->wait,
client->head != client->tail || !evdev->exist);
if (retval)
return retval;
if (!evdev->exist)
return -ENODEV;
/*如果獲得了數(shù)據(jù)則取出來,調(diào)用evdev_fetch_next_event*/
while (retval + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
/*input_event_to_user調(diào)用copy_to_user傳入用戶程序中,這樣讀取完成*/
if (input_event_to_user(buffer + retval, &event))
return -EFAULT;
retval += input_event_size();
}
return retval;
}
路徑:kernel-3.18/drivers/input/input-compat.c
int input_event_to_user(char __user *buffer,
const struct input_event *event)
{
if (INPUT_COMPAT_TEST && !COMPAT_USE_64BIT_TIME) {
struct input_event_compat compat_event;
compat_event.time.tv_sec = event->time.tv_sec;
compat_event.time.tv_usec = event->time.tv_usec;
compat_event.type = event->type;
compat_event.code = event->code;
compat_event.value = event->value;
if (copy_to_user(buffer, &compat_event,
sizeof(struct input_event_compat)))
return -EFAULT;
} else {
if (copy_to_user(buffer, event, sizeof(struct input_event)))
return -EFAULT;
}
return 0;
}
分析:該函數(shù)中,最終調(diào)用input_event_to_use()->copy_to_user()把數(shù)據(jù)拷貝到用戶空間,到此按鍵上報(bào)事件內(nèi)核流程分析就結(jié)束了。
總結(jié)

總結(jié)一下事件的傳遞過程:
首先在驅(qū)動(dòng)層中,調(diào)用inport_report_key和input_sync,
然后他調(diào)用了input core層的input_event,
input_event調(diào)用了input_handle_event對(duì)事件進(jìn)行分派,調(diào)用input_pass_event,
在這里他會(huì)把事件傳遞給具體的handler層,
然后在相應(yīng)handler的event處理函數(shù)中,封裝一個(gè)event,
然后把它投入evdev的那個(gè)client_list上的client的事件buffer中,
等待用戶空間來讀取。
最后通過evdev_read->input_event_to_user->copy_to_user把數(shù)據(jù)拷貝到用戶空間
調(diào)用函數(shù)如下:
input_event()->input_handle_event() ->input_pass_values()
->input_to_handler->handle->handler->event(handle,type, code, value)
->evdev_events() ->evdev_pass_values() ->__pass_event()
把數(shù)據(jù)存在客戶端的buffer中,等待讀取
最后evdev_read->input_event_to_user->copy_to_user
拷貝到用戶空間
總體來說:
Driver(設(shè)備驅(qū)動(dòng)層)->Inputcore(輸入子系統(tǒng)核心層)
->Event handler(事件處理層)->userspace(用戶空間)
Stay hungry,Stay foolish!
荊軻刺秦王
參考文檔:
https://blog.csdn.net/u010142953/article/details/46680529
https://www.linuxidc.com/Linux/2011-09/43187p5.htm
https://blog.csdn.net/wh8_2011/article/details/51678336
https://blog.csdn.net/sdkdlwk/article/details/71108226
https://blog.csdn.net/hb9312z/article/details/78414334
http://www.cnblogs.com/myblesh/articles/2367648.html
https://blog.csdn.net/u013604527/article/details/53432623
http://www.cnblogs.com/ant-man/p/9204977.html
https://blog.csdn.net/u010177751/article/details/38520245
https://blog.csdn.net/liyanfei123456/article/details/53196693