Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quality of life for development update #4

Closed
wants to merge 3 commits into from
Closed
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
25 changes: 25 additions & 0 deletions montool/README.mkdn
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,28 @@ monitor.
For instructions:

`cargo run -- --help`


```
A tool for interacting with the hapenny tinyboot serial monitor

Usage: hapenny-montool [OPTIONS] <PORT> <COMMAND>

Commands:
ping Perform a basic check to see if tinyboot appears to be running
peek Load a single 32-bit word from an address in the target
poke Write a single 32-bit word into the taget
write Write the contents of a file into the target. Useful for loading a program from a .bin file
call Call into an address in the target
run Write and then Call at the given address , (default zero)
help Print this message or the help of the given subcommand(s)

Arguments:
<PORT> Path to serial port on your machine, e.g. /dev/ttyUSB0 or COM1:

Options:
-b, --baud-rate <BAUD_RATE> Baud rate of serial port [default: 115200]
-h, --help Print help
-V, --version Print version

```
188 changes: 104 additions & 84 deletions montool/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::{time::Duration, path::PathBuf, io::ErrorKind, io::Write};
use std::{io::ErrorKind, io::Write, path::PathBuf, time::Duration};

use anyhow::{Context, Result, bail};
use anyhow::{bail, Context, Result};
use clap::Parser;
use indicatif::ProgressBar;
use serialport::SerialPort;
use clap::Parser;

/// A tool for interacting with the hapenny tinyboot serial monitor.
#[derive(Debug, Parser)]
Expand Down Expand Up @@ -31,33 +31,43 @@ enum SubCmd {
},
/// Write a single 32-bit word into the taget.
Poke {
/// Address to write.
#[clap(value_parser = parse_int::parse::<u32>)]
address: u32,
/// Value to write.
#[clap(value_parser = parse_int::parse::<u32>)]
value: u32,
/// Address to write.
#[clap(value_parser = parse_int::parse::<u32>)]
address: u32,
},
/// Write the contents of a file into the target. Useful for loading a
/// program from a .bin file.
Write {
/// Address to begin writing.
#[clap(value_parser = parse_int::parse::<u32>)]
address: u32,
/// File containing bytes to write; will be padded out to a multiple of
/// 4.
image_file: PathBuf,
/// Address to begin writing.
#[clap(value_parser = parse_int::parse::<u32>,default_value_t = 0)]
address: u32,
},
/// Call into an address in the target.
Call {
/// Address to call.
#[clap(value_parser = parse_int::parse::<u32>)]
address: u32,
/// If provided, the tool will immediately begin echoing back data
/// received on the serial report until you kill it. This is useful for
/// loading and running programs that are chatty, such as Dhrystone.
#[clap(long)]
then_echo: bool,
/// Address to call.
#[clap(value_parser = parse_int::parse::<u32>,default_value_t = 0)]
address: u32,
},
/// Write and then Call at the given address , (default zero).
Run {
/// File containing bytes to write; will be padded out to a multiple of
/// 4.
image_file: PathBuf,
#[clap(value_parser = parse_int::parse::<u32>,default_value_t = 0)]
address: u32,
#[clap(long)]
then_echo: bool,
},
}

