Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ jobs:
- run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} ${{ matrix.rs_flags }} --package hello_world --no-default-features qemu ${{ matrix.qemu_flags }} --microvm
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 }} ${{ 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
Loading