- 了解loongson cpufreq
1.clock 初始化
170 static int loongson3_clock_init(void)
171 {
172 int i;
173
174 for_each_possible_cpu(i) {
175 sprintf(clk_names[i], "cpu%d_clk", i);
176 cpu_clks[i].name = clk_names[i];
177 cpu_clks[i].flags = CLK_ALWAYS_ENABLED | CLK_RATE_PROPAGATES;
178 cpu_clks[i].rate = cpu_clock_freq / 1000;
179 }
180
181 /* clock table init */
182 for (i = MIN_FREQ_LEVEL;
183 (loongson3_clockmod_table[i].frequency != CPUFREQ_TABLE_END);
184 i++)
185 loongson3_clockmod_table[i].frequency = ((cpu_clock_freq / 1000) * i) / 8;
186
187 return 0;
188 }
189 arch_initcall(loongson3_clock_init);
1).如何获取cpu_clock_freq?
函数no_efiboot_env(arch/mips/loongson64/common/env.c)如下所示:
如果定义CONFIG_LEFI_FIRMWARE_INTERFACE,则cpu_clock_freq通过解析fw_arg2获取到;
93 int *_prom_envp;
94 long l;
95
96 /* firmware arguments are initialized in head.S */
97 _prom_envp = (int *)fw_arg2;
98
99 l = (long)*_prom_envp;
100 while (l != 0) {
101 parse_even_earlier(cpu_clock_freq, "cpuclock", l);
102 parse_even_earlier(memsize, "memsize", l);
103 parse_even_earlier(highmemsize, "highmemsize", l);
104 _prom_envp++;
105 l = (long)*_prom_envp;
106 }
如果没有定义,则通过fw_arg2找到ecpu,进而cpu_clock_freq = ecpu->cpu_clock_freq;
118 /* firmware arguments are initialized in head.S */
119 boot_p = (struct boot_params *)fw_arg2;
120 loongson_p = &(boot_p->efi.sysinfo.lp);
124 ecpu = (struct efi_cpuinfo_loongson *)
125 ((u64)loongson_p + loongson_p->cpu_offset);
135 cpu_clock_freq = ecpu->cpu_clock_freq;
假如读取到的cpu_clock_freq等于0,则根据cpu 的类型来设置cpu freq,如下所示:
290 if (cpu_clock_freq == 0) {
291 processor_id = (¤t_cpu_data)->processor_id;
293 switch (processor_id & PRID_REV_MASK) {
294 case PRID_REV_LOONGSON2E:
295 cpu_clock_freq = 533080000;
296 break;
297 case PRID_REV_LOONGSON2F:
298 cpu_clock_freq = 797000000;
299 break;
300 case PRID_REV_LOONGSON3A_R1:
301 case PRID_REV_LOONGSON3A_R2_0:
302 case PRID_REV_LOONGSON3A_R2_1:
303 case PRID_REV_LOONGSON3A_R3_0:
304 case PRID_REV_LOONGSON3A_R3_1:
305 cpu_clock_freq = 900000000;
306 break;
307 case PRID_REV_LOONGSON3B_R1:
308 case PRID_REV_LOONGSON3B_R2:
309 cpu_clock_freq = 1000000000;
310 break;
311 default:
312 cpu_clock_freq = 100000000;
313 break;
314 }
315 }
2). 初始化loongson3_clockmod_table[];
2.loongson3 cpufreq 初始化
drivers/cpufreq/loongson3_cpufreq.c:
614 static int __init cpufreq_init(void)
615 {
637 for (i = 0; i < MAX_PACKAGES; i++) {
639 spin_lock_init(&cpufreq_reg_lock[i]);
640 }
641
642 /* Register platform stuff */
643 ret = platform_driver_register(&platform_driver);
649 if ((current_cpu_type() == CPU_LOONGSON3_COMP)) {
650 cpufreq_register_notifier(&ls3a4000_cpufreq_notifier_block,
651 CPUFREQ_TRANSITION_NOTIFIER);
652 } else {
653 cpufreq_register_notifier(&loongson3_cpufreq_notifier_block,
654 CPUFREQ_TRANSITION_NOTIFIER);
655 }
656
657 ret = cpufreq_register_driver(&loongson3_cpufreq_driver);
658
659 return ret;
660 }
661 subsys_initcall(cpufreq_init);
-
spin_lock_init(&cpufreq_reg_lock[i]);
龙芯3a1000使用同一个寄存器的同一个频控域来控制一个处理器芯片的所有核心,龙芯3B1500,3A2000,3A3000虽然在频控域上实现了对每个核的单独控制,但是同一个处理器芯片上的核心依旧共享同一个寄存器。因此在多核并发的情况下, 必须用锁来保证对频控域寄存器的串行访问。龙芯最多支持4个处理器芯片互联,因此初始化长度为4的自旋锁数组cpufreq_reg_lock[]。
-
platform_driver_register(&platform_driver);
484 static struct platform_driver platform_driver = {
485 .driver = {
486 .name = "loongson3_cpufreq",
487 .owner = THIS_MODULE,
488 },
489 .id_table = platform_device_ids,
490 };
- cpufreq_register_driver(&loongson3_cpufreq_driver);
465 static struct cpufreq_driver loongson3_cpufreq_driver = {
466 .name = "loongson3",
467 .init = loongson3_cpufreq_cpu_init,
468 .verify = cpufreq_generic_frequency_table_verify,
469 .target_index = loongson3_cpufreq_target,
470 .get = loongson3_cpufreq_get,
471 .exit = loongson3_cpufreq_exit,
472 .attr = cpufreq_generic_attr,
473 };
注册驱动loongson3_cpufreq_driver, 注册过程中会导致.init函数指针被调用。
- init(): 设置最高主频(P0状态下的频率,终极来源是BIOS所传递的参数efi_cpuinfo_loongson::cpu_clock_freq); 提供8级频率表(P0-P7的各个频率值); 初始化cpufreq_policy等相关数据结构。每个cpu核都有自己的控制策略(cpufreq_policy),意味着不同的核心允许通过不同的策略来管理。
27 /* Minimum CLK support */
28 enum {
29 DC_ZERO, DC_12PT, DC_25PT, DC_37PT, DC_50PT, DC_62PT,
30 DC_75PT, DC_87PT, DC_DISABLE, DC_RESV
31 };
32
>> 33 struct cpufreq_frequency_table loongson3_clockmod_table[] = {
>> 34 {
0, DC_ZERO, CPUFREQ_ENTRY_INVALID},
>> 35 {
0, DC_12PT, CPUFREQ_ENTRY_INVALID},
>> 36 {
0, DC_25PT, CPUFREQ_ENTRY_INVALID},
>> 37 {
0, DC_37PT, CPUFREQ_ENTRY_INVALID},
38 {
0, DC_50PT, 0},
39 {
0, DC_62PT, 0},
40 {
0, DC_75PT, 0},
41 {
0, DC_87PT, 0},
42 {
0, DC_DISABLE, 0},
>> 43 {
0, DC_RESV, CPUFREQ_TABLE_END},
44 };
>> 45 EXPORT_SYMBOL_GPL(loongson3_clockmod_table);
- target_index : 切换频率时调用,它会将当前cpu核的主频设置成CPUfreq策略提供的目标频率,具体通过写ChipConfig寄存器(3A1000)或FreqCtrl寄存器(3B1500及更新的cpu)中的分频系数完成。
401 static int loongson3_cpufreq_target(struct cpufreq_policy *policy,
402 unsigned int index)
403 {
404 unsigned int freq;
405 unsigned int cpu = policy->cpu;
406 unsigned int package = cpu_data[cpu].package;
407 int ret = 0;
408
409 if (!cpu_online(cpu))
410 return -ENODEV;
411
412 if ((current_cpu_type() == CPU_LOONGSON3_COMP)) {
413 freq = ls3a4000_freq_table[index].frequency;
414 } else {
415 freq =
416 ((cpu_clock_freq / 1000) *
417 loongson3_clockmod_table[index].driver_data) / 8;
418 }
419
420 /* setting the cpu frequency */
421 if ((current_cpu_type() == CPU_LOONGSON3_COMP)) {
422 mutex_lock(&ls3a4000_mutex[package]);
423 if (dvfs_enabled) {
424 ret = ls3a4000_dvfs_scale(policy, freq);
425 } else {
426 ret = ls3a4000_freq_scale(policy, freq);
427 }
428 mutex_unlock(&ls3a4000_mutex[package]);
429 } else {
430 spin_lock(&cpufreq_reg_lock[package]);
431 ret = clk_set_rate(policy->clk, freq);
432 spin_unlock(&cpufreq_reg_lock[package]);
433 }
434
435 return ret;
436 }
当cpufreq_register_driver注册cpufreq驱动时,通过调用如下代码,在cpu热插拔状态机中注册一个状态点和一对配套的回调函数。每当一个新cpu核上线过程达到"cpufreq:online"状态点时,就会调用cpuhp_cpufreq_online;同样,每当一个cpu核下线过程达到 "cpufreq:online"状态点时,就会调用cpuhp_cpufreq_offline。
2527 ret = cpuhp_setup_state_nocalls_cpuslocked(CPUHP_AP_ONLINE_DYN,
2528 "cpufreq:online",
2529 cpuhp_cpufreq_online,
2530 cpuhp_cpufreq_offline);
有了驱动程序,loongson_cpufreq_init 注册设备loongson3_cpufreq_device,然后驱动和设备进行匹配。
arch/mips/loongson64/common/platform.c:
>> 20 static struct platform_device loongson3_cpufreq_device = {
21 .name = "loongson3_cpufreq",
22 .id = -1,
23 };
24
>> 25 static int __init loongson_cpufreq_init(void)
26 {
27 struct cpuinfo_mips *c = ¤t_cpu_data;
28
32 if ((c->processor_id & PRID_REV_MASK) >= PRID_REV_LOONGSON3A_R1)
33 return platform_device_register(&loongson3_cpufreq_device);
34
35 return -ENODEV;
36 }
37
38 arch_initcall(loongson_cpufreq_init);
3调节cpuf freq
当CPUFreq的governor决定调节频率时,它就会调用cpufreq框架中的cpufreq_driver_target/__cpufreq_driver_target().
1956 int __cpufreq_driver_target(struct cpufreq_policy *policy,
1957 unsigned int target_freq,
1958 unsigned int relation)
1959 {
1960 unsigned int old_target_freq = target_freq;
1961 int index;
1962
1963 if (cpufreq_disabled())
1964 return -ENODEV;
1965
1966 /* Make sure that target_freq is within supported range */
1967 target_freq = clamp_val(target_freq, policy->min, policy->max);
1968
1969 pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n",
1970 policy->cpu, target_freq, relation, old_target_freq);
1971
1972 /*
1973 * This might look like a redundant call as we are checking it again
1974 * after finding index. But it is left intentionally for cases where
1975 * exactly same freq is called again and so we can save on few function
1976 * calls.
1977 */
1978 if (target_freq == policy->cur)
1979 return 0;
1980
1981 /* Save last value to restore later on errors */
1982 policy->restore_freq = policy->cur;
1983
1984 if (cpufreq_driver->target)
1985 return cpufreq_driver->target(policy, target_freq, relation);
1986
1987 if (!cpufreq_driver->target_index)
1988 return -EINVAL;
1989
1990 index = cpufreq_frequency_table_target(policy, target_freq, relation);
1991
1992 return __target_index(policy, index);
1993 }
调控策略所给出的目标频率允许与频率表中的有效频率存在一定误差,CPUfreq核心会在频率表中选择与目标频率最接近的有效频率,这就是舍入方法的作用,relation参数设置为:
231 #define CPUFREQ_RELATION_L 0 /* lowest frequency at or above target */
232 #define CPUFREQ_RELATION_H 1 /* highest frequency below or at target */
233 #define CPUFREQ_RELATION_C 2 /* closest frequency to target */
- CPUFREQ_RELATION_L: cpufreq 核心选择频率表中不低于目标频率的所有有效频率的最小者;
- CPUFREQ_RELATION_H: cpufreq 核心选择频率表中不高于目标频率的所有有效频率的最大者;
- CPUFREQ_RELATION_C:cpufreq 核心选择频率表中目标频率附近最接近的有效频率。
4.龙芯CPUAutoplug
根据全局cpu负载动态开关核,自动调核被命名为CPUAutoplug。linux-4.9版本之前,cpu热插拔机制是基于通知块的,从linux-4.9版本开始全面改造成了基于状态机的。