use indicatif::ProgressBar;
use std::{thread::sleep, time::Duration};
use crate::{
commands::{self, Speed},
probe::WchLink,
Error, Result, RiscvChip,
};
pub struct ProbeSession {
pub probe: WchLink,
pub chip_family: RiscvChip,
pub speed: Speed,
}
impl ProbeSession {
pub fn attach(probe: WchLink, expected_chip: Option<RiscvChip>, speed: Speed) -> Result<Self> {
let mut probe = probe;
let chip = expected_chip.unwrap_or(RiscvChip::CH32V103);
if !probe.info.variant.support_chip(chip) {
log::error!(
"Current WCH-Link variant doesn't support the choosen MCU, please use WCH-LinkE!"
);
return Err(Error::UnsupportedChip(chip));
}
let mut chip_info = None;
for _ in 0..3 {
probe.send_command(commands::SetSpeed {
riscvchip: chip as u8,
speed,
})?;
if let Ok(resp) = probe.send_command(commands::control::AttachChip) {
log::info!("Attached chip: {}", resp);
chip_info = Some(resp);
if let Some(expected_chip) = expected_chip {
if resp.chip_family != expected_chip {
log::error!(
"Attached chip type ({:?}) does not match expected chip type ({:?})",
resp.chip_family,
expected_chip
);
return Err(Error::ChipMismatch(expected_chip, resp.chip_family));
}
}
if expected_chip.is_none() {
probe.send_command(commands::SetSpeed {
riscvchip: resp.chip_family as u8,
speed,
})?;
}
break;
} else {
log::debug!("retrying...");
sleep(Duration::from_millis(100));
}
}
let chip_info = chip_info.ok_or(Error::NotAttached)?;
chip_info.chip_family.do_post_init(&mut probe)?;
Ok(ProbeSession {
probe,
chip_family: chip_info.chip_family,
speed,
})
}
pub fn detach_chip(&mut self) -> Result<()> {
log::trace!("Detach chip");
self.probe.send_command(commands::control::OptEnd)?;
Ok(())
}
fn reattach_chip(&mut self) -> Result<()> {
log::debug!("Reattach chip");
self.detach_chip()?;
let _ = self.probe.send_command(commands::control::AttachChip)?;
Ok(())
}
pub fn dump_info(&mut self) -> Result<()> {
if self.chip_family.support_query_info() {
let esig = if self.probe.info.version() >= (2, 9) {
self.probe.send_command(commands::GetChipInfo::V2)?
} else {
self.probe.send_command(commands::GetChipInfo::V1)?
};
log::info!("Chip ESIG: {esig}");
let flash_protected = self
.probe
.send_command(commands::ConfigChip::CheckReadProtect)?;
let protected = flash_protected == commands::ConfigChip::FLAG_READ_PROTECTED;
log::info!("Flash protected: {}", protected);
if protected {
log::warn!("Flash is protected, debug access is not available");
}
}
if self.chip_family.support_ram_rom_mode() {
let sram_code_mode = self
.probe
.send_command(commands::control::GetChipRomRamSplit)?;
log::debug!("SRAM CODE split mode: {}", sram_code_mode);
}
Ok(())
}
pub fn unprotect_flash(&mut self) -> Result<()> {
self.reattach_chip()?;
let read_protected = self
.probe
.send_command(commands::ConfigChip::CheckReadProtect)?;
if read_protected == commands::ConfigChip::FLAG_READ_PROTECTED {
log::info!("Flash already unprotected");
}
self.probe.send_command(commands::ConfigChip::Unprotect)?;
self.reattach_chip()?;
let read_protected = self
.probe
.send_command(commands::ConfigChip::CheckReadProtect)?;
log::info!(
"Read protected: {}",
read_protected == commands::ConfigChip::FLAG_READ_PROTECTED
);
let write_protected = self
.probe
.send_command(commands::ConfigChip::CheckReadProtectEx)?;
if write_protected == commands::ConfigChip::FLAG_WRITE_PROTECTED {
log::warn!("Flash is write protected!");
log::warn!("try to unprotect...");
self.probe
.send_command(commands::ConfigChip::UnprotectEx(0xff))?;
self.reattach_chip()?;
let write_protected = self
.probe
.send_command(commands::ConfigChip::CheckReadProtectEx)?;
println!(
"Write protected: {}",
write_protected == commands::ConfigChip::FLAG_WRITE_PROTECTED
);
}
Ok(())
}
pub fn protect_flash(&mut self) -> Result<()> {
self.reattach_chip()?;
let read_protected = self
.probe
.send_command(commands::ConfigChip::CheckReadProtect)?;
if read_protected == commands::ConfigChip::FLAG_READ_PROTECTED {
log::warn!("Flash already protected");
}
self.probe.send_command(commands::ConfigChip::Protect)?;
self.reattach_chip()?;
let read_protected = self
.probe
.send_command(commands::ConfigChip::CheckReadProtect)?;
log::info!(
"Read protected: {}",
read_protected == commands::ConfigChip::FLAG_READ_PROTECTED
);
Ok(())
}
pub fn erase_flash(&mut self) -> Result<()> {
if self.chip_family.support_flash_protect() {
let ret = self
.probe
.send_command(commands::ConfigChip::CheckReadProtect)?;
if ret == commands::ConfigChip::FLAG_READ_PROTECTED {
log::warn!("Flash is protected, unprotecting...");
self.unprotect_flash()?;
} else if ret == 2 {
self.unprotect_flash()?; } else {
log::warn!("Unknown flash protect status: {}", ret);
}
}
self.probe.send_command(commands::Program::EraseFlash)?;
self.probe.send_command(commands::control::AttachChip)?;
Ok(())
}
pub fn write_flash(&mut self, data: &[u8], address: u32) -> Result<()> {
let chip_family = self.chip_family;
let write_pack_size = chip_family.write_pack_size();
let data_packet_size = chip_family.data_packet_size();
if chip_family.support_flash_protect() {
self.unprotect_flash()?;
}
let data = data.to_vec();
log::debug!(
"Using write pack size {} data pack size {}",
write_pack_size,
data_packet_size
);
self.probe.send_command(commands::SetWriteMemoryRegion {
start_addr: address,
len: data.len() as _,
})?;
self.probe.send_command(commands::Program::WriteFlashOP)?;
let flash_op = self.chip_family.get_flash_op();
self.probe.write_data(flash_op, data_packet_size)?;
log::debug!("Flash OP written");
let n = self
.probe
.send_command(commands::Program::Unknown07AfterFlashOPWritten)?;
if n != 0x07 {
return Err(Error::Custom(
"Unknown07AfterFlashOPWritten failed".to_string(),
));
}
let bar = ProgressBar::new(data.len() as _);
self.probe.send_command(commands::Program::WriteFlash)?;
for chunk in data.chunks(write_pack_size as usize) {
self.probe
.write_data_with_progress(chunk, data_packet_size, &|nbytes| {
bar.inc(nbytes as _);
})?;
let rxbuf = self.probe.read_data(4)?;
if rxbuf[3] != 0x04 {
return Err(Error::Custom(format!(
"Error while fastprogram: {:02x?}",
rxbuf
)));
}
}
bar.finish();
log::debug!("Fastprogram done");
let _ = self.probe.send_command(commands::Program::End)?;
Ok(())
}
pub fn soft_reset(&mut self) -> Result<()> {
self.probe.send_command(commands::Reset::Soft)?; Ok(())
}
pub fn read_memory(&mut self, address: u32, length: u32) -> Result<Vec<u8>> {
let mut length = length;
if length % 4 != 0 {
length = (length / 4 + 1) * 4;
}
self.probe.send_command(commands::SetReadMemoryRegion {
start_addr: address,
len: length,
})?;
self.probe.send_command(commands::Program::ReadMemory)?;
let mut mem = self.probe.read_data(length as usize)?;
for chunk in mem.chunks_exact_mut(4) {
chunk.reverse();
}
if mem.starts_with(&[0xA9, 0xBD, 0xF9, 0xF3]) {
log::warn!("A9 BD F9 F3 sequence detected!");
log::warn!("If the chip is just put into debug mode, you should flash the new firmware to the chip first");
log::warn!("Or else this indicates a reading to invalid location");
}
Ok(mem)
}
pub fn set_sdi_print_enabled(&mut self, enable: bool) -> Result<()> {
if !self.probe.info.variant.support_sdi_print() {
return Err(Error::Custom(
"Probe doesn't support SDI print functionality".to_string(),
));
}
if !self.chip_family.support_sdi_print() {
return Err(Error::Custom(
"Chip doesn't support SDI print functionality".to_string(),
));
}
self.probe
.send_command(commands::control::SetSdiPrintEnabled(enable))?;
Ok(())
}
pub fn erase_flash_by_power_off(probe: &mut WchLink, chip_family: RiscvChip) -> Result<()> {
if !probe.info.variant.support_power_funcs() {
return Err(Error::Custom(
"Probe doesn't support power off erase".to_string(),
));
}
if !chip_family.support_special_erase() {
return Err(Error::Custom(
"Chip doesn't support power off erase".to_string(),
));
}
probe.send_command(commands::SetSpeed {
riscvchip: chip_family as u8,
speed: Speed::default(),
})?;
probe.send_command(commands::control::EraseCodeFlash::ByPowerOff(chip_family))?;
Ok(())
}
pub fn erase_flash_by_rst_pin(probe: &mut WchLink, chip_family: RiscvChip) -> Result<()> {
if !probe.info.variant.support_power_funcs() {
return Err(Error::Custom(
"Probe doesn't support reset pin erase".to_string(),
));
}
if !chip_family.support_special_erase() {
return Err(Error::Custom(
"Chip doesn't support reset pin erase".to_string(),
));
}
probe.send_command(commands::SetSpeed {
riscvchip: chip_family as u8,
speed: Speed::default(),
})?;
probe.send_command(commands::control::EraseCodeFlash::ByPinRST(chip_family))?;
Ok(())
}
}