Android 按鍵處理二(按鍵流程之內(nèi)核層篇)

前言:上一篇中,我們掌握一些必要的基礎(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
input_handler 結(jié)構(gòu)體
路徑: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é)

圖1
總結(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容