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_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);?
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的Linux睡眠唤醒机制分析--以IMX6UL为例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IMX8QXP内部M4移植rt-thre
- 下一篇: CPU方案简介 RK3308 - 智能音