1. 前言
前文(Linux cpufreq framework(2)_cpufreq driver)从平台驱动工程师的角度,简单的介绍了编写一个cpufreq driver的大概步骤。但要更深入理解、更灵活的使用,必须理解其内部的实现逻辑。
因此,本文将从cpufreq framework core的角度,对cpufreq framework的内部实现做一个简单的分析。
2. 提供给用户空间的接口
cpufreq core的内部实现,比较繁杂,因此我们需要一个分析的突破口,而cpufreq framework提供的API是一个不错的选择。因为API抽象了cpufreq的功能,而所有的cpufreq core、cpufreq driver等模块,都是为这些功能服务的。
由“linux cpufreq framework(1)_概述”的介绍可知,cpufreq framework通过sysfs向用户空间提供接口,具体如下:
/sys/devices/system/cpu/cpu0/cpufreq/
|-- affected_cpus
|-- cpuinfo_cur_freq
|-- cpuinfo_max_freq
|-- cpuinfo_min_freq
|-- cpuinfo_transition_latency
|-- related_cpus
|-- scaling_available_frequencies
|-- scaling_available_governors
|-- scaling_cur_freq
|-- scaling_driver
|-- scaling_governor
|-- scaling_max_freq
|-- scaling_min_freq
|-- scaling_setspeed
`—stats
|-- time_in_state
|-- total_trans
`-- trans_table
1)“cpufreq”目录
在kernel的模型中,cpufreq被抽象为cpu device的一个interface,因此它位于对应的cpu目录(/sys/devices/system/cpu/cpuX)下面。
有些平台,所有cpu core的频率和电压时统一控制的,即改变某个core上的频率,其它core同样受影响。此时只需要实现其中一个core(通常为cpu0)的cpufreq即可,其它core的cpufreq直接是cpu0的符号链接。因此,使用这些API时,随便进入某一个cpu下面的cpufreq目录即可。
而另一些些平台,不同core可以单独控制,这时不同cpu目录下的cpufreq就不一样了。
到底某一个cpufreq可以控制多少cpu core呢?可以通过cpufreq/affected_cpus和cpufreq/related_cpus两个文件查看,其中的区别是:affected_cpus表示该cpufreq影响到哪些cpu core(没有显示处于offline状态的cpu),related_cpus则包括了online+offline的所有core。
2)cpuinfo相关的信息
由“Linux cpufreq framework(2)_cpufreq driver”的描述可知,cpufreq driver初始化时,会根据frequency table等信息,填充struct cpufreq_policy变量中的struct cpufreq_cpuinfo变量,该变量保存了CPU调频有关的固定信息(不可以在运行过程中修改,主要包括:最大频率(cpuinfo_max_freq)、最小频率(cpuinfo_min_freq)、频率转换延迟(cpuinfo_transition_latency )。
另外,通过cpuinfo_cur_freq ,可以获取cpu core的当前频率(真实的、cpu的当前运行频率,会通过cpufreq_driver->get回调读取)。
当前,这四个“cpuinfo_”开头的sysfs API,都是只读的。
3)scaling_available_frequencies,获取当前可以配置的频率列表,从frequency table直接读取。readonly。
4) scaling_driver,当前加载的cpufreq driver名称,readonly。
5)scaling_available_governors和scaling_governor,系统中可用的governor列表,以及当前使用的governor。governor相关的内容会在后续详细描述。readonly。
6)scaling_cur_freq,从cpufreq core或者governor的角度,看到的当前频率,和cpuinfo_cur_freq的意义不相同,后面的分析中根据实例在描述。readonly。
7)scaling_max_freq、scaling_min_freq和scaling_setspeed
scaling_max_freq和scaling_min_freq表示调频策略所允许的最大和最小频率,对于可以自动调整频率的cpu,修改它们,就是最终的频率调整。
对不能自动调整频率的cpu,则需要通过其它方式,主动的设置cpu频率,这些都是由具体的governor完成。其中有一个特例:
如果使用的governor是“userspace” governor,则可以通过scaling_setspeed节点,直接修改cpu频率。
3. 频率调整的步骤
开始分析之前,我们先以“userspace” governor为例,介绍一下频率调整的步骤。“userspace”governor是所有governor中最简单的一个,同时又是驱动工程师比较常用的一个,借助它,可以从用户空间修改cpu的频率,操作方法如下(为了简单,以shell脚本的形式给出):
cd /sys/devices/system/cpu/cpu0/cpufreq/
cat cpuinfo_max_freq; cat cpuinfo_min_freq #获取“物理”上的频率范围cat scaling_available_frequencies #获取可用的频率列表
cat scaling_available_governors #获取可用的governors
cat scaling_governor #当前的governor
cat cpuinfo_cur_freq; cat scaling_cur_freq #获取当前的频率信息,可以比较一下是否不同cat scaling_max_freq; cat scaling_min_freq #获取当前调频策略所限定的频率范围
#假设CPU不可以自动调整频率
echo userspace > scaling_governor #governor切换为userspace#如果需要切换的频率值在scaling_available_frequencies内,且在cpuinfo_max_freq/cpuinfo_min_freq的范围内。
#如果需要切换的频率不在scaling_max_freq/scaling_min_freq的范围内,修改这两个值
echo xxx > scaling_max_freq; echo xxx > scaling_min_freq
#最后,设置频率值
echo xxx > scaling_setspeed
4. 内部逻辑分析
4.1 初始化
基于linux设备模型的思想,kernel会使用device和driver两个实体,抽象设备及其驱动,当这两个实体同时存在时,则执行driver的初始化接口(即driver开始运行)。cpufreq也不例外,基本遵循了上面的思路。但由于cpufreq是一类比较特殊的设备(它只是cpu device的一个功能,本身并不以任何形式存在),在实现上,就有点迂回。
首先,driver的抽象比较容易理解,就是我们在“Linux cpufreq framework(2)_cpufreq driver”中描述的struct cpufreq_driver结构。那device呢?先看看下面的图片:
注1:该图片不包括CPU hotplug的情况,hotplug时,会走两外一个流程,大概原理类似,本文就不详细介绍这种情况了。
前面讲过,cpufreq driver注册时,会调用subsys_interface_register接口,注册一个subsystem interface,该interface的定义如下:
1: /* drivers/cpufreq/cpufreq.c */
2: static struct subsys_interface cpufreq_interface = {3: .name = "cpufreq",
4: .subsys = &cpu_subsys,5: .add_dev = cpufreq_add_dev,6: .remove_dev = cpufreq_remove_dev,7: };
该interface的subsys是“cpu_subsys”,就是cpu bus(struct bus_type cpu_subsys),提供了add_dev和remove_dev两个回调函数。
由“Linux设备模型(6)_Bus”中的描述可知,当bus下有设备probe的时候(此处为cpu device的probe),会调用其下所有interface的add_dev回调函数。物理意义是什么呢?
cpufreq是cpu device的一个功能,当cpu device开始枚举时,当然要创建代表该功能(cpufreq)的设备。而这个设备的具体形态,只有该功能相关的代码(cpufreq core)知道,因此就只能交给它了。
2)__cpufreq_add_dev
由上面图片所示的流程可知,cpufreq设备的添加,最终是由__cpufreq_add_dev接口完成的,该接口的主要功能,是创建一个代表该cpufreq的设备(struct cpufreq_policy类型的变量),并以它为参数,调用cpufreq_driver的init接口(可参考“Linux cpufreq framework(2)_cpufreq driver”),对它进行初始化。如下:
1: static int __cpufreq_add_dev(struct device *dev, struct subsys_interface *sif)2: {3: unsigned int j, cpu = dev->id;4: int ret = -ENOMEM;
5: struct cpufreq_policy *policy;
6: unsigned long flags;7: bool recover_policy = cpufreq_suspended;8: #ifdef CONFIG_HOTPLUG_CPU9: struct cpufreq_policy *tpolicy;
10: #endif11:12: if (cpu_is_offline(cpu))
13: return 0;
14:15: pr_debug("adding CPU %u\n", cpu);
16:17: #ifdef CONFIG_SMP18: /* check whether a different CPU already registered this
19: * CPU because it is in the same boat. */20: policy = cpufreq_cpu_get(cpu);21: if (unlikely(policy)) {
22: cpufreq_cpu_put(policy);23: return 0;
24: }25: #endif26:27: if (!down_read_trylock(&cpufreq_rwsem))
28: return 0;
29:30: #ifdef CONFIG_HOTPLUG_CPU31: /* Check if this cpu was hot-unplugged earlier and has siblings */
32: read_lock_irqsave(&cpufreq_driver_lock, flags);33: list_for_each_entry(tpolicy, &cpufreq_policy_list, policy_list) {34: if (cpumask_test_cpu(cpu, tpolicy->related_cpus)) {
35: read_unlock_irqrestore(&cpufreq_driver_lock, flags);36: ret = cpufreq_add_policy_cpu(tpolicy, cpu, dev);37: up_read(&cpufreq_rwsem);38: return ret;
39: }40: }41: read_unlock_irqrestore(&cpufreq_driver_lock, flags);42: #endif43:44: /*
45: * Restore the saved policy when doing light-weight init and fall back46: * to the full init if that fails.47: */48: policy = recover_policy ? cpufreq_policy_restore(cpu) : NULL;49: if (!policy) {
50: recover_policy = false;51: policy = cpufreq_policy_alloc();52: if (!policy)
53: goto nomem_out;
54: }55:56: /*
57: * In the resume path, since we restore a saved policy, the assignment58: * to policy->cpu is like an update of the existing policy, rather than59: * the creation of a brand new one. So we need to perform this update60: * by invoking update_policy_cpu().61: */62: if (recover_policy && cpu != policy->cpu)
63: WARN_ON(update_policy_cpu(policy, cpu, dev));64: else
65: policy->cpu = cpu;66:67: cpumask_copy(policy->cpus, cpumask_of(cpu));68:69: init_completion(&policy->kobj_unregister);70: INIT_WORK(&policy->update, handle_update);71:72: /* call driver. From then on the cpufreq must be able
73: * to accept all calls to ->verify and ->setpolicy for this CPU74: */75: ret = cpufreq_driver->init(policy);76: if (ret) {
77: pr_debug("initialization failed\n");
78: goto err_set_policy_cpu;
79: }80:81: /* related cpus should atleast have policy->cpus */
82: cpumask_or(policy->related_cpus, policy->related_cpus, policy->cpus);83:84: /*
85: * affected cpus must always be the one, which are online. We aren't86: * managing offline cpus here.87: */88: cpumask_and(policy->cpus, policy->cpus, cpu_online_mask);89:90: if (!recover_policy) {
91: policy->user_policy.min = policy->min;92: policy->user_policy.max = policy->max;93: }94:95: down_write(&policy->rwsem);96: write_lock_irqsave(&cpufreq_driver_lock, flags);97: for_each_cpu(j, policy->cpus)98: per_cpu(cpufreq_cpu_data, j) = policy;99: write_unlock_irqrestore(&cpufreq_driver_lock, flags);100:101: if (cpufreq_driver->get && !cpufreq_driver->setpolicy) {
102: policy->cur = cpufreq_driver->get(policy->cpu);103: if (!policy->cur) {
104: pr_err("%s: ->get() failed\n", __func__);
105: goto err_get_freq;
106: }107: }108:109: /*
110: * Sometimes boot loaders set CPU frequency to a value outside of111: * frequency table present with cpufreq core. In such cases CPU might be112: * unstable if it has to run on that frequency for long duration of time113: * and so its better to set it to a frequency which is specified in114: * freq-table. This also makes cpufreq stats inconsistent as115: * cpufreq-stats would fail to register because current frequency of CPU116: * isn't found in freq-table.117: *118: * Because we don't want this change to effect boot process badly, we go119: * for the next freq which is >= policy->cur ('cur' must be set by now,120: * otherwise we will end up setting freq to lowest of the table as 'cur'121: * is initialized to zero).122: *123: * We are passing target-freq as "policy->cur - 1" otherwise124: * __cpufreq_driver_target() would simply fail, as policy->cur will be125: * equal to target-freq.126: */127: if ((cpufreq_driver->flags & CPUFREQ_NEED_INITIAL_FREQ_CHECK)
128: && has_target()) {129: /* Are we running at unknown frequency ? */
130: ret = cpufreq_frequency_table_get_index(policy, policy->cur);131: if (ret == -EINVAL) {
132: /* Warn user and fix it */
133: pr_warn("%s: CPU%d: Running at unlisted freq: %u KHz\n",
134: __func__, policy->cpu, policy->cur);135: ret = __cpufreq_driver_target(policy, policy->cur - 1,136: CPUFREQ_RELATION_L);137:138: /*
139: * Reaching here after boot in a few seconds may not140: * mean that system will remain stable at "unknown"141: * frequency for longer duration. Hence, a BUG_ON().142: */143: BUG_ON(ret);144: pr_warn("%s: CPU%d: Unlisted initial frequency changed to: %u KHz\n",
145: __func__, policy->cpu, policy->cur);146: }147: }148:149: blocking_notifier_call_chain(&cpufreq_policy_notifier_list,150: CPUFREQ_START, policy);151:152: if (!recover_policy) {
153: ret = cpufreq_add_dev_interface(policy, dev);154: if (ret)
155: goto err_out_unregister;
156: blocking_notifier_call_chain(&cpufreq_policy_notifier_list,157: CPUFREQ_CREATE_POLICY, policy);158: }159:160: write_lock_irqsave(&cpufreq_driver_lock, flags);161: list_add(&policy->policy_list, &cpufreq_policy_list);162: write_unlock_irqrestore(&cpufreq_driver_lock, flags);163:164: cpufreq_init_policy(policy);165:166: if (!recover_policy) {
167: policy->user_policy.policy = policy->policy;168: policy->user_policy.governor = policy->governor;169: }170: up_write(&policy->rwsem);171:172: kobject_uevent(&policy->kobj, KOBJ_ADD);173: up_read(&cpufreq_rwsem);174:175: pr_debug("initialization complete\n");
176:177: return 0;
178:179: err_out_unregister:180: err_get_freq:181: write_lock_irqsave(&cpufreq_driver_lock, flags);182: for_each_cpu(j, policy->cpus)183: per_cpu(cpufreq_cpu_data, j) = NULL;184: write_unlock_irqrestore(&cpufreq_driver_lock, flags);185:186: up_write(&policy->rwsem);187:188: if (cpufreq_driver->exit)189: cpufreq_driver->exit(policy);
190: err_set_policy_cpu:191: if (recover_policy) {
192: /* Do not leave stale fallback data behind. */
193: per_cpu(cpufreq_cpu_data_fallback, cpu) = NULL;194: cpufreq_policy_put_kobj(policy);195: }196: cpufreq_policy_free(policy);197:198: nomem_out:199: up_read(&cpufreq_rwsem);200:201: return ret;
202: }
12~13行:如果cpu处于offline状态,则直接返回。
17~25行:对于SMP系统,可能存在所有的CPU core,使用相同的cpufreq policy的情况。此时,当primary CPU枚举时,调用__cpufreq_add_dev接口创建cpufreq policy时,会同时将该policy提供给其它CPU使用。当其他CPU枚举时,它需要判断是否已经有人代劳了,如果有,则直接返回。这就是这几行代码的逻辑。
30~40行:这几行是处理具有hotplug功能的CPU的。有前面的逻辑可知,如果系统中多个CPU共用一个cpufreq policy,primary CPU在枚举时,会帮忙添加其它CPU的cpufreq policy。但这有一个条件:它只帮忙处理那些处于online状态的CPU,而那些offline状态的CPU,则需要在online的时候,自行判断,并将自身的cpufreq policy添加到系统。
注2:有关上面的两段实现,后面会专门用一个章节介绍。
44~54行:分配cpufreq policy。根据是否是suspend & resume的过程,会有不同的处理方式,我们先不去纠结这些细节。
55~65行:将当前cpu保存在policy的cpu字段。
67行:初始化policy->cpus变量,由“Linux cpufreq framework(2)_cpufreq driver”可知,该变量是一个cpumask类型的变量,记录该policy可以控制哪些online的cpu(毫无疑问,至少会包含当前的cpu)。
75行:调用driver的init接口,相当于设备模型中的driver probe。此时cpufreq deice(policy)和cpufreq driver已经成功会和,driver开始执行。具体行为,请参考“Linux cpufreq framework(2)_cpufreq driver”。
82行:初始化policy->related_cpus,至少包含所有的online CPUs(policy->cpus,由cpufreq_driver->init初始化)。
88行:根据当前CPU online状态,将policy->cpus中处于offline状态的CPU剔除。
97~98行:初始化所有其它共用cpufreq policy的、处于online状态的CPU(policy->cpus)的cpufreq_cpu_data变量,表明它们也都已经有了cpufreq policy(回到17~25行的逻辑,其中的判断,就是依据该变量)。
109~147行:如果cpufreq driver定义了CPUFREQ_NEED_INITIAL_FREQ_CHECK flag,则要求cpufreq core在启动时检查当前频率(policy->cur)是否在frequency table范围内,如果不在,调用__cpufreq_driver_target重新设置频率。
152~158行:对于不是suspend&resume的场景,调用cpufreq_add_dev_interface接口,在该cpu的目录下,创建cpufreq的sysfs目录,以及相应的attribute文件(可参考本文第2章的内容),并为其它共用policy的、处于online状态的cpu,创建指向该cpufreq目录的符号链接。
161行:将新创建的policy,挂到一个全局链表中(cpufreq_policy_list)。
164行:调用cpufreq_init_policy,为该policy分配一个governor,并调用cpufreq_set_policy接口,为该cpu设置一个默认的cpufreq policy。这一段的逻辑有点纠结,我们后面再细讲。
166~-169:更新policy->user_policy,后面再细讲。
3)SMP、Hotplug等场景下,多个CPU共用cpufreq policy的情况总结
前面多次提到,在SMP系统中,多个CPU core可能会由相同的调频机制(其实大多数平台都是这样的)控制,也就是说,所有CPU core的频率和电压,是同时调节的。这种情况下,只需要创建一个cpufreq policy即可,涉及到的代码逻辑包括:
a)primary CPU枚举时,__cpufreq_add_dev会调用cpufreq driver的init接口(cpufreq_driver->init),driver需要根据当前的系统情况,设置policy->cpus,告诉cpufreq core哪些CPU共用同一个cpufreq policy。
b)primary CPU的__cpufreq_add_dev继续执行,初始化policy->related_cpus,并将policy->cpus中处于offline状态的CPU剔除。具体可参考上面的代码分析。
c)primary CPU的__cpufreq_add_dev继续执行,创建sysfs接口,同时为policy->cpus中的其它CPU创建相应的符号链接。
d)secondary CPUs枚举,执行__cpufreq_add_dev,判断primary CPU已经代劳之后,直接退出。
e)对于hotplugable的CPU,hotplug in时,由于primary CPU没有帮忙创建sysfs的符号链接,或者hotplug out的时候符号链接被删除,因此需要重新创建。
4.2 频率调整
cpufreq framework的频率调整逻辑,总结如下:
通过调整policy(struct cpufreq_policy),确定CPU频率调整的一个大方向,主要是由min_freq和max_freq组成的频率范围;
通过cpufreq governor,确定最终的频率值。
下面结合代码,做进一步的阐述。
1)cpufreq_set_policy
cpufreq_set_policy用来设置一个新的cpufreq policy,调用的时机包括:
a)初始化时(__cpufreq_add_dev->cpufreq_init_policy->cpufreq_set_policy),将cpufreq_driver->init时提供的基础policy,设置生效。
b)修改scaling_max_freq或scaling_min_freq时(store_one->cpufreq_set_policy),将用户空间设置的新的频率范围,设置生效。
c)修改cpufreq governor时(scaling_governor->store_scaling_governor->cpufreq_set_policy),更新governor。
来看一下cpufreq_set_policy都做了什么事情。
1: static int cpufreq_set_policy(struct cpufreq_policy *policy,2: struct cpufreq_policy *new_policy)
3: {4: struct cpufreq_governor *old_gov;
5: int ret;
6:7: pr_debug("setting new policy for CPU %u: %u - %u kHz\n",
8: new_policy->cpu, new_policy->min, new_policy->max);9:10: memcpy(&new_policy->cpuinfo, &policy->cpuinfo, sizeof(policy->cpuinfo));
11:12: if (new_policy->min > policy->max || new_policy->max < policy->min)
13: return -EINVAL;
14:15: /* verify the cpu speed can be set within this limit */
16: ret = cpufreq_driver->verify(new_policy);17: if (ret)
18: return ret;
19:20: /* adjust if necessary - all reasons */
21: blocking_notifier_call_chain(&cpufreq_policy_notifier_list,22: CPUFREQ_ADJUST, new_policy);23:24: /* adjust if necessary - hardware incompatibility*/
25: blocking_notifier_call_chain(&cpufreq_policy_notifier_list,26: CPUFREQ_INCOMPATIBLE, new_policy);27:28: /*
29: * verify the cpu speed can be set within this limit, which might be30: * different to the first one31: */32: ret = cpufreq_driver->verify(new_policy);33: if (ret)
34: return ret;
35:36: /* notification of the new policy */
37: blocking_notifier_call_chain(&cpufreq_policy_notifier_list,38: CPUFREQ_NOTIFY, new_policy);39:40: policy->min = new_policy->min;41: policy->max = new_policy->max;42:43: pr_debug("new min and max freqs are %u - %u kHz\n",
44: policy->min, policy->max);45:46: if (cpufreq_driver->setpolicy) {
47: policy->policy = new_policy->policy;48: pr_debug("setting range\n");
49: return cpufreq_driver->setpolicy(new_policy);
50: }51:52: if (new_policy->governor == policy->governor)
53: goto out;
54:55: pr_debug("governor switch\n");
56:57: /* save old, working values */
58: old_gov = policy->governor;59: /* end old governor */
60: if (old_gov) {
61: __cpufreq_governor(policy, CPUFREQ_GOV_STOP);62: up_write(&policy->rwsem);63: __cpufreq_governor(policy, CPUFREQ_GOV_POLICY_EXIT);64: down_write(&policy->rwsem);65: }66:67: /* start new governor */
68: policy->governor = new_policy->governor;69: if (!__cpufreq_governor(policy, CPUFREQ_GOV_POLICY_INIT)) {
70: if (!__cpufreq_governor(policy, CPUFREQ_GOV_START))
71: goto out;
72:73: up_write(&policy->rwsem);74: __cpufreq_governor(policy, CPUFREQ_GOV_POLICY_EXIT);75: down_write(&policy->rwsem);76: }77:78: /* new governor failed, so re-start old one */
79: pr_debug("starting governor %s failed\n", policy->governor->name);
80: if (old_gov) {
81: policy->governor = old_gov;82: __cpufreq_governor(policy, CPUFREQ_GOV_POLICY_INIT);83: __cpufreq_governor(policy, CPUFREQ_GOV_START);84: }85:86: return -EINVAL;
87:88: out:89: pr_debug("governor: change or update limits\n");
90: return __cpufreq_governor(policy, CPUFREQ_GOV_LIMITS);
91: }
15~18行:调用driver的verify接口,判断新的policy是否有效。
20~26行:notifier接口用于policy的调整,如果有其它模块对新的policy不满意,可以通过这样的机制修正,具体不再描述,感兴趣的读者可以自行研究。
28~34行:修正后,再交给driver进行verify。
46~50行:如果driver提供了setpolicy回调(回忆一下“Linux cpufreq framework(2)_cpufreq driver”),则说明硬件有能力根据policy所指定的范围,自行调节频率,其它机制就不需要了,调用该回调,将新的policy配置给硬件后,退出。
否则(后面的代码),则需要由governor执行具体的频率调整动作,调用governor的接口,切换governor。有关cpufreq governor的描述,请参考下一篇文章。
2)scaling_setspeed
上面提到了,policy只规定了频率调整的一个范围,如果driver不支持setpolicy操作,则需要由cpufreq governor确定具体的频率值,并调用driver的target或者target_index接口,修改CPU的频率值。
有关cpufreq governor的介绍,请参考后续的文章(“linux cpufreq framework(4)_cpufreq governor”)。不过这其中有一个例外,就是当governor为“userspace”时(参考第3章的描述),可以直接通过scaling_setspeed文件,从用户空间修改频率值,代码如下:
1: static ssize_t store_scaling_setspeed(struct cpufreq_policy *policy,2: const char *buf, size_t count)3: {4: unsigned int freq = 0;5: unsigned int ret;6:7: if (!policy->governor || !policy->governor->store_setspeed)
8: return -EINVAL;
9:10: ret = sscanf(buf, "%u", &freq);
11: if (ret != 1)
12: return -EINVAL;
13:14: policy->governor->store_setspeed(policy, freq);15:16: return count;
17: }
由此可以猜到,“userspace” governor具有store_setspeed函数,该函数应该可以直接修改频率值。留到(“linux cpufreq framework(4)_cpufreq governor”)再欣赏吧。
原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。