___2017

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
  328 随笔 :: 18 文章 :: 15 评论 :: 18万 阅读
受不了xxxx恶心人的行为,遂搬迁至博客园。
始发:2016-12-15 22:19:01
 
 
版本信息:
Linux:3.10
Android: 4.4

  

一、唤醒源

设备休眠后,通过触发唤醒源使设备恢复正常工作模式。设备唤醒源有多种,对于Android设备常见的就有PowerKey、来电唤醒、Alarm唤醒等。
唤醒源的实现处于内核空间,本文重点讨论下PowerKey作为唤醒源的具体实现。

二、PowerKey唤醒源

PowerKey唤醒设备的原理,本质其实就是中断。

PowerKey连接到CPU的一个输入(Input)引脚(Pin)上,该Pin运行在中断模式上。一旦PowerKey按下,引发Pin中断;而该中断具有唤醒CPU的功能,于是设备得以唤醒。

三、PowerKey对应的Pin Configuration

和PowerKey相连的Pin的具体配置位于板级dts文件中,比如如下配置:

arch/arm/boot/dts/xxxxx.dts
power-key {
        /** 是CPU的哪个Pin */
        gpios = <&gpio0 GPIO_A5 GPIO_ACTIVE_LOW>;
        /** Key code  */
        linux,code = <116>;
        /** 起个名字 */
        label = "power";
        /** 该Pin具有wakeup的功能 */
        gpio-key,wakeup;
};

 

着重说下linux,code = <116>,116怎么来的?
对于键盘,每一个按键都有唯一的编码,在Linux中,编码值位于:
1
2
3
4
5
6
7
8
9
input.h (kernel\include\uapi\linux)
/*
 * Keys and buttons
 */
#define KEY_RESERVED    0
#define KEY_ESC     1
#define KEY_BACKSPACE   14
#define KEY_TAB     15
#define KEY_POWER   116 /* SC System Power Down */

可知,PowerKey的编码也在该文件中,且编码值为116;一旦按下PowerKey,该值作为键值传到input_event结构体的code成员变量中:

1
2
3
4
5
6
7
8
9
10
11
input.h (kernel\include\uapi\linux)
/*
 * The event structure itself
 */
  
struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};
之后我们会写个Linux应用程序读取code值。

 

四、PowerKey驱动

1、PowerKey驱动注册

在我的板上,PowerKey驱动是按照platform_device注册的,设备驱动:
1
2
3
4
5
6
7
8
9
10
11
12
static struct platform_driver keys_device_driver = {
    .probe      = keys_probe,
    .remove     = keys_remove,
    .driver     = {
        .name   = "xxx-keypad",
        .owner  = THIS_MODULE,
        .of_match_table = xxx_key_match,
#ifdef CONFIG_PM
        .pm = &keys_pm_ops,
#endif
    }
};

注册为平台驱动:

1
module_platform_driver(keys_device_driver);

这里遇到了“新伙伴”:之前驱动注册时调用的是“module_init/module_exit”宏,PowerKey驱动注册用“module_platform_driver”,什么鬼?看下宏注释:

1
2
3
4
5
6
7
8
/* module_platform_driver() - Helper macro for drivers that don't do
 * anything special in module init/exit.  This eliminates(清除/淘汰) a lot of
 * boilerplate(样板文件).  Each module may only use this macro once, and
 * calling it replaces module_init() and module_exit()
 */
#define module_platform_driver(__platform_driver) \
    module_driver(__platform_driver, platform_driver_register, \
            platform_driver_unregister)

我们并不需要在“module_init/module_exit”宏规定的函数中做什么工作,使用这种方式(注册驱动的模版)注册驱动的话就得准备xxx_init/xxx_exit函数,而采用“module_platform_driver”注册就免去了这些无用功。 

2、PowerKey驱动实现

贯穿始终的连个结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * 描述Key具有的属性
 */
struct xxx_keys_button {
    u32 code;  // key code
    const char *desc;//key label
    u32 state; //key up & down state
    int gpio;
    int active_low;
    int wakeup;
    struct timer_list timer;
};
  
/**
 * 驱动属性封装
 */
