//! /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/zero reads as endless zero bytes, 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/console the console terminal (keyboard + screen) //! /dev/hvc0 the virtio console: a second terminal //! /dev/tty your controlling terminal, whichever it is (tty.rs) //! /dev/ptmx opening it creates a pseudo-terminal: you get the //! /dev/pts/N master end, and its slave end appears as /dev/pts/N //! /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. //! //! Stage 19: every node has a fixed identity, like on Linux. Its inode //! number is the hash of its path (the same every time you look), and its //! *device number* (`st_rdev`, "major:minor") says which driver it is: //! 1:3 is null, 5:1 the console, 136:N the pty /dev/pts/N (`DEVICES`). //! An open device file fstat()s as its node (syscall.rs `fd_stat`), which //! is how `ttyname()` and the `tty` command find a terminal's name. 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::tty; use crate::virtio::{console, gpu, rng}; use alloc::{string::String, sync::Arc, vec::Vec}; use spin::Mutex; pub struct DevFs; impl FileSystem for DevFs { fn name(&self) -> &'static str { "devfs" } fn root(&self) -> Arc { Arc::new(DevDir) } } /// Every device node: (name, major, minor, permissions) -- the numbers and /// modes Linux gives the same devices. const DEVICES: &[(&str, u64, u64, u32)] = &[ ("null", 1, 3, 0o666), ("zero", 1, 5, 0o666), ("random", 1, 8, 0o666), ("urandom", 1, 9, 0o666), ("tty", 5, 0, 0o666), ("console", 5, 1, 0o600), ("ptmx", 5, 2, 0o666), ("hvc0", 229, 0, 0o600), ("fb0", 29, 0, 0o660), ]; /// The major number of /dev/pts/N (whose minor is N). const PTS_MAJOR: u64 = 136; /// The devices that exist on this machine. fn names() -> Vec<&'static str> { let mut names = alloc::vec!["console", "null", "ptmx", "pts", "random", "tty", "urandom", "zero"]; 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)?; if name == "pts" { return Ok(Arc::new(PtsDir)); } Ok(Arc::new(DevNode(name))) } fn list(&self) -> Result, Errno> { let kind = |n| if n == "pts" { InodeKind::Directory } else { InodeKind::Device }; Ok(names().into_iter().map(|n| DirEntry { name: String::from(n), kind: kind(n), ino: 0 }).collect()) } } /// /dev/pts: one entry per pseudo-terminal (named by number). struct PtsDir; impl Inode for PtsDir { fn kind(&self) -> InodeKind { InodeKind::Directory } fn size(&self) -> usize { tty::pty_count() } fn lookup(&self, name: &str) -> Result, Errno> { let n: usize = name.parse().map_err(|_| Errno::ENOENT)?; if !tty::pty_numbers().contains(&n) { return Err(Errno::ENOENT); } Ok(Arc::new(PtsNode(n))) } fn list(&self) -> Result, Errno> { Ok(tty::pty_numbers().into_iter().map(|n| DirEntry { name: alloc::format!("{}", n), kind: InodeKind::Device, ino: 0 }).collect()) } } struct PtsNode(usize); impl Inode for PtsNode { fn kind(&self) -> InodeKind { InodeKind::Device } fn size(&self) -> usize { 0 } fn mode(&self) -> u32 { 0o620 } fn rdev(&self) -> u64 { crate::fs::file::makedev(PTS_MAJOR, self.0 as u64) } fn open_device(&self) -> Result>, Errno> { tty::open_pts(self.0).map(Some) } } /// 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 { DEVICES.iter().find(|d| d.0 == self.0).map_or(0o666, |d| d.3) } fn rdev(&self) -> u64 { DEVICES.iter().find(|d| d.0 == self.0).map_or(0, |d| crate::fs::file::makedev(d.1, d.2)) } fn open_device(&self) -> Result>, Errno> { Ok(Some(match self.0 { "null" => Arc::new(Null), "zero" => Arc::new(Zero), "random" | "urandom" => Arc::new(Random(self.0)), "console" => tty::console_file(), "hvc0" => tty::open_hvc(), "tty" => tty::open_ctty()?, "ptmx" => tty::open_ptmx()?, "fb0" => Arc::new(Framebuffer::new(gpu::framebuffer().ok_or(Errno::ENODEV)?)), _ => return Ok(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) } fn proc_link(&self) -> Option { Some(String::from("/dev/null")) } } /// /dev/zero: as many zero bytes as you ask for. struct Zero; impl File for Zero { fn read(&self, buf: &mut [u8]) -> Result { buf.fill(0); Ok(buf.len()) } fn write(&self, buf: &[u8]) -> Result { Ok(buf.len()) } fn stat(&self) -> Result { device_stat(0) } fn proc_link(&self) -> Option { Some(String::from("/dev/zero")) } } /// /dev/random or /dev/urandom (the name: which one was opened). struct Random(&'static str); 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) } fn proc_link(&self) -> Option { Some(alloc::format!("/dev/{}", self.0)) } } /// /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 proc_link(&self) -> Option { Some(String::from("/dev/fb0")) } 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 } }