CPUFreq是一种实时的电压和频率调节技术,也叫DVFS(Dynamic Voltage and Frequency Scaling)动态电压频率调节。
随着技术的发展,CPU的频率越来越高,性能越来越好,芯片制造工艺也越来越先进。但高性能的同时也带来高发热。其实移动嵌入式设备并不需要时刻保持高性能。因此,需要一种机制,实现动态地调节频率和电压,以实现性能和功耗的平衡。
和一般的linux子系统类似,CPUFreq采用了机制与策略分离的设计架构。分为三个模块:
cpufreq core: 对cpufreq governors和cpufreq drivers进行了封装和抽象并定义了清晰的接口,从而在设计上完成了对机制和策略的分离。
cpufreq drivers:位于cpucore的底层,用于设置具体cpu硬件的频率。通过cpufreq driver可以使cpu频率得到调整。cpufreq driver借助Linux Cpufreq标准子系统中的cpufreq_driver结构体,完成cpu调频驱动的注册及实现。
cpufreq governor:位于cpucore的上层,用于CPU升降频检测,根据系统和负载,决定cpu频率要调节到多少。cpufreq governor借助于linux cpufreq子系统中cpufreq_governor结构体,完成了cpu调频策略的注册和实现。
linux cpufreq通过向系统注册实现cpufreq driver和cpufreq governor。cpu governor实现调频的策略,cpu driver实现调频的实际操作,从而完成动态调节频率和电压。一般情况下,优先调节频率,频率无法满足,再调节电压以实现调频。
cpufreq相关的节点位于/sys/devices/system/cpu/cpu0/cpufreq目录下:
$ cd /sys/devices/system/cpu/cpu0/cpufreq
可以看到以下节点:
shell@tiny4412:/sys/devices/system/cpu/cpu0/cpufreq # ls
affected_cpus
cpuinfo_cur_freq
cpuinfo_max_freq
cpuinfo_min_freq
cpuinfo_transition_latency
related_cpus
scaling_available_governors
scaling_cur_freq
scaling_driver
scaling_governor
scaling_max_freq
scaling_min_freq
scaling_setspeed
stats
CPUFreq子系统将一些共同的逻辑代码组织在一起,构成了CPUFreq核心模块。这些公共逻辑模块向CPUFreq和其它内核模块提供了必要的API完成一个完整的CPUFreq子系统。这一节我们分析CPUFreq核心层的一些重要API的实现及使用。
代码位置:
/drivers/cpufreq/cpufreq.c
static int __init cpufreq_core_init(void)
{
int cpu;
if (cpufreq_disabled())
return -ENODEV;
for_each_possible_cpu(cpu) {
per_cpu(cpufreq_policy_cpu, cpu) = -1;
init_rwsem(&per_cpu(cpu_policy_rwsem, cpu));
}
cpufreq_global_kobject = kobject_create_and_add("cpufreq", &cpu_subsys.dev_root->kobj);
BUG_ON(!cpufreq_global_kobject);
#if defined(CONFIG_ARCH_SUNXI) && defined(CONFIG_HOTPLUG_CPU)
/* register reboot notifier for process cpus when reboot */
register_reboot_notifier(&reboot_notifier);
#endif
return 0;
}
core_initcall(cpufreq_core_init);
可见,CPUFreq子系统在系统启动阶段由Initcall机制调用完成核心部分的初始化工作。cpufreq_policy_cpu是一个per_cpu变量,在smp系统下,每个cpu可以有自己独立的policy,也可以与其它cpu共用一个policy。通过kobject_create_and_add函数建立cpufreq节点,这与我们之前看到的sys下的cpufreq节点相吻合。该节点以后会用来放其它一些参数。
参数cpu_subsys是内核的一个全局变量,是由更早期的初始化时初始化的,代码在drivers/base/cpu.c中:
struct bus_type cpu_subsys = {
.name = "cpu",
.dev_name = "cpu",
};
EXPORT_SYMBOL_GPL(cpu_subsys);
void __init cpu_dev_init(void)
{
if (subsys_system_register(&cpu_subsys, cpu_root_attr_groups))
panic("Failed to register CPU subsystem");
cpu_dev_register_generic();
}
这将会建立一根cpu总线,总线下挂着系统中所有的cpu,cpu总线设备的根目录就位于:/sys/devices/system/cpu,同时,/sys/bus下也会出现一个cpu的总线节点。cpu总线设备的根目录下会依次出现cpu0,cpu1,…… cpux节点,每个cpu对应其中的一个设备节点。CPUFreq子系统利用这个cpu_subsys来获取系统中的cpu设备,并在这些cpu设备下面建立相应的cpufreq对象,这个我们在后面再讨论。
这样看来,cpufreq子系统的初始化其实没有做什么重要的事情,只是初始化了几个per_cpu变量和建立了一个cpufreq文件节点。下图是初始化过程的序列图:
系统中可以同时存在多个governor策略,一个policy通过cpufreq_policy结构中的governor指针和某个governor相关联。要想一个governor被policy使用,首先要把该governor注册到cpufreq的核心中,我们可以通过核心层提供的API来完成注册:
int cpufreq_register_governor(struct cpufreq_governor *governor)
{
int err;
if (!governor)
return -EINVAL;
if (cpufreq_disabled())
return -ENODEV;
mutex_lock(&cpufreq_governor_mutex);
governor->initialized = 0;
err = -EBUSY;
if (__find_governor(governor->name) == NULL) {
err = 0;
list_add(&governor->governor_list, &cpufreq_governor_list);
}
mutex_unlock(&cpufreq_governor_mutex);
return err;
}
核心层定义了一个全局链表变量:cpufreq_governor_list,注册函数首先根据governor的名称,通过__find_governor()函数查找该governor是否已經被注册过,如果没有被注册过,则把代表该governor的结构体添加到cpufreq_governor_list链表中。
与governor不同,系统中只会存在一个cpufreq_driver驱动,cpufreq_driver是平台相关的,负责最终实施频率的调整动作,而选择工作频率的策略是由governor完成的。所以,系统中只需要注册一个cpufreq_driver即可,它只负责如何控制该平台的时钟系统,从而设定由governor确定的工作频率。核心提供了一个API:cpufreq_register_driver来完成注册工作。
下面我们分析一下这个函数的工作过程:
int cpufreq_register_driver(struct cpufreq_driver *driver_data)
{
unsigned long flags;
int ret;
if (cpufreq_disabled())
return -ENODEV;
// 从代码可以看到,verify和init回调函数必须要实现,而setpolicy和target回调则至少要被实现其中的一个。
if (!driver_data || !driver_data->verify || !driver_data->init ||
((!driver_data->setpolicy) && (!driver_data->target)))
return -EINVAL;
pr_debug("trying to register driver %s\n", driver_data->name);
if (driver_data->setpolicy)
driver_data->flags |= CPUFREQ_CONST_LOOPS;
write_lock_irqsave(&cpufreq_driver_lock, flags);
//检查全局变量cpufreq_driver是否已经被赋值,如果没有,则传入的参数被赋值给全局变量cpufreq_driver,从而保证了系统中只会注册一个cpufreq_driver驱动
if (cpufreq_driver) {
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
return -EBUSY;
}
cpufreq_driver = driver_data;
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
//通过subsys_interface_register给每一个cpu建立一个cpufreq_policy
ret = subsys_interface_register(&cpufreq_interface);
if (ret)
goto err_null_driver;
if (!(cpufreq_driver->flags & CPUFREQ_STICKY)) {
int i;
ret = -ENODEV;
/* check for at least one working CPU */
for (i = 0; i < nr_cpu_ids; i++)
if (cpu_possible(i) && per_cpu(cpufreq_cpu_data, i)) {
ret = 0;
break;
}
/* if all ->init() calls failed, unregister */
if (ret) {
pr_debug("no CPU initialized for driver %s\n",
driver_data->name);
goto err_if_unreg;
}
}
//注册cpu hot plug通知,以便在cpu hot plug的时候,能够动态地处理各个cpu policy之间的关系(比如迁移负责管理的cpu等等)
register_hotcpu_notifier(&cpufreq_cpu_notifier);
pr_debug("driver %s up and running\n", driver_data->name);
return 0;
err_if_unreg:
subsys_interface_unregister(&cpufreq_interface);
err_null_driver:
write_lock_irqsave(&cpufreq_driver_lock, flags);
cpufreq_driver = NULL;
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
return ret;
}
cpufreq_interface结构体如下:
static struct subsys_interface cpufreq_interface = {
.name = "cpufreq",
.subsys = &cpu_subsys,
.add_dev = cpufreq_add_dev,
.remove_dev = cpufreq_remove_dev,
};
subsys_interface_register遍历子系统下面的每一个子设备,然后用这个子设备作为参数,调用cpufrq_interface结构的add_dev回调函数,这里的回调函数被指向了cpufreq_add_dev。
通过__cpufreq_set_policy函数,最终使得该policy正式生效。到这里,每个cpu的policy已经建立完毕,并正式开始工作。
__cpufreq_set_policy函数时序图如下:
int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list);
int cpufreq_unregister_notifier(struct notifier_block *nb, unsigned int list);
以上两个API用于注册和注销cpufreq系统的通知消息,第二个参数可以选择通知的类型,可以有以下两种类型:
- CPUFREQ_TRANSITION_NOTIFIER 收到频率变更通知
- CPUFREQ_POLICY_NOTIFIER 收到policy更新通知
cpufreq_driver_target:用来设置目标频率,实际回调cpufreq的target函数。
int __cpufreq_driver_target(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
int retval = -EINVAL;
unsigned int old_target_freq = target_freq;
if (cpufreq_disabled())
return -ENODEV;
/* Make sure that target_freq is within supported range */
if (target_freq > policy->max)
target_freq = policy->max;
if (target_freq < policy->min)
target_freq = policy->min;
pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n",
policy->cpu, target_freq, relation, old_target_freq);
if (target_freq == policy->cur)
return 0;
if (cpufreq_driver->target)
retval = cpufreq_driver->target(policy, target_freq, relation);
return retval;
}
通常一个驱动工程师驱动需要实现是大多是cpufreq driver,这部有具体的cpu差异。cpufreq driver主要完成平台相关的CPU频率/电压的控制,它在cpufreq framework中是非常简单的一个模块,主要是定义一个struct cpufreq_driver变量,填充必要的字段,并根据平台的特性,实现其中的回调函数。然后注册到系统中去。
cpufreq_driver 结构体如下所示。
struct cpufreq_driver {
struct module *owner; //一般这THIS_MODULE
char name[CPUFREQ_NAME_LEN]; //cpufreq driver名字,如"cpufreq-sunxi"
u8 flags; //标志:可以设置一些值,如CPUFREQ_STICKY,表示就算所有的init调用都失败了,driver也不被remove。
bool have_governor_per_policy;
/* needed by all drivers */
int (*init) (struct cpufreq_policy *policy); //必须实现,用于在cpufreq core在cpu device添加后运行
int (*verify) (struct cpufreq_policy *policy); //必须实现,在当上层软件需要设定一个新的policy的时候,会调用driver的verify回调函数,检查该policy是否合法
/* define one out of two */
int (*setpolicy) (struct cpufreq_policy *policy); //一般不实现
int (*target) (struct cpufreq_policy *policy, //实际的调频函数
unsigned int target_freq,
unsigned int relation);
/* should be defined, if possible */
unsigned int (*get) (unsigned int cpu); //用于获取指定cpu的频率值
/* optional */
unsigned int (*getavg) (struct cpufreq_policy *policy,
unsigned int cpu);
int (*bios_limit) (int cpu, unsigned int *limit);
int (*exit) (struct cpufreq_policy *policy);
int (*suspend) (struct cpufreq_policy *policy);
int (*resume) (struct cpufreq_policy *policy);
struct freq_attr **attr;
}
下面例子填充并实现cpufreq_driver结构体中这些必要成员。
static struct cpufreq_driver sunxi_cpufreq_driver = {
.name = "cpufreq-sunxi",
.flags = CPUFREQ_STICKY,
.init = sunxi_cpufreq_init,
.verify = sunxi_cpufreq_verify,
.target = sunxi_cpufreq_target,
.get = sunxi_cpufreq_get,
.attr = sunxi_cpufreq_attr,
};
先看一下init函数,init函数主要完成从device tree里获取对应的clock,regulator配置最大最小频率等。
device tree配置如下:
cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x0>;
enable-method = "psci";
cpufreq_tbl = < 480000
648000
720000
816000
912000
1008000
1104000
1152000
1200000>;
clock-latency = <2000000>;
clock-frequency = <1008000000>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0 &SYS_SLEEP_0>;
};
Init函数如下:
static int __init sunxi_cpufreq_initcall(void)
{
struct device_node *np;
const struct property *prop;
struct cpufreq_frequency_table *freq_tbl;
const __be32 *val;
int ret, cnt, i;
np = of_find_node_by_path("/cpus/cpu@0");
if (!np) {
CPUFREQ_ERR("No cpu node found\n");
return -ENODEV;
}
if (of_property_read_u32(np, "clock-latency",
&sunxi_cpufreq.transition_latency))
sunxi_cpufreq.transition_latency = CPUFREQ_ETERNAL;
prop = of_find_property(np, "cpufreq_tbl", NULL);
if (!prop || !prop->value) {
CPUFREQ_ERR("Invalid cpufreq_tbl\n");
ret = -ENODEV;
goto out_put_node;
}
cnt = prop->length / sizeof(u32);
val = prop->value;
freq_tbl = kmalloc(sizeof(*freq_tbl) * (cnt + 1), GFP_KERNEL);
if (!freq_tbl) {
ret = -ENOMEM;
goto out_put_node;
}
for (i = 0; i < cnt; i++) {
freq_tbl[i].index = i;
freq_tbl[i].frequency = be32_to_cpup(val++);
}
freq_tbl[i].index = i;
freq_tbl[i].frequency = CPUFREQ_TABLE_END;
sunxi_cpufreq.freq_table = freq_tbl;
#ifdef CONFIG_DEBUG_FS
sunxi_cpufreq.cpufreq_set_us = 0;
sunxi_cpufreq.cpufreq_get_us = 0;
#endif
sunxi_cpufreq.last_freq = ~0;
sunxi_cpufreq.clk_pll = clk_get(NULL, PLL_CPU_CLK);
if (IS_ERR_OR_NULL(sunxi_cpufreq.clk_pll)) {
CPUFREQ_ERR("Unable to get PLL CPU clock\n");
ret = PTR_ERR(sunxi_cpufreq.clk_pll);
goto out_err_clk_pll;
}
sunxi_cpufreq.clk_cpu = clk_get(NULL, CPU_CLK);
if (IS_ERR_OR_NULL(sunxi_cpufreq.clk_cpu)) {
CPUFREQ_ERR("Unable to get CPU clock\n");
ret = PTR_ERR(sunxi_cpufreq.clk_cpu);
goto out_err_clk_cpu;
}
sunxi_cpufreq.vdd_cpu = regulator_get(NULL, CPU_VDD);
if (IS_ERR_OR_NULL(sunxi_cpufreq.vdd_cpu)) {
CPUFREQ_ERR("Unable to get CPU regulator\n");
ret = PTR_ERR(sunxi_cpufreq.vdd_cpu);
/* do not return error even if error*/
}
/* init cpu frequency from dt */
ret = __init_freq_dt();
if (ret == -ENODEV
#ifdef CONFIG_CPU_VOLTAGE_SCALING
|| ret == -EINVAL
#endif
)
goto out_err_dt;
pr_debug("[cpufreq] max: %uMHz, min: %uMHz, ext: %uMHz, boot: %uMHz\n",
sunxi_cpufreq.max_freq / 1000, sunxi_cpufreq.min_freq / 1000,
sunxi_cpufreq.ext_freq / 1000, sunxi_cpufreq.boot_freq / 1000);
#ifdef CONFIG_CPU_VOLTAGE_SCALING
__vftable_show();
sunxi_cpufreq.last_vdd = sunxi_cpufreq_getvolt();
#endif
mutex_init(&sunxi_cpufreq.lock);
ret = cpufreq_register_driver(&sunxi_cpufreq_driver);
if (ret) {
CPUFREQ_ERR("failed register driver\n");
goto out_err_register;
} else {
goto out_put_node;
}
out_err_register:
mutex_destroy(&sunxi_cpufreq.lock);
out_err_dt:
if (!IS_ERR_OR_NULL(sunxi_cpufreq.vdd_cpu)) {
regulator_put(sunxi_cpufreq.vdd_cpu);
}
clk_put(sunxi_cpufreq.clk_cpu);
out_err_clk_cpu:
clk_put(sunxi_cpufreq.clk_pll);
out_err_clk_pll:
kfree(freq_tbl);
out_put_node:
of_node_put(np);
return ret;
}
从上面可以看出,init函数主要的工作是从device tree中获取资源并配置最大最小频率等,然后注册一个cpufreq驱动。
下看看一下cpufreq_frequency_table_verify的实现,该函数主要是确保在policy->min和policy->max之间至少有一个有效
频率,并且所有其他的指标都符合。
static int sunxi_cpufreq_verify(struct cpufreq_policy *policy)
{
return cpufreq_frequency_table_verify(policy, sunxi_cpufreq.freq_table);
}
get函数主要是获取当前cpu频率。
static unsigned int sunxi_cpufreq_get(unsigned int cpu)
{
unsigned int current_freq = 0;
#ifdef CONFIG_DEBUG_FS
ktime_t calltime = ktime_get();
#endif
clk_get_rate(sunxi_cpufreq.clk_pll);
current_freq = clk_get_rate(sunxi_cpufreq.clk_cpu) / 1000;
#ifdef CONFIG_DEBUG_FS
sunxi_cpufreq.cpufreq_get_us = ktime_to_us(ktime_sub(ktime_get(), calltime));
#endif
return current_freq;
}
target是实现调频调压的操作者。
static int sunxi_cpufreq_target(struct cpufreq_policy *policy,
__u32 freq, __u32 relation)
{
int ret = 0;
unsigned int index;
struct cpufreq_freqs freqs;
#ifdef CONFIG_DEBUG_FS
ktime_t calltime;
#endif
#ifdef CONFIG_SMP
int i;
#endif
#ifdef CONFIG_CPU_VOLTAGE_SCALING
unsigned int new_vdd;
#endif
mutex_lock(&sunxi_cpufreq.lock);
/* avoid repeated calls which cause a needless amout of duplicated
* logging output (and CPU time as the calculation process is
* done) */
if (freq == sunxi_cpufreq.last_freq)
goto out;
CPUFREQ_DBG(DEBUG_FREQ, "request frequency is %uKHz\n", freq);
if (unlikely(sunxi_boot_lock))
freq = freq > sunxi_cpufreq.boot_freq ? sunxi_cpufreq.boot_freq : freq;
/* try to look for a valid frequency value from cpu frequency table */
if (cpufreq_frequency_table_target(policy, sunxi_cpufreq.freq_table,
freq, relation, &index)) {
CPUFREQ_ERR("try to look for %uKHz failed!\n", freq);
ret = -EINVAL;
goto out;
}
/* frequency is same as the value last set, need not adjust */
if (sunxi_cpufreq.freq_table[index].frequency == sunxi_cpufreq.last_freq)
goto out;
freq = sunxi_cpufreq.freq_table[index].frequency;
CPUFREQ_DBG(DEBUG_FREQ, "target is find: %uKHz, entry %u\n", freq, index);
/* notify that cpu clock will be adjust if needed */
if (policy) {
freqs.cpu = policy->cpu;
freqs.old = sunxi_cpufreq.last_freq;
freqs.new = freq;
#ifdef CONFIG_SMP
/* notifiers */
for_each_cpu(i, policy->cpus) {
freqs.cpu = i;
cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
}
#else
cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
#endif
}
#ifdef CONFIG_CPU_VOLTAGE_SCALING
/* get vdd value for new frequency */
new_vdd = __get_vdd_value(freq * 1000);
CPUFREQ_DBG(DEBUG_FREQ, "set cpu vdd to %dmv\n", new_vdd);
if (!IS_ERR_OR_NULL(sunxi_cpufreq.vdd_cpu) && (new_vdd > sunxi_cpufreq.last_vdd)) {
CPUFREQ_DBG(DEBUG_FREQ, "set cpu vdd to %dmv\n", new_vdd);
if (regulator_set_voltage(sunxi_cpufreq.vdd_cpu, new_vdd*1000, new_vdd*1000)) {
CPUFREQ_ERR("try to set cpu vdd failed!\n");
/* notify everyone that clock transition finish */
if (policy) {
freqs.cpu = policy->cpu;;
freqs.old = freqs.new;
freqs.new = sunxi_cpufreq.last_freq;
#ifdef CONFIG_SMP
/* notifiers */
for_each_cpu(i, policy->cpus) {
freqs.cpu = i;
cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
}
#else
cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
#endif
}
return -EINVAL;
}
}
#endif
#ifdef CONFIG_DEBUG_FS
calltime = ktime_get();
#endif
/* try to set cpu frequency */
#ifndef CONFIG_SUNXI_ARISC
if (__set_cpufreq_by_ccu(freq))
#else
if (arisc_dvfs_set_cpufreq(freq, ARISC_DVFS_PLL1, ARISC_DVFS_SYN, NULL, NULL))
#endif
{
CPUFREQ_ERR("set cpu frequency to %uKHz failed!\n", freq);
#ifdef CONFIG_CPU_VOLTAGE_SCALING
if (!IS_ERR_OR_NULL(sunxi_cpufreq.vdd_cpu) && (new_vdd > sunxi_cpufreq.last_vdd)) {
if (regulator_set_voltage(sunxi_cpufreq.vdd_cpu,
sunxi_cpufreq.last_vdd*1000, sunxi_cpufreq.last_vdd*1000)) {
CPUFREQ_ERR("try to set voltage failed!\n");
sunxi_cpufreq.last_vdd = new_vdd;
}
}
#endif
/* set cpu frequency failed */
if (policy) {
freqs.cpu = policy->cpu;
freqs.old = freqs.new;
freqs.new = sunxi_cpufreq.last_freq;
#ifdef CONFIG_SMP
/* notifiers */
for_each_cpu(i, policy->cpus) {
freqs.cpu = i;
cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
}
#else
cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
#endif
}
ret = -EINVAL;
goto out;
}
#ifdef CONFIG_DEBUG_FS
sunxi_cpufreq.cpufreq_set_us = ktime_to_us(ktime_sub(ktime_get(), calltime));
#endif
#ifdef CONFIG_CPU_VOLTAGE_SCALING
if(sunxi_cpufreq.vdd_cpu && (new_vdd < sunxi_cpufreq.last_vdd)) {
CPUFREQ_DBG(DEBUG_FREQ, "set cpu vdd to %dmv\n", new_vdd);
if(regulator_set_voltage(sunxi_cpufreq.vdd_cpu, new_vdd*1000, new_vdd*1000)) {
CPUFREQ_ERR("try to set voltage failed!\n");
new_vdd = sunxi_cpufreq.last_vdd;
}
}
sunxi_cpufreq.last_vdd = new_vdd;
#endif
/* notify that cpu clock will be adjust if needed */
if (policy) {
#ifdef CONFIG_SMP
for_each_cpu(i, policy->cpus) {
freqs.cpu = i;
cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
}
#else
cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
#endif
}
sunxi_cpufreq.last_freq = freq;
CPUFREQ_DBG(DEBUG_FREQ, "DVFS done! Freq[%uMHz] Volt[%umv] ok\n", \
sunxi_cpufreq_get(0) / 1000, sunxi_cpufreq_getvolt());
out:
mutex_unlock(&sunxi_cpufreq.lock);
return ret;
}
上面提到过,governor的作用是根据系统的负载,检测系统的负载状况,然后根据当前的负载,选择出某个可供使用的工作频率,然后把该工作频率传递给cpufreq_driver,完成频率的动态调节。内核默认提供了5种governor供我们使用.
- Performance: 性能优先的governor,直接将cpu频率设置为policy->{min,max}中的最大值。一般会被选做默认的governor以节省系统启动时间,之后再切换.
- Powersave:功耗优先的governor,直接将cpu频率设置为policy->{min,max}中的最小值。
- Userspace: 由用户空间程序通过scaling_setspeed文件修改频率。一般用作调试。
- Ondemand:根据CPU的当前使用率,动态的调节CPU频率。
- interactive: 交互式动态调节CPU频率,与Ondemand类似,由谷歌开发并广泛使用于手机平板等设备上。本文主要讨论该governor。
我们看一下cpufreq_governor结构体:
struct cpufreq_governor {
char name[CPUFREQ_NAME_LEN]; //governor的名字,这里被赋值为interactive
int initialized; //初始化标志位
int (*governor) (struct cpufreq_policy *policy,
unsigned int event); //这个calback用于控制governor的行为,比较重要,是governor的一个去切入点
ssize_t (*show_setspeed) (struct cpufreq_policy *policy,
char *buf);
int (*store_setspeed) (struct cpufreq_policy *policy,
unsigned int freq);
unsigned int max_transition_latency; /* HW must be able to switch to
next freq faster than this value in nano secs or we
will fallback to performance governor */
struct list_head governor_list; //所有注册的governor都会被add到这个链表里面
struct module *owner;
};
定义一个governor如下:
struct cpufreq_governor cpufreq_gov_interactive = {
.name = "interactive",
.governor = cpufreq_governor_interactive,
.max_transition_latency = 10000000,
.owner = THIS_MODULE,
};
governor是这个结构的核心字段,cpufreq_governor注册后,cpufreq的核心层通过该字段操纵这个governor的行为,包括:初始化、启动、退出等工作。
__
ufreq_set_policy函数对该cpu的policy进行设定。如果policy认为这是一个新的governor(和原来使用的旧的governor不相同),policy会通过__
cpufreq_governor函数,并传递CPUFREQ_GOV_POLICY_INIT参数,而__cpufreq_governor函数实际上是调用cpufreq_governor结构中的governor回调函数。 case CPUFREQ_GOV_POLICY_INIT:
if (have_governor_per_policy()) {
WARN_ON(tunables);
} else if (tunables) {
tunables->usage_count++;
policy->governor_data = tunables;
return 0;
}
tunables = kzalloc(sizeof(*tunables), GFP_KERNEL);
if (!tunables) {
pr_err("%s: POLICY_INIT: kzalloc failed\n", __func__);
return -ENOMEM;
}
tunables->usage_count = 1;
tunables->io_is_busy = true;
tunables->above_hispeed_delay = default_above_hispeed_delay;
tunables->nabove_hispeed_delay =
ARRAY_SIZE(default_above_hispeed_delay);
tunables->go_hispeed_load = DEFAULT_GO_HISPEED_LOAD;
tunables->target_loads = default_target_loads;
tunables->ntarget_loads = ARRAY_SIZE(default_target_loads);
tunables->min_sample_time = DEFAULT_MIN_SAMPLE_TIME;
tunables->timer_rate = DEFAULT_TIMER_RATE;
tunables->boostpulse_duration_val = DEFAULT_MIN_SAMPLE_TIME;
tunables->timer_slack_val = DEFAULT_TIMER_SLACK;
spin_lock_init(&tunables->target_loads_lock);
spin_lock_init(&tunables->above_hispeed_delay_lock);
policy->governor_data = tunables;
if (!have_governor_per_policy())
common_tunables = tunables;
rc = sysfs_create_group(get_governor_parent_kobj(policy),
get_sysfs_attr());
if (rc) {
kfree(tunables);
policy->governor_data = NULL;
if (!have_governor_per_policy())
common_tunables = NULL;
return rc;
}
if (!policy->governor->initialized) {
idle_notifier_register(&cpufreq_interactive_idle_nb);
cpufreq_register_notifier(&cpufreq_notifier_block,
CPUFREQ_TRANSITION_NOTIFIER);
}
#ifdef CONFIG_CPU_FREQ_INPUT_EVNT_NOTIFY
if (!input_handler_register_count) {
cpumask_clear(&interactive_cpumask);
rc = input_register_handler(
&cpufreq_interactive_input_handler);
if (rc)
return rc;
}
tunables->input_event_freq = policy->max *
DEFAULT_INPUT_EVENT_FRFQ_PERCENT / 100;
tunables->input_dev_monitor = true;
input_handler_register_count++;
#endif
break;
时序图如下:
经过sysfs_create_group后在/sys/devices/system/cpu/cpufreq/interactive建立了对应的sys节点,节点主要包括:
boost: interactive对突发任务的处理。
boostpulse:对突发任务的处理频率上升后持续的时间
go_hispeed_load:高频阈值。当系统的负载超过该值,升频,否则降频。
hispeed_freq: 当workload达到 go_hispeed_load时,频率将被拉高到这个值
input_boost:对input事件,如触屏等突发处理
min_sample_time:最小采样时间。每次调频结果必须维持至少这个时间。
timer_rate: 采样定时器的采样率。
当CPU不处于idel状态时,timer_rate作为采样速率来计算CPU的workload. 当CPU处于idel状态,此时使用一个可延时定时器,会导致CPU不能从idel状态苏醒来响应定时器. 定时器的最大的可延时时间用timer_slack表示,默认值80000 uS.
- 一个governor如何被启动的?
类似governor初始化,event CPUFREQ_GOV_START被调用:
case CPUFREQ_GOV_START:
mutex_lock(&gov_lock);
freq_table = cpufreq_frequency_get_table(policy->cpu);
//如果没有设置hispeed_freq的值的话,就设置hispeed_freq为policy->max
if (!tunables->hispeed_freq)
tunables->hispeed_freq = policy->max;
//遍历所有处于online状态的CPU
for_each_cpu(j, policy->cpus) {
pcpu = &per_cpu(cpuinfo, j);
pcpu->policy = policy;
pcpu->target_freq = policy->cur;
pcpu->freq_table = freq_table;
pcpu->floor_freq = pcpu->target_freq;
pcpu->floor_validate_time =
ktime_to_us(ktime_get());
pcpu->hispeed_validate_time =
pcpu->floor_validate_time;
pcpu->max_freq = policy->max;
down_write(&pcpu->enable_sem);
del_timer_sync(&pcpu->cpu_timer);
del_timer_sync(&pcpu->cpu_slack_timer);
//启动相关的定时器
cpufreq_interactive_timer_start(tunables, j);
//启动定时器以后governor就可以工作了,所以设置pcpu->governor_enabled为1
pcpu->governor_enabled = 1;
up_write(&pcpu->enable_sem);
}
mutex_unlock(&gov_lock);
break;
现在,governor 字段被设置为cpufreq_governor_interactive,我们看看它的实现:
static int cpufreq_governor_interactive(struct cpufreq_policy *policy,
unsigned int event)
{
int rc;
unsigned int j;
struct cpufreq_interactive_cpuinfo *pcpu;
struct cpufreq_frequency_table *freq_table;
struct cpufreq_interactive_tunables *tunables;
unsigned long flags;
if (have_governor_per_policy())
tunables = policy->governor_data;
else
tunables = common_tunables;
WARN_ON(!tunables && (event != CPUFREQ_GOV_POLICY_INIT));
switch (event) {
case CPUFREQ_GOV_POLICY_INIT:
if (have_governor_per_policy()) {
WARN_ON(tunables);
} else if (tunables) {
tunables->usage_count++;
policy->governor_data = tunables;
return 0;
}
tunables = kzalloc(sizeof(*tunables), GFP_KERNEL);
if (!tunables) {
pr_err("%s: POLICY_INIT: kzalloc failed\n", __func__);
return -ENOMEM;
}
tunables->usage_count = 1;
tunables->io_is_busy = true;
tunables->above_hispeed_delay = default_above_hispeed_delay;
tunables->nabove_hispeed_delay =
ARRAY_SIZE(default_above_hispeed_delay);
tunables->go_hispeed_load = DEFAULT_GO_HISPEED_LOAD;
tunables->target_loads = default_target_loads;
tunables->ntarget_loads = ARRAY_SIZE(default_target_loads);
tunables->min_sample_time = DEFAULT_MIN_SAMPLE_TIME;
tunables->timer_rate = DEFAULT_TIMER_RATE;
tunables->boostpulse_duration_val = DEFAULT_MIN_SAMPLE_TIME;
tunables->timer_slack_val = DEFAULT_TIMER_SLACK;
spin_lock_init(&tunables->target_loads_lock);
spin_lock_init(&tunables->above_hispeed_delay_lock);
policy->governor_data = tunables;
if (!have_governor_per_policy())
common_tunables = tunables;
rc = sysfs_create_group(get_governor_parent_kobj(policy),
get_sysfs_attr());
if (rc) {
kfree(tunables);
policy->governor_data = NULL;
if (!have_governor_per_policy())
common_tunables = NULL;
return rc;
}
if (!policy->governor->initialized) {
idle_notifier_register(&cpufreq_interactive_idle_nb);
cpufreq_register_notifier(&cpufreq_notifier_block,
CPUFREQ_TRANSITION_NOTIFIER);
}
#ifdef CONFIG_CPU_FREQ_INPUT_EVNT_NOTIFY
if (!input_handler_register_count) {
cpumask_clear(&interactive_cpumask);
rc = input_register_handler(
&cpufreq_interactive_input_handler);
if (rc)
return rc;
}
tunables->input_event_freq = policy->max *
DEFAULT_INPUT_EVENT_FRFQ_PERCENT / 100;
tunables->input_dev_monitor = true;
input_handler_register_count++;
#endif
break;
case CPUFREQ_GOV_POLICY_EXIT:
if (!--tunables->usage_count) {
if (policy->governor->initialized == 1) {
cpufreq_unregister_notifier(&cpufreq_notifier_block,
CPUFREQ_TRANSITION_NOTIFIER);
idle_notifier_unregister(&cpufreq_interactive_idle_nb);
}
sysfs_remove_group(get_governor_parent_kobj(policy),
get_sysfs_attr());
kfree(tunables);
common_tunables = NULL;
}
#ifdef CONFIG_CPU_FREQ_INPUT_EVNT_NOTIFY
if (input_handler_register_count > 0)
input_handler_register_count--;
if (!input_handler_register_count) {
cpumask_clear(&interactive_cpumask);
input_unregister_handler(&cpufreq_interactive_input_handler);
}
#endif
policy->governor_data = NULL;
break;
case CPUFREQ_GOV_START:
mutex_lock(&gov_lock);
freq_table = cpufreq_frequency_get_table(policy->cpu);
if (!tunables->hispeed_freq)
tunables->hispeed_freq = policy->max;
for_each_cpu(j, policy->cpus) {
pcpu = &per_cpu(cpuinfo, j);
pcpu->policy = policy;
pcpu->target_freq = policy->cur;
pcpu->freq_table = freq_table;
pcpu->floor_freq = pcpu->target_freq;
pcpu->floor_validate_time =
ktime_to_us(ktime_get());
pcpu->hispeed_validate_time =
pcpu->floor_validate_time;
pcpu->max_freq = policy->max;
down_write(&pcpu->enable_sem);
del_timer_sync(&pcpu->cpu_timer);
del_timer_sync(&pcpu->cpu_slack_timer);
cpufreq_interactive_timer_start(tunables, j);
pcpu->governor_enabled = 1;
up_write(&pcpu->enable_sem);
}
#ifdef CONFIG_CPU_FREQ_INPUT_EVNT_NOTIFY
cpumask_or(&interactive_cpumask, &interactive_cpumask, policy->cpus);
#endif
mutex_unlock(&gov_lock);
break;
case CPUFREQ_GOV_STOP:
mutex_lock(&gov_lock);
for_each_cpu(j, policy->cpus) {
pcpu = &per_cpu(cpuinfo, j);
down_write(&pcpu->enable_sem);
pcpu->governor_enabled = 0;
del_timer_sync(&pcpu->cpu_timer);
del_timer_sync(&pcpu->cpu_slack_timer);
up_write(&pcpu->enable_sem);
}
#ifdef CONFIG_CPU_FREQ_INPUT_EVNT_NOTIFY
cpumask_andnot(&interactive_cpumask, &interactive_cpumask, policy->cpus);
#endif
mutex_unlock(&gov_lock);
break;
case CPUFREQ_GOV_LIMITS:
if (policy->max < policy->cur)
__cpufreq_driver_target(policy,
policy->max, CPUFREQ_RELATION_H);
else if (policy->min > policy->cur)
__cpufreq_driver_target(policy,
policy->min, CPUFREQ_RELATION_L);
for_each_cpu(j, policy->cpus) {
pcpu = &per_cpu(cpuinfo, j);
down_read(&pcpu->enable_sem);
if (pcpu->governor_enabled == 0) {
up_read(&pcpu->enable_sem);
continue;
}
spin_lock_irqsave(&pcpu->target_freq_lock, flags);
if (policy->max < pcpu->target_freq)
pcpu->target_freq = policy->max;
else if (policy->min > pcpu->target_freq)
pcpu->target_freq = policy->min;
spin_unlock_irqrestore(&pcpu->target_freq_lock, flags);
up_read(&pcpu->enable_sem);
/* Reschedule timer only if policy->max is raised.
* Delete the timers, else the timer callback may
* return without re-arm the timer when failed
* acquire the semaphore. This race may cause timer
* stopped unexpectedly.
*/
if (policy->max > pcpu->max_freq) {
down_write(&pcpu->enable_sem);
del_timer_sync(&pcpu->cpu_timer);
del_timer_sync(&pcpu->cpu_slack_timer);
cpufreq_interactive_timer_start(tunables, j);
up_write(&pcpu->enable_sem);
}
pcpu->max_freq = policy->max;
}
break;
}
return 0;
}
该函数主要初始化两个定时器,cpufreq_interactive_timer和cpufreq_interactive_nop_timer。
关键在于cpufreq_interactive_timer定时器的实现。