|
| 1 | +//! Device Filesystem (devfs) |
| 2 | +//! |
| 3 | +//! Provides a virtual filesystem mounted at /dev containing device nodes. |
| 4 | +//! Unlike ext2, devfs doesn't use disk storage - all nodes are virtual. |
| 5 | +//! |
| 6 | +//! # Supported Devices |
| 7 | +//! |
| 8 | +//! - `/dev/null` - Discards all writes, reads return EOF |
| 9 | +//! - `/dev/zero` - Discards all writes, reads return zero bytes |
| 10 | +//! - `/dev/console` - System console (serial output) |
| 11 | +//! - `/dev/tty` - Current process's controlling terminal |
| 12 | +//! |
| 13 | +//! # Architecture |
| 14 | +//! |
| 15 | +//! ```text |
| 16 | +//! sys_open("/dev/null") |
| 17 | +//! | |
| 18 | +//! v |
| 19 | +//! devfs_open() |
| 20 | +//! | |
| 21 | +//! v |
| 22 | +//! DeviceFile { device_type: DevNull } |
| 23 | +//! | |
| 24 | +//! v |
| 25 | +//! sys_read/sys_write dispatch to device handlers |
| 26 | +//! ``` |
| 27 | +
|
| 28 | +use alloc::string::String; |
| 29 | +use alloc::vec::Vec; |
| 30 | +use spin::Mutex; |
| 31 | + |
| 32 | +/// Device types supported by devfs |
| 33 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 34 | +pub enum DeviceType { |
| 35 | + /// /dev/null - discards writes, reads return EOF |
| 36 | + Null, |
| 37 | + /// /dev/zero - discards writes, reads return zero bytes |
| 38 | + Zero, |
| 39 | + /// /dev/console - system console (serial port) |
| 40 | + Console, |
| 41 | + /// /dev/tty - controlling terminal |
| 42 | + Tty, |
| 43 | +} |
| 44 | + |
| 45 | +impl DeviceType { |
| 46 | + /// Get the device name (without /dev/ prefix) |
| 47 | + pub fn name(&self) -> &'static str { |
| 48 | + match self { |
| 49 | + DeviceType::Null => "null", |
| 50 | + DeviceType::Zero => "zero", |
| 51 | + DeviceType::Console => "console", |
| 52 | + DeviceType::Tty => "tty", |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + /// Get the inode number for this device |
| 57 | + /// Using fixed inode numbers for devices |
| 58 | + pub fn inode(&self) -> u64 { |
| 59 | + match self { |
| 60 | + DeviceType::Null => 1, |
| 61 | + DeviceType::Zero => 2, |
| 62 | + DeviceType::Console => 3, |
| 63 | + DeviceType::Tty => 4, |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + /// Check if this device is readable |
| 68 | + pub fn is_readable(&self) -> bool { |
| 69 | + true // All devices are readable |
| 70 | + } |
| 71 | + |
| 72 | + /// Check if this device is writable |
| 73 | + pub fn is_writable(&self) -> bool { |
| 74 | + true // All devices are writable |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +/// A devfs device node |
| 79 | +#[derive(Debug, Clone)] |
| 80 | +pub struct DeviceNode { |
| 81 | + /// Device type |
| 82 | + pub device_type: DeviceType, |
| 83 | + /// Device major number (for st_rdev) |
| 84 | + pub major: u32, |
| 85 | + /// Device minor number (for st_rdev) |
| 86 | + pub minor: u32, |
| 87 | +} |
| 88 | + |
| 89 | +impl DeviceNode { |
| 90 | + /// Create a new device node |
| 91 | + pub fn new(device_type: DeviceType, major: u32, minor: u32) -> Self { |
| 92 | + Self { |
| 93 | + device_type, |
| 94 | + major, |
| 95 | + minor, |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + /// Get the combined device number (major << 8 | minor) |
| 100 | + pub fn rdev(&self) -> u64 { |
| 101 | + ((self.major as u64) << 8) | (self.minor as u64) |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +/// Global devfs state |
| 106 | +struct DevfsState { |
| 107 | + /// Registered devices |
| 108 | + devices: Vec<DeviceNode>, |
| 109 | + /// Whether devfs is initialized |
| 110 | + initialized: bool, |
| 111 | +} |
| 112 | + |
| 113 | +impl DevfsState { |
| 114 | + const fn new() -> Self { |
| 115 | + Self { |
| 116 | + devices: Vec::new(), |
| 117 | + initialized: false, |
| 118 | + } |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +static DEVFS: Mutex<DevfsState> = Mutex::new(DevfsState::new()); |
| 123 | + |
| 124 | +/// Initialize devfs with standard devices |
| 125 | +pub fn init() { |
| 126 | + let mut devfs = DEVFS.lock(); |
| 127 | + if devfs.initialized { |
| 128 | + log::warn!("devfs already initialized"); |
| 129 | + return; |
| 130 | + } |
| 131 | + |
| 132 | + // Register standard devices |
| 133 | + // Major 1 = memory devices (null, zero, etc.) |
| 134 | + devfs.devices.push(DeviceNode::new(DeviceType::Null, 1, 3)); // /dev/null |
| 135 | + devfs.devices.push(DeviceNode::new(DeviceType::Zero, 1, 5)); // /dev/zero |
| 136 | + |
| 137 | + // Major 5 = TTY devices |
| 138 | + devfs.devices.push(DeviceNode::new(DeviceType::Console, 5, 1)); // /dev/console |
| 139 | + devfs.devices.push(DeviceNode::new(DeviceType::Tty, 5, 0)); // /dev/tty |
| 140 | + |
| 141 | + devfs.initialized = true; |
| 142 | + log::info!("devfs: initialized with {} devices", devfs.devices.len()); |
| 143 | + |
| 144 | + // Register mount point |
| 145 | + crate::fs::vfs::mount::mount("/dev", "devfs"); |
| 146 | +} |
| 147 | + |
| 148 | +/// Look up a device by path (without /dev/ prefix) |
| 149 | +pub fn lookup(name: &str) -> Option<DeviceNode> { |
| 150 | + let devfs = DEVFS.lock(); |
| 151 | + for device in &devfs.devices { |
| 152 | + if device.device_type.name() == name { |
| 153 | + return Some(device.clone()); |
| 154 | + } |
| 155 | + } |
| 156 | + None |
| 157 | +} |
| 158 | + |
| 159 | +/// Look up a device by inode number |
| 160 | +pub fn lookup_by_inode(inode: u64) -> Option<DeviceNode> { |
| 161 | + let devfs = DEVFS.lock(); |
| 162 | + for device in &devfs.devices { |
| 163 | + if device.device_type.inode() == inode { |
| 164 | + return Some(device.clone()); |
| 165 | + } |
| 166 | + } |
| 167 | + None |
| 168 | +} |
| 169 | + |
| 170 | +/// List all device names (for /dev directory listing) |
| 171 | +pub fn list_devices() -> Vec<String> { |
| 172 | + let devfs = DEVFS.lock(); |
| 173 | + devfs.devices.iter().map(|d| String::from(d.device_type.name())).collect() |
| 174 | +} |
| 175 | + |
| 176 | +/// Check if devfs is initialized |
| 177 | +pub fn is_initialized() -> bool { |
| 178 | + DEVFS.lock().initialized |
| 179 | +} |
| 180 | + |
| 181 | +/// Device file operations - read from device |
| 182 | +pub fn device_read(device_type: DeviceType, buf: &mut [u8]) -> Result<usize, i32> { |
| 183 | + match device_type { |
| 184 | + DeviceType::Null => { |
| 185 | + // /dev/null always returns EOF (0 bytes read) |
| 186 | + Ok(0) |
| 187 | + } |
| 188 | + DeviceType::Zero => { |
| 189 | + // /dev/zero fills buffer with zeros |
| 190 | + for byte in buf.iter_mut() { |
| 191 | + *byte = 0; |
| 192 | + } |
| 193 | + Ok(buf.len()) |
| 194 | + } |
| 195 | + DeviceType::Console | DeviceType::Tty => { |
| 196 | + // Console/TTY read - for now return EAGAIN (no input available) |
| 197 | + // In the future, this would read from keyboard buffer |
| 198 | + Err(-11) // EAGAIN |
| 199 | + } |
| 200 | + } |
| 201 | +} |
| 202 | + |
| 203 | +/// Device file operations - write to device |
| 204 | +pub fn device_write(device_type: DeviceType, buf: &[u8]) -> Result<usize, i32> { |
| 205 | + match device_type { |
| 206 | + DeviceType::Null | DeviceType::Zero => { |
| 207 | + // /dev/null and /dev/zero discard all writes |
| 208 | + Ok(buf.len()) |
| 209 | + } |
| 210 | + DeviceType::Console | DeviceType::Tty => { |
| 211 | + // Console/TTY write - output to serial port |
| 212 | + for &byte in buf { |
| 213 | + crate::serial::write_byte(byte); |
| 214 | + } |
| 215 | + Ok(buf.len()) |
| 216 | + } |
| 217 | + } |
| 218 | +} |
0 commit comments