use std::io::{BufRead, Write};
use std::process::ExitCode;
use serde_json::{Value, json};
use crate::args::Cli;
pub fn discovery(_cli: &Cli) -> ExitCode {
let stdin = std::io::stdin();
let mut out = std::io::stdout();
for line in stdin.lock().lines() {
let Ok(line) = line else { break };
let word = line
.split_whitespace()
.next()
.unwrap_or("")
.to_ascii_uppercase();
match word.as_str() {
"" => {}
"HELLO" => emit(
&mut out,
&json!({"eventType":"hello","protocolVersion":1,"message":"OK"}),
),
"START" => emit(&mut out, &json!({"eventType":"start","message":"OK"})),
"STOP" => emit(&mut out, &json!({"eventType":"stop","message":"OK"})),
"LIST" => emit(&mut out, &json!({"eventType":"list","ports": list_ports()})),
"START_SYNC" => {
emit(&mut out, &json!({"eventType":"start_sync","message":"OK"}));
for p in list_ports() {
emit(&mut out, &json!({"eventType":"add","port": p}));
}
}
"QUIT" => {
emit(&mut out, &json!({"eventType":"quit","message":"OK"}));
return ExitCode::SUCCESS;
}
other => emit(
&mut out,
&json!({"eventType": other.to_ascii_lowercase(), "error": true, "message": format!("unknown command {other}")}),
),
}
}
ExitCode::SUCCESS
}
fn list_ports() -> Vec<Value> {
let mut ports = wchlink_ports();
ports.extend(oep_ports());
ports.extend(hid_ports());
ports
}
fn oep_ports() -> Vec<Value> {
let mut out = Vec::new();
for dev in crate::oep::oep_devices() {
let Some(path) = crate::oep::oep_port(&dev) else {
continue;
};
let cache = crate::broker::listing_cache(&path);
match read_oep_listing(&dev, &path) {
Some(ports) => {
let _ = std::fs::write(&cache, Value::Array(ports.clone()).to_string());
out.extend(ports);
}
None => {
if let Some(Value::Array(ports)) = std::fs::read_to_string(&cache)
.ok()
.and_then(|t| serde_json::from_str(&t).ok())
{
out.extend(ports);
}
}
}
}
out
}
fn read_oep_listing(dev: &ch32rv_usb::UsbDeviceInfo, path: &str) -> Option<Vec<Value>> {
let quick = std::time::Duration::from_millis(300);
let mut p = match crate::broker::existing_link(path) {
Some(mut l) => {
l.set_timeout(quick);
ch32rv_oep::session::Probe::connect(l).ok()?
}
None => crate::oep::connect_upstream(path, Some(quick)).ok()?.0,
};
let slots = ch32rv_oep::config::slots(&mut p).ok()?;
let states = ch32rv_oep::config::slot_states(&mut p).unwrap_or_default();
let id = crate::oep::probe_id(dev);
let db = ch32rv_target::Db::builtin();
Some(
slots
.iter()
.map(|s| {
let st = states.iter().find(|x| x.slot == s.slot);
let family =
st.and_then(|x| x.wch_chip_id())
.and_then(|c| match db.resolve_by_chip_id(c) {
ch32rv_target::Resolution::Sku(k) => Some(k.family.clone()),
ch32rv_target::Resolution::Family(f, _) => Some(f),
ch32rv_target::Resolution::Unknown => None,
});
let state = match st.map(|x| x.state) {
Some(0) => "connected",
Some(1) => "absent",
Some(2) => "lock-mismatch",
Some(3) => "no-target-id",
_ => "unknown",
};
json!({
"address": format!("oep://{id}/{}", s.name),
"label": match &family {
Some(f) => format!("{} ({f}) on OEP probe {id}", s.name),
None => format!("{} on OEP probe {id}", s.name),
},
"protocol": "oep",
"protocolLabel": "OEP probe",
"hardwareId": format!("{id}/{}", s.name),
"properties": {
"vid": format!("0x{:04x}", dev.vid()),
"pid": format!("0x{:04x}", dev.pid()),
"slot": s.name,
"state": state,
"chip": family,
"port": path,
},
})
})
.collect(),
)
}
fn wchlink_ports() -> Vec<Value> {
let entries = crate::cmd_probe::wch_devices().unwrap_or_default();
entries
.iter()
.map(|e| {
let serial = e.dev.serial().unwrap_or("unknown");
let driver = e.dev.foreign_driver();
let label = match &driver {
Some(d) => format!("WCH-Link {serial} (cannot open: driver {d})"),
None => format!("WCH-Link {serial}"),
};
let mut v = json!({
"address": format!("wchlink://{serial}"),
"label": label,
"protocol": "wchlink",
"protocolLabel": "WCH-Link (RISC-V debug)",
"hardwareId": serial,
"properties": {
"serial": serial,
"vid": format!("0x{:04x}", e.dev.vid()),
"pid": format!("0x{:04x}", e.dev.pid()),
"mode": crate::cmd_probe::mode_str(e.mode),
},
});
if let Some(d) = driver {
v["properties"]["driver"] = json!(d);
}
v
})
.collect()
}
fn hid_ports() -> Vec<Value> {
let devs = ch32rv_usb::enumerate().unwrap_or_default();
devs.iter()
.filter(|d| ch32rv_boot::DEFAULT_HID_IDS.contains(&(d.vid(), d.pid())))
.map(|d| {
let topology = d.topology();
json!({
"address": format!("hid://{topology}"),
"label": format!("HID bootloader {topology}"),
"protocol": "hid",
"protocolLabel": "USB HID bootloader (rv003usb)",
"hardwareId": topology,
"properties": {
"vid": format!("0x{:04x}", d.vid()),
"pid": format!("0x{:04x}", d.pid()),
"topology": topology,
},
})
})
.collect()
}
fn emit(out: &mut impl Write, v: &Value) {
let _ = writeln!(out, "{v}");
let _ = out.flush();
}
use std::net::{Shutdown, TcpStream};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, Receiver, RecvTimeoutError};
use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
use std::time::Duration;
use ch32rv_contract::policy::MonitorSource;
use ch32rv_usb::DeviceLock;
use serialport::SerialPort;
use crate::cmd_probe::Entry;
use crate::session::Session;
use crate::source::{self, DmiSource};
const SERIAL_SOURCES: [MonitorSource; 6] = [
MonitorSource::Uart,
MonitorSource::Sdi,
MonitorSource::Dmdata,
MonitorSource::Dmseq,
MonitorSource::Rtt,
MonitorSource::FixtureUart,
];
const OEP_SOURCES: [MonitorSource; 4] = [
MonitorSource::Dmseq,
MonitorSource::Dmdata,
MonitorSource::Sdi,
MonitorSource::FixtureUart,
];
const WCHLINK_SOURCES: [MonitorSource; 4] = [
MonitorSource::Dmdata,
MonitorSource::Sdi,
MonitorSource::Dmseq,
MonitorSource::Rtt,
];
const CHIP_AUTO: &str = "auto";
const BAUDS: [u32; 20] = [
300, 600, 750, 1200, 2400, 4800, 9600, 19200, 31250, 38400, 57600, 74880, 115200, 230400,
250000, 460800, 500000, 921600, 1_000_000, 2_000_000,
];
const TICK: Duration = Duration::from_millis(50);
#[derive(Debug, Clone, PartialEq, Eq)]
struct Settings {
sources: &'static [MonitorSource],
source: MonitorSource,
baud: u32,
dtr: bool,
rts: bool,
chip: Option<String>,
}
impl Settings {
fn new(protocol: &str, chip: Option<String>) -> Self {
let sources: &'static [MonitorSource] = match protocol {
"wchlink" => &WCHLINK_SOURCES,
"oep" => &OEP_SOURCES,
_ => &SERIAL_SOURCES,
};
Settings {
sources,
source: sources[0],
baud: 9600,
dtr: true,
rts: true,
chip,
}
}
fn set(&mut self, key: &str, value: &str) -> Result<(), String> {
let on_off = |v: &str| match v {
"on" => Ok(true),
"off" => Ok(false),
_ => Err(format!("{key} is on or off, not `{value}`")),
};
match key {
"source" => {
self.source = *self
.sources
.iter()
.find(|s| s.as_str() == value)
.ok_or_else(|| format!("unknown source `{value}`"))?;
}
"baudrate" => {
self.baud = value
.parse()
.ok()
.filter(|b| BAUDS.contains(b))
.ok_or_else(|| format!("unsupported baudrate `{value}`"))?;
}
"dtr" => self.dtr = on_off(value)?,
"rts" => self.rts = on_off(value)?,
"chip" if value == CHIP_AUTO => self.chip = None,
"chip" => {
if !chip_names().iter().any(|n| n == value) {
return Err(format!("`{value}` is not a chip in ch32rv's DB"));
}
self.chip = Some(value.to_owned());
}
_ => return Err(format!("unknown setting `{key}`")),
}
Ok(())
}
fn describe(&self, protocol: &str) -> Value {
let on_off = |b: bool| if b { "on" } else { "off" };
json!({
"protocol": protocol,
"configuration_parameters": {
"source": {
"label": "Runtime output source", "type": "enum",
"value": self.sources.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
"selected": self.source.as_str(),
},
"baudrate": {
"label": "Baudrate (uart)", "type": "enum",
"value": BAUDS.iter().map(u32::to_string).collect::<Vec<_>>(),
"selected": self.baud.to_string(),
},
"dtr": {
"label": "DTR (uart)", "type": "enum",
"value": ["on", "off"], "selected": on_off(self.dtr),
},
"rts": {
"label": "RTS (uart)", "type": "enum",
"value": ["on", "off"], "selected": on_off(self.rts),
},
"chip": {
"label": "Board chip", "type": "enum",
"value": std::iter::once(CHIP_AUTO.to_owned()).chain(chip_names()).collect::<Vec<_>>(),
"selected": self.chip.as_deref().unwrap_or(CHIP_AUTO),
},
}
})
}
}
fn chip_names() -> Vec<String> {
let db = ch32rv_target::Db::builtin();
let mut names: Vec<String> = db
.skus()
.iter()
.flat_map(|s| [s.family.clone(), s.sku.clone()])
.collect();
names.sort();
names.dedup();
names
}
#[derive(Clone)]
struct Env {
speed: String,
lock_timeout: Duration,
}
struct Shared {
settings: Mutex<Settings>,
stop: AtomicBool,
}
impl Shared {
fn settings(&self) -> Settings {
match self.settings.lock() {
Ok(g) => g.clone(),
Err(p) => p.into_inner().clone(),
}
}
}
pub fn monitor(cli: &Cli, protocol: &str) -> ExitCode {
let stdin = std::io::stdin();
let mut out = std::io::stdout();
let env = Env {
speed: cli.speed.clone(),
lock_timeout: Duration::from_secs(cli.lock_timeout),
};
let shared = Arc::new(Shared {
settings: Mutex::new(Settings::new(protocol, cli.chip.clone())),
stop: AtomicBool::new(false),
});
let mut running: Option<(JoinHandle<()>, TcpStream)> = None;
let close = |running: &mut Option<(JoinHandle<()>, TcpStream)>| {
shared.stop.store(true, Ordering::SeqCst);
if let Some((h, sock)) = running.take() {
let _ = sock.shutdown(Shutdown::Both);
let _ = h.join();
}
};
for line in stdin.lock().lines() {
let Ok(line) = line else { break };
let parts: Vec<&str> = line.split_whitespace().collect();
let word = parts.first().copied().unwrap_or("").to_ascii_uppercase();
match word.as_str() {
"" => {}
"HELLO" => emit(
&mut out,
&json!({"eventType":"hello","protocolVersion":1,"message":"OK"}),
),
"DESCRIBE" => emit(
&mut out,
&json!({
"eventType":"describe","message":"OK",
"port_description": shared.settings().describe(protocol),
}),
),
"CONFIGURE" => {
let result = match (parts.get(1), parts.get(2)) {
(Some(k), Some(v)) => match shared.settings.lock() {
Ok(mut g) => g.set(k, v),
Err(p) => p.into_inner().set(k, v),
},
_ => Err("CONFIGURE <key> <value>".to_owned()),
};
match result {
Ok(()) => emit(&mut out, &json!({"eventType":"configure","message":"OK"})),
Err(m) => emit(
&mut out,
&json!({"eventType":"configure","error":true,"message":m}),
),
}
}
"OPEN" => {
let (Some(&client), Some(&address)) = (parts.get(1), parts.get(2)) else {
emit(
&mut out,
&json!({"eventType":"open","error":true,"message":"OPEN needs <host:port> <port>"}),
);
continue;
};
if running.is_some() {
emit(
&mut out,
&json!({"eventType":"open","error":true,"message":"already open"}),
);
continue;
}
shared.stop.store(false, Ordering::SeqCst);
let (tx, rx) = mpsc::channel();
let (client, address) = (client.to_owned(), address.to_owned());
let (shared2, env2) = (shared.clone(), env.clone());
let handle =
std::thread::spawn(move || session(&client, &address, &shared2, &env2, tx));
match rx.recv() {
Ok(Ok(sock)) => {
running = Some((handle, sock));
emit(&mut out, &json!({"eventType":"open","message":"OK"}));
}
Ok(Err(m)) => {
let _ = handle.join();
emit(
&mut out,
&json!({"eventType":"open","error":true,"message":m}),
);
}
Err(_) => {
let _ = handle.join();
emit(
&mut out,
&json!({"eventType":"open","error":true,"message":"monitor session ended before it opened"}),
);
}
}
}
"CLOSE" => {
close(&mut running);
emit(&mut out, &json!({"eventType":"close","message":"OK"}));
}
"QUIT" => {
close(&mut running);
emit(&mut out, &json!({"eventType":"quit","message":"OK"}));
return ExitCode::SUCCESS;
}
other => emit(
&mut out,
&json!({
"eventType": other.to_ascii_lowercase(),"error":true,
"message":format!("unknown command {other}")}),
),
}
}
close(&mut running);
ExitCode::SUCCESS
}
struct Resolved {
entry: Option<Entry>,
port: Option<String>,
oep: Option<crate::oep::OepAddr>,
}
fn resolve_address(address: &str) -> Result<Resolved, String> {
let entries = crate::cmd_probe::wch_devices().map_err(|e| format!("USB enumeration: {e}"))?;
if let Some(serial) = address.strip_prefix("wchlink://") {
let entry = entries
.into_iter()
.find(|e| e.dev.serial() == Some(serial))
.ok_or_else(|| format!("no WCH-Link with serial {serial} is connected"))?;
let port = entry.dev.serial_ports().into_iter().next();
return Ok(Resolved {
entry: Some(entry),
port,
oep: None,
});
}
if address.starts_with("oep://") {
let a = crate::oep::resolve_oep_url(address)?;
return Ok(Resolved {
entry: None,
port: None,
oep: Some(a),
});
}
let sel = ch32rv_usb::Selector::Port(address.to_owned());
let mut owners: Vec<Entry> = entries
.into_iter()
.enumerate()
.filter(|(i, e)| sel.matches(&e.dev, *i))
.map(|(_, e)| e)
.collect();
if owners.len() > 1 {
return Err(format!(
"{} WCH-Links could own {address} (without a USB serial number they cannot be told \
apart on this OS); pick the Link's wchlink://<serial> port, or name it with --probe",
owners.len()
));
}
let entry = owners.pop();
let oep = entry
.is_none()
.then(|| crate::oep::OepAddr::Serial(address.to_owned()));
Ok(Resolved {
entry,
port: Some(address.to_owned()),
oep,
})
}
enum Backend {
Uart {
port: Box<dyn SerialPort>,
applied: Settings,
},
Sdi {
rx: Receiver<std::io::Result<Vec<u8>>>,
_lock: DeviceLock,
},
Dmi {
src: Box<DmiSource>,
session: Box<Session>,
},
Oep {
console: Box<crate::oep::ConsoleSession>,
source: MonitorSource,
baud: u32,
},
}
enum Leave {
Stop,
Switch,
Failed(String),
}
fn lock_for(entry: &Entry, env: &Env) -> Result<DeviceLock, String> {
let key = entry
.dev
.serial()
.map(str::to_owned)
.unwrap_or_else(|| entry.dev.topology());
DeviceLock::acquire(&key, env.lock_timeout)
.map_err(|e| format!("{e} (another ch32rv is using this probe)"))
}
impl Backend {
fn open(r: &Resolved, s: Settings, env: &Env) -> Result<Self, String> {
let wch_via_broker = r.entry.as_ref().and_then(|e| {
matches!(s.source, MonitorSource::Dmdata | MonitorSource::Dmseq)
.then(|| crate::oep::OepAddr::Wch(crate::broker::BrokerTarget::wch(e)))
});
if let Some(oep) = wch_via_broker.as_ref().or(r.oep.as_ref())
&& (s.source != MonitorSource::Uart || matches!(oep, crate::oep::OepAddr::Slot { .. }))
{
use crate::oep::StreamWanted;
use ch32rv_oep::stream::Mechanism;
let wanted = match s.source {
MonitorSource::Sdi => StreamWanted::Console(Mechanism::Sdi),
MonitorSource::Dmdata => StreamWanted::Console(Mechanism::Dmdata),
MonitorSource::Dmseq => StreamWanted::Console(Mechanism::Dmseq),
MonitorSource::FixtureUart => StreamWanted::FixtureUart(s.baud),
MonitorSource::Uart | MonitorSource::Rtt => {
return Err(format!(
"{} is not available on an OEP probe (it offers dmseq, dmdata, sdi and fixture-uart)",
s.source.as_str()
));
}
};
let c = crate::oep::ConsoleSession::open(oep, wanted, s.chip.as_deref())?;
return Ok(Backend::Oep {
console: Box::new(c),
source: s.source,
baud: s.baud,
});
}
match s.source {
MonitorSource::FixtureUart => {
Err("fixture-uart is an OEP probe's source (this port is not one)".to_owned())
}
MonitorSource::Uart => {
let path = r
.port
.as_deref()
.ok_or("this probe has no serial port for uart")?;
let mut port = serialport::new(path, s.baud)
.timeout(TICK)
.open()
.map_err(|e| format!("open {path}: {e}"))?;
let _ = port.write_data_terminal_ready(s.dtr);
let _ = port.write_request_to_send(s.rts);
Ok(Backend::Uart { port, applied: s })
}
MonitorSource::Sdi => {
let entry = r
.entry
.as_ref()
.ok_or("sdi needs a WCH-LinkE (this serial port is not one)")?;
let path = r
.port
.as_deref()
.ok_or("this probe has no serial port for sdi")?;
let lock = lock_for(entry, env)?;
let (speed, _) = crate::parse::speed(&env.speed)?;
crate::cmd_monitor::enable_sdi(entry, speed, s.chip.as_deref())
.map_err(|e| e.msg)?;
Ok(Backend::Sdi {
rx: raw_reader(path)?,
_lock: lock,
})
}
MonitorSource::Dmdata | MonitorSource::Dmseq | MonitorSource::Rtt => {
let entry = r.entry.as_ref().ok_or_else(|| {
format!(
"{} needs a WCH-Link (this serial port is not one)",
s.source.as_str()
)
})?;
let (speed, _) = crate::parse::speed(&env.speed)?;
let mut warnings = Vec::new();
let mut session = Session::attach(
entry,
speed,
Duration::from_millis(1000),
env.lock_timeout,
s.chip.as_deref(),
None,
&mut warnings,
)
.map_err(|e| format!("attach: {e}"))?;
let src = DmiSource::open(&mut session, s.source, &mut warnings)
.map_err(|e| format!("open {}: {e}", s.source.as_str()))?;
let _ = session.dm().resume();
Ok(Backend::Dmi {
src: Box::new(src),
session: Box::new(session),
})
}
}
}
fn run(&mut self, shared: &Shared, sock: &mut TcpStream, input: &Receiver<Vec<u8>>) -> Leave {
let mut pending = Vec::new();
let mut buf = [0u8; 512];
loop {
if shared.stop.load(Ordering::SeqCst) {
return Leave::Stop;
}
let now = shared.settings();
source::drain_input(input, &mut pending);
let got: Result<Vec<u8>, String> = match self {
Backend::Uart { port, applied, .. } => {
if now.source != MonitorSource::Uart {
return Leave::Switch;
}
if now.baud != applied.baud {
let _ = port.set_baud_rate(now.baud);
}
if now.dtr != applied.dtr {
let _ = port.write_data_terminal_ready(now.dtr);
}
if now.rts != applied.rts {
let _ = port.write_request_to_send(now.rts);
}
*applied = now.clone();
if !pending.is_empty() {
if let Err(e) = port.write_all(&pending) {
return Leave::Failed(format!("serial port write failed: {e}"));
}
pending.clear();
}
match port.read(&mut buf) {
Ok(n) => Ok(buf[..n].to_vec()),
Err(e) if e.kind() == std::io::ErrorKind::TimedOut => Ok(Vec::new()),
Err(e) => Err(format!("serial port read failed: {e}")),
}
}
Backend::Sdi { rx, .. } => {
if now.source != MonitorSource::Sdi {
return Leave::Switch;
}
pending.clear();
match rx.recv_timeout(TICK) {
Ok(Ok(d)) if !d.is_empty() => Ok(d),
Err(RecvTimeoutError::Timeout) => Ok(Vec::new()),
Ok(Err(e)) => Err(format!("serial port read failed: {e}")),
Ok(Ok(_)) | Err(RecvTimeoutError::Disconnected) => {
Err("the serial port closed (probe disconnected?)".to_owned())
}
}
}
Backend::Oep {
console,
source,
baud,
} => {
if now.source != *source {
return Leave::Switch;
}
if *source == MonitorSource::FixtureUart && now.baud != *baud {
if let Err(e) = console.set_baud(now.baud) {
return Leave::Failed(format!("fixture UART baud: {e}"));
}
*baud = now.baud;
}
if !pending.is_empty() {
match console.write(&pending) {
Ok(n) => {
pending.drain(..n.min(pending.len()));
}
Err(e) => return Leave::Failed(format!("console write failed: {e}")),
}
}
match console.poll() {
Ok(b) => {
if b.is_empty() {
std::thread::sleep(Duration::from_millis(20));
}
Ok(b)
}
Err(e) => Err(format!("console read failed: {e}")),
}
}
Backend::Dmi { src, session } => {
if now.source.as_str() != src.name() {
return Leave::Switch;
}
match src.poll(session, &mut pending) {
Ok(b) => {
if b.is_empty() {
std::thread::sleep(src.idle());
}
Ok(b)
}
Err(e) => Err(format!("{} failed: {e}", src.name())),
}
}
};
match got {
Ok(b) if b.is_empty() => {}
Ok(b) => {
if sock.write_all(&b).and_then(|()| sock.flush()).is_err() {
return Leave::Stop;
}
}
Err(m) => return Leave::Failed(m),
}
}
}
}
fn raw_reader(path: &str) -> Result<Receiver<std::io::Result<Vec<u8>>>, String> {
use std::io::Read;
let mut file = crate::cmd_monitor::open_raw(path).map_err(|e| format!("open {path}: {e}"))?;
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let mut chunk = [0u8; 512];
loop {
let r = file.read(&mut chunk).map(|n| chunk[..n].to_vec());
let last = !matches!(&r, Ok(v) if !v.is_empty());
if tx.send(r).is_err() || last {
break;
}
}
});
Ok(rx)
}
fn session(
client: &str,
address: &str,
shared: &Shared,
env: &Env,
ready: mpsc::Sender<Result<TcpStream, String>>,
) {
let setup = || -> Result<(Resolved, Backend, TcpStream), String> {
let r = resolve_address(address)?;
let backend = Backend::open(&r, shared.settings(), env)?;
let sock = TcpStream::connect(client).map_err(|e| format!("connect {client}: {e}"))?;
Ok((r, backend, sock))
};
let (resolved, mut backend, mut sock) = match setup() {
Ok(v) => v,
Err(m) => {
let _ = ready.send(Err(m));
return;
}
};
let (Ok(keep), Ok(reader)) = (sock.try_clone(), sock.try_clone()) else {
let _ = ready.send(Err("socket clone failed".to_owned()));
return;
};
if ready.send(Ok(keep)).is_err() {
return;
}
let input = source::spawn_reader(reader);
let wch = resolved.entry.is_some();
say_reclock(&mut sock, shared.settings().source, wch);
loop {
match backend.run(shared, &mut sock, &input) {
Leave::Stop => return,
Leave::Switch => {
drop(backend);
let next = shared.settings();
match Backend::open(&resolved, next.clone(), env) {
Ok(b) => {
backend = b;
say_reclock(&mut sock, next.source, wch);
}
Err(m) => lost(&mut sock, &m),
}
}
Leave::Failed(m) => {
drop(backend);
lost(&mut sock, &m);
}
}
}
}
fn say_reclock(sock: &mut TcpStream, source: MonitorSource, wch_link: bool) {
if source == MonitorSource::Uart || !wch_link {
return;
}
let _ = write!(
sock,
"[ch32rv monitor] attaching may have changed the target clock (UART baud rate, millis, \
timers); reset the board to run at its own clock\r\n"
);
let _ = sock.flush();
}
fn lost(sock: &mut TcpStream, why: &str) -> ! {
let _ = write!(sock, "\r\n[ch32rv monitor] stopped: {why}\r\n");
let _ = sock.flush();
let _ = sock.shutdown(Shutdown::Both);
std::process::exit(0)
}