diff options
Diffstat (limited to 'nrf-softdevice-defmt-rtt/src')
-rw-r--r-- | nrf-softdevice-defmt-rtt/src/channel.rs | 112 | ||||
-rw-r--r-- | nrf-softdevice-defmt-rtt/src/lib.rs | 125 |
2 files changed, 0 insertions, 237 deletions
diff --git a/nrf-softdevice-defmt-rtt/src/channel.rs b/nrf-softdevice-defmt-rtt/src/channel.rs deleted file mode 100644 index d0d899f..0000000 --- a/nrf-softdevice-defmt-rtt/src/channel.rs +++ /dev/null @@ -1,112 +0,0 @@ -use core::ptr; -use core::sync::atomic::{AtomicUsize, Ordering}; - -use crate::{MODE_BLOCK_IF_FULL, MODE_MASK, SIZE}; - -#[repr(C)] -pub(crate) struct Channel { - pub name: *const u8, - pub buffer: *mut u8, - pub size: usize, - pub write: AtomicUsize, - pub read: AtomicUsize, - /// Channel properties. - /// - /// Currently, only the lowest 2 bits are used to set the channel mode (see constants below). - pub flags: AtomicUsize, -} - -impl Channel { - pub fn write_all(&self, mut bytes: &[u8]) { - // the host-connection-status is only modified after RAM initialization while the device is - // halted, so we only need to check it once before the write-loop - let write = match self.host_is_connected() { - true => Channel::blocking_write, - false => Channel::nonblocking_write, - }; - - while !bytes.is_empty() { - let consumed = write(self, bytes); - if consumed != 0 { - bytes = &bytes[consumed..]; - } - } - } - - fn blocking_write(&self, bytes: &[u8]) -> usize { - if bytes.is_empty() { - return 0; - } - - let read = self.read.load(Ordering::Relaxed); - let write = self.write.load(Ordering::Acquire); - let available = if read > write { - read - write - 1 - } else if read == 0 { - SIZE - write - 1 - } else { - SIZE - write - }; - - if available == 0 { - return 0; - } - - let cursor = write; - let len = bytes.len().min(available); - - unsafe { - if cursor + len > SIZE { - // split memcpy - let pivot = SIZE - cursor; - ptr::copy_nonoverlapping(bytes.as_ptr(), self.buffer.add(cursor), pivot); - ptr::copy_nonoverlapping(bytes.as_ptr().add(pivot), self.buffer, len - pivot); - } else { - // single memcpy - ptr::copy_nonoverlapping(bytes.as_ptr(), self.buffer.add(cursor), len); - } - } - self.write.store(write.wrapping_add(len) % SIZE, Ordering::Release); - - len - } - - fn nonblocking_write(&self, bytes: &[u8]) -> usize { - let write = self.write.load(Ordering::Acquire); - let cursor = write; - // NOTE truncate at SIZE to avoid more than one "wrap-around" in a single `write` call - let len = bytes.len().min(SIZE); - - unsafe { - if cursor + len > SIZE { - // split memcpy - let pivot = SIZE - cursor; - ptr::copy_nonoverlapping(bytes.as_ptr(), self.buffer.add(cursor), pivot); - ptr::copy_nonoverlapping(bytes.as_ptr().add(pivot), self.buffer, len - pivot); - } else { - // single memcpy - ptr::copy_nonoverlapping(bytes.as_ptr(), self.buffer.add(cursor), len); - } - } - self.write.store(write.wrapping_add(len) % SIZE, Ordering::Release); - - len - } - - pub fn flush(&self) { - // return early, if host is disconnected - if !self.host_is_connected() { - return; - } - - // busy wait, until the read- catches up with the write-pointer - let read = || self.read.load(Ordering::Relaxed); - let write = || self.write.load(Ordering::Relaxed); - while read() != write() {} - } - - fn host_is_connected(&self) -> bool { - // we assume that a host is connected if we are in blocking-mode. this is what probe-run does. - self.flags.load(Ordering::Relaxed) & MODE_MASK == MODE_BLOCK_IF_FULL - } -} diff --git a/nrf-softdevice-defmt-rtt/src/lib.rs b/nrf-softdevice-defmt-rtt/src/lib.rs deleted file mode 100644 index 90d4d6f..0000000 --- a/nrf-softdevice-defmt-rtt/src/lib.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! [`defmt`](https://github.com/knurling-rs/defmt) global logger over RTT. -//! -//! NOTE when using this crate it's not possible to use (link to) the `rtt-target` crate -//! -//! To use this crate, link to it by importing it somewhere in your project. -//! -//! ``` -//! // src/main.rs or src/bin/my-app.rs -//! use defmt_rtt as _; -//! ``` -//! -//! # Blocking/Non-blocking -//! -//! `probe-run` puts RTT into blocking-mode, to avoid losing data. -//! -//! As an effect this implementation may block forever if `probe-run` disconnects on runtime. This -//! is because the RTT buffer will fill up and writing will eventually halt the program execution. -//! -//! `defmt::flush` would also block forever in that case. - -#![no_std] - -mod channel; - -use core::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}; - -use crate::channel::Channel; - -#[defmt::global_logger] -struct Logger; - -/// Global logger lock. -static TAKEN: AtomicBool = AtomicBool::new(false); -static INTERRUPTS_TOKEN: AtomicU8 = AtomicU8::new(0); -static mut ENCODER: defmt::Encoder = defmt::Encoder::new(); - -unsafe impl defmt::Logger for Logger { - fn acquire() { - let token = unsafe { critical_section::acquire() }; - - if !TAKEN.load(Ordering::Relaxed) { - // no need for CAS because interrupts are disabled - TAKEN.store(true, Ordering::Relaxed); - - INTERRUPTS_TOKEN.store(token, Ordering::Relaxed); - - // safety: accessing the `static mut` is OK because we have disabled interrupts. - unsafe { ENCODER.start_frame(do_write) } - } else { - unsafe { critical_section::release(token) }; - } - } - - unsafe fn flush() { - // SAFETY: if we get here, the global logger mutex is currently acquired - handle().flush(); - } - - unsafe fn release() { - // safety: accessing the `static mut` is OK because we have disabled interrupts. - ENCODER.end_frame(do_write); - TAKEN.store(false, Ordering::Relaxed); - critical_section::release(INTERRUPTS_TOKEN.load(Ordering::Relaxed)); - } - - unsafe fn write(bytes: &[u8]) { - // safety: accessing the `static mut` is OK because we have disabled interrupts. - ENCODER.write(bytes, do_write); - } -} - -fn do_write(bytes: &[u8]) { - unsafe { handle().write_all(bytes) } -} - -#[repr(C)] -struct Header { - id: [u8; 16], - max_up_channels: usize, - max_down_channels: usize, - up_channel: Channel, -} - -const MODE_MASK: usize = 0b11; -/// Block the application if the RTT buffer is full, wait for the host to read data. -const MODE_BLOCK_IF_FULL: usize = 2; -/// Don't block if the RTT buffer is full. Truncate data to output as much as fits. -const MODE_NON_BLOCKING_TRIM: usize = 1; - -// TODO make configurable -// NOTE use a power of 2 for best performance -const SIZE: usize = 1024; - -// make sure we only get shared references to the header/channel (avoid UB) -/// # Safety -/// `Channel` API is not re-entrant; this handle should not be held from different execution -/// contexts (e.g. thread-mode, interrupt context) -unsafe fn handle() -> &'static Channel { - // NOTE the `rtt-target` API is too permissive. It allows writing arbitrary data to any - // channel (`set_print_channel` + `rprint*`) and that can corrupt defmt log frames. - // So we declare the RTT control block here and make it impossible to use `rtt-target` together - // with this crate. - #[no_mangle] - static mut _SEGGER_RTT: Header = Header { - id: *b"SEGGER RTT\0\0\0\0\0\0", - max_up_channels: 1, - max_down_channels: 0, - up_channel: Channel { - name: NAME as *const _ as *const u8, - buffer: unsafe { &mut BUFFER as *mut _ as *mut u8 }, - size: SIZE, - write: AtomicUsize::new(0), - read: AtomicUsize::new(0), - flags: AtomicUsize::new(MODE_NON_BLOCKING_TRIM), - }, - }; - - #[cfg_attr(target_os = "macos", link_section = ".uninit,defmt-rtt.BUFFER")] - #[cfg_attr(not(target_os = "macos"), link_section = ".uninit.defmt-rtt.BUFFER")] - static mut BUFFER: [u8; SIZE] = [0; SIZE]; - - static NAME: &[u8] = b"defmt\0"; - - &_SEGGER_RTT.up_channel -} |