DVFS in Linux Kernel
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://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.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
요약본
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