zoukankan      html  css  js  c++  java
  • 【Android休眠】之PowerKey唤醒源实现

    受不了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中,编码值位于:
    input.h (kernelincludeuapilinux)
    /*
     * 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成员变量中:

    input.h (kernelincludeuapilinux)
    /*
     * The event structure itself
     */
     
    struct input_event {
    	struct timeval time;
    	__u16 type;
    	__u16 code;
    	__s32 value;
    };
    之后我们会写个Linux应用程序读取code值。

     

    四、PowerKey驱动

    1、PowerKey驱动注册

    在我的板上,PowerKey驱动是按照platform_device注册的,设备驱动:
    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
    	}
    };

    注册为平台驱动:

    module_platform_driver(keys_device_driver);
    

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

    /* 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驱动实现

    贯穿始终的连个结构体:

    /**
     * 描述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()函数起始,注意代码的注释:

    // 省略异常处理代码
    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配置

    // 解析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.
    ");
    		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)唤醒源注册

    wakeup.c (kerneldriversasepower)
    /**@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?

    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 事件读取

    #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
    ", 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
    ", event.code);
    				printf("event value: %d
    ", event.value);
    			} else {
    				printf("type != EV_KEY, type: %d
    ", event.type);
    			}
    		}
     
    		usleep(10*1000);
    	}
     
    	close(event_fd);
    	return 0;
    }

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

  • 相关阅读:
    消息中间件 kafka rabbitmq 选型差异
    kafka生产部署
    logback不同业务的日志打印到不同文件
    Java并发编程核心概念一览
    大数据分析常用去重算法分析『Bitmap 篇』
    java 堆调优
    大规模使用 Apache Kafka 的20个最佳实践
    es定期删除数据
    HTTP协议中源端口和目标端口的问题
    How to duplicate the records in a MongoDB collection
  • 原文地址:https://www.cnblogs.com/rockyching2009/p/13256569.html
Copyright © 2011-2022 走看看