Skip to main content

strat9_kernel/hardware/video/
display_scheme.rs

1//! Display VFS scheme : exposes framebuffer as `/dev/display/`.
2//!
3//! Modeled after Redox OS `vesad` + `driver-graphics` but implemented
4//! as a kernel scheme with `GraphicsAdapter` support for multi-display.
5//!
6//! # Files
7//!
8//! - `info`              : read-only: adapter info, display list
9//! - `0.0`, `0.1`, ...   : read/write: per-display screen (display_id.screen_id)
10//! - `damage`            : write-only: present dirty rect or full screen
11//! - `clear`             : write-only: clear screen to black
12//!
13//! # Multi-display
14//!
15//! ```text
16//! /dev/display/
17//! ├── info
18//! ├── 0.0          ← display 0, screen 0 (primary)
19//! ├── 0.1          ← display 0, screen 1 (secondary, if present)
20//! ├── damage
21//! └── clear
22//! ```
23
24use crate::{
25    hardware::video::{
26        graphics_adapter::{
27            get_adapter_for_display, total_display_count, Damage, DisplayScreen, GraphicsAdapter,
28            HeapScreen, SimpleDisplayAdapter,
29        },
30        Framebuffer,
31    },
32    syscall::error::SyscallError,
33    vfs::scheme::{
34        finalize_pseudo_stat, DirEntry, FileFlags, FileStat, OpenFlags, OpenResult, Scheme,
35        DEV_CHAR_FS, DT_CHR, DT_REG,
36    },
37};
38use alloc::{collections::BTreeMap, format, string::String, sync::Arc, vec, vec::Vec};
39use core::sync::atomic::{AtomicU64, Ordering};
40use spin::RwLock;
41
42const MAX_HANDLES: usize = 64;
43
44#[derive(Clone, Debug)]
45enum Handle {
46    Root,
47    Info,
48    /// Per-display screen: (global_display_id, offscreen buffer).
49    Screen(u32, Arc<RwLock<HeapScreen>>),
50    Damage,
51    Clear,
52}
53
54pub struct DisplayScheme {
55    handles: RwLock<BTreeMap<u64, Handle>>,
56    next: AtomicU64,
57    /// Offscreen buffers keyed by display_id.
58    screens: RwLock<BTreeMap<u32, Arc<RwLock<HeapScreen>>>>,
59}
60
61impl DisplayScheme {
62    fn new() -> Self {
63        Self {
64            handles: RwLock::new(BTreeMap::new()),
65            next: AtomicU64::new(1),
66            screens: RwLock::new(BTreeMap::new()),
67        }
68    }
69
70    fn alloc_id(&self) -> u64 {
71        self.next.fetch_add(1, Ordering::Relaxed)
72    }
73
74    /// Get or create the offscreen buffer for a display.
75    fn get_or_create_screen(&self, display_id: u32) -> Arc<RwLock<HeapScreen>> {
76        let screens = self.screens.read();
77        if let Some(screen) = screens.get(&display_id) {
78            return screen.clone();
79        }
80        drop(screens);
81
82        // Create new screen for this display.
83        let (w, h) = get_adapter_for_display(display_id as usize)
84            .map(|(a, local_id)| a.display_size(local_id))
85            .unwrap_or((800, 600));
86
87        let bpp = Framebuffer::info()
88            .map(|i| i.format.bits_per_pixel)
89            .unwrap_or(32);
90
91        let screen = Arc::new(RwLock::new(HeapScreen::new(w, h, bpp)));
92        self.screens.write().insert(display_id, screen.clone());
93        screen
94    }
95
96    /// Parse "display_id.screen_id" path to global display_id.
97    fn parse_display_path(path: &str) -> Option<u32> {
98        let parts: Vec<&str> = path.split('.').collect();
99        if parts.len() != 2 {
100            return None;
101        }
102        let display_id: u32 = parts[0].parse().ok()?;
103        let _screen_id: u32 = parts[1].parse().ok()?;
104        // For now, screen_id is always 0 (single screen per display).
105        Some(display_id)
106    }
107}
108
109impl Scheme for DisplayScheme {
110    fn open(&self, path: &str, _flags: OpenFlags) -> Result<OpenResult, SyscallError> {
111        let path = path.trim_start_matches('/');
112        let id = self.alloc_id();
113
114        if self.handles.read().len() >= MAX_HANDLES {
115            return Err(SyscallError::OutOfMemory);
116        }
117
118        let handle = if path.is_empty() {
119            Handle::Root
120        } else if path == "info" {
121            Handle::Info
122        } else if path == "damage" {
123            Handle::Damage
124        } else if path == "clear" {
125            Handle::Clear
126        } else if let Some(display_id) = Self::parse_display_path(path) {
127            let screen = self.get_or_create_screen(display_id);
128            Handle::Screen(display_id, screen)
129        } else {
130            return Err(SyscallError::NotFound);
131        };
132
133        self.handles.write().insert(id, handle.clone());
134
135        let flags = match &handle {
136            Handle::Root => FileFlags::DIRECTORY,
137            Handle::Info => FileFlags::DEVICE,
138            Handle::Screen(..) => FileFlags::DEVICE | FileFlags::CHUNK_WRITE,
139            Handle::Damage | Handle::Clear => FileFlags::DEVICE | FileFlags::CHUNK_WRITE,
140        };
141
142        Ok(OpenResult {
143            file_id: id,
144            size: None,
145            flags,
146        })
147    }
148
149    fn read(&self, fid: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
150        let h = self.handles.read();
151        let handle = h.get(&fid).ok_or(SyscallError::BadHandle)?;
152
153        match handle {
154            Handle::Root => {
155                let total = total_display_count();
156                let mut out = format!("displays={}\n", total);
157                for i in 0..total {
158                    if let Some((adapter, local_id)) = get_adapter_for_display(i) {
159                        let (w, h) = adapter.display_size(local_id);
160                        out.push_str(&format!("{}.0={}x{}\n", i, w, h));
161                    }
162                }
163                out.push_str("damage\n");
164                out.push_str("clear\n");
165                let b = out.as_bytes();
166                let start = offset as usize;
167                if start >= b.len() {
168                    return Ok(0);
169                }
170                let n = core::cmp::min(b.len() - start, buf.len());
171                buf[..n].copy_from_slice(&b[start..start + n]);
172                Ok(n)
173            }
174            Handle::Info => {
175                let total = total_display_count();
176                let mut s = format!("adapter_displays={}\n", total);
177                for i in 0..total {
178                    if let Some((adapter, local_id)) = get_adapter_for_display(i) {
179                        let (w, h) = adapter.display_size(local_id);
180                        s.push_str(&format!("display.{}.{}x{}\n", i, w, h));
181                    }
182                }
183                let b = s.as_bytes();
184                let start = offset as usize;
185                if start >= b.len() {
186                    return Ok(0);
187                }
188                let n = core::cmp::min(b.len() - start, buf.len());
189                buf[..n].copy_from_slice(&b[start..start + n]);
190                Ok(n)
191            }
192            Handle::Screen(display_id, screen) => {
193                // Read pixel data from offscreen buffer.
194                let scr = screen.read();
195                let scr_stride = scr.stride() as usize;
196                let start = offset as usize;
197                let total = (scr_stride * scr.height() as usize);
198                if start >= total {
199                    return Ok(0);
200                }
201                let n = core::cmp::min(total - start, buf.len());
202                unsafe {
203                    core::ptr::copy_nonoverlapping(scr.pixels().add(start), buf.as_mut_ptr(), n);
204                }
205                Ok(n)
206            }
207            Handle::Damage | Handle::Clear => Err(SyscallError::PermissionDenied),
208        }
209    }
210
211    fn write(&self, fid: u64, _offset: u64, buf: &[u8]) -> Result<usize, SyscallError> {
212        let h = self.handles.read();
213        let handle = h.get(&fid).ok_or(SyscallError::BadHandle)?;
214
215        match handle {
216            Handle::Root | Handle::Info => Err(SyscallError::PermissionDenied),
217            Handle::Screen(display_id, screen) => {
218                // Write pixel data to offscreen buffer.
219                // Format: [x_lo, x_hi, y_lo, y_hi, r, g, b, r, g, b, ...]
220                if buf.len() < 7 {
221                    return Err(SyscallError::InvalidArgument);
222                }
223                let x = u16::from_le_bytes([buf[0], buf[1]]) as u32;
224                let y = u16::from_le_bytes([buf[2], buf[3]]) as u32;
225                let pixels = &buf[4..];
226
227                let mut scr = screen.write();
228                let bpp = scr.bpp() as usize;
229                let bpx = bpp / 8;
230                let stride = scr.stride() as usize;
231                let dst = scr.pixels_mut();
232
233                if bpp == 32 {
234                    let mut off = 0;
235                    let mut py = y;
236                    while off + 3 < pixels.len() && py < scr.height() {
237                        let mut px = x;
238                        while off + 3 < pixels.len() && px < scr.width() {
239                            let r = pixels[off];
240                            let g = pixels[off + 1];
241                            let b = pixels[off + 2];
242                            off += 3;
243                            let pixel = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
244                            unsafe {
245                                core::ptr::write_volatile(
246                                    dst.add(py as usize * stride + px as usize * 4) as *mut u32,
247                                    pixel,
248                                );
249                            }
250                            px += 1;
251                        }
252                        py += 1;
253                    }
254                } else if bpp == 24 {
255                    let mut off = 0;
256                    let mut py = y;
257                    while off + 2 < pixels.len() && py < scr.height() {
258                        let mut px = x;
259                        while off + 2 < pixels.len() && px < scr.width() {
260                            let r = pixels[off];
261                            let g = pixels[off + 1];
262                            let b = pixels[off + 2];
263                            off += 3;
264                            let ptr = unsafe { dst.add(py as usize * stride + px as usize * 3) };
265                            unsafe {
266                                core::ptr::write_volatile(ptr, r);
267                                core::ptr::write_volatile(ptr.add(1), g);
268                                core::ptr::write_volatile(ptr.add(2), b);
269                            }
270                            px += 1;
271                        }
272                        py += 1;
273                    }
274                }
275                Ok(buf.len())
276            }
277            Handle::Damage => {
278                // Parse: "present" or "display_id,x,y,w,h" or just "x,y,w,h" for display 0.
279                let s = core::str::from_utf8(buf).unwrap_or("").trim();
280                if s == "present" || s.is_empty() {
281                    // Present all displays.
282                    let total = total_display_count();
283                    for i in 0..total {
284                        if let Some((adapter, local_id)) = get_adapter_for_display(i) {
285                            if let Some(screen_arc) = self.screens.read().get(&(i as u32)) {
286                                let scr = screen_arc.read();
287                                let damage = Damage::full(scr.width(), scr.height());
288                                adapter.update_plane(local_id, &*scr, damage);
289                            }
290                        }
291                    }
292                } else {
293                    let parts: Vec<&str> = s.split(',').collect();
294                    let (display_id, x, y, w, h) = if parts.len() == 5 {
295                        let did: u32 = parts[0].parse().unwrap_or(0);
296                        (
297                            did,
298                            parts[1].parse().unwrap_or(0),
299                            parts[2].parse().unwrap_or(0),
300                            parts[3].parse().unwrap_or(0),
301                            parts[4].parse().unwrap_or(0),
302                        )
303                    } else if parts.len() == 4 {
304                        (
305                            0u32,
306                            parts[0].parse().unwrap_or(0),
307                            parts[1].parse().unwrap_or(0),
308                            parts[2].parse().unwrap_or(0),
309                            parts[3].parse().unwrap_or(0),
310                        )
311                    } else {
312                        return Err(SyscallError::InvalidArgument);
313                    };
314                    if w > 0 && h > 0 {
315                        if let Some((adapter, local_id)) =
316                            get_adapter_for_display(display_id as usize)
317                        {
318                            if let Some(screen_arc) = self.screens.read().get(&display_id) {
319                                let scr = screen_arc.read();
320                                let damage = Damage {
321                                    x,
322                                    y,
323                                    width: w,
324                                    height: h,
325                                };
326                                adapter.update_plane(local_id, &*scr, damage);
327                            }
328                        }
329                    }
330                }
331                Ok(buf.len())
332            }
333            Handle::Clear => {
334                // Clear all offscreen buffers to black.
335                let screens = self.screens.read();
336                for (_, screen_arc) in screens.iter() {
337                    let mut scr = screen_arc.write();
338                    unsafe {
339                        core::ptr::write_bytes(
340                            scr.pixels_mut(),
341                            0,
342                            scr.stride() as usize * scr.height() as usize,
343                        );
344                    }
345                }
346                // Also clear the onscreen framebuffers.
347                let total = total_display_count();
348                for i in 0..total {
349                    if let Some((adapter, local_id)) = get_adapter_for_display(i) {
350                        let fb = adapter.create_framebuffer(
351                            adapter.display_size(local_id).0,
352                            adapter.display_size(local_id).1,
353                        );
354                        adapter.update_plane(local_id, &fb, Damage::full(fb.width(), fb.height()));
355                    }
356                }
357                Ok(buf.len())
358            }
359        }
360    }
361
362    fn close(&self, fid: u64) -> Result<(), SyscallError> {
363        self.handles.write().remove(&fid);
364        Ok(())
365    }
366
367    fn stat(&self, fid: u64) -> Result<FileStat, SyscallError> {
368        let h = self.handles.read();
369        let handle = h.get(&fid).ok_or(SyscallError::BadHandle)?;
370
371        Ok(match handle {
372            Handle::Root => finalize_pseudo_stat(
373                FileStat {
374                    st_ino: 0,
375                    st_mode: 0o040555,
376                    st_nlink: 2,
377                    st_size: 0,
378                    ..FileStat::zeroed()
379                },
380                DEV_CHAR_FS,
381                0,
382            ),
383            Handle::Info => finalize_pseudo_stat(
384                FileStat {
385                    st_ino: 1,
386                    st_mode: 0o0100444,
387                    st_nlink: 1,
388                    st_size: 256,
389                    ..FileStat::zeroed()
390                },
391                DEV_CHAR_FS,
392                1,
393            ),
394            Handle::Screen(did, _) => finalize_pseudo_stat(
395                FileStat {
396                    st_ino: 2 + *did as u64,
397                    st_mode: 0o0100666,
398                    st_nlink: 1,
399                    st_size: 0,
400                    ..FileStat::zeroed()
401                },
402                DEV_CHAR_FS,
403                2 + *did as u64,
404            ),
405            Handle::Damage => finalize_pseudo_stat(
406                FileStat {
407                    st_ino: 0x100,
408                    st_mode: 0o0100222,
409                    st_nlink: 1,
410                    st_size: 0,
411                    ..FileStat::zeroed()
412                },
413                DEV_CHAR_FS,
414                0x100,
415            ),
416            Handle::Clear => finalize_pseudo_stat(
417                FileStat {
418                    st_ino: 0x101,
419                    st_mode: 0o0100222,
420                    st_nlink: 1,
421                    st_size: 0,
422                    ..FileStat::zeroed()
423                },
424                DEV_CHAR_FS,
425                0x101,
426            ),
427        })
428    }
429
430    fn readdir(&self, fid: u64) -> Result<Vec<DirEntry>, SyscallError> {
431        let h = self.handles.read();
432        if !matches!(h.get(&fid), Some(Handle::Root)) {
433            return Err(SyscallError::InvalidArgument);
434        }
435
436        let total = total_display_count();
437        let mut entries = Vec::new();
438
439        entries.push(DirEntry {
440            ino: 1,
441            file_type: DT_REG,
442            name: String::from("info"),
443        });
444
445        for i in 0..total {
446            entries.push(DirEntry {
447                ino: 2 + i as u64,
448                file_type: DT_CHR,
449                name: format!("{}.0", i),
450            });
451        }
452
453        entries.push(DirEntry {
454            ino: 0x100,
455            file_type: DT_CHR,
456            name: String::from("damage"),
457        });
458        entries.push(DirEntry {
459            ino: 0x101,
460            file_type: DT_CHR,
461            name: String::from("clear"),
462        });
463
464        Ok(entries)
465    }
466}
467
468/// Register the display scheme at `/dev/display/`.
469pub fn register_display_scheme() -> Result<(), SyscallError> {
470    // Create and register the default adapter from the global framebuffer.
471    if let Some(adapter) = SimpleDisplayAdapter::from_framebuffer() {
472        graphics_adapter::register_adapter(Arc::new(adapter));
473    }
474
475    let scheme = Arc::new(DisplayScheme::new());
476    crate::vfs::scheme_router::register_scheme("display", scheme)?;
477    crate::vfs::scheme_router::mount_scheme("display", "/dev/display")?;
478    log::info!(
479        "[display] Scheme at /dev/display/ ({} display(s))",
480        total_display_count()
481    );
482    Ok(())
483}
484
485use super::graphics_adapter;