linux cpufreq framework(1)_概述

作者:wowo 发布于:2015-6-13 22:20 分类:电源管理子系统

1. 前言

linux kernel主要通过三类机制实现SMP系统CPU core的电源管理功能:

1)cpu hotplug。根据应用场景,enable/disable CPU core,具体可参考“Linux CPU core的电源管理(4)_cpu control”。

2) cpuidle framework。在没有进程调度的时候,让CPU core进入idle状态,具体可参考“cpuidle framework系列文章”。

3) cpufreq framework。根据使用场景和系统负荷,调整CPU core的电压(voltage)和频率(frequency),具体可参考本文以及后续cpufreq相关的。

对CPU core来说,功耗和性能是一对不可调和的矛盾,通过调整CPU的电压和频率,可以在功耗和性能之间找一个平衡点。由于调整是在系统运行的过程中,因此cpufreq framework的功能也称作动态电压/频率调整(Dynamic Voltage/Frequency Scaling,DVFS)。

本文主要从功能说明和软件架构两个角度介绍cpufreq framework。

2. 功能说明

cpufreq framework的核心功能,是通过调整CPU core的电压和频率,兼顾系统的性能和功耗。在不需要高性能时,降低电压和频率,以降低功耗;在需要高性能时,提高电压和频率,以提高性能。要达到此目的,有两个关键点:

1)如果控制CPU core的电压和频率。

2)何时改变CPU core的电压和频率。

针对这两个关键点,CPU core有两种实现。

实现1:CPU core根据自身的负荷,自动调整电压和频率,不需要OS级别的软件参与。

这种实现,软件复杂度非常低,通常情况下,只需要告诉CPU core电压和频率的调整范围(通过频率表示,scaling_min_freq和scaling_max_freq,也称作policy),CPU core即可自行调整。因此:

关键点1,由CPU core自行处理;

关键点2,OS需要根据大致的应用场景(例如,是高性能场景,还是低性能场景),设定一个频率范围,改变时机,由CPU core自行决定。

注1:由于软件参与度小,该实现的省电效率可能较低。

实现2:CPU core不参与任何的逻辑动作,由OS软件根据系统运行情况,调整电压和频率。

这种实现,几乎完全由软件掌控DVFS行为:

关键点1,基于clock frameworkregulator framework提供的接口,控制CPU core的频率和电压;

关键点2,根据应用场景,手动(用户发起,例如省电模式)或者自动(软件自动调整,例如HMP)的调整。

注2:对关键点2来说,如果调整比较频繁,则需要CPU core在不同频率之间转换的速度足够快,后面会详细介绍。

为了实现上述功能需求,cpufreq framework抽象出cpufreq driver、cpufreq policy(策略)、cpufreq governor等多个软件实体,具体请参考下面的说明。

3. 软件架构

cpufreq framework的软件架构如下面图片所示:

cpufreq framework 对下,cpufreq framework基于cpu subsystem driver、OPP、clock framework、regulator framework等模块,提供对CPU core频率和电压的控制。这一部分主要由cpufreq driver实现。

对上,cpufreq framework会通过cpufreq core、cpufreq governors、cpufreq stats等模块,以sysfs的形式,向用户空间提供cpu frequency的查询、控制等接口。同时,在频率改变的时候,通过notifier通知关心的driver。

内部,cpufreq framework包括cpufreq core、cpufreq driver、cpufreq governors、cpufreq stats等模块,具体功能会在下一章详细分析。

注3:cpufreq driver中有,有一个特别的driver----arm big·little driver,用于实现ARM平台big·little的切换逻辑。虽然arm bit·little和cpufreq不是同一个概念,但它们的目的和逻辑非常类似,因此就放到这里了。后续会有专门的文章介绍该功能,因此分析cpufreq framework其它内容时,会直接把它忽略不表。

4. 软件模块的功能及API描述

4.1 cpufreq core

cpufreq core是cpufreq framework的核心模块,和kernel其它framework类似,它主要实现三类功能:

对上,以sysfs的形式向用户空间提供统一的接口,以notifier的形式向其它driver提供频率变化的通知;

对下,提供CPU core频率和电压控制的驱动框架,方便底层driver的开发;同时,提供governor框架,用于实现不同的频率调整机制;

内部,封装各种逻辑,实现所需功能。这些逻辑主要围绕struct cpufreq_driver、struct cpufreq_policy和struct cpufreq_governor三个数据结构进行,下面会详细分析。

1)struct cpufreq_driver

