| // 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); | |
| } | |
| } | |
| } |