萬安堂 發表於 2023-12-29 00:00:00

手把手教你中断唤醒系统

<p>
        <img title="手把手教你中断唤醒系统" alt="手把手教你中断唤醒系统" border="0" src="https://zhuji.jb51.net/uploads/img/202305/584de0fe32dc918b4d9c49bfd83c60e3.jpg"></p>
<p>
        在消费类电子中,功耗是很重要的,甚至项目后期一直在调功耗,看看哪里还可以再省电。由此就有了 Linux 电源管理子系统,该子系统包含很多方面:什么时候可以降帧、什么时候可以关掉其他 CPU core、系统运行时如果某外设很少用需要让它运行时休眠、系统休眠时要保证哪些外设可以唤醒系统。
</p>
<p>
        博主今天要讨论的,就是一个按键如何唤醒系统,类似于手机的电源键。
</p>
<p>
        这个功能并不是新功能,所以 Linux 内部有一个 demo 可以使用,先教大家如何使用该 demo,然后较大家如何撰写中断唤醒系统驱动。
</p>
<h3>
        官方 demo
</h3>
<p>
        demo 目录:/kernel4.14/drivers/input/keyboard/gpio_keys.c
</p>
<p>
        该驱动是专门为按键准备的,是一个身经百战的驱动,任何时候测试按键中断或者中断唤醒系统都可以用它,很多时候比自己写的驱动靠谱。
</p>
<p>
        要想使用该驱动,首先在该目录的 Makefile 中增加:
</p>
<ol class="dp-sql">
<li class="alt">
                <span><span>obj-y += gpio_keys.o </span></span>
        </li>
</ol>
<p>
        设备树中增加:
</p>
<p>
        <key_enter></key_enter></p>
<ol class="dp-sql">
<li class="alt">
                <span><span>gpio-keys { </span></span>
        </li>
        <li>
                <span>compatible = <span class="string">"gpio-keys"</span><span>; </span></span>
        </li>
        <li class="alt">
                <span>#address-cells = &lt;1&gt;; </span>
        </li>
        <li>
                <span>#<span class="keyword">size</span><span>-cells = &lt;0&gt;; </span></span>
        </li>
        <li class="alt">
                <span>autorepeat; </span>
        </li>
        <li>
                <span>key0 { </span>
        </li>
        <li class="alt">
                <span>label = <span class="string">"GPIO Key Enter"</span><span>; </span></span>
        </li>
        <li>
                <span>linux,code = <key_enter>; </key_enter></span>
        </li>
        <li class="alt">
                <span>gpios = &lt;&amp;gpio1 18 GPIO_ACTIVE_LOW&gt;; </span>
        </li>
        <li>
                <span>gpio-<span class="keyword">key</span><span>,wakeup; </span></span>
        </li>
        <li class="alt">
                <span>}; </span>
        </li>
        <li>
                <span>}; </span>
        </li>
</ol>
<p>
        compatible 属性是 “gpio-keys”,gpio_keys.c 文件的674行会匹配这个属性,匹配到了该驱动就会运行。
</p>
<p>
        linux,code 属性是按键值,Linux 对所有按键事件都有编号,所以KEY_ENTER 实际是一个数字,是驱动向上层报告的一个按键值。
</p>
<p>
        gpios 属性是标明哪一个 GPIO 口,低电平触发,大家可以自己选一个 GPIO。
</p>
<p>
        gpio-key,wakeup 是代表此GPIO支持中断唤醒,你也可以写成:wakeup-source。新老版本而已。
</p>
<p>
        修改就是这么简单,不过语法要符合各位手中的开发板平台。然后编译出内核和设备树文件,下载到板子中。(Linux 内核根目录会有 .config 文件,确保 CONFIG_PM_SLEEP=y 有打开)
</p>
<p>
        如果驱动加载成功,在 /proc/interrupts 中可以看到:
</p>
<p>
        <img title="手把手教你中断唤醒系统" alt="手把手教你中断唤醒系统" border="0" src="https://zhuji.jb51.net/uploads/img/202305/dab0d212d2d9f2bd2045390792d9b293.jpg"></p>
<p>
        从左往右第一列是软件中断号(唯一)。
</p>
<p>
        第二列是 CPU,表示该中断在该CPU上触发了多少次,多核会有多列。
