⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/arch/x86_64/
dpms.rs

1//! DPMS (Display Power Management Signaling) for x86_64.
2//!
3//! Controls display power states via VGA register manipulation. Supports
4//! DPMS states: On, Standby, Suspend, Off. Includes an idle timer that
5//! transitions the display to lower power states after configurable
6//! inactivity periods.
7//!
8//! When DPMS is Off, framebuffer blits should be skipped to save
9//! CPU/bus bandwidth.
10
11#![allow(dead_code)]
12
13use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU8, Ordering};
14
15use crate::error::{KernelError, KernelResult};
16
17// ---------------------------------------------------------------------------
18// DPMS state definitions
19// ---------------------------------------------------------------------------
20
21/// Display power management states per VESA DPMS specification.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[repr(u8)]
24pub enum DpmsState {
25    /// Display fully on (H-sync: on, V-sync: on).
26    On = 0,
27    /// Standby mode (H-sync: off, V-sync: on). Quick wake-up.
28    Standby = 1,
29    /// Suspend mode (H-sync: on, V-sync: off). Moderate wake-up.
30    Suspend = 2,
31    /// Off (H-sync: off, V-sync: off). Longest wake-up.
32    Off = 3,
33}
34
35impl DpmsState {
36    /// Convert from raw u8 value.
37    fn from_u8(val: u8) -> Self {
38        match val {
39            0 => Self::On,
40            1 => Self::Standby,
41            2 => Self::Suspend,
42            3 => Self::Off,
43            _ => Self::On,
44        }
45    }
46}
47
48impl core::fmt::Display for DpmsState {
49    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        match self {
51            Self::On => write!(f, "On"),
52            Self::Standby => write!(f, "Standby"),
53            Self::Suspend => write!(f, "Suspend"),
54            Self::Off => write!(f, "Off"),
55        }
56    }
57}
58
59// ---------------------------------------------------------------------------
60// VGA register constants for DPMS
61// ---------------------------------------------------------------------------
62
63/// VGA Sequencer Index Register.
64const VGA_SEQ_INDEX: u16 = 0x03C4;
65
66/// VGA Sequencer Data Register.
67const VGA_SEQ_DATA: u16 = 0x03C5;
68
69/// Sequencer register 1: Clocking Mode.
70const SEQ_CLOCKING_MODE: u8 = 0x01;
71
72/// Bit 5 in Clocking Mode register: Screen Off.
73const SCREEN_OFF_BIT: u8 = 1 << 5;
74
75/// VGA CRT Controller Index Register.
76const VGA_CRTC_INDEX: u16 = 0x03D4;
77
78/// VGA CRT Controller Data Register.
79const VGA_CRTC_DATA: u16 = 0x03D5;
80
81/// CRTC register 0x17: Mode Control.
82const CRTC_MODE_CONTROL: u8 = 0x17;
83
84/// Bit 7 in CRTC Mode Control: Enable CRTC (sync generation).
85const CRTC_ENABLE_SYNC: u8 = 1 << 7;
86
87/// VGA Attribute Controller Address/Data Register.
88const VGA_ATTR_INDEX: u16 = 0x03C0;
89
90/// VGA Input Status Register 1 (for attribute controller reset).
91const VGA_INPUT_STATUS_1: u16 = 0x03DA;
92
93// ---------------------------------------------------------------------------
94// Global state
95// ---------------------------------------------------------------------------
96
97static DPMS_INITIALIZED: AtomicBool = AtomicBool::new(false);
98static CURRENT_DPMS_STATE: AtomicU8 = AtomicU8::new(0); // On
99
100/// Idle timeout in seconds (0 = disabled).
101static IDLE_TIMEOUT_SECS: AtomicU32 = AtomicU32::new(300); // 5 minutes default
102
103/// Ticks since last input event (incremented by timer, reset on input).
104static IDLE_TICKS: AtomicU32 = AtomicU32::new(0);
105
106/// Whether framebuffer blitting should be suppressed (DPMS Off).
107static FRAMEBUFFER_SUPPRESSED: AtomicBool = AtomicBool::new(false);
108
109// ---------------------------------------------------------------------------
110// Initialization
111// ---------------------------------------------------------------------------
112
113/// Initialize the DPMS subsystem.
114///
115/// Registers the idle timer callback and sets initial display state to On.
116pub fn dpms_init() -> KernelResult<()> {
117    if DPMS_INITIALIZED.load(Ordering::Acquire) {
118        return Err(KernelError::AlreadyExists {
119            resource: "DPMS",
120            id: 0,
121        });
122    }
123
124    CURRENT_DPMS_STATE.store(DpmsState::On as u8, Ordering::Release);
125    IDLE_TICKS.store(0, Ordering::Release);
126    FRAMEBUFFER_SUPPRESSED.store(false, Ordering::Release);
127
128    DPMS_INITIALIZED.store(true, Ordering::Release);
129    println!(
130        "[DPMS] Initialized, idle timeout: {}s",
131        IDLE_TIMEOUT_SECS.load(Ordering::Relaxed)
132    );
133
134    Ok(())
135}
136
137// ---------------------------------------------------------------------------
138// DPMS state control
139// ---------------------------------------------------------------------------
140
141/// Set the display DPMS state.
142///
143/// Controls the VGA sync signals to transition the display into the
144/// requested power state. Returns an error if DPMS is not initialized.
145pub fn dpms_set_state(state: DpmsState) -> KernelResult<()> {
146    if !DPMS_INITIALIZED.load(Ordering::Acquire) {
147        return Err(KernelError::NotInitialized { subsystem: "DPMS" });
148    }
149
150    let old_state = DpmsState::from_u8(CURRENT_DPMS_STATE.load(Ordering::Acquire));
151    if old_state == state {
152        return Ok(());
153    }
154
155    match state {
156        DpmsState::On => {
157            // Re-enable display output.
158            set_screen_on(true);
159            set_sync_enabled(true);
160            FRAMEBUFFER_SUPPRESSED.store(false, Ordering::Release);
161        }
162        DpmsState::Standby => {
163            // H-sync off, V-sync on: disable horizontal sync only.
164            // VGA registers don't have fine-grained H/V sync control via
165            // standard ports; use screen-off as an approximation.
166            set_screen_on(false);
167            FRAMEBUFFER_SUPPRESSED.store(true, Ordering::Release);
168        }
169        DpmsState::Suspend => {
170            // H-sync on, V-sync off.
171            set_screen_on(false);
172            FRAMEBUFFER_SUPPRESSED.store(true, Ordering::Release);
173        }
174        DpmsState::Off => {
175            // Both syncs off -- full display power down.
176            set_screen_on(false);
177            set_sync_enabled(false);
178            FRAMEBUFFER_SUPPRESSED.store(true, Ordering::Release);
179        }
180    }
181
182    CURRENT_DPMS_STATE.store(state as u8, Ordering::Release);
183    println!("[DPMS] State changed: {} -> {}", old_state, state);
184
185    Ok(())
186}
187
188/// Get the current DPMS state.
189pub fn dpms_get_state() -> DpmsState {
190    DpmsState::from_u8(CURRENT_DPMS_STATE.load(Ordering::Acquire))
191}
192
193/// Check whether the framebuffer should skip blitting (DPMS not On).
194pub fn is_framebuffer_suppressed() -> bool {
195    FRAMEBUFFER_SUPPRESSED.load(Ordering::Acquire)
196}
197
198// ---------------------------------------------------------------------------
199// Idle timer
200// ---------------------------------------------------------------------------
201
202/// Set the DPMS idle timeout in seconds.
203///
204/// The display transitions to Off after this many seconds of no input.
205/// Set to 0 to disable idle blanking.
206pub fn dpms_set_idle_timeout(seconds: u32) {
207    IDLE_TIMEOUT_SECS.store(seconds, Ordering::Release);
208    println!("[DPMS] Idle timeout set to {}s", seconds);
209}
210
211/// Get the current idle timeout in seconds.
212pub fn dpms_get_idle_timeout() -> u32 {
213    IDLE_TIMEOUT_SECS.load(Ordering::Acquire)
214}
215
216/// Reset the idle timer.
217///
218/// Should be called on every input event (keyboard, mouse, touch) to
219/// prevent the display from blanking during active use.
220pub fn dpms_reset_idle() {
221    IDLE_TICKS.store(0, Ordering::Release);
222
223    // If display was blanked by idle, wake it up.
224    let state = dpms_get_state();
225    if state != DpmsState::On {
226        let _ = dpms_set_state(DpmsState::On);
227    }
228}
229
230/// Tick the idle timer (called from the scheduler/timer interrupt).
231///
232/// Increments the idle counter and transitions display state when the
233/// timeout is reached.
234pub fn dpms_idle_tick() {
235    if !DPMS_INITIALIZED.load(Ordering::Acquire) {
236        return;
237    }
238
239    let timeout = IDLE_TIMEOUT_SECS.load(Ordering::Acquire);
240    if timeout == 0 {
241        return;
242    }
243
244    // Only count ticks when display is On.
245    if dpms_get_state() != DpmsState::On {
246        return;
247    }
248
249    let ticks = IDLE_TICKS.fetch_add(1, Ordering::AcqRel);
250
251    // Timer fires at ~1Hz for idle tracking. When ticks reach timeout,
252    // blank the display.
253    if ticks >= timeout {
254        let _ = dpms_set_state(DpmsState::Off);
255    }
256}
257
258// ---------------------------------------------------------------------------
259// VGA register helpers
260// ---------------------------------------------------------------------------
261
262/// Enable or disable the display screen via VGA Sequencer register.
263fn set_screen_on(on: bool) {
264    // SAFETY: Ports 0x03C4/0x03C5 are standard VGA Sequencer registers.
265    // Writing to SEQ register 1 (Clocking Mode) bit 5 controls screen
266    // blanking. This does not affect video memory contents.
267    unsafe {
268        super::outb(VGA_SEQ_INDEX, SEQ_CLOCKING_MODE);
269        let mut val = super::inb(VGA_SEQ_DATA);
270        if on {
271            val &= !SCREEN_OFF_BIT;
272        } else {
273            val |= SCREEN_OFF_BIT;
274        }
275        super::outb(VGA_SEQ_INDEX, SEQ_CLOCKING_MODE);
276        super::outb(VGA_SEQ_DATA, val);
277    }
278}
279
280/// Enable or disable CRT sync signal generation.
281fn set_sync_enabled(enabled: bool) {
282    // SAFETY: Ports 0x03D4/0x03D5 are standard VGA CRTC registers.
283    // Writing CRTC register 0x17 bit 7 controls sync generation.
284    // Disabling sync is the mechanism for DPMS Off state.
285    unsafe {
286        super::outb(VGA_CRTC_INDEX, CRTC_MODE_CONTROL);
287        let mut val = super::inb(VGA_CRTC_DATA);
288        if enabled {
289            val |= CRTC_ENABLE_SYNC;
290        } else {
291            val &= !CRTC_ENABLE_SYNC;
292        }
293        super::outb(VGA_CRTC_INDEX, CRTC_MODE_CONTROL);
294        super::outb(VGA_CRTC_DATA, val);
295    }
296}
297
298// ---------------------------------------------------------------------------
299// Tests
300// ---------------------------------------------------------------------------
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[test]
307    fn test_dpms_state_from_u8() {
308        assert_eq!(DpmsState::from_u8(0), DpmsState::On);
309        assert_eq!(DpmsState::from_u8(1), DpmsState::Standby);
310        assert_eq!(DpmsState::from_u8(2), DpmsState::Suspend);
311        assert_eq!(DpmsState::from_u8(3), DpmsState::Off);
312        assert_eq!(DpmsState::from_u8(42), DpmsState::On); // fallback
313    }
314
315    #[test]
316    fn test_dpms_state_display() {
317        assert_eq!(alloc::format!("{}", DpmsState::On), "On");
318        assert_eq!(alloc::format!("{}", DpmsState::Standby), "Standby");
319        assert_eq!(alloc::format!("{}", DpmsState::Suspend), "Suspend");
320        assert_eq!(alloc::format!("{}", DpmsState::Off), "Off");
321    }
322
323    #[test]
324    fn test_dpms_state_repr() {
325        assert_eq!(DpmsState::On as u8, 0);
326        assert_eq!(DpmsState::Standby as u8, 1);
327        assert_eq!(DpmsState::Suspend as u8, 2);
328        assert_eq!(DpmsState::Off as u8, 3);
329    }
330
331    #[test]
332    fn test_idle_timeout_default() {
333        // Default is 300 seconds (5 minutes).
334        let timeout = 300u32;
335        assert_eq!(timeout, 300);
336    }
337
338    #[test]
339    fn test_screen_off_bit() {
340        assert_eq!(SCREEN_OFF_BIT, 0x20);
341    }
342
343    #[test]
344    fn test_crtc_enable_sync() {
345        assert_eq!(CRTC_ENABLE_SYNC, 0x80);
346    }
347}