1046: embassy-stm32: Fix bug when Uart::read future is dropped and DMA request was not stopped r=lulf a=guillaume-michel

fixes #1045 

regression was introduced with PR #1031

Co-authored-by: Guillaume MICHEL <guillaume@squaremind.io>
This commit is contained in:
bors[bot] 2022-11-07 17:48:04 +00:00 committed by GitHub
commit c53614f057
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -6,6 +6,8 @@ use core::task::Poll;
use atomic_polyfill::{compiler_fence, Ordering};
use embassy_cortex_m::interrupt::InterruptExt;
use embassy_futures::select::{select, Either};
use embassy_hal_common::drop::OnDrop;
use embassy_hal_common::{into_ref, PeripheralRef};
use crate::dma::NoDma;
@ -85,6 +87,13 @@ pub enum Error {
BufferTooLong,
}
enum ReadCompletionEvent {
// DMA Read transfer completed first
DmaCompleted,
// Idle line detected first
Idle,
}
pub struct Uart<'d, T: BasicInstance, TxDma = NoDma, RxDma = NoDma> {
tx: UartTx<'d, T, TxDma>,
rx: UartRx<'d, T, RxDma>,
@ -385,30 +394,50 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
self.inner_read(buffer, true).await
}
async fn inner_read(&mut self, buffer: &mut [u8], enable_idle_line_detection: bool) -> Result<usize, Error>
async fn inner_read_run(
&mut self,
buffer: &mut [u8],
enable_idle_line_detection: bool,
) -> Result<ReadCompletionEvent, Error>
where
RxDma: crate::usart::RxDma<T>,
{
if buffer.is_empty() {
return Ok(0);
} else if buffer.len() > 0xFFFF {
return Err(Error::BufferTooLong);
}
let r = T::regs();
let buffer_len = buffer.len();
// make sure USART state is restored to neutral state when this future is dropped
let _drop = OnDrop::new(move || {
// defmt::trace!("Clear all USART interrupts and DMA Read Request");
// clear all interrupts and DMA Rx Request
// SAFETY: only clears Rx related flags
unsafe {
r.cr1().modify(|w| {
// disable RXNE interrupt
w.set_rxneie(false);
// disable parity interrupt
w.set_peie(false);
// disable idle line interrupt
w.set_idleie(false);
});
r.cr3().modify(|w| {
// disable Error Interrupt: (Frame error, Noise error, Overrun error)
w.set_eie(false);
// disable DMA Rx Request
w.set_dmar(false);
});
}
});
let ch = &mut self.rx_dma;
let request = ch.request();
// Start USART DMA
// will not do anything yet because DMAR is not yet set
// future which will complete when DMA Read request completes
let transfer = crate::dma::read(ch, request, rdr(T::regs()), buffer);
// SAFETY: The only way we might have a problem is using split rx and tx
// here we only modify or read Rx related flags, interrupts and DMA channel
unsafe {
// Start USART DMA
// will not do anything yet because DMAR is not yet set
ch.start_read(request, rdr(r), buffer, Default::default());
// clear ORE flag just before enabling DMA Rx Request: can be mandatory for the second transfer
if !self.detect_previous_overrun {
let sr = sr(r).read();
@ -443,9 +472,7 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
// something went wrong
// because the only way to get this flag cleared is to have an interrupt
// abort DMA transfer
ch.request_stop();
while ch.is_running() {}
// DMA will be stopped when transfer is dropped
let sr = sr(r).read();
// This read also clears the error and idle interrupt flags on v1.
@ -468,26 +495,30 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
unreachable!();
}
// clear idle flag
if enable_idle_line_detection {
let sr = sr(r).read();
// This read also clears the error and idle interrupt flags on v1.
rdr(r).read_volatile();
clear_interrupt_flags(r, sr);
if !enable_idle_line_detection {
transfer.await;
// enable idle interrupt
r.cr1().modify(|w| {
w.set_idleie(true);
});
return Ok(ReadCompletionEvent::DmaCompleted);
}
// clear idle flag
let sr = sr(r).read();
// This read also clears the error and idle interrupt flags on v1.
rdr(r).read_volatile();
clear_interrupt_flags(r, sr);
// enable idle interrupt
r.cr1().modify(|w| {
w.set_idleie(true);
});
}
compiler_fence(Ordering::SeqCst);
let res = poll_fn(move |cx| {
// future which completes when idle line is detected
let idle = poll_fn(move |cx| {
let s = T::state();
ch.set_waker(cx.waker());
s.rx_waker.register(cx.waker());
// SAFETY: read only and we only use Rx related flags
@ -507,10 +538,6 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
if has_errors {
// all Rx interrupts and Rx DMA Request have already been cleared in interrupt handler
// stop dma transfer
ch.request_stop();
while ch.is_running() {}
if sr.pe() {
return Poll::Ready(Err(Error::Parity));
}
@ -525,45 +552,54 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
}
}
if enable_idle_line_detection && sr.idle() {
// Idle line
// stop dma transfer
ch.request_stop();
while ch.is_running() {}
let n = buffer_len - (ch.remaining_transfers() as usize);
return Poll::Ready(Ok(n));
} else if !ch.is_running() {
// DMA complete
return Poll::Ready(Ok(buffer_len));
if sr.idle() {
// Idle line detected
return Poll::Ready(Ok(()));
}
Poll::Pending
})
.await;
});
// clear all interrupts and DMA Rx Request
// SAFETY: only clears Rx related flags
unsafe {
r.cr1().modify(|w| {
// disable RXNE interrupt
w.set_rxneie(false);
// disable parity interrupt
w.set_peie(false);
// disable idle line interrupt
w.set_idleie(false);
});
r.cr3().modify(|w| {
// disable Error Interrupt: (Frame error, Noise error, Overrun error)
w.set_eie(false);
// disable DMA Rx Request
w.set_dmar(false);
});
// wait for the first of DMA request or idle line detected to completes
// select consumes its arguments
// when transfer is dropped, it will stop the DMA request
match select(transfer, idle).await {
// DMA transfer completed first
Either::First(()) => Ok(ReadCompletionEvent::DmaCompleted),
// Idle line detected first
Either::Second(Ok(())) => Ok(ReadCompletionEvent::Idle),
// error occurred
Either::Second(Err(e)) => Err(e),
}
}
async fn inner_read(&mut self, buffer: &mut [u8], enable_idle_line_detection: bool) -> Result<usize, Error>
where
RxDma: crate::usart::RxDma<T>,
{
if buffer.is_empty() {
return Ok(0);
} else if buffer.len() > 0xFFFF {
return Err(Error::BufferTooLong);
}
res
let buffer_len = buffer.len();
// wait for DMA to complete or IDLE line detection if requested
let res = self.inner_read_run(buffer, enable_idle_line_detection).await;
let ch = &mut self.rx_dma;
match res {
Ok(ReadCompletionEvent::DmaCompleted) => Ok(buffer_len),
Ok(ReadCompletionEvent::Idle) => {
let n = buffer_len - (ch.remaining_transfers() as usize);
Ok(n)
}
Err(e) => Err(e),
}
}
}