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