struct cpufreq_driver用于抽象cpufreq驱动,是平台驱动工程师关注最多的结构,其定义如下:

   1: /* include/linux/cpufreq.h */
   2: struct cpufreq_driver {
   3:     char            name[CPUFREQ_NAME_LEN];
   4:     u8            flags;
   5:     void            *driver_data;
   6:  
   7:     /* needed by all drivers */
   8:     int    (*init)        (struct cpufreq_policy *policy);
   9:     int    (*verify)    (struct cpufreq_policy *policy);
  10:  
  11:     /* define one out of two */
  12:     int    (*setpolicy)    (struct cpufreq_policy *policy);
  13:  
  14:     /*
  15:      * On failure, should always restore frequency to policy->restore_freq
  16:      * (i.e. old freq).
  17:      */
  18:     int    (*target)    (struct cpufreq_policy *policy,    /* Deprecated */
  19:                  unsigned int target_freq,
  20:                  unsigned int relation);
  21:     int    (*target_index)    (struct cpufreq_policy *policy,
  22:                  unsigned int index);
  23:     /*
  24:      * Only for drivers with target_index() and CPUFREQ_ASYNC_NOTIFICATION
  25:      * unset.
  26:      *
  27:      * get_intermediate should return a stable intermediate frequency
  28:      * platform wants to switch to and target_intermediate() should set CPU
  29:      * to to that frequency, before jumping to the frequency corresponding
  30:      * to 'index'. Core will take care of sending notifications and driver
  31:      * doesn't have to handle them in target_intermediate() or
  32:      * target_index().
  33:      *
  34:      * Drivers can return '0' from get_intermediate() in case they don't
  35:      * wish to switch to intermediate frequency for some target frequency.
  36:      * In that case core will directly call ->target_index().
  37:      */
  38:     unsigned int (*get_intermediate)(struct cpufreq_policy *policy,
  39:                      unsigned int index);
  40:     int    (*target_intermediate)(struct cpufreq_policy *policy,
  41:                        unsigned int index);
  42:  
  43:     /* should be defined, if possible */
  44:     unsigned int    (*get)    (unsigned int cpu);
  45:  
  46:     /* optional */
  47:     int    (*bios_limit)    (int cpu, unsigned int *limit);
  48:  
  49:     int    (*exit)        (struct cpufreq_policy *policy);
  50:     void    (*stop_cpu)    (struct cpufreq_policy *policy);
  51:     int    (*suspend)    (struct cpufreq_policy *policy);
  52:     int    (*resume)    (struct cpufreq_policy *policy);
  53:     struct freq_attr    **attr;
  54:  
  55:     /* platform specific boost support code */
  56:     bool                    boost_supported;
  57:     bool                    boost_enabled;
  58:     int     (*set_boost)    (int state);
  59: };

介绍该结构之前,我们先思考一个问题:由设备模型可知,driver是用来驱动设备的,那么struct cpufreq_driver所对应的设备是什么?也许从该结构中回调函数的参数可以猜到,是struct cpufreq_policy。但这相当难以理解,后面再分析。

name,该driver的名字,需要唯一,因为cpufreq framework允许同时注册多个driver,用户可以根据实际情况选择使用哪个driver。driver的标识,就是name。

flags,一些flag,具体会在后续的文章中介绍。

init,driver的入口,由cpufreq core在设备枚举的时候调用,driver需要根据硬件情况,填充policy的内容。

verify,验证policy中的内容是否符合硬件要求。它和init接口都是必须实现的接口。

setpolicy,对于第2章所讲的“实现一”,driver需要提供这个接口,用于设置CPU core动态频率调整的范围(即policy)。

target、target_index,对于第2章所讲的“实现二”,driver需要实现这两个接口中的一个(target为旧接口,不推荐使用),用于设置CPU core为指定频率(同时修改为对应的电压)。

后面的接口都是可选的,会在后续的章节中再分析。

有关struct cpufreq_driver的API包括:

   1: int cpufreq_register_driver(struct cpufreq_driver *driver_data);
   2: int cpufreq_unregister_driver(struct cpufreq_driver *driver_data);
   3:  
   4: const char *cpufreq_get_current_driver(void);
   5: void *cpufreq_get_driver_data(void);

分别为driver的注册、注销。获取当前所使用的driver名称,以及该driver的私有数据结构(driver_data字段)。

2)struct cpufreq_policy

struct cpufreq_policy是比较抽象的一个数据结构(蜗蜗觉得,是cpufreq framework中最难理解的地方),我们需要借助cpufreq core中的一些实现逻辑,去分析、理解它。

