Skip to main content

hermit/console/
mod.rs

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