use core::convert::Infallible;
#[cfg(not(feature = "portable-atomic"))]
use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
#[cfg(feature = "portable-atomic")]
use portable_atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
use embassy_sync::waitqueue::AtomicWaker;
use embedded_hal_async::delay::DelayNs;
use crate::{
FIFO_DEPTH, Tx,
regs::{self, fields::InterruptEnable},
};
#[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 WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS];
static TX_CONTEXTS: [TxContext; NUM_WAKERS] = [const { TxContext::new() }; NUM_WAKERS];
static TX_DONE: [AtomicBool; NUM_WAKERS] = [const { AtomicBool::new(false) }; NUM_WAKERS];
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("invalid waker slot index: {0}")]
pub struct InvalidWakerIndex(pub usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TxToken {
base_addr: usize,
waker_idx: usize,
}
impl TxToken {
#[inline]
pub fn base_addr(&self) -> usize {
self.base_addr
}
#[inline]
pub fn waker_idx(&self) -> usize {
self.waker_idx
}
#[inline]
pub const unsafe fn steal(base_addr: usize, waker_idx: usize) -> Self {
Self {
base_addr,
waker_idx,
}
}
}
pub unsafe fn on_interrupt_tx(token: &TxToken) {
if token.waker_idx >= NUM_WAKERS {
return;
}
let waker_slot = token.waker_idx;
let mut tx = unsafe { Tx::steal(token.base_addr) };
let status = tx.regs.read_lsr();
let ier = InterruptEnable::new_with_raw_value(tx.regs.read_ier_or_dlm());
if !ier.thr_empty() {
return;
}
let context = &TX_CONTEXTS[waker_slot];
let raw_data_ptr = context.raw_data.load(Ordering::Acquire) as *const u8;
if raw_data_ptr.is_null() {
return;
}
let slice_len = context.transfer_len.load(Ordering::Relaxed);
let mut progress = context.progress.load(Ordering::Relaxed);
let slice = unsafe { core::slice::from_raw_parts(raw_data_ptr, slice_len) };
if (progress >= slice_len && status.thr_empty()) || slice_len == 0 {
TX_DONE[waker_slot].store(true, Ordering::Release);
tx.disable_interrupt();
WAKERS[waker_slot].wake();
return;
}
while progress < slice_len {
match tx.write_fifo(slice[progress]) {
Ok(_) => progress += 1,
Err(nb::Error::WouldBlock) => break,
}
}
context.progress.store(progress, Ordering::Relaxed);
}
struct TxContext {
progress: AtomicUsize,
raw_data: AtomicPtr<u8>,
transfer_len: AtomicUsize,
}
impl TxContext {
const fn new() -> Self {
Self {
progress: AtomicUsize::new(0),
raw_data: AtomicPtr::new(core::ptr::null_mut()),
transfer_len: AtomicUsize::new(0),
}
}
}
pub struct TxFuture<'tx, 'buf> {
waker_idx: usize,
reg_block: regs::MmioRegisters<'static>,
completed: bool,
phantom: core::marker::PhantomData<(&'tx (), &'buf ())>,
}
impl<'tx, 'buf> TxFuture<'tx, 'buf> {
pub fn new(tx: &mut Tx, waker_idx: usize, data: &'buf [u8]) -> Result<Self, InvalidWakerIndex> {
TX_DONE[waker_idx].store(false, Ordering::Relaxed);
tx.disable_interrupt();
tx.reset_fifo();
let init_fill_count = core::cmp::min(data.len(), FIFO_DEPTH);
let context_ref = &TX_CONTEXTS[waker_idx];
context_ref
.transfer_len
.store(data.len(), Ordering::Relaxed);
context_ref
.progress
.store(init_fill_count, Ordering::Relaxed);
context_ref
.raw_data
.store(data.as_ptr() as *mut u8, Ordering::Release);
for data in data.iter().take(init_fill_count) {
tx.write_fifo_unchecked(*data);
}
tx.enable_interrupt();
Ok(Self {
waker_idx,
reg_block: unsafe { tx.regs.clone() },
completed: false,
phantom: core::marker::PhantomData,
})
}
}
impl Future for TxFuture<'_, '_> {
type Output = usize;
fn poll(
mut self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
WAKERS[self.waker_idx].register(cx.waker());
if TX_DONE[self.waker_idx].swap(false, Ordering::Acquire) {
let context = &TX_CONTEXTS[self.waker_idx];
context
.raw_data
.store(core::ptr::null_mut(), Ordering::Release);
let progress = context.progress.load(Ordering::Relaxed);
self.completed = true;
return core::task::Poll::Ready(progress);
}
core::task::Poll::Pending
}
}
impl Drop for TxFuture<'_, '_> {
fn drop(&mut self) {
let mut tx = Tx::new(unsafe { self.reg_block.clone() });
tx.disable_interrupt();
if !self.completed {
let context_ref = &TX_CONTEXTS[self.waker_idx];
context_ref.progress.store(0, Ordering::Relaxed);
context_ref
.raw_data
.store(core::ptr::null_mut(), Ordering::Release);
}
}
}
pub struct TxAsync<D: DelayNs> {
tx: Tx,
token: TxToken,
delay: D,
}
impl<D: DelayNs> TxAsync<D> {
pub fn new(tx: Tx, waker_idx: usize, delay: D) -> Result<Self, InvalidWakerIndex> {
if waker_idx >= NUM_WAKERS {
return Err(InvalidWakerIndex(waker_idx));
}
let token = TxToken {
base_addr: unsafe { tx.regs.ptr() } as usize,
waker_idx,
};
Ok(Self { tx, token, delay })
}
#[inline]
pub fn token(&self) -> TxToken {
self.token
}
pub fn write<'buf>(&mut self, buf: &'buf [u8]) -> TxFuture<'_, 'buf> {
TxFuture::new(&mut self.tx, self.token.waker_idx, buf).unwrap()
}
pub async fn flush(&mut self) {
while !self.tx.tx_empty() {
self.delay.delay_us(10).await;
}
}
pub fn release(self) -> Tx {
self.tx
}
}
impl<D: DelayNs> embedded_io::ErrorType for TxAsync<D> {
type Error = Infallible;
}
impl<D: DelayNs> embedded_io_async::Write for TxAsync<D> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
Ok(self.write(buf).await)
}
async fn flush(&mut self) -> Result<(), Self::Error> {
self.flush().await;
Ok(())
}
}