Linux电源管理之 Suspend

作者简介:Loopers,码龄11年,喜欢研究内核基本原理

  • suspend 代码入口

    • state_store函数分析

    • pm_suspend函数分析

    • enter_state函数分析

  • suspend_prepare 函数分析

  • suspend_devices_and_enter 函数分析

    • dpm_suspend_start 函数分析

    • suspend_enter 函数分析

我们知道Linux电源管理存在的几种方式,如何查看这几种方式,以及最后的如何睡眠唤醒等。通过echo mem > /sys/power/state就可以达到睡眠,所以可以根据此节点的sys代码分析suspend的流程。

suspend 代码入口

执行如下命令:

echo mem > /sys/power/state

根据sys节点的属性命令规则,可以得到此节点的实现代码为:  state_store

state_store函数分析

static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
      const char *buf, size_t n)
{
 suspend_state_t state;
 int error;
 
 error = pm_autosleep_lock();
 if (error)
  return error;
 
 if (pm_autosleep_state() > PM_SUSPEND_ON) {
  error = -EBUSY;
  goto out;
 }
 
 state = decode_state(buf, n);
 if (state < PM_SUSPEND_MAX)
  error = pm_suspend(state);
 else if (state == PM_SUSPEND_MAX)
  error = hibernate();
 else
  error = -EINVAL;
 
 out:
 pm_autosleep_unlock();
 return error ? error : n;
}
  1. pm_autosleep_lock

int pm_autosleep_lock(void)
{
 return mutex_lock_interruptible(&autosleep_lock);
}

获得autosleep锁,锁住autosleep功能,此功能在后面分析。

  1. 判断当前autosleep的状态,如果当前状态大于PM_SUSPEND_ON则,返回退出。关于suspend的状态如下:

#define PM_SUSPEND_ON  ((__force suspend_state_t) 0)
#define PM_SUSPEND_FREEZE ((__force suspend_state_t) 1)
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 2)
#define PM_SUSPEND_MEM  ((__force suspend_state_t) 3)
#define PM_SUSPEND_MIN  PM_SUSPEND_FREEZE
#define PM_SUSPEND_MAX  ((__force suspend_state_t) 4)
  1. 解析当前传入的state。如果state小于PM_SUSPEND_MAX就走suspend流程,等于PM_SUSPEND_MAX就走hibernate流程。假如我们传入的是mem, 则就会走suspend流程。

pm_suspend函数分析

int pm_suspend(suspend_state_t state)
{
 int error;
 
 if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
  return -EINVAL;
 
 pm_suspend_marker("entry");
 error = enter_state(state);
 if (error) {
  suspend_stats.fail++;
  dpm_save_failed_errno(error);
 } else {
  suspend_stats.success++;
 }
 pm_suspend_marker("exit");
 return error;
}
  1. 依然会再次判断当前的state是否在PM_SUSPEND_ON和PM_SUSPEND_MAX之间

  2. pm_suspend_marker("entry")

