use std::io::Write;
use std::process::ExitCode;
use std::time::{Duration, Instant};
use ch32rv_contract::ErrorKind;
use ch32rv_contract::policy::MonitorSource;
use crate::args::{Cli, MonitorArgs, MonitorCmd, SwitchState};
use crate::cmd_probe::{Entry, fail, mode_str, select_entry};
use crate::parse;
use crate::session::Session;
use crate::source::{self, DmiSource, OpenError, Sink};
pub fn monitor(cli: &Cli, args: &MonitorArgs) -> ExitCode {
match &args.cmd {
Some(MonitorCmd::List) => return list(cli),
Some(MonitorCmd::Sdi { state }) => return sdi_toggle(cli, *state),
None => {}
}
match args.source {
MonitorSource::Uart => run_uart(cli, args),
MonitorSource::Sdi => run_sdi(cli, args),
MonitorSource::Dmdata | MonitorSource::Rtt => run_dmi(cli, args.source),
}
}
fn run_duration(cli: &Cli) -> Option<Duration> {
cli.duration.map(Duration::from_secs)
}
fn finish_ok(
cli: &Cli,
cmd: &str,
source: &str,
warnings: Vec<ch32rv_contract::Warning>,
) -> ExitCode {
if cli.json {
let mut env = ch32rv_contract::ResultEnvelope::success(cmd);
env.result = Some(serde_json::json!({ "source": source }));
env.warnings = warnings;
crate::print_envelope(&env)
} else {
ExitCode::SUCCESS
}
}
fn resolve_port(
cli: &Cli,
cmd: &str,
entry: &Entry,
explicit: &Option<String>,
) -> Result<String, ExitCode> {
match explicit {
Some(p) => Ok(p.clone()),
None => entry.dev.serial_ports().into_iter().next().ok_or_else(|| {
fail(
cli,
cmd,
ErrorKind::DeviceNotFound,
"no CDC serial port found for this probe",
Some("pass --port /dev/ttyACMx, or check the probe's serial interface"),
)
}),
}
}
fn stream_port(
cli: &Cli,
cmd: &str,
port_path: &str,
baud: u32,
label: &'static str,
raw: bool,
warnings: Vec<ch32rv_contract::Warning>,
) -> ExitCode {
if !cli.json {
eprintln!("monitor: {label} on {port_path} @ {baud} baud (Ctrl-C to stop)");
}
let deadline = run_duration(cli).map(|d| Instant::now() + d);
let mut sink = Sink::new(cli, label);
let mut buf = [0u8; 512];
#[cfg(unix)]
if raw {
use std::io::Read;
let mut file = match std::fs::File::open(port_path) {
Ok(f) => f,
Err(e) => {
return fail(
cli,
cmd,
ErrorKind::DeviceOpenFailed,
format!("open {port_path}: {e}"),
None,
);
}
};
loop {
if let Some(dl) = deadline
&& Instant::now() >= dl
{
sink.finish();
return finish_ok(cli, cmd, label, warnings);
}
match file.read(&mut buf) {
Ok(0) => {
sink.finish();
return fail(
cli,
cmd,
ErrorKind::DeviceNotFound,
format!("{port_path} closed (probe disconnected?)"),
None,
);
}
Ok(n) => sink.write(&buf[..n]),
Err(e) => {
sink.finish();
return fail(
cli,
cmd,
ErrorKind::TransferFailed,
format!("read {port_path}: {e}"),
None,
);
}
}
}
}
#[cfg(not(unix))]
let _ = raw;
let mut sp = match serialport::new(port_path, baud)
.timeout(Duration::from_millis(200))
.open()
{
Ok(s) => s,
Err(e) => {
return fail(
cli,
cmd,
ErrorKind::DeviceOpenFailed,
format!("open {port_path}: {e}"),
None,
);
}
};
let input = source::spawn_reader(std::io::stdin());
let mut pending = Vec::new();
loop {
if let Some(dl) = deadline
&& Instant::now() >= dl
{
sink.finish();
return finish_ok(cli, cmd, label, warnings);
}
source::drain_input(&input, &mut pending);
if !pending.is_empty() {
if let Err(e) = sp.write_all(&pending) {
sink.finish();
return fail(
cli,
cmd,
ErrorKind::TransferFailed,
format!("write {port_path}: {e}"),
None,
);
}
pending.clear();
}
match std::io::Read::read(&mut sp, &mut buf) {
Ok(0) => {}
Ok(n) => sink.write(&buf[..n]),
Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => {}
Err(e) => {
sink.finish();
return fail(
cli,
cmd,
ErrorKind::TransferFailed,
format!("read {port_path}: {e}"),
None,
);
}
}
}
}
fn run_uart(cli: &Cli, args: &MonitorArgs) -> ExitCode {
const CMD: &str = "monitor";
let entry = match select_entry(cli, CMD) {
Ok(e) => e,
Err(c) => return c,
};
let _lock = match crate::cmd_probe::lock_probe(cli, CMD, &entry) {
Ok(l) => l,
Err(c) => return c,
};
let port = match resolve_port(cli, CMD, &entry, &args.port) {
Ok(p) => p,
Err(c) => return c,
};
stream_port(cli, CMD, &port, args.baud, "uart", false, Vec::new())
}
fn run_sdi(cli: &Cli, args: &MonitorArgs) -> ExitCode {
const CMD: &str = "monitor";
let entry = match select_entry(cli, CMD) {
Ok(e) => e,
Err(c) => return c,
};
if entry.mode != ch32rv_contract::ProbeMode::Riscv {
return fail(
cli,
CMD,
ErrorKind::CapabilityUnsupported,
"sdi needs a RISC-V-mode LinkE",
None,
);
}
let _lock = match crate::cmd_probe::lock_probe(cli, CMD, &entry) {
Ok(l) => l,
Err(c) => return c,
};
let (speed, warnings) = match parse::speed(&cli.speed) {
Ok(v) => v,
Err(m) => return fail(cli, CMD, ErrorKind::Usage, m, None),
};
{
let mut link = match ch32rv_wchlink::WchLink::open(&entry.dev) {
Ok(l) => l,
Err(e) => return fail(cli, CMD, ErrorKind::DeviceOpenFailed, e.to_string(), None),
};
match link.probe_info() {
Ok(info) if matches!(info.variant, ch32rv_wchlink::Variant::LinkE) => {}
Ok(_) => {
return fail(
cli,
CMD,
ErrorKind::CapabilityUnsupported,
"SDI print forwarding is only available on a WCH-LinkE",
Some(
"use --source dmdata (host-side DMI) which works on any probe including the CH549 Link",
),
);
}
Err(e) => return fail(cli, CMD, ErrorKind::DeviceOpenFailed, e.to_string(), None),
}
let _ = link.set_speed_default(speed);
let attach = match link.attach_chip() {
Ok(a) => a,
Err(e) => return fail(cli, CMD, ErrorKind::AttachFailed, e.to_string(), None),
};
let _ = link.set_speed(attach.family_byte, speed);
if let Err(e) = link.set_sdi_print_enabled(true) {
return fail(
cli,
CMD,
ErrorKind::TransferFailed,
format!("enable SDI failed: {e}"),
None,
);
}
}
std::thread::sleep(Duration::from_millis(200));
let port = match resolve_port(cli, CMD, &entry, &args.port) {
Ok(p) => p,
Err(c) => return c,
};
if !cli.json {
eprintln!(
"note: sdi CDC forwarding is not yet reliable from ch32rv; if nothing appears, use \
`--source dmdata` (SerialDMDATA) or `wlink sdi-print enable`."
);
}
stream_port(cli, CMD, &port, args.baud, "sdi", true, warnings)
}
fn run_dmi(cli: &Cli, source: MonitorSource) -> ExitCode {
const CMD: &str = "monitor";
let entry = match select_entry(cli, CMD) {
Ok(e) => e,
Err(c) => return c,
};
if entry.mode != ch32rv_contract::ProbeMode::Riscv {
return fail(
cli,
CMD,
ErrorKind::CapabilityUnsupported,
format!(
"{} monitor needs a RISC-V-mode probe (this is {})",
source.as_str(),
mode_str(entry.mode)
),
None,
);
}
let (speed, mut warnings) = match parse::speed(&cli.speed) {
Ok(v) => v,
Err(m) => return fail(cli, CMD, ErrorKind::Usage, m, None),
};
let mut session = match Session::attach(
&entry,
speed,
Duration::from_millis(1000),
Duration::from_secs(cli.lock_timeout),
cli.chip.as_deref(),
cli.db.as_deref(),
&mut warnings,
) {
Ok(s) => s,
Err(e) => return crate::cmd_probe::session_error(cli, CMD, e),
};
let mut src = match DmiSource::open(&mut session, source, &mut warnings) {
Ok(s) => s,
Err(e) => return open_error(cli, CMD, e),
};
if !cli.json {
eprintln!(
"monitor: {} via {} (Ctrl-C to stop; stdin goes to the target)",
src.describe(),
entry.dev.serial().unwrap_or("?")
);
for w in &warnings {
eprintln!("warning[{}]: {}", w.code, w.msg);
}
}
let _ = session.dm().resume();
let input = source::spawn_reader(std::io::stdin());
let mut sink = Sink::new(cli, src.name());
let deadline = run_duration(cli).map(|d| Instant::now() + d);
let result = source::stream(&mut session, &mut src, &mut sink, &input, deadline);
sink.finish();
match result {
Ok(()) => finish_ok(cli, CMD, src.name(), warnings),
Err(e) => fail(
cli,
CMD,
source::dmi_error_kind(&e),
format!("{} stream failed: {e}", src.name()),
None,
),
}
}
pub(crate) fn open_error(cli: &Cli, cmd: &str, e: OpenError) -> ExitCode {
match e {
OpenError::NotDmi => fail(
cli,
cmd,
ErrorKind::CapabilityUnsupported,
"this source is a CDC serial one (uart/sdi), not a DMI source",
Some("use --source dmdata or --source rtt"),
),
OpenError::NoControlBlock { scan_len } => fail(
cli,
cmd,
ErrorKind::CapabilityUnsupported,
format!(
"no SEGGER RTT control block in the first {scan_len} bytes of RAM (from 0x2000_0000)"
),
Some("flash a SerialRTT/RTT sketch first; the block only appears after begin()"),
),
OpenError::Dmi(e) => fail(
cli,
cmd,
source::dmi_error_kind(&e),
format!("open source: {e}"),
None,
),
}
}
fn list(cli: &Cli) -> ExitCode {
let entries = crate::cmd_probe::wch_devices().unwrap_or_default();
if cli.json {
let ports: Vec<_> = entries
.iter()
.map(|e| {
serde_json::json!({
"probe": e.dev.serial(),
"mode": mode_str(e.mode),
"cdc_ports": e.dev.serial_ports(),
})
})
.collect();
let mut env = ch32rv_contract::ResultEnvelope::success("monitor.list");
env.result = Some(serde_json::json!({ "probes": ports }));
crate::print_envelope(&env)
} else {
println!(
"{:<16} {:<7} CDC PORTS (uart / sdi share these)",
"PROBE", "MODE"
);
for e in &entries {
println!(
"{:<16} {:<7} {}",
e.dev.serial().unwrap_or("-"),
mode_str(e.mode),
render_ports(&e.dev.serial_ports())
);
}
println!("\ndmdata / rtt do not use a CDC port (host reads them over DMI).");
ExitCode::SUCCESS
}
}
fn render_ports(ports: &[String]) -> String {
if ports.is_empty() {
"-".to_owned()
} else {
ports.join(", ")
}
}
fn sdi_toggle(cli: &Cli, state: SwitchState) -> ExitCode {
const CMD: &str = "monitor.sdi";
let entry: Entry = match select_entry(cli, CMD) {
Ok(e) => e,
Err(c) => return c,
};
let mut link = match ch32rv_wchlink::WchLink::open(&entry.dev) {
Ok(l) => l,
Err(e) => return fail(cli, CMD, ErrorKind::DeviceOpenFailed, e.to_string(), None),
};
let on = matches!(state, SwitchState::On);
let _ = link.probe_info();
let (speed, _) = parse::speed(&cli.speed).unwrap_or((ch32rv_wchlink::Speed::High, Vec::new()));
let _ = link.set_speed_default(speed);
if let Ok(attach) = link.attach_chip() {
let _ = link.set_speed(attach.family_byte, speed);
}
if let Err(e) = link.set_sdi_print_enabled(on) {
return fail(
cli,
CMD,
ErrorKind::TransferFailed,
format!("set SDI failed: {e}"),
None,
);
}
if cli.json {
let mut env = ch32rv_contract::ResultEnvelope::success(CMD);
env.result = Some(serde_json::json!({ "sdi": on }));
crate::print_envelope(&env)
} else {
println!(
"SDI print forwarding {}",
if on { "enabled" } else { "disabled" }
);
ExitCode::SUCCESS
}
}