use core::{cell::RefCell, convert::Infallible, sync::atomic::AtomicBool};
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use raw_slice::RawBufSlice;
use crate::{FIFO_DEPTH, Tx};
#[cfg(feature = "1-waker")]
pub const NUM_WAKERS: usize = 1;
#[cfg(feature = "2-wakers")]
pub const NUM_WAKERS: usize = 2;
#[cfg(feature = "4-wakers")]
pub const NUM_WAKERS: usize = 4;
#[cfg(feature = "8-wakers")]
pub const NUM_WAKERS: usize = 8;
#[cfg(feature = "16-wakers")]
pub const NUM_WAKERS: usize = 16;
#[cfg(feature = "32-wakers")]
pub const NUM_WAKERS: usize = 32;
static UART_TX_WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS];
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; NUM_WAKERS] =
[const { Mutex::new(RefCell::new(TxContext::new())) }; NUM_WAKERS];
static TX_DONE: [AtomicBool; NUM_WAKERS] = [const { AtomicBool::new(false) }; NUM_WAKERS];
#[derive(Debug, thiserror::Error)]
#[error("invalid waker slot index: {0}")]
pub struct InvalidWakerIndex(pub usize);
pub fn on_interrupt_tx(uartlite_tx: &mut Tx, waker_slot: usize) {
if waker_slot >= NUM_WAKERS {
return;
}
let status = uartlite_tx.regs.read_stat_reg();
if !status.intr_enabled() {
return;
}
let mut context = critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[waker_slot].borrow(cs);
*context_ref.borrow()
});
if context.slice.is_null() {
return;
}
let slice_len = context.slice.len().unwrap();
if (context.progress >= slice_len && status.tx_fifo_empty()) || slice_len == 0 {
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[waker_slot].borrow(cs);
*context_ref.borrow_mut() = context;
});
TX_DONE[waker_slot].store(true, core::sync::atomic::Ordering::Relaxed);
UART_TX_WAKERS[waker_slot].wake();
return;
}
let slice = unsafe { context.slice.get() }.expect("slice is invalid");
while context.progress < slice_len {
if uartlite_tx.regs.read_stat_reg().tx_fifo_full() {
break;
}
uartlite_tx.write_fifo_unchecked(slice[context.progress]);
context.progress += 1;
}
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[waker_slot].borrow(cs);
*context_ref.borrow_mut() = context;
});
}
#[derive(Debug, Copy, Clone)]
pub struct TxContext {
progress: usize,
slice: RawBufSlice,
}
#[allow(clippy::new_without_default)]
impl TxContext {
pub const fn new() -> Self {
Self {
progress: 0,
slice: RawBufSlice::new_nulled(),
}
}
}
pub struct TxFuture<'tx> {
waker_idx: usize,
tx: &'tx mut TxAsync,
}
impl<'tx> TxFuture<'tx> {
pub unsafe fn new(
tx: &'tx mut TxAsync,
waker_idx: usize,
data: &[u8],
) -> Result<TxFuture<'tx>, InvalidWakerIndex> {
TX_DONE[waker_idx].store(false, core::sync::atomic::Ordering::Relaxed);
tx.tx.reset_fifo();
let init_fill_count = core::cmp::min(data.len(), FIFO_DEPTH);
for data in data.iter().take(init_fill_count) {
tx.tx.write_fifo_unchecked(*data);
}
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[waker_idx].borrow(cs);
let mut context = context_ref.borrow_mut();
unsafe {
context.slice.set(data);
}
context.progress = init_fill_count;
});
Ok(Self { waker_idx, tx })
}
}
impl Future for TxFuture<'_> {
type Output = usize;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
UART_TX_WAKERS[self.waker_idx].register(cx.waker());
if TX_DONE[self.waker_idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
let progress = critical_section::with(|cs| {
let mut ctx = TX_CONTEXTS[self.waker_idx].borrow(cs).borrow_mut();
ctx.slice.set_null();
ctx.progress
});
return core::task::Poll::Ready(progress);
}
core::task::Poll::Pending
}
}
impl Drop for TxFuture<'_> {
fn drop(&mut self) {
if !TX_DONE[self.waker_idx].load(core::sync::atomic::Ordering::Relaxed) {
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[self.waker_idx].borrow(cs);
let mut context_mut = context_ref.borrow_mut();
context_mut.slice.set_null();
context_mut.progress = 0;
self.tx.tx.reset_fifo();
});
}
}
}
pub struct TxAsync {
pub(crate) tx: Tx,
waker_idx: usize,
}
impl TxAsync {
pub fn new(tx: Tx, waker_idx: usize) -> Result<Self, InvalidWakerIndex> {
if waker_idx >= NUM_WAKERS {
return Err(InvalidWakerIndex(waker_idx));
}
Ok(Self { tx, waker_idx })
}
pub async fn write(&mut self, buf: &[u8]) -> usize {
if buf.is_empty() {
return 0;
}
let fut = unsafe { TxFuture::new(self, self.waker_idx, buf).unwrap() };
fut.await
}
pub fn release(self) -> Tx {
self.tx
}
}
impl embedded_io::ErrorType for TxAsync {
type Error = Infallible;
}
impl embedded_io_async::Write for TxAsync {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
Ok(self.write(buf).await)
}
async fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}