</p>
<p>
        第三列是中断控制器,imx6ull开发板根中断控制器是GPC,外部中断控制器是gpio-mxc,两者是级联关系。
</p>
<p>
        第四列是硬件中断号,也就是GPIO口编号。
</p>
<p>
        第五列表示该中断是边沿触发还是电平触发。
</p>
<p>
        第六列是中断名称,可以找到一个 GPIO Key Enter,如果驱动加载成功就能看到,如果失败就看不到。
</p>
<h3>
        验证方法
</h3>
<p>
        在内核中,休眠方式有很多种,可以通过下面命令查看
</p>
<ol class="dp-sql">
<li class="alt">
                <span><span># cat /sys/power/state </span></span>
        </li>
</ol>
<p>
        常用的休眠方式有freeze、standby、mem、disk
</p>
<p>
        freeze:冻结I/O设备,将它们置于低功耗状态,使处理器进入空闲状态,唤醒最快,耗电比其它standby, mem, disk方式高
</p>
<p>
        standby:除了冻结I/O设备外,还会暂停系统,唤醒较快,耗电比其它 mem, disk方式高
</p>
<p>
        mem:将运行状态数据存到内存,并关闭外设,进入等待模式,唤醒较慢,耗电比disk方式高
</p>
<p>
        disk:将运行状态数据存到硬盘,然后关机,唤醒最慢
</p>
<p>
        示例:
</p>
<ol class="dp-sql">
<li class="alt">
                <span><span># echo mem &gt; /sys/power/state </span></span>
        </li>
</ol>
<p>
        系统进入睡眠后,基本都会停掉UI、停掉串口,串口无法操作,如图:
</p>
<p>
        <img title="手把手教你中断唤醒系统" alt="手把手教你中断唤醒系统" border="0" src="https://zhuji.jb51.net/uploads/img/202305/9936167ea3ec0ca30d2dfd78b9a35038.jpg"></p>
<p>
        按下按键,系统恢复:
</p>
<p>
        <img title="手把手教你中断唤醒系统" alt="手把手教你中断唤醒系统" border="0" src="https://zhuji.jb51.net/uploads/img/202305/a12f5d75232f17bf9a19c503ce1db65c.jpg"></p>
<p>
        当然这里的 log 并不完整,输入 dmesg 可以看到完整 log:
</p>
<p>
        <img title="手把手教你中断唤醒系统" alt="手把手教你中断唤醒系统" border="0" src="https://zhuji.jb51.net/uploads/img/202305/74e58678bc347db190bf9e05a5f45b1e.jpg"></p>
<p>
        PM:power manager
</p>
<p>
        具体干了什么,图中有解释,分为 suspend 过程和 resume 过程。
</p>
<p>
        其实一个中断让它支持唤醒系统,最主要是多了两个函数:suspend、resume。
</p>
<p>
        suspend 函数在系统整体 suspend 的时候,会调用每个外设注册的 suspend,我们在这个函数中调用 enable_irq_wake,表示该中断在系统休眠时是 enable 状态。
</p>
<p>
        resume 函数在系统整体 resume 的时候,会调用每个外设注册的 resume 函数,在 resume 函数中调用 disable_irq_wake ,表示该中断在系统运行时不需要。两者成对使用。
</p>
<p>
        具体参看下面文章,写的很好:
</p>
<p>
        http://www.wowotech.net/irq_subsystem/irq_handle_procedure.html
</p>
<p>
        大家也可以研究一下 gpio_keys.c,该驱动看起来比较复杂,但是很完善,毕竟身经百战,什么因素都考虑到了,测试就用它!
</p>
<h3>
        博主写的 demo
</h3>
<p>
        博主下面给的是简化版,并且自测OK,分享给大家,以后如果需要可以copy
</p>
<p>
        xxx.c
</p>
<p>
        <linux module.h=""><linux i2c.h=""><linux interrupt.h=""><linux delay.h=""><linux uaccess.h=""><linux pm.h=""><linux slab.h=""><linux sysctl.h=""><linux proc_fs.h=""><linux delay.h=""><linux platform_device.h=""><linux input.h=""><linux gpio_keys.h=""><linux workqueue.h=""><linux gpio.h=""><linux of.h=""><linux of_platform.h=""><linux of_gpio.h=""><linux of_irq.h=""><linux spinlock.h=""><linux cdev.h=""></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></linux></p>
