Skip to main content

strat9_kernel/hardware/
ec.rs

1//! Embedded Controller (EC) driver for x86.
2//!
3//! Provides direct access to the ACPI Embedded Controller via I/O ports
4//! 0x62 (data) and 0x66 (command/status). Used for temperature reading,
5//! fan control, and battery management on laptops (ThinkPad X13, etc.).
6//!
7//! Reference: ACPI Embedded Controller Interface Specification 1.0a
8
9#![allow(dead_code)]
10
11use crate::arch::x86_64::io::{inb, outb};
12use core::sync::atomic::{AtomicBool, Ordering};
13use spin::Mutex;
14
15/// EC data port (read/write data bytes).
16const EC_DATA_PORT: u16 = 0x62;
17
18/// EC command/status port (write commands, read status).
19const EC_SC_PORT: u16 = 0x66;
20
21/// EC status register bits.
22const EC_STATUS_OBF: u8 = 1 << 0; // Output Buffer Full : data available to read
23const EC_STATUS_IBF: u8 = 1 << 1; // Input Buffer Full : controller busy, don't write
24const EC_STATUS_SCI: u8 = 1 << 5; // SCI event pending
25
26/// EC commands.
27const EC_CMD_READ: u8 = 0x80; // Read EC register
28const EC_CMD_WRITE: u8 = 0x81; // Write EC register
29const EC_CMD_QUERY: u8 = 0x84; // Query event
30
31/// Maximum number of retries when waiting for EC to become ready.
32const EC_TIMEOUT: u32 = 100_000;
33
34/// Whether the EC was detected and initialized successfully.
35static EC_AVAILABLE: AtomicBool = AtomicBool::new(false);
36
37/// Serialize all EC access to prevent interleaved command/data sequences.
38static EC_LOCK: Mutex<()> = Mutex::new(());
39
40/// Wait for the EC input buffer to be empty (ready to accept a write).
41///
42/// Returns `true` if the IBF cleared, `false` on timeout.
43fn ec_wait_ibf() -> bool {
44    for _ in 0..EC_TIMEOUT {
45        unsafe {
46            if inb(EC_SC_PORT) & EC_STATUS_IBF == 0 {
47                return true;
48            }
49        }
50        core::hint::spin_loop();
51    }
52    false
53}
54
55/// Wait for the EC output buffer to become full (data available to read).
56///
57/// Returns `Some(byte)` if data is ready, `None` on timeout.
58fn ec_wait_obf() -> Option<u8> {
59    for _ in 0..EC_TIMEOUT {
60        unsafe {
61            let status = inb(EC_SC_PORT);
62            if status & EC_STATUS_OBF != 0 {
63                return Some(inb(EC_DATA_PORT));
64            }
65        }
66        core::hint::spin_loop();
67    }
68    None
69}
70
71/// Read a single byte from an EC register.
72///
73/// # Safety
74/// The caller must ensure no concurrent EC access occurs without holding `EC_LOCK`.
75unsafe fn ec_read_byte(reg: u8) -> Option<u8> {
76    if !ec_wait_ibf() {
77        return None;
78    }
79    outb(EC_SC_PORT, EC_CMD_READ);
80    if !ec_wait_ibf() {
81        return None;
82    }
83    outb(EC_DATA_PORT, reg);
84    ec_wait_obf()
85}
86
87/// Write a single byte to an EC register.
88///
89/// # Safety
90/// The caller must ensure no concurrent EC access occurs without holding `EC_LOCK`.
91unsafe fn ec_write_byte(reg: u8, val: u8) -> bool {
92    if !ec_wait_ibf() {
93        return false;
94    }
95    outb(EC_SC_PORT, EC_CMD_WRITE);
96    if !ec_wait_ibf() {
97        return false;
98    }
99    outb(EC_DATA_PORT, reg);
100    if !ec_wait_ibf() {
101        return false;
102    }
103    outb(EC_DATA_PORT, val);
104    true
105}
106
107/// Query the EC for a pending event. Returns the event byte (Query ID).
108///
109/// # Safety
110/// The caller must ensure no concurrent EC access occurs without holding `EC_LOCK`.
111unsafe fn ec_query() -> Option<u8> {
112    if !ec_wait_ibf() {
113        return None;
114    }
115    outb(EC_SC_PORT, EC_CMD_QUERY);
116    ec_wait_obf()
117}
118
119// =========================================================================
120// Public API
121// =========================================================================
122
123/// Detect and initialize the Embedded Controller.
124///
125/// Probes port 0x66 to verify the EC is present. On most x86 laptops the EC
126/// is always present; this mainly serves to confirm the I/O ports are wired.
127pub fn init() {
128    // Try reading EC status to see if the port responds.
129    // A non-zero/0xFF response indicates a live EC.
130    let _lock = EC_LOCK.lock();
131    unsafe {
132        let status = inb(EC_SC_PORT);
133        if status != 0xFF {
134            EC_AVAILABLE.store(true, Ordering::Release);
135            log::info!(
136                "[EC] Embedded Controller detected (status=0x{:02x})",
137                status
138            );
139        } else {
140            log::warn!("[EC] No Embedded Controller found (status=0xFF)");
141        }
142    }
143}
144
145/// Returns whether the EC was detected.
146pub fn is_available() -> bool {
147    EC_AVAILABLE.load(Ordering::Acquire)
148}
149
150/// Read a byte from an EC register.
151pub fn read(reg: u8) -> Option<u8> {
152    let _lock = EC_LOCK.lock();
153    if !EC_AVAILABLE.load(Ordering::Acquire) {
154        return None;
155    }
156    unsafe { ec_read_byte(reg) }
157}
158
159/// Write a byte to an EC register.
160pub fn write(reg: u8, val: u8) -> bool {
161    let _lock = EC_LOCK.lock();
162    if !EC_AVAILABLE.load(Ordering::Acquire) {
163        return false;
164    }
165    unsafe { ec_write_byte(reg, val) }
166}
167
168/// Query a pending EC event (SCI). Returns the query byte.
169pub fn query_event() -> Option<u8> {
170    let _lock = EC_LOCK.lock();
171    if !EC_AVAILABLE.load(Ordering::Acquire) {
172        return None;
173    }
174    unsafe { ec_query() }
175}
176
177/// Check if there is a pending SCI event in the EC.
178pub fn has_sci_event() -> bool {
179    if !EC_AVAILABLE.load(Ordering::Acquire) {
180        return false;
181    }
182    unsafe { inb(EC_SC_PORT) & EC_STATUS_SCI != 0 }
183}