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> = 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()); }