From b91ce0c37c9e621426d8f9f79e87e01f524458c8 Mon Sep 17 00:00:00 2001 From: Stuart Donnan Date: Mon, 10 Aug 2026 15:36:44 -0700 Subject: [PATCH 01/10] lpc55_isp: refactor with trait --- lpc55_isp/src/bin/cfpa-update.rs | 8 +- lpc55_isp/src/bin/lpc55_flash.rs | 104 ++++---- lpc55_isp/src/cmd.rs | 62 ++--- lpc55_isp/src/isp.rs | 445 +++---------------------------- lpc55_isp/src/lib.rs | 1 + lpc55_isp/src/serial.rs | 352 ++++++++++++++++++++++++ 6 files changed, 468 insertions(+), 504 deletions(-) create mode 100644 lpc55_isp/src/serial.rs diff --git a/lpc55_isp/src/bin/cfpa-update.rs b/lpc55_isp/src/bin/cfpa-update.rs index 89ab1f6..05211b0 100644 --- a/lpc55_isp/src/bin/cfpa-update.rs +++ b/lpc55_isp/src/bin/cfpa-update.rs @@ -6,7 +6,7 @@ use anyhow::Result; use clap::Parser; use lpc55_areas::*; use lpc55_isp::cmd::{do_isp_read_memory, do_isp_write_memory}; -use lpc55_isp::isp::do_ping; +use lpc55_isp::isp::Isp; use packed_struct::prelude::*; use serialport::{DataBits, FlowControl, Parity, StopBits}; use std::io::Write; @@ -39,10 +39,10 @@ fn main() -> Result<()> { .stop_bits(StopBits::One) .open()?; - do_ping(port.as_mut())?; + port.do_ping()?; // 0x9de00 is the fixed address of the CFPA region - let m = do_isp_read_memory(&mut *port, 0x9de00, 512)?; + let m = do_isp_read_memory(&mut port, 0x9de00, 512)?; let mut cfpa: [u8; 512] = [0; 512]; @@ -74,7 +74,7 @@ fn main() -> Result<()> { } else { println!("Writing updated CFPA region back to the device"); - do_isp_write_memory(&mut *port, 0x9de00, &updated)?; + do_isp_write_memory(&mut port, 0x9de00, &updated)?; println!("done!"); } diff --git a/lpc55_isp/src/bin/lpc55_flash.rs b/lpc55_isp/src/bin/lpc55_flash.rs index c99b9e2..1439f92 100644 --- a/lpc55_isp/src/bin/lpc55_flash.rs +++ b/lpc55_isp/src/bin/lpc55_flash.rs @@ -6,7 +6,7 @@ use anyhow::{bail, Context, Result}; use byteorder::ByteOrder; use clap::Parser; use lpc55_isp::cmd::*; -use lpc55_isp::isp::{do_ping, BootloaderProperty, KeyType}; +use lpc55_isp::isp::{BootloaderProperty, KeyType, Isp}; use serialport::{DataBits, FlowControl, Parity, StopBits}; use std::io::{ErrorKind, Read, Write}; use std::path::PathBuf; @@ -109,7 +109,7 @@ enum CfpaChoice { #[derive(Debug, Parser)] #[clap(name = "isp")] -struct Isp { +struct IspArgs { /// UART port #[clap(name = "port")] port: String, @@ -239,7 +239,7 @@ fn pretty_print_error(params: Vec) { } fn main() -> Result<()> { - let cmd = Isp::parse(); + let cmd = IspArgs::parse(); // The target _technically_ has autobaud but it's very flaky // and these seem to be the preferred settings @@ -282,7 +282,7 @@ fn main() -> Result<()> { match cmd.cmd { ISPCommand::Ping => { - do_ping(&mut *port)?; + port.do_ping()?; println!("ping success."); } ISPCommand::ReadMemory { @@ -290,9 +290,9 @@ fn main() -> Result<()> { count, path, } => { - do_ping(&mut *port)?; + port.do_ping()?; - let m = do_isp_read_memory(&mut *port, address, count)?; + let m = do_isp_read_memory(&mut port, address, count)?; let mut out = std::fs::OpenOptions::new() .write(true) @@ -304,19 +304,19 @@ fn main() -> Result<()> { println!("Output written to {:?}", path); } ISPCommand::WriteMemory { address, file } => { - do_ping(&mut *port)?; + port.do_ping()?; println!("If you didn't already erase the flash this operation will fail!"); println!("This operation may take a while"); let infile = std::fs::read(file)?; - do_isp_write_memory(&mut *port, address, &infile)?; + do_isp_write_memory(&mut port, address, &infile)?; println!("Write complete!"); } ISPCommand::FlashEraseAll => { - do_ping(&mut *port)?; + port.do_ping()?; - do_isp_flash_erase_all(&mut *port)?; + do_isp_flash_erase_all(&mut port)?; println!("Flash erased!"); } @@ -324,36 +324,36 @@ fn main() -> Result<()> { start_address, byte_count, } => { - do_ping(&mut *port)?; + port.do_ping()?; - do_isp_flash_erase_region(&mut *port, start_address, byte_count)?; + do_isp_flash_erase_region(&mut port, start_address, byte_count)?; println!("Flash region erased!"); } // Yes this is just another write-memory call but remembering addresses // is hard. ISPCommand::WriteCMPA { file } => { - do_ping(&mut *port)?; + port.do_ping()?; let infile = std::fs::read(file)?; - do_isp_write_memory(&mut *port, 0x9e400, &infile)?; + do_isp_write_memory(&mut port, 0x9e400, &infile)?; println!("Write to CMPA done!"); } ISPCommand::EraseCMPA => { - do_ping(&mut *port)?; + port.do_ping()?; // Write 512 bytes of zero let bytes = [0; 512]; - do_isp_write_memory(&mut *port, 0x9e400, &bytes)?; + do_isp_write_memory(&mut port, 0x9e400, &bytes)?; println!("CMPA region erased!"); println!("You can now boot unsigned images"); } ISPCommand::ReadCMPA { file } => { - do_ping(&mut *port)?; + port.do_ping()?; - let m = do_isp_read_memory(&mut *port, 0x9e400, 512)?; + let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; let mut out = match file { Some(ref path) => Box::new( @@ -370,7 +370,7 @@ fn main() -> Result<()> { eprintln!("CMPA Output written to {:?}", file); } ISPCommand::ReadCFPA { page, file } => { - do_ping(&mut *port)?; + port.do_ping()?; let data = if let Some(page) = page { // Only read one page as requested @@ -379,12 +379,12 @@ fn main() -> Result<()> { CfpaChoice::Ping => 0x9e000, CfpaChoice::Pong => 0x9e200, }; - do_isp_read_memory(&mut *port, addr, 512)? + do_isp_read_memory(&mut port, addr, 512)? } else { // Read ping and pong pages and only write out the latest one. - let ping = do_isp_read_memory(&mut *port, 0x9e000, 512) + let ping = do_isp_read_memory(&mut port, 0x9e000, 512) .context("reading CFPA ping page")?; - let pong = do_isp_read_memory(&mut *port, 0x9e200, 512) + let pong = do_isp_read_memory(&mut port, 0x9e200, 512) .context("reading CFPA pong page")?; let ping_d = lpc55_areas::CFPAPage::from_bytes(ping[..].try_into().unwrap())?; let pong_d = lpc55_areas::CFPAPage::from_bytes(pong[..].try_into().unwrap())?; @@ -414,7 +414,7 @@ fn main() -> Result<()> { update_version, file, } => { - do_ping(&mut *port)?; + port.do_ping()?; let bytes = std::fs::read(file)?; let mut new_cfpa = lpc55_areas::CFPAPage::from_bytes( @@ -423,7 +423,7 @@ fn main() -> Result<()> { // Read the CMPA so we can compare the two to try to avoid locking // the user out of their chip. - let m = do_isp_read_memory(&mut *port, 0x9e400, 512)?; + let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; let cmpa = lpc55_areas::CMPAPage::from_bytes(m[..].try_into().unwrap())?; if (new_cfpa.dcfg_cc_socu_ns_pin != 0 || new_cfpa.dcfg_cc_socu_ns_dflt != 0) && (cmpa.cc_socu_pin == 0 || cmpa.cc_socu_dflt == 0) @@ -439,8 +439,8 @@ fn main() -> Result<()> { if update_version { // Read the current CFPA areas to figure out what version we // need to set. - let ping = do_isp_read_memory(&mut *port, 0x9_e000, 512)?; - let pong = do_isp_read_memory(&mut *port, 0x9_e200, 512)?; + let ping = do_isp_read_memory(&mut port, 0x9_e000, 512)?; + let pong = do_isp_read_memory(&mut port, 0x9_e200, 512)?; let ping = lpc55_areas::CFPAPage::from_bytes(ping[..].try_into().unwrap())?; let pong = lpc55_areas::CFPAPage::from_bytes(pong[..].try_into().unwrap())?; @@ -455,14 +455,14 @@ fn main() -> Result<()> { } let new_bytes = new_cfpa.to_vec()?; - do_isp_write_memory(&mut *port, 0x9_de00, &new_bytes)?; + do_isp_write_memory(&mut port, 0x9_de00, &new_bytes)?; println!("Write to CFPA done!"); } ISPCommand::Restore => { - do_ping(&mut *port)?; + port.do_ping()?; println!("Erasing flash"); - do_isp_flash_erase_all(&mut *port)?; + do_isp_flash_erase_all(&mut port)?; println!("Erasing done."); // we need to fill 0x134 bytes to cover the vector table @@ -482,57 +482,57 @@ fn main() -> Result<()> { byteorder::LittleEndian::write_u32(&mut bytes[0x130..0x134], 0xe7fee7fe); println!("Writing bytes"); - do_isp_write_memory(&mut *port, 0x0, &bytes)?; + do_isp_write_memory(&mut port, 0x0, &bytes)?; println!("Restore done! SWD should work now."); } ISPCommand::SendSBUpdate { file } => { - do_ping(&mut *port)?; + port.do_ping()?; println!("Sending SB file, this may take a while"); let infile = std::fs::read(file)?; - do_recv_sb_file(&mut *port, &infile)?; + do_recv_sb_file(&mut port, &infile)?; println!("Send complete!"); } ISPCommand::Enroll => { - do_ping(&mut *port)?; + port.do_ping()?; println!("Generating new activation code"); - do_enroll(&mut *port)?; + do_enroll(&mut port)?; println!("done."); println!("If you want to save this, remember to write to non-volatile memory"); } ISPCommand::GenerateUDS => { - do_ping(&mut *port)?; + port.do_ping()?; println!("Generating new UDS"); - do_generate_uds(&mut *port)?; + do_generate_uds(&mut port)?; println!("done."); println!("If you want to save this, remember to write to non-volatile memory"); } ISPCommand::WriteKeyStore => { - do_ping(&mut *port)?; + port.do_ping()?; println!("Writing key store to flash"); - do_save_keystore(&mut *port)?; + do_save_keystore(&mut port)?; println!("done."); } ISPCommand::EraseKeyStore => { - do_ping(&mut *port)?; + port.do_ping()?; println!("Erasing existing keystore"); // Write 3 * 512 bytes of 0 let bytes = vec![0; 512 * 3]; - do_isp_write_keystore(&mut *port, &bytes)?; - do_save_keystore(&mut *port)?; + do_isp_write_keystore(&mut port, &bytes)?; + do_save_keystore(&mut port)?; println!("done.") } ISPCommand::SetSBKek { file } => { - do_ping(&mut *port)?; + port.do_ping()?; let mut infile = std::fs::OpenOptions::new().read(true).open(file)?; @@ -544,19 +544,19 @@ fn main() -> Result<()> { actual_bytes.reverse(); - do_isp_set_userkey(&mut *port, KeyType::SBKEK, &actual_bytes)?; + do_isp_set_userkey(&mut port, KeyType::SBKEK, &actual_bytes)?; println!("done."); } ISPCommand::SetupKeyStore { file } => { - do_ping(&mut *port)?; + port.do_ping()?; // Step 1: Enroll println!("Generating new activation code"); - do_enroll(&mut *port)?; + do_enroll(&mut port)?; // Step 2: Generate UDS println!("Generating new UDS"); - do_generate_uds(&mut *port)?; + do_generate_uds(&mut port)?; // Step 3: Set the SBKEK let mut infile = std::fs::OpenOptions::new().read(true).open(file)?; @@ -571,20 +571,20 @@ fn main() -> Result<()> { actual_bytes.reverse(); println!("Setting user key"); - do_isp_set_userkey(&mut *port, KeyType::SBKEK, &actual_bytes)?; + do_isp_set_userkey(&mut port, KeyType::SBKEK, &actual_bytes)?; println!("Writing keystore"); // Step 4: Write the keystore to persistent storage - do_save_keystore(&mut *port)?; + do_save_keystore(&mut port)?; } ISPCommand::GetProperty { prop } => { - do_ping(&mut *port)?; - let result = do_isp_get_property(&mut *port, prop)?; + port.do_ping()?; + let result = do_isp_get_property(&mut port, prop)?; pretty_print_bootloader_prop(prop, result); } ISPCommand::LastError => { - do_ping(&mut *port)?; - let result = do_isp_last_error(&mut *port)?; + port.do_ping()?; + let result = do_isp_last_error(&mut port)?; pretty_print_error(result); } } diff --git a/lpc55_isp/src/cmd.rs b/lpc55_isp/src/cmd.rs index b40208b..2e9c9f0 100644 --- a/lpc55_isp/src/cmd.rs +++ b/lpc55_isp/src/cmd.rs @@ -10,27 +10,27 @@ enum DataPhase<'a> { Recv { code: ResponseCode, cnt: u32 }, } -fn do_command( - port: &mut dyn serialport::SerialPort, +fn do_command( + port: &mut P, tag: CommandTag, command_resp: ResponseCode, args: impl Into>, d: DataPhase, ) -> Result>, IspError> { - send_command(port, tag, args)?; + port.send_command(tag, args)?; - read_response(port, command_resp)?; + port.read_response(command_resp)?; let ret = match d { DataPhase::NoData => None, DataPhase::Send { code, data } => { - send_data(port, data)?; - read_response(port, code)?; + port.send_data(data)?; + port.read_response(code)?; None } DataPhase::Recv { code, cnt } => { - let r = recv_data(port, cnt)?; - read_response(port, code)?; + let r = port.recv_data(cnt)?; + port.read_response(code)?; Some(r) } }; @@ -38,7 +38,7 @@ fn do_command( Ok(ret) } -pub fn do_save_keystore(port: &mut dyn serialport::SerialPort) -> Result<(), IspError> { +pub fn do_save_keystore(port: &mut P) -> Result<(), IspError> { do_command( port, CommandTag::KeyProvision, @@ -55,7 +55,7 @@ pub fn do_save_keystore(port: &mut dyn serialport::SerialPort) -> Result<(), Isp Ok(()) } -pub fn do_enroll(port: &mut dyn serialport::SerialPort) -> Result<(), IspError> { +pub fn do_enroll(port: &mut P) -> Result<(), IspError> { do_command( port, CommandTag::KeyProvision, @@ -67,7 +67,7 @@ pub fn do_enroll(port: &mut dyn serialport::SerialPort) -> Result<(), IspError> Ok(()) } -pub fn do_generate_uds(port: &mut dyn serialport::SerialPort) -> Result<(), IspError> { +pub fn do_generate_uds(port: &mut P) -> Result<(), IspError> { do_command( port, CommandTag::KeyProvision, @@ -86,10 +86,7 @@ pub fn do_generate_uds(port: &mut dyn serialport::SerialPort) -> Result<(), IspE Ok(()) } -pub fn do_isp_write_keystore( - port: &mut dyn serialport::SerialPort, - data: &[u8], -) -> Result<(), IspError> { +pub fn do_isp_write_keystore(port: &mut P, data: &[u8]) -> Result<(), IspError> { do_command( port, CommandTag::KeyProvision, @@ -104,7 +101,7 @@ pub fn do_isp_write_keystore( Ok(()) } -pub fn do_recv_sb_file(port: &mut dyn serialport::SerialPort, data: &[u8]) -> Result<(), IspError> { +pub fn do_recv_sb_file(port: &mut P, data: &[u8]) -> Result<(), IspError> { do_command( port, CommandTag::ReceiveSbFile, @@ -119,8 +116,8 @@ pub fn do_recv_sb_file(port: &mut dyn serialport::SerialPort, data: &[u8]) -> Re Ok(()) } -pub fn do_isp_set_userkey( - port: &mut dyn serialport::SerialPort, +pub fn do_isp_set_userkey( + port: &mut P, key_type: KeyType, data: &[u8], ) -> Result<(), IspError> { @@ -145,8 +142,8 @@ pub fn do_isp_set_userkey( Ok(()) } -pub fn do_isp_read_memory( - port: &mut dyn serialport::SerialPort, +pub fn do_isp_read_memory( + port: &mut P, address: u32, cnt: u32, ) -> Result, IspError> { @@ -169,8 +166,8 @@ pub fn do_isp_read_memory( Ok(f.unwrap()) } -pub fn do_isp_write_memory( - port: &mut dyn serialport::SerialPort, +pub fn do_isp_write_memory( + port: &mut P, address: u32, data: &[u8], ) -> Result<(), IspError> { @@ -195,7 +192,7 @@ pub fn do_isp_write_memory( Ok(()) } -pub fn do_isp_flash_erase_all(port: &mut dyn serialport::SerialPort) -> Result<(), IspError> { +pub fn do_isp_flash_erase_all(port: &mut P) -> Result<(), IspError> { do_command( port, CommandTag::FlashEraseAll, @@ -210,8 +207,8 @@ pub fn do_isp_flash_erase_all(port: &mut dyn serialport::SerialPort) -> Result<( Ok(()) } -pub fn do_isp_flash_erase_region( - port: &mut dyn serialport::SerialPort, +pub fn do_isp_flash_erase_region( + port: &mut P, start_address: u32, byte_count: u32, ) -> Result<(), IspError> { @@ -231,20 +228,19 @@ pub fn do_isp_flash_erase_region( Ok(()) } -pub fn do_isp_get_property( - port: &mut dyn serialport::SerialPort, +pub fn do_isp_get_property( + port: &mut P, prop: BootloaderProperty, ) -> Result, IspError> { - send_command(port, CommandTag::GetProperty, [prop as u32])?; + port.send_command(CommandTag::GetProperty, [prop as u32])?; - let f = read_response(port, ResponseCode::GetProperty)?; + let f = port.read_response(ResponseCode::GetProperty)?; Ok(f) } -pub fn do_isp_last_error(port: &mut dyn serialport::SerialPort) -> Result, IspError> { - send_command( - port, +pub fn do_isp_last_error(port: &mut P) -> Result, IspError> { + port.send_command( CommandTag::GetProperty, [ // Arg 0 = LastCRC @@ -254,7 +250,7 @@ pub fn do_isp_last_error(port: &mut dyn serialport::SerialPort) -> Result PacketHeader { + pub fn new(ptype: PacketType) -> PacketHeader { PacketHeader { start_byte: 0x5A_u8, packet_type: ptype as u8, @@ -118,53 +116,29 @@ pub enum BootloaderProperty { #[packed_struct(bit_numbering = "msb0")] pub struct PingResponse { #[packed_field(size_bytes = "2")] - header: PacketHeader, - protocol_bugfix: u8, - protocol_minor: u8, - protocol_major: u8, - protocol_name: u8, - options_low: u8, - options_high: u8, - crc16_low: u8, - crc16_high: u8, -} - -#[repr(C)] -#[derive(Debug, PackedStruct)] -#[packed_struct(bit_numbering = "msb0", endian = "msb")] -pub struct FramingPacket { - #[packed_field(size_bytes = "2")] - header: PacketHeader, - length_low: u8, - length_high: u8, - crc16_low: u8, - crc16_high: u8, -} - -impl FramingPacket { - fn new(ptype: PacketType) -> FramingPacket { - FramingPacket { - header: PacketHeader::new(ptype), - length_low: 0, - length_high: 0, - crc16_low: 0, - crc16_high: 0, - } - } + pub header: PacketHeader, + pub protocol_bugfix: u8, + pub protocol_minor: u8, + pub protocol_major: u8, + pub protocol_name: u8, + pub options_low: u8, + pub options_high: u8, + pub crc16_low: u8, + pub crc16_high: u8, } #[derive(Debug, PackedStruct, Default)] #[packed_struct(bit_numbering = "msb0", endian = "msb")] #[repr(C)] pub struct RawCommand { - tag: u8, - flags: u8, - reserved: u8, - parameter_count: u8, + pub tag: u8, + pub flags: u8, + pub reserved: u8, + pub parameter_count: u8, } impl RawCommand { - fn new(c: CommandTag, count: usize) -> RawCommand { + pub fn new(c: CommandTag, count: usize) -> RawCommand { RawCommand { tag: c as u8, flags: 0, @@ -174,185 +148,16 @@ impl RawCommand { } } -// Command packets can take a variable number -// of arguments. This is unfortunately a pain to serialize -// in a structure. So we cheat a little and and only make the -// existing structure a packed_struct and later append the -// arguments manually -#[derive(Debug, PackedStruct)] -#[packed_struct(bit_numbering = "msb0", endian = "msb")] -#[repr(C)] -pub struct VariablePacket { - #[packed_field(size_bytes = "6")] - packet: FramingPacket, - #[packed_field(size_bytes = "4")] - raw_command: RawCommand, -} - -pub struct CommandPacket { - packet: VariablePacket, - params: Vec, -} - -impl CommandPacket { - fn new_command(c: CommandTag, args: impl Into>) -> CommandPacket { - let args = args.into(); - - let mut v = VariablePacket { - packet: FramingPacket::new(PacketType::Command), - raw_command: RawCommand::new(c, args.len()), - }; - - let arg_bytes = args.len() * 4; - // Total length of the command packet. the 4 bytes are for - // the fixed fields - let len: u16 = u16::try_from(4 + arg_bytes).expect("args vec too long for command packet"); - - v.packet.length_low = (len & 0xFF) as u8; - v.packet.length_high = ((len >> 8) & 0xff) as u8; - - let mut crc = CRCu16::crc16xmodem(); - - let bytes = v.pack().unwrap(); - - // CRC over everything except the CRC field, this includes the framing - // header as well as the rest of the argument - crc.digest(&bytes[..0x4]); - crc.digest(&bytes[0x6..]); - - for e in args.iter() { - crc.digest(&e.to_le_bytes()); - } - - let digest = crc.get_crc(); - - v.packet.crc16_low = (digest & 0xff) as u8; - v.packet.crc16_high = ((digest >> 8) & 0xff) as u8; - - CommandPacket { - packet: v, - params: args, - } - } - - fn to_bytes(&self) -> Vec { - let mut v = Vec::new(); - - v.extend_from_slice(&self.packet.pack().unwrap()); - - for e in self.params.iter() { - v.extend_from_slice(&e.to_le_bytes()); - } - - v - } -} - -pub struct DataPacket { - packet: FramingPacket, - data: Vec, -} - -impl DataPacket { - fn new_data(args: impl Into>) -> DataPacket { - let args = args.into(); - let arg_len = u16::try_from(args.len()).expect("args vector too long for DataPacket"); - - let mut f = FramingPacket::new(PacketType::Data); - - f.length_low = (arg_len & 0xFF) as u8; - f.length_high = ((arg_len >> 8) & 0xff) as u8; - - let mut crc = CRCu16::crc16xmodem(); - - let bytes = f.pack().unwrap(); - - crc.digest(&bytes[..0x4]); - crc.digest(&bytes[0x6..]); - crc.digest(&args); - - let digest = crc.get_crc(); - - f.crc16_low = (digest & 0xff) as u8; - f.crc16_high = ((digest >> 8) & 0xff) as u8; - - DataPacket { - packet: f, - data: args, - } - } - - fn to_bytes(&self) -> Vec { - let mut v = Vec::new(); - - v.extend_from_slice(&self.packet.pack().unwrap()); - v.extend_from_slice(&self.data); - - v - } -} - -pub fn do_ping(port: &mut dyn serialport::SerialPort) -> Result<(), IspError> { - let ping = PacketHeader::new(PacketType::Ping); - - let ping_bytes = ping.pack().unwrap(); - - port.write_all(&ping_bytes)?; - - port.flush()?; - - let mut response_bytes: [u8; 10] = [0; 10]; - - port.read_exact(&mut response_bytes)?; - - let response = PingResponse::unpack(&response_bytes).map_err(IspError::Unpack)?; - - if response.header.packet_type != (PacketType::PingResponse as u8) { - return Err(IspError::BadAck(response.header.packet_type)); - } - - Ok(()) -} - -fn send_ack(port: &mut dyn serialport::SerialPort) -> Result<(), IspError> { - let packet = PacketHeader::new(PacketType::Ack); - - let bytes = packet.pack().unwrap(); - - port.write_all(&bytes)?; - port.flush()?; - - Ok(()) -} - -fn read_ack(port: &mut dyn serialport::SerialPort) -> Result<(), IspError> { - let mut ack_bytes: [u8; 2] = [0; 2]; - - port.read_exact(&mut ack_bytes)?; - - // Note: PacketHeader unpack should not be able to fail here - let ack = PacketHeader::unpack_from_slice(&ack_bytes).unwrap(); - - // Ack abort comes with a response packet explaining why - if ack.packet_type == PacketType::AckAbort as u8 { - let p = read_response(port, ResponseCode::Generic)?; - if p.is_empty() { - return Err(IspError::MissingErrorCode); - } - // The return value is always the first parameter - let retval = p[0]; - - return Err(retval2err(retval).into()); - } - - if ack.packet_type != (PacketType::Ack as u8) { - return Err(IspError::BadAck(ack.packet_type)); - } - - Ok(()) +pub trait Isp { + fn do_ping(&mut self) -> Result<(), IspError>; + fn read_data(&mut self) -> Result, IspError>; + fn read_response(&mut self, response_type: ResponseCode) -> Result, IspError>; + fn send_command(&mut self, cmd: CommandTag, args: impl Into>) -> Result<(), IspError>; + fn send_data(&mut self, data: &[u8]) -> Result<(), IspError>; + fn recv_data(&mut self, cnt: u32) -> Result, IspError>; } -fn retval2err(retval: u32) -> StatusResponse { +pub fn retval2err(retval: u32) -> StatusResponse { if let Some(e) = KnownError::from_u32(retval) { StatusResponse::Known(e) } else { @@ -360,193 +165,13 @@ fn retval2err(retval: u32) -> StatusResponse { } } -fn check_crc(frame_bytes: &[u8], response: &[u8], frame: &FramingPacket) -> Result<(), IspError> { - let mut crc = CRCu16::crc16xmodem(); - crc.digest(&frame_bytes[..0x4]); - crc.digest(&frame_bytes[0x6..]); - crc.digest(&response); - - let digest = crc.get_crc(); - - if !(((digest & 0xff) == frame.crc16_low.into()) - && (((digest >> 8) & 0xff) == frame.crc16_high.into())) - { - return Err(IspError::CrcFailure { - expected: u16::from_le_bytes([frame.crc16_low, frame.crc16_high]), - got: digest, - }); - } - - Ok(()) -} - -pub fn read_data(port: &mut dyn serialport::SerialPort) -> Result, IspError> { - let mut frame_bytes = vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; - port.read_exact(&mut frame_bytes)?; - - let frame = FramingPacket::unpack_from_slice(&frame_bytes).unwrap(); - - require_frame_type(&frame, PacketType::Data)?; - - let length = usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); - let mut response = vec![0; length]; - port.read_exact(&mut response)?; - - check_crc(&frame_bytes, &response, &frame)?; - - Ok(response) -} - -// Okay _technically_ the response can return values from get-property but for -// now just return (). If we _really_ need properties we can add that later -pub fn read_response( - port: &mut dyn serialport::SerialPort, - response_type: ResponseCode, -) -> Result, IspError> { - let mut frame_bytes = vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; - port.read_exact(&mut frame_bytes)?; - - let frame = FramingPacket::unpack_from_slice(&frame_bytes).unwrap(); - - // A response packet is a specific type of command packet. - require_frame_type(&frame, PacketType::Command)?; - - let length: usize = usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); - let mut response = vec![0; length]; - port.read_exact(&mut response)?; - - check_crc(&frame_bytes, &response, &frame)?; - - let command = - RawCommand::unpack_from_slice(&response[..RawCommand::packed_bytes_size(None).unwrap()]) - .map_err(IspError::Unpack)?; - - // Note: we tolerate A0 (generic response) here because many commands return - // it on failure instead of the expected response type. - if command.tag != (response_type as u8) && command.tag != 0xA0 { - return Err(IspError::WrongResponse { - expected: response_type, - got: command.tag, +pub fn require_packet_type(header: &PacketHeader, ty: PacketType) -> Result<(), IspError> { + if header.packet_type != ty as u8 { + return Err(IspError::WrongPacket { + expected: ty, + got: header.packet_type, }); } - - let mut params: Vec = Vec::new(); - let index = RawCommand::packed_bytes_size(None).unwrap(); - - let end_of_params = index + usize::from(command.parameter_count) * 4; - let param_bytes = response - .get(index..end_of_params) - .ok_or(IspError::TruncatedParams { - expected_len: end_of_params, - actual_len: response.len(), - })?; - - for p in param_bytes.chunks_exact(4) { - params.push(u32::from_le_bytes(p.try_into().unwrap())); - } - - send_ack(port)?; - - // First paramter is always the return code; - let retval = params[0]; - - if retval != 0 { - Err(retval2err(retval).into()) - } else { - Ok(params) - } -} - -pub fn send_command( - port: &mut dyn serialport::SerialPort, - cmd: CommandTag, - args: impl Into>, -) -> Result<(), IspError> { - let command_bytes = CommandPacket::new_command(cmd, args).to_bytes(); - - port.write_all(&command_bytes)?; - port.flush()?; - - read_ack(port)?; - - Ok(()) -} - -pub fn send_data(port: &mut dyn serialport::SerialPort, data: &[u8]) -> Result<(), IspError> { - // Target doesn't like it when we send an entire binary in one pass - // so break it down into 512 byte chunks which is what the existing - // tools seem to use - for chunk in data.chunks(512) { - let data_bytes = DataPacket::new_data(chunk).to_bytes(); - - port.write_all(&data_bytes)?; - port.flush()?; - - read_ack(port)?; - } - - Ok(()) -} - -pub fn recv_data(port: &mut dyn serialport::SerialPort, cnt: u32) -> Result, IspError> { - let cnt = cnt as usize; - let mut data = Vec::with_capacity(cnt); - - while data.len() < cnt { - data.extend_from_slice(&read_data(port)?); - send_ack(port)?; - } - - Ok(data) -} - -pub fn do_isp_write_memory( - port: &mut dyn serialport::SerialPort, - address: u32, - data: &[u8], -) -> Result<(), IspError> { - let len = u32::try_from(data.len()).expect("can't send more than 4 GiB"); - let args = vec![address, len, 0x0]; - - send_command(port, CommandTag::WriteMemory, args)?; - - read_response(port, ResponseCode::Generic)?; - - send_data(port, data)?; - - read_response(port, ResponseCode::Generic)?; - - Ok(()) -} - -pub fn do_isp_flash_erase_all(port: &mut dyn serialport::SerialPort) -> Result<(), IspError> { - let args = vec![ - // Erase internal flash - 0x0_u32, - ]; - - send_command(port, CommandTag::FlashEraseAll, args)?; - - read_response(port, ResponseCode::Generic)?; - - Ok(()) -} - -pub fn do_isp_flash_erase_region( - port: &mut dyn serialport::SerialPort, - start_address: u32, - byte_count: u32, -) -> Result<(), IspError> { - let args = vec![ - start_address, - byte_count, - 0_u32, // internal flash memory identifier - ]; - - send_command(port, CommandTag::FlashEraseRegion, args)?; - - read_response(port, ResponseCode::Generic)?; - Ok(()) } @@ -641,13 +266,3 @@ pub enum KnownError { #[error("Security violation (err 10001)")] SecurityViolation = 10001, } - -fn require_frame_type(frame: &FramingPacket, ty: PacketType) -> Result<(), IspError> { - if frame.header.packet_type != ty as u8 { - return Err(IspError::WrongPacket { - expected: ty, - got: frame.header.packet_type, - }); - } - Ok(()) -} diff --git a/lpc55_isp/src/lib.rs b/lpc55_isp/src/lib.rs index 516896b..927a82c 100644 --- a/lpc55_isp/src/lib.rs +++ b/lpc55_isp/src/lib.rs @@ -4,3 +4,4 @@ pub mod cmd; pub mod isp; +pub mod serial; diff --git a/lpc55_isp/src/serial.rs b/lpc55_isp/src/serial.rs new file mode 100644 index 0000000..3f303e9 --- /dev/null +++ b/lpc55_isp/src/serial.rs @@ -0,0 +1,352 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// This implementation focuses on UART as that is the common interface available +// on hosts, however the packet format should apply to other serial protocols + +use crate::isp::*; +use crc_any::CRCu16; +use packed_struct::prelude::*; +use serialport::SerialPort; +use std::convert::TryInto; +use std::io::Read; + +#[repr(C)] +#[derive(Debug, PackedStruct)] +#[packed_struct(bit_numbering = "msb0", endian = "msb")] +pub struct FramingPacket { + #[packed_field(size_bytes = "2")] + header: PacketHeader, + length_low: u8, + length_high: u8, + crc16_low: u8, + crc16_high: u8, +} + +impl FramingPacket { + fn new(ptype: PacketType) -> FramingPacket { + FramingPacket { + header: PacketHeader::new(ptype), + length_low: 0, + length_high: 0, + crc16_low: 0, + crc16_high: 0, + } + } +} + +// Command packets can take a variable number +// of arguments. This is unfortunately a pain to serialize +// in a structure. So we cheat a little and and only make the +// existing structure a packed_struct and later append the +// arguments manually +#[derive(Debug, PackedStruct)] +#[packed_struct(bit_numbering = "msb0", endian = "msb")] +#[repr(C)] +pub struct VariablePacket { + #[packed_field(size_bytes = "6")] + packet: FramingPacket, + #[packed_field(size_bytes = "4")] + raw_command: RawCommand, +} + +pub struct CommandPacket { + packet: VariablePacket, + params: Vec, +} + +impl CommandPacket { + fn new_command(c: CommandTag, args: impl Into>) -> CommandPacket { + let args = args.into(); + + let mut v = VariablePacket { + packet: FramingPacket::new(PacketType::Command), + raw_command: RawCommand::new(c, args.len()), + }; + + let arg_bytes = args.len() * 4; + // Total length of the command packet. the 4 bytes are for + // the fixed fields + let len: u16 = u16::try_from(4 + arg_bytes).expect("args vec too long for command packet"); + + v.packet.length_low = (len & 0xFF) as u8; + v.packet.length_high = ((len >> 8) & 0xff) as u8; + + let mut crc = CRCu16::crc16xmodem(); + + let bytes = v.pack().unwrap(); + + // CRC over everything except the CRC field, this includes the framing + // header as well as the rest of the argument + crc.digest(&bytes[..0x4]); + crc.digest(&bytes[0x6..]); + + for e in args.iter() { + crc.digest(&e.to_le_bytes()); + } + + let digest = crc.get_crc(); + + v.packet.crc16_low = (digest & 0xff) as u8; + v.packet.crc16_high = ((digest >> 8) & 0xff) as u8; + + CommandPacket { + packet: v, + params: args, + } + } + + fn to_bytes(&self) -> Vec { + let mut v = Vec::new(); + + v.extend_from_slice(&self.packet.pack().unwrap()); + + for e in self.params.iter() { + v.extend_from_slice(&e.to_le_bytes()); + } + + v + } +} + +pub struct DataPacket { + packet: FramingPacket, + data: Vec, +} + +impl DataPacket { + fn new_data(args: impl Into>) -> DataPacket { + let args = args.into(); + let arg_len = u16::try_from(args.len()).expect("args vector too long for DataPacket"); + + let mut f = FramingPacket::new(PacketType::Data); + + f.length_low = (arg_len & 0xFF) as u8; + f.length_high = ((arg_len >> 8) & 0xff) as u8; + + let mut crc = CRCu16::crc16xmodem(); + + let bytes = f.pack().unwrap(); + + crc.digest(&bytes[..0x4]); + crc.digest(&bytes[0x6..]); + crc.digest(&args); + + let digest = crc.get_crc(); + + f.crc16_low = (digest & 0xff) as u8; + f.crc16_high = ((digest >> 8) & 0xff) as u8; + + DataPacket { + packet: f, + data: args, + } + } + + fn to_bytes(&self) -> Vec { + let mut v = Vec::new(); + + v.extend_from_slice(&self.packet.pack().unwrap()); + v.extend_from_slice(&self.data); + + v + } +} + +fn send_ack(port: &mut Box) -> Result<(), IspError> { + let packet = PacketHeader::new(PacketType::Ack); + + let bytes = packet.pack().unwrap(); + + port.write_all(&bytes)?; + port.flush()?; + + Ok(()) +} + +fn read_ack(port: &mut Box) -> Result<(), IspError> { + let mut ack_bytes: [u8; 2] = [0; 2]; + + port.read_exact(&mut ack_bytes)?; + + // Note: PacketHeader unpack should not be able to fail here + let ack = PacketHeader::unpack_from_slice(&ack_bytes).unwrap(); + + // Ack abort comes with a response packet explaining why + if ack.packet_type == PacketType::AckAbort as u8 { + let p = port.read_response(ResponseCode::Generic)?; + if p.is_empty() { + return Err(IspError::MissingErrorCode); + } + // The return value is always the first parameter + let retval = p[0]; + + return Err(retval2err(retval).into()); + } + + if ack.packet_type != (PacketType::Ack as u8) { + return Err(IspError::BadAck(ack.packet_type)); + } + + Ok(()) +} + +fn check_crc(frame_bytes: &[u8], response: &[u8], frame: &FramingPacket) -> Result<(), IspError> { + let mut crc = CRCu16::crc16xmodem(); + crc.digest(&frame_bytes[..0x4]); + crc.digest(&frame_bytes[0x6..]); + crc.digest(&response); + + let digest = crc.get_crc(); + + if !(((digest & 0xff) == frame.crc16_low.into()) + && (((digest >> 8) & 0xff) == frame.crc16_high.into())) + { + return Err(IspError::CrcFailure { + expected: u16::from_le_bytes([frame.crc16_low, frame.crc16_high]), + got: digest, + }); + } + + Ok(()) +} + +impl Isp for Box { + fn do_ping(&mut self) -> Result<(), IspError> { + let ping = PacketHeader::new(PacketType::Ping); + + let ping_bytes = ping.pack().unwrap(); + + self.write_all(&ping_bytes)?; + + self.flush()?; + + let mut response_bytes: [u8; 10] = [0; 10]; + + self.read_exact(&mut response_bytes)?; + + let response = PingResponse::unpack(&response_bytes).map_err(IspError::Unpack)?; + + if response.header.packet_type != (PacketType::PingResponse as u8) { + return Err(IspError::BadAck(response.header.packet_type)); + } + + Ok(()) + } + + fn read_data(&mut self) -> Result, IspError> { + let mut frame_bytes = vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; + self.read_exact(&mut frame_bytes)?; + + let frame = FramingPacket::unpack_from_slice(&frame_bytes).unwrap(); + + require_packet_type(&frame.header, PacketType::Data)?; + + let length = usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); + let mut response = vec![0; length]; + self.read_exact(&mut response)?; + + check_crc(&frame_bytes, &response, &frame)?; + + Ok(response) + } + + // Okay _technically_ the response can return values from get-property but for + // now just return (). If we _really_ need properties we can add that later + fn read_response(&mut self, response_type: ResponseCode) -> Result, IspError> { + let mut frame_bytes = vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; + self.read_exact(&mut frame_bytes)?; + + let frame = FramingPacket::unpack_from_slice(&frame_bytes).unwrap(); + + // A response packet is a specific type of command packet. + require_packet_type(&frame.header, PacketType::Command)?; + + let length: usize = usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); + let mut response = vec![0; length]; + self.read_exact(&mut response)?; + + check_crc(&frame_bytes, &response, &frame)?; + + let command = RawCommand::unpack_from_slice( + &response[..RawCommand::packed_bytes_size(None).unwrap()], + ) + .map_err(IspError::Unpack)?; + + // Note: we tolerate A0 (generic response) here because many commands return + // it on failure instead of the expected response type. + if command.tag != (response_type as u8) && command.tag != 0xA0 { + return Err(IspError::WrongResponse { + expected: response_type, + got: command.tag, + }); + } + + let mut params: Vec = Vec::new(); + let index = RawCommand::packed_bytes_size(None).unwrap(); + + let end_of_params = index + usize::from(command.parameter_count) * 4; + let param_bytes = response + .get(index..end_of_params) + .ok_or(IspError::TruncatedParams { + expected_len: end_of_params, + actual_len: response.len(), + })?; + + for p in param_bytes.chunks_exact(4) { + params.push(u32::from_le_bytes(p.try_into().unwrap())); + } + + send_ack(self)?; + + // First paramter is always the return code; + let retval = params[0]; + + if retval != 0 { + Err(retval2err(retval).into()) + } else { + Ok(params) + } + } + + fn send_command(&mut self, cmd: CommandTag, args: impl Into>) -> Result<(), IspError> { + let command_bytes = CommandPacket::new_command(cmd, args).to_bytes(); + + self.write_all(&command_bytes)?; + self.flush()?; + + read_ack(self)?; + + Ok(()) + } + + fn send_data(&mut self, data: &[u8]) -> Result<(), IspError> { + // Target doesn't like it when we send an entire binary in one pass + // so break it down into 512 byte chunks which is what the existing + // tools seem to use + for chunk in data.chunks(512) { + let data_bytes = DataPacket::new_data(chunk).to_bytes(); + + self.write_all(&data_bytes)?; + self.flush()?; + + read_ack(self)?; + } + + Ok(()) + } + + fn recv_data(&mut self, cnt: u32) -> Result, IspError> { + let cnt = cnt as usize; + let mut data = Vec::with_capacity(cnt); + + while data.len() < cnt { + data.extend_from_slice(&self.read_data()?); + send_ack(self)?; + } + + Ok(data) + } +} + From f16382e66af4d3329e33ec288799aeae1b957546 Mon Sep 17 00:00:00 2001 From: Stuart Donnan Date: Mon, 10 Aug 2026 21:41:39 -0700 Subject: [PATCH 02/10] working prototype usb support --- Cargo.lock | 711 ++++++++++++++++--------------- Cargo.toml | 1 + lpc55_isp/Cargo.toml | 2 + lpc55_isp/src/bin/lpc55_flash.rs | 140 ++++-- lpc55_isp/src/cmd.rs | 26 +- lpc55_isp/src/isp.rs | 75 +--- lpc55_isp/src/lib.rs | 1 + lpc55_isp/src/serial.rs | 144 +++++-- lpc55_isp/src/usb.rs | 449 +++++++++++++++++++ rust-toolchain.toml | 2 - 10 files changed, 1047 insertions(+), 504 deletions(-) create mode 100644 lpc55_isp/src/usb.rs delete mode 100644 rust-toolchain.toml diff --git a/Cargo.lock b/Cargo.lock index a1d04c3..c9f9924 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,35 +23,20 @@ dependencies = [ "mach", ] -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.6.20" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -64,47 +49,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" -dependencies = [ - "backtrace", -] +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "array-init" @@ -117,49 +99,34 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitfield" -version = "0.19.1" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db1bcd90f88eabbf0cadbfb87a45bceeaebcd3b4bc9e43da379cd2ef0162590d" +checksum = "b45721c9db4c7a20899d05efb7ad9235f50b256e980db30ffb229abf732934c3" dependencies = [ "bitfield-macros", ] [[package]] name = "bitfield-macros" -version = "0.19.1" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3787a07661997bfc05dd3431e379c0188573f78857080cf682e1393ab8e4d64c" +checksum = "c0cb6f3d4773a2107b94cbeccaa5b5f0b35a88389b5d522d13d659f64317b22d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 3.0.3", ] [[package]] @@ -170,15 +137,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -203,15 +170,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.45" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", "clap_derive", @@ -219,9 +186,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstream", "anstyle", @@ -232,35 +199,35 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.45" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 3.0.3", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -269,6 +236,22 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -280,15 +263,15 @@ dependencies = [ [[package]] name = "crc-any" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73" +checksum = "46db9f663dfb869b80fcf59e32d7a80fc6c464a4f6328f3f06a00f5e36d05f8c" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -315,7 +298,19 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.119", +] + +[[package]] +name = "devinfo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb596a92f5b22f2ccd0f3dcaf11516ae8cff8b9f5448919893d561b4bfeb418" +dependencies = [ + "anyhow", + "libc", + "libdevinfo-sys", + "num_enum", ] [[package]] @@ -331,18 +326,19 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", + "regex", ] [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -350,14 +346,20 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -372,6 +374,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + [[package]] name = "generic-array" version = "0.14.7" @@ -384,9 +392,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -394,10 +402,10 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.31.1" +name = "hashbrown" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -411,17 +419,37 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "io-kit-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d3a048d09fbb6597dbf7c69f40d14df4a49487db1487191618c893fc3b1c26" +dependencies = [ + "core-foundation-sys", + "mach2 0.5.0", +] + [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "lazy_static" @@ -434,27 +462,33 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.175" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libdevinfo-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e92d2955daf279495d959fb506f3db6d17e2d62ab103f27c7d30be27a763cec1" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "log" -version = "0.4.27" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "lpc55_areas" @@ -474,10 +508,12 @@ dependencies = [ "byteorder", "clap", "crc-any", + "env_logger", "hex", "lpc55_areas", "num-derive", "num-traits", + "nusb", "packed_struct", "parse_int", "serialport", @@ -550,6 +586,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -562,18 +607,9 @@ version = "0.1.0" [[package]] name = "memchr" -version = "2.7.5" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "nix" @@ -594,11 +630,10 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -606,7 +641,7 @@ dependencies = [ "num-traits", "rand", "serde", - "smallvec 1.15.1", + "smallvec 1.15.2", "zeroize", ] @@ -618,7 +653,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.119", ] [[package]] @@ -632,11 +667,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -652,19 +686,57 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.36.7" +name = "num_enum" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ - "memchr", + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "nusb" +version = "0.2.3" +source = "git+https://github.com/oxidecomputer/nusb.git?branch=add_illumos#899ccceef9a50946e87a4af4d5615e18abfc41d9" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "devinfo", + "futures-core", + "io-kit-sys", + "libc", + "linux-raw-sys", + "log", + "once_cell", + "rustix", + "slab", + "windows-sys", ] +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "packed_struct" @@ -736,20 +808,29 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" -version = "1.0.97" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -762,9 +843,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "rand_chacha", "rand_core", @@ -791,9 +872,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -803,9 +884,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -814,15 +895,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", @@ -840,37 +921,32 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] -name = "ryu" -version = "1.0.20" +name = "rustversion" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ + "serde_core", "serde_derive", ] @@ -885,36 +961,46 @@ dependencies = [ "smallvec 0.6.14", ] +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] name = "serde_spanned" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -926,7 +1012,7 @@ dependencies = [ "IOKit-sys", "bitflags 1.3.2", "cfg-if", - "mach2", + "mach2 0.4.3", "nix", "regex", "winapi", @@ -953,6 +1039,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "0.6.14" @@ -964,15 +1056,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" [[package]] name = "spki" @@ -1005,7 +1097,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.119", ] [[package]] @@ -1027,9 +1119,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -1044,32 +1147,32 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "terminal_size" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] name = "thiserror" -version = "2.0.14" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.14" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 3.0.3", ] [[package]] @@ -1090,58 +1193,79 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.119", ] [[package]] name = "toml" -version = "0.9.5" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "serde", + "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] name = "toml_datetime" -version = "0.7.0" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ - "serde", + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", ] [[package]] name = "toml_parser" -version = "1.0.2" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "winnow", + "winnow 1.0.4", ] [[package]] name = "toml_writer" -version = "1.0.2" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "typenum" -version = "1.18.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "utf8parse" @@ -1185,162 +1309,33 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.3", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" +name = "winnow" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "0.7.12" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] [[package]] name = "wyz" @@ -1365,40 +1360,46 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.119", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.119", ] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 7a5a8f3..0cc3c72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ hex = { version = "0.4.3", default-features = false, features = ["std"] } log = { version = "0.4", default-features = false } num-derive = { version = "0.4", default-features = false } num-traits = { version = "0.2.15", default-features = false } +nusb = { git = "https://github.com/oxidecomputer/nusb.git", branch = "add_illumos" } packed_struct = { version = "0.10.1", default-features = false, features = ["std"] } parse_int = { version = "0.9.0", default-features = false } pem-rfc7468 = { version = "0.7.0", features = ["std"] } diff --git a/lpc55_isp/Cargo.toml b/lpc55_isp/Cargo.toml index 9f04d94..50c9738 100644 --- a/lpc55_isp/Cargo.toml +++ b/lpc55_isp/Cargo.toml @@ -10,10 +10,12 @@ anyhow.workspace = true byteorder.workspace = true clap.workspace = true crc-any.workspace = true +env_logger.workspace = true hex.workspace = true lpc55_areas.workspace = true num-derive.workspace = true num-traits.workspace = true +nusb.workspace = true packed_struct.workspace = true parse_int.workspace = true serialport.workspace = true diff --git a/lpc55_isp/src/bin/lpc55_flash.rs b/lpc55_isp/src/bin/lpc55_flash.rs index 1439f92..e6ae527 100644 --- a/lpc55_isp/src/bin/lpc55_flash.rs +++ b/lpc55_isp/src/bin/lpc55_flash.rs @@ -6,7 +6,8 @@ use anyhow::{bail, Context, Result}; use byteorder::ByteOrder; use clap::Parser; use lpc55_isp::cmd::*; -use lpc55_isp::isp::{BootloaderProperty, KeyType, Isp}; +use lpc55_isp::isp::{BootloaderProperty, Isp, KeyType}; +use lpc55_isp::usb::UsbIsp; use serialport::{DataBits, FlowControl, Parity, StopBits}; use std::io::{ErrorKind, Read, Write}; use std::path::PathBuf; @@ -107,12 +108,62 @@ enum CfpaChoice { Pong, } +enum Interface { + Usb(UsbIsp), + Serial(Box), +} + +impl Isp for Interface { + fn do_ping(&mut self) -> std::result::Result<(), lpc55_isp::isp::IspError> { + match self { + Interface::Usb(i) => i.do_ping(), + Interface::Serial(i) => i.do_ping(), + } + } + + fn send_data(&mut self, data: &[u8]) -> std::result::Result<(), lpc55_isp::isp::IspError> { + match self { + Interface::Usb(i) => i.send_data(data), + Interface::Serial(i) => i.send_data(data), + } + } + + fn recv_data(&mut self, cnt: u32) -> std::result::Result, lpc55_isp::isp::IspError> { + match self { + Interface::Usb(i) => i.recv_data(cnt), + Interface::Serial(i) => i.recv_data(cnt), + } + } + + fn send_command( + &mut self, + cmd: lpc55_isp::isp::CommandTag, + args: &[u32], + ) -> std::result::Result<(), lpc55_isp::isp::IspError> { + match self { + Interface::Usb(i) => i.send_command(cmd, args), + Interface::Serial(i) => i.send_command(cmd, args), + } + } + + fn read_response( + &mut self, + response_type: lpc55_isp::isp::ResponseCode, + ) -> std::result::Result, lpc55_isp::isp::IspError> { + match self { + Interface::Usb(i) => i.read_response(response_type), + Interface::Serial(i) => i.read_response(response_type), + } + } +} + #[derive(Debug, Parser)] #[clap(name = "isp")] struct IspArgs { - /// UART port - #[clap(name = "port")] - port: String, + #[arg(long)] + serial: Option, + #[arg(long, conflicts_with = "serial")] + usb: Option, /// How fast to run the UART. 57,600 baud seems very reliable but is rather /// slow. In certain test setups we've gotten rates of up to 1Mbaud to work /// reliably -- your mileage may vary! @@ -241,44 +292,53 @@ fn pretty_print_error(params: Vec) { fn main() -> Result<()> { let cmd = IspArgs::parse(); - // The target _technically_ has autobaud but it's very flaky - // and these seem to be the preferred settings - // - // We initially set the timeout short so we can drain the incoming buffer in - // a portable manner below. We'll adjust it up after that. - let mut port = serialport::new(&cmd.port, cmd.baud_rate) - .timeout(Duration::from_millis(100)) - .data_bits(DataBits::Eight) - .flow_control(FlowControl::None) - .parity(Parity::None) - .stop_bits(StopBits::One) - .open()?; - - // Extract any bytes left over in the serial port driver from previous - // interaction. - loop { - let mut throwaway = [0; 16]; - match port.read(&mut throwaway) { - Ok(0) => { - // This should only happen on nonblocking reads, which we - // haven't asked for, but it does mean the buffer is empty so - // treat it as success. - break; - } - Ok(_) => { - // We've collected some characters to throw away, keep going. - } - Err(e) if e.kind() == ErrorKind::TimedOut => { - // Buffer is empty! - break; - } - Err(e) => { - return Err(e.into()); + env_logger::init(); + + let mut port: Interface = if let Some(port) = cmd.serial { + // The target _technically_ has autobaud but it's very flaky + // and these seem to be the preferred settings + // + // We initially set the timeout short so we can drain the incoming buffer in + // a portable manner below. We'll adjust it up after that. + let mut port = serialport::new(&port, cmd.baud_rate) + .timeout(Duration::from_millis(100)) + .data_bits(DataBits::Eight) + .flow_control(FlowControl::None) + .parity(Parity::None) + .stop_bits(StopBits::One) + .open()?; + + // Extract any bytes left over in the serial port driver from previous + // interaction. + loop { + let mut throwaway = [0; 16]; + match port.read(&mut throwaway) { + Ok(0) => { + // This should only happen on nonblocking reads, which we + // haven't asked for, but it does mean the buffer is empty so + // treat it as success. + break; + } + Ok(_) => { + // We've collected some characters to throw away, keep going. + } + Err(e) if e.kind() == ErrorKind::TimedOut => { + // Buffer is empty! + break; + } + Err(e) => { + return Err(e.into()); + } } } - } - // Crank the timeout back up. - port.set_timeout(Duration::from_secs(1))?; + // Crank the timeout back up. + port.set_timeout(Duration::from_secs(1))?; + Interface::Serial(port) + } else if let Some(port) = cmd.usb { + Interface::Usb(UsbIsp::new(&port.parse()?)?) + } else { + bail!("You must choose --serial or --usb"); + }; match cmd.cmd { ISPCommand::Ping => { diff --git a/lpc55_isp/src/cmd.rs b/lpc55_isp/src/cmd.rs index 2e9c9f0..4d3c58c 100644 --- a/lpc55_isp/src/cmd.rs +++ b/lpc55_isp/src/cmd.rs @@ -14,7 +14,7 @@ fn do_command( port: &mut P, tag: CommandTag, command_resp: ResponseCode, - args: impl Into>, + args: &[u32], d: DataPhase, ) -> Result>, IspError> { port.send_command(tag, args)?; @@ -43,7 +43,7 @@ pub fn do_save_keystore(port: &mut P) -> Result<(), IspError> { port, CommandTag::KeyProvision, ResponseCode::Generic, - [ + &[ // Arg 0 = WriteNonVolatile KeyProvisionCmds::WriteNonVolatile as u32, // Arg 1 = Memory ID (0 = internal flash) @@ -60,7 +60,7 @@ pub fn do_enroll(port: &mut P) -> Result<(), IspError> { port, CommandTag::KeyProvision, ResponseCode::Generic, - [KeyProvisionCmds::Enroll as u32], + &[KeyProvisionCmds::Enroll as u32], DataPhase::NoData, )?; @@ -72,7 +72,7 @@ pub fn do_generate_uds(port: &mut P) -> Result<(), IspError> { port, CommandTag::KeyProvision, ResponseCode::Generic, - [ + &[ // Arg 0 = SetIntrinsicKey KeyProvisionCmds::SetIntrinsicKey as u32, // Arg 1 = UDS @@ -91,7 +91,7 @@ pub fn do_isp_write_keystore(port: &mut P, data: &[u8]) -> Result<(), Is port, CommandTag::KeyProvision, ResponseCode::KeyProvision, - [KeyProvisionCmds::WriteKeyStore as u32], + &[KeyProvisionCmds::WriteKeyStore as u32], DataPhase::Send { code: ResponseCode::Generic, data, @@ -106,7 +106,7 @@ pub fn do_recv_sb_file(port: &mut P, data: &[u8]) -> Result<(), IspError port, CommandTag::ReceiveSbFile, ResponseCode::Generic, - [data.len() as u32], + &[data.len() as u32], DataPhase::Send { code: ResponseCode::Generic, data, @@ -125,7 +125,7 @@ pub fn do_isp_set_userkey( port, CommandTag::KeyProvision, ResponseCode::KeyProvision, - [ + &[ // Arg0 = Set User Key KeyProvisionCmds::SetUserKey as u32, // Arg1 = Key type @@ -151,7 +151,7 @@ pub fn do_isp_read_memory( port, CommandTag::ReadMemory, ResponseCode::ReadMemory, - [ + &[ // Arg0 = address address, // Arg1 = length cnt, // Arg2 = memory type @@ -175,7 +175,7 @@ pub fn do_isp_write_memory( port, CommandTag::WriteMemory, ResponseCode::Generic, - [ + &[ // arg 0 = address address, // arg 1 = len @@ -197,7 +197,7 @@ pub fn do_isp_flash_erase_all(port: &mut P) -> Result<(), IspError> { port, CommandTag::FlashEraseAll, ResponseCode::Generic, - [ + &[ // Erase internal flash 0x0_u32, ], @@ -216,7 +216,7 @@ pub fn do_isp_flash_erase_region( port, CommandTag::FlashEraseRegion, ResponseCode::Generic, - [ + &[ start_address, byte_count, // Erase internal flash @@ -232,7 +232,7 @@ pub fn do_isp_get_property( port: &mut P, prop: BootloaderProperty, ) -> Result, IspError> { - port.send_command(CommandTag::GetProperty, [prop as u32])?; + port.send_command(CommandTag::GetProperty, &[prop as u32])?; let f = port.read_response(ResponseCode::GetProperty)?; @@ -242,7 +242,7 @@ pub fn do_isp_get_property( pub fn do_isp_last_error(port: &mut P) -> Result, IspError> { port.send_command( CommandTag::GetProperty, - [ + &[ // Arg 0 = LastCRC BootloaderProperty::CRCStatus as u32, // Arg 1 = Last error diff --git a/lpc55_isp/src/isp.rs b/lpc55_isp/src/isp.rs index e38bd99..9d044fa 100644 --- a/lpc55_isp/src/isp.rs +++ b/lpc55_isp/src/isp.rs @@ -8,18 +8,6 @@ use num_traits::FromPrimitive; use packed_struct::prelude::*; use thiserror::Error; -#[repr(u8)] -#[derive(Copy, Clone, Debug)] -pub enum PacketType { - Ack = 0xA1, - //Nak = 0xA2, - AckAbort = 0xA3, - Command = 0xA4, - Data = 0xA5, - Ping = 0xA6, - PingResponse = 0xA7, -} - #[repr(u8)] #[derive(Copy, Clone, Debug)] pub enum ResponseCode { @@ -68,25 +56,6 @@ pub enum CommandTag { KeyProvision = 0x15, } -#[repr(C)] -#[derive(Debug, PackedStruct)] -#[packed_struct(size_bytes = "2", bit_numbering = "msb0", endian = "msb")] -pub struct PacketHeader { - #[packed_field(bytes = "0")] - pub start_byte: u8, - #[packed_field(bytes = "1")] - pub packet_type: u8, -} - -impl PacketHeader { - pub fn new(ptype: PacketType) -> PacketHeader { - PacketHeader { - start_byte: 0x5A_u8, - packet_type: ptype as u8, - } - } -} - #[repr(C)] #[derive(Debug, FromPrimitive, Clone, Copy, ValueEnum)] pub enum BootloaderProperty { @@ -111,22 +80,6 @@ pub enum BootloaderProperty { FFRKeyStoreStatus = 29, } -#[repr(C)] -#[derive(Debug, PackedStruct)] -#[packed_struct(bit_numbering = "msb0")] -pub struct PingResponse { - #[packed_field(size_bytes = "2")] - pub header: PacketHeader, - pub protocol_bugfix: u8, - pub protocol_minor: u8, - pub protocol_major: u8, - pub protocol_name: u8, - pub options_low: u8, - pub options_high: u8, - pub crc16_low: u8, - pub crc16_high: u8, -} - #[derive(Debug, PackedStruct, Default)] #[packed_struct(bit_numbering = "msb0", endian = "msb")] #[repr(C)] @@ -150,9 +103,15 @@ impl RawCommand { pub trait Isp { fn do_ping(&mut self) -> Result<(), IspError>; - fn read_data(&mut self) -> Result, IspError>; - fn read_response(&mut self, response_type: ResponseCode) -> Result, IspError>; - fn send_command(&mut self, cmd: CommandTag, args: impl Into>) -> Result<(), IspError>; + fn read_response( + &mut self, + response_type: ResponseCode, + ) -> Result, IspError>; + fn send_command( + &mut self, + cmd: CommandTag, + args: &[u32], + ) -> Result<(), IspError>; fn send_data(&mut self, data: &[u8]) -> Result<(), IspError>; fn recv_data(&mut self, cnt: u32) -> Result, IspError>; } @@ -165,16 +124,6 @@ pub fn retval2err(retval: u32) -> StatusResponse { } } -pub fn require_packet_type(header: &PacketHeader, ty: PacketType) -> Result<(), IspError> { - if header.packet_type != ty as u8 { - return Err(IspError::WrongPacket { - expected: ty, - got: header.packet_type, - }); - } - Ok(()) -} - /// Errors encountered during ISP interaction with the LPC55. #[derive(Debug, Error)] pub enum IspError { @@ -186,7 +135,7 @@ pub enum IspError { /// the next packet should be of type `expected`, we instead got a packet of /// type `got`. #[error("Expected a {expected:?} packet, got {got:#02x}")] - WrongPacket { expected: PacketType, got: u8 }, + WrongPacket { expected: u8, got: u8 }, /// We got the packet we were expecting but it contained an unexpcted /// response code. @@ -257,7 +206,9 @@ pub enum StatusResponse { /// messages. /// /// See LPC55 User Manual chapter 8.7 table 251 for more. -#[derive(Debug, FromPrimitive, Copy, Clone, Eq, PartialEq, ToPrimitive, Error)] +#[derive( + Debug, FromPrimitive, Copy, Clone, Eq, PartialEq, ToPrimitive, Error, +)] pub enum KnownError { #[error("Cumulative write error (did you forget to erase?) (err 10203)")] CumulativeWriteError = 10203, diff --git a/lpc55_isp/src/lib.rs b/lpc55_isp/src/lib.rs index 927a82c..28b981f 100644 --- a/lpc55_isp/src/lib.rs +++ b/lpc55_isp/src/lib.rs @@ -5,3 +5,4 @@ pub mod cmd; pub mod isp; pub mod serial; +pub mod usb; diff --git a/lpc55_isp/src/serial.rs b/lpc55_isp/src/serial.rs index 3f303e9..0637eb7 100644 --- a/lpc55_isp/src/serial.rs +++ b/lpc55_isp/src/serial.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. // This implementation focuses on UART as that is the common interface available -// on hosts, however the packet format should apply to other serial protocols +// on hosts, however the packet format applies to other serial protocols use crate::isp::*; use crc_any::CRCu16; @@ -12,6 +12,66 @@ use serialport::SerialPort; use std::convert::TryInto; use std::io::Read; +#[repr(u8)] +#[derive(Copy, Clone, Debug)] +pub enum PacketType { + Ack = 0xA1, + //Nak = 0xA2, + AckAbort = 0xA3, + Command = 0xA4, + Data = 0xA5, + Ping = 0xA6, + PingResponse = 0xA7, +} + +#[repr(C)] +#[derive(Debug, PackedStruct)] +#[packed_struct(size_bytes = "2", bit_numbering = "msb0", endian = "msb")] +pub struct PacketHeader { + #[packed_field(bytes = "0")] + pub start_byte: u8, + #[packed_field(bytes = "1")] + pub packet_type: u8, +} + +impl PacketHeader { + pub fn new(ptype: PacketType) -> PacketHeader { + PacketHeader { + start_byte: 0x5A_u8, + packet_type: ptype as u8, + } + } +} + +pub fn require_packet_type( + frame: &FramingPacket, + ty: PacketType, +) -> Result<(), IspError> { + if frame.header.packet_type != ty as u8 { + return Err(IspError::WrongPacket { + expected: ty as u8, + got: frame.header.packet_type, + }); + } + Ok(()) +} + +#[repr(C)] +#[derive(Debug, PackedStruct)] +#[packed_struct(bit_numbering = "msb0")] +pub struct PingResponse { + #[packed_field(size_bytes = "2")] + pub header: PacketHeader, + pub protocol_bugfix: u8, + pub protocol_minor: u8, + pub protocol_major: u8, + pub protocol_name: u8, + pub options_low: u8, + pub options_high: u8, + pub crc16_low: u8, + pub crc16_high: u8, +} + #[repr(C)] #[derive(Debug, PackedStruct)] #[packed_struct(bit_numbering = "msb0", endian = "msb")] @@ -68,7 +128,8 @@ impl CommandPacket { let arg_bytes = args.len() * 4; // Total length of the command packet. the 4 bytes are for // the fixed fields - let len: u16 = u16::try_from(4 + arg_bytes).expect("args vec too long for command packet"); + let len: u16 = u16::try_from(4 + arg_bytes) + .expect("args vec too long for command packet"); v.packet.length_low = (len & 0xFF) as u8; v.packet.length_high = ((len >> 8) & 0xff) as u8; @@ -118,7 +179,8 @@ pub struct DataPacket { impl DataPacket { fn new_data(args: impl Into>) -> DataPacket { let args = args.into(); - let arg_len = u16::try_from(args.len()).expect("args vector too long for DataPacket"); + let arg_len = u16::try_from(args.len()) + .expect("args vector too long for DataPacket"); let mut f = FramingPacket::new(PacketType::Data); @@ -192,7 +254,30 @@ fn read_ack(port: &mut Box) -> Result<(), IspError> { Ok(()) } -fn check_crc(frame_bytes: &[u8], response: &[u8], frame: &FramingPacket) -> Result<(), IspError> { +fn read_data(port: &mut Box) -> Result, IspError> { + let mut frame_bytes = + vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; + port.read_exact(&mut frame_bytes)?; + + let frame = FramingPacket::unpack_from_slice(&frame_bytes).unwrap(); + + require_packet_type(&frame, PacketType::Data)?; + + let length = + usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); + let mut response = vec![0; length]; + port.read_exact(&mut response)?; + + check_crc(&frame_bytes, &response, &frame)?; + + Ok(response) +} + +fn check_crc( + frame_bytes: &[u8], + response: &[u8], + frame: &FramingPacket, +) -> Result<(), IspError> { let mut crc = CRCu16::crc16xmodem(); crc.digest(&frame_bytes[..0x4]); crc.digest(&frame_bytes[0x6..]); @@ -226,7 +311,8 @@ impl Isp for Box { self.read_exact(&mut response_bytes)?; - let response = PingResponse::unpack(&response_bytes).map_err(IspError::Unpack)?; + let response = + PingResponse::unpack(&response_bytes).map_err(IspError::Unpack)?; if response.header.packet_type != (PacketType::PingResponse as u8) { return Err(IspError::BadAck(response.header.packet_type)); @@ -235,35 +321,25 @@ impl Isp for Box { Ok(()) } - fn read_data(&mut self) -> Result, IspError> { - let mut frame_bytes = vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; - self.read_exact(&mut frame_bytes)?; - - let frame = FramingPacket::unpack_from_slice(&frame_bytes).unwrap(); - - require_packet_type(&frame.header, PacketType::Data)?; - - let length = usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); - let mut response = vec![0; length]; - self.read_exact(&mut response)?; - - check_crc(&frame_bytes, &response, &frame)?; - - Ok(response) - } - // Okay _technically_ the response can return values from get-property but for // now just return (). If we _really_ need properties we can add that later - fn read_response(&mut self, response_type: ResponseCode) -> Result, IspError> { - let mut frame_bytes = vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; + fn read_response( + &mut self, + response_type: ResponseCode, + ) -> Result, IspError> { + let mut frame_bytes = + vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; self.read_exact(&mut frame_bytes)?; let frame = FramingPacket::unpack_from_slice(&frame_bytes).unwrap(); // A response packet is a specific type of command packet. - require_packet_type(&frame.header, PacketType::Command)?; + require_packet_type(&frame, PacketType::Command)?; - let length: usize = usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); + let length: usize = usize::from(u16::from_le_bytes([ + frame.length_low, + frame.length_high, + ])); let mut response = vec![0; length]; self.read_exact(&mut response)?; @@ -287,12 +363,12 @@ impl Isp for Box { let index = RawCommand::packed_bytes_size(None).unwrap(); let end_of_params = index + usize::from(command.parameter_count) * 4; - let param_bytes = response - .get(index..end_of_params) - .ok_or(IspError::TruncatedParams { + let param_bytes = response.get(index..end_of_params).ok_or( + IspError::TruncatedParams { expected_len: end_of_params, actual_len: response.len(), - })?; + }, + )?; for p in param_bytes.chunks_exact(4) { params.push(u32::from_le_bytes(p.try_into().unwrap())); @@ -310,7 +386,11 @@ impl Isp for Box { } } - fn send_command(&mut self, cmd: CommandTag, args: impl Into>) -> Result<(), IspError> { + fn send_command( + &mut self, + cmd: CommandTag, + args: &[u32], + ) -> Result<(), IspError> { let command_bytes = CommandPacket::new_command(cmd, args).to_bytes(); self.write_all(&command_bytes)?; @@ -342,7 +422,7 @@ impl Isp for Box { let mut data = Vec::with_capacity(cnt); while data.len() < cnt { - data.extend_from_slice(&self.read_data()?); + data.extend_from_slice(&read_data(self)?); send_ack(self)?; } diff --git a/lpc55_isp/src/usb.rs b/lpc55_isp/src/usb.rs new file mode 100644 index 0000000..31d1500 --- /dev/null +++ b/lpc55_isp/src/usb.rs @@ -0,0 +1,449 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// The USB protocol is only partially documented in the manual. The main part +// that they leave out is that there are no ACKs. There is no documented ping +// command format, and `blhost` does not seem to try use a ping when +// communicating over USB. + +use crate::isp::*; +use nusb::transfer::{In, Interrupt, Out}; +use nusb::{list_devices, MaybeFuture}; +use packed_struct::prelude::*; +use std::convert::TryInto; +use std::io::{self, ErrorKind, Read, Write}; +use std::time::Duration; +use thiserror::Error; + +const DEFAULT_USB_TIMEOUT: Duration = Duration::from_millis(100); +const MAX_TX_SIZE: usize = 60; // per the docs + +#[repr(u8)] +#[derive(Copy, Clone, Debug)] +pub enum ReportId { + CommandOut = 0x01, + DataOut = 0x02, + CommandIn = 0x03, + DataIn = 0x04, +} + +pub fn require_packet_type( + frame: &FramingPacket, + ty: ReportId, +) -> Result<(), IspError> { + if frame.report_id != ty as u8 { + return Err(IspError::WrongPacket { + expected: ty as u8, + got: frame.report_id, + }); + } + Ok(()) +} + +#[repr(C)] +#[derive(Debug, PackedStruct)] +#[packed_struct(bit_numbering = "msb0", endian = "msb")] +pub struct FramingPacket { + #[packed_field(size_bytes = "1")] + report_id: u8, + pad: u8, + length_low: u8, + length_high: u8, +} + +impl FramingPacket { + fn new(ctype: ReportId) -> FramingPacket { + FramingPacket { + report_id: ctype as u8, + pad: 0, + length_low: 0, + length_high: 0, + } + } +} + +// Command packets can take a variable number +// of arguments. This is unfortunately a pain to serialize +// in a structure. So we cheat a little and and only make the +// existing structure a packed_struct and later append the +// arguments manually +#[derive(Debug, PackedStruct)] +#[packed_struct(bit_numbering = "msb0", endian = "msb")] +#[repr(C)] +pub struct VariablePacket { + #[packed_field(size_bytes = "4")] + packet: FramingPacket, + #[packed_field(size_bytes = "4")] + raw_command: RawCommand, +} + +pub struct CommandPacket { + packet: VariablePacket, + params: Vec, +} + +impl CommandPacket { + fn new_command(c: CommandTag, args: impl Into>) -> CommandPacket { + let args = args.into(); + + let mut v = VariablePacket { + packet: FramingPacket::new(ReportId::CommandOut), + raw_command: RawCommand::new(c, args.len()), + }; + + let arg_bytes = args.len() * 4; + // Total length of the command packet. the 4 bytes are for + // the fixed fields + // TODO check that length is less than max for USB + let len: u16 = u16::try_from(4 + arg_bytes) + .expect("args vec too long for command packet"); + + v.packet.length_low = (len & 0xFF) as u8; + v.packet.length_high = ((len >> 8) & 0xff) as u8; + + CommandPacket { + packet: v, + params: args, + } + } + + fn to_bytes(&self) -> Vec { + let mut v = Vec::new(); + + v.extend_from_slice(&self.packet.pack().unwrap()); + + for e in self.params.iter() { + v.extend_from_slice(&e.to_le_bytes()); + } + + v + } +} + +pub struct DataPacket { + packet: FramingPacket, + data: Vec, +} + +impl DataPacket { + fn new_data(args: impl Into>) -> DataPacket { + let args = args.into(); + let arg_len = u16::try_from(args.len()) + .expect("args vector too long for DataPacket"); + + let mut f = FramingPacket::new(ReportId::DataOut); + + f.length_low = (arg_len & 0xFF) as u8; + f.length_high = ((arg_len >> 8) & 0xff) as u8; + + DataPacket { + packet: f, + data: args, + } + } + + fn to_bytes(&self) -> Vec { + let mut v = Vec::new(); + + v.extend_from_slice(&self.packet.pack().unwrap()); + v.extend_from_slice(&self.data); + + v + } +} + +impl Isp for UsbIsp { + // TODO figure out what the ping method is on USB + fn do_ping(&mut self) -> Result<(), IspError> { + Ok(()) + } + + fn read_response( + &mut self, + response_type: ResponseCode, + ) -> Result, IspError> { + let frame_size = FramingPacket::packed_bytes_size(None).unwrap(); + + let mut rx_bytes = vec![0; MAX_TX_SIZE]; + self.reader.read_exact(&mut rx_bytes)?; + + let frame = + FramingPacket::unpack_from_slice(&rx_bytes[..frame_size]).unwrap(); + + // A response packet is a specific type of command packet. + require_packet_type(&frame, ReportId::CommandIn)?; + + let length: usize = usize::from(u16::from_le_bytes([ + frame.length_low, + frame.length_high, + ])); + let response = &rx_bytes[frame_size..frame_size + length]; + + let command = RawCommand::unpack_from_slice( + &response[..RawCommand::packed_bytes_size(None).unwrap()], + ) + .map_err(IspError::Unpack)?; + + // Note: we tolerate A0 (generic response) here because many commands + // return it on failure instead of the expected response type. + if command.tag != (response_type as u8) && command.tag != 0xA0 { + return Err(IspError::WrongResponse { + expected: response_type, + got: command.tag, + }); + } + + let mut params: Vec = Vec::new(); + let index = RawCommand::packed_bytes_size(None).unwrap(); + + let end_of_params = index + usize::from(command.parameter_count) * 4; + let param_bytes = response.get(index..end_of_params).ok_or( + IspError::TruncatedParams { + expected_len: end_of_params, + actual_len: response.len(), + }, + )?; + + for p in param_bytes.chunks_exact(4) { + params.push(u32::from_le_bytes(p.try_into().unwrap())); + } + + // First parameter is always the return code; + let retval = params[0]; + + if retval != 0 { + Err(retval2err(retval).into()) + } else { + Ok(params) + } + } + + fn send_command( + &mut self, + cmd: CommandTag, + args: &[u32], + ) -> Result<(), IspError> { + let command_bytes = CommandPacket::new_command(cmd, args).to_bytes(); + + self.writer.write_all(&command_bytes)?; + self.writer.flush()?; + + Ok(()) + } + + fn send_data(&mut self, data: &[u8]) -> Result<(), IspError> { + let frame_size = FramingPacket::packed_bytes_size(None).unwrap(); + // we must send transfer sized chunks. + for chunk in data.chunks(MAX_TX_SIZE - frame_size) { + let data_bytes = DataPacket::new_data(chunk).to_bytes(); + + self.writer.write_all(&data_bytes)?; + self.writer.flush()?; + } + + Ok(()) + } + + fn recv_data(&mut self, cnt: u32) -> Result, IspError> { + let cnt = cnt as usize; + let mut data = Vec::with_capacity(cnt); + let mut buffer = [0u8; MAX_TX_SIZE]; + let frame_size = FramingPacket::packed_bytes_size(None).unwrap(); + + while data.len() < cnt { + self.reader.read_exact(&mut buffer)?; + + let frame = FramingPacket::unpack_from_slice(&buffer[..frame_size]) + .unwrap(); + // A response packet is a specific type of command packet. + require_packet_type(&frame, ReportId::DataIn)?; + + let length: usize = usize::from(u16::from_le_bytes([ + frame.length_low, + frame.length_high, + ])); + let response = &buffer[frame_size..frame_size + length]; + + data.extend_from_slice(response); + } + + Ok(data) + } +} + +/// Used for USB interface to ISP devices +pub struct UsbIsp { + writer: nusb::io::EndpointWrite, + reader: nusb::io::EndpointRead, +} + +#[derive(Clone, Copy, PartialEq)] +pub struct UsbId { + pub vendor_id: u16, + pub product_id: u16, +} + +// because we are accustomed to seeing USB ID's in hex +impl std::fmt::Debug for UsbId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "UsbId {{ vendor_id: 0x{:04x}, product_id: 0x{:04x} }}", + self.vendor_id, self.product_id + ) + } +} + +// ideally we would use the serial here, but the ISP ROM bootloader doesn't seem +// to provide one. in the case where multiple devices are present we let the +// user optionally supply a port chain +#[derive(Default, Debug, Clone)] +pub struct DeviceSelector { + pub usb_id: Option, + pub port_chain: Option>, +} + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum ParseError { + #[error("invalid device selector `{0}`")] + InvalidFormat(String), + #[error("invalid vendor ID `{0}`")] + InvalidVendorId(String), + #[error("invalid device ID `{0}`")] + InvalidDeviceId(String), + #[error("invalid port chain `{0}`")] + InvalidPortChain(String), +} + +fn parse_port_chain(s: &str) -> Result, ParseError> { + s.trim() + .split("-") + .map(|v| { + v.parse::() + .map_err(|_| ParseError::InvalidPortChain(s.to_owned())) + }) + .collect() +} + +impl std::str::FromStr for DeviceSelector { + type Err = ParseError; + + /// Parse a DeviceSelector from a string that looks like + /// `vid:pid[:port-chain-items]` + /// + /// `vid` and `pid` are hex vendor/product IDs without 0x prefix. the port + /// chain is base 10 integers separated by '-' such as "1" or "1-5-2" with + /// left most being the first in the chain + fn from_str(s: &str) -> Result { + let toks: Vec<&str> = s.trim().split(":").collect(); + // ensure we have a reasonable number of tokens + if toks.len() < 2 || toks.len() > 3 { + return Err(ParseError::InvalidFormat(s.to_owned())); + } + Ok(DeviceSelector { + usb_id: Some(UsbId { + vendor_id: u16::from_str_radix(toks[0], 16).map_err(|_| { + ParseError::InvalidVendorId(toks[0].to_owned()) + })?, + product_id: u16::from_str_radix(toks[1], 16).map_err(|_| { + ParseError::InvalidDeviceId(toks[1].to_owned()) + })?, + }), + port_chain: if let Some(port_chain) = toks.get(2) { + Some(parse_port_chain(port_chain)?) + } else { + None + }, + }) + } +} + +impl UsbIsp { + pub fn new(dev_sel: &DeviceSelector) -> Result { + let device = list_devices() + .wait()? + .find(|dev| { + let id_match = if let Some(ref id_sel) = dev_sel.usb_id { + dev.vendor_id() == id_sel.vendor_id + && dev.product_id() == id_sel.product_id + } else { + true + }; + let port_match = if let Some(ref chain_sel) = dev_sel.port_chain + { + chain_sel == dev.port_chain() + } else { + true + }; + id_match && port_match + }) + .ok_or(io::Error::new(ErrorKind::NotFound, "device not found"))?; + + let device = device.open().wait()?; + let interface = device.detach_and_claim_interface(0).wait()?; + let writer = interface + .endpoint::(0x02)? + .writer(MAX_TX_SIZE) + .with_write_timeout(DEFAULT_USB_TIMEOUT); + let mut reader = interface + .endpoint::(0x81)? + .reader(MAX_TX_SIZE) + .with_num_transfers(2) + .with_read_timeout(DEFAULT_USB_TIMEOUT); + // drain any existing messages + let mut buffer = [0; 128]; + while reader.read(&mut buffer).is_ok() {} + Ok(UsbIsp { writer, reader }) + } + + pub fn close(self) {} +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_device_selector() { + let ds: DeviceSelector = "AA:55".parse().unwrap(); + assert_eq!( + ds.usb_id, + Some(UsbId { + vendor_id: 0xAA, + product_id: 0x55 + }) + ); + assert_eq!(ds.port_chain, None); + let ds: DeviceSelector = "AA:55:1-2".parse().unwrap(); + assert_eq!( + ds.usb_id, + Some(UsbId { + vendor_id: 0xAA, + product_id: 0x55 + }) + ); + assert_eq!(ds.port_chain, Some(vec![1, 2])); + let ds: DeviceSelector = "AA:55:1".parse().unwrap(); + assert_eq!( + ds.usb_id, + Some(UsbId { + vendor_id: 0xAA, + product_id: 0x55 + }) + ); + assert_eq!(ds.port_chain, Some(vec![1])); + let err = "AZ:55".parse::().err().unwrap(); + assert!(matches!(err, ParseError::InvalidVendorId(_))); + let err = "AA:5Z".parse::().err().unwrap(); + assert!(matches!(err, ParseError::InvalidDeviceId(_))); + let err = "AAAAA:55".parse::().err().unwrap(); + assert!(matches!(err, ParseError::InvalidVendorId(_))); + let err = "AA:5Z::".parse::().err().unwrap(); + assert!(matches!(err, ParseError::InvalidFormat(_))); + let err = "Foo".parse::().err().unwrap(); + assert!(matches!(err, ParseError::InvalidFormat(_))); + let err = "AA:55:foo".parse::().err().unwrap(); + assert!(matches!(err, ParseError::InvalidPortChain(_))); + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index b67e7d5..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "1.89.0" From 90f8cafe21edbe12eb28e84abb25595c6c02cd42 Mon Sep 17 00:00:00 2001 From: Stuart Donnan Date: Tue, 11 Aug 2026 12:10:19 -0700 Subject: [PATCH 03/10] make serial protocol generic to interface --- lpc55_isp/src/serial.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lpc55_isp/src/serial.rs b/lpc55_isp/src/serial.rs index 0637eb7..c21cc12 100644 --- a/lpc55_isp/src/serial.rs +++ b/lpc55_isp/src/serial.rs @@ -8,9 +8,8 @@ use crate::isp::*; use crc_any::CRCu16; use packed_struct::prelude::*; -use serialport::SerialPort; use std::convert::TryInto; -use std::io::Read; +use std::io::{Read, Write}; #[repr(u8)] #[derive(Copy, Clone, Debug)] @@ -216,7 +215,7 @@ impl DataPacket { } } -fn send_ack(port: &mut Box) -> Result<(), IspError> { +fn send_ack(port: &mut RW) -> Result<(), IspError> { let packet = PacketHeader::new(PacketType::Ack); let bytes = packet.pack().unwrap(); @@ -227,7 +226,7 @@ fn send_ack(port: &mut Box) -> Result<(), IspError> { Ok(()) } -fn read_ack(port: &mut Box) -> Result<(), IspError> { +fn read_ack(port: &mut RW) -> Result<(), IspError> { let mut ack_bytes: [u8; 2] = [0; 2]; port.read_exact(&mut ack_bytes)?; @@ -254,7 +253,7 @@ fn read_ack(port: &mut Box) -> Result<(), IspError> { Ok(()) } -fn read_data(port: &mut Box) -> Result, IspError> { +fn read_data(port: &mut RW) -> Result, IspError> { let mut frame_bytes = vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; port.read_exact(&mut frame_bytes)?; @@ -297,7 +296,7 @@ fn check_crc( Ok(()) } -impl Isp for Box { +impl Isp for RW { fn do_ping(&mut self) -> Result<(), IspError> { let ping = PacketHeader::new(PacketType::Ping); @@ -429,4 +428,3 @@ impl Isp for Box { Ok(data) } } - From 2ffb177fe07d6b3e597277bc84c40258946bd478 Mon Sep 17 00:00:00 2001 From: Stuart Donnan Date: Tue, 11 Aug 2026 17:49:07 -0700 Subject: [PATCH 04/10] refactor a bit with cli improvements --- Cargo.lock | 1 + lpc55_isp/Cargo.toml | 1 + lpc55_isp/src/bin/lpc55_flash.rs | 193 ++++++++++++------------------- lpc55_isp/src/isp.rs | 2 +- lpc55_isp/src/lib.rs | 1 + lpc55_isp/src/usb.rs | 97 +++++++++++----- lpc55_isp/src/util.rs | 169 +++++++++++++++++++++++++++ 7 files changed, 317 insertions(+), 147 deletions(-) create mode 100644 lpc55_isp/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index c9f9924..3f9ac02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -510,6 +510,7 @@ dependencies = [ "crc-any", "env_logger", "hex", + "log", "lpc55_areas", "num-derive", "num-traits", diff --git a/lpc55_isp/Cargo.toml b/lpc55_isp/Cargo.toml index 50c9738..9f1e01f 100644 --- a/lpc55_isp/Cargo.toml +++ b/lpc55_isp/Cargo.toml @@ -12,6 +12,7 @@ clap.workspace = true crc-any.workspace = true env_logger.workspace = true hex.workspace = true +log.workspace = true lpc55_areas.workspace = true num-derive.workspace = true num-traits.workspace = true diff --git a/lpc55_isp/src/bin/lpc55_flash.rs b/lpc55_isp/src/bin/lpc55_flash.rs index e6ae527..6e09744 100644 --- a/lpc55_isp/src/bin/lpc55_flash.rs +++ b/lpc55_isp/src/bin/lpc55_flash.rs @@ -2,16 +2,14 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use anyhow::{bail, Context, Result}; +use anyhow::{bail, Context, Result, anyhow}; use byteorder::ByteOrder; -use clap::Parser; +use clap::{Parser, CommandFactory}; use lpc55_isp::cmd::*; use lpc55_isp::isp::{BootloaderProperty, Isp, KeyType}; -use lpc55_isp::usb::UsbIsp; -use serialport::{DataBits, FlowControl, Parity, StopBits}; -use std::io::{ErrorKind, Read, Write}; +use lpc55_isp::util::*; +use std::io::{Read, Write}; use std::path::PathBuf; -use std::time::Duration; #[derive(Debug, Parser)] enum ISPCommand { @@ -99,6 +97,8 @@ enum ISPCommand { prop: BootloaderProperty, }, LastError, + /// List available devices + List, } #[derive(Copy, Clone, Debug, clap::ValueEnum)] @@ -108,67 +108,26 @@ enum CfpaChoice { Pong, } -enum Interface { - Usb(UsbIsp), - Serial(Box), -} - -impl Isp for Interface { - fn do_ping(&mut self) -> std::result::Result<(), lpc55_isp::isp::IspError> { - match self { - Interface::Usb(i) => i.do_ping(), - Interface::Serial(i) => i.do_ping(), - } - } - - fn send_data(&mut self, data: &[u8]) -> std::result::Result<(), lpc55_isp::isp::IspError> { - match self { - Interface::Usb(i) => i.send_data(data), - Interface::Serial(i) => i.send_data(data), - } - } - - fn recv_data(&mut self, cnt: u32) -> std::result::Result, lpc55_isp::isp::IspError> { - match self { - Interface::Usb(i) => i.recv_data(cnt), - Interface::Serial(i) => i.recv_data(cnt), - } - } - - fn send_command( - &mut self, - cmd: lpc55_isp::isp::CommandTag, - args: &[u32], - ) -> std::result::Result<(), lpc55_isp::isp::IspError> { - match self { - Interface::Usb(i) => i.send_command(cmd, args), - Interface::Serial(i) => i.send_command(cmd, args), - } - } - - fn read_response( - &mut self, - response_type: lpc55_isp::isp::ResponseCode, - ) -> std::result::Result, lpc55_isp::isp::IspError> { - match self { - Interface::Usb(i) => i.read_response(response_type), - Interface::Serial(i) => i.read_response(response_type), - } - } +#[derive(Copy, Clone, Debug, clap::ValueEnum)] +enum InterfaceKind { + Serial, + Usb, } #[derive(Debug, Parser)] #[clap(name = "isp")] struct IspArgs { - #[arg(long)] - serial: Option, - #[arg(long, conflicts_with = "serial")] - usb: Option, - /// How fast to run the UART. 57,600 baud seems very reliable but is rather - /// slow. In certain test setups we've gotten rates of up to 1Mbaud to work - /// reliably -- your mileage may vary! - #[clap(short = 'b', default_value = "57600")] - baud_rate: u32, + /// A serial port name/path like COM1 or /dev/ttyUSB0 or usb selector + /// vid:pid[:bus@path-to-port] such as 1fc9:0021:001@7-22 + #[arg(name = "port")] + port: Option, + /// Type of connection to use + #[arg(value_enum, short, long, default_value_t = InterfaceKind::Serial)] + kind: InterfaceKind, + /// Baud rate for serial if not the default + #[clap(short = 'b')] + baud_rate: Option, + /// Command to run #[clap(subcommand)] cmd: ISPCommand, } @@ -294,52 +253,28 @@ fn main() -> Result<()> { env_logger::init(); - let mut port: Interface = if let Some(port) = cmd.serial { - // The target _technically_ has autobaud but it's very flaky - // and these seem to be the preferred settings - // - // We initially set the timeout short so we can drain the incoming buffer in - // a portable manner below. We'll adjust it up after that. - let mut port = serialport::new(&port, cmd.baud_rate) - .timeout(Duration::from_millis(100)) - .data_bits(DataBits::Eight) - .flow_control(FlowControl::None) - .parity(Parity::None) - .stop_bits(StopBits::One) - .open()?; - - // Extract any bytes left over in the serial port driver from previous - // interaction. - loop { - let mut throwaway = [0; 16]; - match port.read(&mut throwaway) { - Ok(0) => { - // This should only happen on nonblocking reads, which we - // haven't asked for, but it does mean the buffer is empty so - // treat it as success. - break; - } - Ok(_) => { - // We've collected some characters to throw away, keep going. - } - Err(e) if e.kind() == ErrorKind::TimedOut => { - // Buffer is empty! - break; - } - Err(e) => { - return Err(e.into()); - } - } - } - // Crank the timeout back up. - port.set_timeout(Duration::from_secs(1))?; - Interface::Serial(port) - } else if let Some(port) = cmd.usb { - Interface::Usb(UsbIsp::new(&port.parse()?)?) - } else { - bail!("You must choose --serial or --usb"); + if matches!(cmd.cmd, ISPCommand::List) { + println!("{}", list_interfaces()?); + return Ok(()) + } + + if cmd.port.is_none() { + println!("{}", IspArgs::command().render_usage()); + return Err(anyhow!("Port required")); + } + + let options = match cmd.kind { + InterfaceKind::Serial => InterfaceOptions::Serial { + name: cmd.port.unwrap(), + baud_rate: cmd.baud_rate, + }, + InterfaceKind::Usb => InterfaceOptions::Usb { + selector: cmd.port.unwrap().parse()?, + }, }; + let mut port = open_interface(options)?; + match cmd.cmd { ISPCommand::Ping => { port.do_ping()?; @@ -446,8 +381,12 @@ fn main() -> Result<()> { .context("reading CFPA ping page")?; let pong = do_isp_read_memory(&mut port, 0x9e200, 512) .context("reading CFPA pong page")?; - let ping_d = lpc55_areas::CFPAPage::from_bytes(ping[..].try_into().unwrap())?; - let pong_d = lpc55_areas::CFPAPage::from_bytes(pong[..].try_into().unwrap())?; + let ping_d = lpc55_areas::CFPAPage::from_bytes( + ping[..].try_into().unwrap(), + )?; + let pong_d = lpc55_areas::CFPAPage::from_bytes( + pong[..].try_into().unwrap(), + )?; println!( "CFPA versions: ping={}, pong={}", ping_d.version, pong_d.version @@ -484,8 +423,10 @@ fn main() -> Result<()> { // Read the CMPA so we can compare the two to try to avoid locking // the user out of their chip. let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; - let cmpa = lpc55_areas::CMPAPage::from_bytes(m[..].try_into().unwrap())?; - if (new_cfpa.dcfg_cc_socu_ns_pin != 0 || new_cfpa.dcfg_cc_socu_ns_dflt != 0) + let cmpa = + lpc55_areas::CMPAPage::from_bytes(m[..].try_into().unwrap())?; + if (new_cfpa.dcfg_cc_socu_ns_pin != 0 + || new_cfpa.dcfg_cc_socu_ns_dflt != 0) && (cmpa.cc_socu_pin == 0 || cmpa.cc_socu_dflt == 0) { bail!( @@ -502,8 +443,12 @@ fn main() -> Result<()> { let ping = do_isp_read_memory(&mut port, 0x9_e000, 512)?; let pong = do_isp_read_memory(&mut port, 0x9_e200, 512)?; - let ping = lpc55_areas::CFPAPage::from_bytes(ping[..].try_into().unwrap())?; - let pong = lpc55_areas::CFPAPage::from_bytes(pong[..].try_into().unwrap())?; + let ping = lpc55_areas::CFPAPage::from_bytes( + ping[..].try_into().unwrap(), + )?; + let pong = lpc55_areas::CFPAPage::from_bytes( + pong[..].try_into().unwrap(), + )?; println!( "ping sector v={}, pong sector v={}", @@ -531,15 +476,24 @@ fn main() -> Result<()> { // Choose a RAM address for the stack (we shouldn't use the stack // but it should be valid anyway) - byteorder::LittleEndian::write_u32(&mut bytes[0x0..0x4], 0x20004000); + byteorder::LittleEndian::write_u32( + &mut bytes[0x0..0x4], + 0x20004000, + ); // Everything else targets the loop to branch instruction at 0x00000130 let mut offset = 4; while offset < 0x130 { - byteorder::LittleEndian::write_u32(&mut bytes[offset..offset + 4], 0x00000131); + byteorder::LittleEndian::write_u32( + &mut bytes[offset..offset + 4], + 0x00000131, + ); offset += 4; } // This is two branch to self instructions - byteorder::LittleEndian::write_u32(&mut bytes[0x130..0x134], 0xe7fee7fe); + byteorder::LittleEndian::write_u32( + &mut bytes[0x130..0x134], + 0xe7fee7fe, + ); println!("Writing bytes"); do_isp_write_memory(&mut port, 0x0, &bytes)?; @@ -594,7 +548,8 @@ fn main() -> Result<()> { ISPCommand::SetSBKek { file } => { port.do_ping()?; - let mut infile = std::fs::OpenOptions::new().read(true).open(file)?; + let mut infile = + std::fs::OpenOptions::new().read(true).open(file)?; let mut raw_bytes = Vec::new(); @@ -619,7 +574,8 @@ fn main() -> Result<()> { do_generate_uds(&mut port)?; // Step 3: Set the SBKEK - let mut infile = std::fs::OpenOptions::new().read(true).open(file)?; + let mut infile = + std::fs::OpenOptions::new().read(true).open(file)?; let mut raw_bytes = Vec::new(); @@ -647,6 +603,9 @@ fn main() -> Result<()> { let result = do_isp_last_error(&mut port)?; pretty_print_error(result); } + ISPCommand::List => { + unreachable!() // we handle list above + } } Ok(()) diff --git a/lpc55_isp/src/isp.rs b/lpc55_isp/src/isp.rs index 9d044fa..74f949f 100644 --- a/lpc55_isp/src/isp.rs +++ b/lpc55_isp/src/isp.rs @@ -181,7 +181,7 @@ pub enum IspError { #[error("unpacking response failed")] Unpack(#[source] packed_struct::PackingError), - /// Our actual use of the serial port failed. + /// Our actual use of the I/O device failed. #[error("Communications error")] Comms(#[from] std::io::Error), } diff --git a/lpc55_isp/src/lib.rs b/lpc55_isp/src/lib.rs index 28b981f..8fd9678 100644 --- a/lpc55_isp/src/lib.rs +++ b/lpc55_isp/src/lib.rs @@ -6,3 +6,4 @@ pub mod cmd; pub mod isp; pub mod serial; pub mod usb; +pub mod util; diff --git a/lpc55_isp/src/usb.rs b/lpc55_isp/src/usb.rs index 31d1500..c95c6e7 100644 --- a/lpc55_isp/src/usb.rs +++ b/lpc55_isp/src/usb.rs @@ -295,13 +295,19 @@ impl std::fmt::Debug for UsbId { } } -// ideally we would use the serial here, but the ISP ROM bootloader doesn't seem -// to provide one. in the case where multiple devices are present we let the -// user optionally supply a port chain -#[derive(Default, Debug, Clone)] +// ideally we would use the serial number here, but the ISP ROM bootloader +// doesn't seem to provide one. in the case where multiple devices are present +// we let the user optionally supply a physical location +#[derive(Debug, Clone)] pub struct DeviceSelector { pub usb_id: Option, - pub port_chain: Option>, + pub location: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Location { + pub bus_id: String, + pub port_chain: Vec, } #[derive(Error, Debug, PartialEq, Eq)] @@ -313,28 +319,46 @@ pub enum ParseError { #[error("invalid device ID `{0}`")] InvalidDeviceId(String), #[error("invalid port chain `{0}`")] - InvalidPortChain(String), + InvalidLocation(String), } -fn parse_port_chain(s: &str) -> Result, ParseError> { - s.trim() - .split("-") - .map(|v| { - v.parse::() - .map_err(|_| ParseError::InvalidPortChain(s.to_owned())) - }) - .collect() +impl std::str::FromStr for Location { + type Err = ParseError; + + /// parse a string like "001@2-3-4" where "001" is the bus ID and "2-3-4" is + /// the port chain in base 10 integers + fn from_str(s: &str) -> Result { + let toks: Vec<&str> = s.trim().split('@').collect(); + + if toks.is_empty() { + return Err(ParseError::InvalidLocation("[empty]".to_owned())); + } + + if toks.len() != 2 { + return Err(ParseError::InvalidLocation(s.to_owned())); + } + + let bus_id = toks[0].to_owned(); + let port_chain = toks[1] + .trim() + .split("-") + .map(|v| { + v.parse::() + .map_err(|_| ParseError::InvalidLocation(s.to_owned())) + }) + .collect::>()?; + + Ok(Location { bus_id, port_chain }) + } } impl std::str::FromStr for DeviceSelector { type Err = ParseError; /// Parse a DeviceSelector from a string that looks like - /// `vid:pid[:port-chain-items]` + /// `vid:pid[:bus@port-chain-items]` /// - /// `vid` and `pid` are hex vendor/product IDs without 0x prefix. the port - /// chain is base 10 integers separated by '-' such as "1" or "1-5-2" with - /// left most being the first in the chain + /// `vid` and `pid` are hex vendor/product IDs without 0x prefix. fn from_str(s: &str) -> Result { let toks: Vec<&str> = s.trim().split(":").collect(); // ensure we have a reasonable number of tokens @@ -350,8 +374,8 @@ impl std::str::FromStr for DeviceSelector { ParseError::InvalidDeviceId(toks[1].to_owned()) })?, }), - port_chain: if let Some(port_chain) = toks.get(2) { - Some(parse_port_chain(port_chain)?) + location: if let Some(location) = toks.get(2) { + Some(location.parse()?) } else { None }, @@ -370,9 +394,9 @@ impl UsbIsp { } else { true }; - let port_match = if let Some(ref chain_sel) = dev_sel.port_chain - { - chain_sel == dev.port_chain() + let port_match = if let Some(ref loc_sel) = dev_sel.location { + loc_sel.bus_id == dev.bus_id() + && loc_sel.port_chain == dev.port_chain() } else { true }; @@ -414,8 +438,9 @@ mod tests { product_id: 0x55 }) ); - assert_eq!(ds.port_chain, None); - let ds: DeviceSelector = "AA:55:1-2".parse().unwrap(); + assert_eq!(ds.location, None); + + let ds: DeviceSelector = "AA:55:001@1-2".parse().unwrap(); assert_eq!( ds.usb_id, Some(UsbId { @@ -423,8 +448,15 @@ mod tests { product_id: 0x55 }) ); - assert_eq!(ds.port_chain, Some(vec![1, 2])); - let ds: DeviceSelector = "AA:55:1".parse().unwrap(); + assert_eq!( + ds.location, + Some(Location { + bus_id: "001".to_owned(), + port_chain: vec![1, 2] + }) + ); + + let ds: DeviceSelector = "AA:55:002@1".parse().unwrap(); assert_eq!( ds.usb_id, Some(UsbId { @@ -432,7 +464,14 @@ mod tests { product_id: 0x55 }) ); - assert_eq!(ds.port_chain, Some(vec![1])); + assert_eq!( + ds.location, + Some(Location { + bus_id: "002".to_owned(), + port_chain: vec![1] + }) + ); + let err = "AZ:55".parse::().err().unwrap(); assert!(matches!(err, ParseError::InvalidVendorId(_))); let err = "AA:5Z".parse::().err().unwrap(); @@ -444,6 +483,6 @@ mod tests { let err = "Foo".parse::().err().unwrap(); assert!(matches!(err, ParseError::InvalidFormat(_))); let err = "AA:55:foo".parse::().err().unwrap(); - assert!(matches!(err, ParseError::InvalidPortChain(_))); + assert!(matches!(err, ParseError::InvalidLocation(_))); } } diff --git a/lpc55_isp/src/util.rs b/lpc55_isp/src/util.rs new file mode 100644 index 0000000..a2b2fbc --- /dev/null +++ b/lpc55_isp/src/util.rs @@ -0,0 +1,169 @@ +use crate::isp::{CommandTag, Isp, IspError, ResponseCode}; +use crate::usb::{DeviceSelector, UsbIsp}; +use anyhow::Result; +use nusb::MaybeFuture; +use serialport::{DataBits, FlowControl, Parity, StopBits}; +use std::time::Duration; + +pub enum InterfaceOptions { + Usb { + selector: DeviceSelector, + }, + Serial { + name: String, + baud_rate: Option, + }, +} + +pub enum Interface { + Usb(UsbIsp), + Serial(Box), +} + +impl Isp for Interface { + fn do_ping(&mut self) -> std::result::Result<(), IspError> { + match self { + Interface::Usb(i) => i.do_ping(), + Interface::Serial(i) => i.do_ping(), + } + } + + fn send_data(&mut self, data: &[u8]) -> std::result::Result<(), IspError> { + match self { + Interface::Usb(i) => i.send_data(data), + Interface::Serial(i) => i.send_data(data), + } + } + + fn recv_data( + &mut self, + cnt: u32, + ) -> std::result::Result, IspError> { + match self { + Interface::Usb(i) => i.recv_data(cnt), + Interface::Serial(i) => i.recv_data(cnt), + } + } + + fn send_command( + &mut self, + cmd: CommandTag, + args: &[u32], + ) -> std::result::Result<(), IspError> { + match self { + Interface::Usb(i) => i.send_command(cmd, args), + Interface::Serial(i) => i.send_command(cmd, args), + } + } + + fn read_response( + &mut self, + response_type: ResponseCode, + ) -> std::result::Result, IspError> { + match self { + Interface::Usb(i) => i.read_response(response_type), + Interface::Serial(i) => i.read_response(response_type), + } + } +} + +pub fn open_interface(interface: InterfaceOptions) -> Result { + match interface { + InterfaceOptions::Serial { name, baud_rate } => { + // The target _technically_ has autobaud but it's very flaky and + // these seem to be the preferred settings. 57,600 baud seems very + // reliable but is rather slow. In certain test setups we've gotten + // rates of up to 1Mbaud to work reliably -- your mileage may vary! + // + // We initially set the timeout short so we can drain the incoming + // buffer in a portable manner below. We'll adjust it up after that. + let mut port = serialport::new(name, baud_rate.unwrap_or(57600)) + .timeout(Duration::from_millis(100)) + .data_bits(DataBits::Eight) + .flow_control(FlowControl::None) + .parity(Parity::None) + .stop_bits(StopBits::One) + .open()?; + + // Extract any bytes left over in the serial port driver from + // previous interaction. + loop { + let mut throwaway = [0; 16]; + match port.read(&mut throwaway) { + Ok(0) => { + // This should only happen on nonblocking reads, which + // we haven't asked for, but it does mean the buffer is + // empty so treat it as success. + break; + } + Ok(_) => { + // We've collected some characters to throw away, keep + // going. + } + Err(e) if e.kind() == std::io::ErrorKind::TimedOut => { + // Buffer is empty! + break; + } + Err(e) => { + return Err(e.into()); + } + } + } + // Crank the timeout back up. + port.set_timeout(Duration::from_secs(1))?; + Ok(Interface::Serial(port)) + } + InterfaceOptions::Usb { selector } => { + Ok(Interface::Usb(UsbIsp::new(&selector)?)) + } + } +} + +pub struct InterfaceList { + pub serial: Vec, + pub usb: Vec<(String, String, String)>, +} + +pub fn list_interfaces() -> Result { + let mut serial = Vec::new(); + for port in serialport::available_ports()?.into_iter() { + serial.push(port.port_name.clone()); + } + serial.sort(); + + let mut usb = Vec::new(); + for port in nusb::list_devices().wait()? { + let vid = format!("{0:04x}", port.vendor_id()); + let pid = format!("{0:04x}", port.product_id()); + let bus = port.bus_id(); + let port_chain = port + .port_chain() + .iter() + .map(|i| i.to_string()) + .collect::>() + .join("-"); + let mfg = port.manufacturer_string().unwrap_or(""); + let dsc = port.product_string().unwrap_or(""); + usb.push(( + format!("{vid}:{pid}"), + format!("{bus}@{port_chain}"), + format!("{}: {}", mfg, dsc), + )) + } + usb.sort_by_cached_key(|dev| dev.1.clone()); + + Ok(InterfaceList{serial, usb}) +} + +impl std::fmt::Display for InterfaceList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for ser in self.serial.iter() { + writeln!(f, "ser {}", ser)?; + } + let padding = self.usb.iter().fold(0, |acc, usb| usb.1.len().max(acc)); + for usb in self.usb.iter() { + writeln!(f, "usb {}:{:padding$} {}", usb.0, usb.1, usb.2)?; + } + Ok(()) + } +} From 6c793b0823d663a78fcb2b3e6754f43275321471 Mon Sep 17 00:00:00 2001 From: Stuart Donnan Date: Tue, 11 Aug 2026 18:01:39 -0700 Subject: [PATCH 05/10] make ping do something on usb --- lpc55_isp/src/usb.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lpc55_isp/src/usb.rs b/lpc55_isp/src/usb.rs index c95c6e7..22e66e4 100644 --- a/lpc55_isp/src/usb.rs +++ b/lpc55_isp/src/usb.rs @@ -154,8 +154,11 @@ impl DataPacket { } impl Isp for UsbIsp { - // TODO figure out what the ping method is on USB + // Docs don't have an encoding for pings/acks on USB so we just read a + // property fn do_ping(&mut self) -> Result<(), IspError> { + self.send_command(CommandTag::GetProperty, &[1])?; + self.read_response(ResponseCode::GetProperty)?; Ok(()) } From 5b27b51ca37baeb569b1a01fb6dfccd8a89f5f87 Mon Sep 17 00:00:00 2001 From: Stuart Donnan Date: Tue, 11 Aug 2026 18:05:46 -0700 Subject: [PATCH 06/10] add back rust-toolchain.toml --- rust-toolchain.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 rust-toolchain.toml diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..b67e7d5 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.89.0" From a9d6a475fb3db937a7c37307de34eec364b98474 Mon Sep 17 00:00:00 2001 From: Stuart Donnan Date: Tue, 11 Aug 2026 18:29:46 -0700 Subject: [PATCH 07/10] formatting --- lpc55_isp/src/bin/lpc55_flash.rs | 6 +++--- lpc55_isp/src/cmd.rs | 10 ++++++++-- lpc55_isp/src/util.rs | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lpc55_isp/src/bin/lpc55_flash.rs b/lpc55_isp/src/bin/lpc55_flash.rs index 6e09744..7a6f069 100644 --- a/lpc55_isp/src/bin/lpc55_flash.rs +++ b/lpc55_isp/src/bin/lpc55_flash.rs @@ -2,9 +2,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use anyhow::{bail, Context, Result, anyhow}; +use anyhow::{anyhow, bail, Context, Result}; use byteorder::ByteOrder; -use clap::{Parser, CommandFactory}; +use clap::{CommandFactory, Parser}; use lpc55_isp::cmd::*; use lpc55_isp::isp::{BootloaderProperty, Isp, KeyType}; use lpc55_isp::util::*; @@ -255,7 +255,7 @@ fn main() -> Result<()> { if matches!(cmd.cmd, ISPCommand::List) { println!("{}", list_interfaces()?); - return Ok(()) + return Ok(()); } if cmd.port.is_none() { diff --git a/lpc55_isp/src/cmd.rs b/lpc55_isp/src/cmd.rs index 4d3c58c..4e71483 100644 --- a/lpc55_isp/src/cmd.rs +++ b/lpc55_isp/src/cmd.rs @@ -86,7 +86,10 @@ pub fn do_generate_uds(port: &mut P) -> Result<(), IspError> { Ok(()) } -pub fn do_isp_write_keystore(port: &mut P, data: &[u8]) -> Result<(), IspError> { +pub fn do_isp_write_keystore( + port: &mut P, + data: &[u8], +) -> Result<(), IspError> { do_command( port, CommandTag::KeyProvision, @@ -101,7 +104,10 @@ pub fn do_isp_write_keystore(port: &mut P, data: &[u8]) -> Result<(), Is Ok(()) } -pub fn do_recv_sb_file(port: &mut P, data: &[u8]) -> Result<(), IspError> { +pub fn do_recv_sb_file( + port: &mut P, + data: &[u8], +) -> Result<(), IspError> { do_command( port, CommandTag::ReceiveSbFile, diff --git a/lpc55_isp/src/util.rs b/lpc55_isp/src/util.rs index a2b2fbc..2e82de4 100644 --- a/lpc55_isp/src/util.rs +++ b/lpc55_isp/src/util.rs @@ -152,7 +152,7 @@ pub fn list_interfaces() -> Result { } usb.sort_by_cached_key(|dev| dev.1.clone()); - Ok(InterfaceList{serial, usb}) + Ok(InterfaceList { serial, usb }) } impl std::fmt::Display for InterfaceList { From c151e816d653da8c1de1124f832154ea8dfdfd0d Mon Sep 17 00:00:00 2001 From: Stuart Donnan Date: Fri, 14 Aug 2026 12:44:40 -0700 Subject: [PATCH 08/10] An alternate way to handle the list function --- lpc55_isp/src/bin/lpc55_flash.rs | 587 ++++++++++++++++--------------- 1 file changed, 296 insertions(+), 291 deletions(-) diff --git a/lpc55_isp/src/bin/lpc55_flash.rs b/lpc55_isp/src/bin/lpc55_flash.rs index 7a6f069..d3bf5bf 100644 --- a/lpc55_isp/src/bin/lpc55_flash.rs +++ b/lpc55_isp/src/bin/lpc55_flash.rs @@ -97,8 +97,6 @@ enum ISPCommand { prop: BootloaderProperty, }, LastError, - /// List available devices - List, } #[derive(Copy, Clone, Debug, clap::ValueEnum)] @@ -119,7 +117,7 @@ enum InterfaceKind { struct IspArgs { /// A serial port name/path like COM1 or /dev/ttyUSB0 or usb selector /// vid:pid[:bus@path-to-port] such as 1fc9:0021:001@7-22 - #[arg(name = "port")] + #[arg(name = "port", required_unless_present("list"))] port: Option, /// Type of connection to use #[arg(value_enum, short, long, default_value_t = InterfaceKind::Serial)] @@ -127,9 +125,12 @@ struct IspArgs { /// Baud rate for serial if not the default #[clap(short = 'b')] baud_rate: Option, + /// List avialable ports and exit + #[arg(short)] + list: bool, /// Command to run #[clap(subcommand)] - cmd: ISPCommand, + cmd: Option, } fn pretty_print_bootloader_prop(prop: BootloaderProperty, params: Vec) { @@ -253,16 +254,11 @@ fn main() -> Result<()> { env_logger::init(); - if matches!(cmd.cmd, ISPCommand::List) { + if cmd.list { println!("{}", list_interfaces()?); return Ok(()); } - if cmd.port.is_none() { - println!("{}", IspArgs::command().render_usage()); - return Err(anyhow!("Port required")); - } - let options = match cmd.kind { InterfaceKind::Serial => InterfaceOptions::Serial { name: cmd.port.unwrap(), @@ -275,337 +271,346 @@ fn main() -> Result<()> { let mut port = open_interface(options)?; - match cmd.cmd { - ISPCommand::Ping => { - port.do_ping()?; - println!("ping success."); - } - ISPCommand::ReadMemory { - address, - count, - path, - } => { - port.do_ping()?; - - let m = do_isp_read_memory(&mut port, address, count)?; - - let mut out = std::fs::OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open(&path)?; - - out.write_all(&m)?; - println!("Output written to {:?}", path); - } - ISPCommand::WriteMemory { address, file } => { - port.do_ping()?; + if let Some(cmd) = cmd.cmd { + match cmd { + ISPCommand::Ping => { + port.do_ping()?; + println!("ping success."); + } + ISPCommand::ReadMemory { + address, + count, + path, + } => { + port.do_ping()?; + + let m = do_isp_read_memory(&mut port, address, count)?; + + let mut out = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(&path)?; + + out.write_all(&m)?; + println!("Output written to {:?}", path); + } + ISPCommand::WriteMemory { address, file } => { + port.do_ping()?; - println!("If you didn't already erase the flash this operation will fail!"); - println!("This operation may take a while"); - let infile = std::fs::read(file)?; + println!("If you didn't already erase the flash this operation will fail!"); + println!("This operation may take a while"); + let infile = std::fs::read(file)?; - do_isp_write_memory(&mut port, address, &infile)?; - println!("Write complete!"); - } - ISPCommand::FlashEraseAll => { - port.do_ping()?; + do_isp_write_memory(&mut port, address, &infile)?; + println!("Write complete!"); + } + ISPCommand::FlashEraseAll => { + port.do_ping()?; - do_isp_flash_erase_all(&mut port)?; + do_isp_flash_erase_all(&mut port)?; - println!("Flash erased!"); - } - ISPCommand::FlashEraseRegion { - start_address, - byte_count, - } => { - port.do_ping()?; + println!("Flash erased!"); + } + ISPCommand::FlashEraseRegion { + start_address, + byte_count, + } => { + port.do_ping()?; + + do_isp_flash_erase_region( + &mut port, + start_address, + byte_count, + )?; - do_isp_flash_erase_region(&mut port, start_address, byte_count)?; + println!("Flash region erased!"); + } + // Yes this is just another write-memory call but remembering addresses + // is hard. + ISPCommand::WriteCMPA { file } => { + port.do_ping()?; - println!("Flash region erased!"); - } - // Yes this is just another write-memory call but remembering addresses - // is hard. - ISPCommand::WriteCMPA { file } => { - port.do_ping()?; + let infile = std::fs::read(file)?; - let infile = std::fs::read(file)?; + do_isp_write_memory(&mut port, 0x9e400, &infile)?; + println!("Write to CMPA done!"); + } + ISPCommand::EraseCMPA => { + port.do_ping()?; - do_isp_write_memory(&mut port, 0x9e400, &infile)?; - println!("Write to CMPA done!"); - } - ISPCommand::EraseCMPA => { - port.do_ping()?; + // Write 512 bytes of zero + let bytes = [0; 512]; - // Write 512 bytes of zero - let bytes = [0; 512]; + do_isp_write_memory(&mut port, 0x9e400, &bytes)?; + println!("CMPA region erased!"); + println!("You can now boot unsigned images"); + } + ISPCommand::ReadCMPA { file } => { + port.do_ping()?; + + let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; + + let mut out = match file { + Some(ref path) => Box::new( + std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(path)?, + ) as Box, + None => Box::new(std::io::stdout()) as Box, + }; - do_isp_write_memory(&mut port, 0x9e400, &bytes)?; - println!("CMPA region erased!"); - println!("You can now boot unsigned images"); - } - ISPCommand::ReadCMPA { file } => { - port.do_ping()?; - - let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; - - let mut out = match file { - Some(ref path) => Box::new( - std::fs::OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open(path)?, - ) as Box, - None => Box::new(std::io::stdout()) as Box, - }; - - out.write_all(&m)?; - eprintln!("CMPA Output written to {:?}", file); - } - ISPCommand::ReadCFPA { page, file } => { - port.do_ping()?; - - let data = if let Some(page) = page { - // Only read one page as requested - let addr = match page { - CfpaChoice::Scratch => 0x9de00, - CfpaChoice::Ping => 0x9e000, - CfpaChoice::Pong => 0x9e200, + out.write_all(&m)?; + eprintln!("CMPA Output written to {:?}", file); + } + ISPCommand::ReadCFPA { page, file } => { + port.do_ping()?; + + let data = if let Some(page) = page { + // Only read one page as requested + let addr = match page { + CfpaChoice::Scratch => 0x9de00, + CfpaChoice::Ping => 0x9e000, + CfpaChoice::Pong => 0x9e200, + }; + do_isp_read_memory(&mut port, addr, 512)? + } else { + // Read ping and pong pages and only write out the latest one. + let ping = do_isp_read_memory(&mut port, 0x9e000, 512) + .context("reading CFPA ping page")?; + let pong = do_isp_read_memory(&mut port, 0x9e200, 512) + .context("reading CFPA pong page")?; + let ping_d = lpc55_areas::CFPAPage::from_bytes( + ping[..].try_into().unwrap(), + )?; + let pong_d = lpc55_areas::CFPAPage::from_bytes( + pong[..].try_into().unwrap(), + )?; + println!( + "CFPA versions: ping={}, pong={}", + ping_d.version, pong_d.version + ); + if ping_d.version > pong_d.version { + println!("choosing ping"); + ping + } else { + println!("choosing pong"); + pong + } }; - do_isp_read_memory(&mut port, addr, 512)? - } else { - // Read ping and pong pages and only write out the latest one. - let ping = do_isp_read_memory(&mut port, 0x9e000, 512) - .context("reading CFPA ping page")?; - let pong = do_isp_read_memory(&mut port, 0x9e200, 512) - .context("reading CFPA pong page")?; - let ping_d = lpc55_areas::CFPAPage::from_bytes( - ping[..].try_into().unwrap(), + + let mut out = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(&file)?; + + out.write_all(&data)?; + println!("CFPA written to {file:?}"); + } + ISPCommand::WriteCFPA { + update_version, + file, + } => { + port.do_ping()?; + + let bytes = std::fs::read(file)?; + let mut new_cfpa = lpc55_areas::CFPAPage::from_bytes( + bytes[..] + .try_into() + .context("CFPA file is not 512 bytes")?, )?; - let pong_d = lpc55_areas::CFPAPage::from_bytes( - pong[..].try_into().unwrap(), + + // Read the CMPA so we can compare the two to try to avoid locking + // the user out of their chip. + let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; + let cmpa = lpc55_areas::CMPAPage::from_bytes( + m[..].try_into().unwrap(), )?; - println!( - "CFPA versions: ping={}, pong={}", - ping_d.version, pong_d.version - ); - if ping_d.version > pong_d.version { - println!("choosing ping"); - ping - } else { - println!("choosing pong"); - pong + if (new_cfpa.dcfg_cc_socu_ns_pin != 0 + || new_cfpa.dcfg_cc_socu_ns_dflt != 0) + && (cmpa.cc_socu_pin == 0 || cmpa.cc_socu_dflt == 0) + { + bail!( + "It looks like the CMPA debug settings aren't set but \ + the CFPA settings are! This will brick the chip!" + ); + // TODO I guess it's remotely possible that we might want an + // override for this check. } - }; - let mut out = std::fs::OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open(&file)?; + if update_version { + // Read the current CFPA areas to figure out what version we + // need to set. + let ping = do_isp_read_memory(&mut port, 0x9_e000, 512)?; + let pong = do_isp_read_memory(&mut port, 0x9_e200, 512)?; + + let ping = lpc55_areas::CFPAPage::from_bytes( + ping[..].try_into().unwrap(), + )?; + let pong = lpc55_areas::CFPAPage::from_bytes( + pong[..].try_into().unwrap(), + )?; + + println!( + "ping sector v={}, pong sector v={}", + ping.version, pong.version + ); + let start_version = u32::max(ping.version, pong.version); + new_cfpa.version = start_version + 1; + println!("note: updated version is {}", new_cfpa.version); + } - out.write_all(&data)?; - println!("CFPA written to {file:?}"); - } - ISPCommand::WriteCFPA { - update_version, - file, - } => { - port.do_ping()?; - - let bytes = std::fs::read(file)?; - let mut new_cfpa = lpc55_areas::CFPAPage::from_bytes( - bytes[..].try_into().context("CFPA file is not 512 bytes")?, - )?; - - // Read the CMPA so we can compare the two to try to avoid locking - // the user out of their chip. - let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; - let cmpa = - lpc55_areas::CMPAPage::from_bytes(m[..].try_into().unwrap())?; - if (new_cfpa.dcfg_cc_socu_ns_pin != 0 - || new_cfpa.dcfg_cc_socu_ns_dflt != 0) - && (cmpa.cc_socu_pin == 0 || cmpa.cc_socu_dflt == 0) - { - bail!( - "It looks like the CMPA debug settings aren't set but \ - the CFPA settings are! This will brick the chip!" - ); - // TODO I guess it's remotely possible that we might want an - // override for this check. + let new_bytes = new_cfpa.to_vec()?; + do_isp_write_memory(&mut port, 0x9_de00, &new_bytes)?; + println!("Write to CFPA done!"); } + ISPCommand::Restore => { + port.do_ping()?; - if update_version { - // Read the current CFPA areas to figure out what version we - // need to set. - let ping = do_isp_read_memory(&mut port, 0x9_e000, 512)?; - let pong = do_isp_read_memory(&mut port, 0x9_e200, 512)?; + println!("Erasing flash"); + do_isp_flash_erase_all(&mut port)?; + println!("Erasing done."); - let ping = lpc55_areas::CFPAPage::from_bytes( - ping[..].try_into().unwrap(), - )?; - let pong = lpc55_areas::CFPAPage::from_bytes( - pong[..].try_into().unwrap(), - )?; + // we need to fill 0x134 bytes to cover the vector table + // plus all interrupts + let mut bytes: [u8; 0x134] = [0u8; 0x134]; - println!( - "ping sector v={}, pong sector v={}", - ping.version, pong.version + // Choose a RAM address for the stack (we shouldn't use the stack + // but it should be valid anyway) + byteorder::LittleEndian::write_u32( + &mut bytes[0x0..0x4], + 0x20004000, ); - let start_version = u32::max(ping.version, pong.version); - new_cfpa.version = start_version + 1; - println!("note: updated version is {}", new_cfpa.version); - } - - let new_bytes = new_cfpa.to_vec()?; - do_isp_write_memory(&mut port, 0x9_de00, &new_bytes)?; - println!("Write to CFPA done!"); - } - ISPCommand::Restore => { - port.do_ping()?; - - println!("Erasing flash"); - do_isp_flash_erase_all(&mut port)?; - println!("Erasing done."); - - // we need to fill 0x134 bytes to cover the vector table - // plus all interrupts - let mut bytes: [u8; 0x134] = [0u8; 0x134]; - - // Choose a RAM address for the stack (we shouldn't use the stack - // but it should be valid anyway) - byteorder::LittleEndian::write_u32( - &mut bytes[0x0..0x4], - 0x20004000, - ); - // Everything else targets the loop to branch instruction at 0x00000130 - let mut offset = 4; - while offset < 0x130 { + // Everything else targets the loop to branch instruction at 0x00000130 + let mut offset = 4; + while offset < 0x130 { + byteorder::LittleEndian::write_u32( + &mut bytes[offset..offset + 4], + 0x00000131, + ); + offset += 4; + } + // This is two branch to self instructions byteorder::LittleEndian::write_u32( - &mut bytes[offset..offset + 4], - 0x00000131, + &mut bytes[0x130..0x134], + 0xe7fee7fe, ); - offset += 4; - } - // This is two branch to self instructions - byteorder::LittleEndian::write_u32( - &mut bytes[0x130..0x134], - 0xe7fee7fe, - ); - println!("Writing bytes"); - do_isp_write_memory(&mut port, 0x0, &bytes)?; + println!("Writing bytes"); + do_isp_write_memory(&mut port, 0x0, &bytes)?; - println!("Restore done! SWD should work now."); - } - ISPCommand::SendSBUpdate { file } => { - port.do_ping()?; + println!("Restore done! SWD should work now."); + } + ISPCommand::SendSBUpdate { file } => { + port.do_ping()?; - println!("Sending SB file, this may take a while"); - let infile = std::fs::read(file)?; + println!("Sending SB file, this may take a while"); + let infile = std::fs::read(file)?; - do_recv_sb_file(&mut port, &infile)?; - println!("Send complete!"); - } - ISPCommand::Enroll => { - port.do_ping()?; + do_recv_sb_file(&mut port, &infile)?; + println!("Send complete!"); + } + ISPCommand::Enroll => { + port.do_ping()?; - println!("Generating new activation code"); + println!("Generating new activation code"); - do_enroll(&mut port)?; - println!("done."); - println!("If you want to save this, remember to write to non-volatile memory"); - } - ISPCommand::GenerateUDS => { - port.do_ping()?; + do_enroll(&mut port)?; + println!("done."); + println!("If you want to save this, remember to write to non-volatile memory"); + } + ISPCommand::GenerateUDS => { + port.do_ping()?; - println!("Generating new UDS"); + println!("Generating new UDS"); - do_generate_uds(&mut port)?; - println!("done."); - println!("If you want to save this, remember to write to non-volatile memory"); - } - ISPCommand::WriteKeyStore => { - port.do_ping()?; + do_generate_uds(&mut port)?; + println!("done."); + println!("If you want to save this, remember to write to non-volatile memory"); + } + ISPCommand::WriteKeyStore => { + port.do_ping()?; - println!("Writing key store to flash"); - do_save_keystore(&mut port)?; - println!("done."); - } - ISPCommand::EraseKeyStore => { - port.do_ping()?; + println!("Writing key store to flash"); + do_save_keystore(&mut port)?; + println!("done."); + } + ISPCommand::EraseKeyStore => { + port.do_ping()?; - println!("Erasing existing keystore"); - // Write 3 * 512 bytes of 0 - let bytes = vec![0; 512 * 3]; + println!("Erasing existing keystore"); + // Write 3 * 512 bytes of 0 + let bytes = vec![0; 512 * 3]; - do_isp_write_keystore(&mut port, &bytes)?; - do_save_keystore(&mut port)?; - println!("done.") - } - ISPCommand::SetSBKek { file } => { - port.do_ping()?; + do_isp_write_keystore(&mut port, &bytes)?; + do_save_keystore(&mut port)?; + println!("done.") + } + ISPCommand::SetSBKek { file } => { + port.do_ping()?; - let mut infile = - std::fs::OpenOptions::new().read(true).open(file)?; + let mut infile = + std::fs::OpenOptions::new().read(true).open(file)?; - let mut raw_bytes = Vec::new(); + let mut raw_bytes = Vec::new(); - infile.read_to_end(&mut raw_bytes)?; + infile.read_to_end(&mut raw_bytes)?; - let mut actual_bytes = hex::decode(&raw_bytes)?; + let mut actual_bytes = hex::decode(&raw_bytes)?; - actual_bytes.reverse(); + actual_bytes.reverse(); - do_isp_set_userkey(&mut port, KeyType::SBKEK, &actual_bytes)?; - println!("done."); - } - ISPCommand::SetupKeyStore { file } => { - port.do_ping()?; + do_isp_set_userkey(&mut port, KeyType::SBKEK, &actual_bytes)?; + println!("done."); + } + ISPCommand::SetupKeyStore { file } => { + port.do_ping()?; - // Step 1: Enroll - println!("Generating new activation code"); - do_enroll(&mut port)?; + // Step 1: Enroll + println!("Generating new activation code"); + do_enroll(&mut port)?; - // Step 2: Generate UDS - println!("Generating new UDS"); - do_generate_uds(&mut port)?; + // Step 2: Generate UDS + println!("Generating new UDS"); + do_generate_uds(&mut port)?; - // Step 3: Set the SBKEK - let mut infile = - std::fs::OpenOptions::new().read(true).open(file)?; + // Step 3: Set the SBKEK + let mut infile = + std::fs::OpenOptions::new().read(true).open(file)?; - let mut raw_bytes = Vec::new(); + let mut raw_bytes = Vec::new(); - infile.read_to_end(&mut raw_bytes)?; + infile.read_to_end(&mut raw_bytes)?; - let mut actual_bytes = hex::decode(&raw_bytes)?; + let mut actual_bytes = hex::decode(&raw_bytes)?; - // NXP stores the key reversed? It's very unclear... - actual_bytes.reverse(); + // NXP stores the key reversed? It's very unclear... + actual_bytes.reverse(); - println!("Setting user key"); - do_isp_set_userkey(&mut port, KeyType::SBKEK, &actual_bytes)?; + println!("Setting user key"); + do_isp_set_userkey(&mut port, KeyType::SBKEK, &actual_bytes)?; - println!("Writing keystore"); - // Step 4: Write the keystore to persistent storage - do_save_keystore(&mut port)?; - } - ISPCommand::GetProperty { prop } => { - port.do_ping()?; - let result = do_isp_get_property(&mut port, prop)?; - pretty_print_bootloader_prop(prop, result); - } - ISPCommand::LastError => { - port.do_ping()?; - let result = do_isp_last_error(&mut port)?; - pretty_print_error(result); - } - ISPCommand::List => { - unreachable!() // we handle list above + println!("Writing keystore"); + // Step 4: Write the keystore to persistent storage + do_save_keystore(&mut port)?; + } + ISPCommand::GetProperty { prop } => { + port.do_ping()?; + let result = do_isp_get_property(&mut port, prop)?; + pretty_print_bootloader_prop(prop, result); + } + ISPCommand::LastError => { + port.do_ping()?; + let result = do_isp_last_error(&mut port)?; + pretty_print_error(result); + } } + } else { + println!("{}", IspArgs::command().render_usage()); + return Err(anyhow!("Command required")); } Ok(()) From b835d378e869743ab2d8510f7c4192f21343e0ae Mon Sep 17 00:00:00 2001 From: Stuart Donnan Date: Thu, 20 Aug 2026 10:10:58 -0700 Subject: [PATCH 09/10] change handling for missing command --- lpc55_isp/src/bin/lpc55_flash.rs | 580 +++++++++++++++---------------- 1 file changed, 290 insertions(+), 290 deletions(-) diff --git a/lpc55_isp/src/bin/lpc55_flash.rs b/lpc55_isp/src/bin/lpc55_flash.rs index d3bf5bf..b6d520e 100644 --- a/lpc55_isp/src/bin/lpc55_flash.rs +++ b/lpc55_isp/src/bin/lpc55_flash.rs @@ -271,347 +271,347 @@ fn main() -> Result<()> { let mut port = open_interface(options)?; - if let Some(cmd) = cmd.cmd { - match cmd { - ISPCommand::Ping => { - port.do_ping()?; - println!("ping success."); - } - ISPCommand::ReadMemory { - address, - count, - path, - } => { - port.do_ping()?; - - let m = do_isp_read_memory(&mut port, address, count)?; - - let mut out = std::fs::OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open(&path)?; - - out.write_all(&m)?; - println!("Output written to {:?}", path); - } - ISPCommand::WriteMemory { address, file } => { - port.do_ping()?; + let Some(cmd) = cmd.cmd else { + println!("{}", IspArgs::command().render_usage()); + return Err(anyhow!("Command required")); + }; - println!("If you didn't already erase the flash this operation will fail!"); - println!("This operation may take a while"); - let infile = std::fs::read(file)?; + match cmd { + ISPCommand::Ping => { + port.do_ping()?; + println!("ping success."); + } + ISPCommand::ReadMemory { + address, + count, + path, + } => { + port.do_ping()?; + + let m = do_isp_read_memory(&mut port, address, count)?; + + let mut out = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(&path)?; + + out.write_all(&m)?; + println!("Output written to {:?}", path); + } + ISPCommand::WriteMemory { address, file } => { + port.do_ping()?; - do_isp_write_memory(&mut port, address, &infile)?; - println!("Write complete!"); - } - ISPCommand::FlashEraseAll => { - port.do_ping()?; + println!("If you didn't already erase the flash this operation will fail!"); + println!("This operation may take a while"); + let infile = std::fs::read(file)?; - do_isp_flash_erase_all(&mut port)?; + do_isp_write_memory(&mut port, address, &infile)?; + println!("Write complete!"); + } + ISPCommand::FlashEraseAll => { + port.do_ping()?; - println!("Flash erased!"); - } - ISPCommand::FlashEraseRegion { + do_isp_flash_erase_all(&mut port)?; + + println!("Flash erased!"); + } + ISPCommand::FlashEraseRegion { + start_address, + byte_count, + } => { + port.do_ping()?; + + do_isp_flash_erase_region( + &mut port, start_address, byte_count, - } => { - port.do_ping()?; - - do_isp_flash_erase_region( - &mut port, - start_address, - byte_count, - )?; - - println!("Flash region erased!"); - } - // Yes this is just another write-memory call but remembering addresses - // is hard. - ISPCommand::WriteCMPA { file } => { - port.do_ping()?; + )?; - let infile = std::fs::read(file)?; + println!("Flash region erased!"); + } + // Yes this is just another write-memory call but remembering addresses + // is hard. + ISPCommand::WriteCMPA { file } => { + port.do_ping()?; - do_isp_write_memory(&mut port, 0x9e400, &infile)?; - println!("Write to CMPA done!"); - } - ISPCommand::EraseCMPA => { - port.do_ping()?; + let infile = std::fs::read(file)?; - // Write 512 bytes of zero - let bytes = [0; 512]; + do_isp_write_memory(&mut port, 0x9e400, &infile)?; + println!("Write to CMPA done!"); + } + ISPCommand::EraseCMPA => { + port.do_ping()?; - do_isp_write_memory(&mut port, 0x9e400, &bytes)?; - println!("CMPA region erased!"); - println!("You can now boot unsigned images"); - } - ISPCommand::ReadCMPA { file } => { - port.do_ping()?; - - let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; - - let mut out = match file { - Some(ref path) => Box::new( - std::fs::OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open(path)?, - ) as Box, - None => Box::new(std::io::stdout()) as Box, - }; + // Write 512 bytes of zero + let bytes = [0; 512]; - out.write_all(&m)?; - eprintln!("CMPA Output written to {:?}", file); - } - ISPCommand::ReadCFPA { page, file } => { - port.do_ping()?; - - let data = if let Some(page) = page { - // Only read one page as requested - let addr = match page { - CfpaChoice::Scratch => 0x9de00, - CfpaChoice::Ping => 0x9e000, - CfpaChoice::Pong => 0x9e200, - }; - do_isp_read_memory(&mut port, addr, 512)? - } else { - // Read ping and pong pages and only write out the latest one. - let ping = do_isp_read_memory(&mut port, 0x9e000, 512) - .context("reading CFPA ping page")?; - let pong = do_isp_read_memory(&mut port, 0x9e200, 512) - .context("reading CFPA pong page")?; - let ping_d = lpc55_areas::CFPAPage::from_bytes( - ping[..].try_into().unwrap(), - )?; - let pong_d = lpc55_areas::CFPAPage::from_bytes( - pong[..].try_into().unwrap(), - )?; - println!( - "CFPA versions: ping={}, pong={}", - ping_d.version, pong_d.version - ); - if ping_d.version > pong_d.version { - println!("choosing ping"); - ping - } else { - println!("choosing pong"); - pong - } + do_isp_write_memory(&mut port, 0x9e400, &bytes)?; + println!("CMPA region erased!"); + println!("You can now boot unsigned images"); + } + ISPCommand::ReadCMPA { file } => { + port.do_ping()?; + + let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; + + let mut out = match file { + Some(ref path) => Box::new( + std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(path)?, + ) as Box, + None => Box::new(std::io::stdout()) as Box, + }; + + out.write_all(&m)?; + eprintln!("CMPA Output written to {:?}", file); + } + ISPCommand::ReadCFPA { page, file } => { + port.do_ping()?; + + let data = if let Some(page) = page { + // Only read one page as requested + let addr = match page { + CfpaChoice::Scratch => 0x9de00, + CfpaChoice::Ping => 0x9e000, + CfpaChoice::Pong => 0x9e200, }; - - let mut out = std::fs::OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open(&file)?; - - out.write_all(&data)?; - println!("CFPA written to {file:?}"); - } - ISPCommand::WriteCFPA { - update_version, - file, - } => { - port.do_ping()?; - - let bytes = std::fs::read(file)?; - let mut new_cfpa = lpc55_areas::CFPAPage::from_bytes( - bytes[..] - .try_into() - .context("CFPA file is not 512 bytes")?, + do_isp_read_memory(&mut port, addr, 512)? + } else { + // Read ping and pong pages and only write out the latest one. + let ping = do_isp_read_memory(&mut port, 0x9e000, 512) + .context("reading CFPA ping page")?; + let pong = do_isp_read_memory(&mut port, 0x9e200, 512) + .context("reading CFPA pong page")?; + let ping_d = lpc55_areas::CFPAPage::from_bytes( + ping[..].try_into().unwrap(), )?; - - // Read the CMPA so we can compare the two to try to avoid locking - // the user out of their chip. - let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; - let cmpa = lpc55_areas::CMPAPage::from_bytes( - m[..].try_into().unwrap(), + let pong_d = lpc55_areas::CFPAPage::from_bytes( + pong[..].try_into().unwrap(), )?; - if (new_cfpa.dcfg_cc_socu_ns_pin != 0 - || new_cfpa.dcfg_cc_socu_ns_dflt != 0) - && (cmpa.cc_socu_pin == 0 || cmpa.cc_socu_dflt == 0) - { - bail!( - "It looks like the CMPA debug settings aren't set but \ - the CFPA settings are! This will brick the chip!" - ); - // TODO I guess it's remotely possible that we might want an - // override for this check. + println!( + "CFPA versions: ping={}, pong={}", + ping_d.version, pong_d.version + ); + if ping_d.version > pong_d.version { + println!("choosing ping"); + ping + } else { + println!("choosing pong"); + pong } + }; - if update_version { - // Read the current CFPA areas to figure out what version we - // need to set. - let ping = do_isp_read_memory(&mut port, 0x9_e000, 512)?; - let pong = do_isp_read_memory(&mut port, 0x9_e200, 512)?; - - let ping = lpc55_areas::CFPAPage::from_bytes( - ping[..].try_into().unwrap(), - )?; - let pong = lpc55_areas::CFPAPage::from_bytes( - pong[..].try_into().unwrap(), - )?; - - println!( - "ping sector v={}, pong sector v={}", - ping.version, pong.version - ); - let start_version = u32::max(ping.version, pong.version); - new_cfpa.version = start_version + 1; - println!("note: updated version is {}", new_cfpa.version); - } + let mut out = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(&file)?; - let new_bytes = new_cfpa.to_vec()?; - do_isp_write_memory(&mut port, 0x9_de00, &new_bytes)?; - println!("Write to CFPA done!"); + out.write_all(&data)?; + println!("CFPA written to {file:?}"); + } + ISPCommand::WriteCFPA { + update_version, + file, + } => { + port.do_ping()?; + + let bytes = std::fs::read(file)?; + let mut new_cfpa = lpc55_areas::CFPAPage::from_bytes( + bytes[..] + .try_into() + .context("CFPA file is not 512 bytes")?, + )?; + + // Read the CMPA so we can compare the two to try to avoid locking + // the user out of their chip. + let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; + let cmpa = lpc55_areas::CMPAPage::from_bytes( + m[..].try_into().unwrap(), + )?; + if (new_cfpa.dcfg_cc_socu_ns_pin != 0 + || new_cfpa.dcfg_cc_socu_ns_dflt != 0) + && (cmpa.cc_socu_pin == 0 || cmpa.cc_socu_dflt == 0) + { + bail!( + "It looks like the CMPA debug settings aren't set but \ + the CFPA settings are! This will brick the chip!" + ); + // TODO I guess it's remotely possible that we might want an + // override for this check. } - ISPCommand::Restore => { - port.do_ping()?; - println!("Erasing flash"); - do_isp_flash_erase_all(&mut port)?; - println!("Erasing done."); + if update_version { + // Read the current CFPA areas to figure out what version we + // need to set. + let ping = do_isp_read_memory(&mut port, 0x9_e000, 512)?; + let pong = do_isp_read_memory(&mut port, 0x9_e200, 512)?; - // we need to fill 0x134 bytes to cover the vector table - // plus all interrupts - let mut bytes: [u8; 0x134] = [0u8; 0x134]; + let ping = lpc55_areas::CFPAPage::from_bytes( + ping[..].try_into().unwrap(), + )?; + let pong = lpc55_areas::CFPAPage::from_bytes( + pong[..].try_into().unwrap(), + )?; - // Choose a RAM address for the stack (we shouldn't use the stack - // but it should be valid anyway) - byteorder::LittleEndian::write_u32( - &mut bytes[0x0..0x4], - 0x20004000, + println!( + "ping sector v={}, pong sector v={}", + ping.version, pong.version ); - // Everything else targets the loop to branch instruction at 0x00000130 - let mut offset = 4; - while offset < 0x130 { - byteorder::LittleEndian::write_u32( - &mut bytes[offset..offset + 4], - 0x00000131, - ); - offset += 4; - } - // This is two branch to self instructions + let start_version = u32::max(ping.version, pong.version); + new_cfpa.version = start_version + 1; + println!("note: updated version is {}", new_cfpa.version); + } + + let new_bytes = new_cfpa.to_vec()?; + do_isp_write_memory(&mut port, 0x9_de00, &new_bytes)?; + println!("Write to CFPA done!"); + } + ISPCommand::Restore => { + port.do_ping()?; + + println!("Erasing flash"); + do_isp_flash_erase_all(&mut port)?; + println!("Erasing done."); + + // we need to fill 0x134 bytes to cover the vector table + // plus all interrupts + let mut bytes: [u8; 0x134] = [0u8; 0x134]; + + // Choose a RAM address for the stack (we shouldn't use the stack + // but it should be valid anyway) + byteorder::LittleEndian::write_u32( + &mut bytes[0x0..0x4], + 0x20004000, + ); + // Everything else targets the loop to branch instruction at 0x00000130 + let mut offset = 4; + while offset < 0x130 { byteorder::LittleEndian::write_u32( - &mut bytes[0x130..0x134], - 0xe7fee7fe, + &mut bytes[offset..offset + 4], + 0x00000131, ); + offset += 4; + } + // This is two branch to self instructions + byteorder::LittleEndian::write_u32( + &mut bytes[0x130..0x134], + 0xe7fee7fe, + ); - println!("Writing bytes"); - do_isp_write_memory(&mut port, 0x0, &bytes)?; + println!("Writing bytes"); + do_isp_write_memory(&mut port, 0x0, &bytes)?; - println!("Restore done! SWD should work now."); - } - ISPCommand::SendSBUpdate { file } => { - port.do_ping()?; + println!("Restore done! SWD should work now."); + } + ISPCommand::SendSBUpdate { file } => { + port.do_ping()?; - println!("Sending SB file, this may take a while"); - let infile = std::fs::read(file)?; + println!("Sending SB file, this may take a while"); + let infile = std::fs::read(file)?; - do_recv_sb_file(&mut port, &infile)?; - println!("Send complete!"); - } - ISPCommand::Enroll => { - port.do_ping()?; + do_recv_sb_file(&mut port, &infile)?; + println!("Send complete!"); + } + ISPCommand::Enroll => { + port.do_ping()?; - println!("Generating new activation code"); + println!("Generating new activation code"); - do_enroll(&mut port)?; - println!("done."); - println!("If you want to save this, remember to write to non-volatile memory"); - } - ISPCommand::GenerateUDS => { - port.do_ping()?; + do_enroll(&mut port)?; + println!("done."); + println!("If you want to save this, remember to write to non-volatile memory"); + } + ISPCommand::GenerateUDS => { + port.do_ping()?; - println!("Generating new UDS"); + println!("Generating new UDS"); - do_generate_uds(&mut port)?; - println!("done."); - println!("If you want to save this, remember to write to non-volatile memory"); - } - ISPCommand::WriteKeyStore => { - port.do_ping()?; + do_generate_uds(&mut port)?; + println!("done."); + println!("If you want to save this, remember to write to non-volatile memory"); + } + ISPCommand::WriteKeyStore => { + port.do_ping()?; - println!("Writing key store to flash"); - do_save_keystore(&mut port)?; - println!("done."); - } - ISPCommand::EraseKeyStore => { - port.do_ping()?; + println!("Writing key store to flash"); + do_save_keystore(&mut port)?; + println!("done."); + } + ISPCommand::EraseKeyStore => { + port.do_ping()?; - println!("Erasing existing keystore"); - // Write 3 * 512 bytes of 0 - let bytes = vec![0; 512 * 3]; + println!("Erasing existing keystore"); + // Write 3 * 512 bytes of 0 + let bytes = vec![0; 512 * 3]; - do_isp_write_keystore(&mut port, &bytes)?; - do_save_keystore(&mut port)?; - println!("done.") - } - ISPCommand::SetSBKek { file } => { - port.do_ping()?; + do_isp_write_keystore(&mut port, &bytes)?; + do_save_keystore(&mut port)?; + println!("done.") + } + ISPCommand::SetSBKek { file } => { + port.do_ping()?; - let mut infile = - std::fs::OpenOptions::new().read(true).open(file)?; + let mut infile = + std::fs::OpenOptions::new().read(true).open(file)?; - let mut raw_bytes = Vec::new(); + let mut raw_bytes = Vec::new(); - infile.read_to_end(&mut raw_bytes)?; + infile.read_to_end(&mut raw_bytes)?; - let mut actual_bytes = hex::decode(&raw_bytes)?; + let mut actual_bytes = hex::decode(&raw_bytes)?; - actual_bytes.reverse(); + actual_bytes.reverse(); - do_isp_set_userkey(&mut port, KeyType::SBKEK, &actual_bytes)?; - println!("done."); - } - ISPCommand::SetupKeyStore { file } => { - port.do_ping()?; + do_isp_set_userkey(&mut port, KeyType::SBKEK, &actual_bytes)?; + println!("done."); + } + ISPCommand::SetupKeyStore { file } => { + port.do_ping()?; - // Step 1: Enroll - println!("Generating new activation code"); - do_enroll(&mut port)?; + // Step 1: Enroll + println!("Generating new activation code"); + do_enroll(&mut port)?; - // Step 2: Generate UDS - println!("Generating new UDS"); - do_generate_uds(&mut port)?; + // Step 2: Generate UDS + println!("Generating new UDS"); + do_generate_uds(&mut port)?; - // Step 3: Set the SBKEK - let mut infile = - std::fs::OpenOptions::new().read(true).open(file)?; + // Step 3: Set the SBKEK + let mut infile = + std::fs::OpenOptions::new().read(true).open(file)?; - let mut raw_bytes = Vec::new(); + let mut raw_bytes = Vec::new(); - infile.read_to_end(&mut raw_bytes)?; + infile.read_to_end(&mut raw_bytes)?; - let mut actual_bytes = hex::decode(&raw_bytes)?; + let mut actual_bytes = hex::decode(&raw_bytes)?; - // NXP stores the key reversed? It's very unclear... - actual_bytes.reverse(); + // NXP stores the key reversed? It's very unclear... + actual_bytes.reverse(); - println!("Setting user key"); - do_isp_set_userkey(&mut port, KeyType::SBKEK, &actual_bytes)?; + println!("Setting user key"); + do_isp_set_userkey(&mut port, KeyType::SBKEK, &actual_bytes)?; - println!("Writing keystore"); - // Step 4: Write the keystore to persistent storage - do_save_keystore(&mut port)?; - } - ISPCommand::GetProperty { prop } => { - port.do_ping()?; - let result = do_isp_get_property(&mut port, prop)?; - pretty_print_bootloader_prop(prop, result); - } - ISPCommand::LastError => { - port.do_ping()?; - let result = do_isp_last_error(&mut port)?; - pretty_print_error(result); - } + println!("Writing keystore"); + // Step 4: Write the keystore to persistent storage + do_save_keystore(&mut port)?; } - } else { - println!("{}", IspArgs::command().render_usage()); - return Err(anyhow!("Command required")); - } + ISPCommand::GetProperty { prop } => { + port.do_ping()?; + let result = do_isp_get_property(&mut port, prop)?; + pretty_print_bootloader_prop(prop, result); + } + ISPCommand::LastError => { + port.do_ping()?; + let result = do_isp_last_error(&mut port)?; + pretty_print_error(result); + } + }; Ok(()) } From 35dab032f27567fe863030e89dd17b8a70c27b64 Mon Sep 17 00:00:00 2001 From: Stuart Donnan Date: Thu, 20 Aug 2026 12:43:46 -0700 Subject: [PATCH 10/10] Format --- lpc55_isp/src/bin/lpc55_flash.rs | 54 +++++++-------------------- lpc55_isp/src/cmd.rs | 10 +---- lpc55_isp/src/isp.rs | 15 ++------ lpc55_isp/src/serial.rs | 53 ++++++++------------------- lpc55_isp/src/usb.rs | 63 ++++++++++---------------------- lpc55_isp/src/util.rs | 15 ++------ 6 files changed, 56 insertions(+), 154 deletions(-) diff --git a/lpc55_isp/src/bin/lpc55_flash.rs b/lpc55_isp/src/bin/lpc55_flash.rs index b6d520e..d11eba9 100644 --- a/lpc55_isp/src/bin/lpc55_flash.rs +++ b/lpc55_isp/src/bin/lpc55_flash.rs @@ -322,11 +322,7 @@ fn main() -> Result<()> { } => { port.do_ping()?; - do_isp_flash_erase_region( - &mut port, - start_address, - byte_count, - )?; + do_isp_flash_erase_region(&mut port, start_address, byte_count)?; println!("Flash region erased!"); } @@ -386,12 +382,8 @@ fn main() -> Result<()> { .context("reading CFPA ping page")?; let pong = do_isp_read_memory(&mut port, 0x9e200, 512) .context("reading CFPA pong page")?; - let ping_d = lpc55_areas::CFPAPage::from_bytes( - ping[..].try_into().unwrap(), - )?; - let pong_d = lpc55_areas::CFPAPage::from_bytes( - pong[..].try_into().unwrap(), - )?; + let ping_d = lpc55_areas::CFPAPage::from_bytes(ping[..].try_into().unwrap())?; + let pong_d = lpc55_areas::CFPAPage::from_bytes(pong[..].try_into().unwrap())?; println!( "CFPA versions: ping={}, pong={}", ping_d.version, pong_d.version @@ -422,19 +414,14 @@ fn main() -> Result<()> { let bytes = std::fs::read(file)?; let mut new_cfpa = lpc55_areas::CFPAPage::from_bytes( - bytes[..] - .try_into() - .context("CFPA file is not 512 bytes")?, + bytes[..].try_into().context("CFPA file is not 512 bytes")?, )?; // Read the CMPA so we can compare the two to try to avoid locking // the user out of their chip. let m = do_isp_read_memory(&mut port, 0x9e400, 512)?; - let cmpa = lpc55_areas::CMPAPage::from_bytes( - m[..].try_into().unwrap(), - )?; - if (new_cfpa.dcfg_cc_socu_ns_pin != 0 - || new_cfpa.dcfg_cc_socu_ns_dflt != 0) + let cmpa = lpc55_areas::CMPAPage::from_bytes(m[..].try_into().unwrap())?; + if (new_cfpa.dcfg_cc_socu_ns_pin != 0 || new_cfpa.dcfg_cc_socu_ns_dflt != 0) && (cmpa.cc_socu_pin == 0 || cmpa.cc_socu_dflt == 0) { bail!( @@ -451,12 +438,8 @@ fn main() -> Result<()> { let ping = do_isp_read_memory(&mut port, 0x9_e000, 512)?; let pong = do_isp_read_memory(&mut port, 0x9_e200, 512)?; - let ping = lpc55_areas::CFPAPage::from_bytes( - ping[..].try_into().unwrap(), - )?; - let pong = lpc55_areas::CFPAPage::from_bytes( - pong[..].try_into().unwrap(), - )?; + let ping = lpc55_areas::CFPAPage::from_bytes(ping[..].try_into().unwrap())?; + let pong = lpc55_areas::CFPAPage::from_bytes(pong[..].try_into().unwrap())?; println!( "ping sector v={}, pong sector v={}", @@ -484,24 +467,15 @@ fn main() -> Result<()> { // Choose a RAM address for the stack (we shouldn't use the stack // but it should be valid anyway) - byteorder::LittleEndian::write_u32( - &mut bytes[0x0..0x4], - 0x20004000, - ); + byteorder::LittleEndian::write_u32(&mut bytes[0x0..0x4], 0x20004000); // Everything else targets the loop to branch instruction at 0x00000130 let mut offset = 4; while offset < 0x130 { - byteorder::LittleEndian::write_u32( - &mut bytes[offset..offset + 4], - 0x00000131, - ); + byteorder::LittleEndian::write_u32(&mut bytes[offset..offset + 4], 0x00000131); offset += 4; } // This is two branch to self instructions - byteorder::LittleEndian::write_u32( - &mut bytes[0x130..0x134], - 0xe7fee7fe, - ); + byteorder::LittleEndian::write_u32(&mut bytes[0x130..0x134], 0xe7fee7fe); println!("Writing bytes"); do_isp_write_memory(&mut port, 0x0, &bytes)?; @@ -556,8 +530,7 @@ fn main() -> Result<()> { ISPCommand::SetSBKek { file } => { port.do_ping()?; - let mut infile = - std::fs::OpenOptions::new().read(true).open(file)?; + let mut infile = std::fs::OpenOptions::new().read(true).open(file)?; let mut raw_bytes = Vec::new(); @@ -582,8 +555,7 @@ fn main() -> Result<()> { do_generate_uds(&mut port)?; // Step 3: Set the SBKEK - let mut infile = - std::fs::OpenOptions::new().read(true).open(file)?; + let mut infile = std::fs::OpenOptions::new().read(true).open(file)?; let mut raw_bytes = Vec::new(); diff --git a/lpc55_isp/src/cmd.rs b/lpc55_isp/src/cmd.rs index 4e71483..4d3c58c 100644 --- a/lpc55_isp/src/cmd.rs +++ b/lpc55_isp/src/cmd.rs @@ -86,10 +86,7 @@ pub fn do_generate_uds(port: &mut P) -> Result<(), IspError> { Ok(()) } -pub fn do_isp_write_keystore( - port: &mut P, - data: &[u8], -) -> Result<(), IspError> { +pub fn do_isp_write_keystore(port: &mut P, data: &[u8]) -> Result<(), IspError> { do_command( port, CommandTag::KeyProvision, @@ -104,10 +101,7 @@ pub fn do_isp_write_keystore( Ok(()) } -pub fn do_recv_sb_file( - port: &mut P, - data: &[u8], -) -> Result<(), IspError> { +pub fn do_recv_sb_file(port: &mut P, data: &[u8]) -> Result<(), IspError> { do_command( port, CommandTag::ReceiveSbFile, diff --git a/lpc55_isp/src/isp.rs b/lpc55_isp/src/isp.rs index 74f949f..c482d0f 100644 --- a/lpc55_isp/src/isp.rs +++ b/lpc55_isp/src/isp.rs @@ -103,15 +103,8 @@ impl RawCommand { pub trait Isp { fn do_ping(&mut self) -> Result<(), IspError>; - fn read_response( - &mut self, - response_type: ResponseCode, - ) -> Result, IspError>; - fn send_command( - &mut self, - cmd: CommandTag, - args: &[u32], - ) -> Result<(), IspError>; + fn read_response(&mut self, response_type: ResponseCode) -> Result, IspError>; + fn send_command(&mut self, cmd: CommandTag, args: &[u32]) -> Result<(), IspError>; fn send_data(&mut self, data: &[u8]) -> Result<(), IspError>; fn recv_data(&mut self, cnt: u32) -> Result, IspError>; } @@ -206,9 +199,7 @@ pub enum StatusResponse { /// messages. /// /// See LPC55 User Manual chapter 8.7 table 251 for more. -#[derive( - Debug, FromPrimitive, Copy, Clone, Eq, PartialEq, ToPrimitive, Error, -)] +#[derive(Debug, FromPrimitive, Copy, Clone, Eq, PartialEq, ToPrimitive, Error)] pub enum KnownError { #[error("Cumulative write error (did you forget to erase?) (err 10203)")] CumulativeWriteError = 10203, diff --git a/lpc55_isp/src/serial.rs b/lpc55_isp/src/serial.rs index c21cc12..c594f0b 100644 --- a/lpc55_isp/src/serial.rs +++ b/lpc55_isp/src/serial.rs @@ -42,10 +42,7 @@ impl PacketHeader { } } -pub fn require_packet_type( - frame: &FramingPacket, - ty: PacketType, -) -> Result<(), IspError> { +pub fn require_packet_type(frame: &FramingPacket, ty: PacketType) -> Result<(), IspError> { if frame.header.packet_type != ty as u8 { return Err(IspError::WrongPacket { expected: ty as u8, @@ -127,8 +124,7 @@ impl CommandPacket { let arg_bytes = args.len() * 4; // Total length of the command packet. the 4 bytes are for // the fixed fields - let len: u16 = u16::try_from(4 + arg_bytes) - .expect("args vec too long for command packet"); + let len: u16 = u16::try_from(4 + arg_bytes).expect("args vec too long for command packet"); v.packet.length_low = (len & 0xFF) as u8; v.packet.length_high = ((len >> 8) & 0xff) as u8; @@ -178,8 +174,7 @@ pub struct DataPacket { impl DataPacket { fn new_data(args: impl Into>) -> DataPacket { let args = args.into(); - let arg_len = u16::try_from(args.len()) - .expect("args vector too long for DataPacket"); + let arg_len = u16::try_from(args.len()).expect("args vector too long for DataPacket"); let mut f = FramingPacket::new(PacketType::Data); @@ -254,16 +249,14 @@ fn read_ack(port: &mut RW) -> Result<(), IspError> { } fn read_data(port: &mut RW) -> Result, IspError> { - let mut frame_bytes = - vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; + let mut frame_bytes = vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; port.read_exact(&mut frame_bytes)?; let frame = FramingPacket::unpack_from_slice(&frame_bytes).unwrap(); require_packet_type(&frame, PacketType::Data)?; - let length = - usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); + let length = usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); let mut response = vec![0; length]; port.read_exact(&mut response)?; @@ -272,11 +265,7 @@ fn read_data(port: &mut RW) -> Result, IspError> { Ok(response) } -fn check_crc( - frame_bytes: &[u8], - response: &[u8], - frame: &FramingPacket, -) -> Result<(), IspError> { +fn check_crc(frame_bytes: &[u8], response: &[u8], frame: &FramingPacket) -> Result<(), IspError> { let mut crc = CRCu16::crc16xmodem(); crc.digest(&frame_bytes[..0x4]); crc.digest(&frame_bytes[0x6..]); @@ -310,8 +299,7 @@ impl Isp for RW { self.read_exact(&mut response_bytes)?; - let response = - PingResponse::unpack(&response_bytes).map_err(IspError::Unpack)?; + let response = PingResponse::unpack(&response_bytes).map_err(IspError::Unpack)?; if response.header.packet_type != (PacketType::PingResponse as u8) { return Err(IspError::BadAck(response.header.packet_type)); @@ -322,12 +310,8 @@ impl Isp for RW { // Okay _technically_ the response can return values from get-property but for // now just return (). If we _really_ need properties we can add that later - fn read_response( - &mut self, - response_type: ResponseCode, - ) -> Result, IspError> { - let mut frame_bytes = - vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; + fn read_response(&mut self, response_type: ResponseCode) -> Result, IspError> { + let mut frame_bytes = vec![0; FramingPacket::packed_bytes_size(None).unwrap()]; self.read_exact(&mut frame_bytes)?; let frame = FramingPacket::unpack_from_slice(&frame_bytes).unwrap(); @@ -335,10 +319,7 @@ impl Isp for RW { // A response packet is a specific type of command packet. require_packet_type(&frame, PacketType::Command)?; - let length: usize = usize::from(u16::from_le_bytes([ - frame.length_low, - frame.length_high, - ])); + let length: usize = usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); let mut response = vec![0; length]; self.read_exact(&mut response)?; @@ -362,12 +343,12 @@ impl Isp for RW { let index = RawCommand::packed_bytes_size(None).unwrap(); let end_of_params = index + usize::from(command.parameter_count) * 4; - let param_bytes = response.get(index..end_of_params).ok_or( - IspError::TruncatedParams { + let param_bytes = response + .get(index..end_of_params) + .ok_or(IspError::TruncatedParams { expected_len: end_of_params, actual_len: response.len(), - }, - )?; + })?; for p in param_bytes.chunks_exact(4) { params.push(u32::from_le_bytes(p.try_into().unwrap())); @@ -385,11 +366,7 @@ impl Isp for RW { } } - fn send_command( - &mut self, - cmd: CommandTag, - args: &[u32], - ) -> Result<(), IspError> { + fn send_command(&mut self, cmd: CommandTag, args: &[u32]) -> Result<(), IspError> { let command_bytes = CommandPacket::new_command(cmd, args).to_bytes(); self.write_all(&command_bytes)?; diff --git a/lpc55_isp/src/usb.rs b/lpc55_isp/src/usb.rs index 22e66e4..ea3e636 100644 --- a/lpc55_isp/src/usb.rs +++ b/lpc55_isp/src/usb.rs @@ -28,10 +28,7 @@ pub enum ReportId { DataIn = 0x04, } -pub fn require_packet_type( - frame: &FramingPacket, - ty: ReportId, -) -> Result<(), IspError> { +pub fn require_packet_type(frame: &FramingPacket, ty: ReportId) -> Result<(), IspError> { if frame.report_id != ty as u8 { return Err(IspError::WrongPacket { expected: ty as u8, @@ -96,8 +93,7 @@ impl CommandPacket { // Total length of the command packet. the 4 bytes are for // the fixed fields // TODO check that length is less than max for USB - let len: u16 = u16::try_from(4 + arg_bytes) - .expect("args vec too long for command packet"); + let len: u16 = u16::try_from(4 + arg_bytes).expect("args vec too long for command packet"); v.packet.length_low = (len & 0xFF) as u8; v.packet.length_high = ((len >> 8) & 0xff) as u8; @@ -129,8 +125,7 @@ pub struct DataPacket { impl DataPacket { fn new_data(args: impl Into>) -> DataPacket { let args = args.into(); - let arg_len = u16::try_from(args.len()) - .expect("args vector too long for DataPacket"); + let arg_len = u16::try_from(args.len()).expect("args vector too long for DataPacket"); let mut f = FramingPacket::new(ReportId::DataOut); @@ -162,25 +157,18 @@ impl Isp for UsbIsp { Ok(()) } - fn read_response( - &mut self, - response_type: ResponseCode, - ) -> Result, IspError> { + fn read_response(&mut self, response_type: ResponseCode) -> Result, IspError> { let frame_size = FramingPacket::packed_bytes_size(None).unwrap(); let mut rx_bytes = vec![0; MAX_TX_SIZE]; self.reader.read_exact(&mut rx_bytes)?; - let frame = - FramingPacket::unpack_from_slice(&rx_bytes[..frame_size]).unwrap(); + let frame = FramingPacket::unpack_from_slice(&rx_bytes[..frame_size]).unwrap(); // A response packet is a specific type of command packet. require_packet_type(&frame, ReportId::CommandIn)?; - let length: usize = usize::from(u16::from_le_bytes([ - frame.length_low, - frame.length_high, - ])); + let length: usize = usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); let response = &rx_bytes[frame_size..frame_size + length]; let command = RawCommand::unpack_from_slice( @@ -201,12 +189,12 @@ impl Isp for UsbIsp { let index = RawCommand::packed_bytes_size(None).unwrap(); let end_of_params = index + usize::from(command.parameter_count) * 4; - let param_bytes = response.get(index..end_of_params).ok_or( - IspError::TruncatedParams { + let param_bytes = response + .get(index..end_of_params) + .ok_or(IspError::TruncatedParams { expected_len: end_of_params, actual_len: response.len(), - }, - )?; + })?; for p in param_bytes.chunks_exact(4) { params.push(u32::from_le_bytes(p.try_into().unwrap())); @@ -222,11 +210,7 @@ impl Isp for UsbIsp { } } - fn send_command( - &mut self, - cmd: CommandTag, - args: &[u32], - ) -> Result<(), IspError> { + fn send_command(&mut self, cmd: CommandTag, args: &[u32]) -> Result<(), IspError> { let command_bytes = CommandPacket::new_command(cmd, args).to_bytes(); self.writer.write_all(&command_bytes)?; @@ -257,15 +241,12 @@ impl Isp for UsbIsp { while data.len() < cnt { self.reader.read_exact(&mut buffer)?; - let frame = FramingPacket::unpack_from_slice(&buffer[..frame_size]) - .unwrap(); + let frame = FramingPacket::unpack_from_slice(&buffer[..frame_size]).unwrap(); // A response packet is a specific type of command packet. require_packet_type(&frame, ReportId::DataIn)?; - let length: usize = usize::from(u16::from_le_bytes([ - frame.length_low, - frame.length_high, - ])); + let length: usize = + usize::from(u16::from_le_bytes([frame.length_low, frame.length_high])); let response = &buffer[frame_size..frame_size + length]; data.extend_from_slice(response); @@ -370,12 +351,10 @@ impl std::str::FromStr for DeviceSelector { } Ok(DeviceSelector { usb_id: Some(UsbId { - vendor_id: u16::from_str_radix(toks[0], 16).map_err(|_| { - ParseError::InvalidVendorId(toks[0].to_owned()) - })?, - product_id: u16::from_str_radix(toks[1], 16).map_err(|_| { - ParseError::InvalidDeviceId(toks[1].to_owned()) - })?, + vendor_id: u16::from_str_radix(toks[0], 16) + .map_err(|_| ParseError::InvalidVendorId(toks[0].to_owned()))?, + product_id: u16::from_str_radix(toks[1], 16) + .map_err(|_| ParseError::InvalidDeviceId(toks[1].to_owned()))?, }), location: if let Some(location) = toks.get(2) { Some(location.parse()?) @@ -392,14 +371,12 @@ impl UsbIsp { .wait()? .find(|dev| { let id_match = if let Some(ref id_sel) = dev_sel.usb_id { - dev.vendor_id() == id_sel.vendor_id - && dev.product_id() == id_sel.product_id + dev.vendor_id() == id_sel.vendor_id && dev.product_id() == id_sel.product_id } else { true }; let port_match = if let Some(ref loc_sel) = dev_sel.location { - loc_sel.bus_id == dev.bus_id() - && loc_sel.port_chain == dev.port_chain() + loc_sel.bus_id == dev.bus_id() && loc_sel.port_chain == dev.port_chain() } else { true }; diff --git a/lpc55_isp/src/util.rs b/lpc55_isp/src/util.rs index 2e82de4..36d12eb 100644 --- a/lpc55_isp/src/util.rs +++ b/lpc55_isp/src/util.rs @@ -35,21 +35,14 @@ impl Isp for Interface { } } - fn recv_data( - &mut self, - cnt: u32, - ) -> std::result::Result, IspError> { + fn recv_data(&mut self, cnt: u32) -> std::result::Result, IspError> { match self { Interface::Usb(i) => i.recv_data(cnt), Interface::Serial(i) => i.recv_data(cnt), } } - fn send_command( - &mut self, - cmd: CommandTag, - args: &[u32], - ) -> std::result::Result<(), IspError> { + fn send_command(&mut self, cmd: CommandTag, args: &[u32]) -> std::result::Result<(), IspError> { match self { Interface::Usb(i) => i.send_command(cmd, args), Interface::Serial(i) => i.send_command(cmd, args), @@ -113,9 +106,7 @@ pub fn open_interface(interface: InterfaceOptions) -> Result { port.set_timeout(Duration::from_secs(1))?; Ok(Interface::Serial(port)) } - InterfaceOptions::Usb { selector } => { - Ok(Interface::Usb(UsbIsp::new(&selector)?)) - } + InterfaceOptions::Usb { selector } => Ok(Interface::Usb(UsbIsp::new(&selector)?)), } }