Initial commit
This commit is contained in:
234
src/target.rs
Normal file
234
src/target.rs
Normal file
@@ -0,0 +1,234 @@
|
||||
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))?;
|
||||
|
||||
port.set_dtr(false)?;
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
|
||||
for i in (0..5).rev() {
|
||||
port.write(b"U")?;
|
||||
|
||||
let mut buf = [0u8; 1];
|
||||
port.read(&mut buf)?;
|
||||
|
||||
if buf[0] == b'U' { break }
|
||||
|
||||
if i == 0 {
|
||||
Err(Error::other(format!("Invalid sync response: 0x{:02X}", buf[0])))?
|
||||
}
|
||||
}
|
||||
|
||||
let mut new = Self { port };
|
||||
|
||||
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 the fuck that means)");
|
||||
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user