use crate::rtt::Error;
use crate::{config::MemoryRegion, Core, MemoryInterface};
use std::cmp::min;
use zerocopy_derive::{FromBytes, FromZeroes};
pub trait RttChannel {
fn number(&self) -> usize;
fn name(&self) -> Option<&str>;
fn buffer_size(&self) -> usize;
}
#[repr(C)]
#[derive(Debug, FromZeroes, FromBytes, Copy, Clone)]
pub(crate) struct RttChannelBufferInner<T> {
standard_name_pointer: T,
buffer_start_pointer: T,
size_of_buffer: T,
write_offset: T,
read_offset: T,
flags: T,
}
impl<T> RttChannelBufferInner<T> {
pub fn write_buffer_ptr_offset(&self) -> usize {
std::mem::offset_of!(RttChannelBufferInner<T>, write_offset)
}
pub fn read_buffer_ptr_offset(&self) -> usize {
std::mem::offset_of!(RttChannelBufferInner<T>, read_offset)
}
pub fn flags_offset(&self) -> usize {
std::mem::offset_of!(RttChannelBufferInner<T>, flags)
}
pub fn size() -> usize {
std::mem::size_of::<RttChannelBufferInner<T>>()
}
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum RttChannelBuffer {
Buffer32(RttChannelBufferInner<u32>),
Buffer64(RttChannelBufferInner<u64>),
}
impl RttChannelBuffer {
pub fn size(&self) -> usize {
match self {
RttChannelBuffer::Buffer32(_) => RttChannelBufferInner::<u32>::size(),
RttChannelBuffer::Buffer64(_) => RttChannelBufferInner::<u64>::size(),
}
}
}
impl From<RttChannelBufferInner<u32>> for RttChannelBuffer {
fn from(value: RttChannelBufferInner<u32>) -> Self {
RttChannelBuffer::Buffer32(value)
}
}
impl From<RttChannelBufferInner<u64>> for RttChannelBuffer {
fn from(value: RttChannelBufferInner<u64>) -> Self {
RttChannelBuffer::Buffer64(value)
}
}
impl RttChannelBuffer {
pub fn buffer_start_pointer(&self) -> u64 {
match self {
RttChannelBuffer::Buffer32(x) => u64::from(x.buffer_start_pointer),
RttChannelBuffer::Buffer64(x) => x.buffer_start_pointer,
}
}
pub fn standard_name_pointer(&self) -> u64 {
match self {
RttChannelBuffer::Buffer32(x) => u64::from(x.standard_name_pointer),
RttChannelBuffer::Buffer64(x) => x.standard_name_pointer,
}
}
pub fn size_of_buffer(&self) -> u64 {
match self {
RttChannelBuffer::Buffer32(x) => u64::from(x.size_of_buffer),
RttChannelBuffer::Buffer64(x) => x.size_of_buffer,
}
}
pub fn read_buffer_offsets(&self, core: &mut Core, ptr: u64) -> Result<(u64, u64), Error> {
Ok(match self {
RttChannelBuffer::Buffer32(h32) => {
let mut block = [0u32; 2];
core.read_32(ptr + h32.write_buffer_ptr_offset() as u64, block.as_mut())?;
(u64::from(block[0]), u64::from(block[1]))
}
RttChannelBuffer::Buffer64(h64) => {
let mut block = [0u64; 2];
core.read_64(ptr + h64.write_buffer_ptr_offset() as u64, block.as_mut())?;
(block[0], block[1])
}
})
}
pub fn write_write_buffer_ptr(
&self,
core: &mut Core,
ptr: u64,
buffer_ptr: u64,
) -> Result<(), Error> {
match self {
RttChannelBuffer::Buffer32(h32) => {
core.write_word_32(
ptr + h32.write_buffer_ptr_offset() as u64,
buffer_ptr.try_into().unwrap(),
)?;
}
RttChannelBuffer::Buffer64(h64) => {
core.write_word_64(ptr + h64.write_buffer_ptr_offset() as u64, buffer_ptr)?;
}
};
Ok(())
}
pub fn write_read_buffer_ptr(
&self,
core: &mut Core,
ptr: u64,
buffer_ptr: u64,
) -> Result<(), Error> {
match self {
RttChannelBuffer::Buffer32(h32) => {
core.write_word_32(
ptr + h32.read_buffer_ptr_offset() as u64,
buffer_ptr.try_into().unwrap(),
)?;
}
RttChannelBuffer::Buffer64(h64) => {
core.write_word_64(ptr + h64.read_buffer_ptr_offset() as u64, buffer_ptr)?;
}
};
Ok(())
}
pub fn read_flags(&self, core: &mut Core, ptr: u64) -> Result<u64, Error> {
Ok(match self {
RttChannelBuffer::Buffer32(h32) => {
u64::from(core.read_word_32(ptr + h32.flags_offset() as u64)?)
}
RttChannelBuffer::Buffer64(h64) => {
core.read_word_64(ptr + h64.flags_offset() as u64)?
}
})
}
pub fn write_flags(&self, core: &mut Core, ptr: u64, flags: u64) -> Result<(), Error> {
match self {
RttChannelBuffer::Buffer32(h32) => {
core.write_word_32(ptr + h32.flags_offset() as u64, flags.try_into().unwrap())?;
}
RttChannelBuffer::Buffer64(h64) => {
core.write_word_64(ptr + h64.flags_offset() as u64, flags)?;
}
};
Ok(())
}
}
#[derive(Debug)]
pub(crate) struct Channel {
number: usize,
core_id: usize,
ptr: u64,
name: Option<String>,
buffer_ptr: u64,
size: u64,
info: RttChannelBuffer,
}
impl Channel {
pub(crate) fn from(
core: &mut Core,
number: usize,
memory_map: &[MemoryRegion],
ptr: u64,
info: RttChannelBuffer,
) -> Result<Option<Channel>, Error> {
let buffer_ptr = info.buffer_start_pointer();
if buffer_ptr == 0 {
return Ok(None);
};
let name = if info.standard_name_pointer() == 0 {
None
} else {
read_c_string(core, memory_map, info.standard_name_pointer())?
};
let size = info.size_of_buffer();
Ok(Some(Channel {
number,
core_id: core.id(),
ptr,
name,
buffer_ptr,
size,
info,
}))
}
pub(crate) fn validate_core_id(&self, core: &mut Core) -> Result<(), Error> {
if core.id() == self.core_id {
Ok(())
} else {
Err(Error::IncorrectCoreSpecified(self.core_id, core.id()))
}
}
pub fn name(&self) -> Option<&str> {
self.name.as_ref().map(|s| s.as_ref())
}
pub fn buffer_size(&self) -> usize {
self.size as usize
}
fn read_pointers(&self, core: &mut Core, channel_kind: &str) -> Result<(u64, u64), Error> {
self.validate_core_id(core)?;
let (write, read): (u64, u64) = self.info.read_buffer_offsets(core, self.ptr)?;
let validate = |which, value| {
if value >= self.size {
Err(Error::ControlBlockCorrupted(format!(
"{which} pointer is {value} while buffer size is {} for {channel_kind}channel {} ({})",
self.size,
self.number,
self.name().unwrap_or("no name"),
)))
} else {
Ok(())
}
};
validate("write", write)?;
validate("read", read)?;
Ok((write, read))
}
}
#[derive(Debug)]
pub struct UpChannel(pub(crate) Channel);
impl UpChannel {
pub fn number(&self) -> usize {
self.0.number
}
pub fn name(&self) -> Option<&str> {
self.0.name()
}
pub fn buffer_size(&self) -> usize {
self.0.buffer_size()
}
pub fn mode(&self, core: &mut Core) -> Result<ChannelMode, Error> {
self.0.validate_core_id(core)?;
let flags = self.0.info.read_flags(core, self.0.ptr)?;
match flags & 0x3 {
0 => Ok(ChannelMode::NoBlockSkip),
1 => Ok(ChannelMode::NoBlockTrim),
2 => Ok(ChannelMode::BlockIfFull),
_ => Err(Error::ControlBlockCorrupted(String::from(
"The channel mode flags are invalid",
))),
}
}
pub fn set_mode(&self, core: &mut Core, mode: ChannelMode) -> Result<(), Error> {
self.0.validate_core_id(core)?;
let flags = self.0.info.read_flags(core, self.0.ptr)?;
let new_flags = (flags & !3) | (mode as u64);
self.0.info.write_flags(core, self.0.ptr, new_flags)?;
Ok(())
}
fn read_core(&self, core: &mut Core, mut buf: &mut [u8]) -> Result<(u64, usize), Error> {
self.0.validate_core_id(core)?;
let (write, mut read) = self.0.read_pointers(core, "up ")?;
let mut total = 0;
while !buf.is_empty() {
let count = min(self.readable_contiguous(write, read), buf.len());
if count == 0 {
break;
}
core.read(self.0.buffer_ptr + read, &mut buf[..count])?;
total += count;
read += count as u64;
if read >= self.0.size {
read = 0;
}
buf = &mut buf[count..];
}
Ok((read, total))
}
pub fn read(&self, core: &mut Core, buf: &mut [u8]) -> Result<usize, Error> {
self.0.validate_core_id(core)?;
let (read, total) = self.read_core(core, buf)?;
if total > 0 {
self.0.info.write_read_buffer_ptr(core, self.0.ptr, read)?;
}
Ok(total)
}
pub fn peek(&self, core: &mut Core, buf: &mut [u8]) -> Result<usize, Error> {
self.0.validate_core_id(core)?;
Ok(self.read_core(core, buf)?.1)
}
fn readable_contiguous(&self, write: u64, read: u64) -> usize {
(if read > write {
self.0.size - read
} else {
write - read
}) as usize
}
}
impl RttChannel for UpChannel {
fn number(&self) -> usize {
self.0.number
}
fn name(&self) -> Option<&str> {
self.0.name()
}
fn buffer_size(&self) -> usize {
self.0.buffer_size()
}
}
#[derive(Debug)]
pub struct DownChannel(pub(crate) Channel);
impl DownChannel {
pub fn number(&self) -> usize {
self.0.number
}
pub fn name(&self) -> Option<&str> {
self.0.name()
}
pub fn buffer_size(&self) -> usize {
self.0.buffer_size()
}
pub fn write(&self, core: &mut Core, mut buf: &[u8]) -> Result<usize, Error> {
self.0.validate_core_id(core)?;
let (mut write, read) = self.0.read_pointers(core, "down ")?;
if self.writable_contiguous(write, read) == 0 {
return Ok(0);
}
let mut total = 0;
while !buf.is_empty() {
let count = min(self.writable_contiguous(write, read), buf.len());
if count == 0 {
break;
}
core.write_8(self.0.buffer_ptr + write, &buf[..count])?;
total += count;
write += count as u64;
if write >= self.0.size {
write = 0;
}
buf = &buf[count..];
}
self.0
.info
.write_write_buffer_ptr(core, self.0.ptr, write)?;
Ok(total)
}
fn writable_contiguous(&self, write: u64, read: u64) -> usize {
(if read > write {
read - write - 1
} else if read == 0 {
self.0.size - write - 1
} else {
self.0.size - write
}) as usize
}
}
impl RttChannel for DownChannel {
fn number(&self) -> usize {
self.0.number
}
fn name(&self) -> Option<&str> {
self.0.name()
}
fn buffer_size(&self) -> usize {
self.0.buffer_size()
}
}
fn read_c_string(
core: &mut Core,
memory_map: &[MemoryRegion],
ptr: u64,
) -> Result<Option<String>, Error> {
let range = memory_map
.iter()
.filter_map(|r| match r {
MemoryRegion::Nvm(r) => Some(&r.range),
MemoryRegion::Ram(r) => Some(&r.range),
_ => None,
})
.find(|r| r.contains(&ptr));
let Some(range) = range else {
return Ok(None);
};
let mut bytes = vec![0u8; min(128, (range.end - ptr) as usize)];
core.read(ptr, bytes.as_mut())?;
let return_value = bytes
.iter()
.position(|&b| b == 0)
.map(|p| String::from_utf8_lossy(&bytes[..p]).into_owned());
tracing::debug!(
"probe-rs-rtt::Channel::read_c_string() result = {:?}",
return_value
);
Ok(return_value)
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
#[repr(u32)]
pub enum ChannelMode {
NoBlockSkip = 0,
NoBlockTrim = 1,
BlockIfFull = 2,
}