前面我们提到过一个问题,cpufreq driver对应的设备是什么?kernel是这样抽象cpufreq的:

cpufreq model 抽象出一个CPU bus(对应的sysfs目录为/sys/devices/system/cpu/,具体可参考cpu subsystem driver相关的描述),所有的CPU device都挂在这个bus上。cpufreq是CPU device的一类特定功能,被抽象为一个subsys interface(有关subsys interface的概念,请参考“Linux设备模型(6)_Bus”)。

当CPU device和CPU driver匹配时,bus core会调用subsys interface的add_dev回调函数,相当于为该特定功能添加一个“device”,进而和该特定功能的“driver”(这里为cpufreq driver)匹配,执行driver的初始化(probe,或者其它)接口。

那么该“特定功能”应该用什么样的“device”表示呢?应具体功能具体对待。kernel使用cpufreq policy(即“调频策略”)来抽象cpufreq。所谓的调频策略,即频率调整的范围,它从一定程度上,代表了cpufreq的属性。这就是struct cpufreq_policy结构的现实意义:

   1: struct cpufreq_policy {
   2:     /* CPUs sharing clock, require sw coordination */
   3:     cpumask_var_t        cpus;    /* Online CPUs only */
   4:     cpumask_var_t        related_cpus; /* Online + Offline CPUs */
   5:  
   6:     unsigned int        shared_type; /* ACPI: ANY or ALL affected CPUs
   7:                         should set cpufreq */
   8:     unsigned int        cpu;    /* cpu nr of CPU managing this policy */
   9:     unsigned int        last_cpu; /* cpu nr of previous CPU that managed
  10:                        * this policy */
  11:     struct clk        *clk;
  12:     struct cpufreq_cpuinfo    cpuinfo;/* see above */
  13:  
  14:     unsigned int        min;    /* in kHz */
  15:     unsigned int        max;    /* in kHz */
  16:     unsigned int        cur;    /* in kHz, only needed if cpufreq
  17:                      * governors are used */
  18:     unsigned int        restore_freq; /* = policy->cur before transition */
  19:     unsigned int        suspend_freq; /* freq to set during suspend */
  20:  
  21:     unsigned int        policy; /* see above */
  22:     struct cpufreq_governor    *governor; /* see below */
  23:     void            *governor_data;
  24:     bool            governor_enabled; /* governor start/stop flag */
  25:  
  26:     struct work_struct    update; /* if update_policy() needs to be
  27:                      * called, but you're in IRQ context */
  28:  
  29:     struct cpufreq_real_policy    user_policy;
  30:     struct cpufreq_frequency_table    *freq_table;
  31:  
  32:     struct list_head        policy_list;
  33:     struct kobject        kobj;
  34:     struct completion    kobj_unregister;
  35:  
  36:     /*
  37:      * The rules for this semaphore:
  38:      * - Any routine that wants to read from the policy structure will
  39:      *   do a down_read on this semaphore.
  40:      * - Any routine that will write to the policy structure and/or may take away
  41:      *   the policy altogether (eg. CPU hotplug), will hold this lock in write
  42:      *   mode before doing so.
  43:      *
  44:      * Additional rules:
  45:      * - Lock should not be held across
  46:      *     __cpufreq_governor(data, CPUFREQ_GOV_POLICY_EXIT);
  47:      */
  48:     struct rw_semaphore    rwsem;
  49:  
  50:     /* Synchronization for frequency transitions */
  51:     bool            transition_ongoing; /* Tracks transition status */
  52:     spinlock_t        transition_lock;
  53:     wait_queue_head_t    transition_wait;
  54:     struct task_struct    *transition_task; /* Task which is doing the transition */
  55:  
  56:     /* For cpufreq driver's internal use */
  57:     void            *driver_data;
  58: };

该结构看着很复杂,现在只需要关心几个事情:

min/max frequency,调频范围,对于可以自动调频的CPU而言,只需要这两个参数就够了。

current frequency和governor,对于不能自动调频的CPU,需要governor设置具体的频率值。下面介绍一下governor。

struct cpufreq_policy不会直接对外提供API。

3) cpufreq governors