<ol class="dp-sql">
<li class="alt">
                <span><span>#include <linux module.h></linux></span></span>
        </li>
        <li>
                <span>#include <linux i2c.h></linux></span>
        </li>
        <li class="alt">
                <span>#include <linux interrupt.h></linux></span>
        </li>
        <li>
                <span>#include <linux delay.h></linux></span>
        </li>
        <li class="alt">
                <span>#include <linux uaccess.h></linux></span>
        </li>
        <li>
                <span>#include <linux pm.h></linux></span>
        </li>
        <li class="alt">
                <span>#include <linux slab.h></linux></span>
        </li>
        <li>
                <span>#include <linux sysctl.h></linux></span>
        </li>
        <li class="alt">
                <span>#include <linux proc_fs.h></linux></span>
        </li>
        <li>
                <span>#include <linux delay.h></linux></span>
        </li>
        <li class="alt">
                <span>#include <linux platform_device.h></linux></span>
        </li>
        <li>
                <span>#include <linux input.h></linux></span>
        </li>
        <li class="alt">
                <span>#include <linux gpio_keys.h></linux></span>
        </li>
        <li>
                <span>#include <linux workqueue.h></linux></span>
        </li>
        <li class="alt">
                <span>#include <linux gpio.h></linux></span>
        </li>
        <li>
                <span>#include <linux class="keyword">of</linux></span><span>.h&gt; </span>
        </li>
        <li class="alt">
                <span>#include <linux of_platform.h></linux></span>
        </li>
        <li>
                <span>#include <linux of_gpio.h></linux></span>
        </li>
        <li class="alt">
                <span>#include <linux of_irq.h></linux></span>
        </li>
        <li>
                <span>#include <linux spinlock.h></linux></span>
        </li>
        <li class="alt">
                <span>#include <linux cdev.h></linux></span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span><span class="keyword">static</span><span> </span><span class="keyword">int</span><span> gpionum = 0; </span></span>
        </li>
        <li>
                <span><span class="keyword">static</span><span> </span><span class="keyword">int</span><span> irqnum = 0; </span></span>
        </li>
        <li class="alt">
                <span></span>
        </li>
        <li>
                <span><span class="keyword">static</span><span> irqreturn_t my_handler(</span><span class="keyword">int</span><span> irq, void *dev_id) </span></span>
        </li>
        <li class="alt">
                <span>{ </span>
        </li>
        <li>
                <span>printk(<span class="string">"%s\r\n"</span><span>,__FUNCTION__); </span></span>
        </li>
        <li class="alt">
                <span><span class="keyword">return</span><span> IRQ_HANDLED; </span></span>
        </li>
        <li>
                <span>} </span>
        </li>
        <li class="alt">
                <span></span>
        </li>
        <li>
                <span><span class="keyword">static</span><span> </span><span class="keyword">int</span><span> gpio_keys_probe(struct platform_device *pdev) </span></span>
        </li>
        <li class="alt">
                <span>{ </span>
        </li>
        <li>
                <span><span class="keyword">int</span><span> ret = 0; </span></span>
        </li>
        <li class="alt">
                <span>struct device_node *node = <span class="op">NULL</span><span>;; /* 设备节点*/ </span></span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span>node = of_find_compatible_node(<span class="op">NULL</span><span>,</span><span class="op">NULL</span><span>,</span><span class="string">"atkalpha-key"</span><span>); </span></span>
        </li>
        <li>
                <span>if (node == <span class="op">NULL</span><span>){ </span></span>
        </li>
        <li class="alt">
                <span>printk(<span class="string">"%s:atkalpha-key node not find!\r\n"</span><span>,__FUNCTION__); </span></span>
        </li>
        <li>
                <span><span class="keyword">return</span><span> -EINVAL; </span></span>
        </li>
        <li class="alt">
                <span>} </span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span>/* 提取 GPIO */ </span>
        </li>
        <li>
                <span>gpionum = of_get_named_gpio(node,<span class="string">"key-gpio"</span><span>, 0); </span></span>
        </li>
        <li class="alt">
                <span>if (gpionum &lt; 0) { </span>
        </li>
        <li>
                <span>printk(<span class="string">"of_get_named_gpio can't get key\r\n"</span><span>); </span></span>
        </li>
        <li class="alt">
                <span>} </span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span>/* 初始化 <span class="keyword">key</span><span> 所使用的 IO,并且设置成中断模式 */ </span></span>
        </li>
        <li>
                <span>gpio_request(gpionum, <span class="string">"key-gpio"</span><span>); </span></span>
        </li>
        <li class="alt">
                <span>gpio_direction_input(gpionum); </span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span>irqnum = gpio_to_irq(gpionum); </span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span>ret = request_irq(irqnum,my_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, <span class="string">"my-key"</span><span>, </span><span class="op">NULL</span><span>); </span></span>
        </li>
        <li>
                <span>if(ret &lt; 0){ </span>
        </li>
        <li class="alt">
                <span>printk(<span class="string">"irq %d request failed!\r\n"</span><span>, irqnum); </span></span>
        </li>
        <li>
                <span><span class="keyword">return</span><span> -EFAULT; </span></span>
        </li>
        <li class="alt">
                <span>} </span>
        </li>
        <li>
                <span><span class="keyword">return</span><span> 0; </span></span>
        </li>
        <li class="alt">
                <span>} </span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span></span>
        </li>
        <li>
                <span><span class="keyword">static</span><span> const struct of_device_id gpio_keys_of_match[] = { </span></span>
        </li>
        <li class="alt">
                <span>{ .compatible = <span class="string">"atkalpha-key"</span><span>, }, </span></span>
        </li>
        <li>
                <span>{ }, </span>
        </li>
        <li class="alt">
                <span>}; </span>
        </li>
        <li>
                <span>MODULE_DEVICE_TABLE(<span class="keyword">of</span><span>, gpio_keys_of_match); </span></span>
        </li>
        <li class="alt">
                <span></span>
        </li>
        <li>
                <span><span class="keyword">static</span><span> </span><span class="keyword">int</span><span> gpio_keys_remove(struct platform_device *pdev) </span></span>
        </li>
        <li class="alt">
                <span>{ </span>
        </li>
        <li>
                <span><span class="keyword">return</span><span> 0; </span></span>
        </li>
        <li class="alt">
                <span>} </span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span><span class="keyword">static</span><span> </span><span class="keyword">int</span><span> gpio_keys_suspend(struct device *dev) </span></span>
        </li>
        <li>
                <span>{ </span>
        </li>
        <li class="alt">
                <span>printk(<span class="string">"%s\r\n"</span><span>,__FUNCTION__); </span></span>
        </li>
        <li>
                <span>enable_irq_wake(irqnum); </span>
        </li>
        <li class="alt">
                <span><span class="keyword">return</span><span> 0; </span></span>
        </li>
        <li>
                <span>} </span>
        </li>
        <li class="alt">
                <span></span>
        </li>
        <li>
                <span><span class="keyword">static</span><span> </span><span class="keyword">int</span><span> gpio_keys_resume(struct device *dev) </span></span>
        </li>
        <li class="alt">
                <span>{ </span>
        </li>
        <li>
                <span>printk(<span class="string">"%s\r\n"</span><span>,__FUNCTION__); </span></span>
        </li>
        <li class="alt">
                <span>disable_irq_wake(irqnum); </span>
        </li>
        <li>
                <span><span class="keyword">return</span><span> 0; </span></span>
        </li>
        <li class="alt">
                <span>} </span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span><span class="keyword">static</span><span> SIMPLE_DEV_PM_OPS(gpio_keys_pm_ops, gpio_keys_suspend, gpio_keys_resume); </span></span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span><span class="keyword">static</span><span> struct platform_driver gpio_keys_device_driver = { </span></span>
        </li>
        <li>
                <span>.probe = gpio_keys_probe, </span>
        </li>
        <li class="alt">
                <span>.remove = gpio_keys_remove, </span>
        </li>
        <li>
                <span>.driver = { </span>
        </li>
        <li class="alt">
                <span>.<span class="keyword">name</span><span> = </span><span class="string">"my-key"</span><span>, </span></span>
        </li>
        <li>
                <span>.pm = &amp;gpio_keys_pm_ops, </span>
        </li>
        <li class="alt">
                <span>.of_match_table = of_match_ptr(gpio_keys_of_match), </span>
        </li>
        <li>
                <span>} </span>
        </li>
        <li class="alt">
                <span>}; </span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span><span class="keyword">static</span><span> </span><span class="keyword">int</span><span> __init gpio_keys_init(void) </span></span>
        </li>
        <li>
                <span>{ </span>
        </li>
        <li class="alt">
                <span><span class="keyword">return</span><span> platform_driver_register(&amp;gpio_keys_device_driver); </span></span>
        </li>
        <li>
                <span>} </span>
        </li>
        <li class="alt">
                <span></span>
        </li>
        <li>
                <span><span class="keyword">static</span><span> void __exit gpio_keys_exit(void) </span></span>
        </li>
        <li class="alt">
                <span>{ </span>
        </li>
        <li>
                <span>platform_driver_unregister(&amp;gpio_keys_device_driver); </span>
        </li>
        <li class="alt">
                <span>} </span>
        </li>
        <li>
                <span></span>
        </li>
        <li class="alt">
                <span>module_init(gpio_keys_init); </span>
        </li>
        <li>
                <span>module_exit(gpio_keys_exit); </span>
        </li>
        <li class="alt">
                <span></span>
        </li>
        <li>
                <span>MODULE_LICENSE(<span class="string">"GPL"</span><span>); </span></span>
        </li>
        <li class="alt">
                <span>MODULE_AUTHOR(<span class="string">"Jason"</span><span>); </span></span>
        </li>
        <li>
                <span>MODULE_DESCRIPTION(<span class="string">"Keyboard driver for GPIOs"</span><span>); </span></span>
        </li>
        <li class="alt">
                <span>MODULE_ALIAS(<span class="string">"platform:gpio-keys"</span><span>); </span></span>
        </li>
