系列文章
高通平台GPU动态调频DCVS . 篇1 . Interface
高通平台GPU动态调频DCVS . 篇2 . Framework & Procedure
高通平台GPU动态调频DCVS . 篇3 . 一个无法调频问题的解决过程
1. 高通平台 GPU DCVS架构
GPU DCVS是基于Linux Devfreq来实现
- 高通的kgsl(kernel-graphics-support-layer)作为devfreq device
- msm-adreno-tz 作为devfreq governor
简单的架构如下
如上图所示
- 首先 KGSL 作为device,msm-adreno-tz 作为governor注册到devfreq core
- KGSL driver通过notify机制去调用 governor msm-adreno-tz 的tz_notify() 函数
- tz_notify() 调用 devfreq core的 update_devfreq()
- update_devfreq() 调用governor的 get_target_freq()来计算处理目标频率
- update_devfreq() 调用KGSL的target回调来传递目标频率,并实施调频
2. Adreno 部分
Anreno作为平台相关的GPU驱动,提供了读取和操作GPU硬件的interface
2.1 频率解析
- 频率表
频率表位于dtsarch/arm64/boot/dts/qcom/sm8150-v2.dtsi
qcom,gpu-pwrlevels {
#address-cells = <1>;
#size-cells = <0>;
compatible = "qcom,gpu-pwrlevels";
qcom,gpu-pwrlevel@0 {
reg = <0>;
qcom,gpu-freq = <810000000>;
qcom,bus-freq = <11>;
qcom,bus-min = <11>;
qcom,bus-max = <11>;
};
qcom,gpu-pwrlevel@1 {
reg = <1>;
qcom,gpu-freq = <585000000>;
qcom,bus-freq = <11>;
qcom,bus-min = <11>;
qcom,bus-max = <11>;
};
qcom,gpu-pwrlevel@2 {
reg = <2>;
qcom,gpu-freq = <499200000>;
qcom,bus-freq = <9>;
qcom,bus-min = <8>;
qcom,bus-max = <11>;
};
qcom,gpu-pwrlevel@3 {
reg = <3>;
qcom,gpu-freq = <427000000>;
qcom,bus-freq = <6>;
qcom,bus-min = <5>;
qcom,bus-max = <7>;
};
qcom,gpu-pwrlevel@4 {
reg = <4>;
qcom,gpu-freq = <345000000>;
qcom,bus-freq = <3>;
qcom,bus-min = <3>;
qcom,bus-max = <5>;
};
qcom,gpu-pwrlevel@5 {
reg = <5>;
qcom,gpu-freq = <257000000>;
qcom,bus-freq = <3>;
qcom,bus-min = <2>;
qcom,bus-max = <4>;
};
qcom,gpu-pwrlevel@6 {
reg = <6>;
qcom,gpu-freq = <0>;
qcom,bus-freq = <0>;
qcom,bus-min = <0>;
qcom,bus-max = <0>;
};
/delete-node/ qcom,gpu-pwrlevel@7;
};
- 驱动解析
drivers/gpu/msm/adreno.c
adreno 驱动对应的设备名为qcom,kgsl-3d0
static const struct of_device_id adreno_match_table[] = {
{ .compatible = "qcom,kgsl-3d0", .data = &device_3d0 },
{}
};
adreno_probe()
-> adreno_of_get_power()
-> adreno_of_get_pwrlevels()
-> adreno_of_parse_pwrlevels()
结果保存在 adreno_device
->kgsl_device
->kgsl_pwrctrl
结构体的kgsl_pwrlevel
结构体类型的的数组中,pwrlevel作为这个数组的index
/**
* struct kgsl_pwrlevel - Struct holding different pwrlevel info obtained from
* from dtsi file
* @gpu_freq: GPU frequency vote in Hz
* @bus_freq: Bus bandwidth vote index
* @bus_min: Min bus index @gpu_freq
* @bus_max: Max bus index @gpu_freq
*/
struct kgsl_pwrlevel {
unsigned int gpu_freq;
unsigned int bus_freq;
unsigned int bus_min;
unsigned int bus_max;
};
3. kgsl 部分
kgsl是抽象层,代码位于drivers/gpu/msm
3.1 注册devfreq device
kgsl是作为Devfreq的device存在, 在kgsl_pwrscale_init()
函数,就会去注册kgsl device到devfreq framework
devfreq = devfreq_add_device(dev, &pwrscale->gpu_profile.profile,
governor, pwrscale->gpu_profile.private_data);
3.2 Notify 定义和注册、通知方法
在DCVS架构中,GPU调频实际上是由adreno-kgsl发起,进而通知msm-adreno-tz governor
- 定义notify head
在kgsl_pwrscale_init()
中
srcu_init_notifier_head(&pwrscale->nh);
- 提供notify register方法
提供kgsl_devfreq_add_notifier()
方法,供governor使用
/*
* kgsl_devfreq_add_notifier - add a fine grained notifier.
* @dev: The device
* @nb: Notifier block that will recieve updates.
*
* Add a notifier to recieve ADRENO_DEVFREQ_NOTIFY_* events
* from the device.
*/
int kgsl_devfreq_add_notifier(struct device *dev,
struct notifier_block *nb)
{
struct kgsl_device *device = dev_get_drvdata(dev);
if (device == NULL)
return -ENODEV;
if (nb == NULL)
return -EINVAL;
return srcu_notifier_chain_register(&device->pwrscale.nh, nb);
}
EXPORT_SYMBOL(kgsl_devfreq_add_notifier);
- 提供notify call chain 方法
触发governor notify 处理函数tz_notify()工作
static void do_devfreq_notify(struct work_struct *work)
{
struct kgsl_pwrscale *pwrscale = container_of(work,
struct kgsl_pwrscale, devfreq_notify_ws);
struct devfreq *devfreq = pwrscale->devfreqptr;
srcu_notifier_call_chain(&pwrscale->nh,
ADRENO_DEVFREQ_NOTIFY_RETIRE,
devfreq);
}
3.3 提供调频回调方法
Devfreq 框架需要device端提供target() 回调, 获取并处理来自devfreq的目标频率
#define KGSL_PWRSCALE_INIT(_priv_data) { \
.enabled = true, \
.gpu_profile = { \
.private_data = _priv_data, \
.profile = { \
.target = kgsl_devfreq_target, \
.get_dev_status = kgsl_devfreq_get_dev_status, \
.get_cur_freq = kgsl_devfreq_get_cur_freq, \
} }, \
/*
* kgsl_devfreq_target - devfreq_dev_profile.target callback
* @dev: see devfreq.h
* @freq: see devfreq.h
* @flags: see devfreq.h
*
* This function expects the device mutex to be unlocked.
*/
int kgsl_devfreq_target(struct device *dev, unsigned long *freq, u32 flags)
{
struct kgsl_device *device = dev_get_drvdata(dev);
struct kgsl_pwrctrl *pwr;
struct kgsl_pwrlevel *pwr_level;
int level;
unsigned int i;
unsigned long cur_freq;
if (device == NULL)
return -ENODEV;
if (freq == NULL)
return -EINVAL;
if (!device->pwrscale.enabled)
return 0;
pwr = &device->pwrctrl;
if (_check_maxfreq(flags)) {
/*
* The GPU is about to get suspended,
* but it needs to be at the max power level when waking up
*/
pwr->wakeup_maxpwrlevel = 1;
return 0;
}
mutex_lock(&device->mutex);
cur_freq = kgsl_pwrctrl_active_freq(pwr);
level = pwr->active_pwrlevel;
pwr_level = &pwr->pwrlevels[level];
/* If the governor recommends a new frequency, update it here */
if (*freq != cur_freq) {
level = pwr->max_pwrlevel;
/*
* Array index of pwrlevels[] should be within the permitted
* power levels, i.e., from max_pwrlevel to min_pwrlevel.
*/
for (i = pwr->min_pwrlevel; (i >= pwr->max_pwrlevel
&& i <= pwr->min_pwrlevel); i--)
if (*freq <= pwr->pwrlevels[i].gpu_freq) {
if (pwr->thermal_cycle == CYCLE_ACTIVE)
level = _thermal_adjust(pwr, i);
else
level = popp_trans2(device, i);
break;
}
if (level != pwr->active_pwrlevel)
kgsl_pwrctrl_pwrlevel_change(device, level);
} else if (popp_stable(device)) {
popp_trans1(device);
}
*freq = kgsl_pwrctrl_active_freq(pwr);
mutex_unlock(&device->mutex);
return 0;
}
EXPORT_SYMBOL(kgsl_devfreq_target);
4. msm-adreno-tz 部分
代码位于drivers/devfreq/governor_msm_adreno_tz.c
4.1 注册devfreq governor
static int __init msm_adreno_tz_init(void)
{
workqueue = create_freezable_workqueue("governor_msm_adreno_tz_wq");
if (workqueue == NULL)
return -ENOMEM;
return devfreq_add_governor(&msm_adreno_tz);
}
4.2 实现notify callback function
实现notify callback function tz_notify()
static int tz_notify(struct notifier_block *nb, unsigned long type, void *devp)
{
int result = 0;
struct devfreq *devfreq = devp;
switch (type) {
case ADRENO_DEVFREQ_NOTIFY_IDLE:
case ADRENO_DEVFREQ_NOTIFY_RETIRE:
mutex_lock(&devfreq->lock);
result = update_devfreq(devfreq);
mutex_unlock(&devfreq->lock);
/* Nofifying partner bus governor if any */
if (partner_gpu_profile && partner_gpu_profile->bus_devfreq) {
mutex_lock(&partner_gpu_profile->bus_devfreq->lock);
update_devfreq(partner_gpu_profile->bus_devfreq);
mutex_unlock(&partner_gpu_profile->bus_devfreq->lock);
}
break;
/* ignored by this governor */
case ADRENO_DEVFREQ_NOTIFY_SUBMIT:
default:
break;
}
return notifier_from_errno(result);
}
tz_notify()
会调用到 update_devfreq()
update_devfreq()
是devfreq core提供通用调频方法, governor通过update_devfreq() 来实现对频率的处理运算以及调用device的target() 回调传递频率到device端.
- 频率处理运算
/* Reevaluate the proper frequency */
err = devfreq->governor->get_target_freq(devfreq, &freq, &flags);
- 通过target回调传递目标频率到device
err = devfreq->profile->target(devfreq->dev.parent, &freq, flags);
5. DEVFREQ Framework
Devfreq是该机制的核心框架,所以需要了解,可以参考下文
Linux DEVFREQ - 通用DVFS Framework
6. 流程简述
调频实际上是由Adreno发起,governor是为了满足devfreq标准框架而存在,流程图如下,从上到下的执行顺序
核心的DCVS决策算法位于TrustZone
adreno_dispatcher_work
->kgsl_pwrscale_update
->queue_work(devfreq_notify_ws)
->do_devfreq_notify(ADRENO_DEVFREQ_NOTIFY_RETIRE)
->tz_notify()
->update_devfreq(devfreq)
->profile->get_dev_status()
->profile.target()
最后: 代码来源
Linux 内核部分分析用的代码来源于内核开源代码,这里是以最新的SM8150作为例子
手机厂商内核开源代码