governor的概念可参考“Linux cpuidle framework(1)_概述和软件架构”中相关的描述。对于不能自动调频的CPU core,必须由软件设定具体的频率值。根据使用场景的不同,会有不同的调整方案,这是由governor模块负责的,如下:

   1: struct cpufreq_governor {
   2:     char    name[CPUFREQ_NAME_LEN];
   3:     int    initialized;
   4:     int    (*governor)    (struct cpufreq_policy *policy,
   5:                  unsigned int event);
   6:     ssize_t    (*show_setspeed)    (struct cpufreq_policy *policy,
   7:                      char *buf);
   8:     int    (*store_setspeed)    (struct cpufreq_policy *policy,
   9:                      unsigned int freq);
  10:     unsigned int max_transition_latency; /* HW must be able to switch to
  11:             next freq faster than this value in nano secs or we
  12:             will fallback to performance governor */
  13:     struct list_head    governor_list;
  14:     struct module        *owner;
  15: };

name,该governor的名称。

governor,用于governor状态切换的回调函数。

show_setspeed、store_setspeed,用于提供sysfs “setspeed” attribute文件的回调函数。

max_transition_latency,该governor所能容忍的最大频率切换延迟。

cpufreq governors主要向具体的governor模块提供governor的注册和注销接口,具体会在后续的文章中详细描述。

4)通过sysfs向用户空间提供的接口

请参考“linux cpufreq framework(3)_cpufreq core”。

4.3 cpufreq drivers

各个driver模块会基于cpufreq core实现具体的driver,请参考“linux cpufreq framework(2)_cpufreq driver”。

4.4 cpufreq stats

提供cpufreq有关的统计信息,请参考“linux cpufreq framework(3)_cpufreq core”。

5. 总结

本文介绍了cpufreq framework的基本情况,后面通过以下的文章,分析其它内容:

linux cpufreq framework(2)_cpufreq driver,从平台驱动工程师的角度,介绍怎么编写cpufreq驱动;

linux cpufreq framework(3)_cpufreq core,分析cpufreq的内部实现,并总结cpufreq提供的sysfs接口,介绍怎么通过sysfs,控制系统的调频行为;

linux cpufreq framework(4)_cpufreq governor,分析cpufreq governor的实现逻辑,并介绍几种常用的governor;

linux cpufreq framework(5)_ARM big·little driver及HMP,认识ARM平台HMP功能。

 

原创文章,转发请注明出处。蜗窝科技www.wowotech.net

标签: Linux cpufreq dvfs hmp

评论:

lolo84
2016-12-09 08:52
群主,我是那个一直在问怎么在linux下运用api函数动态调整时钟频率的,请问devfreq 的调整有相应的帖子吗?
wowo
2016-12-09 09:42
@lolo84:抱歉啊,我没有写过devfreq,因为觉得现在用到的不是很多。
seven
2016-03-27 18:20
1)如果控制CPU core的电压和频率。
挑一个错别字, 应该是 “如何控制“ 吧
kobe.bao
2016-03-07 20:45
Hi wowo:

   关于电源管理,有个问题请教,是这样的(高通soc芯片):

   需要regulator供电的外设,都会在其DTS中对regulator进行设置(暂且称之为regulator设置节点),例如:

   qcom,ctrl-supply-entry@0 {

         qcom,supply-name = "vdda";

         qcom,supply-min-voltage = <1250000>;

         qcom,supply-max-voltage = <1250000>;

        ...

         };

    驱动代码中会对该节点进行解析,利用regulator_get、regulator_count_voltages等API函数进行设置。

    通常,外设的DTS中还有一个这样的节点,例如:

    vdda-supply = <&pm8994_l2>

    该节点的解释是“Phandle for vreg regulator device node”,暂且称为regulator设备节点。但是我在代码中并没有找到对该节点的解析,我想请问,regulator设备节点与regulator设置节点是怎么联系起来的?
kobe.bao
2016-03-07 20:47
@kobe.bao:sorry,我发错地方了
paoshapaoxue
2015-09-27 15:18
hi,wowo:
   文中多次提到cpu core 自动调频,可以根据自身的负载进行调频,只需要软件设置频率最大和最小调节范围即可,这个自调节是硬件自己实现的吗?之前没听说过,有没有相关的资料或者代码之类的可以阅读参考?
wowo
2015-09-27 17:26
@paoshapaoxue:是这样的。据我所知,kernel中支持cpufreq的那些CPU中,只有intel和索尼的LongRun有这样的特性。代码可以参考“drivers/cpufreq/longrun.c”,不过最好能拿到这些CPU的资料,遗憾的是我也没有。
paoshapaoxue
2015-08-20 17:15
hi,wowo:
   你在本文中说的 hot plut 功能可以参考文章“Linux CPU core的电源管理(4)_cpu control” 这个文章我怎么一直没找到呢,是不是还没有完成啊?