</ol>
<p>
        xxx.dts
</p>
<ol class="dp-sql">
<li class="alt">
                <span><span class="keyword">key</span><span> { </span></span>
        </li>
        <li>
                <span>#address-cells = &lt;1&gt;; </span>
        </li>
        <li class="alt">
                <span>#<span class="keyword">size</span><span>-cells = &lt;1&gt;; </span></span>
        </li>
        <li>
                <span>compatible = <span class="string">"atkalpha-key"</span><span>; </span></span>
        </li>
        <li class="alt">
                <span><span class="keyword">key</span><span>-gpio = &lt;&amp;gpio1 18 GPIO_ACTIVE_LOW&gt;; /* KEY0 */ </span></span>
        </li>
        <li>
                <span>interrupt-parent = &lt;&amp;gpio1&gt;; </span>
        </li>
        <li class="alt">
                <span>interrupts = &lt;18 irq_type_edge_both=""&gt;; /* FALLING RISING */ </span>
        </li>
        <li>
                <span>gpio-<span class="keyword">key</span><span>,wakeup; </span></span>
        </li>
        <li class="alt">
                <span>status = <span class="string">"okay"</span><span>; </span></span>
        </li>
        <li>
                <span>}; </span>
        </li>
</ol>
<p>
        最后再总结一下:中断唤醒系统和普通的驱动区别在于,多了两个函数:suspend 和 resume,在 suspend 函数中,调用 enable_irq_wake,表示该中断号在系统休眠时也是 enable 状态,可以触发中断。在 resume 函数中,调用 disable_irq_wake ,恢复原始的中断触发路径。
