高通平台按键驱动代码分析

一、Input输入子系统架构

高通平台按键驱动代码分析

Input Driver(Input设备驱动层)->Input core(输入子系统核心层)->Event handler(事件处理层)->User space(用户空间)

 

二、高通平台PMIC_RESIN按键上报方式

高通平台将音量下键绑在PMIC_RESIN上,这样长按电源键重启外,还可以按音量下键重启。

1、Adb监听按键事件:

adb>adb shell

msm8937_64:/ $ getevent

getevent

。。

add device 3: /dev/input/event0

  name:     "qpnp_pon"

add device 5: /dev/input/event3

  name:     "soc:gpio_keys"

/dev/input/event3: 0001 0073 00000001  //上键用的是event3(音量上键用的是gpio-key方式)

/dev/input/event3: 0000 0000 00000000

/dev/input/event3: 0001 0073 00000000

/dev/input/event3: 0000 0000 00000000

/dev/input/event0: 0001 0072 00000001  //下键用的是event0(音量下键绑在PMIC_RESIN)

/dev/input/event0: 0000 0000 00000000

/dev/input/event0: 0001 0072 00000000

/dev/input/event0: 0000 0000 00000000

2、代码分析

(1)kernel dtsi配置

\kernel\msm-4.9\arch\arm64\boot\dts\qcom\pm8916.dtsi

                 pm8916_pon: qcom,[email protected] {

                         compatible = "qcom,qpnp-power-on";

                         reg = <0x800 0x100>;

                         interrupts = <0x0 0x8 0x0 IRQ_TYPE_NONE>,

                                       <0x0 0x8 0x1 IRQ_TYPE_NONE>;

                         interrupt-names = "kpdpwr", "resin";

                         qcom,pon-dbc-delay = <15625>;

                         qcom,system-reset;

                         qcom,clear-warm-reset;

                         qcom,store-hard-reset-reason;

 

                         qcom,pon_1 {

                                  qcom,pon-type = <0>;

                                  qcom,support-reset = <1>;

                                  qcom,pull-up = <1>;

                                 qcom,s1-timer = <10256>;

                                  qcom,s2-timer = <2000>;

                                  qcom,s2-type = <1>;

                                  linux,code = <116>;  //0x74,Power键

                         };

 

                         qcom,pon_2 {

                                  qcom,pon-type = <1>;

                                  qcom,pull-up = <1>;

                                  linux,code = <114>;//0x72,音量下键

                         };

                 }

pon-type就是复位源,linux,code就是对应的按键,116是电源键,114是音量下键。

(2) \kernel\msm-4.9\drivers\input\misc\qpnp-power-on.c

qpnp_pon_probe()

-->qpnp_pon_config_init()

-->qpnp_pon_request_irqs()

        switch (cfg->pon_type) {

        case PON_KPDPWR:

                 rc = devm_request_irq(&pon->pdev->dev, cfg->state_irq,

                                                           qpnp_kpdpwr_irq,

                                  IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,

                                                  "qpnp_kpdpwr_status", pon);

                 break;

        case PON_RESIN:

                 rc = devm_request_irq(&pon->pdev->dev, cfg->state_irq,

                                                           qpnp_resin_irq,

                                  IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,

                                                  "qpnp_resin_status", pon);

 

cfg->pon_type 就是dtsi定义的qcom,pon-type 值。

Power键0:PON_KPDPWR,

音量下键1:PON_RESIN,

enum pon_type {

        PON_KPDPWR,

        PON_RESIN,

        PON_CBLPWR,

        PON_KPDPWR_RESIN,

};

-->qpnp_resin_irq()  //音量下键中断处理函数

        rc = qpnp_pon_input_dispatch(pon, PON_RESIN);

 

-->qpnp_pon_input_dispatch()

//按power键

[32m[   60.841928] [31m: PMIC input: code=114, sts=0x2  //音量下键按下

[32m[   61.017427] [31m: PMIC input: code=114, sts=0x0  //音量下键放开

//按音量下键

[32m[   61.969266] [31m: PMIC input: code=116, sts=0x1  //power键按下

[32m[   62.128491] [31m: PMIC input: code=116, sts=0x0  //power键放开

//power+音量下键,截屏功能

[32m[  782.773798] [31m: PMIC input: code=114, sts=0x2

[32m[  782.849212] [31m: PMIC input: code=116, sts=0x3

[32m[  784.655553] [31m: PMIC input: code=114, sts=0x1

[32m[  784.719879] [31m: PMIC input: code=116, sts=0x0

 

input_report_key(pon->pon_input, cfg->key_code, key_status);  //INPUT子系统上报键值

input_sync(pon->pon_input); //同步用于告诉input core子系统报告结束

因为按键按下到松开,会产生2次中断,因此会调用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);

(3) \kernel\msm-4.9\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_event()函数函数处理同下面GPIO按键方式,具体详见下面。

三、GPIO按键上报方式

1、代码分析

(1)kernel dtsi配置:

\kernel\msm-4.9\arch\arm64\boot\dts\qcom\qm215-qrd.dtsi

&soc {

        gpio_keys {

                 compatible = "gpio-keys";

                 input-name = "gpio-keys";

                 pinctrl-names = "default";

                 pinctrl-0 = <&gpio_key_active>;

 

                 vol_up {

                         label = "volume_up";

                         gpios = <&tlmm 91 0x1>;

                         linux,input-type = <1>;

                         linux,code = <115>;  //0x73,音量上键

                         debounce-interval = <15>;

                         linux,can-disable;

                         gpio-key,wakeup;

                 };

        };

驱动解析这个设备树配置的代码:\kernel\msm-4.9\drivers\input\keyboard\gpio_keys.c

(2)\kernel\msm-4.9\drivers\input\keyboard\gpio_keys.c

-->gpio_keys_probe()

    gpio_keys_get_devtree_pdata(dev);//解析dts

    gpio_keys_setup_key(pdev, input, bdata, button);

    input_register_device(input); //注册device

--> gpio_keys_setup_key()

[32m[    4.912838] [33mgpio-keys soc:gpio_keys[31m: gpio_keys_setup_key to request GPIO 91,gpio_keys_gpio_isr;desc=volume_up

        if (gpio_is_valid(button->gpio)) {

                 INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);

                 isr = gpio_keys_gpio_isr;

--> gpio_keys_gpio_work_func()

        gpio_keys_gpio_report_event(bdata);

--> gpio_keys_gpio_report_event()

        if (type == EV_ABS) {

                 if (state)

                         input_event(input, type, button->code, button->value);

        } else {

                 input_event(input, type, button->code, state);

        }

        input_sync(input);

(3)\kernel\msm-4.9\drivers\input\input.c

-->input_event() \kernel\msm-4.9\drivers\input\input.c  (power key和音量下键也最终调用到这个函数)

--> input_handle_event()  

int disposition = input_get_disposition(dev, type, code, &value);

        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;

        }

        if (disposition & INPUT_FLUSH) {  //等到input_sync才调用input_pass_values

                 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;

        }

如下按上键log

[  103.651716] [31m: input_get_disposition  type= 1; code =115

[  103.651754] [31m: input_handle_event  disposition= 0x1;   // INPUT_PASS_TO_HANDLERS

[  103.656441] [33minput[31m: input_get_disposition  type= 0; code =0 

[   67.631716] [31m: input_handle_event:type=1;code=115  // type= EV_KEY; key code=0x73

[  67.631747] [31m: input_handle_event: disposition=0x1; values=1  // disposition =INPUT_PASS_TO_HANDLERSvalues=1按键按下

[   67.635746] [31m: input_handle_event:dev->vals=1; dev->max_vals=10

[   67.642163] [31m: input_handle_event:type=0;code=0  / type =EV_SYNcode =SYN_REPORT input_sync函数调用的。

[   67.647117] [31m: input_handle_event: disposition=0x9; values=0 

[   67.651806] [31m: input_handle_event:dev->vals=2; dev->max_vals=10

[   67.940863] [31m: input_handle_event:type=1;code=115

[   67.940893][31m: input_handle_event: disposition=0x1; values=0  // values=0放开按键

[   67.944892] [31m: input_handle_event:dev->vals=1; dev->max_vals=10

[   67.951346] [31m: input_handle_event:type=0;code=0

[   67.956263] [31m: input_handle_event: disposition=0x9; values=0

[   67.960956] [31m: input_handle_event:dev->vals=2; dev->max_vals=10

 

对应下面4条信息:

adb>adb shell

msm8937_64:/ $ getevent

getevent

add device 3: /dev/input/event0

  name:     "qpnp_pon"

add device 5: /dev/input/event3

  name:     "soc:gpio_keys"

/dev/input/event3: 0001 0073 00000001  //上键用的是event3(上键用的是gpio-key方式),按下

/dev/input/event3: 0000 0000 00000000 // input_sync,同步用于告诉input core子系统报告结束

/dev/input/event3: 0001 0073 00000000  //按键松开

/dev/input/event3: 0000 0000 00000000  // input_sync,

 

#define INPUT_IGNORE_EVENT     0

#define INPUT_PASS_TO_HANDLERS       1

#define INPUT_PASS_TO_DEVICE   2

#define INPUT_SLOT               4

#define INPUT_FLUSH            8

#define INPUT_PASS_TO_ALL          (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)

 

\kernel\msm-4.9\include\uapi\linux\ input-event-codes.h

/* * Event types */

#define EV_SYN                        0x00

#define EV_KEY                        0x01

#define EV_REL                        0x02

#define EV_ABS                        0x03

#define EV_MSC                       0x04

#define EV_SW                         0x05

#define EV_LED                        0x11

#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

#define EV_CNT                       (EV_MAX+1)

 

/* * Synchronization events. */

#define SYN_REPORT              0

#define SYN_CONFIG              1

#define SYN_MT_REPORT                2

#define SYN_DROPPED                     3

#define SYN_MAX                              0xf

#define SYN_CNT                               (SYN_MAX+1)

-->input_pass_values() \kernel\msm-4.9\drivers\input\input.c

           handle = rcu_dereference(dev->grab);

        if (handle) { {//如果是绑定的handle,则调用绑定的handler->event函数调用input_to_handler,进行事件的处理

                     count = input_to_handler(handle, vals, count);

           } else { //如果没有绑定,则遍历dev的h_list链表,寻找handle,如果handle已经打开,说明有进程读取设备关联的evdev。  

                     list_for_each_entry_rcu(handle, &dev->h_list, d_node)

                                if (handle->open) {

                                          count = input_to_handler(handle, vals, count);

                                          if (!count)

                                                     break;

                                }

           }

只有在handle被打开的情况下才会接收到事件,接下来会去调用input_to_handler()继续上报

--> input_to_handler()

           if (handler->events)

                     handler->events(handle, vals, count);  //实际调用这边

           else if (handler->event)

                     for (v = vals; v != vals + count; v++)

                                handler->event(handle, v->type, v->code, v->value);

这个event()或者events()函数在哪定义呢,看input_handler 结构体。

(4)\kernel\msm-4.9\drivers\input\evdev.c

static struct input_handler evdev_handler = {

           .event                    = evdev_event,

           .events                  = evdev_events,

           .connect     = evdev_connect,

           .disconnect           = evdev_disconnect,

           .legacy_minors    = true,

           .minor                    = EVDEV_MINOR_BASE,

           .name                    = "evdev",

           .id_table     = evdev_ids,

};

-->evdev_pass_values()  \kernel\msm-4.9\drivers\input\evdev.c

                     event.type = v->type;

                     event.code = v->code;

                     event.value = v->value;

                     __pass_event(client, &event);

           if (wakeup)

                     wake_up_interruptible(&evdev->wait);

先调用__pass_event()进一步上报数据,然后调用wake_up_interruptible唤醒中断

-->__pass_event()

           client->buffer[client->head++] = *event;

最终将事件传递给了用户端的client结构中buffer中,buffer是一个环形缓存区,等待用户空间来读取;

 

(5)读取流程

读取大致过程:先调用evdev_open_device打开设备,然后调用evdev_read去读取。

-->evdev_read()

           for (;;) {

                     if (!evdev->exist || client->revoked)

                                return -ENODEV;

 

                     if (client->packet_head == client->tail &&

                         (file->f_flags & O_NONBLOCK))

                                return -EAGAIN;

 

                     /*

                      * count == 0 is special - no IO is done but we check

                      * for error conditions (see above).

                      */

                     if (count == 0)

                                break;

 

                     while (read + input_event_size() <= count &&

                            evdev_fetch_next_event(client, &event)) {

 

                                if (input_event_to_user(buffer + read, &event))

                                          return -EFAULT;

 

                                read += input_event_size();

                     }

 

                     if (read)

                                break;

 

                     if (!(file->f_flags & O_NONBLOCK)) {

                                error = wait_event_interruptible(evdev->wait,

                                                     client->packet_head != client->tail ||

                                                     !evdev->exist || client->revoked);

                                if (error)

                                          return error;

                     }

-->input_event_to_user()- kernel\msm-4.9\drivers\input\input-compat.c

copy_to_user(buffer, event, sizeof(struct input_event))

最终调用input_event_to_use()->copy_to_user()把数据拷贝到用户空间,到此按键上报事件内核流程分析就结束了。

 

总的来说,调用函数如下:

 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()

把数据存在客户端的buffer中,等待读取。

最后evdev_read->input_event_to_user->copy_to_user拷贝到用户空间