static void pm_suspend_marker(char *annotation)
{
 struct timespec ts;
 struct rtc_time tm;
 
 getnstimeofday(&ts);
 rtc_time_to_tm(ts.tv_sec, &tm);
 pr_info("PM: suspend %s %d-%02d-%02d %02d:%02d:%02d.%09lu UTC\n",
  annotation, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
  tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
}

在suspend之间记录时间,用于统计或者调试suspend花费的时间。

  1. 调用enter_state进入suspend的下一步,如果执行suspend成功,增加suspend.success的引用计数,否则增加suspend.fail的引用计数。

enter_state函数分析

static int enter_state(suspend_state_t state)
{
 int error;
 
 trace_suspend_resume(TPS("suspend_enter"), state, true);
 if (state == PM_SUSPEND_FREEZE) {
#ifdef CONFIG_PM_DEBUG
  if (pm_test_level != TEST_NONE && pm_test_level <= TEST_CPUS) {
   pr_warning("PM: Unsupported test mode for freeze state,"
       "please choose none/freezer/devices/platform.\n");
   return -EAGAIN;
  }
#endif
 } else if (!valid_state(state)) {
  return -EINVAL;
 }
 if (!mutex_trylock(&pm_mutex))
  return -EBUSY;
 
 if (state == PM_SUSPEND_FREEZE)
  freeze_begin();
 
 trace_suspend_resume(TPS("sync_filesystems"), 0, true);
 printk(KERN_INFO "PM: Syncing filesystems ... ");
 sys_sync();
 printk("done.\n");
 trace_suspend_resume(TPS("sync_filesystems"), 0, false);
 
 pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
 error = suspend_prepare(state);
 if (error)
  goto Unlock;
 
 if (suspend_test(TEST_FREEZER))
  goto Finish;
 
 trace_suspend_resume(TPS("suspend_enter"), state, false);
 pr_debug("PM: Entering %s sleep\n", pm_states[state]);
 pm_restrict_gfp_mask();
 error = suspend_devices_and_enter(state);
 pm_restore_gfp_mask();
 
 Finish:
 pr_debug("PM: Finishing wakeup.\n");
 suspend_finish();
 Unlock:
 mutex_unlock(&pm_mutex);
 return error;
}
  1. 通过vaild_state函数用来判断该平台是否支持该状态睡眠。

static bool valid_state(suspend_state_t state)
{
 return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}
  1. 调用mutex_trylock获得一个mutex锁,防止在suspend的时候再次suspend。

  2. 如果当前state是PM_SUSPEND_FREEZE,则调用freeze_begin做开始准备工作。

  3. 同步文件系统。

  4. 调用suspend_prepare做进一步suspend前期准备工作,准备控制台,冻结内核线程等。

  5. 调用suspend_devices_and_enter做设备以及系统相关的susupend操作。

  6. 调用suspend_finish做最后的恢复工作。

suspend_prepare 函数分析

static int suspend_prepare(suspend_state_t state)
{
 int error;
 
 if (!sleep_state_supported(state))
  return -EPERM;
 
 pm_prepare_console();
 
 error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
 if (error)
  goto Finish;
 
 trace_suspend_resume(TPS("freeze_processes"), 0, true);
 error = suspend_freeze_processes();
 trace_suspend_resume(TPS("freeze_processes"), 0, false);
 if (!error)
  return 0;
 
 suspend_stats.failed_freeze++;
 dpm_save_failed_step(SUSPEND_FREEZE);
 Finish:
 pm_notifier_call_chain(PM_POST_SUSPEND);
 pm_restore_console();
 return error;
}
  1. 检测该平台suspend_ops是否实现了enter函数。

static bool sleep_state_supported(suspend_state_t state)
{
 return state == PM_SUSPEND_FREEZE || (suspend_ops && suspend_ops->enter);
}
  1. 调用pm_prepare_console函数切换控制台,重新分配一个suspend模式下控制台,然后重定向kmsg。

  2. 通过调用pm通知链,发送PM_SUSPEND_PREPARE消息。

int pm_notifier_call_chain(unsigned long val)
{
 int ret = blocking_notifier_call_chain(&pm_chain_head, val, NULL);
 
 return notifier_to_errno(ret);
}

那谁会收到这类消息呢? 只有通过register_pm_notifier的设备,子系统会在这个时候处理自己的事情。

int register_pm_notifier(struct notifier_block *nb)
{
 return blocking_notifier_chain_register(&pm_chain_head, nb);
}
  1. 调用suspend_freeze_processes冻结userhelper进程,已经内核线程。如果冻结出现失败,记录失败的引用计数。

  2. 接着会通过通知链恢复suspend,已经恢复控制台。

suspend_devices_and_enter 函数分析

int suspend_devices_and_enter(suspend_state_t state)
{
 int error;
 bool wakeup = false;
 
 if (!sleep_state_supported(state))
  return -ENOSYS;
 
 error = platform_suspend_begin(state);
 if (error)
  goto Close;
 
 suspend_console();
 suspend_test_start();
 error = dpm_suspend_start(PMSG_SUSPEND);
 if (error) {
  pr_err("PM: Some devices failed to suspend, or early wake event detected\n");
  log_suspend_abort_reason("Some devices failed to suspend, or early wake event detected");
  goto Recover_platform;
 }
 suspend_test_finish("suspend devices");
 if (suspend_test(TEST_DEVICES))
  goto Recover_platform;
 
 do {
  error = suspend_enter(state, &wakeup);
 } while (!error && !wakeup && platform_suspend_again(state));
 
 Resume_devices:
 suspend_test_start();
 dpm_resume_end(PMSG_RESUME);
 suspend_test_finish("resume devices");
 trace_suspend_resume(TPS("resume_console"), state, true);
 resume_console();
 trace_suspend_resume(TPS("resume_console"), state, false);
 
 Close:
 platform_resume_end(state);
 return error;
 
 Recover_platform:
 platform_recover(state);
 goto Resume_devices;
}
  1. 调用sleep_state_supported函数判断当前平台是否实现了suspend_ops,已经suspend_ops->enter函数。

  2. 如果当前状态是freeze,就调用freeze_ops的begin函数。否则就调用平台相关的begin函数。这里的begin主要是各个平台pm的一些设置,每个平台的操作都不一样,这里不详细说明。

  3. 调用suspend_console挂起控制台,防止其它代码访问该控制台。

  4. 调用suspend_test_start记录当前suspend刚开始的时候的时间,使用jiffies表示。

  5. 调用dpm_suspend_start函数,该函数主要是调用所有设备的prepare和suspend回调函数。如果出现suspend失败,则会打印"fail suspend"的log,以及调用platform_recover函数执行平台相关的recover回调。

  6. 调用suspend_enter使整个系统进入suspend状态。

dpm_suspend_start 函数分析

int dpm_suspend_start(pm_message_t state)
{
 int error;
 
 error = dpm_prepare(state);
 if (error) {
  suspend_stats.failed_prepare++;
  dpm_save_failed_step(SUSPEND_PREPARE);
 } else
  error = dpm_suspend(state);
 return error;
}
  1. 调用dpm_prepare函数,执行所有设备的prepare回调函数。执行顺序是pm_domain-type-class-bus-driver,如果失败设置failed_prepare的引用计数值。

  2. 调用dpm_suspend函数,执行所有设备的suspend回调函数。

suspend_enter 函数分析

上面对dpm_suspend_start函数进行了分析,该函数中主要是调用所有设备的prepare和suspend回调函数。而在suspend_enter主要是使系统进入到suspend中。

static int suspend_enter(suspend_state_t state, bool *wakeup)
{
 char suspend_abort[MAX_SUSPEND_ABORT_LEN];
 int error, last_dev;
 
 error = platform_suspend_prepare(state);
 if (error)
  goto Platform_finish;
 
 error = dpm_suspend_late(PMSG_SUSPEND);
 if (error) {
  last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
  last_dev %= REC_FAILED_NUM;
  printk(KERN_ERR "PM: late suspend of devices failed\n");
  log_suspend_abort_reason("%s device failed to power down",
   suspend_stats.failed_devs[last_dev]);
  goto Platform_finish;
 }
 error = platform_suspend_prepare_late(state);
 if (error)
  goto Devices_early_resume;
 
 error = dpm_suspend_noirq(PMSG_SUSPEND);
 if (error) {
  last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
  last_dev %= REC_FAILED_NUM;
  printk(KERN_ERR "PM: noirq suspend of devices failed\n");
  log_suspend_abort_reason("noirq suspend of %s device failed",
   suspend_stats.failed_devs[last_dev]);
  goto Platform_early_resume;
 }
 error = platform_suspend_prepare_noirq(state);
 if (error)
  goto Platform_wake;
 
 if (suspend_test(TEST_PLATFORM))
  goto Platform_wake;
 
 if (state == PM_SUSPEND_FREEZE) {
  trace_suspend_resume(TPS("machine_suspend"), state, true);
  freeze_enter();
  trace_suspend_resume(TPS("machine_suspend"), state, false);
  goto Platform_wake;
 }
 
 error = disable_nonboot_cpus();
 if (error || suspend_test(TEST_CPUS)) {
  log_suspend_abort_reason("Disabling non-boot cpus failed");
  goto Enable_cpus;
 }
 
 arch_suspend_disable_irqs();
 BUG_ON(!irqs_disabled());
 
 error = syscore_suspend();
 if (!error) {
  *wakeup = pm_wakeup_pending();
  if (!(suspend_test(TEST_CORE) || *wakeup)) {
   trace_suspend_resume(TPS("machine_suspend"),
    state, true);
   error = suspend_ops->enter(state);
   trace_suspend_resume(TPS("machine_suspend"),
    state, false);
   events_check_enabled = false;
  } else if (*wakeup) {
   pm_get_active_wakeup_sources(suspend_abort,
    MAX_SUSPEND_ABORT_LEN);
   log_suspend_abort_reason(suspend_abort);
   error = -EBUSY;
  }
  syscore_resume();
 }
 
 arch_suspend_enable_irqs();
 BUG_ON(irqs_disabled());
 
 Enable_cpus:
 enable_nonboot_cpus();
 
 Platform_wake:
 platform_resume_noirq(state);
 dpm_resume_noirq(PMSG_RESUME);
 
 Platform_early_resume:
 platform_resume_early(state);
 
 Devices_early_resume:
 dpm_resume_early(PMSG_RESUME);
 
 Platform_finish:
 platform_resume_finish(state);
 return error;
}
  1. 调用平台相关prepare回调函数,如果平台prepare设置失败,在调用平台相关的finish回调函数。

  2. 调用dpm_suspend_late函数。此函数主要调用dpm_suspend_list中的设备的suspend_late回调函数,然后又将这些设备加入到dpm_late_early_list链表中。如果出现失败,则跳到platform_finish做恢复工作。

  3. 如果当前休眠状态是PM_SUSPEND_FREEZE的话,调用freeze_ops中的prepare回调。

  4. 调用dpm_suspend_noirq函数,此函数主要是从dpm_late_early_list链表中取一个设备,然后调用该设备的suspend_noirq回调,同时将该设备加入到dpm_noirq_list链表中。

  5. 回调平台相关的preplate_late函数,做suspend最后关头的事情。

  6. 如果休眠状态是PM_SUSPEND_FREEZE,则frozen processes + suspended devices + idle processors

  7. disable所有非nonboot的CPU,失败之后启动CPU。

  8. 关掉全局cpu中断,如果关掉中断,则报BUG

  9. 执行所有system core的suspend回调函数。

  10. 如果执行成功的话,这时候系统还会调用pm_wakeup_pending检查下,是否有唤醒事件发生,如果发生,停止suspend,恢复系统。

  11. 调用suspend_ops->enter函数,此函数会回调系统suspend函数,一般在ATF里实现。

  12. 这时候系统已经睡眠,如果这时候有唤醒事件发生,比如按下手机的power按键,系统又会接着suspend的地方,再次往下执行。也就是suspend的一些列反操作。

最后用一张图来总结Suspend的流程:

7744c8edc4113d08c04235064b7acecd.png
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值