diff options
| author | Maarten de Vries <[email protected]> | 2024-05-22 16:26:14 +0200 |
|---|---|---|
| committer | Maarten de Vries <[email protected]> | 2024-05-27 16:10:08 +0200 |
| commit | 854ae5da8fde2b2b33b6ad3c5de1a1d441b4d6b3 (patch) | |
| tree | 1ddb3516c703a0abc22e94d75e46924b4bd30d89 | |
| parent | 8b9e2efec25ae36041038b70f608fe55f5061a1f (diff) | |
embassy_stm32: implement optional FIFO scheduling for outgoing frames
| -rw-r--r-- | embassy-stm32/src/can/bxcan/mod.rs | 29 | ||||
| -rw-r--r-- | 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> { | |||
| 133 | self | 133 | self |
| 134 | } | 134 | } |
| 135 | 135 | ||
| 136 | /// Enables or disables automatic retransmission of messages. | 136 | /// Enables or disables automatic retransmission of frames. |
| 137 | /// | 137 | /// |
| 138 | /// If this is enabled, the CAN peripheral will automatically try to retransmit each frame | 138 | /// If this is enabled, the CAN peripheral will automatically try to retransmit each frame |
| 139 | /// until it can be sent. Otherwise, it will try only once to send each frame. | 139 | /// 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> { | |||
| 298 | T::regs().ier().modify(|i| i.set_slkie(false)); | 298 | T::regs().ier().modify(|i| i.set_slkie(false)); |
| 299 | } | 299 | } |
| 300 | 300 | ||
| 301 | /// Enable FIFO scheduling of outgoing frames. | ||
| 302 | /// | ||
| 303 | /// If this is enabled, frames will be transmitted in the order that they are passed to | ||
| 304 | /// [`write()`][Self::write] or [`try_write()`][Self::try_write()]. | ||
| 305 | /// | ||
| 306 | /// If this is disabled, frames are transmitted in order of priority. | ||
| 307 | /// | ||
| 308 | /// FIFO scheduling is disabled by default. | ||
| 309 | pub fn set_tx_fifo_scheduling(&mut self, enabled: bool) { | ||
| 310 | Registers(T::regs()).set_tx_fifo_scheduling(enabled) | ||
| 311 | } | ||
| 312 | |||
| 313 | /// Checks if FIFO scheduling of outgoing frames is enabled. | ||
| 314 | pub fn tx_fifo_scheduling_enabled(&self) -> bool { | ||
| 315 | Registers(T::regs()).tx_fifo_scheduling_enabled() | ||
| 316 | } | ||
| 317 | |||
| 301 | /// Queues the message to be sent. | 318 | /// Queues the message to be sent. |
| 302 | /// | 319 | /// |
| 303 | /// If the TX queue is full, this will wait until there is space, therefore exerting backpressure. | 320 | /// 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> { | |||
| 318 | } | 335 | } |
| 319 | 336 | ||
| 320 | /// Waits until any of the transmit mailboxes become empty | 337 | /// Waits until any of the transmit mailboxes become empty |
| 338 | /// | ||
| 339 | /// Note that [`Self::try_write()`] may fail with [`TryWriteError::Full`], | ||
| 340 | /// even after the future returned by this function completes. | ||
| 341 | /// This will happen if FIFO scheduling of outgoing frames is not enabled, | ||
| 342 | /// and a frame with equal priority is already queued for transmission. | ||
| 321 | pub async fn flush_any(&self) { | 343 | pub async fn flush_any(&self) { |
| 322 | CanTx::<T>::flush_any_inner().await | 344 | CanTx::<T>::flush_any_inner().await |
| 323 | } | 345 | } |
| @@ -505,6 +527,11 @@ impl<'d, T: Instance> CanTx<'d, T> { | |||
| 505 | } | 527 | } |
| 506 | 528 | ||
| 507 | /// Waits until any of the transmit mailboxes become empty | 529 | /// Waits until any of the transmit mailboxes become empty |
| 530 | /// | ||
| 531 | /// Note that [`Self::try_write()`] may fail with [`TryWriteError::Full`], | ||
| 532 | /// even after the future returned by this function completes. | ||
| 533 | /// This will happen if FIFO scheduling of outgoing frames is not enabled, | ||
| 534 | /// and a frame with equal priority is already queued for transmission. | ||
| 508 | pub async fn flush_any(&self) { | 535 | pub async fn flush_any(&self) { |
| 509 | Self::flush_any_inner().await | 536 | Self::flush_any_inner().await |
| 510 | } | 537 | } |
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 { | |||
| 181 | None | 181 | None |
| 182 | } | 182 | } |
| 183 | 183 | ||
| 184 | /// Enables or disables FIFO scheduling of outgoing mailboxes. | ||
| 185 | /// | ||
| 186 | /// If this is enabled, mailboxes are scheduled based on the time when the transmit request bit of the mailbox was set. | ||
| 187 | /// | ||
| 188 | /// If this is disabled, mailboxes are scheduled based on the priority of the frame in the mailbox. | ||
| 189 | pub fn set_tx_fifo_scheduling(&mut self, enabled: bool) { | ||
| 190 | self.0.mcr().modify(|w| w.set_txfp(enabled)) | ||
| 191 | } | ||
| 192 | |||
| 193 | /// Checks if FIFO scheduling of outgoing mailboxes is enabled. | ||
| 194 | pub fn tx_fifo_scheduling_enabled(&self) -> bool { | ||
| 195 | self.0.mcr().read().txfp() | ||
| 196 | } | ||
| 197 | |||
| 184 | /// Puts a CAN frame in a transmit mailbox for transmission on the bus. | 198 | /// Puts a CAN frame in a transmit mailbox for transmission on the bus. |
| 185 | /// | 199 | /// |
| 186 | /// Frames are transmitted to the bus based on their priority (see [`FramePriority`]). | 200 | /// The behavior of this function depends on wheter or not FIFO scheduling is enabled. |
| 187 | /// Transmit order is preserved for frames with identical priority. | 201 | /// See [`Self::set_tx_fifo_scheduling()`] and [`Self::tx_fifo_scheduling_enabled()`]. |
| 202 | /// | ||
| 203 | /// # Priority based scheduling | ||
| 204 | /// | ||
| 205 | /// If FIFO scheduling is disabled, frames are transmitted to the bus based on their | ||
| 206 | /// priority (see [`FramePriority`]). Transmit order is preserved for frames with identical | ||
| 207 | /// priority. | ||
| 188 | /// | 208 | /// |
| 189 | /// If all transmit mailboxes are full, and `frame` has a higher priority than the | 209 | /// If all transmit mailboxes are full, and `frame` has a higher priority than the |
| 190 | /// lowest-priority message in the transmit mailboxes, transmission of the enqueued frame is | 210 | /// lowest-priority message in the transmit mailboxes, transmission of the enqueued frame is |
| 191 | /// cancelled and `frame` is enqueued instead. The frame that was replaced is returned as | 211 | /// cancelled and `frame` is enqueued instead. The frame that was replaced is returned as |
| 192 | /// [`TransmitStatus::dequeued_frame`]. | 212 | /// [`TransmitStatus::dequeued_frame`]. |
| 213 | /// | ||
| 214 | /// # FIFO scheduling | ||
| 215 | /// | ||
| 216 | /// If FIFO scheduling is enabled, frames are transmitted in the order that they are passed to this function. | ||
| 217 | /// | ||
| 218 | /// If all transmit mailboxes are full, this function returns [`nb::Error::WouldBlock`]. | ||
| 193 | pub fn transmit(&mut self, frame: &Frame) -> nb::Result<TransmitStatus, Infallible> { | 219 | pub fn transmit(&mut self, frame: &Frame) -> nb::Result<TransmitStatus, Infallible> { |
| 220 | // Check if FIFO scheduling is enabled. | ||
| 221 | let fifo_scheduling = self.0.mcr().read().txfp(); | ||
| 222 | |||
| 194 | // Get the index of the next free mailbox or the one with the lowest priority. | 223 | // Get the index of the next free mailbox or the one with the lowest priority. |
| 195 | let tsr = self.0.tsr().read(); | 224 | let tsr = self.0.tsr().read(); |
| 196 | let idx = tsr.code() as usize; | 225 | let idx = tsr.code() as usize; |
| 197 | 226 | ||
| 198 | let frame_is_pending = !tsr.tme(0) || !tsr.tme(1) || !tsr.tme(2); | 227 | let frame_is_pending = !tsr.tme(0) || !tsr.tme(1) || !tsr.tme(2); |
| 199 | let pending_frame = if frame_is_pending { | 228 | let all_frames_are_pending = !tsr.tme(0) && !tsr.tme(1) && !tsr.tme(2); |
| 200 | // High priority frames are transmitted first by the mailbox system. | 229 | |
| 201 | // Frames with identical identifier shall be transmitted in FIFO order. | 230 | let pending_frame; |
| 202 | // The controller schedules pending frames of same priority based on the | 231 | if fifo_scheduling && all_frames_are_pending { |
| 203 | // mailbox index instead. As a workaround check all pending mailboxes | 232 | // FIFO scheduling is enabled and all mailboxes are full. |
| 204 | // and only accept higher priority frames. | 233 | // We will not drop a lower priority frame, we just report WouldBlock. |
| 234 | return Err(nb::Error::WouldBlock); | ||
| 235 | } else if !fifo_scheduling && frame_is_pending { | ||
| 236 | // Priority scheduling is enabled and alteast one mailbox is full. | ||
| 237 | // | ||
| 238 | // In this mode, the peripheral transmits high priority frames first. | ||
| 239 | // Frames with identical priority should be transmitted in FIFO order, | ||
| 240 | // but the controller schedules pending frames of same priority based on the | ||
| 241 | // mailbox index. As a workaround check all pending mailboxes and only accept | ||
| 242 | // higher priority frames. | ||
| 205 | self.check_priority(0, frame.id().into())?; | 243 | self.check_priority(0, frame.id().into())?; |
| 206 | self.check_priority(1, frame.id().into())?; | 244 | self.check_priority(1, frame.id().into())?; |
| 207 | self.check_priority(2, frame.id().into())?; | 245 | self.check_priority(2, frame.id().into())?; |
| 208 | 246 | ||
| 209 | let all_frames_are_pending = !tsr.tme(0) && !tsr.tme(1) && !tsr.tme(2); | ||
| 210 | if all_frames_are_pending { | 247 | if all_frames_are_pending { |
| 211 | // No free mailbox is available. This can only happen when three frames with | 248 | // No free mailbox is available. This can only happen when three frames with |
| 212 | // ascending priority (descending IDs) were requested for transmission and all | 249 | // ascending priority (descending IDs) were requested for transmission and all |
| 213 | // of them are blocked by bus traffic with even higher priority. | 250 | // of them are blocked by bus traffic with even higher priority. |
| 214 | // To prevent a priority inversion abort and replace the lowest priority frame. | 251 | // To prevent a priority inversion abort and replace the lowest priority frame. |
| 215 | self.read_pending_mailbox(idx) | 252 | pending_frame = self.read_pending_mailbox(idx); |
| 216 | } else { | 253 | } else { |
| 217 | // There was a free mailbox. | 254 | // There was a free mailbox. |
| 218 | None | 255 | pending_frame = None; |
| 219 | } | 256 | } |
| 220 | } else { | 257 | } else { |
| 221 | // All mailboxes are available: Send frame without performing any checks. | 258 | // Either we have FIFO scheduling and at-least one free mailbox, |
| 222 | None | 259 | // or we have priority scheduling and all mailboxes are free. |
| 223 | }; | 260 | // No further checks are needed. |
| 261 | pending_frame = None | ||
| 262 | } | ||
| 224 | 263 | ||
| 225 | self.write_mailbox(idx, frame); | 264 | self.write_mailbox(idx, frame); |
| 226 | 265 | ||
