ABOUT ME

-

Today
-
Yesterday
-
Total
-
choco@desktop:~/tistory
$ 정보처리기사 요점정리
1과목 2과목 3과목 4과목 5과목 실기

$ Linux Kernel
Power Management DVFS
  • DVFS in Linux Kernel
    SW개발/Linux 2023. 6. 20. 23:52

    Power Management in Linux Kernel

    목차

    https://computer-choco.tistory.com/679

     

    동적인 전력 관리 (Dynamic Power Management) 의 대표적인 방법은 DVFS (Dynamic Voltage and Frequency Scaling) 이며, Linux kernel 에서는 이를 위하여 CPUFREQ 및 DEVFREQ framework 를 지원합니다.

    https://domybestinlife.tistory.com/220

     

    DVFS 의 효과. 그래프 추가

    공식 추가

     

    CPU Power Managment 를 위한 DVFS 뿐 아니라 Non CPU Power managment 를 위한 DVFS 의 필요성이 대두되면서 DEVFREQ framework 도 지원하게 되었습니다. (공식 문서 확인 필요)

     

    1. CPUFREQ framework

    cpufreq framework 는 다음과 같이 구성되어있습니다. 크게 cpufreq governor 와 cpufreq core 로 나눠볼 수 있습니다. 하단의 cpufreq driver 는 사용자가 cpufreq framework 를 사용하기 위해 직접 개발한 device driver 입니다.

    linux kernel 소스코드에서 drivers/cpufreq/ 하단에는 아래와 같은 파일들이 있습니다. cpufreq core 에 해당하는 cpufreq.c 파일과, governor 파일들을 확인할 수 있습니다. 단, sched_util governor 는 다른 cpufreq governor 들과 달리 kernel/sched/ 경로에 존재합니다.

    $ ls
    acpi-cpufreq.c               cpufreq_userspace.c   longhaul.h             ppc_cbe_cpufreq.c            scmi-cpufreq.c
    amd_freq_sensitivity.c       cpufreq-dt.c          longrun.c              ppc_cbe_cpufreq.h            scpi-cpufreq.c
    amd-pstate.c                 cpufreq-dt.h          loongson1-cpufreq.c    ppc_cbe_cpufreq_pervasive.c  sh-cpufreq.c
    amd-pstate-trace.c           cpufreq-dt-platdev.c  loongson2_cpufreq.c    ppc_cbe_cpufreq_pmi.c        sparc-us2e-cpufreq.c
    amd-pstate-trace.h           cpufreq-nforce2.c     Makefile               pxa2xx-cpufreq.c             sparc-us3-cpufreq.c
    amd-pstate-ut.c              davinci-cpufreq.c     maple-cpufreq.c        pxa3xx-cpufreq.c             spear-cpufreq.c
    armada-37xx-cpufreq.c        e_powersaver.c        mediatek-cpufreq.c     qcom-cpufreq-hw.c            speedstep-centrino.c
    armada-8k-cpufreq.c          elanfreq.c            mediatek-cpufreq-hw.c  qcom-cpufreq-nvmem.c         speedstep-ich.c
    bmips-cpufreq.c              freq_table.c          mvebu-cpufreq.c        qoriq-cpufreq.c              speedstep-lib.c
    brcmstb-avs-cpufreq.c        gx-suspmod.c          omap-cpufreq.c         raspberrypi-cpufreq.c        speedstep-lib.h
    cppc_cpufreq.c               highbank-cpufreq.c    p4-clockmod.c          s3c2410-cpufreq.c            speedstep-smi.c
    cpufreq.c                    ia64-acpi-cpufreq.c   pasemi-cpufreq.c       s3c2412-cpufreq.c            sti-cpufreq.c
    cpufreq_conservative.c       imx6q-cpufreq.c       pcc-cpufreq.c          s3c2416-cpufreq.c            sun50i-cpufreq-nvmem.c
    cpufreq_governor.c           imx-cpufreq-dt.c      pmac32-cpufreq.c       s3c2440-cpufreq.c            tegra124-cpufreq.c
    cpufreq_governor.h           intel_pstate.c        pmac64-cpufreq.c       s3c24xx-cpufreq.c            tegra186-cpufreq.c
    cpufreq_governor_attr_set.c  Kconfig               powernow-k6.c          s3c24xx-cpufreq-debugfs.c    tegra194-cpufreq.c
    cpufreq_ondemand.c           Kconfig.arm           powernow-k7.c          s3c64xx-cpufreq.c            tegra20-cpufreq.c
    cpufreq_ondemand.h           Kconfig.powerpc       powernow-k7.h          s5pv210-cpufreq.c            ti-cpufreq.c
    cpufreq_performance.c        Kconfig.x86           powernow-k8.c          sa1100-cpufreq.c             vexpress-spc-cpufreq.c
    cpufreq_powersave.c          kirkwood-cpufreq.c    powernow-k8.h          sa1110-cpufreq.c
    cpufreq_stats.c              longhaul.c            powernv-cpufreq.c      sc520_freq.c

    cpufreq governor 는 다음과 같은 종류들이 있습니다.

    (governor 와 policy 의 역할 설명)

     

    governor 종류

    https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt

    2.1  Performance
    2.2  Powersave
    2.3  Userspace
    2.4  Ondemand
    2.5  Conservative
    2.6  Schedutil

    schedutil governor

    https://www.linuxdays.cz/2018/video/Giovanni_Gherdovich-Schedutil_frequency_scaling_governor.pdf

    https://shnoh.tistory.com/9

    https://patchwork.kernel.org/project/linux-pm/patch/1842158.0Xhak3Uaac@vostro.rjw.lan/

     

    이 중, ARM64 라면 설정을 변경하지 않았다면 default governor 는 schedutil governor 로 설정됩니다.

    // drivers/cpufreq/Kconfig

    choice
            prompt "Default CPUFreq governor"
            default CPU_FREQ_DEFAULT_GOV_USERSPACE if ARM_SA1100_CPUFREQ || ARM_SA1110_CPUFREQ
            default CPU_FREQ_DEFAULT_GOV_SCHEDUTIL if ARM64 || ARM
            default CPU_FREQ_DEFAULT_GOV_SCHEDUTIL if X86_INTEL_PSTATE && SMP
            default CPU_FREQ_DEFAULT_GOV_PERFORMANCE
            help
              This option sets which CPUFreq governor shall be loaded at
              startup. If in doubt, use the default setting.

    위와 같이 "CPU_FREQ_DEFAULT_GOV_SCHEDUTIL config 가 enabled 상태라면, default governor 함수의 리턴 값은 sched util governor 가 됩니다.

    // kernel/sched/cpufreq_schedutil.c

    #ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL
    struct cpufreq_governor *cpufreq_default_governor(void)
    {
            return &schedutil_gov;
    }
    #endif

     

    mediatek-cpufreq.c 를 cpufreq device driver 로 작성했다고 가정하고, cpufreq governor 가 시작되는 순서를 보면 다음과 같습니다. (그림으로 정리 필요)

    cpufreq_register_driver -> subsys_interface_register -> sif->add_dev(dev, sif) -> cpufreq_add_dev -> cpufreq_online -> cpufreq_init_policy(policy) -> cpufreq_set_policy -> cpufreq_start_governor(policy) -> policy->governor->start(policy) -> cpufreq_add_update_util_hook(...,sugov_update_single_freq) -> sugov_deferred_update(sg_policy) -> sugov_work -> \__cpufreq_driver_target(sg_policy->policy, freq, CPUFREQ_RELATION_L) -> cpufreq_driver->target(policy, target_freq, relation) / \_\_target_index(policy, policy->cached_resolved_idx) -> mtk_cpufreq_set_target

     

    전체 요약 그림 말고도 실제 파일 간의 어떻게 설정하는지 자세한 그림도 추가 필요

     

    이렇게 sched util governor 이므로, scheduler 의 timer tick 에 맞춰서 clock 및 voltage 값을 설정하게 됩니다.

    // drivers/cpufreq/mediatek-cpufreq.c

    static int mtk_cpufreq_set_target(struct cpufreq_policy *policy,
                                      unsigned int index)
    {
            struct cpufreq_frequency_table *freq_table = policy->freq_table;
            struct clk *cpu_clk = policy->clk;
            struct clk *armpll = clk_get_parent(cpu_clk);
            struct mtk_cpu_dvfs_info *info = policy->driver_data;
            struct device *cpu_dev = info->cpu_dev;
            struct dev_pm_opp *opp;
            long freq_hz, pre_freq_hz;
            int vproc, pre_vproc, inter_vproc, target_vproc, ret;
    
            inter_vproc = info->intermediate_voltage;
    
            pre_freq_hz = clk_get_rate(cpu_clk);
    
            mutex_lock(&info->reg_lock);
    
            if (unlikely(info->pre_vproc <= 0))
                    pre_vproc = regulator_get_voltage(info->proc_reg);
            else
                    pre_vproc = info->pre_vproc;
    
            if (pre_vproc < 0) {
                    dev_err(cpu_dev, "invalid Vproc value: %d\n", pre_vproc);
                    ret = pre_vproc;
                    goto out;
            }
    
            freq_hz = freq_table[index].frequency * 1000;
    
            opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);

     

    만약 ondemand 라면 추가하기

     

    sysfs

    $ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
    $ echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

    https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=ms_bk&logNo=220062890908 

     

    요약본

     

    2. DEVFREQ framework

    devfreq framework 는 다음과 같이 구성되어있습니다. devfreq core, governor, event 로 크게 3가지로 나누어볼 수 있습니다. 하단의 devfreq driver 는 사용자가 devfreq framework 를 활용하기 위하여 직접 device 에 맞게 작성한 device driver 를 의미합니다.

    linux kernel 소스 코드에서 drivers/devfreq/ 경로 밑에는 아래와 같은 파일들이 있습니다.

    아래에서는 devfreq core 에 해당하는 devfreq.cgovernor 들에 해당하는 governor_*.c 파일들, 각 vendor 별 devfreq device driver 파일을 중심으로 볼 예정입니다.

    $ ls
    devfreq.c        governor_passive.c         imx8m-ddrc.c       rk3399_dmc.c
    devfreq-event.c  governor_performance.c     imx-bus.c          sun8i-a33-mbus.c
    event/           governor_powersave.c       Kconfig            tegra30-devfreq.c
    exynos-bus.c     governor_simpleondemand.c  Makefile
    governor.h       governor_userspace.c       mtk-cci-devfreq.c

    devfreq framework 를 사용하기 위해서는

    (1) devfreq device driver 파일을 생성해야하며,

     

    mediatek 의 driver 를 보면 다음과 같이 device driver 의 probe 함수에서 'devm_devfreq_add_device' 를 호출하고 있습니다. 이와 같이 devm_devfreq_add_device 함수를 호출해야합니다. 또한 3번째 함수 인자로 어떤 governor 를 활용할지 전달해야합니다. governor 의 종류는 DEVFREQ_GOV_SIMPLE_ONDEMAND, DEVFREQ_GOV_PERFORMANCE, DEVFREQ_GOV_POWERSAVE, DEVFREQ_GOV_USERSPACE, DEVFREQ_GOV_PASSIVE 가 있습니다.

    // drivers/devfreq/mtk-cci-devfreq.c

            drv->devfreq = devm_devfreq_add_device(dev, &mtk_ccifreq_profile,
                                                   DEVFREQ_GOV_PASSIVE,
                                                   passive_data);

    devm_devfreq_add_device 함수 내부에서는 governor에 연결된 event_handler 가 호출됩니다.

    // drivers/devfreq/devfreq.c

            devfreq->governor = governor;
            err = devfreq->governor->event_handler(devfreq, DEVFREQ_GOV_START,
                                                    NULL);

    만일, governor 가 simple_ondemand 였다면, 아래와 같은 handler 가 호출됩니다.

    // drivers/devfreq/governor_simpleondemand.c

    static int devfreq_simple_ondemand_handler(struct devfreq *devfreq,
                                    unsigned int event, void *data)
    {
            switch (event) {
            case DEVFREQ_GOV_START:
                    devfreq_monitor_start(devfreq);
                    break;

    simple_ondemand governor 는 일정 시간마다 반복해서 동작하는 governor 로, linux kernel 의 work queue 를 활용하여 동작합니다. devfreq_monitor_start 함수 내부를 보면 queue_delayed_work 함수를 통해 work queue 를 등록하는 것을 확인할 수 있습니다. 시간 간격에 해당하는 polling_ms 는 device tree 에서 선언된 값을 사용합니다.

    // drivers/devfreq/devfreq.c

    void devfreq_monitor_start(struct devfreq *devfreq)
    {
            if (IS_SUPPORTED_FLAG(devfreq->governor->flags, IRQ_DRIVEN))
                    return;
    
            switch (devfreq->profile->timer) {
            case DEVFREQ_TIMER_DEFERRABLE:
                    INIT_DEFERRABLE_WORK(&devfreq->work, devfreq_monitor);
                    break;
            case DEVFREQ_TIMER_DELAYED:
                    INIT_DELAYED_WORK(&devfreq->work, devfreq_monitor);
                    break;
            default:
                    return;
            }
    
            if (devfreq->profile->polling_ms)
                    queue_delayed_work(devfreq_wq, &devfreq->work,
                            msecs_to_jiffies(devfreq->profile->polling_ms));
    }

    work queue 를 등록할 때, devfreq_monitor 라는 함수를 연결해두었기 때문에, polling_ms 시간마다 devfreq_monitor 동작을 시행하게 됩니다. devfreq_monitor 함수 내부에서는 다음과 같이 update_devfreq 함수가 호출됩니다.

    // drivers/devfreq/devfreq.c

    static void devfreq_monitor(struct work_struct *work)
    {
            int err;
            struct devfreq *devfreq = container_of(work,
                                            struct devfreq, work.work);
    
            mutex_lock(&devfreq->lock);
            err = update_devfreq(devfreq);
            if (err)
                    dev_err(&devfreq->dev, "dvfs failed with (%d) error\n", err);
    
            queue_delayed_work(devfreq_wq, &devfreq->work,
                                    msecs_to_jiffies(devfreq->profile->polling_ms));
            mutex_unlock(&devfreq->lock);
    
            trace_devfreq_monitor(devfreq);
    }

    update_devfreq 함수 내부에서는 devfreq_update_target 함수를 호출합니다.

    int devfreq_update_target(struct devfreq *devfreq, unsigned long freq)
    {
            unsigned long min_freq, max_freq;
            int err = 0;
            u32 flags = 0;
    
            lockdep_assert_held(&devfreq->lock);
    
            if (!devfreq->governor)
                    return -EINVAL;
    
            /* Reevaluate the proper frequency */
            err = devfreq->governor->get_target_freq(devfreq, &freq);
            if (err)
                    return err;
            devfreq_get_freq_range(devfreq, &min_freq, &max_freq);
    
            if (freq < min_freq) {
                    freq = min_freq;
                    flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use GLB */
            }
            if (freq > max_freq) {
                    freq = max_freq;
                    flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */
            }
    
            return devfreq_set_target(devfreq, freq, flags);
    }

    devfreq_update_target 함수 내부에서는 governor 의 get_target_freq 함수를 이용하여, 다음에 설정할 frequency 값을 계산하고, devfreq_set_target 함수를 통하여 해당 값으로 설정합니다.

    만일, simple_ondemand governor 라면 get_target_freq 는 다음과 같이 계산됩니다.

    // drivers/devfreq/governor_simpleondemand.c

            /* Set the desired frequency based on the load */
            a = stat->busy_time;
            a *= stat->current_frequency;
            b = div_u64(a, stat->total_time);
            b *= 100;
            b = div_u64(b, (dfso_upthreshold - dfso_downdifferential / 2));
            *freq = (unsigned long) b;

    이렇게 계산된 값을 이용하여 devfreq_set_target 함수에서는 profile 에 연결된 target 함수를 호출합니다.

    // drivers/devfreq/devfreq.c

    static int devfreq_set_target(struct devfreq *devfreq, unsigned long new_freq,
                                  u32 flags)
    {
            struct devfreq_freqs freqs;
            unsigned long cur_freq;
            int err = 0;
    
            if (devfreq->profile->get_cur_freq)
                    devfreq->profile->get_cur_freq(devfreq->dev.parent, &cur_freq);
            else
                    cur_freq = devfreq->previous_freq;
    
            freqs.old = cur_freq;
            freqs.new = new_freq;
            devfreq_notify_transition(devfreq, &freqs, DEVFREQ_PRECHANGE);
    
            err = devfreq->profile->target(devfreq->dev.parent, &new_freq, flags);
            if (err) {
                    freqs.new = cur_freq;
                    devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
                    return err;
            }

    profile 은 처음에 devm_devfreq_add_device 를 할 때, 인자로 넘겨준 값이기 때문에, 아래 예시에서는 mediatek 의 mtk_ccifreq_target 함수가 연결되어 실행되게 됩니다.

    // drivers/devfreq/mtk-cci-devfreq.c

    static struct devfreq_dev_profile mtk_ccifreq_profile = {
            .target = mtk_ccifreq_target,
    };

    mtk_ccifreq_target 함수 내부에서는 clk 및 voltage 값을 설정합니다.

    // drivers/devfreq/mtk-cci-devfreq.c

    static int mtk_ccifreq_target(struct device *dev, unsigned long *freq,
                                  u32 flags)
    {
            struct mtk_ccifreq_drv *drv = dev_get_drvdata(dev);
            struct clk *cci_pll = clk_get_parent(drv->cci_clk);
            struct dev_pm_opp *opp;
            unsigned long opp_rate;
            int voltage, pre_voltage, inter_voltage, target_voltage, ret;
    
            if (!drv)
                    return -EINVAL;
    
            if (drv->pre_freq == *freq)
                    return 0;
    
            inter_voltage = drv->inter_voltage;
    
            opp_rate = *freq;
            opp = devfreq_recommended_opp(dev, &opp_rate, 1);
            if (IS_ERR(opp)) {
                    dev_err(dev, "failed to find opp for freq: %ld\n", opp_rate);
                    return PTR_ERR(opp);
            }
    
            mutex_lock(&drv->reg_lock);
    
            voltage = dev_pm_opp_get_voltage(opp);
            dev_pm_opp_put(opp);
    
            pre_voltage = regulator_get_voltage(drv->proc_reg);
            if (pre_voltage < 0) {
                    dev_err(dev, "invalid vproc value: %d\n", pre_voltage);
                    ret = pre_voltage;
                    goto out_unlock;
            }
    
            /* scale up: set voltage first then freq. */
            target_voltage = max(inter_voltage, voltage);
            if (pre_voltage <= target_voltage) {
                    ret = mtk_ccifreq_set_voltage(drv, target_voltage);
                    if (ret) {
                            dev_err(dev, "failed to scale up voltage\n");
                            goto out_restore_voltage;
                    }
            }
    
            /* switch the cci clock to intermediate clock source. */
            ret = clk_set_parent(drv->cci_clk, drv->inter_clk);
            if (ret) {
                    dev_err(dev, "failed to re-parent cci clock\n");
                    goto out_restore_voltage;
            }

     

    (2) device tree 에 노드를 추가해야 합니다.

    mediatek 의 device tree 예시를 보면 다음과 같습니다.

    // arch/arm64/boot/dts/mediatek/mt8183.dtsi

            cci: cci {
                    compatible = "mediatek,mt8183-cci";
                    clocks = <&mcucfg CLK_MCU_BUS_SEL>,
                             <&topckgen CLK_TOP_ARMPLL_DIV_PLL1>;
                    clock-names = "cci", "intermediate";
                    operating-points-v2 = <&cci_opp>;
            };

    governor 종류 추가

    simple_ondemand – performance – powersave – userspace – passive

     

    sysfs

    $ cat /sys/kernel/debug/devfreq/devfreq_summary
    $ echo [available frequency] > /sys/class/devfreq/[dev name]/min_freq and read it
    $ echo [available frequency] > /sys/class/devfreq/[dev name]/max_freq and read it
    $ cat /sys/class/devfreq/[dev name]/cur_freq
    $ echo 0 > /sys/class/devfreq/[dev name]/trans_stat 
    $ cat /sys/class/devfreq/[dev name]/trans_stat

    https://elinux.org/images/6/61/ELC_Europe_2020_Optimizing_and_Developing_Non-CPU_Device_Power_Management_by_DEVFREQ_Chanwoo_Choi.pdf

     

    요약본

     

    governor 설명 추가 필요

    opp 설명 추가 필요

    pm qos 설명 추가 필요

    drivers/base/power/qos.c

    kernel/power/qos.c

    governor 와 policy 의 관계

     

    vendor 별 최적화한 알고리즘 - CL (Closed Loop) DVFS, memlat 

     

    cf) 기본적인 initcall, workqueue, notifier, device tree 설명 (Appendix 로 작성하기), scmi 추가?

     

    참고

    CPUFREQ

    https://dreamlog.tistory.com/4

    https://linux-sunxi.org/Cpufreq

    http://www.wowotech.net/pm_subsystem/cpufreq_overview.html

    https://usermanual.wiki/Document/RockchipDeveloperGuideLinux44CPUFreqCN.1647056508/html

    https://www.cnblogs.com/LoyenWang/p/11385811.html

    http://www.wowotech.net/process_management/schedutil_governor.html

    https://idebian.wordpress.com/2008/06/22/cpu-frequency-scaling-in-linux/

    https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/power_management_guide/tuning_cpufreq_policy_and_speed

     

    DEVFREQ

    https://m.fx361.com/news/2020/0430/6610871.html

    https://usermanual.wiki/Document/RockchipDeveloperGuideLinux44Devfreq.527956033/html

    https://www.cnblogs.com/hellokitty2/p/13061707.html

    https://blog.csdn.net/feelabclihu/article/details/105592301

    https://www.modb.pro/db/87721

    https://blog.csdn.net/weixin_39059738/article/details/104260671

    https://zhuanlan.zhihu.com/p/585218834

    https://elinux.org/images/6/61/ELC_Europe_2020_Optimizing_and_Developing_Non-CPU_Device_Power_Management_by_DEVFREQ_Chanwoo_Choi.pdf

    https://events.static.linuxfound.org/images/stories/pdf/lcjp2012_ham.pdf

    댓글

Designed by Tistory.