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 = <KEY_1>;
};

其中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_suspend
	ldr	r7, =resume
	sub	r7, r7, r6
	add	r8, r1, r4
	add	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, #0x0
	mcr     p15, 0, r6, c7, c5, 0
	mcr     p15, 0, r6, c7, c5, 6
	/* enable the Icache and branch prediction */
	mov     r6, #0x1800
	mcr     p15, 0, r6, c1, c0, 0
	isb

	/* 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, #0x1f
	cmp	r7, #0x1e
	bne	ldo_check_done3
	ldr	r7, [r11, #MX6Q_ANATOP_CORE]
	orr	r7, r7, #0x1f
	str	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, #0x0
	str	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, #0x0
	beq	dsm_only_resume_io
	resume_mmdc_io
	b	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);

 

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值