| // Copyright 2021 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::convert::TryFrom; |
| use std::fs::File; |
| use std::io::BufRead; |
| use std::io::BufReader; |
| use std::path::Path; |
| use std::path::PathBuf; |
| use std::str::FromStr; |
| use std::sync::Mutex; |
| |
| use anyhow::bail; |
| use anyhow::Context; |
| use anyhow::Result; |
| use log::warn; |
| |
| use crate::arch; |
| use crate::power; |
| use crate::sync::NoPoison; |
| |
| /// Parse the first line of a file as a type implementing std::str::FromStr. |
| pub fn read_from_file<T: FromStr, P: AsRef<Path>>(path: &P) -> Result<T> |
| where |
| T::Err: std::error::Error + Sync + Send + 'static, |
| { |
| let reader = File::open(path).map(BufReader::new)?; |
| let line = reader.lines().next().context("No content in file")??; |
| line.parse() |
| .with_context(|| format!("failed to parse \"{line}\"")) |
| } |
| |
| #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
| pub enum GameMode { |
| // Game mode is off. |
| Off = 0, |
| // Game mode is on, borealis is the foreground subsystem. |
| Borealis = 1, |
| // Game mode for ARC is on, which means we shouldn't evict Android apps that |
| // are foreground or perceptible. |
| Arc = 2, |
| } |
| |
| impl TryFrom<u8> for GameMode { |
| type Error = anyhow::Error; |
| |
| fn try_from(mode_raw: u8) -> Result<GameMode> { |
| Ok(match mode_raw { |
| 0 => GameMode::Off, |
| 1 => GameMode::Borealis, |
| 2 => GameMode::Arc, |
| _ => bail!("Unsupported game mode value"), |
| }) |
| } |
| } |
| |
| static GAME_MODE: Mutex<GameMode> = Mutex::new(GameMode::Off); |
| |
| pub struct TuneSwappiness { |
| pub swappiness: u32, |
| } |
| |
| // Returns a TuneSwappiness object when swappiness needs to be tuned after setting game mode. |
| pub fn set_game_mode( |
| power_preference_manager: &dyn power::PowerPreferencesManager, |
| mode: GameMode, |
| root: PathBuf, |
| ) -> Option<TuneSwappiness> { |
| let old_mode = { |
| let mut data = GAME_MODE.do_lock(); |
| let old_mode = *data; |
| *data = mode; |
| old_mode |
| }; |
| |
| // Don't fail game mode settings if EPP can't be changed. |
| if let Err(e) = update_power_preferences(power_preference_manager) { |
| warn!( |
| "Unable to set EPP {:?}. Continue setting other game mode options.", |
| e |
| ); |
| } |
| |
| if old_mode != GameMode::Borealis && mode == GameMode::Borealis { |
| arch::apply_borealis_tuning(&root, true) |
| } else if old_mode == GameMode::Borealis && mode != GameMode::Borealis { |
| arch::apply_borealis_tuning(&root, false) |
| } else { |
| None |
| } |
| } |
| |
| pub fn get_game_mode() -> GameMode { |
| *GAME_MODE.do_lock() |
| } |
| |
| #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
| pub enum RTCAudioActive { |
| // RTC is not active. |
| Inactive = 0, |
| // RTC is active, RTC audio is playing and recording. |
| Active = 1, |
| } |
| static RTC_AUDIO_ACTIVE: Mutex<RTCAudioActive> = Mutex::new(RTCAudioActive::Inactive); |
| |
| impl TryFrom<u8> for RTCAudioActive { |
| type Error = anyhow::Error; |
| |
| fn try_from(active_raw: u8) -> Result<RTCAudioActive> { |
| Ok(match active_raw { |
| 0 => RTCAudioActive::Inactive, |
| 1 => RTCAudioActive::Active, |
| _ => bail!("Unsupported RTC audio active value"), |
| }) |
| } |
| } |
| |
| #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
| pub enum FullscreenVideo { |
| // Full screen video is not active. |
| Inactive = 0, |
| // Full screen video is active. |
| Active = 1, |
| } |
| static FULLSCREEN_VIDEO: Mutex<FullscreenVideo> = Mutex::new(FullscreenVideo::Inactive); |
| |
| impl TryFrom<u8> for FullscreenVideo { |
| type Error = anyhow::Error; |
| |
| fn try_from(mode_raw: u8) -> Result<FullscreenVideo> { |
| Ok(match mode_raw { |
| 0 => FullscreenVideo::Inactive, |
| 1 => FullscreenVideo::Active, |
| _ => bail!("Unsupported fullscreen video mode"), |
| }) |
| } |
| } |
| |
| #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
| pub enum VmBootMode { |
| // VM boot mode is not active. |
| Inactive = 0, |
| // VM boot mode active. |
| Active = 1, |
| } |
| static VMBOOT_MODE: Mutex<VmBootMode> = Mutex::new(VmBootMode::Inactive); |
| |
| impl TryFrom<u8> for VmBootMode { |
| type Error = anyhow::Error; |
| |
| fn try_from(mode_raw: u8) -> Result<VmBootMode> { |
| Ok(match mode_raw { |
| 0 => VmBootMode::Inactive, |
| 1 => VmBootMode::Active, |
| _ => bail!("Unsupported VM boot mode"), |
| }) |
| } |
| } |
| |
| pub fn is_vm_boot_mode_enabled() -> bool { |
| // Since this vm_boot_mode has no performance impact on lower end x86_64 |
| // devices and may hit thermal throttole on high end x86_64 ddevices, |
| // this feature is disabled on x86_64. |
| #[cfg(not(target_arch = "x86_64"))] |
| return true; |
| #[cfg(target_arch = "x86_64")] |
| return false; |
| } |
| |
| #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
| pub enum BatterySaverMode { |
| // Battery saver mode is not active. |
| Inactive = 0, |
| // Battery saver mode is active. |
| Active = 1, |
| } |
| static BATTERY_SAVER_MODE: Mutex<BatterySaverMode> = Mutex::new(BatterySaverMode::Inactive); |
| |
| impl TryFrom<u8> for BatterySaverMode { |
| type Error = anyhow::Error; |
| |
| fn try_from(active_raw: u8) -> Result<BatterySaverMode> { |
| Ok(match active_raw { |
| 0 => BatterySaverMode::Inactive, |
| 1 => BatterySaverMode::Active, |
| _ => bail!("Unsupported battery saver mode value"), |
| }) |
| } |
| } |
| |
| pub fn update_power_preferences( |
| power_preference_manager: &dyn power::PowerPreferencesManager, |
| ) -> Result<()> { |
| // We need to ensure that any function that locks more than one lock does it |
| // in the same order to avoid any dead locks. |
| let rtc_data = RTC_AUDIO_ACTIVE.do_lock(); |
| let fsv_data = FULLSCREEN_VIDEO.do_lock(); |
| let game_data = GAME_MODE.do_lock(); |
| let boot_data = VMBOOT_MODE.do_lock(); |
| let bsm_data = BATTERY_SAVER_MODE.do_lock(); |
| power_preference_manager |
| .update_power_preferences(*rtc_data, *fsv_data, *game_data, *boot_data, *bsm_data) |
| } |
| |
| pub fn set_rtc_audio_active( |
| power_preference_manager: &dyn power::PowerPreferencesManager, |
| mode: RTCAudioActive, |
| ) -> Result<()> { |
| *RTC_AUDIO_ACTIVE.do_lock() = mode; |
| |
| update_power_preferences(power_preference_manager)?; |
| Ok(()) |
| } |
| |
| pub fn get_rtc_audio_active() -> RTCAudioActive { |
| *RTC_AUDIO_ACTIVE.do_lock() |
| } |
| |
| pub fn set_fullscreen_video( |
| power_preference_manager: &dyn power::PowerPreferencesManager, |
| mode: FullscreenVideo, |
| ) -> Result<()> { |
| *FULLSCREEN_VIDEO.do_lock() = mode; |
| |
| update_power_preferences(power_preference_manager)?; |
| |
| Ok(()) |
| } |
| |
| pub fn get_fullscreen_video() -> FullscreenVideo { |
| *FULLSCREEN_VIDEO.do_lock() |
| } |
| |
| pub fn on_battery_saver_mode_change( |
| power_preference_manager: &dyn power::PowerPreferencesManager, |
| mode: BatterySaverMode, |
| ) -> Result<()> { |
| *BATTERY_SAVER_MODE.do_lock() = mode; |
| |
| update_power_preferences(power_preference_manager)?; |
| |
| Ok(()) |
| } |
| |
| pub fn set_vm_boot_mode( |
| power_preference_manager: &dyn power::PowerPreferencesManager, |
| mode: VmBootMode, |
| ) -> Result<()> { |
| if !is_vm_boot_mode_enabled() { |
| bail!("VM boot mode is not enabled"); |
| } |
| *VMBOOT_MODE.do_lock() = mode; |
| |
| update_power_preferences(power_preference_manager)?; |
| |
| Ok(()) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use std::io::Write; |
| |
| use tempfile::tempdir; |
| use tempfile::NamedTempFile; |
| |
| use super::*; |
| use crate::test_utils::*; |
| |
| #[test] |
| fn test_read_from_file() { |
| for (content, expected) in [("123", 123), ("456\n789", 456)] { |
| let mut file = NamedTempFile::new().unwrap(); |
| file.write_all(content.as_bytes()).unwrap(); |
| |
| assert_eq!( |
| read_from_file::<u64, _>(&file.path()).unwrap(), |
| expected as u64 |
| ); |
| assert_eq!( |
| read_from_file::<u32, _>(&file.path()).unwrap(), |
| expected as u32 |
| ); |
| assert_eq!( |
| read_from_file::<i64, _>(&file.path()).unwrap(), |
| expected as i64 |
| ); |
| assert_eq!(read_from_file::<i32, _>(&file.path()).unwrap(), expected); |
| } |
| |
| for (negative_content, expected) in [("-123", -123), ("-456\n789", -456)] { |
| let mut file = NamedTempFile::new().unwrap(); |
| file.write_all(negative_content.as_bytes()).unwrap(); |
| |
| assert_eq!( |
| read_from_file::<i64, _>(&file.path()).unwrap(), |
| expected as i64 |
| ); |
| assert_eq!(read_from_file::<i32, _>(&file.path()).unwrap(), expected); |
| |
| assert!(read_from_file::<u64, _>(&file.path()).is_err()); |
| assert!(read_from_file::<u32, _>(&file.path()).is_err()); |
| } |
| |
| for wrong_content in ["", "abc"] { |
| let mut file = NamedTempFile::new().unwrap(); |
| file.write_all(wrong_content.as_bytes()).unwrap(); |
| |
| assert!(read_from_file::<u64, _>(&file.path()).is_err()); |
| } |
| } |
| |
| #[test] |
| fn test_get_set_game_mode() { |
| let tmp_root = tempdir().unwrap(); |
| let root = tmp_root.path(); |
| setup_mock_cpu_dev_dirs(root).unwrap(); |
| setup_mock_cpu_files(root).unwrap(); |
| setup_mock_intel_gpu_dev_dirs(root); |
| setup_mock_intel_gpu_files(root); |
| let power_manager = MockPowerPreferencesManager { |
| root: root.to_path_buf(), |
| }; |
| assert_eq!(get_game_mode(), GameMode::Off); |
| set_game_mode(&power_manager, GameMode::Borealis, root.to_path_buf()); |
| assert_eq!(get_game_mode(), GameMode::Borealis); |
| set_game_mode(&power_manager, GameMode::Arc, root.to_path_buf()); |
| assert_eq!(get_game_mode(), GameMode::Arc); |
| } |
| |
| #[test] |
| fn test_get_set_rtc_audio_active() { |
| let tmp_root = tempdir().unwrap(); |
| let root = tmp_root.path(); |
| test_write_cpuset_root_cpus(root, "0-3"); |
| let power_manager = MockPowerPreferencesManager { |
| root: root.to_path_buf(), |
| }; |
| |
| assert_eq!(get_rtc_audio_active(), RTCAudioActive::Inactive); |
| set_rtc_audio_active(&power_manager, RTCAudioActive::Active).unwrap(); |
| assert_eq!(get_rtc_audio_active(), RTCAudioActive::Active); |
| } |
| |
| #[test] |
| fn test_get_set_fullscreen_video() { |
| let tmp_root = tempdir().unwrap(); |
| let root = tmp_root.path(); |
| test_write_cpuset_root_cpus(root, "0-3"); |
| let power_manager = MockPowerPreferencesManager { |
| root: root.to_path_buf(), |
| }; |
| |
| assert_eq!(get_fullscreen_video(), FullscreenVideo::Inactive); |
| set_fullscreen_video(&power_manager, FullscreenVideo::Active).unwrap(); |
| assert_eq!(get_fullscreen_video(), FullscreenVideo::Active); |
| } |
| } |