diff --git a/Cargo.toml b/Cargo.toml index 867a0c8..0b241c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ default-target = "targets/aarch64-vesper-metta.json" unstable = [] realtime = [] noserial = [] -jlink = ['jlink_rtt'] +jlink = [] #'jlink_rtt' #[lib] #name = "nucleus" @@ -37,7 +37,7 @@ bitflags = "1.0.1" register = "0.3.2" cortex-a = "2.4" #embedded-serial = "0.5.0" -jlink_rtt = { version = "0.1.0", optional = true } +# jlink_rtt = { version = "0.1.0", optional = true } [profile.dev] panic = "abort" # @todo try panic_rtt when feature jlink diff --git a/src/jlink_rtt.rs b/src/jlink_rtt.rs new file mode 100644 index 0000000..3650552 --- /dev/null +++ b/src/jlink_rtt.rs @@ -0,0 +1,244 @@ +// 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::ptr; + +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: *const u8, + buf_start: *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. Those functions are + /// implemented differently here. + flags: u32, +} + +impl Buffer { + fn init(&mut self, buf: &mut [u8]) { + self.name = b"Terminal\0".as_ptr(); + self.buf_start = buf.as_mut_ptr(); + self.size_of_buffer = buf.len() as u32; + self.write_offset = 0; + self.read_offset = 0; + self.flags = 0; // 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.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 NUM_UP + max_up_buffers: i32, + /// Initialized to NUM_DOWN + 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, +} + +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 as *const u8, + buf_start: 0 as *mut u8, + read_offset: 0, + write_offset: 0, + flags: 0, + size_of_buffer: 0, + }, + down: Buffer { + name: 0 as *const u8, + buf_start: 0 as *mut u8, + write_offset: 0, + read_offset: 0, + flags: 0, + 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 fmt::Write for Output { + fn write_str(&mut self, s: &str) -> fmt::Result { + unsafe { + _SEGGER_RTT.up.write(s.as_bytes(), true); + } + 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 { + Self { blocked: false } + } +} + +impl fmt::Write for NonBlockingOutput { + fn write_str(&mut self, s: &str) -> fmt::Result { + if !self.blocked { + unsafe { + _SEGGER_RTT.init(); + if !_SEGGER_RTT.up.write(s.as_bytes(), false) { + self.blocked = true; + } + } + } + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index dda8d0e..0b1064d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,7 @@ extern crate rlibc; pub mod arch; pub use arch::*; mod devices; +mod jlink_rtt; mod macros; pub mod platform; mod sync;