当前位置: 首页 > 科技观察

教你打断和唤醒系统

时间:2023-03-17 12:50:38 科技观察

在消费类电子产品中,功耗是很重要的,甚至项目后期都调整了功耗,看能省电到什么地方。于是就有了Linux的电源管理子系统,它包括很多方面:什么时候可以掉帧,什么时候可以关闭其他CPU核心,如果一个外设在系统运行的时候很少使用,需要让它休眠在运行时,系统休眠时,需要保证哪些外设可以唤醒系统。博主今天要讨论的是如何通过按键唤醒系统,类似于手机的电源键。这个功能并不新鲜,所以Linux里面有一个demo,先教你如何使用demo,然后再教你写一个中断来唤醒系统驱动。官方demomo目录:/kernel4.14/drivers/input/keyboard/gpio_keys.c这个驱动是专门为按键准备的,是一个经过测试的驱动。可随时用于测试按键中断或中断唤醒系统。很多时候比自己写的驱动靠谱。要使用这个驱动,首先要在该目录的Makefile中添加:obj-y+=gpio_keys.o在设备树中添加:gpio-keys{compatible="gpio-keys";#address-cells=<1>;#size-cells=<0>;autorepeat;key0{label="GPIOKeyEnter";linux,code=;gpios=<&gpio118GPIO_ACTIVE_LOW>;gpio-key,wakeup;};};兼容属性为“gpio-keys”,gpio_keys.c文件的第674行会匹配这个属性,如果匹配驱动就会运行。linux,code属性是键值,Linux对所有的键事件都有编号,所以KEY_ENTER其实就是一个编号,是驱动上报给上层的键值。gpios属性是表示低电平触发哪个GPIO口。您可以自己选择一个GPIO。gpio-key,wakeup表示这个GPIO支持中断唤醒,也可以写:wakeup-source。仅限新旧版本。修改就这么简单,但是语法要符合你手上的开发板平台。然后编译内核和设备树文件,下载到板子上。(Linux内核根目录下会有一个.config文件,确保CONFIG_PM_SLEEP=y开启)如果驱动加载成功,可以在/proc/interrupts中看到:从左到右第一列是软件中断号(唯一)。第二列是CPU,表示中断在CPU上触发了多少次,多核会有多列。第三列是中断控制器。imx6ull开发板的根中断控制器为GPC,外部中断控制器为gpio-mxc。两者是级联的。第四列是硬件中断号,即GPIO端口号。第五列表示中断是边沿触发还是电平触发。第六栏是中断名称,可以找到一个GPIOKeyEnter,驱动加载成功可以看到,失败则看不到。验证方式在内核中,睡眠模式有很多种,可以通过以下命令查看#cat/sys/power/state常用的睡眠模式有freeze、standby、mem、diskfreeze:冻结I/O设备和Standby:除了冻结I/??O设备外,还会挂起系统,唤醒速度更快,比其他mem更耗电,Diskmodehighmem:将运行状态数据保存到内存中,并关闭外设,进入等待模式,唤醒较慢,功耗比磁盘模式磁盘高:将运行状态数据保存到硬盘,然后关机,唤醒最慢示例:#echomem>/sys/power/state系统进入休眠后,基本停止UI和串口,串口无法操作,如图:按下按钮,系统恢复:当然是log这里不完整,可以输入dmesg查看完整日志:PM:Whatdoestpowermanager具体做的,如图,分为suspend流程和resume流程。事实上,中断允许它支持唤醒系统。最重要的是多了两个函数:suspend和resume。当suspend函数将系统整体挂起时,会调用各个外设注册的suspend。我们在这个函数中调用了enable_irq_wake,表示系统休眠时开启中断。resume函数会在整个系统恢复时调用各个外设注册的resume函数,在resume函数中调用disable_irq_wake,表示系统运行时不需要中断。两者成对使用。具体可以参考下面这篇文章,写的很好:http://www.wowotech.net/irq_subsystem/irq_handle_procedure.html也可以研究下gpio_keys.c,驱动看着复杂,但是很完整,毕竟身经百战,各种因素都考虑进去了,用它来测试吧!博主下面写的demo博主是简化版的,自测还行,分享给大家,需要的可以copyxxx.c#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#includestaticintgpionum=0;staticintirqnum=0;staticirqreturn_tmy_handler(intirq,void*dev_id){printk("%s\r\n“,__功能__);返回IRQ_HANDLED;}staticintgpio_keys_probe(structplatform_device*pdev){intret=0;structdevice_node*node=NULL;;/*设备节点*/node=of_find_compatible_node(NULL,NULL,"atkalpha-key");if(node==NULL){printk("%s:atkalpha-keynodenotfind!\r\n",__FUNCTION__);??return-EINVAL;}/*提取GPIO*/gpionum=of_get_named_gpio(node,"key-gpio",0);if(gpionum<0){printk("of_get_named_gpiocan'tgetkey\r\n");}/*初始化key所使用的IO,并设置成中断模式*/gpio_request(gpionum,"key-gpio");gpio_direction_input(gpionum);irqnum=gpio_to_irq(gpionum);ret=request_irq(irqnum,my_handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,"my-key",NULL);if(ret<0){printk("irq%drequestfailed!\r\n",irqnum);返回-EFAULT;}return0;}staticconststructof_device_idgpio_keys_of_match[]={{.compatible="atkalpha-key",},{},};MODULE_DEVICE_TABLE(of,gpio_keys_of_match);staticintgpio_keys_remove(structplatform_device*pdev){return0;}staticintgpio_keys_suspend(structdevice*dev){printk("%s\r\n",__FUNCTION__);??enable_irq_wake(irqnum);return0;}staticintgpio_keys_resume(structdevice*dev){printk("%s\r\n",__FUNCTION__);??disable_irq_wake(irqnum);return0;}staticSIMPLE_DEV_PM_OPS(gpio_keys_pm_ops,gpio_keys_suspend,gpio_keys_resume);staticstructplatform_drivergpio_keys_device_driver={.probe=gpio_keys_probe,.remove=gpio_keys_remove,.driver={.name="my-key",.pm==&gpio_keys_probe,.driver={.name="my-key",.pm==&gpio_op_match_table.pm=&gpio_op_match_table.(gpio_keys_of_match),}};staticint__initgpio_keys_init(void){returnplatform_driver_register(&gpio_keys_device_driver);}staticvoid__exitgpio_keys_exit(void){platform_driver_unregister(&gpio_keys_device_driver);}module_init(gpio_keys_init);module_exit(gpio_keys_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Jason");MODULE_DESCRIPTION("KeyboarddriverforGPIOs");MODULE_ALIAS("platform:gpio-keys");xxx.dtskey{#address-cells=<1>;#size-cells=<1>;compatible="atkalpha-key";key-gpio=<&gpio118GPIO_ACTIVE_LOW>;/*KEY0*/interrupt-parent=<&gpio1>;interrupts=<18IRQ_TYPE_EDGE_BOTH>;/*FALLINGRISING*/gpio-key,wakeup;status="okay》;};最后总结一下:中断唤醒系统和普通驱动的区别在于多了两个函数:suspend和resume。触发中断在resume函数中,调用disable_irq_wake恢复原来的中断触发路径。然后使用SIMPLE_DEV_PM_OPS宏将suspend和resume函数注册到gpio_keys_pm_ops操作集,最后由平台注册到系统中。完成后,系统休眠过程中会调用设备注册的suspend函数,系统唤醒过程中会调用设备注册的resume函数。至于probe函数的写法,我在GPIO子系统和中断子系统系列文章中都讲过这些函数的使用。你可以去我的网站查看:http://www.linuxer.vip注意:这个demo只是用来唤醒系统的,如果你的中断是在I2C等设备驱动中的,必须马上进行I2C通信唤醒系统后的中断处理函数。写法不同,但框架是一样的。另外,驱动程序的中断处理函数里面什么也没有,所以唤醒后执行完中断处理函数,又会休眠。如果希望系统在中断唤醒后保持唤醒状态,请使用中断处理函数中的__pm_stay_awake()和__pm_relax()函数。