use std::io::{Read, Write};
use std::sync::mpsc::{self, Receiver};
use std::time::{Duration, Instant};
use ch32rv_contract::event::Event;
use ch32rv_contract::policy::MonitorSource;
use ch32rv_contract::{ErrorKind, Warning};
use ch32rv_dmi::DmiError;
use crate::args::Cli;
use crate::session::Session;
const RTT_RAM_BASE: u32 = 0x2000_0000;
const RTT_MAGIC: &[u8] = b"SEGGER RTT";
const RTT_MAX_BUF: u32 = 0x1_0000;
const RTT_MAX_CHANNELS: u32 = 16;
const RTT_DEFAULT_SCAN: u32 = 8 * 1024;
const RTT_READ_CHUNK: u32 = 8192;
const RTT_HEADER_LEN: u32 = 24;
const RTT_DESC_LEN: u32 = 24;
const RTT_DESC_WRITE_OFF: u32 = 12;
const RTT_DESC_READ_OFF: u32 = 16;
fn le32(b: &[u8], off: usize) -> u32 {
u32::from_le_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]])
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct RttHeader {
offset: usize,
max_up: u32,
max_down: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct RingDesc {
buffer: u32,
size: u32,
wr: u32,
rd: u32,
}
impl RingDesc {
fn parse(d: &[u8]) -> Option<Self> {
if d.len() < RTT_DESC_LEN as usize {
return None;
}
let desc = RingDesc {
buffer: le32(d, 4),
size: le32(d, 8),
wr: le32(d, 12),
rd: le32(d, 16),
};
let ok = desc.size > 0
&& desc.size <= RTT_MAX_BUF
&& desc.wr < desc.size
&& desc.rd < desc.size
&& desc.buffer >= RTT_RAM_BASE;
ok.then_some(desc)
}
fn used(&self) -> u32 {
if self.wr >= self.rd {
self.wr - self.rd
} else {
self.size - self.rd + self.wr
}
}
}
fn find_control_block(snap: &[u8]) -> Option<RttHeader> {
let mut from = 0usize;
while let Some(rel) = snap[from..]
.windows(RTT_MAGIC.len())
.position(|w| w == RTT_MAGIC)
{
let pos = from + rel;
let hdr_end = pos + RTT_HEADER_LEN as usize;
if hdr_end + RTT_DESC_LEN as usize <= snap.len() {
let max_up = le32(snap, pos + 16);
let max_down = le32(snap, pos + 20);
if (1..=RTT_MAX_CHANNELS).contains(&max_up)
&& max_down <= RTT_MAX_CHANNELS
&& RingDesc::parse(&snap[hdr_end..]).is_some()
{
return Some(RttHeader {
offset: pos,
max_up,
max_down,
});
}
}
from = pos + 1;
}
None
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct RttChannels {
up: u32,
down: Option<u32>,
}
pub(crate) enum DmiSource {
Dmdata,
Rtt(RttChannels),
}
pub(crate) enum OpenError {
NotDmi,
NoControlBlock {
scan_len: u32,
},
Dmi(DmiError),
}
impl From<DmiError> for OpenError {
fn from(e: DmiError) -> Self {
OpenError::Dmi(e)
}
}
fn transport(e: ch32rv_wchlink::WchLinkError) -> DmiError {
DmiError::Transport(e.to_string())
}
fn read_region(session: &mut Session, base: u32, len: u32) -> Vec<u8> {
let mut buf = Vec::with_capacity(len as usize);
let mut off = 0u32;
while off < len {
let want = RTT_READ_CHUNK.min(len - off);
match session.link().read_mem(base + off, want) {
Ok(mut chunk) => {
buf.append(&mut chunk);
off += want;
}
Err(_) => break,
}
}
buf
}
impl DmiSource {
pub(crate) fn open(
session: &mut Session,
source: MonitorSource,
warnings: &mut Vec<Warning>,
) -> Result<Self, OpenError> {
match source {
MonitorSource::Dmdata => Ok(DmiSource::Dmdata),
MonitorSource::Rtt => Self::open_rtt(session, warnings),
MonitorSource::Uart | MonitorSource::Sdi => Err(OpenError::NotDmi),
}
}
fn open_rtt(session: &mut Session, warnings: &mut Vec<Warning>) -> Result<Self, OpenError> {
let scan_len = {
let db = session.db();
match db.resolve_by_chip_id(session.attach.chip_id) {
ch32rv_target::Resolution::Sku(s) if s.sram_bytes > 0 => {
s.sram_bytes.min(64 * 1024)
}
_ => RTT_DEFAULT_SCAN,
}
};
let mut found = None;
for _ in 0..10 {
session.dm().halt()?;
let snap = read_region(session, RTT_RAM_BASE, scan_len);
if let Some(h) = find_control_block(&snap) {
found = Some(h);
break;
}
session.dm().resume()?;
std::thread::sleep(Duration::from_millis(100));
}
let Some(h) = found else {
return Err(OpenError::NoControlBlock { scan_len });
};
if h.max_up > 1 || h.max_down > 1 {
warnings.push(Warning {
code: "rtt-channels".to_owned(),
msg: format!(
"the RTT control block has {} up / {} down channels; only channel 0 is streamed",
h.max_up, h.max_down
),
});
}
let base = RTT_RAM_BASE + h.offset as u32;
let up = base + RTT_HEADER_LEN;
let down = (h.max_down >= 1).then_some(up + RTT_DESC_LEN * h.max_up);
Ok(DmiSource::Rtt(RttChannels { up, down }))
}
pub(crate) fn name(&self) -> &'static str {
match self {
DmiSource::Dmdata => "dmdata",
DmiSource::Rtt(_) => "rtt",
}
}
pub(crate) fn idle(&self) -> Duration {
match self {
DmiSource::Dmdata => Duration::from_millis(2),
DmiSource::Rtt(_) => Duration::from_millis(50),
}
}
pub(crate) fn describe(&self) -> String {
match self {
DmiSource::Dmdata => "dmdata (DMI mailbox, core runs)".to_owned(),
DmiSource::Rtt(ch) => format!(
"rtt (RAM ring @ 0x{:08x}, core briefly halts per poll)",
ch.up - RTT_HEADER_LEN
),
}
}
pub(crate) fn poll(
&mut self,
session: &mut Session,
input: &mut Vec<u8>,
) -> Result<Vec<u8>, DmiError> {
match self {
DmiSource::Dmdata => {
let r = session.dm().dmdata_poll(&input[..input.len().min(3)])?;
input.drain(..r.sent);
Ok(r.received)
}
DmiSource::Rtt(ch) => {
let ch = *ch;
let was_halted = session.dm().is_halted()?;
if !was_halted {
session.dm().halt()?;
}
let result = rtt_exchange(session, ch, input);
if !was_halted {
let _ = session.dm().resume();
}
result
}
}
}
}
fn rtt_exchange(
session: &mut Session,
ch: RttChannels,
input: &mut Vec<u8>,
) -> Result<Vec<u8>, DmiError> {
let raw = session
.link()
.read_mem(ch.up, RTT_DESC_LEN)
.map_err(transport)?;
let Some(up) = RingDesc::parse(&raw) else {
return Ok(Vec::new());
};
let mut out = Vec::new();
if up.wr != up.rd {
let link = session.link();
if up.wr > up.rd {
out = link
.read_mem(up.buffer + up.rd, up.wr - up.rd)
.map_err(transport)?;
} else {
out = link
.read_mem(up.buffer + up.rd, up.size - up.rd)
.map_err(transport)?;
if up.wr > 0 {
out.extend(link.read_mem(up.buffer, up.wr).map_err(transport)?);
}
}
session.dm().write_mem32(ch.up + RTT_DESC_READ_OFF, up.wr)?;
}
if let Some(down_addr) = ch.down
&& !input.is_empty()
{
let raw = session
.link()
.read_mem(down_addr, RTT_DESC_LEN)
.map_err(transport)?;
if let Some(down) = RingDesc::parse(&raw) {
let room = (down.size - 1 - down.used()) as usize;
let n = input.len().min(room);
if n > 0 {
let first = n.min((down.size - down.wr) as usize);
let mut dm = session.dm();
dm.write_mem(down.buffer + down.wr, &input[..first])?;
if n > first {
dm.write_mem(down.buffer, &input[first..n])?;
}
dm.write_mem32(
down_addr + RTT_DESC_WRITE_OFF,
(down.wr + n as u32) % down.size,
)?;
input.drain(..n);
}
}
}
Ok(out)
}
pub(crate) fn dmi_error_kind(e: &DmiError) -> ErrorKind {
match e {
DmiError::Timeout => ErrorKind::TransportTimeout,
_ => ErrorKind::TransferFailed,
}
}
pub(crate) enum Sink {
Raw,
Json {
source: &'static str,
pending: Vec<u8>,
},
}
impl Sink {
pub(crate) fn new(cli: &Cli, source: &'static str) -> Self {
if cli.json {
Sink::Json {
source,
pending: Vec::new(),
}
} else {
Sink::Raw
}
}
pub(crate) fn write(&mut self, bytes: &[u8]) {
if bytes.is_empty() {
return;
}
match self {
Sink::Raw => {
let mut out = std::io::stdout().lock();
let _ = out.write_all(bytes);
let _ = out.flush();
}
Sink::Json { source, pending } => {
pending.extend_from_slice(bytes);
let text = take_text(pending);
emit_output(source, text);
}
}
}
pub(crate) fn finish(&mut self) {
if let Sink::Json { source, pending } = self
&& !pending.is_empty()
{
let text = String::from_utf8_lossy(pending).into_owned();
pending.clear();
emit_output(source, text);
}
}
}
fn emit_output(source: &str, data: String) {
if data.is_empty() {
return;
}
let ev = Event::Output {
source: source.to_owned(),
data,
};
if let Ok(line) = serde_json::to_string(&ev) {
let mut err = std::io::stderr().lock();
let _ = writeln!(err, "{line}");
}
}
fn take_text(buf: &mut Vec<u8>) -> String {
let mut out = String::new();
let mut i = 0usize;
loop {
match std::str::from_utf8(&buf[i..]) {
Ok(s) => {
out.push_str(s);
i = buf.len();
break;
}
Err(e) => {
let ok = e.valid_up_to();
out.push_str(&String::from_utf8_lossy(&buf[i..i + ok]));
i += ok;
match e.error_len() {
Some(bad) => {
out.push('\u{FFFD}');
i += bad;
}
None => break,
}
}
}
}
buf.drain(..i);
out
}
pub(crate) fn spawn_reader(mut r: impl Read + Send + 'static) -> Receiver<Vec<u8>> {
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let mut buf = [0u8; 256];
loop {
match r.read(&mut buf) {
Ok(0) => break,
Ok(n) => {
if tx.send(buf[..n].to_vec()).is_err() {
break;
}
}
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
Err(_) => break,
}
}
});
rx
}
pub(crate) fn drain_input(rx: &Receiver<Vec<u8>>, into: &mut Vec<u8>) {
while let Ok(chunk) = rx.try_recv() {
into.extend_from_slice(&chunk);
}
}
pub(crate) fn stream(
session: &mut Session,
src: &mut DmiSource,
sink: &mut Sink,
input: &Receiver<Vec<u8>>,
deadline: Option<Instant>,
) -> Result<(), DmiError> {
let mut pending = Vec::new();
loop {
if let Some(dl) = deadline
&& Instant::now() >= dl
{
return Ok(());
}
drain_input(input, &mut pending);
let out = src.poll(session, &mut pending)?;
if out.is_empty() {
std::thread::sleep(src.idle());
} else {
sink.write(&out);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn snapshot(at: usize, max_up: u32, max_down: u32, up0: RingDesc) -> Vec<u8> {
let descs = (max_up + max_down) as usize;
let mut ram = vec![0u8; at + 24 + 24 * descs + 8];
ram[at..at + RTT_MAGIC.len()].copy_from_slice(RTT_MAGIC);
ram[at + 16..at + 20].copy_from_slice(&max_up.to_le_bytes());
ram[at + 20..at + 24].copy_from_slice(&max_down.to_le_bytes());
let d = at + 24;
ram[d + 4..d + 8].copy_from_slice(&up0.buffer.to_le_bytes());
ram[d + 8..d + 12].copy_from_slice(&up0.size.to_le_bytes());
ram[d + 12..d + 16].copy_from_slice(&up0.wr.to_le_bytes());
ram[d + 16..d + 20].copy_from_slice(&up0.rd.to_le_bytes());
ram
}
fn valid_up0() -> RingDesc {
RingDesc {
buffer: RTT_RAM_BASE + 0x100,
size: 256,
wr: 10,
rd: 0,
}
}
#[test]
fn finds_valid_control_block_with_counts() {
let ram = snapshot(64, 1, 1, valid_up0());
assert_eq!(
find_control_block(&ram),
Some(RttHeader {
offset: 64,
max_up: 1,
max_down: 1
})
);
}
#[test]
fn down_descriptor_follows_all_up_descriptors() {
let h = RttHeader {
offset: 0,
max_up: 2,
max_down: 1,
};
let base = RTT_RAM_BASE + h.offset as u32;
let up = base + RTT_HEADER_LEN;
let down = up + RTT_DESC_LEN * h.max_up;
assert_eq!(down, RTT_RAM_BASE + 24 + 48);
}
#[test]
fn rejects_bogus_channel_counts() {
assert_eq!(find_control_block(&snapshot(0, 0, 1, valid_up0())), None);
assert_eq!(find_control_block(&snapshot(0, 1000, 1, valid_up0())), None);
}
#[test]
fn skips_magic_with_bogus_descriptor() {
let bogus = RingDesc {
buffer: 0,
size: 0xFFFF_FFFF,
wr: 0,
rd: 0,
};
assert_eq!(find_control_block(&snapshot(64, 1, 1, bogus)), None);
assert_eq!(find_control_block(&[0u8; 64]), None);
}
#[test]
fn ring_used_handles_wrap() {
let d = RingDesc {
buffer: RTT_RAM_BASE,
size: 16,
wr: 2,
rd: 14,
};
assert_eq!(d.used(), 4);
assert_eq!(RingDesc { wr: 9, rd: 3, ..d }.used(), 6);
}
#[test]
fn take_text_keeps_incomplete_tail_across_chunks() {
let mut buf = b"ab\xe3\x81".to_vec();
assert_eq!(take_text(&mut buf), "ab");
assert_eq!(buf, b"\xe3\x81");
buf.extend_from_slice(b"\x93!");
assert_eq!(take_text(&mut buf), "こ!");
assert!(buf.is_empty());
}
#[test]
fn take_text_replaces_invalid_bytes() {
let mut buf = b"x\xffy".to_vec();
assert_eq!(take_text(&mut buf), "x\u{FFFD}y");
assert!(buf.is_empty());
}
#[test]
fn le32_reads_little_endian() {
assert_eq!(le32(&[0x78, 0x56, 0x34, 0x12], 0), 0x1234_5678);
}
}