From 41b7e4a434c0d1fe1fc5d93afe76f8058aa411db Mon Sep 17 00:00:00 2001
From: Corey Schuhen <cschuhen@gmail.com>
Date: Sun, 24 Mar 2024 08:08:12 +1000
Subject: [PATCH] BXCAN: Create TxMode in order to support buffered TX.

---
 embassy-stm32/src/can/bxcan.rs | 130 ++++++++++++++++++++++++++++++---
 embassy-stm32/src/can/fdcan.rs |   5 +-
 2 files changed, 120 insertions(+), 15 deletions(-)

diff --git a/embassy-stm32/src/can/bxcan.rs b/embassy-stm32/src/can/bxcan.rs
index 66f6e7067..45a3836c0 100644
--- a/embassy-stm32/src/can/bxcan.rs
+++ b/embassy-stm32/src/can/bxcan.rs
@@ -17,7 +17,6 @@ use crate::rcc::RccPeripheral;
 use crate::{interrupt, peripherals, Peripheral};
 
 pub mod enums;
-use enums::*;
 pub mod frame;
 pub mod util;
 
@@ -49,8 +48,7 @@ impl<T: Instance> interrupt::typelevel::Handler<T::TXInterrupt> for TxInterruptH
             v.set_rqcp(1, true);
             v.set_rqcp(2, true);
         });
-
-        T::state().tx_waker.wake();
+        T::state().tx_mode.on_interrupt::<T>();
     }
 }
 
