diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c7359..8891864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Add functions for switching IAP mode + ## [0.1.1] - 2024-11-15 ### Fixed diff --git a/README.md b/README.md index ab6e4d7..c4c455a 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ - [x] Read/write chip register - very handy for debugging - [x] Code-Protect & Code-Unprotect for supported chips - [x] Enable or Disable 3.3V, 5V output +- [x] Switch IAP mode - [x] [SDI print](https://www.cnblogs.com/liaigu/p/17628184.html) support, requires 2.10+ firmware - [x] [Serial port watching](https://github.com/ch32-rs/wlink/pull/36) for a smooth development experience - [x] Windows native driver support, no need to install libusb manually (requires x86 build) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 6fc05c1..13d3aa0 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -426,4 +426,5 @@ impl Command for DisableDebug { // 81 0D 02 08 xx ClearCodeFlash // 81 11 01 0D unknown in query info, before GetChipRomRamSplit // 81 0D 02 EE 00/02/03 SetSDLineMode -// 81 0F 01 01 SetIAPMode +// 81 0F 01 01 EnterIAPMode +// 83 02 00 00 QuitIAPMode diff --git a/src/main.rs b/src/main.rs index 4cb8b4a..633242d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -146,6 +146,13 @@ enum Commands { #[arg(long)] dap: bool, }, + /// Enter or quit IAP mode + Iap { + #[arg(long)] + enter: bool, + #[arg(long)] + quit: bool, + }, /// List probes List {}, /// Enable or disable power output @@ -206,6 +213,17 @@ fn main() -> Result<()> { WchLink::switch_from_dap_to_rv(device_index)?; } } + Some(Commands::Iap { enter, quit }) => { + WchLink::list_probes()?; + log::warn!("This is an experimental feature, better use the WCH-LinkUtility!"); + if !(enter ^ quit) { + println!("Please choose one mode to switch, either --enter or --quit"); + } else if enter { + WchLink::iap_enter(device_index)?; + } else { + WchLink::iap_quit(device_index)?; + } + } Some(Commands::List {}) => { WchLink::list_probes()?; } diff --git a/src/probe.rs b/src/probe.rs index aa5e61e..cbbb737 100644 --- a/src/probe.rs +++ b/src/probe.rs @@ -19,6 +19,12 @@ pub const PRODUCT_ID_DAP: u16 = 0x8012; pub const ENDPOINT_OUT_DAP: u8 = 0x02; +pub const VENDOR_ID_IAP: u16 = 0x4348; +pub const PRODUCT_ID_IAP: u16 = 0x55e0; + +pub const ENDPOINT_OUT_IAP: u8 = 0x02; +pub const ENDPOINT_IN_IAP: u8 = 0x02; + /// All WCH-Link probe variants, see-also: #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] #[repr(u8)] @@ -99,9 +105,11 @@ impl WchLink { let device = match crate::usb_device::open_nth(VENDOR_ID, PRODUCT_ID, nth) { Ok(dev) => dev, Err(e) => { - // Detect if it is in DAP mode + // Detect if it is in DAP or IAP mode if crate::usb_device::open_nth(VENDOR_ID_DAP, PRODUCT_ID_DAP, nth).is_ok() { return Err(Error::ProbeModeNotSupported); + } else if crate::usb_device::open_nth(VENDOR_ID_IAP, PRODUCT_ID_IAP, nth).is_ok() { + return Err(Error::ProbeModeNotSupported); } else { return Err(e); } @@ -135,6 +143,10 @@ impl WchLink { for dev in devs { println!("{} (DAP mode)", dev) } + let devs = usb_device::list_devices(VENDOR_ID_IAP, PRODUCT_ID_IAP)?; + for dev in devs { + println!("{} (IAP mode)", dev) + } Ok(()) } @@ -173,6 +185,47 @@ impl WchLink { Ok(()) } + /// Switch IAP mode + // ref: https://github.com/cjacker/wlink-iap/blob/main/src/main.c + pub fn iap_enter(nth: usize) -> Result<()> { + + // Check device mode + let vid; let pid; let endp_out; + if crate::usb_device::open_nth(VENDOR_ID, PRODUCT_ID, nth).is_ok() { + vid = VENDOR_ID; + pid = PRODUCT_ID; + endp_out = ENDPOINT_OUT; + } else { + if crate::usb_device::open_nth(VENDOR_ID_DAP, PRODUCT_ID_DAP, nth).is_ok() { + vid = VENDOR_ID_DAP; + pid = PRODUCT_ID_DAP; + endp_out = ENDPOINT_OUT_DAP; + } else { + return Err(crate::Error::ProbeNotFound); + } + } + + let mut dev = crate::usb_device::open_nth(vid, pid, nth)?; + + log::info!("Enter IAP mode"); + let buf = [0x81, 0x0f, 0x01, 0x01]; + log::trace!("send {} {}", hex::encode(&buf[..3]), hex::encode(&buf[3..])); + let _ = dev.write_endpoint(endp_out, &buf); + + Ok(()) + } + + pub fn iap_quit(nth: usize) -> Result<()> { + let mut dev = crate::usb_device::open_nth(VENDOR_ID_IAP, PRODUCT_ID_IAP, nth)?; + + log::info!("Quit IAP mode"); + let buf = [0x83, 0x02, 0x00, 0x00]; + log::trace!("send {} {}", hex::encode(&buf[..3]), hex::encode(&buf[3..])); + let _ = dev.write_endpoint(ENDPOINT_OUT_IAP, &buf); + + Ok(()) + } + pub fn set_power_output_enabled(nth: usize, cmd: commands::control::SetPower) -> Result<()> { let mut probe = Self::open_nth(nth)?; diff --git a/src/usb_device.rs b/src/usb_device.rs index b83c744..95d73e3 100644 --- a/src/usb_device.rs +++ b/src/usb_device.rs @@ -117,9 +117,12 @@ pub mod libusb { log::trace!("Device: {:?}", &device); - let desc = device.device_descriptor()?; - let serial_number = handle.read_serial_number_string_ascii(&desc)?; - log::debug!("Serial number: {:?}", serial_number); + // In IAP mode, the device does not have a serial number + if !(vid == crate::probe::VENDOR_ID_IAP && pid == crate::probe::PRODUCT_ID_IAP) { + let desc = device.device_descriptor()?; + let serial_number = handle.read_serial_number_string_ascii(&desc)?; + log::debug!("Serial number: {:?}", serial_number); + } handle.claim_interface(0)?; @@ -175,7 +178,19 @@ pub mod ch375_driver { Library::new("WCHLinkDLL.dll") .map_err(|_| Error::Custom("WCHLinkDLL.dll not found".to_string()))?, ); - let lib = CH375_DRIVER.as_ref().unwrap(); + let mut lib = CH375_DRIVER.as_ref().unwrap(); + + // For IAP mode, load CH375DLL.dll if USB ID is zero + let get_usb_id: Symbol u32> = + { lib.get(b"CH375GetUsbID").unwrap() }; + if get_usb_id(0) == 0x0000_0000 { + CH375_DRIVER = Some( + Library::new("CH375DLL.dll") + .map_err(|_| Error::Custom("CH375DLL.dll not found".to_string()))?, + ); + lib = CH375_DRIVER.as_ref().unwrap(); + } + let get_version: Symbol u32> = { lib.get(b"CH375GetVersion").unwrap() }; let get_driver_version: Symbol u32> =