vesper/src/jlink_rtt.rs

275 lines
7.7 KiB
Rust

// Custom implementation of JLink RTT debug protocol
// jlink_rtt crate has too many strange bugs
/// This module implements a limited version of the Segger
/// Real Time Transfer protocol between the debugger host
/// and the target program.
/// RTT works by scanning memory to look for a control block
/// containing a magic string (it is also possible to tell
/// the monitor exactly where to find this block).
/// The control block defines a set of "up" channels
/// and "down" channels that are named pipes of communication
/// between the two systems.
/// Each of these channels is implemented as a simple
/// ring buffer.
/// The cost of logging data to RTT is the cost of formatting
/// and writing it to the ring buffer in memory.
use core::fmt;
use core::mem::size_of;
use core::ptr;
use static_assertions::const_assert_eq;
static mut UP_BUF: [u8; 1024] = [0u8; 1024];
static mut DOWN_BUF: [u8; 16] = [0u8; 16];
/// Ring buffer for communicating between target and host.
/// This must be binary compatible with the RTT implementation
/// in the JLINK device.
#[repr(C)]
struct Buffer {
name: u32, //*const u8,
buf_start: u32, // *mut u8,
size_of_buffer: u32,
/// Position of next item to be written
/// Volatile as the host may change it.
write_offset: u32,
/// Position of next item to be read by host.
/// Volatile as the host may change it.
read_offset: u32,
/// In the segger library these flags control blocking
/// or non-blocking behavior.
flags: u32,
}
// Assumed by OpenOCD and probably JLink too...
const_assert_eq!(size_of::<Buffer>(), 24);
// Operating modes. Define behavior if buffer is full (not enough space for entire message)
const NO_BLOCK_SKIP: u32 = 0; // Skip. Do not block, output nothing. (Default)
const NO_BLOCK_TRIM: u32 = 1; // Trim: Do not block, output as much as fits.
const BLOCK_IF_FULL: u32 = 2; // Block: Wait until there is space in the buffer.
impl Buffer {
fn init(&mut self, buf: &mut [u8]) {
self.name = b"Terminal\0".as_ptr() as u32;
self.buf_start = buf.as_mut_ptr() as u32;
self.size_of_buffer = buf.len() as u32;
self.write_offset = 0;
self.read_offset = 0;
self.flags = NO_BLOCK_TRIM; // Non-blocking mode
}
fn get_read_offset(&self) -> u32 {
unsafe { ptr::read_volatile(&self.read_offset as *const u32) }
}
#[allow(unused)]
fn set_read_offset(&mut self, offset: u32) {
unsafe {
ptr::write_volatile(&mut self.read_offset as *mut u32, offset);
}
}
fn get_write_offset(&self) -> u32 {
unsafe { ptr::read_volatile(&self.write_offset as *const u32) }
}
fn set_write_offset(&mut self, offset: u32) {
unsafe {
ptr::write_volatile(&mut self.write_offset as *mut u32, offset);
}
}
/// Write data to the ring buffer.
/// Returns true if all of the data was written, which
/// will always be the case if blocking==true.
/// Returns false if blocking==false and the buffer was
/// full.
fn write(&mut self, buf: &[u8], blocking: bool) -> bool {
let mut buf = buf;
let mut write_off = self.get_write_offset() as usize;
let size_of_buffer = self.size_of_buffer as usize;
while buf.len() > 0 {
let read_off = self.get_read_offset() as usize;
let wrapping_capacity = if read_off > write_off {
read_off - write_off - 1
} else {
size_of_buffer - (write_off - read_off + 1)
};
// If we're full and non-blocking, return now.
// Otherwise, we'll spin with a series of 0 byte
// length increments until the host consumes data
// from the ring buffer.
if wrapping_capacity == 0 && !blocking {
return false;
}
let flat_capacity = size_of_buffer - write_off;
let to_copy = buf.len().min(flat_capacity).min(wrapping_capacity);
unsafe {
ptr::copy(
buf.as_ptr(),
(self.buf_start as *mut u8).offset(write_off as isize),
to_copy,
);
}
write_off += to_copy;
if write_off == size_of_buffer {
write_off = 0;
}
self.set_write_offset(write_off as u32);
buf = &buf[to_copy..];
}
true
}
}
/// The ControlBlock is the magic struct that the JLINK looks
/// for to discover the ring buffers.
#[repr(C)]
pub struct ControlBlock {
/// Initialized to "SEGGER RTT"
id: [u8; 16],
/// Initialized to 1
max_up_buffers: i32,
/// Initialized to 1
max_down_buffers: i32,
/// Note that RTT allows for this to be an array of
/// "up" buffers of size max_up_buffers, but for simplicity
/// just a single buffer is implemented here.
up: Buffer,
/// Note that RTT allows for this to be an array of
/// "down" buffers of size max_down_buffers, but for simplicity
/// just a single buffer is implemented here.
down: Buffer,
}
const_assert_eq!(size_of::<ControlBlock>(), 24 + 24 + 24);
unsafe impl Sync for ControlBlock {}
impl ControlBlock {
fn init(&mut self) {
if self.id[0] == b'S' {
return;
}
// Unsafe: use of mutable static
// mutable statics can be mutated by multiple threads: aliasing violations
// or data races will cause undefined behavior
unsafe {
self.up.init(&mut UP_BUF);
self.down.init(&mut DOWN_BUF);
}
// Compose the ident string such that we won't
// emit the string sequence in flash
self.id.copy_from_slice(b"_EGGER:RTT\0\0\0\0\0\0");
self.id[0] = b'S';
self.id[6] = b' ';
}
}
#[no_mangle]
pub static mut _SEGGER_RTT: ControlBlock = ControlBlock {
id: [0u8; 16],
max_up_buffers: 1,
max_down_buffers: 1,
up: Buffer {
name: 0,
buf_start: 0,
read_offset: 0,
write_offset: 0,
flags: NO_BLOCK_TRIM,
size_of_buffer: 0,
},
down: Buffer {
name: 0,
buf_start: 0,
write_offset: 0,
read_offset: 0,
flags: NO_BLOCK_TRIM,
size_of_buffer: 0,
},
};
/// A blocking output stream allowing data to be logged from the
/// target to the host.
/// Implements fmt::Write.
pub struct Output {}
impl Output {
/// Create a blocking output stream
#[inline]
pub fn new() -> Self {
unsafe {
_SEGGER_RTT.init();
}
Self {}
}
}
impl Drop for Output {
fn drop(&mut self) {}
}
impl crate::devices::ConsoleOps for Output {
fn puts(&self, s: &str) {
unsafe {
_SEGGER_RTT.up.write(s.as_bytes(), true);
}
}
fn putc(&self, c: char) {
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
self.puts(s);
}
}
impl fmt::Write for Output {
fn write_str(&mut self, s: &str) -> fmt::Result {
use crate::devices::console::ConsoleOps;
self.puts(s);
Ok(())
}
}
/// A non-blocking output stream allowing data to be logged from the
/// target to the host.
/// Implements fmt::Write.
pub struct NonBlockingOutput {
blocked: bool,
}
impl NonBlockingOutput {
/// Create a non-blocking output stream
#[inline]
pub fn new() -> Self {
unsafe {
_SEGGER_RTT.init();
}
Self { blocked: false }
}
}
impl fmt::Write for NonBlockingOutput {
fn write_str(&mut self, s: &str) -> fmt::Result {
if !self.blocked {
unsafe {
if !_SEGGER_RTT.up.write(s.as_bytes(), false) {
self.blocked = true;
}
}
}
Ok(())
}
}