Linux睡眠唤醒机制分析--以IMX6UL为例

鉴于当前做的项目中有低功耗的需求,因此查探了一番Linux的睡眠及唤醒的机制。

当前网络上已经有很多关于睡眠唤醒的分析文章,有的分析也非常透彻,因此本文只从寄存器以及汇编处理和CPU架构方面来补充一下。

睡眠总体上可以分为浅睡及深睡眠,从PM管理上来说,主要是电源域和时钟控制的不同,如下IMX6介绍:

Linux进入睡眠的方式,在应用层可以直接操作/sys/power/state文件,cat此文件可以查看支持睡眠的种类,我的imx6开发板只有3种模式:

root@mys6ull14x14:~# cat /sys/power/state 
freeze standby mem
  • freeze: 冻结I/O设备,将它们置于低功耗状态,使处理器进入空闲状态,唤醒最快,耗电比其它standby, mem, disk方式高
  • standby:除了冻结I/O设备外,还会暂停系统,唤醒较快,耗电比其它 mem, disk方式高
  • mem:将运行状态数据存到内存,并关闭外设,进入等待模式,唤醒较慢,耗电比disk方式高
  • disk: 将运行状态数据存到硬盘,然后关机,唤醒最慢

在我的IMX6UL板子上,外设只有一个wifi而且是禁用状态,5V正常工作时,电流170ma左右,进入mem状态,大概20ma

看一下睡眠和唤醒的过程日志:

root@mys6ull14x14:~# echo mem > /sys/power/state PM: Syncing filesystems ... done.
Freezing user space processes ... (elapsed 0.002 seconds) done.
Freezing remaining freezable tasks ... (elapsed 0.001 seconds) done.
Suspending console(s) (use no_console_suspend to debug)
----此时已进入睡眠,调试串口无打印
--以下为唤醒过程的输出:
RTW:  suspend start
PM: suspend of devices complete after 12.485 msecs
PM: suspend devices took 0.010 seconds
PM: late suspend of devices complete after 2.056 msecs
PM: noirq suspend of devices complete after 2.235 msecs
Disabling non-boot CPUs ...
PM: noirq resume of devices complete after 1.326 msecs
PM: early resume of devices complete after 1.394 msecs
gpmi-nand 1806000.gpmi-nand: enable the asynchronous EDO mode 5
RTW: resume start
==> rtl8188e_iol_efuse_patch
RTW: wlan0- hw port(0) mac_addr =a0:2c:36:e7:23:5e
RTW: rtw_resume_common:0 in 590 ms
PM: resume of devices complete after 659.844 msecs
PM: resume devices took 0.660 seconds
Restarting tasks ... done.
root@mys6ull14x14:~# 

Linux将各种外设进入低功耗模式之后,关闭各域的Power,对于ARM core,直接使用WFI指令让其进入低功耗模式,此后等待外部唤醒。

对于唤醒机制,首先得确认CPU支持哪些唤醒源,针对IMX6,唤醒源如下:

对于Other wakeup source,主要是一些外设,比如UART,SD卡,网络等等,可以直接在外设寄存器中查看。

对于唤醒源的设定,主要是进入低功耗模式之前,屏蔽其中断,睡眠后当此中断发生时,arm core会自动唤醒。

关于设定方式,主要在DTS中配置,如gpio:

user {label = "User Button";gpios = <&gpio5 0 GPIO_ACTIVE_HIGH>;gpio-key,wakeup;linux,code = ;
};

其中gpio-key,wakeup即设定为支持唤醒功能。在驱动中,关于此配置最主要的操作为enable_irq_wake,最后会调用到cpu层接口

static struct irq_chip imx_gpc_chip = {.name			= "GPC",.irq_eoi		= irq_chip_eoi_parent,.irq_mask		= imx_gpc_irq_mask,.irq_unmask		= imx_gpc_irq_unmask,.irq_retrigger		= irq_chip_retrigger_hierarchy,.irq_set_wake		= imx_gpc_irq_set_wake,.irq_set_type           = irq_chip_set_type_parent,
#ifdef CONFIG_SMP.irq_set_affinity	= irq_chip_set_affinity_parent,
#endif
};

在imx_gpc_irq_set_wake把唤醒中断ID记录到gpc_wake_irqs,

static int imx_gpc_irq_set_wake(struct irq_data *d, unsigned int on)
{unsigned int idx = d->hwirq / 32;unsigned long flags;u32 mask;mask = 1 << d->hwirq % 32;spin_lock_irqsave(&gpc_lock, flags);gpc_wake_irqs[idx] = on ? gpc_wake_irqs[idx] | mask :gpc_wake_irqs[idx] & ~mask;spin_unlock_irqrestore(&gpc_lock, flags);/** Do *not* call into the parent, as the GIC doesn't have any* wake-up facility...*/return 0;
}

登记之后在进入睡眠之前应用imx_gpc_pre_suspend:

void imx_gpc_pre_suspend(bool arm_power_off)
{void __iomem *reg_imr1 = gpc_base + GPC_IMR1;int i;if (cpu_is_imx6q() && imx_get_soc_revision() == IMX_CHIP_REVISION_2_0)_imx6q_pm_pu_power_off(&imx6q_pu_domain.base);/* power down the mega-fast power domain */if ((cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) && arm_power_off)imx_gpc_mf_mix_off();/* Tell GPC to power off ARM core when suspend */if (arm_power_off)imx_gpc_set_arm_power_in_lpm(arm_power_off);for (i = 0; i < IMR_NUM; i++) {gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4);writel_relaxed(~gpc_wake_irqs[i], reg_imr1 + i * 4);}
}

 

