#![deny(missing_docs)]
#![deny(warnings)]
#![no_std]
use core::{
cell::UnsafeCell,
cmp, ptr,
sync::atomic::{self, AtomicUsize, Ordering},
};
use ufmt::uWrite;
pub use cortex_m_funnel_macros::funnel;
#[doc(hidden)]
pub use ufmt::uwriteln;
#[doc(hidden)]
#[repr(C)]
pub struct Inner<B>
where
B: ?Sized,
{
write: UnsafeCell<usize>,
read: UnsafeCell<usize>,
buffer: UnsafeCell<B>,
}
unsafe impl<B> Sync for Inner<B> where B: ?Sized {}
impl<B> Inner<B> {
#[doc(hidden)]
pub const fn new(buffer: B) -> Self {
Self {
write: UnsafeCell::new(0),
read: UnsafeCell::new(0),
buffer: UnsafeCell::new(buffer),
}
}
}
#[repr(transparent)]
pub struct Logger {
inner: &'static Inner<[u8]>,
}
impl Logger {
pub fn get() -> Option<Self> {
if cfg!(not(cortex_m)) {
return None;
}
if (cfg!(debug_assertions) && cfg!(feature = "max_level_off"))
|| cfg!(feature = "release_max_level_off")
{
return None;
}
const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32;
const NVIC_IPR: *const u32 = 0xE000_E400 as *const u32;
extern "Rust" {
fn __funnel_logger(nvic_prio: u8) -> Option<Logger>;
}
unsafe {
let icsr = SCB_ICSR.read_volatile() as u8;
if icsr == 0 {
None
} else if icsr < 16 {
None
} else {
let nr = icsr - 16;
let ipr = NVIC_IPR.add((nr >> 2) as usize).read_volatile();
let nvic_prio = (ipr >> (8 * (nr % 4))) as u8;
__funnel_logger(nvic_prio)
}
}
}
fn log(&self, s: &str) -> Result<(), ()> {
unsafe {
let write = &mut *self.inner.write.get();
let buffer = &mut *self.inner.buffer.get();
let input = s.as_bytes();
let blen = buffer.len();
let ilen = input.len();
if ilen > blen {
return Err(());
}
let read = *self.inner.read.get();
if blen >= ilen + (*write).wrapping_sub(read) {
let w = *write % blen;
if w + ilen > blen {
let mid = blen - w;
ptr::copy_nonoverlapping(input.as_ptr(), buffer.as_mut_ptr().add(w), mid);
ptr::copy_nonoverlapping(
input.as_ptr().add(mid),
buffer.as_mut_ptr(),
ilen - mid,
);
} else {
ptr::copy_nonoverlapping(input.as_ptr(), buffer.as_mut_ptr().add(w), ilen);
}
*write = (*write).wrapping_add(ilen);
Ok(())
} else {
Err(())
}
}
}
}
impl uWrite for Logger {
type Error = ();
fn write_str(&mut self, s: &str) -> Result<(), ()> {
self.log(s)
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! _flog {
($($tt:tt)*) => {{
if let Some(mut logger) = $crate::Logger::get() {
$crate::uwriteln!(logger, $($tt)*)
} else {
Ok(())
}
}};
}
#[doc(hidden)]
#[derive(PartialEq, PartialOrd)]
pub enum Level {
Error,
Warn,
Info,
Debug,
Trace,
}
#[doc(hidden)]
pub fn is_enabled(lvl: Level) -> bool {
if let Some(threshold) = selected_log_level() {
lvl <= threshold
} else {
false
}
}
fn selected_log_level() -> Option<Level> {
if cfg!(debug_assertions) {
if cfg!(feature = "max_level_off") {
return None;
}
if cfg!(feature = "max_level_error") {
return Some(Level::Error);
}
if cfg!(feature = "max_level_warn") {
return Some(Level::Warn);
}
if cfg!(feature = "max_level_info") {
return Some(Level::Info);
}
if cfg!(feature = "max_level_debug") {
return Some(Level::Debug);
}
if cfg!(feature = "max_level_trace") {
return Some(Level::Trace);
}
} else {
if cfg!(feature = "release_max_level_off") {
return None;
}
if cfg!(feature = "release_max_level_error") {
return Some(Level::Error);
}
if cfg!(feature = "release_max_level_warn") {
return Some(Level::Warn);
}
if cfg!(feature = "release_max_level_info") {
return Some(Level::Info);
}
if cfg!(feature = "release_max_level_debug") {
return Some(Level::Debug);
}
if cfg!(feature = "release_max_level_trace") {
return Some(Level::Trace);
}
}
Some(Level::Trace)
}
#[macro_export]
macro_rules! log_enabled {
($e:expr) => {{
$crate::is_enabled($crate::Level::$e)
}}
}
#[macro_export]
macro_rules! error {
($($tt:tt)*) => {{
if $crate::is_enabled($crate::Level::Error) {
$crate::_flog!($($tt)*)
} else {
Ok(())
}
}}
}
#[macro_export]
macro_rules! warn {
($($tt:tt)*) => {{
if $crate::is_enabled($crate::Level::Warn) {
$crate::_flog!($($tt)*)
} else {
Ok(())
}
}}
}
#[macro_export]
macro_rules! info {
($($tt:tt)*) => {{
if $crate::is_enabled($crate::Level::Info) {
$crate::_flog!($($tt)*)
} else {
Ok(())
}
}}
}
#[macro_export]
macro_rules! debug {
($($tt:tt)*) => {{
if $crate::is_enabled($crate::Level::Debug) {
$crate::_flog!($($tt)*)
} else {
Ok(())
}
}}
}
#[macro_export]
macro_rules! trace {
($($tt:tt)*) => {{
if $crate::is_enabled($crate::Level::Trace) {
$crate::_flog!($($tt)*)
} else {
Ok(())
}
}}
}
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct Drain {
inner: &'static Inner<[u8]>,
}
impl Drain {
pub fn get_all() -> &'static [Self] {
if cfg!(not(cortex_m)) {
return &[];
}
if (cfg!(debug_assertions) && cfg!(feature = "max_level_off"))
|| cfg!(feature = "release_max_level_off")
{
return &[];
}
extern "Rust" {
fn __funnel_drains() -> &'static [Drain];
}
unsafe { __funnel_drains() }
}
pub fn read<'b>(&self, buf: &'b mut [u8]) -> &'b [u8] {
unsafe {
let readf = &mut *self.inner.read.get();
let writef: *const AtomicUsize = self.inner.write.get() as *const _;
let blen = (*self.inner.buffer.get()).len();
let p = (*self.inner.buffer.get()).as_ptr();
if blen == 0 {
return &[];
}
let read = *readf;
let write = (*writef).load(Ordering::Relaxed);
atomic::compiler_fence(Ordering::Acquire);
if write > read {
let c = cmp::min(buf.len(), write.wrapping_sub(read));
let r = read % blen;
if r + c > blen {
let mid = blen - r;
ptr::copy_nonoverlapping(p.add(r), buf.as_mut_ptr(), mid);
ptr::copy_nonoverlapping(p, buf.as_mut_ptr().add(mid), c - mid);
} else {
ptr::copy_nonoverlapping(p.add(r), buf.as_mut_ptr(), c);
}
atomic::compiler_fence(Ordering::Release); *readf = (*readf).wrapping_add(c);
buf.get_unchecked(..c)
} else {
&[]
}
}
}
}
impl Iterator for Drain {
type Item = u8;
fn next(&mut self) -> Option<u8> {
self.read(&mut [0]).first().cloned()
}
}
#[cfg(test)]
mod tests {
use super::{Drain, Inner, Logger};
#[test]
fn sanity() {
static INNER: Inner<[u8; 32]> = Inner::new([0; 32]);
let inner = &INNER;
let m = "Hello, world!";
let logger = Logger { inner };
logger.log(m).unwrap();
unsafe {
assert!((*logger.inner.buffer.get()).starts_with(m.as_bytes()));
}
}
#[test]
fn drain() {
static INNER: Inner<[u8; 32]> = Inner::new([0; 32]);
let inner = &INNER;
let logger = Logger { inner };
let mut drain = Drain { inner };
assert_eq!(drain.next(), None);
logger.log("A").unwrap();
assert_eq!(drain.next(), Some(b'A'));
assert_eq!(drain.next(), None);
logger.log("B").unwrap();
assert_eq!(drain.next(), Some(b'B'));
assert_eq!(drain.next(), None);
logger.log("CD").unwrap();
assert_eq!(drain.next(), Some(b'C'));
assert_eq!(drain.next(), Some(b'D'));
assert_eq!(drain.next(), None);
}
#[test]
fn read() {
static INNER: Inner<[u8; 16]> = Inner::new([0; 16]);
let inner = &INNER;
let logger = Logger { inner };
let drain = Drain { inner };
let mut buf = [0; 8];
logger.log("Hello, world!").unwrap();
assert_eq!(drain.read(&mut buf), b"Hello, w");
assert_eq!(drain.read(&mut buf), b"orld!");
assert_eq!(drain.read(&mut buf), b"");
logger.log("Hello, world!").unwrap();
assert_eq!(drain.read(&mut buf), b"Hello, w");
assert_eq!(drain.read(&mut buf), b"orld!");
assert_eq!(drain.read(&mut buf), b"");
}
#[test]
fn split_write() {
const N: usize = 32;
const M: usize = 24;
static INNER: Inner<[u8; N]> = Inner::new([0; N]);
let m = "Hello, world!";
let inner = &INNER;
unsafe {
*inner.read.get() = M;
*inner.write.get() = M;
let logger = Logger { inner };
logger.log(m).unwrap();
let m = m.as_bytes();
let buffer = &*logger.inner.buffer.get();
assert_eq!(buffer[M..], m[..(N - M)]);
assert_eq!(buffer[..(m.len() - (N - M))], m[(N - M)..]);
}
}
#[test]
fn wrap_around() {
static INNER: Inner<[u8; 32]> = Inner::new([0; 32]);
let m = "Hello, world!";
let inner = &INNER;
unsafe {
*inner.read.get() = usize::max_value();
*inner.write.get() = usize::max_value();
let logger = Logger { inner };
logger.log(m).unwrap();
let buffer = &*logger.inner.buffer.get();
assert_eq!(buffer.last(), Some(&b'H'));
assert_eq!(buffer[..m.len() - 1], m.as_bytes()[1..]);
}
}
}