//! /dev: device files. (Unix's big idea: devices look like files, so `cat`, //! `>` and every program that reads or writes files can use them.) //! //! /dev/null reads as empty, swallows writes //! /dev/random random bytes (virtio-rng; /dev/urandom is the same -- //! /dev/urandom on modern Linux they only differ before boot-time seeding) //! /dev/hvc0 the virtio console: a second terminal //! /dev/fb0 the virtio-gpu framebuffer: pixels at byte offsets, //! plus ioctls (see syscall.rs) for its size, flushing, //! filling rectangles and moving the text console //! //! A tiny file system (`DevFs`) mounted at /dev lists them; opening one //! gives a device-specific `File` (via `Inode::open_device`) instead of the //! usual read-the-inode file. Linux does the same with "character device" //! inodes whose `open` is routed to a driver. use crate::errno::Errno; use crate::fs::file::{File, KIND_DEVICE, SEEK_CUR, SEEK_END, SEEK_SET, Stat}; use crate::fs::{DirEntry, FileSystem, Inode, InodeKind}; use crate::virtio::{console, gpu, rng}; use alloc::{string::String, sync::Arc, vec::Vec}; use core::sync::atomic::Ordering; use spin::Mutex; pub struct DevFs; impl FileSystem for DevFs { fn name(&self) -> &'static str { "devfs" } fn root(&self) -> Arc { Arc::new(DevDir) } } /// The devices that exist on this machine. fn names() -> Vec<&'static str> { let mut names = alloc::vec!["null", "random", "urandom"]; if console::present() { names.push("hvc0"); } if gpu::framebuffer().is_some() { names.push("fb0"); } names.sort(); names } struct DevDir; impl Inode for DevDir { fn kind(&self) -> InodeKind { InodeKind::Directory } fn size(&self) -> usize { names().len() } fn lookup(&self, name: &str) -> Result, Errno> { let name = names().into_iter().find(|n| *n == name).ok_or(Errno::ENOENT)?; Ok(Arc::new(DevNode(name))) } fn list(&self) -> Result, Errno> { Ok(names().into_iter().map(|n| DirEntry { name: String::from(n), kind: InodeKind::Device }).collect()) } } /// One device's directory entry. struct DevNode(&'static str); impl Inode for DevNode { fn kind(&self) -> InodeKind { InodeKind::Device } fn size(&self) -> usize { match self.0 { "fb0" => gpu::framebuffer().map_or(0, |f| f.size_bytes()), _ => 0, } } fn mode(&self) -> u32 { 0o666 } fn open_device(&self) -> Option> { Some(match self.0 { "null" => Arc::new(Null), "random" | "urandom" => Arc::new(Random), "hvc0" => { console::OPEN_COUNT.fetch_add(1, Ordering::Relaxed); Arc::new(Hvc) } "fb0" => Arc::new(Framebuffer::new(gpu::framebuffer()?)), _ => return None, }) } } fn device_stat(size: usize) -> Result { Ok(Stat { kind: KIND_DEVICE, size: size as u64, mode: 0o666 }) } struct Null; impl File for Null { fn read(&self, _buf: &mut [u8]) -> Result { Ok(0) } fn write(&self, buf: &[u8]) -> Result { Ok(buf.len()) } fn stat(&self) -> Result { device_stat(0) } } struct Random; impl File for Random { fn read(&self, buf: &mut [u8]) -> Result { let n = buf.len().min(65536); rng::fill(&mut buf[..n]); Ok(n) } /// Linux mixes written bytes into its pool; we have no pool, so we /// just accept them. fn write(&self, buf: &[u8]) -> Result { Ok(buf.len()) } fn stat(&self) -> Result { device_stat(0) } } struct Hvc; impl File for Hvc { fn read(&self, buf: &mut [u8]) -> Result { console::read(buf) } fn write(&self, buf: &[u8]) -> Result { console::write(buf); Ok(buf.len()) // what didn't fit is dropped, like a serial line } fn stat(&self) -> Result { device_stat(0) } } impl Drop for Hvc { fn drop(&mut self) { console::OPEN_COUNT.fetch_sub(1, Ordering::Relaxed); } } /// /dev/fb0: the framebuffer as a file of `stride * height * 4` bytes. /// Writing changes pixels in memory; they reach the screen on the FLUSH /// ioctl or when the file is closed (we remember which rows changed). pub struct Framebuffer { pub info: gpu::FbInfo, offset: Mutex, dirty: Mutex>, // pixel rows [first, last) } impl Framebuffer { fn new(info: gpu::FbInfo) -> Framebuffer { Framebuffer { info, offset: Mutex::new(0), dirty: Mutex::new(None) } } fn bytes(&self) -> &'static mut [u8] { unsafe { core::slice::from_raw_parts_mut(self.info.base as *mut u8, self.info.size_bytes()) } } /// Remember that pixel rows [y0, y1) changed. pub fn mark_dirty(&self, y0: u32, y1: u32) { let mut dirty = self.dirty.lock(); *dirty = Some(match *dirty { Some((a, b)) => (a.min(y0), b.max(y1)), None => (y0, y1), }); } /// Send the changed rows to the screen. pub fn flush(&self) -> bool { match self.dirty.lock().take() { Some((y0, y1)) => gpu::flush(0, y0, self.info.width, y1 - y0), None => true, } } /// Fill a rectangle with one colour (clipped to the screen). pub fn fill(&self, x: u32, y: u32, w: u32, h: u32, color: u32) { let (x0, y0) = (x.min(self.info.width), y.min(self.info.height)); let (x1, y1) = (x.saturating_add(w).min(self.info.width), y.saturating_add(h).min(self.info.height)); for row in y0..y1 { let start = (row * self.info.stride) as usize; let line = unsafe { core::slice::from_raw_parts_mut(self.info.pixels().add(start), self.info.width as usize) }; line[x0 as usize..x1 as usize].fill(color); } if y1 > y0 { self.mark_dirty(y0, y1); } } } impl File for Framebuffer { fn read(&self, buf: &mut [u8]) -> Result { let mut offset = self.offset.lock(); let src = self.bytes(); let n = buf.len().min(src.len().saturating_sub(*offset)); buf[..n].copy_from_slice(&src[*offset..*offset + n]); *offset += n; Ok(n) } fn write(&self, buf: &[u8]) -> Result { let mut offset = self.offset.lock(); let dst = self.bytes(); if *offset >= dst.len() && !buf.is_empty() { return Err(Errno::ENOSPC); // past the end of the screen } let n = buf.len().min(dst.len() - *offset); dst[*offset..*offset + n].copy_from_slice(&buf[..n]); let row_bytes = self.info.stride as usize * 4; if n > 0 { self.mark_dirty((*offset / row_bytes) as u32, ((*offset + n - 1) / row_bytes + 1) as u32); } *offset += n; Ok(n) } fn seek(&self, offset: i64, whence: u64) -> Result { let mut pos = self.offset.lock(); let base = match whence { SEEK_SET => 0, SEEK_CUR => *pos as i64, SEEK_END => self.info.size_bytes() as i64, _ => return Err(Errno::EINVAL), }; let new = base.checked_add(offset).filter(|&p| p >= 0).ok_or(Errno::EINVAL)?; *pos = new as usize; Ok(new as u64) } fn stat(&self) -> Result { device_stat(self.info.size_bytes()) } fn as_framebuffer(&self) -> Option<&Framebuffer> { Some(self) } } impl Drop for Framebuffer { fn drop(&mut self) { self.flush(); // like close() on Linux's deferred-io framebuffers } }