Implements BufRead in kata_io

Reading one byte at a time in a loop causes excessive syscalls. For
example, ZMODEM needs to do this when finding the end of each frame.
This change copies parts of std::io needed to wrap a Read in a BufReader
so that the read will be done eagerly.

It enables buffered UART reads for the whole DebugConsole at the point
where its single Read object is initialized.

Change-Id: I19935ca02333bc74d9b581d384a1854968ac5329
GitOrigin-RevId: 1aa80e036561a6a18e79dbf28bcab240efe80cd1
This commit is contained in:
Matt Harvey 2021-09-26 15:54:16 -07:00 committed by Sam Leffler
parent d70003982c
commit 4faa2e4c82
4 changed files with 109 additions and 1 deletions

View File

@ -8,6 +8,7 @@ description = "Kata OS DebugConsole"
[dependencies]
panic-halt = "0.2.0"
kata-allocator = { path = "../kata-allocator" }
kata-io = { path = "../kata-io" }
kata-logger = { path = "../kata-logger" }
kata-panic = { path = "../kata-panic" }
kata-shell = { path = "../kata-shell" }

View File

@ -15,6 +15,7 @@
extern crate kata_panic;
use kata_allocator;
use kata_io;
use kata_logger::KataLogger;
use kata_shell;
use kata_uart_client;
@ -45,6 +46,6 @@ pub extern "C" fn pre_init() {
pub extern "C" fn run() -> ! {
trace!("run");
let mut tx = kata_uart_client::Tx::new();
let mut rx = kata_uart_client::Rx::new();
let mut rx = kata_io::BufReader::new(kata_uart_client::Rx::new());
kata_shell::repl(&mut tx, &mut rx);
}

View File

@ -3,3 +3,6 @@ name = "kata-io"
version = "0.1.0"
authors = ["Matt Harvey <mattharvey@google.com>"]
edition = "2018"
[dependencies]
memchr = { version = "2.4.1", default-features = false }

View File

@ -1,5 +1,9 @@
#![no_std]
extern crate alloc;
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::cmp;
#[derive(Debug)]
@ -30,6 +34,38 @@ pub trait Read {
}
}
/// Partial mimic of std::io::BufRead.
pub trait BufRead: Read {
fn fill_buf(&mut self) -> Result<&[u8]>;
fn consume(&mut self, amt: usize);
fn read_until(&mut self, delim: u8, buf: &mut Vec<u8>) -> Result<usize> {
// Implementation adapted from std::io.
let mut read = 0;
loop {
let (done, used) = {
let available = self.fill_buf()?;
match memchr::memchr(delim, available) {
Some(i) => {
buf.extend_from_slice(&available[..=i]);
(true, i + 1)
}
None => {
buf.extend_from_slice(available);
(false, available.len())
}
}
};
self.consume(used);
read += used;
if done || used == 0 {
return Ok(read);
}
}
}
}
/// Partial mimic of std::io::Write.
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
@ -103,3 +139,70 @@ where
(**self).flush()
}
}
pub struct BufReader<R> {
inner: R,
buf: Box<[u8]>,
pos: usize,
cap: usize,
}
impl<R: Read> BufReader<R> {
pub fn new(inner: R) -> BufReader<R> {
const BUFFER_SIZE : usize = 1024; // free to be changed
BufReader {
inner: inner,
buf: Box::new([0u8; BUFFER_SIZE]),
pos: 0,
cap: 0,
}
}
fn discard_buffer(&mut self) {
// Implementation copied from std::io.
self.pos = 0;
self.cap = 0;
}
}
impl<R: Read> Read for BufReader<R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
// Implementation copied from std::io.
// If we don't have any buffered data and we're doing a massive read
// (larger than our internal buffer), bypass our internal buffer
// entirely.
if self.pos == self.cap && buf.len() >= self.buf.len() {
self.discard_buffer();
return self.inner.read(buf);
}
let nread = {
let mut rem = self.fill_buf()?;
rem.read(buf)?
};
self.consume(nread);
Ok(nread)
}
}
impl<R: Read> BufRead for BufReader<R> {
fn fill_buf(&mut self) -> Result<&[u8]> {
// Implementation copied from std::io.
// If we've reached the end of our internal buffer then we need to fetch
// some more data from the underlying reader.
// Branch using `>=` instead of the more correct `==`
// to tell the compiler that the pos..cap slice is always valid.
if self.pos >= self.cap {
debug_assert!(self.pos == self.cap);
self.cap = self.inner.read(&mut self.buf)?;
self.pos = 0;
}
Ok(&self.buf[self.pos..self.cap])
}
fn consume(&mut self, amt: usize) {
// Implementation copied from std::io.
self.pos = cmp::min(self.pos + amt, self.cap);
}
}