writel_relaxed(~gpc_wake_irqs[i], reg_imr1 + i * 4); 写入中断屏蔽寄存器即可.

睡眠后,cpu大部分功能不可用,包括中断控制器,当中断发生时,不能进入正常的中断响应程序,而且被另一套独立的“中断控制器”所接管,跳入指定的地址执行,在imx6上此地址可以在如下寄存器中配置:

SRC_GPR1为睡眠后中断发生时跳入的地址,SRC_GPR2为相关参数

在Linux中使用如下,文件arch/arm/mach-imx/suspend-imx6.S 中函数ENTRY(imx6_suspend)会保存相关寄存器

ENTRY(imx6_suspend)ldr	r1, [r0, #PM_INFO_PBASE_OFFSET]ldr	r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET]ldr	r3, [r0, #PM_INFO_DDR_TYPE_OFFSET]ldr	r4, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET]/** counting the resume address in iram* to set it in SRC register.*/ldr	r6, =imx6_suspendldr	r7, =resumesub	r7, r7, r6add	r8, r1, r4add	r9, r8, r7/** make sure TLB contain the addr we want,* as we will access them after MMDC IO floated.*/ldr	r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET]ldr	r6, [r11, #0x0]ldr	r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET]ldr	r6, [r11, #0x0]ldr	r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET]ldr	r6, [r11, #0x0]/* use r11 to store the IO address */ldr	r11, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET]/* store physical resume addr and pm_info address. */str	r9, [r11, #MX6Q_SRC_GPR1]str	r1, [r11, #MX6Q_SRC_GPR2]

关于保存的位置和内容,文件中有相关注释:

/** ==================== low level suspend ====================** Better to follow below rules to use ARM registers:* r0: pm_info structure address;* r1 ~ r4: for saving pm_info members;* r5 ~ r10: free registers;* r11: io base address.** suspend ocram space layout:* ======================== high address ======================*                              .*                              .*                              .*                              ^*                              ^*                              ^*                      imx6_suspend code*              PM_INFO structure(imx6_cpu_pm_info)* ======================== low address =======================*/

关于恢复,resume地址此前已经存入R9 即SRC_GPR1:

resume:/* invalidate L1 I-cache first */mov     r6, #0x0mcr     p15, 0, r6, c7, c5, 0mcr     p15, 0, r6, c7, c5, 6/* enable the Icache and branch prediction */mov     r6, #0x1800mcr     p15, 0, r6, c1, c0, 0isb/* restore it with 0x1f if use ldo bypass mode.*/ldr	r11, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET]ldr	r7, [r11, #MX6Q_ANATOP_CORE]and	r7, r7, #0x1fcmp	r7, #0x1ebne	ldo_check_done3ldr	r7, [r11, #MX6Q_ANATOP_CORE]orr	r7, r7, #0x1fstr	r7, [r11, #MX6Q_ANATOP_CORE]
ldo_check_done3:/* get physical resume address from pm_info. */ldr	lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET]/* clear core0's entry and parameter */ldr	r11, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET]mov	r7, #0x0str	r7, [r11, #MX6Q_SRC_GPR1]str	r7, [r11, #MX6Q_SRC_GPR2]ldr	r3, [r0, #PM_INFO_DDR_TYPE_OFFSET]mov	r5, #0x1/* check whether it supports Mega/Fast off */ldr	r6, [r0, #PM_INFO_MMDC_NUM_OFFSET]cmp	r6, #0x0beq	dsm_only_resume_ioresume_mmdc_iob	dsm_resume_mmdc_done
dsm_only_resume_io:ldr	r3, [r0, #PM_INFO_DDR_TYPE_OFFSET]resume_io
dsm_resume_mmdc_done:ret	lr

关于结构体imx6_cpu_pm_info与汇编中保存位置的定义,需要一一对应:

/** This structure is for passing necessary data for low level ocram* suspend code(arch/arm/mach-imx/suspend-imx6.S), if this struct* definition is changed, the offset definition in* arch/arm/mach-imx/suspend-imx6.S must be also changed accordingly,* otherwise, the suspend to ocram function will be broken!*/
struct imx6_cpu_pm_info {phys_addr_t pbase; /* The physical address of pm_info. */phys_addr_t resume_addr; /* The physical resume address for asm code */u32 ddr_type;u32 pm_info_size; /* Size of pm_info. */struct imx6_pm_base mmdc0_base;struct imx6_pm_base mmdc1_base;struct imx6_pm_base src_base;struct imx6_pm_base iomuxc_base;struct imx6_pm_base ccm_base;struct imx6_pm_base gpc_base;struct imx6_pm_base l2_base;struct imx6_pm_base anatop_base;u32 ttbr1; /* Store TTBR1 */u32 mmdc_io_num; /* Number of MMDC IOs which need saved/restored. */u32 mmdc_io_val[MX6_MAX_MMDC_IO_NUM][2]; /* To save offset and value */u32 mmdc_num; /* Number of MMDC registers which need saved/restored. */u32 mmdc_val[MX6_MAX_MMDC_NUM][2];
} __aligned(8);

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部