diff --git a/Cargo.lock b/Cargo.lock index cafbc7b..c1f02e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,18 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c821a6e124197eb56d907ccc2188eab1038fb919c914f47976e64dd8dbc855d1" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "cc" version = "1.0.98" @@ -134,6 +146,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "deranged" version = "0.3.11" @@ -228,6 +246,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -252,6 +280,26 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libudev" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" +dependencies = [ + "libc", + "libudev-sys", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "libusb1-sys" version = "0.7.0" @@ -270,12 +318,32 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -394,6 +462,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "scroll" version = "0.12.0" @@ -433,6 +507,24 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serialport" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241ebb629ed9bf598b2b392ba42aa429f9ef2a0099001246a36ac4c084ee183f" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "core-foundation-sys", + "io-kit-sys", + "libudev", + "mach2", + "nix", + "scopeguard", + "unescaper", + "winapi", +] + [[package]] name = "simplelog" version = "0.12.2" @@ -470,6 +562,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.36" @@ -503,6 +615,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "unescaper" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" +dependencies = [ + "thiserror", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -557,9 +678,26 @@ dependencies = [ "scroll", "serde", "serde_yaml", + "serialport", "simplelog", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.8" @@ -569,6 +707,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 4c71335..1805cd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,4 @@ object = { version = "0.36.0", default-features = false, features = [ "std", ] } indicatif = "0.17" +serialport = { version = "4.5", default-features = false } diff --git a/devices/0x21-CH32V00x.yaml b/devices/0x21-CH32V00x.yaml index 8067621..e785e0d 100644 --- a/devices/0x21-CH32V00x.yaml +++ b/devices/0x21-CH32V00x.yaml @@ -7,9 +7,75 @@ support_usb: false support_serial: true description: CH32V00x (RISC-V2A) Series config_registers: + # Ref: section "16.5 User Option Bytes" of RM manual + - offset: 0x00 + name: RDPR_USER + description: RDPR, nRDPR, USER, nUSER + reset: 0x08F75AA5 + type: u32 + fields: + - bit_range: [7, 0] + name: RDPR + description: Read Protection. 0xA5 for unprotected, otherwise read-protected (ignoring WRP) + explaination: + 0xa5: Unprotected + _: Protected + - bit_range: [16, 16] + name: IWDG_SW + description: Independent watchdog (IWDG) hardware enable + explaination: + 1: IWDG enabled by software, and not enabled by hardware + 0: IWDG enabled by hardware (along with the LSI clock) + - bit_range: [18, 18] + name: STANDBY_RST + description: System reset control under the standby mode + explaination: + 1: Disabled, entering standby-mode without RST + 0: Enabled + - bit_range: [20, 19] + name: RST_MODE + description: External pin PD7 reset mode + explaination: + 0b00: Ignoring pin states within 128us after turning on the multiplexing function + 0b01: Ignoring pin states within 1ms after turning on the multiplexing function + 0b10: Ignoring pin states within 12ms after turning on the multiplexing function + 0b11: Multiplexing function off, PD7 for I/O function + - bit_range: [21, 21] + name: START_MODE + description: Power-on startup mode + explaination: + 1: Start from BOOT area + 0: Start from user CODE area + - offset: 0x04 + name: DATA + description: Customizable 2 byte data, DATA0, nDATA0, DATA1, nDATA1 + reset: 0xFF00FF00 + type: u32 + fields: + - bit_range: [7, 0] + name: DATA0 + - bit_range: [23, 16] + name: DATA1 + - offset: 0x08 + name: WRPR + # Each bit is used to control the write-protect status of 1 sector (1K bytes/sector) + description: Flash memory write protection status + type: u32 + reset: 0xFFFFFFFF + explaination: + 0xFFFFFFFF: Unprotected + _: Some 1K sections are protected variants: - - name: CH32V003*4*6 - chip_id: 51 - alt_chip_ids: [50, 49, 48] + - name: CH32V003F4P6 + chip_id: 0x30 flash_size: 16K - + - name: CH32V003F4U6 + chip_id: 0x31 + flash_size: 16K + - name: CH32V003A4M6 + chip_id: 0x32 + flash_size: 16K + - name: CH32V003J4M6 + chip_id: 0x33 + flash_size: 16K + # TODO: add CH32V002, 004 & 006 - chip IDs unknown at present diff --git a/src/flashing.rs b/src/flashing.rs index 1ec4896..9cd8448 100644 --- a/src/flashing.rs +++ b/src/flashing.rs @@ -1,19 +1,19 @@ //! Chip flashing routine use std::time::Duration; -use anyhow::Result; +use anyhow::{Ok, Result}; use indicatif::ProgressBar; use scroll::{Pread, Pwrite, LE}; use crate::{ constants::{CFG_MASK_ALL, CFG_MASK_RDPR_USER_DATA_WPR}, device::{parse_number, ChipDB}, - transport::UsbTransport, - Chip, Command, Transport, + transport::{SerialTransport, UsbTransport}, + Baudrate, Chip, Command, Transport, }; -pub struct Flashing { - transport: T, +pub struct Flashing<'a> { + transport: Box, pub chip: Chip, /// Chip unique identifier chip_uid: Vec, @@ -22,8 +22,8 @@ pub struct Flashing { code_flash_protected: bool, } -impl Flashing { - pub fn get_chip(transport: &mut UsbTransport) -> Result { +impl<'a> Flashing<'a> { + pub fn get_chip(transport: &mut impl Transport) -> Result { let identify = Command::identify(0, 0); let resp = transport.transfer(identify)?; @@ -33,7 +33,7 @@ impl Flashing { Ok(chip) } - pub fn new_from_usb_transport(mut transport: UsbTransport) -> Result { + pub fn new_from_transport(mut transport: impl Transport + 'a) -> Result { let identify = Command::identify(0, 0); let resp = transport.transfer(identify)?; anyhow::ensure!(resp.is_ok(), "idenfity chip failed"); @@ -63,7 +63,7 @@ impl Flashing { let chip_uid = resp.payload()[18..].to_vec(); let f = Flashing { - transport, + transport: Box::new(transport), chip, chip_uid, bootloader_version: btver, @@ -73,20 +73,26 @@ impl Flashing { Ok(f) } - pub fn new_from_usb() -> Result { - let transport = UsbTransport::open_any()?; + pub fn new_from_serial(port: Option<&str>, baudrate: Option) -> Result { + let baudrate = baudrate.unwrap_or_default(); - Self::new_from_usb_transport(transport) + let transport = match port { + Some(port) => SerialTransport::open(port, baudrate)?, + None => SerialTransport::open_any(baudrate)?, + }; + + Self::new_from_transport(transport) } - pub fn open_nth_usb_device(nth: usize) -> Result { - let transport = UsbTransport::open_nth(nth)?; - let flashing = Flashing::new_from_usb_transport(transport)?; - Ok(flashing) + pub fn new_from_usb(device: Option) -> Result { + let transport = match device { + Some(device) => UsbTransport::open_nth(device)?, + None => UsbTransport::open_any()?, + }; + + Self::new_from_transport(transport) } -} -impl Flashing { /// Reidentify chip using correct chip uid pub fn reidenfity(&mut self) -> Result<()> { let identify = Command::identify(self.chip.chip_id, self.chip.device_type); diff --git a/src/lib.rs b/src/lib.rs index 651c73f..98a1f8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,4 +10,4 @@ pub mod transport; pub use self::device::Chip; pub use self::flashing::Flashing; pub use self::protocol::{Command, Response}; -pub use self::transport::Transport; +pub use self::transport::{Baudrate, Transport}; diff --git a/src/main.rs b/src/main.rs index f36c773..eb4074f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,19 +5,40 @@ use anyhow::Result; use clap::{Parser, Subcommand}; use hxdmp::hexdump; -use wchisp::{constants::SECTOR_SIZE, transport::UsbTransport, Flashing}; +use wchisp::{ + constants::SECTOR_SIZE, + transport::{SerialTransport, UsbTransport}, + Baudrate, Flashing, +}; -#[derive(clap::Parser)] +#[derive(Parser)] #[command(author, version, about, long_about = None)] +#[clap(group(clap::ArgGroup::new("transport").args(&["usb", "serial"])))] struct Cli { - /// Optional device index to operate on - #[arg(long, short = 'd', value_name = "INDEX")] - device: Option, - /// Turn debugging information on #[arg(long = "verbose", short = 'v')] debug: bool, + /// Use the USB transport layer + #[arg(long, short, default_value_t = true, default_value_if("serial", clap::builder::ArgPredicate::IsPresent, "false"), conflicts_with_all = ["serial", "port", "baudrate"])] + usb: bool, + + /// Use the Serial transport layer + #[arg(long, short, conflicts_with_all = ["usb", "device"])] + serial: bool, + + /// Optional USB device index to operate on + #[arg(long, short, value_name = "INDEX", default_value = None, requires = "usb")] + device: Option, + + /// Select the serial port + #[arg(long, short, requires = "serial")] + port: Option, + + /// Select the serial baudrate + #[arg(long, short, ignore_case = true, value_enum, requires = "serial")] + baudrate: Option, + #[command(subcommand)] command: Option, } @@ -118,32 +139,55 @@ fn main() -> Result<()> { ); } - let device_idx = cli.device.unwrap_or_default(); - - match cli.command { + match &cli.command { None | Some(Commands::Probe {}) => { - let ndevices = UsbTransport::scan_devices()?; - log::info!("Found {} devices", ndevices); - log::info!("hint: use `wchisp info` to check chip info"); - for i in 0..ndevices { - let mut trans = UsbTransport::open_nth(i)?; - let chip = Flashing::get_chip(&mut trans)?; - println!("Device #{}: {}", i, chip); + if cli.usb { + let ndevices = UsbTransport::scan_devices()?; + log::info!( + "Found {ndevices} USB device{}", + match ndevices { + 1 => "", + _ => "s", + } + ); + for i in 0..ndevices { + let mut trans = UsbTransport::open_nth(i)?; + let chip = Flashing::get_chip(&mut trans)?; + log::info!("\tDevice #{i}: {chip}"); + } + } + if cli.serial { + let ports = SerialTransport::scan_ports()?; + let port_len = ports.len(); + log::info!( + "Found {port_len} serial port{}:", + match port_len { + 1 => "", + _ => "s", + } + ); + for p in ports { + log::info!("\t{p}"); + } } + + log::info!("hint: use `wchisp info` to check chip info"); } Some(Commands::Info { chip }) => { - let mut flashing = Flashing::open_nth_usb_device(device_idx)?; + let mut flashing = get_flashing(&cli)?; + if let Some(expected_chip_name) = chip { flashing.check_chip_name(&expected_chip_name)?; } flashing.dump_info()?; } Some(Commands::Reset {}) => { - let mut flashing = Flashing::open_nth_usb_device(device_idx)?; + let mut flashing = get_flashing(&cli)?; + let _ = flashing.reset(); } Some(Commands::Erase {}) => { - let mut flashing = Flashing::open_nth_usb_device(device_idx)?; + let mut flashing = get_flashing(&cli)?; let sectors = flashing.chip.flash_size / 1024; flashing.erase_code(sectors)?; @@ -155,7 +199,7 @@ fn main() -> Result<()> { no_verify, no_reset, }) => { - let mut flashing = Flashing::open_nth_usb_device(device_idx)?; + let mut flashing = get_flashing(&cli)?; flashing.dump_info()?; @@ -163,7 +207,7 @@ fn main() -> Result<()> { extend_firmware_to_sector_boundary(&mut binary); log::info!("Firmware size: {}", binary.len()); - if no_erase { + if *no_erase { log::warn!("Skipping erase"); } else { log::info!("Erasing..."); @@ -178,7 +222,7 @@ fn main() -> Result<()> { flashing.flash(&binary)?; sleep(Duration::from_millis(500)); - if no_verify { + if *no_verify { log::warn!("Skipping verify"); } else { log::info!("Verifying..."); @@ -186,7 +230,7 @@ fn main() -> Result<()> { log::info!("Verify OK"); } - if no_reset { + if *no_reset { log::warn!("Skipping reset"); } else { log::info!("Now reset device and skip any communication errors"); @@ -194,7 +238,8 @@ fn main() -> Result<()> { } } Some(Commands::Verify { path }) => { - let mut flashing = Flashing::open_nth_usb_device(device_idx)?; + let mut flashing = get_flashing(&cli)?; + let mut binary = wchisp::format::read_firmware_from_file(path)?; extend_firmware_to_sector_boundary(&mut binary); log::info!("Firmware size: {}", binary.len()); @@ -203,7 +248,8 @@ fn main() -> Result<()> { log::info!("Verify OK"); } Some(Commands::Eeprom { command }) => { - let mut flashing = Flashing::open_nth_usb_device(device_idx)?; + let mut flashing = get_flashing(&cli)?; + match command { None | Some(EepromCommands::Dump { .. }) => { flashing.reidenfity()?; @@ -235,7 +281,7 @@ fn main() -> Result<()> { Some(EepromCommands::Write { path, no_erase }) => { flashing.reidenfity()?; - if no_erase { + if *no_erase { log::warn!("Skipping erase"); } else { log::info!("Erasing EEPROM(Data Flash)..."); @@ -260,7 +306,8 @@ fn main() -> Result<()> { } } Some(Commands::Config { command }) => { - let mut flashing = Flashing::open_nth_usb_device(device_idx)?; + let mut flashing = get_flashing(&cli)?; + match command { None | Some(ConfigCommands::Info {}) => { flashing.dump_config()?; @@ -292,3 +339,13 @@ fn extend_firmware_to_sector_boundary(buf: &mut Vec) { buf.extend_from_slice(&vec![0; remain]); } } + +fn get_flashing(cli: &Cli) -> Result> { + if cli.usb { + Flashing::new_from_usb(cli.device) + } else if cli.serial { + Flashing::new_from_serial(cli.port.as_deref(), cli.baudrate) + } else { + unreachable!("No transport specified"); + } +} diff --git a/src/protocol.rs b/src/protocol.rs index 3642392..699eef1 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -137,6 +137,10 @@ impl Command { Command::DataErase { sectors } } + pub fn set_baud(baudrate: u32) -> Self { + Command::SetBaud { baudrate } + } + // TODO(visiblity) pub fn into_raw(self) -> Result> { match self { @@ -257,7 +261,20 @@ impl Command { buf[7] = sectors as u8; Ok(buf.to_vec()) } - // TODO: WriteOTP, ReadOTP, SetBaud + Command::SetBaud { baudrate } => { + let baudrate = baudrate.to_le_bytes(); + let buf = vec![ + commands::SET_BAUD, + 0x04, + 0x00, + baudrate[0], + baudrate[1], + baudrate[2], + baudrate[3], + ]; + Ok(buf) + } + // TODO: WriteOTP, ReadOTP _ => unimplemented!(), } } diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 054c973..7785acf 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -5,8 +5,10 @@ use anyhow::Result; use crate::protocol::{Command, Response}; +pub use self::serial::{Baudrate, SerialTransport}; pub use self::usb::UsbTransport; +mod serial; mod usb; const DEFAULT_TRANSPORT_TIMEOUT_MS: u64 = 1000; diff --git a/src/transport/serial.rs b/src/transport/serial.rs new file mode 100644 index 0000000..739cd6a --- /dev/null +++ b/src/transport/serial.rs @@ -0,0 +1,173 @@ +//! Serial Transportation. +use std::{fmt::Display, io::Read, time::Duration}; + +use anyhow::{Error, Ok, Result}; +use clap::{builder::PossibleValue, ValueEnum}; +use scroll::Pread; +use serialport::SerialPort; + +use super::{Command, Transport}; + +const SERIAL_TIMEOUT_MS: u64 = 1000; + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub enum Baudrate { + #[default] + Baud115200, + Baud1m, + Baud2m, +} + +impl From for u32 { + fn from(value: Baudrate) -> Self { + match value { + Baudrate::Baud115200 => 115200, + Baudrate::Baud1m => 1000000, + Baudrate::Baud2m => 2000000, + } + } +} + +impl Display for Baudrate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", u32::from(*self)) + } +} + +impl ValueEnum for Baudrate { + fn value_variants<'a>() -> &'a [Self] { + &[Baudrate::Baud115200, Baudrate::Baud1m, Baudrate::Baud2m] + } + + fn to_possible_value(&self) -> Option { + match self { + Baudrate::Baud115200 => Some(PossibleValue::new("Baud115200").aliases(["115200"])), + Baudrate::Baud1m => { + Some(PossibleValue::new("Baud1m").aliases(["1000000", "1_000_000", "1m"])) + } + Baudrate::Baud2m => { + Some(PossibleValue::new("Baud2m").aliases(["2000000", "2_000_000", "2m"])) + } + } + } +} + +pub struct SerialTransport { + serial_port: Box, +} + +impl SerialTransport { + pub fn scan_ports() -> Result> { + let ports = serialport::available_ports()?; + Ok(ports.into_iter().map(|p| p.port_name).collect()) + } + + pub fn open(port: &str, baudrate: Baudrate) -> Result { + log::info!("Opening serial port: \"{}\" @ 115200 baud", port); + let port = serialport::new(port, Baudrate::default().into()) + .timeout(Duration::from_millis(SERIAL_TIMEOUT_MS)) + .open()?; + + let mut transport = SerialTransport { serial_port: port }; + transport.set_baudrate(baudrate)?; + + Ok(transport) + } + + pub fn open_nth(nth: usize, baudrate: Baudrate) -> Result { + let ports = serialport::available_ports()?; + + match ports.get(nth) { + Some(port) => Self::open(&port.port_name, baudrate), + None => Err(Error::msg("No serial ports found!")), + } + } + + pub fn open_any(baudrate: Baudrate) -> Result { + Self::open_nth(0, baudrate) + } + + pub fn set_baudrate(&mut self, baudrate: impl Into) -> Result<()> { + let baudrate: u32 = baudrate.into(); + + if baudrate != self.serial_port.baud_rate()? { + let resp: crate::Response = self.transfer(Command::set_baud(baudrate))?; + anyhow::ensure!(resp.is_ok(), "set baudrate failed"); + + if let Some(0xfe) = resp.payload().first() { + log::info!("Custom baudrate not supported by the current chip. Using 115200"); + } else { + log::info!("Switching baudrate to: {baudrate} baud"); + self.serial_port.set_baud_rate(baudrate.into())?; + } + } + + Ok(()) + } +} + +impl Transport for SerialTransport { + fn send_raw(&mut self, raw: &[u8]) -> Result<()> { + let mut v = Vec::new(); + + v.extend_from_slice(&[0x57, 0xab]); // Append request prefix + v.extend_from_slice(raw); + v.extend_from_slice(&[raw.iter().fold(0u8, |acc, &val| acc.wrapping_add(val))]); // Append the CRC + + self.serial_port.write_all(&v)?; + self.serial_port.flush()?; + Ok(()) + } + + fn recv_raw(&mut self, _timeout: Duration) -> Result> { + // Ignore the custom timeout + // self.serial_port.set_timeout(timeout)?; + + // Read the serial header and validate. + let mut head_buf = [0u8; 2]; + self.serial_port.read_exact(&mut head_buf)?; + anyhow::ensure!( + head_buf == [0x55, 0xaa], + "Response has invalid serial header {head_buf:02x?}", + ); + + // Read the payload header and extract given length value. + let mut payload_head_buf = [0u8; 4]; + self.serial_port.read_exact(&mut payload_head_buf)?; + let payload_data_len = payload_head_buf.pread_with::(2, scroll::LE)? as usize; + anyhow::ensure!(payload_data_len > 0, "Response data length is zero"); + + // Read the amount of payload data given in the header. + let mut payload_data_buf = vec![0u8; payload_data_len]; + self.serial_port.read_exact(&mut payload_data_buf)?; + + // Read the checksum and verify against actual sum calculated from + // entire payload (header + data). + let mut cksum_buf = [0u8; 1]; + self.serial_port.read_exact(&mut cksum_buf)?; + + // Stuff the payload header and data into response to be returned. + let resp_vec: Vec = payload_head_buf + .into_iter() + .chain(payload_data_buf.into_iter()) + .collect(); + + // Read the checksum and verify against actual sum calculated from + // entire payload (header + data). + let checksum = resp_vec.iter().fold(0u8, |acc, &val| acc.wrapping_add(val)); + anyhow::ensure!( + checksum == cksum_buf[0], + "Response has incorrect checksum ({:02x} != {:02x})", + cksum_buf[0], + checksum + ); + + Ok(resp_vec) + } +} + +impl Drop for SerialTransport { + fn drop(&mut self) { + let _ = self.set_baudrate(Baudrate::Baud115200); + } +} diff --git a/src/transport/usb.rs b/src/transport/usb.rs index c01da90..706da36 100644 --- a/src/transport/usb.rs +++ b/src/transport/usb.rs @@ -25,7 +25,10 @@ impl UsbTransport { .filter(|device| { device .device_descriptor() - .map(|desc| (desc.vendor_id() == 0x4348 || desc.vendor_id() == 0x1a86) && desc.product_id() == 0x55e0) + .map(|desc| { + (desc.vendor_id() == 0x4348 || desc.vendor_id() == 0x1a86) + && desc.product_id() == 0x55e0 + }) .unwrap_or(false) }) .enumerate() @@ -37,6 +40,8 @@ impl UsbTransport { } pub fn open_nth(nth: usize) -> Result { + log::info!("Opening USB device #{}", nth); + let context = Context::new()?; let device = context @@ -45,7 +50,10 @@ impl UsbTransport { .filter(|device| { device .device_descriptor() - .map(|desc| (desc.vendor_id() == 0x4348 || desc.vendor_id() == 0x1a86) && desc.product_id() == 0x55e0) + .map(|desc| { + (desc.vendor_id() == 0x4348 || desc.vendor_id() == 0x1a86) + && desc.product_id() == 0x55e0 + }) .unwrap_or(false) }) .nth(nth)