wowo
2015-08-20 17:37
@paoshapaoxue:是的,没有加超链接的引用,就是没来得及写,抱歉哈
dashan
2015-07-14 21:43
你好蜗窝,感谢你分享了这么好的文章。现在有一个问题请教一下,我的系统ca9,双核,cpu1&cpu0,自己开发的网卡驱动,工作正常,假设其中断为neta-irq, 如果把beta-irq亲和性设置到cpu1, 发流正常,中断在cpu1正确处理,之后offline cpu1, 中断迁移到cpu 0上,中断依然可以正确处理,收发正常,但是再执行online命令把cpu1启动起来的时候却失败了,这个问题必须发流触发中断才能复现,我分析下来发现cpu-secondary-kernel没有执行,您能否给点建议,我看不出哪里出错了,谢谢
wowo
2015-07-14 22:09
@dashan:建议从“为什么”cpu secondary kernel“没有执行查起,大致的调用流程是:
cpu_up-->_cpu_up-->__cpu_up(arch/arm/kernel/smp.c)-->smp_ops.smp_boot_secondary
在这些函数里面加一些打印,查一下在哪里终止的,大概就明白原因了。
dashan
2015-07-17 12:41
@wowo:谢谢,当时有点记错了,是secondary_start_kernel没有被执行,这部分代码是汇编相关的。请问怎么在汇编加打印? 下面是当时写的邮件:
axxx_secondary_startup    secondary_startup  __secondary_switched  secondary_start_kernel

in C function “arm_xxx_boot_secondary” (located in file platsmp-xxx.c), pointer of “axxx_secondary_startup” is written to a address as following, do you know when and where to run the code in that address of “pmsu_mp_base + PMSU_BOOT_ADDR_REDIRECT_OFFSET(hw_cpu)”?

if want to start cpu1, secondary_start_kernel must be called, but from my log, this function is not called when the issue happen? I need to know why it is not called.

Would you like to tell me some details about it? thank you.

pmsu_set_cpu_boot_addr(cpu, axxx_secondary_startup);

……

void pmsu_set_cpu_boot_addr(int hw_cpu, void *boot_addr)
{
         writel(virt_to_phys(boot_addr), pmsu_mp_base +
                   PMSU_BOOT_ADDR_REDIRECT_OFFSET(hw_cpu));
}
谢谢
wowo
2015-07-17 14:23
@dashan:据您描述,cpu_die之后,CPU1应该是被掉电了。然后在.smp_boot_secondary()中,会重新上电,CPU1从bootloader开始执行,执行的代码就是“pmsu_mp_base + PMSU_BOOT_ADDR_REDIRECT_OFFSET(hw_cpu)”。
cpu up失败,没有执行secondary_startup的原因,可能包括:
1. 上电失败,CPU压根没有执行。
2. CPU上电到执行“pmsu_mp_base + PMSU_BOOT_ADDR_REDIRECT_OFFSET(hw_cpu)”处代码之间,出现问题。
3. “pmsu_mp_base + PMSU_BOOT_ADDR_REDIRECT_OFFSET(hw_cpu)”中的数据丢失,
但和网卡的中断发流有什么关系呢?发流导致有些内存被覆盖?
正面的查法,还是顺着secondary cpu boot的流程,检查异常的位置。不用打印,可以用其它方法,比如点LED,实在不行,拉GPIO也行。找到异常位置之后,就很好找到原因。
侧面的查法,可以让网卡只产生中,不发流,看看是否出问题,这样可以知道到底是中断的原因,还是发流的原因,然后再进一步debug。
dashan
2015-07-17 22:19
@wowo:非常感谢您的回复,我会尝试按你说的调试,有发现再告诉你
bear20081015
2015-06-24 02:46
能否介绍下确定一款SOC的频点和电压的原则或方法?比如拿到一款新的SOC,我们应该给它选定哪些频点和电压呢。个人理解选好了这个,再加上cpufreq的软件框架,才能很好的达到power优化的目标吧?
wowo
2015-06-24 09:45
@bear20081015:据我所知,选频点的一般做法是:
1. 根据SOC的spec,确定soc支持的频率和电压范围。
2. 基于性能和功耗的考量,在允许范围内,选出一些潜在的组合。
3. 通过大规模的老化测试(一般一天测试一个频点,需要尽量多的设备参与),剔除那些不稳定的频点,同时选出可工作的最低和最高频点。
4. 根据实际的应用情况,在cpufreq中保留几个,用作日常调频。一般3~5个就够了。

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog