hermit/
console.rs

1#![allow(dead_code)]
2
3use core::mem::MaybeUninit;
4use core::{fmt, mem};
5
6use heapless::Vec;
7use hermit_sync::{InterruptTicketMutex, Lazy};
8
9use crate::arch::SerialDevice;
10#[cfg(feature = "console")]
11use crate::drivers::console::VirtioUART;
12use crate::executor::WakerRegistration;
13use crate::io;
14#[cfg(not(target_arch = "riscv64"))]
15use crate::syscalls::interfaces::serial_buf_hypercall;
16
17const SERIAL_BUFFER_SIZE: usize = 256;
18
19pub(crate) enum IoDevice {
20	#[cfg(not(target_arch = "riscv64"))]
21	Uhyve(UhyveSerial),
22	Uart(SerialDevice),
23	#[cfg(feature = "console")]
24	Virtio(VirtioUART),
25}
26
27impl IoDevice {
28	pub fn write(&self, buf: &[u8]) {
29		match self {
30			#[cfg(not(target_arch = "riscv64"))]
31			IoDevice::Uhyve(s) => s.write(buf),
32			IoDevice::Uart(s) => s.write(buf),
33			#[cfg(feature = "console")]
34			IoDevice::Virtio(s) => s.write(buf),
35		}
36
37		#[cfg(all(target_arch = "x86_64", feature = "vga"))]
38		for &byte in buf {
39			// vga::write_byte() checks if VGA support has been initialized,
40			// so we don't need any additional if clause around it.
41			crate::arch::kernel::vga::write_byte(byte);
42		}
43	}
44
45	pub fn read(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
46		match self {
47			#[cfg(not(target_arch = "riscv64"))]
48			IoDevice::Uhyve(s) => s.read(buf),
49			IoDevice::Uart(s) => s.read(buf),
50			#[cfg(feature = "console")]
51			IoDevice::Virtio(s) => s.read(buf),
52		}
53	}
54
55	pub fn can_read(&self) -> bool {
56		match self {
57			#[cfg(not(target_arch = "riscv64"))]
58			IoDevice::Uhyve(s) => s.can_read(),
59			IoDevice::Uart(s) => s.can_read(),
60			#[cfg(feature = "console")]
61			IoDevice::Virtio(s) => s.can_read(),
62		}
63	}
64}
65
66#[cfg(not(target_arch = "riscv64"))]
67pub(crate) struct UhyveSerial;
68
69#[cfg(not(target_arch = "riscv64"))]
70impl UhyveSerial {
71	pub const fn new() -> Self {
72		Self {}
73	}
74
75	pub fn write(&self, buf: &[u8]) {
76		serial_buf_hypercall(buf);
77	}
78
79	pub fn read(&self, _buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
80		Ok(0)
81	}
82
83	pub fn can_read(&self) -> bool {
84		false
85	}
86}
87
88pub(crate) struct Console {
89	device: IoDevice,
90	buffer: Vec<u8, SERIAL_BUFFER_SIZE>,
91}
92
93impl Console {
94	pub fn new(device: IoDevice) -> Self {
95		Self {
96			device,
97			buffer: Vec::new(),
98		}
99	}
100
101	/// Writes a buffer to the console.
102	/// The content is buffered until a newline is encountered or the internal buffer is full.
103	/// To force early output, use [`flush`](Self::flush).
104	pub fn write(&mut self, buf: &[u8]) {
105		if SERIAL_BUFFER_SIZE - self.buffer.len() >= buf.len() {
106			// unwrap: we checked that buf fits in self.buffer
107			self.buffer.extend_from_slice(buf).unwrap();
108			if buf.contains(&b'\n') {
109				self.flush();
110			}
111		} else {
112			self.device.write(&self.buffer);
113			self.buffer.clear();
114			if buf.len() >= SERIAL_BUFFER_SIZE {
115				self.device.write(buf);
116			} else {
117				// unwrap: we checked that buf fits in self.buffer
118				self.buffer.extend_from_slice(buf).unwrap();
119				if buf.contains(&b'\n') {
120					self.flush();
121				}
122			}
123		}
124	}
125
126	/// Immediately writes everything in the internal buffer to the output.
127	pub fn flush(&mut self) {
128		if !self.buffer.is_empty() {
129			self.device.write(&self.buffer);
130			self.buffer.clear();
131		}
132	}
133
134	pub fn read(&mut self, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
135		self.device.read(buf)
136	}
137
138	pub fn can_read(&self) -> bool {
139		self.device.can_read()
140	}
141
142	#[cfg(feature = "console")]
143	pub fn replace_device(&mut self, device: IoDevice) {
144		self.device = device;
145	}
146}
147
148/// A collection of methods that are required to format
149/// a message to Hermit's console.
150impl fmt::Write for Console {
151	/// Print a string of characters.
152	#[inline]
153	fn write_str(&mut self, s: &str) -> fmt::Result {
154		if !s.is_empty() {
155			self.write(s.as_bytes());
156		}
157
158		Ok(())
159	}
160}
161
162pub(crate) static CONSOLE_WAKER: InterruptTicketMutex<WakerRegistration> =
163	InterruptTicketMutex::new(WakerRegistration::new());
164pub(crate) static CONSOLE: Lazy<InterruptTicketMutex<Console>> = Lazy::new(|| {
165	crate::CoreLocal::install();
166
167	#[cfg(not(target_arch = "riscv64"))]
168	if crate::env::is_uhyve() {
169		InterruptTicketMutex::new(Console::new(IoDevice::Uhyve(UhyveSerial::new())))
170	} else {
171		InterruptTicketMutex::new(Console::new(IoDevice::Uart(SerialDevice::new())))
172	}
173	#[cfg(target_arch = "riscv64")]
174	InterruptTicketMutex::new(Console::new(IoDevice::Uart(SerialDevice::new())))
175});
176
177#[doc(hidden)]
178pub fn _print(args: fmt::Arguments<'_>) {
179	use fmt::Write;
180	CONSOLE.lock().write_fmt(args).unwrap();
181}
182
183#[doc(hidden)]
184pub fn _panic_print(args: fmt::Arguments<'_>) {
185	use fmt::Write;
186	let mut console = unsafe { CONSOLE.make_guard_unchecked() };
187	console.write_fmt(args).ok();
188	mem::forget(console);
189}
190
191#[cfg(all(test, not(target_os = "none")))]
192mod tests {
193	use super::*;
194
195	#[test]
196	fn test_console() {
197		println!("HelloWorld");
198	}
199}