use std::ops::Range;
use crate::emulator::Memory;
use crate::soc::Width;
use crate::time::VirtualNanos;
pub(crate) const PERIOD: u64 = 1_000_000;
pub(crate) fn next_drain(at: VirtualNanos) -> VirtualNanos {
VirtualNanos((at.0 / PERIOD + 1) * PERIOD)
}
pub(crate) const SYMBOL: &str = "_SEGGER_RTT";
const ID: &[u8; 16] = b"SEGGER RTT\0\0\0\0\0\0";
const UP_CHANNELS: u64 = 16;
const UP: u64 = 24;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Fields {
pointer: Width,
}
impl Fields {
fn pointer_bytes(self) -> u64 {
u64::from(self.pointer.bytes())
}
fn buffer(self) -> u64 {
self.pointer_bytes()
}
fn size(self) -> u64 {
2 * self.pointer_bytes()
}
fn write(self) -> u64 {
self.size() + 4
}
fn read(self) -> u64 {
self.size() + 8
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Rtt {
block: Block,
ram: Vec<Range<u64>>,
fields: Fields,
by_id: bool,
skip: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Block {
At(u64),
Somewhere,
Nowhere,
}
impl Rtt {
pub(crate) fn new(
symbol: Option<u64>,
has_symbols: bool,
ram: Vec<Range<u64>>,
pointer: Width,
) -> Self {
let block = match symbol {
Some(at) => Block::At(at),
None if has_symbols => Block::Nowhere,
None => Block::Somewhere,
};
Self {
block,
ram,
fields: Fields { pointer },
by_id: block == Block::Somewhere,
skip: 0,
}
}
pub(crate) fn found(&self) -> Option<u64> {
match self.block {
Block::At(at) if self.by_id => Some(at),
_ => None,
}
}
pub(crate) fn waiting(&self, memory: &mut dyn Memory) -> u64 {
let Block::At(block) = self.block else {
return 0;
};
channel(memory, block, &self.ram, self.fields).map_or(0, |channel| channel.waiting())
}
pub(crate) fn put_back(&mut self, found: Option<u64>, skip: u64) {
if self.by_id {
self.block = found.map_or(Block::Somewhere, Block::At);
}
self.skip = skip;
}
pub(crate) fn has_none(&self) -> bool {
self.block == Block::Nowhere
}
pub(crate) fn drain(&mut self, memory: &mut dyn Memory) -> Vec<u8> {
if self.block == Block::Somewhere
&& let Some(at) = find(memory, &self.ram)
{
self.block = Block::At(at);
}
let Block::At(block) = self.block else {
return Vec::new();
};
let mut bytes = terminal(memory, block, &self.ram, self.fields).unwrap_or_default();
let skipped = bytes
.len()
.min(usize::try_from(self.skip).unwrap_or(usize::MAX));
bytes.drain(..skipped);
self.skip -= skipped as u64;
bytes
}
}
fn at(memory: &mut dyn Memory, base: u64, offset: u64, width: Width) -> Option<u64> {
memory.read(base.checked_add(offset)?, width).ok()
}
fn has_id(memory: &mut dyn Memory, address: u64) -> bool {
(0..)
.zip(ID)
.all(|(offset, &byte)| at(memory, address, offset, Width::W8) == Some(u64::from(byte)))
}
fn find(memory: &mut dyn Memory, ram: &[Range<u64>]) -> Option<u64> {
ram.iter()
.flat_map(|range| (range.start..range.end).step_by(4))
.find(|&address| has_id(memory, address))
}
struct Channel {
up: u64,
buffer: u64,
size: u64,
write: u64,
read: u64,
}
impl Channel {
fn waiting(&self) -> u64 {
(self.write + self.size - self.read) % self.size
}
}
fn channel(
memory: &mut dyn Memory,
block: u64,
ram: &[Range<u64>],
fields: Fields,
) -> Option<Channel> {
if !has_id(memory, block) || at(memory, block, UP_CHANNELS, Width::W32)? == 0 {
return None;
}
let up = block.checked_add(UP)?;
let word = |memory: &mut dyn Memory, offset| at(memory, up, offset, Width::W32);
let buffer = at(memory, up, fields.buffer(), fields.pointer)?;
let size = word(memory, fields.size())?;
let write = word(memory, fields.write())?;
let read = word(memory, fields.read())?;
let end = buffer.checked_add(size)?;
let in_ram = ram
.iter()
.any(|range| range.start <= buffer && end <= range.end);
(in_ram && write < size && read < size).then_some(Channel {
up,
buffer,
size,
write,
read,
})
}
fn terminal(
memory: &mut dyn Memory,
block: u64,
ram: &[Range<u64>],
fields: Fields,
) -> Option<Vec<u8>> {
let Channel {
up,
buffer,
size,
write,
read,
} = channel(memory, block, ram, fields)?;
let (first, second) = if read <= write {
(read..write, 0..0)
} else {
(read..size, 0..write)
};
let bytes = first
.chain(second)
.map(|offset| {
at(memory, buffer, offset, Width::W8).and_then(|byte| u8::try_from(byte).ok())
})
.collect::<Option<Vec<u8>>>()?;
memory.write(up + fields.read(), Width::W32, write).ok()?;
Some(bytes)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::laid::Laid;
const RAM: Range<u64> = 0x2000_0000..0x2000_1000;
const BLOCK: u64 = 0x2000_0100;
const BUFFER: u64 = 0x2000_0400;
const SIZE: u32 = 16;
const ILP32: Fields = Fields {
pointer: Width::W32,
};
fn laid(text: &[u8], from: u32) -> Laid {
let mut memory = Laid::default();
memory.bytes(BUFFER, &[b'.'; SIZE as usize]);
for (offset, &byte) in (from..).zip(text) {
memory.bytes(BUFFER + u64::from(offset % SIZE), &[byte]);
}
memory.bytes(BLOCK, ID);
let write = (from + u32::try_from(text.len()).unwrap()) % SIZE;
memory.word(BLOCK + UP_CHANNELS, 1);
memory.word(BLOCK + 20, 1);
for (offset, value) in [
(0, 0),
(ILP32.buffer(), u32::try_from(BUFFER).unwrap()),
(ILP32.size(), SIZE),
(ILP32.write(), write),
(ILP32.read(), from),
(20, 2),
] {
memory.word(BLOCK + UP + offset, value);
}
memory
}
fn read_offset(memory: &mut Laid) -> u64 {
memory.read(BLOCK + UP + ILP32.read(), Width::W32).unwrap()
}
#[test]
fn the_control_block_is_found_by_symbol_or_by_its_id() {
let ram: Vec<Range<u64>> = [RAM].into();
let mut memory = laid(b"hi", 0);
let mut by_symbol = Rtt::new(Some(BLOCK), true, ram.clone(), Width::W32);
assert_eq!(by_symbol.drain(&mut memory), b"hi");
let mut memory = laid(b"hi", 0);
let mut by_id = Rtt::new(None, false, ram.clone(), Width::W32);
assert_eq!(by_id.drain(&mut memory), b"hi");
assert_eq!(by_id.block, Block::At(BLOCK), "found once, kept");
let mut nowhere = Rtt::new(None, true, ram, Width::W32);
assert!(nowhere.has_none(), "symbols without the block's");
assert!(nowhere.drain(&mut memory).is_empty());
let mut empty = Laid::default();
empty.bytes(0x2000_0000, &[0; 0x1000]);
let mut waiting = Rtt::new(None, false, [RAM].into(), Width::W32);
assert!(waiting.drain(&mut empty).is_empty(), "not there yet");
assert_eq!(waiting.block, Block::Somewhere);
}
#[test]
fn a_drain_takes_from_rdoff_to_wroff_across_the_wrap_and_writes_rdoff_back() {
let mut rtt = Rtt::new(Some(BLOCK), true, [RAM].into(), Width::W32);
let mut memory = laid(b"0123456789", 3);
assert_eq!(rtt.drain(&mut memory), b"0123456789");
assert_eq!(read_offset(&mut memory), 13);
assert!(rtt.drain(&mut memory).is_empty(), "nothing new");
let mut memory = laid(b"round the end", 10);
assert_eq!(rtt.drain(&mut memory), b"round the end", "past the wrap");
assert_eq!(read_offset(&mut memory), 7);
let mut spoiled = laid(b"x", 0);
spoiled.bytes(BLOCK, b"SEGGER RTX");
assert!(
rtt.drain(&mut spoiled).is_empty(),
"an ID that doesn't check out"
);
let mut spoiled = laid(b"x", 0);
spoiled.word(BLOCK + UP + ILP32.write(), SIZE);
assert!(
rtt.drain(&mut spoiled).is_empty(),
"an offset past the buffer"
);
assert_eq!(read_offset(&mut spoiled), 0, "left as it was");
}
#[test]
fn a_firmware_with_64_bit_pointers_is_drained_where_its_layout_puts_each_field() {
const LP64: Fields = Fields {
pointer: Width::W64,
};
let mut memory = Laid::default();
memory.bytes(BUFFER, b"wide");
memory.bytes(BLOCK, ID);
memory.word(BLOCK + UP_CHANNELS, 1);
memory.bytes(BLOCK + UP + LP64.buffer(), &BUFFER.to_le_bytes());
for (offset, value) in [(LP64.size(), SIZE), (LP64.write(), 4), (LP64.read(), 0)] {
memory.word(BLOCK + UP + offset, value);
}
let mut rtt = Rtt::new(Some(BLOCK), true, [RAM].into(), Width::W64);
assert_eq!(rtt.drain(&mut memory), b"wide");
assert_eq!(memory.read(BLOCK + UP + 24, Width::W32), Some(4), "RdOff");
}
#[test]
fn a_buffer_outside_ram_is_never_read() {
let mut rtt = Rtt::new(Some(BLOCK), true, [RAM].into(), Width::W32);
let mut huge = laid(b"x", 0);
for (offset, value) in [
(ILP32.size(), u32::MAX),
(ILP32.write(), 0xFFFF_FFF0),
(ILP32.read(), 0),
] {
huge.word(BLOCK + UP + offset, value);
}
assert!(rtt.drain(&mut huge).is_empty(), "a buffer of 4 GiB");
assert_eq!(read_offset(&mut huge), 0);
let mut past = laid(b"x", 0);
let near_the_end = u32::try_from(RAM.end).unwrap() - SIZE / 2;
past.bytes(u64::from(near_the_end), &[b'x'; SIZE as usize]);
past.word(BLOCK + UP + ILP32.buffer(), near_the_end);
assert!(rtt.drain(&mut past).is_empty(), "running past RAM's end");
assert_eq!(read_offset(&mut past), 0);
}
}