在Linux中,cpufreq子系统是用于管理和调节CPU频率。它提供了一组接口和机制,允许系统根据需求动态地调整CPU的工作频率,以实现功耗优化、性能调节和温度控制等目标。
cpufreq_register_driver
函数是用于在Linux内核中注册CPU频率调节器驱动程序的函数。它将驱动程序的相关信息注册到内核,使得内核能够使用该驱动程序进行CPU频率的调节和管理。
函数原型如下:
int cpufreq_register_driver(struct cpufreq_driver *driver_data);
参数driver_data
是一个指向struct cpufreq_driver
结构体的指针,用于描述和配置CPU频率调节器驱动程序的属性和操作。
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);
};
从上可以看出对struct cpufreq_driver需要实现一些回调,如下将进行简单的介绍
int (*init)(struct cpufreq_policy *policy);此函数将实现对struct cpufreq_policy的部分初始化,在cpufreq_online(unsigned int cpu)函数中被调用。
int (*verify)(struct cpufreq_policy *policy);此函数是用于验证policy的函数,在内核中被cpufreq_set_policy函数调用。
int (*target_index)(struct cpufreq_policy *policy, unsigned int index);此函数根据index设置频率
unsigned int (*get)(unsigned int cpu); 此函数用于获取当前cpu的频率。
struct freq_attr **attr; 用于描述Linux内核中的CPU频率调节器的通用属性。可以使用sysfs接口来读取和修改。
int (*suspend)(struct cpufreq_policy *policy);用于休眠
int (*resume)(struct cpufreq_policy *policy);用于唤醒
struct cpufreq_governor
是描述 CPU 频率调节器的数据结构,它包含了调节器名称、回调函数等信息。在注册之前,需要填充这个结构体。然后,通过调用 cpufreq_register_governor()
函数,将数据结构传递给函数,以注册调节器。
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;
};
linux kernel中默认已经实现了很多governor ,如performance,schedutil等等
struct cpufreq_policy是Linux内核中描述CPU频率调节器的策略的结构体。它包含了当前CPU频率调节器的状态和属性,如最小频率、最大频率、当前频率、可设置的频率等等。
它的定义如下:
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 fix; /*in kHz, extended for test and other usage*/
unsigned int restore_freq; /* = policy->cur before transition */
unsigned int suspend_freq; /* freq to set during suspend */
unsigned int target_freq; /* freq that the policy wants to set */
unsigned int policy; /* see above */
unsigned int last_policy; /* policy before unplug */
struct cpufreq_governor *governor; /* see below */
void *governor_data;
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;
enum cpufreq_table_sorting freq_table_sorted;
struct list_head policy_list;
struct kobject kobj;
struct completion kobj_unregister;
struct notifier_block pm_qos_freq_nb;
/*
* 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().
*/
bool 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;
};
它在cpufreq_online(unsigned int cpu)函数中分配和初始化的。
可以在如下节点下查看policy的信息。
cpu注册时的设备树信息,可以从红色部分看到cpu的频率信息。
cpus {
#address-cells = <2>;
#size-cells = <0>;
cpu-map {
cluster0 {
core0 {
cpu = <&CPU0>;
};
core1 {
cpu = <&CPU1>;
};
core2 {
cpu = <&CPU2>;
};
core3 {
cpu = <&CPU3>;
};
};
cluster1 {
core0 {
cpu = <&CPU4>;
};
core1 {
cpu = <&CPU5>;
};
core2 {
cpu = <&CPU6>;
};
core3 {
cpu = <&CPU7>;
};
};
};
CPU0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a55","arm,armv8";
reg = <0x0 0x0>;
enable-method = "psci";
cpu-idle-states = <&LIT_CORE_PD>;
cpufreq-data-v1 = <&cpufreq_clus0>;
sched-energy-costs = <&CPU_COST_0 &CLUSTER_COST_0>;
capacity-dmips-mhz = <544>;
sugov_slack_timer;
};
CPU1: cpu@100 {
device_type = "cpu";
compatible = "arm,cortex-a55","arm,armv8";
reg = <0x0 0x100>;
enable-method = "psci";
cpu-idle-states = <&LIT_CORE_PD>;
cpufreq-data-v1 = <&cpufreq_clus0>;
sched-energy-costs = <&CPU_COST_0 &CLUSTER_COST_0>;
capacity-dmips-mhz = <544>;
};
CPU2: cpu@200 {
device_type = "cpu";
compatible = "arm,cortex-a55","arm,armv8";
reg = <0x0 0x200>;
enable-method = "psci";
cpu-idle-states = <&LIT_CORE_PD>;
cpufreq-data-v1 = <&cpufreq_clus0>;
sched-energy-costs = <&CPU_COST_0 &CLUSTER_COST_0>;
capacity-dmips-mhz = <544>;
};
CPU3: cpu@300 {
device_type = "cpu";
compatible = "arm,cortex-a55","arm,armv8";
reg = <0x0 0x300>;
enable-method = "psci";
cpu-idle-states = <&LIT_CORE_PD>;
cpufreq-data-v1 = <&cpufreq_clus0>;
sched-energy-costs = <&CPU_COST_0 &CLUSTER_COST_0>;
capacity-dmips-mhz = <544>;
};
CPU4: cpu@400 {
device_type = "cpu";
compatible = "arm,cortex-a75","arm,armv8";
reg = <0x0 0x400>;
enable-method = "psci";
cpu-idle-states = <&BIG_CORE_PD>;
cpufreq-data-v1 = <&cpufreq_clus1>;
sched-energy-costs = <&CPU_COST_1 &CLUSTER_COST_1>;
capacity-dmips-mhz = <1024>;
};
CPU5: cpu@500 {
device_type = "cpu";
compatible = "arm,cortex-a75","arm,armv8";
reg = <0x0 0x500>;
enable-method = "psci";
cpu-idle-states = <&BIG_CORE_PD>;
cpufreq-data-v1 = <&cpufreq_clus1>;
sched-energy-costs = <&CPU_COST_1 &CLUSTER_COST_1>;
capacity-dmips-mhz = <1024>;
};
CPU6: cpu@600 {
device_type = "cpu";
compatible = "arm,cortex-a75","arm,armv8";
reg = <0x0 0x600>;
enable-method = "psci";
cpu-idle-states = <&BIG_CORE_PD>;
cpufreq-data-v1 = <&cpufreq_clus1>;
sched-energy-costs = <&CPU_COST_1 &CLUSTER_COST_1>;
capacity-dmips-mhz = <1024>;
};
CPU7: cpu@700 {
device_type = "cpu";
compatible = "arm,cortex-a75","arm,armv8";
reg = <0x0 0x700>;
enable-method = "psci";
cpu-idle-states = <&BIG_CORE_PD>;
cpufreq-data-v1 = <&cpufreq_clus1>;
sched-energy-costs = <&CPU_COST_1 &CLUSTER_COST_1>;
capacity-dmips-mhz = <1024>;
};
};
cpufreq_clus0: cpufreq-clus0 {
cpufreq-cluster-id = <0>;
cpufreq-sub-clusters = <&cpufreq_scu>,
<&cpufreq_periph>,
<&cpufreq_gic>;
clock-latency = <50000>;
voltage-latency = <6413>;
cpufreq-boost-disable;
nvmem-cells = <&lit_bin>;
nvmem-cell-names = "dvfs_bin";
operating-points = <
/*kHZ uV*/
1820000 1000000
1716000 996875
1534000 937500
1482000 921875
1228800 843750
1144000 815625
962000 759375
768000 750000
614400 750000
>;
operating-points-1 = <
/*kHZ uV*/
1820000 928125
1716000 896875
1534000 843750
1482000 828125
1228800 753125
1144000 750000
962000 750000
768000 750000
614400 750000
>;
operating-points-2 = <
/*kHZ uV*/
1820000 959375
1716000 928125
1534000 871875
1482000 856250
1228800 778125
1144000 753125
962000 750000
768000 750000
614400 750000
>;
operating-points-3 = <
/*kHZ uV*/
1820000 981250
1716000 950000
1534000 893750
1482000 878125
1228800 800000
1144000 775000
962000 750000
768000 750000
614400 750000
>;
operating-points-4 = <
/*kHZ uV*/
1820000 1000000
1716000 996875
1534000 937500
1482000 921875
1228800 843750
1144000 815625
962000 759375
768000 750000
614400 750000
>;
};