Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
87 changes: 78 additions & 9 deletions src/arch/riscv64/kernel/serial.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
}

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<usize, Errno> {
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<usize, Errno> {
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)
}
}

Expand All @@ -16,24 +68,41 @@ impl ErrorType for SerialDevice {

impl Read for SerialDevice {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
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<bool, Self::Error> {
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<usize, Self::Error> {
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> {
Expand Down
5 changes: 5 additions & 0 deletions src/console/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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;
Expand Down
43 changes: 35 additions & 8 deletions src/fd/stdio/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,49 @@ pub struct ConsoleStdin;

impl ObjectInterface for ConsoleStdin {
async fn poll(&self, event: PollEvent) -> io::Result<PollEvent> {
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<usize> {
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
Expand Down
12 changes: 10 additions & 2 deletions xtask/src/ci/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Device>,
Expand Down Expand Up @@ -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 => {
Expand All @@ -358,7 +364,9 @@ impl Qemu {
}
}

cpu_args.push("-semihosting".to_owned());
if !self.no_semihosting {
cpu_args.push("-semihosting".to_owned());
}
cpu_args
}
}
Expand Down
Loading