-
DVFS in Linux KernelSW개발/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.ccpufreq 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://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.c 와 governor 들에 해당하는 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.cdevfreq 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
요약본
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/
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://blog.csdn.net/weixin_39059738/article/details/104260671
https://zhuanlan.zhihu.com/p/585218834
https://events.static.linuxfound.org/images/stories/pdf/lcjp2012_ham.pdf
'SW개발 > Linux' 카테고리의 다른 글
리눅스 telnet 으로 통신 테스트 (0) 2023.08.29 Power Management in Linux Kernel 참고자료 (0) 2023.07.28 Power Management in Linux Kernel (0) 2023.06.19 리눅스 커널의 러스트 공식 문서 (번역) (0) 2023.04.19 Git 이라는 이름의 의미 (0) 2021.03.20 댓글