From 9f28c7ab8d96d19a010246318dee1de73b3ed4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Sat, 2 Jan 2021 22:40:36 +0100 Subject: [PATCH 1/7] uarte: Do not spin when stopping a receive future Spinning on drop() is still required when the future has not been stopped so that DMA finishes before the buffer is released. --- embassy-nrf/src/uarte.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index 648298b84..494b92df3 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -313,7 +313,7 @@ where { fn drop(self: &mut Self) { if self.uarte.rx_started() { - trace!("stoprx"); + trace!("stoprx (drop)"); self.uarte.instance.events_rxstarted.reset(); self.uarte @@ -364,9 +364,20 @@ where T: Instance, { /// Stops the ongoing reception and returns the number of bytes received. - pub async fn stop(self) -> usize { - drop(self); - let len = T::state().rx_done.wait().await; + pub async fn stop(mut self) -> usize { + let len = if self.uarte.rx_started() { + trace!("stoprx (stop)"); + + self.uarte.instance.events_rxstarted.reset(); + self.uarte + .instance + .tasks_stoprx + .write(|w| unsafe { w.bits(1) }); + T::state().rx_done.wait().await + } else { + // Transfer was stopped before it even started. No bytes were sent. + 0 + }; len as _ } } From a7c03e4cb6a9d39d0139cb88d224a0f80fd21820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Sun, 3 Jan 2021 11:12:11 +0100 Subject: [PATCH 2/7] uarte: Only stop RX forcefully when a reception is running The STOPRX task always triggers a timeout of ~55bit times until the RXTO event is generated. Before we disabled the receiver only after the timeout. With this change the receiver is stopped right after reception has ended because the DMA buffer is full. For forced RX aborts like `stop()` or on drop still need to wait for the RXTO event before disabling the receiver. --- embassy-nrf/src/uarte.rs | 43 ++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index 494b92df3..895ac11c3 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -170,6 +170,13 @@ where trace!("endrx"); let len = uarte.rxd.amount.read().bits(); compiler_fence(Ordering::SeqCst); + + if uarte.events_rxstarted.read().bits() != 0 { + // The ENDRX was signal triggered because DMA buffer is full. + uarte.events_rxstarted.reset(); + try_disable = true; + } + T::state().rx_done.signal(len); } @@ -227,6 +234,7 @@ impl embassy::uart::Uart for Uarte { // `mem::forget()` on a previous future after polling it once. assert!(!self.rx_started()); + T::state().rx_done.reset(); self.enable(); ReceiveFuture { @@ -334,27 +342,28 @@ where fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let Self { uarte, buf } = unsafe { self.get_unchecked_mut() }; - if !uarte.rx_started() { - let uarte = &uarte.instance; + match T::state().rx_done.poll_wait(cx) { + Poll::Pending if !uarte.rx_started() => { + let uarte = &uarte.instance; - T::state().rx_done.reset(); + let ptr = buf.as_ptr(); + let len = buf.len(); + assert!(len <= EASY_DMA_SIZE); - let ptr = buf.as_ptr(); - let len = buf.len(); - assert!(len <= EASY_DMA_SIZE); + compiler_fence(Ordering::SeqCst); + uarte.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + uarte + .rxd + .maxcnt + .write(|w| unsafe { w.maxcnt().bits(len as _) }); - compiler_fence(Ordering::SeqCst); - uarte.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - uarte - .rxd - .maxcnt - .write(|w| unsafe { w.maxcnt().bits(len as _) }); - - trace!("startrx"); - uarte.tasks_startrx.write(|w| unsafe { w.bits(1) }); + trace!("startrx"); + uarte.tasks_startrx.write(|w| unsafe { w.bits(1) }); + Poll::Pending + } + Poll::Pending => Poll::Pending, + Poll::Ready(_) => Poll::Ready(Ok(())), } - - T::state().rx_done.poll_wait(cx).map(|_| Ok(())) } } From a3b3305b8e9afbedde58e6c44c112335b25fa176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Sun, 3 Jan 2021 12:02:03 +0100 Subject: [PATCH 3/7] uarte: Only stop TX forcefully when a transmissions is running This comes with insignificant power consumption improvements but makes the code of the RX and TX case symmetric. --- embassy-nrf/src/uarte.rs | 46 +++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index 895ac11c3..80c31b671 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -156,6 +156,13 @@ where uarte.events_endtx.reset(); trace!("endtx"); compiler_fence(Ordering::SeqCst); + + if uarte.events_txstarted.read().bits() != 0 { + // The ENDTX was signal triggered because DMA has finished. + uarte.events_txstarted.reset(); + try_disable = true; + } + T::state().tx_done.signal(()); } @@ -211,6 +218,7 @@ impl embassy::uart::Uart for Uarte { // `mem::forget()` on a previous future after polling it once. assert!(!self.tx_started()); + T::state().tx_done.reset(); self.enable(); SendFuture { @@ -281,28 +289,28 @@ where fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let Self { uarte, buf } = unsafe { self.get_unchecked_mut() }; - if !uarte.tx_started() { - let uarte = &uarte.instance; + match T::state().tx_done.poll_wait(cx) { + Poll::Pending if !uarte.tx_started() => { + let uarte = &uarte.instance; + let ptr = buf.as_ptr(); + let len = buf.len(); + assert!(len <= EASY_DMA_SIZE); + // TODO: panic if buffer is not in SRAM - T::state().tx_done.reset(); + compiler_fence(Ordering::SeqCst); + uarte.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + uarte + .txd + .maxcnt + .write(|w| unsafe { w.maxcnt().bits(len as _) }); - let ptr = buf.as_ptr(); - let len = buf.len(); - assert!(len <= EASY_DMA_SIZE); - // TODO: panic if buffer is not in SRAM - - compiler_fence(Ordering::SeqCst); - uarte.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - uarte - .txd - .maxcnt - .write(|w| unsafe { w.maxcnt().bits(len as _) }); - - trace!("starttx"); - uarte.tasks_starttx.write(|w| unsafe { w.bits(1) }); + trace!("starttx"); + uarte.tasks_starttx.write(|w| unsafe { w.bits(1) }); + Poll::Pending + } + Poll::Pending => Poll::Pending, + Poll::Ready(_) => Poll::Ready(Ok(())), } - - T::state().tx_done.poll_wait(cx).map(|()| Ok(())) } } From 85ec9dd16fb5649900722228e46e9c68370901a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Sun, 3 Jan 2021 12:09:51 +0100 Subject: [PATCH 4/7] uarte: Be on safe side with potentially racy code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PS does not specify how many cycles it takes for a STARTXX task to generate a XXSTARTED event. I think it is instantaneous but let’s be on the safe side for the following sequence: 1. poll() starttx 2. drop() txstarted not yet set, but future gets dropped 3. txstarted set by hardware, peripheral enabled after it was dropped --- embassy-nrf/src/uarte.rs | 55 +++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index 80c31b671..156dbbefd 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -289,27 +289,31 @@ where fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let Self { uarte, buf } = unsafe { self.get_unchecked_mut() }; - match T::state().tx_done.poll_wait(cx) { - Poll::Pending if !uarte.tx_started() => { - let uarte = &uarte.instance; - let ptr = buf.as_ptr(); - let len = buf.len(); - assert!(len <= EASY_DMA_SIZE); - // TODO: panic if buffer is not in SRAM + if T::state().tx_done.poll_wait(cx).is_pending() { + let ptr = buf.as_ptr(); + let len = buf.len(); + assert!(len <= EASY_DMA_SIZE); + // TODO: panic if buffer is not in SRAM - compiler_fence(Ordering::SeqCst); - uarte.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - uarte - .txd - .maxcnt - .write(|w| unsafe { w.maxcnt().bits(len as _) }); + compiler_fence(Ordering::SeqCst); + uarte + .instance + .txd + .ptr + .write(|w| unsafe { w.ptr().bits(ptr as u32) }); + uarte + .instance + .txd + .maxcnt + .write(|w| unsafe { w.maxcnt().bits(len as _) }); - trace!("starttx"); - uarte.tasks_starttx.write(|w| unsafe { w.bits(1) }); - Poll::Pending - } - Poll::Pending => Poll::Pending, - Poll::Ready(_) => Poll::Ready(Ok(())), + trace!("starttx"); + uarte.instance.tasks_starttx.write(|w| unsafe { w.bits(1) }); + while !uarte.tx_started() {} // Make sure transmission has started + + Poll::Pending + } else { + Poll::Ready(Ok(())) } } } @@ -352,21 +356,26 @@ where match T::state().rx_done.poll_wait(cx) { Poll::Pending if !uarte.rx_started() => { - let uarte = &uarte.instance; - let ptr = buf.as_ptr(); let len = buf.len(); assert!(len <= EASY_DMA_SIZE); compiler_fence(Ordering::SeqCst); - uarte.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); uarte + .instance + .rxd + .ptr + .write(|w| unsafe { w.ptr().bits(ptr as u32) }); + uarte + .instance .rxd .maxcnt .write(|w| unsafe { w.maxcnt().bits(len as _) }); trace!("startrx"); - uarte.tasks_startrx.write(|w| unsafe { w.bits(1) }); + uarte.instance.tasks_startrx.write(|w| unsafe { w.bits(1) }); + while !uarte.rx_started() {} // Make sure reception has started + Poll::Pending } Poll::Pending => Poll::Pending, From 9b1f7b8a17bd1ebd61e68c5d2a2116794d31cfa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Sun, 3 Jan 2021 13:31:33 +0100 Subject: [PATCH 5/7] uarte: Enable peripheral with first poll This fixes a lockup when a future is dropped before it was polled. --- embassy-nrf/src/uarte.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index 156dbbefd..aee91f803 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -219,7 +219,6 @@ impl embassy::uart::Uart for Uarte { assert!(!self.tx_started()); T::state().tx_done.reset(); - self.enable(); SendFuture { uarte: self, @@ -243,7 +242,6 @@ impl embassy::uart::Uart for Uarte { assert!(!self.rx_started()); T::state().rx_done.reset(); - self.enable(); ReceiveFuture { uarte: self, @@ -257,7 +255,7 @@ pub struct SendFuture<'a, T> where T: Instance, { - uarte: &'a Uarte, + uarte: &'a mut Uarte, buf: &'a [u8], } @@ -295,6 +293,8 @@ where assert!(len <= EASY_DMA_SIZE); // TODO: panic if buffer is not in SRAM + uarte.enable(); + compiler_fence(Ordering::SeqCst); uarte .instance @@ -323,7 +323,7 @@ pub struct ReceiveFuture<'a, T> where T: Instance, { - uarte: &'a Uarte, + uarte: &'a mut Uarte, buf: &'a mut [u8], } @@ -359,6 +359,8 @@ where let ptr = buf.as_ptr(); let len = buf.len(); assert!(len <= EASY_DMA_SIZE); + + uarte.enable(); compiler_fence(Ordering::SeqCst); uarte From 93780fa31d3c563e05fac6cd6ff8f15e43dc80fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Sun, 3 Jan 2021 13:56:13 +0100 Subject: [PATCH 6/7] uarte: Wait for the peripheral to be disabled Prevents a panic in the case of: 1. Abort a receive future 2. Free Uarte::free() 3. Uarte::new() -> panicked at 'assertion failed: uarte.enable.read().enable().is_disabled()' --- embassy-nrf/src/uarte.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index aee91f803..dc2093c38 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -131,6 +131,8 @@ where } pub fn free(self) -> (T, T::Interrupt, Pins) { + // Wait for the peripheral to be disabled from the ISR. + while self.instance.enable.read().enable().is_enabled() {} (self.instance, self.irq, self.pins) } From 0631623b511d2906993bf2fa3799ea7eec3d7f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Kr=C3=B6ger?= Date: Sun, 3 Jan 2021 17:05:04 +0100 Subject: [PATCH 7/7] uarte: Low power wait for RX drop --- embassy-nrf/src/uarte.rs | 13 ++++++++----- embassy-nrf/src/util/mod.rs | 10 ++++++++++ embassy/src/util/signal.rs | 9 ++------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index dc2093c38..bfebf5cab 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -17,8 +17,8 @@ use crate::hal::gpio::Port as GpioPort; use crate::hal::pac; use crate::hal::prelude::*; use crate::hal::target_constants::EASY_DMA_SIZE; -use crate::interrupt; use crate::interrupt::OwnedInterrupt; +use crate::{interrupt, util}; pub use crate::hal::uarte::Pins; // Re-export SVD variants to allow user to directly set values. @@ -275,7 +275,9 @@ where .instance .tasks_stoptx .write(|w| unsafe { w.bits(1) }); - T::state().tx_done.blocking_wait(); + + // TX is stopped almost instantly, spinning is fine. + while !T::state().tx_done.signaled() {} } } } @@ -342,7 +344,8 @@ where .instance .tasks_stoprx .write(|w| unsafe { w.bits(1) }); - T::state().rx_done.blocking_wait(); + + util::low_power_wait_until(|| T::state().rx_done.signaled()) } } } @@ -361,7 +364,7 @@ where let ptr = buf.as_ptr(); let len = buf.len(); assert!(len <= EASY_DMA_SIZE); - + uarte.enable(); compiler_fence(Ordering::SeqCst); @@ -394,7 +397,7 @@ where T: Instance, { /// Stops the ongoing reception and returns the number of bytes received. - pub async fn stop(mut self) -> usize { + pub async fn stop(self) -> usize { let len = if self.uarte.rx_started() { trace!("stoprx (stop)"); diff --git a/embassy-nrf/src/util/mod.rs b/embassy-nrf/src/util/mod.rs index 2fd5453d3..cf3306545 100644 --- a/embassy-nrf/src/util/mod.rs +++ b/embassy-nrf/src/util/mod.rs @@ -1,2 +1,12 @@ pub mod peripheral; pub mod ring_buffer; + +/// Low power blocking wait loop using WFE/SEV. +pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) { + while !condition() { + // WFE might "eat" an event that would have otherwise woken the executor. + cortex_m::asm::wfe(); + } + // Retrigger an event to be transparent to the executor. + cortex_m::asm::sev(); +} diff --git a/embassy/src/util/signal.rs b/embassy/src/util/signal.rs index cb6410611..8e778d1e3 100644 --- a/embassy/src/util/signal.rs +++ b/embassy/src/util/signal.rs @@ -63,12 +63,7 @@ impl Signal { futures::future::poll_fn(move |cx| self.poll_wait(cx)) } - /// Blocks until the signal has been received. - /// - /// Returns immediately when [`poll_wait()`] has not been called before. - pub fn blocking_wait(&self) { - while cortex_m::interrupt::free(|_| { - matches!(unsafe { &*self.state.get() }, State::Waiting(_)) - }) {} + pub fn signaled(&self) -> bool { + cortex_m::interrupt::free(|_| matches!(unsafe { &*self.state.get() }, State::Signaled(_))) } }