@@ -258,7 +256,7 @@ impl<'d, T: Instance> CanTx<'d, T> {
     /// If the TX queue is full, this will wait until there is space, therefore exerting backpressure.
     pub async fn write(&mut self, frame: &Frame) -> crate::can::bx::TransmitStatus {
         poll_fn(|cx| {
-            T::state().tx_waker.register(cx.waker());
+            T::state().tx_mode.register(cx.waker());
             if let Ok(status) = self.tx.transmit(frame) {
                 return Poll::Ready(status);
             }
@@ -277,7 +275,7 @@ impl<'d, T: Instance> CanTx<'d, T> {
 
     async fn flush_inner(mb: crate::can::bx::Mailbox) {
         poll_fn(|cx| {
-            T::state().tx_waker.register(cx.waker());
+            T::state().tx_mode.register(cx.waker());
             if T::regs().tsr().read().tme(mb.index()) {
                 return Poll::Ready(());
             }
@@ -294,7 +292,7 @@ impl<'d, T: Instance> CanTx<'d, T> {
 
     async fn flush_any_inner() {
         poll_fn(|cx| {
-            T::state().tx_waker.register(cx.waker());
+            T::state().tx_mode.register(cx.waker());
 
             let tsr = T::regs().tsr().read();
             if tsr.tme(crate::can::bx::Mailbox::Mailbox0.index())
@@ -316,7 +314,7 @@ impl<'d, T: Instance> CanTx<'d, T> {
 
     async fn flush_all_inner() {
         poll_fn(|cx| {
-            T::state().tx_waker.register(cx.waker());
+            T::state().tx_mode.register(cx.waker());
 
             let tsr = T::regs().tsr().read();
             if tsr.tme(crate::can::bx::Mailbox::Mailbox0.index())
@@ -335,6 +333,62 @@ impl<'d, T: Instance> CanTx<'d, T> {
     pub async fn flush_all(&self) {
         Self::flush_all_inner().await
     }
+
+    /// Return a buffered instance of driver. User must supply Buffers
+    pub fn buffered<const TX_BUF_SIZE: usize>(
+        self,
+        txb: &'static mut TxBuf<TX_BUF_SIZE>,
+    ) -> BufferedCanTx<'d, T, TX_BUF_SIZE> {
+        BufferedCanTx::new(self.tx, txb)
+    }
+}
+
+/// User supplied buffer for TX buffering
+pub type TxBuf<const BUF_SIZE: usize> = Channel<CriticalSectionRawMutex, Frame, BUF_SIZE>;
+
+/// CAN driver, transmit half.
+pub struct BufferedCanTx<'d, T: Instance, const TX_BUF_SIZE: usize> {
+    _tx: crate::can::bx::Tx<BxcanInstance<'d, T>>,
+    tx_buf: &'static TxBuf<TX_BUF_SIZE>,
+}
+
+impl<'d, T: Instance, const TX_BUF_SIZE: usize> BufferedCanTx<'d, T, TX_BUF_SIZE> {
+    fn new(_tx: crate::can::bx::Tx<BxcanInstance<'d, T>>, tx_buf: &'static TxBuf<TX_BUF_SIZE>) -> Self {
+        Self { _tx, tx_buf }.setup()
+    }
+
+    fn setup(self) -> Self {
+        // We don't want interrupts being processed while we change modes.
+        critical_section::with(|_| unsafe {
+            let tx_inner = self::common::ClassicBufferedTxInner {
+                tx_receiver: self.tx_buf.receiver().into(),
+            };
+            T::mut_state().tx_mode = TxMode::Buffered(tx_inner);
+        });
+        self
+    }
+
+    /// Async write frame to TX buffer.
+    pub async fn write(&mut self, frame: &Frame) {
+        self.tx_buf.send(*frame).await;
+        T::TXInterrupt::pend(); // Wake for Tx
+    }
+
+    /// Returns a sender that can be used for sending CAN frames.
+    pub fn writer(&self) -> BufferedCanSender {
+        BufferedCanSender {
+            tx_buf: self.tx_buf.sender().into(),
+            waker: T::TXInterrupt::pend,
+        }
+    }
+}
+
+impl<'d, T: Instance, const TX_BUF_SIZE: usize> Drop for BufferedCanTx<'d, T, TX_BUF_SIZE> {
+    fn drop(&mut self) {
+        critical_section::with(|_| unsafe {
+            T::mut_state().tx_mode = TxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new());
+        });
+    }
 }
 
 /// CAN driver, receive half.
@@ -365,7 +419,7 @@ impl<'d, T: Instance> CanRx<'d, T> {
         T::state().rx_mode.wait_not_empty::<T>().await
     }
 
-    /// Return a buffered instance of driver without CAN FD support. User must supply Buffers
+    /// Return a buffered instance of driver. User must supply Buffers
     pub fn buffered<const RX_BUF_SIZE: usize>(
         self,
         rxb: &'static mut RxBuf<RX_BUF_SIZE>,
@@ -442,6 +496,14 @@ impl<'d, T: Instance, const RX_BUF_SIZE: usize> BufferedCanRx<'d, T, RX_BUF_SIZE
     }
 }
 
+impl<'d, T: Instance, const RX_BUF_SIZE: usize> Drop for BufferedCanRx<'d, T, RX_BUF_SIZE> {
+    fn drop(&mut self) {
+        critical_section::with(|_| unsafe {
+            T::mut_state().rx_mode = RxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new());
+        });
+    }
+}
+
 use crate::can::bx::RxFifo;
 
 impl<'d, T: Instance> Drop for Can<'d, T> {
@@ -568,18 +630,62 @@ impl RxMode {
         }
     }
 }
+
+enum TxMode {
+    NonBuffered(AtomicWaker),
+    Buffered(self::common::ClassicBufferedTxInner),
+}
+
+impl TxMode {
+    pub fn buffer_free<T: Instance>(&self) -> bool {
+        let tsr = T::regs().tsr().read();
+        tsr.tme(crate::can::bx::Mailbox::Mailbox0.index())
+            || tsr.tme(crate::can::bx::Mailbox::Mailbox1.index())
+            || tsr.tme(crate::can::bx::Mailbox::Mailbox2.index())
+    }
+    pub fn on_interrupt<T: Instance>(&self) {
+        match &T::state().tx_mode {
+            TxMode::NonBuffered(waker) => waker.wake(),
+            TxMode::Buffered(buf) => {
+                while self.buffer_free::<T>() {
+                    match buf.tx_receiver.try_receive() {
+                        Ok(frame) => {
+                            let mut registers = crate::can::bx::Registers { canregs: T::regs() };
+                            _ = registers.transmit(&frame);
+                        }
+                        Err(_) => {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fn register(&self, arg: &core::task::Waker) {
+        match self {
+            TxMode::NonBuffered(waker) => {
+                waker.register(arg);
+            }
+            _ => {
+                panic!("Bad mode");
+            }
+        }
+    }
+}
+
 struct State {
-    pub tx_waker: AtomicWaker,
-    pub err_waker: AtomicWaker,
     pub(crate) rx_mode: RxMode,
+    pub(crate) tx_mode: TxMode,
+    pub err_waker: AtomicWaker,
 }
 
 impl State {
     pub const fn new() -> Self {
         Self {
-            tx_waker: AtomicWaker::new(),
-            err_waker: AtomicWaker::new(),
             rx_mode: RxMode::NonBuffered(AtomicWaker::new()),
+            tx_mode: TxMode::NonBuffered(AtomicWaker::new()),
+            err_waker: AtomicWaker::new(),
         }
     }
 }
diff --git a/embassy-stm32/src/can/fdcan.rs b/embassy-stm32/src/can/fdcan.rs
index 42c9bd9f6..e58d8c0ec 100644
--- a/embassy-stm32/src/can/fdcan.rs
+++ b/embassy-stm32/src/can/fdcan.rs
@@ -507,7 +507,7 @@ pub struct BufferedCanFd<'d, T: Instance, const TX_BUF_SIZE: usize, const RX_BUF
 /// Sender that can be used for sending CAN frames.
 #[derive(Copy, Clone)]
 pub struct BufferedFdCanSender {
-    tx_buf: embassy_sync::channel::DynamicSender<'static, FdFrame>,
+    tx_buf: DynamicSender<'static, FdFrame>,
     waker: fn(),
 }
 
@@ -532,8 +532,7 @@ impl BufferedFdCanSender {
 }
 
 /// Receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver.
-pub type BufferedFdCanReceiver =
-    embassy_sync::channel::DynamicReceiver<'static, Result<(FdFrame, Timestamp), BusError>>;
+pub type BufferedFdCanReceiver = DynamicReceiver<'static, Result<(FdFrame, Timestamp), BusError>>;
 
 impl<'c, 'd, T: Instance, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize>
     BufferedCanFd<'d, T, TX_BUF_SIZE, RX_BUF_SIZE>