rz shell command

This change adds an "rz" command to the shell that calls a function
rz(). It switches the IO from the shell to a ZMODEM receiver that
produces a Rust upload object that collects the uploaded bytes and
computes a CRC32 checksum on the fly.

In a later change, we will retain the most recent payload in a
shell-owned object until it is consumed by an "install" command.

Limitations:

  * Sender hangup will cause the receiver to block forever and the
    prompt not to come back, since kata-uart-client does reads that
    block forever.
  * Logging must be set to a level higher than debug or messages from
    the zmodem crate itself corrupt the transfer.
  * The sender must rate limit to ~150 bytes/sec to avoid running
    too far ahead of the Renode UART, which drops bytes when the RX
    FIFO is full.
  * Uploading too big a payload will cause a kata_panic! unless the
    heap size is increased in kata-debug-console/src/run.rs

Demo:

KATA> loglevel error
ERROR
KATA> rz
**B0100000023be50
[Ctrl-C]

$ sz -O ~/random < /tmp/term | pv -L 150 > /tmp/term
Sending: random
Bytes Sent:  15360/  16384   BPS:26805    ETA 00:00   150 B 0:00:01 [ 146 B/s] [ <=>     Bytes Sent:  16384   BPS:144                                                            ]

Transfer complete
16.6KiB 0:01:53 [ 149 B/s] [               <=>                                          ]

$ stty sane -echo -icanon; socat - /tmp/term 2> /dev/null; stty sane;
size: 16384, crc32: 991b1d60
KATA>
[Ctrl-C]

$ crc32 ~/random
991b1d60

Change-Id: I53252b821a829a667a23a9fd072f71c6955fdc1a
GitOrigin-RevId: c818a35f186dcd8c083891bfaa84ad0a9f9fef7d
This commit is contained in:
Matt Harvey 2021-09-21 21:50:21 -07:00 committed by Sam Leffler
parent 929966d376
commit 7363bd638c
3 changed files with 83 additions and 3 deletions

View File

@ -5,7 +5,9 @@ authors = ["Matt Harvey <mattharvey@google.com>"]
edition = "2018"
[dependencies]
crc = { version = "1.4.0", default_features = false }
cstr_core = "0.2"
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
kata-io = { path = "../kata-io" }
kata-line-reader = { path = "../kata-line-reader" }
kata-proc-common = { path = "../../ProcessManager/kata-proc-common" }
@ -13,3 +15,4 @@ kata-security-interface = { path = "../../SecurityCoordinator/kata-security-inte
kata-storage-interface = { path = "../../StorageManager/kata-storage-interface" }
log = "0.4"
postcard = { version = "0.7", features = ["alloc"], default-features = false }
zmodem = { path = "../zmodem" }

View File

@ -6,6 +6,7 @@ use alloc::vec::Vec;
use core::fmt;
use core::fmt::Write;
use cstr_core::CString;
use hex;
use postcard;
use kata_io as io;
@ -15,10 +16,13 @@ use kata_storage_interface::kata_storage_delete;
use kata_storage_interface::kata_storage_read;
use kata_storage_interface::kata_storage_write;
mod rz;
/// Error type indicating why a command line is not runnable.
enum CommandError {
UnknownCommand,
BadArgs,
IO,
Formatter(fmt::Error),
}
@ -27,6 +31,7 @@ impl fmt::Display for CommandError {
match self {
CommandError::UnknownCommand => write!(f, "unknown command"),
CommandError::BadArgs => write!(f, "invalid arguments"),
CommandError::IO => write!(f, "input / output error"),
CommandError::Formatter(e) => write!(f, "{}", e),
}
}
@ -50,6 +55,12 @@ impl From<fmt::Error> for CommandError {
}
}
impl From<io::Error> for CommandError {
fn from(_err: io::Error) -> CommandError {
CommandError::IO
}
}
/// Read-eval-print loop for the DebugConsole command line interface.
pub fn repl(output: &mut dyn io::Write, input: &mut dyn io::Read) -> ! {
let mut line_reader = LineReader::new();
@ -57,7 +68,7 @@ pub fn repl(output: &mut dyn io::Write, input: &mut dyn io::Read) -> ! {
const PROMPT: &str = "KATA> ";
let _ = output.write_str(PROMPT);
match line_reader.read_line(output, input) {
Ok(cmdline) => dispatch_command(cmdline, output),
Ok(cmdline) => dispatch_command(cmdline, input, output),
Err(e) => {
let _ = writeln!(output, "\n{}", e);
}
@ -69,7 +80,7 @@ pub fn repl(output: &mut dyn io::Write, input: &mut dyn io::Read) -> ! {
///
/// The line is split on whitespace. The first token is the command; the
/// remaining tokens are the arguments.
fn dispatch_command(cmdline: &str, output: &mut dyn io::Write) {
fn dispatch_command(cmdline: &str, input: &mut dyn io::Read, output: &mut dyn io::Write) {
let mut args = cmdline.split_ascii_whitespace();
match args.nth(0) {
Some(command) => {
@ -88,6 +99,7 @@ fn dispatch_command(cmdline: &str, output: &mut dyn io::Write) {
"kvwrite" => kvwrite_command(&mut args, output),
"install" => install_command(&mut args, output),
"loglevel" => loglevel_command(&mut args, output),
"rz" => rz_command(input, output),
"ps" => ps_command(),
"scecho" => scecho_command(cmdline, output),
"start" => start_command(&mut args, output),
@ -155,7 +167,7 @@ fn scecho_command(cmdline: &str, output: &mut dyn io::Write) -> Result<(), Comma
Ok(())
}
// Set/display the max log level for the DebugConsole.
/// Implements a command to configure the max log level for the DebugConsole.
fn loglevel_command(
args: &mut dyn Iterator<Item = &str>,
output: &mut dyn io::Write,
@ -175,6 +187,21 @@ fn loglevel_command(
Ok(writeln!(output, "{}", log::max_level())?)
}
/// Implements a command to receive a blob using ZMODEM.
fn rz_command(
input: &mut dyn io::Read,
mut output: &mut dyn io::Write,
) -> Result<(), CommandError> {
let upload = rz::rz(input, &mut output)?;
writeln!(
output,
"size: {}, crc32: {}",
upload.contents().len(),
hex::encode(upload.crc32().to_be_bytes())
)?;
Ok(())
}
/// Implements a "ps" command that dumps seL4 scheduler state to the console.
fn ps_command() -> Result<(), CommandError> {
extern "C" {

View File

@ -0,0 +1,50 @@
/// Wrapper types for fully-buffered ZMODEM receives.
use alloc::vec::Vec;
use crc::crc32;
use crc::Hasher32;
use zmodem;
use kata_io as io;
pub struct Upload {
digest: crc32::Digest,
contents: Vec<u8>,
}
impl Upload {
pub fn new() -> Upload {
Upload {
digest: crc32::Digest::new(crc32::IEEE),
contents: Vec::new(),
}
}
pub fn crc32(&self) -> u32 {
self.digest.sum32()
}
pub fn contents(&self) -> &[u8] {
self.contents.as_slice()
}
}
impl io::Write for Upload {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.digest.write(buf);
self.contents.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// Receives using ZMODEM and wraps the result as an Upload.
pub fn rz<R: io::Read, W: io::Write>(r: R, w: W) -> Result<Upload, io::Error> {
let mut upload = Upload::new();
zmodem::recv::recv(r, w, &mut upload)?;
Ok(upload)
}