//! The console "tty": keyboard input with a tiny line discipline, and the //! `File` behind file descriptors 0, 1 and 2. //! //! Like a Linux terminal in canonical mode: typed characters are echoed and //! collected in a line buffer (Backspace edits it), and only when Enter is //! pressed does the whole line become readable from fd 0. So a user program's //! `read(0, ...)` gets a complete, already-edited line. use crate::errno::Errno; use crate::fs::file::{File, KIND_CONSOLE, Stat}; use crate::thread::WaitQueue; use crate::{print, serial_print, vga_buffer}; use alloc::string::String; use conquer_once::spin::OnceCell; use crossbeam_queue::ArrayQueue; use spin::Mutex; /// The line being typed (not yet readable). static LINE: Mutex = Mutex::new(String::new()); /// Finished lines, as bytes, waiting for `read(0, ...)`. static INPUT: OnceCell> = OnceCell::uninit(); /// Threads blocked in `Console::read`, waiting for a line. static INPUT_WAIT: WaitQueue = WaitQueue::new(); fn input() -> &'static ArrayQueue { INPUT.get_or_init(|| ArrayQueue::new(4096)) } /// Called by the keyboard task for every typed character. pub fn input_char(c: char) { let mut line = LINE.lock(); match c { '\u{8}' => { // Backspace: only erase what's in the current line. if line.pop().is_some() { vga_buffer::backspace(); serial_print!("\x08"); // uart_16550 turns this into "\x08 \x08" } } '\n' => { print!("\n"); serial_print!("\n"); for b in line.bytes().chain(Some(b'\n')) { let _ = input().push(b); // queue full: drop (nobody is reading) } line.clear(); INPUT_WAIT.wake_all(); // a reader may be blocked in Console::read } c => { print!("{}", c); serial_print!("{}", c); line.push(c); } } } /// The console as a file: reading gets keyboard lines, writing prints to the /// screen and serial port. pub struct Console; impl File for Console { /// Blocks until a line is available, then returns up to `buf.len()` /// bytes of it (stopping after the '\n'). fn read(&self, buf: &mut [u8]) -> Result { if buf.is_empty() { return Ok(0); } // Block until a line arrives: the thread is parked on INPUT_WAIT // (using no CPU) and `input_char` wakes it when Enter is pressed. // Lines are queued whole, so once the first byte is there the rest // of the line is too. (The closure runs with interrupts off; the // ArrayQueue is lock-free, so that's fine.) Ok(INPUT_WAIT.wait_until(|| { let mut n = 0; while n < buf.len() { let Some(b) = input().pop() else { break }; buf[n] = b; n += 1; if b == b'\n' { break; } } if n > 0 { Some(n) } else { None } })) } fn write(&self, buf: &[u8]) -> Result { // Print valid UTF-8 as is, anything else as '?'. for chunk in buf.utf8_chunks() { print!("{}", chunk.valid()); serial_print!("{}", chunk.valid()); if !chunk.invalid().is_empty() { print!("?"); serial_print!("?"); } } Ok(buf.len()) } fn stat(&self) -> Result { Ok(Stat { kind: KIND_CONSOLE, size: 0 }) } }