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

veridian_kernel/drivers/
gpu.rs

1//! GPU Driver Module
2//!
3//! Supports VBE (VESA BIOS Extensions) and GOP (Graphics Output Protocol) for
4//! framebuffer access
5
6// GPU driver
7
8use core::slice;
9
10use spin::Mutex;
11
12use crate::{
13    error::KernelError,
14    graphics::{Color, PixelFormat},
15};
16
17/// VBE Mode Info Block
18#[repr(C, packed)]
19#[derive(Debug, Clone, Copy)]
20pub struct VbeModeInfo {
21    pub attributes: u16,
22    pub window_a: u8,
23    pub window_b: u8,
24    pub granularity: u16,
25    pub window_size: u16,
26    pub segment_a: u16,
27    pub segment_b: u16,
28    pub win_func_ptr: u32,
29    pub pitch: u16,
30    pub width: u16,
31    pub height: u16,
32    pub w_char: u8,
33    pub y_char: u8,
34    pub planes: u8,
35    pub bpp: u8,
36    pub banks: u8,
37    pub memory_model: u8,
38    pub bank_size: u8,
39    pub image_pages: u8,
40    pub reserved0: u8,
41    pub red_mask: u8,
42    pub red_position: u8,
43    pub green_mask: u8,
44    pub green_position: u8,
45    pub blue_mask: u8,
46    pub blue_position: u8,
47    pub reserved_mask: u8,
48    pub reserved_position: u8,
49    pub direct_color_attributes: u8,
50    pub framebuffer: u32,
51    pub off_screen_mem_off: u32,
52    pub off_screen_mem_size: u16,
53}
54
55/// GOP Pixel Format
56#[repr(u32)]
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum GopPixelFormat {
59    RedGreenBlueReserved = 0,
60    BlueGreenRedReserved = 1,
61    BitMask = 2,
62    BltOnly = 3,
63}
64
65/// GOP Mode Info
66#[repr(C)]
67#[derive(Debug, Clone, Copy)]
68pub struct GopModeInfo {
69    pub version: u32,
70    pub horizontal_resolution: u32,
71    pub vertical_resolution: u32,
72    pub pixel_format: GopPixelFormat,
73    pub pixel_information: [u32; 4],
74    pub pixels_per_scan_line: u32,
75}
76
77/// GPU Driver
78pub struct GpuDriver {
79    framebuffer_addr: usize,
80    width: usize,
81    height: usize,
82    pitch: usize,
83    #[allow(dead_code)] // Bytes-per-pixel for VBE/GOP framebuffer formats
84    bpp: usize,
85    pixel_format: PixelFormat,
86}
87
88// PixelFormat is imported from crate::graphics::PixelFormat
89
90impl GpuDriver {
91    /// Create GPU driver from VBE mode info
92    pub fn from_vbe(mode_info: &VbeModeInfo) -> Result<Self, KernelError> {
93        if mode_info.framebuffer == 0 {
94            return Err(KernelError::HardwareError {
95                device: "vbe",
96                code: 1,
97            });
98        }
99
100        let pixel_format = if mode_info.red_position == 0 {
101            PixelFormat::Bgr888
102        } else {
103            PixelFormat::Rgb888
104        };
105
106        Ok(Self {
107            framebuffer_addr: mode_info.framebuffer as usize,
108            width: mode_info.width as usize,
109            height: mode_info.height as usize,
110            pitch: mode_info.pitch as usize,
111            bpp: mode_info.bpp as usize / 8,
112            pixel_format,
113        })
114    }
115
116    /// Create GPU driver from GOP mode info
117    pub fn from_gop(framebuffer_addr: usize, mode_info: &GopModeInfo) -> Result<Self, KernelError> {
118        if framebuffer_addr == 0 {
119            return Err(KernelError::HardwareError {
120                device: "gop",
121                code: 1,
122            });
123        }
124
125        let pixel_format = match mode_info.pixel_format {
126            GopPixelFormat::RedGreenBlueReserved => PixelFormat::Rgba8888,
127            GopPixelFormat::BlueGreenRedReserved => PixelFormat::Bgra8888,
128            _ => PixelFormat::Rgba8888,
129        };
130
131        Ok(Self {
132            framebuffer_addr,
133            width: mode_info.horizontal_resolution as usize,
134            height: mode_info.vertical_resolution as usize,
135            pitch: (mode_info.pixels_per_scan_line * 4) as usize,
136            bpp: 4,
137            pixel_format,
138        })
139    }
140
141    /// Create a simple framebuffer driver (for testing)
142    pub fn simple(framebuffer_addr: usize, width: usize, height: usize) -> Self {
143        Self {
144            framebuffer_addr,
145            width,
146            height,
147            pitch: width * 4,
148            bpp: 4,
149            pixel_format: PixelFormat::Rgba8888,
150        }
151    }
152
153    /// Get framebuffer as mutable slice
154    fn framebuffer_mut(&mut self) -> &mut [u32] {
155        // SAFETY: framebuffer_addr points to a memory-mapped framebuffer region
156        // provided by the VBE/GOP firmware or set during initialization. The
157        // slice covers exactly (pitch * height / 4) u32 entries, which
158        // corresponds to the full framebuffer. We hold &mut self so no other
159        // reference to this data exists.
160        unsafe {
161            slice::from_raw_parts_mut(
162                self.framebuffer_addr as *mut u32,
163                (self.pitch * self.height) / 4,
164            )
165        }
166    }
167
168    /// Set a pixel at (x, y)
169    pub fn set_pixel(&mut self, x: usize, y: usize, color: Color) -> Result<(), KernelError> {
170        if x >= self.width || y >= self.height {
171            return Err(KernelError::InvalidArgument {
172                name: "coordinates",
173                value: "out_of_bounds",
174            });
175        }
176
177        let offset = y * (self.pitch / 4) + x;
178        let pixel_value = self.color_to_pixel(color);
179
180        let fb = self.framebuffer_mut();
181        fb[offset] = pixel_value;
182
183        Ok(())
184    }
185
186    /// Fill a rectangle
187    pub fn fill_rect(
188        &mut self,
189        x: usize,
190        y: usize,
191        w: usize,
192        h: usize,
193        color: Color,
194    ) -> Result<(), KernelError> {
195        let pixel_value = self.color_to_pixel(color);
196        let width = self.width;
197        let height = self.height;
198        let pitch = self.pitch;
199        let fb = self.framebuffer_mut();
200
201        for dy in 0..h {
202            let row_y = y + dy;
203            if row_y >= height {
204                break;
205            }
206
207            for dx in 0..w {
208                let col_x = x + dx;
209                if col_x >= width {
210                    break;
211                }
212
213                let offset = row_y * (pitch / 4) + col_x;
214                fb[offset] = pixel_value;
215            }
216        }
217
218        Ok(())
219    }
220
221    /// Clear the screen
222    pub fn clear(&mut self, color: Color) {
223        if let Err(_e) = self.fill_rect(0, 0, self.width, self.height, color) {
224            crate::println!("[GPU] Warning: fill_rect failed during clear: {:?}", _e);
225        }
226    }
227
228    /// Convert Color to pixel value based on format
229    fn color_to_pixel(&self, color: Color) -> u32 {
230        match self.pixel_format {
231            PixelFormat::Rgb888
232            | PixelFormat::Rgba8888
233            | PixelFormat::Xrgb8888
234            | PixelFormat::Argb8888 => {
235                ((color.a as u32) << 24)
236                    | ((color.r as u32) << 16)
237                    | ((color.g as u32) << 8)
238                    | (color.b as u32)
239            }
240            PixelFormat::Bgr888
241            | PixelFormat::Bgra8888
242            | PixelFormat::Xbgr8888
243            | PixelFormat::Abgr8888
244            | PixelFormat::Bgrx8888 => {
245                ((color.a as u32) << 24)
246                    | ((color.b as u32) << 16)
247                    | ((color.g as u32) << 8)
248                    | (color.r as u32)
249            }
250            PixelFormat::Rgb565 => {
251                let r5 = (color.r as u32 & 0xF8) << 8;
252                let g6 = (color.g as u32 & 0xFC) << 3;
253                let b5 = (color.b as u32) >> 3;
254                r5 | g6 | b5
255            }
256            PixelFormat::Gray8 => {
257                let luma = (color.r as u32 * 77 + color.g as u32 * 150 + color.b as u32 * 29) >> 8;
258                luma | (luma << 8) | (luma << 16) | 0xFF00_0000
259            }
260        }
261    }
262
263    /// Get width
264    pub fn width(&self) -> usize {
265        self.width
266    }
267
268    /// Get height
269    pub fn height(&self) -> usize {
270        self.height
271    }
272
273    /// Return a string describing how this driver was configured.
274    fn detection_mode(&self) -> &'static str {
275        // The simple() fallback always uses 0xFD000000; a real bootloader
276        // framebuffer will have a different address.
277        if self.framebuffer_addr == 0xFD000000 {
278            "fallback"
279        } else {
280            "GOP detected"
281        }
282    }
283
284    /// Blit buffer to screen
285    pub fn blit(
286        &mut self,
287        buffer: &[u32],
288        x: usize,
289        y: usize,
290        w: usize,
291        h: usize,
292    ) -> Result<(), KernelError> {
293        let width = self.width;
294        let height = self.height;
295        let pitch = self.pitch;
296        let fb = self.framebuffer_mut();
297
298        for dy in 0..h {
299            let row_y = y + dy;
300            if row_y >= height {
301                break;
302            }
303
304            for dx in 0..w {
305                let col_x = x + dx;
306                if col_x >= width {
307                    break;
308                }
309
310                let buf_offset = dy * w + dx;
311                let fb_offset = row_y * (pitch / 4) + col_x;
312
313                if buf_offset < buffer.len() {
314                    fb[fb_offset] = buffer[buf_offset];
315                }
316            }
317        }
318
319        Ok(())
320    }
321}
322
323/// Global GPU driver instance protected by Mutex
324static GPU_DRIVER: Mutex<Option<GpuDriver>> = Mutex::new(None);
325
326/// Initialize GPU driver
327pub fn init() -> Result<(), KernelError> {
328    println!("[GPU] Initializing GPU driver...");
329
330    let driver = detect_framebuffer();
331
332    let (width, height, mode) = (driver.width, driver.height, driver.detection_mode());
333
334    *GPU_DRIVER.lock() = Some(driver);
335
336    println!(
337        "[GPU] GPU driver initialized ({}x{}, {})",
338        width, height, mode
339    );
340    Ok(())
341}
342
343/// Detect the framebuffer from the bootloader's BootInfo (x86_64 UEFI GOP).
344///
345/// On x86_64, queries the boot framebuffer info provided by the UEFI
346/// bootloader. On other architectures (or if no framebuffer is available),
347/// falls back to a simple software framebuffer.
348fn detect_framebuffer() -> GpuDriver {
349    #[cfg(target_arch = "x86_64")]
350    {
351        if let Some(fb_info) = crate::arch::x86_64::boot::get_framebuffer_info() {
352            let pixel_format = if fb_info.is_bgr {
353                PixelFormat::Bgra8888
354            } else {
355                PixelFormat::Rgba8888
356            };
357
358            return GpuDriver {
359                framebuffer_addr: fb_info.buffer as usize,
360                width: fb_info.width,
361                height: fb_info.height,
362                pitch: fb_info.stride,
363                bpp: fb_info.bpp,
364                pixel_format,
365            };
366        }
367    }
368
369    // Fallback for non-x86_64 architectures or when no bootloader framebuffer
370    // is available
371    GpuDriver::simple(0xFD000000, 1024, 768)
372}
373
374/// Execute a closure with the GPU driver (mutable access)
375pub fn with_driver<R, F: FnOnce(&mut GpuDriver) -> R>(f: F) -> Option<R> {
376    GPU_DRIVER.lock().as_mut().map(f)
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_pixel_format() {
385        let driver = GpuDriver::simple(0x1000000, 800, 600);
386        let color = Color {
387            r: 255,
388            g: 128,
389            b: 64,
390            a: 255,
391        };
392        let pixel = driver.color_to_pixel(color);
393
394        // RGBA8888 format
395        assert_eq!(pixel, 0xFF_FF_80_40);
396    }
397}