1use std::fmt;
7use std::time::Duration;
8
9use bitvec::{bitvec, field::BitField, order::Lsb0, vec::BitVec, view::BitView};
10use nusb::{DeviceInfo, MaybeFuture};
11use probe_rs_target::ScanChainElement;
12
13use self::{commands::Speed, usb_interface::WchLinkUsbDevice};
14use super::JtagAccess;
15use crate::{
16 architecture::riscv::{
17 communication_interface::{RiscvError, RiscvInterfaceBuilder},
18 dtm::jtag_dtm::JtagDtmBuilder,
19 },
20 probe::{
21 DebugProbe, DebugProbeError, DebugProbeInfo, DebugProbeSelector, JtagSequence, ProbeError,
22 ProbeFactory, WireProtocol,
23 list::{ProbeListItem, usb_probe_accessibility},
24 },
25};
26
27mod commands;
28mod usb_interface;
29
30const VENDOR_ID: u16 = 0x1a86;
31const PRODUCT_ID: u16 = 0x8010;
32
33const DMI_VALUE_BIT_OFFSET: u32 = 2;
35const DMI_ADDRESS_BIT_OFFSET: u32 = 34;
36const DMI_OP_MASK: u128 = 0b11; const DMI_OP_NOP: u8 = 0;
39const DMI_OP_READ: u8 = 1;
40const DMI_OP_WRITE: u8 = 2;
41
42const REG_BYPASS_ADDRESS: u8 = 0x1f;
43const REG_IDCODE_ADDRESS: u8 = 0x01;
44const REG_DTMCS_ADDRESS: u8 = 0x10;
45const REG_DMI_ADDRESS: u8 = 0x11;
46
47const DTMCS_DMIRESET_MASK: u32 = 1 << 16;
48const DTMCS_DMIHARDRESET_MASK: u32 = 1 << 17;
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52#[repr(u8)]
53pub enum WchLinkVariant {
54 Ch549 = 1,
56 ECh32v305 = 2,
58 SCh32v203 = 3,
60 WCh32v208 = 5,
62}
63
64impl fmt::Display for WchLinkVariant {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 WchLinkVariant::Ch549 => write!(f, "WCH-Link-CH549"),
68 WchLinkVariant::ECh32v305 => write!(f, "WCH-LinkE-CH32V305"),
69 WchLinkVariant::SCh32v203 => write!(f, "WCH-LinkS-CH32V203"),
70 WchLinkVariant::WCh32v208 => write!(f, "WCH-LinkW-CH32V208"),
71 }
72 }
73}
74
75impl WchLinkVariant {
76 fn try_from_u8(value: u8) -> Result<Self, WchLinkError> {
77 match value {
78 1 => Ok(Self::Ch549),
79 2 | 0x12 => Ok(Self::ECh32v305),
80 3 => Ok(Self::SCh32v203),
81 5 | 0x85 => Ok(Self::WCh32v208),
82 _ => Err(WchLinkError::UnknownDevice),
83 }
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89#[repr(u8)]
90pub enum RiscvChip {
91 CH32V103 = 0x01,
93 CH57X = 0x02,
95 CH56X = 0x03,
97 CH32F10X = 0x04,
99 CH32V20X = 0x05,
101 CH32V30X = 0x06,
103 CH58X = 0x07,
105 CH32V003 = 0x09,
107 CH8571 = 0x0A, CH59X = 0x0B, CH643 = 0x0C, CH32X035 = 0x0D, CH32L103 = 0x0E, CH641 = 0x49,
120 CH32V00X = 0x4e,
122 CH32V317 = 0x86,
124 CH32H4 = 0xC6,
126}
127
128impl RiscvChip {
129 fn try_from_u8(value: u8) -> Option<Self> {
130 match value {
131 0x01 => Some(RiscvChip::CH32V103),
132 0x02 => Some(RiscvChip::CH57X),
133 0x03 => Some(RiscvChip::CH56X),
134 0x04 => Some(RiscvChip::CH32F10X),
135 0x05 => Some(RiscvChip::CH32V20X),
136 0x06 => Some(RiscvChip::CH32V30X),
137 0x07 => Some(RiscvChip::CH58X),
138 0x09 => Some(RiscvChip::CH32V003),
139 0x0A => Some(RiscvChip::CH8571),
140 0x0B => Some(RiscvChip::CH59X),
141 0x0C => Some(RiscvChip::CH643),
142 0x0D => Some(RiscvChip::CH32X035),
143 0x0E => Some(RiscvChip::CH32L103),
144 0x49 => Some(RiscvChip::CH641),
145 0x4E => Some(RiscvChip::CH32V00X),
146 0x86 => Some(RiscvChip::CH32V317),
147 0xC6 => Some(RiscvChip::CH32H4),
148 _ => None,
149 }
150 }
151
152 fn support_flash_protect(&self) -> bool {
153 matches!(
154 self,
155 RiscvChip::CH32V103
156 | RiscvChip::CH32V20X
157 | RiscvChip::CH32V30X
158 | RiscvChip::CH32V003
159 | RiscvChip::CH32V00X
160 | RiscvChip::CH643
161 | RiscvChip::CH32L103
162 | RiscvChip::CH32X035
163 | RiscvChip::CH641
164 | RiscvChip::CH32V317
165 | RiscvChip::CH32H4
166 )
167 }
168}
169
170#[derive(Debug)]
172pub struct WchLinkFactory;
173
174impl std::fmt::Display for WchLinkFactory {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 f.write_str("WchLink")
177 }
178}
179
180impl ProbeFactory for WchLinkFactory {
181 fn open(&self, selector: &DebugProbeSelector) -> Result<Box<dyn DebugProbe>, DebugProbeError> {
182 let device = WchLinkUsbDevice::new_from_selector(selector)?;
183 let mut wlink = WchLink {
184 device,
185 name: "WCH-Link".into(),
186 variant: WchLinkVariant::Ch549,
187 v_major: 0,
188 v_minor: 0,
189 chip_id: 0,
190 chip_family: RiscvChip::CH32V103,
191 last_dmi_read: None,
192 speed: Speed::default(),
193 idle_cycles: 0,
194 };
195
196 wlink.init()?;
197
198 Ok(Box::new(wlink))
199 }
200
201 fn list_probes(&self) -> Vec<ProbeListItem> {
202 list_wlink_devices()
203 }
204}
205
206pub struct WchLink {
208 device: WchLinkUsbDevice,
209 name: String,
210 variant: WchLinkVariant,
211 v_major: u8,
212 v_minor: u8,
213 chip_family: RiscvChip,
215 chip_id: u32,
217 last_dmi_read: Option<(u8, u32, u8)>,
219 speed: commands::Speed,
220 idle_cycles: u8,
221}
222
223impl fmt::Debug for WchLink {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 f.debug_struct("WchLink")
226 .field("name", &self.name)
227 .field("variant", &self.variant)
228 .field("v_major", &self.v_major)
229 .field("v_minor", &self.v_minor)
230 .field("chip_family", &self.chip_family)
231 .field("chip_id", &self.chip_id)
232 .field("last_dmi_read", &self.last_dmi_read)
233 .field("speed", &self.speed)
234 .field("idle_cycles", &self.idle_cycles)
235 .finish()
236 }
237}
238
239impl WchLink {
240 fn get_probe_info(&mut self) -> Result<(), DebugProbeError> {
241 let probe_info = self.device.send_command(commands::GetProbeInfo)?;
242 self.v_major = probe_info.major_version;
243 self.v_minor = probe_info.minor_version;
244
245 if self.v_major != 0x02 && self.v_minor < 0x07 {
246 return Err(WchLinkError::UnsupportedFirmwareVersion("2.7").into());
247 }
248
249 self.variant = probe_info.variant;
250
251 Ok(())
252 }
253
254 fn init(&mut self) -> Result<(), DebugProbeError> {
255 tracing::debug!("Initializing WCH-Link...");
257
258 self.get_probe_info()?;
259
260 let version_code = self.v_major * 10 + self.v_minor;
262
263 tracing::info!(
264 "WCH-Link variant: {}, firmware version: {}.{} (v{})",
265 self.variant,
266 self.v_major,
267 self.v_minor,
268 version_code
269 );
270
271 if self.v_major != 0x02 && self.v_minor < 0x7 {
272 return Err(WchLinkError::UnsupportedFirmwareVersion("2.7").into());
273 }
274 self.name = format!("{} v{}.{}", self.variant, self.v_major, self.v_minor);
275
276 Ok(())
277 }
278
279 pub fn chip_id(&self) -> u32 {
281 self.chip_id
282 }
283
284 pub fn chip_family(&self) -> RiscvChip {
286 self.chip_family
287 }
288
289 fn dmi_op_read(&mut self, addr: u8) -> Result<(u8, u32, u8), DebugProbeError> {
290 let resp = self.device.send_command(commands::DmiOp::read(addr))?;
291
292 Ok((resp.addr, resp.data, resp.op))
293 }
294
295 fn dmi_op_write(&mut self, addr: u8, data: u32) -> Result<(u8, u32, u8), DebugProbeError> {
296 let resp = self
297 .device
298 .send_command(commands::DmiOp::write(addr, data))?;
299
300 Ok((resp.addr, resp.data, resp.op))
301 }
302
303 fn dmi_op_nop(&mut self) -> Result<(u8, u32, u8), DebugProbeError> {
304 let resp = self.device.send_command(commands::DmiOp::nop())?;
305
306 Ok((resp.addr, resp.data, resp.op))
307 }
308}
309
310impl DebugProbe for WchLink {
311 fn get_name(&self) -> &str {
312 &self.name
313 }
314
315 fn speed_khz(&self) -> u32 {
316 self.speed.to_khz()
317 }
318
319 fn set_speed(&mut self, speed_khz: u32) -> Result<u32, DebugProbeError> {
320 let speed =
321 Speed::from_khz(speed_khz).ok_or(DebugProbeError::UnsupportedSpeed(speed_khz))?;
322 self.speed = speed;
323 self.device
324 .send_command(commands::SetSpeed(self.chip_family, speed))?;
325 Ok(speed.to_khz())
326 }
327
328 fn attach(&mut self) -> Result<(), DebugProbeError> {
330 tracing::trace!("attach to target chip");
332
333 self.device
334 .send_command(commands::SetSpeed(self.chip_family, self.speed))?;
335
336 let resp = self.device.send_command(commands::AttachChip)?;
337
338 self.chip_family = resp.chip_family;
339
340 tracing::info!("attached riscv chip {:?}", self.chip_family);
341
342 self.chip_id = resp.chip_id;
343
344 if self.chip_family.support_flash_protect() {
345 self.device.send_command(commands::CheckFlashProtection)?;
346 self.device.send_command(commands::UnprotectFlash)?;
347 }
348
349 Ok(())
350 }
351
352 fn detach(&mut self) -> Result<(), crate::Error> {
353 tracing::trace!("Detach chip");
354 self.device.send_command(commands::DetachChip)?;
355
356 Ok(())
357 }
358
359 fn target_reset(&mut self) -> Result<(), DebugProbeError> {
360 self.device.send_command(commands::ResetTarget)?;
361 Ok(())
362 }
363
364 fn target_reset_assert(&mut self) -> Result<(), DebugProbeError> {
365 tracing::info!("target reset assert");
366 self.device
367 .send_command(commands::DmiOp::write(0x10, 0x80000001))?;
368 Ok(())
369 }
370
371 fn target_reset_deassert(&mut self) -> Result<(), DebugProbeError> {
372 tracing::info!("target reset deassert");
373 self.device
374 .send_command(commands::DmiOp::write(0x10, 0x00000001))?;
375 Ok(())
376 }
377
378 fn select_protocol(&mut self, protocol: WireProtocol) -> Result<(), DebugProbeError> {
379 match protocol {
381 WireProtocol::Jtag => Ok(()),
382 _ => Err(DebugProbeError::UnsupportedProtocol(protocol)),
383 }
384 }
385
386 fn active_protocol(&self) -> Option<WireProtocol> {
387 Some(WireProtocol::Jtag)
388 }
389
390 fn into_probe(self: Box<Self>) -> Box<dyn DebugProbe> {
391 self
392 }
393
394 fn has_riscv_interface(&self) -> bool {
395 true
396 }
397
398 fn try_get_riscv_interface_builder<'probe>(
399 &'probe mut self,
400 ) -> Result<Box<dyn RiscvInterfaceBuilder<'probe> + 'probe>, RiscvError> {
401 Ok(Box::new(JtagDtmBuilder::new(self)))
402 }
403}
404
405impl JtagAccess for WchLink {
407 fn set_expected_scan_chain(
408 &mut self,
409 _scan_chain: &[ScanChainElement],
410 ) -> Result<(), DebugProbeError> {
411 Ok(())
412 }
413
414 fn set_scan_chain(&mut self, _scan_chain: &[ScanChainElement]) -> Result<(), DebugProbeError> {
415 Ok(())
416 }
417
418 fn scan_chain(&mut self) -> Result<&[ScanChainElement], DebugProbeError> {
419 Ok(&[])
420 }
421
422 fn tap_reset(&mut self) -> Result<(), DebugProbeError> {
423 Ok(())
424 }
425
426 fn read_register(&mut self, address: u32, len: u32) -> Result<BitVec, DebugProbeError> {
427 tracing::debug!("read register 0x{:08x}", address);
428 assert_eq!(len, 32);
429
430 let mut ret = bitvec![0; len as usize];
431 match address as u8 {
432 REG_IDCODE_ADDRESS => {
433 tracing::debug!("using hard coded idcode 0x00000001");
435 ret[0..8].store_le::<u8>(0x1);
436 Ok(ret)
437 }
438 REG_DTMCS_ADDRESS => {
439 ret[0..8].store_le::<u8>(0x71);
442 Ok(ret)
443 }
444 REG_BYPASS_ADDRESS => Ok(bitvec![0; 4]),
445 _ => panic!("unknown read register address {address:08x}"),
446 }
447 }
448
449 fn set_idle_cycles(&mut self, idle_cycles: u8) -> Result<(), DebugProbeError> {
450 self.idle_cycles = idle_cycles;
451 Ok(())
452 }
453
454 fn idle_cycles(&self) -> u8 {
455 self.idle_cycles
456 }
457
458 fn write_register(
459 &mut self,
460 address: u32,
461 data: &[u8],
462 len: u32,
463 ) -> Result<BitVec, DebugProbeError> {
464 match address as u8 {
465 REG_DTMCS_ADDRESS => {
466 let val = u32::from_le_bytes(data.try_into().unwrap());
467 if val & DTMCS_DMIRESET_MASK != 0 {
468 tracing::debug!("DMI reset");
469 self.dmi_op_write(0x10, 0x00000000)?;
470 self.dmi_op_write(0x10, 0x00000001)?;
471 } else if val & DTMCS_DMIHARDRESET_MASK != 0 {
473 return Err(WchLinkError::UnsupportedOperation.into());
474 }
475
476 let mut ret = bitvec![0; len as usize];
477 ret[0..8].store_le::<u8>(0x71);
478 Ok(ret)
479 }
480 REG_DMI_ADDRESS => {
481 assert_eq!(
482 len, 41,
483 "should be 41 bits: 8 bits abits + 32 bits data + 2 bits op"
484 );
485 let register_value: u128 = u128::from_le_bytes(data.try_into().unwrap());
486
487 let dmi_addr = ((register_value >> DMI_ADDRESS_BIT_OFFSET) & 0x3f) as u8;
488 let dmi_value = ((register_value >> DMI_VALUE_BIT_OFFSET) & 0xffffffff) as u32;
489 let dmi_op = (register_value & DMI_OP_MASK) as u8;
490
491 tracing::trace!(
492 "dmi op={} addr 0x{:02x} data 0x{:08x}",
493 dmi_op,
494 dmi_addr,
495 dmi_value,
496 );
497
498 let (addr, data, op) = match dmi_op {
499 DMI_OP_READ => {
500 let (addr, data, op) = self.dmi_op_read(dmi_addr)?;
501 tracing::trace!("dmi read 0x{:02x} 0x{:08x} op={}", addr, data, op);
502 self.last_dmi_read = Some((addr, data, op));
503 (addr, data, op)
504 }
505 DMI_OP_NOP => {
506 let (addr, data, op) = if dmi_addr == 0 && dmi_value == 0 {
509 self.last_dmi_read.unwrap()
510 } else {
511 self.dmi_op_nop()?
512 };
513 tracing::trace!("dmi nop 0x{:02x} 0x{:08x} op={}", addr, data, op);
514 (addr, data, op)
515 }
516 DMI_OP_WRITE => {
517 let (addr, data, op) = self.dmi_op_write(dmi_addr, dmi_value)?;
518 tracing::trace!("dmi write 0x{:02x} 0x{:08x} op={}", addr, data, op);
519 if dmi_addr == 0x10 && dmi_value == 0x40000001 {
520 std::thread::sleep(Duration::from_millis(10));
522 }
523 (addr, data, op)
524 }
525 _ => unreachable!("unknown dmi_op {dmi_op}"),
526 };
527
528 let ret = ((addr as u128) << DMI_ADDRESS_BIT_OFFSET)
529 | ((data as u128) << DMI_VALUE_BIT_OFFSET)
530 | (op as u128);
531
532 let ret_bytes = ret.to_le_bytes();
533 Ok(ret_bytes
534 .iter()
535 .fold(BitVec::with_capacity(128), |mut acc, s| {
536 acc.extend_from_bitslice(s.view_bits::<Lsb0>());
537 acc
538 }))
539 }
540 _ => unreachable!("unknown register address 0x{:08x}", address),
541 }
542 }
543
544 fn write_dr(&mut self, _data: &[u8], _len: u32) -> Result<BitVec, DebugProbeError> {
545 Err(DebugProbeError::NotImplemented {
546 function_name: "write_dr",
547 })
548 }
549
550 fn shift_raw_sequence(&mut self, _sequence: JtagSequence) -> Result<BitVec, DebugProbeError> {
551 Err(DebugProbeError::NotImplemented {
552 function_name: "shift_raw_sequence ",
553 })
554 }
555}
556
557fn get_wlink_info(device: &DeviceInfo) -> Option<ProbeListItem> {
558 if matches!(device.product_string(), Some("WCH-Link") | Some("WCH_Link")) {
559 let info = DebugProbeInfo::new(
560 "WCH-Link",
561 VENDOR_ID,
562 PRODUCT_ID,
563 device.serial_number().map(|s| s.to_string()),
564 &WchLinkFactory,
565 None,
566 false,
567 );
568 Some(ProbeListItem {
569 info,
570 accessibility: usb_probe_accessibility(device),
571 })
572 } else {
573 None
574 }
575}
576
577#[tracing::instrument(skip_all)]
578fn list_wlink_devices() -> Vec<ProbeListItem> {
579 tracing::debug!("Searching for WCH-Link(RV) probes");
580 let devices = match nusb::list_devices().wait() {
581 Ok(devices) => devices,
582 Err(e) => {
583 tracing::warn!("error listing WCH-Link devices: {e}");
584 return vec![];
585 }
586 };
587 let probes: Vec<_> = devices
588 .filter(|device| device.vendor_id() == VENDOR_ID && device.product_id() == PRODUCT_ID)
589 .filter_map(|device| get_wlink_info(&device))
590 .collect();
591
592 tracing::debug!("Found {} WCH-Link probes total", probes.len());
593 probes
594}
595
596#[derive(thiserror::Error, Debug, docsplay::Display)]
597pub(crate) enum WchLinkError {
598 UnknownDevice,
600 UnsupportedFirmwareVersion(&'static str),
602 NotEnoughBytesWritten { is: usize, should: usize },
604 NotEnoughBytesRead { is: usize, should: usize },
606 EndpointNotFound,
608 InvalidPayload,
610 Protocol(u8, Vec<u8>),
612 UnknownChip(u8),
614 UnsupportedOperation,
616}
617
618impl ProbeError for WchLinkError {}