电源管理(2) - loongson cpufreq

  • 了解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 = (&current_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 = &current_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版本开始全面改造成了基于状态机的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值