struct xxx_keys_drvdata {
    int nbuttons;
    bool in_suspend;    /* Flag to indicate if we're suspending/resuming */
    int result;
    struct input_dev *input;
    struct xxx_keys_button button[0];
};

  

(1)驱动从xxx_probe()函数起始,注意代码的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// 省略异常处理代码
static int keys_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct device_node *np = pdev->dev.of_node;
    struct xxx_keys_drvdata *ddata = NULL;
    struct input_dev *input = NULL;
    int i, error = 0;
    int wakeup, key_num = 0;
  
    // 1、of_get_child_count: 获取pin configuration的数目
    key_num = of_get_child_count(np);
  
    // 2、为xxx_keys_drvdata 分配空间
    ddata = devm_kzalloc(dev, sizeof(struct xxx_keys_drvdata) +
        key_num * sizeof(struct xxx_keys_button), GFP_KERNEL);
     
    // 3、PowerKey是作为Input设备进行注册的,这里为PowerKey分配Input设备空间
    input = devm_input_allocate_device(dev);
  
    platform_set_drvdata(pdev, ddata);
  
    // input->name:设备名字,可以通过cat /sys/class/input/eventX/device/name查看
    input->name = "xxx-keypad";
    input->dev.parent = dev;
  
    input->id.bustype = BUS_HOST; // 总线类型
    input->id.vendor = 0x0001;
    input->id.product = 0x0001;
    input->id.version = 0x0100;
    ddata->input = input;
  
    ddata->nbuttons = key_num;
    // 4、解析之前的dts文件
    error = xxx_keys_parse_dt(ddata, pdev);
  
    struct xxx_keys_button *button = &ddata->button[i];
    // 6、code = 116
    if (button->code){
        setup_timer(&button->timer,
                keys_timer, (unsigned long)button);}
  
    // 7、解析dts文件的时候赋值,此处非0
    if (button->wakeup)
        wakeup = 1;
     
    // 8、__set_bit(code, input->keybit); input->keybit: 存放PowerKey键值
    input_set_capability(input, EV_KEY, button->code);
  
    struct xxx_keys_button *button = &ddata->button[i];
    int irq;
    // 9、->desc:解析dts文件的时候赋值,devm_gpio_request()申请GPIO
    error = devm_gpio_request(dev, button->gpio, button->desc ?: "keys");
    // 10、PowerKey相连的Pin为输入模式
    error = gpio_direction_input(button->gpio);
    // 11、设置为中断Pin并获取中断号irq
    irq = gpio_to_irq(button->gpio);
  
    /**keys_isr:中断Handler
     * 中断触发方式:IRQF_TRIGGER_FALLING下降沿、IRQF_TRIGGER_RISING上升沿
     */
    error = devm_request_irq(dev, irq, keys_isr,
        (button->active_low)?IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING,
        button->desc ? button->desc : "keys", button);
    }
  
    // 存放KEY_WAKEUP键值
    input_set_capability(input, EV_KEY, KEY_WAKEUP);
    // 12、wakeup非0则启用唤醒CPU功能
    device_init_wakeup(dev, wakeup);
  
    // 注册Input驱动
    error = input_register_device(input);
  
    return error;
  
 fail2:
    device_init_wakeup(dev, 0);
 fail1:
    while (--i >= 0) {
        del_timer_sync(&ddata->button[i].timer);
    }
 fail0:
    platform_set_drvdata(pdev, NULL);
  
    return error;
}

这里完成:

  • 数据成员空间分配
  • 数据成员初始化
  • dts文件中PowerKey配置解析
  • Input设备驱动注册
  • 启用唤醒功能
  • 作为唤醒源的中断ISR注册

