Compare commits

..

4 Commits

Author SHA1 Message Date
eda224c64d Fix the sense of the --no-reset flag
The argument was set to 'SetFalse', so when present was setting the bool
to false, but the test was also negated, so reset was opposite of
desired.
2023-11-06 19:31:28 -08:00
c53a940ca5 Add README 2023-11-06 19:31:10 -08:00
2de6345f96 Minor cleanup, commented-out beginnings of elf support 2023-11-06 00:35:21 -08:00
b07c8cd8c8 Improve commands, get write support working 2023-11-06 00:34:44 -08:00
5 changed files with 351 additions and 108 deletions

24
Cargo.lock generated
View File

@@ -156,7 +156,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]
[[package]]
@@ -324,9 +324,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.149"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "linux-raw-sys"
@@ -565,9 +565,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.38"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
@@ -795,29 +795,29 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "winnow"
version = "0.5.18"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176b6138793677221d420fd2f0aeeced263f197688b36484660da767bca2fa32"
checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b"
dependencies = [
"memchr",
]
[[package]]
name = "zerocopy"
version = "0.7.24"
version = "0.7.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "092cd76b01a033a9965b9097da258689d9e17c69ded5dcf41bca001dd20ebc6d"
checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.24"
version = "0.7.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13a20a7c6a90e2034bcc65495799da92efcec6a8dd4f3fcb6f7a48988637ead"
checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
"syn 2.0.39",
]

29
README.md Normal file
View File

