aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2023-04-19 21:36:04 +0000
committerGitHub <[email protected]>2023-04-19 21:36:04 +0000
commit41e90e22e2c62e67f98459755bc0ad113b40c596 (patch)
tree7cb6d0c88e912453cae9abcbfc7f69491dd94d67
parent26f4d7d28369e9f9329b3bf34568f213e56ad86d (diff)
parent64b80c2e4d390b16a384e197f1c7ed2a9a6fc946 (diff)
Merge #1370
1370: stm32/i2c: fix races when using dma. r=Dirbaio a=xoviat This change addresses two races: 1. It removes the `chunks_transferred` state variable that is modified inside the interrupt. Analysis of the code reveals that the only time the waker can be woken is when `chunks_transferred` is incremented. Therefore, waking is enough to signal the `poll_fn` that the `chunks_transferred` has incremented. Moving to `remaining_len` clarifies the code, since there is no need to track how many chunks are remaining. 2. It moves the start of the transfer until after the waker is registered, which could theoretically occur if the clock speed is very low, but probably never would even if this wasn't fixed. There is another race that I noticed: between writes the waker may not yet be registered. In that case, the code would simply be stuck and the `poll_fn` would never be woken. There is no way to resolve this without broadening the scope of the analysis, and this will likely never occur. Co-authored-by: xoviat <[email protected]>
-rw-r--r--embassy-stm32/src/i2c/v2.rs110
1 files changed, 49 insertions, 61 deletions
diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs
index 39e6702e5..853bc128f 100644
--- a/embassy-stm32/src/i2c/v2.rs
+++ b/embassy-stm32/src/i2c/v2.rs
@@ -1,6 +1,5 @@
1use core::cmp; 1use core::cmp;
2use core::future::poll_fn; 2use core::future::poll_fn;
3use core::sync::atomic::{AtomicUsize, Ordering};
4use core::task::Poll; 3use core::task::Poll;
5 4
6use embassy_embedded_hal::SetConfig; 5use embassy_embedded_hal::SetConfig;
@@ -35,14 +34,12 @@ impl Default for Config {
35 34
36pub struct State { 35pub struct State {
37 waker: AtomicWaker, 36 waker: AtomicWaker,
38 chunks_transferred: AtomicUsize,
39} 37}
40 38
41impl State { 39impl State {
42 pub(crate) const fn new() -> Self { 40 pub(crate) const fn new() -> Self {
43 Self { 41 Self {
44 waker: AtomicWaker::new(), 42 waker: AtomicWaker::new(),
45 chunks_transferred: AtomicUsize::new(0),
46 } 43 }
47 } 44 }
48} 45}
@@ -130,10 +127,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
130 let isr = regs.isr().read(); 127 let isr = regs.isr().read();
131 128
132 if isr.tcr() || isr.tc() { 129 if isr.tcr() || isr.tc() {
133 let state = T::state(); 130 T::state().waker.wake();
134 let transferred = state.chunks_transferred.load(Ordering::Relaxed);
135 state.chunks_transferred.store(transferred + 1, Ordering::Relaxed);
136 state.waker.wake();
137 } 131 }
138 // The flag can only be cleared by writting to nbytes, we won't do that here, so disable 132 // The flag can only be cleared by writting to nbytes, we won't do that here, so disable
139 // the interrupt 133 // the interrupt
@@ -457,12 +451,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
457 TXDMA: crate::i2c::TxDma<T>, 451 TXDMA: crate::i2c::TxDma<T>,
458 { 452 {
459 let total_len = write.len(); 453 let total_len = write.len();
460 let completed_chunks = total_len / 255;
461 let total_chunks = if completed_chunks * 255 == total_len {
462 completed_chunks
463 } else {
464 completed_chunks + 1
465 };
466 454
467 let dma_transfer = unsafe { 455 let dma_transfer = unsafe {
468 let regs = T::regs(); 456 let regs = T::regs();
@@ -480,7 +468,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
480 }; 468 };
481 469
482 let state = T::state(); 470 let state = T::state();
483 state.chunks_transferred.store(0, Ordering::Relaxed);
484 let mut remaining_len = total_len; 471 let mut remaining_len = total_len;
485 472
486 let on_drop = OnDrop::new(|| { 473 let on_drop = OnDrop::new(|| {
@@ -495,33 +482,35 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
495 } 482 }
496 }); 483 });
497 484
498 // NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers
499 if first_slice {
500 unsafe {
501 Self::master_write(
502 address,
503 total_len.min(255),
504 Stop::Software,
505 (total_chunks != 1) || !last_slice,
506 &check_timeout,
507 )?;
508 }
509 } else {
510 unsafe {
511 Self::master_continue(total_len.min(255), (total_chunks != 1) || !last_slice, &check_timeout)?;
512 T::regs().cr1().modify(|w| w.set_tcie(true));
513 }
514 }
515
516 poll_fn(|cx| { 485 poll_fn(|cx| {
517 state.waker.register(cx.waker()); 486 state.waker.register(cx.waker());
518 let chunks_transferred = state.chunks_transferred.load(Ordering::Relaxed);
519 487
520 if chunks_transferred == total_chunks { 488 let isr = unsafe { T::regs().isr().read() };
489 if remaining_len == total_len {
490 // NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers
491 if first_slice {
492 unsafe {
493 Self::master_write(
494 address,
495 total_len.min(255),
496 Stop::Software,
497 (total_len > 255) || !last_slice,
498 &check_timeout,
499 )?;
500 }
501 } else {
502 unsafe {
503 Self::master_continue(total_len.min(255), (total_len > 255) || !last_slice, &check_timeout)?;
504 T::regs().cr1().modify(|w| w.set_tcie(true));
505 }
506 }
507 } else if !(isr.tcr() || isr.tc()) {
508 // poll_fn was woken without an interrupt present
509 return Poll::Pending;
510 } else if remaining_len == 0 {
521 return Poll::Ready(Ok(())); 511 return Poll::Ready(Ok(()));
522 } else if chunks_transferred != 0 { 512 } else {
523 remaining_len = remaining_len.saturating_sub(255); 513 let last_piece = (remaining_len <= 255) && last_slice;
524 let last_piece = (chunks_transferred + 1 == total_chunks) && last_slice;
525 514
526 // NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers 515 // NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers
527 unsafe { 516 unsafe {
@@ -531,6 +520,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
531 T::regs().cr1().modify(|w| w.set_tcie(true)); 520 T::regs().cr1().modify(|w| w.set_tcie(true));
532 } 521 }
533 } 522 }
523
524 remaining_len = remaining_len.saturating_sub(255);
534 Poll::Pending 525 Poll::Pending
535 }) 526 })
536 .await?; 527 .await?;
@@ -559,12 +550,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
559 RXDMA: crate::i2c::RxDma<T>, 550 RXDMA: crate::i2c::RxDma<T>,
560 { 551 {
561 let total_len = buffer.len(); 552 let total_len = buffer.len();
562 let completed_chunks = total_len / 255;
563 let total_chunks = if completed_chunks * 255 == total_len {
564 completed_chunks
565 } else {
566 completed_chunks + 1
567 };
568 553
569 let dma_transfer = unsafe { 554 let dma_transfer = unsafe {
570 let regs = T::regs(); 555 let regs = T::regs();
@@ -580,7 +565,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
580 }; 565 };
581 566
582 let state = T::state(); 567 let state = T::state();
583 state.chunks_transferred.store(0, Ordering::Relaxed);
584 let mut remaining_len = total_len; 568 let mut remaining_len = total_len;
585 569
586 let on_drop = OnDrop::new(|| { 570 let on_drop = OnDrop::new(|| {
@@ -593,27 +577,29 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
593 } 577 }
594 }); 578 });
595 579
596 // NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers
597 unsafe {
598 Self::master_read(
599 address,
600 total_len.min(255),
601 Stop::Software,
602 total_chunks != 1,
603 restart,
604 &check_timeout,
605 )?;
606 }
607
608 poll_fn(|cx| { 580 poll_fn(|cx| {
609 state.waker.register(cx.waker()); 581 state.waker.register(cx.waker());
610 let chunks_transferred = state.chunks_transferred.load(Ordering::Relaxed);
611 582
612 if chunks_transferred == total_chunks { 583 let isr = unsafe { T::regs().isr().read() };
584 if remaining_len == total_len {
585 // NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers
586 unsafe {
587 Self::master_read(
588 address,
589 total_len.min(255),
590 Stop::Software,
591 total_len > 255,
592 restart,
593 &check_timeout,
594 )?;
595 }
596 } else if !(isr.tcr() || isr.tc()) {
597 // poll_fn was woken without an interrupt present
598 return Poll::Pending;
599 } else if remaining_len == 0 {
613 return Poll::Ready(Ok(())); 600 return Poll::Ready(Ok(()));
614 } else if chunks_transferred != 0 { 601 } else {
615 remaining_len = remaining_len.saturating_sub(255); 602 let last_piece = remaining_len <= 255;
616 let last_piece = chunks_transferred + 1 == total_chunks;
617 603
618 // NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers 604 // NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers
619 unsafe { 605 unsafe {
@@ -623,6 +609,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
623 T::regs().cr1().modify(|w| w.set_tcie(true)); 609 T::regs().cr1().modify(|w| w.set_tcie(true));
624 } 610 }
625 } 611 }
612
613 remaining_len = remaining_len.saturating_sub(255);
626 Poll::Pending 614 Poll::Pending
627 }) 615 })
628 .await?; 616 .await?;