diff options
| author | Dario Nieuwenhuis <[email protected]> | 2024-05-28 11:12:37 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-05-28 11:12:37 +0000 |
| commit | 000b022ae2e52e9abaabbd10110b4c583fe4344c (patch) | |
| tree | 9986e9e7ec1f2feba794de4361c7b52b77729ef4 /embassy-stm32 | |
| parent | 34bc439f174ed95cf2e733c7276e314361fb50da (diff) | |
| parent | 807e573994d046d0cd00e631db111fafd2627559 (diff) | |
Merge pull request #2988 from de-vri-es/bxcan-tx-fifo-scheduling
embassy_stm32: implement optional FIFO scheduling for outgoing frames
Diffstat (limited to 'embassy-stm32')
| -rw-r--r-- | embassy-stm32/src/can/bxcan/mod.rs | 45 | ||||
| -rw-r--r-- | embassy-stm32/src/can/bxcan/registers.rs | 75 |
2 files changed, 98 insertions, 22 deletions
diff --git a/embassy-stm32/src/can/bxcan/mod.rs b/embassy-stm32/src/can/bxcan/mod.rs index 912634b84..0ac4cdab6 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. |
| @@ -307,7 +324,13 @@ impl<'d, T: Instance> Can<'d, T> { | |||
| 307 | 324 | ||
| 308 | /// Attempts to transmit a frame without blocking. | 325 | /// Attempts to transmit a frame without blocking. |
| 309 | /// | 326 | /// |
| 310 | /// Returns [Err(TryWriteError::Full)] if all transmit mailboxes are full. | 327 | /// Returns [Err(TryWriteError::Full)] if the frame can not be queued for transmission now. |
| 328 | /// | ||
| 329 | /// If FIFO scheduling is enabled, any empty mailbox will be used. | ||
| 330 | /// | ||
| 331 | /// Otherwise, the frame will only be accepted if there is no frame with the same priority already queued. | ||
| 332 | /// This is done to work around a hardware limitation that could lead to out-of-order delivery | ||
| 333 | /// of frames with the same priority. | ||
| 311 | pub fn try_write(&mut self, frame: &Frame) -> Result<TransmitStatus, TryWriteError> { | 334 | pub fn try_write(&mut self, frame: &Frame) -> Result<TransmitStatus, TryWriteError> { |
| 312 | self.split().0.try_write(frame) | 335 | self.split().0.try_write(frame) |
| 313 | } | 336 | } |
| @@ -318,6 +341,11 @@ impl<'d, T: Instance> Can<'d, T> { | |||
| 318 | } | 341 | } |
| 319 | 342 | ||
| 320 | /// Waits until any of the transmit mailboxes become empty | 343 | /// Waits until any of the transmit mailboxes become empty |
| 344 | /// | ||
| 345 | /// Note that [`Self::try_write()`] may fail with [`TryWriteError::Full`], | ||
| 346 | /// even after the future returned by this function completes. | ||
| 347 | /// This will happen if FIFO scheduling of outgoing frames is not enabled, | ||
| 348 | /// and a frame with equal priority is already queued for transmission. | ||
| 321 | pub async fn flush_any(&self) { | 349 | pub async fn flush_any(&self) { |
| 322 | CanTx::<T>::flush_any_inner().await | 350 | CanTx::<T>::flush_any_inner().await |
| 323 | } | 351 | } |
| @@ -465,7 +493,13 @@ impl<'d, T: Instance> CanTx<'d, T> { | |||
| 465 | 493 | ||
| 466 | /// Attempts to transmit a frame without blocking. | 494 | /// Attempts to transmit a frame without blocking. |
| 467 | /// | 495 | /// |
| 468 | /// Returns [Err(TryWriteError::Full)] if all transmit mailboxes are full. | 496 | /// Returns [Err(TryWriteError::Full)] if the frame can not be queued for transmission now. |
| 497 | /// | ||
| 498 | /// If FIFO scheduling is enabled, any empty mailbox will be used. | ||
| 499 | /// | ||
| 500 | /// Otherwise, the frame will only be accepted if there is no frame with the same priority already queued. | ||
| 501 | /// This is done to work around a hardware limitation that could lead to out-of-order delivery | ||
| 502 | /// of frames with the same priority. | ||
| 469 | pub fn try_write(&mut self, frame: &Frame) -> Result<TransmitStatus, TryWriteError> { | 503 | pub fn try_write(&mut self, frame: &Frame) -> Result<TransmitStatus, TryWriteError> { |
| 470 | Registers(T::regs()).transmit(frame).map_err(|_| TryWriteError::Full) | 504 | Registers(T::regs()).transmit(frame).map_err(|_| TryWriteError::Full) |
| 471 | } | 505 | } |
| @@ -505,6 +539,11 @@ impl<'d, T: Instance> CanTx<'d, T> { | |||
| 505 | } | 539 | } |
| 506 | 540 | ||
| 507 | /// Waits until any of the transmit mailboxes become empty | 541 | /// Waits until any of the transmit mailboxes become empty |
| 542 | /// | ||
| 543 | /// Note that [`Self::try_write()`] may fail with [`TryWriteError::Full`], | ||
| 544 | /// even after the future returned by this function completes. | ||
| 545 | /// This will happen if FIFO scheduling of outgoing frames is not enabled, | ||
| 546 | /// and a frame with equal priority is already queued for transmission. | ||
| 508 | pub async fn flush_any(&self) { | 547 | pub async fn flush_any(&self) { |
| 509 | Self::flush_any_inner().await | 548 | Self::flush_any_inner().await |
| 510 | } | 549 | } |
diff --git a/embassy-stm32/src/can/bxcan/registers.rs b/embassy-stm32/src/can/bxcan/registers.rs index 446f3ad6f..ad27e0744 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 | // frames with a different priority. | ||
| 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 | ||
| @@ -237,18 +276,16 @@ impl Registers { | |||
| 237 | } | 276 | } |
| 238 | 277 | ||
| 239 | /// Returns `Ok` when the mailbox is free or if it contains pending frame with a | 278 | /// Returns `Ok` when the mailbox is free or if it contains pending frame with a |
| 240 | /// lower priority (higher ID) than the identifier `id`. | 279 | /// different priority from the identifier `id`. |
| 241 | fn check_priority(&self, idx: usize, id: IdReg) -> nb::Result<(), Infallible> { | 280 | fn check_priority(&self, idx: usize, id: IdReg) -> nb::Result<(), Infallible> { |
| 242 | // Read the pending frame's id to check its priority. | 281 | // Read the pending frame's id to check its priority. |
| 243 | assert!(idx < 3); | 282 | assert!(idx < 3); |
| 244 | let tir = &self.0.tx(idx).tir().read(); | 283 | let tir = &self.0.tx(idx).tir().read(); |
| 245 | //let tir = &can.tx[idx].tir.read(); | ||
| 246 | 284 | ||
| 247 | // Check the priority by comparing the identifiers. But first make sure the | 285 | // Check the priority by comparing the identifiers. But first make sure the |
| 248 | // frame has not finished the transmission (`TXRQ` == 0) in the meantime. | 286 | // frame has not finished the transmission (`TXRQ` == 0) in the meantime. |
| 249 | if tir.txrq() && id <= IdReg::from_register(tir.0) { | 287 | if tir.txrq() && id == IdReg::from_register(tir.0) { |
| 250 | // There's a mailbox whose priority is higher or equal | 288 | // There's a mailbox whose priority is equal to the priority of the new frame. |
| 251 | // the priority of the new frame. | ||
| 252 | return Err(nb::Error::WouldBlock); | 289 | return Err(nb::Error::WouldBlock); |
| 253 | } | 290 | } |
| 254 | 291 | ||
