高通平台GPU动态调频DCVS . 篇2 . Framework & Procedure


系列文章
高通平台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 频率解析

  • 频率表
    频率表位于dts arch/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作为例子
手机厂商内核开源代码

发布了23 篇原创文章 · 获赞 31 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/memory01/article/details/97273453