19.3 CPUIdle驱动
目前的ARM SoC(System on Chip)大多支持几个不同的Idle级别,CPUIdle驱动子系统存在的目的就是对这些Idle状态进行管理,并根据系统的运行情况进入不同的Idle级别。具体SoC的底层CPUIdle驱动实现提供一个Idle级别表,实现各种不同Idle状态的进入和退出流程。
对于Intel系列笔记本计算机,支持ACPI(Advanced Configuration and Power Interface,高级配置和
电源接口),一般有4个不同的C状态(其中C0为操作状态,C1是Halt状态,C2是Stop-Clock状态,C3是
Sleep状态),如表19.3所示。
表19.3 4个不同的C状态
对于ARM,各个SoC对于Idle的实现方法差异比较大,各个SoC对于Idle的实现方法差异比较大,最简单的Idle级别将CPU核置于WFI(wait for interrupt 等待中断发生)状态,因此在默认情况下,若SoC未实现自身的芯片级CPUIdle驱动,则会进入cpu_do_idle(),对于ARM V7,其实现位于arch/arm/mm/proc-v7.S中:
/*
* cpu_v7_do_idle()
*
* Idle the processor (eg, wait for interrupt).
*
* IRQs are already disabled.
*/
ENTRY(cpu_v7_do_idle)
dsb @ WFI may enter a low-power mode
wfi
ret lr
ENDPROC(cpu_v7_do_idle)
CPUIdle的核心层提供如下API以用于注册一个cpuidle_driver的实例:
linux/cpuidle.h
extern int cpuidle_register_driver(struct cpuidle_driver *drv);
drivers/cpuidle/driver.c
/**
* cpuidle_register_driver – registers a driver
* @drv: a pointer to a valid struct cpuidle_driver
*
* Register the driver under a lock to prevent concurrent attempts to
* [un]register the driver from occuring at the same time.
*
* Returns 0 on success, a negative error code (returned by
* __cpuidle_register_driver()) otherwise.
*/
int cpuidle_register_driver(struct cpuidle_driver *drv)
{
int ret;
spin_lock(&cpuidle_driver_lock); //
自旋锁锁定期间不允许阻塞,要求临界区比较小
ret =
__cpuidle_register_driver(drv);
spin_unlock(&cpuidle_driver_lock);
return ret;
}
EXPORT_SYMBOL_GPL(cpuidle_register_driver);
提供如下API来注册一个cpuidle_device:
linux/cpuidle.h
extern int cpuidle_register_device(struct cpuidle_device *dev);
drivers/cpuidle/cpuidle.c
/**
* cpuidle_register_device – registers a CPU’s idle PM feature
* @dev: the cpu
*/
int cpuidle_register_device(struct cpuidle_device *dev)
{
int ret = -EBUSY;
if (!dev)
return -EINVAL;
mutex_lock(&cpuidle_lock);
if (dev->registered)
goto out_unlock;
__cpuidle_device_init(dev);
ret = __cpuidle_register_device(dev);
if (ret)
goto out_unlock;
ret = cpuidle_add_sysfs(dev);
if (ret)
goto out_unregister;
ret = cpuidle_enable_device(dev);
if (ret)
goto out_sysfs;
cpuidle_install_idle_handler();
out_unlock:
mutex_unlock(&cpuidle_lock);
return ret;
out_sysfs:
cpuidle_remove_sysfs(dev);
out_unregister:
__cpuidle_unregister_device(dev);
goto out_unlock;
}
备注:
CPUIdle驱动必须针对每个CPU注册相应的cpuidle_device,这意味着对于多核CPU,需要针对每个CPU注册一次。
cpuidle_register_driver()接受1个cpuidle_driver结构体的指针参数,该结构体是CPUIdle驱动的主体,
其定义如代码清单19.4所示。
代码清单19.4 cpuidle_driver结构体
struct cpuidle_driver {
const char *name;
struct module *owner;
int refcnt;
/* used by the cpuidle framework to setup the broadcast timer */
unsigned int bctimer:1;
/* states array must be ordered in decreasing power consumption */
struct cpuidle_state states[CPUIDLE_STATE_MAX];
int state_count;
int safe_state_index;
/* the driver handles the cpus in cpumask */
struct cpumask *cpumask;
};
该结构体的关键成员是1个cpuidle_state的表,该表就是用于存储各种不同Idle级别的信息,定义如代码清单19.5所示。
代码清单19.5 cpuidle_state结构体
struct cpuidle_state {
char name[CPUIDLE_NAME_LEN]; // Idle状态的名称
char desc[CPUIDLE_DESC_LEN];Idle状态的描述
unsigned int flags;
unsigned int exit_latency; /* in US */ //退出该Idle状态需要的延迟
int power_usage; /* in mW */
unsigned int target_residency; /* in US */
bool disabled; /* disabled on all CPUs */
int (*enter)(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index);//进入该Idle状态的实现方法。
int (*enter_dead) (struct cpuidle_device *dev, int index);
};
一个具体的AT91 SoC的CPUIdle驱动实例drivers/cpuidle/cpuidle-at91.c,有两个Idle级别,即WFI和RAM_SR,其具体实现如代码清单19.6所示。
#define AT91_MAX_STATES 2
static void (*at91_standby)(void); // 函数指针
/* Actual code that puts the SoC in different idle states */
static int at91_enter_idle(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
at91_standby(); //调用函数指针
return index;
}
static struct cpuidle_driver at91_idle_driver = {
.name = “at91_idle”,
.owner = THIS_MODULE,
.states[0] = ARM_CPUIDLE_WFI_STATE,
.states[1] = {
.enter = at91_enter_idle,
.exit_latency = 10,
.target_residency = 10000,
.flags = CPUIDLE_FLAG_TIME_VALID,
.name = “RAM_SR“,
.desc = “WFI and DDR Self Refresh”,
},
.state_count = AT91_MAX_STATES,
};
/* Initialize CPU idle by registering the idle states */
static int at91_cpuidle_probe(struct platform_device *dev)
{
at91_standby = (void *)(dev->dev.platform_data); // 给函数指针赋初值
return cpuidle_register(&at91_idle_driver, NULL); // 注册CPUIdel
}
static struct platform_driver at91_cpuidle_driver = {
.driver = {
.name = “cpuidle-at91”,
.owner = THIS_MODULE,
},
.probe = at91_cpuidle_probe,
};
module_platform_driver(at91_cpuidle_driver);
在CPUIdle子系统中也有对应的governor(调节器)来抉择何时进入何种Idle级别的策略,这些governor包括CPU_IDLE_GOV_LADDER、CPU_IDLE_GOV_MENU。LADDER在进入和退出Idle级别的时候是步进的,以过去的Idle时间作为参考,适用于没有采用动态时间节拍的系统(即没有选择NO_HZ的系统),不依赖于NO_HZ配置选项;MENU总是根据预期的空闲时间直接进入目标Idle级别,依赖于内核的NO_HZ选项。
图19.3演示LADDER步进从C0进入C3,而MENU则可能直接从C0跳入C3。
图19.3 LADDER与MENU的区别
CPUIdle子系统还通过sys向userspace导出了一些节点:
一类是针对整个系统的/sys/devices/system/cpu/cpuidle,通过其中的current_driver、current_governor、available_governors等节点获取或设置CPUIdle的驱动信息以及governor。
一类是针对每个CPU的/sys/devices/system/cpu/cpux/cpuidle,通过子节点暴露各个在线的CPU中每个不同Idle级别的name、desc、power、latency等信息。
Linux CPUIdle子系统的总体架构,如图19.4所示。
图19.4 Linux CPUIdle子系统的整体架构
今天的文章第19章 Linux电源管理的系统架构和驱动之CPUIdle驱动分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/9722.html