From 854ae5da8fde2b2b33b6ad3c5de1a1d441b4d6b3 Mon Sep 17 00:00:00 2001
From: Maarten de Vries <maarten@de-vri.es>
Date: Wed, 22 May 2024 16:26:14 +0200
Subject: [PATCH] embassy_stm32: implement optional FIFO scheduling for
 outgoing frames

---
 embassy-stm32/src/can/bxcan/mod.rs       | 29 +++++++++-
 embassy-stm32/src/can/bxcan/registers.rs | 67 +++++++++++++++++++-----
 2 files changed, 81 insertions(+), 15 deletions(-)

diff --git a/embassy-stm32/src/can/bxcan/mod.rs b/embassy-stm32/src/can/bxcan/mod.rs
index 912634b84..05b1b83d8 100644
--- a/embassy-stm32/src/can/bxcan/mod.rs
+++ b/embassy-stm32/src/can/bxcan/mod.rs
@@ -133,7 +133,7 @@ impl<T: Instance> CanConfig<'_, T> {
         self
     }
 
-    /// Enables or disables automatic retransmission of messages.
+    /// Enables or disables automatic retransmission of frames.
     ///
     /// If this is enabled, the CAN peripheral will automatically try to retransmit each frame
     /// until it can be sent. Otherwise, it will try only once to send each frame.
@@ -298,6 +298,23 @@ impl<'d, T: Instance> Can<'d, T> {
         T::regs().ier().modify(|i| i.set_slkie(false));
     }
 
+    /// Enable FIFO scheduling of outgoing frames.
+    ///
+    /// If this is enabled, frames will be transmitted in the order that they are passed to
+    /// [`write()`][Self::write] or [`try_write()`][Self::try_write()].
+    ///
+    /// If this is disabled, frames are transmitted in order of priority.
+    ///
+    /// FIFO scheduling is disabled by default.
+    pub fn set_tx_fifo_scheduling(&mut self, enabled: bool) {
+        Registers(T::regs()).set_tx_fifo_scheduling(enabled)
+    }
+
+    /// Checks if FIFO scheduling of outgoing frames is enabled.
+    pub fn tx_fifo_scheduling_enabled(&self) -> bool {
+        Registers(T::regs()).tx_fifo_scheduling_enabled()
+    }
+
     /// Queues the message to be sent.
     ///
     /// If the TX queue is full, this will wait until there is space, therefore exerting backpressure.
@@ -318,6 +335,11 @@ impl<'d, T: Instance> Can<'d, T> {
     }
 
     /// Waits until any of the transmit mailboxes become empty
+    ///
+    /// Note that [`Self::try_write()`] may fail with [`TryWriteError::Full`],
+    /// even after the future returned by this function completes.
+    /// This will happen if FIFO scheduling of outgoing frames is not enabled,
+    /// and a frame with equal priority is already queued for transmission.
     pub async fn flush_any(&self) {
         CanTx::<T>::flush_any_inner().await
     }
@@ -505,6 +527,11 @@ impl<'d, T: Instance> CanTx<'d, T> {
     }
 
     /// Waits until any of the transmit mailboxes become empty
+    ///
+    /// Note that [`Self::try_write()`] may fail with [`TryWriteError::Full`],
+    /// even after the future returned by this function completes.
+    /// This will happen if FIFO scheduling of outgoing frames is not enabled,
+    /// and a frame with equal priority is already queued for transmission.
     pub async fn flush_any(&self) {
         Self::flush_any_inner().await
     }
diff --git a/embassy-stm32/src/can/bxcan/registers.rs b/embassy-stm32/src/can/bxcan/registers.rs
index 446f3ad6f..a0519cd1f 100644
--- a/embassy-stm32/src/can/bxcan/registers.rs
+++ b/embassy-stm32/src/can/bxcan/registers.rs
@@ -181,46 +181,85 @@ impl Registers {
         None
     }
 
+    /// Enables or disables FIFO scheduling of outgoing mailboxes.
+    ///
+    /// If this is enabled, mailboxes are scheduled based on the time when the transmit request bit of the mailbox was set.
+    ///
+    /// If this is disabled, mailboxes are scheduled based on the priority of the frame in the mailbox.
+    pub fn set_tx_fifo_scheduling(&mut self, enabled: bool) {
+        self.0.mcr().modify(|w| w.set_txfp(enabled))
+    }
+
+    /// Checks if FIFO scheduling of outgoing mailboxes is enabled.
+    pub fn tx_fifo_scheduling_enabled(&self) -> bool {
+        self.0.mcr().read().txfp()
+    }
+
     /// Puts a CAN frame in a transmit mailbox for transmission on the bus.
     ///
