19.9 挂起到RAM Linux支持
STANDBY(浅度休眠,唤醒延迟小)、
挂起到RAM(中度休眠,SDRAM自刷新,简称S2RAM)、
挂起到硬盘(深度休眠,最省电,唤醒延迟大,简称STD)等形式的待机,如图19.9所示。
图19.9 Linux的待机模式
一般的嵌入式产品只实现挂起到RAM,即将系统的状态保存于内存中,并将SDRAM置于自刷新状态,待用户按键等操作后再重新恢复系统。少数嵌入式Linux系统会实现挂起到硬盘,挂起到硬盘与挂起到RAM的不同是:
挂起到RAM并不关机,挂起到硬盘则把系统的状态保持于磁盘,然后关闭整个系统。 在Linux下,这些行为通常是由用户空间触发的,
通过向/sys/power/state设备节点写入mem可开始挂起到RAM的流程。许多Linux产品会有一个按键,一按就进入挂起到RAM。这通常是由于
与这个按键对应的输入设备驱动汇报了一个和电源相关的input_event,用户空间的电源管理daemon进程收到这个事件后,再触发挂起到RAM的。内核也有一个INPUT_APMPOWER(APM:高级电源管理)驱动,位于drivers/input/apm-power.c下,这个文件在内核级别侦听EV_PWR类事件,并通过apm_queue_event(APM_USER_SUSPEND)自动引发挂起到RAM(S2RAM):
static void system_power_event(unsigned int keycode)
{
switch (keycode) {
case KEY_SUSPEND:
apm_queue_event(APM_USER_SUSPEND); //自动引发挂起到RAM(S2RAM) pr_info("Requesting system suspend...
");
break;
default:
break;
}
}
static void apmpower_event(struct input_handle *handle, unsigned int type,
unsigned int code, int value)
{
/* only react on key down events */
if (value != 1)
return; switch (type) {
case EV_PWR: //
内核级别侦听EV_PWR类事件 system_power_event(code);
break;
default:
break;
}
}
drivers/char/apm-emulation.c
void apm_queue_event(apm_event_t event)
{
unsigned long flags;
spin_lock_irqsave(&kapmd_queue_lock, flags); //获取自旋锁(锁定期间不允许阻塞)、关闭中断、保存标志
queue_add_event(&kapmd_queue, event);
spin_unlock_irqrestore(&kapmd_queue_lock, flags);
wake_up_interruptible(&kapmd_wait);//释放自旋锁、使能中断、恢复标志
}
EXPORT_SYMBOL(apm_queue_event);
在Linux内核中,挂起到RAM的挂起和恢复大致的流程如图19.10所示(操作包括同步文件系统、freeze进程、设备驱动挂起以及系统的挂起入口等)。
图19.10 Linux挂起到RAM流程
linux/device.h
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};在dev_pm_ops结构体中,封装
挂起到RAM和挂起到硬盘所需的回调函数,如代码清单19.13所示。代码清单19.13 dev_pm_ops结构体
linux/pm.h
struct dev_pm_ops {
int (*prepare)(struct device *dev);
void (*complete)(struct device *dev);
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
int (*freeze)(struct device *dev);
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
int (*freeze_noirq)(struct device *dev);
int (*thaw_noirq)(struct device *dev);
int (*poweroff_noirq)(struct device *dev);
int (*restore_noirq)(struct device *dev);
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};
图19.10给出挂起到RAM的时候,这些PM回调函数的调用时机。
目前比较推荐的做法是在platform_driver、i2c_driver和spi_driver等xxx_driver结构体实例的driver成员中,以上述结构体的形式封装PM回调函数,并赋值到driver的pm属性成员。例如在drivers/spi/spi-s3c64xx.c中,platform_driver中的pm成员被赋值。
代码清单19.14 设备驱动中的pm成员
#ifdef CONFIG_PM_SLEEP
static int s3c64xx_spi_suspend(struct device *dev)
{
struct spi_master *master = dev_get_drvdata(dev);
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);
int ret = spi_master_suspend(master);
if (ret)
return ret;
ret = pm_runtime_force_suspend(dev);
if (ret < 0)
return ret;
sdd->cur_speed = 0; /* Output Clock is stopped */
return 0;
}
static int s3c64xx_spi_resume(struct device *dev)
{
struct spi_master *master = dev_get_drvdata(dev);
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);
struct s3c64xx_spi_info *sci = sdd->cntrlr_info;
int ret;
if (sci->cfg_gpio)
sci->cfg_gpio();
ret = pm_runtime_force_resume(dev);
if (ret < 0)
return ret;
s3c64xx_spi_hwinit(sdd, sdd->port_id);
return spi_master_resume(master);
}
#endif /* CONFIG_PM_SLEEP */
#ifdef CONFIG_PM
static int s3c64xx_spi_runtime_suspend(struct device *dev)
{
struct spi_master *master = dev_get_drvdata(dev);
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);
clk_disable_unprepare(sdd->clk);
clk_disable_unprepare(sdd->src_clk);
clk_disable_unprepare(sdd->ioclk);
return 0;
}
static int s3c64xx_spi_runtime_resume(struct device *dev)
{
struct spi_master *master = dev_get_drvdata(dev);
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);
int ret;
if (sdd->port_conf->clk_ioclk) {
ret = clk_prepare_enable(sdd->ioclk);
if (ret != 0)
return ret;
}
ret = clk_prepare_enable(sdd->src_clk);
if (ret != 0)
goto err_disable_ioclk;
ret = clk_prepare_enable(sdd->clk);
if (ret != 0)
goto err_disable_src_clk;
return 0;
err_disable_src_clk:
clk_disable_unprepare(sdd->src_clk);
err_disable_ioclk:
clk_disable_unprepare(sdd->ioclk);
return ret;
}
#endif /* CONFIG_PM */
static const struct dev_pm_ops s3c64xx_spi_pm = {
SET_SYSTEM_SLEEP_PM_OPS(s3c64xx_spi_suspend, s3c64xx_spi_resume)
SET_RUNTIME_PM_OPS(s3c64xx_spi_runtime_suspend,
s3c64xx_spi_runtime_resume, NULL)
};
static const struct of_device_id s3c64xx_spi_dt_match[] = {
{ .compatible = "samsung,s3c2443-spi",
.data = (void *)&s3c2443_spi_port_config,
},
{ },
};
MODULE_DEVICE_TABLE(of, s3c64xx_spi_dt_match);
static struct platform_driver s3c64xx_spi_driver = {
.driver = {
.name
= "s3c64xx-spi",
.pm = &
s3c64xx_spi_pm,
.of_match_table = of_match_ptr(s3c64xx_spi_dt_match),
},
.probe = s3c64xx_spi_probe,
.remove = s3c64xx_spi_remove,
.id_table = s3c64xx_spi_driver_ids,
};
module_platform_driver(s3c64xx_spi_driver);
分析:s3c64xx_spi_suspend()完成时钟的禁止,s3c64xx_spi_resume()则完成硬件的重新初始化、时钟的使能等工作。宏SET_SYSTEM_SLEEP_PM_OPS完成suspend、resume等成员函数的赋值:#define SET_SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn)
.suspend = suspend_fn,
.resume = resume_fn,
.freeze = suspend_fn,
.thaw = resume_fn,
.poweroff = suspend_fn,
.restore = resume_fn,
除上述推行的做法外,在platform_driver、i2c_driver、spi_driver等xxx_driver结构体中仍保留过时的suspend、resume入口函数(目前不再推荐使用过时的接口,而是推荐赋值xxx_driver中的driver的pm成员),代码清单19.15 设备驱动中过时的PM成员函数。
linux/platform_device.h
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver
driver;
const struct platform_device_id *id_table;
};
在Linux的核心层中,实际上是优先选择执行xxx_driver.driver.pm.suspend()成员函数,在前者不存在的情况下,执行过时的xxx_driver.suspend(),如platform_pm_suspend()的逻辑如代码清单19.16所示。
代码清单19.16 驱动核心层寻找PM回调的顺序
drivers/base/platform.c
#ifdef CONFIG_SUSPEND
int platform_pm_suspend(struct device *dev)
{
struct device_driver *drv = dev->driver;
int ret = 0;
if (!drv)
return 0;
if (drv->pm) {
if (drv->pm->suspend)
ret =
drv->pm->suspend(dev); } else {
ret =
platform_legacy_suspend(dev, PMSG_SUSPEND); }
return ret;
}
int platform_pm_resume(struct device *dev)
{
struct device_driver *drv = dev->driver;
int ret = 0;
if (!drv)
return 0;
if (drv->pm) {
if (drv->pm->resume)
ret =
drv->pm->resume(dev); } else {
ret =
platform_legacy_resume(dev); }
return ret;
}
#endif /* CONFIG_SUSPEND */
一般来讲,在设备驱动的挂起入口函数中,会关闭设备、关闭该设备的时钟输入,甚至是关闭设备的电源,在恢复时则完成相反的的操作。在
挂起到RAM的挂起和恢复过程中,系统恢复后要求所有设备的驱动都工作正常。为调试这个过程,可以使能内核的
PM_DEBUG选项,如果在挂起和恢复的过程中,看到内核的打印信息以关注具体的详细流程,
在Bootloader传递给内核的bootargs中设置标志no_console_suspend。
在将Linux移植到一个新的ARM SoC的过程中,最终
系统挂起的入口需由芯片供应商在相应的
arch/arm/mach-xxx中实现
platform_suspend_ops的成员函数,一般主要实现其中的enter和valid成员,并将整个platform_suspend_ops结构体通过内核通用API
suspend_set_ops()注册进系统,如arch/arm/mach-prima2/pm.c中prima2
SoC级挂起流程的逻辑如代码清单19.17所示。代码清单19.17 系统挂起到RAM的SoC级代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "pm.h"
/*
* suspend asm codes will access these to make DRAM become self-refresh and
* system sleep
*/
u32 sirfsoc_pwrc_base;
void __iomem *sirfsoc_memc_base;
static void sirfsoc_set_wakeup_source(void)
{
u32 pwr_trigger_en_reg;
pwr_trigger_en_reg = sirfsoc_rtc_iobrg_readl(sirfsoc_pwrc_base + SIRFSOC_PWRC_TRIGGER_EN);
#define X_ON_KEY_B (1 << 0)
#define RTC_ALARM0_B (1 << 2)
#define RTC_ALARM1_B (1 << 3)
sirfsoc_rtc_iobrg_writel(pwr_trigger_en_reg | X_ON_KEY_B |
RTC_ALARM0_B | RTC_ALARM1_B,
sirfsoc_pwrc_base + SIRFSOC_PWRC_TRIGGER_EN);
}
static void sirfsoc_set_sleep_mode(u32 mode)
{
u32 sleep_mode = sirfsoc_rtc_iobrg_readl(sirfsoc_pwrc_base + SIRFSOC_PWRC_PDN_CTRL);
sleep_mode &= ~(SIRFSOC_SLEEP_MODE_MASK << 1);
sleep_mode |= mode << 1;
sirfsoc_rtc_iobrg_writel(sleep_mode, sirfsoc_pwrc_base +
SIRFSOC_PWRC_PDN_CTRL);
}
//将系统恢复回来后重新开始执行的物理地址存入与SoC相关的寄存器中static int sirfsoc_pre_suspend_power_off(void)
{
u32 wakeup_entry = __pa_symbol(cpu_resume);
sirfsoc_rtc_iobrg_writel(wakeup_entry, sirfsoc_pwrc_base + SIRFSOC_PWRC_SCRATCH_PAD1);
sirfsoc_set_wakeup_source();
sirfsoc_set_sleep_mode(SIRFSOC_DEEP_SLEEP_MODE);// 深度休眠模式
return 0;
}
static int sirfsoc_pm_enter(suspend_state_t state)
{
switch (state) {
case PM_SUSPEND_MEM:
sirfsoc_pre_suspend_power_off();
outer_disable();
/* go zzz */
cpu_suspend(0, sirfsoc_finish_suspend);
outer_resume();
break;
default:
return -EINVAL;
}
return 0;
}
static const struct platform_suspend_ops sirfsoc_pm_ops = {
.enter = sirfsoc_pm_enter,
.valid = suspend_valid_only_mem,
};
static const struct of_device_id pwrc_ids[] = {
{ .compatible = "sirf,prima2-pwrc" },
{}
};
static int __init sirfsoc_of_pwrc_init(void)
{
struct device_node *np; np = of_find_matching_node(NULL, pwrc_ids);
if (!np) {
pr_err("unable to find compatible sirf pwrc node in dtb
");
return -ENOENT;
}
/*
* pwrc behind rtciobrg is not located in memory space
* though the property is named reg. reg only means base
* offset for pwrc. then of_iomap is not suitable here.
*/
if (of_property_read_u32(np, "reg", &sirfsoc_pwrc_base))
panic("unable to find base address of pwrc node in dtb
");
of_node_put(np);
return 0;
}
static const struct of_device_id memc_ids[] = {
{ .compatible = "sirf,prima2-memc" },
{}
};
static int sirfsoc_memc_probe(struct platform_device *op)
{
struct device_node *np = op->dev.of_node;
sirfsoc_memc_base = of_iomap(np, 0);
if (!sirfsoc_memc_base)
panic("unable to map memc registers
");
return 0;
}
static struct platform_driver sirfsoc_memc_driver = {
.probe = sirfsoc_memc_probe,
.driver = {
.name = "sirfsoc-memc",
.of_match_table = memc_ids,
},
};
static int __init sirfsoc_memc_init(void)
{
return platform_driver_register(&sirfsoc_memc_driver);
}
int __init sirfsoc_pm_init(void)
{
sirfsoc_of_pwrc_init();
sirfsoc_memc_init();
suspend_set_ops(&sirfsoc_pm_ops);
return 0;
}
分析: 在系统重新恢复中,会执行cpu_resume这段汇编,并进行设置唤醒源等操作。而cpu_suspend(0, sirfsoc_finish_suspend);以及其中调用的与SoC相关的用汇编实现的函数sirfsoc_finish_suspend()真正完成最后的待机并将系统置于深度睡眠,同时置SDRAM于自刷新状态的过程,具体的代码高度依赖于特定的芯片,其实现一般是一段汇编。arch/arm/mach-prima2/sleep.S/*
* sleep mode for CSR SiRFprimaII
*
* Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company.
*
* Licensed under GPLv2 or later.
*/
#include
#include
#include
#include "pm.h"
#define DENALI_CTL_22_OFF 0x58
#define DENALI_CTL_112_OFF 0x1c0
.text
ENTRY(sirfsoc_finish_suspend) @入口
@ r5: mem controller
ldr r0, =sirfsoc_memc_base
ldr r5, [r0]
@ r6: pwrc base offset
ldr r0, =sirfsoc_pwrc_base
ldr r6, [r0]
@ r7: rtc iobrg controller
ldr r0, =sirfsoc_rtciobrg_base
ldr r7, [r0]
@ Read the power control register and set the
@ sleep force bit.
add r0, r6, #SIRFSOC_PWRC_PDN_CTRL
bl __sirfsoc_rtc_iobrg_readl
orr r0,r0,#SIRFSOC_PWR_SLEEPFORCE
add r1, r6, #SIRFSOC_PWRC_PDN_CTRL
bl sirfsoc_rtc_iobrg_pre_writel
mov r1, #0x1
@ read the MEM ctl register and set the self
@ refresh bit
ldr r2, [r5, #DENALI_CTL_22_OFF]
orr r2, r2, #0x1
@ Following code has to run from cache since
@ the RAM is going to self refresh mode
.align 5
str r2, [r5, #DENALI_CTL_22_OFF]
1:
ldr r4, [r5, #DENALI_CTL_112_OFF]
tst r4, #0x1
bne 1b
@ write SLEEPFORCE through rtc iobridge
str r1, [r7]
@ wait rtc io bridge sync
1:
ldr r3, [r7]
tst r3, #0x01
bne 1b
b .