Cpufreq framework 分析-1

之前在做高通平台CPU性能和温度的控制模块,Thermal-Engine 的策略调整.
通过温度的变化范围来控制CPU Cluster 0 和Cluster 1 的频率变化.

  1. CPU的cpufreq 如何配置.

那现在我们从底层出发,来研究CPU频率 how to change .

该模块的分析先从代码开始,分析Cpufreq ,cpuidle 的架构.

下面是高通平台MSM8939 自定义的 频率表.

    qcom,msm-cpufreq {
        compatible = "qcom,msm-cpufreq";
        clock-names = "l2_clk", "cpu0_clk", "cpu1_clk", "cpu2_clk",
                "cpu3_clk", "cpu4_clk", "cpu5_clk",
                "cpu6_clk", "cpu7_clk";
        clocks = <&clock_cpu clk_a53ssmux_cci>,
             <&clock_cpu clk_a53ssmux_bc>,
             <&clock_cpu clk_a53ssmux_bc>,
             <&clock_cpu clk_a53ssmux_bc>,
             <&clock_cpu clk_a53ssmux_bc>,
             <&clock_cpu clk_a53ssmux_lc>,
             <&clock_cpu clk_a53ssmux_lc>,
             <&clock_cpu clk_a53ssmux_lc>,
             <&clock_cpu clk_a53ssmux_lc>;

        qcom,governor-per-policy;

        qcom,cpufreq-table-0 =
             < 200000 >,
             < 345600 >,
             < 400000 >,
             < 533330 >,
             < 800000 >,
             < 960000 >,
             < 1113600 >,
             < 1344000 >,
             < 1459200 >,
             < 1497600 >,
             < 1651200 >;

        qcom,cpufreq-table-4 =
             < 200000 >,
             < 249600 >,
             < 400000 >,
             < 499200 >,
             < 800000 >,
             < 998400 >,
             < 1113600 >,
             < 1209600 >;
    };

上面我们可以看到频率从200M到1.2G 几个离散的频点.  并且有2个单独core的频率表.
   qcom,cpufreq-table-4   和   qcom,cpufreq-table-0
  1. Cpu driver 是如何获取上面定义的table的.
static struct of_device_id match_table[] = {
    { .compatible = "qcom,msm-cpufreq" },// ID匹配上面dtsi 中定义的table 数组. 
    {}
};

static struct platform_driver msm_cpufreq_plat_driver = {
    .driver = {
        .name = "msm-cpufreq",
        .of_match_table = match_table,
        .owner = THIS_MODULE,
    },
};

下面来看看probe 函数的执行过程.

static int __init msm_cpufreq_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    char clk_name[] = "cpu??_clk";
    char tbl_name[] = "qcom,cpufreq-table-??";
    struct clk *c;
    int cpu;
    struct cpufreq_frequency_table *ftbl;

    l2_clk = devm_clk_get(dev, "l2_clk");
    if (IS_ERR(l2_clk))
        l2_clk = NULL;

