#![no_std]
pub(crate) mod consts;
use bbqueue::{BBBuffer, GrantW, Producer};
use core::{
cell::UnsafeCell,
mem::MaybeUninit,
sync::atomic::{AtomicBool, AtomicUsize, AtomicU8, Ordering},
};
use cortex_m::{interrupt, register};
pub use crate::consts::BUF_SIZE;
#[derive(Debug, defmt::Format, PartialEq, Eq)]
pub enum Error {
InternalLatchingFault,
UseBeforeInitLatchingFault,
Bbq(BBQError),
}
impl From<BBQError> for Error {
fn from(other: BBQError) -> Self {
Error::Bbq(other)
}
}
pub use bbqueue::Consumer;
pub use bbqueue::Error as BBQError;
pub use bbqueue::GrantR;
pub use bbqueue::SplitGrantR;
pub fn init() -> Result<DefmtConsumer, Error> {
let (prod, cons) = BBQ.try_split()?;
unsafe {
BBQ_PRODUCER.uc_mu_fp.get().write(MaybeUninit::new(prod));
}
BBQ_STATE.store(logstate::INIT_NO_STORED_GRANT, Ordering::Release);
Ok(DefmtConsumer { cons })
}
pub struct DefmtConsumer {
cons: Consumer<'static, BUF_SIZE>,
}
impl DefmtConsumer {
pub fn read(&mut self) -> Result<GrantR<'static, BUF_SIZE>, Error> {
Ok(self.cons.read()?)
}
pub fn split_read(&mut self) -> Result<SplitGrantR<'static, BUF_SIZE>, Error> {
Ok(self.cons.split_read()?)
}
}
mod logstate {
pub const UNINIT: u8 = 0;
pub const INIT_NO_STORED_GRANT: u8 = 1;
pub const INIT_GRANT_IS_STORED: u8 = 2;
pub const LATCH_INTERNAL_ERROR: u8 = 100;
pub const LATCH_USE_BEFORE_INIT: u8 = 101;
}
#[inline]
fn check_latch(ordering: Ordering) -> Result<(), Error> {
match BBQ_STATE.load(ordering) {
i if i < logstate::LATCH_INTERNAL_ERROR => Ok(()),
logstate::LATCH_USE_BEFORE_INIT => Err(Error::UseBeforeInitLatchingFault),
_ => Err(Error::InternalLatchingFault),
}
}
#[inline]
fn latch_assert_eq<T: PartialEq>(left: T, right: T) -> Result<(), Error> {
if left == right {
Ok(())
} else {
BBQ_STATE.store(logstate::LATCH_INTERNAL_ERROR, Ordering::Release);
Err(Error::InternalLatchingFault)
}
}
struct UnsafeProducer {
uc_mu_fp: UnsafeCell<MaybeUninit<Producer<'static, BUF_SIZE>>>,
}
impl UnsafeProducer {
const fn new() -> Self {
Self {
uc_mu_fp: UnsafeCell::new(MaybeUninit::uninit()),
}
}
unsafe fn get_mut(&self) -> Result<&mut Producer<'static, BUF_SIZE>, Error> {
latch_assert_eq(logstate::INIT_NO_STORED_GRANT, BBQ_STATE.load(Ordering::Relaxed))?;
let const_ptr: *const Producer<'static, BUF_SIZE> = self.uc_mu_fp.get().cast();
let mut_ptr: *mut Producer<'static, BUF_SIZE> = const_ptr as *mut _;
let ref_mut: &mut Producer<'static, BUF_SIZE> = &mut *mut_ptr;
Ok(ref_mut)
}
}
unsafe impl Sync for UnsafeProducer {}
struct UnsafeGrantW {
uc_mu_fgw: UnsafeCell<MaybeUninit<GrantW<'static, BUF_SIZE>>>,
offset: AtomicUsize,
}
impl UnsafeGrantW {
const fn new() -> Self {
Self {
uc_mu_fgw: UnsafeCell::new(MaybeUninit::uninit()),
offset: AtomicUsize::new(0),
}
}
unsafe fn put(&self, grant: GrantW<'static, BUF_SIZE>, offset: usize) -> Result<(), Error> {
latch_assert_eq(logstate::INIT_NO_STORED_GRANT, BBQ_STATE.load(Ordering::Relaxed))?;
self.uc_mu_fgw.get().write(MaybeUninit::new(grant));
self.offset.store(offset, Ordering::Relaxed);
BBQ_STATE.store(logstate::INIT_GRANT_IS_STORED, Ordering::Relaxed);
Ok(())
}
unsafe fn take(&self) -> Result<Option<(GrantW<'static, BUF_SIZE>, usize)>, Error> {
check_latch(Ordering::Relaxed)?;
Ok(match BBQ_STATE.load(Ordering::Relaxed) {
logstate::INIT_GRANT_IS_STORED => {
let grant = self
.uc_mu_fgw
.get()
.cast::<GrantW<'static, BUF_SIZE>>()
.read();
BBQ_STATE.store(logstate::INIT_NO_STORED_GRANT, Ordering::Relaxed);
Some((grant, self.offset.load(Ordering::Relaxed)))
}
logstate::INIT_NO_STORED_GRANT => {
let producer = BBQ_PRODUCER.get_mut()?;
self.offset.store(0, Ordering::Relaxed);
producer.grant_max_remaining(BUF_SIZE).ok().map(|g| (g, 0))
}
n => {
if n < logstate::LATCH_INTERNAL_ERROR {
BBQ_STATE.store(logstate::LATCH_INTERNAL_ERROR, Ordering::Relaxed);
}
return Err(Error::InternalLatchingFault);
},
})
}
}
unsafe impl Sync for UnsafeGrantW {}
static BBQ: BBBuffer<BUF_SIZE> = BBBuffer::new();
static BBQ_STATE: AtomicU8 = AtomicU8::new(logstate::UNINIT);
static BBQ_PRODUCER: UnsafeProducer = UnsafeProducer::new();
static BBQ_GRANT_W: UnsafeGrantW = UnsafeGrantW::new();
static TAKEN: AtomicBool = AtomicBool::new(false);
static INTERRUPTS_ACTIVE: AtomicBool = AtomicBool::new(false);
static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
#[defmt::global_logger]
struct Logger;
unsafe impl defmt::Logger for Logger {
fn acquire() {
let primask = register::primask::read();
interrupt::disable();
let state = BBQ_STATE.load(Ordering::Relaxed);
let taken = TAKEN.load(Ordering::Relaxed);
let bail = match (taken, state) {
(false, logstate::INIT_NO_STORED_GRANT) => false,
(_, logstate::UNINIT) => {
BBQ_STATE.store(logstate::LATCH_USE_BEFORE_INIT, Ordering::Relaxed);
true
}
_ => {
BBQ_STATE.store(logstate::LATCH_INTERNAL_ERROR, Ordering::Relaxed);
true
}
};
if bail {
if primask.is_active() {
unsafe { interrupt::enable(); }
}
return;
}
TAKEN.store(true, Ordering::Relaxed);
INTERRUPTS_ACTIVE.store(primask.is_active(), Ordering::Relaxed);
unsafe { ENCODER.start_frame(do_write) }
}
unsafe fn flush() {
}
unsafe fn release() {
ENCODER.end_frame(do_write);
match BBQ_GRANT_W.take() {
Ok(Some((grant, offset))) => grant.commit(offset),
_ => {}
}
TAKEN.store(false, Ordering::Relaxed);
if INTERRUPTS_ACTIVE.load(Ordering::Relaxed) {
interrupt::enable()
}
}
unsafe fn write(bytes: &[u8]) {
if check_latch(Ordering::Relaxed).is_err() {
return;
}
ENCODER.write(bytes, do_write);
}
}
fn do_write(mut remaining: &[u8]) {
while !remaining.is_empty() {
match unsafe { BBQ_GRANT_W.take() } {
Ok(Some((mut grant, mut offset))) => {
let glen = grant.len();
let min = remaining.len().min(grant.len() - offset);
grant[offset..][..min].copy_from_slice(&remaining[..min]);
offset += min;
remaining = &remaining[min..];
if offset >= glen {
grant.commit(offset);
} else {
unsafe {
if BBQ_GRANT_W.put(grant, offset).is_err() {
return;
}
}
}
},
Ok(None) => return,
Err(_) => return,
}
}
}