Expand All @@ -73,108 +83,120 @@ fn main() -> Result<()> {

match args.cmd {
SubCmd::Ping => {
do_cmd(&mut port, &[5])
.context("pinging")?;
do_cmd(&mut port, &[5]).context("pinging")?;
}
SubCmd::Peek { address } => {
// load addr register
let mut cmd = [3, 0, 0, 0, 0];
cmd[1..].copy_from_slice(&address.to_le_bytes());
do_cmd(&mut port, &cmd)
.context("loading A")?;
do_cmd(&mut port, &cmd).context("loading A")?;
// load count register
let cmd = [4, 1, 0, 0, 0];
do_cmd(&mut port, &cmd)
.context("loading C")?;
do_cmd(&mut port, &cmd).context("loading C")?;
// read out the data
let cmd = [2];
do_cmd(&mut port, &cmd)
.context("sending GET")?;
do_cmd(&mut port, &cmd).context("sending GET")?;
let mut data = [0; 4];
port.read_exact(&mut data)
.context("waiting for data")?;
port.read_exact(&mut data).context("waiting for data")?;
println!("{:#x}", u32::from_le_bytes(data));
}
SubCmd::Poke { address, value } => {
// load addr register
let mut cmd = [3, 0, 0, 0, 0];
cmd[1..].copy_from_slice(&address.to_le_bytes());
do_cmd(&mut port, &cmd)
.context("loading A")?;
do_cmd(&mut port, &cmd).context("loading A")?;
// load count register
let cmd = [4, 1, 0, 0, 0];
do_cmd(&mut port, &cmd)
.context("loading C")?;
do_cmd(&mut port, &cmd).context("loading C")?;
// deposit the data.
let mut cmd = [1, 0, 0, 0, 0];
cmd[1..].copy_from_slice(&value.to_le_bytes());
do_cmd(&mut port, &cmd)
.context("sending PUT")?;
do_cmd(&mut port, &cmd).context("sending PUT")?;
}
SubCmd::Write { address, image_file } => {
let mut image = std::fs::read(&image_file)?;
while image.len() % 4 != 0 {
image.push(0);
}
// load addr register
let mut cmd = [3, 0, 0, 0, 0];
cmd[1..].copy_from_slice(&address.to_le_bytes());
do_cmd(&mut port, &cmd)
.context("loading A")?;
let bar = ProgressBar::new(image.len() as u64);
for chunk in image.chunks(256) {
// load count register
let word_count = u32::try_from(chunk.len() / 4)?;
let mut cmd = [4, 0, 0, 0, 0];
cmd[1..].copy_from_slice(&word_count.to_le_bytes());
do_cmd(&mut port, &cmd)
.context("loading C")?;
let mut packet = vec![1];
packet.extend_from_slice(chunk);
// deposit the data.
do_cmd(&mut port, &packet)
.context("sending PUT")?;
bar.inc(chunk.len() as u64);
}
bar.finish();
SubCmd::Write {
address,
image_file,
} => {
do_write(image_file, address, &mut port)?;
}
SubCmd::Call { address, then_echo } => {
// load addr register
let mut cmd = [3, 0, 0, 0, 0];
cmd[1..].copy_from_slice(&address.to_le_bytes());
do_cmd(&mut port, &cmd)
.context("loading A")?;
// go!
do_cmd(&mut port, &[0])
.context("sending CALL")?;

if then_echo {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
loop {
let mut b = [0];
match port.read_exact(&mut b) {
Ok(()) => {
write!(stdout, "{}", b[0] as char)?;
stdout.flush()?;
},
Err(e) if e.kind() == ErrorKind::TimedOut => {
// meh
}
other => other?,
}
}
}
do_call(address, port, then_echo)?;
}
SubCmd::Run {
address,
image_file,
then_echo,
} => {
do_write(image_file, address, &mut port)?;
do_call(address, port, then_echo)?;
}
}

Ok(())
}

fn do_write(
image_file: PathBuf,
address: u32,
port: &mut Box<dyn SerialPort>,
) -> Result<(), anyhow::Error> {
let mut image = std::fs::read(&image_file)?;
while image.len() % 4 != 0 {
image.push(0);
}
let mut cmd = [3, 0, 0, 0, 0];
cmd[1..].copy_from_slice(&address.to_le_bytes());
do_cmd(port, &cmd).context("loading A")?;
let bar = ProgressBar::new(image.len() as u64);
for chunk in image.chunks(256) {
// load count register
let word_count = u32::try_from(chunk.len() / 4)?;
let mut cmd = [4, 0, 0, 0, 0];
cmd[1..].copy_from_slice(&word_count.to_le_bytes());
do_cmd(port, &cmd).context("loading C")?;
let mut packet = vec![1];
packet.extend_from_slice(chunk);
// deposit the data.
do_cmd(port, &packet).context("sending PUT")?;
bar.inc(chunk.len() as u64);
}
bar.finish();
Ok(())
}

fn do_call(
address: u32,
mut port: Box<dyn SerialPort>,
then_echo: bool,
) -> Result<(), anyhow::Error> {
let mut cmd = [3, 0, 0, 0, 0];
cmd[1..].copy_from_slice(&address.to_le_bytes());
do_cmd(&mut port, &cmd).context("loading A")?;
do_cmd(&mut port, &[0]).context("sending CALL")?;
Ok(if then_echo {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
loop {
let mut b = [0];
match port.read_exact(&mut b) {
Ok(()) => {
write!(stdout, "{}", b[0] as char)?;
stdout.flush()?;
}
Err(e) if e.kind() == ErrorKind::TimedOut => {
// meh
}
other => other?,
}
}
})
}

fn do_cmd(port: &mut Box<dyn SerialPort>, cmd: &[u8]) -> Result<()> {
port.write_all(&cmd).context("writing command")?;
let mut response = [0; 1];
port.read_exact(&mut response).context("collecting response byte")?;
port.read_exact(&mut response)
.context("collecting response byte")?;
match response[0] {
0xAA => Ok(()),
0xFF => {
Expand All @@ -200,8 +222,7 @@ fn drain(port: &mut Box<dyn SerialPort>) -> Result<()> {
Err(e) if e.kind() == ErrorKind::TimedOut => {
break;
}
Err(e) => return Err(e)
.context("attempting to drain buffer"),
Err(e) => return Err(e).context("attempting to drain buffer"),
}
}
port.set_timeout(saved_timeout)
Expand All @@ -213,4 +234,3 @@ fn drain(port: &mut Box<dyn SerialPort>) -> Result<()> {

Ok(())
}

1 change: 1 addition & 0 deletions tinyboot/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target/
Loading