</p>
<p>
        然后使用 SIMPLE_DEV_PM_OPS 宏将 suspend 和 resume 函数注册到 gpio_keys_pm_ops 操作集,最终由 platform 注册到系统中。这样完成后,系统休眠过程中就会调用到设备注册的 suspend,系统唤醒过程中就会调用设备注册的 resume 函数。
</p>
<p>
        至于 probe 函数的书写,我在 GPIO 子系统和中断子系统系列文章都讲过这些函数的使用,大家可以去我的网站查看:
</p>
<p>
        http://www.linuxer.vip
</p>
<p>
        note:该 demo 只用来唤醒系统,如果你的中断是在 I2C 等设备驱动中,唤醒系统后要立刻在中断处理函数中进行 I2C 通信,写法不太一样,但是框架相同。
</p>
<p>
        另外,该驱动的中断处理函数中没做什么东西,因此唤醒后执行完中断处理函数后又会睡过去。如果你想要该中断唤醒系统后让系统一直处于唤醒状态,请在中断处理函数中使用 __pm_stay_awake() 和 __pm_relax()函数。
</p>
<p>
        原文链接:https://mp.weixin.qq.com/s/FooG30_Fr4svQcqbK9y_9g
</p>
頁: [1]
查看完整版本: 手把手教你中断唤醒系统