hermit/
console.rs

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