Files
tulflash/src/main.rs

389 lines
9.3 KiB
Rust

use std::ffi::OsString;
use std::io::{Cursor, Error, Write};
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use crossterm::event::{KeyCode, KeyModifiers};
use pbr::ProgressBar;
mod args;
use args::*;
mod ihex;
use ihex::{IntelHexWriter, IntelHexReader, DataRecord};
mod monitor;
use monitor::Monitor;
mod target;
use target::{PhysicalTarget, Target};
mod utils;
#[cfg(test)]
mod tests;
pub fn read_chip_into(writer: &mut impl Write, target: &mut impl Target, start: usize, end: usize) -> Result<(), Error> {
let mut pb = ProgressBar::new((end - start) as u64 + 1);
pb.message("reading chip flash ");
pb.set_units(pbr::Units::Bytes);
let mut last = start;
for i in (start..=end).step_by(1024).chain(std::iter::once(end + 1)) {
if i == last { continue }
let size = i - last;
let mut buf = [0u8; 1024];
target.read_flash(last as u16, &mut buf[..size])?;
writer.write_all(&buf[..size])?;
pb.add(size as u64);
last = i;
}
pb.finish_println("");
Ok(())
}
fn read_chip(args: ArgRead, target: &mut PhysicalTarget) -> Result<(), Error> {
let mut file = std::fs::File::create(args.path)?;
match args.bin {
true => read_chip_into(&mut file, target, args.start, args.end)?,
false => {
let mut writer = IntelHexWriter::init(file, args.start as u16);
read_chip_into(&mut writer, target, args.start, args.end)?;
writer.finish()?;
}
}
Ok(())
}
pub fn write_chip_chunk(data: &[u8], target: &mut impl Target, mut start: usize, verify: bool) -> Result<(), Error> {
let mut head = Vec::new();
if start & 0x7F != 0 {
head.resize(start & 0x7F, 0);
let mut pb = ProgressBar::new(head.len() as u64);
pb.message("reading chip flash ");
pb.set_units(pbr::Units::Bytes);
target.read_flash(start as u16 & !0x7F, &mut head)?;
pb.add(head.len() as u64);
pb.finish_println("");
}
let mut tail = Vec::new();
let end = start + data.len();
if end & 0x7F != 0 {
tail.resize(128 - (end & 0x7F), 0);
let mut pb = ProgressBar::new(tail.len() as u64);
pb.message("reading chip flash ");
pb.set_units(pbr::Units::Bytes);
target.read_flash(end as u16, &mut tail)?;
pb.add(tail.len() as u64);
pb.finish_println("");
}
let data = [ &head, data, &tail ].concat();
assert_eq!(data.len() & 0x7F, 0);
start = start & !0x7F;
if start + data.len() > 0x10000 {
Err(Error::other("Data does not fit into memory"))?
}
let mut page = start / 128;
let mut pb = ProgressBar::new(data.len() as u64);
pb.message("writing chip flash ");
pb.set_units(pbr::Units::Bytes);
for chunk in data.as_chunks::<128>().0.iter() {
target.write_flash(page as u16, chunk)?;
pb.add(chunk.len() as u64);
page += 1;
}
pb.finish_println("");
if verify {
let mut verify: Cursor<Vec<u8>> = Cursor::new(Vec::new());
read_chip_into(&mut verify, target, start, start + data.len() - 1)?;
if verify.get_ref() != &data {
Err(Error::other("Write mismatch"))?
}
}
Ok(())
}
fn write_chip_data(args: &ArgWrite, target: &mut PhysicalTarget) -> Result<(), Error> {
if args.bin {
let mut file = std::fs::read(&args.path)?;
let size = args.end - args.start + 1;
if size < file.len() {
file.resize(size, 0);
}
return write_chip_chunk(&file, target, args.start, !args.skip_verify)
}
let hexreader = IntelHexReader::init(std::fs::File::open(&args.path)?);
if args.write_individual {
let mut last_addr = 0;
let mut next_expected_addr = 0;
let mut last_data = Vec::new();
for record in hexreader {
let DataRecord { addr, mut data } = record?;
if addr != next_expected_addr {
if !last_data.is_empty() {
write_chip_chunk(&last_data, target, last_addr, !args.skip_verify)?;
last_data.clear();
}
last_addr = addr;
next_expected_addr = addr;
}
next_expected_addr += data.len();
last_data.append(&mut data);
}
if !last_data.is_empty() {
write_chip_chunk(&last_data, target, last_addr, !args.skip_verify)?;
}
return Ok(())
}
let mut first_addr = 0xFFFF;
let mut last_addr = 0;
let mut buf = vec![0u8; 0x10000];
for record in hexreader {
let DataRecord { addr, data } = record?;
if addr < first_addr {
first_addr = addr;
}
if data.len() + addr - 1 > last_addr {
last_addr = addr + data.len() - 1;
}
buf[addr..addr + data.len()].copy_from_slice(&data);
}
write_chip_chunk(&buf[first_addr..=last_addr], target, first_addr, !args.skip_verify)
}
fn write_chip(args: ArgWrite, target: &mut PhysicalTarget) -> Result<(), Error> {
loop {
if let Some(cmd) = args.cmd.as_ref() {
let cmd_path = &args.cmd_path;
let mut args = shlex::bytes::split(cmd.as_encoded_bytes())
.ok_or(Error::other("Error parsing commandline arguments"))?
.into_iter()
.map(|x| unsafe { OsString::from_encoded_bytes_unchecked(x) });
if let Some(cmd_path) = cmd_path {
if !cmd_path.is_dir() {
Err(Error::other("invalid cmd path (not a dir)"))?
}
}
let Some(name) = args.next() else {
Err(Error::other("No command provided"))?
};
let out = std::process::Command::new(name)
.current_dir(cmd_path.as_ref().map(|p| p.as_path()).unwrap_or(&std::path::Path::new(".")))
.args(args)
.output()?;
if !out.status.success() {
Err(Error::other(format!("Command failed with exit code {}:\n\n{}",
out.status.code().unwrap_or(1),
String::from_utf8_lossy(&out.stderr))))?
}
println!("Command `{}` successful.", cmd.to_string_lossy());
}
write_chip_data(&args, target)?;
if !args.monitor { break }
let mut mon = Monitor::new();
let reflash_handle = mon.add_hook(KeyCode::Char('f'), KeyModifiers::CONTROL, if args.cmd.is_none() { "Re-flash target" } else { "Re-run command and flash target" });
let cause = mon.run(target.port())?;
if cause != reflash_handle { break }
target.resync_target()?;
}
Ok(())
}
fn erase_chip(_args: ArgErase, target: &mut PhysicalTarget) -> Result<(), Error> {
target.erase()?;
Ok(())
}
fn monitor_chip(_args: ArgMonitor, target: &mut PhysicalTarget) -> Result<(), Error> {
let _ = Monitor::new().run(target.port())?;
Ok(())
}
fn run_chip(args: ArgRun, target: &mut PhysicalTarget) -> Result<(), Error> {
let build_dir = args.path.parent().unwrap().join("tulflash");
let source_file_name = args.path.file_name().ok_or(Error::other("invalid path to compilation source"))?;
let source_file = build_dir.join(&source_file_name);
let output_file = source_file.with_extension("ihx");
let cmd = Some(OsString::from(format!("sdcc \"{}\"", source_file_name.to_string_lossy())));
let cmd_path = Some(build_dir.clone());
std::fs::create_dir_all(build_dir)?;
std::fs::copy(args.path, &source_file)?;
write_chip(ArgWrite {
path: output_file.into_os_string(),
cmd_path,
cmd,
start: 0,
end: 65535,
bin: false,
skip_verify: false,
write_individual: false,
monitor: true
}, target)
}
fn gen_example(args: ArgExample) -> Result<(), Error> {
let path = PathBuf::from(args.path.unwrap_or(OsString::from("example.c")));
if path.try_exists()? {
Err(Error::other("File already exists"))?
}
std::fs::write(&path, include_bytes!("../example.c"))?;
let path = path.to_string_lossy();
println!("Example source code generated to `{}`.", path);
println!("Execute `tulflash run \"{}\"` to run it.", path);
Ok(())
}
fn main() {
let args = args::parse();
if let ArgCommand::Example(args) = args.command {
if let Err(err) = gen_example(args) {
println!("Error generating example source code: {}", err);
}
return
}
let start = SystemTime::now();
let port = args.port.or_else(|| {
let ports = serial_enumerator::get_serial_list();
// First try TUL (works on Linux and macOS)...
for port in &ports {
if let Some(vendor) = &port.vendor && vendor.as_str() == "TUL" {
return Some(port.name.clone().into())
}
}
// ...then try FTDI (which Windows overrides)
for port in &ports {
if let Some(vendor) = &port.vendor && vendor.as_str() == "FTDI" {
return Some(port.name.clone().into())
}
}
None
});
let Some(port) = port else {
println!("Could not find any compatible serial port. Please make sure that the target is connected.");
println!("If the issue persists, please specify the port manually with the `--port ...` argument.");
println!();
let ports = serial_enumerator::get_serial_list();
if ports.is_empty() {
println!("No usable ports detected.");
}
if !ports.is_empty() {
println!("Possible ports:");
for port in serial_enumerator::get_serial_list() {
println!(" - {} ({}, {})",
port.name,
port.vendor.unwrap_or_else(|| "unknown vendor".to_string()),
port.product.unwrap_or_else(|| "unknown product".to_string()));
}
}
std::process::exit(1);
};
let mut target = PhysicalTarget::init(port, if args.slow { 57600 } else { 312500 }).unwrap();
let res = match args.command {
ArgCommand::Read(args) => read_chip(args, &mut target),
ArgCommand::Write(args) => write_chip(args, &mut target),
ArgCommand::Erase(args) => erase_chip(args, &mut target),
ArgCommand::Monitor(args) => monitor_chip(args, &mut target),
ArgCommand::Run(args) => run_chip(args, &mut target),
ArgCommand::Example(_) => unreachable!()
};
if let Err(err) = res {
println!("An error occured during the operation: {}", err);
std::process::exit(1);
}
let end = SystemTime::now();
let dur = end.duration_since(start).unwrap_or(Duration::ZERO);
println!("Finished operation in {} ms.", dur.as_millis());
}