253 lines
6.1 KiB
Rust
253 lines
6.1 KiB
Rust
use std::io::Error;
|
|
use std::path::Path;
|
|
use std::time::Duration;
|
|
|
|
use serial2::SerialPort;
|
|
|
|
use crate::utils::Hex;
|
|
|
|
pub trait Target {
|
|
fn read_flash(&mut self, offset: u16, out: &mut [u8]) -> Result<(), Error>;
|
|
fn write_flash(&mut self, page: u16, data: &[u8; 128]) -> Result<(), Error>;
|
|
fn erase(&mut self) -> Result<(), Error>;
|
|
}
|
|
|
|
pub struct PhysicalTarget {
|
|
port: SerialPort
|
|
}
|
|
|
|
impl PhysicalTarget {
|
|
pub fn init(path: impl AsRef<Path>, baud: u32) -> Result<Self, Error> {
|
|
let mut port = SerialPort::open(path, baud)?;
|
|
|
|
port.set_read_timeout(Duration::from_millis(100))?;
|
|
|
|
let mut new = Self { port };
|
|
|
|
new.resync_target()?;
|
|
|
|
let manufacturer = new.read_info(0x00, 0x00).unwrap();
|
|
let family = new.read_info(0x00, 0x01).unwrap();
|
|
let name = new.read_info(0x00, 0x02).unwrap();
|
|
let rev = new.read_info(0x00, 0x03).unwrap();
|
|
|
|
println!("Detected chip ID: {manufacturer:02X} {family:02X} {name:02X} {rev:02X} (whatever that means)");
|
|
|
|
Ok(new)
|
|
}
|
|
|
|
pub fn resync_target(&mut self) -> Result<(), Error> {
|
|
self.port.discard_buffers()?;
|
|
self.port.set_dtr(true)?;
|
|
self.port.set_rts(true)?;
|
|
std::thread::sleep(Duration::from_millis(10));
|
|
self.port.set_dtr(false)?;
|
|
std::thread::sleep(Duration::from_millis(100));
|
|
|
|
for i in (0..5).rev() {
|
|
self.port.write(b"U")?;
|
|
|
|
let mut buf = [0u8; 1];
|
|
self.port.read(&mut buf)?;
|
|
|
|
if buf[0] == b'U' { break }
|
|
|
|
if i == 0 {
|
|
Err(Error::other(format!("Invalid sync response: 0x{:02X}", buf[0])))?
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn read_info(&mut self, category: u8, idx: u8) -> Result<u8, Error> {
|
|
self.send_command(0x05, 0, &[category, idx])?;
|
|
|
|
let mut resp_hex_digits = Vec::new();
|
|
|
|
loop {
|
|
let mut tmp = [0u8; 1];
|
|
|
|
self.port.read_exact(&mut tmp)?;
|
|
|
|
match tmp[0] {
|
|
x @ (b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F') => resp_hex_digits.push(x),
|
|
b'.' | b'\r' => {},
|
|
b'\n' => break,
|
|
b'P' => Err(Error::other("Security error"))?,
|
|
b'X' => Err(Error::other("Checksum error"))?,
|
|
x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))?
|
|
}
|
|
}
|
|
|
|
let mut rx = [0u8; 1];
|
|
|
|
let resp = resp_hex_digits.as_chunks::<2>().0.iter()
|
|
.map(u8::hex_decode);
|
|
|
|
if resp.len() != rx.len() {
|
|
Err(Error::other(format!("Response size mismatch, got {} bytes, expected {} bytes", resp.len(), rx.len())))?
|
|
}
|
|
|
|
rx.iter_mut()
|
|
.zip(resp)
|
|
.for_each(|(dest, src)| *dest = src);
|
|
|
|
Ok(u8::from_le_bytes(rx))
|
|
}
|
|
|
|
fn send_command(&mut self, cmd_type: u8, offset: u16, tx: &[u8]) -> Result<(), Error> {
|
|
if tx.len() >= 256 {
|
|
Err(Error::other(format!("Payload too large: {} bytes", tx.len())))?
|
|
}
|
|
|
|
// Why, the ACTUAL FUCK, ARE THEY USING ASCII FOR TRANSFERING BINARIES?!
|
|
|
|
let mut ascii_buf = Vec::with_capacity(1 + 2 + 4 + 2 + tx.len() * 2 + 1); // Mark, Length, Offset, Type, Data, Checksum
|
|
|
|
ascii_buf.push(b':');
|
|
ascii_buf.extend_from_slice(&(tx.len() as u8).hex_encode());
|
|
ascii_buf.extend_from_slice(&offset.hex_encode());
|
|
ascii_buf.extend_from_slice(&cmd_type.hex_encode());
|
|
|
|
let mut checksum: u8 = 0;
|
|
|
|
checksum = checksum.wrapping_add(cmd_type);
|
|
checksum = checksum.wrapping_add(tx.len() as u8);
|
|
checksum = checksum.wrapping_add(offset as u8);
|
|
checksum = checksum.wrapping_add((offset >> 8) as u8);
|
|
|
|
for val in tx {
|
|
ascii_buf.extend_from_slice(&val.hex_encode());
|
|
checksum = checksum.wrapping_add(*val);
|
|
}
|
|
|
|
ascii_buf.extend_from_slice(&checksum.wrapping_neg().hex_encode());
|
|
|
|
self.port.write_all(&ascii_buf)?;
|
|
|
|
let mut echo = vec![0u8; ascii_buf.len()];
|
|
|
|
self.port.read_exact(&mut echo)?;
|
|
|
|
if echo != ascii_buf {
|
|
Err(Error::other("Echo response mismatch"))?
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn port(&mut self) -> &mut SerialPort {
|
|
&mut self.port
|
|
}
|
|
}
|
|
|
|
impl Target for PhysicalTarget {
|
|
fn read_flash(&mut self, offset: u16, out: &mut [u8]) -> Result<(), Error> {
|
|
self.send_command(0x04, 0, &[
|
|
offset.to_be_bytes().as_slice(),
|
|
offset.saturating_add(out.len() as u16 - 1).to_be_bytes().as_slice(),
|
|
&[0] // Flash
|
|
].concat())?;
|
|
|
|
let mut resp_hex_digits = Vec::with_capacity(out.len() * 2);
|
|
|
|
let mut ignore = false;
|
|
let mut finish_line = false;
|
|
|
|
loop {
|
|
let mut tmp = [0u8; 1];
|
|
|
|
self.port.read_exact(&mut tmp)?;
|
|
|
|
match tmp[0] {
|
|
x @ (b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F') => {
|
|
if !ignore {
|
|
resp_hex_digits.push(x);
|
|
if resp_hex_digits.len() >= resp_hex_digits.capacity() {
|
|
ignore = true;
|
|
finish_line = true;
|
|
}
|
|
}
|
|
},
|
|
b'=' => {
|
|
ignore = false;
|
|
}
|
|
b'.' | b'\r' => {},
|
|
b'\n' => {
|
|
ignore = true;
|
|
if finish_line { break }
|
|
},
|
|
b'P' => Err(Error::other("Security error"))?,
|
|
b'X' => Err(Error::other("Checksum error"))?,
|
|
x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))?
|
|
}
|
|
}
|
|
|
|
let resp = resp_hex_digits.as_chunks::<2>().0.iter()
|
|
.map(u8::hex_decode);
|
|
|
|
out.iter_mut()
|
|
.zip(resp)
|
|
.for_each(|(dest, src)| *dest = src);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn write_flash(&mut self, page: u16, data: &[u8; 128]) -> Result<(), Error> {
|
|
self.send_command(0x00, page * 128, data)?;
|
|
|
|
loop {
|
|
let mut tmp = [0u8; 1];
|
|
|
|
self.port.read_exact(&mut tmp)?;
|
|
|
|
match tmp[0] {
|
|
b'.' | b'\r' => {},
|
|
b'\n' => break,
|
|
b'P' => Err(Error::other("Security error"))?,
|
|
b'X' => Err(Error::other("Checksum error"))?,
|
|
x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))?
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn erase(&mut self) -> Result<(), Error> {
|
|
self.send_command(0x03, 0, &[0x07])?;
|
|
|
|
let old_timeout = self.port.get_read_timeout()?;
|
|
self.port.set_read_timeout(Duration::from_secs(15))?;
|
|
|
|
loop {
|
|
let mut tmp = [0u8; 1];
|
|
|
|
self.port.read_exact(&mut tmp)?;
|
|
|
|
match tmp[0] {
|
|
b'.' | b'\r' => {},
|
|
b'\n' => break,
|
|
b'P' => Err(Error::other("Security error"))?,
|
|
b'X' => Err(Error::other("Checksum error"))?,
|
|
x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))?
|
|
}
|
|
}
|
|
|
|
self.port.set_read_timeout(old_timeout)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Drop for PhysicalTarget {
|
|
fn drop(&mut self) {
|
|
let _ = self.port.set_dtr(true);
|
|
let _ = self.port.set_rts(true);
|
|
std::thread::sleep(Duration::from_millis(100));
|
|
let _ = self.port.set_dtr(false);
|
|
let _ = self.port.set_rts(false);
|
|
}
|
|
}
|
|
|