[linux thermal] cpufreq_power2state()函数学习

前言

在thermal管理中,IPA策略将会为各个actors(cpu big core、little core、GPU等)分配预算功率,以达到“控制温度的同时尽量保证性能”的目的。那么该功率是如何起作用的?

大致思路:power_budget—>target_freq—>cooling_state,先进行功率划分,再获得待调节的目标频率,然后获得冷却等级。最后cooling devices根据冷却等级进行冷却操作,这才起到作用。

这一部分的代码在/drivers/thermal/power_allocator.c中。在power_allocator.c中的allocator_power()函数完成了这一过程,在这一过程中功率转换为冷却等级是通过调用cdev->ops->power2state(cdev, instance->tz, power, &state)来完成的。此操作函数在kernel4.14/drivers/thermal/cpu_cooling.c中给出。如下为此ops数据结构的定义。早在cooling_device注册的时候就赋值给了cdev->ops。因此cdev->ops->power2state()调用才得以生效。

static struct thermal_cooling_device_ops cpufreq_power_cooling_ops = {
	.get_max_state		= cpufreq_get_max_state,
	.get_cur_state		= cpufreq_get_cur_state,
	.set_cur_state		= cpufreq_set_cur_state,
	.get_requested_power	= cpufreq_get_requested_power,
	.state2power		= cpufreq_state2power,
	.power2state		= cpufreq_power2state,
};

在cpu_cooling.c中对cpufreq_power2state()函数做了定义。源代码中关于这一部分的简要描述如下:

Cooling state <-> CPUFreq frequency

Cooling states are translated to frequencies throughout this driver and this
is the relation between them.

Highest cooling state corresponds to lowest possible frequency.

i.e.
	 level 0 --> 1st Max Freq
	 level 1 --> 2nd Max Freq
	 ...

今天重点关注cpufreq_power2state()。

cpufreq_power2state()

代码路径:kernel4.14/drivers/thermal/cpu_cooling.c(注意从v5.6开始这个源文件更改为cpufreq_cooling.c我想是为了和devfreq_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_pm_opp数据结构
dev:要查找的是哪个cpu的频率值,dev指的就是这个cpu device。也可以是gpu
freq:要查的频率值
available:true或者false,true 匹配的是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;
}

【声明】本博文为个人学习笔记,仅供参考。转发请注明出处。如发现有误,还请不吝赐教,多谢!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值