⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/fs/
devfs.rs

1//! Device Filesystem (/dev)
2//!
3//! Provides device nodes for hardware and virtual devices.
4
5use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec};
6
7#[cfg(not(target_arch = "aarch64"))]
8use spin::RwLock;
9
10#[cfg(target_arch = "aarch64")]
11use super::bare_lock::RwLock;
12use super::{DirEntry, Filesystem, Metadata, NodeType, Permissions, VfsNode};
13use crate::error::{FsError, KernelError};
14
15/// Device node
16struct DevNode {
17    name: String,
18    node_type: NodeType,
19    _major: u32,
20    _minor: u32,
21    permissions: Permissions,
22}
23
24impl DevNode {
25    fn new_char(name: String, major: u32, minor: u32) -> Self {
26        Self {
27            name,
28            node_type: NodeType::CharDevice,
29            _major: major,
30            _minor: minor,
31            permissions: Permissions::default(),
32        }
33    }
34
35    #[allow(dead_code)] // Block device creation API -- used when block devices are registered
36    fn new_block(name: String, major: u32, minor: u32) -> Self {
37        Self {
38            name,
39            node_type: NodeType::BlockDevice,
40            _major: major,
41            _minor: minor,
42            permissions: Permissions::default(),
43        }
44    }
45}
46
47impl VfsNode for DevNode {
48    fn node_type(&self) -> NodeType {
49        self.node_type
50    }
51
52    fn read(&self, _offset: usize, buffer: &mut [u8]) -> Result<usize, KernelError> {
53        // Special handling for common devices
54        match self.name.as_str() {
55            "null" => {
56                // /dev/null always returns EOF
57                Ok(0)
58            }
59            "zero" => {
60                // /dev/zero returns zeros
61                buffer.fill(0);
62                Ok(buffer.len())
63            }
64            "random" | "urandom" => {
65                // Simple pseudo-random for now
66                for byte in buffer.iter_mut() {
67                    *byte = (crate::read_timestamp() & 0xFF) as u8;
68                }
69                Ok(buffer.len())
70            }
71            "console" | "tty0" => {
72                // Read from serial console, respecting terminal state (ICANON, ECHO).
73                //
74                // Canonical mode (ICANON set, default): buffer until newline/EOF,
75                // support VERASE (backspace), return full line.
76                // Raw mode (ICANON cleared): return characters immediately per
77                // VMIN/VTIME settings.
78                #[cfg(target_arch = "x86_64")]
79                {
80                    use crate::drivers::terminal;
81
82                    let canonical = terminal::is_canonical_mode();
83                    let echo = terminal::is_echo_enabled();
84                    let verase = terminal::get_verase();
85                    let vmin = terminal::get_vmin() as usize;
86
87                    if canonical {
88                        // Canonical mode: line-buffered with editing support.
89                        // Buffer characters until newline, CR, or EOF (Ctrl-D).
90                        // Support backspace (VERASE) to erase last character.
91                        //
92                        // Block indefinitely waiting for input, just like a real
93                        // terminal device. POSIX requires read() on a terminal to
94                        // block until data is available. Without this, shells and
95                        // other interactive programs see EOF and exit immediately.
96                        let mut line_buf: [u8; 4096] = [0u8; 4096];
97                        let mut line_len: usize = 0;
98
99                        loop {
100                            let byte = loop {
101                                let lsr: u8;
102                                // SAFETY: Reading COM1 LSR (port 0x3FD).
103                                unsafe {
104                                    core::arch::asm!(
105                                        "in al, dx",
106                                        out("al") lsr,
107                                        in("dx") 0x3FDu16,
108                                        options(nomem, nostack)
109                                    );
110                                }
111                                if lsr & 1 != 0 {
112                                    let data: u8;
113                                    // SAFETY: Reading COM1 data register (port 0x3F8).
114                                    unsafe {
115                                        core::arch::asm!(
116                                            "in al, dx",
117                                            out("al") data,
118                                            in("dx") 0x3F8u16,
119                                            options(nomem, nostack)
120                                        );
121                                    }
122                                    break data;
123                                }
124                                core::hint::spin_loop();
125                            };
126
127                            // Handle special characters in canonical mode
128                            if byte == verase || byte == 8 {
129                                // Backspace: erase last character
130                                if line_len > 0 {
131                                    line_len -= 1;
132                                    if echo {
133                                        // Echo backspace-space-backspace
134                                        for &b in &[8u8, b' ', 8u8] {
135                                            // SAFETY: Writing COM1 data register.
136                                            unsafe {
137                                                while {
138                                                    let s: u8;
139                                                    core::arch::asm!(
140                                                        "in al, dx",
141                                                        out("al") s,
142                                                        in("dx") 0x3FDu16,
143                                                        options(nomem, nostack)
144                                                    );
145                                                    s & 0x20 == 0
146                                                } {
147                                                    core::hint::spin_loop();
148                                                }
149                                                core::arch::asm!(
150                                                    "out dx, al",
151                                                    in("al") b,
152                                                    in("dx") 0x3F8u16,
153                                                    options(nomem, nostack)
154                                                );
155                                            }
156                                        }
157                                    }
158                                }
159                                continue;
160                            }
161
162                            // CR -> NL conversion (c_iflag ICRNL)
163                            let byte = if byte == b'\r' { b'\n' } else { byte };
164
165                            // Store in line buffer
166                            if line_len < line_buf.len() {
167                                line_buf[line_len] = byte;
168                                line_len += 1;
169                            }
170
171                            // Echo the character
172                            if echo {
173                                let echo_byte = byte;
174                                // SAFETY: Writing COM1 data register.
175                                unsafe {
176                                    while {
177                                        let s: u8;
178                                        core::arch::asm!(
179                                            "in al, dx",
180                                            out("al") s,
181                                            in("dx") 0x3FDu16,
182                                            options(nomem, nostack)
183                                        );
184                                        s & 0x20 == 0
185                                    } {
186                                        core::hint::spin_loop();
187                                    }
188                                    core::arch::asm!(
189                                        "out dx, al",
190                                        in("al") echo_byte,
191                                        in("dx") 0x3F8u16,
192                                        options(nomem, nostack)
193                                    );
194                                    // Also echo CR after NL for terminal display
195                                    if echo_byte == b'\n' {
196                                        while {
197                                            let s: u8;
198                                            core::arch::asm!(
199                                                "in al, dx",
200                                                out("al") s,
201                                                in("dx") 0x3FDu16,
202                                                options(nomem, nostack)
203                                            );
204                                            s & 0x20 == 0
205                                        } {
206                                            core::hint::spin_loop();
207                                        }
208                                        core::arch::asm!(
209                                            "out dx, al",
210                                            in("al") b'\r',
211                                            in("dx") 0x3F8u16,
212                                            options(nomem, nostack)
213                                        );
214                                    }
215                                }
216                            }
217
218                            // Line complete on newline or EOF
219                            if byte == b'\n' || byte == 4 {
220                                // Ctrl-D (EOF)
221                                let copy_len = line_len.min(buffer.len());
222                                buffer[..copy_len].copy_from_slice(&line_buf[..copy_len]);
223                                return Ok(copy_len);
224                            }
225                        }
226                    } else {
227                        // Raw mode (non-canonical): return characters immediately.
228                        // VMIN controls minimum chars, VTIME controls timeout.
229                        let target = if vmin == 0 { 1 } else { vmin.min(buffer.len()) };
230                        let max_spins: u64 = if terminal::get_vtime() > 0 {
231                            // VTIME is in tenths of a second; approximate with spin count
232                            (terminal::get_vtime() as u64) * 10_000_000
233                        } else if vmin == 0 {
234                            // VMIN=0, VTIME=0: pure non-blocking
235                            1
236                        } else {
237                            // VMIN>0, VTIME=0: block indefinitely
238                            u64::MAX
239                        };
240
241                        let mut bytes_read = 0usize;
242                        let mut total_spins: u64 = 0;
243
244                        while bytes_read < target {
245                            let lsr: u8;
246                            // SAFETY: Reading COM1 LSR (port 0x3FD).
247                            unsafe {
248                                core::arch::asm!(
249                                    "in al, dx",
250                                    out("al") lsr,
251                                    in("dx") 0x3FDu16,
252                                    options(nomem, nostack)
253                                );
254                            }
255                            if lsr & 1 != 0 {
256                                let data: u8;
257                                // SAFETY: Reading COM1 data register (port 0x3F8).
258                                unsafe {
259                                    core::arch::asm!(
260                                        "in al, dx",
261                                        out("al") data,
262                                        in("dx") 0x3F8u16,
263                                        options(nomem, nostack)
264                                    );
265                                }
266                                buffer[bytes_read] = data;
267                                bytes_read += 1;
268
269                                // Echo if enabled (even in raw mode)
270                                if echo {
271                                    // SAFETY: Writing COM1 data register.
272                                    unsafe {
273                                        while {
274                                            let s: u8;
275                                            core::arch::asm!(
276                                                "in al, dx",
277                                                out("al") s,
278                                                in("dx") 0x3FDu16,
279                                                options(nomem, nostack)
280                                            );
281                                            s & 0x20 == 0
282                                        } {
283                                            core::hint::spin_loop();
284                                        }
285                                        core::arch::asm!(
286                                            "out dx, al",
287                                            in("al") data,
288                                            in("dx") 0x3F8u16,
289                                            options(nomem, nostack)
290                                        );
291                                    }
292                                }
293
294                                // Reset timeout after each character
295                                total_spins = 0;
296                            } else {
297                                total_spins += 1;
298                                if total_spins >= max_spins {
299                                    break;
300                                }
301                                core::hint::spin_loop();
302                            }
303                        }
304
305                        Ok(bytes_read)
306                    }
307                }
308                // Non-x86_64: no serial port polling available, return EOF
309                #[cfg(not(target_arch = "x86_64"))]
310                {
311                    let _ = buffer;
312                    Ok(0)
313                }
314            }
315            // evdev input device nodes: /dev/input/event*
316            "input/event0" | "input/event1" => {
317                let minor = self._minor;
318                let bytes_read = crate::drivers::evdev::read_device(minor, buffer);
319                Ok(bytes_read)
320            }
321            // DRI device nodes: /dev/dri/card0, /dev/dri/renderD128
322            "dri/card0" | "dri/renderD128" => {
323                // DRM devices are ioctl-driven; read() is not the primary interface.
324                // Return 0 (no data available for plain reads).
325                Ok(0)
326            }
327            _ => {
328                // Dispatch read to registered device driver via driver framework
329                if let Some(fw) = crate::services::driver_framework::try_get_driver_framework() {
330                    // Look up device by major/minor number
331                    let devices = fw.list_devices();
332                    for dev in &devices {
333                        if let Some(ref driver_name) = dev.driver {
334                            // Match device name to our device node name
335                            if dev.name == self.name {
336                                // Device has a bound driver -- attempt read via framework
337                                return fw.read_device(dev.id, _offset as u64, buffer).map_err(
338                                    |_| KernelError::OperationNotSupported {
339                                        operation: "device read failed",
340                                    },
341                                );
342                            }
343                            let _ = driver_name; // suppress unused warning
344                        }
345                    }
346                }
347                Err(KernelError::OperationNotSupported {
348                    operation: "read on unregistered device",
349                })
350            }
351        }
352    }
353
354    fn write(&self, _offset: usize, data: &[u8]) -> Result<usize, KernelError> {
355        match self.name.as_str() {
356            "null" => {
357                // /dev/null discards all data
358                Ok(data.len())
359            }
360            "console" | "tty0" => {
361                // Write to console
362                for &_byte in data {
363                    crate::print!("{}", _byte as char);
364                }
365                Ok(data.len())
366            }
367            _ => {
368                // Dispatch write to registered device driver via driver framework
369                if let Some(fw) = crate::services::driver_framework::try_get_driver_framework() {
370                    let devices = fw.list_devices();
371                    for dev in &devices {
372                        if let Some(ref driver_name) = dev.driver {
373                            if dev.name == self.name {
374                                return fw.write_device(dev.id, _offset as u64, data).map_err(
375                                    |_| KernelError::OperationNotSupported {
376                                        operation: "device write failed",
377                                    },
378                                );
379                            }
380                            let _ = driver_name;
381                        }
382                    }
383                }
384                Err(KernelError::OperationNotSupported {
385                    operation: "write on unregistered device",
386                })
387            }
388        }
389    }
390
391    fn metadata(&self) -> Result<Metadata, KernelError> {
392        Ok(Metadata {
393            node_type: self.node_type,
394            size: 0,
395            permissions: self.permissions,
396            uid: 0,
397            gid: 0,
398            created: 0,
399            modified: 0,
400            accessed: 0,
401            inode: 0,
402        })
403    }
404
405    fn readdir(&self) -> Result<Vec<DirEntry>, KernelError> {
406        Err(KernelError::FsError(FsError::NotADirectory))
407    }
408
409    fn lookup(&self, _name: &str) -> Result<Arc<dyn VfsNode>, KernelError> {
410        Err(KernelError::FsError(FsError::NotADirectory))
411    }
412
413    fn create(
414        &self,
415        _name: &str,
416        _permissions: Permissions,
417    ) -> Result<Arc<dyn VfsNode>, KernelError> {
418        Err(KernelError::OperationNotSupported {
419            operation: "create in device node",
420        })
421    }
422
423    fn mkdir(
424        &self,
425        _name: &str,
426        _permissions: Permissions,
427    ) -> Result<Arc<dyn VfsNode>, KernelError> {
428        Err(KernelError::OperationNotSupported {
429            operation: "mkdir in device node",
430        })
431    }
432
433    fn unlink(&self, _name: &str) -> Result<(), KernelError> {
434        Err(KernelError::OperationNotSupported {
435            operation: "unlink device node",
436        })
437    }
438
439    fn truncate(&self, _size: usize) -> Result<(), KernelError> {
440        Err(KernelError::OperationNotSupported {
441            operation: "truncate device node",
442        })
443    }
444}
445
446/// A device subdirectory (e.g., `/dev/dri/`, `/dev/input/`).
447///
448/// Contains its own set of device nodes, supports lookup and readdir.
449struct DevSubDir {
450    #[allow(dead_code)] // Retained for debugging/identification
451    name: String,
452    devices: RwLock<BTreeMap<String, Arc<DevNode>>>,
453}
454
455impl DevSubDir {
456    fn new(name: String) -> Self {
457        Self {
458            name,
459            devices: RwLock::new(BTreeMap::new()),
460        }
461    }
462
463    fn add_device(&self, dev_name: String, node: Arc<DevNode>) {
464        self.devices.write().insert(dev_name, node);
465    }
466}
467
468impl VfsNode for DevSubDir {
469    fn node_type(&self) -> NodeType {
470        NodeType::Directory
471    }
472
473    fn read(&self, _offset: usize, buffer: &mut [u8]) -> Result<usize, KernelError> {
474        // Dispatch reads on child device nodes.
475        // This is called when a file under the subdir is read directly.
476        // For evdev nodes, read input events; for DRI nodes, not supported.
477        let _ = buffer;
478        Err(KernelError::FsError(FsError::IsADirectory))
479    }
480
481    fn write(&self, _offset: usize, _data: &[u8]) -> Result<usize, KernelError> {
482        Err(KernelError::FsError(FsError::IsADirectory))
483    }
484
485    fn metadata(&self) -> Result<Metadata, KernelError> {
486        Ok(Metadata {
487            node_type: NodeType::Directory,
488            size: 0,
489            permissions: Permissions::default(),
490            uid: 0,
491            gid: 0,
492            created: 0,
493            modified: 0,
494            accessed: 0,
495            inode: 0,
496        })
497    }
498
499    fn readdir(&self) -> Result<Vec<DirEntry>, KernelError> {
500        let devices = self.devices.read();
501        let mut entries = Vec::new();
502
503        entries.push(DirEntry {
504            name: String::from("."),
505            node_type: NodeType::Directory,
506            inode: 0,
507        });
508
509        entries.push(DirEntry {
510            name: String::from(".."),
511            node_type: NodeType::Directory,
512            inode: 0,
513        });
514
515        for (name, device) in devices.iter() {
516            entries.push(DirEntry {
517                name: name.clone(),
518                node_type: device.node_type,
519                inode: 0,
520            });
521        }
522
523        Ok(entries)
524    }
525
526    fn lookup(&self, name: &str) -> Result<Arc<dyn VfsNode>, KernelError> {
527        let devices = self.devices.read();
528        devices
529            .get(name)
530            .map(|node| node.clone() as Arc<dyn VfsNode>)
531            .ok_or(KernelError::FsError(FsError::NotFound))
532    }
533
534    fn create(
535        &self,
536        _name: &str,
537        _permissions: Permissions,
538    ) -> Result<Arc<dyn VfsNode>, KernelError> {
539        Err(KernelError::OperationNotSupported {
540            operation: "create in device subdir",
541        })
542    }
543
544    fn mkdir(
545        &self,
546        _name: &str,
547        _permissions: Permissions,
548    ) -> Result<Arc<dyn VfsNode>, KernelError> {
549        Err(KernelError::OperationNotSupported {
550            operation: "mkdir in device subdir",
551        })
552    }
553
554    fn unlink(&self, _name: &str) -> Result<(), KernelError> {
555        Err(KernelError::OperationNotSupported {
556            operation: "unlink device subdir node",
557        })
558    }
559
560    fn truncate(&self, _size: usize) -> Result<(), KernelError> {
561        Err(KernelError::FsError(FsError::IsADirectory))
562    }
563}
564
565/// Device filesystem root directory
566struct DevRoot {
567    devices: RwLock<BTreeMap<String, Arc<DevNode>>>,
568    /// Subdirectories (dri, input, etc.)
569    subdirs: RwLock<BTreeMap<String, Arc<DevSubDir>>>,
570}
571
572impl DevRoot {
573    fn new() -> Self {
574        let mut devices = BTreeMap::new();
575
576        // Create standard device nodes
577        devices.insert(
578            String::from("null"),
579            Arc::new(DevNode::new_char(String::from("null"), 1, 3)),
580        );
581
582        devices.insert(
583            String::from("zero"),
584            Arc::new(DevNode::new_char(String::from("zero"), 1, 5)),
585        );
586
587        devices.insert(
588            String::from("random"),
589            Arc::new(DevNode::new_char(String::from("random"), 1, 8)),
590        );
591
592        devices.insert(
593            String::from("urandom"),
594            Arc::new(DevNode::new_char(String::from("urandom"), 1, 9)),
595        );
596
597        devices.insert(
598            String::from("console"),
599            Arc::new(DevNode::new_char(String::from("console"), 5, 1)),
600        );
601
602        devices.insert(
603            String::from("tty0"),
604            Arc::new(DevNode::new_char(String::from("tty0"), 4, 0)),
605        );
606
607        // Create /dev/dri/ subdirectory with DRM device nodes
608        let dri_dir = Arc::new(DevSubDir::new(String::from("dri")));
609        dri_dir.add_device(
610            String::from("card0"),
611            Arc::new(DevNode::new_char(String::from("dri/card0"), 226, 0)),
612        );
613        dri_dir.add_device(
614            String::from("renderD128"),
615            Arc::new(DevNode::new_char(String::from("dri/renderD128"), 226, 128)),
616        );
617
618        // Create /dev/input/ subdirectory with evdev device nodes
619        let input_dir = Arc::new(DevSubDir::new(String::from("input")));
620        input_dir.add_device(
621            String::from("event0"),
622            Arc::new(DevNode::new_char(String::from("input/event0"), 13, 64)),
623        );
624        input_dir.add_device(
625            String::from("event1"),
626            Arc::new(DevNode::new_char(String::from("input/event1"), 13, 65)),
627        );
628
629        let mut subdirs = BTreeMap::new();
630        subdirs.insert(String::from("dri"), dri_dir);
631        subdirs.insert(String::from("input"), input_dir);
632
633        Self {
634            devices: RwLock::new(devices),
635            subdirs: RwLock::new(subdirs),
636        }
637    }
638}
639
640impl VfsNode for DevRoot {
641    fn node_type(&self) -> NodeType {
642        NodeType::Directory
643    }
644
645    fn read(&self, _offset: usize, _buffer: &mut [u8]) -> Result<usize, KernelError> {
646        Err(KernelError::FsError(FsError::IsADirectory))
647    }
648
649    fn write(&self, _offset: usize, _data: &[u8]) -> Result<usize, KernelError> {
650        Err(KernelError::FsError(FsError::IsADirectory))
651    }
652
653    fn metadata(&self) -> Result<Metadata, KernelError> {
654        Ok(Metadata {
655            node_type: NodeType::Directory,
656            size: 0,
657            permissions: Permissions::default(),
658            uid: 0,
659            gid: 0,
660            created: 0,
661            modified: 0,
662            accessed: 0,
663            inode: 0,
664        })
665    }
666
667    fn readdir(&self) -> Result<Vec<DirEntry>, KernelError> {
668        let devices = self.devices.read();
669        let subdirs = self.subdirs.read();
670        let mut entries = Vec::new();
671
672        entries.push(DirEntry {
673            name: String::from("."),
674            node_type: NodeType::Directory,
675            inode: 0,
676        });
677
678        entries.push(DirEntry {
679            name: String::from(".."),
680            node_type: NodeType::Directory,
681            inode: 0,
682        });
683
684        // List subdirectories (dri, input)
685        for (name, _) in subdirs.iter() {
686            entries.push(DirEntry {
687                name: name.clone(),
688                node_type: NodeType::Directory,
689                inode: 0,
690            });
691        }
692
693        for (name, device) in devices.iter() {
694            entries.push(DirEntry {
695                name: name.clone(),
696                node_type: device.node_type,
697                inode: 0,
698            });
699        }
700
701        Ok(entries)
702    }
703
704    fn lookup(&self, name: &str) -> Result<Arc<dyn VfsNode>, KernelError> {
705        // Check subdirectories first (dri, input)
706        {
707            let subdirs = self.subdirs.read();
708            if let Some(subdir) = subdirs.get(name) {
709                return Ok(subdir.clone() as Arc<dyn VfsNode>);
710            }
711        }
712
713        let devices = self.devices.read();
714        devices
715            .get(name)
716            .map(|node| node.clone() as Arc<dyn VfsNode>)
717            .ok_or(KernelError::FsError(FsError::NotFound))
718    }
719
720    fn create(
721        &self,
722        _name: &str,
723        _permissions: Permissions,
724    ) -> Result<Arc<dyn VfsNode>, KernelError> {
725        Err(KernelError::OperationNotSupported {
726            operation: "create in /dev",
727        })
728    }
729
730    fn mkdir(
731        &self,
732        _name: &str,
733        _permissions: Permissions,
734    ) -> Result<Arc<dyn VfsNode>, KernelError> {
735        Err(KernelError::OperationNotSupported {
736            operation: "mkdir in /dev",
737        })
738    }
739
740    fn unlink(&self, _name: &str) -> Result<(), KernelError> {
741        Err(KernelError::OperationNotSupported {
742            operation: "unlink in /dev",
743        })
744    }
745
746    fn truncate(&self, _size: usize) -> Result<(), KernelError> {
747        Err(KernelError::FsError(FsError::IsADirectory))
748    }
749}
750
751/// Device filesystem
752pub struct DevFs {
753    root: Arc<DevRoot>,
754}
755
756impl DevFs {
757    pub fn new() -> Self {
758        Self {
759            root: Arc::new(DevRoot::new()),
760        }
761    }
762}
763
764impl Default for DevFs {
765    fn default() -> Self {
766        Self::new()
767    }
768}
769
770impl Filesystem for DevFs {
771    fn root(&self) -> Arc<dyn VfsNode> {
772        self.root.clone() as Arc<dyn VfsNode>
773    }
774
775    fn name(&self) -> &str {
776        "devfs"
777    }
778
779    fn is_readonly(&self) -> bool {
780        false
781    }
782
783    fn sync(&self) -> Result<(), KernelError> {
784        Ok(())
785    }
786}