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, HeapScreen,
28            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};
39use core::sync::atomic::{AtomicU64, Ordering};
40use spin::{Once, RwLock};
41
42const MAX_HANDLES: usize = 64;
43
44/// SIMD pixel-ingest ops for the Screen write path (RGB888 wire format ->
45/// screen format). Detected once on first use; falls back to scalar
46/// implementations when no SIMD extension is available.
47fn fb_ops() -> crate::framebuffer::FramebufferOps {
48    static OPS: Once<crate::framebuffer::FramebufferOps> = Once::new();
49    *OPS.call_once(crate::framebuffer::FramebufferOps::detect)
50}
51
52#[derive(Clone, Debug)]
53enum Handle {
54    Root,
55    Info,
56    /// Per-display screen: (global_display_id, offscreen buffer).
57    Screen(u32, Arc<RwLock<HeapScreen>>),
58    Damage,
59    Clear,
60}
61
62pub struct DisplayScheme {
63    handles: RwLock<BTreeMap<u64, Handle>>,
64    next: AtomicU64,
65    /// Offscreen buffers keyed by display_id.
66    screens: RwLock<BTreeMap<u32, Arc<RwLock<HeapScreen>>>>,
67}
68
69impl DisplayScheme {
70    fn new() -> Self {
71        Self {
72            handles: RwLock::new(BTreeMap::new()),
73            next: AtomicU64::new(1),
74            screens: RwLock::new(BTreeMap::new()),
75        }
76    }
77
78    fn alloc_id(&self) -> u64 {
79        self.next.fetch_add(1, Ordering::Relaxed)
80    }
81
82    /// Get or create the offscreen buffer for a display.
83    fn get_or_create_screen(&self, display_id: u32) -> Arc<RwLock<HeapScreen>> {
84        let screens = self.screens.read();
85        if let Some(screen) = screens.get(&display_id) {
86            return screen.clone();
87        }
88        drop(screens);
89
90        // Create new screen for this display.
91        let (w, h) = get_adapter_for_display(display_id as usize)
92            .map(|(a, local_id)| a.display_size(local_id))
93            .unwrap_or((800, 600));
94
95        let bpp = Framebuffer::info()
96            .map(|i| i.format.bits_per_pixel)
97            .unwrap_or(32);
98
99        let screen = Arc::new(RwLock::new(HeapScreen::new(w, h, bpp)));
100        self.screens.write().insert(display_id, screen.clone());
101        screen
102    }
103
104    /// Parse "display_id.screen_id" path to global display_id.
105    fn parse_display_path(path: &str) -> Option<u32> {
106        let parts: Vec<&str> = path.split('.').collect();
107        if parts.len() != 2 {
108            return None;
109        }
110        let display_id: u32 = parts[0].parse().ok()?;
111        let _screen_id: u32 = parts[1].parse().ok()?;
112        // For now, screen_id is always 0 (single screen per display).
113        Some(display_id)
114    }
115}
116
117impl Scheme for DisplayScheme {
118    fn open(&self, path: &str, _flags: OpenFlags) -> Result<OpenResult, SyscallError> {
119        let path = path.trim_start_matches('/');
120        let id = self.alloc_id();
121
122        if self.handles.read().len() >= MAX_HANDLES {
123            return Err(SyscallError::OutOfMemory);
124        }
125
126        let handle = if path.is_empty() {
127            Handle::Root
128        } else if path == "info" {
129            Handle::Info
130        } else if path == "damage" {
131            Handle::Damage
132        } else if path == "clear" {
133            Handle::Clear
134        } else if let Some(display_id) = Self::parse_display_path(path) {
135            let screen = self.get_or_create_screen(display_id);
136            Handle::Screen(display_id, screen)
137        } else {
138            return Err(SyscallError::NotFound);
139        };
140
141        self.handles.write().insert(id, handle.clone());
142
143        let flags = match &handle {
144            Handle::Root => FileFlags::DIRECTORY,
145            Handle::Info => FileFlags::DEVICE,
146            Handle::Screen(..) => FileFlags::DEVICE | FileFlags::CHUNK_WRITE,
147            Handle::Damage | Handle::Clear => FileFlags::DEVICE | FileFlags::CHUNK_WRITE,
148        };
149
150        Ok(OpenResult {
151            file_id: id,
152            size: None,
153            flags,
154        })
155    }
156
157    fn read(&self, fid: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
158        let h = self.handles.read();
159        let handle = h.get(&fid).ok_or(SyscallError::BadHandle)?;
160
161        match handle {
162            Handle::Root => {
163                let total = total_display_count();
164                let mut out = format!("displays={}\n", total);
165                for i in 0..total {
166                    if let Some((adapter, local_id)) = get_adapter_for_display(i) {
167                        let (w, h) = adapter.display_size(local_id);
168                        out.push_str(&format!("{}.0={}x{}\n", i, w, h));
169                    }
170                }
171                out.push_str("damage\n");
172                out.push_str("clear\n");
173                let b = out.as_bytes();
174                let start = offset as usize;
175                if start >= b.len() {
176                    return Ok(0);
177                }
178                let n = core::cmp::min(b.len() - start, buf.len());
179                buf[..n].copy_from_slice(&b[start..start + n]);
180                Ok(n)
181            }
182            Handle::Info => {
183                let total = total_display_count();
184                let mut s = format!("adapter_displays={}\n", total);
185                for i in 0..total {
186                    if let Some((adapter, local_id)) = get_adapter_for_display(i) {
187                        let (w, h) = adapter.display_size(local_id);
188                        s.push_str(&format!("display.{}.{}x{}\n", i, w, h));
189                    }
190                }
191                let b = s.as_bytes();
192                let start = offset as usize;
193                if start >= b.len() {
194                    return Ok(0);
195                }
196                let n = core::cmp::min(b.len() - start, buf.len());
197                buf[..n].copy_from_slice(&b[start..start + n]);
198                Ok(n)
199            }
200            Handle::Screen(_display_id, screen) => {
201                // Read pixel data from offscreen buffer.
202                let scr = screen.read();
203                let scr_stride = scr.stride() as usize;
204                let start = offset as usize;
205                let total = scr_stride * scr.height() as usize;
206                if start >= total {
207                    return Ok(0);
208                }
209                let n = core::cmp::min(total - start, buf.len());
210                unsafe {
211                    core::ptr::copy_nonoverlapping(scr.pixels().add(start), buf.as_mut_ptr(), n);
212                }
213                Ok(n)
214            }
215            Handle::Damage | Handle::Clear => Err(SyscallError::PermissionDenied),
216        }
217    }
218
219    fn write(&self, fid: u64, _offset: u64, buf: &[u8]) -> Result<usize, SyscallError> {
220        let h = self.handles.read();
221        let handle = h.get(&fid).ok_or(SyscallError::BadHandle)?;
222
223        match handle {
224            Handle::Root | Handle::Info => Err(SyscallError::PermissionDenied),
225            Handle::Screen(_display_id, screen) => {
226                // Write pixel data to offscreen buffer.
227                //
228                // Wire format v2:
229                //   [x u16le][y u16le][w u16le][r,g,b × w per row, ...]
230                // - `w == 0` means "fill to the right edge" (screen_w - x).
231                // - Rows advance by exactly w pixels per row while pixel
232                //   data remains; a single write can carry h*w pixels.
233                // - Pixels are RGB888 and are converted to the screen
234                //   format on ingest (SIMD-accelerated for 32bpp screens).
235                //
236                // Note: the file offset is currently ignored; callers must
237                // send complete rectangles starting at their (x, y).
238                if buf.len() < 7 {
239                    return Err(SyscallError::InvalidArgument);
240                }
241                let x = u16::from_le_bytes([buf[0], buf[1]]) as usize;
242                let y = u16::from_le_bytes([buf[2], buf[3]]) as usize;
243                let req_w = u16::from_le_bytes([buf[4], buf[5]]) as usize;
244                let pixels = &buf[6..];
245
246                let mut scr = screen.write();
247                let bpp = scr.bpp() as usize;
248                let bpx = bpp / 8;
249                let screen_w = scr.width() as usize;
250                let screen_h = scr.height() as usize;
251                let stride = scr.stride() as usize;
252                let dst = scr.pixels_mut();
253
254                if x >= screen_w || y >= screen_h {
255                    return Err(SyscallError::InvalidArgument);
256                }
257                // Clip the requested width to the right edge (w == 0 => edge).
258                let w = if req_w == 0 {
259                    screen_w - x
260                } else {
261                    req_w.min(screen_w - x)
262                };
263                if w == 0 || pixels.is_empty() {
264                    return Ok(buf.len());
265                }
266
267                // Per-row ingest. Each row consumes exactly w*bpp/8 target
268                // pixels from the stream; the stream ends when exhausted or
269                // when the bottom of the screen is reached.
270                let src_row_bytes = w * 3; // wire format is always RGB888
271                let mut off = 0usize;
272                let mut py = y;
273                while py < screen_h && off + src_row_bytes <= pixels.len() {
274                    let dst_off = py * stride + x * bpx;
275                    unsafe {
276                        if bpp == 32 {
277                            // SIMD RGB888 -> XRGB8888 (pshufb-based, handles
278                            // unaligned edges with a scalar tail). Plain
279                            // stores: the HeapScreen lives in cached RAM,
280                            // volatile would only block vectorization.
281                            (fb_ops().convert)(
282                                dst.add(dst_off) as *mut u32,
283                                pixels.as_ptr().add(off),
284                                w,
285                            );
286                        } else if bpp == 24 {
287                            let ptr = dst.add(dst_off);
288                            for i in 0..src_row_bytes {
289                                *ptr.add(i) = pixels[off + i];
290                            }
291                        } else {
292                            // Unsupported screen bpp: drop silently.
293                            return Ok(buf.len());
294                        }
295                    }
296                    off += src_row_bytes;
297                    py += 1;
298                }
299                Ok(buf.len())
300            }
301            Handle::Damage => {
302                // Parse: "present" or "display_id,x,y,w,h" or just "x,y,w,h" for display 0.
303                let s = core::str::from_utf8(buf).unwrap_or("").trim();
304                if s == "present" || s.is_empty() {
305                    // Present all displays.
306                    let total = total_display_count();
307                    for i in 0..total {
308                        if let Some((adapter, local_id)) = get_adapter_for_display(i) {
309                            if let Some(screen_arc) = self.screens.read().get(&(i as u32)) {
310                                let scr = screen_arc.read();
311                                let damage = Damage::full(scr.width(), scr.height());
312                                adapter.update_plane(local_id, &*scr, damage);
313                            }
314                        }
315                    }
316                } else {
317                    let parts: Vec<&str> = s.split(',').collect();
318                    let (display_id, x, y, w, h) = if parts.len() == 5 {
319                        let did: u32 = parts[0].parse().unwrap_or(0);
320                        (
321                            did,
322                            parts[1].parse().unwrap_or(0),
323                            parts[2].parse().unwrap_or(0),
324                            parts[3].parse().unwrap_or(0),
325                            parts[4].parse().unwrap_or(0),
326                        )
327                    } else if parts.len() == 4 {
328                        (
329                            0u32,
330                            parts[0].parse().unwrap_or(0),
331                            parts[1].parse().unwrap_or(0),
332                            parts[2].parse().unwrap_or(0),
333                            parts[3].parse().unwrap_or(0),
334                        )
335                    } else {
336                        return Err(SyscallError::InvalidArgument);
337                    };
338                    if w > 0 && h > 0 {
339                        if let Some((adapter, local_id)) =
340                            get_adapter_for_display(display_id as usize)
341                        {
342                            if let Some(screen_arc) = self.screens.read().get(&display_id) {
343                                let scr = screen_arc.read();
344                                let damage = Damage {
345                                    x,
346                                    y,
347                                    width: w,
348                                    height: h,
349                                };
350                                adapter.update_plane(local_id, &*scr, damage);
351                            }
352                        }
353                    }
354                }
355                Ok(buf.len())
356            }
357            Handle::Clear => {
358                // Clear all offscreen buffers to black.
359                let screens = self.screens.read();
360                for (_, screen_arc) in screens.iter() {
361                    let mut scr = screen_arc.write();
362                    unsafe {
363                        core::ptr::write_bytes(
364                            scr.pixels_mut(),
365                            0,
366                            scr.stride() as usize * scr.height() as usize,
367                        );
368                    }
369                }
370                // Also clear the onscreen framebuffers.
371                let total = total_display_count();
372                for i in 0..total {
373                    if let Some((adapter, local_id)) = get_adapter_for_display(i) {
374                        let fb = adapter.create_framebuffer(
375                            adapter.display_size(local_id).0,
376                            adapter.display_size(local_id).1,
377                        );
378                        adapter.update_plane(local_id, &fb, Damage::full(fb.width(), fb.height()));
379                    }
380                }
381                Ok(buf.len())
382            }
383        }
384    }
385
386    fn close(&self, fid: u64) -> Result<(), SyscallError> {
387        self.handles.write().remove(&fid);
388        Ok(())
389    }
390
391    fn stat(&self, fid: u64) -> Result<FileStat, SyscallError> {
392        let h = self.handles.read();
393        let handle = h.get(&fid).ok_or(SyscallError::BadHandle)?;
394
395        Ok(match handle {
396            Handle::Root => finalize_pseudo_stat(
397                FileStat {
398                    st_ino: 0,
399                    st_mode: 0o040555,
400                    st_nlink: 2,
401                    st_size: 0,
402                    ..FileStat::zeroed()
403                },
404                DEV_CHAR_FS,
405                0,
406            ),
407            Handle::Info => finalize_pseudo_stat(
408                FileStat {
409                    st_ino: 1,
410                    st_mode: 0o0100444,
411                    st_nlink: 1,
412                    st_size: 256,
413                    ..FileStat::zeroed()
414                },
415                DEV_CHAR_FS,
416                1,
417            ),
418            Handle::Screen(did, _) => finalize_pseudo_stat(
419                FileStat {
420                    st_ino: 2 + *did as u64,
421                    st_mode: 0o0100666,
422                    st_nlink: 1,
423                    st_size: 0,
424                    ..FileStat::zeroed()
425                },
426                DEV_CHAR_FS,
427                2 + *did as u64,
428            ),
429            Handle::Damage => finalize_pseudo_stat(
430                FileStat {
431                    st_ino: 0x100,
432                    st_mode: 0o0100222,
433                    st_nlink: 1,
434                    st_size: 0,
435                    ..FileStat::zeroed()
436                },
437                DEV_CHAR_FS,
438                0x100,
439            ),
440            Handle::Clear => finalize_pseudo_stat(
441                FileStat {
442                    st_ino: 0x101,
443                    st_mode: 0o0100222,
444                    st_nlink: 1,
445                    st_size: 0,
446                    ..FileStat::zeroed()
447                },
448                DEV_CHAR_FS,
449                0x101,
450            ),
451        })
452    }
453
454    fn readdir(&self, fid: u64) -> Result<Vec<DirEntry>, SyscallError> {
455        let h = self.handles.read();
456        if !matches!(h.get(&fid), Some(Handle::Root)) {
457            return Err(SyscallError::InvalidArgument);
458        }
459
460        let total = total_display_count();
461        let mut entries = Vec::new();
462
463        entries.push(DirEntry {
464            ino: 1,
465            file_type: DT_REG,
466            name: String::from("info"),
467        });
468
469        for i in 0..total {
470            entries.push(DirEntry {
471                ino: 2 + i as u64,
472                file_type: DT_CHR,
473                name: format!("{}.0", i),
474            });
475        }
476
477        entries.push(DirEntry {
478            ino: 0x100,
479            file_type: DT_CHR,
480            name: String::from("damage"),
481        });
482        entries.push(DirEntry {
483            ino: 0x101,
484            file_type: DT_CHR,
485            name: String::from("clear"),
486        });
487
488        Ok(entries)
489    }
490}
491
492/// Register the display scheme at `/dev/display/`.
493pub fn register_display_scheme() -> Result<(), SyscallError> {
494    // Create and register the default adapter from the global framebuffer.
495    if let Some(adapter) = SimpleDisplayAdapter::from_framebuffer() {
496        graphics_adapter::register_adapter(Arc::new(adapter));
497    }
498
499    let scheme = Arc::new(DisplayScheme::new());
500    crate::vfs::scheme_router::register_scheme("display", scheme)?;
501    crate::vfs::scheme_router::mount_scheme("display", "/dev/display")?;
502    log::info!(
503        "[display] Scheme at /dev/display/ ({} display(s))",
504        total_display_count()
505    );
506    Ok(())
507}
508
509use super::graphics_adapter;