embassy_stm32: implement optional FIFO scheduling for outgoing frames

This commit is contained in:
Maarten de Vries 2024-05-22 16:26:14 +02:00
parent 8b9e2efec2
commit 854ae5da8f
2 changed files with 81 additions and 15 deletions

View File

@ -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
}

View File

@ -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);