SW개발/Linux

DVFS in Linux Kernel

초코쨔응 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