//获取上面平台定义的clk , 一共有9个,因为是八核心U. 
/* clock-names = "l2_clk", "cpu0_clk", "cpu1_clk", "cpu2_clk", "cpu3_clk", "cpu4_clk", "cpu5_clk", "cpu6_clk", "cpu7_clk"; */
    for_each_possible_cpu(cpu) {
        snprintf(clk_name, sizeof(clk_name), "cpu%d_clk", cpu); 
        c = devm_clk_get(dev, clk_name);
        if (IS_ERR(c))
            return PTR_ERR(c);
        cpu_clk[cpu] = c;
    }
    hotplug_ready = true;

    /* Use per-policy governor tunable for some targets */
    if (of_property_read_bool(dev->of_node, "qcom,governor-per-policy"))
        msm_cpufreq_driver.flags |= CPUFREQ_HAVE_GOVERNOR_PER_POLICY;

    /* Parse commong cpufreq table for all CPUs */    尝试获取common 就是普通的频率表
    ftbl = cpufreq_parse_dt(dev, "qcom,cpufreq-table", 0);
    if (!IS_ERR(ftbl)) {
        for_each_possible_cpu(cpu)
            per_cpu(freq_table, cpu) = ftbl;
        return 0;
    }

    /* * No common table. Parse individual tables for each unique * CPU clock. */  
     // 如果没有普通的频率表, 尝试针对每个唯一类型的CPU时钟获取独享的 CPU tables
    for_each_possible_cpu(cpu) {
        snprintf(tbl_name, sizeof(tbl_name),
             "qcom,cpufreq-table-%d", cpu);
        ftbl = cpufreq_parse_dt(dev, tbl_name, cpu);

        /* CPU0 must contain freq table */
        if (cpu == 0 && IS_ERR(ftbl)) {
            dev_err(dev, "Failed to parse CPU0's freq table\n");
            return PTR_ERR(ftbl);
        }
        if (cpu == 0) {
            per_cpu(freq_table, cpu) = ftbl;
            continue;
        }

        if (cpu_clk[cpu] != cpu_clk[cpu - 1] && IS_ERR(ftbl)) {
            dev_err(dev, "Failed to parse CPU%d's freq table\n",
                cpu);
            return PTR_ERR(ftbl);
        }

        /* Use previous CPU's table if it shares same clock */ 
        // 判断是否有冲突的CLK配置 
        if (cpu_clk[cpu] == cpu_clk[cpu - 1]) {
            if (!IS_ERR(ftbl)) {
                dev_warn(dev, "Conflicting tables for CPU%d\n",
                     cpu);
                kfree(ftbl);
            }
            ftbl = per_cpu(freq_table, cpu - 1);
        }
        per_cpu(freq_table, cpu) = ftbl;
    }

    return 0;
}
从上面的probe来看.基本就是获取了频率 table 和clk资源.
frequency table是CPU core可以正确运行的一组频率/电压组合,一般情况下,会在项目启动的初期,通过“try频点”的方法,确定出稳定性、通用性都符合要求的频点。
frequency table之所以存在的一个思考点是:table是频率和电压之间的一个一一对应的组合,因此cpufreq framework只需要关心频率,所有的策略都称做“调频”策略。而cpufreq driver可以在“调频”的同时,通过table取出和频率对应的电压,进行修改CPU core电压,实现“调压”的功能。 

  1. cpufreq device的注册
static struct notifier_block msm_cpufreq_pm_notifier = {
    .notifier_call = msm_cpufreq_pm_event,
};

static struct freq_attr *msm_freq_attr[] = {
    &cpufreq_freq_attr_scaling_available_freqs,
    NULL,
};

static struct cpufreq_driver msm_cpufreq_driver = {
    /* lps calculations are handled here. */
    .flags      = CPUFREQ_STICKY | CPUFREQ_CONST_LOOPS,
    .init       = msm_cpufreq_init,
    .verify     = msm_cpufreq_verify,
    .target     = msm_cpufreq_target,
    .get        = msm_cpufreq_get_freq,
    .name       = "msm",
    .attr       = msm_freq_attr,
};


static int __init msm_cpufreq_register(void)
{
    int cpu, rc;
// 设置每个U的状态为非挂起.
    for_each_possible_cpu(cpu) {
        mutex_init(&(per_cpu(cpufreq_suspend, cpu).suspend_mutex));
        per_cpu(cpufreq_suspend, cpu).device_suspended = 0;
    }
// 注册driver 
    rc = platform_driver_probe(&msm_cpufreq_plat_driver,
                   msm_cpufreq_probe);
    if (rc < 0) {
        /* Unblock hotplug if msm-cpufreq probe fails */
        unregister_hotcpu_notifier(&msm_cpufreq_cpu_notifier);
        for_each_possible_cpu(cpu)
            mutex_destroy(&(per_cpu(cpufreq_suspend, cpu).
                    suspend_mutex));
        return rc;
    }
// 通过notifer 机制注册 通知.
    register_pm_notifier(&msm_cpufreq_pm_notifier);
    // 向cpufreq 的core层注册driver,相当于向bus 注册driver,然后会自动匹配设备 ,也就是policy设备.
    return cpufreq_register_driver(&msm_cpufreq_driver);
}

你可能感兴趣的:(性能,高通)