前言
在thermal管理中,IPA策略将会为各个actors(cpu big core、little core、GPU等)分配预算功率,以达到“控制温度的同时尽量保证性能”的目的。那么该功率是如何起作用的?
大致思路:power_budget—>target_freq—>cooling_state,先进行功率划分,再获得待调节的目标频率,然后获得冷却等级。最后cooling devices根据冷却等级进行冷却操作,这才起到作用。
IPA这一部分的代码在/drivers/thermal/power_allocator.c中。
而由功率转换为冷却等级的过程,我们在cpufreq_power2state()这个函数中可以清晰地看到。
下面是今天关于cpufreq_power2state()的学习笔记。
cpufreq_power2state()
代码路径:kernel4.14/drivers/thermal/cpu_cooling.c
函数功能简述:
快速获取当前cpu的频率值,由该频率值获得静态功耗,再由静态功耗间接获得动态功耗,经过系统负载归一化后的动态功耗在cpufreq_cdev->freq_table中所对应的频率即为待调节的目标频率(target_freq),target_freq对应一个cooling state。
注意:此函数的计算结果会受静态功耗和系统负载这两个因素的影响,因此同样的功耗输入可能会得出不同的冷却等级。
具体计算过程请看函数实体。
static int cpufreq_power2state(struct thermal_cooling_device *cdev,
struct thermal_zone_device *tz, u32 power,
unsigned long *state)
{
unsigned int cur_freq, target_freq;
int ret;
s32 dyn_power;
u32 last_load, normalised_power, static_power;
struct cpufreq_cooling_device *cpufreq_cdev = cdev->devdata;
struct cpufreq_policy *policy = cpufreq_cdev->policy;
cur_freq = cpufreq_quick_get(policy->cpu);//快速获取当前cpu的频率,这个函数的定义在
//drivers/cpufreq/cpufreq.c中给出
ret = get_static_power(cpufreq_cdev, tz, cur_freq, &static_power);//获取当前频率值对应的静态功耗(a)
if (ret)
return ret;
dyn_power = power - static_power; //动态功耗 = 总功耗 - 静态功耗
dyn_power = dyn_power > 0 ? dyn_power : 0;
last_load = cpufreq_cdev->last_load ?: 1; //获取系统负载,如果有负载则按本身的负载计算,如果无负载则值为1(全负载)
normalised_power = (dyn_power * 100) / last_load; //在系统负载影响下的功耗
target_freq = cpu_power_to_freq(cpufreq_cdev, normalised_power); //对比cpufreq_cdev->freq_table中
//的功耗获得最终要调节的频率值target_freq。这里的freq_table和opp相同吗?
*state = get_level(cpufreq_cdev, target_freq); //获得cpufreq_cdev->freq_table中和target_freq对应的
//数组标号,(标号-1)来作为cooling state(标号是从1开始的,因此需要减一)
trace_thermal_power_cpu_limit(policy->related_cpus, target_freq, *state,
power);
return 0;
}
##(a) get_static_power()
static int get_static_power(struct cpufreq_cooling_device *cpufreq_cdev,
struct thermal_zone_device *tz, unsigned long freq,
u32 *power)
{
struct dev_pm_opp *opp;
unsigned long voltage;
struct cpufreq_policy *policy = cpufreq_cdev->policy;
struct cpumask *cpumask = policy->related_cpus;
unsigned long freq_hz = freq * 1000;
struct device *dev;
if (!cpufreq_cdev->plat_get_static_power) {
*power = 0;
return 0;
}
dev = get_cpu_device(policy->cpu);//最终调用per_cpu,生成一个cpu_sys_devices类型的per_cpu变量:policy->cpu
WARN_ON(!dev);
opp = dev_pm_opp_find_freq_exact(dev, freq_hz, true);//获取dev_pm_opp数据结构(b)
if (IS_ERR(opp)) {
dev_warn_ratelimited(dev, "Failed to find OPP for frequency %lu: %ld\n",
freq_hz, PTR_ERR(opp));
return -EINVAL;
}
voltage = dev_pm_opp_get_voltage(opp);//获取频率对应的电压值
dev_pm_opp_put(opp);
if (voltage == 0) {
dev_err_ratelimited(dev, "Failed to get voltage for frequency %lu\n",
freq_hz);
return -EINVAL;
}
return cpufreq_cdev->plat_get_static_power(cpumask, tz->passive_delay,
voltage, power);//这一步plat_get_static_power取决于自己平台用什么方式获取静态功耗
}
##(b)dev_pm_opp_find_freq_exact()
路径:kernel4.14/drivers/base/power/opp/core.c
查找一个确切的频率值,返回一个dev_om_opp数据结构
dev:要查找的是哪个cpu的频率值,dev指的就是这个cpu device。也可以是gpu
freq:要查的频率值
available:true或者false ture 匹配的是OPP中的频率值, false匹配的是OPP未给出的一个频率值
struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev,
unsigned long freq,
bool available)
{
struct opp_table *opp_table;
struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE);
opp_table = _find_opp_table(dev);//找到policy->cpu所对应的opp_table。具体就是遍历opp_tables链表,找到与当前设备相
//匹配的opp_table。理想情况是,这时opp_table已经初始化过了。那么它是什么时候被填充的呢?
// 下面我们来看下这个过程:
// 在平台自己的cpufreq初始化的时候会调用: dev_pm_opp_add() //xxx-cpufreq.c
// dev_pm_opp_add() //core.c
// dev_pm_opp_get_opp_table() // core.c
// _allocate_opp_table(dev) //core.c //调到这里,初始化过程完成
// opp_device的初始化、opp_table数据结构的填充以及opp_tables链表填充都是在core.c中的_allocate_opp_table()函数中完成,
// 这个函数最终返回一个opp_table结构体(该结构体定义在opp.h中感兴趣的可以去看下)
/*下面检查opp_table*/
if (IS_ERR(opp_table)) {
int r = PTR_ERR(opp_table);
dev_err(dev, "%s: OPP table not found (%d)\n", __func__, r);
return ERR_PTR(r);
}
mutex_lock(&opp_table->lock);//这个锁也已经在_allocate_opp_table()中初始化了
list_for_each_entry(temp_opp, &opp_table->opp_list, node) {//遍历opp_list,找到所要查找的频率所对应的opp对
if (temp_opp->available == available &&
temp_opp->rate == freq) { //????这里的两个判断的原始值来自哪里?
opp = temp_opp; //获得dev_pm_opp
/* Increment the reference count of OPP */
dev_pm_opp_get(opp);//dev_pm_opp的kref加1
break;
}
}
mutex_unlock(&opp_table->lock);
dev_pm_opp_put_opp_table(opp_table);//put kref
return opp;
}
【声明】本博文为个人学习笔记,仅供参考。转发请注明出处。如发现有误,还请不吝赐教,多谢!