use core::{
cell::UnsafeCell,
mem::MaybeUninit,
ptr,
sync::atomic::{AtomicU32, Ordering},
};
include!(concat!(env!("OUT_DIR"), "/consts.rs"));
pub(crate) unsafe fn write(bytes: &[u8]) {
unsafe { &*_SEGGER_RTT.up_channel.get() }.write_all(bytes)
}
pub(crate) unsafe fn flush() {
unsafe { &*_SEGGER_RTT.up_channel.get() }.flush()
}
const MODE_MASK: u32 = 0b11;
const MODE_BLOCK_IF_FULL: u32 = 2;
const MODE_NON_BLOCKING_TRIM: u32 = 1;
#[unsafe(no_mangle)]
static _SEGGER_RTT: RttHeader = RttHeader::new(NAME.as_ptr(), BUFFER.0.get().cast());
#[cfg_attr(target_os = "macos", unsafe(link_section = ".uninit,defmt-rtt.BUFFER"))]
#[cfg_attr(
not(target_os = "macos"),
unsafe(link_section = ".uninit.defmt-rtt.BUFFER")
)]
static BUFFER: UnsafeBuffer = UnsafeBuffer(UnsafeCell::new([MaybeUninit::uninit(); BUF_SIZE]));
#[unsafe(link_section = ".data")]
static NAME: [u8; 6] = *b"defmt\0";
#[repr(C)]
struct RttHeader {
id: [u8; 16],
max_up_channels: u32,
max_down_channels: u32,
up_channel: UnsafeCell<Channel>,
}
impl RttHeader {
const fn new(name: *const u8, buffer: *mut MaybeUninit<u8>) -> Self {
RttHeader {
id: *b"SEGGER RTT\0\0\0\0\0\0", max_up_channels: 1,
max_down_channels: 0,
up_channel: UnsafeCell::new(Channel {
name,
buffer,
size: BUF_SIZE as u32,
write: AtomicU32::new(0),
read: AtomicU32::new(0),
flags: AtomicU32::new(MODE_NON_BLOCKING_TRIM),
}),
}
}
}
unsafe impl Sync for RttHeader {}
#[repr(transparent)]
struct UnsafeBuffer(UnsafeCell<[MaybeUninit<u8>; BUF_SIZE]>);
unsafe impl Sync for UnsafeBuffer {}
#[repr(C)]
struct Channel {
name: *const u8,
buffer: *mut MaybeUninit<u8>,
size: u32,
write: AtomicU32,
read: AtomicU32,
flags: AtomicU32,
}
impl Channel {
fn write_all(&self, mut bytes: &[u8]) {
let host_connected = self.host_is_connected();
let write_fn = match host_connected {
true => Self::blocking_write,
false => Self::nonblocking_write,
};
while !bytes.is_empty() {
let consumed = write_fn(self, bytes);
if consumed != 0 {
bytes = &bytes[consumed..];
} else if !host_connected {
break;
}
}
}
fn blocking_write(&self, bytes: &[u8]) -> usize {
if bytes.is_empty() {
return 0;
}
let read = self.read.load(Ordering::Relaxed) as usize;
let write = self.write.load(Ordering::Acquire) as usize;
let available = available_buffer_size(read, write);
if available == 0 {
return 0;
}
self.write_impl(bytes, write, available)
}
fn nonblocking_write(&self, bytes: &[u8]) -> usize {
let read = self.read.load(Ordering::Relaxed) as usize;
let write = self.write.load(Ordering::Acquire) as usize;
let available = available_buffer_size(read, write);
self.write_impl(bytes, write, available)
}
fn write_impl(&self, bytes: &[u8], cursor: usize, available: usize) -> usize {
let len = bytes.len().min(available);
let buf: *mut u8 = self.buffer.cast();
if cursor + len > BUF_SIZE {
let pivot = BUF_SIZE - cursor;
unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), buf.add(cursor), pivot) };
unsafe { ptr::copy_nonoverlapping(bytes.as_ptr().add(pivot), buf, len - pivot) };
} else {
unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), buf.add(cursor), len) };
}
self.write.store(
(cursor.wrapping_add(len) % BUF_SIZE) as u32,
Ordering::Release,
);
len
}
fn flush(&self) {
if !self.host_is_connected() {
return;
}
let read = || self.read.load(Ordering::Relaxed);
let write = || self.write.load(Ordering::Relaxed);
while read() != write() {}
}
fn host_is_connected(&self) -> bool {
self.flags.load(Ordering::Relaxed) & MODE_MASK == MODE_BLOCK_IF_FULL
}
}
fn available_buffer_size(read_cursor: usize, write_cursor: usize) -> usize {
if read_cursor > write_cursor {
read_cursor - write_cursor - 1
} else {
BUF_SIZE - write_cursor - 1 + read_cursor
}
}