【cpufreq】linux cpufreq软件架构分析(2)
cpufreq的全局变量
首先介绍几个cpufreq的关键全局变量,能大概了解cpufreq的实现
static struct cpufreq_driver *cpufreq_driver; /* 调频驱动指针,驱动开发者根据具体hardware实现,主要包括:设置频率,获取频率等接口 */
static DEFINE_PER_CPU(struct cpufreq_policy *, cpufreq_cpu_data); /* 区分大小核时,每个CPU可有不同的调频策略 */
static DEFINE_RWLOCK(cpufreq_driver_lock); /* 这个锁保护上面的数据 */static LIST_HEAD(cpufreq_policy_list); /* CPU策略链表指针 */
static LIST_HEAD(cpufreq_governor_list); /* governor链表指针,cpufreq支持performance、powersave等多个governor */static BLOCKING_NOTIFIER_HEAD(cpufreq_policy_notifier_list); /* policy更新时,回调通知各注册模块 */
static struct srcu_notifier_head cpufreq_transition_notifier_list; /* 频率变化时,回调通知各注册模块 */static bool cpufreq_suspended; /* cpufreq是否进入suspend */
struct kobject *cpufreq_global_kobject; /* cpufreq的文件节点,路径sys/devices/system/cpu/ */
struct cpufreq_policy结构体
每个CPU有自己的policy,包含的数据有:频率表、当前使用的governor等
struct cpufreq_policy { /* CPUs sharing clock, require sw coordination */ cpumask_var_t cpus; /* Online CPUs only */ cpumask_var_t related_cpus; /* Online + Offline CPUs */ cpumask_var_t real_cpus; /* Related and present */ unsigned int shared_type; /* ACPI: ANY or ALL affected CPUs should set cpufreq */ unsigned int cpu; /* cpu managing this policy, must be online */ struct clk *clk; struct cpufreq_cpuinfo cpuinfo;/* see above */ unsigned int min; /* in kHz */ unsigned int max; /* in kHz */ unsigned int cur; /* in kHz, only needed if cpufreq governors are used */ unsigned int restore_freq; /* = policy->cur before transition */ unsigned int suspend_freq; /* freq to set during suspend */ unsigned int policy; /* see above */ unsigned int last_policy; /* policy before unplug */ struct cpufreq_governor *governor; /* 当前CPU使用的governor指针 */ void *governor_data; /* governor使用的一些私有配置数据,不同governor有不同的配置 */ char last_governor[CPUFREQ_NAME_LEN]; /* last governor used */ struct work_struct update; /* if update_policy() needs to be called, but you're in IRQ context */ struct cpufreq_user_policy user_policy; struct cpufreq_frequency_table *freq_table; /* 频率表,开发根据具体hardware配置 */ enum cpufreq_table_sorting freq_table_sorted; /* 频率表是升序还是降序 */ struct list_head policy_list; struct kobject kobj; struct completion kobj_unregister; /* * The rules for this semaphore: * - Any routine that wants to read from the policy structure will * do a down_read on this semaphore. * - Any routine that will write to the policy structure and/or may take away * the policy altogether (eg. CPU hotplug), will hold this lock in write * mode before doing so. */ struct rw_semaphore rwsem; /* * Fast switch flags: * - fast_switch_possible should be set by the driver if it can * guarantee that frequency can be changed on any CPU sharing the * policy and that the change will affect all of the policy CPUs then. * - fast_switch_enabled is to be set by governors that support fast * frequency switching with the help of cpufreq_enable_fast_switch(). */ /* 由调频drivers设置,如果drivers支持在任意CPU调频且共享policy的所有CPU频率都生效,那么配置为1 */ bool fast_switch_possible; /* 由governor设置,如果fast_switch_possible不支持则不支持快速调频,或者如果注册了调频通知事件,通知操作就意味着不能快速调频 */ bool fast_switch_enabled; /* * Preferred average time interval between consecutive invocations of * the driver to set the frequency for this policy. To be set by the * scaling driver (0, which is the default, means no preference). */ unsigned int transition_delay_us; /* * Remote DVFS flag (Not added to the driver structure as we don't want * to access another structure from scheduler hotpath). * * Should be set if CPUs can do DVFS on behalf of other CPUs from * different cpufreq policies. */ bool dvfs_possible_from_any_cpu; /* Cached frequency lookup from cpufreq_driver_resolve_freq. */ /* 最后一次目标频率值 */ unsigned int cached_target_freq; int cached_resolved_idx; /* Synchronization for frequency transitions */ bool transition_ongoing; /* Tracks transition status */ spinlock_t transition_lock; wait_queue_head_t transition_wait; struct task_struct *transition_task; /* Task which is doing the transition */ /* cpufreq-stats */ struct cpufreq_stats *stats; /* For cpufreq driver's internal use */ void *driver_data; };
struct cpufreq_driver结构体
驱动开发者根据特定hardware平台来实现struct cpufreq_driver结构体的核心功能
struct cpufreq_driver { char name[CPUFREQ_NAME_LEN]; u8 flags; void *driver_data; /* needed by all drivers */ int (*init)(struct cpufreq_policy *policy); int (*verify)(struct cpufreq_policy *policy); /* define one out of two */ int (*setpolicy)(struct cpufreq_policy *policy); /* * On failure, should always restore frequency to policy->restore_freq * (i.e. old freq). */ int (*target)(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation); /* Deprecated */ int (*target_index)(struct cpufreq_policy *policy, unsigned int index); unsigned int (*fast_switch)(struct cpufreq_policy *policy, unsigned int target_freq); /* * Caches and returns the lowest driver-supported frequency greater than * or equal to the target frequency, subject to any driver limitations. * Does not set the frequency. Only to be implemented for drivers with * target(). */ unsigned int (*resolve_freq)(struct cpufreq_policy *policy, unsigned int target_freq); /* * Only for drivers with target_index() and CPUFREQ_ASYNC_NOTIFICATION * unset. * * get_intermediate should return a stable intermediate frequency * platform wants to switch to and target_intermediate() should set CPU * to to that frequency, before jumping to the frequency corresponding * to 'index'. Core will take care of sending notifications and driver * doesn't have to handle them in target_intermediate() or * target_index(). * * Drivers can return '0' from get_intermediate() in case they don't * wish to switch to intermediate frequency for some target frequency. * In that case core will directly call ->target_index(). */ unsigned int (*get_intermediate)(struct cpufreq_policy *policy, unsigned int index); int (*target_intermediate)(struct cpufreq_policy *policy, unsigned int index); /* should be defined, if possible */ unsigned int (*get)(unsigned int cpu); /* optional */ int (*bios_limit)(int cpu, unsigned int *limit); int (*exit)(struct cpufreq_policy *policy); void (*stop_cpu)(struct cpufreq_policy *policy); int (*suspend)(struct cpufreq_policy *policy); int (*resume)(struct cpufreq_policy *policy); /* Will be called after the driver is fully initialized */ void (*ready)(struct cpufreq_policy *policy); struct freq_attr **attr; /* platform specific boost support code */ bool boost_enabled; int (*set_boost)(int state); };
- int (*init)(struct cpufreq_policy *policy)
init是驱动开发者编写的driver的初始化入口,其重要功能是对policy变量初始化。
- 申请freq_table内存空间并初始化
- 设置CPU进入suspend时的频率
- 告诉policy要管理哪些CPU,如果是SMP架构,可在回调中使用cpufreq_generic_init完成大部分功能的初始化
- cpuinfo,该CPU硬件支持的固定信息,包括最大频率、最小频率、切换延迟,其中最大频率、最小频率可以通过frequency table推导得出。
- int (*verify)(struct cpufreq_policy *policy)
当上层软件需要设定一个新的policy的时候,会调用driver的verify回调函数,检查该policy是否合法。
- 检查逻辑很简单:policy的频率范围{min,max},是否超出policy->cpuinfo的频率范围,是否超出frequency table中的频率范围。
- 可直接使用cpufreq_generic_frequency_table_verify接口
这里为什么要检查呢?因为涉及三个层次的频率
驱动底层:根据hardware设计的几个离散频率,代表了CPU的调频能力
中间层:policy->cpuinfo中的频率范围,一般和freq_table保持一致,也可以小于table的范围,必须在初始化时给定,之后不可修改
最上层:policy->min policy->max,用户可通过sysfs修改,verify就是检查者两个参数与freq_table进行比较,看是否设置的合理
- int (*target)(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation) /* Deprecated */
target回调函数,不建议使用了,就不讲了。
-
int (*target_index)(struct cpufreq_policy *policy, unsigned int index)
index表示frequency table中的index。driver需要通过index,将CPU的频率设置为对应的值,同时设置对应的电压。
- int (*setpolicy)(struct cpufreq_policy *policy)
暂时没看到使用场景
-
unsigned int (*resolve_freq)(struct cpufreq_policy *policy, unsigned int target_freq)
只是返回大于等于target_freq的驱动支持的最小频率值,不会修改频率。
- int (*suspend)(struct cpufreq_policy *policy)
一般使用cpufreq_generic_suspend接口,在系统进入睡眠时设置suspend频率
-
int (*resume)(struct cpufreq_policy *policy)
在系统唤醒时的执行的回调,可用于设置唤醒的频率
struct cpufreq_governor结构体
这个结构体规定了governor一些通用功能接口,可以看到接口入参均为policy,在init、start、stop、limits等会使用policy->governor_data或者policy->min等数据
struct cpufreq_governor { char name[CPUFREQ_NAME_LEN]; int (*init)(struct cpufreq_policy *policy); void (*exit)(struct cpufreq_policy *policy); int (*start)(struct cpufreq_policy *policy); void (*stop)(struct cpufreq_policy *policy); void (*limits)(struct cpufreq_policy *policy); ssize_t (*show_setspeed) (struct cpufreq_policy *policy, char *buf); int (*store_setspeed) (struct cpufreq_policy *policy, unsigned int freq); /* For governors which change frequency dynamically by themselves */ bool dynamic_switching; struct list_head governor_list; struct module *owner; };