高通平台按键驱动代码分析
一、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_HANDLERS;values=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_SYN,code =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拷贝到用户空间