1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
//! Target side implementation of the RTT (Real-Time Transfer) I/O protocol
//!
//! RTT implements input and output to/from a debug probe using in-memory ring buffers and memory
//! polling. This enables debug logging from the microcontroller with minimal delays and no
//! blocking, making it usable even in real-time applications where e.g. semihosting delays cannot
//! be tolerated.
//!
//! # Hardware support
//!
//! This crate is platform agnostic and can be used on any chip that supports background memory
//! access via its debug interface. The printing macros require a critical section which is
//! platform-dependent. Built-in ARM Cortex-M support can be enabled with the "cortex-m" feature,
//! and RISC-V support can be enabled with the "riscv" feature.
//!
//! To interface with RTT from the host computer, a debug probe such as an ST-Link or J-Link is
//! required. The normal debug protocol (e.g. SWD) is used to access RTT, so no extra connections
//! such as SWO pins are needed.
//!
//! # Initialization
//!
//! RTT must be initialized at the start of your program using one of the init macros. See the
//! macros for more details.
//!
//! The initialization macros return channel objects that can be used for writing and reading.
//! Different channel objects can safely be used concurrently in different contexts without locking.
//! In an interrupt-based application with realtime constraints you could use a separate channel for
//! every interrupt context to allow for lock-free logging.
//!
//! # Channels and virtual terminals
//!
//! RTT supports multiple *channels* in both directions. Up channels go from target to host, and
//! down channels go from host to target. Each channel is identified by its direction and number.
//!
//! By convention channel 0 is reserved for terminal use. In the up direction there is a set of
//! escape sequences that further enable the single channel to be treated as up to 16 *virtual
//! terminals*. This can be used to separate different types of messages (for example, log levels)
//! from each other without having to allocate memory for multiple buffers. As a downside, multiple
//! threads cannot write to the same channel at once, even if using different virtual terminal
//! numbers, so access has to be synchronized. Down channel 0 is conventionally used for keyboard
//! input.
//!
//! **Note:** Some host side programs only display channel 0 by default, so to see the other
//! channels you might need to configure them appropriately.
//!
//! The other channels can be used to either enable concurrent use from multiple sources without
//! locking, or to send e.g. binary data in either direction.
//!
//! Channel 0 can also be used for arbitrary data, but most tools expect it to be plain text.
//!
//! # Channel modes
//!
//! By default, channels start in [`NoBlockSkip`](ChannelMode::NoBlockSkip) mode, which discards
//! data if the buffer is full. This enables RTT to not crash the application if there is no debug
//! probe attached or if the host is not reading the buffers. However if the application outputs
//! faster than the host can read (which is easy to do, because writing is very fast), messages will
//! be lost. Channels can be set to blocking mode if this is desirable, however in that case the
//! application will likely freeze when the buffer fills up if a debugger is not attached.
//!
//! The channel mode can also be changed on the fly by the debug probe. Therefore it may be
//! advantageous to use a non-blocking mode in your microcontroller code, and set a blocking mode as
//! needed when debugging. That way you will never end up with an application that freezes without a
//! debugger connected.
//!
//! # Printing
//!
//! For no-hassle output the [`rprint`] and [`rprintln`] macros are provided. They use a single down
//! channel defined at initialization time, and a critical section for synchronization, and they
//! therefore work exactly like the standard `println` style macros. They can be used from any
//! context. The [`rtt_init_print`] convenience macro initializes printing on channel 0.
//!
//!
//! ```
//! use rtt_target::{rtt_init_print, rprintln};
//!
//! fn main() -> ! {
//! rtt_init_print!();
//! loop {
//! rprintln!("Hello, world!");
//! }
//! }
//! ```
//! # Debug
//!
//! To use rtt functionality only in debug builds use macros prefixed with `debug_*`. They have
//! exactly the same functionality as without debug - the only difference is that they are removed
//! when built with `--release`. It's save to use [`debug_rprintln`] and [`debug_rprint`] even if
//! rtt was initialized with [`rtt_init`] instead of [`debug_rtt_init`].
//!
//! Under the hood this uses the [`debug-assertions`] flag. Set this flag to true to include all debug
//! macros also in release mode.
//!
//! [debug-assertions]: https://doc.rust-lang.org/cargo/reference/profiles.html#debug-assertions
//!
//! ```
//! use rtt_target::{debug_rtt_init_print, debug_rprintln};
//!
//! fn main() -> ! {
//! debug_rtt_init_print!(); // nop in --release
//! loop {
//! debug_rprintln!("Hello, world!"); // not present in --release
//! }
//! }
//! ```
//!
//! The macros also support an extended syntax to print to different RTT virtual terminals.
//!
//! Please note that because a critical section is used, printing into a blocking channel will cause
//! the application to block and freeze when the buffer is full.
//!
//! # Reading
//!
//! The following example shows how to set up the RTT to read simple input sent from the host
//! to the target.
//!
//! ```
//! use rtt_target::{rtt_init_default, rprintln};
//!
//! fn main() -> ! {
//! let mode = loop {
//! read = channels.down.0.read(&mut read_buf);
//! for i in 0..read {
//! match read_buf[i] as char {
//! '0' => break 0,
//! '1' => break 1,
//! _ => {}
//! }
//! }
//! };
//! }
//! ```
#![no_std]
use core::convert::Infallible;
use core::fmt;
use core::mem::MaybeUninit;
use ufmt_write::uWrite;
#[macro_use]
mod init;
#[doc(hidden)]
/// Public due to access from macro
pub mod debug;
/// Public due to access from macro
#[doc(hidden)]
pub mod rtt;
#[macro_use]
mod print;
pub use print::*;
/// RTT up (target to host) channel
///
/// Supports writing binary data directly, or writing strings via [`core::fmt`] macros such as
/// [`write`] as well as the ufmt crate's `uwrite` macros (use the `u` method).
///
/// Note that the formatted writing implementations diverge slightly from the trait definitions in
/// that if the channel is in non-blocking mode, writing will *not* block.
pub struct UpChannel(*mut rtt::RttChannel);
unsafe impl Send for UpChannel {}
impl UpChannel {
/// Public due to access from macro.
#[doc(hidden)]
pub unsafe fn new(channel: *mut rtt::RttChannel) -> Self {
UpChannel(channel)
}
#[allow(clippy::mut_from_ref)]
fn channel(&self) -> &mut rtt::RttChannel {
unsafe { &mut *self.0 }
}
/// Writes `buf` to the channel and returns the number of bytes written. Behavior when the
/// buffer is full is subject to the channel blocking mode.
pub fn write(&mut self, buf: &[u8]) -> usize {
let mut writer = self.channel().writer();
writer.write(buf);
writer.commit()
}
/// Creates a writer for formatted writing with ufmt.
///
/// The correct way to use this method is to call it once for each write operation. This is so
/// that non blocking modes will work correctly.
///
/// ```
/// let mut output = channels.up.0;
/// uwriteln!(output.u(), "Hello, ufmt!");
/// ```
pub fn u(&mut self) -> uWriter {
uWriter(self.channel().writer())
}
/// Gets the current blocking mode of the channel. The default is `NoBlockSkip`.
pub fn mode(&self) -> ChannelMode {
self.channel().mode()
}
/// Sets the blocking mode of the channel
pub fn set_mode(&mut self, mode: ChannelMode) {
self.channel().set_mode(mode)
}
/// Converts the channel into a virtual terminal that can be used for writing into multiple
/// virtual terminals.
pub fn into_terminal(self) -> TerminalChannel {
TerminalChannel::new(self)
}
/// Magically creates a channel out of thin air. Return `None` if the channel number is too
/// high, or if the channel has not been initialized.
///
/// Calling this function will cause a linking error if `rtt_init` has not been called.
///
/// # Safety
///
/// It's undefined behavior for something else to access the channel through anything else
/// besides the returned object during or after calling this function. Essentially this function
/// is only safe to use in panic handlers and the like that permanently disable interrupts.
pub unsafe fn conjure(number: usize) -> Option<UpChannel> {
extern "C" {
#[link_name = "_SEGGER_RTT"]
static mut CONTROL_BLOCK: MaybeUninit<rtt::RttHeader>;
}
if number >= (*CONTROL_BLOCK.as_ptr()).max_up_channels() {
return None;
}
// First addition moves to the start of the up channel array, second addition moves to the
// correct channel.
let ptr = (CONTROL_BLOCK.as_ptr().add(1) as *mut rtt::RttChannel).add(number);
if !(*ptr).is_initialized() {
return None;
}
Some(UpChannel(ptr))
}
}
impl fmt::Write for UpChannel {
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
self.channel().writer().write_str(s)
}
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<(), fmt::Error> {
self.channel().writer().write_fmt(args)
}
}
/// Writer for ufmt. Don't store an instance of this, but rather create a new one for every write.
#[allow(non_camel_case_types)]
pub struct uWriter<'c>(rtt::RttWriter<'c>);
impl uWrite for uWriter<'_> {
type Error = Infallible;
fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
self.0.write(s.as_bytes());
Ok(())
}
}
/// RTT down (host to target) channel
pub struct DownChannel(*mut rtt::RttChannel);
unsafe impl Send for DownChannel {}
impl DownChannel {
/// Public due to access from macro.
#[doc(hidden)]
pub unsafe fn new(channel: *mut rtt::RttChannel) -> Self {
DownChannel(channel)
}
fn channel(&mut self) -> &mut rtt::RttChannel {
unsafe { &mut *self.0 }
}
/// Reads up to `buf.len()` bytes from the channel and return the number of bytes read. This
/// method never blocks.
pub fn read(&mut self, buf: &mut [u8]) -> usize {
self.channel().read(buf)
}
}
/// Specifies what to do when a channel doesn't have enough buffer space for a complete write.
#[derive(Eq, PartialEq)]
#[repr(usize)]
pub enum ChannelMode {
/// Skip writing the data completely if it doesn't fit in its entirety.
NoBlockSkip = 0,
/// Write as much as possible of the data and ignore the rest.
NoBlockTrim = 1,
/// Block (spin) if the buffer is full. If within a critical section such as inside
/// [`rprintln`], this will cause the application to freeze until the host reads from the
/// buffer.
BlockIfFull = 2,
}
/// An up channel that supports writing into multiple virtual terminals within the same buffer.
///
/// An [`UpChannel`] can be turned into a `TerminalChannel` by using the
/// [`into_terminal`](UpChannel::into_terminal()) method.
///
/// Virtual terminals allow you to share one buffer for writing multiple streams. The virtual
/// terminals number from 0 to 15 and are implemented with a simple "terminal switch" sequence on
/// the fly, so there is no need to declare them in advance. You could, for example, use different
/// terminal numbers for messages of different priorities to separate them in a viewer program.
/// Printing uses a `TerminalChannel` internally.
pub struct TerminalChannel {
channel: UpChannel,
current: u8,
}
impl TerminalChannel {
pub(crate) fn new(channel: UpChannel) -> Self {
Self {
channel,
current: 0,
}
}
/// Creates a writer to write a message to the virtual terminal specified by `number`.
///
/// The correct way to use this method is to call it once for each write operation. This is so
/// that non blocking modes will work correctly.
///
/// The writer supports formatted writing with the standard `write` and ufmt's `uwrite`.
pub fn write(&mut self, number: u8) -> TerminalWriter {
const TERMINAL_ID: [u8; 16] = *b"0123456789ABCDEF";
let mut writer = self.channel.channel().writer();
if number != self.current {
// The terminal switch command must be sent in full so the mode cannot be NoBlockTrim
let mode = self.channel.mode();
let mode = if mode == ChannelMode::NoBlockTrim {
ChannelMode::NoBlockSkip
} else {
mode
};
writer.write_with_mode(mode, &[0xff, TERMINAL_ID[(number & 0x0f) as usize]]);
self.current = number;
}
TerminalWriter {
writer,
number,
current: &mut self.current,
}
}
/// Gets the current blocking mode of the channel. The default is `NoBlockSkip`.
pub fn mode(&self) -> ChannelMode {
self.channel.mode()
}
/// Sets the blocking mode of the channel
pub fn set_mode(&mut self, mode: ChannelMode) {
self.channel.set_mode(mode)
}
}
/// Formatted writing operation. Don't store an instance of this, but rather create a new one for
/// every write.
pub struct TerminalWriter<'c> {
writer: rtt::RttWriter<'c>,
number: u8,
current: &'c mut u8,
}
impl fmt::Write for TerminalWriter<'_> {
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
self.writer.write(s.as_bytes());
Ok(())
}
}
impl uWrite for TerminalWriter<'_> {
type Error = Infallible;
fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
self.writer.write(s.as_bytes());
Ok(())
}
}
impl Drop for TerminalWriter<'_> {
fn drop(&mut self) {
if !self.writer.is_failed() {
*self.current = self.number;
}
}
}