uconsole_sleep/
power_mode.rs

1//! Power mode helper - combines display toggling with CPU frequency changes
2
3use crate::hardware::{backlight, drm_panel, framebuffer};
4use crate::{CpuFreqConfig, WifiConfig};
5use log::{debug, info, warn};
6use std::fs;
7
8fn toggle_display(dry_run: bool) -> Result<(), String> {
9    let backlight_path = match backlight::find_backlight() {
10        Ok(Some(p)) => p,
11        Ok(None) => return Err("backlight not found".to_string()),
12        Err(e) => return Err(format!("failed to find backlight: {}", e)),
13    };
14
15    let framebuffer_path = framebuffer::find_framebuffer().ok().flatten();
16    let drm_path = drm_panel::find_drm_panel().ok().flatten();
17
18    let bl_state =
19        fs::read_to_string(backlight_path.join("bl_power")).unwrap_or_else(|_| "4".to_string());
20    let bl_state_trim = bl_state.trim();
21
22    if bl_state_trim == "4" {
23        // ON
24        info!("Turning display ON");
25        if !dry_run {
26            if let Some(fb) = framebuffer_path {
27                let _ = fs::write(fb.join("blank"), "0");
28            }
29            let _ = fs::write(backlight_path.join("bl_power"), "0");
30            if let Some(drm) = drm_path {
31                let _ = fs::write(drm.join("status"), "detect");
32            }
33        } else {
34            debug!("DRY-RUN: toggle_display ON skipped");
35        }
36    } else {
37        // OFF
38        info!("Turning display OFF");
39        if !dry_run {
40            if let Some(drm) = drm_path {
41                let _ = fs::write(drm.join("status"), "off");
42            }
43            if let Some(fb) = framebuffer_path {
44                let _ = fs::write(fb.join("blank"), "1");
45            }
46            let _ = fs::write(backlight_path.join("bl_power"), "4");
47        } else {
48            debug!("DRY-RUN: toggle_display OFF skipped");
49        }
50    }
51    Ok(())
52}
53
54/// Power saving mode state
55#[derive(Clone, Debug, PartialEq)]
56pub enum PowerMode {
57    Normal,
58    Saving,
59}
60
61pub fn enter_saving_mode(cpu_config: &CpuFreqConfig, dry_run: bool, wifi: Option<&WifiConfig>) {
62    info!("Entering power-saving mode");
63    if let Err(e) = toggle_display(dry_run) {
64        warn!("toggle_display failed: {}", e);
65    }
66    cpu_config.apply_saving_mode(dry_run);
67    if let Some(w) = wifi {
68        w.block(dry_run);
69    }
70}
71
72/// Exit power-saving mode: restore CPU then turn display on
73pub fn exit_saving_mode(cpu_config: &CpuFreqConfig, dry_run: bool, wifi: Option<&WifiConfig>) {
74    info!("Exiting power-saving mode");
75    cpu_config.apply_normal_mode(dry_run);
76    if let Err(e) = toggle_display(dry_run) {
77        warn!("toggle_display failed: {}", e);
78    }
79    if let Some(w) = wifi {
80        w.unblock(dry_run);
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use std::env;
88    use std::fs;
89
90    #[test]
91    fn test_enter_exit_saving_mode_dryrun() {
92        let tmp = env::temp_dir().join(format!(
93            "uconsole_pm_test_{}",
94            std::time::SystemTime::now()
95                .duration_since(std::time::UNIX_EPOCH)
96                .unwrap()
97                .as_millis()
98        ));
99        let _ = fs::create_dir_all(&tmp);
100        let cpu = CpuFreqConfig::with_policy_path(tmp.clone(), Some(String::from("100,200")));
101        // Dry run should not create policy files
102        enter_saving_mode(&cpu, true, None);
103        assert!(!tmp.join("scaling_min_freq").exists());
104        assert!(!tmp.join("scaling_max_freq").exists());
105
106        // Non-dry-run should write
107        enter_saving_mode(&cpu, false, None);
108        assert!(tmp.join("scaling_min_freq").exists());
109        assert!(tmp.join("scaling_max_freq").exists());
110
111        // exit - verify it doesn't panic
112        exit_saving_mode(&cpu, false, None);
113    }
114}