Skip to main content

hermit/console/
mod.rs

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