@@ -0,0 +1,29 @@
# lpc55prog
`lpc55prog` was built as a tool for flashing NXP LPC5526/28 microcontrollers
using their built-in bootloader ROM's USB ISP mode. Work was started on this
tool as I couldn't find an official or community tool for doing so.
Later I discovered the very poorly named/discoverable `bltool` that implements
this protocol (which I have [mirrored](https://git.gotroot.ca/ktims/blhost) in
my forge).
## Features
`lpc55prog` can read, write and erase the flash of LPC5526, LPC5528, LPC55S26 &
LPC55S28 microcontrollers using the USB ISP. Object files can be read or written
in raw binary or Intel hex, and can also be written to simple ELF.
It has only been tested on Linux, but will likely also work on other *nix
systems like macOS. Windows might work, please let me know if it does 🙂.
## Installation
```shell
git clone https://git.gotroot.ca/ktims/lpc55prog
cargo install --path lpc55prog
```
## Usage
See `lpc55prog help`.

View File

@@ -1,14 +1,10 @@
extern crate object;
use std::fs::File;
use std::io::prelude::*;
use std::iter::repeat;
use std::path::Path;
use std::path::PathBuf;
use clap::ValueEnum;
use log::debug;
use log::warn;
use log::{debug, warn};
use object::elf;
use object::Endianness;
@@ -23,13 +19,20 @@ pub enum MemoryFileType {
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
struct ElfWriter;
struct IhexWriter;
struct BinWriter;
struct ElfReader;
struct ElfReader {
file_contents: Vec<u8>,
}
struct IhexReader {
file_contents: String,
}
@@ -50,11 +53,13 @@ impl MemoryFileType {
_ => None,
}
}
pub fn object_file_reader(
pub fn object_file_reader_auto(
filename: &PathBuf,
) -> Result<Box<dyn ObjectFileReader>, std::io::Error> {
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::Bin) => BinReader::new(filename),
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> {
match *self {
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,
/// 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.
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
@@ -245,13 +260,16 @@ impl ObjectFileReader for BinReader {
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...
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));
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 }))
}
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 mut file_base = None;
let mut cur_base = 0;
let mut data = Vec::new();
for rec in reader {
if let Ok(record) = rec {
match record {
@@ -285,11 +305,27 @@ impl ObjectFileReader for IhexReader {
ihex::Record::Data { offset, mut value } => {
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 {
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.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,
_ => 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,
// })
// }
// }

View File

@@ -1,5 +1,7 @@
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 clap_num::maybe_hex;
@@ -102,7 +104,7 @@ enum ObjectFileType {
Elf,
Ihex,
Bin,
/// Derive file type from file name
/// Derive file type from file name (not magic)
Auto,
}
@@ -112,7 +114,7 @@ enum WriteEraseMethod {
All,
/// Erase only the area to be written
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,
}
@@ -127,14 +129,17 @@ impl ObjectFileType {
}
fn file_reader(&self, filename: &PathBuf) -> Result<Box<dyn ObjectFileReader>, std::io::Error> {
match self {
Self::Auto => MemoryFileType::object_file_reader(filename),
_ => unimplemented!(),
Self::Auto => MemoryFileType::object_file_reader_auto(filename),
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)]
struct ReadArgs {
/// Object file to write
file: PathBuf,
#[arg(short = 't', long = "type", value_enum, default_value_t = ObjectFileType::Auto)]
/// Type of object file to generate
@@ -143,9 +148,11 @@ struct ReadArgs {
/// USB device to act on
#[command(flatten)]
devspec: UsbDeviceSpecifier,
/// Base memory address on microcontroller (defaults to start of flash)
#[arg(short = 'a', long = "base-address", value_parser=maybe_hex::<u32>)]
addr: Option<u32>,
/// Size to read in bytes (defaults to size of flash)
#[arg(short, long, value_parser=maybe_hex::<u32>)]
size: Option<u32>,
@@ -153,6 +160,7 @@ struct ReadArgs {
#[derive(Args, Debug)]
struct WriteArgs {
/// Object file to read
file: PathBuf,
#[arg(short = 't', long = "type", value_enum, default_value_t = ObjectFileType::Auto)]
filetype: ObjectFileType,
@@ -160,17 +168,21 @@ struct WriteArgs {
/// USB device to act on
#[command(flatten)]
devspec: UsbDeviceSpecifier,
/// Base memory address on microcontroller to write to (defaults to start of flash)
#[arg(short = 'a', long = "write-address", value_parser=maybe_hex::<u32>)]
addr: Option<u32>,
/// Size to write in bytes (defaults to size of flash)
#[arg(short, long, value_parser=maybe_hex::<u32>)]
size: Option<u32>,
/// Don't reset the microcontroller after writing
#[arg(short, long, action=ArgAction::SetFalse)]
#[arg(short, long)]
no_reset: bool,
#[arg(short, long, default_value = "region")]
/// Erase method to use before writing
#[arg(short, long, default_value = "region")]
erase: WriteEraseMethod,
}
@@ -194,6 +206,7 @@ struct EraseArgs {
/// Address to start erasing at
#[arg(short = 'a', long="base-address", value_parser=maybe_hex::<u32>, conflicts_with="erase_all")]
addr: Option<u32>,
/// Number of bytes to erase
#[arg(short, long, value_parser=maybe_hex::<u32>, conflicts_with="erase_all")]
size: Option<u32>,
@@ -210,6 +223,9 @@ enum Commands {
/// Read microcontroller memory to a file
Read(ReadArgs),
/// 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),
/// Erase the microcontroller's flash
Erase(EraseArgs),
@@ -227,9 +243,35 @@ fn read_write_style() -> ProgressStyle {
.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> {
let api = HidApi::new()?;
let isp = connect_device(&api, &args.devspec)?;
let isp = connect_device(&args.devspec)?;
let mut infile = args
.filetype
@@ -240,41 +282,57 @@ fn write_file_to_flash(args: &WriteArgs) -> Result<(), Error> {
.read_all(None)
.or_else(|e| Err(anyhow!("Unable to read file contents: {}", e)))?;
println!("Input file first section contents:");
for (i, line) in contents.chunks(16).enumerate() {
print!("{:08x} ", i * 16);
for block in line.chunks(8) {
for s in block.iter().map(|x| format!("{:02x} ", x)) {
print!("{}", s);
}
print!(" ");
}
println!();
}
debug!(
"Input file read (origin 0x{:08x} size 0x{:08x}):",
contents.address,
contents.data.len()
);
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 {
// isp.reset()?;
// }
debug!(
"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(())
}
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(&api, &args.devspec)?;
let isp = connect_device(&args.devspec)?;
let flash_start = match args.addr {
None => isp.GetFlashStartAddress()?,
Some(addr) => addr,
};
let flash_size = match args.size {
None => isp.GetFlashSizeInBytes()?,
Some(size) => size,
};
let flash_start = args.addr.unwrap_or(isp.GetFlashStartAddress()?);
let flash_size = args.size.unwrap_or(isp.GetFlashSizeInBytes()?);
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> {
let api = hidapi::HidApi::new()?;
let isp = connect_device(&api, &args.devspec)?;
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(())
let isp = connect_device(&args.devspec)?;
do_erase(&isp, args.erase_all, args.addr, args.size)
}
fn reset(args: &ResetArgs) -> Result<(), Error> {
let api = hidapi::HidApi::new()?;
let isp = connect_device(&api, &args.devspec)?;
let isp = connect_device(&args.devspec)?;
isp.reset()
}
@@ -375,11 +413,9 @@ fn get_matching_devices<'a>(
}
}
fn connect_device(
api: &hidapi::HidApi,
devspec: &UsbDeviceSpecifier,
) -> Result<protocol::UsbIsp, Error> {
let matches = get_matching_devices(api, devspec)?;
fn connect_device(devspec: &UsbDeviceSpecifier) -> Result<protocol::UsbIsp, Error> {
let api = HidApi::new()?;
let matches = get_matching_devices(&api, devspec)?;
if matches.len() < 1 {
return Err(NoDevicesError {}.into());
} else if matches.len() > 1 {
@@ -391,7 +427,7 @@ fn connect_device(
matches[0].product_string().unwrap_or("device"),
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);
debug!("Connected to device {}", proto.GetUniqueDeviceId()?);
Ok(proto)

View File

@@ -6,7 +6,7 @@ use hidapi::HidDevice;
use log::debug;
use num_enum::{FromPrimitive, IntoPrimitive};
use std::fmt::{Debug, Display};
use std::io::Write;
use std::io::{Read, Write};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use uuid::Uuid;
@@ -17,6 +17,9 @@ use crate::packet::{
};
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)]
#[derive(IntoPrimitive, EnumIter, Debug, PartialEq, Clone)]
pub enum MemoryId {
@@ -309,6 +312,7 @@ pub struct UsbIsp {
impl UsbIsp {
pub fn new(device: HidDevice) -> Self {
device.set_blocking_mode(false).unwrap();
Self { device }
}
pub fn send_command(&self, p: CommandPacket) -> Result<ResponsePacket, Error> {
@@ -316,23 +320,31 @@ impl UsbIsp {
self.write_packet(Packet::CommandPacket(p))
}
pub fn write_packet(&self, p: Packet) -> Result<ResponsePacket, Error> {
let mut expecting_response = true;
debug!("Writing packet {:?}", &p);
let usb_packet = match p {
Packet::CommandPacket(p) => {
UsbPacket::new(packet::ReportId::CommandOut, Packet::CommandPacket(p))
}
Packet::ResponsePacket(_) => panic!("We should not be writing responses"),
Packet::DataPacket(p) => {
expecting_response = false;
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 resp_timeout = if expecting_response { READ_TIMEOUT } else { 0 };
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
debug!("Got response {:?}", parsed);
match parsed.1 {
Packet::ResponsePacket(p) => Ok(p),
@@ -341,13 +353,27 @@ impl UsbIsp {
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> {
let mut rbuf = [0u8; 64];
let mut read = 0;
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
match parsed.1 {
Packet::DataPacket(p) => {
@@ -371,6 +397,43 @@ impl UsbIsp {
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 {
// Same as get_property but less checking
let command = CommandPacket {
@@ -597,10 +660,7 @@ impl UsbIsp {
params: vec![
start_address,
byte_count,
match memory_id {
Some(v) => v.into(),
None => MemoryId::Internal.into(),
},
memory_id.unwrap_or(MemoryId::Internal).into(),
],
};
let pr = self.send_command(command)?;
@@ -615,7 +675,7 @@ impl UsbIsp {
// We expect a GenericResponse with success after the transfer
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
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)?)
}
}