-    /// Frames are transmitted to the bus based on their priority (see [`FramePriority`]).
-    /// Transmit order is preserved for frames with identical priority.
+    /// The behavior of this function depends on wheter or not FIFO scheduling is enabled.
+    /// See [`Self::set_tx_fifo_scheduling()`] and [`Self::tx_fifo_scheduling_enabled()`].
+    ///
+    /// # Priority based scheduling
+    ///
+    /// If FIFO scheduling is disabled, frames are transmitted to the bus based on their
+    /// priority (see [`FramePriority`]). Transmit order is preserved for frames with identical
+    /// priority.
     ///
     /// If all transmit mailboxes are full, and `frame` has a higher priority than the
     /// lowest-priority message in the transmit mailboxes, transmission of the enqueued frame is
     /// cancelled and `frame` is enqueued instead. The frame that was replaced is returned as
     /// [`TransmitStatus::dequeued_frame`].
+    ///
+    /// # FIFO scheduling
+    ///
+    /// If FIFO scheduling is enabled, frames are transmitted in the order that they are passed to this function.
+    ///
+    /// If all transmit mailboxes are full, this function returns [`nb::Error::WouldBlock`].
     pub fn transmit(&mut self, frame: &Frame) -> nb::Result<TransmitStatus, Infallible> {
+        // Check if FIFO scheduling is enabled.
+        let fifo_scheduling = self.0.mcr().read().txfp();
+
         // Get the index of the next free mailbox or the one with the lowest priority.
         let tsr = self.0.tsr().read();
         let idx = tsr.code() as usize;
 
         let frame_is_pending = !tsr.tme(0) || !tsr.tme(1) || !tsr.tme(2);
-        let pending_frame = if frame_is_pending {
-            // High priority frames are transmitted first by the mailbox system.
-            // Frames with identical identifier shall be transmitted in FIFO order.
-            // The controller schedules pending frames of same priority based on the
-            // mailbox index instead. As a workaround check all pending mailboxes
-            // and only accept higher priority frames.
+        let all_frames_are_pending = !tsr.tme(0) && !tsr.tme(1) && !tsr.tme(2);
+
+        let pending_frame;
+        if fifo_scheduling && all_frames_are_pending {
+            // FIFO scheduling is enabled and all mailboxes are full.
+            // We will not drop a lower priority frame, we just report WouldBlock.
+            return Err(nb::Error::WouldBlock);
+        } else if !fifo_scheduling && frame_is_pending {
+            // Priority scheduling is enabled and alteast one mailbox is full.
+            //
+            // In this mode, the peripheral transmits high priority frames first.
+            // Frames with identical priority should be transmitted in FIFO order,
+            // but the controller schedules pending frames of same priority based on the
+            // mailbox index. As a workaround check all pending mailboxes and only accept
+            // higher priority frames.
             self.check_priority(0, frame.id().into())?;
             self.check_priority(1, frame.id().into())?;
             self.check_priority(2, frame.id().into())?;
 
-            let all_frames_are_pending = !tsr.tme(0) && !tsr.tme(1) && !tsr.tme(2);
             if all_frames_are_pending {
                 // No free mailbox is available. This can only happen when three frames with
                 // ascending priority (descending IDs) were requested for transmission and all
                 // of them are blocked by bus traffic with even higher priority.
                 // To prevent a priority inversion abort and replace the lowest priority frame.
-                self.read_pending_mailbox(idx)
+                pending_frame = self.read_pending_mailbox(idx);
             } else {
                 // There was a free mailbox.
-                None
+                pending_frame = None;
             }
         } else {
-            // All mailboxes are available: Send frame without performing any checks.
-            None
-        };
+            // Either we have FIFO scheduling and at-least one free mailbox,
+            // or we have priority scheduling and all mailboxes are free.
+            // No further checks are needed.
+            pending_frame = None
+        }
 
         self.write_mailbox(idx, frame);