use std::time::Duration;
use ch32rv_contract::Warning;
use ch32rv_dmi::{DebugModule, DtmAccess};
use ch32rv_usb::{DeviceLock, LockError};
use ch32rv_wchlink::{
AttachInfo, ChipInfo, ChipInfoStatus, ProbeInfo, Speed, WchLink, WchLinkError, family_name,
};
use crate::cmd_probe::Entry;
pub struct Session {
link: WchLink,
pub attach: AttachInfo,
pub probe_info: ProbeInfo,
pub chip: Option<ChipInfo>,
db: ch32rv_target::Db,
_lock: DeviceLock,
reset_run: bool,
ran_before: [bool; 2],
}
pub enum SessionError {
Open(WchLinkError),
ProbeInfo(WchLinkError),
NoTarget,
Attach(String),
ChipMismatch(String),
ChipNotInDb(String),
DbOverlay(String),
Busy(LockError),
}
impl std::fmt::Display for SessionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SessionError::Open(e) | SessionError::ProbeInfo(e) => write!(f, "{e}"),
SessionError::NoTarget => f.write_str("no target detected on the debug pins"),
SessionError::Attach(m)
| SessionError::ChipMismatch(m)
| SessionError::ChipNotInDb(m)
| SessionError::DbOverlay(m) => f.write_str(m),
SessionError::Busy(e) => write!(f, "{e} (another ch32rv is using this probe)"),
}
}
}
impl Session {
pub fn select_core(&mut self, core: u32) -> Result<(), String> {
if core == 0 {
return Ok(());
}
if core != 1 || self.attach.family_byte != 0xc6 {
return Err("--core 1 requires an attached CH32H417 target".into());
}
self.link.select_hart(core).map_err(|e| e.to_string())
}
pub fn attach(
entry: &Entry,
speed: Speed,
timeout: Duration,
lock_timeout: Duration,
chip: Option<&str>,
db_overlay: Option<&std::path::Path>,
warnings: &mut Vec<Warning>,
) -> Result<Self, SessionError> {
let lock_key = entry
.dev
.serial()
.map(str::to_owned)
.unwrap_or_else(|| entry.dev.topology());
let lock = DeviceLock::acquire(&lock_key, lock_timeout).map_err(SessionError::Busy)?;
let db = match db_overlay {
Some(path) => ch32rv_target::Db::with_overlay(path).map_err(SessionError::DbOverlay)?,
None => ch32rv_target::Db::builtin(),
};
let mut link = open_with_retry(entry).map_err(SessionError::Open)?;
link.set_timeout(timeout);
let _ = link.detach_chip();
let probe_info = link.probe_info().map_err(SessionError::ProbeInfo)?;
let attach = attach_once(&mut link, speed)?;
if let Some(requested) = chip
&& let Err(e) = check_chip(&db, requested, &attach)
{
let _ = link.detach_chip();
return Err(e);
}
let chip = match link.chip_info() {
Ok(ChipInfoStatus::Ok(ci)) => Some(ci),
Ok(ChipInfoStatus::NoAnswer) => {
warnings.push(Warning {
code: "uuid-unavailable".to_owned(),
msg: "the target did not answer the UUID query (protected, or unsupported by this family)".to_owned(),
});
None
}
Ok(ChipInfoStatus::CorruptedReadback) => {
warnings.push(Warning {
code: "probe-readback-corrupted".to_owned(),
msg: "the probe held a corrupted target readback (known LinkE state); recovered via re-detect".to_owned(),
});
let _ = link.redetect_chip();
let _ = link.detach_chip();
let _ = attach_once(&mut link, speed)?;
match link.chip_info() {
Ok(ChipInfoStatus::Ok(ci)) => Some(ci),
_ => {
warnings.push(Warning {
code: "probe-readback-corrupted".to_owned(),
msg: "recovery did not produce a clean readback; replug the probe if values look wrong".to_owned(),
});
None
}
}
}
Err(_) => None,
};
if let Some(ci) = &chip
&& reported_flash_bytes(ci).is_none()
{
let (_, source) = flash_capacity(chip.as_ref(), &db, attach.chip_id);
warnings.push(Warning {
code: "flash-capacity-unavailable".to_owned(),
msg: format!(
"ChipInfo flash capacity 0x{:04x} is zero or an erased pattern; capacity source: {source}",
ci.flash_bytes / 1024
),
});
}
let mut ran_before = [false; 2];
if attach.family_byte == 0xc6
&& RESUME_OTHER_HARTS.load(std::sync::atomic::Ordering::Relaxed)
{
for (hart, ran) in ran_before.iter_mut().enumerate() {
*ran = hart_ran(&mut link, hart as u32).unwrap_or(false);
}
let _ = link.select_hart(0);
}
Ok(Self {
link,
attach,
probe_info,
chip,
db,
_lock: lock,
reset_run: false,
ran_before,
})
}
pub fn family(&self) -> String {
family_name(self.attach.family_byte)
.map(str::to_owned)
.unwrap_or_else(|| format!("unknown(0x{:02x})", self.attach.family_byte))
}
pub fn db(&self) -> &ch32rv_target::Db {
&self.db
}
pub fn flash_capacity(&self) -> (Option<u32>, &'static str) {
flash_capacity(self.chip.as_ref(), &self.db, self.attach.chip_id)
}
pub fn mark_reset_run(&mut self) {
self.reset_run = true;
}
pub fn dm(&mut self) -> DebugModule<'_, WchLink> {
DebugModule::new(&mut self.link)
}
pub fn link(&mut self) -> &mut WchLink {
&mut self.link
}
}
fn reported_flash_bytes(chip: &ChipInfo) -> Option<u32> {
match chip.flash_bytes / 1024 {
0 | 0xffff | 0xe339 => None,
_ => Some(chip.flash_bytes),
}
}
fn flash_capacity(
chip: Option<&ChipInfo>,
db: &ch32rv_target::Db,
chip_id: u32,
) -> (Option<u32>, &'static str) {
if let Some(bytes) = chip.and_then(reported_flash_bytes) {
return (Some(bytes), "probe");
}
match db.resolve_by_chip_id(chip_id) {
ch32rv_target::Resolution::Sku(s) if s.flash_bytes > 0 => (Some(s.flash_bytes), "db"),
_ => (None, "unavailable"),
}
}
impl Drop for Session {
fn drop(&mut self) {
if self.attach.family_byte == 0xc6
&& RESUME_OTHER_HARTS.load(std::sync::atomic::Ordering::Relaxed)
{
let keep = self.link.selected_hart();
for hart in (0..2).filter(|&h| !(h == 0 && keep == 0)) {
if self.ran_before[hart as usize]
&& matches!(hart_ran(&mut self.link, hart), Ok(true))
{
let _ = resume_hart(&mut self.link, hart);
}
}
}
if RELEASE_DM.load(std::sync::atomic::Ordering::Relaxed) {
let _ = self.link.dmi_write(DMCONTROL, 0);
if self.reset_run {
if self.link.rst_out(true).is_ok() {
std::thread::sleep(Duration::from_millis(20));
let _ = self.link.rst_out(false);
}
return;
}
}
let _ = self.link.detach_chip();
}
}
fn hart_ran(link: &mut WchLink, hart: u32) -> Result<bool, ch32rv_dmi::DmiError> {
link.select_hart(hart)?;
let mut dm = DebugModule::new(link);
if !dm.is_halted()? {
return Ok(true);
}
Ok(dm.read_reg(ch32rv_dmi::RegName::Pc)? != 0)
}
fn resume_hart(link: &mut WchLink, hart: u32) -> Result<(), ch32rv_dmi::DmiError> {
link.select_hart(hart)?;
DebugModule::new(link).resume()
}
pub(crate) fn check_chip(
db: &ch32rv_target::Db,
requested: &str,
attach: &AttachInfo,
) -> Result<(), SessionError> {
let req_fams = db.families_for_chip_name(requested);
let detected_fam = match db.resolve_by_chip_id(attach.chip_id) {
ch32rv_target::Resolution::Sku(s) => s.family.clone(),
ch32rv_target::Resolution::Family(f, _) => f,
ch32rv_target::Resolution::Unknown => {
family_name(attach.family_byte).unwrap_or("").to_owned()
}
};
if req_fams.is_empty() {
return Err(SessionError::ChipNotInDb(format!(
"--chip {requested} is not in the target DB (detected {} from chip_id 0x{:08x})",
if detected_fam.is_empty() {
"an unknown part"
} else {
&detected_fam
},
attach.chip_id
)));
}
if !req_fams.is_empty()
&& !detected_fam.is_empty()
&& !req_fams
.iter()
.any(|f| f.eq_ignore_ascii_case(&detected_fam))
{
return Err(SessionError::ChipMismatch(format!(
"--chip {requested} (family {}) conflicts with the detected {detected_fam} (chip_id 0x{:08x})",
req_fams.join("/"),
attach.chip_id
)));
}
if let Some(other) = db.sku_conflict(requested, attach.chip_id) {
return Err(SessionError::ChipMismatch(format!(
"--chip {requested} conflicts with the detected {other} (chip_id 0x{:08x})",
attach.chip_id
)));
}
Ok(())
}
pub(crate) static CONNECT_UNDER_RESET: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub(crate) static RESUME_OTHER_HARTS: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub(crate) static RELEASE_DM: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
const DMCONTROL: u8 = 0x10;
const DMSTATUS: u8 = 0x11;
const DMCONTROL_HALT: u32 = 0x8000_0001;
fn attach_under_reset(link: &mut WchLink) -> Result<AttachInfo, SessionError> {
link.rst_out(true)
.map_err(|e| SessionError::Attach(format!("RstOut failed: {e}")))?;
std::thread::sleep(Duration::from_millis(20));
let _ = link.attach_chip();
let _ = link.dmi_op(DMCONTROL, DMCONTROL_HALT, 2);
link.rst_out(false)
.map_err(|e| SessionError::Attach(format!("RstOut failed: {e}")))?;
let mut last = None;
for k in 0..12 {
if k > 0 {
std::thread::sleep(Duration::from_millis(200));
}
match link.attach_chip() {
Ok(a) => {
let _ = link.dmi_op(DMCONTROL, DMCONTROL_HALT, 2);
let _ = link.dmi_op(DMSTATUS, 0, 1);
return Ok(a);
}
Err(e) => last = Some(e),
}
}
Err(match last {
Some(WchLinkError::Protocol { reason: 0x55, .. }) | None => SessionError::NoTarget,
Some(e) => SessionError::Attach(format!("attach under reset failed: {e}")),
})
}
fn attach_once(link: &mut WchLink, speed: Speed) -> Result<AttachInfo, SessionError> {
link.set_speed_default(speed)
.map_err(|e| SessionError::Attach(format!("SetSpeed failed: {e}")))?;
if CONNECT_UNDER_RESET.load(std::sync::atomic::Ordering::Relaxed) {
return attach_under_reset(link);
}
link.attach_chip().map_err(|e| match e {
WchLinkError::Protocol { reason: 0x55, .. } | WchLinkError::UnexpectedResponse(_) => {
SessionError::NoTarget
}
other => SessionError::Attach(format!("attach failed: {other}")),
})
}
pub(crate) fn open_with_retry(entry: &Entry) -> Result<WchLink, WchLinkError> {
let mut last = WchLink::open(&entry.dev);
for _ in 0..2 {
match &last {
Err(WchLinkError::Usb(ch32rv_usb::UsbError::AccessDenied(_))) | Ok(_) => break,
Err(_) => {
std::thread::sleep(Duration::from_secs(1));
last = WchLink::open(&entry.dev);
}
}
}
last
}
#[cfg(test)]
mod capacity_tests {
use super::*;
#[test]
fn erased_capacity_falls_back_only_for_a_resolved_sku() {
let db = ch32rv_target::Db::builtin();
let mut chip = ChipInfo {
flash_bytes: 0,
uuid: [1; 8],
protection_raw: [0; 4],
chip_id_echo: 0x31500000,
};
for kib in [0, 0xffff, 0xe339] {
chip.flash_bytes = kib * 1024;
assert_eq!(
flash_capacity(Some(&chip), &db, 0x31500000),
(Some(196608), "db")
);
assert_eq!(
flash_capacity(Some(&chip), &db, 0xdeadbeef),
(None, "unavailable")
);
}
chip.flash_bytes = 288 * 1024;
assert_eq!(
flash_capacity(Some(&chip), &db, 0x30700528),
(Some(288 * 1024), "probe")
);
assert_eq!(flash_capacity(None, &db, 0xdeadbeef), (None, "unavailable"));
}
}