blob: fd07de0ff9b1cc2e99d304a48b890382622eb732 [file] [log] [blame] [edit]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
mod auto_epp;
mod auto_epp_config;
mod borealis;
mod cgroup_x86_64;
mod cpu_scaling;
mod globals;
mod gpu_freq_scaling;
use std::path::Path;
use std::path::PathBuf;
use anyhow::Context;
use anyhow::Result;
pub use borealis::apply_borealis_tuning;
use log::error;
use self::cgroup_x86_64::media_dynamic_cgroup;
use self::cgroup_x86_64::MediaDynamicCgroupAction;
use self::globals::BSM_SIGNAL;
use self::globals::DYNAMIC_EPP;
use self::globals::MEDIA_CGROUP_SIGNAL;
use self::globals::RTC_FS_SIGNAL;
use self::gpu_freq_scaling::intel_device;
use crate::common::BatterySaverMode;
use crate::common::FullscreenVideo;
use crate::common::RTCAudioActive;
use crate::config::EnergyPerformancePreference;
use crate::config::PowerPreferences;
use crate::config::PowerSourceType;
use crate::cpu_utils::write_to_cpu_policy_patterns;
fn has_epp(root_path: &Path) -> Result<bool> {
const CPU0_EPP_PATH: &str =
"sys/devices/system/cpu/cpufreq/policy0/energy_performance_preference";
let pattern = root_path
.join(CPU0_EPP_PATH)
.to_str()
.context("Cannot convert cpu0 epp path to string")?
.to_owned();
Ok(Path::new(&pattern).exists())
}
fn set_epp(root_path: &Path, epp: EnergyPerformancePreference) -> Result<()> {
const EPP_PATTERN: &str =
"sys/devices/system/cpu/cpufreq/policy*/energy_performance_preference";
let epp_str = match epp {
EnergyPerformancePreference::Default => "default",
EnergyPerformancePreference::Performance => "performance",
EnergyPerformancePreference::BalancePerformance => "balance_performance",
EnergyPerformancePreference::BalancePower => "balance_power",
EnergyPerformancePreference::Power => "power",
};
if has_epp(root_path)? {
let pattern = root_path
.join(EPP_PATTERN)
.to_str()
.context("Cannot convert epp path to string")?
.to_owned();
return write_to_cpu_policy_patterns(&pattern, epp_str);
}
Ok(())
}
fn get_first_scaling_governor(root_path: &Path) -> Result<String> {
const FIRST_GOVERNOR_PATTERN: &str = "sys/devices/system/cpu/cpufreq/policy0/scaling_governor";
let governor = std::fs::read_to_string(root_path.join(FIRST_GOVERNOR_PATTERN))?;
Ok(governor)
}
// Intel only tuning to limit GPU frequency (i.e. power draw) during video conference
fn set_gt_boost_freq_mhz(mode: RTCAudioActive) -> Result<()> {
if intel_device::is_intel_device(PathBuf::from("/")) {
set_gt_boost_freq_mhz_impl(Path::new("/"), mode)
} else {
/* AMD */
Ok(())
}
}
// Extract the impl function for unittest.
fn set_gt_boost_freq_mhz_impl(root: &Path, mode: RTCAudioActive) -> Result<()> {
let mut gpu_config = intel_device::IntelGpuDeviceConfig::new(root.to_owned(), 100)?;
gpu_config.set_rtc_audio_active(mode == RTCAudioActive::Active)
}
fn set_default_epp(root_path: &Path) -> Result<()> {
// When scaling_governor is performance, energy_performance_preference can only be
// performance.
//
// Reference:
// https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/third_party/kernel/v6.6/drivers/cpufreq/intel_pstate.c;drc=1a868273760040b746518aca7fea4f8c07366884;l=795
match get_first_scaling_governor(root_path) {
Ok(governor) if governor.trim() == "performance" => Ok(()),
_ => {
// Default EPP
set_epp(root_path, EnergyPerformancePreference::BalancePerformance)
}
}
}
pub fn apply_platform_power_settings(
root_path: &Path,
power_source: PowerSourceType,
rtc: RTCAudioActive,
fullscreen: FullscreenVideo,
bsm: BatterySaverMode,
need_default_epp: bool,
) -> Result<()> {
let fullscreen_video_efficiency_mode = power_source == PowerSourceType::DC
&& (rtc == RTCAudioActive::Active || fullscreen == FullscreenVideo::Active);
if DYNAMIC_EPP.read_value() {
RTC_FS_SIGNAL.set_value(fullscreen_video_efficiency_mode);
MEDIA_CGROUP_SIGNAL.set_value(fullscreen == FullscreenVideo::Active);
BSM_SIGNAL.set_value(bsm == BatterySaverMode::Active);
} else if bsm == BatterySaverMode::Inactive {
if fullscreen_video_efficiency_mode {
if let Err(err) = set_epp(root_path, EnergyPerformancePreference::BalancePower) {
error!("Failed to set energy performance preference: {:#}", err);
}
} else if need_default_epp {
set_default_epp(root_path)?;
}
} else if need_default_epp {
set_default_epp(root_path)?;
}
match fullscreen {
FullscreenVideo::Active => media_dynamic_cgroup(MediaDynamicCgroupAction::Start)?,
FullscreenVideo::Inactive => media_dynamic_cgroup(MediaDynamicCgroupAction::Stop)?,
}
if let Err(err) = set_gt_boost_freq_mhz(rtc) {
error!("Set boost freq not supported: {:#}", err)
}
Ok(())
}
pub fn apply_platform_power_preferences(
root_path: &Path,
preferences: &PowerPreferences,
) -> Result<()> {
if !DYNAMIC_EPP.read_value() {
if let Some(epp) = preferences.epp {
set_epp(root_path, epp)?;
}
}
Ok(())
}
pub fn init() {
cgroup_x86_64::register_feature();
auto_epp::init();
}
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::tempdir;
use super::*;
use crate::common;
use crate::config::Governor;
use crate::config::PowerPreferencesType;
use crate::power::DirectoryPowerPreferencesManager;
use crate::power::PowerPreferencesManager;
use crate::test_utils::*;
#[test]
#[cfg(target_arch = "x86_64")]
fn test_set_gt_boost_freq_mhz() {
let tmp_root = tempdir().unwrap();
let root = tmp_root.path();
setup_mock_intel_gpu_dev_dirs(root);
setup_mock_intel_gpu_files(root);
write_mock_cpuinfo(
root,
"filter_out",
"Intel(R) Core(TM) i3-10110U CPU @ 2.10GHz",
);
set_gt_boost_freq_mhz_impl(root, RTCAudioActive::Active)
.expect_err("Should return error on non-intel CPUs");
write_mock_cpuinfo(
root,
"GenuineIntel",
"Intel(R) Core(TM) i3-10110U CPU @ 2.10GHz",
);
set_intel_gpu_min(root, 300);
set_intel_gpu_max(root, 1100);
set_intel_gpu_boost(root, 0);
set_gt_boost_freq_mhz_impl(root, RTCAudioActive::Active)
.expect_err("Should return error when gpu_boost is 0");
set_intel_gpu_boost(root, 500);
set_gt_boost_freq_mhz_impl(root, RTCAudioActive::Active).unwrap();
assert_eq!(get_intel_gpu_boost(root), 300);
set_gt_boost_freq_mhz_impl(root, RTCAudioActive::Inactive).unwrap();
assert_eq!(get_intel_gpu_boost(root), 1100);
}
fn read_epp(root: &Path) -> Result<String> {
let epp_path = root
.join("sys/devices/system/cpu/cpufreq/policy0/")
.join("energy_performance_preference");
let epp = std::fs::read_to_string(epp_path)?;
Ok(epp)
}
fn write_epp(root: &Path, value: &str, affected_cpus: &str) -> Result<()> {
let policy_path = root.join("sys/devices/system/cpu/cpufreq/policy0");
fs::create_dir_all(&policy_path)?;
std::fs::write(policy_path.join("energy_performance_preference"), value)?;
std::fs::write(policy_path.join("affected_cpus"), affected_cpus)?;
Ok(())
}
#[test]
/// Tests the various EPP permutations
fn test_power_update_power_preferences_epp() {
let root = tempdir().unwrap();
test_write_cpuset_root_cpus(root.path(), "0-3");
let tests = [
(
FakePowerSourceProvider {
power_source: PowerSourceType::DC,
},
RTCAudioActive::Active,
FullscreenVideo::Inactive,
"balance_power",
),
(
FakePowerSourceProvider {
power_source: PowerSourceType::DC,
},
RTCAudioActive::Inactive,
FullscreenVideo::Active,
"balance_power",
),
(
FakePowerSourceProvider {
power_source: PowerSourceType::DC,
},
RTCAudioActive::Active,
FullscreenVideo::Active,
"balance_power",
),
(
FakePowerSourceProvider {
power_source: PowerSourceType::DC,
},
RTCAudioActive::Inactive,
FullscreenVideo::Inactive,
"balance_performance",
),
(
FakePowerSourceProvider {
power_source: PowerSourceType::AC,
},
RTCAudioActive::Inactive,
FullscreenVideo::Active,
"balance_performance",
),
(
FakePowerSourceProvider {
power_source: PowerSourceType::AC,
},
RTCAudioActive::Active,
FullscreenVideo::Active,
"balance_performance",
),
(
FakePowerSourceProvider {
power_source: PowerSourceType::AC,
},
RTCAudioActive::Active,
FullscreenVideo::Inactive,
"balance_performance",
),
];
for test in tests {
write_epp(root.path(), "balance_performance", AFFECTED_CPU0).unwrap();
let mut fake_config = FakeConfig::new();
fake_config.write_power_preference(
PowerSourceType::AC,
PowerPreferencesType::Default,
&PowerPreferences {
governor: Some(Governor::Schedutil),
epp: None,
cpu_offline: None,
cpufreq_disable_boost: false,
},
);
let config_provider = fake_config.provider();
let manager =
DirectoryPowerPreferencesManager::new(root.path(), config_provider, test.0);
manager
.update_power_preferences(
test.1,
test.2,
common::GameMode::Off,
common::VmBootMode::Inactive,
common::BatterySaverMode::Inactive,
)
.unwrap();
let epp = read_epp(root.path()).unwrap();
assert_eq!(epp, test.3);
}
}
#[test]
fn test_power_update_power_preferences_battery_saver_active() {
let temp_dir = tempdir().unwrap();
let root = temp_dir.path();
test_write_cpuset_root_cpus(root, "0-3");
let power_source_provider = FakePowerSourceProvider {
power_source: PowerSourceType::AC,
};
let mut fake_config = FakeConfig::new();
fake_config.write_power_preference(
PowerSourceType::AC,
PowerPreferencesType::ArcvmGaming,
&PowerPreferences {
governor: Some(Governor::Schedutil),
epp: None,
cpu_offline: None,
cpufreq_disable_boost: false,
},
);
fake_config.write_power_preference(
PowerSourceType::AC,
PowerPreferencesType::BatterySaver,
&PowerPreferences {
governor: Some(Governor::Conservative),
epp: None,
cpu_offline: None,
cpufreq_disable_boost: false,
},
);
fake_config.write_power_preference(
PowerSourceType::AC,
PowerPreferencesType::Default,
&PowerPreferences {
governor: Some(Governor::Schedutil),
epp: None,
cpu_offline: None,
cpufreq_disable_boost: false,
},
);
let config_provider = fake_config.provider();
let manager =
DirectoryPowerPreferencesManager::new(root, config_provider, power_source_provider);
let tests = [
(
BatterySaverMode::Active,
"balance_performance",
Governor::Conservative,
AFFECTED_CPU0, // policy0 affected_cpus
AFFECTED_CPU1, // policy1 affected_cpus
),
(
BatterySaverMode::Inactive,
"balance_performance",
Governor::Schedutil,
AFFECTED_CPU0, // policy0 affected_cpus
AFFECTED_CPU1, // policy1 affected_cpus
),
(
BatterySaverMode::Active,
"balance_performance",
Governor::Conservative,
AFFECTED_CPU_NONE, // policy0 affected_cpus, which has no affected cpus
AFFECTED_CPU1, // policy1 affected_cpus
),
];
// Test device without EPP path
for test in tests {
let orig_governor = Governor::Performance;
let policy0 = PolicyConfigs {
policy_path: TEST_CPUFREQ_POLICIES[0],
governor: &orig_governor,
affected_cpus: test.3,
};
let policy1 = PolicyConfigs {
policy_path: TEST_CPUFREQ_POLICIES[1],
governor: &orig_governor,
affected_cpus: test.4,
};
let policies = vec![policy0, policy1];
write_per_policy_scaling_governor(root, policies);
manager
.update_power_preferences(
common::RTCAudioActive::Inactive,
common::FullscreenVideo::Inactive,
common::GameMode::Arc,
common::VmBootMode::Inactive,
test.0,
)
.unwrap();
let mut expected_governors = vec![test.2, test.2];
if test.3.is_empty() {
expected_governors[0] = orig_governor;
}
check_per_policy_scaling_governor(root, expected_governors);
}
// Test device with EPP path
let orig_epp = "balance_performance";
for test in tests {
write_epp(root, orig_epp, test.3).unwrap();
manager
.update_power_preferences(
common::RTCAudioActive::Inactive,
common::FullscreenVideo::Inactive,
common::GameMode::Arc,
common::VmBootMode::Inactive,
test.0,
)
.unwrap();
let epp = read_epp(root).unwrap();
let mut expected_epp = test.1;
if test.3.is_empty() {
expected_epp = orig_epp;
}
assert_eq!(epp, expected_epp);
}
}
}