(2)解析dts文件中PowerKey配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 解析dts文件中PowerKey配置
static int xxx_keys_parse_dt(struct xxx_keys_drvdata *pdata, struct platform_device *pdev)
{
    struct device_node *node = pdev->dev.of_node;
    struct device_node *child_node;
    int ret, gpio, i =0;
    u32 code, flags;;
  
    if(of_property_read_u32(child_node, "linux,code", &code)) {
        dev_err(&pdev->dev, "Missing linux,code property in the DT.\n");
        ret = -EINVAL;
        goto error_ret;
    }
    pdata->button[i].code = code; // 116
    pdata->button[i].desc = of_get_property(child_node, "label", NULL); // "power"
  
    gpio = of_get_gpio_flags(child_node, 0, &flags);
    pdata->button[i].gpio = gpio;
    pdata->button[i].active_low = flags & OF_GPIO_ACTIVE_LOW;
    pdata->button[i].wakeup = !!of_get_property(child_node, "gpio-key,wakeup", NULL);
  
    return 0;
error_ret:
    return ret;
}

 

(3)唤醒源注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
wakeup.c (kernel\drivers\base\power)
/**@dev: Device to handle.
 * @enable: Whether or not to enable @dev as a wakeup device.
 */
int device_init_wakeup(struct device *dev, bool enable)
{
    int ret = 0;
    if (enable) {
        // 1、dev->power.can_wakeup = true
        device_set_wakeup_capable(dev, true);
        // 2、Enable given device to be a wakeup source.
        ret = device_wakeup_enable(dev);
    } else {
        device_set_wakeup_capable(dev, false);
    }
  
    return ret;
}

 

(4)唤醒动作
还记得之前注册的中断处理函数keys_isr?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
    unsigned long irqflags, const char *devname, void *dev_id)
devm_request_irq(dev, irq, keys_isr,(button->active_low)?IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING,
    button->desc ? button->desc : "keys", button);
  
static irqreturn_t keys_isr(int irq, void *dev_id)
{
    // 1、获取在keys_probe()建立的xxx_keys_drvdata对象数据
    struct xxx_keys_drvdata *pdata = xxx_key_get_drvdata();
    // 2、dev_id即evm_request_irq()的最后一个参数,这里就是我们的PowerKey
    struct xxx_keys_button *button = (struct xxx_keys_button *)dev_id;
    struct input_dev *input = pdata->input;
  
    // 3、具有休眠唤醒功能且处于休眠模式,
    if(button->wakeup == 1 && pdata->in_suspend == true){
        button->state = 1;
        input_event(input, EV_KEY, button->code, button->state);
        input_sync(input);
    }
    // Timer去抖动
    mod_timer(&button->timer,
                jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
    return IRQ_HANDLED;
}
  
setup_timer(&button->timer, keys_timer, (unsigned long)button)
static void keys_timer(unsigned long _data)
{
    struct xxx_keys_drvdata *pdata = xxx_key_get_drvdata();
    struct xxx_keys_button *button = (struct xxx_keys_button *)_data;
    struct input_dev *input = pdata->input;
    int state;
     
    state = !!((gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low);
     
    if(button->state != state) {
        button->state = state;      
        input_event(input, EV_KEY, button->code, button->state);
        input_event(input, EV_KEY, button->code, button->state);
        input_sync(input);
    }
  
    if(state)
        mod_timer(&button->timer,
            jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
}

如果处于休眠态,直接上报唤醒事件(button->state = 1);否则就需要判断按键状态(keys_timer)。

至此,PowerKey作为唤醒源的实现就完成了。

五、PowerKey 事件读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h> 
#include <linux/input.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
   
#define DEV_PATH "/dev/input/event2"   // PowerKey report event node
  
int main(int argc, char **argv)
{
    int event_fd = -1;
    struct input_event event = {0};
    const size_t read_size = sizeof(struct input_event);
  
    event_fd = open(DEV_PATH, O_RDONLY);
    if (event_fd <= 0) {
        printf("%s open failed: %s\n", DEV_PATH, strerror(errno));
        return -1;
    }
  
    while (1) {
        if (read(event_fd, &event, read_size) == read_size) {
            if (event.type == EV_KEY) {
                printf("event code: %d\n", event.code);
                printf("event value: %d\n", event.value);
            } else {
                printf("type != EV_KEY, type: %d\n", event.type);
            }
        }
  
        usleep(10*1000);
    }
  
    close(event_fd);
    return 0;
}

编译、adb push到Android设备中,运行后操作PowerKey,可见Log:

posted on   yin'xiang  阅读(1862)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示