Linux电源管理(3)

  1. CPU热插拔
    1. 判断自己的核
    2. G核和LP核集群的切换时机
  2. 挂起到RAM
  3. 运行时的PM

CPU热插拔

CPU热插拔功能在linux中已经存在很久了,一般来讲,在用户空间可以通过/sys/devices/system/cpu/cpun/online节点来操作一个CPU的在线与离线。

1
2
3
# echo 0 > /sys/devices/system/cpu/cpu1/online
CPU 1 is now offline
# echo 1 > /sys/devices/system/cpu/cpu1/online

在嵌入式系统中,CPU热插拔可以作为一种省电的方式,在需要时再开启更多的CPU。对于芯片公司来说,也针对热插拔功能加入架构上的支持。多核心的处理器通常包含为高性能设计的G核和为低功耗设计的LP核。在内核中,会根据运行负载进行G核与LP核的集群切换。

要实现CPU热插拔通常需要关注如下这几个问题:

  1. 如何判断自己是什么核?
  2. G核和LP核集群的切换时机?
  3. G核何时进行动态热插拔?

判断自己的核

每个核都可以通过调用is_lp_cluster()来判断当前正在执行的CPU是LP还是G处理器:

1
2
3
4
5
6
static inline unsigned int is_lp_cluster(void)
{
unsigned int reg;
reg = readl(FLOW_CTRL_CLUSTER_CONTROL);
return (reg & 1); /* 0 == G, 1 == LP */
}

G核和LP核集群的切换时机

  1. 从LP核切换到G核: 当前执行于LP集群,CPUFreq驱动判断出LP核需要增加频率到超过高值门限
  2. 从G核切换到LP核:当前执行于G集群,CPUFreq驱动判断出G核需要降低频率到低于低值门限
  3. G核的动态插拔1:当执行于G集群,CPUFreq驱动判断出G核需要降低频率到低于低值门限,且最慢的CPUID小于nr_cpu_ids,关闭最慢的CPU。
  4. G核的动态插拔2:当执行于G集群,CPUFreq驱动判断出某G核需要设置频率大于高值门限,根据负载平衡状态,可以再开一个核或关闭一个核。

挂起到RAM

Linux支持STANDBY、挂起到RAM、挂起到硬盘等形式的待机。一般的嵌入式产品只实现了挂起到RAM(也称s2ram或STR),即将系统状态保存于内存,并将SDRAM置于自刷新状态。而挂起到硬盘(STD)则是把系统状态保存于硬盘,然后关闭整个系统。

在内核中,大致的挂起到RAM的挂起和恢复流程如下所示:

挂起到RAM

在内核的device_driver结构中,有一个pm成员,它是一个dev_pm_ops结构体指针,其中封装了挂起到RAM和挂起的硬盘所需要的所有回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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_late)(struct device *dev);
int (*resume_early)(struct device *dev);
int (*freeze_late)(struct device *dev);
int (*thaw_early)(struct device *dev);
int (*poweroff_late)(struct device *dev);
int (*restore_early)(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);
};

在各个具体设备的驱动实现中,通常会实现相关的suspend及resume接口,并赋值给dev_pm_ops结构体指针,用于对设备的挂起等操作。除此外,linux中,总线驱动上仍然保留着过时的suspend和resume等接口。

在调试过程中,可以使能内核的PM_DEBUG选项,同时在Bootloader传递给内核的bootargs中设置标志no_console_suspend,即可看到内核的相关打印信息。

在将linux移植到一个新的ARM SoC的过程中,最终系统挂起的入口需要由芯片供应商在相应的arch/arm/mach-xxx中实现platform_suspend_ops的成员函数,一般主要实现其中的enter和valid成员,具体可参考相应目录下的pm.c示例。

运行时的PM

dev_pm_ops结构中包含3个以runtime开头的成员: runtime_suspend、runtime_resume和runtime_idle,它们是辅助设备完成运行时的电源管理的。

运行时的PM与前文挂起操作不太一样,它是针对单个设备,指系统在非睡眠状态下的情况下,某个设备在空闲时可以进入运行时挂起状态,而在忙时执行运行时恢复使得设备进入正常工作状态。

我们可以这样理解Linux的运行时PM机制,每个设备都有引用计数usage_count和活跃子设备计数child_count,当两个计数都为0时,就进入空闲状态,调用pm_request_idle(dev)。当设备进入空闲状态,与pm_request_idle(dev)对应的PM核并不一定直接调用设备驱动的runtime_suspend(),它实际上在多数情况下是调用与该设备对应的bus_type的runtime_idle()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static pm_callback_t __rpm_get_callback(struct device *dev, size_t cb_offset)
{
pm_callback_t cb;
const struct dev_pm_ops *ops;

if (dev->pm_domain)
ops = &dev->pm_domain->ops;
else if (dev->type && dev->type->pm)
ops = dev->type->pm;
else if (dev->class && dev->class->pm)
ops = dev->class->pm;
else if (dev->bus && dev->bus->pm)
ops = dev->bus->pm;
else
ops = NULL;

if (ops)
cb = *(pm_callback_t *)((void *)ops + cb_offset);
else
cb = NULL;

if (!cb && dev->driver && dev->driver->pm)
cb = *(pm_callback_t *)((void *)dev->driver->pm + cb_offset);

return cb;
}

据此可知,bus_type级的回调可以被pm_domain、type、class覆盖掉,这些都统称为子系统。bus_type等子系统级别的runtime_idle行为完全由相应的总线类型、设备分类和pm_domain因素决定,但一般的行为是子系统级别的runtime_idle()会调度设备驱动的runtime_suspend()。

在具体设备驱动中,一般的用法则是在设备驱动的probe()时运行pm_runtime_enable()使能运行时PM支持,在运行过程中动态执行”pm_runtime_get_xxx()->做工作->pm_runtime_put_xxx()”的序列。可参考drivers/watchdog/omap_wdt.c中运行时PM的实现。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com

文章标题:Linux电源管理(3)

本文作者:红尘追风

发布时间:2017-01-28, 18:51:30

原始链接:http://www.micernel.com/2017/01/28/Linux%E7%94%B5%E6%BA%90%E7%AE%A1%E7%90%86(3)/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录