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

veridian_kernel/graphics/
framebuffer.rs

1//! Framebuffer implementation
2//!
3//! Provides the low-level framebuffer device interface including physical
4//! address tracking for user-space mmap and double-buffered rendering.
5
6// Framebuffer implementation
7
8use core::sync::atomic::{AtomicU64, Ordering};
9
10use spin::Mutex;
11
12use super::{Color, GraphicsContext, Rect};
13use crate::error::KernelError;
14
15/// Framebuffer information structure passed to user space via syscall.
16///
17/// Must be repr(C) for stable ABI across kernel/user boundary.
18#[repr(C)]
19#[derive(Debug, Clone, Copy)]
20pub struct FbInfo {
21    pub width: u32,
22    pub height: u32,
23    pub pitch: u32,
24    pub bpp: u32,
25    pub phys_addr: u64,
26    pub size: u64,
27    /// Pixel format: 0 = BGRA, 1 = RGBA
28    pub format: u32,
29    pub _reserved: u32,
30}
31
32/// Framebuffer configuration
33pub struct Framebuffer {
34    width: u32,
35    height: u32,
36    pitch: u32,
37    bpp: u8,
38    buffer: Option<*mut u32>,
39    /// Physical address of the framebuffer (for user-space mmap)
40    phys_addr: u64,
41    /// Total size of the framebuffer in bytes
42    size: u64,
43    /// Pixel format (0 = BGRA, 1 = RGBA)
44    format: u32,
45}
46
47// SAFETY: Framebuffer contains a raw pointer to memory-mapped framebuffer
48// memory that is shared via the Mutex<Framebuffer> wrapper. The pointer is
49// only accessed while the Mutex is held, preventing data races. The
50// underlying memory-mapped region is valid for the kernel's lifetime.
51unsafe impl Send for Framebuffer {}
52unsafe impl Sync for Framebuffer {}
53
54impl Framebuffer {
55    pub const fn new() -> Self {
56        Self {
57            width: 0,
58            height: 0,
59            pitch: 0,
60            bpp: 32,
61            buffer: None,
62            phys_addr: 0,
63            size: 0,
64            format: 0,
65        }
66    }
67
68    pub fn configure(&mut self, width: u32, height: u32, buffer: *mut u32) {
69        self.width = width;
70        self.height = height;
71        self.pitch = width * 4;
72        self.buffer = Some(buffer);
73        self.size = (self.pitch as u64) * (height as u64);
74    }
75
76    /// Configure with physical address tracking (for user-space mmap).
77    pub fn configure_with_phys(
78        &mut self,
79        width: u32,
80        height: u32,
81        pitch: u32,
82        bpp: u8,
83        buffer: *mut u32,
84        phys_addr: u64,
85        format: u32,
86    ) {
87        self.width = width;
88        self.height = height;
89        self.pitch = pitch;
90        self.bpp = bpp;
91        self.buffer = Some(buffer);
92        self.phys_addr = phys_addr;
93        self.size = (pitch as u64) * (height as u64);
94        self.format = format;
95    }
96
97    pub fn width(&self) -> u32 {
98        self.width
99    }
100
101    pub fn height(&self) -> u32 {
102        self.height
103    }
104
105    pub fn pitch(&self) -> u32 {
106        self.pitch
107    }
108
109    pub fn phys_addr(&self) -> u64 {
110        self.phys_addr
111    }
112
113    pub fn size(&self) -> u64 {
114        self.size
115    }
116
117    /// Get framebuffer info for user space.
118    pub fn get_info(&self) -> FbInfo {
119        FbInfo {
120            width: self.width,
121            height: self.height,
122            pitch: self.pitch,
123            bpp: self.bpp as u32,
124            phys_addr: self.phys_addr,
125            size: self.size,
126            format: self.format,
127            _reserved: 0,
128        }
129    }
130
131    /// Get the raw buffer pointer (for compositor back-buffer swap).
132    pub fn buffer_ptr(&self) -> Option<*mut u32> {
133        self.buffer
134    }
135}
136
137impl Default for Framebuffer {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl GraphicsContext for Framebuffer {
144    fn draw_pixel(&mut self, x: i32, y: i32, color: Color) {
145        if x < 0 || y < 0 || x >= self.width as i32 || y >= self.height as i32 {
146            return;
147        }
148
149        if let Some(buffer) = self.buffer {
150            // SAFETY: 'buffer' is a raw pointer to the framebuffer's pixel memory,
151            // set during initialization. The bounds check above guarantees x and y
152            // are within [0, width) and [0, height) respectively, so the computed
153            // offset is within the allocated framebuffer region.
154            unsafe {
155                let offset = (y as u32 * self.width + x as u32) as isize;
156                *buffer.offset(offset) = color.to_u32();
157            }
158        }
159    }
160
161    fn draw_rect(&mut self, rect: Rect, color: Color) {
162        // Draw top
163        for x in rect.x..(rect.x + rect.width as i32) {
164            self.draw_pixel(x, rect.y, color);
165            self.draw_pixel(x, rect.y + rect.height as i32 - 1, color);
166        }
167        // Draw sides
168        for y in rect.y..(rect.y + rect.height as i32) {
169            self.draw_pixel(rect.x, y, color);
170            self.draw_pixel(rect.x + rect.width as i32 - 1, y, color);
171        }
172    }
173
174    fn fill_rect(&mut self, rect: Rect, color: Color) {
175        for y in rect.y..(rect.y + rect.height as i32) {
176            for x in rect.x..(rect.x + rect.width as i32) {
177                self.draw_pixel(x, y, color);
178            }
179        }
180    }
181
182    fn clear(&mut self, color: Color) {
183        self.fill_rect(
184            Rect {
185                x: 0,
186                y: 0,
187                width: self.width,
188                height: self.height,
189            },
190            color,
191        );
192    }
193}
194
195static FRAMEBUFFER: Mutex<Framebuffer> = Mutex::new(Framebuffer::new());
196
197/// Execute a closure with the framebuffer (mutable access)
198pub fn with_framebuffer<R, F: FnOnce(&mut Framebuffer) -> R>(f: F) -> R {
199    f(&mut FRAMEBUFFER.lock())
200}
201
202/// Global tracking of the framebuffer's physical address for user-space mmap.
203/// Set during fbcon init when the bootloader provides framebuffer info.
204static FB_PHYS_ADDR: AtomicU64 = AtomicU64::new(0);
205
206/// Store the framebuffer physical address (called from bootstrap).
207pub fn set_phys_addr(phys: u64) {
208    FB_PHYS_ADDR.store(phys, Ordering::Release);
209    FRAMEBUFFER.lock().phys_addr = phys;
210}
211
212/// Get the framebuffer physical address.
213pub fn get_phys_addr() -> u64 {
214    FB_PHYS_ADDR.load(Ordering::Acquire)
215}
216
217/// Get framebuffer info (safe to call from syscall context).
218pub fn get_fb_info() -> Option<FbInfo> {
219    let fb = FRAMEBUFFER.lock();
220    if fb.width == 0 || fb.height == 0 {
221        return None;
222    }
223    Some(fb.get_info())
224}
225
226/// Initialize framebuffer
227#[cfg_attr(target_arch = "aarch64", allow(unused_variables))]
228pub fn init() -> Result<(), KernelError> {
229    println!("[FB] Initializing framebuffer...");
230
231    let fb = FRAMEBUFFER.lock();
232
233    println!(
234        "[FB] Framebuffer initialized ({}x{}, phys=0x{:x})",
235        fb.width(),
236        fb.height(),
237        fb.phys_addr(),
238    );
239    Ok(())
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn test_framebuffer_create() {
248        let fb = Framebuffer::new();
249        assert_eq!(fb.width(), 0);
250        assert_eq!(fb.height(), 0);
251    }
252}