389 lines
9.3 KiB
Rust
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());
|
|
}
|
|
|