Compare commits
2 Commits
8a62303718
...
2de6345f96
Author | SHA1 | Date | |
---|---|---|---|
2de6345f96 | |||
b07c8cd8c8 |
24
Cargo.lock
generated
24
Cargo.lock
generated
@ -156,7 +156,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.38",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -324,9 +324,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.149"
|
version = "0.2.150"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
@ -565,9 +565,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.38"
|
version = "2.0.39"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -795,29 +795,29 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.5.18"
|
version = "0.5.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "176b6138793677221d420fd2f0aeeced263f197688b36484660da767bca2fa32"
|
checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.24"
|
version = "0.7.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "092cd76b01a033a9965b9097da258689d9e17c69ded5dcf41bca001dd20ebc6d"
|
checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.7.24"
|
version = "0.7.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a13a20a7c6a90e2034bcc65495799da92efcec6a8dd4f3fcb6f7a48988637ead"
|
checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.38",
|
"syn 2.0.39",
|
||||||
]
|
]
|
||||||
|
116
src/file.rs
116
src/file.rs
@ -1,14 +1,10 @@
|
|||||||
extern crate object;
|
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::iter::repeat;
|
use std::iter::repeat;
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use log::debug;
|
use log::{debug, warn};
|
||||||
use log::warn;
|
|
||||||
use object::elf;
|
use object::elf;
|
||||||
use object::Endianness;
|
use object::Endianness;
|
||||||
|
|
||||||
@ -23,13 +19,20 @@ pub enum MemoryFileType {
|
|||||||
Bin,
|
Bin,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct MemorySection {
|
||||||
|
pub address: u32,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
const M33_FLAGS: u32 = elf::EF_ARM_EABI_VER5 | elf::EF_ARM_ABI_FLOAT_HARD; // processor has hard float, even if code may not use it
|
const M33_FLAGS: u32 = elf::EF_ARM_EABI_VER5 | elf::EF_ARM_ABI_FLOAT_HARD; // processor has hard float, even if code may not use it
|
||||||
|
|
||||||
struct ElfWriter;
|
struct ElfWriter;
|
||||||
struct IhexWriter;
|
struct IhexWriter;
|
||||||
struct BinWriter;
|
struct BinWriter;
|
||||||
|
|
||||||
struct ElfReader;
|
struct ElfReader {
|
||||||
|
file_contents: Vec<u8>,
|
||||||
|
}
|
||||||
struct IhexReader {
|
struct IhexReader {
|
||||||
file_contents: String,
|
file_contents: String,
|
||||||
}
|
}
|
||||||
@ -50,11 +53,13 @@ impl MemoryFileType {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn object_file_reader(
|
pub fn object_file_reader_auto(
|
||||||
filename: &PathBuf,
|
filename: &PathBuf,
|
||||||
) -> Result<Box<dyn ObjectFileReader>, std::io::Error> {
|
) -> Result<Box<dyn ObjectFileReader>, std::io::Error> {
|
||||||
match MemoryFileType::type_from_filename(filename.to_str().unwrap_or("")) {
|
match MemoryFileType::type_from_filename(filename.to_str().unwrap_or("")) {
|
||||||
Some(MemoryFileType::Elf) => unimplemented!(),
|
Some(MemoryFileType::Elf) => {
|
||||||
|
unimplemented!("Sorry ELF reading support is coming later")
|
||||||
|
}
|
||||||
Some(MemoryFileType::Ihex) => IhexReader::new(filename),
|
Some(MemoryFileType::Ihex) => IhexReader::new(filename),
|
||||||
Some(MemoryFileType::Bin) => BinReader::new(filename),
|
Some(MemoryFileType::Bin) => BinReader::new(filename),
|
||||||
None => {
|
None => {
|
||||||
@ -63,6 +68,16 @@ impl MemoryFileType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn object_file_reader(
|
||||||
|
&self,
|
||||||
|
filename: &PathBuf,
|
||||||
|
) -> Result<Box<dyn ObjectFileReader>, std::io::Error> {
|
||||||
|
Ok(match *self {
|
||||||
|
MemoryFileType::Elf => unimplemented!("Sorry ELF reading support is coming later"),
|
||||||
|
MemoryFileType::Ihex => IhexReader::new(filename)?,
|
||||||
|
MemoryFileType::Bin => BinReader::new(filename)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
pub fn mem_writer(&self) -> Box<dyn MemoryWriter> {
|
pub fn mem_writer(&self) -> Box<dyn MemoryWriter> {
|
||||||
match *self {
|
match *self {
|
||||||
MemoryFileType::Elf => Box::new(ElfWriter),
|
MemoryFileType::Elf => Box::new(ElfWriter),
|
||||||
@ -91,7 +106,7 @@ pub trait ObjectFileReader {
|
|||||||
/// Return the base address and entire data of `text` sections in the object file,
|
/// Return the base address and entire data of `text` sections in the object file,
|
||||||
/// copied into a Vec<u8>. This will allocate the necessary space for the entire memory
|
/// copied into a Vec<u8>. This will allocate the necessary space for the entire memory
|
||||||
/// map of the `text` sections, which in some cases is probably a horrible idea.
|
/// map of the `text` sections, which in some cases is probably a horrible idea.
|
||||||
fn read_all(&mut self, size: Option<u32>) -> Result<Vec<u8>, Box<dyn std::error::Error>>;
|
fn read_all(&mut self, size: Option<u32>) -> Result<MemorySection, Box<dyn std::error::Error>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An object that can write a block of memory to a file
|
/// An object that can write a block of memory to a file
|
||||||
@ -245,13 +260,16 @@ impl ObjectFileReader for BinReader {
|
|||||||
Ok(Box::new(BinReader { file, filesize }))
|
Ok(Box::new(BinReader { file, filesize }))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_all(&mut self, size: Option<u32>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
fn read_all(&mut self, size: Option<u32>) -> Result<MemorySection, Box<dyn std::error::Error>> {
|
||||||
// The casting here is a bit janky...
|
// The casting here is a bit janky...
|
||||||
let size = std::cmp::min(size.unwrap_or(self.filesize as u32) as usize, self.filesize);
|
let size = std::cmp::min(size.unwrap_or(self.filesize as u32) as usize, self.filesize);
|
||||||
let mut buf = Vec::from_iter(std::iter::repeat(0).take(size));
|
let mut buf = Vec::from_iter(std::iter::repeat(0).take(size));
|
||||||
self.file.read_exact(&mut buf)?;
|
self.file.read_exact(&mut buf)?;
|
||||||
|
|
||||||
Ok(buf)
|
Ok(MemorySection {
|
||||||
|
data: buf,
|
||||||
|
address: 0, // Bin file has no address information
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,10 +283,12 @@ impl ObjectFileReader for IhexReader {
|
|||||||
|
|
||||||
Ok(Box::new(IhexReader { file_contents }))
|
Ok(Box::new(IhexReader { file_contents }))
|
||||||
}
|
}
|
||||||
fn read_all(&mut self, size: Option<u32>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
fn read_all(&mut self, size: Option<u32>) -> Result<MemorySection, Box<dyn std::error::Error>> {
|
||||||
let reader = ihex::Reader::new(&self.file_contents);
|
let reader = ihex::Reader::new(&self.file_contents);
|
||||||
|
let mut file_base = None;
|
||||||
let mut cur_base = 0;
|
let mut cur_base = 0;
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
|
|
||||||
for rec in reader {
|
for rec in reader {
|
||||||
if let Ok(record) = rec {
|
if let Ok(record) = rec {
|
||||||
match record {
|
match record {
|
||||||
@ -285,11 +305,27 @@ impl ObjectFileReader for IhexReader {
|
|||||||
|
|
||||||
ihex::Record::Data { offset, mut value } => {
|
ihex::Record::Data { offset, mut value } => {
|
||||||
let record_base = cur_base + (offset as u32);
|
let record_base = cur_base + (offset as u32);
|
||||||
|
// File base comes from the address of the first record
|
||||||
|
if file_base.is_none() {
|
||||||
|
file_base = Some(record_base);
|
||||||
|
}
|
||||||
if (data.len() as u32) < record_base {
|
if (data.len() as u32) < record_base {
|
||||||
debug!("Extending vec by {} to take up slack space", (record_base as usize) - data.len());
|
// We're going to coalesce disjoint 'sections' in Ihex files, because they don't support 'real' sections to identify
|
||||||
|
// what should or should not be programmed
|
||||||
|
debug!(
|
||||||
|
"Extending vec by {} to take up slack space",
|
||||||
|
(record_base as usize) - data.len()
|
||||||
|
);
|
||||||
data.extend(repeat(0xff).take((record_base as usize) - data.len()));
|
data.extend(repeat(0xff).take((record_base as usize) - data.len()));
|
||||||
}
|
}
|
||||||
data.append(&mut value);
|
data.append(&mut value);
|
||||||
|
if size.is_some() && data.len() >= size.unwrap() as usize {
|
||||||
|
data.truncate(size.unwrap() as usize);
|
||||||
|
return Ok(MemorySection {
|
||||||
|
data,
|
||||||
|
address: file_base.unwrap_or(0),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ihex::Record::EndOfFile => continue,
|
ihex::Record::EndOfFile => continue,
|
||||||
_ => unimplemented!(
|
_ => unimplemented!(
|
||||||
@ -306,6 +342,58 @@ impl ObjectFileReader for IhexReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(data)
|
Ok(MemorySection {
|
||||||
|
data,
|
||||||
|
address: file_base.unwrap_or(0),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: This is getting a bit hairy to figure out which sections to include, so forget about it for now.
|
||||||
|
|
||||||
|
// impl ObjectFileReader for ElfReader {
|
||||||
|
// fn new(file: &PathBuf) -> Result<Box<dyn ObjectFileReader>, std::io::Error>
|
||||||
|
// where
|
||||||
|
// Self: Sized,
|
||||||
|
// {
|
||||||
|
// let file_contents = std::fs::read(file)?;
|
||||||
|
// Ok(Box::new(ElfReader { file_contents }))
|
||||||
|
// }
|
||||||
|
// fn read_all(&mut self, size: Option<u32>) -> Result<MemorySection, Box<dyn std::error::Error>> {
|
||||||
|
// let object = object::File::parse(&*self.file_contents).unwrap();
|
||||||
|
// let data = Vec::new();
|
||||||
|
// let file_base = None;
|
||||||
|
|
||||||
|
// debug!(
|
||||||
|
// "Opened object file, found sections: {:?}",
|
||||||
|
// object
|
||||||
|
// .sections()
|
||||||
|
// .map(|x| x.name().unwrap().to_owned())
|
||||||
|
// .collect::<Vec<String>>()
|
||||||
|
// );
|
||||||
|
|
||||||
|
// for sec in object.sections() {
|
||||||
|
// if let SectionFlags::Elf{ sh_flags: flags } = sec.flags() {
|
||||||
|
// if flags & elf::SHT_PROGBITS {
|
||||||
|
// if file_base.is_none() {
|
||||||
|
// file_base = Some(sec.address());
|
||||||
|
// } else if sec.address() < file_base.unwrap() {
|
||||||
|
// // Make space at the beginning of data
|
||||||
|
// data.
|
||||||
|
// file_base = Some(sec.address());
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Ok(MemorySection {
|
||||||
|
// data: section
|
||||||
|
// .data_range(section.address(), size)?
|
||||||
|
// .ok_or("section is empty or smaller than given size")?
|
||||||
|
// .to_vec(),
|
||||||
|
// address: section.address() as u32,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
162
src/main.rs
162
src/main.rs
@ -1,5 +1,7 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::{ffi::CString, fs::File, io::BufWriter, os::unix::prelude::OsStrExt, path::PathBuf};
|
use std::{
|
||||||
|
ffi::CString, fmt::Write, fs::File, io::BufWriter, os::unix::prelude::OsStrExt, path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Error};
|
use anyhow::{anyhow, Context, Error};
|
||||||
use clap_num::maybe_hex;
|
use clap_num::maybe_hex;
|
||||||
@ -102,7 +104,7 @@ enum ObjectFileType {
|
|||||||
Elf,
|
Elf,
|
||||||
Ihex,
|
Ihex,
|
||||||
Bin,
|
Bin,
|
||||||
/// Derive file type from file name
|
/// Derive file type from file name (not magic)
|
||||||
Auto,
|
Auto,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +114,7 @@ enum WriteEraseMethod {
|
|||||||
All,
|
All,
|
||||||
/// Erase only the area to be written
|
/// Erase only the area to be written
|
||||||
Region,
|
Region,
|
||||||
/// Don't do an erase. Memory to be written must be erased already if it's flash.
|
/// Don't erase (assume memory is erased)
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,14 +129,17 @@ impl ObjectFileType {
|
|||||||
}
|
}
|
||||||
fn file_reader(&self, filename: &PathBuf) -> Result<Box<dyn ObjectFileReader>, std::io::Error> {
|
fn file_reader(&self, filename: &PathBuf) -> Result<Box<dyn ObjectFileReader>, std::io::Error> {
|
||||||
match self {
|
match self {
|
||||||
Self::Auto => MemoryFileType::object_file_reader(filename),
|
Self::Auto => MemoryFileType::object_file_reader_auto(filename),
|
||||||
_ => unimplemented!(),
|
Self::Bin => MemoryFileType::Bin.object_file_reader(filename),
|
||||||
|
Self::Ihex => MemoryFileType::Ihex.object_file_reader(filename),
|
||||||
|
Self::Elf => MemoryFileType::Elf.object_file_reader(filename),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
struct ReadArgs {
|
struct ReadArgs {
|
||||||
|
/// Object file to write
|
||||||
file: PathBuf,
|
file: PathBuf,
|
||||||
#[arg(short = 't', long = "type", value_enum, default_value_t = ObjectFileType::Auto)]
|
#[arg(short = 't', long = "type", value_enum, default_value_t = ObjectFileType::Auto)]
|
||||||
/// Type of object file to generate
|
/// Type of object file to generate
|
||||||
@ -143,9 +148,11 @@ struct ReadArgs {
|
|||||||
/// USB device to act on
|
/// USB device to act on
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
devspec: UsbDeviceSpecifier,
|
devspec: UsbDeviceSpecifier,
|
||||||
|
|
||||||
/// Base memory address on microcontroller (defaults to start of flash)
|
/// Base memory address on microcontroller (defaults to start of flash)
|
||||||
#[arg(short = 'a', long = "base-address", value_parser=maybe_hex::<u32>)]
|
#[arg(short = 'a', long = "base-address", value_parser=maybe_hex::<u32>)]
|
||||||
addr: Option<u32>,
|
addr: Option<u32>,
|
||||||
|
|
||||||
/// Size to read in bytes (defaults to size of flash)
|
/// Size to read in bytes (defaults to size of flash)
|
||||||
#[arg(short, long, value_parser=maybe_hex::<u32>)]
|
#[arg(short, long, value_parser=maybe_hex::<u32>)]
|
||||||
size: Option<u32>,
|
size: Option<u32>,
|
||||||
@ -153,6 +160,7 @@ struct ReadArgs {
|
|||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
struct WriteArgs {
|
struct WriteArgs {
|
||||||
|
/// Object file to read
|
||||||
file: PathBuf,
|
file: PathBuf,
|
||||||
#[arg(short = 't', long = "type", value_enum, default_value_t = ObjectFileType::Auto)]
|
#[arg(short = 't', long = "type", value_enum, default_value_t = ObjectFileType::Auto)]
|
||||||
filetype: ObjectFileType,
|
filetype: ObjectFileType,
|
||||||
@ -160,17 +168,21 @@ struct WriteArgs {
|
|||||||
/// USB device to act on
|
/// USB device to act on
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
devspec: UsbDeviceSpecifier,
|
devspec: UsbDeviceSpecifier,
|
||||||
|
|
||||||
/// Base memory address on microcontroller to write to (defaults to start of flash)
|
/// Base memory address on microcontroller to write to (defaults to start of flash)
|
||||||
#[arg(short = 'a', long = "write-address", value_parser=maybe_hex::<u32>)]
|
#[arg(short = 'a', long = "write-address", value_parser=maybe_hex::<u32>)]
|
||||||
addr: Option<u32>,
|
addr: Option<u32>,
|
||||||
|
|
||||||
/// Size to write in bytes (defaults to size of flash)
|
/// Size to write in bytes (defaults to size of flash)
|
||||||
#[arg(short, long, value_parser=maybe_hex::<u32>)]
|
#[arg(short, long, value_parser=maybe_hex::<u32>)]
|
||||||
size: Option<u32>,
|
size: Option<u32>,
|
||||||
|
|
||||||
/// Don't reset the microcontroller after writing
|
/// Don't reset the microcontroller after writing
|
||||||
#[arg(short, long, action=ArgAction::SetFalse)]
|
#[arg(short, long, action=ArgAction::SetFalse)]
|
||||||
no_reset: bool,
|
no_reset: bool,
|
||||||
#[arg(short, long, default_value = "region")]
|
|
||||||
/// Erase method to use before writing
|
/// Erase method to use before writing
|
||||||
|
#[arg(short, long, default_value = "region")]
|
||||||
erase: WriteEraseMethod,
|
erase: WriteEraseMethod,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +206,7 @@ struct EraseArgs {
|
|||||||
/// Address to start erasing at
|
/// Address to start erasing at
|
||||||
#[arg(short = 'a', long="base-address", value_parser=maybe_hex::<u32>, conflicts_with="erase_all")]
|
#[arg(short = 'a', long="base-address", value_parser=maybe_hex::<u32>, conflicts_with="erase_all")]
|
||||||
addr: Option<u32>,
|
addr: Option<u32>,
|
||||||
|
|
||||||
/// Number of bytes to erase
|
/// Number of bytes to erase
|
||||||
#[arg(short, long, value_parser=maybe_hex::<u32>, conflicts_with="erase_all")]
|
#[arg(short, long, value_parser=maybe_hex::<u32>, conflicts_with="erase_all")]
|
||||||
size: Option<u32>,
|
size: Option<u32>,
|
||||||
@ -210,6 +223,9 @@ enum Commands {
|
|||||||
/// Read microcontroller memory to a file
|
/// Read microcontroller memory to a file
|
||||||
Read(ReadArgs),
|
Read(ReadArgs),
|
||||||
/// Write a file to microcontroller memory
|
/// Write a file to microcontroller memory
|
||||||
|
///
|
||||||
|
/// Read the object file, ignoring address mapping and relocation, and write its
|
||||||
|
/// read-only sections to the given address (or flash)
|
||||||
Write(WriteArgs),
|
Write(WriteArgs),
|
||||||
/// Erase the microcontroller's flash
|
/// Erase the microcontroller's flash
|
||||||
Erase(EraseArgs),
|
Erase(EraseArgs),
|
||||||
@ -227,9 +243,35 @@ fn read_write_style() -> ProgressStyle {
|
|||||||
.progress_chars("##-")
|
.progress_chars("##-")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn do_erase(
|
||||||
|
isp: &protocol::UsbIsp,
|
||||||
|
all: bool,
|
||||||
|
start_addr: Option<u32>,
|
||||||
|
size: Option<u32>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let erase_pb = ProgressBar::new(size.unwrap_or(0).into())
|
||||||
|
.with_style(read_write_style())
|
||||||
|
.with_prefix("Erasing flash");
|
||||||
|
if all {
|
||||||
|
erase_pb.set_length(isp.GetFlashSizeInBytes()?.into());
|
||||||
|
isp.flash_erase_all(None)?;
|
||||||
|
erase_pb.finish();
|
||||||
|
} else {
|
||||||
|
let flash_start = isp.GetFlashStartAddress()?;
|
||||||
|
let flash_size = isp.GetFlashSizeInBytes()?;
|
||||||
|
let start_addr = start_addr.unwrap_or(flash_start);
|
||||||
|
let size = size.unwrap_or(flash_size);
|
||||||
|
if start_addr + size > flash_start + flash_size {
|
||||||
|
warn!("Looks like you're attempting to erase beyond the end of flash (0x{:x}-0x{:x})! This will probably fail.", start_addr, start_addr+size);
|
||||||
|
}
|
||||||
|
isp.flash_erase_region(None, start_addr, size)?;
|
||||||
|
erase_pb.finish();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn write_file_to_flash(args: &WriteArgs) -> Result<(), Error> {
|
fn write_file_to_flash(args: &WriteArgs) -> Result<(), Error> {
|
||||||
let api = HidApi::new()?;
|
let isp = connect_device(&args.devspec)?;
|
||||||
let isp = connect_device(&api, &args.devspec)?;
|
|
||||||
|
|
||||||
let mut infile = args
|
let mut infile = args
|
||||||
.filetype
|
.filetype
|
||||||
@ -240,41 +282,57 @@ fn write_file_to_flash(args: &WriteArgs) -> Result<(), Error> {
|
|||||||
.read_all(None)
|
.read_all(None)
|
||||||
.or_else(|e| Err(anyhow!("Unable to read file contents: {}", e)))?;
|
.or_else(|e| Err(anyhow!("Unable to read file contents: {}", e)))?;
|
||||||
|
|
||||||
println!("Input file first section contents:");
|
debug!(
|
||||||
for (i, line) in contents.chunks(16).enumerate() {
|
"Input file read (origin 0x{:08x} size 0x{:08x}):",
|
||||||
print!("{:08x} ", i * 16);
|
contents.address,
|
||||||
for block in line.chunks(8) {
|
contents.data.len()
|
||||||
for s in block.iter().map(|x| format!("{:02x} ", x)) {
|
);
|
||||||
print!("{}", s);
|
|
||||||
}
|
|
||||||
print!(" ");
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
|
|
||||||
let flash_start = args.addr.unwrap_or(isp.GetFlashStartAddress()?);
|
let flash_start = args.addr.unwrap_or(isp.GetFlashStartAddress()?);
|
||||||
let flash_size = args.size.unwrap_or(isp.GetFlashSizeInBytes()?);
|
let flash_size = args.size.unwrap_or(contents.data.len().try_into()?);
|
||||||
|
|
||||||
// if !args.no_reset {
|
debug!(
|
||||||
// isp.reset()?;
|
"Will start write/erase at 0x{:08x} for 0x{:08x} bytes",
|
||||||
// }
|
flash_start, flash_size
|
||||||
|
);
|
||||||
|
|
||||||
|
match args.erase {
|
||||||
|
WriteEraseMethod::All => {
|
||||||
|
do_erase(&isp, true, None, None).with_context(|| "Erasing all flash")?
|
||||||
|
}
|
||||||
|
WriteEraseMethod::Region => do_erase(&isp, false, Some(flash_start), Some(flash_size))
|
||||||
|
.with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Erasing flash region 0x{:08x} size 0x{:08x}",
|
||||||
|
flash_start, flash_size
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
WriteEraseMethod::None => (),
|
||||||
|
}
|
||||||
|
debug!(
|
||||||
|
"Beginning flash write at 0x{:08x} for 0x{:08x}",
|
||||||
|
flash_start, flash_size
|
||||||
|
);
|
||||||
|
let mut data_to_write = &contents.data[..flash_size as usize];
|
||||||
|
isp.WriteMemory(
|
||||||
|
&mut data_to_write,
|
||||||
|
flash_start,
|
||||||
|
flash_size,
|
||||||
|
Some(protocol::MemoryId::Internal),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if !args.no_reset {
|
||||||
|
isp.reset()?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_flash_to_file(args: &ReadArgs) -> Result<(), Error> {
|
fn read_flash_to_file(args: &ReadArgs) -> Result<(), Error> {
|
||||||
let api = hidapi::HidApi::new()?; // is this free, or should it be passed?
|
let isp = connect_device(&args.devspec)?;
|
||||||
let isp = connect_device(&api, &args.devspec)?;
|
|
||||||
|
|
||||||
let flash_start = match args.addr {
|
let flash_start = args.addr.unwrap_or(isp.GetFlashStartAddress()?);
|
||||||
None => isp.GetFlashStartAddress()?,
|
let flash_size = args.size.unwrap_or(isp.GetFlashSizeInBytes()?);
|
||||||
Some(addr) => addr,
|
|
||||||
};
|
|
||||||
|
|
||||||
let flash_size = match args.size {
|
|
||||||
None => isp.GetFlashSizeInBytes()?,
|
|
||||||
Some(size) => size,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(flash_size as usize);
|
let mut buf = Vec::with_capacity(flash_size as usize);
|
||||||
|
|
||||||
@ -319,32 +377,12 @@ fn read_flash_to_file(args: &ReadArgs) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn erase_flash(args: &EraseArgs) -> Result<(), Error> {
|
fn erase_flash(args: &EraseArgs) -> Result<(), Error> {
|
||||||
let api = hidapi::HidApi::new()?;
|
let isp = connect_device(&args.devspec)?;
|
||||||
let isp = connect_device(&api, &args.devspec)?;
|
do_erase(&isp, args.erase_all, args.addr, args.size)
|
||||||
|
|
||||||
let erase_pb = ProgressBar::new(1)
|
|
||||||
.with_style(read_write_style())
|
|
||||||
.with_prefix("Erasing flash");
|
|
||||||
if args.erase_all {
|
|
||||||
isp.flash_erase_all(None)?;
|
|
||||||
erase_pb.finish();
|
|
||||||
} else {
|
|
||||||
let flash_start = isp.GetFlashStartAddress()?;
|
|
||||||
let flash_size = isp.GetFlashSizeInBytes()?;
|
|
||||||
let start_addr = args.addr.unwrap_or(flash_start);
|
|
||||||
let size = args.addr.unwrap_or(flash_size);
|
|
||||||
if start_addr + size > flash_start + flash_size {
|
|
||||||
warn!("Looks like you're attempting to erase beyond the end of flash (0x{:x}-0x{:x})! This will probably fail.", start_addr, start_addr+size);
|
|
||||||
}
|
|
||||||
isp.flash_erase_region(None, start_addr, size)?;
|
|
||||||
erase_pb.finish();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(args: &ResetArgs) -> Result<(), Error> {
|
fn reset(args: &ResetArgs) -> Result<(), Error> {
|
||||||
let api = hidapi::HidApi::new()?;
|
let isp = connect_device(&args.devspec)?;
|
||||||
let isp = connect_device(&api, &args.devspec)?;
|
|
||||||
|
|
||||||
isp.reset()
|
isp.reset()
|
||||||
}
|
}
|
||||||
@ -375,11 +413,9 @@ fn get_matching_devices<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connect_device(
|
fn connect_device(devspec: &UsbDeviceSpecifier) -> Result<protocol::UsbIsp, Error> {
|
||||||
api: &hidapi::HidApi,
|
let api = HidApi::new()?;
|
||||||
devspec: &UsbDeviceSpecifier,
|
let matches = get_matching_devices(&api, devspec)?;
|
||||||
) -> Result<protocol::UsbIsp, Error> {
|
|
||||||
let matches = get_matching_devices(api, devspec)?;
|
|
||||||
if matches.len() < 1 {
|
if matches.len() < 1 {
|
||||||
return Err(NoDevicesError {}.into());
|
return Err(NoDevicesError {}.into());
|
||||||
} else if matches.len() > 1 {
|
} else if matches.len() > 1 {
|
||||||
@ -391,7 +427,7 @@ fn connect_device(
|
|||||||
matches[0].product_string().unwrap_or("device"),
|
matches[0].product_string().unwrap_or("device"),
|
||||||
matches[0].path().to_str()?
|
matches[0].path().to_str()?
|
||||||
);
|
);
|
||||||
let dev = matches[0].open_device(api)?;
|
let dev = matches[0].open_device(&api)?;
|
||||||
let proto = protocol::UsbIsp::new(dev);
|
let proto = protocol::UsbIsp::new(dev);
|
||||||
debug!("Connected to device {}", proto.GetUniqueDeviceId()?);
|
debug!("Connected to device {}", proto.GetUniqueDeviceId()?);
|
||||||
Ok(proto)
|
Ok(proto)
|
||||||
|
112
src/protocol.rs
112
src/protocol.rs
@ -6,7 +6,7 @@ use hidapi::HidDevice;
|
|||||||
use log::debug;
|
use log::debug;
|
||||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
use std::io::Write;
|
use std::io::{Read, Write};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -17,6 +17,9 @@ use crate::packet::{
|
|||||||
};
|
};
|
||||||
use crate::packet::{CommandFlags, CommandPacket, CommandTag};
|
use crate::packet::{CommandFlags, CommandPacket, CommandTag};
|
||||||
|
|
||||||
|
const DATA_PACKET_MAX: usize = 56; //TODO: Need to read this from the chip
|
||||||
|
const READ_TIMEOUT: i32 = 1000;
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(IntoPrimitive, EnumIter, Debug, PartialEq, Clone)]
|
#[derive(IntoPrimitive, EnumIter, Debug, PartialEq, Clone)]
|
||||||
pub enum MemoryId {
|
pub enum MemoryId {
|
||||||
@ -309,6 +312,7 @@ pub struct UsbIsp {
|
|||||||
|
|
||||||
impl UsbIsp {
|
impl UsbIsp {
|
||||||
pub fn new(device: HidDevice) -> Self {
|
pub fn new(device: HidDevice) -> Self {
|
||||||
|
device.set_blocking_mode(false).unwrap();
|
||||||
Self { device }
|
Self { device }
|
||||||
}
|
}
|
||||||
pub fn send_command(&self, p: CommandPacket) -> Result<ResponsePacket, Error> {
|
pub fn send_command(&self, p: CommandPacket) -> Result<ResponsePacket, Error> {
|
||||||
@ -316,23 +320,31 @@ impl UsbIsp {
|
|||||||
self.write_packet(Packet::CommandPacket(p))
|
self.write_packet(Packet::CommandPacket(p))
|
||||||
}
|
}
|
||||||
pub fn write_packet(&self, p: Packet) -> Result<ResponsePacket, Error> {
|
pub fn write_packet(&self, p: Packet) -> Result<ResponsePacket, Error> {
|
||||||
|
let mut expecting_response = true;
|
||||||
|
debug!("Writing packet {:?}", &p);
|
||||||
let usb_packet = match p {
|
let usb_packet = match p {
|
||||||
Packet::CommandPacket(p) => {
|
Packet::CommandPacket(p) => {
|
||||||
UsbPacket::new(packet::ReportId::CommandOut, Packet::CommandPacket(p))
|
UsbPacket::new(packet::ReportId::CommandOut, Packet::CommandPacket(p))
|
||||||
}
|
}
|
||||||
Packet::ResponsePacket(_) => panic!("We should not be writing responses"),
|
Packet::ResponsePacket(_) => panic!("We should not be writing responses"),
|
||||||
Packet::DataPacket(p) => {
|
Packet::DataPacket(p) => {
|
||||||
|
expecting_response = false;
|
||||||
UsbPacket::new(packet::ReportId::DataOut, Packet::DataPacket(p))
|
UsbPacket::new(packet::ReportId::DataOut, Packet::DataPacket(p))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut buf = Vec::with_capacity(usb_packet.length());
|
|
||||||
usb_packet.write(&mut buf)?;
|
|
||||||
|
|
||||||
//FIXME: don't expect a response on data packets
|
let mut buf = Vec::with_capacity(usb_packet.length());
|
||||||
|
usb_packet.write(&mut buf)?; // Write packet to buffer
|
||||||
|
debug!("raw packet: {:x?}", buf);
|
||||||
let _ = self.device.write(&buf)?;
|
let _ = self.device.write(&buf)?;
|
||||||
|
|
||||||
|
let resp_timeout = if expecting_response { READ_TIMEOUT } else { 0 };
|
||||||
|
|
||||||
let mut rbuf = [0u8; 64];
|
let mut rbuf = [0u8; 64];
|
||||||
let res_size = self.device.read(&mut rbuf[..])?;
|
let res_size = self.device.read_timeout(&mut rbuf[..], resp_timeout)?;
|
||||||
|
if res_size > 0 {
|
||||||
let parsed = packet::usb_packet(&rbuf[..res_size]).unwrap(); //TODO: handle more gracefully, but requires ownership of I
|
let parsed = packet::usb_packet(&rbuf[..res_size]).unwrap(); //TODO: handle more gracefully, but requires ownership of I
|
||||||
|
debug!("Got response {:?}", parsed);
|
||||||
|
|
||||||
match parsed.1 {
|
match parsed.1 {
|
||||||
Packet::ResponsePacket(p) => Ok(p),
|
Packet::ResponsePacket(p) => Ok(p),
|
||||||
@ -341,13 +353,27 @@ impl UsbIsp {
|
|||||||
Err(anyhow!("Data IN packets not expected in reply to commands"))
|
Err(anyhow!("Data IN packets not expected in reply to commands"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if !expecting_response {
|
||||||
|
Ok(ResponsePacket {
|
||||||
|
flags: CommandFlags::empty(),
|
||||||
|
tag: packet::ResponseTag::GenericResponse,
|
||||||
|
reserved: 0,
|
||||||
|
param_count: 2,
|
||||||
|
params: ResponseParameters::GenericResponse(packet::GenericResponseParams {
|
||||||
|
status: StatusCode::Success,
|
||||||
|
command: CommandTag::WriteMemory,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Expected a response and didn't get one"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_data(&self, buf: &mut impl Write, byte_count: usize) -> Result<(), Error> {
|
pub fn read_data(&self, buf: &mut impl Write, byte_count: usize) -> Result<(), Error> {
|
||||||
let mut rbuf = [0u8; 64];
|
let mut rbuf = [0u8; 64];
|
||||||
let mut read = 0;
|
let mut read = 0;
|
||||||
while read < byte_count {
|
while read < byte_count {
|
||||||
let res_size = self.device.read(&mut rbuf[..])?;
|
let res_size = self.device.read_timeout(&mut rbuf[..], READ_TIMEOUT)?;
|
||||||
let parsed = packet::usb_packet(&rbuf[..res_size]).unwrap(); //TODO: handle more gracefully, but requires ownership of I
|
let parsed = packet::usb_packet(&rbuf[..res_size]).unwrap(); //TODO: handle more gracefully, but requires ownership of I
|
||||||
match parsed.1 {
|
match parsed.1 {
|
||||||
Packet::DataPacket(p) => {
|
Packet::DataPacket(p) => {
|
||||||
@ -371,6 +397,43 @@ impl UsbIsp {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_data(&self, buf: &mut impl Read, byte_count: usize) -> Result<u32, Error> {
|
||||||
|
debug!("Writing {} bytes", byte_count);
|
||||||
|
let mut tmp_buf = [0u8; DATA_PACKET_MAX];
|
||||||
|
let mut written = 0;
|
||||||
|
while written < byte_count {
|
||||||
|
let to_write = buf.read(&mut tmp_buf)?;
|
||||||
|
debug!("{}..{}", written, written + to_write);
|
||||||
|
// Avoid the unnecessary copies somehow?
|
||||||
|
let resp = self
|
||||||
|
.write_packet(Packet::DataPacket(packet::DataPacket {
|
||||||
|
bytes: tmp_buf[..to_write].to_vec(),
|
||||||
|
}))?
|
||||||
|
.params;
|
||||||
|
|
||||||
|
match resp {
|
||||||
|
ResponseParameters::GenericResponse(_) => (),
|
||||||
|
_ => Err(anyhow!("Unexpected response during data phase: {:?}", resp))?,
|
||||||
|
};
|
||||||
|
|
||||||
|
written += to_write;
|
||||||
|
}
|
||||||
|
debug!("Wrote total of {} bytes", written);
|
||||||
|
|
||||||
|
// After we're done we expect a GenericResponse with success.
|
||||||
|
// For some reason we need to send an empty data packet to finish the write
|
||||||
|
let resp = self.write_packet(Packet::DataPacket(packet::DataPacket { bytes: vec![] }))?;
|
||||||
|
let params = &resp.params;
|
||||||
|
|
||||||
|
match params {
|
||||||
|
ResponseParameters::GenericResponse(rp) if rp.status == StatusCode::Success => Ok(written as u32),
|
||||||
|
_ => Err(anyhow!(
|
||||||
|
"Got an error response after data phase: {:?}",
|
||||||
|
resp
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_last_error(&self) -> u32 {
|
pub fn get_last_error(&self) -> u32 {
|
||||||
// Same as get_property but less checking
|
// Same as get_property but less checking
|
||||||
let command = CommandPacket {
|
let command = CommandPacket {
|
||||||
@ -597,10 +660,7 @@ impl UsbIsp {
|
|||||||
params: vec![
|
params: vec![
|
||||||
start_address,
|
start_address,
|
||||||
byte_count,
|
byte_count,
|
||||||
match memory_id {
|
memory_id.unwrap_or(MemoryId::Internal).into(),
|
||||||
Some(v) => v.into(),
|
|
||||||
None => MemoryId::Internal.into(),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
let pr = self.send_command(command)?;
|
let pr = self.send_command(command)?;
|
||||||
@ -615,7 +675,7 @@ impl UsbIsp {
|
|||||||
|
|
||||||
// We expect a GenericResponse with success after the transfer
|
// We expect a GenericResponse with success after the transfer
|
||||||
let mut rbuf = vec![0u8; 64];
|
let mut rbuf = vec![0u8; 64];
|
||||||
let res_size = self.device.read(&mut rbuf[..])?;
|
let res_size = self.device.read_timeout(&mut rbuf[..], READ_TIMEOUT)?;
|
||||||
let parsed = packet::usb_packet(&rbuf[..res_size]).unwrap().1; //TODO: handle more gracefully, but requires ownership of I
|
let parsed = packet::usb_packet(&rbuf[..res_size]).unwrap().1; //TODO: handle more gracefully, but requires ownership of I
|
||||||
|
|
||||||
match parsed {
|
match parsed {
|
||||||
@ -632,4 +692,34 @@ impl UsbIsp {
|
|||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn WriteMemory(
|
||||||
|
&self,
|
||||||
|
buf: &mut impl std::io::Read,
|
||||||
|
start_address: u32,
|
||||||
|
byte_count: u32,
|
||||||
|
memory_id: Option<MemoryId>,
|
||||||
|
) -> Result<u32, Error> {
|
||||||
|
let command = CommandPacket {
|
||||||
|
tag: CommandTag::WriteMemory,
|
||||||
|
flags: CommandFlags::DATA_FOLLOWS,
|
||||||
|
reserved: 0,
|
||||||
|
param_count: 3,
|
||||||
|
params: vec![
|
||||||
|
start_address,
|
||||||
|
byte_count,
|
||||||
|
memory_id.unwrap_or(MemoryId::Internal).into(),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let pr = self.write_packet(Packet::CommandPacket(command))?;
|
||||||
|
|
||||||
|
match pr.params {
|
||||||
|
ResponseParameters::GenericResponse(params) => match params.status {
|
||||||
|
StatusCode::Success => (),
|
||||||
|
_ => return Err(anyhow!("Error returned from device: {:?}", params.status)),
|
||||||
|
},
|
||||||
|
_ => return Err(anyhow!("Unexpected reply to WriteMemory {:?}", pr.params)),
|
||||||
|
};
|
||||||
|
Ok(self.write_data(buf, byte_count as usize)?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user