diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2f1233865..547ddebc1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -259,6 +259,8 @@ jobs: if: matrix.arch == 'x86_64' - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} ${{ matrix.rs_flags }} --package stdin qemu ${{ matrix.qemu_flags }} if: matrix.arch != 'riscv64' + - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} --package stdin qemu ${{ matrix.qemu_flags }} --no-semihosting + if: matrix.arch == 'riscv64' - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} ${{ matrix.rs_flags }} --package stdin --features hermit/virtio-console qemu ${{ matrix.qemu_flags }} --devices virtio-console-pci if: matrix.arch != 'riscv64' - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} ${{ matrix.rs_flags }} --package stdin --features hermit/virtio-console --no-default-features qemu ${{ matrix.qemu_flags }} --devices virtio-console-mmio --microvm diff --git a/src/arch/riscv64/kernel/serial.rs b/src/arch/riscv64/kernel/serial.rs index 30f1f3dc97..26cf3502ce 100644 --- a/src/arch/riscv64/kernel/serial.rs +++ b/src/arch/riscv64/kernel/serial.rs @@ -1,12 +1,64 @@ +use core::hint; + use embedded_io::{ErrorType, Read, ReadReady, Write}; +use sbi_rt::Physical; use crate::errno::Errno; -pub(crate) struct SerialDevice; +const SBI_CONSOLE_BUFFER_SIZE: usize = 256; + +#[repr(C, align(4096))] +pub(crate) struct SerialDevice { + sbi_buffer: [u8; SBI_CONSOLE_BUFFER_SIZE], + buffered_byte: Option, +} impl SerialDevice { pub fn new() -> Self { - Self {} + Self { + sbi_buffer: [0; SBI_CONSOLE_BUFFER_SIZE], + buffered_byte: None, + } + } + + fn read_from_console(&mut self, buf: &mut [u8]) -> Result { + let len = buf.len().min(self.sbi_buffer.len()); + if len == 0 { + return Ok(0); + } + + // Kernel data is identity-mapped on RISC-V. Using a page-aligned bounce buffer + // avoids walking the page table before it exists or while it is already locked. + let physical = Physical::<&mut [u8]>::new(len, self.sbi_buffer.as_mut_ptr().addr(), 0); + let read = sbi_rt::console_read(physical) + .into_result() + .map_err(|_| Errno::Io)?; + + if read > len { + return Err(Errno::Io); + } + + buf[..read].copy_from_slice(&self.sbi_buffer[..read]); + Ok(read) + } + + fn write_to_console(&mut self, buf: &[u8]) -> Result { + let len = buf.len().min(self.sbi_buffer.len()); + if len == 0 { + return Ok(0); + } + + self.sbi_buffer[..len].copy_from_slice(&buf[..len]); + let physical = Physical::<&[u8]>::new(len, self.sbi_buffer.as_ptr().addr(), 0); + let written = sbi_rt::console_write(physical) + .into_result() + .map_err(|_| Errno::Io)?; + + if written > len { + return Err(Errno::Io); + } + + Ok(written) } } @@ -16,24 +68,41 @@ impl ErrorType for SerialDevice { impl Read for SerialDevice { fn read(&mut self, buf: &mut [u8]) -> Result { - let _ = buf; - Ok(0) + if buf.is_empty() { + return Ok(0); + } + + if let Some(byte) = self.buffered_byte.take() { + buf[0] = byte; + return Ok(1); + } + + self.read_from_console(buf) } } impl ReadReady for SerialDevice { fn read_ready(&mut self) -> Result { - Ok(false) + if self.buffered_byte.is_none() { + let mut byte = 0; + if self.read_from_console(core::slice::from_mut(&mut byte))? == 1 { + self.buffered_byte = Some(byte); + } + } + + Ok(self.buffered_byte.is_some()) } } impl Write for SerialDevice { fn write(&mut self, buf: &[u8]) -> Result { - for byte in buf { - sbi_rt::console_write_byte(*byte); + loop { + let written = self.write_to_console(buf)?; + if written > 0 || buf.is_empty() { + return Ok(written); + } + hint::spin_loop(); } - - Ok(buf.len()) } fn flush(&mut self) -> Result<(), Self::Error> { diff --git a/src/console/mod.rs b/src/console/mod.rs index 4b974d8d50..3d229fa33d 100644 --- a/src/console/mod.rs +++ b/src/console/mod.rs @@ -15,6 +15,7 @@ use crate::executor::WakerRegistration; const SERIAL_BUFFER_SIZE: usize = 256; +#[cfg_attr(target_arch = "riscv64", allow(clippy::large_enum_variant))] pub(crate) enum IoDevice { #[cfg(feature = "uhyve")] Uhyve(uhyve::UhyveSerial), @@ -89,6 +90,10 @@ impl Console { } } + pub fn requires_input_polling(&self) -> bool { + cfg!(target_arch = "riscv64") && matches!(&self.device, IoDevice::Uart(_)) + } + #[cfg(feature = "virtio-console")] pub fn replace_device(&mut self, device: IoDevice) { self.device = device; diff --git a/src/fd/stdio/console.rs b/src/fd/stdio/console.rs index 93f688f305..36cb56a261 100644 --- a/src/fd/stdio/console.rs +++ b/src/fd/stdio/console.rs @@ -11,22 +11,49 @@ pub struct ConsoleStdin; impl ObjectInterface for ConsoleStdin { async fn poll(&self, event: PollEvent) -> io::Result { - let available = if CONSOLE.lock().read_ready()? { - PollEvent::POLLIN | PollEvent::POLLRDNORM | PollEvent::POLLRDBAND - } else { - PollEvent::empty() - }; - - Ok(event & available) + future::poll_fn(|cx| { + let readable = PollEvent::POLLIN | PollEvent::POLLRDNORM | PollEvent::POLLRDBAND; + let (available, requires_polling) = { + let mut console = CONSOLE.lock(); + (console.read_ready()?, console.requires_input_polling()) + }; + let ready = event + & if available { + readable + } else { + PollEvent::empty() + }; + + if !ready.is_empty() || !event.intersects(readable) { + Poll::Ready(Ok(ready)) + } else { + if requires_polling { + cx.waker().wake_by_ref(); + } else { + CONSOLE_WAKER.lock().register(cx.waker()); + if CONSOLE.lock().read_ready()? { + return Poll::Ready(Ok(event & readable)); + } + } + Poll::Pending + } + }) + .await } async fn read(&self, buf: &mut [u8]) -> io::Result { future::poll_fn(|cx| { - let read_bytes = CONSOLE.lock().read(buf)?; + let (read_bytes, requires_polling) = { + let mut console = CONSOLE.lock(); + (console.read(buf)?, console.requires_input_polling()) + }; if read_bytes > 0 { CONSOLE.lock().write_all(&buf[..read_bytes])?; CONSOLE.lock().flush()?; Poll::Ready(Ok(read_bytes)) + } else if requires_polling { + cx.waker().wake_by_ref(); + Poll::Pending } else { CONSOLE_WAKER.lock().register(cx.waker()); Poll::Pending diff --git a/xtask/src/ci/qemu.rs b/xtask/src/ci/qemu.rs index e14ec22c07..2859c9a979 100644 --- a/xtask/src/ci/qemu.rs +++ b/xtask/src/ci/qemu.rs @@ -41,6 +41,10 @@ pub struct Qemu { #[arg(long)] uefi: bool, + /// Disable semihosting. + #[arg(long)] + no_semihosting: bool, + /// Devices to enable. #[arg(long)] devices: Vec, @@ -345,7 +349,9 @@ impl Qemu { cpu_args.push("max,lpa2=off".to_owned()); } - cpu_args.push("-semihosting".to_owned()); + if !self.no_semihosting { + cpu_args.push("-semihosting".to_owned()); + } cpu_args } Arch::Riscv64 => { @@ -358,7 +364,9 @@ impl Qemu { } } - cpu_args.push("-semihosting".to_owned()); + if !self.no_semihosting { + cpu_args.push("-semihosting".to_owned()); + } cpu_args } }