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;
pub fn monitor(cli: &Cli, args: &MonitorArgs) -> ExitCode {
const CMD: &str = "monitor";
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 => run_dmdata(cli, args),
MonitorSource::Rtt => fail(
cli,
CMD,
ErrorKind::CapabilityUnsupported,
"rtt monitor is not implemented yet",
Some("uart / sdi / dmdata are available; rtt (RAM ring buffer) comes next"),
),
}
}
fn run_duration(cli: &Cli) -> Option<Duration> {
cli.timeout.map(Duration::from_secs)
}
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: &str,
raw: bool,
) -> 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 out = std::io::stdout().lock();
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
{
break;
}
match file.read(&mut buf) {
Ok(0) => break,
Ok(n) => {
let _ = out.write_all(&buf[..n]);
let _ = out.flush();
}
Err(e) => {
eprintln!("\nmonitor: serial error: {e}");
break;
}
}
}
return ExitCode::SUCCESS;
}
#[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,
);
}
};
loop {
if let Some(dl) = deadline
&& Instant::now() >= dl
{
break;
}
match std::io::Read::read(&mut sp, &mut buf) {
Ok(0) => {}
Ok(n) => {
let _ = out.write_all(&buf[..n]);
let _ = out.flush();
}
Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => {}
Err(e) => {
eprintln!("\nmonitor: serial error: {e}");
break;
}
}
}
ExitCode::SUCCESS
}
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 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)
}
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 (speed, mut warnings) = match parse::speed(&cli.speed) {
Ok(v) => v,
Err(m) => return fail(cli, CMD, ErrorKind::Usage, m, None),
};
let _ = &mut warnings;
{
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::TransportTimeout,
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)
}
fn run_dmdata(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,
format!(
"dmdata monitor needs a RISC-V-mode probe (this is {})",
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),
cli.chip.as_deref(),
&mut warnings,
) {
Ok(s) => s,
Err(_) => return fail(cli, CMD, ErrorKind::AttachFailed, "attach failed", None),
};
if !cli.json {
eprintln!(
"monitor: dmdata (DMI poll, core runs) via {} (Ctrl-C to stop)",
entry.dev.serial().unwrap_or("?")
);
}
let deadline = run_duration(cli).map(|d| Instant::now() + d);
let mut out = std::io::stdout().lock();
let mut dm = session.dm();
let _ = dm.resume();
loop {
if let Some(dl) = deadline
&& Instant::now() >= dl
{
break;
}
match dm.dmdata_poll(&[]) {
Ok(Some(bytes)) if !bytes.is_empty() => {
let _ = out.write_all(&bytes);
let _ = out.flush();
}
Ok(_) => std::thread::sleep(Duration::from_millis(2)),
Err(e) => {
eprintln!("\nmonitor: dmi error: {e}");
break;
}
}
}
ExitCode::SUCCESS
}
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::TransportTimeout,
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
}
}